diff options
Diffstat (limited to 'nixos')
36 files changed, 796 insertions, 288 deletions
diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md index 0d5bc76a2aa57..5bcdf6e58eb17 100644 --- a/nixos/doc/manual/development/writing-nixos-tests.section.md +++ b/nixos/doc/manual/development/writing-nixos-tests.section.md @@ -273,12 +273,13 @@ The following methods are available on machine objects: `wait_for_open_port` -: Wait until a process is listening on the given TCP port (on - `localhost`, at least). +: Wait until a process is listening on the given TCP port and IP address + (default `localhost`). `wait_for_closed_port` -: Wait until nobody is listening on the given TCP port. +: Wait until nobody is listening on the given TCP port and IP address + (default `localhost`). `wait_for_x` diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml index dc921dad97499..308f7c6fb0f6d 100644 --- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml +++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml @@ -483,8 +483,8 @@ start_all() </term> <listitem> <para> - Wait until a process is listening on the given TCP port (on - <literal>localhost</literal>, at least). + Wait until a process is listening on the given TCP port and + IP address (default <literal>localhost</literal>). </para> </listitem> </varlistentry> @@ -494,7 +494,8 @@ start_all() </term> <listitem> <para> - Wait until nobody is listening on the given TCP port. + Wait until nobody is listening on the given TCP port and IP + address (default <literal>localhost</literal>). </para> </listitem> </varlistentry> diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml index 4837b29c585a1..2b4fb6fc92f23 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml @@ -39,6 +39,15 @@ </listitem> <listitem> <para> + <link xlink:href="https://github.com/alexivkin/CUPS-PDF-to-PDF">cups-pdf-to-pdf</link>, + a pdf-generating cups backend based on + <link xlink:href="https://www.cups-pdf.de/">cups-pdf</link>. + Available as + <link linkend="opt-services.printing.cups-pdf.enable">services.printing.cups-pdf</link>. + </para> + </listitem> + <listitem> + <para> <link xlink:href="https://github.com/junegunn/fzf">fzf</link>, a command line fuzzyfinder. Available as <link linkend="opt-programs.fzf.fuzzyCompletion">programs.fzf</link>. @@ -348,6 +357,14 @@ </listitem> <listitem> <para> + <literal>services.grafana</literal> listens only on localhost + by default again. This was changed to upstreams default of + <literal>0.0.0.0</literal> by accident in the freeform setting + conversion. + </para> + </listitem> + <listitem> + <para> A new <literal>virtualisation.rosetta</literal> module was added to allow running <literal>x86_64</literal> binaries through @@ -376,6 +393,14 @@ </listitem> <listitem> <para> + A new option <literal>recommendedBrotliSettings</literal> has + been added to <literal>services.nginx</literal>. Learn more + about compression in Brotli format + <link xlink:href="https://github.com/google/ngx_brotli/blob/master/README.md">here</link>. + </para> + </listitem> + <listitem> + <para> Resilio sync secret keys can now be provided using a secrets file at runtime, preventing these secrets from ending up in the Nix store. diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index b3354eec65fb5..1328f317dbfa2 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -18,6 +18,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [blesh](https://github.com/akinomyoga/ble.sh), a line editor written in pure bash. Available as [programs.bash.blesh](#opt-programs.bash.blesh.enable). +- [cups-pdf-to-pdf](https://github.com/alexivkin/CUPS-PDF-to-PDF), a pdf-generating cups backend based on [cups-pdf](https://www.cups-pdf.de/). Available as [services.printing.cups-pdf](#opt-services.printing.cups-pdf.enable). + - [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion). - [atuin](https://github.com/ellie/atuin), a sync server for shell history. Available as [services.atuin](#opt-services.atuin.enable). @@ -97,12 +99,16 @@ In addition to numerous new and upgraded packages, this release has the followin - `nixos/lib/make-disk-image.nix` can now mutate EFI variables, run user-provided EFI firmware or variable templates. This is now extensively documented in the NixOS manual. +- `services.grafana` listens only on localhost by default again. This was changed to upstreams default of `0.0.0.0` by accident in the freeform setting conversion. + - A new `virtualisation.rosetta` module was added to allow running `x86_64` binaries through [Rosetta](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) inside virtualised NixOS guests on Apple silicon. This feature works by default with the [UTM](https://docs.getutm.app/) virtualisation [package](https://search.nixos.org/packages?channel=unstable&show=utm&from=0&size=1&sort=relevance&type=packages&query=utm). - The new option `users.motdFile` allows configuring a Message Of The Day that can be updated dynamically. - Enabling global redirect in `services.nginx.virtualHosts` now allows one to add exceptions with the `locations` option. +- A new option `recommendedBrotliSettings` has been added to `services.nginx`. Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/blob/master/README.md). + - Resilio sync secret keys can now be provided using a secrets file at runtime, preventing these secrets from ending up in the Nix store. - The `firewall` and `nat` module now has a nftables based implementation. Enable `networking.nftables` to use it. diff --git a/nixos/lib/test-driver/test_driver/__init__.py b/nixos/lib/test-driver/test_driver/__init__.py index 61d91c9ed6545..db7e0ed33a892 100755 --- a/nixos/lib/test-driver/test_driver/__init__.py +++ b/nixos/lib/test-driver/test_driver/__init__.py @@ -41,11 +41,9 @@ def writeable_dir(arg: str) -> Path: """ path = Path(arg) if not path.is_dir(): - raise argparse.ArgumentTypeError("{0} is not a directory".format(path)) + raise argparse.ArgumentTypeError(f"{path} is not a directory") if not os.access(path, os.W_OK): - raise argparse.ArgumentTypeError( - "{0} is not a writeable directory".format(path) - ) + raise argparse.ArgumentTypeError(f"{path} is not a writeable directory") return path diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py index 6542a2e2f6938..de6abbb4679e2 100644 --- a/nixos/lib/test-driver/test_driver/driver.py +++ b/nixos/lib/test-driver/test_driver/driver.py @@ -19,15 +19,11 @@ def get_tmp_dir() -> Path: tmp_dir.mkdir(mode=0o700, exist_ok=True) if not tmp_dir.is_dir(): raise NotADirectoryError( - "The directory defined by TMPDIR, TEMP, TMP or CWD: {0} is not a directory".format( - tmp_dir - ) + f"The directory defined by TMPDIR, TEMP, TMP or CWD: {tmp_dir} is not a directory" ) if not os.access(tmp_dir, os.W_OK): raise PermissionError( - "The directory defined by TMPDIR, TEMP, TMP, or CWD: {0} is not writeable".format( - tmp_dir - ) + f"The directory defined by TMPDIR, TEMP, TMP, or CWD: {tmp_dir} is not writeable" ) return tmp_dir diff --git a/nixos/lib/test-driver/test_driver/logger.py b/nixos/lib/test-driver/test_driver/logger.py index 59ed295472315..e6182ff7c761d 100644 --- a/nixos/lib/test-driver/test_driver/logger.py +++ b/nixos/lib/test-driver/test_driver/logger.py @@ -36,7 +36,7 @@ class Logger: def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str: if "machine" in attributes: - return "{}: {}".format(attributes["machine"], message) + return f"{attributes['machine']}: {message}" return message def log_line(self, message: str, attributes: Dict[str, str]) -> None: @@ -62,9 +62,7 @@ class Logger: def log_serial(self, message: str, machine: str) -> None: self.enqueue({"msg": message, "machine": machine, "type": "serial"}) if self._print_serial_logs: - self._eprint( - Style.DIM + "{} # {}".format(machine, message) + Style.RESET_ALL - ) + self._eprint(Style.DIM + f"{machine} # {message}" + Style.RESET_ALL) def enqueue(self, item: Dict[str, str]) -> None: self.queue.put(item) @@ -97,7 +95,7 @@ class Logger: yield self.drain_log_queue() toc = time.time() - self.log("(finished: {}, in {:.2f} seconds)".format(message, toc - tic)) + self.log(f"(finished: {message}, in {toc - tic:.2f} seconds)") self.xml.endElement("nest") diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py index ffbc7c18e42b6..cbf3aa733c448 100644 --- a/nixos/lib/test-driver/test_driver/machine.py +++ b/nixos/lib/test-driver/test_driver/machine.py @@ -420,8 +420,8 @@ class Machine: def send_monitor_command(self, command: str) -> str: self.run_callbacks() - with self.nested("sending monitor command: {}".format(command)): - message = ("{}\n".format(command)).encode() + with self.nested(f"sending monitor command: {command}"): + message = f"{command}\n".encode() assert self.monitor is not None self.monitor.send(message) return self.wait_for_monitor_prompt() @@ -438,7 +438,7 @@ class Machine: info = self.get_unit_info(unit, user) state = info["ActiveState"] if state == "failed": - raise Exception('unit "{}" reached state "{}"'.format(unit, state)) + raise Exception(f'unit "{unit}" reached state "{state}"') if state == "inactive": status, jobs = self.systemctl("list-jobs --full 2>&1", user) @@ -446,27 +446,24 @@ class Machine: info = self.get_unit_info(unit, user) if info["ActiveState"] == state: raise Exception( - ( - 'unit "{}" is inactive and there ' "are no pending jobs" - ).format(unit) + f'unit "{unit}" is inactive and there are no pending jobs' ) return state == "active" with self.nested( - "waiting for unit {}{}".format( - unit, f" with user {user}" if user is not None else "" - ) + f"waiting for unit {unit}" + + (f" with user {user}" if user is not None else "") ): retry(check_active, timeout) def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]: - status, lines = self.systemctl('--no-pager show "{}"'.format(unit), user) + status, lines = self.systemctl(f'--no-pager show "{unit}"', user) if status != 0: raise Exception( - 'retrieving systemctl info for unit "{}" {} failed with exit code {}'.format( - unit, "" if user is None else 'under user "{}"'.format(user), status - ) + f'retrieving systemctl info for unit "{unit}"' + + ("" if user is None else f' under user "{user}"') + + f" failed with exit code {status}" ) line_pattern = re.compile(r"^([^=]+)=(.*)$") @@ -486,24 +483,22 @@ class Machine: if user is not None: q = q.replace("'", "\\'") return self.execute( - ( - "su -l {} --shell /bin/sh -c " - "$'XDG_RUNTIME_DIR=/run/user/`id -u` " - "systemctl --user {}'" - ).format(user, q) + f"su -l {user} --shell /bin/sh -c " + "$'XDG_RUNTIME_DIR=/run/user/`id -u` " + f"systemctl --user {q}'" ) - return self.execute("systemctl {}".format(q)) + return self.execute(f"systemctl {q}") def require_unit_state(self, unit: str, require_state: str = "active") -> None: with self.nested( - "checking if unit ‘{}’ has reached state '{}'".format(unit, require_state) + f"checking if unit ‘{unit}’ has reached state '{require_state}'" ): info = self.get_unit_info(unit) state = info["ActiveState"] if state != require_state: raise Exception( - "Expected unit ‘{}’ to to be in state ".format(unit) - + "'{}' but it is in state ‘{}’".format(require_state, state) + f"Expected unit ‘{unit}’ to to be in state " + f"'{require_state}' but it is in state ‘{state}’" ) def _next_newline_closed_block_from_shell(self) -> str: @@ -593,13 +588,11 @@ class Machine: """Execute each command and check that it succeeds.""" output = "" for command in commands: - with self.nested("must succeed: {}".format(command)): + with self.nested(f"must succeed: {command}"): (status, out) = self.execute(command, timeout=timeout) if status != 0: - self.log("output: {}".format(out)) - raise Exception( - "command `{}` failed (exit code {})".format(command, status) - ) + self.log(f"output: {out}") + raise Exception(f"command `{command}` failed (exit code {status})") output += out return output @@ -607,12 +600,10 @@ class Machine: """Execute each command and check that it fails.""" output = "" for command in commands: - with self.nested("must fail: {}".format(command)): + with self.nested(f"must fail: {command}"): (status, out) = self.execute(command, timeout=timeout) if status == 0: - raise Exception( - "command `{}` unexpectedly succeeded".format(command) - ) + raise Exception(f"command `{command}` unexpectedly succeeded") output += out return output @@ -627,7 +618,7 @@ class Machine: status, output = self.execute(command, timeout=timeout) return status == 0 - with self.nested("waiting for success: {}".format(command)): + with self.nested(f"waiting for success: {command}"): retry(check_success, timeout) return output @@ -642,7 +633,7 @@ class Machine: status, output = self.execute(command, timeout=timeout) return status != 0 - with self.nested("waiting for failure: {}".format(command)): + with self.nested(f"waiting for failure: {command}"): retry(check_failure) return output @@ -661,8 +652,8 @@ class Machine: def get_tty_text(self, tty: str) -> str: status, output = self.execute( - "fold -w$(stty -F /dev/tty{0} size | " - "awk '{{print $2}}') /dev/vcs{0}".format(tty) + f"fold -w$(stty -F /dev/tty{tty} size | " + f"awk '{{print $2}}') /dev/vcs{tty}" ) return output @@ -681,11 +672,11 @@ class Machine: ) return len(matcher.findall(text)) > 0 - with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)): + with self.nested(f"waiting for {regexp} to appear on tty {tty}"): retry(tty_matches) def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None: - with self.nested("sending keys ‘{}‘".format(chars)): + with self.nested(f"sending keys ‘{chars}‘"): for char in chars: self.send_key(char, delay) @@ -693,33 +684,33 @@ class Machine: """Waits until the file exists in machine's file system.""" def check_file(_: Any) -> bool: - status, _ = self.execute("test -e {}".format(filename)) + status, _ = self.execute(f"test -e {filename}") return status == 0 - with self.nested("waiting for file ‘{}‘".format(filename)): + with self.nested(f"waiting for file ‘{filename}‘"): retry(check_file) - def wait_for_open_port(self, port: int) -> None: + def wait_for_open_port(self, port: int, addr: str = "localhost") -> None: def port_is_open(_: Any) -> bool: - status, _ = self.execute("nc -z localhost {}".format(port)) + status, _ = self.execute(f"nc -z {addr} {port}") return status == 0 - with self.nested("waiting for TCP port {}".format(port)): + with self.nested(f"waiting for TCP port {port} on {addr}"): retry(port_is_open) - def wait_for_closed_port(self, port: int) -> None: + def wait_for_closed_port(self, port: int, addr: str = "localhost") -> None: def port_is_closed(_: Any) -> bool: - status, _ = self.execute("nc -z localhost {}".format(port)) + status, _ = self.execute(f"nc -z {addr} {port}") return status != 0 - with self.nested("waiting for TCP port {} to be closed".format(port)): + with self.nested(f"waiting for TCP port {port} on {addr} to be closed"): retry(port_is_closed) def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]: - return self.systemctl("start {}".format(jobname), user) + return self.systemctl(f"start {jobname}", user) def stop_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]: - return self.systemctl("stop {}".format(jobname), user) + return self.systemctl(f"stop {jobname}", user) def wait_for_job(self, jobname: str) -> None: self.wait_for_unit(jobname) @@ -739,21 +730,21 @@ class Machine: toc = time.time() self.log("connected to guest root shell") - self.log("(connecting took {:.2f} seconds)".format(toc - tic)) + self.log(f"(connecting took {toc - tic:.2f} seconds)") self.connected = True def screenshot(self, filename: str) -> None: word_pattern = re.compile(r"^\w+$") if word_pattern.match(filename): - filename = os.path.join(self.out_dir, "{}.png".format(filename)) - tmp = "{}.ppm".format(filename) + filename = os.path.join(self.out_dir, f"{filename}.png") + tmp = f"{filename}.ppm" with self.nested( - "making screenshot {}".format(filename), + f"making screenshot {filename}", {"image": os.path.basename(filename)}, ): - self.send_monitor_command("screendump {}".format(tmp)) - ret = subprocess.run("pnmtopng {} > {}".format(tmp, filename), shell=True) + self.send_monitor_command(f"screendump {tmp}") + ret = subprocess.run(f"pnmtopng {tmp} > {filename}", shell=True) os.unlink(tmp) if ret.returncode != 0: raise Exception("Cannot convert screenshot") @@ -815,7 +806,7 @@ class Machine: def dump_tty_contents(self, tty: str) -> None: """Debugging: Dump the contents of the TTY<n>""" - self.execute("fold -w 80 /dev/vcs{} | systemd-cat".format(tty)) + self.execute(f"fold -w 80 /dev/vcs{tty} | systemd-cat") def _get_screen_text_variants(self, model_ids: Iterable[int]) -> List[str]: with tempfile.TemporaryDirectory() as tmpdir: @@ -837,15 +828,15 @@ class Machine: return True if last: - self.log("Last OCR attempt failed. Text was: {}".format(variants)) + self.log(f"Last OCR attempt failed. Text was: {variants}") return False - with self.nested("waiting for {} to appear on screen".format(regex)): + with self.nested(f"waiting for {regex} to appear on screen"): retry(screen_matches) def wait_for_console_text(self, regex: str) -> None: - with self.nested("waiting for {} to appear on console".format(regex)): + with self.nested(f"waiting for {regex} to appear on console"): # Buffer the console output, this is needed # to match multiline regexes. console = io.StringIO() @@ -862,7 +853,7 @@ class Machine: def send_key(self, key: str, delay: Optional[float] = 0.01) -> None: key = CHAR_TO_KEY.get(key, key) - self.send_monitor_command("sendkey {}".format(key)) + self.send_monitor_command(f"sendkey {key}") if delay is not None: time.sleep(delay) @@ -921,7 +912,7 @@ class Machine: self.pid = self.process.pid self.booted = True - self.log("QEMU running (pid {})".format(self.pid)) + self.log(f"QEMU running (pid {self.pid})") def cleanup_statedir(self) -> None: shutil.rmtree(self.state_dir) @@ -975,7 +966,7 @@ class Machine: names = self.get_window_names() if last_try: self.log( - "Last chance to match {} on the window list,".format(regexp) + f"Last chance to match {regexp} on the window list," + " which currently contains: " + ", ".join(names) ) @@ -992,9 +983,7 @@ class Machine: """Forward a TCP port on the host to a TCP port on the guest. Useful during interactive testing. """ - self.send_monitor_command( - "hostfwd_add tcp::{}-:{}".format(host_port, guest_port) - ) + self.send_monitor_command(f"hostfwd_add tcp::{host_port}-:{guest_port}") def block(self) -> None: """Make the machine unreachable by shutting down eth1 (the multicast diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix index b3cdaf5568d4f..1067b21a22b07 100644 --- a/nixos/modules/misc/version.nix +++ b/nixos/modules/misc/version.nix @@ -89,6 +89,12 @@ in stateVersion = mkOption { type = types.str; + # TODO Remove this and drop the default of the option so people are forced to set it. + # Doing this also means fixing the comment in nixos/modules/testing/test-instrumentation.nix + apply = v: + lib.warnIf (options.system.stateVersion.highestPrio == (lib.mkOptionDefault { }).priority) + "system.stateVersion is not set, defaulting to ${v}. Read why this matters on https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion." + v; default = cfg.release; defaultText = literalExpression "config.${opt.release}"; description = lib.mdDoc '' @@ -149,14 +155,6 @@ in "os-release".text = attrsToText osReleaseContents; }; - # We have to use `warnings` because when warning in the default of the option - # the warning would also be shown when building the manual since the manual - # has to evaluate the default. - # - # TODO Remove this and drop the default of the option so people are forced to set it. - # Doing this also means fixing the comment in nixos/modules/testing/test-instrumentation.nix - warnings = lib.optional (options.system.stateVersion.highestPrio == (lib.mkOptionDefault { }).priority) - "system.stateVersion is not set, defaulting to ${config.system.stateVersion}. Read why this matters on https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion."; }; # uses version info nixpkgs, which requires a full nixpkgs path diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index a1e7cf01882e3..dd0921243a7c4 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1028,6 +1028,7 @@ ./services/networking/znc/default.nix ./services/printing/cupsd.nix ./services/printing/ipp-usb.nix + ./services/printing/cups-pdf.nix ./services/scheduling/atd.nix ./services/scheduling/cron.nix ./services/scheduling/fcron.nix @@ -1310,6 +1311,7 @@ ./tasks/filesystems/btrfs.nix ./tasks/filesystems/cifs.nix ./tasks/filesystems/ecryptfs.nix + ./tasks/filesystems/envfs.nix ./tasks/filesystems/exfat.nix ./tasks/filesystems/ext.nix ./tasks/filesystems/f2fs.nix diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix index 15fad007bd3ed..a981814730a17 100644 --- a/nixos/modules/profiles/macos-builder.nix +++ b/nixos/modules/profiles/macos-builder.nix @@ -11,6 +11,17 @@ in { imports = [ ../virtualisation/qemu-vm.nix + + # Avoid a dependency on stateVersion + { + disabledModules = [ + ../virtualisation/nixos-containers.nix + ../services/x11/desktop-managers/xterm.nix + ]; + config = { + }; + options.boot.isContainer = lib.mkOption { default = false; internal = true; }; + } ]; # The builder is not intended to be used interactively @@ -97,7 +108,14 @@ in # To prevent gratuitous rebuilds on each change to Nixpkgs nixos.revision = null; - stateVersion = "22.05"; + stateVersion = lib.mkDefault (throw '' + The macOS linux builder should not need a stateVersion to be set, but a module + has accessed stateVersion nonetheless. + Please inspect the trace of the following command to figure out which module + has a dependency on stateVersion. + + nix-instantiate --attr darwin.builder --show-trace + ''); }; users.users."${user}"= { diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix index ae8e1dd8463bf..c5fc09dcea028 100644 --- a/nixos/modules/services/backup/borgbackup.nix +++ b/nixos/modules/services/backup/borgbackup.nix @@ -150,8 +150,9 @@ let # Ensure that the home directory already exists # We can't assert createHome == true because that's not the case for root cd "${config.users.users.${cfg.user}.home}" - ${install} -d .config/borg - ${install} -d .cache/borg + # Create each directory separately to prevent root owned parent dirs + ${install} -d .config .config/borg + ${install} -d .cache .cache/borg '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) '' ${install} -d ${escapeShellArg cfg.repo} '')); diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix index cb604db91dc36..df339e4ba011f 100644 --- a/nixos/modules/services/hardware/supergfxd.nix +++ b/nixos/modules/services/hardware/supergfxd.nix @@ -23,7 +23,10 @@ in config = lib.mkIf cfg.enable { environment.systemPackages = [ pkgs.supergfxctl ]; - environment.etc."supergfxd.conf" = lib.mkIf (cfg.settings != null) { source = json.generate "supergfxd.conf" cfg.settings; }; + environment.etc."supergfxd.conf" = lib.mkIf (cfg.settings != null) { + source = json.generate "supergfxd.conf" cfg.settings; + mode = "0644"; + }; services.dbus.enable = true; diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix index cd0da4fc50987..a9504acee3511 100644 --- a/nixos/modules/services/mail/exim.nix +++ b/nixos/modules/services/mail/exim.nix @@ -116,8 +116,9 @@ in wantedBy = [ "multi-user.target" ]; restartTriggers = [ config.environment.etc."exim.conf".source ]; serviceConfig = { - ExecStart = "${cfg.package}/bin/exim -bdf -q${cfg.queueRunnerInterval}"; - ExecReload = "${coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = "+${cfg.package}/bin/exim -bdf -q${cfg.queueRunnerInterval}"; + ExecReload = "+${coreutils}/bin/kill -HUP $MAINPID"; + User = cfg.user; }; preStart = '' if ! test -d ${cfg.spoolDir}; then diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix index b9b581acb34a5..3087d879b9d2b 100644 --- a/nixos/modules/services/matrix/synapse.nix +++ b/nixos/modules/services/matrix/synapse.nix @@ -507,6 +507,12 @@ in { sqlite3 = null; psycopg2 = "matrix-synapse"; }.${cfg.settings.database.name}; + defaultText = lib.literalExpression '' + { + sqlite3 = null; + psycopg2 = "matrix-synapse"; + }.''${cfg.settings.database.name}; + ''; description = lib.mdDoc '' Username to connect with psycopg2, set to null when using sqlite3. diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index b13706f641cf0..10db7cdfb33cc 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -819,7 +819,7 @@ in optionals (pkgs.hostPlatform ? gcc.arch) ( # a builder can run code for `gcc.arch` and inferior architectures [ "gccarch-${pkgs.hostPlatform.gcc.arch}" ] ++ - map (x: "gccarch-${x}") systems.architectures.inferiors.${pkgs.hostPlatform.gcc.arch} + map (x: "gccarch-${x}") (systems.architectures.inferiors.${pkgs.hostPlatform.gcc.arch} or []) ) ); } diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 9a9a0ab755325..9cce4c71d964a 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -364,9 +364,15 @@ in { }; http_addr = mkOption { - description = lib.mdDoc "Listening address."; - default = ""; type = types.str; + default = "127.0.0.1"; + description = lib.mdDoc '' + Listening address. + + ::: {.note} + This setting intentionally varies from upstream's default to be a bit more secure by default. + ::: + ''; }; http_port = mkOption { diff --git a/nixos/modules/services/monitoring/uptime-kuma.nix b/nixos/modules/services/monitoring/uptime-kuma.nix index b6dc993e6a050..8e6c825b35eac 100644 --- a/nixos/modules/services/monitoring/uptime-kuma.nix +++ b/nixos/modules/services/monitoring/uptime-kuma.nix @@ -28,7 +28,7 @@ in }; description = lib.mdDoc '' Additional configuration for Uptime Kuma, see - <https://github.com/louislam/uptime-kuma/wiki/Environment-Variables"> + <https://github.com/louislam/uptime-kuma/wiki/Environment-Variables> for supported values. ''; }; diff --git a/nixos/modules/services/networking/openconnect.nix b/nixos/modules/services/networking/openconnect.nix index 469f0a3bc3bb6..4676b1733af68 100644 --- a/nixos/modules/services/networking/openconnect.nix +++ b/nixos/modules/services/networking/openconnect.nix @@ -32,6 +32,7 @@ let description = lib.mdDoc "Username to authenticate with."; example = "example-user"; type = types.nullOr types.str; + default = null; }; # Note: It does not make sense to provide a way to declaratively @@ -108,7 +109,7 @@ let ExecStart = "${openconnect}/bin/openconnect --config=${ generateConfig name icfg } ${icfg.gateway}"; - StandardInput = "file:${icfg.passwordFile}"; + StandardInput = lib.mkIf (icfg.passwordFile != null) "file:${icfg.passwordFile}"; ProtectHome = true; }; diff --git a/nixos/modules/services/networking/powerdns.nix b/nixos/modules/services/networking/powerdns.nix index 6aa5928d63707..850a128cf1a46 100644 --- a/nixos/modules/services/networking/powerdns.nix +++ b/nixos/modules/services/networking/powerdns.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.powerdns; configDir = pkgs.writeTextDir "pdns.conf" "${cfg.extraConfig}"; + finalConfigDir = if cfg.secretFile == null then configDir else "/run/pdns"; in { options = { services.powerdns = { @@ -19,6 +20,19 @@ in { for details on supported values. ''; }; + + secretFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/powerdns.env"; + description = lib.mdDoc '' + Environment variables from this file will be interpolated into the + final config file using envsubst with this syntax: `$ENVIRONMENT` + or `''${VARIABLE}`. + The file should contain lines formatted as `SECRET_VAR=SECRET_VALUE`. + This is useful to avoid putting secrets into the nix store. + ''; + }; }; }; @@ -31,7 +45,13 @@ in { after = [ "network.target" "mysql.service" "postgresql.service" "openldap.service" ]; serviceConfig = { - ExecStart = [ "" "${pkgs.pdns}/bin/pdns_server --config-dir=${configDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ]; + EnvironmentFile = lib.optional (cfg.secretFile != null) cfg.secretFile; + ExecStartPre = lib.optional (cfg.secretFile != null) + (pkgs.writeShellScript "pdns-pre-start" '' + umask 077 + ${pkgs.envsubst}/bin/envsubst -i "${configDir}/pdns.conf" > ${finalConfigDir}/pdns.conf + ''); + ExecStart = [ "" "${pkgs.pdns}/bin/pdns_server --config-dir=${finalConfigDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ]; }; }; diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix index 09b23a60a4afc..7db83e6a584ba 100644 --- a/nixos/modules/services/networking/tinc.nix +++ b/nixos/modules/services/networking/tinc.nix @@ -349,91 +349,94 @@ in ###### implementation - config = mkIf (cfg.networks != { }) { - - environment.etc = foldr (a: b: a // b) { } - (flip mapAttrsToList cfg.networks (network: data: - flip mapAttrs' data.hosts (host: text: nameValuePair - ("tinc/${network}/hosts/${host}") - ({ mode = "0644"; user = "tinc.${network}"; inherit text; }) - ) // { - "tinc/${network}/tinc.conf" = { - mode = "0444"; - text = '' - ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)} - ${data.extraConfig} - ''; + config = mkIf (cfg.networks != { }) ( + let + etcConfig = foldr (a: b: a // b) { } + (flip mapAttrsToList cfg.networks (network: data: + flip mapAttrs' data.hosts (host: text: nameValuePair + ("tinc/${network}/hosts/${host}") + ({ mode = "0644"; user = "tinc.${network}"; inherit text; }) + ) // { + "tinc/${network}/tinc.conf" = { + mode = "0444"; + text = '' + ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)} + ${data.extraConfig} + ''; + }; + } + )); + in { + environment.etc = etcConfig; + + systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair + ("tinc.${network}") + (let version = getVersion data.package; in { + description = "Tinc Daemon - ${network}"; + wantedBy = [ "multi-user.target" ]; + path = [ data.package ]; + reloadTriggers = mkIf (versionAtLeast version "1.1pre") [ (builtins.toJSON etcConfig) ]; + restartTriggers = mkIf (versionOlder version "1.1pre") [ (builtins.toJSON etcConfig) ]; + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = "3"; + ExecReload = mkIf (versionAtLeast version "1.1pre") "${data.package}/bin/tinc -n ${network} reload"; + ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}"; }; - } - )); - - systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair - ("tinc.${network}") - ({ - description = "Tinc Daemon - ${network}"; - wantedBy = [ "multi-user.target" ]; - path = [ data.package ]; - restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ]; - serviceConfig = { - Type = "simple"; - Restart = "always"; - RestartSec = "3"; - ExecReload = mkIf (versionAtLeast (getVersion data.package) "1.1pre") "${data.package}/bin/tinc -n ${network} reload"; - ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}"; + preStart = '' + mkdir -p /etc/tinc/${network}/hosts + chown tinc.${network} /etc/tinc/${network}/hosts + mkdir -p /etc/tinc/${network}/invitations + chown tinc.${network} /etc/tinc/${network}/invitations + + # Determine how we should generate our keys + if type tinc >/dev/null 2>&1; then + # Tinc 1.1+ uses the tinc helper application for key generation + ${if data.ed25519PrivateKeyFile != null then " # ed25519 Keyfile managed by nix" else '' + # Prefer ED25519 keys (only in 1.1+) + [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys + ''} + ${if data.rsaPrivateKeyFile != null then " # RSA Keyfile managed by nix" else '' + [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096 + ''} + # In case there isn't anything to do + true + else + # Tinc 1.0 uses the tincd application + [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096 + fi + ''; + }) + ); + + environment.systemPackages = let + cli-wrappers = pkgs.stdenv.mkDerivation { + name = "tinc-cli-wrappers"; + nativeBuildInputs = [ pkgs.makeWrapper ]; + buildCommand = '' + mkdir -p $out/bin + ${concatStringsSep "\n" (mapAttrsToList (network: data: + optionalString (versionAtLeast data.package.version "1.1pre") '' + makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \ + --add-flags "--pidfile=/run/tinc.${network}.pid" \ + --add-flags "--config=/etc/tinc/${network}" + '') cfg.networks)} + ''; }; - preStart = '' - mkdir -p /etc/tinc/${network}/hosts - chown tinc.${network} /etc/tinc/${network}/hosts - mkdir -p /etc/tinc/${network}/invitations - chown tinc.${network} /etc/tinc/${network}/invitations - - # Determine how we should generate our keys - if type tinc >/dev/null 2>&1; then - # Tinc 1.1+ uses the tinc helper application for key generation - ${if data.ed25519PrivateKeyFile != null then " # ed25519 Keyfile managed by nix" else '' - # Prefer ED25519 keys (only in 1.1+) - [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys - ''} - ${if data.rsaPrivateKeyFile != null then " # RSA Keyfile managed by nix" else '' - [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096 - ''} - # In case there isn't anything to do - true - else - # Tinc 1.0 uses the tincd application - [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096 - fi - ''; - }) - ); - - environment.systemPackages = let - cli-wrappers = pkgs.stdenv.mkDerivation { - name = "tinc-cli-wrappers"; - nativeBuildInputs = [ pkgs.makeWrapper ]; - buildCommand = '' - mkdir -p $out/bin - ${concatStringsSep "\n" (mapAttrsToList (network: data: - optionalString (versionAtLeast data.package.version "1.1pre") '' - makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \ - --add-flags "--pidfile=/run/tinc.${network}.pid" \ - --add-flags "--config=/etc/tinc/${network}" - '') cfg.networks)} - ''; - }; - in [ cli-wrappers ]; - - users.users = flip mapAttrs' cfg.networks (network: _: - nameValuePair ("tinc.${network}") ({ - description = "Tinc daemon user for ${network}"; - isSystemUser = true; - group = "tinc.${network}"; - }) - ); - users.groups = flip mapAttrs' cfg.networks (network: _: - nameValuePair "tinc.${network}" {} - ); - }; + in [ cli-wrappers ]; + + users.users = flip mapAttrs' cfg.networks (network: _: + nameValuePair ("tinc.${network}") ({ + description = "Tinc daemon user for ${network}"; + isSystemUser = true; + group = "tinc.${network}"; + }) + ); + users.groups = flip mapAttrs' cfg.networks (network: _: + nameValuePair "tinc.${network}" {} + ); + }); meta.maintainers = with maintainers; [ minijackson mic92 ]; } diff --git a/nixos/modules/services/printing/cups-pdf.nix b/nixos/modules/services/printing/cups-pdf.nix new file mode 100644 index 0000000000000..07f24367132f5 --- /dev/null +++ b/nixos/modules/services/printing/cups-pdf.nix @@ -0,0 +1,185 @@ +{ config, lib, pkgs, ... }: + +let + + # cups calls its backends as user `lp` (which is good!), + # but cups-pdf wants to be called as `root`, so it can change ownership of files. + # We add a suid wrapper and a wrapper script to trick cups into calling the suid wrapper. + # Note that a symlink to the suid wrapper alone wouldn't suffice, cups would complain + # > File "/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-cups-progs/lib/cups/backend/cups-pdf" has insecure permissions (0104554/uid=0/gid=20) + + # wrapper script that redirects calls to the suid wrapper + cups-pdf-wrapper = pkgs.writeTextFile { + name = "${pkgs.cups-pdf-to-pdf.name}-wrapper.sh"; + executable = true; + destination = "/lib/cups/backend/cups-pdf"; + checkPhase = '' + ${pkgs.stdenv.shellDryRun} "$target" + ${lib.getExe pkgs.shellcheck} "$target" + ''; + text = '' + #! ${pkgs.runtimeShell} + exec "${config.security.wrapperDir}/cups-pdf" "$@" + ''; + }; + + # wrapped cups-pdf package that uses the suid wrapper + cups-pdf-wrapped = pkgs.buildEnv { + name = "${pkgs.cups-pdf-to-pdf.name}-wrapped"; + # using the wrapper as first path ensures it is used + paths = [ cups-pdf-wrapper pkgs.cups-pdf-to-pdf ]; + ignoreCollisions = true; + }; + + instanceSettings = name: { + freeformType = with lib.types; nullOr (oneOf [ int str path package ]); + # override defaults: + # inject instance name into paths, + # also avoid conflicts between user names and special dirs + options.Out = lib.mkOption { + type = with lib.types; nullOr singleLineStr; + default = "/var/spool/cups-pdf-${name}/users/\${USER}"; + defaultText = "/var/spool/cups-pdf-{instance-name}/users/\${USER}"; + example = "\${HOME}/cups-pdf"; + description = lib.mdDoc '' + output directory; + `''${HOME}` will be expanded to the user's home directory, + `''${USER}` will be expanded to the user name. + ''; + }; + options.AnonDirName = lib.mkOption { + type = with lib.types; nullOr singleLineStr; + default = "/var/spool/cups-pdf-${name}/anonymous"; + defaultText = "/var/spool/cups-pdf-{instance-name}/anonymous"; + example = "/var/lib/cups-pdf"; + description = lib.mdDoc "path for anonymously created PDF files"; + }; + options.Spool = lib.mkOption { + type = with lib.types; nullOr singleLineStr; + default = "/var/spool/cups-pdf-${name}/spool"; + defaultText = "/var/spool/cups-pdf-{instance-name}/spool"; + example = "/var/lib/cups-pdf"; + description = lib.mdDoc "spool directory"; + }; + options.Anonuser = lib.mkOption { + type = lib.types.singleLineStr; + default = "root"; + description = lib.mdDoc '' + User for anonymous PDF creation. + An empty string disables this feature. + ''; + }; + options.GhostScript = lib.mkOption { + type = with lib.types; nullOr path; + default = lib.getExe pkgs.ghostscript; + defaultText = lib.literalExpression "lib.getExe pkgs.ghostscript"; + example = lib.literalExpression ''''${pkgs.ghostscript}/bin/ps2pdf''; + description = lib.mdDoc "location of GhostScript binary"; + }; + }; + + instanceConfig = { name, config, ... }: { + options = { + enable = (lib.mkEnableOption (lib.mdDoc "this cups-pdf instance")) // { default = true; }; + installPrinter = (lib.mkEnableOption (lib.mdDoc '' + a CUPS printer queue for this instance. + The queue will be named after the instance and will use the {file}`CUPS-PDF_opt.ppd` ppd file. + If this is disabled, you need to add the queue yourself to use the instance + '')) // { default = true; }; + confFileText = lib.mkOption { + type = lib.types.lines; + description = lib.mdDoc '' + This will contain the contents of {file}`cups-pdf.conf` for this instance, derived from {option}`settings`. + You can use this option to append text to the file. + ''; + }; + settings = lib.mkOption { + type = lib.types.submodule (instanceSettings name); + default = {}; + example = { + Out = "\${HOME}/cups-pdf"; + UserUMask = "0033"; + }; + description = lib.mdDoc '' + Settings for a cups-pdf instance, see the descriptions in the template config file in the cups-pdf package. + The key value pairs declared here will be translated into proper key value pairs for {file}`cups-pdf.conf`. + Setting a value to `null` disables the option and removes it from the file. + ''; + }; + }; + config.confFileText = lib.pipe config.settings [ + (lib.filterAttrs (key: value: value != null)) + (lib.mapAttrs (key: builtins.toString)) + (lib.mapAttrsToList (key: value: "${key} ${value}\n")) + lib.concatStrings + ]; + }; + + cupsPdfCfg = config.services.printing.cups-pdf; + + copyConfigFileCmds = lib.pipe cupsPdfCfg.instances [ + (lib.filterAttrs (name: lib.getAttr "enable")) + (lib.mapAttrs (name: lib.getAttr "confFileText")) + (lib.mapAttrs (name: pkgs.writeText "cups-pdf-${name}.conf")) + (lib.mapAttrsToList (name: confFile: "ln --symbolic --no-target-directory ${confFile} /var/lib/cups/cups-pdf-${name}.conf\n")) + lib.concatStrings + ]; + + printerSettings = lib.pipe cupsPdfCfg.instances [ + (lib.filterAttrs (name: lib.getAttr "enable")) + (lib.filterAttrs (name: lib.getAttr "installPrinter")) + (lib.mapAttrsToList (name: instance: (lib.mapAttrs (key: lib.mkDefault) { + inherit name; + model = "CUPS-PDF_opt.ppd"; + deviceUri = "cups-pdf:/${name}"; + description = "virtual printer for cups-pdf instance ${name}"; + location = instance.settings.Out; + }))) + ]; + +in + +{ + + options.services.printing.cups-pdf = { + enable = lib.mkEnableOption (lib.mdDoc '' + the cups-pdf virtual pdf printer backend. + By default, this will install a single printer `pdf`. + but this can be changed/extended with {option}`services.printing.cups-pdf.instances` + ''); + instances = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule instanceConfig); + default.pdf = {}; + example.pdf.settings = { + Out = "\${HOME}/cups-pdf"; + UserUMask = "0033"; + }; + description = lib.mdDoc '' + Permits to raise one or more cups-pdf instances. + Each instance is named by an attribute name, and the attribute's values control the instance' configuration. + ''; + }; + }; + + config = lib.mkIf cupsPdfCfg.enable { + services.printing.enable = true; + services.printing.drivers = [ cups-pdf-wrapped ]; + hardware.printers.ensurePrinters = printerSettings; + # the cups module will install the default config file, + # but we don't need it and it would confuse cups-pdf + systemd.services.cups.preStart = lib.mkAfter '' + rm -f /var/lib/cups/cups-pdf.conf + ${copyConfigFileCmds} + ''; + security.wrappers.cups-pdf = { + group = "lp"; + owner = "root"; + permissions = "+r,ug+x"; + setuid = true; + source = "${pkgs.cups-pdf-to-pdf}/lib/cups/backend/cups-pdf"; + }; + }; + + meta.maintainers = [ lib.maintainers.yarny ]; + +} diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix index 1ab0e679a54ba..b8104ade4676d 100644 --- a/nixos/modules/services/web-apps/discourse.nix +++ b/nixos/modules/services/web-apps/discourse.nix @@ -820,10 +820,10 @@ in services.nginx = lib.mkIf cfg.nginx.enable { enable = true; - additionalModules = [ pkgs.nginxModules.brotli ]; recommendedTlsSettings = true; recommendedOptimisation = true; + recommendedBrotliSettings = true; recommendedGzipSettings = true; recommendedProxySettings = true; diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix index a623e45691dfe..90ca3002c5924 100644 --- a/nixos/modules/services/web-apps/hedgedoc.nix +++ b/nixos/modules/services/web-apps/hedgedoc.nix @@ -291,7 +291,8 @@ in }; defaultNotePath = mkOption { type = types.nullOr types.str; - default = "./public/default.md"; + default = "${cfg.package}/public/default.md"; + defaultText = literalExpression "\"\${cfg.package}/public/default.md\""; description = lib.mdDoc '' Path to the default Note file. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -299,7 +300,8 @@ in }; docsPath = mkOption { type = types.nullOr types.str; - default = "./public/docs"; + default = "${cfg.package}/public/docs"; + defaultText = literalExpression "\"\${cfg.package}/public/docs\""; description = lib.mdDoc '' Path to the docs directory. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -307,7 +309,8 @@ in }; indexPath = mkOption { type = types.nullOr types.str; - default = "./public/views/index.ejs"; + default = "${cfg.package}/public/views/index.ejs"; + defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\""; description = lib.mdDoc '' Path to the index template file. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -315,7 +318,8 @@ in }; hackmdPath = mkOption { type = types.nullOr types.str; - default = "./public/views/hackmd.ejs"; + default = "${cfg.package}/public/views/hackmd.ejs"; + defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\""; description = lib.mdDoc '' Path to the hackmd template file. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -323,8 +327,8 @@ in }; errorPath = mkOption { type = types.nullOr types.str; - default = null; - defaultText = literalExpression "./public/views/error.ejs"; + default = "${cfg.package}/public/views/error.ejs"; + defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\""; description = lib.mdDoc '' Path to the error template file. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -332,8 +336,8 @@ in }; prettyPath = mkOption { type = types.nullOr types.str; - default = null; - defaultText = literalExpression "./public/views/pretty.ejs"; + default = "${cfg.package}/public/views/pretty.ejs"; + defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\""; description = lib.mdDoc '' Path to the pretty template file. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -341,8 +345,8 @@ in }; slidePath = mkOption { type = types.nullOr types.str; - default = null; - defaultText = literalExpression "./public/views/slide.hbs"; + default = "${cfg.package}/public/views/slide.hbs"; + defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\""; description = lib.mdDoc '' Path to the slide template file. (Non-canonical paths are relative to HedgeDoc's base directory) @@ -351,7 +355,7 @@ in uploadsPath = mkOption { type = types.str; default = "${cfg.workDir}/uploads"; - defaultText = literalExpression "/var/lib/${name}/uploads"; + defaultText = literalExpression "\"\${cfg.workDir}/uploads\""; description = lib.mdDoc '' Path under which uploaded files are saved. ''; diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 8377e8a76d529..95e600ea79a5a 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -29,6 +29,43 @@ let ) cfg.virtualHosts; enableIPv6 = config.networking.enableIPv6; + # Mime.types values are taken from brotli sample configuration - https://github.com/google/ngx_brotli + # and Nginx Server Configs - https://github.com/h5bp/server-configs-nginx + compressMimeTypes = [ + "application/atom+xml" + "application/geo+json" + "application/json" + "application/ld+json" + "application/manifest+json" + "application/rdf+xml" + "application/vnd.ms-fontobject" + "application/wasm" + "application/x-rss+xml" + "application/x-web-app-manifest+json" + "application/xhtml+xml" + "application/xliff+xml" + "application/xml" + "font/collection" + "font/otf" + "font/ttf" + "image/bmp" + "image/svg+xml" + "image/vnd.microsoft.icon" + "text/cache-manifest" + "text/calendar" + "text/css" + "text/csv" + "text/html" + "text/javascript" + "text/markdown" + "text/plain" + "text/vcard" + "text/vnd.rim.location.xloc" + "text/vtt" + "text/x-component" + "text/xml" + ]; + defaultFastcgiParams = { SCRIPT_FILENAME = "$document_root$fastcgi_script_name"; QUERY_STRING = "$query_string"; @@ -140,6 +177,16 @@ let ssl_stapling_verify on; ''} + ${optionalString (cfg.recommendedBrotliSettings) '' + brotli on; + brotli_static on; + brotli_comp_level 5; + brotli_window 512k; + brotli_min_length 256; + brotli_types ${lib.concatStringsSep " " compressMimeTypes}; + brotli_buffers 32 8k; + ''} + ${optionalString (cfg.recommendedGzipSettings) '' gzip on; gzip_proxied any; @@ -456,6 +503,16 @@ in ''; }; + recommendedBrotliSettings = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enable recommended brotli settings. Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/blob/master/README.md). + + This adds `pkgs.nginxModules.brotli` to `services.nginx.additionalModules`. + ''; + }; + recommendedGzipSettings = mkOption { default = false; type = types.bool; @@ -537,11 +594,10 @@ in additionalModules = mkOption { default = []; type = types.listOf (types.attrsOf types.anything); - example = literalExpression "[ pkgs.nginxModules.brotli ]"; + example = literalExpression "[ pkgs.nginxModules.echo ]"; description = lib.mdDoc '' Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/) - to install. Packaged modules are available in - `pkgs.nginxModules`. + to install. Packaged modules are available in `pkgs.nginxModules`. ''; }; @@ -999,6 +1055,8 @@ in groups = config.users.groups; }) dependentCertNames; + services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli; + systemd.services.nginx = { description = "Nginx Web Server"; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index 87e66f73be0ec..7f817e5d350da 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -1,6 +1,6 @@ { config, lib, pkgs, ... }: let - inherit (lib) mkOption types optionalString stringAfter; + inherit (lib) mkOption mkDefault types optionalString stringAfter; cfg = config.boot.binfmt; @@ -281,7 +281,7 @@ in { config = { boot.binfmt.registrations = builtins.listToAttrs (map (system: { name = system; - value = let + value = { config, ... }: let interpreter = getEmulator system; qemuArch = getQemuArch system; @@ -292,13 +292,13 @@ in { in if preserveArgvZero then "${wrapper}/bin/${wrapperName}" else interpreter; - in { - inherit preserveArgvZero; + in ({ + preserveArgvZero = mkDefault preserveArgvZero; - interpreter = interpreterReg; - wrapInterpreterInShell = !preserveArgvZero; - interpreterSandboxPath = dirOf (dirOf interpreterReg); - } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")); + interpreter = mkDefault interpreterReg; + wrapInterpreterInShell = mkDefault (!config.preserveArgvZero); + interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter)); + } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"))); }) cfg.emulatedSystems); nix.settings = lib.mkIf (cfg.emulatedSystems != []) { extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux"; diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix index b41e7524320e2..cbc61d55d6bb3 100644 --- a/nixos/modules/system/boot/initrd-openvpn.nix +++ b/nixos/modules/system/boot/initrd-openvpn.nix @@ -68,11 +68,8 @@ in $out/bin/openvpn --show-gateway ''; - # Add `iproute /bin/ip` to the config, to ensure that openvpn - # is able to set the routes boot.initrd.network.postCommands = '' - (cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \ - openvpn /dev/stdin & + openvpn /etc/initrd.ovpn & ''; }; diff --git a/nixos/modules/tasks/filesystems/envfs.nix b/nixos/modules/tasks/filesystems/envfs.nix new file mode 100644 index 0000000000000..ef8f655c532a9 --- /dev/null +++ b/nixos/modules/tasks/filesystems/envfs.nix @@ -0,0 +1,51 @@ +{ pkgs, config, lib, ... }: + +let + cfg = config.services.envfs; + mounts = { + "/usr/bin" = { + device = "none"; + fsType = "envfs"; + options = [ + "fallback-path=${pkgs.runCommand "fallback-path" {} '' + mkdir -p $out + ln -s ${pkgs.coreutils}/bin/env $out/env + ln -s ${config.system.build.binsh}/bin/sh $out/sh + ''}" + ]; + }; + "/bin" = { + device = "/usr/bin"; + fsType = "none"; + options = [ "bind" ]; + }; + }; +in { + options = { + services.envfs = { + enable = lib.mkEnableOption (lib.mdDoc "Envfs filesystem") // { + description = lib.mdDoc '' + Fuse filesystem that returns symlinks to executables based on the PATH + of the requesting process. This is useful to execute shebangs on NixOS + that assume hard coded locations in locations like /bin or /usr/bin + etc. + ''; + }; + package = lib.mkOption { + type = lib.types.package; + description = lib.mdDoc "Which package to use for the envfs."; + default = pkgs.envfs; + defaultText = lib.mdDoc "pkgs.envfs"; + }; + }; + }; + config = lib.mkIf (cfg.enable) { + environment.systemPackages = [ cfg.package ]; + # we also want these mounts in virtual machines. + fileSystems = if config.virtualisation ? qemu then lib.mkVMOverride mounts else mounts; + + # We no longer need those when using envfs + system.activationScripts.usrbinenv = lib.mkForce ""; + system.activationScripts.binsh = lib.mkForce ""; + }; +} diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 0f14f2b501c22..6c77596475170 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -97,10 +97,15 @@ let in map (x: "${mountPoint x}.mount") (getPoolFilesystems pool); - getKeyLocations = pool: - if isBool cfgZfs.requestEncryptionCredentials - then "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}" - else "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}"; + getKeyLocations = pool: if isBool cfgZfs.requestEncryptionCredentials then { + hasKeys = cfgZfs.requestEncryptionCredentials; + command = "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}"; + } else let + keys = filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials; + in { + hasKeys = keys != []; + command = "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString keys}"; + }; createImportService = { pool, systemd, force, prefix ? "" }: nameValuePair "zfs-import-${pool}" { @@ -124,7 +129,9 @@ let RemainAfterExit = true; }; environment.ZFS_FORCE = optionalString force "-f"; - script = (importLib { + script = let + keyLocations = getKeyLocations pool; + in (importLib { # See comments at importLib definition. zpoolCmd = "${cfgZfs.package}/sbin/zpool"; awkCmd = "${pkgs.gawk}/bin/awk"; @@ -139,10 +146,8 @@ let done poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool. if poolImported "${pool}"; then - ${optionalString (if isBool cfgZfs.requestEncryptionCredentials - then cfgZfs.requestEncryptionCredentials - else cfgZfs.requestEncryptionCredentials != []) '' - ${getKeyLocations pool} | while IFS=$'\t' read ds kl ks; do + ${optionalString keyLocations.hasKeys '' + ${keyLocations.command} | while IFS=$'\t' read ds kl ks; do { if [[ "$ks" != unavailable ]]; then continue @@ -565,7 +570,7 @@ in '' else concatMapStrings (fs: '' zfs load-key -- ${escapeShellArg fs} - '') cfgZfs.requestEncryptionCredentials} + '') (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)} '') rootPools)); # Systemd in stage 1 diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 15c1fcdf6c3db..dbc3ce828e216 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -155,6 +155,7 @@ in { coturn = handleTest ./coturn.nix {}; couchdb = handleTest ./couchdb.nix {}; cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {}; + cups-pdf = handleTest ./cups-pdf.nix {}; custom-ca = handleTest ./custom-ca.nix {}; croc = handleTest ./croc.nix {}; deluge = handleTest ./deluge.nix {}; @@ -194,6 +195,7 @@ in { engelsystem = handleTest ./engelsystem.nix {}; enlightenment = handleTest ./enlightenment.nix {}; env = handleTest ./env.nix {}; + envfs = handleTest ./envfs.nix {}; envoy = handleTest ./envoy.nix {}; ergo = handleTest ./ergo.nix {}; ergochat = handleTest ./ergochat.nix {}; diff --git a/nixos/tests/cups-pdf.nix b/nixos/tests/cups-pdf.nix new file mode 100644 index 0000000000000..70d14f29e2e5d --- /dev/null +++ b/nixos/tests/cups-pdf.nix @@ -0,0 +1,40 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "cups-pdf"; + + nodes.machine = { pkgs, ... }: { + imports = [ ./common/user-account.nix ]; + environment.systemPackages = [ pkgs.poppler_utils ]; + fonts.fonts = [ pkgs.dejavu_fonts ]; # yields more OCR-able pdf + services.printing.cups-pdf.enable = true; + services.printing.cups-pdf.instances = { + opt = {}; + noopt.installPrinter = false; + }; + hardware.printers.ensurePrinters = [{ + name = "noopt"; + model = "CUPS-PDF_noopt.ppd"; + deviceUri = "cups-pdf:/noopt"; + }]; + }; + + # we cannot check the files with pdftotext, due to + # https://github.com/alexivkin/CUPS-PDF-to-PDF/issues/7 + # we need `imagemagickBig` as it has ghostscript support + + testScript = '' + from subprocess import run + machine.wait_for_unit("cups.service") + for name in ("opt", "noopt"): + text = f"test text {name}".upper() + machine.wait_until_succeeds(f"lpstat -v {name}") + machine.succeed(f"su - alice -c 'echo -e \"\n {text}\" | lp -d {name}'") + # wait until the pdf files are completely produced and readable by alice + machine.wait_until_succeeds(f"su - alice -c 'pdfinfo /var/spool/cups-pdf-{name}/users/alice/*.pdf'") + machine.succeed(f"cp /var/spool/cups-pdf-{name}/users/alice/*.pdf /tmp/{name}.pdf") + machine.copy_from_vm(f"/tmp/{name}.pdf", "") + run(f"${pkgs.imagemagickBig}/bin/convert -density 300 $out/{name}.pdf $out/{name}.jpeg", shell=True, check=True) + assert text.encode() in run(f"${lib.getExe pkgs.tesseract} $out/{name}.jpeg stdout", shell=True, check=True, capture_output=True).stdout + ''; + + meta.maintainers = [ lib.maintainers.yarny ]; +}) diff --git a/nixos/tests/envfs.nix b/nixos/tests/envfs.nix new file mode 100644 index 0000000000000..3f9cd1edb595a --- /dev/null +++ b/nixos/tests/envfs.nix @@ -0,0 +1,42 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: +let + pythonShebang = pkgs.writeScript "python-shebang" '' + #!/usr/bin/python + print("OK") + ''; + + bashShebang = pkgs.writeScript "bash-shebang" '' + #!/usr/bin/bash + echo "OK" + ''; +in +{ + name = "envfs"; + nodes.machine.services.envfs.enable = true; + + testScript = '' + start_all() + machine.wait_until_succeeds("mountpoint -q /usr/bin/") + machine.succeed( + "PATH=${pkgs.coreutils}/bin /usr/bin/cp --version", + # check fallback paths + "PATH= /usr/bin/sh --version", + "PATH= /usr/bin/env --version", + "PATH= test -e /usr/bin/sh", + "PATH= test -e /usr/bin/env", + # no stat + "! test -e /usr/bin/cp", + # also picks up PATH that was set after execve + "! /usr/bin/hello", + "PATH=${pkgs.hello}/bin /usr/bin/hello", + ) + + out = machine.succeed("PATH=${pkgs.python3}/bin ${pythonShebang}") + print(out) + assert out == "OK\n" + + out = machine.succeed("PATH=${pkgs.bash}/bin ${bashShebang}") + print(out) + assert out == "OK\n" + ''; +}) diff --git a/nixos/tests/initrd-network-openvpn/default.nix b/nixos/tests/initrd-network-openvpn/default.nix index bb4c41e6d7095..dbb34c28eea74 100644 --- a/nixos/tests/initrd-network-openvpn/default.nix +++ b/nixos/tests/initrd-network-openvpn/default.nix @@ -91,6 +91,7 @@ import ../make-test-python.nix ({ lib, ...}: config = '' dev tun0 ifconfig 10.8.0.1 10.8.0.2 + cipher AES-256-CBC ${secretblock} ''; }; diff --git a/nixos/tests/initrd-network-openvpn/initrd.ovpn b/nixos/tests/initrd-network-openvpn/initrd.ovpn index 5926a48af00f4..3ada4130e8682 100644 --- a/nixos/tests/initrd-network-openvpn/initrd.ovpn +++ b/nixos/tests/initrd-network-openvpn/initrd.ovpn @@ -3,6 +3,7 @@ dev tun ifconfig 10.8.0.2 10.8.0.1 # Only force VLAN 2 through the VPN route 192.168.2.0 255.255.255.0 10.8.0.1 +cipher AES-256-CBC secret [inline] <secret> # @@ -26,4 +27,4 @@ be5a69522a8e60ccb217f8521681b45d e7811584363597599cce2040a68ac00e f2125540e0f7f4adc37cb3f0d922eeb7 -----END OpenVPN Static key V1----- -</secret> \ No newline at end of file +</secret> diff --git a/nixos/tests/trafficserver.nix b/nixos/tests/trafficserver.nix index 983ded4f172e2..e4557c6c50e54 100644 --- a/nixos/tests/trafficserver.nix +++ b/nixos/tests/trafficserver.nix @@ -172,6 +172,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { assert re.fullmatch(expected, out) is not None, "no matching logs" out = json.loads(ats.succeed(f"traffic_logstats -jf {access_log_path}")) + assert isinstance(out, dict) assert out["total"]["error.total"]["req"] == "0", "unexpected log stat" ''; }) diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix index 29df691cecbed..3e55369daa06a 100644 --- a/nixos/tests/zfs.nix +++ b/nixos/tests/zfs.nix @@ -17,103 +17,151 @@ let makeTest { name = "zfs-" + name; meta = with pkgs.lib.maintainers; { - maintainers = [ adisbladis ]; + maintainers = [ adisbladis elvishjerricco ]; }; nodes.machine = { pkgs, lib, ... }: let usersharePath = "/var/lib/samba/usershares"; in { - virtualisation.emptyDiskImages = [ 4096 ]; + virtualisation = { + emptyDiskImages = [ 4096 4096 ]; + useBootLoader = true; + useEFIBoot = true; + }; + boot.loader.systemd-boot.enable = true; + boot.loader.timeout = 0; + boot.loader.efi.canTouchEfiVariables = true; networking.hostId = "deadbeef"; boot.kernelPackages = kernelPackage; boot.supportedFilesystems = [ "zfs" ]; boot.zfs.enableUnstable = enableUnstable; - services.samba = { - enable = true; - extraConfig = '' - registry shares = yes - usershare path = ${usersharePath} - usershare allow guests = yes - usershare max shares = 100 - usershare owner only = no - ''; + environment.systemPackages = [ pkgs.parted ]; + + # /dev/disk/by-id doesn't get populated in the NixOS test framework + boot.zfs.devNodes = "/dev/disk/by-uuid"; + + specialisation.samba.configuration = { + services.samba = { + enable = true; + extraConfig = '' + registry shares = yes + usershare path = ${usersharePath} + usershare allow guests = yes + usershare max shares = 100 + usershare owner only = no + ''; + }; + systemd.services.samba-smbd.serviceConfig.ExecStartPre = + "${pkgs.coreutils}/bin/mkdir -m +t -p ${usersharePath}"; + virtualisation.fileSystems = { + "/tmp/mnt" = { + device = "rpool/root"; + fsType = "zfs"; + }; + }; }; - systemd.services.samba-smbd.serviceConfig.ExecStartPre = - "${pkgs.coreutils}/bin/mkdir -m +t -p ${usersharePath}"; - environment.systemPackages = [ pkgs.parted ]; + specialisation.encryption.configuration = { + boot.zfs.requestEncryptionCredentials = [ "automatic" ]; + virtualisation.fileSystems."/automatic" = { + device = "automatic"; + fsType = "zfs"; + }; + virtualisation.fileSystems."/manual" = { + device = "manual"; + fsType = "zfs"; + }; + virtualisation.fileSystems."/manual/encrypted" = { + device = "manual/encrypted"; + fsType = "zfs"; + options = [ "noauto" ]; + }; + }; - # Setup regular fileSystems machinery to ensure forceImportAll can be - # tested via the regular service units. - virtualisation.fileSystems = { - "/forcepool" = { + specialisation.forcepool.configuration = { + systemd.services.zfs-import-forcepool.wantedBy = lib.mkVMOverride [ "forcepool.mount" ]; + systemd.targets.zfs.wantedBy = lib.mkVMOverride []; + boot.zfs.forceImportAll = true; + virtualisation.fileSystems."/forcepool" = { device = "forcepool"; fsType = "zfs"; options = [ "noauto" ]; }; }; - - # forcepool doesn't exist at first boot, and we need to manually test - # the import after tweaking the hostId. - systemd.services.zfs-import-forcepool.wantedBy = lib.mkVMOverride []; - systemd.targets.zfs.wantedBy = lib.mkVMOverride []; - boot.zfs.forceImportAll = true; - # /dev/disk/by-id doesn't get populated in the NixOS test framework - boot.zfs.devNodes = "/dev/disk/by-uuid"; }; testScript = '' + machine.wait_for_unit("multi-user.target") machine.succeed( - "modprobe zfs", "zpool status", - "ls /dev", - "mkdir /tmp/mnt", - "udevadm settle", - "parted --script /dev/vdb mklabel msdos", - "parted --script /dev/vdb -- mkpart primary 1024M -1s", - "udevadm settle", - "zpool create rpool /dev/vdb1", - "zfs create -o mountpoint=legacy rpool/root", - # shared datasets cannot have legacy mountpoint - "zfs create rpool/shared_smb", - "mount -t zfs rpool/root /tmp/mnt", - "udevadm settle", - # wait for samba services - "systemctl is-system-running --wait", - "zfs set sharesmb=on rpool/shared_smb", - "zfs share rpool/shared_smb", - "smbclient -gNL localhost | grep rpool_shared_smb", - "umount /tmp/mnt", - "zpool destroy rpool", - "udevadm settle", + "parted --script /dev/vdc mklabel msdos", + "parted --script /dev/vdc -- mkpart primary 1024M -1s", + "parted --script /dev/vdd mklabel msdos", + "parted --script /dev/vdd -- mkpart primary 1024M -1s", ) - machine.succeed( - 'echo password | zpool create -o altroot="/tmp/mnt" ' - + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1", - "zfs create -o mountpoint=legacy rpool/root", - "mount -t zfs rpool/root /tmp/mnt", - "udevadm settle", - "umount /tmp/mnt", - "zpool destroy rpool", - "udevadm settle", - ) + with subtest("sharesmb works"): + machine.succeed( + "zpool create rpool /dev/vdc1", + "zfs create -o mountpoint=legacy rpool/root", + # shared datasets cannot have legacy mountpoint + "zfs create rpool/shared_smb", + "bootctl set-default nixos-generation-1-specialisation-samba.conf", + "sync", + ) + machine.crash() + machine.wait_for_unit("multi-user.target") + machine.succeed( + "zfs set sharesmb=on rpool/shared_smb", + "zfs share rpool/shared_smb", + "smbclient -gNL localhost | grep rpool_shared_smb", + "umount /tmp/mnt", + "zpool destroy rpool", + ) + + with subtest("encryption works"): + machine.succeed( + 'echo password | zpool create -O mountpoint=legacy ' + + "-O encryption=aes-256-gcm -O keyformat=passphrase automatic /dev/vdc1", + "zpool create -O mountpoint=legacy manual /dev/vdd1", + "echo otherpass | zfs create " + + "-o encryption=aes-256-gcm -o keyformat=passphrase manual/encrypted", + "bootctl set-default nixos-generation-1-specialisation-encryption.conf", + "sync", + "zpool export automatic", + "zpool export manual", + ) + machine.crash() + machine.start() + machine.wait_for_console_text("Starting password query on") + machine.send_console("password\n") + machine.wait_for_unit("multi-user.target") + machine.succeed( + "zfs get keystatus manual/encrypted | grep unavailable", + "echo otherpass | zfs load-key manual/encrypted", + "systemctl start manual-encrypted.mount", + "umount /automatic /manual/encrypted /manual", + "zpool destroy automatic", + "zpool destroy manual", + ) with subtest("boot.zfs.forceImportAll works"): machine.succeed( "rm /etc/hostid", "zgenhostid deadcafe", - "zpool create forcepool /dev/vdb1 -O mountpoint=legacy", + "zpool create forcepool /dev/vdc1 -O mountpoint=legacy", + "bootctl set-default nixos-generation-1-specialisation-forcepool.conf", + "rm /etc/hostid", + "sync", ) - machine.shutdown() - machine.start() - machine.succeed("udevadm settle") + machine.crash() + machine.wait_for_unit("multi-user.target") machine.fail("zpool import forcepool") machine.succeed( - "systemctl start zfs-import-forcepool.service", - "mount -t zfs forcepool /tmp/mnt", + "systemctl start forcepool.mount", + "mount | grep forcepool", ) '' + extraTest; |