WIP: feat/automated-account-deletion #174

Draft
b12f wants to merge 23 commits from feat/automated-account-deletion into main
11 changed files with 410 additions and 301 deletions
Showing only changes of commit 3bc699fccf - Show all commits

View file

@ -111,7 +111,8 @@
devShells.ci = pkgs.mkShell { buildInputs = with pkgs; [ nodejs ]; }; devShells.ci = pkgs.mkShell { buildInputs = with pkgs; [ nodejs ]; };
}; };
flake = let flake =
let
username = "barkeeper"; username = "barkeeper";
in in
{ {

View file

@ -44,15 +44,15 @@
}; };
nachtigall-test = { nachtigall-test = {
imports = [ imports = [
self.inputs.agenix.nixosModules.default self.inputs.agenix.nixosModules.default
self.nixosModules.home-manager self.nixosModules.home-manager
./nachtigall/test-vm.nix ./nachtigall/test-vm.nix
self.nixosModules.overlays self.nixosModules.overlays
self.nixosModules.core self.nixosModules.core
self.nixosModules.docker self.nixosModules.docker
]; ];
}; };
flora-6 = self.nixos-flake.lib.mkLinuxSystem { flora-6 = self.nixos-flake.lib.mkLinuxSystem {
imports = [ imports = [

View file

@ -1,40 +1,39 @@
{ flake, lib, ... }: { flake, lib, ... }:
{ {
imports = imports = [
[ ./backups.nix
./backups.nix ./apps/nginx.nix
./apps/nginx.nix
./apps/collabora.nix ./apps/collabora.nix
./apps/coturn.nix ./apps/coturn.nix
./apps/forgejo.nix ./apps/forgejo.nix
./apps/keycloak.nix ./apps/keycloak.nix
./apps/mailman.nix ./apps/mailman.nix
./apps/mastodon.nix ./apps/mastodon.nix
./apps/mediawiki.nix ./apps/mediawiki.nix
./apps/nextcloud.nix ./apps/nextcloud.nix
./apps/nginx-mastodon.nix ./apps/nginx-mastodon.nix
./apps/nginx-mastodon-files.nix ./apps/nginx-mastodon-files.nix
./apps/nginx-prometheus-exporters.nix ./apps/nginx-prometheus-exporters.nix
./apps/nginx-website.nix ./apps/nginx-website.nix
./apps/nginx-website-miom.nix ./apps/nginx-website-miom.nix
./apps/opensearch.nix ./apps/opensearch.nix
./apps/owncast.nix ./apps/owncast.nix
./apps/postgresql.nix ./apps/postgresql.nix
./apps/prometheus-exporters.nix ./apps/prometheus-exporters.nix
./apps/promtail.nix ./apps/promtail.nix
./apps/searx.nix ./apps/searx.nix
./apps/tmate.nix ./apps/tmate.nix
./apps/matrix/irc.nix ./apps/matrix/irc.nix
./apps/matrix/mautrix-telegram.nix ./apps/matrix/mautrix-telegram.nix
./apps/matrix/synapse.nix ./apps/matrix/synapse.nix
./apps/nginx-matrix.nix ./apps/nginx-matrix.nix
]; ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory";
security.acme.preliminarySelfsigned = true; security.acme.preliminarySelfsigned = true;

View file

@ -7,7 +7,8 @@
{ {
# Configuration common to all Linux systems # Configuration common to all Linux systems
flake = { flake = {
lib = let lib =
let
callLibs = file: import file { inherit lib; }; callLibs = file: import file { inherit lib; };
in in
rec { rec {

View file

@ -4,11 +4,17 @@
lib, lib,
pkgs, 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" # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers"
inherit (utils.systemdUtils.unitOptions) unitOption; inherit (utils.systemdUtils.unitOptions) unitOption;
in { in
{
options.pub-solar-os.backups = { options.pub-solar-os.backups = {
stores = stores =
with lib; with lib;

View file

@ -59,9 +59,7 @@
"pub.solar" = "pub.solar" =
flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar; flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar;
}; };
plugins = [ plugins = [ flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener ];
flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener
];
}; };
pub-solar-os.backups.backups.keycloak = { pub-solar-os.backups.backups.keycloak = {

View file

@ -1,4 +1,10 @@
{ config, options, pkgs, lib, ... }: {
config,
options,
pkgs,
lib,
...
}:
let let
cfg = config.services.keycloak; cfg = config.services.keycloak;
@ -40,35 +46,79 @@ let
prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}"; prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}";
in in
{ {
imports = imports = [
[ (mkRenamedOptionModule
(mkRenamedOptionModule [
[ "services" "keycloak" "bindAddress" ] "services"
[ "services" "keycloak" "settings" "http-host" ]) "keycloak"
(mkRenamedOptionModule "bindAddress"
[ "services" "keycloak" "forceBackendUrlToFrontendUrl"] ]
[ "services" "keycloak" "settings" "hostname-strict-backchannel"]) [
(mkChangedOptionModule "services"
[ "services" "keycloak" "httpPort" ] "keycloak"
[ "services" "keycloak" "settings" "http-port" ] "settings"
(config: "http-host"
builtins.fromJSON config.services.keycloak.httpPort)) ]
(mkChangedOptionModule )
[ "services" "keycloak" "httpsPort" ] (mkRenamedOptionModule
[ "services" "keycloak" "settings" "https-port" ] [
(config: "services"
builtins.fromJSON config.services.keycloak.httpsPort)) "keycloak"
(mkRemovedOptionModule "forceBackendUrlToFrontendUrl"
[ "services" "keycloak" "frontendUrl" ] ]
'' [
Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. "services"
NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. "keycloak"
See its description for more information. "settings"
'') "hostname-strict-backchannel"
(mkRemovedOptionModule ]
[ "services" "keycloak" "extraConfig" ] )
"Use `services.keycloak.settings' instead.") (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 = options.services.keycloak =
let let
@ -82,9 +132,11 @@ in
path path
enum enum
package package
port; port
;
assertStringPath = optionName: value: assertStringPath =
optionName: value:
if isPath value then if isPath value then
throw '' throw ''
services.keycloak.${optionName}: services.keycloak.${optionName}:
@ -92,7 +144,8 @@ in
is a Nix path, but should be a string, since Nix is a Nix path, but should be a string, since Nix
paths are copied into the world-readable Nix store. paths are copied into the world-readable Nix store.
'' ''
else value; else
value;
in in
{ {
enable = mkOption { enable = mkOption {
@ -139,7 +192,11 @@ in
database = { database = {
type = mkOption { type = mkOption {
type = enum [ "mysql" "mariadb" "postgresql" ]; type = enum [
"mysql"
"mariadb"
"postgresql"
];
default = "postgresql"; default = "postgresql";
example = "mariadb"; example = "mariadb";
description = '' description = ''
@ -286,7 +343,14 @@ in
settings = mkOption { settings = mkOption {
type = lib.types.submodule { type = lib.types.submodule {
freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ])); freeformType = attrsOf (
nullOr (oneOf [
str
int
bool
(attrsOf path)
])
);
options = { options = {
http-host = mkOption { http-host = mkOption {
@ -365,7 +429,12 @@ in
}; };
proxy = mkOption { proxy = mkOption {
type = enum [ "edge" "reencrypt" "passthrough" "none" ]; type = enum [
"edge"
"reencrypt"
"passthrough"
"none"
];
default = "none"; default = "none";
example = "edge"; example = "edge";
description = '' description = ''
@ -422,7 +491,12 @@ in
# connect to it. # connect to it.
databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost"; databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost";
createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql"; 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" { } '' mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } ''
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
@ -454,216 +528,242 @@ in
fi fi
done 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 { keycloakConfig = lib.generators.toKeyValue {
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
mkValueString = v: mkValueString =
if isInt v then toString v v:
else if isString v then v if isInt v then
else if true == v then "true" toString v
else if false == v then "false" else if isString v then
else if isSecret v then hashString "sha256" v._secret v
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) 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; 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); confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig);
keycloakBuild = cfg.package.override { keycloakBuild = cfg.package.override {
inherit confFile; inherit confFile;
plugins = cfg.package.enabledPlugins ++ cfg.plugins ++ plugins =
(with cfg.package.plugins; [quarkus-systemd-notify quarkus-systemd-notify-deployment]); cfg.package.enabledPlugins
++ cfg.plugins
++ (with cfg.package.plugins; [
quarkus-systemd-notify
quarkus-systemd-notify-deployment
]);
}; };
in in
mkIf cfg.enable mkIf cfg.enable {
{ assertions = [
assertions = [ {
{ assertion =
assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); (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"; 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; assertion =
message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; 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 != 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 = '' assertion = cfg.settings.hostname-url or null == null;
The option `services.keycloak.settings.hostname-url' has been removed. message = ''
Set `services.keycloak.settings.hostname' instead. The option `services.keycloak.settings.hostname-url' has been removed.
See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. 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 = '' assertion = cfg.settings.hostname-strict-backchannel or null == null;
The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. message = ''
Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. The option `services.keycloak.settings.hostname-strict-backchannel' has been removed.
See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. 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 = services.keycloak.settings =
let let
postgresParams = concatStringsSep "&" ( postgresParams = concatStringsSep "&" (
optionals cfg.database.useSSL [ optionals cfg.database.useSSL [ "ssl=true" ]
"ssl=true" ++ optionals (cfg.database.caCert != null) [
] ++ optionals (cfg.database.caCert != null) [ "sslrootcert=${cfg.database.caCert}"
"sslrootcert=${cfg.database.caCert}" "sslmode=verify-ca"
"sslmode=verify-ca" ]
] );
); mariadbParams = concatStringsSep "&" (
mariadbParams = concatStringsSep "&" ([ [ "characterEncoding=UTF-8" ]
"characterEncoding=UTF-8" ++ optionals cfg.database.useSSL [
] ++ optionals cfg.database.useSSL [
"useSSL=true" "useSSL=true"
"requireSSL=true" "requireSSL=true"
"verifyServerCertificate=true" "verifyServerCertificate=true"
] ++ optionals (cfg.database.caCert != null) [ ]
++ optionals (cfg.database.caCert != null) [
"trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}" "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}"
"trustCertificateKeyStorePassword=notsosecretpassword" "trustCertificateKeyStorePassword=notsosecretpassword"
]); ]
dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; );
in dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams;
mkMerge [ 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 = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type;
db-password._secret = cfg.database.passwordFile; db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
db-url-host = cfg.database.host; db-password._secret = cfg.database.passwordFile;
db-url-port = toString cfg.database.port; db-url-host = cfg.database.host;
db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; db-url-port = toString cfg.database.port;
db-url-properties = prefixUnlessEmpty "?" dbProps; db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name;
db-url = null; db-url-properties = prefixUnlessEmpty "?" dbProps;
} db-url = null;
(mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { }
https-certificate-file = "/run/keycloak/ssl/ssl_cert"; (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; https-certificate-file = "/run/keycloak/ssl/ssl_cert";
}) https-certificate-key-file = "/run/keycloak/ssl/ssl_key";
]; })
];
systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL {
after = [ "postgresql.service" ]; after = [ "postgresql.service" ];
before = [ "keycloak.service" ]; before = [ "keycloak.service" ];
bindsTo = [ "postgresql.service" ]; bindsTo = [ "postgresql.service" ];
path = [ config.services.postgresql.package ]; path = [ config.services.postgresql.package ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
User = "postgres"; User = "postgres";
Group = "postgres"; Group = "postgres";
LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; 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"'
'';
}; };
script = ''
set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit
systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { create_role="$(mktemp)"
after = [ "mysql.service" ]; trap 'rm -f "$create_role"' EXIT
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
# Read the password from the credentials directory and # Read the password from the credentials directory and
# escape any single quotes by adding additional single # escape any single quotes by adding additional single
# quotes after them, following the rules laid out here: # quotes after them, following the rules laid out here:
# https://dev.mysql.com/doc/refman/8.0/en/string-literals.html # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
db_password="''${db_password//\'/\'\'}" db_password="''${db_password//\'/\'\'}"
( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role"
echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role"
echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" '';
) | mysql -N };
'';
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 = # Read the password from the credentials directory and
let # escape any single quotes by adding additional single
databaseServices = # quotes after them, following the rules laid out here:
if createLocalPostgreSQL then [ # 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" "keycloakPostgreSQLInit.service"
"postgresql.service" "postgresql.service"
] ]
else if createLocalMySQL then [ else if createLocalMySQL then
[
"keycloakMySQLInit.service" "keycloakMySQLInit.service"
"mysql.service" "mysql.service"
] ]
else [ ]; else
secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); [ ];
mkSecretReplacement = file: '' secretPaths = catAttrs "_secret" (collect isSecret cfg.settings);
replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf 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; secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
in extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags;
{ in
after = databaseServices; {
bindsTo = databaseServices; after = databaseServices;
wantedBy = [ "multi-user.target" ]; bindsTo = databaseServices;
path = with pkgs; [ wantedBy = [ "multi-user.target" ];
keycloakBuild path = with pkgs; [
openssl keycloakBuild
replace-secret openssl
]; replace-secret
environment = { ];
KC_HOME_DIR = "/run/keycloak"; environment = {
KC_CONF_DIR = "/run/keycloak/conf"; KC_HOME_DIR = "/run/keycloak";
}; KC_CONF_DIR = "/run/keycloak/conf";
serviceConfig = { };
LoadCredential = serviceConfig = {
map (p: "${baseNameOf p}:${p}") secretPaths LoadCredential =
++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ map (p: "${baseNameOf p}:${p}") secretPaths
"ssl_cert:${cfg.sslCertificate}" ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
"ssl_key:${cfg.sslCertificateKey}" "ssl_cert:${cfg.sslCertificate}"
]; "ssl_key:${cfg.sslCertificateKey}"
User = "keycloak"; ];
Group = "keycloak"; User = "keycloak";
DynamicUser = true; Group = "keycloak";
RuntimeDirectory = "keycloak"; DynamicUser = true;
RuntimeDirectoryMode = "0700"; RuntimeDirectory = "keycloak";
AmbientCapabilities = "CAP_NET_BIND_SERVICE"; RuntimeDirectoryMode = "0700";
Type = "notify"; # Requires quarkus-systemd-notify plugin AmbientCapabilities = "CAP_NET_BIND_SERVICE";
NotifyAccess = "all"; Type = "notify"; # Requires quarkus-systemd-notify plugin
}; NotifyAccess = "all";
script = '' };
script =
''
set -o errexit -o pipefail -o nounset -o errtrace set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit shopt -s inherit_errexit
@ -681,24 +781,26 @@ in
# sequences. # sequences.
sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf 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 mkdir -p /run/keycloak/ssl
cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/
'' + '' ''
+ ''
export KEYCLOAK_ADMIN=admin export KEYCLOAK_ADMIN=admin
export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword} export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword}
kc.sh --verbose start --optimized ${extraStartupFlags} kc.sh --verbose start --optimized ${extraStartupFlags}
''; '';
}; };
services.postgresql.enable = mkDefault createLocalPostgreSQL; services.postgresql.enable = mkDefault createLocalPostgreSQL;
services.mysql.enable = mkDefault createLocalMySQL; services.mysql.enable = mkDefault createLocalMySQL;
services.mysql.package = services.mysql.package =
let let
dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80;
in in
mkIf createLocalMySQL (mkDefault dbPkg); mkIf createLocalMySQL (mkDefault dbPkg);
}; };
meta.doc = ./keycloak.md; meta.doc = ./keycloak.md;
meta.maintainers = [ maintainers.talyz ]; meta.maintainers = [ maintainers.talyz ];

View file

@ -5,15 +5,22 @@ args@{
pkgs, pkgs,
inputs, inputs,
... ...
}: let }:
let
nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { };
loadTestFiles = with lib; dir: mapAttrs' (name: _: let loadTestFiles =
test = ((import (dir + "/${name}")) args); with lib;
in { dir:
name = "test-" + (lib.strings.removeSuffix ".nix" name); mapAttrs' (
value = nixos-lib.runTest test; name: _:
}) let
(filterAttrs (name: _: (hasSuffix ".nix" name) && name != "default.nix") test = ((import (dir + "/${name}")) args);
(builtins.readDir dir)); in
in loadTestFiles ./. {
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 ./.

View file

@ -3,10 +3,12 @@
lib, lib,
config, config,
... ...
}: let }:
puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) {}); let
puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) {}); puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) { });
in { puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) { });
in
{
imports = [ ./global.nix ]; imports = [ ./global.nix ];
security.polkit.enable = true; security.polkit.enable = true;
@ -18,9 +20,7 @@ in {
services.getty.autologinUser = config.pub-solar-os.authentication.username; services.getty.autologinUser = config.pub-solar-os.authentication.username;
virtualisation.qemu.options = [ virtualisation.qemu.options = [ "-vga std" ];
"-vga std"
];
home-manager.users.${config.pub-solar-os.authentication.username} = { home-manager.users.${config.pub-solar-os.authentication.username} = {
programs.bash.profileExtra = '' programs.bash.profileExtra = ''
@ -34,9 +34,9 @@ in {
''; '';
config = { config = {
modifier = "Mod4"; modifier = "Mod4";
terminal = "${pkgs.alacritty}/bin/alacritty"; terminal = "${pkgs.alacritty}/bin/alacritty";
startup = [ startup = [
{command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket";} { command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket"; }
]; ];
}; };
}; };

View file

@ -1,8 +1,6 @@
{ { writeShellScriptBin, curl }:
writeShellScriptBin, writeShellScriptBin "puppeteer-run" ''
curl set -e
}: 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
'' ''

View file

@ -1,7 +1,4 @@
{ { buildNpmPackage, nodejs }:
buildNpmPackage,
nodejs,
}:
buildNpmPackage rec { buildNpmPackage rec {
src = ./.; src = ./.;
name = "puppeteer-socket"; name = "puppeteer-socket";