NixOSDots/modules/macos/ollama.nix
2025-12-27 00:56:26 +01:00

180 lines
5.6 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
inherit (lib) types;
cfg = config.services.ollama;
ollama = lib.getExe cfg.package;
in
{
options = {
services.ollama = {
enable = lib.mkEnableOption "ollama server for local large language models";
package = lib.mkPackageOption pkgs "ollama" {
example = "pkgs.ollama";
extraDescription = ''
On macOS, hardware acceleration is automatically handled by Metal.
The standard `ollama` package should work for most users.
'';
};
home = lib.mkOption {
type = types.str;
default = "$HOME/.ollama";
example = "/Users/foo/.ollama";
description = ''
The home directory that the ollama service uses for its data.
On macOS, this defaults to the user's home directory.
'';
};
models = lib.mkOption {
type = types.str;
default = "${cfg.home}/models";
defaultText = "\${config.services.ollama.home}/models";
example = "/Users/foo/.ollama/models";
description = ''
The directory that the ollama service will read models from and download new models to.
'';
};
host = lib.mkOption {
type = types.str;
default = "127.0.0.1";
example = "0.0.0.0";
description = ''
The host address which the ollama server HTTP interface listens to.
'';
};
port = lib.mkOption {
type = types.port;
default = 11434;
example = 11111;
description = ''
Which port the ollama server listens to.
'';
};
environmentVariables = lib.mkOption {
type = types.attrsOf types.str;
default = { };
example = {
OLLAMA_DEBUG = "1";
OLLAMA_NUM_PARALLEL = "2";
};
description = ''
Set arbitrary environment variables for the ollama service.
Be aware that these are only seen by the ollama server (launchd daemon),
not normal invocations like `ollama run`.
Since `ollama run` is mostly a shell around the ollama server, this is usually sufficient.
'';
};
loadModels = lib.mkOption {
type = types.listOf types.str;
apply = builtins.filter (model: model != "");
default = [ ];
example = [
"llama3.2"
"qwen2.5:7b"
"deepseek-r1:8b"
];
description = ''
Download these models using `ollama pull` after the service starts.
This creates a separate LaunchAgent that will pull the models in the background.
Models are only pulled if not already present.
Search for models at: <https://ollama.com/library>
'';
};
};
};
config = lib.mkIf cfg.enable {
# Ajouter le package ollama au PATH système
environment.systemPackages = [ cfg.package ];
# Service principal ollama
launchd.user.agents.ollama = {
path = [ config.environment.systemPath ];
serviceConfig = {
# Garder le service actif en permanence
KeepAlive = true;
# Démarrer au login
RunAtLoad = true;
# Commande pour lancer ollama
ProgramArguments = [ "${ollama}" "serve" ];
# Variables d'environnement
EnvironmentVariables = cfg.environmentVariables // {
# L'adresse et le port d'écoute
OLLAMA_HOST = "${cfg.host}:${toString cfg.port}";
# Le répertoire des modèles (on utilise toujours OLLAMA_MODELS)
OLLAMA_MODELS = cfg.models;
};
# Logs de sortie standard (laissé à null pour utiliser le système de logs macOS)
# Les logs seront visibles via: log show --predicate 'process == "ollama"' --info
StandardOutPath = null;
StandardErrorPath = null;
};
};
# Service optionnel pour télécharger les modèles
launchd.user.agents.ollama-model-loader = lib.mkIf (cfg.loadModels != [ ]) {
path = [ config.environment.systemPath ];
serviceConfig = {
# Ne garder actif que pendant le téléchargement
KeepAlive = false;
# Démarrer au login
RunAtLoad = true;
# Script pour télécharger les modèles
ProgramArguments = [
"${pkgs.bash}/bin/bash"
"-c"
''
# Attendre que le service ollama soit prêt
for i in {1..30}; do
if ${ollama} list &>/dev/null; then
break
fi
echo "Waiting for ollama service to be ready... ($i/30)"
sleep 2
done
# Télécharger chaque modèle s'il n'existe pas déjà
${lib.concatMapStringsSep "\n" (model: ''
if ! ${ollama} list | grep -q "${lib.escapeShellArg model}"; then
echo "Pulling model: ${model}"
${ollama} pull ${lib.escapeShellArg model}
else
echo "Model already exists: ${model}"
fi
'') cfg.loadModels}
''
];
StandardOutPath = null;
StandardErrorPath = null;
};
};
# Message informatif lors de l'activation
system.activationScripts.postActivation.text = lib.mkIf cfg.enable ''
echo "Ollama service configured to listen on ${cfg.host}:${toString cfg.port}"
echo "Models will be stored in: ${cfg.models}"
${lib.optionalString (cfg.loadModels != [ ]) ''
echo "The following models will be downloaded automatically:"
${lib.concatMapStringsSep "\n" (model: '' echo " - ${model}"'') cfg.loadModels}
''}
'';
};
meta.maintainers = with lib.maintainers; [ ];
}