Skip to content

feat(container): containerize Transmission with haugene + Podman VPN#78

Open
smartwatermelon wants to merge 13 commits intomainfrom
claude/feature-containerized-transmission-1773018144
Open

feat(container): containerize Transmission with haugene + Podman VPN#78
smartwatermelon wants to merge 13 commits intomainfrom
claude/feature-containerized-transmission-1773018144

Conversation

@smartwatermelon
Copy link
Owner

Summary

Implements the containerized Transmission + PIA VPN stack described in docs/container-transmission-proposal.md. Replaces the brittle native PIA Desktop + split tunnel + shell script monitoring architecture with a single container providing kernel-level VPN enforcement.

Decision: haugene/transmission-openvpn on Podman (not gluetun + linuxserver on OrbStack) — single container, automatic PIA port forwarding, CLI-friendly runtime for headless server.

Files added/changed

  • app-setup/podman-transmission-setup.sh — orchestrator: installs Podman, inits rootful transmission-vm machine, deploys compose stack, writes .env from keychain, creates two LaunchAgents (machine start + trigger watcher)
  • app-setup/containers/transmission/compose.yml — haugene compose template with __VARIABLE__ placeholders
  • app-setup/templates/transmission-post-done.sh — runs inside container on torrent completion; writes KEY=VALUE trigger file to NAS .done/ dir
  • app-setup/templates/transmission-trigger-watcher.sh — macOS LaunchAgent; polls .done/ every 60s, maps /data~/.local/mnt/DSMedia, invokes existing transmission-done.sh; dead-letter after 5 retries
  • config/config.conf.template — adds ONEPASSWORD_PIA_ITEM, PIA_VPN_REGION, LAN_SUBNET
  • prep-airdrop.sh — retrieves PIA credentials from 1Password, stores combined username:password in keychain (pia-account-${HOSTNAME_LOWER})
  • app-setup/run-app-setup.sh — adds podman-transmission-setup.sh at position 3 (filebot/catch/plex shift to 4/5/6)
  • docs/container-transmission-proposal.md — updated: §2 architecture, §3 components, §5.1-5.6 open questions resolved, §6 compose description, §7 migration plan with Phase 1/2/3 steps, §8 risks, §10 success criteria

Architecture: trigger-file handoff

The existing transmission-done.sh (FileBot, macOS-specific) cannot run in the container. Solution: container writes a KEY=VALUE trigger file to the NAS-mounted .done/ directory; the macOS trigger-watcher LaunchAgent picks it up, translates paths, and calls the existing script unchanged. No changes to the FileBot pipeline.

Migration phases

  • Phase 1 (ready to execute): deploy on port 9092 alongside existing native stack; validate VPN, kill switch, port forwarding, NAS bind mount, trigger-watcher end-to-end
  • Phase 2 (after ≥3 days parallel): cutover to port 9091; requires Caddy config update (separate repo) + duti handler reset
  • Phase 3 (after Phase 2 stable): decommission vpn-monitor, pia-proxy-consent, pia-monitor, pia-split-tunnel-monitor, plex-vpn-bypass LaunchAgents + PIA Desktop

Test plan

  • shellcheck passes on all new/modified scripts (enforced by pre-commit hook — clean)
  • podman-transmission-setup.sh --help or dry-run on tilsit (Phase 1 server validation)
  • Phase 1 checklist in docs/container-transmission-proposal.md §7: VPN IP check, kill switch test, port forwarding, NAS bind mount, web UI at port 9092
  • Trigger-watcher end-to-end: complete a test torrent, confirm FileBot processes it
  • Reboot test: confirm Podman machine and container restart without intervention

Do not merge until Phase 1 server validation is complete on tilsit.

🤖 Generated with Claude Code

smartwatermelon pushed a commit that referenced this pull request Mar 9, 2026
LaunchAgents run with a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that
does not include Homebrew's /opt/homebrew/bin where podman is installed.
The generated wrapper had no PATH setup, causing podman to be not found
at login and the container stack to silently fail to start after reboot.

Fix: bake the Homebrew prefix (determined at deploy time) into the
generated script's PATH export. HOMEBREW_PREFIX is an unescaped variable
in the heredoc, so it expands to the actual path at setup time.

Reported by sentry[bot] on PR #78.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude Code Bot and others added 13 commits March 9, 2026 19:36
…up.sh

- Move `podman system connection default` outside the MACHINE_EXISTS==false
  branch so it runs unconditionally; connection is client-side config and
  must be set on every run, not just on first init.
- Replace `cd ... && podman compose` in generated wrapper with
  `--project-directory` absolute path; eliminates silent failure if the
  directory doesn't exist at wrapper runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… order

Insert at position 3; filebot/catch/plex shift to 4/5/6. Fractional
positions (2.5) aren't supported by the integer sort loop, so explicit
renumbering is used. Update header comment and --help output to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update §2 architecture diagram to reflect haugene/Podman decision
  (was gluetun/OrbStack in original draft)
- Add resolution notes to all §5.1–5.6 open questions:
  §5.1 NAS bind mount: validated by setup script + documented reboot-order risk
  §5.2 Startup ordering: LaunchAgent concurrent, recovery path documented
  §5.3 PIA credentials: keychain pattern implemented in prep-airdrop.sh
  §5.4 Port forwarding: haugene handles automatically (no separate service)
  §5.5 Config migration: web UI re-add approach documented
  §5.6 Web UI access: LOCAL_NETWORK env var + Caddy Phase 2 task noted
- Update §7 migration plan with actual Phase 1/2/3 steps and
  Phase 2 prerequisites (Phase 1 stable, Caddy, duti handler reset)
- Fix MD040 (code fence language) and MD032 (list blank line)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
podman-transmission-setup.sh:
- Replace bash -c "cd ... && podman compose" at initial container start
  (line 613) with --project-directory, matching the generated wrapper fix.
  Both call sites now use the same pattern.

docs/container-transmission-proposal.md:
- Fix Phase 2 step 4: remove reference to non-existent TRANSMISSION_PORT
  flag; replace with explicit compose.yml port edit + restart commands.
- Rewrite §3 (Components) to reflect haugene/Podman (was gluetun/OrbStack).
- Replace stale §6 gluetun compose example with accurate description of the
  deployed template at app-setup/containers/transmission/compose.yml.
- Update §8 risks table: replace OrbStack/gluetun/VirtioFS/WireGuard risks
  with Podman/haugene/OpenVPN equivalents.
- Update §10 success criteria: docker exec -> podman exec, add trigger-watcher
  end-to-end test, note Phase 3 prerequisite for final checklist item.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LaunchAgents run with a minimal PATH that does not include Homebrew.
Bake in the Homebrew prefix at deploy time so podman is findable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 1 deploys alongside the existing native Transmission on port 9091.
Without a configurable host port, both stacks would fight over the same
port. Add TRANSMISSION_HOST_PORT to config.conf.template (default: 9091)
and __TRANSMISSION_HOST_PORT__ placeholder to compose.yml. The setup
script substitutes it at deploy time.

To deploy Phase 1: set TRANSMISSION_HOST_PORT="9092" in config.conf
before running prep-airdrop.sh. Phase 2 cutover: change to 9091 and
restart the stack.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 'op item get --fields username' with 'op read op://vault/item/username'
for consistency and reliability. Field IDs (username, password) are stable
1Password internals; label names (user, pass) are user-editable. Both refer
to the same fields in this item but IDs are more robust.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@smartwatermelon smartwatermelon force-pushed the claude/feature-containerized-transmission-1773018144 branch from b0ab0c1 to 6070c16 Compare March 10, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant