diff --git a/HANDOFF.md b/HANDOFF.md index 5888957..b6057e6 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,6 +1,6 @@ # ArchNest — Handoff Notes -Status snapshot as of **2026-06-20**. Written so a fresh AI session (or human) can pick this up with zero prior context. Branch names rotate every session — always run `git branch --show-current` and work on a fresh feature branch off `main` (recent branches have used a `kiro/` naming pattern). +Status snapshot as of **2026-06-21**. Written so a fresh AI session (or human) can pick this up with zero prior context. Branch names rotate every session — always run `git branch --show-current` and work on a fresh feature branch off `main` (recent branches have used a `kiro/` or `claude/` naming pattern). ## TL;DR @@ -12,8 +12,17 @@ Since then, **Docker container visibility/management was expanded** (shipped, de - **Persistent SSH terminal sessions** (PR #30) — terminals stay connected across in-app page navigation. - **Docker-over-SSH management** + **Docker push-agent monitoring** (PR #31) — see the "Docker: three ways" section below. -### → NEXT TASK for the picking-up agent: the **Mesh Prerequisite Gate** -This is **designed but NOT built**. Full design + the 4 open decisions are in **`docs/mesh-prerequisite-gate.md`** — read it first. It requires a NetBird mesh to be configured/tested/verified before the rest of the app can be configured. **The hard part is lockout-safety** (a failed mesh test must never lock the admin out). **Do not start coding until the user answers DECIDE A–D in that doc** (escape-hatch behavior, what "verified" means, member behavior, and crucially whether to default the gate OFF so it doesn't immediately gate the live production instance). Use `AskUserQuestion`. +**The Mesh Prerequisite Gate is now built and shipped** (no longer the open task): NetBird-mesh-required-before-config, with universal CIDR-based verification (not NetBird-specific), a routed-mesh/VPC-peering reachability fallback, and a dedicated "Mesh" section in Settings to configure/test it. Defaults OFF, so it does not lock the live instance. Commits: `46d95fc` (gate), `0409159` (universal CIDR check), `800072f` (routed-mesh fallback), `4a4a5a0` (Settings UI) — all merged to `main`. + +Most recently (this session, real user dogfooding rather than a planned feature): walked the user through replacing a broken/insecure Docker-TCP-API integration attempt with a working **SSH Host** integration to a real VM ("Portainer VM," running Portainer + a test container), confirmed Docker-over-SSH container management works end to end, and added supporting UX: +- **Docker setup-script hint in Settings** (commit `628187b`, branch `claude/youthful-cerf-ibvxfb`, **pushed but NOT YET merged to `main`** — user explicitly deferred merging once already; revisit with the user before merging) — when editing a Docker (`type: 'docker'`) integration's `baseUrl`, Settings now renders a copyable systemd-override + `curl` verification script scoped to that exact host/port, so users don't have to hand-derive the remote-API-enablement steps themselves. +- **Help page expansion** (commit `36a79ab`, same branch, pushed) — every page entry in `src/pages/Help.tsx` now has at least one real-world example callout (icon + optional label + scenario text), plus a "New here? Start in this order" quick-start card above the grid, aimed at first-time users who don't yet know which page does what. + +### → NEXT TASK for the picking-up agent +No new feature is queued. Pick up from here: +1. **Decide with the user whether to merge `claude/youthful-cerf-ibvxfb` into `main`.** It contains the Docker setup-script hint (`628187b`) and the Help page expansion (`36a79ab`), both already build-clean (`npm run build` passes). Nothing else is blocking it. +2. **Ask the user if removing the unused Docker API integration (the one superseded by the SSH Host setup) is done** — this was a live-instance UI action on their end, not something done via this repo's code. +3. Otherwise, check with the user for the next priority — there is no pending design doc or half-built feature waiting right now (mesh gate and Docker UX work above are both fully shipped or ready-to-merge). ## Standing rules (read before doing anything) @@ -66,6 +75,9 @@ See `TERMIX_MIGRATION.md` for the phase-by-phase record of the original feature 11. **Settings UX fixes** — secret fields show a "· saved" indicator instead of appearing blank/deleted after reload (`secretKeys: string[]` on the integration serializer); SSH host cards default-collapsed if already configured; SSH private-key/cert fields support file upload to avoid paste corruption. 12. **Persistent terminal sessions** (PR #30) — SSH terminal tabs/panes stay connected when you navigate to other pages and back. See `src/lib/TerminalSessionContext.tsx`. 13. **Docker-over-SSH + agent monitoring** (PR #31) — two new ways to see/manage Docker without exposing the Engine TCP socket. See "Docker: three ways" below. +14. **Mesh Prerequisite Gate** (`46d95fc`, `0409159`, `800072f`, `4a4a5a0`) — requires a verified mesh network (universal CIDR check, not NetBird-specific, with a routed-mesh/VPC-peering fallback) before the app can be configured; defaults OFF; configurable/testable from a dedicated Settings → Mesh section. +15. **Docker integration setup-script hint** (`628187b`, on `claude/youthful-cerf-ibvxfb`, not yet merged) — Settings shows a host-specific systemd-override + curl script when configuring a Docker (`type: 'docker'`) integration's `baseUrl`, so enabling the remote Engine API doesn't require looking up the steps elsewhere. +16. **Help page expansion** (`36a79ab`, same branch) — quick-start ordering card + real-world example callouts per page, for first-time users. ## Docker: three ways (PR #31) @@ -122,6 +134,6 @@ Moved to **`ROADMAP.md`** ("Known non-blocking stubs"). Summary: the Infrastruct 1. Read this file, then `ROADMAP.md` (deferred/tiered work), then `docs/` (subsystem design docs — `docker-agent-monitoring.md`, `mesh-prerequisite-gate.md`), then `TERMIX_MIGRATION.md` for feature-level history, then skim `git log --oneline -30`. 2. Frontend: prefer `npm run build` (`tsc -b && vite build`) over a plain `tsc --noEmit` (stricter, catches more). Backend: `npx tsc --noEmit -p .` from `backend/`. Both must pass before any commit. -3. **The next planned feature is the Mesh Prerequisite Gate** — designed in `docs/mesh-prerequisite-gate.md`, NOT built. It has open decisions (A–D) that **must be answered by the user before coding** (especially DECIDE D: defaulting the gate OFF so it doesn't lock the live production instance). Auth Phases 1-3 are done; Phase 4 SSO is a deferred paid AWS add-on (`ROADMAP.md`). +3. **The Mesh Prerequisite Gate is built and shipped** (Settings → Mesh; defaults OFF). **There is no other planned feature queued right now** — check the "→ NEXT TASK" section above first (merge decision on `claude/youthful-cerf-ibvxfb`), then ask the user for the next priority. Auth Phases 1-3 are done; Phase 4 SSO is a deferred paid AWS add-on (`ROADMAP.md`). 4. If asked to add a feature, follow existing patterns: integration adapters in `backend/src/integrations/`, SSH-backed engines in `backend/src/ssh/`, one route file per feature in `backend/src/routes/`, one `api.ts` entry + page component per frontend feature. Subsystem-level work gets a `docs/` design doc first. 5. For anything ambiguous in scope, use `AskUserQuestion` rather than guessing — that's how the auth phases, the Docker agent tiering, and the mesh-gate decisions were all scoped. diff --git a/README.md b/README.md index 4b842fb..27cb045 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,16 @@ backend routes are built and working — there is no pending/on-hold page. Auth is feature-complete for self-hosted (Phases 1-3: user menu wiring, password/sessions/login-log, multi-user roles with a 10-seat cap); Phase 4 (Authentik SSO) is **deferred to a paid AWS add-on** — see `ROADMAP.md`. -Recently shipped: persistent terminal sessions across navigation, and Docker +Recently shipped: persistent terminal sessions across navigation, Docker container visibility/management three ways (Engine TCP API, `docker` CLI over -SSH, and a read-only push agent — see `docs/docker-agent-monitoring.md`). +SSH, and a read-only push agent — see `docs/docker-agent-monitoring.md`), and +the **Mesh Prerequisite Gate** — a universal CIDR-based mesh-verification +requirement (with a routed-mesh/VPC-peering fallback, not NetBird-specific), +configurable from Settings → Mesh and defaulting OFF so it can't lock the live +instance. -The **next planned feature is the Mesh Prerequisite Gate** — requiring a -verified NetBird mesh before the app can be configured. It is **designed but -not built** (`docs/mesh-prerequisite-gate.md`) and has open decisions that need -the user's sign-off before coding (notably defaulting it OFF so it can't lock -the live instance). See `HANDOFF.md` for where to resume. +There is no feature currently in progress. See `HANDOFF.md` for the latest +status and next steps. If you're a fresh AI session: read this file, then `HANDOFF.md` (current task state + standing workflow rules), then `design-decisions.md` (visual diff --git a/src/pages/Help.tsx b/src/pages/Help.tsx index d5cf4dc..9d72b0a 100644 --- a/src/pages/Help.tsx +++ b/src/pages/Help.tsx @@ -10,6 +10,11 @@ import { Gauge, Settings, Search, + Lightbulb, + ArrowRightLeft, + ArrowLeftRight, + Shuffle, + Rocket, type LucideIcon, } from 'lucide-react' @@ -25,6 +30,7 @@ interface GuideEntry { title: string description: string tips?: string[] + examples?: { icon?: LucideIcon; label?: string; text: string }[] } const guideEntries: GuideEntry[] = [ @@ -34,6 +40,9 @@ const guideEntries: GuideEntry[] = [ description: 'The home dashboard. Shows overall system health, a rollup of connected integrations, recent activity, and shortcuts into the rest of the app.', tips: ['Click "Connected Integrations" entries to jump straight to Infrastructure.'], + examples: [ + { text: 'First thing in the morning: open Glance to see if anything went offline overnight before digging into any one page.' }, + ], }, { icon: Server, @@ -41,6 +50,9 @@ const guideEntries: GuideEntry[] = [ description: 'Lists every connected integration (Proxmox, AWS, Docker, NetBird, Cloudflare, Uptime Kuma, Weather, SSH hosts) and the live resources/health each one reports.', tips: ['Add new integrations from Settings → Integrations — they show up here automatically.'], + examples: [ + { text: 'Connect Proxmox and AWS, then check VM and EC2 health side by side without opening either provider\'s own dashboard.' }, + ], }, { icon: Bookmark, @@ -50,61 +62,139 @@ const guideEntries: GuideEntry[] = [ 'Icons are auto-detected from the title or URL (e.g. typing "Proxmox" picks up the real Proxmox logo) — pick "Choose manually" if it guesses wrong.', 'Star a bookmark to pin it to the Favorites panel.', ], + examples: [ + { text: 'Organize your router admin page, NAS UI, and internal wiki into "Network", "Storage", and "Docs" categories so new teammates can find them without asking.' }, + ], }, { icon: Terminal, title: 'Terminal', description: 'A full SSH terminal to any host you\'ve added as an integration — supports tabs, split panes, jump hosts, and certificate auth.', tips: ['Session output can be logged; theme and font preferences are remembered between visits.'], + examples: [ + { text: 'SSH into a public-facing jump host, then open a second tab routed through it to reach a private VM that has no public IP of its own.' }, + ], }, { icon: Waypoints, title: 'Tunnels', - description: 'Local, remote, and dynamic (SOCKS5) SSH tunnels. Tunnels can be set to auto-start whenever the backend boots.', + description: + 'Local, remote, and dynamic (SOCKS5) SSH tunnels, each riding on top of an existing SSH Host integration. Tunnels can be set to auto-start whenever the backend boots, and the app keeps retrying if a connection drops.', + tips: ['Pick an SSH host from Settings → Integrations → SSH Hosts first — tunnels are created on top of one.'], + examples: [ + { + icon: ArrowRightLeft, + label: 'Local Forward', + text: 'A database on a remote server only listens on its own localhost. Forward remote 5432 to your local 5432, and connect a DB client as if it were running on your machine.', + }, + { + icon: ArrowLeftRight, + label: 'Remote Forward', + text: 'You\'re running something on your laptop (e.g. a dev server on :3000) and need a remote server to reach it temporarily — remote forward exposes your local port on the remote side.', + }, + { + icon: Shuffle, + label: 'Dynamic (SOCKS5)', + text: 'Point your browser\'s proxy settings at the SOCKS5 port and browse as if you were sitting inside the remote network — useful for reaching a whole subnet of internal services through one SSH host, instead of forwarding each port individually.', + }, + ], }, { icon: FolderOpen, title: 'Files', description: 'Browse, edit, upload, and download files over SFTP on any connected SSH host — and transfer files directly between two hosts without round-tripping through your machine.', tips: ['Use the "Send to another host" action on a file row to start a host-to-host transfer; progress shows live in the panel at the bottom.'], + examples: [ + { text: 'Move a large backup folder from one VPS straight to another, without downloading gigabytes to your laptop first just to re-upload them.' }, + ], }, { icon: Box, title: 'Containers', - description: 'Manage Docker containers on remote hosts — start, stop, view logs, and exec into a running container.', + description: 'Manage Docker containers on remote hosts — start, stop, view logs, and exec into a running container. Containers can come from a direct Docker API connection, an SSH host (runs the `docker` CLI for you), or a lightweight monitoring agent.', + tips: ['No Docker API port to expose? Add the host as an SSH Host integration instead — containers show up the same way, just routed through SSH.'], + examples: [ + { text: 'A container crashed on a VM you don\'t want to manually SSH into — pick the host on the Containers page, hit restart, and watch the new logs without leaving the browser.' }, + ], }, { icon: MonitorSmartphone, title: 'Remote Desktop', description: 'RDP, VNC, and Telnet sessions to remote machines, streamed through the built-in Guacamole proxy — no separate client needed.', + examples: [ + { text: 'Open a VNC session into a headless Ubuntu box to fix a stuck GUI app, entirely inside the browser tab — no VNC viewer install required.' }, + ], }, { icon: Gauge, title: 'Host Metrics', description: 'Live CPU, memory, disk, network, listening-port, firewall, process, and login-activity widgets for any SSH-managed host.', + examples: [ + { text: 'Catch a host slowly running out of disk space days before it takes a service down with it.' }, + ], }, { icon: Settings, title: 'Settings', description: 'Your profile, integrations, appearance, notifications, and full data export/import (back up or migrate every integration, bookmark, and tunnel as a single JSON file).', + examples: [ + { text: 'Export everything before reinstalling the OS on the box ArchNest itself runs on, then import it on the fresh install to get back to where you left off.' }, + ], }, { icon: Search, title: 'Search (top bar)', description: 'The search box at the top of every page looks across pages, integrations, and bookmarks at once — press Enter to jump to the top match.', + examples: [ + { text: 'Type "proxmox" and hit Enter to jump straight to your Proxmox integration, instead of clicking through to Infrastructure first.' }, + ], }, ] +const quickStartSteps = [ + 'Add at least one integration in Settings → Integrations — an SSH Host is the easiest first connection, since Terminal, Files, Tunnels, Host Metrics, and Containers can all use it.', + 'Open Terminal or Files to confirm the connection actually works end to end.', + 'If you need to reach something deeper in a private network (a DB, an internal site, a whole subnet), set up a Tunnel for it instead of opening more ports.', + 'Bookmark the dashboards and tools you check often in BookNest, so the next visit is one click instead of a search.', +] + export default function Help() { return (
-
+

How ArchNest works

- A quick tour of every page and what it's for. Use the sidebar to navigate, or the search bar at the top to jump straight to something. + A quick tour of every page and what it's for, with real examples for each. Use the sidebar to navigate, or the + search bar at the top to jump straight to something.

+
+
+
+ +
+

New here? Start in this order

+
+
    + {quickStartSteps.map((step, i) => ( +
  1. + {step} +
  2. + ))} +
+
+
{guideEntries.map((entry) => { const Icon = entry.icon @@ -136,6 +226,31 @@ export default function Help() { ))} )} + {entry.examples && entry.examples.length > 0 && ( +
+ {entry.examples.map((ex, i) => { + const ExIcon = ex.icon ?? Lightbulb + return ( +
+ +

+ {ex.label && {ex.label}: } + {ex.text} +

+
+ ) + })} +
+ )}
) })} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 5a4130a..c77e0d7 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -784,6 +784,77 @@ function SshHostsSection() { type NewIntegrationDraft = { id: number; type: string; values: Record } +function dockerHostInfo(baseUrl: string): { host: string; port: string } | null { + if (!baseUrl || baseUrl.startsWith('unix://')) return null + try { + const u = new URL(baseUrl.replace(/^tcp:\/\//, 'http://')) + if (u.protocol !== 'http:' && u.protocol !== 'https:') return null + if (!u.hostname) return null + return { host: u.hostname, port: u.port || '2375' } + } catch { + return null + } +} + +function DockerSetupHint({ baseUrl }: { baseUrl: string }) { + const [copied, setCopied] = useState(false) + const info = dockerHostInfo(baseUrl) + if (!info) return null + + const script = `sudo mkdir -p /etc/systemd/system/docker.service.d +sudo tee /etc/systemd/system/docker.service.d/override.conf > /dev/null < +
+ + Run this on {info.host} to expose its Docker API on port {info.port}: + + +
+
+        {script}
+      
+
+ ) +} + function IntegrationsSection() { const { user } = useAuth() const isAdmin = user?.role === 'admin' @@ -1051,6 +1122,9 @@ function IntegrationsSection() { '••••••••••••', existing, )} + {def.type === 'docker' && ( + + )}
) })} @@ -1100,6 +1174,7 @@ function IntegrationsSection() { (f, value) => setNewDraftField(draft.id, f.key, value), '', )} + {def.type === 'docker' && } ) })}