From 23ae8a695fa651d4e79dad76cca0bc4ba1523a84 Mon Sep 17 00:00:00 2001 From: Bryan Honof Date: Thu, 16 Jan 2025 18:23:58 +0100 Subject: [PATCH] colima: init Allows the user to enable the Colima container runtime for macOS. Normally Colima is run inside the current user's their context, but I wanted to use Colima closer to how Docker works on Linux. Where the Docker daemon on Linux is run as the root user, and users have to either run the docker command with sudo, or add themselves to the docker group. Effectively enabling multi-user interaction on macOS. Just enabling the following config doesn't do a whole lot, as the user would have to log in as the colima user to interact with the colima VM. services.colima.enable = true; Instead, this module is meant to be used as follows, so that the user can use Colima as a Docker Desktop for macOS alternative. services.colima = { enable = true; enableDockerCompatability = true; }; This will set up everything for the Docker CLI to work with the Colima VM under the hood. Co-authored-by: Sam <30577766+Samasaur1@users.noreply.github.com> Refs: https://github.com/abiosoft/colima --- modules/misc/ids.nix | 2 + modules/module-list.nix | 1 + modules/services/colima/default.nix | 192 ++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 modules/services/colima/default.nix diff --git a/modules/misc/ids.nix b/modules/misc/ids.nix index 34b368593..81dd0acb1 100644 --- a/modules/misc/ids.nix +++ b/modules/misc/ids.nix @@ -39,11 +39,13 @@ in ids.uids = { nixbld = lib.mkDefault 350; _prometheus-node-exporter = 534; + colima = 400; }; ids.gids = { nixbld = lib.mkDefault (if config.system.stateVersion < 5 then 30000 else 350); _prometheus-node-exporter = 534; + _colima = 400; }; }; diff --git a/modules/module-list.nix b/modules/module-list.nix index 8b2215ba3..fcca17d82 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -61,6 +61,7 @@ ./services/buildkite-agents.nix ./services/chunkwm.nix ./services/cachix-agent.nix + ./services/colima ./services/dnsmasq.nix ./services/emacs.nix ./services/eternal-terminal.nix diff --git a/modules/services/colima/default.nix b/modules/services/colima/default.nix new file mode 100644 index 000000000..46914c58c --- /dev/null +++ b/modules/services/colima/default.nix @@ -0,0 +1,192 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; +let + cfg = config.services.colima; + user = config.users.users."colima"; + group = config.users.groups."_colima"; +in +{ + options.services.colima = { + enable = mkEnableOption "Colima, a macOS container runtime"; + + enableDockerCompatability = mkOption { + type = types.bool; + default = false; + description = '' + Create a symlink from Colima's socket to /var/run/docker.sock, and set + its permissions so that users part of the _colima group can use it. + ''; + }; + + package = mkPackageOption pkgs "colima" { }; + + stateDir = lib.mkOption { + type = types.path; + default = "/var/lib/colima"; + description = "State directory of the Colima process."; + }; + + logFile = mkOption { + type = types.path; + default = "/var/log/colima.log"; + description = "Combined stdout and stderr of the colima process. Set to /dev/null to disable."; + }; + + groupMembers = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + List of users that should be added to the _colima group. + Only has effect with enableDockerCompatability enabled. + ''; + }; + + runtime = mkOption { + type = types.enum [ + "docker" + "containerd" + "incus" + ]; + default = "docker"; + description = "The runtime to use with Colima."; + }; + + architectue = mkOption { + type = types.enum [ + "x86_64" + "aarch64" + "host" + ]; + default = "host"; + description = "The architecture to use for the Colima virtual machine."; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--vz-rosetta" ]; + description = "Extra commandline options to pass to the colima start command."; + }; + + vmType = mkOption { + type = types.enum [ + "qemu" + "vz" + ]; + default = "vz"; + description = "Virtual machine type to use with Colima."; + }; + }; + + config = mkMerge [ + (mkIf cfg.enableDockerCompatability { + assertions = [ + { + assertion = !cfg.enable; + message = "services.colima.enableDockerCompatability doesn't make sense without enabling services.colima.enable"; + } + ]; + + launchd.daemons.colima-create-docker-socket-and-set-permissions = { + script = '' + # Wait for the docker socket to be created. This is important when + # we enabled Colima and Docker compatability at the same time, for + # the first time. Colima takes a while creating the VM. + until [ -S ${cfg.stateDir}/.colima/default/docker.sock ] + do + sleep 5 + done + + chmod g+rw ${cfg.stateDir}/.colima/default/docker.sock + ln -sf ${cfg.stateDir}/.colima/default/docker.sock /var/run/docker.sock + ''; + + serviceConfig = { + RunAtLoad = true; + EnvironmentVariables.PATH = "/usr/bin:/bin:/usr/sbin:/sbin"; + }; + }; + + users.groups."_colima".members = cfg.groupMembers; + + environment.systemPackages = [ + pkgs.docker + ]; + }) + + (mkIf cfg.enable { + launchd.daemons.colima = { + script = + concatStringsSep " " [ + "exec" + (getExe cfg.package) + "start" + "--foreground" + "--runtime ${cfg.runtime}" + "--arch ${cfg.architectue}" + "--vm-type ${cfg.vmType}" + ] + + escapeShellArgs cfg.extraFlags; + + serviceConfig = { + KeepAlive = true; + RunAtLoad = true; + StandardErrorPath = cfg.logFile; + StandardOutPath = cfg.logFile; + GroupName = group.name; + UserName = user.name; + WorkingDirectory = cfg.stateDir; + EnvironmentVariables = { + PATH = "${pkgs.colima}/bin:${pkgs.docker}/bin:/usr/bin:/bin:/usr/sbin:/sbin"; + COLIMA_HOME = "${cfg.stateDir}/.colima"; + }; + }; + }; + + system.activationScripts.preActivation.text = '' + touch '${cfg.logFile}' + chown ${toString user.uid}:${toString user.gid} '${cfg.logFile}' + ''; + + users = { + knownGroups = [ + "colima" + "_colima" + ]; + knownUsers = [ + "colima" + "_colima" + ]; + }; + + users.users."colima" = { + uid = config.ids.uids.colima; + gid = config.ids.gids._colima; + home = cfg.stateDir; + # The username isn't allowed to have an underscore in the beginning of + # its name, otherwise the VM will fail to start with the following error + # > "[hostagent] identifier \"_colima\" must match ^[A-Za-z0-9]+(?:[._-](?:[A-Za-z0-9]+))*$: invalid argument" fields.level=fatal + name = "colima"; + createHome = true; + shell = "/bin/bash"; + description = "System user for Colima"; + }; + + users.groups."_colima" = { + gid = config.ids.gids._colima; + name = "_colima"; + description = "System group for Colima"; + }; + }) + ]; + + meta.maintainers = [ + lib.maintainers.bryanhonof or "bryanhonof" + ]; +}