feat(files): move filestash to upload., serve files. as read-only static browse

Filestash cannot be served under a subpath (its SPA hardcodes /api, /assets,
/login at root), so it moves to its own subdomain upload.${BASE_DOMAIN}. The
apex-level ask was a static, no-auth file browser at files.${BASE_DOMAIN}: caddy
now file_server browse's the same ${FILES_DATA_DIR} share read-only there.

- filestash general.host -> upload.; nginx upload. vhost keeps the 2g body +
  upload streaming + /admin throttle; new files. vhost is a plain static proxy.
- caddy: 60-files.caddy split into 60-upload.caddy (filestash) + 62-files.caddy
  (file_server browse of /srv/files); caddy mounts FILES_DATA_DIR :ro.
- LE_SUBDOMAINS += upload (both upload. and files. are vhosts needing certs).

Security: files. is unconditionally public read (no auth), independent of
FILES_AUTH. The htpasswd login only gates writes via upload. Documented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
oierandClaude Opus 4.8 committed 2026-07-24 17:03:33 +02:00
1 parent 243787c530
commit 16523e4f62
13 files changed
+133 -63

No files matched your search

+9 -5
View File
@@ -8,9 +8,10 @@ on a localhost-only port (`CADDY_HTTP_BIND=127.0.0.1`, `CADDY_HTTP_PORT=8880`);
nginx reverse-proxies every public vhost to it by Host header. Roles:
1. **Reverse proxy** — `auth.` → drasl, `avatar.` → nmsr, `status.` → uptime-kuma,
`files.` → filestash.
`upload.` → filestash.
2. **Static** — apex serves the landing page + `/launcher/` downloads;
`distribution.` serves a static site from another repo.
`distribution.` serves a static site from another repo; `files.` serves a
read-only browse of the upload share.
Image: `caddy:alpine`. caddy natively reads `{$BASE_DOMAIN}` env placeholders —
no template render needed.
@@ -37,12 +38,15 @@ The Caddyfile imports per-vhost snippets from `caddy/conf.d/`:
`DISTRIBUTION_WEB_ROOT`, another repo).
- `40-avatar.caddy` — `avatar.` → `nmsr:8080` (skin/avatar renderer).
- `50-status.caddy` — `status.` → `uptime-kuma:3001` (status page).
- `60-files.caddy` — `files.` → `filestash:8334` (player file share, `20-files.md`).
- `60-upload.caddy` — `upload.` → `filestash:8334` (player file share, `20-files.md`).
- `62-files.caddy` — `files.` → static `/srv/files` (read-only, no-auth `file_server
browse` of the same `FILES_DATA_DIR` filestash writes to).
All vhosts are plain `http://…`; TLS is nginx's job. Mounts: `./www:/srv/www:ro`,
`./launcher:/srv/launcher:ro`, `${DISTRIBUTION_WEB_ROOT}:/srv/distribution:ro`.
`./launcher:/srv/launcher:ro`, `${DISTRIBUTION_WEB_ROOT}:/srv/distribution:ro`,
`${FILES_DATA_DIR}:/srv/files:ro`.
> **Don't rewrite the `Host` header for `files.`** — filestash rejects any
> **Don't rewrite the `Host` header for `upload.`** — filestash rejects any
> request whose Host doesn't match its configured `general.host`, with *"only
> traffic from X is allowed"* (a 403 that reads like an auth failure). nginx
> forwards the original Host and caddy passes it through untouched. Keep it that