--- # --------------------------------------------------------------------------- # 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: Audiobookshelf # 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" # --------------------------------------------------------------------------- # Audiobookshelf # --------------------------------------------------------------------------- host: audiobooks.tanshu.com container_name: audiobookshelf unit_name: "{{ container_name }}.service" # Already fully qualified, so rootless Podman never resolves a short name # against unqualified-search-registries — there is no TTY to answer a prompt # when the systemd unit starts. image: ghcr.io/advplyr/audiobookshelf:latest # The Quadlet file is .network. caddy/roles/register pins the # Podman network name to _net (so: audiobookshelf.network, # audiobookshelf_net). network_name: "{{ container_name }}" host_directory: "{{ podman_data_dir }}/{{ container_name }}" # The four mount targets, straight from the Docker-era `docker run`. Two are # state, two are the library: # # config/ absdatabase.sqlite + migrations/ irreplaceable, 2.6 MB # metadata/ backups/, cache/, logs/, streams/ irreplaceable, 19 MB # audiobooks/ the book library 15 GB, re-downloadable media # podcasts/ downloaded podcast episodes # # The image declares NO VOLUME (verified against the published image), so these # are conventional bind-mount targets, not image volumes: without the Volume= # lines in the unit Podman creates nothing, the data lands in the container's # writable layer, and recreating the container destroys it. config_dir: "{{ host_directory }}/config" metadata_dir: "{{ host_directory }}/metadata" audiobooks_dir: "{{ host_directory }}/audiobooks" podcasts_dir: "{{ host_directory }}/podcasts" # The port inside the container that Caddy proxies to. The image sets # EXPOSE 80 and ENV PORT=80. container_port: 80 # The one setting the Docker-era container passed. TZ is not a secret, so unlike # bitwarden/ and tandoor/ this unit has no EnvironmentFile=: Audiobookshelf # generates its own JWT signing key inside config/ and needs nothing from # Ansible that has to be hidden. timezone: Asia/Kolkata # The image ships no HEALTHCHECK, so this play defines one on the unit. # /healthcheck is the app's own unauthenticated endpoint and answers 200 OK. # `wget` is what the runtime image has — `curl` exists only in its builder # stage. health_cmd: "wget -q -O /dev/null http://127.0.0.1:{{ container_port }}/healthcheck" # 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 }} }