feat(inventory): one service inventory drives planner, allow-lists, and seams
rank 3. shared/services.yml is the single source of truth for every deployable service (hosts, unit dirs, units, path claims, tests, scrape/ dns/probe facts, aliases, stacks); scripts/service-map.py is its only reader (hosts/services/allows/plan/seams/owner/units/stack/get/render/ check). deploy-plan.py becomes a thin CLI wrapper; both --service allow-lists (deploy.sh, deploy-remote.sh) and the changed-test mapper are the inventory. Selection semantics: host scoping (a host's tree selects only its own services), the quadlet netns/network coupling graph resolved across directories (gluetun's riders live in their own dirs), stacks, and canonical restart order. Against the pinned old-planner fixture: 921 raw deltas reduced to 17, all intentional (anubis now maps; cross-host paths select nothing; the retired qwen-sharp template plans nothing and the live froggeric template plans frank-archon; dankserver/romm/romm.container, missed by the old filename rule, plans the romm trio). The fixture is regenerated as the new pin. Integrity gates: schema, unit claims, quadlet-file ownership, path-glob liveness, Pi-hole hosts / Prometheus jobs / DNS / homepage probes drift. tests/service_inventory.bats pins all of it plus the plan fixture and the deploy-plan CLI. Allow-list grep seams in hermes_webui/observability_quadlet become behavioral allows calls.
This commit is contained in:
1 parent
156ea71879
commit
bdead570e2
17 files changed
+3435
-699
No files matched your search
@@ -0,0 +1,22 @@
|
||||
# Dankserver stack secrets and configuration.
|
||||
# Copy to .env and fill real values. Never commit real secrets.
|
||||
|
||||
# RomM admin credentials (first boot)
|
||||
ROMM_USERNAME=changeme
|
||||
ROMM_PASSWORD=changeme
|
||||
ROMM_AUTH_SECRET_KEY=changeme
|
||||
|
||||
# RomM database (MariaDB)
|
||||
ROMM_DB_USER=romm
|
||||
ROMM_DB_PASSWD=changeme
|
||||
MARIADB_ROOT_PASSWORD=changeme
|
||||
|
||||
# RomM Redis
|
||||
# (no password needed for local Redis)
|
||||
|
||||
# Copyparty
|
||||
COPAY_PASS=changeme
|
||||
|
||||
# Jellyfin
|
||||
JELLYFIN_PUBLISH_SERVER_URL=http://192.168.0.235:8096
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Dankserver stack — media & library services.
|
||||
# Deploy: cd /home/dank/serverAppConfigs && podman-compose -f dankserver/compose.yaml up -d
|
||||
# See ../docs/Dankserver/ for service documentation.
|
||||
|
||||
networks:
|
||||
appnet:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
# ═══════════════════════════════════════════════
|
||||
# RomM — retro game manager (requires MariaDB + Redis)
|
||||
# ═══════════════════════════════════════════════
|
||||
mariadb:
|
||||
image: docker.io/mariadb:11
|
||||
container_name: romm-mariadb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: "${MARIADB_ROOT_PASSWORD}"
|
||||
MARIADB_DATABASE: romm
|
||||
MARIADB_USER: "${ROMM_DB_USER}"
|
||||
MARIADB_PASSWORD: "${ROMM_DB_PASSWD}"
|
||||
volumes:
|
||||
- /opt/romm/mariadb:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis:
|
||||
image: docker.io/redis:7-alpine
|
||||
container_name: romm-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /opt/romm/redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
romm:
|
||||
image: ghcr.io/rommapp/romm:latest
|
||||
container_name: romm
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ROMM_USERNAME: "${ROMM_USERNAME}"
|
||||
ROMM_PASSWORD: "${ROMM_PASSWORD}"
|
||||
ROMM_AUTH_SECRET_KEY: "${ROMM_AUTH_SECRET_KEY}"
|
||||
DB_HOST: mariadb
|
||||
DB_NAME: romm
|
||||
DB_USER: "${ROMM_DB_USER}"
|
||||
DB_PASSWD: "${ROMM_DB_PASSWD}"
|
||||
REDIS_HOST: redis
|
||||
HASHEOUS_API_ENABLED: "true"
|
||||
ports:
|
||||
- "8085:8080"
|
||||
volumes:
|
||||
- /opt/romm/library:/romm/library
|
||||
- /opt/romm/resources:/romm/resources
|
||||
- /opt/romm/config:/romm/config
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# Copyparty — file sharing and streaming
|
||||
# ═══════════════════════════════════════════════
|
||||
copyparty:
|
||||
image: ghcr.io/9001/copyparty-ac:latest
|
||||
container_name: copyparty
|
||||
restart: unless-stopped
|
||||
# read-only since 2026-08-31: copyparty perms are per-field "letters[,user]"
|
||||
# with no user = every unauthenticated request. Anonymous gets r (browse +
|
||||
# download; the guarded files.franksdank.cloud chain reaches copyparty
|
||||
# unauthenticated after authentik). dank gets ra (admin panel). No w/m/d
|
||||
# anywhere: uploads, moves, deletes rejected server-side. Interface: `--help-accounts` in-image.
|
||||
command: ["-a", "dank:${COPAY_PASS}", "-v", "/w::r:ra,dank"]
|
||||
ports:
|
||||
- "3923:3923"
|
||||
volumes:
|
||||
- /mnt/entropy:/w:ro
|
||||
- /opt/copyparty/cfg:/cfg
|
||||
healthcheck:
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3923/')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# Jellyfin — media server (movies, TV, music)
|
||||
# ═══════════════════════════════════════════════
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8096:8096/tcp"
|
||||
- "8920:8920/tcp"
|
||||
- "7359:7359/udp"
|
||||
- "1900:1900/udp"
|
||||
environment:
|
||||
- JELLYFIN_PublishedServerUrl=http://192.168.0.235:8096
|
||||
- DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
|
||||
volumes:
|
||||
- /opt/jellyfin/config:/config
|
||||
- /opt/jellyfin/cache:/cache
|
||||
- /mnt/entropy:/data:ro
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
group_add:
|
||||
- "992"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# Calibre-Web — e-book web frontend
|
||||
# ═══════════════════════════════════════════════
|
||||
calibre-web:
|
||||
image: lscr.io/linuxserver/calibre-web:0.6.27-ls402
|
||||
container_name: calibre-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PUID: "1000"
|
||||
PGID: "1000"
|
||||
TZ: Etc/UTC
|
||||
OAUTHLIB_RELAX_TOKEN_SCOPE: "1"
|
||||
ports:
|
||||
- "8083:8083"
|
||||
volumes:
|
||||
- /opt/calibre-web/config:/config
|
||||
- /opt/calibre-web/books:/books
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8083/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
|
||||
# node-exporter left this file on 2026-09-16 for
|
||||
# dankserver/observability/quadlet/node-exporter.container: a compose-only
|
||||
# container does not survive a reboot here — it died `Exited (143)` and stayed
|
||||
# dead, leaving Prometheus' node-dankserver job scraping nothing — and the
|
||||
# compose form bound the wildcard address. One owner per container name.
|
||||
Reference in new issue
Block a user