Skip to content

Commit 98aea1f

Browse files
authored
feat: add podman integration across darwin and nixos (#79)
2 parents c1a45ba + ef2b612 commit 98aea1f

File tree

8 files changed

+258
-1
lines changed

8 files changed

+258
-1
lines changed

home/shared/dev/cli-tools/default.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
gum
1717
hyperfine
1818
outfieldr
19-
podman
2019
vhs
2120
];
2221
}

home/shared/dev/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
./editorconfig.nix
99
./ghostty.nix
1010
./helix.nix
11+
./podman.nix
1112
./tmux.nix
1213
./yazi.nix
1314
];

home/shared/dev/podman.nix

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{ pkgs, ... }:
2+
{
3+
services = {
4+
podman = {
5+
enable = if pkgs.stdenv.hostPlatform.isLinux then true else false;
6+
autoUpdate.enable = true;
7+
};
8+
podman-darwin = {
9+
enable = if pkgs.stdenv.hostPlatform.isDarwin then true else false;
10+
};
11+
};
12+
}

modules/home/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
imports = [
33
./programs
4+
./services
45
];
56
}

modules/home/services/default.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
imports = [
3+
./podman-darwin.nix
4+
];
5+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
}

system/nixos/settings/virtualisation.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
libvirtd.enable = true;
44
podman = {
55
enable = true;
6+
autoPrune.enable = true;
67
dockerCompat = true;
78
defaultNetwork.settings.dns_enabled = true;
89
};

system/shared/user.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"dialout"
2121
"libvirtd"
2222
"networkmanager"
23+
"podman"
2324
];
2425
initialPassword = "01234567";
2526
isNormalUser = true;

0 commit comments

Comments
 (0)