aboutsummaryrefslogtreecommitdiffstats
path: root/modules/by-name/ni/nix-sync
diff options
context:
space:
mode:
Diffstat (limited to 'modules/by-name/ni/nix-sync')
-rw-r--r--modules/by-name/ni/nix-sync/hosts.nix48
-rw-r--r--modules/by-name/ni/nix-sync/internal_module.nix299
-rw-r--r--modules/by-name/ni/nix-sync/module.nix61
3 files changed, 408 insertions, 0 deletions
diff --git a/modules/by-name/ni/nix-sync/hosts.nix b/modules/by-name/ni/nix-sync/hosts.nix
new file mode 100644
index 0000000..98dbbf1
--- /dev/null
+++ b/modules/by-name/ni/nix-sync/hosts.nix
@@ -0,0 +1,48 @@
+{...}: let
+ extraWkdSettings = {
+ locations."/.well-known/openpgpkey/hu/".extraConfig = ''
+ default_type application/octet-stream;
+
+ # Came from: https://www.uriports.com/blog/setting-up-openpgp-web-key-directory/
+ # No idea if it is actually necessary
+ # add_header Access-Control-Allow-Origin * always;
+ '';
+ };
+in [
+ {
+ domain = "vhack.eu";
+ url = "https://codeberg.org/vhack.eu/website.git";
+ }
+ {
+ domain = "b-peetz.de";
+ url = "https://codeberg.org/bpeetz/b-peetz.de.git";
+ }
+
+ # Trinitrix
+ {
+ domain = "trinitrix.vhack.eu";
+ url = "https://codeberg.org/trinitrix/website.git";
+ }
+
+ # WKD
+ {
+ domain = "openpgpkey.b-peetz.de";
+ url = "https://codeberg.org/vhack.eu/gpg_wkd.git";
+ extraSettings = extraWkdSettings;
+ }
+ {
+ domain = "openpgpkey.s-schoeffel.de";
+ url = "https://codeberg.org/vhack.eu/gpg_wkd.git";
+ extraSettings = extraWkdSettings;
+ }
+ {
+ domain = "openpgpkey.sils.li";
+ url = "https://codeberg.org/vhack.eu/gpg_wkd.git";
+ extraSettings = extraWkdSettings;
+ }
+ {
+ domain = "openpgpkey.vhack.eu";
+ url = "https://codeberg.org/vhack.eu/gpg_wkd.git";
+ extraSettings = extraWkdSettings;
+ }
+]
diff --git a/modules/by-name/ni/nix-sync/internal_module.nix b/modules/by-name/ni/nix-sync/internal_module.nix
new file mode 100644
index 0000000..a3ab0af
--- /dev/null
+++ b/modules/by-name/ni/nix-sync/internal_module.nix
@@ -0,0 +1,299 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}: let
+ cfg = config.services.nix-sync;
+ esa = lib.strings.escapeShellArg;
+
+ mkTimer = name: repo: {
+ description = "Nix sync ${name} timer";
+ wantedBy = ["timers.target"];
+ timerConfig = {
+ OnUnitActiveSec = repo.interval;
+ };
+ wants = ["network-online.target"];
+ after = ["network-online.target"];
+ };
+
+ parents = path: let
+ split_path = builtins.split "/" path;
+ filename = builtins.elemAt split_path (builtins.length split_path - 1);
+ path_build =
+ lib.strings.removeSuffix "/" (builtins.replaceStrings [filename] [""] path);
+ final_path =
+ if filename == ""
+ then parents path_build
+ else path_build;
+ in
+ final_path;
+
+ mkUnit = name: repo: let
+ optionalPathSeparator =
+ if lib.strings.hasPrefix "/" repo.path
+ then ""
+ else "/";
+ /*
+ * `ln` tries to create a symlink in the directory, if the target ends with a '/',
+ * thus remove it.
+ */
+ repoPath = lib.strings.removeSuffix "/" repo.path;
+
+ repoCachePath = cfg.cachePath + optionalPathSeparator + repo.path;
+ execStartScript = pkgs.writeScript "nix-sync-exec" ''
+ #! /usr/bin/env dash
+ export XDG_CACHE_HOME="$CACHE_DIRECTORY";
+ cd ${esa repoCachePath};
+
+ git fetch
+ origin="$(git rev-parse @{u})";
+ branch="$(git rev-parse @)";
+
+ if ! [ "$origin" = "$branch" ]; then
+ git pull --rebase;
+
+ out_paths=$(mktemp);
+ nix build . --print-out-paths --experimental-features 'nix-command flakes' > "$out_paths";
+ [ "$(wc -l < "$out_paths")" -gt 1 ] && (echo "To many out-paths"; exit 1)
+ out_path="$(cat "$out_paths")";
+ rm ${esa repoPath};
+ ln -s "$out_path" ${esa repoPath};
+ rm "$out_paths";
+ fi
+ '';
+ execStartPreScript = ''
+ export XDG_CACHE_HOME="$CACHE_DIRECTORY";
+
+ if ! [ -d ${esa repoCachePath}/.git ]; then
+ mkdir --parents ${esa repoCachePath};
+ git clone ${esa repo.uri} ${esa repoCachePath};
+
+ out_paths=$(mktemp);
+ nix build ${esa repoCachePath} --print-out-paths --experimental-features 'nix-command flakes' > "$out_paths";
+ [ "$(wc -l < "$out_paths")" -gt 1 ] && (echo "To many out-paths"; exit 1)
+ out_path="$(cat "$out_paths")";
+ ln -s "$out_path" ${esa repoPath};
+ rm "$out_paths";
+ fi
+
+ if ! [ -L ${esa repoPath} ]; then
+ cd ${esa repoCachePath};
+
+ git pull --rebase;
+
+ out_paths=$(mktemp);
+ nix build . --print-out-paths --experimental-features 'nix-command flakes' > "$out_paths";
+ [ "$(wc -l < "$out_paths")" -gt 1 ] && { echo "To many out-paths"; exit 1; }
+ out_path="$(cat "$out_paths")";
+
+ if [ -d ${esa repoPath} ]; then
+ rm -d ${esa repoPath};
+ else
+ mkdir --parents "$(dirname ${esa repoPath})";
+ fi
+ [ -e ${esa repoPath} ] && rm ${esa repoPath};
+
+ ln -s "$out_path" ${esa repoPath};
+ rm "$out_paths";
+ fi
+ '';
+ in {
+ description = "Nix Sync ${name}";
+ wantedBy = ["default.target"];
+ after = ["network.target"];
+ path = with pkgs; [openssh git nix mktemp coreutils dash];
+ preStart = execStartPreScript;
+
+ serviceConfig = {
+ TimeoutSec = 0;
+ ExecStart = execStartScript;
+ Restart = "on-abort";
+ # User and group
+ User = cfg.user;
+ Group = cfg.group;
+ # Runtime directory and mode
+ RuntimeDirectory = "nix-sync";
+ RuntimeDirectoryMode = "0750";
+ # Cache directory and mode
+ CacheDirectory = "nix-sync";
+ CacheDirectoryMode = "0750";
+ # Logs directory and mode
+ LogsDirectory = "nix-sync";
+ LogsDirectoryMode = "0750";
+ # Proc filesystem
+ ProcSubset = "all";
+ ProtectProc = "invisible";
+ # New file permissions
+ UMask = "0027"; # 0640 / 0750
+ # Capabilities
+ AmbientCapabilities = ["CAP_CHOWN"];
+ CapabilityBoundingSet = ["CAP_CHOWN"];
+ # Security
+ NoNewPrivileges = true;
+ # Sandboxing (sorted by occurrence in https://www.freedesktop.org/software/systemd/man/systemd.exec.html)
+ ReadWritePaths = ["${esa (parents repo.path)}" "-${esa (parents repoCachePath)}" "-${esa cfg.cachePath}"];
+ ReadOnlyPaths = ["/nix"]; # TODO: Should be irrelevant, as we have ProtectSystem=Strict <2024-06-01>
+ 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;
+ # System Call Filtering
+ SystemCallArchitectures = "native";
+ SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"];
+ };
+ };
+
+ services =
+ lib.mapAttrs' (name: repo: {
+ name = "nix-sync-${name}";
+ value = mkUnit name repo;
+ })
+ cfg.repositories;
+ timers =
+ lib.mapAttrs' (name: repo: {
+ name = "nix-sync-${name}";
+ value = mkTimer name repo;
+ })
+ cfg.repositories;
+
+ # generate the websites directory, so systemd can mount it read write
+ generatedDirectories =
+ lib.mapAttrsToList (
+ _: repo: "d ${esa (parents repo.path)} 0755 ${cfg.user} ${cfg.group}"
+ )
+ cfg.repositories;
+
+ repositoryType = lib.types.submodule ({name, ...}: {
+ options = {
+ name = lib.mkOption {
+ internal = true;
+ default = name;
+ type = lib.types.str;
+ description = "The name that should be given to this unit.";
+ };
+
+ path = lib.mkOption {
+ type = lib.types.str;
+ description = "The path at which to sync the repository";
+ };
+
+ uri = lib.mkOption {
+ type = lib.types.str;
+ example = "ssh://user@example.com:/~[user]/path/to/repo.git";
+ description = ''
+ The URI of the remote to be synchronized. This is only used in the
+ event that the directory does not already exist. See
+ <link xlink:href="https://git-scm.com/docs/git-clone#_git_urls"/>
+ for the supported URIs.
+ '';
+ };
+
+ extraSettings = lib.mkOption {
+ type = lib.types.attrsOf lib.types.anything;
+ example = lib.literalExpression ''
+ {
+ locations."/.well-known/openpgpkey/hu/" = {
+ extraConfig = \'\'
+ default_type application/octet-stream;
+
+ add_header Access-Control-Allow-Origin * always;
+ \'\';
+ };
+ }
+ '';
+ description = ''
+ Extra config to add the the nginx virtual host.
+ '';
+ };
+
+ interval = lib.mkOption {
+ type = lib.types.int;
+ default = 500;
+ description = ''
+ The interval, specified in seconds, at which the synchronization will
+ be triggered.
+ '';
+ };
+ };
+ });
+in {
+ options = {
+ services.nix-sync = {
+ enable = lib.mkEnableOption "nix-sync services";
+
+ user = lib.mkOption {
+ type = lib.types.str;
+ default = "nix-sync";
+ description = lib.mdDoc "User account under which nix-sync units runs.";
+ };
+
+ group = lib.mkOption {
+ type = lib.types.str;
+ default = "nix-sync";
+ description = lib.mdDoc "Group account under which nix-sync units runs.";
+ };
+
+ cachePath = lib.mkOption {
+ type = lib.types.str;
+ default = "/var/lib/nix-sync";
+ description = lib.mdDoc ''
+ Where to cache git directories. Should not end with a slash ("/")
+ '';
+ };
+
+ repositories = lib.mkOption {
+ type = with lib.types; attrsOf repositoryType;
+ description = ''
+ The repositories that should be synchronized.
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = !lib.strings.hasSuffix "/" cfg.cachePath;
+ message = "Your cachePath ('${cfg.cachePath}') ends with a slash ('/'), please use: '${lib.strings.removeSuffix "/" cfg.cachePath}'.";
+ }
+ ];
+ systemd = {
+ tmpfiles.rules =
+ generatedDirectories;
+
+ inherit services timers;
+ };
+ users.users =
+ if cfg.user == "nix-sync"
+ then {
+ nix-sync = {
+ group = "${cfg.group}";
+ isSystemUser = true;
+ };
+ }
+ else lib.warnIf (cfg.user != "nix-sync") "The user (${cfg.user}) is not \"nix-sync\", thus you are responible for generating it.";
+ users.groups =
+ if cfg.group == "nix-sync"
+ then {
+ nix-sync = {
+ members = ["${cfg.user}"];
+ };
+ }
+ else lib.warnIf (cfg.group != "nix-sync") "The group (${cfg.group}) is not \"nix-sync\", thus you are responible for generating it.";
+ };
+}
diff --git a/modules/by-name/ni/nix-sync/module.nix b/modules/by-name/ni/nix-sync/module.nix
new file mode 100644
index 0000000..0a92888
--- /dev/null
+++ b/modules/by-name/ni/nix-sync/module.nix
@@ -0,0 +1,61 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.vhack.nix-sync;
+
+ mkNixSyncRepository = {
+ domain,
+ root ? "",
+ url,
+ extraSettings ? {},
+ }: {
+ name = "${domain}";
+ value = {
+ path = "/etc/nginx/websites/${domain}/${root}";
+ uri = "${url}";
+ inherit extraSettings;
+ };
+ };
+ nixSyncRepositories = builtins.listToAttrs (builtins.map mkNixSyncRepository domains);
+
+ mkVirtHost = {
+ domain,
+ root ? "",
+ url,
+ extraSettings ? {},
+ }: {
+ name = "${domain}";
+ value =
+ lib.recursiveUpdate {
+ forceSSL = true;
+ enableACME = true;
+ root = "/etc/nginx/websites/${domain}/${root}";
+ }
+ extraSettings;
+ };
+ virtHosts = builtins.listToAttrs (builtins.map mkVirtHost domains);
+
+ domains = import ./hosts.nix {};
+in {
+ imports = [
+ ./internal_module.nix
+ ];
+
+ options.vhack.nix-sync = {
+ enable = lib.mkEnableOption ''
+ a website git ops solution.
+ '';
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.nix-sync = {
+ enable = true;
+ repositories = nixSyncRepositories;
+ };
+
+ vhack.nginx.enable = true;
+ services.nginx.virtualHosts = virtHosts;
+ };
+}