Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce370a5b28 | ||
|
|
bfe4d185bd | ||
|
|
94a9f3aa31 | ||
|
|
d2810ab610 | ||
|
|
7a8cc83385 | ||
|
|
c4623f1629 |
No files matched your search
@@ -20,7 +20,7 @@ Wants=authentik-server.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=authentik-outpost
|
ContainerName=authentik-outpost
|
||||||
Image=ghcr.io/goauthentik/server:2026.8.3
|
Image=ghcr.io/goauthentik/server:2026.8.3@sha256:ab9b4e8cc4ab3f8d1198d2db6aeea66bafea1963b3f2843589e0d163f97d9849
|
||||||
Exec=worker
|
Exec=worker
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=authentik-postgres
|
ContainerName=authentik-postgres
|
||||||
Image=docker.io/library/postgres:16-alpine
|
Image=docker.io/library/postgres:16-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
||||||
Environment=POSTGRES_DB=authentik
|
Environment=POSTGRES_DB=authentik
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Wants=authentik-postgres.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=authentik-server
|
ContainerName=authentik-server
|
||||||
Image=ghcr.io/goauthentik/server:2026.8.3
|
Image=ghcr.io/goauthentik/server:2026.8.3@sha256:ab9b4e8cc4ab3f8d1198d2db6aeea66bafea1963b3f2843589e0d163f97d9849
|
||||||
Exec=server
|
Exec=server
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Wants=authentik-server.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=authentik-worker
|
ContainerName=authentik-worker
|
||||||
Image=ghcr.io/goauthentik/server:2026.8.3
|
Image=ghcr.io/goauthentik/server:2026.8.3@sha256:ab9b4e8cc4ab3f8d1198d2db6aeea66bafea1963b3f2843589e0d163f97d9849
|
||||||
Exec=worker
|
Exec=worker
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ After=network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=gitea-postgres
|
ContainerName=gitea-postgres
|
||||||
Network=gitea.network
|
Network=gitea.network
|
||||||
Image=docker.io/library/postgres:16-alpine
|
Image=docker.io/library/postgres:16-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea
|
||||||
EnvironmentFile=/home/dank/Containers/gitea/env
|
EnvironmentFile=/home/dank/Containers/gitea/env
|
||||||
Environment=POSTGRES_USER=gitea
|
Environment=POSTGRES_USER=gitea
|
||||||
Environment=POSTGRES_PASSWORD=${GITEA_DB_PASSWORD}
|
Environment=POSTGRES_PASSWORD=${GITEA_DB_PASSWORD}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ After=gitea-postgres.service network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=gitea-server
|
ContainerName=gitea-server
|
||||||
Network=gitea.network
|
Network=gitea.network
|
||||||
Image=docker.io/gitea/gitea:latest
|
Image=docker.io/gitea/gitea:latest@sha256:30777c91280d973fa02be48b1ade735bd6859e8f6f7af3b4444d519437852504
|
||||||
EnvironmentFile=/home/dank/Containers/gitea/env
|
EnvironmentFile=/home/dank/Containers/gitea/env
|
||||||
Environment=DATABASE_TYPE=postgres
|
Environment=DATABASE_TYPE=postgres
|
||||||
Environment=DATABASE_HOST=gitea-postgres
|
Environment=DATABASE_HOST=gitea-postgres
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ After=gluetun.service network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=marreta_browser
|
ContainerName=marreta_browser
|
||||||
Network=gluetun.container
|
Network=gluetun.container
|
||||||
Image=docker.io/lightpanda/browser:nightly
|
Image=docker.io/lightpanda/browser:nightly@sha256:979c36ed869f1016b8dcf22939a0633c262f2c30e892a03a9f720eba2ff1931e
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=300
|
TimeoutStartSec=300
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ After=gluetun.service marreta-browser.service network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=marreta
|
ContainerName=marreta
|
||||||
Network=gluetun.container
|
Network=gluetun.container
|
||||||
Image=ghcr.io/manualdousuario/marreta:latest
|
Image=ghcr.io/manualdousuario/marreta:latest@sha256:30f2a34567b79b66da794b4b9423751bbbcd07ceff600d9f6a2d219e7e439362
|
||||||
# Relocate off SearXNG's 8080 inside the shared netns.
|
# Relocate off SearXNG's 8080 inside the shared netns.
|
||||||
Environment=NGINX_HTTP_PORT=8085
|
Environment=NGINX_HTTP_PORT=8085
|
||||||
Environment=APP_NAME=Marreta
|
Environment=APP_NAME=Marreta
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=alertmanager
|
ContainerName=alertmanager
|
||||||
Image=docker.io/prom/alertmanager:latest
|
Image=docker.io/prom/alertmanager:latest@sha256:e9733bafb1bdef9b00e25a21f8f99dc26a22224bf16641ad754d1649f4c3357a
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
User=0:0
|
User=0:0
|
||||||
Exec=--config.file=/etc/alertmanager/alertmanager.yml --storage.path=/alertmanager
|
Exec=--config.file=/etc/alertmanager/alertmanager.yml --storage.path=/alertmanager
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=cadvisor
|
ContainerName=cadvisor
|
||||||
Image=gcr.io/cadvisor/cadvisor:v0.49.1
|
Image=gcr.io/cadvisor/cadvisor:v0.49.1@sha256:3cde6faf0791ebf7b41d6f8ae7145466fed712ea6f252c935294d2608b1af388
|
||||||
PodmanArgs=--pid=host --cgroupns=host
|
PodmanArgs=--pid=host --cgroupns=host
|
||||||
PublishPort=127.0.0.1:8080:8080
|
PublishPort=127.0.0.1:8080:8080
|
||||||
PublishPort=192.168.0.143:8080:8080
|
PublishPort=192.168.0.143:8080:8080
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Wants=network-online.target prometheus.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=grafana
|
ContainerName=grafana
|
||||||
Image=docker.io/grafana/grafana:latest
|
Image=docker.io/grafana/grafana:latest@sha256:dbbc38ea21103cefb50396340cd88afec779c7e070b0a7bf5a75b4ce46651d3f
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
User=0:0
|
User=0:0
|
||||||
PublishPort=127.0.0.1:3002:3000
|
PublishPort=127.0.0.1:3002:3000
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=node-exporter
|
ContainerName=node-exporter
|
||||||
Image=docker.io/prom/node-exporter:latest
|
Image=docker.io/prom/node-exporter:latest@sha256:1b4e4438faca4dd7e001dd445d161a4a2091b0fededa84093b3a8dfeae1f1be0
|
||||||
Network=host
|
Network=host
|
||||||
PodmanArgs=--pid=host
|
PodmanArgs=--pid=host
|
||||||
Exec=--path.procfs=/host/proc --path.sysfs=/host/sys --path.rootfs=/rootfs --web.listen-address=127.0.0.1:9100 --web.listen-address=192.168.0.143:9100
|
Exec=--path.procfs=/host/proc --path.sysfs=/host/sys --path.rootfs=/rootfs --web.listen-address=127.0.0.1:9100 --web.listen-address=192.168.0.143:9100
|
||||||
|
|||||||
@@ -43,14 +43,16 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=prometheus
|
ContainerName=prometheus
|
||||||
Image=docker.io/prom/prometheus:latest
|
Image=docker.io/prom/prometheus:latest@sha256:fa3302279710be2f21a8301ec17a8b4f2301fd829bd113839b3b6b02b6f191a2
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
# Name the podman gateway so scrape jobs can reach same-host targets (the
|
# Same-host scrape targets are dialed at host.containers.internal (the podman
|
||||||
# netavark hairpin gap bars this container from dialing its own host's LAN
|
# gateway): a bridge container cannot dial its own host's LAN IP (netavark
|
||||||
# IP; the cadvisor/gpu jobs and the blackbox scrape all ride this name).
|
# hairpin gap, measured 2026-10-21), and traffic to the gateway lands on host
|
||||||
# Same mechanism the hermes Caddy upstream learned the hard way (Container
|
# loopback / published ports (cadvisor/gpu jobs and the blackbox scrape all
|
||||||
# Host Reachability in the vault).
|
# ride it). The name resolves natively — NO HostAddr key: quadlet 5.7.0 calls
|
||||||
HostAddr=host.containers.internal=169.254.1.2
|
# it unsupported and the WHOLE user quadlet batch then fails to generate,
|
||||||
|
# silently orphaning every other unit (live 2026-10-21: prometheus.service
|
||||||
|
# vanished while its container kept running as an orphan).
|
||||||
User=0:0
|
User=0:0
|
||||||
Exec=--config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus --storage.tsdb.retention.time=30d --web.enable-lifecycle
|
Exec=--config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus --storage.tsdb.retention.time=30d --web.enable-lifecycle
|
||||||
PublishPort=127.0.0.1:9090:9090
|
PublishPort=127.0.0.1:9090:9090
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ After=network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=open-webui
|
ContainerName=open-webui
|
||||||
Network=open-webui.network
|
Network=open-webui.network
|
||||||
Image=ghcr.io/open-webui/open-webui:main
|
Image=ghcr.io/open-webui/open-webui:main@sha256:4860ba005ae8f19f08d1e12b0868855b7b339ea0dad9927f196820fc0de04575
|
||||||
PublishPort=3005:8080
|
PublishPort=3005:8080
|
||||||
Environment=OPENAI_API_BASE_URL=http://192.168.0.143:7770
|
Environment=OPENAI_API_BASE_URL=http://192.168.0.143:7770
|
||||||
Environment=ANONYMIZED_TELEMETRY=False
|
Environment=ANONYMIZED_TELEMETRY=False
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=pihole
|
ContainerName=pihole
|
||||||
Image=docker.io/pihole/pihole:latest
|
Image=docker.io/pihole/pihole:latest@sha256:d34134716fc32038b052a8674c7c6879e0ff21f0e6d8af345166fffa2c27c113
|
||||||
Network=slirp4netns
|
Network=slirp4netns
|
||||||
PublishPort=0.0.0.0:53:53/udp
|
PublishPort=0.0.0.0:53:53/udp
|
||||||
PublishPort=0.0.0.0:53:53/tcp
|
PublishPort=0.0.0.0:53:53/tcp
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ After=searxng-network.service network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=gluetun
|
ContainerName=gluetun
|
||||||
Network=searxng.network
|
Network=searxng.network
|
||||||
Image=docker.io/qmcgaw/gluetun:latest
|
Image=docker.io/qmcgaw/gluetun:latest@sha256:c2ee4191762fca6b6a3c3359a731198a95d9c9af4db32d466e223bf3a84e779b
|
||||||
AddCapability=NET_ADMIN
|
AddCapability=NET_ADMIN
|
||||||
AddDevice=/dev/net/tun:/dev/net/tun
|
AddDevice=/dev/net/tun:/dev/net/tun
|
||||||
Environment=VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
Environment=VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ After=searxng-network.service network.target
|
|||||||
ContainerName=searxng-valkey
|
ContainerName=searxng-valkey
|
||||||
Network=searxng.network
|
Network=searxng.network
|
||||||
NetworkAlias=valkey
|
NetworkAlias=valkey
|
||||||
Image=docker.io/valkey/valkey:9-alpine
|
Image=docker.io/valkey/valkey:9-alpine@sha256:48332870af354a799964c0012ae1194a0bf2bf894eb508f945810596dc2d8d11
|
||||||
Volume=/opt/searxng/valkey:/data
|
Volume=/opt/searxng/valkey:/data
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ After=gluetun.service searxng-valkey.service network.target
|
|||||||
[Container]
|
[Container]
|
||||||
ContainerName=searxng
|
ContainerName=searxng
|
||||||
Network=gluetun.container
|
Network=gluetun.container
|
||||||
Image=ghcr.io/searxng/searxng:latest
|
Image=ghcr.io/searxng/searxng:latest@sha256:5ed0323b5f5f8701264ef65e239a2bd83cb57098f2b971fa30bf79c06eeae271
|
||||||
Environment=SEARXNG_SECRET=${SEARXNG_SECRET}
|
Environment=SEARXNG_SECRET=${SEARXNG_SECRET}
|
||||||
Environment=SEARXNG_BASE_URL=${SEARXNG_BASE_URL}
|
Environment=SEARXNG_BASE_URL=${SEARXNG_BASE_URL}
|
||||||
Volume=/opt/searxng/config:/etc/searxng
|
Volume=/opt/searxng/config:/etc/searxng
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=seerr
|
ContainerName=seerr
|
||||||
Image=ghcr.io/seerr-team/seerr:v3.4.1
|
Image=ghcr.io/seerr-team/seerr:v3.4.1@sha256:f4768de5f616248d723e05891f3345a1402123775d03bf0890dbfedc0831bda1
|
||||||
Network=dankclaw_default
|
Network=dankclaw_default
|
||||||
PublishPort=127.0.0.1:5055:5055
|
PublishPort=127.0.0.1:5055:5055
|
||||||
Volume=/home/dank/Containers/seerr/data:/app/config
|
Volume=/home/dank/Containers/seerr/data:/app/config
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=calibre-server
|
ContainerName=calibre-server
|
||||||
Image=lscr.io/linuxserver/calibre:9.15.0
|
Image=lscr.io/linuxserver/calibre:9.15.0@sha256:59e4b0e036e62f228f8bcfae1ade42f47047719885fe454ddbad38e4597b987b
|
||||||
Network=pasta
|
Network=pasta
|
||||||
EnvironmentFile=/home/dank/Containers/calibre-server/env
|
EnvironmentFile=/home/dank/Containers/calibre-server/env
|
||||||
Environment=PUID=1000
|
Environment=PUID=1000
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=calibre-web
|
ContainerName=calibre-web
|
||||||
Image=lscr.io/linuxserver/calibre-web:0.6.27-ls402
|
Image=lscr.io/linuxserver/calibre-web:0.6.27-ls402@sha256:802729966faed5e32d671cd4f92ba6196e9e754a6c0f740ce1ee9acd86b10e55
|
||||||
Network=pasta
|
Network=pasta
|
||||||
Environment=DOCKER_MODS=linuxserver/mods:universal-calibre
|
Environment=DOCKER_MODS=linuxserver/mods:universal-calibre
|
||||||
Environment=LANG=en_US.UTF-8
|
Environment=LANG=en_US.UTF-8
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=copyparty
|
ContainerName=copyparty
|
||||||
Image=ghcr.io/9001/copyparty-ac:latest
|
# Digest-pinned: upstream stopped publishing version tags after 1.19.15, so
|
||||||
|
# the pin rides :latest@digest (the tag in the reference is cosmetic; the
|
||||||
|
# digest is the pull). Pinned 2026-10-21 at the build whose label is
|
||||||
|
# 1.20.26 - closes GHSA-3c88-p299-7hvr (1.20.25) and GHSA-hg99-jqfp-rf76
|
||||||
|
# (1.20.26); the previously running build was the rolling :latest at
|
||||||
|
# 1.20.24 (issue #73 on command-center).
|
||||||
|
Image=ghcr.io/9001/copyparty-ac:latest@sha256:7f77459160a4bf34fdca79f2965dff4f0c1d7069a3d9cdfb3632acb041dd2f6a
|
||||||
Network=dankserver_appnet
|
Network=dankserver_appnet
|
||||||
EnvironmentFile=/home/dank/Containers/copyparty/env
|
EnvironmentFile=/home/dank/Containers/copyparty/env
|
||||||
Exec=-a ${COPAY_ACCOUNT}:${COPAY_PASSWORD} -v /w::r:ra,dank
|
Exec=-a ${COPAY_ACCOUNT}:${COPAY_PASSWORD} -v /w::r:ra,dank
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=jellyfin
|
ContainerName=jellyfin
|
||||||
Image=ghcr.io/jellyfin/jellyfin:latest
|
Image=ghcr.io/jellyfin/jellyfin:latest@sha256:c3efa69a22f2691fc88d6bb035e87a8560e395a6047f062fb381c61c3185e8f5
|
||||||
Network=dankserver_appnet
|
Network=dankserver_appnet
|
||||||
Environment=DEBIAN_FRONTEND=noninteractive
|
Environment=DEBIAN_FRONTEND=noninteractive
|
||||||
Environment=DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
|
Environment=DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=cadvisor
|
ContainerName=cadvisor
|
||||||
Image=gcr.io/cadvisor/cadvisor:v0.49.1
|
Image=gcr.io/cadvisor/cadvisor:v0.49.1@sha256:3cde6faf0791ebf7b41d6f8ae7145466fed712ea6f252c935294d2608b1af388
|
||||||
PodmanArgs=--pid=host --cgroupns=host
|
PodmanArgs=--pid=host --cgroupns=host
|
||||||
PublishPort=127.0.0.1:8080:8080
|
PublishPort=127.0.0.1:8080:8080
|
||||||
PublishPort=192.168.0.235:8080:8080
|
PublishPort=192.168.0.235:8080:8080
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=node-exporter
|
ContainerName=node-exporter
|
||||||
Image=docker.io/prom/node-exporter:latest
|
Image=docker.io/prom/node-exporter:latest@sha256:1b4e4438faca4dd7e001dd445d161a4a2091b0fededa84093b3a8dfeae1f1be0
|
||||||
Network=host
|
Network=host
|
||||||
PodmanArgs=--pid=host
|
PodmanArgs=--pid=host
|
||||||
Exec=--path.procfs=/host/proc --path.sysfs=/host/sys --path.rootfs=/rootfs --web.listen-address=127.0.0.1:9100 --web.listen-address=192.168.0.235:9100
|
Exec=--path.procfs=/host/proc --path.sysfs=/host/sys --path.rootfs=/rootfs --web.listen-address=127.0.0.1:9100 --web.listen-address=192.168.0.235:9100
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ After=network-online.target
|
|||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
Image=lscr.io/linuxserver/plex:1.43.5.11029-6bf8af3f0-ls329
|
Image=lscr.io/linuxserver/plex:1.43.5.11029-6bf8af3f0-ls329@sha256:8f655bad7dcbc5398a78f70a5af12600b4ed3424f901566ed1eaa6710e9e2334
|
||||||
ContainerName=plex
|
ContainerName=plex
|
||||||
Network=dankserver_appnet:alias=plex
|
Network=dankserver_appnet:alias=plex
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=romm-mariadb
|
ContainerName=romm-mariadb
|
||||||
Image=docker.io/library/mariadb:11
|
Image=docker.io/library/mariadb:11@sha256:f16d7a344413e519dff518f27bdb221388f84b0d274c97c4fb5f2cb0dc1b34fa
|
||||||
Network=dankserver_appnet:alias=mariadb
|
Network=dankserver_appnet:alias=mariadb
|
||||||
EnvironmentFile=/home/dank/Containers/romm/env
|
EnvironmentFile=/home/dank/Containers/romm/env
|
||||||
Environment=MARIADB_DATABASE=romm
|
Environment=MARIADB_DATABASE=romm
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ After=network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=romm-redis
|
ContainerName=romm-redis
|
||||||
Image=docker.io/library/redis:7-alpine
|
Image=docker.io/library/redis:7-alpine@sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499
|
||||||
Network=dankserver_appnet:alias=redis
|
Network=dankserver_appnet:alias=redis
|
||||||
Volume=/home/dank/Containers/romm/redis:/data
|
Volume=/home/dank/Containers/romm/redis:/data
|
||||||
HealthCmd=redis-cli ping
|
HealthCmd=redis-cli ping
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ After=romm-mariadb.service romm-redis.service network.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=romm
|
ContainerName=romm
|
||||||
Image=ghcr.io/rommapp/romm:latest
|
Image=ghcr.io/rommapp/romm:latest@sha256:03295f95a6290e752641f9553ac4198b0e5b71b879d57f7b2371b98f608feb54
|
||||||
Network=dankserver_appnet
|
Network=dankserver_appnet
|
||||||
EnvironmentFile=/home/dank/Containers/romm/env
|
EnvironmentFile=/home/dank/Containers/romm/env
|
||||||
Environment=DB_HOST=mariadb
|
Environment=DB_HOST=mariadb
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ After=network-online.target
|
|||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
Image=lscr.io/linuxserver/tautulli:v2.18.1-ls245
|
Image=lscr.io/linuxserver/tautulli:v2.18.1-ls245@sha256:c66d3afa8f7a701a48dd1b1f57b22eca0483d895d670fe11ecff783f6a9bbd73
|
||||||
ContainerName=tautulli
|
ContainerName=tautulli
|
||||||
Network=dankserver_appnet:alias=tautulli
|
Network=dankserver_appnet:alias=tautulli
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Description=Gluetun VPN tunnel (FrankBook-Pro podman machine)
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=gluetun
|
ContainerName=gluetun
|
||||||
Image=docker.io/qmcgaw/gluetun:latest
|
Image=docker.io/qmcgaw/gluetun:latest@sha256:c2ee4191762fca6b6a3c3359a731198a95d9c9af4db32d466e223bf3a84e779b
|
||||||
AddCapability=NET_ADMIN
|
AddCapability=NET_ADMIN
|
||||||
AddDevice=/dev/net/tun:/dev/net/tun
|
AddDevice=/dev/net/tun:/dev/net/tun
|
||||||
Environment=VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
Environment=VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ After=gluetun.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=lidarr
|
ContainerName=lidarr
|
||||||
Image=lscr.io/linuxserver/lidarr:latest
|
Image=lscr.io/linuxserver/lidarr:latest@sha256:d8549ff9ea29b4015c32257360652c1000c31ea471a2b3f0835ae77292faf35b
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
Environment=PGID=0
|
Environment=PGID=0
|
||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ After=gluetun.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=prowlarr
|
ContainerName=prowlarr
|
||||||
Image=lscr.io/linuxserver/prowlarr:latest
|
Image=lscr.io/linuxserver/prowlarr:latest@sha256:b3b00047ed20f6ce2b16780145cb6a0acb51c9e28ea6aa6f5dcd33c7034f1797
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
Environment=PGID=0
|
Environment=PGID=0
|
||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Network=gluetun.container
|
|||||||
# Pinned to the last 4.x line: Readarr's retired qB client requires the
|
# Pinned to the last 4.x line: Readarr's retired qB client requires the
|
||||||
# qB 4.x login response (HTTP 200, body "Ok."); qB 5.x answers 204 with an
|
# qB 4.x login response (HTTP 200, body "Ok."); qB 5.x answers 204 with an
|
||||||
# empty body, which fails Readarr's auth check. All other clients accept 4.x.
|
# empty body, which fails Readarr's auth check. All other clients accept 4.x.
|
||||||
Image=lscr.io/linuxserver/qbittorrent:4.6.7
|
Image=lscr.io/linuxserver/qbittorrent:4.6.7@sha256:55f15d44396315551f87294a176efae733b16e283f38980308e46073950257c6
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
Environment=PGID=0
|
Environment=PGID=0
|
||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ After=gluetun.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=radarr
|
ContainerName=radarr
|
||||||
Image=lscr.io/linuxserver/radarr:latest
|
Image=lscr.io/linuxserver/radarr:latest@sha256:fc0ae3c50f8128b97e84381903ec8d6845c3a569641cb7117c124376cccb57c1
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
Environment=PGID=0
|
Environment=PGID=0
|
||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ After=gluetun.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=readarr
|
ContainerName=readarr
|
||||||
Image=lscr.io/linuxserver/readarr:amd64-1.0.980
|
Image=lscr.io/linuxserver/readarr:amd64-1.0.980@sha256:a95e82dbb0eb2bb43de4c150f1dab9dd821306c67ea0b418983a957e731f2b61
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
Environment=PGID=0
|
Environment=PGID=0
|
||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ After=gluetun.service
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=sonarr
|
ContainerName=sonarr
|
||||||
Image=lscr.io/linuxserver/sonarr:latest
|
Image=lscr.io/linuxserver/sonarr:latest@sha256:889dcc58c6aea859033985894b519b2e6b1e55d92527ed123f9f52e42ae7c97b
|
||||||
Environment=PUID=0
|
Environment=PUID=0
|
||||||
Environment=PGID=0
|
Environment=PGID=0
|
||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
|
|||||||
@@ -252,16 +252,24 @@ install_signal_relay() {
|
|||||||
# The relay binds the host's LAN IP (the one address a bridge container
|
# The relay binds the host's LAN IP (the one address a bridge container
|
||||||
# can reach as host.containers.internal), so keep the unauthenticated
|
# can reach as host.containers.internal), so keep the unauthenticated
|
||||||
# endpoint off the rest of the LAN: admit loopback, the host itself
|
# endpoint off the rest of the LAN: admit loopback, the host itself
|
||||||
# (its own health probe) and the podman bridge subnet only. Idempotent:
|
# (its own health probe) and the podman bridge subnet only.
|
||||||
# -C before -I.
|
# ORDER IS THE GATE: `iptables -I` inserts at position 1, so the old
|
||||||
|
# shape (-C/-I x3 ACCEPT, then -C/-I the DROP) landed the blanket DROP
|
||||||
|
# ABOVE the ACCEPTs and every probe timed out beside a LISTENing socket
|
||||||
|
# (live 2026-10-21: 159 packets on the DROP, 0 on each ACCEPT; the whole
|
||||||
|
# dankclaw drain aborted at the alertmanager gate twice before the cause
|
||||||
|
# was read off `nft list ruleset`). Rebuild the tail instead of inserting
|
||||||
|
# it: delete any blanket DROP wherever it sits, keep the three ACCEPTs
|
||||||
|
# at the head (-C before -I, unchanged), then APPEND the DROP so it is
|
||||||
|
# last and the ACCEPTs decide first. Converges on poisoned hosts.
|
||||||
|
run_privileged iptables -D INPUT -p tcp --dport 18093 -j DROP 2>/dev/null || true
|
||||||
run_privileged iptables -C INPUT -p tcp --dport 18093 -s 127.0.0.1 -j ACCEPT 2>/dev/null \
|
run_privileged iptables -C INPUT -p tcp --dport 18093 -s 127.0.0.1 -j ACCEPT 2>/dev/null \
|
||||||
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -s 127.0.0.1 -j ACCEPT
|
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -s 127.0.0.1 -j ACCEPT
|
||||||
run_privileged iptables -C INPUT -p tcp --dport 18093 -s 192.168.0.143 -j ACCEPT 2>/dev/null \
|
run_privileged iptables -C INPUT -p tcp --dport 18093 -s 192.168.0.143 -j ACCEPT 2>/dev/null \
|
||||||
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -s 192.168.0.143 -j ACCEPT
|
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -s 192.168.0.143 -j ACCEPT
|
||||||
run_privileged iptables -C INPUT -p tcp --dport 18093 -s 10.89.0.0/16 -j ACCEPT 2>/dev/null \
|
run_privileged iptables -C INPUT -p tcp --dport 18093 -s 10.89.0.0/16 -j ACCEPT 2>/dev/null \
|
||||||
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -s 10.89.0.0/16 -j ACCEPT
|
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -s 10.89.0.0/16 -j ACCEPT
|
||||||
run_privileged iptables -C INPUT -p tcp --dport 18093 -j DROP 2>/dev/null \
|
run_privileged iptables -A INPUT -p tcp --dport 18093 -j DROP
|
||||||
|| run_privileged iptables -I INPUT -p tcp --dport 18093 -j DROP
|
|
||||||
# The interpreter needs a moment to bind after restart; a one-shot curl
|
# The interpreter needs a moment to bind after restart; a one-shot curl
|
||||||
# raced the socket on 2026-09-17 and failed a healthy deploy.
|
# raced the socket on 2026-09-17 and failed a healthy deploy.
|
||||||
local i
|
local i
|
||||||
|
|||||||
@@ -130,10 +130,19 @@ Quickest unblock: `podman compose up -d --force-recreate <svc>`. Durable fix
|
|||||||
|
|
||||||
## 4. Podman 5.x quadlet v1 generator quirks (verified live 2026-08-28)
|
## 4. Podman 5.x quadlet v1 generator quirks (verified live 2026-08-28)
|
||||||
|
|
||||||
- **Hard-fail keys** — the whole unit silently fails to generate
|
- **Hard-fail keys abort the WHOLE BATCH, not one unit** (podman 5.7.0,
|
||||||
(`systemctl --user cat <unit>` shows "could not be found"): any
|
live 2026-10-21): one unsupported key — e.g. `HostAddr=` — makes the
|
||||||
dash-prefixed `[Container]` option (`--shm-size=…` → `unsupported key …
|
generator log `unsupported key … in group 'Container'` plus
|
||||||
in group 'Container'`); `Command=` is unsupported, use `Exec=`.
|
`processing encountered some errors` and generate NOTHING for that
|
||||||
|
manager. The running containers keep going as orphans, so the failure
|
||||||
|
first appears on the next deploy as `Unit <x>.service not found` for a
|
||||||
|
service that is visibly up. Other offenders: any dash-prefixed
|
||||||
|
`[Container]` option (`--shm-size=…`), `Command=` (use `Exec=`).
|
||||||
|
The seam `tests/quadlet_image_pins.bats` fails the build on any
|
||||||
|
generator-rejected key in any host's units.
|
||||||
|
Also: an image whose ENTRYPOINT is the binary takes FLAGS ONLY in
|
||||||
|
`Exec=` — repeating the binary makes it a positional arg
|
||||||
|
(`unexpected /bin/blackbox_exporter`, live 2026-10-21).
|
||||||
- **HealthCmd space-splitting:** the v1 generator splits `HealthCmd` on
|
- **HealthCmd space-splitting:** the v1 generator splits `HealthCmd` on
|
||||||
spaces into the healthcheck argv; a quoted probe
|
spaces into the healthcheck argv; a quoted probe
|
||||||
`/bin/sh -c "pg_isready …"` lands as argv token `"pg_isready` →
|
`/bin/sh -c "pg_isready …"` lands as argv token `"pg_isready` →
|
||||||
@@ -148,6 +157,16 @@ Quickest unblock: `podman compose up -d --force-recreate <svc>`. Durable fix
|
|||||||
"unsupported key"`, `systemctl --user cat probe.service` (shows the
|
"unsupported key"`, `systemctl --user cat probe.service` (shows the
|
||||||
generated ExecStart verbatim), then remove + reload.
|
generated ExecStart verbatim), then remove + reload.
|
||||||
|
|
||||||
|
## 4a. Image pins (fleet law, 2026-10-21)
|
||||||
|
|
||||||
|
Every registry image in a quadlet is digest-pinned (`tag@sha256:…`);
|
||||||
|
locally built images (`localhost/*`, `franksdank/*`) are exempt — their
|
||||||
|
build is the pin. A rolling tag lets a rebuild move under a running
|
||||||
|
service with nobody approving it, and it makes a CVE audit ungradeable.
|
||||||
|
Gate: `tests/quadlet_image_pins.bats`. Where upstream publishes no version
|
||||||
|
tags (copyparty-ac stops at 1.19.15), pin `:latest@sha256:` and name the
|
||||||
|
version the config label carries in the comment beside it.
|
||||||
|
|
||||||
## 5. Rootless Quadlet log retrieval
|
## 5. Rootless Quadlet log retrieval
|
||||||
|
|
||||||
Step 0 — establish ownership: `systemctl --user status <name>.service`
|
Step 0 — establish ownership: `systemctl --user status <name>.service`
|
||||||
|
|||||||
@@ -34,15 +34,16 @@ https://github.com/open-webui/open-webui
|
|||||||
The picker is built from the `model` table in `webui.db` (one row per model)
|
The picker is built from the `model` table in `webui.db` (one row per model)
|
||||||
plus the `model_ids` of each **enabled** OpenAI connection, NOT from live
|
plus the `model_ids` of each **enabled** OpenAI connection, NOT from live
|
||||||
connection discovery — so the picker shows exactly what is seeded there.
|
connection discovery — so the picker shows exactly what is seeded there.
|
||||||
As of 2026-10-07 the picker exposes **exactly one model: `frank-zealot`** *(unverified
|
Since 2026-10-21 the picker exposes **all three fleet models to every
|
||||||
2026-10-18: `webui.db` is the authority; the fleet itself serves all three ids
|
account**: `frank-dragoon`, `frank-archon`, `frank-zealot` (per Frank: open
|
||||||
`frank-dragoon`/`frank-zealot`/`frank-archon`, per the cards in
|
`frank-archon` to everyone; the other two were already public). Proven with
|
||||||
`shared/homepage/services.json`)*.
|
a `role=user` token: `/api/models` lists all three ids and a chat turn to
|
||||||
|
`frank-archon` answers end-to-end.
|
||||||
|
|
||||||
| Connection (`openai.api_configs` idx) | URL as recorded 2026-10-09 | Enabled | `model_ids` | Endpoint page |
|
| Connection (`openai.api_configs` idx) | `openai.api_base_urls` (live 2026-10-21) | Enabled | `model_ids` | Endpoint page |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| 0 | `http://host.containers.internal:7770/v1` | no (2026-10-07) | `frank-dragoon` | [[frank-dragoon]] |
|
| 0 | `http://host.containers.internal:7770/v1` | yes | `frank-dragoon` | [[frank-dragoon]] |
|
||||||
| 1 | `http://192.168.0.235:7770/v1` | no (2026-10-07) | `frank-archon` | [[frank-archon]] |
|
| 1 | `http://192.168.0.235:7770/v1` | yes | `frank-archon` | [[frank-archon]] |
|
||||||
| 2 | `http://host.containers.internal:7771/v1` | yes | `frank-zealot` | [[frank-zealot]] |
|
| 2 | `http://host.containers.internal:7771/v1` | yes | `frank-zealot` | [[frank-zealot]] |
|
||||||
|
|
||||||
The URLs in the first column are the DB's recorded values and predate the 2026-08-22
|
The URLs in the first column are the DB's recorded values and predate the 2026-08-22
|
||||||
@@ -50,9 +51,10 @@ bridge move; on this network they are wrong — see the reachability gotcha belo
|
|||||||
is the corrected law. The picker row for `frank-zealot` was re-keyed on 2026-10-18 after
|
is the corrected law. The picker row for `frank-zealot` was re-keyed on 2026-10-18 after
|
||||||
that day's payload swap (see [[frank-zealot]] for what was deployed and when).
|
that day's payload swap (see [[frank-zealot]] for what was deployed and when).
|
||||||
|
|
||||||
The `model` table carries exactly two decoration rows, `frank-zealot` and
|
The `model` table carries three decoration rows — `frank-zealot`,
|
||||||
`frank-dragoon`. Both the context length and the description live **inside
|
`frank-dragoon`, `frank-archon` (200,000 context, added 2026-10-21).
|
||||||
JSON columns** — `params` holds `{"context": {"length": …}}` and `meta` holds
|
Both the context length and the description live **inside JSON columns** —
|
||||||
|
`params` holds `{"context": {"length": …}}` and `meta` holds
|
||||||
`{"description": …}`; there is no `context_length` or `description` column, so
|
`{"description": …}`; there is no `context_length` or `description` column, so
|
||||||
`sqlite3` one-liners against those names fail. `params.context.length` is a copy
|
`sqlite3` one-liners against those names fail. `params.context.length` is a copy
|
||||||
of the endpoint's per-slot window, so the authoritative number lives on the
|
of the endpoint's per-slot window, so the authoritative number lives on the
|
||||||
@@ -380,6 +382,20 @@ neither.
|
|||||||
name: /selected model/i })`) and the welcome text "How can I help you
|
name: /selected model/i })`) and the welcome text "How can I help you
|
||||||
today?"; that snapshot is also the cheapest proof the picker is populated.
|
today?"; that snapshot is also the cheapest proof the picker is populated.
|
||||||
|
|
||||||
|
- **2026-10-21 — picker opened to all three models, for everyone.** Per
|
||||||
|
Frank: `frank-archon` accessible to everyone. Three-leg repair on
|
||||||
|
`webui.db` (snapshot `webui.db.bak-20261021` first): connection idx 1
|
||||||
|
`enable: true`, a `model` row `frank-archon` (params
|
||||||
|
`{"context":{"length":200000}}`, meta description from the endpoint card),
|
||||||
|
and an `access_grant` row `(resource_type='model', resource_id='frank-archon',
|
||||||
|
principal_id='*', 'read')` — `principal_id='*'` is the public grant, not a
|
||||||
|
role row. Verified as a plain `role=user` token: `/api/models` lists all
|
||||||
|
three ids, chat to `frank-archon` answers. Token-mint gotchas: the backend
|
||||||
|
package imports only with cwd `/app/backend`, and `WEBUI_SECRET_KEY` must
|
||||||
|
come from **PID 1's environ** (`podman exec` sessions do not inherit the
|
||||||
|
app process env; the `.webui_secret_key` file happens to equal it — use it
|
||||||
|
explicitly, `podman exec -e`).
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [[Dankclaw Stack]]
|
- [[Dankclaw Stack]]
|
||||||
|
|||||||
@@ -74,11 +74,19 @@ published ports — node `:9100`, cadvisor `:8080`, gpu `:9401` all answer there
|
|||||||
— while the host's own LAN IP is refused (`192.168.0.143:8080`, `:9401`), so
|
— while the host's own LAN IP is refused (`192.168.0.143:8080`, `:9401`), so
|
||||||
every same-host Prometheus target uses the gateway and only cross-host targets
|
every same-host Prometheus target uses the gateway and only cross-host targets
|
||||||
(dankserver) use plain LAN. Whether LAN works at all is network-specific: the
|
(dankserver) use plain LAN. Whether LAN works at all is network-specific: the
|
||||||
relay at `192.168.0.143:18093` IS reached from alertmanager's network, with
|
relay at `192.168.0.143:18093` is reached from alertmanager's network only
|
||||||
iptables INPUT admitting loopback, the host itself and the podman bridge subnet
|
while `install_signal_relay`'s INPUT rules are in force: loopback, the host
|
||||||
`10.89.0.0/16` before the DROP (`scripts/deploy-remote.sh:255-262`). Never
|
itself and the podman bridge subnet `10.89.0.0/16` accepted **above** a
|
||||||
point a scrape target at a host LAN IP from its own host on the strength of
|
trailing blanket DROP. The law the poisoned host taught (2026-10-21):
|
||||||
one working example; dial the gateway and probe.
|
`iptables -I` prepends, so inserting the DROP last lands it first — the old
|
||||||
|
install order dropped every packet (159 on the DROP, 0 on the ACCEPTs), the
|
||||||
|
deployer's health gate failed beside a LISTENing socket, and the drain
|
||||||
|
aborted twice before `nft list ruleset` named the cause. The install now
|
||||||
|
rebuilds the tail (delete the DROP wherever it sits, ACCEPTs at the head,
|
||||||
|
DROP appended); verify any change to it with the sandboxed bats case, not
|
||||||
|
with `-C` greps — `-C` answers existence, never order. Never point a scrape
|
||||||
|
target at a host LAN IP from its own host on the strength of one working
|
||||||
|
example; dial the gateway and probe.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ FRANKBOOK_UNITS=(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "readarr keeps its pinned image" {
|
@test "readarr keeps its pinned image" {
|
||||||
grep -q '^Image=lscr.io/linuxserver/readarr:amd64-1.0.980$' \
|
# 2026-10-21 fleet law: version tag AND digest (tests/quadlet_image_pins.bats)
|
||||||
|
grep -qE '^Image=lscr\.io/linuxserver/readarr:amd64-1\.0\.980@sha256:[0-9a-f]{64}$' \
|
||||||
"$REPO_ROOT/frankbook-pro/readarr/quadlet/readarr.container" || return 1
|
"$REPO_ROOT/frankbook-pro/readarr/quadlet/readarr.container" || return 1
|
||||||
}
|
}
|
||||||
@@ -559,6 +559,64 @@ assert any('ALERTS' in e for e in exprs), exprs
|
|||||||
grep -q -- '--dport 18093 -j DROP' "$handler"
|
grep -q -- '--dport 18093 -j DROP' "$handler"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "the deployer installs the relay firewall with the ACCEPTs deciding before the DROP" {
|
||||||
|
# Behavioral, run for real against a sandbox that plays the kernel: the
|
||||||
|
# rule set is a LIST and iptables -I prepends, so the shape this function
|
||||||
|
# had until 2026-10-21 (-C/-I ACCEPT x3, then -C/-I the blanket DROP)
|
||||||
|
# landed the DROP at position 1 and every packet died there — the drain
|
||||||
|
# failed its health gate beside a LISTENing socket twice. Seeding the
|
||||||
|
# sandbox with that poisoned order also pins the convergent repair: a
|
||||||
|
# re-deploy must rewrite the tail, not skip because -C finds every rule.
|
||||||
|
local sandbox="$BATS_TEST_TMPDIR/fw"
|
||||||
|
mkdir -p "$sandbox/bin"
|
||||||
|
cat > "$sandbox/bin/iptables" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ordered rule list in $FW_STATE: -C lookup, -I at head, -A at tail, -D delete.
|
||||||
|
state="${FW_STATE:?}"
|
||||||
|
touch "$state"
|
||||||
|
op="$1"; shift
|
||||||
|
case "$op" in
|
||||||
|
-C) grep -Fxq -- "$*" "$state" ;;
|
||||||
|
-I) grep -Fxq -- "$*" "$state" || { { echo "$*"; cat "$state"; } > "$state.new"; mv "$state.new" "$state"; } ;;
|
||||||
|
-A) grep -Fxq -- "$*" "$state" || echo "$*" >> "$state" ;;
|
||||||
|
-D) if grep -Fxq -- "$*" "$state"; then
|
||||||
|
grep -Fxv -- "$*" "$state" > "$state.new"; mv "$state.new" "$state"
|
||||||
|
else exit 1; fi ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "$sandbox/bin/iptables"
|
||||||
|
# Poisoned state exactly as the pre-fix deployer left Dankclaw.
|
||||||
|
printf '%s\n' \
|
||||||
|
'INPUT -p tcp --dport 18093 -j DROP' \
|
||||||
|
'INPUT -p tcp --dport 18093 -s 10.89.0.0/16 -j ACCEPT' \
|
||||||
|
'INPUT -p tcp --dport 18093 -s 192.168.0.143 -j ACCEPT' \
|
||||||
|
'INPUT -p tcp --dport 18093 -s 127.0.0.1 -j ACCEPT' \
|
||||||
|
> "$sandbox/rules"
|
||||||
|
(
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
export PATH="$sandbox/bin:$PATH" FW_STATE="$sandbox/rules" HOME="$sandbox/home"
|
||||||
|
mkdir -p "$HOME"
|
||||||
|
eval "$(sed -n '/^install_signal_relay()/,/^}/p' scripts/deploy-remote.sh)"
|
||||||
|
run_privileged() { "$@"; }
|
||||||
|
systemctl() { :; }
|
||||||
|
curl() { return 0; }
|
||||||
|
sleep() { :; }
|
||||||
|
install_signal_relay
|
||||||
|
)
|
||||||
|
# Exactly one blanket DROP, and it sits after every ACCEPT.
|
||||||
|
[ "$(grep -cF -- 'INPUT -p tcp --dport 18093 -j DROP' "$sandbox/rules")" -eq 1 ]
|
||||||
|
local drop_at total
|
||||||
|
drop_at="$(grep -nF -- 'INPUT -p tcp --dport 18093 -j DROP' "$sandbox/rules" | cut -d: -f1)"
|
||||||
|
total="$(wc -l < "$sandbox/rules")"
|
||||||
|
[ "$drop_at" -eq "$total" ]
|
||||||
|
local s seen
|
||||||
|
for s in 127.0.0.1 192.168.0.143 10.89.0.0/16; do
|
||||||
|
seen="$(grep -nF -- "-s $s -j ACCEPT" "$sandbox/rules" | cut -d: -f1)"
|
||||||
|
[ -n "$seen" ] && [ "$seen" -lt "$drop_at" ]
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
@test "the planner selects alertmanager for its config and relay files" {
|
@test "the planner selects alertmanager for its config and relay files" {
|
||||||
run python3 "$REPO_ROOT/scripts/deploy-plan.py" --target dankclaw \
|
run python3 "$REPO_ROOT/scripts/deploy-plan.py" --target dankclaw \
|
||||||
--paths shared/prometheus/alertmanager.yml
|
--paths shared/prometheus/alertmanager.yml
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ QDIR="$REPO_ROOT/dankclaw/open-webui/quadlet"
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "open-webui uses the CPU image, not :cuda (dankclaw is CPU-only)" {
|
@test "open-webui uses the CPU image, not :cuda (dankclaw is CPU-only)" {
|
||||||
grep -qE '^Image=ghcr\.io/open-webui/open-webui:main$' "$QDIR/open-webui.container" \
|
# 2026-10-21 fleet law: tag AND digest (tests/quadlet_image_pins.bats)
|
||||||
|| { echo "open-webui.container must use the :main (CPU) image"; return 1; }
|
grep -qE '^Image=ghcr\.io/open-webui/open-webui:main@sha256:[0-9a-f]{64}$' "$QDIR/open-webui.container" \
|
||||||
|
|| { echo "open-webui.container must use the :main (CPU) image, digest-pinned"; return 1; }
|
||||||
if grep -qE '^Image=.*:cuda' "$QDIR/open-webui.container"; then
|
if grep -qE '^Image=.*:cuda' "$QDIR/open-webui.container"; then
|
||||||
echo "open-webui.container must not use the :cuda image on a CPU-only host"
|
echo "open-webui.container must not use the :cuda image on a CPU-only host"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
# Seam: fleet-wide quadlet laws learned the hard way.
|
||||||
|
# 1. Every registry image is digest-pinned. A rolling tag (":latest",
|
||||||
|
# ":9-alpine", ":nightly") lets a rebuild move under a running
|
||||||
|
# service at the next restart/reboot with nobody approving it, and it
|
||||||
|
# makes a CVE audit ungradeable (you cannot score a tag). Locally
|
||||||
|
# built images (localhost/*, franksdank/*) are exempt: their build
|
||||||
|
# IS the pin, and they never pull.
|
||||||
|
# 2. No unit carries a key the podman 5.7.0 quadlet generator rejects.
|
||||||
|
# The generator aborts the WHOLE batch on an unsupported key, so one
|
||||||
|
# bad file silently un-generates every other unit in that manager -
|
||||||
|
# live 2026-10-21: HostAddr= in prometheus.container orphaned
|
||||||
|
# prometheus.service while its container kept running, and the next
|
||||||
|
# deploy died on "Unit prometheus.service not found".
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
@test "every registry Image= is digest-pinned across all three hosts" {
|
||||||
|
bad=""
|
||||||
|
while IFS= read -r unit; do
|
||||||
|
while IFS= read -r img; do
|
||||||
|
case "$img" in
|
||||||
|
localhost/*|franksdank/*) continue ;;
|
||||||
|
esac
|
||||||
|
case "$img" in
|
||||||
|
*@sha256:*) ;;
|
||||||
|
*) bad="$bad$unit: $img"$'\n' ;;
|
||||||
|
esac
|
||||||
|
done < <(sed -n 's/^Image=//p' "$unit")
|
||||||
|
done < <(find "$REPO_ROOT"/dankserver "$REPO_ROOT"/dankclaw "$REPO_ROOT"/frankbook-pro \
|
||||||
|
-path '*/quadlet/*.container' -not -path '*.AGENTTRASH*')
|
||||||
|
[ -z "$bad" ] || { printf 'unpinned images:\n%s' "$bad" >&2; return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no unit uses a key the 5.7.0 quadlet generator rejects (batch killer)" {
|
||||||
|
# HostAddr rejected by podman 5.7.0 (live 2026-10-21); Device= and
|
||||||
|
# Command= are the older v1 findings, guarded host-locally too.
|
||||||
|
# Keep this list current with every generator finding.
|
||||||
|
bad=""
|
||||||
|
while IFS= read -r unit; do
|
||||||
|
for key in HostAddr Device Command; do
|
||||||
|
grep -q "^${key}=" "$unit" && bad="$bad$unit: ${key}="$'\n'
|
||||||
|
done
|
||||||
|
done < <(find "$REPO_ROOT"/dankserver "$REPO_ROOT"/dankclaw "$REPO_ROOT"/frankbook-pro \
|
||||||
|
-path '*/quadlet/*.container' -not -path '*.AGENTTRASH*')
|
||||||
|
[ -z "$bad" ] || { printf 'generator-rejected keys:\n%s' "$bad" >&2; return 1; }
|
||||||
|
}
|
||||||
@@ -20,8 +20,9 @@ SEERR_UNIT="$REPO_ROOT/dankclaw/seerr/quadlet/seerr.container"
|
|||||||
grep -q '^Restart=always$' "$SEERR_UNIT"
|
grep -q '^Restart=always$' "$SEERR_UNIT"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "seerr image is pinned to an exact version tag" {
|
@test "seerr image is pinned to an exact version tag and digest" {
|
||||||
grep -Eq '^Image=ghcr\.io/seerr-team/seerr:v[0-9]+\.[0-9]+\.[0-9]+$' "$SEERR_UNIT"
|
# 2026-10-21 fleet law: tag AND digest (tests/quadlet_image_pins.bats)
|
||||||
|
grep -Eq '^Image=ghcr\.io/seerr-team/seerr:v[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}$' "$SEERR_UNIT"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "seerr publishes 5055 on loopback only (guard chain is the user path)" {
|
@test "seerr publishes 5055 on loopback only (guard chain is the user path)" {
|
||||||
|
|||||||
Reference in new issue
Block a user