Build the new Svelte client (client_v2) in CI, bake it into the Docker
image alongside the legacy React client, and serve it by default. Set
SPOOLMAN_LEGACY_CLIENT=TRUE to fall back to the old client.
SinglePageApplication is generalized to serve either client: the React
client keeps its index.html + base-path rewriting, while the Svelte
client uses a 200.html SPA fallback, serves prerendered per-route pages,
and serves relative-path assets verbatim. The tweaked-HTML store is moved
off self.html, which StaticFiles uses as its HTML-mode flag.
The Playwright frontend suite targets the React DOM, so it's pinned to
the legacy client via SPOOLMAN_LEGACY_CLIENT for now.
Claude-Session: https://claude.ai/code/session_01VQQtwPBPiMCoZCpBbbqS64
Brings in the latest #798 commit (388082f) that our earlier merge missed: it
drops --chown from the builder COPYs (matching the Podman build fix already on
master), removes the redundant `usermod -G users app`, and simplifies the
entrypoint to re-exec itself via gosu when root instead of duplicating the
uvicorn invocation. Dockerfile comment conflict resolved in favour of the
Podman-explaining comment.
Brings in jpiccari's unprivileged-Docker support (rootless entrypoint that only
uses gosu when running as root and only remaps app UID/GID when they differ, plus
dropping the redundant `groupmod -g 1000 users` in the runner stage).
Combines cleanly with the Podman build/test enablement already on master
(builder-stage --chown removal, postgres healthcheck, run.py engine selection),
giving full rootless build + run + test support.
Running tests_integration against rootless Podman surfaced three issues; none
affect the Docker/CI path:
- run.py: add a SPOOLMAN_CONTAINER_ENGINE env var (defaults to "docker") so the
build/compose commands can be driven by podman instead.
- Dockerfile: drop `--chown=app:app` from the builder-stage COPYs. The "app"
user only exists in the runner stage; Docker/BuildKit tolerated the dangling
reference but Podman rejects it. Final ownership is unchanged — it is set when
the files are copied into the runner stage.
- docker-compose-postgres.yml: add a pg_isready healthcheck and gate spoolman on
`condition: service_healthy`, matching the mariadb/cockroachdb compose files.
Without it spoolman raced postgres' initdb and died on connection-refused
before running migrations.
Verified: full 4-DB suite (sqlite/postgres/mariadb/cockroachdb) green via
`SPOOLMAN_CONTAINER_ENGINE=podman uv run poe itest` — 223 tests each.
This change enables the container to run in unprivileged mode (without
--privileged or additional capabilities) by making permission drops
conditional:
- Changed users group GID from 1000 to 1001 to avoid conflicts with
the app user's primary group (UID 1000)
- Only modify user/group IDs when PUID/PGID environment variables differ
from the default value of 1000, avoiding unnecessary privilege
requirements
- Skip su-exec entirely when running as the default user, since changing
user context requires additional capabilities that unprivileged
containers don't have
This allows the container to work in restricted environments like
Kubernetes with security contexts or rootless Docker while maintaining
backward compatibility for users who customize PUID/PGID.
Fixes Donkie#791
This proved to be extremely slow on emulated hardware to build e.g. the linux/arm/v7 image.
Since the built client is architecture agnostic, it's instead now built on the native hardware and inserted into the docker image.
Had to remove the VOLUME definition in the dockerfile for this, otherwise the data dir would always be mounted by an anonymous volume if the user didn't mount it correctly, so the python script wouldn't be able to detect it.