Commit Graph

6 Commits

Author SHA1 Message Date
0c9f0585fa Implement porthole v0.1: profiles, supervised open/close, reconnect
Full CLI per spec v0.2 - add/open/close/edit/status/list/remove/wipe/
completions, plus a hidden `__supervise` subcommand that IS the
supervisor process.

- profile.rs: TOML-backed profiles at ~/.config/porthole/profiles/,
  validated -l/-r/-d mapping + --via grammar, atomic writes.
- instance.rs: JSON runtime state at ~/.local/state/porthole/, an
  flock-based lock file that's the source of truth for "is this open"
  (survives a crash/kill -9 without stale-lock cleanup), pid liveness
  checked against /proc rather than trusted from disk.
- supervisor.rs: the __supervise loop - spawns ssh, traps SIGTERM/SIGINT
  into a flag (rather than inferring intent from ssh's exit status),
  classifies failures as fatal/known-transient/unrecognized, backs off
  with a stability-reset, rotates its log.
- ssh.rs: builds the ssh invocation, including splitting --via into a
  -J jump chain plus the mandatory positional target.
- open.rs: the detach/re-exec dance (setsid via pre_exec) and a bounded
  wait for the supervisor to reach Up/Error before open returns, so an
  immediate failure surfaces as a non-zero exit instead of a false
  "opened" - this took a real bug fix during smoke testing, since the
  instance file's initial state (Reconnecting, meaning "attempt in
  flight") was indistinguishable from "already failed once" by state
  alone.
- close.rs: SIGTERM+wait, or SIGKILL the whole process group with
  --force so ssh can't be left orphaned.

Smoke-tested against invalid/unreachable hosts (no real infrastructure
touched): CLI surface, validation errors, add/edit/list/status/remove,
the reconnect/backoff loop with live state transitions, close mid-retry,
edit-while-running's warning, open --all, and wipe. cargo test: 14/14.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 15:43:49 +02:00
bc8f1cc5d2 Fix --via/-J grammar and --force process-group kill in spec
Found both while translating the spec into code:

- `ssh -J a,b,c` alone isn't valid - ssh -J only carries jump hosts
  before the final hop; it still needs a positional destination. --via's
  last entry has to double as that target, which also makes --via
  mandatory (there's no other field naming "the host ssh connects to"),
  reversing the earlier "no" in the required column.
- A plain single-pid SIGKILL on `close --force` would leave the `ssh`
  child orphaned, since a killed process can't forward anything to it.
  Documents sending SIGKILL to the whole process group instead, relying
  on the supervisor's own setsid() call making it the group leader.
2026-08-13 15:43:36 +02:00
b18ee7405e Address implementation gaps in spec: supervisor lifecycle, reconnect policy, instance state
Bumps to v0.2. Resolves the open questions and design gaps flagged during
review before implementation starts:

- Spells out the supervisor detach/re-exec mechanism (§3), mirroring how
  vmic solves the same one-shot-CLI-can't-host-a-daemon problem.
- Forces BatchMode/ExitOnForwardFailure/ConnectTimeout on every ssh
  invocation so headless failures surface instead of hanging on a TTY
  prompt (§3.1).
- Adds backoff + stderr-based fatal/transient failure classification so
  reconnect doesn't retry forever against a permanently broken profile
  (§4).
- Collapses the Instance `state` enum to up/reconnecting/error, with
  "no instance file" as the sole meaning of closed, removing the prior
  ambiguity around close vs. crash vs. down (§2.2, §3).
- Fixes --via's conflicting comma-list-vs-repeated-flag examples by
  committing to a straight ssh -J passthrough grammar (§3.1/§5.1).
- Resolves the edit --restart open question: no --restart, stays explicit.
- Simplifies exit codes to 0/1/2 (was a 6-code table with its own TODO).
- Adds a lock file for concurrent-open safety and a log rotation policy.
- Calls out reboot/logout survival as an explicit v0.1 non-goal, with
  `open --all` as the only hook left for external autostart wiring (§8).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 15:26:43 +02:00
5675200a3c added .gitignore 2026-08-13 15:21:31 +02:00
11302572e0 added initial spec 2026-08-13 15:21:13 +02:00
9a86a6293a Initial commit 2026-08-13 13:12:24 +00:00