Run Bitwarden as a rootless Podman Quadlet
Replaces the docker_container role on monoco with a Quadlet systemd user unit on emily, and moves state from /var/lib/bitwarden to ~/podman-data/bitwarden. Postgres is authoritative for the vault and already moved with the cluster restore, so only the file data comes across -- and the stale db.sqlite3 that has sat beside it for years is deliberately excluded from that copy. The role warns if it finds the file in the data directory rather than failing, because with DATABASE_URL set it is inert but looks exactly like a restore source. The image runs as container-root and writes /data as root, so UserNS=keep-id:uid=0,gid=0 maps that onto the connecting user and leaves nothing owned by a subuid. Both integrations go through the shared includes rather than local copies -- caddy/roles/register for the network and the Caddy drop-in, postgres/roles/attach for the database -- so this app writes the same drop-in bytes as every other service and a new service never means editing Caddy or Postgres. That is also what lets the Docker-era roles/network go: it existed to attach an already-created container to a network (docker_network's connected:/appends:), which podman_network has no equivalent for. The dead files/nginx.conf.j2 goes with it -- Caddy handles the WebSocket upgrade and imposes no request-body limit, so its 525M client_max_body_size needs no replacement. Three ordering details are load-bearing: - The register include runs in pre_tasks, before the unit is started. Quadlet fails unit *generation*, not merely startup, when a referenced .network file does not exist yet, so starting first reports "unit not found". - roles/postgres flushes handlers immediately after attaching. The restart that joins Postgres to this network is a handler, so by default it runs at the end of the play, after the app has already started and failed to resolve the database. - The play waits on the image's own health check (podman healthcheck run) rather than on systemctl is-active. Vaultwarden runs its Diesel migrations before Rocket answers, and Restart=always means even a crash-looping container is reported active. DATABASE_URL and DOMAIN move into a 0600 environment file read through EnvironmentFile=, since the Quadlet unit is world-readable and DATABASE_URL carries the password. The credentials are unchanged, so the restored database connects with no reset. readme.md carries the monoco -> emily file copy command by command.
This commit is contained in:
1 parent
4cd9308ae3
commit
9bf2cbd7a9
13 files changed
+790
-143
No files matched your search
+76
-10
@@ -1,21 +1,87 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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: Vaultwarden
|
||||
# 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"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vaultwarden (the Bitwarden-compatible server)
|
||||
# ---------------------------------------------------------------------------
|
||||
# The service keeps the Docker-era name everywhere — directory, container, unit,
|
||||
# Podman network and Postgres database are all `bitwarden`, and the Caddyfile
|
||||
# upstream is `bitwarden:80`. Only the image is `vaultwarden/server`. Renaming
|
||||
# it would touch the Caddyfile, the Postgres database and every client's saved
|
||||
# server URL, for no gain.
|
||||
host: vault.tanshu.com
|
||||
|
||||
docker_container: bitwarden
|
||||
image_name: vaultwarden/server:latest
|
||||
container_name: bitwarden
|
||||
unit_name: "{{ container_name }}.service"
|
||||
|
||||
host_directory: bitwarden
|
||||
# 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.
|
||||
image: docker.io/vaultwarden/server:latest
|
||||
|
||||
docker_network: "{{ docker_container }}_net"
|
||||
# The Quadlet file is <network_name>.network. caddy/roles/register pins the
|
||||
# Podman network name to <network_name>_net (so: bitwarden.network,
|
||||
# bitwarden_net).
|
||||
network_name: "{{ container_name }}"
|
||||
|
||||
host_directory: "{{ podman_data_dir }}/{{ container_name }}"
|
||||
|
||||
# The image's VOLUME is /data and ROCKET_PORT is 80, so this is both the bind
|
||||
# mount target and the port Caddy proxies to.
|
||||
data_dir: "{{ host_directory }}/data"
|
||||
container_port: 80
|
||||
|
||||
# DATABASE_URL carries the Postgres password, and the Quadlet unit is
|
||||
# world-readable in ~/.config, so it goes in this 0600 file instead and is read
|
||||
# by systemd through EnvironmentFile=.
|
||||
env_file: "{{ host_directory }}/bitwarden.env"
|
||||
|
||||
# The shared Postgres unit this app attaches itself to (postgres/playbook.yml).
|
||||
# roles/postgres owns this app's drop-ins there and the "Restart Postgres"
|
||||
# handler that applies them.
|
||||
postgres_container: postgres
|
||||
caddy_container: caddy
|
||||
postgres_unit: "{{ postgres_container }}.service"
|
||||
|
||||
caddyfile_path: /var/lib/caddy/conf/Caddyfile
|
||||
# The credentials the Docker-era playbook embedded in its db_url, kept as-is so
|
||||
# the already-restored `bitwarden` database and the app's own connection string
|
||||
# agree. Postgres is authoritative for this service; the SQLite file the image
|
||||
# would use by default is stale and deliberately not migrated (see readme.md).
|
||||
pg_user: postgres
|
||||
pg_password: '123456'
|
||||
pg_database: bitwarden
|
||||
|
||||
# Hostname, not an IP, and the same `postgres` name the old db_url used:
|
||||
# postgres/roles/attach joins the postgres container to bitwarden_net, which is
|
||||
# what makes this resolve. (`db`, the shared NetworkAlias, resolves too.)
|
||||
database_url: "postgresql://{{ pg_user }}:{{ pg_password }}@{{ postgres_container }}:5432/{{ pg_database }}"
|
||||
|
||||
# Vaultwarden validates that DOMAIN carries a scheme and serves its web vault
|
||||
# and API links from it. The old container set exactly this.
|
||||
domain: "https://{{ host }}"
|
||||
|
||||
# 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"
|
||||
|
||||
# No request-body limit: Caddy streams request bodies and imposes none by
|
||||
# default, so the 525M `client_max_body_size` the dead nginx front end carried
|
||||
# has no equivalent — and needs none.
|
||||
snippet_block: |
|
||||
{{ host }} {
|
||||
reverse_proxy {{ docker_container }}:80
|
||||
reverse_proxy {{ container_name }}:{{ container_port }}
|
||||
}
|
||||
|
||||
db_url: postgresql://postgres:123456@{{ postgres_container }}:5432/bitwarden
|
||||
|
||||
Reference in new issue
Block a user