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