Per-host toggle for the read-only, no-auth browse of the shared upload dir,
so it can stay on for the LAN-party host (oxps) and be off on the
internet-facing one (cochi). Defaults to `on` — existing hosts unchanged.
Neither layer has conditionals, so each gets the value substituted in:
- caddy: docker-compose mounts 62-files.${FILES_BROWSE:-on}.caddy at the
fixed conf.d target (62-files.caddy -> 62-files.on.caddy, plus a
comments-only 62-files.off.caddy stub — caddy needs a parseable file
there). Same trick as FILES_MOUNT_MODE.
- nginx: render-nginx.sh splices nginx/files-browse.server.tmpl into
${FILES_BROWSE_SERVER} and adds/omits the name in the port-80 redirect.
This is the gate that matters; caddy is bound to 127.0.0.1.
Applying a change needs render-nginx.sh --install plus a caddy
--force-recreate (single-file bind mount pins the inode). With `off`,
drop `files` from LE_SUBDOMAINS on that host. upload. is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Add an env-driven `user:`/`group_add` to the filestash service plus
tooling/filestash-set-run-user.sh. Running the container as a real host
account (oxps: oier=1002) makes every file it writes into the share owned
by that account, so scp/ssh and the web UI share one owner — no chown dance.
Defaults FILES_UID/FILES_GID=1000, leaving cochi (image's own uid) untouched.
group_add 1000 keeps the 0730 image binary + 0770 /app/data usable under any
uid; the helper also chowns the filestash_state volume (1000:1000) so a
non-1000 uid doesn't crash-loop unable to write its own sqlite/config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two prod hosts now diverge on filestash auth: cochi stays htpasswd
(internet-facing), oxps runs FILES_AUTH=none/passthrough since it is
LAN-only, satisfying the internal-only gate. Document the per-host
status table and the flip-back rule for if oxps ever gains an internet
path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SPA computes its redirect origin as (force_ssl ? "https://" : "http://")
+ general.host. With host set to "https://files.${BASE_DOMAIN}" that produced
"http://https://files.ulicraft.net", so every client-side redirect broke and
the page never routed anywhere.
Server-side this was invisible: SecureOrigin strips the scheme before the
Host check, so `curl /` returned 200, the API answered, and the uptime
monitor stayed green — only real browsers failed. The tell is a
`POST /report?...msg=Redirecting to http://https://...` line in the log.
Set host to the bare hostname and force_ssl=true (which the origin needs to
build https://, and which only emits an HSTS header — it does not redirect,
so it can't loop behind the plain-http nginx→caddy hop).
Verified: origin is now "https://files.ulicraft.net"; login + ls still work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One shared login (htpasswd) for all players, writable. Config is rendered
from a template and mounted read-only, so git stays authoritative — the
admin console loads but cannot save, by design.
Filestash's OIDC/SAML middlewares are enterprise-only, so Keycloak SSO is
out; FILES_AUTH keeps htpasswd/passthrough switchable for later. Auth-off
(passthrough) is only valid once the host is off the public internet —
render-config.sh warns loudly when rendering it.
Three traps, all found by testing against the pinned image:
- the `local` backend is admin-gated: without LOCAL_BACKEND_SECRET in the
connection params every login fails with "backend error - Not Allowed"
- the htpasswd plugin only accepts $2a$ bcrypt, so a $2y$ hash from
`htpasswd -B` fails every login silently — use `openssl passwd -6`.
render-config.sh rejects the wrong format rather than shipping it
- APPLICATION_URL/ADMIN_PASSWORD env make filestash rewrite config.json at
boot, which fails on the :ro mount — both live in the rendered config
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>