From 809ea5c6bd44535e02e93ae4277c01e1c0d1b46d Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sat, 20 Jul 2024 19:42:43 +0200 Subject: [PATCH] nixos/ollama: replace flawed sandboxing option The ollama module in its default configuration relies on systemd's `DynamicUser=` feature for user allocation. In #305076 that allocation was made conditional and tied to the `sandboxing` option, that was intended to fix access to model directories outside the allocated state directory. However, by disabling sandboxing ollama would inadvertently run as root, given that `User=` and `Group=` are not required to be set. The correct way to grant access to other paths is to allocate static user and group, and grant permissions to the destination path to that allocation. We therefore replace the sandboxing option user and group options, that default to `null`, which means they default to `DynamicUser=`, but can be replaced with a statically allocated user/group, and thereby a stable uid/gid. Fixes: 552eb759 ("nixos/ollama: add options to bypass sandboxing") --- nixos/modules/services/misc/ollama.nix | 67 ++++++++++++++++++-------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index a0a32f1702bf..bfa59fb506c7 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -1,25 +1,54 @@ { 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.") ]; 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. @@ -27,9 +56,11 @@ 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. @@ -38,20 +69,6 @@ in 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 = [ ]; @@ -149,6 +166,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,11 +185,14 @@ 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; }; postStart = mkBefore ''