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