From 0a745b625220f8c6c591c86a44b59a2380487785 Mon Sep 17 00:00:00 2001 From: eljamm Date: Wed, 20 Nov 2024 10:45:05 +0100 Subject: [PATCH] nixos/omnom: init module --- nixos/modules/module-list.nix | 1 + nixos/modules/services/misc/omnom.nix | 281 ++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 nixos/modules/services/misc/omnom.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 362e4db45e2332..8beaad54c5bef7 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -811,6 +811,7 @@ ./services/misc/octoprint.nix ./services/misc/ollama.nix ./services/misc/ombi.nix + ./services/misc/omnom.nix ./services/misc/open-webui.nix ./services/misc/osrm.nix ./services/misc/owncast.nix diff --git a/nixos/modules/services/misc/omnom.nix b/nixos/modules/services/misc/omnom.nix new file mode 100644 index 00000000000000..ffc7e34fe9bae3 --- /dev/null +++ b/nixos/modules/services/misc/omnom.nix @@ -0,0 +1,281 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.omnom; + settingsFormat = pkgs.formats.yaml { }; + + configFile = settingsFormat.generate "omnom-config.yml" cfg.settings; +in +{ + options = { + services.omnom = { + enable = lib.mkEnableOption "Omnom, a webpage bookmarking and snapshotting service"; + package = lib.mkPackageOption pkgs "omnom" { }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/omnom"; + description = "The directory where Omnom stores its data files."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 7331; + description = "The Omnom service port."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open ports in the firewall."; + }; + + user = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "omnom"; + description = "The Omnom service user."; + }; + + group = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "omnom"; + description = "The Omnom service group."; + }; + + secretsFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + YAML file containing all secrets. This needs to be in the same structure as the configuration. + + This must contain the SMTP username and password. + + Example: + ```yaml + smtp: + username: "test" + password: "test" + ``` + ''; + }; + + settings = lib.mkOption { + description = '' + Configuration options for the /etc/omnom/config.yml file. + ''; + type = lib.types.submodule { + freeformType = settingsFormat.type; + options = { + app = { + debug = lib.mkEnableOption "debug mode"; + disable_signup = lib.mkEnableOption "restricting user creation"; + results_per_page = lib.mkOption { + type = lib.types.int; + default = 20; + description = "Number of results per page."; + }; + }; + db = { + connection = lib.mkOption { + type = lib.types.str; + default = "${cfg.dataDir}/db.sqlite3"; + description = "Database connection URI."; + defaultText = lib.literalExpression '' + "''${config.services.omnom.dataDir}/db.sqlite3" + ''; + }; + type = lib.mkOption { + type = lib.types.enum [ "sqlite" ]; + default = "sqlite"; + description = "Database type."; + }; + }; + server = { + address = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1:${toString cfg.port}"; + description = "Server address."; + defaultText = lib.literalExpression '' + "127.0.0.1:''${config.services.omnom.port}" + ''; + }; + secure_cookie = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to limit cookies to a secure channel."; + }; + }; + storage = { + type = lib.mkOption { + type = lib.types.str; + default = "fs"; + description = "Storage type."; + }; + root = lib.mkOption { + type = lib.types.path; + default = "${cfg.dataDir}/static/data"; + defaultText = lib.literalExpression '' + "''${config.services.omnom.dataDir}/static/data" + ''; + description = "Where the snapshots are saved."; + }; + }; + smtp = { + tls = lib.mkEnableOption "Whether TLS encryption should be used."; + tls_allow_insecure = lib.mkEnableOption "Whether to allow insecure TLS."; + host = lib.mkOption { + type = lib.types.str; + default = ""; + description = "SMTP server hostname."; + }; + port = lib.mkOption { + type = lib.types.port; + default = 25; + description = "SMTP server port address."; + }; + sender = lib.mkOption { + type = lib.types.str; + default = "Omnom "; + description = "Omnom sender e-mail."; + }; + send_timeout = lib.mkOption { + type = lib.types.int; + default = 10; + description = "Send timeout duration in seconds."; + }; + connection_timeout = lib.mkOption { + type = lib.types.int; + default = 5; + description = "Connection timeout duration in seconds."; + }; + }; + }; + }; + default = { }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !lib.hasAttr "username" cfg.settings.smtp; + message = '' + `services.omnom.settings.smtp.username` must be defined in `services.omnom.secretsFile`. + ''; + } + { + assertion = !lib.hasAttr "password" cfg.settings.smtp; + message = '' + `services.omnom.settings.smtp.password` must be defined in `services.omnom.secretsFile`. + ''; + } + { + assertion = !(cfg.settings.storage.root != "${cfg.dataDir}/static/data"); + message = '' + For Omnom to access the snapshots, it needs the storage root + directory to be inside the service's working directory. + + As such, `services.omnom.settings.storage.root` must be the same as + `''${services.omnom.dataDir}/static/data`. + ''; + } + ]; + + environment.etc."omnom/config.yml".source = configFile; + + systemd.services.omnom = { + path = with pkgs; [ + yq-go # needed by startup script + ]; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + StateDirectory = "omnom"; + WorkingDirectory = cfg.dataDir; + Restart = "on-failure"; + RestartSec = "10s"; + LoadCredential = lib.mkIf (cfg.secretsFile != null) [ + "SECRETS_FILE:${cfg.secretsFile}" + ]; + }; + script = '' + export CONFIG_FILE=${configFile} + + ${lib.optionalString (cfg.secretsFile != null) '' + # merge secrets into main config + yq eval-all "select(fileIndex == 0) * select(fileIndex == 1)" \ + "${configFile}" "$CREDENTIALS_DIRECTORY/SECRETS_FILE" > "${cfg.dataDir}/config.yml" + + CONFIG_FILE="${cfg.dataDir}/config.yml" + ''} + + ${lib.getExe cfg.package} listen --config "$CONFIG_FILE" + ''; + after = [ + "network.target" + "systemd-tmpfiles-setup.service" + ]; + wantedBy = [ "multi-user.target" ]; + }; + + # TODO: The program needs to run from the dataDir for it the work, which + # is difficult to do with a DynamicUser. + # After this has been fixed upstream, remove this and use DynamicUser, instead. + users = { + users = lib.mkIf (cfg.user == "omnom") { + omnom = { + group = cfg.group; + home = cfg.dataDir; + isSystemUser = true; + }; + }; + groups = lib.mkIf (cfg.group == "omnom") { omnom = { }; }; + }; + + systemd.tmpfiles.settings."10-omnom" = + let + settings = { + inherit (cfg) user group; + }; + in + { + "${cfg.dataDir}"."d" = settings; + "${cfg.dataDir}/templates"."L+" = settings // { + argument = "${cfg.package}/share/templates"; + }; + "${cfg.settings.storage.root}"."d" = settings; + } + // lib.optionalAttrs (cfg.settings.db.type == "sqlite") { + "${cfg.dataDir}/db.sqlite3"."f" = settings; + } + // lib.optionalAttrs (cfg.secretsFile != null) { + "${cfg.dataDir}/config.yml"."f" = settings // { + mode = "0600"; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + + environment.systemPackages = + let + omnom-wrapped = pkgs.writeScriptBin "omnom" '' + #! ${pkgs.runtimeShell} + cd ${cfg.dataDir} + sudo=exec + if [[ "$USER" != ${cfg.user} ]]; then + sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}' + fi + $sudo ${lib.getExe cfg.package} "$@" + ''; + in + [ omnom-wrapped ]; + }; +}