diff --git a/modules/misc/news/2025/10/2025-10-15_10-44-58.nix b/modules/misc/news/2025/10/2025-10-15_10-44-58.nix new file mode 100644 index 00000000..94440572 --- /dev/null +++ b/modules/misc/news/2025/10/2025-10-15_10-44-58.nix @@ -0,0 +1,9 @@ +{ + time = "2025-10-14T23:44:58+00:00"; + condition = true; + message = '' + A new module is available: `services.colima` + + Colima is a tool for orchestrating container runtimes under a linux VM. + ''; +} diff --git a/modules/services/colima.nix b/modules/services/colima.nix new file mode 100644 index 00000000..da5f83f8 --- /dev/null +++ b/modules/services/colima.nix @@ -0,0 +1,266 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.colima; + yamlFormat = pkgs.formats.yaml { }; +in +{ + meta.maintainers = [ + lib.hm.maintainers.will-lol + ]; + + options.services.colima = { + enable = lib.mkEnableOption "Colima, a container runtime"; + + package = lib.mkPackageOption pkgs "colima" { }; + dockerPackage = lib.mkPackageOption pkgs "docker" { + extraDescription = "Used by colima to activate profiles. Not needed if no profile is set to isActive."; + }; + perlPackage = lib.mkPackageOption pkgs "perl" { + extraDescription = "Used by colima during image download for the shasum command."; + }; + sshPackage = lib.mkPackageOption pkgs "openssh" { + extraDescription = "Used by colima to manage the vm."; + }; + coreutilsPackage = lib.mkPackageOption pkgs "coreutils" { + extraDescription = "Used in various ways by colima."; + }; + curlPackage = lib.mkPackageOption pkgs "curl" { + extraDescription = "Used by colima to donwload images."; + }; + bashPackage = lib.mkPackageOption pkgs "bashNonInteractive" { + extraDescription = "Used by colima's internal scripts."; + }; + + profiles = lib.mkOption { + default = { + default = { + isActive = true; + isService = true; + }; + }; + description = '' + Profiles allow multiple colima configurations. The default profile is active by default. + If you have used colima before, you may need to delete existing configuration using `colima delete` or use a different profile. + + Note that removing a configured profile will not delete the corresponding Colima instance. + You will need to manually run `colima delete ` to remove the instance and release resources. + ''; + example = '' + { + default = { + isActive = true; + isService = true; + }; + rosetta = { + isService = true; + settings.rosetta = true; + }; + powerful = { + settings.cpu = 8; + }; + }; + ''; + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + options = { + name = lib.mkOption { + type = lib.types.str; + default = name; + readOnly = true; + description = "The profile's name."; + }; + + isService = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Whether this profile will run as a service. + ''; + }; + + isActive = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Whether to set this profile as: + - active docker context + - active kubernetes context + - active incus remote + Exactly one or zero profiles should have this option set. + ''; + }; + + logFile = lib.mkOption { + type = lib.types.path; + default = "${config.home.homeDirectory}/.local/state/colima-${name}.log"; + defaultText = lib.literalExpression "\${config.home.homeDirectory}/.local/state/colima-\${name}.log"; + description = "Combined stdout and stderr log file for the Colima service."; + }; + + settings = lib.mkOption { + inherit (yamlFormat) type; + default = { }; + description = "Colima configuration settings, see or run `colima template`."; + example = '' + { + cpu = 2; + disk = 100; + memory = 2; + arch = "host"; + runtime = "docker"; + hostname = null; + kubernetes = { + enabled = false; + version = "v1.33.3+k3s1"; + k3sArgs = [ "--disable=traefik" ]; + port = 0; + }; + autoActivate = true; + network = { + address = false; + mode = "shared"; + interface = "en0"; + preferredRoute = false; + dns = [ ]; + dnsHosts = { + "host.docker.internal" = "host.lima.internal"; + }; + hostAddresses = false; + }; + forwardAgent = false; + docker = { }; + vmType = "qemu"; + portForwarder = "ssh"; + rosetta = false; + binfmt = true; + nestedVirtualization = false; + mountType = "sshfs"; + mountInotify = false; + cpuType = "host"; + provision = [ ]; + sshConfig = true; + sshPort = 0; + mounts = [ ]; + diskImage = ""; + rootDisk = 20; + env = { }; + } + ''; + }; + }; + } + ) + ); + }; + }; + + config = lib.mkIf cfg.enable ({ + assertions = [ + { + assertion = (lib.count (p: p.isActive) (lib.attrValues cfg.profiles)) <= 1; + message = "Only one Colima profile can be active at a time."; + } + ]; + + home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + + home.file = lib.mkMerge ( + lib.mapAttrsToList (profileName: profile: { + ".colima/${profileName}/colima.yaml" = { + source = yamlFormat.generate "colima.yaml" profile.settings; + }; + }) (lib.filterAttrs (name: profile: profile.settings != { }) cfg.profiles) + ); + + programs.docker-cli.settings.currentContext = + let + activeProfile = lib.findFirst (p: p.isActive) null (lib.attrValues cfg.profiles); + in + lib.mkIf (activeProfile != null) ( + if activeProfile.name != "default" then "colima-${activeProfile.name}" else "colima" + ); + + launchd.agents = lib.mkIf pkgs.stdenv.isDarwin ( + lib.mapAttrs' ( + name: profile: + lib.nameValuePair "colima-${name}" { + enable = true; + config = { + ProgramArguments = [ + "${lib.getExe cfg.package}" + "start" + name + "-f" + "--activate=${if profile.isActive then "true" else "false"}" + "--save-config=false" + ]; + KeepAlive = true; + RunAtLoad = true; + EnvironmentVariables.PATH = lib.makeBinPath [ + cfg.package + cfg.perlPackage + cfg.dockerPackage + cfg.sshPackage + cfg.coreutilsPackage + cfg.curlPackage + cfg.bashPackage + pkgs.darwin.DarwinTools + ]; + StandardOutPath = profile.logFile; + StandardErrorPath = profile.logFile; + }; + } + ) (lib.filterAttrs (_: p: p.isService) cfg.profiles) + ); + + systemd.user.services = lib.mkIf pkgs.stdenv.isLinux ( + lib.mapAttrs' ( + name: profile: + lib.nameValuePair "colima-${name}" { + Unit = { + Description = "Colima container runtime (${name} profile)"; + After = [ "network-online.target" ]; + Wants = [ "network-online.target" ]; + }; + Service = { + ExecStart = '' + ${lib.getExe cfg.package} start ${name} \ + -f \ + --activate=${if profile.isActive then "true" else "false"} \ + --save-config=false + ''; + Restart = "always"; + RestartSec = 2; + Environment = [ + "PATH=${ + lib.makeBinPath [ + cfg.package + cfg.perlPackage + cfg.dockerPackage + cfg.sshPackage + cfg.coreutilsPackage + cfg.curlPackage + cfg.bashPackage + ] + }" + ]; + StandardOutput = "append:${profile.logFile}"; + StandardError = "append:${profile.logFile}"; + }; + Install = { + WantedBy = [ "default.target" ]; + }; + } + ) (lib.filterAttrs (_: p: p.isService) cfg.profiles) + ); + }); +} diff --git a/tests/modules/services/colima/darwin/colima-custom-settings.nix b/tests/modules/services/colima/darwin/colima-custom-settings.nix new file mode 100644 index 00000000..3c908c0a --- /dev/null +++ b/tests/modules/services/colima/darwin/colima-custom-settings.nix @@ -0,0 +1,39 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + services.colima = { + enable = true; + package = config.lib.test.mkStubPackage { + name = "colima"; + outPath = "@colima@"; + }; + dockerPackage = config.lib.test.mkStubPackage { + name = "docker"; + outPath = "@docker@"; + }; + perlPackage = config.lib.test.mkStubPackage { + name = "perl"; + outPath = "@perl@"; + }; + sshPackage = config.lib.test.mkStubPackage { + name = "openssh"; + outPath = "@openssh@"; + }; + + profiles.default.settings = { + cpu = 4; + memory = 8; + kubernetes.enabled = true; + }; + }; + + nmt.script = '' + assertFileExists home-files/.colima/default/colima.yaml + assertFileContent home-files/.colima/default/colima.yaml ${./custom-settings-expected.yaml} + ''; +} diff --git a/tests/modules/services/colima/darwin/colima-default-config.nix b/tests/modules/services/colima/darwin/colima-default-config.nix new file mode 100644 index 00000000..fbc29de8 --- /dev/null +++ b/tests/modules/services/colima/darwin/colima-default-config.nix @@ -0,0 +1,61 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + nixpkgs.overlays = [ + (self: super: { + darwin = super.darwin // { + DarwinTools = config.lib.test.mkStubPackage { + name = "DarwinTools"; + outPath = "@DarwinTools@"; + }; + }; + }) + ]; + + services.colima = { + enable = true; + package = config.lib.test.mkStubPackage { + name = "colima"; + outPath = "@colima@"; + }; + dockerPackage = config.lib.test.mkStubPackage { + name = "docker"; + outPath = "@docker@"; + }; + perlPackage = config.lib.test.mkStubPackage { + name = "perl"; + outPath = "@perl@"; + }; + sshPackage = config.lib.test.mkStubPackage { + name = "openssh"; + outPath = "@openssh@"; + }; + coreutilsPackage = config.lib.test.mkStubPackage { + name = "coreutils"; + outPath = "@coreutils@"; + }; + curlPackage = config.lib.test.mkStubPackage { + name = "curl"; + outPath = "@curl@"; + }; + bashPackage = config.lib.test.mkStubPackage { + name = "bashNonInteractive"; + outPath = "@bashNonInteractive@"; + }; + }; + + nmt.script = '' + assertPathNotExists home-files/.colima/default/colima.yaml + + serviceFile=LaunchAgents/org.nix-community.home.colima-default.plist + + assertFileExists "$serviceFile" + + assertFileContent "$serviceFile" ${./expected-agent.plist} + ''; +} diff --git a/tests/modules/services/colima/darwin/custom-settings-expected.yaml b/tests/modules/services/colima/darwin/custom-settings-expected.yaml new file mode 100644 index 00000000..4a166598 --- /dev/null +++ b/tests/modules/services/colima/darwin/custom-settings-expected.yaml @@ -0,0 +1,4 @@ +cpu: 4 +kubernetes: + enabled: true +memory: 8 diff --git a/tests/modules/services/colima/darwin/default.nix b/tests/modules/services/colima/darwin/default.nix new file mode 100644 index 00000000..1ce901bb --- /dev/null +++ b/tests/modules/services/colima/darwin/default.nix @@ -0,0 +1,4 @@ +{ + "colima-default-config" = ./colima-default-config.nix; + "colima-custom-settings" = ./colima-custom-settings.nix; +} diff --git a/tests/modules/services/colima/darwin/expected-agent.plist b/tests/modules/services/colima/darwin/expected-agent.plist new file mode 100644 index 00000000..e4e43477 --- /dev/null +++ b/tests/modules/services/colima/darwin/expected-agent.plist @@ -0,0 +1,30 @@ + + + + + EnvironmentVariables + + PATH + @colima@/bin:@perl@/bin:@docker@/bin:@openssh@/bin:@coreutils@/bin:@curl@/bin:@bashNonInteractive@/bin:@DarwinTools@/bin + + KeepAlive + + Label + org.nix-community.home.colima-default + ProgramArguments + + @colima@/bin/colima + start + default + -f + --activate=true + --save-config=false + + RunAtLoad + + StandardErrorPath + /home/hm-user/.local/state/colima-default.log + StandardOutPath + /home/hm-user/.local/state/colima-default.log + + \ No newline at end of file diff --git a/tests/modules/services/colima/default.nix b/tests/modules/services/colima/default.nix new file mode 100644 index 00000000..4b1206a2 --- /dev/null +++ b/tests/modules/services/colima/default.nix @@ -0,0 +1,3 @@ +{ lib, pkgs, ... }: +(lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin (import ./darwin/default.nix)) +// (lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux (import ./linux/default.nix)) diff --git a/tests/modules/services/colima/linux/colima-default-config.nix b/tests/modules/services/colima/linux/colima-default-config.nix new file mode 100644 index 00000000..940f997f --- /dev/null +++ b/tests/modules/services/colima/linux/colima-default-config.nix @@ -0,0 +1,50 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + services.colima = { + enable = true; + package = config.lib.test.mkStubPackage { + name = "colima"; + outPath = "@colima@"; + }; + dockerPackage = config.lib.test.mkStubPackage { + name = "docker"; + outPath = "@docker@"; + }; + perlPackage = config.lib.test.mkStubPackage { + name = "perl"; + outPath = "@perl@"; + }; + sshPackage = config.lib.test.mkStubPackage { + name = "openssh"; + outPath = "@openssh@"; + }; + coreutilsPackage = config.lib.test.mkStubPackage { + name = "coreutils"; + outPath = "@coreutils@"; + }; + curlPackage = config.lib.test.mkStubPackage { + name = "curl"; + outPath = "@curl@"; + }; + bashPackage = config.lib.test.mkStubPackage { + name = "bashNonInteractive"; + outPath = "@bashNonInteractive@"; + }; + }; + + nmt.script = '' + assertPathNotExists home-files/.colima/default/colima.yaml + + assertFileExists home-files/.config/systemd/user/colima-default.service + + assertFileContent \ + home-files/.config/systemd/user/colima-default.service \ + ${./expected-service.service} + ''; +} diff --git a/tests/modules/services/colima/linux/default.nix b/tests/modules/services/colima/linux/default.nix new file mode 100644 index 00000000..ee233831 --- /dev/null +++ b/tests/modules/services/colima/linux/default.nix @@ -0,0 +1,3 @@ +{ + "colima-default-config" = ./colima-default-config.nix; +} diff --git a/tests/modules/services/colima/linux/expected-service.service b/tests/modules/services/colima/linux/expected-service.service new file mode 100644 index 00000000..82dacb73 --- /dev/null +++ b/tests/modules/services/colima/linux/expected-service.service @@ -0,0 +1,19 @@ +[Install] +WantedBy=default.target + +[Service] +Environment=PATH=@colima@/bin:@perl@/bin:@docker@/bin:@openssh@/bin:@coreutils@/bin:@curl@/bin:@bashNonInteractive@/bin +ExecStart=@colima@/bin/colima start default \ + -f \ + --activate=true \ + --save-config=false + +Restart=always +RestartSec=2 +StandardError=append:/home/hm-user/.local/state/colima-default.log +StandardOutput=append:/home/hm-user/.local/state/colima-default.log + +[Unit] +After=network-online.target +Description=Colima container runtime (default profile) +Wants=network-online.target