diff --git a/hosts/sobotka/server.nix b/hosts/sobotka/server.nix index 33ae668c..c43ded9b 100644 --- a/hosts/sobotka/server.nix +++ b/hosts/sobotka/server.nix @@ -8,6 +8,9 @@ uid = 994; gid = 993; + gitea = { + enable = true; + }; unbound = { enable = true; }; @@ -15,9 +18,6 @@ enable = true; }; homepage-dashboard = { - enable = false; - }; - dashy = { enable = true; }; bazarr = { @@ -67,7 +67,6 @@ podman = { enable = true; gluetun.enable = true; - dashy.enable = true; qbittorrent = { enable = true; port = 8080; diff --git a/modules/default.nix b/modules/default.nix index 959a7f9a..5ff49992 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -124,7 +124,6 @@ ./server/caddy ./server/fail2ban ./server/homepage-dashboard - ./server/dashy ./server/vaultwarden ./server/bazarr ./server/prowlarr @@ -138,6 +137,8 @@ ./server/unbound ./server/uptime-kuma ./server/keepalived + ./server/gitea + ./server/postgres ]; }; settings = { diff --git a/modules/server/dashy/default.nix b/modules/server/dashy/default.nix deleted file mode 100644 index f9bb9ff1..00000000 --- a/modules/server/dashy/default.nix +++ /dev/null @@ -1,151 +0,0 @@ -{ - pkgs, - config, - lib, - ... -}: let - unit = "dashy"; - cfg = config.server.${unit}; - srv = config.server; - - hl = config.server; - mergedServices = hl // (hl.podman or {}); - - dashyCategories = [ - "Arr" - "Media" - "Downloads" - "Services" - "Smart Home" - ]; - - getServicesByCategory = cat: - lib.attrsets.filterAttrs (name: value: (value ? category && value.category == cat)) mergedServices; - - # This function was missing its 'services' argument at the end of the call. - mkItems = services: - lib.attrsets.mapAttrsToList (name: value: { - title = value.name or name; - description = value.description or ""; - url = - if value ? href - then value.href - else if value ? url - then "https://${value.url}${value.path or ""}" - else ""; - icon = value.icon or ""; - }) - services; # <-- FIX: Added the 'services' argument here. - - esc = s: lib.replaceStrings ["\""] ["\\\""] (toString s); - - renderSection = { - name, - icon, - items, - }: '' - - name: "${esc name}" - icon: "${esc icon}" - items: - ${lib.concatStringsSep "\n" ( - lib.lists.map (item: '' - - title: "${esc item.title}" - description: "${esc item.description}" - url: "${esc item.url}" - icon: "${esc item.icon}" - '') - items - )} - ''; -in { - options.server.${unit} = { - enable = lib.mkEnableOption { - description = "Enable ${unit}"; - }; - - configFile = lib.mkOption { - type = lib.types.path; - readOnly = true; - internal = true; - description = "Path to the generated Dashy config file."; - }; - - misc = lib.mkOption { - default = []; - type = lib.types.listOf ( - lib.types.attrsOf ( - lib.types.submodule { - options = { - name = lib.mkOption {type = lib.types.str;}; - description = lib.mkOption { - type = lib.types.str; - default = ""; - }; - href = lib.mkOption {type = lib.types.str;}; - icon = lib.mkOption {type = lib.types.str;}; - }; - } - ) - ); - }; - }; - - config = lib.mkIf cfg.enable { - services.glances.enable = true; - - server.dashy.configFile = pkgs.writeText "conf.yml" '' - pageInfo: - title: "${esc "${srv.domain} Homelab"}" - description: "${esc "Homelab made with NixOS"}" - navLinks: - - title: "GitHub" - path: "https://github.com/cnsta/cnix" - - appConfig: - theme: "material-dark" - layout: "auto" - iconSize: "medium" - language: "en" - statusCheck: true - hideComponents: - hideSettings: false - - sections: - ${lib.concatStringsSep "\n" ( - lib.lists.map ( - cat: - renderSection { - name = cat; - icon = "fas fa-box"; - items = mkItems (getServicesByCategory cat); - } - ) - dashyCategories - )} - ${renderSection { - name = "Misc"; - icon = "fas fa-ellipsis-h"; - items = - lib.lists.map (x: { - title = x.name; - description = x.description or ""; - url = x.href or ""; - icon = x.icon or ""; - }) - cfg.misc; - }} - ${renderSection { - name = "Monitoring"; - icon = "fas fa-monitor-heart-rate"; - items = [ - { - title = "Glances"; - description = "System Monitoring"; - url = "http://localhost:${toString config.services.glances.port}"; - icon = "hl-glances"; - } - ]; - }} - ''; - }; -} diff --git a/modules/server/gitea/default.nix b/modules/server/gitea/default.nix new file mode 100644 index 00000000..b2c2e1e5 --- /dev/null +++ b/modules/server/gitea/default.nix @@ -0,0 +1,112 @@ +# taken from @jtojnar +{ + config, + lib, + ... +}: let + unit = "gitea"; + srv = config.server; + cfg = config.server.${unit}; +in { + options.server.${unit} = { + enable = lib.mkEnableOption { + description = "Enable ${unit}"; + }; + url = lib.mkOption { + type = lib.types.str; + default = "git.${srv.domain}"; + }; + port = lib.mkOption { + type = lib.types.int; + default = 5003; + description = "The port to host Gitea on."; + }; + homepage.name = lib.mkOption { + type = lib.types.str; + default = "Gitea"; + }; + homepage.description = lib.mkOption { + type = lib.types.str; + default = "Git with a cup of tea"; + }; + homepage.icon = lib.mkOption { + type = lib.types.str; + default = "gitea.svg"; + }; + homepage.category = lib.mkOption { + type = lib.types.str; + default = "Services"; + }; + }; + config = lib.mkIf cfg.enable { + services.${unit} = { + enable = true; + appName = "cnix code forge"; + + database = { + type = "postgres"; + socket = "/run/postgresql"; + name = "gitea"; + user = "gitea"; + createDatabase = false; + }; + + lfs = { + enable = true; + }; + + settings = { + cors = { + ENABLED = true; + SCHEME = "https"; + ALLOW_DOMAIN = cfg.url; + }; + log = { + MODE = "console"; + }; + mailer = { + ENABLED = true; + MAILER_TYPE = "sendmail"; + FROM = "noreply+adam@cnst.dev"; + SENDMAIL_PATH = "/run/wrappers/bin/sendmail"; + }; + picture = { + DISABLE_GRAVATAR = true; + }; + repository = { + DEFAULT_BRANCH = "main"; + DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls"; + DISABLE_DOWNLOAD_SOURCE_ARCHIVES = true; + }; + server = { + DOMAIN = cfg.url; + LANDING_PAGE = "explore"; + HTTP_PORT = cfg.port; + ROOT_URL = "https://${cfg.url}/"; + }; + security = { + DISABLE_GIT_HOOKS = false; + }; + service = { + DISABLE_REGISTRATION = true; + }; + session = { + COOKIE_SECURE = true; + }; + }; + }; + + services.caddy.virtualHosts."${cfg.url}" = { + useACMEHost = srv.domain; + extraConfig = '' + reverse_proxy http://127.0.0.1:5003 + ''; + }; + + server.postgresql.databases = [ + { + database = "gitea"; + } + ]; + }; +} diff --git a/modules/server/podman/default.nix b/modules/server/podman/default.nix index fecade3d..1a0cbd08 100644 --- a/modules/server/podman/default.nix +++ b/modules/server/podman/default.nix @@ -1,6 +1,7 @@ { config, lib, + pkgs, ... }: let srv = config.server; @@ -23,18 +24,6 @@ in { options.server.podman = { enable = lib.mkEnableOption "Enables Podman"; gluetun.enable = lib.mkEnableOption "Enables gluetun"; - dashy = { - enable = lib.mkEnableOption "Enable dashy"; - port = lib.mkOption { - type = lib.types.int; - default = 8081; - description = "The port to host Dashy on."; - }; - url = lib.mkOption { - type = lib.types.str; - default = "dashy.${srv.domain}"; - }; - }; qbittorrent = { enable = lib.mkEnableOption "Enable qBittorrent"; @@ -149,15 +138,6 @@ in { }; services.caddy.virtualHosts = lib.mkMerge [ - (lib.mkIf cfg.dashy.enable { - "${cfg.dashy.url}" = { - useACMEHost = srv.domain; - extraConfig = '' - reverse_proxy http://127.0.0.1:${toString cfg.dashy.port} - ''; - }; - }) - (lib.mkIf cfg.qbittorrent.enable { "${cfg.qbittorrent.url}" = { useACMEHost = srv.domain; @@ -216,17 +196,6 @@ in { }; }) - (lib.mkIf cfg.dashy.enable { - dashy = { - image = "lissy93/dashy:latest"; - autoStart = true; - ports = ["${toString cfg.dashy.port}:80"]; - volumes = [ - "${srv.dashy.configFile}:/app/user-data/conf.yml" - ]; - }; - }) - (lib.mkIf cfg.qbittorrent.enable { qbittorrent = { image = "ghcr.io/hotio/qbittorrent:latest"; diff --git a/modules/server/postgres/default.nix b/modules/server/postgres/default.nix new file mode 100644 index 00000000..4d71a49e --- /dev/null +++ b/modules/server/postgres/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./postgres.nix + ./postgres-upgrade.nix + ]; +} diff --git a/modules/server/postgres/postgres-upgrade.nix b/modules/server/postgres/postgres-upgrade.nix new file mode 100644 index 00000000..addb6bd8 --- /dev/null +++ b/modules/server/postgres/postgres-upgrade.nix @@ -0,0 +1,48 @@ +# taken from @jtojnar +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib) types mkOption; + + cfg = config.server.postgresql; +in { + options = { + server.postgresql = { + upgradeTargetPackage = mkOption { + type = types.nullOr types.package; + default = null; + description = "PostgreSQL package that we want to upgrade to. When set, an update script will be installed."; + }; + }; + }; + + config = { + # https://nixos.org/manual/nixos/unstable/#module-services-postgres-upgrading + environment.systemPackages = lib.mkIf (cfg.upgradeTargetPackage != null) [ + (pkgs.writeScriptBin "upgrade-pg-cluster" '' + set -eux + # XXX it's perhaps advisable to stop all services that depend on postgresql + systemctl stop postgresql + + export NEWDATA="/var/lib/postgresql/${cfg.upgradeTargetPackage.psqlSchema}" + + export NEWBIN="${cfg.upgradeTargetPackage}/bin" + + export OLDDATA="${config.services.postgresql.dataDir}" + export OLDBIN="${config.services.postgresql.package}/bin" + + install -d -m 0700 -o postgres -g postgres "$NEWDATA" + cd "$NEWDATA" + sudo -u postgres $NEWBIN/initdb -D "$NEWDATA" + + sudo -u postgres $NEWBIN/pg_upgrade \ + --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \ + --old-bindir $OLDBIN --new-bindir $NEWBIN \ + "$@" + '') + ]; + }; +} diff --git a/modules/server/postgres/postgres.nix b/modules/server/postgres/postgres.nix new file mode 100644 index 00000000..ed6f5023 --- /dev/null +++ b/modules/server/postgres/postgres.nix @@ -0,0 +1,139 @@ +# taken from @jtojnar +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib) types mkOption; + + cfg = config.server.postgresql; + + database = {name, ...}: { + options = { + database = mkOption { + type = types.str; + description = "Database name"; + }; + + extraUsers = mkOption { + type = types.listOf types.str; + default = []; + description = "List of extra users with access to this database."; + }; + + extensions = mkOption { + type = types.listOf types.str; + default = []; + description = "List of extensions to install and enable."; + }; + }; + }; +in { + options = { + server.postgresql = { + databases = mkOption { + type = types.listOf (types.submodule database); + default = []; + description = "List of databases to set up."; + }; + }; + }; + + config = lib.mkIf (cfg.databases != []) { + services.postgresql = { + enable = true; + package = pkgs.postgresql_17; + extensions = lib.filter (x: x != null) ( + lib.concatMap ( + {extensions, ...}: map (ext: config.services.postgresql.package.pkgs.${ext} or null) extensions + ) + cfg.databases + ); + authentication = lib.mkForce '' + local all postgres peer + local sameuser all peer + + # extra users + ${lib.concatMapStringsSep "\n" ( + { + database, + extraUsers, + ... + }: + lib.concatMapStringsSep "\n" (user: "local ${database} ${user} peer") extraUsers + ) + cfg.databases} + ''; + ensureUsers = let + dbToUsers = { + database, + extraUsers, + ... + }: + # we use same username as dbname + [database] ++ extraUsers; + in + map (name: {inherit name;}) (lib.unique (builtins.concatMap dbToUsers cfg.databases)); + }; + + systemd.services = { + postgres-setup = let + pgsql = config.services.postgresql; + in { + after = ["postgresql.service"]; + wantedBy = ["multi-user.target"]; + path = [pgsql.package]; + script = + lib.concatMapStringsSep "\n" ( + { + database, + extensions, + extraUsers, + ... + }: let + createExtensionsSql = + lib.concatMapStringsSep "; " ( + ext: ''CREATE EXTENSION IF NOT EXISTS "${ext}"'' + ) + extensions; + createExtensionsIfAny = lib.optionalString (extensions != []) '' + $PSQL -d '${database}' -c '${createExtensionsSql}' + ''; + in '' + set -eu + + PSQL="${pkgs.util-linux}/bin/runuser -u ${pgsql.superUser} -- psql --port=${toString pgsql.settings.port} --tuples-only --no-align" + + if ! $PSQL -c "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep --quiet 1; then + $PSQL -c 'CREATE DATABASE "${database}" WITH OWNER = "${database}"' + ${createExtensionsIfAny} + fi + ${ + lib.optionalString (extraUsers != []) + "$PSQL '${database}' -c '${ + lib.concatMapStringsSep "\n" ( + user: "GRANT ALL ON ALL TABLES IN SCHEMA public TO ${user};" + ) + extraUsers + }'" + } + '' + ) + cfg.databases; + + serviceConfig = { + Type = "oneshot"; + }; + }; + + postgresql.serviceConfig = { + # Required by PLV8. + MemoryDenyWriteExecute = false; + SystemCallFilter = [ + "@pkey" + ]; + }; + }; + }; +}