Unverified Commit 96e56b44 authored by Aleksana's avatar Aleksana Committed by GitHub
Browse files

nixos/ollama: add `syncModels` to remove models not defined in `loadModels` (#463358)

parents baa76d3a b61914ea
Loading
Loading
Loading
Loading
+61 −23
Original line number Diff line number Diff line
@@ -154,15 +154,32 @@ in
      };
      loadModels = lib.mkOption {
        type = types.listOf types.str;
        apply = builtins.filter (model: model != "");
        default = [ ];
        example = [
          "dolphin3"
          "gemma3"
          "gemma3:27b"
          "deepseek-r1:latest"
          "deepseek-r1:1.5b"
        ];
        description = ''
          Download these models using `ollama pull` as soon as `ollama.service` has started.

          This creates a systemd unit `ollama-model-loader.service`.
          Use `services.ollama.syncModels` to automatically remove any models not currently declared here.

          Search for models of your choice from: <https://ollama.com/library>
        '';
      };
      syncModels = lib.mkOption {
        type = types.bool;
        default = false;
        description = ''
          Synchronize all currently installed models with those declared in `services.ollama.loadModels`,
          removing any models that are installed but not currently declared there.
        '';
      };
      openFirewall = lib.mkOption {
        type = types.bool;
        default = false;
@@ -266,7 +283,7 @@ in
        };
    };

    systemd.services.ollama-model-loader = lib.mkIf (cfg.loadModels != [ ]) {
    systemd.services.ollama-model-loader = lib.mkIf (cfg.loadModels != [ ] || cfg.syncModels) {
      description = "Download ollama models in the background";
      wantedBy = [
        "multi-user.target"
@@ -289,29 +306,50 @@ in
        RestartSteps = "10";
      };

      script = ''
        total=${toString (builtins.length cfg.loadModels)}
        failed=0

        for model in ${lib.escapeShellArgs cfg.loadModels}; do
          '${lib.getExe ollamaPackage}' pull "$model" &
        done

        for job in $(jobs -p); do
          set +e
          wait $job
          exit_code=$?
          set -e
      script =
        let
          binaryInputs = lib.mapAttrs (_: lib.getExe) {
            ollama = ollamaPackage;
            parallel = pkgs.parallel;
            awk = pkgs.gawk;
            sed = pkgs.gnused;
          };
          inherit (binaryInputs)
            ollama
            parallel
            awk
            sed
            ;

          if [ $exit_code != 0 ]; then
            failed=$((failed + 1))
          declaredModelsRegex = lib.pipe cfg.loadModels [
            (map lib.escapeRegex)
            (lib.concatStringsSep "|")
            (lib.escape [ "/" ])
            lib.escapeShellArg
          ];
        in
        ''
          ${lib.optionalString cfg.syncModels ''
            installed=$('${ollama}' list | '${awk}' 'NR > 1 {print $1}')
            ${
              # if `declaredModelsRegex` is empty, sed will err
              if (cfg.loadModels != [ ]) then
                ''
                  echo declared models regex: ${declaredModelsRegex}
                  undeclared=$(echo "$installed" | '${sed}' -E /${declaredModelsRegex}/d)
                ''
              else
                ''
                  undeclared="$installed"
                ''
            }
            if [ -n "$undeclared" ]; then
              echo removing: $undeclared
              '${ollama}' rm $undeclared
            fi
        done
          ''}

        if [ $failed != 0 ]; then
          echo "error: $failed out of $total attempted model downloads failed" >&2
          exit 1
        fi
          '${parallel}' --tag '${ollama}' pull ::: ${lib.escapeShellArgs cfg.loadModels}
        '';
    };