/ 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 }