When adding/editing a Docker integration with a tcp:// or http:// remote
URL, display a copyable systemd override + curl verification script
scoped to the entered host:port, so enabling the daemon's API doesn't
require looking up the steps separately.
* Add mesh prerequisite gate (NetBird verification before app config)
Implements the design in docs/mesh-prerequisite-gate.md per the user's
DECIDE A-D answers: a permanent admin override, B1 (reachable) verification
with host mesh IP shown informationally, members allowed in with a notice
instead of being blocked, and mesh.required defaulting off so the live
production instance is unaffected.
- system_config kv table + getConfig/setConfig helpers
- /api/system/mesh-status, /mesh/verify, /mesh/override, /mesh/required
- AuthContext gains a 'needs-mesh' status (admins only) and exposes
meshStatus for a member-facing banner
- MeshGate page reuses the integration create+test flow to connect NetBird
* Make mesh verification universal (CIDR check, not NetBird-specific)
Replace the NetBird-adapter-based "reachable" check with a vendor-agnostic
one: the admin supplies the mesh's IP range (CIDR), and verification just
confirms this host has an address inside it. Works identically for
NetBird, WireGuard, ZeroTier, Tailscale, or any other mesh tech, with no
integration record or vendor API call required.
* Add reachability fallback for routed meshes (VPC peering, etc.)
A host can be on the mesh's "side" of a routed network (e.g. a VPC peered
into a NetBird/WireGuard mesh) without holding a local IP in the mesh's
own CIDR. Local-IP-in-CIDR stays the primary check; if it fails, the admin
can supply a known peer/gateway IP on the mesh and we verify by pinging
it instead. Adds iputils to the backend image for the ping binary.
* Add Mesh section to Settings for configuring/testing the mesh gate
Admins can now toggle mesh.required, run verify/override, and see
current mesh status entirely from the app, without hitting the API
directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hu9pZvJY4BgmcQeAw2ugk
---------
Co-authored-by: Claude <noreply@anthropic.com>
Admins can now toggle mesh.required, run verify/override, and see
current mesh status entirely from the app, without hitting the API
directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hu9pZvJY4BgmcQeAw2ugk
* Add mesh prerequisite gate (NetBird verification before app config)
Implements the design in docs/mesh-prerequisite-gate.md per the user's
DECIDE A-D answers: a permanent admin override, B1 (reachable) verification
with host mesh IP shown informationally, members allowed in with a notice
instead of being blocked, and mesh.required defaulting off so the live
production instance is unaffected.
- system_config kv table + getConfig/setConfig helpers
- /api/system/mesh-status, /mesh/verify, /mesh/override, /mesh/required
- AuthContext gains a 'needs-mesh' status (admins only) and exposes
meshStatus for a member-facing banner
- MeshGate page reuses the integration create+test flow to connect NetBird
* Make mesh verification universal (CIDR check, not NetBird-specific)
Replace the NetBird-adapter-based "reachable" check with a vendor-agnostic
one: the admin supplies the mesh's IP range (CIDR), and verification just
confirms this host has an address inside it. Works identically for
NetBird, WireGuard, ZeroTier, Tailscale, or any other mesh tech, with no
integration record or vendor API call required.
* Add reachability fallback for routed meshes (VPC peering, etc.)
A host can be on the mesh's "side" of a routed network (e.g. a VPC peered
into a NetBird/WireGuard mesh) without holding a local IP in the mesh's
own CIDR. Local-IP-in-CIDR stays the primary check; if it fails, the admin
can supply a known peer/gateway IP on the mesh and we verify by pinging
it instead. Adds iputils to the backend image for the ping binary.
---------
Co-authored-by: Claude <noreply@anthropic.com>
A host can be on the mesh's "side" of a routed network (e.g. a VPC peered
into a NetBird/WireGuard mesh) without holding a local IP in the mesh's
own CIDR. Local-IP-in-CIDR stays the primary check; if it fails, the admin
can supply a known peer/gateway IP on the mesh and we verify by pinging
it instead. Adds iputils to the backend image for the ping binary.
Replace the NetBird-adapter-based "reachable" check with a vendor-agnostic
one: the admin supplies the mesh's IP range (CIDR), and verification just
confirms this host has an address inside it. Works identically for
NetBird, WireGuard, ZeroTier, Tailscale, or any other mesh tech, with no
integration record or vendor API call required.
Implements the design in docs/mesh-prerequisite-gate.md per the user's
DECIDE A-D answers: a permanent admin override, B1 (reachable) verification
with host mesh IP shown informationally, members allowed in with a notice
instead of being blocked, and mesh.required defaulting off so the live
production instance is unaffected.
- system_config kv table + getConfig/setConfig helpers
- /api/system/mesh-status, /mesh/verify, /mesh/override, /mesh/required
- AuthContext gains a 'needs-mesh' status (admins only) and exposes
meshStatus for a member-facing banner
- MeshGate page reuses the integration create+test flow to connect NetBird
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>
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>
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>
The Phase 3 Users section uses the ManagedUser type but it was never
added to the import from ../lib/api, which broke the CI frontend build
(`tsc -b && vite build`: "Cannot find name 'ManagedUser'"). A local
`tsc --noEmit` had passed off a stale incremental build cache and missed
it. Add the type to the existing import; verified with a clean
`tsc -b && vite build`.
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>
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>
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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
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>
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.
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'.
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.
- 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
- 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
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.
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.
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.
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
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
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