--- # --------------------------------------------------------------------------- # Rootless Podman / Quadlet # --------------------------------------------------------------------------- # Duplicated on purpose from caddy/vars/default.yml: a play cannot include that # file wholesale, because it also sets `host_directory` — to the Caddy data # directory — and every playbook defines that for itself. Keep these three in # sync with caddy/vars/default.yml. (Its fourth, podman_unprivileged_port_start, # belongs to the podman tasks, which this play does not run: Yamtrack listens on # 8000 inside the container and publishes nothing host-side.) # # podman_user is derived from the connection: this play carries no become, so # the Quadlet units belong to whoever Ansible connects as. Requires gather_facts # (set in playbook.yml). podman_user: "{{ ansible_facts['user_id'] }}" podman_data_dir: "{{ ansible_facts['user_dir'] }}/podman-data" quadlet_dir: "{{ ansible_facts['user_dir'] }}/.config/containers/systemd" # --------------------------------------------------------------------------- # Yamtrack # --------------------------------------------------------------------------- host: track.tanshu.com container_name: yamtrack unit_name: "{{ container_name }}.service" # Fully qualified on purpose: rootless Podman resolves short names against # unqualified-search-registries, and there is no TTY to answer a prompt when # the systemd unit starts. ghcr.io was already qualified; the tag is explicit # for the same reason. Nothing is pinned — see readme.md, "Notes and gotchas". image: ghcr.io/fuzzygrim/yamtrack:latest # The Quadlet file is .network. caddy/roles/register pins the # Podman network name to _net (so: yamtrack.network, yamtrack_net, # which is the name the Docker-era deployment used). network_name: "{{ container_name }}" host_directory: "{{ podman_data_dir }}/{{ container_name }}" # The image's bind mount: /yamtrack/db holds db.sqlite3 and its WAL sidecars. # Nothing else on /yamtrack is state — staticfiles/ is baked into the image and # the code is read-only — so this is the only directory worth keeping. data_dir: "{{ host_directory }}/data" # The port inside the container that Caddy proxies to: the image's own nginx # listens there and forwards to gunicorn. Reproduces the Docker-era Caddyfile # entry (`reverse_proxy yamtrack:8000`) exactly. container_port: 8000 # SECRET, TZ and REDIS_URL live here rather than as Environment= lines in the # unit file: that file is world-readable in ~/.config. Read by systemd through # EnvironmentFile=, not by Ansible, so it has to be written before the unit # starts. env_file: "{{ host_directory }}/{{ container_name }}.env" # The uid this unit maps onto podman_user, and the PUID/PGID the image's # entrypoint is given. It is 0 — container root — and that is deliberate: # # 1. `--userns=keep-id` does two things: it maps one container uid onto the # connecting user, and it runs the container *as that uid*. The entrypoint's # first act is `groupmod`/`usermod`, which rewrite /etc/group and # /etc/passwd — files owned by container-root in the image. With # `keep-id:uid=1000` those run as `abc` and die on "cannot lock /etc/group" # (the unit exits 10) before supervisord is ever reached. Root is required. # 2. Mapping container root onto podman_user is what keeps # ~/podman-data/yamtrack owned by podman_user rather than by a subuid. It is # the same choice caddy, bitwarden and tandoor make. # # It is also rendered as PUID/PGID in the environment file: the entrypoint's own # default is 1000, which under this mapping is a subuid on the host, so its # `chown -R abc:abc db` would strand the database on an unmapped uid. See # readme.md, "Who runs as whom". app_uid: 0 app_gid: 0 yamtrack_tz: "Asia/Kolkata" # The Django SECRET_KEY, as the image calls it (`SECRET`). Carried over verbatim # from the Docker-era playbook so existing sessions and password-reset tokens # stay valid — rotating it logs every user out. Committed in plaintext as # before; flagged as a follow-up, not changed here. yamtrack_secret: "c14c5970eb728fbf8b2325fd3004069d" # What the old container passed as URLS, which Yamtrack turns into its CSRF # trusted origins. ALLOWED_HOSTS is deliberately not set: its default is `*`, # which is what the Docker-era container relied on. urls: "https://{{ host }}" # --------------------------------------------------------------------------- # Valkey (the shared cache / Celery broker) # --------------------------------------------------------------------------- # Yamtrack hard-requires Redis: its cache backend is django_redis with no # fallback, the Celery worker and beat need a broker, and several metadata # providers talk to Redis directly. It is a *separate* playbook, deployed before # this one: yamtrack/roles/valkey only attaches its network to that shared unit. # # The Docker-era container was given `redis://valkey:6379`, and valkey is what # the container is called, so the URL is unchanged. valkey_container: valkey valkey_unit: "{{ valkey_container }}.service" redis_url: "redis://{{ valkey_container }}:6379" # --------------------------------------------------------------------------- # Caddy # --------------------------------------------------------------------------- # The shared Caddy unit and its Caddyfile, both owned by caddy/playbook.yml. caddy_name: caddy caddyfile_path: "{{ podman_data_dir }}/{{ caddy_name }}/conf/Caddyfile" snippet_block: | {{ host }} { reverse_proxy {{ container_name }}:{{ container_port }} }