prisma vpn: teardown dies before droplet destroy; fetch leaks its tunnel #664

Closed
opened 2026-08-21 14:04:45 +00:00 by kert · 1 comment
Owner

Two lifecycle bugs observed 2026-08-19 → 2026-08-21 (droplet idled ~2 days before manual teardown):

  1. vpn down ordering: down() runs detach_zotero_proxy() BEFORE client.droplets.destroy(). _restore_zotero_prefs() raises PermissionError on the container-owned (uid 100999) prefs.js, so the droplet destroy + key delete + state wipe never execute. Fix: destroy the droplet first (it is the billable resource), wrap prefs restoration in try/except, and copy via a root-capable path (docker exec into zotero, or sudo) when the direct write fails.
  2. fetch leaks its SSH tunnel: the auto-opened port-forward (ssh -fN -L 1080:...) survived the fetch run by ~2 days — -fN daemonizes, so the CLI's close-on-exit never sees the real PID. Track the daemonized PID (or use -o ControlMaster/ControlPath with an explicit exit) and kill it in a finally.

Manual remediation already done: tunnel killed, droplet 565110236 + ssh key 55643846 deleted via API (204/204), .state/prisma-vpn/ wiped, prefs verified proxy-free.

Two lifecycle bugs observed 2026-08-19 → 2026-08-21 (droplet idled ~2 days before manual teardown): 1. **`vpn down` ordering**: `down()` runs `detach_zotero_proxy()` BEFORE `client.droplets.destroy()`. `_restore_zotero_prefs()` raises `PermissionError` on the container-owned (uid 100999) `prefs.js`, so the droplet destroy + key delete + state wipe never execute. Fix: destroy the droplet first (it is the billable resource), wrap prefs restoration in try/except, and copy via a root-capable path (docker exec into zotero, or sudo) when the direct write fails. 2. **`fetch` leaks its SSH tunnel**: the auto-opened port-forward (`ssh -fN -L 1080:...`) survived the fetch run by ~2 days — `-fN` daemonizes, so the CLI's close-on-exit never sees the real PID. Track the daemonized PID (or use `-o ControlMaster`/`ControlPath` with an explicit exit) and kill it in a finally. Manual remediation already done: tunnel killed, droplet 565110236 + ssh key 55643846 deleted via API (204/204), `.state/prisma-vpn/` wiped, prefs verified proxy-free.
kert added the bib label 2026-08-21 14:04:45 +00:00
Author
Owner

Fixed in 2b71d41. (1) vpn down now destroys the droplet and deletes the account key FIRST, then stops the tunnel, then detaches the Zotero proxy inside try/except (a PermissionError on the container-owned prefs.js is logged, never allowed to skip the destroy or the state wipe); _restore_zotero_prefs falls back to sudo cp/chown the way _rewrite_prefs already wrote. (2) The SSH tunnel is now a Popen child Python owns (ssh -N in a new session, no -f): the PID is recorded before the listener appears, an early exit or a never-ready start kills the child and clears the pid file, so a fetch can no longer leak a daemonized port-forward. Tests: down ordering (destroy → tunnel → detach) and detach-failure resilience, Popen args/early-exit/never-ready, sudo fallback.

Fixed in 2b71d41. (1) vpn down now destroys the droplet and deletes the account key FIRST, then stops the tunnel, then detaches the Zotero proxy inside try/except (a PermissionError on the container-owned prefs.js is logged, never allowed to skip the destroy or the state wipe); _restore_zotero_prefs falls back to sudo cp/chown the way _rewrite_prefs already wrote. (2) The SSH tunnel is now a Popen child Python owns (ssh -N in a new session, no -f): the PID is recorded before the listener appears, an early exit or a never-ready start kills the child and clears the pid file, so a fetch can no longer leak a daemonized port-forward. Tests: down ordering (destroy → tunnel → detach) and detach-failure resilience, Popen args/early-exit/never-ready, sudo fallback.
kert closed this issue 2026-09-11 20:47:31 +00:00
Sign in to join this conversation.