Bring the docs in line with what shipped since the auth phases, and hand
off the next planned feature cleanly for another agent to pick up.
- HANDOFF.md: new TL;DR (auth complete; persistent terminals + Docker
three-ways shipped); prominent "next task = Mesh Prerequisite Gate"
callout warning not to code before the open decisions are answered;
corrected standing rules (kiro/<feature> branches, gh-based workflow,
npm run build over plain tsc, Co-authored-by trailers); architecture
sections updated for TerminalSessionContext, dockerSsh/agents routes,
docker_agent_reports table, ssh/docker.ts, and the new agent env vars;
new "Docker: three ways" section.
- README.md: Containers/Terminal page rows, route-group list, SSH layer,
agent/ dir, ARCHNEST_AGENT_TOKEN/ARCHNEST_AGENT_STALE_MS, current-state
paragraph, and doc reading order.
- design-decisions.md: Terminal (persistence) and Containers (three
sources + detail tab) page notes; backend Docker-transport note; mesh
gate flagged under Future Integration Notes.
- docs/mesh-prerequisite-gate.md (new): full design with lockout-safety
invariants and the open decisions (A-D) needed before implementation.
Docs only; no code changed.
Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>
The Terminal page held all session state (xterm instances and their
WebSockets) in component-local React state. Because it renders as a
`<Route element={<Terminal />}>`, navigating away unmounted it and ran
the xterm cleanup (`term.dispose()` + `ws.close()`), tearing down every
SSH session. Returning to the page reconnected from scratch, losing
scrollback and any running work.
Lift terminal sessions into a `TerminalSessionProvider` mounted above the
router (in `main.tsx`, inside `AuthProvider`). The provider owns each
pane's xterm instance, fit addon, WebSocket, and a persistent wrapper DOM
node. Wrappers live in a hidden container at the app root; the Terminal
page re-parents them into its grid on mount and moves them back to the
hidden root on unmount instead of disposing — so the xterm + WebSocket
keep running in the background across route changes.
Disconnect semantics: closing a tab/pane (or shrinking the 1/2/4 grid)
destroys those sessions; logout tears down all sessions. A full browser
reload still drops connections (the WebSocket dies with the page) — this
persists across in-app navigation only.
Shared terminal constants/types/prefs are split into a non-component
module (`src/lib/terminalPrefs.ts`) so the context file stays a clean
component module.
Also document the terminal window grid-view tiering in ROADMAP.md
(self-hosted = 4-window cap, current; paid = as many as fit on screen,
planned for the AWS deployment), and realign HANDOFF/README/design-docs
to reflect that auth Phase 3 (multi-user) shipped and Phase 4 (SSO) is
deferred to a paid AWS add-on.
Verified with a clean `tsc -b && vite build` (frontend) and
`tsc --noEmit -p .` (backend).
Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>
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>
* 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>
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.
Documents the real backend, all 8 completed integration adapters, known
caveats (Proxmox TLS, fast-jwt vuln, SSH key textarea UX, the
IntegrationType/integrationTypes enum duplication footgun), and what's
explicitly on hold (Terminal/Termix), so another AI session can resume
work with full context.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF