System design, CloudFormation, theming assets #3

Merged
sam merged 1 commit from kiro/system-design-and-cleanup into main 2026-06-24 13:55:06 +00:00
19 changed files with 1065 additions and 245 deletions

View file

@ -1,14 +1,5 @@
{
"mcpServers": {
"aws-docs": {
"command": "uvx",
"args": ["awslabs.aws-documentation-mcp-server@latest"],
"env": {
"FASTMCP_LOG_LEVEL": "ERROR"
},
"disabled": false,
"autoApprove": []
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"],

292
README.md
View file

@ -1,256 +1,94 @@
# 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.
A multi-tenant SaaS platform for infrastructure management — SSH terminal,
Docker management, remote desktop, host metrics, file management, and 9
real integration adapters from a single browser interface. Developer-first
alternative to enterprise RMM tools, starting at $2.50/month.
**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.
## Pricing
## What this is, in one paragraph
| | Starter | Pro | Team |
|---|---|---|---|
| Monthly | $2.50/mo | $4.25/mo | $12/mo |
| Annual | $25/yr | $45/yr | $95/yr |
| Hosts | 50 | 125 | Unlimited |
| Users | 5 | 50 | 200 |
| Remote Desktop | — | ✓ | ✓ |
| SSO | — | — | ✓ |
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.
## Features
## Current state & direction
**SSH Suite** — Terminal (multi-tab, split panes, persistent sessions), tunnels
(local/remote/SOCKS5), SFTP file manager, host-to-host transfer, host metrics
(5s polling), jump-host chaining, tmux, certificate auth (OPKSSH).
**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.
**Docker** — Management via TCP API, CLI over SSH, or push agent. Container
actions, logs, interactive exec, detail views.
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, 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`), 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.
**Remote Desktop** — RDP/VNC/Telnet via Guacamole (Pro+).
There is no feature currently in progress. See `HANDOFF.md` for the latest
status and next steps.
**Integrations** — Proxmox, Docker, AWS, Cloudflare, NetBird, Uptime Kuma,
Weather, SSH, Remote Desktop. All real, no mocks.
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/tiered work) and the `docs/` design docs (`docker-agent-monitoring.md`,
`mesh-prerequisite-gate.md`), then `TERMIX_MIGRATION.md`
(history of how the SSH/Docker/Guacamole feature set was built) if you need
that context.
**Bookmarks** — Categorized hub with favorites, link health, full CRUD.
## Pages
**Auth** — Cognito (OIDC/SAML SSO for Team), MFA, multi-user roles, audit log.
| 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); **sessions stay connected across page navigation** |
| 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 containers across **three sources** (Engine TCP API, `docker` CLI over SSH, or a read-only push agent) — list/start/stop/restart/pause/remove, logs, interactive exec; tabbed with a clickable per-container detail view |
| 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.
**4 Themes** — ArchNest Dark, Midnight Blue, Forest, Light.
## 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` (JWT
carrying a server-tracked session id; signing out revokes the session
server-side).
- `src/lib/TerminalSessionContext.tsx` — keeps SSH terminal sessions
(xterm + WebSocket + DOM node) alive above the router so they survive
in-app navigation; shared constants in `src/lib/terminalPrefs.ts`.
- `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).
Hybrid: Akamai Cloud for compute, AWS for managed services.
### 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,
plus `sessions`/`login_events` tables and a multi-user `users` schema
(`role` admin/member + `active` columns).
- `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, password change, sessions,
login audit log, and admin-only user management (`/api/setup`,
`/api/auth/*`, `/api/users`)
- `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 Engine TCP API: container list/stats/logs/actions + exec WebSocket
- `dockerSsh.ts` — Docker over SSH: runs the `docker` CLI on a remote SSH host (list/logs/actions + exec WebSocket); no dockerd socket exposed
- `agents.ts` — Docker monitoring agents: token-gated push ingest (`POST /api/agents/docker/report`) + read-only host/container views
- `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
- `docker.ts` — runs the `docker` CLI over SSH for the Containers page's
"Docker over SSH" source (list/logs/actions + interactive exec)
- `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),
`ARCHNEST_AGENT_TOKEN` (shared token enabling the Docker monitoring-agent
ingest endpoint — ingest is disabled / returns 503 when unset),
`ARCHNEST_AGENT_STALE_MS` (default 90000; when an agent report is shown stale).
- `backend/src/docker/` — Docker Engine TCP API client used by `docker.ts`.
- `agent/` — the standalone Docker monitoring agent (`archnest-docker-agent.sh`
+ install/README). Runs on each Docker VM and pushes reports to ArchNest.
| Layer | Provider | Service |
|-------|----------|---------|
| Compute | Akamai | G7 Dedicated (4GB, ARM) |
| Load Balancer | Akamai | NodeBalancer |
| Frontend | Akamai | Object Storage |
| Database | Self-managed | PostgreSQL (RLS) |
| Cache | Self-managed | Redis |
| Auth | AWS | Cognito |
| Secrets | AWS | Secrets Manager |
| Storage | AWS | S3 |
| DNS | AWS | Route 53 |
| Email | AWS | SES |
## Development
**Infrastructure cost:** ~$66.50/month at 50 users. Scales to full AWS
(Fargate + Aurora) at 100+ users / $500+ MRR.
Frontend:
```bash
npm install
npm run dev
```
Backend:
```bash
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:
```bash
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.
See [`docs/aws-architecture/system-design.md`](docs/aws-architecture/system-design.md)
for the full system design with diagrams, cost analysis, tier enforcement,
and scale-up path.
## 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)
**Frontend**: React 19, Vite 8, TypeScript, Tailwind CSS v4, React Router,
Recharts, Lucide React, xterm.js
**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
**Backend**: Fastify 5, TypeScript, PostgreSQL, Redis, zod, ssh2
**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.
**Auth**: AWS Cognito (OIDC/SAML SSO, MFA, PKCE)
**Deploy target:** Docker on `racknerd1` → Nginx Proxy Manager at
`archnest.snsnetlabs.com`.
**CI/CD**: Forgejo Actions → Docker → Akamai VM deploy
## Deployment
## Development
**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.
```bash
npm install && npm run dev # frontend
cd backend && npm install && npm run dev # backend
```
`docker-compose.yml` runs 3 services: `archnest` (frontend), `archnest-backend`,
and `guacd` (remote desktop sidecar).
Type-check before committing:
```bash
npm run build # frontend
cd backend && npx tsc --noEmit # backend
```
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)`.
## Documentation
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.
| File | Content |
|------|---------|
| [`docs/aws-architecture/system-design.md`](docs/aws-architecture/system-design.md) | Full architecture, costs, tier enforcement |
| [`design-decisions.md`](design-decisions.md) | Visual conventions + per-page notes |
| [`HANDOFF.md`](HANDOFF.md) | Current state, workflow rules |
| [`ROADMAP.md`](ROADMAP.md) | Deferred/tiered work |

View file

@ -28,10 +28,10 @@
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.12",
"@types/node": "^22.10.5",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.20.0",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
"typescript": "^5.9.3"
}
},
"node_modules/@aws-crypto/crc32": {
@ -1259,9 +1259,9 @@
}
},
"node_modules/@types/node": {
"version": "22.19.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz",
"integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==",
"version": "22.20.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz",
"integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==",
"dev": true,
"license": "MIT",
"dependencies": {

View file

@ -29,9 +29,9 @@
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.12",
"@types/node": "^22.10.5",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.20.0",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
"typescript": "^5.9.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

View file

@ -0,0 +1,73 @@
from diagrams import Diagram, Cluster, Edge
from diagrams.aws.security import Cognito, SecretsManager
from diagrams.aws.storage import S3
from diagrams.aws.network import Route53
from diagrams.aws.compute import Lambda
from diagrams.aws.engagement import SES
from diagrams.onprem.container import Docker
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.inmemory import Redis
from diagrams.onprem.network import Nginx
from diagrams.onprem.client import User
from diagrams.generic.storage import Storage
with Diagram("ArchNest SaaS - Hybrid Architecture", show=False, filename="/tmp/archnest-hybrid", direction="TB", outformat="png"):
users = User("Tenants")
with Cluster("Akamai Cloud"):
lb = Nginx("NodeBalancer\nHTTPS/WSS")
with Cluster("G7 Dedicated (4GB, 2 vCPU, ARM)"):
backend = Server("Fastify\nBackend API")
websocket = Server("Fastify\nWebSocket Service")
guacd = Docker("guacd\n(RDP/VNC)")
with Cluster("Data (Self-Managed)"):
postgres = PostgreSQL("PostgreSQL\n(RLS Enabled)")
redis = Redis("Redis\n(Sessions/Cache)")
static = Storage("Object Storage\n(React SPA)")
with Cluster("AWS (Managed Services Only)"):
cognito = Cognito("Cognito\nUser Pools + SSO")
pre_token = Lambda("Pre-Token\nLambda")
secrets = SecretsManager("Secrets Manager\nSSH Keys")
s3 = S3("S3\nBackups + Logs")
route53 = Route53("Route 53")
ses = SES("SES\nEmail")
stripe_lambda = Lambda("Stripe\nWebhook Lambda")
with Cluster("Tenant Infrastructure"):
host1 = Server("SSH Host A")
host2 = Server("SSH Host B")
docker_host = Docker("Docker Host")
# User flow
users >> route53 >> lb
lb >> static
lb >> backend
lb >> websocket
# Backend connections
backend >> postgres
backend >> redis
backend >> secrets
backend >> s3
websocket >> redis
websocket >> guacd
# Auth
cognito >> pre_token
backend >> cognito
stripe_lambda >> cognito
# Outbound to tenant hosts (direct, no NAT needed)
backend >> host1
backend >> host2
websocket >> host1
websocket >> docker_host
# Email
backend >> ses

View file

@ -0,0 +1,419 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ArchNest — Product Design Review</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #0D0E10; color: #E8E6E0; line-height: 1.6; }
.container { max-width: 1400px; margin: 0 auto; padding: 40px 60px 120px; }
h1 { font-size: 32px; color: #C8A434; font-weight: 700; margin-bottom: 8px; letter-spacing: 1px; text-transform: uppercase; }
h2 { font-size: 22px; color: #C8A434; font-weight: 600; margin: 48px 0 16px; padding-bottom: 8px; border-bottom: 1px solid #1E2025; }
h3 { font-size: 16px; color: #E8E6E0; font-weight: 600; margin: 24px 0 12px; }
p { margin: 12px 0; color: #E8E6E0; font-size: 14px; }
.subtitle { color: #7A7D85; font-size: 14px; margin-bottom: 32px; }
.card { background: #141518; border: 1px solid #1E2025; border-radius: 12px; padding: 24px; margin: 16px 0; }
.card:hover { border-color: #C8A434; transition: border-color 0.2s ease; }
.card-title { font-size: 11px; text-transform: uppercase; letter-spacing: 1.5px; color: #7A7D85; margin-bottom: 12px; font-weight: 500; }
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
th { background: #141518; color: #C8A434; text-align: left; padding: 12px 16px; border: 1px solid #1E2025; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }
td { padding: 10px 16px; border: 1px solid #1E2025; color: #E8E6E0; }
tr:hover td { background: #1a1b1f; }
code { background: #1a1b1f; color: #C8A434; padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'JetBrains Mono', monospace; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 16px 0; }
.badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
.badge-green { background: rgba(46,204,113,0.15); color: #2ECC71; }
.badge-gold { background: rgba(200,164,52,0.15); color: #C8A434; }
.badge-blue { background: rgba(59,130,246,0.15); color: #3B82F6; }
.mermaid { background: #141518; border-radius: 12px; padding: 24px; margin: 24px 0; border: 1px solid #1E2025; }
.feature-list { list-style: none; padding: 0; }
.feature-list li { padding: 8px 0; border-bottom: 1px solid #1E2025; font-size: 14px; }
.feature-list li:last-child { border-bottom: none; }
.feature-list li::before { content: "\2192"; color: #C8A434; margin-right: 10px; }
.section-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
@media (max-width: 900px) { .section-grid { grid-template-columns: 1fr; } }
.cost-total { font-size: 28px; font-weight: 700; color: #C8A434; }
.module-price { font-size: 20px; font-weight: 700; color: #2ECC71; }
.theme-swatch { display: inline-block; width: 24px; height: 24px; border-radius: 6px; margin-right: 6px; vertical-align: middle; border: 1px solid #1E2025; }
.approval-bar { position: sticky; bottom: 0; background: #141518; border-top: 1px solid #C8A434; padding: 16px 60px; display: flex; justify-content: space-between; align-items: center; z-index: 100; }
.btn { padding: 10px 24px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; }
.btn-approve { background: #C8A434; color: #0D0E10; }
.btn-approve:hover { background: #dab944; }
.btn-reject { background: transparent; color: #E74C3C; border: 1px solid #E74C3C; }
.btn-reject:hover { background: rgba(231,76,60,0.1); }
.hero { background: linear-gradient(135deg, #141518 0%, #0D0E10 50%, #1a1510 100%); border-radius: 16px; padding: 48px; margin-bottom: 32px; border: 1px solid #1E2025; }
.lock-icon { color: #7A7D85; margin-right: 6px; }
.free-badge { background: rgba(46,204,113,0.15); color: #2ECC71; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600; margin-left: 8px; }
.paid-badge { background: rgba(200,164,52,0.15); color: #C8A434; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600; margin-left: 8px; }
</style>
</head>
<body>
<div class="container">
<div class="hero">
<h1>ArchNest</h1>
<p class="subtitle">Self-Hosted Product Design — Open Core + Paid Modules</p>
<p>Free self-hosted ops dashboard. Unlock features with $5 one-time module purchases. Own it forever. No subscriptions.</p>
<div style="margin-top: 16px;">
<span class="badge badge-green">Free Core</span>
<span class="badge badge-gold">$5/Module</span>
<span class="badge badge-blue">Self-Hosted</span>
</div>
</div>
<h2>Business Model</h2>
<div class="section-grid">
<div class="card">
<div class="card-title">How It Works</div>
<ul class="feature-list">
<li>Free core — genuinely useful self-hosted dashboard</li>
<li>$5 one-time purchase per module (30 modules available)</li>
<li>Bundles at discount ($10-$99)</li>
<li>Free core updates forever</li>
<li>Customer owns it — no vendor lock-in</li>
</ul>
</div>
<div class="card">
<div class="card-title">Your Economics</div>
<ul class="feature-list">
<li>Infrastructure cost: ~$1/month (license server)</li>
<li>Profit margin: 95%+ per sale</li>
<li>Zero churn (one-time, not subscription)</li>
<li>Zero hosting cost per customer</li>
<li>Net per $5 module (after Stripe): $4.55</li>
</ul>
</div>
</div>
<h2>Free Core</h2>
<div class="card">
<div class="card-title">Ships Free — No Purchase Required</div>
<table>
<tr><th>Feature</th><th>Free Limit</th></tr>
<tr><td>Dashboard (Glance)</td><td>Full</td></tr>
<tr><td>Infrastructure Overview</td><td>Full</td></tr>
<tr><td>SSH Terminal</td><td>1 tab, 1 pane</td></tr>
<tr><td>SSH Tunnels</td><td>Manual start only</td></tr>
<tr><td>SFTP File Manager</td><td>Full</td></tr>
<tr><td>Docker Management</td><td>TCP API only, 1 source</td></tr>
<tr><td>Host Metrics</td><td>Basic (CPU/memory/disk)</td></tr>
<tr><td>Bookmarks</td><td>10 max</td></tr>
<tr><td>SSH Hosts</td><td>3 max</td></tr>
<tr><td>Users</td><td>1 (admin only)</td></tr>
<tr><td>Theme</td><td>ArchNest Dark only</td></tr>
<tr><td>Help Page</td><td>Full</td></tr>
</table>
</div>
<h2>Paid Modules — $5 Each</h2>
<h3>SSH Modules (8)</h3>
<div class="grid">
<div class="card">
<div class="card-title">1. Multi-Pane Terminal <span class="paid-badge">$5</span></div>
<p>Split panes (2/4), multiple tabs</p>
</div>
<div class="card">
<div class="card-title">2. tmux Integration <span class="paid-badge">$5</span></div>
<p>Attach to existing tmux sessions</p>
</div>
<div class="card">
<div class="card-title">3. Jump-Host Chaining <span class="paid-badge">$5</span></div>
<p>Connect through intermediary hosts</p>
</div>
<div class="card">
<div class="card-title">4. Certificate Auth <span class="paid-badge">$5</span></div>
<p>OPKSSH certificate-based SSH auth</p>
</div>
<div class="card">
<div class="card-title">5. Tunnel Auto-Start <span class="paid-badge">$5</span></div>
<p>Tunnels start automatically on boot</p>
</div>
<div class="card">
<div class="card-title">6. Persistent Sessions <span class="paid-badge">$5</span></div>
<p>Terminal sessions survive navigation</p>
</div>
<div class="card">
<div class="card-title">7. Session Recording <span class="paid-badge">$5</span></div>
<p>Record terminal sessions to disk</p>
</div>
<div class="card">
<div class="card-title">8. Host-to-Host Transfer <span class="paid-badge">$5</span></div>
<p>Copy/move files between SSH hosts</p>
</div>
</div>
<h3>Docker Modules (4)</h3>
<div class="grid">
<div class="card">
<div class="card-title">9. Docker over SSH <span class="paid-badge">$5</span></div>
<p>Manage containers via CLI over SSH</p>
</div>
<div class="card">
<div class="card-title">10. Docker Push Agent <span class="paid-badge">$5</span></div>
<p>Outbound-only monitoring agent</p>
</div>
<div class="card">
<div class="card-title">11. Container Exec <span class="paid-badge">$5</span></div>
<p>Interactive shell into containers</p>
</div>
<div class="card">
<div class="card-title">12. Container Details <span class="paid-badge">$5</span></div>
<p>Full inspect: ports, networks, env, mounts</p>
</div>
</div>
<h3>Integration Modules (6)</h3>
<div class="grid">
<div class="card">
<div class="card-title">13. Unlimited SSH Hosts <span class="paid-badge">$5</span></div>
<p>Remove 3-host cap</p>
</div>
<div class="card">
<div class="card-title">14. Proxmox <span class="paid-badge">$5</span></div>
<p>VM/LXC management</p>
</div>
<div class="card">
<div class="card-title">15. AWS <span class="paid-badge">$5</span></div>
<p>EC2 + STS resource inventory</p>
</div>
<div class="card">
<div class="card-title">16. Cloudflare <span class="paid-badge">$5</span></div>
<p>DNS zones, resource listing</p>
</div>
<div class="card">
<div class="card-title">17. NetBird <span class="paid-badge">$5</span></div>
<p>Mesh peers, connectivity</p>
</div>
<div class="card">
<div class="card-title">18. Uptime Kuma <span class="paid-badge">$5</span></div>
<p>Monitor status/health</p>
</div>
</div>
<h3>Desktop & Theme Modules (6)</h3>
<div class="grid">
<div class="card">
<div class="card-title">19. Remote Desktop: RDP <span class="paid-badge">$5</span></div>
<p>Windows RDP via Guacamole</p>
</div>
<div class="card">
<div class="card-title">20. Remote Desktop: VNC <span class="paid-badge">$5</span></div>
<p>VNC sessions via Guacamole</p>
</div>
<div class="card">
<div class="card-title">21. Remote Desktop: Telnet <span class="paid-badge">$5</span></div>
<p>Telnet sessions via Guacamole</p>
</div>
<div class="card">
<div class="card-title">22. Theme: Midnight Blue <span class="paid-badge">$5</span></div>
<div style="margin:4px 0;"><span class="theme-swatch" style="background:#0B0F1A;"></span><span class="theme-swatch" style="background:#3B82F6;"></span></div>
</div>
<div class="card">
<div class="card-title">23. Theme: Forest <span class="paid-badge">$5</span></div>
<div style="margin:4px 0;"><span class="theme-swatch" style="background:#0A120E;"></span><span class="theme-swatch" style="background:#10B981;"></span></div>
</div>
<div class="card">
<div class="card-title">24. Theme: Light <span class="paid-badge">$5</span></div>
<div style="margin:4px 0;"><span class="theme-swatch" style="background:#F5F5F5;"></span><span class="theme-swatch" style="background:#C8A434;"></span></div>
</div>
</div>
<h3>Platform Modules (6)</h3>
<div class="grid">
<div class="card">
<div class="card-title">25. Multi-User <span class="paid-badge">$5</span></div>
<p>Admin/member roles, up to 10 seats</p>
</div>
<div class="card">
<div class="card-title">26. Advanced Metrics <span class="paid-badge">$5</span></div>
<p>Network, processes, ports, firewall, login stats</p>
</div>
<div class="card">
<div class="card-title">27. Data Export/Import <span class="paid-badge">$5</span></div>
<p>Backup/restore full config as JSON</p>
</div>
<div class="card">
<div class="card-title">28. Audit Log <span class="paid-badge">$5</span></div>
<p>Full activity log with export</p>
</div>
<div class="card">
<div class="card-title">29. Unlimited Bookmarks <span class="paid-badge">$5</span></div>
<p>Remove 10-bookmark cap</p>
</div>
<div class="card">
<div class="card-title">30. Global Search <span class="paid-badge">$5</span></div>
<p>Search pages, integrations, bookmarks</p>
</div>
</div>
<h2>Bundles</h2>
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
<div class="card" style="text-align:center;">
<div class="card-title">SSH Pro</div>
<p class="module-price">$25</p>
<p style="color:#7A7D85;font-size:12px;">All 8 SSH modules (save $15)</p>
</div>
<div class="card" style="text-align:center;">
<div class="card-title">Docker Pro</div>
<p class="module-price">$15</p>
<p style="color:#7A7D85;font-size:12px;">All 4 Docker modules (save $5)</p>
</div>
<div class="card" style="text-align:center;">
<div class="card-title">Remote Desktop</div>
<p class="module-price">$10</p>
<p style="color:#7A7D85;font-size:12px;">RDP + VNC + Telnet (save $5)</p>
</div>
<div class="card" style="text-align:center;">
<div class="card-title">All Themes</div>
<p class="module-price">$10</p>
<p style="color:#7A7D85;font-size:12px;">3 extra themes (save $5)</p>
</div>
<div class="card" style="text-align:center;border-color:#C8A434;">
<div class="card-title" style="color:#C8A434;">Everything</div>
<p class="cost-total">$99</p>
<p style="color:#7A7D85;font-size:12px;">All 30 modules forever (save $51)</p>
</div>
</div>
<h2>License System</h2>
<div class="mermaid">
graph LR
BOOT[ArchNest Boot] --> CHECK[License Check<br/>HTTPS to license server]
CHECK --> RESP[Signed Response<br/>modules + valid_until]
RESP --> VALIDATE[Validate Ed25519<br/>signature locally]
VALIDATE --> UNLOCK[Unlock purchased<br/>modules]
UNLOCK --> WEEKLY[Re-check weekly]
WEEKLY --> CHECK
</div>
<div class="section-grid">
<div class="card">
<div class="card-title">How It Works</div>
<ul class="feature-list">
<li>Phone-home on boot + once weekly</li>
<li>Returns signed JSON: modules[] + valid_until (7 days)</li>
<li>Ed25519 signature validated locally (public key in code)</li>
<li>Works offline for 7 days between checks</li>
<li>After 7 days offline → falls back to free core</li>
</ul>
</div>
<div class="card">
<div class="card-title">License Server Stack</div>
<ul class="feature-list">
<li>Cloudflare Workers (free tier: 100K req/day)</li>
<li>Cloudflare D1 database (free tier: 5GB)</li>
<li>Stripe for payments</li>
<li>Total cost: ~$1/month + Stripe fees</li>
<li>Net per module sale: $4.55 (after Stripe)</li>
</ul>
</div>
</div>
<h2>Purchase Flow</h2>
<div class="mermaid">
graph LR
BROWSE[Browse Module Store<br/>in Settings] --> BUY[Click Buy → $5]
BUY --> STRIPE[Stripe Checkout]
STRIPE --> WEBHOOK[Webhook → License Server]
WEBHOOK --> RECORD[Record purchase<br/>in D1 database]
RECORD --> POLL[Next license check<br/>returns new module]
POLL --> ACTIVE[Feature unlocks]
</div>
<h2>Revenue Projections</h2>
<div class="card">
<table>
<tr><th>Stage</th><th>Installs/mo</th><th>Avg Modules Bought</th><th>Revenue/mo</th></tr>
<tr><td>Early (month 1-3)</td><td>50</td><td>3 modules ($15)</td><td>$750</td></tr>
<tr><td>Growth (month 4-6)</td><td>200</td><td>4 modules ($20)</td><td>$4,000</td></tr>
<tr><td>Steady (month 7-12)</td><td>500</td><td>5 modules ($25)</td><td>$12,500</td></tr>
<tr><td>Mature (year 2)</td><td>1,000</td><td>$30 avg (bundles)</td><td>$30,000</td></tr>
</table>
<p style="margin-top:16px;color:#7A7D85;">Infrastructure cost stays at ~$1/month regardless of scale. 95%+ margin at all stages.</p>
</div>
<h2>What Changes From Current Code</h2>
<div class="card">
<table>
<tr><th>Area</th><th>Current</th><th>New</th></tr>
<tr><td>Database</td><td>SQLite</td><td>SQLite (stays)</td></tr>
<tr><td>Auth</td><td>Local JWT</td><td>Local JWT (stays)</td></tr>
<tr><td>Multi-tenant</td><td>N/A</td><td>Not needed (single-tenant per install)</td></tr>
<tr><td>License</td><td>None</td><td>Weekly phone-home + signature validation</td></tr>
<tr><td>Module gating</td><td>None</td><td>Fastify middleware + frontend lock UI</td></tr>
<tr><td>Settings</td><td>Current tabs</td><td>+ "Module Store" tab</td></tr>
<tr><td>Stripe</td><td>None</td><td>Checkout for purchases</td></tr>
</table>
<p style="margin-top:16px;"><strong>Key insight:</strong> Almost no infrastructure changes. You're adding a license layer and a store UI — not rewriting anything.</p>
</div>
<h2>Implementation Phases</h2>
<div class="grid">
<div class="card">
<div class="card-title">Phase 1 — License Infrastructure</div>
<ul class="feature-list">
<li>Build license server (CF Workers + D1)</li>
<li>Add license check to backend</li>
<li>Add module enforcement middleware</li>
<li>Add "Module Store" tab in Settings</li>
</ul>
</div>
<div class="card">
<div class="card-title">Phase 2 — Module Gating</div>
<ul class="feature-list">
<li>Define module boundaries in routes</li>
<li>Add lock UI to gated features</li>
<li>Free tier caps (3 hosts, 1 pane, 10 bookmarks)</li>
</ul>
</div>
<div class="card">
<div class="card-title">Phase 3 — Purchase Flow</div>
<ul class="feature-list">
<li>Stripe Checkout integration</li>
<li>Module activation on webhook</li>
<li>Bundle discounts</li>
<li>Purchase history in Settings</li>
</ul>
</div>
<div class="card">
<div class="card-title">Phase 4 — Distribution</div>
<ul class="feature-list">
<li>Public Docker image</li>
<li>Landing page + module catalog</li>
<li>Installation docs</li>
<li>Demo instance</li>
</ul>
</div>
</div>
<h2>Open Decisions</h2>
<table>
<tr><th>#</th><th>Question</th><th>Options</th></tr>
<tr><td>1</td><td>Source code visibility</td><td>Open-source (MIT) vs source-available (BSL) vs proprietary</td></tr>
<tr><td>2</td><td>Distribution</td><td>Docker Hub vs GitHub Container Registry</td></tr>
<tr><td>3</td><td>Landing page</td><td>Cloudflare Pages vs separate repo</td></tr>
<tr><td>4</td><td>Refund policy</td><td>30-day vs no refunds ($5 is low)</td></tr>
<tr><td>5</td><td>Module store UX</td><td>In-app tab vs external website</td></tr>
<tr><td>6</td><td>License transfer</td><td>Unlimited vs 1/year</td></tr>
</table>
</div>
<div class="approval-bar">
<div>
<strong style="color: #C8A434;">Product Design Review</strong>
<span style="color: #7A7D85; margin-left: 12px;">ArchNest — Self-Hosted + $5 Modules</span>
</div>
<div>
<button class="btn btn-reject" onclick="alert('Tell Kiro what to change.')">Request Changes</button>
<button class="btn btn-approve" style="margin-left: 12px;" onclick="alert('Approved! Ready to build the license system.')">Approve Design</button>
</div>
</div>
<script>
mermaid.initialize({ theme: 'dark', themeVariables: { primaryColor: '#C8A434', primaryTextColor: '#E8E6E0', primaryBorderColor: '#1E2025', lineColor: '#7A7D85', secondaryColor: '#141518', tertiaryColor: '#0D0E10', background: '#141518', mainBkg: '#141518', nodeBorder: '#C8A434' }});
</script>
</body>
</html>

View file

@ -0,0 +1,326 @@
# ArchNest — Self-Hosted Product Design
> Open-core model: free self-hosted base with $5 one-time module purchases.
> No subscriptions. No SaaS. Customer owns it forever.
---
## Business Model
| Aspect | Detail |
|--------|--------|
| **Core** | Free, self-hosted, open-source (or source-available) |
| **Modules** | $5 one-time purchase each (lifetime license) |
| **Updates** | Free core updates forever. Module updates included. |
| **License** | Phone-home on boot + weekly check. Works offline between checks. |
| **Revenue** | Volume × $5. Target: high module attach rate per install. |
| **Infrastructure cost** | Near zero (license server + payment processor only) |
---
## Free Core (What Ships for Free)
The free tier must be genuinely useful — good enough to adopt, limited enough
to want more.
| Feature | Included Free |
|---------|--------------|
| Dashboard (Glance page) | ✓ |
| Infrastructure overview | ✓ |
| SSH Terminal (1 tab, 1 pane) | ✓ |
| SSH Tunnels (manual start only) | ✓ |
| SFTP File Manager | ✓ |
| Docker management (TCP API only, 1 source) | ✓ |
| Host Metrics (basic: CPU/memory/disk) | ✓ |
| Bookmarks (10 max) | ✓ |
| Settings (Profile, Integrations) | ✓ |
| 3 SSH host integrations max | ✓ |
| 1 user (admin only) | ✓ |
| Single theme (ArchNest Dark) | ✓ |
| Help page | ✓ |
**Why this works:** A solo developer with 13 servers can use ArchNest for
free with a functional terminal, basic Docker visibility, and file management.
The moment they want split panes, more hosts, multi-user, or RDP — they buy
modules.
---
## Paid Modules ($5 Each)
### SSH Modules
| # | Module | What It Unlocks |
|---|--------|-----------------|
| 1 | **Multi-Pane Terminal** | Split panes (2/4), multiple tabs |
| 2 | **tmux Integration** | Attach to existing tmux sessions |
| 3 | **Jump-Host Chaining** | Connect through intermediary hosts (ProxyJump) |
| 4 | **Certificate Auth (OPKSSH)** | Certificate-based SSH authentication |
| 5 | **Tunnel Auto-Start** | Tunnels start automatically on boot |
| 6 | **Persistent Sessions** | Terminal sessions survive page navigation |
| 7 | **Session Recording** | Record terminal sessions to disk |
| 8 | **Host-to-Host Transfer** | Copy/move files between two SSH hosts |
### Docker Modules
| # | Module | What It Unlocks |
|---|--------|-----------------|
| 9 | **Docker over SSH** | Manage containers via `docker` CLI over SSH (no exposed socket) |
| 10 | **Docker Push Agent** | Outbound-only monitoring agent for Docker hosts |
| 11 | **Container Exec** | Interactive shell into running containers |
| 12 | **Container Detail View** | Full inspect: ports, networks, mounts, env, labels |
### Integration Modules
| # | Module | What It Unlocks |
|---|--------|-----------------|
| 13 | **Unlimited SSH Hosts** | Remove 3-host cap (unlimited integrations) |
| 14 | **Proxmox Integration** | VM/LXC management |
| 15 | **AWS Integration** | EC2 + STS resource inventory |
| 16 | **Cloudflare Integration** | DNS zones, resource listing |
| 17 | **NetBird Integration** | Mesh peers, connectivity |
| 18 | **Uptime Kuma Integration** | Monitor status/health |
### Desktop & Display Modules
| # | Module | What It Unlocks |
|---|--------|-----------------|
| 19 | **Remote Desktop (RDP)** | RDP sessions via Guacamole |
| 20 | **Remote Desktop (VNC)** | VNC sessions via Guacamole |
| 21 | **Remote Desktop (Telnet)** | Telnet sessions via Guacamole |
| 22 | **Theme: Midnight Blue** | Blue accent theme |
| 23 | **Theme: Forest** | Emerald accent theme |
| 24 | **Theme: Light** | Light mode theme |
### Platform Modules
| # | Module | What It Unlocks |
|---|--------|-----------------|
| 25 | **Multi-User** | Add users (admin/member roles, up to 10 seats) |
| 26 | **Advanced Metrics** | Full host metrics (network, processes, ports, firewall, login stats) |
| 27 | **Data Export/Import** | Backup/restore integrations + secrets + bookmarks + tunnels |
| 28 | **Audit Log** | Full activity audit log with export |
| 29 | **Unlimited Bookmarks** | Remove 10-bookmark cap |
| 30 | **Global Search** | Search across pages, integrations, bookmarks |
---
## Bundles (Discounted)
| Bundle | Modules Included | Price | Savings |
|--------|-----------------|-------|---------|
| **SSH Pro** | #18 (all SSH modules) | $25 | Save $15 |
| **Docker Pro** | #912 (all Docker modules) | $15 | Save $5 |
| **Remote Desktop** | #1921 (RDP + VNC + Telnet) | $10 | Save $5 |
| **All Themes** | #2224 (3 themes) | $10 | Save $5 |
| **Everything** | All 30 modules | $99 | Save $51 |
---
## Revenue Model
| Scenario | Installs/mo | Avg modules purchased | Revenue/mo |
|----------|-------------|----------------------|------------|
| Early (month 1-3) | 50 | 3 modules ($15 avg) | $750 |
| Growth (month 4-6) | 200 | 4 modules ($20 avg) | $4,000 |
| Steady (month 7-12) | 500 | 5 modules ($25 avg) | $12,500 |
| Mature (year 2) | 1,000 | 4 modules + bundles ($30 avg) | $30,000 |
**Infrastructure cost:** ~$20-30/month (license server + Stripe + domain).
**Profit margin:** ~95%+ (no SaaS hosting, no per-tenant compute).
---
## License System Architecture
### Phone-Home (Light Touch)
```
┌─────────────────────┐ ┌────────────────────────┐
│ Customer Install │ │ License Server │
│ │ │ (Akamai / Cloudflare │
│ Fastify Backend │────────▶│ Workers / Lambda) │
│ on boot + weekly │ │ │
│ │◀────────│ Returns: │
│ Validates signed │ │ - licensed_modules[] │
│ response locally │ │ - valid_until (7day) │
└─────────────────────┘ │ - signature │
└────────────────────────┘
```
**How it works:**
1. Customer installs ArchNest (Docker Compose or bare metal)
2. On first boot, backend calls license server with install ID
3. License server returns a signed JSON payload:
- `modules`: list of purchased module slugs
- `valid_until`: timestamp (7 days from now)
- `signature`: Ed25519 signature of the payload
4. Backend validates the signature locally (public key embedded in code)
5. If signature valid and `valid_until` hasn't expired → features unlocked
6. Re-checks weekly. If server unreachable, works offline for 7 days.
7. After 7 days without a successful check → falls back to free core only
**Grace period:** 7 days offline. Generous enough for server maintenance,
network issues, etc. If someone loses internet for a week, they keep working.
### License Server Stack
| Component | Provider | Cost |
|-----------|----------|------|
| License API | Cloudflare Workers (free tier: 100K req/day) | $0 |
| Database | Cloudflare D1 (free tier: 5GB) | $0 |
| Payment | Stripe (2.9% + $0.30 per transaction) | Per-sale |
| Domain | Route 53 or Cloudflare | $1/mo |
| **Total** | | **~$1/mo + Stripe fees** |
At $5/module, Stripe takes ~$0.45 per transaction. Net per module: **$4.55**.
### Purchase Flow
```
Customer browses modules in Settings → Module Store tab
→ Clicks "Buy" → Stripe Checkout ($5)
→ Stripe webhook → License server records purchase
→ Customer's next license check returns new module
→ Feature unlocks immediately (or within minutes on next poll)
```
### Install ID Generation
- Generated on first boot: `SHA-256(machine-id + secret-key + timestamp)`
- Stored in the database
- Tied to Stripe customer on first purchase
- Transferable (customer can request a reset if they move servers)
---
## Module Enforcement (Backend)
```typescript
// Fastify plugin — runs before route handlers
const tierMiddleware = (app) => {
app.addHook('onRequest', async (req, reply) => {
const license = app.licenseCache; // refreshed weekly
req.modules = license?.modules ?? [];
});
};
// Route-level check
app.get('/api/terminal/connect', {
preHandler: [requireModule('multi-pane-terminal')],
handler: terminalConnect
});
function requireModule(slug: string) {
return async (req, reply) => {
if (!req.modules.includes(slug)) {
reply.code(402).send({
error: 'Module required',
module: slug,
price: '$5',
purchaseUrl: `https://archnest.io/modules/${slug}`
});
}
};
}
```
**Frontend enforcement:**
- Module-gated UI elements show a lock icon + "Unlock for $5" prompt
- Clicking opens the purchase flow (in-app or redirect to store)
- After purchase, UI refreshes and feature unlocks
---
## Free Core Updates
- All users get bug fixes, security patches, and core feature improvements
- Module features don't get stripped from updates — once bought, always works
- New modules may be added over time (new revenue without churning existing customers)
- Major version upgrades (v2, v3) may require a new "Everything" bundle purchase (TBD)
---
## Comparison: SaaS vs Self-Hosted Module Model
| | SaaS (old design) | Self-Hosted Modules (new) |
|---|---|---|
| Infra cost | $66-300/mo | ~$1/mo |
| Revenue model | Recurring ($2.50-12/mo) | One-time ($5/module) |
| Churn risk | High (monthly cancel) | None (one-time) |
| Support burden | High (you host it) | Low (they host it) |
| Profit margin | 60-65% | 95%+ |
| Scale limit | Your AWS bill | Their hardware |
| Customer lock-in | Subscription | Ownership (better reputation) |
---
## Tech Stack (Unchanged)
| Layer | Tech |
|-------|------|
| Frontend | React 19, Vite 8, TypeScript, Tailwind v4 |
| Backend | Fastify 5, TypeScript, SQLite (better-sqlite3) |
| Auth | Local JWT + bcrypt (self-hosted, no Cognito) |
| License | Phone-home to Cloudflare Workers |
| Payment | Stripe Checkout |
| Deploy | Docker Compose (customer's hardware) |
| CI/CD | Forgejo Actions |
---
## What Changes From Current Codebase
| Area | Current | New |
|------|---------|-----|
| Database | SQLite (stays) | SQLite (stays — no Postgres migration needed) |
| Auth | Local JWT (stays) | Local JWT (stays — no Cognito needed) |
| Multi-tenant | Not needed | Not needed (single-tenant per install) |
| License check | None | New: weekly phone-home + local signature validation |
| Module gating | None | New: Fastify middleware + frontend lock UI |
| Settings page | Current tabs | New: "Module Store" tab |
| Stripe | None | New: Stripe Checkout for purchases |
**Key insight:** This model requires almost no infrastructure changes to the
current codebase. You're adding a license middleware layer and a store UI —
not rewriting the database, auth, or deployment.
---
## Implementation Priority
### Phase 1: License Infrastructure
1. Build license server (Cloudflare Workers + D1)
2. Add license check to backend (on boot + weekly cron)
3. Add module enforcement middleware
4. Add "Module Store" tab in Settings
### Phase 2: Module Gating
1. Define module boundaries in code (which routes require which module)
2. Add lock UI to gated features in frontend
3. Free tier caps (3 hosts, 1 pane, 10 bookmarks)
### Phase 3: Purchase Flow
1. Stripe integration (Checkout, webhooks)
2. Module activation on purchase
3. Bundle discounts
4. Purchase history in Settings
### Phase 4: Distribution
1. Public Docker image on Docker Hub / GitHub Container Registry
2. Landing page with module catalog
3. Installation docs
4. Demo instance for prospects
---
## Open Decisions
| # | Question | Options |
|---|----------|---------|
| 1 | Source code visibility | Open-source (MIT/Apache) vs source-available (BSL) vs proprietary |
| 2 | Docker Hub vs self-hosted registry | Docker Hub (wider reach) vs GHCR (free private) |
| 3 | Landing page tech | Static site on Cloudflare Pages vs separate repo |
| 4 | Refund policy | 30-day no-questions vs no refunds ($5 is low enough) |
| 5 | Module store UX | In-app tab vs external website |
| 6 | License transfer | Allow unlimited vs 1 transfer per year |

View file

@ -0,0 +1,173 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: >
ArchNest - Single-user self-hosted ops dashboard on AWS.
Deploys a t4g.small EC2 instance with Docker Compose.
Parameters:
KeyPairName:
Type: String
Default: kiro-ide-key
Description: SSH key pair name for EC2 access
InstanceType:
Type: String
Default: t4g.small
AllowedValues:
- t4g.micro
- t4g.small
- t4g.medium
Description: EC2 instance type (ARM/Graviton)
VolumeSize:
Type: Number
Default: 30
Description: EBS volume size in GB
Resources:
# Security Group — allows SSH, HTTP, HTTPS, and the backend port
ArchNestSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ArchNest security group
GroupName: archnest-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Description: SSH access
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: HTTP (redirect to HTTPS)
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: HTTPS
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: 0.0.0.0/0
Description: Frontend (direct, before proxy)
- IpProtocol: tcp
FromPort: 4000
ToPort: 4000
CidrIp: 0.0.0.0/0
Description: Backend API
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: All outbound (SSH to managed hosts, Docker pulls, etc.)
Tags:
- Key: Name
Value: archnest-sg
# Elastic IP — stable public IP across stop/start
ArchNestEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: archnest-eip
# EC2 Instance
ArchNestInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
KeyName: !Ref KeyPairName
ImageId: !FindInMap [RegionAMI, !Ref 'AWS::Region', AMI]
SecurityGroupIds:
- !Ref ArchNestSecurityGroup
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: !Ref VolumeSize
VolumeType: gp3
Encrypted: true
UserData:
Fn::Base64: |
#!/bin/bash
set -e
# Update system
apt-get update -y
apt-get upgrade -y
# Install Docker
apt-get install -y docker.io docker-compose-v2 git curl
systemctl enable --now docker
# Create deploy directory
mkdir -p /opt/archnest
chown ubuntu:ubuntu /opt/archnest
# Signal ready
echo "ArchNest instance ready" > /opt/archnest/READY
Tags:
- Key: Name
Value: archnest
# Associate Elastic IP with instance
ArchNestEIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId: !Ref ArchNestInstance
EIP: !Ref ArchNestEIP
# Budget alarm — $30/month ceiling
ArchNestBudget:
Type: AWS::Budgets::Budget
Properties:
Budget:
BudgetName: archnest-monthly
BudgetType: COST
TimeUnit: MONTHLY
BudgetLimit:
Amount: 30
Unit: USD
NotificationsWithSubscribers:
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 80
Subscribers:
- SubscriptionType: EMAIL
Address: samueljamesinc@gmail.com
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 100
Subscribers:
- SubscriptionType: EMAIL
Address: samueljamesinc@gmail.com
Mappings:
# Ubuntu 24.04 LTS ARM64 AMIs per region
RegionAMI:
us-east-1:
AMI: ami-0a7a4e87939439934
us-east-2:
AMI: ami-0ea3405d2d2522162
us-west-2:
AMI: ami-05d38da78ce859165
Outputs:
PublicIP:
Description: ArchNest public IP address
Value: !Ref ArchNestEIP
SSHCommand:
Description: SSH into the instance
Value: !Sub 'ssh -i ~/.ssh/kiro_ide_key ubuntu@${ArchNestEIP}'
InstanceId:
Description: EC2 Instance ID
Value: !Ref ArchNestInstance
EstimatedMonthlyCost:
Description: Estimated monthly cost
Value: '~$15/month (t4g.small + 30GB gp3 + Elastic IP)'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
pics/midnight-blue-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
pics/theme2-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
pics/theme2-light-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
pics/theme3-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
pics/theme3-light-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB