diff options
Diffstat (limited to 'nixos')
99 files changed, 2965 insertions, 563 deletions
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md index 3a4800742b048..71ec9bbc88925 100644 --- a/nixos/doc/manual/development/settings-options.section.md +++ b/nixos/doc/manual/development/settings-options.section.md @@ -73,6 +73,34 @@ have a predefined type and string generator already declared under It returns a set with INI-specific attributes `type` and `generate` as specified [below](#pkgs-formats-result). + The type of the input is an *attrset* of sections; key-value pairs where + the key is the section name and the value is the corresponding content + which is also an *attrset* of key-value pairs for the actual key-value + mappings of the INI format. + The values of the INI atoms are subject to the above parameters (e.g. lists + may be transformed into multiple key-value pairs depending on + `listToValue`). + +`pkgs.formats.iniWithGlobalSection` { *`listsAsDuplicateKeys`* ? false, *`listToValue`* ? null, \.\.\. } + +: A function taking an attribute set with values + + `listsAsDuplicateKeys` + + : A boolean for controlling whether list values can be used to + represent duplicate INI keys + + `listToValue` + + : A function for turning a list of values into a single value. + + It returns a set with INI-specific attributes `type` and `generate` + as specified [below](#pkgs-formats-result). + The type of the input is an *attrset* of the structure + `{ sections = {}; globalSection = {}; }` where *sections* are several + sections as with *pkgs.formats.ini* and *globalSection* being just a single + attrset of key-value pairs for a single section, the global section which + preceedes the section definitions. `pkgs.formats.toml` { } diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index 08cba1790ca2f..daabca2aee609 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -16,6 +16,8 @@ In addition to numerous new and upgraded packages, this release has the followin - `linuxPackages_testing_bcachefs` is now fully deprecated by `linuxPackages_latest`, and is therefore no longer available. +- The default kernel package has been updated from 6.1 to 6.6. All supported kernels remain available. + - NixOS now installs a stub ELF loader that prints an informative error message when users attempt to run binaries not made for NixOS. - This can be disabled through the `environment.stub-ld.enable` option. - If you use `programs.nix-ld.enable`, no changes are needed. The stub will be disabled automatically. @@ -50,6 +52,8 @@ In addition to numerous new and upgraded packages, this release has the followin } ``` +- Plasma 6 is now available and can be installed with `services.xserver.desktopManager.plasma6.enable = true;`. Plasma 5 will likely be deprecated in the next release (24.11). Note that Plasma 6 runs as Wayland by default, and the X11 session needs to be explicitly selected if necessary. + ## New Services {#sec-release-24.05-new-services} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> @@ -74,9 +78,15 @@ In addition to numerous new and upgraded packages, this release has the followin - [hebbot](https://github.com/haecker-felix/hebbot), a Matrix bot to generate "This Week in X" like blog posts. Available as [services.hebbot](#opt-services.hebbot.enable). +- [Python Matter Server](https://github.com/home-assistant-libs/python-matter-server), a + Matter Controller Server exposing websocket connections for use with other services, notably Home Assistant. + Available as [services.matter-server](#opt-services.matter-server.enable) + - [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable). The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been marked deprecated and will be dropped after 24.05 due to lack of maintenance of the anki-sync-server softwares. +- [transfer-sh](https://github.com/dutchcoders/transfer.sh), a tool that supports easy and fast file sharing from the command-line. Available as [services.transfer-sh](#opt-services.transfer-sh.enable). + - [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable). - [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable). @@ -85,8 +95,12 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m - [go-camo](https://github.com/cactus/go-camo), a secure image proxy server. Available as [services.go-camo](#opt-services.go-camo.enable). +- [Monado](https://monado.freedesktop.org/), an open source XR runtime. Available as [services.monado](#opt-services.monado.enable). + - [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable). +- [armagetronad](https://wiki.armagetronad.org), a mid-2000s 3D lightcycle game widely played at iD Tech Camps. You can define multiple servers using `services.armagetronad.<server>.enable`. + - [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable). - [ALVR](https://github.com/alvr-org/alvr), a VR desktop streamer. Available as [programs.alvr](#opt-programs.alvr.enable) @@ -97,11 +111,13 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m - [systemd-lock-handler](https://git.sr.ht/~whynothugo/systemd-lock-handler/), a bridge between logind D-Bus events and systemd targets. Available as [services.systemd-lock-handler.enable](#opt-services.systemd-lock-handler.enable). +- [Mealie](https://nightly.mealie.io/), a self-hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in NuxtJS for a pleasant user experience for the whole family. Available as [services.mealie](#opt-services.mealie.enable) + ## Backward Incompatibilities {#sec-release-24.05-incompatibilities} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> -- `himalaya` was updated to v1.0.0-beta, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta) for details. +- `himalaya` was updated to `v1.0.0-beta.3`, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta.3) for details. - The `power.ups` module now generates `upsd.conf`, `upsd.users` and `upsmon.conf` automatically from a set of new configuration options. This breaks compatibility with existing `power.ups` setups where these files were created manually. Back up these files before upgrading NixOS. @@ -148,6 +164,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m release notes of [v19](https://github.com/systemd/mkosi/releases/tag/v19) and [v20](https://github.com/systemd/mkosi/releases/tag/v20) for a list of changes. +- The `services.vikunja` systemd service now uses `vikunja` as dynamic user instead of `vikunja-api`. Database users might need to be changed. + +- The `services.vikunja.setupNginx` setting has been removed. Users now need to setup the webserver configuration on their own with a proxy pass to the vikunja service. + - The `woodpecker-*` packages have been updated to v2 which includes [breaking changes](https://woodpecker-ci.org/docs/next/migrations#200). - `services.nginx` will no longer advertise HTTP/3 availability automatically. This must now be manually added, preferably to each location block. @@ -279,6 +299,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m - Cinnamon has been updated to 6.0. Please beware that the [Wayland session](https://blog.linuxmint.com/?p=4591) is still experimental in this release. +- New `boot.loader.systemd-boot.xbootldrMountPoint` allows setting up a separate [XBOOTLDR partition](https://uapi-group.org/specifications/specs/boot_loader_specification/) to store boot files. Useful on systems with a small EFI System partition that cannot be easily repartitioned. + +- `boot.loader.systemd-boot` will now verify that `efiSysMountPoint` (and `xbootldrMountPoint` if configured) are mounted partitions. + - `services.postgresql.extraPlugins` changed its type from just a list of packages to also a function that returns such a list. For example a config line like ``services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [ postgis ];`` is recommended to be changed to ``services.postgresql.extraPlugins = ps: with ps; [ postgis ];``; @@ -380,6 +404,11 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m - The `mpich` package expression now requires `withPm` to be a list, e.g. `"hydra:gforker"` becomes `[ "hydra" "gforker" ]`. +- When merging systemd unit options (of type `unitOption`), + if at least one definition is a list, all those which aren't are now lifted into a list, + making it possible to accumulate definitions without resorting to `mkForce`, + hence to retain the definitions not anticipating that need. + - YouTrack is bumped to 2023.3. The update is not performed automatically, it requires manual interaction. See the YouTrack section in the manual for details. - QtMultimedia has changed its default backend to `QT_MEDIA_BACKEND=ffmpeg` (previously `gstreamer` on Linux or `darwin` on MacOS). diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix index 047e72e2ac0df..9bdbf4e0713de 100644 --- a/nixos/lib/make-disk-image.nix +++ b/nixos/lib/make-disk-image.nix @@ -56,6 +56,14 @@ This partition table type uses GPT and: - creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ; - creates an primary ext4 partition starting after the boot partition and extending to the full disk image +#### `efixbootldr` + +This partition table type uses GPT and: + +- creates an FAT32 ESP partition from 8MiB to 100MiB, set it bootable ; +- creates an FAT32 BOOT partition from 100MiB to specified `bootSize` parameter (256MiB by default), set `bls_boot` flag ; +- creates an primary ext4 partition starting after the boot partition and extending to the full disk image + #### `hybrid` This partition table type uses GPT and: @@ -111,19 +119,7 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag # When setting one of `user' or `group', the other needs to be set too. contents ? [] -, # Type of partition table to use; either "legacy", "efi", or "none". - # For "efi" images, the GPT partition table is used and a mandatory ESP - # partition of reasonable size is created in addition to the root partition. - # For "legacy", the msdos partition table is used and a single large root - # partition is created. - # For "legacy+gpt", the GPT partition table is used, a 1MiB no-fs partition for - # use by the bootloader is created, and a single large root partition is - # created. - # For "hybrid", the GPT partition table is used and a mandatory ESP - # partition of reasonable size is created in addition to the root partition. - # Also a legacy MBR will be present. - # For "none", no partition table is created. Enabling `installBootLoader` - # most likely fails as GRUB will probably refuse to install. +, # Type of partition table to use; described in the `Image Partitioning` section above. partitionTableType ? "legacy" , # Whether to invoke `switch-to-configuration boot` during image creation @@ -193,11 +189,11 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag additionalPaths ? [] }: -assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "hybrid" "none" ]); +assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "efixbootldr" "hybrid" "none" ]); assert (lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null) "In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID."); # We use -E offset=X below, which is only supported by e2fsprogs assert (lib.assertMsg (partitionTableType != "none" -> fsType == "ext4") "to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4"); -assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi or legacy+gpt."); +assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi, efixbootldr, or legacy+gpt."); # If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader. assert (lib.assertMsg (onlyNixStore -> contents == [] && configFile == null && !installBootLoader) "In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed."); # Either both or none of {user,group} need to be set @@ -225,6 +221,7 @@ let format' = format; in let legacy = "1"; "legacy+gpt" = "2"; efi = "2"; + efixbootldr = "3"; hybrid = "3"; }.${partitionTableType}; @@ -266,6 +263,23 @@ let format' = format; in let $diskImage ''} ''; + efixbootldr = '' + parted --script $diskImage -- \ + mklabel gpt \ + mkpart ESP fat32 8MiB 100MiB \ + set 1 boot on \ + mkpart BOOT fat32 100MiB ${bootSize} \ + set 2 bls_boot on \ + mkpart ROOT ext4 ${bootSize} -1 + ${optionalString deterministic '' + sgdisk \ + --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \ + --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \ + --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \ + --partition-guid=3:${rootGPUID} \ + $diskImage + ''} + ''; hybrid = '' parted --script $diskImage -- \ mklabel gpt \ @@ -436,7 +450,7 @@ let format' = format; in let diskImage=nixos.raw ${if diskSize == "auto" then '' - ${if partitionTableType == "efi" || partitionTableType == "hybrid" then '' + ${if partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "hybrid" then '' # Add the GPT at the end gptSpace=$(( 512 * 34 * 1 )) # Normally we'd need to account for alignment and things, if bootSize @@ -570,6 +584,15 @@ let format' = format; in let ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"} ''} + ${optionalString (partitionTableType == "efixbootldr") '' + mkdir -p /mnt/{boot,efi} + mkfs.vfat -n ESP /dev/vda1 + mkfs.vfat -n BOOT /dev/vda2 + mount /dev/vda1 /mnt/efi + mount /dev/vda2 /mnt/boot + + ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"} + ''} # Install a configuration.nix mkdir -p /mnt/etc/nixos @@ -586,6 +609,13 @@ let format' = format; in let ''} # Set up core system link, bootloader (sd-boot, GRUB, uboot, etc.), etc. + + # NOTE: systemd-boot-builder.py calls nix-env --list-generations which + # clobbers $HOME/.nix-defexpr/channels/nixos This would cause a folder + # /homeless-shelter to show up in the final image which in turn breaks + # nix builds in the target image if sandboxing is turned off (through + # __noChroot for example). + export HOME=$TMPDIR NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix index c9cca619ed70a..ef218e674ebf1 100644 --- a/nixos/lib/systemd-lib.nix +++ b/nixos/lib/systemd-lib.nix @@ -378,7 +378,7 @@ in rec { ''; targetToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = '' [Unit] @@ -387,7 +387,7 @@ in rec { }; serviceToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def ('' [Service] '' + (let env = cfg.globalEnvironment // def.environment; @@ -408,7 +408,7 @@ in rec { }; socketToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def '' [Socket] ${attrsToSection def.socketConfig} @@ -418,7 +418,7 @@ in rec { }; timerToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def '' [Timer] ${attrsToSection def.timerConfig} @@ -426,7 +426,7 @@ in rec { }; pathToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def '' [Path] ${attrsToSection def.pathConfig} @@ -434,7 +434,7 @@ in rec { }; mountToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def '' [Mount] ${attrsToSection def.mountConfig} @@ -442,7 +442,7 @@ in rec { }; automountToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def '' [Automount] ${attrsToSection def.automountConfig} @@ -450,7 +450,7 @@ in rec { }; sliceToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable overrideStrategy; + { inherit (def) aliases wantedBy requiredBy upheldBy enable overrideStrategy; text = commonUnitText def '' [Slice] ${attrsToSection def.sliceConfig} diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix index bc7880da9fe0e..e4953ba72dd92 100644 --- a/nixos/lib/systemd-unit-options.nix +++ b/nixos/lib/systemd-unit-options.nix @@ -21,14 +21,8 @@ in rec { let defs' = filterOverrides defs; in - if isList (head defs').value - then concatMap (def: - if builtins.typeOf def.value == "list" - then def.value - else - throw "The definitions for systemd unit options should be either all lists, representing repeatable options, or all non-lists, but for the option ${showOption loc}, the definitions are a mix of list and non-list ${lib.options.showDefs defs'}" - ) defs' - + if any (def: isList def.value) defs' + then concatMap (def: toList def.value) defs' else mergeEqualOption loc defs'; }; diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py index 786821b0cc0d6..f792c04591996 100644 --- a/nixos/lib/test-driver/test_driver/driver.py +++ b/nixos/lib/test-driver/test_driver/driver.py @@ -7,11 +7,15 @@ from contextlib import contextmanager from pathlib import Path from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional, Union +from colorama import Fore, Style + from test_driver.logger import rootlog from test_driver.machine import Machine, NixStartScript, retry from test_driver.polling_condition import PollingCondition from test_driver.vlan import VLan +SENTINEL = object() + def get_tmp_dir() -> Path: """Returns a temporary directory that is defined by TMPDIR, TEMP, TMP or CWD @@ -187,23 +191,61 @@ class Driver: # to swallow them and prevent itself from terminating. os.kill(os.getpid(), signal.SIGTERM) - def create_machine(self, args: Dict[str, Any]) -> Machine: + def create_machine( + self, + start_command: str | dict, + *, + name: Optional[str] = None, + keep_vm_state: bool = False, + ) -> Machine: + # Legacy args handling + # FIXME: remove after 24.05 + if isinstance(start_command, dict): + if name is not None or keep_vm_state: + raise TypeError( + "Dictionary passed to create_machine must be the only argument" + ) + + args = start_command + start_command = args.pop("startCommand", SENTINEL) + + if start_command is SENTINEL: + raise TypeError( + "Dictionary passed to create_machine must contain startCommand" + ) + + if not isinstance(start_command, str): + raise TypeError( + f"startCommand must be a string, got: {repr(start_command)}" + ) + + name = args.pop("name", None) + keep_vm_state = args.pop("keep_vm_state", False) + + if args: + raise TypeError( + f"Unsupported arguments passed to create_machine: {args}" + ) + + rootlog.warning( + Fore.YELLOW + + Style.BRIGHT + + "WARNING: Using create_machine with a single dictionary argument is deprecated and will be removed in NixOS 24.11" + + Style.RESET_ALL + ) + # End legacy args handling + tmp_dir = get_tmp_dir() - if args.get("startCommand"): - start_command: str = args.get("startCommand", "") - cmd = NixStartScript(start_command) - name = args.get("name", cmd.machine_name) - else: - cmd = Machine.create_startcommand(args) # type: ignore - name = args.get("name", "machine") + cmd = NixStartScript(start_command) + name = name or cmd.machine_name return Machine( tmp_dir=tmp_dir, out_dir=self.out_dir, start_command=cmd, name=name, - keep_vm_state=args.get("keep_vm_state", False), + keep_vm_state=keep_vm_state, ) def serial_stdout_on(self) -> None: diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py index 93411a4a348e1..df8628bce9568 100644 --- a/nixos/lib/test-driver/test_driver/machine.py +++ b/nixos/lib/test-driver/test_driver/machine.py @@ -208,7 +208,6 @@ class StartCommand: ), stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, shell=True, cwd=state_dir, env=self.build_environment(state_dir, shared_dir), @@ -235,77 +234,6 @@ class NixStartScript(StartCommand): return name -class LegacyStartCommand(StartCommand): - """Used in some places to create an ad-hoc machine instead of - using nix test instrumentation + module system for that purpose. - Legacy. - """ - - def __init__( - self, - netBackendArgs: Optional[str] = None, # noqa: N803 - netFrontendArgs: Optional[str] = None, # noqa: N803 - hda: Optional[Tuple[Path, str]] = None, - cdrom: Optional[str] = None, - usb: Optional[str] = None, - bios: Optional[str] = None, - qemuBinary: Optional[str] = None, # noqa: N803 - qemuFlags: Optional[str] = None, # noqa: N803 - ): - if qemuBinary is not None: - self._cmd = qemuBinary - else: - self._cmd = "qemu-kvm" - - self._cmd += " -m 384" - - # networking - net_backend = "-netdev user,id=net0" - net_frontend = "-device virtio-net-pci,netdev=net0" - if netBackendArgs is not None: - net_backend += "," + netBackendArgs - if netFrontendArgs is not None: - net_frontend += "," + netFrontendArgs - self._cmd += f" {net_backend} {net_frontend}" - - # hda - hda_cmd = "" - if hda is not None: - hda_path = hda[0].resolve() - hda_interface = hda[1] - if hda_interface == "scsi": - hda_cmd += ( - f" -drive id=hda,file={hda_path},werror=report,if=none" - " -device scsi-hd,drive=hda" - ) - else: - hda_cmd += f" -drive file={hda_path},if={hda_interface},werror=report" - self._cmd += hda_cmd - - # cdrom - if cdrom is not None: - self._cmd += f" -cdrom {cdrom}" - - # usb - usb_cmd = "" - if usb is not None: - # https://github.com/qemu/qemu/blob/master/docs/usb2.txt - usb_cmd += ( - " -device usb-ehci" - f" -drive id=usbdisk,file={usb},if=none,readonly" - " -device usb-storage,drive=usbdisk " - ) - self._cmd += usb_cmd - - # bios - if bios is not None: - self._cmd += f" -bios {bios}" - - # qemu flags - if qemuFlags is not None: - self._cmd += f" {qemuFlags}" - - class Machine: """A handle to the machine with this name, that also knows how to manage the machine lifecycle with the help of a start script / command.""" @@ -377,29 +305,6 @@ class Machine: self.booted = False self.connected = False - @staticmethod - def create_startcommand(args: Dict[str, str]) -> StartCommand: - rootlog.warning( - "Using legacy create_startcommand(), " - "please use proper nix test vm instrumentation, instead " - "to generate the appropriate nixos test vm qemu startup script" - ) - hda = None - if args.get("hda"): - hda_arg: str = args.get("hda", "") - hda_arg_path: Path = Path(hda_arg) - hda = (hda_arg_path, args.get("hdaInterface", "")) - return LegacyStartCommand( - netBackendArgs=args.get("netBackendArgs"), - netFrontendArgs=args.get("netFrontendArgs"), - hda=hda, - cdrom=args.get("cdrom"), - usb=args.get("usb"), - bios=args.get("bios"), - qemuBinary=args.get("qemuBinary"), - qemuFlags=args.get("qemuFlags"), - ) - def is_up(self) -> bool: return self.booted and self.connected diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py index 15e59ce01047d..976992ea00158 100644 --- a/nixos/lib/test-script-prepend.py +++ b/nixos/lib/test-script-prepend.py @@ -26,6 +26,17 @@ class PollingConditionProtocol(Protocol): raise Exception("This is just type information for the Nix test driver") +class CreateMachineProtocol(Protocol): + def __call__( + self, + start_command: str | dict, + *, + name: Optional[str] = None, + keep_vm_state: bool = False, + ) -> Machine: + raise Exception("This is just type information for the Nix test driver") + + start_all: Callable[[], None] subtest: Callable[[str], ContextManager[None]] retry: RetryProtocol @@ -34,7 +45,7 @@ machines: List[Machine] vlans: List[VLan] driver: Driver log: Logger -create_machine: Callable[[Dict[str, Any]], Machine] +create_machine: CreateMachineProtocol run_tests: Callable[[], None] join_all: Callable[[], None] serial_stdout_off: Callable[[], None] diff --git a/nixos/lib/testing/nixos-test-base.nix b/nixos/lib/testing/nixos-test-base.nix index 59e6e38433679..d76a25361f8ca 100644 --- a/nixos/lib/testing/nixos-test-base.nix +++ b/nixos/lib/testing/nixos-test-base.nix @@ -16,7 +16,11 @@ in # The human version (e.g. 21.05-pre) is left as is, because it is useful # for external modules that test with e.g. testers.nixosTest and rely on that # version number. - config.system.nixos.revision = mkForce "constant-nixos-revision"; + config.system.nixos = { + revision = mkForce "constant-nixos-revision"; + versionSuffix = mkForce "test"; + label = mkForce "test"; + }; } ]; diff --git a/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix index 73e6d386fd1da..7941d69e38d2b 100644 --- a/nixos/lib/testing/nodes.nix +++ b/nixos/lib/testing/nodes.nix @@ -14,6 +14,25 @@ let types ; + inherit (hostPkgs) hostPlatform; + + guestSystem = + if hostPlatform.isLinux + then hostPlatform.system + else + let + hostToGuest = { + "x86_64-darwin" = "x86_64-linux"; + "aarch64-darwin" = "aarch64-linux"; + }; + + supportedHosts = lib.concatStringsSep ", " (lib.attrNames hostToGuest); + + message = + "NixOS Test: don't know which VM guest system to pair with VM host system: ${hostPlatform.system}. Perhaps you intended to run the tests on a Linux host, or one of the following systems that may run NixOS tests: ${supportedHosts}"; + in + hostToGuest.${hostPlatform.system} or (throw message); + baseOS = import ../eval-config.nix { inherit lib; @@ -27,13 +46,14 @@ let ({ config, ... }: { virtualisation.qemu.package = testModuleArgs.config.qemu.package; + virtualisation.host.pkgs = hostPkgs; }) ({ options, ... }: { key = "nodes.nix-pkgs"; config = optionalAttrs (!config.node.pkgsReadOnly) ( mkIf (!options.nixpkgs.pkgs.isDefined) { # TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates. - nixpkgs.system = hostPkgs.stdenv.hostPlatform.system; + nixpkgs.system = guestSystem; } ); }) diff --git a/nixos/lib/testing/pkgs.nix b/nixos/lib/testing/pkgs.nix index 22dd586868e30..46d82c65d26a3 100644 --- a/nixos/lib/testing/pkgs.nix +++ b/nixos/lib/testing/pkgs.nix @@ -2,7 +2,11 @@ { config = { # default pkgs for use in VMs - _module.args.pkgs = hostPkgs; + _module.args.pkgs = + # TODO: deprecate it everywhere; not just on darwin. Throw on darwin? + lib.warnIf hostPkgs.stdenv.hostPlatform.isDarwin + "Do not use the `pkgs` module argument in tests you want to run on darwin. It is ambiguous, and many tests are broken because of it. If you need to use a package on the VM host, use `hostPkgs`. Otherwise, use `config.node.pkgs`, or `config.nodes.<name>.nixpkgs.pkgs`." + hostPkgs; defaults = { # TODO: a module to set a shared pkgs, if options.nixpkgs.* is untouched by user (highestPrio) */ diff --git a/nixos/lib/testing/run.nix b/nixos/lib/testing/run.nix index 9440c1acdfd81..de5a9b97e61d5 100644 --- a/nixos/lib/testing/run.nix +++ b/nixos/lib/testing/run.nix @@ -41,7 +41,9 @@ in rawTestDerivation = hostPkgs.stdenv.mkDerivation { name = "vm-test-run-${config.name}"; - requiredSystemFeatures = [ "kvm" "nixos-test" ]; + requiredSystemFeatures = [ "nixos-test" ] + ++ lib.optionals hostPkgs.stdenv.hostPlatform.isLinux [ "kvm" ] + ++ lib.optionals hostPkgs.stdenv.hostPlatform.isDarwin [ "apple-virt" ]; buildCommand = '' mkdir -p $out diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix index 530727f3f2928..ee8d2652b1c72 100644 --- a/nixos/modules/i18n/input-method/fcitx5.nix +++ b/nixos/modules/i18n/input-method/fcitx5.nix @@ -5,7 +5,10 @@ with lib; let im = config.i18n.inputMethod; cfg = im.fcitx5; - fcitx5Package = pkgs.fcitx5-with-addons.override { inherit (cfg) addons; }; + fcitx5Package = + if cfg.plasma6Support + then pkgs.qt6Packages.fcitx5-with-addons.override { inherit (cfg) addons; } + else pkgs.libsForQt5.fcitx5-with-addons.override { inherit (cfg) addons; }; settingsFormat = pkgs.formats.ini { }; in { @@ -27,6 +30,15 @@ in See [Using Fcitx 5 on Wayland](https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland). ''; }; + plasma6Support = mkOption { + type = types.bool; + default = config.services.xserver.desktopManager.plasma6.enable; + defaultText = literalExpression "config.services.xserver.desktopManager.plasma6.enable"; + description = lib.mdDoc '' + Use qt6 versions of fcitx5 packages. + Required for configuring fcitx5 in KDE System Settings. + ''; + }; quickPhrase = mkOption { type = with types; attrsOf str; default = { }; diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix new file mode 100644 index 0000000000000..11118db3aae2a --- /dev/null +++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix @@ -0,0 +1,46 @@ +# This module defines a NixOS installation CD that contains Plasma 6. + +{ pkgs, ... }: + +{ + imports = [ ./installation-cd-graphical-calamares.nix ]; + + isoImage.edition = "plasma6"; + + services.xserver = { + desktopManager.plasma6.enable = true; + + # Automatically login as nixos. + displayManager = { + sddm.enable = true; + autoLogin = { + enable = true; + user = "nixos"; + }; + }; + }; + + environment.systemPackages = [ + # FIXME: using Qt5 builds of Maliit as upstream has not ported to Qt6 yet + pkgs.maliit-framework + pkgs.maliit-keyboard + ]; + + system.activationScripts.installerDesktop = let + + # Comes from documentation.nix when xserver and nixos.enable are true. + manualDesktopFile = "/run/current-system/sw/share/applications/nixos-manual.desktop"; + + homeDir = "/home/nixos/"; + desktopDir = homeDir + "Desktop/"; + + in '' + mkdir -p ${desktopDir} + chown nixos ${homeDir} ${desktopDir} + + ln -sfT ${manualDesktopFile} ${desktopDir + "nixos-manual.desktop"} + ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop ${desktopDir + "gparted.desktop"} + ln -sfT ${pkgs.calamares-nixos}/share/applications/io.calamares.calamares.desktop ${desktopDir + "io.calamares.calamares.desktop"} + ''; + +} diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix index a50f22cbe471d..028a2d74041e6 100644 --- a/nixos/modules/installer/netboot/netboot.nix +++ b/nixos/modules/installer/netboot/netboot.nix @@ -62,19 +62,12 @@ with lib; }; fileSystems."/nix/store" = mkImageMediaOverride - { fsType = "overlay"; - device = "overlay"; - options = [ - "lowerdir=/nix/.ro-store" - "upperdir=/nix/.rw-store/store" - "workdir=/nix/.rw-store/work" - ]; - - depends = [ - "/nix/.ro-store" - "/nix/.rw-store/store" - "/nix/.rw-store/work" - ]; + { overlay = { + lowerdir = [ "/nix/.ro-store" ]; + upperdir = "/nix/.rw-store/store"; + workdir = "/nix/.rw-store/work"; + }; + neededForBoot = true; }; boot.initrd.availableKernelModules = [ "squashfs" "overlay" ]; diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix index da321a9234493..10f800cd741a0 100644 --- a/nixos/modules/misc/nixpkgs.nix +++ b/nixos/modules/misc/nixpkgs.nix @@ -208,7 +208,11 @@ in example = { system = "x86_64-linux"; }; # Make sure that the final value has all fields for sake of other modules # referring to this. - apply = lib.systems.elaborate; + apply = inputBuildPlatform: + let elaborated = lib.systems.elaborate inputBuildPlatform; + in if lib.systems.equals elaborated cfg.hostPlatform + then cfg.hostPlatform # make identical, so that `==` equality works; see https://github.com/NixOS/nixpkgs/issues/278001 + else elaborated; defaultText = literalExpression ''config.nixpkgs.hostPlatform''; description = lib.mdDoc '' diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix index 0536cfc9624a2..be9a88a077887 100644 --- a/nixos/modules/misc/nixpkgs/test.nix +++ b/nixos/modules/misc/nixpkgs/test.nix @@ -12,6 +12,10 @@ let nixpkgs.hostPlatform = "aarch64-linux"; nixpkgs.buildPlatform = "aarch64-darwin"; }; + withSameHostAndBuild = eval { + nixpkgs.hostPlatform = "aarch64-linux"; + nixpkgs.buildPlatform = "aarch64-linux"; + }; ambiguous = { _file = "ambiguous.nix"; nixpkgs.hostPlatform = "aarch64-linux"; @@ -81,6 +85,8 @@ lib.recurseIntoAttrs { assert withHost._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-linux"; assert withHostAndBuild._module.args.pkgs.stdenv.hostPlatform.system == "aarch64-linux"; assert withHostAndBuild._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-darwin"; + assert withSameHostAndBuild.config.nixpkgs.buildPlatform == withSameHostAndBuild.config.nixpkgs.hostPlatform; + assert withSameHostAndBuild._module.args.pkgs.stdenv.buildPlatform == withSameHostAndBuild._module.args.pkgs.stdenv.hostPlatform; assert builtins.trace (lib.head (getErrors ambiguous)) getErrors ambiguous == ['' diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 8683af5ed439f..cfe2350d5762e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -163,6 +163,7 @@ ./programs/clash-verge.nix ./programs/cnping.nix ./programs/command-not-found/command-not-found.nix + ./programs/coolercontrol.nix ./programs/criu.nix ./programs/darling.nix ./programs/dconf.nix @@ -512,6 +513,7 @@ ./services/editors/infinoted.nix ./services/finance/odoo.nix ./services/games/archisteamfarm.nix + ./services/games/armagetronad.nix ./services/games/crossfire-server.nix ./services/games/deliantra-server.nix ./services/games/factorio.nix @@ -548,6 +550,7 @@ ./services/hardware/lcd.nix ./services/hardware/lirc.nix ./services/hardware/nvidia-container-toolkit-cdi-generator + ./services/hardware/monado.nix ./services/hardware/nvidia-optimus.nix ./services/hardware/openrgb.nix ./services/hardware/pcscd.nix @@ -583,6 +586,7 @@ ./services/home-automation/govee2mqtt.nix ./services/home-automation/home-assistant.nix ./services/home-automation/homeassistant-satellite.nix + ./services/home-automation/matter-server.nix ./services/home-automation/zigbee2mqtt.nix ./services/home-automation/zwave-js.nix ./services/logging/SystemdJournal2Gelf.nix @@ -716,6 +720,7 @@ ./services/misc/libreddit.nix ./services/misc/lidarr.nix ./services/misc/lifecycled.nix + ./services/misc/llama-cpp.nix ./services/misc/logkeys.nix ./services/misc/mame.nix ./services/misc/mbpfan.nix @@ -784,6 +789,7 @@ ./services/misc/tiddlywiki.nix ./services/misc/tp-auto-kbbl.nix ./services/misc/tuxclocker.nix + ./services/misc/transfer-sh.nix ./services/misc/tzupdate.nix ./services/misc/uhub.nix ./services/misc/weechat.nix @@ -1323,6 +1329,7 @@ ./services/web-apps/mastodon.nix ./services/web-apps/matomo.nix ./services/web-apps/mattermost.nix + ./services/web-apps/mealie.nix ./services/web-apps/mediawiki.nix ./services/web-apps/meme-bingo-web.nix ./services/web-apps/microbin.nix diff --git a/nixos/modules/programs/coolercontrol.nix b/nixos/modules/programs/coolercontrol.nix new file mode 100644 index 0000000000000..6e7299ad16b72 --- /dev/null +++ b/nixos/modules/programs/coolercontrol.nix @@ -0,0 +1,37 @@ +{ config +, lib +, pkgs +, ... +}: + +let + cfg = config.programs.coolercontrol; +in +{ + ##### interface + options = { + programs.coolercontrol.enable = lib.mkEnableOption (lib.mdDoc "CoolerControl GUI & its background services"); + }; + + ##### implementation + config = lib.mkIf cfg.enable { + environment.systemPackages = with pkgs.coolercontrol; [ + coolercontrol-gui + ]; + + systemd = { + packages = with pkgs.coolercontrol; [ + coolercontrol-liqctld + coolercontrold + ]; + + # https://github.com/NixOS/nixpkgs/issues/81138 + services = { + coolercontrol-liqctld.wantedBy = [ "multi-user.target" ]; + coolercontrold.wantedBy = [ "multi-user.target" ]; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ OPNA2608 codifryed ]; +} diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix index 8f82de0336667..179d2de87cc58 100644 --- a/nixos/modules/programs/gnupg.nix +++ b/nixos/modules/programs/gnupg.nix @@ -15,6 +15,7 @@ let defaultPinentryFlavor = if xserverCfg.desktopManager.lxqt.enable || xserverCfg.desktopManager.plasma5.enable + || xserverCfg.desktopManager.plasma6.enable || xserverCfg.desktopManager.deepin.enable then "qt" else if xserverCfg.desktopManager.xfce.enable then diff --git a/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix index a16fad03eefe0..8cdf1eb4e6452 100644 --- a/nixos/modules/programs/kdeconnect.nix +++ b/nixos/modules/programs/kdeconnect.nix @@ -20,7 +20,10 @@ with lib; cfg = config.programs.kdeconnect; in mkIf cfg.enable { - environment.systemPackages = [ cfg.package ]; + environment.systemPackages = [ + cfg.package + pkgs.sshfs + ]; networking.firewall = rec { allowedTCPPortRanges = [ { from = 1714; to = 1764; } ]; allowedUDPPortRanges = allowedTCPPortRanges; diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix index c7f1e622f7baf..d6e2a82af100e 100644 --- a/nixos/modules/programs/steam.nix +++ b/nixos/modules/programs/steam.nix @@ -56,6 +56,8 @@ in { # use the setuid wrapped bubblewrap bubblewrap = "${config.security.wrapperDir}/.."; }; + } // optionalAttrs cfg.extest.enable { + extraEnv.LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so"; }); description = lib.mdDoc '' The Steam package to use. Additional libraries are added from the system @@ -114,6 +116,11 @@ in { }; }; }; + + extest.enable = mkEnableOption (lib.mdDoc '' + Load the extest library into Steam, to translate X11 input events to + uinput events (e.g. for using Steam Input on Wayland) + ''); }; config = mkIf cfg.enable { diff --git a/nixos/modules/programs/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix index 57ee629b28810..ca2503ae5da77 100644 --- a/nixos/modules/programs/wayland/sway.nix +++ b/nixos/modules/programs/wayland/sway.nix @@ -119,10 +119,10 @@ in { extraPackages = mkOption { type = with types; listOf package; default = with pkgs; [ - swaylock swayidle foot dmenu + swaylock swayidle foot dmenu wmenu ]; defaultText = literalExpression '' - with pkgs; [ swaylock swayidle foot dmenu ]; + with pkgs; [ swaylock swayidle foot dmenu wmenu ]; ''; example = literalExpression '' with pkgs; [ diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index ed03254cb5ee5..560e5eff5c39a 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -96,6 +96,10 @@ let pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in { + imports = [ + (lib.mkRenamedOptionModule [ "enableKwallet" ] [ "kwallet" "enable" ]) + ]; + options = { name = mkOption { @@ -462,16 +466,23 @@ let ''; }; - enableKwallet = mkOption { - default = false; - type = types.bool; - description = lib.mdDoc '' - If enabled, pam_wallet will attempt to automatically unlock the - user's default KDE wallet upon login. If the user has no wallet named - "kdewallet", or the login password does not match their wallet - password, KDE will prompt separately after login. - ''; + kwallet = { + enable = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + If enabled, pam_wallet will attempt to automatically unlock the + user's default KDE wallet upon login. If the user has no wallet named + "kdewallet", or the login password does not match their wallet + password, KDE will prompt separately after login. + ''; + }; + + package = mkPackageOption pkgs.plasma5Packages "kwallet-pam" { + pkgsText = "pkgs.plasma5Packages"; + }; }; + sssdStrictAccess = mkOption { default = false; type = types.bool; @@ -686,7 +697,7 @@ let (config.security.pam.enableEcryptfs || config.security.pam.enableFscrypt || cfg.pamMount - || cfg.enableKwallet + || cfg.kwallet.enable || cfg.enableGnomeKeyring || config.services.intune.enable || cfg.googleAuthenticator.enable @@ -711,9 +722,7 @@ let { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = { disable_interactive = true; }; } - { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = { - kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5"; - }; } + { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; } { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; } { name = "intune"; enable = config.services.intune.enable; control = "optional"; modulePath = "${pkgs.intune-portal}/lib/security/pam_intune.so"; } { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = { @@ -848,9 +857,7 @@ let order = "user,group,default"; debug = true; }; } - { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = { - kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5"; - }; } + { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; } { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = { auto_start = true; }; } @@ -1458,9 +1465,9 @@ in ''; } { - assertion = config.security.pam.zfs.enable -> (config.boot.zfs.enabled || config.boot.zfs.enableUnstable); + assertion = config.security.pam.zfs.enable -> config.boot.zfs.enabled; message = '' - `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled` or `boot.zfs.enableUnstable`). + `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled`). ''; } { diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix index aa24c0842baba..09448833620c6 100644 --- a/nixos/modules/services/desktops/pipewire/pipewire.nix +++ b/nixos/modules/services/desktops/pipewire/pipewire.nix @@ -246,6 +246,9 @@ in { description = lib.mdDoc '' List of packages that provide PipeWire configuration, in the form of `share/pipewire/*/*.conf` files. + + LV2 dependencies will be picked up from config packages automatically + via `passthru.requiredLv2Packages`. ''; }; @@ -258,7 +261,8 @@ in { be made available to PipeWire for [filter chains][wiki-filter-chain]. Config packages have their required LV2 plugins added automatically, - so they don't need to be specified here. + so they don't need to be specified here. Config packages need to set + `passthru.requiredLv2Packages` for this to work. [wiki-filter-chain]: https://docs.pipewire.org/page_module_filter_chain.html ''; @@ -293,6 +297,18 @@ in { assertion = (cfg.alsa.enable || cfg.pulse.enable) -> cfg.audio.enable; message = "Using PipeWire's ALSA/PulseAudio compatibility layers requires running PipeWire as the sound server. Set `services.pipewire.audio.enable` to true."; } + { + assertion = builtins.length + (builtins.attrNames + ( + lib.filterAttrs + (name: value: + lib.hasPrefix "pipewire/" name || name == "pipewire" + ) + config.environment.etc + )) == 1; + message = "Using `environment.etc.\"pipewire<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` instead."; + } ]; environment.systemPackages = [ cfg.package ] diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix index dc4d726d76327..009d68bd4f28d 100644 --- a/nixos/modules/services/desktops/pipewire/wireplumber.nix +++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix @@ -30,6 +30,9 @@ in description = lib.mdDoc '' List of packages that provide WirePlumber configuration, in the form of `share/wireplumber/*/*.lua` files. + + LV2 dependencies will be picked up from config packages automatically + via `passthru.requiredLv2Packages`. ''; }; @@ -42,7 +45,8 @@ in be made available to WirePlumber for [filter chains][wiki-filter-chain]. Config packages have their required LV2 plugins added automatically, - so they don't need to be specified here. + so they don't need to be specified here. Config packages need to set + `passthru.requiredLv2Packages` for this to work. [wiki-filter-chain]: https://docs.pipewire.org/page_module_filter_chain.html ''; @@ -56,13 +60,13 @@ in -- PipeWire is not used for audio, so prevent it from grabbing audio devices alsa_monitor.enable = function() end ''; - systemwideConfigPkg = pkgs.writeTextDir "wireplumber/main.lua.d/80-systemwide.lua" '' + systemwideConfigPkg = pkgs.writeTextDir "share/wireplumber/main.lua.d/80-systemwide.lua" '' -- When running system-wide, these settings need to be disabled (they -- use functions that aren't available on the system dbus). alsa_monitor.properties["alsa.reserve"] = false default_access.properties["enable-flatpak-portal"] = false ''; - systemwideBluetoothConfigPkg = pkgs.writeTextDir "wireplumber/bluetooth.lua.d/80-systemwide.lua" '' + systemwideBluetoothConfigPkg = pkgs.writeTextDir "share/wireplumber/bluetooth.lua.d/80-systemwide.lua" '' -- When running system-wide, logind-integration needs to be disabled. bluez_monitor.properties["with-logind"] = false ''; @@ -98,6 +102,18 @@ in assertion = !config.hardware.bluetooth.hsphfpd.enable; message = "Using WirePlumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false"; } + { + assertion = builtins.length + (builtins.attrNames + ( + lib.filterAttrs + (name: value: + lib.hasPrefix "wireplumber/" name || name == "wireplumber" + ) + config.environment.etc + )) == 1; + message = "Using `environment.etc.\"wireplumber<...>\"` directly is no longer supported in 24.05. Use `services.pipewire.wireplumber.configPackages` instead."; + } ]; environment.systemPackages = [ cfg.package ]; diff --git a/nixos/modules/services/development/nixseparatedebuginfod.nix b/nixos/modules/services/development/nixseparatedebuginfod.nix index daf85153d339f..a2ec0d2c80e1f 100644 --- a/nixos/modules/services/development/nixseparatedebuginfod.nix +++ b/nixos/modules/services/development/nixseparatedebuginfod.nix @@ -90,7 +90,9 @@ in users.groups.nixseparatedebuginfod = { }; - nix.settings.extra-allowed-users = [ "nixseparatedebuginfod" ]; + nix.settings = lib.optionalAttrs (lib.versionAtLeast config.nix.package.version "2.4") { + extra-allowed-users = [ "nixseparatedebuginfod" ]; + }; environment.variables.DEBUGINFOD_URLS = "http://${url}"; diff --git a/nixos/modules/services/games/armagetronad.nix b/nixos/modules/services/games/armagetronad.nix new file mode 100644 index 0000000000000..f79818e0e53b5 --- /dev/null +++ b/nixos/modules/services/games/armagetronad.nix @@ -0,0 +1,268 @@ +{ config, lib, pkgs, ... }: +let + inherit (lib) mkEnableOption mkIf mkOption mkMerge literalExpression; + inherit (lib) mapAttrsToList filterAttrs unique recursiveUpdate types; + + mkValueStringArmagetron = with lib; v: + if isInt v then toString v + else if isFloat v then toString v + else if isString v then v + else if true == v then "1" + else if false == v then "0" + else if null == v then "" + else throw "unsupported type: ${builtins.typeOf v}: ${(lib.generators.toPretty {} v)}"; + + settingsFormat = pkgs.formats.keyValue { + mkKeyValue = lib.generators.mkKeyValueDefault + { + mkValueString = mkValueStringArmagetron; + } " "; + listsAsDuplicateKeys = true; + }; + + cfg = config.services.armagetronad; + enabledServers = lib.filterAttrs (n: v: v.enable) cfg.servers; + nameToId = serverName: "armagetronad-${serverName}"; + getStateDirectory = serverName: "armagetronad/${serverName}"; + getServerRoot = serverName: "/var/lib/${getStateDirectory serverName}"; +in +{ + options = { + services.armagetronad = { + servers = mkOption { + description = lib.mdDoc "Armagetron server definitions."; + default = { }; + type = types.attrsOf (types.submodule { + options = { + enable = mkEnableOption (lib.mdDoc "armagetronad"); + + package = lib.mkPackageOptionMD pkgs "armagetronad-dedicated" { + example = '' + pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated + ''; + extraDescription = '' + Ensure that you use a derivation which contains the path `bin/armagetronad-dedicated`. + ''; + }; + + host = mkOption { + type = types.str; + default = "0.0.0.0"; + description = lib.mdDoc "Host to listen on. Used for SERVER_IP."; + }; + + port = mkOption { + type = types.port; + default = 4534; + description = lib.mdDoc "Port to listen on. Used for SERVER_PORT."; + }; + + dns = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc "DNS address to use for this server. Optional."; + }; + + openFirewall = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Set to true to open the configured UDP port for Armagetron Advanced."; + }; + + name = mkOption { + type = types.str; + description = "The name of this server."; + }; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + description = lib.mdDoc '' + Armagetron Advanced server rules configuration. Refer to: + <https://wiki.armagetronad.org/index.php?title=Console_Commands> + or `armagetronad-dedicated --doc` for a list. + + This attrset is used to populate `settings_custom.cfg`; see: + <https://wiki.armagetronad.org/index.php/Configuration_Files> + ''; + example = literalExpression '' + { + CYCLE_RUBBER = 40; + } + ''; + }; + + roundSettings = mkOption { + type = settingsFormat.type; + default = { }; + description = lib.mdDoc '' + Armagetron Advanced server per-round configuration. Refer to: + <https://wiki.armagetronad.org/index.php?title=Console_Commands> + or `armagetronad-dedicated --doc` for a list. + + This attrset is used to populate `everytime.cfg`; see: + <https://wiki.armagetronad.org/index.php/Configuration_Files> + ''; + example = literalExpression '' + { + SAY = [ + "Hosted on NixOS" + "https://nixos.org" + "iD Tech High Rubber rul3z!! Happy New Year 2008!!1" + ]; + } + ''; + }; + }; + }); + }; + }; + }; + + config = mkIf (enabledServers != { }) { + systemd.tmpfiles.settings = mkMerge (mapAttrsToList + (serverName: serverCfg: + let + serverId = nameToId serverName; + serverRoot = getServerRoot serverName; + serverInfo = ( + { + SERVER_IP = serverCfg.host; + SERVER_PORT = serverCfg.port; + SERVER_NAME = serverCfg.name; + } // (lib.optionalAttrs (serverCfg.dns != null) { SERVER_DNS = serverCfg.dns; }) + ); + customSettings = serverCfg.settings; + everytimeSettings = serverCfg.roundSettings; + + serverInfoCfg = settingsFormat.generate "server_info.${serverName}.cfg" serverInfo; + customSettingsCfg = settingsFormat.generate "settings_custom.${serverName}.cfg" customSettings; + everytimeSettingsCfg = settingsFormat.generate "everytime.${serverName}.cfg" everytimeSettings; + in + { + "10-armagetronad-${serverId}" = { + "${serverRoot}/data" = { + d = { + group = serverId; + user = serverId; + mode = "0750"; + }; + }; + "${serverRoot}/settings" = { + d = { + group = serverId; + user = serverId; + mode = "0750"; + }; + }; + "${serverRoot}/var" = { + d = { + group = serverId; + user = serverId; + mode = "0750"; + }; + }; + "${serverRoot}/resource" = { + d = { + group = serverId; + user = serverId; + mode = "0750"; + }; + }; + "${serverRoot}/input" = { + "f+" = { + group = serverId; + user = serverId; + mode = "0640"; + }; + }; + "${serverRoot}/settings/server_info.cfg" = { + "L+" = { + argument = "${serverInfoCfg}"; + }; + }; + "${serverRoot}/settings/settings_custom.cfg" = { + "L+" = { + argument = "${customSettingsCfg}"; + }; + }; + "${serverRoot}/settings/everytime.cfg" = { + "L+" = { + argument = "${everytimeSettingsCfg}"; + }; + }; + }; + } + ) + enabledServers + ); + + systemd.services = mkMerge (mapAttrsToList + (serverName: serverCfg: + let + serverId = nameToId serverName; + in + { + "armagetronad-${serverName}" = { + description = "Armagetron Advanced Dedicated Server for ${serverName}"; + wants = [ "basic.target" ]; + after = [ "basic.target" "network.target" "multi-user.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = + let + serverRoot = getServerRoot serverName; + in + { + Type = "simple"; + StateDirectory = getStateDirectory serverName; + ExecStart = "${lib.getExe serverCfg.package} --daemon --input ${serverRoot}/input --userdatadir ${serverRoot}/data --userconfigdir ${serverRoot}/settings --vardir ${serverRoot}/var --autoresourcedir ${serverRoot}/resource"; + Restart = "on-failure"; + CapabilityBoundingSet = ""; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictNamespaces = true; + RestrictSUIDSGID = true; + User = serverId; + Group = serverId; + }; + }; + }) + enabledServers + ); + + networking.firewall.allowedUDPPorts = + unique (mapAttrsToList (serverName: serverCfg: serverCfg.port) (filterAttrs (serverName: serverCfg: serverCfg.openFirewall) enabledServers)); + + users.users = mkMerge (mapAttrsToList + (serverName: serverCfg: + { + ${nameToId serverName} = { + group = nameToId serverName; + description = "Armagetron Advanced dedicated user for server ${serverName}"; + isSystemUser = true; + }; + }) + enabledServers + ); + + users.groups = mkMerge (mapAttrsToList + (serverName: serverCfg: + { + ${nameToId serverName} = { }; + }) + enabledServers + ); + }; +} diff --git a/nixos/modules/services/hardware/monado.nix b/nixos/modules/services/hardware/monado.nix new file mode 100644 index 0000000000000..9f9c6c39a0b49 --- /dev/null +++ b/nixos/modules/services/hardware/monado.nix @@ -0,0 +1,102 @@ +{ config +, lib +, pkgs +, ... +}: +let + inherit (lib) mkDefault mkEnableOption mkIf mkOption mkPackageOption types; + + cfg = config.services.monado; + +in +{ + options.services.monado = { + enable = mkEnableOption "Monado user service"; + + package = mkPackageOption pkgs "monado" { }; + + defaultRuntime = mkOption { + type = types.bool; + description = '' + Whether to enable Monado as the default OpenXR runtime on the system. + + Note that applications can bypass this option by setting an active + runtime in a writable XDG_CONFIG_DIRS location like `~/.config`. + ''; + default = false; + example = true; + }; + + highPriority = mkEnableOption "high priority capability for monado-service" + // mkOption { default = true; }; + }; + + config = mkIf cfg.enable { + security.wrappers."monado-service" = mkIf cfg.highPriority { + setuid = false; + owner = "root"; + group = "root"; + # cap_sys_nice needed for asynchronous reprojection + capabilities = "cap_sys_nice+eip"; + source = lib.getExe' cfg.package "monado-service"; + }; + + services.udev.packages = with pkgs; [ xr-hardware ]; + + systemd.user = { + services.monado = { + description = "Monado XR runtime service module"; + requires = [ "monado.socket" ]; + conflicts = [ "monado-dev.service" ]; + + unitConfig.ConditionUser = "!root"; + + environment = { + # Default options + # https://gitlab.freedesktop.org/monado/monado/-/blob/4548e1738591d0904f8db4df8ede652ece889a76/src/xrt/targets/service/monado.in.service#L12 + XRT_COMPOSITOR_LOG = mkDefault "debug"; + XRT_PRINT_OPTIONS = mkDefault "on"; + IPC_EXIT_ON_DISCONNECT = mkDefault "off"; + }; + + serviceConfig = { + ExecStart = + if cfg.highPriority + then "${config.security.wrapperDir}/monado-service" + else lib.getExe' cfg.package "monado-service"; + Restart = "no"; + }; + + restartTriggers = [ cfg.package ]; + }; + + sockets.monado = { + description = "Monado XR service module connection socket"; + conflicts = [ "monado-dev.service" ]; + + unitConfig.ConditionUser = "!root"; + + socketConfig = { + ListenStream = "%t/monado_comp_ipc"; + RemoveOnStop = true; + + # If Monado crashes while starting up, we want to close incoming OpenXR connections + FlushPending = true; + }; + + restartTriggers = [ cfg.package ]; + + wantedBy = [ "sockets.target" ]; + }; + }; + + environment.systemPackages = [ cfg.package ]; + environment.pathsToLink = [ "/share/openxr" ]; + + environment.etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime { + source = "${cfg.package}/share/openxr/1/openxr_monado.json"; + }; + }; + + meta.maintainers = with lib.maintainers; [ Scrumplex ]; +} diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix index a90d234f65c0c..1aaa2d07b9bde 100644 --- a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix +++ b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/cdi-generate.nix @@ -1,37 +1,58 @@ -{ config, lib, pkgs }: let +{ + addDriverRunpath, + glibc, + jq, + lib, + nvidia-container-toolkit, + nvidia-driver, + runtimeShell, + writeScriptBin, +}: +let mountOptions = { options = ["ro" "nosuid" "nodev" "bind"]; }; mounts = [ - { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-cuda-mps-control"; + # FIXME: Making /usr mounts optional + { hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control"; containerPath = "/usr/bin/nvidia-cuda-mps-control"; } - { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-cuda-mps-server"; + { hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server"; containerPath = "/usr/bin/nvidia-cuda-mps-server"; } - { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-debugdump"; + { hostPath = lib.getExe' nvidia-driver "nvidia-debugdump"; containerPath = "/usr/bin/nvidia-debugdump"; } - { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-powerd"; + { hostPath = lib.getExe' nvidia-driver "nvidia-powerd"; containerPath = "/usr/bin/nvidia-powerd"; } - { hostPath = "${lib.getBin config.hardware.nvidia.package}/bin/nvidia-smi"; + { hostPath = lib.getExe' nvidia-driver "nvidia-smi"; containerPath = "/usr/bin/nvidia-smi"; } - { hostPath = "${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk"; + { hostPath = lib.getExe' nvidia-container-toolkit "nvidia-ctk"; containerPath = "/usr/bin/nvidia-ctk"; } - { hostPath = "${pkgs.glibc}/lib"; - containerPath = "${pkgs.glibc}/lib"; } - { hostPath = "${pkgs.glibc}/lib64"; - containerPath = "${pkgs.glibc}/lib64"; } + { hostPath = "${lib.getLib glibc}/lib"; + containerPath = "${lib.getLib glibc}/lib"; } + + # FIXME: use closureinfo + { + hostPath = addDriverRunpath.driverLink; + containerPath = addDriverRunpath.driverLink; + } + { hostPath = "${lib.getLib glibc}/lib"; + containerPath = "${lib.getLib glibc}/lib"; } + { hostPath = "${lib.getLib glibc}/lib64"; + containerPath = "${lib.getLib glibc}/lib64"; } ]; jqAddMountExpression = ".containerEdits.mounts[.containerEdits.mounts | length] |= . +"; mountsToJq = lib.concatMap (mount: - ["${pkgs.jq}/bin/jq '${jqAddMountExpression} ${builtins.toJSON (mount // mountOptions)}'"]) + ["${lib.getExe jq} '${jqAddMountExpression} ${builtins.toJSON (mount // mountOptions)}'"]) mounts; -in '' -#! ${pkgs.runtimeShell} +in +writeScriptBin "nvidia-cdi-generator" +'' +#! ${runtimeShell} function cdiGenerate { - ${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk cdi generate \ + ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"} cdi generate \ --format json \ - --ldconfig-path ${pkgs.glibc.bin}/bin/ldconfig \ - --library-search-path ${config.hardware.nvidia.package}/lib \ - --nvidia-ctk-path ${pkgs.nvidia-container-toolkit}/bin/nvidia-ctk + --ldconfig-path ${lib.getExe' glibc "ldconfig"} \ + --library-search-path ${lib.getLib nvidia-driver}/lib \ + --nvidia-ctk-path ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"} } cdiGenerate | \ diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix index 3c96e9c41be52..b95bdf191fad2 100644 --- a/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix +++ b/nixos/modules/services/hardware/nvidia-container-toolkit-cdi-generator/default.nix @@ -26,9 +26,11 @@ serviceConfig = { RuntimeDirectory = "cdi"; RemainAfterExit = true; - ExecStart = let - script = (pkgs.writeScriptBin "nvidia-cdi-generator" - (import ./cdi-generate.nix { inherit config lib pkgs; })); in (lib.getExe script); + ExecStart = + let + script = pkgs.callPackage ./cdi-generate.nix { nvidia-driver = config.hardware.nvidia.package; }; + in + lib.getExe script; Type = "oneshot"; }; }; diff --git a/nixos/modules/services/home-automation/ebusd.nix b/nixos/modules/services/home-automation/ebusd.nix index 519d116e0e55c..f68a8bdb6bfa2 100644 --- a/nixos/modules/services/home-automation/ebusd.nix +++ b/nixos/modules/services/home-automation/ebusd.nix @@ -15,12 +15,12 @@ let "--port=${toString cfg.port}" "--configpath=${cfg.configpath}" "--scanconfig=${cfg.scanconfig}" + "--log=all:${cfg.logs.all}" "--log=main:${cfg.logs.main}" "--log=network:${cfg.logs.network}" "--log=bus:${cfg.logs.bus}" "--log=update:${cfg.logs.update}" "--log=other:${cfg.logs.other}" - "--log=all:${cfg.logs.all}" ] ++ lib.optionals cfg.readonly [ "--readonly" ] ++ lib.optionals cfg.mqtt.enable [ diff --git a/nixos/modules/services/home-automation/matter-server.nix b/nixos/modules/services/home-automation/matter-server.nix new file mode 100644 index 0000000000000..864ef9e200837 --- /dev/null +++ b/nixos/modules/services/home-automation/matter-server.nix @@ -0,0 +1,125 @@ +{ lib +, pkgs +, config +, ... +}: + +with lib; + +let + cfg = config.services.matter-server; + storageDir = "matter-server"; + storagePath = "/var/lib/${storageDir}"; + vendorId = "4939"; # home-assistant vendor ID +in + +{ + meta.maintainers = with lib.maintainers; [ leonm1 ]; + + options.services.matter-server = with types; { + enable = mkEnableOption (lib.mdDoc "Matter-server"); + + package = mkPackageOptionMD pkgs "python-matter-server" { }; + + port = mkOption { + type = types.port; + default = 5580; + description = "Port to expose the matter-server service on."; + }; + + logLevel = mkOption { + type = types.enum [ "critical" "error" "warning" "info" "debug" ]; + default = "info"; + description = "Verbosity of logs from the matter-server"; + }; + + extraArgs = mkOption { + type = listOf str; + default = []; + description = '' + Extra arguments to pass to the matter-server executable. + See https://github.com/home-assistant-libs/python-matter-server?tab=readme-ov-file#running-the-development-server for options. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.matter-server = { + after = [ "network-online.target" ]; + before = [ "home-assistant.service" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + description = "Matter Server"; + environment.HOME = storagePath; + serviceConfig = { + ExecStart = (concatStringsSep " " [ + "${cfg.package}/bin/matter-server" + "--port" (toString cfg.port) + "--vendorid" vendorId + "--storage-path" storagePath + "--log-level" "${cfg.logLevel}" + "${escapeShellArgs cfg.extraArgs}" + ]); + # Start with a clean root filesystem, and allowlist what the container + # is permitted to access. + TemporaryFileSystem = "/"; + # Allowlist /nix/store (to allow the binary to find its dependencies) + # and dbus. + ReadOnlyPaths = "/nix/store /run/dbus"; + # Let systemd manage `/var/lib/matter-server` for us inside the + # ephemeral TemporaryFileSystem. + StateDirectory = storageDir; + # `python-matter-server` writes to /data even when a storage-path is + # specified. This bind-mount points /data at the systemd-managed + # /var/lib/matter-server, so all files get dropped into the state + # directory. + BindPaths = "${storagePath}:/data"; + + # Hardening bits + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + DevicePolicy = "closed"; + DynamicUser = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallFilter = concatStringsSep " " [ + "~" # Blocklist + "@clock" + "@cpu-emulation" + "@debug" + "@module" + "@mount" + "@obsolete" + "@privileged" + "@raw-io" + "@reboot" + "@resources" + "@swap" + ]; + UMask = "0077"; + }; + }; + }; +} + diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix index e8fbc05423d31..78d1d6339ed65 100644 --- a/nixos/modules/services/misc/docker-registry.nix +++ b/nixos/modules/services/misc/docker-registry.nix @@ -63,6 +63,12 @@ in { type = types.port; }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Opens the port used by the firewall."; + }; + storagePath = mkOption { type = types.nullOr types.path; default = "/var/lib/docker-registry"; @@ -154,5 +160,9 @@ in { isSystemUser = true; }; users.groups.docker-registry = {}; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; }; } diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index d9359d2b5cd44..3ac3beb4de078 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -1,27 +1,44 @@ -{ config, lib, pkgs, ... }: let +{ config, lib, pkgs, ... }: +let + inherit (lib) types; cfg = config.services.ollama; - -in { - + ollamaPackage = cfg.package.override { + inherit (cfg) acceleration; + linuxPackages = config.boot.kernelPackages // { + nvidia_x11 = config.hardware.nvidia.package; + }; + }; +in +{ options = { services.ollama = { enable = lib.mkEnableOption ( lib.mdDoc "Server for local large language models" ); listenAddress = lib.mkOption { - type = lib.types.str; + type = types.str; default = "127.0.0.1:11434"; description = lib.mdDoc '' Specifies the bind address on which the ollama server HTTP interface listens. ''; }; + acceleration = lib.mkOption { + type = types.nullOr (types.enum [ "rocm" "cuda" ]); + default = null; + example = "rocm"; + description = lib.mdDoc '' + Specifies the interface to use for hardware acceleration. + + - `rocm`: supported by modern AMD GPUs + - `cuda`: supported by modern NVIDIA GPUs + ''; + }; package = lib.mkPackageOption pkgs "ollama" { }; }; }; config = lib.mkIf cfg.enable { - systemd = { services.ollama = { wantedBy = [ "multi-user.target" ]; @@ -33,7 +50,7 @@ in { OLLAMA_HOST = cfg.listenAddress; }; serviceConfig = { - ExecStart = "${lib.getExe cfg.package} serve"; + ExecStart = "${lib.getExe ollamaPackage} serve"; WorkingDirectory = "/var/lib/ollama"; StateDirectory = [ "ollama" ]; DynamicUser = true; @@ -41,10 +58,8 @@ in { }; }; - environment.systemPackages = [ cfg.package ]; - + environment.systemPackages = [ ollamaPackage ]; }; - meta.maintainers = with lib.maintainers; [ onny ]; - + meta.maintainers = with lib.maintainers; [ abysssol onny ]; } diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix index 1256d8315c8b7..9314c4f3848d8 100644 --- a/nixos/modules/services/misc/paperless.nix +++ b/nixos/modules/services/misc/paperless.nix @@ -307,6 +307,9 @@ in Restart = "on-failure"; }; environment = env; + # Allow the consumer to access the private /tmp directory of the server. + # This is required to support consuming files via a local folder. + unitConfig.JoinsNamespaceOf = "paperless-task-queue.service"; }; systemd.services.paperless-web = { @@ -339,6 +342,7 @@ in User = cfg.user; Restart = "on-failure"; + LimitNOFILE = 65536; # gunicorn needs setuid, liblapack needs mbind SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid mbind" ]; # Needs to serve web page diff --git a/nixos/modules/services/misc/transfer-sh.nix b/nixos/modules/services/misc/transfer-sh.nix new file mode 100644 index 0000000000000..899d9dfc3c108 --- /dev/null +++ b/nixos/modules/services/misc/transfer-sh.nix @@ -0,0 +1,102 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.transfer-sh; + inherit (lib) + mkDefault mkEnableOption mkPackageOption mkIf mkOption + types mapAttrs isBool getExe boolToString mdDoc optionalAttrs; +in +{ + options.services.transfer-sh = { + enable = mkEnableOption (mdDoc "Easy and fast file sharing from the command-line"); + + package = mkPackageOption pkgs "transfer-sh" { }; + + settings = mkOption { + type = types.submodule { freeformType = with types; attrsOf (oneOf [ bool int str ]); }; + default = { }; + example = { + LISTENER = ":8080"; + BASEDIR = "/var/lib/transfer.sh"; + TLS_LISTENER_ONLY = false; + }; + description = mdDoc '' + Additional configuration for transfer-sh, see + <https://github.com/dutchcoders/transfer.sh#usage-1> + for supported values. + + For secrets use secretFile option instead. + ''; + }; + + provider = mkOption { + type = types.enum [ "local" "s3" "storj" "gdrive" ]; + default = "local"; + description = mdDoc "Storage providers to use"; + }; + + secretFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/secrets/transfer-sh.env"; + description = mdDoc '' + Path to file containing environment variables. + Useful for passing down secrets. + Some variables that can be considered secrets are: + - AWS_ACCESS_KEY + - AWS_ACCESS_KEY + - TLS_PRIVATE_KEY + - HTTP_AUTH_HTPASSWD + ''; + }; + }; + + config = + let + localProvider = (cfg.provider == "local"); + stateDirectory = "/var/lib/transfer.sh"; + in + mkIf cfg.enable + { + services.transfer-sh.settings = { + LISTENER = mkDefault ":8080"; + } // optionalAttrs localProvider { + BASEDIR = mkDefault stateDirectory; + }; + + systemd.services.transfer-sh = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = mapAttrs (_: v: if isBool v then boolToString v else toString v) cfg.settings; + serviceConfig = { + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + DevicePolicy = "closed"; + DynamicUser = true; + ExecStart = "${getExe cfg.package} --provider ${cfg.provider}"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = [ "native" ]; + SystemCallFilter = [ "@system-service" ]; + StateDirectory = baseNameOf stateDirectory; + } // optionalAttrs (cfg.secretFile != null) { + EnvironmentFile = cfg.secretFile; + } // optionalAttrs localProvider { + ReadWritePaths = cfg.settings.BASEDIR; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ ocfox ]; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nut.nix b/nixos/modules/services/monitoring/prometheus/exporters/nut.nix index 1c86b48b4509b..e58a394456a38 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/nut.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/nut.nix @@ -36,6 +36,17 @@ in provisioned outside of Nix store. ''; }; + nutVariables = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + List of NUT variable names to monitor. + + If no variables are set, all numeric variables will be exported automatically. + See the [upstream docs](https://github.com/DRuggeri/nut_exporter?tab=readme-ov-file#variables-and-information) + for more information. + ''; + }; }; serviceOpts = { script = '' @@ -44,7 +55,9 @@ in ${pkgs.prometheus-nut-exporter}/bin/nut_exporter \ --nut.server=${cfg.nutServer} \ --web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \ - ${optionalString (cfg.nutUser != "") "--nut.username=${cfg.nutUser}"} + ${optionalString (cfg.nutUser != "") "--nut.username=${cfg.nutUser}"} \ + ${optionalString (cfg.nutVariables != []) "--nut.vars_enable=${concatStringsSep "," cfg.nutVariables}"} \ + ${concatStringsSep " " cfg.extraFlags} ''; }; } diff --git a/nixos/modules/services/networking/bird-lg.nix b/nixos/modules/services/networking/bird-lg.nix index be9f4101e6abe..1c59f7a6ae7c6 100644 --- a/nixos/modules/services/networking/bird-lg.nix +++ b/nixos/modules/services/networking/bird-lg.nix @@ -194,8 +194,8 @@ in allowedIPs = mkOption { type = types.listOf types.str; default = [ ]; - example = [ "192.168.25.52" "192.168.25.53" ]; - description = lib.mdDoc "List of IPs to allow (default all allowed)."; + example = [ "192.168.25.52" "192.168.25.53" "192.168.0.0/24" ]; + description = lib.mdDoc "List of IPs or networks to allow (default all allowed)."; }; birdSocket = mkOption { diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 266a7ea1435e1..8d5ac02ba88be 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -13,6 +13,8 @@ let enableDHCP = config.networking.dhcpcd.enable && (config.networking.useDHCP || any (i: i.useDHCP == true) interfaces); + enableNTPService = (config.services.ntp.enable || config.services.ntpd-rs.enable || config.services.openntpd.enable || config.services.chrony.enable); + # Don't start dhcpcd on explicitly configured interfaces or on # interfaces that are part of a bridge, bond or sit device. ignoredInterfaces = @@ -89,20 +91,22 @@ let ${cfg.extraConfig} ''; - exitHook = pkgs.writeText "dhcpcd.exit-hook" - '' + exitHook = pkgs.writeText "dhcpcd.exit-hook" '' + ${optionalString enableNTPService '' if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then - # Restart ntpd. We need to restart it to make sure that it - # will actually do something: if ntpd cannot resolve the - # server hostnames in its config file, then it will never do - # anything ever again ("couldn't resolve ..., giving up on - # it"), so we silently lose time synchronisation. This also - # applies to openntpd. - /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service ntpd-rs.service || true + # Restart ntpd. We need to restart it to make sure that it will actually do something: + # if ntpd cannot resolve the server hostnames in its config file, then it will never do + # anything ever again ("couldn't resolve ..., giving up on it"), so we silently lose + # time synchronisation. This also applies to openntpd. + ${optionalString config.services.ntp.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service || true"} + ${optionalString config.services.ntpd-rs.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd-rs.service || true"} + ${optionalString config.services.openntpd.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart openntpd.service || true"} + ${optionalString config.services.chrony.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart chronyd.service || true"} fi + ''} - ${cfg.runHook} - ''; + ${cfg.runHook} + ''; in @@ -232,7 +236,7 @@ in wants = [ "network.target" ]; before = [ "network-online.target" ]; - restartTriggers = [ exitHook ]; + restartTriggers = optional (enableNTPService || cfg.runHook != "") [ exitHook ]; # Stopping dhcpcd during a reconfiguration is undesirable # because it brings down the network interfaces configured by @@ -261,7 +265,9 @@ in environment.systemPackages = [ dhcpcd ]; - environment.etc."dhcpcd.exit-hook".source = exitHook; + environment.etc."dhcpcd.exit-hook" = mkIf (enableNTPService || cfg.runHook != "") { + source = exitHook; + }; powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable '' diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix index ad9eefb422525..4a08f5ed23709 100644 --- a/nixos/modules/services/networking/mosquitto.nix +++ b/nixos/modules/services/networking/mosquitto.nix @@ -177,17 +177,6 @@ let '' ++ hashedLines)); - makeACLFile = idx: users: supplement: - pkgs.writeText "mosquitto-acl-${toString idx}.conf" - (concatStringsSep - "\n" - (flatten [ - supplement - (mapAttrsToList - (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl) - users) - ])); - authPluginOptions = with types; submodule { options = { plugin = mkOption { @@ -342,7 +331,7 @@ let formatListener = idx: listener: [ "listener ${toString listener.port} ${toString listener.address}" - "acl_file ${makeACLFile idx listener.users listener.acl}" + "acl_file /etc/mosquitto/acl-${toString idx}.conf" ] ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" ++ formatFreeform {} listener.settings @@ -698,6 +687,27 @@ in cfg.listeners); }; + environment.etc = listToAttrs ( + imap0 + (idx: listener: { + name = "mosquitto/acl-${toString idx}.conf"; + value = { + user = config.users.users.mosquitto.name; + group = config.users.users.mosquitto.group; + mode = "0400"; + text = (concatStringsSep + "\n" + (flatten [ + listener.acl + (mapAttrsToList + (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl) + listener.users) + ])); + }; + }) + cfg.listeners + ); + users.users.mosquitto = { description = "Mosquitto MQTT Broker Daemon owner"; group = "mosquitto"; diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix index 938d585e31793..5bbf875f0d57b 100644 --- a/nixos/modules/services/networking/searx.nix +++ b/nixos/modules/services/networking/searx.nix @@ -213,7 +213,7 @@ in serviceConfig = { User = "searx"; Group = "searx"; - ExecStart = "${cfg.package}/bin/searx-run"; + ExecStart = lib.getExe cfg.package; } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = builtins.toPath cfg.environmentFile; }; environment = { diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix index f11fe57d6ce5e..972299a4697a0 100644 --- a/nixos/modules/services/networking/tailscale.nix +++ b/nixos/modules/services/networking/tailscale.nix @@ -66,6 +66,13 @@ in { default = []; example = ["--ssh"]; }; + + extraDaemonFlags = mkOption { + description = lib.mdDoc "Extra flags to pass to {command}`tailscaled`."; + type = types.listOf types.str; + default = []; + example = ["--no-logs-no-support"]; + }; }; config = mkIf cfg.enable { @@ -80,7 +87,7 @@ in { ] ++ lib.optional config.networking.resolvconf.enable config.networking.resolvconf.package; serviceConfig.Environment = [ "PORT=${toString cfg.port}" - ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName}"'' + ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName} ${lib.concatStringsSep " " cfg.extraDaemonFlags}"'' ] ++ (lib.optionals (cfg.permitCertUid != null) [ "TS_PERMIT_CERT_UID=${cfg.permitCertUid}" ]); diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix index 616b32f117979..8438e472e11ee 100644 --- a/nixos/modules/services/networking/unbound.nix +++ b/nixos/modules/services/networking/unbound.nix @@ -24,12 +24,24 @@ let confNoServer = concatStringsSep "\n" ((mapAttrsToList (toConf "") (builtins.removeAttrs cfg.settings [ "server" ])) ++ [""]); confServer = concatStringsSep "\n" (mapAttrsToList (toConf " ") (builtins.removeAttrs cfg.settings.server [ "define-tag" ])); - confFile = pkgs.writeText "unbound.conf" '' + confFileUnchecked = pkgs.writeText "unbound.conf" '' server: ${optionalString (cfg.settings.server.define-tag != "") (toOption " " "define-tag" cfg.settings.server.define-tag)} ${confServer} ${confNoServer} ''; + confFile = if cfg.checkconf then pkgs.runCommandLocal "unbound-checkconf" { } '' + cp ${confFileUnchecked} unbound.conf + + # fake stateDir which is not accesible in the sandbox + mkdir -p $PWD/state + sed -i unbound.conf \ + -e '/auto-trust-anchor-file/d' \ + -e "s|${cfg.stateDir}|$PWD/state|" + ${cfg.package}/bin/unbound-checkconf unbound.conf + + cp ${confFileUnchecked} $out + '' else confFileUnchecked; rootTrustAnchorFile = "${cfg.stateDir}/root.key"; @@ -62,6 +74,17 @@ in { description = lib.mdDoc "Directory holding all state for unbound to run."; }; + checkconf = mkOption { + type = types.bool; + default = !cfg.settings ? include; + defaultText = "!config.services.unbound.settings ? include"; + description = lib.mdDoc '' + Wether to check the resulting config file with unbound checkconf for syntax errors. + + If settings.include is used, then this options is disabled, as the import can likely not be resolved at build time. + ''; + }; + resolveLocalQueries = mkOption { type = types.bool; default = true; diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix index c659d93b40872..9d074c3027d02 100644 --- a/nixos/modules/services/security/kanidm.nix +++ b/nixos/modules/services/security/kanidm.nix @@ -132,6 +132,28 @@ in default = "WriteReplica"; type = lib.types.enum [ "WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica" ]; }; + online_backup = { + path = lib.mkOption { + description = lib.mdDoc "Path to the output directory for backups."; + type = lib.types.path; + default = "/var/lib/kanidm/backups"; + }; + schedule = lib.mkOption { + description = lib.mdDoc "The schedule for backups in cron format."; + type = lib.types.str; + default = "00 22 * * *"; + }; + versions = lib.mkOption { + description = lib.mdDoc '' + Number of backups to keep. + + The default is set to `0`, in order to disable backups by default. + ''; + type = lib.types.ints.unsigned; + default = 0; + example = 7; + }; + }; }; }; default = { }; @@ -233,6 +255,14 @@ in environment.systemPackages = lib.mkIf cfg.enableClient [ cfg.package ]; + systemd.tmpfiles.settings."10-kanidm" = { + ${cfg.serverSettings.online_backup.path}.d = { + mode = "0700"; + user = "kanidm"; + group = "kanidm"; + }; + }; + systemd.services.kanidm = lib.mkIf cfg.enableServer { description = "kanidm identity management daemon"; wantedBy = [ "multi-user.target" ]; @@ -253,6 +283,8 @@ in BindPaths = [ # To create the socket "/run/kanidmd:/run/kanidmd" + # To store backups + cfg.serverSettings.online_backup.path ]; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix index 470db735bf649..60d8015d0ceeb 100644 --- a/nixos/modules/services/security/vaultwarden/default.nix +++ b/nixos/modules/services/security/vaultwarden/default.nix @@ -180,7 +180,6 @@ in { users.groups.vaultwarden = { }; systemd.services.vaultwarden = { - aliases = [ "bitwarden_rs.service" ]; after = [ "network.target" ]; path = with pkgs; [ openssl ]; serviceConfig = { @@ -202,7 +201,6 @@ in { }; systemd.services.backup-vaultwarden = mkIf (cfg.backupDir != null) { - aliases = [ "backup-bitwarden_rs.service" ]; description = "Backup vaultwarden"; environment = { DATA_FOLDER = "/var/lib/bitwarden_rs"; @@ -222,7 +220,6 @@ in { }; systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) { - aliases = [ "backup-bitwarden_rs.timer" ]; description = "Backup vaultwarden on time"; timerConfig = { OnCalendar = mkDefault "23:00"; @@ -240,6 +237,9 @@ in { }; }; - # uses attributes of the linked package - meta.buildDocsInSandbox = false; + meta = { + # uses attributes of the linked package + buildDocsInSandbox = false; + maintainers = with lib.maintainers; [ dotlambda SuperSandro2000 ]; + }; } diff --git a/nixos/modules/services/web-apps/mealie.nix b/nixos/modules/services/web-apps/mealie.nix new file mode 100644 index 0000000000000..8bb7542c6b56d --- /dev/null +++ b/nixos/modules/services/web-apps/mealie.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ...}: +let + cfg = config.services.mealie; + pkg = cfg.package; +in +{ + options.services.mealie = { + enable = lib.mkEnableOption "Mealie, a recipe manager and meal planner"; + + package = lib.mkPackageOption pkgs "mealie" { }; + + listenAddress = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Address on which the service should listen."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 9000; + description = "Port on which to serve the Mealie service."; + }; + + settings = lib.mkOption { + type = with lib.types; attrsOf anything; + default = {}; + description = lib.mdDoc '' + Configuration of the Mealie service. + + See [the mealie documentation](https://nightly.mealie.io/documentation/getting-started/installation/backend-config/) for available options and default values. + + In addition to the official documentation, you can set {env}`MEALIE_LOG_FILE`. + ''; + example = { + ALLOW_SIGNUP = "false"; + }; + }; + + credentialsFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/secrets/mealie-credentials.env"; + description = '' + File containing credentials used in mealie such as {env}`POSTGRES_PASSWORD` + or sensitive LDAP options. + + Expects the format of an `EnvironmentFile=`, as described by {manpage}`systemd.exec(5)`. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.mealie = { + description = "Mealie, a self hosted recipe manager and meal planner"; + + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + PRODUCTION = "true"; + ALEMBIC_CONFIG_FILE="${pkg}/config/alembic.ini"; + API_PORT = toString cfg.port; + DATA_DIR = "/var/lib/mealie"; + CRF_MODEL_PATH = "/var/lib/mealie/model.crfmodel"; + } // (builtins.mapAttrs (_: val: toString val) cfg.settings); + + serviceConfig = { + DynamicUser = true; + User = "mealie"; + ExecStartPre = "${pkg}/libexec/init_db"; + ExecStart = "${lib.getExe pkg} -b ${cfg.listenAddress}:${builtins.toString cfg.port}"; + EnvironmentFile = lib.mkIf (cfg.credentialsFile != null) cfg.credentialsFile; + StateDirectory = "mealie"; + StandardOutput="journal"; + }; + }; + }; +} diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix index 1a5b7d0c24e9b..16b6fb0d655d7 100644 --- a/nixos/modules/services/web-apps/miniflux.nix +++ b/nixos/modules/services/web-apps/miniflux.nix @@ -16,10 +16,20 @@ in { options = { services.miniflux = { - enable = mkEnableOption (lib.mdDoc "miniflux and creates a local postgres database for it"); + enable = mkEnableOption (lib.mdDoc "miniflux"); package = mkPackageOption pkgs "miniflux" { }; + createDatabaseLocally = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether a PostgreSQL database should be automatically created and + configured on the local host. If set to `false`, you need provision a + database yourself and make sure to create the hstore extension in it. + ''; + }; + config = mkOption { type = with types; attrsOf (oneOf [ str int ]); example = literalExpression '' @@ -38,7 +48,7 @@ in ''; }; - adminCredentialsFile = mkOption { + adminCredentialsFile = mkOption { type = types.path; description = lib.mdDoc '' File containing the ADMIN_USERNAME and @@ -51,14 +61,14 @@ in }; config = mkIf cfg.enable { - services.miniflux.config = { + services.miniflux.config = { LISTEN_ADDR = mkDefault defaultAddress; - DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux"; + DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux"; RUN_MIGRATIONS = 1; CREATE_ADMIN = 1; }; - services.postgresql = { + services.postgresql = lib.mkIf cfg.createDatabaseLocally { enable = true; ensureUsers = [ { name = "miniflux"; @@ -67,7 +77,7 @@ in ensureDatabases = [ "miniflux" ]; }; - systemd.services.miniflux-dbsetup = { + systemd.services.miniflux-dbsetup = lib.mkIf cfg.createDatabaseLocally { description = "Miniflux database setup"; requires = [ "postgresql.service" ]; after = [ "network.target" "postgresql.service" ]; @@ -81,8 +91,9 @@ in systemd.services.miniflux = { description = "Miniflux service"; wantedBy = [ "multi-user.target" ]; - requires = [ "miniflux-dbsetup.service" ]; - after = [ "network.target" "postgresql.service" "miniflux-dbsetup.service" ]; + requires = lib.optional cfg.createDatabaseLocally "miniflux-dbsetup.service"; + after = [ "network.target" ] + ++ lib.optionals cfg.createDatabaseLocally [ "postgresql.service" "miniflux-dbsetup.service" ]; serviceConfig = { ExecStart = "${cfg.package}/bin/miniflux"; @@ -129,6 +140,7 @@ in include "${pkgs.apparmorRulesFromClosure { name = "miniflux"; } cfg.package}" r ${cfg.package}/bin/miniflux, r @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size, + rw /run/miniflux/**, } ''; }; diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index 08f90dcf59d80..5cda4a00a9de5 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -45,7 +45,7 @@ let }; }; - webroot = pkgs.runCommand + webroot = pkgs.runCommandLocal "${cfg.package.name or "nextcloud"}-with-apps" { } '' diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix index ccf995fccf3e5..39eb7c65c6359 100644 --- a/nixos/modules/services/web-apps/photoprism.nix +++ b/nixos/modules/services/web-apps/photoprism.nix @@ -104,6 +104,7 @@ in StateDirectory = "photoprism"; WorkingDirectory = "/var/lib/photoprism"; RuntimeDirectory = "photoprism"; + ReadWritePaths = [ cfg.originalsPath cfg.importPath cfg.storagePath ]; LoadCredential = lib.optionalString (cfg.passwordFile != null) "PHOTOPRISM_ADMIN_PASSWORD:${cfg.passwordFile}"; diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix index b893f2c1f33c7..efa9c676d9a5d 100644 --- a/nixos/modules/services/web-apps/vikunja.nix +++ b/nixos/modules/services/web-apps/vikunja.nix @@ -9,10 +9,13 @@ let useMysql = cfg.database.type == "mysql"; usePostgresql = cfg.database.type == "postgres"; in { + imports = [ + (mkRemovedOptionModule [ "services" "vikunja" "setupNginx" ] "services.vikunja no longer supports the automatic set up of a nginx virtual host. Set up your own webserver config with a proxy pass to the vikunja service.") + ]; + options.services.vikunja = with lib; { enable = mkEnableOption (lib.mdDoc "vikunja service"); - package-api = mkPackageOption pkgs "vikunja-api" { }; - package-frontend = mkPackageOption pkgs "vikunja-frontend" { }; + package = mkPackageOption pkgs "vikunja" { }; environmentFiles = mkOption { type = types.listOf types.path; default = [ ]; @@ -21,25 +24,10 @@ in { For example passwords should be set in one of these files. ''; }; - setupNginx = mkOption { - type = types.bool; - default = config.services.nginx.enable; - defaultText = literalExpression "config.services.nginx.enable"; - description = lib.mdDoc '' - Whether to setup NGINX. - Further nginx configuration can be done by changing - {option}`services.nginx.virtualHosts.<frontendHostname>`. - This does not enable TLS or ACME by default. To enable this, set the - {option}`services.nginx.virtualHosts.<frontendHostname>.enableACME` to - `true` and if appropriate do the same for - {option}`services.nginx.virtualHosts.<frontendHostname>.forceSSL`. - ''; - }; frontendScheme = mkOption { type = types.enum [ "http" "https" ]; description = lib.mdDoc '' Whether the site is available via http or https. - This does not configure https or ACME in nginx! ''; }; frontendHostname = mkOption { @@ -104,42 +92,27 @@ in { }; }; - systemd.services.vikunja-api = { - description = "vikunja-api"; + systemd.services.vikunja = { + description = "vikunja"; after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; wantedBy = [ "multi-user.target" ]; - path = [ cfg.package-api ]; + path = [ cfg.package ]; restartTriggers = [ configFile ]; serviceConfig = { Type = "simple"; DynamicUser = true; StateDirectory = "vikunja"; - ExecStart = "${cfg.package-api}/bin/vikunja"; + ExecStart = "${cfg.package}/bin/vikunja"; Restart = "always"; EnvironmentFile = cfg.environmentFiles; }; }; - services.nginx.virtualHosts."${cfg.frontendHostname}" = mkIf cfg.setupNginx { - locations = { - "/" = { - root = cfg.package-frontend; - tryFiles = "try_files $uri $uri/ /"; - }; - "~* ^/(api|dav|\\.well-known)/" = { - proxyPass = "http://localhost:${toString cfg.port}"; - extraConfig = '' - client_max_body_size 20M; - ''; - }; - }; - }; - environment.etc."vikunja/config.yaml".source = configFile; environment.systemPackages = [ - cfg.package-api # for admin `vikunja` CLI + cfg.package # for admin `vikunja` CLI ]; }; } diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix index 66cb4ee29c0a9..ecb8d1e91bde2 100644 --- a/nixos/modules/services/x11/desktop-managers/default.nix +++ b/nixos/modules/services/x11/desktop-managers/default.nix @@ -18,7 +18,7 @@ in # determines the default: later modules (if enabled) are preferred. # E.g., if Plasma 5 is enabled, it supersedes xterm. imports = [ - ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./lumina.nix + ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./plasma6.nix ./lumina.nix ./lxqt.nix ./enlightenment.nix ./gnome.nix ./retroarch.nix ./kodi.nix ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix ./cinnamon.nix ./budgie.nix ./deepin.nix diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index 0eb492ce46843..7645b3070369c 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -362,7 +362,7 @@ in security.pam.services.kde = { allowNullPassword = true; }; - security.pam.services.login.enableKwallet = true; + security.pam.services.login.kwallet.enable = true; systemd.user.services = { plasma-early-setup = mkIf cfg.runUsingSystemd { diff --git a/nixos/modules/services/x11/desktop-managers/plasma6.nix b/nixos/modules/services/x11/desktop-managers/plasma6.nix new file mode 100644 index 0000000000000..1237261e0af7b --- /dev/null +++ b/nixos/modules/services/x11/desktop-managers/plasma6.nix @@ -0,0 +1,291 @@ +{ + config, + lib, + pkgs, + utils, + ... +}: let + xcfg = config.services.xserver; + cfg = xcfg.desktopManager.plasma6; + + inherit (pkgs) kdePackages; + inherit (lib) literalExpression mkDefault mkIf mkOption mkPackageOptionMD types; + + activationScript = '' + # will be rebuilt automatically + rm -fv $HOME/.cache/ksycoca* + ''; +in { + options = { + services.xserver.desktopManager.plasma6 = { + enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Enable the Plasma 6 (KDE 6) desktop environment."; + }; + + enableQt5Integration = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Enable Qt 5 integration (theming, etc). Disable for a pure Qt 6 system."; + }; + + notoPackage = mkPackageOptionMD pkgs "Noto fonts - used for UI by default" { + default = ["noto-fonts"]; + example = "noto-fonts-lgc-plus"; + }; + }; + + environment.plasma6.excludePackages = mkOption { + description = lib.mdDoc "List of default packages to exclude from the configuration"; + type = types.listOf types.package; + default = []; + example = literalExpression "[ pkgs.kdePackages.elisa ]"; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> !config.services.xserver.desktopManager.plasma5.enable; + message = "Cannot enable plasma5 and plasma6 at the same time!"; + } + ]; + + qt.enable = true; + environment.systemPackages = with kdePackages; let + requiredPackages = [ + # Hack? To make everything run on Wayland + qtwayland + # Needed to render SVG icons + qtsvg + + # Frameworks with globally loadable bits + frameworkintegration # provides Qt plugin + kauth # provides helper service + kcoreaddons # provides extra mime type info + kded # provides helper service + kfilemetadata # provides Qt plugins + kguiaddons # provides geo URL handlers + kiconthemes # provides Qt plugins + kimageformats # provides Qt plugins + kio # provides helper service + a bunch of other stuff + kpackage # provides kpackagetool tool + kservice # provides kbuildsycoca6 tool + kwallet # provides helper service + kwallet-pam # provides helper service + kwalletmanager # provides KCMs and stuff + plasma-activities # provides plasma-activities-cli tool + solid # provides solid-hardware6 tool + phonon-vlc # provides Phonon plugin + + # Core Plasma parts + kwin + pkgs.xwayland + + kscreen + libkscreen + + kscreenlocker + + kactivitymanagerd + kde-cli-tools + kglobalacceld + kwrited # wall message proxy, not to be confused with kwrite + + milou + polkit-kde-agent-1 + + plasma-desktop + plasma-workspace + + # Crash handler + drkonqi + + # Application integration + libplasma # provides Kirigami platform theme + plasma-integration # provides Qt platform theme + kde-gtk-config + + # Artwork + themes + breeze + breeze-icons + breeze-gtk + ocean-sound-theme + plasma-workspace-wallpapers + pkgs.hicolor-icon-theme # fallback icons + qqc2-breeze-style + qqc2-desktop-style + + # misc Plasma extras + kdeplasma-addons + + pkgs.xdg-user-dirs # recommended upstream + + # Plasma utilities + kmenuedit + + kinfocenter + plasma-systemmonitor + ksystemstats + libksysguard + + spectacle + systemsettings + kcmutils + + # Gear + baloo + dolphin + dolphin-plugins + ffmpegthumbs + kdegraphics-thumbnailers + kde-inotify-survey + kio-admin + kio-extras + kio-fuse + ]; + optionalPackages = [ + plasma-browser-integration + konsole + (lib.getBin qttools) # Expose qdbus in PATH + + ark + elisa + gwenview + okular + kate + khelpcenter + print-manager + ]; + in + requiredPackages + ++ utils.removePackagesByName optionalPackages config.environment.plasma6.excludePackages + ++ lib.optionals config.services.xserver.desktopManager.plasma6.enableQt5Integration [ + breeze.qt5 + plasma-integration.qt5 + pkgs.plasma5Packages.kwayland-integration + kio-extras-kf5 + ] + # Optional hardware support features + ++ lib.optionals config.hardware.bluetooth.enable [bluedevil bluez-qt pkgs.openobex pkgs.obexftp] + ++ lib.optional config.networking.networkmanager.enable plasma-nm + ++ lib.optional config.hardware.pulseaudio.enable plasma-pa + ++ lib.optional config.services.pipewire.pulse.enable plasma-pa + ++ lib.optional config.powerManagement.enable powerdevil + ++ lib.optional config.services.colord.enable colord-kde + ++ lib.optional config.services.hardware.bolt.enable plasma-thunderbolt + ++ lib.optionals config.services.samba.enable [kdenetwork-filesharing pkgs.samba] + ++ lib.optional config.services.xserver.wacom.enable wacomtablet + ++ lib.optional config.services.flatpak.enable flatpak-kcm; + + environment.pathsToLink = [ + # FIXME: modules should link subdirs of `/share` rather than relying on this + "/share" + "/libexec" # for drkonqi + ]; + + environment.etc."X11/xkb".source = xcfg.xkb.dir; + + # Add ~/.config/kdedefaults to XDG_CONFIG_DIRS for shells, since Plasma sets that. + # FIXME: maybe we should append to XDG_CONFIG_DIRS in /etc/set-environment instead? + environment.sessionVariables.XDG_CONFIG_DIRS = ["$HOME/.config/kdedefaults"]; + + # Needed for things that depend on other store.kde.org packages to install correctly, + # notably Plasma look-and-feel packages (a.k.a. Global Themes) + # + # FIXME: this is annoyingly impure and should really be fixed at source level somehow, + # but kpackage is a library so we can't just wrap the one thing invoking it and be done. + # This also means things won't work for people not on Plasma, but at least this way it + # works for SOME people. + environment.sessionVariables.KPACKAGE_DEP_RESOLVERS_PATH = "${kdePackages.frameworkintegration.out}/libexec/kf6/kpackagehandlers"; + + # Enable GTK applications to load SVG icons + services.xserver.gdk-pixbuf.modulePackages = [pkgs.librsvg]; + + fonts.packages = [cfg.notoPackage pkgs.hack-font]; + fonts.fontconfig.defaultFonts = { + monospace = ["Hack" "Noto Sans Mono"]; + sansSerif = ["Noto Sans"]; + serif = ["Noto Serif"]; + }; + + programs.ssh.askPassword = mkDefault "${kdePackages.ksshaskpass.out}/bin/ksshaskpass"; + + # Enable helpful DBus services. + services.accounts-daemon.enable = true; + # when changing an account picture the accounts-daemon reads a temporary file containing the image which systemsettings5 may place under /tmp + systemd.services.accounts-daemon.serviceConfig.PrivateTmp = false; + + services.power-profiles-daemon.enable = mkDefault true; + services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true); + services.udisks2.enable = true; + services.upower.enable = config.powerManagement.enable; + services.xserver.libinput.enable = mkDefault true; + + # Extra UDEV rules used by Solid + services.udev.packages = [ + # libmtp has "bin", "dev", "out" outputs. UDEV rules file is in "out". + pkgs.libmtp.out + pkgs.media-player-info + ]; + + # Set up Dr. Konqi as crash handler + systemd.packages = [kdePackages.drkonqi]; + systemd.services."drkonqi-coredump-processor@".wantedBy = ["systemd-coredump@.service"]; + + xdg.portal.enable = true; + xdg.portal.extraPortals = [kdePackages.xdg-desktop-portal-kde]; + xdg.portal.configPackages = mkDefault [kdePackages.xdg-desktop-portal-kde]; + services.pipewire.enable = mkDefault true; + + services.xserver.displayManager = { + sessionPackages = [kdePackages.plasma-workspace]; + defaultSession = mkDefault "plasma"; + }; + services.xserver.displayManager.sddm = { + package = kdePackages.sddm; + theme = mkDefault "breeze"; + extraPackages = with kdePackages; [ + breeze-icons + kirigami + plasma5support + qtsvg + qtvirtualkeyboard + ]; + }; + + security.pam.services = { + login.kwallet = { + enable = true; + package = kdePackages.kwallet-pam; + }; + kde.kwallet = { + enable = true; + package = kdePackages.kwallet-pam; + }; + kde-fingerprint = lib.mkIf config.services.fprintd.enable { fprintAuth = true; }; + kde-smartcard = lib.mkIf config.security.pam.p11.enable { p11Auth = true; }; + }; + + programs.dconf.enable = true; + + programs.firefox.nativeMessagingHosts.packages = [kdePackages.plasma-browser-integration]; + + programs.chromium = { + enablePlasmaBrowserIntegration = true; + plasmaBrowserIntegrationPackage = pkgs.kdePackages.plasma-browser-integration; + }; + + programs.kdeconnect.package = kdePackages.kdeconnect-kde; + + # FIXME: ugly hack. See #292632 for details. + system.userActivationScripts.rebuildSycoca = activationScript; + systemd.user.services.nixos-rebuild-sycoca = { + description = "Rebuild KDE system configuration cache"; + wantedBy = [ "graphical-session-pre.target" ]; + serviceConfig.Type = "oneshot"; + script = activationScript; + }; + }; +} diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix index 0576619cc8d28..5b7f4bc58d80c 100644 --- a/nixos/modules/services/x11/display-managers/sddm.nix +++ b/nixos/modules/services/x11/display-managers/sddm.nix @@ -7,7 +7,10 @@ let cfg = dmcfg.sddm; xEnv = config.systemd.services.display-manager.environment; - sddm = cfg.package; + sddm = cfg.package.override(old: { + withWayland = cfg.wayland.enable; + extraPackages = old.extraPackages or [] ++ cfg.extraPackages; + }); iniFmt = pkgs.formats.ini { }; @@ -140,6 +143,15 @@ in ''; }; + extraPackages = mkOption { + type = types.listOf types.package; + default = []; + defaultText = "[]"; + description = lib.mdDoc '' + Extra Qt plugins / QML libraries to add to the environment. + ''; + }; + autoNumlock = mkOption { type = types.bool; default = false; @@ -211,7 +223,7 @@ in keymap_variant = xcfg.xkb.variant; keymap_options = xcfg.xkb.options; }; - }; in "${pkgs.weston}/bin/weston --shell=fullscreen-shell.so -c ${westonIni}"; + }; in "${pkgs.weston}/bin/weston --shell=kiosk -c ${westonIni}"; description = lib.mdDoc "Command used to start the selected compositor"; }; }; @@ -235,15 +247,7 @@ in } ]; - services.xserver.displayManager.job = { - environment = { - # Load themes from system environment - QT_PLUGIN_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtPluginPrefix; - QML2_IMPORT_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtQmlPrefix; - }; - - execCmd = "exec /run/current-system/sw/bin/sddm"; - }; + services.xserver.displayManager.job.execCmd = "exec /run/current-system/sw/bin/sddm"; security.pam.services = { sddm.text = '' diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index a9978d7adf803..03bff1dee5b9d 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -17,6 +17,9 @@ from dataclasses import dataclass # These values will be replaced with actual values during the package build EFI_SYS_MOUNT_POINT = "@efiSysMountPoint@" +BOOT_MOUNT_POINT = "@bootMountPoint@" +LOADER_CONF = f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf" # Always stored on the ESP +NIXOS_DIR = "@nixosDir@" TIMEOUT = "@timeout@" EDITOR = "@editor@" == "1" CONSOLE_MODE = "@consoleMode@" @@ -28,6 +31,7 @@ CONFIGURATION_LIMIT = int("@configurationLimit@") CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@" GRACEFUL = "@graceful@" COPY_EXTRA_FILES = "@copyExtraFiles@" +CHECK_MOUNTPOINTS = "@checkMountpoints@" @dataclass class BootSpec: @@ -39,6 +43,7 @@ class BootSpec: system: str toplevel: str specialisations: Dict[str, "BootSpec"] + sortKey: str initrdSecrets: str | None = None @@ -69,6 +74,7 @@ def system_dir(profile: str | None, generation: int, specialisation: str | None) return d BOOT_ENTRY = """title {title} +sort-key {sort_key} version Generation {generation} {description} linux {kernel} initrd {initrd} @@ -87,7 +93,7 @@ def generation_conf_filename(profile: str | None, generation: int, specialisatio def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None: - with open(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", 'w') as f: + with open(f"{LOADER_CONF}.tmp", 'w') as f: if TIMEOUT != "": f.write(f"timeout {TIMEOUT}\n") f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation)) @@ -96,7 +102,7 @@ def write_loader_conf(profile: str | None, generation: int, specialisation: str f.write(f"console-mode {CONSOLE_MODE}\n") f.flush() os.fsync(f.fileno()) - os.rename(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf") + os.rename(f"{LOADER_CONF}.tmp", LOADER_CONF) def get_bootspec(profile: str | None, generation: int) -> BootSpec: @@ -119,16 +125,22 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec: def bootspec_from_json(bootspec_json: Dict) -> BootSpec: specialisations = bootspec_json['org.nixos.specialisation.v1'] specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()} - return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations) + systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {}) + sortKey = systemdBootExtension.get('sortKey', 'nixos') + return BootSpec( + **bootspec_json['org.nixos.bootspec.v1'], + specialisations=specialisations, + sortKey=sortKey + ) def copy_from_file(file: str, dry_run: bool = False) -> str: store_file_path = os.path.realpath(file) suffix = os.path.basename(store_file_path) store_dir = os.path.basename(os.path.dirname(store_file_path)) - efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix) + efi_file_path = f"{NIXOS_DIR}/{store_dir}-{suffix}.efi" if not dry_run: - copy_if_not_exists(store_file_path, f"{EFI_SYS_MOUNT_POINT}%s" % (efi_file_path)) + copy_if_not_exists(store_file_path, f"{BOOT_MOUNT_POINT}{efi_file_path}") return efi_file_path def write_entry(profile: str | None, generation: int, specialisation: str | None, @@ -145,7 +157,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None try: if bootspec.initrdSecrets is not None: - subprocess.check_call([bootspec.initrdSecrets, f"{EFI_SYS_MOUNT_POINT}%s" % (initrd)]) + subprocess.check_call([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)]) except subprocess.CalledProcessError: if current: print("failed to create initrd secrets!", file=sys.stderr) @@ -155,7 +167,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr) print("note: this is normal after having removed " "or renamed a file in `boot.initrd.secrets`", file=sys.stderr) - entry_file = f"{EFI_SYS_MOUNT_POINT}/loader/entries/%s" % ( + entry_file = f"{BOOT_MOUNT_POINT}/loader/entries/%s" % ( generation_conf_filename(profile, generation, specialisation)) tmp_path = "%s.tmp" % (entry_file) kernel_params = "init=%s " % bootspec.init @@ -166,6 +178,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None with open(tmp_path, 'w') as f: f.write(BOOT_ENTRY.format(title=title, + sort_key=bootspec.sortKey, generation=generation, kernel=kernel, initrd=initrd, @@ -202,14 +215,14 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]: def remove_old_entries(gens: list[SystemIdentifier]) -> None: - rex_profile = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$") - rex_generation = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$") + rex_profile = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$") + rex_generation = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$") known_paths = [] for gen in gens: bootspec = get_bootspec(gen.profile, gen.generation) known_paths.append(copy_from_file(bootspec.kernel, True)) known_paths.append(copy_from_file(bootspec.initrd, True)) - for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"): + for path in glob.iglob(f"{BOOT_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"): if rex_profile.match(path): prof = rex_profile.sub(r"\1", path) else: @@ -220,11 +233,18 @@ def remove_old_entries(gens: list[SystemIdentifier]) -> None: continue if not (prof, gen_number, None) in gens: os.unlink(path) - for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"): + for path in glob.iglob(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/*"): if not path in known_paths and not os.path.isdir(path): os.unlink(path) +def cleanup_esp() -> None: + for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*"): + os.unlink(path) + if os.path.isdir(f"{EFI_SYS_MOUNT_POINT}/{NIXOS_DIR}"): + shutil.rmtree(f"{EFI_SYS_MOUNT_POINT}/{NIXOS_DIR}") + + def get_profiles() -> list[str]: if os.path.isdir("/nix/var/nix/profiles/system-profiles/"): return [x @@ -255,6 +275,9 @@ def install_bootloader(args: argparse.Namespace) -> None: # flags to pass to bootctl install/update bootctl_flags = [] + if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: + bootctl_flags.append(f"--boot-path={BOOT_MOUNT_POINT}") + if CAN_TOUCH_EFI_VARIABLES != "1": bootctl_flags.append("--no-variables") @@ -263,8 +286,8 @@ def install_bootloader(args: argparse.Namespace) -> None: if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1": # bootctl uses fopen() with modes "wxe" and fails if the file exists. - if os.path.exists(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"): - os.unlink(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf") + if os.path.exists(LOADER_CONF): + os.unlink(LOADER_CONF) subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"]) else: @@ -291,13 +314,15 @@ def install_bootloader(args: argparse.Namespace) -> None: print("updating systemd-boot from %s to %s" % (installed_version, available_version)) subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"]) - os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos", exist_ok=True) - os.makedirs(f"{EFI_SYS_MOUNT_POINT}/loader/entries", exist_ok=True) + os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}", exist_ok=True) + os.makedirs(f"{BOOT_MOUNT_POINT}/loader/entries", exist_ok=True) gens = get_generations() for profile in get_profiles(): gens += get_generations(profile) + remove_old_entries(gens) + for gen in gens: try: bootspec = get_bootspec(gen.profile, gen.generation) @@ -315,9 +340,15 @@ def install_bootloader(args: argparse.Namespace) -> None: else: raise e - for root, _, files in os.walk(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", topdown=False): - relative_root = root.removeprefix(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files").removeprefix("/") - actual_root = os.path.join(f"{EFI_SYS_MOUNT_POINT}", relative_root) + if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: + # Cleanup any entries in ESP if xbootldrMountPoint is set. + # If the user later unsets xbootldrMountPoint, entries in XBOOTLDR will not be cleaned up + # automatically, as we don't have information about the mount point anymore. + cleanup_esp() + + for root, _, files in os.walk(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", topdown=False): + relative_root = root.removeprefix(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files").removeprefix("/") + actual_root = os.path.join(f"{BOOT_MOUNT_POINT}", relative_root) for file in files: actual_file = os.path.join(actual_root, file) @@ -330,7 +361,7 @@ def install_bootloader(args: argparse.Namespace) -> None: os.rmdir(actual_root) os.rmdir(root) - os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", exist_ok=True) + os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", exist_ok=True) subprocess.check_call(COPY_EXTRA_FILES) @@ -340,6 +371,8 @@ def main() -> None: parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot") args = parser.parse_args() + subprocess.check_call(CHECK_MOUNTPOINTS) + try: install_bootloader(args) finally: @@ -347,9 +380,14 @@ def main() -> None: # it can leave the system in an unbootable state, when a crash/outage # happens shortly after an update. To decrease the likelihood of this # event sync the efi filesystem after each update. - rc = libc.syncfs(os.open(f"{EFI_SYS_MOUNT_POINT}", os.O_RDONLY)) + rc = libc.syncfs(os.open(f"{BOOT_MOUNT_POINT}", os.O_RDONLY)) if rc != 0: - print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr) + print(f"could not sync {BOOT_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr) + + if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT: + rc = libc.syncfs(os.open(EFI_SYS_MOUNT_POINT, os.O_RDONLY)) + if rc != 0: + print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr) if __name__ == '__main__': diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix index ea4553b8208f6..ba07506266e26 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -7,7 +7,7 @@ let efi = config.boot.loader.efi; - systemdBootBuilder = pkgs.substituteAll { + systemdBootBuilder = pkgs.substituteAll rec { src = ./systemd-boot-builder.py; isExecutable = true; @@ -28,33 +28,48 @@ let inherit (efi) efiSysMountPoint canTouchEfiVariables; + bootMountPoint = if cfg.xbootldrMountPoint != null + then cfg.xbootldrMountPoint + else efi.efiSysMountPoint; + + nixosDir = "/EFI/nixos"; + inherit (config.system.nixos) distroName; memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86plus; netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi; + checkMountpoints = pkgs.writeShellScript "check-mountpoints" '' + fail() { + echo "$1 = '$2' is not a mounted partition. Is the path configured correctly?" >&2 + exit 1 + } + ${pkgs.util-linuxMinimal}/bin/findmnt ${efiSysMountPoint} > /dev/null || fail efiSysMountPoint ${efiSysMountPoint} + ${lib.optionalString + (cfg.xbootldrMountPoint != null) + "${pkgs.util-linuxMinimal}/bin/findmnt ${cfg.xbootldrMountPoint} > /dev/null || fail xbootldrMountPoint ${cfg.xbootldrMountPoint}"} + ''; + copyExtraFiles = pkgs.writeShellScript "copy-extra-files" '' empty_file=$(${pkgs.coreutils}/bin/mktemp) ${concatStrings (mapAttrsToList (n: v: '' - ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n} - ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -Dp "${v}" "${bootMountPoint}/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/"${escapeShellArg n} '') cfg.extraFiles)} ${concatStrings (mapAttrsToList (n: v: '' - ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n} - ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${bootMountPoint}/loader/entries/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n} '') cfg.extraEntries)} ''; }; - checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" { - nativeBuildInputs = [ pkgs.mypy ]; - } '' + checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" { } '' mkdir -p $out/bin install -m755 ${systemdBootBuilder} $out/bin/systemd-boot-builder - mypy \ + ${lib.getExe pkgs.buildPackages.mypy} \ --no-implicit-optional \ --disallow-untyped-calls \ --disallow-untyped-defs \ @@ -72,6 +87,16 @@ in { imports = [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ]) + (lib.mkChangedOptionModule + [ "boot" "loader" "systemd-boot" "memtest86" "entryFilename" ] + [ "boot" "loader" "systemd-boot" "memtest86" "sortKey" ] + (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.memtest86.entryFilename) + ) + (lib.mkChangedOptionModule + [ "boot" "loader" "systemd-boot" "netbootxyz" "entryFilename" ] + [ "boot" "loader" "systemd-boot" "netbootxyz" "sortKey" ] + (config: lib.strings.removeSuffix ".conf" config.boot.loader.systemd-boot.netbootxyz.entryFilename) + ) ]; options.boot.loader.systemd-boot = { @@ -87,6 +112,35 @@ in { ''; }; + sortKey = mkOption { + default = "nixos"; + type = lib.types.str; + description = '' + The sort key used for the NixOS bootloader entries. + This key determines sorting relative to non-NixOS entries. + See also https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting + + This option can also be used to control the sorting of NixOS specialisations. + + By default, specialisations inherit the sort key of their parent generation + and will have the same value for both the sort-key and the version (i.e. the generation number), + systemd-boot will therefore sort them based on their file name, meaning that + in your boot menu you will have each main generation directly followed by + its specialisations sorted alphabetically by their names. + + If you want a different ordering for a specialisation, you can override + its sort-key which will cause the specialisation to be uncoupled from its + parent generation. It will then be sorted by its new sort-key just like + any other boot entry. + + The sort-key is stored in the generation's bootspec, which means that + generations keep their sort-keys even if the original definition of the + generation was removed from the NixOS configuration. + It also means that updating the sort-key will only affect new generations, + while old ones will keep the sort-key that they were originally built with. + ''; + }; + editor = mkOption { default = true; @@ -101,6 +155,18 @@ in { ''; }; + xbootldrMountPoint = mkOption { + default = null; + type = types.nullOr types.str; + description = lib.mdDoc '' + Where the XBOOTLDR partition is mounted. + + If set, this partition will be used as $BOOT to store boot loader entries and extra files + instead of the EFI partition. As per the bootloader specification, it is recommended that + the EFI and XBOOTLDR partitions be mounted at `/efi` and `/boot`, respectively. + ''; + }; + configurationLimit = mkOption { default = null; example = 120; @@ -110,7 +176,7 @@ in { Useful to prevent boot partition running out of disk space. `null` means no limit i.e. all generations - that were not garbage collected yet. + that have not been garbage collected yet. ''; }; @@ -157,13 +223,15 @@ in { ''; }; - entryFilename = mkOption { - default = "memtest86.conf"; + sortKey = mkOption { + default = "o_memtest86"; type = types.str; description = lib.mdDoc '' - `systemd-boot` orders the menu entries by the config file names, + `systemd-boot` orders the menu entries by their sort keys, so if you want something to appear after all the NixOS entries, it should start with {file}`o` or onwards. + + See also {option}`boot.loader.systemd-boot.sortKey`. ''; }; }; @@ -180,13 +248,15 @@ in { ''; }; - entryFilename = mkOption { - default = "o_netbootxyz.conf"; + sortKey = mkOption { + default = "o_netbootxyz"; type = types.str; description = lib.mdDoc '' - `systemd-boot` orders the menu entries by the config file names, + `systemd-boot` orders the menu entries by their sort keys, so if you want something to appear after all the NixOS entries, it should start with {file}`o` or onwards. + + See also {option}`boot.loader.systemd-boot.sortKey`. ''; }; }; @@ -198,17 +268,19 @@ in { { "memtest86.conf" = ''' title Memtest86+ efi /efi/memtest86/memtest.efi + sort-key z_memtest '''; } ''; description = lib.mdDoc '' Any additional entries you want added to the `systemd-boot` menu. - These entries will be copied to {file}`/boot/loader/entries`. + These entries will be copied to {file}`$BOOT/loader/entries`. Each attribute name denotes the destination file name, and the corresponding attribute value is the contents of the entry. - `systemd-boot` orders the menu entries by the config file names, - so if you want something to appear after all the NixOS entries, - it should start with {file}`o` or onwards. + To control the ordering of the entry in the boot menu, use the sort-key + field, see + https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting + and {option}`boot.loader.systemd-boot.sortKey`. ''; }; @@ -219,9 +291,9 @@ in { { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; } ''; description = lib.mdDoc '' - A set of files to be copied to {file}`/boot`. + A set of files to be copied to {file}`$BOOT`. Each attribute name denotes the destination file name in - {file}`/boot`, while the corresponding + {file}`$BOOT`, while the corresponding attribute value specifies the source file. ''; }; @@ -246,6 +318,18 @@ in { config = mkIf cfg.enable { assertions = [ { + assertion = (hasPrefix "/" efi.efiSysMountPoint); + message = "The ESP mount point '${efi.efiSysMountPoint}' must be an absolute path"; + } + { + assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint); + message = "The XBOOTLDR mount point '${cfg.xbootldrMountPoint}' must be an absolute path"; + } + { + assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint; + message = "The XBOOTLDR mount point '${cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${efi.efiSysMountPoint}'"; + } + { assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; message = "This kernel does not support the EFI boot stub"; } @@ -289,19 +373,25 @@ in { boot.loader.systemd-boot.extraEntries = mkMerge [ (mkIf cfg.memtest86.enable { - "${cfg.memtest86.entryFilename}" = '' + "memtest86.conf" = '' title Memtest86+ efi /efi/memtest86/memtest.efi + sort-key ${cfg.memtest86.sortKey} ''; }) (mkIf cfg.netbootxyz.enable { - "${cfg.netbootxyz.entryFilename}" = '' + "netbootxyz.conf" = '' title netboot.xyz efi /efi/netbootxyz/netboot.xyz.efi + sort-key ${cfg.netbootxyz.sortKey} ''; }) ]; + boot.bootspec.extensions."org.nixos.systemd-boot" = { + inherit (config.boot.loader.systemd-boot) sortKey; + }; + system = { build.installBootLoader = finalSystemdBootBuilder; diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index a7399bd55e770..88d6a2ded873c 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -647,9 +647,9 @@ let "BatmanAdvanced" ]) # Note: For DHCP the values both, none, v4, v6 are deprecated - (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"]) + (assertValueOneOf "DHCP" (boolValues ++ ["ipv4" "ipv6"])) (assertValueOneOf "DHCPServer" boolValues) - (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "fallback" "ipv4-fallback"]) + (assertValueOneOf "LinkLocalAddressing" (boolValues ++ ["ipv4" "ipv6" "fallback" "ipv4-fallback"])) (assertValueOneOf "IPv6LinkLocalAddressGenerationMode" ["eui64" "none" "stable-privacy" "random"]) (assertValueOneOf "IPv4LLRoute" boolValues) (assertValueOneOf "DefaultRouteOnDevice" boolValues) diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix index b041b8951fa37..16bca40993aeb 100644 --- a/nixos/modules/system/boot/plymouth.nix +++ b/nixos/modules/system/boot/plymouth.nix @@ -186,6 +186,8 @@ in # module might come from a theme cp ${themesEnv}/lib/plymouth/*.so $out cp ${plymouth}/lib/plymouth/renderers/*.so $out/renderers + # useless in the initrd, and adds several megabytes to the closure + rm $out/renderers/x11.so ''; "/etc/plymouth/themes".source = pkgs.runCommand "plymouth-initrd-themes" {} '' # Check if the actual requested theme is here @@ -271,6 +273,8 @@ in # module might come from a theme cp ${themesEnv}/lib/plymouth/*.so $out/lib/plymouth cp ${plymouth}/lib/plymouth/renderers/*.so $out/lib/plymouth/renderers + # useless in the initrd, and adds several megabytes to the closure + rm $out/lib/plymouth/renderers/x11.so mkdir -p $out/share/plymouth/themes cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index e29fa49ea23be..49090423e078c 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -97,6 +97,7 @@ let # Maintaining state across reboots. "systemd-random-seed.service" + "systemd-boot-random-seed.service" "systemd-backlight@.service" "systemd-rfkill.service" "systemd-rfkill.socket" @@ -667,7 +668,6 @@ in # Don't bother with certain units in containers. systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container"; - systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container"; # Increase numeric PID range (set directly instead of copying a one-line file from systemd) # https://github.com/systemd/systemd/pull/12226 diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix index 03ef00e5683c1..271d8f86d0e61 100644 --- a/nixos/modules/system/boot/systemd/coredump.nix +++ b/nixos/modules/system/boot/systemd/coredump.nix @@ -52,7 +52,7 @@ in { # See: https://github.com/NixOS/nixpkgs/issues/213408 pkgs.substitute { src = "${systemd}/example/sysctl.d/50-coredump.conf"; - replacements = [ + substitutions = [ "--replace" "${systemd}" "${pkgs.symlinkJoin { name = "systemd"; paths = [ systemd ]; }}" diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix index 3be744acd0b3b..6cc387cb6f43f 100644 --- a/nixos/modules/system/boot/systemd/repart.nix +++ b/nixos/modules/system/boot/systemd/repart.nix @@ -10,6 +10,20 @@ let "repart.d" format (lib.mapAttrs (_n: v: { Partition = v; }) cfg.partitions); + + partitionAssertions = lib.mapAttrsToList (fileName: definition: + let + maxLabelLength = 36; # GPT_LABEL_MAX defined in systemd's gpt.h + labelLength = builtins.stringLength definition.Label; + in + { + assertion = definition ? Label -> maxLabelLength >= labelLength; + message = '' + The partition label '${definition.Label}' defined for '${fileName}' is ${toString labelLength} + characters long, but the maximum label length supported by systemd is ${toString maxLabelLength}. + ''; + } + ) cfg.partitions; in { options = { @@ -81,7 +95,7 @@ in 'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled. ''; } - ]; + ] ++ partitionAssertions; # systemd-repart uses loopback devices for partition creation boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop"; diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix index e72a1e37759e9..191b462711948 100644 --- a/nixos/modules/tasks/filesystems.nix +++ b/nixos/modules/tasks/filesystems.nix @@ -105,7 +105,7 @@ let type = types.bool; description = lib.mdDoc '' If the device does not currently contain a filesystem (as - determined by {command}`blkid`, then automatically + determined by {command}`blkid`), then automatically format it with the filesystem type specified in {option}`fsType`. Use with caution. ''; diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 98df6a40e8a1b..d11424c11c810 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -211,6 +211,7 @@ in imports = [ (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.") + (mkRemovedOptionModule [ "boot" "zfs" "enableUnstable" ] "Instead set `boot.zfs.package = pkgs.zfs_unstable;`") ]; ###### interface @@ -219,9 +220,9 @@ in boot.zfs = { package = mkOption { type = types.package; - default = if cfgZfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs; - defaultText = literalExpression "if zfsUnstable is enabled then pkgs.zfsUnstable else pkgs.zfs"; - description = lib.mdDoc "Configured ZFS userland tools package, use `pkgs.zfsUnstable` if you want to track the latest staging ZFS branch."; + default = pkgs.zfs; + defaultText = literalExpression "pkgs.zfs"; + description = lib.mdDoc "Configured ZFS userland tools package, use `pkgs.zfs_unstable` if you want to track the latest staging ZFS branch."; }; modulePackage = mkOption { @@ -239,19 +240,6 @@ in description = lib.mdDoc "True if ZFS filesystem support is enabled"; }; - enableUnstable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Use the unstable zfs package. This might be an option, if the latest - kernel is not yet supported by a published release of ZFS. Enabling - this option will install a development version of ZFS on Linux. The - version will have already passed an extensive test suite, but it is - more likely to hit an undiscovered bug compared to running a released - version of ZFS on Linux. - ''; - }; - allowHibernation = mkOption { type = types.bool; default = false; @@ -347,24 +335,12 @@ in removeLinuxDRM = lib.mkOption { type = types.bool; default = false; - description = lib.mdDoc '' - Linux 6.2 dropped some kernel symbols required on aarch64 required by zfs. - Enabling this option will bring them back to allow this kernel version. - Note that in some jurisdictions this may be illegal as it might be considered - removing copyright protection from the code. - See https://www.ifross.org/?q=en/artikel/ongoing-dispute-over-value-exportsymbolgpl-function for further information. + description = '' + Patch the kernel to change symbols needed by ZFS from + EXPORT_SYMBOL_GPL to EXPORT_SYMBOL. - If configure your kernel package with `zfs.latestCompatibleLinuxPackages`, you will need to also pass removeLinuxDRM to that package like this: - - ``` - { pkgs, ... }: { - boot.kernelPackages = (pkgs.zfs.override { - removeLinuxDRM = pkgs.hostPlatform.isAarch64; - }).latestCompatibleLinuxPackages; - - boot.zfs.removeLinuxDRM = true; - } - ``` + Currently has no effect, but may again in future if a kernel + update breaks ZFS due to symbols being newly changed to GPL. ''; }; }; @@ -588,7 +564,7 @@ in kernelParams = lib.optionals (!config.boot.zfs.allowHibernation) [ "nohibernate" ]; extraModulePackages = [ - (cfgZfs.modulePackage.override { inherit (cfgZfs) removeLinuxDRM; }) + cfgZfs.modulePackage ]; }; @@ -725,21 +701,6 @@ in services.udev.packages = [ cfgZfs.package ]; # to hook zvol naming, etc. systemd.packages = [ cfgZfs.package ]; - # Export kernel_neon_* symbols again. - # This change is necessary until ZFS figures out a solution - # with upstream or in their build system to fill the gap for - # this symbol. - # In the meantime, we restore what was once a working piece of code - # in the kernel. - boot.kernelPatches = lib.optional (cfgZfs.removeLinuxDRM && pkgs.stdenv.hostPlatform.system == "aarch64-linux") { - name = "export-neon-symbols-as-gpl"; - patch = pkgs.fetchpatch { - url = "https://github.com/torvalds/linux/commit/aaeca98456431a8d9382ecf48ac4843e252c07b3.patch"; - hash = "sha256-L2g4G1tlWPIi/QRckMuHDcdWBcKpObSWSRTvbHRIwIk="; - revert = true; - }; - }; - systemd.services = let createImportService' = pool: createImportService { inherit pool; diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix index 3bbe0ba458516..a561c5682ae58 100644 --- a/nixos/modules/virtualisation/incus.nix +++ b/nixos/modules/virtualisation/incus.nix @@ -107,6 +107,13 @@ in }; config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !(config.networking.firewall.enable && !config.networking.nftables.enable && config.virtualisation.incus.enable); + message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`"; + } + ]; + # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md boot.kernel.sysctl = { "fs.aio-max-nr" = lib.mkDefault 524288; diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix index 5a99dc8a1bb90..a977390542166 100644 --- a/nixos/modules/virtualisation/podman/default.nix +++ b/nixos/modules/virtualisation/podman/default.nix @@ -216,9 +216,11 @@ in requires = [ "podman.service" ]; }; + systemd.services.podman.environment = config.networking.proxy.envVars; systemd.sockets.podman.wantedBy = [ "sockets.target" ]; systemd.sockets.podman.socketConfig.SocketGroup = "podman"; + systemd.user.services.podman.environment = config.networking.proxy.envVars; systemd.user.sockets.podman.wantedBy = [ "sockets.target" ]; systemd.timers.podman-prune.timerConfig = lib.mkIf cfg.autoPrune.enable { diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 75ba6dacc122c..b5a8b08eee70d 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -877,11 +877,9 @@ in type = types.package; default = (pkgs.OVMF.override { secureBoot = cfg.useSecureBoot; - systemManagementModeRequired = cfg.useSecureBoot; }).fd; defaultText = ''(pkgs.OVMF.override { secureBoot = cfg.useSecureBoot; - systemManagementModeRequired = cfg.useSecureBoot; }).fd''; description = lib.mdDoc "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; @@ -1185,7 +1183,7 @@ in "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" ]) - (mkIf (cfg.efi.OVMF.systemManagementModeRequired or false) [ + (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [ "-machine" "q35,smm=on" "-global" "driver=cfi.pflash01,property=secure,value=on" ]) diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix index 50a8f8189590e..0ecf7f490cf6f 100644 --- a/nixos/modules/virtualisation/virtualbox-host.nix +++ b/nixos/modules/virtualisation/virtualbox-host.nix @@ -6,7 +6,7 @@ let cfg = config.virtualisation.virtualbox.host; virtualbox = cfg.package.override { - inherit (cfg) enableHardening headless enableWebService; + inherit (cfg) enableHardening headless enableWebService enableKvm; extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null; }; @@ -81,13 +81,24 @@ in Build VirtualBox web service tool (vboxwebsrv) to allow managing VMs via other webpage frontend tools. Useful for headless servers. ''; }; + + enableKvm = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Enable KVM support for VirtualBox. This increases compatibility with Linux kernel versions, because the VirtualBox kernel modules + are not required. + + This option is incompatible with `enableHardening` and `addNetworkInterface`. + + Note: This is experimental. Please check https://github.com/cyberus-technology/virtualbox-kvm/issues. + ''; + }; }; config = mkIf cfg.enable (mkMerge [{ warnings = mkIf (pkgs.config.virtualbox.enableExtensionPack or false) ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"]; - boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ]; - boot.extraModulePackages = [ kernelModules ]; environment.systemPackages = [ virtualbox ]; security.wrappers = let @@ -114,17 +125,43 @@ in services.udev.extraRules = '' - KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd" - KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd" - KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd" SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}" SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}" SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor" SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor" ''; + } (mkIf cfg.enableKvm { + assertions = [ + { + assertion = !cfg.addNetworkInterface; + message = "VirtualBox KVM only supports standard NAT networking for VMs. Please turn off virtualisation.virtualbox.host.addNetworkInferface."; + } + + { + assertion = !cfg.enableHardening; + message = "VirtualBox KVM is not compatible with hardening: Please turn off virtualisation.virtualbox.host.enableHardening."; + } + ]; + + warnings = [ + '' + KVM support in VirtualBox is experimental. Not all security features are available yet. + See: https://github.com/cyberus-technology/virtualbox-kvm/issues/12 + '' + ]; + }) (mkIf (!cfg.enableKvm) { + boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ]; + boot.extraModulePackages = [ kernelModules ]; + + services.udev.extraRules = + '' + KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd" + KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd" + KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd" + ''; # Since we lack the right setuid/setcap binaries, set up a host-only network by default. - } (mkIf cfg.addNetworkInterface { + }) (mkIf cfg.addNetworkInterface { systemd.services.vboxnet0 = { description = "VirtualBox vboxnet0 Interface"; requires = [ "dev-vboxnetctl.device" ]; diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index 7700441b1d6be..459700514ffc5 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -142,7 +142,6 @@ in rec { (onFullSupported "nixos.tests.networking.networkd.virtual") (onFullSupported "nixos.tests.networking.networkd.vlan") (onFullSupported "nixos.tests.systemd-networkd-ipv6-prefix-delegation") - (onFullSupported "nixos.tests.nfs3.simple") (onFullSupported "nixos.tests.nfs4.simple") (onSystems ["x86_64-linux"] "nixos.tests.oci-containers.podman") (onFullSupported "nixos.tests.openssh") diff --git a/nixos/release-small.nix b/nixos/release-small.nix index 6204dc731ad96..cac20b63925f0 100644 --- a/nixos/release-small.nix +++ b/nixos/release-small.nix @@ -43,7 +43,7 @@ in rec { login misc nat - nfs3 + nfs4 openssh php predictable-interface-names @@ -125,7 +125,7 @@ in rec { "nixos.tests.misc" "nixos.tests.nat.firewall" "nixos.tests.nat.standalone" - "nixos.tests.nfs3.simple" + "nixos.tests.nfs4.simple" "nixos.tests.openssh" "nixos.tests.php.fpm" "nixos.tests.php.pcre" diff --git a/nixos/release.nix b/nixos/release.nix index 2acc5ade7848b..ff60b0b79f6d9 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -177,6 +177,12 @@ in rec { inherit system; }); + iso_plasma6 = forMatchingSystems supportedSystems (system: makeIso { + module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix; + type = "plasma6"; + inherit system; + }); + iso_gnome = forMatchingSystems supportedSystems (system: makeIso { module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix; type = "gnome"; diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix index 272782dc2f621..d63a77fcdd23c 100644 --- a/nixos/tests/acme.nix +++ b/nixos/tests/acme.nix @@ -1,4 +1,7 @@ -{ pkgs, lib, ... }: let +{ config, lib, ... }: let + + pkgs = config.node.pkgs; + commonConfig = ./common/acme/client; dnsServerIP = nodes: nodes.dnsserver.networking.primaryIPAddress; diff --git a/nixos/tests/akkoma.nix b/nixos/tests/akkoma.nix index 287e2d485999e..2907017ee3d54 100644 --- a/nixos/tests/akkoma.nix +++ b/nixos/tests/akkoma.nix @@ -31,16 +31,12 @@ let export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt" - echo '${userPassword}' | ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test" - echo "y" | ${pkgs.toot}/bin/toot post "hello world Jamy here" - - # Retrieving timeline with toot currently broken due to incompatible timestamp format - # cf. <https://akkoma.dev/AkkomaGang/akkoma/issues/637> and <https://github.com/ihabunek/toot/issues/399> - #echo "y" | ${pkgs.toot}/bin/toot timeline | grep -F -q "hello world Jamy here" + ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test" -p '${userPassword}' + ${pkgs.toot}/bin/toot post "hello world Jamy here" + ${pkgs.toot}/bin/toot timeline -1 | grep -F -q "hello world Jamy here" # Test file upload - echo "y" | ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) \ - | grep -F -q "https://akkoma.nixos.test/media" + ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) ''; checkFe = pkgs.writers.writeBashBin "checkFe" '' diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 1144a5611dcf8..7376cd40b910a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -78,8 +78,9 @@ let # it with `allowAliases = false`? # warnIf pkgs.config.allowAliases "nixosTests: pkgs includes aliases." { + _file = "${__curPos.file} readOnlyPkgs"; _class = "nixosTest"; - node.pkgs = pkgs; + node.pkgs = pkgs.pkgsLinux; }; in { @@ -128,6 +129,7 @@ in { appliance-repart-image = runTest ./appliance-repart-image.nix; apparmor = handleTest ./apparmor.nix {}; archi = handleTest ./archi.nix {}; + armagetronad = handleTest ./armagetronad.nix {}; atd = handleTest ./atd.nix {}; atop = handleTest ./atop.nix {}; atuin = handleTest ./atuin.nix {}; @@ -497,6 +499,7 @@ in { lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; }); lxd-image-server = handleTest ./lxd-image-server.nix {}; #logstash = handleTest ./logstash.nix {}; + lomiri-system-settings = handleTest ./lomiri-system-settings.nix {}; lorri = handleTest ./lorri/default.nix {}; maddy = discoverTests (import ./maddy { inherit handleTest; }); maestral = handleTest ./maestral.nix {}; @@ -510,12 +513,14 @@ in { mastodon = discoverTests (import ./web-apps/mastodon { inherit handleTestOn; }); pixelfed = discoverTests (import ./web-apps/pixelfed { inherit handleTestOn; }); mate = handleTest ./mate.nix {}; + matter-server = handleTest ./matter-server.nix {}; matomo = handleTest ./matomo.nix {}; matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {}; matrix-conduit = handleTest ./matrix/conduit.nix {}; matrix-synapse = handleTest ./matrix/synapse.nix {}; matrix-synapse-workers = handleTest ./matrix/synapse-workers.nix {}; mattermost = handleTest ./mattermost.nix {}; + mealie = handleTest ./mealie.nix {}; mediamtx = handleTest ./mediamtx.nix {}; mediatomb = handleTest ./mediatomb.nix {}; mediawiki = handleTest ./mediawiki.nix {}; @@ -535,6 +540,7 @@ in { mobilizon = handleTest ./mobilizon.nix {}; mod_perl = handleTest ./mod_perl.nix {}; molly-brown = handleTest ./molly-brown.nix {}; + monado = handleTest ./monado.nix {}; monica = handleTest ./web-apps/monica.nix {}; mongodb = handleTest ./mongodb.nix {}; moodle = handleTest ./moodle.nix {}; @@ -692,6 +698,7 @@ in { plantuml-server = handleTest ./plantuml-server.nix {}; plasma-bigscreen = handleTest ./plasma-bigscreen.nix {}; plasma5 = handleTest ./plasma5.nix {}; + plasma6 = handleTest ./plasma6.nix {}; plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {}; plausible = handleTest ./plausible.nix {}; please = handleTest ./please.nix {}; @@ -911,6 +918,7 @@ in { tor = handleTest ./tor.nix {}; traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {}; trafficserver = handleTest ./trafficserver.nix {}; + transfer-sh = handleTest ./transfer-sh.nix {}; transmission = handleTest ./transmission.nix { transmission = pkgs.transmission; }; transmission_4 = handleTest ./transmission.nix { transmission = pkgs.transmission_4; }; # tracee requires bpf diff --git a/nixos/tests/armagetronad.nix b/nixos/tests/armagetronad.nix new file mode 100644 index 0000000000000..ff2841dedd218 --- /dev/null +++ b/nixos/tests/armagetronad.nix @@ -0,0 +1,272 @@ +import ./make-test-python.nix ({ pkgs, ...} : + +let + user = "alice"; + + client = + { pkgs, ... }: + + { imports = [ ./common/user-account.nix ./common/x11.nix ]; + hardware.opengl.driSupport = true; + virtualisation.memorySize = 256; + environment = { + systemPackages = [ pkgs.armagetronad ]; + variables.XAUTHORITY = "/home/${user}/.Xauthority"; + }; + test-support.displayManager.auto.user = user; + }; + +in { + name = "armagetronad"; + meta = with pkgs.lib.maintainers; { + maintainers = [ numinit ]; + }; + + enableOCR = true; + + nodes = + { + server = { + services.armagetronad.servers = { + high-rubber = { + enable = true; + name = "Smoke Test High Rubber Server"; + port = 4534; + settings = { + SERVER_OPTIONS = "High Rubber server made to run smoke tests."; + CYCLE_RUBBER = 40; + SIZE_FACTOR = 0.5; + }; + roundSettings = { + SAY = [ + "NixOS Smoke Test Server" + "https://nixos.org" + ]; + }; + }; + sty = { + enable = true; + name = "Smoke Test sty+ct+ap Server"; + package = pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated; + port = 4535; + settings = { + SERVER_OPTIONS = "sty+ct+ap server made to run smoke tests."; + CYCLE_RUBBER = 20; + SIZE_FACTOR = 0.5; + }; + roundSettings = { + SAY = [ + "NixOS Smoke Test sty+ct+ap Server" + "https://nixos.org" + ]; + }; + }; + trunk = { + enable = true; + name = "Smoke Test trunk Server"; + package = pkgs.armagetronad."0.4".dedicated; + port = 4536; + settings = { + SERVER_OPTIONS = "0.4 server made to run smoke tests."; + CYCLE_RUBBER = 20; + SIZE_FACTOR = 0.5; + }; + roundSettings = { + SAY = [ + "NixOS Smoke Test 0.4 Server" + "https://nixos.org" + ]; + }; + }; + }; + }; + + client1 = client; + client2 = client; + }; + + testScript = let + xdo = name: text: let + xdoScript = pkgs.writeText "${name}.xdo" text; + in "${pkgs.xdotool}/bin/xdotool ${xdoScript}"; + in + '' + import shlex + import threading + from collections import namedtuple + + class Client(namedtuple('Client', ('node', 'name'))): + def send(self, *keys): + for key in keys: + self.node.send_key(key) + + def send_on(self, text, *keys): + self.node.wait_for_text(text) + self.send(*keys) + + Server = namedtuple('Server', ('node', 'name', 'address', 'port', 'welcome', 'attacker', 'victim', 'coredump_delay')) + + # Clients and their in-game names + clients = ( + Client(client1, 'Arduino'), + Client(client2, 'SmOoThIcE') + ) + + # Server configs. + servers = ( + Server(server, 'high-rubber', 'server', 4534, 'NixOS Smoke Test Server', 'SmOoThIcE', 'Arduino', 8), + Server(server, 'sty', 'server', 4535, 'NixOS Smoke Test sty+ct+ap Server', 'Arduino', 'SmOoThIcE', 8), + Server(server, 'trunk', 'server', 4536, 'NixOS Smoke Test 0.4 Server', 'Arduino', 'SmOoThIcE', 8) + ) + + """ + Runs a command as the client user. + """ + def run(cmd): + return "su - ${user} -c " + shlex.quote(cmd) + + screenshot_idx = 1 + + """ + Takes screenshots on all clients. + """ + def take_screenshots(screenshot_idx): + for client in clients: + client.node.screenshot(f"screen_{client.name}_{screenshot_idx}") + return screenshot_idx + 1 + + # Wait for the servers to come up. + start_all() + for srv in servers: + srv.node.wait_for_unit(f"armagetronad-{srv.name}") + srv.node.wait_until_succeeds(f"ss --numeric --udp --listening | grep -q {srv.port}") + + # Make sure console commands work through the named pipe we created. + for srv in servers: + srv.node.succeed( + f"echo 'say Testing!' >> /var/lib/armagetronad/{srv.name}/input" + ) + srv.node.succeed( + f"echo 'say Testing again!' >> /var/lib/armagetronad/{srv.name}/input" + ) + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing!'" + ) + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing again!'" + ) + + """ + Sets up a client, waiting for the given barrier on completion. + """ + def client_setup(client, servers, barrier): + client.node.wait_for_x() + + # Configure Armagetron. + client.node.succeed( + run("mkdir -p ~/.armagetronad/var"), + run(f"echo 'PLAYER_1 {client.name}' >> ~/.armagetronad/var/autoexec.cfg") + ) + for idx, srv in enumerate(servers): + client.node.succeed( + run(f"echo 'BOOKMARK_{idx+1}_ADDRESS {srv.address}' >> ~/.armagetronad/var/autoexec.cfg"), + run(f"echo 'BOOKMARK_{idx+1}_NAME {srv.name}' >> ~/.armagetronad/var/autoexec.cfg"), + run(f"echo 'BOOKMARK_{idx+1}_PORT {srv.port}' >> ~/.armagetronad/var/autoexec.cfg") + ) + + # Start Armagetron. + client.node.succeed(run("ulimit -c unlimited; armagetronad >&2 & disown")) + client.node.wait_until_succeeds( + run( + "${xdo "create_new_win-select_main_window" '' + search --onlyvisible --name "Armagetron Advanced" + windowfocus --sync + windowactivate --sync + ''}" + ) + ) + + # Get through the tutorial. + client.send_on('Language Settings', 'ret') + client.send_on('First Setup', 'ret') + client.send_on('Welcome to Armagetron Advanced', 'ret') + client.send_on('round 1', 'esc') + client.send_on('Menu', 'up', 'up', 'ret') + client.send_on('We hope you', 'ret') + client.send_on('Armagetron Advanced', 'ret') + client.send_on('Play Game', 'ret') + + # Online > LAN > Network Setup > Mates > Server Bookmarks + client.send_on('Multiplayer', 'down', 'down', 'down', 'down', 'ret') + + barrier.wait() + + # Get to the Server Bookmarks screen on both clients. This takes a while so do it asynchronously. + barrier = threading.Barrier(3, timeout=120) + for client in clients: + threading.Thread(target=client_setup, args=(client, servers, barrier)).start() + barrier.wait() + + # Main testing loop. Iterates through each server bookmark and connects to them in sequence. + # Assumes that the game is currently on the Server Bookmarks screen. + for srv in servers: + screenshot_idx = take_screenshots(screenshot_idx) + + # Connect both clients at once, one second apart. + for client in clients: + client.send('ret') + client.node.sleep(1) + + # Wait for clients to connect + for client in clients: + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*entered the game'" + ) + + # Wait for the match to start + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: {srv.welcome}'" + ) + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: https://nixos.org'" + ) + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Go (round 1 of 10)'" + ) + + # Wait a bit + srv.node.sleep(srv.coredump_delay) + + # Turn the attacker player's lightcycle left + attacker = next(client for client in clients if client.name == srv.attacker) + victim = next(client for client in clients if client.name == srv.victim) + attacker.send('left') + screenshot_idx = take_screenshots(screenshot_idx) + + # Wait for coredump. + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q '{attacker.name} core dumped {victim.name}'" + ) + screenshot_idx = take_screenshots(screenshot_idx) + + # Disconnect both clients from the server + for client in clients: + client.send('esc') + client.send_on('Menu', 'up', 'up', 'ret') + srv.node.wait_until_succeeds( + f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*left the game'" + ) + + # Next server. + for client in clients: + client.send_on('Server Bookmarks', 'down') + + # Stop the servers + for srv in servers: + srv.node.succeed( + f"systemctl stop armagetronad-{srv.name}" + ) + srv.node.wait_until_fails(f"ss --numeric --udp --listening | grep -q {srv.port}") + ''; + +}) diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix index ec2a9f6527c93..56f72dddf526c 100644 --- a/nixos/tests/boot.nix +++ b/nixos/tests/boot.nix @@ -4,10 +4,41 @@ }: with import ../lib/testing-python.nix { inherit system pkgs; }; -with pkgs.lib; let - qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; }; + lib = pkgs.lib; + qemu-common = import ../lib/qemu-common.nix { inherit lib pkgs; }; + + mkStartCommand = { + memory ? 2048, + cdrom ? null, + usb ? null, + pxe ? null, + uboot ? false, + uefi ? false, + extraFlags ? [], + }: let + qemu = qemu-common.qemuBinary pkgs.qemu_test; + + flags = [ + "-m" (toString memory) + "-netdev" ("user,id=net0" + (lib.optionalString (pxe != null) ",tftp=${pxe},bootfile=netboot.ipxe")) + "-device" ("virtio-net-pci,netdev=net0" + (lib.optionalString (pxe != null && uefi) ",romfile=${pkgs.ipxe}/ipxe.efirom")) + ] ++ lib.optionals (cdrom != null) [ + "-cdrom" cdrom + ] ++ lib.optionals (usb != null) [ + "-device" "usb-ehci" + "-drive" "id=usbdisk,file=${usb},if=none,readonly" + "-device" "usb-storage,drive=usbdisk" + ] ++ lib.optionals (pxe != null) [ + "-boot" "order=n" + ] ++ lib.optionals uefi [ + "-drive" "if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware}" + "-drive" "if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}" + ] ++ extraFlags; + + flagsStr = lib.concatStringsSep " " flags; + in "${qemu} ${flagsStr}"; iso = (import ../lib/eval-config.nix { @@ -28,21 +59,16 @@ let ]; }).config.system.build.sdImage; - pythonDict = params: "\n {\n ${concatStringsSep ",\n " (mapAttrsToList (name: param: "\"${name}\": \"${param}\"") params)},\n }\n"; - - makeBootTest = name: extraConfig: + makeBootTest = name: config: let - machineConfig = pythonDict ({ - qemuBinary = qemu-common.qemuBinary pkgs.qemu_test; - qemuFlags = "-m 768"; - } // extraConfig); + startCommand = mkStartCommand config; in makeTest { name = "boot-" + name; nodes = { }; testScript = '' - machine = create_machine(${machineConfig}) + machine = create_machine("${startCommand}") machine.start() machine.wait_for_unit("multi-user.target") machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system") @@ -73,43 +99,35 @@ let config.system.build.netbootIpxeScript ]; }; - machineConfig = pythonDict ({ - qemuBinary = qemu-common.qemuBinary pkgs.qemu_test; - qemuFlags = "-boot order=n -m 2000"; - netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe"; + startCommand = mkStartCommand ({ + pxe = ipxeBootDir; } // extraConfig); in makeTest { name = "boot-netboot-" + name; nodes = { }; testScript = '' - machine = create_machine(${machineConfig}) + machine = create_machine("${startCommand}") machine.start() machine.wait_for_unit("multi-user.target") machine.shutdown() ''; }; - uefiBinary = { - x86_64-linux = "${pkgs.OVMF.fd}/FV/OVMF.fd"; - aarch64-linux = "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd"; - }.${pkgs.stdenv.hostPlatform.system}; in { uefiCdrom = makeBootTest "uefi-cdrom" { + uefi = true; cdrom = "${iso}/iso/${iso.isoName}"; - bios = uefiBinary; }; uefiUsb = makeBootTest "uefi-usb" { + uefi = true; usb = "${iso}/iso/${iso.isoName}"; - bios = uefiBinary; }; uefiNetboot = makeNetbootTest "uefi" { - bios = uefiBinary; - # Custom ROM is needed for EFI PXE boot. I failed to understand exactly why, because QEMU should still use iPXE for EFI. - netFrontendArgs = "romfile=${pkgs.ipxe}/ipxe.efirom"; + uefi = true; }; -} // optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") { +} // lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") { biosCdrom = makeBootTest "bios-cdrom" { cdrom = "${iso}/iso/${iso.isoName}"; }; @@ -124,9 +142,12 @@ in { sdImage = "${sd}/sd-image/${sd.imageName}"; mutableImage = "/tmp/linked-image.qcow2"; - machineConfig = pythonDict { - bios = "${pkgs.ubootQemuX86}/u-boot.rom"; - qemuFlags = "-m 768 -machine type=pc,accel=tcg -drive file=${mutableImage},if=ide,format=qcow2"; + startCommand = mkStartCommand { + extraFlags = [ + "-bios" "${pkgs.ubootQemuX86}/u-boot.rom" + "-machine" "type=pc,accel=tcg" + "-drive" "file=${mutableImage},if=virtio" + ]; }; in makeTest { name = "boot-uboot-extlinux"; @@ -138,11 +159,14 @@ in { if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0: raise RuntimeError("Could not create mutable linked image") - machine = create_machine(${machineConfig}) + machine = create_machine("${startCommand}") machine.start() machine.wait_for_unit("multi-user.target") machine.succeed("nix store verify -r --no-trust --option experimental-features nix-command /run/current-system") machine.shutdown() ''; + + # kernel can't find rootfs after boot - investigate? + meta.broken = true; }; } diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix index 1a64c464039b4..82922102f07b5 100644 --- a/nixos/tests/common/ec2.nix +++ b/nixos/tests/common/ec2.nix @@ -61,7 +61,7 @@ with pkgs.lib; + " $QEMU_OPTS" ) - machine = create_machine({"startCommand": start_command}) + machine = create_machine(start_command) try: '' + indentLines script + '' finally: diff --git a/nixos/tests/consul.nix b/nixos/tests/consul.nix index 6233234ff083b..c819312068dc7 100644 --- a/nixos/tests/consul.nix +++ b/nixos/tests/consul.nix @@ -42,6 +42,8 @@ let ]; networking.firewall = firewallSettings; + nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ]; + services.consul = { enable = true; inherit webUi; @@ -65,6 +67,8 @@ let ]; networking.firewall = firewallSettings; + nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ]; + services.consul = assert builtins.elem thisConsensusServerHost allConsensusServerHosts; { diff --git a/nixos/tests/docker-registry.nix b/nixos/tests/docker-registry.nix index db20cb52c3e3a..3969ef3f0226f 100644 --- a/nixos/tests/docker-registry.nix +++ b/nixos/tests/docker-registry.nix @@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { services.dockerRegistry.port = 8080; services.dockerRegistry.listenAddress = "0.0.0.0"; services.dockerRegistry.enableGarbageCollect = true; - networking.firewall.allowedTCPPorts = [ 8080 ]; + services.dockerRegistry.openFirewall = true; }; client1 = { ... }: { diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index f252eb9ff61e5..7d91076600f97 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -58,6 +58,20 @@ let ''; config.Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "/testfile" ]; }; + + nonRootTestImage = + pkgs.dockerTools.streamLayeredImage rec { + name = "non-root-test"; + tag = "latest"; + uid = 1000; + gid = 1000; + uname = "user"; + gname = "user"; + config = { + User = "user"; + Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "${pkgs.coreutils}/bin/stat" ]; + }; + }; in { name = "docker-tools"; meta = with pkgs.lib.maintainers; { @@ -181,7 +195,7 @@ in { ): docker.succeed( "docker load --input='${examples.bashLayeredWithUser}'", - "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'", + "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 755 == $(stat --format=%a /nix) && test 755 == $(stat --format=%a /nix/store)'", "docker rmi ${examples.bashLayeredWithUser.imageName}", ) @@ -604,5 +618,11 @@ in { "${chownTestImage} | docker load", "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)" ) + + with subtest("streamLayeredImage: with non-root user"): + docker.succeed( + "${nonRootTestImage} | docker load", + "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)" + ) ''; }) diff --git a/nixos/tests/incus/container.nix b/nixos/tests/incus/container.nix index 0f42d16f133d6..eb00429e53fe1 100644 --- a/nixos/tests/incus/container.nix +++ b/nixos/tests/incus/container.nix @@ -5,6 +5,8 @@ let configuration = { # Building documentation makes the test unnecessarily take a longer time: documentation.enable = lib.mkForce false; + + boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; } // extra; }; @@ -40,6 +42,12 @@ in with machine.nested("Waiting for instance to start and be usable"): retry(instance_is_up) + def check_sysctl(instance): + with subtest("systemd sysctl settings are applied"): + machine.succeed(f"incus exec {instance} -- systemctl status systemd-sysctl") + sysctl = machine.succeed(f"incus exec {instance} -- sysctl net.ipv4.ip_forward").strip().split(" ")[-1] + assert "1" == sysctl, f"systemd-sysctl configuration not correctly applied, {sysctl} != 1" + machine.wait_for_unit("incus.service") # no preseed should mean no service @@ -83,6 +91,7 @@ in with subtest("lxc-container generator configures plain container"): # reuse the existing container to save some time machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") + check_sysctl("container") with subtest("lxc-container generator configures nested container"): machine.execute("incus delete --force container") @@ -94,6 +103,8 @@ in target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip() assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service" + check_sysctl("container") + with subtest("lxc-container generator configures privileged container"): machine.execute("incus delete --force container") machine.succeed("incus launch nixos container --config security.privileged=true") @@ -101,5 +112,7 @@ in retry(instance_is_up) machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") + + check_sysctl("container") ''; }) diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index b6cb6a0c6d455..97bb7f8def595 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -83,46 +83,34 @@ let , postInstallCommands, preBootCommands, postBootCommands, extraConfig , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest }: - let iface = "virtio"; - isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); - bios = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd"; + let + qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; }; + isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); + qemu = qemu-common.qemuBinary pkgs.qemu_test; in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then '' machine.succeed("true") '' else '' + import os import subprocess - tpm_folder = os.environ['NIX_BUILD_TOP'] - def assemble_qemu_flags(): - flags = "-cpu max" - ${if (system == "x86_64-linux" || system == "i686-linux") - then ''flags += " -m 1024"'' - else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"'' - } - ${optionalString clevisTest ''flags += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"''} - ${optionalString clevisTest ''flags += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""''} - return flags + tpm_folder = os.environ['NIX_BUILD_TOP'] - qemu_flags = {"qemuFlags": assemble_qemu_flags()} + startcommand = "${qemu} -m 2048" - import os + ${optionalString clevisTest '' + startcommand += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0" + startcommand += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\"" + ''} + ${optionalString isEfi '' + startcommand +=" -drive if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware} -drive if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}" + ''} image_dir = machine.state_dir disk_image = os.path.join(image_dir, "machine.qcow2") - - hd_flags = { - "hdaInterface": "${iface}", - "hda": disk_image, - } - ${optionalString isEfi '' - hd_flags.update( - bios="${pkgs.OVMF.fd}/FV/${bios}" - )'' - } - default_flags = {**hd_flags, **qemu_flags} - + startcommand += f" -drive file={disk_image},if=virtio,werror=report" def create_machine_named(name): - return create_machine({**default_flags, "name": name}) + return create_machine(startcommand, name=name) class Tpm: def __init__(self): @@ -471,7 +459,7 @@ let # builds stuff in the VM, needs more juice virtualisation.diskSize = 8 * 1024; virtualisation.cores = 8; - virtualisation.memorySize = 1536; + virtualisation.memorySize = 2048; boot.initrd.systemd.enable = systemdStage1; diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix index e168f8233c763..512dc06ee77ec 100644 --- a/nixos/tests/k3s/default.nix +++ b/nixos/tests/k3s/default.nix @@ -6,6 +6,11 @@ let allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs; in { + # Testing K3s with Etcd backend + etcd = lib.mapAttrs (_: k3s: import ./etcd.nix { + inherit system pkgs k3s; + inherit (pkgs) etcd; + }) allK3s; # Run a single node k3s cluster and verify a pod can run single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s; # Run a multi-node k3s cluster and verify pod networking works across nodes diff --git a/nixos/tests/k3s/etcd.nix b/nixos/tests/k3s/etcd.nix new file mode 100644 index 0000000000000..d6e9a294adb13 --- /dev/null +++ b/nixos/tests/k3s/etcd.nix @@ -0,0 +1,100 @@ +import ../make-test-python.nix ({ pkgs, lib, k3s, etcd, ... }: + +{ + name = "${k3s.name}-etcd"; + + nodes = { + + etcd = { ... }: { + services.etcd = { + enable = true; + openFirewall = true; + listenClientUrls = [ "http://192.168.1.1:2379" "http://127.0.0.1:2379" ]; + listenPeerUrls = [ "http://192.168.1.1:2380" ]; + initialAdvertisePeerUrls = [ "http://192.168.1.1:2380" ]; + initialCluster = [ "etcd=http://192.168.1.1:2380" ]; + }; + networking = { + useDHCP = false; + defaultGateway = "192.168.1.1"; + interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [ + { address = "192.168.1.1"; prefixLength = 24; } + ]; + }; + }; + + k3s = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ jq ]; + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = 1536; + virtualisation.diskSize = 4096; + + services.k3s = { + enable = true; + role = "server"; + extraFlags = builtins.toString [ + "--datastore-endpoint=\"http://192.168.1.1:2379\"" + "--disable" "coredns" + "--disable" "local-storage" + "--disable" "metrics-server" + "--disable" "servicelb" + "--disable" "traefik" + "--node-ip" "192.168.1.2" + ]; + }; + + networking = { + firewall = { + allowedTCPPorts = [ 2379 2380 6443 ]; + allowedUDPPorts = [ 8472 ]; + }; + useDHCP = false; + defaultGateway = "192.168.1.2"; + interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [ + { address = "192.168.1.2"; prefixLength = 24; } + ]; + }; + }; + + }; + + testScript = '' + with subtest("should start etcd"): + etcd.start() + etcd.wait_for_unit("etcd.service") + + with subtest("should wait for etcdctl endpoint status to succeed"): + etcd.wait_until_succeeds("etcdctl endpoint status") + + with subtest("should start k3s"): + k3s.start() + k3s.wait_for_unit("k3s") + + with subtest("should test if kubectl works"): + k3s.wait_until_succeeds("k3s kubectl get node") + + with subtest("should wait for service account to show up; takes a sec"): + k3s.wait_until_succeeds("k3s kubectl get serviceaccount default") + + with subtest("should create a sample secret object"): + k3s.succeed("k3s kubectl create secret generic nixossecret --from-literal thesecret=abacadabra") + + with subtest("should check if secret is correct"): + k3s.wait_until_succeeds("[[ $(kubectl get secrets nixossecret -o json | jq -r .data.thesecret | base64 -d) == abacadabra ]]") + + with subtest("should have a secret in database"): + etcd.wait_until_succeeds("[[ $(etcdctl get /registry/secrets/default/nixossecret | head -c1 | wc -c) -ne 0 ]]") + + with subtest("should delete the secret"): + k3s.succeed("k3s kubectl delete secret nixossecret") + + with subtest("should not have a secret in database"): + etcd.wait_until_fails("[[ $(etcdctl get /registry/secrets/default/nixossecret | head -c1 | wc -c) -ne 0 ]]") + + with subtest("should shutdown k3s and etcd"): + k3s.shutdown() + etcd.shutdown() + ''; + + meta.maintainers = etcd.meta.maintainers ++ k3s.meta.maintainers; +}) diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix index 0dcab39f3fada..9714a94382ee0 100644 --- a/nixos/tests/kernel-generic.nix +++ b/nixos/tests/kernel-generic.nix @@ -30,7 +30,6 @@ let linux_5_10_hardened linux_5_15_hardened linux_6_1_hardened - linux_6_5_hardened linux_6_6_hardened linux_6_7_hardened linux_rt_5_4 diff --git a/nixos/tests/lomiri-system-settings.nix b/nixos/tests/lomiri-system-settings.nix new file mode 100644 index 0000000000000..867fc14797e77 --- /dev/null +++ b/nixos/tests/lomiri-system-settings.nix @@ -0,0 +1,99 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "lomiri-system-settings-standalone"; + meta.maintainers = lib.teams.lomiri.members; + + nodes.machine = { config, pkgs, ... }: { + imports = [ + ./common/x11.nix + ]; + + services.xserver.enable = true; + + environment = { + systemPackages = with pkgs.lomiri; [ + suru-icon-theme + lomiri-system-settings + ]; + variables = { + UITK_ICON_THEME = "suru"; + }; + }; + + i18n.supportedLocales = [ "all" ]; + + fonts.packages = with pkgs; [ + # Intended font & helps with OCR + ubuntu_font_family + ]; + + services.upower.enable = true; + }; + + enableOCR = true; + + testScript = let + settingsPages = [ + # Base pages + { name = "wifi"; type = "internal"; element = "networks"; } + { name = "bluetooth"; type = "internal"; element = "discoverable|None detected"; } + # only text we can really look for with VPN is on a button, OCR on CI struggles with it + { name = "vpn"; type = "internal"; element = "Add|Manual|Configuration"; skipOCR = true; } + { name = "appearance"; type = "internal"; element = "Background image|blur effects"; } + { name = "desktop"; type = "internal"; element = "workspaces|Icon size"; } + { name = "sound"; type = "internal"; element = "Silent Mode|Message sound"; } + { name = "language"; type = "internal"; element = "Display language|External keyboard"; } + { name = "notification"; type = "internal"; element = "Apps that notify"; } + { name = "gestures"; type = "internal"; element = "Edge drag"; } + { name = "mouse"; type = "internal"; element = "Cursor speed|Wheel scrolling speed"; } + { name = "timedate"; type = "internal"; element = "Time zone|Set the time and date"; } + + # External plugins + { name = "security-privacy"; type = "external"; element = "Locking|unlocking|permissions"; elementLocalised = "Sperren|Entsperren|Berechtigungen"; } + ]; + in + '' + machine.wait_for_x() + + with subtest("lomiri system settings launches"): + machine.execute("lomiri-system-settings >&2 &") + machine.wait_for_text("System Settings") + machine.screenshot("lss_open") + + # Move focus to start of plugins list for following list of tests + machine.send_key("tab") + machine.send_key("tab") + machine.screenshot("lss_focus") + + # tab through & open all sub-menus, to make sure none of them fail + '' + (lib.strings.concatMapStringsSep "\n" (page: '' + machine.send_key("tab") + machine.send_key("kp_enter") + '' + + lib.optionalString (!(page.skipOCR or false)) '' + with subtest("lomiri system settings ${page.name} works"): + machine.wait_for_text(r"(${page.element})") + machine.screenshot("lss_page_${page.name}") + '') settingsPages) + '' + + machine.execute("pkill -f lomiri-system-settings") + + with subtest("lomiri system settings localisation works"): + machine.execute("env LANG=de_DE.UTF-8 lomiri-system-settings >&2 &") + machine.wait_for_text("Systemeinstellungen") + machine.screenshot("lss_localised_open") + + # Move focus to start of plugins list for following list of tests + machine.send_key("tab") + machine.send_key("tab") + machine.screenshot("lss_focus_localised") + + '' + (lib.strings.concatMapStringsSep "\n" (page: '' + machine.send_key("tab") + machine.send_key("kp_enter") + '' + lib.optionalString (page.type == "external") '' + with subtest("lomiri system settings ${page.name} localisation works"): + machine.wait_for_text(r"(${page.elementLocalised})") + machine.screenshot("lss_localised_page_${page.name}") + '') settingsPages) + '' + ''; +}) diff --git a/nixos/tests/matter-server.nix b/nixos/tests/matter-server.nix new file mode 100644 index 0000000000000..c646e9840d194 --- /dev/null +++ b/nixos/tests/matter-server.nix @@ -0,0 +1,45 @@ +import ./make-test-python.nix ({ pkgs, lib, ...} : + +let + chipVersion = pkgs.python311Packages.home-assistant-chip-core.version; +in + +{ + name = "matter-server"; + meta.maintainers = with lib.maintainers; [ leonm1 ]; + + nodes = { + machine = { config, ... }: { + services.matter-server = { + enable = true; + port = 1234; + }; + }; + }; + + testScript = /* python */ '' + start_all() + + machine.wait_for_unit("matter-server.service") + machine.wait_for_open_port(1234) + + with subtest("Check websocket server initialized"): + output = machine.succeed("echo \"\" | ${pkgs.websocat}/bin/websocat ws://localhost:1234/ws") + machine.log(output) + + assert '"sdk_version": "${chipVersion}"' in output, ( + 'CHIP version \"${chipVersion}\" not present in websocket message' + ) + + assert '"fabric_id": 1' in output, ( + "fabric_id not propagated to server" + ) + + with subtest("Check storage directory is created"): + machine.succeed("ls /var/lib/matter-server/chip.json") + + with subtest("Check systemd hardening"): + _, output = machine.execute("systemd-analyze security matter-server.service | grep -v '✓'") + machine.log(output) + ''; +}) diff --git a/nixos/tests/mealie.nix b/nixos/tests/mealie.nix new file mode 100644 index 0000000000000..88f749c712948 --- /dev/null +++ b/nixos/tests/mealie.nix @@ -0,0 +1,24 @@ +import ./make-test-python.nix ({ pkgs, ...} : + +{ + name = "mealie"; + meta = with pkgs.lib.maintainers; { + maintainers = [ litchipi ]; + }; + + nodes = { + server = { + services.mealie = { + enable = true; + port = 9001; + }; + }; + }; + + testScript = '' + start_all() + server.wait_for_unit("mealie.service") + server.wait_for_open_port(9001) + server.succeed("curl --fail http://localhost:9001") + ''; +}) diff --git a/nixos/tests/miniflux.nix b/nixos/tests/miniflux.nix index a3af53db0e7a1..6d38224448ed6 100644 --- a/nixos/tests/miniflux.nix +++ b/nixos/tests/miniflux.nix @@ -15,6 +15,10 @@ let ADMIN_USERNAME=${username} ADMIN_PASSWORD=${password} ''; + postgresPassword = "correcthorsebatterystaple"; + postgresPasswordFile = pkgs.writeText "pgpass" '' + *:*:*:*:${postgresPassword} + ''; in { @@ -56,32 +60,62 @@ in adminCredentialsFile = customAdminCredentialsFile; }; }; + + postgresTcp = { config, pkgs, lib, ... }: { + services.postgresql = { + enable = true; + initialScript = pkgs.writeText "init-postgres" '' + CREATE USER miniflux WITH PASSWORD '${postgresPassword}'; + CREATE DATABASE miniflux WITH OWNER miniflux; + ''; + enableTCPIP = true; + authentication = '' + host sameuser miniflux samenet scram-sha-256 + ''; + }; + systemd.services.postgresql.postStart = lib.mkAfter '' + $PSQL -tAd miniflux -c 'CREATE EXTENSION hstore;' + ''; + networking.firewall.allowedTCPPorts = [ config.services.postgresql.port ]; + }; + externalDb = { ... }: { + security.apparmor.enable = true; + services.miniflux = { + enable = true; + createDatabaseLocally = false; + inherit adminCredentialsFile; + config = { + DATABASE_URL = "user=miniflux host=postgresTcp dbname=miniflux sslmode=disable"; + PGPASSFILE = "/run/miniflux/pgpass"; + }; + }; + systemd.services.miniflux.preStart = '' + cp ${postgresPasswordFile} /run/miniflux/pgpass + chmod 600 /run/miniflux/pgpass + ''; + }; }; testScript = '' - start_all() + def runTest(machine, port, user): + machine.wait_for_unit("miniflux.service") + machine.wait_for_open_port(port) + machine.succeed(f"curl --fail 'http://localhost:{port}/healthcheck' | grep OK") + machine.succeed( + f"curl 'http://localhost:{port}/v1/me' -u '{user}' -H Content-Type:application/json | grep '\"is_admin\":true'" + ) + machine.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') - default.wait_for_unit("miniflux.service") - default.wait_for_open_port(${toString defaultPort}) - default.succeed("curl --fail 'http://localhost:${toString defaultPort}/healthcheck' | grep OK") - default.succeed( - "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'" - ) - default.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') + default.start() + withoutSudo.start() + customized.start() + postgresTcp.start() - withoutSudo.wait_for_unit("miniflux.service") - withoutSudo.wait_for_open_port(${toString defaultPort}) - withoutSudo.succeed("curl --fail 'http://localhost:${toString defaultPort}/healthcheck' | grep OK") - withoutSudo.succeed( - "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'" - ) - withoutSudo.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') + runTest(default, ${toString defaultPort}, "${defaultUsername}:${defaultPassword}") + runTest(withoutSudo, ${toString defaultPort}, "${defaultUsername}:${defaultPassword}") + runTest(customized, ${toString port}, "${username}:${password}") - customized.wait_for_unit("miniflux.service") - customized.wait_for_open_port(${toString port}) - customized.succeed("curl --fail 'http://localhost:${toString port}/healthcheck' | grep OK") - customized.succeed( - "curl 'http://localhost:${toString port}/v1/me' -u '${username}:${password}' -H Content-Type:application/json | grep '\"is_admin\":true'" - ) - customized.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') + postgresTcp.wait_for_unit("postgresql.service") + externalDb.start() + runTest(externalDb, ${toString defaultPort}, "${defaultUsername}:${defaultPassword}") ''; }) diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix index ece4864f771c0..67eb0cd884400 100644 --- a/nixos/tests/minio.nix +++ b/nixos/tests/minio.nix @@ -43,17 +43,17 @@ import ./make-test-python.nix ({ pkgs, ... }: # Minio requires at least 1GiB of free disk space to run. virtualisation.diskSize = 4 * 1024; + + # Minio pre allocates 2GiB or memory, reserve some more + virtualisation.memorySize = 4096; }; }; testScript = '' - import time start_all() # simulate manually editing root credentials file machine.wait_for_unit("multi-user.target") - machine.copy_from_host("${credsPartial}", "${rootCredentialsFile}") - time.sleep(3) machine.copy_from_host("${credsFull}", "${rootCredentialsFile}") machine.wait_for_unit("minio.service") diff --git a/nixos/tests/monado.nix b/nixos/tests/monado.nix new file mode 100644 index 0000000000000..8368950951e73 --- /dev/null +++ b/nixos/tests/monado.nix @@ -0,0 +1,39 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "monado"; + + nodes.machine = + { pkgs, ... }: + + { + hardware.opengl.enable = true; + users.users.alice = { + isNormalUser = true; + uid = 1000; + }; + + services.monado = { + enable = true; + defaultRuntime = true; + }; + # Stop Monado from probing for any hardware + systemd.user.services.monado.environment.SIMULATED_ENABLE = "1"; + + environment.systemPackages = with pkgs; [ openxr-loader ]; + }; + + testScript = { nodes, ... }: + let + userId = toString nodes.machine.users.users.alice.uid; + runtimePath = "/run/user/${userId}"; + in + '' + machine.succeed("loginctl enable-linger alice") + machine.wait_for_unit("user@${userId}.service") + + machine.wait_for_unit("monado.socket", "alice") + machine.systemctl("start monado.service", "alice") + machine.wait_for_unit("monado.service", "alice") + + machine.succeed("su -- alice -c env XDG_RUNTIME_DIR=${runtimePath} openxr_runtime_list") + ''; +}) diff --git a/nixos/tests/plasma6.nix b/nixos/tests/plasma6.nix new file mode 100644 index 0000000000000..ec5b3f24ef749 --- /dev/null +++ b/nixos/tests/plasma6.nix @@ -0,0 +1,64 @@ +import ./make-test-python.nix ({ pkgs, ...} : + +{ + name = "plasma6"; + meta = with pkgs.lib.maintainers; { + maintainers = [ k900 ]; + }; + + nodes.machine = { ... }: + + { + imports = [ ./common/user-account.nix ]; + services.xserver.enable = true; + services.xserver.displayManager.sddm.enable = true; + # FIXME: this should be testing Wayland + services.xserver.displayManager.defaultSession = "plasmax11"; + services.xserver.desktopManager.plasma6.enable = true; + environment.plasma6.excludePackages = [ pkgs.kdePackages.elisa ]; + services.xserver.displayManager.autoLogin = { + enable = true; + user = "alice"; + }; + }; + + testScript = { nodes, ... }: let + user = nodes.machine.users.users.alice; + xdo = "${pkgs.xdotool}/bin/xdotool"; + in '' + with subtest("Wait for login"): + start_all() + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") + + with subtest("Check plasmashell started"): + machine.wait_until_succeeds("pgrep plasmashell") + machine.wait_for_window("^Desktop ") + + with subtest("Check that KDED is running"): + machine.succeed("pgrep kded6") + + with subtest("Ensure Elisa is not installed"): + machine.fail("which elisa") + + machine.succeed("su - ${user.name} -c 'xauth merge /tmp/xauth_*'") + + with subtest("Run Dolphin"): + machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'") + machine.wait_for_window(" Dolphin") + + with subtest("Run Konsole"): + machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 konsole >&2 &'") + machine.wait_for_window("Konsole") + + with subtest("Run systemsettings"): + machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 systemsettings >&2 &'") + machine.wait_for_window("Settings") + + with subtest("Wait to get a screenshot"): + machine.execute( + "${xdo} key Alt+F1 sleep 10" + ) + machine.screenshot("screen") + ''; +}) diff --git a/nixos/tests/qemu-vm-external-disk-image.nix b/nixos/tests/qemu-vm-external-disk-image.nix index a229fc5e39633..c481159511a02 100644 --- a/nixos/tests/qemu-vm-external-disk-image.nix +++ b/nixos/tests/qemu-vm-external-disk-image.nix @@ -69,5 +69,8 @@ in os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name machine.succeed("findmnt --kernel --source ${rootFsDevice} --target /") + + # Make sure systemd boot didn't clobber this + machine.succeed("[ ! -e /homeless-shelter ]") ''; } diff --git a/nixos/tests/searx.nix b/nixos/tests/searx.nix index 2f808cb65266e..02a88f690db78 100644 --- a/nixos/tests/searx.nix +++ b/nixos/tests/searx.nix @@ -36,7 +36,7 @@ import ./make-test-python.nix ({ pkgs, ...} : }; # fancy setup: run in uWSGI and use nginx as proxy - nodes.fancy = { ... }: { + nodes.fancy = { config, ... }: { imports = [ ../modules/profiles/minimal.nix ]; services.searx = { @@ -65,7 +65,7 @@ import ./make-test-python.nix ({ pkgs, ...} : include ${pkgs.nginx}/conf/uwsgi_params; uwsgi_pass unix:/run/searx/uwsgi.sock; ''; - locations."/searx/static/".alias = "${pkgs.searx}/share/static/"; + locations."/searx/static/".alias = "${config.services.searx.package}/share/static/"; }; # allow nginx access to the searx socket @@ -108,7 +108,7 @@ import ./make-test-python.nix ({ pkgs, ...} : "${pkgs.curl}/bin/curl --fail http://localhost/searx >&2" ) fancy.succeed( - "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/oscar/js/bootstrap.min.js >&2" + "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/simple/js/leaflet.js >&2" ) ''; }) diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index ce3245f3d8627..54c380602bd40 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -14,6 +14,72 @@ let boot.loader.efi.canTouchEfiVariables = true; environment.systemPackages = [ pkgs.efibootmgr ]; }; + + commonXbootldr = { config, lib, pkgs, ... }: + let + diskImage = import ../lib/make-disk-image.nix { + inherit config lib pkgs; + label = "nixos"; + format = "qcow2"; + partitionTableType = "efixbootldr"; + touchEFIVars = true; + installBootLoader = true; + }; + in + { + imports = [ common ]; + virtualisation.useBootLoader = lib.mkForce false; # Only way to tell qemu-vm not to create the default system image + virtualisation.directBoot.enable = false; # But don't direct boot either because we're testing systemd-boot + + system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition + virtualisation.efi.variables = "${diskImage}/efi-vars.fd"; + + virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage` + virtualisation.bootPartition = null; + virtualisation.fileSystems = { + "/" = { + device = "/dev/vda3"; + fsType = "ext4"; + }; + "/boot" = { + device = "/dev/vda2"; + fsType = "vfat"; + noCheck = true; + }; + "/efi" = { + device = "/dev/vda1"; + fsType = "vfat"; + noCheck = true; + }; + }; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.efiSysMountPoint = "/efi"; + boot.loader.systemd-boot.xbootldrMountPoint = "/boot"; + }; + + customDiskImage = nodes: '' + import os + import subprocess + import tempfile + + tmp_disk_image = tempfile.NamedTemporaryFile() + + subprocess.run([ + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", + "create", + "-f", + "qcow2", + "-b", + "${nodes.machine.system.build.diskImage}/nixos.qcow2", + "-F", + "qcow2", + tmp_disk_image.name, + ]) + + # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + ''; in { basic = makeTest { @@ -27,6 +93,7 @@ in machine.wait_for_unit("multi-user.target") machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf") # Ensure we actually booted using systemd-boot # Magic number is the vendor UUID used by systemd-boot. @@ -49,15 +116,17 @@ in virtualisation.useSecureBoot = true; }; - testScript = '' + testScript = let + efiArch = pkgs.stdenv.hostPlatform.efiArch; + in { nodes, ... }: '' machine.start(allow_reboot=True) machine.wait_for_unit("multi-user.target") machine.succeed("sbctl create-keys") machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine") - machine.succeed('sbctl sign /boot/EFI/systemd/systemd-bootx64.efi') - machine.succeed('sbctl sign /boot/EFI/BOOT/BOOTX64.EFI') - machine.succeed('sbctl sign /boot/EFI/nixos/*bzImage.efi') + machine.succeed('sbctl sign /boot/EFI/systemd/systemd-boot${efiArch}.efi') + machine.succeed('sbctl sign /boot/EFI/BOOT/BOOT${toUpper efiArch}.EFI') + machine.succeed('sbctl sign /boot/EFI/nixos/*${nodes.machine.system.boot.loader.kernelFile}.efi') machine.reboot() @@ -65,6 +134,32 @@ in ''; }; + basicXbootldr = makeTest { + name = "systemd-boot-xbootldr"; + meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; + + nodes.machine = commonXbootldr; + + testScript = { nodes, ... }: '' + ${customDiskImage nodes} + + machine.start() + machine.wait_for_unit("multi-user.target") + + machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") + machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf") + + # Ensure we actually booted using systemd-boot + # Magic number is the vendor UUID used by systemd-boot. + machine.succeed( + "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" + ) + + # "bootctl install" should have created an EFI entry + machine.succeed('efibootmgr | grep "Linux Boot Manager"') + ''; + }; + # Check that specialisations create corresponding boot entries. specialisation = makeTest { name = "systemd-boot-specialisation"; @@ -72,7 +167,9 @@ in nodes.machine = { pkgs, lib, ... }: { imports = [ common ]; - specialisation.something.configuration = {}; + specialisation.something.configuration = { + boot.loader.systemd-boot.sortKey = "something"; + }; }; testScript = '' @@ -85,6 +182,9 @@ in machine.succeed( "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" ) + machine.succeed( + "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) ''; }; @@ -162,25 +262,46 @@ in }; testScript = '' - machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf") + machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") ''; }; - entryFilename = makeTest { - name = "systemd-boot-entry-filename"; + memtestSortKey = makeTest { + name = "systemd-boot-memtest-sortkey"; meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ]; nodes.machine = { pkgs, lib, ... }: { imports = [ common ]; boot.loader.systemd-boot.memtest86.enable = true; - boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf"; + boot.loader.systemd-boot.memtest86.sortKey = "apple"; }; testScript = '' - machine.fail("test -e /boot/loader/entries/memtest86.conf") - machine.succeed("test -e /boot/loader/entries/apple.conf") + machine.succeed("test -e /boot/loader/entries/memtest86.conf") machine.succeed("test -e /boot/efi/memtest86/memtest.efi") + machine.succeed("grep 'sort-key apple' /boot/loader/entries/memtest86.conf") + ''; + }; + + entryFilenameXbootldr = makeTest { + name = "systemd-boot-entry-filename-xbootldr"; + meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ]; + + nodes.machine = { pkgs, lib, ... }: { + imports = [ commonXbootldr ]; + boot.loader.systemd-boot.memtest86.enable = true; + }; + + testScript = { nodes, ... }: '' + ${customDiskImage nodes} + + machine.start() + machine.wait_for_unit("multi-user.target") + + machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi") + machine.succeed("test -e /boot/loader/entries/memtest86.conf") + machine.succeed("test -e /boot/EFI/memtest86/memtest.efi") ''; }; @@ -271,9 +392,9 @@ in machine.succeed("${finalSystem}/bin/switch-to-configuration boot") machine.fail("test -e /boot/efi/fruits/tomato.efi") machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") - machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf") + machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") - machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf") + machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/netbootxyz.conf") machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") ''; }; diff --git a/nixos/tests/transfer-sh.nix b/nixos/tests/transfer-sh.nix new file mode 100644 index 0000000000000..f4ab7d28858e1 --- /dev/null +++ b/nixos/tests/transfer-sh.nix @@ -0,0 +1,20 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "transfer-sh"; + + meta = { + maintainers = with lib.maintainers; [ ocfox ]; + }; + + nodes.machine = { pkgs, ... }: { + services.transfer-sh = { + enable = true; + settings.LISTENER = ":1234"; + }; + }; + + testScript = '' + machine.wait_for_unit("transfer-sh.service") + machine.wait_for_open_port(1234) + machine.succeed("curl --fail http://localhost:1234/") + ''; +}) diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix index 60fd5ce13854e..4e2bf166a7b6c 100644 --- a/nixos/tests/vikunja.nix +++ b/nixos/tests/vikunja.nix @@ -13,15 +13,20 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { frontendScheme = "http"; frontendHostname = "localhost"; }; - services.nginx.enable = true; + services.nginx = { + enable = true; + virtualHosts."http://localhost" = { + locations."/".proxyPass = "http://localhost:3456"; + }; + }; }; vikunjaPostgresql = { pkgs, ... }: { services.vikunja = { enable = true; database = { type = "postgres"; - user = "vikunja-api"; - database = "vikunja-api"; + user = "vikunja"; + database = "vikunja"; host = "/run/postgresql"; }; frontendScheme = "http"; @@ -30,20 +35,25 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { }; services.postgresql = { enable = true; - ensureDatabases = [ "vikunja-api" ]; + ensureDatabases = [ "vikunja" ]; ensureUsers = [ - { name = "vikunja-api"; + { name = "vikunja"; ensureDBOwnership = true; } ]; }; - services.nginx.enable = true; + services.nginx = { + enable = true; + virtualHosts."http://localhost" = { + locations."/".proxyPass = "http://localhost:9090"; + }; + }; }; }; testScript = '' - vikunjaSqlite.wait_for_unit("vikunja-api.service") + vikunjaSqlite.wait_for_unit("vikunja.service") vikunjaSqlite.wait_for_open_port(3456) vikunjaSqlite.succeed("curl --fail http://localhost:3456/api/v1/info") @@ -52,7 +62,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { vikunjaSqlite.succeed("curl --fail http://localhost/api/v1/info") vikunjaSqlite.succeed("curl --fail http://localhost") - vikunjaPostgresql.wait_for_unit("vikunja-api.service") + vikunjaPostgresql.wait_for_unit("vikunja.service") vikunjaPostgresql.wait_for_open_port(9090) vikunjaPostgresql.succeed("curl --fail http://localhost:9090/api/v1/info") diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix index e522d0679e151..3c2a391233dbd 100644 --- a/nixos/tests/virtualbox.nix +++ b/nixos/tests/virtualbox.nix @@ -3,6 +3,7 @@ pkgs ? import ../.. { inherit system config; }, debug ? false, enableUnfree ? false, + enableKvm ? false, use64bitGuest ? true }: @@ -340,7 +341,7 @@ let testExtensionPack.vmFlags = enableExtensionPackVMFlags; }; - mkVBoxTest = useExtensionPack: vms: name: testScript: makeTest { + mkVBoxTest = vboxHostConfig: vms: name: testScript: makeTest { name = "virtualbox-${name}"; nodes.machine = { lib, config, ... }: { @@ -349,14 +350,23 @@ let vmConfigs = mapAttrsToList mkVMConf vms; in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs; virtualisation.memorySize = 2048; - virtualisation.qemu.options = ["-cpu" "kvm64,svm=on,vmx=on"]; - virtualisation.virtualbox.host.enable = true; + + virtualisation.qemu.options = let + # IvyBridge is reasonably ancient to be compatible with recent + # Intel/AMD hosts and sufficient for the KVM flavor. + guestCpu = if config.virtualisation.virtualbox.host.enableKvm then "IvyBridge" else "kvm64"; + in ["-cpu" "${guestCpu},svm=on,vmx=on"]; + test-support.displayManager.auto.user = "alice"; users.users.alice.extraGroups = let inherit (config.virtualisation.virtualbox.host) enableHardening; - in lib.mkIf enableHardening (lib.singleton "vboxusers"); - virtualisation.virtualbox.host.enableExtensionPack = useExtensionPack; - nixpkgs.config.allowUnfree = useExtensionPack; + in lib.mkIf enableHardening [ "vboxusers" ]; + + virtualisation.virtualbox.host = { + enable = true; + } // vboxHostConfig; + + nixpkgs.config.allowUnfree = config.virtualisation.virtualbox.host.enableExtensionPack; }; testScript = '' @@ -390,7 +400,7 @@ let }; }; - unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) { + unfreeTests = mapAttrs (mkVBoxTest { enableExtensionPack = true; } vboxVMsWithExtpack) { enable-extension-pack = '' create_vm_testExtensionPack() vbm("startvm testExtensionPack") @@ -409,7 +419,24 @@ let ''; }; -in mapAttrs (mkVBoxTest false vboxVMs) { + kvmTests = mapAttrs (mkVBoxTest { + enableKvm = true; + + # Once the KVM version supports these, we can enable them. + addNetworkInterface = false; + enableHardening = false; + } vboxVMs) { + kvm-headless = '' + create_vm_headless() + machine.succeed(ru("VBoxHeadless --startvm headless >&2 & disown %1")) + wait_for_startup_headless() + wait_for_vm_boot_headless() + shutdown_vm_headless() + destroy_vm_headless() + ''; + }; + +in mapAttrs (mkVBoxTest {} vboxVMs) { simple-gui = '' # Home to select Tools, down to move to the VM, enter to start it. def send_vm_startup(): @@ -519,4 +546,6 @@ in mapAttrs (mkVBoxTest false vboxVMs) { destroy_vm_test1() destroy_vm_test2() ''; -} // (optionalAttrs enableUnfree unfreeTests) +} +// (optionalAttrs enableKvm kvmTests) +// (optionalAttrs enableUnfree unfreeTests) diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix index 0b411b0b9d8a5..851fced2c5e1e 100644 --- a/nixos/tests/zfs.nix +++ b/nixos/tests/zfs.nix @@ -7,14 +7,14 @@ with import ../lib/testing-python.nix { inherit system pkgs; }; let - makeZfsTest = name: + makeZfsTest = { kernelPackages , enableSystemdStage1 ? false , zfsPackage , extraTest ? "" }: makeTest { - name = "zfs-" + name; + name = zfsPackage.kernelModuleAttribute; meta = with pkgs.lib.maintainers; { maintainers = [ elvishjerricco ]; }; @@ -192,23 +192,23 @@ let in { # maintainer: @raitobezarius - series_2_1 = makeZfsTest "2.1-series" { + series_2_1 = makeZfsTest { zfsPackage = pkgs.zfs_2_1; kernelPackages = pkgs.linuxPackages; }; - stable = makeZfsTest "stable" { - zfsPackage = pkgs.zfsStable; + series_2_2 = makeZfsTest { + zfsPackage = pkgs.zfs_2_2; kernelPackages = pkgs.linuxPackages; }; - unstable = makeZfsTest "unstable" rec { - zfsPackage = pkgs.zfsUnstable; + unstable = makeZfsTest rec { + zfsPackage = pkgs.zfs_unstable; kernelPackages = zfsPackage.latestCompatibleLinuxPackages; }; - unstableWithSystemdStage1 = makeZfsTest "unstable" rec { - zfsPackage = pkgs.zfsUnstable; + unstableWithSystemdStage1 = makeZfsTest rec { + zfsPackage = pkgs.zfs_unstable; kernelPackages = zfsPackage.latestCompatibleLinuxPackages; enableSystemdStage1 = true; }; |