diff options
Diffstat (limited to 'nixos')
528 files changed, 18702 insertions, 8471 deletions
diff --git a/nixos/README.md b/nixos/README.md index 5f751e10e20a..2261803f4e7d 100644 --- a/nixos/README.md +++ b/nixos/README.md @@ -80,6 +80,7 @@ Reviewing process: - Ensure that all file paths [fit the guidelines](../CONTRIBUTING.md#file-naming-and-organisation). - Ensure that the module tests, if any, are succeeding. +- Ensure that new module tests are added to the package `passthru.tests`. - Ensure that the introduced options are correct. - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated). - Description, default and example should be provided. @@ -95,7 +96,8 @@ Sample template for a new module review is provided below. ##### Reviewed points - [ ] module path fits the guidelines -- [ ] module tests succeed on ARCHITECTURE +- [ ] module tests, if any, succeed on ARCHITECTURE +- [ ] module tests, if any, are added to package `passthru.tests` - [ ] options have appropriate types - [ ] options have default - [ ] options have example diff --git a/nixos/doc/manual/configuration/adding-custom-packages.section.md b/nixos/doc/manual/configuration/adding-custom-packages.section.md index f9a5221d6c93..f2012d9c2462 100644 --- a/nixos/doc/manual/configuration/adding-custom-packages.section.md +++ b/nixos/doc/manual/configuration/adding-custom-packages.section.md @@ -90,16 +90,30 @@ Hello, world! Most pre-built executables will not work on NixOS. There are two notable exceptions: flatpaks and AppImages. For flatpaks see the [dedicated -section](#module-services-flatpak). AppImages will not run "as-is" on NixOS. -First you need to install `appimage-run`: add to `/etc/nixos/configuration.nix` +section](#module-services-flatpak). AppImages can run "as-is" on NixOS. + +First you need to enable AppImage support: add to `/etc/nixos/configuration.nix` ```nix { - environment.systemPackages = [ pkgs.appimage-run ]; + programs.appimage.enable = true; + programs.appimage.binfmt = true; } ``` -Then instead of running the AppImage "as-is", run `appimage-run foo.appimage`. +Then you can run the AppImage "as-is" or with `appimage-run foo.appimage`. + +If there are shared libraries missing add them with + +```nix +{ + programs.appimage.package = pkgs.appimage-run.override { + extraPkgs = pkgs: [ + # missing libraries here, e.g.: `pkgs.libepoxy` + ]; + } +} +``` To make other pre-built executables work on NixOS, you need to package them with Nix and special helpers like `autoPatchelfHook` or `buildFHSEnv`. See diff --git a/nixos/doc/manual/configuration/customizing-packages.section.md b/nixos/doc/manual/configuration/customizing-packages.section.md index 074932b3f110..db7a6c60fade 100644 --- a/nixos/doc/manual/configuration/customizing-packages.section.md +++ b/nixos/doc/manual/configuration/customizing-packages.section.md @@ -33,8 +33,8 @@ Unfortunately, Nixpkgs currently lacks a way to query available package configur ::: {.note} For example, many packages come with extensions one might add. Examples include: -- [`passExtensions.pass-otp`](https://search.nixos.org/packages/query=passExtensions.pass-otp) -- [`python310Packages.requests`](https://search.nixos.org/packages/query=python310Packages.requests) +- [`passExtensions.pass-otp`](https://search.nixos.org/packages?query=passExtensions.pass-otp) +- [`python312Packages.requests`](https://search.nixos.org/packages?query=python312Packages.requests) You can use them like this: ```nix diff --git a/nixos/doc/manual/configuration/linux-kernel.chapter.md b/nixos/doc/manual/configuration/linux-kernel.chapter.md index 3bc97446f452..4aeda5f46764 100644 --- a/nixos/doc/manual/configuration/linux-kernel.chapter.md +++ b/nixos/doc/manual/configuration/linux-kernel.chapter.md @@ -133,20 +133,3 @@ This section was moved to the [Nixpkgs manual](https://nixos.org/nixpkgs/manual# It's a common issue that the latest stable version of ZFS doesn't support the latest available Linux kernel. It is recommended to use the latest available LTS that's compatible with ZFS. Usually this is the default kernel provided by nixpkgs (i.e. `pkgs.linuxPackages`). - -Alternatively, it's possible to pin the system to the latest available kernel -version _that is supported by ZFS_ like this: - -```nix -{ - boot.kernelPackages = pkgs.zfs.latestCompatibleLinuxPackages; -} -``` - -Please note that the version this attribute points to isn't monotonic because the latest kernel -version only refers to kernel versions supported by the Linux developers. In other words, -the latest kernel version that ZFS is compatible with may decrease over time. - -An example: the latest version ZFS is compatible with is 5.19 which is a non-longterm version. When 5.19 -is out of maintenance, the latest supported kernel version is 5.15 because it's longterm and the versions -5.16, 5.17 and 5.18 are already out of maintenance because they're non-longterm. diff --git a/nixos/doc/manual/configuration/wayland.chapter.md b/nixos/doc/manual/configuration/wayland.chapter.md index 27c027d38514..2d32f06987df 100644 --- a/nixos/doc/manual/configuration/wayland.chapter.md +++ b/nixos/doc/manual/configuration/wayland.chapter.md @@ -18,14 +18,9 @@ This installs the sway compositor along with some essential utilities. Now you can start sway from the TTY console. If you are using a wlroots-based compositor, like sway, and want to be -able to share your screen, you might want to activate this option: - -```nix -{ - xdg.portal.wlr.enable = true; -} -``` - -and configure Pipewire using +able to share your screen, make sure to configure Pipewire using [](#opt-services.pipewire.enable) and related options. + +For more helpful tips and tricks, see the +[wiki page about Sway](https://wiki.nixos.org/wiki/Sway). diff --git a/nixos/doc/manual/development/writing-modules.chapter.md b/nixos/doc/manual/development/writing-modules.chapter.md index 67a5cc23a6aa..4b1be6520ef8 100644 --- a/nixos/doc/manual/development/writing-modules.chapter.md +++ b/nixos/doc/manual/development/writing-modules.chapter.md @@ -140,7 +140,8 @@ in { path = [ pkgs.su ]; script = '' - mkdir -m 0755 -p $(dirname ${toString cfg.output}) + mkdir -p $(dirname ${toString cfg.output}) + chmod 0755 $(dirname ${toString cfg.output}) exec updatedb \ --localuser=${cfg.localuser} \ ${optionalString (!cfg.includeStore) "--prunepaths='/nix/store'"} \ diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md index 3ce12f41c60f..bd588e2ba80b 100644 --- a/nixos/doc/manual/development/writing-nixos-tests.section.md +++ b/nixos/doc/manual/development/writing-nixos-tests.section.md @@ -71,20 +71,20 @@ nix-build -A nixosTests.hostname ### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos} -Outside the `nixpkgs` repository, you can instantiate the test by first importing the NixOS library, +Outside the `nixpkgs` repository, you can use the `runNixOSTest` function from +`pkgs.testers`: ```nix -let nixos-lib = import (nixpkgs + "/nixos/lib") { }; +let pkgs = import <nixpkgs> {}; in -nixos-lib.runTest { +pkgs.testers.runNixOSTest { imports = [ ./test.nix ]; - hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs defaults.services.foo.package = mypkg; } ``` -`runTest` returns a derivation that runs the test. +`runNixOSTest` returns a derivation that runs the test. ## Configuring the nodes {#sec-nixos-test-nodes} diff --git a/nixos/doc/manual/installation/installing-virtualbox-guest.section.md b/nixos/doc/manual/installation/installing-virtualbox-guest.section.md index 415119bd8c89..a887a923e57f 100644 --- a/nixos/doc/manual/installation/installing-virtualbox-guest.section.md +++ b/nixos/doc/manual/installation/installing-virtualbox-guest.section.md @@ -1,10 +1,8 @@ # Installing in a VirtualBox guest {#sec-installing-virtualbox-guest} Installing NixOS into a VirtualBox guest is convenient for users who -want to try NixOS without installing it on bare metal. If you want to -use a pre-made VirtualBox appliance, it is available at [the downloads -page](https://nixos.org/download/#nixos-virtualbox). If you want to set -up a VirtualBox guest manually, follow these instructions: +want to try NixOS without installing it on bare metal. If you want to set +up a VirtualBox guest, follow these instructions: 1. Add a New Machine in VirtualBox with OS Type "Linux / Other Linux" diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index e729fdcbb139..2f869b07e072 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -85,7 +85,7 @@ In addition to numerous new and upgraded packages, this release has the followin - [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html), a lightweight shipper for forwarding and centralizing log data. Available as [services.filebeat](#opt-services.filebeat.enable). -- [FRRouting](https://frrouting.org/), a popular suite of Internet routing protocol daemons (BGP, BFD, OSPF, IS-IS, VRRP and others). Available as [services.frr](#opt-services.frr.babel.enable). +- [FRRouting](https://frrouting.org/), a popular suite of Internet routing protocol daemons (BGP, BFD, OSPF, IS-IS, VRRP and others). Available as [services.frr](#opt-services.frr.babeld.enable). - [Grafana Mimir](https://grafana.com/oss/mimir/), an open source, horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus. Available as [services.mimir](#opt-services.mimir.enable). diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index f5d1d3016a78..cae573c3452a 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -252,7 +252,7 @@ In addition to numerous new and updated packages, this release has the following } ``` -- The default module options for [services.snapserver.openFirewall](#opt-services.snapserver.openFirewall), [services.tmate-ssh-server.openFirewall](#opt-services.tmate-ssh-server.openFirewall) and [services.unifi-video.openFirewall](#opt-services.unifi-video.openFirewall) have been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall. +- The default module options for [services.snapserver.openFirewall](#opt-services.snapserver.openFirewall), [services.tmate-ssh-server.openFirewall](#opt-services.tmate-ssh-server.openFirewall) and `services.unifi-video.openFirewall` have been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall. - The option `i18n.inputMethod.fcitx5.enableRimeData` has been removed. Default RIME data is now included in `fcitx5-rime` by default, and can be customized using diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index 158257f20a7a..aee8b8072799 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -201,6 +201,8 @@ The pre-existing `services.ankisyncd` has been marked deprecated and will be dro - [watchdogd](https://troglobit.com/projects/watchdogd/), a system and process supervisor using watchdog timers. Available as [services.watchdogd](#opt-services.watchdogd.enable). +- [WiVRn](https://github.com/Meumeu/WiVRn), an OpenXR streaming application. Available as [services.wivrn](#opt-services.wivrn.enable). + - [Workout-tracker](https://github.com/jovandeginste/workout-tracker), a workout tracking web application for personal use. - [wyoming-satellite](https://github.com/rhasspy/wyoming-satellite), a voice assistant satellite for Home Assistant using the Wyoming protocol. Available as [services.wyoming.satellite](#opt-services.wyoming.satellite.enable). diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 1ec8c7ff9066..10f3ea319340 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -7,7 +7,8 @@ - **This will be the last release of Nixpkgs to support macOS Sierra 10.12 to macOS Catalina 10.15.** Starting with release 25.05, the minimum supported version will be macOS Big Sur 11, and we cannot guarantee that packages will continue to work on older versions of macOS. Users on old macOS versions should consider upgrading to a supported version (potentially using [OpenCore Legacy Patcher](https://dortania.github.io/OpenCore-Legacy-Patcher/) for old hardware) or installing NixOS. - If neither of those options are viable and you require new versions of software, [MacPorts](https://www.macports.org/) supports back to Mac OS X Snow Leopard 10.6. + If neither of those options are viable and you require new versions of software, [MacPorts](https://www.macports.org/) supports versions back to Mac OS X Snow Leopard 10.6. + - Nix was updated to 2.24, which brings a lot of improvements and fixes. See the release notes for [2.19](https://nix.dev/manual/nix/latest/release-notes/rl-2.19), [2.20](https://nix.dev/manual/nix/latest/release-notes/rl-2.20), @@ -15,100 +16,124 @@ [2.22](https://nix.dev/manual/nix/latest/release-notes/rl-2.22), [2.23](https://nix.dev/manual/nix/latest/release-notes/rl-2.23), [2.24](https://nix.dev/manual/nix/latest/release-notes/rl-2.24). - Notable changes include improvements to Git fetching, documentation comment support in `nix-repl> :doc`, as well as many quality of life improvements. + Notable changes include improvements to Git fetching, documentation comment support in `nix-repl> :doc`, as well as many quality of life additions. + +- This will be the last release of Nixpkgs to support versions of CUDA prior to CUDA 12.0. + These versions only work with old compiler versions that will be unsupported by the time of the Nixpkgs 25.05 release. + In the future, users should expect CUDA versions to be dropped as the compiler versions they require leave upstream support windows. -- Convenience options for `amdgpu`, open source driver for Radeon cards, is now available under `hardware.amdgpu`. +- Convenience options for `amdgpu`, the open source driver for Radeon cards, are now available under [`hardware.amdgpu`](#opt-hardware.amdgpu.initrd.enable). -- [AMDVLK](https://github.com/GPUOpen-Drivers/AMDVLK), AMD's open source Vulkan driver, is now available to be configured as `hardware.amdgpu.amdvlk` option. - This also allows configuring runtime settings of AMDVLK and enabling experimental features. -- The `moonlight-qt` package ([Moonlight game streaming](https://moonlight-stream.org/)) now has HDR support on Linux systems. +- [AMDVLK](https://github.com/GPUOpen-Drivers/AMDVLK), AMD's open source Vulkan driver, is now available to be configured under the [`hardware.amdgpu.amdvlk`](#opt-hardware.amdgpu.amdvlk.enable) option. + This also allows configuring runtime settings for AMDVLK, including enabling experimental features. + +- The `moonlight-qt` package (for [Moonlight game streaming](https://moonlight-stream.org/)) now has HDR support on Linux systems. - PostgreSQL now defaults to major version 16. +- GNOME has been updated to version 47. Refer to the [release notes](https://release.gnome.org/47/) for more details. + - `authelia` has been upgraded to version 4.38. This version brings several features and improvements which are detailed in the [release blog post](https://www.authelia.com/blog/4.38-release-notes/). - This release also deprecates some configuration keys, which are likely to be removed in future version 5.0, but they are still supported and expected to be working in the current version. + This release also deprecates some configuration keys which are likely to be removed in version 5.0.0. - `compressDrv` can compress selected files in a derivation. `compressDrvWeb` compresses files for common web server usage (`.gz` with `zopfli`, `.br` with `brotli`). -- `hardware.display` is a new module implementing workarounds for misbehaving monitors - through setting up custom EDID files and forcing kernel/framebuffer modes. +- [`hardware.display`](#opt-hardware.display.edid.enable) is a new module implementing workarounds for misbehaving monitors + by setting up custom EDID files and forcing kernel/framebuffer modes. -- A new display-manager `services.displayManager.ly` was added. - It is a tui based replacement of sddm and lightdm for window manager users. - Users can use it by `services.displayManager.ly.enable` and config it by - `services.displayManager.ly.settings` to generate `/etc/ly/config.ini` +- [`services.displayManager.ly`](#opt-services.displayManager.ly.enable) is a new module for configuring the display manager [ly](https://github.com/fairyglade/ly), + a TUI-based replacement for SDDM and LightDM meant for window manager users. + +- `srcOnly` was rewritten to be more readable, have additional warnings in the event that something is probably wrong, use the `stdenv` provided by the derivation, and Noogle-compatible documentation was added. - The default sound server for most graphical sessions has been switched from PulseAudio to PipeWire. - Users that want to keep PulseAudio will want to set `services.pipewire.enable = false;` and `hardware.pulseaudio.enable = true;`. + Users that want to keep using PulseAudio will want to set `services.pipewire.enable = false;` and `hardware.pulseaudio.enable = true;`. There is currently no plan to fully deprecate and remove PulseAudio, however, PipeWire should generally be preferred for new installs. - The Rust rewrite of the `switch-to-configuration` program is now used for system activation by default. If you experience any issues, please report them. - The original Perl script can still be used for now by setting `system.switch.enableNg` to `false`. + The original Perl script is deprecated and is planned for removal in the 25.05 release. It will remain accessible until then by setting `system.switch.enableNg` to `false`. - Support for mounting filesystems from block devices protected with [dm-verity](https://docs.kernel.org/admin-guide/device-mapper/verity.html) - was added through the `boot.initrd.systemd.dmVerity` option. + was added through the [`boot.initrd.systemd.dmVerity`](#opt-boot.initrd.systemd.dmVerity.enable) option. -- The [Xen Hypervisor](https://xenproject.org) is once again available as a virtualisation option under [`virtualisation.xen`](#opt-virtualisation.xen.enable). - - This release includes Xen [4.17.5](https://wiki.xenproject.org/wiki/Xen_Project_4.17_Release_Notes), [4.18.3](https://wiki.xenproject.org/wiki/Xen_Project_4.18_Release_Notes) and [4.19.0](https://wiki.xenproject.org/wiki/Xen_Project_4.19_Release_Notes), as well as support for booting the hypervisor on EFI systems. +- The [Xen Project Hypervisor](https://xenproject.org) is once again available as a virtualisation option under [`virtualisation.xen`](#opt-virtualisation.xen.enable). + - This release includes Xen [4.19.0](https://wiki.xenproject.org/wiki/Xen_Project_4.19_Release_Notes) and support for booting the hypervisor on EFI systems. ::: {.warning} - Booting into Xen through a legacy BIOS bootloader or with the legacy script-based Stage 1 initrd have been **deprecated**. Only EFI booting and the new systemd-based Stage 1 initrd are supported. + Booting into the Xen Project Hypervisor through a legacy BIOS bootloader or with the legacy script-based Stage 1 initrd have been **deprecated**. Only EFI booting and the new systemd-based Stage 1 initrd are supported. ::: - - There are two flavours of Xen available by default: `xen`, which includes all built-in components, and `xen-slim`, which replaces the built-in components with their Nixpkgs equivalents. - - The `qemu-xen-traditional` component has been deprecated by upstream Xen, and is no longer available in any of the Xen packages. + - The `qemu-xen-traditional` component has been deprecated by the upstream Xen Project, and is no longer included in the Xen build. - The OCaml-based Xen Store can now be configured using [`virtualisation.xen.store.settings`](#opt-virtualisation.xen.store.settings). - The `virtualisation.xen.bridge` options have been deprecated in this release cycle. Users who need network bridges are encouraged to set up their own networking configurations. +- A new option [`systemd.enableStrictShellChecks`](#opt-systemd.enableStrictShellChecks) has been added. When enabled, all systemd scripts generated by NixOS will + be checked with [shellcheck](https://www.shellcheck.net) and any errors or warnings will cause the build to fail. + This affects all scripts that have been created through the `script`, `reload`, `preStart`, `postStart`, `preStop` and `postStop` options for systemd services. + This does not affect commandlines passed directly to `ExecStart`, `ExecReload`, `ExecStartPre`, `ExecStartPost`, `ExecStop` or `ExecStopPost`. + It therefore also does not affect systemd units that are coming from packages and that are not defined through the NixOS config. + This option is disabled by default, and although some services have already been fixed, it is still likely that you will encounter build failures when enabling this. + We encourage people to enable this option when they are willing and able to submit fixes for potential build failures to nixpkgs. + The option can also be enabled or disabled for individual services using the `enableStrictShellChecks` option on the service itself, which will take precedence over the global setting. + ## New Modules {#sec-release-24.11-new-modules} -- [TaskChampion Sync-Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server), a [Taskwariror 3](https://taskwarrior.org/docs/upgrade-3/) sync server, replacing Taskwarrior 2's sync server named [`taskserver`](https://github.com/GothenburgBitFactory/taskserver). +- [Cyrus IMAP](https://github.com/cyrusimap/cyrus-imapd), an email, contacts and calendar server. Available as [services.cyrus-imap](#opt-services.cyrus-imap.enable) service. + +- [TaskChampion Sync-Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server), a [Taskwarrior 3](https://taskwarrior.org/docs/upgrade-3/) sync server. Available as [services.taskchampion-sync-server](#opt-services.taskchampion-sync-server.enable). -- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable) service. +- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), a proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable). - [Gancio](https://gancio.org/), a shared agenda for local communities. Available as [services.gancio](#opt-services.gancio.enable). -- [Goatcounter](https://www.goatcounter.com/), Easy web analytics. No tracking of personal data. Available as [services.goatcounter](options.html#opt-services.goatcocunter.enable). +- [Goatcounter](https://www.goatcounter.com/), an easy web analytics platform with no tracking of personal data. Available as [services.goatcounter](options.html#opt-services.goatcocunter.enable). -- [UWSM](https://github.com/Vladimir-csp/uwsm), a wayland session manager to wrap Wayland Compositors into useful systemd units such as `graphical-session.target`. Available as [programs.uwsm](#opt-programs.uwsm.enable). +- [Privatebin](https://github.com/PrivateBin/PrivateBin/), a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Available as [services.privatebin](#opt-services.privatebin.enable). -- [Open-WebUI](https://github.com/open-webui/open-webui), a user-friendly WebUI - for LLMs. Available as [services.open-webui](#opt-services.open-webui.enable) - service. +- [UWSM](https://github.com/Vladimir-csp/uwsm), a wayland session manager to wrap Wayland compositors into useful systemd units such as `graphical-session.target`. Available as [programs.uwsm](#opt-programs.uwsm.enable). -- [Quickwit](https://quickwit.io), sub-second search & analytics engine on cloud storage. Available as [services.quickwit](options.html#opt-services.quickwit). +- [Open-WebUI](https://github.com/open-webui/open-webui), a user-friendly WebUI for LLMs. Available as [services.open-webui](#opt-services.open-webui.enable). + +- [Quickwit](https://quickwit.io), a sub-second search & analytics engine on cloud storage. Available as [services.quickwit](options.html#opt-services.quickwit.enable). - [Userborn](https://github.com/nikstur/userborn), a service for declarative user management. This can be used instead of the `update-users-groups.pl` - Perl script and instead of systemd-sysusers. To achieve a system without - Perl, this is the now recommended tool over systemd-sysusers because it can - alos create normal users and change passwords. Available as - [services.userborn](#opt-services.userborn.enable) + Perl script and/or systemd-sysusers. This is now recommended over + systemd-sysusers to achieve a system without Perl, as it can create normal + users and change passwords. Available as [services.userborn](#opt-services.userborn.enable). + +- [Hatsu](https://github.com/importantimport/hatsu), a self-hosted bridge that interacts with Fediverse on behalf of your static site. Available as [services.hatsu](options.html#opt-services.hatsu.enable). + +- [Flood](https://flood.js.org/), a beautiful WebUI for various torrent clients. Available as [services.flood](options.html#opt-services.flood.enable). + +- [Niri](https://github.com/YaLTeR/niri), a scrollable-tiling Wayland compositor. Available as [programs.niri](options.html#opt-programs.niri.enable). -- [Flood](https://flood.js.org/), a beautiful WebUI for various torrent clients. Available as [services.flood](options.html#opt-services.flood). +- [Firefly-iii Data Importer](https://github.com/firefly-iii/data-importer), a data importer for Firefly-III. Available as [services.firefly-iii-data-importer](options.html#opt-services.firefly-iii-data-importer.enable). -- [Firefly-iii Data Importer](https://github.com/firefly-iii/data-importer), a data importer for Firefly-III. Available as [services.firefly-iii-data-importer](options.html#opt-services.firefly-iii-data-importer) +- [Dashy](https://dashy.to), an open source, highly customizable, easy to use, privacy-respecting dashboard app. Available as [services.dashy](options.html#opt-services.dashy). - [QGroundControl], a ground station support and configuration manager for the PX4 and APM Flight Stacks. Available as [programs.qgroundcontrol](options.html#opt-programs.qgroundcontrol.enable). -- [Eintopf](https://eintopf.info), community event and calendar web application. Available as [services.eintopf](options.html#opt-services.eintopf). +- [Eintopf](https://eintopf.info), a community event and calendar web application. Available as [services.eintopf](options.html#opt-services.eintopf.enable). - [Radicle](https://radicle.xyz), an open source, peer-to-peer code collaboration stack built on Git. Available as [services.radicle](#opt-services.radicle.enable). -- [ddns-updater](https://github.com/qdm12/ddns-updater), a service to update DNS records periodically with WebUI for many DNS providers. Available as [services.ddns-updater](#opt-services.ddns-updater.enable). +- [ddns-updater](https://github.com/qdm12/ddns-updater), a service with a WebUI to update DNS records periodically for many providers. Available as [services.ddns-updater](#opt-services.ddns-updater.enable). -- [Immersed VR](https://immersed.com/), a closed-source coworking platform. Available as [programs.immersed-vr](#opt-programs.immersed-vr.enable). +- [Immersed](https://immersed.com/), a closed-source coworking platform. Available as [programs.immersed](#opt-programs.immersed.enable). -- [HomeBox](https://github.com/sysadminsmedia/homebox): the inventory and organization system built for the Home User. Available as [services.homebox](#opt-services.homebox.enable). +- [HomeBox](https://github.com/sysadminsmedia/homebox), an inventory and organization system built for the home user. Available as [services.homebox](#opt-services.homebox.enable). - [matrix-hookshot](https://matrix-org.github.io/matrix-hookshot), a Matrix bot for connecting to external services. Available as [services.matrix-hookshot](#opt-services.matrix-hookshot.enable). -- [Renovate](https://github.com/renovatebot/renovate), a dependency updating tool for various git forges and language ecosystems. Available as [services.renovate](#opt-services.renovate.enable). +- [Renovate](https://github.com/renovatebot/renovate), a dependency updating tool for various Git forges and language ecosystems. Available as [services.renovate](#opt-services.renovate.enable). -- [Music Assistant](https://music-assistant.io/), a music library manager for your offline and online music sources which can easily stream your favourite music to a wide range of supported players. Available as [services.music-assistant](#opt-services.music-assistant.enable). +- [Music Assistant](https://music-assistant.io/), a music library manager for your offline and online music sources that can stream to a wide range of supported players. Available as [services.music-assistant](#opt-services.music-assistant.enable). - [zeronsd](https://github.com/zerotier/zeronsd), a DNS server for ZeroTier users. Available with [services.zeronsd.servedNetworks](#opt-services.zeronsd.servedNetworks). -- [wg-access-server](https://github.com/freifunkMUC/wg-access-server/), an all-in-one WireGuard VPN solution with a web ui for connecting devices. Available at [services.wg-access-server](#opt-services.wg-access-server.enable). +- [Collabora Online](https://www.collaboraonline.com/), a collaborative online office suite based on LibreOffice technology. Available as [services.collabora-online](options.html#opt-services.collabora-online.enable). + +- [wg-access-server](https://github.com/freifunkMUC/wg-access-server/), an all-in-one WireGuard VPN solution with a WebUI for connecting devices. Available as [services.wg-access-server](#opt-services.wg-access-server.enable). - [Pingvin Share](https://github.com/stonith404/pingvin-share), a self-hosted file sharing platform and an alternative for WeTransfer. Available as [services.pingvin-share](#opt-services.pingvin-share.enable). @@ -116,90 +141,156 @@ - [Localsend](https://localsend.org/), an open source cross-platform alternative to AirDrop. Available as [programs.localsend](#opt-programs.localsend.enable). -- [cryptpad](https://cryptpad.org/), a privacy-oriented collaborative platform (docs/drive/etc), has been added back. Available as [services.cryptpad](#opt-services.cryptpad.enable). +- [Gatus](https://github.com/TwiN/gatus), an automated developer-oriented status page. Available as [services.gatus](#opt-services.gatus.enable). + +- [cryptpad](https://cryptpad.org/), a privacy-oriented collaborative office suite, has been added back. Available as [services.cryptpad](#opt-services.cryptpad.enable). -- [realm](https://github.com/zhboner/realm), a simple, high performance relay server written in rust. Available as [services.realm.enable](#opt-services.realm.enable). +- [realm](https://github.com/zhboner/realm), a simple, high performance relay server written in Rust. Available as [services.realm](#opt-services.realm.enable). -- [Gotenberg](https://gotenberg.dev), an API server for converting files to PDFs that can be used alongside Paperless-ngx. Available as [services.gotenberg](options.html#opt-services.gotenberg). +- [Gotenberg](https://gotenberg.dev), an API server for converting files to PDFs that can be used alongside Paperless-ngx. Available as [services.gotenberg](options.html#opt-services.gotenberg.enable). -- [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld). +- [Suricata](https://suricata.io/), a free and open source, mature, fast and robust network threat detection engine. Available as [services.suricata](options.html#opt-services.suricata.enable). -- [MenhirLib](https://gitlab.inria.fr/fpottier/menhir/-/tree/master/coq-menhirlib) A support library for verified Coq parsers produced by Menhir. +- [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld.enable). -- [Glance](https://github.com/glanceapp/glance), a self-hosted dashboard that puts all your feeds in one place. Available as [services.glance](option.html#opt-services.glance). +- [Glance](https://github.com/glanceapp/glance), a self-hosted dashboard that puts all your feeds in one place. Available as [services.glance](option.html#opt-services.glance.enable). -- [Apache Tika](https://github.com/apache/tika), a toolkit that detects and extracts metadata and text from over a thousand different file types. Available as [services.tika](option.html#opt-services.tika). +- [Apache Tika](https://github.com/apache/tika), a toolkit that detects and extracts metadata and text from over a thousand different file types. Available as [services.tika](option.html#opt-services.tika.enable). -- [Misskey](https://misskey-hub.net/en/), an interplanetary microblogging platform. Available as [services.misskey](options.html#opt-services.misskey). +- [Misskey](https://misskey-hub.net/en/), an interplanetary microblogging platform. Available as [services.misskey](options.html#opt-services.misskey.enable). -- [Improved File Manager](https://github.com/misterunknown/ifm), or IFM, a single-file web-based file manager. Available as [services.ifm](options.html#opt-services.ifm.enable) +- [Improved File Manager (IFM)](https://github.com/misterunknown/ifm), a single-file web-based file manager. Available as [services.ifm](options.html#opt-services.ifm.enable). - [OpenGFW](https://github.com/apernet/OpenGFW), an implementation of the Great Firewall on Linux. Available as [services.opengfw](#opt-services.opengfw.enable). - [Rathole](https://github.com/rapiz1/rathole), a lightweight and high-performance reverse proxy for NAT traversal. Available as [services.rathole](#opt-services.rathole.enable). -- [Proton Mail bridge](https://proton.me/mail/bridge), a desktop application that runs in the background, encrypting and decrypting messages as they enter and leave your computer. It lets you add your Proton Mail account to your favorite email client via IMAP/SMTP by creating a local email server on your computer. +- [Proton Mail bridge](https://proton.me/mail/bridge), a desktop application that runs in the background, encrypting and decrypting messages as they enter and leave your computer. Available as [services.protonmail-bridge](#opt-services.protonmail-bridge.enable). + +- [chromadb](https://www.trychroma.com/), an open-source AI application database with batteries included. Available as [services.chromadb](options.html#opt-services.chromadb.enable). -- [chromadb](https://www.trychroma.com/), an open-source AI application - database. Batteries included. Available as [services.chromadb](options.html#opt-services.chromadb.enable). +- [bitmagnet](https://bitmagnet.io/), a self-hosted BitTorrent indexer, DHT crawler, content classifier and torrent search engine with WebUI, GraphQL API and Servarr stack integration. Available as [services.bitmagnet](options.html#opt-services.bitmagnet.enable). - [Wakapi](https://wakapi.dev/), a time tracking software for programmers. Available as [services.wakapi](#opt-services.wakapi.enable). - [foot](https://codeberg.org/dnkl/foot), a fast, lightweight and minimalistic Wayland terminal emulator. Available as [programs.foot](#opt-programs.foot.enable). -- [ToDesk](https://www.todesk.com/linux.html), a remote desktop applicaton. Available as [services.todesk.enable](#opt-services.todesk.enable). +- [ToDesk](https://www.todesk.com/linux.html), a remote desktop application. Available as [services.todesk](#opt-services.todesk.enable). -- [Dependency Track](https://dependencytrack.org/), an intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain. Available as [services.dependency-track](option.html#opt-services.dependency-track). +- [Dependency Track](https://dependencytrack.org/), an intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain. Available as [services.dependency-track](option.html#opt-services.dependency-track.enable). + +- [Immich](https://github.com/immich-app/immich), a self-hosted photo and video backup solution. Available as [services.immich](#opt-services.immich.enable). + +- [saunafs](https://saunafs.com), a distributed POSIX file system. Available as [services.saunafs](options.html#opt-services.saunafs.enable). + +- [obs-studio](https://obsproject.com/), a free and open source software for video recording and live streaming. Available as [programs.obs-studio](#opt-programs.obs-studio.enable). + +- [Veilid](https://veilid.com), a privacy-focused, headless server for data sharing and messaging on a peer-to-peer network. Available as [services.veilid](#opt-services.veilid.enable). + +- [Fedimint](https://github.com/fedimint/fedimint), a module based system for building federated applications (Federated E-Cash Mint). Available as [services.fedimintd](#opt-services.fedimintd). + +- [tiny-dfr](https://github.com/WhatAmISupposedToPutHere/tiny-dfr), a dynamic function row daemon for the Touch Bar found on some Apple laptops. Available as [hardware.apple.touchBar.enable](options.html#opt-hardware.apple.touchBar.enable). + +- [Swapspace](https://github.com/Tookmund/Swapspace), a dynamic swap space manager that turns your unused free space into swap automatically. Available as [services.swapspace](#opt-services.swapspace.enable). + +- [Zapret](https://github.com/bol-van/zapret), a DPI bypass tool. Available as [services.zapret](option.html#opt-services.zapret.enable). ## Backward Incompatibilities {#sec-release-24.11-incompatibilities} -- `transmission` package has been aliased with a `trace` warning to `transmission_3`. Since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0), and Transmission 3 will eventually go away, it was decided perform this warning alias to make people aware of the new version. The `services.transmission.package` defaults to `transmission_3` as well because the upgrade can cause data loss in certain specific usage patterns (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory per your usage: +- Nixpkgs now requires Nix 2.3.17 or newer to allow for zstd compressed binary artifacts. + +- The `sound` options have been removed or renamed, as they had a lot of unintended side effects. See [below](#sec-release-24.11-migration-sound) for details. + +- The NVIDIA driver no longer defaults to the proprietary kernel module with versions >= 560. You will need to manually set `hardware.nvidia.open` to select the proprietary or open modules. + +- The `(buildPythonPackage { ... }).override` attribute is now deprecated and removed in favour of `overridePythonAttrs`. + This change does not affect the override interface of most Python packages, as [`<pkg>.override`](https://nixos.org/manual/nixpkgs/unstable/#sec-pkg-override) provided by `callPackage` shadows such a locally-defined `override` attribute. + +- All Cinnamon and XApp packages have been moved to top-level (i.e., `cinnamon.nemo` is now `nemo`). + +- All GNOME packages have been moved to top-level (i.e., `gnome.nautilus` is now `nautilus`). + +- `transmission` has been aliased with a `trace` warning to `transmission_3`, since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0) and Transmission 3 will eventually go away -- this is meant to make people aware of the new version. `services.transmission.package` now also defaults to `transmission_3`, as the upgrade can cause data loss in some cases (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory if you may be affected: - `transmission-gtk`: `~/.config/transmission` - `transmission-daemon` using NixOS module: `${config.services.transmission.home}/.config/transmission-daemon` (defaults to `/var/lib/transmission/.config/transmission-daemon`) -- `androidenv.androidPkgs_9_0` has been removed, and replaced with `androidenv.androidPkgs` for a more complete Android SDK including support for Android 9 and later. +- The default `mongodb` version has been updated from 5.0 to 7.0. + For more information, see the compatibility changes for MongoDB [6.0](https://www.mongodb.com/docs/manual/release-notes/6.0-compatibility/) and [7.0](https://www.mongodb.com/docs/manual/release-notes/7.0-compatibility/). + +- `unifi` has been updated to UniFi 8. + `unifi7` was removed as it is vulnerable to CVE-2024-42025 and required a version of MongoDB that has reached end of life. + +- `androidenv.androidPkgs_9_0` has been removed. It is replaced with `androidenv.androidPkgs` for a more complete Android SDK, including support for Android 9 and later. + +- The VirtualBox demo installer appliance has been removed. + Please use the standard installer ISOs instead. - `grafana` has been updated to version 11.1. This version doesn't support setting `http_addr` to a hostname anymore, an IP address is expected. +- `deno` has been updated to Deno 2, which has breaking changes. + See the [migration guide](https://docs.deno.com/runtime/reference/migration_guide/) for details. + +- `gogs` has been removed. Upstream development has stalled and it has several + [critical vulnerabilities](https://github.com/gogs/gogs/issues/7777) that weren't addressed + within a year. Consider migrating to `forgejo` or `gitea`. + - `knot-dns` has been updated to version 3.4.x. Check the [migration guide](https://www.knot-dns.cz/docs/latest/html/migration.html#upgrade-3-3-x-to-3-4-x) for breaking changes. +- `mutmut` has been updated to version 3.0.5. + - `services.kubernetes.kubelet.clusterDns` now accepts a list of DNS resolvers rather than a single string, bringing the module more in line with the upstream Kubelet configuration schema. +- `bluemap` has changed the format used to store map tiles, and the database layout has been heavily modified. Upstream recommends a clean reinstallation: <https://github.com/BlueMap-Minecraft/BlueMap/releases/tag/v5.2>. Unless you are using an SQL storage backend, this should only entail deleting the contents of `config.services.bluemap.coreSettings.data` (defaults to `/var/lib/bluemap`) and `config.services.bluemap.webRoot` (defaults to `/var/lib/bluemap/web`). + - `wstunnel` has had a major version upgrade that entailed rewriting the program in Rust. - The module was updated to accommodate for breaking changes. - Breaking changes to the module API were minimised as much as possible, - but some were nonetheless inevitable due to changes in the upstream CLI. - Certain options were moved from separate CLI arguments into the forward specifications, - and those options were also removed from the module's API, - please consult the wstunnel man page for more detail. + The module was updated to accommodate for breaking changes and breaking changes to the + module options were minimised as much as possible. Nonetheless, some were inevitable due + to changes in the upstream CLI. Certain options were moved from separate CLI arguments into + the forward specifications, and those options were also removed from the module's options. + Please consult the wstunnel man page for more details. Also be aware that if you have set additional options in `services.wstunnel.{clients,servers}.<name>.extraArgs`, - that those might have been removed or modified upstream. + they may have been modified or removed upstream. + +- `gnat` and `gnatPackages` now use GNAT 13 instead of GNAT 12. This matches + the default `gcc` version. + +- `percona-server_8_4` and `mysql84` now have password authentication via the deprecated `mysql_native_password` disabled by default. This authentication plugin can be enabled via a CLI argument again, for detailed instructions and alternative authentication methods [see upstream documentation](https://dev.mysql.com/doc/refman/8.4/en/native-pluggable-authentication.html). The config file directive `default_authentication_plugin` has been removed. + +- Percona has decided not to follow the LTS/ Innovation release scheme of upstream MySQL and thus [will only create releases for MySQL LTS versions](https://www.percona.com/blog/no-mysql-9-x-innovation-releases-from-percona/). Hence, the package names `percona-server_lts`, `percona-server_innovation`, `percona-xtrabackup_lts` and `percona-xtrabackup_innovation` are deprecated. + - `percona-server` and `percona-server_lts` now point towards the new LTS release `percona-server_8_4`. The previous LTS continues to be supported and is available as `percona-server_8_0`. The same is true for the supporting `percona-xtrabackup` tooling. - `clang-tools_<version>` packages have been moved into `llvmPackages_<version>` (i.e. `clang-tools_18` is now `llvmPackages_18.clang-tools`). - For convenience, the top-level `clang-tools` attribute remains and is now bound to `llvmPackages.clang-tools`. - Top-level `clang_tools_<version>` attributes are now aliases; these will be removed in a future release. -- `buildbot` was updated to 4.0, the AngularJS frontend has been replaced by a React frontend, see the [upstream release notes](https://docs.buildbot.net/current/manual/upgrading/4.0-upgrade.html). +- `buildbot` was updated to 4.0 and the AngularJS frontend replaced by a React frontend. See the [upstream release notes](https://docs.buildbot.net/current/manual/upgrading/4.0-upgrade.html). + +- `headscale` has been updated to version 0.23.0 which reworked large parts of the configuration, including DNS, Magic DNS prefixes and ACL policy files. See the [upstream changelog](https://github.com/juanfont/headscale/releases/tag/v0.23.0) for details. -- `nginx` package no longer includes `gd` and `geoip` dependencies. For enabling it, override `nginx` package with the optionals `withImageFilter` and `withGeoIP`. +- `nginx` package no longer includes the `gd` and `geoip` dependencies. To re-enable them, override `nginx` with the options `withImageFilter = true;` and `withGeoIP = true;`. -- `systemd.enableUnifiedCgroupHierarchy` option has been removed. - In systemd 256 support for cgroup v1 ('legacy' and 'hybrid' hierarchies) is now considered obsolete and systemd by default will refuse to boot under it. - To forcibly reenable cgroup v1 support, you can `set boot.kernelParams = [ "systemd.unified_cgroup_hierachy=0" "SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1" ]`. - NixOS does not officially support this configuration and might cause your system to be unbootable in future versions. You are on your own. +- `systemd.enableUnifiedCgroupHierarchy` has been removed. + In systemd 256, support for cgroup v1 ('legacy' and 'hybrid' hierarchies) is now considered obsolete and systemd will refuse to boot under it by default. + To forcibly re-enable cgroup v1 support, you can set `boot.kernelParams = [ "systemd.unified_cgroup_hierarchy=0" "SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1" ]`. + This is not an officially supported configuration and might cause your system to become unbootable in future versions. You are on your own. -- `nrfutil` which previously pointed to the now-deprecated `pc-nrfutil` python package, has been repackaged under the same name with the new nrfutil tool. +- `nrfutil` -- which previously pointed to the now-deprecated `pc-nrfutil` Python package -- has been repackaged under the same name with the new nrfutil tool. -- `openssh` and `openssh_hpn` are now compiled without Kerberos 5 / GSSAPI support in an effort to reduce the attack surface of the components for the majority of users. Users needing this support can - use the new `opensshWithKerberos` and `openssh_hpnWithKerberos` flavors (e.g. `programs.ssh.package = pkgs.openssh_gssapi`). +- `openssh` and `openssh_hpn` are now compiled without Kerberos 5 / GSSAPI support in an effort to reduce the attack surface of the components. Users needing this support can + use the new `opensshWithKerberos` and `openssh_hpnWithKerberos` package flavors (e.g. `programs.ssh.package = pkgs.openssh_gssapi`). - `security.ipa.ipaHostname` now defaults to the value of `networking.fqdn` if it is set, instead of the previous hardcoded default of `${networking.hostName}.${security.ipa.domain}`. -- The `MSMTP_QUEUE` and `MSMTP_LOG` environment variables accepted by `msmtpq` have now been renamed to `MSMTPQ_Q` and `MSMTPQ_LOG` respectively. +- The `MSMTP_QUEUE` and `MSMTP_LOG` environment variables accepted by `msmtpq` have been renamed to `MSMTPQ_Q` and `MSMTPQ_LOG` respectively. -- The logrotate service has received hardening and now requires enabling `allowNetworking`, if logrotate needs to access the network. +- The logrotate service has been hardened and now requires enabling `allowNetworking` if network access is required. + +- `mautrix-whatsapp` has been updated to version 0.11.0, which is a major rewrite of the bridge. Config file changes are required. + +- qBittorrent has been updated to major version 5, which drops support for Qt 5. + The `qbittorrent-qt5` package has been removed. - The fcgiwrap module now allows multiple instances running as distinct users. The option `services.fgciwrap` now takes an attribute set of the @@ -211,67 +302,107 @@ Processes also now run as a dynamically allocated user by default instead of root. -- The `mautrix-signal` module was adapted to incorporate the configuration rearrangement that resulted from the update to the mautrix bridgev2 architecture. Pre-0.7.0 configurations should continue to work. - In case you want to update your configuration make sure to check the NixOS manual. +- The `mautrix-signal` module was adapted to incorporate the configuration changes that resulted from the update to the mautrix bridgev2 architecture. Pre-0.7.0 configurations should continue to work. + In case you want to update your configuration, make sure to check the NixOS manual. - The nvidia driver no longer defaults to the proprietary driver starting with version 560. You will need to manually set `hardware.nvidia.open` to select the proprietary or open driver. +- `postgresql` no longer accepts the `enableSystemd` override. Use `systemdSupport` instead. + +- The dhcpcd service (`networking.useDHCP`) has been hardened and now runs exclusively as the "dhcpcd" user. + Users that were relying on the root privileges in `networking.dhcpcd.runHook` will have to write specific [sudo](security.sudo.extraRules) or [polkit](security.polkit.extraConfig) rules to allow dhcpcd to perform privileged actions. + + As part of these changes, the DHCP lease files directory has also been moved from `/var/db/dhcpcd` to `/var/lib/dhcpcd`. This migration is performed automatically, but users may have to update their backup configuration. + - `singularity-tools` have the `storeDir` argument removed from its override interface and use `builtins.storeDir` instead. -- Two build helpers in `singularity-tools`, i.e., `mkLayer` and `shellScript`, are deprecated, as they are no longer involved in image-building. Maintainers will remove them in future releases. +- The `mkLayer` and `shellScript` build helpers in `singularity-tools` are deprecated, as they are no longer involved in image-building. Maintainers will remove them in future releases. - The `rust.toTargetArch`, `rust.toTargetOs`, `rust.toTargetFamily`, `rust.toTargetVendor`, `rust.toRustTarget`, `rust.toRustTargetSpec`, `rust.toRustTargetSpecShort`, and `rust.IsNoStdTarget` functions are deprecated in favour of the `rust.platform.arch`, `rust.platform.os`, `rust.platform.target-family`, `rust.platform.vendor`, `rust.rustcTarget`, `rust.rustcTargetSpec`, `rust.cargoShortTarget`, `rust.cargoEnvVarTarget`, and `rust.isNoStdTarget` platform attributes respectively. -- The `budgie` and `budgiePlugins` scope have been removed and their packages - moved into the top level scope (i.e., `budgie.budgie-desktop` is now - `budgie-desktop`) +- All Budgie and `budgiePlugins` packages have been moved to top-level (i.e., + `budgie.budgie-desktop` is now `budgie-desktop` and `budgiePlugins.budgie-media-player-applet` + is now `budgie-media-player-applet`). -- The method to safely handle secrets in the `networking.wireless` module has been changed to benefit from a [new feature](https://w1.fi/cgit/hostap/commit/?id=e680a51e94a33591f61edb210926bcb71217a21a) of wpa_supplicant. +- The method of safely handling secrets in the `networking.wireless` module has been changed to benefit from a [new feature](https://w1.fi/cgit/hostap/commit/?id=e680a51e94a33591f61edb210926bcb71217a21a) of `wpa_supplicant`. The syntax to refer to secrets has changed slightly and the option `networking.wireless.environmentFile` has been replaced by `networking.wireless.secretsFile`; see the description of the latter for how to upgrade. -- All Cinnamon and XApp packages have been moved to top-level (i.e., `cinnamon.nemo` is now `nemo`). - -- All GNOME packages have been moved to top-level (i.e., `gnome.nautilus` is now `nautilus`). +- NetBox was updated to `>= 4.1.0`. + Have a look at the breaking changes + of the [4.0 release](https://github.com/netbox-community/netbox/releases/tag/v4.0.0) + and the [4.1 release](https://github.com/netbox-community/netbox/releases/tag/v4.1.0), + make the required changes to your database, if needed, + then upgrade by setting `services.netbox.package = pkgs.netbox_4_1;` + in your configuration. - `services.cgit` now runs as the cgit user by default instead of root. This change requires granting access to the repositories to this user or setting the appropriate one through `services.cgit.some-instance.user`. +- All Oracle JDKs and JREs (`oraclejdk`, `oraclejdk8`, `oraclejre`, `oraclejre8`, + `jrePlugin`, `jre8Plugin`, `jdkdistro`, `oraclejdk8distro`, and `oraclejdk11`) + were dropped due to being unmaintained and heavily insecure. OpenJDK provides + compatible replacements for JDKs and JREs. + +- `gradle_6` was removed due to being [unsupported upstream as of 10 Feb 2023](https://endoflife.date/gradle). + Additionally, it had numerous security vulnerabilities that were only patched + in later versions, such as [CVE-2021-29429](https://nvd.nist.gov/vuln/detail/CVE-2021-32751), + [CVE-2021-29427](https://nvd.nist.gov/vuln/detail/CVE-2021-29427), [CVE-2021-29428](https://nvd.nist.gov/vuln/detail/CVE-2021-29428), and [CVE-2021-32751](https://nvd.nist.gov/vuln/detail/CVE-2021-32751). + - `nvimpager` was updated to version 0.13.0, which changes the order of user and nvimpager settings: user commands in `-c` and `--cmd` now override the respective default settings because they are executed later. +- `javacard-devkit` was dropped due to having a dependency on the Oracle JDK, + as well as being several years out-of-date. + - Kubernetes `featureGates` have changed from a `listOf str` to `attrsOf bool`. This refactor makes it possible to also disable feature gates, without having to use `extraOpts` flags. A previous configuration may have looked like this: + ```nix - featureGates = [ "EphemeralContainers" ]; - extraOpts = pkgs.lib.concatStringsSep " " ( - [ - ''--feature-gates="CSIMigration=false"'' - }); + { + featureGates = [ "EphemeralContainers" ]; + extraOpts = pkgs.lib.concatStringsSep " " ( + [ + ''--feature-gates="CSIMigration=false"'' + ] + ); + } ``` - Using an AttrSet instead, the new configuration would be: + Using an attribute set instead, the new configuration would be: + ```nix - featureGates = {EphemeralContainers = true; CSIMigration=false;}; + { + featureGates = { + EphemeralContainers = true; + CSIMigration=false; + }; + } ``` -- `pkgs.nextcloud27` has been removed since it's EOL. +- `pkgs.nextcloud27` has been removed as it has reached EOL. - The `environment.noXlibs` option has been removed. It was a common source of unexpected rebuilds and breakage that was often hard to diagnose. If you need to disable certain libraries, you're encouraged to add your own overlay to your configuration that targets the packages you care about. -- `frigate` was updated past 0.14.0. This release includes various breaking changes, so please go read the [release notes](https://github.com/blakeblackshear/frigate/releases/tag/v0.14.0). - Most prominently access to the webinterface and API are now protected by authentication. Retrieve the auto-created +- `frigate` was updated past 0.14.0. This release includes various breaking changes, so please review the [release notes](https://github.com/blakeblackshear/frigate/releases/tag/v0.14.0). + Most prominently, access to the web interface and API are now protected by authentication. Retrieve the auto-created admin account from the `frigate.service` journal after upgrading. +- `nodePackages.coc-python` was dropped, as [its upstream is unmaintained](https://github.com/neoclide/coc-python). The associated `vimPlugins.coc-python` was also dropped. + The upstream project recommends using `coc-pyright` or `coc-jedi` as replacements. + +- `forgejo` has been upgraded from version 7.0 to version 9.0, see the release notes for [8.0](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#8-0-0) and [9.0](https://codeberg.org/forgejo/forgejo/milestone/7235). + - `services.forgejo.mailerPasswordFile` has been deprecated by the drop-in replacement `services.forgejo.secrets.mailer.PASSWD`, which is part of the new free-form `services.forgejo.secrets` option. `services.forgejo.secrets` is a small wrapper over systemd's `LoadCredential=`. It has the same structure (sections/keys) as `services.forgejo.settings` but takes file paths that will be read before service startup instead of some plaintext value. + `services.forgejo.package` now defaults to `forgejo-lts`, the Long Term Support version of Forgejo. - `forgejo` and `forgejo-lts` no longer support the opt-in feature [PAM (Pluggable Authentication Module)](https://forgejo.org/docs/latest/user/authentication/#pam-pluggable-authentication-module). @@ -279,37 +410,69 @@ - `services.ddclient.use` has been deprecated: `ddclient` now supports separate IPv4 and IPv6 configuration. Use `services.ddclient.usev4` and `services.ddclient.usev6` instead. -- `services.pgbouncer` systemd service is configured with `Type=notify-reload` and allows reloading configuration without process restart. PgBouncer configuration options were moved to the free-form type option named [`services.pgbouncer.settings`](#opt-services.pgbouncer.settings) according to the NixOS RFC 0042. +- `services.pgbouncer` systemd service is now configured with `Type=notify-reload` and allows reloading configuration without process restart. PgBouncer configuration options were moved to the freeform type option under [`services.pgbouncer.settings`](#opt-services.pgbouncer.settings). + +- Docear was removed because it was unmaintained upstream. + JabRef, Zotero, or Mendeley are potential replacements. + +- `nodePackages.coc-metals` was removed due to being deprecated upstream. + `vimPlugins.nvim-metals` is its official replacement. + +- `matrix-sliding-sync` was removed because it has been replaced by the simplified sliding sync functionality introduced in matrix-synapse 114.0. + +- `nodePackages.coc-tslint`, `vimPlugins.coc-tslint`, `nodePackages.coc-tslint-plugin`, + and `vimPlugins.coc-tslint-plugin` were removed due to being deprecated upstream. The + `nodePackages.coc-eslint` and `vimPlugins.coc-eslint` packages offer comparable + features for `eslint`, which replaced `tslint`. + +- Tcl packages have been moved into the `tclPackages` scope. - `teleport` has been upgraded from major version 15 to major version 16. Refer to upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/) and [release notes for v16](https://goteleport.com/docs/changelog/#1600-061324). -- `tests.overriding` has its `passthru.tests` restructured as an attribute set instead of a list, making individual tests accessible by their names. +- `tests.overriding`'s `passthru.tests` has been restructured as an attribute set instead of a list, making individual tests accessible by their names. + +- `skk-dict` was split into multiple packages under `skkDictionaries`. + If in doubt of what to use, try `skkDictionaries.l`. As part of this change, the dictionaries + were moved from `$out/share` to `$out/share/skk`. The dictionaries also won't + be converted to UTF-8 unless the `useUtf8` package option is enabled; UTF-8 + converted dictionaries will have the .utf8 suffix appended to its filename. - `vaultwarden` lost the capability to bind to privileged ports. If you rely on this behavior, override the systemd unit to allow `CAP_NET_BIND_SERVICE` in - your local configuration. + your configuration. -- The Invoiceplane module now only accepts the structured `settings` option. - `extraConfig` is now removed. +- `services.invoiceplane.sites.<name>.extraConfig` was removed. Configuration must now be done + through the structured `services.invoiceplane.sites.<name>.settings` option. -- The `ollama` services replaces its `sandbox` toggle with options to configure - a static `user` and `group`. The `writablePaths` option has been removed and +- `services.ollama.sandbox` has been replaced with options to configure + a static `user` and `group`. The `writablePaths` option has also been removed and the models directory is now always exempt from sandboxing. +- The `gns3-server` service now runs under the `gns3` system user + instead of a dynamically created one via `DynamicUser`. + The use of SUID wrappers is incompatible with SystemD's `DynamicUser` setting, + and GNS3 requires calling ubridge through its SUID wrapper to function properly. + This change requires to manually move the following directories: + * from `/var/lib/private/gns3` to `/var/lib/gns3` + * from `/var/log/private/gns3` to `/var/log/gns3` + and to change the ownership of these directories and their contents to `gns3` (including `/etc/gns3`). + - Legacy package `stalwart-mail_0_6` was dropped, please note the [manual upgrade process](https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md) before changing the package to `pkgs.stalwart-mail` in [`services.stalwart-mail.package`](#opt-services.stalwart-mail.package). -- The `nomad_1_5` package was dropped, as [it has reached end-of-life upstream](https://support.hashicorp.com/hc/en-us/articles/360021185113-Support-Period-and-End-of-Life-EOL-Policy). Evaluating it will throw an error. +- `nomad_1_5` and `nomad_1_6` were dropped, as [they have reached end-of-life upstream](https://support.hashicorp.com/hc/en-us/articles/360021185113-Support-Period-and-End-of-Life-EOL-Policy). Evaluating them will throw an error. + +- The default `nomad` package has been updated to 1.8.x. For more information, see [breaking changes for Nomad 1.8](https://developer.hashicorp.com/nomad/docs/upgrade/upgrade-specific#nomad-1-8-0) - `androidndkPkgs` has been updated to `androidndkPkgs_26`. - Android NDK version 26 and SDK version 33 are now the default versions used for cross compilation to android. -- the `ankisyncd` package and its `services.ankisyncd` have been removed, use [`services.anki-sync-server`](#opt-services.anki-sync-server.enable) instead. +- `ankisyncd` package and its `services.ankisyncd` have been removed. Use [`services.anki-sync-server`](#opt-services.anki-sync-server.enable) instead. - `nodePackages.vscode-css-languageserver-bin`, `nodePackages.vscode-html-languageserver-bin`, and `nodePackages.vscode-json-languageserver-bin` were dropped due to an unmaintained upstream. @@ -318,35 +481,35 @@ - `nodePackages.prisma` has been replaced by `prisma`. - `fetchNextcloudApp` has been rewritten to use `fetchurl` rather than - `fetchzip`. This invalidates all existing hashes but you can restore the old + `fetchzip`. This invalidates all existing hashes, but you can restore the old behavior by passing it `unpack = true`. -- `haskell.lib.compose.justStaticExecutables` now disallows references to GHC in the - output by default, to alert users to closure size issues caused by +- `haskell.lib.compose.justStaticExecutables` now disallows references to GHC in its + output by default to alert users to closure size issues caused by [#164630](https://github.com/NixOS/nixpkgs/issues/164630). See ["Packaging Helpers" in the Haskell section of the Nixpkgs manual](https://nixos.org/manual/nixpkgs/unstable/#haskell-packaging-helpers) for information on working around `output '...' is not allowed to refer to the following paths` errors caused by this change. -- The `stalwart-mail` service now runs under the `stalwart-mail` system user - instead of a dynamically created one via `DynamicUser`, to avoid automatic - ownership changes on its large file store each time the service was started. +- `services.stalwart-mail` now runs under the `stalwart-mail` system user + instead of a dynamic one via `DynamicUser` in order to avoid automatic + ownership changes on its large file store on service restart. This change requires to manually move the state directory from - `/var/lib/private/stalwart-mail` to `/var/lib/stalwart-mail` and to + `/var/lib/private/stalwart-mail` to `/var/lib/stalwart-mail`, and to change the ownership of the directory and its content to `stalwart-mail`. -- The `stalwart-mail` module now uses RocksDB as the default storage backend - for `stateVersion` ≥ 24.11. (It was previously using SQLite for structured - data and the filesystem for blobs). +- `services.stalwart-mail` now uses RocksDB as the default storage backend + for `stateVersion` ≥ 24.11. It was previously using SQLite for structured + data and the filesystem for blobs. -- The `stargazer` service has been hardened to improve security, but these +- `services.stargazer` has been hardened to improve security, but these changes make break certain setups, particularly around traditional CGI. - - The `stargazer.allowCgiUser` option has been added, enabling + - `services.stargazer.allowCgiUser` has been added, enabling Stargazer's `cgi-user` option to work, which was previously broken. -- The `shiori` service now requires an HTTP secret value `SHIORI_HTTP_SECRET_KEY` to be provided via environment variable. The nixos module therefore, now provides an environmentFile option: +- `services.shiori` now requires the HTTP secret value `SHIORI_HTTP_SECRET_KEY` to be provided as an environment variable. `services.shiori.environmentFile` has been introduced to handle this: ``` # This is how a environment file can be generated: @@ -356,29 +519,32 @@ - `/share/nano` is now only linked when `programs.nano.enable` is enabled. -- PPD files for Utax printers got renamed (spaces replaced by underscores) in newest `foomatic-db` package; users of Utax printers might need to adapt their `hardware.printers.ensurePrinters.*.model` value. +- PPD files for Utax printers were renamed (spaces replaced by underscores) in the newest `foomatic-db` package. Users of Utax printers might need to adapt their `hardware.printers.ensurePrinters.*.model` value to account for this. -- The `kvdo` kernel module package was removed, because it was upstreamed in kernel version 6.9, where it is called `dm-vdo`. +- `sqldeveloper` was dropped due to being severely out-of-date and having a dependency on + JavaFX for Java 8, which we do not support. + +- The `kvdo` kernel module package was removed as it was upstreamed in kernel version 6.9, where it is now called `dm-vdo`. - `libe57format` has been updated to `>= 3.0.0`, which contains some backward-incompatible API changes. See the [release note](https://github.com/asmaloney/libE57Format/releases/tag/v3.0.0) for more details. - `gitlab` deprecated support for *runner registration tokens* in GitLab 16.0, disabled their support in GitLab 17.0 and will - ultimately remove it in GitLab 18.0, as outlined in the - [documentation](https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#estimated-time-frame-for-planned-changes). + ultimately remove it in GitLab 18.0 (as outlined in the + [documentation](https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#estimated-time-frame-for-planned-changes)). After upgrading to GitLab >= 17.0, it is possible to re-enable support for registration tokens in the UI until GitLab 18.0. Refer to the manual on [using registration tokens after GitLab 17.0](https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#using-registration-tokens-after-gitlab-170). GitLab administrators should migrate to the [new runner registration workflow](https://docs.gitlab.com/17.0/ee/ci/runners/new_creation_workflow.html#using-registration-tokens-after-gitlab-170) with *runner authentication tokens* until the release of GitLab 18.0. -- `gitlab` has been updated from 16.x to 17.x and requires at least `postgresql` 14.9, as stated in the [documentation](https://docs.gitlab.com/17.1/ee/install/requirements.html#postgresql-requirements). Check the [upgrade guide](#module-services-postgres-upgrading) in the NixOS manual on how to upgrade your PostgreSQL installation. +- `gitlab` has been updated from 16.x to 17.x and requires `postgresql` >= 14.9, as stated in the [documentation](https://docs.gitlab.com/17.1/ee/install/requirements.html#postgresql-requirements). Check the [upgrade guide](#module-services-postgres-upgrading) in the NixOS manual on how to upgrade your PostgreSQL installation. -- `gitaly` (part of `gitlab`) is now using the bundled `git` package instead of `pkgs.git` to maintain compatibility with GitLab. +- `gitaly` (part of `gitlab`) is now using the bundled `git` package instead of `pkgs.git`, to maintain compatibility with GitLab. - `nixos/gitlab` no longer adds `pkgs.git` to `environment.systemPackages` by default. - The `replay-sorcery` package and module was removed as it unmaintained upstream. Consider using `gpu-screen-recorder` or `obs-studio` instead. -- To follow [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) a few options of `samba` have been moved from `extraConfig` and `configText` to the new freeform option `settings` and renamed, e.g.: +- A few options of `services.samba` have been moved from `extraConfig` and `configText` to the new freeform option `settings` and renamed, e.g.: - `services.samba.invalidUsers` to `services.samba.settings.global."invalid users"` - `services.samba.securityType` to `services.samba.settings.global."security type"` - `services.samba.shares` to `services.samba.settings` @@ -388,8 +554,10 @@ - `zx` was updated to v8, which introduces several breaking changes. See the [v8 changelog](https://github.com/google/zx/releases/tag/8.0.0) for more information. -- The `dnscrypt-wrapper` module was removed since the project has been effectively unmaintained since 2018; moreover the NixOS module had to rely on an abandoned version of dnscrypt-proxy v1 for the rotation of keys. - To wrap a resolver with DNSCrypt you can instead use `dnsdist`. See options `services.dnsdist.dnscrypt.*` +- `feishin` removed support for Navidrome `< v0.53.2` due to an API change. See the [v0.10.0 release notes](https://github.com/jeffvli/feishin/releases/tag/v0.10.0) for more information. + +- `services.dnscrypt-wrapper` was removed, as the project has been effectively unmaintained since 2018. Moreover, the NixOS module had to rely on an abandoned version of `dnscrypt-proxy` v1 for the rotation of keys. + To wrap a resolver with DNSCrypt, you can instead use `dnsdist`. See `services.dnsdist.dnscrypt` - The `portunus` package and service do not support weak password hashes anymore. If you installed Portunus on NixOS 23.11 or earlier, upgrade to NixOS 24.05 first to get support for strong password hashing. @@ -404,7 +572,7 @@ Explicitly set `kubelet.hostname` to `networking.fqdnOrHostName` to get back the old default behavior. -- Docker now defaults to 27.x, because version 24.x stopped receiving security updates and bug fixes after [February 1, 2024](https://github.com/moby/moby/pull/46772#discussion_r1686464084). +- Docker now defaults to 27.x, as version 24.x stopped receiving security updates and bug fixes after [February 1, 2024](https://github.com/moby/moby/pull/46772#discussion_r1686464084). - `postgresql` was split into default and -dev outputs. To make this work without circular dependencies, the output of the `pg_config` system view has been removed. The `pg_config` binary is provided in the -dev output and still works as expected. @@ -413,78 +581,142 @@ - `programs.vim.defaultEditor` now only works if `programs.vim.enable` is enabled. +- `services.mautrix-meta` was updated to [0.4](https://github.com/mautrix/meta/releases/tag/v0.4.0). This release makes significant changes to the settings format. If you have custom settings you should migrate them to the new format. Unfortunately upstream provides little guidance for how to do this, but [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) may serve as a useful reference. The NixOS module should warn you if you still have any old settings configured. + +- The `nodePackages.shout` package has been removed because it was deprecated upstream in favor of `thelounge`. + The `shout` top-level attribute was an alias to this package. + The associated `services.shout` module has also been removed. + +- `prometheus-openldap-exporter` was removed, as it was unmaintained both upstream and in nixpkgs. + - The `indi-full` package no longer contains non-free drivers. To get the old collection of drivers use `indi-full-nonfree` or create your own collection of drivers by overriding indi-with-drivers. E.g.: `pkgs.indi-with-drivers.override {extraDrivers = with pkgs.indi-3rdparty; [indi-gphoto];}` - `/share/vim-plugins` now only gets linked if `programs.vim.enable` is enabled +- The `services.guix` module now manages trusted substitute servers + declaratively. Instead of `guix archive --authorize`, list keys with + `services.guix.substituters.authorizedKeys`. Default substitute servers can be + set via `services.guix.substituters.urls`. + - The `tracy` package no longer works on X11, since it's moved to Wayland support, which is the intended default behavior by Tracy maintainers. X11 users have to switch to the new package `tracy-x11`. -- The `services.prometheus.exporters.minio` option has been removed, as it's upstream implementation was broken and unmaintained. +- `gollum` has been upgraded to major version 6. Please review their [migration notes](https://github.com/gollum/gollum/wiki/6.0-Release-Notes). + +- `services.prometheus.exporters.minio` option has been removed, as it's upstream implementation was broken and unmaintained. Minio now has built-in [Prometheus metrics exposure](https://min.io/docs/minio/linux/operations/monitoring/collect-minio-metrics-using-prometheus.html), which can be used instead. -- The `services.patroni.raft` option has been removed, as Raft has been [deprecated by upstream since 3.0.0](https://github.com/patroni/patroni/blob/master/docs/releases.rst#version-300) +- The `services.prometheus.exporters.tor` option has been removed, as its upstream implementation was broken and unmaintained. + +- `services.patroni.raft` has been removed, as Raft has been [deprecated by upstream since 3.0.0](https://github.com/patroni/patroni/blob/master/docs/releases.rst#version-300). + +- The `jd-cli` package was removed due to an inactive upstream and a dependency on the shut down + JCenter JAR repository. + Java decompilers already packaged in Nixpkgs include `bytecode-viewer` (GUI), `cfr` (CLI), and `procyon` (CLI). + +- The `jd-gui` package was removed due to an inactive upstream and a dependency on the end-of-life Gradle 6. + Java decompilers already packaged in Nixpkgs include `bytecode-viewer` (GUI), `cfr` (CLI), and `procyon` (CLI). - `services.roundcube.maxAttachmentSize` will multiply the value set with `1.37` to offset overhead introduced by the base64 encoding applied to attachments. -- The `sound` options have been removed or renamed, as they had a lot of unintended side effects. See [below](#sec-release-24.11-migration-sound) for details. +- `services.mxisd` has been removed as both [mxisd](https://github.com/kamax-matrix/mxisd) and [ma1sd](https://github.com/ma1uta/ma1sd) are no longer maintained. + Consequently, the package `ma1sd` has also been removed. + +- The `rss-bridge` service drops the support to load a configuration file from `${config.services.rss-bridge.dataDir}/config.ini.php`. + Consider using the `services.rss-bridge.config` option instead. -- The `services.mxisd` module has been removed as both [mxisd](https://github.com/kamax-matrix/mxisd) and [ma1sd](https://github.com/ma1uta/ma1sd) are not maintained any longer. - Consequently the package `pkgs.ma1sd` has also been removed. +- `mikutter` has been removed, as the package was broken and had no maintainers in nixpkgs. -- `ffmpeg_5` has been removed. Please use the unversioned `ffmpeg`, - pin a newer version, or if necessary pin `ffmpeg_4` for compatibility. +- `xdg.portal.gtkUsePortal` has been removed, as it had been deprecated for over 2 years. Using the `GTK_USE_PORTAL` environment variable in this manner is not intended nor encouraged by the GTK developers, but can still be done manually via `environment.sessionVariables`. -- The `xdg.portal.gtkUsePortal` option has been removed, as it had been deprecated for over 2 years. Using the `GTK_USE_PORTAL` environment variable in this manner is not intended nor encouraged by the GTK developers, but can still be done manually via `environment.sessionVariables`. +- Support for the legacy CUPS browsing and LDAP have been removed from `services.printing`. If `cups` or `ldap` are in the `BrowseRemoteProtocols` setting in `services.printing.browsedConf`, it needs to be removed. -- The `services.trust-dns` module has been renamed to `services.hickory-dns`. +- `services.trust-dns` has been renamed to `services.hickory-dns`. -- The option `services.prometheus.exporters.pgbouncer.connectionStringFile` has been removed since +- `services.prometheus.exporters.pgbouncer.connectionStringFile` has been removed since it leaked the connection string (and thus potentially the DB password) into the cmdline of process making it effectively world-readable. Use [`services.prometheus.exporters.pgbouncer.connectionEnvFile`](#opt-services.prometheus.exporters.pgbouncer.connectionEnvFile) instead. -- The `lsh` package and the `services.lshd` module have been removed as they had no maintainer in Nixpkgs and hadn’t seen an upstream release in over a decade. It is recommended to migrate to `openssh` and `services.openssh`. +- `lsh` and `services.lshd` have been removed as they had no maintainer in Nixpkgs and no upstream release in over a decade. It is recommended to migrate to `openssh` and `services.openssh`. + +- `ceph` has been upgraded to v19. See the [Ceph "squid" release notes](https://docs.ceph.com/en/latest/releases/squid/#v19-2-0-squid) for details and recommended upgrade procedure. + +- `services.frr` has been refactored to use upstream service scripts. The per-daemon configurations + have been removed in favour of an `integrated-vtysh-config` style config. The daemon submodules + now use the daemon name (e.g. `ospfd`) instead of the protocol name (`ospf`). The daemons `zebra`, + `mgmtd` and `staticd` are always enabled if a config is present. The `vtyListenAddress` and + `vtyListenPort` options have been removed; use `options` or `extraOptions` instead, respectively. - `opencv2` and `opencv3` have been removed, as they are obsolete and were not used by any other package. External users are encouraged to migrate to OpenCV 4. -- The `tvheadend` package and the `services.tvheadend` module have been - removed as nobody was willing to maintain them and they were stuck on - an unmaintained version that required FFmpeg 4; please see [pull +- `tvheadend` package and the `services.tvheadend` module have been + removed due to lack of maintenance in Nixpkgs and being stuck on + an unmaintained version that required FFmpeg 4. Please see the related [pull request #332259](https://github.com/NixOS/nixpkgs/pull/332259) if you are interested in maintaining a newer version. -- The `antennas` package and the `services.antennas` module have been - removed as they only work with `tvheadend` (see above). +- `antennas` and `services.antennas` have been removed as they only work with `tvheadend` (see above). -- The `system.build.brightboxImage` image has been removed as It did not build anymore and has not seen any maintenance in over 7 years (excluding tree-wide changes). +- `system.build.brightboxImage` has been removed as it no longer built and has not seen any maintenance in over 7 years (excluding tree-wide changes). -- The `services.syncplay` module now exposes all currently available command-line arguments for `syncplay-server` as options, as well as a `useACMEHost` option for easy TLS setup. +- `services.syncplay` now exposes all currently available command-line arguments for `syncplay-server` as options, as well as a `useACMEHost` option for easy TLS setup. The systemd service now uses `DynamicUser`/`StateDirectory` and the `user` and `group` options have been deprecated. -- The `openlens` package got removed, suggested replacment `lens-desktop` +- `openlens` was removed. It is recommended to use `lens-desktop` instead. -- The `services.dnsmasq.extraConfig` option has been removed, as it had been deprecated for over 2 years. This option has been replaced by `services.dnsmasq.settings`. +- `services.dnsmasq.extraConfig` has been removed, as it had been deprecated for over 2 years. This option has been replaced by `services.dnsmasq.settings`. - The NixOS installation media no longer support the ReiserFS or JFS file systems by default. - Minimal installer ISOs are no longer built on the small channel. Please obtain installer images from the full release channels. -- The `isync` package has been updated to version `1.5.0`, which introduces some breaking changes. See the [compatibility concerns](https://sourceforge.net/projects/isync/files/isync/1.5.0/) for more details. +- The default FFmpeg version is now 7.1, and FFmpeg 5 has been removed. + Please prefer using the package variants without a version suffix, + or pin FFmpeg 6 or 4 if necessary for compatibility. + Note that we keep old versions around only as required + to support packages in the tree, + and FFmpeg 4 especially should be avoided in favour of newer versions + as it may be removed soon. + +- `openssl` now defaults to the latest version line `3.3.x`, instead of `3.0.x` before. While there should be no major code incompatibilities, newer OpenSSL versions typically strengthen the default security level. This means that you may have to explicitly allow weak ciphers, hashes and key lengths if necessary. See: [OpenSSL security level documentation](https://docs.openssl.org/3.3/man3/SSL_CTX_set_security_level/). + +- `isync` has been updated to version `1.5.0`, which introduces some breaking changes. See the [compatibility concerns](https://sourceforge.net/projects/isync/files/isync/1.5.0/) for more details. - Legacy package `globalprotect-openconnect` 1.x and related module - `globalprotect-vpn` were dropped. Two new packages `gpauth` and `gpclient` - from the 2.x version of the GlobalProtect-openconnect project are added in its + `services.globalprotect` were dropped. Two new packages -- `gpauth` and `gpclient` + from the 2.x version of the GlobalProtect-openconnect project -- are added in its place. The GUI components related to the project are non-free and not packaged. +- Compatible string matching for `hardware.deviceTree.overlays` has been changed to a more correct behavior. See [below](#sec-release-24.11-migration-dto-compatible) for details. + +- `rustic` was upgraded to `0.9.0`, which contains [breaking changes to the config file format](https://github.com/rustic-rs/rustic/releases/tag/v0.9.0). + +- `pkgs.formats.ini` and `pkgs.formats.iniWithGlobalSection` with + `listsAsDuplicateKeys` or `listToValue` no longer merge non-list values into + lists by default. Backwards-compatible behavior can be enabled with + `atomsCoercedToLists`. + +- Atlassian Server products have been removed, as support for the Atlassian Server + products ended in February 2024 and there was insufficient interest in + maintaining the Atlassian Data Center replacements: + - The `atlassian-bamboo` package + - The `atlassian-confluence` package and its `services.confluence` NixOS module + - The `atlassian-crowd` package and its `services.crowd` NixOS module + - The `atlassian-jira` package and its `services.jira` NixOS module + + +- `python3Packages.nose` has been removed, as it has been deprecated and unmaintained for almost a decade and does not work on Python 3.12. + Please switch to `pytest` or another test runner/framework. + ## Other Notable Changes {#sec-release-24.11-notable-changes} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> @@ -504,6 +736,12 @@ `goModules`, `modRoot`, `vendorHash`, `deleteVendor`, and `proxyVendor` are now passed as derivation attributes. `goModules` and `vendorHash` are no longer placed under `passthru`. +- `buildFlags`/`buildFlagsArray` on `buildGoModule` have been deprecated. 24.11 is the last release where `buildGoModule` accepts these flags (while throwing a warning). + Use the [`ldflags`](https://nixos.org/manual/nixpkgs/unstable/#var-go-ldflags) and/or [`tags`](https://nixos.org/manual/nixpkgs/unstable/#var-go-tags) attributes or + [the environment](https://nixos.org/manual/nixpkgs/unstable/#ssec-go-environment) instead. + +- `buildGoPackage` has been deprecated. 24.11 is the last release with `buildGoPackage` available. + - `hareHook` has been added as the language framework for Hare. From now on, it, not the `hare` package, should be added to `nativeBuildInputs` when building Hare programs. @@ -512,6 +750,11 @@ - `lib.misc.mapAttrsFlatten` is now formally deprecated and will be removed in future releases; use the identical [`lib.attrsets.mapAttrsToList`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.attrsets.mapAttrsToList) instead. +- `virtualisation.docker.liveRestore` has been renamed to `virtualisation.docker.daemon.settings."live-restore"` and turned off by default for state versions of at least 24.11. + +- Tailscale's `authKeyFile` can now have its corresponding parameters set through `config.services.tailscale.authKeyParameters`, allowing for non-ephemeral unsupervised deployment and more. + See [Registering new nodes using OAuth credentials](https://tailscale.com/kb/1215/oauth-clients#registering-new-nodes-using-oauth-credentials) for the supported options. + - `nixosTests` now provide a working IPv6 setup for VLAN 1 by default. - Kanidm can now be provisioned using the new [`services.kanidm.provision`] option, but requires using a patched version available via `pkgs.kanidm.withSecretProvisioning`. @@ -520,11 +763,29 @@ - The kubelet configuration file can now be amended with arbitrary additional content using the `services.kubernetes.kubelet.extraConfig` option. +- The `services.seafile` module was updated to major version 11. + - As part of this upgrade, the database backend will be migrated to MySQL. + This process should be automatic, but in case of a botched migration, + old sqlite files are not removed and can be used to manually migrate the database. + - Additionally, the updated CSRF protection may prevent some users from logging in. + Specific origin addresses can be whitelisted using the `services.seafile.seahubExtraConf` option + (e.g. `services.seafile.seahubExtraConf = ''CSRF_TRUSTED_ORIGINS = ["https://example.com"]'';`). + Note that first solution of the [official FAQ answer](https://cloud.seatable.io/dtable/external-links/7b976c85f504491cbe8e/?tid=0000&vid=0000&row-id=BQhH-2HSQs68Nq2EW91DBA) + is not allowed by the `services.nginx` module's config-checker. + +- The latest available version of Nextcloud is v30 (available as `pkgs.nextcloud30`). The installation logic is as follows: + - If [`services.nextcloud.package`](#opt-services.nextcloud.package) is specified explicitly, this package will be installed (**recommended**) + - If [`system.stateVersion`](#opt-system.stateVersion) is >=24.05, `pkgs.nextcloud29` will be installed by default. + - If [`system.stateVersion`](#opt-system.stateVersion) is >=24.11, `pkgs.nextcloud30` will be installed by default. + - Please note that an upgrade from v28 (or older) to v30 directly is not possible. Please upgrade to `nextcloud29` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud29;`](options.html#opt-services.nextcloud.package). + - To facilitate dependency injection, the `imgui` package now builds a static archive using vcpkg' CMake rules. The derivation now installs "impl" headers selectively instead of by a wildcard. Use `imgui.src` if you just want to access the unpacked sources. -- Linux 4.19 has been removed because it will reach its end of life within the lifespan of 24.11 +- The new `boot.loader.systemd-boot.windows` option makes setting up dual-booting with Windows on a different drive easier. + +- Linux 4.19 has been removed because it will reach its end of life within the lifespan of 24.11. - Unprivileged access to the kernel syslog via `dmesg` is now restricted by default. Users wanting to keep an unrestricted access to it can set `boot.kernel.sysctl."kernel.dmesg_restrict" = false`. @@ -532,8 +793,12 @@ - The `i18n.inputMethod` module introduces two new properties: `enable` and `type`, for declaring whether to enable an alternative input method and defining which input method respectfully. The options available in `type` are the same as the existing `enabled` option. `enabled` is now deprecated, and will be removed in a future release. -- `security.pam.u2f` now follows RFC42. - All module options are now settable through the freeform `.settings`. +- `security.pam.u2f` now uses freeform options; all module options are now configurable through `security.pam.u2f.settings`. + +- `mikutter` was removed as the package was broken and had no maintainers. + +- `services.getty.autologinOnce` was added to limit the automatic login to once per boot and on the first tty only. + When using full disk encryption, this option allows to unlock the system without retyping the passphrase while keeping the other ttys protected. - Gollum was upgraded to major version 6. Read their [migration notes](https://github.com/gollum/gollum/wiki/6.0-Release-Notes). @@ -543,18 +808,29 @@ - `services.timesyncd.fallbackServers` was added and defaults to `networking.timeServers`. -- Cinnamon has been updated to 6.2, please check [upstream announcement](https://www.linuxmint.com/rel_wilma_whatsnew.php) for more details. - Following Mint 22 defaults, the Cinnamon module no longer ships geary and hexchat by default. +- Cinnamon has been updated to 6.2. Please check [upstream announcement](https://www.linuxmint.com/rel_wilma_whatsnew.php) for more details. + Following Mint 22 defaults, the Cinnamon module no longer ships `geary` and `hexchat` by default. - `zfs.latestCompatibleLinuxPackages` is deprecated and is now pointing at the default kernel. If using the stable LTS kernel (default `linuxPackages` is not possible then you must explicitly pin a specific kernel release. For example, `boot.kernelPackages = pkgs.linuxPackages_6_6`. Please be aware that non-LTS kernels are likely to go EOL before ZFS supports the latest supported non-LTS release, requiring manual intervention. - The `shadowstack` hardening flag has been added, though disabled by default. -- `xxd` is now provided by the `tinyxxd` package, rather than `vim.xxd`, to reduce closure size and vulnerability impact. Since it has the same options and semantics as Vim's `xxd` utility, there is no user impact. Vim's `xxd` remains available as the `vim.xxd` package. +- `xxd` is now provided by the `tinyxxd` package rather than `vim.xxd` to reduce closure size and vulnerability impact. Since it has the same options and semantics as Vim's `xxd` utility, there is no user impact. Vim's `xxd` remains available as the `vim.xxd` package. + +- `restic` module now has an option for inhibiting system sleep while backups are running, defaulting to off (not inhibiting sleep). Available as [`services.restic.backups.<name>.inhibitsSleep`](#opt-services.restic.backups._name_.inhibitsSleep). + +- The arguments from [](#opt-services.postgresql.initdbArgs) now get shell-escaped. + +- Mattermost has been updated from 9.5 to 9.11 ESR. See the [changelog](https://docs.mattermost.com/about/mattermost-v9-changelog.html#release-v9-11-extended-support-release) for more details. -- `prometheus-openldap-exporter` was removed since it was unmaintained upstream and had no nixpkgs maintainers. +- `cargo-tauri.hook` was introduced to help users build [Tauri](https://tauri.app/) projects. It is meant to be used alongside + `rustPlatform.buildRustPackage` and Node hooks such as `npmConfigHook`, `pnpm.configHook`, and the new `yarnConfig` -- `restic` module now has an option for inhibiting system sleep while backups are running, defaulting to off (not inhibiting sleep), available as [`services.restic.backups.<name>.inhibitsSleep`](#opt-services.restic.backups._name_.inhibitsSleep). +- `power.ups` now powers off UPSs during a power outage event. + This saves UPS battery and ensures that host(s) get back up again when power comes back, even in the scenario when the UPS would have had enough capacity to keep power on during the whole power outage. + If you like the old behaviour of keeping the UPSs on (and emptying the battery) after the host(s) have shut down, and risk not getting a power cycle event to get the host(s) back up, set `power.ups.upsmon.settings.POWERDOWNFLAG = null;`. + +- `nixos-firewall-tool` now supports nftables in addition to iptables and is installed by default when NixOS firewall is enabled. - Support for *runner registration tokens* has been [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in `gitlab-runner` 15.6 and is expected to be removed in `gitlab-runner` 18.0. Configuration of existing runners @@ -564,10 +840,25 @@ - `iproute2` now has libbpf support. +- `postgresql` is now [hardened by default](#module-services-postgres-hardening) using the common `systemd` settings for that. + + If you use extensions that are not packaged in nixpkgs, please review whether it still works + with the current settings and adjust accordingly if needed. + - `nix.channel.enable = false` no longer implies `nix.settings.nix-path = []`. - Since Nix 2.13, a `nix-path` set in `nix.conf` cannot be overriden by the `NIX_PATH` configuration variable. + Since Nix 2.13, a `nix-path` set in `nix.conf` cannot be overridden by the `NIX_PATH` configuration variable. + +- ZFS now imports its pools in `postResumeCommands` rather than `postDeviceCommands`. If you had `postDeviceCommands` scripts that depended on ZFS pools being imported, those now need to be in `postResumeCommands`. + +- `services.automatic-timezoned.enable = true` will now set `time.timeZone = null`. + This is to avoid silently shadowing a user's explicitly defined timezone without recognition on the user's part. + +- `services.localtimed.enable = true` will now set `time.timeZone = null`. + This is to avoid silently shadowing a user's explicitly defined timezone without recognition on the user's part. + +- `qgis` and `qgis-ltr` are now built without `grass` by default. `grass` support can be enabled with `qgis.override { withGrass = true; }`. -## Detailed migration information {#sec-release-24.11-migration} +## Detailed Migration Information {#sec-release-24.11-migration} ### `sound` options removal {#sec-release-24.11-migration-sound} @@ -609,3 +900,22 @@ in { ]; }; ``` + +### `hardware.deviceTree.overlays` compatible string matching {#sec-release-24.11-migration-dto-compatible} + +The original compatible string implementation in older NixOS versions relied on substring matching, +which is incorrect for overlays with multiple compatible strings and other cases. + +The new behavior is consistent with what other tools already do - the overlay is considered applicable if, +and only if, _any_ of the compatible strings in the overlay match _any_ of the compatible strings in the DT. + +To provide some examples: + +| Overlay `compatible` | DT `compatible` | Pre-24.11 behavior | Correct behavior | Notes | +|----------------------|-----------------|--------------------|------------------|--------------------------------------------| +| `"foo"` | `"foo", "bar"` | match | match | Most common use case does not change | +| `"foo"` | `"foobar"` | match | no match | Substrings should not be matched | +| `"foo bar"` | `"foo", "bar"` | match | no match | Separators should not be matched to spaces | +| `"foo", "bar"` | `"baz", "bar"` | no match | match | One compatible string matching is enough | + +Note that this also allows writing overlays that explicitly apply to multiple boards. diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix index fedd85f09b80..39961f0f25d5 100644 --- a/nixos/lib/systemd-lib.nix +++ b/nixos/lib/systemd-lib.nix @@ -17,6 +17,7 @@ let filterAttrs flatten flip + hasPrefix head isInt isFloat @@ -98,8 +99,9 @@ in rec { l = reverseList (stringToCharacters s); suffix = head l; nums = tail l; - in elem suffix (["K" "M" "G" "T"] ++ digits) - && all (num: elem num digits) nums; + in builtins.isInt s + || (elem suffix (["K" "M" "G" "T"] ++ digits) + && all (num: elem num digits) nums); assertByteFormat = name: group: attr: optional (attr ? ${name} && ! isByteFormat attr.${name}) @@ -196,6 +198,10 @@ in rec { optional (attr ? ${name}) "Systemd ${group} field `${name}' has been removed. See ${see}"; + assertKeyIsSystemdCredential = name: group: attr: + optional (attr ? ${name} && !(hasPrefix "@" attr.${name})) + "Systemd ${group} field `${name}' is not a systemd credential"; + checkUnitConfig = group: checks: attrs: let # We're applied at the top-level type (attrsOf unitOption), so the actual # unit options might contain attributes from mkOverride and mkIf that we need to @@ -386,18 +392,27 @@ in rec { ''} ''; # */ - makeJobScript = name: text: + makeJobScript = { name, text, enableStrictShellChecks }: let scriptName = replaceStrings [ "\\" "@" ] [ "-" "_" ] (shellEscape name); - out = (pkgs.writeShellScriptBin scriptName '' - set -e - ${text} - '').overrideAttrs (_: { + out = ( + if ! enableStrictShellChecks then + pkgs.writeShellScriptBin scriptName '' + set -e + + ${text} + '' + else + pkgs.writeShellApplication { + name = scriptName; + inherit text; + } + ).overrideAttrs (_: { # The derivation name is different from the script file name # to keep the script file name short to avoid cluttering logs. name = "unit-script-${scriptName}"; }); - in "${out}/bin/${scriptName}"; + in lib.getExe out; unitConfig = { config, name, options, ... }: { config = { @@ -448,10 +463,16 @@ in rec { }; }; - serviceConfig = { name, config, ... }: { + serviceConfig = + let + nixosConfig = config; + in + { name, lib, config, ... }: { config = { name = "${name}.service"; environment.PATH = mkIf (config.path != []) "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}"; + + enableStrictShellChecks = lib.mkOptionDefault nixosConfig.systemd.enableStrictShellChecks; }; }; diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix index 160f2bf9483a..b02a11ef33a6 100644 --- a/nixos/lib/systemd-unit-options.nix +++ b/nixos/lib/systemd-unit-options.nix @@ -17,6 +17,7 @@ let concatMap filterOverrides isList + literalExpression mergeEqualOption mkIf mkMerge @@ -357,6 +358,14 @@ in rec { ''; }; + enableStrictShellChecks = mkOption { + type = types.bool; + description = "Enable running shellcheck on the generated scripts for this unit."; + # The default gets set in systemd-lib.nix because we don't have access to + # the full NixOS config here. + defaultText = literalExpression "config.systemd.enableStrictShellChecks"; + }; + script = mkOption { type = types.lines; default = ""; @@ -428,27 +437,51 @@ in rec { config = mkMerge [ (mkIf (config.preStart != "") rec { - jobScripts = makeJobScript "${name}-pre-start" config.preStart; + jobScripts = makeJobScript { + name = "${name}-pre-start"; + text = config.preStart; + inherit (config) enableStrictShellChecks; + }; serviceConfig.ExecStartPre = [ jobScripts ]; }) (mkIf (config.script != "") rec { - jobScripts = makeJobScript "${name}-start" config.script; + jobScripts = makeJobScript { + name = "${name}-start"; + text = config.script; + inherit (config) enableStrictShellChecks; + }; serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs; }) (mkIf (config.postStart != "") rec { - jobScripts = (makeJobScript "${name}-post-start" config.postStart); + jobScripts = makeJobScript { + name = "${name}-post-start"; + text = config.postStart; + inherit (config) enableStrictShellChecks; + }; serviceConfig.ExecStartPost = [ jobScripts ]; }) (mkIf (config.reload != "") rec { - jobScripts = makeJobScript "${name}-reload" config.reload; + jobScripts = makeJobScript { + name = "${name}-reload"; + text = config.reload; + inherit (config) enableStrictShellChecks; + }; serviceConfig.ExecReload = jobScripts; }) (mkIf (config.preStop != "") rec { - jobScripts = makeJobScript "${name}-pre-stop" config.preStop; + jobScripts = makeJobScript { + name = "${name}-pre-stop"; + text = config.preStop; + inherit (config) enableStrictShellChecks; + }; serviceConfig.ExecStop = jobScripts; }) (mkIf (config.postStop != "") rec { - jobScripts = makeJobScript "${name}-post-stop" config.postStop; + jobScripts = makeJobScript { + name = "${name}-post-stop"; + text = config.postStop; + inherit (config) enableStrictShellChecks; + }; serviceConfig.ExecStopPost = jobScripts; }) ]; diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py index 01b64b92e977..0f01bd6d0ab4 100644 --- a/nixos/lib/test-driver/test_driver/driver.py +++ b/nixos/lib/test-driver/test_driver/driver.py @@ -99,7 +99,16 @@ class Driver: with self.logger.nested("cleanup"): self.race_timer.cancel() for machine in self.machines: - machine.release() + try: + machine.release() + except Exception as e: + self.logger.error(f"Error during cleanup of {machine.name}: {e}") + + for vlan in self.vlans: + try: + vlan.stop() + except Exception as e: + self.logger.error(f"Error during cleanup of vlan{vlan.nr}: {e}") def subtest(self, name: str) -> Iterator[None]: """Group logs under a given test name""" diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py index 3a1d5bc1be76..7a602ce6608f 100644 --- a/nixos/lib/test-driver/test_driver/machine.py +++ b/nixos/lib/test-driver/test_driver/machine.py @@ -1234,6 +1234,9 @@ class Machine: self.monitor.close() self.serial_thread.join() + if self.qmp_client: + self.qmp_client.close() + def run_callbacks(self) -> None: for callback in self.callbacks: callback() diff --git a/nixos/lib/test-driver/test_driver/qmp.py b/nixos/lib/test-driver/test_driver/qmp.py index 62ca6d7d5b80..99c02ca1c120 100644 --- a/nixos/lib/test-driver/test_driver/qmp.py +++ b/nixos/lib/test-driver/test_driver/qmp.py @@ -49,7 +49,7 @@ class QMPSession: sock.connect(str(path)) return cls(sock) - def __del__(self) -> None: + def close(self) -> None: self.sock.close() def _wait_for_new_result(self) -> dict[str, str]: diff --git a/nixos/lib/test-driver/test_driver/vlan.py b/nixos/lib/test-driver/test_driver/vlan.py index 9340fc92ed4c..03ecbe25e8a2 100644 --- a/nixos/lib/test-driver/test_driver/vlan.py +++ b/nixos/lib/test-driver/test_driver/vlan.py @@ -59,7 +59,7 @@ class VLan: self.logger.info(f"running vlan (pid {self.pid}; ctl {self.socket_dir})") - def __del__(self) -> None: + def stop(self) -> None: self.logger.info(f"kill vlan (pid {self.pid})") self.fd.close() self.process.terminate() diff --git a/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix index 9aecca10ac6b..4c528609fb4e 100644 --- a/nixos/lib/testing/nodes.nix +++ b/nixos/lib/testing/nodes.nix @@ -13,7 +13,7 @@ let types ; - inherit (hostPkgs) hostPlatform; + inherit (hostPkgs.stdenv) hostPlatform; guestSystem = if hostPlatform.isLinux @@ -151,7 +151,7 @@ in nodesCompat = mapAttrs (name: config: config // { - config = lib.warnIf (lib.isInOldestRelease 2211) + config = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2211) "Module argument `nodes.${name}.config` is deprecated. Use `nodes.${name}` instead." config; }) diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix index 1b3724bfc170..e08cf572f7f9 100644 --- a/nixos/maintainers/scripts/ec2/amazon-image.nix +++ b/nixos/maintainers/scripts/ec2/amazon-image.nix @@ -1,24 +1,47 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let - inherit (lib) mkOption optionalString types versionAtLeast; + inherit (lib) + mkOption + optionalString + types + versionAtLeast + ; inherit (lib.options) literalExpression; cfg = config.amazonImage; amiBootMode = if config.ec2.efi then "uefi" else "legacy-bios"; - -in { - - imports = [ ../../../modules/virtualisation/amazon-image.nix ]; +in +{ + imports = [ + ../../../modules/virtualisation/amazon-image.nix + ../../../modules/virtualisation/disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "amazonImage" + "sizeMB" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; # Amazon recommends setting this to the highest possible value for a good EBS # experience, which prior to 4.15 was 255. # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes config.boot.kernelParams = - let timeout = - if versionAtLeast config.boot.kernelPackages.kernel.version "4.15" - then "4294967295" - else "255"; - in [ "nvme_core.io_timeout=${timeout}" ]; + let + timeout = + if versionAtLeast config.boot.kernelPackages.kernel.version "4.15" then "4294967295" else "255"; + in + [ "nvme_core.io_timeout=${timeout}" ]; options.amazonImage = { name = mkOption { @@ -34,30 +57,32 @@ in { } ] ''; - default = []; + default = [ ]; description = '' This option lists files to be copied to fixed locations in the generated image. Glob patterns work. ''; }; - sizeMB = mkOption { - type = with types; either (enum [ "auto" ]) int; - default = 3072; - example = 8192; - description = "The size in MB of the image"; - }; - format = mkOption { - type = types.enum [ "raw" "qcow2" "vpc" ]; + type = types.enum [ + "raw" + "qcow2" + "vpc" + ]; default = "vpc"; description = "The image format to output"; }; }; - config.system.build.amazonImage = let - configFile = pkgs.writeText "configuration.nix" - '' + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault + # to avoid breaking existing configs using that. + config.virtualisation.diskSize = lib.mkOverride 1490 (3 * 1024); + config.virtualisation.diskSizeAutoSupported = !config.ec2.zfs.enable; + + config.system.build.amazonImage = + let + configFile = pkgs.writeText "configuration.nix" '' { modulesPath, ... }: { imports = [ "''${modulesPath}/virtualisation/amazon-image.nix" ]; ${optionalString config.ec2.efi '' @@ -70,91 +95,102 @@ in { } ''; - zfsBuilder = import ../../../lib/make-multi-disk-zfs-image.nix { - inherit lib config configFile pkgs; - inherit (cfg) contents format name; - - includeChannel = true; - - bootSize = 1000; # 1G is the minimum EBS volume - - rootSize = cfg.sizeMB; - rootPoolProperties = { - ashift = 12; - autoexpand = "on"; + zfsBuilder = import ../../../lib/make-multi-disk-zfs-image.nix { + inherit + lib + config + configFile + pkgs + ; + inherit (cfg) contents format name; + + includeChannel = true; + + bootSize = 1000; # 1G is the minimum EBS volume + + rootSize = config.virtualisation.diskSize; + rootPoolProperties = { + ashift = 12; + autoexpand = "on"; + }; + + datasets = config.ec2.zfs.datasets; + + postVM = '' + extension=''${rootDiskImage##*.} + friendlyName=$out/${cfg.name} + rootDisk="$friendlyName.root.$extension" + bootDisk="$friendlyName.boot.$extension" + mv "$rootDiskImage" "$rootDisk" + mv "$bootDiskImage" "$bootDisk" + + mkdir -p $out/nix-support + echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products + echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products + + ${pkgs.jq}/bin/jq -n \ + --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ + --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ + --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ + --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ + --arg boot_mode "${amiBootMode}" \ + --arg root "$rootDisk" \ + --arg boot "$bootDisk" \ + '{} + | .label = $system_label + | .boot_mode = $boot_mode + | .system = $system + | .disks.boot.logical_bytes = $boot_logical_bytes + | .disks.boot.file = $boot + | .disks.root.logical_bytes = $root_logical_bytes + | .disks.root.file = $root + ' > $out/nix-support/image-info.json + ''; }; - datasets = config.ec2.zfs.datasets; - - postVM = '' - extension=''${rootDiskImage##*.} - friendlyName=$out/${cfg.name} - rootDisk="$friendlyName.root.$extension" - bootDisk="$friendlyName.boot.$extension" - mv "$rootDiskImage" "$rootDisk" - mv "$bootDiskImage" "$bootDisk" - - mkdir -p $out/nix-support - echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products - echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products - - ${pkgs.jq}/bin/jq -n \ - --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ - --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ - --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ - --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ - --arg boot_mode "${amiBootMode}" \ - --arg root "$rootDisk" \ - --arg boot "$bootDisk" \ - '{} - | .label = $system_label - | .boot_mode = $boot_mode - | .system = $system - | .disks.boot.logical_bytes = $boot_logical_bytes - | .disks.boot.file = $boot - | .disks.root.logical_bytes = $root_logical_bytes - | .disks.root.file = $root - ' > $out/nix-support/image-info.json - ''; - }; - - extBuilder = import ../../../lib/make-disk-image.nix { - inherit lib config configFile pkgs; - - inherit (cfg) contents format name; - - fsType = "ext4"; - partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt"; - - diskSize = cfg.sizeMB; - - postVM = '' - extension=''${diskImage##*.} - friendlyName=$out/${cfg.name}.$extension - mv "$diskImage" "$friendlyName" - diskImage=$friendlyName - - mkdir -p $out/nix-support - echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products - - ${pkgs.jq}/bin/jq -n \ - --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ - --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ - --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ - --arg boot_mode "${amiBootMode}" \ - --arg file "$diskImage" \ - '{} - | .label = $system_label - | .boot_mode = $boot_mode - | .system = $system - | .logical_bytes = $logical_bytes - | .file = $file - | .disks.root.logical_bytes = $logical_bytes - | .disks.root.file = $file - ' > $out/nix-support/image-info.json - ''; - }; - in if config.ec2.zfs.enable then zfsBuilder else extBuilder; + extBuilder = import ../../../lib/make-disk-image.nix { + inherit + lib + config + configFile + pkgs + ; + + inherit (cfg) contents format name; + + fsType = "ext4"; + partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt"; + + inherit (config.virtualisation) diskSize; + + postVM = '' + extension=''${diskImage##*.} + friendlyName=$out/${cfg.name}.$extension + mv "$diskImage" "$friendlyName" + diskImage=$friendlyName + + mkdir -p $out/nix-support + echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products + + ${pkgs.jq}/bin/jq -n \ + --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ + --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ + --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ + --arg boot_mode "${amiBootMode}" \ + --arg file "$diskImage" \ + '{} + | .label = $system_label + | .boot_mode = $boot_mode + | .system = $system + | .logical_bytes = $logical_bytes + | .file = $file + | .disks.root.logical_bytes = $logical_bytes + | .disks.root.file = $file + ' > $out/nix-support/image-info.json + ''; + }; + in + if config.ec2.zfs.enable then zfsBuilder else extBuilder; meta.maintainers = with lib.maintainers; [ arianvp ]; } diff --git a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix index 9799f333aec0..57022bd2f784 100644 --- a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix +++ b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix @@ -1,6 +1,11 @@ # nix-build '<nixpkgs/nixos>' -A config.system.build.openstackImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/openstack-image.nix ]; }" -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let inherit (lib) mkOption types; copyChannel = true; @@ -10,9 +15,20 @@ in { imports = [ ../../../modules/virtualisation/openstack-config.nix + ../../../modules/virtualisation/disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "openstackImage" + "sizeMB" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) ] ++ (lib.optional copyChannel ../../../modules/installer/cd-dvd/channel.nix); - options.openstackImage = { name = mkOption { type = types.str; @@ -22,18 +38,15 @@ in ramMB = mkOption { type = types.int; - default = 1024; + default = (3 * 1024); description = "RAM allocation for build VM"; }; - sizeMB = mkOption { - type = types.int; - default = 8192; - description = "The size in MB of the image"; - }; - format = mkOption { - type = types.enum [ "raw" "qcow2" ]; + type = types.enum [ + "raw" + "qcow2" + ]; default = "qcow2"; description = "The image format to output"; }; @@ -54,24 +67,28 @@ in }; }; + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault + # to avoid breaking existing configs using that. + virtualisation.diskSize = lib.mkOverride 1490 (8 * 1024); + virtualisation.diskSizeAutoSupported = false; + system.build.openstackImage = import ../../../lib/make-single-disk-zfs-image.nix { inherit lib config; inherit (cfg) contents format name; pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package - configFile = pkgs.writeText "configuration.nix" - '' - { modulesPath, ... }: { - imports = [ "''${modulesPath}/virtualisation/openstack-config.nix" ]; - openstack.zfs.enable = true; - } - ''; + configFile = pkgs.writeText "configuration.nix" '' + { modulesPath, ... }: { + imports = [ "''${modulesPath}/virtualisation/openstack-config.nix" ]; + openstack.zfs.enable = true; + } + ''; includeChannel = copyChannel; bootSize = 1000; memSize = cfg.ramMB; - rootSize = cfg.sizeMB; + rootSize = config.virtualisation.diskSize; rootPoolProperties = { ashift = 12; autoexpand = "on"; diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix index 7b58d3adc22b..205fd1fa7b96 100644 --- a/nixos/modules/config/fonts/fontconfig.nix +++ b/nixos/modules/config/fonts/fontconfig.nix @@ -52,7 +52,7 @@ let ${lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' <!-- Pre-generated font caches --> <cachedir>${cache}</cachedir> - ${lib.optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) '' + ${lib.optionalString (pkgs.stdenv.hostPlatform.isx86_64 && cfg.cache32Bit) '' <cachedir>${cache32}</cachedir> ''} ''} diff --git a/nixos/modules/config/ldso.nix b/nixos/modules/config/ldso.nix index 60156dd04098..f8d89db0220e 100644 --- a/nixos/modules/config/ldso.nix +++ b/nixos/modules/config/ldso.nix @@ -32,7 +32,7 @@ in { config = { assertions = [ - { assertion = isNull config.environment.ldso32 || pkgs.stdenv.isx86_64; + { assertion = isNull config.environment.ldso32 || pkgs.stdenv.hostPlatform.isx86_64; message = "Option environment.ldso32 currently only works on x86_64."; } ]; @@ -44,7 +44,7 @@ in { "d /${libDir} 0755 root root - -" "L+ /${libDir}/${ldsoBasename} - - - - ${config.environment.ldso}" ] - ) ++ optionals pkgs.stdenv.isx86_64 ( + ) ++ optionals pkgs.stdenv.hostPlatform.isx86_64 ( if isNull config.environment.ldso32 then [ "r /${libDir32}/${ldsoBasename32} - - - - -" ] else [ diff --git a/nixos/modules/config/nix-flakes.nix b/nixos/modules/config/nix-flakes.nix index d6c31735a6ca..90e207fd4a61 100644 --- a/nixos/modules/config/nix-flakes.nix +++ b/nixos/modules/config/nix-flakes.nix @@ -72,7 +72,7 @@ in type = "path"; path = config.flake.outPath; } // filterAttrs - (n: _: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash") + (n: _: n == "lastModified" || n == "rev" || n == "narHash") config.flake )); }; diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix index 6a6aeaab6541..6e6e577e6e20 100644 --- a/nixos/modules/config/pulseaudio.nix +++ b/nixos/modules/config/pulseaudio.nix @@ -12,7 +12,7 @@ let # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps # using 32bit alsa on 64bit linux. - enable32BitAlsaPlugins = cfg.support32Bit && pkgs.stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null); + enable32BitAlsaPlugins = cfg.support32Bit && pkgs.stdenv.hostPlatform.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null); myConfigFile = diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix index 3609b1ba475b..70ee02421cc3 100644 --- a/nixos/modules/config/resolvconf.nix +++ b/nixos/modules/config/resolvconf.nix @@ -114,6 +114,15 @@ in ''; }; + subscriberFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = []; + description = '' + Files written by resolvconf updates + ''; + internal = true; + }; + }; }; @@ -132,6 +141,10 @@ in } (lib.mkIf cfg.enable { + users.groups.resolvconf = {}; + + networking.resolvconf.subscriberFiles = [ "/etc/resolv.conf" ]; + networking.resolvconf.package = pkgs.openresolv; environment.systemPackages = [ cfg.package ]; @@ -143,12 +156,18 @@ in wants = [ "network-pre.target" ]; wantedBy = [ "multi-user.target" ]; restartTriggers = [ config.environment.etc."resolvconf.conf".source ]; - - serviceConfig = { - Type = "oneshot"; - ExecStart = "${cfg.package}/bin/resolvconf -u"; - RemainAfterExit = true; - }; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + + script = '' + ${lib.getExe cfg.package} -u + chgrp resolvconf ${lib.escapeShellArgs cfg.subscriberFiles} + chmod g=u ${lib.escapeShellArgs cfg.subscriberFiles} + ${lib.getExe' pkgs.acl "setfacl"} -R \ + -m group:resolvconf:rwx \ + -m default:group:resolvconf:rwx \ + /run/resolvconf + ''; }; }) diff --git a/nixos/modules/config/stub-ld.nix b/nixos/modules/config/stub-ld.nix index 87b7bdf07a2d..05945852ee45 100644 --- a/nixos/modules/config/stub-ld.nix +++ b/nixos/modules/config/stub-ld.nix @@ -49,7 +49,7 @@ in { config = mkIf cfg.enable { environment.ldso = mkDefault stub-ld; - environment.ldso32 = mkIf pkgs.stdenv.isx86_64 (mkDefault stub-ld32); + environment.ldso32 = mkIf pkgs.stdenv.hostPlatform.isx86_64 (mkDefault stub-ld32); }; meta.maintainers = with lib.maintainers; [ tejing ]; diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix index bf8bc43d6bb5..78d4847092d5 100644 --- a/nixos/modules/config/terminfo.nix +++ b/nixos/modules/config/terminfo.nix @@ -24,25 +24,35 @@ config = { + # This should not contain packages that are broken or can't build, since it + # will break this expression + # + # Currently broken packages: + # - contour + # # can be generated with: # lib.attrNames (lib.filterAttrs # (_: drv: (builtins.tryEval (lib.isDerivation drv && drv ? terminfo)).value) # pkgs) - environment.systemPackages = lib.mkIf config.environment.enableAllTerminfo (map (x: x.terminfo) (with pkgs.pkgsBuildBuild; [ - alacritty - contour - foot - kitty - mtm - rio - rxvt-unicode-unwrapped - rxvt-unicode-unwrapped-emoji - st - termite - tmux - wezterm - yaft - ])); + environment.systemPackages = lib.mkIf config.environment.enableAllTerminfo ( + map (x: x.terminfo) ( + with pkgs.pkgsBuildBuild; + [ + alacritty + foot + kitty + mtm + rio + rxvt-unicode-unwrapped + rxvt-unicode-unwrapped-emoji + st + termite + tmux + wezterm + yaft + ] + ) + ); environment.pathsToLink = [ "/share/terminfo" diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index 69646e550f1f..845f9fdaf68e 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -6,6 +6,7 @@ let attrNames attrValues concatMap + concatMapStringsSep concatStrings elem filter @@ -13,6 +14,7 @@ let flatten flip foldr + generators getAttr hasAttr id @@ -944,16 +946,18 @@ in { warnings = flip concatMap (attrValues cfg.users) (user: let - unambiguousPasswordConfiguration = 1 >= length (filter (x: x != null) ([ - user.hashedPassword - user.hashedPasswordFile - user.password + passwordOptions = [ + "hashedPassword" + "hashedPasswordFile" + "password" ] ++ optionals cfg.mutableUsers [ # For immutable users, initialHashedPassword is set to hashedPassword, # so using these options would always trigger the assertion. - user.initialHashedPassword - user.initialPassword - ])); + "initialHashedPassword" + "initialPassword" + ]; + unambiguousPasswordConfiguration = 1 >= length + (filter (x: x != null) (map (flip getAttr user) passwordOptions)); in optional (!unambiguousPasswordConfiguration) '' The user '${user.name}' has multiple of the options `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword` @@ -961,6 +965,13 @@ in { The options silently discard others by the order of precedence given above which can lead to surprising results. To resolve this warning, set at most one of the options above to a non-`null` value. + + The values of these options are: + ${concatMapStringsSep + "\n" + (value: + "* users.users.\"${user.name}\".${value}: ${generators.toPretty {} user.${value}}") + passwordOptions} '') ++ filter (x: x != null) ( flip mapAttrsToList cfg.users (_: user: diff --git a/nixos/modules/config/xdg/autostart.nix b/nixos/modules/config/xdg/autostart.nix index 6f33f592cf57..8fc3cb9920fa 100644 --- a/nixos/modules/config/xdg/autostart.nix +++ b/nixos/modules/config/xdg/autostart.nix @@ -10,7 +10,7 @@ default = true; description = '' Whether to install files to support the - [XDG Autostart specification](https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html). + [XDG Autostart specification](https://specifications.freedesktop.org/autostart-spec/latest). ''; }; }; diff --git a/nixos/modules/config/xdg/icons.nix b/nixos/modules/config/xdg/icons.nix index 9e2b3c2e46e0..5fc7b3506a1d 100644 --- a/nixos/modules/config/xdg/icons.nix +++ b/nixos/modules/config/xdg/icons.nix @@ -10,7 +10,15 @@ default = true; description = '' Whether to install files to support the - [XDG Icon Theme specification](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html). + [XDG Icon Theme specification](https://specifications.freedesktop.org/icon-theme-spec/latest). + ''; + }; + xdg.icons.fallbackCursorThemes = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = '' + Names of the fallback cursor themes, in order of preference, to be used when no other icon source can be found. + Set to `[]` to disable the fallback entirely. ''; }; }; @@ -25,6 +33,15 @@ # Empty icon theme that contains index.theme file describing directories # where toolkits should look for icons installed by apps. pkgs.hicolor-icon-theme + ] ++ lib.optionals (config.xdg.icons.fallbackCursorThemes != []) [ + (pkgs.writeTextFile { + name = "fallback-cursor-theme"; + text = '' + [Icon Theme] + Inherits=${lib.concatStringsSep "," config.xdg.icons.fallbackCursorThemes} + ''; + destination = "/share/icons/default/index.theme"; + }) ]; # libXcursor looks for cursors in XCURSOR_PATH diff --git a/nixos/modules/config/xdg/menus.nix b/nixos/modules/config/xdg/menus.nix index a71a46dd36cc..6c05d49189ad 100644 --- a/nixos/modules/config/xdg/menus.nix +++ b/nixos/modules/config/xdg/menus.nix @@ -10,7 +10,7 @@ default = true; description = '' Whether to install files to support the - [XDG Desktop Menu specification](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html). + [XDG Desktop Menu specification](https://specifications.freedesktop.org/menu-spec/latest). ''; }; }; diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix index 9bd1af397002..20a49a5346e2 100644 --- a/nixos/modules/config/xdg/mime.nix +++ b/nixos/modules/config/xdg/mime.nix @@ -17,8 +17,8 @@ in default = true; description = '' Whether to install files to support the - [XDG Shared MIME-info specification](https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html) and the - [XDG MIME Applications specification](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html). + [XDG Shared MIME-info specification](https://specifications.freedesktop.org/shared-mime-info-spec/latest) and the + [XDG MIME Applications specification](https://specifications.freedesktop.org/mime-apps-spec/latest). ''; }; @@ -32,7 +32,7 @@ in description = '' Adds associations between mimetypes and applications. See the [ - specifications](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations) for more information. + specifications](https://specifications.freedesktop.org/mime-apps-spec/latest/associations) for more information. ''; }; @@ -46,7 +46,7 @@ in description = '' Sets the default applications for given mimetypes. See the [ - specifications](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#default) for more information. + specifications](https://specifications.freedesktop.org/mime-apps-spec/latest/default) for more information. ''; }; @@ -60,7 +60,7 @@ in description = '' Removes associations between mimetypes and applications. See the [ - specifications](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations) for more information. + specifications](https://specifications.freedesktop.org/mime-apps-spec/latest/associations) for more information. ''; }; }; diff --git a/nixos/modules/hardware/apple-touchbar.nix b/nixos/modules/hardware/apple-touchbar.nix new file mode 100644 index 000000000000..700ef173a907 --- /dev/null +++ b/nixos/modules/hardware/apple-touchbar.nix @@ -0,0 +1,43 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.hardware.apple.touchBar; + format = pkgs.formats.toml { }; + cfgFile = format.generate "config.toml" cfg.settings; +in +{ + options.hardware.apple.touchBar = { + enable = lib.mkEnableOption "support for the Touch Bar on some Apple laptops using tiny-dfr"; + package = lib.mkPackageOption pkgs "tiny-dfr" { }; + + settings = lib.mkOption { + type = format.type; + default = { }; + description = '' + Configuration for tiny-dfr. See [example configuration][1] for available options. + + [1]: https://github.com/WhatAmISupposedToPutHere/tiny-dfr/blob/master/share/tiny-dfr/config.toml + ''; + example = lib.literalExpression '' + { + MediaLayerDefault = true; + ShowButtonOutlines = false; + EnablePixelShift = true; + } + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.packages = [ cfg.package ]; + services.udev.packages = [ cfg.package ]; + + environment.etc."tiny-dfr/config.toml".source = cfgFile; + systemd.services.tiny-dfr.restartTriggers = [ cfgFile ]; + }; +} diff --git a/nixos/modules/hardware/corectrl.nix b/nixos/modules/hardware/corectrl.nix index 88b94e12f6b6..6e680ddc846e 100644 --- a/nixos/modules/hardware/corectrl.nix +++ b/nixos/modules/hardware/corectrl.nix @@ -1,23 +1,35 @@ -{ config, pkgs, lib, ... }: +{ + config, + pkgs, + lib, + ... +}: let + inherit (lib) + mkEnableOption + mkIf + mkOption + mkPackageOption + ; + cfg = config.programs.corectrl; in { options.programs.corectrl = { - enable = lib.mkEnableOption '' + enable = mkEnableOption '' CoreCtrl, a tool to overclock amd graphics cards and processors. Add your user to the corectrl group to run corectrl without needing to enter your password ''; - package = lib.mkPackageOption pkgs "corectrl" { + package = mkPackageOption pkgs "corectrl" { extraDescription = "Useful for overriding the configuration options used for the package."; }; gpuOverclock = { - enable = lib.mkEnableOption '' + enable = mkEnableOption '' GPU overclocking ''; - ppfeaturemask = lib.mkOption { + ppfeaturemask = mkOption { type = lib.types.str; default = "0xfffd7fff"; example = "0xffffffff"; @@ -31,33 +43,34 @@ in }; }; - config = lib.mkIf cfg.enable (lib.mkMerge [ - { - environment.systemPackages = [ cfg.package ]; + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; - services.dbus.packages = [ cfg.package ]; + services.dbus.packages = [ cfg.package ]; - users.groups.corectrl = { }; + users.groups.corectrl = { }; - security.polkit.extraConfig = '' - polkit.addRule(function(action, subject) { - if ((action.id == "org.corectrl.helper.init" || - action.id == "org.corectrl.helperkiller.init") && - subject.local == true && - subject.active == true && - subject.isInGroup("corectrl")) { - return polkit.Result.YES; - } - }); - ''; - } + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + if ((action.id == "org.corectrl.helper.init" || + action.id == "org.corectrl.helperkiller.init") && + subject.local == true && + subject.active == true && + subject.isInGroup("corectrl")) { + return polkit.Result.YES; + } + }); + ''; - (lib.mkIf cfg.gpuOverclock.enable { - # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n169 - # The overdrive bit - boot.kernelParams = [ "amdgpu.ppfeaturemask=${cfg.gpuOverclock.ppfeaturemask}" ]; - }) - ]); + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n169 + # The overdrive bit + boot.kernelParams = mkIf cfg.gpuOverclock.enable [ + "amdgpu.ppfeaturemask=${cfg.gpuOverclock.ppfeaturemask}" + ]; + }; - meta.maintainers = with lib.maintainers; [ artturin ]; + meta.maintainers = with lib.maintainers; [ + artturin + Scrumplex + ]; } diff --git a/nixos/modules/hardware/graphics.nix b/nixos/modules/hardware/graphics.nix index 99c122f75c2a..2d6b377ae5fb 100644 --- a/nixos/modules/hardware/graphics.nix +++ b/nixos/modules/hardware/graphics.nix @@ -14,7 +14,7 @@ let in { imports = [ - (lib.mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "graphics" "extraPackages" ]) (lib.mkRemovedOptionModule [ "hardware" "opengl" "s3tcSupport" ] "S3TC support is now always enabled in Mesa.") (lib.mkRemovedOptionModule [ "hardware" "opengl" "driSupport"] "The setting can be removed.") @@ -100,7 +100,7 @@ in config = lib.mkIf cfg.enable { assertions = [ { - assertion = cfg.enable32Bit -> pkgs.stdenv.isx86_64; + assertion = cfg.enable32Bit -> pkgs.stdenv.hostPlatform.isx86_64; message = "`hardware.graphics.enable32Bit` only makes sense on a 64-bit system."; } { @@ -112,7 +112,7 @@ in systemd.tmpfiles.settings.graphics-driver = { "/run/opengl-driver"."L+".argument = toString driversEnv; "/run/opengl-driver-32" = - if pkgs.stdenv.isi686 then + if pkgs.stdenv.hostPlatform.isi686 then { "L+".argument = "opengl-driver"; } else if cfg.enable32Bit then { "L+".argument = toString driversEnv32; } diff --git a/nixos/modules/hardware/hackrf.nix b/nixos/modules/hardware/hackrf.nix index 7f03b765bbda..467fd71ff1b0 100644 --- a/nixos/modules/hardware/hackrf.nix +++ b/nixos/modules/hardware/hackrf.nix @@ -12,6 +12,7 @@ in description = '' Enables hackrf udev rules and ensures 'plugdev' group exists. This is a prerequisite to using HackRF devices without being root, since HackRF USB descriptors will be owned by plugdev through udev. + Ensure your user is a member of the 'plugdev' group after enabling. ''; }; }; diff --git a/nixos/modules/hardware/nitrokey.nix b/nixos/modules/hardware/nitrokey.nix index 08d6b16790f7..6a11ba0891d7 100644 --- a/nixos/modules/hardware/nitrokey.nix +++ b/nixos/modules/hardware/nitrokey.nix @@ -11,14 +11,12 @@ in type = lib.types.bool; default = false; description = '' - Enables udev rules for Nitrokey devices. By default grants access - to users in the "nitrokey" group. You may want to install the - nitrokey-app package, depending on your device and needs. + Enables udev rules for Nitrokey devices. ''; }; }; config = lib.mkIf cfg.enable { - services.udev.packages = [ pkgs.libnitrokey ]; + services.udev.packages = [ pkgs.nitrokey-udev-rules ]; }; } diff --git a/nixos/modules/hardware/steam-hardware.nix b/nixos/modules/hardware/steam-hardware.nix index aed008b588e8..bd1e6ae8f708 100644 --- a/nixos/modules/hardware/steam-hardware.nix +++ b/nixos/modules/hardware/steam-hardware.nix @@ -16,7 +16,7 @@ in config = lib.mkIf cfg.enable { services.udev.packages = [ - pkgs.steamPackages.steam + pkgs.steam-unwrapped ]; # The uinput module needs to be loaded in order to trigger the udev rules diff --git a/nixos/modules/hardware/system-76.nix b/nixos/modules/hardware/system-76.nix index ca40ee0ebb37..d81c59fbc924 100644 --- a/nixos/modules/hardware/system-76.nix +++ b/nixos/modules/hardware/system-76.nix @@ -36,7 +36,7 @@ let }; }; - power-pkg = config.boot.kernelPackages.system76-power; + power-pkg = pkgs.system76-power; powerConfig = mkIf cfg.power-daemon.enable { # Make system76-power usable by root from the command line. environment.systemPackages = [ power-pkg ]; diff --git a/nixos/modules/hardware/tuxedo-drivers.nix b/nixos/modules/hardware/tuxedo-drivers.nix new file mode 100644 index 000000000000..aa951782dbc0 --- /dev/null +++ b/nixos/modules/hardware/tuxedo-drivers.nix @@ -0,0 +1,35 @@ +{ config, lib, ... }: +let + cfg = config.hardware.tuxedo-drivers; + tuxedo-drivers = config.boot.kernelPackages.tuxedo-drivers; +in +{ + imports = [ + (lib.mkRenamedOptionModule + [ + "hardware" + "tuxedo-keyboard" + ] + [ + "hardware" + "tuxedo-drivers" + ] + ) + ]; + + options.hardware.tuxedo-drivers = { + enable = lib.mkEnableOption '' + The tuxedo-drivers driver enables access to the following on TUXEDO notebooks: + - Driver for Fn-keys + - SysFS control of brightness/color/mode for most TUXEDO keyboards + - Hardware I/O driver for TUXEDO Control Center + + For more inforation it is best to check at the source code description: <https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers> + ''; + }; + + config = lib.mkIf cfg.enable { + boot.kernelModules = [ "tuxedo_keyboard" ]; + boot.extraModulePackages = [ tuxedo-drivers ]; + }; +} diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix deleted file mode 100644 index 01ec486fb88f..000000000000 --- a/nixos/modules/hardware/tuxedo-keyboard.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.hardware.tuxedo-keyboard; - tuxedo-keyboard = config.boot.kernelPackages.tuxedo-keyboard; -in - { - options.hardware.tuxedo-keyboard = { - enable = lib.mkEnableOption '' - the tuxedo-keyboard driver. - - To configure the driver, pass the options to the {option}`boot.kernelParams` configuration. - There are several parameters you can change. It's best to check at the source code description which options are supported. - You can find all the supported parameters at: <https://github.com/tuxedocomputers/tuxedo-keyboard#kernelparam> - - In order to use the `custom` lighting with the maximumg brightness and a color of `0xff0a0a` one would put pass {option}`boot.kernelParams` like this: - - ``` - boot.kernelParams = [ - "tuxedo_keyboard.mode=0" - "tuxedo_keyboard.brightness=255" - "tuxedo_keyboard.color_left=0xff0a0a" - ]; - ``` - ''; - }; - - config = lib.mkIf cfg.enable - { - boot.kernelModules = ["tuxedo_keyboard"]; - boot.extraModulePackages = [ tuxedo-keyboard ]; - }; - } diff --git a/nixos/modules/hardware/uinput.nix b/nixos/modules/hardware/uinput.nix index 55e86bfa6bdb..1845d9cfe565 100644 --- a/nixos/modules/hardware/uinput.nix +++ b/nixos/modules/hardware/uinput.nix @@ -1,8 +1,9 @@ -{ config, pkgs, lib, ... }: +{ config, lib, ... }: let cfg = config.hardware.uinput; -in { +in +{ options.hardware.uinput = { enable = lib.mkEnableOption "uinput support"; }; @@ -10,7 +11,7 @@ in { config = lib.mkIf cfg.enable { boot.kernelModules = [ "uinput" ]; - users.groups.uinput = {}; + users.groups.uinput.gid = config.ids.gids.uinput; services.udev.extraRules = '' SUBSYSTEM=="misc", KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput" diff --git a/nixos/modules/hardware/usb-storage.nix b/nixos/modules/hardware/usb-storage.nix index 8d145ce51c00..32c1707b2716 100644 --- a/nixos/modules/hardware/usb-storage.nix +++ b/nixos/modules/hardware/usb-storage.nix @@ -1,8 +1,9 @@ { config, lib, pkgs, ... }: + { - options.hardware.usbStorage.manageStartStop = lib.mkOption { + options.hardware.usbStorage.manageShutdown = lib.mkOption { type = lib.types.bool; - default = true; + default = false; description = '' Enable this option to gracefully spin-down external storage during shutdown. If you suspect improper head parking after poweroff, install `smartmontools` and check @@ -10,9 +11,11 @@ ''; }; - config = lib.mkIf config.hardware.usbStorage.manageStartStop { + config = lib.mkIf config.hardware.usbStorage.manageShutdown { services.udev.extraRules = '' - ACTION=="add|change", SUBSYSTEM=="scsi_disk", DRIVERS=="usb-storage", ATTR{manage_system_start_stop}="1" + ACTION=="add|change", SUBSYSTEM=="scsi_disk", DRIVERS=="usb-storage|uas", ATTR{manage_shutdown}="1" ''; }; + + imports = [(lib.mkRenamedOptionModule [ "hardware" "usbStorage" "manageStartStop" ] [ "hardware" "usbStorage" "manageShutdown" ])]; } diff --git a/nixos/modules/hardware/video/webcam/ipu6.nix b/nixos/modules/hardware/video/webcam/ipu6.nix index ae54e24ee2da..803902530dd9 100644 --- a/nixos/modules/hardware/video/webcam/ipu6.nix +++ b/nixos/modules/hardware/video/webcam/ipu6.nix @@ -26,9 +26,9 @@ in config = mkIf cfg.enable { - # Module is upstream as of 6.10 - boot.extraModulePackages = with config.boot.kernelPackages; - optional (kernelOlder "6.10") ipu6-drivers; + # Module is upstream as of 6.10, + # but still needs various out-of-tree i2c and the `intel-ipu6-psys` kernel driver + boot.extraModulePackages = with config.boot.kernelPackages; [ ipu6-drivers ]; hardware.firmware = with pkgs; [ ipu6-camera-bins diff --git a/nixos/modules/i18n/input-method/default.md b/nixos/modules/i18n/input-method/default.md index 8101717a4da7..b7312348cd7a 100644 --- a/nixos/modules/i18n/input-method/default.md +++ b/nixos/modules/i18n/input-method/default.md @@ -40,6 +40,7 @@ Available extra IBus engines are: - Anthy (`ibus-engines.anthy`): Anthy is a system for Japanese input method. It converts Hiragana text to Kana Kanji mixed text. - Hangul (`ibus-engines.hangul`): Korean input method. + - libpinyin (`ibus-engines.libpinyin`): A Chinese input method. - m17n (`ibus-engines.m17n`): m17n is an input method that uses input methods and corresponding icons in the m17n database. - mozc (`ibus-engines.mozc`): A Japanese input method from diff --git a/nixos/modules/image/repart-image.nix b/nixos/modules/image/repart-image.nix index 41f68a0282ac..bbcf4815aced 100644 --- a/nixos/modules/image/repart-image.nix +++ b/nixos/modules/image/repart-image.nix @@ -30,7 +30,7 @@ , imageFileBasename , compression , fileSystems -, partitionsJSON +, finalPartitions , split , seed , definitionsDirectory @@ -101,6 +101,11 @@ in ) // { __structuredAttrs = true; + + # the image will be self-contained so we can drop references + # to the closure that was used to build it + unsafeDiscardReferences.out = true; + nativeBuildInputs = [ systemd fakeroot @@ -110,7 +115,9 @@ in env = mkfsEnv; - inherit partitionsJSON definitionsDirectory; + inherit finalPartitions definitionsDirectory; + + partitionsJSON = builtins.toJSON finalAttrs.finalPartitions; # relative path to the repart definitions that are read by systemd-repart finalRepartDefinitions = "repart.d"; @@ -136,7 +143,7 @@ in patchPhase = '' runHook prePatch - amendedRepartDefinitionsDir=$(${amendRepartDefinitions} $partitionsJSON $definitionsDirectory) + amendedRepartDefinitionsDir=$(${amendRepartDefinitions} <(echo "$partitionsJSON") $definitionsDirectory) ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions runHook postPatch diff --git a/nixos/modules/image/repart-verity-store.nix b/nixos/modules/image/repart-verity-store.nix index 3f341ca421f2..d270dc069f34 100644 --- a/nixos/modules/image/repart-verity-store.nix +++ b/nixos/modules/image/repart-verity-store.nix @@ -117,10 +117,6 @@ in # do not prepare the ESP, this is done in the final image systemdRepartFlags = previousAttrs.systemdRepartFlags ++ [ "--defer-partitions=esp" ]; - - # the image will be self-contained so we can drop references - # to the closure that was used to build it - unsafeDiscardReferences.out = true; } ); @@ -163,21 +159,20 @@ in createEmpty = false; }).overrideAttrs ( - finalAttrs: previousAttrs: - let - copyUki = "CopyFiles=${config.system.build.uki}/${config.system.boot.loader.ukiFile}:${cfg.ukiPath}"; - in - { + finalAttrs: previousAttrs: { + # add entry to inject UKI into ESP + finalPartitions = lib.recursiveUpdate previousAttrs.finalPartitions { + ${cfg.partitionIds.esp}.contents = { + "${cfg.ukiPath}".source = "${config.system.build.uki}/${config.system.boot.loader.ukiFile}"; + }; + }; + nativeBuildInputs = previousAttrs.nativeBuildInputs ++ [ pkgs.systemdUkify verityHashCheck + pkgs.jq ]; - postPatch = '' - # add entry to inject UKI into ESP - echo '${copyUki}' >> $finalRepartDefinitions/${cfg.partitionIds.esp}.conf - ''; - preBuild = '' # check that we build the final image with the same intermediate image for # which the injected UKI was built by comparing the UKI cmdline with the repart output @@ -194,9 +189,23 @@ in chmod +w ${config.image.repart.imageFileBasename}.raw ''; - # the image will be self-contained so we can drop references - # to the closure that was used to build it - unsafeDiscardReferences.out = true; + # replace "TBD" with the original roothash values + preInstall = '' + mv -v repart-output{.json,_orig.json} + + jq --slurp --indent -1 \ + '.[0] as $intermediate | .[1] as $final + | $intermediate | map(select(.roothash != null) | { "uuid":.uuid,"roothash":.roothash }) as $uuids + | $final + $uuids + | group_by(.uuid) + | map(add) + | sort_by(.offset)' \ + ${config.system.build.intermediateImage}/repart-output.json \ + repart-output_orig.json \ + > repart-output.json + + rm -v repart-output_orig.json + ''; } ); }; diff --git a/nixos/modules/image/repart.nix b/nixos/modules/image/repart.nix index b2f03d96ad35..544779cd6e9b 100644 --- a/nixos/modules/image/repart.nix +++ b/nixos/modules/image/repart.nix @@ -318,14 +318,12 @@ in format (lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions); - partitionsJSON = pkgs.writeText "partitions.json" (builtins.toJSON cfg.finalPartitions); - mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions; in pkgs.callPackage ./repart-image.nix { systemd = cfg.package; - inherit (cfg) name version imageFileBasename compression split seed sectorSize; - inherit fileSystems definitionsDirectory partitionsJSON mkfsEnv; + inherit (cfg) name version imageFileBasename compression split seed sectorSize finalPartitions; + inherit fileSystems definitionsDirectory mkfsEnv; }; meta.maintainers = with lib.maintainers; [ nikstur willibutz ]; diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix index 541bc5b0189f..63abf88cab09 100644 --- a/nixos/modules/installer/cd-dvd/iso-image.nix +++ b/nixos/modules/installer/cd-dvd/iso-image.nix @@ -8,7 +8,7 @@ let * to a menuentry for use in grub. * * * defaults: {name, image, params, initrd} - * * options: [ option... ] + * * options: [ option... ] * * option: {name, params, class} */ menuBuilderGrub2 = @@ -772,9 +772,10 @@ in # here and it causes a cyclic dependency. boot.loader.grub.enable = false; - environment.systemPackages = [ grubPkgs.grub2 grubPkgs.grub2_efi ] + environment.systemPackages = [ grubPkgs.grub2 ] ++ lib.optional (config.isoImage.makeBiosBootable) pkgs.syslinux ; + system.extraDependencies = [ grubPkgs.grub2_efi ]; # In stage 1 of the boot, mount the CD as the root FS by label so # that we don't need to know its device. We pass the label of the diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix index 167c2b521893..adcc10316fea 100644 --- a/nixos/modules/installer/netboot/netboot.nix +++ b/nixos/modules/installer/netboot/netboot.nix @@ -9,12 +9,7 @@ with lib; options = { netboot.squashfsCompression = mkOption { - default = with pkgs.stdenv.hostPlatform; "xz -Xdict-size 100% " - + lib.optionalString isx86 "-Xbcj x86" - # Untested but should also reduce size for these platforms - + lib.optionalString isAarch "-Xbcj arm" - + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc" - + lib.optionalString (isSparc) "-Xbcj sparc"; + default = "zstd -Xcompression-level 19"; description = '' Compression settings to use for the squashfs nix store. ''; diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix index 5e23e8dda432..36ce0fb728f0 100644 --- a/nixos/modules/installer/tools/nix-fallback-paths.nix +++ b/nixos/modules/installer/tools/nix-fallback-paths.nix @@ -1,7 +1,8 @@ { - x86_64-linux = "/nix/store/mczjdfprd67mdn90488854bf6b3nkp8j-nix-2.18.7"; - i686-linux = "/nix/store/qqll8zrx7ibdx34ry1ijanqdpdpnibbc-nix-2.18.7"; - aarch64-linux = "/nix/store/lwysvjn745fwsz8nv13zzsfq0dhiyxlp-nix-2.18.7"; - x86_64-darwin = "/nix/store/frzvlvzzj7hwvg8p0y0ivl27430nxhfy-nix-2.18.7"; - aarch64-darwin = "/nix/store/43dp3pl3k95gszp1hl9sjm22gly65sxi-nix-2.18.7"; + x86_64-linux = "/nix/store/hdy82qidsybc3fg561pqfwagv44vschb-nix-2.24.10"; + i686-linux = "/nix/store/dyx4p79q6blva585bf90wbjjb7iyq8ra-nix-2.24.10"; + aarch64-linux = "/nix/store/30gnc15nig1awa11vii9yz3z8518rnr3-nix-2.24.10"; + riscv64-linux = "/nix/store/bxc2pyp1vj8kr77khyx5nglw73jqb98w-nix-riscv64-unknown-linux-gnu-2.24.10"; + x86_64-darwin = "/nix/store/6mrkghigrci6dz2lnncqpgf80yi8gl7h-nix-2.24.10"; + aarch64-darwin = "/nix/store/3f81gjiv836rjmsb29zab0pbjwf9did8-nix-2.24.10"; } diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index 0a01d6bb9b69..0887dd56a67e 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -106,11 +106,12 @@ let # isNormalUser = true; # extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. # packages = with pkgs; [ - # firefox # tree # ]; # }; + # programs.firefox.enable = true; + # List packages installed in system profile. To search, run: # \$ nix search wget # environment.systemPackages = with pkgs; [ @@ -216,7 +217,7 @@ in imports = let mkToolModule = { name, package ? pkgs.${name} }: { config, ... }: { options.system.tools.${name}.enable = lib.mkEnableOption "${name} script" // { - default = true; + default = config.nix.enable && ! config.system.disableInstallerTools; internal = true; }; @@ -227,34 +228,21 @@ in in [ (mkToolModule { name = "nixos-build-vms"; }) (mkToolModule { name = "nixos-enter"; }) - (mkToolModule { name = "nixos-generate-config"; package = nixos-generate-config; }) - (mkToolModule { name = "nixos-install"; package = nixos-install; }) + (mkToolModule { name = "nixos-generate-config"; package = config.system.build.nixos-generate-config; }) + (mkToolModule { name = "nixos-install"; package = config.system.build.nixos-install; }) (mkToolModule { name = "nixos-option"; }) - (mkToolModule { name = "nixos-rebuild"; package = nixos-rebuild; }) + (mkToolModule { name = "nixos-rebuild"; package = config.system.build.nixos-rebuild; }) (mkToolModule { name = "nixos-version"; package = nixos-version; }) ]; - config = lib.mkMerge [ - (lib.mkIf config.system.disableInstallerTools { - system.tools = { - nixos-build-vms.enable = false; - nixos-enter.enable = false; - nixos-generate-config.enable = false; - nixos-install.enable = false; - nixos-option.enable = false; - nixos-rebuild.enable = false; - nixos-version.enable = false; - }; - }) - { - documentation.man.man-db.skipPackages = [ nixos-version ]; + config = { + documentation.man.man-db.skipPackages = [ nixos-version ]; - # These may be used in auxiliary scripts (ie not part of toplevel), so they are defined unconditionally. - system.build = { - inherit nixos-generate-config nixos-install nixos-rebuild; - nixos-option = lib.warn "Accessing nixos-option through `config.system.build` is deprecated, use `pkgs.nixos-option` instead." pkgs.nixos-option; - nixos-enter = lib.warn "Accessing nixos-enter through `config.system.build` is deprecated, use `pkgs.nixos-enter` instead." pkgs.nixos-enter; - }; - } - ]; + # These may be used in auxiliary scripts (ie not part of toplevel), so they are defined unconditionally. + system.build = { + inherit nixos-generate-config nixos-install nixos-rebuild; + nixos-option = lib.warn "Accessing nixos-option through `config.system.build` is deprecated, use `pkgs.nixos-option` instead." pkgs.nixos-option; + nixos-enter = lib.warn "Accessing nixos-enter through `config.system.build` is deprecated, use `pkgs.nixos-enter` instead." pkgs.nixos-enter; + }; + }; } diff --git a/nixos/modules/installer/virtualbox-demo.nix b/nixos/modules/installer/virtualbox-demo.nix deleted file mode 100644 index 289a8cf9e506..000000000000 --- a/nixos/modules/installer/virtualbox-demo.nix +++ /dev/null @@ -1,61 +0,0 @@ -{ lib, ... }: - -with lib; - -{ - imports = - [ ../virtualisation/virtualbox-image.nix - ../installer/cd-dvd/channel.nix - ../profiles/demo.nix - ../profiles/clone-config.nix - ]; - - # FIXME: UUID detection is currently broken - boot.loader.grub.fsIdentifier = "provided"; - - # Allow mounting of shared folders. - users.users.demo.extraGroups = [ "vboxsf" ]; - - # Add some more video drivers to give X11 a shot at working in - # VMware and QEMU. - services.xserver.videoDrivers = mkOverride 40 [ "virtualbox" "vmware" "cirrus" "vesa" "modesetting" ]; - - powerManagement.enable = false; - system.stateVersion = lib.mkDefault lib.trivial.release; - - installer.cloneConfigExtra = '' - # Let demo build as a trusted user. - # nix.settings.trusted-users = [ "demo" ]; - - # Mount a VirtualBox shared folder. - # This is configurable in the VirtualBox menu at - # Machine / Settings / Shared Folders. - # fileSystems."/mnt" = { - # fsType = "vboxsf"; - # device = "nameofdevicetomount"; - # options = [ "rw" ]; - # }; - - # By default, the NixOS VirtualBox demo image includes SDDM and Plasma. - # If you prefer another desktop manager or display manager, you may want - # to disable the default. - # services.xserver.desktopManager.plasma5.enable = lib.mkForce false; - # services.displayManager.sddm.enable = lib.mkForce false; - - # Enable GDM/GNOME by uncommenting above two lines and two lines below. - # services.xserver.displayManager.gdm.enable = true; - # services.xserver.desktopManager.gnome.enable = true; - - # Set your time zone. - # time.timeZone = "Europe/Amsterdam"; - - # List packages installed in system profile. To search, run: - # \$ nix search wget - # environment.systemPackages = with pkgs; [ - # wget vim - # ]; - - # Enable the OpenSSH daemon. - # services.openssh.enable = true; - ''; -} diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index 1bdaf713ab5d..814ee77c0119 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -35,7 +35,6 @@ in }; - config = { ids.uids = { @@ -111,7 +110,7 @@ in postgres = 71; #vboxusers = 72; # unused #vboxsf = 73; # unused - smbguest = 74; # unused + smbguest = 74; # unused varnish = 75; datadog = 76; lighttpd = 77; @@ -237,7 +236,7 @@ in riemanntools = 203; subsonic = 204; # riak = 205; # unused, remove 2022-07-22 - #shout = 206; # dynamically allocated as of 2021-09-18 + #shout = 206; # dynamically allocated as of 2021-09-18, module removed 2024-10-19 gateone = 207; namecoin = 208; #lxd = 210; # unused @@ -290,14 +289,14 @@ in postgrey = 258; # hound = 259; # unused, removed 2023-11-21 leaps = 260; - ipfs = 261; + ipfs = 261; # stanchion = 262; # unused, removed 2020-10-14 # riak-cs = 263; # unused, removed 2020-10-14 infinoted = 264; sickbeard = 265; headphones = 266; # couchpotato = 267; # unused, removed 2022-01-01 - gogs = 268; + # gogs = 268; # unused, removed in 2024-10-12 #pdns-recursor = 269; # dynamically allocated as of 2020-20-18 #kresd = 270; # switched to "knot-resolver" with dynamic ID rpc = 271; @@ -357,7 +356,24 @@ in localtimed = 325; automatic-timezoned = 326; - # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! + # When adding a uid, make sure it doesn't match an existing gid. + # + # !!! Don't use uids above "399"! !!! + # + # The reason behind this restriction is that, NixOS by default allocates + # system user UIDs/GIDs in the range of `400..999`. System users/groups + # created using command like `useradd` will have UID and GID in this range[1]. + # + # If a newly added ID goes beyond "399", it may conflict with existing + # system user or group of the same id in someone else's NixOS. + # This could break their system and make that person upset for a whole day. + # + # Sidenote: the default is defined in `shadow` module[2], and the relavent change + # was made way back in 2014[3]. + # + # [1]: https://man7.org/linux/man-pages/man5/login.defs.5.html#:~:text=SYS_UID_MAX%20(number)%2C%20SYS_UID_MIN%20(number) + # [2]: <nixos/modules/programs/shadow.nix> + # [3]: https://github.com/NixOS/nixpkgs/commit/0e23a175de3687df8232fe118cbe87f04228ff28 nixbld = 30000; # start of range of uids nobody = 65534; @@ -436,7 +452,7 @@ in postgres = 71; vboxusers = 72; vboxsf = 73; - smbguest = 74; # unused + smbguest = 74; # unused varnish = 75; datadog = 76; lighttpd = 77; @@ -608,7 +624,7 @@ in sickbeard = 265; headphones = 266; # couchpotato = 267; # unused, removed 2022-01-01 - gogs = 268; + # gogs = 268; # unused, removed in 2024-10-12 #kresd = 270; # switched to "knot-resolver" with dynamic ID #rpc = 271; # unused #geoip = 272; # unused @@ -666,10 +682,28 @@ in rstudio-server = 324; localtimed = 325; automatic-timezoned = 326; + uinput = 327; # When adding a gid, make sure it doesn't match an existing # uid. Users and groups with the same name should have equal - # uids and gids. Also, don't use gids above 399! + # uids and gids. + # + # !!! Don't use gids above "399"! !!! + # + # The reason behind this restriction is that, NixOS by default allocates + # system user UIDs/GIDs in the range of `400..999`. System users/groups + # created using command like `useradd` will have UID and GID in this range[1]. + # + # If a newly added ID goes beyond "399", it may conflict with existing + # system user or group of the same id in someone else's NixOS. + # This could break their system and make that person upset for a whole day. + # + # Sidenote: the default is defined in `shadow` module[2], and the relavent change + # was made way back in 2014[3]. + # + # [1]: https://man7.org/linux/man-pages/man5/login.defs.5.html#:~:text=SYS_UID_MAX%20(number)%2C%20SYS_UID_MIN%20(number) + # [2]: <nixos/modules/programs/shadow.nix> + # [3]: https://github.com/NixOS/nixpkgs/commit/0e23a175de3687df8232fe118cbe87f04228ff28 # For exceptional cases where you really need a gid above 399, leave a # comment stating why. diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix index db917f73a064..5fe9b6c1c700 100644 --- a/nixos/modules/misc/version.nix +++ b/nixos/modules/misc/version.nix @@ -22,21 +22,26 @@ let { NAME = "${cfg.distroName}"; ID = "${cfg.distroId}"; + ID_LIKE = optionalString (!isNixos) "nixos"; + VENDOR_NAME = cfg.vendorName; VERSION = "${cfg.release} (${cfg.codeName})"; VERSION_CODENAME = toLower cfg.codeName; VERSION_ID = cfg.release; BUILD_ID = cfg.version; PRETTY_NAME = "${cfg.distroName} ${cfg.release} (${cfg.codeName})"; + CPE_NAME = "cpe:/o:${cfg.vendorId}:${cfg.distroId}:${cfg.release}"; LOGO = "nix-snowflake"; HOME_URL = optionalString isNixos "https://nixos.org/"; + VENDOR_URL = optionalString isNixos "https://nixos.org/"; DOCUMENTATION_URL = optionalString isNixos "https://nixos.org/learn.html"; SUPPORT_URL = optionalString isNixos "https://nixos.org/community.html"; BUG_REPORT_URL = optionalString isNixos "https://github.com/NixOS/nixpkgs/issues"; ANSI_COLOR = optionalString isNixos "1;34"; IMAGE_ID = optionalString (config.system.image.id != null) config.system.image.id; IMAGE_VERSION = optionalString (config.system.image.version != null) config.system.image.version; - } // lib.optionalAttrs (cfg.variant_id != null) { - VARIANT_ID = cfg.variant_id; + VARIANT = optionalString (cfg.variantName != null) cfg.variantName; + VARIANT_ID = optionalString (cfg.variant_id != null) cfg.variant_id; + DEFAULT_HOSTNAME = config.networking.fqdnOrHostName; }; initrdReleaseContents = (removeAttrs osReleaseContents [ "BUILD_ID" ]) // { @@ -116,6 +121,27 @@ in description = "A lower-case string identifying a specific variant or edition of the operating system"; example = "installer"; }; + + variantName = mkOption { + type = types.nullOr types.str; + default = null; + description = "A string identifying a specific variant or edition of the operating system suitable for presentation to the user"; + example = "NixOS Installer Image"; + }; + + vendorId = mkOption { + internal = true; + type = types.str; + default = "nixos"; + description = "The id of the operating system vendor"; + }; + + vendorName = mkOption { + internal = true; + type = types.str; + default = "NixOS"; + description = "The name of the operating system vendor"; + }; }; image = { diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4a97be04fe7f..0537dbf3ad1c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -48,6 +48,7 @@ ./config/zram.nix ./hardware/acpilight.nix ./hardware/all-firmware.nix + ./hardware/apple-touchbar.nix ./hardware/bladeRF.nix ./hardware/brillo.nix ./hardware/ckb-next.nix @@ -97,7 +98,7 @@ ./hardware/sensor/iio.nix ./hardware/steam-hardware.nix ./hardware/system-76.nix - ./hardware/tuxedo-keyboard.nix + ./hardware/tuxedo-drivers.nix ./hardware/ubertooth.nix ./hardware/uinput.nix ./hardware/uni-sync.nix @@ -171,6 +172,7 @@ ./programs/cpu-energy-meter.nix ./programs/command-not-found/command-not-found.nix ./programs/coolercontrol.nix + ./programs/corefreq.nix ./programs/criu.nix ./programs/darling.nix ./programs/dconf.nix @@ -215,7 +217,7 @@ ./programs/iftop.nix ./programs/i3lock.nix ./programs/iio-hyprland.nix - ./programs/immersed-vr.nix + ./programs/immersed.nix ./programs/iotop.nix ./programs/java.nix ./programs/joycond-cemuhook.nix @@ -259,6 +261,7 @@ ./programs/oblogout.nix ./programs/oddjobd.nix ./programs/openvpn3.nix + ./programs/obs-studio.nix ./programs/partition-manager.nix ./programs/plotinus.nix ./programs/pqos-wrapper.nix @@ -310,6 +313,7 @@ ./programs/wayland/hyprland.nix ./programs/wayland/labwc.nix ./programs/wayland/miracle-wm.nix + ./programs/wayland/niri.nix ./programs/wayland/river.nix ./programs/wayland/sway.nix ./programs/wayland/uwsm.nix @@ -516,10 +520,10 @@ ./services/desktops/gnome/gnome-remote-desktop.nix ./services/desktops/gnome/gnome-settings-daemon.nix ./services/desktops/gnome/gnome-user-share.nix + ./services/desktops/gnome/localsearch.nix ./services/desktops/gnome/rygel.nix ./services/desktops/gnome/sushi.nix - ./services/desktops/gnome/tracker-miners.nix - ./services/desktops/gnome/tracker.nix + ./services/desktops/gnome/tinysparql.nix ./services/desktops/gsignond.nix ./services/desktops/gvfs.nix ./services/desktops/malcontent.nix @@ -592,6 +596,7 @@ ./services/hardware/irqbalance.nix ./services/hardware/joycond.nix ./services/hardware/kanata.nix + ./services/hardware/kmonad.nix ./services/hardware/lcd.nix ./services/hardware/libinput.nix ./services/hardware/lirc.nix @@ -659,6 +664,7 @@ ./services/logging/ulogd.nix ./services/mail/automx2.nix ./services/mail/clamsmtp.nix + ./services/mail/cyrus-imap.nix ./services/mail/davmail.nix ./services/mail/dkimproxy-out.nix ./services/mail/dovecot.nix @@ -708,7 +714,6 @@ ./services/matrix/mjolnir.nix ./services/matrix/mx-puppet-discord.nix ./services/matrix/pantalaimon.nix - ./services/matrix/matrix-sliding-sync.nix ./services/matrix/synapse.nix ./services/misc/airsonic.nix ./services/misc/amazon-ssm-agent.nix @@ -758,7 +763,6 @@ ./services/misc/gitlab.nix ./services/misc/gitolite.nix ./services/misc/gitweb.nix - ./services/misc/gogs.nix ./services/misc/gollum.nix ./services/misc/gotenberg.nix ./services/misc/gpsd.nix @@ -882,6 +886,7 @@ ./services/monitoring/datadog-agent.nix ./services/monitoring/do-agent.nix ./services/monitoring/fusion-inventory.nix + ./services/monitoring/gatus.nix ./services/monitoring/goss.nix ./services/monitoring/grafana-agent.nix ./services/monitoring/grafana-image-renderer.nix @@ -964,6 +969,7 @@ ./services/network-filesystems/rsyncd.nix ./services/network-filesystems/samba-wsdd.nix ./services/network-filesystems/samba.nix + ./services/network-filesystems/saunafs.nix ./services/network-filesystems/tahoe.nix ./services/network-filesystems/u9fs.nix ./services/network-filesystems/webdav-server-rs.nix @@ -978,6 +984,7 @@ ./services/networking/aria2.nix ./services/networking/asterisk.nix ./services/networking/atftpd.nix + ./services/networking/atticd.nix ./services/networking/autossh.nix ./services/networking/avahi-daemon.nix ./services/networking/babeld.nix @@ -1029,6 +1036,7 @@ ./services/networking/expressvpn.nix ./services/networking/fakeroute.nix ./services/networking/fastnetmon-advanced.nix + ./services/networking/fedimintd.nix ./services/networking/ferm.nix ./services/networking/firefox-syncserver.nix ./services/networking/fireqos.nix @@ -1192,6 +1200,7 @@ ./services/networking/scion/scion-daemon.nix ./services/networking/scion/scion-dispatcher.nix ./services/networking/scion/scion-router.nix + ./services/networking/scion/scion-ip-gateway.nix ./services/networking/seafile.nix ./services/networking/searx.nix ./services/networking/shadowsocks.nix @@ -1199,7 +1208,6 @@ ./services/networking/shellhub-agent.nix ./services/networking/shorewall.nix ./services/networking/shorewall6.nix - ./services/networking/shout.nix ./services/networking/sing-box.nix ./services/networking/sitespeed-io.nix ./services/networking/skydns.nix @@ -1227,6 +1235,7 @@ ./services/networking/syncthing.nix ./services/networking/tailscale.nix ./services/networking/tailscale-auth.nix + ./services/networking/tailscale-derper.nix ./services/networking/tayga.nix ./services/networking/tcpcrypt.nix ./services/networking/teamspeak3.nix @@ -1250,6 +1259,7 @@ ./services/networking/uptermd.nix ./services/networking/v2ray.nix ./services/networking/v2raya.nix + ./services/networking/veilid.nix ./services/networking/vdirsyncer.nix ./services/networking/vsftpd.nix ./services/networking/wasabibackend.nix @@ -1270,6 +1280,7 @@ ./services/networking/xray.nix ./services/networking/xrdp.nix ./services/networking/yggdrasil.nix + ./services/networking/zapret.nix ./services/networking/zerobin.nix ./services/networking/zeronet.nix ./services/networking/zerotierone.nix @@ -1346,11 +1357,13 @@ ./services/system/nscd.nix ./services/system/saslauthd.nix ./services/system/self-deploy.nix + ./services/system/swapspace.nix ./services/system/systembus-notify.nix ./services/system/systemd-lock-handler.nix ./services/system/uptimed.nix ./services/system/userborn.nix ./services/system/zram-generator.nix + ./services/torrent/bitmagnet.nix ./services/torrent/deluge.nix ./services/torrent/flexget.nix ./services/torrent/flood.nix @@ -1370,18 +1383,16 @@ ./services/video/mirakurun.nix ./services/video/photonvision.nix ./services/video/mediamtx.nix - ./services/video/unifi-video.nix ./services/video/v4l2-relayd.nix + ./services/video/wivrn.nix ./services/wayland/cage.nix ./services/wayland/hypridle.nix ./services/web-apps/akkoma.nix ./services/web-apps/alps.nix ./services/web-apps/anuko-time-tracker.nix ./services/web-apps/artalk.nix - ./services/web-apps/atlassian/confluence.nix - ./services/web-apps/atlassian/crowd.nix - ./services/web-apps/atlassian/jira.nix ./services/web-apps/audiobookshelf.nix + ./services/web-apps/bluemap.nix ./services/web-apps/bookstack.nix ./services/web-apps/c2fmzq-server.nix ./services/web-apps/calibre-web.nix @@ -1391,6 +1402,7 @@ ./services/web-apps/chatgpt-retrieval-plugin.nix ./services/web-apps/cloudlog.nix ./services/web-apps/code-server.nix + ./services/web-apps/collabora-online.nix ./services/web-apps/commafeed.nix ./services/web-apps/convos.nix ./services/web-apps/crabfit.nix @@ -1422,6 +1434,7 @@ ./services/web-apps/goatcounter.nix ./services/web-apps/guacamole-client.nix ./services/web-apps/guacamole-server.nix + ./services/web-apps/hatsu.nix ./services/web-apps/healthchecks.nix ./services/web-apps/hedgedoc.nix ./services/web-apps/hledger-web.nix @@ -1430,6 +1443,7 @@ ./services/web-apps/icingaweb2/icingaweb2.nix ./services/web-apps/icingaweb2/module-monitoring.nix ./services/web-apps/ifm.nix + ./services/web-apps/immich.nix ./services/web-apps/invidious.nix ./services/web-apps/invoiceplane.nix ./services/web-apps/isso.nix @@ -1458,6 +1472,7 @@ ./services/web-apps/netbox.nix ./services/web-apps/nextcloud.nix ./services/web-apps/nextcloud-notify_push.nix + ./services/web-apps/nextcloud-whiteboard-server.nix ./services/web-apps/nextjs-ollama-llm-ui.nix ./services/web-apps/nexus.nix ./services/web-apps/nifi.nix @@ -1465,6 +1480,7 @@ ./services/web-apps/ocis.nix ./services/web-apps/onlyoffice.nix ./services/web-apps/openvscode-server.nix + ./services/web-apps/mediagoblin.nix ./services/web-apps/mobilizon.nix ./services/web-apps/openwebrx.nix ./services/web-apps/outline.nix @@ -1480,6 +1496,7 @@ ./services/web-apps/powerdns-admin.nix ./services/web-apps/pretalx.nix ./services/web-apps/pretix.nix + ./services/web-apps/privatebin.nix ./services/web-apps/prosody-filer.nix ./services/web-apps/rimgo.nix ./services/web-apps/rutorrent.nix @@ -1511,7 +1528,6 @@ ./services/web-apps/zitadel.nix ./services/web-servers/agate.nix ./services/web-servers/apache-httpd/default.nix - ./services/web-servers/bluemap.nix ./services/web-servers/caddy/default.nix ./services/web-servers/darkhttpd.nix ./services/web-servers/fcgiwrap.nix @@ -1533,6 +1549,7 @@ ./services/web-servers/phpfpm/default.nix ./services/web-servers/pomerium.nix ./services/web-servers/rustus.nix + ./services/web-servers/send.nix ./services/web-servers/stargazer.nix ./services/web-servers/static-web-server.nix ./services/web-servers/tomcat.nix @@ -1639,6 +1656,7 @@ ./system/boot/systemd/sysupdate.nix ./system/boot/systemd/sysusers.nix ./system/boot/systemd/tmpfiles.nix + ./system/boot/systemd/tpm2.nix ./system/boot/systemd/user.nix ./system/boot/systemd/userdbd.nix ./system/boot/systemd/homed.nix diff --git a/nixos/modules/profiles/all-hardware.nix b/nixos/modules/profiles/all-hardware.nix index 249b767593f2..970b76cfbfdf 100644 --- a/nixos/modules/profiles/all-hardware.nix +++ b/nixos/modules/profiles/all-hardware.nix @@ -52,7 +52,7 @@ in # VMware support. "mptspi" "vmxnet3" "vsock" ] ++ lib.optional platform.isx86 "vmw_balloon" - ++ lib.optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ + ++ lib.optionals (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) [ "vmw_vmci" "vmwgfx" "vmw_vsock_vmci_transport" # Hyper-V support. @@ -68,7 +68,7 @@ in # Broadcom "vc4" - ] ++ lib.optionals pkgs.stdenv.isAarch64 [ + ] ++ lib.optionals pkgs.stdenv.hostPlatform.isAarch64 [ # Most of the following falls into two categories: # - early KMS / early display # - early storage (e.g. USB) support diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix index be79bd205cb3..943f9d8e3106 100644 --- a/nixos/modules/profiles/base.nix +++ b/nixos/modules/profiles/base.nix @@ -19,13 +19,7 @@ pkgs.cryptsetup # needed for dm-crypt volumes # Some text editors. - (pkgs.vim.customize { - name = "vim"; - vimrcConfig.packages.default = { - start = [ pkgs.vimPlugins.vim-nix ]; - }; - vimrcConfig.customRC = "syntax on"; - }) + pkgs.vim # Some networking tools. pkgs.fuse diff --git a/nixos/modules/profiles/demo.nix b/nixos/modules/profiles/demo.nix deleted file mode 100644 index 52ba40902e87..000000000000 --- a/nixos/modules/profiles/demo.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ ... }: - -{ - imports = [ ./graphical.nix ]; - - users.users.demo = - { isNormalUser = true; - description = "Demo user account"; - extraGroups = [ "wheel" ]; - password = "demo"; - uid = 1000; - }; - - services.displayManager = { - autoLogin = { - enable = true; - user = "demo"; - }; - sddm.autoLogin.relogin = true; - }; -} diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix index bf8414e1e108..896bb6bc9fbd 100644 --- a/nixos/modules/profiles/macos-builder.nix +++ b/nixos/modules/profiles/macos-builder.nix @@ -1,268 +1,5 @@ -{ config, lib, options, ... }: - -let - keysDirectory = "/var/keys"; - - user = "builder"; - - keyType = "ed25519"; - - cfg = config.virtualisation.darwin-builder; - +let lib = import ../../../lib; in - -{ - imports = [ - ../virtualisation/qemu-vm.nix - - # Avoid a dependency on stateVersion - { - disabledModules = [ - ../virtualisation/nixos-containers.nix - ../services/x11/desktop-managers/xterm.nix - ]; - # swraid's default depends on stateVersion - config.boot.swraid.enable = false; - options.boot.isContainer = lib.mkOption { default = false; internal = true; }; - } - ]; - - options.virtualisation.darwin-builder = with lib; { - diskSize = mkOption { - default = 20 * 1024; - type = types.int; - example = 30720; - description = "The maximum disk space allocated to the runner in MB"; - }; - memorySize = mkOption { - default = 3 * 1024; - type = types.int; - example = 8192; - description = "The runner's memory in MB"; - }; - min-free = mkOption { - default = 1024 * 1024 * 1024; - type = types.int; - example = 1073741824; - description = '' - The threshold (in bytes) of free disk space left at which to - start garbage collection on the runner - ''; - }; - max-free = mkOption { - default = 3 * 1024 * 1024 * 1024; - type = types.int; - example = 3221225472; - description = '' - The threshold (in bytes) of free disk space left at which to - stop garbage collection on the runner - ''; - }; - workingDirectory = mkOption { - default = "."; - type = types.str; - example = "/var/lib/darwin-builder"; - description = '' - The working directory to use to run the script. When running - as part of a flake will need to be set to a non read-only filesystem. - ''; - }; - hostPort = mkOption { - default = 31022; - type = types.int; - example = 22; - description = '' - The localhost host port to forward TCP to the guest port. - ''; - }; - }; - - config = { - # The builder is not intended to be used interactively - documentation.enable = false; - - environment.etc = { - "ssh/ssh_host_ed25519_key" = { - mode = "0600"; - - source = ./keys/ssh_host_ed25519_key; - }; - - "ssh/ssh_host_ed25519_key.pub" = { - mode = "0644"; - - source = ./keys/ssh_host_ed25519_key.pub; - }; - }; - - # DNS fails for QEMU user networking (SLiRP) on macOS. See: - # - # https://github.com/utmapp/UTM/issues/2353 - # - # This works around that by using a public DNS server other than the DNS - # server that QEMU provides (normally 10.0.2.3) - networking.nameservers = [ "8.8.8.8" ]; - - # The linux builder is a lightweight VM for remote building; not evaluation. - nix.channel.enable = false; - # remote builder uses `nix-daemon` (ssh-ng:) or `nix-store --serve` (ssh:) - # --force: do not complain when missing - # TODO: install a store-only nix - # https://github.com/NixOS/rfcs/blob/master/rfcs/0134-nix-store-layer.md#detailed-design - environment.extraSetup = '' - rm --force $out/bin/{nix-instantiate,nix-build,nix-shell,nix-prefetch*,nix} - ''; - # Deployment is by image. - # TODO system.switch.enable = false;? - system.disableInstallerTools = true; - - nix.settings = { - auto-optimise-store = true; - - min-free = cfg.min-free; - - max-free = cfg.max-free; - - trusted-users = [ user ]; - }; - - services = { - getty.autologinUser = user; - - openssh = { - enable = true; - - authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ]; - }; - }; - - system.build.macos-builder-installer = - let - privateKey = "/etc/nix/${user}_${keyType}"; - - publicKey = "${privateKey}.pub"; - - # This installCredentials script is written so that it's as easy as - # possible for a user to audit before confirming the `sudo` - installCredentials = hostPkgs.writeShellScript "install-credentials" '' - set -euo pipefail - - KEYS="''${1}" - INSTALL=${hostPkgs.coreutils}/bin/install - "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey} - "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey} - ''; - - hostPkgs = config.virtualisation.host.pkgs; - - script = hostPkgs.writeShellScriptBin "create-builder" ( - '' - set -euo pipefail - '' + - # When running as non-interactively as part of a DarwinConfiguration the working directory - # must be set to a writeable directory. - (if cfg.workingDirectory != "." then '' - ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" - cd "${cfg.workingDirectory}" - '' else "") + '' - KEYS="''${KEYS:-./keys}" - ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" - PRIVATE_KEY="''${KEYS}/${user}_${keyType}" - PUBLIC_KEY="''${PRIVATE_KEY}.pub" - if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then - ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" - ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' - fi - if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then - (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") - fi - KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm} - ''); - - in - script.overrideAttrs (old: { - pos = __curPos; # sets meta.position to point here; see script binding above for package definition - meta = (old.meta or { }) // { - platforms = lib.platforms.darwin; - }; - passthru = (old.passthru or { }) // { - # Let users in the repl inspect the config - nixosConfig = config; - nixosOptions = options; - }; - }); - - system = { - # To prevent gratuitous rebuilds on each change to Nixpkgs - nixos.revision = null; - - stateVersion = lib.mkDefault (throw '' - The macOS linux builder should not need a stateVersion to be set, but a module - has accessed stateVersion nonetheless. - Please inspect the trace of the following command to figure out which module - has a dependency on stateVersion. - - nix-instantiate --attr darwin.linux-builder --show-trace - ''); - }; - - users.users."${user}" = { - isNormalUser = true; - }; - - security.polkit.enable = true; - - security.polkit.extraConfig = '' - polkit.addRule(function(action, subject) { - if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") { - return "yes"; - } else { - return "no"; - } - }) - ''; - - virtualisation = { - diskSize = cfg.diskSize; - - memorySize = cfg.memorySize; - - forwardPorts = [ - { from = "host"; guest.port = 22; host.port = cfg.hostPort; } - ]; - - # Disable graphics for the builder since users will likely want to run it - # non-interactively in the background. - graphics = false; - - sharedDirectories.keys = { - source = "\"$KEYS\""; - target = keysDirectory; - }; - - # If we don't enable this option then the host will fail to delegate builds - # to the guest, because: - # - # - The host will lock the path to build - # - The host will delegate the build to the guest - # - The guest will attempt to lock the same path and fail because - # the lockfile on the host is visible on the guest - # - # Snapshotting the host's /nix/store as an image isolates the guest VM's - # /nix/store from the host's /nix/store, preventing this problem. - useNixStoreImage = true; - - # Obviously the /nix/store needs to be writable on the guest in order for it - # to perform builds. - writableStore = true; - - # This ensures that anything built on the guest isn't lost when the guest is - # restarted. - writableStoreUseTmpfs = false; - - # Pass certificates from host to the guest otherwise when custom CA certificates - # are required we can't use the cached builder. - useHostCerts = true; - }; - }; -} + lib.warnIf (lib.isInOldestRelease 2411) + "nixos/modules/profiles/macos-builder.nix has moved to nixos/modules/profiles/nix-builder-vm.nix; please update your NixOS imports." + ./nix-builder-vm.nix diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix index df21b75c82b1..4ca2b8cc207f 100644 --- a/nixos/modules/profiles/minimal.nix +++ b/nixos/modules/profiles/minimal.nix @@ -29,8 +29,6 @@ with lib; programs.command-not-found.enable = mkDefault false; - programs.ssh.setXAuthLocation = mkDefault false; - services.logrotate.enable = mkDefault false; services.udisks2.enable = mkDefault false; diff --git a/nixos/modules/profiles/nix-builder-vm.nix b/nixos/modules/profiles/nix-builder-vm.nix new file mode 100644 index 000000000000..fcaca974f302 --- /dev/null +++ b/nixos/modules/profiles/nix-builder-vm.nix @@ -0,0 +1,284 @@ +/* + This profile uses NixOS to create a remote builder VM to build Linux packages, + which can be used to build packages for Linux on other operating systems; + primarily macOS. + + It contains both the relevant guest settings as well as an installer script + that manages it as a QEMU virtual machine on the host. +*/ +{ + config, + lib, + options, + ... +}: + +let + keysDirectory = "/var/keys"; + + user = "builder"; + + keyType = "ed25519"; + + cfg = config.virtualisation.darwin-builder; + +in + +{ + imports = [ + ../virtualisation/qemu-vm.nix + + # Avoid a dependency on stateVersion + { + disabledModules = [ + ../virtualisation/nixos-containers.nix + ../services/x11/desktop-managers/xterm.nix + ]; + # swraid's default depends on stateVersion + config.boot.swraid.enable = false; + options.boot.isContainer = lib.mkOption { + default = false; + internal = true; + }; + } + ]; + + options.virtualisation.darwin-builder = with lib; { + diskSize = mkOption { + default = 20 * 1024; + type = types.int; + example = 30720; + description = "The maximum disk space allocated to the runner in MB"; + }; + memorySize = mkOption { + default = 3 * 1024; + type = types.int; + example = 8192; + description = "The runner's memory in MB"; + }; + min-free = mkOption { + default = 1024 * 1024 * 1024; + type = types.int; + example = 1073741824; + description = '' + The threshold (in bytes) of free disk space left at which to + start garbage collection on the runner + ''; + }; + max-free = mkOption { + default = 3 * 1024 * 1024 * 1024; + type = types.int; + example = 3221225472; + description = '' + The threshold (in bytes) of free disk space left at which to + stop garbage collection on the runner + ''; + }; + workingDirectory = mkOption { + default = "."; + type = types.str; + example = "/var/lib/darwin-builder"; + description = '' + The working directory to use to run the script. When running + as part of a flake will need to be set to a non read-only filesystem. + ''; + }; + hostPort = mkOption { + default = 31022; + type = types.int; + example = 22; + description = '' + The localhost host port to forward TCP to the guest port. + ''; + }; + }; + + config = { + # The builder is not intended to be used interactively + documentation.enable = false; + + environment.etc = { + "ssh/ssh_host_ed25519_key" = { + mode = "0600"; + + source = ./keys/ssh_host_ed25519_key; + }; + + "ssh/ssh_host_ed25519_key.pub" = { + mode = "0644"; + + source = ./keys/ssh_host_ed25519_key.pub; + }; + }; + + # DNS fails for QEMU user networking (SLiRP) on macOS. See: + # + # https://github.com/utmapp/UTM/issues/2353 + # + # This works around that by using a public DNS server other than the DNS + # server that QEMU provides (normally 10.0.2.3) + networking.nameservers = [ "8.8.8.8" ]; + + # The linux builder is a lightweight VM for remote building; not evaluation. + nix.channel.enable = false; + + # Deployment is by image. + # TODO system.switch.enable = false;? + system.disableInstallerTools = true; + + nix.settings = { + auto-optimise-store = true; + + min-free = cfg.min-free; + + max-free = cfg.max-free; + + trusted-users = [ user ]; + }; + + services = { + getty.autologinUser = user; + + openssh = { + enable = true; + + authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ]; + }; + }; + + system.build.macos-builder-installer = + let + privateKey = "/etc/nix/${user}_${keyType}"; + + publicKey = "${privateKey}.pub"; + + # This installCredentials script is written so that it's as easy as + # possible for a user to audit before confirming the `sudo` + installCredentials = hostPkgs.writeShellScript "install-credentials" '' + set -euo pipefail + + KEYS="''${1}" + INSTALL=${hostPkgs.coreutils}/bin/install + "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey} + "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey} + ''; + + hostPkgs = config.virtualisation.host.pkgs; + + script = hostPkgs.writeShellScriptBin "create-builder" ( + '' + set -euo pipefail + '' + + + # When running as non-interactively as part of a DarwinConfiguration the working directory + # must be set to a writeable directory. + ( + if cfg.workingDirectory != "." then + '' + ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" + cd "${cfg.workingDirectory}" + '' + else + "" + ) + + '' + KEYS="''${KEYS:-./keys}" + ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" + PRIVATE_KEY="''${KEYS}/${user}_${keyType}" + PUBLIC_KEY="''${PRIVATE_KEY}.pub" + if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then + ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" + ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' + fi + if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then + (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") + fi + KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm} + '' + ); + + in + script.overrideAttrs (old: { + pos = __curPos; # sets meta.position to point here; see script binding above for package definition + meta = (old.meta or { }) // { + platforms = lib.platforms.darwin; + }; + passthru = (old.passthru or { }) // { + # Let users in the repl inspect the config + nixosConfig = config; + nixosOptions = options; + }; + }); + + system = { + # To prevent gratuitous rebuilds on each change to Nixpkgs + nixos.revision = null; + + # to be updated by module maintainers, see nixpkgs#325610 + stateVersion = "24.05"; + }; + + users.users."${user}" = { + isNormalUser = true; + }; + + security.polkit.enable = true; + + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") { + return "yes"; + } else { + return "no"; + } + }) + ''; + + virtualisation = { + diskSize = cfg.diskSize; + + memorySize = cfg.memorySize; + + forwardPorts = [ + { + from = "host"; + guest.port = 22; + host.port = cfg.hostPort; + } + ]; + + # Disable graphics for the builder since users will likely want to run it + # non-interactively in the background. + graphics = false; + + sharedDirectories.keys = { + source = "\"$KEYS\""; + target = keysDirectory; + }; + + # If we don't enable this option then the host will fail to delegate builds + # to the guest, because: + # + # - The host will lock the path to build + # - The host will delegate the build to the guest + # - The guest will attempt to lock the same path and fail because + # the lockfile on the host is visible on the guest + # + # Snapshotting the host's /nix/store as an image isolates the guest VM's + # /nix/store from the host's /nix/store, preventing this problem. + useNixStoreImage = true; + + # Obviously the /nix/store needs to be writable on the guest in order for it + # to perform builds. + writableStore = true; + + # This ensures that anything built on the guest isn't lost when the guest is + # restarted. + writableStoreUseTmpfs = false; + + # Pass certificates from host to the guest otherwise when custom CA certificates + # are required we can't use the cached builder. + useHostCerts = true; + }; + }; +} diff --git a/nixos/modules/programs/_1password.nix b/nixos/modules/programs/_1password.nix index 5dff199341b9..28ddb3b1a43e 100644 --- a/nixos/modules/programs/_1password.nix +++ b/nixos/modules/programs/_1password.nix @@ -17,7 +17,7 @@ in enable = lib.mkEnableOption "the 1Password CLI tool"; package = lib.mkPackageOption pkgs "1Password CLI" { - default = [ "_1password" ]; + default = [ "_1password-cli" ]; }; }; }; diff --git a/nixos/modules/programs/corefreq.nix b/nixos/modules/programs/corefreq.nix new file mode 100644 index 000000000000..c656b4a13da1 --- /dev/null +++ b/nixos/modules/programs/corefreq.nix @@ -0,0 +1,42 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.corefreq; + kernelPackages = config.boot.kernelPackages; +in +{ + options = { + programs.corefreq = { + enable = lib.mkEnableOption "Whether to enable the corefreq daemon and kernel module"; + + package = lib.mkOption { + type = lib.types.package; + default = kernelPackages.corefreq; + defaultText = lib.literalExpression "config.boot.kernelPackages.corefreq"; + description = '' + The corefreq package to use. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + boot.extraModulePackages = [ cfg.package ]; + boot.kernelModules = [ "corefreqk" ]; + + # Create a systemd service for the corefreq daemon + systemd.services.corefreq = { + description = "CoreFreq daemon"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = lib.getExe' cfg.package "corefreqd"; + }; + }; + }; +} diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix index 7521708fa94f..ed874cad74e4 100644 --- a/nixos/modules/programs/firefox.nix +++ b/nixos/modules/programs/firefox.nix @@ -1,4 +1,9 @@ -{ pkgs, config, lib, ... }: +{ + pkgs, + config, + lib, + ... +}: let cfg = config.programs.firefox; @@ -78,7 +83,7 @@ in wrapperConfig = lib.mkOption { type = lib.types.attrs; - default = {}; + default = { }; description = "Arguments to pass to Firefox wrapper"; }; @@ -99,7 +104,13 @@ in }; preferences = lib.mkOption { - type = with lib.types; attrsOf (oneOf [ bool int str ]); + type = + with lib.types; + attrsOf (oneOf [ + bool + int + str + ]); default = { }; description = '' Preferences to set from `about:config`. @@ -112,7 +123,12 @@ in }; preferencesStatus = lib.mkOption { - type = lib.types.enum [ "default" "locked" "user" "clear" ]; + type = lib.types.enum [ + "default" + "locked" + "user" + "clear" + ]; default = "locked"; description = '' The status of `firefox.preferences`. @@ -127,111 +143,113 @@ in languagePacks = lib.mkOption { # Available languages can be found in https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/ - type = lib.types.listOf (lib.types.enum ([ - "ach" - "af" - "an" - "ar" - "ast" - "az" - "be" - "bg" - "bn" - "br" - "bs" - "ca-valencia" - "ca" - "cak" - "cs" - "cy" - "da" - "de" - "dsb" - "el" - "en-CA" - "en-GB" - "en-US" - "eo" - "es-AR" - "es-CL" - "es-ES" - "es-MX" - "et" - "eu" - "fa" - "ff" - "fi" - "fr" - "fur" - "fy-NL" - "ga-IE" - "gd" - "gl" - "gn" - "gu-IN" - "he" - "hi-IN" - "hr" - "hsb" - "hu" - "hy-AM" - "ia" - "id" - "is" - "it" - "ja" - "ka" - "kab" - "kk" - "km" - "kn" - "ko" - "lij" - "lt" - "lv" - "mk" - "mr" - "ms" - "my" - "nb-NO" - "ne-NP" - "nl" - "nn-NO" - "oc" - "pa-IN" - "pl" - "pt-BR" - "pt-PT" - "rm" - "ro" - "ru" - "sat" - "sc" - "sco" - "si" - "sk" - "skr" - "sl" - "son" - "sq" - "sr" - "sv-SE" - "szl" - "ta" - "te" - "tg" - "th" - "tl" - "tr" - "trs" - "uk" - "ur" - "uz" - "vi" - "xh" - "zh-CN" - "zh-TW" - ])); + type = lib.types.listOf ( + lib.types.enum ([ + "ach" + "af" + "an" + "ar" + "ast" + "az" + "be" + "bg" + "bn" + "br" + "bs" + "ca-valencia" + "ca" + "cak" + "cs" + "cy" + "da" + "de" + "dsb" + "el" + "en-CA" + "en-GB" + "en-US" + "eo" + "es-AR" + "es-CL" + "es-ES" + "es-MX" + "et" + "eu" + "fa" + "ff" + "fi" + "fr" + "fur" + "fy-NL" + "ga-IE" + "gd" + "gl" + "gn" + "gu-IN" + "he" + "hi-IN" + "hr" + "hsb" + "hu" + "hy-AM" + "ia" + "id" + "is" + "it" + "ja" + "ka" + "kab" + "kk" + "km" + "kn" + "ko" + "lij" + "lt" + "lv" + "mk" + "mr" + "ms" + "my" + "nb-NO" + "ne-NP" + "nl" + "nn-NO" + "oc" + "pa-IN" + "pl" + "pt-BR" + "pt-PT" + "rm" + "ro" + "ru" + "sat" + "sc" + "sco" + "si" + "sk" + "skr" + "sl" + "son" + "sq" + "sr" + "sv-SE" + "szl" + "ta" + "te" + "tg" + "th" + "tl" + "tr" + "trs" + "uk" + "ur" + "uz" + "vi" + "xh" + "zh-CN" + "zh-TW" + ]) + ); default = [ ]; description = '' The language packs to install. @@ -249,10 +267,23 @@ in ''; }; + autoConfigFiles = lib.mkOption { + type = with lib.types; listOf path; + default = [ ]; + description = '' + AutoConfig files can be used to set and lock preferences that are not covered + by the policies.json for Mac and Linux. This method can be used to automatically + change user preferences or prevent the end user from modifiying specific + preferences by locking them. More info can be found in https://support.mozilla.org/en-US/kb/customizing-firefox-using-autoconfig. + + Files are concated and autoConfig is appended. + ''; + }; + nativeMessagingHosts = ({ packages = lib.mkOption { type = lib.types.listOf lib.types.package; - default = []; + default = [ ]; description = '' Additional packages containing native messaging hosts that should be made available to Firefox extensions. ''; @@ -260,48 +291,64 @@ in }) // (builtins.mapAttrs (k: v: lib.mkEnableOption "${v.name} support") nmhOptions); }; - config = let - forEachEnabledNmh = fn: lib.flatten (lib.mapAttrsToList (k: v: lib.optional cfg.nativeMessagingHosts.${k} (fn k v)) nmhOptions); - in lib.mkIf cfg.enable { - warnings = forEachEnabledNmh (k: v: - "The `programs.firefox.nativeMessagingHosts.${k}` option is deprecated, " + - "please add `${v.package.pname}` to `programs.firefox.nativeMessagingHosts.packages` instead." - ); - programs.firefox.nativeMessagingHosts.packages = forEachEnabledNmh (_: v: v.package); + config = + let + forEachEnabledNmh = + fn: + lib.flatten ( + lib.mapAttrsToList (k: v: lib.optional cfg.nativeMessagingHosts.${k} (fn k v)) nmhOptions + ); + in + lib.mkIf cfg.enable { + warnings = forEachEnabledNmh ( + k: v: + "The `programs.firefox.nativeMessagingHosts.${k}` option is deprecated, " + + "please add `${v.package.pname}` to `programs.firefox.nativeMessagingHosts.packages` instead." + ); + programs.firefox.nativeMessagingHosts.packages = forEachEnabledNmh (_: v: v.package); - environment.systemPackages = [ - (cfg.package.override (old: { - extraPrefsFiles = old.extraPrefsFiles or [] ++ [(pkgs.writeText "firefox-autoconfig.js" cfg.autoConfig)]; - nativeMessagingHosts = old.nativeMessagingHosts or [] ++ cfg.nativeMessagingHosts.packages; - cfg = (old.cfg or {}) // cfg.wrapperConfig; - })) - ]; + environment.systemPackages = [ + (cfg.package.override (old: { + extraPrefsFiles = + old.extraPrefsFiles or [ ] + ++ cfg.autoConfigFiles + ++ [ (pkgs.writeText "firefox-autoconfig.js" cfg.autoConfig) ]; + nativeMessagingHosts = old.nativeMessagingHosts or [ ] ++ cfg.nativeMessagingHosts.packages; + cfg = (old.cfg or { }) // cfg.wrapperConfig; + })) + ]; - environment.etc = - let - policiesJSON = policyFormat.generate "firefox-policies.json" { inherit (cfg) policies; }; - in - lib.mkIf (cfg.policies != { }) { - "firefox/policies/policies.json".source = "${policiesJSON}"; - }; + environment.etc = + let + policiesJSON = policyFormat.generate "firefox-policies.json" { inherit (cfg) policies; }; + in + lib.mkIf (cfg.policies != { }) { + "firefox/policies/policies.json".source = "${policiesJSON}"; + }; - # Preferences are converted into a policy - programs.firefox.policies = { - DisableAppUpdate = true; - Preferences = (builtins.mapAttrs - (_: value: { Value = value; Status = cfg.preferencesStatus; }) - cfg.preferences); - ExtensionSettings = builtins.listToAttrs (builtins.map - (lang: lib.attrsets.nameValuePair - "langpack-${lang}@firefox.mozilla.org" - { - installation_mode = "normal_installed"; - install_url = "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi"; - } - ) - cfg.languagePacks); + # Preferences are converted into a policy + programs.firefox.policies = { + DisableAppUpdate = true; + Preferences = ( + builtins.mapAttrs (_: value: { + Value = value; + Status = cfg.preferencesStatus; + }) cfg.preferences + ); + ExtensionSettings = builtins.listToAttrs ( + builtins.map ( + lang: + lib.attrsets.nameValuePair "langpack-${lang}@firefox.mozilla.org" { + installation_mode = "normal_installed"; + install_url = "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi"; + } + ) cfg.languagePacks + ); + }; }; - }; - meta.maintainers = with lib.maintainers; [ danth ]; + meta.maintainers = with lib.maintainers; [ + danth + linsui + ]; } diff --git a/nixos/modules/programs/gamemode.nix b/nixos/modules/programs/gamemode.nix index 14892f9c6eac..bd3248c1c5a2 100644 --- a/nixos/modules/programs/gamemode.nix +++ b/nixos/modules/programs/gamemode.nix @@ -2,7 +2,7 @@ let cfg = config.programs.gamemode; - settingsFormat = pkgs.formats.ini { }; + settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; }; configFile = settingsFormat.generate "gamemode.ini" cfg.settings; in { diff --git a/nixos/modules/programs/gpu-screen-recorder.nix b/nixos/modules/programs/gpu-screen-recorder.nix index 39d0e2545241..5a9401943dfe 100644 --- a/nixos/modules/programs/gpu-screen-recorder.nix +++ b/nixos/modules/programs/gpu-screen-recorder.nix @@ -1,14 +1,20 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.programs.gpu-screen-recorder; package = cfg.package.override { inherit (config.security) wrapperDir; }; -in { +in +{ options = { programs.gpu-screen-recorder = { - package = lib.mkPackageOption pkgs "gpu-screen-recorder" {}; + package = lib.mkPackageOption pkgs "gpu-screen-recorder" { }; enable = lib.mkOption { type = lib.types.bool; @@ -28,12 +34,6 @@ in { capabilities = "cap_sys_admin+ep"; source = "${package}/bin/gsr-kms-server"; }; - security.wrappers."gpu-screen-recorder" = { - owner = "root"; - group = "root"; - capabilities = "cap_sys_nice+ep"; - source = "${package}/bin/gpu-screen-recorder"; - }; }; meta.maintainers = with lib.maintainers; [ timschumi ]; diff --git a/nixos/modules/programs/immersed-vr.nix b/nixos/modules/programs/immersed-vr.nix deleted file mode 100644 index 57edb3cbaea0..000000000000 --- a/nixos/modules/programs/immersed-vr.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: - -let - cfg = config.programs.immersed-vr; -in -{ - - options = { - programs.immersed-vr = { - enable = lib.mkEnableOption "immersed-vr"; - - package = lib.mkPackageOption pkgs "immersed-vr" {}; - }; - }; - - config = lib.mkIf cfg.enable { - boot = { - kernelModules = [ "v4l2loopback" "snd-aloop" ]; - extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; - extraModprobeConfig = '' - options v4l2loopback exclusive_caps=1 card_label="v4l2loopback Virtual Camera" - ''; - }; - - environment.systemPackages = [ cfg.package ]; - }; - - meta.maintainers = pkgs.immersed-vr.meta.maintainers; -} diff --git a/nixos/modules/programs/immersed.nix b/nixos/modules/programs/immersed.nix new file mode 100644 index 000000000000..0fb217b91d54 --- /dev/null +++ b/nixos/modules/programs/immersed.nix @@ -0,0 +1,49 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.immersed; +in +{ + imports = [ + (lib.mkRenamedOptionModule + [ + "programs" + "immersed-vr" + ] + [ + "programs" + "immersed" + ] + ) + ]; + + options = { + programs.immersed = { + enable = lib.mkEnableOption "immersed"; + + package = lib.mkPackageOption pkgs "immersed" { }; + }; + }; + + config = lib.mkIf cfg.enable { + boot = { + kernelModules = [ + "v4l2loopback" + "snd-aloop" + ]; + extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; + extraModprobeConfig = '' + options v4l2loopback exclusive_caps=1 card_label="v4l2loopback Virtual Camera" + ''; + }; + + environment.systemPackages = [ cfg.package ]; + }; + + meta.maintainers = pkgs.immersed.meta.maintainers; +} diff --git a/nixos/modules/programs/localsend.nix b/nixos/modules/programs/localsend.nix index 47f54246a40f..a4580057ba4b 100644 --- a/nixos/modules/programs/localsend.nix +++ b/nixos/modules/programs/localsend.nix @@ -12,14 +12,19 @@ in options.programs.localsend = { enable = lib.mkEnableOption "localsend, an open source cross-platform alternative to AirDrop"; - openFirewall = lib.mkEnableOption "opening the firewall port ${toString firewallPort} for receiving files" // { - default = true; - }; + package = lib.mkPackageOption pkgs "localsend" { }; + + openFirewall = + lib.mkEnableOption "opening the firewall port ${toString firewallPort} for receiving files" + // { + default = true; + }; }; config = lib.mkIf cfg.enable { - environment.systemPackages = [ pkgs.localsend ]; + environment.systemPackages = [ cfg.package ]; networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ firewallPort ]; + networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ firewallPort ]; }; meta.maintainers = with lib.maintainers; [ pandapip1 ]; diff --git a/nixos/modules/programs/nix-ld.nix b/nixos/modules/programs/nix-ld.nix index 770cccd13b50..edfe7f1f6a3e 100644 --- a/nixos/modules/programs/nix-ld.nix +++ b/nixos/modules/programs/nix-ld.nix @@ -34,7 +34,7 @@ in environment.pathsToLink = [ "/share/nix-ld" ]; - environment.variables = { + environment.sessionVariables = { NIX_LD = "/run/current-system/sw/share/nix-ld/lib/ld.so"; NIX_LD_LIBRARY_PATH = "/run/current-system/sw/share/nix-ld/lib"; }; diff --git a/nixos/modules/programs/nncp.nix b/nixos/modules/programs/nncp.nix index 3feccef4cf11..6ac0363e50a0 100644 --- a/nixos/modules/programs/nncp.nix +++ b/nixos/modules/programs/nncp.nix @@ -74,19 +74,13 @@ in { wantedBy = [ "basic.target" ]; serviceConfig.Type = "oneshot"; script = '' - umask u=rw - nncpCfgDir=$(mktemp --directory nncp.XXX) - for f in ${jsonCfgFile} ${builtins.toString config.programs.nncp.secrets}; do - tmpdir=$(mktemp --directory nncp.XXX) - nncp-cfgdir -cfg $f -dump $tmpdir - find $tmpdir -size 1c -delete - cp -a $tmpdir/* $nncpCfgDir/ - rm -rf $tmpdir - done - nncp-cfgdir -load $nncpCfgDir > ${nncpCfgFile} - rm -rf $nncpCfgDir + umask 127 + rm -f ${nncpCfgFile} + for f in ${jsonCfgFile} ${builtins.toString config.programs.nncp.secrets} + do + ${lib.getExe pkgs.hjson-go} -c <"$f" + done |${lib.getExe pkgs.jq} --slurp add >${nncpCfgFile} chgrp ${programCfg.group} ${nncpCfgFile} - chmod g+r ${nncpCfgFile} ''; }; }; diff --git a/nixos/modules/programs/obs-studio.nix b/nixos/modules/programs/obs-studio.nix new file mode 100644 index 000000000000..ee9ec520d1fb --- /dev/null +++ b/nixos/modules/programs/obs-studio.nix @@ -0,0 +1,64 @@ +{ + pkgs, + lib, + config, + ... +}: + +let + cfg = config.programs.obs-studio; +in +{ + options.programs.obs-studio = { + enable = lib.mkEnableOption "Free and open source software for video recording and live streaming"; + + package = lib.mkPackageOption pkgs "obs-studio" { example = "obs-studio"; }; + + finalPackage = lib.mkOption { + type = lib.types.package; + visible = false; + readOnly = true; + description = "Resulting customized OBS Studio package."; + }; + + plugins = lib.mkOption { + default = [ ]; + example = lib.literalExpression "[ pkgs.obs-studio-plugins.wlrobs ]"; + description = "Optional OBS plugins."; + type = lib.types.listOf lib.types.package; + }; + + enableVirtualCamera = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Installs and sets up the v4l2loopback kernel module, necessary for OBS + to start a virtual camera. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + programs.obs-studio.finalPackage = pkgs.wrapOBS.override { obs-studio = cfg.package; } { + plugins = cfg.plugins; + }; + + environment.systemPackages = [ cfg.finalPackage ]; + + boot = lib.mkIf cfg.enableVirtualCamera { + kernelModules = [ "v4l2loopback" ]; + extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; + + extraModprobeConfig = '' + options v4l2loopback devices=1 video_nr=1 card_label="OBS Cam" exclusive_caps=1 + ''; + }; + + security.polkit.enable = lib.mkIf cfg.enableVirtualCamera true; + }; + + meta.maintainers = with lib.maintainers; [ + CaptainJawZ + GaetanLepage + ]; +} diff --git a/nixos/modules/programs/openvpn3.nix b/nixos/modules/programs/openvpn3.nix index 10042b44471f..085775af173b 100644 --- a/nixos/modules/programs/openvpn3.nix +++ b/nixos/modules/programs/openvpn3.nix @@ -1,29 +1,87 @@ { config, lib, pkgs, ... }: let + json = pkgs.formats.json { }; cfg = config.programs.openvpn3; -in -{ + + inherit (lib) mkEnableOption mkPackageOption mkOption literalExpression max options lists; + inherit (lib.types) bool submodule ints; +in { options.programs.openvpn3 = { - enable = lib.mkEnableOption "the openvpn3 client"; - package = lib.mkOption { - type = lib.types.package; - default = pkgs.openvpn3.override { - enableSystemdResolved = config.services.resolved.enable; + enable = mkEnableOption "the openvpn3 client"; + package = mkPackageOption pkgs "openvpn3" { }; + netcfg = mkOption { + description = "Network configuration"; + default = { }; + type = submodule { + options = { + settings = mkOption { + description = "Options stored in {file}`/etc/openvpn3/netcfg.json` configuration file"; + default = { }; + type = submodule { + freeformType = json.type; + options = { + systemd_resolved = mkOption { + type = bool; + description = "Whether to use systemd-resolved integration"; + default = config.services.resolved.enable; + defaultText = literalExpression "config.services.resolved.enable"; + example = false; + }; + }; + }; + }; + }; + }; + }; + log-service = mkOption { + description = "Log service configuration"; + default = { }; + type = submodule { + options = { + settings = mkOption { + description = "Options stored in {file}`/etc/openvpn3/log-service.json` configuration file"; + default = { }; + type = submodule { + freeformType = json.type; + options = { + journald = mkOption { + description = "Use systemd-journald"; + type = bool; + default = true; + example = false; + }; + log_dbus_details = mkOption { + description = "Add D-Bus details in log file/syslog"; + type = bool; + default = true; + example = false; + }; + log_level = mkOption { + description = "How verbose should the logging be"; + type = (ints.between 0 7) // { + merge = _loc: defs: + lists.foldl max 0 (options.getValues defs); + }; + default = 3; + example = 6; + }; + timestamp = mkOption { + description = "Add timestamp log file"; + type = bool; + default = false; + example = true; + }; + }; + }; + }; + }; }; - defaultText = lib.literalExpression ''pkgs.openvpn3.override { - enableSystemdResolved = config.services.resolved.enable; - }''; - description = '' - Which package to use for `openvpn3`. - ''; }; }; config = lib.mkIf cfg.enable { - services.dbus.packages = [ - cfg.package - ]; + services.dbus.packages = [ cfg.package ]; users.users.openvpn = { isSystemUser = true; @@ -31,13 +89,25 @@ in group = "openvpn"; }; - users.groups.openvpn = { - gid = config.ids.gids.openvpn; + users.groups.openvpn = { gid = config.ids.gids.openvpn; }; + + environment = { + systemPackages = [ cfg.package ]; + etc = { + "openvpn3/netcfg.json".source = + json.generate "netcfg.json" cfg.netcfg.settings; + "openvpn3/log-service.json".source = + json.generate "log-service.json" cfg.log-service.settings; + }; }; - environment.systemPackages = [ - cfg.package - ]; + systemd = { + packages = [ cfg.package ]; + tmpfiles.rules = [ + "d /etc/openvpn3/configs 0750 openvpn openvpn - -" + ]; + }; }; + meta.maintainers = with lib.maintainers; [ shamilton progrm_jarvis ]; } diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix index 92e6f4ccaeb1..dab8425721c5 100644 --- a/nixos/modules/programs/steam.nix +++ b/nixos/modules/programs/steam.nix @@ -31,7 +31,7 @@ in { default = pkgs.steam; defaultText = lib.literalExpression "pkgs.steam"; example = lib.literalExpression '' - pkgs.steam-small.override { + pkgs.steam.override { extraEnv = { MANGOHUD = true; OBS_VKCAPTURE = true; diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix index a010bb6c6d57..1795db5c2470 100644 --- a/nixos/modules/programs/tsm-client.nix +++ b/nixos/modules/programs/tsm-client.nix @@ -22,7 +22,7 @@ let serverOptions = { name, config, ... }: { freeformType = attrsOf (either scalarType (listOf scalarType)); # Client system-options file directives are explained here: - # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=commands-processing-options + # https://www.ibm.com/docs/en/storage-protect/8.1.24?topic=commands-processing-options options.servername = mkOption { type = servernameType; default = name; diff --git a/nixos/modules/programs/wayland/cardboard.nix b/nixos/modules/programs/wayland/cardboard.nix index 5a25b5359c24..96089bdf94ed 100644 --- a/nixos/modules/programs/wayland/cardboard.nix +++ b/nixos/modules/programs/wayland/cardboard.nix @@ -19,6 +19,6 @@ in # To make a cardboard session available for certain DMs like SDDM services.displayManager.sessionPackages = [ cfg.package ]; } - (import ./wayland-session.nix { inherit lib; }) + (import ./wayland-session.nix { inherit lib pkgs; }) ]); } diff --git a/nixos/modules/programs/wayland/hyprland.nix b/nixos/modules/programs/wayland/hyprland.nix index f8b70af40600..6e69c1730e57 100644 --- a/nixos/modules/programs/wayland/hyprland.nix +++ b/nixos/modules/programs/wayland/hyprland.nix @@ -70,7 +70,7 @@ in } (import ./wayland-session.nix { - inherit lib; + inherit lib pkgs; enableXWayland = cfg.xwayland.enable; enableWlrPortal = lib.mkDefault false; # Hyprland has its own portal, wlr is not needed }) diff --git a/nixos/modules/programs/wayland/labwc.nix b/nixos/modules/programs/wayland/labwc.nix index 4c74b45bb4a3..c09ab8240d9f 100644 --- a/nixos/modules/programs/wayland/labwc.nix +++ b/nixos/modules/programs/wayland/labwc.nix @@ -20,6 +20,6 @@ in # To make a labwc session available for certain DMs like SDDM services.displayManager.sessionPackages = [ cfg.package ]; } - (import ./wayland-session.nix { inherit lib; }) + (import ./wayland-session.nix { inherit lib pkgs; }) ]); } diff --git a/nixos/modules/programs/wayland/miracle-wm.nix b/nixos/modules/programs/wayland/miracle-wm.nix index e20b62acb462..3cee538c8997 100644 --- a/nixos/modules/programs/wayland/miracle-wm.nix +++ b/nixos/modules/programs/wayland/miracle-wm.nix @@ -30,11 +30,12 @@ in } (import ./wayland-session.nix { - inherit lib; + inherit lib pkgs; # Hardcoded path in Mir, not really possible to disable enableXWayland = true; # No portal support yet: https://github.com/mattkae/miracle-wm/issues/164 enableWlrPortal = false; + enableGtkPortal = false; }) ] ); diff --git a/nixos/modules/programs/wayland/niri.nix b/nixos/modules/programs/wayland/niri.nix new file mode 100644 index 000000000000..4092969a3e45 --- /dev/null +++ b/nixos/modules/programs/wayland/niri.nix @@ -0,0 +1,55 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.programs.niri; +in +{ + options.programs.niri = { + enable = lib.mkEnableOption "Niri, a scrollable-tiling Wayland compositor"; + + package = lib.mkPackageOption pkgs "niri" { }; + }; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + environment.systemPackages = [ cfg.package ]; + + services = { + displayManager.sessionPackages = [ cfg.package ]; + + # Recommended by upstream + # https://github.com/YaLTeR/niri/wiki/Important-Software#portals + gnome.gnome-keyring.enable = lib.mkDefault true; + }; + + systemd.packages = [ cfg.package ]; + + xdg.portal = { + enable = lib.mkDefault true; + + configPackages = [ cfg.package ]; + + # Recommended by upstream, required for screencast support + # https://github.com/YaLTeR/niri/wiki/Important-Software#portals + extraPortals = [ pkgs.xdg-desktop-portal-gnome ]; + }; + } + + (import ./wayland-session.nix { + inherit lib pkgs; + enableWlrPortal = false; + enableXWayland = false; + }) + ] + ); + + meta.maintainers = with lib.maintainers; [ + getchoo + sodiboo + ]; +} diff --git a/nixos/modules/programs/wayland/river.nix b/nixos/modules/programs/wayland/river.nix index da793a9ff344..6391f00e2f62 100644 --- a/nixos/modules/programs/wayland/river.nix +++ b/nixos/modules/programs/wayland/river.nix @@ -56,7 +56,7 @@ in } (import ./wayland-session.nix { - inherit lib; + inherit lib pkgs; enableXWayland = cfg.xwayland.enable; }) ]); diff --git a/nixos/modules/programs/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix index 27c32ce42dc5..74fc216569e5 100644 --- a/nixos/modules/programs/wayland/sway.nix +++ b/nixos/modules/programs/wayland/sway.nix @@ -85,9 +85,10 @@ in extraPackages = lib.mkOption { type = with lib.types; listOf package; - default = with pkgs; [ swaylock swayidle foot dmenu wmenu ]; + # Packages used in default config + default = with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ]; defaultText = lib.literalExpression '' - with pkgs; [ swaylock swayidle foot dmenu wmenu ]; + with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ]; ''; example = lib.literalExpression '' with pkgs; [ i3status i3status-rust termite rofi light ] @@ -144,11 +145,23 @@ in services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package; # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913 - xdg.portal.config.sway.default = lib.mkDefault [ "wlr" "gtk" ]; + # https://github.com/emersion/xdg-desktop-portal-wlr/blob/master/contrib/wlroots-portals.conf + # https://github.com/emersion/xdg-desktop-portal-wlr/pull/315 + xdg.portal.config.sway = { + # Use xdg-desktop-portal-gtk for every portal interface... + default = "gtk"; + # ... except for the ScreenCast, Screenshot and Secret + "org.freedesktop.impl.portal.ScreenCast" = "wlr"; + "org.freedesktop.impl.portal.Screenshot" = "wlr"; + # ignore inhibit bc gtk portal always returns as success, + # despite sway/the wlr portal not having an implementation, + # stopping firefox from using wayland idle-inhibit + "org.freedesktop.impl.portal.Inhibit" = "none"; + }; } (import ./wayland-session.nix { - inherit lib; + inherit lib pkgs; enableXWayland = cfg.xwayland.enable; }) ]); diff --git a/nixos/modules/programs/wayland/uwsm.nix b/nixos/modules/programs/wayland/uwsm.nix index 5497cc269ebe..18e7fdc1f64a 100644 --- a/nixos/modules/programs/wayland/uwsm.nix +++ b/nixos/modules/programs/wayland/uwsm.nix @@ -108,18 +108,19 @@ in systemd.packages = [ cfg.package ]; environment.pathsToLink = [ "/share/uwsm" ]; - services.graphical-desktop.enable = true; - # UWSM recommends dbus broker for better compatibility services.dbus.implementation = "broker"; - services.displayManager.sessionPackages = lib.mapAttrsToList ( - name: value: - mk_uwsm_desktop_entry { - inherit name; - inherit (value) prettyName comment binPath; - } - ) cfg.waylandCompositors; + services.displayManager = { + enable = true; + sessionPackages = lib.mapAttrsToList ( + name: value: + mk_uwsm_desktop_entry { + inherit name; + inherit (value) prettyName comment binPath; + } + ) cfg.waylandCompositors; + }; }; meta.maintainers = with lib.maintainers; [ diff --git a/nixos/modules/programs/wayland/waybar.nix b/nixos/modules/programs/wayland/waybar.nix index 35c5cc86f3b4..074537ea9ed3 100644 --- a/nixos/modules/programs/wayland/waybar.nix +++ b/nixos/modules/programs/wayland/waybar.nix @@ -11,7 +11,11 @@ in { options.programs.waybar = { enable = lib.mkEnableOption "waybar, a highly customizable Wayland bar for Sway and Wlroots based compositors"; - package = lib.mkPackageOption pkgs "waybar" { }; + package = + lib.mkPackageOption pkgs "waybar" { } + // lib.mkOption { + apply = pkg: pkg.override { systemdSupport = true; }; + }; }; config = lib.mkIf cfg.enable { diff --git a/nixos/modules/programs/wayland/wayfire.nix b/nixos/modules/programs/wayland/wayfire.nix index 726300ce5381..1c3950bff47d 100644 --- a/nixos/modules/programs/wayland/wayfire.nix +++ b/nixos/modules/programs/wayland/wayfire.nix @@ -63,7 +63,7 @@ in }; } (import ./wayland-session.nix { - inherit lib; + inherit lib pkgs; enableXWayland = cfg.xwayland.enable; }) ] diff --git a/nixos/modules/programs/wayland/wayland-session.nix b/nixos/modules/programs/wayland/wayland-session.nix index b3d6cc368b20..1f5dc413d377 100644 --- a/nixos/modules/programs/wayland/wayland-session.nix +++ b/nixos/modules/programs/wayland/wayland-session.nix @@ -1,7 +1,9 @@ { lib, + pkgs, enableXWayland ? true, enableWlrPortal ? true, + enableGtkPortal ? true, }: { @@ -18,6 +20,9 @@ services.graphical-desktop.enable = true; xdg.portal.wlr.enable = enableWlrPortal; + xdg.portal.extraPortals = lib.mkIf enableGtkPortal [ + pkgs.xdg-desktop-portal-gtk + ]; # Window manager only sessions (unlike DEs) don't handle XDG # autostart files, so force them to run the service diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix index 2120cf1af07e..f1ed26ddb569 100644 --- a/nixos/modules/programs/zsh/oh-my-zsh.nix +++ b/nixos/modules/programs/zsh/oh-my-zsh.nix @@ -87,6 +87,15 @@ in Without this option it would default to the read-only nix store. ''; }; + + preLoaded = lib.mkOption { + type = lib.types.lines; + default = ""; + description = '' + Shell commands executed before the `oh-my-zsh` is loaded. + For example, to disable async git prompt write `zstyle ':omz:alpha:lib:git' async-prompt no` (more information https://github.com/ohmyzsh/ohmyzsh?tab=readme-ov-file#async-git-prompt) + ''; + }; }; }; @@ -120,6 +129,7 @@ in ZSH_CACHE_DIR=${cfg.cacheDir} ''} + ${cfg.preLoaded} source $ZSH/oh-my-zsh.sh ''; diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 63f612eec960..18b7e9faaa72 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -62,7 +62,9 @@ in (mkRemovedOptionModule [ "services" "beegfsEnable" ] "The BeeGFS module has been removed") (mkRemovedOptionModule [ "services" "cgmanager" "enable"] "cgmanager was deprecated by lxc and therefore removed from nixpkgs.") (mkRemovedOptionModule [ "services" "chronos" ] "The corresponding package was removed from nixpkgs.") + (mkRemovedOptionModule [ "services" "confluence" ] "Atlassian software has been removed, as support for the Atlassian Server products ended in February 2024 and there was insufficient interest in maintaining the Atlassian Data Center replacements") (mkRemovedOptionModule [ "services" "couchpotato" ] "The corresponding package was removed from nixpkgs.") + (mkRemovedOptionModule [ "services" "crowd" ] "Atlassian software has been removed, as support for the Atlassian Server products ended in February 2024 and there was insufficient interest in maintaining the Atlassian Data Center replacements") (mkRemovedOptionModule [ "services" "dd-agent" ] "dd-agent was removed from nixpkgs in favor of the newer datadog-agent.") (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead") (mkRemovedOptionModule [ "services" "dnscrypt-wrapper" ] '' @@ -82,11 +84,13 @@ in (mkRemovedOptionModule [ "services" "homeassistant-satellite"] "The `services.homeassistant-satellite` module has been replaced by `services.wyoming-satellite`.") (mkRemovedOptionModule [ "services" "hydron" ] "The `services.hydron` module has been removed as the project has been archived upstream since 2022 and is affected by a severe remote code execution vulnerability.") (mkRemovedOptionModule [ "services" "ihatemoney" ] "The ihatemoney module has been removed for lack of downstream maintainer") + (mkRemovedOptionModule [ "services" "jira" ] "Atlassian software has been removed, as support for the Atlassian Server products ended in February 2024 and there was insufficient interest in maintaining the Atlassian Data Center replacements") (mkRemovedOptionModule [ "services" "kippo" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "lshd" ] "The corresponding package was removed from nixpkgs as it had no maintainer in Nixpkgs and hasn't seen an upstream release in over a decades.") (mkRemovedOptionModule [ "services" "mailpile" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "marathon" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "mathics" ] "The Mathics module has been removed") + (mkRemovedOptionModule [ "services" "matrix-sliding-sync" ] "The matrix-sliding-sync package has been removed, since matrix-synapse incorporated its functionality") (mkRemovedOptionModule [ "services" "meguca" ] "Use meguca has been removed from nixpkgs") (mkRemovedOptionModule [ "services" "mesos" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "mxisd" ] "The mxisd module has been removed as both mxisd and ma1sd got removed.") @@ -102,12 +106,14 @@ in (mkRemovedOptionModule [ "services" "railcar" ] "the corresponding package has been removed from nixpkgs") (mkRemovedOptionModule [ "services" "replay-sorcery" ] "the corresponding package has been removed from nixpkgs as it is unmaintained upstream. Consider using `gpu-screen-recorder` or `obs-studio` instead.") (mkRemovedOptionModule [ "services" "seeks" ] "") + (mkRemovedOptionModule [ "services" "shout" ] "shout was removed because it was deprecated upstream in favor of thelounge.") (mkRemovedOptionModule [ "services" "ssmtp" ] '' The ssmtp package and the corresponding module have been removed due to the program being unmaintained. The options `programs.msmtp.*` can be used instead. '') (mkRemovedOptionModule [ "services" "tvheadend" ] "The tvheadend package and the corresponding module have been removed as nobody was willing to maintain them and they were stuck on an unmaintained version that required FFmpeg 4; please see https://github.com/NixOS/nixpkgs/pull/332259 if you are interested in maintaining a newer version.") + (mkRemovedOptionModule [ "services" "unifi-video" ] "The unifi-video package and the corresponding module have been removed as the software has been unsupported since 2021 and requires a MongoDB version that has reached end of life.") (mkRemovedOptionModule [ "services" "venus" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "wakeonlan"] "This module was removed in favor of enabling it with networking.interfaces.<name>.wakeOnLan") (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.") diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix index f7774e685f7e..140fa590efef 100644 --- a/nixos/modules/security/acme/default.nix +++ b/nixos/modules/security/acme/default.nix @@ -183,7 +183,6 @@ let certToConfig = cert: data: let acmeServer = data.server; useDns = data.dnsProvider != null; - useDnsOrS3 = useDns || data.s3Bucket != null; destPath = "/var/lib/acme/${cert}"; selfsignedDeps = lib.optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ]; @@ -217,7 +216,7 @@ let protocolOpts = if useDns then ( [ "--dns" data.dnsProvider ] - ++ lib.optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ] + ++ lib.optionals (!data.dnsPropagationCheck) [ "--dns.propagation-disable-ans" ] ++ lib.optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ] ) else if data.s3Bucket != null then [ "--http" "--http.s3-bucket" data.s3Bucket ] else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ] @@ -344,7 +343,7 @@ let serviceConfig = commonServiceConfig // { Group = data.group; - # Let's Encrypt Failed Validation Limit allows 5 retries per hour, per account, hostname and hour. + # Let's Encrypt Failed Validation Limit allows 5 retries per hour, per account, hostname and hour. # This avoids eating them all up if something is misconfigured upon the first try. RestartSec = 15 * 60; @@ -367,13 +366,11 @@ let "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates" ]; - EnvironmentFile = lib.mkIf useDnsOrS3 data.environmentFile; + EnvironmentFile = lib.mkIf (data.environmentFile != null) data.environmentFile; - Environment = lib.mkIf useDnsOrS3 - (lib.mapAttrsToList (k: v: ''"${k}=%d/${k}"'') data.credentialFiles); + Environment = lib.mapAttrsToList (k: v: ''"${k}=%d/${k}"'') data.credentialFiles; - LoadCredential = lib.mkIf useDnsOrS3 - (lib.mapAttrsToList (k: v: "${k}:${v}") data.credentialFiles); + LoadCredential = lib.mapAttrsToList (k: v: "${k}:${v}") data.credentialFiles; # Run as root (Prefixed with +) ExecStartPost = "+" + (pkgs.writeShellScript "acme-postrun" '' diff --git a/nixos/modules/security/acme/mk-cert-ownership-assertion.nix b/nixos/modules/security/acme/mk-cert-ownership-assertion.nix index b80d89aeb9fc..53a3fbaadd2e 100644 --- a/nixos/modules/security/acme/mk-cert-ownership-assertion.nix +++ b/nixos/modules/security/acme/mk-cert-ownership-assertion.nix @@ -1,4 +1,21 @@ -{ cert, group, groups, user }: { - assertion = cert.group == group || builtins.any (u: u == user) groups.${cert.group}.members; - message = "Group for certificate ${cert.domain} must be ${group}, or user ${user} must be a member of group ${cert.group}"; +lib: + +{ cert, groups, services }: +let + catSep = builtins.concatStringsSep; + + svcGroups = svc: + (lib.optional (svc.serviceConfig ? Group) svc.serviceConfig.Group) + ++ (svc.serviceConfig.SupplementaryGroups or [ ]); +in +{ + assertion = builtins.all (svc: + svc.serviceConfig.User or "root" == "root" + || builtins.elem svc.serviceConfig.User groups.${cert.group}.members + || builtins.elem cert.group (svcGroups svc) + ) services; + + message = "Certificate ${cert.domain} (group=${cert.group}) must be readable by service(s) ${ + catSep ", " (map (svc: "${svc.name} (user=${svc.serviceConfig.User} groups=${catSep " " (svcGroups svc)})") services) + }"; } diff --git a/nixos/modules/security/ca.nix b/nixos/modules/security/ca.nix index 8aae6eb3f29b..76c1010f4199 100644 --- a/nixos/modules/security/ca.nix +++ b/nixos/modules/security/ca.nix @@ -24,7 +24,8 @@ in internal = true; }; - security.pki.useCompatibleBundle = mkEnableOption ''usage of a compatibility bundle. + security.pki.useCompatibleBundle = mkEnableOption '' + usage of a compatibility bundle. Such a bundle consists exclusively of `BEGIN CERTIFICATE` and no `BEGIN TRUSTED CERTIFICATE`, which is an OpenSSL specific PEM format. diff --git a/nixos/modules/security/isolate.nix b/nixos/modules/security/isolate.nix index 3cc0176f3db3..11a6af588b5b 100644 --- a/nixos/modules/security/isolate.nix +++ b/nixos/modules/security/isolate.nix @@ -125,7 +125,7 @@ in }; systemd.slices.isolate = { - description = "Isolate sandbox slice"; + description = "Isolate Sandbox Slice"; }; meta.maintainers = with maintainers; [ virchau13 ]; diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index 2ff08cbfde81..e50038ecbec9 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -287,6 +287,18 @@ let ''; }; + rssh = lib.mkOption { + default = false; + type = lib.types.bool; + description = '' + If set, the calling user's SSH agent is used to authenticate + against the configured keys. This module works in a manner + similar to pam_ssh_agent_auth, but supports a wider range + of SSH key types, including those protected by security + keys (FIDO2). + ''; + }; + duoSecurity = { enable = lib.mkOption { default = false; @@ -673,6 +685,7 @@ let { name = "ssh_agent_auth"; enable = config.security.pam.sshAgentAuth.enable && cfg.sshAgentAuth; control = "sufficient"; modulePath = "${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so"; settings = { file = lib.concatStringsSep ":" config.security.pam.sshAgentAuth.authorizedKeysFiles; }; } + (let inherit (config.security.pam) rssh; in { name = "rssh"; enable = rssh.enable && cfg.rssh; control = "sufficient"; modulePath = "${pkgs.pam_rssh}/lib/libpam_rssh.so"; inherit (rssh) settings; }) (let p11 = config.security.pam.p11; in { name = "p11"; enable = cfg.p11Auth; control = p11.control; modulePath = "${pkgs.pam_p11}/lib/security/pam_p11.so"; args = [ "${pkgs.opensc}/lib/opensc-pkcs11.so" ]; }) @@ -950,8 +963,9 @@ let value.source = pkgs.writeText "${name}.pam" service.text; }; - optionalSudoConfigForSSHAgentAuth = lib.optionalString config.security.pam.sshAgentAuth.enable '' - # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic. + optionalSudoConfigForSSHAgentAuth = lib.optionalString + (config.security.pam.sshAgentAuth.enable || config.security.pam.rssh.enable) '' + # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so and libpam_rssh.so can do their magic. Defaults env_keep+=SSH_AUTH_SOCK ''; @@ -1068,6 +1082,55 @@ in }; }; + security.pam.rssh = { + enable = lib.mkEnableOption "authenticating using a signature performed by the ssh-agent"; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = moduleSettingsType; + options = { + auth_key_file = lib.mkOption { + type = with lib.types; nullOr nonEmptyStr; + description = '' + Path to file with trusted public keys in OpenSSH's `authorized_keys` format. The following + variables are expanded to the respective PAM items: + + - `service`: `PAM_SERVICE`, the service name, + - `user`: `PAM_USER`, the username of the entity under whose identity service will be given, + - `tty`: `PAM_TTY`, the terminal name, + - `rhost`: `PAM_RHOST`, the requesting hostname, and + - `ruser`: `PAM_RUSER`, the requesting entity. + + These PAM items are explained in {manpage}`pam_get_item(3)`. + + Variables may be specified as `$var`, `''${var}` or `''${var:defaultValue}`. + + ::: {.note} + Specifying user-writeable files here results in an insecure configuration: a malicious process + can then edit such an `authorized_keys` file and bypass the ssh-agent-based authentication. + + This option is ignored if {option}`security.pam.rssh.settings.authorized_keys_command` is set. + + If both this option and {option}`security.pam.rssh.settings.authorized_keys_command` are unset, + the keys will be read from `''${HOME}/.ssh/authorized_keys`, which should be considered + insecure. + ''; + default = "/etc/ssh/authorized_keys.d/$ruser"; + }; + }; + }; + + default = { }; + description = '' + Options to pass to the pam_rssh module. Refer to + <https://github.com/z4yx/pam_rssh/blob/main/README.md#optional-arguments> + for supported values. + + ${moduleSettingsDescription} + ''; + }; + }; + security.pam.enableOTPW = lib.mkEnableOption "the OTPW (one-time password) PAM module"; security.pam.dp9ik = { @@ -1105,8 +1168,7 @@ in If set, users can authenticate with their Kerberos password. This requires a valid Kerberos configuration - (`config.security.krb5.enable` should be set to - `true`). + (`security.krb5.enable` should be set to `true`). Note that the Kerberos PAM modules are not necessary when using SSS to handle Kerberos authentication. @@ -1512,16 +1574,30 @@ in Did you forget to set `services.openssh.enable` ? ''; } + { + assertion = with config.security.pam.rssh; + enable -> (settings.auth_key_file or null != null || settings.authorized_keys_command or null != null); + message = '' + security.pam.rssh.enable requires either security.pam.rssh.settings.auth_key_file or + security.pam.rssh.settings.authorized_keys_command to be set. + ''; + } ]; warnings = lib.optional - (with lib; with config.security.pam.sshAgentAuth; - enable && lib.any (s: lib.hasPrefix "%h" s || lib.hasPrefix "~" s) authorizedKeysFiles) - ''config.security.pam.sshAgentAuth.authorizedKeysFiles contains files in the user's home directory. + (with config.security.pam.sshAgentAuth; + enable && lib.any (s: lib.hasPrefix "%h" s || lib.hasPrefix "~" s) authorizedKeysFiles) '' + security.pam.sshAgentAuth.authorizedKeysFiles contains files in the user's home directory. Specifying user-writeable files there result in an insecure configuration: a malicious process can then edit such an authorized_keys file and bypass the ssh-agent-based authentication. See https://github.com/NixOS/nixpkgs/issues/31611 + '' ++ lib.optional + (with config.security.pam.rssh; + enable && settings.auth_key_file or null != null && settings.authorized_keys_command or null != null) '' + security.pam.rssh.settings.auth_key_file will be ignored as + security.pam.rssh.settings.authorized_keys_command has been specified. + Explictly set the former to null to silence this warning. ''; environment.systemPackages = diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix index b5dae96d79c6..3bfe921673ed 100644 --- a/nixos/modules/security/wrappers/default.nix +++ b/nixos/modules/security/wrappers/default.nix @@ -165,6 +165,10 @@ in ###### interface options = { + security.enableWrappers = lib.mkEnableOption "SUID/SGID wrappers" // { + default = true; + }; + security.wrappers = lib.mkOption { type = lib.types.attrsOf wrapperType; default = {}; @@ -227,7 +231,7 @@ in }; ###### implementation - config = { + config = lib.mkIf config.security.enableWrappers { assertions = lib.mapAttrsToList (name: opts: @@ -249,8 +253,8 @@ in }; in { # These are mount related wrappers that require the +s permission. - fusermount = mkSetuidRoot "${pkgs.fuse}/bin/fusermount"; - fusermount3 = mkSetuidRoot "${pkgs.fuse3}/bin/fusermount3"; + fusermount = mkSetuidRoot "${lib.getBin pkgs.fuse}/bin/fusermount"; + fusermount3 = mkSetuidRoot "${lib.getBin pkgs.fuse3}/bin/fusermount3"; mount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount"; umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount"; }; diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix index a5cb0da29592..767f1add1cae 100644 --- a/nixos/modules/services/audio/jack.nix +++ b/nixos/modules/services/audio/jack.nix @@ -8,7 +8,7 @@ let pcmPlugin = cfg.jackd.enable && cfg.alsa.enable; loopback = cfg.jackd.enable && cfg.loopback.enable; - enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 && pkgs.pkgsi686Linux.alsa-lib != null; + enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.hostPlatform.isx86_64 && pkgs.pkgsi686Linux.alsa-lib != null; umaskNeeded = versionOlder cfg.jackd.package.version "1.9.12"; bridgeNeeded = versionAtLeast cfg.jackd.package.version "1.9.12"; @@ -260,7 +260,7 @@ in { systemd.services.jack-session = { description = "JACK session"; script = '' - jack_wait -w + ${pkgs.jack-example-tools}/bin/jack_wait -w ${cfg.jackd.session} ${lib.optionalString cfg.loopback.enable cfg.loopback.session} ''; diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix index 6c60fc2d4875..189923f0ed96 100644 --- a/nixos/modules/services/audio/snapserver.nix +++ b/nixos/modules/services/audio/snapserver.nix @@ -30,12 +30,12 @@ let lib.optionalString (val != null) "${val}"; os' = prefix: val: lib.optionalString (val != null) (prefix + "${val}"); - flatten = key: value: + toQueryString = key: value: "&${key}=${value}"; in "--stream.stream=\"${opt.type}://" + os opt.location + "?" + os' "name=" name + os' "&sampleformat=" opt.sampleFormat + os' "&codec=" opt.codec - + lib.concatStrings (lib.mapAttrsToList lib.flatten opt.query) + "\""; + + lib.concatStrings (lib.mapAttrsToList toQueryString opt.query) + "\""; optionalNull = val: ret: lib.optional (val != null) ret; @@ -80,6 +80,8 @@ in { ''; }; + package = lib.options.mkPackageOption pkgs "snapcast" { }; + listenAddress = lib.mkOption { type = lib.types.str; default = "::"; @@ -186,7 +188,8 @@ in { http.docRoot = lib.mkOption { type = with lib.types; nullOr path; - default = null; + default = pkgs.snapweb; + defaultText = lib.literalExpression "pkgs.snapweb"; description = '' Path to serve from the HTTP servers root. ''; @@ -285,7 +288,7 @@ in { serviceConfig = { DynamicUser = true; - ExecStart = "${pkgs.snapcast}/bin/snapserver --daemon ${optionString}"; + ExecStart = "${cfg.package}/bin/snapserver --daemon ${optionString}"; Type = "forking"; LimitRTPRIO = 50; LimitRTTIME = "infinity"; diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix index f7a86915d5bc..0851b5b0bf14 100644 --- a/nixos/modules/services/backup/bacula.nix +++ b/nixos/modules/services/backup/bacula.nix @@ -657,7 +657,7 @@ in { config = mkIf (fd_cfg.enable || sd_cfg.enable || dir_cfg.enable) { systemd.slices.system-bacula = { - description = "Bacula Slice"; + description = "Bacula Backup System Slice"; documentation = [ "man:bacula(8)" "https://www.bacula.org/" ]; }; diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix index 9e1abb85bfe5..6589fcd9466c 100644 --- a/nixos/modules/services/backup/tsm.nix +++ b/nixos/modules/services/backup/tsm.nix @@ -90,7 +90,7 @@ in environment.HOME = "/var/lib/tsm-backup"; serviceConfig = { # for exit status description see - # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=clients-client-return-codes + # https://www.ibm.com/docs/en/storage-protect/8.1.24?topic=clients-client-return-codes SuccessExitStatus = "4 8"; # The `-se` option must come after the command. # The `-optfile` option suppresses a `dsm.opt`-not-found warning. diff --git a/nixos/modules/services/blockchain/ethereum/geth.nix b/nixos/modules/services/blockchain/ethereum/geth.nix index adf9fc1db32a..52040f2b9ec5 100644 --- a/nixos/modules/services/blockchain/ethereum/geth.nix +++ b/nixos/modules/services/blockchain/ethereum/geth.nix @@ -103,7 +103,7 @@ let }; network = lib.mkOption { - type = lib.types.nullOr (lib.types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]); + type = lib.types.nullOr (lib.types.enum [ "goerli" "holesky" "rinkeby" "yolov2" "ropsten" ]); default = null; description = "The network to connect to. Mainnet (null) is the default ethereum network."; }; diff --git a/nixos/modules/services/blockchain/ethereum/lighthouse.nix b/nixos/modules/services/blockchain/ethereum/lighthouse.nix index 66a762763cb8..247174028adc 100644 --- a/nixos/modules/services/blockchain/ethereum/lighthouse.nix +++ b/nixos/modules/services/blockchain/ethereum/lighthouse.nix @@ -1,14 +1,18 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let - cfg = config.services.lighthouse; -in { - +in +{ options = { services.lighthouse = { beacon = lib.mkOption { description = "Beacon node"; - default = {}; + default = { }; type = lib.types.submodule { options = { enable = lib.mkEnableOption "Lightouse Beacon node"; @@ -133,7 +137,7 @@ in { validator = lib.mkOption { description = "Validator node"; - default = {}; + default = { }; type = lib.types.submodule { options = { enable = lib.mkOption { @@ -152,7 +156,7 @@ in { beaconNodes = lib.mkOption { type = lib.types.listOf lib.types.str; - default = ["http://localhost:5052"]; + default = [ "http://localhost:5052" ]; description = '' Beacon nodes to connect to. ''; @@ -190,7 +194,13 @@ in { }; network = lib.mkOption { - type = lib.types.enum [ "mainnet" "gnosis" "chiado" "sepolia" "holesky" ]; + type = lib.types.enum [ + "mainnet" + "gnosis" + "chiado" + "sepolia" + "holesky" + ]; default = "mainnet"; description = '' The network to connect to. Mainnet is the default ethereum network. @@ -205,19 +215,19 @@ in { default = ""; example = ""; }; + + package = lib.mkPackageOption pkgs "lighthouse" { }; }; }; config = lib.mkIf (cfg.beacon.enable || cfg.validator.enable) { - - environment.systemPackages = [ pkgs.lighthouse ] ; + environment.systemPackages = [ cfg.package ]; networking.firewall = lib.mkIf cfg.beacon.enable { allowedTCPPorts = lib.mkIf cfg.beacon.openFirewall [ cfg.beacon.port ]; allowedUDPPorts = lib.mkIf cfg.beacon.openFirewall [ cfg.beacon.port ]; }; - systemd.services.lighthouse-beacon = lib.mkIf cfg.beacon.enable { description = "Lighthouse beacon node (connect to P2P nodes and verify blocks)"; wantedBy = [ "multi-user.target" ]; @@ -227,7 +237,7 @@ in { # make sure the chain data directory is created on first run mkdir -p ${cfg.beacon.dataDir}/${cfg.network} - ${pkgs.lighthouse}/bin/lighthouse beacon_node \ + ${lib.getExe cfg.package} beacon_node \ --disable-upnp \ ${lib.optionalString cfg.beacon.disableDepositContractSync "--disable-deposit-contract-sync"} \ --port ${toString cfg.beacon.port} \ @@ -236,8 +246,8 @@ in { --datadir ${cfg.beacon.dataDir}/${cfg.network} \ --execution-endpoint http://${cfg.beacon.execution.address}:${toString cfg.beacon.execution.port} \ --execution-jwt ''${CREDENTIALS_DIRECTORY}/LIGHTHOUSE_JWT \ - ${lib.optionalString cfg.beacon.http.enable '' --http --http-address ${cfg.beacon.http.address} --http-port ${toString cfg.beacon.http.port}''} \ - ${lib.optionalString cfg.beacon.metrics.enable '' --metrics --metrics-address ${cfg.beacon.metrics.address} --metrics-port ${toString cfg.beacon.metrics.port}''} \ + ${lib.optionalString cfg.beacon.http.enable ''--http --http-address ${cfg.beacon.http.address} --http-port ${toString cfg.beacon.http.port}''} \ + ${lib.optionalString cfg.beacon.metrics.enable ''--metrics --metrics-address ${cfg.beacon.metrics.address} --metrics-port ${toString cfg.beacon.metrics.port}''} \ ${cfg.extraArgs} ${cfg.beacon.extraArgs} ''; serviceConfig = { @@ -262,7 +272,10 @@ in { RestrictNamespaces = true; LockPersonality = true; RemoveIPC = true; - SystemCallFilter = [ "@system-service" "~@privileged" ]; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; }; }; @@ -275,7 +288,7 @@ in { # make sure the chain data directory is created on first run mkdir -p ${cfg.validator.dataDir}/${cfg.network} - ${pkgs.lighthouse}/bin/lighthouse validator_client \ + ${lib.getExe cfg.package} validator_client \ --network ${cfg.network} \ --beacon-nodes ${lib.concatStringsSep "," cfg.validator.beaconNodes} \ --datadir ${cfg.validator.dataDir}/${cfg.network} \ @@ -305,8 +318,14 @@ in { RestrictNamespaces = true; LockPersonality = true; RemoveIPC = true; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; - SystemCallFilter = [ "@system-service" "~@privileged" ]; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; }; }; }; diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix index 01608d33d8d1..9f01a00aad6d 100644 --- a/nixos/modules/services/computing/boinc/client.nix +++ b/nixos/modules/services/computing/boinc/client.nix @@ -95,7 +95,7 @@ in after = ["network.target"]; wantedBy = ["multi-user.target"]; script = '' - ${fhsEnvExecutable} --dir ${cfg.dataDir} ${allowRemoteGuiRpcFlag} + exec ${fhsEnvExecutable} --dir ${cfg.dataDir} ${allowRemoteGuiRpcFlag} ''; serviceConfig = { User = "boinc"; diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix index f4944d3ce318..dd739798963c 100644 --- a/nixos/modules/services/computing/slurm/slurm.nix +++ b/nixos/modules/services/computing/slurm/slurm.nix @@ -32,6 +32,12 @@ let ${cfg.extraCgroupConfig} ''; + mpiConf = pkgs.writeTextDir "mpi.conf" + '' + PMIxCliTmpDirBase=${cfg.mpi.PmixCliTmpDirBase} + ${cfg.mpi.extraMpiConfig} + ''; + slurmdbdConf = pkgs.writeText "slurmdbd.conf" '' DbdHost=${cfg.dbdserver.dbdHost} @@ -45,7 +51,7 @@ let # in the same directory as slurm.conf etcSlurm = pkgs.symlinkJoin { name = "etc-slurm"; - paths = [ configFile cgroupConfig plugStackConfig ] ++ cfg.extraConfigPaths; + paths = [ configFile cgroupConfig plugStackConfig mpiConf ] ++ cfg.extraConfigPaths; }; in @@ -242,6 +248,24 @@ in ''; }; + mpi = { + PmixCliTmpDirBase = lib.mkOption { + default = "/tmp/pmix"; + type = lib.types.str; + description = '' + Base path for PMIx temporary files. + ''; + }; + + extraMpiConfig = lib.mkOption { + default = ""; + type = lib.types.lines; + description = '' + Extra configuration for that will be added to `mpi.conf`. + ''; + }; + }; + extraPlugstackConfig = lib.mkOption { default = ""; type = lib.types.lines; @@ -372,8 +396,9 @@ in }; }; - systemd.tmpfiles.rules = lib.mkIf cfg.client.enable [ + systemd.tmpfiles.rules = lib.optionals cfg.client.enable [ "d /var/spool/slurmd 755 root root -" + "d ${cfg.mpi.PmixCliTmpDirBase} 755 root root -" ]; services.openssh.settings.X11Forwarding = lib.mkIf cfg.client.enable (lib.mkDefault true); diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix index 300e4e72a01e..617d58450326 100644 --- a/nixos/modules/services/continuous-integration/buildbot/master.nix +++ b/nixos/modules/services/continuous-integration/buildbot/master.nix @@ -4,8 +4,8 @@ let cfg = config.services.buildbot-master; opt = options.services.buildbot-master; - package = pkgs.python3.pkgs.toPythonModule cfg.package; - python = package.pythonModule; + package = cfg.package.python.pkgs.toPythonModule cfg.package; + python = cfg.package.python; escapeStr = lib.escape [ "'" ]; @@ -93,13 +93,13 @@ in { }; extraConfig = lib.mkOption { - type = lib.types.str; + type = lib.types.lines; description = "Extra configuration to append to master.cfg"; default = "c['buildbotNetUsageData'] = None"; }; extraImports = lib.mkOption { - type = lib.types.str; + type = lib.types.lines; description = "Extra python imports to prepend to master.cfg"; default = ""; example = "from buildbot.process.project import Project"; diff --git a/nixos/modules/services/continuous-integration/github-runner/service.nix b/nixos/modules/services/continuous-integration/github-runner/service.nix index 4b1fc230c2d3..ab2ebb7a498d 100644 --- a/nixos/modules/services/continuous-integration/github-runner/service.nix +++ b/nixos/modules/services/continuous-integration/github-runner/service.nix @@ -19,7 +19,9 @@ with lib; ]) ); - config.systemd.services = flip mapAttrs' config.services.github-runners (name: cfg: + config.systemd.services = + let enabledRunners = filterAttrs (_: cfg: cfg.enable) config.services.github-runners; + in (flip mapAttrs' enabledRunners (name: cfg: let svcName = "github-runner-${name}"; systemdDir = "github-runner/${name}"; @@ -296,5 +298,5 @@ with lib; cfg.serviceOverrides ]; } - ); + )); } diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix index a8a8973f25ba..45d6b2428a46 100644 --- a/nixos/modules/services/continuous-integration/hydra/default.nix +++ b/nixos/modules/services/continuous-integration/hydra/default.nix @@ -311,7 +311,7 @@ in ]; systemd.slices.system-hydra = { - description = "Hydra Slice"; + description = "Hydra CI Server Slice"; documentation = [ "file://${cfg.package}/share/doc/hydra/index.html" "https://nixos.org/hydra/manual/" ]; }; @@ -330,12 +330,12 @@ in ln -sf ${hydraConf} ${baseDir}/hydra.conf - mkdir -m 0700 -p ${baseDir}/www + mkdir -m 0700 ${baseDir}/www || true chown hydra-www:hydra ${baseDir}/www - mkdir -m 0700 -p ${baseDir}/queue-runner - mkdir -m 0750 -p ${baseDir}/build-logs - mkdir -m 0750 -p ${baseDir}/runcommand-logs + mkdir -m 0700 ${baseDir}/queue-runner || true + mkdir -m 0750 ${baseDir}/build-logs || true + mkdir -m 0750 ${baseDir}/runcommand-logs || true chown hydra-queue-runner:hydra \ ${baseDir}/queue-runner \ ${baseDir}/build-logs \ @@ -362,8 +362,8 @@ in # Move legacy hydra-www roots. if [ -e /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots ]; then - find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f \ - | xargs -r mv -f -t ${cfg.gcRootsDir}/ + find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f -print0 \ + | xargs -0 -r mv -f -t ${cfg.gcRootsDir}/ rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots fi @@ -520,7 +520,7 @@ in elif [[ $compression == zstd ]]; then compression="zstd --rm" fi - find ${baseDir}/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r "$compression" --force --quiet + find ${baseDir}/build-logs -type f -name "*.drv" -mtime +3 -size +0c -print0 | xargs -0 -r "$compression" --force --quiet ''; startAt = "Sun 01:45"; serviceConfig.Slice = "system-hydra.slice"; diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix index c0599a65b480..d5a6b93a6cf8 100644 --- a/nixos/modules/services/continuous-integration/jenkins/slave.nix +++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix @@ -1,6 +1,7 @@ { config, lib, pkgs, ... }: -with lib; + let + inherit (lib) mkIf mkOption types; cfg = config.services.jenkinsSlave; masterCfg = config.services.jenkins; in { @@ -47,16 +48,16 @@ in { ''; }; - javaPackage = mkPackageOption pkgs "jdk" { }; + javaPackage = lib.mkPackageOption pkgs "jdk" { }; }; }; config = mkIf (cfg.enable && !masterCfg.enable) { - users.groups = optionalAttrs (cfg.group == "jenkins") { + users.groups = lib.optionalAttrs (cfg.group == "jenkins") { jenkins.gid = config.ids.gids.jenkins; }; - users.users = optionalAttrs (cfg.user == "jenkins") { + users.users = lib.optionalAttrs (cfg.user == "jenkins") { jenkins = { description = "jenkins user"; createHome = true; diff --git a/nixos/modules/services/databases/influxdb2.nix b/nixos/modules/services/databases/influxdb2.nix index a534cdfbe165..20a94c03994b 100644 --- a/nixos/modules/services/databases/influxdb2.nix +++ b/nixos/modules/services/databases/influxdb2.nix @@ -67,16 +67,16 @@ let inherit (cfg.provision) organizations users; }); - provisioningScript = pkgs.writeShellScript "post-start-provision" '' - set -euo pipefail - export INFLUX_HOST="http://"${escapeShellArg ( + influxHost = "http://${escapeShellArg ( if ! hasAttr "http-bind-address" cfg.settings || hasInfix "0.0.0.0" cfg.settings.http-bind-address then "localhost:8086" else cfg.settings.http-bind-address - )} + )}"; - # Wait for the influxdb server to come online + waitUntilServiceIsReady = pkgs.writeShellScript "wait-until-service-is-ready" '' + set -euo pipefail + export INFLUX_HOST=${influxHost} count=0 while ! influx ping &>/dev/null; do if [ "$count" -eq 300 ]; then @@ -92,6 +92,11 @@ let sleep 0.1 count=$((count++)) done + ''; + + provisioningScript = pkgs.writeShellScript "post-start-provision" '' + set -euo pipefail + export INFLUX_HOST=${influxHost} # Do the initial database setup. Pass /dev/null as configs-path to # avoid saving the token as the active config. @@ -433,6 +438,7 @@ in ZONEINFO = "${pkgs.tzdata}/share/zoneinfo"; }; serviceConfig = { + Type = "exec"; # When credentials are used with systemd before v257 this is necessary to make the service start reliably (see systemd/systemd#33953) ExecStart = "${cfg.package}/bin/influxd --bolt-path \${STATE_DIRECTORY}/influxd.bolt --engine-path \${STATE_DIRECTORY}/engine"; StateDirectory = "influxdb2"; User = "influxdb2"; @@ -447,11 +453,13 @@ in "admin-token:${cfg.provision.initialSetup.tokenFile}" ]; - ExecStartPost = mkIf cfg.provision.enable ( + ExecStartPost = [ + waitUntilServiceIsReady + ] ++ (lib.optionals cfg.provision.enable ( [provisioningScript] ++ # Only the restarter runs with elevated privileges optional anyAuthDefined "+${restarterScript}" - ); + )); }; path = [ diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix index 4b2e83e71e20..40844b26d0da 100644 --- a/nixos/modules/services/databases/mysql.nix +++ b/nixos/modules/services/databases/mysql.nix @@ -1,7 +1,4 @@ { config, lib, pkgs, ... }: - -with lib; - let cfg = config.services.mysql; @@ -9,7 +6,7 @@ let isMariaDB = lib.getName cfg.package == lib.getName pkgs.mariadb; isOracle = lib.getName cfg.package == lib.getName pkgs.mysql80; # Oracle MySQL has supported "notify" service type since 8.0 - hasNotify = isMariaDB || (isOracle && versionAtLeast cfg.package.version "8.0"); + hasNotify = isMariaDB || (isOracle && lib.versionAtLeast cfg.package.version "8.0"); mysqldOptions = "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}"; @@ -21,11 +18,11 @@ in { imports = [ - (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.") - (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.") - (mkRemovedOptionModule [ "services" "mysql" "extraOptions" ] "Use services.mysql.settings.mysqld instead.") - (mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.") - (mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.") + (lib.mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.") + (lib.mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.") + (lib.mkRemovedOptionModule [ "services" "mysql" "extraOptions" ] "Use services.mysql.settings.mysqld instead.") + (lib.mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.") + (lib.mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.") ]; ###### interface @@ -34,18 +31,18 @@ in services.mysql = { - enable = mkEnableOption "MySQL server"; + enable = lib.mkEnableOption "MySQL server"; - package = mkOption { - type = types.package; - example = literalExpression "pkgs.mariadb"; + package = lib.mkOption { + type = lib.types.package; + example = lib.literalExpression "pkgs.mariadb"; description = '' Which MySQL derivation to use. MariaDB packages are supported too. ''; }; - user = mkOption { - type = types.str; + user = lib.mkOption { + type = lib.types.str; default = "mysql"; description = '' User account under which MySQL runs. @@ -58,8 +55,8 @@ in ''; }; - group = mkOption { - type = types.str; + group = lib.mkOption { + type = lib.types.str; default = "mysql"; description = '' Group account under which MySQL runs. @@ -72,8 +69,8 @@ in ''; }; - dataDir = mkOption { - type = types.path; + dataDir = lib.mkOption { + type = lib.types.path; example = "/var/lib/mysql"; description = '' The data directory for MySQL. @@ -85,8 +82,8 @@ in ''; }; - configFile = mkOption { - type = types.path; + configFile = lib.mkOption { + type = lib.types.path; default = configFile; defaultText = '' A configuration file automatically generated by NixOS. @@ -95,7 +92,7 @@ in Override the configuration file used by MySQL. By default, NixOS generates one automatically from {option}`services.mysql.settings`. ''; - example = literalExpression '' + example = lib.literalExpression '' pkgs.writeText "my.cnf" ''' [mysqld] datadir = /var/lib/mysql @@ -107,7 +104,7 @@ in ''; }; - settings = mkOption { + settings = lib.mkOption { type = format.type; default = {}; description = '' @@ -123,7 +120,7 @@ in `1`, or `0`. See the provided example below. ::: ''; - example = literalExpression '' + example = lib.literalExpression '' { mysqld = { key_buffer_size = "6G"; @@ -139,17 +136,17 @@ in ''; }; - initialDatabases = mkOption { - type = types.listOf (types.submodule { + initialDatabases = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { options = { - name = mkOption { - type = types.str; + name = lib.mkOption { + type = lib.types.str; description = '' The name of the database to create. ''; }; - schema = mkOption { - type = types.nullOr types.path; + schema = lib.mkOption { + type = lib.types.nullOr lib.types.path; default = null; description = '' The initial schema of the database; if null (the default), @@ -163,7 +160,7 @@ in List of database names and their initial schemas that should be used to create databases on the first startup of MySQL. The schema attribute is optional: If not specified, an empty database is created. ''; - example = literalExpression '' + example = lib.literalExpression '' [ { name = "foodatabase"; schema = ./foodatabase.sql; } { name = "bardatabase"; } @@ -171,14 +168,14 @@ in ''; }; - initialScript = mkOption { - type = types.nullOr types.path; + initialScript = lib.mkOption { + type = lib.types.nullOr lib.types.path; default = null; description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database."; }; - ensureDatabases = mkOption { - type = types.listOf types.str; + ensureDatabases = lib.mkOption { + type = lib.types.listOf lib.types.str; default = []; description = '' Ensures that the specified databases exist. @@ -192,17 +189,17 @@ in ]; }; - ensureUsers = mkOption { - type = types.listOf (types.submodule { + ensureUsers = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { options = { - name = mkOption { - type = types.str; + name = lib.mkOption { + type = lib.types.str; description = '' Name of the user to ensure. ''; }; - ensurePermissions = mkOption { - type = types.attrsOf types.str; + ensurePermissions = lib.mkOption { + type = lib.types.attrsOf lib.types.str; default = {}; description = '' Permissions to ensure for the user, specified as attribute set. @@ -216,7 +213,7 @@ in [GRANT syntax](https://mariadb.com/kb/en/library/grant/). The attributes are used as `GRANT ''${attrName} ON ''${attrValue}`. ''; - example = literalExpression '' + example = lib.literalExpression '' { "database.*" = "ALL PRIVILEGES"; "*.*" = "SELECT, LOCK TABLES"; @@ -234,7 +231,7 @@ in option is changed. This means that users created and permissions assigned once through this option or otherwise have to be removed manually. ''; - example = literalExpression '' + example = lib.literalExpression '' [ { name = "nextcloud"; @@ -253,40 +250,40 @@ in }; replication = { - role = mkOption { - type = types.enum [ "master" "slave" "none" ]; + role = lib.mkOption { + type = lib.types.enum [ "master" "slave" "none" ]; default = "none"; description = "Role of the MySQL server instance."; }; - serverId = mkOption { - type = types.int; + serverId = lib.mkOption { + type = lib.types.int; default = 1; description = "Id of the MySQL server instance. This number must be unique for each instance."; }; - masterHost = mkOption { - type = types.str; + masterHost = lib.mkOption { + type = lib.types.str; description = "Hostname of the MySQL master server."; }; - slaveHost = mkOption { - type = types.str; + slaveHost = lib.mkOption { + type = lib.types.str; description = "Hostname of the MySQL slave server."; }; - masterUser = mkOption { - type = types.str; + masterUser = lib.mkOption { + type = lib.types.str; description = "Username of the MySQL replication user."; }; - masterPassword = mkOption { - type = types.str; + masterPassword = lib.mkOption { + type = lib.types.str; description = "Password of the MySQL replication user."; }; - masterPort = mkOption { - type = types.port; + masterPort = lib.mkOption { + type = lib.types.port; default = 3306; description = "Port number on which the MySQL master server runs."; }; @@ -298,30 +295,30 @@ in ###### implementation - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { services.mysql.dataDir = - mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql" + lib.mkDefault (if lib.versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql" else "/var/mysql"); - services.mysql.settings.mysqld = mkMerge [ + services.mysql.settings.mysqld = lib.mkMerge [ { datadir = cfg.dataDir; - port = mkDefault 3306; + port = lib.mkDefault 3306; } - (mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") { + (lib.mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") { log-bin = "mysql-bin-${toString cfg.replication.serverId}"; log-bin-index = "mysql-bin-${toString cfg.replication.serverId}.index"; relay-log = "mysql-relay-bin"; server-id = cfg.replication.serverId; binlog-ignore-db = [ "information_schema" "performance_schema" "mysql" ]; }) - (mkIf (!isMariaDB) { + (lib.mkIf (!isMariaDB) { plugin-load-add = "auth_socket.so"; }) ]; - users.users = optionalAttrs (cfg.user == "mysql") { + users.users = lib.optionalAttrs (cfg.user == "mysql") { mysql = { description = "MySQL server user"; group = cfg.group; @@ -329,7 +326,7 @@ in }; }; - users.groups = optionalAttrs (cfg.group == "mysql") { + users.groups = lib.optionalAttrs (cfg.group == "mysql") { mysql.gid = config.ids.gids.mysql; }; @@ -337,6 +334,12 @@ in environment.etc."my.cnf".source = cfg.configFile; + # The mysql_install_db binary will try to adjust the permissions, but fail to do so with a permission + # denied error in some circumstances. Setting the permissions manually with tmpfiles is a workaround. + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.group} - -" + ]; + systemd.services.mysql = { description = "MySQL Server"; @@ -380,7 +383,7 @@ in # The super user account to use on *first* run of MySQL server superUser = if isMariaDB then cfg.user else "root"; in '' - ${optionalString (!hasNotify) '' + ${lib.optionalString (!hasNotify) '' # Wait until the MySQL server is available for use while [ ! -e /run/mysqld/mysqld.sock ] do @@ -397,13 +400,13 @@ in echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;" ) | ${cfg.package}/bin/mysql -u ${superUser} -N - ${concatMapStrings (database: '' + ${lib.concatMapStrings (database: '' # Create initial databases if ! test -e "${cfg.dataDir}/${database.name}"; then echo "Creating initial database: ${database.name}" ( echo 'create database `${database.name}`;' - ${optionalString (database.schema != null) '' + ${lib.optionalString (database.schema != null) '' echo 'use `${database.name}`;' # TODO: this silently falls through if database.schema does not exist, @@ -420,7 +423,7 @@ in fi '') cfg.initialDatabases} - ${optionalString (cfg.replication.role == "master") + ${lib.optionalString (cfg.replication.role == "master") '' # Set up the replication master @@ -431,7 +434,7 @@ in ) | ${cfg.package}/bin/mysql -u ${superUser} -N ''} - ${optionalString (cfg.replication.role == "slave") + ${lib.optionalString (cfg.replication.role == "slave") '' # Set up the replication slave @@ -441,7 +444,7 @@ in ) | ${cfg.package}/bin/mysql -u ${superUser} -N ''} - ${optionalString (cfg.initialScript != null) + ${lib.optionalString (cfg.initialScript != null) '' # Execute initial script # using toString to avoid copying the file to nix store if given as path instead of string, @@ -452,25 +455,25 @@ in rm ${cfg.dataDir}/mysql_init fi - ${optionalString (cfg.ensureDatabases != []) '' + ${lib.optionalString (cfg.ensureDatabases != []) '' ( - ${concatMapStrings (database: '' + ${lib.concatMapStrings (database: '' echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;" '') cfg.ensureDatabases} ) | ${cfg.package}/bin/mysql -N ''} - ${concatMapStrings (user: + ${lib.concatMapStrings (user: '' ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};" - ${concatStringsSep "\n" (mapAttrsToList (database: permission: '' + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (database: permission: '' echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';" '') user.ensurePermissions)} ) | ${cfg.package}/bin/mysql -N '') cfg.ensureUsers} ''; - serviceConfig = mkMerge [ + serviceConfig = lib.mkMerge [ { Type = if hasNotify then "notify" else "simple"; Restart = "on-abort"; @@ -506,7 +509,7 @@ in # System Call Filtering SystemCallArchitectures = "native"; } - (mkIf (cfg.dataDir == "/var/lib/mysql") { + (lib.mkIf (cfg.dataDir == "/var/lib/mysql") { StateDirectory = "mysql"; StateDirectoryMode = "0700"; }) diff --git a/nixos/modules/services/databases/postgresql.md b/nixos/modules/services/databases/postgresql.md index 77c87e3f063b..6cd3defb24a3 100644 --- a/nixos/modules/services/databases/postgresql.md +++ b/nixos/modules/services/databases/postgresql.md @@ -187,7 +187,7 @@ $ nix-instantiate --eval -A postgresql_13.psqlSchema ``` For an upgrade, a script like this can be used to simplify the process: ```nix -{ config, pkgs, ... }: +{ config, lib, pkgs, ... }: { environment.systemPackages = [ (let @@ -196,6 +196,7 @@ For an upgrade, a script like this can be used to simplify the process: newPostgres = pkgs.postgresql_13.withPackages (pp: [ # pp.plv8 ]); + cfg = config.services.postgresql; in pkgs.writeScriptBin "upgrade-pg-cluster" '' set -eux # XXX it's perhaps advisable to stop all services that depend on postgresql @@ -205,12 +206,12 @@ For an upgrade, a script like this can be used to simplify the process: export NEWBIN="${newPostgres}/bin" - export OLDDATA="${config.services.postgresql.dataDir}" - export OLDBIN="${config.services.postgresql.package}/bin" + export OLDDATA="${cfg.dataDir}" + export OLDBIN="${cfg.package}/bin" install -d -m 0700 -o postgres -g postgres "$NEWDATA" cd "$NEWDATA" - sudo -u postgres $NEWBIN/initdb -D "$NEWDATA" + sudo -u postgres $NEWBIN/initdb -D "$NEWDATA" ${lib.escapeShellArgs cfg.initdbArgs} sudo -u postgres $NEWBIN/pg_upgrade \ --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \ @@ -363,6 +364,24 @@ postgresql.withJIT.pname evaluates to `"foobar"`. +## Service hardening {#module-services-postgres-hardening} + +The service created by the [`postgresql`-module](#opt-services.postgresql.enable) uses +several common hardening options from `systemd`, most notably: + +* Memory pages must not be both writable and executable (this only applies to non-JIT setups). +* A system call filter (see {manpage}`systemd.exec(5)` for details on `@system-service`). +* A stricter default UMask (`0027`). +* Only sockets of type `AF_INET`/`AF_INET6`/`AF_NETLINK`/`AF_UNIX` allowed. +* Restricted filesystem access (private `/tmp`, most of the file-system hierachy is mounted read-only, only process directories in `/proc` that are owned by the same user). + +The NixOS module also contains necessary adjustments for extensions from `nixpkgs` +if these are enabled. If an extension or a postgresql feature from `nixpkgs` breaks +with hardening, it's considered a bug. + +When using extensions that are not packaged in `nixpkgs`, hardening adjustments may +become necessary. + ## Notable differences to upstream {#module-services-postgres-upstream-deviation} - To avoid circular dependencies between default and -dev outputs, the output of the `pg_config` system view has been removed. diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 55b3dd282ec4..876969ef9bb5 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -7,6 +7,7 @@ let concatStringsSep const elem + escapeShellArgs filterAttrs isString literalExpression @@ -545,7 +546,7 @@ in rm -f ${cfg.dataDir}/*.conf # Initialise the database. - initdb -U ${cfg.superUser} ${concatStringsSep " " cfg.initdbArgs} + initdb -U ${cfg.superUser} ${escapeShellArgs cfg.initdbArgs} # See postStart! touch "${cfg.dataDir}/.first_startup" @@ -622,7 +623,46 @@ in TimeoutSec = 120; ExecStart = "${postgresql}/bin/postgres"; + + # Hardening + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "strict"; + MemoryDenyWriteExecute = lib.mkDefault (cfg.settings.jit == "off"); + NoNewPrivileges = true; + LockPersonality = true; + PrivateDevices = true; + PrivateMounts = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" # used for network interface enumeration + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged @resources" + ]; + UMask = if groupAccessAvailable then "0027" else "0077"; } + (mkIf (cfg.dataDir != "/var/lib/postgresql") { + ReadWritePaths = [ cfg.dataDir ]; + }) (mkIf (cfg.dataDir == "/var/lib/postgresql/${cfg.package.psqlSchema}") { StateDirectory = "postgresql postgresql/${cfg.package.psqlSchema}"; StateDirectoryMode = if groupAccessAvailable then "0750" else "0700"; diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix index 7a3f408aa98e..43829b13c6c0 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -72,7 +72,28 @@ in { defaultText = literalExpression '' if name == "" then "redis" else "redis-''${name}" ''; - description = "The username and groupname for redis-server."; + description = '' + User account under which this instance of redis-server runs. + + ::: {.note} + If left as the default value this user will automatically be + created on system activation, otherwise you are responsible for + ensuring the user exists before the redis service starts. + ''; + }; + + group = mkOption { + type = types.str; + default = config.user; + defaultText = literalExpression "config.user"; + description = '' + Group account under which this instance of redis-server runs. + + ::: {.note} + If left as the default value this group will automatically be + created on system activation, otherwise you are responsible for + ensuring the group exists before the redis service starts. + ''; }; port = mkOption { @@ -337,7 +358,7 @@ in { redisConfStore = redisConfig conf.settings; in '' touch "${redisConfVar}" "${redisConfRun}" - chown '${conf.user}' "${redisConfVar}" "${redisConfRun}" + chown '${conf.user}':'${conf.group}' "${redisConfVar}" "${redisConfRun}" chmod 0600 "${redisConfVar}" "${redisConfRun}" if [ ! -s ${redisConfVar} ]; then echo 'include "${redisConfRun}"' > "${redisConfVar}" @@ -353,7 +374,7 @@ in { Type = "notify"; # User and group User = conf.user; - Group = conf.user; + Group = conf.group; # Runtime directory and mode RuntimeDirectory = redisName name; RuntimeDirectoryMode = "0750"; diff --git a/nixos/modules/services/databases/surrealdb.nix b/nixos/modules/services/databases/surrealdb.nix index b88129ebc6f4..d38b7b311f1f 100644 --- a/nixos/modules/services/databases/surrealdb.nix +++ b/nixos/modules/services/databases/surrealdb.nix @@ -14,9 +14,9 @@ in { type = lib.types.str; description = '' The path that surrealdb will write data to. Use null for in-memory. - Can be one of "memory", "file://:path", "tikv://:addr". + Can be one of "memory", "rocksdb://:path", "surrealkv://:path", "tikv://:addr", "fdb://:addr". ''; - default = "file:///var/lib/surrealdb/"; + default = "rocksdb:///var/lib/surrealdb/"; example = "memory"; }; @@ -41,10 +41,9 @@ in { extraFlags = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; - example = [ "--allow-all" "--auth" "--user root" "--pass root" ]; + example = [ "--allow-all" "--user" "root" "--pass" "root" ]; description = '' - Specify a list of additional command line flags, - which get escaped and are then passed to surrealdb. + Specify a list of additional command line flags. ''; }; }; @@ -61,7 +60,7 @@ in { after = [ "network.target" ]; serviceConfig = { - ExecStart = "${cfg.package}/bin/surreal start --bind ${cfg.host}:${toString cfg.port} ${lib.escapeShellArgs cfg.extraFlags} -- ${cfg.dbPath}"; + ExecStart = "${cfg.package}/bin/surreal start --bind ${cfg.host}:${toString cfg.port} ${lib.strings.concatStringsSep " " cfg.extraFlags} -- ${cfg.dbPath}"; DynamicUser = true; Restart = "on-failure"; StateDirectory = "surrealdb"; diff --git a/nixos/modules/services/databases/tigerbeetle.md b/nixos/modules/services/databases/tigerbeetle.md index 12d920e7bcc7..7cf3bedda5e0 100644 --- a/nixos/modules/services/databases/tigerbeetle.md +++ b/nixos/modules/services/databases/tigerbeetle.md @@ -35,3 +35,10 @@ Note that the TigerBeetle module won't open any firewall ports automatically, so A complete list of options for TigerBeetle can be found [here](#opt-services.tigerbeetle.enable). +## Upgrading {#module-services-tigerbeetle-upgrading} + +Usually, TigerBeetle's [upgrade process](https://docs.tigerbeetle.com/operating/upgrading) only requires replacing the binary used for the servers. +This is not directly possible with NixOS since the new binary will be located at a different place in the Nix store. + +However, since TigerBeetle is managed through systemd on NixOS, the only action you need to take when upgrading is to make sure the version of TigerBeetle you're upgrading to supports upgrades from the version you're currently running. +This information will be on the [release notes](https://github.com/tigerbeetle/tigerbeetle/releases) for the version you're upgrading to. diff --git a/nixos/modules/services/databases/tigerbeetle.nix b/nixos/modules/services/databases/tigerbeetle.nix index a9c7a24250a6..7b512055e44e 100644 --- a/nixos/modules/services/databases/tigerbeetle.nix +++ b/nixos/modules/services/databases/tigerbeetle.nix @@ -42,8 +42,8 @@ in }; cacheGridSize = mkOption { - type = types.strMatching "[0-9]+(K|M|G)B"; - default = "1GB"; + type = types.strMatching "[0-9]+(K|M|G)iB"; + default = "1GiB"; description = '' The grid cache size. The grid cache acts like a page cache for TigerBeetle. @@ -97,16 +97,26 @@ in ''; serviceConfig = { - Type = "exec"; - + DevicePolicy = "closed"; DynamicUser = true; + ExecStart = "${lib.getExe cfg.package} start --cache-grid=${cfg.cacheGridSize} --addresses=${lib.escapeShellArg (builtins.concatStringsSep "," cfg.addresses)} ${replicaDataPath}"; + LockPersonality = true; + ProtectClock = true; + ProtectControlGroups = true; ProtectHome = true; - DevicePolicy = "closed"; - + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; StateDirectory = "tigerbeetle"; StateDirectoryMode = 700; - - ExecStart = "${lib.getExe cfg.package} start --cache-grid=${cfg.cacheGridSize} --addresses=${lib.escapeShellArg (builtins.concatStringsSep "," cfg.addresses)} ${replicaDataPath}"; + Type = "exec"; }; }; diff --git a/nixos/modules/services/databases/victoriametrics.nix b/nixos/modules/services/databases/victoriametrics.nix index 923163a8049e..b26ad47c9a13 100644 --- a/nixos/modules/services/databases/victoriametrics.nix +++ b/nixos/modules/services/databases/victoriametrics.nix @@ -1,65 +1,188 @@ -{ config, pkgs, lib, ... }: -let cfg = config.services.victoriametrics; in { - options.services.victoriametrics = with lib; { - enable = mkEnableOption "VictoriaMetrics, a time series database, long-term remote storage for Prometheus"; + config, + pkgs, + lib, + ... +}: +with lib; +let + cfg = config.services.victoriametrics; + settingsFormat = pkgs.formats.yaml { }; + + startCLIList = + [ + "${cfg.package}/bin/victoria-metrics" + "-storageDataPath=/var/lib/${cfg.stateDir}" + "-httpListenAddr=${cfg.listenAddress}" + + ] + ++ lib.optionals (cfg.retentionPeriod != null) [ "-retentionPeriod=${cfg.retentionPeriod}" ] + ++ cfg.extraOptions; + prometheusConfigYml = checkedConfig ( + settingsFormat.generate "prometheusConfig.yaml" cfg.prometheusConfig + ); + + checkedConfig = + file: + pkgs.runCommand "checked-config" { nativeBuildInputs = [ cfg.package ]; } '' + ln -s ${file} $out + ${lib.escapeShellArgs startCLIList} -promscrape.config=${file} -dryRun + ''; +in +{ + options.services.victoriametrics = { + enable = mkEnableOption "VictoriaMetrics is a fast, cost-effective and scalable monitoring solution and time series database."; package = mkPackageOption pkgs "victoriametrics" { }; + listenAddress = mkOption { default = ":8428"; type = types.str; description = '' - The listen address for the http interface. + TCP address to listen for incoming http requests. ''; }; + + stateDir = mkOption { + type = types.str; + default = "victoriametrics"; + description = '' + Directory below `/var/lib` to store VictoriaMetrics metrics data. + This directory will be created automatically using systemd's StateDirectory mechanism. + ''; + }; + retentionPeriod = mkOption { - type = types.int; - default = 1; + type = types.nullOr types.str; + default = null; + example = "15d"; + description = '' + How long to retain samples in storage. + The minimum retentionPeriod is 24h or 1d. See also -retentionFilter + The following optional suffixes are supported: s (second), h (hour), d (day), w (week), y (year). + If suffix isn't set, then the duration is counted in months (default 1) + ''; + }; + + prometheusConfig = lib.mkOption { + type = lib.types.submodule { freeformType = settingsFormat.type; }; + default = { }; + example = literalExpression '' + { + scrape_configs = [ + { + job_name = "postgres-exporter"; + metrics_path = "/metrics"; + static_configs = [ + { + targets = ["1.2.3.4:9187"]; + labels.type = "database"; + } + ]; + } + { + job_name = "node-exporter"; + metrics_path = "/metrics"; + static_configs = [ + { + targets = ["1.2.3.4:9100"]; + labels.type = "node"; + } + { + targets = ["5.6.7.8:9100"]; + labels.type = "node"; + } + ]; + } + ]; + } + ''; description = '' - Retention period in months. + Config for prometheus style metrics. + See the docs: <https://docs.victoriametrics.com/vmagent/#how-to-collect-metrics-in-prometheus-format> + for more information. ''; }; + extraOptions = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; + example = literalExpression '' + [ + "-httpAuth.username=username" + "-httpAuth.password=file:///abs/path/to/file" + "-loggerLevel=WARN" + ] + ''; description = '' - Extra options to pass to VictoriaMetrics. See the README: - <https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md> - or {command}`victoriametrics -help` for more - information. + Extra options to pass to VictoriaMetrics. See the docs: + <https://docs.victoriametrics.com/single-server-victoriametrics/#list-of-command-line-flags> + or {command}`victoriametrics -help` for more information. ''; }; }; + config = lib.mkIf cfg.enable { systemd.services.victoriametrics = { description = "VictoriaMetrics time series database"; + wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; startLimitBurst = 5; + serviceConfig = { - Restart = "on-failure"; - RestartSec = 1; - StateDirectory = "victoriametrics"; + ExecStart = lib.escapeShellArgs ( + startCLIList + ++ lib.optionals (cfg.prometheusConfig != null) [ "-promscrape.config=${prometheusConfigYml}" ] + ); + DynamicUser = true; - ExecStart = '' - ${cfg.package}/bin/victoria-metrics \ - -storageDataPath=/var/lib/victoriametrics \ - -httpListenAddr ${cfg.listenAddress} \ - -retentionPeriod ${toString cfg.retentionPeriod} \ - ${lib.escapeShellArgs cfg.extraOptions} - ''; - # victoriametrics 1.59 with ~7GB of data seems to eventually panic when merging files and then - # begins restart-looping forever. Set LimitNOFILE= to a large number to work around this issue. - # - # panic: FATAL: unrecoverable error when merging small parts in the partition "/var/lib/victoriametrics/data/small/2021_08": - # cannot open source part for merging: cannot open values file in stream mode: - # cannot open file "/var/lib/victoriametrics/data/small/2021_08/[...]/values.bin": - # open /var/lib/victoriametrics/data/small/2021_08/[...]/values.bin: too many open files + RestartSec = 1; + Restart = "on-failure"; + RuntimeDirectory = "victoriametrics"; + RuntimeDirectoryMode = "0700"; + StateDirectory = cfg.stateDir; + StateDirectoryMode = "0700"; + + # Increase the limit to avoid errors like 'too many open files' when merging small parts LimitNOFILE = 1048576; + + # Hardening + DeviceAllow = [ "/dev/null rw" ]; + DevicePolicy = "strict"; + LockPersonality = true; + MemoryDenyWriteExecute = 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 = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; }; - wantedBy = [ "multi-user.target" ]; postStart = let - bindAddr = (lib.optionalString (lib.hasPrefix ":" cfg.listenAddress) "127.0.0.1") + cfg.listenAddress; + bindAddr = + (lib.optionalString (lib.hasPrefix ":" cfg.listenAddress) "127.0.0.1") + cfg.listenAddress; in lib.mkBefore '' until ${lib.getBin pkgs.curl}/bin/curl -s -o /dev/null http://${bindAddr}/ping; do diff --git a/nixos/modules/services/desktop-managers/lomiri.nix b/nixos/modules/services/desktop-managers/lomiri.nix index a8b17d274060..c8faa352a644 100644 --- a/nixos/modules/services/desktop-managers/lomiri.nix +++ b/nixos/modules/services/desktop-managers/lomiri.nix @@ -1,205 +1,258 @@ -{ config, pkgs, lib, ... }: +{ + config, + pkgs, + lib, + ... +}: let cfg = config.services.desktopManager.lomiri; -in { +in +{ options.services.desktopManager.lomiri = { enable = lib.mkEnableOption '' the Lomiri graphical shell (formerly known as Unity8) ''; + + basics = lib.mkOption { + internal = true; + description = '' + Enable basic things for getting Lomiri working. + ''; + type = lib.types.bool; + default = config.services.xserver.displayManager.lightdm.greeters.lomiri.enable || cfg.enable; + }; }; - config = lib.mkIf cfg.enable { - environment = { - systemPackages = (with pkgs; [ - glib # XDG MIME-related tools identify it as GNOME, add gio for MIME identification to work - libayatana-common - ubports-click - ]) ++ (with pkgs.lomiri; [ - content-hub + config = lib.mkMerge [ + # Basics for getting Lomiri to work + (lib.mkIf cfg.basics { + environment = { + # To override the default keyboard layout in Lomiri + etc.${pkgs.lomiri.lomiri.passthru.etcLayoutsFile}.text = lib.strings.replaceStrings [ "," ] [ + "\n" + ] config.services.xserver.xkb.layout; + + pathsToLink = [ + # Data + "/share/locale" # TODO LUITK hardcoded default locale path, fix individual apps to not rely on it + "/share/wallpapers" + ]; + + systemPackages = with pkgs.lomiri; [ + lomiri-wallpapers # default + additional wallpaper + suru-icon-theme # basic indicator icons + ]; + }; + + fonts.packages = with pkgs; [ + ubuntu-classic # Ubuntu is default font + ]; + + # Xwayland is partly hardcoded in Mir so it can't really be fully turned off, and it must be on PATH for X11 apps *and Lomiri's web browser* to work. + # Until Mir/Lomiri can be properly used without it, force it on so everything behaves as expected. + programs.xwayland.enable = lib.mkForce true; + + services.ayatana-indicators = { + enable = true; + packages = ( + with pkgs; + [ + ayatana-indicator-datetime # Clock + ayatana-indicator-session # Controls for shutting down etc + ] + ); + }; + }) + + # Full Lomiri DE + (lib.mkIf cfg.enable { + # We need the basic setup as well + services.desktopManager.lomiri.basics = true; + + environment = { + systemPackages = + (with pkgs; [ + glib # XDG MIME-related tools identify it as GNOME, add gio for MIME identification to work + libayatana-common + ubports-click + ]) + ++ (with pkgs.lomiri; [ + hfd-service + history-service + libusermetrics + lomiri + lomiri-calculator-app + lomiri-camera-app + lomiri-clock-app + lomiri-content-hub + lomiri-docviewer-app + lomiri-download-manager + lomiri-filemanager-app + lomiri-gallery-app + lomiri-polkit-agent + lomiri-schemas # exposes some required dbus interfaces + lomiri-session # wrappers to properly launch the session + lomiri-sounds + lomiri-system-settings + lomiri-terminal-app + lomiri-thumbnailer + lomiri-url-dispatcher + mediascanner2 # TODO possibly needs to be kicked off by graphical-session.target + morph-browser + qtmir # not having its desktop file for Xwayland available causes any X11 application to crash the session + telephony-service + teleports + ]); + }; + + hardware = { + bluetooth.enable = lib.mkDefault true; + }; + + networking.networkmanager.enable = lib.mkDefault true; + + systemd.packages = with pkgs.lomiri; [ + hfd-service + lomiri-download-manager + ]; + + services.dbus.packages = with pkgs.lomiri; [ hfd-service - history-service libusermetrics - lomiri - lomiri-calculator-app - lomiri-camera-app - lomiri-clock-app - lomiri-docviewer-app lomiri-download-manager - lomiri-filemanager-app - lomiri-gallery-app - lomiri-polkit-agent - lomiri-schemas # exposes some required dbus interfaces - lomiri-session # wrappers to properly launch the session - lomiri-sounds - lomiri-system-settings - lomiri-terminal-app - lomiri-thumbnailer - lomiri-url-dispatcher - lomiri-wallpapers - mediascanner2 # TODO possibly needs to be kicked off by graphical-session.target - morph-browser - qtmir # not having its desktop file for Xwayland available causes any X11 application to crash the session - suru-icon-theme - telephony-service - teleports - ]); - variables = { - # To override the keyboard layouts in Lomiri - NIXOS_XKB_LAYOUTS = config.services.xserver.xkb.layout; - }; - }; + ]; - hardware = { - bluetooth.enable = lib.mkDefault true; - }; + # Copy-pasted basic stuff + hardware.graphics.enable = lib.mkDefault true; + fonts.enableDefaultPackages = lib.mkDefault true; + programs.dconf.enable = lib.mkDefault true; - networking.networkmanager.enable = lib.mkDefault true; - - systemd.packages = with pkgs.lomiri; [ - hfd-service - lomiri-download-manager - ]; - - services.dbus.packages = with pkgs.lomiri; [ - hfd-service - libusermetrics - lomiri-download-manager - ]; - - fonts.packages = with pkgs; [ - # Applications tend to default to Ubuntu font - ubuntu-classic - ]; - - # Copy-pasted basic stuff - hardware.graphics.enable = lib.mkDefault true; - fonts.enableDefaultPackages = lib.mkDefault true; - programs.dconf.enable = lib.mkDefault true; - - # Xwayland is partly hardcoded in Mir so it can't really be fully turned off, and it must be on PATH for X11 apps *and Lomiri's web browser* to work. - # Until Mir/Lomiri can be properly used without it, force it on so everything behaves as expected. - programs.xwayland.enable = lib.mkForce true; - - services.accounts-daemon.enable = true; - - services.ayatana-indicators = { - enable = true; - packages = (with pkgs; [ - ayatana-indicator-datetime - ayatana-indicator-display - ayatana-indicator-messages - ayatana-indicator-power - ayatana-indicator-session - ] ++ lib.optionals config.hardware.bluetooth.enable [ - ayatana-indicator-bluetooth - ] ++ lib.optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [ - ayatana-indicator-sound - ]) ++ (with pkgs.lomiri; [ - telephony-service - ] ++ lib.optionals config.networking.networkmanager.enable [ - lomiri-indicator-network - ]); - }; + services.accounts-daemon.enable = true; - services.udisks2.enable = true; - services.upower.enable = true; - services.geoclue2.enable = true; + services.ayatana-indicators = { + enable = true; + packages = + ( + with pkgs; + [ + ayatana-indicator-display + ayatana-indicator-messages + ayatana-indicator-power + ] + ++ lib.optionals config.hardware.bluetooth.enable [ ayatana-indicator-bluetooth ] + ++ lib.optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [ + ayatana-indicator-sound + ] + ) + ++ ( + with pkgs.lomiri; + [ telephony-service ] + ++ lib.optionals config.networking.networkmanager.enable [ lomiri-indicator-network ] + ); + }; - services.gnome.evolution-data-server = { - enable = true; - plugins = with pkgs; [ - # TODO: lomiri.address-book-service - ]; - }; + services.udisks2.enable = true; + services.upower.enable = true; + services.geoclue2.enable = true; - services.telepathy.enable = true; + services.gnome.evolution-data-server = { + enable = true; + plugins = with pkgs; [ + # TODO: lomiri.address-book-service + ]; + }; - services.displayManager = { - defaultSession = lib.mkDefault "lomiri"; - sessionPackages = with pkgs.lomiri; [ lomiri-session ]; - }; + services.telepathy.enable = true; - services.xserver = { - enable = lib.mkDefault true; - displayManager.lightdm = { - enable = lib.mkDefault true; - greeters.lomiri.enable = lib.mkDefault true; + services.displayManager = { + defaultSession = lib.mkDefault "lomiri"; + sessionPackages = with pkgs.lomiri; [ lomiri-session ]; }; - }; - environment.pathsToLink = [ - # Configs for inter-app data exchange system - "/share/content-hub/peers" - # Configs for inter-app URL requests - "/share/lomiri-url-dispatcher/urls" - # Splash screens & other images for desktop apps launched via lomiri-app-launch - "/share/lomiri-app-launch" - # TODO Try to get maliit stuff working - "/share/maliit/plugins" - # At least the network indicator is still under the unity name, due to leftover Unity-isms - "/share/unity" - # Data - "/share/locale" # TODO LUITK hardcoded default locale path, fix individual apps to not rely on it - "/share/sounds" - "/share/wallpapers" - ]; - - systemd.user.services = { - # Unconditionally run service that collects system-installed URL handlers before LUD - # TODO also run user-installed one? - "lomiri-url-dispatcher-update-system-dir" = { - description = "Lomiri URL dispatcher system directory updater"; - wantedBy = [ "lomiri-url-dispatcher.service" ]; - before = [ "lomiri-url-dispatcher.service" ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/"; + services.xserver = { + enable = lib.mkDefault true; + displayManager.lightdm = { + enable = lib.mkDefault true; + greeters.lomiri.enable = lib.mkDefault true; }; }; - "lomiri-polkit-agent" = rec { - description = "Lomiri Polkit agent"; - wantedBy = [ "lomiri.service" "lomiri-full-greeter.service" "lomiri-full-shell.service" "lomiri-greeter.service" "lomiri-shell.service" ]; - after = [ "graphical-session.target" ]; - partOf = wantedBy; - serviceConfig = { - Type = "simple"; - Restart = "always"; - ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent"; + environment.pathsToLink = [ + # Configs for inter-app data exchange system + "/share/lomiri-content-hub/peers" + # Configs for inter-app URL requests + "/share/lomiri-url-dispatcher/urls" + # Splash screens & other images for desktop apps launched via lomiri-app-launch + "/share/lomiri-app-launch" + # TODO Try to get maliit stuff working + "/share/maliit/plugins" + # At least the network indicator is still under the unity name, due to leftover Unity-isms + "/share/unity" + # Data + "/share/sounds" + ]; + + systemd.user.services = { + # Unconditionally run service that collects system-installed URL handlers before LUD + # TODO also run user-installed one? + "lomiri-url-dispatcher-update-system-dir" = { + description = "Lomiri URL dispatcher system directory updater"; + wantedBy = [ "lomiri-url-dispatcher.service" ]; + before = [ "lomiri-url-dispatcher.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/"; + }; }; - }; - }; - systemd.services = { - "dbus-com.lomiri.UserMetrics" = { - serviceConfig = { - Type = "dbus"; - BusName = "com.lomiri.UserMetrics"; - User = "usermetrics"; - StandardOutput = "syslog"; - SyslogIdentifier = "com.lomiri.UserMetrics"; - ExecStart = "${pkgs.lomiri.libusermetrics}/libexec/libusermetrics/usermetricsservice"; - } // lib.optionalAttrs (!config.security.apparmor.enable) { - # Due to https://gitlab.com/ubports/development/core/libusermetrics/-/issues/8, auth must be disabled when not using AppArmor, lest the next database usage breaks - Environment = "USERMETRICS_NO_AUTH=1"; + "lomiri-polkit-agent" = rec { + description = "Lomiri Polkit agent"; + wantedBy = [ + "lomiri.service" + "lomiri-full-greeter.service" + "lomiri-full-shell.service" + "lomiri-greeter.service" + "lomiri-shell.service" + ]; + after = [ "graphical-session.target" ]; + partOf = wantedBy; + serviceConfig = { + Type = "simple"; + Restart = "always"; + ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent"; + }; }; }; - }; - users.users.usermetrics = { - group = "usermetrics"; - home = "/var/lib/usermetrics"; - createHome = true; - isSystemUser = true; - }; + systemd.services = { + "dbus-com.lomiri.UserMetrics" = { + serviceConfig = + { + Type = "dbus"; + BusName = "com.lomiri.UserMetrics"; + User = "usermetrics"; + StandardOutput = "syslog"; + SyslogIdentifier = "com.lomiri.UserMetrics"; + ExecStart = "${pkgs.lomiri.libusermetrics}/libexec/libusermetrics/usermetricsservice"; + } + // lib.optionalAttrs (!config.security.apparmor.enable) { + # Due to https://gitlab.com/ubports/development/core/libusermetrics/-/issues/8, auth must be disabled when not using AppArmor, lest the next database usage breaks + Environment = "USERMETRICS_NO_AUTH=1"; + }; + }; + }; - users.groups.usermetrics = { }; + users.users.usermetrics = { + group = "usermetrics"; + home = "/var/lib/usermetrics"; + createHome = true; + isSystemUser = true; + }; - # TODO content-hub cannot pass files between applications without asking AA for permissions. And alot of the Lomiri stack is designed with AA availability in mind. This might be a requirement to be closer to upstream? - # But content-hub currently fails to pass files between applications even with AA enabled, and we can get away without AA in many places. Let's see how this develops before requiring this for good. - # security.apparmor.enable = true; - }; + users.groups.usermetrics = { }; + }) + ]; meta.maintainers = lib.teams.lomiri.members; } diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix index 2df28c80e8a1..890bda68e77c 100644 --- a/nixos/modules/services/desktop-managers/plasma6.nix +++ b/nixos/modules/services/desktop-managers/plasma6.nix @@ -12,7 +12,7 @@ activationScript = '' # will be rebuilt automatically - rm -fv $HOME/.cache/ksycoca* + rm -fv "$HOME/.cache/ksycoca"* ''; in { options = { @@ -58,6 +58,7 @@ in { ]; qt.enable = true; + programs.xwayland.enable = true; environment.systemPackages = with kdePackages; let requiredPackages = [ qtwayland # Hack? To make everything run on Wayland @@ -78,6 +79,7 @@ in { kio-fuse # fuse interface for KIO kpackage # provides kpackagetool tool kservice # provides kbuildsycoca6 tool + kunifiedpush # provides a background service and a KCM kwallet # provides helper service kwallet-pam # provides helper service kwalletmanager # provides KCMs and stuff @@ -87,7 +89,6 @@ in { # Core Plasma parts kwin - pkgs.xwayland kscreen libkscreen kscreenlocker @@ -143,10 +144,12 @@ in { kate khelpcenter dolphin + baloo-widgets # baloo information in Dolphin dolphin-plugins spectacle ffmpegthumbs krdp + xwaylandvideobridge # exposes Wayland windows to X11 screen capture ] ++ lib.optionals config.services.flatpak.enable [ # Since PackageKit Nix support is not there yet, # only install discover if flatpak is enabled. @@ -243,10 +246,15 @@ in { systemd.services."drkonqi-coredump-processor@".wantedBy = ["systemd-coredump@.service"]; xdg.icons.enable = true; + xdg.icons.fallbackCursorThemes = mkDefault ["breeze_cursors"]; xdg.portal.enable = true; - xdg.portal.extraPortals = [kdePackages.xdg-desktop-portal-kde]; - xdg.portal.configPackages = mkDefault [kdePackages.xdg-desktop-portal-kde]; + xdg.portal.extraPortals = [ + kdePackages.kwallet + kdePackages.xdg-desktop-portal-kde + pkgs.xdg-desktop-portal-gtk + ]; + xdg.portal.configPackages = mkDefault [kdePackages.plasma-workspace]; services.pipewire.enable = mkDefault true; # Enable screen reader by default diff --git a/nixos/modules/services/desktops/gnome/localsearch.nix b/nixos/modules/services/desktops/gnome/localsearch.nix new file mode 100644 index 000000000000..9160d1f06431 --- /dev/null +++ b/nixos/modules/services/desktops/gnome/localsearch.nix @@ -0,0 +1,50 @@ +{ + config, + pkgs, + lib, + ... +}: + +{ + meta = { + maintainers = lib.teams.gnome.members; + }; + + imports = [ + (lib.mkRenamedOptionModule + [ + "services" + "gnome" + "tracker-miners" + "enable" + ] + [ + "services" + "gnome" + "localsearch" + "enable" + ] + ) + ]; + + options = { + services.gnome.localsearch = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable LocalSearch, indexing services for TinySPARQL + search engine and metadata storage system. + ''; + }; + }; + }; + + config = lib.mkIf config.services.gnome.localsearch.enable { + environment.systemPackages = [ pkgs.localsearch ]; + + services.dbus.packages = [ pkgs.localsearch ]; + + systemd.packages = [ pkgs.localsearch ]; + }; +} diff --git a/nixos/modules/services/desktops/gnome/tinysparql.nix b/nixos/modules/services/desktops/gnome/tinysparql.nix new file mode 100644 index 000000000000..551b5800e84c --- /dev/null +++ b/nixos/modules/services/desktops/gnome/tinysparql.nix @@ -0,0 +1,66 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.services.gnome.tinysparql; +in +{ + meta = { + maintainers = lib.teams.gnome.members; + }; + + imports = [ + (lib.mkRemovedOptionModule + [ + "services" + "gnome" + "tracker" + "subcommandPackages" + ] + '' + This option is broken since 3.7 and since 3.8 tracker (tinysparql) no longer expect + CLI to be extended by external projects, note that tracker-miners (localsearch) now + provides its own CLI tool. + '' + ) + (lib.mkRenamedOptionModule + [ + "services" + "gnome" + "tracker" + "enable" + ] + [ + "services" + "gnome" + "tinysparql" + "enable" + ] + ) + ]; + + options = { + services.gnome.tinysparql = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable TinySPARQL services, a search engine, + search tool and metadata storage system. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ pkgs.tinysparql ]; + + services.dbus.packages = [ pkgs.tinysparql ]; + + systemd.packages = [ pkgs.tinysparql ]; + }; +} diff --git a/nixos/modules/services/desktops/gnome/tracker-miners.nix b/nixos/modules/services/desktops/gnome/tracker-miners.nix deleted file mode 100644 index d5d42cee9f8b..000000000000 --- a/nixos/modules/services/desktops/gnome/tracker-miners.nix +++ /dev/null @@ -1,44 +0,0 @@ -# Tracker Miners daemons. - -{ config, pkgs, lib, ... }: - -{ - - meta = { - maintainers = lib.teams.gnome.members; - }; - - ###### interface - - options = { - - services.gnome.tracker-miners = { - - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to enable Tracker miners, indexing services for Tracker - search engine and metadata storage system. - ''; - }; - - }; - - }; - - ###### implementation - - config = lib.mkIf config.services.gnome.tracker-miners.enable { - - environment.systemPackages = [ pkgs.tracker-miners ]; - - services.dbus.packages = [ pkgs.tracker-miners ]; - - systemd.packages = [ pkgs.tracker-miners ]; - - services.gnome.tracker.subcommandPackages = [ pkgs.tracker-miners ]; - - }; - -} diff --git a/nixos/modules/services/desktops/gnome/tracker.nix b/nixos/modules/services/desktops/gnome/tracker.nix deleted file mode 100644 index 45b679571c70..000000000000 --- a/nixos/modules/services/desktops/gnome/tracker.nix +++ /dev/null @@ -1,66 +0,0 @@ -# Tracker daemon. - -{ config, pkgs, lib, ... }: - -let - cfg = config.services.gnome.tracker; -in -{ - - meta = { - maintainers = lib.teams.gnome.members; - }; - - ###### interface - - options = { - - services.gnome.tracker = { - - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to enable Tracker services, a search engine, - search tool and metadata storage system. - ''; - }; - - subcommandPackages = lib.mkOption { - type = lib.types.listOf lib.types.package; - default = [ ]; - internal = true; - description = '' - List of packages containing tracker3 subcommands. - ''; - }; - - }; - - }; - - - ###### implementation - - config = lib.mkIf cfg.enable { - - environment.systemPackages = [ pkgs.tracker ]; - - services.dbus.packages = [ pkgs.tracker ]; - - systemd.packages = [ pkgs.tracker ]; - - environment.variables = { - TRACKER_CLI_SUBCOMMANDS_DIR = - let - subcommandPackagesTree = pkgs.symlinkJoin { - name = "tracker-with-subcommands-${pkgs.tracker.version}"; - paths = [ pkgs.tracker ] ++ cfg.subcommandPackages; - }; - in - "${subcommandPackagesTree}/libexec/tracker3"; - }; - - }; - -} diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix index bf57d1255774..9431a09b9936 100644 --- a/nixos/modules/services/desktops/pipewire/pipewire.nix +++ b/nixos/modules/services/desktops/pipewire/pipewire.nix @@ -19,7 +19,7 @@ let ''; cfg = config.services.pipewire; enable32BitAlsaPlugins = cfg.alsa.support32Bit - && pkgs.stdenv.isx86_64 + && pkgs.stdenv.hostPlatform.isx86_64 && pkgs.pkgsi686Linux.pipewire != null; # The package doesn't output to $out/lib/pipewire directly so that the @@ -332,7 +332,7 @@ in { { # JACK intentionally not checked, as PW-on-JACK setups are a thing that some people may want 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."; + message = "Using PipeWire's ALSA/PulseAudio compatibility layers requires running PipeWire as the sound server. Either set `services.pipewire.audio.enable` to true to enable audio support, or set both `services.pipewire.pulse.enable` and `services.pipewire.alsa.enable` to false to use pipewire exclusively for the compositor."; } { assertion = length diff --git a/nixos/modules/services/desktops/system76-scheduler.nix b/nixos/modules/services/desktops/system76-scheduler.nix index b021ae6bfbfa..b8f80276ef76 100644 --- a/nixos/modules/services/desktops/system76-scheduler.nix +++ b/nixos/modules/services/desktops/system76-scheduler.nix @@ -95,8 +95,8 @@ in { package = mkOption { type = types.package; - default = config.boot.kernelPackages.system76-scheduler; - defaultText = literalExpression "config.boot.kernelPackages.system76-scheduler"; + default = pkgs.system76-scheduler; + defaultText = literalExpression "pkgs.system76-scheduler"; description = "Which System76-Scheduler package to use."; }; @@ -252,7 +252,7 @@ in { # No custom settings: just use stock configuration with a fix for Pipewire "system76-scheduler/config.kdl".source = "${cfg.package}/data/config.kdl"; "system76-scheduler/process-scheduler/00-dist.kdl".source = "${cfg.package}/data/pop_os.kdl"; - "system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source = ../../../../pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl; + "system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source = ../../../../pkgs/by-name/sy/system76-scheduler/01-fix-pipewire-paths.kdl; }) (let diff --git a/nixos/modules/services/development/athens.nix b/nixos/modules/services/development/athens.nix index 235d234609c0..ddd4699fea2a 100644 --- a/nixos/modules/services/development/athens.nix +++ b/nixos/modules/services/development/athens.nix @@ -168,7 +168,7 @@ in type = lib.types.package; default = pkgs.go; defaultText = lib.literalExpression "pkgs.go"; - example = "pkgs.go_1_21"; + example = "pkgs.go_1_23"; description = '' The Go package used by Athens at runtime. diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix index b3991fa4c268..d9687fdbf713 100644 --- a/nixos/modules/services/development/jupyter/default.nix +++ b/nixos/modules/services/development/jupyter/default.nix @@ -185,7 +185,7 @@ in { }) (lib.mkIf (cfg.enable && (cfg.user == "jupyter")) { users.extraUsers.jupyter = { - extraGroups = [ cfg.group ]; + inherit (cfg) group; home = "/var/lib/jupyter"; createHome = true; isSystemUser = true; diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix index a9a4f386e060..3e89c01dc841 100644 --- a/nixos/modules/services/games/factorio.nix +++ b/nixos/modules/services/games/factorio.nix @@ -36,7 +36,9 @@ let } // cfg.extraSettings; serverSettingsString = builtins.toJSON (lib.filterAttrsRecursive (n: v: v != null) serverSettings); serverSettingsFile = pkgs.writeText "server-settings.json" serverSettingsString; - serverAdminsFile = pkgs.writeText "server-adminlist.json" (builtins.toJSON cfg.admins); + playerListOption = name: list: + lib.optionalString (list != []) + "--${name}=${pkgs.writeText "${name}.json" (builtins.toJSON list)}"; modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods cfg.mods-dat; in { @@ -59,6 +61,30 @@ in ''; }; + allowedPlayers = lib.mkOption { + # I would personally prefer for `allowedPlayers = []` to mean "no-one + # can connect" but Factorio seems to ignore empty whitelists (even with + # --use-server-whitelist) so we can't implement that behaviour, so we + # might as well match theirs. + type = lib.types.listOf lib.types.str; + default = []; + example = [ "Rseding91" "Oxyd" ]; + description = '' + If non-empty, only these player names are allowed to connect. The game + will not be able to save any changes made in-game with the /whitelist + console command, though they will still take effect until the server + is restarted. + + If empty, the whitelist defaults to open, but can be managed with the + in-game /whitelist console command (see: /help whitelist), which will + cause changes to be saved to the game's state directory (see also: + `stateDirName`). + ''; + }; + # Opting not to include the banlist in addition the the whitelist because: + # - banlists are not as often known in advance, + # - losing banlist changes on restart seems much more of a headache. + admins = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; @@ -177,7 +203,7 @@ in extraSettings = lib.mkOption { type = lib.types.attrs; default = {}; - example = { admins = [ "username" ];}; + example = { max_players = 64; }; description = '' Extra game configuration that will go into server-settings.json ''; @@ -298,7 +324,9 @@ in }" (lib.optionalString cfg.loadLatestSave "--start-server-load-latest") (lib.optionalString (cfg.mods != []) "--mod-directory=${modDir}") - (lib.optionalString (cfg.admins != []) "--server-adminlist=${serverAdminsFile}") + (playerListOption "server-adminlist" cfg.admins) + (playerListOption "server-whitelist" cfg.allowedPlayers) + (lib.optionalString (cfg.allowedPlayers != []) "--use-server-whitelist") ]; # Sandboxing diff --git a/nixos/modules/services/hardware/amdvlk.nix b/nixos/modules/services/hardware/amdvlk.nix index 32d6fb3be21d..e2fd91811d01 100644 --- a/nixos/modules/services/hardware/amdvlk.nix +++ b/nixos/modules/services/hardware/amdvlk.nix @@ -37,8 +37,6 @@ in { extraPackages32 = [ cfg.support32Bit.package ]; }; - services.xserver.videoDrivers = [ "amdgpu" ]; - environment.sessionVariables = lib.mkIf cfg.supportExperimental.enable { AMDVLK_ENABLE_DEVELOPING_EXT = "all"; }; diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix index a73cc970576a..741abfba30b6 100644 --- a/nixos/modules/services/hardware/bluetooth.nix +++ b/nixos/modules/services/hardware/bluetooth.nix @@ -62,7 +62,10 @@ in ControllerMode = "bredr"; }; }; - description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf)."; + description = '' + Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf). + See <https://github.com/bluez/bluez/blob/master/src/main.conf> for full list of options. + ''; }; input = mkOption { @@ -74,7 +77,10 @@ in ClassicBondedOnly = true; }; }; - description = "Set configuration for the input service (/etc/bluetooth/input.conf)."; + description = '' + Set configuration for the input service (/etc/bluetooth/input.conf). + See <https://github.com/bluez/bluez/blob/master/profiles/input/input.conf> for full list of options. + ''; }; network = mkOption { @@ -85,7 +91,10 @@ in DisableSecurity = true; }; }; - description = "Set configuration for the network service (/etc/bluetooth/network.conf)."; + description = '' + Set configuration for the network service (/etc/bluetooth/network.conf). + See <https://github.com/bluez/bluez/blob/master/profiles/network/network.conf> for full list of options. + ''; }; }; }; diff --git a/nixos/modules/services/hardware/iptsd.nix b/nixos/modules/services/hardware/iptsd.nix index 3a299c2428df..bfd4b3522792 100644 --- a/nixos/modules/services/hardware/iptsd.nix +++ b/nixos/modules/services/hardware/iptsd.nix @@ -18,15 +18,15 @@ in { type = lib.types.submodule { freeformType = format.type; options = { - Touch = { + Touchscreen = { DisableOnPalm = lib.mkOption { default = false; - description = "Ignore all touch inputs if a palm was registered on the display."; + description = "Ignore all touchscreen inputs if a palm was registered on the display."; type = lib.types.bool; }; DisableOnStylus = lib.mkOption { default = false; - description = "Ignore all touch inputs if a stylus is in proximity."; + description = "Ignore all touchscreen inputs if a stylus is in proximity."; type = lib.types.bool; }; }; @@ -43,6 +43,10 @@ in { }; config = lib.mkIf cfg.enable { + warnings = lib.optional (lib.hasAttr "Touch" cfg.config) '' + The option `services.iptsd.config.Touch` has been renamed to `services.iptsd.config.Touchscreen`. + ''; + systemd.packages = [ pkgs.iptsd ]; environment.etc."iptsd.conf".source = configFile; systemd.services."iptsd@".restartTriggers = [ configFile ]; diff --git a/nixos/modules/services/hardware/kmonad.nix b/nixos/modules/services/hardware/kmonad.nix new file mode 100644 index 000000000000..bc1b5409f9dd --- /dev/null +++ b/nixos/modules/services/hardware/kmonad.nix @@ -0,0 +1,190 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.kmonad; + + # Per-keyboard options: + keyboard = + { name, ... }: + { + options = { + name = lib.mkOption { + type = lib.types.str; + example = "laptop-internal"; + description = "Keyboard name."; + }; + + device = lib.mkOption { + type = lib.types.path; + example = "/dev/input/by-id/some-dev"; + description = "Path to the keyboard's device file."; + }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + Extra permission groups to attach to the KMonad instance for + this keyboard. + + Since KMonad runs as an unprivileged user, it may sometimes + need extra permissions in order to read the keyboard device + file. If your keyboard's device file isn't in the input + group you'll need to list its group in this option. + ''; + }; + + defcfg = { + enable = lib.mkEnableOption '' + Automatically generate the defcfg block. + + When this is option is set to true the config option for + this keyboard should not include a defcfg block. + ''; + + compose = { + key = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "ralt"; + description = "The (optional) compose key to use."; + }; + + delay = lib.mkOption { + type = lib.types.int; + default = 5; + description = "The delay (in milliseconds) between compose key sequences."; + }; + }; + + fallthrough = lib.mkEnableOption "Re-emit unhandled key events."; + + allowCommands = lib.mkEnableOption "Allow keys to run shell commands."; + }; + + config = lib.mkOption { + type = lib.types.lines; + description = "Keyboard configuration."; + }; + }; + + config = { + name = lib.mkDefault name; + }; + }; + + # Create a complete KMonad configuration file: + mkCfg = + keyboard: + let + defcfg = '' + (defcfg + input (device-file "${keyboard.device}") + output (uinput-sink "kmonad-${keyboard.name}") + ${ + lib.optionalString (keyboard.defcfg.compose.key != null) '' + cmp-seq ${keyboard.defcfg.compose.key} + cmp-seq-delay ${toString keyboard.defcfg.compose.delay} + '' + } + fallthrough ${lib.boolToString keyboard.defcfg.fallthrough} + allow-cmd ${lib.boolToString keyboard.defcfg.allowCommands} + ) + ''; + in + pkgs.writeTextFile { + name = "kmonad-${keyboard.name}.cfg"; + text = lib.optionalString keyboard.defcfg.enable (defcfg + "\n") + keyboard.config; + checkPhase = "${cfg.package}/bin/kmonad -d $out"; + }; + + # Build a systemd path config that starts the service below when a + # keyboard device appears: + mkPath = + keyboard: + let + name = "kmonad-${keyboard.name}"; + in + lib.nameValuePair name { + description = "KMonad trigger for ${keyboard.device}"; + wantedBy = [ "paths.target" ]; + pathConfig = { + Unit = "${name}.service"; + PathExists = keyboard.device; + }; + }; + + # Build a systemd service that starts KMonad: + mkService = + keyboard: + let + cmd = [ + (lib.getExe cfg.package) + "--input" + ''device-file "${keyboard.device}"'' + ] ++ cfg.extraArgs ++ [ "${mkCfg keyboard}" ]; + in + lib.nameValuePair "kmonad-${keyboard.name}" { + description = "KMonad for ${keyboard.device}"; + script = lib.escapeShellArgs cmd; + unitConfig = { + # Control rate limiting. + # Stop the restart logic if we restart more than + # StartLimitBurst times in a period of StartLimitIntervalSec. + StartLimitIntervalSec = 2; + StartLimitBurst = 5; + }; + serviceConfig = { + Restart = "always"; + # Restart at increasing intervals from 2s to 1m + RestartSec = 2; + RestartSteps = 30; + RestartMaxDelaySec = "1min"; + Nice = -20; + DynamicUser = true; + User = "kmonad"; + Group = "kmonad"; + SupplementaryGroups = [ + # These ensure that our dynamic user has access to the device node + config.users.groups.input.name + config.users.groups.uinput.name + ] ++ keyboard.extraGroups; + }; + }; +in +{ + options.services.kmonad = { + enable = lib.mkEnableOption "KMonad: An advanced keyboard manager."; + + package = lib.mkPackageOption pkgs "kmonad" { }; + + keyboards = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule keyboard); + default = { }; + description = "Keyboard configuration."; + }; + + extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "--log-level" + "debug" + ]; + description = "Extra arguments to pass to KMonad."; + }; + }; + + config = lib.mkIf cfg.enable { + hardware.uinput.enable = true; + + systemd = { + paths = lib.mapAttrs' (_: mkPath) cfg.keyboards; + services = lib.mapAttrs' (_: mkService) cfg.keyboards; + }; + }; +} diff --git a/nixos/modules/services/hardware/monado.nix b/nixos/modules/services/hardware/monado.nix index 9f9c6c39a0b4..f7fdf421ac17 100644 --- a/nixos/modules/services/hardware/monado.nix +++ b/nixos/modules/services/hardware/monado.nix @@ -1,10 +1,18 @@ -{ config -, lib -, pkgs -, ... +{ + config, + lib, + pkgs, + ... }: let - inherit (lib) mkDefault mkEnableOption mkIf mkOption mkPackageOption types; + inherit (lib) + mkDefault + mkEnableOption + mkIf + mkOption + mkPackageOption + types + ; cfg = config.services.monado; @@ -27,7 +35,8 @@ in example = true; }; - highPriority = mkEnableOption "high priority capability for monado-service" + highPriority = + mkEnableOption "high priority capability for monado-service" // mkOption { default = true; }; }; @@ -61,9 +70,10 @@ in serviceConfig = { ExecStart = - if cfg.highPriority - then "${config.security.wrapperDir}/monado-service" - else lib.getExe' cfg.package "monado-service"; + if cfg.highPriority then + "${config.security.wrapperDir}/monado-service" + else + lib.getExe' cfg.package "monado-service"; Restart = "no"; }; @@ -93,6 +103,8 @@ in environment.systemPackages = [ cfg.package ]; environment.pathsToLink = [ "/share/openxr" ]; + hardware.graphics.extraPackages = [ pkgs.monado-vulkan-layers ]; + environment.etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime { source = "${cfg.package}/share/openxr/1/openxr_monado.json"; }; diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix b/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix index a188c16141e1..3538e52bda3b 100644 --- a/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix +++ b/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix @@ -77,50 +77,65 @@ }; - config = { - - virtualisation.docker.daemon.settings = lib.mkIf - (config.hardware.nvidia-container-toolkit.enable && - (lib.versionAtLeast config.virtualisation.docker.package.version "25")) { - features.cdi = true; - }; - - hardware.nvidia-container-toolkit.mounts = let - nvidia-driver = config.hardware.nvidia.package; - in (lib.mkMerge [ - [{ hostPath = pkgs.addDriverRunpath.driverLink; - containerPath = pkgs.addDriverRunpath.driverLink; } - { hostPath = "${lib.getLib nvidia-driver}/etc"; - containerPath = "${lib.getLib nvidia-driver}/etc"; } - { hostPath = "${lib.getLib nvidia-driver}/share"; - containerPath = "${lib.getLib nvidia-driver}/share"; } - { hostPath = "${lib.getLib pkgs.glibc}/lib"; - containerPath = "${lib.getLib pkgs.glibc}/lib"; } - { hostPath = "${lib.getLib pkgs.glibc}/lib64"; - containerPath = "${lib.getLib pkgs.glibc}/lib64"; }] - (lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-executables - [{ hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control"; - containerPath = "/usr/bin/nvidia-cuda-mps-control"; } - { hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server"; - containerPath = "/usr/bin/nvidia-cuda-mps-server"; } - { hostPath = lib.getExe' nvidia-driver "nvidia-debugdump"; - containerPath = "/usr/bin/nvidia-debugdump"; } - { hostPath = lib.getExe' nvidia-driver "nvidia-powerd"; - containerPath = "/usr/bin/nvidia-powerd"; } - { hostPath = lib.getExe' nvidia-driver "nvidia-smi"; - containerPath = "/usr/bin/nvidia-smi"; }]) - # nvidia-docker 1.0 uses /usr/local/nvidia/lib{,64} - # e.g. - # - https://gitlab.com/nvidia/container-images/cuda/-/blob/e3ff10eab3a1424fe394899df0e0f8ca5a410f0f/dist/12.3.1/ubi9/base/Dockerfile#L44 - # - https://github.com/NVIDIA/nvidia-docker/blob/01d2c9436620d7dde4672e414698afe6da4a282f/src/nvidia/volumes.go#L104-L173 - (lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-docker-1-directories - [{ hostPath = "${lib.getLib nvidia-driver}/lib"; - containerPath = "/usr/local/nvidia/lib"; } - { hostPath = "${lib.getLib nvidia-driver}/lib"; - containerPath = "/usr/local/nvidia/lib64"; }]) - ]); - - systemd.services.nvidia-container-toolkit-cdi-generator = lib.mkIf config.hardware.nvidia-container-toolkit.enable { + config = lib.mkIf config.hardware.nvidia-container-toolkit.enable { + assertions = [ + { assertion = config.hardware.nvidia.datacenter.enable || lib.elem "nvidia" config.services.xserver.videoDrivers; + message = ''`nvidia-container-toolkit` requires nvidia datacenter or desktop drivers: set `hardware.nvidia.datacenter.enable` or add "nvidia" to `services.xserver.videoDrivers`''; + }]; + + virtualisation.docker = { + daemon.settings = lib.mkIf + (lib.versionAtLeast config.virtualisation.docker.package.version "25") { + features.cdi = true; + }; + + rootless.daemon.settings = lib.mkIf + (config.virtualisation.docker.rootless.enable && + (lib.versionAtLeast config.virtualisation.docker.package.version "25")) { + features.cdi = true; + }; + }; + + hardware = { + graphics.enable = lib.mkIf (!config.hardware.nvidia.datacenter.enable) true; + + nvidia-container-toolkit.mounts = let + nvidia-driver = config.hardware.nvidia.package; + in (lib.mkMerge [ + [{ hostPath = pkgs.addDriverRunpath.driverLink; + containerPath = pkgs.addDriverRunpath.driverLink; } + { hostPath = "${lib.getLib nvidia-driver}/etc"; + containerPath = "${lib.getLib nvidia-driver}/etc"; } + { hostPath = "${lib.getLib nvidia-driver}/share"; + containerPath = "${lib.getLib nvidia-driver}/share"; } + { hostPath = "${lib.getLib pkgs.glibc}/lib"; + containerPath = "${lib.getLib pkgs.glibc}/lib"; } + { hostPath = "${lib.getLib pkgs.glibc}/lib64"; + containerPath = "${lib.getLib pkgs.glibc}/lib64"; }] + (lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-executables + [{ hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control"; + containerPath = "/usr/bin/nvidia-cuda-mps-control"; } + { hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server"; + containerPath = "/usr/bin/nvidia-cuda-mps-server"; } + { hostPath = lib.getExe' nvidia-driver "nvidia-debugdump"; + containerPath = "/usr/bin/nvidia-debugdump"; } + { hostPath = lib.getExe' nvidia-driver "nvidia-powerd"; + containerPath = "/usr/bin/nvidia-powerd"; } + { hostPath = lib.getExe' nvidia-driver "nvidia-smi"; + containerPath = "/usr/bin/nvidia-smi"; }]) + # nvidia-docker 1.0 uses /usr/local/nvidia/lib{,64} + # e.g. + # - https://gitlab.com/nvidia/container-images/cuda/-/blob/e3ff10eab3a1424fe394899df0e0f8ca5a410f0f/dist/12.3.1/ubi9/base/Dockerfile#L44 + # - https://github.com/NVIDIA/nvidia-docker/blob/01d2c9436620d7dde4672e414698afe6da4a282f/src/nvidia/volumes.go#L104-L173 + (lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-docker-1-directories + [{ hostPath = "${lib.getLib nvidia-driver}/lib"; + containerPath = "/usr/local/nvidia/lib"; } + { hostPath = "${lib.getLib nvidia-driver}/lib"; + containerPath = "/usr/local/nvidia/lib64"; }]) + ]); + }; + + systemd.services.nvidia-container-toolkit-cdi-generator = { description = "Container Device Interface (CDI) for Nvidia generator"; wantedBy = [ "multi-user.target" ]; after = [ "systemd-udev-settle.service" ]; diff --git a/nixos/modules/services/hardware/spacenavd.nix b/nixos/modules/services/hardware/spacenavd.nix index c09439356998..db3ab37dc267 100644 --- a/nixos/modules/services/hardware/spacenavd.nix +++ b/nixos/modules/services/hardware/spacenavd.nix @@ -1,8 +1,13 @@ -{ config, lib, pkgs, ... }: -let cfg = config.hardware.spacenavd; - -in { - +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.hardware.spacenavd; +in +{ options = { hardware.spacenavd = { enable = lib.mkEnableOption "spacenavd to support 3DConnexion devices"; @@ -10,12 +15,9 @@ in { }; config = lib.mkIf cfg.enable { - systemd.user.services.spacenavd = { - description = "Daemon for the Spacenavigator 6DOF mice by 3Dconnexion"; - wantedBy = [ "graphical.target" ]; - serviceConfig = { - ExecStart = "${pkgs.spacenavd}/bin/spacenavd -d -l syslog"; - }; + systemd = { + packages = [ pkgs.spacenavd ]; + services.spacenavd.enable = true; }; }; } diff --git a/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix index d2137971b3a7..a9bcf250cf69 100644 --- a/nixos/modules/services/hardware/triggerhappy.nix +++ b/nixos/modules/services/hardware/triggerhappy.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.services.triggerhappy; @@ -6,35 +11,53 @@ let socket = "/run/thd.socket"; configFile = pkgs.writeText "triggerhappy.conf" '' - ${lib.concatMapStringsSep "\n" - ({ keys, event, cmd, ... }: - ''${lib.concatMapStringsSep "+" (x: "KEY_" + x) keys} ${toString { press = 1; hold = 2; release = 0; }.${event}} ${cmd}'' - ) - cfg.bindings} + ${lib.concatMapStringsSep "\n" ( + { + keys, + event, + cmd, + ... + }: + ''${lib.concatMapStringsSep "+" (x: "KEY_" + x) keys} ${ + toString + { + press = 1; + hold = 2; + release = 0; + } + .${event} + } ${cmd}'' + ) cfg.bindings} ${cfg.extraConfig} ''; - bindingCfg = { ... }: { - options = { - - keys = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "List of keys to match. Key names as defined in linux/input-event-codes.h"; - }; - - event = lib.mkOption { - type = lib.types.enum ["press" "hold" "release"]; - default = "press"; - description = "Event to match."; - }; + bindingCfg = + { ... }: + { + options = { + + keys = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "List of keys to match. Key names as defined in linux/input-event-codes.h"; + }; + + event = lib.mkOption { + type = lib.types.enum [ + "press" + "hold" + "release" + ]; + default = "press"; + description = "Event to match."; + }; + + cmd = lib.mkOption { + type = lib.types.str; + description = "What to run."; + }; - cmd = lib.mkOption { - type = lib.types.str; - description = "What to run."; }; - }; - }; in @@ -65,9 +88,9 @@ in bindings = lib.mkOption { type = lib.types.listOf (lib.types.submodule bindingCfg); - default = []; + default = [ ]; example = lib.literalExpression '' - [ { keys = ["PLAYPAUSE"]; cmd = "''${pkgs.mpc-cli}/bin/mpc -q toggle"; } ] + [ { keys = ["PLAYPAUSE"]; cmd = "''${lib.getExe pkgs.mpc} -q toggle"; } ] ''; description = '' Key bindings for {command}`triggerhappy`. @@ -86,7 +109,6 @@ in }; - ###### implementation config = lib.mkIf cfg.enable { @@ -101,18 +123,22 @@ in wantedBy = [ "multi-user.target" ]; description = "Global hotkey daemon"; serviceConfig = { - ExecStart = "${pkgs.triggerhappy}/bin/thd ${lib.optionalString (cfg.user != "root") "--user ${cfg.user}"} --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*"; + ExecStart = "${pkgs.triggerhappy}/bin/thd ${ + lib.optionalString (cfg.user != "root") "--user ${cfg.user}" + } --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*"; }; }; - services.udev.packages = lib.singleton (pkgs.writeTextFile { - name = "triggerhappy-udev-rules"; - destination = "/etc/udev/rules.d/61-triggerhappy.rules"; - text = '' - ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \ - RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev" - ''; - }); + services.udev.packages = lib.singleton ( + pkgs.writeTextFile { + name = "triggerhappy-udev-rules"; + destination = "/etc/udev/rules.d/61-triggerhappy.rules"; + text = '' + ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \ + RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev" + ''; + } + ); }; diff --git a/nixos/modules/services/hardware/tuxedo-rs.nix b/nixos/modules/services/hardware/tuxedo-rs.nix index cfdc1c64d118..4ba7825ae8a5 100644 --- a/nixos/modules/services/hardware/tuxedo-rs.nix +++ b/nixos/modules/services/hardware/tuxedo-rs.nix @@ -14,7 +14,7 @@ in config = lib.mkIf cfg.enable (lib.mkMerge [ { - hardware.tuxedo-keyboard.enable = true; + hardware.tuxedo-drivers.enable = true; systemd = { services.tailord = { diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix index e52aa5a58df2..1cdb70c24ee3 100644 --- a/nixos/modules/services/hardware/udisks2.nix +++ b/nixos/modules/services/hardware/udisks2.nix @@ -18,6 +18,8 @@ in enable = lib.mkEnableOption "udisks2, a DBus service that allows applications to query and manipulate storage devices"; + package = lib.mkPackageOption pkgs "udisks2" {}; + mountOnMedia = lib.mkOption { type = lib.types.bool; default = false; @@ -67,11 +69,11 @@ in config = lib.mkIf config.services.udisks2.enable { - environment.systemPackages = [ pkgs.udisks2 ]; + environment.systemPackages = [ cfg.package ]; environment.etc = (lib.mapAttrs' (name: value: lib.nameValuePair "udisks2/${name}" { source = value; } ) configFiles) // ( let - libblockdev = pkgs.udisks2.libblockdev; + libblockdev = cfg.package.libblockdev; majorVer = lib.versions.major libblockdev.version; in { # We need to make sure /etc/libblockdev/@major_ver@/conf.d is populated to avoid @@ -82,18 +84,18 @@ in security.polkit.enable = true; - services.dbus.packages = [ pkgs.udisks2 ]; + services.dbus.packages = [ cfg.package ]; systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ] ++ lib.optional cfg.mountOnMedia "D! /media 0755 root root -"; - services.udev.packages = [ pkgs.udisks2 ]; + services.udev.packages = [ cfg.package ]; services.udev.extraRules = lib.optionalString cfg.mountOnMedia '' ENV{ID_FS_USAGE}=="filesystem", ENV{UDISKS_FILESYSTEM_SHARED}="1" ''; - systemd.packages = [ pkgs.udisks2 ]; + systemd.packages = [ cfg.package ]; }; } diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix index bcc768cc05d3..43604b17e848 100644 --- a/nixos/modules/services/hardware/upower.nix +++ b/nixos/modules/services/hardware/upower.nix @@ -85,7 +85,7 @@ in percentageLow = lib.mkOption { type = lib.types.ints.unsigned; - default = 10; + default = 20; description = '' When `usePercentageForPolicy` is `true`, the levels at which UPower will consider the @@ -103,7 +103,7 @@ in percentageCritical = lib.mkOption { type = lib.types.ints.unsigned; - default = 3; + default = 5; description = '' When `usePercentageForPolicy` is `true`, the levels at which UPower will consider the diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index c7b28b910515..d5af0bcd5e91 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -6,17 +6,21 @@ let cfg = config.services.home-assistant; format = pkgs.formats.yaml {}; - # Render config attribute sets to YAML - # Values that are null will be filtered from the output, so this is one way to have optional - # options shown in settings. - # We post-process the result to add support for YAML functions, like secrets or includes, see e.g. + # Post-process YAML output to add support for YAML functions, like + # secrets or includes, by naively unquoting strings with leading bangs + # and at least one space-separated parameter. # https://www.home-assistant.io/docs/configuration/secrets/ - filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) (lib.recursiveUpdate customLovelaceModulesResources (cfg.config or {})); - configFile = pkgs.runCommandLocal "configuration.yaml" { } '' - cp ${format.generate "configuration.yaml" filteredConfig} $out + renderYAMLFile = fn: yaml: pkgs.runCommandLocal fn { } '' + cp ${format.generate fn yaml} $out sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out ''; - lovelaceConfigFile = format.generate "ui-lovelace.yaml" cfg.lovelaceConfig; + + # Filter null values from the configuration, so that we can still advertise + # optional options in the config attribute. + filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) (lib.recursiveUpdate customLovelaceModulesResources (cfg.config or {})); + configFile = renderYAMLFile "configuration.yaml" filteredConfig; + + lovelaceConfigFile = renderYAMLFile "ui-lovelace.yaml" cfg.lovelaceConfig; # Components advertised by the home-assistant package availableComponents = cfg.package.availableComponents; diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix index 6fb5f0588d97..1c161e167be8 100644 --- a/nixos/modules/services/logging/logrotate.nix +++ b/nixos/modules/services/logging/logrotate.nix @@ -260,6 +260,9 @@ in # hardening CapabilityBoundingSet = [ "CAP_CHOWN" + "CAP_DAC_OVERRIDE" + "CAP_KILL" + "CAP_SETUID" "CAP_SETGID" ]; DevicePolicy = "closed"; @@ -280,16 +283,16 @@ in ProtectSystem = "full"; RestrictNamespaces = true; RestrictRealtime = true; - RestrictSUIDSGID = true; + RestrictSUIDSGID = false; # can create sgid directories SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged @resources" - "@chown" + "@chown @setuid" ]; UMask = "0027"; } // lib.optionalAttrs (!cfg.allowNetworking) { - PrivateNetwork = true; + PrivateNetwork = true; # e.g. mail delivery RestrictAddressFamilies = "none"; }; }; diff --git a/nixos/modules/services/logging/promtail.nix b/nixos/modules/services/logging/promtail.nix index 9eccd34cef23..d6038055c05c 100644 --- a/nixos/modules/services/logging/promtail.nix +++ b/nixos/modules/services/logging/promtail.nix @@ -10,15 +10,29 @@ let allowPositionsFile = !lib.hasPrefix "/var/cache/promtail" positionsFile; positionsFile = cfg.configuration.positions.filename; + + configFile = if cfg.configFile != null + then cfg.configFile + else prettyJSON cfg.configuration; + in { options.services.promtail = with types; { enable = mkEnableOption "the Promtail ingresser"; - configuration = mkOption { type = (pkgs.formats.json {}).type; description = '' Specify the configuration for Promtail in Nix. + This option will be ignored if `services.promtail.configFile` is defined. + ''; + }; + + configFile = mkOption { + type = nullOr path; + default = null; + description = '' + Config file path for Promtail. + If this option is defined, the value of `services.promtail.configuration` will be ignored. ''; }; @@ -42,14 +56,14 @@ in { stopIfChanged = false; preStart = '' - ${lib.getExe pkgs.promtail} -config.file=${prettyJSON cfg.configuration} -check-syntax + ${lib.getExe pkgs.promtail} -config.file=${configFile} -check-syntax ''; serviceConfig = { Restart = "on-failure"; TimeoutStopSec = 10; - ExecStart = "${pkgs.promtail}/bin/promtail -config.file=${prettyJSON cfg.configuration} ${escapeShellArgs cfg.extraFlags}"; + ExecStart = "${pkgs.promtail}/bin/promtail -config.file=${configFile} ${escapeShellArgs cfg.extraFlags}"; ProtectSystem = "strict"; ProtectHome = true; @@ -80,7 +94,7 @@ in { PrivateUsers = true; SupplementaryGroups = lib.optional (allowSystemdJournal) "systemd-journal"; - } // (optionalAttrs (!pkgs.stdenv.isAarch64) { # FIXME: figure out why this breaks on aarch64 + } // (optionalAttrs (!pkgs.stdenv.hostPlatform.isAarch64) { # FIXME: figure out why this breaks on aarch64 SystemCallFilter = "@system-service"; }); }; diff --git a/nixos/modules/services/logging/vector.nix b/nixos/modules/services/logging/vector.nix index c68d546bb96c..7c69e790171f 100644 --- a/nixos/modules/services/logging/vector.nix +++ b/nixos/modules/services/logging/vector.nix @@ -27,7 +27,7 @@ in config = lib.mkIf cfg.enable { # for cli usage - environment.systemPackages = [ pkgs.vector ]; + environment.systemPackages = [ cfg.package ]; systemd.services.vector = { description = "Vector event and log aggregator"; @@ -40,7 +40,7 @@ in conf = format.generate "vector.toml" cfg.settings; validateConfig = file: pkgs.runCommand "validate-vector-conf" { - nativeBuildInputs = [ pkgs.vector ]; + nativeBuildInputs = [ cfg.package ]; } '' vector validate --no-environment "${file}" ln -s "${file}" "$out" diff --git a/nixos/modules/services/mail/cyrus-imap.nix b/nixos/modules/services/mail/cyrus-imap.nix new file mode 100644 index 000000000000..f5ace168b045 --- /dev/null +++ b/nixos/modules/services/mail/cyrus-imap.nix @@ -0,0 +1,379 @@ +{ + pkgs, + lib, + config, + ... +}: +let + cfg = config.services.cyrus-imap; + cyrus-imapdPkg = pkgs.cyrus-imapd; + inherit (lib) + mkEnableOption + mkIf + mkOption + optionalAttrs + optionalString + generators + mapAttrsToList + ; + inherit (lib.strings) concatStringsSep; + inherit (lib.types) + attrsOf + submodule + listOf + oneOf + str + int + bool + nullOr + path + ; + + mkCyrusConfig = + settings: + let + mkCyrusOptionsList = + v: + mapAttrsToList ( + p: q: + if (q != null) then + if builtins.isInt q then + "${p}=${builtins.toString q}" + else + "${p}=\"${if builtins.isList q then (concatStringsSep " " q) else q}\"" + else + "" + ) v; + mkCyrusOptionsString = v: concatStringsSep " " (mkCyrusOptionsList v); + in + concatStringsSep "\n " (mapAttrsToList (n: v: n + " " + (mkCyrusOptionsString v)) settings); + + cyrusConfig = lib.concatStringsSep "\n" ( + lib.mapAttrsToList (n: v: '' + ${n} { + ${mkCyrusConfig v} + } + '') cfg.cyrusSettings + ); + + imapdConfig = + with generators; + toKeyValue { + mkKeyValue = mkKeyValueDefault { + mkValueString = + v: + if builtins.isBool v then + if v then "yes" else "no" + else if builtins.isList v then + concatStringsSep " " v + else + mkValueStringDefault { } v; + } ": "; + } cfg.imapdSettings; +in +{ + options.services.cyrus-imap = { + enable = mkEnableOption "Cyrus IMAP, an email, contacts and calendar server"; + debug = mkEnableOption "debugging messages for the Cyrus master process"; + + listenQueue = mkOption { + type = int; + default = 32; + description = '' + Socket listen queue backlog size. See listen(2) for more information about a backlog. + Default is 32, which may be increased if you have a very high connection rate. + ''; + }; + tmpDBDir = mkOption { + type = path; + default = "/run/cyrus/db"; + description = '' + Location where DB files are stored. + Databases in this directory are recreated upon startup, so ideally they should live in ephemeral storage for best performance. + ''; + }; + cyrusSettings = mkOption { + type = submodule { + freeformType = attrsOf ( + attrsOf (oneOf [ + bool + int + (listOf str) + ]) + ); + options = { + START = mkOption { + default = { + recover = { + cmd = [ + "ctl_cyrusdb" + "-r" + ]; + }; + }; + description = '' + This section lists the processes to run before any SERVICES are spawned. + This section is typically used to initialize databases. + Master itself will not startup until all tasks in START have completed, so put no blocking commands here. + ''; + }; + SERVICES = mkOption { + default = { + imap = { + cmd = [ "imapd" ]; + listen = "imap"; + prefork = 0; + }; + pop3 = { + cmd = [ "pop3d" ]; + listen = "pop3"; + prefork = 0; + }; + lmtpunix = { + cmd = [ "lmtpd" ]; + listen = "/run/cyrus/lmtp"; + prefork = 0; + }; + notify = { + cmd = [ "notifyd" ]; + listen = "/run/cyrus/notify"; + proto = "udp"; + prefork = 0; + }; + }; + description = '' + This section is the heart of the cyrus.conf file. It lists the processes that should be spawned to handle client connections made on certain Internet/UNIX sockets. + ''; + }; + EVENTS = mkOption { + default = { + tlsprune = { + cmd = [ "tls_prune" ]; + at = 400; + }; + delprune = { + cmd = [ + "cyr_expire" + "-E" + "3" + ]; + at = 400; + }; + deleteprune = { + cmd = [ + "cyr_expire" + "-E" + "4" + "-D" + "28" + ]; + at = 430; + }; + expungeprune = { + cmd = [ + "cyr_expire" + "-E" + "4" + "-X" + "28" + ]; + at = 445; + }; + checkpoint = { + cmd = [ + "ctl_cyrusdb" + "-c" + ]; + period = 30; + }; + }; + description = '' + This section lists processes that should be run at specific intervals, similar to cron jobs. This section is typically used to perform scheduled cleanup/maintenance. + ''; + }; + DAEMON = mkOption { + default = { }; + description = '' + This section lists long running daemons to start before any SERVICES are spawned. master(8) will ensure that these processes are running, restarting any process which dies or forks. All listed processes will be shutdown when master(8) is exiting. + ''; + }; + }; + }; + description = "Cyrus configuration settings. See [cyrus.conf(5)](https://www.cyrusimap.org/imap/reference/manpages/configs/cyrus.conf.html)"; + }; + imapdSettings = mkOption { + type = submodule { + freeformType = attrsOf (oneOf [ + str + int + bool + (listOf str) + ]); + options = { + configdirectory = mkOption { + type = path; + default = "/var/lib/cyrus"; + description = '' + The pathname of the IMAP configuration directory. + ''; + }; + lmtpsocket = mkOption { + type = path; + default = "/run/cyrus/lmtp"; + description = '' + Unix socket that lmtpd listens on, used by deliver(8). This should match the path specified in cyrus.conf(5). + ''; + }; + idlesocket = mkOption { + type = path; + default = "/run/cyrus/idle"; + description = '' + Unix socket that idled listens on. + ''; + }; + notifysocket = mkOption { + type = path; + default = "/run/cyrus/notify"; + description = '' + Unix domain socket that the mail notification daemon listens on. + ''; + }; + }; + }; + default = { + admins = [ "cyrus" ]; + allowplaintext = true; + defaultdomain = "localhost"; + defaultpartition = "default"; + duplicate_db_path = "/run/cyrus/db/deliver.db"; + hashimapspool = true; + httpmodules = [ + "carddav" + "caldav" + ]; + mboxname_lockpath = "/run/cyrus/lock"; + partition-default = "/var/lib/cyrus/storage"; + popminpoll = 1; + proc_path = "/run/cyrus/proc"; + ptscache_db_path = "/run/cyrus/db/ptscache.db"; + sasl_auto_transition = true; + sasl_pwcheck_method = [ "saslauthd" ]; + sievedir = "/var/lib/cyrus/sieve"; + statuscache_db_path = "/run/cyrus/db/statuscache.db"; + syslog_prefix = "cyrus"; + tls_client_ca_dir = "/etc/ssl/certs"; + tls_session_timeout = 1440; + tls_sessions_db_path = "/run/cyrus/db/tls_sessions.db"; + virtdomains = "on"; + }; + description = "IMAP configuration settings. See [imapd.conf(5)](https://www.cyrusimap.org/imap/reference/manpages/configs/imapd.conf.html)"; + }; + + user = mkOption { + type = nullOr str; + default = null; + description = "Cyrus IMAP user name. If this is not set, a user named `cyrus` will be created."; + }; + + group = mkOption { + type = nullOr str; + default = null; + description = "Cyrus IMAP group name. If this is not set, a group named `cyrus` will be created."; + }; + + imapdConfigFile = mkOption { + type = nullOr path; + default = null; + description = "Path to the configuration file used for cyrus-imap."; + apply = v: if v != null then v else pkgs.writeText "imapd.conf" imapdConfig; + }; + + cyrusConfigFile = mkOption { + type = nullOr path; + default = null; + description = "Path to the configuration file used for Cyrus."; + apply = v: if v != null then v else pkgs.writeText "cyrus.conf" cyrusConfig; + }; + + sslCACert = mkOption { + type = nullOr str; + default = null; + description = "File path which containing one or more CA certificates to use."; + }; + + sslServerCert = mkOption { + type = nullOr str; + default = null; + description = "File containing the global certificate used for all services (IMAP, POP3, LMTP, Sieve)"; + }; + + sslServerKey = mkOption { + type = nullOr str; + default = null; + description = "File containing the private key belonging to the global server certificate."; + }; + }; + + config = mkIf cfg.enable { + users.users.cyrus = optionalAttrs (cfg.user == null) { + description = "Cyrus IMAP user"; + isSystemUser = true; + group = optionalString (cfg.group == null) "cyrus"; + }; + + users.groups.cyrus = optionalAttrs (cfg.group == null) { }; + + environment.etc."imapd.conf".source = cfg.imapdConfigFile; + environment.etc."cyrus.conf".source = cfg.cyrusConfigFile; + + systemd.services.cyrus-imap = { + description = "Cyrus IMAP server"; + + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ + "/etc/imapd.conf" + "/etc/cyrus.conf" + ]; + + startLimitIntervalSec = 60; + environment = { + CYRUS_VERBOSE = mkIf cfg.debug "1"; + LISTENQUEUE = builtins.toString cfg.listenQueue; + }; + serviceConfig = { + User = if (cfg.user == null) then "cyrus" else cfg.user; + Group = if (cfg.group == null) then "cyrus" else cfg.group; + Type = "simple"; + ExecStart = "${cyrus-imapdPkg}/libexec/master -l $LISTENQUEUE -C /etc/imapd.conf -M /etc/cyrus.conf -p /run/cyrus/master.pid -D"; + Restart = "on-failure"; + RestartSec = "1s"; + RuntimeDirectory = "cyrus"; + StateDirectory = "cyrus"; + + # Hardening + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + PrivateTmp = true; + PrivateDevices = true; + ProtectSystem = "full"; + CapabilityBoundingSet = [ "~CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_MODULE" ]; + MemoryDenyWriteExecute = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + }; + preStart = '' + mkdir -p '${cfg.imapdSettings.configdirectory}/socket' '${cfg.tmpDBDir}' /run/cyrus/proc /run/cyrus/lock + ''; + }; + environment.systemPackages = [ cyrus-imapdPkg ]; + }; +} diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix index 75b08bb0593b..95b77da250b2 100644 --- a/nixos/modules/services/mail/mailman.nix +++ b/nixos/modules/services/mail/mailman.nix @@ -30,6 +30,10 @@ let ENGINE = "haystack.backends.whoosh_backend.WhooshEngine"; PATH = "/var/lib/mailman-web/fulltext-index"; }; + } // lib.optionalAttrs cfg.enablePostfix { + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"; + EMAIL_HOST = "127.0.0.1"; + EMAIL_PORT = 25; } // cfg.webSettings; webSettingsJSON = pkgs.writeText "settings.json" (builtins.toJSON webSettings); diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix index 1a9a3bdf26b9..337734488e31 100644 --- a/nixos/modules/services/mail/roundcube.nix +++ b/nixos/modules/services/mail/roundcube.nix @@ -55,7 +55,12 @@ in default = ""; }; passwordFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; + example = lib.literalExpression '' + pkgs.writeText "roundcube-postgres-passwd.txt" ''' + hostname:port:database:username:password + ''' + ''; description = '' Password file for the postgresql connection. Must be formatted according to PostgreSQL .pgpass standard (see https://www.postgresql.org/docs/current/libpq-pgpass.html) diff --git a/nixos/modules/services/mail/stalwart-mail.nix b/nixos/modules/services/mail/stalwart-mail.nix index aabe46d607a8..9985e49067a9 100644 --- a/nixos/modules/services/mail/stalwart-mail.nix +++ b/nixos/modules/services/mail/stalwart-mail.nix @@ -82,6 +82,7 @@ in { ) { webadmin = lib.mkDefault "file://${cfg.package.webadmin}/webadmin.zip"; }; + webadmin.path = "/var/cache/stalwart-mail"; }; # This service stores a potentially large amount of data. @@ -117,6 +118,7 @@ in { StandardOutput = "journal"; StandardError = "journal"; + CacheDirectory = "stalwart-mail"; StateDirectory = "stalwart-mail"; # Bind standard privileged ports diff --git a/nixos/modules/services/matrix/conduit.nix b/nixos/modules/services/matrix/conduit.nix index c484f67327d8..98ed19abd366 100644 --- a/nixos/modules/services/matrix/conduit.nix +++ b/nixos/modules/services/matrix/conduit.nix @@ -111,6 +111,8 @@ in description = "Conduit Matrix Server"; documentation = [ "https://gitlab.com/famedly/conduit/" ]; wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; environment = lib.mkMerge ([ { CONDUIT_CONFIG = configFile; } cfg.extraEnvironment diff --git a/nixos/modules/services/matrix/hebbot.nix b/nixos/modules/services/matrix/hebbot.nix index 9f344fbe08d0..ba676422ca1f 100644 --- a/nixos/modules/services/matrix/hebbot.nix +++ b/nixos/modules/services/matrix/hebbot.nix @@ -70,7 +70,7 @@ in LoadCredential = "bot-password-file:${cfg.botPasswordFile}"; RestartSec = "10s"; StateDirectory = "hebbot"; - WorkingDirectory = "hebbot"; + WorkingDirectory = "/var/lib/hebbot"; }; }; }; diff --git a/nixos/modules/services/matrix/matrix-sliding-sync.nix b/nixos/modules/services/matrix/matrix-sliding-sync.nix deleted file mode 100644 index d273bba3e52d..000000000000 --- a/nixos/modules/services/matrix/matrix-sliding-sync.nix +++ /dev/null @@ -1,107 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.services.matrix-sliding-sync; -in -{ - imports = [ - (lib.mkRenamedOptionModule [ "services" "matrix-synapse" "sliding-sync" ] [ "services" "matrix-sliding-sync" ]) - ]; - - options.services.matrix-sliding-sync = { - enable = lib.mkEnableOption "sliding sync"; - - package = lib.mkPackageOption pkgs "matrix-sliding-sync" { }; - - settings = lib.mkOption { - type = lib.types.submodule { - freeformType = with lib.types; attrsOf str; - options = { - SYNCV3_SERVER = lib.mkOption { - type = lib.types.str; - description = '' - The destination homeserver to talk to not including `/_matrix/` e.g `https://matrix.example.org`. - ''; - }; - - SYNCV3_DB = lib.mkOption { - type = lib.types.str; - default = "postgresql:///matrix-sliding-sync?host=/run/postgresql"; - description = '' - The postgres connection string. - Refer to <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>. - ''; - }; - - SYNCV3_BINDADDR = lib.mkOption { - type = lib.types.str; - default = "127.0.0.1:8009"; - example = "[::]:8008"; - description = "The interface and port or path (for unix socket) to listen on."; - }; - - SYNCV3_LOG_LEVEL = lib.mkOption { - type = lib.types.enum [ "trace" "debug" "info" "warn" "error" "fatal" ]; - default = "info"; - description = "The level of verbosity for messages logged."; - }; - }; - }; - default = { }; - description = '' - Freeform environment variables passed to the sliding sync proxy. - Refer to <https://github.com/matrix-org/sliding-sync#setup> for all supported values. - ''; - }; - - createDatabase = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Whether to enable and configure `services.postgres` to ensure that the database user `matrix-sliding-sync` - and the database `matrix-sliding-sync` exist. - ''; - }; - - environmentFile = lib.mkOption { - type = lib.types.str; - description = '' - Environment file as defined in {manpage}`systemd.exec(5)`. - - This must contain the {env}`SYNCV3_SECRET` variable which should - be generated with {command}`openssl rand -hex 32`. - ''; - }; - }; - - config = lib.mkIf cfg.enable { - services.postgresql = lib.optionalAttrs cfg.createDatabase { - enable = true; - ensureDatabases = [ "matrix-sliding-sync" ]; - ensureUsers = [ { - name = "matrix-sliding-sync"; - ensureDBOwnership = true; - } ]; - }; - - systemd.services.matrix-sliding-sync = rec { - after = - lib.optional cfg.createDatabase "postgresql.service" - ++ lib.optional config.services.dendrite.enable "dendrite.service" - ++ lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit; - wants = after; - wantedBy = [ "multi-user.target" ]; - environment = cfg.settings; - serviceConfig = { - DynamicUser = true; - EnvironmentFile = cfg.environmentFile; - ExecStart = lib.getExe cfg.package; - StateDirectory = "matrix-sliding-sync"; - WorkingDirectory = "%S/matrix-sliding-sync"; - RuntimeDirectory = "matrix-sliding-sync"; - Restart = "on-failure"; - RestartSec = "1s"; - }; - }; - }; -} diff --git a/nixos/modules/services/matrix/mautrix-meta.nix b/nixos/modules/services/matrix/mautrix-meta.nix index f0905c3af129..79f7239df9e5 100644 --- a/nixos/modules/services/matrix/mautrix-meta.nix +++ b/nixos/modules/services/matrix/mautrix-meta.nix @@ -69,11 +69,6 @@ in { appservice = { id = ""; - database = { - type = "sqlite3-fk-wal"; - uri = "file:${fullDataDir config}/mautrix-meta.db?_txlock=immediate"; - }; - bot = { username = ""; }; @@ -83,37 +78,43 @@ in { address = "http://${config.settings.appservice.hostname}:${toString config.settings.appservice.port}"; }; - meta = { - mode = ""; - }; - bridge = { - # Enable encryption by default to make the bridge more secure - encryption = { - allow = true; - default = true; - require = true; + permissions = {}; + }; - # Recommended options from mautrix documentation - # for additional security. - delete_keys = { - dont_store_outbound = true; - ratchet_on_decrypt = true; - delete_fully_used_on_decrypt = true; - delete_prev_on_new_session = true; - delete_on_device_delete = true; - periodically_delete_expired = true; - delete_outdated_inbound = true; - }; + database = { + type = "sqlite3-fk-wal"; + uri = "file:${fullDataDir config}/mautrix-meta.db?_txlock=immediate"; + }; - verification_levels = { - receive = "cross-signed-tofu"; - send = "cross-signed-tofu"; - share = "cross-signed-tofu"; - }; + # Enable encryption by default to make the bridge more secure + encryption = { + allow = true; + default = true; + require = true; + + # Recommended options from mautrix documentation + # for additional security. + delete_keys = { + dont_store_outbound = true; + ratchet_on_decrypt = true; + delete_fully_used_on_decrypt = true; + delete_prev_on_new_session = true; + delete_on_device_delete = true; + periodically_delete_expired = true; + delete_outdated_inbound = true; }; - permissions = {}; + # TODO: This effectively disables encryption. But this is the value provided when a <0.4 config is migrated. Changing it will corrupt the database. + # https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L24 + # If you wish to encrypt the local database you should set this to an environment variable substitution and reset the bridge or somehow migrate the DB. + pickle_key = "mautrix.bridge.e2ee"; + + verification_levels = { + receive = "cross-signed-tofu"; + send = "cross-signed-tofu"; + share = "cross-signed-tofu"; + }; }; logging = { @@ -124,6 +125,10 @@ in { time_format = " "; }; }; + + network = { + mode = ""; + }; }; defaultText = '' { @@ -261,7 +266,7 @@ in { description = '' Configuration of multiple `mautrix-meta` instances. `services.mautrix-meta.instances.facebook` and `services.mautrix-meta.instances.instagram` - come preconfigured with meta.mode, appservice.id, bot username, display name and avatar. + come preconfigured with network.mode, appservice.id, bot username, display name and avatar. ''; example = '' @@ -283,7 +288,7 @@ in { messenger = { enable = true; settings = { - meta.mode = "messenger"; + network.mode = "messenger"; homeserver.domain = "example.com"; appservice = { id = "messenger"; @@ -313,9 +318,9 @@ in { ''; } { - assertion = builtins.elem cfg.settings.meta.mode [ "facebook" "facebook-tor" "messenger" "instagram" ]; + assertion = builtins.elem cfg.settings.network.mode [ "facebook" "facebook-tor" "messenger" "instagram" ]; message = '' - The option `services.mautrix-meta.instances.${name}.settings.meta.mode` has to be set + The option `services.mautrix-meta.instances.${name}.settings.network.mode` has to be set to one of: facebook, facebook-tor, messenger, instagram. This configures the mode of the bridge. ''; @@ -338,6 +343,24 @@ in { The option `services.mautrix-meta.instances.${name}.settings.appservice.bot.username` has to be set. ''; } + { + assertion = !(cfg.settings ? bridge.disable_xma); + message = '' + The option `bridge.disable_xma` has been moved to `network.disable_xma_always`. Please [migrate your configuration](https://github.com/mautrix/meta/releases/tag/v0.4.0). You may wish to use [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) for reference. + ''; + } + { + assertion = !(cfg.settings ? bridge.displayname_template); + message = '' + The option `bridge.displayname_template` has been moved to `network.displayname_template`. Please [migrate your configuration](https://github.com/mautrix/meta/releases/tag/v0.4.0). You may wish to use [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) for reference. + ''; + } + { + assertion = !(cfg.settings ? meta); + message = '' + The options in `meta` have been moved to `network`. Please [migrate your configuration](https://github.com/mautrix/meta/releases/tag/v0.4.0). You may wish to use [the auto-migration code](https://github.com/mautrix/meta/blob/f5440b05aac125b4c95b1af85635a717cbc6dd0e/cmd/mautrix-meta/legacymigrate.go#L23) for reference. + ''; + } ]) enabledInstances)); users.users = lib.mapAttrs' (name: cfg: lib.nameValuePair "mautrix-meta-${name}" { @@ -518,11 +541,7 @@ in { in { instagram = { settings = { - meta.mode = mkDefault "instagram"; - - bridge = { - username_template = mkDefault "instagram_{{.}}"; - }; + network.mode = mkDefault "instagram"; appservice = { id = mkDefault "instagram"; @@ -532,16 +551,13 @@ in { displayname = mkDefault "Instagram bridge bot"; avatar = mkDefault "mxc://maunium.net/JxjlbZUlCPULEeHZSwleUXQv"; }; + username_template = mkDefault "instagram_{{.}}"; }; }; }; facebook = { settings = { - meta.mode = mkDefault "facebook"; - - bridge = { - username_template = mkDefault "facebook_{{.}}"; - }; + network.mode = mkDefault "facebook"; appservice = { id = mkDefault "facebook"; @@ -551,6 +567,7 @@ in { displayname = mkDefault "Facebook bridge bot"; avatar = mkDefault "mxc://maunium.net/ygtkteZsXnGJLJHRchUwYWak"; }; + username_template = mkDefault "facebook_{{.}}"; }; }; }; @@ -558,5 +575,5 @@ in { } ]; - meta.maintainers = with lib.maintainers; [ rutherther ]; + meta.maintainers = with lib.maintainers; [ ]; } diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix index e00dece33cab..085f38e522e6 100644 --- a/nixos/modules/services/matrix/mjolnir.nix +++ b/nixos/modules/services/matrix/mjolnir.nix @@ -186,6 +186,10 @@ in } ]; + # This defaults to true in the application, + # which breaks older configs using pantalaimon or access tokens + services.mjolnir.settings.encryption.use = lib.mkDefault false; + services.pantalaimon-headless.instances."mjolnir" = lib.mkIf cfg.pantalaimon.enable { homeserver = cfg.homeserverUrl; diff --git a/nixos/modules/services/misc/anki-sync-server.md b/nixos/modules/services/misc/anki-sync-server.md index 4796abf25c43..1210cad6d0af 100644 --- a/nixos/modules/services/misc/anki-sync-server.md +++ b/nixos/modules/services/misc/anki-sync-server.md @@ -42,6 +42,15 @@ Here, `passwordFile` is the path to a file containing just the password in plaintext. Make sure to set permissions to make this file unreadable to any user besides root. +By default, synced data are stored in */var/lib/anki-sync-server/*ankiuser**. +You can change the directory by using `services.anki-sync-server.baseDirectory` + +```nix +{ + services.anki-sync-server.baseDirectory = "/home/anki/data"; +} +``` + By default, the server listen address {option}`services.anki-sync-server.host` is set to localhost, listening on port {option}`services.anki-sync-server.port`, and does not open the firewall. This diff --git a/nixos/modules/services/misc/anki-sync-server.nix b/nixos/modules/services/misc/anki-sync-server.nix index a65382009417..c77afe8c3819 100644 --- a/nixos/modules/services/misc/anki-sync-server.nix +++ b/nixos/modules/services/misc/anki-sync-server.nix @@ -59,6 +59,13 @@ in { description = "Port number anki-sync-server listens to."; }; + baseDirectory = mkOption { + type = types.str; + default = "%S/%N"; + description = "Base directory where user(s) synchronized data will be stored."; + }; + + openFirewall = mkOption { default = false; type = types.bool; @@ -114,7 +121,7 @@ in { wantedBy = ["multi-user.target"]; path = [cfg.package]; environment = { - SYNC_BASE = "%S/%N"; + SYNC_BASE = cfg.baseDirectory; SYNC_HOST = specEscape cfg.address; SYNC_PORT = toString cfg.port; }; diff --git a/nixos/modules/services/misc/bazarr.nix b/nixos/modules/services/misc/bazarr.nix index e81b5d2b736e..30924da936b8 100644 --- a/nixos/modules/services/misc/bazarr.nix +++ b/nixos/modules/services/misc/bazarr.nix @@ -7,6 +7,8 @@ in services.bazarr = { enable = lib.mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr"; + package = lib.mkPackageOption pkgs "bazarr" { }; + openFirewall = lib.mkOption { type = lib.types.bool; default = false; @@ -35,7 +37,7 @@ in config = lib.mkIf cfg.enable { systemd.services.bazarr = { - description = "bazarr"; + description = "Bazarr"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; @@ -46,12 +48,14 @@ in StateDirectory = "bazarr"; SyslogIdentifier = "bazarr"; ExecStart = pkgs.writeShellScript "start-bazarr" '' - ${pkgs.bazarr}/bin/bazarr \ + ${cfg.package}/bin/bazarr \ --config '/var/lib/${StateDirectory}' \ --port ${toString cfg.listenPort} \ --no-update True ''; Restart = "on-failure"; + KillSignal = "SIGINT"; + SuccessExitStatus = "0 156"; }; }; diff --git a/nixos/modules/services/misc/calibre-server.nix b/nixos/modules/services/misc/calibre-server.nix index 5fa3c11a48aa..160a1c1e53c4 100644 --- a/nixos/modules/services/misc/calibre-server.nix +++ b/nixos/modules/services/misc/calibre-server.nix @@ -12,7 +12,7 @@ let "--port" = cfg.port; "--auth-mode" = cfg.auth.mode; "--userdb" = cfg.auth.userDb; - }) ++ [(lib.optionalString (cfg.auth.enable == true) "--enable-auth")]) + }) ++ [ (lib.optionalString (cfg.auth.enable == true) "--enable-auth") ] ++ cfg.extraFlags) ); in @@ -42,6 +42,15 @@ in ''; }; + extraFlags = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + Extra flags to pass to the calibre-server command. + See the [calibre-server documentation](${generatedDocumentationLink}) for details. + ''; + }; + user = lib.mkOption { type = lib.types.str; default = "calibre-server"; @@ -73,6 +82,13 @@ in ''; }; + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = + "Open ports in the firewall for the Calibre Server web interface."; + }; + auth = { enable = lib.mkOption { type = lib.types.bool; @@ -137,6 +153,9 @@ in }; }; + networking.firewall = + lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; }; + }; meta.maintainers = with lib.maintainers; [ gaelreyrol ]; diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix index 619fbcf92308..1869822a07f3 100644 --- a/nixos/modules/services/misc/disnix.nix +++ b/nixos/modules/services/misc/disnix.nix @@ -40,7 +40,7 @@ in ###### implementation config = lib.mkIf cfg.enable { - dysnomia.enable = true; + services.dysnomia.enable = true; environment.systemPackages = [ pkgs.disnix ] ++ lib.optional cfg.useWebServiceInterface pkgs.DisnixWebService; environment.variables.PATH = lib.optionals cfg.enableProfilePath (map (profileName: "/nix/var/nix/profiles/disnix/${profileName}/bin" ) cfg.profiles); @@ -74,7 +74,7 @@ in restartIfChanged = false; - path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ]; + path = [ config.nix.package cfg.package config.services.dysnomia.package "/run/current-system/sw" ]; environment = { HOME = "/root"; diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix index 99d5e3e1804a..76fc16020d32 100644 --- a/nixos/modules/services/misc/docker-registry.nix +++ b/nixos/modules/services/misc/docker-registry.nix @@ -96,7 +96,12 @@ in { extraConfig = lib.mkOption { description = '' - Docker extra registry configuration via environment variables. + Docker extra registry configuration. + ''; + example = lib.literalExpression '' + { + log.level = "debug"; + } ''; default = {}; type = lib.types.attrs; diff --git a/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix index 9f421d7ec375..36b6293843c4 100644 --- a/nixos/modules/services/misc/dysnomia.nix +++ b/nixos/modules/services/misc/dysnomia.nix @@ -1,6 +1,6 @@ {pkgs, lib, config, ...}: let - cfg = config.dysnomia; + cfg = config.services.dysnomia; printProperties = properties: lib.concatMapStrings (propertyName: @@ -79,7 +79,7 @@ let in { options = { - dysnomia = { + services.dysnomia = { enable = lib.mkOption { type = lib.types.bool; @@ -142,6 +142,10 @@ in }; }; + imports = [ + (lib.mkRenamedOptionModule ["dysnomia"] ["services" "dysnomia"]) + ]; + config = lib.mkIf cfg.enable { environment.etc = { @@ -164,7 +168,7 @@ in environment.systemPackages = [ cfg.package ]; - dysnomia.package = pkgs.dysnomia.override (origArgs: dysnomiaFlags // lib.optionalAttrs (cfg.enableLegacyModules) { + services.dysnomia.package = pkgs.dysnomia.override (origArgs: dysnomiaFlags // lib.optionalAttrs (cfg.enableLegacyModules) { enableLegacy = builtins.trace '' WARNING: Dysnomia has been configured to use the legacy 'process' and 'wrapper' modules for compatibility reasons! If you rely on these modules, consider @@ -181,7 +185,7 @@ in '' true; }); - dysnomia.properties = { + services.dysnomia.properties = { hostname = config.networking.hostName; inherit (pkgs.stdenv.hostPlatform) system; @@ -208,7 +212,7 @@ in ++ lib.optional (dysnomiaFlags.enableSubversionRepository) "subversion-repository"; }; - dysnomia.containers = lib.recursiveUpdate ({ + services.dysnomia.containers = lib.recursiveUpdate ({ process = {}; wrapper = {}; } diff --git a/nixos/modules/services/misc/fstrim.nix b/nixos/modules/services/misc/fstrim.nix index 8ead4a645f7c..f22505841fa3 100644 --- a/nixos/modules/services/misc/fstrim.nix +++ b/nixos/modules/services/misc/fstrim.nix @@ -8,7 +8,9 @@ in { options = { services.fstrim = { - enable = lib.mkEnableOption "periodic SSD TRIM of mounted partitions in background"; + enable = (lib.mkEnableOption "periodic SSD TRIM of mounted partitions in background" // { + default = true; + }); interval = lib.mkOption { type = lib.types.str; diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 9fd6014f2c71..ecbc087f739f 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -1127,6 +1127,11 @@ in { environment.systemPackages = [ gitlab-rake gitlab-rails cfg.packages.gitlab-shell ]; + systemd.slices.system-gitlab = { + description = "GitLab DevOps Platform Slice"; + documentation = [ "https://docs.gitlab.com/" ]; + }; + systemd.targets.gitlab = { description = "Common target for all GitLab services."; wantedBy = [ "multi-user.target" ]; @@ -1197,6 +1202,7 @@ in { ''; serviceConfig = { + Slice = "system-gitlab.slice"; User = pgsql.superUser; Type = "oneshot"; RemainAfterExit = true; @@ -1220,6 +1226,9 @@ in { unitConfig = { ConditionPathExists = "!${cfg.registry.certFile}"; }; + serviceConfig = { + Slice = "system-gitlab.slice"; + }; }; # Ensure Docker Registry launches after the certificate generation job @@ -1308,6 +1317,7 @@ in { TimeoutSec = "infinity"; Restart = "on-failure"; WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + Slice = "system-gitlab.slice"; RemainAfterExit = true; ExecStartPre = let @@ -1424,6 +1434,7 @@ in { TimeoutSec = "infinity"; Restart = "on-failure"; WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + Slice = "system-gitlab.slice"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "gitlab-db-config" '' @@ -1480,6 +1491,7 @@ in { TimeoutSec = "infinity"; Restart = "always"; WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + Slice = "system-gitlab.slice"; ExecStart = utils.escapeSystemdExecArgs ( [ "${cfg.packages.gitlab}/share/gitlab/bin/sidekiq-cluster" @@ -1512,6 +1524,7 @@ in { Restart = "on-failure"; WorkingDirectory = gitlabEnv.HOME; RuntimeDirectory = "gitaly"; + Slice = "system-gitlab.slice"; ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}"; }; }; @@ -1573,6 +1586,7 @@ in { WorkingDirectory = gitlabEnv.HOME; RuntimeDirectory = "gitlab-pages"; RuntimeDirectoryMode = "0700"; + Slice = "system-gitlab.slice"; }; }; @@ -1596,6 +1610,7 @@ in { TimeoutSec = "infinity"; Restart = "on-failure"; WorkingDirectory = gitlabEnv.HOME; + Slice = "system-gitlab.slice"; ExecStartPre = pkgs.writeShellScript "gitlab-workhorse-pre-start" '' set -o errexit -o pipefail -o nounset shopt -s dotglob nullglob inherit_errexit @@ -1637,6 +1652,7 @@ in { Group = cfg.group; ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.statePath}/config/mail_room.yml"; WorkingDirectory = gitlabEnv.HOME; + Slice = "system-gitlab.slice"; }; }; @@ -1671,6 +1687,7 @@ in { TimeoutSec = "infinity"; Restart = "on-failure"; WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + Slice = "system-gitlab.slice"; ExecStart = concatStringsSep " " [ "${cfg.packages.gitlab.rubyEnv}/bin/bundle" "exec" "puma" "-e production" @@ -1695,6 +1712,7 @@ in { serviceConfig = { User = cfg.user; Group = cfg.group; + Slice = "system-gitlab.slice"; ExecStart = "${gitlab-rake}/bin/gitlab-rake gitlab:backup:create"; }; }; diff --git a/nixos/modules/services/misc/gogs.nix b/nixos/modules/services/misc/gogs.nix deleted file mode 100644 index a2c1ad0779e1..000000000000 --- a/nixos/modules/services/misc/gogs.nix +++ /dev/null @@ -1,271 +0,0 @@ -{ config, lib, options, pkgs, ... }: -let - cfg = config.services.gogs; - opt = options.services.gogs; - configFile = pkgs.writeText "app.ini" '' - BRAND_NAME = ${cfg.appName} - RUN_USER = ${cfg.user} - RUN_MODE = prod - - [database] - TYPE = ${cfg.database.type} - HOST = ${cfg.database.host}:${toString cfg.database.port} - NAME = ${cfg.database.name} - USER = ${cfg.database.user} - PASSWORD = #dbpass# - PATH = ${cfg.database.path} - - [repository] - ROOT = ${cfg.repositoryRoot} - - [server] - DOMAIN = ${cfg.domain} - HTTP_ADDR = ${cfg.httpAddress} - HTTP_PORT = ${toString cfg.httpPort} - EXTERNAL_URL = ${cfg.rootUrl} - - [session] - COOKIE_NAME = session - COOKIE_SECURE = ${lib.boolToString cfg.cookieSecure} - - [security] - SECRET_KEY = #secretkey# - INSTALL_LOCK = true - - [log] - ROOT_PATH = ${cfg.stateDir}/log - - ${cfg.extraConfig} - ''; -in - -{ - options = { - services.gogs = { - enable = lib.mkOption { - default = false; - type = lib.types.bool; - description = "Enable Go Git Service."; - }; - - useWizard = lib.mkOption { - default = false; - type = lib.types.bool; - description = "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator."; - }; - - stateDir = lib.mkOption { - default = "/var/lib/gogs"; - type = lib.types.str; - description = "Gogs data directory."; - }; - - user = lib.mkOption { - type = lib.types.str; - default = "gogs"; - description = "User account under which Gogs runs."; - }; - - group = lib.mkOption { - type = lib.types.str; - default = "gogs"; - description = "Group account under which Gogs runs."; - }; - - database = { - type = lib.mkOption { - type = lib.types.enum [ "sqlite3" "mysql" "postgres" ]; - example = "mysql"; - default = "sqlite3"; - description = "Database engine to use."; - }; - - host = lib.mkOption { - type = lib.types.str; - default = "127.0.0.1"; - description = "Database host address."; - }; - - port = lib.mkOption { - type = lib.types.port; - default = 3306; - description = "Database host port."; - }; - - name = lib.mkOption { - type = lib.types.str; - default = "gogs"; - description = "Database name."; - }; - - user = lib.mkOption { - type = lib.types.str; - default = "gogs"; - description = "Database user."; - }; - - password = lib.mkOption { - type = lib.types.str; - default = ""; - description = '' - The password corresponding to {option}`database.user`. - Warning: this is stored in cleartext in the Nix store! - Use {option}`database.passwordFile` instead. - ''; - }; - - passwordFile = lib.mkOption { - type = lib.types.nullOr lib.types.path; - default = null; - example = "/run/keys/gogs-dbpassword"; - description = '' - A file containing the password corresponding to - {option}`database.user`. - ''; - }; - - path = lib.mkOption { - type = lib.types.str; - default = "${cfg.stateDir}/data/gogs.db"; - defaultText = lib.literalExpression ''"''${config.${opt.stateDir}}/data/gogs.db"''; - description = "Path to the sqlite3 database file."; - }; - }; - - appName = lib.mkOption { - type = lib.types.str; - default = "Gogs: Go Git Service"; - description = "Application name."; - }; - - repositoryRoot = lib.mkOption { - type = lib.types.str; - default = "${cfg.stateDir}/repositories"; - defaultText = lib.literalExpression ''"''${config.${opt.stateDir}}/repositories"''; - description = "Path to the git repositories."; - }; - - domain = lib.mkOption { - type = lib.types.str; - default = "localhost"; - description = "Domain name of your server."; - }; - - rootUrl = lib.mkOption { - type = lib.types.str; - default = "http://localhost:3000/"; - description = "Full public URL of Gogs server."; - }; - - httpAddress = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - description = "HTTP listen address."; - }; - - httpPort = lib.mkOption { - type = lib.types.port; - default = 3000; - description = "HTTP listen port."; - }; - - cookieSecure = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Marks session cookies as "secure" as a hint for browsers to only send - them via HTTPS. This option is recommend, if Gogs is being served over HTTPS. - ''; - }; - - extraConfig = lib.mkOption { - type = lib.types.str; - default = ""; - description = "Configuration lines appended to the generated Gogs configuration file."; - }; - }; - }; - - config = lib.mkIf cfg.enable { - - systemd.services.gogs = { - description = "Gogs (Go Git Service)"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pkgs.gogs ]; - - preStart = let - runConfig = "${cfg.stateDir}/custom/conf/app.ini"; - secretKey = "${cfg.stateDir}/custom/conf/secret_key"; - in '' - mkdir -p ${cfg.stateDir} - - # copy custom configuration and generate a random secret key if needed - ${lib.optionalString (cfg.useWizard == false) '' - mkdir -p ${cfg.stateDir}/custom/conf - cp -f ${configFile} ${runConfig} - - if [ ! -e ${secretKey} ]; then - head -c 16 /dev/urandom | base64 > ${secretKey} - fi - - KEY=$(head -n1 ${secretKey}) - DBPASS=$(head -n1 ${cfg.database.passwordFile}) - sed -e "s,#secretkey#,$KEY,g" \ - -e "s,#dbpass#,$DBPASS,g" \ - -i ${runConfig} - ''} - - mkdir -p ${cfg.repositoryRoot} - # update all hooks' binary paths - HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*") - if [ "$HOOKS" ] - then - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs}/bin/gogs,g' $HOOKS - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS - fi - ''; - - serviceConfig = { - Type = "simple"; - User = cfg.user; - Group = cfg.group; - WorkingDirectory = cfg.stateDir; - ExecStart = "${pkgs.gogs}/bin/gogs web"; - Restart = "always"; - UMask = "0027"; - }; - - environment = { - USER = cfg.user; - HOME = cfg.stateDir; - GOGS_WORK_DIR = cfg.stateDir; - }; - }; - - users = lib.mkIf (cfg.user == "gogs") { - users.gogs = { - description = "Go Git Service"; - uid = config.ids.uids.gogs; - group = "gogs"; - home = cfg.stateDir; - createHome = true; - shell = pkgs.bash; - }; - groups.gogs.gid = config.ids.gids.gogs; - }; - - warnings = lib.optional (cfg.database.password != "") - ''config.services.gogs.database.password will be stored as plaintext - in the Nix store. Use database.passwordFile instead.''; - - # Create database passwordFile default when password is configured. - services.gogs.database.passwordFile = - (lib.mkDefault (toString (pkgs.writeTextFile { - name = "gogs-database-password"; - text = cfg.database.password; - }))); - }; -} diff --git a/nixos/modules/services/misc/gotenberg.nix b/nixos/modules/services/misc/gotenberg.nix index 57932c656d63..ed8629a7fa46 100644 --- a/nixos/modules/services/misc/gotenberg.nix +++ b/nixos/modules/services/misc/gotenberg.nix @@ -244,7 +244,6 @@ in SystemCallFilter = [ "@system-service" - "~@resources" "~@privileged" ]; SystemCallArchitectures = "native"; diff --git a/nixos/modules/services/misc/guix/default.nix b/nixos/modules/services/misc/guix/default.nix index 7174ff36b709..9cd346a92b51 100644 --- a/nixos/modules/services/misc/guix/default.nix +++ b/nixos/modules/services/misc/guix/default.nix @@ -46,6 +46,17 @@ let GUIX_LOCPATH = "${cfg.stateDir}/guix/profiles/per-user/root/guix-profile/lib/locale"; LC_ALL = "C.UTF-8"; }; + + # Currently, this is just done the lazy way with the official Guix script. A + # more "formal" way would be creating our own Guix script to handle and + # generate the ACL file ourselves. + aclFile = pkgs.runCommandLocal "guix-acl" { } '' + export GUIX_CONFIGURATION_DIRECTORY=./ + for official_server_keys in ${lib.concatStringsSep " " cfg.substituters.authorizedKeys}; do + ${lib.getExe' cfg.package "guix"} archive --authorize < "$official_server_keys" + done + install -Dm0600 ./acl "$out" + ''; in { meta.maintainers = with lib.maintainers; [ foo-dogsquared ]; @@ -118,6 +129,57 @@ in example = "/gnu/var"; }; + substituters = { + urls = lib.mkOption { + type = with lib.types; listOf str; + default = [ + "https://ci.guix.gnu.org" + "https://bordeaux.guix.gnu.org" + "https://berlin.guix.gnu.org" + ]; + example = lib.literalExpression '' + options.services.guix.substituters.urls.default ++ [ + "https://guix.example.com" + "https://guix.example.org" + ] + ''; + description = '' + A list of substitute servers' URLs for the Guix daemon to download + substitutes from. + ''; + }; + + authorizedKeys = lib.mkOption { + type = with lib.types; listOf path; + default = [ + "${cfg.package}/share/guix/ci.guix.gnu.org.pub" + "${cfg.package}/share/guix/bordeaux.guix.gnu.org.pub" + "${cfg.package}/share/guix/berlin.guix.gnu.org.pub" + ]; + defaultText = '' + The packaged signing keys from {option}`services.guix.package`. + ''; + example = lib.literalExpression '' + options.services.guix.substituters.authorizedKeys.default ++ [ + (builtins.fetchurl { + url = "https://guix.example.com/signing-key.pub"; + }) + + (builtins.fetchurl { + url = "https://guix.example.org/static/signing-key.pub"; + }) + ] + ''; + description = '' + A list of signing keys for each substitute server to be authorized as + a source of substitutes. Without this, the listed substitute servers + from {option}`services.guix.substituters.urls` would be ignored [with + some + exceptions](https://guix.gnu.org/manual/en/html_node/Substitute-Authentication.html). + ''; + }; + }; + publish = { enable = mkEnableOption "substitute server for your Guix store directory"; @@ -213,8 +275,10 @@ in systemd.services.guix-daemon = { environment = serviceEnv; script = '' - ${lib.getExe' package "guix-daemon"} \ + exec ${lib.getExe' package "guix-daemon"} \ --build-users-group=${cfg.group} \ + ${lib.optionalString (cfg.substituters.urls != [ ]) + "--substitute-urls='${lib.concatStringsSep " " cfg.substituters.urls}'"} \ ${lib.escapeShellArgs cfg.extraArgs} ''; serviceConfig = { @@ -254,11 +318,7 @@ in # Make transferring files from one store to another easier with the usual # case being of most substitutes from the official Guix CI instance. - system.activationScripts.guix-authorize-keys = '' - for official_server_keys in ${package}/share/guix/*.pub; do - ${lib.getExe' package "guix"} archive --authorize < $official_server_keys - done - ''; + environment.etc."guix/acl".source = aclFile; # Link the usual Guix profiles to the home directory. This is useful in # ephemeral setups where only certain part of the filesystem is @@ -270,8 +330,8 @@ in in '' [ -d "${userProfile}" ] && ln -sfn "${userProfile}" "${location}" ''; - linkProfileToPath = acc: profile: location: let - in acc + (linkProfile profile location); + linkProfileToPath = acc: profile: location: + acc + (linkProfile profile location); # This should contain export-only Guix user profiles. The rest of it is # handled manually in the activation script. @@ -324,7 +384,7 @@ in } ''; script = '' - ${lib.getExe' package "guix"} publish \ + exec ${lib.getExe' package "guix"} publish \ --user=${cfg.publish.user} --port=${builtins.toString cfg.publish.port} \ ${lib.escapeShellArgs cfg.publish.extraArgs} ''; @@ -380,14 +440,12 @@ in description = "Guix garbage collection"; startAt = cfg.gc.dates; script = '' - ${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs} + exec ${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs} ''; - serviceConfig = { Type = "oneshot"; - PrivateDevices = true; - PrivateNetworks = true; + PrivateNetwork = true; ProtectControlGroups = true; ProtectHostname = true; ProtectKernelTunables = true; diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix index f0972f8caff4..d9eef161eb72 100644 --- a/nixos/modules/services/misc/klipper.nix +++ b/nixos/modules/services/misc/klipper.nix @@ -113,6 +113,7 @@ in ''; serial = lib.mkOption { type = lib.types.nullOr path; + default = null; description = "Path to serial port this printer is connected to. Leave `null` to derive it from `service.klipper.settings`."; }; configFile = lib.mkOption { diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix index 054a7803b9c4..ed98ef3ed8a6 100644 --- a/nixos/modules/services/misc/mame.nix +++ b/nixos/modules/services/misc/mame.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, ... }: let cfg = config.services.mame; - mame = "mame${lib.optionalString pkgs.stdenv.is64bit "64"}"; + mame = "mame${lib.optionalString pkgs.stdenv.hostPlatform.is64bit "64"}"; in { options = { diff --git a/nixos/modules/services/misc/mollysocket.nix b/nixos/modules/services/misc/mollysocket.nix index f40caa4a782e..460e8aa05266 100644 --- a/nixos/modules/services/misc/mollysocket.nix +++ b/nixos/modules/services/misc/mollysocket.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, ... }: let - inherit (lib) getExe mkIf mkOption mkEnableOption optionals types; + inherit (lib) getExe mkIf mkOption mkEnableOption types; cfg = config.services.mollysocket; configuration = format.generate "mollysocket.conf" cfg.settings; @@ -85,9 +85,7 @@ in { after = [ "network-online.target" ]; wants = [ "network-online.target" ]; environment.RUST_LOG = cfg.logLevel; - serviceConfig = let - capabilities = [ "" ] ++ optionals (cfg.settings.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; - in { + serviceConfig = { EnvironmentFile = cfg.environmentFile; ExecStart = "${getExe package} server"; KillSignal = "SIGINT"; @@ -97,8 +95,6 @@ in { WorkingDirectory = "/var/lib/mollysocket"; # hardening - AmbientCapabilities = capabilities; - CapabilityBoundingSet = capabilities; DevicePolicy = "closed"; DynamicUser = true; LockPersonality = true; diff --git a/nixos/modules/services/misc/nix-gc.nix b/nixos/modules/services/misc/nix-gc.nix index 9caca5d74079..69b25d6ae648 100644 --- a/nixos/modules/services/misc/nix-gc.nix +++ b/nixos/modules/services/misc/nix-gc.nix @@ -26,7 +26,7 @@ in How often or when garbage collection is performed. For most desktop and server systems a sufficient garbage collection is once a week. - The format is described in + This value must be a calendar event in the format specified by {manpage}`systemd.time(7)`. ''; }; diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix index a2726d455009..4738baf6d3e5 100644 --- a/nixos/modules/services/misc/nzbget.nix +++ b/nixos/modules/services/misc/nzbget.nix @@ -1,7 +1,6 @@ { config, pkgs, lib, ... }: let cfg = config.services.nzbget; - pkg = pkgs.nzbget; stateDir = "/var/lib/nzbget"; configFile = "${stateDir}/nzbget.conf"; configOpts = lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "-o ${name}=${lib.escapeShellArg (toStr value)}") cfg.settings); @@ -24,6 +23,8 @@ in services.nzbget = { enable = lib.mkEnableOption "NZBGet, for downloading files from news servers"; + package = lib.mkPackageOption pkgs "nzbget" { }; + user = lib.mkOption { type = lib.types.str; default = "nzbget"; @@ -64,8 +65,8 @@ in InfoTarget = "screen"; DetailTarget = "screen"; # required paths - ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf"; - WebDir = "${pkg}/share/nzbget/webui"; + ConfigTemplate = "${cfg.package}/share/nzbget/nzbget.conf"; + WebDir = "${cfg.package}/share/nzbget/webui"; # nixos handles package updates UpdateCheck = "none"; }; @@ -81,7 +82,7 @@ in preStart = '' if [ ! -f ${configFile} ]; then - ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile} + ${pkgs.coreutils}/bin/install -m 0700 ${cfg.package}/share/nzbget/nzbget.conf ${configFile} fi ''; @@ -92,8 +93,8 @@ in Group = cfg.group; UMask = "0002"; Restart = "on-failure"; - ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}"; - ExecStop = "${pkg}/bin/nzbget --quit"; + ExecStart = "${cfg.package}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}"; + ExecStop = "${cfg.package}/bin/nzbget --quit"; }; }; diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index 42b2926a7a1e..d8e4c9c302b3 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -5,10 +5,9 @@ let baseConfig = { plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine"; - server.host = cfg.host; server.port = cfg.port; webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg"; - }; + } // lib.optionalAttrs (cfg.host != null) {server.host = cfg.host;}; fullConfig = lib.recursiveUpdate cfg.extraConfig baseConfig; @@ -29,8 +28,8 @@ in enable = lib.mkEnableOption "OctoPrint, web interface for 3D printers"; host = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; + type = lib.types.nullOr lib.types.str; + default = null; description = '' Host to bind OctoPrint to. ''; diff --git a/nixos/modules/services/misc/ombi.nix b/nixos/modules/services/misc/ombi.nix index 51f3c3e468a5..89d8b5c53763 100644 --- a/nixos/modules/services/misc/ombi.nix +++ b/nixos/modules/services/misc/ombi.nix @@ -12,6 +12,8 @@ in { on how to set up a reverse proxy ''; + package = lib.mkPackageOption pkgs "ombi" { }; + dataDir = lib.mkOption { type = lib.types.str; default = "/var/lib/ombi"; @@ -58,7 +60,7 @@ in { Type = "simple"; User = cfg.user; Group = cfg.group; - ExecStart = "${pkgs.ombi}/bin/Ombi --storage '${cfg.dataDir}' --host 'http://*:${toString cfg.port}'"; + ExecStart = "${lib.getExe cfg.package} --storage '${cfg.dataDir}' --host 'http://*:${toString cfg.port}'"; Restart = "on-failure"; }; }; diff --git a/nixos/modules/services/misc/open-webui.nix b/nixos/modules/services/misc/open-webui.nix index abe27ccdbcf7..fd6fa64b9e19 100644 --- a/nixos/modules/services/misc/open-webui.nix +++ b/nixos/modules/services/misc/open-webui.nix @@ -93,6 +93,7 @@ in DATA_DIR = "."; HF_HOME = "."; SENTENCE_TRANSFORMERS_HOME = "."; + WEBUI_URL = "http://localhost:${toString cfg.port}"; } // cfg.environment; serviceConfig = { diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix index e45dded3683d..70cf4f9ff6c6 100644 --- a/nixos/modules/services/misc/paperless.nix +++ b/nixos/modules/services/misc/paperless.nix @@ -234,7 +234,7 @@ in services.redis.servers.paperless.enable = mkIf enableRedis true; systemd.slices.system-paperless = { - description = "Paperless slice"; + description = "Paperless Document Management System Slice"; documentation = [ "https://docs.paperless-ngx.com" ]; }; @@ -290,11 +290,12 @@ in '' + optionalString (cfg.passwordFile != null) '' export PAPERLESS_ADMIN_USER="''${PAPERLESS_ADMIN_USER:-admin}" - export PAPERLESS_ADMIN_PASSWORD=$(cat $CREDENTIALS_DIRECTORY/PAPERLESS_ADMIN_PASSWORD) + PAPERLESS_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PAPERLESS_ADMIN_PASSWORD") + export PAPERLESS_ADMIN_PASSWORD superuserState="$PAPERLESS_ADMIN_USER:$PAPERLESS_ADMIN_PASSWORD" superuserStateFile="${cfg.dataDir}/superuser-state" - if [[ $(cat "$superuserStateFile" 2>/dev/null) != $superuserState ]]; then + if [[ $(cat "$superuserStateFile" 2>/dev/null) != "$superuserState" ]]; then ${cfg.package}/bin/paperless-ngx manage_superuser echo "$superuserState" > "$superuserStateFile" fi @@ -353,7 +354,8 @@ in tr -dc A-Za-z0-9 < /dev/urandom | head -c64 | ${pkgs.moreutils}/bin/sponge '${secretKeyFile}' ) fi - export PAPERLESS_SECRET_KEY=$(cat '${secretKeyFile}') + PAPERLESS_SECRET_KEY="$(cat '${secretKeyFile}')" + export PAPERLESS_SECRET_KEY if [[ ! $PAPERLESS_SECRET_KEY ]]; then echo "PAPERLESS_SECRET_KEY is empty, refusing to start." exit 1 @@ -370,9 +372,6 @@ in SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid mbind" ]; # Needs to serve web page PrivateNetwork = false; - } // lib.optionalAttrs (cfg.port < 1024) { - AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; }; environment = env // { PYTHONPATH = "${cfg.package.python.pkgs.makePythonPath cfg.package.propagatedBuildInputs}:${cfg.package}/lib/paperless-ngx/src"; diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix index 026e4eae6d70..4cd2f35113ec 100644 --- a/nixos/modules/services/misc/redmine.nix +++ b/nixos/modules/services/misc/redmine.nix @@ -74,6 +74,12 @@ in description = "Group under which Redmine is ran."; }; + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "IP address Redmine should bind to."; + }; + port = mkOption { type = types.port; default = 3000; @@ -429,7 +435,31 @@ in Group = cfg.group; TimeoutSec = "300"; WorkingDirectory = "${cfg.package}/share/redmine"; - ExecStart="${bundle} exec rails server -u webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'"; + ExecStart="${bundle} exec rails server -u webrick -e production -b ${toString cfg.address} -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'"; + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = 027; }; }; diff --git a/nixos/modules/services/misc/renovate.nix b/nixos/modules/services/misc/renovate.nix index 9062b7424b68..9fbd8ec9e1ac 100644 --- a/nixos/modules/services/misc/renovate.nix +++ b/nixos/modules/services/misc/renovate.nix @@ -100,12 +100,10 @@ in ] ++ cfg.runtimePackages; serviceConfig = { - Type = "oneshot"; User = "renovate"; Group = "renovate"; DynamicUser = true; LoadCredential = lib.mapAttrsToList (name: value: "SECRET-${name}:${value}") cfg.credentials; - RemainAfterExit = false; Restart = "on-failure"; CacheDirectory = "renovate"; StateDirectory = "renovate"; diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix index 8cff517c56d9..42f782bcd6e1 100644 --- a/nixos/modules/services/misc/snapper.nix +++ b/nixos/modules/services/misc/snapper.nix @@ -35,6 +35,14 @@ let descriptionClass = "conjunction"; }; + intOrNumberOrRange = lib.types.either lib.types.ints.unsigned ( + lib.types.strMatching "[[:digit:]]+(\-[[:digit:]]+)?" + // { + description = "string containing either a number or a range"; + descriptionClass = "conjunction"; + } + ); + configOptions = { SUBVOLUME = lib.mkOption { type = lib.types.path; @@ -93,7 +101,7 @@ let }; TIMELINE_LIMIT_HOURLY = lib.mkOption { - type = lib.types.int; + type = intOrNumberOrRange; default = 10; description = '' Limits for timeline cleanup. @@ -101,7 +109,7 @@ let }; TIMELINE_LIMIT_DAILY = lib.mkOption { - type = lib.types.int; + type = intOrNumberOrRange; default = 10; description = '' Limits for timeline cleanup. @@ -109,7 +117,7 @@ let }; TIMELINE_LIMIT_WEEKLY = lib.mkOption { - type = lib.types.int; + type = intOrNumberOrRange; default = 0; description = '' Limits for timeline cleanup. @@ -117,7 +125,7 @@ let }; TIMELINE_LIMIT_MONTHLY = lib.mkOption { - type = lib.types.int; + type = intOrNumberOrRange; default = 10; description = '' Limits for timeline cleanup. @@ -125,7 +133,7 @@ let }; TIMELINE_LIMIT_QUARTERLY = lib.mkOption { - type = lib.types.int; + type = intOrNumberOrRange; default = 0; description = '' Limits for timeline cleanup. @@ -133,7 +141,7 @@ let }; TIMELINE_LIMIT_YEARLY = lib.mkOption { - type = lib.types.int; + type = intOrNumberOrRange; default = 10; description = '' Limits for timeline cleanup. diff --git a/nixos/modules/services/misc/tabby.nix b/nixos/modules/services/misc/tabby.nix index d63a6b24ae3d..8fabd10d90fa 100644 --- a/nixos/modules/services/misc/tabby.nix +++ b/nixos/modules/services/misc/tabby.nix @@ -71,7 +71,7 @@ in - nixpkgs.config.cudaSupport - nixpkgs.config.rocmSupport - - if stdenv.isDarwin && stdenv.isAarch64 + - if stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64 IFF multiple acceleration methods are found to be enabled or if you haven't set either `cudaSupport or rocmSupport` you will have to diff --git a/nixos/modules/services/misc/tandoor-recipes.nix b/nixos/modules/services/misc/tandoor-recipes.nix index 1c903d280378..f8a85e0ac221 100644 --- a/nixos/modules/services/misc/tandoor-recipes.nix +++ b/nixos/modules/services/misc/tandoor-recipes.nix @@ -119,9 +119,6 @@ in # gunicorn needs setuid SystemCallFilter = [ "@system-service" "~@privileged" "@resources" "@setuid" "@keyring" ]; UMask = "0066"; - } // lib.optionalAttrs (cfg.port < 1024) { - AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; }; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/misc/transfer-sh.nix b/nixos/modules/services/misc/transfer-sh.nix index 150af2337e14..b0d1b724d727 100644 --- a/nixos/modules/services/misc/transfer-sh.nix +++ b/nixos/modules/services/misc/transfer-sh.nix @@ -69,7 +69,6 @@ in 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}"; diff --git a/nixos/modules/services/misc/tzupdate.nix b/nixos/modules/services/misc/tzupdate.nix index be63bb179e42..9e979bb47675 100644 --- a/nixos/modules/services/misc/tzupdate.nix +++ b/nixos/modules/services/misc/tzupdate.nix @@ -30,16 +30,15 @@ in { description = "tzupdate timezone update service"; wants = [ "network-online.target" ]; after = [ "network-online.target" ]; + script = '' + timedatectl set-timezone $(${lib.getExe pkgs.tzupdate} --print-only) + ''; serviceConfig = { Type = "oneshot"; - # We could link directly into pkgs.tzdata, but at least timedatectl seems - # to expect the symlink to point directly to a file in etc. - # Setting the "debian timezone file" to point at /dev/null stops it doing anything. - ExecStart = "${pkgs.tzupdate}/bin/tzupdate -z /etc/zoneinfo -d /dev/null"; }; }; }; - meta.maintainers = [ ]; + meta.maintainers = with lib.maintainers; [ doronbehar ]; } diff --git a/nixos/modules/services/misc/wastebin.nix b/nixos/modules/services/misc/wastebin.nix index f24bf94fa52b..51dfe625c010 100644 --- a/nixos/modules/services/misc/wastebin.nix +++ b/nixos/modules/services/misc/wastebin.nix @@ -126,7 +126,6 @@ in 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}"; diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix index 5ac98bdf0382..04232b3c9346 100644 --- a/nixos/modules/services/monitoring/datadog-agent.nix +++ b/nixos/modules/services/monitoring/datadog-agent.nix @@ -288,7 +288,7 @@ in { path = [ ]; script = '' export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile}) - ${datadogPkg}/bin/trace-agent -config /etc/datadog-agent/datadog.yaml + ${datadogPkg}/bin/trace-agent --config /etc/datadog-agent/datadog.yaml ''; }); diff --git a/nixos/modules/services/monitoring/gatus.nix b/nixos/modules/services/monitoring/gatus.nix new file mode 100644 index 000000000000..408115f5d514 --- /dev/null +++ b/nixos/modules/services/monitoring/gatus.nix @@ -0,0 +1,132 @@ +{ + pkgs, + lib, + config, + ... +}: +let + cfg = config.services.gatus; + + settingsFormat = pkgs.formats.yaml { }; + + inherit (lib) + getExe + literalExpression + maintainers + mkEnableOption + mkIf + mkOption + mkPackageOption + ; + + inherit (lib.types) + bool + int + nullOr + path + submodule + ; +in +{ + options.services.gatus = { + enable = mkEnableOption "Gatus"; + + package = mkPackageOption pkgs "gatus" { }; + + configFile = mkOption { + type = path; + default = settingsFormat.generate "gatus.yaml" cfg.settings; + defaultText = literalExpression '' + let settingsFormat = pkgs.formats.yaml { }; in settingsFormat.generate "gatus.yaml" cfg.settings; + ''; + description = '' + Path to the Gatus configuration file. + Overrides any configuration made using the `settings` option. + ''; + }; + + environmentFile = mkOption { + type = nullOr path; + default = null; + description = '' + File to load as environment file. + Environmental variables from this file can be interpolated in the configuration file using `''${VARIABLE}`. + This is useful to avoid putting secrets into the nix store. + ''; + }; + + settings = mkOption { + type = submodule { + freeformType = settingsFormat.type; + options = { + web.port = mkOption { + type = int; + default = 8080; + description = '' + The TCP port to serve the Gatus service at. + ''; + }; + }; + }; + + default = { }; + + example = literalExpression '' + { + web.port = 8080; + endpoints = [{ + name = "website"; + url = "https://twin.sh/health"; + interval = "5m"; + conditions = [ + "[STATUS] == 200" + "[BODY].status == UP" + "[RESPONSE_TIME] < 300" + ]; + }]; + } + ''; + + description = '' + Configuration for Gatus. + Supported options can be found at the [docs](https://gatus.io/docs). + ''; + }; + + openFirewall = mkOption { + type = bool; + default = false; + description = '' + Whether to open the firewall for the Gatus web interface. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.gatus = { + description = "Automated developer-oriented status page"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + User = "gatus"; + Group = "gatus"; + Type = "simple"; + Restart = "on-failure"; + ExecStart = getExe cfg.package; + StateDirectory = "gatus"; + SyslogIdentifier = "gatus"; + EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile; + }; + + environment = { + GATUS_CONFIG_PATH = cfg.configFile; + }; + }; + + networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ cfg.settings.web.port ]; + }; + + meta.maintainers = with maintainers; [ pizzapim ]; +} diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index eae2658b7ffb..dbbec443bf42 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -255,6 +255,7 @@ in Grafana settings. See <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/> for available options. INI format is used. ''; + default = { }; type = types.submodule { freeformType = settingsFormatIni.type; diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix index cb259013c670..7987f34f1278 100644 --- a/nixos/modules/services/monitoring/graphite.nix +++ b/nixos/modules/services/monitoring/graphite.nix @@ -273,6 +273,7 @@ in { after = [ "network.target" ]; environment = carbonEnv; serviceConfig = { + Slice = "system-graphite.slice"; RuntimeDirectory = name; ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}"; User = "graphite"; @@ -295,6 +296,7 @@ in { after = [ "network.target" ]; environment = carbonEnv; serviceConfig = { + Slice = "system-graphite.slice"; RuntimeDirectory = name; ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}"; User = "graphite"; @@ -311,6 +313,7 @@ in { after = [ "network.target" ]; environment = carbonEnv; serviceConfig = { + Slice = "system-graphite.slice"; RuntimeDirectory = name; ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}"; User = "graphite"; @@ -360,6 +363,7 @@ in { User = "graphite"; Group = "graphite"; PermissionsStartOnly = true; + Slice = "system-graphite.slice"; }; preStart = '' if ! test -e ${dataDir}/db-created; then @@ -397,6 +401,7 @@ in { WorkingDirectory = dataDir; User = "graphite"; Group = "graphite"; + Slice = "system-graphite.slice"; }; preStart = '' if ! test -e ${dataDir}/db-created; then @@ -413,6 +418,11 @@ in { cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay || cfg.web.enable || cfg.seyren.enable ) { + systemd.slices.system-graphite = { + description = "Graphite Graphing System Slice"; + documentation = [ "https://graphite.readthedocs.io/en/latest/overview.html" ]; + }; + users.users.graphite = { uid = config.ids.uids.graphite; group = "graphite"; diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix index 0929b0ab5171..6e18ef562b8f 100644 --- a/nixos/modules/services/monitoring/netdata.nix +++ b/nixos/modules/services/monitoring/netdata.nix @@ -218,7 +218,6 @@ in { ps.psycopg2 ps.python-ldap ps.netdata-pandas - ps.changefinder ]); services.netdata.configDir.".opt-out-from-anonymous-statistics" = mkIf (!cfg.enableAnalyticsReporting) (pkgs.writeText ".opt-out-from-anonymous-statistics" ""); diff --git a/nixos/modules/services/monitoring/nezha-agent.nix b/nixos/modules/services/monitoring/nezha-agent.nix index 7ebbc7f2f329..e51328e48795 100644 --- a/nixos/modules/services/monitoring/nezha-agent.nix +++ b/nixos/modules/services/monitoring/nezha-agent.nix @@ -31,6 +31,20 @@ in Enable GPU monitoring. ''; }; + temperature = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Enable temperature monitoring. + ''; + }; + useIPv6CountryCode = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Use ipv6 countrycode to report location. + ''; + }; disableCommandExecute = lib.mkOption { type = lib.types.bool; default = true; @@ -78,6 +92,14 @@ in Address to the dashboard ''; }; + extraFlags = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "--gpu" ]; + description = '' + Extra command-line flags passed to nezha-agent. + ''; + }; }; }; @@ -96,7 +118,7 @@ in startLimitBurst = 3; script = lib.concatStringsSep " " ( [ - "${cfg.package}/bin/agent" + "${lib.getExe cfg.package}" "--disable-auto-update" "--disable-force-update" "--password $(cat ${cfg.passwordFile})" @@ -109,6 +131,9 @@ in ++ lib.optional cfg.skipProcess "--skip-procs" ++ lib.optional cfg.tls "--tls" ++ lib.optional cfg.gpu "--gpu" + ++ lib.optional cfg.temperature "--temperature" + ++ lib.optional cfg.useIPv6CountryCode "--use-ipv6-countrycode" + ++ cfg.extraFlags ); wantedBy = [ "multi-user.target" ]; }; diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix index 4de80acfa9a8..01e7a7366709 100644 --- a/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixos/modules/services/monitoring/prometheus/default.nix @@ -201,6 +201,26 @@ let }; }; + promTypes.sigv4 = types.submodule { + options = { + region = mkOpt types.str '' + The AWS region. + ''; + access_key = mkOpt types.str '' + The Access Key ID. + ''; + secret_key = mkOpt types.str '' + The Secret Access Key. + ''; + profile = mkOpt types.str '' + The named AWS profile used to authenticate. + ''; + role_arn = mkOpt types.str '' + The AWS role ARN. + ''; + }; + }; + promTypes.tls_config = types.submodule { options = { ca_file = mkOpt types.str '' @@ -1464,6 +1484,9 @@ let Sets the `Authorization` header on every remote write request with the bearer token read from the configured file. It is mutually exclusive with `bearer_token`. ''; + sigv4 = mkOpt promTypes.sigv4 '' + Configures AWS Signature Version 4 settings. + ''; tls_config = mkOpt promTypes.tls_config '' Configures the remote write request's TLS settings. ''; @@ -1811,8 +1834,6 @@ in StateDirectory = cfg.stateDir; StateDirectoryMode = "0700"; # Hardening - AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ]; DeviceAllow = [ "/dev/null rw" ]; DevicePolicy = "strict"; LockPersonality = true; diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 29a30e938e75..c388a7aa5558 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -50,6 +50,7 @@ let "junos-czerwonk" "kea" "keylight" + "klipper" "knot" "lnd" "mail" @@ -87,7 +88,6 @@ let "statsd" "surfboard" "systemd" - "tor" "unbound" "unifi" "unpoller" @@ -298,6 +298,9 @@ in The Minio exporter has been removed, as it was broken and unmaintained. See the 24.11 release notes for more information. '') + (lib.mkRemovedOptionModule [ "tor" ] '' + The Tor exporter has been removed, as it was broken and unmaintained. + '') ]; }; description = "Prometheus exporter configuration"; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix index ba438ea74a3b..9028d1fd5a66 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix @@ -21,8 +21,8 @@ in }; leasesPath = mkOption { type = types.path; - default = "/var/lib/misc/dnsmasq.leases"; - example = "/var/lib/dnsmasq/dnsmasq.leases"; + default = "/var/lib/dnsmasq/dnsmasq.leases"; + example = "/var/lib/misc/dnsmasq.leases"; description = '' Path to the `dnsmasq.leases` file. ''; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/klipper.nix b/nixos/modules/services/monitoring/prometheus/exporters/klipper.nix new file mode 100644 index 000000000000..2d38a5023a0f --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/klipper.nix @@ -0,0 +1,55 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.prometheus.exporters.klipper; + inherit (lib) + mkOption + mkMerge + mkIf + types + concatStringsSep + any + optionalString + ; + moonraker = config.services.moonraker; +in +{ + port = 9101; + extraOpts = { + package = lib.mkPackageOption pkgs "prometheus-klipper-exporter" { }; + + moonrakerApiKey = mkOption { + type = types.str; + default = ""; + description = '' + API Key to authenticate with the Moonraker APIs. + Only needed if the host running the exporter is not a trusted client to Moonraker. + ''; + }; + }; + serviceOpts = mkMerge ( + [ + { + serviceConfig = { + ExecStart = concatStringsSep " " [ + "${cfg.package}/bin/prometheus-klipper-exporter" + (optionalString (cfg.moonrakerApiKey != "") "--moonraker.apikey ${cfg.moonrakerApiKey}") + "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}" + "${concatStringsSep " " cfg.extraFlags}" + ]; + }; + } + ] + ++ [ + (mkIf config.services.moonraker.enable { + after = [ "moonraker.service" ]; + requires = [ "moonraker.service" ]; + }) + ] + ); +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix deleted file mode 100644 index d39112d0c283..000000000000 --- a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ config, lib, pkgs, options, ... }: - -let - cfg = config.services.prometheus.exporters.tor; - inherit (lib) mkOption types concatStringsSep; -in -{ - port = 9130; - extraOpts = { - torControlAddress = mkOption { - type = types.str; - default = "127.0.0.1"; - description = '' - Tor control IP address or hostname. - ''; - }; - - torControlPort = mkOption { - type = types.port; - default = 9051; - description = '' - Tor control port. - ''; - }; - }; - serviceOpts = { - serviceConfig = { - ExecStart = '' - ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \ - -b ${cfg.listenAddress} \ - -p ${toString cfg.port} \ - -a ${cfg.torControlAddress} \ - -c ${toString cfg.torControlPort} \ - ${concatStringsSep " \\\n " cfg.extraFlags} - ''; - }; - - # CPython requires a process to either have $HOME defined or run as a UID - # defined in /etc/passwd. The latter is false with DynamicUser, so define a - # dummy $HOME. https://bugs.python.org/issue10496 - environment = { HOME = "/var/empty"; }; - }; -} diff --git a/nixos/modules/services/monitoring/rustdesk-server.nix b/nixos/modules/services/monitoring/rustdesk-server.nix index ea4dd43cbb35..10495ee0618d 100644 --- a/nixos/modules/services/monitoring/rustdesk-server.nix +++ b/nixos/modules/services/monitoring/rustdesk-server.nix @@ -3,6 +3,12 @@ let TCPPorts = [21115 21116 21117 21118 21119]; UDPPorts = [21116]; in { + imports = [ + (lib.mkRemovedOptionModule [ "services" "rustdesk-server" "relayIP" ] "This option has been replaced by services.rustdesk-server.signal.relayHosts") + (lib.mkRenamedOptionModule [ "services" "rustdesk-server" "extraRelayArgs" ] [ "services" "rustdesk-server" "relay" "extraArgs" ]) + (lib.mkRenamedOptionModule [ "services" "rustdesk-server" "extraSignalArgs" ] [ "services" "rustdesk-server" "signal" "extraArgs" ]) + ]; + options.services.rustdesk-server = with lib; with types; { enable = mkEnableOption "RustDesk, a remote access and remote control software, allowing maintenance of computers and other devices"; @@ -18,30 +24,53 @@ in { ''; }; - relayIP = mkOption { - type = str; - description = '' - The public facing IP of the RustDesk relay. - ''; - }; + signal = { + enable = mkOption { + type = bool; + default = true; + description = '' + Whether to enable the RustDesk signal server. + ''; + }; + + relayHosts = mkOption { + type = listOf str; + default = []; + # reference: https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/relay/ + description = '' + The relay server IP addresses or DNS names of the RustDesk relay. + ''; + }; + + extraArgs = mkOption { + type = listOf str; + default = []; + example = [ "-k" "_" ]; + description = '' + A list of extra command line arguments to pass to the `hbbs` process. + ''; + }; - extraSignalArgs = mkOption { - type = listOf str; - default = []; - example = [ "-k" "_" ]; - description = '' - A list of extra command line arguments to pass to the `hbbs` process. - ''; }; - extraRelayArgs = mkOption { - type = listOf str; - default = []; - example = [ "-k" "_" ]; - description = '' - A list of extra command line arguments to pass to the `hbbr` process. - ''; + relay = { + enable = mkOption { + type = bool; + default = true; + description = '' + Whether to enable the RustDesk relay server. + ''; + }; + extraArgs = mkOption { + type = listOf str; + default = []; + example = [ "-k" "_" ]; + description = '' + A list of extra command line arguments to pass to the `hbbr` process. + ''; + }; }; + }; config = let @@ -86,7 +115,7 @@ in { systemd.slices.system-rustdesk = { enable = true; - description = "Slice designed to contain RustDesk Signal & RustDesk Relay"; + description = "RustDesk Remote Desktop Slice"; }; systemd.targets.rustdesk = { @@ -96,13 +125,17 @@ in { wantedBy = [ "multi-user.target" ]; }; - systemd.services.rustdesk-signal = lib.mkMerge [ serviceDefaults { - serviceConfig.ExecStart = "${cfg.package}/bin/hbbs -r ${cfg.relayIP} ${lib.escapeShellArgs cfg.extraSignalArgs}"; - } ]; + systemd.services.rustdesk-signal = + let + relayArg = builtins.concatStringsSep ":" cfg.signal.relayHosts; + in + lib.mkIf cfg.signal.enable (lib.mkMerge [ serviceDefaults { + serviceConfig.ExecStart = "${cfg.package}/bin/hbbs --relay-servers ${relayArg} ${lib.escapeShellArgs cfg.signal.extraArgs}"; + } ]); - systemd.services.rustdesk-relay = lib.mkMerge [ serviceDefaults { - serviceConfig.ExecStart = "${cfg.package}/bin/hbbr ${lib.escapeShellArgs cfg.extraRelayArgs}"; - } ]; + systemd.services.rustdesk-relay = lib.mkIf cfg.relay.enable (lib.mkMerge [ serviceDefaults { + serviceConfig.ExecStart = "${cfg.package}/bin/hbbr ${lib.escapeShellArgs cfg.relay.extraArgs}"; + } ]); }; meta.maintainers = with lib.maintainers; [ ppom ]; diff --git a/nixos/modules/services/monitoring/scrutiny.nix b/nixos/modules/services/monitoring/scrutiny.nix index c0ead07066ec..37a991674dae 100644 --- a/nixos/modules/services/monitoring/scrutiny.nix +++ b/nixos/modules/services/monitoring/scrutiny.nix @@ -177,6 +177,18 @@ in SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db"; SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny"; }; + postStart = '' + for i in $(seq 300); do + if "${lib.getExe pkgs.curl}" --fail --silent --head "http://${cfg.settings.web.listen.host}:${toString cfg.settings.web.listen.port}" >/dev/null; then + echo "Scrutiny is ready (port is open)" + exit 0 + fi + echo "Waiting for Scrutiny to open port..." + sleep 0.2 + done + echo "Timeout waiting for Scrutiny to open port" >&2 + exit 1 + ''; serviceConfig = { DynamicUser = true; ExecStart = "${getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}"; diff --git a/nixos/modules/services/monitoring/teamviewer.nix b/nixos/modules/services/monitoring/teamviewer.nix index 360cdd1c6b6a..45bb45a53c1b 100644 --- a/nixos/modules/services/monitoring/teamviewer.nix +++ b/nixos/modules/services/monitoring/teamviewer.nix @@ -1,37 +1,31 @@ { config, lib, pkgs, ... }: -with lib; - let - cfg = config.services.teamviewer; - in - { - - ###### interface - options = { - - services.teamviewer.enable = mkEnableOption "TeamViewer daemon"; - + services.teamviewer = { + enable = lib.mkEnableOption "TeamViewer daemon & system package"; + package = lib.mkPackageOption pkgs "teamviewer" { }; + }; }; - ###### implementation + config = lib.mkIf (cfg.enable) { + environment.systemPackages = [ cfg.package ]; - config = mkIf (cfg.enable) { - - environment.systemPackages = [ pkgs.teamviewer ]; - - services.dbus.packages = [ pkgs.teamviewer ]; + services.dbus.packages = [ cfg.package ]; systemd.services.teamviewerd = { description = "TeamViewer remote control daemon"; wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ]; - after = [ "network-online.target" "network.target" "dbus.service" ]; + after = [ + "network-online.target" + "network.target" + "dbus.service" + ]; requires = [ "dbus.service" ]; preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer"; @@ -39,12 +33,11 @@ in startLimitBurst = 10; serviceConfig = { Type = "simple"; - ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -f"; + ExecStart = "${cfg.package}/bin/teamviewerd -f"; PIDFile = "/run/teamviewerd.pid"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; Restart = "on-abort"; }; }; }; - } diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix index 9361393d91af..272f6f38f545 100644 --- a/nixos/modules/services/monitoring/ups.nix +++ b/nixos/modules/services/monitoring/ups.nix @@ -309,8 +309,10 @@ let defaultText = lib.literalMD '' { MINSUPPLIES = 1; - RUN_AS_USER = "root"; + MONITOR = <generated from config.power.ups.upsmon.monitor> NOTIFYCMD = "''${pkgs.nut}/bin/upssched"; + POWERDOWNFLAG = "/run/killpower"; + RUN_AS_USER = "root"; SHUTDOWNCMD = "''${pkgs.systemd}/bin/shutdown now"; } ''; @@ -330,11 +332,12 @@ let config = { enable = lib.mkDefault (lib.elem cfg.mode [ "standalone" "netserver" "netclient" ]); settings = { - RUN_AS_USER = "root"; # TODO: replace 'root' by another username. MINSUPPLIES = lib.mkDefault 1; + MONITOR = lib.flip lib.mapAttrsToList cfg.upsmon.monitor (name: monitor: with monitor; [ system powerValue user "\"@upsmon_password_${name}@\"" type ]); NOTIFYCMD = lib.mkDefault "${pkgs.nut}/bin/upssched"; + POWERDOWNFLAG = lib.mkDefault "/run/killpower"; + RUN_AS_USER = "root"; # TODO: replace 'root' by another username. SHUTDOWNCMD = lib.mkDefault "${pkgs.systemd}/bin/shutdown now"; - MONITOR = lib.flip lib.mapAttrsToList cfg.upsmon.monitor (name: monitor: with monitor; [ system powerValue user "\"@upsmon_password_${name}@\"" type ]); }; }; }; @@ -574,6 +577,24 @@ in ]; }; + systemd.services.ups-killpower = lib.mkIf (cfg.upsmon.settings.POWERDOWNFLAG != null) { + enable = cfg.upsd.enable; + description = "UPS Kill Power"; + wantedBy = [ "shutdown.target" ]; + after = [ "shutdown.target" ]; + before = [ "final.target" ]; + unitConfig = { + ConditionPathExists = cfg.upsmon.settings.POWERDOWNFLAG; + DefaultDependencies = "no"; + }; + environment = envVars; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.nut}/bin/upsdrvctl shutdown"; + Slice = "system-ups.slice"; + }; + }; + environment.etc = { "nut/nut.conf".source = pkgs.writeText "nut.conf" '' diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix index 8df25e6373ca..e661d3ff33bf 100644 --- a/nixos/modules/services/network-filesystems/samba.nix +++ b/nixos/modules/services/network-filesystems/samba.nix @@ -179,7 +179,7 @@ in systemd = { slices.system-samba = { - description = "Samba slice"; + description = "Samba (SMB Networking Protocol) Slice"; }; targets.samba = { description = "Samba Server"; diff --git a/nixos/modules/services/network-filesystems/saunafs.nix b/nixos/modules/services/network-filesystems/saunafs.nix new file mode 100644 index 000000000000..5c3c513c06f7 --- /dev/null +++ b/nixos/modules/services/network-filesystems/saunafs.nix @@ -0,0 +1,287 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.saunafs; + + settingsFormat = + let + listSep = " "; + allowedTypes = with lib.types; [ + bool + int + float + str + ]; + valueToString = + val: + if lib.isList val then + lib.concatStringsSep listSep (map (x: valueToString x) val) + else if lib.isBool val then + (if val then "1" else "0") + else + toString val; + + in + { + type = + let + valueType = + lib.types.oneOf ( + [ + (lib.types.listOf valueType) + ] + ++ allowedTypes + ) + // { + description = "Flat key-value file"; + }; + in + lib.types.attrsOf valueType; + + generate = + name: value: + pkgs.writeText name ( + lib.concatStringsSep "\n" (lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value) + ); + }; + + initTool = pkgs.writeShellScriptBin "sfsmaster-init" '' + if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.sfs ]; then + cp --update=none ${pkgs.saunafs}/var/lib/saunafs/metadata.sfs.empty ${cfg.master.settings.DATA_PATH}/metadata.sfs + chmod +w ${cfg.master.settings.DATA_PATH}/metadata.sfs + fi + ''; + + # master config file + masterCfg = settingsFormat.generate "sfsmaster.cfg" cfg.master.settings; + + # metalogger config file + metaloggerCfg = settingsFormat.generate "sfsmetalogger.cfg" cfg.metalogger.settings; + + # chunkserver config file + chunkserverCfg = settingsFormat.generate "sfschunkserver.cfg" cfg.chunkserver.settings; + + # generic template for all daemons + systemdService = name: extraConfig: configFile: { + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ + "network.target" + "network-online.target" + ]; + + serviceConfig = { + Type = "forking"; + ExecStart = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} start"; + ExecStop = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} stop"; + ExecReload = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} reload"; + } // extraConfig; + }; + +in +{ + ###### interface + + options = { + services.saunafs = { + masterHost = lib.mkOption { + type = lib.types.str; + default = null; + description = "IP or hostname name of master host."; + }; + + sfsUser = lib.mkOption { + type = lib.types.str; + default = "saunafs"; + description = "Run daemons as user."; + }; + + client.enable = lib.mkEnableOption "Saunafs client"; + + master = { + enable = lib.mkOption { + type = lib.types.bool; + description = '' + Enable Saunafs master daemon. + + You need to run `sfsmaster-init` on a freshly installed master server to + initialize the `DATA_PATH` directory. + ''; + default = false; + }; + + exports = lib.mkOption { + type = with lib.types; listOf str; + default = null; + description = "Paths to exports file (see {manpage}`sfsexports.cfg(5)`)."; + example = lib.literalExpression '' + [ "* / rw,alldirs,admin,maproot=0:0" ]; + ''; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + description = "Whether to automatically open the necessary ports in the firewall."; + default = false; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options.DATA_PATH = lib.mkOption { + type = lib.types.str; + default = "/var/lib/saunafs/master"; + description = "Data storage directory."; + }; + }; + + description = "Contents of config file ({manpage}`sfsmaster.cfg(5)`)."; + }; + }; + + metalogger = { + enable = lib.mkEnableOption "Saunafs metalogger daemon"; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options.DATA_PATH = lib.mkOption { + type = lib.types.str; + default = "/var/lib/saunafs/metalogger"; + description = "Data storage directory"; + }; + }; + + description = "Contents of metalogger config file (see {manpage}`sfsmetalogger.cfg(5)`)."; + }; + }; + + chunkserver = { + enable = lib.mkEnableOption "Saunafs chunkserver daemon"; + + openFirewall = lib.mkOption { + type = lib.types.bool; + description = "Whether to automatically open the necessary ports in the firewall."; + default = false; + }; + + hdds = lib.mkOption { + type = with lib.types; listOf str; + default = null; + + example = lib.literalExpression '' + [ "/mnt/hdd1" ]; + ''; + + description = '' + Mount points to be used by chunkserver for storage (see {manpage}`sfshdd.cfg(5)`). + + Note, that these mount points must writeable by the user defined by the saunafs user. + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options.DATA_PATH = lib.mkOption { + type = lib.types.str; + default = "/var/lib/saunafs/chunkserver"; + description = "Directory for chunck meta data"; + }; + }; + + description = "Contents of chunkserver config file (see {manpage}`sfschunkserver.cfg(5)`)."; + }; + }; + }; + }; + + ###### implementation + + config = + lib.mkIf (cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable) + { + + warnings = [ + (lib.mkIf (cfg.sfsUser == "root") "Running saunafs services as root is not recommended.") + ]; + + # Service settings + services.saunafs = { + master.settings = lib.mkIf cfg.master.enable { + WORKING_USER = cfg.sfsUser; + EXPORTS_FILENAME = toString ( + pkgs.writeText "sfsexports.cfg" (lib.concatStringsSep "\n" cfg.master.exports) + ); + }; + + metalogger.settings = lib.mkIf cfg.metalogger.enable { + WORKING_USER = cfg.sfsUser; + MASTER_HOST = cfg.masterHost; + }; + + chunkserver.settings = lib.mkIf cfg.chunkserver.enable { + WORKING_USER = cfg.sfsUser; + MASTER_HOST = cfg.masterHost; + HDD_CONF_FILENAME = toString ( + pkgs.writeText "sfshdd.cfg" (lib.concatStringsSep "\n" cfg.chunkserver.hdds) + ); + }; + }; + + # Create system user account for daemons + users = + lib.mkIf + (cfg.sfsUser != "root" && (cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable)) + { + users."${cfg.sfsUser}" = { + isSystemUser = true; + description = "saunafs daemon user"; + group = "saunafs"; + }; + groups."${cfg.sfsUser}" = { }; + }; + + environment.systemPackages = + (lib.optional cfg.client.enable pkgs.saunafs) ++ (lib.optional cfg.master.enable initTool); + + networking.firewall.allowedTCPPorts = + (lib.optionals cfg.master.openFirewall [ + 9419 + 9420 + 9421 + ]) + ++ (lib.optional cfg.chunkserver.openFirewall 9422); + + # Ensure storage directories exist + systemd.tmpfiles.rules = + lib.optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -" + ++ lib.optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -" + ++ lib.optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -"; + + # Service definitions + systemd.services.sfs-master = lib.mkIf cfg.master.enable ( + systemdService "master" { + TimeoutStartSec = 1800; + TimeoutStopSec = 1800; + Restart = "no"; + } masterCfg + ); + + systemd.services.sfs-metalogger = lib.mkIf cfg.metalogger.enable ( + systemdService "metalogger" { Restart = "on-abort"; } metaloggerCfg + ); + + systemd.services.sfs-chunkserver = lib.mkIf cfg.chunkserver.enable ( + systemdService "chunkserver" { Restart = "on-abort"; } chunkserverCfg + ); + }; +} diff --git a/nixos/modules/services/networking/atticd.nix b/nixos/modules/services/networking/atticd.nix new file mode 100644 index 000000000000..3984c434c60e --- /dev/null +++ b/nixos/modules/services/networking/atticd.nix @@ -0,0 +1,234 @@ +{ + lib, + pkgs, + config, + ... +}: + +let + inherit (lib) types; + + cfg = config.services.atticd; + + format = pkgs.formats.toml { }; + + checkedConfigFile = + pkgs.runCommand "checked-attic-server.toml" + { + configFile = format.generate "server.toml" cfg.settings; + } + '' + export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="$(${lib.getExe pkgs.openssl} genrsa -traditional 4096 | ${pkgs.coreutils}/bin/base64 -w0)" + export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:" + ${lib.getExe cfg.package} --mode check-config -f $configFile + cat <$configFile >$out + ''; + + atticadmShim = pkgs.writeShellScript "atticadm" '' + if [ -n "$ATTICADM_PWD" ]; then + cd "$ATTICADM_PWD" + if [ "$?" != "0" ]; then + >&2 echo "Warning: Failed to change directory to $ATTICADM_PWD" + fi + fi + + exec ${cfg.package}/bin/atticadm -f ${checkedConfigFile} "$@" + ''; + + atticadmWrapper = pkgs.writeShellScriptBin "atticd-atticadm" '' + exec systemd-run \ + --quiet \ + --pipe \ + --pty \ + --same-dir \ + --wait \ + --collect \ + --service-type=exec \ + --property=EnvironmentFile=${cfg.environmentFile} \ + --property=DynamicUser=yes \ + --property=User=${cfg.user} \ + --property=Environment=ATTICADM_PWD=$(pwd) \ + --working-directory / \ + -- \ + ${atticadmShim} "$@" + ''; + + hasLocalPostgresDB = + let + url = cfg.settings.database.url or ""; + localStrings = [ + "localhost" + "127.0.0.1" + "/run/postgresql" + ]; + hasLocalStrings = lib.any (lib.flip lib.hasInfix url) localStrings; + in + config.services.postgresql.enable && lib.hasPrefix "postgresql://" url && hasLocalStrings; +in +{ + options = { + services.atticd = { + enable = lib.mkEnableOption "the atticd, the Nix Binary Cache server"; + + package = lib.mkPackageOption pkgs "attic-server" { }; + + environmentFile = lib.mkOption { + description = '' + Path to an EnvironmentFile containing required environment + variables: + + - ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64: The base64-encoded RSA PEM PKCS1 of the + RS256 JWT secret. Generate it with `openssl genrsa -traditional 4096 | base64 -w0`. + ''; + type = types.nullOr types.path; + default = null; + }; + + user = lib.mkOption { + description = '' + The group under which attic runs. + ''; + type = types.str; + default = "atticd"; + }; + + group = lib.mkOption { + description = '' + The user under which attic runs. + ''; + type = types.str; + default = "atticd"; + }; + + settings = lib.mkOption { + description = '' + Structured configurations of atticd. + See https://github.com/zhaofengli/attic/blob/main/server/src/config-template.toml + ''; + type = format.type; + default = { }; + }; + + mode = lib.mkOption { + description = '' + Mode in which to run the server. + + 'monolithic' runs all components, and is suitable for single-node deployments. + + 'api-server' runs only the API server, and is suitable for clustering. + + 'garbage-collector' only runs the garbage collector periodically. + + A simple NixOS-based Attic deployment will typically have one 'monolithic' and any number of 'api-server' nodes. + + There are several other supported modes that perform one-off operations, but these are the only ones that make sense to run via the NixOS module. + ''; + type = lib.types.enum [ + "monolithic" + "api-server" + "garbage-collector" + ]; + default = "monolithic"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.environmentFile != null; + message = '' + <option>services.atticd.environmentFile</option> is not set. + + Run `openssl genrsa -traditional 4096 | base64 -w0` and create a file with the following contents: + + ATTIC_SERVER_TOKEN_RS256_SECRET="output from command" + + Then, set `services.atticd.environmentFile` to the quoted absolute path of the file. + ''; + } + ]; + + services.atticd.settings = { + chunking = lib.mkDefault { + nar-size-threshold = 65536; + min-size = 16384; # 16 KiB + avg-size = 65536; # 64 KiB + max-size = 262144; # 256 KiB + }; + + database.url = lib.mkDefault "sqlite:///var/lib/atticd/server.db?mode=rwc"; + + # "storage" is internally tagged + # if the user sets something the whole thing must be replaced + storage = lib.mkDefault { + type = "local"; + path = "/var/lib/atticd/storage"; + }; + }; + + systemd.services.atticd = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ] ++ lib.optionals hasLocalPostgresDB [ "postgresql.service" ]; + requires = lib.optionals hasLocalPostgresDB [ "postgresql.service" ]; + wants = [ "network-online.target" ]; + + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} -f ${checkedConfigFile} --mode ${cfg.mode}"; + EnvironmentFile = cfg.environmentFile; + StateDirectory = "atticd"; # for usage with local storage and sqlite + DynamicUser = true; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + RestartSec = 10; + + CapabilityBoundingSet = [ "" ]; + DeviceAllow = ""; + DevicePolicy = "closed"; + 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"; + ProtectSystem = "strict"; + ReadWritePaths = + let + path = cfg.settings.storage.path; + isDefaultStateDirectory = path == "/var/lib/atticd" || lib.hasPrefix "/var/lib/atticd/" path; + in + lib.optionals (cfg.settings.storage.type or "" == "local" && !isDefaultStateDirectory) [ path ]; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@resources" + "~@privileged" + ]; + UMask = "0077"; + }; + }; + + environment.systemPackages = [ + atticadmWrapper + ]; + }; +} diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix index 72ccb910982c..73fc210728d8 100644 --- a/nixos/modules/services/networking/avahi-daemon.nix +++ b/nixos/modules/services/networking/avahi-daemon.nix @@ -317,6 +317,47 @@ in Type = "dbus"; ExecStart = "${cfg.package}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}"; ConfigurationDirectory = "avahi/services"; + + # Hardening + CapabilityBoundingSet = [ + # https://github.com/avahi/avahi/blob/v0.9-rc1/avahi-daemon/caps.c#L38 + "CAP_SYS_CHROOT" + "CAP_SETUID" + "CAP_SETGID" + ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = false; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "@chown setgroups setresuid" + ]; + UMask = "0077"; }; }; diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix index 225e330ad184..6079062db6c3 100644 --- a/nixos/modules/services/networking/bind.nix +++ b/nixos/modules/services/networking/bind.nix @@ -45,7 +45,7 @@ let default = [ "any" ]; }; extraConfig = lib.mkOption { - type = lib.types.str; + type = lib.types.lines; description = "Extra zone config to be appended at the end of the zone section."; default = ""; }; diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix index a7f39b379181..6aaaf6295068 100644 --- a/nixos/modules/services/networking/cjdns.nix +++ b/nixos/modules/services/networking/cjdns.nix @@ -274,7 +274,7 @@ in CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID"; ProtectSystem = true; # Doesn't work on i686, causing service to fail - MemoryDenyWriteExecute = !pkgs.stdenv.isi686; + MemoryDenyWriteExecute = !pkgs.stdenv.hostPlatform.isi686; ProtectHome = true; PrivateTmp = true; }; diff --git a/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix index 40c157d1006e..ab8806dc03a1 100644 --- a/nixos/modules/services/networking/coturn.nix +++ b/nixos/modules/services/networking/coturn.nix @@ -1,41 +1,41 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, utils, ... }: let cfg = config.services.coturn; pidfile = "/run/turnserver/turnserver.pid"; configFile = pkgs.writeText "turnserver.conf" '' -listening-port=${toString cfg.listening-port} -tls-listening-port=${toString cfg.tls-listening-port} -alt-listening-port=${toString cfg.alt-listening-port} -alt-tls-listening-port=${toString cfg.alt-tls-listening-port} -${lib.concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)} -${lib.concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)} -min-port=${toString cfg.min-port} -max-port=${toString cfg.max-port} -${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"} -${lib.optionalString cfg.no-auth "no-auth"} -${lib.optionalString cfg.use-auth-secret "use-auth-secret"} -${lib.optionalString (cfg.static-auth-secret != null) ("static-auth-secret=${cfg.static-auth-secret}")} -${lib.optionalString (cfg.static-auth-secret-file != null) ("static-auth-secret=#static-auth-secret#")} -realm=${cfg.realm} -${lib.optionalString cfg.no-udp "no-udp"} -${lib.optionalString cfg.no-tcp "no-tcp"} -${lib.optionalString cfg.no-tls "no-tls"} -${lib.optionalString cfg.no-dtls "no-dtls"} -${lib.optionalString cfg.no-udp-relay "no-udp-relay"} -${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"} -${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"} -${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"} -${lib.optionalString (cfg.dh-file != null) ("dh-file=${cfg.dh-file}")} -no-stdout-log -syslog -pidfile=${pidfile} -${lib.optionalString cfg.secure-stun "secure-stun"} -${lib.optionalString cfg.no-cli "no-cli"} -cli-ip=${cfg.cli-ip} -cli-port=${toString cfg.cli-port} -${lib.optionalString (cfg.cli-password != null) ("cli-password=${cfg.cli-password}")} -${cfg.extraConfig} -''; + listening-port=${toString cfg.listening-port} + tls-listening-port=${toString cfg.tls-listening-port} + alt-listening-port=${toString cfg.alt-listening-port} + alt-tls-listening-port=${toString cfg.alt-tls-listening-port} + ${lib.concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)} + ${lib.concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)} + min-port=${toString cfg.min-port} + max-port=${toString cfg.max-port} + ${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"} + ${lib.optionalString cfg.no-auth "no-auth"} + ${lib.optionalString cfg.use-auth-secret "use-auth-secret"} + ${lib.optionalString (cfg.static-auth-secret != null) "static-auth-secret=${cfg.static-auth-secret}"} + ${lib.optionalString (cfg.static-auth-secret-file != null) "static-auth-secret=#static-auth-secret#"} + realm=${cfg.realm} + ${lib.optionalString cfg.no-udp "no-udp"} + ${lib.optionalString cfg.no-tcp "no-tcp"} + ${lib.optionalString cfg.no-tls "no-tls"} + ${lib.optionalString cfg.no-dtls "no-dtls"} + ${lib.optionalString cfg.no-udp-relay "no-udp-relay"} + ${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"} + ${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"} + ${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"} + ${lib.optionalString (cfg.dh-file != null) "dh-file=${cfg.dh-file}"} + no-stdout-log + syslog + pidfile=${pidfile} + ${lib.optionalString cfg.secure-stun "secure-stun"} + ${lib.optionalString cfg.no-cli "no-cli"} + cli-ip=${cfg.cli-ip} + cli-port=${toString cfg.cli-port} + ${lib.optionalString (cfg.cli-password != null) "cli-password=${cfg.cli-password}"} + ${cfg.extraConfig} + ''; in { options = { services.coturn = { @@ -301,7 +301,7 @@ in { }; }; - config = lib.mkIf cfg.enable (lib.mkMerge ([ + config = lib.mkIf cfg.enable (lib.mkMerge [ { assertions = [ { assertion = cfg.static-auth-secret != null -> cfg.static-auth-secret-file == null ; message = "static-auth-secret and static-auth-secret-file cannot be set at the same time"; @@ -341,25 +341,66 @@ in { '' } chmod 640 ${runConfig} ''; - serviceConfig = { + serviceConfig = rec { Type = "simple"; - ExecStart = "${pkgs.coturn}/bin/turnserver -c ${runConfig}"; - RuntimeDirectory = "turnserver"; + ExecStart = utils.escapeSystemdExecArgs [ + (lib.getExe' pkgs.coturn "turnserver") + "-c" + runConfig + ]; User = "turnserver"; Group = "turnserver"; - AmbientCapabilities = - lib.mkIf ( - cfg.listening-port < 1024 || - cfg.alt-listening-port < 1024 || - cfg.tls-listening-port < 1024 || - cfg.alt-tls-listening-port < 1024 || - cfg.min-port < 1024 - ) "cap_net_bind_service"; + RuntimeDirectory = [ + "coturn" + "turnserver" + ]; + RuntimeDirectoryMode = "0700"; Restart = "on-abort"; + + # Hardening + AmbientCapabilities = if + cfg.listening-port < 1024 || + cfg.alt-listening-port < 1024 || + cfg.tls-listening-port < 1024 || + cfg.alt-tls-listening-port < 1024 || + cfg.min-port < 1024 + then [ "CAP_NET_BIND_SERVICE" ] else [ "" ]; + CapabilityBoundingSet = AmbientCapabilities; + DevicePolicy = "closed"; + 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"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ] ++ lib.optionals (cfg.listening-ips == [ ]) [ + # only used for interface discovery when no listening ips are configured + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged @resources" + ]; + UMask = "0077"; }; }; - systemd.tmpfiles.rules = [ - "d /run/coturn 0700 turnserver turnserver - -" - ]; - }])); + }]); } diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix index da76ff5153d6..90a3993df686 100644 --- a/nixos/modules/services/networking/ddclient.nix +++ b/nixos/modules/services/networking/ddclient.nix @@ -222,7 +222,7 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; restartTriggers = lib.optional (cfg.configFile != null) cfg.configFile; - path = lib.optional (lib.hasPrefix "if," cfg.use) pkgs.iproute2; + path = lib.optional (lib.hasPrefix "if," cfg.use || lib.hasPrefix "if," cfg.usev4 || lib.hasPrefix "if," cfg.usev6) pkgs.iproute2; serviceConfig = { DynamicUser = true; diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 9b3269e965f5..059dc59e8a0a 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -10,7 +10,7 @@ let enableDHCP = config.networking.dhcpcd.enable && (config.networking.useDHCP || lib.any (i: i.useDHCP == true) interfaces); - enableNTPService = (config.services.ntp.enable || config.services.ntpd-rs.enable || config.services.openntpd.enable || config.services.chrony.enable); + useResolvConf = config.networking.resolvconf.enable; # Don't start dhcpcd on explicitly configured interfaces or on # interfaces that are part of a bridge, bond or sit device. @@ -88,23 +88,6 @@ let ${cfg.extraConfig} ''; - exitHook = pkgs.writeText "dhcpcd.exit-hook" '' - ${lib.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. - ${lib.optionalString config.services.ntp.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service || true"} - ${lib.optionalString config.services.ntpd-rs.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd-rs.service || true"} - ${lib.optionalString config.services.openntpd.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart openntpd.service || true"} - ${lib.optionalString config.services.chrony.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart chronyd.service || true"} - fi - ''} - - ${cfg.runHook} - ''; - in { @@ -181,6 +164,19 @@ in description = '' Shell code that will be run after all other hooks. See `man dhcpcd-run-hooks` for details on what is possible. + + ::: {.note} + To use sudo or similar tools in your script you may have to set: + + systemd.services.dhcpcd.serviceConfig.NoNewPrivileges = false; + + In addition, as most of the filesystem is inaccessible to dhcpcd + by default, you may want to define some exceptions, e.g. + + systemd.services.dhcpcd.serviceConfig.ReadOnlyPaths = [ + "/run/user/1000/bus" # to send desktop notifications + ]; + ::: ''; }; @@ -206,22 +202,6 @@ in config = lib.mkIf enableDHCP { - assertions = [ { - # dhcpcd doesn't start properly with malloc ∉ [ libc scudo ] - # see https://github.com/NixOS/nixpkgs/issues/151696 - assertion = - dhcpcd.enablePrivSep - -> lib.elem config.environment.memoryAllocator.provider [ "libc" "scudo" ]; - message = '' - dhcpcd with privilege separation is incompatible with chosen system malloc. - Currently only the `libc` and `scudo` allocators are known to work. - To disable dhcpcd's privilege separation, overlay Nixpkgs and override dhcpcd - to set `enablePrivSep = false`. - ''; - } ]; - - environment.etc."dhcpcd.conf".source = dhcpcdConf; - systemd.services.dhcpcd = let cfgN = config.networking; hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "") @@ -230,10 +210,11 @@ in { description = "DHCP Client"; wantedBy = [ "multi-user.target" ] ++ lib.optional (!hasDefaultGatewaySet) "network-online.target"; - wants = [ "network.target" ]; + wants = [ "network.target" "resolvconf.service" ]; + after = [ "resolvconf.service" ]; before = [ "network-online.target" ]; - restartTriggers = lib.optional (enableNTPService || cfg.runHook != "") [ exitHook ]; + restartTriggers = [ cfg.runHook ]; # Stopping dhcpcd during a reconfiguration is undesirable # because it brings down the network interfaces configured by @@ -247,13 +228,64 @@ in serviceConfig = { Type = "forking"; PIDFile = "/run/dhcpcd/pid"; + SupplementaryGroups = lib.optional useResolvConf "resolvconf"; + User = "dhcpcd"; + Group = "dhcpcd"; + StateDirectory = "dhcpcd"; RuntimeDirectory = "dhcpcd"; + + ExecStartPre = "+${pkgs.writeShellScript "migrate-dhcpcd" '' + # migrate from old database directory + if test -f /var/db/dhcpcd/duid; then + echo 'migrating DHCP leases from /var/db/dhcpcd to /var/lib/dhcpcd ...' + mv /var/db/dhcpcd/* -t /var/lib/dhcpcd + chown dhcpcd:dhcpcd /var/lib/dhcpcd/* + rmdir /var/db/dhcpcd || true + echo done + fi + ''}"; + ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${lib.optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}"; ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind"; Restart = "always"; + AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_RAW" "CAP_NET_BIND_SERVICE" ]; + ReadWritePaths = [ "/proc/sys/net/ipv4" "/proc/sys/net/ipv6" ] + ++ lib.optionals useResolvConf ([ "/run/resolvconf" ] ++ config.networking.resolvconf.subscriberFiles); + DeviceAllow = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = lib.mkDefault true; # may be disabled for sudo in runHook + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = false; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = "tmpfs"; # allow exceptions to be added to ReadOnlyPaths, etc. + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" "AF_PACKET" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallFilter = [ + "@system-service" + "~@aio" "~@chown" "~@keyring" "~@memlock" + ]; + SystemCallArchitectures = "native"; + UMask = "0027"; }; }; + # Note: the service could run with `DynamicUser`, however that makes + # impossible (for no good reason, see systemd issue #20495) to disable + # `NoNewPrivileges` or `ProtectHome`, which users may want to in order + # to run certain scripts in `networking.dhcpcd.runHook`. users.users.dhcpcd = { isSystemUser = true; group = "dhcpcd"; @@ -262,9 +294,7 @@ in environment.systemPackages = [ dhcpcd ]; - environment.etc."dhcpcd.exit-hook" = lib.mkIf (enableNTPService || cfg.runHook != "") { - source = exitHook; - }; + environment.etc."dhcpcd.exit-hook".text = cfg.runHook; powerManagement.resumeCommands = lib.mkIf config.systemd.services.dhcpcd.enable '' diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix index 633e37ad25e9..dfa6b8035168 100644 --- a/nixos/modules/services/networking/dnsmasq.nix +++ b/nixos/modules/services/networking/dnsmasq.nix @@ -133,6 +133,11 @@ in dnsmasq_conf=/etc/dnsmasq-conf.conf dnsmasq_resolv=/etc/dnsmasq-resolv.conf ''; + + subscriberFiles = [ + "/etc/dnsmasq-conf.conf" + "/etc/dnsmasq-resolv.conf" + ]; }; systemd.services.dnsmasq = { diff --git a/nixos/modules/services/networking/fastnetmon-advanced.nix b/nixos/modules/services/networking/fastnetmon-advanced.nix index 26e8ad8b76d9..d534c7fbc058 100644 --- a/nixos/modules/services/networking/fastnetmon-advanced.nix +++ b/nixos/modules/services/networking/fastnetmon-advanced.nix @@ -160,6 +160,8 @@ in { } }); ''; + # dbus/polkit with DynamicUser is broken with the default implementation + services.dbus.implementation = "broker"; # We don't use the existing gobgp NixOS module and package, because the gobgp # version might not be compatible with fastnetmon. Also, the service name diff --git a/nixos/modules/services/networking/fedimintd.nix b/nixos/modules/services/networking/fedimintd.nix new file mode 100644 index 000000000000..c7d93854e21a --- /dev/null +++ b/nixos/modules/services/networking/fedimintd.nix @@ -0,0 +1,309 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) + concatLists + filterAttrs + mapAttrs' + mapAttrsToList + mkEnableOption + mkIf + mkOption + mkOverride + mkPackageOption + nameValuePair + recursiveUpdate + types + ; + + fedimintdOpts = + { + config, + lib, + name, + ... + }: + { + options = { + enable = mkEnableOption "fedimintd"; + + package = mkPackageOption pkgs "fedimint" { }; + + environment = mkOption { + type = types.attrsOf types.str; + description = "Extra Environment variables to pass to the fedimintd."; + default = { + RUST_BACKTRACE = "1"; + }; + example = { + RUST_LOG = "info,fm=debug"; + RUST_BACKTRACE = "1"; + }; + }; + + p2p = { + openFirewall = mkOption { + type = types.bool; + default = true; + description = "Opens port in firewall for fedimintd's p2p port"; + }; + port = mkOption { + type = types.port; + default = 8173; + description = "Port to bind on for p2p connections from peers"; + }; + bind = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "Address to bind on for p2p connections from peers"; + }; + url = mkOption { + type = types.str; + example = "fedimint://p2p.myfedimint.com:8173"; + description = '' + Public address for p2p connections from peers + ''; + }; + }; + api = { + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Opens port in firewall for fedimintd's api port"; + }; + port = mkOption { + type = types.port; + default = 8174; + description = "Port to bind on for API connections relied by the reverse proxy/tls terminator."; + }; + bind = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Address to bind on for API connections relied by the reverse proxy/tls terminator."; + }; + url = mkOption { + type = types.str; + description = '' + Public URL of the API address of the reverse proxy/tls terminator. Usually starting with `wss://`. + ''; + }; + }; + bitcoin = { + network = mkOption { + type = types.str; + default = "signet"; + example = "bitcoin"; + description = "Bitcoin network to participate in."; + }; + rpc = { + url = mkOption { + type = types.str; + default = "http://127.0.0.1:38332"; + example = "signet"; + description = "Bitcoin node (bitcoind/electrum/esplora) address to connect to"; + }; + + kind = mkOption { + type = types.str; + default = "bitcoind"; + example = "electrum"; + description = "Kind of a bitcoin node."; + }; + + secretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + If set the URL specified in `bitcoin.rpc.url` will get the content of this file added + as an URL password, so `http://user@example.com` will turn into `http://user:SOMESECRET@example.com`. + + Example: + + `/etc/nix-bitcoin-secrets/bitcoin-rpcpassword-public` (for nix-bitcoin default) + ''; + }; + }; + }; + + consensus.finalityDelay = mkOption { + type = types.ints.unsigned; + default = 10; + description = "Consensus peg-in finality delay."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/fedimintd-${name}/"; + readOnly = true; + description = '' + Path to the data dir fedimintd will use to store its data. + Note that due to using the DynamicUser feature of systemd, this value should not be changed + and is set to be read only. + ''; + }; + + nginx = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to configure nginx for fedimintd + ''; + }; + fqdn = mkOption { + type = types.str; + example = "api.myfedimint.com"; + description = "Public domain of the API address of the reverse proxy/tls terminator."; + }; + path = mkOption { + type = types.str; + example = "/"; + default = "/ws/"; + description = "Path to host the API on and forward to the daemon's api port"; + }; + config = mkOption { + type = types.submodule ( + recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { + inherit config lib; + }) { } + ); + default = { }; + description = "Overrides to the nginx vhost section for api"; + }; + }; + }; + }; +in +{ + options = { + services.fedimintd = mkOption { + type = types.attrsOf (types.submodule fedimintdOpts); + default = { }; + description = "Specification of one or more fedimintd instances."; + }; + }; + + config = + let + eachFedimintd = filterAttrs (fedimintdName: cfg: cfg.enable) config.services.fedimintd; + eachFedimintdNginx = filterAttrs (fedimintdName: cfg: cfg.nginx.enable) eachFedimintd; + in + mkIf (eachFedimintd != { }) { + + networking.firewall.allowedTCPPorts = concatLists ( + mapAttrsToList ( + fedimintdName: cfg: + (lib.optional cfg.api.openFirewall cfg.api.port ++ lib.optional cfg.p2p.openFirewall cfg.p2p.port) + ) eachFedimintd + ); + + systemd.services = mapAttrs' ( + fedimintdName: cfg: + (nameValuePair "fedimintd-${fedimintdName}" ( + let + startScript = pkgs.writeShellScript "fedimintd-start" ( + ( + if cfg.bitcoin.rpc.secretFile != null then + '' + secret=$(${pkgs.coreutils}/bin/head -n 1 "${cfg.bitcoin.rpc.secretFile}") + prefix="''${FM_BITCOIN_RPC_URL%*@*}" # Everything before the last '@' + suffix="''${FM_BITCOIN_RPC_URL##*@}" # Everything after the last '@' + FM_BITCOIN_RPC_URL="''${prefix}:''${secret}@''${suffix}" + '' + else + "" + ) + + '' + exec ${cfg.package}/bin/fedimintd + '' + ); + in + { + description = "Fedimint Server"; + documentation = [ "https://github.com/fedimint/fedimint/" ]; + wantedBy = [ "multi-user.target" ]; + environment = lib.mkMerge [ + { + FM_BIND_P2P = "${cfg.p2p.bind}:${toString cfg.p2p.port}"; + FM_BIND_API = "${cfg.api.bind}:${toString cfg.api.port}"; + FM_P2P_URL = cfg.p2p.url; + FM_API_URL = cfg.api.url; + FM_DATA_DIR = cfg.dataDir; + FM_BITCOIN_NETWORK = cfg.bitcoin.network; + FM_BITCOIN_RPC_URL = cfg.bitcoin.rpc.url; + FM_BITCOIN_RPC_KIND = cfg.bitcoin.rpc.kind; + } + cfg.environment + ]; + serviceConfig = { + DynamicUser = true; + + StateDirectory = "fedimintd-${fedimintdName}"; + StateDirectoryMode = "0700"; + ExecStart = startScript; + + Restart = "always"; + RestartSec = 10; + StartLimitBurst = 5; + UMask = "007"; + LimitNOFILE = "100000"; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "full"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + }; + } + )) + ) eachFedimintd; + + services.nginx.virtualHosts = mapAttrs' ( + fedimintdName: cfg: + (nameValuePair cfg.nginx.fqdn ( + lib.mkMerge [ + cfg.nginx.config + + { + # Note: we want by default to enable OpenSSL, but it seems anything 100 and above is + # overriden by default value from vhost-options.nix + enableACME = mkOverride 99 true; + forceSSL = mkOverride 99 true; + locations.${cfg.nginx.path} = { + proxyPass = "http://127.0.0.1:${toString cfg.api.port}/"; + proxyWebsockets = true; + extraConfig = '' + proxy_pass_header Authorization; + ''; + }; + } + ] + )) + ) eachFedimintdNginx; + }; + + meta.maintainers = with lib.maintainers; [ dpc ]; +} diff --git a/nixos/modules/services/networking/firewall-iptables.nix b/nixos/modules/services/networking/firewall-iptables.nix index e4fa7676fd07..086106f0d8d2 100644 --- a/nixos/modules/services/networking/firewall-iptables.nix +++ b/nixos/modules/services/networking/firewall-iptables.nix @@ -297,7 +297,6 @@ in } ]; - environment.systemPackages = [ pkgs.nixos-firewall-tool ]; networking.firewall.checkReversePath = lib.mkIf (!kernelHasRPFilter) (lib.mkDefault false); systemd.services.firewall = { diff --git a/nixos/modules/services/networking/firewall-nftables.nix b/nixos/modules/services/networking/firewall-nftables.nix index f954a5284103..06f070caf25a 100644 --- a/nixos/modules/services/networking/firewall-nftables.nix +++ b/nixos/modules/services/networking/firewall-nftables.nix @@ -81,6 +81,13 @@ in networking.nftables.tables."nixos-fw".family = "inet"; networking.nftables.tables."nixos-fw".content = '' + set temp-ports { + comment "Temporarily opened ports" + type inet_proto . inet_service + flags interval + auto-merge + } + ${lib.optionalString (cfg.checkReversePath != false) '' chain rpfilter { type filter hook prerouting priority mangle + 10; policy drop; @@ -147,6 +154,8 @@ in '' ) cfg.allInterfaces)} + meta l4proto . th dport @temp-ports accept + ${lib.optionalString cfg.allowPing '' icmp type echo-request ${lib.optionalString (cfg.pingLimit != null) "limit rate ${cfg.pingLimit}"} accept comment "allow ping" ''} diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix index 27e17c464ba9..ab575b884a71 100644 --- a/nixos/modules/services/networking/firewall.nix +++ b/nixos/modules/services/networking/firewall.nix @@ -274,7 +274,10 @@ in networking.firewall.trustedInterfaces = [ "lo" ]; - environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages; + environment.systemPackages = [ + cfg.package + pkgs.nixos-firewall-tool + ] ++ cfg.extraPackages; boot.kernelModules = (lib.optional cfg.autoLoadConntrackHelpers "nf_conntrack") ++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules; diff --git a/nixos/modules/services/networking/freeradius.nix b/nixos/modules/services/networking/freeradius.nix index 39a137aa541c..d747219b29cb 100644 --- a/nixos/modules/services/networking/freeradius.nix +++ b/nixos/modules/services/networking/freeradius.nix @@ -10,14 +10,14 @@ let after = ["network.target"]; wants = ["network.target"]; preStart = '' - ${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout + ${cfg.package}/bin/radiusd -C -d ${cfg.configDir} -l stdout ''; serviceConfig = { - ExecStart = "${pkgs.freeradius}/bin/radiusd -f -d ${cfg.configDir} -l stdout" + + ExecStart = "${cfg.package}/bin/radiusd -f -d ${cfg.configDir} -l stdout" + lib.optionalString cfg.debug " -xx"; ExecReload = [ - "${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout" + "${cfg.package}/bin/radiusd -C -d ${cfg.configDir} -l stdout" "${pkgs.coreutils}/bin/kill -HUP $MAINPID" ]; User = "radius"; @@ -32,6 +32,8 @@ let freeradiusConfig = { enable = lib.mkEnableOption "the freeradius server"; + package = lib.mkPackageOption pkgs "freeradius" { }; + configDir = lib.mkOption { type = lib.types.path; default = "/etc/raddb"; @@ -72,7 +74,9 @@ in /*uid = config.ids.uids.radius;*/ description = "Radius daemon user"; isSystemUser = true; + group = "radius"; }; + groups.radius = {}; }; systemd.services.freeradius = freeradiusService cfg; diff --git a/nixos/modules/services/networking/frr.nix b/nixos/modules/services/networking/frr.nix index fd5673651f36..a70d1dd6554f 100644 --- a/nixos/modules/services/networking/frr.nix +++ b/nixos/modules/services/networking/frr.nix @@ -1,10 +1,55 @@ { config, lib, pkgs, ... }: + let cfg = config.services.frr; - services = [ - "static" + daemons = [ + "bgpd" + "ospfd" + "ospf6d" + "ripd" + "ripngd" + "isisd" + "pimd" + "pim6d" + "ldpd" + "nhrpd" + "eigrpd" + "babeld" + "sharpd" + "pbrd" + "bfdd" + "fabricd" + "vrrpd" + "pathd" + ]; + + daemonDefaultOptions = { + zebra = "-A 127.0.0.1 -s 90000000"; + mgmtd = "-A 127.0.0.1"; + bgpd = "-A 127.0.0.1"; + ospfd = "-A 127.0.0.1"; + ospf6d = "-A ::1"; + ripd = "-A 127.0.0.1"; + ripngd = "-A ::1"; + isisd = "-A 127.0.0.1"; + pimd = "-A 127.0.0.1"; + pim6d = "-A ::1"; + ldpd = "-A 127.0.0.1"; + nhrpd = "-A 127.0.0.1"; + eigrpd = "-A 127.0.0.1"; + babeld = "-A 127.0.0.1"; + sharpd = "-A 127.0.0.1"; + pbrd = "-A 127.0.0.1"; + staticd = "-A 127.0.0.1"; + bfdd = "-A 127.0.0.1"; + fabricd = "-A 127.0.0.1"; + vrrpd = "-A 127.0.0.1"; + pathd = "-A 127.0.0.1"; + }; + + renamedServices = [ "bgp" "ospf" "ospf6" @@ -22,210 +67,194 @@ let "fabric" ]; - allServices = services ++ [ "zebra" "mgmt" ]; - - isEnabled = service: cfg.${service}.enable; + obsoleteServices = renamedServices ++ [ "static" "mgmt" "zebra" ]; - daemonName = service: if service == "zebra" then service else "${service}d"; + allDaemons = builtins.attrNames daemonDefaultOptions; - configFile = service: - let - scfg = cfg.${service}; - in - if scfg.configFile != null then scfg.configFile - else pkgs.writeText "${daemonName service}.conf" - '' - ! FRR ${daemonName service} configuration - ! - hostname ${config.networking.hostName} - log syslog - service password-encryption - ! - ${scfg.config} - ! - end - ''; + isEnabled = service: cfg.${service}.enable; - serviceOptions = service: + daemonLine = d: "${d}=${if isEnabled d then "yes" else "no"}"; + + configFile = + if cfg.configFile != null then + cfg.configFile + else + pkgs.writeText "frr.conf" '' + ! FRR configuration + ! + hostname ${config.networking.hostName} + log syslog + service password-encryption + service integrated-vtysh-config + ! + ${cfg.config} + ! + end + ''; + + serviceOptions = + service: { - enable = lib.mkEnableOption "the FRR ${lib.toUpper service} routing protocol"; - - configFile = lib.mkOption { - type = lib.types.nullOr lib.types.path; - default = null; - example = "/etc/frr/${daemonName service}.conf"; - description = '' - Configuration file to use for FRR ${daemonName service}. - By default the NixOS generated files are used. - ''; - }; - - config = lib.mkOption { - type = lib.types.lines; - default = ""; - example = - let - examples = { - rip = '' - router rip - network 10.0.0.0/8 - ''; - - ospf = '' - router ospf - network 10.0.0.0/8 area 0 - ''; - - bgp = '' - router bgp 65001 - neighbor 10.0.0.1 remote-as 65001 - ''; - }; - in - examples.${service} or ""; - description = '' - ${daemonName service} configuration statements. - ''; - }; - - vtyListenAddress = lib.mkOption { - type = lib.types.str; - default = "localhost"; - description = '' - Address to bind to for the VTY interface. - ''; - }; - - vtyListenPort = lib.mkOption { - type = lib.types.nullOr lib.types.int; - default = null; + options = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ daemonDefaultOptions.${service} ]; description = '' - TCP Port to bind to for the VTY interface. + Options for the FRR ${service} daemon. ''; }; - extraOptions = lib.mkOption { type = lib.types.listOf lib.types.str; - default = []; + default = [ ]; description = '' - Extra options for the daemon. + Extra options to be appended to the FRR ${service} daemon options. ''; }; - }; + } + // (if (builtins.elem service daemons) then { enable = lib.mkEnableOption "FRR ${service}"; } else { }); in { ###### interface - imports = [ - { - options.services.frr = { - zebra = (serviceOptions "zebra") // { - enable = lib.mkOption { - type = lib.types.bool; - default = lib.any isEnabled services; + imports = + [ + { + options.services.frr = { + configFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/etc/frr/frr.conf"; description = '' - Whether to enable the Zebra routing manager. - - The Zebra routing manager is automatically enabled - if any routing protocols are configured. + Configuration file to use for FRR. + By default the NixOS generated files are used. ''; }; - }; - mgmt = (serviceOptions "mgmt") // { - enable = lib.mkOption { - type = lib.types.bool; - default = isEnabled "static"; - defaultText = lib.literalExpression "config.services.frr.static.enable"; + config = lib.mkOption { + type = lib.types.lines; + default = ""; + example = '' + router rip + network 10.0.0.0/8 + router ospf + network 10.0.0.0/8 area 0 + router bgp 65001 + neighbor 10.0.0.1 remote-as 65001 + ''; description = '' - Whether to enable the Configuration management daemon. - - The Configuration management daemon is automatically - enabled if needed, at the moment this is when staticd - is enabled. + FRR configuration statements. + ''; + }; + openFilesLimit = lib.mkOption { + type = lib.types.ints.unsigned; + default = 1024; + description = '' + This is the maximum number of FD's that will be available. Use a + reasonable value for your setup if you are expecting a large number + of peers in say BGP. ''; }; }; - }; - } - { options.services.frr = (lib.genAttrs services serviceOptions); } - ]; + } + { options.services.frr = (lib.genAttrs allDaemons serviceOptions); } + (lib.mkRemovedOptionModule [ "services" "frr" "zebra" "enable" ] "FRR zebra is always enabled") + ] + ++ (map (d: lib.mkRenamedOptionModule [ "services" "frr" d "enable" ] [ "services" "frr" "${d}d" "enable" ]) renamedServices) + ++ (map (d: lib.mkRenamedOptionModule [ "services" "frr" d "extraOptions" ] [ "services" "frr" "${d}d" "extraOptions" ]) (renamedServices ++ [ "static" "mgmt" ])) + ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "enable" ] "FRR ${d}d is always enabled") [ "static" "mgmt" ]) + ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "config" ] "FRR switched to integrated-vtysh-config, please use services.frr.config") obsoleteServices) + ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "configFile" ] "FRR switched to integrated-vtysh-config, please use services.frr.config or services.frr.configFile") obsoleteServices) + ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "vtyListenAddress" ] "Please change -A option in services.frr.${d}.options instead") obsoleteServices) + ++ (map (d: lib.mkRemovedOptionModule [ "services" "frr" d "vtyListenPort" ] "Please use `-P «vtyListenPort»` option with services.frr.${d}.extraOptions instead, or change services.frr.${d}.options accordingly") obsoleteServices) + ; ###### implementation - config = lib.mkIf (lib.any isEnabled allServices) { - - environment.systemPackages = [ - pkgs.frr # for the vtysh tool - ]; - - users.users.frr = { - description = "FRR daemon user"; - isSystemUser = true; - group = "frr"; - }; + config = + let + daemonList = lib.concatStringsSep "\n" (map daemonLine daemons); + daemonOptionLine = d: "${d}_options=\"${lib.concatStringsSep " " (cfg.${d}.options ++ cfg.${d}.extraOptions)}\""; + daemonOptions = lib.concatStringsSep "\n" (map daemonOptionLine allDaemons); + in + lib.mkIf (lib.any isEnabled daemons || cfg.configFile != null || cfg.config != "") { - users.groups = { - frr = {}; - # Members of the frrvty group can use vtysh to inspect the FRR daemons - frrvty = { members = [ "frr" ]; }; - }; + environment.systemPackages = [ + pkgs.frr # for the vtysh tool + ]; - environment.etc = let - mkEtcLink = service: { - name = "frr/${daemonName service}.conf"; - value.source = configFile service; + users.users.frr = { + description = "FRR daemon user"; + isSystemUser = true; + group = "frr"; }; - in - (builtins.listToAttrs - (map mkEtcLink (lib.filter isEnabled allServices))) // { - "frr/vtysh.conf".text = ""; + + users.groups = { + frr = { }; + # Members of the frrvty group can use vtysh to inspect the FRR daemons + frrvty = { + members = [ "frr" ]; + }; }; - systemd.tmpfiles.rules = [ - "d /run/frr 0750 frr frr -" - ]; - - systemd.services = - let - frrService = service: - let - scfg = cfg.${service}; - daemon = daemonName service; - in - lib.nameValuePair daemon ({ - wantedBy = [ "multi-user.target" ]; - after = [ "network-pre.target" "systemd-sysctl.service" ] ++ lib.optionals (service != "zebra") [ "zebra.service" ]; - bindsTo = lib.optionals (service != "zebra") [ "zebra.service" ]; - wants = [ "network.target" ]; - - description = if service == "zebra" then "FRR Zebra routing manager" - else "FRR ${lib.toUpper service} routing daemon"; - - unitConfig.Documentation = if service == "zebra" then "man:zebra(8)" - else "man:${daemon}(8) man:zebra(8)"; - - restartTriggers = lib.mkIf (service != "mgmt") [ - (configFile service) - ]; - reloadIfChanged = (service != "mgmt"); - - serviceConfig = { - PIDFile = "frr/${daemon}.pid"; - ExecStart = "${pkgs.frr}/libexec/frr/${daemon}" - + lib.optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}" - + lib.optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}" - + " " + (lib.concatStringsSep " " scfg.extraOptions); - ExecReload = lib.mkIf (service != "mgmt") "${pkgs.python3.interpreter} ${pkgs.frr}/libexec/frr/frr-reload.py --reload --daemon ${daemon} --bindir ${pkgs.frr}/bin --rundir /run/frr /etc/frr/${daemon}.conf"; - Restart = "on-abnormal"; - }; - }); - in - lib.listToAttrs (map frrService (lib.filter isEnabled allServices)); + environment.etc = { + "frr/frr.conf".source = configFile; + "frr/vtysh.conf".text = '' + service integrated-vtysh-config + ''; + "frr/daemons".text = '' + # This file tells the frr package which daemons to start. + # + # The watchfrr, zebra and staticd daemons are always started. + # + # This part is auto-generated from services.frr.<daemon>.enable config + ${daemonList} + + # If this option is set the /etc/init.d/frr script automatically loads + # the config via "vtysh -b" when the servers are started. + # + vtysh_enable=yes + + # This part is auto-generated from services.frr.<daemon>.options or + # services.frr.<daemon>.extraOptions + ${daemonOptions} + ''; + }; - }; + systemd.tmpfiles.rules = [ "d /run/frr 0750 frr frr -" ]; + + systemd.services.frr = { + description = "FRRouting"; + documentation = [ "https://frrouting.readthedocs.io/en/latest/setup.html" ]; + wants = [ "network.target" ]; + after = [ + "network-pre.target" + "systemd-sysctl.service" + ]; + before = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + startLimitIntervalSec = 180; + reloadIfChanged = true; + restartTriggers = [ + configFile + daemonList + ]; + serviceConfig = { + Nice = -5; + Type = "forking"; + NotifyAccess = "all"; + StartLimitBurst = "3"; + TimeoutSec = 120; + WatchdogSec = 60; + RestartSec = 5; + Restart = "always"; + LimitNOFILE = cfg.openFilesLimit; + PIDFile = "/run/frr/watchfrr.pid"; + ExecStart = "${pkgs.frr}/libexec/frr/frrinit.sh start"; + ExecStop = "${pkgs.frr}/libexec/frr/frrinit.sh stop"; + ExecReload = "${pkgs.frr}/libexec/frr/frrinit.sh reload"; + }; + }; + }; meta.maintainers = with lib.maintainers; [ woffs ]; - } diff --git a/nixos/modules/services/networking/gns3-server.nix b/nixos/modules/services/networking/gns3-server.nix index ec6a53dddc70..71980f52df2d 100644 --- a/nixos/modules/services/networking/gns3-server.nix +++ b/nixos/modules/services/networking/gns3-server.nix @@ -129,8 +129,15 @@ in { } ]; + users.groups.gns3 = { }; + users.groups.ubridge = lib.mkIf cfg.ubridge.enable { }; + users.users.gns3 = { + group = "gns3"; + isSystemUser = true; + }; + security.wrappers.ubridge = lib.mkIf cfg.ubridge.enable { capabilities = "cap_net_raw,cap_net_admin=eip"; group = "ubridge"; @@ -150,7 +157,7 @@ in { }; } (lib.mkIf (cfg.ubridge.enable) { - Server.ubridge_path = lib.mkDefault (lib.getExe cfg.ubridge.package); + Server.ubridge_path = lib.mkDefault "/run/wrappers/bin/ubridge"; }) (lib.mkIf (cfg.auth.enable) { Server = { @@ -206,7 +213,6 @@ in { serviceConfig = { ConfigurationDirectory = "gns3"; ConfigurationDirectoryMode = "0750"; - DynamicUser = true; Environment = "HOME=%S/gns3"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; ExecStart = "${lib.getExe cfg.package} ${commandArgs}"; @@ -227,14 +233,27 @@ in { User = "gns3"; WorkingDirectory = "%S/gns3"; + # Required for ubridge integration to work + # + # GNS3 needs to run SUID binaries (ubridge) + # but NoNewPrivileges breaks execution of SUID binaries + DynamicUser = false; + NoNewPrivileges = false; + RestrictSUIDSGID = false; + PrivateUsers = false; + # Hardening - DeviceAllow = lib.optional flags.enableLibvirtd "/dev/kvm"; + DeviceAllow = [ + # ubridge needs access to tun/tap devices + "/dev/net/tap rw" + "/dev/net/tun rw" + ] ++ lib.optionals flags.enableLibvirtd [ + "/dev/kvm" + ]; DevicePolicy = "closed"; LockPersonality = true; MemoryDenyWriteExecute = true; - NoNewPrivileges = true; PrivateTmp = true; - PrivateUsers = true; # Don't restrict ProcSubset because python3Packages.psutil requires read access to /proc/stat # ProcSubset = "pid"; ProtectClock = true; @@ -255,8 +274,7 @@ in { ]; RestrictNamespaces = true; RestrictRealtime = true; - RestrictSUIDSGID = true; - UMask = "0077"; + UMask = "0022"; }; }; }; diff --git a/nixos/modules/services/networking/go-camo.nix b/nixos/modules/services/networking/go-camo.nix index cb3b6eade464..adf80901cdc9 100644 --- a/nixos/modules/services/networking/go-camo.nix +++ b/nixos/modules/services/networking/go-camo.nix @@ -55,7 +55,8 @@ in GOCAMO_HMAC_FILE = "%d/hmac"; }; script = '' - export GOCAMO_HMAC=$(cat "$GOCAMO_HMAC_FILE") + GOCAMO_HMAC="$(cat "$GOCAMO_HMAC_FILE")" + export GOCAMO_HMAC exec ${lib.escapeShellArgs(lib.lists.remove "" ([ "${pkgs.go-camo}/bin/go-camo" cfg.listen cfg.sslListen cfg.sslKey cfg.sslCert ] ++ cfg.extraOptions))} ''; serviceConfig = { diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix index ea66faeabbf2..9261ec03c532 100644 --- a/nixos/modules/services/networking/headscale.nix +++ b/nixos/modules/services/networking/headscale.nix @@ -3,25 +3,38 @@ lib, pkgs, ... -}: -with lib; let +}: let cfg = config.services.headscale; dataDir = "/var/lib/headscale"; runDir = "/run/headscale"; + cliConfig = { + # Turn off update checks since the origin of our package + # is nixpkgs and not Github. + disable_check_updates = true; + + unix_socket = "${runDir}/headscale.sock"; + }; + settingsFormat = pkgs.formats.yaml {}; configFile = settingsFormat.generate "headscale.yaml" cfg.settings; + cliConfigFile = settingsFormat.generate "headscale.yaml" cliConfig; + + assertRemovedOption = option: message: { + assertion = !lib.hasAttrByPath option cfg; + message = "The option `services.headscale.${lib.options.showOption option}` was removed. " + message; + }; in { options = { services.headscale = { - enable = mkEnableOption "headscale, Open Source coordination server for Tailscale"; + enable = lib.mkEnableOption "headscale, Open Source coordination server for Tailscale"; - package = mkPackageOption pkgs "headscale" { }; + package = lib.mkPackageOption pkgs "headscale" {}; - user = mkOption { + user = lib.mkOption { default = "headscale"; - type = types.str; + type = lib.types.str; description = '' User account under which headscale runs. @@ -33,9 +46,9 @@ in { ''; }; - group = mkOption { + group = lib.mkOption { default = "headscale"; - type = types.str; + type = lib.types.str; description = '' Group under which headscale runs. @@ -47,8 +60,8 @@ in { ''; }; - address = mkOption { - type = types.str; + address = lib.mkOption { + type = lib.types.str; default = "127.0.0.1"; description = '' Listening address of headscale. @@ -56,8 +69,8 @@ in { example = "0.0.0.0"; }; - port = mkOption { - type = types.port; + port = lib.mkOption { + type = lib.types.port; default = 8080; description = '' Listening port of headscale. @@ -65,18 +78,18 @@ in { example = 443; }; - settings = mkOption { + settings = lib.mkOption { description = '' Overrides to {file}`config.yaml` as a Nix attribute set. Check the [example config](https://github.com/juanfont/headscale/blob/main/config-example.yaml) for possible options. ''; - type = types.submodule { + type = lib.types.submodule { freeformType = settingsFormat.type; options = { - server_url = mkOption { - type = types.str; + server_url = lib.mkOption { + type = lib.types.str; default = "http://127.0.0.1:8080"; description = '' The url clients will connect to. @@ -84,25 +97,49 @@ in { example = "https://myheadscale.example.com:443"; }; - private_key_path = mkOption { - type = types.path; - default = "${dataDir}/private.key"; + noise.private_key_path = lib.mkOption { + type = lib.types.path; + default = "${dataDir}/noise_private.key"; description = '' - Path to private key file, generated automatically if it does not exist. + Path to noise private key file, generated automatically if it does not exist. ''; }; - noise.private_key_path = mkOption { - type = types.path; - default = "${dataDir}/noise_private.key"; - description = '' - Path to noise private key file, generated automatically if it does not exist. + prefixes = let + prefDesc = '' + Each prefix consists of either an IPv4 or IPv6 address, + and the associated prefix length, delimited by a slash. + It must be within IP ranges supported by the Tailscale + client - i.e., subnets of 100.64.0.0/10 and fd7a:115c:a1e0::/48. ''; + in { + v4 = lib.mkOption { + type = lib.types.str; + default = "100.64.0.0/10"; + description = prefDesc; + }; + + v6 = lib.mkOption { + type = lib.types.str; + default = "fd7a:115c:a1e0::/48"; + description = prefDesc; + }; + + allocation = lib.mkOption { + type = lib.types.enum ["sequential" "random"]; + example = "random"; + default = "sequential"; + description = '' + Strategy used for allocation of IPs to nodes, available options: + - sequential (default): assigns the next free IP from the previous given IP. + - random: assigns the next free IP from a pseudo-random IP generator (crypto/rand). + ''; + }; }; derp = { - urls = mkOption { - type = types.listOf types.str; + urls = lib.mkOption { + type = lib.types.listOf lib.types.str; default = ["https://controlplane.tailscale.com/derpmap/default"]; description = '' List of urls containing DERP maps. @@ -110,8 +147,8 @@ in { ''; }; - paths = mkOption { - type = types.listOf types.path; + paths = lib.mkOption { + type = lib.types.listOf lib.types.path; default = []; description = '' List of file paths containing DERP maps. @@ -119,8 +156,8 @@ in { ''; }; - auto_update_enable = mkOption { - type = types.bool; + auto_update_enable = lib.mkOption { + type = lib.types.bool; default = true; description = '' Whether to automatically update DERP maps on a set frequency. @@ -128,18 +165,26 @@ in { example = false; }; - update_frequency = mkOption { - type = types.str; + update_frequency = lib.mkOption { + type = lib.types.str; default = "24h"; description = '' Frequency to update DERP maps. ''; example = "5m"; }; + + server.private_key_path = lib.mkOption { + type = lib.types.path; + default = "${dataDir}/derp_server_private.key"; + description = '' + Path to derp private key file, generated automatically if it does not exist. + ''; + }; }; - ephemeral_node_inactivity_timeout = mkOption { - type = types.str; + ephemeral_node_inactivity_timeout = lib.mkOption { + type = lib.types.str; default = "30m"; description = '' Time before an inactive ephemeral node is deleted. @@ -147,128 +192,144 @@ in { example = "5m"; }; - db_type = mkOption { - type = types.enum ["sqlite3" "postgres"]; - example = "postgres"; - default = "sqlite3"; - description = "Database engine to use."; - }; - - db_host = mkOption { - type = types.nullOr types.str; - default = null; - example = "127.0.0.1"; - description = "Database host address."; - }; - - db_port = mkOption { - type = types.nullOr types.port; - default = null; - example = 3306; - description = "Database host port."; - }; + database = { + type = lib.mkOption { + type = lib.types.enum ["sqlite" "sqlite3" "postgres"]; + example = "postgres"; + default = "sqlite"; + description = '' + Database engine to use. + Please note that using Postgres is highly discouraged as it is only supported for legacy reasons. + All new development, testing and optimisations are done with SQLite in mind. + ''; + }; - db_name = mkOption { - type = types.nullOr types.str; - default = null; - example = "headscale"; - description = "Database name."; - }; + sqlite = { + path = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "${dataDir}/db.sqlite"; + description = "Path to the sqlite3 database file."; + }; - db_user = mkOption { - type = types.nullOr types.str; - default = null; - example = "headscale"; - description = "Database user."; - }; + write_ahead_log = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Enable WAL mode for SQLite. This is recommended for production environments. + https://www.sqlite.org/wal.html + ''; + example = true; + }; + }; - db_password_file = mkOption { - type = types.nullOr types.path; - default = null; - example = "/run/keys/headscale-dbpassword"; - description = '' - A file containing the password corresponding to - {option}`database.user`. - ''; - }; + postgres = { + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "127.0.0.1"; + description = "Database host address."; + }; - db_path = mkOption { - type = types.nullOr types.str; - default = "${dataDir}/db.sqlite"; - description = "Path to the sqlite3 database file."; - }; + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = null; + example = 3306; + description = "Database host port."; + }; - log.level = mkOption { - type = types.str; - default = "info"; - description = '' - headscale log level. - ''; - example = "debug"; - }; + name = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "headscale"; + description = "Database name."; + }; - log.format = mkOption { - type = types.str; - default = "text"; - description = '' - headscale log format. - ''; - example = "json"; - }; + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "headscale"; + description = "Database user."; + }; - dns_config = { - nameservers = mkOption { - type = types.listOf types.str; - default = ["1.1.1.1"]; - description = '' - List of nameservers to pass to Tailscale clients. - ''; + password_file = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/keys/headscale-dbpassword"; + description = '' + A file containing the password corresponding to + {option}`database.user`. + ''; + }; }; + }; - override_local_dns = mkOption { - type = types.bool; - default = false; + log = { + level = lib.mkOption { + type = lib.types.str; + default = "info"; description = '' - Whether to use [Override local DNS](https://tailscale.com/kb/1054/dns/). + headscale log level. ''; - example = true; + example = "debug"; }; - domains = mkOption { - type = types.listOf types.str; - default = []; + format = lib.mkOption { + type = lib.types.str; + default = "text"; description = '' - Search domains to inject to Tailscale clients. + headscale log format. ''; - example = ["mydomain.internal"]; + example = "json"; }; + }; - magic_dns = mkOption { - type = types.bool; + dns = { + magic_dns = lib.mkOption { + type = lib.types.bool; default = true; description = '' Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). - Only works if there is at least a nameserver defined. ''; example = false; }; - base_domain = mkOption { - type = types.str; + base_domain = lib.mkOption { + type = lib.types.str; default = ""; description = '' Defines the base domain to create the hostnames for MagicDNS. - {option}`baseDomain` must be a FQDNs, without the trailing dot. - The FQDN of the hosts will be - `hostname.namespace.base_domain` (e.g. - `myhost.mynamespace.example.com`). + This domain must be different from the {option}`server_url` + domain. + {option}`base_domain` must be a FQDN, without the trailing dot. + The FQDN of the hosts will be `hostname.base_domain` (e.g. + `myhost.tailnet.example.com`). + ''; + example = "tailnet.example.com"; + }; + + nameservers = { + global = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = '' + List of nameservers to pass to Tailscale clients. + ''; + }; + }; + + search_domains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = '' + Search domains to inject to Tailscale clients. ''; + example = ["mydomain.internal"]; }; }; oidc = { - issuer = mkOption { - type = types.str; + issuer = lib.mkOption { + type = lib.types.str; default = ""; description = '' URL to OpenID issuer. @@ -276,33 +337,33 @@ in { example = "https://openid.example.com"; }; - client_id = mkOption { - type = types.str; + client_id = lib.mkOption { + type = lib.types.str; default = ""; description = '' OpenID Connect client ID. ''; }; - client_secret_path = mkOption { - type = types.nullOr types.str; + client_secret_path = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = null; description = '' Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}. ''; }; - scope = mkOption { - type = types.listOf types.str; + scope = lib.mkOption { + type = lib.types.listOf lib.types.str; default = ["openid" "profile" "email"]; description = '' Scopes used in the OIDC flow. ''; }; - extra_params = mkOption { - type = types.attrsOf types.str; - default = { }; + extra_params = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; description = '' Custom query parameters to send with the Authorize Endpoint request. ''; @@ -311,27 +372,27 @@ in { }; }; - allowed_domains = mkOption { - type = types.listOf types.str; - default = [ ]; + allowed_domains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; description = '' Allowed principal domains. if an authenticated user's domain is not in this list authentication request will be rejected. ''; - example = [ "example.com" ]; + example = ["example.com"]; }; - allowed_users = mkOption { - type = types.listOf types.str; - default = [ ]; + allowed_users = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; description = '' Users allowed to authenticate even if not in allowedDomains. ''; - example = [ "alice@example.com" ]; + example = ["alice@example.com"]; }; - strip_email_domain = mkOption { - type = types.bool; + strip_email_domain = lib.mkOption { + type = lib.types.bool; default = true; description = '' Whether the domain part of the email address should be removed when generating namespaces. @@ -339,16 +400,16 @@ in { }; }; - tls_letsencrypt_hostname = mkOption { - type = types.nullOr types.str; + tls_letsencrypt_hostname = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = ""; description = '' Domain name to request a TLS certificate for. ''; }; - tls_letsencrypt_challenge_type = mkOption { - type = types.enum ["TLS-ALPN-01" "HTTP-01"]; + tls_letsencrypt_challenge_type = lib.mkOption { + type = lib.types.enum ["TLS-ALPN-01" "HTTP-01"]; default = "HTTP-01"; description = '' Type of ACME challenge to use, currently supported types: @@ -356,8 +417,8 @@ in { ''; }; - tls_letsencrypt_listen = mkOption { - type = types.nullOr types.str; + tls_letsencrypt_listen = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = ":http"; description = '' When HTTP-01 challenge is chosen, letsencrypt must set up a @@ -366,28 +427,40 @@ in { ''; }; - tls_cert_path = mkOption { - type = types.nullOr types.path; + tls_cert_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; default = null; description = '' Path to already created certificate. ''; }; - tls_key_path = mkOption { - type = types.nullOr types.path; + tls_key_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; default = null; description = '' Path to key for already created certificate. ''; }; - acl_policy_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing ACL policies. - ''; + policy = { + mode = lib.mkOption { + type = lib.types.enum ["file" "database"]; + default = "file"; + description = '' + The mode can be "file" or "database" that defines + where the ACL policies are stored and read from. + ''; + }; + + path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + If the mode is set to "file", the path to a + HuJSON file containing ACL policies. + ''; + }; }; }; }; @@ -395,67 +468,73 @@ in { }; }; - imports = [ - # TODO address + port = listen_addr - (mkRenamedOptionModule ["services" "headscale" "serverUrl"] ["services" "headscale" "settings" "server_url"]) - (mkRenamedOptionModule ["services" "headscale" "privateKeyFile"] ["services" "headscale" "settings" "private_key_path"]) - (mkRenamedOptionModule ["services" "headscale" "derp" "urls"] ["services" "headscale" "settings" "derp" "urls"]) - (mkRenamedOptionModule ["services" "headscale" "derp" "paths"] ["services" "headscale" "settings" "derp" "paths"]) + imports = with lib; [ (mkRenamedOptionModule ["services" "headscale" "derp" "autoUpdate"] ["services" "headscale" "settings" "derp" "auto_update_enable"]) + (mkRenamedOptionModule ["services" "headscale" "derp" "paths"] ["services" "headscale" "settings" "derp" "paths"]) (mkRenamedOptionModule ["services" "headscale" "derp" "updateFrequency"] ["services" "headscale" "settings" "derp" "update_frequency"]) + (mkRenamedOptionModule ["services" "headscale" "derp" "urls"] ["services" "headscale" "settings" "derp" "urls"]) (mkRenamedOptionModule ["services" "headscale" "ephemeralNodeInactivityTimeout"] ["services" "headscale" "settings" "ephemeral_node_inactivity_timeout"]) - (mkRenamedOptionModule ["services" "headscale" "database" "type"] ["services" "headscale" "settings" "db_type"]) - (mkRenamedOptionModule ["services" "headscale" "database" "path"] ["services" "headscale" "settings" "db_path"]) - (mkRenamedOptionModule ["services" "headscale" "database" "host"] ["services" "headscale" "settings" "db_host"]) - (mkRenamedOptionModule ["services" "headscale" "database" "port"] ["services" "headscale" "settings" "db_port"]) - (mkRenamedOptionModule ["services" "headscale" "database" "name"] ["services" "headscale" "settings" "db_name"]) - (mkRenamedOptionModule ["services" "headscale" "database" "user"] ["services" "headscale" "settings" "db_user"]) - (mkRenamedOptionModule ["services" "headscale" "database" "passwordFile"] ["services" "headscale" "settings" "db_password_file"]) (mkRenamedOptionModule ["services" "headscale" "logLevel"] ["services" "headscale" "settings" "log" "level"]) - (mkRenamedOptionModule ["services" "headscale" "dns" "nameservers"] ["services" "headscale" "settings" "dns_config" "nameservers"]) - (mkRenamedOptionModule ["services" "headscale" "dns" "domains"] ["services" "headscale" "settings" "dns_config" "domains"]) - (mkRenamedOptionModule ["services" "headscale" "dns" "magicDns"] ["services" "headscale" "settings" "dns_config" "magic_dns"]) - (mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"]) - (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"]) (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"]) (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_path"]) - (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"]) - (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"]) - (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"]) + (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"]) + (mkRenamedOptionModule ["services" "headscale" "serverUrl"] ["services" "headscale" "settings" "server_url"]) (mkRenamedOptionModule ["services" "headscale" "tls" "certFile"] ["services" "headscale" "settings" "tls_cert_path"]) (mkRenamedOptionModule ["services" "headscale" "tls" "keyFile"] ["services" "headscale" "settings" "tls_key_path"]) - (mkRenamedOptionModule ["services" "headscale" "aclPolicyFile"] ["services" "headscale" "settings" "acl_policy_path"]) + (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"]) + (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"]) + (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"]) (mkRemovedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] '' Headscale no longer uses domain_map. If you're using an old version of headscale you can still set this option via services.headscale.settings.oidc.domain_map. '') ]; - config = mkIf cfg.enable { - services.headscale.settings = { - listen_addr = mkDefault "${cfg.address}:${toString cfg.port}"; - - # Turn off update checks since the origin of our package - # is nixpkgs and not Github. - disable_check_updates = true; - - unix_socket = "${runDir}/headscale.sock"; - - tls_letsencrypt_cache_dir = "${dataDir}/.cache"; - }; + config = lib.mkIf cfg.enable { + assertions = [ + { + # This is stricter than it needs to be but is exactly what upstream does: + # https://github.com/kradalby/headscale/blob/adc084f20f843d7963c999764fa83939668d2d2c/hscontrol/types/config.go#L799 + assertion = with cfg.settings; dns.use_username_in_magic_dns or false || dns.base_domain == "" || !lib.hasInfix dns.base_domain server_url; + message = "server_url cannot contain the base_domain, this will cause the headscale server and embedded DERP to become unreachable from the Tailscale node."; + } + { + assertion = with cfg.settings; dns.magic_dns -> dns.base_domain != ""; + message = "dns.base_domain must be set when using MagicDNS"; + } + (assertRemovedOption ["settings" "acl_policy_path"] "Use `policy.path` instead.") + (assertRemovedOption ["settings" "db_host"] "Use `database.postgres.host` instead.") + (assertRemovedOption ["settings" "db_name"] "Use `database.postgres.name` instead.") + (assertRemovedOption ["settings" "db_password_file"] "Use `database.postgres.password_file` instead.") + (assertRemovedOption ["settings" "db_path"] "Use `database.sqlite.path` instead.") + (assertRemovedOption ["settings" "db_port"] "Use `database.postgres.port` instead.") + (assertRemovedOption ["settings" "db_type"] "Use `database.type` instead.") + (assertRemovedOption ["settings" "db_user"] "Use `database.postgres.user` instead.") + (assertRemovedOption ["settings" "dns_config"] "Use `dns` instead.") + (assertRemovedOption ["settings" "dns_config" "domains"] "Use `dns.search_domains` instead.") + (assertRemovedOption ["settings" "dns_config" "nameservers"] "Use `dns.nameservers.global` instead.") + ]; + + services.headscale.settings = lib.mkMerge [ + cliConfig + { + listen_addr = lib.mkDefault "${cfg.address}:${toString cfg.port}"; + + tls_letsencrypt_cache_dir = "${dataDir}/.cache"; + } + ]; environment = { - # Setup the headscale configuration in a known path in /etc to - # allow both the Server and the Client use it to find the socket - # for communication. - etc."headscale/config.yaml".source = configFile; + # Headscale CLI needs a minimal config to be able to locate the unix socket + # to talk to the server instance. + etc."headscale/config.yaml".source = cliConfigFile; - systemPackages = [ cfg.package ]; + systemPackages = [cfg.package]; }; - users.groups.headscale = mkIf (cfg.group == "headscale") {}; + users.groups.headscale = lib.mkIf (cfg.group == "headscale") {}; - users.users.headscale = mkIf (cfg.user == "headscale") { + users.users.headscale = lib.mkIf (cfg.user == "headscale") { description = "headscale user"; home = dataDir; group = cfg.group; @@ -464,23 +543,20 @@ in { systemd.services.headscale = { description = "headscale coordination server for Tailscale"; - wants = [ "network-online.target" ]; + wants = ["network-online.target"]; after = ["network-online.target"]; wantedBy = ["multi-user.target"]; - restartTriggers = [configFile]; - - environment.GIN_MODE = "release"; script = '' - ${optionalString (cfg.settings.db_password_file != null) '' - export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})" + ${lib.optionalString (cfg.settings.database.postgres.password_file != null) '' + export HEADSCALE_DATABASE_POSTGRES_PASS="$(head -n1 ${lib.escapeShellArg cfg.settings.database.postgres.password_file})" ''} - exec ${cfg.package}/bin/headscale serve + exec ${lib.getExe cfg.package} serve --config ${configFile} ''; serviceConfig = let - capabilityBoundingSet = ["CAP_CHOWN"] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE"; + capabilityBoundingSet = ["CAP_CHOWN"] ++ lib.optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE"; in { Restart = "always"; Type = "simple"; @@ -525,5 +601,5 @@ in { }; }; - meta.maintainers = with maintainers; [kradalby misterio77]; + meta.maintainers = with lib.maintainers; [kradalby misterio77]; } diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix index 2bb9df15de2c..72069e609ac2 100644 --- a/nixos/modules/services/networking/i2pd.nix +++ b/nixos/modules/services/networking/i2pd.nix @@ -1,17 +1,17 @@ { config, lib, pkgs, ... }: -with lib; - let + inherit (lib) mkIf mkOption mkDefault mkEnableOption types optional optionals; + inherit (lib.types) nullOr bool listOf str attrsOf submodule; cfg = config.services.i2pd; homeDir = "/var/lib/i2pd"; strOpt = k: v: k + " = " + v; - boolOpt = k: v: k + " = " + boolToString v; + boolOpt = k: v: k + " = " + lib.boolToString v; intOpt = k: v: k + " = " + toString v; - lstOpt = k: xs: k + " = " + concatStringsSep "," xs; + lstOpt = k: xs: k + " = " + lib.concatStringsSep "," xs; optionalNullString = o: s: optional (s != null) (strOpt o s); optionalNullBool = o: b: optional (b != null) (boolOpt o b); optionalNullInt = o: i: optional (i != null) (intOpt o i); @@ -54,7 +54,7 @@ let mkKeyedEndpointOpt = name: addr: port: keyloc: (mkEndpointOpt name addr port) // { keys = mkOption { - type = with types; nullOr str; + type = nullOr str; default = keyloc; description = '' File to persist ${lib.toUpper name} keys. @@ -162,8 +162,8 @@ let (sec "meshnets") (boolOpt "yggdrasil" cfg.yggdrasil.enable) ] ++ (optionalNullString "yggaddress" cfg.yggdrasil.address) - ++ (flip map - (collect (proto: proto ? port && proto ? address) cfg.proto) + ++ (lib.flip map + (lib.collect (proto: proto ? port && proto ? address) cfg.proto) (proto: let protoOpts = [ (sec proto.name) (boolOpt "enabled" proto.enable) @@ -178,10 +178,10 @@ let ++ (optionals (proto ? outproxy) (optionalNullString "outproxy" proto.outproxy)) ++ (optionals (proto ? outproxyPort) (optionalNullInt "outproxyport" proto.outproxyPort)) ++ (optionals (proto ? outproxyEnable) (optionalNullBool "outproxy.enabled" proto.outproxyEnable)); - in (concatStringsSep "\n" protoOpts) + in (lib.concatStringsSep "\n" protoOpts) )); in - pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts); + pkgs.writeText "i2pd.conf" (lib.concatStringsSep "\n" opts); tunnelConf = let mkOutTunnel = tun: @@ -200,7 +200,7 @@ let ++ (optionals (tun ? outbound.quantity) (optionalNullInt "outbound.quantity" tun.outbound.quantity)) ++ (optionals (tun ? crypto.tagsToSend) (optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend)); in - concatStringsSep "\n" outTunOpts; + lib.concatStringsSep "\n" outTunOpts; mkInTunnel = tun: let @@ -214,16 +214,16 @@ let ++ (optionals (tun ? inPort) (optionalNullInt "inport" tun.inPort)) ++ (optionals (tun ? accessList) (optionalEmptyList "accesslist" tun.accessList)); in - concatStringsSep "\n" inTunOpts; + lib.concatStringsSep "\n" inTunOpts; - allOutTunnels = collect (tun: tun ? port && tun ? destination) cfg.outTunnels; - allInTunnels = collect (tun: tun ? port && tun ? address) cfg.inTunnels; + allOutTunnels = lib.collect (tun: tun ? port && tun ? destination) cfg.outTunnels; + allInTunnels = lib.collect (tun: tun ? port && tun ? address) cfg.inTunnels; opts = [ notice ] ++ (map mkOutTunnel allOutTunnels) ++ (map mkInTunnel allInTunnels); in - pkgs.writeText "i2pd-tunnels.conf" (concatStringsSep "\n" opts); + pkgs.writeText "i2pd-tunnels.conf" (lib.concatStringsSep "\n" opts); - i2pdFlags = concatStringsSep " " ( + i2pdFlags = lib.concatStringsSep " " ( optional (cfg.address != null) ("--host=" + cfg.address) ++ [ "--service" ("--conf=" + i2pdConf) @@ -235,7 +235,7 @@ in { imports = [ - (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ]) + (lib.mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ]) ]; ###### interface @@ -252,7 +252,7 @@ in ''; }; - package = mkPackageOption pkgs "i2pd" { }; + package = lib.mkPackageOption pkgs "i2pd" { }; logLevel = mkOption { type = types.enum ["debug" "info" "warn" "error"]; @@ -269,7 +269,7 @@ in logCLFTime = mkEnableOption "full CLF-formatted date and time to log"; address = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Your external IP or hostname. @@ -277,7 +277,7 @@ in }; family = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Specify a family the router belongs to. @@ -285,7 +285,7 @@ in }; dataDir = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Alternative path to storage of i2pd data (RI, keys, peer profiles, ...) @@ -301,7 +301,7 @@ in }; ifname = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Network interface to bind to. @@ -309,7 +309,7 @@ in }; ifname4 = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' IPv4 interface to bind to. @@ -317,7 +317,7 @@ in }; ifname6 = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' IPv6 interface to bind to. @@ -325,7 +325,7 @@ in }; ntcpProxy = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Proxy URL for NTCP transport. @@ -399,7 +399,7 @@ in reseed.verify = mkEnableOption "SU3 signature verification"; reseed.file = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Full path to SU3 file to reseed from. @@ -407,7 +407,7 @@ in }; reseed.urls = mkOption { - type = with types; listOf str; + type = listOf str; default = []; description = '' Reseed URLs. @@ -415,7 +415,7 @@ in }; reseed.floodfill = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Path to router info of floodfill to reseed from. @@ -423,7 +423,7 @@ in }; reseed.zipfile = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Path to local .zip file to reseed from. @@ -431,7 +431,7 @@ in }; reseed.proxy = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' URL for reseed proxy, supports http/socks. @@ -446,7 +446,7 @@ in ''; }; addressbook.subscriptions = mkOption { - type = with types; listOf str; + type = listOf str; default = [ "http://inr.i2p/export/alive-hosts.txt" "http://i2p-projekt.i2p/hosts.txt" @@ -460,7 +460,7 @@ in trust.enable = mkEnableOption "explicit trust options"; trust.family = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Router Family to trust for first hops. @@ -468,7 +468,7 @@ in }; trust.routers = mkOption { - type = with types; listOf str; + type = listOf str; default = []; description = '' Only connect to the listed routers. @@ -543,7 +543,7 @@ in yggdrasil.enable = mkEnableOption "Yggdrasil"; yggdrasil.address = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Your local yggdrasil address. Specify it if you want to bind your router to a @@ -572,7 +572,7 @@ in }; strictHeaders = mkOption { - type = with types; nullOr bool; + type = nullOr bool; default = null; description = '' Enable strict host checking on WebUI. @@ -580,7 +580,7 @@ in }; hostname = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = '' Expected hostname for WebUI. @@ -591,7 +591,7 @@ in proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat") // { outproxy = mkOption { - type = with types; nullOr str; + type = nullOr str; default = null; description = "Upstream outproxy bind address."; }; @@ -618,7 +618,7 @@ in outTunnels = mkOption { default = {}; - type = with types; attrsOf (submodule ( + type = attrsOf (submodule ( { name, ... }: { options = { destinationPort = mkOption { @@ -639,7 +639,7 @@ in inTunnels = mkOption { default = {}; - type = with types; attrsOf (submodule ( + type = attrsOf (submodule ( { name, ... }: { options = { inPort = mkOption { @@ -648,7 +648,7 @@ in description = "Service port. Default to the tunnel's listen port."; }; accessList = mkOption { - type = with types; listOf str; + type = listOf str; default = []; description = "I2P nodes that are allowed to connect to this service."; }; diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix index bf1795f87e73..5ee0db32d65d 100644 --- a/nixos/modules/services/networking/iwd.nix +++ b/nixos/modules/services/networking/iwd.nix @@ -36,7 +36,7 @@ in description = '' Options passed to iwd. - See [here](https://iwd.wiki.kernel.org/networkconfigurationsettings) for supported options. + See {manpage}`iwd.config(5)` for supported options. ''; }; }; diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix index 8c468e121299..14eb64eae253 100644 --- a/nixos/modules/services/networking/jitsi-videobridge.nix +++ b/nixos/modules/services/networking/jitsi-videobridge.nix @@ -156,7 +156,7 @@ in default = null; example = "192.168.1.42"; description = '' - Local address when running behind NAT. + Local address to assume when running behind NAT. ''; }; @@ -165,7 +165,25 @@ in default = null; example = "1.2.3.4"; description = '' - Public address when running behind NAT. + Public address to assume when running behind NAT. + ''; + }; + + harvesterAddresses = lib.mkOption { + type = listOf str; + default = [ + "stunserver.stunprotocol.org:3478" + "stun.framasoft.org:3478" + "meet-jit-si-turnrelay.jitsi.net:443" + ]; + example = []; + description = '' + Addresses of public STUN services to use to automatically find + the public and local addresses of this Jitsi-Videobridge instance + without the need for manual configuration. + + This option is ignored if {option}`services.jitsi-videobridge.nat.localAddress` + and {option}`services.jitsi-videobridge.nat.publicAddress` are set. ''; }; }; @@ -199,10 +217,13 @@ in config = lib.mkIf cfg.enable { users.groups.jitsi-meet = {}; - services.jitsi-videobridge.extraProperties = lib.optionalAttrs (cfg.nat.localAddress != null) { - "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress; - "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress; - }; + services.jitsi-videobridge.extraProperties = + if (cfg.nat.localAddress != null) then { + "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" = cfg.nat.localAddress; + "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" = cfg.nat.publicAddress; + } else { + "org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES" = lib.concatStringsSep "," cfg.nat.harvesterAddresses; + }; systemd.services.jitsi-videobridge2 = let jvbProps = { diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix index 145b4ad1dd3f..d174edb0fc93 100644 --- a/nixos/modules/services/networking/knot.nix +++ b/nixos/modules/services/networking/knot.nix @@ -337,6 +337,7 @@ in { SystemCallFilter = [ "@system-service" "~@privileged" + "@chown" ] ++ optionals (cfg.enableXDP) [ "bpf" ]; diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix index 892f863aacfe..4c1d6c50d830 100644 --- a/nixos/modules/services/networking/kresd.nix +++ b/nixos/modules/services/networking/kresd.nix @@ -62,6 +62,7 @@ in { default = ""; description = '' Extra lines to be added verbatim to the generated configuration file. + See upstream documentation <https://www.knot-resolver.cz/documentation/stable/config-overview.html> for more details. ''; }; listenPlain = lib.mkOption { diff --git a/nixos/modules/services/networking/mihomo.nix b/nixos/modules/services/networking/mihomo.nix index a425952b54ce..05d3d304d460 100644 --- a/nixos/modules/services/networking/mihomo.nix +++ b/nixos/modules/services/networking/mihomo.nix @@ -18,7 +18,7 @@ in package = lib.mkPackageOption pkgs "mihomo" { }; configFile = lib.mkOption { - type = lib.types.nullOr lib.types.path; + type = lib.types.path; description = "Configuration file to use."; }; diff --git a/nixos/modules/services/networking/monero.nix b/nixos/modules/services/networking/monero.nix index 37a687f524b9..5cb418a9f1e6 100644 --- a/nixos/modules/services/networking/monero.nix +++ b/nixos/modules/services/networking/monero.nix @@ -1,12 +1,10 @@ { config, lib, pkgs, ... }: -with lib; - let cfg = config.services.monero; listToConf = option: list: - concatMapStrings (value: "${option}=${value}\n") list; + lib.concatMapStrings (value: "${option}=${value}\n") list; login = (cfg.rpc.user != null && cfg.rpc.password != null); @@ -14,17 +12,17 @@ let log-file=/dev/stdout data-dir=${dataDir} - ${optionalString mining.enable '' + ${lib.optionalString mining.enable '' start-mining=${mining.address} mining-threads=${toString mining.threads} ''} rpc-bind-ip=${rpc.address} rpc-bind-port=${toString rpc.port} - ${optionalString login '' + ${lib.optionalString login '' rpc-login=${rpc.user}:${rpc.password} ''} - ${optionalString rpc.restricted '' + ${lib.optionalString rpc.restricted '' restricted-rpc=1 ''} @@ -50,34 +48,34 @@ in services.monero = { - enable = mkEnableOption "Monero node daemon"; + enable = lib.mkEnableOption "Monero node daemon"; - dataDir = mkOption { - type = types.str; + dataDir = lib.mkOption { + type = lib.types.str; default = "/var/lib/monero"; description = '' The directory where Monero stores its data files. ''; }; - mining.enable = mkOption { - type = types.bool; + mining.enable = lib.mkOption { + type = lib.types.bool; default = false; description = '' Whether to mine monero. ''; }; - mining.address = mkOption { - type = types.str; + mining.address = lib.mkOption { + type = lib.types.str; default = ""; description = '' Monero address where to send mining rewards. ''; }; - mining.threads = mkOption { - type = types.addCheck types.int (x: x>=0); + mining.threads = lib.mkOption { + type = lib.types.addCheck lib.types.int (x: x>=0); default = 0; description = '' Number of threads used for mining. @@ -85,48 +83,48 @@ in ''; }; - rpc.user = mkOption { - type = types.nullOr types.str; + rpc.user = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = null; description = '' User name for RPC connections. ''; }; - rpc.password = mkOption { - type = types.nullOr types.str; + rpc.password = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = null; description = '' Password for RPC connections. ''; }; - rpc.address = mkOption { - type = types.str; + rpc.address = lib.mkOption { + type = lib.types.str; default = "127.0.0.1"; description = '' IP address the RPC server will bind to. ''; }; - rpc.port = mkOption { - type = types.port; + rpc.port = lib.mkOption { + type = lib.types.port; default = 18081; description = '' Port the RPC server will bind to. ''; }; - rpc.restricted = mkOption { - type = types.bool; + rpc.restricted = lib.mkOption { + type = lib.types.bool; default = false; description = '' Whether to restrict RPC to view only commands. ''; }; - limits.upload = mkOption { - type = types.addCheck types.int (x: x>=-1); + limits.upload = lib.mkOption { + type = lib.types.addCheck lib.types.int (x: x>=-1); default = -1; description = '' Limit of the upload rate in kB/s. @@ -134,8 +132,8 @@ in ''; }; - limits.download = mkOption { - type = types.addCheck types.int (x: x>=-1); + limits.download = lib.mkOption { + type = lib.types.addCheck lib.types.int (x: x>=-1); default = -1; description = '' Limit of the download rate in kB/s. @@ -143,8 +141,8 @@ in ''; }; - limits.threads = mkOption { - type = types.addCheck types.int (x: x>=0); + limits.threads = lib.mkOption { + type = lib.types.addCheck lib.types.int (x: x>=0); default = 0; description = '' Maximum number of threads used for a parallel job. @@ -152,8 +150,8 @@ in ''; }; - limits.syncSize = mkOption { - type = types.addCheck types.int (x: x>=0); + limits.syncSize = lib.mkOption { + type = lib.types.addCheck lib.types.int (x: x>=0); default = 0; description = '' Maximum number of blocks to sync at once. @@ -161,16 +159,16 @@ in ''; }; - extraNodes = mkOption { - type = types.listOf types.str; + extraNodes = lib.mkOption { + type = lib.types.listOf lib.types.str; default = [ ]; description = '' List of additional peer IP addresses to add to the local list. ''; }; - priorityNodes = mkOption { - type = types.listOf types.str; + priorityNodes = lib.mkOption { + type = lib.types.listOf lib.types.str; default = [ ]; description = '' List of peer IP addresses to connect to and @@ -178,8 +176,8 @@ in ''; }; - exclusiveNodes = mkOption { - type = types.listOf types.str; + exclusiveNodes = lib.mkOption { + type = lib.types.listOf lib.types.str; default = [ ]; description = '' List of peer IP addresses to connect to *only*. @@ -187,8 +185,8 @@ in ''; }; - extraConfig = mkOption { - type = types.lines; + extraConfig = lib.mkOption { + type = lib.types.lines; default = ""; description = '' Extra lines to be added verbatim to monerod configuration. @@ -202,7 +200,7 @@ in ###### implementation - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { users.users.monero = { isSystemUser = true; @@ -228,7 +226,7 @@ in }; }; - assertions = singleton { + assertions = lib.singleton { assertion = cfg.mining.enable -> cfg.mining.address != ""; message = '' You need a Monero address to receive mining rewards: diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix index 85676d29f2b1..9e346d3384fa 100644 --- a/nixos/modules/services/networking/murmur.nix +++ b/nixos/modules/services/networking/murmur.nix @@ -6,7 +6,7 @@ let cfg = config.services.murmur; forking = cfg.logFile != null; configFile = pkgs.writeText "murmurd.ini" '' - database=/var/lib/murmur/murmur.sqlite + database=${cfg.stateDir}/murmur.sqlite dbDriver=QSQLITE autobanAttempts=${toString cfg.autobanAttempts} @@ -69,6 +69,32 @@ in ''; }; + user = mkOption { + type = types.str; + default = "murmur"; + description = '' + The name of an existing user to use to run the service. + If not specified, the default user will be created. + ''; + }; + + group = mkOption { + type = types.str; + default = "murmur"; + description = '' + The name of an existing group to use to run the service. + If not specified, the default group will be created. + ''; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/murmur"; + description = '' + Directory to store data for the server. + ''; + }; + autobanAttempts = mkOption { type = types.int; default = 10; @@ -257,7 +283,7 @@ in environmentFile = mkOption { type = types.nullOr types.path; default = null; - example = "/var/lib/murmur/murmurd.env"; + example = literalExpression ''"''${config.services.murmur.stateDir}/murmurd.env"''; description = '' Environment file as defined in {manpage}`systemd.exec(5)`. @@ -289,14 +315,14 @@ in }; config = mkIf cfg.enable { - users.users.murmur = { + users.users.murmur = mkIf (cfg.user == "murmur") { description = "Murmur Service user"; - home = "/var/lib/murmur"; + home = cfg.stateDir; createHome = true; uid = config.ids.uids.murmur; - group = "murmur"; + group = cfg.group; }; - users.groups.murmur = { + users.groups.murmur = mkIf (cfg.group == "murmur") { gid = config.ids.gids.murmur; }; @@ -324,8 +350,8 @@ in Restart = "always"; RuntimeDirectory = "murmur"; RuntimeDirectoryMode = "0700"; - User = "murmur"; - Group = "murmur"; + User = cfg.user; + Group = cfg.group; # service hardening AmbientCapabilities = "CAP_NET_BIND_SERVICE"; @@ -349,6 +375,7 @@ in RestrictRealtime = true; SystemCallArchitectures = "native"; SystemCallFilter = "@system-service"; + UMask = 027; }; }; @@ -361,7 +388,7 @@ in "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> - <policy user="murmur"> + <policy user="${cfg.user}"> <allow own="net.sourceforge.mumble.murmur"/> </policy> @@ -386,9 +413,9 @@ in r ${config.environment.etc."os-release".source}, r ${config.environment.etc."lsb-release".source}, - owner rwk /var/lib/murmur/murmur.sqlite, - owner rw /var/lib/murmur/murmur.sqlite-journal, - owner r /var/lib/murmur/, + owner rwk ${cfg.stateDir}/murmur.sqlite, + owner rw ${cfg.stateDir}/murmur.sqlite-journal, + owner r ${cfg.stateDir}/, r /run/murmur/murmurd.pid, r /run/murmur/murmurd.ini, r ${configFile}, diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index fda8245ba97d..dedd53e345fe 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -514,6 +514,12 @@ in environment.etc = { "NetworkManager/NetworkManager.conf".source = configFile; + + # The networkmanager-l2tp plugin expects /etc/ipsec.secrets to include /etc/ipsec.d/ipsec.nm-l2tp.secrets; + # see https://github.com/NixOS/nixpkgs/issues/64965 + "ipsec.secrets".text = '' + include ipsec.d/ipsec.nm-l2tp.secrets + ''; } // builtins.listToAttrs (map (pkg: nameValuePair "NetworkManager/${pkg.networkManagerPlugin}" { diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix index b17416c1e3d3..fe878ddbe05f 100644 --- a/nixos/modules/services/networking/nsd.nix +++ b/nixos/modules/services/networking/nsd.nix @@ -603,8 +603,8 @@ in reuseport = mkOption { type = types.bool; - default = pkgs.stdenv.isLinux; - defaultText = literalExpression "pkgs.stdenv.isLinux"; + default = pkgs.stdenv.hostPlatform.isLinux; + defaultText = literalExpression "pkgs.stdenv.hostPlatform.isLinux"; description = '' Whether to enable SO_REUSEPORT on all used sockets. This lets multiple processes bind to the same port. This speeds up operation especially diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix index e7ea8866d79b..bdc5adb394c2 100644 --- a/nixos/modules/services/networking/ntp/ntpd.nix +++ b/nixos/modules/services/networking/ntp/ntpd.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; @@ -8,10 +13,8 @@ let cfg = config.services.ntp; - stateDir = "/var/lib/ntp"; - configFile = pkgs.writeText "ntp.conf" '' - driftfile ${stateDir}/ntp.drift + driftfile /var/lib/ntp/ntp.drift restrict default ${toString cfg.restrictDefault} restrict -6 default ${toString cfg.restrictDefault} @@ -25,7 +28,12 @@ let ${cfg.extraConfig} ''; - ntpFlags = [ "-c" "${configFile}" "-u" "ntp:ntp" ] ++ cfg.extraFlags; + ntpFlags = [ + "-c" + "${configFile}" + "-u" + "ntp:ntp" + ] ++ cfg.extraFlags; in @@ -58,7 +66,14 @@ in recommended in section 6.5.1.1.3, answer "No" of https://support.ntp.org/Support/AccessRestrictions ''; - default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ]; + default = [ + "limited" + "kod" + "nomodify" + "notrap" + "noquery" + "nopeer" + ]; }; restrictSource = mkOption { @@ -69,7 +84,13 @@ in The default flags allow peers to be added by ntpd from configured pool(s), but not by other means. ''; - default = [ "limited" "kod" "nomodify" "notrap" "noquery" ]; + default = [ + "limited" + "kod" + "nomodify" + "notrap" + "noquery" + ]; }; servers = mkOption { @@ -96,14 +117,13 @@ in type = types.listOf types.str; description = "Extra flags passed to the ntpd command."; example = literalExpression ''[ "--interface=eth0" ]''; - default = []; + default = [ ]; }; }; }; - ###### implementation config = mkIf config.services.ntp.enable { @@ -113,34 +133,57 @@ in environment.systemPackages = [ pkgs.ntp ]; services.timesyncd.enable = mkForce false; - systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; }; - - users.users.ntp = - { isSystemUser = true; - group = "ntp"; - description = "NTP daemon user"; - home = stateDir; - }; - users.groups.ntp = {}; - - systemd.services.ntpd = - { description = "NTP Daemon"; - - wantedBy = [ "multi-user.target" ]; - wants = [ "time-sync.target" ]; - before = [ "time-sync.target" ]; - - preStart = - '' - mkdir -m 0755 -p ${stateDir} - chown ntp ${stateDir} - ''; + systemd.services.systemd-timedated.environment = { + SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; + }; - serviceConfig = { - ExecStart = "@${ntp}/bin/ntpd ntpd -g ${builtins.toString ntpFlags}"; - Type = "forking"; - }; + users.users.ntp = { + isSystemUser = true; + group = "ntp"; + description = "NTP daemon user"; + home = "/var/lib/ntp"; + }; + users.groups.ntp = { }; + + systemd.services.ntpd = { + description = "NTP Daemon"; + + wantedBy = [ "multi-user.target" ]; + wants = [ "time-sync.target" ]; + before = [ "time-sync.target" ]; + + serviceConfig = { + ExecStart = "@${ntp}/bin/ntpd ntpd -g ${builtins.toString ntpFlags}"; + Type = "forking"; + StateDirectory = "ntp"; + + # Hardening options + PrivateDevices = true; + PrivateIPC = true; + PrivateTmp = true; + ProtectClock = false; + ProtectHome = true; + + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = true; + + RestrictNamespaces = true; + RestrictRealtime = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + AmbientCapabilities = [ + "CAP_SYS_TIME" + ]; + + ProtectControlGroups = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + RestrictSUIDSGID = true; }; + }; }; diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix index fba6d5e48f92..56b1f6f5ab8f 100644 --- a/nixos/modules/services/networking/openvpn.nix +++ b/nixos/modules/services/networking/openvpn.nix @@ -73,8 +73,9 @@ let openvpn-restart = { wantedBy = [ "sleep.target" ]; path = [ pkgs.procps ]; - script = "pkill --signal SIGHUP --exact openvpn"; - #SIGHUP makes openvpn process to self-exit and then it got restarted by systemd because of Restart=always + script = let + unitNames = map (n: "openvpn-${n}.service") (builtins.attrNames cfg.servers); + in "systemctl try-restart ${lib.escapeShellArgs unitNames}"; description = "Sends a signal to OpenVPN process to trigger a restart after return from sleep"; }; }; diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix index a152b72143da..01baa58879da 100644 --- a/nixos/modules/services/networking/pleroma.nix +++ b/nixos/modules/services/networking/pleroma.nix @@ -1,7 +1,13 @@ -{ config, options, lib, pkgs, stdenv, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.services.pleroma; -in { +in +{ options = { services.pleroma = with lib; { enable = mkEnableOption "pleroma"; @@ -48,7 +54,7 @@ in { Have a look to Pleroma section in the NixOS manual for more information. - ''; + ''; }; secretConfigFile = mkOption { @@ -73,7 +79,7 @@ in { group = cfg.group; isSystemUser = true; }; - groups."${cfg.group}" = {}; + groups."${cfg.group}" = { }; }; environment.systemPackages = [ cfg.package ]; @@ -90,57 +96,79 @@ in { import_config "${cfg.secretConfigFile}" ''; - systemd.services.pleroma = { - description = "Pleroma social network"; - wants = [ "network-online.target" ]; - after = [ "network-online.target" "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ]; - environment.RELEASE_COOKIE = "/var/lib/pleroma/.cookie"; - serviceConfig = { - User = cfg.user; - Group = cfg.group; - Type = "exec"; - WorkingDirectory = "~"; - StateDirectory = "pleroma pleroma/static pleroma/uploads"; - StateDirectoryMode = "700"; - - # Checking the conf file is there then running the database - # migration before each service start, just in case there are - # some pending ones. - # - # It's sub-optimal as we'll always run this, even if pleroma - # has not been updated. But the no-op process is pretty fast. - # Better be safe than sorry migration-wise. - ExecStartPre = - let preScript = pkgs.writers.writeBashBin "pleromaStartPre" '' - if [ ! -f /var/lib/pleroma/.cookie ] - then - echo "Creating cookie file" - dd if=/dev/urandom bs=1 count=16 | hexdump -e '16/1 "%02x"' > /var/lib/pleroma/.cookie - fi - ${cfg.package}/bin/pleroma_ctl migrate - ''; - in "${preScript}/bin/pleromaStartPre"; - - ExecStart = "${cfg.package}/bin/pleroma start"; - ExecStop = "${cfg.package}/bin/pleroma stop"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - - # Systemd sandboxing directives. - # Taken from the upstream contrib systemd service at - # pleroma/installation/pleroma.service - PrivateTmp = true; - ProtectHome = true; - ProtectSystem = "full"; - PrivateDevices = false; - NoNewPrivileges = true; - CapabilityBoundingSet = "~CAP_SYS_ADMIN"; + systemd.services = + let + commonSystemdServiceConfig = { + User = cfg.user; + Group = cfg.group; + WorkingDirectory = "~"; + StateDirectory = "pleroma pleroma/static pleroma/uploads"; + StateDirectoryMode = "700"; + # Systemd sandboxing directives. + # Taken from the upstream contrib systemd service at + # pleroma/installation/pleroma.service + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "full"; + PrivateDevices = false; + NoNewPrivileges = true; + CapabilityBoundingSet = "~CAP_SYS_ADMIN"; + }; + + in + { + pleroma-migrations = { + description = "Pleroma social network migrations"; + wants = [ "network-online.target" ]; + after = [ + "network-online.target" + "postgresql.service" + ]; + wantedBy = [ "pleroma.service" ]; + environment.RELEASE_COOKIE = "/var/lib/pleroma/.cookie"; + serviceConfig = commonSystemdServiceConfig // { + Type = "oneshot"; + # Checking the conf file is there then running the database + # migration before each service start, just in case there are + # some pending ones. + # + # It's sub-optimal as we'll always run this, even if pleroma + # has not been updated. But the no-op process is pretty fast. + # Better be safe than sorry migration-wise. + ExecStart = + let + preScript = pkgs.writers.writeBashBin "pleroma-migrations" '' + if [ ! -f /var/lib/pleroma/.cookie ] + then + echo "Creating cookie file" + dd if=/dev/urandom bs=1 count=16 | hexdump -e '16/1 "%02x"' > /var/lib/pleroma/.cookie + fi + ${cfg.package}/bin/pleroma_ctl migrate + ''; + in + "${preScript}/bin/pleroma-migrations"; + }; + # disksup requires bash + path = [ pkgs.bash ]; + }; + + pleroma = { + description = "Pleroma social network"; + wants = [ "pleroma-migrations.service" ]; + after = [ "pleroma-migrations.service" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ]; + environment.RELEASE_COOKIE = "/var/lib/pleroma/.cookie"; + serviceConfig = commonSystemdServiceConfig // { + Type = "exec"; + ExecStart = "${cfg.package}/bin/pleroma start"; + ExecStop = "${cfg.package}/bin/pleroma stop"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + # disksup requires bash + path = [ pkgs.bash ]; + }; }; - # disksup requires bash - path = [ pkgs.bash ]; - }; - }; meta.maintainers = with lib.maintainers; [ picnoir ]; meta.doc = ./pleroma.md; diff --git a/nixos/modules/services/networking/pptpd.nix b/nixos/modules/services/networking/pptpd.nix index b28015800f3c..441ab52372f9 100644 --- a/nixos/modules/services/networking/pptpd.nix +++ b/nixos/modules/services/networking/pptpd.nix @@ -101,15 +101,12 @@ with lib; secrets="/etc/ppp-pptpd/chap-secrets" - [ -f "$secrets" ] || cat > "$secrets" << EOF + [ -f "$secrets" ] || install -m 600 -o root -g root /dev/stdin "$secrets" << EOF # From: pptpd-1.4.0/samples/chap-secrets # Secrets for authentication using CHAP # client server secret IP addresses #username pptpd password * EOF - - chown root:root "$secrets" - chmod 600 "$secrets" ''; serviceConfig = { diff --git a/nixos/modules/services/networking/quorum.nix b/nixos/modules/services/networking/quorum.nix index bddcd18c7fbe..8f6df0881b23 100644 --- a/nixos/modules/services/networking/quorum.nix +++ b/nixos/modules/services/networking/quorum.nix @@ -201,11 +201,11 @@ in { --syncmode ${cfg.syncmode} \ ${optionalString (cfg.permissioned) "--permissioned"} \ - --mine --minerthreads 1 \ + --mine --miner.threads 1 \ ${optionalString (cfg.rpc.enable) "--rpc --rpcaddr ${cfg.rpc.address} --rpcport ${toString cfg.rpc.port} --rpcapi ${cfg.rpc.api}"} \ ${optionalString (cfg.ws.enable) - "--ws --wsaddr ${cfg.ws.address} --wsport ${toString cfg.ws.port} --wsapi ${cfg.ws.api} --wsorigins ${cfg.ws.origins}"} \ + "--ws --ws.addr ${cfg.ws.address} --ws.port ${toString cfg.ws.port} --ws.api ${cfg.ws.api} --ws.origins ${cfg.ws.origins}"} \ --emitcheckpoints \ --datadir ${dataDir} \ --port ${toString cfg.port}''; diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix index 62a242e88c9b..7037e6817803 100644 --- a/nixos/modules/services/networking/radicale.nix +++ b/nixos/modules/services/networking/radicale.nix @@ -52,7 +52,7 @@ in { default = { }; description = '' Configuration for Radicale. See - <https://radicale.org/3.0.html#documentation/configuration>. + <https://radicale.org/v3.html#configuration>. This option is mutually exclusive with {option}`config`. ''; example = literalExpression '' @@ -74,7 +74,7 @@ in { type = format.type; description = '' Configuration for Radicale's rights file. See - <https://radicale.org/3.0.html#documentation/authentication-and-rights>. + <https://radicale.org/v3.html#authentication-and-rights>. This option only works in conjunction with {option}`settings`. Setting this will also set {option}`settings.rights.type` and {option}`settings.rights.file` to appropriate values. diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix index 02773d78b132..f9c682f168b7 100644 --- a/nixos/modules/services/networking/resilio.nix +++ b/nixos/modules/services/networking/resilio.nix @@ -5,8 +5,6 @@ with lib; let cfg = config.services.resilio; - resilioSync = pkgs.resilio-sync; - sharedFoldersRecord = map (entry: { dir = entry.directory; @@ -83,6 +81,8 @@ in ''; }; + package = mkPackageOption pkgs "resilio-sync" { }; + deviceName = mkOption { type = types.str; example = "Voltron"; @@ -285,7 +285,7 @@ in RuntimeDirectory = "rslsync"; ExecStartPre = "${createConfig}/bin/create-resilio-config"; ExecStart = '' - ${resilioSync}/bin/rslsync --nodaemon --config ${runConfigPath} + ${lib.getExe cfg.package} --nodaemon --config ${runConfigPath} ''; }; }; diff --git a/nixos/modules/services/networking/scion/scion-ip-gateway.nix b/nixos/modules/services/networking/scion/scion-ip-gateway.nix new file mode 100644 index 000000000000..1801828c8ad0 --- /dev/null +++ b/nixos/modules/services/networking/scion/scion-ip-gateway.nix @@ -0,0 +1,92 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + globalCfg = config.services.scion; + cfg = config.services.scion.scion-ip-gateway; + toml = pkgs.formats.toml { }; + json = pkgs.formats.json { }; + connectionDir = if globalCfg.stateless then "/run" else "/var/lib"; + defaultConfig = { + tunnel = { }; + gateway = { + traffic_policy_file = "${trafficConfigFile}"; + }; + }; + defaultTrafficConfig = { + ASes = { }; + ConfigVersion = 9001; + }; + configFile = toml.generate "scion-ip-gateway.toml" (recursiveUpdate defaultConfig cfg.config); + trafficConfigFile = json.generate "scion-ip-gateway-traffic.json" ( + recursiveUpdate defaultTrafficConfig cfg.trafficConfig + ); +in +{ + options.services.scion.scion-ip-gateway = { + enable = mkEnableOption "the scion-ip-gateway service"; + config = mkOption { + default = { }; + type = toml.type; + example = literalExpression '' + { + tunnel = { + src_ipv4 = "172.16.100.1"; + }; + } + ''; + description = '' + scion-ip-gateway daemon configuration + ''; + }; + trafficConfig = mkOption { + default = { }; + type = json.type; + example = literalExpression '' + { + ASes = { + "2-ffaa:0:b" = { + Nets = [ + "172.16.1.0/24" + ]; + }; + }; + ConfigVersion = 9001; + } + ''; + description = '' + scion-ip-gateway traffic configuration + ''; + }; + }; + config = mkIf cfg.enable { + systemd.services.scion-ip-gateway = { + description = "SCION IP Gateway Service"; + after = [ + "network-online.target" + "scion-dispatcher.service" + ]; + wants = [ + "network-online.target" + "scion-dispatcher.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + Group = if (config.services.scion.scion-dispatcher.enable == true) then "scion" else null; + ExecStart = "${globalCfg.package}/bin/scion-ip-gateway --config ${configFile}"; + DynamicUser = true; + AmbientCapabilities = [ "CAP_NET_ADMIN" ]; + Restart = "on-failure"; + KillMode = "control-group"; + RemainAfterExit = false; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/scion/scion.nix b/nixos/modules/services/networking/scion/scion.nix index efab97f7cd8d..0b26f24e7ed0 100644 --- a/nixos/modules/services/networking/scion/scion.nix +++ b/nixos/modules/services/networking/scion/scion.nix @@ -42,6 +42,7 @@ in scion-daemon.enable = true; scion-router.enable = true; scion-control.enable = true; + scion-ip-gateway.enable = true; }; assertions = [ { assertion = cfg.bypassBootstrapWarning == true; diff --git a/nixos/modules/services/networking/seafile.nix b/nixos/modules/services/networking/seafile.nix index 486bc145cd5d..356bcfe04419 100644 --- a/nixos/modules/services/networking/seafile.nix +++ b/nixos/modules/services/networking/seafile.nix @@ -1,19 +1,42 @@ -{ config, lib, pkgs, ... }: -with lib; +{ + config, + lib, + pkgs, + ... +}: let cfg = config.services.seafile; settingsFormat = pkgs.formats.ini { }; - ccnetConf = settingsFormat.generate "ccnet.conf" cfg.ccnetSettings; + ccnetConf = settingsFormat.generate "ccnet.conf" ( + lib.attrsets.recursiveUpdate { + Database = { + ENGINE = "mysql"; + UNIX_SOCKET = "/var/run/mysqld/mysqld.sock"; + DB = "ccnet_db"; + CONNECTION_CHARSET = "utf8"; + }; + } cfg.ccnetSettings + ); - seafileConf = settingsFormat.generate "seafile.conf" cfg.seafileSettings; + seafileConf = settingsFormat.generate "seafile.conf" ( + lib.attrsets.recursiveUpdate { + database = { + type = "mysql"; + unix_socket = "/var/run/mysqld/mysqld.sock"; + db_name = "seafile_db"; + connection_charset = "utf8"; + }; + } cfg.seafileSettings + ); seahubSettings = pkgs.writeText "seahub_settings.py" '' FILE_SERVER_ROOT = '${cfg.ccnetSettings.General.SERVICE_URL}/seafhttp' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': '${seahubDir}/seahub.db', + 'ENGINE': 'django.db.backends.mysql', + 'NAME' : 'seahub_db', + 'HOST' : '/var/run/mysqld/mysqld.sock', } } MEDIA_ROOT = '${seahubDir}/media/' @@ -21,23 +44,25 @@ let SERVICE_URL = '${cfg.ccnetSettings.General.SERVICE_URL}' + CSRF_TRUSTED_ORIGINS = ["${cfg.ccnetSettings.General.SERVICE_URL}"] + with open('${seafRoot}/.seahubSecret') as f: SECRET_KEY = f.readline().rstrip() ${cfg.seahubExtraConf} ''; - seafRoot = "/var/lib/seafile"; # hardcode it due to dynamicuser + seafRoot = "/var/lib/seafile"; ccnetDir = "${seafRoot}/ccnet"; - dataDir = "${seafRoot}/data"; seahubDir = "${seafRoot}/seahub"; + defaultUser = "seafile"; in { ###### Interface - options.services.seafile = { + options.services.seafile = with lib; { enable = mkEnableOption "Seafile server"; ccnetSettings = mkOption { @@ -47,7 +72,7 @@ in options = { General = { SERVICE_URL = mkOption { - type = types.str; + type = types.singleLineStr; example = "https://www.example.com"; description = '' Seahub public URL. @@ -78,11 +103,17 @@ in ''; }; host = mkOption { - type = types.str; - default = "127.0.0.1"; - example = "0.0.0.0"; + type = types.singleLineStr; + default = "ipv4:127.0.0.1"; + example = "unix:/run/seafile/server.sock"; description = '' - The binding address used by seafile fileserver. + The bind address used by seafile fileserver. + + The addr can be defined as one of the following: + - ipv6:<ipv6addr> for binding to an IPv6 address. + - unix:<named pipe> for binding to a unix named socket + - ipv4:<ipv4addr> for binding to an ipv4 address + Otherwise the addr is assumed to be ipv4. ''; }; }; @@ -96,6 +127,19 @@ in ''; }; + seahubAddress = mkOption { + type = types.singleLineStr; + default = "unix:/run/seahub/gunicorn.sock"; + example = "[::1]:8083"; + description = '' + Which address to bind the seahub server to, of the form: + - HOST + - HOST:PORT + - unix:PATH. + IPv6 HOSTs must be wrapped in brackets. + ''; + }; + workers = mkOption { type = types.int; default = 4; @@ -107,7 +151,7 @@ in adminEmail = mkOption { example = "john@example.com"; - type = types.str; + type = types.singleLineStr; description = '' Seafile Seahub Admin Account Email. ''; @@ -115,17 +159,79 @@ in initialAdminPassword = mkOption { example = "someStrongPass"; - type = types.str; + type = types.singleLineStr; description = '' Seafile Seahub Admin Account initial password. - Should be change via Seahub web front-end. + Should be changed via Seahub web front-end. ''; }; - seafilePackage = mkPackageOption pkgs "seafile-server" { }; + seahubPackage = mkPackageOption pkgs "seahub" { }; + + user = mkOption { + type = types.singleLineStr; + default = defaultUser; + description = "User account under which seafile runs."; + }; + + group = mkOption { + type = types.singleLineStr; + default = defaultUser; + description = "Group under which seafile runs."; + }; + + dataDir = mkOption { + type = types.path; + default = "${seafRoot}/data"; + description = "Path in which to store user data"; + }; + + gc = { + enable = mkEnableOption "automatic garbage collection on stored data blocks"; + + dates = mkOption { + type = types.listOf types.singleLineStr; + default = [ "Sun 03:00:00" ]; + description = '' + When to run garbage collection on stored data blocks. + The time format is described in {manpage}`systemd.time(7)`. + ''; + }; + + randomizedDelaySec = mkOption { + default = "0"; + type = types.singleLineStr; + example = "45min"; + description = '' + Add a randomized delay before each garbage collection. + The delay will be chosen between zero and this value. + This value must be a time span in the format specified by + {manpage}`systemd.time(7)` + ''; + }; + + persistent = mkOption { + default = true; + type = types.bool; + example = false; + description = '' + Takes a boolean argument. If true, the time when the service + unit was last triggered is stored on disk. When the timer is + activated, the service unit is triggered immediately if it + would have been triggered at least once during the time when + the timer was inactive. Such triggering is nonetheless + subject to the delay imposed by RandomizedDelaySec=. This is + useful to catch up on missed runs of the service when the + system was powered down. + ''; + }; + }; seahubExtraConf = mkOption { default = ""; + example = '' + CSRF_TRUSTED_ORIGINS = ["https://example.com"] + ''; type = types.lines; description = '' Extra config to append to `seahub_settings.py` file. @@ -137,12 +243,40 @@ in ###### Implementation - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { + services.mysql = { + enable = true; + package = lib.mkDefault pkgs.mariadb; + ensureDatabases = [ + "ccnet_db" + "seafile_db" + "seahub_db" + ]; + ensureUsers = [ + { + name = cfg.user; + ensurePermissions = { + "ccnet_db.*" = "ALL PRIVILEGES"; + "seafile_db.*" = "ALL PRIVILEGES"; + "seahub_db.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; environment.etc."seafile/ccnet.conf".source = ccnetConf; environment.etc."seafile/seafile.conf".source = seafileConf; environment.etc."seafile/seahub_settings.py".source = seahubSettings; + users.users = lib.optionalAttrs (cfg.user == defaultUser) { + "${defaultUser}" = { + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = lib.optionalAttrs (cfg.group == defaultUser) { "${defaultUser}" = { }; }; + systemd.targets.seafile = { wantedBy = [ "multi-user.target" ]; description = "Seafile components"; @@ -150,10 +284,12 @@ in systemd.services = let - securityOptions = { + serviceOptions = { ProtectHome = true; PrivateUsers = true; PrivateDevices = true; + PrivateTmp = true; + ProtectSystem = "strict"; ProtectClock = true; ProtectHostname = true; ProtectProc = "invisible"; @@ -162,36 +298,49 @@ in ProtectKernelLogs = true; ProtectControlGroups = true; RestrictNamespaces = true; + RemoveIPC = true; LockPersonality = true; RestrictRealtime = true; RestrictSUIDSGID = true; + NoNewPrivileges = true; MemoryDenyWriteExecute = true; SystemCallArchitectures = "native"; - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" ]; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + ]; + + User = cfg.user; + Group = cfg.group; + StateDirectory = "seafile"; + RuntimeDirectory = "seafile"; + LogsDirectory = "seafile"; + ConfigurationDirectory = "seafile"; + ReadWritePaths = lib.lists.optional (cfg.dataDir != "${seafRoot}/data") cfg.dataDir; }; in { seaf-server = { description = "Seafile server"; partOf = [ "seafile.target" ]; - after = [ "network.target" ]; + unitConfig.RequiresMountsFor = lib.lists.optional (cfg.dataDir != "${seafRoot}/data") cfg.dataDir; + requires = [ "mysql.service" ]; + after = [ + "network.target" + "mysql.service" + ]; wantedBy = [ "seafile.target" ]; - restartTriggers = [ ccnetConf seafileConf ]; - path = [ pkgs.sqlite ]; - serviceConfig = securityOptions // { - User = "seafile"; - Group = "seafile"; - DynamicUser = true; - StateDirectory = "seafile"; - RuntimeDirectory = "seafile"; - LogsDirectory = "seafile"; - ConfigurationDirectory = "seafile"; + restartTriggers = [ + ccnetConf + seafileConf + ]; + serviceConfig = serviceOptions // { ExecStart = '' - ${cfg.seafilePackage}/bin/seaf-server \ + ${lib.getExe cfg.seahubPackage.seafile-server} \ --foreground \ -F /etc/seafile \ -c ${ccnetDir} \ - -d ${dataDir} \ + -d ${cfg.dataDir} \ -l /var/log/seafile/server.log \ -P /run/seafile/server.pid \ -p /run/seafile @@ -199,100 +348,201 @@ in }; preStart = '' if [ ! -f "${seafRoot}/server-setup" ]; then - mkdir -p ${dataDir}/library-template - mkdir -p ${ccnetDir}/{GroupMgr,misc,OrgMgr,PeerMgr} - sqlite3 ${ccnetDir}/GroupMgr/groupmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/groupmgr.sql" - sqlite3 ${ccnetDir}/misc/config.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/config.sql" - sqlite3 ${ccnetDir}/OrgMgr/orgmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/org.sql" - sqlite3 ${ccnetDir}/PeerMgr/usermgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/user.sql" - sqlite3 ${dataDir}/seafile.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/seafile.sql" - echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup + mkdir -p ${cfg.dataDir}/library-template + # Load schema on first install + ${pkgs.mariadb.client}/bin/mysql --database=ccnet_db < ${cfg.seahubPackage.seafile-server}/share/seafile/sql/mysql/ccnet.sql + ${pkgs.mariadb.client}/bin/mysql --database=seafile_db < ${cfg.seahubPackage.seafile-server}/share/seafile/sql/mysql/seafile.sql + echo "${cfg.seahubPackage.seafile-server.version}-mysql" > "${seafRoot}"/server-setup + echo Loaded MySQL schemas for first install fi # checking for upgrades and handling them installedMajor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f1) installedMinor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f2) - pkgMajor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f1) - pkgMinor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f2) + pkgMajor=$(echo "${cfg.seahubPackage.seafile-server.version}" | cut -d"." -f1) + pkgMinor=$(echo "${cfg.seahubPackage.seafile-server.version}" | cut -d"." -f2) if [[ $installedMajor == $pkgMajor && $installedMinor == $pkgMinor ]]; then : - elif [[ $installedMajor == 8 && $installedMinor == 0 && $pkgMajor == 9 && $pkgMinor == 0 ]]; then - # Upgrade from 8.0 to 9.0 - sqlite3 ${dataDir}/seafile.db ".read ${pkgs.seahub}/scripts/upgrade/sql/9.0.0/sqlite3/seafile.sql" - echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup - elif [[ $installedMajor == 9 && $installedMinor == 0 && $pkgMajor == 10 && $pkgMinor == 0 ]]; then - # Upgrade from 9.0 to 10.0 - sqlite3 ${dataDir}/seafile.db ".read ${pkgs.seahub}/scripts/upgrade/sql/10.0.0/sqlite3/seafile.sql" - echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup + elif [[ $installedMajor == 10 && $installedMinor == 0 && $pkgMajor == 11 && $pkgMinor == 0 ]]; then + # Upgrade from 10.0 to 11.0: migrate to mysql + echo Migrating from version 10 to 11 + + # From https://github.com/haiwen/seahub/blob/e12f941bfef7191795d8c72a7d339c01062964b2/scripts/sqlite2mysql.sh + + echo Migrating ccnet database to MySQL + ${lib.getExe pkgs.sqlite} ${ccnetDir}/PeerMgr/usermgr.db ".dump" | \ + ${lib.getExe cfg.seahubPackage.python3} ${cfg.seahubPackage}/scripts/sqlite2mysql.py > ${ccnetDir}/ccnet.sql + ${lib.getExe pkgs.sqlite} ${ccnetDir}/GroupMgr/groupmgr.db ".dump" | \ + ${lib.getExe cfg.seahubPackage.python3} ${cfg.seahubPackage}/scripts/sqlite2mysql.py >> ${ccnetDir}/ccnet.sql + sed 's/ctime INTEGER/ctime BIGINT/g' -i ${ccnetDir}/ccnet.sql + sed 's/email TEXT, role TEXT/email VARCHAR(255), role TEXT/g' -i ${ccnetDir}/ccnet.sql + ${pkgs.mariadb.client}/bin/mysql --database=ccnet_db < ${ccnetDir}/ccnet.sql + + echo Migrating seafile database to MySQL + ${lib.getExe pkgs.sqlite} ${cfg.dataDir}/seafile.db ".dump" | \ + ${lib.getExe cfg.seahubPackage.python3} ${cfg.seahubPackage}/scripts/sqlite2mysql.py > ${cfg.dataDir}/seafile.sql + sed 's/owner_id TEXT/owner_id VARCHAR(255)/g' -i ${cfg.dataDir}/seafile.sql + sed 's/user_name TEXT/user_name VARCHAR(255)/g' -i ${cfg.dataDir}/seafile.sql + ${pkgs.mariadb.client}/bin/mysql --database=seafile_db < ${cfg.dataDir}/seafile.sql + + echo Migrating seahub database to MySQL + echo 'SET FOREIGN_KEY_CHECKS=0;' > ${seahubDir}/seahub.sql + ${lib.getExe pkgs.sqlite} ${seahubDir}/seahub.db ".dump" | \ + ${lib.getExe cfg.seahubPackage.python3} ${cfg.seahubPackage}/scripts/sqlite2mysql.py >> ${seahubDir}/seahub.sql + sed 's/`permission` , `reporter` text NOT NULL/`permission` longtext NOT NULL/g' -i ${seahubDir}/seahub.sql + sed 's/varchar(256) NOT NULL UNIQUE/varchar(255) NOT NULL UNIQUE/g' -i ${seahubDir}/seahub.sql + sed 's/, UNIQUE (`user_email`, `contact_email`)//g' -i ${seahubDir}/seahub.sql + sed '/INSERT INTO `base_dirfileslastmodifiedinfo`/d' -i ${seahubDir}/seahub.sql + sed '/INSERT INTO `notifications_usernotification`/d' -i ${seahubDir}/seahub.sql + sed 's/DEFERRABLE INITIALLY DEFERRED//g' -i ${seahubDir}/seahub.sql + ${pkgs.mariadb.client}/bin/mysql --database=seahub_db < ${seahubDir}/seahub.sql + + echo "${cfg.seahubPackage.seafile-server.version}-mysql" > "${seafRoot}"/server-setup + echo Migration complete else - echo "Unsupported upgrade" >&2 + echo "Unsupported upgrade: $installedMajor.$installedMinor to $pkgMajor.$pkgMinor" >&2 exit 1 fi ''; + + # Fix unix socket permissions + postStart = ( + lib.strings.optionalString (lib.strings.hasPrefix "unix:" cfg.seafileSettings.fileserver.host) '' + while [[ ! -S "${lib.strings.removePrefix "unix:" cfg.seafileSettings.fileserver.host}" ]]; do + sleep 1 + done + chmod 666 "${lib.strings.removePrefix "unix:" cfg.seafileSettings.fileserver.host}" + '' + ); }; seahub = { description = "Seafile Server Web Frontend"; wantedBy = [ "seafile.target" ]; partOf = [ "seafile.target" ]; - after = [ "network.target" "seaf-server.service" ]; - requires = [ "seaf-server.service" ]; + unitConfig.RequiresMountsFor = lib.lists.optional (cfg.dataDir != "${seafRoot}/data") cfg.dataDir; + requires = [ + "mysql.service" + "seaf-server.service" + ]; + after = [ + "network.target" + "mysql.service" + "seaf-server.service" + ]; restartTriggers = [ seahubSettings ]; environment = { - PYTHONPATH = "${pkgs.seahub.pythonPath}:${pkgs.seahub}/thirdpart:${pkgs.seahub}"; + PYTHONPATH = "${cfg.seahubPackage.pythonPath}:${cfg.seahubPackage}/thirdpart:${cfg.seahubPackage}"; DJANGO_SETTINGS_MODULE = "seahub.settings"; CCNET_CONF_DIR = ccnetDir; - SEAFILE_CONF_DIR = dataDir; + SEAFILE_CONF_DIR = cfg.dataDir; SEAFILE_CENTRAL_CONF_DIR = "/etc/seafile"; SEAFILE_RPC_PIPE_PATH = "/run/seafile"; SEAHUB_LOG_DIR = "/var/log/seafile"; }; - serviceConfig = securityOptions // { - User = "seafile"; - Group = "seafile"; - DynamicUser = true; + serviceConfig = serviceOptions // { RuntimeDirectory = "seahub"; - StateDirectory = "seafile"; - LogsDirectory = "seafile"; - ConfigurationDirectory = "seafile"; ExecStart = '' - ${pkgs.seahub.python.pkgs.gunicorn}/bin/gunicorn seahub.wsgi:application \ + ${lib.getExe cfg.seahubPackage.python3.pkgs.gunicorn} seahub.wsgi:application \ --name seahub \ --workers ${toString cfg.workers} \ --log-level=info \ --preload \ --timeout=1200 \ --limit-request-line=8190 \ - --bind unix:/run/seahub/gunicorn.sock + --bind ${cfg.seahubAddress} ''; }; preStart = '' mkdir -p ${seahubDir}/media # Link all media except avatars - for m in `find ${pkgs.seahub}/media/ -maxdepth 1 -not -name "avatars"`; do + for m in `find ${cfg.seahubPackage}/media/ -maxdepth 1 -not -name "avatars"`; do ln -sf $m ${seahubDir}/media/ done if [ ! -e "${seafRoot}/.seahubSecret" ]; then - ${pkgs.seahub.python}/bin/python ${pkgs.seahub}/tools/secret_key_generator.py > ${seafRoot}/.seahubSecret - chmod 400 ${seafRoot}/.seahubSecret + ( + umask 377 && + ${lib.getExe cfg.seahubPackage.python3} ${cfg.seahubPackage}/tools/secret_key_generator.py > ${seafRoot}/.seahubSecret + ) fi if [ ! -f "${seafRoot}/seahub-setup" ]; then # avatars directory should be writable - install -D -t ${seahubDir}/media/avatars/ ${pkgs.seahub}/media/avatars/default.png - install -D -t ${seahubDir}/media/avatars/groups ${pkgs.seahub}/media/avatars/groups/default.png + install -D -t ${seahubDir}/media/avatars/ ${cfg.seahubPackage}/media/avatars/default.png + install -D -t ${seahubDir}/media/avatars/groups ${cfg.seahubPackage}/media/avatars/groups/default.png # init database - ${pkgs.seahub}/manage.py migrate + ${cfg.seahubPackage}/manage.py migrate # create admin account - ${pkgs.expect}/bin/expect -c 'spawn ${pkgs.seahub}/manage.py createsuperuser --email=${cfg.adminEmail}; expect "Password: "; send "${cfg.initialAdminPassword}\r"; expect "Password (again): "; send "${cfg.initialAdminPassword}\r"; expect "Superuser created successfully."' - echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup" + ${lib.getExe pkgs.expect} -c 'spawn ${cfg.seahubPackage}/manage.py createsuperuser --email=${cfg.adminEmail}; expect "Password: "; send "${cfg.initialAdminPassword}\r"; expect "Password (again): "; send "${cfg.initialAdminPassword}\r"; expect "Superuser created successfully."' + echo "${cfg.seahubPackage.version}-mysql" > "${seafRoot}/seahub-setup" fi if [ $(cat "${seafRoot}/seahub-setup" | cut -d"-" -f1) != "${pkgs.seahub.version}" ]; then - # update database - ${pkgs.seahub}/manage.py migrate - echo "${pkgs.seahub.version}-sqlite" > "${seafRoot}/seahub-setup" + # run django migrations + ${cfg.seahubPackage}/manage.py migrate + echo "${cfg.seahubPackage.version}-mysql" > "${seafRoot}/seahub-setup" + fi + ''; + }; + + seaf-gc = { + description = "Seafile storage garbage collection"; + conflicts = [ + "seaf-server.service" + "seahub.service" + ]; + after = [ + "seaf-server.service" + "seahub.service" + ]; + unitConfig.RequiresMountsFor = lib.lists.optional (cfg.dataDir != "${seafRoot}/data") cfg.dataDir; + onSuccess = [ + "seaf-server.service" + "seahub.service" + ]; + onFailure = [ + "seaf-server.service" + "seahub.service" + ]; + startAt = lib.lists.optionals cfg.gc.enable cfg.gc.dates; + serviceConfig = serviceOptions // { + Type = "oneshot"; + }; + script = '' + if [ ! -f "${seafRoot}/server-setup" ]; then + echo "Server not setup yet, GC not needed" >&2 + exit + fi + + # checking for pending upgrades + installedMajor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f1) + installedMinor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f2) + pkgMajor=$(echo "${cfg.seahubPackage.seafile-server.version}" | cut -d"." -f1) + pkgMinor=$(echo "${cfg.seahubPackage.seafile-server.version}" | cut -d"." -f2) + + if [[ $installedMajor != $pkgMajor || $installedMinor != $pkgMinor ]]; then + echo "Server not upgraded yet" >&2 + exit fi + + # Clean up user-deleted blocks and libraries + ${cfg.seahubPackage.seafile-server}/bin/seafserv-gc \ + -F /etc/seafile \ + -c ${ccnetDir} \ + -d ${cfg.dataDir} \ + --rm-fs ''; }; }; + + systemd.timers.seaf-gc = lib.mkIf cfg.gc.enable { + timerConfig = { + randomizedDelaySec = cfg.gc.randomizedDelaySec; + Persistent = cfg.gc.persistent; + }; + }; }; + + meta.maintainers = with lib.maintainers; [ + greizgh + schmittlauch + ]; } diff --git a/nixos/modules/services/networking/shorewall.nix b/nixos/modules/services/networking/shorewall.nix index 05087aaa8b3b..8d05543e9051 100644 --- a/nixos/modules/services/networking/shorewall.nix +++ b/nixos/modules/services/networking/shorewall.nix @@ -58,7 +58,7 @@ in { install -D -d -m 750 /var/lib/shorewall install -D -d -m 755 /var/lock/subsys touch /var/log/shorewall.log - chown 750 /var/log/shorewall.log + chmod 750 /var/log/shorewall.log ''; }; environment = { diff --git a/nixos/modules/services/networking/shorewall6.nix b/nixos/modules/services/networking/shorewall6.nix index 1eab3284d15f..3088e59fa9eb 100644 --- a/nixos/modules/services/networking/shorewall6.nix +++ b/nixos/modules/services/networking/shorewall6.nix @@ -58,7 +58,7 @@ in { install -D -d -m 750 /var/lib/shorewall6 install -D -d -m 755 /var/lock/subsys touch /var/log/shorewall6.log - chown 750 /var/log/shorewall6.log + chmod 750 /var/log/shorewall6.log ''; }; environment = { diff --git a/nixos/modules/services/networking/shout.nix b/nixos/modules/services/networking/shout.nix deleted file mode 100644 index 017b8590197a..000000000000 --- a/nixos/modules/services/networking/shout.nix +++ /dev/null @@ -1,115 +0,0 @@ -{ pkgs, lib, config, ... }: - -with lib; - -let - cfg = config.services.shout; - shoutHome = "/var/lib/shout"; - - defaultConfig = pkgs.runCommand "config.js" { preferLocalBuild = true; } '' - EDITOR=true ${pkgs.shout}/bin/shout config --home $PWD - mv config.js $out - ''; - - finalConfigFile = if (cfg.configFile != null) then cfg.configFile else '' - var _ = require('${pkgs.shout}/lib/node_modules/shout/node_modules/lodash') - - module.exports = _.merge( - {}, - require('${defaultConfig}'), - ${builtins.toJSON cfg.config} - ) - ''; - -in { - options.services.shout = { - enable = mkEnableOption "Shout web IRC client"; - - private = mkOption { - type = types.bool; - default = false; - description = '' - Make your shout instance private. You will need to configure user - accounts by adding entries in {file}`${shoutHome}/users`. - ''; - }; - - listenAddress = mkOption { - type = types.str; - default = "0.0.0.0"; - description = "IP interface to listen on for http connections."; - }; - - port = mkOption { - type = types.port; - default = 9000; - description = "TCP port to listen on for http connections."; - }; - - configFile = mkOption { - type = types.nullOr types.lines; - default = null; - description = '' - Contents of Shout's {file}`config.js` file. - - Used for backward compatibility, recommended way is now to use - the `config` option. - - Documentation: http://shout-irc.com/docs/server/configuration.html - ''; - }; - - config = mkOption { - default = {}; - type = types.attrs; - example = { - displayNetwork = false; - defaults = { - name = "Your Network"; - host = "localhost"; - port = 6697; - }; - }; - description = '' - Shout {file}`config.js` contents as attribute set (will be - converted to JSON to generate the configuration file). - - The options defined here will be merged to the default configuration file. - - Documentation: http://shout-irc.com/docs/server/configuration.html - ''; - }; - }; - - config = mkIf cfg.enable { - users.users.shout = { - isSystemUser = true; - group = "shout"; - description = "Shout daemon user"; - home = shoutHome; - createHome = true; - }; - users.groups.shout = {}; - - systemd.services.shout = { - description = "Shout web IRC client"; - wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ]; - after = [ "network-online.target" ]; - preStart = "ln -sf ${pkgs.writeText "config.js" finalConfigFile} ${shoutHome}/config.js"; - script = concatStringsSep " " [ - "${pkgs.shout}/bin/shout" - (if cfg.private then "--private" else "--public") - "--port" (toString cfg.port) - "--host" (toString cfg.listenAddress) - "--home" shoutHome - ]; - serviceConfig = { - User = "shout"; - ProtectHome = "true"; - ProtectSystem = "full"; - PrivateTmp = "true"; - }; - }; - }; -} diff --git a/nixos/modules/services/networking/sing-box.nix b/nixos/modules/services/networking/sing-box.nix index 9f09e528e74d..1eadeaf4cbc1 100644 --- a/nixos/modules/services/networking/sing-box.nix +++ b/nixos/modules/services/networking/sing-box.nix @@ -55,11 +55,17 @@ in systemd.packages = [ cfg.package ]; systemd.services.sing-box = { - preStart = '' - umask 0077 - mkdir -p /etc/sing-box - ${utils.genJqSecretsReplacementSnippet cfg.settings "/etc/sing-box/config.json"} - ''; + preStart = utils.genJqSecretsReplacementSnippet cfg.settings "/run/sing-box/config.json"; + serviceConfig = { + StateDirectory = "sing-box"; + StateDirectoryMode = "0700"; + RuntimeDirectory = "sing-box"; + RuntimeDirectoryMode = "0700"; + ExecStart = [ + "" + "${lib.getExe cfg.package} -D \${STATE_DIRECTORY} -C \${RUNTIME_DIRECTORY} run" + ]; + }; wantedBy = [ "multi-user.target" ]; }; }; diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix index d9c314f5ec64..f96853939bd2 100644 --- a/nixos/modules/services/networking/smokeping.nix +++ b/nixos/modules/services/networking/smokeping.nix @@ -375,8 +375,5 @@ in }; }; - meta.maintainers = with lib.maintainers; [ - erictapen - nh2 - ]; + meta.maintainers = with lib.maintainers; [ nh2 ]; } diff --git a/nixos/modules/services/networking/soju.nix b/nixos/modules/services/networking/soju.nix index 0f4969b930e4..808c708163b2 100644 --- a/nixos/modules/services/networking/soju.nix +++ b/nixos/modules/services/networking/soju.nix @@ -12,7 +12,7 @@ let tlsCfg = optionalString (cfg.tlsCertificate != null) "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}"; logCfg = optionalString cfg.enableMessageLogging - "log fs ${stateDir}/logs"; + "message-store fs ${stateDir}/logs"; configFile = pkgs.writeText "soju.conf" '' ${listenCfg} diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index 0d127a6544c0..26ca39f73d39 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -585,7 +585,8 @@ in if ! [ -h "${k.path}" ]; then rm -f "${k.path}" fi - mkdir -m 0755 -p "$(dirname '${k.path}')" + mkdir -p "$(dirname '${k.path}')" + chmod 0755 "$(dirname '${k.path}')" ssh-keygen \ -t "${k.type}" \ ${lib.optionalString (k ? bits) "-b ${toString k.bits}"} \ diff --git a/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix index 0c04a9c85396..1cb9f4e1fc5a 100644 --- a/nixos/modules/services/networking/strongswan.nix +++ b/nixos/modules/services/networking/strongswan.nix @@ -3,14 +3,12 @@ let inherit (builtins) toFile; - inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList + inherit (lib) concatMapStrings concatStringsSep mapAttrsToList mkIf mkEnableOption mkOption types literalExpression optionalString; cfg = config.services.strongswan; - ipsecSecrets = secrets: toFile "ipsec.secrets" ( - concatMapStringsSep "\n" (f: "include ${f}") secrets - ); + ipsecSecrets = secrets: concatMapStrings (f: "include ${f}\n") secrets; ipsecConf = {setup, connections, ca}: let @@ -138,16 +136,12 @@ in }; - config = with cfg; - let - secretsFile = ipsecSecrets cfg.secrets; - in - mkIf enable + config = with cfg; mkIf enable { # here we should use the default strongswan ipsec.secrets and # append to it (default one is empty so not a pb for now) - environment.etc."ipsec.secrets".source = secretsFile; + environment.etc."ipsec.secrets".text = ipsecSecrets cfg.secrets; systemd.services.strongswan = { description = "strongSwan IPSec Service"; @@ -156,7 +150,10 @@ in wants = [ "network-online.target" ]; after = [ "network-online.target" ]; environment = { - STRONGSWAN_CONF = strongswanConf { inherit setup connections ca secretsFile managePlugins enabledPlugins; }; + STRONGSWAN_CONF = strongswanConf { + inherit setup connections ca managePlugins enabledPlugins; + secretsFile = "/etc/ipsec.secrets"; + }; }; serviceConfig = { ExecStart = "${pkgs.strongswan}/sbin/ipsec start --nofork"; diff --git a/nixos/modules/services/networking/supplicant.nix b/nixos/modules/services/networking/supplicant.nix index 52645500d4f6..2e1ce24976b2 100644 --- a/nixos/modules/services/networking/supplicant.nix +++ b/nixos/modules/services/networking/supplicant.nix @@ -26,12 +26,15 @@ let ifaceArg = concatStringsSep " -N " (map (i: "-i${i}") (splitString " " iface)); driverArg = optionalString (suppl.driver != null) "-D${suppl.driver}"; bridgeArg = optionalString (suppl.bridge!="") "-b${suppl.bridge}"; - confFileArg = optionalString (suppl.configFile.path!=null) "-c${suppl.configFile.path}"; extraConfFile = pkgs.writeText "supplicant-extra-conf-${replaceStrings [" "] ["-"] iface}" '' ${optionalString suppl.userControlled.enable "ctrl_interface=DIR=${suppl.userControlled.socketDir} GROUP=${suppl.userControlled.group}"} ${optionalString suppl.configFile.writable "update_config=1"} ${suppl.extraConf} ''; + confArgs = escapeShellArgs + (if suppl.configFile.path == null + then [ "-c${extraConfFile}" ] + else [ "-c${suppl.configFile.path}" "-I${extraConfFile}" ]); in { description = "Supplicant ${iface}${optionalString (iface=="WLAN"||iface=="LAN") " %I"}"; wantedBy = [ "multi-user.target" ] ++ deps; @@ -51,7 +54,7 @@ let ''} ''; - serviceConfig.ExecStart = "${pkgs.wpa_supplicant}/bin/wpa_supplicant -s ${driverArg} ${confFileArg} -I${extraConfFile} ${bridgeArg} ${suppl.extraCmdArgs} ${if (iface=="WLAN"||iface=="LAN") then "-i%I" else (if (iface=="DBUS") then "-u" else ifaceArg)}"; + serviceConfig.ExecStart = "${pkgs.wpa_supplicant}/bin/wpa_supplicant -s ${driverArg} ${confArgs} ${bridgeArg} ${suppl.extraCmdArgs} ${if (iface=="WLAN"||iface=="LAN") then "-i%I" else (if (iface=="DBUS") then "-u" else ifaceArg)}"; }; diff --git a/nixos/modules/services/networking/suricata/default.nix b/nixos/modules/services/networking/suricata/default.nix new file mode 100644 index 000000000000..5473fc913ebc --- /dev/null +++ b/nixos/modules/services/networking/suricata/default.nix @@ -0,0 +1,282 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.services.suricata; + pkg = cfg.package; + yaml = pkgs.formats.yaml { }; + inherit (lib) + mkEnableOption + mkPackageOption + mkOption + types + literalExpression + filterAttrsRecursive + concatStringsSep + strings + lists + mkIf + ; +in +{ + meta.maintainers = with lib.maintainers; [ felbinger ]; + + options.services.suricata = { + enable = mkEnableOption "Suricata"; + + package = mkPackageOption pkgs "suricata" { }; + + configFile = mkOption { + type = types.path; + visible = false; + default = pkgs.writeTextFile { + name = "suricata.yaml"; + text = '' + %YAML 1.1 + --- + ${builtins.readFile ( + yaml.generate "suricata-settings-raw.yaml" ( + filterAttrsRecursive (name: value: value != null) cfg.settings + ) + )} + ''; + }; + description = '' + Configuration file for suricata. + + It is not usual to override the default values; it is recommended to use `settings`. + If you want to include extra configuration to the file, use the `settings.includes`. + ''; + }; + + settings = mkOption { + type = types.submodule (import ./settings.nix { inherit config lib yaml; }); + example = literalExpression '' + vars.address-groups.HOME_NET = "192.168.178.0/24"; + outputs = [ + { + fast = { + enabled = true; + filename = "fast.log"; + append = "yes"; + }; + } + { + eve-log = { + enabled = true; + filetype = "regular"; + filename = "eve.json"; + community-id = true; + types = [ + { + alert.tagged-packets = "yes"; + } + ]; + }; + } + ]; + af-packet = [ + { + interface = "eth0"; + cluster-id = "99"; + cluster-type = "cluster_flow"; + defrag = "yes"; + } + { + interface = "default"; + } + ]; + af-xdp = [ + { + interface = "eth1"; + } + ]; + dpdk.interfaces = [ + { + interface = "eth2"; + } + ]; + pcap = [ + { + interface = "eth3"; + } + ]; + app-layer.protocols = { + telnet.enabled = "yes"; + dnp3.enabled = "yes"; + modbus.enabled = "yes"; + }; + ''; + description = "Suricata settings"; + }; + + enabledSources = mkOption { + type = types.listOf types.str; + # see: nix-shell -p suricata python3Packages.pyyaml --command 'suricata-update list-sources' + default = [ + "et/open" + "etnetera/aggressive" + "stamus/lateral" + "oisf/trafficid" + "tgreen/hunting" + "sslbl/ja3-fingerprints" + "sslbl/ssl-fp-blacklist" + "malsilo/win-malware" + "pawpatrules" + ]; + description = '' + List of sources that should be enabled. + Currently sources which require a secret-code are not supported. + ''; + }; + + disabledRules = mkOption { + type = types.listOf types.str; + # protocol dnp3 seams to be disabled, which causes the signature evaluation to fail, so we disable the + # dnp3 rules, see https://github.com/OISF/suricata/blob/master/rules/dnp3-events.rules for more details + default = [ + "2270000" + "2270001" + "2270002" + "2270003" + "2270004" + ]; + description = '' + List of rules that should be disabled. + ''; + }; + }; + + config = + let + captureInterfaces = + let + inherit (lists) unique optionals; + in + unique ( + map (e: e.interface) ( + (optionals (cfg.settings.af-packet != null) cfg.settings.af-packet) + ++ (optionals (cfg.settings.af-xdp != null) cfg.settings.af-xdp) + ++ (optionals ( + cfg.settings.dpdk != null && cfg.settings.dpdk.interfaces != null + ) cfg.settings.dpdk.interfaces) + ++ (optionals (cfg.settings.pcap != null) cfg.settings.pcap) + ) + ); + in + mkIf cfg.enable { + assertions = [ + { + assertion = (builtins.length captureInterfaces) > 0; + message = '' + At least one capture interface must be configured: + - `services.suricata.settings.af-packet` + - `services.suricata.settings.af-xdp` + - `services.suricata.settings.dpdk.interfaces` + - `services.suricata.settings.pcap` + ''; + } + ]; + + boot.kernelModules = mkIf (cfg.settings.af-packet != null) [ "af_packet" ]; + + users = { + groups.${cfg.settings.run-as.group} = { }; + users.${cfg.settings.run-as.user} = { + group = cfg.settings.run-as.group; + isSystemUser = true; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.settings."default-log-dir"} 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}" + "d /var/lib/suricata 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}" + "d ${cfg.settings."default-rule-path"} 755 ${cfg.settings.run-as.user} ${cfg.settings.run-as.group}" + ]; + + systemd.services = { + suricata-update = { + description = "Update Suricata Rules"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + script = + let + python = pkgs.python3.withPackages (ps: with ps; [ pyyaml ]); + enabledSourcesCmds = map ( + src: "${python.interpreter} ${pkg}/bin/suricata-update enable-source ${src}" + ) cfg.enabledSources; + in + '' + ${concatStringsSep "\n" enabledSourcesCmds} + ${python.interpreter} ${pkg}/bin/suricata-update update-sources + ${python.interpreter} ${pkg}/bin/suricata-update update --suricata-conf ${cfg.configFile} --no-test \ + --disable-conf ${pkgs.writeText "suricata-disable-conf" "${concatStringsSep "\n" cfg.disabledRules}"} + ''; + serviceConfig = { + Type = "oneshot"; + + PrivateTmp = true; + PrivateDevices = true; + PrivateIPC = true; + + DynamicUser = true; + User = cfg.settings.run-as.user; + Group = cfg.settings.run-as.group; + + ReadOnlyPaths = cfg.configFile; + ReadWritePaths = [ + "/var/lib/suricata" + cfg.settings."default-rule-path" + ]; + }; + }; + suricata = { + description = "Suricata"; + wantedBy = [ "multi-user.target" ]; + after = [ "suricata-update.service" ]; + serviceConfig = + let + interfaceOptions = strings.concatMapStrings (interface: " -i ${interface}") captureInterfaces; + in + { + ExecStartPre = "!${pkg}/bin/suricata -c ${cfg.configFile} -T"; + ExecStart = "!${pkg}/bin/suricata -c ${cfg.configFile}${interfaceOptions}"; + Restart = "on-failure"; + + User = cfg.settings.run-as.user; + Group = cfg.settings.run-as.group; + + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateIPC = true; + ProtectSystem = "strict"; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + ProtectHostname = true; + ProtectProc = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + ProcSubset = "pid"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + RemoveIPC = true; + + ReadOnlyPaths = cfg.configFile; + ReadWritePaths = cfg.settings."default-log-dir"; + RuntimeDirectory = "suricata"; + }; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/suricata/settings.nix b/nixos/modules/services/networking/suricata/settings.nix new file mode 100644 index 000000000000..f96d78ca66d5 --- /dev/null +++ b/nixos/modules/services/networking/suricata/settings.nix @@ -0,0 +1,625 @@ +{ + lib, + config, + yaml, + ... +}: +let + cfg = config.services.suricata; + inherit (lib) + mkEnableOption + mkOption + types + literalExpression + ; + mkDisableOption = + name: + mkEnableOption name + // { + default = true; + example = false; + }; +in +{ + freeformType = yaml.type; + options = { + vars = mkOption { + type = types.nullOr ( + types.submodule { + options = { + address-groups = mkOption { + type = ( + types.submodule { + options = { + HOME_NET = mkOption { default = "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"; }; + EXTERNAL_NET = mkOption { default = "!$HOME_NET"; }; + HTTP_SERVERS = mkOption { default = "$HOME_NET"; }; + SMTP_SERVERS = mkOption { default = "$HOME_NET"; }; + SQL_SERVERS = mkOption { default = "$HOME_NET"; }; + DNS_SERVERS = mkOption { default = "$HOME_NET"; }; + TELNET_SERVERS = mkOption { default = "$HOME_NET"; }; + AIM_SERVERS = mkOption { default = "$EXTERNAL_NET"; }; + DC_SERVERS = mkOption { default = "$HOME_NET"; }; + DNP3_SERVER = mkOption { default = "$HOME_NET"; }; + DNP3_CLIENT = mkOption { default = "$HOME_NET"; }; + MODBUS_CLIENT = mkOption { default = "$HOME_NET"; }; + MODBUS_SERVER = mkOption { default = "$HOME_NET"; }; + ENIP_CLIENT = mkOption { default = "$HOME_NET"; }; + ENIP_SERVER = mkOption { default = "$HOME_NET"; }; + }; + } + ); + default = { }; + example = { + HOME_NET = "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"; + EXTERNAL_NET = "!$HOME_NET"; + HTTP_SERVERS = "$HOME_NET"; + SMTP_SERVERS = "$HOME_NET"; + SQL_SERVERS = "$HOME_NET"; + DNS_SERVERS = "$HOME_NET"; + TELNET_SERVERS = "$HOME_NET"; + AIM_SERVERS = "$EXTERNAL_NET"; + DC_SERVERS = "$HOME_NET"; + DNP3_SERVER = "$HOME_NET"; + DNP3_CLIENT = "$HOME_NET"; + MODBUS_CLIENT = "$HOME_NET"; + MODBUS_SERVER = "$HOME_NET"; + ENIP_CLIENT = "$HOME_NET"; + ENIP_SERVER = "$HOME_NET"; + }; + description = '' + The address group variables for suricata, if not defined the + default value of suricata (see example) will be used. + Your settings will extend the predefined values in example. + ''; + }; + + port-groups = mkOption { + type = with types; nullOr (attrsOf str); + default = { + HTTP_PORTS = "80"; + SHELLCODE_PORTS = "!80"; + ORACLE_PORTS = "1521"; + SSH_PORTS = "22"; + DNP3_PORTS = "20000"; + MODBUS_PORTS = "502"; + FILE_DATA_PORTS = "[$HTTP_PORTS,110,143]"; + FTP_PORTS = "21"; + GENEVE_PORTS = "6081"; + VXLAN_PORTS = "4789"; + TEREDO_PORTS = "3544"; + }; + description = '' + The port group variables for suricata. + ''; + }; + }; + } + ); + default = { }; # add default values to config + }; + + stats = mkOption { + type = + with types; + nullOr (submodule { + options = { + enable = mkEnableOption "suricata global stats"; + + interval = mkOption { + type = types.str; + default = "8"; + description = '' + The interval field (in seconds) controls the interval at + which stats are updated in the log. + ''; + }; + + decoder-events = mkOption { + type = types.bool; + default = true; + description = '' + Add decode events to stats + ''; + }; + + decoder-events-prefix = mkOption { + type = types.str; + default = "decoder.event"; + description = '' + Decoder event prefix in stats. Has been 'decoder' before, but that leads + to missing events in the eve.stats records. + ''; + }; + + stream-events = mkOption { + type = types.bool; + default = false; + description = '' + Add stream events as stats. + ''; + }; + }; + }); + default = null; # do not add to config unless specified + }; + + plugins = mkOption { + type = with types; nullOr (listOf path); + default = null; + description = '' + Plugins -- Experimental -- specify the filename for each plugin shared object + ''; + }; + + outputs = mkOption { + type = + with types; + nullOr ( + listOf ( + attrsOf (submodule { + freeformType = yaml.type; + options = { + enabled = mkEnableOption "<NAME>"; + }; + }) + ) + ); + default = null; + example = literalExpression '' + [ + { + fast = { + enabled = "yes"; + filename = "fast.log"; + append = "yes"; + }; + } + { + eve-log = { + enabled = "yes"; + filetype = "regular"; + filename = "eve.json"; + community-id = true; + types = [ + { + alert.tagged-packets = "yes"; + } + ]; + }; + } + ]; + ''; + description = '' + Configure the type of alert (and other) logging you would like. + + Valid values for <NAME> are e. g. `fast`, `eve-log`, `syslog`, `file-store`, ... + - `fast`: a line based alerts log similar to Snort's fast.log + - `eve-log`: Extensible Event Format (nicknamed EVE) event log in JSON format + + For more details regarding the configuration, checkout the shipped suricata.yaml + ```shell + nix-shell -p suricata yq coreutils-full --command 'yq < $(dirname $(which suricata))/../etc/suricata/suricata.yaml' + ``` + and the [suricata documentation](https://docs.suricata.io/en/latest/output/index.html). + ''; + }; + + "default-log-dir" = mkOption { + type = types.str; + default = "/var/log/suricata"; + description = '' + The default logging directory. Any log or output file will be placed here if it's + not specified with a full path name. This can be overridden with the -l command + line parameter. + ''; + }; + + logging = { + "default-log-level" = mkOption { + type = types.enum [ + "error" + "warning" + "notice" + "info" + "perf" + "config" + "debug" + ]; + default = "notice"; + description = '' + The default log level: can be overridden in an output section. + Note that debug level logging will only be emitted if Suricata was + compiled with the --enable-debug configure option. + ''; + }; + + "default-log-format" = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The default output format. Optional parameter, should default to + something reasonable if not provided. Can be overridden in an + output section. You can leave this out to get the default. + ''; + }; + + "default-output-filter" = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A regex to filter output. Can be overridden in an output section. + Defaults to empty (no filter). + ''; + }; + + "stacktrace-on-signal" = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Requires libunwind to be available when Suricata is configured and built. + If a signal unexpectedly terminates Suricata, displays a brief diagnostic + message with the offending stacktrace if enabled. + ''; + }; + + outputs = { + console = { + enable = mkDisableOption "logging to console"; + }; + file = { + enable = mkDisableOption "logging to file"; + + level = mkOption { + type = types.enum [ + "error" + "warning" + "notice" + "info" + "perf" + "config" + "debug" + ]; + default = "info"; + description = '' + Loglevel for logs written to the logfile + ''; + }; + + filename = mkOption { + type = types.str; + default = "suricata.log"; + description = '' + Filename of the logfile + ''; + }; + + format = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Logformat for logs written to the logfile + ''; + }; + + type = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Type of logfile + ''; + }; + }; + syslog = { + enable = mkEnableOption "logging to syslog"; + + facility = mkOption { + type = types.str; + default = "local5"; + description = '' + Facility to log to + ''; + }; + + format = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Logformat for logs send to syslog + ''; + }; + + type = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Type of logs send to syslog + ''; + }; + }; + }; + }; + + "af-packet" = mkOption { + type = + with types; + nullOr ( + listOf (submodule { + freeformType = yaml.type; + options = { + interface = mkOption { + type = types.str; + default = null; + }; + }; + }) + ); + default = null; + description = '' + Linux high speed capture support + ''; + }; + + "af-xdp" = mkOption { + type = + with types; + nullOr ( + listOf (submodule { + freeformType = yaml.type; + options = { + interface = mkOption { + type = types.str; + default = null; + }; + }; + }) + ); + default = null; + description = '' + Linux high speed af-xdp capture support, see + [docs/capture-hardware/af-xdp](https://docs.suricata.io/en/suricata-7.0.3/capture-hardware/af-xdp.html) + ''; + }; + + "dpdk" = mkOption { + type = + with types; + nullOr (submodule { + options = { + eal-params.proc-type = mkOption { + type = with types; nullOr str; + default = null; + }; + interfaces = mkOption { + type = + with types; + nullOr ( + listOf (submodule { + freeformType = yaml.type; + options = { + interface = mkOption { + type = types.str; + default = null; + }; + }; + }) + ); + default = null; + }; + }; + }); + default = null; + description = '' + DPDK capture support, see + [docs/capture-hardware/dpdk](https://docs.suricata.io/en/suricata-7.0.3/capture-hardware/dpdk.html) + ''; + }; + + "pcap" = mkOption { + type = + with types; + nullOr ( + listOf (submodule { + freeformType = yaml.type; + options = { + interface = mkOption { + type = types.str; + default = null; + }; + }; + }) + ); + default = null; + description = '' + Cross platform libpcap capture support + ''; + }; + + "pcap-file".checksum-checks = mkOption { + type = types.enum [ + "yes" + "no" + "auto" + ]; + default = "auto"; + description = '' + Possible values are: + - yes: checksum validation is forced + - no: checksum validation is disabled + - auto: Suricata uses a statistical approach to detect when + checksum off-loading is used. (default) + Warning: 'checksum-validation' must be set to yes to have checksum tested + ''; + }; + + "app-layer" = mkOption { + type = + with types; + nullOr (submodule { + options = { + "error-policy" = mkOption { + type = types.enum [ + "drop-flow" + "pass-flow" + "bypass" + "drop-packet" + "pass-packet" + "reject" + "ignore" + ]; + default = "ignore"; + description = '' + The error-policy setting applies to all app-layer parsers. Values can be + "drop-flow", "pass-flow", "bypass", "drop-packet", "pass-packet", "reject" or + "ignore" (the default). + ''; + }; + protocols = mkOption { + type = + with types; + nullOr ( + attrsOf (submodule { + freeformType = yaml.type; + options = { + enabled = mkOption { + type = types.enum [ + "yes" + "no" + "detection-only" + ]; + default = "no"; + description = '' + The option "enabled" takes 3 values - "yes", "no", "detection-only". + "yes" enables both detection and the parser, "no" disables both, and + "detection-only" enables protocol detection only (parser disabled). + ''; + }; + }; + }) + ); + default = null; + }; + }; + }); + default = null; # do not add to config unless specified + }; + + "run-as" = { + user = mkOption { + type = types.str; + default = "suricata"; + description = "Run Suricata with a specific user-id"; + }; + group = mkOption { + type = types.str; + default = "suricata"; + description = "Run Suricata with a specific group-id"; + }; + }; + + "host-mode" = mkOption { + type = types.enum [ + "router" + "sniffer-only" + "auto" + ]; + default = "auto"; + description = '' + If the Suricata box is a router for the sniffed networks, set it to 'router'. If + it is a pure sniffing setup, set it to 'sniffer-only'. If set to auto, the variable + is internally switched to 'router' in IPS mode and 'sniffer-only' in IDS mode. + This feature is currently only used by the reject* keywords. + ''; + }; + + "unix-command" = mkOption { + type = + with types; + nullOr (submodule { + options = { + enabled = mkOption { + type = types.either types.bool (types.enum [ "auto" ]); + default = "auto"; + }; + filename = mkOption { + type = types.path; + default = "/run/suricata/suricata-command.socket"; + }; + }; + }); + default = { }; + description = '' + Unix command socket that can be used to pass commands to Suricata. + An external tool can then connect to get information from Suricata + or trigger some modifications of the engine. Set enabled to yes + to activate the feature. In auto mode, the feature will only be + activated in live capture mode. You can use the filename variable to set + the file name of the socket. + ''; + }; + + "exception-policy" = mkOption { + type = types.enum [ + "auto" + "drop-packet" + "drop-flow" + "reject" + "bypass" + "pass-packet" + "pass-flow" + "ignore" + ]; + default = "auto"; + description = '' + Define a common behavior for all exception policies. + In IPS mode, the default is drop-flow. For cases when that's not possible, the + engine will fall to drop-packet. To fallback to old behavior (setting each of + them individually, or ignoring all), set this to ignore. + All values available for exception policies can be used, and there is one + extra option: auto - which means drop-flow or drop-packet (as explained above) + in IPS mode, and ignore in IDS mode. Exception policy values are: drop-packet, + drop-flow, reject, bypass, pass-packet, pass-flow, ignore (disable). + ''; + }; + + "default-rule-path" = mkOption { + type = types.path; + default = "/var/lib/suricata/rules"; + description = "Path in which suricata-update managed rules are stored by default"; + }; + + "rule-files" = mkOption { + type = types.listOf types.str; + default = [ "suricata.rules" ]; + description = "Files to load suricata-update managed rules, relative to 'default-rule-path'"; + }; + + "classification-file" = mkOption { + type = types.str; + default = "/var/lib/suricata/rules/classification.config"; + description = "Suricata classification configuration file"; + }; + + "reference-config-file" = mkOption { + type = types.str; + default = "${cfg.package}/etc/suricata/reference.config"; + description = "Suricata reference configuration file"; + }; + + "threshold-file" = mkOption { + type = types.str; + default = "${cfg.package}/etc/suricata/threshold.config"; + description = "Suricata threshold configuration file"; + }; + + includes = mkOption { + type = with types; nullOr (listOf path); + default = null; + description = '' + Files to include in the suricata configuration. See + [docs/configuration/suricata-yaml](https://docs.suricata.io/en/suricata-7.0.3/configuration/suricata-yaml.html) + for available options. + ''; + }; + }; +} diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index 94ff838b50e0..2d32cf451706 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -714,10 +714,6 @@ in { ExecStart = updateConfig; }; }; - - syncthing-resume = { - wantedBy = [ "suspend.target" ]; - }; }; }; } diff --git a/nixos/modules/services/networking/tailscale-derper.nix b/nixos/modules/services/networking/tailscale-derper.nix new file mode 100644 index 000000000000..9549cc5c6640 --- /dev/null +++ b/nixos/modules/services/networking/tailscale-derper.nix @@ -0,0 +1,132 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.tailscale.derper; +in +{ + meta.maintainers = with lib.maintainers; [ SuperSandro2000 ]; + + options = { + services.tailscale.derper = { + enable = lib.mkEnableOption "Tailscale Derper. See upstream doc <https://tailscale.com/kb/1118/custom-derp-servers> how to configure it on clients"; + + domain = lib.mkOption { + type = lib.types.str; + description = "Domain name under which the derper server is reachable."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to open the firewall for the specified port. + Derper requires the used ports to be opened, otherwise it doesn't work as expected. + ''; + }; + + package = lib.mkPackageOption pkgs [ + "tailscale" + "derper" + ] { }; + + stunPort = lib.mkOption { + type = lib.types.port; + default = 3478; + description = '' + STUN port to listen on. + See online docs <https://tailscale.com/kb/1118/custom-derp-servers#prerequisites> on how to configure a different external port. + ''; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8010; + description = "The port the derper process will listen on. This is not the port tailscale will connect to."; + }; + + verifyClients = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to verify clients against a locally running tailscale daemon if they are allowed to connect to this node or not. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall = lib.mkIf cfg.openFirewall { + # port 80 and 443 are opened by nginx already + allowedUDPPorts = [ cfg.stunPort ]; + }; + + services = { + nginx = { + enable = true; + upstreams.tailscale-derper = { + servers."127.0.0.1:${toString cfg.port}" = { }; + extraConfig = '' + keepalive 64; + ''; + }; + virtualHosts."${cfg.domain}" = { + addSSL = true; # this cannot be forceSSL as derper sends some information over port 80, too. + locations."/" = { + proxyPass = "http://tailscale-derper"; + proxyWebsockets = true; + extraConfig = '' + keepalive_timeout 0; + proxy_buffering off; + ''; + }; + }; + }; + + tailscale.enable = lib.mkIf cfg.verifyClients true; + }; + + systemd.services.tailscale-derper = { + serviceConfig = { + ExecStart = + "${lib.getExe' cfg.package "derper"} -a :${toString cfg.port} -c /var/lib/derper/derper.key -hostname=${cfg.domain} -stun-port ${toString cfg.stunPort}" + + lib.optionalString cfg.verifyClients " -verify-clients"; + DynamicUser = true; + Restart = "always"; + RestartSec = "5sec"; # don't crash loop immediately + StateDirectory = "derper"; + Type = "simple"; + + CapabilityBoundingSet = [ "" ]; + DeviceAllow = null; + LockPersonality = true; + NoNewPrivileges = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" ]; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix index 2acae677390c..d30193ecc129 100644 --- a/nixos/modules/services/networking/tailscale.nix +++ b/nixos/modules/services/networking/tailscale.nix @@ -29,6 +29,12 @@ in { description = "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node."; }; + disableTaildrop = mkOption { + default = false; + type = types.bool; + description = "Whether to disable the Taildrop feature for sending files between nodes."; + }; + package = lib.mkPackageOption pkgs "tailscale" {}; openFirewall = mkOption { @@ -57,6 +63,34 @@ in { example = "/run/secrets/tailscale_key"; description = '' A file containing the auth key. + Tailscale will be automatically started if provided. + ''; + }; + + authKeyParameters = mkOption { + type = types.submodule { + options = { + ephemeral = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether to register as an ephemeral node."; + }; + preauthorized = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether to skip manual device approval."; + }; + baseURL = mkOption { + type = types.nullOr types.str; + default = null; + description = "Base URL for the Tailscale API."; + }; + }; + }; + default = { }; + description = '' + Extra parameters to pass after the auth key. + See https://tailscale.com/kb/1215/oauth-clients#registering-new-nodes-using-oauth-credentials ''; }; @@ -88,8 +122,10 @@ in { environment.systemPackages = [ cfg.package ]; # for the CLI systemd.packages = [ cfg.package ]; systemd.services.tailscaled = { + after = lib.mkIf (config.networking.networkmanager.enable) [ "NetworkManager-wait-online.service" ]; wantedBy = [ "multi-user.target" ]; path = [ + (builtins.dirOf config.security.wrapperDir) # for `su` to use taildrive with correct access rights pkgs.procps # for collecting running services (opt-in feature) pkgs.getent # for `getent` to look up user shells pkgs.kmod # required to pass tailscale's v6nat check @@ -99,6 +135,8 @@ in { ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName} ${lib.concatStringsSep " " cfg.extraDaemonFlags}"'' ] ++ (lib.optionals (cfg.permitCertUid != null) [ "TS_PERMIT_CERT_UID=${cfg.permitCertUid}" + ]) ++ (lib.optionals (cfg.disableTaildrop) [ + "TS_DISABLE_TAILDROP=true" ]); # Restart tailscaled with a single `systemctl restart` at the # end of activation, rather than a `stop` followed by a later @@ -124,13 +162,22 @@ in { # https://github.com/tailscale/tailscale/blob/v1.72.1/ipn/backend.go#L24-L32 script = let statusCommand = "${lib.getExe cfg.package} status --json --peers=false | ${lib.getExe pkgs.jq} -r '.BackendState'"; + paramToString = v: + if (builtins.isBool v) then (lib.boolToString v) + else (toString v); + params = lib.pipe cfg.authKeyParameters [ + (lib.filterAttrs (_: v: v != null)) + (lib.mapAttrsToList (k: v: "${k}=${paramToString v}")) + (builtins.concatStringsSep "&") + (params: if params != "" then "?${params}" else "") + ]; in '' while [[ "$(${statusCommand})" == "NoState" ]]; do sleep 0.5 done status=$(${statusCommand}) if [[ "$status" == "NeedsLogin" || "$status" == "NeedsMachineAuth" ]]; then - ${lib.getExe cfg.package} up --auth-key 'file:${cfg.authKeyFile}' ${escapeShellArgs cfg.extraUpFlags} + ${lib.getExe cfg.package} up --auth-key "$(cat ${cfg.authKeyFile})${params}" ${escapeShellArgs cfg.extraUpFlags} fi ''; }; diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix index 38908e3d6f1d..8c5bc82ffcb1 100644 --- a/nixos/modules/services/networking/unifi.nix +++ b/nixos/modules/services/networking/unifi.nix @@ -2,8 +2,8 @@ let cfg = config.services.unifi; stateDir = "/var/lib/unifi"; - cmd = lib.escapeShellArgs ([ "@${cfg.jrePackage}/bin/java" "java" ] - ++ lib.optionals (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16") [ + cmd = lib.escapeShellArgs ([ + "@${cfg.jrePackage}/bin/java" "java" "--add-opens=java.base/java.lang=ALL-UNNAMED" "--add-opens=java.base/java.time=ALL-UNNAMED" "--add-opens=java.base/sun.security.util=ALL-UNNAMED" @@ -27,24 +27,19 @@ in ''; }; - services.unifi.jrePackage = lib.mkOption { - type = lib.types.package; - default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8; - defaultText = lib.literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8''; - description = '' - The JRE package to use. Check the release notes to ensure it is supported. + services.unifi.jrePackage = lib.mkPackageOption pkgs "jdk" { + default = "jdk17_headless"; + extraDescription = '' + Check the UniFi controller release notes to ensure it is supported. ''; }; - services.unifi.unifiPackage = lib.mkPackageOption pkgs "unifi5" { }; + services.unifi.unifiPackage = lib.mkPackageOption pkgs "unifi" { + default = "unifi8"; + }; services.unifi.mongodbPackage = lib.mkPackageOption pkgs "mongodb" { - default = "mongodb-5_0"; - extraDescription = '' - ::: {.note} - unifi7 officially only supports mongodb up until 4.4 but works with 5.0. - ::: - ''; + default = "mongodb-7_0"; }; services.unifi.openFirewall = lib.mkOption { @@ -92,6 +87,29 @@ in config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = lib.versionAtLeast config.system.stateVersion "24.11" + || ( + options.services.unifi.unifiPackage.highestPrio < (lib.mkOptionDefault { }).priority + && options.services.unifi.mongodbPackage.highestPrio < (lib.mkOptionDefault { }).priority + ); + message = '' + Support for UniFi < 8 has been dropped; please explicitly set + `services.unifi.unifiPackage` and `services.unifi.mongodbPackage`. + + Note that the previous default MongoDB version was 5.0 and MongoDB + only supports migrating one major version at a time; therefore, you + may wish to set `services.unifi.mongodbPackage = pkgs.mongodb-6_0;` + and activate your configuration before upgrading again to the default + `mongodb-7_0` supported by `unifi8`. + + For more information, see the MongoDB upgrade notes: + <https://www.mongodb.com/docs/manual/release-notes/7.0-upgrade-standalone/#upgrade-recommendations-and-checklists> + ''; + } + ]; + users.users.unifi = { isSystemUser = true; group = "unifi"; diff --git a/nixos/modules/services/networking/veilid.nix b/nixos/modules/services/networking/veilid.nix new file mode 100644 index 000000000000..d471a5f61952 --- /dev/null +++ b/nixos/modules/services/networking/veilid.nix @@ -0,0 +1,227 @@ +{ + config, + pkgs, + lib, + ... +}: +with lib; +let + cfg = config.services.veilid; + dataDir = "/var/db/veilid-server"; + + settingsFormat = pkgs.formats.yaml { }; + configFile = settingsFormat.generate "veilid-server.conf" cfg.settings; +in +{ + config = mkIf cfg.enable { + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 5150 ]; + allowedUDPPorts = [ 5150 ]; + }; + + # Based on https://gitlab.com/veilid/veilid/-/blob/main/package/systemd/veilid-server.service?ref_type=heads + systemd.services.veilid = { + enable = true; + description = "Veilid Headless Node"; + wants = [ "network-online.target" ]; + before = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ configFile ]; + environment = { + RUST_BACKTRACE = "1"; + }; + serviceConfig = { + ExecStart = "${pkgs.veilid}/bin/veilid-server -c ${configFile}"; + ExecReload = "${pkgs.coreutils}/bin/kill -s HUP $MAINPID"; + KillSignal = "SIGQUIT"; + TimeoutStopSec = 5; + WorkingDirectory = "/"; + User = "veilid"; + Group = "veilid"; + UMask = "0002"; + + CapabilityBoundingSet = ""; + SystemCallFilter = [ "@system-service" ]; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectHome = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ReadWritePaths = dataDir; + + RestrictRealtime = true; + SystemCallArchitectures = "native"; + LockPersonality = true; + RestrictSUIDSGID = true; + }; + }; + users.users.veilid = { + isSystemUser = true; + group = "veilid"; + home = dataDir; + createHome = true; + }; + users.groups.veilid = { }; + + environment = { + systemPackages = [ pkgs.veilid ]; + }; + services.veilid.settings = { }; + }; + + options.services.veilid = { + enable = mkEnableOption "Veilid Headless Node"; + openFirewall = mkOption { + default = false; + type = types.bool; + description = "Whether to open firewall on ports 5150/tcp, 5150/udp"; + }; + settings = mkOption { + description = '' + Build veilid-server.conf with nix expression. + Check <link xlink:href="https://veilid.gitlab.io/developer-book/admin/config.html#configuration-keys">Configuration Keys</link>. + ''; + type = types.submodule { + freeformType = settingsFormat.type; + + options = { + client_api = { + ipc_enabled = mkOption { + type = types.bool; + default = true; + description = "veilid-server will respond to Python and other JSON client requests."; + }; + ipc_directory = mkOption { + type = types.str; + default = "${dataDir}/ipc"; + description = "IPC directory where file sockets are stored."; + }; + }; + logging = { + system = { + enabled = mkOption { + type = types.bool; + default = true; + description = "Events of type 'system' will be logged."; + }; + level = mkOption { + type = types.str; + default = "info"; + example = "debug"; + description = "The minimum priority of system events to be logged."; + }; + }; + terminal = { + enabled = mkOption { + type = types.bool; + default = false; + description = "Events of type 'terminal' will be logged."; + }; + level = mkOption { + type = types.str; + default = "info"; + example = "debug"; + description = "The minimum priority of terminal events to be logged."; + }; + }; + api = { + enabled = mkOption { + type = types.bool; + default = false; + description = "Events of type 'api' will be logged."; + }; + level = mkOption { + type = types.str; + default = "info"; + example = "debug"; + description = "The minimum priority of api events to be logged."; + }; + }; + }; + core = { + capabilities = { + disable = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "APPM" ]; + description = "A list of capabilities to disable (for example, DHTV to say you cannot store DHT information)."; + }; + }; + protected_store = { + allow_insecure_fallback = mkOption { + type = types.bool; + default = true; + description = "If we can't use system-provided secure storage, should we proceed anyway?"; + }; + always_use_insecure_storage = mkOption { + type = types.bool; + default = true; + description = "Should we bypass any attempt to use system-provided secure storage?"; + }; + directory = mkOption { + type = types.str; + default = "${dataDir}/protected_store"; + description = "The filesystem directory to store your protected store in."; + }; + }; + table_store = { + directory = mkOption { + type = types.str; + default = "${dataDir}/table_store"; + description = "The filesystem directory to store your table store within."; + }; + }; + block_store = { + directory = mkOption { + type = types.nullOr types.str; + default = "${dataDir}/block_store"; + description = "The filesystem directory to store blocks for the block store."; + }; + }; + network = { + routing_table = { + bootstrap = mkOption { + type = types.listOf types.str; + default = [ "bootstrap.veilid.net" ]; + description = "Host name of existing well-known Veilid bootstrap servers for the network to connect to."; + }; + node_id = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Base64-encoded public key for the node, used as the node's ID."; + }; + }; + dht = { + min_peer_count = mkOption { + type = types.number; + default = 20; + description = "Minimum number of nodes to keep in the peer table."; + }; + }; + upnp = mkOption { + type = types.bool; + default = true; + description = "Should the app try to improve its incoming network connectivity using UPnP?"; + }; + detect_address_changes = mkOption { + type = types.bool; + default = true; + description = "Should veilid-core detect and notify on network address changes?"; + }; + }; + }; + }; + }; + }; + }; + + meta.maintainers = with maintainers; [ figboy9 ]; +} diff --git a/nixos/modules/services/networking/xl2tpd.nix b/nixos/modules/services/networking/xl2tpd.nix index 8d192be6c2fa..4ecd3c54e412 100644 --- a/nixos/modules/services/networking/xl2tpd.nix +++ b/nixos/modules/services/networking/xl2tpd.nix @@ -104,31 +104,18 @@ with lib; wantedBy = [ "multi-user.target" ]; preStart = '' - mkdir -p -m 700 /etc/xl2tpd + install -m 700 -d /etc/xl2tpd/ppp - pushd /etc/xl2tpd > /dev/null - - mkdir -p -m 700 ppp - - [ -f ppp/chap-secrets ] || cat > ppp/chap-secrets << EOF + [ -f /etc/xl2tpd/ppp/chap-secrets ] || install -m 600 -o root -g root /dev/stdin /etc/xl2tpd/ppp/chap-secrets <<EOF # Secrets for authentication using CHAP # client server secret IP addresses #username xl2tpd password * EOF - chown root:root ppp/chap-secrets - chmod 600 ppp/chap-secrets - # The documentation says this file should be present but doesn't explain why and things work even if not there: - [ -f l2tp-secrets ] || (echo -n "* * "; ${pkgs.apg}/bin/apg -n 1 -m 32 -x 32 -a 1 -M LCN) > l2tp-secrets - chown root:root l2tp-secrets - chmod 600 l2tp-secrets - - popd > /dev/null + [ -f /etc/xl2tpd/l2tp-secrets ] || install -m 600 -o root -g root <(echo -n "* * "; ${pkgs.apg}/bin/apg -n 1 -m 32 -x 32 -a 1 -M LCN) /etc/xl2tpd/l2tp-secrets - mkdir -p /run/xl2tpd - chown root:root /run/xl2tpd - chmod 700 /run/xl2tpd + install -m 701 -o root -g root -d /run/xl2tpd ''; serviceConfig = { diff --git a/nixos/modules/services/networking/zapret.nix b/nixos/modules/services/networking/zapret.nix new file mode 100644 index 000000000000..53309b046931 --- /dev/null +++ b/nixos/modules/services/networking/zapret.nix @@ -0,0 +1,159 @@ +{ + lib, + config, + pkgs, + ... +}: +let + cfg = config.services.zapret; + + whitelist = lib.optionalString ( + cfg.whitelist != null + ) "--hostlist ${pkgs.writeText "zapret-whitelist" (lib.concatStringsSep "\n" cfg.whitelist)}"; + + blacklist = + lib.optionalString (cfg.blacklist != null) + "--hostlist-exclude ${pkgs.writeText "zapret-blacklist" (lib.concatStringsSep "\n" cfg.blacklist)}"; + + ports = if cfg.httpSupport then "80,443" else "443"; +in +{ + options.services.zapret = { + enable = lib.mkEnableOption "the Zapret DPI bypass service."; + package = lib.mkPackageOption pkgs "zapret" { }; + params = lib.mkOption { + default = [ ]; + type = with lib.types; listOf str; + example = '' + [ + "--dpi-desync=fake,disorder2" + "--dpi-desync-ttl=1" + "--dpi-desync-autottl=2" + ]; + ''; + description = '' + Specify the bypass parameters for Zapret binary. + There are no universal parameters as they vary between different networks, so you'll have to find them yourself. + + This can be done by running the `blockcheck` binary from zapret package, i.e. `nix-shell -p zapret --command blockcheck`. + It'll try different params and then tell you which params are working for your network. + ''; + }; + whitelist = lib.mkOption { + default = null; + type = with lib.types; nullOr (listOf str); + example = '' + [ + "youtube.com" + "googlevideo.com" + "ytimg.com" + "youtu.be" + ] + ''; + description = '' + Specify a list of domains to bypass. All other domains will be ignored. + You can specify either whitelist or blacklist, but not both. + If neither are specified, then bypass all domains. + + It is recommended to specify the whitelist. This will make sure that other resources won't be affected by this service. + ''; + }; + blacklist = lib.mkOption { + default = null; + type = with lib.types; nullOr (listOf str); + example = '' + [ + "example.com" + ] + ''; + description = '' + Specify a list of domains NOT to bypass. All other domains will be bypassed. + You can specify either whitelist or blacklist, but not both. + If neither are specified, then bypass all domains. + ''; + }; + qnum = lib.mkOption { + default = 200; + type = lib.types.int; + description = '' + Routing queue number. + Only change this if you already use the default queue number somewhere else. + ''; + }; + configureFirewall = lib.mkOption { + default = true; + type = lib.types.bool; + description = '' + Whether to setup firewall routing so that system http(s) traffic is forwarded via this service. + Disable if you want to set it up manually. + ''; + }; + httpSupport = lib.mkOption { + default = true; + type = lib.types.bool; + description = '' + Whether to route http traffic on port 80. + Http bypass rarely works and you might want to disable it if you don't utilise http connections. + ''; + }; + }; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + assertions = [ + { + assertion = (cfg.whitelist == null) || (cfg.blacklist == null); + message = "Can't specify both whitelist and blacklist."; + } + { + assertion = (builtins.length cfg.params) != 0; + message = "You have to specify zapret parameters. See the params option's description."; + } + ]; + + systemd.services.zapret = { + description = "DPI bypass service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/nfqws --pidfile=/run/nfqws.pid ${lib.concatStringsSep " " cfg.params} ${whitelist} ${blacklist} --qnum=${toString cfg.qnum}"; + Type = "simple"; + PIDFile = "/run/nfqws.pid"; + Restart = "always"; + RuntimeMaxSec = "1h"; # This service loves to crash silently or cause network slowdowns. It also restarts instantly. In my experience restarting it hourly provided the best experience. + + # hardening + DevicePolicy = "closed"; + KeyringMode = "private"; + PrivateTmp = true; + PrivateMounts = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + ProtectProc = "invisible"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + }; + }; + } + + # Route system traffic via service for specified ports. + (lib.mkIf cfg.configureFirewall { + networking.firewall.extraCommands = '' + iptables -t mangle -I POSTROUTING -p tcp -m multiport --dports ${ports} -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num ${toString cfg.qnum} --queue-bypass + ''; + }) + ] + ); + + meta.maintainers = with lib.maintainers; [ + voronind + nishimara + ]; +} diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix index 668bccab2e2d..a1fb0b3951e4 100644 --- a/nixos/modules/services/printing/cupsd.nix +++ b/nixos/modules/services/printing/cupsd.nix @@ -9,7 +9,6 @@ let cfg = config.services.printing; cups = cfg.package; - avahiEnabled = config.services.avahi.enable; polkitEnabled = config.security.polkit.enable; additionalBackends = pkgs.runCommand "additional-cups-backends" { @@ -99,7 +98,7 @@ let cupsdFile (writeConf "client.conf" cfg.clientConf) (writeConf "snmp.conf" cfg.snmpConf) - ] ++ optional avahiEnabled browsedFile + ] ++ optional cfg.browsed.enable browsedFile ++ cfg.drivers; pathsToLink = [ "/etc/cups" ]; ignoreCollisions = true; @@ -185,8 +184,8 @@ in type = types.bool; default = false; description = '' - Whether to open the firewall for TCP/UDP ports specified in - listenAdrresses option. + Whether to open the firewall for TCP ports specified in + listenAddresses option. ''; }; @@ -270,6 +269,15 @@ in ''; }; + browsed.enable = mkOption { + type = types.bool; + default = config.services.avahi.enable; + defaultText = literalExpression "config.services.avahi.enable"; + description = '' + Whether to enable the CUPS Remote Printer Discovery (browsed) daemon. + ''; + }; + browsedConf = mkOption { type = types.lines; default = ""; @@ -339,7 +347,7 @@ in services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper; services.udev.packages = cfg.drivers; - # Allow asswordless printer admin for members of wheel group + # Allow passwordless printer admin for members of wheel group security.polkit.extraConfig = mkIf polkitEnabled '' polkit.addRule(function(action, subject) { if (action.id == "org.opensuse.cupspkhelper.mechanism.all-edit" && @@ -419,7 +427,7 @@ in serviceConfig.PrivateTmp = true; }; - systemd.services.cups-browsed = mkIf avahiEnabled + systemd.services.cups-browsed = mkIf cfg.browsed.enable { description = "CUPS Remote Printer Discovery"; wantedBy = [ "multi-user.target" ]; @@ -485,7 +493,6 @@ in listenPorts = parsePorts cfg.listenAddresses; in mkIf cfg.openFirewall { allowedTCPPorts = listenPorts; - allowedUDPPorts = listenPorts; }; }; diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix index 1cc137341e11..bbd6bde5ebc1 100644 --- a/nixos/modules/services/security/authelia.nix +++ b/nixos/modules/services/security/authelia.nix @@ -308,7 +308,8 @@ in { description = "Authelia authentication and authorization server"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + after = [ "network-online.target" ]; # Checks SMTP notifier creds during startup + wants = [ "network-online.target" ]; environment = (lib.filterAttrs (_: v: v != null) { X_AUTHELIA_CONFIG_FILTERS = lib.mkIf (oidcJwksConfigFile != [ ]) "template"; diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 90b69df2d4a3..2b0929599723 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -165,7 +165,7 @@ in environment.etc."clamav/clamd.conf".source = clamdConfigFile; systemd.slices.system-clamav = { - description = "ClamAV slice"; + description = "ClamAV Antivirus Slice"; }; systemd.services.clamav-daemon = mkIf cfg.daemon.enable { diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix index cf2fffac3f5d..a368b6eee2a6 100644 --- a/nixos/modules/services/security/kanidm.nix +++ b/nixos/modules/services/security/kanidm.nix @@ -198,16 +198,19 @@ let ''; serverPort = + let + address = cfg.serverSettings.bindaddress; + in # ipv6: - if hasInfix "]:" cfg.serverSettings.bindaddress then - last (splitString "]:" cfg.serverSettings.bindaddress) + if hasInfix "]:" address then + last (splitString "]:" address) else # ipv4: - if hasInfix "." cfg.serverSettings.bindaddress then - last (splitString ":" cfg.serverSettings.bindaddress) + if hasInfix "." address then + last (splitString ":" address) # default is 8443 else - "8443"; + throw "Address not parseable as IPv4 nor IPv6."; in { options.services.kanidm = { @@ -225,6 +228,7 @@ in bindaddress = mkOption { description = "Address/port combination the webserver binds to."; example = "[::1]:8443"; + default = "127.0.0.1:8443"; type = types.str; }; # Should be optional but toml does not accept null @@ -979,7 +983,6 @@ in }; meta.maintainers = with lib.maintainers; [ - erictapen Flakebi oddlama ]; diff --git a/nixos/modules/services/security/oauth2-proxy.nix b/nixos/modules/services/security/oauth2-proxy.nix index a897f04ea633..38da06e1538a 100644 --- a/nixos/modules/services/security/oauth2-proxy.nix +++ b/nixos/modules/services/security/oauth2-proxy.nix @@ -62,7 +62,7 @@ let } // lib.optionalAttrs (cfg.passBasicAuth) { basic-auth-password = cfg.basicAuthPassword; } // lib.optionalAttrs (cfg.htpasswd.file != null) { - display-htpasswd-file = cfg.htpasswd.displayForm; + display-htpasswd-form = cfg.htpasswd.displayForm; } // lib.optionalAttrs tls.enable { tls-cert-file = tls.certificate; tls-key-file = tls.key; diff --git a/nixos/modules/services/system/automatic-timezoned.nix b/nixos/modules/services/system/automatic-timezoned.nix index 6150aa22cfbd..50f84f39af7d 100644 --- a/nixos/modules/services/system/automatic-timezoned.nix +++ b/nixos/modules/services/system/automatic-timezoned.nix @@ -16,6 +16,11 @@ in timezone up-to-date based on the current location. It uses geoclue2 to determine the current location and systemd-timedated to actually set the timezone. + + To avoid silent overriding by the service, if you have explicitly set a + timezone, either remove it or ensure that it is set with a lower priority + than the default value using `lib.mkDefault` or `lib.mkOverride`. This is + to make the choice deliberate. An error will be presented otherwise. ''; }; package = mkPackageOption pkgs "automatic-timezoned" { }; @@ -23,6 +28,10 @@ in }; config = mkIf cfg.enable { + # This will give users an error if they have set an explicit time + # zone, rather than having the service silently override it. + time.timeZone = null; + security.polkit.extraConfig = '' polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.timedate1.set-timezone" diff --git a/nixos/modules/services/system/localtimed.nix b/nixos/modules/services/system/localtimed.nix index bd83d227aa35..70d65f4d2bba 100644 --- a/nixos/modules/services/system/localtimed.nix +++ b/nixos/modules/services/system/localtimed.nix @@ -16,6 +16,11 @@ in { Enable `localtimed`, a simple daemon for keeping the system timezone up-to-date based on the current location. It uses geoclue2 to determine the current location. + + To avoid silent overriding by the service, if you have explicitly set a + timezone, either remove it or ensure that it is set with a lower priority + than the default value using `lib.mkDefault` or `lib.mkOverride`. This is + to make the choice deliberate. An error will be presented otherwise. ''; }; package = mkPackageOption pkgs "localtime" { }; @@ -24,6 +29,10 @@ in { }; config = mkIf cfg.enable { + # This will give users an error if they have set an explicit time + # zone, rather than having the service silently override it. + time.timeZone = null; + services.geoclue2.appConfig.localtimed = { isAllowed = true; isSystem = true; diff --git a/nixos/modules/services/system/swapspace.nix b/nixos/modules/services/system/swapspace.nix new file mode 100644 index 000000000000..9dfea492cd67 --- /dev/null +++ b/nixos/modules/services/system/swapspace.nix @@ -0,0 +1,120 @@ +{ + config, + lib, + pkgs, + utils, + ... +}: +let + cfg = config.services.swapspace; + inherit (lib) + types + mkOption + mkPackageOption + mkEnableOption + ; + configFile = pkgs.writeText "swapspace.conf" (lib.generators.toKeyValue { } cfg.settings); +in +{ + options.services.swapspace = { + enable = mkEnableOption "Swapspace, a dynamic swap space manager"; + package = mkPackageOption pkgs "swapspace" { }; + extraArgs = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "-P" + "-v" + ]; + description = "Any extra arguments to pass to swapspace"; + }; + settings = mkOption { + type = types.submodule { + options = { + swappath = mkOption { + type = types.str; + default = "/var/lib/swapspace"; + description = "Location where swapspace may create and delete swapfiles"; + }; + lower_freelimit = mkOption { + type = types.ints.between 0 99; + default = 20; + description = "Lower free-space threshold: if the percentage of free space drops below this number, additional swapspace is allocated"; + }; + upper_freelimit = mkOption { + type = types.ints.between 0 100; + default = 60; + description = "Upper free-space threshold: if the percentage of free space exceeds this number, swapspace will attempt to free up swapspace"; + }; + freetarget = mkOption { + type = types.ints.between 2 99; + default = 30; + description = '' + Percentage of free space swapspace should aim for when adding swapspace. + This should fall somewhere between lower_freelimit and upper_freelimit. + ''; + }; + min_swapsize = mkOption { + type = types.str; + default = "4m"; + description = "Smallest allowed size for individual swapfiles"; + }; + max_swapsize = mkOption { + type = types.str; + default = "2t"; + description = "Greatest allowed size for individual swapfiles"; + }; + cooldown = mkOption { + type = types.ints.unsigned; + default = 600; + description = '' + Duration (roughly in seconds) of the moratorium on swap allocation that is instated if disk space runs out, or the cooldown time after a new swapfile is successfully allocated before swapspace will consider deallocating swap space again. + The default cooldown period is about 10 minutes. + ''; + }; + buffer_elasticity = mkOption { + type = types.ints.between 0 100; + default = 30; + description = ''Percentage of buffer space considered to be "free"''; + }; + cache_elasticity = mkOption { + type = types.ints.between 0 100; + default = 80; + description = ''Percentage of cache space considered to be "free"''; + }; + }; + }; + default = { }; + description = '' + Config file for swapspace. + See the options here: <https://github.com/Tookmund/Swapspace/blob/master/swapspace.conf> + ''; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + systemd.packages = [ cfg.package ]; + systemd.services.swapspace = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = [ + "" + "${lib.getExe cfg.package} -c ${configFile} ${utils.escapeSystemdExecArgs cfg.extraArgs}" + ]; + }; + }; + systemd.tmpfiles.settings.swapspace = { + ${cfg.settings.swappath}.d = { + mode = "0700"; + }; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ + Luflosi + phanirithvij + ]; + }; +} diff --git a/nixos/modules/services/system/userborn.nix b/nixos/modules/services/system/userborn.nix index 07e8be34266e..bd3f2175b128 100644 --- a/nixos/modules/services/system/userborn.nix +++ b/nixos/modules/services/system/userborn.nix @@ -100,7 +100,7 @@ in lib.nameValuePair (toString opts.home) { d = { mode = opts.homeMode; - user = username; + user = opts.name; inherit (opts) group; }; } diff --git a/nixos/modules/services/torrent/bitmagnet.nix b/nixos/modules/services/torrent/bitmagnet.nix new file mode 100644 index 000000000000..c0929b69f451 --- /dev/null +++ b/nixos/modules/services/torrent/bitmagnet.nix @@ -0,0 +1,194 @@ +{ + pkgs, + config, + lib, + ... +}: +let + cfg = config.services.bitmagnet; + inherit (lib) + mkEnableOption + mkIf + mkOption + mkPackageOption + optional + ; + inherit (lib.types) + bool + int + port + str + submodule + ; + inherit (lib.generators) toYAML; + + freeformType = (pkgs.formats.yaml { }).type; +in +{ + options.services.bitmagnet = { + enable = mkEnableOption "Bitmagnet service"; + useLocalPostgresDB = mkOption { + description = "Use a local postgresql database, create user and database"; + type = bool; + default = true; + }; + settings = mkOption { + description = "Bitmagnet configuration (https://bitmagnet.io/setup/configuration.html)."; + default = { }; + type = submodule { + inherit freeformType; + options = { + http_server = mkOption { + default = { }; + description = "HTTP server settings"; + type = submodule { + inherit freeformType; + options = { + port = mkOption { + type = str; + default = ":3333"; + description = "HTTP server listen port"; + }; + }; + }; + }; + dht_server = mkOption { + default = { }; + description = "DHT server settings"; + type = submodule { + inherit freeformType; + options = { + port = mkOption { + type = port; + default = 3334; + description = "DHT listen port"; + }; + }; + }; + }; + postgres = mkOption { + default = { }; + description = "PostgreSQL database configuration"; + type = submodule { + inherit freeformType; + options = { + host = mkOption { + type = str; + default = ""; + description = "Address, hostname or Unix socket path of the database server"; + }; + name = mkOption { + type = str; + default = "bitmagnet"; + description = "Database name to connect to"; + }; + user = mkOption { + type = str; + default = ""; + description = "User to connect as"; + }; + password = mkOption { + type = str; + default = ""; + description = "Password for database user"; + }; + }; + }; + }; + }; + }; + }; + package = mkPackageOption pkgs "bitmagnet" { }; + user = mkOption { + description = "User running bitmagnet"; + type = str; + default = "bitmagnet"; + }; + group = mkOption { + description = "Group of user running bitmagnet"; + type = str; + default = "bitmagnet"; + }; + openFirewall = mkOption { + description = "Open DHT ports in firewall"; + type = bool; + default = false; + }; + }; + config = mkIf cfg.enable { + environment.etc."xdg/bitmagnet/config.yml" = { + text = toYAML { } cfg.settings; + mode = "0440"; + user = cfg.user; + group = cfg.group; + }; + systemd.services.bitmagnet = { + enable = true; + wantedBy = [ "multi-user.target" ]; + after = [ + "network.target" + ] ++ optional cfg.useLocalPostgresDB "postgresql.service"; + requires = optional cfg.useLocalPostgresDB "postgresql.service"; + serviceConfig = { + Type = "simple"; + DynamicUser = true; + User = cfg.user; + Group = cfg.group; + ExecStart = "${cfg.package}/bin/bitmagnet worker run --all"; + Restart = "on-failure"; + WorkingDirectory = "/var/lib/bitmagnet"; + StateDirectory = "bitmagnet"; + + # Sandboxing (sorted by occurrence in https://www.freedesktop.org/software/systemd/man/systemd.exec.html) + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + }; + }; + users.users = mkIf (cfg.user == "bitmagnet") { + bitmagnet = { + group = cfg.group; + isSystemUser = true; + }; + }; + users.groups = mkIf (cfg.group == "bitmagnet") { bitmagnet = { }; }; + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.settings.dht_server.port ]; + allowedUDPPorts = [ cfg.settings.dht_server.port ]; + }; + services.postgresql = mkIf cfg.useLocalPostgresDB { + enable = true; + ensureDatabases = [ + cfg.settings.postgres.name + (if (cfg.settings.postgres.user == "") then cfg.user else cfg.settings.postgres.user) + ]; + ensureUsers = [ + { + name = if (cfg.settings.postgres.user == "") then cfg.user else cfg.settings.postgres.user; + ensureDBOwnership = true; + } + ]; + }; + }; + + meta.maintainers = with lib.maintainers; [ gileri ]; +} diff --git a/nixos/modules/services/ttys/getty.nix b/nixos/modules/services/ttys/getty.nix index e88bb4628635..fd9bd7aca019 100644 --- a/nixos/modules/services/ttys/getty.nix +++ b/nixos/modules/services/ttys/getty.nix @@ -7,14 +7,26 @@ let baseArgs = [ "--login-program" "${cfg.loginProgram}" - ] ++ optionals (cfg.autologinUser != null) [ + ] ++ optionals (cfg.autologinUser != null && !cfg.autologinOnce) [ "--autologin" cfg.autologinUser ] ++ optionals (cfg.loginOptions != null) [ "--login-options" cfg.loginOptions ] ++ cfg.extraArgs; gettyCmd = args: - "@${pkgs.util-linux}/sbin/agetty agetty ${escapeShellArgs baseArgs} ${args}"; + "${lib.getExe' pkgs.util-linux "agetty"} ${escapeShellArgs baseArgs} ${args}"; + + autologinScript = '' + otherArgs="--noclear --keep-baud $TTY 115200,38400,9600 $TERM"; + ${lib.optionalString cfg.autologinOnce '' + autologged="/run/agetty.autologged" + if test "$TTY" = tty1 && ! test -f "$autologged"; then + touch "$autologged" + exec ${gettyCmd "$otherArgs --autologin ${cfg.autologinUser}"} + fi + ''} + exec ${gettyCmd "$otherArgs"} + ''; in @@ -40,6 +52,16 @@ in ''; }; + autologinOnce = mkOption { + type = types.bool; + default = false; + description = '' + If enabled the automatic login will only happen in the first tty + once per boot. This can be useful to avoid retyping the account + password on systems with full disk encrypted. + ''; + }; + loginProgram = mkOption { type = types.path; default = "${pkgs.shadow}/bin/login"; @@ -106,9 +128,11 @@ in systemd.services."getty@" = { serviceConfig.ExecStart = [ - "" # override upstream default with an empty ExecStart - (gettyCmd "--noclear --keep-baud %I 115200,38400,9600 $TERM") + # override upstream default with an empty ExecStart + "" + (pkgs.writers.writeDash "getty" autologinScript) ]; + environment.TTY = "%I"; restartIfChanged = false; }; diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix index 5c259b7fa14d..72e96df5442e 100644 --- a/nixos/modules/services/video/frigate.nix +++ b/nixos/modules/services/video/frigate.nix @@ -518,13 +518,15 @@ in libva-utils procps radeontop - ] ++ lib.optionals (!stdenv.isAarch64) [ + ] ++ lib.optionals (!stdenv.hostPlatform.isAarch64) [ # not available on aarch64-linux intel-gpu-tools ]; serviceConfig = { + ExecStartPre = "-rm /var/cache/frigate/*.mp4"; ExecStart = "${cfg.package.python.interpreter} -m frigate"; Restart = "on-failure"; + SyslogIdentifier = "frigate"; User = "frigate"; Group = "frigate"; diff --git a/nixos/modules/services/video/go2rtc/default.nix b/nixos/modules/services/video/go2rtc/default.nix index 3121ce4737c7..f74c172907fe 100644 --- a/nixos/modules/services/video/go2rtc/default.nix +++ b/nixos/modules/services/video/go2rtc/default.nix @@ -55,8 +55,8 @@ in ffmpeg = { bin = mkOption { type = path; - default = lib.getExe pkgs.ffmpeg_7-headless; - defaultText = literalExpression "lib.getExe pkgs.ffmpeg_7-headless"; + default = lib.getExe pkgs.ffmpeg-headless; + defaultText = literalExpression "lib.getExe pkgs.ffmpeg-headless"; description = '' The ffmpeg package to use for transcoding. ''; diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix deleted file mode 100644 index 99c04bafd141..000000000000 --- a/nixos/modules/services/video/unifi-video.nix +++ /dev/null @@ -1,252 +0,0 @@ -{ config, lib, options, pkgs, utils, ... }: -with lib; -let - cfg = config.services.unifi-video; - opt = options.services.unifi-video; - mainClass = "com.ubnt.airvision.Main"; - cmd = '' - ${pkgs.jsvc}/bin/jsvc \ - -cwd ${stateDir} \ - -debug \ - -verbose:class \ - -nodetach \ - -user unifi-video \ - -home ${cfg.jrePackage}/lib/openjdk \ - -cp ${pkgs.commonsDaemon}/share/java/commons-daemon-1.2.4.jar:${stateDir}/lib/airvision.jar \ - -pidfile ${cfg.pidFile} \ - -procname unifi-video \ - -Djava.security.egd=file:/dev/./urandom \ - -Xmx${toString cfg.maximumJavaHeapSize}M \ - -Xss512K \ - -XX:+UseG1GC \ - -XX:+UseStringDeduplication \ - -XX:MaxMetaspaceSize=768M \ - -Djava.library.path=${stateDir}/lib \ - -Djava.awt.headless=true \ - -Djavax.net.ssl.trustStore=${stateDir}/etc/ufv-truststore \ - -Dfile.encoding=UTF-8 \ - -Dav.tempdir=/var/cache/unifi-video - ''; - - mongoConf = pkgs.writeTextFile { - name = "mongo.conf"; - executable = false; - text = '' - # for documentation of all options, see https://www.mongodb.com/docs/manual/reference/configuration-options/ - - storage: - dbPath: ${cfg.dataDir}/db - journal: - enabled: true - syncPeriodSecs: 60 - - systemLog: - destination: file - logAppend: true - path: ${stateDir}/logs/mongod.log - - net: - port: 7441 - bindIp: 127.0.0.1 - http: - enabled: false - - operationProfiling: - slowOpThresholdMs: 500 - mode: off - ''; - }; - - - mongoWtConf = pkgs.writeTextFile { - name = "mongowt.conf"; - executable = false; - text = '' - # for documentation of all options, see: - # https://www.mongodb.com/docs/manual/reference/configuration-options/ - - storage: - dbPath: ${cfg.dataDir}/db-wt - journal: - enabled: true - wiredTiger: - engineConfig: - cacheSizeGB: 1 - - systemLog: - destination: file - logAppend: true - path: logs/mongod.log - - net: - port: 7441 - bindIp: 127.0.0.1 - - operationProfiling: - slowOpThresholdMs: 500 - mode: off - ''; - }; - - stateDir = "/var/lib/unifi-video"; - -in -{ - - options.services.unifi-video = { - - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether or not to enable the unifi-video service. - ''; - }; - - jrePackage = mkPackageOption pkgs "jre8" { }; - - unifiVideoPackage = mkPackageOption pkgs "unifi-video" { }; - - mongodbPackage = mkPackageOption pkgs "mongodb" { - default = "mongodb-5_0"; - }; - - logDir = mkOption { - type = types.str; - default = "${stateDir}/logs"; - description = '' - Where to store the logs. - ''; - }; - - dataDir = mkOption { - type = types.str; - default = "${stateDir}/data"; - description = '' - Where to store the database and other data. - ''; - }; - - openFirewall = mkOption { - type = types.bool; - default = false; - description = '' - Whether or not to open the required ports on the firewall. - ''; - }; - - maximumJavaHeapSize = mkOption { - type = types.nullOr types.int; - default = 1024; - example = 4096; - description = '' - Set the maximum heap size for the JVM in MB. - ''; - }; - - pidFile = mkOption { - type = types.path; - default = "${cfg.dataDir}/unifi-video.pid"; - defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"''; - description = "Location of unifi-video pid file."; - }; - - }; - - config = mkIf cfg.enable { - - warnings = optional - (options.services.unifi-video.openFirewall.highestPrio >= (mkOptionDefault null).priority) - "The current services.unifi-video.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning."; - - users.users.unifi-video = { - description = "UniFi Video controller daemon user"; - home = stateDir; - group = "unifi-video"; - isSystemUser = true; - }; - users.groups.unifi-video = {}; - - networking.firewall = mkIf cfg.openFirewall { - # https://help.ui.com/hc/en-us/articles/217875218-UniFi-Video-Ports-Used - allowedTCPPorts = [ - 7080 # HTTP portal - 7443 # HTTPS portal - 7445 # Video over HTTP (mobile app) - 7446 # Video over HTTPS (mobile app) - 7447 # RTSP via the controller - 7442 # Camera management from cameras to NVR over WAN - ]; - allowedUDPPorts = [ - 6666 # Inbound camera streams sent over WAN - ]; - }; - - systemd.tmpfiles.rules = [ - "d '${stateDir}' 0700 unifi-video unifi-video - -" - "d '/var/cache/unifi-video' 0700 unifi-video unifi-video - -" - - "d '${stateDir}/logs' 0700 unifi-video unifi-video - -" - "C '${stateDir}/etc' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/etc" - "C '${stateDir}/webapps' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/webapps" - "C '${stateDir}/email' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/email" - "C '${stateDir}/fw' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/fw" - "C '${stateDir}/lib' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/lib" - - "d '${stateDir}/data' 0700 unifi-video unifi-video - -" - "d '${stateDir}/data/db' 0700 unifi-video unifi-video - -" - "C '${stateDir}/data/system.properties' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/etc/system.properties" - - "d '${stateDir}/bin' 0700 unifi-video unifi-video - -" - "f '${stateDir}/bin/evostreamms' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/evostreamms" - "f '${stateDir}/bin/libavcodec.so.54' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/libavcodec.so.54" - "f '${stateDir}/bin/libavformat.so.54' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/libavformat.so.54" - "f '${stateDir}/bin/libavutil.so.52' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/libavutil.so.52" - "f '${stateDir}/bin/ubnt.avtool' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/ubnt.avtool" - "f '${stateDir}/bin/ubnt.updater' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/ubnt.updater" - "C '${stateDir}/bin/mongo' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongo" - "C '${stateDir}/bin/mongod' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongod" - "C '${stateDir}/bin/mongoperf' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongoperf" - "C '${stateDir}/bin/mongos' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongos" - - "d '${stateDir}/conf' 0700 unifi-video unifi-video - -" - "C '${stateDir}/conf/evostream' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/evostream" - "Z '${stateDir}/conf/evostream' 0700 unifi-video unifi-video - -" - "L+ '${stateDir}/conf/mongodv3.0+.conf' 0700 unifi-video unifi-video - ${mongoConf}" - "L+ '${stateDir}/conf/mongodv3.6+.conf' 0700 unifi-video unifi-video - ${mongoConf}" - "L+ '${stateDir}/conf/mongod-wt.conf' 0700 unifi-video unifi-video - ${mongoWtConf}" - "L+ '${stateDir}/conf/catalina.policy' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/catalina.policy" - "L+ '${stateDir}/conf/catalina.properties' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/catalina.properties" - "L+ '${stateDir}/conf/context.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/context.xml" - "L+ '${stateDir}/conf/logging.properties' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/logging.properties" - "L+ '${stateDir}/conf/server.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/server.xml" - "L+ '${stateDir}/conf/tomcat-users.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/tomcat-users.xml" - "L+ '${stateDir}/conf/web.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/web.xml" - ]; - - systemd.services.unifi-video = { - description = "UniFi Video NVR daemon"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ] ; - unitConfig.RequiresMountsFor = stateDir; - # Make sure package upgrades trigger a service restart - restartTriggers = [ cfg.unifiVideoPackage cfg.mongodbPackage ]; - path = with pkgs; [ gawk coreutils busybox which jre8 lsb-release libcap util-linux ]; - serviceConfig = { - Type = "simple"; - ExecStart = "${(removeSuffix "\n" cmd)} ${mainClass} start"; - ExecStop = "${(removeSuffix "\n" cmd)} stop ${mainClass} stop"; - Restart = "on-failure"; - UMask = "0077"; - User = "unifi-video"; - WorkingDirectory = "${stateDir}"; - }; - }; - }; - - imports = [ - (mkRenamedOptionModule [ "services" "unifi-video" "openPorts" ] [ "services" "unifi-video" "openFirewall" ]) - ]; - - meta.maintainers = with lib.maintainers; [ rsynnest ]; -} diff --git a/nixos/modules/services/video/wivrn.nix b/nixos/modules/services/video/wivrn.nix new file mode 100644 index 000000000000..f571832053ad --- /dev/null +++ b/nixos/modules/services/video/wivrn.nix @@ -0,0 +1,226 @@ +{ + config, + pkgs, + lib, + ... +}: +let + inherit (lib) + mkIf + mkEnableOption + mkPackageOption + mkOption + optionalString + optionalAttrs + isDerivation + recursiveUpdate + getExe + literalExpression + types + maintainers + ; + cfg = config.services.wivrn; + configFormat = pkgs.formats.json { }; + + # For the application option to work with systemd PATH, we find the store binary path of + # the package, concat all of the following strings, and then update the application attribute. + # Application can either be a package or a list that has a package as the first element. + applicationExists = builtins.hasAttr "application" cfg.config.json; + applicationListNotEmpty = ( + if builtins.isList cfg.config.json.application then + (builtins.length cfg.config.json.application) != 0 + else + true + ); + applicationCheck = applicationExists && applicationListNotEmpty; + + applicationBinary = ( + if builtins.isList cfg.config.json.application then + builtins.head cfg.config.json.application + else + cfg.config.json.application + ); + applicationStrings = builtins.tail cfg.config.json.application; + + applicationPath = mkIf applicationCheck applicationBinary; + + applicationConcat = ( + if builtins.isList cfg.config.json.application then + builtins.concatStringsSep " " ([ (getExe applicationBinary) ] ++ applicationStrings) + else + (getExe applicationBinary) + ); + applicationUpdate = recursiveUpdate cfg.config.json ( + optionalAttrs applicationCheck { application = applicationConcat; } + ); + configFile = configFormat.generate "config.json" applicationUpdate; +in +{ + options = { + services.wivrn = { + enable = mkEnableOption "WiVRn, an OpenXR streaming application"; + + package = mkPackageOption pkgs "wivrn" { }; + + openFirewall = mkEnableOption "the default ports in the firewall for the WiVRn server"; + + defaultRuntime = mkEnableOption '' + WiVRn Monado as the default OpenXR runtime on the system. + The config can be found at `/etc/xdg/openxr/1/active_runtime.json`. + + Note that applications can bypass this option by setting an active + runtime in a writable XDG_CONFIG_DIRS location like `~/.config` + ''; + + autoStart = mkEnableOption "starting the service by default"; + + monadoEnvironment = mkOption { + type = types.attrs; + description = "Environment variables to be passed to the Monado environment."; + default = { + XRT_COMPOSITOR_LOG = "debug"; + XRT_PRINT_OPTIONS = "on"; + IPC_EXIT_ON_DISCONNECT = "off"; + }; + }; + + extraPackages = mkOption { + type = types.listOf types.package; + description = "Packages to add to the wivrn-application service $PATH."; + default = [ ]; + example = literalExpression "[ pkgs.bash pkgs.procps ]"; + }; + + config = { + enable = mkEnableOption "configuration for WiVRn"; + json = mkOption { + type = configFormat.type; + description = '' + Configuration for WiVRn. The attributes are serialized to JSON in config.json. + + Note that the application option must be either a package or a + list with package as the first element. + + See https://github.com/Meumeu/WiVRn/blob/master/docs/configuration.md + ''; + default = { }; + example = literalExpression '' + { + scale = 0.8; + bitrate = 100000000; + encoders = [ + { + encoder = "nvenc"; + codec = "h264"; + width = 1.0; + height = 1.0; + offset_x = 0.0; + offset_y = 0.0; + } + ]; + application = [ pkgs.wlx-overlay-s ]; + tcp_only = true; + } + ''; + }; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !applicationCheck || isDerivation applicationBinary; + message = "The application in WiVRn configuration is not a package. Please ensure that the application is a package or that a package is the first element in the list."; + } + ]; + + systemd.user = { + services = { + # The WiVRn server runs in a hardened service and starts the applications in a different service + wivrn = { + description = "WiVRn XR runtime service"; + environment = { + # Default options + # https://gitlab.freedesktop.org/monado/monado/-/blob/598080453545c6bf313829e5780ffb7dde9b79dc/src/xrt/targets/service/monado.in.service#L12 + XRT_COMPOSITOR_LOG = "debug"; + XRT_PRINT_OPTIONS = "on"; + IPC_EXIT_ON_DISCONNECT = "off"; + } // cfg.monadoEnvironment; + serviceConfig = { + ExecStart = ( + (getExe cfg.package) + " --systemd" + optionalString cfg.config.enable " -f ${configFile}" + ); + # Hardening options + CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; + AmbientCapabilities = [ "CAP_SYS_NICE" ]; + LockPersonality = true; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictSUIDSGID = true; + }; + wantedBy = mkIf cfg.autoStart [ "default.target" ]; + restartTriggers = [ + cfg.package + configFile + ]; + }; + wivrn-application = mkIf applicationCheck { + description = "WiVRn application service"; + requires = [ "wivrn.service" ]; + serviceConfig = { + ExecStart = ( + (getExe cfg.package) + " --application" + optionalString cfg.config.enable " -f ${configFile}" + ); + Restart = "on-failure"; + RestartSec = 0; + PrivateTmp = true; + }; + # We need to add the application to PATH so WiVRn can find it + path = [ applicationPath ] ++ cfg.extraPackages; + }; + }; + }; + + services = { + # WiVRn can be used with some wired headsets so we include xr-hardware + udev.packages = with pkgs; [ + android-udev-rules + xr-hardware + ]; + avahi = { + enable = true; + publish = { + enable = true; + userServices = true; + }; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 9757 ]; + allowedUDPPorts = [ 9757 ]; + }; + + environment = { + systemPackages = [ + cfg.package + applicationPath + ]; + pathsToLink = [ "/share/openxr" ]; + etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime { + source = "${cfg.package}/share/openxr/1/openxr_wivrn.json"; + }; + }; + }; + meta.maintainers = with maintainers; [ passivelemon ]; +} diff --git a/nixos/modules/services/wayland/hypridle.nix b/nixos/modules/services/wayland/hypridle.nix index ee35812fd440..83a081aa6741 100644 --- a/nixos/modules/services/wayland/hypridle.nix +++ b/nixos/modules/services/wayland/hypridle.nix @@ -17,7 +17,15 @@ in config = lib.mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; - systemd.packages = [ cfg.package ]; + systemd = { + packages = [ cfg.package ]; + user.services.hypridle.wantedBy = [ "graphical-session.target" ]; + user.services.hypridle.path = [ + config.programs.hyprland.package + config.programs.hyprlock.package + pkgs.procps + ]; + }; }; meta.maintainers = with lib.maintainers; [ johnrtitor ]; diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix deleted file mode 100644 index 683a1c7603ef..000000000000 --- a/nixos/modules/services/web-apps/atlassian/confluence.nix +++ /dev/null @@ -1,224 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.confluence; - - pkg = cfg.package.override (optionalAttrs cfg.sso.enable { - enableSSO = cfg.sso.enable; - }); - - crowdProperties = pkgs.writeText "crowd.properties" '' - application.name ${cfg.sso.applicationName} - application.password ${if cfg.sso.applicationPassword != null then cfg.sso.applicationPassword else "@NIXOS_CONFLUENCE_CROWD_SSO_PWD@"} - application.login.url ${cfg.sso.crowd}/console/ - - crowd.server.url ${cfg.sso.crowd}/services/ - crowd.base.url ${cfg.sso.crowd}/ - - session.isauthenticated session.isauthenticated - session.tokenkey session.tokenkey - session.validationinterval ${toString cfg.sso.validationInterval} - session.lastvalidation session.lastvalidation - ''; - -in - -{ - options = { - services.confluence = { - enable = mkEnableOption "Atlassian Confluence service"; - - user = mkOption { - type = types.str; - default = "confluence"; - description = "User which runs confluence."; - }; - - group = mkOption { - type = types.str; - default = "confluence"; - description = "Group which runs confluence."; - }; - - home = mkOption { - type = types.str; - default = "/var/lib/confluence"; - description = "Home directory of the confluence instance."; - }; - - listenAddress = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Address to listen on."; - }; - - listenPort = mkOption { - type = types.port; - default = 8090; - description = "Port to listen on."; - }; - - catalinaOptions = mkOption { - type = types.listOf types.str; - default = []; - example = [ "-Xms1024m" "-Xmx2048m" "-Dconfluence.disable.peopledirectory.all=true" ]; - description = "Java options to pass to catalina/tomcat."; - }; - - proxy = { - enable = mkEnableOption "proxy support"; - - name = mkOption { - type = types.str; - example = "confluence.example.com"; - description = "Virtual hostname at the proxy"; - }; - - port = mkOption { - type = types.port; - default = 443; - example = 80; - description = "Port used at the proxy"; - }; - - scheme = mkOption { - type = types.str; - default = "https"; - example = "http"; - description = "Protocol used at the proxy."; - }; - }; - - sso = { - enable = mkEnableOption "SSO with Atlassian Crowd"; - - crowd = mkOption { - type = types.str; - example = "http://localhost:8095/crowd"; - description = "Crowd Base URL without trailing slash"; - }; - - applicationName = mkOption { - type = types.str; - example = "jira"; - description = "Exact name of this Confluence instance in Crowd"; - }; - - applicationPassword = mkOption { - type = types.nullOr types.str; - default = null; - description = "Application password of this Confluence instance in Crowd"; - }; - - applicationPasswordFile = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the application password for Crowd of Confluence."; - }; - - validationInterval = mkOption { - type = types.int; - default = 2; - example = 0; - description = '' - Set to 0, if you want authentication checks to occur on each - request. Otherwise set to the number of minutes between request - to validate if the user is logged in or out of the Crowd SSO - server. Setting this value to 1 or higher will increase the - performance of Crowd's integration. - ''; - }; - }; - - package = mkPackageOption pkgs "atlassian-confluence" { }; - - jrePackage = mkPackageOption pkgs "oraclejre8" { - extraDescription = '' - ::: {.note } - Atlassian only supports the Oracle JRE (JRASERVER-46152). - ::: - ''; - }; - }; - }; - - config = mkIf cfg.enable { - users.users.${cfg.user} = { - isSystemUser = true; - group = cfg.group; - }; - - assertions = [ - { assertion = cfg.sso.enable -> ((cfg.sso.applicationPassword == null) != (cfg.sso.applicationPasswordFile)); - message = "Please set either applicationPassword or applicationPasswordFile"; - } - ]; - - warnings = mkIf (cfg.sso.enable && cfg.sso.applicationPassword != null) [ - "Using `services.confluence.sso.applicationPassword` is deprecated! Use `applicationPasswordFile` instead!" - ]; - - users.groups.${cfg.group} = {}; - - systemd.tmpfiles.rules = [ - "d '${cfg.home}' - ${cfg.user} - - -" - "d /run/confluence - - - - -" - - "L+ /run/confluence/home - - - - ${cfg.home}" - "L+ /run/confluence/logs - - - - ${cfg.home}/logs" - "L+ /run/confluence/temp - - - - ${cfg.home}/temp" - "L+ /run/confluence/work - - - - ${cfg.home}/work" - "L+ /run/confluence/server.xml - - - - ${cfg.home}/server.xml" - ]; - - systemd.services.confluence = { - description = "Atlassian Confluence"; - - wantedBy = [ "multi-user.target" ]; - requires = [ "postgresql.service" ]; - after = [ "postgresql.service" ]; - - path = [ cfg.jrePackage pkgs.bash ]; - - environment = { - CONF_USER = cfg.user; - JAVA_HOME = "${cfg.jrePackage}"; - CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions; - JAVA_OPTS = mkIf cfg.sso.enable "-Dcrowd.properties=${cfg.home}/crowd.properties"; - }; - - preStart = '' - mkdir -p ${cfg.home}/{logs,work,temp,deploy} - - sed -e 's,port="8090",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \ - '' + (lib.optionalString cfg.proxy.enable '' - -e 's,protocol="org.apache.coyote.http11.Http11NioProtocol",protocol="org.apache.coyote.http11.Http11NioProtocol" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}",' \ - '') + '' - ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml - - ${optionalString cfg.sso.enable '' - install -m660 ${crowdProperties} ${cfg.home}/crowd.properties - ${optionalString (cfg.sso.applicationPasswordFile != null) '' - ${pkgs.replace-secret}/bin/replace-secret \ - '@NIXOS_CONFLUENCE_CROWD_SSO_PWD@' \ - ${cfg.sso.applicationPasswordFile} \ - ${cfg.home}/crowd.properties - ''} - ''} - ''; - - serviceConfig = { - User = cfg.user; - Group = cfg.group; - PrivateTmp = true; - Restart = "on-failure"; - RestartSec = "10"; - ExecStart = "${pkg}/bin/start-confluence.sh -fg"; - ExecStop = "${pkg}/bin/stop-confluence.sh"; - }; - }; - }; -} diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix deleted file mode 100644 index 527fa1743df2..000000000000 --- a/nixos/modules/services/web-apps/atlassian/crowd.nix +++ /dev/null @@ -1,193 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.crowd; - - pkg = cfg.package.override { - home = cfg.home; - port = cfg.listenPort; - openidPassword = cfg.openidPassword; - } // (optionalAttrs cfg.proxy.enable { - proxyUrl = "${cfg.proxy.scheme}://${cfg.proxy.name}:${toString cfg.proxy.port}"; - }); - - crowdPropertiesFile = pkgs.writeText "crowd.properties" '' - application.name crowd-openid-server - application.password @NIXOS_CROWD_OPENID_PW@ - application.base.url http://localhost:${toString cfg.listenPort}/openidserver - application.login.url http://localhost:${toString cfg.listenPort}/openidserver - application.login.url.template http://localhost:${toString cfg.listenPort}/openidserver?returnToUrl=''${RETURN_TO_URL} - - crowd.server.url http://localhost:${toString cfg.listenPort}/crowd/services/ - - session.isauthenticated session.isauthenticated - session.tokenkey session.tokenkey - session.validationinterval 0 - session.lastvalidation session.lastvalidation - ''; - -in - -{ - options = { - services.crowd = { - enable = mkEnableOption "Atlassian Crowd service"; - - user = mkOption { - type = types.str; - default = "crowd"; - description = "User which runs Crowd."; - }; - - group = mkOption { - type = types.str; - default = "crowd"; - description = "Group which runs Crowd."; - }; - - home = mkOption { - type = types.str; - default = "/var/lib/crowd"; - description = "Home directory of the Crowd instance."; - }; - - listenAddress = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Address to listen on."; - }; - - listenPort = mkOption { - type = types.port; - default = 8092; - description = "Port to listen on."; - }; - - openidPassword = mkOption { - type = types.str; - default = "WILL_NEVER_BE_SET"; - description = "Application password for OpenID server."; - }; - - openidPasswordFile = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the file containing the application password for OpenID server."; - }; - - catalinaOptions = mkOption { - type = types.listOf types.str; - default = []; - example = [ "-Xms1024m" "-Xmx2048m" ]; - description = "Java options to pass to catalina/tomcat."; - }; - - proxy = { - enable = mkEnableOption "reverse proxy support"; - - name = mkOption { - type = types.str; - example = "crowd.example.com"; - description = "Virtual hostname at the proxy"; - }; - - port = mkOption { - type = types.port; - default = 443; - example = 80; - description = "Port used at the proxy"; - }; - - scheme = mkOption { - type = types.str; - default = "https"; - example = "http"; - description = "Protocol used at the proxy."; - }; - - secure = mkOption { - type = types.bool; - default = true; - description = "Whether the connections to the proxy should be considered secure."; - }; - }; - - package = mkPackageOption pkgs "atlassian-crowd" { }; - - jrePackage = mkPackageOption pkgs "oraclejre8" { - extraDescription = '' - ::: {.note } - Atlassian only supports the Oracle JRE (JRASERVER-46152). - ::: - ''; - }; - }; - }; - - config = mkIf cfg.enable { - users.users.${cfg.user} = { - isSystemUser = true; - group = cfg.group; - }; - - users.groups.${cfg.group} = {}; - - systemd.tmpfiles.rules = [ - "d '${cfg.home}' - ${cfg.user} ${cfg.group} - -" - "d /run/atlassian-crowd - - - - -" - - "L+ /run/atlassian-crowd/database - - - - ${cfg.home}/database" - "L+ /run/atlassian-crowd/logs - - - - ${cfg.home}/logs" - "L+ /run/atlassian-crowd/work - - - - ${cfg.home}/work" - "L+ /run/atlassian-crowd/server.xml - - - - ${cfg.home}/server.xml" - ]; - - systemd.services.atlassian-crowd = { - description = "Atlassian Crowd"; - - wantedBy = [ "multi-user.target" ]; - requires = [ "postgresql.service" ]; - after = [ "postgresql.service" ]; - - path = [ cfg.jrePackage ]; - - environment = { - JAVA_HOME = "${cfg.jrePackage}"; - CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions; - CATALINA_TMPDIR = "/tmp"; - JAVA_OPTS = mkIf (cfg.openidPasswordFile != null) "-Dcrowd.properties=${cfg.home}/crowd.properties"; - }; - - preStart = '' - rm -rf ${cfg.home}/work - mkdir -p ${cfg.home}/{logs,database,work} - - sed -e 's,port="8095",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \ - '' + (lib.optionalString cfg.proxy.enable '' - -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \ - '') + '' - ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml - - ${optionalString (cfg.openidPasswordFile != null) '' - install -m660 ${crowdPropertiesFile} ${cfg.home}/crowd.properties - ${pkgs.replace-secret}/bin/replace-secret \ - '@NIXOS_CROWD_OPENID_PW@' \ - ${cfg.openidPasswordFile} \ - ${cfg.home}/crowd.properties - ''} - ''; - - serviceConfig = { - User = cfg.user; - Group = cfg.group; - PrivateTmp = true; - Restart = "on-failure"; - RestartSec = "10"; - ExecStart = "${pkg}/start_crowd.sh -fg"; - }; - }; - }; -} diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix deleted file mode 100644 index 40c5d95cae3a..000000000000 --- a/nixos/modules/services/web-apps/atlassian/jira.nix +++ /dev/null @@ -1,219 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.jira; - - pkg = cfg.package.override (optionalAttrs cfg.sso.enable { - enableSSO = cfg.sso.enable; - }); - - crowdProperties = pkgs.writeText "crowd.properties" '' - application.name ${cfg.sso.applicationName} - application.password @NIXOS_JIRA_CROWD_SSO_PWD@ - application.login.url ${cfg.sso.crowd}/console/ - - crowd.server.url ${cfg.sso.crowd}/services/ - crowd.base.url ${cfg.sso.crowd}/ - - session.isauthenticated session.isauthenticated - session.tokenkey session.tokenkey - session.validationinterval ${toString cfg.sso.validationInterval} - session.lastvalidation session.lastvalidation - ''; - -in - -{ - options = { - services.jira = { - enable = mkEnableOption "Atlassian JIRA service"; - - user = mkOption { - type = types.str; - default = "jira"; - description = "User which runs JIRA."; - }; - - group = mkOption { - type = types.str; - default = "jira"; - description = "Group which runs JIRA."; - }; - - home = mkOption { - type = types.str; - default = "/var/lib/jira"; - description = "Home directory of the JIRA instance."; - }; - - listenAddress = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Address to listen on."; - }; - - listenPort = mkOption { - type = types.port; - default = 8091; - description = "Port to listen on."; - }; - - catalinaOptions = mkOption { - type = types.listOf types.str; - default = []; - example = [ "-Xms1024m" "-Xmx2048m" ]; - description = "Java options to pass to catalina/tomcat."; - }; - - proxy = { - enable = mkEnableOption "reverse proxy support"; - - name = mkOption { - type = types.str; - example = "jira.example.com"; - description = "Virtual hostname at the proxy"; - }; - - port = mkOption { - type = types.port; - default = 443; - example = 80; - description = "Port used at the proxy"; - }; - - scheme = mkOption { - type = types.str; - default = "https"; - example = "http"; - description = "Protocol used at the proxy."; - }; - - secure = mkOption { - type = types.bool; - default = true; - description = "Whether the connections to the proxy should be considered secure."; - }; - }; - - sso = { - enable = mkEnableOption "SSO with Atlassian Crowd"; - - crowd = mkOption { - type = types.str; - example = "http://localhost:8095/crowd"; - description = "Crowd Base URL without trailing slash"; - }; - - applicationName = mkOption { - type = types.str; - example = "jira"; - description = "Exact name of this JIRA instance in Crowd"; - }; - - applicationPasswordFile = mkOption { - type = types.str; - description = "Path to the file containing the application password of this JIRA instance in Crowd"; - }; - - validationInterval = mkOption { - type = types.int; - default = 2; - example = 0; - description = '' - Set to 0, if you want authentication checks to occur on each - request. Otherwise set to the number of minutes between request - to validate if the user is logged in or out of the Crowd SSO - server. Setting this value to 1 or higher will increase the - performance of Crowd's integration. - ''; - }; - }; - - package = mkPackageOption pkgs "atlassian-jira" { }; - - jrePackage = mkPackageOption pkgs "oraclejre8" { - extraDescription = '' - ::: {.note } - Atlassian only supports the Oracle JRE (JRASERVER-46152). - ::: - ''; - }; - }; - }; - - config = mkIf cfg.enable { - users.users.${cfg.user} = { - isSystemUser = true; - group = cfg.group; - home = cfg.home; - }; - - users.groups.${cfg.group} = {}; - - systemd.tmpfiles.rules = [ - "d '${cfg.home}' - ${cfg.user} - - -" - "d /run/atlassian-jira - - - - -" - - "L+ /run/atlassian-jira/home - - - - ${cfg.home}" - "L+ /run/atlassian-jira/logs - - - - ${cfg.home}/logs" - "L+ /run/atlassian-jira/work - - - - ${cfg.home}/work" - "L+ /run/atlassian-jira/temp - - - - ${cfg.home}/temp" - "L+ /run/atlassian-jira/server.xml - - - - ${cfg.home}/server.xml" - ]; - - systemd.services.atlassian-jira = { - description = "Atlassian JIRA"; - - wantedBy = [ "multi-user.target" ]; - requires = [ "postgresql.service" ]; - after = [ "postgresql.service" ]; - - path = [ cfg.jrePackage pkgs.bash ]; - - environment = { - JIRA_USER = cfg.user; - JIRA_HOME = cfg.home; - JAVA_HOME = "${cfg.jrePackage}"; - CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions; - JAVA_OPTS = mkIf cfg.sso.enable "-Dcrowd.properties=${cfg.home}/crowd.properties"; - }; - - preStart = '' - mkdir -p ${cfg.home}/{logs,work,temp,deploy} - - sed -e 's,port="8080",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \ - '' + (lib.optionalString cfg.proxy.enable '' - -e 's,protocol="HTTP/1.1",protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${toString cfg.proxy.secure}",' \ - '') + '' - ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml - - ${optionalString cfg.sso.enable '' - install -m660 ${crowdProperties} ${cfg.home}/crowd.properties - ${pkgs.replace-secret}/bin/replace-secret \ - '@NIXOS_JIRA_CROWD_SSO_PWD@' \ - ${cfg.sso.applicationPasswordFile} \ - ${cfg.home}/crowd.properties - ''} - ''; - - serviceConfig = { - User = cfg.user; - Group = cfg.group; - PrivateTmp = true; - Restart = "on-failure"; - RestartSec = "10"; - ExecStart = "${pkg}/bin/start-jira.sh -fg"; - ExecStop = "${pkg}/bin/stop-jira.sh"; - }; - }; - }; - - imports = [ - (mkRemovedOptionModule [ "services" "jira" "sso" "applicationPassword" ] '' - Use `applicationPasswordFile` instead! - '') - ]; -} diff --git a/nixos/modules/services/web-servers/bluemap.nix b/nixos/modules/services/web-apps/bluemap.nix index 731468fd9a0e..0b45501980e6 100644 --- a/nixos/modules/services/web-servers/bluemap.nix +++ b/nixos/modules/services/web-apps/bluemap.nix @@ -13,6 +13,9 @@ let (format.generate "${name}.conf" value)) cfg.maps); + addonsFolder = pkgs.linkFarm "addons" + (lib.attrsets.mapAttrs' (name: value: lib.nameValuePair "${name}.jar" value) cfg.addons); + storageFolder = pkgs.linkFarm "storage" (lib.attrsets.mapAttrs' (name: value: lib.nameValuePair "${name}.conf" @@ -25,11 +28,16 @@ let "core.conf" = coreConfig; "webapp.conf" = webappConfig; "webserver.conf" = webserverConfig; - "resourcepacks" = pkgs.linkFarm "resourcepacks" cfg.resourcepacks; + "packs" = pkgs.linkFarm "packs" cfg.resourcepacks; + "addons" = addonsFolder; }; inherit (lib) mkOption; in { + imports = [ + (lib.mkRenamedOptionModule [ "services" "bluemap" "resourcepacks" ] [ "services" "bluemap" "packs" ]) + ]; + options.services.bluemap = { enable = lib.mkEnableOption "bluemap"; @@ -219,6 +227,26 @@ in { ''; }; + addons = mkOption { + type = lib.types.attrsOf lib.types.pathInStore; + default = { }; + description = '' + A set of jar addons to be loaded. + + See <https://bluemap.bluecolored.de/3rdPartySupport.html> for a list of officially recognized addons. + ''; + + example = lib.literalExpression '' + { + blueBridge = ./blueBridge.jar; + blueBorder = pkgs.fetchurl { + url = "https://github.com/pop4959/BlueBorder/releases/download/1.1.1/BlueBorder-1.1.1.jar"; + hash = "..."; + }; + } + ''; + }; + storage = mkOption { type = lib.types.attrsOf (lib.types.submodule { freeformType = format.type; @@ -249,10 +277,13 @@ in { ''; }; - resourcepacks = mkOption { + packs = mkOption { type = lib.types.attrsOf lib.types.pathInStore; default = { }; - description = "A set of resourcepacks to use, loaded in alphabetical order"; + description = '' + A set of resourcepacks, datapacks, and mods to extract resources from, + loaded in alphabetical order. + ''; }; }; @@ -293,11 +324,12 @@ in { "${cfg.host}" = { root = config.services.bluemap.webRoot; locations = { - "~* ^/maps/[^/]*/tiles/[^/]*.json$".extraConfig = '' - error_page 404 =200 /assets/emptyTile.json; + "@empty".return = "204"; + + "~* ^/maps/[^/]*/tiles/".extraConfig = '' + error_page 404 = @empty; gzip_static always; ''; - "~* ^/maps/[^/]*/tiles/[^/]*.png$".tryFiles = "$uri =204"; }; }; }; diff --git a/nixos/modules/services/web-apps/collabora-online.nix b/nixos/modules/services/web-apps/collabora-online.nix new file mode 100644 index 000000000000..122674b67110 --- /dev/null +++ b/nixos/modules/services/web-apps/collabora-online.nix @@ -0,0 +1,200 @@ +{ + config, + lib, + pkgs, + utils, + ... +}: + +let + cfg = config.services.collabora-online; + + freeformType = lib.types.attrsOf ((pkgs.formats.json { }).type) // { + description = '' + `coolwsd.xml` configuration type, used to override values in the default configuration. + + Attribute names correspond to XML tags unless prefixed with `@`. Nested attribute sets + correspond to nested XML tags. Attribute prefixed with `@` correspond to XML attributes. E.g., + `{ storage.wopi."@allow" = true; }` in Nix corresponds to + `<storage><wopi allow="true"/></storage>` in `coolwsd.xml`, or `--o:storage.wopi[@allow]=true` + in the command line. + + Arrays correspond to multiple elements with the same tag name. E.g. + `{ host = [ '''127\.0\.0\.1''' "::1" ]; }` in Nix corresponds to + ```xml + <net><post_allow> + <host>127\.0\.0\.1</host> + <host>::1</host> + </post_allow></net> + ``` + in `coolwsd.xml`, or + `--o:net.post_allow.host[0]='127\.0\.0\.1 --o:net.post_allow.host[1]=::1` in the command line. + + Null values could be used to remove an element from the default configuration. + ''; + }; + + configFile = + pkgs.runCommandLocal "coolwsd.xml" + { + nativeBuildInputs = [ + pkgs.jq + pkgs.yq-go + ]; + userConfig = builtins.toJSON { config = cfg.settings; }; + passAsFile = [ "userConfig" ]; + } + # Merge the cfg.settings into the default coolwsd.xml. + # See https://github.com/CollaboraOnline/online/issues/10049. + '' + yq --input-format=xml \ + --xml-attribute-prefix=@ \ + --output-format=json \ + ${cfg.package}/etc/coolwsd/coolwsd.xml \ + > ./default_coolwsd.json + + jq '.[0] * .[1] | del(..|nulls)' \ + --slurp \ + ./default_coolwsd.json \ + $userConfigPath \ + > ./merged.json + + yq --output-format=xml \ + --xml-attribute-prefix=@ \ + ./merged.json \ + > $out + ''; +in +{ + options.services.collabora-online = { + enable = lib.mkEnableOption "collabora-online"; + + package = lib.mkPackageOption pkgs "Collabora Online" { default = "collabora-online"; }; + + port = lib.mkOption { + type = lib.types.port; + default = 9980; + description = "Listening port"; + }; + + settings = lib.mkOption { + type = freeformType; + default = { }; + description = '' + Configuration for Collabora Online WebSocket Daemon, see + <https://sdk.collaboraonline.com/docs/installation/Configuration.html>, or + <https://github.com/CollaboraOnline/online/blob/master/coolwsd.xml.in> for the default + configuration. + ''; + }; + + aliasGroups = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + host = lib.mkOption { + type = lib.types.str; + example = "scheme://hostname:port"; + description = "Hostname to allow or deny."; + }; + + aliases = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "scheme://aliasname1:port" + "scheme://aliasname2:port" + ]; + description = "A list of regex pattern of aliasname."; + }; + }; + } + ); + default = [ ]; + description = "Alias groups to use."; + }; + + extraArgs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra arguments to pass to the service."; + }; + }; + + config = lib.mkIf cfg.enable { + services.collabora-online.settings = { + child_root_path = lib.mkDefault "/var/lib/cool/child-roots"; + sys_template_path = lib.mkDefault "/var/lib/cool/systemplate"; + + file_server_root_path = lib.mkDefault "${config.services.collabora-online.package}/share/coolwsd"; + + # Use mount namespaces instead of setcap'd coolmount/coolforkit. + mount_namespaces = lib.mkDefault true; + + # By default, use dummy self-signed certificates provided for testing. + ssl.ca_file_path = lib.mkDefault "${config.services.collabora-online.package}/etc/coolwsd/ca-chain.cert.pem"; + ssl.cert_file_path = lib.mkDefault "${config.services.collabora-online.package}/etc/coolwsd/cert.pem"; + ssl.key_file_path = lib.mkDefault "${config.services.collabora-online.package}/etc/coolwsd/key.pem"; + }; + + users.users.cool = { + isSystemUser = true; + group = "cool"; + }; + users.groups.cool = { }; + + systemd.services.coolwsd-systemplate-setup = { + description = "Collabora Online WebSocket Daemon Setup"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = utils.escapeSystemdExecArgs [ + "${cfg.package}/bin/coolwsd-systemplate-setup" + "/var/lib/cool/systemplate" + "${cfg.package.libreoffice}/lib/collaboraoffice" + ]; + RemainAfterExit = true; + StateDirectory = "cool"; + Type = "oneshot"; + User = "cool"; + }; + }; + + systemd.services.coolwsd = { + description = "Collabora Online WebSocket Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ + "network.target" + "coolwsd-systemplate-setup.service" + ]; + + environment = builtins.listToAttrs ( + lib.imap1 (n: ag: { + name = "aliasgroup${toString n}"; + value = lib.concatStringsSep "," ([ ag.host ] ++ ag.aliases); + }) cfg.aliasGroups + ); + + serviceConfig = { + ExecStart = utils.escapeSystemdExecArgs ( + [ + "${cfg.package}/bin/coolwsd" + "--config-file=${configFile}" + "--port=${toString cfg.port}" + "--use-env-vars" + "--version" + ] + ++ cfg.extraArgs + ); + KillMode = "mixed"; + KillSignal = "SIGINT"; + LimitNOFILE = "infinity:infinity"; + Restart = "always"; + StateDirectory = "cool"; + TimeoutStopSec = 120; + User = "cool"; + }; + }; + }; + + meta.maintainers = [ lib.maintainers.xzfc ]; +} diff --git a/nixos/modules/services/web-apps/dashy.nix b/nixos/modules/services/web-apps/dashy.nix new file mode 100644 index 000000000000..47bcab976e6d --- /dev/null +++ b/nixos/modules/services/web-apps/dashy.nix @@ -0,0 +1,173 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib.types) package str; + inherit (lib) + mkIf + mkOption + mkEnableOption + mkPackageOption + ; + cfg = config.services.dashy; +in +{ + options.services.dashy = { + enable = mkEnableOption '' + Dashy, a highly customizable, easy to use, privacy-respecting dashboard app. + + Note that this builds a static web app as opposed to running a full node server, unlike the default docker image. + + Writing config changes to disk through the UI, triggering a rebuild through the UI and application status checks are + unavailable without the node server; Everything else will work fine. + + See the deployment docs for [building from source](https://dashy.to/docs/deployment#build-from-source), [hosting with a CDN](https://dashy.to/docs/deployment#hosting-with-cdn) and [CDN cloud deploy](https://dashy.to/docs/deployment#cdn--cloud-deploy) for more information. + ''; + + virtualHost = { + enableNginx = mkEnableOption "a virtualhost to serve dashy through nginx"; + + domain = mkOption { + description = '' + Domain to use for the virtual host. + + This can be used to change nginx options like + ```nix + services.nginx.virtualHosts."$\{config.services.dashy.virtualHost.domain}".listen = [ ... ] + ``` + or + ```nix + services.nginx.virtualHosts."example.com".listen = [ ... ] + ``` + ''; + type = str; + }; + }; + + package = mkPackageOption pkgs "dashy-ui" { }; + + finalDrv = mkOption { + readOnly = true; + default = + if cfg.settings != { } then cfg.package.override { inherit (cfg) settings; } else cfg.package; + defaultText = '' + if cfg.settings != {} + then cfg.package.override {inherit (cfg) settings;} + else cfg.package; + ''; + type = package; + description = '' + Final derivation containing the fully built static files + ''; + }; + + settings = mkOption { + default = { }; + description = '' + Settings serialized into `user-data/conf.yml` before build. + If left empty, the default configuration shipped with the package will be used instead. + + Note that the full configuration will be written to the nix store as world readable, which may include secrets such as [password hashes](https://dashy.to/docs/configuring#appconfigauthusers-optional). + + To add files such as icons or backgrounds, you can reference them in line such as + ```nix + icon = "$\{./icon.png}"; + ``` + This will add the file to the nix store upon build, referencing it by file path as expected by Dashy. + ''; + example = '' + { + appConfig = { + cssThemes = [ + "example-theme-1" + "example-theme-2" + ]; + enableFontAwesome = true; + fontAwesomeKey = "e9076c7025"; + theme = "thebe"; + }; + pageInfo = { + description = "My Awesome Dashboard"; + navLinks = [ + { + path = "/"; + title = "Home"; + } + { + path = "https://example.com"; + title = "Example 1"; + } + { + path = "https://example.com"; + title = "Example 2"; + } + ]; + title = "Dashy"; + }; + sections = [ + { + displayData = { + collapsed = true; + cols = 2; + customStyles = "border: 2px dashed red;"; + itemSize = "large"; + }; + items = [ + { + backgroundColor = "#0079ff"; + color = "#00ffc9"; + description = "Source code and documentation on GitHub"; + icon = "fab fa-github"; + target = "sametab"; + title = "Source"; + url = "https://github.com/Lissy93/dashy"; + } + { + description = "View currently open issues, or raise a new one"; + icon = "fas fa-bug"; + title = "Issues"; + url = "https://github.com/Lissy93/dashy/issues"; + } + { + description = "Live Demo #1"; + icon = "fas fa-rocket"; + target = "iframe"; + title = "Demo 1"; + url = "https://dashy-demo-1.as93.net"; + } + { + description = "Live Demo #2"; + icon = "favicon"; + target = "newtab"; + title = "Demo 2"; + url = "https://dashy-demo-2.as93.net"; + } + ]; + name = "Getting Started"; + } + ]; + } + ''; + inherit (pkgs.formats.json { }) type; + }; + }; + + config = mkIf cfg.enable { + services.nginx = mkIf cfg.virtualHost.enableNginx { + enable = true; + virtualHosts."${cfg.virtualHost.domain}" = { + locations."/" = { + root = cfg.finalDrv; + tryFiles = "$uri /index.html "; + }; + }; + }; + }; + + meta.maintainers = [ + lib.maintainers.therealgramdalf + ]; +} diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix index 7fbbd8a0c284..45e16603373d 100644 --- a/nixos/modules/services/web-apps/dex.nix +++ b/nixos/modules/services/web-apps/dex.nix @@ -80,7 +80,6 @@ in ]; RuntimeDirectory = "dex"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; BindReadOnlyPaths = [ "/nix/store" "-/etc/dex" @@ -91,7 +90,6 @@ in "-/etc/ssl/certs/ca-certificates.crt" ]; BindPaths = optional (cfg.settings.storage.type == "postgres") "/var/run/postgresql"; - CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; # ProtectClock= adds DeviceAllow=char-rtc r DeviceAllow = ""; DynamicUser = true; diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix index a075070f38b2..b288a08efd85 100644 --- a/nixos/modules/services/web-apps/dokuwiki.nix +++ b/nixos/modules/services/web-apps/dokuwiki.nix @@ -49,10 +49,10 @@ let in if isString v then toPhpString v # NOTE: If any value contains a , (comma) this will not get escaped - else if isList v && any lib.strings.isCoercibleToString v then toPhpString (concatMapStringsSep "," toString v) + else if isList v && strings.isConvertibleWithToString v then toPhpString (concatMapStringsSep "," toString v) else if isInt v then toString v else if isBool v then toString (if v then 1 else 0) - else if isHasAttr "_file" then "trim(file_get_contents(${toPhpString v._file}))" + else if isHasAttr "_file" then "trim(file_get_contents(${toPhpString (toString v._file)}))" else if isHasAttr "_raw" then v._raw else abort "The dokuwiki localConf value ${lib.generators.toPretty {} v} can not be encoded." ; diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix index 9a6556676597..0e8bcc703efa 100644 --- a/nixos/modules/services/web-apps/freshrss.nix +++ b/nixos/modules/services/web-apps/freshrss.nix @@ -4,8 +4,6 @@ with lib; let cfg = config.services.freshrss; - poolName = "freshrss"; - extension-env = pkgs.buildEnv { name = "freshrss-extensions"; paths = cfg.extensions; @@ -141,8 +139,8 @@ in }; pool = mkOption { - type = types.str; - default = poolName; + type = types.nullOr types.str; + default = "freshrss"; description = '' Name of the php-fpm pool to use and setup. If not specified, a pool will be created with default values. @@ -166,7 +164,6 @@ in let defaultServiceConfig = { ReadWritePaths = "${cfg.dataDir}"; - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; DeviceAllow = ""; LockPersonality = true; NoNewPrivileges = true; @@ -235,8 +232,8 @@ in }; # Set up phpfpm pool - services.phpfpm.pools = mkIf (cfg.pool == poolName) { - ${poolName} = { + services.phpfpm.pools = mkIf (cfg.pool != null) { + ${cfg.pool} = { user = "freshrss"; settings = { "listen.owner" = "nginx"; @@ -271,9 +268,9 @@ in let settingsFlags = concatStringsSep " \\\n " (mapAttrsToList (k: v: "${k} ${toString v}") { - "--default_user" = ''"${cfg.defaultUser}"''; - "--auth_type" = ''"${cfg.authType}"''; - "--base_url" = ''"${cfg.baseUrl}"''; + "--default-user" = ''"${cfg.defaultUser}"''; + "--auth-type" = ''"${cfg.authType}"''; + "--base-url" = ''"${cfg.baseUrl}"''; "--language" = ''"${cfg.language}"''; "--db-type" = ''"${cfg.database.type}"''; # The following attributes are optional depending on the type of diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix index 573c9d0d7dbb..acb2dfeeff54 100644 --- a/nixos/modules/services/web-apps/gerrit.nix +++ b/nixos/modules/services/web-apps/gerrit.nix @@ -222,6 +222,27 @@ in StandardOutput = "journal"; StateDirectory = "gerrit"; WorkingDirectory = "%S/gerrit"; + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "full"; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = 027; }; }; }; diff --git a/nixos/modules/services/web-apps/hatsu.md b/nixos/modules/services/web-apps/hatsu.md new file mode 100644 index 000000000000..e2c61d00f121 --- /dev/null +++ b/nixos/modules/services/web-apps/hatsu.md @@ -0,0 +1,23 @@ +# Hatsu {#module-services-hatsu} + +[Hatsu](https://github.com/importantimport/hatsu) is an fully-automated ActivityPub bridge for static sites. + +## Quickstart {#module-services-hatsu-quickstart} + +the minimum configuration to start hatsu server would look like this: + +```nix +{ + services.hatsu = { + enable = true; + settings = { + HATSU_DOMAIN = "hatsu.local"; + HATSU_PRIMARY_ACCOUNT = "example.com"; + }; + }; +} +``` + +this will start the hatsu server on port 3939 and save the database in `/var/lib/hatsu/hatsu.sqlite3`. + +Please refer to the [Hatsu Documentation](https://hatsu.cli.rs) for additional configuration options. diff --git a/nixos/modules/services/web-apps/hatsu.nix b/nixos/modules/services/web-apps/hatsu.nix new file mode 100644 index 000000000000..093ae150cfdc --- /dev/null +++ b/nixos/modules/services/web-apps/hatsu.nix @@ -0,0 +1,97 @@ +{ + lib, + pkgs, + config, + ... +}: +let + cfg = config.services.hatsu; +in +{ + meta.doc = ./hatsu.md; + meta.maintainers = with lib.maintainers; [ kwaa ]; + + options.services.hatsu = { + enable = lib.mkEnableOption "Self-hosted and fully-automated ActivityPub bridge for static sites"; + + package = lib.mkPackageOption pkgs "hatsu" { }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = + with lib.types; + attrsOf ( + nullOr (oneOf [ + bool + int + port + str + ]) + ); + + options = { + HATSU_DATABASE_URL = lib.mkOption { + type = lib.types.str; + default = "sqlite:///var/lib/hatsu/hatsu.sqlite?mode=rwc"; + example = "postgres://username:password@host/database"; + description = "Database URL."; + }; + + HATSU_DOMAIN = lib.mkOption { + type = lib.types.str; + description = "The domain name of your instance (eg 'hatsu.local')."; + }; + + HATSU_LISTEN_HOST = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host where hatsu should listen for incoming requests."; + }; + + HATSU_LISTEN_PORT = lib.mkOption { + type = lib.types.port; + apply = toString; + default = 3939; + description = "Port where hatsu should listen for incoming requests."; + }; + + HATSU_PRIMARY_ACCOUNT = lib.mkOption { + type = lib.types.str; + description = "The primary account of your instance (eg 'example.com')."; + }; + }; + }; + + default = { }; + + description = '' + Configuration for Hatsu, see + <link xlink:href="https://hatsu.cli.rs/admins/environments.html"/> + for supported values. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.hatsu = { + environment = cfg.settings; + + description = "Hatsu server"; + documentation = [ "https://hatsu.cli.rs/" ]; + + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + ExecStart = "${lib.getExe cfg.package}"; + Restart = "on-failure"; + StateDirectory = "hatsu"; + Type = "simple"; + WorkingDirectory = "%S/hatsu"; + }; + }; + }; +} diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix index b9761061aaae..862ba730abfc 100644 --- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix +++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix @@ -9,7 +9,7 @@ }; }; in { - meta.maintainers = with maintainers; [ das_j ]; + meta.maintainers = teams.helsinki-systems.members; options.services.icingaweb2 = with types; { enable = mkEnableOption "the icingaweb2 web interface"; diff --git a/nixos/modules/services/web-apps/immich.nix b/nixos/modules/services/web-apps/immich.nix new file mode 100644 index 000000000000..6d9c909c4568 --- /dev/null +++ b/nixos/modules/services/web-apps/immich.nix @@ -0,0 +1,367 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.immich; + format = pkgs.formats.json { }; + isPostgresUnixSocket = lib.hasPrefix "/" cfg.database.host; + isRedisUnixSocket = lib.hasPrefix "/" cfg.redis.host; + + commonServiceConfig = { + Type = "simple"; + Restart = "on-failure"; + RestartSec = 3; + + # Hardening + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + PrivateUsers = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateMounts = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + }; + inherit (lib) + types + mkIf + mkOption + mkEnableOption + ; +in +{ + options.services.immich = { + enable = mkEnableOption "Immich"; + package = lib.mkPackageOption pkgs "immich" { }; + + mediaLocation = mkOption { + type = types.path; + default = "/var/lib/immich"; + description = "Directory used to store media files. If it is not the default, the directory has to be created manually such that the immich user is able to read and write to it."; + }; + environment = mkOption { + type = types.submodule { freeformType = types.attrsOf types.str; }; + default = { }; + example = { + IMMICH_LOG_LEVEL = "verbose"; + }; + description = '' + Extra configuration environment variables. Refer to the [documentation](https://immich.app/docs/install/environment-variables) for options tagged with 'server', 'api' or 'microservices'. + ''; + }; + secretsFile = mkOption { + type = types.nullOr ( + types.str + // { + # We don't want users to be able to pass a path literal here but + # it should look like a path. + check = it: lib.isString it && lib.types.path.check it; + } + ); + default = null; + example = "/run/secrets/immich"; + description = '' + Path of a file with extra environment variables to be loaded from disk. This file is not added to the nix store, so it can be used to pass secrets to immich. Refer to the [documentation](https://immich.app/docs/install/environment-variables) for options. + + To set a database password set this to a file containing: + ``` + DB_PASSWORD=<pass> + ``` + ''; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = "The host that immich will listen on."; + }; + port = mkOption { + type = types.port; + default = 2283; + description = "The port that immich will listen on."; + }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Whether to open the immich port in the firewall"; + }; + user = mkOption { + type = types.str; + default = "immich"; + description = "The user immich should run as."; + }; + group = mkOption { + type = types.str; + default = "immich"; + description = "The group immich should run as."; + }; + + settings = mkOption { + default = null; + description = '' + Configuration for Immich. + See <https://immich.app/docs/install/config-file/> or navigate to + <https://my.immich.app/admin/system-settings> for + options and defaults. + Setting it to `null` allows configuring Immich in the web interface. + ''; + type = types.nullOr ( + types.submodule { + freeformType = format.type; + options = { + newVersionCheck.enabled = mkOption { + type = types.bool; + default = false; + description = '' + Check for new versions. + This feature relies on periodic communication with github.com. + ''; + }; + server.externalDomain = mkOption { + type = types.str; + default = ""; + description = "Domain for publicly shared links, including `http(s)://`."; + }; + }; + } + ); + }; + + machine-learning = { + enable = + mkEnableOption "immich's machine-learning functionality to detect faces and search for objects" + // { + default = true; + }; + environment = mkOption { + type = types.submodule { freeformType = types.attrsOf types.str; }; + default = { }; + example = { + MACHINE_LEARNING_MODEL_TTL = "600"; + }; + description = '' + Extra configuration environment variables. Refer to the [documentation](https://immich.app/docs/install/environment-variables) for options tagged with 'machine-learning'. + ''; + }; + }; + + database = { + enable = + mkEnableOption "the postgresql database for use with immich. See {option}`services.postgresql`" + // { + default = true; + }; + createDB = mkEnableOption "the automatic creation of the database for immich." // { + default = true; + }; + name = mkOption { + type = types.str; + default = "immich"; + description = "The name of the immich database."; + }; + host = mkOption { + type = types.str; + default = "/run/postgresql"; + example = "127.0.0.1"; + description = "Hostname or address of the postgresql server. If an absolute path is given here, it will be interpreted as a unix socket path."; + }; + port = mkOption { + type = types.port; + default = 5432; + description = "Port of the postgresql server."; + }; + user = mkOption { + type = types.str; + default = "immich"; + description = "The database user for immich."; + }; + }; + redis = { + enable = mkEnableOption "a redis cache for use with immich" // { + default = true; + }; + host = mkOption { + type = types.str; + default = config.services.redis.servers.immich.unixSocket; + defaultText = lib.literalExpression "config.services.redis.servers.immich.unixSocket"; + description = "The host that redis will listen on."; + }; + port = mkOption { + type = types.port; + default = 0; + description = "The port that redis will listen on. Set to zero to disable TCP."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !isPostgresUnixSocket -> cfg.secretsFile != null; + message = "A secrets file containing at least the database password must be provided when unix sockets are not used."; + } + ]; + + services.postgresql = mkIf cfg.database.enable { + enable = true; + ensureDatabases = mkIf cfg.database.createDB [ cfg.database.name ]; + ensureUsers = mkIf cfg.database.createDB [ + { + name = cfg.database.user; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + extraPlugins = ps: with ps; [ pgvecto-rs ]; + settings = { + shared_preload_libraries = [ "vectors.so" ]; + search_path = "\"$user\", public, vectors"; + }; + }; + systemd.services.postgresql.serviceConfig.ExecStartPost = + let + sqlFile = pkgs.writeText "immich-pgvectors-setup.sql" '' + CREATE EXTENSION IF NOT EXISTS unaccent; + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + CREATE EXTENSION IF NOT EXISTS vectors; + CREATE EXTENSION IF NOT EXISTS cube; + CREATE EXTENSION IF NOT EXISTS earthdistance; + CREATE EXTENSION IF NOT EXISTS pg_trgm; + + ALTER SCHEMA public OWNER TO ${cfg.database.user}; + ALTER SCHEMA vectors OWNER TO ${cfg.database.user}; + GRANT SELECT ON TABLE pg_vector_index_stat TO ${cfg.database.user}; + + ALTER EXTENSION vectors UPDATE; + ''; + in + [ + '' + ${lib.getExe' config.services.postgresql.package "psql"} -d "${cfg.database.name}" -f "${sqlFile}" + '' + ]; + + services.redis.servers = mkIf cfg.redis.enable { + immich = { + enable = true; + port = cfg.redis.port; + bind = mkIf (!isRedisUnixSocket) cfg.redis.host; + }; + }; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + services.immich.environment = + let + postgresEnv = + if isPostgresUnixSocket then + { DB_URL = "postgresql:///${cfg.database.name}?host=${cfg.database.host}"; } + else + { + DB_HOSTNAME = cfg.database.host; + DB_PORT = toString cfg.database.port; + DB_DATABASE_NAME = cfg.database.name; + DB_USERNAME = cfg.database.user; + }; + redisEnv = + if isRedisUnixSocket then + { REDIS_SOCKET = cfg.redis.host; } + else + { + REDIS_PORT = toString cfg.redis.port; + REDIS_HOSTNAME = cfg.redis.host; + }; + in + postgresEnv + // redisEnv + // { + IMMICH_HOST = cfg.host; + IMMICH_PORT = toString cfg.port; + IMMICH_MEDIA_LOCATION = cfg.mediaLocation; + IMMICH_MACHINE_LEARNING_URL = "http://localhost:3003"; + } + // lib.optionalAttrs (cfg.settings != null) { + IMMICH_CONFIG_FILE = "${format.generate "immich.json" cfg.settings}"; + }; + + services.immich.machine-learning.environment = { + MACHINE_LEARNING_WORKERS = "1"; + MACHINE_LEARNING_WORKER_TIMEOUT = "120"; + MACHINE_LEARNING_CACHE_FOLDER = "/var/cache/immich"; + IMMICH_HOST = "localhost"; + IMMICH_PORT = "3003"; + }; + + systemd.slices.system-immich = { + description = "Immich (self-hosted photo and video backup solution) slice"; + documentation = [ "https://immich.app/docs" ]; + }; + + systemd.services.immich-server = { + description = "Immich backend server (Self-hosted photo and video backup solution)"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + inherit (cfg) environment; + path = [ + # gzip and pg_dumpall are used by the backup service + pkgs.gzip + config.services.postgresql.package + ]; + + serviceConfig = commonServiceConfig // { + ExecStart = lib.getExe cfg.package; + EnvironmentFile = mkIf (cfg.secretsFile != null) cfg.secretsFile; + Slice = "system-immich.slice"; + StateDirectory = "immich"; + SyslogIdentifier = "immich"; + RuntimeDirectory = "immich"; + User = cfg.user; + Group = cfg.group; + # ensure that immich-server has permission to connect to the redis socket. + SupplementaryGroups = mkIf (cfg.redis.enable && isRedisUnixSocket) [ + config.services.redis.servers.immich.group + ]; + }; + }; + + systemd.services.immich-machine-learning = mkIf cfg.machine-learning.enable { + description = "immich machine learning"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + inherit (cfg.machine-learning) environment; + serviceConfig = commonServiceConfig // { + ExecStart = lib.getExe (cfg.package.machine-learning.override { immich = cfg.package; }); + Slice = "system-immich.slice"; + CacheDirectory = "immich"; + User = cfg.user; + Group = cfg.group; + }; + }; + + users.users = mkIf (cfg.user == "immich") { + immich = { + name = "immich"; + group = cfg.group; + isSystemUser = true; + }; + }; + users.groups = mkIf (cfg.group == "immich") { immich = { }; }; + + meta.maintainers = with lib.maintainers; [ jvanbruegge ]; + }; +} diff --git a/nixos/modules/services/web-apps/invoiceplane.nix b/nixos/modules/services/web-apps/invoiceplane.nix index 9a9f180b2102..b60dbe3f7bc3 100644 --- a/nixos/modules/services/web-apps/invoiceplane.nix +++ b/nixos/modules/services/web-apps/invoiceplane.nix @@ -31,7 +31,7 @@ let mkPhpValue = v: if isString v then escapeShellArg v # NOTE: If any value contains a , (comma) this will not get escaped - else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v) + else if isList v && strings.isConvertibleWithToString v then escapeShellArg (concatMapStringsSep "," toString v) else if isInt v then toString v else if isBool v then boolToString v else abort "The Invoiceplane config value ${lib.generators.toPretty {} v} can not be encoded." diff --git a/nixos/modules/services/web-apps/jitsi-meet.md b/nixos/modules/services/web-apps/jitsi-meet.md index 577f82e315be..705cf69274ca 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.md +++ b/nixos/modules/services/web-apps/jitsi-meet.md @@ -19,6 +19,13 @@ A minimal configuration using Let's Encrypt for TLS certificates looks like this } ``` +Jitsi Meet depends on the Prosody XMPP server only for message passing from +the web browser while the default Prosody configuration is intended for use +with standalone XMPP clients and XMPP federation. If you only use Prosody as +a backend for Jitsi Meet it is therefore recommended to also enable +{option}`services.jitsi-meet.prosody.lockdown` option to disable unnecessary +Prosody features such as federation or the file proxy. + ## Configuration {#module-services-jitsi-configuration} Here is the minimal configuration with additional configurations: @@ -27,6 +34,7 @@ Here is the minimal configuration with additional configurations: services.jitsi-meet = { enable = true; hostName = "jitsi.example.com"; + prosody.lockdown = true; config = { enableWelcomePage = false; prejoinPageEnabled = true; diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 39aa7379c0ed..f880691b55e9 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -175,11 +175,26 @@ in prosody.enable = mkOption { type = bool; default = true; + example = false; description = '' Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this off if you want to configure it manually. ''; }; + prosody.lockdown = mkOption { + type = bool; + default = false; + example = true; + description = '' + Whether to disable Prosody features not needed by Jitsi Meet. + + The default Prosody configuration assumes that it will be used as a + general-purpose XMPP server rather than as a companion service for + Jitsi Meet. This option reconfigures Prosody to only listen on + localhost without support for TLS termination, XMPP federation or + the file transfer proxy. + ''; + }; excalidraw.enable = mkEnableOption "Excalidraw collaboration backend for Jitsi"; excalidraw.port = mkOption { @@ -211,7 +226,10 @@ in smacks = mkDefault true; tls = mkDefault true; websocket = mkDefault true; + proxy65 = mkIf cfg.prosody.lockdown (mkDefault false); }; + httpInterfaces = mkIf cfg.prosody.lockdown (mkDefault [ "127.0.0.1" ]); + httpsPorts = mkIf cfg.prosody.lockdown (mkDefault []); muc = [ { domain = "conference.${cfg.hostName}"; @@ -232,7 +250,7 @@ in extraConfig = '' restrict_room_creation = true storage = "memory" - admins = { "focus@auth.${cfg.hostName}" } + admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" } ''; } { @@ -300,7 +318,7 @@ in muc_component = "conference.${cfg.hostName}" breakout_rooms_component = "breakout.${cfg.hostName}" '') - (mkBefore '' + (mkBefore ('' muc_mapper_domain_base = "${cfg.hostName}" cross_domain_websocket = true; @@ -310,7 +328,10 @@ in "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" } - '') + '' + optionalString cfg.prosody.lockdown '' + c2s_interfaces = { "127.0.0.1" }; + modules_disabled = { "s2s" }; + '')) ]; virtualHosts.${cfg.hostName} = { enabled = true; @@ -444,7 +465,29 @@ in Type = "simple"; ExecStart = "${pkgs.jitsi-excalidraw}/bin/jitsi-excalidraw-backend"; Restart = "on-failure"; + + DynamicUser = true; Group = "jitsi-meet"; + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectClock = true; + ProtectHome = true; + ProtectProc = true; + ProtectKernelLogs = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + LockPersonality = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallFilter = [ "@system-service @pkey" "~@privileged" ]; }; }; @@ -513,7 +556,11 @@ in cp ${overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""} $out/interface_config.js cp ./libs/external_api.min.js $out/external_api.js ''; - in '' + in (optionalString cfg.excalidraw.enable '' + handle /socket.io/ { + reverse_proxy 127.0.0.1:${toString cfg.excalidraw.port} + } + '') + '' handle /http-bind { header Host ${cfg.hostName} reverse_proxy 127.0.0.1:5280 diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix index 5d429675bafc..f7c7ea9ab1fb 100644 --- a/nixos/modules/services/web-apps/keycloak.nix +++ b/nixos/modules/services/web-apps/keycloak.nix @@ -249,12 +249,14 @@ in package = mkPackageOption pkgs "keycloak" { }; initialAdminPassword = mkOption { - type = str; - default = "changeme"; + type = nullOr str; + default = null; description = '' - Initial password set for the `admin` - user. The password is not stored safely and should be changed + Initial password set for the temporary `admin` user. + The password is not stored safely and should be changed immediately in the admin panel. + + See [Admin bootstrap and recovery](https://www.keycloak.org/server/bootstrap-admin-recovery) for details. ''; }; @@ -351,35 +353,12 @@ in for more information about hostname configuration. ''; }; - - proxy = mkOption { - type = enum [ "edge" "reencrypt" "passthrough" "none" ]; - default = "none"; - example = "edge"; - description = '' - The proxy address forwarding mode if the server is - behind a reverse proxy. - - - `edge`: - Enables communication through HTTP between the - proxy and Keycloak. - - `reencrypt`: - Requires communication through HTTPS between the - proxy and Keycloak. - - `passthrough`: - Enables communication through HTTP or HTTPS between - the proxy and Keycloak. - - See <https://www.keycloak.org/server/reverseproxy> for more information. - ''; - }; }; }; example = literalExpression '' { hostname = "keycloak.example.com"; - proxy = "reencrypt"; https-key-store-file = "/path/to/file"; https-key-store-password = { _secret = "/run/keys/store_password"; }; } @@ -497,6 +476,16 @@ in See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. ''; } + { + assertion = cfg.settings.proxy or null == null; + message = '' + The option `services.keycloak.settings.proxy' has been removed. + Set `services.keycloak.settings.proxy-headers` in combination + with other hostname options as needed instead. + See [Proxy option removed](https://www.keycloak.org/docs/latest/upgrading/index.html#proxy-option-removed) + for more information. + ''; + } ]; environment.systemPackages = [ keycloakBuild ]; @@ -633,6 +622,9 @@ in environment = { KC_HOME_DIR = "/run/keycloak"; KC_CONF_DIR = "/run/keycloak/conf"; + } // lib.optionalAttrs (cfg.initialAdminPassword != null) { + KC_BOOTSTRAP_ADMIN_USERNAME = "admin"; + KC_BOOTSTRAP_ADMIN_PASSWORD = cfg.initialAdminPassword; }; serviceConfig = { LoadCredential = @@ -658,6 +650,7 @@ in ln -s ${themesBundle} /run/keycloak/themes ln -s ${keycloakBuild}/providers /run/keycloak/ + ln -s ${keycloakBuild}/lib /run/keycloak/ install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf @@ -672,8 +665,6 @@ in mkdir -p /run/keycloak/ssl cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ '' + '' - export KEYCLOAK_ADMIN=admin - export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword} kc.sh --verbose start --optimized ''; }; diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix index 5c383e9f16ab..a3a2bbd1eca6 100644 --- a/nixos/modules/services/web-apps/mastodon.nix +++ b/nixos/modules/services/web-apps/mastodon.nix @@ -12,9 +12,12 @@ let RAILS_ENV = "production"; NODE_ENV = "production"; + BOOTSNAP_CACHE_DIR="/var/cache/mastodon/precompile"; LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so"; - # mastodon-web concurrency. + MASTODON_USE_LIBVIPS = "true"; + + # Concurrency mastodon-web WEB_CONCURRENCY = toString cfg.webProcesses; MAX_THREADS = toString cfg.webThreads; @@ -24,7 +27,7 @@ let DB_NAME = cfg.database.name; LOCAL_DOMAIN = cfg.localDomain; SMTP_SERVER = cfg.smtp.host; - SMTP_PORT = toString(cfg.smtp.port); + SMTP_PORT = toString cfg.smtp.port; SMTP_FROM_ADDRESS = cfg.smtp.fromAddress; PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system"; PAPERCLIP_ROOT_URL = "/system"; @@ -33,12 +36,12 @@ let TRUSTED_PROXY_IP = cfg.trustedProxy; } // lib.optionalAttrs (cfg.redis.host != null) { REDIS_HOST = cfg.redis.host; } - // lib.optionalAttrs (cfg.redis.port != null) { REDIS_PORT = toString(cfg.redis.port); } + // lib.optionalAttrs (cfg.redis.port != null) { REDIS_PORT = toString cfg.redis.port; } // lib.optionalAttrs (cfg.redis.createLocally && cfg.redis.enableUnixSocket) { REDIS_URL = "unix://${config.services.redis.servers.mastodon.unixSocket}"; } // lib.optionalAttrs (cfg.database.host != "/run/postgresql" && cfg.database.port != null) { DB_PORT = toString cfg.database.port; } // lib.optionalAttrs cfg.smtp.authenticate { SMTP_LOGIN = cfg.smtp.user; } // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_HOST = cfg.elasticsearch.host; } - // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PORT = toString(cfg.elasticsearch.port); } + // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PORT = toString cfg.elasticsearch.port; } // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PRESET = cfg.elasticsearch.preset; } // lib.optionalAttrs (cfg.elasticsearch.user != null) { ES_USER = cfg.elasticsearch.user; } // cfg.extraConfig; @@ -51,6 +54,9 @@ let Group = cfg.group; # Working directory WorkingDirectory = cfg.package; + # Cache directory and mode + CacheDirectory = "mastodon"; + CacheDirectoryMode = "0750"; # State directory and mode StateDirectory = "mastodon"; StateDirectoryMode = "0750"; @@ -127,7 +133,7 @@ let description = "Mastodon sidekiq${jobClassLabel}"; wantedBy = [ "mastodon.target" ]; environment = env // { - PORT = toString(cfg.sidekiqPort); + PORT = toString cfg.sidekiqPort; DB_POOL = threads; }; serviceConfig = { @@ -309,7 +315,7 @@ in { Voluntary Application Server Identification. A new keypair can be generated by running: - `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys` + `nix build -f '<nixpkgs>' mastodon; cd result; RAILS_ENV=production bin/rake webpush:generate_keys` If {option}`mastodon.vapidPrivateKeyFile`does not exist, it and this file will be created with a new keypair. @@ -324,12 +330,57 @@ in { type = lib.types.str; }; + activeRecordEncryptionDeterministicKeyFile = lib.mkOption { + description = '' + This key must be set to enable the Active Record Encryption feature within + Rails that Mastodon uses to encrypt and decrypt some database attributes. + A new Active Record keys can be generated by running: + + `nix build -f '<nixpkgs>' mastodon; cd result; RAILS_ENV=production ./bin/rails db:encryption:init` + + If this file does not exist, it will be created with a new Active Record + keys. + ''; + default = "/var/lib/mastodon/secrets/active-record-encryption-deterministic-key"; + type = lib.types.str; + }; + + activeRecordEncryptionKeyDerivationSaltFile = lib.mkOption { + description = '' + This key must be set to enable the Active Record Encryption feature within + Rails that Mastodon uses to encrypt and decrypt some database attributes. + A new Active Record keys can be generated by running: + + `nix build -f '<nixpkgs>' mastodon; cd result; RAILS_ENV=production ./bin/rails db:encryption:init` + + If this file does not exist, it will be created with a new Active Record + keys. + ''; + default = "/var/lib/mastodon/secrets/active-record-encryption-key-derivation-salt"; + type = lib.types.str; + }; + + activeRecordEncryptionPrimaryKeyFile = lib.mkOption { + description = '' + This key must be set to enable the Active Record Encryption feature within + Rails that Mastodon uses to encrypt and decrypt some database attributes. + A new Active Record keys can be generated by running: + + `nix build -f '<nixpkgs>' mastodon; cd result; RAILS_ENV=production ./bin/rails db:encryption:init` + + If this file does not exist, it will be created with a new Active Record + keys. + ''; + default = "/var/lib/mastodon/secrets/active-record-encryption-primary-key"; + type = lib.types.str; + }; + secretKeyBaseFile = lib.mkOption { description = '' Path to file containing the secret key base. A new secret key base can be generated by running: - `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret` + `nix build -f '<nixpkgs>' mastodon; cd result; bin/bundle exec rails secret` If this file does not exist, it will be created with a new secret key base. ''; @@ -342,7 +393,7 @@ in { Path to file containing the OTP secret. A new OTP secret can be generated by running: - `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret` + `nix build -f '<nixpkgs>' mastodon; cd result; bin/bundle exec rails secret` If this file does not exist, it will be created with a new OTP secret. ''; @@ -708,13 +759,28 @@ in { script = '' umask 077 + if ! test -d /var/cache/mastodon/precompile; then + ${cfg.package}/bin/bundle exec bootsnap precompile --gemfile ${cfg.package}/app ${cfg.package}/lib + fi + if ! test -f ${cfg.activeRecordEncryptionDeterministicKeyFile}; then + mkdir -p $(dirname ${cfg.activeRecordEncryptionDeterministicKeyFile}) + bin/rails db:encryption:init | grep --only-matching "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=[^ ]\+" | sed 's/^ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=//' > ${cfg.activeRecordEncryptionDeterministicKeyFile} + fi + if ! test -f ${cfg.activeRecordEncryptionKeyDerivationSaltFile}; then + mkdir -p $(dirname ${cfg.activeRecordEncryptionKeyDerivationSaltFile}) + bin/rails db:encryption:init | grep --only-matching "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=[^ ]\+" | sed 's/^ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=//' > ${cfg.activeRecordEncryptionKeyDerivationSaltFile} + fi + if ! test -f ${cfg.activeRecordEncryptionPrimaryKeyFile}; then + mkdir -p $(dirname ${cfg.activeRecordEncryptionPrimaryKeyFile}) + bin/rails db:encryption:init | grep --only-matching "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=[^ ]\+" | sed 's/^ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=//' > ${cfg.activeRecordEncryptionPrimaryKeyFile} + fi if ! test -f ${cfg.secretKeyBaseFile}; then mkdir -p $(dirname ${cfg.secretKeyBaseFile}) - bin/rake secret > ${cfg.secretKeyBaseFile} + bin/bundle exec rails secret > ${cfg.secretKeyBaseFile} fi if ! test -f ${cfg.otpSecretFile}; then mkdir -p $(dirname ${cfg.otpSecretFile}) - bin/rake secret > ${cfg.otpSecretFile} + bin/bundle exec rails secret > ${cfg.otpSecretFile} fi if ! test -f ${cfg.vapidPrivateKeyFile}; then mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile}) @@ -724,6 +790,9 @@ in { fi cat > /var/lib/mastodon/.secrets_env <<EOF + ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="$(cat ${cfg.activeRecordEncryptionDeterministicKeyFile})" + ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="$(cat ${cfg.activeRecordEncryptionKeyDerivationSaltFile})" + ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="$(cat ${cfg.activeRecordEncryptionPrimaryKeyFile})" SECRET_KEY_BASE="$(cat ${cfg.secretKeyBaseFile})" OTP_SECRET="$(cat ${cfg.otpSecretFile})" VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})" @@ -802,7 +871,7 @@ in { description = "Mastodon web"; environment = env // (if cfg.enableUnixSocket then { SOCKET = "/run/mastodon-web/web.socket"; } - else { PORT = toString(cfg.webPort); } + else { PORT = toString cfg.webPort; } ); serviceConfig = { ExecStart = "${cfg.package}/bin/puma -C config/puma.rb"; @@ -816,7 +885,7 @@ in { # System Call Filtering SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ]; } // cfgService; - path = with pkgs; [ ffmpeg-headless file imagemagick ]; + path = with pkgs; [ ffmpeg-headless file ]; }; systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable { @@ -851,7 +920,7 @@ in { }; locations."@proxy" = { - proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString(cfg.webPort)}"); + proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString cfg.webPort}"); proxyWebsockets = true; }; @@ -903,7 +972,7 @@ in { inherit (cfg) group; }; }) - (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package pkgs.imagemagick ]) + (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package ]) (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {${config.services.mastodon.user}.extraGroups = [ "redis-mastodon" ];}) ]; diff --git a/nixos/modules/services/web-apps/mediagoblin.nix b/nixos/modules/services/web-apps/mediagoblin.nix new file mode 100644 index 000000000000..a6f2e5cdd0a3 --- /dev/null +++ b/nixos/modules/services/web-apps/mediagoblin.nix @@ -0,0 +1,377 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.mediagoblin; + + mkSubSectionKeyValue = + depth: k: v: + if lib.isAttrs v then + let + inherit (lib.strings) replicate; + in + "${replicate depth "["}${k}${replicate depth "]"}\n" + + lib.generators.toINIWithGlobalSection { + mkKeyValue = mkSubSectionKeyValue (depth + 1); + } { globalSection = v; } + else + lib.generators.mkKeyValueDefault { + mkValueString = v: if lib.isString v then ''"${v}"'' else lib.generators.mkValueStringDefault { } v; + } " = " k v; + + iniFormat = pkgs.formats.ini { }; + + # we need to build our own GI_TYPELIB_PATH because celery and paster need this information, too and cannot easily be re-wrapped + GI_TYPELIB_PATH = + let + needsGst = + (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.audio") + || (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.video"); + in + lib.makeSearchPathOutput "out" "lib/girepository-1.0" ( + with pkgs.gst_all_1; + [ + pkgs.glib + gst-plugins-base + gstreamer + ] + # audio and video share most dependencies, so we can just take audio + ++ lib.optionals needsGst cfg.package.optional-dependencies.audio + ); + + finalPackage = cfg.package.python.buildEnv.override { + extraLibs = + with cfg.package.python.pkgs; + [ + (toPythonModule cfg.package) + ] + ++ cfg.pluginPackages + # not documented in extras... + ++ lib.optional (lib.hasPrefix "postgresql://" cfg.settings.mediagoblin.sql_engine) psycopg2 + ++ ( + let + inherit (cfg.settings.mediagoblin) plugins; + in + with cfg.package.optional-dependencies; + lib.optionals (plugins ? "mediagoblin.media_types.audio") audio + ++ lib.optionals (plugins ? "mediagoblin.media_types.video") video + ++ lib.optionals (plugins ? "mediagoblin.media_types.raw_image") raw_image + ++ lib.optionals (plugins ? "mediagoblin.media_types.ascii") ascii + ++ lib.optionals (plugins ? "mediagoblin.plugins.ldap") ldap + ); + }; +in +{ + options = { + services.mediagoblin = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable MediaGoblin. + + After the initial deployment, make sure to add an admin account: + ``` + mediagoblin-gmg adduser --username admin --email admin@example.com + mediagoblin-gmg makeadmin admin + ``` + ''; + }; + + domain = lib.mkOption { + type = lib.types.str; + example = "mediagoblin.example.com"; + description = "Domain under which mediagoblin will be served."; + }; + + createDatabaseLocally = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = "Whether to configure a local postgres database and connect to it."; + }; + + package = lib.mkPackageOption pkgs "mediagoblin" { }; + + pluginPackages = lib.mkOption { + type = with lib.types; listOf package; + default = [ ]; + description = "Plugins to add to the environment of MediaGoblin. They still need to be enabled in the config."; + }; + + settings = lib.mkOption { + description = "Settings which are written into `mediagoblin.ini`."; + default = { }; + type = lib.types.submodule { + freeformType = lib.types.anything; + + options = { + mediagoblin = { + allow_registration = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable user self registration. This is generally not recommend due to spammers. + See [upstream FAQ](https://docs.mediagoblin.org/en/stable/siteadmin/production-deployments.html#should-i-keep-open-registration-enabled). + ''; + }; + + email_debug_mode = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = '' + Disable email debug mode to start sending outgoing mails. + This requires configuring SMTP settings, + see the [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/configuration.html#enabling-email-notifications) + for details. + ''; + }; + + email_sender_address = lib.mkOption { + type = lib.types.str; + example = "noreply@example.org"; + description = "Email address which notices are sent from."; + }; + + sql_engine = lib.mkOption { + type = lib.types.str; + default = "sqlite:///var/lib/mediagoblin/mediagoblin.db"; + example = "postgresql:///mediagoblin"; + description = "Database to use."; + }; + + plugins = lib.mkOption { + defaultText = '' + { + "mediagoblin.plugins.geolocation" = { }; + "mediagoblin.plugins.processing_info" = { }; + "mediagoblin.plugins.basic_auth" = { }; + "mediagoblin.media_types.image" = { }; + } + ''; + description = '' + Plugins to enable. See [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/plugins.html) for details. + Extra dependencies are automatically enabled. + ''; + }; + }; + }; + }; + }; + + paste = { + port = lib.mkOption { + type = lib.types.port; + default = 6543; + description = "Port under which paste will listen."; + }; + + settings = lib.mkOption { + description = "Settings which are written into `paste.ini`."; + default = { }; + type = lib.types.submodule { + freeformType = iniFormat.type; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ + (pkgs.writeShellScriptBin "mediagoblin-gmg" '' + sudo=exec + if [[ "$USER" != mediagoblin ]]; then + sudo='exec /run/wrappers/bin/sudo -u mediagoblin' + fi + $sudo sh -c "cd /var/lib/mediagoblin; env GI_TYPELIB_PATH=${GI_TYPELIB_PATH} ${lib.getExe' finalPackage "gmg"} $@" + '') + ]; + + services = { + mediagoblin.settings.mediagoblin = { + plugins = { + "mediagoblin.media_types.image" = { }; + "mediagoblin.plugins.basic_auth" = { }; + "mediagoblin.plugins.geolocation" = { }; + "mediagoblin.plugins.processing_info" = { }; + }; + sql_engine = lib.mkIf cfg.createDatabaseLocally "postgresql:///mediagoblin"; + }; + + nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + virtualHosts = { + # see https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/nginx.conf.template + "${cfg.domain}" = { + forceSSL = true; + extraConfig = '' + # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/Dockerfile.nginx.in#L5 + client_max_body_size 100M; + + more_set_headers X-Content-Type-Options nosniff; + ''; + locations = { + "/".proxyPass = "http://127.0.0.1:${toString cfg.paste.port}"; + "/mgoblin_static/".alias = "${finalPackage}/${finalPackage.python.sitePackages}/mediagoblin/static/"; + "/mgoblin_media/".alias = "/var/lib/mediagoblin/user_dev/media/public/"; + "/theme_static/".alias = "/var/lib/mediagoblin/user_dev/theme_static/"; + "/plugin_static/".alias = "/var/lib/mediagoblin/user_dev/plugin_static/"; + }; + }; + }; + }; + + postgresql = lib.mkIf cfg.createDatabaseLocally { + enable = true; + ensureDatabases = [ "mediagoblin" ]; + ensureUsers = [ + { + name = "mediagoblin"; + ensureDBOwnership = true; + } + ]; + }; + + rabbitmq.enable = true; + }; + + systemd.services = + let + serviceDefaults = { + wantedBy = [ "multi-user.target" ]; + path = + lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ] + ++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") ( + with pkgs; + [ + poppler_utils + unoconv + ] + ); + serviceConfig = { + AmbientCapabilities = ""; + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + Group = "mediagoblin"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RemoveIPC = true; + StateDirectory = "mediagoblin"; + StateDirectoryMode = "0750"; + User = "mediagoblin"; + WorkingDirectory = "/var/lib/mediagoblin/"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "@chown" + ]; + UMask = "0027"; + }; + }; + + generatedPasteConfig = iniFormat.generate "paste.ini" cfg.paste.settings; + pasteConfig = pkgs.runCommand "paste-combined.ini" { nativeBuildInputs = [ pkgs.crudini ]; } '' + cp ${cfg.package.src}/paste.ini $out + chmod +w $out + crudini --merge $out < ${generatedPasteConfig} + ''; + in + { + mediagoblin-celeryd = lib.recursiveUpdate serviceDefaults { + # we cannot change DEFAULT.data_dir inside mediagoblin.ini because of an annoying bug + # https://todo.sr.ht/~mediagoblin/mediagoblin/57 + preStart = '' + cp --remove-destination ${ + pkgs.writeText "mediagoblin.ini" ( + lib.generators.toINI { } (lib.filterAttrsRecursive (n: v: n != "plugins") cfg.settings) + + "\n" + + lib.generators.toINI { mkKeyValue = mkSubSectionKeyValue 2; } { + inherit (cfg.settings.mediagoblin) plugins; + } + ) + } /var/lib/mediagoblin/mediagoblin.ini + ''; + serviceConfig = { + Environment = [ + "CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery" + "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}" + "MEDIAGOBLIN_CONFIG=/var/lib/mediagoblin/mediagoblin.ini" + "PASTE_CONFIG=${pasteConfig}" + ]; + ExecStart = "${lib.getExe' finalPackage "celery"} worker --loglevel=INFO"; + }; + unitConfig.Description = "MediaGoblin Celery"; + }; + + mediagoblin-paster = lib.recursiveUpdate serviceDefaults { + after = [ + "mediagoblin-celeryd.service" + "postgresql.service" + ]; + requires = [ + "mediagoblin-celeryd.service" + "postgresql.service" + ]; + preStart = '' + cp --remove-destination ${pasteConfig} /var/lib/mediagoblin/paste.ini + ${lib.getExe' finalPackage "gmg"} dbupdate + ''; + serviceConfig = { + Environment = [ + "CELERY_ALWAYS_EAGER=false" + "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}" + ]; + ExecStart = "${lib.getExe' finalPackage "paster"} serve /var/lib/mediagoblin/paste.ini"; + }; + unitConfig.Description = "Mediagoblin"; + }; + }; + + systemd.tmpfiles.settings."mediagoblin"."/var/lib/mediagoblin/user_dev".d = { + group = "mediagoblin"; + mode = "2750"; + user = "mediagoblin"; + }; + + users = { + groups.mediagoblin = { }; + users = { + mediagoblin = { + group = "mediagoblin"; + home = "/var/lib/mediagoblin"; + isSystemUser = true; + }; + nginx.extraGroups = [ "mediagoblin" ]; + }; + }; + }; +} diff --git a/nixos/modules/services/web-apps/microbin.nix b/nixos/modules/services/web-apps/microbin.nix index 0ebe644a2595..e404609a4c22 100644 --- a/nixos/modules/services/web-apps/microbin.nix +++ b/nixos/modules/services/web-apps/microbin.nix @@ -61,7 +61,6 @@ in wantedBy = [ "multi-user.target" ]; environment = lib.mapAttrs (_: v: if lib.isBool v then lib.boolToString v else toString v) cfg.settings; serviceConfig = { - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; DevicePolicy = "closed"; DynamicUser = true; EnvironmentFile = lib.optional (cfg.passwordFile != null) cfg.passwordFile; diff --git a/nixos/modules/services/web-apps/mobilizon.nix b/nixos/modules/services/web-apps/mobilizon.nix index b7fad7f3066e..922d385f8d3b 100644 --- a/nixos/modules/services/web-apps/mobilizon.nix +++ b/nixos/modules/services/web-apps/mobilizon.nix @@ -52,7 +52,7 @@ let dbUser = if repoSettings.username != null then repoSettings.username else "mobilizon"; postgresql = config.services.postgresql.package; - postgresqlSocketDir = "/var/run/postgresql"; + postgresqlSocketDir = "/run/postgresql"; secretEnvFile = "/var/lib/mobilizon/secret-env.sh"; in diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix index 7bcbde2a018e..01319722afa9 100644 --- a/nixos/modules/services/web-apps/netbox.nix +++ b/nixos/modules/services/web-apps/netbox.nix @@ -75,21 +75,17 @@ in { package = lib.mkOption { type = lib.types.package; default = - if lib.versionAtLeast config.system.stateVersion "24.05" + if lib.versionAtLeast config.system.stateVersion "24.11" + then pkgs.netbox_4_1 + else if lib.versionAtLeast config.system.stateVersion "24.05" then pkgs.netbox_3_7 - else if lib.versionAtLeast config.system.stateVersion "23.11" - then pkgs.netbox_3_6 - else if lib.versionAtLeast config.system.stateVersion "23.05" - then pkgs.netbox_3_5 - else pkgs.netbox_3_3; + else pkgs.netbox_3_6; defaultText = lib.literalExpression '' - if lib.versionAtLeast config.system.stateVersion "24.05" + if lib.versionAtLeast config.system.stateVersion "24.11" + then pkgs.netbox_4_1 + else if lib.versionAtLeast config.system.stateVersion "24.05" then pkgs.netbox_3_7 - else if lib.versionAtLeast config.system.stateVersion "23.11" - then pkgs.netbox_3_6 - else if lib.versionAtLeast config.system.stateVersion "23.05" - then pkgs.netbox_3_5 - else pkgs.netbox_3_3; + else pkgs.netbox_3_6; ''; description = '' NetBox package to use. @@ -328,6 +324,7 @@ in { --pythonpath ${pkg}/opt/netbox/netbox ''; PrivateTmp = true; + TimeoutStartSec = lib.mkDefault "5min"; }; }; diff --git a/nixos/modules/services/web-apps/nextcloud-notify_push.nix b/nixos/modules/services/web-apps/nextcloud-notify_push.nix index 4da5aff0c83e..475cddf5f751 100644 --- a/nixos/modules/services/web-apps/nextcloud-notify_push.nix +++ b/nixos/modules/services/web-apps/nextcloud-notify_push.nix @@ -48,7 +48,7 @@ in ] ( opt: options.services.nextcloud.config.${opt} // { default = config.services.nextcloud.config.${opt}; - defaultText = "config.services.nextcloud.config.${opt}"; + defaultText = lib.literalExpression "config.services.nextcloud.config.${opt}"; } ) ); @@ -77,15 +77,21 @@ in dbType = if cfg.dbtype == "pgsql" then "postgresql" else cfg.dbtype; dbUser = lib.optionalString (cfg.dbuser != null) cfg.dbuser; dbPass = lib.optionalString (cfg.dbpassFile != null) ":$DATABASE_PASSWORD"; - isSocket = lib.hasPrefix "/" (toString cfg.dbhost); + dbHostHasPrefix = prefix: lib.hasPrefix prefix (toString cfg.dbhost); + isPostgresql = dbType == "postgresql"; + isMysql = dbType == "mysql"; + isSocket = (isPostgresql && dbHostHasPrefix "/") || (isMysql && dbHostHasPrefix "localhost:/"); dbHost = lib.optionalString (cfg.dbhost != null) (if isSocket then - if dbType == "postgresql" then "?host=${cfg.dbhost}" else - if dbType == "mysql" then "?socket=${cfg.dbhost}" else throw "unsupported dbtype" + lib.optionalString isMysql "@localhost" else "@${cfg.dbhost}"); + dbOpts = lib.optionalString (cfg.dbhost != null && isSocket) ( + if isPostgresql then "?host=${cfg.dbhost}" else + if isMysql then "?socket=${lib.removePrefix "localhost:" cfg.dbhost}" else throw "unsupported dbtype" + ); dbName = lib.optionalString (cfg.dbname != null) "/${cfg.dbname}"; - dbUrl = "${dbType}://${dbUser}${dbPass}${lib.optionalString (!isSocket) dbHost}${dbName}${lib.optionalString isSocket dbHost}"; + dbUrl = "${dbType}://${dbUser}${dbPass}${dbHost}${dbName}${dbOpts}"; in lib.optionalString (dbPass != "") '' export DATABASE_PASSWORD="$(<"${cfg.dbpassFile}")" '' + '' diff --git a/nixos/modules/services/web-apps/nextcloud-whiteboard-server.nix b/nixos/modules/services/web-apps/nextcloud-whiteboard-server.nix new file mode 100644 index 000000000000..ff95f54506f5 --- /dev/null +++ b/nixos/modules/services/web-apps/nextcloud-whiteboard-server.nix @@ -0,0 +1,72 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + + inherit (lib) + mkIf + mkEnableOption + mkOption + types + literalExpression + ; + cfg = config.services.nextcloud-whiteboard-server; + +in +{ + options.services.nextcloud-whiteboard-server = { + + enable = mkEnableOption "Nextcloud backend server for the Whiteboard app"; + + settings = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + Settings to configure backend server. Especially the Nextcloud host + url has to be set. The required environment variable `JWT_SECRET_KEY` + should be set via the secrets option. + ''; + example = literalExpression '' + { + NEXTCLOUD_URL = "https://nextcloud.example.org"; + } + ''; + }; + + secrets = lib.mkOption { + type = with types; listOf str; + description = '' + A list of files containing the various secrets. Should be in the + format expected by systemd's `EnvironmentFile` directory. + ''; + default = [ ]; + }; + + }; + + config = mkIf cfg.enable { + + systemd.services.nextcloud-whiteboard-server = { + description = "Nextcloud backend server for the Whiteboard app"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + environment = cfg.settings; + serviceConfig = { + ExecStart = "${lib.getExe pkgs.nextcloud-whiteboard-server}"; + WorkingDirectory = "%S/whiteboard"; + StateDirectory = "whiteboard"; + EnvironmentFile = [ cfg.secrets ]; + DynamicUser = true; + }; + }; + + }; + + meta.maintainers = with lib.maintainers; [ onny ]; + +} diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md index b695cf9620a4..8d9aa878c287 100644 --- a/nixos/modules/services/web-apps/nextcloud.md +++ b/nixos/modules/services/web-apps/nextcloud.md @@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using [services.nextcloud](#opt-services.nextcloud.enable). A desktop client is packaged at `pkgs.nextcloud-client`. -The current default by NixOS is `nextcloud29` which is also the latest +The current default by NixOS is `nextcloud30` which is also the latest major version available. ## Basic usage {#module-services-nextcloud-basic-usage} diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index c8c4fe4b4d61..8cb4b4c439f9 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -300,7 +300,7 @@ in { package = mkOption { type = types.package; description = "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud28" "nextcloud29" ]; + relatedPackages = [ "nextcloud28" "nextcloud29" "nextcloud30" ]; }; phpPackage = mkPackageOption pkgs "php" { example = "php82"; @@ -821,7 +821,7 @@ in { config = mkIf cfg.enable (mkMerge [ { warnings = let - latest = 29; + latest = 30; upgradeWarning = major: nixos: '' A legacy Nextcloud install (from before NixOS ${nixos}) may be installed. @@ -847,11 +847,11 @@ in { If you have an existing installation with a custom table prefix, make sure it is set correctly in `config.php` and remove the option from your NixOS config. '') - ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11")) ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05")) ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11")) ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05")) - ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11")); + ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11")) + ++ (optional (versionOlder cfg.package.version "30") (upgradeWarning 29 "24.11")); services.nextcloud.package = with pkgs; mkDefault ( @@ -862,7 +862,8 @@ in { `pkgs.nextcloud`. '' else if versionOlder stateVersion "24.05" then nextcloud27 - else nextcloud29 + else if versionOlder stateVersion "24.11" then nextcloud29 + else nextcloud30 ); services.nextcloud.phpPackage = @@ -929,7 +930,10 @@ in { nextcloud-setup = let c = cfg.config; occInstallCmd = let - mkExport = { arg, value }: "export ${arg}=${value}"; + mkExport = { arg, value }: '' + ${arg}=${value}; + export ${arg}; + ''; dbpass = { arg = "DBPASS"; value = if c.dbpassFile != null @@ -1019,7 +1023,7 @@ in { ''; serviceConfig.Type = "oneshot"; serviceConfig.User = "nextcloud"; - # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent + # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent # an automatic creation of the database user. environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false"; }; diff --git a/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix b/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix index 9bd2cf310c0a..cbb0ce813a34 100644 --- a/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix +++ b/nixos/modules/services/web-apps/nextjs-ollama-llm-ui.nix @@ -52,7 +52,7 @@ in ollamaUrl = lib.mkOption { type = lib.types.str; - default = "127.0.0.1:11434"; + default = "http://127.0.0.1:11434"; example = "https://ollama.example.org"; description = '' The address (including host and port) under which we can access the Ollama backend server. @@ -79,6 +79,7 @@ in serviceConfig = { ExecStart = "${lib.getExe nextjs-ollama-llm-ui}"; DynamicUser = true; + CacheDirectory = "nextjs-ollama-llm-ui"; }; }; }; diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix index 4c095ea79bbd..cf6429c0094e 100644 --- a/nixos/modules/services/web-apps/node-red.nix +++ b/nixos/modules/services/web-apps/node-red.nix @@ -10,7 +10,7 @@ in options.services.node-red = { enable = mkEnableOption "the Node-RED service"; - package = mkPackageOption pkgs [ "nodePackages" "node-red" ] { }; + package = mkPackageOption pkgs [ "node-red" ] { }; openFirewall = mkOption { type = types.bool; @@ -31,8 +31,8 @@ in configFile = mkOption { type = types.path; - default = "${cfg.package}/lib/node_modules/node-red/settings.js"; - defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/settings.js"''; + default = "${cfg.package}/lib/node_modules/node-red/packages/node_modules/node-red/settings.js"; + defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/packages/node_modules/node-red/settings.js"''; description = '' Path to the JavaScript configuration file. See <https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js> @@ -118,7 +118,7 @@ in environment = { HOME = cfg.userDir; }; - path = lib.optionals cfg.withNpmAndGcc [ pkgs.nodePackages.npm pkgs.gcc ]; + path = lib.optionals cfg.withNpmAndGcc [ pkgs.nodejs pkgs.gcc ]; serviceConfig = mkMerge [ { User = cfg.user; diff --git a/nixos/modules/services/web-apps/openwebrx.nix b/nixos/modules/services/web-apps/openwebrx.nix index 614eb963b4a3..2ac187dc88c9 100644 --- a/nixos/modules/services/web-apps/openwebrx.nix +++ b/nixos/modules/services/web-apps/openwebrx.nix @@ -18,7 +18,7 @@ in codec2 js8call m17-cxx-demod - alsaUtils + alsa-utils netcat ]; serviceConfig = { diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix index e3f15f4f438c..4f5ea3390e88 100644 --- a/nixos/modules/services/web-apps/peertube.nix +++ b/nixos/modules/services/web-apps/peertube.nix @@ -432,7 +432,6 @@ in { path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ]; script = '' - #!/bin/sh umask 077 cat > /var/lib/peertube/config/local.yaml <<EOF ${lib.optionalString (cfg.secrets.secretsFile != null) '' @@ -457,7 +456,7 @@ in { ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides - node dist/server + exec node dist/server ''; serviceConfig = { Type = "simple"; diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix index ec4126b420cd..f880b3539e00 100644 --- a/nixos/modules/services/web-apps/photoprism.nix +++ b/nixos/modules/services/web-apps/photoprism.nix @@ -109,7 +109,6 @@ in LoadCredential = lib.optionalString (cfg.passwordFile != null) "PHOTOPRISM_ADMIN_PASSWORD:${cfg.passwordFile}"; - CapabilityBoundingSet = ""; LockPersonality = true; PrivateDevices = true; PrivateUsers = true; @@ -126,9 +125,6 @@ in SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@setuid @keyring" ]; UMask = "0066"; - } // lib.optionalAttrs (cfg.port < 1024) { - AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; }; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix index 46d671f8504b..62db479da3d4 100644 --- a/nixos/modules/services/web-apps/pixelfed.nix +++ b/nixos/modules/services/web-apps/pixelfed.nix @@ -406,7 +406,7 @@ in { # is unnecessary as it's part of the installPhase of pixelfed. # Install Horizon - # FIXME: require write access to public/ — should be done as part of install — pixelfed-manage horizon:publish + # FIXME: require write access to public/ — should be done as part of install — pixelfed-manage horizon:publish # Perform the first migration. [[ ! -f ${cfg.dataDir}/.initial-migration ]] && pixelfed-manage migrate --force && touch ${cfg.dataDir}/.initial-migration diff --git a/nixos/modules/services/web-apps/powerdns-admin.nix b/nixos/modules/services/web-apps/powerdns-admin.nix index d64c468a9cb5..d1886129515e 100644 --- a/nixos/modules/services/web-apps/powerdns-admin.nix +++ b/nixos/modules/services/web-apps/powerdns-admin.nix @@ -87,7 +87,6 @@ in User = "powerdnsadmin"; Group = "powerdnsadmin"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; BindReadOnlyPaths = [ "/nix/store" "-/etc/resolv.conf" @@ -97,7 +96,6 @@ in ] ++ (optional (cfg.secretKeyFile != null) cfg.secretKeyFile) ++ (optional (cfg.saltFile != null) cfg.saltFile); - CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; # ProtectClock= adds DeviceAllow=char-rtc r DeviceAllow = ""; # Implies ProtectSystem=strict, which re-mounts all paths diff --git a/nixos/modules/services/web-apps/pretalx.nix b/nixos/modules/services/web-apps/pretalx.nix index 2280d9165b40..89952f09c92e 100644 --- a/nixos/modules/services/web-apps/pretalx.nix +++ b/nixos/modules/services/web-apps/pretalx.nix @@ -22,7 +22,6 @@ let ] ++ finalPackage.optional-dependencies.redis ++ lib.optionals cfg.celery.enable [ celery ] - ++ lib.optionals (cfg.settings.database.backend == "mysql") finalPackage.optional-dependencies.mysql ++ lib.optionals (cfg.settings.database.backend == "postgresql") finalPackage.optional-dependencies.postgres; }; in @@ -184,6 +183,17 @@ in }; }; + files = { + upload_limit = lib.mkOption { + type = lib.types.ints.positive; + default = 10; + example = 50; + description = '' + Maximum file upload size in MiB. + ''; + }; + }; + filesystem = { data = lib.mkOption { type = lib.types.path; @@ -294,6 +304,15 @@ in '') ]; + services.logrotate.settings.pretalx = { + files = "${cfg.settings.filesystem.logs}/*.log"; + su = "${cfg.user} ${cfg.group}"; + frequency = "weekly"; + rotate = "12"; + copytruncate = true; + compress = true; + }; + services = { nginx = lib.mkIf cfg.nginx.enable { enable = true; diff --git a/nixos/modules/services/web-apps/pretix.nix b/nixos/modules/services/web-apps/pretix.nix index bcd59ce40ae7..f521c49668c8 100644 --- a/nixos/modules/services/web-apps/pretix.nix +++ b/nixos/modules/services/web-apps/pretix.nix @@ -403,6 +403,15 @@ in '') ]; + services.logrotate.settings.pretix = { + files = "${cfg.settings.pretix.logdir}/*.log"; + su = "${cfg.user} ${cfg.group}"; + frequency = "weekly"; + rotate = "12"; + copytruncate = true; + compress = true; + }; + services = { nginx = mkIf cfg.nginx.enable { enable = true; diff --git a/nixos/modules/services/web-apps/privatebin.nix b/nixos/modules/services/web-apps/privatebin.nix new file mode 100644 index 000000000000..6f6f9c27e389 --- /dev/null +++ b/nixos/modules/services/web-apps/privatebin.nix @@ -0,0 +1,228 @@ +{ + pkgs, + config, + lib, + ... +}: + +let + cfg = config.services.privatebin; + + customToINI = lib.generators.toINI { + mkKeyValue = lib.generators.mkKeyValueDefault { + mkValueString = + v: + if v == true then + ''true'' + else if v == false then + ''false'' + else if builtins.isInt v then + ''${builtins.toString v}'' + else if builtins.isPath v then + ''"${builtins.toString v}"'' + else if builtins.isString v then + ''"${v}"'' + else + lib.generators.mkValueStringDefault { } v; + } "="; + }; + + privatebinSettings = pkgs.writeTextDir "conf.php" (customToINI cfg.settings); + + user = cfg.user; + group = cfg.group; + + defaultUser = "privatebin"; + defaultGroup = "privatebin"; + +in +{ + + options.services.privatebin = { + + enable = lib.mkEnableOption "Privatebin: A minimalist, open source online + pastebin where the server has zero knowledge of pasted data."; + + user = lib.mkOption { + type = lib.types.str; + default = defaultUser; + description = "User account under which privatebin runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = if cfg.enableNginx then "nginx" else defaultGroup; + defaultText = "If `services.privatebin.enableNginx` is true then `nginx` else ${defaultGroup}"; + description = '' + Group under which privatebin runs. It is best to set this to the group + of whatever webserver is being used as the frontend. + ''; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/privatebin"; + description = '' + The place where privatebin stores its state. + ''; + }; + + package = lib.mkPackageOption pkgs "privatebin" { }; + + enableNginx = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable nginx or not. If enabled, an nginx virtual host will + be created for access to firefly-iii. If not enabled, then you may use + `''${config.services.firefly-iii.package}` as your document root in + whichever webserver you wish to setup. + ''; + }; + + virtualHost = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = '' + The hostname at which you wish privatebin to be served. If you have + enabled nginx using `services.privatebin.enableNginx` then this will + be used. + ''; + }; + + poolConfig = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.str + lib.types.int + lib.types.bool + ] + ); + defaultText = lib.literalExpression '' + { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + } + ''; + default = { }; + description = '' + Options for the PrivateBin PHP pool. See the documentation on <literal>php-fpm.conf</literal> + for details on configuration directives. + ''; + }; + + settings = lib.mkOption { + default = { }; + description = '' + Options for privatebin configuration. Refer to + <https://github.com/PrivateBin/PrivateBin/wiki/Configuration> for + details on supported values. + ''; + example = lib.literalExpression '' + { + main = { + name = "NixOS Based Privatebin"; + discussion = false; + defaultformatter = "plalib.types.intext"; + qrcode = true + }; + model.class = "Filesystem"; + model_options.dir = "/var/lib/privatebin/data"; + } + ''; + type = lib.types.submodule { freeformType = lib.types.attrsOf lib.types.anything; }; + }; + }; + + config = lib.mkIf cfg.enable { + + services.privatebin.settings = { + main = lib.mkDefault { }; + model.class = lib.mkDefault "Filesystem"; + model_options.dir = lib.mkDefault "${cfg.dataDir}/data"; + purge.dir = lib.mkDefault "${cfg.dataDir}/purge"; + traffic = { + dir = lib.mkDefault "${cfg.dataDir}/traffic"; + header = "X_FORWARDED_FOR"; + }; + }; + + services.phpfpm.pools.privatebin = { + inherit user group; + phpPackage = pkgs.php83; + phpOptions = '' + log_errors = on + ''; + settings = { + "listen.mode" = lib.mkDefault "0660"; + "listen.owner" = lib.mkDefault user; + "listen.group" = lib.mkDefault group; + "pm" = lib.mkDefault "dynamic"; + "pm.max_children" = lib.mkDefault 32; + "pm.start_servers" = lib.mkDefault 2; + "pm.min_spare_servers" = lib.mkDefault 2; + "pm.max_spare_servers" = lib.mkDefault 4; + "pm.max_requests" = lib.mkDefault 500; + }; + phpEnv.CONFIG_PATH = lib.strings.removeSuffix "/conf.php" (builtins.toString privatebinSettings); + }; + + services.nginx = lib.mkIf cfg.enableNginx { + enable = true; + recommendedTlsSettings = lib.mkDefault true; + recommendedOptimisation = lib.mkDefault true; + recommendedGzipSettings = lib.mkDefault true; + virtualHosts.${cfg.virtualHost} = { + root = "${cfg.package}"; + locations = { + "/" = { + tryFiles = "$uri $uri/ /index.php?$query_string"; + index = "index.php"; + extraConfig = '' + sendfile off; + ''; + }; + "~ \.php$" = { + extraConfig = '' + include ${config.services.nginx.package}/conf/fastcgi_params ; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice + fastcgi_pass unix:${config.services.phpfpm.pools.privatebin.socket}; + ''; + }; + }; + }; + }; + + systemd.tmpfiles.settings."10-privatebin" = + lib.attrsets.genAttrs + [ + "${cfg.dataDir}/data" + "${cfg.dataDir}/traffic" + "${cfg.dataDir}/purge" + ] + (n: { + d = { + group = group; + mode = "0750"; + user = user; + }; + }); + + users = { + users = lib.mkIf (user == defaultUser) { + ${defaultUser} = { + description = "Privatebin service user"; + inherit group; + isSystemUser = true; + home = cfg.dataDir; + }; + }; + groups = lib.mkIf (group == defaultGroup) { ${defaultGroup} = { }; }; + }; + }; +} diff --git a/nixos/modules/services/web-apps/rss-bridge.nix b/nixos/modules/services/web-apps/rss-bridge.nix index b03c7f7e8069..9c22b72d31cd 100644 --- a/nixos/modules/services/web-apps/rss-bridge.nix +++ b/nixos/modules/services/web-apps/rss-bridge.nix @@ -5,7 +5,6 @@ let poolName = "rss-bridge"; - configAttr = lib.recursiveUpdate { FileCache.path = "${cfg.dataDir}/cache/"; } cfg.config; cfgHalf = lib.mapAttrsRecursive (path: value: let envName = lib.toUpper ("RSSBRIDGE_" + lib.concatStringsSep "_" path); envValue = if lib.isList value then @@ -14,7 +13,7 @@ let lib.boolToString value else toString value; - in "fastcgi_param \"${envName}\" \"${envValue}\";") configAttr; + in if (value != null) then "fastcgi_param \"${envName}\" \"${envValue}\";" else null) cfg.config; cfgEnv = lib.concatStringsSep "\n" (lib.collect lib.isString cfgHalf); in { @@ -70,9 +69,26 @@ in }; config = mkOption { - type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ])); - default = {}; - defaultText = options.literalExpression "FileCache.path = \"\${config.services.rss-bridge.dataDir}/cache/\""; + type = types.submodule { + freeformType = (pkgs.formats.ini {}).type; + options = { + system = { + enabled_bridges = mkOption { + type = with types; nullOr (either str (listOf str)); + description = "Only enabled bridges are available for feed production"; + default = null; + }; + }; + FileCache = { + path = mkOption { + type = types.str; + description = "Directory where to store cache files (if cache.type = \"file\")."; + default = "${cfg.dataDir}/cache/"; + defaultText = options.literalExpression "\${config.services.rss-bridge.dataDir}/cache/"; + }; + }; + }; + }; example = options.literalExpression '' { system.enabled_bridges = [ "*" ]; @@ -112,15 +128,13 @@ in }; }; }; - systemd.tmpfiles.settings.rss-bridge = let - perm = { - mode = "0750"; - user = cfg.user; - group = cfg.group; - }; - in { - "${configAttr.FileCache.path}".d = perm; - "${cfg.dataDir}/config.ini.php".z = perm; + + systemd.tmpfiles.settings.rss-bridge = { + "${cfg.config.FileCache.path}".d = { + mode = "0750"; + user = cfg.user; + group = cfg.group; + }; }; services.nginx = mkIf (cfg.virtualHost != null) { @@ -139,7 +153,6 @@ in fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket}; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param RSSBRIDGE_DATA ${cfg.dataDir}; ${cfgEnv} ''; }; diff --git a/nixos/modules/services/web-apps/sftpgo.nix b/nixos/modules/services/web-apps/sftpgo.nix index 3ad6d0436afc..4d9a8e44c2b3 100644 --- a/nixos/modules/services/web-apps/sftpgo.nix +++ b/nixos/modules/services/web-apps/sftpgo.nix @@ -35,13 +35,21 @@ in }; dataDir = mkOption { - type = types.str; + type = types.path; default = "/var/lib/sftpgo"; description = '' The directory where SFTPGo stores its data files. ''; }; + extraReadWriteDirs = mkOption { + type = types.listOf types.path; + default = []; + description = '' + Extra directories where SFTPGo is allowed to write to. + ''; + }; + user = mkOption { type = types.str; default = defaultUser; @@ -63,7 +71,7 @@ in type = with types; nullOr path; description = '' Path to a json file containing users and folders to load (or update) on startup. - Check the [documentation](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md) + Check the [documentation](https://sftpgo.github.io/latest/config-file/) for the `--loaddata-from` command line argument for more info. ''; }; @@ -72,7 +80,7 @@ in default = {}; description = '' The primary sftpgo configuration. See the - [configuration reference](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md) + [configuration reference](https://sftpgo.github.io/latest/config-file/) for possible values. ''; type = with types; submodule { @@ -324,7 +332,7 @@ in User = cfg.user; Group = cfg.group; WorkingDirectory = cfg.dataDir; - ReadWritePaths = [ cfg.dataDir ]; + ReadWritePaths = [ cfg.dataDir ] ++ cfg.extraReadWriteDirs; LimitNOFILE = 8192; # taken from upstream KillMode = "mixed"; ExecStart = "${cfg.package}/bin/sftpgo serve ${utils.escapeSystemdExecArgs cfg.extraArgs}"; diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix index df3eeaef1618..bec5ab4083bb 100644 --- a/nixos/modules/services/web-apps/shiori.nix +++ b/nixos/modules/services/web-apps/shiori.nix @@ -90,7 +90,6 @@ in { "/var/run/mysqld"; CapabilityBoundingSet = ""; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; DeviceAllow = ""; diff --git a/nixos/modules/services/web-apps/snipe-it.nix b/nixos/modules/services/web-apps/snipe-it.nix index 272dd23d7271..e9a68006ae64 100644 --- a/nixos/modules/services/web-apps/snipe-it.nix +++ b/nixos/modules/services/web-apps/snipe-it.nix @@ -331,7 +331,7 @@ in { APP_CONFIG_CACHE = "/run/snipe-it/cache/config.php"; APP_ROUTES_CACHE = "/run/snipe-it/cache/routes-v7.php"; APP_EVENTS_CACHE = "/run/snipe-it/cache/events.php"; - SESSION_SECURE_COOKIE = tlsEnabled; + SECURE_COOKIES = tlsEnabled; }; services.mysql = mkIf db.createLocally { diff --git a/nixos/modules/services/web-apps/stirling-pdf.nix b/nixos/modules/services/web-apps/stirling-pdf.nix index ea7d7fbf54e5..48fe744b6014 100644 --- a/nixos/modules/services/web-apps/stirling-pdf.nix +++ b/nixos/modules/services/web-apps/stirling-pdf.nix @@ -59,6 +59,7 @@ in pngquant tesseract python3Packages.weasyprint + ghostscript_headless ] ++ lib.optional (cfg.environment.INSTALL_BOOK_AND_ADVANCED_HTML_OPS or "false" == "true") calibre; diff --git a/nixos/modules/services/web-apps/wakapi.nix b/nixos/modules/services/web-apps/wakapi.nix index 2dbfb6d9b3d5..982b572fb2fa 100644 --- a/nixos/modules/services/web-apps/wakapi.nix +++ b/nixos/modules/services/web-apps/wakapi.nix @@ -66,6 +66,60 @@ in The path to a file containing the password for the smtp mailer used by Wakapi. ''; }; + + database = { + createLocally = mkEnableOption '' + automatic database configuration. + + ::: {.note} + Only PostgreSQL is supported for the time being. + ::: + ''; + + dialect = mkOption { + type = types.nullOr ( + types.enum [ + "postgres" + "sqlite3" + "mysql" + "cockroach" + "mssql" + ] + ); + default = cfg.settings.db.dialect or null; # handle case where dialect is not set + defaultText = '' + Database dialect from settings if {option}`services.wakatime.settings.db.dialect` + is set, or `null` otherwise. + ''; + description = '' + The database type to use for Wakapi. + ''; + }; + + name = mkOption { + type = types.str; + default = cfg.settings.db.name or "wakapi"; + defaultText = '' + Database name from settings if {option}`services.wakatime.settings.db.name` + is set, or "wakapi" otherwise. + ''; + description = '' + The name of the database to use for Wakapi. + ''; + }; + + user = mkOption { + type = types.str; + default = cfg.settings.db.user or "wakapi"; + defaultText = '' + User from settings if {option}`services.wakatime.settings.db.user` + is set, or "wakapi" otherwise. + ''; + description = '' + The name of the user to use for Wakapi. + ''; + }; + }; }; config = mkIf cfg.enable { @@ -73,10 +127,10 @@ in description = "Wakapi (self-hosted WakaTime-compatible backend)"; wants = [ "network-online.target" - ] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service"; + ] ++ optional (cfg.database.dialect == "postgres") "postgresql.service"; after = [ "network-online.target" - ] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service"; + ] ++ optional (cfg.database.dialect == "postgres") "postgresql.service"; wantedBy = [ "multi-user.target" ]; script = '' @@ -112,6 +166,7 @@ in RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; + StateDirectory = "wakapi"; StateDirectoryMode = "0700"; Restart = "always"; }; @@ -127,13 +182,26 @@ in message = "Either `services.wakapi.passwordSalt` or `services.wakapi.passwordSaltFile` must be set."; } { - assertion = cfg.passwordSalt != null -> cfg.passwordSaltFile != null; + assertion = !(cfg.passwordSalt != null && cfg.passwordSaltFile != null); message = "Both `services.wakapi.passwordSalt` `services.wakapi.passwordSaltFile` should not be set at the same time."; } { - assertion = cfg.smtpPassword != null -> cfg.smtpPasswordFile != null; + assertion = !(cfg.smtpPassword != null && cfg.smtpPasswordFile != null); message = "Both `services.wakapi.smtpPassword` `services.wakapi.smtpPasswordFile` should not be set at the same time."; } + { + assertion = cfg.database.createLocally -> cfg.settings.db.dialect != null; + message = "`services.wakapi.database.createLocally` is true, but a database dialect is not set!"; + } + ]; + + warnings = [ + (lib.optionalString (cfg.database.createLocally -> cfg.settings.db.dialect != "postgres") '' + You have enabled automatic database configuration, but the database dialect is not set to "posgres". + + The Wakapi module only supports for PostgreSQL. Please set `services.wakapi.database.createLocally` + to `false`, or switch to "postgres" as your database dialect. + '') ]; users = { @@ -145,10 +213,10 @@ in groups.wakapi = { }; }; - services.postgresql = mkIf (cfg.settings.db.dialect == "postgres") { + services.postgresql = mkIf (cfg.database.createLocally && cfg.database.dialect == "postgres") { enable = true; - ensureDatabases = singleton cfg.settings.db.name; + ensureDatabases = singleton cfg.database.name; ensureUsers = singleton { name = cfg.settings.db.user; ensureDBOwnership = true; @@ -160,5 +228,8 @@ in }; }; - meta.maintainers = with lib.maintainers; [ isabelroses ]; + meta.maintainers = with lib.maintainers; [ + isabelroses + NotAShelf + ]; } diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix index 38b5e2c52d89..d03df9afe63d 100644 --- a/nixos/modules/services/web-apps/wordpress.nix +++ b/nixos/modules/services/web-apps/wordpress.nix @@ -81,10 +81,10 @@ let in if isString v then toPhpString v # NOTE: If any value contains a , (comma) this will not get escaped - else if isList v && any lib.strings.isCoercibleToString v then toPhpString (concatMapStringsSep "," toString v) + else if isList v && strings.isConvertibleWithToString v then toPhpString (concatMapStringsSep "," toString v) else if isInt v then toString v else if isBool v then boolToString v - else if isHasAttr "_file" then "trim(file_get_contents(${toPhpString v._file}))" + else if isHasAttr "_file" then "trim(file_get_contents(${toPhpString (toString v._file)}))" else if isHasAttr "_raw" then v._raw else abort "The Wordpress config value ${lib.generators.toPretty {} v} can not be encoded." ; @@ -154,8 +154,8 @@ let (l: warn "setting this option with a list is deprecated" listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l)) (attrsOf path); - default = { inherit (pkgs.wordpressPackages.themes) twentytwentythree; }; - defaultText = literalExpression "{ inherit (pkgs.wordpressPackages.themes) twentytwentythree; }"; + default = { inherit (pkgs.wordpressPackages.themes) twentytwentyfour; }; + defaultText = literalExpression "{ inherit (pkgs.wordpressPackages.themes) twentytwentyfour; }"; description = '' Path(s) to respective theme(s) which are copied from the 'theme' directory. diff --git a/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix index ff48a978b734..e80cdda68087 100644 --- a/nixos/modules/services/web-apps/youtrack.nix +++ b/nixos/modules/services/web-apps/youtrack.nix @@ -9,6 +9,8 @@ in (lib.mkRenamedOptionModule [ "services" "youtrack" "port" ] [ "services" "youtrack" "environmentalParameters" "listen-port" ]) (lib.mkRemovedOptionModule [ "services" "youtrack" "maxMemory" ] "Please instead use `services.youtrack.generalParameters`.") (lib.mkRemovedOptionModule [ "services" "youtrack" "maxMetaspaceSize" ] "Please instead use `services.youtrack.generalParameters`.") + (lib.mkRemovedOptionModule [ "services" "youtrack" "extraParams" ] "Please migrate to `services.youtrack.generalParameters`.") + (lib.mkRemovedOptionModule [ "services" "youtrack" "jvmOpts" ] "Please migrate to `services.youtrack.generalParameters`.") ]; options.services.youtrack = { @@ -22,33 +24,15 @@ in type = lib.types.str; }; - extraParams = lib.mkOption { - default = {}; - description = '' - Extra parameters to pass to youtrack. - Use to configure YouTrack 2022.x, deprecated with YouTrack 2023.x. Use `services.youtrack.generalParameters`. - https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html - for more information. - ''; - example = lib.literalExpression '' - { - "jetbrains.youtrack.overrideRootPassword" = "tortuga"; - } - ''; - type = lib.types.attrsOf lib.types.str; - visible = false; - }; - package = lib.mkOption { description = '' Package to use. ''; type = lib.types.package; - default = null; - relatedPackages = [ "youtrack_2022_3" "youtrack" ]; + default = pkgs.youtrack; + defaultText = lib.literalExpression "pkgs.youtrack"; }; - statePath = lib.mkOption { description = '' Path were the YouTrack state is stored. @@ -67,19 +51,6 @@ in type = lib.types.nullOr lib.types.str; }; - jvmOpts = lib.mkOption { - description = '' - Extra options to pass to the JVM. - Only has a use with YouTrack 2022.x, deprecated with YouTrack 2023.x. Use `serivces.youtrack.generalParameters`. - See https://www.jetbrains.com/help/youtrack/standalone/Configure-JVM-Options.html - for more information. - ''; - type = lib.types.separatedString " "; - example = "--J-XX:MetaspaceSize=250m"; - default = ""; - visible = false; - }; - autoUpgrade = lib.mkOption { type = lib.types.bool; default = true; @@ -90,7 +61,6 @@ in type = with lib.types; listOf str; description = '' General configuration parameters and other JVM options. - Only has an effect for YouTrack 2023.x. See https://www.jetbrains.com/help/youtrack/server/2023.3/youtrack-java-start-parameters.html#general-parameters for more information. ''; @@ -121,7 +91,6 @@ in }; description = '' Environmental configuration parameters, set imperatively. The values doesn't get removed, when removed in Nix. - Only has an effect for YouTrack 2023.x. See https://www.jetbrains.com/help/youtrack/server/2023.3/youtrack-java-start-parameters.html#environmental-parameters for more information. ''; @@ -135,90 +104,47 @@ in }; config = lib.mkIf cfg.enable { - warnings = lib.optional (lib.versions.major cfg.package.version <= "2022") - "YouTrack 2022.x is deprecated. See https://nixos.org/manual/nixos/unstable/index.html#module-services-youtrack for details on how to upgrade." - ++ lib.optional (cfg.extraParams != {} && (lib.versions.major cfg.package.version >= "2023")) - "'services.youtrack.extraParams' is deprecated and has no effect on YouTrack 2023.x and newer. Please migrate to 'services.youtrack.generalParameters'" - ++ lib.optional (cfg.jvmOpts != "" && (lib.versions.major cfg.package.version >= "2023")) - "'services.youtrack.jvmOpts' is deprecated and has no effect on YouTrack 2023.x and newer. Please migrate to 'services.youtrack.generalParameters'"; - - # XXX: Drop all version feature switches at the point when we consider YT 2022.3 as outdated. - services.youtrack.package = lib.mkDefault ( - if lib.versionAtLeast config.system.stateVersion "24.11" then pkgs.youtrack - else pkgs.youtrack_2022_3 - ); - - services.youtrack.generalParameters = lib.optional (lib.versions.major cfg.package.version >= "2023") - "-Ddisable.configuration.wizard.on.upgrade=${lib.boolToString cfg.autoUpgrade}" - ++ (lib.mapAttrsToList (k: v: "-D${k}=${v}") cfg.extraParams); + services.youtrack.generalParameters = [ "-Ddisable.configuration.wizard.on.upgrade=${lib.boolToString cfg.autoUpgrade}" ]; systemd.services.youtrack = let - service_jar = let - mergeAttrList = lib.foldl' lib.mergeAttrs {}; - stdParams = mergeAttrList [ - (lib.optionalAttrs (cfg.environmentalParameters ? base-url && cfg.environmentalParameters.base-url != null) { - "jetbrains.youtrack.baseUrl" = cfg.environmentalParameters.base-url; - }) - { - "java.aws.headless" = "true"; - "jetbrains.youtrack.disableBrowser" = "true"; - } - ]; - extraAttr = lib.concatStringsSep " " (lib.mapAttrsToList (k: v: "-D${k}=${v}") (stdParams // cfg.extraParams)); - in { - environment.HOME = cfg.statePath; - environment.YOUTRACK_JVM_OPTS = "${extraAttr}"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ unixtools.hostname ]; - serviceConfig = { + jvmoptions = pkgs.writeTextFile { + name = "youtrack.jvmoptions"; + text = (lib.concatStringsSep "\n" cfg.generalParameters); + }; + + package = cfg.package.override { + statePath = cfg.statePath; + }; + in { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ unixtools.hostname ]; + preStart = '' + # This detects old (i.e. <= 2022.3) installations that were not migrated yet + # and migrates them to the new state directory style + if [[ -d ${cfg.statePath}/teamsysdata ]] && [[ ! -d ${cfg.statePath}/2022_3 ]] + then + mkdir -p ${cfg.statePath}/2022_3 + mv ${cfg.statePath}/teamsysdata ${cfg.statePath}/2022_3 + mv ${cfg.statePath}/.youtrack ${cfg.statePath}/2022_3 + fi + mkdir -p ${cfg.statePath}/{backups,conf,data,logs,temp} + ${pkgs.coreutils}/bin/ln -fs ${jvmoptions} ${cfg.statePath}/conf/youtrack.jvmoptions + ${package}/bin/youtrack configure ${lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "--${name}=${toString value}") cfg.environmentalParameters )} + ''; + serviceConfig = lib.mkMerge [ + { Type = "simple"; User = "youtrack"; Group = "youtrack"; Restart = "on-failure"; - ExecStart = ''${cfg.package}/bin/youtrack ${cfg.jvmOpts} ${cfg.environmentalParameters.listen-address}:${toString cfg.environmentalParameters.listen-port}''; - }; - }; - service_zip = let - jvmoptions = pkgs.writeTextFile { - name = "youtrack.jvmoptions"; - text = (lib.concatStringsSep "\n" cfg.generalParameters); - }; - - package = cfg.package.override { - statePath = cfg.statePath; - }; - in { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ unixtools.hostname ]; - preStart = '' - # This detects old (i.e. <= 2022.3) installations that were not migrated yet - # and migrates them to the new state directory style - if [[ -d ${cfg.statePath}/teamsysdata ]] && [[ ! -d ${cfg.statePath}/2022_3 ]] - then - mkdir -p ${cfg.statePath}/2022_3 - mv ${cfg.statePath}/teamsysdata ${cfg.statePath}/2022_3 - mv ${cfg.statePath}/.youtrack ${cfg.statePath}/2022_3 - fi - mkdir -p ${cfg.statePath}/{backups,conf,data,logs,temp} - ${pkgs.coreutils}/bin/ln -fs ${jvmoptions} ${cfg.statePath}/conf/youtrack.jvmoptions - ${package}/bin/youtrack configure ${lib.concatStringsSep " " (lib.mapAttrsToList (name: value: "--${name}=${toString value}") cfg.environmentalParameters )} - ''; - serviceConfig = lib.mkMerge [ - { - Type = "simple"; - User = "youtrack"; - Group = "youtrack"; - Restart = "on-failure"; - ExecStart = "${package}/bin/youtrack run"; - } - (lib.mkIf (cfg.statePath == "/var/lib/youtrack") { - StateDirectory = "youtrack"; - }) - ]; - }; - in if (lib.versions.major cfg.package.version >= "2023") then service_zip else service_jar; + ExecStart = "${package}/bin/youtrack run"; + } + (lib.mkIf (cfg.statePath == "/var/lib/youtrack") { + StateDirectory = "youtrack"; + }) + ]; + }; users.users.youtrack = { description = "Youtrack service user"; diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index 46cb09959579..1ac86c1a5c1d 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -33,7 +33,9 @@ let certName = if hostOpts.useACMEHost != null then hostOpts.useACMEHost else hostOpts.hostName; }) (filter (hostOpts: hostOpts.enableACME || hostOpts.useACMEHost != null) vhosts); - dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); + vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); + dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server + independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server mkListenInfo = hostOpts: if hostOpts.listen != [] then @@ -371,7 +373,7 @@ let echo "$options" >> $out ''; - mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; + mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib; in @@ -641,10 +643,10 @@ in ''; } ] ++ map (name: mkCertOwnershipAssertion { - inherit (cfg) group user; cert = config.security.acme.certs.${name}; groups = config.users.groups; - }) dependentCertNames; + services = [ config.systemd.services.httpd ] ++ lib.optional (vhostCertNames != []) config.systemd.services.httpd-config-reload; + }) vhostCertNames; warnings = mapAttrsToList (name: hostOpts: '' @@ -747,8 +749,10 @@ in systemd.services.httpd = { description = "Apache HTTPD"; wantedBy = [ "multi-user.target" ]; - wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames); - after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames; + wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames); + after = [ "network.target" ] + ++ map (certName: "acme-selfsigned-${certName}.service") vhostCertNames + ++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa before = map (certName: "acme-${certName}.service") dependentCertNames; restartTriggers = [ cfg.configFile ]; @@ -789,9 +793,9 @@ in # which allows the acme-finished-$cert.target to signify the successful updating # of certs end-to-end. systemd.services.httpd-config-reload = let - sslServices = map (certName: "acme-${certName}.service") dependentCertNames; - sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames; - in mkIf (sslServices != []) { + sslServices = map (certName: "acme-${certName}.service") vhostCertNames; + sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames; + in mkIf (vhostCertNames != []) { wantedBy = sslServices ++ [ "multi-user.target" ]; # Before the finished targets, after the renew services. # This service might be needed for HTTP-01 challenges, but we only want to confirm @@ -801,7 +805,7 @@ in restartTriggers = [ cfg.configFile ]; # Block reloading if not all certs exist yet. # Happens when config changes add new vhosts/certs. - unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames; + unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames; serviceConfig = { Type = "oneshot"; TimeoutSec = 60; diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix index 496705beff7b..8f8a4da35cc5 100644 --- a/nixos/modules/services/web-servers/caddy/default.nix +++ b/nixos/modules/services/web-servers/caddy/default.nix @@ -5,8 +5,12 @@ with lib; let cfg = config.services.caddy; + certs = config.security.acme.certs; virtualHosts = attrValues cfg.virtualHosts; - acmeVHosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts; + acmeEnabledVhosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts; + vhostCertNames = unique (map (hostOpts: hostOpts.useACMEHost) acmeEnabledVhosts); + dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server + independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server mkVHostConf = hostOpts: let @@ -51,9 +55,7 @@ let configPath = "/etc/${etcConfigFile}"; - acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts); - - mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; + mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib; in { imports = [ @@ -329,10 +331,10 @@ in message = "To specify an adapter other than 'caddyfile' please provide your own configuration via `services.caddy.configFile`"; } ] ++ map (name: mkCertOwnershipAssertion { - inherit (cfg) group user; cert = config.security.acme.certs.${name}; groups = config.users.groups; - }) acmeHosts; + services = [ config.systemd.services.caddy ]; + }) vhostCertNames; services.caddy.globalConfig = '' ${optionalString (cfg.email != null) "email ${cfg.email}"} @@ -348,9 +350,10 @@ in systemd.packages = [ cfg.package ]; systemd.services.caddy = { - wants = map (hostOpts: "acme-finished-${hostOpts.useACMEHost}.target") acmeVHosts; - after = map (hostOpts: "acme-selfsigned-${hostOpts.useACMEHost}.service") acmeVHosts; - before = map (hostOpts: "acme-${hostOpts.useACMEHost}.service") acmeVHosts; + wants = map (certName: "acme-finished-${certName}.target") vhostCertNames; + after = map (certName: "acme-selfsigned-${certName}.service") vhostCertNames + ++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa + before = map (certName: "acme-${certName}.service") dependentCertNames; wantedBy = [ "multi-user.target" ]; startLimitIntervalSec = 14400; @@ -397,10 +400,10 @@ in security.acme.certs = let - certCfg = map (useACMEHost: nameValuePair useACMEHost { + certCfg = map (certName: nameValuePair certName { group = mkDefault cfg.group; reloadServices = [ "caddy.service" ]; - }) acmeHosts; + }) vhostCertNames; in listToAttrs certCfg; diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix index 7cf71ff6ff06..05c92b1a387b 100644 --- a/nixos/modules/services/web-servers/garage.nix +++ b/nixos/modules/services/web-servers/garage.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; @@ -6,11 +11,18 @@ let cfg = config.services.garage; toml = pkgs.formats.toml { }; configFile = toml.generate "garage.toml" cfg.settings; + + anyHasPrefix = + prefix: strOrList: + if isString strOrList then + hasPrefix prefix strOrList + else + any ({ path, ... }: hasPrefix prefix path) strOrList; in { meta = { doc = ./garage.md; - maintainers = [ ]; + maintainers = [ maintainers.mjm ]; }; options.services.garage = { @@ -20,7 +32,9 @@ in type = types.attrsOf types.str; description = "Extra environment variables to pass to the Garage server."; default = { }; - example = { RUST_BACKTRACE = "yes"; }; + example = { + RUST_BACKTRACE = "yes"; + }; }; environmentFile = mkOption { @@ -30,7 +44,13 @@ in }; logLevel = mkOption { - type = types.enum ([ "error" "warn" "info" "debug" "trace" ]); + type = types.enum ([ + "error" + "warn" + "info" + "debug" + "trace" + ]); default = "info"; example = "debug"; description = "Garage log level, see <https://garagehq.deuxfleurs.fr/documentation/quick-start/#launching-the-garage-server> for examples."; @@ -49,10 +69,12 @@ in data_dir = mkOption { default = "/var/lib/garage/data"; - example = [ { - path = "/var/lib/garage/data"; - capacity = "2T"; - } ]; + example = [ + { + path = "/var/lib/garage/data"; + capacity = "2T"; + } + ]; type = with types; either path (listOf attrs); description = '' The directory in which Garage will store the data blocks of objects. This folder can be placed on an HDD. @@ -83,7 +105,9 @@ in # These assertions can be removed in NixOS 24.11, when all users have been # warned once. { - assertion = (cfg.settings ? replication_factor || cfg.settings ? replication_mode) || lib.versionOlder cfg.package.version "1.0.0"; + assertion = + (cfg.settings ? replication_factor || cfg.settings ? replication_mode) + || lib.versionOlder cfg.package.version "1.0.0"; message = '' Garage 1.0.0 requires an explicit replication factor to be set. Please set replication_factor to 1 explicitly to preserve the previous behavior. @@ -129,14 +153,25 @@ in systemd.services.garage = { description = "Garage Object Storage (S3 compatible)"; - after = [ "network.target" "network-online.target" ]; - wants = [ "network.target" "network-online.target" ]; + after = [ + "network.target" + "network-online.target" + ]; + wants = [ + "network.target" + "network-online.target" + ]; wantedBy = [ "multi-user.target" ]; - restartTriggers = [ configFile ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile); + restartTriggers = [ + configFile + ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile); serviceConfig = { ExecStart = "${cfg.package}/bin/garage server"; - StateDirectory = mkIf (hasPrefix "/var/lib/garage" cfg.settings.data_dir || hasPrefix "/var/lib/garage" cfg.settings.metadata_dir) "garage"; + StateDirectory = mkIf ( + anyHasPrefix "/var/lib/garage" cfg.settings.data_dir + || hasPrefix "/var/lib/garage" cfg.settings.metadata_dir + ) "garage"; DynamicUser = lib.mkDefault true; ProtectHome = true; NoNewPrivileges = true; diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index b5ff630a4d48..922df1ea03ab 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -7,7 +7,9 @@ let inherit (config.security.acme) certs; vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts; acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME || vhostConfig.useACMEHost != null) vhostsConfigs; - dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); + vhostCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); + dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server + independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server virtualHosts = mapAttrs (vhostName: vhostConfig: let serverName = if vhostConfig.serverName != null @@ -129,12 +131,9 @@ let '')); commonHttpConfig = '' - # Load mime types. + # Load mime types and configure maximum size of the types hash tables. include ${cfg.defaultMimeTypes}; - # When recommendedOptimisation is disabled nginx fails to start because the mailmap mime.types database - # contains 1026 entries and the default is only 1024. Setting to a higher number to remove the need to - # overwrite it because nginx does not allow duplicated settings. - types_hash_max_size 4096; + types_hash_max_size ${toString cfg.typesHashMaxSize}; include ${cfg.package}/conf/fastcgi.conf; include ${cfg.package}/conf/uwsgi_params; @@ -474,7 +473,7 @@ let '') authDef) ); - mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; + mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib; oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic")); in @@ -856,9 +855,12 @@ in type = types.bool; default = false; description = '' - Resolves domains of proxyPass targets at runtime - and not only at start, you have to set - services.nginx.resolver, too. + Resolves domains of proxyPass targets at runtime and not only at startup. + This can be used as a workaround if nginx fails to start because of not-yet-working DNS. + + :::{.warn} + `services.nginx.resolver` must be set for this option to work. + ::: ''; }; @@ -896,6 +898,19 @@ in ''; }; + typesHashMaxSize = mkOption { + type = types.ints.positive; + default = if cfg.defaultMimeTypes == "${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024; + defaultText = literalExpression ''if config.services.nginx.defaultMimeTypes == "''${pkgs.mailcap}/etc/nginx/mime.types" then 2688 else 1024''; + description = '' + Sets the maximum size of the types hash tables (`types_hash_max_size`). + It is recommended that the minimum size possible size is used. + If {option}`recommendedOptimisation` is disabled, nginx would otherwise + fail to start since the mailmap `mime.types` database has more entries + than the nginx default value 1024. + ''; + }; + proxyCachePath = mkOption { type = types.attrsOf (types.submodule ({ ... }: { options = { @@ -1061,7 +1076,7 @@ in ''; }; "memcached" = { - servers."unix:/run//memcached/memcached.sock" = {}; + servers."unix:/run/memcached/memcached.sock" = {}; }; }; }; @@ -1138,14 +1153,6 @@ in } { - assertion = any (host: host.rejectSSL) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.19.4"; - message = '' - services.nginx.virtualHosts.<name>.rejectSSL requires nginx version - 1.19.4 or above; see the documentation for services.nginx.package. - ''; - } - - { assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts); message = '' Options services.nginx.service.virtualHosts.<name>.enableACME and @@ -1204,15 +1211,16 @@ in ''; } ] ++ map (name: mkCertOwnershipAssertion { - inherit (cfg) group user; cert = config.security.acme.certs.${name}; groups = config.users.groups; - }) dependentCertNames; + services = [ config.systemd.services.nginx ] ++ lib.optional (cfg.enableReload || vhostCertNames != []) config.systemd.services.nginx-config-reload; + }) vhostCertNames; services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli ++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd; services.nginx.virtualHosts.localhost = mkIf cfg.statusPage { + serverAliases = [ "127.0.0.1" ] ++ lib.optional config.networking.enableIPv6 "[::1]"; listenAddresses = lib.mkDefault ([ "0.0.0.0" ] ++ lib.optional enableIPv6 "[::]"); @@ -1230,8 +1238,10 @@ in systemd.services.nginx = { description = "Nginx Web Server"; wantedBy = [ "multi-user.target" ]; - wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames); - after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames; + wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) vhostCertNames); + after = [ "network.target" ] + ++ map (certName: "acme-selfsigned-${certName}.service") vhostCertNames + ++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa # Nginx needs to be started in order to be able to request certificates # (it's hosting the acme challenge after all) # This fixes https://github.com/NixOS/nixpkgs/issues/81842 @@ -1310,9 +1320,9 @@ in # which allows the acme-finished-$cert.target to signify the successful updating # of certs end-to-end. systemd.services.nginx-config-reload = let - sslServices = map (certName: "acme-${certName}.service") dependentCertNames; - sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames; - in mkIf (cfg.enableReload || sslServices != []) { + sslServices = map (certName: "acme-${certName}.service") vhostCertNames; + sslTargets = map (certName: "acme-finished-${certName}.target") vhostCertNames; + in mkIf (cfg.enableReload || vhostCertNames != []) { wants = optionals cfg.enableReload [ "nginx.service" ]; wantedBy = sslServices ++ [ "multi-user.target" ]; # Before the finished targets, after the renew services. @@ -1323,7 +1333,7 @@ in restartTriggers = optionals cfg.enableReload [ configFile ]; # Block reloading if not all certs exist yet. # Happens when config changes add new vhosts/certs. - unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames); + unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") vhostCertNames); serviceConfig = { Type = "oneshot"; TimeoutSec = 60; @@ -1368,7 +1378,7 @@ in ]; services.logrotate.settings.nginx = mapAttrs (_: mkDefault) { - files = "/var/log/nginx/*.log"; + files = [ "/var/log/nginx/*.log" ]; frequency = "weekly"; su = "${cfg.user} ${cfg.group}"; rotate = 26; diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix index 8cefd481d3f9..da55dc92f596 100644 --- a/nixos/modules/services/web-servers/nginx/location-options.nix +++ b/nixos/modules/services/web-servers/nginx/location-options.nix @@ -30,10 +30,7 @@ with lib; default = null; description = '' Basic Auth password file for a vhost. - Can be created via: {command}`htpasswd -c <filename> <username>`. - - WARNING: The generate file contains the users' passwords in a - non-cryptographically-securely hashed way. + Can be created by running {command}`nix-shell --packages apacheHttpd --run 'htpasswd -B -c FILENAME USERNAME'`. ''; }; diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 24fcb101c910..895fa8499715 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -345,10 +345,7 @@ with lib; default = null; description = '' Basic Auth password file for a vhost. - Can be created via: {command}`htpasswd -c <filename> <username>`. - - WARNING: The generate file contains the users' passwords in a - non-cryptographically-securely hashed way. + Can be created by running {command}`nix-shell --packages apacheHttpd --run 'htpasswd -B -c FILENAME USERNAME'`. ''; }; diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix index ca77a0838f55..2312fab2702b 100644 --- a/nixos/modules/services/web-servers/phpfpm/default.nix +++ b/nixos/modules/services/web-servers/phpfpm/default.nix @@ -239,8 +239,8 @@ in { daemonize = false; }; - systemd.slices.phpfpm = { - description = "PHP FastCGI Process manager pools slice"; + systemd.slices.system-phpfpm = { + description = "PHP FastCGI Process Manager Slice"; }; systemd.targets.phpfpm = { @@ -258,7 +258,7 @@ in { cfgFile = fpmCfgFile pool poolOpts; iniFile = phpIni poolOpts; in { - Slice = "phpfpm.slice"; + Slice = "system-phpfpm.slice"; PrivateDevices = true; PrivateTmp = true; ProtectSystem = "full"; diff --git a/nixos/modules/services/web-servers/send.nix b/nixos/modules/services/web-servers/send.nix new file mode 100644 index 000000000000..696fbbdc7c80 --- /dev/null +++ b/nixos/modules/services/web-servers/send.nix @@ -0,0 +1,228 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) mkOption types; + cfg = config.services.send; +in +{ + options = { + services.send = { + enable = lib.mkEnableOption "Send, a file sharing web sevice for ffsend."; + + package = lib.mkPackageOption pkgs "send" { }; + + environment = mkOption { + type = + with types; + attrsOf ( + nullOr (oneOf [ + bool + int + str + (listOf int) + ]) + ); + description = '' + All the available config options and their defaults can be found here: https://github.com/timvisee/send/blob/master/server/config.js, + some descriptions can found here: https://github.com/timvisee/send/blob/master/docs/docker.md#environment-variables + + Values under {option}`services.send.environment` will override the predefined values in the Send service. + - Time/duration should be in seconds + - Filesize values should be in bytes + ''; + example = { + DEFAULT_DOWNLOADS = 1; + DETECT_BASE_URL = true; + EXPIRE_TIMES_SECONDS = [ + 300 + 3600 + 86400 + 604800 + ]; + }; + }; + + dataDir = lib.mkOption { + type = types.path; + readOnly = true; + default = "/var/lib/send"; + description = '' + Directory for uploaded files. + Due to limitations in {option}`systemd.services.send.serviceConfig.DynamicUser`, this item is read only. + ''; + }; + + baseUrl = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Base URL for the Send service. + Leave it blank to automatically detect the base url. + ''; + }; + + host = lib.mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The hostname or IP address for Send to bind to."; + }; + + port = lib.mkOption { + type = types.port; + default = 1443; + description = "Port the Send service listens on."; + }; + + openFirewall = lib.mkOption { + type = types.bool; + default = false; + description = "Whether to open firewall ports for send"; + }; + + redis = { + createLocally = lib.mkOption { + type = types.bool; + default = true; + description = "Whether to create a local redis automatically."; + }; + + name = lib.mkOption { + type = types.str; + default = "send"; + description = '' + Name of the redis server. + Only used if {option}`services.send.redis.createLocally` is set to true. + ''; + }; + + host = lib.mkOption { + type = types.str; + default = "localhost"; + description = "Redis server address."; + }; + + port = lib.mkOption { + type = types.port; + default = 6379; + description = "Port of the redis server."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/agenix/send-redis-password"; + description = '' + The path to the file containing the Redis password. + + If {option}`services.send.redis.createLocally` is set to true, + the content of this file will be used as the password for the locally created Redis instance. + + Leave it blank if no password is required. + ''; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + + services.send.environment.DETECT_BASE_URL = cfg.baseUrl == null; + + assertions = [ + { + assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost"; + message = "the redis host must be localhost if services.send.redis.createLocally is set to true"; + } + ]; + + networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port; + + services.redis = lib.optionalAttrs cfg.redis.createLocally { + servers."${cfg.redis.name}" = { + enable = true; + bind = "localhost"; + port = cfg.redis.port; + }; + }; + + systemd.services.send = { + serviceConfig = { + Type = "simple"; + Restart = "always"; + StateDirectory = "send"; + WorkingDirectory = cfg.dataDir; + ReadWritePaths = cfg.dataDir; + LoadCredential = lib.optionalString ( + cfg.redis.passwordFile != null + ) "redis-password:${cfg.redis.passwordFile}"; + + # Hardening + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + AmbientCapabilities = lib.optionalString (cfg.port < 1024) "cap_net_bind_service"; + DynamicUser = true; + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + RemoveIPC = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "full"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = "0077"; + }; + environment = + { + IP_ADDRESS = cfg.host; + PORT = toString cfg.port; + BASE_URL = if (cfg.baseUrl == null) then "http://${cfg.host}:${toString cfg.port}" else cfg.baseUrl; + FILE_DIR = cfg.dataDir + "/uploads"; + REDIS_HOST = cfg.redis.host; + REDIS_PORT = toString cfg.redis.port; + } + // (lib.mapAttrs ( + name: value: + if lib.isList value then + "[" + lib.concatStringsSep ", " (map (x: toString x) value) + "]" + else if lib.isBool value then + lib.boolToString value + else + toString value + ) cfg.environment); + after = + [ + "network.target" + ] + ++ lib.optionals cfg.redis.createLocally [ + "redis-${cfg.redis.name}.service" + ]; + description = "Send web service"; + wantedBy = [ "multi-user.target" ]; + script = '' + ${lib.optionalString (cfg.redis.passwordFile != null) '' + export REDIS_PASSWORD="$(cat $CREDENTIALS_DIRECTORY/redis-password)" + ''} + ${lib.getExe cfg.package} + ''; + }; + }; + + meta.maintainers = with lib.maintainers; [ moraxyc ]; +} diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix index cd5c9e1c948f..7f824680b92e 100644 --- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix +++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix @@ -192,10 +192,7 @@ in xdg.portal.enable = true; xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-xapp - (pkgs.xdg-desktop-portal-gtk.override { - # Do not build portals that we already have. - buildPortalsInGnome = false; - }) + pkgs.xdg-desktop-portal-gtk ]; services.orca.enable = mkDefault (notExcluded pkgs.orca); diff --git a/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix index 70149bcf4ae0..4829990acc4f 100644 --- a/nixos/modules/services/x11/desktop-managers/deepin.nix +++ b/nixos/modules/services/x11/desktop-managers/deepin.nix @@ -74,9 +74,7 @@ in xdg.icons.enable = true; xdg.portal.enable = mkDefault true; xdg.portal.extraPortals = mkDefault [ - (pkgs.xdg-desktop-portal-gtk.override { - buildPortalsInGnome = false; - }) + pkgs.xdg-desktop-portal-gtk ]; # https://github.com/NixOS/nixpkgs/pull/247766#issuecomment-1722839259 diff --git a/nixos/modules/services/x11/desktop-managers/gnome.md b/nixos/modules/services/x11/desktop-managers/gnome.md index 6641bc9ff43b..f959c0912652 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome.md +++ b/nixos/modules/services/x11/desktop-managers/gnome.md @@ -39,16 +39,16 @@ Note that this mechanism can only exclude core utilities, games and core develop ### Disabling GNOME services {#sec-gnome-disabling-services} -It is also possible to disable many of the [core services](https://github.com/NixOS/nixpkgs/blob/b8ec4fd2a4edc4e30d02ba7b1a2cc1358f3db1d5/nixos/modules/services/x11/desktop-managers/gnome.nix#L329-L348). For example, if you do not need indexing files, you can disable Tracker with: +It is also possible to disable many of the [core services](https://github.com/NixOS/nixpkgs/blob/b8ec4fd2a4edc4e30d02ba7b1a2cc1358f3db1d5/nixos/modules/services/x11/desktop-managers/gnome.nix#L329-L348). For example, if you do not need indexing files, you can disable TinySPARQL with: ```nix { - services.gnome.tracker-miners.enable = false; - services.gnome.tracker.enable = false; + services.gnome.localsearch.enable = false; + services.gnome.tinysparql.enable = false; } ``` -Note, however, that doing so is not supported and might break some applications. Notably, GNOME Music cannot work without Tracker. +Note, however, that doing so is not supported and might break some applications. Notably, GNOME Music cannot work without TinySPARQL. ### GNOME games {#sec-gnome-games} diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix index bb2ce03e73a3..cbbce8468569 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome.nix @@ -265,8 +265,8 @@ in services.gnome.evolution-data-server.enable = true; services.gnome.gnome-keyring.enable = true; services.gnome.gnome-online-accounts.enable = mkDefault true; - services.gnome.tracker-miners.enable = mkDefault true; - services.gnome.tracker.enable = mkDefault true; + services.gnome.localsearch.enable = mkDefault true; + services.gnome.tinysparql.enable = mkDefault true; services.hardware.bolt.enable = mkDefault true; # TODO: Enable once #177946 is resolved # services.packagekit.enable = mkDefault true; @@ -281,10 +281,7 @@ in xdg.portal.enable = true; xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gnome - (pkgs.xdg-desktop-portal-gtk.override { - # Do not build portals that we already have. - buildPortalsInGnome = false; - }) + pkgs.xdg-desktop-portal-gtk ]; xdg.portal.configPackages = mkDefault [ pkgs.gnome-session ]; @@ -386,6 +383,7 @@ in pkgs.gnome-menus pkgs.gtk3.out # for gtk-launch program pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/ + pkgs.xdg-user-dirs-gtk # Used to create the default bookmarks ]; in mandatoryPackages diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix index 98d3555ccbc5..8810b5f8c8b0 100644 --- a/nixos/modules/services/x11/desktop-managers/xfce.nix +++ b/nixos/modules/services/x11/desktop-managers/xfce.nix @@ -80,7 +80,7 @@ in }; config = mkIf cfg.enable { - environment.systemPackages = utils.removePackagesByName (with pkgs.xfce // pkgs; [ + environment.systemPackages = utils.removePackagesByName (with pkgs; [ glib # for gsettings gtk3.out # gtk-update-icon-cache @@ -88,7 +88,7 @@ in adwaita-icon-theme hicolor-icon-theme tango-icon-theme - xfce4-icon-theme + xfce.xfce4-icon-theme desktop-file-utils shared-mime-info # for update-mime-database @@ -99,37 +99,37 @@ in # Needed by Xfce's xinitrc script xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/ - exo - garcon - libxfce4ui - - mousepad - parole - ristretto - xfce4-appfinder - xfce4-notifyd - xfce4-screenshooter - xfce4-session - xfce4-settings - xfce4-taskmanager - xfce4-terminal + xfce.exo + xfce.garcon + xfce.libxfce4ui + + xfce.mousepad + xfce.parole + xfce.ristretto + xfce.xfce4-appfinder + xfce.xfce4-notifyd + xfce.xfce4-screenshooter + xfce.xfce4-session + xfce.xfce4-settings + xfce.xfce4-taskmanager + xfce.xfce4-terminal ] # TODO: NetworkManager doesn't belong here ++ optional config.networking.networkmanager.enable networkmanagerapplet - ++ optional config.powerManagement.enable xfce4-power-manager + ++ optional config.powerManagement.enable xfce.xfce4-power-manager ++ optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [ pavucontrol # volume up/down keys support: # xfce4-pulseaudio-plugin includes all the functionalities of xfce4-volumed-pulse # but can only be used with xfce4-panel, so for no-desktop usage we still include # xfce4-volumed-pulse - (if cfg.noDesktop then xfce4-volumed-pulse else xfce4-pulseaudio-plugin) + (if cfg.noDesktop then xfce.xfce4-volumed-pulse else xfce.xfce4-pulseaudio-plugin) ] ++ optionals cfg.enableXfwm [ - xfwm4 - xfwm4-themes + xfce.xfwm4 + xfce.xfwm4-themes ] ++ optionals (!cfg.noDesktop) [ - xfce4-panel - xfdesktop - ] ++ optional cfg.enableScreensaver xfce4-screensaver) excludePackages; + xfce.xfce4-panel + xfce.xfdesktop + ] ++ optional cfg.enableScreensaver xfce.xfce4-screensaver) excludePackages; programs.gnupg.agent.pinentryPackage = mkDefault pkgs.pinentry-gtk2; programs.xfconf.enable = true; diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/lomiri.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/lomiri.nix index 0cc79178358b..387f485e2fea 100644 --- a/nixos/modules/services/x11/display-managers/lightdm-greeters/lomiri.nix +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/lomiri.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let @@ -17,6 +22,9 @@ in }; config = lib.mkIf (ldmcfg.enable && cfg.enable) { + # Lomiri greeter == Lomiri shell in special mode, need some basics setup at least + services.desktopManager.lomiri.basics = true; + services.xserver.displayManager.lightdm.greeters.gtk.enable = false; services.xserver.displayManager.lightdm.greeter = lib.mkDefault { diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix index 140d453589b8..e052241acfd7 100644 --- a/nixos/modules/system/activation/activation-script.nix +++ b/nixos/modules/system/activation/activation-script.nix @@ -205,10 +205,14 @@ in system.build.installBootLoader = mkOption { internal = true; - # "; true" => make the `$out` argument from switch-to-configuration.pl - # go to `true` instead of `echo`, hiding the useless path - # from the log. - default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true"; + default = pkgs.writeShellScript "no-bootloader" '' + echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2 + ''; + defaultText = lib.literalExpression '' + pkgs.writeShellScript "no-bootloader" ''' + echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2 + ''' + ''; description = '' A program that writes a bootloader installation script to the path passed in the first command line argument. @@ -234,11 +238,12 @@ in system.activationScripts.var = ""; # obsolete systemd.tmpfiles.rules = [ + "D /var/empty 0555 root root -" + "h /var/empty - - - - +i" + ] ++ lib.optionals config.nix.enable [ # Prevent the current configuration from being garbage-collected. "d /nix/var/nix/gcroots -" "L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system" - "D /var/empty 0555 root root -" - "h /var/empty - - - - +i" ]; system.activationScripts.usrbinenv = if config.environment.usrbinenv != null diff --git a/nixos/modules/system/activation/switchable-system.nix b/nixos/modules/system/activation/switchable-system.nix index 883584a32ce2..d1326a18e5fe 100644 --- a/nixos/modules/system/activation/switchable-system.nix +++ b/nixos/modules/system/activation/switchable-system.nix @@ -1,13 +1,19 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let - - perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]); - + perlWrapped = pkgs.perl.withPackages ( + p: with p; [ + ConfigIniFiles + FileSlurp + ] + ); in - { - options.system.switch = { enable = lib.mkOption { type = lib.types.bool; @@ -36,6 +42,17 @@ in config = lib.mkMerge [ (lib.mkIf (config.system.switch.enable && !config.system.switch.enableNg) { + warnings = [ + '' + The Perl implementation of switch-to-configuration will be deprecated + and removed in the 25.05 release of NixOS. Please migrate to the + newer implementation by removing `system.switch.enableNg = false` + from your configuration. If you are unable to migrate due to any + issues with the new implementation, please create an issue and tag + the maintainers of `switch-to-configuration-ng`. + '' + ]; + system.activatableSystemBuilderCommands = '' mkdir $out/bin substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index ed0ece19f2fa..6abbd4b673c0 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -20,6 +20,12 @@ let ''} ln -s ${config.system.build.etc}/etc $out/etc + + ${lib.optionalString config.system.etc.overlay.enable '' + ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image + ln -s ${config.system.build.etcBasedir} $out/etc-basedir + ''} + ln -s ${config.system.path} $out/sw ln -s "$systemd" $out/systemd @@ -68,9 +74,19 @@ let else showWarnings config.warnings baseSystem; # Replace runtime dependencies - system = foldr ({ oldDependency, newDependency }: drv: - pkgs.replaceDependency { inherit oldDependency newDependency drv; } - ) baseSystemAssertWarn config.system.replaceRuntimeDependencies; + system = let inherit (config.system.replaceDependencies) replacements cutoffPackages; in + if replacements == [] then + # Avoid IFD if possible, by sidestepping replaceDependencies if no replacements are specified. + baseSystemAssertWarn + else + (pkgs.replaceDependencies.override { + replaceDirectDependencies = pkgs.replaceDirectDependencies.override { + nix = config.nix.package; + }; + }) { + drv = baseSystemAssertWarn; + inherit replacements cutoffPackages; + }; systemWithBuildDeps = system.overrideAttrs (o: { systemBuildClosure = pkgs.closureInfo { rootPaths = [ system.drvPath ]; }; @@ -87,6 +103,7 @@ in (mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.") (mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.") (mkRenamedOptionModule [ "system" "forbiddenDependenciesRegex" ] [ "system" "forbiddenDependenciesRegexes" ]) + (mkRenamedOptionModule [ "system" "replaceRuntimeDependencies" ] [ "system" "replaceDependencies" "replacements" ]) ]; options = { @@ -205,31 +222,47 @@ in ''; }; - system.replaceRuntimeDependencies = mkOption { - default = []; - example = lib.literalExpression "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]"; - type = types.listOf (types.submodule ( - { ... }: { - options.original = mkOption { - type = types.package; - description = "The original package to override."; - }; - - options.replacement = mkOption { - type = types.package; - description = "The replacement package."; - }; - }) - ); - apply = map ({ original, replacement, ... }: { - oldDependency = original; - newDependency = replacement; - }); - description = '' - List of packages to override without doing a full rebuild. - The original derivation and replacement derivation must have the same - name length, and ideally should have close-to-identical directory layout. - ''; + system.replaceDependencies = { + replacements = mkOption { + default = []; + example = lib.literalExpression "[ ({ oldDependency = pkgs.openssl; newDependency = pkgs.callPackage /path/to/openssl { }; }) ]"; + type = types.listOf (types.submodule ( + { ... }: { + imports = [ + (mkRenamedOptionModule [ "original" ] [ "oldDependency" ]) + (mkRenamedOptionModule [ "replacement" ] [ "newDependency" ]) + ]; + + options.oldDependency = mkOption { + type = types.package; + description = "The original package to override."; + }; + + options.newDependency = mkOption { + type = types.package; + description = "The replacement package."; + }; + }) + ); + apply = map ({ oldDependency, newDependency, ... }: { + inherit oldDependency newDependency; + }); + description = '' + List of packages to override without doing a full rebuild. + The original derivation and replacement derivation must have the same + name length, and ideally should have close-to-identical directory layout. + ''; + }; + + cutoffPackages = mkOption { + default = [ config.system.build.initialRamdisk ]; + defaultText = literalExpression "[ config.system.build.initialRamdisk ]"; + type = types.listOf types.package; + description = '' + Packages to which no replacements should be applied. + The initrd is matched by default, because its structure renders the replacement process ineffective and prone to breakage. + ''; + }; }; system.name = mkOption { diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index a0ef92c0505f..5a4ee29dadfe 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -28,8 +28,6 @@ let '' else interpreter; - getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs; - getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch; # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from: # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix @@ -280,28 +278,50 @@ in { ''; type = types.listOf (types.enum (builtins.attrNames magics)); }; + + preferStaticEmulators = mkOption { + default = false; + description = '' + Whether to use static emulators when available. + + This enables the kernel to preload the emulator binaries when + the binfmt registrations are added, obviating the need to make + the emulator binaries available inside chroots and chroot-like + sandboxes. + ''; + type = types.bool; + }; }; }; config = { + assertions = lib.mapAttrsToList (name: reg: { + assertion = reg.fixBinary -> !reg.wrapInterpreterInShell; + message = "boot.binfmt.registrations.\"${name}\" cannot have fixBinary when the interpreter is invoked through a shell."; + }) cfg.registrations; + boot.binfmt.registrations = builtins.listToAttrs (map (system: assert system != pkgs.stdenv.hostPlatform.system; { name = system; value = { config, ... }: let - interpreter = getEmulator system; - qemuArch = getQemuArch system; + elaborated = lib.systems.elaborate { inherit system; }; + useStaticEmulator = cfg.preferStaticEmulators && elaborated.staticEmulatorAvailable pkgs; + interpreter = elaborated.emulator (if useStaticEmulator then pkgs.pkgsStatic else pkgs); + + inherit (elaborated) qemuArch; + isQemu = "qemu-${qemuArch}" == baseNameOf interpreter; - preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter; interpreterReg = let wrapperName = "qemu-${qemuArch}-binfmt-P"; wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter; in - if preserveArgvZero then "${wrapper}/bin/${wrapperName}" + if isQemu && !useStaticEmulator then "${wrapper}/bin/${wrapperName}" else interpreter; in ({ - preserveArgvZero = mkDefault preserveArgvZero; + preserveArgvZero = mkDefault isQemu; interpreter = mkDefault interpreterReg; - wrapInterpreterInShell = mkDefault (!config.preserveArgvZero); + fixBinary = mkDefault useStaticEmulator; + wrapInterpreterInShell = mkDefault (!config.preserveArgvZero && !config.fixBinary); interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter)); } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"))); }) cfg.emulatedSystems); diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix index 4f8a2273a7ac..1910ff52eac8 100644 --- a/nixos/modules/system/boot/grow-partition.nix +++ b/nixos/modules/system/boot/grow-partition.nix @@ -43,7 +43,7 @@ with lib; while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do parentDevice="''${parentDevice%[0-9]}"; done - partNum="''${rootDevice#''${parentDevice}}" + partNum="''${rootDevice#"''${parentDevice}"}" if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then parentDevice="''${parentDevice%p}" fi 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 6cab35820c88..85fcbb83cece 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; @@ -10,16 +15,21 @@ let # We check the source code in a derivation that does not depend on the # system configuration so that most users don't have to redo the check and require # the necessary dependencies. - checkedSource = pkgs.runCommand "systemd-boot" { - preferLocalBuild = true; - } '' - install -m755 -D ${./systemd-boot-builder.py} $out - ${lib.getExe pkgs.buildPackages.mypy} \ - --no-implicit-optional \ - --disallow-untyped-calls \ - --disallow-untyped-defs \ - $out - ''; + checkedSource = + pkgs.runCommand "systemd-boot" + { + preferLocalBuild = true; + } + '' + install -m755 -D ${./systemd-boot-builder.py} $out + ${lib.getExe pkgs.buildPackages.mypy} \ + --no-implicit-optional \ + --disallow-untyped-calls \ + --disallow-untyped-defs \ + $out + ''; + + edk2ShellEspPath = "efi/edk2-uefi-shell/shell.efi"; systemdBootBuilder = pkgs.substituteAll rec { name = "systemd-boot"; @@ -44,13 +54,17 @@ let configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit; - inherit (cfg) consoleMode graceful editor rebootForBitlocker; + inherit (cfg) + consoleMode + graceful + editor + rebootForBitlocker + ; inherit (efi) efiSysMountPoint canTouchEfiVariables; - bootMountPoint = if cfg.xbootldrMountPoint != null - then cfg.xbootldrMountPoint - else efi.efiSysMountPoint; + bootMountPoint = + if cfg.xbootldrMountPoint != null then cfg.xbootldrMountPoint else efi.efiSysMountPoint; nixosDir = "/EFI/nixos"; @@ -66,23 +80,27 @@ let 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}"} + ${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}" "${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}" "${bootMountPoint}/loader/entries/"${escapeShellArg n} - ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n} - '') cfg.extraEntries)} + ${concatStrings ( + mapAttrsToList (n: v: '' + ${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}" "${bootMountPoint}/loader/entries/"${escapeShellArg n} + ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n} + '') cfg.extraEntries + )} ''; }; @@ -91,23 +109,61 @@ let ${systemdBootBuilder}/bin/systemd-boot "$@" ${cfg.extraInstallCommands} ''; -in { +in +{ meta.maintainers = with lib.maintainers; [ julienmalka ]; - 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) - ) - ]; + 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 = { enable = mkOption { @@ -124,7 +180,7 @@ in { sortKey = mkOption { default = "nixos"; - type = lib.types.str; + type = types.str; description = '' The sort key used for the NixOS bootloader entries. This key determines sorting relative to non-NixOS entries. @@ -218,7 +274,15 @@ in { consoleMode = mkOption { default = "keep"; - type = types.enum [ "0" "1" "2" "5" "auto" "max" "keep" ]; + type = types.enum [ + "0" + "1" + "2" + "5" + "auto" + "max" + "keep" + ]; description = '' The resolution of the console. The following values are valid: @@ -281,9 +345,32 @@ in { }; }; + edk2-uefi-shell = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Make the EDK2 UEFI Shell available from the systemd-boot menu. + It can be used to manually boot other operating systems or for debugging. + ''; + }; + + sortKey = mkOption { + type = types.str; + default = "o_edk2-uefi-shell"; + description = '' + `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`.. + ''; + }; + }; + extraEntries = mkOption { type = types.attrsOf types.lines; - default = {}; + default = { }; example = literalExpression '' { "memtest86.conf" = ''' title Memtest86+ @@ -306,7 +393,7 @@ in { extraFiles = mkOption { type = types.attrsOf types.path; - default = {}; + default = { }; example = literalExpression '' { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; } ''; @@ -349,40 +436,126 @@ in { Windows can unseal the encryption key. ''; }; + + windows = mkOption { + default = { }; + description = '' + Make Windows bootable from systemd-boot. This option is not necessary when Windows and + NixOS use the same EFI System Partition (ESP). In that case, Windows will automatically be + detected by systemd-boot. + + However, if Windows is installed on a separate drive or ESP, you can use this option to add + a menu entry for each installation manually. + + The attribute name is used for the title of the menu entry and internal file names. + ''; + example = literalExpression '' + { + "10".efiDeviceHandle = "HD0c3"; + "11-ame" = { + title = "Windows 11 Ameliorated Edition"; + efiDeviceHandle = "HD0b1"; + }; + "11-home" = { + title = "Windows 11 Home"; + efiDeviceHandle = "FS1"; + sortKey = "z_windows"; + }; + } + ''; + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + efiDeviceHandle = mkOption { + type = types.str; + example = "HD1b3"; + description = '' + The device handle of the EFI System Partition (ESP) where the Windows bootloader is + located. This is the device handle that the EDK2 UEFI Shell uses to load the + bootloader. + + To find this handle, follow these steps: + 1. Set {option}`boot.loader.systemd-boot.edk2-uefi-shell.enable` to `true` + 2. Run `nixos-rebuild boot` + 3. Reboot and select "EDK2 UEFI Shell" from the systemd-boot menu + 4. Run `map -c` to list all consistent device handles + 5. For each device handle (for example, `HD0c1`), run `ls HD0c1:\EFI` + 6. If the output contains the directory `Microsoft`, you might have found the correct device handle + 7. Run `HD0c1:\EFI\Microsoft\Boot\Bootmgfw.efi` to check if Windows boots correctly + 8. If it does, this device handle is the one you need (in this example, `HD0c1`) + + This option is required, there is no useful default. + ''; + }; + + title = mkOption { + type = types.str; + example = "Michaelsoft Binbows"; + default = "Windows ${name}"; + defaultText = ''attribute name of this entry, prefixed with "Windows "''; + description = '' + The title of the boot menu entry. + ''; + }; + + sortKey = mkOption { + type = types.str; + default = "o_windows_${name}"; + defaultText = ''attribute name of this entry, prefixed with "o_windows_"''; + description = '' + `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`.. + ''; + }; + }; + } + ) + ); + }; }; config = mkIf cfg.enable { - assertions = [ - { - assertion = (hasPrefix "/" efi.efiSysMountPoint); - message = "The ESP mount point '${toString efi.efiSysMountPoint}' must be an absolute path"; - } - { - assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint); - message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' must be an absolute path"; - } - { - assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint; - message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${toString efi.efiSysMountPoint}'"; - } - { - assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; - message = "This kernel does not support the EFI boot stub"; - } - { - assertion = cfg.installDeviceTree -> config.hardware.deviceTree.enable -> config.hardware.deviceTree.name != null; - message = "Cannot install devicetree without 'config.hardware.deviceTree.enable' enabled and 'config.hardware.deviceTree.name' set"; - } - ] ++ concatMap (filename: [ - { - assertion = !(hasInfix "/" filename); - message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported"; - } - { - assertion = hasSuffix ".conf" filename; - message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension"; - } - ]) (builtins.attrNames cfg.extraEntries) + assertions = + [ + { + assertion = (hasPrefix "/" efi.efiSysMountPoint); + message = "The ESP mount point '${toString efi.efiSysMountPoint}' must be an absolute path"; + } + { + assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint); + message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' must be an absolute path"; + } + { + assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint; + message = "The XBOOTLDR mount point '${toString cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${toString efi.efiSysMountPoint}'"; + } + { + assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; + message = "This kernel does not support the EFI boot stub"; + } + { + assertion = + cfg.installDeviceTree + -> config.hardware.deviceTree.enable + -> config.hardware.deviceTree.name != null; + message = "Cannot install devicetree without 'config.hardware.deviceTree.enable' enabled and 'config.hardware.deviceTree.name' set"; + } + ] + ++ concatMap (filename: [ + { + assertion = !(hasInfix "/" filename); + message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported"; + } + { + assertion = hasSuffix ".conf" filename; + message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension"; + } + ]) (builtins.attrNames cfg.extraEntries) ++ concatMap (filename: [ { assertion = !(hasPrefix "/" filename); @@ -396,7 +569,13 @@ in { assertion = !(hasInfix "nixos/.extra-files" (toLower filename)); message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory"; } - ]) (builtins.attrNames cfg.extraFiles); + ]) (builtins.attrNames cfg.extraFiles) + ++ concatMap (winVersion: [ + { + assertion = lib.match "^[-_0-9A-Za-z]+$" winVersion != null; + message = "boot.loader.systemd-boot.windows.${winVersion} is invalid: key must only contain alphanumeric characters, hyphens, and underscores"; + } + ]) (builtins.attrNames cfg.windows); boot.loader.grub.enable = mkDefault false; @@ -409,24 +588,44 @@ in { (mkIf cfg.netbootxyz.enable { "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}"; }) + (mkIf (cfg.edk2-uefi-shell.enable || cfg.windows != { }) { + ${edk2ShellEspPath} = "${pkgs.edk2-uefi-shell}/shell.efi"; + }) ]; - boot.loader.systemd-boot.extraEntries = mkMerge [ - (mkIf cfg.memtest86.enable { - "memtest86.conf" = '' - title Memtest86+ - efi /efi/memtest86/memtest.efi - sort-key ${cfg.memtest86.sortKey} + boot.loader.systemd-boot.extraEntries = mkMerge ( + [ + (mkIf cfg.memtest86.enable { + "memtest86.conf" = '' + title Memtest86+ + efi /efi/memtest86/memtest.efi + sort-key ${cfg.memtest86.sortKey} + ''; + }) + (mkIf cfg.netbootxyz.enable { + "netbootxyz.conf" = '' + title netboot.xyz + efi /efi/netbootxyz/netboot.xyz.efi + sort-key ${cfg.netbootxyz.sortKey} + ''; + }) + (mkIf cfg.edk2-uefi-shell.enable { + "edk2-uefi-shell.conf" = '' + title EDK2 UEFI Shell + efi /${edk2ShellEspPath} + sort-key ${cfg.edk2-uefi-shell.sortKey} + ''; + }) + ] + ++ (mapAttrsToList (winVersion: cfg: { + "windows_${winVersion}.conf" = '' + title ${cfg.title} + efi /${edk2ShellEspPath} + options -nointerrupt -nomap -noversion ${cfg.efiDeviceHandle}:EFI\Microsoft\Boot\Bootmgfw.efi + sort-key ${cfg.sortKey} ''; - }) - (mkIf cfg.netbootxyz.enable { - "netbootxyz.conf" = '' - title netboot.xyz - efi /efi/netbootxyz/netboot.xyz.efi - sort-key ${cfg.netbootxyz.sortKey} - ''; - }) - ]; + }) cfg.windows) + ); boot.bootspec.extensions."org.nixos.systemd-boot" = { inherit (config.boot.loader.systemd-boot) sortKey; diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 65190e65d9b9..70b455871b4b 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -1088,6 +1088,8 @@ in storePaths = [ "${config.boot.initrd.systemd.package}/bin/systemd-cryptsetup" "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator" + ] ++ lib.optionals config.boot.initrd.systemd.tpm2.enable [ + "${config.boot.initrd.systemd.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" ]; }; diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index e000d99cfb48..130d6098b1e2 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -411,11 +411,14 @@ let (assertValueOneOf "Layer2SpecificHeader" [ "none" "default" ]) ]; - # NOTE The PrivateKey directive is missing on purpose here, please - # do not add it to this list. The nix store is world-readable let's - # refrain ourselves from providing a footgun. + # NOTE Check whether the key starts with an @, in which case it is + # interpreted as the name of the credential from which the actual key + # shall be read by systemd-creds. + # Do not remove this check as the nix store is world-readable. sectionWireGuard = checkUnitConfig "WireGuard" [ + (assertKeyIsSystemdCredential "PrivateKey") (assertOnlyFields [ + "PrivateKey" "PrivateKeyFile" "ListenPort" "FirewallMark" @@ -426,12 +429,15 @@ let (assertRange "FirewallMark" 1 4294967295) ]; - # NOTE The PresharedKey directive is missing on purpose here, please - # do not add it to this list. The nix store is world-readable,let's - # refrain ourselves from providing a footgun. + # NOTE Check whether the key starts with an @, in which case it is + # interpreted as the name of the credential from which the actual key + # shall be read by systemd-creds. + # Do not remove this check as the nix store is world-readable. sectionWireGuardPeer = checkUnitConfigWithLegacyKey "wireguardPeerConfig" "WireGuardPeer" [ + (assertKeyIsSystemdCredential "PresharedKey") (assertOnlyFields [ "PublicKey" + "PresharedKey" "PresharedKeyFile" "AllowedIPs" "Endpoint" @@ -855,6 +861,7 @@ let "UseGateway" "UseRoutes" "UseTimezone" + "IPv6OnlyMode" "ClientIdentifier" "VendorClassIdentifier" "UserClass" @@ -888,6 +895,7 @@ let (assertValueOneOf "UseGateway" boolValues) (assertValueOneOf "UseRoutes" boolValues) (assertValueOneOf "UseTimezone" boolValues) + (assertValueOneOf "IPv6OnlyMode" boolValues) (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"]) (assertInt "IAID") (assertValueOneOf "RequestBroadcast" boolValues) @@ -907,7 +915,9 @@ let "UseAddress" "UseDNS" "UseNTP" + "SendHostname" "UseHostname" + "Hostname" "UseDomains" "RouteMetric" "RapidCommit" @@ -928,6 +938,7 @@ let (assertValueOneOf "UseAddress" boolValues) (assertValueOneOf "UseDNS" boolValues) (assertValueOneOf "UseNTP" boolValues) + (assertValueOneOf "SendHostname" boolValues) (assertValueOneOf "UseHostname" boolValues) (assertValueOneOf "UseDomains" (boolValues ++ ["route"])) (assertInt "RouteMetric") diff --git a/nixos/modules/system/boot/shutdown.nix b/nixos/modules/system/boot/shutdown.nix index 8cda7b3aabe8..3f25dca0cbf9 100644 --- a/nixos/modules/system/boot/shutdown.nix +++ b/nixos/modules/system/boot/shutdown.nix @@ -14,6 +14,7 @@ with lib; unitConfig = { DefaultDependencies = false; ConditionPathExists = "/dev/rtc"; + ConditionPathIsReadWrite = "/etc/"; }; serviceConfig = { diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 082380216d2a..280d38ce32f4 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -327,7 +327,7 @@ let setHostId = optionalString (config.networking.hostId != null) '' hi="${config.networking.hostId}" - ${if pkgs.stdenv.isBigEndian then '' + ${if pkgs.stdenv.hostPlatform.isBigEndian then '' echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid '' else '' echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid @@ -349,13 +349,7 @@ let { object = "${modulesClosure}/lib"; symlink = "/lib"; } - { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { - src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; - preferLocalBuild = true; - } '' - target=$out - ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out - ''; + { object = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; symlink = "/etc/modprobe.d/ubuntu.conf"; } { object = config.environment.etc."modprobe.d/nixos.conf".source; @@ -411,7 +405,7 @@ let ${lib.optionalString (config.boot.initrd.secrets == {}) "exit 0"} - export PATH=${pkgs.coreutils}/bin:${pkgs.libarchive}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin + export PATH=${pkgs.coreutils}/bin:${pkgs.cpio}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin function cleanup { if [ -n "$tmp" -a -d "$tmp" ]; then @@ -432,7 +426,7 @@ let } # mindepth 1 so that we don't change the mode of / - (cd "$tmp" && find . -mindepth 1 | xargs touch -amt 197001010000 && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \ + (cd "$tmp" && find . -mindepth 1 | xargs touch -amt 197001010000 && find . -mindepth 1 -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null) | \ ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1" ''; diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh index a89e3d817637..b5627ec8e571 100755 --- a/nixos/modules/system/boot/stage-2-init.sh +++ b/nixos/modules/system/boot/stage-2-init.sh @@ -125,14 +125,6 @@ ln -sfn "$systemConfig" /run/booted-system @shell@ @postBootCommands@ -# Ensure systemd doesn't try to populate /etc, by forcing its first-boot -# heuristic off. It doesn't matter what's in /etc/machine-id for this purpose, -# and systemd will immediately fill in the file when it starts, so just -# creating it is enough. This `: >>` pattern avoids forking and avoids changing -# the mtime if the file already exists. -: >> /etc/machine-id - - # No need to restore the stdout/stderr streams we never redirected and # especially no need to start systemd if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index bb6fcc1d38ce..9260bc22b13e 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -33,12 +33,11 @@ let "nss-lookup.target" "nss-user-lookup.target" "time-sync.target" + "first-boot-complete.target" ] ++ optionals cfg.package.withCryptsetup [ "cryptsetup.target" "cryptsetup-pre.target" "remote-cryptsetup.target" - ] ++ optionals cfg.package.withTpm2Tss [ - "tpm2.target" ] ++ [ "sigpwr.target" "timers.target" @@ -161,6 +160,7 @@ let # Misc. "systemd-sysctl.service" + "systemd-machine-id-commit.service" ] ++ optionals cfg.package.withTimedated [ "dbus-org.freedesktop.timedate1.service" "systemd-timedated.service" @@ -198,6 +198,8 @@ in package = mkPackageOption pkgs "systemd" {}; + enableStrictShellChecks = mkEnableOption "" // { description = "Whether to run shellcheck on the generated scripts for systemd units."; }; + units = mkOption { description = "Definition of systemd units; see {manpage}`systemd.unit(5)`."; default = {}; @@ -570,6 +572,15 @@ in "systemd/user-generators" = { source = hooks "user-generators" cfg.user.generators; }; "systemd/system-generators" = { source = hooks "system-generators" cfg.generators; }; "systemd/system-shutdown" = { source = hooks "system-shutdown" cfg.shutdown; }; + + # Ignore all other preset files so systemd doesn't try to enable/disable + # units during runtime. + "systemd/system-preset/00-nixos.preset".text = '' + ignore * + ''; + "systemd/user-preset/00-nixos.preset".text = '' + ignore * + ''; }); services.dbus.enable = true; @@ -679,7 +690,7 @@ in # Increase numeric PID range (set directly instead of copying a one-line file from systemd) # https://github.com/systemd/systemd/pull/12226 - boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304); + boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.hostPlatform.is64bit (lib.mkDefault 4194304); services.logrotate.settings = { "/var/log/btmp" = mapAttrs (_: mkDefault) { diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 2ccc964820fe..1915fef59cc4 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -38,6 +38,7 @@ let "kmod-static-nodes.service" "local-fs-pre.target" "local-fs.target" + "modprobe@.service" "multi-user.target" "paths.target" "poweroff.target" @@ -68,7 +69,6 @@ let "systemd-reboot.service" "systemd-sysctl.service" "timers.target" - "tpm2.target" "umount.target" "systemd-bsod.service" ] ++ cfg.additionalUpstreamUnits; @@ -349,15 +349,6 @@ in { visible = "shallow"; description = "Definition of slice configurations."; }; - - enableTpm2 = mkOption { - default = cfg.package.withTpm2Tss; - defaultText = "boot.initrd.systemd.package.withTpm2Tss"; - type = types.bool; - description = '' - Whether to enable TPM2 support in the initrd. - ''; - }; }; config = mkIf (config.boot.initrd.enable && cfg.enable) { @@ -394,9 +385,7 @@ in { # systemd needs this for some features "autofs" # systemd-cryptenroll - ] ++ lib.optional cfg.enableTpm2 "tpm-tis" - ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb" - ++ lib.optional cfg.package.withEfi "efivarfs"; + ] ++ lib.optional cfg.package.withEfi "efivarfs"; boot.kernelParams = [ "root=${config.boot.initrd.systemd.root}" @@ -448,9 +437,7 @@ in { "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe"; "/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf"; - "/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } '' - ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out - ''; + "/etc/modprobe.d/ubuntu.conf".source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; "/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases; "/etc/os-release".source = config.boot.initrd.osRelease; @@ -495,10 +482,6 @@ in { # so NSS can look up usernames "${pkgs.glibc}/lib/libnss_files.so.2" - ] ++ optionals (cfg.package.withCryptsetup && cfg.enableTpm2) [ - # tpm2 support - "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" - pkgs.tpm2-tss ] ++ optionals cfg.package.withCryptsetup [ # fido2 support "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" @@ -522,12 +505,20 @@ in { in nameValuePair "${n}.automount" (automountToUnit v)) cfg.automounts); - services.initrd-nixos-activation = { - after = [ "initrd-fs.target" ]; + services.initrd-find-nixos-closure = { + description = "Find NixOS closure"; + + unitConfig = { + RequiresMountsFor = "/sysroot/nix/store"; + DefaultDependencies = false; + }; + before = [ "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; requiredBy = [ "initrd.target" ]; - unitConfig.AssertPathExists = "/etc/initrd-release"; - serviceConfig.Type = "oneshot"; - description = "NixOS Activation"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; script = /* bash */ '' set -uo pipefail @@ -538,7 +529,7 @@ in { for o in $(< /proc/cmdline); do case $o in init=*) - IFS== read -r -a initParam <<< "$o" + IFS="=" read -r -a initParam <<< "$o" closure="''${initParam[1]}" ;; esac @@ -557,6 +548,8 @@ in { # Assume the directory containing the init script is the closure. closure="$(dirname "$closure")" + ln --symbolic "$closure" /nixos-closure + # If we are not booting a NixOS closure (e.g. init=/bin/sh), # we don't know what root to prepare so we don't do anything if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then @@ -565,16 +558,52 @@ in { exit 0 fi echo 'NEW_INIT=' > /etc/switch-root.conf + ''; + }; + # We need to propagate /run for things like /run/booted-system + # and /run/current-system. + mounts = [ + { + where = "/sysroot/run"; + what = "/run"; + options = "bind"; + unitConfig = { + # See the comment on the mount unit for /run/etc-metadata + DefaultDependencies = false; + }; + requiredBy = [ "initrd-fs.target" ]; + before = [ "initrd-fs.target" ]; + } + ]; + + services.initrd-nixos-activation = { + requires = [ + config.boot.initrd.systemd.services.initrd-find-nixos-closure.name + ]; + after = [ + "initrd-fs.target" + config.boot.initrd.systemd.services.initrd-find-nixos-closure.name + ]; + requiredBy = [ "initrd.target" ]; + unitConfig = { + AssertPathExists = "/etc/initrd-release"; + RequiresMountsFor = [ + "/sysroot/run" + ]; + }; + serviceConfig.Type = "oneshot"; + description = "NixOS Activation"; + + script = /* bash */ '' + set -uo pipefail + export PATH="/bin:${cfg.package.util-linux}/bin" - # We need to propagate /run for things like /run/booted-system - # and /run/current-system. - mkdir -p /sysroot/run - mount --bind /run /sysroot/run + closure="$(realpath /nixos-closure)" # Initialize the system export IN_NIXOS_SYSTEMD_STAGE1=true - exec chroot /sysroot $closure/prepare-root + exec chroot /sysroot "$closure/prepare-root" ''; }; diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix index 44c72f2768cc..9a0f18c26f94 100644 --- a/nixos/modules/system/boot/systemd/tmpfiles.nix +++ b/nixos/modules/system/boot/systemd/tmpfiles.nix @@ -281,15 +281,19 @@ in ) cfg.settings); systemd.tmpfiles.rules = [ - "d /nix/var 0755 root root - -" - "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system" "d /run/lock 0755 root root - -" "d /var/db 0755 root root - -" "L /var/lock - - - - ../run/lock" - # Boot-time cleanup + ] ++ lib.optionals config.nix.enable [ + "d /nix/var 0755 root root - -" + "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system" + ] + # Boot-time cleanup + ++ [ "R! /etc/group.lock - - - - -" "R! /etc/passwd.lock - - - - -" "R! /etc/shadow.lock - - - - -" + ] ++ lib.optionals config.nix.enable [ "R! /nix/var/nix/gcroots/tmp - - - - -" "R! /nix/var/nix/temproots - - - - -" ]; diff --git a/nixos/modules/system/boot/systemd/tpm2.nix b/nixos/modules/system/boot/systemd/tpm2.nix new file mode 100644 index 000000000000..6b6c9fb23f24 --- /dev/null +++ b/nixos/modules/system/boot/systemd/tpm2.nix @@ -0,0 +1,80 @@ +{ + lib, + config, + pkgs, + ... +}: +{ + meta.maintainers = [ lib.maintainers.elvishjerricco ]; + + imports = [ + (lib.mkRenamedOptionModule + [ + "boot" + "initrd" + "systemd" + "enableTpm2" + ] + [ + "boot" + "initrd" + "systemd" + "tpm2" + "enable" + ] + ) + ]; + + options = { + systemd.tpm2.enable = lib.mkEnableOption "systemd TPM2 support" // { + default = config.systemd.package.withTpm2Tss; + defaultText = "systemd.package.withTpm2Tss"; + }; + + boot.initrd.systemd.tpm2.enable = lib.mkEnableOption "systemd initrd TPM2 support" // { + default = config.boot.initrd.systemd.package.withTpm2Tss; + defaultText = "boot.initrd.systemd.package.withTpm2Tss"; + }; + }; + + # TODO: pcrphase, pcrextend, pcrfs, pcrmachine + config = lib.mkMerge [ + # Stage 2 + ( + let + cfg = config.systemd; + in + lib.mkIf cfg.tpm2.enable { + systemd.additionalUpstreamSystemUnits = [ + "tpm2.target" + "systemd-tpm2-setup-early.service" + "systemd-tpm2-setup.service" + ]; + } + ) + + # Stage 1 + ( + let + cfg = config.boot.initrd.systemd; + in + lib.mkIf (cfg.enable && cfg.tpm2.enable) { + boot.initrd.systemd.additionalUpstreamUnits = [ + "tpm2.target" + "systemd-tpm2-setup-early.service" + ]; + + boot.initrd.availableKernelModules = + [ "tpm-tis" ] + ++ lib.optional ( + !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7) + ) "tpm-crb"; + boot.initrd.systemd.storePaths = [ + pkgs.tpm2-tss + "${cfg.package}/lib/systemd/systemd-tpm2-setup" + "${cfg.package}/lib/systemd/system-generators/systemd-tpm2-generator" + ]; + } + ) + ]; +} diff --git a/nixos/modules/system/boot/unl0kr.nix b/nixos/modules/system/boot/unl0kr.nix index bb3916ed4599..30cf3ff8105e 100644 --- a/nixos/modules/system/boot/unl0kr.nix +++ b/nixos/modules/system/boot/unl0kr.nix @@ -1,27 +1,85 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.boot.initrd.unl0kr; + settingsFormat = pkgs.formats.ini { }; in { options.boot.initrd.unl0kr = { enable = lib.mkEnableOption "unl0kr in initrd" // { + description = ''Whether to enable the unl0kr on-screen keyboard in initrd to unlock LUKS.''; + }; + + allowVendorDrivers = lib.mkEnableOption "load optional drivers" // { + description = ''Whether to load additional drivers for certain vendors (I.E: Wacom, Intel, etc.)''; + }; + + settings = lib.mkOption { description = '' - Whether to enable the unl0kr on-screen keyboard in initrd to unlock LUKS. + Configuration for `unl0kr`. + + See `unl0kr.conf(5)` for supported values. + + Alternatively, visit `https://gitlab.com/postmarketOS/buffybox/-/blob/unl0kr-2.0.0/unl0kr.conf` ''; + + example = lib.literalExpression '' + { + general.animations = true; + theme = { + default = "pmos-dark"; + alternate = "pmos-light"; + }; + } + ''; + default = { }; + type = lib.types.submodule { freeformType = settingsFormat.type; }; }; }; config = lib.mkIf cfg.enable { - meta.maintainers = []; + meta.maintainers = with lib.maintainers; [ hustlerone ]; assertions = [ { assertion = cfg.enable -> config.boot.initrd.systemd.enable; message = "boot.initrd.unl0kr is only supported with boot.initrd.systemd."; } + { + assertion = !config.boot.plymouth.enable; + message = "unl0kr will not work if plymouth is enabled."; + } + { + assertion = !config.hardware.amdgpu.initrd.enable; + message = "unl0kr has issues with video drivers that are loaded on stage 1."; + } ]; + boot.initrd.availableKernelModules = + lib.optionals cfg.enable [ + "hid-multitouch" + "hid-generic" + "usbhid" + + "i2c-designware-core" + "i2c-designware-platform" + "i2c-hid-acpi" + + "usbtouchscreen" + "evdev" + ] + ++ lib.optionals cfg.allowVendorDrivers [ + "intel_lpss_pci" + "elo" + "wacom" + ]; + boot.initrd.systemd = { + contents."/etc/unl0kr.conf".source = settingsFormat.generate "unl0kr.conf" cfg.settings; storePaths = with pkgs; [ "${pkgs.gnugrep}/bin/grep" libinput @@ -42,9 +100,7 @@ in "systemd-vconsole-setup.service" "udev.service" ]; - before = [ - "shutdown.target" - ]; + before = [ "shutdown.target" ]; script = '' # This script acts as a Password Agent: https://systemd.io/PASSWORD_AGENTS/ @@ -56,7 +112,7 @@ in do for file in `ls $DIR/ask.*`; do socket="$(cat "$file" | ${pkgs.gnugrep}/bin/grep "Socket=" | cut -d= -f2)" - ${pkgs.unl0kr}/bin/unl0kr | ${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password 1 "$socket" + ${pkgs.unl0kr}/bin/unl0kr -v -C "/etc/unl0kr.conf" | ${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password 1 "$socket" done done ''; diff --git a/nixos/modules/system/etc/etc-activation.nix b/nixos/modules/system/etc/etc-activation.nix index 6c6352b0419d..944920e92335 100644 --- a/nixos/modules/system/etc/etc-activation.nix +++ b/nixos/modules/system/etc/etc-activation.nix @@ -1,4 +1,4 @@ -{ config, lib, ... }: +{ config, lib, pkgs, ... }: { @@ -34,12 +34,30 @@ mounts = [ { where = "/run/etc-metadata"; - what = "/sysroot${config.system.build.etcMetadataImage}"; + what = "/etc-metadata-image"; type = "erofs"; options = "loop"; - unitConfig.RequiresMountsFor = [ - "/sysroot/nix/store" + unitConfig = { + # Since this unit depends on the nix store being mounted, it cannot + # be a dependency of local-fs.target, because if it did, we'd have + # local-fs.target ordered after the nix store mount which would cause + # things like network.target to only become active after the nix store + # has been mounted. + # This breaks for instance setups where sshd needs to be up before + # any encrypted disks can be mounted. + DefaultDependencies = false; + RequiresMountsFor = [ + "/sysroot/nix/store" + ]; + }; + requires = [ + config.boot.initrd.systemd.services.initrd-find-etc.name ]; + after = [ + config.boot.initrd.systemd.services.initrd-find-etc.name + ]; + requiredBy = [ "initrd-fs.target" ]; + before = [ "initrd-fs.target" ]; } { where = "/sysroot/etc"; @@ -49,7 +67,7 @@ "relatime" "redirect_dir=on" "metacopy=on" - "lowerdir=/run/etc-metadata::/sysroot${config.system.build.etcBasedir}" + "lowerdir=/run/etc-metadata::/etc-basedir" ] ++ lib.optionals config.system.etc.overlay.mutable [ "rw" "upperdir=/sysroot/.rw-etc/upper" @@ -59,28 +77,77 @@ ]); requiredBy = [ "initrd-fs.target" ]; before = [ "initrd-fs.target" ]; - requires = lib.mkIf config.system.etc.overlay.mutable [ "rw-etc.service" ]; - after = lib.mkIf config.system.etc.overlay.mutable [ "rw-etc.service" ]; - unitConfig.RequiresMountsFor = [ - "/sysroot/nix/store" - "/run/etc-metadata" + requires = [ + config.boot.initrd.systemd.services.initrd-find-etc.name + ] ++ lib.optionals config.system.etc.overlay.mutable [ + config.boot.initrd.systemd.services."rw-etc".name + ]; + after = [ + config.boot.initrd.systemd.services.initrd-find-etc.name + ] ++ lib.optionals config.system.etc.overlay.mutable [ + config.boot.initrd.systemd.services."rw-etc".name ]; - } - ]; - services = lib.mkIf config.system.etc.overlay.mutable { - rw-etc = { unitConfig = { + RequiresMountsFor = [ + "/sysroot/nix/store" + "/run/etc-metadata" + ]; DefaultDependencies = false; - RequiresMountsFor = "/sysroot"; }; - serviceConfig = { - Type = "oneshot"; - ExecStart = '' - /bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work + } + ]; + services = lib.mkMerge [ + (lib.mkIf config.system.etc.overlay.mutable { + rw-etc = { + requiredBy = [ "initrd-fs.target" ]; + before = [ "initrd-fs.target" ]; + unitConfig = { + DefaultDependencies = false; + RequiresMountsFor = "/sysroot"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = '' + /bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work + ''; + }; + }; + }) + { + initrd-find-etc = { + description = "Find the path to the etc metadata image and based dir"; + requires = [ + config.boot.initrd.systemd.services.initrd-find-nixos-closure.name + ]; + after = [ + config.boot.initrd.systemd.services.initrd-find-nixos-closure.name + ]; + before = [ "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; + requiredBy = [ "initrd.target" ]; + unitConfig = { + DefaultDependencies = false; + RequiresMountsFor = "/sysroot/nix/store"; + }; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + + script = /* bash */ '' + set -uo pipefail + + closure="$(realpath /nixos-closure)" + + metadata_image="$(chroot /sysroot ${lib.getExe' pkgs.coreutils "realpath"} "$closure/etc-metadata-image")" + ln -s "/sysroot$metadata_image" /etc-metadata-image + + basedir="$(chroot /sysroot ${lib.getExe' pkgs.coreutils "realpath"} "$closure/etc-basedir")" + ln -s "/sysroot$basedir" /etc-basedir ''; }; - }; - }; + } + ]; }; }) diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix index 594b9aab61b7..e8eeae03a360 100644 --- a/nixos/modules/system/etc/etc.nix +++ b/nixos/modules/system/etc/etc.nix @@ -231,13 +231,13 @@ in if [[ ! $IN_NIXOS_SYSTEMD_STAGE1 ]] && [[ "${config.system.build.etc}/etc" != "$(readlink -f /run/current-system/etc)" ]]; then echo "remounting /etc..." - tmpMetadataMount=$(mktemp --directory) + tmpMetadataMount=$(mktemp --directory -t nixos-etc-metadata.XXXXXXXXXX) mount --type erofs ${config.system.build.etcMetadataImage} $tmpMetadataMount # Mount the new /etc overlay to a temporary private mount. # This needs the indirection via a private bind mount because you # cannot move shared mounts. - tmpEtcMount=$(mktemp --directory) + tmpEtcMount=$(mktemp --directory -t nixos-etc.XXXXXXXXXX) mount --bind --make-private $tmpEtcMount $tmpEtcMount mount --type overlay overlay \ --options lowerdir=$tmpMetadataMount::${config.system.build.etcBasedir},${etcOverlayOptions} \ @@ -276,6 +276,22 @@ in # Unmount the top /etc mount to atomically reveal the new mount. umount --lazy --recursive /etc + + # Unmount the temporary mount + umount --lazy "$tmpEtcMount" + rmdir "$tmpEtcMount" + + # Unmount old metadata mounts + # For some reason, `findmnt /tmp --submounts` does not show the nested + # mounts. So we'll just find all mounts of type erofs and filter on the + # name of the mountpoint. + findmnt --type erofs --list --kernel --output TARGET | while read -r mountPoint; do + if [[ "$mountPoint" =~ ^/tmp/nixos-etc-metadata\..{10}$ && + "$mountPoint" != "$tmpMetadataMount" ]]; then + umount --lazy $mountPoint + rmdir "$mountPoint" + fi + done fi '' else '' # Set up the statically computed bits of /etc. diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix index 32a52041e57a..c995016d85c7 100644 --- a/nixos/modules/tasks/auto-upgrade.nix +++ b/nixos/modules/tasks/auto-upgrade.nix @@ -168,7 +168,7 @@ in { assertions = [{ assertion = !((cfg.channel != null) && (cfg.flake != null)); message = '' - The options 'system.autoUpgrade.channels' and 'system.autoUpgrade.flake' cannot both be set. + The options 'system.autoUpgrade.channel' and 'system.autoUpgrade.flake' cannot both be set. ''; }]; diff --git a/nixos/modules/tasks/bcache.nix b/nixos/modules/tasks/bcache.nix index ba3449874622..d2c816d663f9 100644 --- a/nixos/modules/tasks/bcache.nix +++ b/nixos/modules/tasks/bcache.nix @@ -11,6 +11,8 @@ in { Whether to enable bcache support in the initrd. ''; + default = config.boot.initrd.systemd.enable && config.boot.bcache.enable; + defaultText = lib.literalExpression "config.boot.initrd.systemd.enable && config.boot.bcache.enable"; }; config = lib.mkIf cfg.enable { diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix index a81bec27bce2..f5e205e26e94 100644 --- a/nixos/modules/tasks/filesystems/nfs.nix +++ b/nixos/modules/tasks/filesystems/nfs.nix @@ -16,7 +16,7 @@ let lib.optionalAttrs (cfg.server.nproc != null) { nfsd.threads = cfg.server.nproc; } // lib.optionalAttrs (cfg.server.hostName != null) { - nfsd.host= cfg.hostName; + nfsd.host = cfg.server.hostName; } // lib.optionalAttrs (cfg.server.mountdPort != null) { mountd.port = cfg.server.mountdPort; } // lib.optionalAttrs (cfg.server.statdPort != null) { diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 1bcfd6ccba93..87876867c572 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -2,13 +2,9 @@ # # TODO: zfs tunables -with utils; -with lib; - let cfgZfs = config.boot.zfs; - optZfs = options.boot.zfs; cfgExpandOnBoot = config.services.zfs.expandOnBoot; cfgSnapshots = config.services.zfs.autoSnapshot; cfgSnapFlags = cfgSnapshots.flags; @@ -17,7 +13,7 @@ let cfgZED = config.services.zfs.zed; selectModulePackage = package: config.boot.kernelPackages.${package.kernelModuleAttribute}; - clevisDatasets = attrNames (filterAttrs (device: _: any (e: e.fsType == "zfs" && (fsNeededForBoot e) && (e.device == device || hasPrefix "${device}/" e.device)) config.system.build.fileSystems) config.boot.initrd.clevis.devices); + clevisDatasets = lib.attrNames (lib.filterAttrs (device: _: lib.any (e: e.fsType == "zfs" && (utils.fsNeededForBoot e) && (e.device == device || lib.hasPrefix "${device}/" e.device)) config.system.build.fileSystems) config.boot.initrd.clevis.devices); inInitrd = config.boot.initrd.supportedFilesystems.zfs or false; inSystem = config.boot.supportedFilesystems.zfs or false; @@ -28,17 +24,17 @@ let zfsAutoSnap = "${autosnapPkg}/bin/zfs-auto-snapshot"; - datasetToPool = x: elemAt (splitString "/" x) 0; + datasetToPool = x: lib.elemAt (lib.splitString "/" x) 0; fsToPool = fs: datasetToPool fs.device; - zfsFilesystems = filter (x: x.fsType == "zfs") config.system.build.fileSystems; + zfsFilesystems = lib.filter (x: x.fsType == "zfs") config.system.build.fileSystems; - allPools = unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools); + allPools = lib.unique ((map fsToPool zfsFilesystems) ++ cfgZfs.extraPools); - rootPools = unique (map fsToPool (filter fsNeededForBoot zfsFilesystems)); + rootPools = lib.unique (map fsToPool (lib.filter utils.fsNeededForBoot zfsFilesystems)); - dataPools = unique (filter (pool: !(elem pool rootPools)) allPools); + dataPools = lib.unique (lib.filter (pool: !(lib.elem pool rootPools)) allPools); snapshotNames = [ "frequent" "hourly" "daily" "weekly" "monthly" ]; @@ -60,7 +56,10 @@ let # sufficient amount of time has passed that we can assume it won't be. In the # latter case it makes one last attempt at importing, allowing the system to # (eventually) boot even with a degraded pool. - importLib = {zpoolCmd, awkCmd, cfgZfs}: '' + importLib = {zpoolCmd, awkCmd, pool}: let + devNodes = if pool != null && cfgZfs.pools ? ${pool} then cfgZfs.pools.${pool}.devNodes else cfgZfs.devNodes; + in '' + # shellcheck disable=SC2013 for o in $(cat /proc/cmdline); do case $o in zfs_force|zfs_force=1|zfs_force=y) @@ -70,7 +69,7 @@ let done poolReady() { pool="$1" - state="$("${zpoolCmd}" import -d "${cfgZfs.devNodes}" 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")" + state="$("${zpoolCmd}" import -d "${devNodes}" 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")" if [[ "$state" = "ONLINE" ]]; then return 0 else @@ -84,12 +83,13 @@ let } poolImport() { pool="$1" - "${zpoolCmd}" import -d "${cfgZfs.devNodes}" -N $ZFS_FORCE "$pool" + # shellcheck disable=SC2086 + "${zpoolCmd}" import -d "${devNodes}" -N $ZFS_FORCE "$pool" } ''; getPoolFilesystems = pool: - filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems; + lib.filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems; getPoolMounts = prefix: pool: let @@ -98,36 +98,36 @@ let # Remove the "/" suffix because even though most mountpoints # won't have it, the "/" mountpoint will, and we can't have the # trailing slash in "/sysroot/" in stage 1. - mountPoint = fs: escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint)); + mountPoint = fs: utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint)); hasUsr = lib.any (fs: fs.mountPoint == "/usr") poolFSes; in map (x: "${mountPoint x}.mount") poolFSes ++ lib.optional hasUsr "sysusr-usr.mount"; - getKeyLocations = pool: if isBool cfgZfs.requestEncryptionCredentials then { + getKeyLocations = pool: if lib.isBool cfgZfs.requestEncryptionCredentials then { hasKeys = cfgZfs.requestEncryptionCredentials; command = "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus -t volume,filesystem ${pool}"; } else let - keys = filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials; + keys = lib.filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials; in { hasKeys = keys != []; command = "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus -t volume,filesystem ${toString keys}"; }; createImportService = { pool, systemd, force, prefix ? "" }: - nameValuePair "zfs-import-${pool}" { + lib.nameValuePair "zfs-import-${pool}" { description = "Import ZFS pool \"${pool}\""; # We wait for systemd-udev-settle to ensure devices are available, # but don't *require* it, because mounts shouldn't be killed if it's stopped. # In the future, hopefully someone will complete this: # https://github.com/zfsonlinux/zfs/pull/4943 - wants = [ "systemd-udev-settle.service" ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target"; + wants = [ "systemd-udev-settle.service" ] ++ lib.optional (config.boot.initrd.clevis.useTang) "network-online.target"; after = [ "systemd-udev-settle.service" "systemd-modules-load.service" "systemd-ask-password-console.service" - ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target"; + ] ++ lib.optional (config.boot.initrd.clevis.useTang) "network-online.target"; requiredBy = getPoolMounts prefix pool ++ [ "zfs-import.target" ]; before = getPoolMounts prefix pool ++ [ "shutdown.target" "zfs-import.target" ]; conflicts = [ "shutdown.target" ]; @@ -138,30 +138,30 @@ let Type = "oneshot"; RemainAfterExit = true; }; - environment.ZFS_FORCE = optionalString force "-f"; + environment.ZFS_FORCE = lib.optionalString force "-f"; script = let keyLocations = getKeyLocations pool; in (importLib { # See comments at importLib definition. zpoolCmd = "${cfgZfs.package}/sbin/zpool"; awkCmd = "${pkgs.gawk}/bin/awk"; - inherit cfgZfs; + inherit pool; }) + '' if ! poolImported "${pool}"; then echo -n "importing ZFS pool \"${pool}\"..." # Loop across the import until it succeeds, because the devices needed may not be discovered yet. - for trial in `seq 1 60`; do + for _ in $(seq 1 60); do poolReady "${pool}" && poolImport "${pool}" && break sleep 1 done poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool. fi if poolImported "${pool}"; then - ${optionalString config.boot.initrd.clevis.enable (concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem} || true ") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets))} + ${lib.optionalString config.boot.initrd.clevis.enable (lib.concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem} || true ") (lib.filter (p: (lib.elemAt (lib.splitString "/" p) 0) == pool) clevisDatasets))} - ${optionalString keyLocations.hasKeys '' - ${keyLocations.command} | while IFS=$'\t' read ds kl ks; do + ${lib.optionalString keyLocations.hasKeys '' + ${keyLocations.command} | while IFS=$'\t' read -r ds kl ks; do { if [[ "$ks" != unavailable ]]; then continue @@ -193,15 +193,15 @@ let ''; }; - zedConf = generators.toKeyValue { - mkKeyValue = generators.mkKeyValueDefault { + zedConf = lib.generators.toKeyValue { + mkKeyValue = lib.generators.mkKeyValueDefault { mkValueString = v: - if isInt v then toString v - else if isString v then "\"${v}\"" + if lib.isInt v then toString v + else if lib.isString v then "\"${v}\"" else if true == v then "1" else if false == v then "0" - else if isList v then "\"" + (concatStringsSep " " v) + "\"" - else err "this value is" (toString v); + else if lib.isList v then "\"" + (lib.concatStringsSep " " v) + "\"" + else lib.err "this value is" (toString v); } "="; } cfgZED.settings; in @@ -209,38 +209,38 @@ 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;`") + (lib.mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.") + (lib.mkRemovedOptionModule [ "boot" "zfs" "enableUnstable" ] "Instead set `boot.zfs.package = pkgs.zfs_unstable;`") ]; ###### interface options = { boot.zfs = { - package = mkOption { - type = types.package; + package = lib.mkOption { + type = lib.types.package; default = pkgs.zfs; - defaultText = literalExpression "pkgs.zfs"; - description = "Configured ZFS userland tools package, use `pkgs.zfs_unstable` if you want to track the latest staging ZFS branch."; + defaultText = lib.literalExpression "pkgs.zfs"; + description = "Configured ZFS userland tools package."; }; - modulePackage = mkOption { + modulePackage = lib.mkOption { internal = true; # It is supposed to be selected automatically, but can be overridden by expert users. default = selectModulePackage cfgZfs.package; - type = types.package; + type = lib.types.package; description = "Configured ZFS kernel module package."; }; - enabled = mkOption { + enabled = lib.mkOption { readOnly = true; - type = types.bool; + type = lib.types.bool; default = inInitrd || inSystem; - defaultText = literalMD "`true` if ZFS filesystem support is enabled"; + defaultText = lib.literalMD "`true` if ZFS filesystem support is enabled"; description = "True if ZFS filesystem support is enabled"; }; - allowHibernation = mkOption { - type = types.bool; + allowHibernation = lib.mkOption { + type = lib.types.bool; default = false; description = '' Allow hibernation support, this may be a unsafe option depending on your @@ -248,8 +248,8 @@ in ''; }; - extraPools = mkOption { - type = types.listOf types.str; + extraPools = lib.mkOption { + type = lib.types.listOf lib.types.str; default = []; example = [ "tank" "data" ]; description = '' @@ -267,19 +267,20 @@ in ''; }; - devNodes = mkOption { - type = types.path; + devNodes = lib.mkOption { + type = lib.types.path; default = "/dev/disk/by-id"; description = '' - Name of directory from which to import ZFS devices. + Name of directory from which to import ZFS device, this is passed to `zpool import` + as the value of the `-d` option. - This should be a path under /dev containing stable names for all devices needed, as - import may fail if device nodes are renamed concurrently with a device failing. + For guidance on choosing this value, see + [the ZFS documentation](https://openzfs.github.io/openzfs-docs/Project%20and%20Community/FAQ.html#selecting-dev-names-when-creating-a-pool-linux). ''; }; - forceImportRoot = mkOption { - type = types.bool; + forceImportRoot = lib.mkOption { + type = lib.types.bool; default = true; description = '' Forcibly import the ZFS root pool(s) during early boot. @@ -296,8 +297,8 @@ in ''; }; - forceImportAll = mkOption { - type = types.bool; + forceImportAll = lib.mkOption { + type = lib.types.bool; default = false; description = '' Forcibly import all ZFS pool(s). @@ -309,8 +310,8 @@ in ''; }; - requestEncryptionCredentials = mkOption { - type = types.either types.bool (types.listOf types.str); + requestEncryptionCredentials = lib.mkOption { + type = lib.types.either lib.types.bool (lib.types.listOf lib.types.str); default = true; example = [ "tank" "data" ]; description = '' @@ -321,8 +322,8 @@ in ''; }; - passwordTimeout = mkOption { - type = types.int; + passwordTimeout = lib.mkOption { + type = lib.types.int; default = 0; description = '' Timeout in seconds to wait for password entry for decrypt at boot. @@ -331,8 +332,25 @@ in ''; }; + pools = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + devNodes = lib.mkOption { + type = lib.types.path; + default = cfgZfs.devNodes; + defaultText = "config.boot.zfs.devNodes"; + description = options.boot.zfs.devNodes.description; + }; + }; + }); + default = { }; + description = '' + Configuration for individual pools to override global defaults. + ''; + }; + removeLinuxDRM = lib.mkOption { - type = types.bool; + type = lib.types.bool; default = false; description = '' Patch the kernel to change symbols needed by ZFS from @@ -345,9 +363,9 @@ in }; services.zfs.autoSnapshot = { - enable = mkOption { + enable = lib.mkOption { default = false; - type = types.bool; + type = lib.types.bool; description = '' Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service. Note that you must set the `com.sun:auto-snapshot` @@ -360,10 +378,10 @@ in ''; }; - flags = mkOption { + flags = lib.mkOption { default = "-k -p"; example = "-k -p --utc"; - type = types.str; + type = lib.types.str; description = '' Flags to pass to the zfs-auto-snapshot command. @@ -379,41 +397,41 @@ in ''; }; - frequent = mkOption { + frequent = lib.mkOption { default = 4; - type = types.int; + type = lib.types.int; description = '' Number of frequent (15-minute) auto-snapshots that you wish to keep. ''; }; - hourly = mkOption { + hourly = lib.mkOption { default = 24; - type = types.int; + type = lib.types.int; description = '' Number of hourly auto-snapshots that you wish to keep. ''; }; - daily = mkOption { + daily = lib.mkOption { default = 7; - type = types.int; + type = lib.types.int; description = '' Number of daily auto-snapshots that you wish to keep. ''; }; - weekly = mkOption { + weekly = lib.mkOption { default = 4; - type = types.int; + type = lib.types.int; description = '' Number of weekly auto-snapshots that you wish to keep. ''; }; - monthly = mkOption { + monthly = lib.mkOption { default = 12; - type = types.int; + type = lib.types.int; description = '' Number of monthly auto-snapshots that you wish to keep. ''; @@ -421,16 +439,16 @@ in }; services.zfs.trim = { - enable = mkOption { + enable = lib.mkOption { description = "Whether to enable periodic TRIM on all ZFS pools."; default = true; example = false; - type = types.bool; + type = lib.types.bool; }; - interval = mkOption { + interval = lib.mkOption { default = "weekly"; - type = types.str; + type = lib.types.str; example = "daily"; description = '' How often we run trim. For most desktop and server systems @@ -441,9 +459,9 @@ in ''; }; - randomizedDelaySec = mkOption { + randomizedDelaySec = lib.mkOption { default = "6h"; - type = types.str; + type = lib.types.str; example = "12h"; description = '' Add a randomized delay before each ZFS trim. @@ -455,11 +473,11 @@ in }; services.zfs.autoScrub = { - enable = mkEnableOption "periodic scrubbing of ZFS pools"; + enable = lib.mkEnableOption "periodic scrubbing of ZFS pools"; - interval = mkOption { + interval = lib.mkOption { default = "monthly"; - type = types.str; + type = lib.types.str; example = "quarterly"; description = '' Systemd calendar expression when to scrub ZFS pools. See @@ -467,9 +485,9 @@ in ''; }; - randomizedDelaySec = mkOption { + randomizedDelaySec = lib.mkOption { default = "6h"; - type = types.str; + type = lib.types.str; example = "12h"; description = '' Add a randomized delay before each ZFS autoscrub. @@ -479,9 +497,9 @@ in ''; }; - pools = mkOption { + pools = lib.mkOption { default = []; - type = types.listOf types.str; + type = lib.types.listOf lib.types.str; example = [ "tank" ]; description = '' List of ZFS pools to periodically scrub. If empty, all pools @@ -490,8 +508,8 @@ in }; }; - services.zfs.expandOnBoot = mkOption { - type = types.either (types.enum [ "disabled" "all" ]) (types.listOf types.str); + services.zfs.expandOnBoot = lib.mkOption { + type = lib.types.either (lib.types.enum [ "disabled" "all" ]) (lib.types.listOf lib.types.str); default = "disabled"; example = [ "tank" "dozer" ]; description = '' @@ -508,10 +526,10 @@ in }; services.zfs.zed = { - enableMail = mkOption { - type = types.bool; + enableMail = lib.mkOption { + type = lib.types.bool; default = config.services.mail.sendmailSetuidWrapper != null; - defaultText = literalExpression '' + defaultText = lib.literalExpression '' config.services.mail.sendmailSetuidWrapper != null ''; description = '' @@ -519,9 +537,9 @@ in ''; }; - settings = mkOption { - type = with types; attrsOf (oneOf [ str int bool (listOf str) ]); - example = literalExpression '' + settings = lib.mkOption { + type = let t = lib.types; in t.attrsOf (t.oneOf [ t.str t.int t.bool (t.listOf t.str) ]); + example = lib.literalExpression '' { ZED_DEBUG_LOG = "/tmp/zed.debug.log"; @@ -549,8 +567,8 @@ in ###### implementation - config = mkMerge [ - (mkIf cfgZfs.enabled { + config = lib.mkMerge [ + (lib.mkIf cfgZfs.enabled { assertions = [ { assertion = cfgZfs.modulePackage.version == cfgZfs.package.version; @@ -569,7 +587,7 @@ in message = "boot.zfs.allowHibernation while force importing is enabled will cause data corruption"; } { - assertion = !(elem "" allPools); + assertion = !(lib.elem "" allPools); message = '' Automatic pool detection found an empty pool name, which can't be used. Hint: for `fileSystems` entries with `fsType = zfs`, the `device` attribute @@ -591,11 +609,10 @@ in ]; }; - boot.initrd = mkIf inInitrd { - # spl has been removed in ≥ 2.2.0. - kernelModules = [ "zfs" ] ++ lib.optional (lib.versionOlder "2.2.0" version) "spl"; + boot.initrd = lib.mkIf inInitrd { + kernelModules = [ "zfs" ]; extraUtilsCommands = - mkIf (!config.boot.initrd.systemd.enable) '' + lib.mkIf (!config.boot.initrd.systemd.enable) '' copy_bin_and_libs ${cfgZfs.package}/sbin/zfs copy_bin_and_libs ${cfgZfs.package}/sbin/zdb copy_bin_and_libs ${cfgZfs.package}/sbin/zpool @@ -603,22 +620,22 @@ in copy_bin_and_libs ${cfgZfs.package}/lib/udev/zvol_id ''; extraUtilsCommandsTest = - mkIf (!config.boot.initrd.systemd.enable) '' + lib.mkIf (!config.boot.initrd.systemd.enable) '' $out/bin/zfs --help >/dev/null 2>&1 $out/bin/zpool --help >/dev/null 2>&1 ''; - postResumeCommands = mkIf (!config.boot.initrd.systemd.enable) (concatStringsSep "\n" (['' - ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}" + postResumeCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (lib.concatStringsSep "\n" (['' + ZFS_FORCE="${lib.optionalString cfgZfs.forceImportRoot "-f"}" ''] ++ [(importLib { # See comments at importLib definition. zpoolCmd = "zpool"; awkCmd = "awk"; - inherit cfgZfs; + pool = null; })] ++ (map (pool: '' echo -n "importing root ZFS pool \"${pool}\"..." # Loop across the import until it succeeds, because the devices needed may not be discovered yet. if ! poolImported "${pool}"; then - for trial in `seq 1 60`; do + for _ in $(seq 1 60); do poolReady "${pool}" > /dev/null && msg="$(poolImport "${pool}" 2>&1)" && break sleep 1 echo -n . @@ -630,21 +647,21 @@ in poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool. fi - ${optionalString config.boot.initrd.clevis.enable (concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem}") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets))} + ${lib.optionalString config.boot.initrd.clevis.enable (lib.concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem}") (lib.filter (p: (lib.elemAt (lib.splitString "/" p) 0) == pool) clevisDatasets))} - ${if isBool cfgZfs.requestEncryptionCredentials - then optionalString cfgZfs.requestEncryptionCredentials '' + ${if lib.isBool cfgZfs.requestEncryptionCredentials + then lib.optionalString cfgZfs.requestEncryptionCredentials '' zfs load-key -a '' - else concatMapStrings (fs: '' - zfs load-key -- ${escapeShellArg fs} - '') (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)} + else lib.concatMapStrings (fs: '' + zfs load-key -- ${lib.escapeShellArg fs} + '') (lib.filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)} '') rootPools))); # Systemd in stage 1 - systemd = mkIf config.boot.initrd.systemd.enable { + systemd = lib.mkIf config.boot.initrd.systemd.enable { packages = [cfgZfs.package]; - services = listToAttrs (map (pool: createImportService { + services = lib.listToAttrs (map (pool: createImportService { inherit pool; systemd = config.boot.initrd.systemd.package; force = cfgZfs.forceImportRoot; @@ -671,18 +688,18 @@ in systemd.shutdownRamfs.storePaths = ["${cfgZfs.package}/bin/zpool"]; # TODO FIXME See https://github.com/NixOS/nixpkgs/pull/99386#issuecomment-798813567. To not break people's bootloader and as probably not everybody would read release notes that thoroughly add inSystem. - boot.loader.grub = mkIf (inInitrd || inSystem) { + boot.loader.grub = lib.mkIf (inInitrd || inSystem) { zfsSupport = true; zfsPackage = cfgZfs.package; }; services.zfs.zed.settings = { - ZED_EMAIL_PROG = mkIf cfgZED.enableMail (mkDefault ( + ZED_EMAIL_PROG = lib.mkIf cfgZED.enableMail (lib.mkDefault ( config.security.wrapperDir + "/" + config.services.mail.sendmailSetuidWrapper.program )); # subject in header for sendmail - ZED_EMAIL_OPTS = mkIf cfgZED.enableMail (mkDefault "@ADDRESS@"); + ZED_EMAIL_OPTS = lib.mkIf cfgZED.enableMail (lib.mkDefault "@ADDRESS@"); PATH = lib.makeBinPath [ cfgZfs.package @@ -701,7 +718,7 @@ in ACTION=="add|change", KERNEL=="sd[a-z]*[0-9]*|mmcblk[0-9]*p[0-9]*|nvme[0-9]*n[0-9]*p[0-9]*", ENV{ID_FS_TYPE}=="zfs_member", ATTR{../queue/scheduler}="none" ''; - environment.etc = genAttrs + environment.etc = lib.genAttrs (map (file: "zfs/zed.d/${file}") [ @@ -726,7 +743,7 @@ in system.fsPackages = [ cfgZfs.package ]; # XXX: needed? zfs doesn't have (need) a fsck environment.systemPackages = [ cfgZfs.package ] - ++ optional cfgSnapshots.enable autosnapPkg; # so the user can run the command to see flags + ++ lib.optional cfgSnapshots.enable autosnapPkg; # so the user can run the command to see flags services.udev.packages = [ cfgZfs.package ]; # to hook zvol naming, etc. systemd.packages = [ cfgZfs.package ]; @@ -741,7 +758,7 @@ in # This forces a sync of any ZFS pools prior to poweroff, even if they're set # to sync=disabled. createSyncService = pool: - nameValuePair "zfs-sync-${pool}" { + lib.nameValuePair "zfs-sync-${pool}" { description = "Sync ZFS pool \"${pool}\""; wantedBy = [ "shutdown.target" ]; unitConfig = { @@ -757,12 +774,12 @@ in }; createZfsService = serv: - nameValuePair serv { + lib.nameValuePair serv { after = [ "systemd-modules-load.service" ]; wantedBy = [ "zfs.target" ]; }; - in listToAttrs (map createImportService' dataPools ++ + in lib.listToAttrs (map createImportService' dataPools ++ map createSyncService allPools ++ map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]); @@ -771,7 +788,7 @@ in systemd.targets.zfs.wantedBy = [ "multi-user.target" ]; }) - (mkIf (cfgZfs.enabled && cfgExpandOnBoot != "disabled") { + (lib.mkIf (cfgZfs.enabled && cfgExpandOnBoot != "disabled") { systemd.services."zpool-expand@" = { description = "Expand ZFS pools"; after = [ "zfs.target" ]; @@ -824,7 +841,7 @@ in }; }) - (mkIf (cfgZfs.enabled && cfgSnapshots.enable) { + (lib.mkIf (cfgZfs.enabled && cfgSnapshots.enable) { systemd.services = let descr = name: if name == "frequent" then "15 mins" else if name == "hourly" then "hour" @@ -862,7 +879,7 @@ in }) snapshotNames); }) - (mkIf (cfgZfs.enabled && cfgScrub.enable) { + (lib.mkIf (cfgZfs.enabled && cfgScrub.enable) { systemd.services.zfs-scrub = { description = "ZFS pools scrubbing"; after = [ "zfs-import.target" ]; @@ -870,9 +887,10 @@ in Type = "simple"; }; script = '' + # shellcheck disable=SC2046 ${cfgZfs.package}/bin/zpool scrub -w ${ if cfgScrub.pools != [] then - (concatStringsSep " " cfgScrub.pools) + (lib.concatStringsSep " " cfgScrub.pools) else "$(${cfgZfs.package}/bin/zpool list -H -o name)" } @@ -890,7 +908,7 @@ in }; }) - (mkIf (cfgZfs.enabled && cfgTrim.enable) { + (lib.mkIf (cfgZfs.enabled && cfgTrim.enable) { systemd.services.zpool-trim = { description = "ZFS pools trim"; after = [ "zfs-import.target" ]; diff --git a/nixos/modules/tasks/lvm.nix b/nixos/modules/tasks/lvm.nix index 0b628725969a..4d1018978eaf 100644 --- a/nixos/modules/tasks/lvm.nix +++ b/nixos/modules/tasks/lvm.nix @@ -31,6 +31,8 @@ in { Whether to enable booting from LVM2 in the initrd. ''; + default = config.boot.initrd.systemd.enable && config.services.lvm.enable; + defaultText = lib.literalExpression "config.boot.initrd.systemd.enable && config.services.lvm.enable"; }; config = mkMerge [ diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index bbf2d337aac6..83c0dc27885e 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -447,7 +447,7 @@ let (let deps = deviceDependency v.interface; in - { description = "Vlan Interface ${n}"; + { description = "MACVLAN Interface ${n}"; wantedBy = [ "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; partOf = [ "network-setup.service" ]; @@ -567,7 +567,7 @@ let (let deps = deviceDependency v.interface; in - { description = "Vlan Interface ${n}"; + { description = "VLAN Interface ${n}"; wantedBy = [ "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; partOf = [ "network-setup.service" ]; diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 7e3e24c727b2..c9ea31a85bee 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -438,7 +438,7 @@ let hostidFile = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } '' hi="${cfg.hostId}" - ${if pkgs.stdenv.isBigEndian then '' + ${if pkgs.stdenv.hostPlatform.isBigEndian then '' echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out '' else '' echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix index 0f551dcbd63b..050c817e8718 100644 --- a/nixos/modules/testing/test-instrumentation.nix +++ b/nixos/modules/testing/test-instrumentation.nix @@ -90,6 +90,7 @@ in contents."/etc/systemd/journald.conf".text = '' [Journal] ForwardToConsole=yes + TTYPath=/dev/${qemu-common.qemuSerialDevice} MaxLevelConsole=debug ''; diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix index ecb57483cce9..76d8a3bb365b 100644 --- a/nixos/modules/virtualisation/azure-image.nix +++ b/nixos/modules/virtualisation/azure-image.nix @@ -1,22 +1,33 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let cfg = config.virtualisation.azureImage; in { - imports = [ ./azure-common.nix ]; + imports = [ + ./azure-common.nix + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "virtualisation" + "azureImage" + "diskSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; options.virtualisation.azureImage = { - diskSize = mkOption { - type = with types; either (enum [ "auto" ]) int; - default = "auto"; - example = 2048; - description = '' - Size of disk image. Unit is MB. - ''; - }; - bootSize = mkOption { type = types.int; default = 256; @@ -35,7 +46,12 @@ in }; vmGeneration = mkOption { - type = with types; enum [ "v1" "v2" ]; + type = + with types; + enum [ + "v1" + "v2" + ]; default = "v1"; description = '' VM Generation to use. @@ -57,7 +73,8 @@ in bootSize = "${toString cfg.bootSize}M"; partitionTableType = if cfg.vmGeneration == "v2" then "efi" else "legacy"; - inherit (cfg) diskSize contents; + inherit (cfg) contents; + inherit (config.virtualisation) diskSize; inherit config lib pkgs; }; }; diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix index 53791e911406..b6ef01516e34 100644 --- a/nixos/modules/virtualisation/digital-ocean-image.nix +++ b/nixos/modules/virtualisation/digital-ocean-image.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let @@ -6,18 +11,24 @@ let in { - imports = [ ./digital-ocean-config.nix ]; + imports = [ + ./digital-ocean-config.nix + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "virtualisation" + "digitalOceanImage" + "diskSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; options = { - virtualisation.digitalOceanImage.diskSize = mkOption { - type = with types; either (enum [ "auto" ]) int; - default = "auto"; - example = 4096; - description = '' - Size of disk image. Unit is MB. - ''; - }; - virtualisation.digitalOceanImage.configFile = mkOption { type = with types; nullOr path; default = null; @@ -31,7 +42,10 @@ in }; virtualisation.digitalOceanImage.compressionMethod = mkOption { - type = types.enum [ "gzip" "bzip2" ]; + type = types.enum [ + "gzip" + "bzip2" + ]; default = "gzip"; example = "bzip2"; description = '' @@ -44,27 +58,35 @@ in #### implementation config = { - system.build.digitalOceanImage = import ../../lib/make-disk-image.nix { name = "digital-ocean-image"; format = "qcow2"; - postVM = let - compress = { - "gzip" = "${pkgs.gzip}/bin/gzip"; - "bzip2" = "${pkgs.bzip2}/bin/bzip2"; - }.${cfg.compressionMethod}; - in '' - ${compress} $diskImage - ''; - configFile = if cfg.configFile == null - then config.virtualisation.digitalOcean.defaultConfigFile - else cfg.configFile; - inherit (cfg) diskSize; + postVM = + let + compress = + { + "gzip" = "${pkgs.gzip}/bin/gzip"; + "bzip2" = "${pkgs.bzip2}/bin/bzip2"; + } + .${cfg.compressionMethod}; + in + '' + ${compress} $diskImage + ''; + configFile = + if cfg.configFile == null then + config.virtualisation.digitalOcean.defaultConfigFile + else + cfg.configFile; + inherit (config.virtualisation) diskSize; inherit config lib pkgs; }; }; - meta.maintainers = with maintainers; [ arianvp eamsden ]; + meta.maintainers = with maintainers; [ + arianvp + eamsden + ]; } diff --git a/nixos/modules/virtualisation/disk-size-option.nix b/nixos/modules/virtualisation/disk-size-option.nix new file mode 100644 index 000000000000..487c3adfe2b7 --- /dev/null +++ b/nixos/modules/virtualisation/disk-size-option.nix @@ -0,0 +1,38 @@ +{ lib, config, ... }: +let + t = lib.types; +in +{ + options = { + virtualisation.diskSizeAutoSupported = lib.mkOption { + type = t.bool; + default = true; + description = '' + Whether the current image builder or vm runner supports `virtualisation.diskSize = "auto".` + ''; + internal = true; + }; + + virtualisation.diskSize = lib.mkOption { + type = t.either (t.enum [ "auto" ]) t.ints.positive; + default = if config.virtualisation.diskSizeAutoSupported then "auto" else 1024; + defaultText = "\"auto\" if diskSizeAutoSupported, else 1024"; + description = '' + The disk size in megabytes of the virtual machine. + ''; + }; + }; + + config = + let + inherit (config.virtualisation) diskSize diskSizeAutoSupported; + in + { + assertions = [ + { + assertion = diskSize != "auto" || diskSizeAutoSupported; + message = "Setting virtualisation.diskSize to `auto` is not supported by the current image build or vm runner; use an explicit size."; + } + ]; + }; +} diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix index d6d775998119..741828c7468a 100644 --- a/nixos/modules/virtualisation/docker.nix +++ b/nixos/modules/virtualisation/docker.nix @@ -52,10 +52,26 @@ in daemon.settings = mkOption { - type = settingsFormat.type; + type = types.submodule { + freeformType = settingsFormat.type; + options = { + live-restore = mkOption { + type = types.bool; + # Prior to NixOS 24.11, this was set to true by default, while upstream defaulted to false. + # Keep the option unset to follow upstream defaults + default = versionOlder config.system.stateVersion "24.11"; + defaultText = literalExpression "lib.versionOlder config.system.stateVersion \"24.11\""; + description = '' + Allow dockerd to be restarted without affecting running container. + This option is incompatible with docker swarm. + ''; + }; + }; + }; default = { }; example = { ipv6 = true; + "live-restore" = true; "fixed-cidr-v6" = "fd00::/80"; }; description = '' @@ -75,16 +91,6 @@ in ''; }; - liveRestore = - mkOption { - type = types.bool; - default = true; - description = '' - Allow dockerd to be restarted without affecting running container. - This option is incompatible with docker swarm. - ''; - }; - storageDriver = mkOption { type = types.nullOr (types.enum ["aufs" "btrfs" "devicemapper" "overlay" "overlay2" "zfs"]); @@ -167,6 +173,11 @@ in }; }; + imports = [ + (mkRemovedOptionModule ["virtualisation" "docker" "socketActivation"] "This option was removed and socket activation is now always active") + (mkAliasOptionModule ["virtualisation" "docker" "liveRestore"] ["virtualisation" "docker" "daemon" "settings" "live-restore"]) + ]; + ###### implementation config = mkIf cfg.enable (mkMerge [{ @@ -244,7 +255,7 @@ in }; assertions = [ - { assertion = cfg.enableNvidia && pkgs.stdenv.isx86_64 -> config.hardware.graphics.enable32Bit or false; + { assertion = cfg.enableNvidia && pkgs.stdenv.hostPlatform.isx86_64 -> config.hardware.graphics.enable32Bit or false; message = "Option enableNvidia on x86_64 requires 32-bit support libraries"; }]; @@ -253,7 +264,6 @@ in hosts = [ "fd://" ]; log-driver = mkDefault cfg.logDriver; storage-driver = mkIf (cfg.storageDriver != null) (mkDefault cfg.storageDriver); - live-restore = mkDefault cfg.liveRestore; runtimes = mkIf cfg.enableNvidia { nvidia = { # Use the legacy nvidia-container-runtime wrapper to allow @@ -266,9 +276,4 @@ in }; } ]); - - imports = [ - (mkRemovedOptionModule ["virtualisation" "docker" "socketActivation"] "This option was removed and socket activation is now always active") - ]; - } diff --git a/nixos/modules/virtualisation/ec2-data.nix b/nixos/modules/virtualisation/ec2-data.nix index 0b9d098dbab7..2da271a59236 100644 --- a/nixos/modules/virtualisation/ec2-data.nix +++ b/nixos/modules/virtualisation/ec2-data.nix @@ -33,7 +33,8 @@ with lib; if ! [ -e /root/.ssh/authorized_keys ]; then echo "obtaining SSH key..." - mkdir -m 0700 -p /root/.ssh + mkdir -p /root/.ssh + chmod 0700 /root/.ssh if [ -s /etc/ec2-metadata/public-keys-0-openssh-key ]; then (umask 177; cat /etc/ec2-metadata/public-keys-0-openssh-key >> /root/.ssh/authorized_keys) echo "new key added to authorized_keys" @@ -45,19 +46,20 @@ with lib; # generate one normally. userData=/etc/ec2-metadata/user-data - mkdir -m 0755 -p /etc/ssh + mkdir -p /etc/ssh + chmod 0755 /etc/ssh if [ -s "$userData" ]; then key="$(sed 's/|/\n/g; s/SSH_HOST_DSA_KEY://; t; d' $userData)" key_pub="$(sed 's/SSH_HOST_DSA_KEY_PUB://; t; d' $userData)" - if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_dsa_key ]; then + if [ -n "$key" ] && [ -n "$key_pub" ] && [ ! -e /etc/ssh/ssh_host_dsa_key ]; then (umask 077; echo "$key" > /etc/ssh/ssh_host_dsa_key) echo "$key_pub" > /etc/ssh/ssh_host_dsa_key.pub fi key="$(sed 's/|/\n/g; s/SSH_HOST_ED25519_KEY://; t; d' $userData)" key_pub="$(sed 's/SSH_HOST_ED25519_KEY_PUB://; t; d' $userData)" - if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_ed25519_key ]; then + if [ -n "$key" ] && [ -n "$key_pub" ] && [ ! -e /etc/ssh/ssh_host_ed25519_key ]; then (umask 077; echo "$key" > /etc/ssh/ssh_host_ed25519_key) echo "$key_pub" > /etc/ssh/ssh_host_ed25519_key.pub fi @@ -79,7 +81,7 @@ with lib; # ec2-get-console-output. echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" > /dev/console for i in /etc/ssh/ssh_host_*_key.pub; do - ${config.programs.ssh.package}/bin/ssh-keygen -l -f $i || true > /dev/console + ${config.programs.ssh.package}/bin/ssh-keygen -l -f "$i" || true > /dev/console done echo "-----END SSH HOST KEY FINGERPRINTS-----" > /dev/console ''; @@ -88,4 +90,6 @@ with lib; }; }; + + meta.maintainers = with maintainers; [ arianvp ]; } diff --git a/nixos/modules/virtualisation/ec2-metadata-fetcher.sh b/nixos/modules/virtualisation/ec2-metadata-fetcher.sh index 716aff7c22fb..66a05e7a436f 100644 --- a/nixos/modules/virtualisation/ec2-metadata-fetcher.sh +++ b/nixos/modules/virtualisation/ec2-metadata-fetcher.sh @@ -1,5 +1,6 @@ metaDir=/etc/ec2-metadata -mkdir -m 0755 -p "$metaDir" +mkdir -p "$metaDir" +chmod 0755 "$metaDir" rm -f "$metaDir/*" get_imds_token() { @@ -40,7 +41,7 @@ while [ $try -le 3 ]; do sleep 1 done -if [ "x$IMDS_TOKEN" == "x" ]; then +if [ "$IMDS_TOKEN" == "" ]; then echo "failed to fetch an IMDS2v token." fi diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix index 8e7b31b439bf..c2529bb3db3f 100644 --- a/nixos/modules/virtualisation/google-compute-image.nix +++ b/nixos/modules/virtualisation/google-compute-image.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let @@ -14,18 +19,24 @@ let in { - imports = [ ./google-compute-config.nix ]; + imports = [ + ./google-compute-config.nix + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "virtualisation" + "googleComputeImage" + "diskSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; options = { - virtualisation.googleComputeImage.diskSize = mkOption { - type = with types; either (enum [ "auto" ]) int; - default = "auto"; - example = 1536; - description = '' - Size of disk image. Unit is MB. - ''; - }; - virtualisation.googleComputeImage.configFile = mkOption { type = with types; nullOr str; default = null; @@ -64,7 +75,13 @@ in system.build.googleComputeImage = import ../../lib/make-disk-image.nix { name = "google-compute-image"; postVM = '' - PATH=$PATH:${with pkgs; lib.makeBinPath [ gnutar gzip ]} + PATH=$PATH:${ + with pkgs; + lib.makeBinPath [ + gnutar + gzip + ] + } pushd $out mv $diskImage disk.raw tar -Sc disk.raw | gzip -${toString cfg.compressionLevel} > \ @@ -75,7 +92,7 @@ in format = "raw"; configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile; partitionTableType = if cfg.efi then "efi" else "legacy"; - inherit (cfg) diskSize; + inherit (config.virtualisation) diskSize; inherit config lib pkgs; }; diff --git a/nixos/modules/virtualisation/hyperv-image.nix b/nixos/modules/virtualisation/hyperv-image.nix index eb1bbe9f3a58..ea0603fa6ae5 100644 --- a/nixos/modules/virtualisation/hyperv-image.nix +++ b/nixos/modules/virtualisation/hyperv-image.nix @@ -1,21 +1,34 @@ -{ config, pkgs, lib, ... }: +{ + config, + pkgs, + lib, + ... +}: with lib; let cfg = config.hyperv; +in +{ + + imports = [ + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "hyperv" + "baseImageSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; -in { options = { hyperv = { - baseImageSize = mkOption { - type = with types; either (enum [ "auto" ]) int; - default = "auto"; - example = 2048; - description = '' - The size of the hyper-v base image in MiB. - ''; - }; vmDerivationName = mkOption { type = types.str; default = "nixos-hyperv-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}"; @@ -34,6 +47,10 @@ in { }; config = { + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault + # to avoid breaking existing configs using that. + virtualisation.diskSize = lib.mkOverride 1490 (4 * 1024); + system.build.hypervImage = import ../../lib/make-disk-image.nix { name = cfg.vmDerivationName; postVM = '' @@ -41,7 +58,7 @@ in { rm $diskImage ''; format = "raw"; - diskSize = cfg.baseImageSize; + inherit (config.virtualisation) diskSize; partitionTableType = "efi"; inherit config lib pkgs; }; diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix index 72c2a2ef5551..195c5259a324 100644 --- a/nixos/modules/virtualisation/libvirtd.nix +++ b/nixos/modules/virtualisation/libvirtd.nix @@ -301,6 +301,27 @@ in ''; }; + shutdownTimeout = mkOption { + type = types.ints.unsigned; + default = 300; + description = '' + Number of seconds we're willing to wait for a guest to shut down. + If parallel shutdown is enabled, this timeout applies as a timeout + for shutting down all guests on a single URI defined in the variable URIS. + If this is 0, then there is no time out (use with caution, as guests might not + respond to a shutdown request). + ''; + }; + + startDelay = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + Number of seconds to wait between each guest start. + If set to 0, all guests will start up in parallel. + ''; + }; + allowedBridges = mkOption { type = types.listOf types.str; default = [ "virbr0" ]; @@ -495,6 +516,8 @@ in environment.ON_BOOT = "${cfg.onBoot}"; environment.ON_SHUTDOWN = "${cfg.onShutdown}"; environment.PARALLEL_SHUTDOWN = "${toString cfg.parallelShutdown}"; + environment.SHUTDOWN_TIMEOUT = "${toString cfg.shutdownTimeout}"; + environment.START_DELAY = "${toString cfg.startDelay}"; }; systemd.sockets.virtlogd = { diff --git a/nixos/modules/virtualisation/linode-image.nix b/nixos/modules/virtualisation/linode-image.nix index 51f793ac011d..ff61c5f5d1db 100644 --- a/nixos/modules/virtualisation/linode-image.nix +++ b/nixos/modules/virtualisation/linode-image.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: with lib; let @@ -12,17 +17,24 @@ let ''; in { - imports = [ ./linode-config.nix ]; + imports = [ + ./linode-config.nix + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "virtualisation" + "linodeImage" + "diskSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; options = { - virtualisation.linodeImage.diskSize = mkOption { - type = with types; either (enum (singleton "auto")) ints.positive; - default = "auto"; - example = 1536; - description = '' - Size of disk image in MB. - ''; - }; virtualisation.linodeImage.configFile = mkOption { type = with types; nullOr str; @@ -57,7 +69,7 @@ in format = "raw"; partitionTableType = "none"; configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile; - inherit (cfg) diskSize; + inherit (config.virtualisation) diskSize; inherit config lib pkgs; }; }; diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix index ba5112aa6cec..812d22d773e1 100644 --- a/nixos/modules/virtualisation/nixos-containers.nix +++ b/nixos/modules/virtualisation/nixos-containers.nix @@ -85,8 +85,13 @@ let startScript = cfg: '' - mkdir -p -m 0755 "$root/etc" "$root/var/lib" - mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/nixos-containers + # Declare root explicitly to avoid shellcheck warnings, it comes from the env + declare root + + mkdir -p "$root/etc" "$root/var/lib" + chmod 0755 "$root/etc" "$root/var/lib" + mkdir -p "$root/var/lib/private" "$root/root" /run/nixos-containers + chmod 0700 "$root/var/lib/private" "$root/root" /run/nixos-containers if ! [ -e "$root/etc/os-release" ]; then touch "$root/etc/os-release" fi @@ -95,19 +100,24 @@ let touch "$root/etc/machine-id" fi - mkdir -p -m 0755 \ + mkdir -p \ + "/nix/var/nix/profiles/per-container/$INSTANCE" \ + "/nix/var/nix/gcroots/per-container/$INSTANCE" + chmod 0755 \ "/nix/var/nix/profiles/per-container/$INSTANCE" \ "/nix/var/nix/gcroots/per-container/$INSTANCE" cp --remove-destination /etc/resolv.conf "$root/etc/resolv.conf" + declare -a extraFlags + if [ "$PRIVATE_NETWORK" = 1 ]; then - extraFlags+=" --private-network" + extraFlags+=("--private-network") fi if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then - extraFlags+=" --network-veth" + extraFlags+=("--network-veth") fi if [ -n "$HOST_PORT" ]; then @@ -115,30 +125,30 @@ let IFS="," for i in $HOST_PORT do - extraFlags+=" --port=$i" + extraFlags+=("--port=$i") done IFS=$OIFS fi if [ -n "$HOST_BRIDGE" ]; then - extraFlags+=" --network-bridge=$HOST_BRIDGE" + extraFlags+=("--network-bridge=$HOST_BRIDGE") fi - extraFlags+=" ${concatStringsSep " " (mapAttrsToList nspawnExtraVethArgs cfg.extraVeths)}" + extraFlags+=(${lib.escapeShellArgs (mapAttrsToList nspawnExtraVethArgs cfg.extraVeths)}) for iface in $INTERFACES; do - extraFlags+=" --network-interface=$iface" + extraFlags+=("--network-interface=$iface") done for iface in $MACVLANS; do - extraFlags+=" --network-macvlan=$iface" + extraFlags+=("--network-macvlan=$iface") done # If the host is 64-bit and the container is 32-bit, add a # --personality flag. ${optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") '' - if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then - extraFlags+=" --personality=x86" + if [ "$(< "''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system")" = i686-linux ]; then + extraFlags+=("--personality=x86") fi ''} @@ -149,9 +159,11 @@ let # Kill signal handling means systemd-nspawn will pass a system-halt signal # to the container systemd when it receives SIGTERM for container shutdown; # containerInit and stage2 have to handle this as well. + # TODO: fix shellcheck issue properly + # shellcheck disable=SC2086 exec ${config.systemd.package}/bin/systemd-nspawn \ --keep-unit \ - -M "$INSTANCE" -D "$root" $extraFlags \ + -M "$INSTANCE" -D "$root" "''${extraFlags[@]}" \ $EXTRA_NSPAWN_FLAGS \ --notify-ready=yes \ --kill-signal=SIGRTMIN+3 \ @@ -203,33 +215,33 @@ let if cfg.${attribute} == null then '' if [ -n "${variable}" ]; then - ${ipcmd} add ${variable} dev $ifaceHost + ${ipcmd} add "${variable}" dev "$ifaceHost" fi '' else - "${ipcmd} add ${cfg.${attribute}} dev $ifaceHost"; + ''${ipcmd} add ${cfg.${attribute}} dev "$ifaceHost"''; renderExtraVeth = name: cfg: if cfg.hostBridge != null then '' # Add ${name} to bridge ${cfg.hostBridge} - ip link set dev ${name} master ${cfg.hostBridge} up + ip link set dev "${name}" master "${cfg.hostBridge}" up '' else '' echo "Bring ${name} up" - ip link set dev ${name} up + ip link set dev "${name}" up # Set IPs and routes for ${name} ${optionalString (cfg.hostAddress != null) '' - ip addr add ${cfg.hostAddress} dev ${name} + ip addr add ${cfg.hostAddress} dev "${name}" ''} ${optionalString (cfg.hostAddress6 != null) '' - ip -6 addr add ${cfg.hostAddress6} dev ${name} + ip -6 addr add ${cfg.hostAddress6} dev "${name}" ''} ${optionalString (cfg.localAddress != null) '' - ip route add ${cfg.localAddress} dev ${name} + ip route add ${cfg.localAddress} dev "${name}" ''} ${optionalString (cfg.localAddress6 != null) '' - ip -6 route add ${cfg.localAddress6} dev ${name} + ip -6 route add ${cfg.localAddress6} dev "${name}" ''} ''; in @@ -238,7 +250,7 @@ let [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then if [ -z "$HOST_BRIDGE" ]; then ifaceHost=ve-$INSTANCE - ip link set dev $ifaceHost up + ip link set dev "$ifaceHost" up ${ipcall cfg "ip addr" "$HOST_ADDRESS" "hostAddress"} ${ipcall cfg "ip -6 addr" "$HOST_ADDRESS6" "hostAddress6"} diff --git a/nixos/modules/virtualisation/oci-image.nix b/nixos/modules/virtualisation/oci-image.nix index 1e2b90bfd46e..fe286853de81 100644 --- a/nixos/modules/virtualisation/oci-image.nix +++ b/nixos/modules/virtualisation/oci-image.nix @@ -1,4 +1,9 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.oci; @@ -7,9 +12,14 @@ in imports = [ ./oci-common.nix ]; config = { + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault + # to avoid breaking existing configs using that. + virtualisation.diskSize = lib.mkOverride 1490 (8 * 1024); + virtualisation.diskSizeAutoSupported = false; + system.build.OCIImage = import ../../lib/make-disk-image.nix { inherit config lib pkgs; - inherit (cfg) diskSize; + inherit (config.virtualisation) diskSize; name = "oci-image"; configFile = ./oci-config-user.nix; format = "qcow2"; @@ -25,7 +35,10 @@ in after = [ "network-online.target" ]; wants = [ "network-online.target" ]; - path = [ pkgs.coreutils pkgs.curl ]; + path = [ + pkgs.coreutils + pkgs.curl + ]; script = '' mkdir -m 0700 -p /root/.ssh if [ -f /root/.ssh/authorized_keys ]; then diff --git a/nixos/modules/virtualisation/oci-options.nix b/nixos/modules/virtualisation/oci-options.nix index 76f3475a4281..b8d66c0290b3 100644 --- a/nixos/modules/virtualisation/oci-options.nix +++ b/nixos/modules/virtualisation/oci-options.nix @@ -1,5 +1,23 @@ -{ config, lib, pkgs, ... }: { + lib, + ... +}: +{ + imports = [ + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "oci" + "diskSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; + options = { oci = { efi = lib.mkOption { @@ -9,12 +27,6 @@ Whether the OCI instance is using EFI. ''; }; - diskSize = lib.mkOption { - type = lib.types.int; - default = 8192; - description = "Size of the disk image created in MB."; - example = "diskSize = 12 * 1024; # 12GiB"; - }; }; }; } diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix index d390c78432ae..9bbe7a596f07 100644 --- a/nixos/modules/virtualisation/proxmox-image.nix +++ b/nixos/modules/virtualisation/proxmox-image.nix @@ -1,8 +1,28 @@ -{ config, pkgs, lib, ... }: +{ + config, + pkgs, + lib, + ... +}: with lib; - { + imports = [ + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "proxmox" + "qemuConf" + "diskSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; + options.proxmox = { qemuConf = { # essential configs @@ -54,7 +74,10 @@ with lib; ''; }; bios = mkOption { - type = types.enum [ "seabios" "ovmf" ]; + type = types.enum [ + "seabios" + "ovmf" + ]; default = "seabios"; description = '' Select BIOS implementation (seabios = Legacy BIOS, ovmf = UEFI). @@ -87,16 +110,6 @@ with lib; either "efi" or "hybrid". ''; }; - diskSize = mkOption { - type = types.str; - default = "auto"; - example = "20480"; - description = '' - The size of the disk, in megabytes. - if "auto" size is calculated based on the contents copied to it and - additionalSpace is taken into account. - ''; - }; net0 = mkOption { type = types.commas; default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1"; @@ -124,8 +137,13 @@ with lib; }; }; qemuExtraConf = mkOption { - type = with types; attrsOf (oneOf [ str int ]); - default = {}; + type = + with types; + attrsOf (oneOf [ + str + int + ]); + default = { }; example = literalExpression '' { cpu = "host"; @@ -137,7 +155,12 @@ with lib; ''; }; partitionTableType = mkOption { - type = types.enum [ "efi" "hybrid" "legacy" "legacy+gpt" ]; + type = types.enum [ + "efi" + "hybrid" + "legacy" + "legacy+gpt" + ]; description = '' Partition table type to use. See make-disk-image.nix partitionTableType for details. Defaults to 'legacy' for 'proxmox.qemuConf.bios="seabios"' (default), other bios values defaults to 'efi'. @@ -185,142 +208,163 @@ with lib; }; }; - config = let - cfg = config.proxmox; - cfgLine = name: value: '' - ${name}: ${builtins.toString value} - ''; - virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); - cfgFile = fileName: properties: pkgs.writeTextDir fileName '' - # generated by NixOS - ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} - #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: - ''; - inherit (cfg) partitionTableType; - supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; - supportBios = partitionTableType == "legacy" || partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; - hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; - hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; - in { - assertions = [ - { - assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; - message = "systemd-boot requires 'ovmf' bios"; - } - { - assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; - message = "'efi' disk partitioning requires 'ovmf' bios"; - } - { - assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; - message = "'legacy' disk partitioning requires 'seabios' bios"; - } - { - assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; - message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; - } - ]; - system.build.VMA = import ../../lib/make-disk-image.nix { - name = "proxmox-${cfg.filenameSuffix}"; + config = + let + cfg = config.proxmox; + cfgLine = name: value: '' + ${name}: ${builtins.toString value} + ''; + virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); + cfgFile = + fileName: properties: + pkgs.writeTextDir fileName '' + # generated by NixOS + ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} + #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: + ''; inherit (cfg) partitionTableType; - postVM = let - # Build qemu with PVE's patch that adds support for the VMA format - vma = (pkgs.qemu_kvm.override { - alsaSupport = false; - pulseSupport = false; - sdlSupport = false; - jackSupport = false; - gtkSupport = false; - vncSupport = false; - smartcardSupport = false; - spiceSupport = false; - ncursesSupport = false; - libiscsiSupport = false; - tpmSupport = false; - numaSupport = false; - seccompSupport = false; - guestAgentSupport = false; - }).overrideAttrs ( super: rec { - # Check https://github.com/proxmox/pve-qemu/tree/master for the version - # of qemu and patch to use - version = "9.0.0"; - src = pkgs.fetchurl { - url = "https://download.qemu.org/qemu-${version}.tar.xz"; - hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; - }; - patches = [ - # Proxmox' VMA tool is published as a particular patch upon QEMU - "${pkgs.fetchFromGitHub { - owner = "proxmox"; - repo = "pve-qemu"; - rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; - hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; - }}/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" - ]; - - buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; - nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; + supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; + supportBios = + partitionTableType == "legacy" + || partitionTableType == "hybrid" + || partitionTableType == "legacy+gpt"; + hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; + hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; + in + { + assertions = [ + { + assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; + message = "systemd-boot requires 'ovmf' bios"; + } + { + assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; + message = "'efi' disk partitioning requires 'ovmf' bios"; + } + { + assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; + message = "'legacy' disk partitioning requires 'seabios' bios"; + } + { + assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; + message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; + } + ]; + system.build.VMA = import ../../lib/make-disk-image.nix { + name = "proxmox-${cfg.filenameSuffix}"; + inherit (cfg) partitionTableType; + postVM = + let + # Build qemu with PVE's patch that adds support for the VMA format + vma = + (pkgs.qemu_kvm.override { + alsaSupport = false; + pulseSupport = false; + sdlSupport = false; + jackSupport = false; + gtkSupport = false; + vncSupport = false; + smartcardSupport = false; + spiceSupport = false; + ncursesSupport = false; + libiscsiSupport = false; + tpmSupport = false; + numaSupport = false; + seccompSupport = false; + guestAgentSupport = false; + }).overrideAttrs + (super: rec { + # Check https://github.com/proxmox/pve-qemu/tree/master for the version + # of qemu and patch to use + version = "9.0.0"; + src = pkgs.fetchurl { + url = "https://download.qemu.org/qemu-${version}.tar.xz"; + hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; + }; + patches = [ + # Proxmox' VMA tool is published as a particular patch upon QEMU + "${ + pkgs.fetchFromGitHub { + owner = "proxmox"; + repo = "pve-qemu"; + rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; + hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; + } + }/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" + ]; - }); - in - '' - ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ - -c ${cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)}/qemu-server.conf drive-virtio0=$diskImage - rm $diskImage - ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" - mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ + buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; + nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; - mkdir -p $out/nix-support - echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products - ''; - inherit (cfg.qemuConf) additionalSpace diskSize bootSize; - format = "raw"; - inherit config lib pkgs; - }; + }); + in + '' + ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ + -c ${ + cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf) + }/qemu-server.conf drive-virtio0=$diskImage + rm $diskImage + ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" + mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ - boot = { - growPartition = true; - kernelParams = [ "console=ttyS0" ]; - loader.grub = { - device = lib.mkDefault (if (hasNoFsPartition || supportBios) then - # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), - # which will be used the bootloader, do not set it as loader.grub.device. - # GRUB installation fails, unless the whole disk is selected. - "/dev/vda" - else - "nodev"); - efiSupport = lib.mkDefault supportEfi; - efiInstallAsRemovable = lib.mkDefault supportEfi; + mkdir -p $out/nix-support + echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products + ''; + inherit (cfg.qemuConf) additionalSpace bootSize; + inherit (config.virtualisation) diskSize; + format = "raw"; + inherit config lib pkgs; }; - loader.timeout = 0; - initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ]; - }; + boot = { + growPartition = true; + kernelParams = [ "console=ttyS0" ]; + loader.grub = { + device = lib.mkDefault ( + if (hasNoFsPartition || supportBios) then + # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), + # which will be used the bootloader, do not set it as loader.grub.device. + # GRUB installation fails, unless the whole disk is selected. + "/dev/vda" + else + "nodev" + ); + efiSupport = lib.mkDefault supportEfi; + efiInstallAsRemovable = lib.mkDefault supportEfi; + }; - fileSystems."/" = { - device = "/dev/disk/by-label/nixos"; - autoResize = true; - fsType = "ext4"; - }; - fileSystems."/boot" = lib.mkIf hasBootPartition { - device = "/dev/disk/by-label/ESP"; - fsType = "vfat"; - }; + loader.timeout = 0; + initrd.availableKernelModules = [ + "uas" + "virtio_blk" + "virtio_pci" + ]; + }; - networking = mkIf cfg.cloudInit.enable { - hostName = mkForce ""; - useDHCP = false; - }; + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + fileSystems."/boot" = lib.mkIf hasBootPartition { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; - services = { - cloud-init = mkIf cfg.cloudInit.enable { - enable = true; - network.enable = true; + networking = mkIf cfg.cloudInit.enable { + hostName = mkForce ""; + useDHCP = false; }; - sshd.enable = mkDefault true; - qemuGuest.enable = true; - }; - proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; - }; + services = { + cloud-init = mkIf cfg.cloudInit.enable { + enable = true; + network.enable = true; + }; + sshd.enable = mkDefault true; + qemuGuest.enable = true; + }; + + proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; + }; } diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index b48f9e64c235..af2f13b003a4 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -4,7 +4,13 @@ # `config'. By default, the Nix store is shared read-only with the # host, which makes (re)building VMs very efficient. -{ config, lib, pkgs, options, ... }: +{ + config, + lib, + pkgs, + options, + ... +}: with lib; @@ -22,235 +28,263 @@ let consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles; - driveOpts = { ... }: { + driveOpts = + { ... }: + { - options = { + options = { - file = mkOption { - type = types.str; - description = "The file image used for this drive."; - }; + file = mkOption { + type = types.str; + description = "The file image used for this drive."; + }; - driveExtraOpts = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Extra options passed to drive flag."; - }; + driveExtraOpts = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Extra options passed to drive flag."; + }; - deviceExtraOpts = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Extra options passed to device flag."; - }; + deviceExtraOpts = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Extra options passed to device flag."; + }; + + name = mkOption { + type = types.nullOr types.str; + default = null; + description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; + }; - name = mkOption { - type = types.nullOr types.str; - default = null; - description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; }; }; - }; - - selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }: - if useDefaultFilesystems then - if useEFIBoot then "efi" else "legacy" - else "none"; - - driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }: + selectPartitionTableLayout = + { useEFIBoot, useDefaultFilesystems }: + if useDefaultFilesystems then if useEFIBoot then "efi" else "legacy" else "none"; + + driveCmdline = + idx: + { + file, + driveExtraOpts, + deviceExtraOpts, + ... + }: let drvId = "drive${toString idx}"; - mkKeyValue = generators.mkKeyValueDefault {} "="; + mkKeyValue = generators.mkKeyValueDefault { } "="; mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts); - driveOpts = mkOpts (driveExtraOpts // { - index = idx; - id = drvId; - "if" = "none"; - inherit file; - }); - deviceOpts = mkOpts (deviceExtraOpts // { - drive = drvId; - }); + driveOpts = mkOpts ( + driveExtraOpts + // { + index = idx; + id = drvId; + "if" = "none"; + inherit file; + } + ); + deviceOpts = mkOpts ( + deviceExtraOpts + // { + drive = drvId; + } + ); device = if cfg.qemu.diskInterface == "scsi" then "-device lsi53c895a -device scsi-hd,${deviceOpts}" else "-device virtio-blk-pci,${deviceOpts}"; in - "-drive ${driveOpts} ${device}"; + "-drive ${driveOpts} ${device}"; drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives); # Shell script to start the VM. - startVM = - '' - #! ${hostPkgs.runtimeShell} - - export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH - - set -e - - # Create an empty ext4 filesystem image. A filesystem image does not - # contain a partition table but just a filesystem. - createEmptyFilesystemImage() { - local name=$1 - local size=$2 - local temp=$(mktemp) - ${qemu}/bin/qemu-img create -f raw "$temp" "$size" - ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" - ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" - rm "$temp" - } + startVM = '' + #! ${hostPkgs.runtimeShell} + + export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH + + set -e + + # Create an empty ext4 filesystem image. A filesystem image does not + # contain a partition table but just a filesystem. + createEmptyFilesystemImage() { + local name=$1 + local size=$2 + local temp=$(mktemp) + ${qemu}/bin/qemu-img create -f raw "$temp" "$size" + ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" + ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" + rm "$temp" + } - NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" - - if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then - echo "Disk image do not exist, creating the virtualisation disk image..." - - ${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then '' - # Create a writable qcow2 image using the systemImage as a backing - # image. - - # CoW prevent size to be attributed to an image. - # FIXME: raise this issue to upstream. - ${qemu}/bin/qemu-img create \ - -f qcow2 \ - -b ${systemImage}/nixos.qcow2 \ - -F qcow2 \ - "$NIX_DISK_IMAGE" - '' else if cfg.useDefaultFilesystems then '' - createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" - '' else '' - # Create an empty disk image without a filesystem. - ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" - '' + NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" + + if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then + echo "Disk image do not exist, creating the virtualisation disk image..." + + ${ + if (cfg.useBootLoader && cfg.useDefaultFilesystems) then + '' + # Create a writable qcow2 image using the systemImage as a backing + # image. + + # CoW prevent size to be attributed to an image. + # FIXME: raise this issue to upstream. + ${qemu}/bin/qemu-img create \ + -f qcow2 \ + -b ${systemImage}/nixos.qcow2 \ + -F qcow2 \ + "$NIX_DISK_IMAGE" + '' + else if cfg.useDefaultFilesystems then + '' + createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" + '' + else + '' + # Create an empty disk image without a filesystem. + ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" + '' + } + echo "Virtualisation disk image created." + fi + + # Create a directory for storing temporary data of the running VM. + if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then + TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) + fi + + ${lib.optionalString (cfg.useNixStoreImage) '' + echo "Creating Nix store image..." + + ${hostPkgs.gnutar}/bin/tar --create \ + --absolute-names \ + --verbatim-files-from \ + --transform 'flags=rSh;s|/nix/store/||' \ + --transform 'flags=rSh;s|~nix~case~hack~[[:digit:]]\+||g' \ + --files-from ${ + hostPkgs.closureInfo { + rootPaths = [ + config.system.build.toplevel + regInfo + ]; } - echo "Virtualisation disk image created." + }/store-paths \ + | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ + --quiet \ + --force-uid=0 \ + --force-gid=0 \ + -L ${nixStoreFilesystemLabel} \ + -U eb176051-bd15-49b7-9e6b-462e0b467019 \ + -T 0 \ + --tar=f \ + "$TMPDIR"/store.img + + echo "Created Nix store image." + ''} + + # Create a directory for exchanging data with the VM. + mkdir -p "$TMPDIR/xchg" + + ${lib.optionalString cfg.useHostCerts '' + mkdir -p "$TMPDIR/certs" + if [ -e "$NIX_SSL_CERT_FILE" ]; then + cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt + else + echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. fi - - # Create a directory for storing temporary data of the running VM. - if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then - TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) + ''} + + ${lib.optionalString cfg.useEFIBoot '' + # Expose EFI variables, it's useful even when we are not using a bootloader (!). + # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence + # no guard against `useBootLoader`. Examples: + # - testing PXE boot or other EFI applications + # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows + NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") + # VM needs writable EFI vars + if ! test -e "$NIX_EFI_VARS"; then + ${ + if cfg.efi.keepVariables then + # We still need the EFI var from the make-disk-image derivation + # because our "switch-to-configuration" process might + # write into it and we want to keep this data. + ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' + else + ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' + } + chmod 0644 "$NIX_EFI_VARS" fi - - ${lib.optionalString (cfg.useNixStoreImage) '' - echo "Creating Nix store image..." - - ${hostPkgs.gnutar}/bin/tar --create \ - --absolute-names \ - --verbatim-files-from \ - --transform 'flags=rSh;s|/nix/store/||' \ - --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \ - | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ - --quiet \ - --force-uid=0 \ - --force-gid=0 \ - -L ${nixStoreFilesystemLabel} \ - -U eb176051-bd15-49b7-9e6b-462e0b467019 \ - -T 0 \ - --tar=f \ - "$TMPDIR"/store.img - - echo "Created Nix store image." - '' + ''} + + ${lib.optionalString cfg.tpm.enable '' + NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") + mkdir -p "$NIX_SWTPM_DIR" + ${lib.getExe cfg.tpm.package} \ + socket \ + --tpmstate dir="$NIX_SWTPM_DIR" \ + --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ + --pid file="$NIX_SWTPM_DIR"/pid --daemon \ + --tpm2 \ + --log file="$NIX_SWTPM_DIR"/stdout,level=6 + + # Enable `fdflags` builtin in Bash + # We will need it to perform surgical modification of the file descriptor + # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor + # on exec. + # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` + # at the end of this script. To work around that, we will just clear + # the `FD_CLOEXEC` bits as a first step. + enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags + # leave a dangling subprocess because the swtpm ctrl socket has + # "terminate" when the last connection disconnects, it stops swtpm. + # When qemu stops, or if the main shell process ends, the coproc will + # get signaled by virtue of the pipe between main and coproc ending. + # Which in turns triggers a socat connect-disconnect to swtpm which + # will stop it. + coproc waitingswtpm { + read || : + echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket } + # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. + fdflags -s-cloexec ''${waitingswtpm[1]} + ''} - # Create a directory for exchanging data with the VM. - mkdir -p "$TMPDIR/xchg" - - ${lib.optionalString cfg.useHostCerts - '' - mkdir -p "$TMPDIR/certs" - if [ -e "$NIX_SSL_CERT_FILE" ]; then - cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt - else - echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. - fi - ''} - - ${lib.optionalString cfg.useEFIBoot - '' - # Expose EFI variables, it's useful even when we are not using a bootloader (!). - # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence - # no guard against `useBootLoader`. Examples: - # - testing PXE boot or other EFI applications - # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows - NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") - # VM needs writable EFI vars - if ! test -e "$NIX_EFI_VARS"; then - ${if cfg.efi.keepVariables then - # We still need the EFI var from the make-disk-image derivation - # because our "switch-to-configuration" process might - # write into it and we want to keep this data. - ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' - else - ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' - } - chmod 0644 "$NIX_EFI_VARS" - fi - ''} - - ${lib.optionalString cfg.tpm.enable '' - NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") - mkdir -p "$NIX_SWTPM_DIR" - ${lib.getExe cfg.tpm.package} \ - socket \ - --tpmstate dir="$NIX_SWTPM_DIR" \ - --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ - --pid file="$NIX_SWTPM_DIR"/pid --daemon \ - --tpm2 \ - --log file="$NIX_SWTPM_DIR"/stdout,level=6 - - # Enable `fdflags` builtin in Bash - # We will need it to perform surgical modification of the file descriptor - # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor - # on exec. - # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` - # at the end of this script. To work around that, we will just clear - # the `FD_CLOEXEC` bits as a first step. - enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags - # leave a dangling subprocess because the swtpm ctrl socket has - # "terminate" when the last connection disconnects, it stops swtpm. - # When qemu stops, or if the main shell process ends, the coproc will - # get signaled by virtue of the pipe between main and coproc ending. - # Which in turns triggers a socat connect-disconnect to swtpm which - # will stop it. - coproc waitingswtpm { - read || : - echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket - } - # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. - fdflags -s-cloexec ''${waitingswtpm[1]} - ''} - - cd "$TMPDIR" - - ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"} - ${flip concatMapStrings cfg.emptyDiskImages (size: '' - if ! test -e "empty$idx.qcow2"; then - ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" - fi - idx=$((idx + 1)) - '')} - - # Start QEMU. - exec ${qemu-common.qemuBinary qemu} \ - -name ${config.system.name} \ - -m ${toString config.virtualisation.memorySize} \ - -smp ${toString config.virtualisation.cores} \ - -device virtio-rng-pci \ - ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ - ${concatStringsSep " \\\n " - (mapAttrsToList - (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}") - config.virtualisation.sharedDirectories)} \ - ${drivesCmdLine config.virtualisation.qemu.drives} \ - ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ - $QEMU_OPTS \ - "$@" - ''; + cd "$TMPDIR" + ${lib.optionalString (cfg.emptyDiskImages != [ ]) "idx=0"} + ${flip concatMapStrings cfg.emptyDiskImages (size: '' + if ! test -e "empty$idx.qcow2"; then + ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" + fi + idx=$((idx + 1)) + '')} + + # Start QEMU. + exec ${qemu-common.qemuBinary qemu} \ + -name ${config.system.name} \ + -m ${toString config.virtualisation.memorySize} \ + -smp ${toString config.virtualisation.cores} \ + -device virtio-rng-pci \ + ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ + ${ + concatStringsSep " \\\n " ( + mapAttrsToList ( + tag: share: + "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}" + ) config.virtualisation.sharedDirectories + ) + } \ + ${drivesCmdLine config.virtualisation.qemu.drives} \ + ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ + $QEMU_OPTS \ + "$@" + ''; regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; }; @@ -283,216 +317,239 @@ let copyChannel = false; OVMF = cfg.efi.OVMF; }; - in - { imports = [ ../profiles/qemu-guest.nix - (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ]) - (mkRemovedOptionModule [ "virtualisation" "bootDevice" ] "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues.") - (mkRemovedOptionModule [ "virtualisation" "efiVars" ] "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue") - (mkRemovedOptionModule [ "virtualisation" "persistBootDevice" ] "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`") + ./disk-size-option.nix + (mkRenamedOptionModule + [ + "virtualisation" + "pathsInNixDB" + ] + [ + "virtualisation" + "additionalPaths" + ] + ) + (mkRemovedOptionModule + [ + "virtualisation" + "bootDevice" + ] + "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues." + ) + (mkRemovedOptionModule + [ + "virtualisation" + "efiVars" + ] + "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue" + ) + (mkRemovedOptionModule + [ + "virtualisation" + "persistBootDevice" + ] + "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`" + ) ]; options = { virtualisation.fileSystems = options.fileSystems; - virtualisation.memorySize = - mkOption { - type = types.ints.positive; - default = 1024; - description = '' - The memory size in megabytes of the virtual machine. - ''; - }; - - virtualisation.msize = - mkOption { - type = types.ints.positive; - default = 16384; - description = '' - The msize (maximum packet size) option passed to 9p file systems, in - bytes. Increasing this should increase performance significantly, - at the cost of higher RAM usage. - ''; - }; + virtualisation.memorySize = mkOption { + type = types.ints.positive; + default = 1024; + description = '' + The memory size in megabytes of the virtual machine. + ''; + }; - virtualisation.diskSize = - mkOption { - type = types.ints.positive; - default = 1024; - description = '' - The disk size in megabytes of the virtual machine. - ''; - }; + virtualisation.msize = mkOption { + type = types.ints.positive; + default = 16384; + description = '' + The msize (maximum packet size) option passed to 9p file systems, in + bytes. Increasing this should increase performance significantly, + at the cost of higher RAM usage. + ''; + }; - virtualisation.diskImage = - mkOption { - type = types.nullOr types.str; - default = "./${config.system.name}.qcow2"; - defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; - description = '' - Path to the disk image containing the root filesystem. - The image will be created on startup if it does not - exist. + virtualisation.diskImage = mkOption { + type = types.nullOr types.str; + default = "./${config.system.name}.qcow2"; + defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; + description = '' + Path to the disk image containing the root filesystem. + The image will be created on startup if it does not + exist. - If null, a tmpfs will be used as the root filesystem and - the VM's state will not be persistent. - ''; - }; + If null, a tmpfs will be used as the root filesystem and + the VM's state will not be persistent. + ''; + }; - virtualisation.bootLoaderDevice = - mkOption { - type = types.path; - default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; - defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; - example = "/dev/disk/by-id/virtio-boot-loader-device"; - description = '' - The path (inside th VM) to the device to boot from when legacy booting. - ''; - }; + virtualisation.bootLoaderDevice = mkOption { + type = types.path; + default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; + defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; + example = "/dev/disk/by-id/virtio-boot-loader-device"; + description = '' + The path (inside th VM) to the device to boot from when legacy booting. + ''; + }; - virtualisation.bootPartition = - mkOption { - type = types.nullOr types.path; - default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; - defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; - example = "/dev/disk/by-label/esp"; - description = '' - The path (inside the VM) to the device containing the EFI System Partition (ESP). + virtualisation.bootPartition = mkOption { + type = types.nullOr types.path; + default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; + defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; + example = "/dev/disk/by-label/esp"; + description = '' + The path (inside the VM) to the device containing the EFI System Partition (ESP). - If you are *not* booting from a UEFI firmware, this value is, by - default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. - ''; - }; + If you are *not* booting from a UEFI firmware, this value is, by + default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. + ''; + }; - virtualisation.rootDevice = - mkOption { - type = types.nullOr types.path; - default = "/dev/disk/by-label/${rootFilesystemLabel}"; - defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; - example = "/dev/disk/by-label/nixos"; - description = '' - The path (inside the VM) to the device containing the root filesystem. - ''; - }; + virtualisation.rootDevice = mkOption { + type = types.nullOr types.path; + default = "/dev/disk/by-label/${rootFilesystemLabel}"; + defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; + example = "/dev/disk/by-label/nixos"; + description = '' + The path (inside the VM) to the device containing the root filesystem. + ''; + }; - virtualisation.emptyDiskImages = - mkOption { - type = types.listOf types.ints.positive; - default = []; - description = '' - Additional disk images to provide to the VM. The value is - a list of size in megabytes of each disk. These disks are - writeable by the VM. - ''; - }; + virtualisation.emptyDiskImages = mkOption { + type = types.listOf types.ints.positive; + default = [ ]; + description = '' + Additional disk images to provide to the VM. The value is + a list of size in megabytes of each disk. These disks are + writeable by the VM. + ''; + }; - virtualisation.graphics = - mkOption { - type = types.bool; - default = true; - description = '' - Whether to run QEMU with a graphics window, or in nographic mode. - Serial console will be enabled on both settings, but this will - change the preferred console. - ''; - }; + virtualisation.graphics = mkOption { + type = types.bool; + default = true; + description = '' + Whether to run QEMU with a graphics window, or in nographic mode. + Serial console will be enabled on both settings, but this will + change the preferred console. + ''; + }; - virtualisation.resolution = - mkOption { - type = options.services.xserver.resolutions.type.nestedTypes.elemType; - default = { x = 1024; y = 768; }; - description = '' - The resolution of the virtual machine display. - ''; + virtualisation.resolution = mkOption { + type = options.services.xserver.resolutions.type.nestedTypes.elemType; + default = { + x = 1024; + y = 768; }; + description = '' + The resolution of the virtual machine display. + ''; + }; - virtualisation.cores = - mkOption { - type = types.ints.positive; - default = 1; - description = '' - Specify the number of cores the guest is permitted to use. - The number can be higher than the available cores on the - host system. - ''; - }; + virtualisation.cores = mkOption { + type = types.ints.positive; + default = 1; + description = '' + Specify the number of cores the guest is permitted to use. + The number can be higher than the available cores on the + host system. + ''; + }; - virtualisation.sharedDirectories = - mkOption { - type = types.attrsOf - (types.submodule { - options.source = mkOption { - type = types.str; - description = "The path of the directory to share, can be a shell variable"; - }; - options.target = mkOption { - type = types.path; - description = "The mount point of the directory inside the virtual machine"; - }; - options.securityModel = mkOption { - type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ]; - default = "mapped-xattr"; - description = '' - The security model to use for this share: + virtualisation.sharedDirectories = mkOption { + type = types.attrsOf ( + types.submodule { + options.source = mkOption { + type = types.str; + description = "The path of the directory to share, can be a shell variable"; + }; + options.target = mkOption { + type = types.path; + description = "The mount point of the directory inside the virtual machine"; + }; + options.securityModel = mkOption { + type = types.enum [ + "passthrough" + "mapped-xattr" + "mapped-file" + "none" + ]; + default = "mapped-xattr"; + description = '' + The security model to use for this share: - - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) - - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes - - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools - - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership - ''; - }; - }); - default = { }; - example = { - my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; + - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) + - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes + - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools + - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership + ''; + }; + } + ); + default = { }; + example = { + my-share = { + source = "/path/to/be/shared"; + target = "/mnt/shared"; }; - description = '' - An attributes set of directories that will be shared with the - virtual machine using VirtFS (9P filesystem over VirtIO). - The attribute name will be used as the 9P mount tag. - ''; }; + description = '' + An attributes set of directories that will be shared with the + virtual machine using VirtFS (9P filesystem over VirtIO). + The attribute name will be used as the 9P mount tag. + ''; + }; - virtualisation.additionalPaths = - mkOption { - type = types.listOf types.path; - default = []; - description = '' - A list of paths whose closure should be made available to - the VM. + virtualisation.additionalPaths = mkOption { + type = types.listOf types.path; + default = [ ]; + description = '' + A list of paths whose closure should be made available to + the VM. - When 9p is used, the closure is registered in the Nix - database in the VM. All other paths in the host Nix store - appear in the guest Nix store as well, but are considered - garbage (because they are not registered in the Nix - database of the guest). + When 9p is used, the closure is registered in the Nix + database in the VM. All other paths in the host Nix store + appear in the guest Nix store as well, but are considered + garbage (because they are not registered in the Nix + database of the guest). - When {option}`virtualisation.useNixStoreImage` is - set, the closure is copied to the Nix store image. - ''; - }; + When {option}`virtualisation.useNixStoreImage` is + set, the closure is copied to the Nix store image. + ''; + }; virtualisation.forwardPorts = mkOption { - type = types.listOf - (types.submodule { + type = types.listOf ( + types.submodule { options.from = mkOption { - type = types.enum [ "host" "guest" ]; + type = types.enum [ + "host" + "guest" + ]; default = "host"; description = '' - Controls the direction in which the ports are mapped: + Controls the direction in which the ports are mapped: - - `"host"` means traffic from the host ports - is forwarded to the given guest port. - - `"guest"` means traffic from the guest ports - is forwarded to the given host port. - ''; + - `"host"` means traffic from the host ports + is forwarded to the given guest port. + - `"guest"` means traffic from the guest ports + is forwarded to the given host port. + ''; }; options.proto = mkOption { - type = types.enum [ "tcp" "udp" ]; + type = types.enum [ + "tcp" + "udp" + ]; default = "tcp"; description = "The protocol to forward."; }; @@ -514,10 +571,10 @@ in type = types.port; description = "The guest port to be mapped."; }; - }); - default = []; - example = lib.literalExpression - '' + } + ); + default = [ ]; + example = lib.literalExpression '' [ # forward local port 2222 -> 22, to ssh into the VM { from = "host"; host.port = 2222; guest.port = 22; } @@ -527,122 +584,121 @@ in host.address = "127.0.0.1"; host.port = 80; } ] - ''; + ''; description = '' - When using the SLiRP user networking (default), this option allows to - forward ports to/from the host/guest. - - ::: {.warning} - If the NixOS firewall on the virtual machine is enabled, you also - have to open the guest ports to enable the traffic between host and - guest. - ::: - - ::: {.note} - Currently QEMU supports only IPv4 forwarding. - ::: - ''; + When using the SLiRP user networking (default), this option allows to + forward ports to/from the host/guest. + + ::: {.warning} + If the NixOS firewall on the virtual machine is enabled, you also + have to open the guest ports to enable the traffic between host and + guest. + ::: + + ::: {.note} + Currently QEMU supports only IPv4 forwarding. + ::: + ''; }; - virtualisation.restrictNetwork = - mkOption { - type = types.bool; - default = false; - example = true; - description = '' - If this option is enabled, the guest will be isolated, i.e. it will - not be able to contact the host and no guest IP packets will be - routed over the host to the outside. This option does not affect - any explicitly set forwarding rules. - ''; - }; + virtualisation.restrictNetwork = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + If this option is enabled, the guest will be isolated, i.e. it will + not be able to contact the host and no guest IP packets will be + routed over the host to the outside. This option does not affect + any explicitly set forwarding rules. + ''; + }; - virtualisation.vlans = - mkOption { - type = types.listOf types.ints.unsigned; - default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ]; - defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; - example = [ 1 2 ]; - description = '' - Virtual networks to which the VM is connected. Each - number «N» in this list causes - the VM to have a virtual Ethernet interface attached to a - separate virtual network on which it will be assigned IP - address - `192.168.«N».«M»`, - where «M» is the index of this VM - in the list of VMs. - ''; - }; + virtualisation.vlans = mkOption { + type = types.listOf types.ints.unsigned; + default = if config.virtualisation.interfaces == { } then [ 1 ] else [ ]; + defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; + example = [ + 1 + 2 + ]; + description = '' + Virtual networks to which the VM is connected. Each + number «N» in this list causes + the VM to have a virtual Ethernet interface attached to a + separate virtual network on which it will be assigned IP + address + `192.168.«N».«M»`, + where «M» is the index of this VM + in the list of VMs. + ''; + }; virtualisation.interfaces = mkOption { - default = {}; + default = { }; example = { enp1s0.vlan = 1; }; description = '' Network interfaces to add to the VM. ''; - type = with types; attrsOf (submodule { - options = { - vlan = mkOption { - type = types.ints.unsigned; - description = '' - VLAN to which the network interface is connected. - ''; - }; + type = + with types; + attrsOf (submodule { + options = { + vlan = mkOption { + type = types.ints.unsigned; + description = '' + VLAN to which the network interface is connected. + ''; + }; - assignIP = mkOption { - type = types.bool; - default = false; - description = '' - Automatically assign an IP address to the network interface using the same scheme as - virtualisation.vlans. - ''; + assignIP = mkOption { + type = types.bool; + default = false; + description = '' + Automatically assign an IP address to the network interface using the same scheme as + virtualisation.vlans. + ''; + }; }; - }; - }); + }); }; - virtualisation.writableStore = - mkOption { - type = types.bool; - default = cfg.mountHostNixStore; - defaultText = literalExpression "cfg.mountHostNixStore"; - description = '' - If enabled, the Nix store in the VM is made writable by - layering an overlay filesystem on top of the host's Nix - store. + virtualisation.writableStore = mkOption { + type = types.bool; + default = cfg.mountHostNixStore; + defaultText = literalExpression "cfg.mountHostNixStore"; + description = '' + If enabled, the Nix store in the VM is made writable by + layering an overlay filesystem on top of the host's Nix + store. - By default, this is enabled if you mount a host Nix store. - ''; - }; + By default, this is enabled if you mount a host Nix store. + ''; + }; - virtualisation.writableStoreUseTmpfs = - mkOption { - type = types.bool; - default = true; - description = '' - Use a tmpfs for the writable store instead of writing to the VM's - own filesystem. - ''; - }; + virtualisation.writableStoreUseTmpfs = mkOption { + type = types.bool; + default = true; + description = '' + Use a tmpfs for the writable store instead of writing to the VM's + own filesystem. + ''; + }; - networking.primaryIPAddress = - mkOption { - type = types.str; - default = ""; - internal = true; - description = "Primary IP address used in /etc/hosts."; - }; + networking.primaryIPAddress = mkOption { + type = types.str; + default = ""; + internal = true; + description = "Primary IP address used in /etc/hosts."; + }; - networking.primaryIPv6Address = - mkOption { - type = types.str; - default = ""; - internal = true; - description = "Primary IPv6 address used in /etc/hosts."; - }; + networking.primaryIPv6Address = mkOption { + type = types.str; + default = ""; + internal = true; + description = "Primary IPv6 address used in /etc/hosts."; + }; virtualisation.host.pkgs = mkOption { type = options.nixpkgs.pkgs.type; @@ -658,31 +714,38 @@ in }; virtualisation.qemu = { - package = - mkOption { - type = types.package; - default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu; - defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; - example = literalExpression "pkgs.qemu_test"; - description = "QEMU package to use."; - }; + package = mkOption { + type = types.package; + default = + if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then + hostPkgs.qemu_kvm + else + hostPkgs.qemu; + defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; + example = literalExpression "pkgs.qemu_test"; + description = "QEMU package to use."; + }; - options = - mkOption { - type = types.listOf types.str; - default = []; - example = [ "-vga std" ]; - description = '' - Options passed to QEMU. - See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. - ''; - }; + options = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "-vga std" ]; + description = '' + Options passed to QEMU. + See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. + ''; + }; consoles = mkOption { type = types.listOf types.str; - default = let - consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ]; - in if cfg.graphics then consoles else reverseList consoles; + default = + let + consoles = [ + "${qemu-common.qemuSerialDevice},115200n8" + "tty0" + ]; + in + if cfg.graphics then consoles else reverseList consoles; example = [ "console=tty1" ]; description = '' The output console devices to pass to the kernel command line via the @@ -695,176 +758,170 @@ in ''; }; - networkingOptions = - mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ - "-net nic,netdev=user.0,model=virtio" - "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" - ]; - description = '' - Networking-related command-line options that should be passed to qemu. - The default is to use userspace networking (SLiRP). - See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. - - If you override this option, be advised to keep - `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) - to keep the default runtime behaviour. - ''; - }; - - drives = - mkOption { - type = types.listOf (types.submodule driveOpts); - description = "Drives passed to qemu."; - }; + networkingOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "-net nic,netdev=user.0,model=virtio" + "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" + ]; + description = '' + Networking-related command-line options that should be passed to qemu. + The default is to use userspace networking (SLiRP). + See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. - diskInterface = - mkOption { - type = types.enum [ "virtio" "scsi" "ide" ]; - default = "virtio"; - example = "scsi"; - description = "The interface used for the virtual hard disks."; - }; + If you override this option, be advised to keep + `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) + to keep the default runtime behaviour. + ''; + }; - guestAgent.enable = - mkOption { - type = types.bool; - default = true; - description = '' - Enable the Qemu guest agent. - ''; - }; + drives = mkOption { + type = types.listOf (types.submodule driveOpts); + description = "Drives passed to qemu."; + }; - virtioKeyboard = - mkOption { - type = types.bool; - default = true; - description = '' - Enable the virtio-keyboard device. - ''; - }; - }; + diskInterface = mkOption { + type = types.enum [ + "virtio" + "scsi" + "ide" + ]; + default = "virtio"; + example = "scsi"; + description = "The interface used for the virtual hard disks."; + }; - virtualisation.useNixStoreImage = - mkOption { + guestAgent.enable = mkOption { type = types.bool; - default = false; + default = true; description = '' - Build and use a disk image for the Nix store, instead of - accessing the host's one through 9p. - - For applications which do a lot of reads from the store, - this can drastically improve performance, but at the cost of - disk space and image build time. - - The Nix store image is built just-in-time right before the VM is - started. Because it does not produce another derivation, the image is - not cached between invocations and never lands in the store or binary - cache. - - If you want a full disk image with a partition table and a root - filesystem instead of only a store image, enable - {option}`virtualisation.useBootLoader` instead. + Enable the Qemu guest agent. ''; }; - virtualisation.mountHostNixStore = - mkOption { + virtioKeyboard = mkOption { type = types.bool; - default = !cfg.useNixStoreImage && !cfg.useBootLoader; - defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; + default = true; description = '' - Mount the host Nix store as a 9p mount. + Enable the virtio-keyboard device. ''; }; + }; - virtualisation.directBoot = { - enable = - mkOption { - type = types.bool; - default = !cfg.useBootLoader; - defaultText = "!cfg.useBootLoader"; - description = '' - If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. - Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) - - This is enabled by default. - If you want to test netboot, consider disabling this option. - Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. - - Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. - Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. - They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details - For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. - - This will not (re-)boot correctly into a system that has switched to a different configuration on disk. - ''; - }; - initrd = - mkOption { - type = types.str; - default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; - defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; - description = '' - In direct boot situations, you may want to influence the initrd to load - to use your own customized payload. - - This is useful if you want to test the netboot image without - testing the firmware or the loading part. - ''; - }; + virtualisation.useNixStoreImage = mkOption { + type = types.bool; + default = false; + description = '' + Build and use a disk image for the Nix store, instead of + accessing the host's one through 9p. + + For applications which do a lot of reads from the store, + this can drastically improve performance, but at the cost of + disk space and image build time. + + The Nix store image is built just-in-time right before the VM is + started. Because it does not produce another derivation, the image is + not cached between invocations and never lands in the store or binary + cache. + + If you want a full disk image with a partition table and a root + filesystem instead of only a store image, enable + {option}`virtualisation.useBootLoader` instead. + ''; + }; + + virtualisation.mountHostNixStore = mkOption { + type = types.bool; + default = !cfg.useNixStoreImage && !cfg.useBootLoader; + defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; + description = '' + Mount the host Nix store as a 9p mount. + ''; }; - virtualisation.useBootLoader = - mkOption { + virtualisation.directBoot = { + enable = mkOption { type = types.bool; - default = false; + default = !cfg.useBootLoader; + defaultText = "!cfg.useBootLoader"; description = '' - Use a boot loader to boot the system. - This allows, among other things, testing the boot loader. + If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. + Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) - If disabled, the kernel and initrd are directly booted, - forgoing any bootloader. + This is enabled by default. + If you want to test netboot, consider disabling this option. + Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. - Check the documentation on {option}`virtualisation.directBoot.enable` for details. - ''; - }; + Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. + Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. + They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details + For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. - virtualisation.installBootLoader = - mkOption { - type = types.bool; - default = cfg.useBootLoader && cfg.useDefaultFilesystems; - defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems"; + This will not (re-)boot correctly into a system that has switched to a different configuration on disk. + ''; + }; + initrd = mkOption { + type = types.str; + default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; + defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; description = '' - Install boot loader to target image. + In direct boot situations, you may want to influence the initrd to load + to use your own customized payload. - This is best-effort and may break with unconventional partition setups. - Use `virtualisation.useDefaultFilesystems` for a known-working configuration. + This is useful if you want to test the netboot image without + testing the firmware or the loading part. ''; }; + }; - virtualisation.useEFIBoot = - mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the virtual machine will provide a EFI boot - manager. - useEFIBoot is ignored if useBootLoader == false. - ''; - }; + virtualisation.useBootLoader = mkOption { + type = types.bool; + default = false; + description = '' + Use a boot loader to boot the system. + This allows, among other things, testing the boot loader. + + If disabled, the kernel and initrd are directly booted, + forgoing any bootloader. + + Check the documentation on {option}`virtualisation.directBoot.enable` for details. + ''; + }; + + virtualisation.installBootLoader = mkOption { + type = types.bool; + default = cfg.useBootLoader && cfg.useDefaultFilesystems; + defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems"; + description = '' + Install boot loader to target image. + + This is best-effort and may break with unconventional partition setups. + Use `virtualisation.useDefaultFilesystems` for a known-working configuration. + ''; + }; + + virtualisation.useEFIBoot = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, the virtual machine will provide a EFI boot + manager. + useEFIBoot is ignored if useBootLoader == false. + ''; + }; virtualisation.efi = { OVMF = mkOption { type = types.package; - default = (pkgs.OVMF.override { - secureBoot = cfg.useSecureBoot; - }).fd; - defaultText = ''(pkgs.OVMF.override { - secureBoot = cfg.useSecureBoot; - }).fd''; + default = + (pkgs.OVMF.override { + secureBoot = cfg.useSecureBoot; + }).fd; + defaultText = '' + (pkgs.OVMF.override { + secureBoot = cfg.useSecureBoot; + }).fd''; description = "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; }; @@ -873,8 +930,8 @@ in default = cfg.efi.OVMF.firmware; defaultText = literalExpression "cfg.efi.OVMF.firmware"; description = '' - Firmware binary for EFI implementation, defaults to OVMF. - ''; + Firmware binary for EFI implementation, defaults to OVMF. + ''; }; variables = mkOption { @@ -882,9 +939,9 @@ in default = cfg.efi.OVMF.variables; defaultText = literalExpression "cfg.efi.OVMF.variables"; description = '' - Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. - Defaults to OVMF. - ''; + Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. + Defaults to OVMF. + ''; }; keepVariables = mkOption { @@ -902,13 +959,16 @@ in deviceModel = mkOption { type = types.str; - default = ({ - "i686-linux" = "tpm-tis"; - "x86_64-linux" = "tpm-tis"; - "ppc64-linux" = "tpm-spapr"; - "armv7-linux" = "tpm-tis-device"; - "aarch64-linux" = "tpm-tis-device"; - }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU")); + default = ( + { + "i686-linux" = "tpm-tis"; + "x86_64-linux" = "tpm-tis"; + "ppc64-linux" = "tpm-spapr"; + "armv7-linux" = "tpm-tis-device"; + "aarch64-linux" = "tpm-tis-device"; + } + .${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU") + ); defaultText = '' Based on the guest platform Linux system: @@ -921,104 +981,104 @@ in }; }; - virtualisation.useDefaultFilesystems = - mkOption { - type = types.bool; - default = true; - description = '' - If enabled, the boot disk of the virtual machine will be - formatted and mounted with the default filesystems for - testing. Swap devices and LUKS will be disabled. + virtualisation.useDefaultFilesystems = mkOption { + type = types.bool; + default = true; + description = '' + If enabled, the boot disk of the virtual machine will be + formatted and mounted with the default filesystems for + testing. Swap devices and LUKS will be disabled. - If disabled, a root filesystem has to be specified and - formatted (for example in the initial ramdisk). - ''; - }; + If disabled, a root filesystem has to be specified and + formatted (for example in the initial ramdisk). + ''; + }; - virtualisation.useSecureBoot = - mkOption { - type = types.bool; - default = false; - description = '' - Enable Secure Boot support in the EFI firmware. - ''; - }; + virtualisation.useSecureBoot = mkOption { + type = types.bool; + default = false; + description = '' + Enable Secure Boot support in the EFI firmware. + ''; + }; - virtualisation.bios = - mkOption { - type = types.nullOr types.package; - default = null; - description = '' - An alternate BIOS (such as `qboot`) with which to start the VM. - Should contain a file named `bios.bin`. - If `null`, QEMU's builtin SeaBIOS will be used. - ''; - }; + virtualisation.bios = mkOption { + type = types.nullOr types.package; + default = null; + description = '' + An alternate BIOS (such as `qboot`) with which to start the VM. + Should contain a file named `bios.bin`. + If `null`, QEMU's builtin SeaBIOS will be used. + ''; + }; - virtualisation.useHostCerts = - mkOption { - type = types.bool; - default = false; - description = '' - If enabled, when `NIX_SSL_CERT_FILE` is set on the host, - pass the CA certificates from the host to the VM. - ''; - }; + virtualisation.useHostCerts = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, when `NIX_SSL_CERT_FILE` is set on the host, + pass the CA certificates from the host to the VM. + ''; + }; }; config = { assertions = - lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule: - [ - { assertion = rule.from == "guest" -> rule.proto == "tcp"; - message = - '' + lib.concatLists ( + lib.flip lib.imap cfg.forwardPorts ( + i: rule: [ + { + assertion = rule.from == "guest" -> rule.proto == "tcp"; + message = '' Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto: Guest forwarding supports only TCP connections. ''; - } - { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; - message = - '' + } + { + assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; + message = '' Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address: The address must be in the default VLAN (10.0.2.0/24). ''; - } - ])) ++ [ - { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; - message = '' - virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. - ''; - } - { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; - message = - '' - You changed the default of `virtualisation.directBoot.initrd` but you are not - using QEMU direct boot. This initrd will not be used in your current - boot configuration. + } + ] + ) + ) + ++ [ + { + assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; + message = '' + virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. + ''; + } + { + assertion = + cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; + message = '' + You changed the default of `virtualisation.directBoot.initrd` but you are not + using QEMU direct boot. This initrd will not be used in your current + boot configuration. - Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. + Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. - If you have a more advanced usecase, please open an issue or a pull request. - ''; - } - { - assertion = cfg.installBootLoader -> config.system.switch.enable; - message = '' - `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work. - Please enable it in your configuration. - ''; - } - ]; + If you have a more advanced usecase, please open an issue or a pull request. + ''; + } + { + assertion = cfg.installBootLoader -> config.system.switch.enable; + message = '' + `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work. + Please enable it in your configuration. + ''; + } + ]; - warnings = - optional (cfg.directBoot.enable && cfg.useBootLoader) - '' - You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering - `useBootLoader` useless. You might want to disable one of those options. - ''; + warnings = optional (cfg.directBoot.enable && cfg.useBootLoader) '' + You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering + `useBootLoader` useless. You might want to disable one of those options. + ''; # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs. @@ -1036,12 +1096,11 @@ in # allow `system.build.toplevel' to be included. (If we had a direct # reference to ${regInfo} here, then we would get a cyclic # dependency.) - boot.postBootCommands = lib.mkIf config.nix.enable - '' - if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then - ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} - fi - ''; + boot.postBootCommands = lib.mkIf config.nix.enable '' + if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then + ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} + fi + ''; boot.initrd.availableKernelModules = optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx" @@ -1078,14 +1137,20 @@ in virtualisation.qemu.networkingOptions = let - forwardingOptions = flip concatMapStrings cfg.forwardPorts - ({ proto, from, host, guest }: - if from == "host" - then "hostfwd=${proto}:${host.address}:${toString host.port}-" + - "${guest.address}:${toString guest.port}," - else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + - "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," - ); + forwardingOptions = flip concatMapStrings cfg.forwardPorts ( + { + proto, + from, + host, + guest, + }: + if from == "host" then + "hostfwd=${proto}:${host.address}:${toString host.port}-" + + "${guest.address}:${toString guest.port}," + else + "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + + "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," + ); restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,"; in [ @@ -1098,20 +1163,29 @@ in "-device virtio-keyboard" ]) (mkIf pkgs.stdenv.hostPlatform.isx86 [ - "-usb" "-device usb-tablet,bus=usb-bus.0" + "-usb" + "-device usb-tablet,bus=usb-bus.0" ]) (mkIf pkgs.stdenv.hostPlatform.isAarch [ - "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" - ]) - (let - alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); - # Replace all non-alphanumeric characters with underscores - sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); - in mkIf cfg.directBoot.enable [ - "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" - "-initrd ${cfg.directBoot.initrd}" - ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' + "-device virtio-gpu-pci" + "-device usb-ehci,id=usb0" + "-device usb-kbd" + "-device usb-tablet" ]) + ( + let + alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); + # Replace all non-alphanumeric characters with underscores + sanitizeShellIdent = + s: + concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); + in + mkIf cfg.directBoot.enable [ + "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" + "-initrd ${cfg.directBoot.initrd}" + ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' + ] + ) (mkIf cfg.useEFIBoot [ "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}" "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS" @@ -1128,26 +1202,32 @@ in "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" ]) (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [ - "-machine" "q35,smm=on" - "-global" "driver=cfi.pflash01,property=secure,value=on" + "-machine" + "q35,smm=on" + "-global" + "driver=cfi.pflash01,property=secure,value=on" ]) ]; virtualisation.qemu.drives = mkMerge [ - (mkIf (cfg.diskImage != null) [{ - name = "root"; - file = ''"$NIX_DISK_IMAGE"''; - driveExtraOpts.cache = "writeback"; - driveExtraOpts.werror = "report"; - deviceExtraOpts.bootindex = "1"; - deviceExtraOpts.serial = rootDriveSerialAttr; - }]) - (mkIf cfg.useNixStoreImage [{ - name = "nix-store"; - file = ''"$TMPDIR"/store.img''; - deviceExtraOpts.bootindex = "2"; - driveExtraOpts.format = "raw"; - }]) + (mkIf (cfg.diskImage != null) [ + { + name = "root"; + file = ''"$NIX_DISK_IMAGE"''; + driveExtraOpts.cache = "writeback"; + driveExtraOpts.werror = "report"; + deviceExtraOpts.bootindex = "1"; + deviceExtraOpts.serial = rootDriveSerialAttr; + } + ]) + (mkIf cfg.useNixStoreImage [ + { + name = "nix-store"; + file = ''"$TMPDIR"/store.img''; + deviceExtraOpts.bootindex = "2"; + driveExtraOpts.format = "raw"; + } + ]) (imap0 (idx: _: { file = "$(pwd)/empty${toString idx}.qcow2"; driveExtraOpts.werror = "report"; @@ -1161,91 +1241,116 @@ in # override by setting `virtualisation.fileSystems = lib.mkForce { };`. fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems); - virtualisation.fileSystems = let - mkSharedDir = tag: share: - { + virtualisation.diskSizeAutoSupported = false; + + virtualisation.fileSystems = + let + mkSharedDir = tag: share: { name = share.target; value.device = tag; value.fsType = "9p"; value.neededForBoot = true; - value.options = - [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ] - ++ lib.optional (tag == "nix-store") "cache=loose"; + value.options = [ + "trans=virtio" + "version=9p2000.L" + "msize=${toString cfg.msize}" + "x-systemd.requires=modprobe@9pnet_virtio.service" + ] ++ lib.optional (tag == "nix-store") "cache=loose"; }; - in lib.mkMerge [ - (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) - { - "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then { - device = "tmpfs"; - fsType = "tmpfs"; - } else { - device = cfg.rootDevice; - fsType = "ext4"; - }); - "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { - device = "tmpfs"; - fsType = "tmpfs"; - neededForBoot = true; - # Sync with systemd's tmp.mount; - options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ]; - }; - "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then { - overlay = { - lowerdir = [ "/nix/.ro-store" ]; - upperdir = "/nix/.rw-store/upper"; - workdir = "/nix/.rw-store/work"; + in + lib.mkMerge [ + (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) + { + "/" = lib.mkIf cfg.useDefaultFilesystems ( + if cfg.diskImage == null then + { + device = "tmpfs"; + fsType = "tmpfs"; + } + else + { + device = cfg.rootDevice; + fsType = "ext4"; + } + ); + "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { + device = "tmpfs"; + fsType = "tmpfs"; + neededForBoot = true; + # Sync with systemd's tmp.mount; + options = [ + "mode=1777" + "strictatime" + "nosuid" + "nodev" + "size=${toString config.boot.tmp.tmpfsSize}" + ]; }; - } else { - device = "/nix/.ro-store"; - options = [ "bind" ]; - }); - "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { - device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; - fsType = "erofs"; - neededForBoot = true; - options = [ "ro" ]; - }; - "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { - fsType = "tmpfs"; - options = [ "mode=0755" ]; - neededForBoot = true; - }; - "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) { - device = cfg.bootPartition; - fsType = "vfat"; - }; - } - ]; + "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) ( + if cfg.writableStore then + { + overlay = { + lowerdir = [ "/nix/.ro-store" ]; + upperdir = "/nix/.rw-store/upper"; + workdir = "/nix/.rw-store/work"; + }; + } + else + { + device = "/nix/.ro-store"; + options = [ "bind" ]; + } + ); + "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { + device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; + fsType = "erofs"; + neededForBoot = true; + options = [ "ro" ]; + }; + "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { + fsType = "tmpfs"; + options = [ "mode=0755" ]; + neededForBoot = true; + }; + "${config.boot.loader.efi.efiSysMountPoint}" = + lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) + { + device = cfg.bootPartition; + fsType = "vfat"; + }; + } + ]; swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ]; - boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {}; + boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) { }; # Don't run ntpd in the guest. It should get the correct time from KVM. services.timesyncd.enable = false; services.qemuGuest.enable = cfg.qemu.guestAgent.enable; - system.build.vm = hostPkgs.runCommand "nixos-vm" { - preferLocalBuild = true; - meta.mainProgram = "run-${config.system.name}-vm"; - } - '' - mkdir -p $out/bin - ln -s ${config.system.build.toplevel} $out/system - ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm - ''; + system.build.vm = + hostPkgs.runCommand "nixos-vm" + { + preferLocalBuild = true; + meta.mainProgram = "run-${config.system.name}-vm"; + } + '' + mkdir -p $out/bin + ln -s ${config.system.build.toplevel} $out/system + ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm + ''; # When building a regular system configuration, override whatever # video driver the host uses. services.xserver.videoDrivers = mkVMOverride [ "modesetting" ]; services.xserver.defaultDepth = mkVMOverride 0; services.xserver.resolutions = mkVMOverride [ cfg.resolution ]; - services.xserver.monitorSection = - '' - # Set a higher refresh rate so that resolutions > 800x600 work. - HorizSync 30-140 - VertRefresh 50-160 - ''; + services.xserver.monitorSection = '' + # Set a higher refresh rate so that resolutions > 800x600 work. + HorizSync 30-140 + VertRefresh 50-160 + ''; # Wireless won't work in the VM. networking.wireless.enable = mkVMOverride false; @@ -1256,8 +1361,10 @@ in networking.usePredictableInterfaceNames = false; - system.requiredKernelConfig = with config.lib.kernelConfig; - [ (isEnabled "VIRTIO_BLK") + system.requiredKernelConfig = + with config.lib.kernelConfig; + [ + (isEnabled "VIRTIO_BLK") (isEnabled "VIRTIO_PCI") (isEnabled "VIRTIO_NET") (isEnabled "EXT4_FS") @@ -1269,10 +1376,12 @@ in (isYes "NET_CORE") (isYes "INET") (isYes "NETWORK_FILESYSTEMS") - ] ++ optionals (!cfg.graphics) [ + ] + ++ optionals (!cfg.graphics) [ (isYes "SERIAL_8250_CONSOLE") (isYes "SERIAL_8250") - ] ++ optionals (cfg.writableStore) [ + ] + ++ optionals (cfg.writableStore) [ (isEnabled "OVERLAY_FS") ]; diff --git a/nixos/modules/virtualisation/virtualbox-guest.nix b/nixos/modules/virtualisation/virtualbox-guest.nix index 31222d553a34..48eb4528a232 100644 --- a/nixos/modules/virtualisation/virtualbox-guest.nix +++ b/nixos/modules/virtualisation/virtualbox-guest.nix @@ -1,5 +1,10 @@ # Module for VirtualBox guests. -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.virtualisation.virtualbox.guest; kernel = config.boot.kernelPackages; @@ -28,7 +33,20 @@ let in { imports = [ - (lib.mkRenamedOptionModule [ "virtualisation" "virtualbox" "guest" "draganddrop" ] [ "virtualisation" "virtualbox" "guest" "dragAndDrop" ]) + (lib.mkRenamedOptionModule + [ + "virtualisation" + "virtualbox" + "guest" + "draganddrop" + ] + [ + "virtualisation" + "virtualbox" + "guest" + "dragAndDrop" + ] + ) ]; options.virtualisation.virtualbox.guest = { @@ -59,36 +77,38 @@ in ###### implementation - config = lib.mkIf cfg.enable (lib.mkMerge [ - { - assertions = [{ - assertion = pkgs.stdenv.hostPlatform.isx86; - message = "Virtualbox not currently supported on ${pkgs.stdenv.hostPlatform.system}"; - }]; + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + assertions = [ + { + assertion = pkgs.stdenv.hostPlatform.isx86; + message = "Virtualbox not currently supported on ${pkgs.stdenv.hostPlatform.system}"; + } + ]; - environment.systemPackages = [ kernel.virtualboxGuestAdditions ]; + environment.systemPackages = [ kernel.virtualboxGuestAdditions ]; - boot.extraModulePackages = [ kernel.virtualboxGuestAdditions ]; + boot.extraModulePackages = [ kernel.virtualboxGuestAdditions ]; - boot.supportedFilesystems = [ "vboxsf" ]; - boot.initrd.supportedFilesystems = [ "vboxsf" ]; + boot.supportedFilesystems = [ "vboxsf" ]; + boot.initrd.supportedFilesystems = [ "vboxsf" ]; - users.groups.vboxsf.gid = config.ids.gids.vboxsf; + users.groups.vboxsf.gid = config.ids.gids.vboxsf; - systemd.services.virtualbox = { - description = "VirtualBox Guest Services"; + systemd.services.virtualbox = { + description = "VirtualBox Guest Services"; - wantedBy = [ "multi-user.target" ]; - requires = [ "dev-vboxguest.device" ]; - after = [ "dev-vboxguest.device" ]; + wantedBy = [ "multi-user.target" ]; + requires = [ "dev-vboxguest.device" ]; + after = [ "dev-vboxguest.device" ]; - unitConfig.ConditionVirtualization = "oracle"; + unitConfig.ConditionVirtualization = "oracle"; - serviceConfig.ExecStart = "@${kernel.virtualboxGuestAdditions}/bin/VBoxService VBoxService --foreground"; - }; + serviceConfig.ExecStart = "@${kernel.virtualboxGuestAdditions}/bin/VBoxService VBoxService --foreground"; + }; - services.udev.extraRules = - '' + services.udev.extraRules = '' # /dev/vboxuser is necessary for VBoxClient to work. Maybe we # should restrict this to logged-in users. KERNEL=="vboxuser", OWNER="root", GROUP="root", MODE="0666" @@ -97,22 +117,17 @@ in SUBSYSTEM=="misc", KERNEL=="vboxguest", TAG+="systemd" ''; - systemd.user.services.virtualboxClientVmsvga = mkVirtualBoxUserService "--vmsvga-session"; - } - ( - lib.mkIf cfg.clipboard { - systemd.user.services.virtualboxClientClipboard = mkVirtualBoxUserService "--clipboard"; + systemd.user.services.virtualboxClientVmsvga = mkVirtualBoxUserService "--vmsvga-session"; } - ) - ( - lib.mkIf cfg.seamless { + (lib.mkIf cfg.clipboard { + systemd.user.services.virtualboxClientClipboard = mkVirtualBoxUserService "--clipboard"; + }) + (lib.mkIf cfg.seamless { systemd.user.services.virtualboxClientSeamless = mkVirtualBoxUserService "--seamless"; - } - ) - ( - lib.mkIf cfg.dragAndDrop { + }) + (lib.mkIf cfg.dragAndDrop { systemd.user.services.virtualboxClientDragAndDrop = mkVirtualBoxUserService "--draganddrop"; - } - ) - ]); + }) + ] + ); } diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix index 8820b4ff5a83..29e2d4340f3c 100644 --- a/nixos/modules/virtualisation/virtualbox-host.nix +++ b/nixos/modules/virtualisation/virtualbox-host.nix @@ -1,9 +1,19 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.virtualisation.virtualbox.host; virtualbox = cfg.package.override { - inherit (cfg) enableHardening headless enableWebService enableKvm; + inherit (cfg) + enableHardening + headless + enableWebService + enableKvm + ; extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null; }; @@ -93,93 +103,119 @@ in }; }; - config = lib.mkIf cfg.enable (lib.mkMerge [{ - warnings = lib.mkIf (pkgs.config.virtualbox.enableExtensionPack or false) - ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"]; - environment.systemPackages = [ virtualbox ]; - - security.wrappers = let - mkSuid = program: { - source = "${virtualbox}/libexec/virtualbox/${program}"; - owner = "root"; - group = "vboxusers"; - setuid = true; - }; - executables = [ - "VBoxHeadless" - "VBoxNetAdpCtl" - "VBoxNetDHCP" - "VBoxNetNAT" - "VBoxVolInfo" - ] ++ (lib.optionals (!cfg.headless) [ - "VBoxSDL" - "VirtualBoxVM" - ]); - in lib.mkIf cfg.enableHardening - (builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) executables)); - - users.groups.vboxusers.gid = config.ids.gids.vboxusers; - - services.udev.extraRules = - '' - 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" - ''; - } (lib.mkIf cfg.enableKvm { - assertions = [ + config = lib.mkIf cfg.enable ( + lib.mkMerge [ { - assertion = !cfg.addNetworkInterface; - message = "VirtualBox KVM only supports standard NAT networking for VMs. Please turn off virtualisation.virtualbox.host.addNetworkInterface."; + warnings = lib.mkIf (pkgs.config.virtualbox.enableExtensionPack or false) [ + "'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'" + ]; + environment.systemPackages = [ virtualbox ]; + + security.wrappers = + let + mkSuid = program: { + source = "${virtualbox}/libexec/virtualbox/${program}"; + owner = "root"; + group = "vboxusers"; + setuid = true; + }; + executables = + [ + "VBoxHeadless" + "VBoxNetAdpCtl" + "VBoxNetDHCP" + "VBoxNetNAT" + "VBoxVolInfo" + ] + ++ (lib.optionals (!cfg.headless) [ + "VBoxSDL" + "VirtualBoxVM" + ]); + in + lib.mkIf cfg.enableHardening ( + builtins.listToAttrs ( + map (x: { + name = x; + value = mkSuid x; + }) executables + ) + ); + + users.groups.vboxusers.gid = config.ids.gids.vboxusers; + + services.udev.extraRules = '' + 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" + ''; } - ]; - }) (lib.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. - }) (lib.mkIf cfg.addNetworkInterface { - systemd.services.vboxnet0 = - { description = "VirtualBox vboxnet0 Interface"; - requires = [ "dev-vboxnetctl.device" ]; - after = [ "dev-vboxnetctl.device" ]; - wantedBy = [ "network.target" "sys-subsystem-net-devices-vboxnet0.device" ]; - path = [ virtualbox ]; - serviceConfig.RemainAfterExit = true; - serviceConfig.Type = "oneshot"; - serviceConfig.PrivateTmp = true; - environment.VBOX_USER_HOME = "/tmp"; - script = - '' + (lib.mkIf cfg.enableKvm { + assertions = [ + { + assertion = !cfg.addNetworkInterface; + message = "VirtualBox KVM only supports standard NAT networking for VMs. Please turn off virtualisation.virtualbox.host.addNetworkInterface."; + } + ]; + }) + (lib.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. + }) + (lib.mkIf cfg.addNetworkInterface { + systemd.services.vboxnet0 = { + description = "VirtualBox vboxnet0 Interface"; + requires = [ "dev-vboxnetctl.device" ]; + after = [ "dev-vboxnetctl.device" ]; + wantedBy = [ + "network.target" + "sys-subsystem-net-devices-vboxnet0.device" + ]; + path = [ virtualbox ]; + serviceConfig.RemainAfterExit = true; + serviceConfig.Type = "oneshot"; + serviceConfig.PrivateTmp = true; + environment.VBOX_USER_HOME = "/tmp"; + script = '' if ! [ -e /sys/class/net/vboxnet0 ]; then VBoxManage hostonlyif create cat /tmp/VBoxSVC.log >&2 fi ''; - postStop = - '' + postStop = '' VBoxManage hostonlyif remove vboxnet0 ''; - }; - - networking.interfaces.vboxnet0.ipv4.addresses = [{ address = "192.168.56.1"; prefixLength = 24; }]; - # Make sure NetworkManager won't assume this interface being up - # means we have internet access. - networking.networkmanager.unmanaged = ["vboxnet0"]; - }) (lib.mkIf config.networking.useNetworkd { - systemd.network.networks."40-vboxnet0".extraConfig = '' - [Link] - RequiredForOnline=no - ''; - }) - -]); + }; + + networking.interfaces.vboxnet0.ipv4.addresses = [ + { + address = "192.168.56.1"; + prefixLength = 24; + } + ]; + # Make sure NetworkManager won't assume this interface being up + # means we have internet access. + networking.networkmanager.unmanaged = [ "vboxnet0" ]; + }) + (lib.mkIf config.networking.useNetworkd { + systemd.network.networks."40-vboxnet0".extraConfig = '' + [Link] + RequiredForOnline=no + ''; + }) + + ] + ); } diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix index 4ab5d17ecd49..22646011d33d 100644 --- a/nixos/modules/virtualisation/virtualbox-image.nix +++ b/nixos/modules/virtualisation/virtualbox-image.nix @@ -1,22 +1,33 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.virtualbox; - -in { +in +{ + imports = [ + ./disk-size-option.nix + (lib.mkRenamedOptionModuleWith { + sinceRelease = 2411; + from = [ + "virtualbox" + "baseImageSize" + ]; + to = [ + "virtualisation" + "diskSize" + ]; + }) + ]; options = { virtualbox = { - baseImageSize = lib.mkOption { - type = with lib.types; either (enum [ "auto" ]) int; - default = "auto"; - example = 50 * 1024; - description = '' - The size of the VirtualBox base image in MiB. - ''; - }; baseImageFreeSpace = lib.mkOption { - type = with lib.types; int; + type = lib.types.int; default = 30 * 1024; description = '' Free space in the VirtualBox base image in MiB. @@ -51,7 +62,14 @@ in { ''; }; params = lib.mkOption { - type = with lib.types; attrsOf (oneOf [ str int bool (listOf str) ]); + type = + with lib.types; + attrsOf (oneOf [ + str + int + bool + (listOf str) + ]); example = { audio = "alsa"; rtcuseutc = "on"; @@ -64,11 +82,21 @@ in { ''; }; exportParams = lib.mkOption { - type = with lib.types; listOf (oneOf [ str int bool (listOf str) ]); + type = + with lib.types; + listOf (oneOf [ + str + int + bool + (listOf str) + ]); example = [ - "--vsys" "0" "--vendor" "ACME Inc." + "--vsys" + "0" + "--vendor" + "ACME Inc." ]; - default = []; + default = [ ]; description = '' Parameters passed to the Virtualbox export command. @@ -86,23 +114,25 @@ in { mountPoint = "/home/demo/storage"; size = 100 * 1024; }; - type = lib.types.nullOr (lib.types.submodule { - options = { - size = lib.mkOption { - type = lib.types.int; - description = "Size in MiB"; + type = lib.types.nullOr ( + lib.types.submodule { + options = { + size = lib.mkOption { + type = lib.types.int; + description = "Size in MiB"; + }; + label = lib.mkOption { + type = lib.types.str; + default = "vm-extra-storage"; + description = "Label for the disk partition"; + }; + mountPoint = lib.mkOption { + type = lib.types.str; + description = "Path where to mount this disk."; + }; }; - label = lib.mkOption { - type = lib.types.str; - default = "vm-extra-storage"; - description = "Label for the disk partition"; - }; - mountPoint = lib.mkOption { - type = lib.types.str; - description = "Path where to mount this disk."; - }; - }; - }); + } + ); }; postExportCommands = lib.mkOption { type = lib.types.lines; @@ -122,7 +152,14 @@ in { ''; }; storageController = lib.mkOption { - type = with lib.types; attrsOf (oneOf [ str int bool (listOf str) ]); + type = + with lib.types; + attrsOf (oneOf [ + str + int + bool + (listOf str) + ]); example = { name = "SCSI"; add = "scsi"; @@ -148,6 +185,9 @@ in { }; config = { + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault + # to avoid breaking existing configs using that. + virtualisation.diskSize = lib.mkOverride 1490 (50 * 1024); virtualbox.params = lib.mkMerge [ (lib.mapAttrs (name: lib.mkDefault) { @@ -172,80 +212,83 @@ in { inherit pkgs lib config; partitionTableType = "legacy"; - diskSize = cfg.baseImageSize; + inherit (config.virtualisation) diskSize; additionalSpace = "${toString cfg.baseImageFreeSpace}M"; - postVM = - '' - export HOME=$PWD - export PATH=${pkgs.virtualbox}/bin:$PATH - - echo "converting image to VirtualBox format..." - VBoxManage convertfromraw $diskImage disk.vdi - - ${lib.optionalString (cfg.extraDisk != null) '' - echo "creating extra disk: data-disk.raw" - dataDiskImage=data-disk.raw - truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage - - parted --script $dataDiskImage -- \ - mklabel msdos \ - mkpart primary ext4 1MiB -1 - eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs) - mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K - echo "creating extra disk: data-disk.vdi" - VBoxManage convertfromraw $dataDiskImage data-disk.vdi - ''} - - echo "creating VirtualBox VM..." - vmName="${cfg.vmName}"; - VBoxManage createvm --name "$vmName" --register \ - --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} - VBoxManage modifyvm "$vmName" \ - --memory ${toString cfg.memorySize} \ - ${lib.cli.toGNUCommandLineShell { } cfg.params} - VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController} - VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \ - --medium disk.vdi - ${lib.optionalString (cfg.extraDisk != null) '' - VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \ - --medium data-disk.vdi - ''} - - echo "exporting VirtualBox VM..." - mkdir -p $out - fn="$out/${cfg.vmFileName}" - VBoxManage export "$vmName" --output "$fn" --options manifest ${lib.escapeShellArgs cfg.exportParams} - ${cfg.postExportCommands} - - rm -v $diskImage - - mkdir -p $out/nix-support - echo "file ova $fn" >> $out/nix-support/hydra-build-products - ''; + postVM = '' + export HOME=$PWD + export PATH=${pkgs.virtualbox}/bin:$PATH + + echo "converting image to VirtualBox format..." + VBoxManage convertfromraw $diskImage disk.vdi + + ${lib.optionalString (cfg.extraDisk != null) '' + echo "creating extra disk: data-disk.raw" + dataDiskImage=data-disk.raw + truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage + + parted --script $dataDiskImage -- \ + mklabel msdos \ + mkpart primary ext4 1MiB -1 + eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs) + mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K + echo "creating extra disk: data-disk.vdi" + VBoxManage convertfromraw $dataDiskImage data-disk.vdi + ''} + + echo "creating VirtualBox VM..." + vmName="${cfg.vmName}"; + VBoxManage createvm --name "$vmName" --register \ + --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} + VBoxManage modifyvm "$vmName" \ + --memory ${toString cfg.memorySize} \ + ${lib.cli.toGNUCommandLineShell { } cfg.params} + VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController} + VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \ + --medium disk.vdi + ${lib.optionalString (cfg.extraDisk != null) '' + VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \ + --medium data-disk.vdi + ''} + + echo "exporting VirtualBox VM..." + mkdir -p $out + fn="$out/${cfg.vmFileName}" + VBoxManage export "$vmName" --output "$fn" --options manifest ${lib.escapeShellArgs cfg.exportParams} + ${cfg.postExportCommands} + + rm -v $diskImage + + mkdir -p $out/nix-support + echo "file ova $fn" >> $out/nix-support/hydra-build-products + ''; }; - fileSystems = { - "/" = { - device = "/dev/disk/by-label/nixos"; - autoResize = true; - fsType = "ext4"; - }; - } // (lib.optionalAttrs (cfg.extraDisk != null) { - ${cfg.extraDisk.mountPoint} = { - device = "/dev/disk/by-label/" + cfg.extraDisk.label; - autoResize = true; - fsType = "ext4"; - }; - }); + fileSystems = + { + "/" = { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + } + // (lib.optionalAttrs (cfg.extraDisk != null) { + ${cfg.extraDisk.mountPoint} = { + device = "/dev/disk/by-label/" + cfg.extraDisk.label; + autoResize = true; + fsType = "ext4"; + }; + }); boot.growPartition = true; boot.loader.grub.device = "/dev/sda"; - swapDevices = [{ - device = "/var/swap"; - size = 2048; - }]; + swapDevices = [ + { + device = "/var/swap"; + size = 2048; + } + ]; virtualisation.virtualbox.guest.enable = true; diff --git a/nixos/modules/virtualisation/vmware-guest.nix b/nixos/modules/virtualisation/vmware-guest.nix index c80181f287b3..5379a481be5b 100644 --- a/nixos/modules/virtualisation/vmware-guest.nix +++ b/nixos/modules/virtualisation/vmware-guest.nix @@ -1,50 +1,62 @@ { config, lib, pkgs, ... }: let + inherit (lib) getExe' literalExpression mkEnableOption mkIf mkOption mkRenamedOptionModule optionals optionalString types; cfg = config.virtualisation.vmware.guest; - open-vm-tools = if cfg.headless then pkgs.open-vm-tools-headless else pkgs.open-vm-tools; xf86inputvmmouse = pkgs.xorg.xf86inputvmmouse; in { imports = [ - (lib.mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ]) + (mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ]) ]; + meta = { + maintainers = [ lib.maintainers.kjeremy ]; + }; + options.virtualisation.vmware.guest = { - enable = lib.mkEnableOption "VMWare Guest Support"; - headless = lib.mkOption { - type = lib.types.bool; + enable = mkEnableOption "VMWare Guest Support"; + headless = mkOption { + type = types.bool; default = !config.services.xserver.enable; - defaultText = "!config.services.xserver.enable"; + defaultText = literalExpression "!config.services.xserver.enable"; description = "Whether to disable X11-related features."; }; + + package = mkOption { + type = types.package; + default = if cfg.headless then pkgs.open-vm-tools-headless else pkgs.open-vm-tools; + defaultText = literalExpression "if config.virtualisation.vmware.headless then pkgs.open-vm-tools-headless else pkgs.open-vm-tools;"; + example = literalExpression "pkgs.open-vm-tools"; + description = "Package providing open-vm-tools."; + }; }; - config = lib.mkIf cfg.enable { + config = mkIf cfg.enable { assertions = [ { assertion = pkgs.stdenv.hostPlatform.isx86 || pkgs.stdenv.hostPlatform.isAarch64; message = "VMWare guest is not currently supported on ${pkgs.stdenv.hostPlatform.system}"; } ]; boot.initrd.availableKernelModules = [ "mptspi" ]; - boot.initrd.kernelModules = lib.optionals pkgs.stdenv.hostPlatform.isx86 [ "vmw_pvscsi" ]; + boot.initrd.kernelModules = optionals pkgs.stdenv.hostPlatform.isx86 [ "vmw_pvscsi" ]; - environment.systemPackages = [ open-vm-tools ]; + environment.systemPackages = [ cfg.package ]; systemd.services.vmware = { description = "VMWare Guest Service"; wantedBy = [ "multi-user.target" ]; after = [ "display-manager.service" ]; unitConfig.ConditionVirtualization = "vmware"; - serviceConfig.ExecStart = "${open-vm-tools}/bin/vmtoolsd"; + serviceConfig.ExecStart = getExe' cfg.package "vmtoolsd"; }; # Mount the vmblock for drag-and-drop and copy-and-paste. - systemd.mounts = lib.mkIf (!cfg.headless) [ + systemd.mounts = mkIf (!cfg.headless) [ { description = "VMware vmblock fuse mount"; documentation = [ "https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/vmblock-fuse/design.txt" ]; unitConfig.ConditionVirtualization = "vmware"; - what = "${open-vm-tools}/bin/vmware-vmblock-fuse"; + what = getExe' cfg.package "vmware-vmblock-fuse"; where = "/run/vmblock-fuse"; type = "fuse"; options = "subtype=vmware-vmblock,default_permissions,allow_other"; @@ -52,19 +64,19 @@ in } ]; - security.wrappers.vmware-user-suid-wrapper = lib.mkIf (!cfg.headless) { + security.wrappers.vmware-user-suid-wrapper = mkIf (!cfg.headless) { setuid = true; owner = "root"; group = "root"; - source = "${open-vm-tools}/bin/vmware-user-suid-wrapper"; + source = getExe' cfg.package "vmware-user-suid-wrapper"; }; - environment.etc.vmware-tools.source = "${open-vm-tools}/etc/vmware-tools/*"; + environment.etc.vmware-tools.source = "${cfg.package}/etc/vmware-tools/*"; - services.xserver = lib.mkIf (!cfg.headless) { - modules = lib.optionals pkgs.stdenv.hostPlatform.isx86 [ xf86inputvmmouse ]; + services.xserver = mkIf (!cfg.headless) { + modules = optionals pkgs.stdenv.hostPlatform.isx86 [ xf86inputvmmouse ]; - config = lib.optionalString (pkgs.stdenv.hostPlatform.isx86) '' + config = optionalString (pkgs.stdenv.hostPlatform.isx86) '' Section "InputClass" Identifier "VMMouse" MatchDevicePath "/dev/input/event*" @@ -74,10 +86,10 @@ in ''; displayManager.sessionCommands = '' - ${open-vm-tools}/bin/vmware-user-suid-wrapper + ${getExe' cfg.package "vmware-user-suid-wrapper"} ''; }; - services.udev.packages = [ open-vm-tools ]; + services.udev.packages = [ cfg.package ]; }; } diff --git a/nixos/modules/virtualisation/xe-guest-utilities.nix b/nixos/modules/virtualisation/xe-guest-utilities.nix index 9bc68c0a3d7d..14dd25e878cf 100644 --- a/nixos/modules/virtualisation/xe-guest-utilities.nix +++ b/nixos/modules/virtualisation/xe-guest-utilities.nix @@ -4,7 +4,7 @@ let in { options = { services.xe-guest-utilities = { - enable = lib.mkEnableOption "the Xen guest utilities daemon"; + enable = lib.mkEnableOption "the XenServer guest utilities daemon"; }; }; config = lib.mkIf cfg.enable { diff --git a/nixos/modules/virtualisation/xen-boot-builder.sh b/nixos/modules/virtualisation/xen-boot-builder.sh index 13e1a4e20243..1b7c1470f6eb 100755 --- a/nixos/modules/virtualisation/xen-boot-builder.sh +++ b/nixos/modules/virtualisation/xen-boot-builder.sh @@ -5,7 +5,7 @@ [[ $# -ne 1 ]] && echo -e "\e[1;31merror:\e[0m xenBootBuilder must be called with exactly one verbosity argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option." && exit 1 case "$1" in "quiet") true ;; - "default" | "info") echo -n "Installing Xen Hypervisor boot entries..." ;; + "default" | "info") echo -n "Installing Xen Project Hypervisor boot entries..." ;; "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m called with the '$1' flag" ;; *) echo -e "\e[1;31merror:\e[0m xenBootBuilder was called with an invalid argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option." @@ -150,7 +150,7 @@ else esac if [ "$1" = "info" ]; then if [[ ${#preGenerations[@]} == "${#postGenerations[@]}" ]]; then - echo -e "\e[1;33mNo Change:\e[0m Xen Hypervisor boot entries were refreshed, but their contents are identical." + echo -e "\e[1;33mNo Change:\e[0m Xen Project Hypervisor boot entries were refreshed, but their contents are identical." else echo -e "\e[1;32mSuccess:\e[0m Changed the following boot entries:" # We briefly unset errexit and pipefail here, as GNU diff has no option to not fail when files differ. diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix index fa2cf2b2c6d5..2911c0c1d97c 100644 --- a/nixos/modules/virtualisation/xen-dom0.nix +++ b/nixos/modules/virtualisation/xen-dom0.nix @@ -1,4 +1,4 @@ -# Xen hypervisor (Dom0) support. +# Xen Project Hypervisor (Dom0) support. { config, @@ -8,6 +8,35 @@ }: let + inherit (builtins) readFile; + inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule mkIf; + inherit (lib.options) + mkOption + mkEnableOption + literalExpression + mkPackageOption + ; + inherit (lib.types) + listOf + str + ints + lines + enum + path + submodule + addCheck + float + bool + int + nullOr + ; + inherit (lib.lists) optional optionals; + inherit (lib.strings) hasSuffix optionalString; + inherit (lib.meta) getExe; + inherit (lib.attrsets) optionalAttrs; + inherit (lib.trivial) boolToString; + inherit (lib.teams.xen) members; + cfg = config.virtualisation.xen; xenBootBuilder = pkgs.writeShellApplication { @@ -22,7 +51,7 @@ let gnused jq ]) - ++ lib.lists.optionals (cfg.efi.bootBuilderVerbosity == "info") ( + ++ optionals (cfg.efi.bootBuilderVerbosity == "info") ( with pkgs; [ bat @@ -36,12 +65,12 @@ let # We disable SC2016 because we don't want to expand the regexes in the sed commands. excludeShellChecks = [ "SC2016" ]; - text = builtins.readFile ./xen-boot-builder.sh; + text = readFile ./xen-boot-builder.sh; }; in { - imports = with lib.modules; [ + imports = [ (mkRemovedOptionModule [ "virtualisation" @@ -123,59 +152,33 @@ in options.virtualisation.xen = { - enable = lib.options.mkEnableOption "the Xen Hypervisor, a virtualisation technology defined as a *type-1 hypervisor*, which allows multiple virtual machines, known as *domains*, to run concurrently on the physical machine. NixOS runs as the privileged *Domain 0*. This option requires a reboot into a Xen kernel to take effect"; + enable = mkEnableOption "the Xen Project Hypervisor, a virtualisation technology defined as a *type-1 hypervisor*, which allows multiple virtual machines, known as *domains*, to run concurrently on the physical machine. NixOS runs as the privileged *Domain 0*. This option requires a reboot into a Xen kernel to take effect"; - debug = lib.options.mkEnableOption "Xen debug features for Domain 0. This option enables some hidden debugging tests and features, and should not be used in production"; + debug = mkEnableOption "Xen debug features for Domain 0. This option enables some hidden debugging tests and features, and should not be used in production"; - trace = lib.options.mkOption { - type = lib.types.bool; + trace = mkOption { + type = bool; default = cfg.debug; - defaultText = lib.options.literalExpression "false"; + defaultText = literalExpression "false"; example = true; description = "Whether to enable Xen debug tracing and logging for Domain 0."; }; - package = lib.options.mkOption { - type = lib.types.package; - default = pkgs.xen; - defaultText = lib.options.literalExpression "pkgs.xen"; - example = lib.options.literalExpression "pkgs.xen-slim"; - description = '' - The package used for Xen Hypervisor. - ''; - relatedPackages = [ - "xen" - "xen-slim" - ]; - }; + package = mkPackageOption pkgs "Xen Hypervisor" { default = [ "xen" ]; }; qemu = { - package = lib.options.mkOption { - type = lib.types.package; - default = pkgs.xen; - defaultText = lib.options.literalExpression "pkgs.xen"; - example = lib.options.literalExpression "pkgs.qemu_xen"; - description = '' - The package with QEMU binaries that runs in Domain 0 - and virtualises the unprivileged domains. - ''; - relatedPackages = [ - "xen" - { - name = "qemu_xen"; - comment = "For use with `pkgs.xen-slim`."; - } - ]; + package = mkPackageOption pkgs "QEMU (with Xen Hypervisor support)" { + default = [ "qemu_xen" ]; }; - pidFile = lib.options.mkOption { - type = lib.types.path; + pidFile = mkOption { + type = path; default = "/run/xen/qemu-dom0.pid"; example = "/var/run/xen/qemu-dom0.pid"; description = "Path to the QEMU PID file."; }; }; - bootParams = lib.options.mkOption { + bootParams = mkOption { default = [ ]; example = '' [ @@ -184,7 +187,7 @@ in "vga=ask" ] ''; - type = lib.types.listOf lib.types.str; + type = listOf str; description = '' Xen Command Line parameters passed to Domain 0 at boot time. Note: these are different from `boot.kernelParams`. See @@ -193,8 +196,8 @@ in }; efi = { - bootBuilderVerbosity = lib.options.mkOption { - type = lib.types.enum [ + bootBuilderVerbosity = mkOption { + type = enum [ "default" "info" "debug" @@ -207,7 +210,7 @@ in - `quiet` supresses all messages. - - `default` adds a simple "Installing Xen Hypervisor boot entries...done." message to the script. + - `default` adds a simple "Installing Xen Project Hypervisor boot entries...done." message to the script. - `info` is the same as `default`, but it also prints a diff with information on which generations were altered. - This option adds two extra dependencies to the script: `diffutils` and `bat`. @@ -218,11 +221,11 @@ in ''; }; - path = lib.options.mkOption { - type = lib.types.path; + path = mkOption { + type = path; default = "${cfg.package.boot}/${cfg.package.efi}"; - defaultText = lib.options.literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}"; - example = lib.options.literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi"; + defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}"; + example = literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi"; description = '' Path to xen.efi. `pkgs.xen` is patched to install the xen.efi file on `$boot/boot/xen.efi`, but an unpatched Xen build may install it @@ -234,10 +237,10 @@ in }; dom0Resources = { - maxVCPUs = lib.options.mkOption { + maxVCPUs = mkOption { default = 0; example = 4; - type = lib.types.ints.unsigned; + type = ints.unsigned; description = '' Amount of virtual CPU cores allocated to Domain 0 on boot. If set to 0, all cores are assigned to Domain 0, and @@ -245,10 +248,10 @@ in ''; }; - memory = lib.options.mkOption { + memory = mkOption { default = 0; example = 512; - type = lib.types.ints.unsigned; + type = ints.unsigned; description = '' Amount of memory (in MiB) allocated to Domain 0 on boot. If set to 0, all memory is assigned to Domain 0, and @@ -256,11 +259,11 @@ in ''; }; - maxMemory = lib.options.mkOption { + maxMemory = mkOption { default = cfg.dom0Resources.memory; - defaultText = lib.options.literalExpression "config.virtualisation.xen.dom0Resources.memory"; + defaultText = literalExpression "config.virtualisation.xen.dom0Resources.memory"; example = 1024; - type = lib.types.ints.unsigned; + type = ints.unsigned; description = '' Maximum amount of memory (in MiB) that Domain 0 can dynamically allocate to itself. Does nothing if set @@ -271,8 +274,8 @@ in }; domains = { - extraConfig = lib.options.mkOption { - type = lib.types.lines; + extraConfig = mkOption { + type = lines; default = ""; example = '' XENDOMAINS_SAVE=/persist/xen/save @@ -288,28 +291,28 @@ in }; store = { - path = lib.options.mkOption { - type = lib.types.path; + path = mkOption { + type = path; default = "${cfg.package}/bin/oxenstored"; - defaultText = lib.options.literalExpression "\${config.virtualisation.xen.package}/bin/oxenstored"; - example = lib.options.literalExpression "\${config.virtualisation.xen.package}/bin/xenstored"; + defaultText = literalExpression "\${config.virtualisation.xen.package}/bin/oxenstored"; + example = literalExpression "\${config.virtualisation.xen.package}/bin/xenstored"; description = '' Path to the Xen Store Daemon. This option is useful to switch between the legacy C-based Xen Store Daemon, and the newer OCaml-based Xen Store Daemon, `oxenstored`. ''; }; - type = lib.options.mkOption { - type = lib.types.enum [ + type = mkOption { + type = enum [ "c" "ocaml" ]; - default = if (lib.strings.hasSuffix "oxenstored" cfg.store.path) then "ocaml" else "c"; + default = if (hasSuffix "oxenstored" cfg.store.path) then "ocaml" else "c"; internal = true; readOnly = true; description = "Helper internal option that determines the type of the Xen Store Daemon based on cfg.store.path."; }; - settings = lib.options.mkOption { + settings = mkOption { default = { }; example = { enableMerge = false; @@ -324,34 +327,34 @@ in The OCaml-based Xen Store Daemon configuration. This option does nothing with the C-based `xenstored`. ''; - type = lib.types.submodule { + type = submodule { options = { - pidFile = lib.options.mkOption { + pidFile = mkOption { default = "/run/xen/xenstored.pid"; example = "/var/run/xen/xenstored.pid"; - type = lib.types.path; + type = path; description = "Path to the Xen Store Daemon PID file."; }; - testEAGAIN = lib.options.mkOption { + testEAGAIN = mkOption { default = cfg.debug; - defaultText = lib.options.literalExpression "config.virtualisation.xen.debug"; + defaultText = literalExpression "config.virtualisation.xen.debug"; example = true; - type = lib.types.bool; + type = bool; visible = false; description = "Randomly fail a transaction with EAGAIN. This option is used for debugging purposes only."; }; - enableMerge = lib.options.mkOption { + enableMerge = mkOption { default = true; example = false; - type = lib.types.bool; + type = bool; description = "Whether to enable transaction merge support."; }; conflict = { - burstLimit = lib.options.mkOption { + burstLimit = mkOption { default = 5.0; example = 15.0; - type = lib.types.addCheck ( - lib.types.float + type = addCheck ( + float // { name = "nonnegativeFloat"; description = "nonnegative floating point number, meaning >=0"; @@ -369,12 +372,12 @@ in domain's requests are ignored. ''; }; - maxHistorySeconds = lib.options.mkOption { + maxHistorySeconds = mkOption { default = 5.0e-2; example = 1.0; - type = lib.types.addCheck ( - lib.types.float // { description = "nonnegative floating point number, meaning >=0"; } - ) (n: n >= 0); + type = addCheck (float // { description = "nonnegative floating point number, meaning >=0"; }) ( + n: n >= 0 + ); description = '' Limits applied to domains whose writes cause other domains' transaction commits to fail. Must include decimal point. @@ -384,10 +387,10 @@ in is the minimum pause-time during which a domain will be ignored. ''; }; - rateLimitIsAggregate = lib.options.mkOption { + rateLimitIsAggregate = mkOption { default = true; example = false; - type = lib.types.bool; + type = bool; description = '' If the conflict.rateLimitIsAggregate option is `true`, then after each tick one point of conflict-credit is given to just one domain: the @@ -408,16 +411,16 @@ in }; }; perms = { - enable = lib.options.mkOption { + enable = mkOption { default = true; example = false; - type = lib.types.bool; + type = bool; description = "Whether to enable the node permission system."; }; - enableWatch = lib.options.mkOption { + enableWatch = mkOption { default = true; example = false; - type = lib.types.bool; + type = bool; description = '' Whether to enable the watch permission system. @@ -432,144 +435,142 @@ in }; }; quota = { - enable = lib.options.mkOption { + enable = mkOption { default = true; example = false; - type = lib.types.bool; + type = bool; description = "Whether to enable the quota system."; }; - maxEntity = lib.options.mkOption { + maxEntity = mkOption { default = 1000; example = 1024; - type = lib.types.ints.positive; + type = ints.positive; description = "Entity limit for transactions."; }; - maxSize = lib.options.mkOption { + maxSize = mkOption { default = 2048; example = 4096; - type = lib.types.ints.positive; + type = ints.positive; description = "Size limit for transactions."; }; - maxWatch = lib.options.mkOption { + maxWatch = mkOption { default = 100; example = 256; - type = lib.types.ints.positive; + type = ints.positive; description = "Maximum number of watches by the Xenstore Watchdog."; }; - transaction = lib.options.mkOption { + transaction = mkOption { default = 10; example = 50; - type = lib.types.ints.positive; + type = ints.positive; description = "Maximum number of transactions."; }; - maxRequests = lib.options.mkOption { + maxRequests = mkOption { default = 1024; example = 1024; - type = lib.types.ints.positive; + type = ints.positive; description = "Maximum number of requests per transaction."; }; - maxPath = lib.options.mkOption { + maxPath = mkOption { default = 1024; example = 1024; - type = lib.types.ints.positive; + type = ints.positive; description = "Path limit for the quota system."; }; - maxOutstanding = lib.options.mkOption { + maxOutstanding = mkOption { default = 1024; example = 1024; - type = lib.types.ints.positive; + type = ints.positive; description = "Maximum outstanding requests, i.e. in-flight requests / domain."; }; - maxWatchEvents = lib.options.mkOption { + maxWatchEvents = mkOption { default = 1024; example = 2048; - type = lib.types.ints.positive; + type = ints.positive; description = "Maximum number of outstanding watch events per watch."; }; }; - persistent = lib.options.mkOption { + persistent = mkOption { default = false; example = true; - type = lib.types.bool; + type = bool; description = "Whether to activate the filed base backend."; }; xenstored = { log = { - file = lib.options.mkOption { + file = mkOption { default = "/var/log/xen/xenstored.log"; example = "/dev/null"; - type = lib.types.path; + type = path; description = "Path to the Xen Store log file."; }; - level = lib.options.mkOption { + level = mkOption { default = if cfg.trace then "debug" else null; - defaultText = lib.options.literalExpression "if (config.virtualisation.xen.trace == true) then \"debug\" else null"; + defaultText = literalExpression "if (config.virtualisation.xen.trace == true) then \"debug\" else null"; example = "error"; - type = lib.types.nullOr ( - lib.types.enum [ - "debug" - "info" - "warn" - "error" - ] - ); + type = nullOr (enum [ + "debug" + "info" + "warn" + "error" + ]); description = "Logging level for the Xen Store."; }; # The hidden options below have no upstream documentation whatsoever. # The nb* options appear to alter the log rotation behaviour, and # the specialOps option appears to affect the Xenbus logging logic. - nbFiles = lib.options.mkOption { + nbFiles = mkOption { default = 10; example = 16; - type = lib.types.int; + type = int; visible = false; description = "Set `xenstored-log-nb-files`."; }; }; accessLog = { - file = lib.options.mkOption { + file = mkOption { default = "/var/log/xen/xenstored-access.log"; example = "/var/log/security/xenstored-access.log"; - type = lib.types.path; + type = path; description = "Path to the Xen Store access log file."; }; - nbLines = lib.options.mkOption { + nbLines = mkOption { default = 13215; example = 16384; - type = lib.types.int; + type = int; visible = false; description = "Set `access-log-nb-lines`."; }; - nbChars = lib.options.mkOption { + nbChars = mkOption { default = 180; example = 256; - type = lib.types.int; + type = int; visible = false; description = "Set `acesss-log-nb-chars`."; }; - specialOps = lib.options.mkOption { + specialOps = mkOption { default = false; example = true; - type = lib.types.bool; + type = bool; visible = false; description = "Set `access-log-special-ops`."; }; }; xenfs = { - kva = lib.options.mkOption { + kva = mkOption { default = "/proc/xen/xsd_kva"; example = cfg.store.settings.xenstored.xenfs.kva; - type = lib.types.path; + type = path; visible = false; description = '' Path to the Xen Store Daemon KVA location inside the XenFS pseudo-filesystem. While it is possible to alter this value, some drivers may be hardcoded to follow the default paths. ''; }; - port = lib.options.mkOption { + port = mkOption { default = "/proc/xen/xsd_port"; example = cfg.store.settings.xenstored.xenfs.port; - type = lib.types.path; + type = path; visible = false; description = '' Path to the Xen Store Daemon userspace port inside the XenFS pseudo-filesystem. @@ -578,11 +579,11 @@ in }; }; }; - ringScanInterval = lib.options.mkOption { + ringScanInterval = mkOption { default = 20; example = 30; - type = lib.types.addCheck ( - lib.types.int + type = addCheck ( + int // { name = "nonzeroInt"; description = "nonzero signed integer, meaning !=0"; @@ -602,10 +603,10 @@ in ## Implementation ## - config = lib.modules.mkIf cfg.enable { + config = mkIf cfg.enable { assertions = [ { - assertion = pkgs.stdenv.isx86_64; + assertion = pkgs.stdenv.hostPlatform.isx86_64; message = "Xen is currently not supported on ${pkgs.stdenv.hostPlatform.system}."; } { @@ -639,18 +640,18 @@ in ]; virtualisation.xen.bootParams = - lib.lists.optionals cfg.trace [ + optionals cfg.trace [ "loglvl=all" "guest_loglvl=all" ] ++ - lib.lists.optional (cfg.dom0Resources.memory != 0) + optional (cfg.dom0Resources.memory != 0) "dom0_mem=${toString cfg.dom0Resources.memory}M${ - lib.strings.optionalString ( + optionalString ( cfg.dom0Resources.memory != cfg.dom0Resources.maxMemory ) ",max:${toString cfg.dom0Resources.maxMemory}M" }" - ++ lib.lists.optional ( + ++ optional ( cfg.dom0Resources.maxVCPUs != 0 ) "dom0_max_vcpus=${toString cfg.dom0Resources.maxVCPUs}"; @@ -701,7 +702,7 @@ in # See the `xenBootBuilder` script in the main `let...in` statement of this file. loader.systemd-boot.extraInstallCommands = '' - ${lib.meta.getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity} + ${getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity} ''; }; @@ -744,7 +745,7 @@ in XENSTORED="${cfg.store.path}" QEMU_XEN="${cfg.qemu.package}/${cfg.qemu.package.qemu-system-i386}" - ${lib.strings.optionalString cfg.trace '' + ${optionalString cfg.trace '' XENSTORED_TRACE=yes XENCONSOLED_TRACE=all ''} @@ -756,10 +757,10 @@ in ''; } # The OCaml-based Xen Store Daemon requires /etc/xen/oxenstored.conf to start. - // lib.attrsets.optionalAttrs (cfg.store.type == "ocaml") { + // optionalAttrs (cfg.store.type == "ocaml") { "xen/oxenstored.conf".text = '' pid-file = ${cfg.store.settings.pidFile} - test-eagain = ${lib.trivial.boolToString cfg.store.settings.testEAGAIN} + test-eagain = ${boolToString cfg.store.settings.testEAGAIN} merge-activate = ${toString cfg.store.settings.enableMerge} conflict-burst-limit = ${toString cfg.store.settings.conflict.burstLimit} conflict-max-history-seconds = ${toString cfg.store.settings.conflict.maxHistorySeconds} @@ -775,7 +776,7 @@ in quota-path-max = ${toString cfg.store.settings.quota.maxPath} quota-maxoutstanding = ${toString cfg.store.settings.quota.maxOutstanding} quota-maxwatchevents = ${toString cfg.store.settings.quota.maxWatchEvents} - persistent = ${lib.trivial.boolToString cfg.store.settings.persistent} + persistent = ${boolToString cfg.store.settings.persistent} xenstored-log-file = ${cfg.store.settings.xenstored.log.file} xenstored-log-level = ${ if isNull cfg.store.settings.xenstored.log.level then @@ -787,7 +788,7 @@ in access-log-file = ${cfg.store.settings.xenstored.accessLog.file} access-log-nb-lines = ${toString cfg.store.settings.xenstored.accessLog.nbLines} acesss-log-nb-chars = ${toString cfg.store.settings.xenstored.accessLog.nbChars} - access-log-special-ops = ${lib.trivial.boolToString cfg.store.settings.xenstored.accessLog.specialOps} + access-log-special-ops = ${boolToString cfg.store.settings.xenstored.accessLog.specialOps} ring-scan-interval = ${toString cfg.store.settings.ringScanInterval} xenstored-kva = ${cfg.store.settings.xenstored.xenfs.kva} xenstored-port = ${cfg.store.settings.xenstored.xenfs.port} @@ -870,5 +871,5 @@ in }; }; }; - meta.maintainers = with lib.maintainers; [ sigmasquadron ]; + meta.maintainers = members; } diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index 0724d51b1971..06403e0fe81e 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -53,7 +53,6 @@ in rec { (onFullSupported "nixos.iso_plasma6") (onFullSupported "nixos.iso_gnome") (onFullSupported "nixos.manual") - (onSystems ["x86_64-linux"] "nixos.ova") (onSystems ["aarch64-linux"] "nixos.sd_image") (onFullSupported "nixos.tests.acme") (onSystems ["x86_64-linux"] "nixos.tests.boot.biosCdrom") diff --git a/nixos/release.nix b/nixos/release.nix index 8a8c45f8f842..67fb96740007 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -230,21 +230,6 @@ in rec { inherit system; }); - # A bootable VirtualBox virtual appliance as an OVA file (i.e. packaged OVF). - ova = forMatchingSystems [ "x86_64-linux" ] (system: - - with import ./.. { inherit system; }; - - hydraJob ((import lib/eval-config.nix { - inherit system; - modules = - [ versionModule - ./modules/installer/virtualbox-demo.nix - ]; - }).config.system.build.virtualBoxOVA) - - ); - # KVM image for proxmox in VMA format proxmoxImage = forMatchingSystems [ "x86_64-linux" ] (system: with import ./.. { inherit system; }; @@ -312,7 +297,7 @@ in rec { [ configuration versionModule ./maintainers/scripts/ec2/amazon-image.nix - ({ ... }: { amazonImage.sizeMB = "auto"; }) + ({ ... }: { virtualisation.diskSize = "auto"; }) ]; }).config.system.build.amazonImage) diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix index a4f00be887be..308032cccc45 100644 --- a/nixos/tests/acme.nix +++ b/nixos/tests/acme.nix @@ -407,7 +407,6 @@ in { '' import time - TOTAL_RETRIES = 20 @@ -428,6 +427,16 @@ in { return retries + 1 + def protect(self, func): + def wrapper(*args, retries: int = 0, **kwargs): + try: + return func(*args, **kwargs) + except Exception as err: + retries = self.handle_fail(retries, err.args) + return wrapper(*args, retries=retries, **kwargs) + + return wrapper + backoff = BackoffTracker() @@ -437,11 +446,13 @@ in { # quickly switch between derivations root_specs = "/tmp/specialisation" node.execute( - f"test -e {root_specs}" - f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}" + f"test -e {root_specs}" + f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}" ) - switcher_path = f"/run/current-system/specialisation/{name}/bin/switch-to-configuration" + switcher_path = ( + f"/run/current-system/specialisation/{name}/bin/switch-to-configuration" + ) rc, _ = node.execute(f"test -e '{switcher_path}'") if rc > 0: switcher_path = f"/tmp/specialisation/{name}/bin/switch-to-configuration" @@ -455,6 +466,15 @@ in { f"{switcher_path} test" ) + # Start a unit explicitly, then wait for it to activate. + # This is used for the acme-finished-* targets, as those + # aren't started by switch-to-configuration, meaning + # wait_for_unit(target) will fail with "no pending jobs" + # if it wins the race and checks the target state before + # the actual unit is started. + def start_and_wait(node, unit): + node.start_job(unit) + node.wait_for_unit(unit) # Ensures the issuer of our cert matches the chain # and matches the issuer we expect it to be. @@ -465,38 +485,45 @@ in { actual_issuer = node.succeed( f"openssl x509 -noout -issuer -in /var/lib/acme/{cert_name}/{fname}" ).partition("=")[2] - print(f"{fname} issuer: {actual_issuer}") - assert issuer.lower() in actual_issuer.lower() + assert ( + issuer.lower() in actual_issuer.lower() + ), f"{fname} issuer mismatch. Expected {issuer} got {actual_issuer}" # Ensure cert comes before chain in fullchain.pem def check_fullchain(node, cert_name): - subject_data = node.succeed( - f"openssl crl2pkcs7 -nocrl -certfile /var/lib/acme/{cert_name}/fullchain.pem" - " | openssl pkcs7 -print_certs -noout" + cert_file = f"/var/lib/acme/{cert_name}/fullchain.pem" + num_certs = node.succeed(f"grep -o 'END CERTIFICATE' {cert_file}") + assert len(num_certs.strip().split("\n")) > 1, "Insufficient certs in fullchain.pem" + + first_cert_data = node.succeed( + f"grep -m1 -B50 'END CERTIFICATE' {cert_file}" + " | openssl x509 -noout -text" ) - for line in subject_data.lower().split("\n"): - if "subject" in line: - print(f"First subject in fullchain.pem: {line}") - assert cert_name.lower() in line + for line in first_cert_data.lower().split("\n"): + if "dns:" in line: + print(f"First DNSName in fullchain.pem: {line}") + assert cert_name.lower() in line, f"{cert_name} not found in {line}" return assert False - def check_connection(node, domain, retries=0): + @backoff.protect + def check_connection(node, domain): result = node.succeed( "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt" f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1" ) for line in result.lower().split("\n"): - if "verification" in line and "error" in line: - retries = backoff.handle_fail(retries, f"Failed to connect to https://{domain}") - return check_connection(node, domain, retries) + assert not ( + "verification" in line and "error" in line + ), f"Failed to connect to https://{domain}" - def check_connection_key_bits(node, domain, bits, retries=0): + @backoff.protect + def check_connection_key_bits(node, domain, bits): result = node.succeed( "openssl s_client -CAfile /tmp/ca.crt" f" -servername {domain} -connect {domain}:443 < /dev/null" @@ -504,12 +531,11 @@ in { ) print("Key type:", result) - if bits not in result: - retries = backoff.handle_fail(retries, f"Did not find expected number of bits ({bits}) in key") - return check_connection_key_bits(node, domain, bits, retries) + assert bits in result, f"Did not find expected number of bits ({bits}) in key" - def check_stapling(node, domain, retries=0): + @backoff.protect + def check_stapling(node, domain): # Pebble doesn't provide a full OCSP responder, so just check the URL result = node.succeed( "openssl s_client -CAfile /tmp/ca.crt" @@ -518,20 +544,20 @@ in { ) print("OCSP Responder URL:", result) - if "${caDomain}:4002" not in result.lower(): - retries = backoff.handle_fail(retries, "OCSP Stapling check failed") - return check_stapling(node, domain, retries) + assert "${caDomain}:4002" in result.lower(), "OCSP Stapling check failed" - def download_ca_certs(node, retries=0): - exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt") - exit_code_2, _ = node.execute( - "curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt" - ) + @backoff.protect + def download_ca_certs(node): + node.succeed("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt") + node.succeed("curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt") + - if exit_code + exit_code_2 > 0: - retries = backoff.handle_fail(retries, "Failed to connect to pebble to download root CA certs") - return download_ca_certs(node, retries) + @backoff.protect + def set_a_record(node): + node.succeed( + 'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a' + ) start_all() @@ -539,9 +565,7 @@ in { dnsserver.wait_for_unit("pebble-challtestsrv.service") client.wait_for_unit("default.target") - client.succeed( - 'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a' - ) + set_a_record(client) acme.systemctl("start network-online.target") acme.wait_for_unit("network-online.target") @@ -552,7 +576,7 @@ in { # Perform http-01 w/ lego test first with subtest("Can request certificate with Lego's built in web server"): switch_to(webserver, "http01lego") - webserver.wait_for_unit("acme-finished-http.example.test.target") + start_and_wait(webserver, "acme-finished-http.example.test.target") check_fullchain(webserver, "http.example.test") check_issuer(webserver, "http.example.test", "pebble") @@ -566,7 +590,7 @@ in { with subtest("Can renew certificates when they expire"): hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") switch_to(webserver, "renew") - webserver.wait_for_unit("acme-finished-http.example.test.target") + start_and_wait(webserver, "acme-finished-http.example.test.target") check_fullchain(webserver, "http.example.test") check_issuer(webserver, "http.example.test", "pebble") hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") @@ -576,7 +600,7 @@ in { with subtest("Handles email change correctly"): hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") switch_to(webserver, "accountchange") - webserver.wait_for_unit("acme-finished-http.example.test.target") + start_and_wait(webserver, "acme-finished-http.example.test.target") check_fullchain(webserver, "http.example.test") check_issuer(webserver, "http.example.test", "pebble") hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem") @@ -587,15 +611,15 @@ in { switch_to(webserver, "general") with subtest("Can request certificate with HTTP-01 challenge"): - webserver.wait_for_unit("acme-finished-a.example.test.target") + start_and_wait(webserver, "acme-finished-a.example.test.target") check_fullchain(webserver, "a.example.test") check_issuer(webserver, "a.example.test", "pebble") webserver.wait_for_unit("nginx.service") check_connection(client, "a.example.test") with subtest("Runs 1 cert for account creation before others"): - webserver.wait_for_unit("acme-finished-b.example.test.target") - webserver.wait_for_unit("acme-finished-c.example.test.target") + start_and_wait(webserver, "acme-finished-b.example.test.target") + start_and_wait(webserver, "acme-finished-c.example.test.target") check_connection(client, "b.example.test") check_connection(client, "c.example.test") @@ -630,38 +654,38 @@ in { with subtest("Correctly implements OCSP stapling"): switch_to(webserver, "ocsp_stapling") - webserver.wait_for_unit("acme-finished-a.example.test.target") + start_and_wait(webserver, "acme-finished-a.example.test.target") check_stapling(client, "a.example.test") with subtest("Can request certificate with HTTP-01 using lego's internal web server"): switch_to(webserver, "lego_server") - webserver.wait_for_unit("acme-finished-lego.example.test.target") + start_and_wait(webserver, "acme-finished-lego.example.test.target") webserver.wait_for_unit("nginx.service") webserver.succeed("echo HENLO && systemctl cat nginx.service") - webserver.succeed("test \"$(stat -c '%U' /var/lib/acme/* | uniq)\" = \"root\"") + webserver.succeed('test "$(stat -c \'%U\' /var/lib/acme/* | uniq)" = "root"') check_connection(client, "a.example.test") check_connection(client, "lego.example.test") with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"): webserver.execute("systemctl stop nginx") switch_to(webserver, "slow_startup") - webserver.wait_for_unit("acme-finished-slow.example.test.target") + start_and_wait(webserver, "acme-finished-slow.example.test.target") check_issuer(webserver, "slow.example.test", "pebble") webserver.wait_for_unit("nginx.service") check_connection(client, "slow.example.test") with subtest("Can limit concurrency of running renewals"): switch_to(webserver, "concurrency_limit") - webserver.wait_for_unit("acme-finished-f.example.test.target") - webserver.wait_for_unit("acme-finished-g.example.test.target") - webserver.wait_for_unit("acme-finished-h.example.test.target") + start_and_wait(webserver, "acme-finished-f.example.test.target") + start_and_wait(webserver, "acme-finished-g.example.test.target") + start_and_wait(webserver, "acme-finished-h.example.test.target") check_connection(client, "f.example.test") check_connection(client, "g.example.test") check_connection(client, "h.example.test") with subtest("Works with caddy"): switch_to(webserver, "caddy") - webserver.wait_for_unit("acme-finished-example.test.target") + start_and_wait(webserver, "acme-finished-example.test.target") webserver.wait_for_unit("caddy.service") # FIXME reloading caddy is not sufficient to load new certs. # Restart it manually until this is fixed. @@ -670,7 +694,7 @@ in { with subtest("security.acme changes reflect on caddy"): switch_to(webserver, "caddy_change_acme_conf") - webserver.wait_for_unit("acme-finished-example.test.target") + start_and_wait(webserver, "acme-finished-example.test.target") webserver.wait_for_unit("caddy.service") # FIXME reloading caddy is not sufficient to load new certs. # Restart it manually until this is fixed. @@ -688,7 +712,8 @@ in { switch_to(webserver, server) for domain in domains: if domain != "wildcard": - webserver.wait_for_unit( + start_and_wait( + webserver, f"acme-finished-{server}-{domain}.example.test.target" ) except Exception as err: @@ -722,7 +747,8 @@ in { with subtest("Can remove an alias from a domain + cert is updated"): test_alias = f"{server}-{domains[0]}-alias.example.test" switch_to(webserver, f"{server}_remove_alias") - webserver.wait_for_unit(f"acme-finished-{test_domain}.target") + wait_for_server() + start_and_wait(webserver, f"acme-finished-{test_domain}.target") wait_for_server() check_connection(client, test_domain) rc, _s = client.execute( @@ -737,7 +763,7 @@ in { switch_to(webserver, server) wait_for_server() switch_to(webserver, f"{server}_change_acme_conf") - webserver.wait_for_unit(f"acme-finished-{test_domain}.target") + start_and_wait(webserver, f"acme-finished-{test_domain}.target") wait_for_server() check_connection_key_bits(client, test_domain, "384") @@ -748,7 +774,7 @@ in { switch_to(webserver, "http01lego_legacyAccountHash", allow_fail=True) # unit is failed, but in a way that this throws no exception: try: - webserver.wait_for_unit("acme-finished-http.example.test.target") + start_and_wait(webserver, "acme-finished-http.example.test.target") except Exception: # The unit is allowed – or even expected – to fail due to not being able to # reach the actual letsencrypt server. We only use it for serialising the diff --git a/nixos/tests/activation/etc-overlay-immutable.nix b/nixos/tests/activation/etc-overlay-immutable.nix index 6d56db43f0b2..882bc651841a 100644 --- a/nixos/tests/activation/etc-overlay-immutable.nix +++ b/nixos/tests/activation/etc-overlay-immutable.nix @@ -15,6 +15,10 @@ boot.kernelPackages = pkgs.linuxPackages_latest; time.timeZone = "Utc"; + # The standard resolvconf service tries to write to /etc and crashes, + # which makes nixos-rebuild exit uncleanly when switching into the new generation + services.resolved.enable = true; + environment.etc = { "mountpoint/.keep".text = "keep"; "filemount".text = "keep"; @@ -23,9 +27,21 @@ specialisation.new-generation.configuration = { environment.etc."newgen".text = "newgen"; }; + specialisation.newer-generation.configuration = { + environment.etc."newergen".text = "newergen"; + }; }; - testScript = '' + testScript = /* python */ '' + newergen = machine.succeed("realpath /run/current-system/specialisation/newer-generation/bin/switch-to-configuration").rstrip() + + with subtest("/run/etc-metadata/ is mounted"): + print(machine.succeed("mountpoint /run/etc-metadata")) + + with subtest("No temporary files leaked into stage 2"): + machine.succeed("[ ! -e /etc-metadata-image ]") + machine.succeed("[ ! -e /etc-basedir ]") + with subtest("/etc is mounted as an overlay"): machine.succeed("findmnt --kernel --type overlay /etc") @@ -50,6 +66,9 @@ with subtest("switching to the same generation"): machine.succeed("/run/current-system/bin/switch-to-configuration test") + with subtest("the initrd didn't get rebuilt"): + machine.succeed("test /run/current-system/initrd -ef /run/current-system/specialisation/new-generation/initrd") + with subtest("switching to a new generation"): machine.fail("stat /etc/newgen") @@ -65,5 +84,13 @@ print(machine.succeed("ls /etc/mountpoint")) print(machine.succeed("stat /etc/mountpoint/extra-file")) print(machine.succeed("findmnt /etc/filemount")) + + machine.succeed(f"{newergen} switch") + + tmpMounts = machine.succeed("find /tmp -maxdepth 1 -type d -regex '/tmp/nixos-etc\\..*' | wc -l").rstrip() + metaMounts = machine.succeed("find /tmp -maxdepth 1 -type d -regex '/tmp/nixos-etc-metadata\\..*' | wc -l").rstrip() + + assert tmpMounts == "0", f"Found {tmpMounts} remaining tmpmounts" + assert metaMounts == "1", f"Found {metaMounts} remaining metamounts" ''; } diff --git a/nixos/tests/activation/etc-overlay-mutable.nix b/nixos/tests/activation/etc-overlay-mutable.nix index 8561ff7fd230..ef465e3616c3 100644 --- a/nixos/tests/activation/etc-overlay-mutable.nix +++ b/nixos/tests/activation/etc-overlay-mutable.nix @@ -15,15 +15,30 @@ specialisation.new-generation.configuration = { environment.etc."newgen".text = "newgen"; }; + specialisation.newer-generation.configuration = { + environment.etc."newergen".text = "newergen"; + }; }; - testScript = '' + testScript = /* python */ '' + newergen = machine.succeed("realpath /run/current-system/specialisation/newer-generation/bin/switch-to-configuration").rstrip() + + with subtest("/run/etc-metadata/ is mounted"): + print(machine.succeed("mountpoint /run/etc-metadata")) + + with subtest("No temporary files leaked into stage 2"): + machine.succeed("[ ! -e /etc-metadata-image ]") + machine.succeed("[ ! -e /etc-basedir ]") + with subtest("/etc is mounted as an overlay"): machine.succeed("findmnt --kernel --type overlay /etc") with subtest("switching to the same generation"): machine.succeed("/run/current-system/bin/switch-to-configuration test") + with subtest("the initrd didn't get rebuilt"): + machine.succeed("test /run/current-system/initrd -ef /run/current-system/specialisation/new-generation/initrd") + with subtest("switching to a new generation"): machine.fail("stat /etc/newgen") machine.succeed("echo -n 'mutable' > /etc/mutable") @@ -45,5 +60,14 @@ print(machine.succeed("findmnt /etc/mountpoint")) print(machine.succeed("stat /etc/mountpoint/extra-file")) print(machine.succeed("findmnt /etc/filemount")) + + machine.succeed(f"{newergen} switch") + assert machine.succeed("cat /etc/newergen") == "newergen" + + tmpMounts = machine.succeed("find /tmp -maxdepth 1 -type d -regex '/tmp/nixos-etc\\..*' | wc -l").rstrip() + metaMounts = machine.succeed("find /tmp -maxdepth 1 -type d -regex '/tmp/nixos-etc-metadata\\..*' | wc -l").rstrip() + + assert tmpMounts == "0", f"Found {tmpMounts} remaining tmpmounts" + assert metaMounts == "1", f"Found {metaMounts} remaining metamounts" ''; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index a625cd92e236..37e005f128a2 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -136,6 +136,7 @@ in { artalk = handleTest ./artalk.nix {}; atd = handleTest ./atd.nix {}; atop = handleTest ./atop.nix {}; + atticd = runTest ./atticd.nix; atuin = handleTest ./atuin.nix {}; audiobookshelf = handleTest ./audiobookshelf.nix {}; auth-mysql = handleTest ./auth-mysql.nix {}; @@ -244,6 +245,7 @@ in { curl-impersonate = handleTest ./curl-impersonate.nix {}; custom-ca = handleTest ./custom-ca.nix {}; croc = handleTest ./croc.nix {}; + cyrus-imap = runTest ./cyrus-imap.nix; darling = handleTest ./darling.nix {}; darling-dmg = runTest ./darling-dmg.nix; dae = handleTest ./dae.nix {}; @@ -322,6 +324,7 @@ in { fancontrol = handleTest ./fancontrol.nix {}; fanout = handleTest ./fanout.nix {}; fcitx5 = handleTest ./fcitx5 {}; + fedimintd = runTest ./fedimintd.nix; fenics = handleTest ./fenics.nix {}; ferm = handleTest ./ferm.nix {}; ferretdb = handleTest ./ferretdb.nix {}; @@ -367,6 +370,7 @@ in { mimir = handleTest ./mimir.nix {}; gancio = handleTest ./gancio.nix {}; garage = handleTest ./garage {}; + gatus = runTest ./gatus.nix; gemstash = handleTest ./gemstash.nix {}; geoserver = runTest ./geoserver.nix; gerrit = handleTest ./gerrit.nix {}; @@ -436,6 +440,7 @@ in { pyload = handleTest ./pyload.nix {}; oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; odoo = handleTest ./odoo.nix {}; + odoo17 = handleTest ./odoo.nix { package = pkgs.odoo17; }; odoo16 = handleTest ./odoo.nix { package = pkgs.odoo16; }; odoo15 = handleTest ./odoo.nix { package = pkgs.odoo15; }; # 9pnet_virtio used to mount /nix partition doesn't support @@ -455,6 +460,7 @@ in { icingaweb2 = handleTest ./icingaweb2.nix {}; ifm = handleTest ./ifm.nix {}; iftop = handleTest ./iftop.nix {}; + immich = handleTest ./web-apps/immich.nix {}; incron = handleTest ./incron.nix {}; incus = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; inherit (pkgs) incus; }); incus-lts = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; }); @@ -653,10 +659,13 @@ in { networking.networkmanager = handleTest ./networking/networkmanager.nix {}; netbox_3_6 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_6; }; netbox_3_7 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_7; }; + netbox_4_0 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_4_0; }; + netbox_4_1 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_4_1; }; netbox-upgrade = handleTest ./web-apps/netbox-upgrade.nix {}; # TODO: put in networking.nix after the test becomes more complete networkingProxy = handleTest ./networking-proxy.nix {}; nextcloud = handleTest ./nextcloud {}; + nextflow = handleTestOn ["x86_64-linux"] ./nextflow.nix {}; nextjs-ollama-llm-ui = runTest ./web-apps/nextjs-ollama-llm-ui.nix; nexus = handleTest ./nexus.nix {}; # TODO: Test nfsv3 + Kerberos @@ -669,6 +678,7 @@ in { nginx-etag-compression = handleTest ./nginx-etag-compression.nix {}; nginx-globalredirect = handleTest ./nginx-globalredirect.nix {}; nginx-http3 = handleTest ./nginx-http3.nix {}; + nginx-mime = handleTest ./nginx-mime.nix {}; nginx-modsecurity = handleTest ./nginx-modsecurity.nix {}; nginx-moreheaders = handleTest ./nginx-moreheaders.nix {}; nginx-njs = handleTest ./nginx-njs.nix {}; @@ -692,8 +702,8 @@ in { nixops = handleTest ./nixops/default.nix {}; nixos-generate-config = handleTest ./nixos-generate-config.nix {}; nixos-rebuild-install-bootloader = handleTestOn ["x86_64-linux"] ./nixos-rebuild-install-bootloader.nix {}; - nixos-rebuild-specialisations = handleTestOn ["x86_64-linux"] ./nixos-rebuild-specialisations.nix {}; - nixos-rebuild-target-host = handleTest ./nixos-rebuild-target-host.nix {}; + nixos-rebuild-specialisations = runTestOn ["x86_64-linux"] ./nixos-rebuild-specialisations.nix; + nixos-rebuild-target-host = runTest ./nixos-rebuild-target-host.nix; nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; }; nixseparatedebuginfod = handleTest ./nixseparatedebuginfod.nix {}; node-red = handleTest ./node-red.nix {}; @@ -708,6 +718,7 @@ in { nsd = handleTest ./nsd.nix {}; ntfy-sh = handleTest ./ntfy-sh.nix {}; ntfy-sh-migration = handleTest ./ntfy-sh-migration.nix {}; + ntpd = handleTest ./ntpd.nix {}; ntpd-rs = handleTest ./ntpd-rs.nix {}; nvidia-container-toolkit = runTest ./nvidia-container-toolkit.nix; nvmetcfg = handleTest ./nvmetcfg.nix {}; @@ -715,6 +726,7 @@ in { nzbhydra2 = handleTest ./nzbhydra2.nix {}; ocis = handleTest ./ocis.nix {}; oddjobd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./oddjobd.nix {}; + obs-studio = handleTest ./obs-studio.nix {}; oh-my-zsh = handleTest ./oh-my-zsh.nix {}; ollama = runTest ./ollama.nix; ollama-cuda = runTestOn ["x86_64-linux" "aarch64-linux"] ./ollama-cuda.nix; @@ -739,6 +751,7 @@ in { image-contents = handleTest ./image-contents.nix {}; openvscode-server = handleTest ./openvscode-server.nix {}; open-webui = runTest ./open-webui.nix; + openvswitch = runTest ./openvswitch.nix; orangefs = handleTest ./orangefs.nix {}; os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {}; osquery = handleTestOn ["x86_64-linux"] ./osquery.nix {}; @@ -762,13 +775,10 @@ in { peering-manager = handleTest ./web-apps/peering-manager.nix {}; peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {}; peroxide = handleTest ./peroxide.nix {}; - pg_anonymizer = handleTest ./pg_anonymizer.nix {}; pgadmin4 = handleTest ./pgadmin4.nix {}; pgbouncer = handleTest ./pgbouncer.nix {}; pghero = runTest ./pghero.nix; - pgjwt = handleTest ./pgjwt.nix {}; pgmanage = handleTest ./pgmanage.nix {}; - pgvecto-rs = handleTest ./pgvecto-rs.nix {}; phosh = handleTest ./phosh.nix {}; photonvision = handleTest ./photonvision.nix {}; photoprism = handleTest ./photoprism.nix {}; @@ -801,13 +811,7 @@ in { postfix = handleTest ./postfix.nix {}; postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {}; postfixadmin = handleTest ./postfixadmin.nix {}; - postgis = handleTest ./postgis.nix {}; - apache_datasketches = handleTest ./apache_datasketches.nix {}; - postgresql = handleTest ./postgresql.nix {}; - postgresql-jit = handleTest ./postgresql-jit.nix {}; - postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {}; - postgresql-tls-client-cert = handleTest ./postgresql-tls-client-cert.nix {}; - postgresql-wal2json = handleTest ./postgresql-wal2json.nix {}; + postgresql = handleTest ./postgresql {}; powerdns = handleTest ./powerdns.nix {}; powerdns-admin = handleTest ./powerdns-admin.nix {}; power-profiles-daemon = handleTest ./power-profiles-daemon.nix {}; @@ -815,9 +819,12 @@ in { predictable-interface-names = handleTest ./predictable-interface-names.nix {}; pretalx = runTest ./web-apps/pretalx.nix; pretix = runTest ./web-apps/pretix.nix; - printing-socket = handleTest ./printing.nix { socket = true; }; - printing-service = handleTest ./printing.nix { socket = false; }; + printing-socket = handleTest ./printing.nix { socket = true; listenTcp = true; }; + printing-service = handleTest ./printing.nix { socket = false; listenTcp = true; }; + printing-socket-notcp = handleTest ./printing.nix { socket = true; listenTcp = false; }; + printing-service-notcp = handleTest ./printing.nix { socket = false; listenTcp = false; }; private-gpt = handleTest ./private-gpt.nix {}; + privatebin = runTest ./privatebin.nix; privoxy = handleTest ./privoxy.nix {}; prometheus = handleTest ./prometheus {}; prometheus-exporters = handleTest ./prometheus-exporters.nix {}; @@ -835,8 +842,8 @@ in { qemu-vm-volatile-root = runTest ./qemu-vm-volatile-root.nix; qemu-vm-external-disk-image = runTest ./qemu-vm-external-disk-image.nix; qemu-vm-store = runTest ./qemu-vm-store.nix; - qgis = handleTest ./qgis.nix { qgisPackage = pkgs.qgis; }; - qgis-ltr = handleTest ./qgis.nix { qgisPackage = pkgs.qgis-ltr; }; + qgis = handleTest ./qgis.nix { package = pkgs.qgis; }; + qgis-ltr = handleTest ./qgis.nix { package = pkgs.qgis-ltr; }; qownnotes = handleTest ./qownnotes.nix {}; qtile = handleTestOn ["x86_64-linux" "aarch64-linux"] ./qtile/default.nix {}; quake3 = handleTest ./quake3.nix {}; @@ -854,8 +861,9 @@ in { realm = handleTest ./realm.nix {}; redis = handleTest ./redis.nix {}; redlib = handleTest ./redlib.nix {}; - redmine = handleTest ./redmine.nix {}; + redmine = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./redmine.nix {}; renovate = handleTest ./renovate.nix {}; + replace-dependencies = handleTest ./replace-dependencies {}; restartByActivationScript = handleTest ./restart-by-activation-script.nix {}; restic-rest-server = handleTest ./restic-rest-server.nix {}; restic = handleTest ./restic.nix {}; @@ -868,6 +876,7 @@ in { rshim = handleTest ./rshim.nix {}; rspamd = handleTest ./rspamd.nix {}; rspamd-trainer = handleTest ./rspamd-trainer.nix {}; + rss-bridge = handleTest ./web-apps/rss-bridge.nix {}; rss2email = handleTest ./rss2email.nix {}; rstudio-server = handleTest ./rstudio-server.nix {}; rsyncd = handleTest ./rsyncd.nix {}; @@ -879,6 +888,7 @@ in { samba-wsdd = handleTest ./samba-wsdd.nix {}; sane = handleTest ./sane.nix {}; sanoid = handleTest ./sanoid.nix {}; + saunafs = handleTest ./saunafs.nix {}; scaphandre = handleTest ./scaphandre.nix {}; schleuder = handleTest ./schleuder.nix {}; scion-freestanding-deployment = handleTest ./scion/freestanding-deployment {}; @@ -887,6 +897,7 @@ in { seafile = handleTest ./seafile.nix {}; searx = runTest ./searx.nix; seatd = handleTest ./seatd.nix {}; + send = runTest ./send.nix; service-runner = handleTest ./service-runner.nix {}; sftpgo = runTest ./sftpgo.nix; sfxr-qt = handleTest ./sfxr-qt.nix {}; @@ -916,6 +927,7 @@ in { sourcehut = handleTest ./sourcehut {}; spacecookie = handleTest ./spacecookie.nix {}; spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {}; + spiped = runTest ./spiped.nix; sqlite3-to-mysql = handleTest ./sqlite3-to-mysql.nix {}; sslh = handleTest ./sslh.nix {}; ssh-agent-auth = handleTest ./ssh-agent-auth.nix {}; @@ -934,10 +946,12 @@ in { sudo = handleTest ./sudo.nix {}; sudo-rs = handleTest ./sudo-rs.nix {}; sunshine = handleTest ./sunshine.nix {}; + suricata = handleTest ./suricata.nix {}; suwayomi-server = handleTest ./suwayomi-server.nix {}; swap-file-btrfs = handleTest ./swap-file-btrfs.nix {}; swap-partition = handleTest ./swap-partition.nix {}; swap-random-encryption = handleTest ./swap-random-encryption.nix {}; + swapspace = handleTestOn ["aarch64-linux" "x86_64-linux"] ./swapspace.nix {}; sway = handleTest ./sway.nix {}; swayfx = handleTest ./swayfx.nix {}; switchTest = handleTest ./switch-test.nix { ng = false; }; @@ -1024,8 +1038,8 @@ in { tiddlywiki = handleTest ./tiddlywiki.nix {}; tigervnc = handleTest ./tigervnc.nix {}; tika = runTest ./tika.nix; - timescaledb = handleTest ./timescaledb.nix {}; timezone = handleTest ./timezone.nix {}; + timidity = handleTestOn ["aarch64-linux" "x86_64-linux"] ./timidity {}; tinc = handleTest ./tinc {}; tinydns = handleTest ./tinydns.nix {}; tinyproxy = handleTest ./tinyproxy.nix {}; @@ -1043,7 +1057,6 @@ in { trezord = handleTest ./trezord.nix {}; trickster = handleTest ./trickster.nix {}; trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {}; - tsja = handleTest ./tsja.nix {}; tsm-client-gui = handleTest ./tsm-client-gui.nix {}; ttyd = handleTest ./web-servers/ttyd.nix {}; txredisapi = handleTest ./txredisapi.nix {}; @@ -1059,6 +1072,7 @@ in { unbound = handleTest ./unbound.nix {}; unifi = handleTest ./unifi.nix {}; unit-php = handleTest ./web-servers/unit-php.nix {}; + unit-perl = handleTest ./web-servers/unit-perl.nix {}; upnp.iptables = handleTest ./upnp.nix { useNftables = false; }; upnp.nftables = handleTest ./upnp.nix { useNftables = true; }; uptermd = handleTest ./uptermd.nix {}; @@ -1077,7 +1091,6 @@ in { uwsgi = handleTest ./uwsgi.nix {}; v2ray = handleTest ./v2ray.nix {}; varnish60 = handleTest ./varnish.nix { package = pkgs.varnish60; }; - varnish74 = handleTest ./varnish.nix { package = pkgs.varnish74; }; varnish75 = handleTest ./varnish.nix { package = pkgs.varnish75; }; vault = handleTest ./vault.nix {}; vault-agent = handleTest ./vault-agent.nix {}; @@ -1092,6 +1105,7 @@ in { vscode-remote-ssh = handleTestOn ["x86_64-linux"] ./vscode-remote-ssh.nix {}; vscodium = discoverTests (import ./vscodium.nix); vsftpd = handleTest ./vsftpd.nix {}; + wakapi = handleTest ./wakapi.nix {}; warzone2100 = handleTest ./warzone2100.nix {}; wasabibackend = handleTest ./wasabibackend.nix {}; wastebin = handleTest ./wastebin.nix {}; diff --git a/nixos/tests/amazon-init-shell.nix b/nixos/tests/amazon-init-shell.nix index 3c040841b6d2..da4480ed5778 100644 --- a/nixos/tests/amazon-init-shell.nix +++ b/nixos/tests/amazon-init-shell.nix @@ -18,16 +18,21 @@ makeTest { meta = with maintainers; { maintainers = [ urbas ]; }; - nodes.machine = { ... }: + nodes.machine = { lib, pkgs, ... }: { imports = [ ../modules/profiles/headless.nix ../modules/virtualisation/amazon-init.nix ]; services.openssh.enable = true; + system.switch.enable = true; networking.hostName = ""; environment.etc."ec2-metadata/user-data" = { text = '' #!/usr/bin/bash echo successful > /tmp/evidence + + # Emulate running nixos-rebuild switch, just without any building. + # https://github.com/nixos/nixpkgs/blob/4c62505847d88f16df11eff3c81bf9a453a4979e/nixos/modules/virtualisation/amazon-init.nix#L55 + /run/current-system/bin/switch-to-configuration test ''; }; }; diff --git a/nixos/tests/apache_datasketches.nix b/nixos/tests/apache_datasketches.nix deleted file mode 100644 index 2bf099ac7991..000000000000 --- a/nixos/tests/apache_datasketches.nix +++ /dev/null @@ -1,29 +0,0 @@ -import ./make-test-python.nix ({ pkgs, ...} : { - name = "postgis"; - meta = with pkgs.lib.maintainers; { - maintainers = [ lsix ]; # TODO: Who's the maintener now? - }; - - nodes = { - master = - { pkgs, ... }: - - { - services.postgresql = let mypg = pkgs.postgresql_15; in { - enable = true; - package = mypg; - extraPlugins = with mypg.pkgs; [ - apache_datasketches - ]; - }; - }; - }; - - testScript = '' - start_all() - master.wait_for_unit("postgresql") - master.sleep(10) # Hopefully this is long enough!! - master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION datasketches;'") - master.succeed("sudo -u postgres psql -c 'SELECT hll_sketch_to_string(hll_sketch_build(1));'") - ''; -}) diff --git a/nixos/tests/apfs.nix b/nixos/tests/apfs.nix index 15ed5aa7f573..3e79fa11dc04 100644 --- a/nixos/tests/apfs.nix +++ b/nixos/tests/apfs.nix @@ -18,6 +18,10 @@ with subtest("mkapfs works with the maximum label length"): machine.succeed("mkapfs -L '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7' /dev/vdb") + with subtest("apfs-label works"): + machine.succeed("mkapfs -L 'myLabel' /dev/vdb") + machine.succeed("apfs-label /dev/vdb | grep -q myLabel") + with subtest("Enable case sensitivity and normalization sensitivity"): machine.succeed( "mkapfs -s -z /dev/vdb", diff --git a/nixos/tests/atticd.nix b/nixos/tests/atticd.nix new file mode 100644 index 000000000000..4193d75d2a92 --- /dev/null +++ b/nixos/tests/atticd.nix @@ -0,0 +1,92 @@ +{ lib, pkgs, ... }: + +let + accessKey = "BKIKJAA5BMMU2RHO6IBB"; + secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12"; + + minioCredentialsFile = pkgs.writeText "minio-credentials-full" '' + MINIO_ROOT_USER=${accessKey} + MINIO_ROOT_PASSWORD=${secretKey} + ''; + environmentFile = pkgs.runCommand "atticd-env" { } '' + echo ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="$(${lib.getExe pkgs.openssl} genrsa -traditional 4096 | ${pkgs.coreutils}/bin/base64 -w0)" > $out + ''; +in + +{ + name = "atticd"; + + nodes = { + local = { + services.atticd = { + enable = true; + + inherit environmentFile; + }; + + environment.systemPackages = [ + pkgs.attic-client + ]; + }; + + s3 = { + services.atticd = { + enable = true; + settings = { + storage = { + type = "s3"; + bucket = "attic"; + region = "us-east-1"; + endpoint = "http://127.0.0.1:9000"; + + credentials = { + access_key_id = accessKey; + secret_access_key = secretKey; + }; + }; + }; + + inherit environmentFile; + }; + + services.minio = { + enable = true; + rootCredentialsFile = minioCredentialsFile; + }; + + environment.systemPackages = [ + pkgs.attic-client + pkgs.minio-client + ]; + }; + }; + + testScript = # python + '' + start_all() + + with subtest("local storage push"): + local.wait_for_unit("atticd.service") + token = local.succeed("atticd-atticadm make-token --sub stop --validity 1y --create-cache '*' --pull '*' --push '*' --delete '*' --configure-cache '*' --configure-cache-retention '*'").strip() + + local.succeed(f"attic login local http://localhost:8080 {token}") + local.succeed("attic cache create test-cache") + local.succeed("attic push test-cache ${environmentFile}") + + with subtest("s3 storage push"): + s3.wait_for_unit("atticd.service") + s3.wait_for_unit("minio.service") + s3.wait_for_open_port(9000) + s3.succeed( + "mc config host add minio " + + "http://localhost:9000 " + + "${accessKey} ${secretKey} --api s3v4", + "mc mb minio/attic", + ) + token = s3.succeed("atticd-atticadm make-token --sub stop --validity 1y --create-cache '*' --pull '*' --push '*' --delete '*' --configure-cache '*' --configure-cache-retention '*'").strip() + + s3.succeed(f"attic login s3 http://localhost:8080 {token}") + s3.succeed("attic cache create test-cache") + s3.succeed("attic push test-cache ${environmentFile}") + ''; +} diff --git a/nixos/tests/avahi.nix b/nixos/tests/avahi.nix index 4ae2f919f2f7..7a2d4bbd0ffc 100644 --- a/nixos/tests/avahi.nix +++ b/nixos/tests/avahi.nix @@ -75,5 +75,7 @@ import ./make-test-python.nix { one.succeed("test `wc -l < out` -gt 0") two.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2") two.succeed("test `wc -l < out` -gt 0") + + one.log(one.execute("systemd-analyze security avahi-daemon.service | grep -v ✓")[1]) ''; } args diff --git a/nixos/tests/boot-stage1.nix b/nixos/tests/boot-stage1.nix index f07802b8c31e..bc6caa8ee627 100644 --- a/nixos/tests/boot-stage1.nix +++ b/nixos/tests/boot-stage1.nix @@ -157,7 +157,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { machine.fail("pgrep -a canary1") machine.fail("kill -0 $(< /run/canary2.pid)") machine.succeed('pgrep -a -f "^@canary3$"') - machine.succeed('pgrep -a -f "^kcanary$"') + machine.succeed('pgrep -a -f "^\\[kcanary\\]$"') ''; meta.maintainers = with pkgs.lib.maintainers; [ aszlig ]; diff --git a/nixos/tests/chrony.nix b/nixos/tests/chrony.nix index 2dcc363728be..9582ab14bb8f 100644 --- a/nixos/tests/chrony.nix +++ b/nixos/tests/chrony.nix @@ -13,8 +13,6 @@ import ./make-test-python.nix ({ lib, ... }: specialisation.hardened.configuration = { services.chrony.enableMemoryLocking = true; environment.memoryAllocator.provider = "graphene-hardened"; - # dhcpcd privsep is incompatible with graphene-hardened - networking.useNetworkd = true; }; }; }; diff --git a/nixos/tests/common/acme/server/default.nix b/nixos/tests/common/acme/server/default.nix index 457495cdb2c0..893c6924027f 100644 --- a/nixos/tests/common/acme/server/default.nix +++ b/nixos/tests/common/acme/server/default.nix @@ -54,11 +54,6 @@ let testCerts = import ./snakeoil-certs.nix; domain = testCerts.domain; - resolver = let - message = "You need to define a resolver for the acme test module."; - firstNS = lib.head config.networking.nameservers; - in if config.networking.nameservers == [] then throw message else firstNS; - pebbleConf.pebble = { listenAddress = "0.0.0.0:443"; managementListenAddress = "0.0.0.0:15000"; diff --git a/nixos/tests/common/resolver.nix b/nixos/tests/common/resolver.nix index 4c3789d0abfa..4d1b96a3a497 100644 --- a/nixos/tests/common/resolver.nix +++ b/nixos/tests/common/resolver.nix @@ -31,10 +31,11 @@ services.bind.forwarders = lib.mkForce []; services.bind.zones = lib.singleton { name = "."; + master = true; file = let addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) "."; mkNsdZoneNames = zones: map addDot (lib.attrNames zones); - mkBindZoneNames = zones: map (zone: addDot zone.name) zones; + mkBindZoneNames = zones: map addDot (lib.attrNames zones); getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones ++ mkBindZoneNames cfg.services.bind.zones; diff --git a/nixos/tests/coturn.nix b/nixos/tests/coturn.nix index b44bf8d06e39..b3c96dba35f8 100644 --- a/nixos/tests/coturn.nix +++ b/nixos/tests/coturn.nix @@ -30,5 +30,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { secretsfile.fail("${pkgs.coturn}/bin/turnutils_uclient -W some-very-secret-string 127.0.0.1 -DgX -e 127.0.0.1 -n 1 -c -y") # allowed-peer-ip, should succeed: secretsfile.succeed("${pkgs.coturn}/bin/turnutils_uclient -W some-very-secret-string 192.168.1.2 -DgX -e 192.168.1.2 -n 1 -c -y") + + default.log(default.execute("systemd-analyze security coturn.service | grep -v '✓'")[1]) ''; }) diff --git a/nixos/tests/croc.nix b/nixos/tests/croc.nix index 5d709eb3d1cb..2e910f525261 100644 --- a/nixos/tests/croc.nix +++ b/nixos/tests/croc.nix @@ -3,11 +3,11 @@ let client = { pkgs, ... }: { environment.systemPackages = [ pkgs.croc ]; }; - pass = pkgs.writeText "pass" "PassRelay"; + pass = "PassRelay"; in { name = "croc"; meta = with pkgs.lib.maintainers; { - maintainers = [ hax404 julm ]; + maintainers = [ equirosa SuperSandro2000 ]; }; nodes = { @@ -38,12 +38,12 @@ in { sender.execute("echo Hello World > testfile01.txt") sender.execute("echo Hello Earth > testfile02.txt") sender.execute( - "croc --pass ${pass} --relay relay send --code topSecret testfile01.txt testfile02.txt >&2 &" + "env CROC_SECRET=topSecret croc --pass ${pass} --relay relay send testfile01.txt testfile02.txt >&2 &" ) # receive the testfiles and check them receiver.succeed( - "croc --pass ${pass} --yes --relay relay topSecret" + "env CROC_SECRET=topSecret croc --pass ${pass} --yes --relay relay" ) assert "Hello World" in receiver.succeed("cat testfile01.txt") assert "Hello Earth" in receiver.succeed("cat testfile02.txt") diff --git a/nixos/tests/cyrus-imap.nix b/nixos/tests/cyrus-imap.nix new file mode 100644 index 000000000000..36145ed4748e --- /dev/null +++ b/nixos/tests/cyrus-imap.nix @@ -0,0 +1,120 @@ +{ lib, pkgs, ... }: +{ + name = "cyrus-imap"; + + meta = { + maintainers = with lib.maintainers; [ moraxyc ]; + }; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = with pkgs; [ + curl + sudo + ]; + services.saslauthd = { + enable = true; + config = '' + DESC="SASL Authentication Daemon" + NAME="saslauthd" + MECH_OPTIONS="" + THREADS=5 + START=yes + OPTIONS="-c -m /run/saslauthd" + ''; + }; + services.cyrus-imap = { + enable = true; + cyrusSettings = { + START = { + recover = { + cmd = [ + "ctl_cyrusdb" + "-r" + ]; + }; + }; + EVENTS = { + tlsprune = { + cmd = [ "tls_prune" ]; + at = 400; + }; + delprune = { + cmd = [ + "cyr_expire" + "-E" + "3" + ]; + at = 400; + }; + deleteprune = { + cmd = [ + "cyr_expire" + "-E" + "4" + "-D" + "28" + ]; + at = 430; + }; + expungeprune = { + cmd = [ + "cyr_expire" + "-E" + "4" + "-X" + "28" + ]; + at = 445; + }; + checkpoint = { + cmd = [ + "ctl_cyrusdb" + "-c" + ]; + period = 30; + }; + }; + SERVICES = { + http = { + cmd = [ "httpd" ]; + listen = "80"; + prefork = 0; + }; + imap = { + cmd = [ "imapd" ]; + listen = "143"; + prefork = 0; + }; + lmtpunix = { + cmd = [ "lmtpd" ]; + listen = "/run/cyrus/lmtp"; + prefork = 0; + }; + notify = { + cmd = [ "notifyd" ]; + listen = "/run/cyrus/notify"; + proto = "udp"; + prefork = 0; + }; + }; + }; + }; + }; + + testScript = '' + machine.wait_for_unit("saslauthd.service") + machine.wait_for_unit("cyrus-imap.service") + + machine.wait_for_open_port(80) + machine.wait_for_open_port(143) + + machine.succeed("echo 'secret' | ${lib.getExe' pkgs.cyrus_sasl.bin "saslpasswd2"} -p -c cyrus") + machine.succeed("chown cyrus /etc/sasldb2") + + machine.succeed("sudo -ucyrus curl --fail --max-time 10 imap://cyrus:secret@localhost:143") + machine.fail("curl --fail --max-time 10 imap://cyrus:wrongsecret@localhost:143") + machine.fail("curl --fail --max-time 10 -X PROPFIND -H 'Depth: 1' 'http://localhost/dav/addressbooks/user/cyrus@localhost/Default'") + ''; +} diff --git a/nixos/tests/dnsdist.nix b/nixos/tests/dnsdist.nix index 9921be419a75..33f25d019413 100644 --- a/nixos/tests/dnsdist.nix +++ b/nixos/tests/dnsdist.nix @@ -64,7 +64,10 @@ in networking.firewall.allowedTCPPorts = [ 443 ]; networking.firewall.allowedUDPPorts = [ 443 ]; services.dnsdist.dnscrypt.enable = true; - services.dnsdist.dnscrypt.providerKey = "${./dnscrypt-wrapper/secret.key}"; + services.dnsdist.dnscrypt.providerKey = pkgs.runCommand "dnscrypt-secret.key" {} '' + echo 'R70+xqm7AaDsPtDgpSjSG7KHvEqVf6u6PZ+E3cGPbOwUQdg6/ + RIIpK6pHkINhrv7nxwIG5c7b/m5NJVT3A1AXQ==' | base64 -id > "$out" + ''; } ]; diff --git a/nixos/tests/docker-tools-overlay.nix b/nixos/tests/docker-tools-overlay.nix index 6781388e639b..14e33899affc 100644 --- a/nixos/tests/docker-tools-overlay.nix +++ b/nixos/tests/docker-tools-overlay.nix @@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { ... }: { virtualisation.docker.enable = true; - virtualisation.docker.storageDriver = "overlay"; # defaults to overlay2 + virtualisation.docker.storageDriver = "overlay2"; }; }; diff --git a/nixos/tests/endlessh-go.nix b/nixos/tests/endlessh-go.nix index b261dbf1c560..70ec7d816612 100644 --- a/nixos/tests/endlessh-go.nix +++ b/nixos/tests/endlessh-go.nix @@ -44,15 +44,19 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: server.wait_for_unit("endlessh-go.service") server.wait_for_open_port(2222) server.wait_for_open_port(9229) + server.fail("curl -sSf server:9229/metrics | grep -q endlessh_client_closed_count_total") client.succeed("nc -dvW5 server 2222") - client.succeed("curl -kv server:9229/metrics") + server.succeed("curl -sSf server:9229/metrics | grep -q endlessh_client_closed_count_total") + client.fail("curl -sSfm 5 server:9229/metrics") with subtest("Privileged"): activate_specialisation("privileged") server.wait_for_unit("endlessh-go.service") server.wait_for_open_port(22) server.wait_for_open_port(92) + server.fail("curl -sSf server:92/metrics | grep -q endlessh_client_closed_count_total") client.succeed("nc -dvW5 server 22") - client.succeed("curl -kv server:92/metrics") + server.succeed("curl -sSf server:92/metrics | grep -q endlessh_client_closed_count_total") + client.fail("curl -sSfm 5 server:92/metrics") ''; }) diff --git a/nixos/tests/etcd/etcd.nix b/nixos/tests/etcd/etcd.nix index a32d0f9a55d1..1ffa5b100975 100644 --- a/nixos/tests/etcd/etcd.nix +++ b/nixos/tests/etcd/etcd.nix @@ -1,8 +1,6 @@ # This test runs simple etcd node - import ../make-test-python.nix ({ pkgs, ... } : { name = "etcd"; - meta = with pkgs.lib.maintainers; { maintainers = [ offline ]; }; @@ -17,6 +15,8 @@ import ../make-test-python.nix ({ pkgs, ... } : { with subtest("should start etcd node"): node.start() node.wait_for_unit("etcd.service") + # Add additional wait for actual readiness + node.wait_until_succeeds("etcdctl endpoint health") with subtest("should write and read some values to etcd"): node.succeed("etcdctl put /foo/bar 'Hello world'") diff --git a/nixos/tests/fedimintd.nix b/nixos/tests/fedimintd.nix new file mode 100644 index 000000000000..19e92b43da65 --- /dev/null +++ b/nixos/tests/fedimintd.nix @@ -0,0 +1,37 @@ +# This test runs the fedimintd and verifies that it starts + +{ pkgs, ... }: + +{ + name = "fedimintd"; + + meta = with pkgs.lib.maintainers; { + maintainers = [ dpc ]; + }; + + nodes.machine = + { ... }: + { + services.fedimintd."mainnet" = { + enable = true; + p2p = { + url = "fedimint://example.com"; + }; + api = { + url = "wss://example.com"; + }; + environment = { + "FM_REL_NOTES_ACK" = "0_4_xyz"; + }; + }; + }; + + testScript = + { nodes, ... }: + '' + start_all() + + machine.wait_for_unit("fedimintd-mainnet.service") + machine.wait_for_open_port(${toString nodes.machine.services.fedimintd.mainnet.api.port}) + ''; +} diff --git a/nixos/tests/filesender.nix b/nixos/tests/filesender.nix index 9274ddbf7e90..346e315956fb 100644 --- a/nixos/tests/filesender.nix +++ b/nixos/tests/filesender.nix @@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "filesender"; meta = { maintainers = with lib.maintainers; [ nhnn ]; - broken = pkgs.stdenv.isAarch64; # selenium.common.exceptions.WebDriverException: Message: Unsupported platform/architecture combination: linux/aarch64 + broken = pkgs.stdenv.hostPlatform.isAarch64; # selenium.common.exceptions.WebDriverException: Message: Unsupported platform/architecture combination: linux/aarch64 }; nodes.filesender = { ... }: let diff --git a/nixos/tests/firewall.nix b/nixos/tests/firewall.nix index 139bc3117740..89f4878fd47f 100644 --- a/nixos/tests/firewall.nix +++ b/nixos/tests/firewall.nix @@ -3,14 +3,31 @@ import ./make-test-python.nix ( { pkgs, nftables, ... } : { name = "firewall" + pkgs.lib.optionalString nftables "-nftables"; meta = with pkgs.lib.maintainers; { - maintainers = [ ]; + maintainers = [ rvfg garyguo ]; }; nodes = { walled = { ... }: - { networking.firewall.enable = true; - networking.firewall.logRefusedPackets = true; + { networking.firewall = { + enable = true; + logRefusedPackets = true; + # Syntax smoke test, not actually verified otherwise + allowedTCPPorts = [ 25 993 8005 ]; + allowedTCPPortRanges = [ + { from = 980; to = 1000; } + { from = 990; to = 1010; } + { from = 8000; to = 8010; } + ]; + interfaces.eth0 = { + allowedTCPPorts = [ 10003 ]; + allowedTCPPortRanges = [ { from = 10000; to = 10005; } ]; + }; + interfaces.eth3 = { + allowedUDPPorts = [ 10003 ]; + allowedUDPPortRanges = [ { from = 10000; to = 10005; } ]; + }; + }; networking.nftables.enable = nftables; services.httpd.enable = true; services.httpd.adminAddr = "foo@example.org"; @@ -48,6 +65,14 @@ import ./make-test-python.nix ( { pkgs, nftables, ... } : { walled.succeed("curl -v http://attacker/ >&2") walled.succeed("ping -c 1 attacker >&2") + # Open tcp port 80 at runtime + walled.succeed("nixos-firewall-tool open tcp 80") + attacker.succeed("curl -v http://walled/ >&2") + + # Reset the firewall + walled.succeed("nixos-firewall-tool reset") + attacker.fail("curl --fail --connect-timeout 2 http://walled/ >&2") + # If we stop the firewall, then connections should succeed. walled.stop_job("${unit}") attacker.succeed("curl -v http://walled/ >&2") diff --git a/nixos/tests/forgejo.nix b/nixos/tests/forgejo.nix index d2315b7f013e..5e6f19e56df0 100644 --- a/nixos/tests/forgejo.nix +++ b/nixos/tests/forgejo.nix @@ -188,7 +188,7 @@ let assert "hello world" == client.succeed("cat /tmp/repo-clone/testfile").strip() with subtest("Testing git protocol version=2 over ssh"): - git_protocol = client.succeed("GIT_TRACE2_EVENT=true git -C /tmp/repo-clone fetch |& grep negotiated-version") + git_protocol = client.succeed("GIT_TRACE2_EVENT=true GIT_TRACE2_EVENT_NESTING=3 git -C /tmp/repo-clone fetch |& grep negotiated-version") version = json.loads(git_protocol).get("value") assert version == "2", f"git did not negotiate protocol version 2, but version {version} instead." diff --git a/nixos/tests/freetube.nix b/nixos/tests/freetube.nix index c0beeeaae61f..c886f1ee116e 100644 --- a/nixos/tests/freetube.nix +++ b/nixos/tests/freetube.nix @@ -24,7 +24,7 @@ let nodes = { "${name}" = machine; }; meta.maintainers = with pkgs.lib.maintainers; [ kirillrdy ]; # time-out on ofborg - meta.broken = pkgs.stdenv.isAarch64; + meta.broken = pkgs.stdenv.hostPlatform.isAarch64; enableOCR = true; testScript = '' @@ -35,7 +35,7 @@ let machine.wait_for_text('Your Subscription list is currently empty') machine.screenshot("main.png") machine.send_key("ctrl-comma") - machine.wait_for_text('Data Settings', timeout=60) + machine.wait_for_text('Data', timeout=60) machine.screenshot("preferences.png") ''; }); diff --git a/nixos/tests/frr.nix b/nixos/tests/frr.nix index edd702dc60e6..f4cd68c9d315 100644 --- a/nixos/tests/frr.nix +++ b/nixos/tests/frr.nix @@ -5,10 +5,11 @@ # # All interfaces are in OSPF Area 0. -import ./make-test-python.nix ({ pkgs, ... }: +import ./make-test-python.nix ( + { pkgs, ... }: let - ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address; + ifAddr = node: iface: (pkgs.lib.head node.networking.interfaces.${iface}.ipv4.addresses).address; ospfConf1 = '' router ospf @@ -25,80 +26,94 @@ import ./make-test-python.nix ({ pkgs, ... }: ''; in - { - name = "frr"; + { + name = "frr"; - meta = with pkgs.lib.maintainers; { - maintainers = [ ]; - }; + meta = with pkgs.lib.maintainers; { + maintainers = [ ]; + }; - nodes = { + nodes = { - client = - { nodes, ... }: - { - virtualisation.vlans = [ 1 ]; - networking.defaultGateway = ifAddr nodes.router1 "eth1"; + client = + { nodes, ... }: + { + virtualisation.vlans = [ 1 ]; + services.frr = { + config = '' + ip route 192.168.0.0/16 ${ifAddr nodes.router1 "eth1"} + ''; }; - - router1 = - { ... }: - { - virtualisation.vlans = [ 1 2 ]; - boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; - networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT"; - services.frr.ospf = { - enable = true; - config = ospfConf1; - }; - - specialisation.ospf.configuration = { - services.frr.ospf.config = ospfConf2; - }; + }; + + router1 = + { ... }: + { + virtualisation.vlans = [ + 1 + 2 + ]; + boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; + networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT"; + services.frr = { + ospfd.enable = true; + config = ospfConf1; }; - router2 = - { ... }: - { - virtualisation.vlans = [ 3 2 ]; - boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; - networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT"; - services.frr.ospf = { - enable = true; - config = ospfConf2; - }; + specialisation.ospf.configuration = { + services.frr.config = ospfConf2; }; - - server = - { nodes, ... }: - { - virtualisation.vlans = [ 3 ]; - networking.defaultGateway = ifAddr nodes.router2 "eth1"; + }; + + router2 = + { ... }: + { + virtualisation.vlans = [ + 3 + 2 + ]; + boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; + networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT"; + services.frr = { + ospfd.enable = true; + config = ospfConf2; }; - }; + }; - testScript = + server = { nodes, ... }: - '' - start_all() - - # Wait for the networking to start on all machines - for machine in client, router1, router2, server: - machine.wait_for_unit("network.target") - - with subtest("Wait for Zebra and OSPFD"): - for gw in router1, router2: - gw.wait_for_unit("zebra") - gw.wait_for_unit("ospfd") - - router1.succeed("${nodes.router1.config.system.build.toplevel}/specialisation/ospf/bin/switch-to-configuration test >&2") - - with subtest("Wait for OSPF to form adjacencies"): - for gw in router1, router2: - gw.wait_until_succeeds("vtysh -c 'show ip ospf neighbor' | grep Full") - gw.wait_until_succeeds("vtysh -c 'show ip route' | grep '^O>'") - - with subtest("Test ICMP"): - client.wait_until_succeeds("ping -4 -c 3 server >&2") - ''; - }) + { + virtualisation.vlans = [ 3 ]; + services.frr = { + config = '' + ip route 192.168.0.0/16 ${ifAddr nodes.router2 "eth1"} + ''; + }; + }; + }; + + testScript = + { nodes, ... }: + '' + start_all() + + # Wait for the networking to start on all machines + for machine in client, router1, router2, server: + machine.wait_for_unit("network.target") + + with subtest("Wait for FRR"): + for gw in client, router1, router2, server: + gw.wait_for_unit("frr") + + router1.succeed("${nodes.router1.system.build.toplevel}/specialisation/ospf/bin/switch-to-configuration test >&2") + + with subtest("Wait for OSPF to form adjacencies"): + for gw in router1, router2: + gw.wait_until_succeeds("vtysh -c 'show ip ospf neighbor' | grep Full") + gw.wait_until_succeeds("vtysh -c 'show ip route' | grep '^O>'") + + with subtest("Test ICMP"): + client.wait_until_succeeds("ping -4 -c 3 server >&2") + ''; + } +) diff --git a/nixos/tests/gatus.nix b/nixos/tests/gatus.nix new file mode 100644 index 000000000000..5bb146540fba --- /dev/null +++ b/nixos/tests/gatus.nix @@ -0,0 +1,34 @@ +{ pkgs, ... }: +{ + name = "gatus"; + meta.maintainers = with pkgs.lib.maintainers; [ pizzapim ]; + + nodes.machine = + { ... }: + { + services.gatus = { + enable = true; + + settings = { + web.port = 8080; + metrics = true; + + endpoints = [ + { + name = "metrics"; + url = "http://localhost:8080/metrics"; + interval = "1s"; + conditions = [ + "[STATUS] == 200" + ]; + } + ]; + }; + }; + }; + + testScript = '' + machine.wait_for_unit("gatus.service") + machine.succeed("curl -s http://localhost:8080/metrics | grep go_info") + ''; +} diff --git a/nixos/tests/gerrit.nix b/nixos/tests/gerrit.nix index 8ae9e89cf6b0..4630f9412948 100644 --- a/nixos/tests/gerrit.nix +++ b/nixos/tests/gerrit.nix @@ -1,12 +1,6 @@ import ./make-test-python.nix ({ pkgs, ... }: -let - lfs = pkgs.fetchurl { - url = "https://gerrit-ci.gerritforge.com/job/plugin-lfs-bazel-master/90/artifact/bazel-bin/plugins/lfs/lfs.jar"; - sha256 = "023b0kd8djm3cn1lf1xl67yv3j12yl8bxccn42lkfmwxjwjfqw6h"; - }; - -in { +{ name = "gerrit"; meta = with pkgs.lib.maintainers; { @@ -25,12 +19,9 @@ in { listenAddress = "[::]:80"; jvmHeapLimit = "1g"; - plugins = [ lfs ]; builtinPlugins = [ "hooks" "webhooks" ]; settings = { gerrit.canonicalWebUrl = "http://server"; - lfs.plugin = "lfs"; - plugins.allowRemoteAdmin = true; sshd.listenAddress = "[::]:2222"; sshd.advertisedAddress = "[::]:2222"; }; diff --git a/nixos/tests/geth.nix b/nixos/tests/geth.nix index dc6490db57c9..7f7315271088 100644 --- a/nixos/tests/geth.nix +++ b/nixos/tests/geth.nix @@ -14,7 +14,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { services.geth."testnet" = { enable = true; port = 30304; - network = "goerli"; + network = "holesky"; http = { enable = true; port = 18545; diff --git a/nixos/tests/github-runner.nix b/nixos/tests/github-runner.nix index 033365d6925c..f3e4b70fa5d3 100644 --- a/nixos/tests/github-runner.nix +++ b/nixos/tests/github-runner.nix @@ -11,6 +11,12 @@ import ./make-test-python.nix ({ pkgs, ... }: tokenFile = builtins.toFile "github-runner.token" "not-so-secret"; }; + services.github-runners.test-disabled = { + enable = false; + url = "https://github.com/yaxitech"; + tokenFile = builtins.toFile "github-runner.token" "not-so-secret"; + }; + systemd.services.dummy-github-com = { wantedBy = [ "multi-user.target" ]; before = [ "github-runner-test.service" ]; @@ -33,5 +39,7 @@ import ./make-test-python.nix ({ pkgs, ... }: assert "Self-hosted runner registration" in out, "did not read runner registration header" machine.wait_until_succeeds("test -f /tmp/registration-connect") + + machine.fail("systemctl list-unit-files | grep test-disabled") ''; }) diff --git a/nixos/tests/guix/publish.nix b/nixos/tests/guix/publish.nix index eb56fc97478c..4a1e49fea903 100644 --- a/nixos/tests/guix/publish.nix +++ b/nixos/tests/guix/publish.nix @@ -47,12 +47,12 @@ in { services.guix = { enable = true; - extraArgs = [ - # Force to only get all substitutes from the local server. We don't - # have anything in the Guix store directory and we cannot get - # anything from the official substitute servers anyways. - "--substitute-urls='http://server.local:${toString publishPort}'" + # Force to only get all substitutes from the local server. We don't + # have anything in the Guix store directory and we cannot get + # anything from the official substitute servers anyways. + substituters.urls = [ "http://server.local:${toString publishPort}" ]; + extraArgs = [ # Enable autodiscovery of the substitute servers in the local # network. This machine shouldn't need to import the signing key from # the substitute server since it is automatically done anyways. diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix index e38834961e13..0c43e3523dac 100644 --- a/nixos/tests/hardened.nix +++ b/nixos/tests/hardened.nix @@ -11,11 +11,6 @@ import ./make-test-python.nix ({ pkgs, ... } : { imports = [ ../modules/profiles/hardened.nix ]; environment.memoryAllocator.provider = "graphene-hardened"; nix.settings.sandbox = false; - nixpkgs.overlays = [ - (final: super: { - dhcpcd = super.dhcpcd.override { enablePrivSep = false; }; - }) - ]; virtualisation.emptyDiskImages = [ 4096 ]; boot.initrd.postDeviceCommands = '' ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb diff --git a/nixos/tests/headscale.nix b/nixos/tests/headscale.nix index 80188b65dbfc..15f7c7be2ae5 100644 --- a/nixos/tests/headscale.nix +++ b/nixos/tests/headscale.nix @@ -38,6 +38,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: region_id = 999; stun_listen_addr = "0.0.0.0:${toString stunPort}"; }; + dns.base_domain = "tailnet"; }; }; nginx = { @@ -77,6 +78,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: # Check that they are reachable from the tailnet peer1.wait_until_succeeds("tailscale ping peer2") - peer2.wait_until_succeeds("tailscale ping peer1") + peer2.wait_until_succeeds("tailscale ping peer1.tailnet") ''; }) diff --git a/nixos/tests/icingaweb2.nix b/nixos/tests/icingaweb2.nix index e631e667bd50..9d05e9fe1390 100644 --- a/nixos/tests/icingaweb2.nix +++ b/nixos/tests/icingaweb2.nix @@ -1,7 +1,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { name = "icingaweb2"; - meta = with pkgs.lib.maintainers; { - maintainers = [ das_j ]; + meta = { + maintainers = pkgs.lib.teams.helsinki-systems.members; }; nodes = { diff --git a/nixos/tests/installed-tests/gdk-pixbuf.nix b/nixos/tests/installed-tests/gdk-pixbuf.nix index 110efdbf710f..a86c20b0fbeb 100644 --- a/nixos/tests/installed-tests/gdk-pixbuf.nix +++ b/nixos/tests/installed-tests/gdk-pixbuf.nix @@ -6,7 +6,7 @@ makeInstalledTest { testConfig = { # Tests allocate a lot of memory trying to exploit a CVE # but qemu-system-i386 has a 2047M memory limit - virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096; + virtualisation.memorySize = if pkgs.stdenv.hostPlatform.isi686 then 2047 else 4096; }; testRunnerFlags = [ "--timeout" "1800" ]; diff --git a/nixos/tests/installed-tests/geocode-glib.nix b/nixos/tests/installed-tests/geocode-glib.nix index fcb38c96ab0f..76a32ee2849a 100644 --- a/nixos/tests/installed-tests/geocode-glib.nix +++ b/nixos/tests/installed-tests/geocode-glib.nix @@ -4,8 +4,10 @@ makeInstalledTest { testConfig = { i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" - # The tests require this locale available. + # The tests require these locales. "en_GB.UTF-8/UTF-8" + "cs_CZ.UTF-8/UTF-8" + "sv_SE.UTF-8/UTF-8" ]; }; diff --git a/nixos/tests/installed-tests/xdg-desktop-portal.nix b/nixos/tests/installed-tests/xdg-desktop-portal.nix index 90529d37ee0f..d40317faeb01 100644 --- a/nixos/tests/installed-tests/xdg-desktop-portal.nix +++ b/nixos/tests/installed-tests/xdg-desktop-portal.nix @@ -3,7 +3,20 @@ makeInstalledTest { tested = pkgs.xdg-desktop-portal; - # Ton of breakage. - # https://github.com/flatpak/xdg-desktop-portal/pull/428 - meta.broken = true; + # Red herring + # Failed to load RealtimeKit property: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.RealtimeKit1 was not provided by any .service files + # Maybe a red herring, enabling PipeWire doesn't fix the location test. + # Failed connect to PipeWire: Couldn't connect to PipeWire + testConfig = { + environment.variables = { + TEST_IN_CI = 1; + XDG_DATA_DIRS = "${pkgs.xdg-desktop-portal.installedTests}/share/installed-tests/xdg-desktop-portal/share"; + }; + # Broken, see comment in the package file. + #services.geoclue2 = { + # enable = true; + # enableDemoAgent = true; + #}; + #location.provider = "geoclue2"; + }; } diff --git a/nixos/tests/jibri.nix b/nixos/tests/jibri.nix index 45e30af9a9a5..2046ea86d5ac 100644 --- a/nixos/tests/jibri.nix +++ b/nixos/tests/jibri.nix @@ -22,9 +22,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { forceSSL = true; }; - security.acme.email = "me@example.org"; + security.acme.defaults.email = "me@example.org"; security.acme.acceptTerms = true; - security.acme.server = "https://example.com"; # self-signed only + security.acme.defaults.server = "https://example.com"; # self-signed only }; testScript = '' @@ -44,7 +44,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jibri@auth.machine'", timeout=33 ) machine.wait_until_succeeds( - "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Joined MUC: jibribrewery@internal.machine'", timeout=34 + "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Joined MUC: jibribrewery@internal.auth.machine'", timeout=34 ) assert '"busyStatus":"IDLE","health":{"healthStatus":"HEALTHY"' in machine.succeed( diff --git a/nixos/tests/k3s/multi-node.nix b/nixos/tests/k3s/multi-node.nix index bc06ad858d8c..58b98b2a1853 100644 --- a/nixos/tests/k3s/multi-node.nix +++ b/nixos/tests/k3s/multi-node.nix @@ -189,7 +189,7 @@ import ../make-test-python.nix ( m.start() m.wait_for_unit("k3s") - is_aarch64 = "${toString pkgs.stdenv.isAarch64}" == "1" + is_aarch64 = "${toString pkgs.stdenv.hostPlatform.isAarch64}" == "1" # wait for the agent to show up server.wait_until_succeeds("k3s kubectl get node agent") diff --git a/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix index 7e2fce20857a..abbc8dc8a476 100644 --- a/nixos/tests/kanidm.nix +++ b/nixos/tests/kanidm.nix @@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ... }: in { name = "kanidm"; - meta.maintainers = with pkgs.lib.maintainers; [ erictapen Flakebi oddlama ]; + meta.maintainers = with pkgs.lib.maintainers; [ Flakebi oddlama ]; nodes.server = { pkgs, ... }: { services.kanidm = { @@ -97,7 +97,7 @@ import ./make-test-python.nix ({ pkgs, ... }: with subtest("Test unixd connection"): client.wait_for_unit("kanidm-unixd.service") client.wait_for_file("/run/kanidm-unixd/sock") - client.wait_until_succeeds("kanidm-unix status | grep working!") + client.wait_until_succeeds("kanidm-unix status | grep online") with subtest("Test user creation"): client.wait_for_unit("getty@tty1.service") @@ -107,7 +107,7 @@ import ./make-test-python.nix ({ pkgs, ... }: client.send_chars("kanidm person posix set-password testuser\n") client.wait_until_tty_matches("1", "Enter new") client.send_chars("${testCredentials.password}\n") - client.wait_until_tty_matches("1", "Retype") + client.wait_until_tty_matches("1", "Reenter") client.send_chars("${testCredentials.password}\n") output = client.succeed("getent passwd testuser") assert "TestUser" in output diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix index a4f452412cdf..d487d87fbacb 100644 --- a/nixos/tests/keepassxc.nix +++ b/nixos/tests/keepassxc.nix @@ -17,6 +17,11 @@ import ./make-test-python.nix ({ pkgs, ...} : services.xserver.enable = true; + # for better OCR + environment.etc."icewm/prefoverride".text = '' + ColorActiveTitleBar = "rgb:FF/FF/FF" + ''; + # Regression test for https://github.com/NixOS/nixpkgs/issues/163482 qt = { enable = true; @@ -41,7 +46,7 @@ import ./make-test-python.nix ({ pkgs, ...} : machine.wait_for_x() with subtest("Can create database and entry with CLI"): - ${aliceDo "keepassxc-cli db-create -k foo.keyfile foo.kdbx"} + ${aliceDo "keepassxc-cli db-create --set-key-file foo.keyfile foo.kdbx"} ${aliceDo "keepassxc-cli add --no-password -k foo.keyfile foo.kdbx bar"} with subtest("Ensure KeePassXC starts"): @@ -62,10 +67,21 @@ import ./make-test-python.nix ({ pkgs, ...} : # Wait for the enter password screen to appear. machine.wait_for_text("/home/alice/foo.kdbx") - # Click on "Browse" button to select keyfile + # Click on "I have key file" button to open keyfile dialog + machine.send_key("tab") machine.send_key("tab") + machine.send_key("tab") + machine.send_key("ret") + + # Select keyfile + machine.wait_for_text("Select key file") machine.send_chars("/home/alice/foo.keyfile") machine.send_key("ret") + + # Open database + machine.wait_for_text("foo.kdbx \\[Locked] - KeePassXC") + machine.send_key("ret") + # Database is unlocked (doesn't have "[Locked]" in the title anymore) machine.wait_for_text("foo.kdbx - KeePassXC") ''; diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix index e5d3b36642e7..0171a1e79e1f 100644 --- a/nixos/tests/kernel-generic.nix +++ b/nixos/tests/kernel-generic.nix @@ -30,6 +30,7 @@ let linux_5_15_hardened linux_6_1_hardened linux_6_6_hardened + linux_6_11_hardened linux_rt_5_4 linux_rt_5_10 linux_rt_5_15 diff --git a/nixos/tests/keycloak.nix b/nixos/tests/keycloak.nix index 67b412c80961..baed9419061d 100644 --- a/nixos/tests/keycloak.nix +++ b/nixos/tests/keycloak.nix @@ -20,6 +20,8 @@ let nodes = { keycloak = { config, ... }: { + virtualisation.memorySize = 2047; + security.pki.certificateFiles = [ certs.ca.cert ]; @@ -48,8 +50,7 @@ let ]; }; environment.systemPackages = with pkgs; [ - xmlstarlet - html-tidy + htmlq jq ]; }; @@ -151,16 +152,14 @@ let # post url. keycloak.succeed( "curl -sSf -c cookie '${frontendUrl}/realms/${realm.realm}/protocol/openid-connect/auth?client_id=${client.name}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=openid+email&response_type=code&response_mode=query&nonce=qw4o89g3qqm' >login_form", - "tidy -asxml -q -m login_form || true", - "xml sel -T -t -m \"_:html/_:body/_:div/_:div/_:div/_:div/_:div/_:div/_:form[@id='kc-form-login']\" -v @action login_form >form_post_url", + "htmlq '#kc-form-login' --attribute action --filename login_form --output form_post_url" ) # Post the login form and save the response. Once again tidy up # the HTML, then extract the authorization code. keycloak.succeed( "curl -sSf -L -b cookie -d 'username=${user.username}' -d 'password=${password}' -d 'credentialId=' \"$(<form_post_url)\" >auth_code_html", - "tidy -asxml -q -m auth_code_html || true", - "xml sel -T -t -m \"_:html/_:body/_:div/_:div/_:div/_:div/_:div/_:input[@id='code']\" -v @value auth_code_html >auth_code", + "htmlq '#code' --attribute value --filename auth_code_html --output auth_code" ) # Exchange the authorization code for an access token. diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix index e8973a50f852..3542dea58f7f 100644 --- a/nixos/tests/keymap.nix +++ b/nixos/tests/keymap.nix @@ -11,15 +11,15 @@ let testReader = pkgs.writeScript "test-input-reader" '' rm -f ${resultFile} ${resultFile}.tmp - logger "testReader: START: Waiting for $1 characters, expecting '$2'." + logger "testReader: START: expecting '$1'." touch ${readyFile} - read -r -N $1 chars + read -r -N ''${#1} -t 60 chars rm -f ${readyFile} - if [ "$chars" == "$2" ]; then - logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp + if [ "$chars" == "$1" ]; then + logger -s "testReader: PASS: Got '$1' as expected." 2>${resultFile}.tmp else - logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp + logger -s "testReader: FAIL: Expected '$1' but got '$chars'." 2>${resultFile}.tmp fi # rename after the file is written to prevent a race condition mv ${resultFile}.tmp ${resultFile} @@ -39,39 +39,29 @@ let import shlex - def run_test_case(cmd, xorg_keymap, test_case_name, inputs, expected): - with subtest(test_case_name): - assert len(inputs) == len(expected) - machine.execute("rm -f ${readyFile} ${resultFile}") + def run_test_case(cmd, inputs, expected): + assert len(inputs) == len(expected) + machine.execute("rm -f ${readyFile} ${resultFile}") - # set up process that expects all the keys to be entered - machine.succeed( - "{} {} {} {} >&2 &".format( - cmd, - "${testReader}", - len(inputs), - shlex.quote("".join(expected)), - ) + # set up process that expects all the keys to be entered + machine.succeed( + "${pkgs.systemd}/bin/systemd-cat -t input-test-reader -- {} {} {} &".format( + cmd, + "${testReader}", + shlex.quote("".join(expected)), ) + ) - if xorg_keymap: - # make sure the xterm window is open and has focus - machine.wait_for_window("testterm") - machine.wait_until_succeeds( - "${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible " - "--class testterm windowfocus --sync" - ) - - # wait for reader to be ready - machine.wait_for_file("${readyFile}") + # wait for reader to be ready + machine.wait_for_file("${readyFile}") - # send all keys - for key in inputs: - machine.send_key(key) + # send all keys + for key in inputs: + machine.send_key(key) - # wait for result and check - machine.wait_for_file("${resultFile}") - machine.succeed("grep -q 'PASS:' ${resultFile}") + # wait for result and check + machine.wait_for_file("${resultFile}") + machine.succeed("grep -q 'PASS:' ${resultFile}") with open("${pkgs.writeText "tests.json" (builtins.toJSON tests)}") as json_file: @@ -87,19 +77,17 @@ let # fighting over the virtual terminal. This does not appear to be a problem # when the X test runs first. keymap_environments = { - "Xorg Keymap": "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e", - "VT Keymap": "openvt -sw --", + "Xorg Keymap": "env DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e", + "VT Keymap": "openvt -c 2 -sw --", } machine.wait_for_x() - for keymap_env_name, command in keymap_environments.items(): - with subtest(keymap_env_name): - for test_case_name, test_data in tests.items(): + for test_case_name, test_data in tests.items(): + for keymap_env_name, command in keymap_environments.items(): + with subtest(f"{test_case_name} - {keymap_env_name}"): run_test_case( command, - False, - test_case_name, test_data["qwerty"], test_data["expect"], ) diff --git a/nixos/tests/libvirtd.nix b/nixos/tests/libvirtd.nix index 27ffaac3e62d..0b068d5a2f08 100644 --- a/nixos/tests/libvirtd.nix +++ b/nixos/tests/libvirtd.nix @@ -30,7 +30,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { testScript = let nixosInstallISO = (import ../release.nix {}).iso_minimal.${pkgs.stdenv.hostPlatform.system}; - virshShutdownCmd = if pkgs.stdenv.isx86_64 then "shutdown" else "destroy"; + virshShutdownCmd = if pkgs.stdenv.hostPlatform.isx86_64 then "shutdown" else "destroy"; in '' start_all() diff --git a/nixos/tests/localsend.nix b/nixos/tests/localsend.nix index 8c0a6ac68190..551b6dd73ce8 100644 --- a/nixos/tests/localsend.nix +++ b/nixos/tests/localsend.nix @@ -16,6 +16,7 @@ import ./make-test-python.nix ( machine.wait_for_open_port(53317) machine.wait_for_window("LocalSend", 10) machine.succeed("netstat --listening --program --tcp | grep -P 'tcp.*53317.*localsend'") + machine.succeed("netstat --listening --program --udp | grep -P 'udp.*53317.*localsend'") ''; } ) diff --git a/nixos/tests/lomiri.nix b/nixos/tests/lomiri.nix index b146cba93fe6..4e400ccf121e 100644 --- a/nixos/tests/lomiri.nix +++ b/nixos/tests/lomiri.nix @@ -4,6 +4,52 @@ let user = "alice"; description = "Alice Foobar"; password = "foobar"; + + # tmpfiles setup to make OCRing on terminal output more reliable + terminalOcrTmpfilesSetup = + { + pkgs, + lib, + config, + }: + let + white = "255, 255, 255"; + black = "0, 0, 0"; + colorSection = color: { + Color = color; + Bold = true; + Transparency = false; + }; + terminalColors = pkgs.writeText "customized.colorscheme" ( + lib.generators.toINI { } { + Background = colorSection white; + Foreground = colorSection black; + Color2 = colorSection black; + Color2Intense = colorSection black; + } + ); + terminalConfig = pkgs.writeText "terminal.ubports.conf" ( + lib.generators.toINI { } { + General = { + colorScheme = "customized"; + fontSize = "16"; + fontStyle = "Inconsolata"; + }; + } + ); + confBase = "${config.users.users.${user}.home}/.config"; + userDirArgs = { + mode = "0700"; + user = user; + group = "users"; + }; + in + { + "${confBase}".d = userDirArgs; + "${confBase}/terminal.ubports".d = userDirArgs; + "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}"; + "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}"; + }; in { greeter = makeTest ( @@ -26,11 +72,13 @@ in inherit description password; }; - services.desktopManager.lomiri.enable = lib.mkForce true; - services.displayManager.defaultSession = lib.mkForce "lomiri"; - - # Help with OCR - fonts.packages = [ pkgs.inconsolata ]; + services.xserver.enable = true; + services.xserver.windowManager.icewm.enable = true; + services.xserver.displayManager.lightdm = { + enable = true; + greeters.lomiri.enable = true; + }; + services.displayManager.defaultSession = lib.mkForce "none+icewm"; }; enableOCR = true; @@ -64,13 +112,8 @@ in # Login machine.send_chars("${password}\n") - machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'") - - # Output rendering from Lomiri has started when it starts printing performance diagnostics - machine.wait_for_console_text("Last frame took") - # Look for datetime's clock, one of the last elements to load - wait_for_text(r"(AM|PM)") - machine.screenshot("lomiri_launched") + machine.wait_for_x() + machine.screenshot("session_launched") ''; } ); @@ -154,47 +197,9 @@ in }; # Help with OCR - systemd.tmpfiles.settings = - let - white = "255, 255, 255"; - black = "0, 0, 0"; - colorSection = color: { - Color = color; - Bold = true; - Transparency = false; - }; - terminalColors = pkgs.writeText "customized.colorscheme" ( - lib.generators.toINI { } { - Background = colorSection white; - Foreground = colorSection black; - Color2 = colorSection black; - Color2Intense = colorSection black; - } - ); - terminalConfig = pkgs.writeText "terminal.ubports.conf" ( - lib.generators.toINI { } { - General = { - colorScheme = "customized"; - fontSize = "16"; - fontStyle = "Inconsolata"; - }; - } - ); - confBase = "${config.users.users.${user}.home}/.config"; - userDirArgs = { - mode = "0700"; - user = user; - group = "users"; - }; - in - { - "10-lomiri-test-setup" = { - "${confBase}".d = userDirArgs; - "${confBase}/terminal.ubports".d = userDirArgs; - "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}"; - "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}"; - }; - }; + systemd.tmpfiles.settings = { + "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; }; + }; }; enableOCR = true; @@ -360,58 +365,20 @@ in }; variables = { - # So we can test what content-hub is working behind the scenes - CONTENT_HUB_LOGGING_LEVEL = "2"; + # So we can test what lomiri-content-hub is working behind the scenes + LOMIRI_CONTENT_HUB_LOGGING_LEVEL = "2"; }; systemPackages = with pkgs; [ - # For a convenient way of kicking off content-hub peer collection - lomiri.content-hub.examples + # For a convenient way of kicking off lomiri-content-hub peer collection + lomiri.lomiri-content-hub.examples ]; }; # Help with OCR - systemd.tmpfiles.settings = - let - white = "255, 255, 255"; - black = "0, 0, 0"; - colorSection = color: { - Color = color; - Bold = true; - Transparency = false; - }; - terminalColors = pkgs.writeText "customized.colorscheme" ( - lib.generators.toINI { } { - Background = colorSection white; - Foreground = colorSection black; - Color2 = colorSection black; - Color2Intense = colorSection black; - } - ); - terminalConfig = pkgs.writeText "terminal.ubports.conf" ( - lib.generators.toINI { } { - General = { - colorScheme = "customized"; - fontSize = "16"; - fontStyle = "Inconsolata"; - }; - } - ); - confBase = "${config.users.users.${user}.home}/.config"; - userDirArgs = { - mode = "0700"; - user = user; - group = "users"; - }; - in - { - "10-lomiri-test-setup" = { - "${confBase}".d = userDirArgs; - "${confBase}/terminal.ubports".d = userDirArgs; - "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}"; - "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}"; - }; - }; + systemd.tmpfiles.settings = { + "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; }; + }; }; enableOCR = true; @@ -484,9 +451,9 @@ in # lomiri-terminal-app has a separate VM test to test its basic functionality - # for the LSS content-hub test to work reliably, we need to kick off peer collecting - machine.send_chars("content-hub-test-importer\n") - wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from content-hub + # for the LSS lomiri-content-hub test to work reliably, we need to kick off peer collecting + machine.send_chars("lomiri-content-hub-test-importer\n") + wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from lomiri-content-hub machine.send_key("ctrl-c") # Doing this here, since we need an in-session shell & separately starting a terminal again wastes time @@ -510,7 +477,7 @@ in wait_for_text("Rotation Lock") machine.screenshot("settings_open") - # lomiri-system-settings has a separate VM test, only test Lomiri-specific content-hub functionalities here + # lomiri-system-settings has a separate VM test, only test Lomiri-specific lomiri-content-hub functionalities here # Make fullscreen, can't navigate to Background plugin via keyboard unless window has non-phone-like aspect ratio toggle_maximise() @@ -536,7 +503,7 @@ in # Peers should be loaded wait_for_text("Morph") # or Gallery, but Morph is already packaged - machine.screenshot("settings_content-hub_peers") + machine.screenshot("settings_lomiri-content-hub_peers") # Select Morph as content source mouse_click(370, 100) @@ -544,11 +511,11 @@ in # Expect Morph to be brought into the foreground, with its Downloads page open wait_for_text("No downloads") - # If content-hub encounters a problem, it may have crashed the original application issuing the request. + # If lomiri-content-hub encounters a problem, it may have crashed the original application issuing the request. # Check that it's still alive machine.succeed("pgrep -u ${user} -f lomiri-system-settings") - machine.screenshot("content-hub_exchange") + machine.screenshot("lomiri-content-hub_exchange") # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign machine.send_key("esc") @@ -700,4 +667,125 @@ in } ); + keymap = + let + pwInput = "qwerty"; + pwOutput = "qwertz"; + in + makeTest ( + { pkgs, lib, ... }: + { + name = "lomiri-keymap"; + + meta = { + maintainers = lib.teams.lomiri.members; + }; + + nodes.machine = + { config, ... }: + { + imports = [ ./common/user-account.nix ]; + + virtualisation.memorySize = 2047; + + users.users.${user} = { + inherit description; + password = lib.mkForce pwOutput; + }; + + services.desktopManager.lomiri.enable = lib.mkForce true; + services.displayManager.defaultSession = lib.mkForce "lomiri"; + + # Help with OCR + fonts.packages = [ pkgs.inconsolata ]; + + services.xserver.xkb.layout = lib.strings.concatStringsSep "," [ + # Start with a non-QWERTY keymap to test keymap patch + "de" + # Then a QWERTY one to test switching + "us" + ]; + + # Help with OCR + systemd.tmpfiles.settings = { + "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; }; + }; + }; + + enableOCR = true; + + testScript = + { nodes, ... }: + '' + def wait_for_text(text): + """ + Wait for on-screen text, and try to optimise retry count for slow hardware. + """ + machine.sleep(10) + machine.wait_for_text(text) + + start_all() + machine.wait_for_unit("multi-user.target") + + # Lomiri in greeter mode should use the correct keymap + with subtest("lomiri greeter keymap works"): + machine.wait_for_unit("display-manager.service") + machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'") + + # Start page shows current time + wait_for_text(r"(AM|PM)") + machine.screenshot("lomiri_greeter_launched") + + # Advance to login part + machine.send_key("ret") + wait_for_text("${description}") + machine.screenshot("lomiri_greeter_login") + + # Login + machine.send_chars("${pwInput}\n") + machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'") + + # Output rendering from Lomiri has started when it starts printing performance diagnostics + machine.wait_for_console_text("Last frame took") + # Look for datetime's clock, one of the last elements to load + wait_for_text(r"(AM|PM)") + machine.screenshot("lomiri_launched") + + # Lomiri in desktop mode should use the correct keymap + with subtest("lomiri session keymap works"): + machine.send_key("ctrl-alt-t") + wait_for_text(r"(${user}|machine)") + machine.screenshot("terminal_opens") + + machine.send_chars("touch ${pwInput}\n") + machine.wait_for_file("/home/alice/${pwOutput}", 10) + + # Issues with this keybind: input leaks to focused surface, may open launcher + # Don't have the keyboard indicator to handle this better + machine.send_key("meta_l-spc") + machine.wait_for_console_text('SET KEYMAP "us"') + + # Handle keybind fallout + machine.sleep(10) # wait for everything to settle + machine.send_key("esc") # close launcher in case it was opened + machine.sleep(2) # wait for animation to finish + # Make sure input leaks are gone + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + machine.send_key("backspace") + + machine.send_chars("touch ${pwInput}\n") + machine.wait_for_file("/home/alice/${pwInput}", 10) + + machine.send_key("alt-f4") + ''; + } + ); } diff --git a/nixos/tests/lvm2/vdo.nix b/nixos/tests/lvm2/vdo.nix index 18d25b7b366d..2b4a3df5159e 100644 --- a/nixos/tests/lvm2/vdo.nix +++ b/nixos/tests/lvm2/vdo.nix @@ -1,7 +1,7 @@ { kernelPackages ? null, mkXfsFlags ? "" }: import ../make-test-python.nix ({ pkgs, lib, ... }: { name = "lvm2-vdo"; - meta.maintainers = lib.teams.helsinki-systems.members; + meta.maintainers = [ ]; nodes.machine = { pkgs, lib, ... }: { # Minimum required size for VDO volume: 5063921664 bytes diff --git a/nixos/tests/mihomo.nix b/nixos/tests/mihomo.nix index 472d10050f7f..a456facb4367 100644 --- a/nixos/tests/mihomo.nix +++ b/nixos/tests/mihomo.nix @@ -39,6 +39,8 @@ import ./make-test-python.nix ({ pkgs, ... }: { machine.fail("curl --fail --max-time 10 --proxy socks5://user:supervillain@localhost:7890 http://localhost") # Web UI - machine.succeed("curl --fail http://localhost:9090") == '{"hello":"clash"}' + result = machine.succeed("curl --fail http://localhost:9090") + target = '{"hello":"mihomo"}\n' + assert result == target, f"{result!r} != {target!r}" ''; }) diff --git a/nixos/tests/mollysocket.nix b/nixos/tests/mollysocket.nix index 8cbd0c0272e0..5135d79fcba9 100644 --- a/nixos/tests/mollysocket.nix +++ b/nixos/tests/mollysocket.nix @@ -16,12 +16,10 @@ in { }; testScript = '' - import json - mollysocket.wait_for_unit("mollysocket.service") mollysocket.wait_for_open_port(${toString port}) out = mollysocket.succeed("curl --fail http://127.0.0.1:${toString port}") - assert json.loads(out)["mollysocket"]["version"] == "${toString pkgs.mollysocket.version}" + assert "Version ${pkgs.mollysocket.version}" in out ''; }) diff --git a/nixos/tests/mongodb.nix b/nixos/tests/mongodb.nix index 1a260814f8b8..d82d5080e69c 100644 --- a/nixos/tests/mongodb.nix +++ b/nixos/tests/mongodb.nix @@ -34,7 +34,7 @@ import ./make-test-python.nix ({ pkgs, ... }: node = {...}: { environment.systemPackages = with pkgs; [ # remember to update mongodb.passthru.tests if you change this - mongodb-5_0 + mongodb-7_0 ]; }; }; @@ -42,7 +42,7 @@ import ./make-test-python.nix ({ pkgs, ... }: testScript = '' node.start() '' - + runMongoDBTest pkgs.mongodb-5_0 + + runMongoDBTest pkgs.mongodb-7_0 + '' node.shutdown() ''; diff --git a/nixos/tests/mpd.nix b/nixos/tests/mpd.nix index 0772c05d12ac..e268eb573808 100644 --- a/nixos/tests/mpd.nix +++ b/nixos/tests/mpd.nix @@ -1,12 +1,13 @@ -import ./make-test-python.nix ({ pkgs, lib, ... }: +import ./make-test-python.nix ( + { pkgs, lib, ... }: let track = pkgs.fetchurl { # Sourced from http://freemusicarchive.org/music/Blue_Wave_Theory/Surf_Music_Month_Challenge/Skyhawk_Beach_fade_in - # License: http://creativecommons.org/licenses/by-sa/4.0/ name = "Blue_Wave_Theory-Skyhawk_Beach.mp3"; url = "https://freemusicarchive.org/file/music/ccCommunity/Blue_Wave_Theory/Surf_Music_Month_Challenge/Blue_Wave_Theory_-_04_-_Skyhawk_Beach.mp3"; - sha256 = "0xw417bxkx4gqqy139bb21yldi37xx8xjfxrwaqa0gyw19dl6mgp"; + hash = "sha256-91VDWwrcP6Cw4rk72VHvZ8RGfRBrpRE8xo/02dcJhHc="; + meta.license = lib.licenses.cc-by-sa-40; }; defaultCfg = rec { @@ -16,42 +17,56 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: musicDirectory = "${dataDir}/music"; }; - defaultMpdCfg = with defaultCfg; { - inherit dataDir musicDirectory user group; + defaultMpdCfg = { + inherit (defaultCfg) + dataDir + musicDirectory + user + group + ; enable = true; }; - musicService = { user, group, musicDirectory }: { - description = "Sets up the music file(s) for MPD to use."; - requires = [ "mpd.service" ]; - after = [ "mpd.service" ]; - wantedBy = [ "default.target" ]; - script = '' - cp ${track} ${musicDirectory} - ''; - serviceConfig = { - User = user; - Group = group; + musicService = + { + user, + group, + musicDirectory, + }: + { + description = "Sets up the music file(s) for MPD to use."; + requires = [ "mpd.service" ]; + after = [ "mpd.service" ]; + wantedBy = [ "default.target" ]; + script = '' + cp ${track} ${musicDirectory} + ''; + serviceConfig = { + User = user; + Group = group; + }; }; - }; - mkServer = { mpd, musicService, }: - { boot.kernelModules = [ "snd-dummy" ]; + mkServer = + { mpd, musicService }: + { + boot.kernelModules = [ "snd-dummy" ]; services.mpd = mpd; systemd.services.musicService = musicService; }; - in { + in + { name = "mpd"; - meta = with pkgs.lib.maintainers; { - maintainers = [ emmanuelrosa ]; + meta = { + maintainers = with lib.maintainers; [ emmanuelrosa ]; }; - nodes = - { client = - { ... }: { }; + nodes = { + client = { ... }: { }; serverALSA = - { ... }: lib.mkMerge [ + { ... }: + lib.mkMerge [ (mkServer { mpd = defaultMpdCfg // { network.listenAddress = "any"; @@ -63,13 +78,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: } ''; }; - musicService = with defaultMpdCfg; musicService { inherit user group musicDirectory; }; + musicService = musicService { inherit (defaultMpdCfg) user group musicDirectory; }; }) { networking.firewall.allowedTCPPorts = [ 6600 ]; } ]; serverPulseAudio = - { ... }: lib.mkMerge [ + { ... }: + lib.mkMerge [ (mkServer { mpd = defaultMpdCfg // { extraConfig = '' @@ -80,7 +96,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: ''; }; - musicService = with defaultCfg; musicService { inherit user group musicDirectory; }; + musicService = musicService { inherit (defaultMpdCfg) user group musicDirectory; }; }) { hardware.pulseaudio = { @@ -94,40 +110,41 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: ]; }; - testScript = '' - mpc = "${pkgs.mpc-cli}/bin/mpc --wait" + testScript = '' + mpc = "${lib.getExe pkgs.mpc} --wait" - # Connects to the given server and attempts to play a tune. - def play_some_music(server): - server.wait_for_unit("mpd.service") - server.succeed(f"{mpc} update") - _, tracks = server.execute(f"{mpc} ls") + # Connects to the given server and attempts to play a tune. + def play_some_music(server): + server.wait_for_unit("mpd.service") + server.succeed(f"{mpc} update") + _, tracks = server.execute(f"{mpc} ls") - for track in tracks.splitlines(): - server.succeed(f"{mpc} add {track}") + for track in tracks.splitlines(): + server.succeed(f"{mpc} add {track}") - _, added_tracks = server.execute(f"{mpc} playlist") + _, added_tracks = server.execute(f"{mpc} playlist") - # Check we succeeded adding audio tracks to the playlist - assert len(added_tracks.splitlines()) > 0 + # Check we succeeded adding audio tracks to the playlist + assert len(added_tracks.splitlines()) > 0 - server.succeed(f"{mpc} play") + server.succeed(f"{mpc} play") - _, output = server.execute(f"{mpc} status") - # Assure audio track is playing - assert "playing" in output + _, output = server.execute(f"{mpc} status") + # Assure audio track is playing + assert "playing" in output - server.succeed(f"{mpc} stop") + server.succeed(f"{mpc} stop") - play_some_music(serverALSA) - play_some_music(serverPulseAudio) + play_some_music(serverALSA) + play_some_music(serverPulseAudio) - client.wait_for_unit("multi-user.target") - client.succeed(f"{mpc} -h serverALSA status") + client.wait_for_unit("multi-user.target") + client.succeed(f"{mpc} -h serverALSA status") - # The PulseAudio-based server is configured not to accept external client connections - # to perform the following test: - client.fail(f"{mpc} -h serverPulseAudio status") - ''; -}) + # The PulseAudio-based server is configured not to accept external client connections + # to perform the following test: + client.fail(f"{mpc} -h serverPulseAudio status") + ''; + } +) diff --git a/nixos/tests/mysql/common.nix b/nixos/tests/mysql/common.nix index ad54b0e00c1b..079eff163b1c 100644 --- a/nixos/tests/mysql/common.nix +++ b/nixos/tests/mysql/common.nix @@ -4,7 +4,7 @@ inherit (pkgs) mysql80; }; perconaPackages = { - inherit (pkgs) percona-server_lts percona-server_innovation; + inherit (pkgs) percona-server_8_0 percona-server_8_4; }; mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}"; } diff --git a/nixos/tests/nebula.nix b/nixos/tests/nebula.nix index 6c468153d5b2..124be2332196 100644 --- a/nixos/tests/nebula.nix +++ b/nixos/tests/nebula.nix @@ -136,9 +136,9 @@ in ${name}.start() ${name}.succeed( "mkdir -p /root/.ssh", - "chown 700 /root/.ssh", + "chmod 700 /root/.ssh", "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", - "chown 600 /root/.ssh/id_snakeoil", + "chmod 600 /root/.ssh/id_snakeoil", "mkdir -p /root" ) ''; diff --git a/nixos/tests/networking/networkd-and-scripted.nix b/nixos/tests/networking/networkd-and-scripted.nix index 777c00f74e22..1b6c12eb58b7 100644 --- a/nixos/tests/networking/networkd-and-scripted.nix +++ b/nixos/tests/networking/networkd-and-scripted.nix @@ -132,6 +132,14 @@ let client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'") client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'") + with subtest("Wait until we have received the nameservers"): + if "${builtins.toJSON networkd}" == "true": + client.wait_until_succeeds("resolvectl status enp2s0 | grep -q 2001:db8::1") + client.wait_until_succeeds("resolvectl status enp2s0 | grep -q 192.168.2.1") + else: + client.wait_until_succeeds("resolvconf -l | grep -q 2001:db8::1") + client.wait_until_succeeds("resolvconf -l | grep -q 192.168.2.1") + with subtest("Test vlan 1"): client.wait_until_succeeds("ping -c 1 192.168.1.1") client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1") diff --git a/nixos/tests/networking/router.nix b/nixos/tests/networking/router.nix index e0ad7fa01591..fab21c9e7862 100644 --- a/nixos/tests/networking/router.nix +++ b/nixos/tests/networking/router.nix @@ -72,6 +72,7 @@ AdvSendAdvert on; AdvManagedFlag on; AdvOtherConfigFlag on; + RDNSS 2001:db8::1 {}; prefix fd00:1234:5678:${toString n}::/64 { AdvAutonomous off; diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix index 9f8b06561b07..a1aba5c1ab64 100644 --- a/nixos/tests/nextcloud/default.nix +++ b/nixos/tests/nextcloud/default.nix @@ -44,13 +44,13 @@ let nodes = { client = { ... }: {}; - nextcloud = { + nextcloud = { lib, ... }: { networking.firewall.allowedTCPPorts = [ 80 ]; services.nextcloud = { enable = true; hostName = "nextcloud"; https = false; - database.createLocally = true; + database.createLocally = lib.mkDefault true; config = { adminpassFile = "${pkgs.writeText "adminpass" config.adminpass}"; # Don't try this at home! }; @@ -104,9 +104,10 @@ let }); in map callNextcloudTest [ ./basic.nix + ./with-declarative-redis-and-secrets.nix ./with-mysql-and-memcached.nix ./with-postgresql-and-redis.nix ./with-objectstore.nix ]; in -listToAttrs (concatMap genTests [ 28 29 ]) +listToAttrs (concatMap genTests [ 28 29 30 ]) diff --git a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix index b09ee1276a13..80041ed481b4 100644 --- a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix +++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix @@ -1,29 +1,18 @@ -args@{ nextcloudVersion ? 27, ... }: -(import ../make-test-python.nix ({ pkgs, ...}: let - adminuser = "custom_admin_username"; - # This will be used both for redis and postgresql - pass = "hunter2"; - # Don't do this at home, use a file outside of the nix store instead - passFile = toString (pkgs.writeText "pass-file" '' - ${pass} - ''); -in { - name = "nextcloud-with-declarative-redis"; +{ name, pkgs, testBase, system, ... }: + +with import ../../lib/testing-python.nix { inherit system pkgs; }; +runTest ({ config, ... }: let inherit (config) adminuser; in { + inherit name; meta = with pkgs.lib.maintainers; { maintainers = [ eqyiel ma27 ]; }; - nodes = { - # The only thing the client needs to do is download a file. - client = { ... }: {}; + imports = [ testBase ]; + nodes = { nextcloud = { config, pkgs, ... }: { - networking.firewall.allowedTCPPorts = [ 80 ]; - + environment.systemPackages = [ pkgs.jq ]; services.nextcloud = { - enable = true; - hostName = "nextcloud"; - package = pkgs.${"nextcloud" + (toString nextcloudVersion)}; caching = { apcu = false; redis = true; @@ -35,10 +24,9 @@ in { dbtype = "pgsql"; dbname = "nextcloud"; dbuser = adminuser; - dbpassFile = passFile; - adminuser = adminuser; - adminpassFile = passFile; + dbpassFile = config.services.nextcloud.config.adminpassFile; }; + secretFile = "/etc/nextcloud-secrets.json"; settings = { @@ -68,7 +56,7 @@ in { package = pkgs.postgresql_14; }; systemd.services.postgresql.postStart = pkgs.lib.mkAfter '' - password=$(cat ${passFile}) + password=$(cat ${config.services.nextcloud.config.dbpassFile}) ${config.services.postgresql.package}/bin/psql <<EOF CREATE ROLE ${adminuser} WITH LOGIN PASSWORD '$password' CREATEDB; CREATE DATABASE nextcloud; @@ -89,38 +77,9 @@ in { }; }; - testScript = let - withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.runtimeShell} - export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" - export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${pass})" - "''${@}" - ''; - copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.runtimeShell} - echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file - ''; - - diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.runtimeShell} - diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) - ''; - in '' - start_all() - nextcloud.wait_for_unit("multi-user.target") - nextcloud.succeed("curl -sSf http://nextcloud/login") - nextcloud.succeed( - "${withRcloneEnv} ${copySharedFile}" - ) - client.wait_for_unit("multi-user.target") - client.succeed( - "${withRcloneEnv} ${diffSharedFile}" - ) - - # redis cache should not be empty - nextcloud.fail('test "[]" = "$(redis-cli --json KEYS "*")"') + test-helpers.extraTests = '' + with subtest("non-empty redis cache"): + # redis cache should not be empty + nextcloud.fail('test 0 -lt "$(redis-cli --pass secret --json KEYS "*" | jq "len")"') ''; -})) args +}) diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix index 07a3e56fae4a..b903f8d0fe95 100644 --- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix +++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix @@ -1,8 +1,8 @@ -{ pkgs, testBase, system, ... }: +{ name, pkgs, testBase, system, ... }: with import ../../lib/testing-python.nix { inherit system pkgs; }; runTest ({ config, ... }: { - name = "nextcloud-with-mysql-and-memcached"; + inherit name; meta = with pkgs.lib.maintainers; { maintainers = [ eqyiel ]; }; diff --git a/nixos/tests/nextflow.nix b/nixos/tests/nextflow.nix new file mode 100644 index 000000000000..b4aad98483b9 --- /dev/null +++ b/nixos/tests/nextflow.nix @@ -0,0 +1,60 @@ +import ./make-test-python.nix ( + { pkgs, ... }: + let + bash = pkgs.dockerTools.pullImage { + imageName = "quay.io/nextflow/bash"; + imageDigest = "sha256:bea0e244b7c5367b2b0de687e7d28f692013aa18970941c7dd184450125163ac"; + sha256 = "161s9f24njjx87qrwq0c9nmnwvyc6iblcxka7hirw78lm7i9x4w5"; + finalImageName = "quay.io/nextflow/bash"; + }; + + hello = pkgs.stdenv.mkDerivation { + name = "nextflow-hello"; + src = pkgs.fetchFromGitHub { + owner = "nextflow-io"; + repo = "hello"; + rev = "afff16a9b45c8e8a4f5a3743780ac13a541762f8"; + hash = "sha256-c8FirHc+J5Y439g0BdHxRtXVrOAzIrGEKA0m1mp9b/U="; + }; + installPhase = '' + cp -r $src $out + ''; + }; + run-nextflow-pipeline = pkgs.writeShellApplication { + name = "run-nextflow-pipeline"; + runtimeInputs = [ pkgs.nextflow ]; + text = '' + export NXF_OFFLINE=true + for b in false true; do + echo "docker.enabled = $b" > nextflow.config + cat nextflow.config + nextflow run -ansi-log false ${hello} + done + ''; + }; + in + { + name = "nextflow"; + + nodes.machine = + { ... }: + { + environment.systemPackages = [ + run-nextflow-pipeline + pkgs.nextflow + ]; + virtualisation = { + docker.enable = true; + }; + }; + + testScript = + { nodes, ... }: + '' + start_all() + machine.wait_for_unit("docker.service") + machine.succeed("docker load < ${bash}") + machine.succeed("run-nextflow-pipeline >&2") + ''; + } +) diff --git a/nixos/tests/nginx-mime.nix b/nixos/tests/nginx-mime.nix new file mode 100644 index 000000000000..157b9f13f142 --- /dev/null +++ b/nixos/tests/nginx-mime.nix @@ -0,0 +1,26 @@ +import ./make-test-python.nix ( + { lib, pkgs, ... }: + { + name = "nginx-mime"; + meta.maintainers = with pkgs.lib.maintainers; [ izorkin ]; + + nodes = { + server = + { pkgs, ... }: + { + services.nginx = { + enable = true; + virtualHosts."localhost" = { }; + }; + }; + }; + + testScript = '' + server.start() + server.wait_for_unit("nginx") + # Check optimal size of types_hash + server.fail("journalctl --unit nginx --grep 'could not build optimal types_hash'") + server.shutdown() + ''; + } +) diff --git a/nixos/tests/nix/upgrade.nix b/nixos/tests/nix/upgrade.nix index c55441586b32..e4cd0cb09b8f 100644 --- a/nixos/tests/nix/upgrade.nix +++ b/nixos/tests/nix/upgrade.nix @@ -7,17 +7,6 @@ let ${pkgs.system} = "${nixVersions.latest}"; }''; - inputDrv = import ../.. { - configuration = { - imports = [ nixos-module ]; - nix.package = nixVersions.latest; - boot.isContainer = true; - - users.users.alice.isNormalUser = true; - }; - system = pkgs.system; - }; - nixos-module = builtins.toFile "nixos-module.nix" '' { lib, pkgs, modulesPath, ... }: { @@ -53,8 +42,13 @@ pkgs.testers.nixosTest { nix.package = nixVersions.stable; system.extraDependencies = [ fallback-paths-external - inputDrv.system ]; + + specialisation.newer-nix.configuration = { + nix.package = lib.mkForce nixVersions.latest; + + users.users.alice.isNormalUser = true; + }; }; testScript = '' @@ -91,7 +85,7 @@ pkgs.testers.nixosTest { with subtest("upgrade-via-switch-to-configuration"): # not using nixos-rebuild due to nix-instantiate being called and forcing all drv's to be rebuilt - print(machine.succeed("${inputDrv.system.outPath}/bin/switch-to-configuration switch")) + print(machine.succeed("/run/current-system/specialisation/newer-nix/bin/switch-to-configuration switch")) result = machine.succeed("nix --version") print(result) diff --git a/nixos/tests/nixos-rebuild-specialisations.nix b/nixos/tests/nixos-rebuild-specialisations.nix index ab67bbaba676..f12d0fc48ce9 100644 --- a/nixos/tests/nixos-rebuild-specialisations.nix +++ b/nixos/tests/nixos-rebuild-specialisations.nix @@ -1,6 +1,10 @@ -import ./make-test-python.nix ({ pkgs, ... }: { +{ hostPkgs, ... }: { name = "nixos-rebuild-specialisations"; + # TODO: remove overlay from nixos/modules/profiles/installation-device.nix + # make it a _small package instead, then remove pkgsReadOnly = false;. + node.pkgsReadOnly = false; + nodes = { machine = { lib, pkgs, ... }: { imports = [ @@ -32,7 +36,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { testScript = let - configFile = pkgs.writeText "configuration.nix" '' + configFile = hostPkgs.writeText "configuration.nix" '' { lib, pkgs, ... }: { imports = [ ./hardware-configuration.nix @@ -119,4 +123,4 @@ import ./make-test-python.nix ({ pkgs, ... }: { machine.fail("nixos-rebuild boot --specialisation foo") machine.fail("nixos-rebuild boot -c foo") ''; -}) +} diff --git a/nixos/tests/nixos-rebuild-target-host.nix b/nixos/tests/nixos-rebuild-target-host.nix index bf80b2fa6606..0e4c3e5f3999 100644 --- a/nixos/tests/nixos-rebuild-target-host.nix +++ b/nixos/tests/nixos-rebuild-target-host.nix @@ -1,8 +1,12 @@ -import ./make-test-python.nix ({ pkgs, ... }: { +{ hostPkgs, ... }: { name = "nixos-rebuild-target-host"; + # TODO: remove overlay from nixos/modules/profiles/installation-device.nix + # make it a _small package instead, then remove pkgsReadOnly = false;. + node.pkgsReadOnly = false; + nodes = { - deployer = { lib, ... }: let + deployer = { lib, pkgs, ... }: let inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; in { imports = [ ../modules/profiles/installation-device.nix ]; @@ -24,6 +28,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { system.build.privateKey = snakeOilPrivateKey; system.build.publicKey = snakeOilPublicKey; + # We don't switch on `deployer`, but we need it to have the dependencies + # available, to be picked up by system.includeBuildDependencies above. + system.switch.enable = true; }; target = { nodes, lib, ... }: let @@ -55,6 +62,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { system.build = { inherit targetConfig; }; + system.switch.enable = true; networking.hostName = "target"; } @@ -69,13 +77,13 @@ import ./make-test-python.nix ({ pkgs, ... }: { StrictHostKeyChecking=no ''; - targetConfigJSON = pkgs.writeText "target-configuration.json" + targetConfigJSON = hostPkgs.writeText "target-configuration.json" (builtins.toJSON nodes.target.system.build.targetConfig); - targetNetworkJSON = pkgs.writeText "target-network.json" + targetNetworkJSON = hostPkgs.writeText "target-network.json" (builtins.toJSON nodes.target.system.build.networkConfig); - configFile = hostname: pkgs.writeText "configuration.nix" '' + configFile = hostname: hostPkgs.writeText "configuration.nix" '' { lib, modulesPath, ... }: { imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") @@ -138,4 +146,4 @@ import ./make-test-python.nix ({ pkgs, ... }: { deployer.succeed(f"mkdir -p {tmp_dir}") deployer.succeed(f"TMPDIR={tmp_dir} nixos-rebuild switch -I nixos-config=/root/configuration-1.nix --target-host root@target &>/dev/console") ''; -}) +} diff --git a/nixos/tests/non-default-filesystems.nix b/nixos/tests/non-default-filesystems.nix index 08a17107dd2f..98abe1cbc175 100644 --- a/nixos/tests/non-default-filesystems.nix +++ b/nixos/tests/non-default-filesystems.nix @@ -82,8 +82,9 @@ with pkgs.lib; machine.wait_for_unit("multi-user.target") with subtest("BTRFS filesystems are mounted correctly"): - machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts") - machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts") + print("output of \"grep -E '/dev/vda' /proc/mounts\":\n" + machine.execute("grep -E '/dev/vda' /proc/mounts")[1]) + machine.succeed("grep -E '/dev/vda / btrfs rw,.*subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts") + machine.succeed("grep -E '/dev/vda /home btrfs rw,.*subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts") ''; }; diff --git a/nixos/tests/ntpd.nix b/nixos/tests/ntpd.nix new file mode 100644 index 000000000000..1864044b64d4 --- /dev/null +++ b/nixos/tests/ntpd.nix @@ -0,0 +1,25 @@ +import ./make-test-python.nix ( + { lib, ... }: + { + name = "ntpd"; + + meta = { + maintainers = with lib.maintainers; [ pyrox0 ]; + }; + + nodes.machine = { + services.ntp = { + enable = true; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit('ntpd.service') + machine.wait_for_console_text('Listen normally on 10 eth*') + machine.succeed('systemctl is-active ntpd.service') + machine.succeed('ntpq -p') + ''; + } +) diff --git a/nixos/tests/obs-studio.nix b/nixos/tests/obs-studio.nix new file mode 100644 index 000000000000..a1b5bacf0428 --- /dev/null +++ b/nixos/tests/obs-studio.nix @@ -0,0 +1,40 @@ +import ./make-test-python.nix ( + { ... }: + + { + name = "obs-studio"; + + nodes.machine = + { pkgs, ... }: + { + imports = [ + ./common/x11.nix + ./common/user-account.nix + ]; + + programs.obs-studio = { + enable = true; + plugins = with pkgs.obs-studio-plugins; [ + wlrobs + obs-vkcapture + ]; + enableVirtualCamera = true; + }; + }; + + testScript = '' + machine.wait_for_x() + machine.succeed("obs --version") + + # virtual camera tests + machine.succeed("lsmod | grep v4l2loopback") + machine.succeed("ls /dev/video1") + machine.succeed("obs --startvirtualcam >&2 &") + machine.wait_for_window("OBS") + machine.sleep(5) + + # test plugins + machine.succeed("which obs-vkcapture") + ''; + } +) diff --git a/nixos/tests/open-webui.nix b/nixos/tests/open-webui.nix index faf4dae671d0..27f913dbffd5 100644 --- a/nixos/tests/open-webui.nix +++ b/nixos/tests/open-webui.nix @@ -31,6 +31,7 @@ in testScript = '' import json + import xml.etree.ElementTree as xml machine.start() @@ -45,5 +46,18 @@ in # Check that the name was overridden via the environmentFile option. assert webui_config["name"] == "${webuiName} (Open WebUI)" + + webui_opensearch_xml = machine.succeed("curl http://127.0.0.1:${mainPort}/opensearch.xml") + webui_opensearch = xml.fromstring(webui_opensearch_xml) + + webui_opensearch_url = webui_opensearch.find( + ".//{http://a9.com/-/spec/opensearch/1.1/}Url" + ) + assert ( + webui_opensearch_url is not None + ), f"no url tag found in {webui_opensearch_xml}" + assert ( + webui_opensearch_url.get("template") == "http://localhost:8080/?q={searchTerms}" + ), "opensearch url doesn't match the configured port" ''; } diff --git a/nixos/tests/openresty-lua.nix b/nixos/tests/openresty-lua.nix index 9e987398f51d..e3629e9ca40a 100644 --- a/nixos/tests/openresty-lua.nix +++ b/nixos/tests/openresty-lua.nix @@ -1,12 +1,11 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let - lualibs = [ + luaLibs = [ pkgs.lua.pkgs.markdown ]; - getPath = lib: type: "${lib}/share/lua/${pkgs.lua.luaversion}/?.${type}"; - getLuaPath = lib: getPath lib "lua"; - luaPath = lib.concatStringsSep ";" (map getLuaPath lualibs); + getLuaPath = lib: "${lib}/share/lua/${pkgs.lua.luaversion}/?.lua"; + luaPath = lib.concatStringsSep ";" (map getLuaPath luaLibs); in { name = "openresty-lua"; diff --git a/nixos/tests/openvswitch.nix b/nixos/tests/openvswitch.nix new file mode 100644 index 000000000000..679641717a1f --- /dev/null +++ b/nixos/tests/openvswitch.nix @@ -0,0 +1,62 @@ +{ + name = "openvswitch"; + + nodes = { + node1 = { + virtualisation.vlans = [ 1 ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + + vswitches.vs0 = { + interfaces = { + eth1 = { }; + }; + }; + + }; + + systemd.network.networks."40-vs0" = { + name = "vs0"; + networkConfig.Address = "10.0.0.1/24"; + }; + + }; + + node2 = { + virtualisation.vlans = [ 1 ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + + vswitches.vs0 = { + interfaces = { + eth1 = { }; + }; + }; + + }; + + systemd.network.networks."40-vs0" = { + name = "vs0"; + networkConfig.Address = "10.0.0.2/24"; + }; + }; + }; + + testScript = # python + '' + start_all() + node1.wait_for_unit("ovsdb.service") + node1.wait_for_unit("ovs-vswitchd.service") + node2.wait_for_unit("ovsdb.service") + node2.wait_for_unit("ovs-vswitchd.service") + + node1.succeed("ping -c3 10.0.0.2") + node2.succeed("ping -c3 10.0.0.1") + ''; +} diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix index 034de0620d88..18b646c80890 100644 --- a/nixos/tests/os-prober.nix +++ b/nixos/tests/os-prober.nix @@ -84,6 +84,10 @@ in { docbook5 docbook_xsl_ns grub2 + nixos-artwork.wallpapers.simple-dark-gray-bootloader + perlPackages.FileCopyRecursive + perlPackages.XMLSAX + perlPackages.XMLSAXBase kbd kbd.dev kmod.dev @@ -92,16 +96,20 @@ in { libxml2.bin libxslt.bin nixos-artwork.wallpapers.simple-dark-gray-bottom - ntp + perlPackages.ConfigIniFiles + perlPackages.FileSlurp + perlPackages.JSON perlPackages.ListCompare perlPackages.XMLLibXML - python3 + # make-options-doc/default.nix + (python3.withPackages (p: [ p.mistune ])) shared-mime-info - stdenv sudo + switch-to-configuration-ng texinfo unionfs-fuse xorg.lndir + os-prober # add curl so that rather than seeing the test attempt to download # curl's tarball, we see what it's trying to download diff --git a/nixos/tests/pg_anonymizer.nix b/nixos/tests/pg_anonymizer.nix deleted file mode 100644 index b26e4dca0580..000000000000 --- a/nixos/tests/pg_anonymizer.nix +++ /dev/null @@ -1,94 +0,0 @@ -import ./make-test-python.nix ({ pkgs, lib, ... }: { - name = "pg_anonymizer"; - meta.maintainers = lib.teams.flyingcircus.members; - - nodes.machine = { pkgs, ... }: { - environment.systemPackages = [ pkgs.pg-dump-anon ]; - services.postgresql = { - enable = true; - extraPlugins = ps: [ ps.anonymizer ]; - settings.shared_preload_libraries = [ "anon" ]; - }; - }; - - testScript = '' - start_all() - machine.wait_for_unit("multi-user.target") - machine.wait_for_unit("postgresql.service") - - with subtest("Setup"): - machine.succeed("sudo -u postgres psql --command 'create database demo'") - machine.succeed( - "sudo -u postgres psql -d demo -f ${pkgs.writeText "init.sql" '' - create extension anon cascade; - select anon.init(); - create table player(id serial, name text, points int); - insert into player(id,name,points) values (1,'Foo', 23); - insert into player(id,name,points) values (2,'Bar',42); - security label for anon on column player.name is 'MASKED WITH FUNCTION anon.fake_last_name();'; - security label for anon on column player.points is 'MASKED WITH VALUE NULL'; - ''}" - ) - - def get_player_table_contents(): - return [ - x.split(',') for x in machine.succeed("sudo -u postgres psql -d demo --csv --command 'select * from player'").splitlines()[1:] - ] - - def check_anonymized_row(row, id, original_name): - assert row[0] == id, f"Expected first row to have ID {id}, but got {row[0]}" - assert row[1] != original_name, f"Expected first row to have a name other than {original_name}" - assert not bool(row[2]), "Expected points to be NULL in first row" - - def find_xsv_in_dump(dump, sep=','): - """ - Expecting to find a CSV (for pg_dump_anon) or TSV (for pg_dump) structure, looking like - - COPY public.player ... - 1,Shields, - 2,Salazar, - \. - - in the given dump (the commas are tabs in case of pg_dump). - Extract the CSV lines and split by `sep`. - """ - - try: - from itertools import dropwhile, takewhile - return [x.split(sep) for x in list(takewhile( - lambda x: x != "\\.", - dropwhile( - lambda x: not x.startswith("COPY public.player"), - dump.splitlines() - ) - ))[1:]] - except: - print(f"Dump to process: {dump}") - raise - - def check_original_data(output): - assert output[0] == ['1','Foo','23'], f"Expected first row from player table to be 1,Foo,23; got {output[0]}" - assert output[1] == ['2','Bar','42'], f"Expected first row from player table to be 2,Bar,42; got {output[1]}" - - def check_anonymized_rows(output): - check_anonymized_row(output[0], '1', 'Foo') - check_anonymized_row(output[1], '2', 'Bar') - - with subtest("Check initial state"): - check_original_data(get_player_table_contents()) - - with subtest("Anonymous dumps"): - check_original_data(find_xsv_in_dump( - machine.succeed("sudo -u postgres pg_dump demo"), - sep='\t' - )) - check_anonymized_rows(find_xsv_in_dump( - machine.succeed("sudo -u postgres pg_dump_anon -U postgres -h /run/postgresql -d demo"), - sep=',' - )) - - with subtest("Anonymize"): - machine.succeed("sudo -u postgres psql -d demo --command 'select anon.anonymize_database();'") - check_anonymized_rows(get_player_table_contents()) - ''; -}) diff --git a/nixos/tests/pgjwt.nix b/nixos/tests/pgjwt.nix deleted file mode 100644 index 8d3310b74eb3..000000000000 --- a/nixos/tests/pgjwt.nix +++ /dev/null @@ -1,34 +0,0 @@ -import ./make-test-python.nix ({ pkgs, lib, ...}: - -with pkgs; { - name = "pgjwt"; - meta = with lib.maintainers; { - maintainers = [ spinus willibutz ]; - }; - - nodes = { - master = { ... }: - { - services.postgresql = { - enable = true; - extraPlugins = ps: with ps; [ pgjwt pgtap ]; - }; - }; - }; - - testScript = { nodes, ... }: - let - sqlSU = "${nodes.master.config.services.postgresql.superUser}"; - pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}"; - in - '' - start_all() - master.wait_for_unit("postgresql") - master.succeed( - "${pkgs.gnused}/bin/sed -e '12 i CREATE EXTENSION pgcrypto;\\nCREATE EXTENSION pgtap;\\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > /tmp/test.sql" - ) - master.succeed( - "${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql" - ) - ''; -}) diff --git a/nixos/tests/pgvecto-rs.nix b/nixos/tests/pgvecto-rs.nix deleted file mode 100644 index 8d9d6c0b88f5..000000000000 --- a/nixos/tests/pgvecto-rs.nix +++ /dev/null @@ -1,76 +0,0 @@ -# mostly copied from ./timescaledb.nix which was copied from ./postgresql.nix -# as it seemed unapproriate to test additional extensions for postgresql there. - -{ system ? builtins.currentSystem -, config ? { } -, pkgs ? import ../.. { inherit system config; } -}: - -with import ../lib/testing-python.nix { inherit system pkgs; }; -with pkgs.lib; - -let - postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs; - # Test cases from https://docs.pgvecto.rs/use-cases/hybrid-search.html - test-sql = pkgs.writeText "postgresql-test" '' - CREATE EXTENSION vectors; - - CREATE TABLE items ( - id bigserial PRIMARY KEY, - content text NOT NULL, - embedding vectors.vector(3) NOT NULL -- 3 dimensions - ); - - INSERT INTO items (content, embedding) VALUES - ('a fat cat sat on a mat and ate a fat rat', '[1, 2, 3]'), - ('a fat dog sat on a mat and ate a fat rat', '[4, 5, 6]'), - ('a thin cat sat on a mat and ate a thin rat', '[7, 8, 9]'), - ('a thin dog sat on a mat and ate a thin rat', '[10, 11, 12]'); - ''; - make-postgresql-test = postgresql-name: postgresql-package: makeTest { - name = postgresql-name; - meta = with pkgs.lib.maintainers; { - maintainers = [ diogotcorreia ]; - }; - - nodes.machine = { ... }: - { - services.postgresql = { - enable = true; - package = postgresql-package; - extraPlugins = ps: with ps; [ - pgvecto-rs - ]; - settings.shared_preload_libraries = "vectors"; - }; - }; - - testScript = '' - def check_count(statement, lines): - return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( - statement, lines - ) - - - machine.start() - machine.wait_for_unit("postgresql") - - with subtest("Postgresql with extension vectors is available just after unit start"): - machine.succeed(check_count("SELECT * FROM pg_available_extensions WHERE name = 'vectors' AND default_version = '${postgresql-package.pkgs.pgvecto-rs.version}';", 1)) - - machine.succeed("sudo -u postgres psql -f ${test-sql}") - - machine.succeed(check_count("SELECT content, embedding FROM items WHERE to_tsvector('english', content) @@ 'cat & rat'::tsquery;", 2)) - - machine.shutdown() - ''; - - }; - applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "14") postgresql-versions; -in -mapAttrs' - (name: package: { - inherit name; - value = make-postgresql-test name package; - }) - applicablePostgresqlVersions diff --git a/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix index 721f27e8f8c6..9e1bc1ccefbf 100644 --- a/nixos/tests/pleroma.nix +++ b/nixos/tests/pleroma.nix @@ -32,18 +32,18 @@ import ./make-test-python.nix ({ pkgs, ... }: # system one. Overriding this pretty bad default behaviour. export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt - toot --debug login_cli -i "pleroma.nixos.test" -e "jamy@nixos.test" -p "jamy-password" + toot login_cli -i "pleroma.nixos.test" -e "jamy@nixos.test" -p 'jamy-password' echo "Login OK" # Send a toot then verify it's part of the public timeline - echo "y" | toot post "hello world Jamy here" + toot post "hello world Jamy here" echo "Send toot OK" - echo "y" | toot timeline | grep -c "hello world Jamy here" + toot timeline -1 | grep -F -q "hello world Jamy here" echo "Get toot from timeline OK" # Test file upload - echo "y" | toot upload ${db-seed} | grep -c "https://pleroma.nixos.test/media" - echo "File upload OK" + echo "y" | ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) \ + | grep -F -q "https://pleroma.nixos.test/media" echo "=====================================================" echo "= SUCCESS =" @@ -244,6 +244,7 @@ import ./make-test-python.nix ({ pkgs, ... }: testScript = { nodes, ... }: '' pleroma.wait_for_unit("postgresql.service") + pleroma.wait_until_succeeds("ls /var/lib/pleroma") pleroma.succeed("provision-db") pleroma.wait_for_file("/var/lib/pleroma") pleroma.succeed("provision-secrets") diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix deleted file mode 100644 index dacf4e576c07..000000000000 --- a/nixos/tests/postgis.nix +++ /dev/null @@ -1,30 +0,0 @@ -import ./make-test-python.nix ({ pkgs, ...} : { - name = "postgis"; - meta = with pkgs.lib.maintainers; { - maintainers = [ lsix ]; - }; - - nodes = { - master = - { pkgs, ... }: - - { - services.postgresql = { - enable = true; - package = pkgs.postgresql; - extraPlugins = ps: with ps; [ - postgis - ]; - }; - }; - }; - - testScript = '' - start_all() - master.wait_for_unit("postgresql") - master.sleep(10) # Hopefully this is long enough!! - master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'") - master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_raster;'") - master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'") - ''; -}) diff --git a/nixos/tests/postgresql-jit.nix b/nixos/tests/postgresql-jit.nix deleted file mode 100644 index f4b1d07a7faf..000000000000 --- a/nixos/tests/postgresql-jit.nix +++ /dev/null @@ -1,55 +0,0 @@ -{ system ? builtins.currentSystem -, config ? {} -, pkgs ? import ../.. { inherit system config; } -, package ? null -}: - -with import ../lib/testing-python.nix { inherit system pkgs; }; - -let - inherit (pkgs) lib; - packages = builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs); - - mkJitTestFromName = name: - mkJitTest pkgs.${name}; - - mkJitTest = package: makeTest { - name = package.name; - meta.maintainers = with lib.maintainers; [ ma27 ]; - nodes.machine = { pkgs, lib, ... }: { - services.postgresql = { - inherit package; - enable = true; - enableJIT = true; - initialScript = pkgs.writeText "init.sql" '' - create table demo (id int); - insert into demo (id) select generate_series(1, 5); - ''; - }; - }; - testScript = '' - machine.start() - machine.wait_for_unit("postgresql.service") - - with subtest("JIT is enabled"): - machine.succeed("sudo -u postgres psql <<<'show jit;' | grep 'on'") - - with subtest("Test JIT works fine"): - output = machine.succeed( - "cat ${pkgs.writeText "test.sql" '' - set jit_above_cost = 1; - EXPLAIN ANALYZE SELECT CONCAT('jit result = ', SUM(id)) FROM demo; - SELECT CONCAT('jit result = ', SUM(id)) from demo; - ''} | sudo -u postgres psql" - ) - assert "JIT:" in output - assert "jit result = 15" in output - - machine.shutdown() - ''; - }; -in -if package == null then - lib.genAttrs packages mkJitTestFromName -else - mkJitTest package diff --git a/nixos/tests/postgresql-tls-client-cert.nix b/nixos/tests/postgresql-tls-client-cert.nix deleted file mode 100644 index c1678ed733be..000000000000 --- a/nixos/tests/postgresql-tls-client-cert.nix +++ /dev/null @@ -1,141 +0,0 @@ -{ system ? builtins.currentSystem -, config ? { } -, pkgs ? import ../.. { inherit system config; } -, package ? null -}: - -with import ../lib/testing-python.nix { inherit system pkgs; }; - -let - lib = pkgs.lib; - - # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`. - makeTestAttribute = name: - { - inherit name; - value = makePostgresqlTlsClientCertTest pkgs."${name}"; - }; - - makePostgresqlTlsClientCertTest = pkg: - let - runWithOpenSSL = file: cmd: pkgs.runCommand file - { - buildInputs = [ pkgs.openssl ]; - } - cmd; - caKey = runWithOpenSSL "ca.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out"; - caCert = runWithOpenSSL - "ca.crt" - '' - openssl req -new -x509 -sha256 -key ${caKey} -out $out -subj "/CN=test.example" -days 36500 - ''; - serverKey = - runWithOpenSSL "server.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out"; - serverKeyPath = "/var/lib/postgresql"; - serverCert = - runWithOpenSSL "server.crt" '' - openssl req -new -sha256 -key ${serverKey} -out server.csr -subj "/CN=db.test.example" - openssl x509 -req -in server.csr -CA ${caCert} -CAkey ${caKey} \ - -CAcreateserial -out $out -days 36500 -sha256 - ''; - clientKey = - runWithOpenSSL "client.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out"; - clientCert = - runWithOpenSSL "client.crt" '' - openssl req -new -sha256 -key ${clientKey} -out client.csr -subj "/CN=test" - openssl x509 -req -in client.csr -CA ${caCert} -CAkey ${caKey} \ - -CAcreateserial -out $out -days 36500 -sha256 - ''; - clientKeyPath = "/root"; - - in - makeTest { - name = "postgresql-tls-client-cert-${pkg.name}"; - meta.maintainers = with lib.maintainers; [ erictapen ]; - - nodes.server = { ... }: { - system.activationScripts = { - keyPlacement.text = '' - mkdir -p '${serverKeyPath}' - cp '${serverKey}' '${serverKeyPath}/server.key' - chown postgres:postgres '${serverKeyPath}/server.key' - chmod 600 '${serverKeyPath}/server.key' - ''; - }; - services.postgresql = { - package = pkg; - enable = true; - enableTCPIP = true; - ensureUsers = [ - { - name = "test"; - ensureDBOwnership = true; - } - ]; - ensureDatabases = [ "test" ]; - settings = { - ssl = "on"; - ssl_ca_file = toString caCert; - ssl_cert_file = toString serverCert; - ssl_key_file = "${serverKeyPath}/server.key"; - }; - authentication = '' - hostssl test test ::/0 cert clientcert=verify-full - ''; - }; - networking = { - interfaces.eth1 = { - ipv6.addresses = [ - { address = "fc00::1"; prefixLength = 120; } - ]; - }; - firewall.allowedTCPPorts = [ 5432 ]; - }; - }; - - nodes.client = { ... }: { - system.activationScripts = { - keyPlacement.text = '' - mkdir -p '${clientKeyPath}' - cp '${clientKey}' '${clientKeyPath}/client.key' - chown root:root '${clientKeyPath}/client.key' - chmod 600 '${clientKeyPath}/client.key' - ''; - }; - environment = { - variables = { - PGHOST = "db.test.example"; - PGPORT = "5432"; - PGDATABASE = "test"; - PGUSER = "test"; - PGSSLMODE = "verify-full"; - PGSSLCERT = clientCert; - PGSSLKEY = "${clientKeyPath}/client.key"; - PGSSLROOTCERT = caCert; - }; - systemPackages = [ pkg ]; - }; - networking = { - interfaces.eth1 = { - ipv6.addresses = [ - { address = "fc00::2"; prefixLength = 120; } - ]; - }; - hosts = { "fc00::1" = [ "db.test.example" ]; }; - }; - }; - - testScript = '' - server.wait_for_unit("multi-user.target") - client.wait_for_unit("multi-user.target") - client.succeed("psql -c \"SELECT 1;\"") - ''; - }; - -in -if package == null then -# all-tests.nix: Maps the generic function over all attributes of PostgreSQL packages - builtins.listToAttrs (map makeTestAttribute (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs))) -else -# Called directly from <package>.tests - makePostgresqlTlsClientCertTest package diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix deleted file mode 100644 index ab2ab4ad0d4f..000000000000 --- a/nixos/tests/postgresql-wal-receiver.nix +++ /dev/null @@ -1,119 +0,0 @@ -{ system ? builtins.currentSystem, - config ? {}, - pkgs ? import ../.. { inherit system config; }, - package ? null -}: - -with import ../lib/testing-python.nix { inherit system pkgs; }; - -let - lib = pkgs.lib; - - # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`. - makeTestAttribute = name: - { - inherit name; - value = makePostgresqlWalReceiverTest pkgs."${name}"; - }; - - makePostgresqlWalReceiverTest = pkg: - let - postgresqlDataDir = "/var/lib/postgresql/${pkg.psqlSchema}"; - replicationUser = "wal_receiver_user"; - replicationSlot = "wal_receiver_slot"; - replicationConn = "postgresql://${replicationUser}@localhost"; - baseBackupDir = "/tmp/pg_basebackup"; - walBackupDir = "/tmp/pg_wal"; - - recoveryFile = pkgs.writeTextDir "recovery.signal" ""; - - in makeTest { - name = "postgresql-wal-receiver-${pkg.name}"; - meta.maintainers = with lib.maintainers; [ pacien ]; - - nodes.machine = { ... }: { - services.postgresql = { - package = pkg; - enable = true; - settings = { - max_replication_slots = 10; - max_wal_senders = 10; - recovery_end_command = "touch recovery.done"; - restore_command = "cp ${walBackupDir}/%f %p"; - wal_level = "archive"; # alias for replica on pg >= 9.6 - }; - authentication = '' - host replication ${replicationUser} all trust - ''; - initialScript = pkgs.writeText "init.sql" '' - create user ${replicationUser} replication; - select * from pg_create_physical_replication_slot('${replicationSlot}'); - ''; - }; - - services.postgresqlWalReceiver.receivers.main = { - postgresqlPackage = pkg; - connection = replicationConn; - slot = replicationSlot; - directory = walBackupDir; - }; - # This is only to speedup test, it isn't time racing. Service is set to autorestart always, - # default 60sec is fine for real system, but is too much for a test - systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5; - }; - - testScript = '' - # make an initial base backup - machine.wait_for_unit("postgresql") - machine.wait_for_unit("postgresql-wal-receiver-main") - # WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other - # required only for 9.4 - machine.sleep(5) - machine.succeed( - "${pkg}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}" - ) - - # create a dummy table with 100 records - machine.succeed( - "sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'" - ) - - # stop postgres and destroy data - machine.systemctl("stop postgresql") - machine.systemctl("stop postgresql-wal-receiver-main") - machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}") - - # restore the base backup - machine.succeed( - "cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}" - ) - - # prepare WAL and recovery - machine.succeed("chmod a+rX -R ${walBackupDir}") - machine.execute( - "for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done" - ) # make use of partial segments too - machine.succeed( - "cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*" - ) - - # replay WAL - machine.systemctl("start postgresql") - machine.wait_for_file("${postgresqlDataDir}/recovery.done") - machine.systemctl("restart postgresql") - machine.wait_for_unit("postgresql") - - # check that our records have been restored - machine.succeed( - "test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100" - ) - ''; - }; - -in -if package == null then - # all-tests.nix: Maps the generic function over all attributes of PostgreSQL packages - builtins.listToAttrs (map makeTestAttribute (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs))) -else - # Called directly from <package>.tests - makePostgresqlWalReceiverTest package diff --git a/nixos/tests/postgresql-wal2json.nix b/nixos/tests/postgresql-wal2json.nix deleted file mode 100644 index 043ad48cbc6e..000000000000 --- a/nixos/tests/postgresql-wal2json.nix +++ /dev/null @@ -1,60 +0,0 @@ -{ - system ? builtins.currentSystem, - config ? { }, - pkgs ? import ../.. { inherit system config; }, - postgresql ? null, -}: - -let - makeTest = import ./make-test-python.nix; - # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`. - makeTestAttribute = name: { - inherit name; - value = makePostgresqlWal2jsonTest pkgs."${name}"; - }; - - makePostgresqlWal2jsonTest = - postgresqlPackage: - makeTest { - name = "postgresql-wal2json-${postgresqlPackage.name}"; - meta.maintainers = with pkgs.lib.maintainers; [ euank ]; - - nodes.machine = { - services.postgresql = { - package = postgresqlPackage; - enable = true; - extraPlugins = with postgresqlPackage.pkgs; [ wal2json ]; - settings = { - wal_level = "logical"; - max_replication_slots = "10"; - max_wal_senders = "10"; - }; - }; - }; - - testScript = '' - machine.wait_for_unit("postgresql") - machine.succeed( - "sudo -u postgres psql -qAt -f ${./postgresql/wal2json/example2.sql} postgres > /tmp/example2.out" - ) - machine.succeed( - "diff ${./postgresql/wal2json/example2.out} /tmp/example2.out" - ) - machine.succeed( - "sudo -u postgres psql -qAt -f ${./postgresql/wal2json/example3.sql} postgres > /tmp/example3.out" - ) - machine.succeed( - "diff ${./postgresql/wal2json/example3.out} /tmp/example3.out" - ) - ''; - }; - -in -# By default, create one test per postgresql version -if postgresql == null then - builtins.listToAttrs ( - map makeTestAttribute (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs)) - ) -# but if postgresql is set, we're being made as a passthru test for a specific postgres + wal2json version, just run one -else - makePostgresqlWal2jsonTest postgresql diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix deleted file mode 100644 index c0dd24cf6ad2..000000000000 --- a/nixos/tests/postgresql.nix +++ /dev/null @@ -1,224 +0,0 @@ -{ system ? builtins.currentSystem, - config ? {}, - pkgs ? import ../.. { inherit system config; } -}: - -with import ../lib/testing-python.nix { inherit system pkgs; }; -with pkgs.lib; - -let - postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs; - test-sql = pkgs.writeText "postgresql-test" '' - CREATE EXTENSION pgcrypto; -- just to check if lib loading works - CREATE TABLE sth ( - id int - ); - INSERT INTO sth (id) VALUES (1); - INSERT INTO sth (id) VALUES (1); - INSERT INTO sth (id) VALUES (1); - INSERT INTO sth (id) VALUES (1); - INSERT INTO sth (id) VALUES (1); - CREATE TABLE xmltest ( doc xml ); - INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled - ''; - make-postgresql-test = postgresql-name: postgresql-package: backup-all: makeTest { - name = postgresql-name; - meta = with pkgs.lib.maintainers; { - maintainers = [ zagy ]; - }; - - nodes.machine = {...}: - { - services.postgresql = { - enable = true; - package = postgresql-package; - }; - - services.postgresqlBackup = { - enable = true; - databases = optional (!backup-all) "postgres"; - }; - }; - - testScript = let - backupName = if backup-all then "all" else "postgres"; - backupService = if backup-all then "postgresqlBackup" else "postgresqlBackup-postgres"; - backupFileBase = "/var/backup/postgresql/${backupName}"; - in '' - def check_count(statement, lines): - return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( - statement, lines - ) - - - machine.start() - machine.wait_for_unit("postgresql") - - with subtest("Postgresql is available just after unit start"): - machine.succeed( - "cat ${test-sql} | sudo -u postgres psql" - ) - - with subtest("Postgresql survives restart (bug #1735)"): - machine.shutdown() - import time - time.sleep(2) - machine.start() - machine.wait_for_unit("postgresql") - - machine.fail(check_count("SELECT * FROM sth;", 3)) - machine.succeed(check_count("SELECT * FROM sth;", 5)) - machine.fail(check_count("SELECT * FROM sth;", 4)) - machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1)) - - with subtest("Backup service works"): - machine.succeed( - "systemctl start ${backupService}.service", - "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'", - "ls -hal /var/backup/postgresql/ >/dev/console", - "stat -c '%a' ${backupFileBase}.sql.gz | grep 600", - ) - with subtest("Backup service removes prev files"): - machine.succeed( - # Create dummy prev files. - "touch ${backupFileBase}.prev.sql{,.gz,.zstd}", - "chown postgres:postgres ${backupFileBase}.prev.sql{,.gz,.zstd}", - - # Run backup. - "systemctl start ${backupService}.service", - "ls -hal /var/backup/postgresql/ >/dev/console", - - # Since nothing has changed in the database, the cur and prev files - # should match. - "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'", - "cmp ${backupFileBase}.sql.gz ${backupFileBase}.prev.sql.gz", - - # The prev files with unused suffix should be removed. - "[ ! -f '${backupFileBase}.prev.sql' ]", - "[ ! -f '${backupFileBase}.prev.sql.zstd' ]", - - # Both cur and prev file should only be accessible by the postgres user. - "stat -c '%a' ${backupFileBase}.sql.gz | grep 600", - "stat -c '%a' '${backupFileBase}.prev.sql.gz' | grep 600", - ) - with subtest("Backup service fails gracefully"): - # Sabotage the backup process - machine.succeed("rm /run/postgresql/.s.PGSQL.5432") - machine.fail( - "systemctl start ${backupService}.service", - ) - machine.succeed( - "ls -hal /var/backup/postgresql/ >/dev/console", - "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'", - "stat ${backupFileBase}.in-progress.sql.gz", - ) - # In a previous version, the second run would overwrite prev.sql.gz, - # so we test a second run as well. - machine.fail( - "systemctl start ${backupService}.service", - ) - machine.succeed( - "stat ${backupFileBase}.in-progress.sql.gz", - "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'", - ) - - - with subtest("Initdb works"): - machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2") - - machine.shutdown() - ''; - - }; - - mk-ensure-clauses-test = postgresql-name: postgresql-package: makeTest { - name = postgresql-name; - meta = with pkgs.lib.maintainers; { - maintainers = [ zagy ]; - }; - - nodes.machine = {...}: - { - services.postgresql = { - enable = true; - package = postgresql-package; - ensureUsers = [ - { - name = "all-clauses"; - ensureClauses = { - superuser = true; - createdb = true; - createrole = true; - "inherit" = true; - login = true; - replication = true; - bypassrls = true; - }; - } - { - name = "default-clauses"; - } - ]; - }; - }; - - testScript = let - getClausesQuery = user: pkgs.lib.concatStringsSep " " - [ - "SELECT row_to_json(row)" - "FROM (" - "SELECT" - "rolsuper," - "rolinherit," - "rolcreaterole," - "rolcreatedb," - "rolcanlogin," - "rolreplication," - "rolbypassrls" - "FROM pg_roles" - "WHERE rolname = '${user}'" - ") row;" - ]; - in '' - import json - machine.start() - machine.wait_for_unit("postgresql") - - with subtest("All user permissions are set according to the ensureClauses attr"): - clauses = json.loads( - machine.succeed( - "sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\"" - ) - ) - print(clauses) - assert clauses['rolsuper'], 'expected user with clauses to have superuser clause' - assert clauses['rolinherit'], 'expected user with clauses to have inherit clause' - assert clauses['rolcreaterole'], 'expected user with clauses to have create role clause' - assert clauses['rolcreatedb'], 'expected user with clauses to have create db clause' - assert clauses['rolcanlogin'], 'expected user with clauses to have login clause' - assert clauses['rolreplication'], 'expected user with clauses to have replication clause' - assert clauses['rolbypassrls'], 'expected user with clauses to have bypassrls clause' - - with subtest("All user permissions default when ensureClauses is not provided"): - clauses = json.loads( - machine.succeed( - "sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\"" - ) - ) - assert not clauses['rolsuper'], 'expected user with no clauses set to have default superuser clause' - assert clauses['rolinherit'], 'expected user with no clauses set to have default inherit clause' - assert not clauses['rolcreaterole'], 'expected user with no clauses set to have default create role clause' - assert not clauses['rolcreatedb'], 'expected user with no clauses set to have default create db clause' - assert clauses['rolcanlogin'], 'expected user with no clauses set to have default login clause' - assert not clauses['rolreplication'], 'expected user with no clauses set to have default replication clause' - assert not clauses['rolbypassrls'], 'expected user with no clauses set to have default bypassrls clause' - - machine.shutdown() - ''; - }; -in - concatMapAttrs (name: package: { - ${name} = make-postgresql-test name package false; - ${name + "-backup-all"} = make-postgresql-test "${name + "-backup-all"}" package true; - ${name + "-clauses"} = mk-ensure-clauses-test name package; - }) postgresql-versions diff --git a/nixos/tests/postgresql/anonymizer.nix b/nixos/tests/postgresql/anonymizer.nix new file mode 100644 index 000000000000..3a5f69086eaa --- /dev/null +++ b/nixos/tests/postgresql/anonymizer.nix @@ -0,0 +1,116 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + makeTest { + name = "postgresql_anonymizer-${package.name}"; + meta.maintainers = lib.teams.flyingcircus.members; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = [ pkgs.pg-dump-anon ]; + services.postgresql = { + inherit package; + enable = true; + extraPlugins = ps: [ ps.anonymizer ]; + settings.shared_preload_libraries = [ "anon" ]; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("postgresql.service") + + with subtest("Setup"): + machine.succeed("sudo -u postgres psql --command 'create database demo'") + machine.succeed( + "sudo -u postgres psql -d demo -f ${pkgs.writeText "init.sql" '' + create extension anon cascade; + select anon.init(); + create table player(id serial, name text, points int); + insert into player(id,name,points) values (1,'Foo', 23); + insert into player(id,name,points) values (2,'Bar',42); + security label for anon on column player.name is 'MASKED WITH FUNCTION anon.fake_last_name();'; + security label for anon on column player.points is 'MASKED WITH VALUE NULL'; + ''}" + ) + + def get_player_table_contents(): + return [ + x.split(',') for x in machine.succeed("sudo -u postgres psql -d demo --csv --command 'select * from player'").splitlines()[1:] + ] + + def check_anonymized_row(row, id, original_name): + assert row[0] == id, f"Expected first row to have ID {id}, but got {row[0]}" + assert row[1] != original_name, f"Expected first row to have a name other than {original_name}" + assert not bool(row[2]), "Expected points to be NULL in first row" + + def find_xsv_in_dump(dump, sep=','): + """ + Expecting to find a CSV (for pg_dump_anon) or TSV (for pg_dump) structure, looking like + + COPY public.player ... + 1,Shields, + 2,Salazar, + \. + + in the given dump (the commas are tabs in case of pg_dump). + Extract the CSV lines and split by `sep`. + """ + + try: + from itertools import dropwhile, takewhile + return [x.split(sep) for x in list(takewhile( + lambda x: x != "\\.", + dropwhile( + lambda x: not x.startswith("COPY public.player"), + dump.splitlines() + ) + ))[1:]] + except: + print(f"Dump to process: {dump}") + raise + + def check_original_data(output): + assert output[0] == ['1','Foo','23'], f"Expected first row from player table to be 1,Foo,23; got {output[0]}" + assert output[1] == ['2','Bar','42'], f"Expected first row from player table to be 2,Bar,42; got {output[1]}" + + def check_anonymized_rows(output): + check_anonymized_row(output[0], '1', 'Foo') + check_anonymized_row(output[1], '2', 'Bar') + + with subtest("Check initial state"): + check_original_data(get_player_table_contents()) + + with subtest("Anonymous dumps"): + check_original_data(find_xsv_in_dump( + machine.succeed("sudo -u postgres pg_dump demo"), + sep='\t' + )) + check_anonymized_rows(find_xsv_in_dump( + machine.succeed("sudo -u postgres pg_dump_anon -U postgres -h /run/postgresql -d demo"), + sep=',' + )) + + with subtest("Anonymize"): + machine.succeed("sudo -u postgres psql -d demo --command 'select anon.anonymize_database();'") + check_anonymized_rows(get_player_table_contents()) + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (_: p: !p.pkgs.anonymizer.meta.broken) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/default.nix b/nixos/tests/postgresql/default.nix new file mode 100644 index 000000000000..4fe7e7a37e7e --- /dev/null +++ b/nixos/tests/postgresql/default.nix @@ -0,0 +1,26 @@ +{ + system ? builtins.currentSystem, + config ? { }, + pkgs ? import ../../.. { inherit system config; }, +}: + +with import ../../lib/testing-python.nix { inherit system pkgs; }; + +let + importWithArgs = path: import path { inherit pkgs makeTest; }; +in +{ + # postgresql + postgresql = importWithArgs ./postgresql.nix; + postgresql-jit = importWithArgs ./postgresql-jit.nix; + postgresql-wal-receiver = importWithArgs ./postgresql-wal-receiver.nix; + postgresql-tls-client-cert = importWithArgs ./postgresql-tls-client-cert.nix; + + # extensions + anonymizer = importWithArgs ./anonymizer.nix; + pgjwt = importWithArgs ./pgjwt.nix; + pgvecto-rs = importWithArgs ./pgvecto-rs.nix; + timescaledb = importWithArgs ./timescaledb.nix; + tsja = importWithArgs ./tsja.nix; + wal2json = importWithArgs ./wal2json.nix; +} diff --git a/nixos/tests/postgresql/pgjwt.nix b/nixos/tests/postgresql/pgjwt.nix new file mode 100644 index 000000000000..81e5dac41ada --- /dev/null +++ b/nixos/tests/postgresql/pgjwt.nix @@ -0,0 +1,57 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + makeTest { + name = "pgjwt-${package.name}"; + meta = with lib.maintainers; { + maintainers = [ + spinus + willibutz + ]; + }; + + nodes.master = + { ... }: + { + services.postgresql = { + inherit package; + enable = true; + extraPlugins = + ps: with ps; [ + pgjwt + pgtap + ]; + }; + }; + + testScript = + { nodes, ... }: + let + sqlSU = "${nodes.master.services.postgresql.superUser}"; + pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}"; + inherit (nodes.master.services.postgresql.package.pkgs) pgjwt; + in + '' + start_all() + master.wait_for_unit("postgresql") + master.succeed( + "${pkgs.sudo}/bin/sudo -u ${sqlSU} ${pgProve}/bin/pg_prove -d postgres -v -f ${pgjwt.src}/test.sql" + ) + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (_: p: !p.pkgs.pgjwt.meta.broken) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/pgvecto-rs.nix b/nixos/tests/postgresql/pgvecto-rs.nix new file mode 100644 index 000000000000..9d8389eecf99 --- /dev/null +++ b/nixos/tests/postgresql/pgvecto-rs.nix @@ -0,0 +1,81 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + # Test cases from https://docs.pgvecto.rs/use-cases/hybrid-search.html + test-sql = pkgs.writeText "postgresql-test" '' + CREATE EXTENSION vectors; + + CREATE TABLE items ( + id bigserial PRIMARY KEY, + content text NOT NULL, + embedding vectors.vector(3) NOT NULL -- 3 dimensions + ); + + INSERT INTO items (content, embedding) VALUES + ('a fat cat sat on a mat and ate a fat rat', '[1, 2, 3]'), + ('a fat dog sat on a mat and ate a fat rat', '[4, 5, 6]'), + ('a thin cat sat on a mat and ate a thin rat', '[7, 8, 9]'), + ('a thin dog sat on a mat and ate a thin rat', '[10, 11, 12]'); + ''; + + makeTestFor = + postgresqlPackage: + makeTest { + name = "pgvecto-rs-${postgresqlPackage.name}"; + meta = with lib.maintainers; { + maintainers = [ diogotcorreia ]; + }; + + nodes.machine = + { ... }: + { + services.postgresql = { + enable = true; + package = postgresqlPackage; + extraPlugins = + ps: with ps; [ + pgvecto-rs + ]; + settings.shared_preload_libraries = "vectors"; + }; + }; + + testScript = + { nodes, ... }: + let + inherit (nodes.machine.services.postgresql.package.pkgs) pgvecto-rs; + in + '' + def check_count(statement, lines): + return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( + statement, lines + ) + + + machine.start() + machine.wait_for_unit("postgresql") + + with subtest("Postgresql with extension vectors is available just after unit start"): + machine.succeed(check_count("SELECT * FROM pg_available_extensions WHERE name = 'vectors' AND default_version = '${pgvecto-rs.version}';", 1)) + + machine.succeed("sudo -u postgres psql -f ${test-sql}") + + machine.succeed(check_count("SELECT content, embedding FROM items WHERE to_tsvector('english', content) @@ 'cat & rat'::tsquery;", 2)) + + machine.shutdown() + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (_: p: !p.pkgs.pgvecto-rs.meta.broken) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/postgresql-jit.nix b/nixos/tests/postgresql/postgresql-jit.nix new file mode 100644 index 000000000000..5d0406062eae --- /dev/null +++ b/nixos/tests/postgresql/postgresql-jit.nix @@ -0,0 +1,58 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + makeTest { + name = "postgresql-jit-${package.name}"; + meta.maintainers = with lib.maintainers; [ ma27 ]; + + nodes.machine = + { pkgs, ... }: + { + services.postgresql = { + inherit package; + enable = true; + enableJIT = true; + initialScript = pkgs.writeText "init.sql" '' + create table demo (id int); + insert into demo (id) select generate_series(1, 5); + ''; + }; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("postgresql.service") + + with subtest("JIT is enabled"): + machine.succeed("sudo -u postgres psql <<<'show jit;' | grep 'on'") + + with subtest("Test JIT works fine"): + output = machine.succeed( + "cat ${pkgs.writeText "test.sql" '' + set jit_above_cost = 1; + EXPLAIN ANALYZE SELECT CONCAT('jit result = ', SUM(id)) FROM demo; + SELECT CONCAT('jit result = ', SUM(id)) from demo; + ''} | sudo -u postgres psql" + ) + assert "JIT:" in output + assert "jit result = 15" in output + + machine.shutdown() + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (n: _: lib.hasSuffix "_jit" n) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/postgresql-tls-client-cert.nix b/nixos/tests/postgresql/postgresql-tls-client-cert.nix new file mode 100644 index 000000000000..d7cddb625256 --- /dev/null +++ b/nixos/tests/postgresql/postgresql-tls-client-cert.nix @@ -0,0 +1,135 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + runWithOpenSSL = + file: cmd: + pkgs.runCommand file { + buildInputs = [ pkgs.openssl ]; + } cmd; + caKey = runWithOpenSSL "ca.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out"; + caCert = runWithOpenSSL "ca.crt" '' + openssl req -new -x509 -sha256 -key ${caKey} -out $out -subj "/CN=test.example" -days 36500 + ''; + serverKey = runWithOpenSSL "server.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out"; + serverKeyPath = "/var/lib/postgresql"; + serverCert = runWithOpenSSL "server.crt" '' + openssl req -new -sha256 -key ${serverKey} -out server.csr -subj "/CN=db.test.example" + openssl x509 -req -in server.csr -CA ${caCert} -CAkey ${caKey} \ + -CAcreateserial -out $out -days 36500 -sha256 + ''; + clientKey = runWithOpenSSL "client.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out"; + clientCert = runWithOpenSSL "client.crt" '' + openssl req -new -sha256 -key ${clientKey} -out client.csr -subj "/CN=test" + openssl x509 -req -in client.csr -CA ${caCert} -CAkey ${caKey} \ + -CAcreateserial -out $out -days 36500 -sha256 + ''; + clientKeyPath = "/root"; + + makeTestFor = + package: + makeTest { + name = "postgresql-tls-client-cert-${package.name}"; + meta.maintainers = with lib.maintainers; [ erictapen ]; + + nodes.server = + { ... }: + { + system.activationScripts = { + keyPlacement.text = '' + mkdir -p '${serverKeyPath}' + cp '${serverKey}' '${serverKeyPath}/server.key' + chown postgres:postgres '${serverKeyPath}/server.key' + chmod 600 '${serverKeyPath}/server.key' + ''; + }; + services.postgresql = { + inherit package; + enable = true; + enableTCPIP = true; + ensureUsers = [ + { + name = "test"; + ensureDBOwnership = true; + } + ]; + ensureDatabases = [ "test" ]; + settings = { + ssl = "on"; + ssl_ca_file = toString caCert; + ssl_cert_file = toString serverCert; + ssl_key_file = "${serverKeyPath}/server.key"; + }; + authentication = '' + hostssl test test ::/0 cert clientcert=verify-full + ''; + }; + networking = { + interfaces.eth1 = { + ipv6.addresses = [ + { + address = "fc00::1"; + prefixLength = 120; + } + ]; + }; + firewall.allowedTCPPorts = [ 5432 ]; + }; + }; + + nodes.client = + { ... }: + { + system.activationScripts = { + keyPlacement.text = '' + mkdir -p '${clientKeyPath}' + cp '${clientKey}' '${clientKeyPath}/client.key' + chown root:root '${clientKeyPath}/client.key' + chmod 600 '${clientKeyPath}/client.key' + ''; + }; + environment = { + variables = { + PGHOST = "db.test.example"; + PGPORT = "5432"; + PGDATABASE = "test"; + PGUSER = "test"; + PGSSLMODE = "verify-full"; + PGSSLCERT = clientCert; + PGSSLKEY = "${clientKeyPath}/client.key"; + PGSSLROOTCERT = caCert; + }; + systemPackages = [ package ]; + }; + networking = { + interfaces.eth1 = { + ipv6.addresses = [ + { + address = "fc00::2"; + prefixLength = 120; + } + ]; + }; + hosts = { + "fc00::1" = [ "db.test.example" ]; + }; + }; + }; + + testScript = '' + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") + client.succeed("psql -c \"SELECT 1;\"") + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) pkgs.postgresqlVersions + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/postgresql-wal-receiver.nix b/nixos/tests/postgresql/postgresql-wal-receiver.nix new file mode 100644 index 000000000000..5c1551c5f2fd --- /dev/null +++ b/nixos/tests/postgresql/postgresql-wal-receiver.nix @@ -0,0 +1,115 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + let + postgresqlDataDir = "/var/lib/postgresql/${package.psqlSchema}"; + replicationUser = "wal_receiver_user"; + replicationSlot = "wal_receiver_slot"; + replicationConn = "postgresql://${replicationUser}@localhost"; + baseBackupDir = "/var/cache/wals/pg_basebackup"; + walBackupDir = "/var/cache/wals/pg_wal"; + recoveryFile = pkgs.writeTextDir "recovery.signal" ""; + in + makeTest { + name = "postgresql-wal-receiver-${package.name}"; + meta.maintainers = with lib.maintainers; [ pacien ]; + + nodes.machine = + { ... }: + { + systemd.tmpfiles.rules = [ + "d /var/cache/wals 0750 postgres postgres - -" + ]; + + services.postgresql = { + inherit package; + enable = true; + settings = { + max_replication_slots = 10; + max_wal_senders = 10; + recovery_end_command = "touch recovery.done"; + restore_command = "cp ${walBackupDir}/%f %p"; + wal_level = "archive"; # alias for replica on pg >= 9.6 + }; + authentication = '' + host replication ${replicationUser} all trust + ''; + initialScript = pkgs.writeText "init.sql" '' + create user ${replicationUser} replication; + select * from pg_create_physical_replication_slot('${replicationSlot}'); + ''; + }; + + services.postgresqlWalReceiver.receivers.main = { + postgresqlPackage = package; + connection = replicationConn; + slot = replicationSlot; + directory = walBackupDir; + }; + # This is only to speedup test, it isn't time racing. Service is set to autorestart always, + # default 60sec is fine for real system, but is too much for a test + systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5; + systemd.services.postgresql.serviceConfig.ReadWritePaths = [ "/var/cache/wals" ]; + }; + + testScript = '' + # make an initial base backup + machine.wait_for_unit("postgresql") + machine.wait_for_unit("postgresql-wal-receiver-main") + # WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other + # required only for 9.4 + machine.sleep(5) + machine.succeed( + "${package}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}" + ) + + # create a dummy table with 100 records + machine.succeed( + "sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'" + ) + + # stop postgres and destroy data + machine.systemctl("stop postgresql") + machine.systemctl("stop postgresql-wal-receiver-main") + machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}") + + # restore the base backup + machine.succeed( + "cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}" + ) + + # prepare WAL and recovery + machine.succeed("chmod a+rX -R ${walBackupDir}") + machine.execute( + "for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done" + ) # make use of partial segments too + machine.succeed( + "cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*" + ) + + # replay WAL + machine.systemctl("start postgresql") + machine.wait_for_file("${postgresqlDataDir}/recovery.done") + machine.systemctl("restart postgresql") + machine.wait_for_unit("postgresql") + + # check that our records have been restored + machine.succeed( + "test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100" + ) + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) pkgs.postgresqlVersions + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/postgresql.nix b/nixos/tests/postgresql/postgresql.nix new file mode 100644 index 000000000000..509a14411de9 --- /dev/null +++ b/nixos/tests/postgresql/postgresql.nix @@ -0,0 +1,244 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + lib.recurseIntoAttrs { + postgresql = makeTestForWithBackupAll package false; + postgresql-backup-all = makeTestForWithBackupAll package true; + postgresql-clauses = makeEnsureTestFor package; + }; + + test-sql = pkgs.writeText "postgresql-test" '' + CREATE EXTENSION pgcrypto; -- just to check if lib loading works + CREATE TABLE sth ( + id int + ); + INSERT INTO sth (id) VALUES (1); + INSERT INTO sth (id) VALUES (1); + INSERT INTO sth (id) VALUES (1); + INSERT INTO sth (id) VALUES (1); + INSERT INTO sth (id) VALUES (1); + CREATE TABLE xmltest ( doc xml ); + INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled + ''; + + makeTestForWithBackupAll = + package: backupAll: + makeTest { + name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}"; + meta = with lib.maintainers; { + maintainers = [ zagy ]; + }; + + nodes.machine = + { ... }: + { + services.postgresql = { + inherit (package) ; + enable = true; + }; + + services.postgresqlBackup = { + enable = true; + databases = lib.optional (!backupAll) "postgres"; + }; + }; + + testScript = + let + backupName = if backupAll then "all" else "postgres"; + backupService = if backupAll then "postgresqlBackup" else "postgresqlBackup-postgres"; + backupFileBase = "/var/backup/postgresql/${backupName}"; + in + '' + def check_count(statement, lines): + return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( + statement, lines + ) + + + machine.start() + machine.wait_for_unit("postgresql") + + with subtest("Postgresql is available just after unit start"): + machine.succeed( + "cat ${test-sql} | sudo -u postgres psql" + ) + + with subtest("Postgresql survives restart (bug #1735)"): + machine.shutdown() + import time + time.sleep(2) + machine.start() + machine.wait_for_unit("postgresql") + + machine.fail(check_count("SELECT * FROM sth;", 3)) + machine.succeed(check_count("SELECT * FROM sth;", 5)) + machine.fail(check_count("SELECT * FROM sth;", 4)) + machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1)) + + with subtest("Backup service works"): + machine.succeed( + "systemctl start ${backupService}.service", + "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'", + "ls -hal /var/backup/postgresql/ >/dev/console", + "stat -c '%a' ${backupFileBase}.sql.gz | grep 600", + ) + with subtest("Backup service removes prev files"): + machine.succeed( + # Create dummy prev files. + "touch ${backupFileBase}.prev.sql{,.gz,.zstd}", + "chown postgres:postgres ${backupFileBase}.prev.sql{,.gz,.zstd}", + + # Run backup. + "systemctl start ${backupService}.service", + "ls -hal /var/backup/postgresql/ >/dev/console", + + # Since nothing has changed in the database, the cur and prev files + # should match. + "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'", + "cmp ${backupFileBase}.sql.gz ${backupFileBase}.prev.sql.gz", + + # The prev files with unused suffix should be removed. + "[ ! -f '${backupFileBase}.prev.sql' ]", + "[ ! -f '${backupFileBase}.prev.sql.zstd' ]", + + # Both cur and prev file should only be accessible by the postgres user. + "stat -c '%a' ${backupFileBase}.sql.gz | grep 600", + "stat -c '%a' '${backupFileBase}.prev.sql.gz' | grep 600", + ) + with subtest("Backup service fails gracefully"): + # Sabotage the backup process + machine.succeed("rm /run/postgresql/.s.PGSQL.5432") + machine.fail( + "systemctl start ${backupService}.service", + ) + machine.succeed( + "ls -hal /var/backup/postgresql/ >/dev/console", + "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'", + "stat ${backupFileBase}.in-progress.sql.gz", + ) + # In a previous version, the second run would overwrite prev.sql.gz, + # so we test a second run as well. + machine.fail( + "systemctl start ${backupService}.service", + ) + machine.succeed( + "stat ${backupFileBase}.in-progress.sql.gz", + "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'", + ) + + + with subtest("Initdb works"): + machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2") + + machine.log(machine.execute("systemd-analyze security postgresql.service | grep -v ✓")[1]) + + machine.shutdown() + ''; + }; + + makeEnsureTestFor = + package: + makeTest { + name = "postgresql-clauses-${package.name}"; + meta = with lib.maintainers; { + maintainers = [ zagy ]; + }; + + nodes.machine = + { ... }: + { + services.postgresql = { + inherit package; + enable = true; + ensureUsers = [ + { + name = "all-clauses"; + ensureClauses = { + superuser = true; + createdb = true; + createrole = true; + "inherit" = true; + login = true; + replication = true; + bypassrls = true; + }; + } + { + name = "default-clauses"; + } + ]; + }; + }; + + testScript = + let + getClausesQuery = + user: + lib.concatStringsSep " " [ + "SELECT row_to_json(row)" + "FROM (" + "SELECT" + "rolsuper," + "rolinherit," + "rolcreaterole," + "rolcreatedb," + "rolcanlogin," + "rolreplication," + "rolbypassrls" + "FROM pg_roles" + "WHERE rolname = '${user}'" + ") row;" + ]; + in + '' + import json + machine.start() + machine.wait_for_unit("postgresql") + + with subtest("All user permissions are set according to the ensureClauses attr"): + clauses = json.loads( + machine.succeed( + "sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\"" + ) + ) + print(clauses) + assert clauses['rolsuper'], 'expected user with clauses to have superuser clause' + assert clauses['rolinherit'], 'expected user with clauses to have inherit clause' + assert clauses['rolcreaterole'], 'expected user with clauses to have create role clause' + assert clauses['rolcreatedb'], 'expected user with clauses to have create db clause' + assert clauses['rolcanlogin'], 'expected user with clauses to have login clause' + assert clauses['rolreplication'], 'expected user with clauses to have replication clause' + assert clauses['rolbypassrls'], 'expected user with clauses to have bypassrls clause' + + with subtest("All user permissions default when ensureClauses is not provided"): + clauses = json.loads( + machine.succeed( + "sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\"" + ) + ) + assert not clauses['rolsuper'], 'expected user with no clauses set to have default superuser clause' + assert clauses['rolinherit'], 'expected user with no clauses set to have default inherit clause' + assert not clauses['rolcreaterole'], 'expected user with no clauses set to have default create role clause' + assert not clauses['rolcreatedb'], 'expected user with no clauses set to have default create db clause' + assert clauses['rolcanlogin'], 'expected user with no clauses set to have default login clause' + assert not clauses['rolreplication'], 'expected user with no clauses set to have default replication clause' + assert not clauses['rolbypassrls'], 'expected user with no clauses set to have default bypassrls clause' + + machine.shutdown() + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) pkgs.postgresqlVersions + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/timescaledb.nix b/nixos/tests/postgresql/timescaledb.nix new file mode 100644 index 000000000000..b29d59c744f0 --- /dev/null +++ b/nixos/tests/postgresql/timescaledb.nix @@ -0,0 +1,100 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + test-sql = pkgs.writeText "postgresql-test" '' + CREATE EXTENSION timescaledb; + CREATE EXTENSION timescaledb_toolkit; + + CREATE TABLE sth ( + time TIMESTAMPTZ NOT NULL, + value DOUBLE PRECISION + ); + + SELECT create_hypertable('sth', 'time'); + + INSERT INTO sth (time, value) VALUES + ('2003-04-12 04:05:06 America/New_York', 1.0), + ('2003-04-12 04:05:07 America/New_York', 2.0), + ('2003-04-12 04:05:08 America/New_York', 3.0), + ('2003-04-12 04:05:09 America/New_York', 4.0), + ('2003-04-12 04:05:10 America/New_York', 5.0) + ; + + WITH t AS ( + SELECT + time_bucket('1 day'::interval, time) AS dt, + stats_agg(value) AS stats + FROM sth + GROUP BY time_bucket('1 day'::interval, time) + ) + SELECT + average(stats) + FROM t; + + SELECT * FROM sth; + ''; + + makeTestFor = + package: + makeTest { + name = "timescaledb-${package.name}"; + meta = with lib.maintainers; { + maintainers = [ typetetris ]; + }; + + nodes.machine = + { ... }: + { + services.postgresql = { + inherit package; + enable = true; + extraPlugins = + ps: with ps; [ + timescaledb + timescaledb_toolkit + ]; + settings = { + shared_preload_libraries = "timescaledb, timescaledb_toolkit"; + }; + }; + }; + + testScript = '' + def check_count(statement, lines): + return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( + statement, lines + ) + + + machine.start() + machine.wait_for_unit("postgresql") + + with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"): + machine.succeed( + "sudo -u postgres psql -f ${test-sql}" + ) + + machine.fail(check_count("SELECT * FROM sth;", 3)) + machine.succeed(check_count("SELECT * FROM sth;", 5)) + machine.fail(check_count("SELECT * FROM sth;", 4)) + + machine.shutdown() + ''; + }; +in +# Not run by default, because this requires allowUnfree. +# To run these tests: +# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.postgresql.timescaledb +lib.dontRecurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (_: p: !p.pkgs.timescaledb.meta.broken) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/tsja.nix b/nixos/tests/postgresql/tsja.nix new file mode 100644 index 000000000000..7c976da21b68 --- /dev/null +++ b/nixos/tests/postgresql/tsja.nix @@ -0,0 +1,50 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + makeTest { + name = "tsja-${package.name}"; + meta = { + maintainers = with lib.maintainers; [ chayleaf ]; + }; + + nodes.master = + { ... }: + { + services.postgresql = { + inherit package; + enable = true; + extraPlugins = + ps: with ps; [ + tsja + ]; + }; + }; + + testScript = '' + start_all() + master.wait_for_unit("postgresql") + master.succeed("sudo -u postgres psql -f /run/current-system/sw/share/postgresql/extension/libtsja_dbinit.sql") + # make sure "日本語" is parsed as a separate lexeme + master.succeed(""" + sudo -u postgres \\ + psql -c "SELECT * FROM ts_debug('japanese', 'PostgreSQLで日本語のテキスト検索ができます。')" \\ + | grep "{日本語}" + """) + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (_: p: !p.pkgs.tsja.meta.broken) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/postgresql/wal2json.nix b/nixos/tests/postgresql/wal2json.nix new file mode 100644 index 000000000000..551254a68ebd --- /dev/null +++ b/nixos/tests/postgresql/wal2json.nix @@ -0,0 +1,52 @@ +{ + pkgs, + makeTest, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + makeTest { + name = "wal2json-${package.name}"; + meta.maintainers = with pkgs.lib.maintainers; [ euank ]; + + nodes.machine = { + services.postgresql = { + inherit package; + enable = true; + extraPlugins = with package.pkgs; [ wal2json ]; + settings = { + wal_level = "logical"; + max_replication_slots = "10"; + max_wal_senders = "10"; + }; + }; + }; + + testScript = '' + machine.wait_for_unit("postgresql") + machine.succeed( + "sudo -u postgres psql -qAt -f ${./wal2json/example2.sql} postgres > /tmp/example2.out" + ) + machine.succeed( + "diff ${./wal2json/example2.out} /tmp/example2.out" + ) + machine.succeed( + "sudo -u postgres psql -qAt -f ${./wal2json/example3.sql} postgres > /tmp/example3.out" + ) + machine.succeed( + "diff ${./wal2json/example3.out} /tmp/example3.out" + ) + ''; + }; +in +lib.recurseIntoAttrs ( + lib.concatMapAttrs (n: p: { ${n} = makeTestFor p; }) ( + lib.filterAttrs (_: p: !p.pkgs.wal2json.meta.broken) pkgs.postgresqlVersions + ) + // { + passthru.override = p: makeTestFor p; + } +) diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix index b413996c67db..5f719a5d97c1 100644 --- a/nixos/tests/printing.nix +++ b/nixos/tests/printing.nix @@ -3,12 +3,17 @@ import ./make-test-python.nix ( { pkgs , socket ? true # whether to use socket activation +, listenTcp ? true # whether to open port 631 on client , ... }: +let + inherit (pkgs) lib; +in + { name = "printing"; - meta = with pkgs.lib.maintainers; { + meta = with lib.maintainers; { maintainers = [ domenkozar matthewbauer ]; }; @@ -35,9 +40,10 @@ import ./make-test-python.nix ( }]; }; - nodes.client = { ... }: { + nodes.client = { lib, ... }: { services.printing.enable = true; services.printing.startWhenNeeded = socket; + services.printing.listenAddresses = lib.mkIf (!listenTcp) []; # Add printer to the client as well, via IPP. hardware.printers.ensurePrinters = [{ name = "DeskjetRemote"; @@ -54,8 +60,8 @@ import ./make-test-python.nix ( start_all() with subtest("Make sure that cups is up on both sides and printers are set up"): - server.wait_for_unit("cups.${if socket then "socket" else "service"}") - client.wait_for_unit("cups.${if socket then "socket" else "service"}") + server.wait_for_unit("ensure-printers.service") + client.wait_for_unit("ensure-printers.service") assert "scheduler is running" in client.succeed("lpstat -r") @@ -63,7 +69,7 @@ import ./make-test-python.nix ( assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H") with subtest("HTTP server is available too"): - client.succeed("curl --fail http://localhost:631/") + ${lib.optionalString listenTcp ''client.succeed("curl --fail http://localhost:631/")''} client.succeed(f"curl --fail http://{server.name}:631/") server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/") diff --git a/nixos/tests/privatebin.nix b/nixos/tests/privatebin.nix new file mode 100644 index 000000000000..c920b6220084 --- /dev/null +++ b/nixos/tests/privatebin.nix @@ -0,0 +1,21 @@ +{ lib, ... }: + +{ + name = "privatebin"; + meta.maintainers = [ lib.maintainers.savyajha ]; + + nodes.dataImporter = + { ... }: + { + services.privatebin = { + enable = true; + enableNginx = true; + }; + }; + + testScript = '' + dataImporter.wait_for_unit("phpfpm-privatebin.service") + dataImporter.wait_for_unit("nginx.service") + dataImporter.succeed("curl -fvvv -Ls http://localhost/ | grep 'PrivateBin'") + ''; +} diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 71eef72df6f3..4aacd8e5a849 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -356,6 +356,46 @@ let ''; }; + exportarr-sonarr = let apikey = "eccff6a992bc2e4b88e46d064b26bb4e"; in { + nodeName = "exportarr_sonarr"; + exporterConfig = { + enable = true; + url = "http://127.0.0.1:8989"; + apiKeyFile = pkgs.writeText "dummy-api-key" apikey; + }; + metricProvider = { + services.sonarr.enable = true; + systemd.services.sonarr.serviceConfig.ExecStartPre = + let + sonarr_config = pkgs.writeText "config.xml" '' + <Config> + <LogLevel>info</LogLevel> + <EnableSsl>False</EnableSsl> + <Port>8989</Port> + <SslPort>9898</SslPort> + <UrlBase></UrlBase> + <BindAddress>*</BindAddress> + <ApiKey>${apikey}</ApiKey> + <AuthenticationMethod>None</AuthenticationMethod> + <UpdateMechanism>BuiltIn</UpdateMechanism> + <Branch>main</Branch> + <InstanceName>Sonarr</InstanceName> + </Config> + ''; + in + [ + ''${pkgs.coreutils}/bin/install -D -m 777 ${sonarr_config} -T /var/lib/sonarr/.config/NzbDrone/config.xml'' + ]; + }; + exporterTest = '' + wait_for_unit("sonarr.service") + wait_for_open_port(8989) + wait_for_unit("prometheus-exportarr-sonarr-exporter.service") + wait_for_open_port(9708) + succeed("curl -sSf http://localhost:9708/metrics | grep sonarr_series_total") + ''; + }; + fastly = { exporterConfig = { enable = true; @@ -597,6 +637,8 @@ let rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7 zmqpubrawblock=tcp://127.0.0.1:28332 zmqpubrawtx=tcp://127.0.0.1:28333 + # https://github.com/lightningnetwork/lnd/issues/9163 + deprecatedrpc=warnings ''; extraCmdlineOptions = [ "-regtest" ]; }; @@ -1511,25 +1553,6 @@ let ''; }; - tor = { - exporterConfig = { - enable = true; - }; - metricProvider = { - # Note: this does not connect the test environment to the Tor network. - # Client, relay, bridge or exit connectivity are disabled by default. - services.tor.enable = true; - services.tor.settings.ControlPort = 9051; - }; - exporterTest = '' - wait_for_unit("tor.service") - wait_for_open_port(9051) - wait_for_unit("prometheus-tor-exporter.service") - wait_for_open_port(9130) - succeed("curl -sSf localhost:9130/metrics | grep 'tor_version{.\\+} 1'") - ''; - }; - unpoller = { nodeName = "unpoller"; exporterConfig.enable = true; @@ -1627,7 +1650,7 @@ let varnish = { exporterConfig = { enable = true; - instance = "/var/spool/varnish/varnish"; + instance = "/run/varnish/varnish"; group = "varnish"; }; metricProvider = { @@ -1636,6 +1659,7 @@ let ]; services.varnish = { enable = true; + stateDir = "/run/varnish/varnish"; config = '' vcl 4.0; backend default { diff --git a/nixos/tests/pulseaudio.nix b/nixos/tests/pulseaudio.nix index dc8e33ccd559..ccc33f28bc6d 100644 --- a/nixos/tests/pulseaudio.nix +++ b/nixos/tests/pulseaudio.nix @@ -40,7 +40,7 @@ let }; environment.systemPackages = [ testers.testPlay pkgs.pavucontrol ] - ++ lib.optional pkgs.stdenv.isx86_64 testers.testPlay32; + ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 testers.testPlay32; } // lib.optionalAttrs systemWide { users.users.alice.extraGroups = [ "pulse-access" ]; systemd.services.pulseaudio.wantedBy = [ "multi-user.target" ]; @@ -54,7 +54,7 @@ let machine.send_chars("testPlay \n") machine.wait_for_file("/tmp/testPlay_success") - ${lib.optionalString pkgs.stdenv.isx86_64 '' + ${lib.optionalString pkgs.stdenv.hostPlatform.isx86_64 '' machine.send_chars("testPlay32 \n") machine.wait_for_file("/tmp/testPlay32_success") ''} diff --git a/nixos/tests/qgis.nix b/nixos/tests/qgis.nix index 7706b8c07747..e8149e488562 100644 --- a/nixos/tests/qgis.nix +++ b/nixos/tests/qgis.nix @@ -1,5 +1,6 @@ -import ./make-test-python.nix ({ pkgs, lib, qgisPackage, ... }: +import ./make-test-python.nix ({ pkgs, lib, package, ... }: let + qgisPackage = package.override { withServer = true; }; testScript = pkgs.writeTextFile { name = "qgis-test.py"; text = (builtins.readFile ../../pkgs/applications/gis/qgis/test.py); @@ -12,19 +13,74 @@ import ./make-test-python.nix ({ pkgs, lib, qgisPackage, ... }: }; nodes = { - machine = { pkgs, ... }: { + machine = { config, pkgs, ... }: + + let + qgisServerUser = config.services.nginx.user; + qgisServerSocket = "/run/qgis_mapserv.socket"; + in + { virtualisation.diskSize = 2 * 1024; imports = [ ./common/x11.nix ]; - environment.systemPackages = [ qgisPackage ]; + environment.systemPackages = [ + qgisPackage + ]; + + systemd.sockets.qgis-server = { + listenStreams = [ qgisServerSocket ]; + socketConfig = { + Accept = false; + SocketUser = qgisServerUser; + SocketMode = 0600; + }; + wantedBy = ["sockets.target" "qgis-server.service"]; + before = [ "qgis-server.service" ]; + }; + + systemd.services.qgis-server = { + description = "QGIS server"; + serviceConfig = { + User = qgisServerUser; + StandardOutput = "null"; + StandardError = "journal"; + StandardInput = "socket"; + Environment = [ + "QT_QPA_PLATFORM_PLUGIN_PATH=${pkgs.libsForQt5.qt5.qtbase}/${pkgs.libsForQt5.qt5.qtbase.qtPluginPrefix}/platforms" + "QGIS_SERVER_LOG_LEVEL=0" + "QGIS_SERVER_LOG_STDERR=1" + ]; + ExecStart = "${qgisPackage}/lib/cgi-bin/qgis_mapserv.fcgi"; + }; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + }; + services.nginx = { + enable = true; + virtualHosts."qgis" = { + locations."~".extraConfig = '' + gzip off; + include ${pkgs.nginx}/conf/fastcgi_params; + include ${pkgs.nginx}/conf/fastcgi.conf; + fastcgi_pass unix:${qgisServerSocket}; + ''; + }; + }; }; }; testScript = '' start_all() + # test desktop machine.succeed("${qgisPackage}/bin/qgis --version | grep 'QGIS ${qgisPackage.version}'") machine.succeed("${qgisPackage}/bin/qgis --code ${testScript}") + + # test server + machine.succeed("${qgisPackage}/bin/qgis_mapserver --version | grep 'QGIS ${qgisPackage.version}'") + + machine.succeed("curl --head http://localhost | grep 'Server:.*${qgisPackage.version}'") + machine.succeed("curl http://localhost/index.json | grep 'Landing page as JSON'") ''; }) diff --git a/nixos/tests/quorum.nix b/nixos/tests/quorum.nix index 31669eb7fc38..dd0d1540e845 100644 --- a/nixos/tests/quorum.nix +++ b/nixos/tests/quorum.nix @@ -62,6 +62,7 @@ in "0x0000000000000000000000000000000000000000000000000000000000000000"; eip155Block = 1; eip158Block = 1; + homesteadBlock = 1; isQuorum = true; istanbul = { epoch = 30000; diff --git a/nixos/tests/renovate.nix b/nixos/tests/renovate.nix index a30b5b3d60b9..deaac4faa5ce 100644 --- a/nixos/tests/renovate.nix +++ b/nixos/tests/renovate.nix @@ -58,12 +58,12 @@ import ./make-test-python.nix ( machine.succeed("git -C /tmp/kitty push origin") machine.succeed(f"echo '{accessToken}' > /etc/renovate-token") - machine.systemctl("start renovate.service") + machine.systemctl("start --wait renovate.service") machine.succeed("tea pulls list --repo meow/kitty | grep 'Configure Renovate'") machine.succeed("tea pulls merge --repo meow/kitty 1") - machine.systemctl("start renovate.service") + machine.systemctl("start --wait renovate.service") ''; } ) diff --git a/nixos/tests/replace-dependencies/default.nix b/nixos/tests/replace-dependencies/default.nix new file mode 100644 index 000000000000..ce0013a868c7 --- /dev/null +++ b/nixos/tests/replace-dependencies/default.nix @@ -0,0 +1,19 @@ +import ../make-test-python.nix ( + { pkgs, ... }: + { + name = "replace-dependencies"; + meta.maintainers = with pkgs.lib.maintainers; [ alois31 ]; + + nodes.machine = + { ... }: + { + nix.settings.experimental-features = [ "ca-derivations" ]; + system.extraDependencies = [ pkgs.stdenvNoCC ]; + }; + + testScript = '' + start_all() + machine.succeed("nix-build --option substitute false ${pkgs.path}/nixos/tests/replace-dependencies/guest.nix") + ''; + } +) diff --git a/nixos/tests/replace-dependencies/guest.nix b/nixos/tests/replace-dependencies/guest.nix new file mode 100644 index 000000000000..f022f1728599 --- /dev/null +++ b/nixos/tests/replace-dependencies/guest.nix @@ -0,0 +1,149 @@ +# This needs to be run in a NixOS test, because Hydra cannot do IFD. +let + pkgs = import ../../.. { }; + inherit (pkgs) + runCommand + writeShellScriptBin + replaceDependency + replaceDependencies + ; + inherit (pkgs.lib) escapeShellArg; + mkCheckOutput = + name: test: output: + runCommand name { } '' + actualOutput="$(${escapeShellArg "${test}/bin/test"})" + if [ "$(${escapeShellArg "${test}/bin/test"})" != ${escapeShellArg output} ]; then + echo >&2 "mismatched output: expected \""${escapeShellArg output}"\", got \"$actualOutput\"" + exit 1 + fi + touch "$out" + ''; + oldDependency = writeShellScriptBin "dependency" '' + echo "got old dependency" + ''; + oldDependency-ca = oldDependency.overrideAttrs { __contentAddressed = true; }; + newDependency = writeShellScriptBin "dependency" '' + echo "got new dependency" + ''; + newDependency-ca = newDependency.overrideAttrs { __contentAddressed = true; }; + basic = writeShellScriptBin "test" '' + ${oldDependency}/bin/dependency + ''; + basic-ca = writeShellScriptBin "test" '' + ${oldDependency-ca}/bin/dependency + ''; + transitive = writeShellScriptBin "test" '' + ${basic}/bin/test + ''; + weirdDependency = writeShellScriptBin "dependency" '' + echo "got weird dependency" + ${basic}/bin/test + ''; + oldDependency1 = writeShellScriptBin "dependency1" '' + echo "got old dependency 1" + ''; + newDependency1 = writeShellScriptBin "dependency1" '' + echo "got new dependency 1" + ''; + oldDependency2 = writeShellScriptBin "dependency2" '' + ${oldDependency1}/bin/dependency1 + echo "got old dependency 2" + ''; + newDependency2 = writeShellScriptBin "dependency2" '' + ${oldDependency1}/bin/dependency1 + echo "got new dependency 2" + ''; + deep = writeShellScriptBin "test" '' + ${oldDependency2}/bin/dependency2 + ''; +in +{ + replacedependency-basic = mkCheckOutput "replacedependency-basic" (replaceDependency { + drv = basic; + inherit oldDependency newDependency; + }) "got new dependency"; + + replacedependency-basic-old-ca = mkCheckOutput "replacedependency-basic" (replaceDependency { + drv = basic-ca; + oldDependency = oldDependency-ca; + inherit newDependency; + }) "got new dependency"; + + replacedependency-basic-new-ca = mkCheckOutput "replacedependency-basic" (replaceDependency { + drv = basic; + inherit oldDependency; + newDependency = newDependency-ca; + }) "got new dependency"; + + replacedependency-transitive = mkCheckOutput "replacedependency-transitive" (replaceDependency { + drv = transitive; + inherit oldDependency newDependency; + }) "got new dependency"; + + replacedependency-weird = + mkCheckOutput "replacedependency-weird" + (replaceDependency { + drv = basic; + inherit oldDependency; + newDependency = weirdDependency; + }) + '' + got weird dependency + got old dependency''; + + replacedependencies-precedence = mkCheckOutput "replacedependencies-precedence" (replaceDependencies + { + drv = basic; + replacements = [ { inherit oldDependency newDependency; } ]; + cutoffPackages = [ oldDependency ]; + } + ) "got new dependency"; + + replacedependencies-self = mkCheckOutput "replacedependencies-self" (replaceDependencies { + drv = basic; + replacements = [ + { + inherit oldDependency; + newDependency = oldDependency; + } + ]; + }) "got old dependency"; + + replacedependencies-deep-order1 = + mkCheckOutput "replacedependencies-deep-order1" + (replaceDependencies { + drv = deep; + replacements = [ + { + oldDependency = oldDependency1; + newDependency = newDependency1; + } + { + oldDependency = oldDependency2; + newDependency = newDependency2; + } + ]; + }) + '' + got new dependency 1 + got new dependency 2''; + + replacedependencies-deep-order2 = + mkCheckOutput "replacedependencies-deep-order2" + (replaceDependencies { + drv = deep; + replacements = [ + { + oldDependency = oldDependency2; + newDependency = newDependency2; + } + { + oldDependency = oldDependency1; + newDependency = newDependency1; + } + ]; + }) + '' + got new dependency 1 + got new dependency 2''; +} diff --git a/nixos/tests/retroarch.nix b/nixos/tests/retroarch.nix index 0e5f60aa8be2..d13ed074b9c9 100644 --- a/nixos/tests/retroarch.nix +++ b/nixos/tests/retroarch.nix @@ -1,10 +1,14 @@ -import ./make-test-python.nix ({ pkgs, ... }: +import ./make-test-python.nix ( + { pkgs, ... }: { name = "retroarch"; - meta = with pkgs.lib; { maintainers = teams.libretro.members ++ [ maintainers.j0hax ]; }; + meta = with pkgs.lib; { + maintainers = teams.libretro.members ++ [ maintainers.j0hax ]; + }; - nodes.machine = { ... }: + nodes.machine = + { ... }: { imports = [ ./common/user-account.nix ]; @@ -23,11 +27,13 @@ import ./make-test-python.nix ({ pkgs, ... }: }; }; - testScript = { nodes, ... }: + testScript = + { nodes, ... }: let user = nodes.machine.config.users.users.alice; xdo = "${pkgs.xdotool}/bin/xdotool"; - in '' + in + '' with subtest("Wait for login"): start_all() machine.wait_for_file("/tmp/xauth_*") @@ -35,7 +41,7 @@ import ./make-test-python.nix ({ pkgs, ... }: with subtest("Check RetroArch started"): machine.wait_until_succeeds("pgrep retroarch") - machine.wait_for_window("^RetroArch ") + machine.wait_for_window("^RetroArch") with subtest("Check configuration created"): machine.wait_for_file("${user.home}/.config/retroarch/retroarch.cfg") @@ -46,4 +52,5 @@ import ./make-test-python.nix ({ pkgs, ... }: ) machine.screenshot("screen") ''; - }) + } +) diff --git a/nixos/tests/saunafs.nix b/nixos/tests/saunafs.nix new file mode 100644 index 000000000000..49d986175716 --- /dev/null +++ b/nixos/tests/saunafs.nix @@ -0,0 +1,122 @@ +import ./make-test-python.nix ( + { pkgs, lib, ... }: + + let + master = + { pkgs, ... }: + { + # data base is stored in memory + # server may crash with default memory size + virtualisation.memorySize = 1024; + + services.saunafs.master = { + enable = true; + openFirewall = true; + exports = [ + "* / rw,alldirs,maproot=0:0" + ]; + }; + }; + + chunkserver = + { pkgs, ... }: + { + virtualisation.emptyDiskImages = [ 4096 ]; + boot.initrd.postDeviceCommands = '' + ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb + ''; + + fileSystems = pkgs.lib.mkVMOverride { + "/data" = { + device = "/dev/disk/by-label/data"; + fsType = "ext4"; + }; + }; + + services.saunafs = { + masterHost = "master"; + chunkserver = { + openFirewall = true; + enable = true; + hdds = [ "/data" ]; + + # The test image is too small and gets set to "full" + settings.HDD_LEAVE_SPACE_DEFAULT = "100M"; + }; + }; + }; + + metalogger = + { pkgs, ... }: + { + services.saunafs = { + masterHost = "master"; + metalogger.enable = true; + }; + }; + + client = + { pkgs, lib, ... }: + { + services.saunafs.client.enable = true; + # systemd.tmpfiles.rules = [ "d /sfs 755 root root -" ]; + systemd.network.enable = true; + + # Use networkd to have properly functioning + # network-online.target + networking = { + useDHCP = false; + useNetworkd = true; + }; + + systemd.mounts = [ + { + requires = [ "network-online.target" ]; + after = [ "network-online.target" ]; + wantedBy = [ "remote-fs.target" ]; + type = "saunafs"; + what = "master:/"; + where = "/sfs"; + } + ]; + }; + + in + { + name = "saunafs"; + + meta.maintainers = [ lib.maintainers.markuskowa ]; + + nodes = { + inherit master metalogger; + chunkserver1 = chunkserver; + chunkserver2 = chunkserver; + client1 = client; + client2 = client; + }; + + testScript = '' + # prepare master server + master.start() + master.wait_for_unit("multi-user.target") + master.succeed("sfsmaster-init") + master.succeed("systemctl restart sfs-master") + master.wait_for_unit("sfs-master.service") + + metalogger.wait_for_unit("sfs-metalogger.service") + + # Setup chunkservers + for chunkserver in [chunkserver1, chunkserver2]: + chunkserver.wait_for_unit("multi-user.target") + chunkserver.succeed("chown saunafs:saunafs /data") + chunkserver.succeed("systemctl restart sfs-chunkserver") + chunkserver.wait_for_unit("sfs-chunkserver.service") + + for client in [client1, client2]: + client.wait_for_unit("multi-user.target") + + client1.succeed("echo test > /sfs/file") + client2.succeed("grep test /sfs/file") + ''; + } +) diff --git a/nixos/tests/scion/freestanding-deployment/bootstrap.sh b/nixos/tests/scion/freestanding-deployment/bootstrap.sh new file mode 100644 index 000000000000..3e2f7da07443 --- /dev/null +++ b/nixos/tests/scion/freestanding-deployment/bootstrap.sh @@ -0,0 +1,73 @@ +set -euo pipefail + +mkdir /tmp/tutorial-scion-certs && cd /tmp/tutorial-scion-certs +mkdir AS{1..5} + +# Create voting and root keys and (self-signed) certificates for core ASes +pushd AS1 +scion-pki certificate create --not-after=3650d --profile=sensitive-voting <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 sensitive voting cert"}') sensitive-voting.pem sensitive-voting.key +scion-pki certificate create --not-after=3650d --profile=regular-voting <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 regular voting cert"}') regular-voting.pem regular-voting.key +scion-pki certificate create --not-after=3650d --profile=cp-root <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 cp root cert"}') cp-root.pem cp-root.key +popd + +pushd AS2 +scion-pki certificate create --not-after=3650d --profile=cp-root <(echo '{"isd_as": "42-ffaa:1:2", "common_name": "42-ffaa:1:2 cp root cert"}') cp-root.pem cp-root.key +popd + +pushd AS3 +scion-pki certificate create --not-after=3650d --profile=sensitive-voting <(echo '{"isd_as": "42-ffaa:1:3", "common_name": "42-ffaa:1:3 sensitive voting cert"}') sensitive-voting.pem sensitive-voting.key +scion-pki certificate create --not-after=3650d --profile=regular-voting <(echo '{"isd_as": "42-ffaa:1:3", "common_name": "42-ffaa:1:3 regular voting cert"}') regular-voting.pem regular-voting.key +popd + +# Create the TRC (Trust Root Configuration) +mkdir tmp +echo ' +isd = 42 +description = "Demo ISD 42" +serial_version = 1 +base_version = 1 +voting_quorum = 2 + +core_ases = ["ffaa:1:1", "ffaa:1:2", "ffaa:1:3"] +authoritative_ases = ["ffaa:1:1", "ffaa:1:2", "ffaa:1:3"] +cert_files = ["AS1/sensitive-voting.pem", "AS1/regular-voting.pem", "AS1/cp-root.pem", "AS2/cp-root.pem", "AS3/sensitive-voting.pem", "AS3/regular-voting.pem"] + +[validity] +not_before = '$(date +%s)' +validity = "365d"' \ +> trc-B1-S1-pld.tmpl + +scion-pki trc payload --out=tmp/ISD42-B1-S1.pld.der --template trc-B1-S1-pld.tmpl +rm trc-B1-S1-pld.tmpl + +# Sign and bundle the TRC +scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS1/sensitive-voting.{pem,key} --out tmp/ISD42-B1-S1.AS1-sensitive.trc +scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS1/regular-voting.{pem,key} --out tmp/ISD42-B1-S1.AS1-regular.trc +scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS3/sensitive-voting.{pem,key} --out tmp/ISD42-B1-S1.AS3-sensitive.trc +scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS3/regular-voting.{pem,key} --out tmp/ISD42-B1-S1.AS3-regular.trc + +scion-pki trc combine tmp/ISD42-B1-S1.AS{1,3}-{sensitive,regular}.trc --payload tmp/ISD42-B1-S1.pld.der --out ISD42-B1-S1.trc +rm tmp -r + +# Create CA key and certificate for issuing ASes +pushd AS1 +scion-pki certificate create --profile=cp-ca <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 CA cert"}') cp-ca.pem cp-ca.key --ca cp-root.pem --ca-key cp-root.key +popd +pushd AS2 +scion-pki certificate create --profile=cp-ca <(echo '{"isd_as": "42-ffaa:1:2", "common_name": "42-ffaa:1:2 CA cert"}') cp-ca.pem cp-ca.key --ca cp-root.pem --ca-key cp-root.key +popd + +# Create AS key and certificate chains +scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 AS cert"}') AS1/cp-as.pem AS1/cp-as.key --ca AS1/cp-ca.pem --ca-key AS1/cp-ca.key --bundle +scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:2", "common_name": "42-ffaa:1:2 AS cert"}') AS2/cp-as.pem AS2/cp-as.key --ca AS2/cp-ca.pem --ca-key AS2/cp-ca.key --bundle +scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:3", "common_name": "42-ffaa:1:3 AS cert"}') AS3/cp-as.pem AS3/cp-as.key --ca AS1/cp-ca.pem --ca-key AS1/cp-ca.key --bundle +scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:4", "common_name": "42-ffaa:1:4 AS cert"}') AS4/cp-as.pem AS4/cp-as.key --ca AS1/cp-ca.pem --ca-key AS1/cp-ca.key --bundle +scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:5", "common_name": "42-ffaa:1:5 AS cert"}') AS5/cp-as.pem AS5/cp-as.key --ca AS2/cp-ca.pem --ca-key AS2/cp-ca.key --bundle + +for i in {1..5} +do + mkdir -p $out/AS$i + cp AS$i/cp-as.{key,pem} $out/AS$i +done + +mv *.trc $out diff --git a/nixos/tests/scion/freestanding-deployment/default.nix b/nixos/tests/scion/freestanding-deployment/default.nix index e060f9c31270..55b2322ab02d 100644 --- a/nixos/tests/scion/freestanding-deployment/default.nix +++ b/nixos/tests/scion/freestanding-deployment/default.nix @@ -5,81 +5,8 @@ let buildInputs = [ pkgs.scion ]; - } '' - set -euo pipefail + } (builtins.readFile ./bootstrap.sh); - mkdir /tmp/tutorial-scion-certs && cd /tmp/tutorial-scion-certs - mkdir AS{1..5} - - # Create voting and root keys and (self-signed) certificates for core ASes - pushd AS1 - scion-pki certificate create --not-after=3650d --profile=sensitive-voting <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 sensitive voting cert"}') sensitive-voting.pem sensitive-voting.key - scion-pki certificate create --not-after=3650d --profile=regular-voting <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 regular voting cert"}') regular-voting.pem regular-voting.key - scion-pki certificate create --not-after=3650d --profile=cp-root <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 cp root cert"}') cp-root.pem cp-root.key - popd - - pushd AS2 - scion-pki certificate create --not-after=3650d --profile=cp-root <(echo '{"isd_as": "42-ffaa:1:2", "common_name": "42-ffaa:1:2 cp root cert"}') cp-root.pem cp-root.key - popd - - pushd AS3 - scion-pki certificate create --not-after=3650d --profile=sensitive-voting <(echo '{"isd_as": "42-ffaa:1:3", "common_name": "42-ffaa:1:3 sensitive voting cert"}') sensitive-voting.pem sensitive-voting.key - scion-pki certificate create --not-after=3650d --profile=regular-voting <(echo '{"isd_as": "42-ffaa:1:3", "common_name": "42-ffaa:1:3 regular voting cert"}') regular-voting.pem regular-voting.key - popd - - # Create the TRC (Trust Root Configuration) - mkdir tmp - echo ' - isd = 42 - description = "Demo ISD 42" - serial_version = 1 - base_version = 1 - voting_quorum = 2 - - core_ases = ["ffaa:1:1", "ffaa:1:2", "ffaa:1:3"] - authoritative_ases = ["ffaa:1:1", "ffaa:1:2", "ffaa:1:3"] - cert_files = ["AS1/sensitive-voting.pem", "AS1/regular-voting.pem", "AS1/cp-root.pem", "AS2/cp-root.pem", "AS3/sensitive-voting.pem", "AS3/regular-voting.pem"] - - [validity] - not_before = '$(date +%s)' - validity = "365d"' \ - > trc-B1-S1-pld.tmpl - - scion-pki trc payload --out=tmp/ISD42-B1-S1.pld.der --template trc-B1-S1-pld.tmpl - rm trc-B1-S1-pld.tmpl - - # Sign and bundle the TRC - scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS1/sensitive-voting.{pem,key} --out tmp/ISD42-B1-S1.AS1-sensitive.trc - scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS1/regular-voting.{pem,key} --out tmp/ISD42-B1-S1.AS1-regular.trc - scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS3/sensitive-voting.{pem,key} --out tmp/ISD42-B1-S1.AS3-sensitive.trc - scion-pki trc sign tmp/ISD42-B1-S1.pld.der AS3/regular-voting.{pem,key} --out tmp/ISD42-B1-S1.AS3-regular.trc - - scion-pki trc combine tmp/ISD42-B1-S1.AS{1,3}-{sensitive,regular}.trc --payload tmp/ISD42-B1-S1.pld.der --out ISD42-B1-S1.trc - rm tmp -r - - # Create CA key and certificate for issuing ASes - pushd AS1 - scion-pki certificate create --profile=cp-ca <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 CA cert"}') cp-ca.pem cp-ca.key --ca cp-root.pem --ca-key cp-root.key - popd - pushd AS2 - scion-pki certificate create --profile=cp-ca <(echo '{"isd_as": "42-ffaa:1:2", "common_name": "42-ffaa:1:2 CA cert"}') cp-ca.pem cp-ca.key --ca cp-root.pem --ca-key cp-root.key - popd - - # Create AS key and certificate chains - scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:1", "common_name": "42-ffaa:1:1 AS cert"}') AS1/cp-as.pem AS1/cp-as.key --ca AS1/cp-ca.pem --ca-key AS1/cp-ca.key --bundle - scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:2", "common_name": "42-ffaa:1:2 AS cert"}') AS2/cp-as.pem AS2/cp-as.key --ca AS2/cp-ca.pem --ca-key AS2/cp-ca.key --bundle - scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:3", "common_name": "42-ffaa:1:3 AS cert"}') AS3/cp-as.pem AS3/cp-as.key --ca AS1/cp-ca.pem --ca-key AS1/cp-ca.key --bundle - scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:4", "common_name": "42-ffaa:1:4 AS cert"}') AS4/cp-as.pem AS4/cp-as.key --ca AS1/cp-ca.pem --ca-key AS1/cp-ca.key --bundle - scion-pki certificate create --profile=cp-as <(echo '{"isd_as": "42-ffaa:1:5", "common_name": "42-ffaa:1:5 AS cert"}') AS5/cp-as.pem AS5/cp-as.key --ca AS2/cp-ca.pem --ca-key AS2/cp-ca.key --bundle - - for i in {1..5} - do - mkdir -p $out/AS$i - cp AS$i/cp-as.{key,pem} $out/AS$i - done - - mv *.trc $out - ''; imports = hostId: [ ({ services.scion = { @@ -121,9 +48,47 @@ in }; scion04 = { ... }: { imports = (imports 4); + networking.interfaces."lo".ipv4.addresses = [{ address = "172.16.1.1"; prefixLength = 32; }]; + services.scion.scion-ip-gateway = { + enable = true; + config = { + tunnel = { + src_ipv4 = "172.16.1.1"; + }; + }; + trafficConfig = { + ASes = { + "42-ffaa:1:5" = { + Nets = [ + "172.16.100.0/24" + ]; + }; + }; + ConfigVersion = 9001; + }; + }; }; scion05 = { ... }: { imports = (imports 5); + networking.interfaces."lo".ipv4.addresses = [{ address = "172.16.100.1"; prefixLength = 32; }]; + services.scion.scion-ip-gateway = { + enable = true; + config = { + tunnel = { + src_ipv4 = "172.16.100.1"; + }; + }; + trafficConfig = { + ASes = { + "42-ffaa:1:4" = { + Nets = [ + "172.16.1.0/24" + ]; + }; + }; + ConfigVersion = 9001; + }; + }; }; }; testScript = let @@ -131,25 +96,35 @@ in addresses="42-ffaa:1:1 42-ffaa:1:2 42-ffaa:1:3 42-ffaa:1:4 42-ffaa:1:5" timeout=100 wait_for_all() { + ret=0 for as in "$@" do scion showpaths $as --no-probe > /dev/null - return 1 + ret=$? + if [ "$ret" -ne "0" ]; then + break + fi done - return 0 + return $ret } ping_all() { + ret=0 for as in "$@" do scion ping "$as,127.0.0.1" -c 3 + ret=$? + if [ "$ret" -ne "0" ]; then + break + fi done - return 0 + return $ret } for i in $(seq 0 $timeout); do - wait_for_all $addresses && exit 0 - ping_all $addresses && exit 0 sleep 1 + wait_for_all $addresses || continue + ping_all $addresses && exit 0 done + exit 1 ''; in '' @@ -183,9 +158,16 @@ in # Wait for scion-control.service on all instances wait_for_unit("scion-control.service") + # Ensure cert is valid against TRC + succeed("scion-pki certificate verify --trc /etc/scion/certs/*.trc /etc/scion/crypto/as/*.pem >&2") + # Execute pingAll command on all instances succeed("${pingAll} >&2") + # Execute ICMP pings across scion-ip-gateway + scion04.succeed("ping -c 3 172.16.100.1 >&2") + scion05.succeed("ping -c 3 172.16.1.1 >&2") + # Restart all scion services and ping again to test robustness succeed("systemctl restart scion-* >&2") succeed("${pingAll} >&2") diff --git a/nixos/tests/scion/freestanding-deployment/topology4.json b/nixos/tests/scion/freestanding-deployment/topology4.json index 03c507a4daf5..0526c1f1edd6 100644 --- a/nixos/tests/scion/freestanding-deployment/topology4.json +++ b/nixos/tests/scion/freestanding-deployment/topology4.json @@ -36,5 +36,11 @@ } } } + }, + "sigs": { + "sig-1": { + "ctrl_addr": "127.0.0.1:30256", + "data_addr": "127.0.0.1:30056" + } } } diff --git a/nixos/tests/scion/freestanding-deployment/topology5.json b/nixos/tests/scion/freestanding-deployment/topology5.json index 6114c1f73c2a..1326bd3c2955 100644 --- a/nixos/tests/scion/freestanding-deployment/topology5.json +++ b/nixos/tests/scion/freestanding-deployment/topology5.json @@ -36,5 +36,11 @@ } } } + }, + "sigs": { + "sig-1": { + "ctrl_addr": "127.0.0.1:30256", + "data_addr": "127.0.0.1:30056" + } } } diff --git a/nixos/tests/scrutiny.nix b/nixos/tests/scrutiny.nix index 33160a6b3088..4ac1b47952e0 100644 --- a/nixos/tests/scrutiny.nix +++ b/nixos/tests/scrutiny.nix @@ -49,10 +49,6 @@ import ./make-test-python.nix ({ lib, ... }: testScript = '' start_all() - # Wait for InfluxDB to be available - machine.wait_for_unit("influxdb2") - machine.wait_for_open_port(8086) - # Wait for Scrutiny to be available machine.wait_for_unit("scrutiny") machine.wait_for_open_port(8080) diff --git a/nixos/tests/seafile.nix b/nixos/tests/seafile.nix index 78e735f4fed7..7784d5fddaed 100644 --- a/nixos/tests/seafile.nix +++ b/nixos/tests/seafile.nix @@ -14,6 +14,7 @@ import ./make-test-python.nix ({ pkgs, ... }: services.seafile = { enable = true; ccnetSettings.General.SERVICE_URL = "http://server"; + seafileSettings.fileserver.host = "unix:/run/seafile/server.sock"; adminEmail = "admin@example.com"; initialAdminPassword = "seafile_password"; }; @@ -22,7 +23,7 @@ import ./make-test-python.nix ({ pkgs, ... }: virtualHosts."server" = { locations."/".proxyPass = "http://unix:/run/seahub/gunicorn.sock"; locations."/seafhttp" = { - proxyPass = "http://127.0.0.1:8082"; + proxyPass = "http://unix:/run/seafile/server.sock"; extraConfig = '' rewrite ^/seafhttp(.*)$ $1 break; client_max_body_size 0; diff --git a/nixos/tests/send.nix b/nixos/tests/send.nix new file mode 100644 index 000000000000..b02f083fef9f --- /dev/null +++ b/nixos/tests/send.nix @@ -0,0 +1,34 @@ +{ lib, pkgs, ... }: +{ + name = "send"; + + meta = { + maintainers = with lib.maintainers; [ moraxyc ]; + }; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = with pkgs; [ + curl + ffsend + ]; + + services.send = { + enable = true; + }; + }; + + testScript = '' + machine.wait_for_unit("send.service") + + machine.wait_for_open_port(1443) + + machine.succeed("curl --fail --max-time 10 http://127.0.0.1:1443") + + machine.succeed("echo HelloWorld > /tmp/test") + url = machine.succeed("ffsend upload -q -h http://127.0.0.1:1443/ /tmp/test") + machine.succeed(f'ffsend download --output /tmp/download {url}') + machine.succeed("cat /tmp/download | grep HelloWorld") + ''; +} diff --git a/nixos/tests/sing-box.nix b/nixos/tests/sing-box.nix index 582d594be3fd..a8a287586af2 100644 --- a/nixos/tests/sing-box.nix +++ b/nixos/tests/sing-box.nix @@ -1,48 +1,548 @@ -import ./make-test-python.nix ({ lib, pkgs, ... }: { +import ./make-test-python.nix ( + { lib, pkgs, ... }: + let + wg-keys = import ./wireguard/snakeoil-keys.nix; - name = "sing-box"; + target_host = "acme.test"; + server_host = "sing-box.test"; - meta = { - maintainers = with lib.maintainers; [ nickcao ]; - }; + hosts = { + "${target_host}" = "1.1.1.1"; + "${server_host}" = "1.1.1.2"; + }; + hostsEntries = lib.mapAttrs' (k: v: { + name = v; + value = lib.singleton k; + }) hosts; + + vmessPort = 1080; + vmessUUID = "bf000d23-0752-40b4-affe-68f7707a9661"; + vmessInbound = { + type = "vmess"; + tag = "inbound:vmess"; + listen = "0.0.0.0"; + listen_port = vmessPort; + users = [ + { + name = "sekai"; + uuid = vmessUUID; + alterId = 0; + } + ]; + }; + vmessOutbound = { + type = "vmess"; + tag = "outbound:vmess"; + server = server_host; + server_port = vmessPort; + uuid = vmessUUID; + security = "auto"; + alter_id = 0; + }; + + tunInbound = { + type = "tun"; + tag = "inbound:tun"; + interface_name = "tun0"; + address = [ + "172.16.0.1/30" + "fd00::1/126" + ]; + auto_route = true; + iproute2_table_index = 2024; + iproute2_rule_index = 9001; + route_address = [ + "${hosts."${target_host}"}/32" + ]; + route_exclude_address = [ + "${hosts."${server_host}"}/32" + ]; + strict_route = false; + sniff = true; + sniff_override_destination = false; + }; + + tproxyPort = 1081; + tproxyPost = pkgs.writeShellApplication { + name = "exe"; + runtimeInputs = with pkgs; [ + iproute2 + iptables + ]; + text = '' + ip route add local default dev lo table 100 + ip rule add fwmark 1 table 100 + + iptables -t mangle -N SING_BOX + iptables -t mangle -A SING_BOX -d 100.64.0.0/10 -j RETURN + iptables -t mangle -A SING_BOX -d 127.0.0.0/8 -j RETURN + iptables -t mangle -A SING_BOX -d 169.254.0.0/16 -j RETURN + iptables -t mangle -A SING_BOX -d 172.16.0.0/12 -j RETURN + iptables -t mangle -A SING_BOX -d 192.0.0.0/24 -j RETURN + iptables -t mangle -A SING_BOX -d 224.0.0.0/4 -j RETURN + iptables -t mangle -A SING_BOX -d 240.0.0.0/4 -j RETURN + iptables -t mangle -A SING_BOX -d 255.255.255.255/32 -j RETURN + + iptables -t mangle -A SING_BOX -d ${hosts."${server_host}"}/32 -p tcp -j RETURN + iptables -t mangle -A SING_BOX -d ${hosts."${server_host}"}/32 -p udp -j RETURN + + iptables -t mangle -A SING_BOX -d ${hosts."${target_host}"}/32 -p tcp -j TPROXY --on-port ${toString tproxyPort} --tproxy-mark 1 + iptables -t mangle -A SING_BOX -d ${hosts."${target_host}"}/32 -p udp -j TPROXY --on-port ${toString tproxyPort} --tproxy-mark 1 + iptables -t mangle -A PREROUTING -j SING_BOX + + iptables -t mangle -N SING_BOX_SELF + iptables -t mangle -A SING_BOX_SELF -d 100.64.0.0/10 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 127.0.0.0/8 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 169.254.0.0/16 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 172.16.0.0/12 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 192.0.0.0/24 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 224.0.0.0/4 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 240.0.0.0/4 -j RETURN + iptables -t mangle -A SING_BOX_SELF -d 255.255.255.255/32 -j RETURN + iptables -t mangle -A SING_BOX_SELF -j RETURN -m mark --mark 1234 + + iptables -t mangle -A SING_BOX_SELF -d ${hosts."${server_host}"}/32 -p tcp -j RETURN + iptables -t mangle -A SING_BOX_SELF -d ${hosts."${server_host}"}/32 -p udp -j RETURN + iptables -t mangle -A SING_BOX_SELF -p tcp -j MARK --set-mark 1 + iptables -t mangle -A SING_BOX_SELF -p udp -j MARK --set-mark 1 + iptables -t mangle -A OUTPUT -j SING_BOX_SELF + ''; + }; + in + { + + name = "sing-box"; - nodes.machine = { pkgs, ... }: { - environment.systemPackages = [ pkgs.curl ]; - services.nginx = { - enable = true; - statusPage = true; + meta = { + maintainers = with lib.maintainers; [ nickcao ]; }; - services.sing-box = { - enable = true; - settings = { - inbounds = [{ - type = "mixed"; - tag = "inbound"; - listen = "127.0.0.1"; - listen_port = 1080; - users = [{ - username = "user"; - password = { _secret = pkgs.writeText "password" "supersecret"; }; - }]; - }]; - outbounds = [{ - type = "direct"; - tag = "outbound"; - }]; - }; + + nodes = { + target = + { pkgs, ... }: + { + networking = { + firewall.enable = false; + hosts = hostsEntries; + useDHCP = false; + interfaces.eth1 = { + ipv4.addresses = [ + { + address = hosts."${target_host}"; + prefixLength = 24; + } + ]; + }; + }; + + services.dnsmasq.enable = true; + + services.nginx = { + enable = true; + package = pkgs.nginxQuic; + + virtualHosts."${target_host}" = { + onlySSL = true; + sslCertificate = ./common/acme/server/acme.test.cert.pem; + sslCertificateKey = ./common/acme/server/acme.test.key.pem; + http2 = true; + http3 = true; + http3_hq = false; + quic = true; + reuseport = true; + locations."/" = { + extraConfig = '' + default_type text/plain; + return 200 "$server_protocol $remote_addr"; + allow ${hosts."${server_host}"}/32; + deny all; + ''; + }; + }; + }; + }; + + server = + { pkgs, ... }: + { + boot.kernel.sysctl = { + "net.ipv4.conf.all.forwarding" = 1; + }; + + networking = { + firewall.enable = false; + hosts = hostsEntries; + useDHCP = false; + interfaces.eth1 = { + ipv4.addresses = [ + { + address = hosts."${server_host}"; + prefixLength = 24; + } + ]; + }; + }; + + systemd.network.wait-online.ignoredInterfaces = [ "wg0" ]; + + networking.wg-quick.interfaces.wg0 = { + address = [ + "10.23.42.1/24" + ]; + listenPort = 2408; + mtu = 1500; + + inherit (wg-keys.peer0) privateKey; + + peers = lib.singleton { + allowedIPs = [ + "10.23.42.2/32" + ]; + + inherit (wg-keys.peer1) publicKey; + }; + + postUp = '' + ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT + ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.23.42.0/24 -o eth1 -j MASQUERADE + ''; + }; + + services.sing-box = { + enable = true; + settings = { + inbounds = [ + vmessInbound + ]; + outbounds = [ + { + type = "direct"; + tag = "outbound:direct"; + } + ]; + }; + }; + }; + + tun = + { pkgs, ... }: + { + networking = { + firewall.enable = false; + hosts = hostsEntries; + useDHCP = false; + interfaces.eth1 = { + ipv4.addresses = [ + { + address = "1.1.1.3"; + prefixLength = 24; + } + ]; + }; + }; + + security.pki.certificates = [ + (builtins.readFile ./common/acme/server/ca.cert.pem) + ]; + + environment.systemPackages = [ + pkgs.curlHTTP3 + pkgs.iproute2 + ]; + + services.sing-box = { + enable = true; + settings = { + inbounds = [ + tunInbound + ]; + outbounds = [ + { + type = "block"; + tag = "outbound:block"; + } + { + type = "direct"; + tag = "outbound:direct"; + } + vmessOutbound + ]; + route = { + final = "outbound:block"; + rules = [ + { + inbound = [ + "inbound:tun" + ]; + outbound = "outbound:vmess"; + } + ]; + }; + }; + }; + }; + + wireguard = + { pkgs, ... }: + { + networking = { + firewall.enable = false; + hosts = hostsEntries; + useDHCP = false; + interfaces.eth1 = { + ipv4.addresses = [ + { + address = "1.1.1.4"; + prefixLength = 24; + } + ]; + }; + }; + + security.pki.certificates = [ + (builtins.readFile ./common/acme/server/ca.cert.pem) + ]; + + environment.systemPackages = [ + pkgs.curlHTTP3 + pkgs.iproute2 + ]; + + services.sing-box = { + enable = true; + settings = { + outbounds = [ + { + type = "block"; + tag = "outbound:block"; + } + { + type = "direct"; + tag = "outbound:direct"; + } + { + detour = "outbound:direct"; + type = "wireguard"; + tag = "outbound:wireguard"; + interface_name = "wg0"; + local_address = [ "10.23.42.2/32" ]; + mtu = 1280; + private_key = wg-keys.peer1.privateKey; + peer_public_key = wg-keys.peer0.publicKey; + server = server_host; + server_port = 2408; + system_interface = true; + } + ]; + route = { + final = "outbound:block"; + }; + }; + }; + }; + + tproxy = + { pkgs, ... }: + { + networking = { + firewall.enable = false; + hosts = hostsEntries; + useDHCP = false; + interfaces.eth1 = { + ipv4.addresses = [ + { + address = "1.1.1.5"; + prefixLength = 24; + } + ]; + }; + }; + + security.pki.certificates = [ + (builtins.readFile ./common/acme/server/ca.cert.pem) + ]; + + environment.systemPackages = [ pkgs.curlHTTP3 ]; + + systemd.services.sing-box.serviceConfig.ExecStartPost = [ + "+${tproxyPost}/bin/exe" + ]; + + services.sing-box = { + enable = true; + settings = { + inbounds = [ + { + tag = "inbound:tproxy"; + type = "tproxy"; + listen = "0.0.0.0"; + listen_port = tproxyPort; + udp_fragment = true; + sniff = true; + sniff_override_destination = false; + } + ]; + outbounds = [ + { + type = "block"; + tag = "outbound:block"; + } + { + type = "direct"; + tag = "outbound:direct"; + } + vmessOutbound + ]; + route = { + final = "outbound:block"; + rules = [ + { + inbound = [ + "inbound:tproxy" + ]; + outbound = "outbound:vmess"; + } + ]; + }; + }; + }; + }; + + fakeip = + { pkgs, ... }: + { + networking = { + firewall.enable = false; + hosts = hostsEntries; + useDHCP = false; + interfaces.eth1 = { + ipv4.addresses = [ + { + address = "1.1.1.6"; + prefixLength = 24; + } + ]; + }; + }; + + environment.systemPackages = [ pkgs.dnsutils ]; + + services.sing-box = { + enable = true; + settings = { + dns = { + final = "dns:default"; + independent_cache = true; + fakeip = { + enabled = true; + "inet4_range" = "198.18.0.0/16"; + }; + servers = [ + { + detour = "outbound:direct"; + tag = "dns:default"; + address = hosts."${target_host}"; + } + { + tag = "dns:fakeip"; + address = "fakeip"; + } + ]; + rules = [ + { + outbound = [ "any" ]; + server = "dns:default"; + } + { + query_type = [ + "A" + "AAAA" + ]; + server = "dns:fakeip"; + + } + ]; + }; + inbounds = [ + tunInbound + ]; + outbounds = [ + { + type = "block"; + tag = "outbound:block"; + } + { + type = "direct"; + tag = "outbound:direct"; + } + { + type = "dns"; + tag = "outbound:dns"; + } + ]; + route = { + final = "outbound:direct"; + rules = [ + { + protocol = "dns"; + outbound = "outbound:dns"; + } + ]; + }; + }; + }; + }; }; - }; - testScript = '' - machine.wait_for_unit("nginx.service") - machine.wait_for_unit("sing-box.service") + testScript = '' + target.wait_for_unit("nginx.service") + target.wait_for_open_port(443) + target.wait_for_unit("dnsmasq.service") + target.wait_for_open_port(53) + + server.wait_for_unit("sing-box.service") + server.wait_for_open_port(1080) + server.wait_for_unit("wg-quick-wg0.service") + server.wait_for_file("/sys/class/net/wg0") + + def test_curl(machine, extra_args=""): + assert ( + machine.succeed(f"curl --fail --max-time 10 --http2 https://${target_host} {extra_args}") + == "HTTP/2.0 ${hosts.${server_host}}" + ) + assert ( + machine.succeed(f"curl --fail --max-time 10 --http3-only https://${target_host} {extra_args}") + == "HTTP/3.0 ${hosts.${server_host}}" + ) + + with subtest("tun"): + tun.wait_for_unit("sing-box.service") + tun.wait_for_unit("sys-devices-virtual-net-${tunInbound.interface_name}.device") + tun.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev ${tunInbound.interface_name}'") + tun.succeed("ip addr show ${tunInbound.interface_name}") + tun.succeed("ip route show table ${toString tunInbound.iproute2_table_index} | grep ${tunInbound.interface_name}") + assert ( + tun.succeed("ip rule list table ${toString tunInbound.iproute2_table_index} | sort | head -1 | awk -F: '{print $1}' | tr -d '\n'") + == "${toString tunInbound.iproute2_rule_index}" + ) + test_curl(tun) + + with subtest("wireguard"): + wireguard.wait_for_unit("sing-box.service") + wireguard.wait_for_unit("sys-devices-virtual-net-wg0.device") + wireguard.succeed("ip addr show wg0") + test_curl(wireguard, "--interface wg0") - machine.wait_for_open_port(80) - machine.wait_for_open_port(1080) + with subtest("tproxy"): + tproxy.wait_for_unit("sing-box.service") + test_curl(tproxy) - machine.succeed("curl --fail --max-time 10 --proxy http://user:supersecret@localhost:1080 http://localhost") - machine.fail("curl --fail --max-time 10 --proxy http://user:supervillain@localhost:1080 http://localhost") - machine.succeed("curl --fail --max-time 10 --proxy socks5://user:supersecret@localhost:1080 http://localhost") - ''; + with subtest("fakeip"): + fakeip.wait_for_unit("sing-box.service") + fakeip.wait_for_unit("sys-devices-virtual-net-${tunInbound.interface_name}.device") + fakeip.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev ${tunInbound.interface_name}'") + fakeip.succeed("dig +short A ${target_host} @${target_host} | grep '^198.18.'") + ''; -}) + } +) diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix index ad516b6e8d2b..f35eaf9c8e78 100644 --- a/nixos/tests/slurm.nix +++ b/nixos/tests/slurm.nix @@ -154,7 +154,7 @@ in { with subtest("run_distributed_command"): # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes). - # The output must contain the 3 different names + # The output must contain the 3 different names submit.succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq") with subtest("check_slurm_dbd"): diff --git a/nixos/tests/spiped.nix b/nixos/tests/spiped.nix new file mode 100644 index 000000000000..a39fc2fd722b --- /dev/null +++ b/nixos/tests/spiped.nix @@ -0,0 +1,73 @@ +{ pkgs, ... }: +let + key = pkgs.runCommand "key" { } "${pkgs.openssl}/bin/openssl rand 32 > $out"; +in +{ + name = "spiped"; + meta = with pkgs.lib.maintainers; { + maintainers = [ tomfitzhenry ]; + }; + + nodes = { + server = + { pkgs, lib, ... }: + { + services.caddy = { + enable = true; + settings = { + apps.http.servers.default = { + listen = [ ":80" ]; + routes = [ + { + handle = [ + { + body = "hello world"; + handler = "static_response"; + status_code = 200; + } + ]; + } + ]; + }; + }; + }; + + systemd.services."spiped@server" = { + wantedBy = [ "multi-user.target" ]; + overrideStrategy = "asDropin"; + }; + systemd.services."spiped@client" = { + wantedBy = [ "multi-user.target" ]; + overrideStrategy = "asDropin"; + }; + services.spiped = { + enable = true; + config = { + server = { + source = "localhost:8080"; + target = "localhost:80"; + keyfile = key; + decrypt = true; + }; + client = { + source = "localhost:8081"; + target = "localhost:8080"; + keyfile = key; + encrypt = true; + }; + }; + }; + }; + }; + + testScript = + { nodes, ... }: + '' + server.wait_for_unit("caddy") + server.wait_for_open_port(80) + server.wait_for_open_port(8080) + server.wait_for_open_port(8081) + + server.succeed("curl http://localhost:8081 | grep hello") + ''; +} diff --git a/nixos/tests/stub-ld.nix b/nixos/tests/stub-ld.nix index 72b0aebf3e6c..f34e082cfd22 100644 --- a/nixos/tests/stub-ld.nix +++ b/nixos/tests/stub-ld.nix @@ -18,7 +18,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { libDir = pkgs.stdenv.hostPlatform.libDir; ldsoBasename = lib.last (lib.splitString "/" pkgs.stdenv.cc.bintools.dynamicLinker); - check32 = pkgs.stdenv.isx86_64; + check32 = pkgs.stdenv.hostPlatform.isx86_64; pkgs32 = pkgs.pkgsi686Linux; libDir32 = pkgs32.stdenv.hostPlatform.libDir; diff --git a/nixos/tests/sunshine.nix b/nixos/tests/sunshine.nix index 7c7e86de203a..a1207bfa49f7 100644 --- a/nixos/tests/sunshine.nix +++ b/nixos/tests/sunshine.nix @@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "sunshine"; meta = { # test is flaky on aarch64 - broken = pkgs.stdenv.isAarch64; + broken = pkgs.stdenv.hostPlatform.isAarch64; maintainers = [ lib.maintainers.devusb ]; }; diff --git a/nixos/tests/suricata.nix b/nixos/tests/suricata.nix new file mode 100644 index 000000000000..e1cdd91aaaa2 --- /dev/null +++ b/nixos/tests/suricata.nix @@ -0,0 +1,86 @@ +import ./make-test-python.nix ( + { lib, pkgs, ... }: + { + name = "suricata"; + meta.maintainers = with lib.maintainers; [ felbinger ]; + + nodes = { + ids = { + imports = [ + ../modules/profiles/minimal.nix + ../modules/services/networking/suricata/default.nix + ]; + + networking.interfaces.eth1 = { + useDHCP = false; + ipv4.addresses = [ + { + address = "192.168.1.2"; + prefixLength = 24; + } + ]; + }; + + # disable suricata-update because this requires an Internet connection + systemd.services.suricata-update.enable = false; + + # install suricata package to make suricatasc program available + environment.systemPackages = with pkgs; [ suricata ]; + + services.suricata = { + enable = true; + settings = { + vars.address-groups.HOME_NET = "192.168.1.0/24"; + unix-command.enabled = true; + outputs = [ { fast.enabled = true; } ]; + af-packet = [ { interface = "eth1"; } ]; + classification-file = "${pkgs.suricata}/etc/suricata/classification.config"; + }; + }; + + # create suricata.rules with the rule to detect the output of the id command + systemd.tmpfiles.rules = [ + ''f /var/lib/suricata/rules/suricata.rules 644 suricata suricata 0 alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2019_07_26;)'' + ]; + }; + helper = { + imports = [ ../modules/profiles/minimal.nix ]; + + networking.interfaces.eth1 = { + useDHCP = false; + ipv4.addresses = [ + { + address = "192.168.1.1"; + prefixLength = 24; + } + ]; + }; + + services.nginx = { + enable = true; + virtualHosts."localhost".locations = { + "/id/".return = "200 'uid=0(root) gid=0(root) groups=0(root)'"; + }; + }; + networking.firewall.allowedTCPPorts = [ 80 ]; + }; + }; + + testScript = '' + start_all() + + # check that configuration has been applied correctly with suricatasc + with subtest("suricata configuration test"): + ids.wait_for_unit("suricata.service") + assert '1' in ids.succeed("suricatasc -c 'iface-list' | ${pkgs.jq}/bin/jq .message.count") + + # test detection of events based on a static ruleset (output of id command) + with subtest("suricata rule test"): + helper.wait_for_unit("nginx.service") + ids.wait_for_unit("suricata.service") + + ids.succeed("curl http://192.168.1.1/id/") + assert "id check returned root [**] [Classification: Potentially Bad Traffic]" in ids.succeed("tail -n 1 /var/log/suricata/fast.log"), "Suricata didn't detect the output of id comment" + ''; + } +) diff --git a/nixos/tests/swapspace.nix b/nixos/tests/swapspace.nix new file mode 100644 index 000000000000..ab0174629eb8 --- /dev/null +++ b/nixos/tests/swapspace.nix @@ -0,0 +1,69 @@ +import ./make-test-python.nix ( + { pkgs, lib, ... }: + + { + name = "swapspace"; + + meta = with pkgs.lib.maintainers; { + maintainers = [ + Luflosi + phanirithvij + ]; + }; + + nodes.machine = { + virtualisation.memorySize = 512; + + services.swapspace = { + enable = true; + extraArgs = [ "-v" ]; + settings = { + # test outside /var/lib/swapspace + swappath = "/swamp"; + cooldown = 1; + }; + }; + + swapDevices = lib.mkOverride 0 [ + { + size = 127; + device = "/root/swapfile"; + } + ]; + boot.kernel.sysctl."vm.swappiness" = 60; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("swapspace.service") + machine.wait_for_unit("root-swapfile.swap") + + swamp = False + with subtest("swapspace works"): + machine.execute("mkdir /root/memfs") + machine.execute("mount -o size=2G -t tmpfs none /root/memfs") + i = 0 + while i < 14: + print(machine.succeed("free -h")) + out = machine.succeed("sh -c 'swapon --show --noheadings --raw --bytes | grep /root/swapfile'") + row = out.split(' ') + # leave 1MB free to not get killed by oom + freebytes=int(row[2]) - int(row[3]) - 1*1024*1024 + machine.succeed(f"dd if=/dev/random of=/root/memfs/{i} bs={freebytes} count=1") + machine.sleep(1) + out = machine.succeed("swapon --show") + print(out) + swamp = "/swamp" in out + if not swamp: + i += 1 + else: + print("*"*10, "SWAPED", "*"*10) + machine.succeed("rm -f /root/memfs/*") + break + + print(machine.succeed("swapspace -e -s /swamp")) + assert "/swamp" not in machine.execute("swapon --show") + assert swamp + ''; + } +) diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 84c6e90689b3..a55155579b4b 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -53,11 +53,8 @@ in { environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff users.mutableUsers = false; - # For boot/switch testing - system.build.installBootLoader = lib.mkForce (pkgs.writeShellScript "install-dummy-loader" '' - echo "installing dummy bootloader" - touch /tmp/bootloader-installed - ''); + # Test that no boot loader still switches, e.g. in the ISO + boot.loader.grub.enable = false; specialisation = rec { brokenInitInterface.configuration.config.system.extraSystemBuilderCmds = '' @@ -260,6 +257,15 @@ in { systemd.services."escaped\\x2ddash".serviceConfig.X-Test = "test"; }; + unitWithMultilineValue.configuration = { + systemd.services.test.serviceConfig.ExecStart = '' + ${pkgs.coreutils}/bin/true \ + # ignored + ; ignored + blah blah + ''; + }; + unitStartingWithDash.configuration = { systemd.services."-" = { wantedBy = [ "multi-user.target" ]; @@ -587,6 +593,19 @@ in { imports = [ slice.configuration ]; systemd.slices.testslice.sliceConfig.MemoryMax = lib.mkForce null; }; + + dbusReload.configuration = { config, ... }: let + dbusService = { + "dbus" = "dbus"; + "broker" = "dbus-broker"; + }.${config.services.dbus.implementation}; + in { + # We want to make sure that stc catches this as a reload, + # not a restart. + systemd.services.${dbusService}.restartTriggers = [ + (pkgs.writeText "dbus-reload-dummy" "dbus reload dummy") + ]; + }; }; }; @@ -663,22 +682,18 @@ in { "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test" ) + boot_loader_text = "Warning: do not know how to make this configuration bootable; please enable a boot loader." with subtest("actions"): # boot action - machine.fail("test -f /tmp/bootloader-installed") out = switch_to_specialisation("${machine}", "simpleService", action="boot") - assert_contains(out, "installing dummy bootloader") + assert_contains(out, boot_loader_text) assert_lacks(out, "activating the configuration...") # good indicator of a system activation - machine.succeed("test -f /tmp/bootloader-installed") - machine.succeed("rm /tmp/bootloader-installed") # switch action - machine.fail("test -f /tmp/bootloader-installed") out = switch_to_specialisation("${machine}", "", action="switch") - assert_contains(out, "installing dummy bootloader") + assert_contains(out, boot_loader_text) assert_contains(out, "activating the configuration...") # good indicator of a system activation - machine.succeed("test -f /tmp/bootloader-installed") # test and dry-activate actions are tested further down below @@ -740,7 +755,7 @@ in { out = switch_to_specialisation("${machine}", "") assert_contains(out, "stopping the following units: test.mount\n") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: ${dbusService}\n") + assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "the following new units were started:") @@ -748,7 +763,7 @@ in { out = switch_to_specialisation("${machine}", "storeMountModified") assert_lacks(out, "stopping the following units:") assert_contains(out, "NOT restarting the following changed units: -.mount") - assert_contains(out, "reloading the following units: ${dbusService}\n") + assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "the following new units were started:") @@ -759,7 +774,7 @@ in { out = switch_to_specialisation("${machine}", "swap") assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: ${dbusService}\n") + assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") assert_lacks(out, "\nstarting the following units:") assert_contains(out, "the following new units were started: swapfile.swap") @@ -768,7 +783,7 @@ in { assert_contains(out, "stopping swap device: /swapfile") assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: ${dbusService}\n") + assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "the following new units were started:") @@ -786,10 +801,10 @@ in { # Start a simple service out = switch_to_specialisation("${machine}", "simpleService") - assert_lacks(out, "installing dummy bootloader") # test does not install a bootloader + assert_lacks(out, boot_loader_text) # test does not install a bootloader assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: ${dbusService}\n") # huh + assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") assert_lacks(out, "\nstarting the following units:") assert_contains(out, "the following new units were started: test.service\n") @@ -863,10 +878,10 @@ in { # Ensure the service can be started when the activation script isn't in toplevel # This is a lot like "Start a simple service", except activation-only deps could be gc-ed out = run_switch("${nodes.machine.specialisation.simpleServiceSeparateActivationScript.configuration.system.build.separateActivationScript}/bin/switch-to-configuration"); - assert_lacks(out, "installing dummy bootloader") # test does not install a bootloader + assert_lacks(out, boot_loader_text) # test does not install a bootloader assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: ${dbusService}\n") # huh + assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") assert_lacks(out, "\nstarting the following units:") assert_contains(out, "the following new units were started: test.service\n") @@ -874,9 +889,16 @@ in { machine.succeed("! test -e /run/current-system/dry-activate") machine.succeed("! test -e /run/current-system/bin/switch-to-configuration") + # Ensure units with multiline values work + out = switch_to_specialisation("${machine}", "unitWithMultilineValue") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_lacks(out, "restarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_contains(out, "starting the following units: test.service") + # Ensure \ works in unit names out = switch_to_specialisation("${machine}", "unitWithBackslash") - assert_contains(out, "stopping the following units: test.service\n") assert_lacks(out, "NOT restarting the following changed units:") assert_lacks(out, "reloading the following units:") assert_lacks(out, "\nrestarting the following units:") @@ -1413,5 +1435,15 @@ in { assert_lacks(out, "the following new units were started:") machine.succeed("systemctl start testservice.service") machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom") # disallow OOMing + + with subtest("dbus reloads"): + out = switch_to_specialisation("${machine}", "") + out = switch_to_specialisation("${machine}", "dbusReload") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: ${dbusService}\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") ''; }) diff --git a/nixos/tests/systemd-binfmt.nix b/nixos/tests/systemd-binfmt.nix index b16fda0ddb1a..a5e46a455d32 100644 --- a/nixos/tests/systemd-binfmt.nix +++ b/nixos/tests/systemd-binfmt.nix @@ -87,4 +87,29 @@ in { ).lower() ''; }; + + chroot = makeTest { + name = "systemd-binfmt-chroot"; + nodes.machine = { pkgs, lib, ... }: { + boot.binfmt.emulatedSystems = [ + "aarch64-linux" "wasm32-wasi" + ]; + boot.binfmt.preferStaticEmulators = true; + + environment.systemPackages = [ + (pkgs.writeShellScriptBin "test-chroot" '' + set -euo pipefail + mkdir -p /tmp/chroot + cp ${lib.getExe' pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.busybox "busybox"} /tmp/chroot/busybox + cp ${lib.getExe pkgs.pkgsCross.wasi32.yaml2json} /tmp/chroot/yaml2json # wasi binaries that build are hard to come by + chroot /tmp/chroot /busybox uname -m | grep aarch64 + echo 42 | chroot /tmp/chroot /yaml2json | grep 42 + '') + ]; + }; + testScript = '' + machine.start() + machine.succeed("test-chroot") + ''; + }; } diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index 79bfcb84ebd7..812d6088ed4e 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -1,6 +1,7 @@ -{ system ? builtins.currentSystem, - config ? {}, - pkgs ? import ../.. { inherit system config; } +{ + system ? builtins.currentSystem, + config ? { }, + pkgs ? import ../.. { inherit system config; }, }: with import ../lib/testing-python.nix { inherit system pkgs; }; @@ -16,7 +17,13 @@ let system.switch.enable = true; }; - commonXbootldr = { config, lib, pkgs, ... }: + commonXbootldr = + { + config, + lib, + pkgs, + ... + }: let diskImage = import ../lib/make-disk-image.nix { inherit config lib pkgs; @@ -85,7 +92,10 @@ in { basic = makeTest { name = "systemd-boot"; - meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; + meta.maintainers = with pkgs.lib.maintainers; [ + danielfullmer + julienmalka + ]; nodes.machine = common; @@ -117,22 +127,25 @@ in virtualisation.useSecureBoot = true; }; - testScript = let - efiArch = pkgs.stdenv.hostPlatform.efiArch; - in { nodes, ... }: '' - machine.start(allow_reboot=True) - machine.wait_for_unit("multi-user.target") + 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-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.succeed("sbctl create-keys") + machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine") + 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() + machine.reboot() - assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") - ''; + assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") + ''; }; basicXbootldr = makeTest { @@ -141,80 +154,97 @@ in nodes.machine = commonXbootldr; - testScript = { nodes, ... }: '' - ${customDiskImage nodes} + testScript = + { nodes, ... }: + '' + ${customDiskImage nodes} - machine.start() - machine.wait_for_unit("multi-user.target") + 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") + 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" - ) + # 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"') - ''; + # "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"; - meta.maintainers = with pkgs.lib.maintainers; [ lukegb julienmalka ]; - - nodes.machine = { pkgs, lib, ... }: { - imports = [ common ]; - specialisation.something.configuration = { - boot.loader.systemd-boot.sortKey = "something"; - - # Since qemu will dynamically create a devicetree blob when starting - # up, it is not straight forward to create an export of that devicetree - # blob without knowing before-hand all the flags we would pass to qemu - # (we would then be able to use `dumpdtb`). Thus, the following config - # will not boot, but it does allow us to assert that the boot entry has - # the correct contents. - boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64; - hardware.deviceTree.name = "dummy.dtb"; - hardware.deviceTree.package = lib.mkForce (pkgs.runCommand "dummy-devicetree-package" { } '' - mkdir -p $out - cp ${pkgs.emptyFile} $out/dummy.dtb - ''); + meta.maintainers = with pkgs.lib.maintainers; [ + lukegb + julienmalka + ]; + + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + specialisation.something.configuration = { + boot.loader.systemd-boot.sortKey = "something"; + + # Since qemu will dynamically create a devicetree blob when starting + # up, it is not straight forward to create an export of that devicetree + # blob without knowing before-hand all the flags we would pass to qemu + # (we would then be able to use `dumpdtb`). Thus, the following config + # will not boot, but it does allow us to assert that the boot entry has + # the correct contents. + boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64; + hardware.deviceTree.name = "dummy.dtb"; + hardware.deviceTree.package = lib.mkForce ( + pkgs.runCommand "dummy-devicetree-package" { } '' + mkdir -p $out + cp ${pkgs.emptyFile} $out/dummy.dtb + '' + ); + }; }; - }; - testScript = { nodes, ... }: '' - machine.start() - machine.wait_for_unit("multi-user.target") + testScript = + { nodes, ... }: + '' + machine.start() + machine.wait_for_unit("multi-user.target") - machine.succeed( - "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - 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" - ) - '' + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' - machine.succeed( - r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" - ) - ''; + machine.succeed( + "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) + 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" + ) + '' + + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' + machine.succeed( + r"grep 'devicetree /EFI/nixos/[a-z0-9]\{32\}.*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) + ''; }; # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI" fallback = makeTest { name = "systemd-boot-fallback"; - meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; - - nodes.machine = { pkgs, lib, ... }: { - imports = [ common ]; - boot.loader.efi.canTouchEfiVariables = mkForce false; - }; + meta.maintainers = with pkgs.lib.maintainers; [ + danielfullmer + julienmalka + ]; + + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + boot.loader.efi.canTouchEfiVariables = mkForce false; + }; testScript = '' machine.start() @@ -235,7 +265,10 @@ in update = makeTest { name = "systemd-boot-update"; - meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ]; + meta.maintainers = with pkgs.lib.maintainers; [ + danielfullmer + julienmalka + ]; nodes.machine = common; @@ -270,33 +303,99 @@ in ''; }; - memtest86 = with pkgs.lib; optionalAttrs (meta.availableOn { inherit system; } pkgs.memtest86plus) (makeTest { - name = "systemd-boot-memtest86"; - meta.maintainers = with maintainers; [ julienmalka ]; + memtest86 = + with pkgs.lib; + optionalAttrs (meta.availableOn { inherit system; } pkgs.memtest86plus) (makeTest { + name = "systemd-boot-memtest86"; + meta.maintainers = with maintainers; [ julienmalka ]; + + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.memtest86.enable = true; + }; - nodes.machine = { pkgs, lib, ... }: { + testScript = '' + machine.succeed("test -e /boot/loader/entries/memtest86.conf") + machine.succeed("test -e /boot/efi/memtest86/memtest.efi") + ''; + }); + + netbootxyz = makeTest { + name = "systemd-boot-netbootxyz"; + meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; + + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.netbootxyz.enable = true; + }; + + testScript = '' + machine.succeed("test -e /boot/loader/entries/netbootxyz.conf") + machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") + ''; + }; + + edk2-uefi-shell = makeTest { + name = "systemd-boot-edk2-uefi-shell"; + meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ]; + + nodes.machine = { ... }: { imports = [ common ]; - boot.loader.systemd-boot.memtest86.enable = true; + boot.loader.systemd-boot.edk2-uefi-shell.enable = true; }; testScript = '' - machine.succeed("test -e /boot/loader/entries/memtest86.conf") - machine.succeed("test -e /boot/efi/memtest86/memtest.efi") + machine.succeed("test -e /boot/loader/entries/edk2-uefi-shell.conf") + machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi") ''; - }); + }; - netbootxyz = makeTest { - name = "systemd-boot-netbootxyz"; - meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; + windows = makeTest { + name = "systemd-boot-windows"; + meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ]; - nodes.machine = { pkgs, lib, ... }: { + nodes.machine = { ... }: { imports = [ common ]; - boot.loader.systemd-boot.netbootxyz.enable = true; + boot.loader.systemd-boot.windows = { + "7" = { + efiDeviceHandle = "HD0c1"; + sortKey = "before_all_others"; + }; + "Ten".efiDeviceHandle = "FS0"; + "11" = { + title = "Title with-_-punctuation ...?!"; + efiDeviceHandle = "HD0d4"; + sortKey = "zzz"; + }; + }; }; testScript = '' - 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/edk2-uefi-shell/shell.efi") + + machine.succeed("test -e /boot/loader/entries/windows_7.conf") + machine.succeed("test -e /boot/loader/entries/windows_Ten.conf") + machine.succeed("test -e /boot/loader/entries/windows_11.conf") + + machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf") + machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf") + + machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf") + machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf") + + machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf") + machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf") + + machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf") + machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf") + machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf') ''; }; @@ -304,11 +403,13 @@ in name = "systemd-boot-memtest-sortkey"; meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; - nodes.machine = { pkgs, lib, ... }: { - imports = [ common ]; - boot.loader.systemd-boot.memtest86.enable = true; - boot.loader.systemd-boot.memtest86.sortKey = "apple"; - }; + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.memtest86.enable = true; + boot.loader.systemd-boot.memtest86.sortKey = "apple"; + }; testScript = '' machine.succeed("test -e /boot/loader/entries/memtest86.conf") @@ -321,35 +422,41 @@ in 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; - }; + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ commonXbootldr ]; + boot.loader.systemd-boot.memtest86.enable = true; + }; - testScript = { nodes, ... }: '' - ${customDiskImage nodes} + testScript = + { nodes, ... }: + '' + ${customDiskImage nodes} - machine.start() - machine.wait_for_unit("multi-user.target") + 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") - ''; + 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") + ''; }; extraEntries = makeTest { name = "systemd-boot-extra-entries"; meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; - nodes.machine = { pkgs, lib, ... }: { - imports = [ common ]; - boot.loader.systemd-boot.extraEntries = { - "banana.conf" = '' - title banana - ''; + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.extraEntries = { + "banana.conf" = '' + title banana + ''; + }; }; - }; testScript = '' machine.succeed("test -e /boot/loader/entries/banana.conf") @@ -361,12 +468,14 @@ in name = "systemd-boot-extra-files"; meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; - nodes.machine = { pkgs, lib, ... }: { - imports = [ common ]; - boot.loader.systemd-boot.extraFiles = { - "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.extraFiles = { + "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; + }; }; - }; testScript = '' machine.succeed("test -e /boot/efi/fruits/tomato.efi") @@ -381,55 +490,62 @@ in nodes = { inherit common; - machine = { pkgs, nodes, ... }: { - imports = [ common ]; - boot.loader.systemd-boot.extraFiles = { - "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; + machine = + { pkgs, nodes, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.extraFiles = { + "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; + }; + + # These are configs for different nodes, but we'll use them here in `machine` + system.extraDependencies = [ + nodes.common.system.build.toplevel + nodes.with_netbootxyz.system.build.toplevel + ]; }; - # These are configs for different nodes, but we'll use them here in `machine` - system.extraDependencies = [ - nodes.common.system.build.toplevel - nodes.with_netbootxyz.system.build.toplevel - ]; - }; - - with_netbootxyz = { pkgs, ... }: { - imports = [ common ]; - boot.loader.systemd-boot.netbootxyz.enable = true; - }; + with_netbootxyz = + { pkgs, ... }: + { + imports = [ common ]; + boot.loader.systemd-boot.netbootxyz.enable = true; + }; }; - testScript = { nodes, ... }: let - originalSystem = nodes.machine.system.build.toplevel; - baseSystem = nodes.common.system.build.toplevel; - finalSystem = nodes.with_netbootxyz.system.build.toplevel; - in '' - machine.succeed("test -e /boot/efi/fruits/tomato.efi") - machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") - - with subtest("remove files when no longer needed"): - machine.succeed("${baseSystem}/bin/switch-to-configuration boot") - machine.fail("test -e /boot/efi/fruits/tomato.efi") - machine.fail("test -d /boot/efi/fruits") - machine.succeed("test -d /boot/efi/nixos/.extra-files") - machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") - machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") - - with subtest("files are added back when needed again"): - machine.succeed("${originalSystem}/bin/switch-to-configuration boot") - machine.succeed("test -e /boot/efi/fruits/tomato.efi") - machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") - - with subtest("simultaneously removing and adding files works"): - 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/netbootxyz.conf") - machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") - 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") - ''; + testScript = + { nodes, ... }: + let + originalSystem = nodes.machine.system.build.toplevel; + baseSystem = nodes.common.system.build.toplevel; + finalSystem = nodes.with_netbootxyz.system.build.toplevel; + in + '' + machine.succeed("test -e /boot/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + + with subtest("remove files when no longer needed"): + machine.succeed("${baseSystem}/bin/switch-to-configuration boot") + machine.fail("test -e /boot/efi/fruits/tomato.efi") + machine.fail("test -d /boot/efi/fruits") + machine.succeed("test -d /boot/efi/nixos/.extra-files") + machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") + + with subtest("files are added back when needed again"): + machine.succeed("${originalSystem}/bin/switch-to-configuration boot") + machine.succeed("test -e /boot/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + + with subtest("simultaneously removing and adding files works"): + 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/netbootxyz.conf") + machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") + 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") + ''; }; garbage-collect-entry = makeTest { @@ -438,17 +554,20 @@ in nodes = { inherit common; - machine = { pkgs, nodes, ... }: { - imports = [ common ]; - - # These are configs for different nodes, but we'll use them here in `machine` - system.extraDependencies = [ - nodes.common.system.build.toplevel - ]; - }; + machine = + { pkgs, nodes, ... }: + { + imports = [ common ]; + + # These are configs for different nodes, but we'll use them here in `machine` + system.extraDependencies = [ + nodes.common.system.build.toplevel + ]; + }; }; - testScript = { nodes, ... }: + testScript = + { nodes, ... }: let baseSystem = nodes.common.system.build.toplevel; in @@ -461,19 +580,18 @@ in ''; }; - no-bootspec = makeTest - { - name = "systemd-boot-no-bootspec"; - meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; - - nodes.machine = { - imports = [ common ]; - boot.bootspec.enable = false; - }; + no-bootspec = makeTest { + name = "systemd-boot-no-bootspec"; + meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ]; - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") - ''; + nodes.machine = { + imports = [ common ]; + boot.bootspec.enable = false; }; + + testScript = '' + machine.start() + machine.wait_for_unit("multi-user.target") + ''; + }; } diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix index 2b7283a82193..b61cb8ddae7b 100644 --- a/nixos/tests/systemd-initrd-simple.nix +++ b/nixos/tests/systemd-initrd-simple.nix @@ -29,6 +29,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { machine.succeed("[ -e /dev/shm ]") # /dev/shm machine.succeed("[ -e /dev/pts/ptmx ]") # /dev/pts machine.succeed("[ -e /run/keys ]") # /run/keys + # /nixos-closure didn't leak into stage-2 + machine.succeed("[ ! -e /nixos-closure ]") with subtest("groups work"): machine.fail("journalctl -b 0 | grep 'systemd-udevd.*Unknown group.*ignoring'") diff --git a/nixos/tests/systemd-networkd.nix b/nixos/tests/systemd-networkd.nix index a595fb9cba4a..34272f9b0411 100644 --- a/nixos/tests/systemd-networkd.nix +++ b/nixos/tests/systemd-networkd.nix @@ -1,4 +1,4 @@ -let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: { +let generateNodeConf = { lib, pkgs, config, privk, pubk, systemdCreds, peerId, nodeId, ...}: { imports = [ common/user-account.nix ]; systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug"; networking.useNetworkd = true; @@ -6,6 +6,7 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: { networking.firewall.enable = false; virtualisation.vlans = [ 1 ]; environment.systemPackages = with pkgs; [ wireguard-tools ]; + environment.etc."credstore/network.wireguard.private" = lib.mkIf systemdCreds { text = privk; }; systemd.network = { enable = true; config = { @@ -15,11 +16,14 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: { "90-wg0" = { netdevConfig = { Kind = "wireguard"; Name = "wg0"; }; wireguardConfig = { + # Test storing wireguard private key using systemd credentials. + PrivateKey = lib.mkIf systemdCreds "@network.wireguard.private"; + # NOTE: we're storing the wireguard private key in the # store for this test. Do not do this in the real # world. Keep in mind the nix store is # world-readable. - PrivateKeyFile = pkgs.writeText "wg0-priv" privk; + PrivateKeyFile = lib.mkIf (!systemdCreds) (pkgs.writeText "wg0-priv" privk); ListenPort = 51820; FirewallMark = 42; }; @@ -74,6 +78,7 @@ in import ./make-test-python.nix ({pkgs, ... }: { let localConf = { privk = "GDiXWlMQKb379XthwX0haAbK6hTdjblllpjGX0heP00="; pubk = "iRxpqj42nnY0Qz8MAQbSm7bXxXP5hkPqWYIULmvW+EE="; + systemdCreds = false; nodeId = "1"; peerId = "2"; }; @@ -83,6 +88,7 @@ in import ./make-test-python.nix ({pkgs, ... }: { let localConf = { privk = "eHxSI2jwX/P4AOI0r8YppPw0+4NZnjOxfbS5mt06K2k="; pubk = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g="; + systemdCreds = true; nodeId = "2"; peerId = "1"; }; diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix index 3430eb9398cb..63c52227e7fd 100644 --- a/nixos/tests/systemd.nix +++ b/nixos/tests/systemd.nix @@ -75,13 +75,27 @@ import ./make-test-python.nix ({ pkgs, ... }: { rebootTime = "10min"; kexecTime = "5min"; }; + + environment.etc."systemd/system-preset/10-testservice.preset".text = '' + disable ${config.systemd.services.testservice1.name} + ''; }; - testScript = '' + testScript = { nodes, ... }: '' import re import subprocess + machine.start(allow_reboot=True) + + # Will not succeed unless ConditionFirstBoot=yes + machine.wait_for_unit("first-boot-complete.target") + + # Make sure, a subsequent boot isn't a ConditionFirstBoot=yes. + machine.reboot() machine.wait_for_x() + state = machine.get_unit_info("first-boot-complete.target")['ActiveState'] + assert state == 'inactive', "Detected first boot despite first-boot-completed.target was already reached on a previous boot." + # wait for user services machine.wait_for_unit("default.target", "alice") @@ -209,5 +223,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { with subtest("systemd environment is properly set"): machine.systemctl("daemon-reexec") # Rewrites /proc/1/environ machine.succeed("grep -q TZDIR=/etc/zoneinfo /proc/1/environ") + + with subtest("systemd presets are ignored"): + machine.succeed("systemctl preset ${nodes.machine.systemd.services.testservice1.name}") + machine.succeed("test -e /etc/systemd/system/${nodes.machine.systemd.services.testservice1.name}") ''; }) diff --git a/nixos/tests/teleport.nix b/nixos/tests/teleport.nix index 0d0b9a713065..b04b45f52132 100644 --- a/nixos/tests/teleport.nix +++ b/nixos/tests/teleport.nix @@ -9,7 +9,6 @@ with import ../lib/testing-python.nix { inherit system pkgs; }; let packages = with pkgs; { "default" = teleport; - "14" = teleport_14; "15" = teleport_15; }; diff --git a/nixos/tests/timescaledb.nix b/nixos/tests/timescaledb.nix deleted file mode 100644 index ba0a3cec6076..000000000000 --- a/nixos/tests/timescaledb.nix +++ /dev/null @@ -1,93 +0,0 @@ -# mostly copied from ./postgresql.nix as it seemed unapproriate to -# test additional extensions for postgresql there. - -{ system ? builtins.currentSystem -, config ? { } -, pkgs ? import ../.. { inherit system config; } -}: - -with import ../lib/testing-python.nix { inherit system pkgs; }; -with pkgs.lib; - -let - postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs; - test-sql = pkgs.writeText "postgresql-test" '' - CREATE EXTENSION timescaledb; - CREATE EXTENSION timescaledb_toolkit; - - CREATE TABLE sth ( - time TIMESTAMPTZ NOT NULL, - value DOUBLE PRECISION - ); - - SELECT create_hypertable('sth', 'time'); - - INSERT INTO sth (time, value) VALUES - ('2003-04-12 04:05:06 America/New_York', 1.0), - ('2003-04-12 04:05:07 America/New_York', 2.0), - ('2003-04-12 04:05:08 America/New_York', 3.0), - ('2003-04-12 04:05:09 America/New_York', 4.0), - ('2003-04-12 04:05:10 America/New_York', 5.0) - ; - - WITH t AS ( - SELECT - time_bucket('1 day'::interval, time) AS dt, - stats_agg(value) AS stats - FROM sth - GROUP BY time_bucket('1 day'::interval, time) - ) - SELECT - average(stats) - FROM t; - ''; - make-postgresql-test = postgresql-name: postgresql-package: makeTest { - name = postgresql-name; - meta = with pkgs.lib.maintainers; { - maintainers = [ typetetris ]; - }; - - nodes.machine = { ... }: - { - services.postgresql = { - enable = true; - package = postgresql-package; - extraPlugins = ps: with ps; [ - timescaledb - timescaledb_toolkit - ]; - settings = { shared_preload_libraries = "timescaledb, timescaledb_toolkit"; }; - }; - }; - - testScript = '' - def check_count(statement, lines): - return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( - statement, lines - ) - - - machine.start() - machine.wait_for_unit("postgresql") - - with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"): - machine.succeed( - "sudo -u postgres psql -f ${test-sql}" - ) - - machine.fail(check_count("SELECT * FROM sth;", 3)) - machine.succeed(check_count("SELECT * FROM sth;", 5)) - machine.fail(check_count("SELECT * FROM sth;", 4)) - - machine.shutdown() - ''; - - }; - applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12") postgresql-versions; -in -mapAttrs' - (name: package: { - inherit name; - value = make-postgresql-test name package; - }) - applicablePostgresqlVersions diff --git a/nixos/tests/timidity/basic.nix b/nixos/tests/timidity/basic.nix new file mode 100644 index 000000000000..4bbbac8fbc6c --- /dev/null +++ b/nixos/tests/timidity/basic.nix @@ -0,0 +1,20 @@ +import ../make-test-python.nix ( + { pkgs, ... }: + { + + name = "timidity"; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = [ pkgs.timidity ]; + }; + + testScript = '' + start_all () + + ## TiMidity++ is around. + machine.succeed("command -v timidity") + ''; + } +) diff --git a/nixos/tests/timidity/default.nix b/nixos/tests/timidity/default.nix new file mode 100644 index 000000000000..8e136a9721e6 --- /dev/null +++ b/nixos/tests/timidity/default.nix @@ -0,0 +1,4 @@ +inputs: { + basic = import ./basic.nix inputs; + with-vorbis = import ./with-vorbis.nix inputs; +} diff --git a/nixos/tests/timidity/tam-lin.midi b/nixos/tests/timidity/tam-lin.midi new file mode 100644 index 000000000000..38560e1ade44 --- /dev/null +++ b/nixos/tests/timidity/tam-lin.midi Binary files differdiff --git a/nixos/tests/timidity/with-vorbis.nix b/nixos/tests/timidity/with-vorbis.nix new file mode 100644 index 000000000000..db7d23a8c9c0 --- /dev/null +++ b/nixos/tests/timidity/with-vorbis.nix @@ -0,0 +1,40 @@ +import ../make-test-python.nix ( + { pkgs, ... }: + { + + name = "timidity-with-vorbis"; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = with pkgs; [ + (timidity.override { enableVorbis = true; }) + ffmpeg # # for `ffprobe` + ]; + }; + + testScript = '' + import json + + start_all() + + ## TiMidity++ is around and it claims to support Ogg Vorbis. + machine.succeed("command -v timidity") + machine.succeed("timidity --help | grep 'Ogg Vorbis'") + + ## TiMidity++ manages to process a MIDI input and produces an Ogg Vorbis + ## output file. NOTE: the `timidity` CLI succeeds even when the input file + ## does not exist; hence our test for the output file's existence. + machine.succeed("cp ${./tam-lin.midi} tam-lin.midi") + machine.succeed("timidity -Ov tam-lin.midi && test -e tam-lin.ogg") + + ## The output file has the expected characteristics. + metadata_as_text = machine.succeed("ffprobe -show_format -print_format json -i tam-lin.ogg") + metadata = json.loads(metadata_as_text) + assert metadata['format']['format_name'] == 'ogg', \ + f"expected 'format_name' to be 'ogg', got '{metadata['format']['format_name']}'" + assert 37 <= float(metadata['format']['duration']) <= 38, \ + f"expected 'duration' to be between 37s and 38s, got {metadata['format']['duration']}s" + ''; + } +) diff --git a/nixos/tests/tmate-ssh-server.nix b/nixos/tests/tmate-ssh-server.nix index 122434c505c1..593663a13a4e 100644 --- a/nixos/tests/tmate-ssh-server.nix +++ b/nixos/tests/tmate-ssh-server.nix @@ -6,9 +6,9 @@ let setUpPrivateKey = name: '' ${name}.succeed( "mkdir -p /root/.ssh", - "chown 700 /root/.ssh", + "chmod 700 /root/.ssh", "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", - "chown 600 /root/.ssh/id_snakeoil", + "chmod 600 /root/.ssh/id_snakeoil", ) ${name}.wait_for_file("/root/.ssh/id_snakeoil") ''; @@ -52,6 +52,7 @@ in server.succeed("scp ${sshOpts} /tmp/tmate.conf client:/tmp/tmate.conf") client.wait_for_file("/tmp/tmate.conf") + client.wait_until_tty_matches("1", "login:") client.send_chars("root\n") client.sleep(2) client.send_chars("tmate -f /tmp/tmate.conf\n") @@ -62,7 +63,8 @@ in client.wait_for_file("/tmp/ssh_command") ssh_cmd = client.succeed("cat /tmp/ssh_command") - client2.succeed("mkdir -p ~/.ssh; ssh-keyscan -p 2223 server > ~/.ssh/known_hosts") + client2.succeed("mkdir -p ~/.ssh; ssh-keyscan -4 -p 2223 server > ~/.ssh/known_hosts") + client2.wait_until_tty_matches("1", "login:") client2.send_chars("root\n") client2.sleep(2) client2.send_chars(ssh_cmd.strip() + "\n") diff --git a/nixos/tests/tsja.nix b/nixos/tests/tsja.nix deleted file mode 100644 index f34358ff3f5f..000000000000 --- a/nixos/tests/tsja.nix +++ /dev/null @@ -1,32 +0,0 @@ -import ./make-test-python.nix ({ pkgs, lib, ...} : { - name = "tsja"; - meta = { - maintainers = with lib.maintainers; [ chayleaf ]; - }; - - nodes = { - master = - { config, ... }: - - { - services.postgresql = { - enable = true; - extraPlugins = ps: with ps; [ - tsja - ]; - }; - }; - }; - - testScript = '' - start_all() - master.wait_for_unit("postgresql") - master.succeed("sudo -u postgres psql -f /run/current-system/sw/share/postgresql/extension/libtsja_dbinit.sql") - # make sure "日本語" is parsed as a separate lexeme - master.succeed(""" - sudo -u postgres \\ - psql -c "SELECT * FROM ts_debug('japanese', 'PostgreSQLで日本語のテキスト検索ができます。')" \\ - | grep "{日本語}" - """) - ''; -}) diff --git a/nixos/tests/unifi.nix b/nixos/tests/unifi.nix index 789b11b55985..15de78045501 100644 --- a/nixos/tests/unifi.nix +++ b/nixos/tests/unifi.nix @@ -31,6 +31,5 @@ let ''; }; in with pkgs; { - unifi7 = makeAppTest unifi7; unifi8 = makeAppTest unifi8; } diff --git a/nixos/tests/varnish.nix b/nixos/tests/varnish.nix index 76cea1ada547..b7cb79a71cb0 100644 --- a/nixos/tests/varnish.nix +++ b/nixos/tests/varnish.nix @@ -8,7 +8,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let in { name = "varnish"; meta = { - maintainers = lib.teams.helsinki-systems.members; + maintainers = [ ]; }; nodes = { diff --git a/nixos/tests/victoriametrics.nix b/nixos/tests/victoriametrics.nix index 5e364b67bf87..e45d0a30f3a6 100644 --- a/nixos/tests/victoriametrics.nix +++ b/nixos/tests/victoriametrics.nix @@ -1,33 +1,41 @@ -# This test runs influxdb and checks if influxdb is up and running +# This test runs victoriametrics and checks if victoriametrics is able to write points and run simple query -import ./make-test-python.nix ({ pkgs, ...} : { - name = "victoriametrics"; - meta = with pkgs.lib.maintainers; { - maintainers = [ yorickvp ]; - }; +import ./make-test-python.nix ( + { pkgs, ... }: + { + name = "victoriametrics"; + meta = with pkgs.lib.maintainers; { + maintainers = [ + yorickvp + ryan4yin + ]; + }; - nodes = { - one = { ... }: { - services.victoriametrics.enable = true; + nodes = { + one = + { ... }: + { + services.victoriametrics.enable = true; + }; }; - }; - testScript = '' - start_all() + testScript = '' + start_all() - one.wait_for_unit("victoriametrics.service") + one.wait_for_unit("victoriametrics.service") - # write some points and run simple query - out = one.succeed( - "curl -f -d 'measurement,tag1=value1,tag2=value2 field1=123,field2=1.23' -X POST 'http://localhost:8428/write'" - ) - cmd = ( - """curl -f -s -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'""" - ) - # data takes a while to appear - one.wait_until_succeeds(f"[[ $({cmd} | wc -l) -ne 0 ]]") - out = one.succeed(cmd) - assert '"values":[123]' in out - assert '"values":[1.23]' in out - ''; -}) + # write some points and run simple query + out = one.succeed( + "curl -f -d 'measurement,tag1=value1,tag2=value2 field1=123,field2=1.23' -X POST 'http://localhost:8428/write'" + ) + cmd = ( + """curl -f -s -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'""" + ) + # data takes a while to appear + one.wait_until_succeeds(f"[[ $({cmd} | wc -l) -ne 0 ]]") + out = one.succeed(cmd) + assert '"values":[123]' in out + assert '"values":[1.23]' in out + ''; + } +) diff --git a/nixos/tests/vscode-remote-ssh.nix b/nixos/tests/vscode-remote-ssh.nix index 278f2308cc16..305f931d4df1 100644 --- a/nixos/tests/vscode-remote-ssh.nix +++ b/nixos/tests/vscode-remote-ssh.nix @@ -28,7 +28,7 @@ in { networking.interfaces.eth1.ipv4.addresses = [ { address = serverAddress; prefixLength = 24; } ]; services.openssh.enable = true; users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; - virtualisation.additionalPaths = with pkgs; [ patchelf bintools stdenv.cc.cc.lib ]; + virtualisation.additionalPaths = with pkgs; [ patchelf bintools (lib.getLib stdenv.cc.cc) ]; }; client = { ... }: { imports = [ ./common/x11.nix ./common/user-account.nix ]; diff --git a/nixos/tests/wakapi.nix b/nixos/tests/wakapi.nix new file mode 100644 index 000000000000..2e611a986e30 --- /dev/null +++ b/nixos/tests/wakapi.nix @@ -0,0 +1,40 @@ +import ./make-test-python.nix ( + { lib, ... }: + { + name = "Wakapi"; + + nodes.machine = { + services.wakapi = { + enable = true; + settings = { + server.port = 3000; # upstream default, set explicitly in case upstream changes it + + db = { + dialect = "postgres"; # `createLocally` only supports postgres + host = "/run/postgresql"; + port = 5432; # service will fail if port is not set + name = "wakapi"; + user = "wakapi"; + }; + }; + + database.createLocally = true; + + # Created with `cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w ${1:-32} | head -n 1` + # Prefer passwordSaltFile in production. + passwordSalt = "NpqCY7eY7fMoIWYmPx5mAgr6YoSlXSuI"; + }; + }; + + # Test that the service is running and that it is reachable. + # This is not very comprehensive for a test, but it should + # catch very basic mistakes in the module. + testScript = '' + machine.wait_for_unit("wakapi.service") + machine.wait_for_open_port(3000) + machine.succeed("curl --fail http://localhost:3000") + ''; + + meta.maintainers = [ lib.maintainers.NotAShelf ]; + } +) diff --git a/nixos/tests/web-apps/healthchecks.nix b/nixos/tests/web-apps/healthchecks.nix index 41c40cd5dd8d..cb81c8b73651 100644 --- a/nixos/tests/web-apps/healthchecks.nix +++ b/nixos/tests/web-apps/healthchecks.nix @@ -24,7 +24,7 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: { with subtest("Home screen loads"): machine.succeed( - "curl -sSfL http://localhost:8000 | grep '<title>Sign In'" + "curl -sSfL http://localhost:8000 | grep '<title>Log In'" ) with subtest("Setting SITE_NAME via freeform option works"): diff --git a/nixos/tests/web-apps/immich.nix b/nixos/tests/web-apps/immich.nix new file mode 100644 index 000000000000..94a63fbfb9c1 --- /dev/null +++ b/nixos/tests/web-apps/immich.nix @@ -0,0 +1,60 @@ +import ../make-test-python.nix ( + { ... }: + { + name = "immich-nixos"; + + nodes.machine = + { pkgs, ... }: + { + # These tests need a little more juice + virtualisation = { + cores = 2; + memorySize = 2048; + diskSize = 4096; + }; + + environment.systemPackages = with pkgs; [ immich-cli ]; + + services.immich = { + enable = true; + environment.IMMICH_LOG_LEVEL = "verbose"; + }; + }; + + testScript = '' + import json + + machine.wait_for_unit("immich-server.service") + + machine.wait_for_open_port(2283) # Server + machine.wait_for_open_port(3003) # Machine learning + machine.succeed("curl --fail http://localhost:2283/") + + machine.succeed(""" + curl -f --json '{ "email": "test@example.com", "name": "Admin", "password": "admin" }' http://localhost:2283/api/auth/admin-sign-up + """) + res = machine.succeed(""" + curl -f --json '{ "email": "test@example.com", "password": "admin" }' http://localhost:2283/api/auth/login + """) + token = json.loads(res)['accessToken'] + + res = machine.succeed(""" + curl -f -H 'Cookie: immich_access_token=%s' --json '{ "name": "API Key", "permissions": ["all"] }' http://localhost:2283/api/api-keys + """ % token) + key = json.loads(res)['secret'] + + machine.succeed(f"immich login http://localhost:2283/api {key}") + res = machine.succeed("immich server-info") + print(res) + + machine.succeed(""" + curl -f -X PUT -H 'Cookie: immich_access_token=%s' --json '{ "command": "start" }' http://localhost:2283/api/jobs/backupDatabase + """ % token) + res = machine.succeed(""" + curl -f -H 'Cookie: immich_access_token=%s' http://localhost:2283/api/jobs + """ % token) + assert json.loads(res)["backupDatabase"]["jobCounts"]["active"] == 1 + machine.wait_until_succeeds("ls /var/lib/immich/backups/*.sql.gz") + ''; + } +) diff --git a/nixos/tests/web-apps/netbox-upgrade.nix b/nixos/tests/web-apps/netbox-upgrade.nix index 4c554e7ae613..b43313bc8a77 100644 --- a/nixos/tests/web-apps/netbox-upgrade.nix +++ b/nixos/tests/web-apps/netbox-upgrade.nix @@ -1,6 +1,6 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: let - oldNetbox = pkgs.netbox_3_6; - newNetbox = pkgs.netbox_3_7; + oldNetbox = pkgs.netbox_3_7; + newNetbox = pkgs.netbox_4_1; in { name = "netbox-upgrade"; @@ -58,8 +58,10 @@ in { return header.split()[1] def check_api_version(version): + # Returns 403 with NetBox >= 4.0, + # but we still get the API version in the headers headers = machine.succeed( - "curl -sSfL http://localhost/api/ --head -H 'Content-Type: application/json'" + "curl -sSL http://localhost/api/ --head -H 'Content-Type: application/json'" ) assert api_version(headers) == version diff --git a/nixos/tests/web-apps/netbox.nix b/nixos/tests/web-apps/netbox.nix index 233f16a8fe0d..2fdd70cfb1bf 100644 --- a/nixos/tests/web-apps/netbox.nix +++ b/nixos/tests/web-apps/netbox.nix @@ -132,7 +132,7 @@ in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: { testScript = let changePassword = pkgs.writeText "change-password.py" '' - from django.contrib.auth.models import User + from users.models import User u = User.objects.get(username='netbox') u.set_password('netbox') u.save() @@ -171,11 +171,6 @@ in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: { machine.succeed("curl -sSfL http://localhost/static/netbox.js") machine.succeed("curl -sSfL http://localhost/static/docs/") - with subtest("Can interact with API"): - json.loads( - machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'") - ) - def login(username: str, password: str): encoded_data = json.dumps({"username": username, "password": password}) uri = "/users/tokens/provision/" diff --git a/nixos/tests/web-apps/rss-bridge.nix b/nixos/tests/web-apps/rss-bridge.nix new file mode 100644 index 000000000000..16ab6cd8b584 --- /dev/null +++ b/nixos/tests/web-apps/rss-bridge.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: +{ + name = "rss-bridge"; + meta.maintainers = with pkgs.lib.maintainers; [ mynacol ]; + + nodes.machine = + { ... }: + { + services.rss-bridge = { + enable = true; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("nginx.service") + machine.wait_for_unit("phpfpm-rss-bridge.service") + + # check for successful feed download + machine.succeed("curl -sS -f 'http://localhost/?action=display&bridge=DemoBridge&context=testCheckbox&format=Atom'") + ''; +} diff --git a/nixos/tests/web-apps/snipe-it.nix b/nixos/tests/web-apps/snipe-it.nix index 123d7742056b..b04bd516831c 100644 --- a/nixos/tests/web-apps/snipe-it.nix +++ b/nixos/tests/web-apps/snipe-it.nix @@ -65,7 +65,9 @@ in { with subtest("Circumvent the pre-flight setup by just writing some settings into the database ourself"): snipeit.succeed( """ - mysql -D ${nodes.snipeit.services.snipe-it.database.name} -e "INSERT INTO settings (id, user_id, site_name) VALUES ('1', '1', '${siteName}');" + mysql -D ${nodes.snipeit.services.snipe-it.database.name} -e " + INSERT INTO settings (id, site_name, login_remote_user_custom_logout_url, login_remote_user_header_name) + VALUES ('1', '${siteName}', 'https://whatever.invalid', 'whatever');" """ ) diff --git a/nixos/tests/web-servers/unit-perl.nix b/nixos/tests/web-servers/unit-perl.nix new file mode 100644 index 000000000000..e632221747cf --- /dev/null +++ b/nixos/tests/web-servers/unit-perl.nix @@ -0,0 +1,46 @@ +import ../make-test-python.nix ( + { pkgs, ... }: + let + testdir = pkgs.writeTextDir "www/app.psgi" '' + my $app = sub { + return [ + "200", + [ "Content-Type" => "text/plain" ], + [ "Hello, Perl on Unit!" ], + ]; + }; + ''; + + in + { + name = "unit-perl-test"; + meta.maintainers = with pkgs.lib.maintainers; [ sgo ]; + + nodes.machine = + { + config, + lib, + pkgs, + ... + }: + { + services.unit = { + enable = true; + config = pkgs.lib.strings.toJSON { + listeners."*:8080".application = "perl"; + applications.perl = { + type = "perl"; + script = "${testdir}/www/app.psgi"; + }; + }; + }; + }; + testScript = '' + machine.wait_for_unit("unit.service") + machine.wait_for_open_port(8080) + + response = machine.succeed("curl -f -vvv -s http://127.0.0.1:8080/") + assert "Hello, Perl on Unit!" in response, "Hello world" + ''; + } +) diff --git a/nixos/tests/wine.nix b/nixos/tests/wine.nix index 7cbe7ac94f1e..7fc98c23c042 100644 --- a/nixos/tests/wine.nix +++ b/nixos/tests/wine.nix @@ -43,7 +43,7 @@ let in listToAttrs ( map (makeWineTest "winePackages" [ hello32 ]) variants - ++ optionals pkgs.stdenv.is64bit + ++ optionals pkgs.stdenv.hostPlatform.is64bit (map (makeWineTest "wineWowPackages" [ hello32 hello64 ]) # This wayland combination times out after spending many hours. # https://hydra.nixos.org/job/nixos/trunk-combined/nixos.tests.wine.wineWowPackages-wayland.x86_64-linux diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix index 7ca70864b837..0ec75b460676 100644 --- a/nixos/tests/wpa_supplicant.nix +++ b/nixos/tests/wpa_supplicant.nix @@ -8,6 +8,8 @@ let maintainers = [ oddlama rnhmjoj ]; }; + naughtyPassphrase = ''!,./;'[]\-=<>?:"{}|_+@$%^&*()`~ # ceci n'est pas un commentaire''; + runConnectionTest = name: extraConfig: runTest { name = "wpa_supplicant-${name}"; inherit meta; @@ -28,7 +30,7 @@ let ssid = "nixos-test-sae"; authentication = { mode = "wpa3-sae"; - saePasswords = [ { password = "reproducibility"; } ]; + saePasswords = [ { password = naughtyPassphrase; } ]; }; bssid = "02:00:00:00:00:00"; }; @@ -37,8 +39,8 @@ let authentication = { mode = "wpa3-sae-transition"; saeAddToMacAllow = true; - saePasswordsFile = pkgs.writeText "password" "reproducibility"; - wpaPasswordFile = pkgs.writeText "password" "reproducibility"; + saePasswordsFile = pkgs.writeText "password" naughtyPassphrase; + wpaPasswordFile = pkgs.writeText "password" naughtyPassphrase; }; bssid = "02:00:00:00:00:01"; }; @@ -46,7 +48,7 @@ let ssid = "nixos-test-wpa2"; authentication = { mode = "wpa2-sha256"; - wpaPassword = "reproducibility"; + wpaPassword = naughtyPassphrase; }; bssid = "02:00:00:00:00:02"; }; @@ -66,7 +68,7 @@ let # secrets secretsFile = pkgs.writeText "wpa-secrets" '' - psk_nixos_test=reproducibility + psk_nixos_test=${naughtyPassphrase} ''; } extraConfig diff --git a/nixos/tests/wstunnel.nix b/nixos/tests/wstunnel.nix index 7a0a8ce3496a..753f78061e7b 100644 --- a/nixos/tests/wstunnel.nix +++ b/nixos/tests/wstunnel.nix @@ -1,3 +1,5 @@ +{ lib, ... }: + let certs = import ./common/acme/server/snakeoil-certs.nix; domain = certs.domain; @@ -6,6 +8,8 @@ in { name = "wstunnel"; + meta.platforms = lib.platforms.linux; + nodes = { server = { virtualisation.vlans = [ 1 ]; diff --git a/nixos/tests/xmpp/ejabberd.nix b/nixos/tests/xmpp/ejabberd.nix index 1a807b27b6f6..a31a1b8eeab8 100644 --- a/nixos/tests/xmpp/ejabberd.nix +++ b/nixos/tests/xmpp/ejabberd.nix @@ -1,3 +1,10 @@ +let + cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } '' + openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=muc.example.com' -days 36500 + mkdir -p $out + cp key.pem cert.pem $out + ''; +in import ../make-test-python.nix ({ pkgs, ... }: { name = "ejabberd"; meta = with pkgs.lib.maintainers; { @@ -5,6 +12,7 @@ import ../make-test-python.nix ({ pkgs, ... }: { }; nodes = { client = { nodes, pkgs, ... }: { + security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ]; networking.extraHosts = '' ${nodes.server.config.networking.primaryIPAddress} example.com ''; @@ -14,6 +22,7 @@ import ../make-test-python.nix ({ pkgs, ... }: { ]; }; server = { config, pkgs, ... }: { + security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ]; networking.extraHosts = '' ${config.networking.primaryIPAddress} example.com ''; @@ -23,6 +32,7 @@ import ../make-test-python.nix ({ pkgs, ... }: { configFile = "/etc/ejabberd.yml"; }; + systemd.services.ejabberd.serviceConfig.TimeoutStartSec = "15min"; environment.etc."ejabberd.yml" = { user = "ejabberd"; mode = "0600"; @@ -40,6 +50,7 @@ import ../make-test-python.nix ({ pkgs, ... }: { max_stanza_size: 65536 shaper: c2s_shaper access: c2s + starttls: true - port: 5269 ip: "::" @@ -56,6 +67,10 @@ import ../make-test-python.nix ({ pkgs, ... }: { request_handlers: "/upload": mod_http_upload + certfiles: + - ${cert pkgs}/key.pem + - ${cert pkgs}/cert.pem + ## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text ## password storage (see auth_password_format option). disable_sasl_mechanisms: "digest-md5" diff --git a/nixos/tests/zammad.nix b/nixos/tests/zammad.nix index faae1949e37b..aaf32c6f13fe 100644 --- a/nixos/tests/zammad.nix +++ b/nixos/tests/zammad.nix @@ -20,8 +20,8 @@ import ./make-test-python.nix ( let cfg = config.services.zammad; in { serviceConfig = { - Type = "simple"; - Restart = "always"; + Type = "oneshot"; + Restart = "on-failure"; User = "zammad"; Group = "zammad"; diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix index 877749a8048b..13db2c8e06be 100644 --- a/nixos/tests/zfs.nix +++ b/nixos/tests/zfs.nix @@ -204,12 +204,12 @@ in { unstable = makeZfsTest rec { zfsPackage = pkgs.zfs_unstable; - kernelPackages = zfsPackage.latestCompatibleLinuxPackages; + kernelPackages = pkgs.linuxPackages; }; unstableWithSystemdStage1 = makeZfsTest rec { zfsPackage = pkgs.zfs_unstable; - kernelPackages = zfsPackage.latestCompatibleLinuxPackages; + kernelPackages = pkgs.linuxPackages; enableSystemdStage1 = true; }; |