Initial cluster config: NixOS flake + k3s + 5 node definitions

- Multi-host flake.nix (3x N97 x86_64 + 2x RPi4 aarch64)
- Shared modules: k3s-cluster, tailscale, meos-gateway
- Node configs: n97-1 (control plane), n97-2/n97-3 (Ollama workers), rpi4-1/rpi4-2 (ARM workers)
- K8s manifests: Forgejo, Postgres (CNPG), Umami, OpenBrain, MeOS Dashboard, MCP servers
- Bootstrap and DB migration scripts
- Migration plan doc (7-day phased rollout)
This commit is contained in:
cos Agent 2026-05-04 12:22:09 -07:00
parent 791a9b6721
commit 7b5ffe6b11
21 changed files with 1184 additions and 2 deletions

View file

@ -0,0 +1,103 @@
# Forgejo on k3s
---
apiVersion: v1
kind: Namespace
metadata:
name: forgejo
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: forgejo-data
namespace: forgejo
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo
namespace: forgejo
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: forgejo
template:
metadata:
labels:
app: forgejo
spec:
containers:
- name: forgejo
image: codeberg.org/forgejo/forgejo:11.0
ports:
- containerPort: 3000
name: http
- containerPort: 22
name: ssh
env:
- name: USER_UID
value: "1000"
- name: USER_GID
value: "1000"
- name: FORGEJO__database__DB_TYPE
value: postgres
- name: FORGEJO__database__HOST
value: postgres-scobleclaw-rw.database.svc.cluster.local:5432
- name: FORGEJO__database__NAME
value: forgejo
- name: FORGEJO__database__USER
value: forgejo
- name: FORGEJO__database__PASSWD
valueFrom:
secretKeyRef:
name: postgres-users
key: forgejo-password
- name: FORGEJO__server__ROOT_URL
value: "https://os.scoble.me/forgejo/"
- name: FORGEJO__server__HTTP_PORT
value: "3000"
- name: FORGEJO__server__DOMAIN
value: os.scoble.me
- name: FORGEJO__server__DISABLE_SSH
value: "false"
- name: FORGEJO__server__SSH_PORT
value: "2222"
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1"
volumes:
- name: data
persistentVolumeClaim:
claimName: forgejo-data
---
apiVersion: v1
kind: Service
metadata:
name: forgejo
namespace: forgejo
spec:
ports:
- name: http
port: 3000
targetPort: 3000
- name: ssh
port: 22
targetPort: 22
selector:
app: forgejo

View file

@ -0,0 +1,83 @@
# MCP Servers — lightweight Node.js workloads for ARM worker (rpi4-1)
# forgejo-mcp, lean-experiment-mcp, wardrobe-ai, homeschool
---
apiVersion: v1
kind: Namespace
metadata:
name: mcp-servers
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo-mcp
namespace: mcp-servers
spec:
replicas: 1
selector:
matchLabels:
app: forgejo-mcp
template:
metadata:
labels:
app: forgejo-mcp
spec:
nodeSelector:
kubernetes.io/arch: arm64
tolerations:
- key: "arm64"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: forgejo-mcp
image: node:22-slim
command: ["node", "/app/index.js"]
workingDir: /app
env:
- name: FORGEJO_URL
value: "http://forgejo.forgejo.svc.cluster.local:3000"
- name: FORGEJO_TOKEN
valueFrom:
secretKeyRef:
name: forgejo-tokens
key: agent-cos
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: lean-experiment-mcp
namespace: mcp-servers
spec:
replicas: 1
selector:
matchLabels:
app: lean-experiment-mcp
template:
metadata:
labels:
app: lean-experiment-mcp
spec:
nodeSelector:
kubernetes.io/arch: arm64
tolerations:
- key: "arm64"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: lean-mcp
image: node:22-slim
command: ["node", "/app/dist/index.js"]
workingDir: /app
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"

View file

@ -0,0 +1,46 @@
# MeOS Dashboard — stateless React app, can run on any node
---
apiVersion: v1
kind: Namespace
metadata:
name: meos-dashboard
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: meos-dashboard
namespace: meos-dashboard
spec:
replicas: 1
selector:
matchLabels:
app: meos-dashboard
template:
metadata:
labels:
app: meos-dashboard
spec:
containers:
- name: dashboard
image: ghcr.io/scobleclaw/meos-dashboard:latest
ports:
- containerPort: 3000
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: meos-dashboard
namespace: meos-dashboard
spec:
ports:
- port: 3000
targetPort: 3000
selector:
app: meos-dashboard

View file

@ -0,0 +1,59 @@
# OpenBrain MCP — deployed to ARM worker (rpi4-1)
---
apiVersion: v1
kind: Namespace
metadata:
name: openbrain
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openbrain
namespace: openbrain
spec:
replicas: 1
selector:
matchLabels:
app: openbrain
template:
metadata:
labels:
app: openbrain
spec:
nodeSelector:
kubernetes.io/arch: arm64
tolerations:
- key: "arm64"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: openbrain
image: node:22-slim
command: ["node", "/app/index.js"]
workingDir: /app
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: PORT
value: "3000"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: openbrain
namespace: openbrain
spec:
ports:
- port: 3000
targetPort: 3000
selector:
app: openbrain

View file

@ -0,0 +1,59 @@
# CloudNative PG operator (recommended for Postgres on K8s)
# https://cloudnative-pg.io/
#
# Install: kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.25/releases/cnpg-1.25.1.yaml
#
# Then create the cluster below.
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-scobleclaw
namespace: database
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:17.4
storage:
size: 5Gi
storageClass: local-path
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
postgresql:
parameters:
max_connections: "200"
shared_buffers: "128MB"
databases:
- name: forgejo
owner: forgejo
- name: umami
owner: umami
users:
- name: forgejo
password: "<set-via-secret>"
- name: umami
password: "<set-via-secret>"
enableSuperuserAccess: false
monitoring:
enablePodMonitor: false
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-users
namespace: database
type: Opaque
stringData:
forgejo-password: "<change-me>"
umami-password: "<change-me>"

View file

@ -0,0 +1,67 @@
# Umami analytics — deployed to ARM worker (rpi4-2)
---
apiVersion: v1
kind: Namespace
metadata:
name: umami
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: umami
namespace: umami
spec:
replicas: 1
selector:
matchLabels:
app: umami
template:
metadata:
labels:
app: umami
spec:
nodeSelector:
kubernetes.io/arch: arm64
tolerations:
- key: "arm64"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: umami
image: ghcr.io/umami-software/umami:postgresql-v2
ports:
- containerPort: 3000
env:
- name: DATABASE_TYPE
value: postgresql
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: umami-db
key: url
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: umami-secret
key: secret
- name: DISABLE_TELEMETRY
value: "1"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: umami
namespace: umami
spec:
ports:
- port: 3000
targetPort: 3000
selector:
app: umami