feat(pam tunnel): add --foreground, --background, --run flags and cross-process tunnel registry#1848
Conversation
|
needs to be rebased from release branch |
485ea3e to
8800be8
Compare
|
@miroberts — Ready for final review. Here's a summary of all changes from the original PR and why. Structural
Behavioral fixes (vs original PR)
Not changed (intentional)
Known limitations (v1, documented)
|
1a6c40b to
6f035f6
Compare
442d7af to
9ec9b7a
Compare
…ss-process tunnel registry
…urn, port safety - Add Windows hard-termination warning to --background and --run banners (was only on --foreground) - Use clean_stale=False in --background polling loop to avoid filesystem churn every 0.5s - Guard port comparison in register_tunnel against non-int-coercible values - Document normalize_bind_host limitation (0.0.0.0 vs 127.0.0.1 caught at OS level) - Note thread-safety characteristic of _registry_dir_initialized flag Made-with: Cursor
b357391 to
0a2e88f
Compare
|
Rebased onto Branch commits (3, was 4):
Dropped: Conflict resolution:
Test update:
|
… in tunnel_helpers The original feat commit was authored against release 17.2.10, which did not yet contain parse_keeper_webrtc_version_from_sdp / set_remote_description_and_parse_version. During the feat-commit refactor those helpers were removed. Upstream has since added both (commit efdf580 and its precursor) and pam_launch/terminal_connection.py imports them. After rebasing this PR onto current upstream/release, the removal became a regression: ImportError: cannot import name 'parse_keeper_webrtc_version_from_sdp' from 'keepercommander.commands.tunnel.port_forward.tunnel_helpers' Restore both helpers verbatim from upstream/release so the import chain (pam_launch -> terminal_connection -> tunnel_helpers) resolves again. Unit tests (15) + full import chain verified locally. Made-with: Cursor
|
CI red after the rebase — now fixed in Why it broke: the feature commit ( Fix: restored both helpers (verbatim from Now green:
|
|
Added docs drop at Placement map:
Rationale: the new capability is CLI-only (Desktop tunnels already persist for the session), so the primary page lives under Coverage: all five flags ( Not merged into the GitBook source itself — this is a drop alongside the code PR so whoever owns the GitBook PR can copy-paste three files into the right paths. |
|
Heads-up: this PR is marked merged, but the merge commit ( Re-opened cleanly as #1993 — same feature, rebased on current |
Summary
Add
--foreground/-fg,--pid-file,--run/-R, and--timeoutflags topam tunnel startthat enable tunnels to be used in non-interactive contexts: shell scripts, systemd services, CI/CD pipelines, and headless automation -- without requiring an interactivekeeper shellsession.--foreground: Blocks the Commander process after the tunnel connects, keeping it alive untilSIGTERM/SIGINT/Ctrl+C. Now waits for the WebRTC connection to reach"connected"state before printing the status banner and writing the PID file.--pid-file: Writes the process PID to a file for external signal-based management.--run <COMMAND>: Starts the tunnel, waits for connection, executes the given shell command, stops the tunnel, and exits with the command's exit code. Ideal for single-script workflows.--timeout <SECONDS>: Controls how long to wait for the tunnel to connect (default: 30s). Used with--foreground,--background, and--run.--background/-bg: Launches a separate Commander process with--foreground, polls the file-based tunnel registry for readiness, then returns control to the caller. The tunnel continues running as an independent background process. Works on all platforms.--target-host/--target-portare missing in non-interactive mode, raisesCommandErrorinstead of callinginput()(which hangs in batch/script contexts).<tempdir>/keeper-tunnel-sessions/) enablespam tunnel listto discover tunnels from any Commander session.pam tunnel stop <RECORD_UID>sendsSIGTERMto the owning process. Stale entries (dead PIDs) are auto-cleaned.Motivation
Currently,
pam tunnel startworks only inside an interactivekeeper shellsession because tunnels run as in-process background threads (spawned bystart_rust_tunnel()viakeeper_pam_webrtc_rs). When Commander is invoked in single-command mode (keeper "pam tunnel start <UID>"),PAMTunnelStartCommand.execute()returns after starting the tunnel, Commander exits, and all tunnel threads die with the process.This is a common pain point for users automating infrastructure with Keeper PAM:
pg_dumpthrough a tunnel)Changes
File:
keepercommander/commands/tunnel_and_connections.py(1 file changed)New imports:
signal,subprocess,threading(all stdlib);unregister_tunnel_session,wait_for_tunnel_connection(fromtunnel_helpers)New arguments on
PAMTunnelStartCommand.pam_cmd_parser:--backgroundlogic (subprocess-based, beforestart_rust_tunnel()):keepercommand with--foregroundand all user-supplied flagssubprocess.Popen(start_new_session=True)as an independent process<tempdir>/keeper-tunnel-sessions/) for readiness--runlogic (new branch, checked before--foreground):wait_for_tunnel_connection(result, timeout=connect_timeout)subprocess.run(command, shell=True)close_tube()+unregister_tunnel_session()sys.exit(proc.returncode)KeyboardInterrupt: exits with code 130--foregroundconnection readiness: Before printing the status banner and writing the PID file, callswait_for_tunnel_connection(result, timeout=connect_timeout)to ensure the tunnel is actually usable.Interactive shell detection: When
--foregroundor--backgroundis used insidekeeper shell(interactive mode,batch_mode=False), the blocking logic is skipped and a message informs the user that tunnels already persist in the shell.File-based tunnel registry: New module-level functions (
_register_tunnel,_unregister_tunnel,_list_registered_tunnels,_tunnel_registry_dir,_is_pid_alive) manage JSON metadata files in<tempdir>/keeper-tunnel-sessions/. Each foreground/background/run tunnel registers on connect and unregisters on cleanup.Enhanced
PAMTunnelListCommand: Now reads both the in-processPyTubeRegistryand the file-based registry. Cross-process tunnels appear in the listing with their mode and PID. Stale entries (dead PIDs) are auto-cleaned.Enhanced
PAMTunnelStopCommand: When a tunnel is not found in the in-process registry, falls back to the file registry and sendsSIGTERMto the owning process.--allalso stops cross-process tunnels.Batch mode fix for
--target-host/--target-port: Whenparams.batch_modeisTrueand these values are missing, raisesCommandErrorinstead of callinginput(), which would hang in scripts.No other files are modified. No new external dependencies are introduced. Only Python stdlib modules (
json,signal,subprocess,threading,time) are used.Usage
Testing
--foregroundaccepted -- argument parser recognizes the flag-fgshorthand accepted -- short form worksFalse-- without the flag,foregroundisFalse--pid-fileaccepted -- argument parser recognizes the flag--pid-filedefaults toNone-- without the flag,pid_fileisNone--runaccepted -- argument parser recognizes the flag, stores string value--rundefaults toNone-- without the flag,run_commandisNone--runwith--port----runand--portwork together--timeoutaccepted -- argument parser recognizes the flag, stores int value--timeoutdefaults to 30 -- without the flag,connect_timeoutis30--timeoutcustom value ----timeout 120setsconnect_timeoutto120--backgroundaccepted -- argument parser recognizes the flag-bgshorthand accepted -- short form works--backgrounddefaults toFalse-- without the flag,backgroundisFalse--backgroundwith--pid-file-- both flags work together--host,--port,--no-trickle-ice,--target-host,--target-portall work alongside new flagsunregister_tunnel_session-- importable fromtunnel_and_connections_stop_tunnel_process-- importable fromtunnel_and_connections--foreground,--background,--runall parse individually but runtime rejects combinations--foregroundis set--runexecutes and exits -- tunnel starts, command runs, tunnel stops, exit code propagated<tempdir>/keeper-tunnel-sessions/tunnel listtunnel listshows cross-process tunnels -- tunnels visible from any Commander sessiontunnel stopstops cross-process tunnels -- sends SIGTERM to owning process--backgrounddaemonizes and returns -- tunnel starts, waits, daemonizes, returns prompt--foregroundwaits for connection -- status banner only prints after WebRTC connection is establishedkill -SIGTERMtriggersclose_tube()and process exits 0KeyboardInterrupttriggers the same clean shutdown--foregroundinsidekeeper shellprints info message, does not block--target-host/--target-portin batch mode raisesCommandErrorType=simplesystemd unit starts, runs, and stops cleanlyBackward Compatibility
--foreground,--pid-file,--run,--timeout,--background) are optional and default toFalse/None/None/30/Falseexecute()method follows the exact same code path as before (the replacedpasswas a no-op)--target-host/--target-portonly triggers whenparams.batch_modeisTrueand the resource requires host/port supply -- interactive shell users still get theinput()promptPAMTunnelListCommandandPAMTunnelStopCommandare enhanced to read the file-based registry but continue to work identically for in-process tunnelsPAMTunnelEditCommand,PAMTunnelDiagnoseCommand, or any other commandjson,signal,subprocess,threading,timefrom stdlibPAMTunnelCommandgroup command registration--foreground,--background, and--runare mutually exclusive (enforced at runtime)