|
| 1 | +{ |
| 2 | + config, |
| 3 | + lib, |
| 4 | + pkgs, |
| 5 | + ... |
| 6 | +}: |
| 7 | +let |
| 8 | + inherit (lib) |
| 9 | + attrNames |
| 10 | + concatStringsSep |
| 11 | + filterAttrs |
| 12 | + mapAttrs' |
| 13 | + mkIf |
| 14 | + mkOption |
| 15 | + nameValuePair |
| 16 | + optionalString |
| 17 | + types |
| 18 | + ; |
| 19 | + |
| 20 | + cfg = config.services.podman-darwin; |
| 21 | + |
| 22 | + machineDefinitionType = types.submodule { |
| 23 | + options = { |
| 24 | + cpus = mkOption { |
| 25 | + type = types.ints.positive; |
| 26 | + default = 4; |
| 27 | + example = 2; |
| 28 | + description = "Number of CPUs to allocate to the machine."; |
| 29 | + }; |
| 30 | + |
| 31 | + memory = mkOption { |
| 32 | + type = types.ints.positive; |
| 33 | + default = 2048; |
| 34 | + example = 8192; |
| 35 | + description = "Memory in MB to allocate to the machine."; |
| 36 | + }; |
| 37 | + |
| 38 | + diskSize = mkOption { |
| 39 | + type = types.ints.positive; |
| 40 | + default = 100; |
| 41 | + example = 200; |
| 42 | + description = "Disk size in GB for the machine."; |
| 43 | + }; |
| 44 | + |
| 45 | + rootful = mkOption { |
| 46 | + type = types.bool; |
| 47 | + default = false; |
| 48 | + description = '' |
| 49 | + Whether to run the machine in rootful mode. |
| 50 | + Rootful mode runs containers as root inside the VM. |
| 51 | + ''; |
| 52 | + }; |
| 53 | + |
| 54 | + autoStart = mkOption { |
| 55 | + type = types.bool; |
| 56 | + default = true; |
| 57 | + description = "Whether to automatically start this machine on login."; |
| 58 | + }; |
| 59 | + |
| 60 | + watchdogInterval = mkOption { |
| 61 | + type = types.ints.positive; |
| 62 | + default = 30; |
| 63 | + example = 60; |
| 64 | + description = "Interval in seconds to check if the machine is running."; |
| 65 | + }; |
| 66 | + }; |
| 67 | + }; |
| 68 | + |
| 69 | + mkWatchdogScript = |
| 70 | + name: machine: |
| 71 | + pkgs.writeShellScript "podman-machine-watchdog-${name}" '' |
| 72 | + set -euo pipefail |
| 73 | +
|
| 74 | + MACHINE_NAME="${name}" |
| 75 | + INTERVAL=${toString machine.watchdogInterval} |
| 76 | + PODMAN="${lib.getExe cfg.package}" |
| 77 | +
|
| 78 | + log() { |
| 79 | + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2 |
| 80 | + } |
| 81 | +
|
| 82 | + check_and_start() { |
| 83 | + local state |
| 84 | + state=$($PODMAN machine inspect "$MACHINE_NAME" --format '{{.State}}' 2>/dev/null || echo "unknown") |
| 85 | +
|
| 86 | + case "$state" in |
| 87 | + running) |
| 88 | + return 0 |
| 89 | + ;; |
| 90 | + stopped|unknown) |
| 91 | + log "Machine '$MACHINE_NAME' is starting..." |
| 92 | + if $PODMAN machine start "$MACHINE_NAME" 2>&1 | while IFS= read -r line; do log "$line"; done; then |
| 93 | + log "Machine '$MACHINE_NAME' started successfully" |
| 94 | + return 0 |
| 95 | + else |
| 96 | + log "Failed to start machine '$MACHINE_NAME'" |
| 97 | + return 1 |
| 98 | + fi |
| 99 | + ;; |
| 100 | + *) |
| 101 | + log "Machine '$MACHINE_NAME' is $state" |
| 102 | + return 1 |
| 103 | + ;; |
| 104 | + esac |
| 105 | + } |
| 106 | +
|
| 107 | + log "Starting watchdog for machine '$MACHINE_NAME' (check interval: ''${INTERVAL}s)" |
| 108 | +
|
| 109 | + while true; do |
| 110 | + check_and_start || true |
| 111 | + sleep "$INTERVAL" |
| 112 | + done |
| 113 | + ''; |
| 114 | +in |
| 115 | +{ |
| 116 | + meta.maintainers = with lib.maintainers; [ delafthi ]; |
| 117 | + |
| 118 | + options.services.podman-darwin = { |
| 119 | + enable = lib.mkEnableOption "Podman, a daemonless container engine"; |
| 120 | + |
| 121 | + package = lib.mkPackageOption pkgs "podman" { }; |
| 122 | + |
| 123 | + useDefaultMachine = mkOption { |
| 124 | + type = types.bool; |
| 125 | + default = true; |
| 126 | + description = '' |
| 127 | + Whether to create and use the default podman machine. |
| 128 | +
|
| 129 | + The default machine will be named `podman-machine-default` and configured with: |
| 130 | + - 4 CPUs |
| 131 | + - 2048 MB RAM |
| 132 | + - 100 GB disk |
| 133 | + - Rootless mode |
| 134 | + - Auto-start enabled |
| 135 | + ''; |
| 136 | + }; |
| 137 | + |
| 138 | + machines = mkOption { |
| 139 | + type = types.attrsOf machineDefinitionType; |
| 140 | + default = { }; |
| 141 | + description = "Declarative podman machine configurations."; |
| 142 | + example = lib.literalExpression '' |
| 143 | + { |
| 144 | + "dev-machine" = { |
| 145 | + cpus = 4; |
| 146 | + memory = 8192; |
| 147 | + diskSize = 100; |
| 148 | + rootful = false; |
| 149 | + autoStart = true; |
| 150 | + watchdogInterval = 30; |
| 151 | + }; |
| 152 | + "testing" = { |
| 153 | + cpus = 2; |
| 154 | + memory = 4096; |
| 155 | + diskSize = 50; |
| 156 | + rootful = false; |
| 157 | + autoStart = false; |
| 158 | + }; |
| 159 | + } |
| 160 | + ''; |
| 161 | + }; |
| 162 | + }; |
| 163 | + |
| 164 | + config = |
| 165 | + let |
| 166 | + podmanCmd = lib.getExe cfg.package; |
| 167 | + allMachines = |
| 168 | + cfg.machines |
| 169 | + // ( |
| 170 | + if cfg.useDefaultMachine then |
| 171 | + { |
| 172 | + "podman-machine-default" = { |
| 173 | + cpus = 4; |
| 174 | + memory = 2048; |
| 175 | + diskSize = 100; |
| 176 | + rootful = false; |
| 177 | + autoStart = true; |
| 178 | + watchdogInterval = 30; |
| 179 | + }; |
| 180 | + } |
| 181 | + else |
| 182 | + { } |
| 183 | + ); |
| 184 | + autoStartMachines = filterAttrs (_name: machine: machine.autoStart) allMachines; |
| 185 | + in |
| 186 | + mkIf (cfg.enable && pkgs.stdenv.isDarwin) { |
| 187 | + |
| 188 | + home.activation.podmanMachines = |
| 189 | + let |
| 190 | + mkMachineInitScript = name: machine: '' |
| 191 | + if ! ${podmanCmd} machine list --format '{{.Name}}' 2>/dev/null | sed 's/\*$//' | grep -q '^${name}$'; then |
| 192 | + echo "Creating podman machine: ${name}" |
| 193 | + ${podmanCmd} machine init ${name} \ |
| 194 | + --cpus ${toString machine.cpus} \ |
| 195 | + --memory ${toString machine.memory} \ |
| 196 | + --disk-size ${toString machine.diskSize} \ |
| 197 | + ${optionalString machine.rootful "--rootful"} |
| 198 | + fi |
| 199 | + ''; |
| 200 | + |
| 201 | + in |
| 202 | + lib.hm.dag.entryAfter [ "writeBoundary" ] '' |
| 203 | + PATH="${cfg.package}/bin:$PATH" |
| 204 | +
|
| 205 | + ${concatStringsSep "\n" (lib.mapAttrsToList mkMachineInitScript allMachines)} |
| 206 | +
|
| 207 | + MANAGED_MACHINES="${concatStringsSep " " (attrNames allMachines)}" |
| 208 | + EXISTING_MACHINES=$(${podmanCmd} machine list --format '{{.Name}}' 2>/dev/null | sed 's/\*$//' || echo "") |
| 209 | +
|
| 210 | + for machine in $EXISTING_MACHINES; do |
| 211 | + if [[ ! " $MANAGED_MACHINES " =~ " $machine " ]]; then |
| 212 | + echo "Removing unmanaged podman machine: $machine" |
| 213 | + ${podmanCmd} machine stop "$machine" 2>/dev/null || true |
| 214 | + ${podmanCmd} machine rm -f "$machine" |
| 215 | + fi |
| 216 | + done |
| 217 | + ''; |
| 218 | + |
| 219 | + home.packages = [ cfg.package ]; |
| 220 | + |
| 221 | + launchd.agents = mapAttrs' ( |
| 222 | + name: machine: |
| 223 | + nameValuePair "podman-machine-${name}" { |
| 224 | + enable = true; |
| 225 | + config = { |
| 226 | + ProgramArguments = [ "${mkWatchdogScript name machine}" ]; |
| 227 | + KeepAlive = { |
| 228 | + Crashed = true; |
| 229 | + SuccessfulExit = false; |
| 230 | + }; |
| 231 | + ProcessType = "Background"; |
| 232 | + RunAtLoad = true; |
| 233 | + }; |
| 234 | + } |
| 235 | + ) autoStartMachines; |
| 236 | + }; |
| 237 | +} |
0 commit comments