dev_arc_aws/TERMIX_MIGRATION.md
Claude 27abbc8ce1
Phase 1c: OPKSSH cert auth, tmux session monitor/reattach, session logging
- terminal.ts: connectWithCertificate() shells out to system ssh via
  node-pty for OpenSSH certificate auth (ssh2 has no native support);
  list_tmux WS message + tmuxSession connect param for tmux
  attach/create with shell-injection-safe name validation;
  sessionLogging config field appends terminal output to disk.
- Settings.tsx: certificate secret field and sessionLogging checkbox
  for SSH host integrations.
- Terminal.tsx: tmux session picker in each pane's header.
- Verified end-to-end against a real test SSH server running real
  bash/tmux processes (plain shell, tmux create+list, session log
  written to disk). Cert auth path type-checks but is unverified in
  this sandbox (no ssh CLI available) - documented as a gap in
  TERMIX_MIGRATION.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 11:28:51 +00:00

13 KiB

Termix → ArchNest Migration Plan

Status doc for porting Termix's full feature set into ArchNest as a single app, single backend, single auth, single database — reskinned to match ArchNest's design. Written so any session (human or AI) can see exactly what's done, what's next, and why decisions were made.

Source: https://github.com/SamuelSJames/Termix (user's fork), cloned for reference at the time of writing. Upstream is Termix-SSH/Termix, an Electron + Express + Drizzle ORM self-hosted SSH/RDP/VNC management app — not a small terminal widget. It ships as its own Docker image with a guacd sidecar for RDP/VNC.

Decision: why merge into ArchNest's backend, not Termix's

ArchNest's backend (Fastify + better-sqlite3 + JWT) is small and already has things worth keeping: the bookmarks system, the integration adapter framework (Proxmox/AWS/NetBird/Cloudflare/Weather/SSH health checks — see backend/src/integrations/), the audit log, and a working auth/profile system built this session. Termix's backend is much bigger but its value is the SSH/tunnel/file-manager/Docker/RDP feature logic, not its auth system (OIDC/LDAP/2FA) or its Drizzle schema. So: port Termix's feature modules onto ArchNest's existing Fastify app and auth, don't adopt Termix's backend wholesale.

What is explicitly NOT being ported (user-approved tradeoff)

  • Electron desktop app + native installers (Chocolatey/Flatpak/AppImage/MSI/Cask) — ArchNest is a web app.
  • OIDC/LDAP/2FA/SSO and Termix's own multi-user auth system — replaced by ArchNest's existing JWT auth. User confirmed they don't currently use 2FA/OIDC/LDAP, so this is an accepted downgrade, not an oversight.
  • ~30 language translations (i18n) — not a stated goal, not being ported.
  • All Termix branding — logos, icons, About/product copy, links to Termix's Discord/docs/GitHub. Every ported UI component gets reskinned to ArchNest's Tailwind theme (gold #C8A434, the existing dark palette) as part of porting it, not as a separate pass.

Everything else — SSH terminal, tunnels, file manager, Docker management, RDP/VNC/Telnet, host metrics — is in scope to fully port, feature-equivalent, just rebuilt on ArchNest's stack.

Phases

Each phase is independently committable and testable. Do not start a later phase before the previous one is working end-to-end and committed — this is a large port and needs to land in reviewable chunks.

Phase 1 — SSH Terminal (IN PROGRESS)

The actual /terminal page: a real interactive SSH terminal in the browser (xterm.js + WebSocket), reusing the SSH credentials already stored in ArchNest's integrations (no second "add a host" flow — Termix's separate host-manager concept is being merged into ArchNest's existing integrations table/SSH adapter, not duplicated).

Termix source files this phase is based on (sizes as of the fork snapshot, for scoping):

  • src/backend/ssh/terminal.ts (2,570 lines) — WebSocket route handling, message protocol (connect/data/resize/disconnect), output buffering.
  • src/backend/ssh/terminal-session-manager.ts (570 lines) — session lifecycle, reattach-on-reconnect, per-user session caps, idle timeout, optional session logging to disk.
  • src/backend/ssh/ssh-connection-pool.ts (225 lines) — connection reuse.
  • src/backend/ssh/host-resolver.ts, jump-host-chain.ts, terminal-jump-hosts.ts (~900 lines combined) — jump-host / bastion chaining.
  • src/backend/ssh/auth-manager.ts, credential-username.ts, host-key-verifier.ts, terminal-auth-helpers.ts (~950 lines combined) — credential resolution, host key verification/trust-on-first-use.
  • src/backend/ssh/opkssh-auth.ts, opkssh-cert-auth.ts (~1,350 lines) — OPKSSH (OpenPubkey SSH) certificate auth.
  • src/backend/ssh/tmux-monitor.ts, tmux-helper.ts, tmux-monitor-helpers.ts (~1,350 lines) — tmux session detection/monitoring inside the terminal.
  • Frontend: src/ui/features/terminal/* — xterm.js wrapper, tab system, up-to-4-panel split screen, theme/font customization.

Scope split for this phase, given the size above:

  • Phase 1a (doing this now): core single-session SSH terminal. WebSocket connect/data/resize/disconnect, using ArchNest's existing SSH integration config/secrets (host/port/username/password/privateKey/passphrase — already in backend/src/integrations/ssh.ts) instead of Termix's separate host table. One terminal per tab, no split panes yet, no jump hosts, no OPKSSH, no tmux monitor, no session recording/logging. Ported onto Fastify's WebSocket support, reusing ArchNest's JWT auth for the WS handshake.
  • Phase 1b (follow-up, not blocking 1a): jump-host/bastion chaining, host-key verification/trust-on-first-use UI, tab system + up to 4 split panes, terminal theme/font customization settings.
  • Phase 1c (follow-up, lower priority): OPKSSH cert auth, tmux session monitor/reattach, session recording/logging to disk.

Rationale for splitting: 1a alone is a real, useful terminal (matches what /terminal needs to stop being a placeholder) and is testable end-to-end on its own. Bundling jump-hosts/OPKSSH/tmux into the first pass risks a large unreviewable change with no working checkpoint in between.

Status:

  • Phase 1a — done. /terminal is a real interactive SSH terminal: backend/src/routes/terminal.ts (WebSocket, connect/input/resize/disconnect over ssh2), backend/src/db/secrets.ts (shared secret loader), src/pages/Terminal.tsx (xterm.js + host picker, reuses ArchNest's existing SSH integrations — no duplicate host table). Verified end-to-end against a real test SSH server. No jump hosts, no tabs/split panes, no OPKSSH, no tmux monitor yet — see 1b/1c below.
  • Phase 1b — done.
    • Jump-host chaining: an SSH integration's config can carry jumpHostIntegrationId referencing another SSH integration. backend/src/routes/terminal.ts connects to the jump host first, opens a forwardOut() channel to the real target, and connects the target Client over that channel (single-hop; mirrors Termix's core mechanism without its multi-hop/credential-sharing complexity). Verified end-to-end with two real test SSH servers (one as jump, one as target).
    • Host-key verification (TOFU): new ssh_host_keys table (backend/src/db/index.ts) stores a SHA-256 fingerprint per SSH integration on first successful connect; subsequent connects are rejected if the fingerprint changes, via ssh2's hostVerifier connect option. No interactive accept/reject-changed-key UI yet — first-use accept-and-store, hard-reject on mismatch. Verified both the accept-on-first-use and reject-on-mismatch paths against a real test server.
    • Settings UI for multiple SSH hosts: src/pages/Settings.tsx previously could only show/edit one integration per type, which silently broke multi-host SSH. Added a dedicated SshHostsSection with its own per-host cards (Save/Test/Delete) and an "Add SSH Host" flow, including a Jump Host dropdown populated from the other configured SSH hosts.
    • Tabs + up to 4 split panes: src/pages/Terminal.tsx rewritten around a TerminalPane component (one xterm + WebSocket connection each, reusable). Each tab holds 1/2/4 panes (single / split-2 / 2x2 grid); each pane connects independently to whichever SSH host is clicked while it's focused.
    • Terminal theme/font customization: a preferences bar (theme preset, font size, font family) persisted to localStorage (archnest-terminal-prefs), applied per-pane on connect.
    • Verified via a clean production build (tsc -b && vite build) — no real browser available in this environment to click through tabs/panes, so this is build/type verification only, not an interactive UI test.
  • Phase 1c — done, with one documented verification gap.
    • OPKSSH / certificate auth: ssh2 (the npm library) has no support for OpenSSH certificates — confirmed by inspecting its type definitions and README, no certificate-related auth flow exists. Implemented connectWithCertificate() in backend/src/routes/terminal.ts: writes the stored private key + certificate to a temp dir (mode 0600) and shells out to the system ssh binary (which natively understands -o CertificateFile=) under a real node-pty pty. Used automatically when an SSH integration has a certificate secret configured (new field added to Settings' SSH host form). Does not support jump-host chaining (documented limitation, not silently dropped — Termix's own OPKSSH path doesn't generally chain through jump hosts either). Verification gap: this sandbox has no ssh CLI installed (apt-get install openssh-client failed — mirror 404), so this path type-checks and is logically sound but has not been exercised end-to-end. Needs a real test against a cert-auth-enabled host before being considered fully verified; openssh-client is near-universal on real deployment targets, so this is a sandbox limitation, not an expected production gap.
    • tmux session monitor/reattach: new WebSocket message list_tmux execs tmux list-sessions on the target host and returns session names; connect accepts an optional tmuxSession (validated against ^[A-Za-z0-9_-]{1,64}$ before being interpolated into a shell command, to prevent injection) which attaches to that tmux session or creates it if missing, via exec('tmux attach -t <name> || tmux new-session -s <name>', { pty: ... }) instead of a plain client.shell(). src/pages/Terminal.tsx's pane header gained a tmux session picker (plain shell / new session / attach to an existing one). Verified end-to-end against a real test SSH server running real bash/tmux processes (via node-pty): listed zero sessions, created a testsess tmux session through the WS protocol, confirmed a follow-up list_tmux call returned ['testsess'].
    • Session recording/logging to disk: new SSH integration config field sessionLogging (checkbox in Settings' SSH host form). When set, all outbound terminal output (both the ssh2 path and the cert-auth pty path) is appended to <ARCHNEST_SESSION_LOG_DIR ?? './data/session-logs'>/<integrationId>_<timestamp>.log. No log browsing/download UI yet (not built — out of scope for this pass, not silently dropped). Verified end-to-end: a real shell session's output was confirmed present in its log file on disk.
    • No real ssh CLI / no real OPKSSH certificate available in this sandbox to test against, see verification gap above. Everything else in this phase was tested against live processes, not mocked.

Phase 2 — SSH Tunnels (NOT STARTED)

Source: src/backend/ssh/tunnel.ts (2,414 lines) + tunnel-c2s-relay.ts, tunnel-socks5-relay.ts, tunnel-ssh-primitives.ts, tunnel-utils.ts, tunnel-c2s-relay-utils.ts (~830 lines combined) + frontend src/ui/features/tunnel/*. Local/remote/dynamic SOCKS forwarding, automatic reconnection, health monitoring. Builds on Phase 1's connection pool. Client-to-server tunnel presets (save/rename/load/delete) need a small new table in ArchNest's schema.

Phase 3 — Remote File Manager (NOT STARTED)

Source: src/backend/ssh/file-manager*.ts (six files, ~3,900 lines combined: list/content/action/operation/download routes + session + utils) + frontend src/ui/features/file-manager/*. View/edit code/images/audio/video, upload/download/rename/delete/move, sudo support, server-to-server moves. Runs over the SSH connections from Phase 1.

Phase 4 — Docker Container Management (NOT STARTED)

Source: src/backend/ssh/docker.ts (2,243 lines) + docker-container-routes.ts (1,093 lines) + docker-console.ts (751 lines) + frontend src/ui/features/docker/*. Start/stop/pause/remove containers, view stats, docker exec terminal. Check for overlap with ArchNest's existing backend/src/integrations/docker.ts adapter (currently just used for health-status resources) before porting — may be able to extend the existing adapter rather than bolt on a second, separate Docker code path.

Phase 5 — RDP/VNC/Telnet (NOT STARTED)

Source: src/backend/guacamole/* + guacamole-lite dependency + frontend src/ui/features/guacamole/*. Biggest infra lift: requires a guacd sidecar container (see docker/docker-compose.yml in the Termix fork) added to ArchNest's own docker-compose.yml — this is a new runtime dependency, not just ported code. Should be scoped in detail (including how guacd networking/ports interact with ArchNest's existing deployment on racknerd1/NPM) before starting.

Also worth checking during/after the phases above

  • src/backend/ssh/host-metrics*.ts (~3,900 lines combined across 8 files) — CPU/memory/disk/network/uptime/firewall/port-monitor/log-viewer/users-permissions/certificates widgets. Not yet assigned to a phase; likely overlaps with ArchNest's existing SSH adapter health probe (backend/src/integrations/ssh.ts) and Infrastructure page — worth a deliberate decision on whether to fold these widgets into the existing Infrastructure page rather than recreate Termix's own dashboard-cards system (src/ui/dashboard/*).
  • src/backend/ssh/host-transfer.ts (3,428 lines) — appears to be server-to-server file/data transfer; likely folds into Phase 3 (file manager) rather than being separate.
  • Data export/import of SSH hosts/credentials/file-manager data — a nice-to-have, not yet scheduled.

Tracking

Update the phase status lines above as work lands. Each phase should get its own commit(s) on claude/wonderful-faraday-qxym5t, following the existing commit message style (descriptive title + why, Co-Authored-By/Claude-Session trailer).