diff --git a/external-hosts/fairphone/configuration.nix b/external-hosts/fairphone/default.nix similarity index 100% rename from external-hosts/fairphone/configuration.nix rename to external-hosts/fairphone/default.nix diff --git a/modules/nixos/web-services/freshrss.nix b/modules/nixos/web-services/freshrss.nix new file mode 100644 index 0000000..5b73c1f --- /dev/null +++ b/modules/nixos/web-services/freshrss.nix @@ -0,0 +1,59 @@ +{ + config, + self, + lib, + ... +}: +let + cfg = config.custom.web-services.freshrss; + + inherit (config.services.freshrss) dataDir; +in +{ + options.custom.web-services.freshrss = { + enable = lib.mkEnableOption ""; + domain = lib.mkOption { + type = lib.types.nonEmptyStr; + default = ""; + }; + port = lib.mkOption { + type = lib.types.port; + default = 22055; + }; + doBackups = lib.mkEnableOption ""; + }; + + config = lib.mkIf cfg.enable { + assertions = lib.singleton { + assertion = self.lib.isPrivateDomain cfg.domain; + message = self.lib.mkUnprotectedMessage "FreshRSS"; + }; + + services.freshrss = { + enable = true; + baseUrl = "https://${cfg.domain}"; + webserver = "caddy"; + virtualHost = ":${toString cfg.port}"; + defaultUser = "seb"; + authType = "none"; + }; + + custom = { + services = { + caddy.virtualHosts.${cfg.domain}.port = cfg.port; + + restic.backups.freshrss = lib.mkIf cfg.doBackups { + conflictingService = "freshrss-updater.service"; + paths = [ dataDir ]; + }; + }; + + persistence.directories = [ dataDir ]; + + meta.sites.${cfg.domain} = { + title = "FreshRSS"; + icon = "sh:freshrss"; + }; + }; + }; +} diff --git a/modules/nixos/web-services/gatus.nix b/modules/nixos/web-services/gatus.nix new file mode 100644 index 0000000..1779501 --- /dev/null +++ b/modules/nixos/web-services/gatus.nix @@ -0,0 +1,220 @@ +{ + config, + lib, + allHosts, + ... +}: +let + cfg = config.custom.web-services.gatus; + dataDir = "/var/lib/gatus"; +in +{ + options.custom.web-services.gatus = { + enable = lib.mkEnableOption ""; + domain = lib.mkOption { + type = lib.types.nonEmptyStr; + default = ""; + }; + port = lib.mkOption { + type = lib.types.port; + default = 8080; + }; + alerts = { + enable = lib.mkEnableOption ""; + ntfyUrl = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "https://${config.custom.web-services.ntfy.domain}"; + }; + }; + generateDefaultEndpoints = lib.mkEnableOption ""; + endpoints = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + options = { + enable = lib.mkEnableOption "" // { + default = true; + }; + name = lib.mkOption { + type = lib.types.nonEmptyStr; + default = name; + }; + group = lib.mkOption { + type = lib.types.nonEmptyStr; + default = ""; + }; + protocol = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "https"; + }; + domain = lib.mkOption { + type = lib.types.nonEmptyStr; + default = ""; + }; + path = lib.mkOption { + type = lib.types.str; + default = ""; + }; + interval = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "30s"; + }; + extraConditions = lib.mkOption { + type = lib.types.listOf lib.types.nonEmptyStr; + default = [ ]; + }; + enableAlerts = lib.mkEnableOption "" // { + default = cfg.alerts.enable; + }; + }; + } + ) + ); + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + sops = lib.mkIf cfg.alerts.enable { + secrets."healthchecks/ping-key" = { }; + templates."gatus.env" = { + content = "HEALTHCHECKS_PING_KEY=${config.sops.placeholder."healthchecks/ping-key"}"; + owner = config.users.users.gatus.name; + restartUnits = [ "gatus.service" ]; + }; + }; + + users = { + users.gatus = { + isSystemUser = true; + group = config.users.groups.gatus.name; + }; + groups.gatus = { }; + }; + + systemd.services.gatus.serviceConfig = { + DynamicUser = lib.mkForce false; + ProtectSystem = "strict"; + ProtectHome = "read-only"; + PrivateTmp = true; + RemoveIPC = true; + }; + + services.gatus = { + enable = true; + environmentFile = lib.mkIf cfg.alerts.enable config.sops.templates."gatus.env".path; + + settings = { + web = { + address = "localhost"; + inherit (cfg) port; + }; + storage = { + type = "sqlite"; + path = "${dataDir}/data.db"; + }; + connectivity.checker.target = "1.1.1.1:53"; # Cloudflare DNS + alerting.ntfy = lib.mkIf cfg.alerts.enable { + topic = "uptime"; + url = cfg.ntfyUrl; + click = "https://${cfg.domain}"; + default-alert = { + enable = true; + failure-threshold = 8; + success-threshold = 4; + send-on-resolved = true; + }; + overrides = lib.singleton { + group = config.networking.hostName; + topic = "splitleaf"; + url = "https://ntfy.sh"; + default-alert = { + failure-threshold = 4; + success-threshold = 2; + }; + }; + }; + ui.default-sort-by = "group"; + maintenance = { + start = "03:00"; + duration = "1h"; + timezone = "Europe/Berlin"; + }; + + endpoints = + let + mkEndpoint = endpoint: { + inherit (endpoint) name group interval; + url = "${endpoint.protocol}://${endpoint.domain}${endpoint.path}"; + alerts = lib.mkIf endpoint.enableAlerts [ { type = "ntfy"; } ]; + ssh = lib.mkIf (endpoint.protocol == "ssh") { + username = ""; + password = ""; + }; + conditions = lib.concatLists [ + endpoint.extraConditions + (lib.optional (lib.elem endpoint.protocol [ + "http" + "https" + ]) "[STATUS] == 200") + (lib.optional (lib.elem endpoint.protocol [ + "tcp" + "ssh" + "icmp" + ]) "[CONNECTED] == true") + ]; + }; + in + cfg.endpoints |> lib.attrValues |> lib.filter (endpoint: endpoint.enable) |> lib.map mkEndpoint; + }; + }; + + systemd.services.gatus.environment.GATUS_DELAY_START_SECONDS = "5"; + + custom = { + web-services.gatus.endpoints = + let + defaultEndpoints = + allHosts + |> lib.mapAttrs ( + _: host: + host.config.custom.services.caddy.virtualHosts |> lib.attrValues |> lib.map (vHost: vHost.domain) + ) + |> lib.concatMapAttrs ( + hostName: domains: + domains + |> lib.filter (domain: domain != cfg.domain) + |> lib.map ( + domain: + lib.nameValuePair domain { + inherit domain; + group = hostName; + } + ) + |> lib.listToAttrs + ); + in + lib.mkIf cfg.generateDefaultEndpoints ( + defaultEndpoints + // { + "healthchecks.io" = lib.mkIf cfg.alerts.enable { + group = "external"; + domain = "hc-ping.com"; + path = "/\${HEALTHCHECKS_PING_KEY}/${config.networking.hostName}-gatus-uptime?create=1"; + interval = "2h"; + }; + } + ); + + services.caddy.virtualHosts.${cfg.domain}.port = cfg.port; + + persistence.directories = [ dataDir ]; + + meta.sites.${cfg.domain} = { + title = "Gatus"; + icon = "sh:gatus"; + }; + }; + }; +} diff --git a/modules/nixos/web-services/stirling-pdf.nix b/modules/nixos/web-services/stirling-pdf.nix new file mode 100644 index 0000000..269e6e1 --- /dev/null +++ b/modules/nixos/web-services/stirling-pdf.nix @@ -0,0 +1,52 @@ +{ config, lib, ... }: +let + cfg = config.custom.web-services.stirling-pdf; +in +{ + options.custom.web-services.stirling-pdf = { + enable = lib.mkEnableOption ""; + domain = lib.mkOption { + type = lib.types.nonEmptyStr; + default = ""; + }; + port = lib.mkOption { + type = lib.types.port; + default = 56191; + }; + branding = { + name = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "Stirling PDF"; + }; + description = lib.mkOption { + type = lib.types.nonEmptyStr; + default = "Your locally hosted one-stop-shop for all your PDF needs."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.stirling-pdf = { + enable = true; + environment = { + SERVER_ADDRESS = "127.0.0.1"; + SERVER_PORT = cfg.port; + SYSTEM_ENABLEANALYTICS = "false"; + LANGS = "de_DE"; + + UI_APPNAME = cfg.branding.name; + UI_APPNAVBARNAME = cfg.branding.name; + UI_HOMEDESCRIPTION = cfg.branding.description; + }; + }; + + custom = { + services.caddy.virtualHosts.${cfg.domain}.port = cfg.port; + + meta.sites.${cfg.domain} = { + title = "Stirling PDF"; + icon = "sh:stirling-pdf"; + }; + }; + }; +}