Merge branch 'master' into current

This commit is contained in:
2024-07-17 17:49:14 +02:00
993 changed files with 14413 additions and 44334 deletions

View File

@@ -10,6 +10,9 @@
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.
- `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.
## New Services {#sec-release-24.11-new-services}
- [Open-WebUI](https://github.com/open-webui/open-webui), a user-friendly WebUI
@@ -24,6 +27,8 @@
- [Eintopf](https://eintopf.info), community event and calendar web application. Available as [services.eintopf](options.html#opt-services.eintopf).
- [Radicle](https://radicle.xyz), an open source, peer-to-peer code collaboration stack built on Git. Available as [services.radicle](#opt-services.radicle.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).
- [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).
@@ -34,6 +39,8 @@
- [realm](https://github.com/zhboner/realm), a simple, high performance relay server written in rust. Available as [services.realm.enable](#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).
- [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld).
- [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).
@@ -85,6 +92,10 @@
Processes also now run as a dynamically allocated user by default instead of
root.
- 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`)
- `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`.

View File

@@ -1,8 +1,8 @@
{ config, lib, pkgs, ... }:
with lib;
let
inherit (lib) mkOption optionalString types versionAtLeast;
inherit (lib.options) literalExpression;
cfg = config.amazonImage;
amiBootMode = if config.ec2.efi then "uefi" else "legacy-bios";
@@ -15,7 +15,7 @@ in {
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes
config.boot.kernelParams =
let timeout =
if pkgs.lib.versionAtLeast config.boot.kernelPackages.kernel.version "4.15"
if versionAtLeast config.boot.kernelPackages.kernel.version "4.15"
then "4294967295"
else "255";
in [ "nvme_core.io_timeout=${timeout}" ];
@@ -156,5 +156,5 @@ in {
};
in if config.ec2.zfs.enable then zfsBuilder else extBuilder;
meta.maintainers = with maintainers; [ arianvp ];
meta.maintainers = with lib.maintainers; [ arianvp ];
}

View File

@@ -0,0 +1,18 @@
{ config, lib, pkgs, ... }:
let
cfg = config.hardware.decklink;
kernelPackages = config.boot.kernelPackages;
in
{
options.hardware.decklink.enable = lib.mkEnableOption "hardware support for the Blackmagic Design Decklink audio/video interfaces";
config = lib.mkIf cfg.enable {
# snd_blackmagic-io can cause issues with pipewire,
# so we do not enable it by default
boot.kernelModules = [ "blackmagic" "blackmagic-io" ];
boot.extraModulePackages = [ kernelPackages.decklink ];
systemd.packages = [ pkgs.blackmagic-desktop-video ];
systemd.services.DesktopVideoHelper.wantedBy = [ "multi-user.target" ];
};
}

View File

@@ -59,6 +59,7 @@
./hardware/cpu/intel-microcode.nix
./hardware/cpu/intel-sgx.nix
./hardware/cpu/x86-msr.nix
./hardware/decklink.nix
./hardware/device-tree.nix
./hardware/digitalbitbox.nix
./hardware/flipperzero.nix
@@ -740,6 +741,7 @@
./services/misc/gitweb.nix
./services/misc/gogs.nix
./services/misc/gollum.nix
./services/misc/gotenberg.nix
./services/misc/gpsd.nix
./services/misc/graphical-desktop.nix
./services/misc/greenclip.nix
@@ -801,6 +803,7 @@
./services/misc/pufferpanel.nix
./services/misc/pykms.nix
./services/misc/radarr.nix
./services/misc/radicle.nix
./services/misc/readarr.nix
./services/misc/redmine.nix
./services/misc/renovate.nix
@@ -841,7 +844,6 @@
./services/misc/wastebin.nix
./services/misc/weechat.nix
./services/misc/workout-tracker.nix
./services/misc/xmr-stak.nix
./services/misc/xmrig.nix
./services/misc/zoneminder.nix
./services/misc/zookeeper.nix

View File

@@ -68,7 +68,6 @@ in
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ProtectUser = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;

View File

@@ -106,6 +106,7 @@ in
as the underlying package isn't being maintained. Working alternatives are
libinput and synaptics.
'')
(mkRemovedOptionModule [ "services" "xmr-stak" ] "The corresponding package was removed from nixpkgs.")
(mkRemovedOptionModule [ "virtualisation" "rkt" ] "The rkt module has been removed, it was archived by upstream")
(mkRemovedOptionModule [ "services" "racoon" ] ''
The racoon module has been removed, because the software project was abandoned upstream.

View File

@@ -1,15 +1,13 @@
# ALSA sound support.
{ config, lib, pkgs, ... }:
with lib;
{
imports = [
(mkRemovedOptionModule [ "sound" ] "The option was heavily overloaded and can be removed from most configurations.")
(lib.mkRemovedOptionModule [ "sound" ] "The option was heavily overloaded and can be removed from most configurations.")
];
options.hardware.alsa.enablePersistence = mkOption {
type = types.bool;
options.hardware.alsa.enablePersistence = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable ALSA sound card state saving on shutdown.
@@ -17,9 +15,9 @@ with lib;
'';
};
config = mkIf config.hardware.alsa.enablePersistence {
config = lib.mkIf config.hardware.alsa.enablePersistence {
# ALSA provides a udev rule for restoring volume settings.
services.udev.packages = [ alsa-utils ];
services.udev.packages = [ pkgs.alsa-utils ];
systemd.services.alsa-store = {
description = "Store Sound Card State";
@@ -32,8 +30,8 @@ with lib;
Type = "oneshot";
RemainAfterExit = true;
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
ExecStart = "${alsa-utils}/sbin/alsactl restore --ignore";
ExecStop = "${alsa-utils}/sbin/alsactl store --ignore";
ExecStart = "${pkgs.alsa-utils}/sbin/alsactl restore --ignore";
ExecStop = "${pkgs.alsa-utils}/sbin/alsactl store --ignore";
};
};
};

View File

@@ -192,7 +192,7 @@ in {
};
network = mkOption {
type = types.enum [ "mainnet" "prater" "goerli" "gnosis" "kiln" "ropsten" "sepolia" ];
type = types.enum [ "mainnet" "gnosis" "chiado" "sepolia" "holesky" ];
default = "mainnet";
description = ''
The network to connect to. Mainnet is the default ethereum network.

View File

@@ -206,9 +206,12 @@ in
extraFlags = mkOption {
description = "Extra flags to pass to the k3s command.";
type = types.str;
default = "";
example = "--no-deploy traefik --cluster-cidr 10.24.0.0/16";
type = with types; either str (listOf str);
default = [ ];
example = [
"--no-deploy traefik"
"--cluster-cidr 10.24.0.0/16"
];
};
disableAgent = mkOption {
@@ -427,7 +430,7 @@ in
++ (optional (cfg.token != "") "--token ${cfg.token}")
++ (optional (cfg.tokenFile != null) "--token-file ${cfg.tokenFile}")
++ (optional (cfg.configPath != null) "--config ${cfg.configPath}")
++ [ cfg.extraFlags ]
++ (lib.flatten cfg.extraFlags)
);
};
};

View File

@@ -15,6 +15,7 @@ let
message_journal_dir = ${cfg.messageJournalDir}
mongodb_uri = ${cfg.mongodbUri}
plugin_dir = /var/lib/graylog/plugins
data_dir = ${cfg.dataDir}
${cfg.extraConfig}
'';
@@ -93,6 +94,12 @@ in
description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
};
dataDir = mkOption {
type = types.str;
default = "/var/lib/graylog/data";
description = "Directory used to store Graylog server state.";
};
messageJournalDir = mkOption {
type = types.str;
default = "/var/lib/graylog/data/journal";

View File

@@ -45,7 +45,7 @@ in
package = mkOption {
type = types.package;
default = pkgs.python3.pkgs.etebase-server;
default = pkgs.etebase-server;
defaultText = literalExpression "pkgs.python3.pkgs.etebase-server";
description = "etebase-server package to use.";
};

View File

@@ -0,0 +1,258 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.gotenberg;
args =
[
"--api-port=${toString cfg.port}"
"--api-timeout=${cfg.timeout}"
"--api-root-path=${cfg.rootPath}"
"--log-level=${cfg.logLevel}"
"--chromium-max-queue-size=${toString cfg.chromium.maxQueueSize}"
"--libreoffice-restart-after=${toString cfg.libreoffice.restartAfter}"
"--libreoffice-max-queue-size=${toString cfg.libreoffice.maxQueueSize}"
"--pdfengines-engines=${lib.concatStringsSep "," cfg.pdfEngines}"
]
++ optional cfg.enableBasicAuth "--api-enable-basic-auth"
++ optional cfg.chromium.autoStart "--chromium-auto-start"
++ optional cfg.chromium.disableJavascript "--chromium-disable-javascript"
++ optional cfg.chromium.disableRoutes "--chromium-disable-routes"
++ optional cfg.libreoffice.autoStart "--libreoffice-auto-start"
++ optional cfg.libreoffice.disableRoutes "--libreoffice-disable-routes";
inherit (lib)
mkEnableOption
mkPackageOption
mkOption
types
mkIf
optional
optionalAttrs
;
in
{
options = {
services.gotenberg = {
enable = mkEnableOption "Gotenberg, a stateless API for PDF files";
# Users can override only gotenberg, libreoffice and chromium if they want to (eg. ungoogled-chromium, different LO version, etc)
# Don't allow setting the qpdf, pdftk, or unoconv paths, as those are very stable
# and there's only one version of each.
package = mkPackageOption pkgs "gotenberg" { };
port = mkOption {
type = types.port;
default = 3000;
description = "Port on which the API should listen.";
};
timeout = mkOption {
type = types.nullOr types.str;
default = "30s";
description = "Timeout for API requests.";
};
rootPath = mkOption {
type = types.str;
default = "/";
description = "Root path for the Gotenberg API.";
};
enableBasicAuth = mkOption {
type = types.bool;
default = false;
description = ''
HTTP Basic Authentication.
If you set this, be sure to set `GOTENBERG_API_BASIC_AUTH_USERNAME`and `GOTENBERG_API_BASIC_AUTH_PASSWORD`
in your `services.gotenberg.environmentFile` file.
'';
};
extraFontPackages = mkOption {
type = types.listOf types.package;
default = [ ];
description = "Extra fonts to make available.";
};
chromium = {
package = mkPackageOption pkgs "chromium" { };
maxQueueSize = mkOption {
type = types.int;
default = 0;
description = "Maximum queue size for chromium-based conversions. Setting to 0 disables the limit.";
};
autoStart = mkOption {
type = types.bool;
default = false;
description = "Automatically start chromium when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it.";
};
disableJavascript = mkOption {
type = types.bool;
default = false;
description = "Disable Javascript execution.";
};
disableRoutes = mkOption {
type = types.bool;
default = false;
description = "Disable all routes allowing Chromium-based conversion.";
};
};
libreoffice = {
package = mkPackageOption pkgs "libreoffice" { };
restartAfter = mkOption {
type = types.int;
default = 10;
description = "Restart LibreOffice after this many conversions. Setting to 0 disables this feature.";
};
maxQueueSize = mkOption {
type = types.int;
default = 0;
description = "Maximum queue size for LibreOffice-based conversions. Setting to 0 disables the limit.";
};
autoStart = mkOption {
type = types.bool;
default = false;
description = "Automatically start LibreOffice when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it.";
};
disableRoutes = mkOption {
type = types.bool;
default = false;
description = "Disable all routes allowing LibreOffice-based conversion.";
};
};
pdfEngines = mkOption {
type = types.listOf (
types.enum [
"pdftk"
"qpdf"
"libreoffice-pdfengine"
"exiftool"
"pdfcpu"
]
);
default = [
"pdftk"
"qpdf"
"libreoffice-pdfengine"
"exiftool"
"pdfcpu"
];
description = ''
PDF engines to enable. Each one can be used to perform a specific task.
See [the documentation](https://gotenberg.dev/docs/configuration#pdf-engines) for more details.
Defaults to all possible PDF engines.
'';
};
logLevel = mkOption {
type = types.enum [
"error"
"warn"
"info"
"debug"
];
default = "info";
description = "The logging level for Gotenberg.";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Environment file to load extra environment variables from.";
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Any extra command-line flags to pass to the Gotenberg service.";
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.enableBasicAuth -> cfg.environmentFile != null;
message = ''
When enabling HTTP Basic Authentication with `services.gotenberg.enableBasicAuth`,
you must provide an environment file via `services.gotenberg.environmentFile` with the appropriate environment variables set in it.
See `services.gotenberg.enableBasicAuth` for the names of those variables.
'';
}
];
systemd.services.gotenberg = {
description = "Gotenberg API server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ cfg.package ];
environment = {
LIBREOFFICE_BIN_PATH = "${cfg.libreoffice.package}/lib/libreoffice/program/soffice.bin";
CHROMIUM_BIN_PATH = lib.getExe cfg.chromium.package;
FONTCONFIG_FILE = pkgs.makeFontsConf {
fontDirectories = [ pkgs.liberation_ttf_v2 ] ++ cfg.extraFontPackages;
};
};
serviceConfig = {
Type = "simple";
DynamicUser = true;
ExecStart = "${lib.getExe cfg.package} ${lib.escapeShellArgs args}";
# Hardening options
PrivateDevices = true;
PrivateIPC = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
SystemCallFilter = [
"@system-service"
"~@resources"
"~@privileged"
];
SystemCallArchitectures = "native";
UMask = 77;
} // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
};
};
meta.maintainers = with lib.maintainers; [ pyrox0 ];
}

View File

@@ -0,0 +1,347 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.radicle;
json = pkgs.formats.json { };
env = rec {
# rad fails if it cannot stat $HOME/.gitconfig
HOME = "/var/lib/radicle";
RAD_HOME = HOME;
};
# Convenient wrapper to run `rad` in the namespaces of `radicle-node.service`
rad-system = pkgs.writeShellScriptBin "rad-system" ''
set -o allexport
${toShellVars env}
# Note that --env is not used to preserve host's envvars like $TERM
exec ${getExe' pkgs.util-linux "nsenter"} -a \
-t "$(${getExe' config.systemd.package "systemctl"} show -P MainPID radicle-node.service)" \
-S "$(${getExe' config.systemd.package "systemctl"} show -P UID radicle-node.service)" \
-G "$(${getExe' config.systemd.package "systemctl"} show -P GID radicle-node.service)" \
${getExe' cfg.package "rad"} "$@"
'';
commonServiceConfig = serviceName: {
environment = env // {
RUST_LOG = mkDefault "info";
};
path = [
pkgs.gitMinimal
];
documentation = [
"https://docs.radicle.xyz/guides/seeder"
];
after = [
"network.target"
"network-online.target"
];
requires = [
"network-online.target"
];
wantedBy = [ "multi-user.target" ];
serviceConfig = mkMerge [
{
BindReadOnlyPaths = [
"${cfg.configFile}:${env.RAD_HOME}/config.json"
"${if isPath cfg.publicKeyFile then cfg.publicKeyFile else pkgs.writeText "radicle.pub" cfg.publicKeyFile}:${env.RAD_HOME}/keys/radicle.pub"
];
KillMode = "process";
StateDirectory = [ "radicle" ];
User = config.users.users.radicle.name;
Group = config.users.groups.radicle.name;
WorkingDirectory = env.HOME;
}
# The following options are only for optimizing:
# systemd-analyze security ${serviceName}
{
BindReadOnlyPaths = [
"-/etc/resolv.conf"
"/etc/ssl/certs/ca-certificates.crt"
"/run/systemd"
];
AmbientCapabilities = "";
CapabilityBoundingSet = "";
DeviceAllow = ""; # ProtectClock= adds DeviceAllow=char-rtc r
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RuntimeDirectoryMode = "700";
SocketBindDeny = [ "any" ];
StateDirectoryMode = "0750";
SystemCallFilter = [
"@system-service"
"~@aio"
"~@chown"
"~@keyring"
"~@memlock"
"~@privileged"
"~@resources"
"~@setuid"
"~@timer"
];
SystemCallArchitectures = "native";
# This is for BindPaths= and BindReadOnlyPaths=
# to allow traversal of directories they create inside RootDirectory=
UMask = "0066";
}
];
confinement = {
enable = true;
mode = "full-apivfs";
packages = [
pkgs.gitMinimal
cfg.package
pkgs.iana-etc
(getLib pkgs.nss)
pkgs.tzdata
];
};
};
in
{
options = {
services.radicle = {
enable = mkEnableOption "Radicle Seed Node";
package = mkPackageOption pkgs "radicle-node" { };
privateKeyFile = mkOption {
type = with types; either path str;
description = ''
SSH private key generated by `rad auth`.
If it contains a colon (`:`) the string before the colon
is taken as the credential name
and the string after as a path encrypted with `systemd-creds`.
'';
};
publicKeyFile = mkOption {
type = with types; either path str;
description = ''
SSH public key generated by `rad auth`.
'';
};
node = {
listenAddress = mkOption {
type = types.str;
default = "0.0.0.0";
example = "127.0.0.1";
description = "The IP address on which `radicle-node` listens.";
};
listenPort = mkOption {
type = types.port;
default = 8776;
description = "The port on which `radicle-node` listens.";
};
openFirewall = mkEnableOption "opening the firewall for `radicle-node`";
extraArgs = mkOption {
type = with types; listOf str;
default = [ ];
description = "Extra arguments for `radicle-node`";
};
};
configFile = mkOption {
type = types.package;
internal = true;
default = (json.generate "config.json" cfg.settings).overrideAttrs (previousAttrs: {
preferLocalBuild = true;
# None of the usual phases are run here because runCommandWith uses buildCommand,
# so just append to buildCommand what would usually be a checkPhase.
buildCommand = previousAttrs.buildCommand + optionalString cfg.checkConfig ''
ln -s $out config.json
install -D -m 644 /dev/stdin keys/radicle.pub <<<"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBgFMhajUng+Rjj/sCFXI9PzG8BQjru2n7JgUVF1Kbv5 snakeoil"
export RAD_HOME=$PWD
${getExe' pkgs.buildPackages.radicle-node "rad"} config >/dev/null || {
cat -n config.json
echo "Invalid config.json according to rad."
echo "Please double-check your services.radicle.settings (producing the config.json above),"
echo "some settings may be missing or have the wrong type."
exit 1
} >&2
'';
});
};
checkConfig = mkEnableOption "checking the {file}`config.json` file resulting from {option}`services.radicle.settings`" // { default = true; };
settings = mkOption {
description = ''
See https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/radicle/src/node/config.rs#L275
'';
default = { };
type = types.submodule {
freeformType = json.type;
};
};
httpd = {
enable = mkEnableOption "Radicle HTTP gateway to radicle-node";
package = mkPackageOption pkgs "radicle-httpd" { };
listenAddress = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The IP address on which `radicle-httpd` listens.";
};
listenPort = mkOption {
type = types.port;
default = 8080;
description = "The port on which `radicle-httpd` listens.";
};
nginx = mkOption {
# Type of a single virtual host, or null.
type = types.nullOr (types.submodule (
recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {
options.serverName = {
default = "radicle-${config.networking.hostName}.${config.networking.domain}";
defaultText = "radicle-\${config.networking.hostName}.\${config.networking.domain}";
};
}
));
default = null;
example = literalExpression ''
{
serverAliases = [
"seed.''${config.networking.domain}"
];
enableACME = false;
useACMEHost = config.networking.domain;
}
'';
description = ''
With this option, you can customize an nginx virtual host which already has sensible defaults for `radicle-httpd`.
Set to `{}` if you do not need any customization to the virtual host.
If enabled, then by default, the {option}`serverName` is
`radicle-''${config.networking.hostName}.''${config.networking.domain}`,
TLS is active, and certificates are acquired via ACME.
If this is set to null (the default), no nginx virtual host will be configured.
'';
};
extraArgs = mkOption {
type = with types; listOf str;
default = [ ];
description = "Extra arguments for `radicle-httpd`";
};
};
};
};
config = mkIf cfg.enable (mkMerge [
{
systemd.services.radicle-node = mkMerge [
(commonServiceConfig "radicle-node")
{
description = "Radicle Node";
documentation = [ "man:radicle-node(1)" ];
serviceConfig = {
ExecStart = "${getExe' cfg.package "radicle-node"} --force --listen ${cfg.node.listenAddress}:${toString cfg.node.listenPort} ${escapeShellArgs cfg.node.extraArgs}";
Restart = mkDefault "on-failure";
RestartSec = "30";
SocketBindAllow = [ "tcp:${toString cfg.node.listenPort}" ];
SystemCallFilter = mkAfter [
# Needed by git upload-pack which calls alarm() and setitimer() when providing a rad clone
"@timer"
];
};
confinement.packages = [
cfg.package
];
}
# Give only access to the private key to radicle-node.
{
serviceConfig =
let keyCred = builtins.split ":" "${cfg.privateKeyFile}"; in
if length keyCred > 1
then {
LoadCredentialEncrypted = [ cfg.privateKeyFile ];
# Note that neither %d nor ${CREDENTIALS_DIRECTORY} works in BindReadOnlyPaths=
BindReadOnlyPaths = [ "/run/credentials/radicle-node.service/${head keyCred}:${env.RAD_HOME}/keys/radicle" ];
}
else {
LoadCredential = [ "radicle:${cfg.privateKeyFile}" ];
BindReadOnlyPaths = [ "/run/credentials/radicle-node.service/radicle:${env.RAD_HOME}/keys/radicle" ];
};
}
];
environment.systemPackages = [
rad-system
];
networking.firewall = mkIf cfg.node.openFirewall {
allowedTCPPorts = [ cfg.node.listenPort ];
};
users = {
users.radicle = {
description = "Radicle";
group = "radicle";
home = env.HOME;
isSystemUser = true;
};
groups.radicle = {
};
};
}
(mkIf cfg.httpd.enable (mkMerge [
{
systemd.services.radicle-httpd = mkMerge [
(commonServiceConfig "radicle-httpd")
{
description = "Radicle HTTP gateway to radicle-node";
documentation = [ "man:radicle-httpd(1)" ];
serviceConfig = {
ExecStart = "${getExe' cfg.httpd.package "radicle-httpd"} --listen ${cfg.httpd.listenAddress}:${toString cfg.httpd.listenPort} ${escapeShellArgs cfg.httpd.extraArgs}";
Restart = mkDefault "on-failure";
RestartSec = "10";
SocketBindAllow = [ "tcp:${toString cfg.httpd.listenPort}" ];
SystemCallFilter = mkAfter [
# Needed by git upload-pack which calls alarm() and setitimer() when providing a git clone
"@timer"
];
};
confinement.packages = [
cfg.httpd.package
];
}
];
}
(mkIf (cfg.httpd.nginx != null) {
services.nginx.virtualHosts.${cfg.httpd.nginx.serverName} = lib.mkMerge [
cfg.httpd.nginx
{
forceSSL = mkDefault true;
enableACME = mkDefault true;
locations."/" = {
proxyPass = "http://${cfg.httpd.listenAddress}:${toString cfg.httpd.listenPort}";
recommendedProxySettings = true;
};
}
];
services.radicle.settings = {
node.alias = mkDefault cfg.httpd.nginx.serverName;
node.externalAddresses = mkDefault [
"${cfg.httpd.nginx.serverName}:${toString cfg.node.listenPort}"
];
};
})
]))
]);
meta.maintainers = with lib.maintainers; [
julm
lorenzleutgeb
];
}

View File

@@ -145,7 +145,7 @@ in {
# https://github.com/krb5/krb5/blob/krb5-1.19.3-final/src/include/kcm.h#L43
listenStreams = [ "/var/run/.heim_org.h5l.kcm-socket" ];
};
krb5.libdefaults.default_ccache_name = "KCM:";
security.krb5.settings.libdefaults.default_ccache_name = "KCM:";
})
(mkIf cfg.sshAuthorizedKeysIntegration {

View File

@@ -1,89 +0,0 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.services.xmr-stak;
pkg = pkgs.xmr-stak.override {
inherit (cfg) openclSupport;
};
in
{
options = {
services.xmr-stak = {
enable = mkEnableOption "xmr-stak miner";
openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)";
extraArgs = mkOption {
type = types.listOf types.str;
default = [];
example = [ "--noCPU" "--currency monero" ];
description = "List of parameters to pass to xmr-stak.";
};
configFiles = mkOption {
type = types.attrsOf types.str;
default = {};
example = literalExpression ''
{
"config.txt" = '''
"verbose_level" : 4,
"h_print_time" : 60,
"tls_secure_algo" : true,
''';
"pools.txt" = '''
"currency" : "monero7",
"pool_list" :
[ { "pool_address" : "pool.supportxmr.com:443",
"wallet_address" : "my-wallet-address",
"rig_id" : "",
"pool_password" : "nixos",
"use_nicehash" : false,
"use_tls" : true,
"tls_fingerprint" : "",
"pool_weight" : 23
},
],
''';
}
'';
description = ''
Content of config files like config.txt, pools.txt or cpu.txt.
'';
};
};
};
config = mkIf cfg.enable {
systemd.services.xmr-stak = {
wantedBy = [ "multi-user.target" ];
bindsTo = [ "network-online.target" ];
after = [ "network-online.target" ];
preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
''));
serviceConfig = let rootRequired = cfg.openclSupport; in {
ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
# xmr-stak generates cpu and/or gpu configuration files
WorkingDirectory = "/tmp";
PrivateTmp = true;
DynamicUser = !rootRequired;
LimitMEMLOCK = toString (1024*1024);
};
};
};
imports = [
(mkRemovedOptionModule ["services" "xmr-stak" "configText"] ''
This option was removed in favour of `services.xmr-stak.configFiles`
because the new config file `pools.txt` was introduced. You are
now able to define all other config files like cpu.txt or amd.txt.
'')
];
}

View File

@@ -7,7 +7,27 @@ in {
port = 9117;
extraOpts = {
settings = mkOption {
type = types.attrs;
type = types.submodule {
options = {
consul = mkOption {
default = null;
type = types.nullOr (types.attrsOf types.anything);
description = ''
Consul integration options. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
This is disabled by default.
'';
};
namespaces = mkOption {
default = [];
type = types.listOf (types.attrsOf types.anything);
description = ''
Namespaces to collect the metrics for. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
'';
};
};
};
default = {};
description = ''
All settings of nginxlog expressed as an Nix attrset.

View File

@@ -8,7 +8,6 @@ let
cfg = config.services.authelia;
format = pkgs.formats.yaml { };
configFile = format.generate "config.yml" cfg.settings;
autheliaOpts = with lib; { name, ... }: {
options = {
@@ -156,18 +155,12 @@ let
};
server = {
host = mkOption {
address = mkOption {
type = types.str;
default = "localhost";
example = "0.0.0.0";
default = "tcp://:9091/";
example = "unix:///var/run/authelia.sock?path=authelia&umask=0117";
description = "The address to listen on.";
};
port = mkOption {
type = types.port;
default = 9091;
description = "The port to listen on.";
};
};
log = {
@@ -233,6 +226,23 @@ let
};
};
};
writeOidcJwksConfigFile = oidcIssuerPrivateKeyFile: pkgs.writeText "oidc-jwks.yaml" ''
identity_providers:
oidc:
jwks:
- key: {{ secret "${oidcIssuerPrivateKeyFile}" | mindent 10 "|" | msquote }}
'';
# Remove an attribute in a nested set
# https://discourse.nixos.org/t/modify-an-attrset-in-nix/29919/5
removeAttrByPath = set: pathList:
lib.updateManyAttrsByPath [{
path = lib.init pathList;
update = old:
lib.filterAttrs (n: v: n != (lib.last pathList)) old;
}]
set;
in
{
options.services.authelia.instances = with lib; mkOption {
@@ -281,9 +291,19 @@ in
let
mkInstanceServiceConfig = instance:
let
cleanedSettings =
if (instance.settings.server?host || instance.settings.server?port || instance.settings.server?path) then
# Old settings are used: display a warning and remove the default value of server.address
# as authelia does not allow both old and new settings to be set
lib.warn "Please replace services.authelia.instances.${instance.name}.settings.{host,port,path} with services.authelia.instances.${instance.name}.settings.address, before release 5.0.0"
(removeAttrByPath instance.settings [ "server" "address" ])
else
instance.settings;
execCommand = "${instance.package}/bin/authelia";
configFile = format.generate "config.yml" instance.settings;
configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}";
configFile = format.generate "config.yml" cleanedSettings;
oidcJwksConfigFile = lib.optional (instance.secrets.oidcIssuerPrivateKeyFile != null) (writeOidcJwksConfigFile instance.secrets.oidcIssuerPrivateKeyFile);
configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles oidcJwksConfigFile])}";
in
{
description = "Authelia authentication and authorization server";
@@ -291,10 +311,10 @@ in
after = [ "network.target" ];
environment =
(lib.filterAttrs (_: v: v != null) {
AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
X_AUTHELIA_CONFIG_FILTERS = lib.mkIf (oidcJwksConfigFile != [ ]) "template";
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile;
AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile;
AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile;
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile;
})
// instance.environmentVariables;

View File

@@ -8,7 +8,7 @@ let
nixos-background-light = pkgs.nixos-artwork.wallpapers.nineish;
nixos-background-dark = pkgs.nixos-artwork.wallpapers.nineish-dark-gray;
nixos-gsettings-overrides = pkgs.budgie.budgie-gsettings-overrides.override {
nixos-gsettings-overrides = pkgs.budgie-gsettings-overrides.override {
inherit (cfg) extraGSettingsOverrides extraGSettingsOverridePackages;
inherit nixos-background-dark nixos-background-light;
};
@@ -40,7 +40,7 @@ let
destination = "/share/gnome-background-properties/nixos.xml";
};
budgie-control-center = pkgs.budgie.budgie-control-center.override {
budgie-control-center' = pkgs.budgie-control-center.override {
enableSshSocket = config.services.openssh.startWhenNeeded;
};
@@ -80,7 +80,7 @@ in {
description = "Extra plugins for the Budgie desktop";
type = types.listOf types.package;
default = [];
example = literalExpression "[ pkgs.budgiePlugins.budgie-analogue-clock-applet ]";
example = literalExpression "[ pkgs.budgie-analogue-clock-applet ]";
};
};
@@ -94,7 +94,7 @@ in {
config = mkIf cfg.enable {
services.displayManager.sessionPackages = with pkgs; [
budgie.budgie-desktop
budgie-desktop
];
services.xserver.displayManager.lightdm.greeters.slick = {
@@ -104,7 +104,7 @@ in {
cursorTheme = mkDefault { name = "Qogir"; package = pkgs.qogir-icon-theme; };
};
services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie.budgie-desktop-view ];
services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie-desktop-view ];
environment.extraInit = ''
${concatMapStrings (p: ''
@@ -121,12 +121,12 @@ in {
environment.systemPackages = with pkgs;
[
# Budgie Desktop.
budgie.budgie-backgrounds
budgie-control-center
(budgie.budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
budgie.budgie-desktop-view
budgie.budgie-screensaver
budgie.budgie-session
budgie-backgrounds
budgie-control-center'
(budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
budgie-desktop-view
budgie-screensaver
budgie-session
# Required by Budgie Menu.
gnome-menus
@@ -210,7 +210,7 @@ in {
xdg.portal.extraPortals = with pkgs; [
xdg-desktop-portal-gtk # provides a XDG Portals implementation.
];
xdg.portal.configPackages = mkDefault [ pkgs.budgie.budgie-desktop ];
xdg.portal.configPackages = mkDefault [ pkgs.budgie-desktop ];
services.geoclue2.enable = mkDefault true; # for BCC's Privacy > Location Services panel.
services.upower.enable = config.powerManagement.enable; # for Budgie's Status Indicator and BCC's Power panel.
@@ -243,12 +243,12 @@ in {
# Register packages for DBus.
services.dbus.packages = [
budgie-control-center
budgie-control-center'
];
# Register packages for udev.
services.udev.packages = with pkgs; [
budgie.magpie
magpie
];
# Shell integration for MATE Terminal.

View File

@@ -22,6 +22,8 @@ let
'';
systemdBootBuilder = pkgs.substituteAll rec {
name = "systemd-boot";
src = checkedSource;
isExecutable = true;

View File

@@ -6,9 +6,8 @@
{ config, lib, pkgs, ... }:
with lib;
let
inherit (lib) mkDefault mkIf;
cfg = config.ec2;
in
@@ -107,5 +106,5 @@ in
# (e.g. it depends on GTK).
services.udisks2.enable = false;
};
meta.maintainers = with maintainers; [ arianvp ];
meta.maintainers = with lib.maintainers; [ arianvp ];
}

View File

@@ -44,12 +44,14 @@ let
qemu-utils
qemu_kvm
rsync
skopeo
squashfs-tools-ng
squashfsTools
sshfs
swtpm
systemd
thin-provisioning-tools
umoci
util-linux
virtiofsd
xdelta
@@ -109,7 +111,7 @@ let
environment = lib.mkMerge [
{
INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
INCUS_OVMF_PATH = ovmf;
INCUS_EDK2_PATH = ovmf;
INCUS_USBIDS_PATH = "${pkgs.hwdata}/share/hwdata/usb.ids";
PATH = lib.mkForce serverBinPath;
}

View File

@@ -4,6 +4,11 @@ with lib;
{
options.proxmoxLXC = {
enable = mkOption {
default = true;
type = types.bool;
description = lib.mdDoc "Whether to enable the Proxmox VE LXC module.";
};
privileged = mkOption {
type = types.bool;
default = false;
@@ -35,7 +40,7 @@ with lib;
let
cfg = config.proxmoxLXC;
in
{
mkIf cfg.enable {
system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
storeContents = [{
object = config.system.build.toplevel;

View File

@@ -240,6 +240,7 @@ in {
custom-ca = handleTest ./custom-ca.nix {};
croc = handleTest ./croc.nix {};
darling = handleTest ./darling.nix {};
darling-dmg = runTest ./darling-dmg.nix;
dae = handleTest ./dae.nix {};
davis = handleTest ./davis.nix {};
db-rest = handleTest ./db-rest.nix {};
@@ -380,6 +381,7 @@ in {
gonic = handleTest ./gonic.nix {};
google-oslogin = handleTest ./google-oslogin {};
goss = handleTest ./goss.nix {};
gotenberg = handleTest ./gotenberg.nix {};
gotify-server = handleTest ./gotify-server.nix {};
gotosocial = runTest ./web-apps/gotosocial.nix;
grafana = handleTest ./grafana {};
@@ -686,7 +688,9 @@ in {
ocis = handleTest ./ocis.nix {};
oddjobd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./oddjobd.nix {};
oh-my-zsh = handleTest ./oh-my-zsh.nix {};
ollama = handleTest ./ollama.nix {};
ollama = runTest ./ollama.nix;
ollama-cuda = runTestOn ["x86_64-linux" "aarch64-linux"] ./ollama-cuda.nix;
ollama-rocm = runTestOn ["x86_64-linux" "aarch64-linux"] ./ollama-rocm.nix;
ombi = handleTest ./ombi.nix {};
openarena = handleTest ./openarena.nix {};
openldap = handleTest ./openldap.nix {};
@@ -809,6 +813,7 @@ in {
rabbitmq = handleTest ./rabbitmq.nix {};
radarr = handleTest ./radarr.nix {};
radicale = handleTest ./radicale.nix {};
radicle = runTest ./radicle.nix;
ragnarwm = handleTest ./ragnarwm.nix {};
rasdaemon = handleTest ./rasdaemon.nix {};
readarr = handleTest ./readarr.nix {};

View File

@@ -25,7 +25,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
services.xserver.desktopManager.budgie = {
enable = true;
extraPlugins = [
pkgs.budgiePlugins.budgie-analogue-clock-applet
pkgs.budgie-analogue-clock-applet
];
};
};
@@ -63,7 +63,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
with subtest("Check if various environment variables are set"):
cmd = "xargs --null --max-args=1 echo < /proc/$(pgrep -xf /run/current-system/sw/bin/budgie-wm)/environ"
machine.succeed(f"{cmd} | grep 'XDG_CURRENT_DESKTOP' | grep 'Budgie:GNOME'")
machine.succeed(f"{cmd} | grep 'BUDGIE_PLUGIN_DATADIR' | grep '${pkgs.budgie.budgie-desktop-with-plugins.pname}'")
machine.succeed(f"{cmd} | grep 'BUDGIE_PLUGIN_DATADIR' | grep '${pkgs.budgie-desktop-with-plugins.pname}'")
with subtest("Open run dialog"):
machine.send_key("alt-f2")

View File

@@ -0,0 +1,34 @@
{ lib, pkgs, ... }:
# This needs to be a VM test because the FUSE kernel module can't be used inside of a derivation in the Nix sandbox.
# This test also exercises the LZFSE support in darling-dmg.
let
# The last kitty release which is stored on an HFS+ filesystem inside the disk image
test-dmg-file = pkgs.fetchurl {
url = "https://github.com/kovidgoyal/kitty/releases/download/v0.17.4/kitty-0.17.4.dmg";
hash = "sha256-m+c5s8fFrgUc0xQNI196WplYBZq9+lNgems5haZUdvA=";
};
in
{
name = "darling-dmg";
meta.maintainers = with lib.maintainers; [ Luflosi ];
nodes.machine = {};
testScript = ''
start_all()
machine.succeed("mkdir mount-point")
machine.succeed("'${pkgs.darling-dmg}/bin/darling-dmg' '${test-dmg-file}' mount-point")
# Crude way to verify the contents
# Taken from https://stackoverflow.com/questions/545387/linux-compute-a-single-hash-for-a-given-folder-contents
# This could be improved. It does not check symlinks for example.
hash = machine.succeed("""
(find mount-point -type f -print0 | sort -z | xargs -0 sha256sum; \
find mount-point \( -type f -o -type d \) -print0 | sort -z | \
xargs -0 stat -c '%n %a') \
| sha256sum
""").strip()
assert hash == "00e61c2ef171093fbf194e420c17bb84bcdb823238d70eb46e375bab2427cc21 -", f"The disk image contents differ from what was expected (was {hash})"
'';
}

26
nixos/tests/gotenberg.nix Normal file
View File

@@ -0,0 +1,26 @@
import ./make-test-python.nix (
{ lib, ... }:
{
name = "gotenberg";
meta.maintainers = with lib.maintainers; [ pyrox0 ];
nodes.machine = {
services.gotenberg = {
enable = true;
};
};
testScript = ''
start_all()
machine.wait_for_unit("gotenberg.service")
# Gotenberg startup
machine.wait_for_open_port(3000)
# Ensure healthcheck endpoint succeeds
machine.succeed("curl http://localhost:3000/health")
'';
}
)

View File

@@ -5,8 +5,11 @@ makeInstalledTest {
testConfig = {
services.flatpak.enable = true;
xdg.portal.enable = true;
xdg.portal.extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
xdg.portal = {
enable = true;
extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
config.common.default = "gtk";
};
environment.systemPackages = with pkgs; [ flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
virtualisation.diskSize = 2048;
};

View File

@@ -49,7 +49,7 @@ import ../make-test-python.nix (
services.k3s.role = "server";
services.k3s.package = k3s;
# Slightly reduce resource usage
services.k3s.extraFlags = builtins.toString [
services.k3s.extraFlags = [
"--disable coredns"
"--disable local-storage"
"--disable metrics-server"

View File

@@ -50,20 +50,14 @@ import ../make-test-python.nix (
services.k3s = {
enable = true;
role = "server";
extraFlags = builtins.toString [
extraFlags = [
"--datastore-endpoint=\"http://192.168.1.1:2379\""
"--disable"
"coredns"
"--disable"
"local-storage"
"--disable"
"metrics-server"
"--disable"
"servicelb"
"--disable"
"traefik"
"--node-ip"
"192.168.1.2"
"--disable coredns"
"--disable local-storage"
"--disable metrics-server"
"--disable servicelb"
"--disable traefik"
"--node-ip 192.168.1.2"
];
};

View File

@@ -76,21 +76,14 @@ import ../make-test-python.nix (
role = "server";
package = k3s;
clusterInit = true;
extraFlags = builtins.toString [
"--disable"
"coredns"
"--disable"
"local-storage"
"--disable"
"metrics-server"
"--disable"
"servicelb"
"--disable"
"traefik"
"--node-ip"
"192.168.1.1"
"--pause-image"
"test.local/pause:local"
extraFlags = [
"--disable coredns"
"--disable local-storage"
"--disable metrics-server"
"--disable servicelb"
"--disable traefik"
"--node-ip 192.168.1.1"
"--pause-image test.local/pause:local"
];
};
networking.firewall.allowedTCPPorts = [

View File

@@ -58,19 +58,13 @@ import ../make-test-python.nix (
services.k3s.role = "server";
services.k3s.package = k3s;
# Slightly reduce resource usage
services.k3s.extraFlags = builtins.toString [
"--disable"
"coredns"
"--disable"
"local-storage"
"--disable"
"metrics-server"
"--disable"
"servicelb"
"--disable"
"traefik"
"--pause-image"
"test.local/pause:local"
services.k3s.extraFlags = [
"--disable coredns"
"--disable local-storage"
"--disable metrics-server"
"--disable servicelb"
"--disable traefik"
"--pause-image test.local/pause:local"
];
users.users = {

View File

@@ -0,0 +1,17 @@
{ lib, ... }:
{
name = "ollama-cuda";
meta.maintainers = with lib.maintainers; [ abysssol ];
nodes.cuda =
{ ... }:
{
services.ollama.enable = true;
services.ollama.acceleration = "cuda";
};
testScript = ''
cuda.wait_for_unit("multi-user.target")
cuda.wait_for_open_port(11434)
'';
}

View File

@@ -0,0 +1,17 @@
{ lib, ... }:
{
name = "ollama-rocm";
meta.maintainers = with lib.maintainers; [ abysssol ];
nodes.rocm =
{ ... }:
{
services.ollama.enable = true;
services.ollama.acceleration = "rocm";
};
testScript = ''
rocm.wait_for_unit("multi-user.target")
rocm.wait_for_open_port(11434)
'';
}

View File

@@ -1,56 +1,53 @@
import ./make-test-python.nix ({ pkgs, lib, ... }:
{ lib, ... }:
let
mainPort = 11434;
altPort = 11435;
curlRequest = port: request:
"curl http://127.0.0.1:${toString port}/api/generate -d '${builtins.toJSON request}'";
prompt = {
model = "tinydolphin";
prompt = "lorem ipsum";
options = {
seed = 69;
temperature = 0;
};
};
in
{
name = "ollama";
meta = with lib.maintainers; {
maintainers = [ abysssol ];
};
meta.maintainers = with lib.maintainers; [ abysssol ];
nodes = {
cpu = { ... }: {
services.ollama.enable = true;
};
cpu =
{ ... }:
{
services.ollama.enable = true;
};
rocm = { ... }: {
services.ollama.enable = true;
services.ollama.acceleration = "rocm";
};
cuda = { ... }: {
services.ollama.enable = true;
services.ollama.acceleration = "cuda";
};
altAddress = { ... }: {
services.ollama.enable = true;
services.ollama.port = altPort;
};
altAddress =
{ ... }:
{
services.ollama.enable = true;
services.ollama.port = altPort;
};
};
testScript = ''
vms = [ cpu, rocm, cuda, altAddress ];
import json
def curl_request_ollama(prompt, port):
json_prompt = json.dumps(prompt)
return f"""curl http://127.0.0.1:{port}/api/generate -d '{json_prompt}'"""
prompt = {
"model": "tinydolphin",
"prompt": "lorem ipsum",
"options": {
"seed": 69,
"temperature": 0,
},
}
vms = [
(cpu, ${toString mainPort}),
(altAddress, ${toString altPort}),
]
start_all()
for vm in vms:
vm.wait_for_unit("multi-user.target")
stdout = cpu.succeed("""${curlRequest mainPort prompt}""", timeout=100)
stdout = altAddress.succeed("""${curlRequest altPort prompt}""", timeout=100)
for (vm, port) in vms:
vm.wait_for_unit("multi-user.target")
vm.wait_for_open_port(port)
stdout = vm.succeed(curl_request_ollama(prompt, port), timeout = 100)
'';
})
}

207
nixos/tests/radicle.nix Normal file
View File

@@ -0,0 +1,207 @@
# This test runs the radicle-node and radicle-httpd services on a seed host,
# and verifies that an alice peer can host a repository on the seed,
# and that a bob peer can send alice a patch via the seed.
{ pkgs, ... }:
let
# The Node ID depends on nodes.seed.services.radicle.privateKeyFile
seed-nid = "z6Mkg52RcwDrPKRzzHaYgBkHH3Gi5p4694fvPstVE9HTyMB6";
seed-ssh-keys = import ./ssh-keys.nix pkgs;
seed-tls-certs = import common/acme/server/snakeoil-certs.nix;
commonHostConfig = { nodes, config, pkgs, ... }: {
environment.systemPackages = [
config.services.radicle.package
pkgs.curl
pkgs.gitMinimal
pkgs.jq
];
environment.etc."gitconfig".text = ''
[init]
defaultBranch = main
[user]
email = root@${config.networking.hostName}
name = ${config.networking.hostName}
'';
networking = {
extraHosts = ''
${nodes.seed.networking.primaryIPAddress} ${nodes.seed.services.radicle.httpd.nginx.serverName}
'';
};
security.pki.certificateFiles = [
seed-tls-certs.ca.cert
];
};
radicleConfig = { nodes, ... }: alias:
pkgs.writeText "config.json" (builtins.toJSON {
preferredSeeds = [
"${seed-nid}@seed:${toString nodes.seed.services.radicle.node.listenPort}"
];
node = {
inherit alias;
relay = "never";
seedingPolicy = {
default = "block";
};
};
});
in
{
name = "radicle";
meta = with pkgs.lib.maintainers; {
maintainers = [
julm
lorenzleutgeb
];
};
nodes = {
seed = { pkgs, config, ... }: {
imports = [ commonHostConfig ];
services.radicle = {
enable = true;
privateKeyFile = seed-ssh-keys.snakeOilEd25519PrivateKey;
publicKeyFile = seed-ssh-keys.snakeOilEd25519PublicKey;
node = {
openFirewall = true;
};
httpd = {
enable = true;
nginx = {
serverName = seed-tls-certs.domain;
addSSL = true;
sslCertificate = seed-tls-certs.${seed-tls-certs.domain}.cert;
sslCertificateKey = seed-tls-certs.${seed-tls-certs.domain}.key;
};
};
settings = {
preferredSeeds = [];
node = {
relay = "always";
seedingPolicy = {
default = "allow";
scope = "all";
};
};
};
};
services.nginx = {
enable = true;
};
networking.firewall.allowedTCPPorts = [ 443 ];
};
alice = {
imports = [ commonHostConfig ];
};
bob = {
imports = [ commonHostConfig ];
};
};
testScript = { nodes, ... }@args: ''
start_all()
with subtest("seed can run radicle-node"):
# The threshold and/or hardening may have to be changed with new features/checks
print(seed.succeed("systemd-analyze security radicle-node.service --threshold=10 --no-pager"))
seed.wait_for_unit("radicle-node.service")
seed.wait_for_open_port(${toString nodes.seed.services.radicle.node.listenPort})
with subtest("seed can run radicle-httpd"):
# The threshold and/or hardening may have to be changed with new features/checks
print(seed.succeed("systemd-analyze security radicle-httpd.service --threshold=10 --no-pager"))
seed.wait_for_unit("radicle-httpd.service")
seed.wait_for_open_port(${toString nodes.seed.services.radicle.httpd.listenPort})
seed.wait_for_open_port(443)
assert alice.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n"
assert bob.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n"
with subtest("alice can create a Node ID"):
alice.succeed("rad auth --alias alice --stdin </dev/null")
alice.copy_from_host("${radicleConfig args "alice"}", "/root/.radicle/config.json")
with subtest("alice can run a node"):
alice.succeed("rad node start")
with subtest("alice can create a Git repository"):
alice.succeed(
"mkdir /tmp/repo",
"git -C /tmp/repo init",
"echo hello world > /tmp/repo/testfile",
"git -C /tmp/repo add .",
"git -C /tmp/repo commit -m init"
)
with subtest("alice can create a Repository ID"):
alice.succeed(
"cd /tmp/repo && rad init --name repo --description descr --default-branch main --public"
)
alice_repo_rid=alice.succeed("cd /tmp/repo && rad inspect --rid").rstrip("\n")
with subtest("alice can send a repository to the seed"):
alice.succeed(f"rad sync --seed ${seed-nid} {alice_repo_rid}")
with subtest(f"seed can receive the repository {alice_repo_rid}"):
seed.wait_until_succeeds("test 1 = \"$(rad-system stats | jq .local.repos)\"")
with subtest("bob can create a Node ID"):
bob.succeed("rad auth --alias bob --stdin </dev/null")
bob.copy_from_host("${radicleConfig args "bob"}", "/root/.radicle/config.json")
bob.succeed("rad node start")
with subtest("bob can clone alice's repository from the seed"):
bob.succeed(f"rad clone {alice_repo_rid} /tmp/repo")
assert bob.succeed("cat /tmp/repo/testfile") == "hello world\n"
with subtest("bob can clone alice's repository from the seed through the HTTP gateway"):
bob.succeed(f"git clone https://${nodes.seed.services.radicle.httpd.nginx.serverName}/{alice_repo_rid[4:]}.git /tmp/repo-http")
assert bob.succeed("cat /tmp/repo-http/testfile") == "hello world\n"
with subtest("alice can push the main branch to the rad remote"):
alice.succeed(
"echo hello bob > /tmp/repo/testfile",
"git -C /tmp/repo add .",
"git -C /tmp/repo commit -m 'hello to bob'",
"git -C /tmp/repo push rad main"
)
with subtest("bob can sync bob's repository from the seed"):
bob.succeed(
"cd /tmp/repo && rad sync --seed ${seed-nid}",
"cd /tmp/repo && git pull"
)
assert bob.succeed("cat /tmp/repo/testfile") == "hello bob\n"
with subtest("bob can push a patch"):
bob.succeed(
"echo hello alice > /tmp/repo/testfile",
"git -C /tmp/repo checkout -b for-alice",
"git -C /tmp/repo add .",
"git -C /tmp/repo commit -m 'hello to alice'",
"git -C /tmp/repo push -o patch.message='hello for alice' rad HEAD:refs/patches"
)
bob_repo_patch1_pid=bob.succeed("cd /tmp/repo && git branch --remotes | sed -ne 's:^ *rad/patches/::'p").rstrip("\n")
with subtest("alice can receive the patch"):
alice.wait_until_succeeds("test 1 = \"$(rad stats | jq .local.patches)\"")
alice.succeed(
f"cd /tmp/repo && rad patch show {bob_repo_patch1_pid} | grep 'opened by bob'",
f"cd /tmp/repo && rad patch checkout {bob_repo_patch1_pid}"
)
assert alice.succeed("cat /tmp/repo/testfile") == "hello alice\n"
with subtest("alice can comment the patch"):
alice.succeed(
f"cd /tmp/repo && rad patch comment {bob_repo_patch1_pid} -m thank-you"
)
with subtest("alice can merge the patch"):
alice.succeed(
"git -C /tmp/repo checkout main",
f"git -C /tmp/repo merge patch/{bob_repo_patch1_pid[:7]}",
"git -C /tmp/repo push rad main",
"cd /tmp/repo && rad patch list | grep -qxF 'Nothing to show.'"
)
'';
}