Skip to content

Commit ad8d7ec

Browse files
committed
podman: add darwin support with machine management
- restructure module from `podman-linux` to platform-agnostic `podman` - move linux-specific implementation to `modules/services/podman/linux/` - add darwin module with declarative machine management - implement launchd-based watchdog for auto-starting machines - maintains backward compatibility with existing linux functionality
1 parent 4ac96eb commit ad8d7ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+476
-38
lines changed

modules/services/podman/darwin.nix

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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;
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+
options.services.podman.darwin = {
117+
useDefaultMachine = mkOption {
118+
type = types.bool;
119+
default = true;
120+
description = ''
121+
Whether to create and use the default podman machine.
122+
123+
The default machine will be named `podman-machine-default` and configured with:
124+
- 4 CPUs
125+
- 2048 MB RAM
126+
- 100 GB disk
127+
- Rootless mode
128+
- Auto-start enabled
129+
'';
130+
};
131+
132+
machines = mkOption {
133+
type = types.attrsOf machineDefinitionType;
134+
default = { };
135+
description = "Declarative podman machine configurations.";
136+
example = lib.literalExpression ''
137+
{
138+
"dev-machine" = {
139+
cpus = 4;
140+
memory = 8192;
141+
diskSize = 100;
142+
rootful = false;
143+
autoStart = true;
144+
watchdogInterval = 30;
145+
};
146+
"testing" = {
147+
cpus = 2;
148+
memory = 4096;
149+
diskSize = 50;
150+
rootful = false;
151+
autoStart = false;
152+
};
153+
}
154+
'';
155+
};
156+
};
157+
158+
config =
159+
let
160+
podmanCmd = lib.getExe cfg.package;
161+
allMachines =
162+
cfg.darwin.machines
163+
// (
164+
if cfg.darwin.useDefaultMachine then
165+
{
166+
"podman-machine-default" = {
167+
cpus = 4;
168+
memory = 2048;
169+
diskSize = 100;
170+
rootful = false;
171+
autoStart = true;
172+
watchdogInterval = 30;
173+
};
174+
}
175+
else
176+
{ }
177+
);
178+
autoStartMachines = filterAttrs (_name: machine: machine.autoStart) allMachines;
179+
in
180+
mkIf (cfg.enable && pkgs.stdenv.isDarwin) {
181+
182+
home.activation.podmanMachines =
183+
let
184+
mkMachineInitScript = name: machine: ''
185+
if ! ${podmanCmd} machine list --format '{{.Name}}' 2>/dev/null | sed 's/\*$//' | grep -q '^${name}$'; then
186+
echo "Creating podman machine: ${name}"
187+
${podmanCmd} machine init ${name} \
188+
--cpus ${toString machine.cpus} \
189+
--memory ${toString machine.memory} \
190+
--disk-size ${toString machine.diskSize} \
191+
${optionalString machine.rootful "--rootful"}
192+
fi
193+
'';
194+
195+
in
196+
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
197+
PATH="${cfg.package}/bin:$PATH"
198+
199+
${concatStringsSep "\n" (lib.mapAttrsToList mkMachineInitScript allMachines)}
200+
201+
MANAGED_MACHINES="${concatStringsSep " " (attrNames allMachines)}"
202+
EXISTING_MACHINES=$(${podmanCmd} machine list --format '{{.Name}}' 2>/dev/null | sed 's/\*$//' || echo "")
203+
204+
for machine in $EXISTING_MACHINES; do
205+
if [[ ! " $MANAGED_MACHINES " =~ " $machine " ]]; then
206+
echo "Removing unmanaged podman machine: $machine"
207+
${podmanCmd} machine stop "$machine" 2>/dev/null || true
208+
${podmanCmd} machine rm -f "$machine"
209+
fi
210+
done
211+
'';
212+
213+
launchd.agents = mapAttrs' (
214+
name: machine:
215+
nameValuePair "podman-machine-${name}" {
216+
enable = true;
217+
config = {
218+
ProgramArguments = [ "${mkWatchdogScript name machine}" ];
219+
KeepAlive = {
220+
Crashed = true;
221+
SuccessfulExit = false;
222+
};
223+
ProcessType = "Background";
224+
RunAtLoad = true;
225+
};
226+
}
227+
) autoStartMachines;
228+
};
229+
}

modules/services/podman-linux/default.nix renamed to modules/services/podman/default.nix

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ in
1212
meta.maintainers = [
1313
lib.hm.maintainers.bamhm182
1414
lib.maintainers.n-hass
15+
lib.maintainers.delafthi
1516
];
1617

1718
imports = [
18-
./builds.nix
19-
./containers.nix
20-
./images.nix
21-
./install-quadlet.nix
22-
./networks.nix
23-
./services.nix
24-
./volumes.nix
19+
./linux/default.nix
20+
./darwin.nix
2521
];
2622

2723
options.services.podman = {
2824
enable = lib.mkEnableOption "Podman, a daemonless container engine";
2925

26+
package = lib.mkPackageOption pkgs "podman" { };
27+
3028
settings = {
3129
containers = lib.mkOption {
3230
type = toml.type;
@@ -94,28 +92,6 @@ in
9492
};
9593

9694
config = lib.mkIf cfg.enable {
97-
assertions = [ (lib.hm.assertions.assertPlatform "podman" pkgs lib.platforms.linux) ];
98-
9995
home.packages = [ cfg.package ];
100-
101-
services.podman.settings.storage = {
102-
storage.driver = lib.mkDefault "overlay";
103-
};
104-
105-
xdg.configFile = {
106-
"containers/policy.json".source =
107-
if cfg.settings.policy != { } then
108-
pkgs.writeText "policy.json" (builtins.toJSON cfg.settings.policy)
109-
else
110-
"${pkgs.skopeo.policy}/default-policy.json";
111-
"containers/registries.conf".source = toml.generate "registries.conf" {
112-
registries = lib.mapAttrs (n: v: { registries = v; }) cfg.settings.registries;
113-
};
114-
"containers/storage.conf".source = toml.generate "storage.conf" cfg.settings.storage;
115-
"containers/containers.conf".source = toml.generate "containers.conf" cfg.settings.containers;
116-
"containers/mounts.conf" = lib.mkIf (cfg.settings.mounts != [ ]) {
117-
text = builtins.concatStringsSep "\n" cfg.settings.mounts;
118-
};
119-
};
12096
};
12197
}

modules/services/podman-linux/builds.nix renamed to modules/services/podman/linux/builds.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ in
182182
let
183183
buildQuadlets = lib.mapAttrsToList toQuadletInternal cfg.builds;
184184
in
185-
lib.mkIf cfg.enable {
185+
lib.mkIf (cfg.enable && pkgs.stdenv.isLinux) {
186186
services.podman.internal.quadletDefinitions = buildQuadlets;
187187
assertions = lib.flatten (map (build: build.assertions) buildQuadlets);
188188

modules/services/podman-linux/containers.nix renamed to modules/services/podman/linux/containers.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ in
401401
let
402402
containerQuadlets = lib.mapAttrsToList toQuadletInternal cfg.containers;
403403
in
404-
lib.mkIf cfg.enable {
404+
lib.mkIf (cfg.enable && pkgs.stdenv.isLinux) {
405405
services.podman.internal.quadletDefinitions = containerQuadlets;
406406
assertions = lib.flatten (map (container: container.assertions) containerQuadlets);
407407

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
config,
3+
pkgs,
4+
lib,
5+
...
6+
}:
7+
let
8+
cfg = config.services.podman;
9+
toml = pkgs.formats.toml { };
10+
in
11+
{
12+
imports = [
13+
./options.nix
14+
./builds.nix
15+
./containers.nix
16+
./images.nix
17+
./install-quadlet.nix
18+
./networks.nix
19+
./services.nix
20+
./volumes.nix
21+
];
22+
23+
config = lib.mkIf (cfg.enable && pkgs.stdenv.isLinux) {
24+
home.packages = [ cfg.package ];
25+
26+
services.podman.settings.storage = {
27+
storage.driver = lib.mkDefault "overlay";
28+
};
29+
30+
xdg.configFile = {
31+
"containers/policy.json".source =
32+
if cfg.settings.policy != { } then
33+
pkgs.writeText "policy.json" (builtins.toJSON cfg.settings.policy)
34+
else
35+
"${pkgs.skopeo.policy}/default-policy.json";
36+
"containers/registries.conf".source = toml.generate "registries.conf" {
37+
registries = lib.mapAttrs (n: v: { registries = v; }) cfg.settings.registries;
38+
};
39+
"containers/storage.conf".source = toml.generate "storage.conf" cfg.settings.storage;
40+
"containers/containers.conf".source = toml.generate "containers.conf" cfg.settings.containers;
41+
"containers/mounts.conf" = lib.mkIf (cfg.settings.mounts != [ ]) {
42+
text = builtins.concatStringsSep "\n" cfg.settings.mounts;
43+
};
44+
};
45+
};
46+
}

modules/services/podman-linux/images.nix renamed to modules/services/podman/linux/images.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ in
165165
let
166166
imageQuadlets = lib.mapAttrsToList toQuadletInternal cfg.images;
167167
in
168-
lib.mkIf cfg.enable {
168+
lib.mkIf (cfg.enable && pkgs.stdenv.isLinux) {
169169
services.podman.internal.quadletDefinitions = imageQuadlets;
170170
assertions = lib.flatten (map (image: image.assertions) imageQuadlets);
171171
};

modules/services/podman-linux/install-quadlet.nix renamed to modules/services/podman/linux/install-quadlet.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ in
9999
{
100100
imports = [ ./options.nix ];
101101

102-
config = lib.mkIf cfg.enable {
102+
config = lib.mkIf (cfg.enable && pkgs.stdenv.isLinux) {
103103
home.file = generateSystemdFileLinks allUnitFiles;
104104

105105
# if the length of builtQuadlets is 0, then we don't need register the activation script

modules/services/podman-linux/networks.nix renamed to modules/services/podman/linux/networks.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ in
180180
let
181181
networkQuadlets = lib.mapAttrsToList toQuadletInternal cfg.networks;
182182
in
183-
lib.mkIf cfg.enable {
183+
lib.mkIf (cfg.enable && pkgs.stdenv.isLinux) {
184184
services.podman.internal.quadletDefinitions = networkQuadlets;
185185
assertions = lib.flatten (map (network: network.assertions) networkQuadlets);
186186

modules/services/podman-linux/options.nix renamed to modules/services/podman/linux/options.nix

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ in
5656
};
5757
};
5858

59-
package = lib.mkPackageOption pkgs "podman" { };
60-
6159
enableTypeChecks = lib.mkEnableOption "type checks for podman quadlets";
6260
};
6361
}

0 commit comments

Comments
 (0)