diff --git a/flake.nix b/flake.nix index d747be5..34aeb3b 100644 --- a/flake.nix +++ b/flake.nix @@ -111,7 +111,8 @@ devShells.ci = pkgs.mkShell { buildInputs = with pkgs; [ nodejs ]; }; }; - flake = let + flake = + let username = "barkeeper"; in { diff --git a/hosts/default.nix b/hosts/default.nix index 764662d..f52214b 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -44,15 +44,15 @@ }; nachtigall-test = { - imports = [ - self.inputs.agenix.nixosModules.default - self.nixosModules.home-manager - ./nachtigall/test-vm.nix - self.nixosModules.overlays - self.nixosModules.core - self.nixosModules.docker - ]; - }; + imports = [ + self.inputs.agenix.nixosModules.default + self.nixosModules.home-manager + ./nachtigall/test-vm.nix + self.nixosModules.overlays + self.nixosModules.core + self.nixosModules.docker + ]; + }; flora-6 = self.nixos-flake.lib.mkLinuxSystem { imports = [ diff --git a/hosts/nachtigall/test-vm.nix b/hosts/nachtigall/test-vm.nix index 038df61..ff0e4dc 100644 --- a/hosts/nachtigall/test-vm.nix +++ b/hosts/nachtigall/test-vm.nix @@ -1,40 +1,39 @@ { flake, lib, ... }: { - imports = - [ - ./backups.nix - ./apps/nginx.nix + imports = [ + ./backups.nix + ./apps/nginx.nix - ./apps/collabora.nix - ./apps/coturn.nix - ./apps/forgejo.nix - ./apps/keycloak.nix - ./apps/mailman.nix - ./apps/mastodon.nix - ./apps/mediawiki.nix - ./apps/nextcloud.nix - ./apps/nginx-mastodon.nix - ./apps/nginx-mastodon-files.nix - ./apps/nginx-prometheus-exporters.nix - ./apps/nginx-website.nix - ./apps/nginx-website-miom.nix - ./apps/opensearch.nix - ./apps/owncast.nix - ./apps/postgresql.nix - ./apps/prometheus-exporters.nix - ./apps/promtail.nix - ./apps/searx.nix - ./apps/tmate.nix + ./apps/collabora.nix + ./apps/coturn.nix + ./apps/forgejo.nix + ./apps/keycloak.nix + ./apps/mailman.nix + ./apps/mastodon.nix + ./apps/mediawiki.nix + ./apps/nextcloud.nix + ./apps/nginx-mastodon.nix + ./apps/nginx-mastodon-files.nix + ./apps/nginx-prometheus-exporters.nix + ./apps/nginx-website.nix + ./apps/nginx-website-miom.nix + ./apps/opensearch.nix + ./apps/owncast.nix + ./apps/postgresql.nix + ./apps/prometheus-exporters.nix + ./apps/promtail.nix + ./apps/searx.nix + ./apps/tmate.nix - ./apps/matrix/irc.nix - ./apps/matrix/mautrix-telegram.nix - ./apps/matrix/synapse.nix - ./apps/nginx-matrix.nix - ]; + ./apps/matrix/irc.nix + ./apps/matrix/mautrix-telegram.nix + ./apps/matrix/synapse.nix + ./apps/nginx-matrix.nix + ]; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - + security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; security.acme.preliminarySelfsigned = true; diff --git a/lib/default.nix b/lib/default.nix index e839748..3f14bf6 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -7,7 +7,8 @@ { # Configuration common to all Linux systems flake = { - lib = let + lib = + let callLibs = file: import file { inherit lib; }; in rec { diff --git a/modules/backups/default.nix b/modules/backups/default.nix index f3695af..996392c 100644 --- a/modules/backups/default.nix +++ b/modules/backups/default.nix @@ -4,11 +4,17 @@ lib, pkgs, ... -}: let - utils = import "${flake.inputs.nixpkgs}/nixos/lib/utils.nix" { inherit lib; inherit config; inherit pkgs; }; +}: +let + utils = import "${flake.inputs.nixpkgs}/nixos/lib/utils.nix" { + inherit lib; + inherit config; + inherit pkgs; + }; # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers" inherit (utils.systemdUtils.unitOptions) unitOption; -in { +in +{ options.pub-solar-os.backups = { stores = with lib; diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index 226c278..5cd5f2d 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -59,9 +59,7 @@ "pub.solar" = flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar; }; - plugins = [ - flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener - ]; + plugins = [ flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener ]; }; pub-solar-os.backups.backups.keycloak = { diff --git a/modules/keycloak/keycloak.nix b/modules/keycloak/keycloak.nix index 98901a6..283f98c 100644 --- a/modules/keycloak/keycloak.nix +++ b/modules/keycloak/keycloak.nix @@ -1,4 +1,10 @@ -{ config, options, pkgs, lib, ... }: +{ + config, + options, + pkgs, + lib, + ... +}: let cfg = config.services.keycloak; @@ -40,35 +46,79 @@ let prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}"; in { - imports = - [ - (mkRenamedOptionModule - [ "services" "keycloak" "bindAddress" ] - [ "services" "keycloak" "settings" "http-host" ]) - (mkRenamedOptionModule - [ "services" "keycloak" "forceBackendUrlToFrontendUrl"] - [ "services" "keycloak" "settings" "hostname-strict-backchannel"]) - (mkChangedOptionModule - [ "services" "keycloak" "httpPort" ] - [ "services" "keycloak" "settings" "http-port" ] - (config: - builtins.fromJSON config.services.keycloak.httpPort)) - (mkChangedOptionModule - [ "services" "keycloak" "httpsPort" ] - [ "services" "keycloak" "settings" "https-port" ] - (config: - builtins.fromJSON config.services.keycloak.httpsPort)) - (mkRemovedOptionModule - [ "services" "keycloak" "frontendUrl" ] - '' - Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. - NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. - See its description for more information. - '') - (mkRemovedOptionModule - [ "services" "keycloak" "extraConfig" ] - "Use `services.keycloak.settings' instead.") - ]; + imports = [ + (mkRenamedOptionModule + [ + "services" + "keycloak" + "bindAddress" + ] + [ + "services" + "keycloak" + "settings" + "http-host" + ] + ) + (mkRenamedOptionModule + [ + "services" + "keycloak" + "forceBackendUrlToFrontendUrl" + ] + [ + "services" + "keycloak" + "settings" + "hostname-strict-backchannel" + ] + ) + (mkChangedOptionModule + [ + "services" + "keycloak" + "httpPort" + ] + [ + "services" + "keycloak" + "settings" + "http-port" + ] + (config: builtins.fromJSON config.services.keycloak.httpPort) + ) + (mkChangedOptionModule + [ + "services" + "keycloak" + "httpsPort" + ] + [ + "services" + "keycloak" + "settings" + "https-port" + ] + (config: builtins.fromJSON config.services.keycloak.httpsPort) + ) + (mkRemovedOptionModule + [ + "services" + "keycloak" + "frontendUrl" + ] + '' + Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. + NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. + See its description for more information. + '' + ) + (mkRemovedOptionModule [ + "services" + "keycloak" + "extraConfig" + ] "Use `services.keycloak.settings' instead.") + ]; options.services.keycloak = let @@ -82,9 +132,11 @@ in path enum package - port; + port + ; - assertStringPath = optionName: value: + assertStringPath = + optionName: value: if isPath value then throw '' services.keycloak.${optionName}: @@ -92,7 +144,8 @@ in is a Nix path, but should be a string, since Nix paths are copied into the world-readable Nix store. '' - else value; + else + value; in { enable = mkOption { @@ -139,7 +192,11 @@ in database = { type = mkOption { - type = enum [ "mysql" "mariadb" "postgresql" ]; + type = enum [ + "mysql" + "mariadb" + "postgresql" + ]; default = "postgresql"; example = "mariadb"; description = '' @@ -286,7 +343,14 @@ in settings = mkOption { type = lib.types.submodule { - freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ])); + freeformType = attrsOf ( + nullOr (oneOf [ + str + int + bool + (attrsOf path) + ]) + ); options = { http-host = mkOption { @@ -365,7 +429,12 @@ in }; proxy = mkOption { - type = enum [ "edge" "reencrypt" "passthrough" "none" ]; + type = enum [ + "edge" + "reencrypt" + "passthrough" + "none" + ]; default = "none"; example = "edge"; description = '' @@ -422,7 +491,12 @@ in # connect to it. databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost"; createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql"; - createLocalMySQL = databaseActuallyCreateLocally && elem cfg.database.type [ "mysql" "mariadb" ]; + createLocalMySQL = + databaseActuallyCreateLocally + && elem cfg.database.type [ + "mysql" + "mariadb" + ]; mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } '' ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt @@ -454,216 +528,242 @@ in fi done - ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)} + ${concatStringsSep "\n" ( + mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes + )} ''; keycloakConfig = lib.generators.toKeyValue { mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { - mkValueString = v: - if isInt v then toString v - else if isString v then v - else if true == v then "true" - else if false == v then "false" - else if isSecret v then hashString "sha256" v._secret - else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + mkValueString = + v: + if isInt v then + toString v + else if isString v then + v + else if true == v then + "true" + else if false == v then + "false" + else if isSecret v then + hashString "sha256" v._secret + else + throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty { }) v}"; }; }; isSecret = v: isAttrs v && v ? _secret && isString v._secret; - filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{ } null])) cfg.settings; + filteredConfig = lib.converge (lib.filterAttrsRecursive ( + _: v: + !elem v [ + { } + null + ] + )) cfg.settings; confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig); keycloakBuild = cfg.package.override { inherit confFile; - plugins = cfg.package.enabledPlugins ++ cfg.plugins ++ - (with cfg.package.plugins; [quarkus-systemd-notify quarkus-systemd-notify-deployment]); + plugins = + cfg.package.enabledPlugins + ++ cfg.plugins + ++ (with cfg.package.plugins; [ + quarkus-systemd-notify + quarkus-systemd-notify-deployment + ]); }; in - mkIf cfg.enable - { - assertions = [ - { - assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); - message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL"; - } - { - assertion = createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true; - message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; - } - { - assertion = cfg.settings.hostname != null || ! cfg.settings.hostname-strict or true; - message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`"; - } - { - assertion = cfg.settings.hostname-url or null == null; - message = '' - The option `services.keycloak.settings.hostname-url' has been removed. - Set `services.keycloak.settings.hostname' instead. - See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. - ''; - } - { - assertion = cfg.settings.hostname-strict-backchannel or null == null; - message = '' - The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. - Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. - See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. - ''; - } - ]; + mkIf cfg.enable { + assertions = [ + { + assertion = + (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); + message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL"; + } + { + assertion = + createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true; + message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; + } + { + assertion = cfg.settings.hostname != null || !cfg.settings.hostname-strict or true; + message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`"; + } + { + assertion = cfg.settings.hostname-url or null == null; + message = '' + The option `services.keycloak.settings.hostname-url' has been removed. + Set `services.keycloak.settings.hostname' instead. + See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. + ''; + } + { + assertion = cfg.settings.hostname-strict-backchannel or null == null; + message = '' + The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. + Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. + See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. + ''; + } + ]; - environment.systemPackages = [ keycloakBuild ]; + environment.systemPackages = [ keycloakBuild ]; - services.keycloak.settings = - let - postgresParams = concatStringsSep "&" ( - optionals cfg.database.useSSL [ - "ssl=true" - ] ++ optionals (cfg.database.caCert != null) [ - "sslrootcert=${cfg.database.caCert}" - "sslmode=verify-ca" - ] - ); - mariadbParams = concatStringsSep "&" ([ - "characterEncoding=UTF-8" - ] ++ optionals cfg.database.useSSL [ + services.keycloak.settings = + let + postgresParams = concatStringsSep "&" ( + optionals cfg.database.useSSL [ "ssl=true" ] + ++ optionals (cfg.database.caCert != null) [ + "sslrootcert=${cfg.database.caCert}" + "sslmode=verify-ca" + ] + ); + mariadbParams = concatStringsSep "&" ( + [ "characterEncoding=UTF-8" ] + ++ optionals cfg.database.useSSL [ "useSSL=true" "requireSSL=true" "verifyServerCertificate=true" - ] ++ optionals (cfg.database.caCert != null) [ + ] + ++ optionals (cfg.database.caCert != null) [ "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}" "trustCertificateKeyStorePassword=notsosecretpassword" - ]); - dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; - in - mkMerge [ - { - db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type; - db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; - db-password._secret = cfg.database.passwordFile; - db-url-host = cfg.database.host; - db-url-port = toString cfg.database.port; - db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; - db-url-properties = prefixUnlessEmpty "?" dbProps; - db-url = null; - } - (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { - https-certificate-file = "/run/keycloak/ssl/ssl_cert"; - https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; - }) - ]; + ] + ); + dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; + in + mkMerge [ + { + db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type; + db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; + db-password._secret = cfg.database.passwordFile; + db-url-host = cfg.database.host; + db-url-port = toString cfg.database.port; + db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; + db-url-properties = prefixUnlessEmpty "?" dbProps; + db-url = null; + } + (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { + https-certificate-file = "/run/keycloak/ssl/ssl_cert"; + https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; + }) + ]; - systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { - after = [ "postgresql.service" ]; - before = [ "keycloak.service" ]; - bindsTo = [ "postgresql.service" ]; - path = [ config.services.postgresql.package ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = "postgres"; - Group = "postgres"; - LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; - }; - script = '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit - - create_role="$(mktemp)" - trap 'rm -f "$create_role"' EXIT - - # Read the password from the credentials directory and - # escape any single quotes by adding additional single - # quotes after them, following the rules laid out here: - # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS - db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" - db_password="''${db_password//\'/\'\'}" - - echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role" - psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" - psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' - ''; + systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { + after = [ "postgresql.service" ]; + before = [ "keycloak.service" ]; + bindsTo = [ "postgresql.service" ]; + path = [ config.services.postgresql.package ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "postgres"; + Group = "postgres"; + LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit - systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { - after = [ "mysql.service" ]; - before = [ "keycloak.service" ]; - bindsTo = [ "mysql.service" ]; - path = [ config.services.mysql.package ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = config.services.mysql.user; - Group = config.services.mysql.group; - LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; - }; - script = '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit + create_role="$(mktemp)" + trap 'rm -f "$create_role"' EXIT - # Read the password from the credentials directory and - # escape any single quotes by adding additional single - # quotes after them, following the rules laid out here: - # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html - db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" - db_password="''${db_password//\'/\'\'}" + # Read the password from the credentials directory and + # escape any single quotes by adding additional single + # quotes after them, following the rules laid out here: + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS + db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" + db_password="''${db_password//\'/\'\'}" - ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" - echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" - echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" - echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" - ) | mysql -N - ''; + echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role" + psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" + psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' + ''; + }; + + systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { + after = [ "mysql.service" ]; + before = [ "keycloak.service" ]; + bindsTo = [ "mysql.service" ]; + path = [ config.services.mysql.package ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = config.services.mysql.user; + Group = config.services.mysql.group; + LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit - systemd.services.keycloak = - let - databaseServices = - if createLocalPostgreSQL then [ + # Read the password from the credentials directory and + # escape any single quotes by adding additional single + # quotes after them, following the rules laid out here: + # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html + db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" + db_password="''${db_password//\'/\'\'}" + + ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" + echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" + echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" + echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" + ) | mysql -N + ''; + }; + + systemd.services.keycloak = + let + databaseServices = + if createLocalPostgreSQL then + [ "keycloakPostgreSQLInit.service" "postgresql.service" ] - else if createLocalMySQL then [ + else if createLocalMySQL then + [ "keycloakMySQLInit.service" "mysql.service" ] - else [ ]; - secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); - mkSecretReplacement = file: '' - replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf - ''; - secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; - extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags; - in - { - after = databaseServices; - bindsTo = databaseServices; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ - keycloakBuild - openssl - replace-secret - ]; - environment = { - KC_HOME_DIR = "/run/keycloak"; - KC_CONF_DIR = "/run/keycloak/conf"; - }; - serviceConfig = { - LoadCredential = - map (p: "${baseNameOf p}:${p}") secretPaths - ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ - "ssl_cert:${cfg.sslCertificate}" - "ssl_key:${cfg.sslCertificateKey}" - ]; - User = "keycloak"; - Group = "keycloak"; - DynamicUser = true; - RuntimeDirectory = "keycloak"; - RuntimeDirectoryMode = "0700"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; - Type = "notify"; # Requires quarkus-systemd-notify plugin - NotifyAccess = "all"; - }; - script = '' + else + [ ]; + secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); + mkSecretReplacement = file: '' + replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf + ''; + secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; + extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags; + in + { + after = databaseServices; + bindsTo = databaseServices; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ + keycloakBuild + openssl + replace-secret + ]; + environment = { + KC_HOME_DIR = "/run/keycloak"; + KC_CONF_DIR = "/run/keycloak/conf"; + }; + serviceConfig = { + LoadCredential = + map (p: "${baseNameOf p}:${p}") secretPaths + ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ + "ssl_cert:${cfg.sslCertificate}" + "ssl_key:${cfg.sslCertificateKey}" + ]; + User = "keycloak"; + Group = "keycloak"; + DynamicUser = true; + RuntimeDirectory = "keycloak"; + RuntimeDirectoryMode = "0700"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + Type = "notify"; # Requires quarkus-systemd-notify plugin + NotifyAccess = "all"; + }; + script = + '' set -o errexit -o pipefail -o nounset -o errtrace shopt -s inherit_errexit @@ -681,24 +781,26 @@ in # sequences. sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf - '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' + '' + + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' mkdir -p /run/keycloak/ssl cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ - '' + '' + '' + + '' export KEYCLOAK_ADMIN=admin export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword} kc.sh --verbose start --optimized ${extraStartupFlags} ''; - }; + }; - services.postgresql.enable = mkDefault createLocalPostgreSQL; - services.mysql.enable = mkDefault createLocalMySQL; - services.mysql.package = - let - dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; - in - mkIf createLocalMySQL (mkDefault dbPkg); - }; + services.postgresql.enable = mkDefault createLocalPostgreSQL; + services.mysql.enable = mkDefault createLocalMySQL; + services.mysql.package = + let + dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; + in + mkIf createLocalMySQL (mkDefault dbPkg); + }; meta.doc = ./keycloak.md; meta.maintainers = [ maintainers.talyz ]; diff --git a/tests/default.nix b/tests/default.nix index 9de3431..08831f1 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -5,15 +5,22 @@ args@{ pkgs, inputs, ... -}: let +}: +let nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; - loadTestFiles = with lib; dir: mapAttrs' (name: _: let - test = ((import (dir + "/${name}")) args); - in { - name = "test-" + (lib.strings.removeSuffix ".nix" name); - value = nixos-lib.runTest test; - }) - (filterAttrs (name: _: (hasSuffix ".nix" name) && name != "default.nix") - (builtins.readDir dir)); -in loadTestFiles ./. + loadTestFiles = + with lib; + dir: + mapAttrs' ( + name: _: + let + test = ((import (dir + "/${name}")) args); + in + { + name = "test-" + (lib.strings.removeSuffix ".nix" name); + value = nixos-lib.runTest test; + } + ) (filterAttrs (name: _: (hasSuffix ".nix" name) && name != "default.nix") (builtins.readDir dir)); +in +loadTestFiles ./. diff --git a/tests/support/client.nix b/tests/support/client.nix index fab5631..43ad235 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -3,10 +3,12 @@ lib, config, ... -}: let - puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) {}); - puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) {}); -in { +}: +let + puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) { }); + puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) { }); +in +{ imports = [ ./global.nix ]; security.polkit.enable = true; @@ -18,9 +20,7 @@ in { services.getty.autologinUser = config.pub-solar-os.authentication.username; - virtualisation.qemu.options = [ - "-vga std" - ]; + virtualisation.qemu.options = [ "-vga std" ]; home-manager.users.${config.pub-solar-os.authentication.username} = { programs.bash.profileExtra = '' @@ -34,9 +34,9 @@ in { ''; config = { modifier = "Mod4"; - terminal = "${pkgs.alacritty}/bin/alacritty"; + terminal = "${pkgs.alacritty}/bin/alacritty"; startup = [ - {command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket";} + { command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket"; } ]; }; }; diff --git a/tests/support/puppeteer-socket/puppeteer-run.nix b/tests/support/puppeteer-socket/puppeteer-run.nix index 296e6e8..f93d9e0 100644 --- a/tests/support/puppeteer-socket/puppeteer-run.nix +++ b/tests/support/puppeteer-socket/puppeteer-run.nix @@ -1,8 +1,6 @@ -{ - writeShellScriptBin, - curl -}: writeShellScriptBin "puppeteer-run" '' -set -e +{ writeShellScriptBin, curl }: +writeShellScriptBin "puppeteer-run" '' + set -e -exec ${curl}/bin/curl --fail-with-body -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket + exec ${curl}/bin/curl --fail-with-body -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket '' diff --git a/tests/support/puppeteer-socket/puppeteer-socket.nix b/tests/support/puppeteer-socket/puppeteer-socket.nix index 5193914..4e93776 100644 --- a/tests/support/puppeteer-socket/puppeteer-socket.nix +++ b/tests/support/puppeteer-socket/puppeteer-socket.nix @@ -1,7 +1,4 @@ -{ - buildNpmPackage, - nodejs, -}: +{ buildNpmPackage, nodejs }: buildNpmPackage rec { src = ./.; name = "puppeteer-socket";