fedora-csb-system-manager
1{
2 config,
3 globals,
4 libx,
5 pkgs,
6 ...
7}:
8{
9
10 imports = [
11 ../common/hardware/laptop.nix
12 ../common/programs/direnv.nix
13 ../common/programs/git.nix
14 ../common/programs/tmux.nix
15 # ../common/services/networkmanager.nix
16 # ../common/services/fprint.nix # With yubikey I don't really need this to be honest
17 ../common/services/ansible.nix
18 ../common/services/containers.nix
19 ../common/services/docker.nix
20 ../common/services/libvirt.nix
21 ../common/services/binfmt.nix
22 # ../common/services/oci-image-mirroring.nixi
23 # ../common/services/ollama.nix # TODO handle nvidia vs not ?
24 ../common/services/prometheus-exporters-node.nix
25 # ../common/services/gitea-runner
26
27 ../redhat
28
29 # OpenShift port forwarding
30 ./openshift-port-forward.nix
31
32 # Remote build system
33 ../../modules/job-notify
34 ../../modules/nixpkgs-consolidate
35 ../../modules/microshift
36
37 # Binary cache
38 ../../modules/harmonia
39
40 # XMPP Research Bot
41 ../../modules/xmpp-research-bot
42
43 # Automated flake updates
44 ../../modules/nix-flake-updater
45 ];
46
47 # Firewall is enabled in openshift-port-forward.nix
48 # networking.firewall.enable = false;
49
50 # OpenShift SNO endpoints
51 networking.extraHosts = ''
52 192.168.100.7 api.ocp4.lab.home
53 192.168.100.7 api-int.ocp4.lab.home
54 192.168.100.7 console-openshift-console.apps.ocp4.lab.home
55 192.168.100.7 oauth-openshift.apps.ocp4.lab.home
56 '';
57
58 # Age secrets
59 age.secrets."ntfy-token" = {
60 file = ../../secrets/sakhalin/ntfy-token.age;
61 mode = "440";
62 owner = "root";
63 group = "users";
64 };
65 age.secrets."harmonia-aomi-signing-key" = {
66 file = ../../secrets/harmonia/aomi-signing-key.age;
67 mode = "440";
68 owner = "root";
69 group = "root";
70 };
71 age.secrets."xmpp-research-bot-password" = {
72 file = ../../secrets/aomi/xmpp-research-bot-password.age;
73 mode = "400";
74 owner = "vincent";
75 group = "users";
76 };
77 age.secrets."gemini-api-key" = {
78 file = ../../secrets/aomi/gemini-api-key.age;
79 mode = "400";
80 owner = "vincent";
81 group = "users";
82 };
83
84 # TODO make it an option ? (otherwise I'll add it for all)
85 users.users.vincent.linger = true;
86
87 # Binary cache server (x86_64-linux)
88 services.harmonia-cache = {
89 enable = true;
90 signKeyPath = config.age.secrets."harmonia-aomi-signing-key".path;
91 port = 5000;
92 workers = 4;
93 priority = 30;
94
95 # Nightly cache pre-population
96 builder = {
97 enable = true;
98 systems = [
99 "aomi" # Self
100 "kyushu" # Work laptop
101 "sakhalin" # Server
102 ];
103 schedule = "02:00"; # 2 AM daily
104 notification = {
105 enable = true;
106 tokenFile = config.age.secrets."ntfy-token".path;
107 };
108 };
109 };
110
111 # Remote build system
112 services.job-notify = {
113 enable = true;
114 ntfyServer = "https://ntfy.sbr.pm";
115 ntfyTokenFile = config.age.secrets."ntfy-token".path;
116 defaultTopic = "builds";
117 };
118
119 # Nixpkgs WIP branch consolidation automation
120 services.nixpkgs-consolidate = {
121 enable = true;
122 user = "vincent";
123 schedule = "daily";
124 ntfyTokenFile = config.age.secrets."ntfy-token".path;
125 configFile = "/home/vincent/.config/nixpkgs-automation/branches.conf";
126 };
127
128 # XMPP Research Bot (uses Vertex AI for Claude, direct API for Gemini)
129 services.xmpp-research-bot = {
130 enable = true;
131 jid = "researchbot@xmpp.sbr.pm";
132 ownerJid = "vincent@xmpp.sbr.pm";
133 passwordFile = config.age.secrets."xmpp-research-bot-password".path;
134 geminiApiKeyFile = config.age.secrets."gemini-api-key".path;
135 vertexProjectId = "itpc-gcp-pnd-pe-eng-claude";
136 vertexRegion = "us-east5";
137 inboxPath = "/home/vincent/desktop/org/inbox.org";
138 commandsPath = "/home/vincent/.config/xmpp-research-bot/commands.yaml";
139 user = "vincent";
140 group = "users";
141 };
142
143 # Automated flake.lock updates with build verification
144 services.nix-flake-updater = {
145 enable = true;
146 repoPath = "/home/vincent/src/home";
147
148 # Build systems across both architectures for verification
149 buildSystems = [
150 # x86_64-linux systems
151 "aomi" # Self (laptop/build server)
152 "kyushu" # Work laptop
153 "sakhalin" # Server
154 "kerkouane" # VPS server
155
156 # aarch64-linux systems
157 "rhea" # Main media server
158 "aion" # XMPP/podcast server
159 "athena" # Raspberry Pi 4
160 "demeter" # Raspberry Pi 4
161 "aix" # Raspberry Pi 4
162 ];
163
164 # Run weekly on Sunday at 2 AM
165 schedule = "Sun *-*-* 02:00:00";
166
167 # Notifications via ntfy
168 ntfyServer = "https://ntfy.sbr.pm";
169 ntfyTopic = "nix-updates";
170 ntfyTokenFile = config.age.secrets."ntfy-token".path;
171
172 # Git settings
173 gitRemote = "origin";
174 branchPrefix = "flake-update-";
175
176 # Run as vincent (has git push access)
177 user = "vincent";
178
179 # Add randomized delay to avoid conflicts
180 randomizedDelaySec = 1800; # 0-30 min delay
181 };
182
183 services = {
184 logind.settings.Login = {
185 HandleLidSwitch = "ignore";
186 HandleLidSwitchExternalPower = "ignore";
187 HandleLidSwitchDocked = "ignore";
188 };
189 wireguard = {
190 enable = true;
191 ips = libx.wg-ips globals.machines.aomi.net.vpn.ips;
192 endpoint = "${globals.net.vpn.endpoint}";
193 endpointPublicKey = "${globals.machines.kerkouane.net.vpn.pubkey}";
194 };
195 ollama = {
196 enable = true;
197 # acceleration = "cuda"; # no nvidia :D
198 host = "0.0.0.0"; # Listen on all interfaces for network access
199 port = 11434;
200 loadModels = [
201 # Coding Models
202 "qwen2.5-coder:7b" # Best coding: 88.4% HumanEval, Apache 2.0 (~4-5GB, 10-15 tok/s)
203 "codestral" # Latest coding (Jan 2025): 86.6% HumanEval, #1 LMsys (~14GB, 8-10 tok/s)
204
205 # Reasoning Models
206 "phi4-reasoning" # Best 14B reasoning: outperforms 70B distillation, MIT (~9GB, 6-10 tok/s)
207 "deepseek-r1:7b" # Lightweight reasoning: MIT license (~4.5GB, 8-12 tok/s)
208
209 # Multimodal
210 "qwen2.5vl:7b" # Best vision: beats Llama 3.2 11B, Apache 2.0 (~6GB, 5-8 tok/s)
211
212 # Quick Tasks
213 "phi3.5:3.8b" # Ultra-fast all-rounder: MIT license (~2.4GB, 15-25 tok/s)
214
215 # Tool Calling / OpenCode Support
216 "llama3.1:8b" # Native tool support, good for OpenCode (~4.7GB)
217 "mistral-nemo" # Fast tool calling support (~7.7GB)
218
219 # Legacy (keeping for compatibility)
220 "mistral:7b-instruct-q4_K_M" # General purpose fallback
221 ];
222 environmentVariables = {
223 OLLAMA_MODELS = "/var/lib/ollama/models";
224 OLLAMA_NUM_PARALLEL = "1"; # CPU-only, keep it simple
225 OLLAMA_KEEP_ALIVE = "10m";
226 };
227 };
228 smartd = {
229 enable = true;
230 devices = [ { device = "/dev/nvme0n1"; } ];
231 };
232 hardware.bolt.enable = true;
233 # gitea-actions-runner = {
234 # instances = {
235 # "aomi-codeberg" = {
236 # name = "aomi";
237 # enable = true;
238 # url = "https://codeberg.org";
239 # # tokenFile = "/home/vincent/sync/codeberg.token";
240 # tokenFile = "/etc/codeberg.token";
241 # labels = [
242 # # "local:host"
243 # "nixos-${pkgs.system}:host"
244 # "native:host"
245 # "docker:docker://gitea/runner-images:ubuntu-latest"
246 # "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
247 # "ubuntu-24.04:docker://gitea/runner-images:ubuntu-24.04"
248 # "ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04"
249 # "ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04"
250 # # "nix:docker://localhost:5921/nix-runner"
251 # ];
252 # hostPackages = with pkgs; [
253 # bash
254 # direnv
255 # coreutils
256 # curl
257 # gawk
258 # nixVersions.stable
259 # gitFull
260 # gnused
261 # docker
262 # openssh
263 # wget
264 # ];
265 # };
266 # };
267 # };
268 };
269
270 # Ollama Prometheus Exporter (Docker-based, built locally)
271 systemd.tmpfiles.rules = [
272 "d /var/lib/ollama-exporter 0755 root root -"
273 "d /var/lib/git-builds 0755 builder users -"
274 ];
275
276 systemd.services.ollama-exporter = {
277 description = "Ollama Prometheus Exporter";
278 after = [
279 "docker.service"
280 "ollama.service"
281 ];
282 requires = [ "docker.service" ];
283 wantedBy = [ "multi-user.target" ];
284
285 serviceConfig = {
286 Type = "simple";
287 Restart = "always";
288 RestartSec = "10s";
289
290 ExecStartPre = [
291 # Stop and remove existing container
292 "-${pkgs.docker}/bin/docker stop ollama-exporter"
293 "-${pkgs.docker}/bin/docker rm ollama-exporter"
294 # Copy source files to build directory (for future manual rebuilds if needed)
295 "${pkgs.coreutils}/bin/cp ${../../tools/ollama-exporter/Dockerfile} /var/lib/ollama-exporter/Dockerfile"
296 "${pkgs.coreutils}/bin/cp ${../../tools/ollama-exporter/ollama_exporter.py} /var/lib/ollama-exporter/ollama_exporter.py"
297 # Build image locally only if it doesn't exist (to avoid DNS timeout issues)
298 "-${pkgs.bash}/bin/bash -c '${pkgs.docker}/bin/docker image inspect ollama-exporter:local >/dev/null 2>&1 || ${pkgs.docker}/bin/docker build -t ollama-exporter:local /var/lib/ollama-exporter'"
299 ];
300
301 ExecStart = ''
302 ${pkgs.docker}/bin/docker run --rm --name ollama-exporter \
303 -p 8000:8000 \
304 -e OLLAMA_HOST=http://localhost:11434 \
305 --network host \
306 ollama-exporter:local
307 '';
308
309 ExecStop = "${pkgs.docker}/bin/docker stop ollama-exporter";
310 };
311 };
312
313 # Open firewall for Ollama exporter
314 networking.firewall.allowedTCPPorts = [ 8000 ];
315
316 # Builder user for remote builds
317 users.users.builder = {
318 isSystemUser = true;
319 group = "users";
320 home = "/var/lib/git-builds";
321 createHome = true;
322 openssh.authorizedKeys.keys = [
323 # This will be populated with kerkouane's SSH key for build triggering
324 ];
325 };
326
327 # Build execution script
328 environment.etc."git-builds/execute-build.sh" = {
329 text = ''
330 #!${pkgs.bash}/bin/bash
331 set -euo pipefail
332
333 REPO_NAME="$1"
334 BUILD_TYPE="''${2:-auto}"
335 REPO_URL="vincent@kerkouane.sbr.pm:git/public/$REPO_NAME.git"
336
337 # Cache directory and worktree
338 CACHE_DIR="/var/lib/git-builds/$REPO_NAME.git"
339 BUILD_DIR="/var/lib/git-builds/$REPO_NAME-build-$(${pkgs.coreutils}/bin/date +%s)"
340
341 echo "Building $REPO_NAME (type: $BUILD_TYPE)"
342 echo "Cache: $CACHE_DIR"
343 echo "Build directory: $BUILD_DIR"
344
345 # Fetch or clone
346 if [ -d "$CACHE_DIR" ]; then
347 echo "Fetching updates for $REPO_NAME..."
348 cd "$CACHE_DIR" && ${pkgs.git}/bin/git fetch origin
349 else
350 echo "Cloning $REPO_NAME..."
351 ${pkgs.git}/bin/git clone --bare "$REPO_URL" "$CACHE_DIR"
352 fi
353
354 # Create worktree for isolated build
355 echo "Creating worktree at $BUILD_DIR..."
356 cd "$CACHE_DIR"
357 ${pkgs.git}/bin/git worktree add "$BUILD_DIR" main
358
359 cd "$BUILD_DIR"
360 echo "Working directory: $(pwd)"
361
362 # Execute build based on type
363 case "$BUILD_TYPE" in
364 nixos)
365 echo "Running NixOS build..."
366 ${pkgs.nixos-rebuild}/bin/nixos-rebuild build --flake .#aomi
367 ;;
368 make)
369 echo "Running make build..."
370 ${pkgs.gnumake}/bin/make build
371 ;;
372 docker)
373 echo "Running Docker build..."
374 ${pkgs.docker}/bin/docker build -t "$REPO_NAME:latest" .
375 ;;
376 go)
377 echo "Running Go build..."
378 ${pkgs.go}/bin/go build ./...
379 ;;
380 custom)
381 if [ -x .git-build.sh ]; then
382 echo "Running custom build script..."
383 ./.git-build.sh
384 else
385 echo "ERROR: .git-build.sh not found or not executable"
386 exit 1
387 fi
388 ;;
389 auto)
390 echo "Auto-detecting build type..."
391 if [ -f flake.nix ]; then
392 echo "Detected NixOS flake"
393 ${pkgs.nixos-rebuild}/bin/nixos-rebuild build --flake .#aomi
394 elif [ -f Makefile ]; then
395 echo "Detected Makefile"
396 ${pkgs.gnumake}/bin/make build
397 elif [ -f Dockerfile ]; then
398 echo "Detected Dockerfile"
399 ${pkgs.docker}/bin/docker build -t "$REPO_NAME:latest" .
400 elif [ -f go.mod ]; then
401 echo "Detected Go module"
402 ${pkgs.go}/bin/go build ./...
403 else
404 echo "ERROR: Could not auto-detect build type"
405 exit 1
406 fi
407 ;;
408 *)
409 echo "ERROR: Unknown build type: $BUILD_TYPE"
410 exit 1
411 ;;
412 esac
413
414 echo "Build completed successfully!"
415
416 # Cleanup worktree (keep cache)
417 echo "Cleaning up worktree..."
418 cd /
419 ${pkgs.git}/bin/git -C "$CACHE_DIR" worktree remove "$BUILD_DIR"
420 echo "Done!"
421 '';
422 mode = "0755";
423 };
424
425 # Allow builder to run systemd-run without password
426 security.sudo.extraRules = [
427 {
428 users = [ "builder" ];
429 commands = [
430 {
431 command = "${pkgs.systemd}/bin/systemd-run";
432 options = [ "NOPASSWD" ];
433 }
434 ];
435 }
436 ];
437
438 # MicroShift via CRC (disabled for now)
439 services.microshift = {
440 enable = false;
441 cpus = 4;
442 memory = 8192; # 8GB
443 diskSize = 40;
444 user = "vincent";
445 # pullSecret will be configured via agenix later
446 };
447
448}