scaffold sMusic: Tauri v2 player core (audio/library/tags/discord/tray) + cover relay + CI
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
commit
c6d313db17
43 changed files with 9016 additions and 0 deletions
59
cover-relay/main.py
Normal file
59
cover-relay/main.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, Header, HTTPException, Request, Response
|
||||
|
||||
SECRET = os.environ.get("SMUSIC_COVER_SECRET", "")
|
||||
DATA_FILE = Path(os.environ.get("SMUSIC_COVER_FILE", "/opt/smusic-cover/cover.jpg"))
|
||||
RATE_LIMIT_PER_MINUTE = 20
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
current_cover_bytes = b""
|
||||
put_history = {}
|
||||
|
||||
if DATA_FILE.exists():
|
||||
current_cover_bytes = DATA_FILE.read_bytes()
|
||||
|
||||
|
||||
def check_rate_limit(ip):
|
||||
now = time.time()
|
||||
hits = [t for t in put_history.get(ip, []) if now - t < 60]
|
||||
hits.append(now)
|
||||
put_history[ip] = hits
|
||||
if len(hits) > RATE_LIMIT_PER_MINUTE:
|
||||
raise HTTPException(status_code=429, detail="too many uploads")
|
||||
|
||||
|
||||
@app.put("/cover")
|
||||
async def upload_cover(request: Request, x_smusic_key: str = Header(default="")):
|
||||
if not SECRET or x_smusic_key != SECRET:
|
||||
raise HTTPException(status_code=401, detail="bad key")
|
||||
|
||||
client_ip = request.client.host if request.client else "unknown"
|
||||
check_rate_limit(client_ip)
|
||||
|
||||
body = await request.body()
|
||||
if len(body) == 0 or len(body) > 5 * 1024 * 1024:
|
||||
raise HTTPException(status_code=400, detail="bad image size")
|
||||
|
||||
global current_cover_bytes
|
||||
current_cover_bytes = body
|
||||
|
||||
DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
DATA_FILE.write_bytes(body)
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/cover")
|
||||
async def get_cover():
|
||||
if not current_cover_bytes:
|
||||
raise HTTPException(status_code=404, detail="no cover yet")
|
||||
|
||||
return Response(
|
||||
content=current_cover_bytes,
|
||||
media_type="image/jpeg",
|
||||
headers={"Cache-Control": "public, max-age=86400, immutable"},
|
||||
)
|
||||
19
cover-relay/nginx-smusic-cover.conf
Normal file
19
cover-relay/nginx-smusic-cover.conf
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
server {
|
||||
listen 443 ssl;
|
||||
server_name rpc.savsis.xyz;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/rpc.savsis.xyz/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/rpc.savsis.xyz/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8090;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name rpc.savsis.xyz;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
2
cover-relay/requirements.txt
Normal file
2
cover-relay/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fastapi
|
||||
uvicorn
|
||||
15
cover-relay/smusic-cover.service
Normal file
15
cover-relay/smusic-cover.service
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=sMusic cover art relay
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=smusiccover
|
||||
WorkingDirectory=/opt/smusic-cover
|
||||
Environment=SMUSIC_COVER_SECRET=CHANGE_ME
|
||||
Environment=SMUSIC_COVER_FILE=/opt/smusic-cover/cover.jpg
|
||||
ExecStart=/opt/smusic-cover/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8090
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Add table
Add a link
Reference in a new issue