1# Systemd services for lxd.
2
3{ config, lib, pkgs, ... }:
4
5let
6 cfg = config.virtualisation.lxd;
7 preseedFormat = pkgs.formats.yaml {};
8in {
9 meta = {
10 maintainers = lib.teams.lxc.members;
11 };
12
13 imports = [
14 (lib.mkRemovedOptionModule [ "virtualisation" "lxd" "zfsPackage" ] "Override zfs in an overlay instead to override it globally")
15 ];
16
17 options = {
18 virtualisation.lxd = {
19 enable = lib.mkOption {
20 type = lib.types.bool;
21 default = false;
22 description = ''
23 This option enables lxd, a daemon that manages
24 containers. Users in the "lxd" group can interact with
25 the daemon (e.g. to start or stop containers) using the
26 {command}`lxc` command line tool, among others.
27
28 Most of the time, you'll also want to start lxcfs, so
29 that containers can "see" the limits:
30 ```
31 virtualisation.lxc.lxcfs.enable = true;
32 ```
33 '';
34 };
35
36 package = lib.mkPackageOption pkgs "lxd-lts" { };
37
38 lxcPackage = lib.mkOption {
39 type = lib.types.package;
40 default = config.virtualisation.lxc.package;
41 defaultText = lib.literalExpression "config.virtualisation.lxc.package";
42 description = "The lxc package to use.";
43 };
44
45 zfsSupport = lib.mkOption {
46 type = lib.types.bool;
47 default = config.boot.zfs.enabled;
48 defaultText = lib.literalExpression "config.boot.zfs.enabled";
49 description = ''
50 Enables lxd to use zfs as a storage for containers.
51
52 This option is enabled by default if a zfs pool is configured
53 with nixos.
54 '';
55 };
56
57 recommendedSysctlSettings = lib.mkOption {
58 type = lib.types.bool;
59 default = false;
60 description = ''
61 Enables various settings to avoid common pitfalls when
62 running containers requiring many file operations.
63 Fixes errors like "Too many open files" or
64 "neighbour: ndisc_cache: neighbor table overflow!".
65 See https://lxd.readthedocs.io/en/latest/production-setup/
66 for details.
67 '';
68 };
69
70 preseed = lib.mkOption {
71 type = lib.types.nullOr (lib.types.submodule {
72 freeformType = preseedFormat.type;
73 });
74
75 default = null;
76
77 description = ''
78 Configuration for LXD preseed, see
79 <https://documentation.ubuntu.com/lxd/en/latest/howto/initialize/#initialize-preseed>
80 for supported values.
81
82 Changes to this will be re-applied to LXD which will overwrite existing entities or create missing ones,
83 but entities will *not* be removed by preseed.
84 '';
85
86 example = lib.literalExpression ''
87 {
88 networks = [
89 {
90 name = "lxdbr0";
91 type = "bridge";
92 config = {
93 "ipv4.address" = "10.0.100.1/24";
94 "ipv4.nat" = "true";
95 };
96 }
97 ];
98 profiles = [
99 {
100 name = "default";
101 devices = {
102 eth0 = {
103 name = "eth0";
104 network = "lxdbr0";
105 type = "nic";
106 };
107 root = {
108 path = "/";
109 pool = "default";
110 size = "35GiB";
111 type = "disk";
112 };
113 };
114 }
115 ];
116 storage_pools = [
117 {
118 name = "default";
119 driver = "dir";
120 config = {
121 source = "/var/lib/lxd/storage-pools/default";
122 };
123 }
124 ];
125 }
126 '';
127 };
128
129 startTimeout = lib.mkOption {
130 type = lib.types.int;
131 default = 600;
132 apply = toString;
133 description = ''
134 Time to wait (in seconds) for LXD to become ready to process requests.
135 If LXD does not reply within the configured time, lxd.service will be
136 considered failed and systemd will attempt to restart it.
137 '';
138 };
139
140 ui = {
141 enable = lib.mkEnableOption "(experimental) LXD UI";
142
143 package = lib.mkPackageOption pkgs [ "lxd-ui" ] { };
144 };
145 };
146 };
147
148 ###### implementation
149 config = lib.mkIf cfg.enable {
150 environment.systemPackages = [ cfg.package ];
151
152 # Note: the following options are also declared in virtualisation.lxc, but
153 # the latter can't be simply enabled to reuse the formers, because it
154 # does a bunch of unrelated things.
155 systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
156
157 security.apparmor = {
158 packages = [ cfg.lxcPackage ];
159 policies = {
160 "bin.lxc-start".profile = ''
161 include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
162 '';
163 "lxc-containers".profile = ''
164 include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
165 '';
166 };
167 };
168
169 # TODO: remove once LXD gets proper support for cgroupsv2
170 # (currently most of the e.g. CPU accounting stuff doesn't work)
171 systemd.enableUnifiedCgroupHierarchy = false;
172
173 systemd.sockets.lxd = {
174 description = "LXD UNIX socket";
175 wantedBy = [ "sockets.target" ];
176
177 socketConfig = {
178 ListenStream = "/var/lib/lxd/unix.socket";
179 SocketMode = "0660";
180 SocketGroup = "lxd";
181 Service = "lxd.service";
182 };
183 };
184
185 systemd.services.lxd = {
186 description = "LXD Container Management Daemon";
187
188 wantedBy = [ "multi-user.target" ];
189 after = [
190 "network-online.target"
191 (lib.mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
192 ];
193 requires = [
194 "network-online.target"
195 "lxd.socket"
196 (lib.mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
197 ];
198 documentation = [ "man:lxd(1)" ];
199
200 path = [ pkgs.util-linux ]
201 ++ lib.optional cfg.zfsSupport config.boot.zfs.package;
202
203 environment = lib.mkIf (cfg.ui.enable) {
204 "LXD_UI" = cfg.ui.package;
205 };
206
207 serviceConfig = {
208 ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd";
209 ExecStartPost = "${cfg.package}/bin/lxd waitready --timeout=${cfg.startTimeout}";
210 ExecStop = "${cfg.package}/bin/lxd shutdown";
211
212 KillMode = "process"; # when stopping, leave the containers alone
213 LimitMEMLOCK = "infinity";
214 LimitNOFILE = "1048576";
215 LimitNPROC = "infinity";
216 TasksMax = "infinity";
217
218 # By default, `lxd` loads configuration files from hard-coded
219 # `/usr/share/lxc/config` - since this is a no-go for us, we have to
220 # explicitly tell it where the actual configuration files are
221 Environment = lib.mkIf (config.virtualisation.lxc.lxcfs.enable)
222 "LXD_LXC_TEMPLATE_CONFIG=${pkgs.lxcfs}/share/lxc/config";
223 };
224
225 unitConfig.ConditionPathExists = "!/var/lib/incus/.migrated-from-lxd";
226 };
227
228 systemd.services.lxd-preseed = lib.mkIf (cfg.preseed != null) {
229 description = "LXD initialization with preseed file";
230 wantedBy = ["multi-user.target"];
231 requires = ["lxd.service"];
232 after = ["lxd.service"];
233
234 script = ''
235 ${pkgs.coreutils}/bin/cat ${preseedFormat.generate "lxd-preseed.yaml" cfg.preseed} | ${cfg.package}/bin/lxd init --preseed
236 '';
237
238 serviceConfig = {
239 Type = "oneshot";
240 };
241 };
242
243 users.groups.lxd = {};
244
245 users.users.root = {
246 subUidRanges = [ { startUid = 1000000; count = 65536; } ];
247 subGidRanges = [ { startGid = 1000000; count = 65536; } ];
248 };
249
250 boot.kernel.sysctl = lib.mkIf cfg.recommendedSysctlSettings {
251 "fs.inotify.max_queued_events" = 1048576;
252 "fs.inotify.max_user_instances" = 1048576;
253 "fs.inotify.max_user_watches" = 1048576;
254 "vm.max_map_count" = 262144; # TODO: Default vm.max_map_count has been increased system-wide
255 "kernel.dmesg_restrict" = 1;
256 "net.ipv4.neigh.default.gc_thresh3" = 8192;
257 "net.ipv6.neigh.default.gc_thresh3" = 8192;
258 "kernel.keys.maxkeys" = 2000;
259 };
260
261 boot.kernelModules = [ "veth" "xt_comment" "xt_CHECKSUM" "xt_MASQUERADE" "vhost_vsock" ]
262 ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
263 };
264}