frankbook: propagate skip-state before restart, PBKDF2 qB seed

The frankbook handler ran port verification even for services the
credential gate skipped, so a parked qbittorrent failed the deploy and
aborted the five arrs. Gate now lives Mac-side (test -s gluetun/env
inside the VM) before restart+verify.

The qB first-boot seed wrote a plaintext WebUI\Password, which qB >= 4.2
ignores (PBKDF2 @ByteArray only), and omitted HostHeaderValidation=false,
which would 401 the LAN forwarder path. Seed now generates the PBKDF2
hash in the VM (python3 hashlib) and sets ServerDomains=* +
HostHeaderValidation=false. Arr categories (lidarr/radarr/tv-sonarr)
pre-created with save_path=/Volumes/COMPLETE/Finished Torrents so any
grab lands in the review dir regardless of wiring details.
This commit is contained in:
Frank committed 2026-09-01 14:22:30 -07:00
1 parent fe34f04454
commit 1a257c06fe
1 file changed
+50 -34
+50 -34
View File
@@ -841,64 +841,80 @@ deploy_frankbook_stack() {
done done
local tmp local tmp
tmp="$(mktemp /tmp/frankbook-vm.XXXXXX)" tmp="$(mktemp /tmp/frankbook-vm.XXXXXX)"
cat > "$tmp" <<'VM_SETUP' cat > "$tmp" <<'VM_SEED'
set -euo pipefail set -euo pipefail
SVC="${1:?service required}" # qBittorrent first-boot seed: conf + categories + machine-local PBKDF2
# qBittorrent first-boot seed: conf + categories + machine-local password. # password. qB >= 4.2 ignores plaintext passwords, so the conf carries the
# @ByteArray(salt:hash) form (PBKDF2-HMAC-SHA512, 100k iterations, 64 bytes).
if [ ! -e /home/core/Containers/qbittorrent/qbit-seeded ]; then if [ ! -e /home/core/Containers/qbittorrent/qbit-seeded ]; then
mkdir -p /Volumes/COMPLETE/qbittorrent-data/qBittorrent mkdir -p /Volumes/COMPLETE/qbittorrent-data/qBittorrent
QBIT_PW="$(openssl rand -base64 18 | tr -d '/+=')" QBIT_PW="$(openssl rand -base64 18 | tr -d '/+=' | head -c 20)"
QBIT_HASH="$(python3 - "$QBIT_PW" <<'PYH'
import hashlib, base64, os, sys
pw = sys.argv[1].encode()
salt = os.urandom(16)
h = hashlib.pbkdf2_hmac("sha512", pw, salt, 100000, dklen=64)
print("@ByteArray(%s:%s)" % (base64.b64encode(salt).decode(), base64.b64encode(h).decode()))
PYH
)"
umask 077 umask 077
printf 'QBIT_WEBUI_PASSWORD=%s\n' "$QBIT_PW" > /home/core/Containers/qbittorrent/env printf 'QBIT_WEBUI_PASSWORD=%s\n' "$QBIT_PW" > /home/core/Containers/qbittorrent/env
umask 022 umask 022
{ cat > /Volumes/COMPLETE/qbittorrent-data/qBittorrent/qBittorrent.conf <<QCONF
cat <<'QCONF'
[AutoRun]
[BitTorrent] [BitTorrent]
Session\DefaultSavePath=/Volumes/COMPLETE Session\\DefaultSavePath=/Volumes/COMPLETE
Session\Port=6881 Session\\Port=6881
Session\QueueingSystemEnabled=true Session\\QueueingSystemEnabled=true
Session\TempPath=/Volumes/INCOMPLETE/incomplete Session\\TempPath=/Volumes/INCOMPLETE/incomplete
Session\TempPathEnabled=true Session\\TempPathEnabled=true
[LegalNotice] [LegalNotice]
Accepted=true
[Meta] [Meta]
MigrationVersion=8 MigrationVersion=8
[Network] [Network]
Cookies\Enabled=true Cookies\\Enabled=true
[Preferences] [Preferences]
WebUI\Address=* WebUI\\Address=*
WebUI\Username=admin WebUI\\ServerDomains=*
WebUI\\HostHeaderValidation=false
WebUI\\Username=admin
WebUI\\Password=$QBIT_HASH
QCONF QCONF
printf 'WebUI\\Password=%s\n' "$QBIT_PW"
} > /Volumes/COMPLETE/qbittorrent-data/qBittorrent/qBittorrent.conf
cat > /Volumes/COMPLETE/qbittorrent-data/qBittorrent/categories.json <<'QCAT' cat > /Volumes/COMPLETE/qbittorrent-data/qBittorrent/categories.json <<'QCAT'
{ {
"Finished Torrents": { "Finished Torrents": {"save_path": "/Volumes/COMPLETE/Finished Torrents"},
"save_path": "/Volumes/COMPLETE/Finished Torrents" "lidarr": {"save_path": "/Volumes/COMPLETE/Finished Torrents"},
}, "radarr": {"save_path": "/Volumes/COMPLETE/Finished Torrents"},
"lidarr": {"save_path": ""}, "tv-sonarr": {"save_path": "/Volumes/COMPLETE/Finished Torrents"}
"radarr": {"save_path": ""},
"tv-sonarr": {"save_path": ""}
} }
QCAT QCAT
touch /home/core/Containers/qbittorrent/qbit-seeded touch /home/core/Containers/qbittorrent/qbit-seeded
echo " seeded qBittorrent config (WebUI password: /home/core/Containers/qbittorrent/env inside the VM)" echo " seeded qBittorrent config (WebUI password: /home/core/Containers/qbittorrent/env inside the VM)"
fi fi
if [ "$SVC" = gluetun ] && [ ! -s /home/core/Containers/gluetun/env ]; then VM_SEED
echo "SKIP: gluetun has no credentials yet." podman machine ssh bash -s < "$tmp"
echo " Write the ProtonVPN WireGuard keys into /home/core/Containers/gluetun/env inside the VM" rm -f "$tmp"
echo " (podman machine ssh), then re-run: ./scripts/deploy.sh frankbook-pro --service gluetun" # Credential gate: without a WireGuard env the tunnel stays parked, and
exit 0 # qBittorrent (which downloads through it) waits with it. The arrs run
fi # without the tunnel, so only these two services are gated.
if [ "$SVC" = qbittorrent ] && [ ! -s /home/core/Containers/gluetun/env ]; then case "$service" in
echo "SKIP: qbittorrent waits for gluetun credentials." gluetun|qbittorrent)
exit 0 if ! podman machine ssh '[ -s /home/core/Containers/gluetun/env ]' >/dev/null 2>&1; then
echo "SKIP: $service waits for gluetun credentials."
echo " Write the WireGuard env into /home/core/Containers/gluetun/env inside the VM"
echo " (podman machine ssh), then re-run: ./scripts/deploy.sh frankbook-pro --service gluetun --service qbittorrent"
return 0
fi fi
;;
esac
tmp="$(mktemp /tmp/frankbook-vm.XXXXXX)"
cat > "$tmp" <<'VM_START'
set -euo pipefail
SVC="${1:?service required}"
systemctl --user daemon-reload 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}')" 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; } [ -n "$unit" ] || { echo "FAIL: no generated systemd unit for $SVC" >&2; exit 1; }
@@ -910,7 +926,7 @@ if [ "$state" != active ] && [ "$state" != activating ]; then
systemctl --user status "$unit" --no-pager -l || true systemctl --user status "$unit" --no-pager -l || true
exit 1 exit 1
fi fi
VM_SETUP VM_START
podman machine ssh bash -s "$service" < "$tmp" podman machine ssh bash -s "$service" < "$tmp"
rm -f "$tmp" rm -f "$tmp"
local port path local port path