Expands the Containers feature with two new ways to see and manage Docker containers without exposing the Docker Engine TCP socket, plus the docs and roadmap entries that frame them. Docker over SSH (management): - Runs the `docker` CLI on a remote SSH host instead of talking to the Engine TCP API, reusing the existing SSH transport (jump-host chaining, host-key verification, key/password auth) via connectTarget + execCommand. No dockerd socket has to be exposed — the mesh + SSH auth are the gate. - backend/src/ssh/docker.ts: list/logs/start/stop/restart/pause/unpause/remove and an interactive `docker exec` shell builder. Container refs are validated against a strict allowlist and single-quoted to prevent command injection; action verbs are whitelisted. - backend/src/routes/dockerSsh.ts: REST routes mirroring the TCP Docker API shape (mutating actions gated by adminOnly) + a /api/docker-ssh/exec WebSocket modeled on the terminal PTY plumbing. - Note: the SSH path uses the ssh2 key/password auth; it does not implement the OpenSSH-certificate (OPKSSH) fallback that the terminal route has. Docker push-agent monitoring (self-hosted, read-only): - A small bash agent (agent/archnest-docker-agent.sh) runs on each Docker VM, collects a rich snapshot (docker ps + inspect + a stats snapshot), masks secret-looking env values locally, and POSTs it to ArchNest. VMs need outbound-only mesh access — no exposed port, no SSH for monitoring. - backend/src/routes/agents.ts: token-gated ingest (POST /api/agents/docker/report, ARCHNEST_AGENT_TOKEN, constant-time compare; 503 when unset, so it is disabled by default) plus user-auth read endpoints (hosts list with staleness flag, per-host containers, single-container detail). New docker_agent_reports table (latest report per host). - Ingest stores data only; it never executes anything from the agent. Containers page: - Host selector now spans Docker API, SSH, and Agent sources. - Intra-page tabs: a Containers list plus dynamic, closeable per-container detail tabs opened by clicking a container name. Agent detail shows overview/state/stats/ports/networks/mounts/env(masked)/labels; docker/ssh degrade gracefully. Agent rows are read-only; docker/ssh keep management. Docs/roadmap: - docs/docker-agent-monitoring.md (design doc, written before implementation). - ROADMAP.md: LXC management (paid), Docker monitoring agent tiering (push self-hosted now / pull-agent paid), terminal grid tiering. Deferred (documented, not built here): the mesh-prerequisite setup gate, the paid pull-agent (Option 2), per-host tokens, time-series metrics. Requires ARCHNEST_AGENT_TOKEN in the backend env to enable agent ingest. Verified: backend `tsc --noEmit` and frontend `tsc -b && vite build` both pass; agent jq filters, byte conversion, and `bash -n` checked locally. Co-authored-by: Samuel James <ssamjame@amazon.com> Co-authored-by: Kiro <noreply@kiro.dev> |
||
|---|---|---|
| .github/workflows | ||
| .kiro/steering | ||
| agent | ||
| assets | ||
| backend | ||
| docs | ||
| pics | ||
| public | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| design-decisions.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.js | ||
| HANDOFF.md | ||
| homarr-bookmarks-import.json | ||
| index.html | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| ROADMAP.md | ||
| TERMIX_MIGRATION.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
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; Phase 2
(password change + sessions + login audit log) and Phase 3 (multi-user
accounts with admin/member roles, 10-seat cap) have shipped. Phase 4
(Authentik SSO) is deferred to a paid add-on for the future AWS
deployment — see ROADMAP.md. With Phases 1-3 done there is no active
auth task in the current self-hosted build; see HANDOFF.md for the full
phase breakdown.
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 ROADMAP.md
(deferred/planned work, incl. the paid SSO add-on) and TERMIX_MIGRATION.md
(history of how the SSH/Docker/Guacamole feature set was built) if you need
that context.
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 bylocalStorage(JWT carrying a server-tracked session id; signing out revokes the session server-side).src/pages/— one file per route (see table above), plusLogin.tsx/Enrollment.tsxfor 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,heroObjectPositionlookup maps) andtopBarHeightlookup for pages with a subtitle (currently only BookNest).
Backend (/backend)
- Fastify 5, TypeScript, ESM (
tsxfor dev,tsc -bfor build), entrypointsrc/server.ts. backend/src/db/index.ts— SQLite schema +logEvent()audit log, plussessions/login_eventstables and a multi-userusersschema (roleadmin/member +activecolumns).backend/src/db/crypto.ts— AES-256-GCMencryptSecret/decryptSecret, keyed byARCHNEST_SECRET_KEY.backend/src/routes/— one file per feature area:auth.ts— setup, login, profile, password change, sessions, login audit log, and admin-only user management (/api/setup,/api/auth/*,/api/users)integrations.ts— integration CRUD + connection testingbookmarks.ts— bookmarks + categories CRUDevents.ts— activity log retrievalterminal.ts— SSH terminal WebSocket (connect/input/resize/list_tmux/disconnect)tunnels.ts— SSH tunnel CRUD + connect/disconnectfiles.ts— SFTP list/read/write/mkdir/rename/delete/chmod/download/uploaddocker.ts— Docker exec WebSocket (interactive container shell)guacamole.ts— Guacamole WebSocket proxy for remote desktopmetrics.ts— live host metrics endpointtransfer.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 implementstestConnection()(required) andlistResources()(optional);registry.tsmapsIntegrationType→ 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 authsftp.ts— ephemeral SFTP connections for file opstransfer.ts— streamed host-to-host copy/move with progress + cancelmetrics/— 10 sequential collectors (cpu, memory, disk, uptime, network, system, processes, ports, firewall, login-stats) — sequential on purpose, to stay under OpenSSH'sMaxSessionslimit 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,
tsxfor dev,tsc -bfor build better-sqlite3for storage@fastify/jwtfor auth tokens,bcryptjsfor password hashingzodfor 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
guacdsidecar
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 racknerd1 →
Build, restart, and clean up → Health 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 ofdesign-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.mdis 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.