Commit graph

110 commits

Author SHA1 Message Date
Samuel James
d863448495
Add auth Phase 3: multi-user accounts with admin/member roles (#28)
Implements Phase 3 of the auth roadmap: multiple user accounts (cap 10),
an admin/member role model, and admin-only gating of config-mutating
routes. Dashboard data stays shared across all users (per the product
decision in HANDOFF.md — this is a household/self-hosted dashboard, not
a multi-tenant app), so there is no per-user data isolation.

Schema (backend/src/db/index.ts):
- Idempotent migration adds `role` (default 'admin') and `active`
  (default 1) columns to `users` when missing. The 'admin' default means
  the pre-existing single user is backfilled to admin on deploy and keeps
  full access; newly created users are inserted explicitly as 'member'.
  Verified against a production-like old schema (columns added, existing
  user backfilled to admin/active).

Auth + access control:
- `/api/setup` creates the first user as admin. Login enforces `active`
  (deactivated accounts get 403) and embeds the live role in the session.
- `app.authenticate` now reads role+active fresh from the DB on every
  request (not from the possibly-stale JWT claim), rejects inactive
  accounts, and stashes the role on req.user.
- New `requireAdmin` (auth + role check) and `adminOnly` (role check for
  routes already behind the plugin-level authenticate hook) decorators.

User management (admin-only, in auth.ts):
- GET/POST/PUT/DELETE /api/users — list, create (admin sets a temp
  password; no public signup), change role, activate/deactivate, delete.
- 10-user cap enforced server-side; guard rails prevent removing the last
  active admin (demote/deactivate/delete) and deleting your own account;
  deactivating or deleting a user drops their sessions immediately.

Admin-only route gating (members get 403):
- integrations create/update/delete/test, tunnels create/delete, data
  export/import. Read routes and tunnel connect/disconnect stay open to
  all authenticated users, as do all the SSH/Docker/RDP tools and
  bookmarks (members are trusted to use the tooling, per product decision).

Frontend:
- api.ts: listUsers/createUser/updateUser/deleteUser + ManagedUser type;
  role+active added to AuthUser.
- Settings: new admin-only "Users" section (create form, role toggle,
  activate/deactivate, delete, 10-cap indicator). Nav filters the Users
  tab by role and guards ?tab= deep-links. Data & Backup shows an
  admin-only notice for members; Integrations shows a read-only banner
  for members. (Backend remains the real enforcement boundary.)

Verified end-to-end against a throwaway backend: role assignment,
member 403s on every admin-only route + 200s on shared/read routes,
admin 200/201s, last-admin guards (409/400), deactivation killing an
active session and blocking re-login (then reactivation restoring it),
and the 10-user cap (409 on the 11th). Both frontend and backend
type-check clean.

Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>
2026-06-20 12:43:24 -04:00
Samuel James
2ccc7b82d7
Add auth Phase 2: password change, sessions, and login audit log (#27)
Builds out the Settings → Security tab (previously a "coming soon"
placeholder) and the backend behind it. Still single-user; multi-user
and SSO remain Phases 3-4.

Backend:
- New `sessions` table (id, user_id, user_agent, ip, created_at,
  last_seen_at) and `login_events` table (user_id, username, ip,
  user_agent, success, created_at).
- Login and setup now mint a session row and embed its id as a `sid`
  claim in the JWT. The `authenticate` hook validates that the session
  still exists (and bumps last_seen_at), so revoking a session genuinely
  invalidates its token instead of relying on the JWT signature alone.
  Tokens minted before sessions existed have no `sid` and stay valid
  until expiry, for backward compatibility.
- Every login attempt (success and failure) is recorded in login_events
  for the audit trail.
- New endpoints: PUT /api/auth/password (verifies current via bcrypt,
  hashes new at cost 12, revokes all *other* sessions on success),
  GET /api/auth/sessions, DELETE /api/auth/sessions/:id (can't revoke
  the current one), POST /api/auth/logout (revokes current session),
  GET /api/auth/login-events?limit.
- AuthContext.logout() now calls POST /api/auth/logout best-effort so
  signing out revokes the server session, not just the local token.

Frontend:
- SecuritySection: change-password form (current/new/confirm with
  show/hide and client-side validation), active-sessions list (device
  description from user-agent, IP, last-seen relative time, per-session
  "Sign out" for non-current sessions), and a recent login-activity feed
  (success/failure dot, user, IP, relative time).
- api.ts: changePassword/listSessions/revokeSession/logout/
  listLoginEvents + AuthSession/LoginEvent types.

Verified end-to-end against a throwaway backend instance: session
creation, second-device session, failed-login logging, cross-session
revocation invalidating the revoked token, password change keeping the
current session alive while revoking others, and logout invalidating the
current session. Frontend + backend both type-check clean.

Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>
2026-06-20 11:50:56 -04:00
Samuel James
993792e193
Fix terminal failing for SSH hosts with a non-cert "certificate" secret (#26)
The Terminal page failed to open a shell for one host (Linode) while
Host Metrics worked fine for the same host, and other hosts (pve1/pve2)
worked everywhere. Root cause: the terminal route takes a special
certificate-auth path whenever an SSH integration has ANY `certificate`
secret set, and that path shells out to the system `ssh` binary under a
pty instead of using the ssh2 library. The metrics path always uses
ssh2, which is why it was unaffected.

That host's `certificate` secret was actually a plain public key
(`ssh-ed25519 AAAA...`), not an OpenSSH certificate. ssh discarded it
("is not a certificate") and then could not load the private key under
the container's libcrypto ("error in libcrypto: unsupported"), ending in
"Permission denied (publickey)". With ssh2 (the metrics path), the same
private key authenticates fine.

Two fixes:
- Only take the cert-auth path when the secret is a genuine OpenSSH
  certificate (key type ends in `-cert-v01@openssh.com`). A plain public
  key now falls through to the normal ssh2 key/password path, which
  already works (proven by the metrics endpoint using the same key).
- Add `-o IdentitiesOnly=yes` to the cert-auth ssh invocation so it only
  offers the provided key/cert and isn't confused by a stray file.

No server-side or key changes were needed on the affected host; this is
purely a routing/robustness fix in the terminal WebSocket handler.

Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>
2026-06-20 11:29:36 -04:00
Samuel James
b2600e2577
docs: fix conflicting Kiro steering/spec files (#25)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* docs: fix conflicting Kiro steering/spec files missed in prior cleanup

The earlier doc-cleanup pass only checked root-level markdown files and
missed .kiro/, a dotdir holding Kiro IDE spec-driven-dev artifacts. Two
files there directly contradicted the real app and design-decisions.md:

- .kiro/steering/design-rules.md is auto-injected into every Kiro
  session, but stated an 80px/50px sidebar and Zustand state management
  — both wrong (real app: 200px/64px sidebar, plain React state +
  localStorage, no Zustand). Rewrote it to match design-decisions.md so
  Kiro doesn't steer future sessions on stale info.
- .kiro/specs/archnest-dashboard/requirements.md was an abandoned
  requirements-only spec (no design.md/tasks.md ever followed) for the
  original 6-page/Network-page/CDN-asset vision, same vintage as the
  already-deleted archnest-blueprint.md. Removed the whole spec
  directory since nothing references it and it never matured past
  requirements.

Documented both files (and why they were removed/rewritten) in the
README's documentation map.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 10:51:11 -04:00
Samuel James
d8286ac42b
docs: realign design docs with deployed app, consolidate, rewrite README (#24)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* docs: realign design docs with the deployed app, consolidate, rewrite README

README.md was badly stale (listed Terminal as "pending/on hold" and only
5 pages, when all 11 pages are built and live). Rewrote it as a detailed,
accurate map of the architecture, every page, every backend route,
every integration adapter, and the SSH subsystem, written explicitly for
this repo's actual audience (the owner + future AI sessions, never the
public).

Deleted archnest-blueprint.md and glance.md: both were pre-backend mockup
specs describing fictional config files (systems.config, infra.config,
fail2ban-driven security scoring) and placeholder data that never matched
the real implementation, and conflicted with the deployed app's actual
page count/nav/data sources. Their still-true content (color palette,
dropdown menu shape, card styling) was folded into design-decisions.md.

Rewrote design-decisions.md's "Page-Specific Notes" into a full "Page
Notes" section covering all 11 pages plus Login/Enrollment (previously
only 4 pages had notes, and those didn't reflect later changes like
Files/Tunnels/Containers/Host Metrics/Remote Desktop shipping). Each
section now states the real data source per page so it can't drift from
the code silently again.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 10:22:04 -04:00
Samuel James
2229a8254a
docs: remove superseded Homarr bookmarks markdown export (#23)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* docs: remove superseded Homarr bookmarks markdown export

homarr-bookmarks.md was a recovery/source-data snapshot (78 entries,
explicitly caveated as approximate pending live-DB confirmation) whose
sole purpose was generating homarr-bookmarks-import.json, which has
since been committed and is the actionable artifact. No other file
references the .md version, so it's now stale duplicate data.

Audited the other root-level markdown files (HANDOFF.md, README.md,
TERMIX_MIGRATION.md, archnest-blueprint.md, design-decisions.md,
glance.md) for conflicting claims against each other and the current
codebase — no contradictions found. archnest-blueprint.md's 6-page nav
table is outdated relative to the real app's 11 pages, but the file is
explicitly framed (in README.md) as the original/historical design
spec rather than a current-state doc, so it's left as-is.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 10:04:45 -04:00
Samuel James
ae5142769d
Update handoff docs for deployed state and auth roadmap (#22)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* Update handoff docs for deployed state and auth-system work-in-progress

HANDOFF.md and README.md still described deployment as the open task;
the app has been live on racknerd1 for several sessions now. Rewrites
both to reflect current state and lay out the 4-phase auth/SSO plan
(menu fix done, password/sessions/login-log/multi-user/SSO pending) so
the next session can pick up at Phase 2 without re-deriving context.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 09:50:06 -04:00
Samuel James
ea14486f6e
Wire up Profile/Appearance/Security in user menu (#21)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* Wire up Profile/Appearance/Security in the user menu

Profile, Appearance, and Security were dead "#" links. Added URL-based
tab deep-linking to the Settings page (?tab=profile|appearance|security|...)
and pointed the menu items at it. Added a Security tab placeholder ahead
of password/sessions/login-log/SSO work.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 09:44:43 -04:00
Samuel James
d9d9f3f610
Add bulk delete-all for bookmarks (#20)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* Add bulk delete-all for bookmarks

Adds DELETE /api/bookmarks to clear every bookmark in one request, and a
"Delete All" button (with confirmation) on the BookNest page so re-imports
don't require deleting dozens of entries one at a time.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 09:09:44 -04:00
Samuel James
ad2cfe808c
Default-collapse already-configured SSH hosts on page load (#19)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Default-collapse already-configured SSH hosts on page load

Previously every SSH host card reset to expanded on each page visit,
showing blank secret fields that looked like saved keys had been
deleted. Hosts with saved secrets now start collapsed on first load.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 09:00:04 -04:00
Samuel James
933845a222 docs: add recovered Homarr bookmarks list (78 entries incl. Uptime Kuma) 2026-06-20 06:59:33 -06:00
Samuel James
63adccb1c7
Show saved indicator for secret fields instead of appearing deleted (#18)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Show saved indicator for secret fields instead of appearing blank/deleted

GET /api/integrations never returns decrypted secret values (by design),
so after navigating away and back, secret/key fields rendered empty -
looking exactly like the saved key had been deleted, even though it was
still intact and encrypted in the database. Expose which secret keys
exist (names only, never values) via secretKeys, and use it to label
fields as "saved" with an appropriate placeholder instead of blank.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 08:53:56 -04:00
Samuel James
7a1d260a35
Enable OpenSSL legacy provider for old-format encrypted SSH keys (#17)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Enable OpenSSL legacy provider for old-format encrypted SSH keys

OpenSSL 3's default provider disables the MD5-based KDF used by
traditional encrypted PEM keys (BEGIN RSA PRIVATE KEY + DEK-Info
headers), causing "error in libcrypto: unsupported" when the ssh
binary tries to decrypt them for certificate-based auth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 08:45:02 -04:00
Samuel James
5a3e4c51f9
Fix integration save data loss; add SSH host card collapse (#16)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Fix integration save wiping untouched config fields

The PUT /api/integrations/:id route fully overwrites config_json with
whatever config object is sent (no merge), but buildPayload only
included fields the user had actually edited. Saving after editing
just one field (e.g. pasting a new SSH key) silently dropped every
other config field. Merge the existing integration's config into the
payload before sending.

* Add collapse/expand for SSH host cards

Click the chevron to collapse a host's card once it's configured.
Collapsed cards keep all field state in memory (just hidden), and
auto-collapse after a successful Save.

* Install openssh-client in backend image for certificate-auth SSH

Certificate-based SSH connections shell out to the system ssh binary
via node-pty (ssh2 has no OpenSSH certificate support), but the
alpine runtime image never installed openssh-client. This caused
'execvp(3) failed: No such file or directory' for any host with an
OPKSSH certificate configured.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 08:30:21 -04:00
Samuel James
b2b4709abe
Add file upload for SSH private key and certificate fields (#15)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 08:11:32 -04:00
Samuel James
c23724bade
Add explicit Host Name field for SSH hosts (#14)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add explicit Host Name field for SSH hosts

The SSH host name was only editable via a small inline header input,
not a clearly labeled form field like the other options (Host / IP,
Port, etc). Add a proper "Host Name" labeled field to both existing
and new SSH host forms.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 07:48:22 -04:00
Samuel James
39c94e4045
Add editable name field to generic integrations (#13)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 07:40:03 -04:00
Samuel James
7de7c1c84e
Allow renaming SSH hosts in Settings (#12)
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 07:23:22 -04:00
Samuel James
20ebd3891b
Use network-traffic background for Integration Health card (#11)
* Use archnest-network-traffic-bg for Connected Integrations card background

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF

* Use archnest-network-traffic-bg for Integration Health card

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 07:15:32 -04:00
Samuel James
a453342de4
Use archnest-network-traffic-bg for Connected Integrations card background (#10)
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 07:02:38 -04:00
Samuel James
b11150d286
Add background images to Connected Integrations and Integration Health cards; fix Node Status scroll clipping (#9)
Both cards were flat with no background frame. Node Status used a centered grid inside an overflow-hidden card, which clipped rows beyond the visible area instead of letting them scroll into view.


Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 06:54:21 -04:00
Samuel James
ddee4b498d
Merge pull request #8 from SamuelSJames/claude/wonderful-faraday-qxym5t
Fix Proxmox connection by using undici's fetch instead of Node's global fetch
2026-06-20 06:38:17 -04:00
Claude
da8555d817
Fix Proxmox connection by using undici's fetch instead of Node's global fetch
Node's global fetch is backed by an internal undici version bundled with Node itself, which differs from the 'undici' npm package used for the insecure Agent (needed for Proxmox's self-signed cert). Passing an Agent from one undici version as the dispatcher for the other's fetch trips an internal handler-shape check, producing 'invalid onRequestStart method'. Importing fetch from the same undici package as the Agent keeps both on the same internal interface.
2026-06-20 10:36:43 +00:00
Samuel James
af4d2bac31
Merge pull request #7 from SamuelSJames/claude/wonderful-faraday-qxym5t
Surface underlying network error instead of generic 'fetch failed'
2026-06-20 06:32:20 -04:00
Claude
cbd666fe60
Surface underlying network error instead of generic 'fetch failed'
undici's fetch() collapses DNS/TLS/connection-refused/timeout failures into a vague TypeError. Unwrap err.cause so Test Connection shows the real reason (e.g. ECONNREFUSED, certificate error) instead of just 'fetch failed'.
2026-06-20 10:31:11 +00:00
Samuel James
0cf53fc1f1
Merge pull request #6 from SamuelSJames/claude/wonderful-faraday-qxym5t
Support multiple integration instances and add credential field hints
2026-06-20 06:20:28 -04:00
Claude
9157a2647a
Support multiple integration instances and add credential field hints
Proxmox, Docker, AWS, and Remote Desktop now allow adding multiple instances, each with its own Save/Test/Remove. Credential fields like Proxmox's API token now show inline guidance on the required format instead of a bare label.
2026-06-20 10:18:04 +00:00
Samuel James
fdb5a8baa3
Merge pull request #5 from SamuelSJames/claude/wonderful-faraday-qxym5t
Fix page titles, dropdown stacking, bookmark editing, and UI polish
2026-06-19 17:36:15 -04:00
Claude
fc9d685651
Fix page titles, dropdown stacking, bookmark editing, and button/select polish
- Add missing pageTitles entries so Tunnels, Files, Containers, Remote
  Desktop, and Host Metrics no longer show "Glance" in the top bar
- Raise the TopBar's stacking context above the page-content section so
  the user-menu dropdown no longer renders behind the hero banner
- Support editing and deleting bookmarks (not just adding), via a
  shared BookmarkModal and per-row edit/delete actions in BookNest
- Standardize <select> styling globally (gold-tinted border, hover/
  focus glow) instead of three inconsistent inline style definitions
- Widen cramped button padding/borders in Files and Containers
2026-06-19 21:34:04 +00:00
Samuel James
3e41571dd7
Merge pull request #4 from SamuelSJames/claude/wonderful-faraday-qxym5t
Fix favicon, dark select dropdowns, add brand bookmark icons and Help…
2026-06-19 17:20:06 -04:00
Claude
57086d2f6f
Fix favicon, dark select dropdowns, add brand bookmark icons and Help page
- Replace mislabeled Vite-logo favicon.svg with proper ArchNest mark
  extracted from the logo, generated at 32/48/256px PNGs
- Force native <select>/<option> elements to render with the dark theme
  (color-scheme + explicit colors) so options are readable
- Auto-detect real brand/service icons for bookmarks (AWS, Proxmox,
  Azure, Docker, etc.) via the dashboard-icons CDN, with manual
  override and graceful fallback to lucide icons
- Add a Help page with a guided tour of every page, linked from the
  sidebar, top-bar search, and the user dropdown menu
2026-06-19 21:13:32 +00:00
Samuel James
2dcd89eb4a Remove unused RotateCcw import breaking the production build
tsc --noEmit passed but 'tsc -b' (used by npm run build, with noUnusedLocals)
failed on the unused lucide-react import, blocking the CI validate job.
2026-06-19 14:25:11 -06:00
Samuel James
ef5e497554 Production deploy: nginx /api proxy, native-module toolchain, hardened CI
The frontend calls the API with relative paths (fetch('/api/...')), so in
production those requests hit the nginx frontend container on :8080 — which
previously only served the SPA and would 404 every API call and WebSocket
route. nginx.conf now proxies /api/ to the archnest-backend service with
WebSocket upgrade support, long timeouts for terminals/tunnels/transfers, and
a 1GB body limit matching the backend's upload cap.

The backend Dockerfile now installs python3/make/g++ in both the build and
runtime stages so the native modules (better-sqlite3, ssh2, node-pty) compile
on alpine instead of crashing the container at startup.

The deploy workflow gains a validate job (type-check + build both apps before
touching the host), a pre-flight check that refuses to deploy without the
host-side .env, and a post-deploy health check against /api/health and the
frontend, with concurrency guarding.
2026-06-19 14:22:08 -06:00
Samuel James
c834d03752
Merge pull request #3 from SamuelSJames/claude/wonderful-faraday-qxym5t
Claude/wonderful faraday qxym5t
2026-06-19 15:55:49 -04:00
Claude
3d9c4c65c2
Update docs: mark feature work complete, document deploy setup as the only remaining task
HANDOFF.md and TERMIX_MIGRATION.md were stale (pre-dated the full Termix migration). Rewrote HANDOFF.md to reflect the current feature-complete state and point straight at deployment setup. Expanded README's Deployment section into concrete steps (host provisioning, secrets, .env, DNS) since the workflow/compose files already exist and just need configuring. Added a top-level .env.example for the server-side .env that docker-compose.yml expects.
2026-06-19 16:41:32 +00:00
Claude
b74a0e2d36
Wire up TopBar search across pages, integrations, and bookmarks
Search now filters static nav pages, integrations, and bookmarks live as you type, with a results dropdown, Enter-to-navigate, and click-outside-to-close. Browser-verified end-to-end.
2026-06-19 16:37:28 +00:00
Claude
5b17bba80e
Add data export/import (Phase 8): portable JSON backup of config + credentials
GET /api/data/export serializes all integrations (with decrypted secrets, for
cross-instance portability), bookmark categories, bookmarks, and tunnels;
POST /api/data/import restores them additively in a transaction with old->new
id remapping. Wires the Settings "Data & Backup" section to download/upload the
backup file. Verified end-to-end including cross-instance portability under a
different ARCHNEST_SECRET_KEY, plus browser verification of the Settings UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 16:13:29 +00:00
Claude
92640d0777
Verify Telnet and RDP remote-desktop paths end-to-end through guacd
Closes the last Phase 5 coverage gap: ran a real telnetd (via socat) and a real
xrdp server (with the libguac-client-rdp plugin), created telnet/rdp
remote_desktop integrations, and confirmed guacd negotiated both connections
and returned real Guacamole protocol instructions over /api/guacamole. All three
protocols (VNC, telnet, RDP) now verified against the identical route code.

Documentation only; no code changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 16:08:16 +00:00
Claude
a2af06f925
Wire guacd sidecar into docker-compose for Remote Desktop deployment
Adds a guacamole/guacd service to docker-compose.yml and points the backend at
it (ARCHNEST_GUACD_HOST/PORT) plus passes through ARCHNEST_GUAC_CRYPT_KEY, with
depends_on ordering. Documents ARCHNEST_GUACD_* in backend/.env.example. Closes
the Phase 5 deployment gap. Compose validated via `docker compose config`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 16:03:40 +00:00
Claude
e10acfd4a1
Close verification gaps for Phases 1b, 6, 7 via real infra + browser tests
With iproute2 and Playwright/Chromium now available in the sandbox:
- Re-verified host-metrics network/ports/firewall collectors against a real
  root SSH host (real eth0, ss ports with process names, parsed iptables rules).
- Browser-verified the host-metrics page, the terminal tabs/split-panes/theme
  UI (live remote prompt, 1->2->4 xterm panes, prefs persisted), and the
  host-to-host transfer UI (live progress panel to completion + on-disk check).

Updates documentation only; no code changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 16:02:40 +00:00
Claude
29c69224b2
Add host-to-host file transfer (Phase 7)
Ports the core of Termix's host-transfer feature: stream files/directories
between two SSH hosts through the backend via SFTP (read source -> write dest),
with up-front scan for progress totals, recursive directory support, optional
move, and cooperative cancellation. Leaves behind Termix's parallel-segment
workers, tar heuristics, watchdogs and retry orchestration as unjustified at
this scale.

Exposed via REST (start/list/status/cancel) with an in-memory transfer registry,
and surfaced in the Files page as a per-entry "send to another host" action plus
a live transfers progress panel. Verified end-to-end against two real SSH
endpoints: recursive copy (binary md5 match), move (source deleted), error
handling, and mid-stream cancel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 15:52:13 +00:00
Claude
e745eebff9
Close out Phase 1: verify OPKSSH/certificate auth end-to-end, mark phase DONE
The one outstanding Phase 1 verification gap was OPKSSH/certificate auth, which
the original pass couldn't exercise (no ssh CLI in the sandbox). With openssh
now available, built a real SSH CA + signed cert + cert-only sshd and drove
ArchNest's /api/terminal WebSocket route end-to-end: a real shell authenticated
purely via the certificate. Updated the doc and flipped Phase 1 to DONE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 15:46:20 +00:00
Claude
f32d93947b
Add host metrics widgets (Phase 6): CPU/mem/disk/network/processes/ports/firewall/login dashboard
Ports Termix's per-host metrics collector logic onto ArchNest's own SSH
connection helpers (not its multi-user/cache/session scaffolding), exposed via
a new authenticated REST endpoint and a dedicated /host-metrics page with
client-side polling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 15:38:30 +00:00
Claude
c37ad3d0d4
Phase 5: RDP/VNC/Telnet remote desktop via guacamole-lite + guacd
Adds a remote_desktop integration type and a /api/guacamole websocket
route that drives guacamole-lite's ClientConnection directly (bypassing
its Server class, which would otherwise attach an unfiltered upgrade
listener that conflicts with the existing @fastify/websocket routes).
The frontend RemoteDesktop page renders the Guacamole protocol stream
via guacamole-common-js. Verified end-to-end against a real guacd and
VNC server, including in an actual browser session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 15:25:10 +00:00
Claude
52646d866d
Phase 4: Docker container management (REST CRUD/actions/stats/logs + exec terminal)
Extends the existing Engine-API-based docker integration adapter rather than
porting Termix's SSH+CLI approach, since ArchNest's docker integrations only
ever configure a baseUrl. Adds backend/src/docker/{client,exec}.ts and
backend/src/routes/docker.ts (REST + websocket exec-terminal via raw socket
hijack), and a new Containers page wired into the sidebar/router.

Verified end-to-end against a real dockerd instance and a real container in
this sandbox, which caught and fixed a genuine bug: calling /exec/{id}/resize
before starting the exec hangs the daemon indefinitely; fixed by setting the
initial size via ConsoleSize at exec-create time instead.
2026-06-19 12:28:30 +00:00
Claude
7edf4548d9
Phase 3: remote file manager (SFTP list/edit/upload/download/rename/delete/chmod)
Ephemeral per-request SFTP connections, whole-file-in-memory view/edit
with a 50MB cap and binary detection, streaming download for files of
any size, multipart upload. No sudo/permission-elevation or
server-to-server transfer in this pass (documented gaps, matching
Termix's own scope for the latter).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 11:56:04 +00:00
Claude
eaa971bb5a
Phase 2: SSH tunnels (local/remote/dynamic SOCKS5 port forwarding)
- backend/src/ssh/connect.ts: extracted shared SSH-connect logic
  (jump-host chaining, TOFU host-key verification) out of terminal.ts
  so tunnels can reuse it.
- backend/src/tunnels/manager.ts + socks5.ts: in-memory tunnel
  runtime manager supporting local forward (forwardOut), remote
  forward (forwardIn), and dynamic SOCKS5 proxying, with automatic
  reconnect/retry and an auto-start-on-boot option. New `tunnels`
  table persists configs as the saved presets.
- backend/src/routes/tunnels.ts: REST CRUD + connect/disconnect.
- src/pages/Tunnels.tsx: new /tunnels page (sidebar entry added) to
  create, start/stop, and delete tunnels with live status polling.
- Verified end-to-end against a real ssh2 test server handling real
  forwardOut/forwardIn requests and a real upstream TCP echo server -
  all three tunnel modes moved real data, and disconnect correctly
  tore down the local listener.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
2026-06-19 11:40:59 +00:00
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
Claude
94b174c72e
Phase 1b: terminal tabs, up to 4 split panes, theme/font customization
Terminal.tsx is rebuilt around a reusable TerminalPane component (one
xterm + WebSocket connection each) so a tab can hold 1, 2, or 4 panes
(single / split-2 / 2x2 grid), each independently connectable to any SSH
host. Added a small terminal preferences bar (theme preset, font size,
font family) persisted to localStorage and applied per-pane.

Also fixes two build-time issues surfaced while wiring this up: an unused
parameter in Settings.tsx's fieldsWithJumpHost helper, and a stale
JSX.Element reference that doesn't resolve under this project's tsc -b
project-reference build (replaced with React.ReactElement).

This completes Phase 1b of the Termix migration (see 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:12:33 +00:00
Claude
5d56a1d902
Phase 1b: SSH jump-host chaining, TOFU host-key verification, multi-host Settings UI
Terminal connections can now reference a jumpHostIntegrationId on the SSH
integration config; the backend connects to the jump host first and tunnels
to the real target via ssh2's forwardOut(), rather than connecting directly.

Added an ssh_host_keys table and a hostVerifier callback that accepts and
stores a host's fingerprint on first connect, then hard-rejects on any
mismatch on subsequent connects (trust-on-first-use).

Settings previously only ever showed/edited one integration per type, which
silently prevented configuring more than one SSH host at all. Added a
dedicated multi-host SSH section (per-host Save/Test/Delete, Add SSH Host,
and a Jump Host dropdown) so jump-host chaining is actually usable from the UI.

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