ArchNest paid SaaS line (AWS) — forked from open-source v1 (dev_archnest v1.0)
Find a file
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
.github/workflows Production deploy: nginx /api proxy, native-module toolchain, hardened CI 2026-06-19 14:22:08 -06:00
.kiro/steering docs: fix conflicting Kiro steering/spec files (#25) 2026-06-20 10:51:11 -04:00
assets Restore Resource Distribution card, remove Resource Utilization, apply blank KPI bg to status row 2026-06-18 17:26:39 +00:00
backend Add auth Phase 3: multi-user accounts with admin/member roles (#28) 2026-06-20 12:43:24 -04:00
pics network kip 2026-06-18 12:34:31 -04:00
public Fix favicon, dark select dropdowns, add brand bookmark icons and Help page 2026-06-19 21:13:32 +00:00
src Add auth Phase 3: multi-user accounts with admin/member roles (#28) 2026-06-20 12:43:24 -04:00
.dockerignore Add Docker deployment and GitHub Actions workflow for racknerd1 2026-06-18 14:18:00 +00:00
.env.example Update docs: mark feature work complete, document deploy setup as the only remaining task 2026-06-19 16:41:32 +00:00
.gitignore Add backend skeleton: Fastify + SQLite API with auth and integrations 2026-06-18 19:04:48 +00:00
design-decisions.md docs: realign design docs with deployed app, consolidate, rewrite README (#24) 2026-06-20 10:22:04 -04:00
docker-compose.yml Wire guacd sidecar into docker-compose for Remote Desktop deployment 2026-06-19 16:03:40 +00:00
Dockerfile Add Docker deployment and GitHub Actions workflow for racknerd1 2026-06-18 14:18:00 +00:00
eslint.config.js update 2026-06-18 08:14:00 -04:00
HANDOFF.md Add auth Phase 3: multi-user accounts with admin/member roles (#28) 2026-06-20 12:43:24 -04:00
homarr-bookmarks-import.json Add bulk delete-all for bookmarks (#20) 2026-06-20 09:09:44 -04:00
index.html Fix favicon, dark select dropdowns, add brand bookmark icons and Help page 2026-06-19 21:13:32 +00:00
nginx.conf Production deploy: nginx /api proxy, native-module toolchain, hardened CI 2026-06-19 14:22:08 -06:00
package-lock.json Phase 5: RDP/VNC/Telnet remote desktop via guacamole-lite + guacd 2026-06-19 15:25:10 +00:00
package.json Phase 5: RDP/VNC/Telnet remote desktop via guacamole-lite + guacd 2026-06-19 15:25:10 +00:00
README.md docs: fix conflicting Kiro steering/spec files (#25) 2026-06-20 10:51:11 -04:00
TERMIX_MIGRATION.md Update docs: mark feature work complete, document deploy setup as the only remaining task 2026-06-19 16:41:32 +00:00
tsconfig.app.json update 2026-06-18 08:14:00 -04:00
tsconfig.json update 2026-06-18 08:14:00 -04:00
tsconfig.node.json update 2026-06-18 08:14:00 -04:00
vite.config.ts Add Phase 1a: core SSH terminal (Termix migration) 2026-06-19 10:52:04 +00:00

ArchNest

A self-hosted ops dashboard for a homelab/cloud setup: live infrastructure monitoring across 9 real integration types, a categorized bookmark hub, a full SSH suite (terminal, tunnels, file manager, host-to-host transfer, live host metrics), Docker container management, and RDP/VNC/Telnet remote desktop — all in one app, with zero mock data anywhere.

This repo is private and will never be public. This README is written for the owner and for any AI session picking up the project cold — it should be detailed enough that neither needs to re-derive context from scratch.

What this is, in one paragraph

ArchNest replaced a Homarr-style bookmark dashboard plus a handful of disconnected admin tools (Proxmox UI, Portainer, separate SSH terminals, WinSCP-equivalents) with one app that talks directly to the underlying systems. It started as a 6-page mockup/portfolio piece and has since grown into an 11-page real tool with a real Fastify backend, real SSH/Docker/cloud integrations, and no synthetic data — every number on every page comes from a live API call, a SQLite-backed table, or an SSH command run against a managed host.

Current state & direction

Live and deployed at archnest.snsnetlabs.com, auto-deploying on every merge to main via .github/workflows/deploy.yml. All 11 pages and their backend routes are built and working — there is no pending/on-hold page. The active area of work is the auth system: the user menu's Profile/Appearance/Security links were fixed in Phase 1; Phases 2-4 (password change + sessions + login audit log, multi-user accounts, Authentik SSO) are planned but not started — see HANDOFF.md for the full phase breakdown and exactly where to resume.

If you're a fresh AI session: read this file, then HANDOFF.md (current task state + standing workflow rules), then design-decisions.md (visual conventions + accurate per-page implementation notes), then TERMIX_MIGRATION.md (history of how the SSH/Docker/Guacamole feature set was built) if you need historical context on a specific feature.

Pages

Page Route What it does
Glance / Home dashboard — system/integration health, resource overview, recent activity, shortcuts
Infrastructure /infrastructure Resource inventory across all integrations — distribution donut, per-resource status grid, integration health, activity
BookNest /booknest Categorized bookmark hub — quick access, favorites, link health, full CRUD
Terminal /terminal Web SSH terminal — multi-tab, split panes, tmux attach, cert auth (OPKSSH)
Tunnels /tunnels SSH tunnel manager — local/remote/dynamic (SOCKS5) forwarding, auto-start, live status
Files /files SFTP file browser/editor over managed SSH hosts, with host-to-host transfer
Containers /containers Docker container management — start/stop/restart/pause/remove, logs, interactive exec
Remote Desktop /remote-desktop RDP/VNC/Telnet sessions via a Guacamole sidecar
Host Metrics /host-metrics Live CPU/memory/disk/network/processes/ports/firewall/login-activity per SSH host, polled every 5s
Settings /settings Profile, Appearance, Security, Integrations, Notifications, Data & Backup, About — deep-linkable via ?tab=
Help /help Static guided tour of every page above
Login / Enrollment /login, /enrollment Auth entry points — not in the sidebar nav

See design-decisions.md's "Page Notes" section for a detailed, per-page breakdown of layout, real data sources, and known quirks — it's kept in sync with the actual code, not a spec written before the page existed.

Architecture

Frontend (/src)

  • React 19 + Vite + TypeScript, Tailwind CSS v4, Recharts (donuts/area charts), Lucide React icons, React Router.
  • src/lib/api.ts — typed fetch wrapper (apiFetch) + one function per backend endpoint + matching TS interfaces. This is the contract between frontend and backend; any new backend route needs a matching entry here.
  • src/lib/AuthContext.tsx — auth state backed by localStorage (single JWT, no session tracking yet — Phase 2 of the auth roadmap).
  • src/pages/ — one file per route (see table above), plus Login.tsx / Enrollment.tsx for the unauthenticated/first-run flows.
  • src/components/TopBar.tsx (title, global search across pages/ integrations/bookmarks, user dropdown), Sidebar.tsx (nav + system-health rollup widget).
  • App.tsx — route table, plus per-route hero-banner config (showHero, heroPaddingTop, heroObjectPosition lookup maps) and topBarHeight lookup for pages with a subtitle (currently only BookNest).

Backend (/backend)

  • Fastify 5, TypeScript, ESM (tsx for dev, tsc -b for build), entrypoint src/server.ts.
  • backend/src/db/index.ts — SQLite schema + logEvent() audit log. Single-user schema today (no role column, no multi-account concept — see HANDOFF.md Phase 3).
  • backend/src/db/crypto.ts — AES-256-GCM encryptSecret/decryptSecret, keyed by ARCHNEST_SECRET_KEY.
  • backend/src/routes/ — one file per feature area:
    • auth.ts — setup, login, profile (/api/setup, /api/auth/login, /api/auth/me)
    • integrations.ts — integration CRUD + connection testing
    • bookmarks.ts — bookmarks + categories CRUD
    • events.ts — activity log retrieval
    • terminal.ts — SSH terminal WebSocket (connect/input/resize/ list_tmux/disconnect)
    • tunnels.ts — SSH tunnel CRUD + connect/disconnect
    • files.ts — SFTP list/read/write/mkdir/rename/delete/chmod/download/upload
    • docker.ts — Docker exec WebSocket (interactive container shell)
    • guacamole.ts — Guacamole WebSocket proxy for remote desktop
    • metrics.ts — live host metrics endpoint
    • transfer.ts — host-to-host file transfer orchestration (start/poll/cancel)
    • data.ts — full backup export/import (integrations + secrets + bookmarks + tunnels)
  • backend/src/integrations/ — one adapter per type, all real (none are stubs): proxmox.ts, docker.ts, netbird.ts, cloudflare.ts, aws.ts, uptimeKuma.ts, weather.ts, ssh.ts, remoteDesktop.ts. Each implements testConnection() (required) and listResources() (optional); registry.ts maps IntegrationType → adapter.
  • backend/src/ssh/ — the shared SSH transport layer used by Terminal, Files, Tunnels, Transfers, and Host Metrics:
    • connect.ts — jump-host chaining, host-key verification, certificate auth
    • sftp.ts — ephemeral SFTP connections for file ops
    • transfer.ts — streamed host-to-host copy/move with progress + cancel
    • metrics/ — 10 sequential collectors (cpu, memory, disk, uptime, network, system, processes, ports, firewall, login-stats) — sequential on purpose, to stay under OpenSSH's MaxSessions limit per host.
  • Docker images run on Alpine; OpenSSL legacy provider is enabled in backend/Dockerfile (OPENSSL_CONF=/etc/ssl/openssl-legacy.cnf) so old-format encrypted PEM keys (BEGIN RSA PRIVATE KEY + DEK-Info) still decrypt under OpenSSL 3 — don't remove this without understanding why.
  • Required env vars, no defaults: ARCHNEST_SECRET_KEY, ARCHNEST_JWT_SECRET. The server refuses to start without both. Optional: ARCHNEST_DB_PATH, PORT, ARCHNEST_GUAC_CRYPT_KEY / ARCHNEST_GUACD_HOST / ARCHNEST_GUACD_PORT, ARCHNEST_CORS_ORIGIN, ARCHNEST_SESSION_LOG_DIR (optional terminal session logging).

Development

Frontend:

npm install
npm run dev

Backend:

cd backend
npm install
ARCHNEST_SECRET_KEY=$(openssl rand -hex 32) ARCHNEST_JWT_SECRET=$(openssl rand -hex 32) npm run dev

ARCHNEST_DB_PATH optionally overrides the SQLite file location (defaults to a local path under backend/). PORT overrides the listen port (check server.ts for the default).

Type-check both before committing — this is the minimum bar, not a substitute for testing in a browser:

npx tsc --noEmit          # from repo root, frontend
cd backend && npx tsc --noEmit   # backend

Vite/the browser surface some runtime errors (e.g. missing icon exports — see the lucide-react gotcha in design-decisions.md) that the type-checker won't catch.

Tech Stack

Frontend

  • React 19 + Vite + TypeScript, React Router, Tailwind CSS v4
  • Recharts (donuts, line/area charts), Lucide React (icons)
  • xterm.js (Terminal page terminal rendering)

Backend

  • Fastify 5 + TypeScript, tsx for dev, tsc -b for build
  • better-sqlite3 for storage
  • @fastify/jwt for auth tokens, bcryptjs for password hashing
  • zod for request validation
  • AES-256-GCM (Node crypto) for encrypting integration secrets at rest
  • SSH client library powering the SSH transport layer (backend/src/ssh/)
  • Guacamole Lite protocol for RDP/VNC/Telnet, proxied to a guacd sidecar

Integrations: Proxmox, Docker, NetBird, Cloudflare, AWS, Uptime Kuma, Weather (wttr.in), SSH, Remote Desktop (RDP/VNC/Telnet via Guacamole) — see backend/src/integrations/ for adapter implementations.

Deploy target: Docker on racknerd1 → Nginx Proxy Manager at archnest.snsnetlabs.com.

Deployment

Live and deployed. .github/workflows/deploy.yml triggers on every push to main: builds, SCPs the repo to racknerd1, and runs docker compose up -d --build there, gated on an /api/health health check. No further setup is needed — merging a PR to main redeploys automatically.

docker-compose.yml runs 3 services: archnest (frontend), archnest-backend, and guacd (remote desktop sidecar).

If a deploy fails, check the workflow run's deploy job steps in order: Pre-flight (confirms host .env exists) → Copy repo to racknerd1Build, restart, and clean upHealth check (backend /api/health).

One-time setup already done (reference only, shouldn't need repeating): host provisioning (Docker/Compose on racknerd1, deploy SSH user, /opt/archnest directory), /opt/archnest/.env populated from .env.example with real secrets, RACKNERD_HOST/RACKNERD_USER/RACKNERD_SSH_KEY added as GitHub Actions secrets, DNS/Nginx Proxy Manager pointed at the host.

Documentation map

  • README.md (this file) — architecture, tech stack, deployment, page list.
  • HANDOFF.md — current task state, standing workflow rules (git workflow, mock-data policy, secrets discipline), and the auth/SSO roadmap. Read this before starting any new work session.
  • design-decisions.md — visual/UX conventions (colors, typography, card style, animations) plus a detailed, accurate-as-of-now "Page Notes" section per page — what's actually rendered and where its data comes from. This is the file to update whenever a page's layout or data source changes.
  • TERMIX_MIGRATION.md — phase-by-phase history of how the SSH/Tunnels/ Files/Containers/Remote Desktop/Host Metrics/Transfer/Data-export feature set was built (originally scoped as a migration from a forked Termix project, hence the name). Useful for historical "why was it built this way" context on those specific features.
  • .kiro/steering/design-rules.md — a condensed duplicate of design-decisions.md's Global Rules, auto-injected into every Kiro IDE session (the Kiro extension reads .kiro/steering/* automatically). If you update a global design rule, update both files in the same change — design-decisions.md is canonical, this one just needs to stay in sync so Kiro doesn't steer on stale info.

Three older docs were deleted as part of a documentation cleanup: archnest-blueprint.md and glance.md (the original 6-page mockup pitch and an early Glance-only spec, both describing fictional config files and placeholder numbers that never matched the real build), and .kiro/specs/archnest-dashboard/ (an abandoned Kiro spec — requirements-only, no design.md/tasks.md ever followed — describing the same stale 6-page/ 80px-sidebar/Zustand-based vision). Their still-accurate content (color palette, dropdown menu shape, card styling) was folded into design-decisions.md and .kiro/steering/design-rules.md; everything else was superseded by the real, deployed implementation described above.