Skip to content

design: per-session config via NSSH_SESSION env injection #12

@abizer

Description

@abizer

Follow-up from #11.

Problem

Today the local→remote rendezvous is a single file: ~/.local/state/nssh/session (TOML with server + topic). Every nssh <host> writes that file, every shim invocation reads it. Consequence: two nssh hostA invocations stomp each other, the last writer wins, and any earlier subscriber gets silently orphaned. #11 papered over this with a per-host pidfile registry + join/replace/new UX, but the root cause is the shared file.

What's available to piggyback on

Honest survey:

Convention Per-session? ssh mosh tmux-survivable Linux-only
SSH_CONNECTION (sshd) yes (client port unique per conn) ⚠ stale (refers to bootstrap ssh) ⚠ tmux strips unless update-environment lists it no
SSH_TTY (sshd) yes ⚠ tmux strips no
XDG_SESSION_ID (pam_systemd) yes ⚠ depends on PAM stack ⚠ tmux strips yes
MOSH_KEY (mosh-server) yes ✓ inherited no
loginctl list-sessions yes (query) ⚠ same n/a yes

None is both portable across ssh+mosh and stable through tmux detach/reattach.

Proposal: inject our own NSSH_SESSION

  1. Each nssh <host> mints a sid (generateTopic()-style random) and writes ~/.local/state/nssh/sessions/<sid>.toml on the remote with that session's server + topic. The legacy single-file session is dropped.
  2. Launch the interactive shell with the sid in env:
    • ssh: ssh -t host 'NSSH_SESSION=<sid> exec $SHELL -l'
    • mosh: mosh host -- env NSSH_SESSION=<sid> $SHELL -l
  3. Shim reads $NSSH_SESSION and looks up ~/.local/state/nssh/sessions/$NSSH_SESSION.toml.
  4. $NSSH_SESSION survives tmux because tmux inherits its initial env from the shell that started it. First tmux (or tmux attach) from inside the nssh-spawned shell bakes the sid into the tmux server's env permanently; every pane and window inherits it forever.

Each nssh invocation becomes a fully independent bridge. No collisions. The --join/--replace/--new flags from #11 become unnecessary (and should probably be removed once this lands, otherwise they're vestigial).

Failure mode this doesn't cover

Local nssh dies, tmux server keeps running on the remote with the dead session's NSSH_SESSION baked in. New nssh hostA gets a fresh sid; tmux attach drops you into the old tmux server whose env still references the dead sid. Shim publishes to a dead topic.

Three reasonable handlings:

  1. Accept it. Document: "if your local nssh dies, tmux kill-server (or tmux source-file a script that re-exports NSSH_SESSION in panes) and start fresh." Simplest, slight rough edge.
  2. Re-discover in the shim. If ~/.local/state/nssh/sessions/$NSSH_SESSION.toml is missing/stale, walk the sessions dir on the remote, pick the newest. Almost always Does The Right Thing in a single-user setup.
  3. Reclaim from new nssh. New nssh, on startup, lists remote sessions/, identifies orphans (no live local owner), and offers to take over a specific orphan by overwriting that file's content with the new topic. tmux env still references the same sid, file content swapped under it. Clean from the user's POV.

Recommendation: ship 1+2 together. Cheap. Worst case is "you tmux kill-server once". Option 3 is bigger and only worth it if 1+2 prove painful in practice.

Implementation notes

  • The shim's TOML lookup is one line different — just substitute session for sessions/<sid>.
  • prepareRemote's heredoc already writes the session file; just change the path to include the sid.
  • Need to think about how nssh status discovers remote sessions: currently it reads the single file; would need to glob sessions/*.toml instead, both locally and on the remote.
  • Env-injection means ssh host becomes ssh -t host '<cmd>', which is a behavioral change for users who relied on running nssh host -- <plain ssh cmd>. Need to either gate this on "is it interactive", or accept the change.

Out of scope here

  • The "key sessions by full SSH target identity" Codex feedback from nssh: harden session recovery #11 — once each session has its own file, that bug just goes away (no shared key to disambiguate).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions