Cut the comment bloat from the Podman playbooks
Comment blocks now stay within three lines and explain only the non-obvious. Long histories, include examples, and commented-out code are gone. No task, key, or value changed; the only data change is comment text inside the generated unit files.
This commit is contained in:
1 parent
2aab1aec1e
commit
476a6d7cd0
85 files changed
+372
-2433
No files matched your search
@@ -1,31 +1,5 @@
|
||||
---
|
||||
# Audiobookshelf as a rootless Podman Quadlet unit, behind the shared Caddy unit
|
||||
# (see caddy/playbook.yml).
|
||||
#
|
||||
# Unlike bitwarden/ and tandoor/, this app has no database to attach: its state
|
||||
# is one SQLite file plus files under ~/podman-data/audiobookshelf, so there is
|
||||
# no postgres role here and nothing to restore from the shared cluster.
|
||||
#
|
||||
# Connects as the container user and runs everything as that user: the Quadlet
|
||||
# units belong to it, so the connection's PAM session is what makes
|
||||
# `systemctl --user` work without any environment juggling.
|
||||
#
|
||||
# gather_facts is required: podman_user and the home directory come from the
|
||||
# user_id / user_dir facts.
|
||||
#
|
||||
# The rootless-Podman prerequisites (installing podman, enabling lingering, the
|
||||
# low-port sysctl, creating the Quadlet directory) are deliberately NOT applied
|
||||
# here. They are the shared task file at caddy/roles/podman/tasks/main.yaml, and
|
||||
# a host that has already run caddy/playbook.yml has them:
|
||||
#
|
||||
# - name: Rootless Podman prerequisites
|
||||
# ansible.builtin.include_tasks: "{{ playbook_dir }}/../caddy/roles/podman/tasks/main.yaml"
|
||||
#
|
||||
# The variables this play needs are duplicated at the top of vars/default.yml, so
|
||||
# it stands on its own. (Applying the commented include above would also need
|
||||
# podman_unprivileged_port_start, which lives in caddy/vars/default.yml.)
|
||||
#
|
||||
# Run with: ansible-playbook -u tanshu audiobookshelf/playbook.yml
|
||||
# Requires caddy to have run first. State is SQLite, not Postgres.
|
||||
- name: Deploy Audiobookshelf as a rootless Podman Quadlet
|
||||
hosts: emily
|
||||
gather_facts: true
|
||||
@@ -33,18 +7,10 @@
|
||||
- vars/default.yml
|
||||
|
||||
pre_tasks:
|
||||
# This must happen before the app unit is started, not after: the generated
|
||||
# unit references {{ network_name }}.network, and Quadlet fails *unit
|
||||
# generation* (so `systemctl --user start` reports "unit not found") when a
|
||||
# referenced .network file does not exist yet. The same include writes the
|
||||
# Caddy-side drop-in that attaches Caddy to this network.
|
||||
#
|
||||
# network_name is a play var, so the include can see it directly.
|
||||
# Quadlet needs the network unit before it generates the app unit.
|
||||
- name: Register Audiobookshelf's network with Caddy
|
||||
ansible.builtin.include_tasks: "{{ playbook_dir }}/../caddy/roles/register/tasks/main.yaml"
|
||||
|
||||
roles:
|
||||
# The app: the four data directories, the Quadlet unit, start + verify.
|
||||
- audiobookshelf
|
||||
# Appends this site's block to the shared Caddyfile and reloads Caddy.
|
||||
- caddy
|
||||
@@ -1,9 +1,4 @@
|
||||
---
|
||||
# Audiobookshelf as a rootless Podman Quadlet unit.
|
||||
#
|
||||
# Everything here runs as {{ podman_user }} — the connection user. No become, no
|
||||
# XDG_RUNTIME_DIR: the SSH connection gave us a real PAM session, so systemd's
|
||||
# user manager is reachable through the standard environment.
|
||||
|
||||
- name: Ensure the Audiobookshelf data directories exist
|
||||
ansible.builtin.file:
|
||||
@@ -13,9 +8,7 @@
|
||||
group: "{{ podman_user }}"
|
||||
mode: '0755'
|
||||
loop:
|
||||
# config/ and metadata/ are the irreplaceable 22 MB — absdatabase.sqlite,
|
||||
# migrations, backups, cache, logs. audiobooks/ and podcasts/ are the
|
||||
# library, which the app reads and podcast downloads are written into.
|
||||
# config/ and metadata/ hold irreplaceable state; the media directories can be re-downloaded.
|
||||
- "{{ config_dir }}"
|
||||
- "{{ metadata_dir }}"
|
||||
- "{{ audiobooks_dir }}"
|
||||
@@ -41,9 +34,7 @@
|
||||
state: started
|
||||
daemon_reload: true
|
||||
rescue:
|
||||
# systemd reports "control process exited with error code" and nothing
|
||||
# else, which is useless for the usual failures (an unattached network, an
|
||||
# unwritable /config, a bad volume). Pull the real reason out.
|
||||
# systemd reports only a generic exit error, so collect the real reason.
|
||||
- name: Collect Audiobookshelf diagnostics
|
||||
ansible.builtin.command: "{{ item }}"
|
||||
loop:
|
||||
@@ -62,18 +53,15 @@
|
||||
ansible.builtin.fail:
|
||||
msg: "Audiobookshelf did not start — see the diagnostics above."
|
||||
|
||||
# Quadlet honours [Install] WantedBy= when it generates the unit, so
|
||||
# `systemctl --user enable` is neither needed nor possible on a generated unit.
|
||||
# Quadlet honours WantedBy=, so systemctl enable is neither needed nor possible on a generated unit.
|
||||
- name: Verify Audiobookshelf is running
|
||||
ansible.builtin.command: "systemctl --user is-active {{ unit_name }}"
|
||||
register: audiobookshelf_active
|
||||
changed_when: false
|
||||
failed_when: audiobookshelf_active.stdout != 'active'
|
||||
|
||||
# `is-active` says the container is running; it does not say the app can serve.
|
||||
# `Restart=always` means even a crash-looping container is reported active. The
|
||||
# image ships no HEALTHCHECK, so the unit defines one (HealthCmd=, see the
|
||||
# template) and this runs exactly that command once, synchronously.
|
||||
# is-active still reports active for a crash-looping container, and the image ships no HEALTHCHECK.
|
||||
# The unit defines one instead, and this runs exactly that command once.
|
||||
- name: Wait for Audiobookshelf to pass its health check
|
||||
ansible.builtin.command: "podman healthcheck run {{ container_name }}"
|
||||
register: audiobookshelf_health
|
||||
@@ -88,10 +76,7 @@
|
||||
changed_when: false
|
||||
failed_when: audiobookshelf_mounts.rc != 0
|
||||
|
||||
# The health check proves the server started — it exits 1 with EROFS if /config
|
||||
# is not writable — but not that /config is the *bind mount*. This does: the
|
||||
# database the app has just created has to appear on the host, not inside the
|
||||
# container's writable layer.
|
||||
# The health check proves the server started, not that /config is the bind mount.
|
||||
- name: Check the database landed on the host
|
||||
ansible.builtin.stat:
|
||||
path: "{{ config_dir }}/absdatabase.sqlite"
|
||||
@@ -106,10 +91,7 @@
|
||||
container, visible on the host, so the bind mount is real.
|
||||
when: audiobookshelf_db.stat.exists
|
||||
|
||||
# This is the check that catches a broken or missing Volume= line, which is the
|
||||
# one failure here that is silent: the app would run perfectly well with its
|
||||
# data in the container's writable layer, and lose all of it on the next
|
||||
# recreate.
|
||||
# A missing Volume= would let the app write to the container layer and lose the data on recreate.
|
||||
- name: Fail if the database is not on the host
|
||||
ansible.builtin.assert:
|
||||
that: audiobookshelf_db.stat.exists
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
---
|
||||
# Caddy is its own playbook (caddy/playbook.yml) and a rootless Quadlet unit
|
||||
# there. These handlers run as the connection user, so `systemctl --user` and
|
||||
# `podman exec` work without any XDG_RUNTIME_DIR juggling.
|
||||
|
||||
# Notified by this role when the Caddyfile changes. A reload keeps existing
|
||||
# connections; a restart would briefly drop every hosted site.
|
||||
#
|
||||
# Note the config path: /etc/caddy/Caddyfile is the mount inside the container,
|
||||
# not {{ caddyfile_path }}, which is the host side of it.
|
||||
# A reload keeps live connections; a restart would drop every hosted site.
|
||||
# The path is inside the container, not the host {{ caddyfile_path }}.
|
||||
- name: Reload Caddy configuration
|
||||
ansible.builtin.command: >-
|
||||
podman exec {{ caddy_name }} caddy reload --config /etc/caddy/Caddyfile
|
||||
changed_when: true
|
||||
listen: "Reload Caddy"
|
||||
|
||||
# Notified by caddy/roles/register when this service's network unit, or the
|
||||
# drop-in that attaches Caddy to it, is written. Quadlet has no live equivalent
|
||||
# of `docker network connect`, so the container has to be recreated — a brief
|
||||
# restart of every hosted site, not just this one. See caddy/readme.md,
|
||||
# "Trade-off worth knowing".
|
||||
#
|
||||
# Defined after the reload above so that when both are notified the cheap
|
||||
# config reload happens first.
|
||||
# Quadlet cannot attach a network live, so Caddy must be recreated.
|
||||
# Defined after the reload so the cheap reload runs first when both are notified.
|
||||
- name: Restart Caddy
|
||||
ansible.builtin.systemd_service:
|
||||
name: "{{ caddy_name }}.service"
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
---
|
||||
# Append this site's block to the shared Caddyfile.
|
||||
#
|
||||
# The Caddyfile is a bind mount into the Caddy container, so Caddy sees the
|
||||
# change as soon as the file is written; the handler only asks Caddy to re-read
|
||||
# it. That is a reload, not a restart, so the other hosted sites are untouched.
|
||||
#
|
||||
# blockinfile is idempotent on its own via the marker, so there is no need to
|
||||
# slurp the file and compare first — which also matters because slurp *fails*
|
||||
# when the Caddyfile does not exist yet, on a host where the Caddy playbook has
|
||||
# not run.
|
||||
|
||||
- name: "Add the {{ host }} site block to the Caddyfile"
|
||||
ansible.builtin.blockinfile:
|
||||
|
||||
@@ -1,75 +1,36 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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).
|
||||
# Derived from the connection user; requires gather_facts.
|
||||
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.
|
||||
# Fully qualified so rootless Podman never prompts for a short name; there is no TTY.
|
||||
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||
|
||||
# The Quadlet file is <network_name>.network. caddy/roles/register pins the
|
||||
# Podman network name to <network_name>_net (so: audiobookshelf.network,
|
||||
# audiobookshelf_net).
|
||||
# The Quadlet file is <network_name>.network; register pins <network_name>_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/ and metadata/ hold irreplaceable state; the media directories can be re-downloaded.
|
||||
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.
|
||||
# The image ships no HEALTHCHECK; /healthcheck is the app's own endpoint.
|
||||
# The runtime image has wget, not curl.
|
||||
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"
|
||||
|
||||
|
||||
Reference in new issue
Block a user