frankbook-pro: *arr stack + qbittorrent quadlets inside podman machine
Seven units installed INSIDE the Mac VM (/home/core/.config/containers/ systemd) by a new deploy_frankbook_stack handler: gluetun (ProtonVPN WireGuard, env-file credentials, publishes qbit's 8080/6881), qbittorrent (Network=gluetun.container, verbatim /Volumes data roots, first-boot config seed with fresh machine-local WebUI password and the Finished Torrents category), and prowlarr/sonarr/radarr/lidarr/readarr (grab-only, config migrated from dankserver at provisioning, readarr pinned to amd64-1.0.980). deploy.sh routes the new target through the ssh-config alias with REMOTE_SHELL=/usr/local/bin/bash (macOS bash 3.2 lacks associative arrays); the planner learns FRANKBOOK_SERVICES and the gluetun->qbittorrent netns pairing; new seam test pins the contract.
This commit is contained in:
+131
-3
@@ -18,7 +18,7 @@ SERVICES=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: deploy-remote.sh <dankclaw|dankserver> <git-revision> [--pending] [service]...
|
||||
Usage: deploy-remote.sh <dankclaw|dankserver|frankbook-pro> <git-revision> [--pending] [service]...
|
||||
|
||||
The service list is explicit. An empty list is a successful no-op.
|
||||
EOF
|
||||
@@ -57,7 +57,7 @@ run_privileged() {
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$TARGET" != dankclaw && "$TARGET" != dankserver ]]; then
|
||||
if [[ "$TARGET" != dankclaw && "$TARGET" != dankserver && "$TARGET" != frankbook-pro ]]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
@@ -96,7 +96,10 @@ service_is_allowed() {
|
||||
dankserver:copyparty|dankserver:jellyfin|dankserver:calibre-web|\
|
||||
dankserver:prowlarr|dankserver:qbittorrent|dankserver:radarr|\
|
||||
dankserver:sonarr|dankserver:lidarr|dankserver:readarr|\
|
||||
dankserver:frank-default-small)
|
||||
dankserver:frank-default-small|\
|
||||
frankbook-pro:gluetun|frankbook-pro:qbittorrent|frankbook-pro:prowlarr|\
|
||||
frankbook-pro:radarr|frankbook-pro:sonarr|frankbook-pro:lidarr|\
|
||||
frankbook-pro:readarr)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
@@ -814,8 +817,133 @@ wait_for_health() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# FrankBook-Pro (macOS): quadlet units live INSIDE the podman machine VM
|
||||
# (/home/core/.config/containers/systemd), not in the macOS user session.
|
||||
# This handler pipes unit files and seed logic into the VM over
|
||||
# `podman machine ssh`; it never touches systemctl on the Mac host and never
|
||||
# uses run_privileged (the Mac account is not passwordless sudo). Unit files
|
||||
# are re-piped on every call (idempotent, cheap) before the selected unit is
|
||||
# restarted; first boot also seeds the qBittorrent config once.
|
||||
FRANKBOOK_SVCS=(gluetun qbittorrent prowlarr sonarr radarr lidarr readarr)
|
||||
deploy_frankbook_stack() {
|
||||
local service="$1"
|
||||
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
|
||||
if ! podman machine inspect --format '{{.State}}' 2>/dev/null | grep -qx running; then
|
||||
echo "FAIL: podman machine is not running on frankbook-pro" >&2
|
||||
return 1
|
||||
fi
|
||||
local vm_dir="/home/core/.config/containers/systemd" svc
|
||||
podman machine ssh "mkdir -p '$vm_dir' /home/core/Containers/gluetun /home/core/Containers/qbittorrent"
|
||||
for svc in "${FRANKBOOK_SVCS[@]}"; do
|
||||
podman machine ssh "cat > '$vm_dir/$svc.container'" \
|
||||
< "frankbook-pro/$svc/quadlet/$svc.container"
|
||||
done
|
||||
local tmp
|
||||
tmp="$(mktemp /tmp/frankbook-vm.XXXXXX)"
|
||||
cat > "$tmp" <<'VM_SETUP'
|
||||
set -euo pipefail
|
||||
SVC="${1:?service required}"
|
||||
# qBittorrent first-boot seed: conf + categories + machine-local password.
|
||||
if [ ! -e /home/core/Containers/qbittorrent/qbit-seeded ]; then
|
||||
mkdir -p /Volumes/COMPLETE/qbittorrent-data/qBittorrent
|
||||
QBIT_PW="$(openssl rand -base64 18 | tr -d '/+=')"
|
||||
umask 077
|
||||
printf 'QBIT_WEBUI_PASSWORD=%s\n' "$QBIT_PW" > /home/core/Containers/qbittorrent/env
|
||||
umask 022
|
||||
{
|
||||
cat <<'QCONF'
|
||||
[AutoRun]
|
||||
|
||||
[BitTorrent]
|
||||
Session\DefaultSavePath=/Volumes/COMPLETE
|
||||
Session\Port=6881
|
||||
Session\QueueingSystemEnabled=true
|
||||
Session\TempPath=/Volumes/INCOMPLETE/incomplete
|
||||
Session\TempPathEnabled=true
|
||||
|
||||
[LegalNotice]
|
||||
|
||||
[Meta]
|
||||
MigrationVersion=8
|
||||
|
||||
[Network]
|
||||
Cookies\Enabled=true
|
||||
|
||||
[Preferences]
|
||||
WebUI\Address=*
|
||||
WebUI\Username=admin
|
||||
QCONF
|
||||
printf 'WebUI\\Password=%s\n' "$QBIT_PW"
|
||||
} > /Volumes/COMPLETE/qbittorrent-data/qBittorrent/qBittorrent.conf
|
||||
cat > /Volumes/COMPLETE/qbittorrent-data/qBittorrent/categories.json <<'QCAT'
|
||||
{
|
||||
"Finished Torrents": {
|
||||
"save_path": "/Volumes/COMPLETE/Finished Torrents"
|
||||
},
|
||||
"lidarr": {"save_path": ""},
|
||||
"radarr": {"save_path": ""},
|
||||
"tv-sonarr": {"save_path": ""}
|
||||
}
|
||||
QCAT
|
||||
touch /home/core/Containers/qbittorrent/qbit-seeded
|
||||
echo " seeded qBittorrent config (WebUI password: /home/core/Containers/qbittorrent/env inside the VM)"
|
||||
fi
|
||||
if [ "$SVC" = gluetun ] && [ ! -s /home/core/Containers/gluetun/env ]; then
|
||||
echo "SKIP: gluetun has no credentials yet."
|
||||
echo " Write the ProtonVPN WireGuard keys into /home/core/Containers/gluetun/env inside the VM"
|
||||
echo " (podman machine ssh), then re-run: ./scripts/deploy.sh frankbook-pro --service gluetun"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$SVC" = qbittorrent ] && [ ! -s /home/core/Containers/gluetun/env ]; then
|
||||
echo "SKIP: qbittorrent waits for gluetun credentials."
|
||||
exit 0
|
||||
fi
|
||||
systemctl --user daemon-reload
|
||||
unit="$(systemctl --user list-units --all --no-legend "${SVC}.service" "container-${SVC}.service" 2>/dev/null | head -n1 | awk '{print $1}')"
|
||||
[ -n "$unit" ] || { echo "FAIL: no generated systemd unit for $SVC" >&2; exit 1; }
|
||||
systemctl --user restart "$unit"
|
||||
sleep 3
|
||||
state="$(systemctl --user is-active "$unit" || true)"
|
||||
if [ "$state" != active ] && [ "$state" != activating ]; then
|
||||
echo "FAIL: $unit is $state" >&2
|
||||
systemctl --user status "$unit" --no-pager -l || true
|
||||
exit 1
|
||||
fi
|
||||
VM_SETUP
|
||||
podman machine ssh bash -s "$service" < "$tmp"
|
||||
rm -f "$tmp"
|
||||
local port path
|
||||
case "$service" in
|
||||
qbittorrent) port=8080; path=/ ;;
|
||||
prowlarr) port=9696; path=/ping ;;
|
||||
sonarr) port=8989; path=/ping ;;
|
||||
radarr) port=7878; path=/ping ;;
|
||||
lidarr) port=8686; path=/ping ;;
|
||||
readarr) port=8787; path=/ping ;;
|
||||
*) return 0 ;; # gluetun publishes only qBittorrent's ports
|
||||
esac
|
||||
printf ' waiting for http://127.0.0.1:%s%s on the Mac...\n' "$port" "$path"
|
||||
local ok=0 i
|
||||
for i in $(seq 1 60); do
|
||||
if curl -fsS -m 4 "http://127.0.0.1:${port}${path}" >/dev/null 2>&1; then
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
[[ "$ok" -eq 1 ]] || { echo "FAIL: $service did not answer on 127.0.0.1:$port" >&2; return 1; }
|
||||
echo " $service answers on 127.0.0.1:$port"
|
||||
}
|
||||
|
||||
deploy_service() {
|
||||
local service="$1"
|
||||
if [[ "$TARGET" == "frankbook-pro" ]]; then
|
||||
# All frankbook-pro services route through the Mac handler: the name
|
||||
# case below addresses the Linux hosts, and service names overlap.
|
||||
deploy_frankbook_stack "$service"
|
||||
return
|
||||
fi
|
||||
case "$service" in
|
||||
caddy) deploy_caddy ;;
|
||||
homepage) deploy_homepage ;;
|
||||
|
||||
Reference in New Issue
Block a user