mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-13 21:50:33 +03:00
nixos/guix: init
This commit is contained in:
parent
092aaf8418
commit
ad277ea47e
9 changed files with 599 additions and 0 deletions
|
@ -14,6 +14,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
||||||
|
|
||||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||||
|
|
||||||
|
- [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.enable).
|
||||||
|
|
||||||
- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable).
|
- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable).
|
||||||
|
|
||||||
- [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
|
- [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
|
||||||
|
|
|
@ -683,6 +683,7 @@
|
||||||
./services/misc/gollum.nix
|
./services/misc/gollum.nix
|
||||||
./services/misc/gpsd.nix
|
./services/misc/gpsd.nix
|
||||||
./services/misc/greenclip.nix
|
./services/misc/greenclip.nix
|
||||||
|
./services/misc/guix
|
||||||
./services/misc/headphones.nix
|
./services/misc/headphones.nix
|
||||||
./services/misc/heisenbridge.nix
|
./services/misc/heisenbridge.nix
|
||||||
./services/misc/homepage-dashboard.nix
|
./services/misc/homepage-dashboard.nix
|
||||||
|
|
394
nixos/modules/services/misc/guix/default.nix
Normal file
394
nixos/modules/services/misc/guix/default.nix
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.guix;
|
||||||
|
|
||||||
|
package = cfg.package.override { inherit (cfg) stateDir storeDir; };
|
||||||
|
|
||||||
|
guixBuildUser = id: {
|
||||||
|
name = "guixbuilder${toString id}";
|
||||||
|
group = cfg.group;
|
||||||
|
extraGroups = [ cfg.group ];
|
||||||
|
createHome = false;
|
||||||
|
description = "Guix build user ${toString id}";
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
guixBuildUsers = numberOfUsers:
|
||||||
|
builtins.listToAttrs (map
|
||||||
|
(user: {
|
||||||
|
name = user.name;
|
||||||
|
value = user;
|
||||||
|
})
|
||||||
|
(builtins.genList guixBuildUser numberOfUsers));
|
||||||
|
|
||||||
|
# A set of Guix user profiles to be linked at activation.
|
||||||
|
guixUserProfiles = {
|
||||||
|
# The current Guix profile that is created through `guix pull`.
|
||||||
|
"current-guix" = "\${XDG_CONFIG_HOME}/guix/current";
|
||||||
|
|
||||||
|
# The default Guix profile similar to $HOME/.nix-profile from Nix.
|
||||||
|
"guix-profile" = "$HOME/.guix-profile";
|
||||||
|
};
|
||||||
|
|
||||||
|
# All of the Guix profiles to be used.
|
||||||
|
guixProfiles = lib.attrValues guixUserProfiles;
|
||||||
|
|
||||||
|
serviceEnv = {
|
||||||
|
GUIX_LOCPATH = "${cfg.stateDir}/guix/profiles/per-user/root/guix-profile/lib/locale";
|
||||||
|
LC_ALL = "C.UTF-8";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
|
||||||
|
|
||||||
|
options.services.guix = with lib; {
|
||||||
|
enable = mkEnableOption "Guix build daemon service";
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "guixbuild";
|
||||||
|
example = "guixbuild";
|
||||||
|
description = ''
|
||||||
|
The group of the Guix build user pool.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
nrBuildUsers = mkOption {
|
||||||
|
type = types.ints.unsigned;
|
||||||
|
description = ''
|
||||||
|
Number of Guix build users to be used in the build pool.
|
||||||
|
'';
|
||||||
|
default = 10;
|
||||||
|
example = 20;
|
||||||
|
};
|
||||||
|
|
||||||
|
extraArgs = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "--max-jobs=4" "--debug" ];
|
||||||
|
description = ''
|
||||||
|
Extra flags to pass to the Guix daemon service.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "guix" {
|
||||||
|
extraDescription = ''
|
||||||
|
It should contain {command}`guix-daemon` and {command}`guix`
|
||||||
|
executable.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
storeDir = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "/gnu/store";
|
||||||
|
description = ''
|
||||||
|
The store directory where the Guix service will serve to/from. Take
|
||||||
|
note Guix cannot take advantage of substitutes if you set it something
|
||||||
|
other than {file}`/gnu/store` since most of the cached builds are
|
||||||
|
assumed to be in there.
|
||||||
|
|
||||||
|
::: {.warning}
|
||||||
|
This will also recompile all packages because the normal cache no
|
||||||
|
longer applies.
|
||||||
|
:::
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
stateDir = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "/var";
|
||||||
|
description = ''
|
||||||
|
The state directory where Guix service will store its data such as its
|
||||||
|
user-specific profiles, cache, and state files.
|
||||||
|
|
||||||
|
::: {.warning}
|
||||||
|
Changing it to something other than the default will rebuild the
|
||||||
|
package.
|
||||||
|
:::
|
||||||
|
'';
|
||||||
|
example = "/gnu/var";
|
||||||
|
};
|
||||||
|
|
||||||
|
publish = {
|
||||||
|
enable = mkEnableOption "substitute server for your Guix store directory";
|
||||||
|
|
||||||
|
generateKeyPair = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
description = ''
|
||||||
|
Whether to generate signing keys in {file}`/etc/guix` which are
|
||||||
|
required to initialize a substitute server. Otherwise,
|
||||||
|
`--public-key=$FILE` and `--private-key=$FILE` can be passed in
|
||||||
|
{option}`services.guix.publish.extraArgs`.
|
||||||
|
'';
|
||||||
|
default = true;
|
||||||
|
example = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8181;
|
||||||
|
example = 8200;
|
||||||
|
description = ''
|
||||||
|
Port of the substitute server to listen on.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "guix-publish";
|
||||||
|
description = ''
|
||||||
|
Name of the user to change once the server is up.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraArgs = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
description = ''
|
||||||
|
Extra flags to pass to the substitute server.
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
example = [
|
||||||
|
"--compression=zstd:6"
|
||||||
|
"--discover=no"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
gc = {
|
||||||
|
enable = mkEnableOption "automatic garbage collection service for Guix";
|
||||||
|
|
||||||
|
extraArgs = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
List of arguments to be passed to {command}`guix gc`.
|
||||||
|
|
||||||
|
When given no option, it will try to collect all garbage which is
|
||||||
|
often inconvenient so it is recommended to set [some
|
||||||
|
options](https://guix.gnu.org/en/manual/en/html_node/Invoking-guix-gc.html).
|
||||||
|
'';
|
||||||
|
example = [
|
||||||
|
"--delete-generations=1m"
|
||||||
|
"--free-space=10G"
|
||||||
|
"--optimize"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
dates = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "03:15";
|
||||||
|
example = "weekly";
|
||||||
|
description = ''
|
||||||
|
How often the garbage collection occurs. This takes the time format
|
||||||
|
from {manpage}`systemd.time(7)`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||||
|
{
|
||||||
|
environment.systemPackages = [ package ];
|
||||||
|
|
||||||
|
users.users = guixBuildUsers cfg.nrBuildUsers;
|
||||||
|
users.groups.${cfg.group} = { };
|
||||||
|
|
||||||
|
# Guix uses Avahi (through guile-avahi) both for the auto-discovering and
|
||||||
|
# advertising substitute servers in the local network.
|
||||||
|
services.avahi.enable = lib.mkDefault true;
|
||||||
|
services.avahi.publish.enable = lib.mkDefault true;
|
||||||
|
services.avahi.publish.userServices = lib.mkDefault true;
|
||||||
|
|
||||||
|
# It's similar to Nix daemon so there's no question whether or not this
|
||||||
|
# should be sandboxed.
|
||||||
|
systemd.services.guix-daemon = {
|
||||||
|
environment = serviceEnv;
|
||||||
|
script = ''
|
||||||
|
${lib.getExe' package "guix-daemon"} \
|
||||||
|
--build-users-group=${cfg.group} \
|
||||||
|
${lib.escapeShellArgs cfg.extraArgs}
|
||||||
|
'';
|
||||||
|
serviceConfig = {
|
||||||
|
OOMPolicy = "continue";
|
||||||
|
RemainAfterExit = "yes";
|
||||||
|
Restart = "always";
|
||||||
|
TasksMax = 8192;
|
||||||
|
};
|
||||||
|
unitConfig.RequiresMountsFor = [
|
||||||
|
cfg.storeDir
|
||||||
|
cfg.stateDir
|
||||||
|
];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# This is based from Nix daemon socket unit from upstream Nix package.
|
||||||
|
# Guix build daemon has support for systemd-style socket activation.
|
||||||
|
systemd.sockets.guix-daemon = {
|
||||||
|
description = "Guix daemon socket";
|
||||||
|
before = [ "multi-user.target" ];
|
||||||
|
listenStreams = [ "${cfg.stateDir}/guix/daemon-socket/socket" ];
|
||||||
|
unitConfig = {
|
||||||
|
RequiresMountsFor = [
|
||||||
|
cfg.storeDir
|
||||||
|
cfg.stateDir
|
||||||
|
];
|
||||||
|
ConditionPathIsReadWrite = "${cfg.stateDir}/guix/daemon-socket";
|
||||||
|
};
|
||||||
|
wantedBy = [ "socket.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.mounts = [{
|
||||||
|
description = "Guix read-only store directory";
|
||||||
|
before = [ "guix-daemon.service" ];
|
||||||
|
what = cfg.storeDir;
|
||||||
|
where = cfg.storeDir;
|
||||||
|
type = "none";
|
||||||
|
options = "bind,ro";
|
||||||
|
|
||||||
|
unitConfig.DefaultDependencies = false;
|
||||||
|
wantedBy = [ "guix-daemon.service" ];
|
||||||
|
}];
|
||||||
|
|
||||||
|
# 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
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Link the usual Guix profiles to the home directory. This is useful in
|
||||||
|
# ephemeral setups where only certain part of the filesystem is
|
||||||
|
# persistent (e.g., "Erase my darlings"-type of setup).
|
||||||
|
system.userActivationScripts.guix-activate-user-profiles.text = let
|
||||||
|
linkProfileToPath = acc: profile: location: let
|
||||||
|
guixProfile = "${cfg.stateDir}/guix/profiles/per-user/\${USER}/${profile}";
|
||||||
|
in acc + ''
|
||||||
|
[ -d "${guixProfile}" ] && ln -sf "${guixProfile}" "${location}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
activationScript = lib.foldlAttrs linkProfileToPath "" guixUserProfiles;
|
||||||
|
in ''
|
||||||
|
# Don't export this please! It is only expected to be used for this
|
||||||
|
# activation script and nothing else.
|
||||||
|
XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
|
||||||
|
|
||||||
|
# Linking the usual Guix profiles into the home directory.
|
||||||
|
${activationScript}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# GUIX_LOCPATH is basically LOCPATH but for Guix libc which in turn used by
|
||||||
|
# virtually every Guix-built packages. This is so that Guix-installed
|
||||||
|
# applications wouldn't use incompatible locale data and not touch its host
|
||||||
|
# system.
|
||||||
|
environment.sessionVariables.GUIX_LOCPATH = lib.makeSearchPath "lib/locale" guixProfiles;
|
||||||
|
|
||||||
|
# What Guix profiles export is very similar to Nix profiles so it is
|
||||||
|
# acceptable to list it here. Also, it is more likely that the user would
|
||||||
|
# want to use packages explicitly installed from Guix so we're putting it
|
||||||
|
# first.
|
||||||
|
environment.profiles = lib.mkBefore guixProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
(lib.mkIf cfg.publish.enable {
|
||||||
|
systemd.services.guix-publish = {
|
||||||
|
description = "Guix remote store";
|
||||||
|
environment = serviceEnv;
|
||||||
|
|
||||||
|
# Mounts will be required by the daemon service anyways so there's no
|
||||||
|
# need add RequiresMountsFor= or something similar.
|
||||||
|
requires = [ "guix-daemon.service" ];
|
||||||
|
after = [ "guix-daemon.service" ];
|
||||||
|
partOf = [ "guix-daemon.service" ];
|
||||||
|
|
||||||
|
preStart = lib.mkIf cfg.publish.generateKeyPair ''
|
||||||
|
# Generate the keypair if it's missing.
|
||||||
|
[ -f "/etc/guix/signing-key.sec" ] && [ -f "/etc/guix/signing-key.pub" ] || \
|
||||||
|
${lib.getExe' package "guix"} archive --generate-key || {
|
||||||
|
rm /etc/guix/signing-key.*;
|
||||||
|
${lib.getExe' package "guix"} archive --generate-key;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
script = ''
|
||||||
|
${lib.getExe' package "guix"} publish \
|
||||||
|
--user=${cfg.publish.user} --port=${builtins.toString cfg.publish.port} \
|
||||||
|
${lib.escapeShellArgs cfg.publish.extraArgs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 10;
|
||||||
|
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"@debug"
|
||||||
|
"@setuid"
|
||||||
|
];
|
||||||
|
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
"AF_UNIX"
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
];
|
||||||
|
|
||||||
|
# While the permissions can be set, it is assumed to be taken by Guix
|
||||||
|
# daemon service which it has already done the setup.
|
||||||
|
ConfigurationDirectory = "guix";
|
||||||
|
|
||||||
|
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
|
CapabilityBoundingSet = [
|
||||||
|
"CAP_NET_BIND_SERVICE"
|
||||||
|
"CAP_SETUID"
|
||||||
|
"CAP_SETGID"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.guix-publish = lib.mkIf (cfg.publish.user == "guix-publish") {
|
||||||
|
description = "Guix publish user";
|
||||||
|
group = config.users.groups.guix-publish.name;
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
users.groups.guix-publish = {};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.gc.enable {
|
||||||
|
# This service should be handled by root to collect all garbage by all
|
||||||
|
# users.
|
||||||
|
systemd.services.guix-gc = {
|
||||||
|
description = "Guix garbage collection";
|
||||||
|
startAt = cfg.gc.dates;
|
||||||
|
script = ''
|
||||||
|
${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateNetworks = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@default"
|
||||||
|
"@file-system"
|
||||||
|
"@basic-io"
|
||||||
|
"@system-service"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.guix-gc.timerConfig.Persistent = true;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
|
@ -357,6 +357,7 @@ in {
|
||||||
grow-partition = runTest ./grow-partition.nix;
|
grow-partition = runTest ./grow-partition.nix;
|
||||||
grub = handleTest ./grub.nix {};
|
grub = handleTest ./grub.nix {};
|
||||||
guacamole-server = handleTest ./guacamole-server.nix {};
|
guacamole-server = handleTest ./guacamole-server.nix {};
|
||||||
|
guix = handleTest ./guix {};
|
||||||
gvisor = handleTest ./gvisor.nix {};
|
gvisor = handleTest ./gvisor.nix {};
|
||||||
hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; };
|
hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; };
|
||||||
hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; };
|
hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; };
|
||||||
|
|
38
nixos/tests/guix/basic.nix
Normal file
38
nixos/tests/guix/basic.nix
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Take note the Guix store directory is empty. Also, we're trying to prevent
|
||||||
|
# Guix from trying to downloading substitutes because of the restricted
|
||||||
|
# access (assuming it's in a sandboxed environment).
|
||||||
|
#
|
||||||
|
# So this test is what it is: a basic test while trying to use Guix as much as
|
||||||
|
# we possibly can (including the API) without triggering its download alarm.
|
||||||
|
|
||||||
|
import ../make-test-python.nix ({ lib, pkgs, ... }: {
|
||||||
|
name = "guix-basic";
|
||||||
|
meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
|
||||||
|
|
||||||
|
nodes.machine = { config, ... }: {
|
||||||
|
environment.etc."guix/scripts".source = ./scripts;
|
||||||
|
services.guix.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("guix-daemon.service")
|
||||||
|
|
||||||
|
# Can't do much here since the environment has restricted network access.
|
||||||
|
with subtest("Guix basic package management"):
|
||||||
|
machine.succeed("guix build --dry-run --verbosity=0 hello")
|
||||||
|
machine.succeed("guix show hello")
|
||||||
|
|
||||||
|
# This is to see if the Guix API is usable and mostly working.
|
||||||
|
with subtest("Guix API scripting"):
|
||||||
|
scripts_dir = pathlib.Path("/etc/guix/scripts")
|
||||||
|
|
||||||
|
text_msg = "Hello there, NixOS!"
|
||||||
|
text_store_file = machine.succeed(f"guix repl -- {scripts_dir}/create-file-to-store.scm '{text_msg}'")
|
||||||
|
assert machine.succeed(f"cat {text_store_file}") == text_msg
|
||||||
|
|
||||||
|
machine.succeed(f"guix repl -- {scripts_dir}/add-existing-files-to-store.scm {scripts_dir}")
|
||||||
|
'';
|
||||||
|
})
|
8
nixos/tests/guix/default.nix
Normal file
8
nixos/tests/guix/default.nix
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{ system ? builtins.currentSystem
|
||||||
|
, pkgs ? import ../../.. { inherit system; }
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
basic = import ./basic.nix { inherit system pkgs; };
|
||||||
|
publish = import ./publish.nix { inherit system pkgs; };
|
||||||
|
}
|
95
nixos/tests/guix/publish.nix
Normal file
95
nixos/tests/guix/publish.nix
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# Testing out the substitute server with two machines in a local network. As a
|
||||||
|
# bonus, we'll also test a feature of the substitute server being able to
|
||||||
|
# advertise its service to the local network with Avahi.
|
||||||
|
|
||||||
|
import ../make-test-python.nix ({ pkgs, lib, ... }: let
|
||||||
|
publishPort = 8181;
|
||||||
|
inherit (builtins) toString;
|
||||||
|
in {
|
||||||
|
name = "guix-publish";
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
|
||||||
|
|
||||||
|
nodes = let
|
||||||
|
commonConfig = { config, ... }: {
|
||||||
|
# We'll be using '--advertise' flag with the
|
||||||
|
# substitute server which requires Avahi.
|
||||||
|
services.avahi = {
|
||||||
|
enable = true;
|
||||||
|
nssmdns = true;
|
||||||
|
publish = {
|
||||||
|
enable = true;
|
||||||
|
userServices = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
server = { config, lib, pkgs, ... }: {
|
||||||
|
imports = [ commonConfig ];
|
||||||
|
|
||||||
|
services.guix = {
|
||||||
|
enable = true;
|
||||||
|
publish = {
|
||||||
|
enable = true;
|
||||||
|
port = publishPort;
|
||||||
|
|
||||||
|
generateKeyPair = true;
|
||||||
|
extraArgs = [ "--advertise" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ publishPort ];
|
||||||
|
};
|
||||||
|
|
||||||
|
client = { config, lib, pkgs, ... }: {
|
||||||
|
imports = [ commonConfig ];
|
||||||
|
|
||||||
|
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}'"
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
"--discover=yes"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
scripts_dir = pathlib.Path("/etc/guix/scripts")
|
||||||
|
|
||||||
|
for machine in machines:
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("guix-daemon.service")
|
||||||
|
machine.wait_for_unit("avahi-daemon.service")
|
||||||
|
|
||||||
|
server.wait_for_unit("guix-publish.service")
|
||||||
|
server.wait_for_open_port(${toString publishPort})
|
||||||
|
server.succeed("curl http://localhost:${toString publishPort}/")
|
||||||
|
|
||||||
|
# Now it's the client turn to make use of it.
|
||||||
|
substitute_server = "http://server.local:${toString publishPort}"
|
||||||
|
client.wait_for_unit("network-online.target")
|
||||||
|
response = client.succeed(f"curl {substitute_server}")
|
||||||
|
assert "Guix Substitute Server" in response
|
||||||
|
|
||||||
|
# Authorizing the server to be used as a substitute server.
|
||||||
|
client.succeed(f"curl -O {substitute_server}/signing-key.pub")
|
||||||
|
client.succeed("guix archive --authorize < ./signing-key.pub")
|
||||||
|
|
||||||
|
# Since we're using the substitute server with the `--advertise` flag, we
|
||||||
|
# might as well check it.
|
||||||
|
client.succeed("avahi-browse --resolve --terminate _guix_publish._tcp | grep '_guix_publish._tcp'")
|
||||||
|
'';
|
||||||
|
})
|
52
nixos/tests/guix/scripts/add-existing-files-to-store.scm
Normal file
52
nixos/tests/guix/scripts/add-existing-files-to-store.scm
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
;; A simple script that adds each file given from the command-line into the
|
||||||
|
;; store and checks them if it's the same.
|
||||||
|
(use-modules (guix)
|
||||||
|
(srfi srfi-1)
|
||||||
|
(ice-9 ftw)
|
||||||
|
(rnrs io ports))
|
||||||
|
|
||||||
|
;; This is based from tests/derivations.scm from Guix source code.
|
||||||
|
(define* (directory-contents dir #:optional (slurp get-bytevector-all))
|
||||||
|
"Return an alist representing the contents of DIR"
|
||||||
|
(define prefix-len (string-length dir))
|
||||||
|
(sort (file-system-fold (const #t)
|
||||||
|
(lambda (path stat result)
|
||||||
|
(alist-cons (string-drop path prefix-len)
|
||||||
|
(call-with-input-file path slurp)
|
||||||
|
result))
|
||||||
|
(lambda (path stat result) result)
|
||||||
|
(lambda (path stat result) result)
|
||||||
|
(lambda (path stat result) result)
|
||||||
|
(lambda (path stat errno result) result)
|
||||||
|
'()
|
||||||
|
dir)
|
||||||
|
(lambda (e1 e2)
|
||||||
|
(string<? (car e1) (car e2)))))
|
||||||
|
|
||||||
|
(define* (check-if-same store drv path)
|
||||||
|
"Check if the given path and its store item are the same"
|
||||||
|
(let* ((filetype (stat:type (stat drv))))
|
||||||
|
(case filetype
|
||||||
|
((regular)
|
||||||
|
(and (valid-path? store drv)
|
||||||
|
(equal? (call-with-input-file path get-bytevector-all)
|
||||||
|
(call-with-input-file drv get-bytevector-all))))
|
||||||
|
((directory)
|
||||||
|
(and (valid-path? store drv)
|
||||||
|
(equal? (directory-contents path)
|
||||||
|
(directory-contents drv))))
|
||||||
|
(else #f))))
|
||||||
|
|
||||||
|
(define* (add-and-check-item-to-store store path)
|
||||||
|
"Add PATH to STORE and check if the contents are the same"
|
||||||
|
(let* ((store-item (add-to-store store
|
||||||
|
(basename path)
|
||||||
|
#t "sha256" path))
|
||||||
|
(is-same (check-if-same store store-item path)))
|
||||||
|
(if (not is-same)
|
||||||
|
(exit 1))))
|
||||||
|
|
||||||
|
(with-store store
|
||||||
|
(map (lambda (path)
|
||||||
|
(add-and-check-item-to-store store (readlink* path)))
|
||||||
|
(cdr (command-line))))
|
8
nixos/tests/guix/scripts/create-file-to-store.scm
Normal file
8
nixos/tests/guix/scripts/create-file-to-store.scm
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
;; A script that creates a store item with the given text and prints the
|
||||||
|
;; resulting store item path.
|
||||||
|
(use-modules (guix))
|
||||||
|
|
||||||
|
(with-store store
|
||||||
|
(display (add-text-to-store store "guix-basic-test-text"
|
||||||
|
(string-join
|
||||||
|
(cdr (command-line))))))
|
Loading…
Add table
Add a link
Reference in a new issue