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

68
nodes/n97-1.nix Normal file
View file

@ -0,0 +1,68 @@
{ config, lib, pkgs, ... }:
let
# n97-1 Tailscale IP — update after initial Tailscale auth
tailscaleIP = "100.x.x.x";
in {
imports = [
../modules/meos-gateway.nix
];
networking.hostName = "n97-1";
# ── k3s server (single-node control plane) ──
services.k3s-cluster = {
enable = true;
role = "server";
tailscaleIP = tailscaleIP;
tokenFile = "/var/lib/k3s/cluster-token";
};
# ── Gateway (nginx + ACME) ──
services.meos-gateway = {
enable = true;
tailscaleIP = tailscaleIP;
};
# ── PostgreSQL (for Forgejo + Umami, transition to K8s) ──
services.postgresql = {
enable = true;
package = pkgs.postgresql_17;
extraPlugins = [ pkgs.postgresql_17.pkgs.pgvector ];
dataDir = "/data/postgresql";
};
# ── Forgejo (bare metal during transition, then K8s) ──
services.forgejo = {
enable = true;
database.type = "postgres";
database.name = "forgejo";
database.user = "forgejo";
settings.server.ROOT_URL = "https://os.scoble.me/forgejo/";
lfs.enable = true;
};
# ── OpenClaw gateway stays bare metal ──
# (managed separately via systemd user service)
# ── Firewall ──
networking.firewall = {
enable = true;
trustedInterfaces = [ "tailscale0" ];
allowedTCPPorts = [ 80 443 6443 ];
};
# ── User ──
users.users.sscoble = {
isNormalUser = true;
extraGroups = [ "wheel" "docker" ];
};
security.sudo.extraRules = [{
users = [ "sscoble" ];
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
}];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "sscoble" ];
}

42
nodes/n97-2.nix Normal file
View file

@ -0,0 +1,42 @@
{ config, lib, pkgs, ... }:
let
tailscaleIP = "100.x.x.x";
in {
networking.hostName = "n97-2";
services.k3s-cluster = {
enable = true;
role = "agent";
serverAddr = "https://100.x.x.x:6443"; # n97-1 IP
tailscaleIP = tailscaleIP;
tokenFile = "/var/lib/k3s/cluster-token";
withOllama = true;
};
# Ollama models to serve
services.ollama.models = [
"deepseek-v4-pro:cloud" # heavy reasoning
"kimi-k2.6:cloud" # backup reasoning
"glm-5.1:cloud" # fallback
];
networking.firewall = {
enable = true;
trustedInterfaces = [ "tailscale0" ];
allowedTCPPorts = [ 11434 ];
};
users.users.sscoble = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
security.sudo.extraRules = [{
users = [ "sscoble" ];
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
}];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "sscoble" ];
}

42
nodes/n97-3.nix Normal file
View file

@ -0,0 +1,42 @@
{ config, lib, pkgs, ... }:
let
tailscaleIP = "100.x.x.x";
in {
networking.hostName = "n97-3";
services.k3s-cluster = {
enable = true;
role = "agent";
serverAddr = "https://100.x.x.x:6443"; # n97-1 IP
tailscaleIP = tailscaleIP;
tokenFile = "/var/lib/k3s/cluster-token";
withOllama = true;
};
services.ollama.models = [
"qwen3-coder-next:cloud" # targeted code
"deepseek-v4-flash:cloud" # fast default
"nomic-embed-text" # embeddings
"mxbai-embed-large" # embeddings
];
networking.firewall = {
enable = true;
trustedInterfaces = [ "tailscale0" ];
allowedTCPPorts = [ 11434 ];
};
users.users.sscoble = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
security.sudo.extraRules = [{
users = [ "sscoble" ];
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
}];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "sscoble" ];
}

36
nodes/rpi4-1.nix Normal file
View file

@ -0,0 +1,36 @@
{ config, lib, pkgs, ... }:
let
tailscaleIP = "100.x.x.x";
in {
networking.hostName = "rpi4-1";
services.k3s-cluster = {
enable = true;
role = "agent";
serverAddr = "https://100.x.x.x:6443"; # n97-1 IP
tailscaleIP = tailscaleIP;
tokenFile = "/var/lib/k3s/cluster-token";
};
# ARM worker — light services only
# Taints/tolerations will be set via kubectl to prevent x86 pods landing here
networking.firewall = {
enable = true;
trustedInterfaces = [ "tailscale0" ];
};
users.users.sscoble = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
security.sudo.extraRules = [{
users = [ "sscoble" ];
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
}];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "sscoble" ];
}

36
nodes/rpi4-2.nix Normal file
View file

@ -0,0 +1,36 @@
{ config, lib, pkgs, ... }:
let
tailscaleIP = "100.x.x.x";
in {
networking.hostName = "rpi4-2";
services.k3s-cluster = {
enable = true;
role = "agent";
serverAddr = "https://100.x.x.x:6443"; # n97-1 IP
tailscaleIP = tailscaleIP;
tokenFile = "/var/lib/k3s/cluster-token";
};
# ARM worker — Umami and monitoring
# Taints/tolerations set via kubectl
networking.firewall = {
enable = true;
trustedInterfaces = [ "tailscale0" ];
};
users.users.sscoble = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
security.sudo.extraRules = [{
users = [ "sscoble" ];
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
}];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "sscoble" ];
}