From 784fff8fafdd3aa55a366d0a7912574840b773b7 Mon Sep 17 00:00:00 2001 From: boris Date: Fri, 10 Jul 2026 13:32:40 +0100 Subject: [PATCH] implemented compose by url, parse, local yaml. --- README.md | 28 + docs/compose-update-policy.md | 87 +++ docs/service-catalog.md | 11 +- docs/topology.md | 80 ++- docs/upstream-sources.yml | 553 ++++++++++++++++++ group_vars/.gitignore | 1 + group_vars/all.yml | 71 --- group_vars/all.yml.example | 73 +++ host_vars/.gitignore | 1 + host_vars/cloud.internal.yml.example | 29 - host_vars/edge.yml.example | 31 - host_vars/generic-downstream.yml.example | 16 +- host_vars/pelican-server.yml.example | 15 - host_vars/wings.internal.example | 29 - roles/alloy/tasks/main.yml | 1 + .../alloy/templates/compose.override.yaml.j2 | 3 + roles/alloy/templates/compose.yaml.j2 | 4 +- roles/alloy/upstream/SOURCE.md | 7 + roles/alloy/upstream/docker-compose.yml | 23 + roles/arr/handlers/.gitkeep | 1 + roles/arr/handlers/main.yml | 6 - roles/arr/tasks/main.yml | 59 +- roles/arr/templates/compose.override.yaml.j2 | 5 + roles/arr/templates/compose.yaml.j2 | 4 +- roles/arr/upstream/SOURCE.md | 7 + roles/authentik/handlers/.gitkeep | 1 + roles/authentik/handlers/main.yml | 6 - roles/authentik/tasks/main.yml | 56 +- .../templates/compose.override.yaml.j2 | 6 + roles/authentik/templates/compose.yaml.j2 | 6 +- roles/authentik/upstream/SOURCE.md | 7 + roles/authentik_ldap/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + .../authentik_ldap/templates/compose.yaml.j2 | 4 +- roles/authentik_ldap/upstream/SOURCE.md | 7 + roles/authentik_proxy/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + .../authentik_proxy/templates/compose.yaml.j2 | 4 +- roles/authentik_proxy/upstream/SOURCE.md | 7 + roles/authentik_rac/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/authentik_rac/templates/compose.yaml.j2 | 4 +- roles/authentik_rac/upstream/SOURCE.md | 7 + roles/cleanuparr/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/cleanuparr/templates/compose.yaml.j2 | 4 +- roles/cleanuparr/upstream/SOURCE.md | 7 + roles/code_server/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/code_server/templates/compose.yaml.j2 | 4 +- roles/code_server/upstream/SOURCE.md | 7 + roles/compose_project/README.md | 11 + roles/compose_project/defaults/main.yml | 20 + roles/compose_project/files/.gitkeep | 1 + roles/compose_project/handlers/main.yml | 6 + roles/compose_project/tasks/main.yml | 75 +++ roles/compose_project/templates/.gitkeep | 1 + roles/docker_app/README.md | 6 +- roles/docker_app/defaults/main.yml | 2 + roles/docker_app/handlers/.gitkeep | 1 + roles/docker_app/handlers/main.yml | 6 - roles/docker_app/tasks/main.yml | 68 +-- .../templates/compose.override.yaml.j2 | 3 + roles/docker_app/templates/compose.yaml.j2 | 4 +- roles/filebrowser_quantum/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + .../templates/compose.yaml.j2 | 4 +- roles/filebrowser_quantum/upstream/SOURCE.md | 7 + roles/fiveetools/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/fiveetools/templates/compose.yaml.j2 | 4 +- roles/fiveetools/upstream/SOURCE.md | 7 + roles/fiveetools/upstream/docker-compose.yml | 13 + roles/flaresolverr/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/flaresolverr/templates/compose.yaml.j2 | 4 +- roles/flaresolverr/upstream/SOURCE.md | 7 + roles/gitea/handlers/.gitkeep | 1 + roles/gitea/handlers/main.yml | 6 - roles/gitea/tasks/main.yml | 56 +- .../gitea/templates/compose.override.yaml.j2 | 3 + roles/gitea/templates/compose.yaml.j2 | 6 +- roles/gitea/upstream/SOURCE.md | 7 + roles/gitea_act_runner/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + .../templates/compose.yaml.j2 | 4 +- roles/gitea_act_runner/upstream/SOURCE.md | 7 + roles/grafana/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/grafana/templates/compose.yaml.j2 | 4 +- roles/grafana/upstream/SOURCE.md | 7 + roles/grafana_renderer/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + .../templates/compose.yaml.j2 | 4 +- roles/grafana_renderer/upstream/SOURCE.md | 7 + .../upstream/docker-compose.yml | 5 + roles/homarr/tasks/main.yml | 1 + .../homarr/templates/compose.override.yaml.j2 | 3 + roles/homarr/templates/compose.yaml.j2 | 4 +- roles/homarr/upstream/SOURCE.md | 7 + roles/immich/handlers/.gitkeep | 1 + roles/immich/handlers/main.yml | 6 - roles/immich/tasks/main.yml | 57 +- .../immich/templates/compose.override.yaml.j2 | 6 + roles/immich/templates/compose.yaml.j2 | 4 +- roles/immich/upstream/SOURCE.md | 7 + roles/ipv6nat/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/ipv6nat/templates/compose.yaml.j2 | 4 +- roles/ipv6nat/upstream/SOURCE.md | 7 + roles/jellyfin/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/jellyfin/templates/compose.yaml.j2 | 4 +- roles/jellyfin/upstream/SOURCE.md | 7 + roles/jellyseerr/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/jellyseerr/templates/compose.yaml.j2 | 4 +- roles/jellyseerr/upstream/SOURCE.md | 7 + roles/kavita/tasks/main.yml | 1 + .../kavita/templates/compose.override.yaml.j2 | 3 + roles/kavita/templates/compose.yaml.j2 | 4 +- roles/kavita/upstream/SOURCE.md | 7 + roles/kiwix_serve/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/kiwix_serve/templates/compose.yaml.j2 | 4 +- roles/kiwix_serve/upstream/SOURCE.md | 7 + roles/loki/tasks/main.yml | 1 + roles/loki/templates/compose.override.yaml.j2 | 3 + roles/loki/templates/compose.yaml.j2 | 4 +- roles/loki/upstream/SOURCE.md | 7 + roles/nginx_content/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/nginx_content/templates/compose.yaml.j2 | 4 +- roles/nginx_content/upstream/SOURCE.md | 7 + roles/ntfy/tasks/main.yml | 1 + roles/ntfy/templates/compose.override.yaml.j2 | 3 + roles/ntfy/templates/compose.yaml.j2 | 4 +- roles/ntfy/upstream/SOURCE.md | 7 + roles/ntfy/upstream/docker-compose.yml | 28 + roles/ocis/handlers/.gitkeep | 1 + roles/ocis/handlers/main.yml | 6 - roles/ocis/tasks/main.yml | 58 +- roles/ocis/templates/compose.override.yaml.j2 | 3 + roles/ocis/templates/compose.yaml.j2 | 4 +- roles/ocis/upstream/SOURCE.md | 7 + roles/paperless_ngx/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/paperless_ngx/templates/compose.yaml.j2 | 4 +- roles/paperless_ngx/upstream/SOURCE.md | 7 + roles/pelican_panel/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/pelican_panel/templates/compose.yaml.j2 | 4 +- roles/pelican_panel/upstream/SOURCE.md | 7 + roles/php_fpm/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/php_fpm/templates/compose.yaml.j2 | 4 +- roles/php_fpm/upstream/SOURCE.md | 7 + roles/pinchflat/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/pinchflat/templates/compose.yaml.j2 | 4 +- roles/pinchflat/upstream/SOURCE.md | 7 + roles/postgres/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/postgres/templates/compose.yaml.j2 | 4 +- roles/postgres/upstream/SOURCE.md | 7 + roles/prometheus/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/prometheus/templates/compose.yaml.j2 | 4 +- roles/prometheus/upstream/SOURCE.md | 7 + roles/pterodactyl/handlers/.gitkeep | 1 + roles/pterodactyl/handlers/main.yml | 6 - roles/pterodactyl/tasks/main.yml | 56 +- .../templates/compose.override.yaml.j2 | 5 + roles/pterodactyl/templates/compose.yaml.j2 | 4 +- roles/pterodactyl/upstream/SOURCE.md | 7 + roles/qbittorrent/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/qbittorrent/templates/compose.yaml.j2 | 4 +- roles/qbittorrent/upstream/SOURCE.md | 7 + roles/requarks/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/requarks/templates/compose.yaml.j2 | 4 +- roles/requarks/upstream/SOURCE.md | 7 + roles/requarks/upstream/docker-compose.yml | 28 + roles/syncthing/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/syncthing/templates/compose.yaml.j2 | 4 +- roles/syncthing/upstream/SOURCE.md | 7 + roles/traefik/README.md | 20 +- roles/traefik/defaults/main.yml | 6 + roles/traefik/tasks/main.yml | 51 +- .../templates/compose.override.yaml.j2 | 3 + roles/traefik/templates/compose.yaml.j2 | 5 + roles/traefik/templates/dynamic-http.yml.j2 | 81 +++ roles/traefik/templates/traefik.yml.j2 | 7 + roles/traefik/upstream/SOURCE.md | 7 + roles/transmission/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/transmission/templates/compose.yaml.j2 | 4 +- roles/transmission/upstream/SOURCE.md | 7 + roles/uptime_kuma/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/uptime_kuma/templates/compose.yaml.j2 | 4 +- roles/uptime_kuma/upstream/SOURCE.md | 7 + roles/uptime_kuma/upstream/docker-compose.yml | 10 + roles/valkey/tasks/main.yml | 1 + .../valkey/templates/compose.override.yaml.j2 | 3 + roles/valkey/templates/compose.yaml.j2 | 4 +- roles/valkey/upstream/SOURCE.md | 7 + roles/vaultwarden/tasks/main.yml | 1 + .../templates/compose.override.yaml.j2 | 3 + roles/vaultwarden/templates/compose.yaml.j2 | 4 +- roles/vaultwarden/upstream/SOURCE.md | 7 + roles/wizarr/tasks/main.yml | 1 + .../wizarr/templates/compose.override.yaml.j2 | 3 + roles/wizarr/templates/compose.yaml.j2 | 4 +- roles/wizarr/upstream/SOURCE.md | 7 + scripts/vendor-upstream-snapshots.py | 388 ++++++++++++ 218 files changed, 2271 insertions(+), 640 deletions(-) create mode 100644 docs/compose-update-policy.md create mode 100644 docs/upstream-sources.yml create mode 100644 group_vars/.gitignore create mode 100644 group_vars/all.yml.example create mode 100644 host_vars/.gitignore delete mode 100644 host_vars/cloud.internal.yml.example delete mode 100644 host_vars/edge.yml.example delete mode 100644 host_vars/pelican-server.yml.example delete mode 100644 host_vars/wings.internal.example create mode 100644 roles/alloy/templates/compose.override.yaml.j2 create mode 100644 roles/alloy/upstream/SOURCE.md create mode 100644 roles/alloy/upstream/docker-compose.yml create mode 100644 roles/arr/handlers/.gitkeep delete mode 100644 roles/arr/handlers/main.yml create mode 100644 roles/arr/templates/compose.override.yaml.j2 create mode 100644 roles/arr/upstream/SOURCE.md create mode 100644 roles/authentik/handlers/.gitkeep delete mode 100644 roles/authentik/handlers/main.yml create mode 100644 roles/authentik/templates/compose.override.yaml.j2 create mode 100644 roles/authentik/upstream/SOURCE.md create mode 100644 roles/authentik_ldap/templates/compose.override.yaml.j2 create mode 100644 roles/authentik_ldap/upstream/SOURCE.md create mode 100644 roles/authentik_proxy/templates/compose.override.yaml.j2 create mode 100644 roles/authentik_proxy/upstream/SOURCE.md create mode 100644 roles/authentik_rac/templates/compose.override.yaml.j2 create mode 100644 roles/authentik_rac/upstream/SOURCE.md create mode 100644 roles/cleanuparr/templates/compose.override.yaml.j2 create mode 100644 roles/cleanuparr/upstream/SOURCE.md create mode 100644 roles/code_server/templates/compose.override.yaml.j2 create mode 100644 roles/code_server/upstream/SOURCE.md create mode 100644 roles/compose_project/README.md create mode 100644 roles/compose_project/defaults/main.yml create mode 100644 roles/compose_project/files/.gitkeep create mode 100644 roles/compose_project/handlers/main.yml create mode 100644 roles/compose_project/tasks/main.yml create mode 100644 roles/compose_project/templates/.gitkeep create mode 100644 roles/docker_app/handlers/.gitkeep delete mode 100644 roles/docker_app/handlers/main.yml create mode 100644 roles/docker_app/templates/compose.override.yaml.j2 create mode 100644 roles/filebrowser_quantum/templates/compose.override.yaml.j2 create mode 100644 roles/filebrowser_quantum/upstream/SOURCE.md create mode 100644 roles/fiveetools/templates/compose.override.yaml.j2 create mode 100644 roles/fiveetools/upstream/SOURCE.md create mode 100644 roles/fiveetools/upstream/docker-compose.yml create mode 100644 roles/flaresolverr/templates/compose.override.yaml.j2 create mode 100644 roles/flaresolverr/upstream/SOURCE.md create mode 100644 roles/gitea/handlers/.gitkeep delete mode 100644 roles/gitea/handlers/main.yml create mode 100644 roles/gitea/templates/compose.override.yaml.j2 create mode 100644 roles/gitea/upstream/SOURCE.md create mode 100644 roles/gitea_act_runner/templates/compose.override.yaml.j2 create mode 100644 roles/gitea_act_runner/upstream/SOURCE.md create mode 100644 roles/grafana/templates/compose.override.yaml.j2 create mode 100644 roles/grafana/upstream/SOURCE.md create mode 100644 roles/grafana_renderer/templates/compose.override.yaml.j2 create mode 100644 roles/grafana_renderer/upstream/SOURCE.md create mode 100644 roles/grafana_renderer/upstream/docker-compose.yml create mode 100644 roles/homarr/templates/compose.override.yaml.j2 create mode 100644 roles/homarr/upstream/SOURCE.md create mode 100644 roles/immich/handlers/.gitkeep delete mode 100644 roles/immich/handlers/main.yml create mode 100644 roles/immich/templates/compose.override.yaml.j2 create mode 100644 roles/immich/upstream/SOURCE.md create mode 100644 roles/ipv6nat/templates/compose.override.yaml.j2 create mode 100644 roles/ipv6nat/upstream/SOURCE.md create mode 100644 roles/jellyfin/templates/compose.override.yaml.j2 create mode 100644 roles/jellyfin/upstream/SOURCE.md create mode 100644 roles/jellyseerr/templates/compose.override.yaml.j2 create mode 100644 roles/jellyseerr/upstream/SOURCE.md create mode 100644 roles/kavita/templates/compose.override.yaml.j2 create mode 100644 roles/kavita/upstream/SOURCE.md create mode 100644 roles/kiwix_serve/templates/compose.override.yaml.j2 create mode 100644 roles/kiwix_serve/upstream/SOURCE.md create mode 100644 roles/loki/templates/compose.override.yaml.j2 create mode 100644 roles/loki/upstream/SOURCE.md create mode 100644 roles/nginx_content/templates/compose.override.yaml.j2 create mode 100644 roles/nginx_content/upstream/SOURCE.md create mode 100644 roles/ntfy/templates/compose.override.yaml.j2 create mode 100644 roles/ntfy/upstream/SOURCE.md create mode 100644 roles/ntfy/upstream/docker-compose.yml create mode 100644 roles/ocis/handlers/.gitkeep delete mode 100644 roles/ocis/handlers/main.yml create mode 100644 roles/ocis/templates/compose.override.yaml.j2 create mode 100644 roles/ocis/upstream/SOURCE.md create mode 100644 roles/paperless_ngx/templates/compose.override.yaml.j2 create mode 100644 roles/paperless_ngx/upstream/SOURCE.md create mode 100644 roles/pelican_panel/templates/compose.override.yaml.j2 create mode 100644 roles/pelican_panel/upstream/SOURCE.md create mode 100644 roles/php_fpm/templates/compose.override.yaml.j2 create mode 100644 roles/php_fpm/upstream/SOURCE.md create mode 100644 roles/pinchflat/templates/compose.override.yaml.j2 create mode 100644 roles/pinchflat/upstream/SOURCE.md create mode 100644 roles/postgres/templates/compose.override.yaml.j2 create mode 100644 roles/postgres/upstream/SOURCE.md create mode 100644 roles/prometheus/templates/compose.override.yaml.j2 create mode 100644 roles/prometheus/upstream/SOURCE.md create mode 100644 roles/pterodactyl/handlers/.gitkeep delete mode 100644 roles/pterodactyl/handlers/main.yml create mode 100644 roles/pterodactyl/templates/compose.override.yaml.j2 create mode 100644 roles/pterodactyl/upstream/SOURCE.md create mode 100644 roles/qbittorrent/templates/compose.override.yaml.j2 create mode 100644 roles/qbittorrent/upstream/SOURCE.md create mode 100644 roles/requarks/templates/compose.override.yaml.j2 create mode 100644 roles/requarks/upstream/SOURCE.md create mode 100644 roles/requarks/upstream/docker-compose.yml create mode 100644 roles/syncthing/templates/compose.override.yaml.j2 create mode 100644 roles/syncthing/upstream/SOURCE.md create mode 100644 roles/traefik/templates/compose.override.yaml.j2 create mode 100644 roles/traefik/templates/dynamic-http.yml.j2 create mode 100644 roles/traefik/upstream/SOURCE.md create mode 100644 roles/transmission/templates/compose.override.yaml.j2 create mode 100644 roles/transmission/upstream/SOURCE.md create mode 100644 roles/uptime_kuma/templates/compose.override.yaml.j2 create mode 100644 roles/uptime_kuma/upstream/SOURCE.md create mode 100644 roles/uptime_kuma/upstream/docker-compose.yml create mode 100644 roles/valkey/templates/compose.override.yaml.j2 create mode 100644 roles/valkey/upstream/SOURCE.md create mode 100644 roles/vaultwarden/templates/compose.override.yaml.j2 create mode 100644 roles/vaultwarden/upstream/SOURCE.md create mode 100644 roles/wizarr/templates/compose.override.yaml.j2 create mode 100644 roles/wizarr/upstream/SOURCE.md create mode 100755 scripts/vendor-upstream-snapshots.py diff --git a/README.md b/README.md index a846eed..4ede5de 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,34 @@ instead of exposing individual host ports. See `host_vars/edge.yml.example`, `host_vars/downstream.yml.example`, and `docs/topology.md`. The full Docker service list is in `docs/service-catalog.md`. +## Compose Updates + +Compose-backed roles deploy from role-owned Jinja templates, not directly from +upstream examples. Upstream README and Compose examples are tracked as copied +snapshots under each role's `upstream/` directory and indexed in +`docs/upstream-sources.yml`. + +Use `compose_paths` for Compose files inside an upstream repository and +`compose_urls` for direct raw HTTP Compose YAML fallbacks. Use role-level +`local_compose_file` for Compose reference material already stored in this +repository. README fenced Compose blocks are parsed when no explicit Compose +file is available. + +Use `docs/compose-update-policy.md` when updating services so upstream drift, +NBOPS overrides, image pins, and generated Compose files stay reviewable in Git. + +To test upstream references for one role: + +```sh +scripts/vendor-upstream-snapshots.py jellyfin +``` + +To refresh copied upstream references: + +```sh +scripts/vendor-upstream-snapshots.py --apply jellyfin +``` + ## Bootstrap Render `templates/cloud-init-user-data.yml.j2` with host variables and pass it to diff --git a/docs/compose-update-policy.md b/docs/compose-update-policy.md new file mode 100644 index 0000000..65439ce --- /dev/null +++ b/docs/compose-update-policy.md @@ -0,0 +1,87 @@ +# Compose Update Policy + +Compose-backed services are deployed from role-owned Jinja templates. Vendored +upstream files are reference snapshots only and must not be deployed directly. + +## Layout + +Each Docker-backed role has: + +- `templates/compose.yaml.j2`: base deployed Compose file +- `templates/compose.override.yaml.j2`: NBOPS-specific local override +- `templates/env.j2`: generated environment file +- `upstream/`: copied upstream reference files + +`docs/upstream-sources.yml` is the authoritative inventory of upstream Git +repositories, README paths, Compose paths, fallback Compose URLs, local Compose +references, refs, and verification status. + +Role entries support one local fallback: + +- `local_compose_file`: repo-local Compose file to copy as role reference + material when upstream does not provide a useful Compose example. + +Repository entries support three upstream Compose discovery sources: + +- `compose_paths`: paths to Compose files inside the checked-out repository. +- `compose_urls`: direct `http` or `https` URLs that return Compose YAML. +- README fenced YAML blocks, parsed when a README is listed in `readme_paths`. + +Repository entries that only use `compose_urls` may omit `url` and `ref`. +Entries that use `readme_paths` or `compose_paths` must include both `url` and +`ref` so the script can clone the source repository. + +## Update Workflow + +1. Read `docs/upstream-sources.yml` for the role being updated. +2. Test the upstream inventory without writing snapshots: + + ```sh + scripts/vendor-upstream-snapshots.py + ``` + +3. Fetch or copy the listed upstream README and Compose files into + `roles//upstream/`: + + ```sh + scripts/vendor-upstream-snapshots.py --apply + ``` + + The script also looks for Compose-shaped fenced YAML blocks in README files. + When exactly one candidate is found, `--apply` writes it as + `compose.from-readme.yaml`. Compose YAML fetched from `compose_urls` is + written as `compose.from-url.yaml`, or `compose.from-url-N.yaml` when a + repository has multiple fallback URLs. A role-level `local_compose_file` is + copied under `roles//upstream/local/`. +4. Update `roles//upstream/SOURCE.md` with source URL, ref, snapshot date, + fetched paths, and any manual verification notes. +5. Commit upstream snapshot changes separately from NBOPS deployment template + changes. +6. Diff upstream snapshots against current role templates. +7. Apply upstream structural changes to `templates/compose.yaml.j2`. +8. Keep NBOPS-local changes in `templates/compose.override.yaml.j2`, including: + Traefik labels, internal networks, host paths, ports, restart policy, logging, + resource limits, and deployment-specific environment wiring. +9. Replace mutable image tags with pinned versions where practical. +10. Document breaking changes or operator actions in the role README. + +## Rules + +- Do not edit generated Compose files on VMs. +- Do not deploy files from `upstream/` directly. +- Do not treat `status: verify` sources as trusted until manually checked. +- Prefer pinned tags or digests over `latest` and other mutable tags. +- Keep HTTP application ports unpublished in Traefik mode; document internal + service ports as comments in generated Compose. + +## Validation + +Run after every Compose template update: + +```sh +ansible-playbook --syntax-check playbooks/site.yml +git diff --check +``` + +Later, add a render check that runs `docker compose config` against generated +`compose.yaml` plus `compose.override.yaml` for selected roles. diff --git a/docs/service-catalog.md b/docs/service-catalog.md index fb583c0..39e2520 100644 --- a/docs/service-catalog.md +++ b/docs/service-catalog.md @@ -54,10 +54,19 @@ Monitoring: - `prometheus` - `uptime_kuma` -Most roles use the shared `docker_app` implementation and support: +Compose-backed roles use the shared `compose_project` lifecycle directly or via +the `docker_app` single-container adapter. They support: - `host` routing mode: publish ports directly and open UFW rules. - `traefik` routing mode: attach to the proxy network and render Traefik labels. +- `traefik_file` routing mode: publish service ports for central edge Traefik, + without rendering Docker labels. Prefer `traefik` for normal multi-VM + deployments so edge Traefik forwards to internal Traefik, and internal Traefik + routes to applications by labels. Override role defaults in `host_vars` or `group_vars`; do not edit generated Compose files on the VM. + +Upstream README and Compose examples are tracked as copied snapshots under each +role's `upstream/` directory. See `docs/upstream-sources.yml` and +`docs/compose-update-policy.md`. diff --git a/docs/topology.md b/docs/topology.md index b995442..e4cbbd5 100644 --- a/docs/topology.md +++ b/docs/topology.md @@ -21,9 +21,9 @@ authentik_domain: auth.example.invalid ## Downstream VMs -Downstream VMs also run Docker. Instead of publishing each application port on -the host, run Traefik on the VM and attach application containers to the shared -Docker proxy network. +Downstream VMs also run Docker and normally run an internal Traefik instance. +Application containers route through that local Traefik using Docker labels and +the shared Docker proxy network. ```yaml nbops_roles: @@ -47,13 +47,85 @@ Traefik labels and join `nbops_proxy_network`, which defaults to `proxy`. Only Traefik entrypoints should be exposed on the downstream host firewall. Use `traefik_bind_address` to bind those entrypoints to the VM's internal address. +For central edge routing, the edge Traefik uses the file provider to route +machine-to-machine traffic to each downstream VM's internal Traefik entrypoint. +The internal Traefik then routes to applications using Docker labels. This keeps +container discovery local to the VM and gives the edge-to-internal leg a stable +place for future mTLS. + +Edge VM: + +```yaml +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - traefik + +traefik_docker_provider_enabled: false +traefik_file_provider_enabled: true +traefik_dynamic_inventory_group: nbops +``` + +Downstream VM: + +```yaml +nbops_service_routing_mode: traefik +nbops_traefik_entrypoint: web +nbops_traefik_tls: false + +traefik_bind_address: "10.0.10.25" +traefik_publish_entrypoints: true + +nbops_traefik_file_routes: + - name: jellyfin + domain: jellyfin.example.invalid + scheme: http + port: 80 + - name: gitea + domain: git.example.invalid + scheme: http + port: 80 +``` + +When the play runs against the edge VM, Traefik renders +`dynamic/http-services.yml` from `nbops_traefik_file_routes` declared across the +inventory group. Each route targets `ansible_host` for that inventory host by +default. Set `target_host` for a different VM address, or set `url` to provide +the complete upstream URL. + +For future mTLS between edge Traefik and internal Traefik, routes can reference a +Traefik `serversTransport`: + +```yaml +nbops_traefik_file_routes: + - name: jellyfin + domain: jellyfin.example.invalid + scheme: https + port: 443 + servers_transport: internal-mtls + +traefik_dynamic_servers_transports: + - name: internal-mtls + server_name: apps-01.example.internal + root_cas: + - /etc/pki/internal/certs/ca.crt + certificates: + - cert_file: /etc/pki/internal/certs/edge.crt + key_file: /etc/pki/internal/private/edge.key +``` + ## Routing Modes Application roles support: - `host`: publish application ports directly and open those ports in UFW. - `traefik`: do not publish HTTP ports; attach to Traefik and render labels. +- `traefik_file`: publish a service port without labels. This is available for + exceptional direct edge-to-app routes, but the preferred multi-VM topology is + edge Traefik -> internal Traefik -> application. Gitea is special: HTTP can be routed through Traefik while SSH remains published with `gitea_publish_ssh: true`. - diff --git a/docs/upstream-sources.yml b/docs/upstream-sources.yml new file mode 100644 index 0000000..b1f2885 --- /dev/null +++ b/docs/upstream-sources.yml @@ -0,0 +1,553 @@ +--- +version: 1 +policy: + vendor_method: copied_snapshots + vendor_root: roles + deployed_from: role_templates + notes: + - Upstream files are reference snapshots only and are not deployed directly. + - "Use compose_paths: [] when upstream documents Compose in README files only." + - "Use compose_urls: [] for direct raw HTTP Compose YAML fallback URLs." + - "Use role-level local_compose_file as a repo-local fallback Compose reference." + - "status: verify means the source mapping must be checked before pinning." + +sources: + alloy: + role: alloy + vendor_dir: roles/alloy/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/grafana/alloy.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + arr: + role: arr + vendor_dir: roles/arr/upstream + local_compose_file: "" + repositories: + - purpose: prowlarr-container + url: https://github.com/linuxserver/docker-prowlarr.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + - purpose: sonarr-container + url: https://github.com/linuxserver/docker-sonarr.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + - purpose: radarr-container + url: https://github.com/linuxserver/docker-radarr.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + - purpose: lidarr-container + url: https://github.com/linuxserver/docker-lidarr.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + - purpose: bazarr-container + url: https://github.com/linuxserver/docker-bazarr.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + authentik: + role: authentik + vendor_dir: roles/authentik/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/goauthentik/authentik.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://docs.goauthentik.io/compose.yml] + + authentik_ldap: + role: authentik_ldap + vendor_dir: roles/authentik_ldap/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/goauthentik/authentik.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://docs.goauthentik.io/add-secure-apps/outposts/manual-deploy-docker-compose/] + + authentik_proxy: + role: authentik_proxy + vendor_dir: roles/authentik_proxy/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/goauthentik/authentik.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://docs.goauthentik.io/add-secure-apps/outposts/manual-deploy-docker-compose/] + + authentik_rac: + role: authentik_rac + vendor_dir: roles/authentik_rac/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/goauthentik/authentik.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://docs.goauthentik.io/add-secure-apps/outposts/manual-deploy-docker-compose/] + + cleanuparr: + role: cleanuparr + vendor_dir: roles/cleanuparr/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/cleanuparr/cleanuparr.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + code_server: + role: code_server + vendor_dir: roles/code_server/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/linuxserver/docker-code-server.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + filebrowser_quantum: + role: filebrowser_quantum + status: verify + vendor_dir: roles/filebrowser_quantum/upstream + local_compose_file: "" + notes: + - Verify upstream repository for the gtstef/filebrowser image before pinning. + repositories: + - purpose: container + url: https://github.com/gtsteffaniak/filebrowser.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://filebrowserquantum.com/en/docs/user-guides/other/standalone/] + + fiveetools: + role: fiveetools + status: verify + vendor_dir: roles/fiveetools/upstream + local_compose_file: "" + notes: + - Verify upstream repository for the jafner/5etools image before pinning. + repositories: + - purpose: container + url: https://github.com/Jafner/5etools-docker.git + ref: main + readme_paths: [README.md] + compose_paths: [docker-compose.yml] + compose_urls: [] + + flaresolverr: + role: flaresolverr + vendor_dir: roles/flaresolverr/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/FlareSolverr/FlareSolverr.git + ref: master + readme_paths: [README.md] + compose_paths: [docker-compose.yml] + compose_urls: [] + + gitea: + role: gitea + vendor_dir: roles/gitea/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/go-gitea/gitea.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://docs.gitea.com/installation/install-with-docker-rootless] + + gitea_act_runner: + role: gitea_act_runner + vendor_dir: roles/gitea_act_runner/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://gitea.com/gitea/act_runner.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [https://docs.gitea.com/usage/actions/act-runner#start-the-runner-using-docker-compose] + + grafana: + role: grafana + vendor_dir: roles/grafana/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/grafana/grafana.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] # Host own, all examples are bad + + grafana_renderer: + role: grafana_renderer + vendor_dir: roles/grafana_renderer/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/grafana/grafana-image-renderer.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + homarr: + role: homarr + vendor_dir: roles/homarr/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/homarr-labs/homarr.git + ref: dev + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + immich: + role: immich + vendor_dir: roles/immich/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/immich-app/immich.git + ref: main + readme_paths: [README.md] + compose_paths: [docker/docker-compose.yml] + compose_urls: [] + + ipv6nat: + role: ipv6nat + vendor_dir: roles/ipv6nat/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/robbertkl/docker-ipv6nat.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + jellyfin: + role: jellyfin + vendor_dir: roles/jellyfin/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/linuxserver/docker-jellyfin.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + - purpose: application + url: https://github.com/jellyfin/jellyfin.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + jellyseerr: + role: jellyseerr + vendor_dir: roles/jellyseerr/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/Fallenbagel/jellyseerr.git + ref: develop + readme_paths: [README.md] + compose_paths: [docker-compose.yml] + compose_urls: [] + + kavita: + role: kavita + vendor_dir: roles/kavita/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/linuxserver/docker-kavita.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + kiwix_serve: + role: kiwix_serve + vendor_dir: roles/kiwix_serve/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/kiwix/kiwix-tools.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + loki: + role: loki + vendor_dir: roles/loki/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/grafana/loki.git + ref: main + readme_paths: [README.md] + compose_paths: [production/docker-compose.yaml] + compose_urls: [] + + nginx_content: + role: nginx_content + vendor_dir: roles/nginx_content/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/nginx/nginx.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + ntfy: + role: ntfy + vendor_dir: roles/ntfy/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/binwiederhier/ntfy.git + ref: main + readme_paths: [README.md] + compose_paths: [docker-compose.yml] + compose_urls: [] + + ocis: + role: ocis + vendor_dir: roles/ocis/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/owncloud/ocis.git + ref: master + readme_paths: [README.md] + compose_paths: [deployments/examples/ocis_full/docker-compose.yml] + compose_urls: [] + + paperless_ngx: + role: paperless_ngx + vendor_dir: roles/paperless_ngx/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/paperless-ngx/paperless-ngx.git + ref: main + readme_paths: [README.md] + compose_paths: [docker/compose/docker-compose.postgres.yml] + compose_urls: [] + + pelican_panel: + role: pelican_panel + vendor_dir: roles/pelican_panel/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/pelican-dev/panel.git + ref: main + readme_paths: [README.md] + compose_paths: [compose-full-stack.yml] + compose_urls: [] + + php_fpm: + role: php_fpm + vendor_dir: roles/php_fpm/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/docker-library/php.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + pinchflat: + role: pinchflat + vendor_dir: roles/pinchflat/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/kieraneglin/pinchflat.git + ref: main + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + postgres: + role: postgres + vendor_dir: roles/postgres/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/docker-library/postgres.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + prometheus: + role: prometheus + vendor_dir: roles/prometheus/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/prometheus/prometheus.git + ref: main + readme_paths: [README.md] + compose_paths: [documentation/examples/prometheus.yml] + compose_urls: [] + + pterodactyl: + role: pterodactyl + vendor_dir: roles/pterodactyl/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/pterodactyl/panel.git + ref: 1.0-develop + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + qbittorrent: + role: qbittorrent + vendor_dir: roles/qbittorrent/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/linuxserver/docker-qbittorrent.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + requarks: + role: requarks + vendor_dir: roles/requarks/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/requarks/wiki.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + syncthing: + role: syncthing + vendor_dir: roles/syncthing/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/linuxserver/docker-syncthing.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + traefik: + role: traefik + vendor_dir: roles/traefik/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/traefik/traefik.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + transmission: + role: transmission + vendor_dir: roles/transmission/upstream + local_compose_file: "" + repositories: + - purpose: container + url: https://github.com/linuxserver/docker-transmission.git + ref: master + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + uptime_kuma: + role: uptime_kuma + vendor_dir: roles/uptime_kuma/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/louislam/uptime-kuma.git + ref: master + readme_paths: [README.md] + compose_paths: [compose.yaml] + compose_urls: [] + + valkey: + role: valkey + vendor_dir: roles/valkey/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/valkey-io/valkey.git + ref: unstable + readme_paths: [README.md] + compose_paths: [] + compose_urls: [] + + vaultwarden: + role: vaultwarden + vendor_dir: roles/vaultwarden/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/dani-garcia/vaultwarden.git + ref: main + readme_paths: [README.md] + compose_paths: [docker/docker-compose.yml] + compose_urls: [] + + wizarr: + role: wizarr + vendor_dir: roles/wizarr/upstream + local_compose_file: "" + repositories: + - purpose: application + url: https://github.com/Wizarrrr/wizarr.git + ref: main + readme_paths: [README.md] + compose_paths: [docker-compose.yml] + compose_urls: [] diff --git a/group_vars/.gitignore b/group_vars/.gitignore new file mode 100644 index 0000000..ad30bfe --- /dev/null +++ b/group_vars/.gitignore @@ -0,0 +1 @@ +*.yml \ No newline at end of file diff --git a/group_vars/all.yml b/group_vars/all.yml index 42a4f21..e69de29 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -1,71 +0,0 @@ ---- -nbops_environment: production -nbops_admin_user: admin -nbops_admin_groups: - - sudo -nbops_hostname: "{{ inventory_hostname }}" -nbops_fqdn: "{{ inventory_hostname }}" -nbops_timezone: Etc/UTC -nbops_locale: en_GB.UTF-8 -nbops_ssh_port: 22 -nbops_dns_servers: - - 1.1.1.1 - - 9.9.9.9 - -nbops_standard_directories: - - /srv/docker - - /srv/config - - /srv/backups - - /srv/data - - /srv/media - - /etc/pki/internal - - /etc/pki/internal/private - - /etc/pki/internal/certs - - /usr/local/share/ca-certificates/internal - -nbops_base_packages: - - acl - - apt-transport-https - - bash-completion - - ca-certificates - - chrony - - curl - - git - - gnupg - - htop - - jq - - locales - - lsb-release - - needrestart - - qemu-guest-agent - - rsync - - sudo - - ufw - - unattended-upgrades - - vim - -nbops_sysctl: - net.ipv4.ip_forward: "1" - net.ipv6.conf.all.forwarding: "1" - fs.inotify.max_user_watches: "1048576" - fs.inotify.max_user_instances: "1024" - -docker_users: - - "{{ nbops_admin_user }}" - -ufw_allowed_tcp_ports: - - "{{ nbops_ssh_port }}" -ufw_allowed_udp_ports: [] - -monitoring_agent: none -monitoring_packages: [] - -mtls_ca_certificates: [] -mtls_host_certificates: [] -mtls_private_keys: [] - -nbops_proxy_network: proxy -nbops_service_routing_mode: host -nbops_service_domain_suffix: example.invalid -nbops_traefik_entrypoint: websecure -nbops_traefik_tls: true diff --git a/group_vars/all.yml.example b/group_vars/all.yml.example new file mode 100644 index 0000000..b71dca7 --- /dev/null +++ b/group_vars/all.yml.example @@ -0,0 +1,73 @@ +--- +nbops_environment: production +nbops_admin_user: admin +nbops_admin_groups: + - sudo +nbops_hostname: "{{ inventory_hostname }}" +nbops_fqdn: "{{ inventory_hostname }}" +nbops_timezone: Etc/UTC +nbops_locale: en_GB.UTF-8 +nbops_ssh_port: 22 +nbops_dns_servers: + - 1.1.1.1 + - 9.9.9.9 + +nbops_standard_directories: + - /srv/docker + - /srv/config + - /srv/backups + - /srv/data + - /srv/media + - /etc/pki/internal + - /etc/pki/internal/private + - /etc/pki/internal/certs + - /usr/local/share/ca-certificates/internal + +nbops_base_packages: + - acl + - apt-transport-https + - bash-completion + - ca-certificates + - chrony + - curl + - git + - gnupg + - htop + - jq + - locales + - lsb-release + - needrestart + - qemu-guest-agent + - rsync + - sudo + - ufw + - unattended-upgrades + - vim + +nbops_sysctl: + net.ipv4.ip_forward: "1" + net.ipv6.conf.all.forwarding: "1" + fs.inotify.max_user_watches: "1048576" + fs.inotify.max_user_instances: "1024" + +docker_users: + - "{{ nbops_admin_user }}" + +ufw_allowed_tcp_ports: + - "{{ nbops_ssh_port }}" +ufw_allowed_udp_ports: [] + +monitoring_agent: none +monitoring_packages: [] + +mtls_ca_certificates: [] +mtls_host_certificates: [] +mtls_private_keys: [] + +nbops_proxy_network: proxy +nbops_service_routing_mode: host +nbops_service_domain_suffix: example.invalid +nbops_traefik_entrypoint: websecure +nbops_traefik_tls: true +nbops_traefik_file_bind_address: "0.0.0.0" +nbops_traefik_file_routes: [] diff --git a/host_vars/.gitignore b/host_vars/.gitignore new file mode 100644 index 0000000..ae741e3 --- /dev/null +++ b/host_vars/.gitignore @@ -0,0 +1 @@ +*internal.yml \ No newline at end of file diff --git a/host_vars/cloud.internal.yml.example b/host_vars/cloud.internal.yml.example deleted file mode 100644 index 21a33a8..0000000 --- a/host_vars/cloud.internal.yml.example +++ /dev/null @@ -1,29 +0,0 @@ ---- -nbops_hostname: cloud -nbops_fqdn: cloud.example.internal -nbops_roles: - - base - - ssh - - ufw - - updates - - docker - - mtls - - monitoring - - traefik - - ocis - -nbops_service_routing_mode: traefik -nbops_traefik_entrypoint: web -nbops_traefik_tls: false - -# Bind this to the VM's internal/LAN address in production. -traefik_bind_address: "0.0.0.0" -traefik_publish_entrypoints: true -traefik_domain: apps-01.example.internal -traefik_forwarded_headers_trusted_ips: - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - -ocis_url: https://files.example.internal - diff --git a/host_vars/edge.yml.example b/host_vars/edge.yml.example deleted file mode 100644 index 49e819c..0000000 --- a/host_vars/edge.yml.example +++ /dev/null @@ -1,31 +0,0 @@ ---- -nbops_hostname: edge -nbops_fqdn: edge.example.invalid -nbops_roles: - - base - - ssh - - ufw - - updates - - docker - - mtls - - monitoring - - traefik - - authentik - - authentik_rac - - authentik_ldap - - authentik_worker - -nbops_service_routing_mode: traefik -nbops_service_domain_suffix: example.invalid -nbops_traefik_entrypoint: websecure -nbops_traefik_tls: true - -traefik_bind_address: "0.0.0.0" -traefik_publish_entrypoints: true -traefik_domain: example.invalid -traefik_acme_email: admin@example.invalid - -authentik_domain: auth.example.invalid -authentik_http_port: 9000 -authentik_https_port: 9443 - diff --git a/host_vars/generic-downstream.yml.example b/host_vars/generic-downstream.yml.example index 1126d66..51848ee 100644 --- a/host_vars/generic-downstream.yml.example +++ b/host_vars/generic-downstream.yml.example @@ -19,8 +19,21 @@ nbops_service_routing_mode: traefik nbops_traefik_entrypoint: web nbops_traefik_tls: false +# Edge Traefik consumes these file-provider routes and forwards to this VM's +# internal Traefik. Internal Traefik still discovers application containers +# through Docker labels. +nbops_traefik_file_routes: + - name: gitea + domain: git.example.internal + scheme: http + port: 80 + - name: immich + domain: photos.example.internal + scheme: http + port: 80 + # Bind this to the VM's internal/LAN address in production. -traefik_bind_address: "0.0.0.0" +traefik_bind_address: "10.0.10.25" traefik_publish_entrypoints: true traefik_domain: apps-01.example.internal traefik_forwarded_headers_trusted_ips: @@ -54,4 +67,3 @@ arr_services: image: lscr.io/linuxserver/bazarr:latest port: 6767 domain: bazarr.example.internal - diff --git a/host_vars/pelican-server.yml.example b/host_vars/pelican-server.yml.example deleted file mode 100644 index e9bed80..0000000 --- a/host_vars/pelican-server.yml.example +++ /dev/null @@ -1,15 +0,0 @@ ---- -nbops_hostname: pelican-node-01 -nbops_fqdn: pelican-node-01.example.internal -nbops_roles: - - base - - ssh - - ufw - - updates - - docker - - pelican_server - -pelican_server_daemon_port: 8080 -pelican_server_sftp_port: 2022 -pelican_server_allocation_ports: - - { port: "25565:25575", proto: tcp } diff --git a/host_vars/wings.internal.example b/host_vars/wings.internal.example deleted file mode 100644 index 21a33a8..0000000 --- a/host_vars/wings.internal.example +++ /dev/null @@ -1,29 +0,0 @@ ---- -nbops_hostname: cloud -nbops_fqdn: cloud.example.internal -nbops_roles: - - base - - ssh - - ufw - - updates - - docker - - mtls - - monitoring - - traefik - - ocis - -nbops_service_routing_mode: traefik -nbops_traefik_entrypoint: web -nbops_traefik_tls: false - -# Bind this to the VM's internal/LAN address in production. -traefik_bind_address: "0.0.0.0" -traefik_publish_entrypoints: true -traefik_domain: apps-01.example.internal -traefik_forwarded_headers_trusted_ips: - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - -ocis_url: https://files.example.internal - diff --git a/roles/alloy/tasks/main.yml b/roles/alloy/tasks/main.yml index f82548e..a6639e0 100644 --- a/roles/alloy/tasks/main.yml +++ b/roles/alloy/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/alloy/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/alloy/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/alloy/templates/compose.override.yaml.j2" docker_app_name: "{{ alloy_name }}" docker_app_dir: "{{ alloy_dir }}" docker_app_image: "{{ alloy_image }}" diff --git a/roles/alloy/templates/compose.override.yaml.j2 b/roles/alloy/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/alloy/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/alloy/templates/compose.yaml.j2 b/roles/alloy/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/alloy/templates/compose.yaml.j2 +++ b/roles/alloy/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/alloy/upstream/SOURCE.md b/roles/alloy/upstream/SOURCE.md new file mode 100644 index 0000000..d5c036e --- /dev/null +++ b/roles/alloy/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `alloy` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/alloy/upstream/docker-compose.yml b/roles/alloy/upstream/docker-compose.yml new file mode 100644 index 0000000..fc24f73 --- /dev/null +++ b/roles/alloy/upstream/docker-compose.yml @@ -0,0 +1,23 @@ +services: + grafana-alloy: + image: grafana/alloy:latest + container_name: grafana-alloy + ports: + - '12345:12345' + volumes: + # mount config file + - './grafana/alloy/config.alloy:/etc/alloy/config.alloy' + # give access to running docker containers for discovery.docker + - /var/run/docker.sock:/var/run/docker.sock + # give access to docker's log files directory (optional) + - /var/lib/docker/containers:/var/lib/docker/containers:ro + environment: + # pass environment variables for config.alloy + GRAFANA_LOKI_URL: ${GRAFANA_LOKI_URL} + GRAFANA_LOKI_USERNAME: ${GRAFANA_LOKI_USERNAME} +# GRAFANA_CLOUD_API_KEY: ${GRAFANA_CLOUD_API_KEY} + command: + - run + - --server.http.listen-addr=0.0.0.0:12345 + - --storage.path=/var/lib/alloy/data + - /etc/alloy/config.alloy \ No newline at end of file diff --git a/roles/arr/handlers/.gitkeep b/roles/arr/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/arr/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/arr/handlers/main.yml b/roles/arr/handlers/main.yml deleted file mode 100644 index 504ee8d..0000000 --- a/roles/arr/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart arr - community.docker.docker_compose_v2: - project_src: "{{ arr_dir }}" - state: restarted - diff --git a/roles/arr/tasks/main.yml b/roles/arr/tasks/main.yml index 7b39f0b..5099337 100644 --- a/roles/arr/tasks/main.yml +++ b/roles/arr/tasks/main.yml @@ -23,43 +23,22 @@ loop: "{{ arr_services | dict2items }}" tags: [arr, config] -- name: Render Arr environment - ansible.builtin.template: - src: env.j2 - dest: "{{ arr_dir }}/.env" - owner: root - group: root - mode: "0644" - tags: [arr, config] - -- name: Ensure Arr proxy network exists - community.docker.docker_network: - name: "{{ arr_traefik_network }}" - state: present - when: arr_routing_mode == "traefik" - tags: [arr, docker] - -- name: Render Arr Compose file - ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ arr_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart arr - tags: [arr, compose] - -- name: Allow Arr ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ arr_tcp_ports }}" - when: arr_routing_mode == "host" - tags: [arr, firewall] - -- name: Start Arr stack - community.docker.docker_compose_v2: - project_src: "{{ arr_dir }}" - state: present - tags: [arr, compose] +- name: Deploy Arr Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ arr_dir }}" + compose_project_directories: + - "{{ arr_dir }}" + - "{{ arr_config_root }}" + - "{{ arr_media_root }}" + - "{{ arr_downloads_root }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/arr/templates/env.j2" + compose_project_env_mode: "0644" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/arr/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/arr/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: "{{ arr_routing_mode == 'traefik' }}" + compose_project_proxy_network: "{{ arr_traefik_network }}" + compose_project_firewall_ports: "{{ arr_tcp_ports }}" + compose_project_firewall_enabled: "{{ arr_routing_mode in ['host', 'traefik_file'] }}" + tags: [arr] diff --git a/roles/arr/templates/compose.override.yaml.j2 b/roles/arr/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..908a113 --- /dev/null +++ b/roles/arr/templates/compose.override.yaml.j2 @@ -0,0 +1,5 @@ +services: +{% for name, service in arr_services.items() %} + {{ name }}: {} +{% endfor %} + diff --git a/roles/arr/templates/compose.yaml.j2 b/roles/arr/templates/compose.yaml.j2 index bc2891b..a74ff4a 100644 --- a/roles/arr/templates/compose.yaml.j2 +++ b/roles/arr/templates/compose.yaml.j2 @@ -4,9 +4,9 @@ services: image: {{ service.image }} restart: unless-stopped env_file: .env -{% if arr_routing_mode == "host" %} +{% if arr_routing_mode in ["host", "traefik_file"] %} ports: - - "{{ service.port }}:{{ service.port }}" + - "{{ nbops_traefik_file_bind_address if arr_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ service.port }}:{{ service.port }}" {% endif %} {% if arr_routing_mode == "traefik" %} # internal service port: {{ service.port }}; not published on the Docker host diff --git a/roles/arr/upstream/SOURCE.md b/roles/arr/upstream/SOURCE.md new file mode 100644 index 0000000..eef44b9 --- /dev/null +++ b/roles/arr/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `arr` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/authentik/handlers/.gitkeep b/roles/authentik/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik/handlers/main.yml b/roles/authentik/handlers/main.yml deleted file mode 100644 index bf098a9..0000000 --- a/roles/authentik/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart authentik - community.docker.docker_compose_v2: - project_src: "{{ authentik_dir }}" - state: restarted - diff --git a/roles/authentik/tasks/main.yml b/roles/authentik/tasks/main.yml index 1c6be2b..f70cd08 100644 --- a/roles/authentik/tasks/main.yml +++ b/roles/authentik/tasks/main.yml @@ -8,44 +8,18 @@ mode: "0755" tags: [authentik] -- name: Render authentik environment - ansible.builtin.template: - src: env.j2 - dest: "{{ authentik_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [authentik, config] - -- name: Ensure authentik proxy network exists - community.docker.docker_network: - name: "{{ authentik_traefik_network }}" - state: present - when: authentik_routing_mode == "traefik" - tags: [authentik, docker] - -- name: Render authentik Compose file - ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ authentik_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart authentik - tags: [authentik, compose] - -- name: Allow authentik ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ authentik_tcp_ports }}" - when: authentik_routing_mode == "host" - tags: [authentik, firewall] - -- name: Start authentik - community.docker.docker_compose_v2: - project_src: "{{ authentik_dir }}" - state: present - tags: [authentik, compose] +- name: Deploy authentik Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ authentik_dir }}" + compose_project_directories: + - "{{ authentik_dir }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/authentik/templates/env.j2" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/authentik/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/authentik/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: "{{ authentik_routing_mode == 'traefik' }}" + compose_project_proxy_network: "{{ authentik_traefik_network }}" + compose_project_firewall_ports: "{{ authentik_tcp_ports }}" + compose_project_firewall_enabled: "{{ authentik_routing_mode in ['host', 'traefik_file'] }}" + tags: [authentik] diff --git a/roles/authentik/templates/compose.override.yaml.j2 b/roles/authentik/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..0200fd4 --- /dev/null +++ b/roles/authentik/templates/compose.override.yaml.j2 @@ -0,0 +1,6 @@ +services: + postgresql: {} + redis: {} + server: {} + worker: {} + diff --git a/roles/authentik/templates/compose.yaml.j2 b/roles/authentik/templates/compose.yaml.j2 index 82efd02..9bd16e4 100644 --- a/roles/authentik/templates/compose.yaml.j2 +++ b/roles/authentik/templates/compose.yaml.j2 @@ -16,10 +16,10 @@ services: restart: unless-stopped command: server env_file: .env -{% if authentik_routing_mode == "host" %} +{% if authentik_routing_mode in ["host", "traefik_file"] %} ports: - - "{{ authentik_http_port }}:9000" - - "{{ authentik_https_port }}:9443" + - "{{ nbops_traefik_file_bind_address if authentik_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ authentik_http_port }}:9000" + - "{{ nbops_traefik_file_bind_address if authentik_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ authentik_https_port }}:9443" {% endif %} {% if authentik_routing_mode == "traefik" %} # internal HTTP service port: 9000; not published on the Docker host diff --git a/roles/authentik/upstream/SOURCE.md b/roles/authentik/upstream/SOURCE.md new file mode 100644 index 0000000..14021e5 --- /dev/null +++ b/roles/authentik/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `authentik` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/authentik_ldap/tasks/main.yml b/roles/authentik_ldap/tasks/main.yml index 826c2f6..0de36da 100644 --- a/roles/authentik_ldap/tasks/main.yml +++ b/roles/authentik_ldap/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/compose.override.yaml.j2" docker_app_name: "{{ authentik_ldap_name }}" docker_app_dir: "{{ authentik_ldap_dir }}" docker_app_image: "{{ authentik_ldap_image }}" diff --git a/roles/authentik_ldap/templates/compose.override.yaml.j2 b/roles/authentik_ldap/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/authentik_ldap/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/authentik_ldap/templates/compose.yaml.j2 b/roles/authentik_ldap/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/authentik_ldap/templates/compose.yaml.j2 +++ b/roles/authentik_ldap/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/authentik_ldap/upstream/SOURCE.md b/roles/authentik_ldap/upstream/SOURCE.md new file mode 100644 index 0000000..5cc0bf5 --- /dev/null +++ b/roles/authentik_ldap/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `authentik_ldap` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/authentik_proxy/tasks/main.yml b/roles/authentik_proxy/tasks/main.yml index 12e5b35..ce7d531 100644 --- a/roles/authentik_proxy/tasks/main.yml +++ b/roles/authentik_proxy/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/compose.override.yaml.j2" docker_app_name: "{{ authentik_proxy_name }}" docker_app_dir: "{{ authentik_proxy_dir }}" docker_app_image: "{{ authentik_proxy_image }}" diff --git a/roles/authentik_proxy/templates/compose.override.yaml.j2 b/roles/authentik_proxy/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/authentik_proxy/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/authentik_proxy/templates/compose.yaml.j2 b/roles/authentik_proxy/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/authentik_proxy/templates/compose.yaml.j2 +++ b/roles/authentik_proxy/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/authentik_proxy/upstream/SOURCE.md b/roles/authentik_proxy/upstream/SOURCE.md new file mode 100644 index 0000000..b61b682 --- /dev/null +++ b/roles/authentik_proxy/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `authentik_proxy` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/authentik_rac/tasks/main.yml b/roles/authentik_rac/tasks/main.yml index 232965e..9a40f03 100644 --- a/roles/authentik_rac/tasks/main.yml +++ b/roles/authentik_rac/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/compose.override.yaml.j2" docker_app_name: "{{ authentik_rac_name }}" docker_app_dir: "{{ authentik_rac_dir }}" docker_app_image: "{{ authentik_rac_image }}" diff --git a/roles/authentik_rac/templates/compose.override.yaml.j2 b/roles/authentik_rac/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/authentik_rac/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/authentik_rac/templates/compose.yaml.j2 b/roles/authentik_rac/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/authentik_rac/templates/compose.yaml.j2 +++ b/roles/authentik_rac/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/authentik_rac/upstream/SOURCE.md b/roles/authentik_rac/upstream/SOURCE.md new file mode 100644 index 0000000..884dcd4 --- /dev/null +++ b/roles/authentik_rac/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `authentik_rac` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/cleanuparr/tasks/main.yml b/roles/cleanuparr/tasks/main.yml index 01fd409..61fea72 100644 --- a/roles/cleanuparr/tasks/main.yml +++ b/roles/cleanuparr/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/compose.override.yaml.j2" docker_app_name: "{{ cleanuparr_name }}" docker_app_dir: "{{ cleanuparr_dir }}" docker_app_image: "{{ cleanuparr_image }}" diff --git a/roles/cleanuparr/templates/compose.override.yaml.j2 b/roles/cleanuparr/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/cleanuparr/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/cleanuparr/templates/compose.yaml.j2 b/roles/cleanuparr/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/cleanuparr/templates/compose.yaml.j2 +++ b/roles/cleanuparr/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/cleanuparr/upstream/SOURCE.md b/roles/cleanuparr/upstream/SOURCE.md new file mode 100644 index 0000000..6073594 --- /dev/null +++ b/roles/cleanuparr/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `cleanuparr` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/code_server/tasks/main.yml b/roles/code_server/tasks/main.yml index ae7a8a3..392c9fc 100644 --- a/roles/code_server/tasks/main.yml +++ b/roles/code_server/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/code_server/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/code_server/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/code_server/templates/compose.override.yaml.j2" docker_app_name: "{{ code_server_name }}" docker_app_dir: "{{ code_server_dir }}" docker_app_image: "{{ code_server_image }}" diff --git a/roles/code_server/templates/compose.override.yaml.j2 b/roles/code_server/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/code_server/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/code_server/templates/compose.yaml.j2 b/roles/code_server/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/code_server/templates/compose.yaml.j2 +++ b/roles/code_server/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/code_server/upstream/SOURCE.md b/roles/code_server/upstream/SOURCE.md new file mode 100644 index 0000000..9d1ed36 --- /dev/null +++ b/roles/code_server/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `code_server` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/compose_project/README.md b/roles/compose_project/README.md new file mode 100644 index 0000000..60045ed --- /dev/null +++ b/roles/compose_project/README.md @@ -0,0 +1,11 @@ +# compose_project + +Normalizes Docker Compose project deployment for all Compose-backed roles. + +This role renders `.env`, `compose.yaml`, and optional `compose.override.yaml`, +creates required directories and proxy networks, opens declared UFW ports, and +starts the Compose project. + +Application roles should provide their own templates and call this role instead +of duplicating Compose lifecycle tasks. + diff --git a/roles/compose_project/defaults/main.yml b/roles/compose_project/defaults/main.yml new file mode 100644 index 0000000..209fcb1 --- /dev/null +++ b/roles/compose_project/defaults/main.yml @@ -0,0 +1,20 @@ +--- +compose_project_dir: "" +compose_project_directories: + - "{{ compose_project_dir }}" +compose_project_env_template_src: "" +compose_project_compose_template_src: "" +compose_project_override_template_src: "" +compose_project_env_dest: "{{ compose_project_dir }}/.env" +compose_project_compose_dest: "{{ compose_project_dir }}/compose.yaml" +compose_project_override_dest: "{{ compose_project_dir }}/compose.override.yaml" +compose_project_env_mode: "0600" +compose_project_compose_mode: "0644" +compose_project_override_mode: "0644" +compose_project_proxy_network: "" +compose_project_create_proxy_network: false +compose_project_firewall_ports: [] +compose_project_firewall_enabled: false +compose_project_state: present +compose_project_pull: missing + diff --git a/roles/compose_project/files/.gitkeep b/roles/compose_project/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/compose_project/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/compose_project/handlers/main.yml b/roles/compose_project/handlers/main.yml new file mode 100644 index 0000000..9909706 --- /dev/null +++ b/roles/compose_project/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart compose project + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: restarted + diff --git a/roles/compose_project/tasks/main.yml b/roles/compose_project/tasks/main.yml new file mode 100644 index 0000000..6ba8cae --- /dev/null +++ b/roles/compose_project/tasks/main.yml @@ -0,0 +1,75 @@ +--- +- name: Validate Compose project directory is configured + ansible.builtin.assert: + that: + - compose_project_dir | length > 0 + - compose_project_compose_template_src | length > 0 + fail_msg: "compose_project_dir and compose_project_compose_template_src are required." + tags: [compose_project] + +- name: Create Compose project directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: "{{ compose_project_directories }}" + tags: [compose_project, directories] + +- name: Ensure Compose project proxy network exists + community.docker.docker_network: + name: "{{ compose_project_proxy_network }}" + state: present + when: + - compose_project_create_proxy_network | bool + - compose_project_proxy_network | length > 0 + tags: [compose_project, docker] + +- name: Render Compose project environment file + ansible.builtin.template: + src: "{{ compose_project_env_template_src }}" + dest: "{{ compose_project_env_dest }}" + owner: root + group: root + mode: "{{ compose_project_env_mode }}" + when: compose_project_env_template_src | length > 0 + no_log: true + tags: [compose_project, config] + +- name: Render Compose project file + ansible.builtin.template: + src: "{{ compose_project_compose_template_src }}" + dest: "{{ compose_project_compose_dest }}" + owner: root + group: root + mode: "{{ compose_project_compose_mode }}" + notify: restart compose project + tags: [compose_project, compose] + +- name: Render Compose project override file + ansible.builtin.template: + src: "{{ compose_project_override_template_src }}" + dest: "{{ compose_project_override_dest }}" + owner: root + group: root + mode: "{{ compose_project_override_mode }}" + when: compose_project_override_template_src | length > 0 + notify: restart compose project + tags: [compose_project, compose] + +- name: Allow Compose project firewall ports + community.general.ufw: + rule: allow + port: "{{ item.port | default(item.host | default(item.container | default(item))) }}" + proto: "{{ item.proto | default(item.protocol | default('tcp')) }}" + loop: "{{ compose_project_firewall_ports }}" + when: compose_project_firewall_enabled | bool + tags: [compose_project, firewall] + +- name: Start Compose project + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: "{{ compose_project_state }}" + pull: "{{ compose_project_pull }}" + tags: [compose_project, compose] diff --git a/roles/compose_project/templates/.gitkeep b/roles/compose_project/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/compose_project/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/docker_app/README.md b/roles/docker_app/README.md index 9150e3d..beca679 100644 --- a/roles/docker_app/README.md +++ b/roles/docker_app/README.md @@ -1,10 +1,12 @@ # docker_app -Shared implementation role for single-container Docker Compose applications. +Shared adapter role for single-container Docker Compose applications. Application roles set `docker_app_*` variables and include this role. +`docker_app` validates single-container app inputs and delegates the common +Compose lifecycle to `compose_project`. + It supports direct host-port publishing with UFW rules or Traefik routing via labels and a shared Docker network. Tags: `docker_app`, `docker`, `config`, `compose`, `firewall`. - diff --git a/roles/docker_app/defaults/main.yml b/roles/docker_app/defaults/main.yml index c31cfdd..199158c 100644 --- a/roles/docker_app/defaults/main.yml +++ b/roles/docker_app/defaults/main.yml @@ -26,7 +26,9 @@ docker_app_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" docker_app_traefik_tls: "{{ nbops_traefik_tls }}" docker_app_traefik_middlewares: [] docker_app_traefik_rule: "Host(`{{ docker_app_domain }}`)" +docker_app_traefik_file_bind_address: "{{ nbops_traefik_file_bind_address }}" docker_app_labels: {} docker_app_ufw_ports: "{{ docker_app_ports }}" docker_app_env_template_src: env.j2 docker_app_compose_template_src: compose.yaml.j2 +docker_app_compose_override_template_src: "" diff --git a/roles/docker_app/handlers/.gitkeep b/roles/docker_app/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/docker_app/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/docker_app/handlers/main.yml b/roles/docker_app/handlers/main.yml deleted file mode 100644 index 53b70f1..0000000 --- a/roles/docker_app/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart docker app - community.docker.docker_compose_v2: - project_src: "{{ docker_app_dir }}" - state: restarted - diff --git a/roles/docker_app/tasks/main.yml b/roles/docker_app/tasks/main.yml index b62d689..807a678 100644 --- a/roles/docker_app/tasks/main.yml +++ b/roles/docker_app/tasks/main.yml @@ -5,58 +5,18 @@ - docker_app_image | length > 0 fail_msg: "docker_app_image must be configured for {{ docker_app_name }}." tags: [docker_app] - -- name: Create Docker app directories - ansible.builtin.file: - path: "{{ item }}" - state: directory - owner: root - group: root - mode: "0755" - loop: "{{ docker_app_directories }}" +- name: Deploy Docker app Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ docker_app_dir }}" + compose_project_directories: "{{ docker_app_directories }}" + compose_project_env_template_src: "{{ docker_app_env_template_src }}" + compose_project_compose_template_src: "{{ docker_app_compose_template_src }}" + compose_project_override_template_src: "{{ docker_app_compose_override_template_src }}" + compose_project_create_proxy_network: "{{ docker_app_routing_mode == 'traefik' and docker_app_network_mode | length == 0 }}" + compose_project_proxy_network: "{{ docker_app_traefik_network }}" + compose_project_firewall_ports: "{{ docker_app_ufw_ports }}" + compose_project_firewall_enabled: "{{ docker_app_routing_mode in ['host', 'traefik_file'] }}" + compose_project_pull: "{{ docker_app_pull }}" tags: [docker_app] - -- name: Ensure proxy network exists - community.docker.docker_network: - name: "{{ docker_app_traefik_network }}" - state: present - when: - - docker_app_routing_mode == "traefik" - - docker_app_network_mode | length == 0 - tags: [docker_app, docker] - -- name: Render Docker app environment - ansible.builtin.template: - src: "{{ docker_app_env_template_src }}" - dest: "{{ docker_app_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [docker_app, config] - -- name: Render Docker app Compose file - ansible.builtin.template: - src: "{{ docker_app_compose_template_src }}" - dest: "{{ docker_app_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart docker app - tags: [docker_app, compose] - -- name: Allow Docker app host ports - community.general.ufw: - rule: allow - port: "{{ item.host | default(item.container) }}" - proto: "{{ item.protocol | default('tcp') }}" - loop: "{{ docker_app_ufw_ports }}" - when: docker_app_routing_mode == "host" - tags: [docker_app, firewall] - -- name: Start Docker app - community.docker.docker_compose_v2: - project_src: "{{ docker_app_dir }}" - state: present - pull: "{{ docker_app_pull }}" - tags: [docker_app, compose] diff --git a/roles/docker_app/templates/compose.override.yaml.j2 b/roles/docker_app/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/docker_app/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/docker_app/templates/compose.yaml.j2 b/roles/docker_app/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/docker_app/templates/compose.yaml.j2 +++ b/roles/docker_app/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/filebrowser_quantum/tasks/main.yml b/roles/filebrowser_quantum/tasks/main.yml index ac1eade..f6125c3 100644 --- a/roles/filebrowser_quantum/tasks/main.yml +++ b/roles/filebrowser_quantum/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/filebrowser_quantum/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/filebrowser_quantum/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/filebrowser_quantum/templates/compose.override.yaml.j2" docker_app_name: "{{ filebrowser_quantum_name }}" docker_app_dir: "{{ filebrowser_quantum_dir }}" docker_app_image: "{{ filebrowser_quantum_image }}" diff --git a/roles/filebrowser_quantum/templates/compose.override.yaml.j2 b/roles/filebrowser_quantum/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/filebrowser_quantum/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/filebrowser_quantum/templates/compose.yaml.j2 b/roles/filebrowser_quantum/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/filebrowser_quantum/templates/compose.yaml.j2 +++ b/roles/filebrowser_quantum/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/filebrowser_quantum/upstream/SOURCE.md b/roles/filebrowser_quantum/upstream/SOURCE.md new file mode 100644 index 0000000..876b466 --- /dev/null +++ b/roles/filebrowser_quantum/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `filebrowser_quantum` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/fiveetools/tasks/main.yml b/roles/fiveetools/tasks/main.yml index f0f6ba7..de7c76d 100644 --- a/roles/fiveetools/tasks/main.yml +++ b/roles/fiveetools/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/fiveetools/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/fiveetools/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/fiveetools/templates/compose.override.yaml.j2" docker_app_name: "{{ fiveetools_name }}" docker_app_dir: "{{ fiveetools_dir }}" docker_app_image: "{{ fiveetools_image }}" diff --git a/roles/fiveetools/templates/compose.override.yaml.j2 b/roles/fiveetools/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/fiveetools/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/fiveetools/templates/compose.yaml.j2 b/roles/fiveetools/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/fiveetools/templates/compose.yaml.j2 +++ b/roles/fiveetools/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/fiveetools/upstream/SOURCE.md b/roles/fiveetools/upstream/SOURCE.md new file mode 100644 index 0000000..11494fe --- /dev/null +++ b/roles/fiveetools/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `fiveetools` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/fiveetools/upstream/docker-compose.yml b/roles/fiveetools/upstream/docker-compose.yml new file mode 100644 index 0000000..f72ae45 --- /dev/null +++ b/roles/fiveetools/upstream/docker-compose.yml @@ -0,0 +1,13 @@ +services: + 5etools: + container_name: 5etools + image: jafner/5etools-docker + volumes: + - ./5etools-docker/htdocs:/usr/local/apache2/htdocs + ports: + - 5370:80/tcp + environment: + - IMG=TRUE # Set to TRUE to pull images from https://github.com/5etools-mirror-2/5etools-img (as a Git submodule) +# - OFFLINE_MODE=TRUE # Optional. Expects "TRUE" or "FALSE". Disables checking for new updates. + - DL_LINK=https://github.com/5etools-mirror-3/5etools-src.git + - IMG_LINK=https://github.com/5etools-mirror-3/5etools-img.git \ No newline at end of file diff --git a/roles/flaresolverr/tasks/main.yml b/roles/flaresolverr/tasks/main.yml index aaef353..9c32916 100644 --- a/roles/flaresolverr/tasks/main.yml +++ b/roles/flaresolverr/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/flaresolverr/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/flaresolverr/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/flaresolverr/templates/compose.override.yaml.j2" docker_app_name: "{{ flaresolverr_name }}" docker_app_dir: "{{ flaresolverr_dir }}" docker_app_image: "{{ flaresolverr_image }}" diff --git a/roles/flaresolverr/templates/compose.override.yaml.j2 b/roles/flaresolverr/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/flaresolverr/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/flaresolverr/templates/compose.yaml.j2 b/roles/flaresolverr/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/flaresolverr/templates/compose.yaml.j2 +++ b/roles/flaresolverr/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/flaresolverr/upstream/SOURCE.md b/roles/flaresolverr/upstream/SOURCE.md new file mode 100644 index 0000000..32329c2 --- /dev/null +++ b/roles/flaresolverr/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `flaresolverr` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/gitea/handlers/.gitkeep b/roles/gitea/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/gitea/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/gitea/handlers/main.yml b/roles/gitea/handlers/main.yml deleted file mode 100644 index 225b084..0000000 --- a/roles/gitea/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart gitea - community.docker.docker_compose_v2: - project_src: "{{ gitea_dir }}" - state: restarted - diff --git a/roles/gitea/tasks/main.yml b/roles/gitea/tasks/main.yml index 4fee573..93674db 100644 --- a/roles/gitea/tasks/main.yml +++ b/roles/gitea/tasks/main.yml @@ -8,41 +8,21 @@ mode: "0755" tags: [gitea] -- name: Render Gitea environment - ansible.builtin.template: - src: env.j2 - dest: "{{ gitea_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [gitea, config] - -- name: Ensure Gitea proxy network exists - community.docker.docker_network: - name: "{{ gitea_traefik_network }}" - state: present - when: gitea_routing_mode == "traefik" - tags: [gitea, docker] - -- name: Render Gitea Compose file - ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ gitea_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart gitea - tags: [gitea, compose] - -- name: Allow Gitea ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ gitea_tcp_ports }}" - when: gitea_routing_mode == "host" - tags: [gitea, firewall] +- name: Deploy Gitea Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ gitea_dir }}" + compose_project_directories: + - "{{ gitea_dir }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/gitea/templates/env.j2" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/gitea/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/gitea/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: "{{ gitea_routing_mode == 'traefik' }}" + compose_project_proxy_network: "{{ gitea_traefik_network }}" + compose_project_firewall_ports: "{{ gitea_tcp_ports }}" + compose_project_firewall_enabled: "{{ gitea_routing_mode in ['host', 'traefik_file'] }}" + tags: [gitea] - name: Allow Gitea SSH port when published behind Traefik community.general.ufw: @@ -53,9 +33,3 @@ - gitea_routing_mode == "traefik" - gitea_publish_ssh | bool tags: [gitea, firewall] - -- name: Start Gitea - community.docker.docker_compose_v2: - project_src: "{{ gitea_dir }}" - state: present - tags: [gitea, compose] diff --git a/roles/gitea/templates/compose.override.yaml.j2 b/roles/gitea/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..fb58fe8 --- /dev/null +++ b/roles/gitea/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + gitea: {} + diff --git a/roles/gitea/templates/compose.yaml.j2 b/roles/gitea/templates/compose.yaml.j2 index 19d53c9..7b77d89 100644 --- a/roles/gitea/templates/compose.yaml.j2 +++ b/roles/gitea/templates/compose.yaml.j2 @@ -3,10 +3,10 @@ services: image: {{ gitea_image }} restart: unless-stopped env_file: .env -{% if gitea_routing_mode == "host" or gitea_publish_ssh | bool %} +{% if gitea_routing_mode in ["host", "traefik_file"] or gitea_publish_ssh | bool %} ports: -{% if gitea_routing_mode == "host" %} - - "{{ gitea_http_port }}:3000" +{% if gitea_routing_mode in ["host", "traefik_file"] %} + - "{{ nbops_traefik_file_bind_address if gitea_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ gitea_http_port }}:3000" {% endif %} {% if gitea_publish_ssh | bool %} - "{{ gitea_ssh_port }}:22" diff --git a/roles/gitea/upstream/SOURCE.md b/roles/gitea/upstream/SOURCE.md new file mode 100644 index 0000000..64aa4d4 --- /dev/null +++ b/roles/gitea/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `gitea` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/gitea_act_runner/tasks/main.yml b/roles/gitea_act_runner/tasks/main.yml index 7f1f98e..d932076 100644 --- a/roles/gitea_act_runner/tasks/main.yml +++ b/roles/gitea_act_runner/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/gitea_act_runner/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/gitea_act_runner/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/gitea_act_runner/templates/compose.override.yaml.j2" docker_app_name: "{{ gitea_act_runner_name }}" docker_app_dir: "{{ gitea_act_runner_dir }}" docker_app_image: "{{ gitea_act_runner_image }}" diff --git a/roles/gitea_act_runner/templates/compose.override.yaml.j2 b/roles/gitea_act_runner/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/gitea_act_runner/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/gitea_act_runner/templates/compose.yaml.j2 b/roles/gitea_act_runner/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/gitea_act_runner/templates/compose.yaml.j2 +++ b/roles/gitea_act_runner/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/gitea_act_runner/upstream/SOURCE.md b/roles/gitea_act_runner/upstream/SOURCE.md new file mode 100644 index 0000000..9c60a52 --- /dev/null +++ b/roles/gitea_act_runner/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `gitea_act_runner` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index a17dca4..ba7c1d9 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/grafana/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/grafana/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/grafana/templates/compose.override.yaml.j2" docker_app_name: "{{ grafana_name }}" docker_app_dir: "{{ grafana_dir }}" docker_app_image: "{{ grafana_image }}" diff --git a/roles/grafana/templates/compose.override.yaml.j2 b/roles/grafana/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/grafana/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/grafana/templates/compose.yaml.j2 b/roles/grafana/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/grafana/templates/compose.yaml.j2 +++ b/roles/grafana/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/grafana/upstream/SOURCE.md b/roles/grafana/upstream/SOURCE.md new file mode 100644 index 0000000..5285399 --- /dev/null +++ b/roles/grafana/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `grafana` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/grafana_renderer/tasks/main.yml b/roles/grafana_renderer/tasks/main.yml index c252757..fe4cb59 100644 --- a/roles/grafana_renderer/tasks/main.yml +++ b/roles/grafana_renderer/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/grafana_renderer/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/grafana_renderer/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/grafana_renderer/templates/compose.override.yaml.j2" docker_app_name: "{{ grafana_renderer_name }}" docker_app_dir: "{{ grafana_renderer_dir }}" docker_app_image: "{{ grafana_renderer_image }}" diff --git a/roles/grafana_renderer/templates/compose.override.yaml.j2 b/roles/grafana_renderer/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/grafana_renderer/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/grafana_renderer/templates/compose.yaml.j2 b/roles/grafana_renderer/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/grafana_renderer/templates/compose.yaml.j2 +++ b/roles/grafana_renderer/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/grafana_renderer/upstream/SOURCE.md b/roles/grafana_renderer/upstream/SOURCE.md new file mode 100644 index 0000000..6ee6c0e --- /dev/null +++ b/roles/grafana_renderer/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `grafana_renderer` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/grafana_renderer/upstream/docker-compose.yml b/roles/grafana_renderer/upstream/docker-compose.yml new file mode 100644 index 0000000..694b48b --- /dev/null +++ b/roles/grafana_renderer/upstream/docker-compose.yml @@ -0,0 +1,5 @@ +services: + renderer: + container_name: grafana-renderer + image: grafana/grafana-image-renderer:latest + restart: unless-stopped \ No newline at end of file diff --git a/roles/homarr/tasks/main.yml b/roles/homarr/tasks/main.yml index af287cd..a7e9e94 100644 --- a/roles/homarr/tasks/main.yml +++ b/roles/homarr/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/homarr/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/homarr/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/homarr/templates/compose.override.yaml.j2" docker_app_name: "{{ homarr_name }}" docker_app_dir: "{{ homarr_dir }}" docker_app_image: "{{ homarr_image }}" diff --git a/roles/homarr/templates/compose.override.yaml.j2 b/roles/homarr/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/homarr/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/homarr/templates/compose.yaml.j2 b/roles/homarr/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/homarr/templates/compose.yaml.j2 +++ b/roles/homarr/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/homarr/upstream/SOURCE.md b/roles/homarr/upstream/SOURCE.md new file mode 100644 index 0000000..5d84207 --- /dev/null +++ b/roles/homarr/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `homarr` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/immich/handlers/.gitkeep b/roles/immich/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/immich/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/immich/handlers/main.yml b/roles/immich/handlers/main.yml deleted file mode 100644 index 8eb6639..0000000 --- a/roles/immich/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart immich - community.docker.docker_compose_v2: - project_src: "{{ immich_dir }}" - state: restarted - diff --git a/roles/immich/tasks/main.yml b/roles/immich/tasks/main.yml index 31cf165..f336de6 100644 --- a/roles/immich/tasks/main.yml +++ b/roles/immich/tasks/main.yml @@ -11,44 +11,19 @@ - "{{ immich_upload_location }}" tags: [immich] -- name: Render Immich environment - ansible.builtin.template: - src: env.j2 - dest: "{{ immich_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [immich, config] - -- name: Ensure Immich proxy network exists - community.docker.docker_network: - name: "{{ immich_traefik_network }}" - state: present - when: immich_routing_mode == "traefik" - tags: [immich, docker] - -- name: Render Immich Compose file - ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ immich_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart immich - tags: [immich, compose] - -- name: Allow Immich ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ immich_tcp_ports }}" - when: immich_routing_mode == "host" - tags: [immich, firewall] - -- name: Start Immich - community.docker.docker_compose_v2: - project_src: "{{ immich_dir }}" - state: present - tags: [immich, compose] +- name: Deploy Immich Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ immich_dir }}" + compose_project_directories: + - "{{ immich_dir }}" + - "{{ immich_upload_location }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/immich/templates/env.j2" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/immich/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/immich/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: "{{ immich_routing_mode == 'traefik' }}" + compose_project_proxy_network: "{{ immich_traefik_network }}" + compose_project_firewall_ports: "{{ immich_tcp_ports }}" + compose_project_firewall_enabled: "{{ immich_routing_mode in ['host', 'traefik_file'] }}" + tags: [immich] diff --git a/roles/immich/templates/compose.override.yaml.j2 b/roles/immich/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..91b8f7e --- /dev/null +++ b/roles/immich/templates/compose.override.yaml.j2 @@ -0,0 +1,6 @@ +services: + immich-server: {} + immich-machine-learning: {} + redis: {} + database: {} + diff --git a/roles/immich/templates/compose.yaml.j2 b/roles/immich/templates/compose.yaml.j2 index 98d6313..0a7f294 100644 --- a/roles/immich/templates/compose.yaml.j2 +++ b/roles/immich/templates/compose.yaml.j2 @@ -3,9 +3,9 @@ services: image: {{ immich_server_image }} restart: unless-stopped env_file: .env -{% if immich_routing_mode == "host" %} +{% if immich_routing_mode in ["host", "traefik_file"] %} ports: - - "{{ immich_http_port }}:2283" + - "{{ nbops_traefik_file_bind_address if immich_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ immich_http_port }}:2283" {% endif %} {% if immich_routing_mode == "traefik" %} # internal HTTP service port: 2283; not published on the Docker host diff --git a/roles/immich/upstream/SOURCE.md b/roles/immich/upstream/SOURCE.md new file mode 100644 index 0000000..88c815a --- /dev/null +++ b/roles/immich/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `immich` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/ipv6nat/tasks/main.yml b/roles/ipv6nat/tasks/main.yml index 9683b09..39a6870 100644 --- a/roles/ipv6nat/tasks/main.yml +++ b/roles/ipv6nat/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/ipv6nat/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/ipv6nat/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/ipv6nat/templates/compose.override.yaml.j2" docker_app_name: "{{ ipv6nat_name }}" docker_app_dir: "{{ ipv6nat_dir }}" docker_app_image: "{{ ipv6nat_image }}" diff --git a/roles/ipv6nat/templates/compose.override.yaml.j2 b/roles/ipv6nat/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/ipv6nat/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/ipv6nat/templates/compose.yaml.j2 b/roles/ipv6nat/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/ipv6nat/templates/compose.yaml.j2 +++ b/roles/ipv6nat/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/ipv6nat/upstream/SOURCE.md b/roles/ipv6nat/upstream/SOURCE.md new file mode 100644 index 0000000..e15517b --- /dev/null +++ b/roles/ipv6nat/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `ipv6nat` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/jellyfin/tasks/main.yml b/roles/jellyfin/tasks/main.yml index 2f042e8..6a53477 100644 --- a/roles/jellyfin/tasks/main.yml +++ b/roles/jellyfin/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/jellyfin/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/jellyfin/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/jellyfin/templates/compose.override.yaml.j2" docker_app_name: "{{ jellyfin_name }}" docker_app_dir: "{{ jellyfin_dir }}" docker_app_image: "{{ jellyfin_image }}" diff --git a/roles/jellyfin/templates/compose.override.yaml.j2 b/roles/jellyfin/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/jellyfin/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/jellyfin/templates/compose.yaml.j2 b/roles/jellyfin/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/jellyfin/templates/compose.yaml.j2 +++ b/roles/jellyfin/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/jellyfin/upstream/SOURCE.md b/roles/jellyfin/upstream/SOURCE.md new file mode 100644 index 0000000..80dcfa7 --- /dev/null +++ b/roles/jellyfin/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `jellyfin` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/jellyseerr/tasks/main.yml b/roles/jellyseerr/tasks/main.yml index eed656b..9d0fa56 100644 --- a/roles/jellyseerr/tasks/main.yml +++ b/roles/jellyseerr/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/jellyseerr/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/jellyseerr/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/jellyseerr/templates/compose.override.yaml.j2" docker_app_name: "{{ jellyseerr_name }}" docker_app_dir: "{{ jellyseerr_dir }}" docker_app_image: "{{ jellyseerr_image }}" diff --git a/roles/jellyseerr/templates/compose.override.yaml.j2 b/roles/jellyseerr/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/jellyseerr/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/jellyseerr/templates/compose.yaml.j2 b/roles/jellyseerr/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/jellyseerr/templates/compose.yaml.j2 +++ b/roles/jellyseerr/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/jellyseerr/upstream/SOURCE.md b/roles/jellyseerr/upstream/SOURCE.md new file mode 100644 index 0000000..c8418bc --- /dev/null +++ b/roles/jellyseerr/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `jellyseerr` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/kavita/tasks/main.yml b/roles/kavita/tasks/main.yml index c6ef68a..62e13e1 100644 --- a/roles/kavita/tasks/main.yml +++ b/roles/kavita/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/kavita/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/kavita/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/kavita/templates/compose.override.yaml.j2" docker_app_name: "{{ kavita_name }}" docker_app_dir: "{{ kavita_dir }}" docker_app_image: "{{ kavita_image }}" diff --git a/roles/kavita/templates/compose.override.yaml.j2 b/roles/kavita/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/kavita/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/kavita/templates/compose.yaml.j2 b/roles/kavita/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/kavita/templates/compose.yaml.j2 +++ b/roles/kavita/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/kavita/upstream/SOURCE.md b/roles/kavita/upstream/SOURCE.md new file mode 100644 index 0000000..0603570 --- /dev/null +++ b/roles/kavita/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `kavita` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/kiwix_serve/tasks/main.yml b/roles/kiwix_serve/tasks/main.yml index 4bbde1a..9b414c8 100644 --- a/roles/kiwix_serve/tasks/main.yml +++ b/roles/kiwix_serve/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/kiwix_serve/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/kiwix_serve/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/kiwix_serve/templates/compose.override.yaml.j2" docker_app_name: "{{ kiwix_serve_name }}" docker_app_dir: "{{ kiwix_serve_dir }}" docker_app_image: "{{ kiwix_serve_image }}" diff --git a/roles/kiwix_serve/templates/compose.override.yaml.j2 b/roles/kiwix_serve/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/kiwix_serve/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/kiwix_serve/templates/compose.yaml.j2 b/roles/kiwix_serve/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/kiwix_serve/templates/compose.yaml.j2 +++ b/roles/kiwix_serve/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/kiwix_serve/upstream/SOURCE.md b/roles/kiwix_serve/upstream/SOURCE.md new file mode 100644 index 0000000..355eda2 --- /dev/null +++ b/roles/kiwix_serve/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `kiwix_serve` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/loki/tasks/main.yml b/roles/loki/tasks/main.yml index cae73f7..987adaa 100644 --- a/roles/loki/tasks/main.yml +++ b/roles/loki/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/loki/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/loki/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/loki/templates/compose.override.yaml.j2" docker_app_name: "{{ loki_name }}" docker_app_dir: "{{ loki_dir }}" docker_app_image: "{{ loki_image }}" diff --git a/roles/loki/templates/compose.override.yaml.j2 b/roles/loki/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/loki/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/loki/templates/compose.yaml.j2 b/roles/loki/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/loki/templates/compose.yaml.j2 +++ b/roles/loki/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/loki/upstream/SOURCE.md b/roles/loki/upstream/SOURCE.md new file mode 100644 index 0000000..ab2c592 --- /dev/null +++ b/roles/loki/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `loki` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/nginx_content/tasks/main.yml b/roles/nginx_content/tasks/main.yml index a349137..59457f2 100644 --- a/roles/nginx_content/tasks/main.yml +++ b/roles/nginx_content/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/nginx_content/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/nginx_content/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/nginx_content/templates/compose.override.yaml.j2" docker_app_name: "{{ nginx_content_name }}" docker_app_dir: "{{ nginx_content_dir }}" docker_app_image: "{{ nginx_content_image }}" diff --git a/roles/nginx_content/templates/compose.override.yaml.j2 b/roles/nginx_content/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/nginx_content/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/nginx_content/templates/compose.yaml.j2 b/roles/nginx_content/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/nginx_content/templates/compose.yaml.j2 +++ b/roles/nginx_content/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/nginx_content/upstream/SOURCE.md b/roles/nginx_content/upstream/SOURCE.md new file mode 100644 index 0000000..24321fe --- /dev/null +++ b/roles/nginx_content/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `nginx_content` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/ntfy/tasks/main.yml b/roles/ntfy/tasks/main.yml index 3e6016a..465e3ec 100644 --- a/roles/ntfy/tasks/main.yml +++ b/roles/ntfy/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/ntfy/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/ntfy/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/ntfy/templates/compose.override.yaml.j2" docker_app_name: "{{ ntfy_name }}" docker_app_dir: "{{ ntfy_dir }}" docker_app_image: "{{ ntfy_image }}" diff --git a/roles/ntfy/templates/compose.override.yaml.j2 b/roles/ntfy/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/ntfy/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/ntfy/templates/compose.yaml.j2 b/roles/ntfy/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/ntfy/templates/compose.yaml.j2 +++ b/roles/ntfy/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/ntfy/upstream/SOURCE.md b/roles/ntfy/upstream/SOURCE.md new file mode 100644 index 0000000..77c4f57 --- /dev/null +++ b/roles/ntfy/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `ntfy` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/ntfy/upstream/docker-compose.yml b/roles/ntfy/upstream/docker-compose.yml new file mode 100644 index 0000000..3583109 --- /dev/null +++ b/roles/ntfy/upstream/docker-compose.yml @@ -0,0 +1,28 @@ +services: + ntfy: + image: binwiederhier/ntfy + container_name: ntfy + command: + - serve + environment: + TZ: Etc/London # optional: set desired timezone + NTFY_BASE_URL: http://ntfy.internal + NTFY_CACHE_FILE: /var/lib/ntfy/cache.db + NTFY_AUTH_FILE: /var/lib/ntfy/auth.db + NTFY_AUTH_DEFAULT_ACCESS: deny-all + NTFY_AUTH_USERS: 'username:base64encodedpassword:admin' # Must escape '$' as '$$' + NTFY_BEHIND_PROXY: true + NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments + NTFY_ENABLE_LOGIN: true + NTFY_UPSTREAM_BASE_URL: https://ntfy.sh + volumes: + - ./cache/ntfy:/var/cache/ntfy + - ./web/ntfy:/etc/ntfy + healthcheck: # optional: remember to adapt the host:port to your environment + test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped + init: true # needed, if healthcheck is used. Prevents zombie processes \ No newline at end of file diff --git a/roles/ocis/handlers/.gitkeep b/roles/ocis/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ocis/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ocis/handlers/main.yml b/roles/ocis/handlers/main.yml deleted file mode 100644 index ebff1a9..0000000 --- a/roles/ocis/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart ocis - community.docker.docker_compose_v2: - project_src: "{{ ocis_dir }}" - state: restarted - diff --git a/roles/ocis/tasks/main.yml b/roles/ocis/tasks/main.yml index d767049..9c8575a 100644 --- a/roles/ocis/tasks/main.yml +++ b/roles/ocis/tasks/main.yml @@ -12,44 +12,20 @@ - "{{ ocis_data_dir }}" tags: [ocis] -- name: Render oCIS environment - ansible.builtin.template: - src: env.j2 - dest: "{{ ocis_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [ocis, config] - -- name: Ensure oCIS proxy network exists - community.docker.docker_network: - name: "{{ ocis_traefik_network }}" - state: present - when: ocis_routing_mode == "traefik" - tags: [ocis, docker] - -- name: Render oCIS Compose file - ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ ocis_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart ocis - tags: [ocis, compose] - -- name: Allow oCIS ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ ocis_tcp_ports }}" - when: ocis_routing_mode == "host" - tags: [ocis, firewall] - -- name: Start oCIS - community.docker.docker_compose_v2: - project_src: "{{ ocis_dir }}" - state: present - tags: [ocis, compose] +- name: Deploy oCIS Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ ocis_dir }}" + compose_project_directories: + - "{{ ocis_dir }}" + - "{{ ocis_config_dir }}" + - "{{ ocis_data_dir }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/ocis/templates/env.j2" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/ocis/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/ocis/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: "{{ ocis_routing_mode == 'traefik' }}" + compose_project_proxy_network: "{{ ocis_traefik_network }}" + compose_project_firewall_ports: "{{ ocis_tcp_ports }}" + compose_project_firewall_enabled: "{{ ocis_routing_mode in ['host', 'traefik_file'] }}" + tags: [ocis] diff --git a/roles/ocis/templates/compose.override.yaml.j2 b/roles/ocis/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..7747f2a --- /dev/null +++ b/roles/ocis/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + ocis: {} + diff --git a/roles/ocis/templates/compose.yaml.j2 b/roles/ocis/templates/compose.yaml.j2 index 4cb13ef..2e5853f 100644 --- a/roles/ocis/templates/compose.yaml.j2 +++ b/roles/ocis/templates/compose.yaml.j2 @@ -4,9 +4,9 @@ services: restart: unless-stopped command: server env_file: .env -{% if ocis_routing_mode == "host" %} +{% if ocis_routing_mode in ["host", "traefik_file"] %} ports: - - "{{ ocis_http_port }}:9200" + - "{{ nbops_traefik_file_bind_address if ocis_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ ocis_http_port }}:9200" {% endif %} {% if ocis_routing_mode == "traefik" %} # internal HTTP service port: 9200; not published on the Docker host diff --git a/roles/ocis/upstream/SOURCE.md b/roles/ocis/upstream/SOURCE.md new file mode 100644 index 0000000..449328a --- /dev/null +++ b/roles/ocis/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `ocis` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/paperless_ngx/tasks/main.yml b/roles/paperless_ngx/tasks/main.yml index f168d2f..b490643 100644 --- a/roles/paperless_ngx/tasks/main.yml +++ b/roles/paperless_ngx/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/paperless_ngx/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/paperless_ngx/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/paperless_ngx/templates/compose.override.yaml.j2" docker_app_name: "{{ paperless_ngx_name }}" docker_app_dir: "{{ paperless_ngx_dir }}" docker_app_image: "{{ paperless_ngx_image }}" diff --git a/roles/paperless_ngx/templates/compose.override.yaml.j2 b/roles/paperless_ngx/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/paperless_ngx/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/paperless_ngx/templates/compose.yaml.j2 b/roles/paperless_ngx/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/paperless_ngx/templates/compose.yaml.j2 +++ b/roles/paperless_ngx/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/paperless_ngx/upstream/SOURCE.md b/roles/paperless_ngx/upstream/SOURCE.md new file mode 100644 index 0000000..fbfb52b --- /dev/null +++ b/roles/paperless_ngx/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `paperless_ngx` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/pelican_panel/tasks/main.yml b/roles/pelican_panel/tasks/main.yml index 5301679..c35c750 100644 --- a/roles/pelican_panel/tasks/main.yml +++ b/roles/pelican_panel/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/pelican_panel/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/pelican_panel/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/pelican_panel/templates/compose.override.yaml.j2" docker_app_name: "{{ pelican_panel_name }}" docker_app_dir: "{{ pelican_panel_dir }}" docker_app_image: "{{ pelican_panel_image }}" diff --git a/roles/pelican_panel/templates/compose.override.yaml.j2 b/roles/pelican_panel/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/pelican_panel/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/pelican_panel/templates/compose.yaml.j2 b/roles/pelican_panel/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/pelican_panel/templates/compose.yaml.j2 +++ b/roles/pelican_panel/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/pelican_panel/upstream/SOURCE.md b/roles/pelican_panel/upstream/SOURCE.md new file mode 100644 index 0000000..0e5d2ee --- /dev/null +++ b/roles/pelican_panel/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `pelican_panel` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/php_fpm/tasks/main.yml b/roles/php_fpm/tasks/main.yml index 1903b60..7b632b0 100644 --- a/roles/php_fpm/tasks/main.yml +++ b/roles/php_fpm/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/php_fpm/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/php_fpm/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/php_fpm/templates/compose.override.yaml.j2" docker_app_name: "{{ php_fpm_name }}" docker_app_dir: "{{ php_fpm_dir }}" docker_app_image: "{{ php_fpm_image }}" diff --git a/roles/php_fpm/templates/compose.override.yaml.j2 b/roles/php_fpm/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/php_fpm/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/php_fpm/templates/compose.yaml.j2 b/roles/php_fpm/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/php_fpm/templates/compose.yaml.j2 +++ b/roles/php_fpm/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/php_fpm/upstream/SOURCE.md b/roles/php_fpm/upstream/SOURCE.md new file mode 100644 index 0000000..78bacf5 --- /dev/null +++ b/roles/php_fpm/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `php_fpm` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/pinchflat/tasks/main.yml b/roles/pinchflat/tasks/main.yml index c199d67..a241b92 100644 --- a/roles/pinchflat/tasks/main.yml +++ b/roles/pinchflat/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/pinchflat/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/pinchflat/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/pinchflat/templates/compose.override.yaml.j2" docker_app_name: "{{ pinchflat_name }}" docker_app_dir: "{{ pinchflat_dir }}" docker_app_image: "{{ pinchflat_image }}" diff --git a/roles/pinchflat/templates/compose.override.yaml.j2 b/roles/pinchflat/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/pinchflat/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/pinchflat/templates/compose.yaml.j2 b/roles/pinchflat/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/pinchflat/templates/compose.yaml.j2 +++ b/roles/pinchflat/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/pinchflat/upstream/SOURCE.md b/roles/pinchflat/upstream/SOURCE.md new file mode 100644 index 0000000..ab991d0 --- /dev/null +++ b/roles/pinchflat/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `pinchflat` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/postgres/tasks/main.yml b/roles/postgres/tasks/main.yml index 542421f..f113a73 100644 --- a/roles/postgres/tasks/main.yml +++ b/roles/postgres/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/postgres/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/postgres/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/postgres/templates/compose.override.yaml.j2" docker_app_name: "{{ postgres_name }}" docker_app_dir: "{{ postgres_dir }}" docker_app_image: "{{ postgres_image }}" diff --git a/roles/postgres/templates/compose.override.yaml.j2 b/roles/postgres/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/postgres/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/postgres/templates/compose.yaml.j2 b/roles/postgres/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/postgres/templates/compose.yaml.j2 +++ b/roles/postgres/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/postgres/upstream/SOURCE.md b/roles/postgres/upstream/SOURCE.md new file mode 100644 index 0000000..9f218ea --- /dev/null +++ b/roles/postgres/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `postgres` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/prometheus/tasks/main.yml b/roles/prometheus/tasks/main.yml index 58bbbab..fc87caf 100644 --- a/roles/prometheus/tasks/main.yml +++ b/roles/prometheus/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/prometheus/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/prometheus/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/prometheus/templates/compose.override.yaml.j2" docker_app_name: "{{ prometheus_name }}" docker_app_dir: "{{ prometheus_dir }}" docker_app_image: "{{ prometheus_image }}" diff --git a/roles/prometheus/templates/compose.override.yaml.j2 b/roles/prometheus/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/prometheus/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/prometheus/templates/compose.yaml.j2 b/roles/prometheus/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/prometheus/templates/compose.yaml.j2 +++ b/roles/prometheus/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/prometheus/upstream/SOURCE.md b/roles/prometheus/upstream/SOURCE.md new file mode 100644 index 0000000..792bfd8 --- /dev/null +++ b/roles/prometheus/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `prometheus` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/pterodactyl/handlers/.gitkeep b/roles/pterodactyl/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pterodactyl/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pterodactyl/handlers/main.yml b/roles/pterodactyl/handlers/main.yml deleted file mode 100644 index 4f50c8a..0000000 --- a/roles/pterodactyl/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart pterodactyl - community.docker.docker_compose_v2: - project_src: "{{ pterodactyl_dir }}" - state: restarted - diff --git a/roles/pterodactyl/tasks/main.yml b/roles/pterodactyl/tasks/main.yml index 6992c9b..eb6788c 100644 --- a/roles/pterodactyl/tasks/main.yml +++ b/roles/pterodactyl/tasks/main.yml @@ -8,44 +8,18 @@ mode: "0755" tags: [pterodactyl] -- name: Render Pterodactyl environment - ansible.builtin.template: - src: env.j2 - dest: "{{ pterodactyl_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [pterodactyl, config] - -- name: Ensure Pterodactyl proxy network exists - community.docker.docker_network: - name: "{{ pterodactyl_traefik_network }}" - state: present - when: pterodactyl_routing_mode == "traefik" - tags: [pterodactyl, docker] - -- name: Render Pterodactyl Compose file - ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ pterodactyl_dir }}/compose.yaml" - owner: root - group: root - mode: "0644" - notify: restart pterodactyl - tags: [pterodactyl, compose] - -- name: Allow Pterodactyl ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ pterodactyl_tcp_ports }}" - when: pterodactyl_routing_mode == "host" - tags: [pterodactyl, firewall] - -- name: Start Pterodactyl - community.docker.docker_compose_v2: - project_src: "{{ pterodactyl_dir }}" - state: present - tags: [pterodactyl, compose] +- name: Deploy Pterodactyl Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ pterodactyl_dir }}" + compose_project_directories: + - "{{ pterodactyl_dir }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/pterodactyl/templates/env.j2" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/pterodactyl/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/pterodactyl/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: "{{ pterodactyl_routing_mode == 'traefik' }}" + compose_project_proxy_network: "{{ pterodactyl_traefik_network }}" + compose_project_firewall_ports: "{{ pterodactyl_tcp_ports }}" + compose_project_firewall_enabled: "{{ pterodactyl_routing_mode in ['host', 'traefik_file'] }}" + tags: [pterodactyl] diff --git a/roles/pterodactyl/templates/compose.override.yaml.j2 b/roles/pterodactyl/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..1529749 --- /dev/null +++ b/roles/pterodactyl/templates/compose.override.yaml.j2 @@ -0,0 +1,5 @@ +services: + panel: {} + database: {} + cache: {} + diff --git a/roles/pterodactyl/templates/compose.yaml.j2 b/roles/pterodactyl/templates/compose.yaml.j2 index 986a754..dd9323c 100644 --- a/roles/pterodactyl/templates/compose.yaml.j2 +++ b/roles/pterodactyl/templates/compose.yaml.j2 @@ -3,9 +3,9 @@ services: image: {{ pterodactyl_panel_image }} restart: unless-stopped env_file: .env -{% if pterodactyl_routing_mode == "host" %} +{% if pterodactyl_routing_mode in ["host", "traefik_file"] %} ports: - - "{{ pterodactyl_http_port }}:80" + - "{{ nbops_traefik_file_bind_address if pterodactyl_routing_mode == 'traefik_file' else '0.0.0.0' }}:{{ pterodactyl_http_port }}:80" {% endif %} {% if pterodactyl_routing_mode == "traefik" %} # internal HTTP service port: 80; not published on the Docker host diff --git a/roles/pterodactyl/upstream/SOURCE.md b/roles/pterodactyl/upstream/SOURCE.md new file mode 100644 index 0000000..f655112 --- /dev/null +++ b/roles/pterodactyl/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `pterodactyl` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/qbittorrent/tasks/main.yml b/roles/qbittorrent/tasks/main.yml index fcddc72..f4ab058 100644 --- a/roles/qbittorrent/tasks/main.yml +++ b/roles/qbittorrent/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/qbittorrent/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/qbittorrent/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/qbittorrent/templates/compose.override.yaml.j2" docker_app_name: "{{ qbittorrent_name }}" docker_app_dir: "{{ qbittorrent_dir }}" docker_app_image: "{{ qbittorrent_image }}" diff --git a/roles/qbittorrent/templates/compose.override.yaml.j2 b/roles/qbittorrent/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/qbittorrent/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/qbittorrent/templates/compose.yaml.j2 b/roles/qbittorrent/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/qbittorrent/templates/compose.yaml.j2 +++ b/roles/qbittorrent/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/qbittorrent/upstream/SOURCE.md b/roles/qbittorrent/upstream/SOURCE.md new file mode 100644 index 0000000..0dceff1 --- /dev/null +++ b/roles/qbittorrent/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `qbittorrent` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/requarks/tasks/main.yml b/roles/requarks/tasks/main.yml index baedde3..050a014 100644 --- a/roles/requarks/tasks/main.yml +++ b/roles/requarks/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/requarks/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/requarks/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/requarks/templates/compose.override.yaml.j2" docker_app_name: "{{ requarks_name }}" docker_app_dir: "{{ requarks_dir }}" docker_app_image: "{{ requarks_image }}" diff --git a/roles/requarks/templates/compose.override.yaml.j2 b/roles/requarks/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/requarks/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/requarks/templates/compose.yaml.j2 b/roles/requarks/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/requarks/templates/compose.yaml.j2 +++ b/roles/requarks/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/requarks/upstream/SOURCE.md b/roles/requarks/upstream/SOURCE.md new file mode 100644 index 0000000..061c52d --- /dev/null +++ b/roles/requarks/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `requarks` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/requarks/upstream/docker-compose.yml b/roles/requarks/upstream/docker-compose.yml new file mode 100644 index 0000000..62e068b --- /dev/null +++ b/roles/requarks/upstream/docker-compose.yml @@ -0,0 +1,28 @@ +services: + wiki: + image: ghcr.io/requarks/wiki:2 + depends_on: + - wiki-db + environment: + DB_TYPE: postgres + DB_HOST: wiki-db + DB_PORT: 5432 + DB_USER: wikijs + DB_PASS: "changeme" + DB_NAME: wiki + restart: unless-stopped + networks: + - default + - web-backend + + wiki-db: + image: postgres:17-alpine + environment: + POSTGRES_DB: wiki + POSTGRES_PASSWORD: "changeme" + POSTGRES_USER: wikijs + logging: + driver: none + restart: unless-stopped + volumes: + - wiki-db-data:/var/lib/postgresql/data \ No newline at end of file diff --git a/roles/syncthing/tasks/main.yml b/roles/syncthing/tasks/main.yml index 3921a86..fab3141 100644 --- a/roles/syncthing/tasks/main.yml +++ b/roles/syncthing/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/syncthing/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/syncthing/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/syncthing/templates/compose.override.yaml.j2" docker_app_name: "{{ syncthing_name }}" docker_app_dir: "{{ syncthing_dir }}" docker_app_image: "{{ syncthing_image }}" diff --git a/roles/syncthing/templates/compose.override.yaml.j2 b/roles/syncthing/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/syncthing/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/syncthing/templates/compose.yaml.j2 b/roles/syncthing/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/syncthing/templates/compose.yaml.j2 +++ b/roles/syncthing/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/syncthing/upstream/SOURCE.md b/roles/syncthing/upstream/SOURCE.md new file mode 100644 index 0000000..b64c82b --- /dev/null +++ b/roles/syncthing/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `syncthing` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/traefik/README.md b/roles/traefik/README.md index 7f99ee1..66dbae7 100644 --- a/roles/traefik/README.md +++ b/roles/traefik/README.md @@ -3,5 +3,23 @@ Deploys Traefik with Docker Compose, a shared proxy network, static config, and UFW rules for HTTP and HTTPS. -Tags: `traefik`, `docker`, `config`, `compose`, `firewall`. +Traefik can use the Docker provider for local label-based routing, the file +provider for central edge-to-internal-Traefik routing, or both: +```yaml +traefik_docker_provider_enabled: false +traefik_file_provider_enabled: true +traefik_dynamic_inventory_group: nbops +``` + +When the file provider is enabled, the role renders +`{{ traefik_dir }}/dynamic/http-services.yml` from `nbops_traefik_file_routes` +declared in host variables across the inventory group. In the normal multi-VM +topology those routes point at each downstream VM's internal Traefik entrypoint, +not directly at application containers. + +Routes can set `servers_transport` to reference entries in +`traefik_dynamic_servers_transports`, which is intended for the future +edge-to-internal mTLS path. + +Tags: `traefik`, `docker`, `config`, `compose`, `firewall`. diff --git a/roles/traefik/defaults/main.yml b/roles/traefik/defaults/main.yml index cd9d78e..2729284 100644 --- a/roles/traefik/defaults/main.yml +++ b/roles/traefik/defaults/main.yml @@ -11,6 +11,12 @@ traefik_acme_email: admin@example.invalid traefik_domain: example.invalid traefik_env: {} traefik_publish_entrypoints: true +traefik_docker_provider_enabled: true +traefik_file_provider_enabled: false +traefik_dynamic_dir: "{{ traefik_dir }}/dynamic" +traefik_dynamic_config_file: "{{ traefik_dynamic_dir }}/http-services.yml" +traefik_dynamic_inventory_group: nbops +traefik_dynamic_servers_transports: [] traefik_forwarded_headers_trusted_ips: [] traefik_tcp_ports: - "{{ traefik_http_port }}" diff --git a/roles/traefik/tasks/main.yml b/roles/traefik/tasks/main.yml index 4396b2a..b16e0f3 100644 --- a/roles/traefik/tasks/main.yml +++ b/roles/traefik/tasks/main.yml @@ -9,6 +9,7 @@ loop: - "{{ traefik_dir }}" - "{{ traefik_dir }}/data" + - "{{ traefik_dynamic_dir }}" tags: [traefik] - name: Create proxy network @@ -17,16 +18,6 @@ state: present tags: [traefik, docker] -- name: Render Traefik environment - ansible.builtin.template: - src: env.j2 - dest: "{{ traefik_dir }}/.env" - owner: root - group: root - mode: "0600" - no_log: true - tags: [traefik, config] - - name: Render Traefik static config ansible.builtin.template: src: traefik.yml.j2 @@ -37,27 +28,31 @@ notify: restart traefik tags: [traefik, config] -- name: Render Traefik Compose file +- name: Render Traefik dynamic HTTP config ansible.builtin.template: - src: compose.yaml.j2 - dest: "{{ traefik_dir }}/compose.yaml" + src: dynamic-http.yml.j2 + dest: "{{ traefik_dynamic_config_file }}" owner: root group: root mode: "0644" + when: traefik_file_provider_enabled | bool notify: restart traefik - tags: [traefik, compose] + tags: [traefik, config] -- name: Allow Traefik TCP ports - community.general.ufw: - rule: allow - port: "{{ item }}" - proto: tcp - loop: "{{ traefik_tcp_ports }}" - when: traefik_publish_entrypoints | bool - tags: [traefik, firewall] - -- name: Start Traefik - community.docker.docker_compose_v2: - project_src: "{{ traefik_dir }}" - state: present - tags: [traefik, compose] +- name: Deploy Traefik Compose project + ansible.builtin.include_role: + name: compose_project + vars: + compose_project_dir: "{{ traefik_dir }}" + compose_project_directories: + - "{{ traefik_dir }}" + - "{{ traefik_dir }}/data" + - "{{ traefik_dynamic_dir }}" + compose_project_env_template_src: "{{ playbook_dir }}/../roles/traefik/templates/env.j2" + compose_project_compose_template_src: "{{ playbook_dir }}/../roles/traefik/templates/compose.yaml.j2" + compose_project_override_template_src: "{{ playbook_dir }}/../roles/traefik/templates/compose.override.yaml.j2" + compose_project_create_proxy_network: true + compose_project_proxy_network: "{{ traefik_network }}" + compose_project_firewall_ports: "{{ traefik_tcp_ports }}" + compose_project_firewall_enabled: "{{ traefik_publish_entrypoints | bool }}" + tags: [traefik] diff --git a/roles/traefik/templates/compose.override.yaml.j2 b/roles/traefik/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..b68a28a --- /dev/null +++ b/roles/traefik/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + traefik: {} + diff --git a/roles/traefik/templates/compose.yaml.j2 b/roles/traefik/templates/compose.yaml.j2 index fe156e0..bc9bf34 100644 --- a/roles/traefik/templates/compose.yaml.j2 +++ b/roles/traefik/templates/compose.yaml.j2 @@ -12,8 +12,13 @@ services: {% endif %} {% endif %} volumes: +{% if traefik_docker_provider_enabled | bool %} - /var/run/docker.sock:/var/run/docker.sock:ro +{% endif %} - ./traefik.yml:/etc/traefik/traefik.yml:ro +{% if traefik_file_provider_enabled | bool %} + - ./dynamic:/etc/traefik/dynamic:ro +{% endif %} - ./data:/data networks: - {{ traefik_network }} diff --git a/roles/traefik/templates/dynamic-http.yml.j2 b/roles/traefik/templates/dynamic-http.yml.j2 new file mode 100644 index 0000000..7b105a4 --- /dev/null +++ b/roles/traefik/templates/dynamic-http.yml.j2 @@ -0,0 +1,81 @@ +{% set ns = namespace(route_count=0) %} +{% for host in groups[traefik_dynamic_inventory_group] | default([]) %} +{% set ns.route_count = ns.route_count + (hostvars[host].nbops_traefik_file_routes | default([]) | length) %} +{% endfor %} +{% if ns.route_count == 0 %} +http: + routers: {} + services: {} +{% else %} +http: + routers: +{% for host in groups[traefik_dynamic_inventory_group] | default([]) %} +{% for route in hostvars[host].nbops_traefik_file_routes | default([]) %} + {{ route.name }}: +{% if route.rule is defined %} + rule: "{{ route.rule }}" +{% else %} + rule: "Host(`{{ route.domain | default(route.name ~ '.' ~ nbops_service_domain_suffix) }}`)" +{% endif %} + entryPoints: + - {{ route.entrypoint | default(nbops_traefik_entrypoint) }} +{% if route.middlewares | default([]) | length > 0 %} + middlewares: +{% for middleware in route.middlewares %} + - {{ middleware }} +{% endfor %} +{% endif %} + service: {{ route.service | default(route.name) }} +{% if route.tls | default(nbops_traefik_tls) | bool %} + tls: + certResolver: {{ route.cert_resolver | default('letsencrypt') }} +{% endif %} +{% endfor %} +{% endfor %} + services: +{% for host in groups[traefik_dynamic_inventory_group] | default([]) %} +{% for route in hostvars[host].nbops_traefik_file_routes | default([]) %} + {{ route.service | default(route.name) }}: + loadBalancer: + servers: +{% if route.url is defined %} + - url: "{{ route.url }}" +{% else %} + - url: "{{ route.scheme | default('http') }}://{{ route.target_host | default(hostvars[host].ansible_host | default(host)) }}:{{ route.port | default(route.service_port | default(route.target_port | default(80))) }}" +{% endif %} +{% if route.pass_host_header | default(true) | bool %} + passHostHeader: true +{% else %} + passHostHeader: false +{% endif %} +{% if route.servers_transport is defined %} + serversTransport: {{ route.servers_transport }} +{% endif %} +{% endfor %} +{% endfor %} +{% if traefik_dynamic_servers_transports | length > 0 %} + serversTransports: +{% for transport in traefik_dynamic_servers_transports %} + {{ transport.name }}: +{% if transport.server_name is defined %} + serverName: "{{ transport.server_name }}" +{% endif %} +{% if transport.insecure_skip_verify | default(false) | bool %} + insecureSkipVerify: true +{% endif %} +{% if transport.root_cas | default([]) | length > 0 %} + rootCAs: +{% for ca in transport.root_cas %} + - {{ ca }} +{% endfor %} +{% endif %} +{% if transport.certificates | default([]) | length > 0 %} + certificates: +{% for certificate in transport.certificates %} + - certFile: {{ certificate.cert_file }} + keyFile: {{ certificate.key_file }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} diff --git a/roles/traefik/templates/traefik.yml.j2 b/roles/traefik/templates/traefik.yml.j2 index 0e0a549..80248e7 100644 --- a/roles/traefik/templates/traefik.yml.j2 +++ b/roles/traefik/templates/traefik.yml.j2 @@ -20,8 +20,15 @@ entryPoints: {% endfor %} {% endif %} providers: +{% if traefik_docker_provider_enabled | bool %} docker: exposedByDefault: false +{% endif %} +{% if traefik_file_provider_enabled | bool %} + file: + directory: /etc/traefik/dynamic + watch: true +{% endif %} certificatesResolvers: letsencrypt: acme: diff --git a/roles/traefik/upstream/SOURCE.md b/roles/traefik/upstream/SOURCE.md new file mode 100644 index 0000000..bf1640f --- /dev/null +++ b/roles/traefik/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `traefik` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/transmission/tasks/main.yml b/roles/transmission/tasks/main.yml index dc9a72a..dd17303 100644 --- a/roles/transmission/tasks/main.yml +++ b/roles/transmission/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/transmission/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/transmission/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/transmission/templates/compose.override.yaml.j2" docker_app_name: "{{ transmission_name }}" docker_app_dir: "{{ transmission_dir }}" docker_app_image: "{{ transmission_image }}" diff --git a/roles/transmission/templates/compose.override.yaml.j2 b/roles/transmission/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/transmission/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/transmission/templates/compose.yaml.j2 b/roles/transmission/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/transmission/templates/compose.yaml.j2 +++ b/roles/transmission/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/transmission/upstream/SOURCE.md b/roles/transmission/upstream/SOURCE.md new file mode 100644 index 0000000..83e1c06 --- /dev/null +++ b/roles/transmission/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `transmission` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/uptime_kuma/tasks/main.yml b/roles/uptime_kuma/tasks/main.yml index 671785d..dd3424e 100644 --- a/roles/uptime_kuma/tasks/main.yml +++ b/roles/uptime_kuma/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/uptime_kuma/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/uptime_kuma/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/uptime_kuma/templates/compose.override.yaml.j2" docker_app_name: "{{ uptime_kuma_name }}" docker_app_dir: "{{ uptime_kuma_dir }}" docker_app_image: "{{ uptime_kuma_image }}" diff --git a/roles/uptime_kuma/templates/compose.override.yaml.j2 b/roles/uptime_kuma/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/uptime_kuma/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/uptime_kuma/templates/compose.yaml.j2 b/roles/uptime_kuma/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/uptime_kuma/templates/compose.yaml.j2 +++ b/roles/uptime_kuma/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/uptime_kuma/upstream/SOURCE.md b/roles/uptime_kuma/upstream/SOURCE.md new file mode 100644 index 0000000..7e7c8f3 --- /dev/null +++ b/roles/uptime_kuma/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `uptime_kuma` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/uptime_kuma/upstream/docker-compose.yml b/roles/uptime_kuma/upstream/docker-compose.yml new file mode 100644 index 0000000..fab327b --- /dev/null +++ b/roles/uptime_kuma/upstream/docker-compose.yml @@ -0,0 +1,10 @@ +services: + uptime-kuma: + image: louislam/uptime-kuma:2 + container_name: uptime-kuma + volumes: + - ./uptime-kuma:/app/data + - /var/run/docker.sock:/var/run/docker.sock + ports: + - 3001:3001 + restart: always \ No newline at end of file diff --git a/roles/valkey/tasks/main.yml b/roles/valkey/tasks/main.yml index 8256543..e8c428f 100644 --- a/roles/valkey/tasks/main.yml +++ b/roles/valkey/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/valkey/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/valkey/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/valkey/templates/compose.override.yaml.j2" docker_app_name: "{{ valkey_name }}" docker_app_dir: "{{ valkey_dir }}" docker_app_image: "{{ valkey_image }}" diff --git a/roles/valkey/templates/compose.override.yaml.j2 b/roles/valkey/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/valkey/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/valkey/templates/compose.yaml.j2 b/roles/valkey/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/valkey/templates/compose.yaml.j2 +++ b/roles/valkey/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/valkey/upstream/SOURCE.md b/roles/valkey/upstream/SOURCE.md new file mode 100644 index 0000000..782e2e2 --- /dev/null +++ b/roles/valkey/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `valkey` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/vaultwarden/tasks/main.yml b/roles/vaultwarden/tasks/main.yml index d23e258..4c31815 100644 --- a/roles/vaultwarden/tasks/main.yml +++ b/roles/vaultwarden/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/vaultwarden/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/vaultwarden/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/vaultwarden/templates/compose.override.yaml.j2" docker_app_name: "{{ vaultwarden_name }}" docker_app_dir: "{{ vaultwarden_dir }}" docker_app_image: "{{ vaultwarden_image }}" diff --git a/roles/vaultwarden/templates/compose.override.yaml.j2 b/roles/vaultwarden/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/vaultwarden/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/vaultwarden/templates/compose.yaml.j2 b/roles/vaultwarden/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/vaultwarden/templates/compose.yaml.j2 +++ b/roles/vaultwarden/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/vaultwarden/upstream/SOURCE.md b/roles/vaultwarden/upstream/SOURCE.md new file mode 100644 index 0000000..9bf3286 --- /dev/null +++ b/roles/vaultwarden/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `vaultwarden` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/roles/wizarr/tasks/main.yml b/roles/wizarr/tasks/main.yml index 63f9cc2..99fc125 100644 --- a/roles/wizarr/tasks/main.yml +++ b/roles/wizarr/tasks/main.yml @@ -5,6 +5,7 @@ vars: docker_app_env_template_src: "{{ playbook_dir }}/../roles/wizarr/templates/env.j2" docker_app_compose_template_src: "{{ playbook_dir }}/../roles/wizarr/templates/compose.yaml.j2" + docker_app_compose_override_template_src: "{{ playbook_dir }}/../roles/wizarr/templates/compose.override.yaml.j2" docker_app_name: "{{ wizarr_name }}" docker_app_dir: "{{ wizarr_dir }}" docker_app_image: "{{ wizarr_image }}" diff --git a/roles/wizarr/templates/compose.override.yaml.j2 b/roles/wizarr/templates/compose.override.yaml.j2 new file mode 100644 index 0000000..dfb8802 --- /dev/null +++ b/roles/wizarr/templates/compose.override.yaml.j2 @@ -0,0 +1,3 @@ +services: + {{ docker_app_name }}: {} + diff --git a/roles/wizarr/templates/compose.yaml.j2 b/roles/wizarr/templates/compose.yaml.j2 index 6de1603..9770fee 100644 --- a/roles/wizarr/templates/compose.yaml.j2 +++ b/roles/wizarr/templates/compose.yaml.j2 @@ -17,10 +17,10 @@ services: {% if docker_app_env | length > 0 %} env_file: .env {% endif %} -{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} +{% if docker_app_routing_mode in ["host", "traefik_file"] and docker_app_ports | length > 0 %} ports: {% for port in docker_app_ports %} - - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" + - "{{ port.bind | default((docker_app_traefik_file_bind_address if docker_app_routing_mode == 'traefik_file' else '0.0.0.0')) }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" {% endfor %} {% endif %} {% if docker_app_routing_mode == "traefik" and docker_app_service_port %} diff --git a/roles/wizarr/upstream/SOURCE.md b/roles/wizarr/upstream/SOURCE.md new file mode 100644 index 0000000..38b2396 --- /dev/null +++ b/roles/wizarr/upstream/SOURCE.md @@ -0,0 +1,7 @@ +# Upstream Snapshot + +This directory stores copied upstream reference files for the `wizarr` role. + +See `docs/upstream-sources.yml` for repository URLs, refs, README paths, Compose paths, and verification status. + +Snapshot files are reference material only. Deployments are generated from this role's `templates/` directory. diff --git a/scripts/vendor-upstream-snapshots.py b/scripts/vendor-upstream-snapshots.py new file mode 100755 index 0000000..8928bce --- /dev/null +++ b/scripts/vendor-upstream-snapshots.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +"""Vendor or test upstream README/Compose snapshots. + +This script intentionally uses only the Python standard library. It parses the +limited YAML shape used by docs/upstream-sources.yml instead of requiring PyYAML. +""" + +from __future__ import annotations + +import argparse +import re +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.parse import urlparse +from urllib.request import Request, urlopen + + +ROOT = Path(__file__).resolve().parents[1] +INVENTORY = ROOT / "docs" / "upstream-sources.yml" +COMPOSE_BLOCK_LANGS = {"", "yaml", "yml", "docker-compose", "compose"} + + +def parse_inline_list(value: str) -> list[str]: + value = value.strip() + if value == "[]": + return [] + if not (value.startswith("[") and value.endswith("]")): + return [value.strip().strip("\"'")] + inner = value[1:-1].strip() + if not inner: + return [] + return [item.strip().strip("\"'") for item in inner.split(",")] + + +def parse_inventory(path: Path) -> dict[str, dict]: + sources: dict[str, dict] = {} + current_role: str | None = None + current_repo: dict | None = None + in_sources = False + in_repositories = False + + for raw_line in path.read_text().splitlines(): + if not raw_line.strip() or raw_line.lstrip().startswith("#"): + continue + + if raw_line == "sources:": + in_sources = True + continue + if not in_sources: + continue + + role_match = re.match(r"^ ([A-Za-z0-9_]+):$", raw_line) + if role_match: + current_role = role_match.group(1) + sources[current_role] = {"repositories": []} + current_repo = None + in_repositories = False + continue + + if current_role is None: + continue + + role_key = re.match(r"^ ([A-Za-z0-9_]+):\s*(.*)$", raw_line) + if role_key: + key, value = role_key.groups() + if key == "repositories": + in_repositories = True + current_repo = None + continue + if key == "notes": + continue + sources[current_role][key] = value.strip().strip("\"'") + continue + + if in_repositories: + repo_start = re.match(r"^ - purpose:\s*(.*)$", raw_line) + if repo_start: + current_repo = {"purpose": repo_start.group(1).strip().strip("\"'")} + sources[current_role]["repositories"].append(current_repo) + continue + + repo_key = re.match(r"^ ([A-Za-z0-9_]+):\s*(.*)$", raw_line) + if repo_key and current_repo is not None: + key, value = repo_key.groups() + value = value.strip() + if key in {"readme_paths", "compose_paths", "compose_urls"}: + current_repo[key] = parse_inline_list(value) + else: + current_repo[key] = value.strip("\"'") + + for role, config in sources.items(): + if "vendor_dir" not in config: + raise ValueError(f"{role}: missing vendor_dir") + config.setdefault("local_compose_file", "") + for repo in config["repositories"]: + if "purpose" not in repo: + raise ValueError(f"{role}: repository missing purpose") + repo.setdefault("readme_paths", []) + repo.setdefault("compose_paths", []) + repo.setdefault("compose_urls", []) + needs_clone = bool(repo["readme_paths"] or repo["compose_paths"]) + has_clone_source = "url" in repo or "ref" in repo + if needs_clone or has_clone_source: + for key in ("url", "ref"): + if key not in repo: + raise ValueError(f"{role}: repository missing {key}") + + return sources + + +def run_capture(command: list[str]) -> tuple[bool, str]: + completed = subprocess.run( + command, + cwd=ROOT, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=False, + ) + return completed.returncode == 0, completed.stdout + + +def copy_if_present(src_root: Path, relative_path: str, dest_root: Path, apply: bool) -> str: + src = src_root / relative_path + if not src.is_file(): + return "missing" + if not apply: + return "found" + + dest = dest_root / relative_path + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dest) + return "copied" + + +def copy_local_file_if_present(relative_path: str, dest_root: Path, apply: bool) -> str: + src = ROOT / relative_path + if not src.is_file(): + return "missing" + if not looks_like_compose(src.read_text(errors="replace")): + return "invalid" + if not apply: + return "found" + + dest = dest_root / "local" / src.name + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dest) + return "copied" + + +def fetch_compose_url(url: str) -> tuple[str, str, str]: + parsed = urlparse(url) + if parsed.scheme not in {"http", "https"}: + return "invalid", "", "unsupported URL scheme" + + try: + request = Request(url, headers={"User-Agent": "nbops-upstream-snapshots/1.0"}) + with urlopen(request, timeout=30) as response: + charset = response.headers.get_content_charset() or "utf-8" + body = response.read().decode(charset, errors="replace") + except HTTPError as exc: + return "missing", "", f"HTTP {exc.code}" + except (OSError, TimeoutError, URLError) as exc: + return "missing", "", str(exc) + + if not looks_like_compose(body): + return "invalid", body, "response is not Compose-shaped YAML" + + return "found", body, "" + + +def compose_url_filename(index: int, total: int) -> str: + if total == 1: + return "compose.from-url.yaml" + return f"compose.from-url-{index}.yaml" + + +def extract_fenced_blocks(markdown: str) -> list[tuple[str, str]]: + pattern = re.compile(r"^```([^\n`]*)\n(.*?)^```", re.MULTILINE | re.DOTALL) + blocks = [] + for match in pattern.finditer(markdown): + lang = match.group(1).strip().split(maxsplit=1)[0].lower() if match.group(1).strip() else "" + blocks.append((lang, match.group(2))) + return blocks + + +def looks_like_compose(text: str) -> bool: + lines = text.splitlines() + for index, line in enumerate(lines): + if re.match(r"^services:\s*(#.*)?$", line): + return any(re.match(r"^ [A-Za-z0-9_.-]+:\s*(#.*)?$", later) for later in lines[index + 1 :]) + return False + + +def readme_compose_candidates(readme_path: Path) -> list[dict]: + if not readme_path.is_file(): + return [] + + candidates = [] + for index, (lang, body) in enumerate(extract_fenced_blocks(readme_path.read_text()), start=1): + if lang not in COMPOSE_BLOCK_LANGS: + continue + if not looks_like_compose(body): + continue + candidates.append({"index": index, "lang": lang or "untagged", "body": body}) + return candidates + + +def print_table(rows: list[list[str]]) -> None: + headers = ["Role", "Status", "Repos", "README", "Compose", "README parse", "Notes"] + widths = [len(header) for header in headers] + for row in rows: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(str(value))) + + def fmt(row: list[str]) -> str: + return " ".join(str(value).ljust(widths[index]) for index, value in enumerate(row)) + + print(fmt(headers)) + print(" ".join("-" * width for width in widths)) + for row in rows: + print(fmt(row)) + + +def snapshot_source(vendor_dir: Path, role: str, status: str, notes: list[str], apply: bool) -> None: + if not apply: + return + text = [ + "# Upstream Snapshot", + "", + f"Role: `{role}`", + "Snapshot source inventory: `docs/upstream-sources.yml`", + f"Snapshot status: `{status}`", + "", + "Fetched repositories:", + *notes, + "", + "These files are reference snapshots only. Deployments are generated from role templates.", + "", + ] + vendor_dir.mkdir(parents=True, exist_ok=True) + (vendor_dir / "SOURCE.md").write_text("\n".join(text)) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Test or vendor upstream README/Compose snapshots.") + parser.add_argument("roles", nargs="*", help="Role names to process. Defaults to all roles.") + parser.add_argument("--apply", action="store_true", help="Copy fetched files into role upstream directories.") + parser.add_argument("--no-parse-readme", action="store_true", help="Do not extract Compose YAML from README blocks.") + parser.add_argument("--list", action="store_true", help="List roles in the upstream inventory and exit.") + parser.add_argument("--validate-inventory", action="store_true", help="Parse the inventory and exit without cloning.") + args = parser.parse_args() + + sources = parse_inventory(INVENTORY) + + if args.list: + print("\n".join(sorted(sources))) + return 0 + + if args.validate_inventory: + print(f"inventory ok: {len(sources)} roles") + return 0 + + selected = {role: config for role, config in sources.items() if not args.roles or role in args.roles} + if not selected: + print(f"no matching roles found for: {', '.join(args.roles)}", file=sys.stderr) + return 2 + + rows: list[list[str]] = [] + failures: list[str] = [] + + for role, config in selected.items(): + vendor_dir = ROOT / config["vendor_dir"] + if args.apply: + vendor_dir.mkdir(parents=True, exist_ok=True) + + repo_count = 0 + readme_found = readme_missing = 0 + compose_found = compose_missing = 0 + readme_candidates = readme_ambiguous = 0 + notes: list[str] = [] + snapshot_notes: list[str] = [] + + for repo in config["repositories"]: + repo_count += 1 + with tempfile.TemporaryDirectory(prefix=f"nbops-upstream-{role}-") as tmp: + tmpdir = Path(tmp) + ok = False + if "url" in repo: + ok, output = run_capture(["git", "clone", "--depth", "1", "--branch", repo["ref"], repo["url"], str(tmpdir)]) + if not ok: + notes.append(f"{repo['purpose']}: clone failed") + failures.append(f"{role}/{repo['purpose']}: {output.splitlines()[-1] if output.splitlines() else 'clone failed'}") + + if ok: + for path in repo["readme_paths"]: + result = copy_if_present(tmpdir, path, vendor_dir / repo["purpose"], args.apply) + if result == "missing": + readme_missing += 1 + else: + readme_found += 1 + snapshot_notes.append(f"- {repo['purpose']} README `{path}`: {result}") + + if args.no_parse_readme: + continue + candidates = readme_compose_candidates(tmpdir / path) + readme_candidates += len(candidates) + if len(candidates) > 1: + readme_ambiguous += 1 + if args.apply and len(candidates) == 1: + dest = vendor_dir / repo["purpose"] / "compose.from-readme.yaml" + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text(candidates[0]["body"]) + + for path in repo["compose_paths"]: + result = copy_if_present(tmpdir, path, vendor_dir / repo["purpose"], args.apply) + if result == "missing": + compose_missing += 1 + else: + compose_found += 1 + snapshot_notes.append(f"- {repo['purpose']} Compose `{path}`: {result}") + + compose_urls = repo["compose_urls"] + for index, url in enumerate(compose_urls, start=1): + result, body, reason = fetch_compose_url(url) + if result == "found": + compose_found += 1 + if args.apply: + dest = vendor_dir / repo["purpose"] / compose_url_filename(index, len(compose_urls)) + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text(body) + result = "copied" + else: + compose_missing += 1 + notes.append(f"{repo['purpose']}: compose URL {result}") + suffix = f" ({reason})" if reason else "" + snapshot_notes.append(f"- {repo['purpose']} Compose URL `{url}`: {result}{suffix}") + + if config["local_compose_file"]: + result = copy_local_file_if_present(config["local_compose_file"], vendor_dir, args.apply) + if result in {"missing", "invalid"}: + compose_missing += 1 + notes.append(f"local compose file {result}") + else: + compose_found += 1 + snapshot_notes.append(f"- local Compose file `{config['local_compose_file']}`: {result}") + + if config.get("status") == "verify": + notes.append("verify source") + if readme_ambiguous: + notes.append("ambiguous README compose") + + status = "error" if any("clone failed" in note for note in notes) else "partial" if readme_missing or compose_missing else "ok" + snapshot_notes.append(f"README Compose candidates: {readme_candidates}") + snapshot_notes.append(f"Ambiguous README files: {readme_ambiguous}") + snapshot_source(vendor_dir, role, config.get("status", "tracked"), snapshot_notes, args.apply) + + rows.append( + [ + role, + status, + str(repo_count), + f"{readme_found}/{readme_found + readme_missing}", + f"{compose_found}/{compose_found + compose_missing}", + str(readme_candidates), + "; ".join(notes), + ] + ) + + print_table(rows) + + if failures: + print("\nFailures:", file=sys.stderr) + for failure in failures: + print(f"- {failure}", file=sys.stderr) + return 1 + + if not args.apply: + print("\nDry run only. Re-run with --apply to copy snapshots into role upstream directories.", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())