wiki: update Architecture

chaib 2026-06-22 20:29:13 +00:00
parent b65761f0c5
commit 9ee457a921

@ -1,143 +1,180 @@
# Architecture
# Atlas OS — System Architecture
Atlas OS runs on **one box**. Not a cluster, not a hyperscaler region, not a sprawl of managed services — a single EU-sovereign Hetzner VPS (NL/DE) that hosts the entire stack. This page explains why that is a deliberate design choice, how the edge funnels every request through one identity gate, and how internal services stay invisible to the public internet.
## Overview: The One-Box Philosophy
> EU-sovereign by design: hosting sits in NL/DE, outside the reach of the US Cloud Act.
Atlas OS is deliberately a single-server architecture. One Hetzner CPX32 VPS
(8 vCPU · 16 GB RAM · 150 GB NVMe · Nuremberg) runs the complete operational
infrastructure of Atlas Corporation: CRM, finance, communications, AI, document
management, fleet control, and 11 websites.
**Why one box?** For a solo founder, operational simplicity outweighs redundancy.
One server = one bill, one SSH target, one Caddy config, one Authentik instance,
and zero inter-datacenter latency. The Tailscale overlay provides device-level
redundancy for access even if Caddy is down.
---
## The one-box philosophy
## Network Topology
Everything Atlas needs to operate as a business lives on a single host:
- **~55 Docker containers** running the full application estate
- **~24 public services** exposed to the internet
- **One Caddy edge** terminating TLS and routing every hostname
- **One identity provider** (Authentik) gating access before any app is reached
The advantage is operational gravity: one place to back up, one place to patch, one network namespace to reason about, one TLS configuration, one SSO policy. A solo operator can hold the whole system in their head. The trade-off — a single failure domain — is mitigated by nightly backups and the discipline of keeping the surface small.
| Layer | Component | Role |
|---|---|---|
| Edge | Caddy | TLS termination, reverse proxy, `forward_auth` to SSO |
| Identity | Authentik (embedded outpost) | SSO gate in front of protected apps |
| Apps | ~55 Docker containers | Cockpit, Work, AI, Files & Comms, Ops, Sites |
| Private spine | Tailscale | Internal-only reach via `tailscale serve` |
| Mesh | NATS | Node-to-node messaging |
| Remote | MeshCentral | Manage client POS / devices |
Admin identity across the box: **atlasshb**.
---
## Request path: Caddy edge → Authentik SSO → apps
Every public hostname resolves to Caddy. Caddy terminates TLS, then — for protected routes — performs a `forward_auth` handshake against the **embedded Authentik outpost**. Only after Authentik confirms the session does the request reach the upstream container.
```mermaid
%%{init:{'theme':'base','themeVariables':{'primaryColor':'#0c1428','primaryTextColor':'#cfe2ff','primaryBorderColor':'#1F6FE0','lineColor':'#4EA0FF'}}}%%
flowchart TD
U[Browser / client] -->|HTTPS| C[Caddy edge<br/>TLS termination]
C -->|forward_auth| A[Authentik<br/>embedded outpost]
A -->|session valid| C
A -.->|no session| L[Login / MFA]
L --> A
C -->|authed request| OS[Atlas OS desktop<br/>command.atlascorporation.nl/os/]
C --> APPS[Protected apps]
subgraph box["One Hetzner VPS — EU-sovereign · ~55 containers"]
C
A
OS
APPS
subgraph internal["Internal-only (no public ingress)"]
T[Loopback-bound services]
TS[tailscale serve]
end
APPS --- T
T --- TS
end
TS -.->|private spine| TN[Tailscale tailnet]
APPS --> M[(The Mind<br/>kernel.sqlite spine)]
M --> R[LLM router :8888]
```
┌──────────────────────────────────────────────┐
Public Internet │ atlas-01 (Hetzner CPX32) │
───────────── │ ┌─────────┐ ┌──────────┐ │
Browser/App ──────►│ │ Caddy │───►│Authentik │──► Services │
│ │ (TLS) │ │ (SSO) │ │
│ └─────────┘ └──────────┘ │
│ │
Tailscale mesh │ ┌────────────────────────────────────────┐ │
──────────────── │ │ Private tailnet (100.x.x.x) │ │
iOS / PC / Laptop─►│ │ Webtop desktop · internal dashboards │ │
│ └────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
The browser desktop at **command.atlascorporation.nl/os/** is itself one of these protected apps — 39 apps organised into Cockpit, Identity, AI, Work, Files & Comms, Ops, and Sites categories. See [[Atlas-OS]] for the full app catalog and [[Security]] for the SSO and MFA model.
### Public → SSO path (every service except mail ports)
1. Request hits Caddy (443 → Docker port)
2. Caddy runs `forward_auth` against Authentik outpost
3. Authentik validates session cookie / issues redirect to login
4. On success: Caddy copies `X-Authentik-*` headers and proxies to service
### Tailscale-only services
- Webtop remote desktop (`tailscale serve :8497`)
- Internal dashboards (portal, status feeds)
- atlas-01 SSH (blocked on public firewall, tailnet-only)
---
## Loopback-binding + `tailscale serve` for internal services
## Service Layers
Not everything should be reachable from the public internet. Atlas keeps its internal surface small with two complementary techniques:
### Tier 0 — Identity & Security
- **Authentik** (`auth.{DOMAIN}`) — OIDC/SSO, MFA, app proxies
- **Vaultwarden** (`vault.{DOMAIN}`) — end-to-end encrypted password vault
- **Caddy** — automatic TLS, reverse proxy, the sole public entry point
### 1. Loopback binding
### Tier 1 — Core Business
- **Odoo 19** — CRM, invoicing, project management, contacts
- **Nextcloud 31** — file storage, DAV (calendar/contacts sync to iOS)
- **Stalwart** — self-hosted email: SMTP, IMAPS, JMAP, Sieve
- **Forgejo** — self-hosted git (this very repo)
Internal-only services bind to **loopback** rather than a public interface. They are reachable from the box itself (and from other containers/proxies on the host) but have **no public ingress**. Caddy can still reverse-proxy to them when an authenticated, SSO-gated route legitimately needs them — but the open internet cannot hit them directly.
### Tier 2 — Finance & Operations
- **Firefly III** — double-entry bookkeeping, budgets, reconciliation
- **Enable Banking** — PSD2 bank feed (ING, Knab → Firefly)
- **Paperless-ngx** — document management, OCR, auto-tagging
- **n8n** — workflow automation (webhooks, scheduled tasks)
- **Uptime Kuma** — service health monitoring with alerting
### 2. `tailscale serve` over the private spine
### Tier 3 — AI & Intelligence
- **LiteLLM proxy** — unified OpenAI-compat API to all models (local + cloud)
- **Open-WebUI** — ChatGPT-style UI on top of LiteLLM
- **Atlas Brain** (`atlas_brain.py`) — Python signal collector, runs every 15 min
- **Atlas Loop** — background nudge cycle, publishes to NATS
- **Atlas Meridian** — autonomous decision engine / battle responder
- **Langfuse** — LLM observability: every call traced with latency, cost, tokens
- **Local model router** (`:8888`) — OpenAI-compat router for local Ollama models
Where a service needs to be reached from another machine — but should never be public — it is exposed via **`tailscale serve`** on the Tailscale tailnet. This is the private spine: only devices enrolled in the tailnet can reach those services. The rule is explicit and absolute:
### Tier 4 — Fleet & Infrastructure
- **MeshCentral** — remote device access, POS terminal fleet management
- **NATS** — real-time pub/sub event bus across all Tailscale devices
- **Atlas heartbeat sink** — aggregates device heartbeats from the NATS mesh
- **Atlas cockpit feeds** — on-disk JSON feeds consumed by the OS desktop
- **Webtop** — full XFCE Linux desktop in the browser (tailnet-only)
> Internal services are reached via `tailscale serve`**never** exposed publicly.
### Tier 5 — Managed Sites (11 sites)
This is why the public service count (~24) is far smaller than the container count (~55): the majority of containers are either dependencies of other services or deliberately kept off the public edge, reachable only over loopback or the tailnet.
All served via Caddy. WordPress sites run in isolated Docker stacks:
| Exposure tier | How it's reached | Example surface |
|---|---|---|
| Public | Caddy → Authentik → app | Atlas OS desktop, public sites |
| Loopback | Bound to localhost; proxied via authed Caddy route only | Backend dependencies |
| Tailnet | `tailscale serve`, tailnet members only | Internal-only services |
The **Remote Desktop** (Webtop `ubuntu-xfce`, Selkies/WebRTC) at `desktop.atlascorporation.nl` is a good illustration of the edge model in practice: it sits behind Authentik SSO, and ships with Claude Code + Chromium pre-installed so OAuth logins work in a real browser. See [[Remote-Desktop]].
| Domain | Stack | Notes |
|--------|-------|-------|
| `atlasworks.nl` | Docker: works-wordpress | Live WooCommerce (Caddy :8111) |
| `atlascorporation.nl` | Docker: corp-wordpress | Corporate site |
| `atlasagency.nl` | Docker: agency-wordpress | Agency services |
| `atlasacademy.nl` | TransIP nginx (static HTML) | Academy landing |
| `atlaspos.nl` | TransIP | AtlasPOS product site |
---
## Data flows through the Mind
## Data Flow — Atlas Brain Cycle
Atlas is not just a pile of apps behind a proxy — it has a reasoning core, and operational data flows through it.
- **The spine is `kernel.sqlite`** — the canonical store of decisions and events (~458 decisions, ~3415 events recorded).
- **Atlas Brain** is the reviewed-execution autonomous core. It can act, but it acts under a gate.
- **The policy gate ALWAYS requires human approval** for anything touching **money**, **external communications**, or **self-modification**. There is no autonomous path around it.
- **The LLM router at `localhost:8888`** picks the cheapest capable model for each task, escalating only when needed:
```mermaid
%%{init:{'theme':'base','themeVariables':{'primaryColor':'#0c1428','primaryTextColor':'#cfe2ff','primaryBorderColor':'#1F6FE0','lineColor':'#4EA0FF'}}}%%
flowchart LR
Q[Task] --> R{LLM router<br/>localhost:8888}
R -->|first| LOC[Local models<br/>LLaMA 1B · hermes3 · Qwen Coder · Qwen 7B]
R -->|then| FREE[Free cloud<br/>Groq llama-3.3-70b · Cerebras · OpenRouter]
R -->|last| PAID[Claude — paid]
LOC --> H[Headroom proxy<br/>context compression]
FREE --> H
PAID --> H
```
Every 15 minutes (atlas-brain.service):
┌──────────────────────────────────────────┐
│ Signal collection phase │
│ ├── 7 IMAP mailboxes (read-only) │
│ ├── Odoo REST API (leads, contacts) │
│ ├── Enable Banking (bank transactions) │
│ └── atlas-01 health/fleet feeds │
└──────────────────┬───────────────────────┘
atlas-state.json
LLM synthesis (model router):
atlas-auto (local) → Groq 70B → Claude Opus
atlas-brief-spoken.json (daily brief)
Atlas OS cockpit panels (Today · Money · Inbox · Pipeline)
```
The router binds locally (`localhost:8888`) — itself an example of the loopback-binding principle: the model router is an internal service, not a public endpoint. The **Headroom proxy** compresses context so requests stay cheap. Local models are tried first, free cloud tiers next, and Claude (paid) only as a last resort.
---
For how the Mind, the policy gate, and the router fit together operationally, see [[AI-Stack]] and [[Atlas-Brain]].
## The AI Mesh
```
atlas-01 (NATS JetStream server)
├── subjects:
│ atlas.events.* system events (disk, CPU, service state)
│ atlas.heartbeat.* device health pings
│ atlas.jobs.* delegated compute jobs (Ollama on NEBULA)
│ atlas.brief.* daily brief push to all devices
└── subscribers:
Caddy PC (this machine) ← atlas-events-collector
NEBULA (HP EliteBook) ← atlas-delegate-worker (5 Ollama models)
MET-Desktop ← Odoo failover + fleet monitor
```
---
## Why this holds together
## Caddy SSO Pattern
| Principle | Implementation |
|---|---|
| One failure domain, one control plane | Single Hetzner VPS, ~55 containers |
| Sovereign data | EU hosting (NL/DE), no US Cloud Act exposure |
| One front door | Caddy terminates TLS for every hostname |
| One identity gate | Authentik `forward_auth` on the embedded outpost |
| Small public surface | Loopback binding + `tailscale serve` keep internals private |
| Cheapest-capable compute | LLM router: local → free cloud → Claude last |
| No silent autonomy on what matters | Policy gate requires human approval for money / comms / self-modify |
Every SSO-gated service uses this Caddyfile block (see `deploy/caddy/Caddyfile.template`):
```caddy
app.example.com {
# Authentik outpost endpoints (must come first)
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
# SSO gate — redirects to login if no valid session
forward_auth authentik-server:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email
X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt
}
# Only reached after successful authentication
reverse_proxy localhost:APP_PORT
}
```
---
### See also
## Docker Compose Topology
- [[Atlas-OS]] — the 39-app browser desktop
- [[AI-Stack]] — the Mind, the router, and the policy gate
- [[Security]] — EU-sovereign hosting, MFA, backups, secrets
- [[Fleet-and-Mesh]] — MeshCentral, Tailscale spine, NATS
- [[Remote-Desktop]] — Webtop behind SSO
| Stack | File | Services |
|-------|------|----------|
| Core | `deploy/compose/atlas-core.yml` | Authentik, Nextcloud, Stalwart, Forgejo, Vaultwarden |
| AI | `deploy/compose/atlas-ai.yml` | LiteLLM, Open-WebUI, Langfuse |
| Business | `atlas-odoo.yml` | Odoo 19 + PostgreSQL |
| Finance | `atlas-finance.yml` | Firefly III + Paperless-ngx + Enable Banking |
| Fleet | `atlas-fleet.yml` | MeshCentral + NATS + Uptime Kuma |
| Sites | Per-site compose | WordPress stacks |
| Desktop | `deploy/webtop-docker-compose.yml` | Webtop (tailnet-only) |
All stacks share the `atlas-net` Docker network. Caddy is the **sole** process with
public port bindings (80/443). All internal services bind only to `127.0.0.1:PORT`.