Compare commits

...

2 Commits

Author SHA1 Message Date
Pablo Ovelleiro Corral
2beaf6f061 Add server and client module 2025-07-29 11:39:37 +02:00
Jörg Thalheim
b059709c83 waypipe: disable gpu for now 2025-07-29 11:37:46 +02:00
4 changed files with 322 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
{ nixpkgs, ... }:
let
mkResticGenerator = machineName: rec {
files."restic-rest-pass" = { };
files."restic-rest-env" = { };
files."restic-repokey" = { };
share = true;
validation.name = script;
runtimeInputs = [ nixpkgs.xkcdpass ];
script = ''
xkcdpass -n 4 -d - > "$out/restic-rest-pass"
xkcdpass -n 4 -d - > "$out/restic-repokey"
echo "RESTIC_REST_USERNAME=${machineName}" > $out/restic-rest-env
echo "RESTIC_REST_PASSWORD=$(cat $out/restic-rest-pass)" >> $out/restic-rest-env
'';
};
in
{
_class = "clan.service";
manifest.name = "restic";
# Define what roles exist
roles.server = {
interface =
{ lib, ... }:
# system ,
# {
# ...
# }:
# # let pkgs = import nixpkgs { inherit system; }; in
{
# These options can be set via 'roles.server.settings'
# options.restic.package = lib.mkPackageOption pkgs "restic" { };
options.directory = lib.mkOption {
type = lib.types.str;
default = "/var/lib/restic";
description = ''
The directory where the restic repositories are stored.
'';
};
};
perInstance =
{ settings, roles, ... }:
{
nixosModule =
{
config,
lib,
pkgs,
...
}:
{
clan.core.vars.generators = {
restic-cert = rec {
files."restic-key" = { };
files."restic-cert".secret = false;
share = true;
runtimeInputs = with pkgs; [
coreutils
openssl
];
validation.script = script;
# TODO openssl will ask for Country, City, etc.
# Can we pass those as prompts?
# We use a wildcard cert for *.restic
script = ''
openssl req -newkey rsa:2048 -nodes -x509 \
-keyout $out/restic-key \
-out $out/restic-cert \
-addext "subjectAltName = DNS:*.restic"
'';
};
restic-server = {
files."htpasswd" = { };
runtimeInputs = [ pkgs.apacheHttpd ];
# depend on all client's generators
dependencies = lib.unique (
map (machine: "restic-${machine}") (lib.attrNames roles.client.machines)
);
script = lib.concatMapStringsSep "\n" (machine: ''
cat $in/restic-${machine}/restic-rest-pass | \
htpasswd -B -i -n ${machine} >> $out/htpasswd
'') (lib.attrNames roles.client.machines);
};
}
// builtins.listToAttrs (
map (machine: {
name = "restic-${machine}";
value = (mkResticGenerator machine);
}) (lib.attrNames roles.client.machines)
);
systemd.paths.restic-htpasswd = {
description = "Watch restic-rest .htpasswd for changes";
pathConfig = {
PathChanged = config.clan.core.vars.generators."restic-server".files."htpasswd".path;
Unit = "restic-rest-server.service";
};
};
systemd.services.restic-rest-server.serviceConfig = {
# TODO not sure if this is the correct fix
RestrictAddressFamilies = lib.mkForce "AF_INET AF_INET6";
LoadCredential = [
"restic-cert:${config.clan.core.vars.generators."restic-cert".files."restic-cert".path}"
"restic-key:${config.clan.core.vars.generators."restic-cert".files."restic-key".path}"
"htpasswd:${config.clan.core.vars.generators."restic-server".files."htpasswd".path}"
];
};
networking.firewall.allowedTCPPorts = [ 8124 ];
services.restic.server = rec {
enable = true;
# prometheus = true;
privateRepos = true;
listenAddress = "0.0.0.0:8124";
dataDir = settings.directory;
extraFlags = [
"--htpasswd-file=%d/htpasswd"
"--listen=${listenAddress}"
"--tls"
"--tls-cert=%d/restic-cert"
"--tls-key=%d/restic-key"
];
};
};
};
};
roles.client = {
# TODO
};
# roles.client = {
# interface = {
# # These options can be set via 'roles.client.settings'
# options.ipRanges = mkOption { type = listOf str; };
# };
#
# # Maps over all instances and produces one result per instance.
# perInstance = { instanceName, settings, machine, roles, ... }: {
# # Analog to 'perSystem' of flake-parts.
# # For every instance of this service we will add a nixosModule to a client-machine
# nixosModule = { config, ... }: {
# # Interaction examples what you could do here:
# # - Get some settings of this machine
# # settings.ipRanges
# #
# # - Get all controller names:
# # allControllerNames = lib.attrNames roles.controller.machines
# #
# # - Get all roles of the machine:
# # machine.roles
# #
# # - Get the settings that where applied to a specific controller machine:
# # roles.controller.machines.jon.settings
# #
# # Add one systemd service for every instance
# systemd.services.zerotier-client-${instanceName} = {
# # ... depend on the '.config' and 'perInstance arguments'
# };
# };
# }
# };
# Maps over all machines and produces one result per machine.
# perMachine = { instances, machine, ... }: {
# # Analog to 'perSystem' of flake-parts.
# # For every machine of this service we will add exactly one nixosModule to a machine
# nixosModule = { config, ... }: {
# # Interaction examples what you could do here:
# # - Get the name of this machine
# # machine.name
# #
# # - Get all roles of this machine across all instances:
# # machine.roles
# #
# # - Get the settings of a specific instance of a specific machine
# # instances.foo.roles.peer.machines.jon.settings
# #
# # Globally enable something
# networking.enable = true;
# };
# };
}

View File

@@ -0,0 +1,25 @@
{ self, inputs, ... }:
let
restic-module = import ../restic { inherit (inputs) nixpkgs; };
in
{
perSystem =
{ pkgs, ... }:
let
nixosTestArgs = {
inherit pkgs;
inherit self;
};
in
{
checks = {
restic = import ./tests {
# inherit (self) clanLib;
inherit restic-module
pkgs self;
} nixosTestArgs;
};
};
clan.inventory.modules.restic = restic-module;
}

View File

@@ -0,0 +1,81 @@
{
pkgs,
self,
clanLib,
restic-module,
...
}:
clanLib.test.makeTestClan {
inherit pkgs self;
nixosTest = (
{ ... }:
{
name = "restic";
clan = {
/*
# Desired inventory for a complete test:
inventory = {
machines = {
# - Two backup servers (destinations)
# - Three backup clients:
# - client_1 and client_2 should backup to both servers
# - client_3 should only backup to server_2
server_1 = { };
server_2 = { };
client_1.tags = [ "backup_to1" "backup_to2" ];
client_2.tags = [ "backup_to1" "backup_to2" ];
client_3.tags = [ "backup_to1" ];
};
services = {
# Apply roles based on tags for clients
restic.backup_dest1.roles.client.tags = [ "backup_to1" ];
restic.backup_dest2.roles.client.tags = [ "backup_to2" ];
# Apply roles based on machine names for servers
restic.backup_dest1.roles.server.machines = ["server_1"];
restic.backup_dest2.roles.server.machines = ["server_2"];
};
};
*/
inventory = {
# Define machines
machines.client_machine = { };
machines.server_machine = { };
modules.restic = restic-module;
instances.test-restic = {
module.name = "restic";
roles.server.machines.server_machine = {
# Server settings
settings.directory = "/var/lib/restic";
};
roles.client.machines.client_machine = {
# Client settings
};
};
};
directory = ./.;
};
# defaults = { };
#
# nodes = { };
testScript = ''
start_all()
server_machine.wait_for_unit("restic-rest-server")
'';
}
);
}

View File

@@ -0,0 +1 @@
{"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", "type": "age"}