Files
Anthias/bin/start_server.sh
Viktor Petersson d98e605cb5 chore(server): bake collectstatic into image, drop runtime scratch mount (#2846)
* chore(server): bake collectstatic into image, drop runtime scratch mount

Static files (admin assets + the bun-built dist/) are immutable from
image build time onward — `bin/start_server.sh` was running
`collectstatic --clear --noinput` on every container start into a host
bind-mount on /home/${USER}/anthias/staticfiles, which existed only as
a writable scratch path for collectstatic to write to. Same data, every
restart, into a directory the container itself populated.

Move the work to where it belongs:

- docker/Dockerfile.server.j2: run `collectstatic --noinput --clear`
  in the production stage, after the bun-built dist/ is COPYed in.
  Wrapped in `HOME=/tmp/anthias-build` because the Django settings
  module instantiates AnthiasSettings() at import time, which writes a
  default anthias.conf into $HOME/.anthias if one isn't there yet
  (start_server.sh seeds /data/.anthias before this same import at
  runtime; at build time the throwaway HOME is removed after the
  RUN finishes).
- src/anthias_server/django_project/settings.py: STATIC_ROOT moves
  from /data/anthias/staticfiles to /usr/src/app/staticfiles. Inside
  the container this path is now read-only — admin + collected app
  static is immutable per-image. Dev (DEBUG=True) bypasses STATIC_ROOT
  entirely via WHITENOISE_USE_FINDERS so the path doesn't have to
  exist in the dev image.
- bin/start_server.sh: drop the runtime collectstatic invocation and
  the "Generating Django static files..." progress line.
- docker-compose.yml.tmpl: drop the
  /home/${USER}/anthias/staticfiles -> /data/anthias/staticfiles
  bind-mount. The host-side directory becomes orphan state after
  upgrade — operators can `rm -rf ~/anthias/staticfiles` once the
  new image is pulled. (One of the two reasons ~/anthias has to
  persist after install. The other — runtime shell scripts in
  ~/anthias/bin/ — is tracked separately in #2845.)

Verified by building the production server image locally
(`docker buildx build --file docker/Dockerfile.server`):
- 210 static files copied to /usr/src/app/staticfiles at image build.
- Container starts, uvicorn comes up, no "Generating Django static
  files..." line.
- `curl http://localhost:8080/static/admin/css/base.css` -> HTTP 200,
  22120 bytes (matches the baked file).
- /data/anthias/ does not exist in the running container -- no
  runtime scratch dir is needed.

Refs #2845.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: address Copilot review nits

Two pure-comment fixes flagged by Copilot review on #2846:

- src/anthias_server/django_project/settings.py: "admin assets +
  collected app static is immutable" -> "admin assets and collected
  app static are immutable" (compound subject takes plural verb).
- docker/Dockerfile.server.j2: "COPYed" -> "copied" in the
  collectstatic comment block.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:43:12 +01:00

77 lines
2.9 KiB
Bash
Executable File

#!/bin/bash
ENVIRONMENT=${ENVIRONMENT:-production}
# Defensively expose legacy /data/.screenly and /data/screenly_assets
# paths as symlinks if a running setup still has them in DB rows or in
# an older docker-compose file. No-op on clean installs.
/usr/src/app/bin/migrate_in_container_paths.sh
mkdir -p \
/data/.config \
/data/.anthias \
/data/.anthias/backups \
/data/anthias_assets
cp -n /usr/src/app/ansible/roles/anthias/files/anthias.conf /data/.anthias/anthias.conf
cp -n /usr/src/app/ansible/roles/anthias/files/default_assets.yml /data/.anthias/default_assets.yml
echo "Running migration..."
# The following block ensures that the migration is transactional and that the
# database is not left in an inconsistent state if the migration fails.
if [ -f /data/.anthias/anthias.db ]; then
python -m anthias_server.manage dbbackup --noinput --clean && \
python -m anthias_server.manage migrate --fake-initial --noinput || \
python -m anthias_server.manage dbrestore --noinput
else
python -m anthias_server.manage migrate && \
python -m anthias_server.manage dbbackup --noinput --clean
fi
UVICORN_BIND_HOST="${LISTEN:-0.0.0.0}"
UVICORN_BIND_PORT="${PORT:-8080}"
# Trust X-Forwarded-* only from explicitly listed proxies. We deliberately
# do NOT default this to '*' — the IP allowlists in views_files.py rely on
# the TCP peer address, and a wildcard would let any client spoof
# REMOTE_ADDR via X-Forwarded-For. Operators who terminate TLS at a
# reverse proxy (e.g. the Caddy sidecar bin/enable_ssl.sh installs) get
# this set automatically via the compose override.
UVICORN_PROXY_ARGS=()
if [[ -n "${FORWARDED_ALLOW_IPS:-}" ]]; then
UVICORN_PROXY_ARGS=(
--proxy-headers
--forwarded-allow-ips "$FORWARDED_ALLOW_IPS"
)
fi
if [[ "$ENVIRONMENT" == "development" ]]; then
echo "Building frontend assets..."
bun install && bun run build
echo "Starting uvicorn (development, --reload)..."
# uvicorn's --reload watches *.py only by default. Add Django
# templates and built CSS so a template / SCSS edit on the host
# propagates to the running worker without a manual restart.
exec uvicorn anthias_server.django_project.asgi:application \
--host "$UVICORN_BIND_HOST" \
--port "$UVICORN_BIND_PORT" \
--timeout-keep-alive 30 \
--reload \
--reload-dir /usr/src/app \
--reload-include "*.html" \
--reload-include "*.css" \
"${UVICORN_PROXY_ARGS[@]}"
else
# collectstatic ran at image build time (docker/Dockerfile.server.j2)
# — STATIC_ROOT is baked into the read-only image layer. No runtime
# invocation needed.
echo "Starting uvicorn..."
exec uvicorn anthias_server.django_project.asgi:application \
--host "$UVICORN_BIND_HOST" \
--port "$UVICORN_BIND_PORT" \
--timeout-keep-alive 30 \
"${UVICORN_PROXY_ARGS[@]}"
fi