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..01d964fde --- /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-docker-compat = { + 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" + ]; +} diff --git a/release.nix b/release.nix index b3e2df7ed..4c8f807f5 100644 --- a/release.nix +++ b/release.nix @@ -97,6 +97,7 @@ in { tests.services-activate-system = makeTest ./tests/services-activate-system.nix; tests.services-activate-system-changed-label-prefix = makeTest ./tests/services-activate-system-changed-label-prefix.nix; tests.services-buildkite-agent = makeTest ./tests/services-buildkite-agent.nix; + tests.services-colima = makeTest ./tests/services-colima.nix; tests.services-github-runners = makeTest ./tests/services-github-runners.nix; tests.services-lorri = makeTest ./tests/services-lorri.nix; tests.services-nix-daemon = makeTest ./tests/services-nix-daemon.nix; diff --git a/tests/services-colima.nix b/tests/services-colima.nix new file mode 100644 index 000000000..0fb0c94ab --- /dev/null +++ b/tests/services-colima.nix @@ -0,0 +1,45 @@ +{ config, pkgs, ... }: + +let + colima = pkgs.runCommand "colima-0.0.0" { } "mkdir $out"; +in + +{ + services.colima = { + enable = true; + enableDockerCompatability = true; + package = colima; + groupMembers = [ "john" "jane" ]; + }; + + test = '' + echo "checking colima service in /Library/LaunchDaemons" >&2 + grep "org.nixos.colima" ${config.out}/Library/LaunchDaemons/org.nixos.colima.plist + grep "${colima}/bin/dnsmasq" ${config.out}/Library/LaunchDaemons/org.nixos.colima.plist + + echo "checking colima docker compat service in /Library/LaunchDaemons" >&2 + grep "org.nixos.colima-docker-compat" ${config.out}/Library/LaunchDaemons/org.nixos.colima-docker-compat.plist + + echo "checking colima config" >&2 + grep -F "--foreground" ${config.out}/Library/LaunchDaemons/org.nixos.colima.plist + grep -F "--runtime docker" ${config.out}/Library/LaunchDaemons/org.nixos.colima.plist + grep -F "--architectue host" ${config.out}/Library/LaunchDaemons/org.nixos.colima.plist + + echo "checking user creation in /activate" >&2 + grep "sysadminctl -addUser ${lib.escapeShellArgs [ "foo" "-UID" config.ids.uids.colima "-GID" config.ids.uids._colima "-fullName" "colima" "-home" "/var/lib/colima" "-shell" "/bin/bash" ]}" ${config.out}/activate + grep "createhomedir -cu ${lib.escapeShellArg "colima"}" ${config.out}/activate + grep "sysadminctl -addUser ${lib.escapeShellArgs [ "colima" "-UID" config.ids.uids.colima ]} .* ${lib.escapeShellArgs [ "-shell" "/bin/bash" ] }" ${config.out}/activate + grep "sysadminctl -addUser ${lib.escapeShellArg "colima"} .* ${lib.escapeShellArgs [ "-home" "/var/lib/colima" ]}" ${config.out}/activate + (! grep "dscl . -delete ${lib.escapeShellArg "/Users/colima"}" ${config.out}/activate) + (! grep "dscl . -delete ${lib.escapeShellArg "/Groups/_colima"}" ${config.out}/activate) + + echo "checking group creation in /activate" >&2 + grep "dscl . -create ${lib.escapeShellArg "/Groups/_colima"} PrimaryGroupID ${builtins.toString config.ids.gids._colima}" ${config.out}/activate + grep "dscl . -create ${lib.escapeShellArg "/Groups/_colima"} RealName ${lib.escapeShellArg "_colima"}" ${config.out}/activate + grep "dscl . -create ${lib.escapeShellArg "/Groups/_colima"} PrimaryGroupID ${builtins.toString config.ids.gids._colima}" ${config.out}/activate + (! grep "dscl . -delete ${lib.escapeShellArg "/Groups/_colima"}" ${config.out}/activate) + + echo "checking group membership in /activate" >&2 + grep "dscl . -create ${lib.escapeShellArg "/Groups/_colima"} GroupMembership ${lib.escapeShellArgs [ "john" "jane" ]}" ${config.out}/activate + ''; +}