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}