diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 38168b312e41..348e6be5498c 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -133,6 +133,10 @@ - The Invoiceplane module now only accepts the structured `settings` option. `extraConfig` is now removed. +- The `ollama` services replaces its `sandbox` toggle with options to configure + a static `user` and `group`. The `writablePaths` option has been removed and + the models directory is now always exempt from sandboxing. + - Legacy package `stalwart-mail_0_6` was dropped, please note the [manual upgrade process](https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md) before changing the package to `pkgs.stalwart-mail` in diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index a0a32f1702bf..07031a082344 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -1,75 +1,76 @@ { config, lib, pkgs, ... }: let - inherit (lib) types mkBefore; + inherit (lib) literalExpression types mkBefore; cfg = config.services.ollama; ollamaPackage = cfg.package.override { inherit (cfg) acceleration; }; + + staticUser = cfg.user != null && cfg.group != null; in { imports = [ (lib.mkRemovedOptionModule [ "services" "ollama" "listenAddress" ] "Use `services.ollama.host` and `services.ollama.port` instead.") + (lib.mkRemovedOptionModule [ "services" "ollama" "sandbox" ] + "Set `services.ollama.user` and `services.ollama.group` instead.") + (lib.mkRemovedOptionModule [ "services" "ollama" "writablePaths" ] + "The `models` directory is now always writable. To make other directories writable, use `systemd.services.ollama.serviceConfig.ReadWritePaths`." ) ]; options = { services.ollama = { enable = lib.mkEnableOption "ollama server for local large language models"; package = lib.mkPackageOption pkgs "ollama" { }; + + user = lib.mkOption { + type = with types; nullOr str; + default = null; + example = "ollama"; + description = '' + User account under which to run ollama. Defaults to [`DynamicUser`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=) + when set to `null`. + + The user will automatically be created, if this option is set to a non-null value. + ''; + }; + + group = lib.mkOption { + type = with types; nullOr str; + default = cfg.user; + defaultText = literalExpression "config.services.ollama.user"; + example = "ollama"; + description = '' + Group under which to run ollama. Only used when `services.ollama.user` is set. + + The group will automatically be created, if this option is set to a non-null value. + ''; + }; + home = lib.mkOption { type = types.str; - default = "%S/ollama"; + default = "/var/lib/ollama"; example = "/home/foo"; description = '' The home directory that the ollama service is started in. - - See also `services.ollama.writablePaths` and `services.ollama.sandbox`. ''; }; + models = lib.mkOption { type = types.str; - default = "%S/ollama/models"; + default = "${cfg.home}/models"; + defaultText = "\${config.services.ollama.home}/models"; example = "/path/to/ollama/models"; description = '' The directory that the ollama service will read models from and download new models to. - - See also `services.ollama.writablePaths` and `services.ollama.sandbox` - if downloading models or other mutation of the filesystem is required. ''; }; - sandbox = lib.mkOption { - type = types.bool; - default = true; - example = false; - description = '' - Whether to enable systemd's sandboxing capabilities. - This sets [`DynamicUser`]( - https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser= - ), which runs the server as a unique user with read-only access to most of the filesystem. - - See also `services.ollama.writablePaths`. - ''; - }; - writablePaths = lib.mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "/home/foo" "/mnt/foo" ]; - description = '' - Paths that the server should have write access to. - - This sets [`ReadWritePaths`]( - https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#ReadWritePaths= - ), which allows specified paths to be written to through the default sandboxing. - - See also `services.ollama.sandbox`. - ''; - }; host = lib.mkOption { type = types.str; default = "127.0.0.1"; - example = "0.0.0.0"; + example = "[::]"; description = '' The host address which the ollama server HTTP interface listens to. ''; @@ -149,6 +150,15 @@ in }; config = lib.mkIf cfg.enable { + users = lib.mkIf staticUser { + users.${cfg.user} = { + inherit (cfg) home; + isSystemUser = true; + group = cfg.group; + }; + groups.${cfg.group} = {}; + }; + systemd.services.ollama = { description = "Server for local large language models"; wantedBy = [ "multi-user.target" ]; @@ -159,12 +169,63 @@ in OLLAMA_HOST = "${cfg.host}:${toString cfg.port}"; HSA_OVERRIDE_GFX_VERSION = lib.mkIf (cfg.rocmOverrideGfx != null) cfg.rocmOverrideGfx; }; - serviceConfig = { + serviceConfig = lib.optionalAttrs staticUser { + User = cfg.user; + Group = cfg.group; + } // { + DynamicUser = true; ExecStart = "${lib.getExe ollamaPackage} serve"; WorkingDirectory = cfg.home; StateDirectory = [ "ollama" ]; - DynamicUser = cfg.sandbox; - ReadWritePaths = cfg.writablePaths; + ReadWritePaths = [ + cfg.home + cfg.models + ]; + + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ + # CUDA + # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf + "char-nvidiactl" + "char-nvidia-caps" + "char-nvidia-uvm" + # ROCm + "char-drm" + "char-kfd" + ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = false; # hides acceleration devices + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "all"; # /proc/meminfo + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + SupplementaryGroups = [ "render" ]; # for rocm to access /dev/dri/renderD* devices + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service @resources" + "~@privileged" + ]; + UMask = "0077"; }; postStart = mkBefore '' set -x