check in time

This commit is contained in:
Chris Kruining 2026-04-02 11:37:34 +02:00
parent 4dfcd5cca8
commit a8a639db6e
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
39 changed files with 436 additions and 307 deletions

View file

@ -8,36 +8,8 @@
directory = ./.;
exportInterfaces = {
persistence = {lib, ...}: let
inherit (lib) mkOption types;
in {
options = {
main = mkOption {
type = types.str;
};
database = mkOption {
type = types.attrsOf types.anything;
};
};
};
servarr = {lib, ...}: let
inherit (lib) mkOption types;
in {
options = {
services = mkOption {
type = types.attrsOf (types.submodule {
options = {
port = mkOption {
type = types.port;
};
};
});
default = "awesome!";
};
};
};
persistence = import ./interfaces/persistence.nix;
servarr = import ./interfaces/servarr.nix;
};
inventory.machines = {
@ -99,8 +71,10 @@
inventory.instances = {
users-chris = {
module.name = "users";
module.input = "clan-core";
module = {
name = "users";
input = "clan-core";
};
roles.default.machines.mandos.settings = {};
roles.default.machines.manwe.settings = {};
@ -116,20 +90,26 @@
};
persistence = {
module.name = "persistence";
module.input = "self";
module = {
name = "persistence";
input = "self";
};
# TODO :: Convert to use tags instead
roles.default.machines.ulmo.settings = {};
};
servarr = {
module.name = "servarr";
module.input = "self";
module = {
name = "servarr";
input = "self";
};
# TODO :: Convert to use tags instead
roles.default.machines.ulmo.settings = {};
roles.default.settings = {
roles.default = {
machines.ulmo.settings = {};
settings = {
enable = true;
services = {
sonarr = {
@ -152,4 +132,5 @@
};
};
};
};
}

View file

@ -1,4 +1,11 @@
{...}: {
{
lib,
clanLib,
exports,
...
}: let
inherit (builtins) toString;
in {
_class = "clan.service";
manifest = {
name = "arda/persistence";
@ -7,28 +14,156 @@
(for now this means a database. and specifically it means postgres)
'';
readme = builtins.readFile ./README.md;
exports.out = ["persistence"];
exports = {
inputs = ["persistence"];
out = ["persistence"];
};
};
roles.default = {
description = '''';
interface = {...}: {
options = {};
interface = {lib, ...}: let
inherit (lib) mkOption types;
in {
options = {
port = mkOption {
type = types.port;
default = 5432;
};
};
};
perInstance = {mkExports, ...}: {
perInstance = {
mkExports,
machine,
settings,
...
}: let
requested_databases =
exports
|> clanLib.selectExports (_scope: true)
|> lib.mapAttrsToList (_: value: value.persistence.databases or [])
|> lib.concatLists;
in {
exports = mkExports {
persistence = {
main = "postgresql";
database.postgresql = {
host = "";
port = 5432;
driver.postgresql = {
host = "localhost";
port = settings.port;
databases = requested_databases;
};
};
};
nixosModule = {...}: {
nixosModule = {
lib,
pkgs,
config,
...
}: {
clan.core.vars.generators.postgresql = let
password_files =
requested_databases
|> lib.map (db: [
{
name = "${db}_password";
value = {
secret = true;
deploy = false;
};
}
])
|> lib.concatLists
|> lib.listToAttrs;
in {
files =
{
"server.crt" = {
secret = true;
deploy = true;
};
"server.key" = {
secret = true;
deploy = true;
};
".pgpass" = {
secret = true;
deploy = true;
owner = "postgres";
group = "postgres";
mode = "0600";
restartUnits = ["service.postgresql"];
};
}
// password_files;
runtimeInputs = with pkgs; [openssl_3_5 pwgen];
script = ''
openssl req \
-new -x509 -days 365 -nodes -text \
-out $out/server.crt \
-keyout $out/server.key \
-subj "/CN=db.${config.networking.fqdn}"
${requested_databases
|> lib.map (db: "pwgen -s 128 1 > $out/${db}_password")
|> lib.join "\n"}
cat << EOL > $out/.pgpass
#host:port:database:user:password
${requested_databases
|> lib.map (db: "*:${toString settings.port}:${db}:${db}:$(cat $out/${db}_password)")
|> lib.join "\n"}
EOL
'';
};
systemd.services.postgresql.environment.PGPASSFILE = config.clan.core.vars.generators.postgresql.files.".pgpass".path;
services = {
postgresql = {
enable = true;
# enableTCPIP = true;
settings = {
port = settings.port;
ssl = true;
};
ensureDatabases = requested_databases;
ensureUsers =
requested_databases
|> lib.map (db: {
name = db;
ensureDBOwnership = true;
ensureClauses = {
login = true;
connection_limit = 5;
};
});
identMap = ''
#map sys user db user
superuser_map root postgres
superuser_map postgres postgres
superuser_map /^(.+)$ \1
'';
authentication = ''
# Generated file, do not edit!
# type database user auth-method optional_ident_map
local sameuser all peer map=superuser_map
# TYPE DATABASE USER ADDRESS METHOD
# local all all trust
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
'';
};
};
};
};
};

View file

@ -1,6 +1,5 @@
{
exports,
clanLib,
lib,
...
}: {
@ -11,8 +10,8 @@
categories = ["Service" "Media"];
readme = builtins.readFile ./README.md;
exports = {
inputs = ["persistence"];
out = ["servarr"];
inputs = [];
out = ["servarr" "persistence"];
};
};
@ -24,6 +23,16 @@
in {
options = {
enable = mkEnableOption "Enable configured *arr services";
database = {
host = mkOption {
type = types.str;
};
port = mkOption {
type = types.port;
};
};
services = mkOption {
type = types.attrsOf (types.submodule ({name, ...}: {
options = {
@ -53,6 +62,10 @@
...
}: {
exports = mkExports {
persistence.databases =
settings.services
|> lib.attrNames;
servarr.services =
settings.services
|> lib.attrNames
@ -73,8 +86,6 @@
servarr = import ./lib.nix (args // {inherit settings;});
services = settings.services |> lib.attrNames;
service_count = services |> lib.length;
db = exports |> clanLib.getExport {serviceName = "persistence";};
in {
imports = [
(import ./sabnzbd.nix (args
@ -121,16 +132,6 @@
openFirewall = true;
port = 2000 + service_count + 3;
};
postgresql = {
ensureDatabases = services;
ensureUsers =
services
|> lib.map (service: {
name = service;
ensureDBOwnership = true;
});
};
};
};
};

View file

@ -13,6 +13,8 @@
options,
...
}: {
dependencies = ["postgresql"];
files = {
api_key = {
secret = true;
@ -33,7 +35,10 @@
runtimeInputs = with pkgs; [pwgen];
script = ''
pwgen -s 128 1 > $out/api_key
echo ${lib.toUpper service}__AUTH__APIKEY="$(cat $out/api_key)" > $out/config.env
cat << EOL > $out/config.env
${lib.toUpper service}__AUTH__APIKEY="$(cat $out/api_key)"
${lib.toUpper service}__POSTGRES_PASSWORD="$(cat $in/postgresql/${service}_password)"
EOL
'';
};
@ -41,7 +46,9 @@
service,
options,
...
}:
}: let
inherit (builtins) toString;
in
{
enable = true;
openFirewall = true;
@ -58,9 +65,10 @@
port = options.port;
};
# Password provided via environment file
postgres = {
host = "localhost";
port = "5432";
host = settings.database.host;
port = toString settings.database.port;
user = service;
maindb = service;
logdb = service;
@ -72,7 +80,7 @@
group = "media";
});
createSystemdService = {
createSystemdService = args @ {
service,
options,
...
@ -81,7 +89,7 @@
terraformConfiguration = self.inputs.terranix.lib.terranixConfiguration {
system = pkgs.stdenv.hostPlatform.system;
modules = [
(createInfra {inherit service options;})
(createInfra args)
];
};
in {
@ -300,7 +308,7 @@
));
};
in {
createModule = services: {...}: {
createModule = services: args: {
config =
services
|> lib.attrsToList
@ -311,10 +319,10 @@ in {
service = name;
options = value // {port = 2000 + i;};
in {
clan.core.vars.generators.${service} = createGenerator {inherit service options;};
services.${service} = createService {inherit service options;};
clan.core.vars.generators.${service} = createGenerator (args // {inherit service options;});
services.${service} = createService (args // {inherit service options;});
systemd.services."${service}-apply-infra" = lib.mkIf settings.enable (createSystemdService {inherit service options;});
systemd.services."${service}-apply-infra" = lib.mkIf settings.enable (createSystemdService (args // {inherit service options;}));
})
|> lib.mkMerge;
};

View file

@ -0,0 +1,20 @@
{lib, ...}: let
inherit (lib) mkOption types;
in {
options = {
main = mkOption {
type = types.nullOr types.str;
default = null;
};
driver = mkOption {
type = types.attrsOf types.anything;
default = {};
};
databases = mkOption {
type = types.listOf types.str;
default = [];
};
};
}

16
interfaces/servarr.nix Normal file
View file

@ -0,0 +1,16 @@
{lib, ...}: let
inherit (lib) mkOption types;
in {
options = {
services = mkOption {
type = types.attrsOf (types.submodule {
options = {
port = mkOption {
type = types.port;
};
};
});
default = {};
};
};
}

View file

@ -66,221 +66,221 @@
};
};
sneeuwvlok = {
services = {
backup.borg.enable = true;
# sneeuwvlok = {
# services = {
# backup.borg.enable = true;
authentication.zitadel = {
enable = true;
organization = {
nix = {
user = {
chris = {
email = "chris@kruining.eu";
firstName = "Chris";
lastName = "Kruining";
roles = ["ORG_OWNER"];
instanceRoles = ["IAM_OWNER"];
};
kaas = {
email = "chris+kaas@kruining.eu";
firstName = "Kaas";
lastName = "Kruining";
};
};
project = {
ulmo = {
projectRoleCheck = true;
projectRoleAssertion = true;
hasProjectCheck = true;
role = {
jellyfin = {
group = "jellyfin";
};
jellyfin_admin = {
group = "jellyfin";
};
};
assign = {
chris = ["jellyfin" "jellyfin_admin"];
kaas = ["jellyfin"];
};
application = {
jellyfin = {
redirectUris = ["https://jellyfin.kruining.eu/sso/OID/redirect/zitadel"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
};
forgejo = {
redirectUris = ["https://git.amarth.cloud/user/oauth2/zitadel/callback"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
};
vaultwarden = {
redirectUris = ["https://vault.kruining.eu/identity/connect/oidc-signin"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
exportMap = {
client_id = "SSO_CLIENT_ID";
client_secret = "SSO_CLIENT_SECRET";
};
};
matrix = {
redirectUris = ["https://matrix.kruining.eu/_synapse/client/oidc/callback"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
};
mydia = {
redirectUris = ["http://localhost:2010/auth/oidc/callback"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
};
grafana = {
redirectUris = ["http://localhost:9001/login/generic_oauth"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
};
};
};
convex = {
projectRoleCheck = true;
projectRoleAssertion = true;
hasProjectCheck = true;
application = {
scry = {
redirectUris = ["https://nautical-salamander-320.eu-west-1.convex.cloud/api/auth/callback/zitadel"];
grantTypes = ["authorizationCode"];
responseTypes = ["code"];
};
};
};
};
action = {
flattenRoles = {
script = ''
(ctx, api) => {
if (ctx.v1.user.grants == undefined || ctx.v1.user.grants.count == 0) {
return;
}
const roles = ctx.v1.user.grants.grants.flatMap(({ roles, projectId }) => roles.map(role => projectId + ':' + role));
api.v1.claims.setClaim('nix:zitadel:custom', JSON.stringify({ roles }));
};
'';
};
};
triggers = [
{
flowType = "customiseToken";
triggerType = "preUserinfoCreation";
actions = ["flattenRoles"];
}
{
flowType = "customiseToken";
triggerType = "preAccessTokenCreation";
actions = ["flattenRoles"];
}
];
};
};
};
communication.matrix.enable = true;
development.forgejo.enable = true;
networking.ssh.enable = true;
networking.caddy.hosts = {
# Expose amarht cloud stuff like this until I have a proper solution
"auth.amarth.cloud" = ''
reverse_proxy http://192.168.1.223:9092
'';
"amarth.cloud" = ''
reverse_proxy http://192.168.1.223:8080
'';
};
media.enable = true;
media.glance.enable = true;
media.mydia.enable = true;
media.nfs.enable = true;
media.jellyfin.enable = true;
# media.servarr = {
# radarr = {
# authentication.zitadel = {
# enable = true;
# port = 2001;
# rootFolders = [
# "/var/media/movies"
# ];
# organization = {
# nix = {
# user = {
# chris = {
# email = "chris@kruining.eu";
# firstName = "Chris";
# lastName = "Kruining";
# roles = ["ORG_OWNER"];
# instanceRoles = ["IAM_OWNER"];
# };
# sonarr = {
# enable = true;
# kaas = {
# email = "chris+kaas@kruining.eu";
# firstName = "Kaas";
# lastName = "Kruining";
# };
# };
# project = {
# ulmo = {
# projectRoleCheck = true;
# projectRoleAssertion = true;
# hasProjectCheck = true;
# role = {
# jellyfin = {
# group = "jellyfin";
# };
# jellyfin_admin = {
# group = "jellyfin";
# };
# };
# assign = {
# chris = ["jellyfin" "jellyfin_admin"];
# kaas = ["jellyfin"];
# };
# application = {
# jellyfin = {
# redirectUris = ["https://jellyfin.kruining.eu/sso/OID/redirect/zitadel"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# };
# forgejo = {
# redirectUris = ["https://git.amarth.cloud/user/oauth2/zitadel/callback"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# };
# vaultwarden = {
# redirectUris = ["https://vault.kruining.eu/identity/connect/oidc-signin"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# exportMap = {
# client_id = "SSO_CLIENT_ID";
# client_secret = "SSO_CLIENT_SECRET";
# };
# };
# matrix = {
# redirectUris = ["https://matrix.kruining.eu/_synapse/client/oidc/callback"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# };
# mydia = {
# redirectUris = ["http://localhost:2010/auth/oidc/callback"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# };
# grafana = {
# redirectUris = ["http://localhost:9001/login/generic_oauth"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# };
# };
# };
# convex = {
# projectRoleCheck = true;
# projectRoleAssertion = true;
# hasProjectCheck = true;
# application = {
# scry = {
# redirectUris = ["https://nautical-salamander-320.eu-west-1.convex.cloud/api/auth/callback/zitadel"];
# grantTypes = ["authorizationCode"];
# responseTypes = ["code"];
# };
# };
# };
# };
# action = {
# flattenRoles = {
# script = ''
# (ctx, api) => {
# if (ctx.v1.user.grants == undefined || ctx.v1.user.grants.count == 0) {
# return;
# }
# const roles = ctx.v1.user.grants.grants.flatMap(({ roles, projectId }) => roles.map(role => projectId + ':' + role));
# api.v1.claims.setClaim('nix:zitadel:custom', JSON.stringify({ roles }));
# };
# '';
# };
# };
# triggers = [
# {
# flowType = "customiseToken";
# triggerType = "preUserinfoCreation";
# actions = ["flattenRoles"];
# }
# {
# flowType = "customiseToken";
# triggerType = "preAccessTokenCreation";
# actions = ["flattenRoles"];
# }
# ];
# };
# };
# };
# communication.matrix.enable = true;
# development.forgejo.enable = true;
# networking.ssh.enable = true;
# networking.caddy.hosts = {
# # Expose amarht cloud stuff like this until I have a proper solution
# "auth.amarth.cloud" = ''
# reverse_proxy http://192.168.1.223:9092
# '';
# "amarth.cloud" = ''
# reverse_proxy http://192.168.1.223:8080
# '';
# };
# media.enable = true;
# media.glance.enable = true;
# media.mydia.enable = true;
# media.nfs.enable = true;
# media.jellyfin.enable = true;
# # media.servarr = {
# # radarr = {
# # enable = true;
# # port = 2001;
# # rootFolders = [
# # "/var/media/movies"
# # ];
# # };
# # sonarr = {
# # enable = true;
# # # debug = true;
# # port = 2002;
# # rootFolders = [
# # "/var/media/series"
# # ];
# # };
# # lidarr = {
# # enable = true;
# # debug = true;
# port = 2002;
# rootFolders = [
# "/var/media/series"
# ];
# # port = 2003;
# # rootFolders = [
# # "/var/media/music"
# # ];
# # };
# # prowlarr = {
# # enable = true;
# # # debug = true;
# # port = 2004;
# # };
# # };
# observability = {
# grafana.enable = true;
# prometheus.enable = true;
# loki.enable = true;
# promtail.enable = true;
# # uptime-kuma.enable = true;
# };
# lidarr = {
# security.vaultwarden = {
# enable = true;
# debug = true;
# port = 2003;
# rootFolders = [
# "/var/media/music"
# ];
# };
# database = {
# # type = "sqlite";
# # file = "/var/lib/vaultwarden/state.db";
# prowlarr = {
# enable = true;
# # debug = true;
# port = 2004;
# type = "postgresql";
# host = "localhost";
# port = 5432;
# sslMode = "disabled";
# };
# };
# };
observability = {
grafana.enable = true;
prometheus.enable = true;
loki.enable = true;
promtail.enable = true;
# uptime-kuma.enable = true;
};
security.vaultwarden = {
enable = true;
database = {
# type = "sqlite";
# file = "/var/lib/vaultwarden/state.db";
type = "postgresql";
host = "localhost";
port = 5432;
sslMode = "disabled";
};
};
};
editor = {
nano.enable = true;
};
};
# editor = {
# nano.enable = true;
# };
# };
}

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris

View file

@ -1 +0,0 @@
../../../../../../sops/machines/ulmo

View file

@ -1 +0,0 @@
../../../../../../sops/users/chris