wiki: update Architecture
parent
b65761f0c5
commit
9ee457a921
1 changed files with 146 additions and 109 deletions
255
Architecture.md
255
Architecture.md
|
|
@ -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
|
Public Internet │ atlas-01 (Hetzner CPX32) │
|
||||||
- **~24 public services** exposed to the internet
|
───────────── │ ┌─────────┐ ┌──────────┐ │
|
||||||
- **One Caddy edge** terminating TLS and routing every hostname
|
Browser/App ──────►│ │ Caddy │───►│Authentik │──► Services │
|
||||||
- **One identity provider** (Authentik) gating access before any app is reached
|
│ │ (TLS) │ │ (SSO) │ │
|
||||||
|
│ └─────────┘ └──────────┘ │
|
||||||
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.
|
│ │
|
||||||
|
Tailscale mesh │ ┌────────────────────────────────────────┐ │
|
||||||
| Layer | Component | Role |
|
──────────────── │ │ Private tailnet (100.x.x.x) │ │
|
||||||
|---|---|---|
|
iOS / PC / Laptop─►│ │ Webtop desktop · internal dashboards │ │
|
||||||
| 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]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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 |
|
| Domain | Stack | Notes |
|
||||||
|---|---|---|
|
|--------|-------|-------|
|
||||||
| Public | Caddy → Authentik → app | Atlas OS desktop, public sites |
|
| `atlasworks.nl` | Docker: works-wordpress | Live WooCommerce (Caddy :8111) |
|
||||||
| Loopback | Bound to localhost; proxied via authed Caddy route only | Backend dependencies |
|
| `atlascorporation.nl` | Docker: corp-wordpress | Corporate site |
|
||||||
| Tailnet | `tailscale serve`, tailnet members only | Internal-only services |
|
| `atlasagency.nl` | Docker: agency-wordpress | Agency services |
|
||||||
|
| `atlasacademy.nl` | TransIP nginx (static HTML) | Academy landing |
|
||||||
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]].
|
| `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.
|
```
|
||||||
|
Every 15 minutes (atlas-brain.service):
|
||||||
- **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.
|
│ Signal collection phase │
|
||||||
- **The policy gate ALWAYS requires human approval** for anything touching **money**, **external communications**, or **self-modification**. There is no autonomous path around it.
|
│ ├── 7 IMAP mailboxes (read-only) │
|
||||||
- **The LLM router at `localhost:8888`** picks the cheapest capable model for each task, escalating only when needed:
|
│ ├── Odoo REST API (leads, contacts) │
|
||||||
|
│ ├── Enable Banking (bank transactions) │
|
||||||
```mermaid
|
│ └── atlas-01 health/fleet feeds │
|
||||||
%%{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]
|
atlas-state.json
|
||||||
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]
|
LLM synthesis (model router):
|
||||||
FREE --> H
|
atlas-auto (local) → Groq 70B → Claude Opus
|
||||||
PAID --> H
|
│
|
||||||
|
▼
|
||||||
|
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 |
|
Every SSO-gated service uses this Caddyfile block (see `deploy/caddy/Caddyfile.template`):
|
||||||
|---|---|
|
|
||||||
| One failure domain, one control plane | Single Hetzner VPS, ~55 containers |
|
```caddy
|
||||||
| Sovereign data | EU hosting (NL/DE), no US Cloud Act exposure |
|
app.example.com {
|
||||||
| One front door | Caddy terminates TLS for every hostname |
|
# Authentik outpost endpoints (must come first)
|
||||||
| One identity gate | Authentik `forward_auth` on the embedded outpost |
|
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
|
||||||
| Small public surface | Loopback binding + `tailscale serve` keep internals private |
|
|
||||||
| Cheapest-capable compute | LLM router: local → free cloud → Claude last |
|
# SSO gate — redirects to login if no valid session
|
||||||
| No silent autonomy on what matters | Policy gate requires human approval for money / comms / self-modify |
|
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
|
| Stack | File | Services |
|
||||||
- [[AI-Stack]] — the Mind, the router, and the policy gate
|
|-------|------|----------|
|
||||||
- [[Security]] — EU-sovereign hosting, MFA, backups, secrets
|
| Core | `deploy/compose/atlas-core.yml` | Authentik, Nextcloud, Stalwart, Forgejo, Vaultwarden |
|
||||||
- [[Fleet-and-Mesh]] — MeshCentral, Tailscale spine, NATS
|
| AI | `deploy/compose/atlas-ai.yml` | LiteLLM, Open-WebUI, Langfuse |
|
||||||
- [[Remote-Desktop]] — Webtop behind SSO
|
| 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`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue