arr fleet: FrankBook-Pro is the sole deployment, all egress VPN-gated
FrankBook-Pro becomes the only *arr/qBittorrent deployment. Every fleet container (qB + five arrs) now shares gluetun's netns, so all egress — torrent, indexer APIs, metadata lookups — exits the WireGuard tunnel by construction (AllowedIPs=0.0.0.0/0); the arrs' own ports publish on the gluetun unit, none on the arr units themselves. - dankserver/compose.yaml: remove the six arr services (the Aug 20 compose copy — never healthy, superseded; config dirs trashed on the host) - dankclaw/Caddyfile + shared/homepage/services.json: arr vhosts and probes repoint 192.168.0.235 -> 192.168.0.112 (FrankBook-Pro) - frankbook-pro units: five arrs join gluetun netns (Wants/After/Network); gluetun publishes 9696/8989/7878/8686/8787 alongside 8080/6881 - tests/frankbook_quadlet.bats: new VPN egress seam — every arr+qB unit must share gluetun's netns and publish no port; gluetun must publish the whole fleet's ports - deploy-plan.py: gluetun changes carry the whole netns family (a gluetun recreation kills all six joiners); dankserver compose set loses the arrs - AGENTS.md: deployment is complete only when unit tests AND the Playwright E2E suite both pass
This commit is contained in:
@@ -130,6 +130,18 @@ Set `FRANK_SUDO_PW` for headless runs on a host without passwordless sudo
|
|||||||
(remote sudo reads it from stdin via `sudo -S`). The value is never stored
|
(remote sudo reads it from stdin via `sudo -S`). The value is never stored
|
||||||
in this repo.
|
in this repo.
|
||||||
|
|
||||||
|
### Deployment completeness (MANDATORY)
|
||||||
|
|
||||||
|
A deployment is **not complete** when `deploy.sh` exits green. It is complete
|
||||||
|
only when BOTH of the following pass:
|
||||||
|
|
||||||
|
1. **Unit/config tests** — `./tests/run.sh` (all seams green), and
|
||||||
|
2. **Playwright end-to-end tests** — `tests/e2e/run.sh` drives the LIVE fleet
|
||||||
|
(homepage, Anubis/Authentik guard chain, guarded vhosts) in a real
|
||||||
|
browser. The E2E suite is deliberately excluded from `./tests/run.sh`
|
||||||
|
because it needs live hosts and credentials, so it MUST be invoked
|
||||||
|
explicitly after every behavior-affecting deploy.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph LR
|
graph LR
|
||||||
A[Edit locally] --> B[Tests pass]
|
A[Edit locally] --> B[Tests pass]
|
||||||
|
|||||||
+12
-12
@@ -241,27 +241,27 @@ jelly.franksdank.cloud {
|
|||||||
# --- *arr Stack (exempt by construction: own auth) ---
|
# --- *arr Stack (exempt by construction: own auth) ---
|
||||||
|
|
||||||
prowlarr.franksdank.cloud {
|
prowlarr.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.235:9696
|
reverse_proxy 192.168.0.112:9696
|
||||||
}
|
}
|
||||||
|
|
||||||
radarr.franksdank.cloud {
|
radarr.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.235:7878
|
reverse_proxy 192.168.0.112:7878
|
||||||
}
|
}
|
||||||
|
|
||||||
sonarr.franksdank.cloud {
|
sonarr.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.235:8989
|
reverse_proxy 192.168.0.112:8989
|
||||||
}
|
}
|
||||||
|
|
||||||
lidarr.franksdank.cloud {
|
lidarr.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.235:8686
|
reverse_proxy 192.168.0.112:8686
|
||||||
}
|
}
|
||||||
|
|
||||||
readarr.franksdank.cloud {
|
readarr.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.235:8787
|
reverse_proxy 192.168.0.112:8787
|
||||||
}
|
}
|
||||||
|
|
||||||
qbittorrent.franksdank.cloud {
|
qbittorrent.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.235:8080
|
reverse_proxy 192.168.0.112:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Internal listener: auth decision layer ---------------------------------
|
# --- Internal listener: auth decision layer ---------------------------------
|
||||||
@@ -354,32 +354,32 @@ http://:8017 {
|
|||||||
|
|
||||||
@prowlarr host prowlarr.franksdank.cloud
|
@prowlarr host prowlarr.franksdank.cloud
|
||||||
handle @prowlarr {
|
handle @prowlarr {
|
||||||
reverse_proxy 192.168.0.235:9696
|
reverse_proxy 192.168.0.112:9696
|
||||||
}
|
}
|
||||||
|
|
||||||
@radarr host radarr.franksdank.cloud
|
@radarr host radarr.franksdank.cloud
|
||||||
handle @radarr {
|
handle @radarr {
|
||||||
reverse_proxy 192.168.0.235:7878
|
reverse_proxy 192.168.0.112:7878
|
||||||
}
|
}
|
||||||
|
|
||||||
@sonarr host sonarr.franksdank.cloud
|
@sonarr host sonarr.franksdank.cloud
|
||||||
handle @sonarr {
|
handle @sonarr {
|
||||||
reverse_proxy 192.168.0.235:8989
|
reverse_proxy 192.168.0.112:8989
|
||||||
}
|
}
|
||||||
|
|
||||||
@lidarr host lidarr.franksdank.cloud
|
@lidarr host lidarr.franksdank.cloud
|
||||||
handle @lidarr {
|
handle @lidarr {
|
||||||
reverse_proxy 192.168.0.235:8686
|
reverse_proxy 192.168.0.112:8686
|
||||||
}
|
}
|
||||||
|
|
||||||
@readarr host readarr.franksdank.cloud
|
@readarr host readarr.franksdank.cloud
|
||||||
handle @readarr {
|
handle @readarr {
|
||||||
reverse_proxy 192.168.0.235:8787
|
reverse_proxy 192.168.0.112:8787
|
||||||
}
|
}
|
||||||
|
|
||||||
@qbittorrent host qbittorrent.franksdank.cloud
|
@qbittorrent host qbittorrent.franksdank.cloud
|
||||||
handle @qbittorrent {
|
handle @qbittorrent {
|
||||||
reverse_proxy 192.168.0.235:8080
|
reverse_proxy 192.168.0.112:8080
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,5 +20,3 @@ COPAY_PASS=changeme
|
|||||||
# Jellyfin
|
# Jellyfin
|
||||||
JELLYFIN_PUBLISH_SERVER_URL=http://192.168.0.235:8096
|
JELLYFIN_PUBLISH_SERVER_URL=http://192.168.0.235:8096
|
||||||
|
|
||||||
# *arr Stack
|
|
||||||
ARR_TZ=America/New_York
|
|
||||||
|
|||||||
@@ -146,109 +146,6 @@ services:
|
|||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════
|
|
||||||
# *arr Stack — automated media download
|
|
||||||
# ═══════════════════════════════════════════════
|
|
||||||
prowlarr:
|
|
||||||
image: lscr.io/linuxserver/prowlarr:latest
|
|
||||||
container_name: prowlarr
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- TZ=America/Los_Angeles
|
|
||||||
- UMASK_SET=022
|
|
||||||
volumes:
|
|
||||||
- /mnt/entropy/downloads:/downloads:ro
|
|
||||||
- /opt/prowlarr/config:/config
|
|
||||||
networks: [appnet]
|
|
||||||
ports:
|
|
||||||
- "9696:9696"
|
|
||||||
|
|
||||||
qbittorrent:
|
|
||||||
image: lscr.io/linuxserver/qbittorrent:latest
|
|
||||||
container_name: qbittorrent
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- TZ=America/Los_Angeles
|
|
||||||
- UMASK_SET=022
|
|
||||||
volumes:
|
|
||||||
- /opt/qbittorrent/config:/config
|
|
||||||
- /mnt/entropy/downloads:/downloads
|
|
||||||
- /mnt/entropy/downloads/incomplete:/downloads/incomplete
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
networks: [appnet]
|
|
||||||
|
|
||||||
radarr:
|
|
||||||
image: lscr.io/linuxserver/radarr:latest
|
|
||||||
container_name: radarr
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- TZ=America/Los_Angeles
|
|
||||||
- UMASK_SET=022
|
|
||||||
volumes:
|
|
||||||
- /opt/radarr/config:/config
|
|
||||||
- /mnt/entropy/Movies:/movies
|
|
||||||
- /mnt/entropy/downloads:/downloads
|
|
||||||
ports:
|
|
||||||
- "7878:7878"
|
|
||||||
networks: [appnet]
|
|
||||||
|
|
||||||
sonarr:
|
|
||||||
image: lscr.io/linuxserver/sonarr:latest
|
|
||||||
container_name: sonarr
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- TZ=America/Los_Angeles
|
|
||||||
- UMASK_SET=022
|
|
||||||
volumes:
|
|
||||||
- /opt/sonarr/config:/config
|
|
||||||
- /mnt/entropy/TVShows:/tv
|
|
||||||
- /mnt/entropy/downloads:/downloads
|
|
||||||
ports:
|
|
||||||
- "8989:8989"
|
|
||||||
networks: [appnet]
|
|
||||||
|
|
||||||
lidarr:
|
|
||||||
image: lscr.io/linuxserver/lidarr:latest
|
|
||||||
container_name: lidarr
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- TZ=America/Los_Angeles
|
|
||||||
- UMASK_SET=022
|
|
||||||
volumes:
|
|
||||||
- /opt/lidarr/config:/config
|
|
||||||
- /mnt/entropy/Music:/music
|
|
||||||
- /mnt/entropy/downloads:/downloads
|
|
||||||
ports:
|
|
||||||
- "8686:8686"
|
|
||||||
networks: [appnet]
|
|
||||||
|
|
||||||
readarr:
|
|
||||||
image: lscr.io/linuxserver/readarr:amd64-1.0.980
|
|
||||||
container_name: readarr
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- TZ=America/Los_Angeles
|
|
||||||
- UMASK_SET=022
|
|
||||||
volumes:
|
|
||||||
- /opt/readarr/config:/config
|
|
||||||
- /mnt/entropy/eBooks:/books
|
|
||||||
- /mnt/entropy/downloads:/downloads
|
|
||||||
ports:
|
|
||||||
- "8787:8787"
|
|
||||||
networks: [appnet]
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════
|
# ═══════════════════════════════════════════════
|
||||||
# Node Exporter — host metrics
|
# Node Exporter — host metrics
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
# (installed by the frankbook-pro handler in scripts/deploy-remote.sh —
|
# (installed by the frankbook-pro handler in scripts/deploy-remote.sh —
|
||||||
# install_quadlet targets the Linux hosts' systemctl --user, not the VM).
|
# install_quadlet targets the Linux hosts' systemctl --user, not the VM).
|
||||||
#
|
#
|
||||||
# WireGuard tunnel to ProtonVPN. qBittorrent shares this container's network
|
# WireGuard tunnel to ProtonVPN. qBittorrent and all five *arrs share this
|
||||||
# namespace (Network=gluetun.container) so torrent egress never touches the
|
# container's network namespace (Network=gluetun.container) so every fleet
|
||||||
# residential IP. qBittorrent's WebUI and BitTorrent ports publish HERE — a
|
# container's egress — torrent traffic, indexer APIs, metadata lookups —
|
||||||
# netns-sharer publishes no ports of its own (same invariant as the dankclaw
|
# exits ONLY through the tunnel (AllowedIPs=0.0.0.0/0). Every fleet port
|
||||||
# searxng/gluetun pair). Credentials live in
|
# publishes HERE; a netns-sharer publishes no ports of its own (same
|
||||||
|
# invariant as the dankclaw searxng/gluetun pair). Credentials live in
|
||||||
# /home/core/Containers/gluetun/env inside the VM (mode 0600, never
|
# /home/core/Containers/gluetun/env inside the VM (mode 0600, never
|
||||||
# committed); the private key is Mac-tunnel-specific and MUST NOT be copied
|
# committed); the private key is Mac-tunnel-specific and MUST NOT be copied
|
||||||
# from dankclaw — one WireGuard peer key cannot serve two machines at once.
|
# from dankclaw — one WireGuard peer key cannot serve two machines at once.
|
||||||
@@ -33,6 +34,11 @@ Environment=WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=${WIREGUARD_PERSISTENT_KEEPA
|
|||||||
PublishPort=8080:8080
|
PublishPort=8080:8080
|
||||||
PublishPort=6881:6881
|
PublishPort=6881:6881
|
||||||
PublishPort=6881:6881/udp
|
PublishPort=6881:6881/udp
|
||||||
|
PublishPort=9696:9696
|
||||||
|
PublishPort=8989:8989
|
||||||
|
PublishPort=7878:7878
|
||||||
|
PublishPort=8686:8686
|
||||||
|
PublishPort=8787:8787
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
EnvironmentFile=/home/core/Containers/gluetun/env
|
EnvironmentFile=/home/core/Containers/gluetun/env
|
||||||
|
|||||||
@@ -3,10 +3,15 @@
|
|||||||
# Install path is INSIDE the machine VM:
|
# Install path is INSIDE the machine VM:
|
||||||
# /home/core/.config/containers/systemd/lidarr.container
|
# /home/core/.config/containers/systemd/lidarr.container
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# Joins gluetun's network namespace: all egress exits through the WireGuard
|
||||||
|
# tunnel; 8686 publishes on the gluetun unit.
|
||||||
# Grab-only: no media binds; config migrated from dankserver's retired
|
# Grab-only: no media binds; config migrated from dankserver's retired
|
||||||
# bare container.
|
# bare container.
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Lidarr (FrankBook-Pro)
|
Description=Lidarr (FrankBook-Pro)
|
||||||
|
Wants=gluetun.service
|
||||||
|
After=gluetun.service
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=lidarr
|
ContainerName=lidarr
|
||||||
@@ -16,7 +21,7 @@ Environment=PGID=0
|
|||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
Environment=UMASK_SET=022
|
Environment=UMASK_SET=022
|
||||||
Volume=/Volumes/COMPLETE/lidarr-data:/config
|
Volume=/Volumes/COMPLETE/lidarr-data:/config
|
||||||
PublishPort=8686:8686
|
Network=gluetun.container
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=900
|
TimeoutStartSec=900
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
# Install path is INSIDE the machine VM:
|
# Install path is INSIDE the machine VM:
|
||||||
# /home/core/.config/containers/systemd/prowlarr.container
|
# /home/core/.config/containers/systemd/prowlarr.container
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# Joins gluetun's network namespace: all egress (indexer APIs, metadata
|
||||||
|
# lookups) exits through the WireGuard tunnel; 9696 publishes on the
|
||||||
|
# gluetun unit. Same-netns targets (qB, the arrs) are loopback addresses.
|
||||||
# Arr on the Mac is grab-only: indexer + app-sync management lives here, the
|
# Arr on the Mac is grab-only: indexer + app-sync management lives here, the
|
||||||
# arrs never auto-import media (the user reviews completed torrents in
|
# arrs never auto-import media (the user reviews completed torrents in
|
||||||
# /Volumes/COMPLETE/"Finished Torrents" and moves files manually into the
|
# /Volumes/COMPLETE/"Finished Torrents" and moves files manually into the
|
||||||
@@ -11,9 +15,12 @@
|
|||||||
# Config is migrated from dankserver's retired bare container
|
# Config is migrated from dankserver's retired bare container
|
||||||
# (tar-archived first, per the Phase 4 cutover note) — indexers and app
|
# (tar-archived first, per the Phase 4 cutover note) — indexers and app
|
||||||
# settings survive; stale host pointers (old qbit at 192.168.0.235:8080)
|
# settings survive; stale host pointers (old qbit at 192.168.0.235:8080)
|
||||||
# are re-pointed to the Mac's qbit via its API after first boot.
|
# are re-pointed to loopback qBittorrent (127.0.0.1:8080, same netns) via
|
||||||
|
# its API after first boot.
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Prowlarr (FrankBook-Pro)
|
Description=Prowlarr (FrankBook-Pro)
|
||||||
|
Wants=gluetun.service
|
||||||
|
After=gluetun.service
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=prowlarr
|
ContainerName=prowlarr
|
||||||
@@ -23,7 +30,7 @@ Environment=PGID=0
|
|||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
Environment=UMASK_SET=022
|
Environment=UMASK_SET=022
|
||||||
Volume=/Volumes/COMPLETE/prowlarr-data:/config
|
Volume=/Volumes/COMPLETE/prowlarr-data:/config
|
||||||
PublishPort=9696:9696
|
Network=gluetun.container
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=900
|
TimeoutStartSec=900
|
||||||
|
|||||||
@@ -3,10 +3,15 @@
|
|||||||
# Install path is INSIDE the machine VM:
|
# Install path is INSIDE the machine VM:
|
||||||
# /home/core/.config/containers/systemd/radarr.container
|
# /home/core/.config/containers/systemd/radarr.container
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# Joins gluetun's network namespace: all egress exits through the WireGuard
|
||||||
|
# tunnel; 7878 publishes on the gluetun unit.
|
||||||
# Grab-only: no media binds; config migrated from dankserver's retired
|
# Grab-only: no media binds; config migrated from dankserver's retired
|
||||||
# bare container.
|
# bare container.
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Radarr (FrankBook-Pro)
|
Description=Radarr (FrankBook-Pro)
|
||||||
|
Wants=gluetun.service
|
||||||
|
After=gluetun.service
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=radarr
|
ContainerName=radarr
|
||||||
@@ -16,7 +21,7 @@ Environment=PGID=0
|
|||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
Environment=UMASK_SET=022
|
Environment=UMASK_SET=022
|
||||||
Volume=/Volumes/COMPLETE/radarr-data:/config
|
Volume=/Volumes/COMPLETE/radarr-data:/config
|
||||||
PublishPort=7878:7878
|
Network=gluetun.container
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=900
|
TimeoutStartSec=900
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
# Install path is INSIDE the machine VM:
|
# Install path is INSIDE the machine VM:
|
||||||
# /home/core/.config/containers/systemd/readarr.container
|
# /home/core/.config/containers/systemd/readarr.container
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# Joins gluetun's network namespace: all egress exits through the WireGuard
|
||||||
|
# tunnel; 8787 publishes on the gluetun unit.
|
||||||
# Grab-only: no media binds; config migrated from dankserver's retired
|
# Grab-only: no media binds; config migrated from dankserver's retired
|
||||||
# bare container.
|
# bare container.
|
||||||
#
|
#
|
||||||
@@ -11,6 +14,8 @@
|
|||||||
# of a broken 1.x nightly for Readarr; the pin avoids surprises.
|
# of a broken 1.x nightly for Readarr; the pin avoids surprises.
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Readarr (FrankBook-Pro)
|
Description=Readarr (FrankBook-Pro)
|
||||||
|
Wants=gluetun.service
|
||||||
|
After=gluetun.service
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=readarr
|
ContainerName=readarr
|
||||||
@@ -20,7 +25,7 @@ Environment=PGID=0
|
|||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
Environment=UMASK_SET=022
|
Environment=UMASK_SET=022
|
||||||
Volume=/Volumes/COMPLETE/readarr-data:/config
|
Volume=/Volumes/COMPLETE/readarr-data:/config
|
||||||
PublishPort=8787:8787
|
Network=gluetun.container
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=900
|
TimeoutStartSec=900
|
||||||
|
|||||||
@@ -3,11 +3,16 @@
|
|||||||
# Install path is INSIDE the machine VM:
|
# Install path is INSIDE the machine VM:
|
||||||
# /home/core/.config/containers/systemd/sonarr.container
|
# /home/core/.config/containers/systemd/sonarr.container
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# Joins gluetun's network namespace: all egress exits through the WireGuard
|
||||||
|
# tunnel; 8989 publishes on the gluetun unit.
|
||||||
# Grab-only: no media binds (the TV library lives on dankserver's entropy
|
# Grab-only: no media binds (the TV library lives on dankserver's entropy
|
||||||
# disk, which the Mac never mounts); config migrated from dankserver's
|
# disk, which the Mac never mounts); config migrated from dankserver's
|
||||||
# retired bare container.
|
# retired bare container.
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Sonarr (FrankBook-Pro)
|
Description=Sonarr (FrankBook-Pro)
|
||||||
|
Wants=gluetun.service
|
||||||
|
After=gluetun.service
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=sonarr
|
ContainerName=sonarr
|
||||||
@@ -17,7 +22,7 @@ Environment=PGID=0
|
|||||||
Environment=TZ=America/Los_Angeles
|
Environment=TZ=America/Los_Angeles
|
||||||
Environment=UMASK_SET=022
|
Environment=UMASK_SET=022
|
||||||
Volume=/Volumes/COMPLETE/sonarr-data:/config
|
Volume=/Volumes/COMPLETE/sonarr-data:/config
|
||||||
PublishPort=8989:8989
|
Network=gluetun.container
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=900
|
TimeoutStartSec=900
|
||||||
|
|||||||
@@ -147,12 +147,6 @@ DANKSERVER_COMPOSE_SERVICES = {
|
|||||||
"copyparty",
|
"copyparty",
|
||||||
"jellyfin",
|
"jellyfin",
|
||||||
"calibre-web",
|
"calibre-web",
|
||||||
"prowlarr",
|
|
||||||
"qbittorrent",
|
|
||||||
"radarr",
|
|
||||||
"sonarr",
|
|
||||||
"lidarr",
|
|
||||||
"readarr",
|
|
||||||
}
|
}
|
||||||
COMPOSE_NAME_MAP = {"mariadb": "romm-mariadb", "redis": "romm-redis"}
|
COMPOSE_NAME_MAP = {"mariadb": "romm-mariadb", "redis": "romm-redis"}
|
||||||
|
|
||||||
@@ -341,9 +335,11 @@ def services_for_path(path: str, target: str) -> set[str]:
|
|||||||
if len(parts) >= 3 and parts[2] == "quadlet":
|
if len(parts) >= 3 and parts[2] == "quadlet":
|
||||||
name = parts[3].removesuffix(".container")
|
name = parts[3].removesuffix(".container")
|
||||||
if name == "gluetun":
|
if name == "gluetun":
|
||||||
# qBittorrent shares gluetun's network namespace; a gluetun
|
# Every *arr + qBittorrent container shares gluetun's
|
||||||
# recreation requires both units to be restarted.
|
# network namespace; a gluetun recreation kills all of
|
||||||
_add(services, "gluetun", "qbittorrent")
|
# them, so the plan restarts the whole netns family.
|
||||||
|
_add(services, "gluetun", "qbittorrent",
|
||||||
|
"prowlarr", "sonarr", "radarr", "lidarr", "readarr")
|
||||||
elif name in FRANKBOOK_SERVICES:
|
elif name in FRANKBOOK_SERVICES:
|
||||||
_add(services, name)
|
_add(services, name)
|
||||||
return services
|
return services
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
"description": "TV series library and automation.",
|
"description": "TV series library and automation.",
|
||||||
"url": "http://sonarr.franksdank.cloud",
|
"url": "http://sonarr.franksdank.cloud",
|
||||||
"docs": "",
|
"docs": "",
|
||||||
"probe": "192.168.0.235:8989",
|
"probe": "192.168.0.112:8989",
|
||||||
"method": "http",
|
"method": "http",
|
||||||
"wip": false,
|
"wip": false,
|
||||||
"showLink": true
|
"showLink": true
|
||||||
@@ -259,7 +259,7 @@
|
|||||||
"description": "Indexer manager.",
|
"description": "Indexer manager.",
|
||||||
"url": "http://prowlarr.franksdank.cloud",
|
"url": "http://prowlarr.franksdank.cloud",
|
||||||
"docs": "",
|
"docs": "",
|
||||||
"probe": "192.168.0.235:9696",
|
"probe": "192.168.0.112:9696",
|
||||||
"method": "http",
|
"method": "http",
|
||||||
"wip": false,
|
"wip": false,
|
||||||
"showLink": true
|
"showLink": true
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
"description": "Movie library and automation.",
|
"description": "Movie library and automation.",
|
||||||
"url": "http://radarr.franksdank.cloud",
|
"url": "http://radarr.franksdank.cloud",
|
||||||
"docs": "",
|
"docs": "",
|
||||||
"probe": "192.168.0.235:7878",
|
"probe": "192.168.0.112:7878",
|
||||||
"method": "http",
|
"method": "http",
|
||||||
"wip": false,
|
"wip": false,
|
||||||
"showLink": true
|
"showLink": true
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
"description": "Book library and automation.",
|
"description": "Book library and automation.",
|
||||||
"url": "http://readarr.franksdank.cloud",
|
"url": "http://readarr.franksdank.cloud",
|
||||||
"docs": "",
|
"docs": "",
|
||||||
"probe": "192.168.0.235:8787",
|
"probe": "192.168.0.112:8787",
|
||||||
"method": "http",
|
"method": "http",
|
||||||
"wip": false,
|
"wip": false,
|
||||||
"showLink": true
|
"showLink": true
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
"description": "Music library and automation.",
|
"description": "Music library and automation.",
|
||||||
"url": "http://lidarr.franksdank.cloud",
|
"url": "http://lidarr.franksdank.cloud",
|
||||||
"docs": "",
|
"docs": "",
|
||||||
"probe": "192.168.0.235:8686",
|
"probe": "192.168.0.112:8686",
|
||||||
"method": "http",
|
"method": "http",
|
||||||
"wip": false,
|
"wip": false,
|
||||||
"showLink": true
|
"showLink": true
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
"description": "Torrent client.",
|
"description": "Torrent client.",
|
||||||
"url": "http://qbittorrent.franksdank.cloud",
|
"url": "http://qbittorrent.franksdank.cloud",
|
||||||
"docs": "",
|
"docs": "",
|
||||||
"probe": "192.168.0.235:8080",
|
"probe": "192.168.0.112:8080",
|
||||||
"method": "http",
|
"method": "http",
|
||||||
"wip": false,
|
"wip": false,
|
||||||
"showLink": true
|
"showLink": true
|
||||||
|
|||||||
@@ -111,11 +111,11 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
|
|||||||
[ "$output" = $'gitea-postgres\ngitea-server' ]
|
[ "$output" = $'gitea-postgres\ngitea-server' ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "frankbook-pro gluetun changes carry qbittorrent; arr changes stay scoped" {
|
@test "frankbook-pro gluetun changes carry the whole netns family; arr changes stay scoped" {
|
||||||
run python3 "$PLANNER" --target frankbook-pro --paths \
|
run python3 "$PLANNER" --target frankbook-pro --paths \
|
||||||
frankbook-pro/gluetun/quadlet/gluetun.container
|
frankbook-pro/gluetun/quadlet/gluetun.container
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
[ "$output" = $'gluetun\nqbittorrent' ]
|
[ "$output" = $'gluetun\nprowlarr\nqbittorrent\nradarr\nsonarr\nlidarr\nreadarr' ]
|
||||||
run python3 "$PLANNER" --target frankbook-pro --paths \
|
run python3 "$PLANNER" --target frankbook-pro --paths \
|
||||||
frankbook-pro/sonarr/quadlet/sonarr.container
|
frankbook-pro/sonarr/quadlet/sonarr.container
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
# Seam: the FrankBook-Pro quadlet units (gluetun, qbittorrent, five *arrs).
|
# Seam: the FrankBook-Pro quadlet units (gluetun, qbittorrent, five *arrs).
|
||||||
# These units are installed INSIDE the Mac's podman machine VM by the
|
# These units are installed INSIDE the Mac's podman machine VM by the
|
||||||
# frankbook-pro handler in deploy-remote.sh. The contract pinned here:
|
# frankbook-pro handler in deploy-remote.sh. The contract pinned here:
|
||||||
# boot persistence, self-healing, the gluetun netns invariant (qBittorrent
|
# boot persistence, self-healing, the VPN egress invariant (the whole
|
||||||
# publishes no port of its own; gluetun publishes both), the verbatim
|
# *arr fleet + qBittorrent share gluetun's netns and publish no ports of
|
||||||
# plan-locked data-root mounts, and zero literal secrets in tracked files.
|
# their own, so ALL egress exits through the WireGuard tunnel —
|
||||||
|
# AllowedIPs=0.0.0.0/0 — by construction; gluetun publishes every fleet
|
||||||
|
# port), the verbatim plan-locked data-root mounts, and zero literal
|
||||||
|
# secrets in tracked files.
|
||||||
|
|
||||||
load helpers
|
load helpers
|
||||||
|
|
||||||
@@ -31,18 +34,22 @@ FRANKBOOK_UNITS=(
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "qbittorrent joins gluetun's netns and publishes no port of its own" {
|
@test "every arr and qbittorrent joins gluetun's netns and publishes no port" {
|
||||||
local unit="$REPO_ROOT/frankbook-pro/qbittorrent/quadlet/qbittorrent.container"
|
local svc unit
|
||||||
grep -q '^Network=gluetun.container$' "$unit" || return 1
|
for svc in qbittorrent prowlarr sonarr radarr lidarr readarr; do
|
||||||
grep -q '^Wants=gluetun.service$' "$unit" || return 1
|
unit="$REPO_ROOT/frankbook-pro/$svc/quadlet/$svc.container"
|
||||||
grep -q '^After=gluetun.service$' "$unit" || return 1
|
grep -q '^Network=gluetun.container$' "$unit" || return 1
|
||||||
! grep -q '^PublishPort=' "$unit" || return 1
|
grep -q '^Wants=gluetun.service$' "$unit" || return 1
|
||||||
|
grep -q '^After=gluetun.service$' "$unit" || return 1
|
||||||
|
! grep -q '^PublishPort=' "$unit" || return 1
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "gluetun publishes qbittorrent's WebUI and torrent ports and needs NET_ADMIN+tun" {
|
@test "gluetun publishes the whole fleet's ports and needs NET_ADMIN+tun" {
|
||||||
local unit="$REPO_ROOT/frankbook-pro/gluetun/quadlet/gluetun.container"
|
local unit="$REPO_ROOT/frankbook-pro/gluetun/quadlet/gluetun.container"
|
||||||
grep -q '^PublishPort=8080:8080$' "$unit" || return 1
|
for port in 8080 6881 9696 8989 7878 8686 8787; do
|
||||||
grep -q '^PublishPort=6881:6881$' "$unit" || return 1
|
grep -q "^PublishPort=$port:$port\$" "$unit" || return 1
|
||||||
|
done
|
||||||
grep -q '^PublishPort=6881:6881/udp$' "$unit" || return 1
|
grep -q '^PublishPort=6881:6881/udp$' "$unit" || return 1
|
||||||
grep -q '^AddCapability=NET_ADMIN$' "$unit" || return 1
|
grep -q '^AddCapability=NET_ADMIN$' "$unit" || return 1
|
||||||
grep -q '^AddDevice=/dev/net/tun:/dev/net/tun$' "$unit" || return 1
|
grep -q '^AddDevice=/dev/net/tun:/dev/net/tun$' "$unit" || return 1
|
||||||
@@ -69,12 +76,7 @@ FRANKBOOK_UNITS=(
|
|||||||
grep -q '^Volume=/Volumes/INCOMPLETE:/Volumes/INCOMPLETE$' "$unit" || return 1
|
grep -q '^Volume=/Volumes/INCOMPLETE:/Volumes/INCOMPLETE$' "$unit" || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "every arr serves its standard port and readarr keeps its pinned image" {
|
@test "readarr keeps its pinned image" {
|
||||||
grep -q '^PublishPort=9696:9696$' "$REPO_ROOT/frankbook-pro/prowlarr/quadlet/prowlarr.container" || return 1
|
|
||||||
grep -q '^PublishPort=8989:8989$' "$REPO_ROOT/frankbook-pro/sonarr/quadlet/sonarr.container" || return 1
|
|
||||||
grep -q '^PublishPort=7878:7878$' "$REPO_ROOT/frankbook-pro/radarr/quadlet/radarr.container" || return 1
|
|
||||||
grep -q '^PublishPort=8686:8686$' "$REPO_ROOT/frankbook-pro/lidarr/quadlet/lidarr.container" || return 1
|
|
||||||
grep -q '^PublishPort=8787:8787$' "$REPO_ROOT/frankbook-pro/readarr/quadlet/readarr.container" || return 1
|
|
||||||
grep -q '^Image=lscr.io/linuxserver/readarr:amd64-1.0.980$' \
|
grep -q '^Image=lscr.io/linuxserver/readarr:amd64-1.0.980$' \
|
||||||
"$REPO_ROOT/frankbook-pro/readarr/quadlet/readarr.container" || return 1
|
"$REPO_ROOT/frankbook-pro/readarr/quadlet/readarr.container" || return 1
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user