System design, CloudFormation, theming assets #3
|
|
@ -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
|
|
@ -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 |
|
||||
|
|
|
|||
12
backend/package-lock.json
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
docs/aws-architecture/archnest-architecture.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
73
docs/aws-architecture/generate_diagram.py
Normal 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
|
||||
419
docs/aws-architecture/system-design.html
Normal 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>
|
||||
326
docs/aws-architecture/system-design.md
Normal 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 1–3 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** | #1–8 (all SSH modules) | $25 | Save $15 |
|
||||
| **Docker Pro** | #9–12 (all Docker modules) | $15 | Save $5 |
|
||||
| **Remote Desktop** | #19–21 (RDP + VNC + Telnet) | $10 | Save $5 |
|
||||
| **All Themes** | #22–24 (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 |
|
||||
173
infrastructure/cloudformation.yml
Normal 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)'
|
||||
BIN
pics/glance-midnightblue-blueprint.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
pics/midnight-blue-bg.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
pics/midnight-blue-theme2.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
pics/setting-theme3-light-example.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
BIN
pics/settings-theme3-example.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
pics/theme2-bg.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
pics/theme2-light-bg.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
pics/theme3-bg.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
pics/theme3-light-bg.png
Normal file
|
After Width: | Height: | Size: 2 MiB |