From 385997cdcc11e9ee312114b253a6a80585aaf025 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Tue, 15 Apr 2025 11:15:17 +0200 Subject: [PATCH] feat: :sparkles: Add NixOS module --- flake.nix | 7 +- nix/module.nix | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 nix/module.nix diff --git a/flake.nix b/flake.nix index f7cb72d4..795b2b52 100644 --- a/flake.nix +++ b/flake.nix @@ -31,5 +31,10 @@ inherit (pkgs) versia-server versia-server-worker; default = self.packages.${system}.versia-server; }; - }); + }) + // { + nixosModules = { + versia-server = import ./nix/module.nix; + }; + }; } diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 00000000..3877fdf1 --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,175 @@ +{ + config, + lib, + pkgs, +}: let + cfg = config.services.versia-server; + configFormat = pkgs.formats.toml {}; + name = "versia-server"; + + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf; +in { + options = { + services.versia-server = { + enable = mkOption { + type = lib.types.bool; + default = false; + description = '' + Enable the Versia Server services. + ''; + }; + + dataDir = mkOption { + type = lib.types.path; + default = "/var/lib/${name}"; + description = '' + Directory where the server will store its data. + ''; + }; + + user = mkOption { + type = lib.types.str; + default = name; + description = '' + User under which the server will run. + ''; + }; + + group = mkOption { + type = lib.types.str; + default = name; + description = '' + Group under which the server will run. + ''; + }; + + nodes = { + api = mkOption { + type = lib.types.attrsOf lib.types.submodule { + options = { + configOverrides = mkOption { + type = lib.types.submodule { + freeformType = configFormat.type; + options = {}; + }; + description = "Overrides for the node's configuration file."; + }; + }; + }; + }; + worker = mkOption { + type = lib.types.attrsOf lib.types.submodule { + options = { + configOverrides = mkOption { + type = lib.types.submodule { + freeformType = configFormat.type; + options = {}; + }; + description = "Overrides for the node's configuration file."; + }; + }; + }; + }; + }; + + config = mkOption { + type = lib.types.submodule { + freeformType = configFormat.type; + options = {}; + }; + description = "Contents of the config file. Check the Versia Server documentation for information on its contents."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.nodes.api != []; + message = "At least one API node must be defined."; + } + { + assertion = cfg.nodes.worker != []; + message = "At least one worker node must be defined."; + } + ]; + }; + + systemd.services = + builtins.mapAttrs (nodeName: node: let + config = cfg.config // node.configOverrides; + configFile = builtins.toFile (configFormat.generate "config-${nodeName}" config); + in { + after = ["network-online.target"]; + wantedBy = ["multi-user.target"]; + requires = ["network-online.target"]; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + User = cfg.user; + Group = cfg.group; + + StateDirectory = "${name}"; + StateDirectoryMode = "0700"; + RuntimeDirectory = "${name}"; + RuntimeDirectoryMode = "0700"; + + # Set the working directory to the data directory + WorkingDirectory = cfg.dataDir; + + StandardOutput = "journal"; + StandardError = "journal"; + SyslogIdentifier = "${name}"; + + Environment = [ + "CONFIG_FILE=${configFile}" + ]; + }; + path = [pkgs.versia-server]; + }) (cfg.nodes.api ++ cfg.nodes.worker) + // builtins.mapAttrs (nodeName: node: let + type = "api"; + exe = lib.getExe pkgs.versia-server; + in { + name = "${name}-${type}-${nodeName}"; + description = "Versia Server ${node.name} (${type})"; + + serviceConfig.ExecStart = "${exe}"; + }) (cfg.nodes.api) + // builtins.mapAttrs (nodeName: node: let + type = "worker"; + exe = lib.getExe pkgs.versia-server-worker; + in { + name = "${name}-${type}-${nodeName}"; + description = "Versia Server ${node.name} (${type})"; + + serviceConfig.ExecStart = "${exe}"; + }) (cfg.nodes.worker); + + systemd.tmpfiles.rules = [ + { + # Create the data directory with the correct permissions + line = "d ${cfg.dataDir} - - - - ${cfg.user} ${cfg.group}"; + } + ]; + + users = { + groups = { + "${cfg.group}" = { + description = "Group for the Versia Server"; + }; + }; + + users = { + "${cfg.user}" = { + isSystemUser = true; + group = cfg.group; + home = cfg.dataDir; + packages = [pkgs.versia-server pkgs.versia-server-worker]; + }; + }; + }; +}