Initial baseline: home platform (k3s + Longhorn) specification repository
Greenfield spec set for a GitOps-driven PaaS+IaaS home platform: vision & requirements (R-* IDs), architecture, 14 domain specs, 11 ADRs, roadmap (phases + workstreams), and AI-agent coordination (roles, protocol, definition-of-done, task board). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
37 files changed
+2830
No files matched your search
@@ -0,0 +1,112 @@
|
||||
# 13 — Service Catalog & the ServiceDefinition
|
||||
|
||||
Serves: **R-GITOPS-02**, ties together **R-NET-***, **R-STATE-***, **R-TLS-***, **R-DNS-***,
|
||||
**R-OBS-***, **R-UPD-***. The catalog is the operator-facing onboarding surface.
|
||||
|
||||
## The ServiceDefinition abstraction
|
||||
|
||||
Onboarding a service is adding/editing one **ServiceDefinition** under `catalog/` in the
|
||||
implementation repo. It captures *intent*; the platform compiles it into concrete resources
|
||||
(Deployment or KubeVirt VM, PVCs, CiliumNetworkPolicies, egress routing, HTTPRoute, Certificate, DNS
|
||||
record, ServiceMonitor + dashboards + alerts, and update policy). Operators declare **what**;
|
||||
platform provides the **how**. This keeps every requirement wired from a single reviewable object.
|
||||
|
||||
### Fields (canonical schema — implementers render manifests from these)
|
||||
|
||||
```yaml
|
||||
name: <string> # DNS label → <name>.home.keithjackson.dev
|
||||
kind: container | vm # PaaS (default) or IaaS via KubeVirt (R-PLAT-02/03)
|
||||
source:
|
||||
image: <repo/image> # container: image ref
|
||||
# or vmTemplate: <image/source> # vm: CDI source image
|
||||
exposure: internal | public # LAN-only vs edge port-forwarded (R-DNS/R-NET)
|
||||
egress: internet | vpn # egress class (R-NET-05); vpn ⇒ kill-switch (R-NET-06)
|
||||
storage:
|
||||
- name: config
|
||||
size: <e.g. 5Gi>
|
||||
class: longhorn # replicated app/config state (R-STATE-02)
|
||||
- name: media # optional bulk tier (R-STATE-04)
|
||||
class: bulk
|
||||
dependencies: # explicit allow rules; everything else denied (R-NET-01/02)
|
||||
- to: <service>
|
||||
port: <n>
|
||||
protocol: tcp|udp
|
||||
ingressRoutes: # hostnames/paths to expose (rendered to HTTPRoute + TLS + DNS)
|
||||
- host: <name>.home.keithjackson.dev
|
||||
port: <servicePort>
|
||||
update:
|
||||
channel: stable # never pre-release unless opted in (R-UPD-01)
|
||||
range: <semver range | tag regex># acceptable versions
|
||||
cadence: <schedule> # how often to check/apply
|
||||
autoRollbackOnFailedHealth: true # R-UPD-03
|
||||
pin: <optional version> # operator freeze escape hatch
|
||||
observability:
|
||||
metrics: true
|
||||
slis: [ up, ... ] # per-service alert intents (R-OBS-01)
|
||||
backup:
|
||||
policy: <ref to backup schedule> # off-cluster (R-STATE-03); or 'reacquirable' for bulk media
|
||||
```
|
||||
|
||||
> The schema is the **contract** between the operator surface and the implementation. Changing it is
|
||||
> an interface change — coordinate per [`agents/coordination-protocol.md`](../agents/coordination-protocol.md).
|
||||
|
||||
## Default posture for every service (unless overridden)
|
||||
- `kind: container`, `exposure: internal`, `egress: internet`.
|
||||
- **Default-deny**: no `dependencies` ⇒ can reach only DNS ([03](03-networking-isolation-and-egress.md)).
|
||||
- Longhorn-backed config PVC; off-cluster backup; trusted TLS; auto-DNS; monitored; auto-updated on
|
||||
`stable`.
|
||||
|
||||
## The initial catalog (13 services)
|
||||
|
||||
Egress and dependencies below are **normative intent**; implementers render the exact policies.
|
||||
"Bulk" = large media tier ([04](04-storage-and-state.md)). All get trusted TLS + auto-DNS + monitoring
|
||||
+ auto-update unless noted.
|
||||
|
||||
| # | Service | kind | exposure | egress | Storage | Key dependencies (explicit allows) | Notes |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | **plex** | container | public | **internet** | config: longhorn (small, replicated); media: **bulk** | — (clients reach it; Seerr → Plex) | Largest service; media on bulk tier, metadata DB on Longhorn ([04](04-storage-and-state.md)) |
|
||||
| 2 | **sonarr** | container | internal | **vpn** | config: longhorn; media/downloads: bulk | → qbittorrent, → jackett; ← seerr | VPN + kill-switch (R-NET-06) |
|
||||
| 3 | **radarr** | container | internal | **vpn** | config: longhorn; media/downloads: bulk | → qbittorrent, → jackett; ← seerr | VPN + kill-switch |
|
||||
| 4 | **jackett** | container | internal | **vpn** | config: longhorn | ← sonarr, ← radarr | VPN + kill-switch (indexer) |
|
||||
| 5 | **seerr** | container | public | internet | config: longhorn | → sonarr:8989, → radarr:7878, → plex:32400 | Request UI; talks to arr + Plex |
|
||||
| 6 | **pihole** | container | internal | internet | config: longhorn | (provides DNS to cluster/LAN) | Also a platform DNS dependency ([09](09-dns-and-ingress.md)) |
|
||||
| 7 | **nextcloud** | container | public | internet | data: longhorn (irreplaceable → backed up) | → its DB | File sync; DR-critical data |
|
||||
| 8 | **minecraft** | container | public | internet | world: longhorn (backed up) | — | Exposes game port via edge |
|
||||
| 9 | **unifi** | container | internal | internet | config: longhorn | — | UniFi network controller |
|
||||
| 10 | **semaphore** | container | internal | internet | config/db: longhorn | — | Ansible UI |
|
||||
| 11 | **kasm** | container | internal | internet | config: longhorn | — | Kasm Workspaces (may need more resources; container-first, VM if required) |
|
||||
| 12 | **gitea** | container | public | internet | repos/db: longhorn (irreplaceable → backed up) | — | Git hosting; DR-critical |
|
||||
| 13 | **qbittorrent** | container | internal | **vpn** | config: longhorn; downloads: **bulk** | ← sonarr, ← radarr | VPN + kill-switch; download client |
|
||||
|
||||
**VPN-class set (R-NET-05/06):** sonarr, radarr, jackett, qbittorrent — all egress via VPN with an
|
||||
enforced kill-switch. Their inter-dependencies (arr → qbittorrent/jackett) are **east-west allows**
|
||||
within the deny-by-default fabric; being VPN-class governs *egress*, not intra-cluster reachability.
|
||||
|
||||
**Dependency graph (explicit allows only; all else denied):**
|
||||
```
|
||||
seerr → sonarr, radarr, plex
|
||||
sonarr → qbittorrent, jackett
|
||||
radarr → qbittorrent, jackett
|
||||
(everything → DNS only, by default)
|
||||
```
|
||||
|
||||
**"…but not limited to":** the catalog is open-ended. New services follow the same ServiceDefinition
|
||||
and inherit all guarantees automatically — that is the point of the abstraction.
|
||||
|
||||
## Cross-cutting notes
|
||||
- **Plex media** is the R-STATE-04 case: keep the metadata DB on replicated Longhorn; keep the large
|
||||
library on the bulk tier; downloads stage on bulk and are handed off by the arr stack.
|
||||
- **DR-critical data** (Nextcloud, Gitea, Minecraft worlds, PiHole config, service DBs) MUST have
|
||||
off-cluster backups ([04](04-storage-and-state.md)); bulk media may be `reacquirable` per operator
|
||||
choice (OQ-2).
|
||||
- **Public exposure** (Plex, Seerr, Nextcloud, Minecraft, Gitea) requires edge port-forwarding
|
||||
([09](09-dns-and-ingress.md)); everything else stays `internal`.
|
||||
- **Kasm** is resource-heavy and may be a candidate for `kind: vm` if container mode proves
|
||||
insufficient — the ServiceDefinition makes that a one-field change.
|
||||
|
||||
## Acceptance gates
|
||||
- Each service is fully wired from its ServiceDefinition alone (compute, storage, policy, egress,
|
||||
TLS, DNS, monitoring, updates) — no manual steps beyond merge.
|
||||
- Default-deny holds; only listed dependencies work.
|
||||
- VPN-class services egress via VPN with a proven kill-switch.
|
||||
- Stateful services survive destroy/rebuild; DR-critical data restores from off-cluster backup.
|
||||
Reference in new issue
Block a user