/ darwinModules / ollama.nix
ollama.nix
  1  {
  2    config,
  3    lib,
  4    pkgs,
  5    ...
  6  }:
  7  let
  8    cfg = config.services.ollama;
  9  
 10    ollamaEnv = {
 11      OLLAMA_HOST = "${cfg.host}:${toString cfg.port}";
 12    }
 13    // (lib.optionalAttrs (cfg.home != null) {
 14      HOME = cfg.home;
 15    })
 16    // (lib.optionalAttrs (cfg.models != null) {
 17      OLLAMA_MODELS = cfg.models;
 18    })
 19    // cfg.environmentVariables;
 20  
 21    modelLoaderScript = pkgs.writeShellScript "ollama-model-loader" ''
 22      export OLLAMA_HOST="${cfg.host}:${toString cfg.port}"
 23  
 24      # Wait for ollama to be ready
 25      for i in {1..30}; do
 26        if ${cfg.package}/bin/ollama list >/dev/null 2>&1; then
 27          break
 28        fi
 29        sleep 2
 30      done
 31  
 32      if ! ${cfg.package}/bin/ollama list >/dev/null 2>&1; then
 33        echo "ollama server not ready after 60s, giving up"
 34        exit 1
 35      fi
 36  
 37      ${lib.optionalString cfg.syncModels ''
 38        # Remove models not in the declared list
 39        ${cfg.package}/bin/ollama list | tail -n +2 | awk '{print $1}' | while read -r model; do
 40          model_name="''${model%%:*}"
 41          found=0
 42          for declared in ${lib.escapeShellArgs cfg.loadModels}; do
 43            declared_name="''${declared%%:*}"
 44            if [ "$model_name" = "$declared_name" ]; then
 45              found=1
 46              break
 47            fi
 48          done
 49          if [ "$found" = "0" ]; then
 50            echo "Removing undeclared model: $model"
 51            ${cfg.package}/bin/ollama rm "$model" || true
 52          fi
 53        done
 54      ''}
 55  
 56      ${lib.concatMapStringsSep "\n" (model: ''
 57        echo "Pulling model: ${model}"
 58        ${cfg.package}/bin/ollama pull "${model}"
 59      '') cfg.loadModels}
 60    '';
 61  in
 62  {
 63    options.services.ollama = {
 64      enable = lib.mkEnableOption "ollama server for local large language models";
 65  
 66      package = lib.mkOption {
 67        type = lib.types.package;
 68        default = pkgs.ollama;
 69        defaultText = lib.literalExpression "pkgs.ollama";
 70        description = "The ollama package to use.";
 71      };
 72  
 73      home = lib.mkOption {
 74        type = lib.types.nullOr lib.types.str;
 75        default = null;
 76        example = "/var/lib/ollama";
 77        description = ''
 78          The home directory for the ollama service.
 79          When null, ollama uses its default (~/.ollama).
 80          Note: launchd plist does not expand shell variables,
 81          so this must be an absolute path if set.
 82        '';
 83      };
 84  
 85      models = lib.mkOption {
 86        type = lib.types.nullOr lib.types.str;
 87        default = null;
 88        example = "/path/to/ollama/models";
 89        description = ''
 90          The directory for ollama models.
 91          When null, ollama uses its default (~/.ollama/models).
 92          Note: launchd plist does not expand shell variables,
 93          so this must be an absolute path if set.
 94        '';
 95      };
 96  
 97      host = lib.mkOption {
 98        type = lib.types.str;
 99        default = "127.0.0.1";
100        description = "The host address which the ollama server HTTP interface listens to.";
101      };
102  
103      port = lib.mkOption {
104        type = lib.types.port;
105        default = 11434;
106        description = "Which port the ollama server listens to.";
107      };
108  
109      environmentVariables = lib.mkOption {
110        type = lib.types.attrsOf lib.types.str;
111        default = { };
112        description = ''
113          Set arbitrary environment variables for the ollama service.
114          Be aware that these are only seen by the ollama server (launchd service),
115          not normal invocations like `ollama run`.
116        '';
117      };
118  
119      loadModels = lib.mkOption {
120        type = lib.types.listOf lib.types.str;
121        default = [ ];
122        description = ''
123          Download these models using `ollama pull` as soon as the service has started.
124          Search for models at: https://ollama.com/library
125        '';
126      };
127  
128      syncModels = lib.mkOption {
129        type = lib.types.bool;
130        default = false;
131        description = ''
132          Synchronize all currently installed models with those declared in
133          `services.ollama.loadModels`, removing any models that are
134          installed but not currently declared there.
135        '';
136      };
137    };
138  
139    config = lib.mkIf cfg.enable {
140      environment.systemPackages = [ cfg.package ];
141  
142      # Ensure directories exist with correct ownership for user agent
143      system.activationScripts.postActivation.text = lib.mkIf (cfg.home != null || cfg.models != null) (
144        lib.mkAfter ''
145          echo "Setting up ollama directories..."
146          ${lib.optionalString (cfg.home != null) ''
147            mkdir -p "${cfg.home}"
148            chown "${config.system.primaryUser}" "${cfg.home}"
149          ''}
150          ${lib.optionalString (cfg.models != null) ''
151            mkdir -p "${cfg.models}"
152            chown "${config.system.primaryUser}" "${cfg.models}"
153          ''}
154        ''
155      );
156  
157      # Main ollama server as a user agent
158      launchd.user.agents.ollama = {
159        path = [ config.environment.systemPath ];
160        serviceConfig = {
161          ProgramArguments = [
162            "${cfg.package}/bin/ollama"
163            "serve"
164          ];
165          EnvironmentVariables = ollamaEnv;
166          KeepAlive = true;
167          RunAtLoad = true;
168          StandardOutPath = "/tmp/ollama.stdout.log";
169          StandardErrorPath = "/tmp/ollama.stderr.log";
170        };
171      };
172  
173      # Model loader agent (runs once after ollama is available)
174      launchd.user.agents.ollama-model-loader = lib.mkIf (cfg.loadModels != [ ] || cfg.syncModels) {
175        serviceConfig = {
176          ProgramArguments = [ "${modelLoaderScript}" ];
177          RunAtLoad = true;
178          StandardOutPath = "/tmp/ollama-model-loader.stdout.log";
179          StandardErrorPath = "/tmp/ollama-model-loader.stderr.log";
180        };
181      };
182    };
183  }