1{ lib, config, pkgs, ... }:
2
3with lib;
4
5let
6 templateSubmodule = { ... }: {
7 options = {
8 enable = mkEnableOption (lib.mdDoc "this template");
9
10 target = mkOption {
11 description = lib.mdDoc "Path in the container";
12 type = types.path;
13 };
14 template = mkOption {
15 description = lib.mdDoc ".tpl file for rendering the target";
16 type = types.path;
17 };
18 when = mkOption {
19 description = lib.mdDoc "Events which trigger a rewrite (create, copy)";
20 type = types.listOf (types.str);
21 };
22 properties = mkOption {
23 description = lib.mdDoc "Additional properties";
24 type = types.attrs;
25 default = {};
26 };
27 };
28 };
29
30 toYAML = name: data: pkgs.writeText name (generators.toYAML {} data);
31
32 cfg = config.virtualisation.lxc;
33 templates = if cfg.templates != {} then let
34 list = mapAttrsToList (name: value: { inherit name; } // value)
35 (filterAttrs (name: value: value.enable) cfg.templates);
36 in
37 {
38 files = map (tpl: {
39 source = tpl.template;
40 target = "/templates/${tpl.name}.tpl";
41 }) list;
42 properties = listToAttrs (map (tpl: nameValuePair tpl.target {
43 when = tpl.when;
44 template = "${tpl.name}.tpl";
45 properties = tpl.properties;
46 }) list);
47 }
48 else { files = []; properties = {}; };
49
50in
51{
52 imports = [
53 ../installer/cd-dvd/channel.nix
54 ../profiles/clone-config.nix
55 ../profiles/minimal.nix
56 ];
57
58 options = {
59 virtualisation.lxc = {
60 templates = mkOption {
61 description = lib.mdDoc "Templates for LXD";
62 type = types.attrsOf (types.submodule (templateSubmodule));
63 default = {};
64 example = literalExpression ''
65 {
66 # create /etc/hostname on container creation. also requires networking.hostName = "" to be set
67 "hostname" = {
68 enable = true;
69 target = "/etc/hostname";
70 template = builtins.toFile "hostname.tpl" "{{ container.name }}";
71 when = [ "create" ];
72 };
73 # create /etc/nixos/hostname.nix with a configuration for keeping the hostname applied
74 "hostname-nix" = {
75 enable = true;
76 target = "/etc/nixos/hostname.nix";
77 template = builtins.toFile "hostname-nix.tpl" "{ ... }: { networking.hostName = \"{{ container.name }}\"; }";
78 # copy keeps the file updated when the container is changed
79 when = [ "create" "copy" ];
80 };
81 # copy allow the user to specify a custom configuration.nix
82 "configuration-nix" = {
83 enable = true;
84 target = "/etc/nixos/configuration.nix";
85 template = builtins.toFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
86 when = [ "create" ];
87 };
88 };
89 '';
90 };
91
92 privilegedContainer = mkOption {
93 type = types.bool;
94 default = false;
95 description = lib.mdDoc ''
96 Whether this LXC container will be running as a privileged container or not. If set to `true` then
97 additional configuration will be applied to the `systemd` instance running within the container as
98 recommended by [distrobuilder](https://linuxcontainers.org/distrobuilder/introduction/).
99 '';
100 };
101 };
102 };
103
104 config = {
105 boot.isContainer = true;
106 boot.postBootCommands =
107 ''
108 # After booting, register the contents of the Nix store in the Nix
109 # database.
110 if [ -f /nix-path-registration ]; then
111 ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
112 rm /nix-path-registration
113 fi
114
115 # nixos-rebuild also requires a "system" profile
116 ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
117 '';
118
119 system.build.metadata = pkgs.callPackage ../../lib/make-system-tarball.nix {
120 contents = [
121 {
122 source = toYAML "metadata.yaml" {
123 architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.system)) 0;
124 creation_date = 1;
125 properties = {
126 description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
127 os = "${config.system.nixos.distroId}";
128 release = "${config.system.nixos.codeName}";
129 };
130 templates = templates.properties;
131 };
132 target = "/metadata.yaml";
133 }
134 ] ++ templates.files;
135 };
136
137 # TODO: build rootfs as squashfs for faster unpack
138 system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
139 extraArgs = "--owner=0";
140
141 storeContents = [
142 {
143 object = config.system.build.toplevel;
144 symlink = "none";
145 }
146 ];
147
148 contents = [
149 {
150 source = config.system.build.toplevel + "/init";
151 target = "/sbin/init";
152 }
153 # Technically this is not required for lxc, but having also make this configuration work with systemd-nspawn.
154 # Nixos will setup the same symlink after start.
155 {
156 source = config.system.build.toplevel + "/etc/os-release";
157 target = "/etc/os-release";
158 }
159 ];
160
161 extraCommands = "mkdir -p proc sys dev";
162 };
163
164 system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
165 #!${pkgs.runtimeShell}
166 ln -fs "$1/init" /sbin/init
167 '';
168
169 # Add the overrides from lxd distrobuilder
170 # https://github.com/lxc/distrobuilder/blob/05978d0d5a72718154f1525c7d043e090ba7c3e0/distrobuilder/main.go#L630
171 systemd.packages = [
172 (pkgs.writeTextFile {
173 name = "systemd-lxc-service-overrides";
174 destination = "/etc/systemd/system/service.d/zzz-lxc-service.conf";
175 text = ''
176 [Service]
177 ProcSubset=all
178 ProtectProc=default
179 ProtectControlGroups=no
180 ProtectKernelTunables=no
181 NoNewPrivileges=no
182 LoadCredential=
183 '' + optionalString cfg.privilegedContainer ''
184 # Additional settings for privileged containers
185 ProtectHome=no
186 ProtectSystem=no
187 PrivateDevices=no
188 PrivateTmp=no
189 ProtectKernelLogs=no
190 ProtectKernelModules=no
191 ReadWritePaths=
192 '';
193 })
194 ];
195
196 # Allow the user to login as root without password.
197 users.users.root.initialHashedPassword = mkOverride 150 "";
198
199 system.activationScripts.installInitScript = mkForce ''
200 ln -fs $systemConfig/init /sbin/init
201 '';
202
203 # Some more help text.
204 services.getty.helpLine =
205 ''
206
207 Log in as "root" with an empty password.
208 '';
209
210 # Containers should be light-weight, so start sshd on demand.
211 services.openssh.enable = mkDefault true;
212 services.openssh.startWhenNeeded = mkDefault true;
213
214 # As this is intended as a standalone image, undo some of the minimal profile stuff
215 environment.noXlibs = false;
216 documentation.enable = true;
217 documentation.nixos.enable = true;
218 services.logrotate.enable = true;
219 };
220}