checkpoint

This commit is contained in:
Chris Kruining 2026-04-16 15:36:33 +02:00
parent be2843ca80
commit e07257e137
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
61 changed files with 258 additions and 156 deletions

View file

@ -1,6 +1,10 @@
{
description = "Nixos config flake";
nixConfig = {
warn-dirty = false;
};
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

View file

@ -1,9 +1,10 @@
{ config, lib, pkgs, namespace, system, inputs, ... }:
let
inherit (lib) mkIf mkEnableOption mkOption types toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs concatMapStringsSep filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames;
inherit (lib) mkIf mkEnableOption mkOption toString types toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs concatMapStringsSep filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames;
inherit (lib.${namespace}.strings) toSnakeCase;
cfg = config.${namespace}.services.authentication.zitadel;
port = 3010;
database = "zitadel";
in
@ -543,12 +544,12 @@ in
networking.caddy = {
hosts = {
"auth.kruining.eu" = ''
reverse_proxy h2c://[::1]:9092
reverse_proxy h2c://[::1]:${toString port}
'';
};
extraConfig = ''
(auth) {
forward_auth h2c://[::1]:9092 {
forward_auth h2c://[::1]:${toString port} {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
@ -612,7 +613,7 @@ in
masterKeyFile = config.sops.secrets."zitadel/masterKey".path;
tlsMode = "external";
settings = {
Port = 9092;
Port = port;
ExternalDomain = "auth.kruining.eu";
ExternalPort = 443;
@ -698,8 +699,6 @@ in
};
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
# Secrets
sops = {
secrets = {

View file

@ -112,10 +112,30 @@ in {
(mkMautrix "mautrix-telegram" 2 {})
(mkMautrix "mautrix-whatsapp" 3 {})
(mkMautrix "arrtrix" 4 {
settings.observability = {
otlp_grpc_endpoint = "http://[::1]:1000";
environmentFile = config.sops.templates."arrtrix/secrets".path;
settings = {
observability = {
otlp_grpc_endpoint = "http://[::1]:9062";
service_name = "arrtrix";
};
network.content = {
movies = {
url = "http://[::1]:${toString config.services.radarr.settings.server.port}";
api_key = "$RADARR_APIKEY";
root_folder_path = "/var/media/movies";
quality_profile_id = 5;
};
series = {
url = "http://[::1]:${toString config.services.radarr.settings.server.port}";
api_key = "$SONARR_APIKEY";
root_folder_path = "/var/media/series";
quality_profile_id = 5;
language_profile_id = 1;
};
};
};
})
{
matrix-synapse = {
@ -167,7 +187,7 @@ in {
};
sso = {
client_whitelist = ["http://[::1]:9092/" "https://auth.kruining.eu/"];
client_whitelist = ["http://[::1]:${toString config.services.zitadel.settings.Port}/" "https://auth.kruining.eu/"];
update_profile_information = true;
};
@ -365,6 +385,14 @@ in {
'';
restartUnits = ["matrix-synapse.service"];
};
"arrtrix/secrets" = {
owner = "arrtrix";
content = ''
RADARR_APIKEY=${config.sops.placeholder."radarr/apikey"}
SONARR_APIKEY=${config.sops.placeholder."sonarr/apikey"}
'';
restartUnits = ["arrtrix.service"];
};
};
};
};

View file

@ -13,11 +13,11 @@ in {
};
config = mkIf cfg.enable {
${namespace}.services.networking.caddy.hosts = {
"https://${config.networking.hostName}:443" = ''
reverse_proxy http://[::1]:2000
'';
};
# ${namespace}.services.networking.caddy.hosts = {
# "https://${config.networking.hostName}.arda:443" = ''
# reverse_proxy http://[::1]:2000
# '';
# };
services.glance = {
enable = true;

View file

@ -56,7 +56,8 @@ in {
auth.authenticationMethod = "External";
server = {
bindaddress = "0.0.0.0";
# bindaddress = "0.0.0.0";
bindaddress = "[::]";
port = port;
};
@ -194,7 +195,7 @@ in {
source = "devopsarr/${service}";
version =
{
radarr = "2.3.3";
radarr = "2.3.5";
sonarr = "3.4.0";
prowlarr = "3.1.0";
lidarr = "1.13.0";
@ -217,10 +218,15 @@ in {
{
method = 1; # HTTP METHOD 1=POST, 2=PUT
name = "Arrtrix";
url = "http://[::1]${toString config'.services.arrtrix.settings.appservice.port}";
url = "http://localhost:${toString config'.services.arrtrix.settings.appservice.port}/_arrtrix/webhook";
on_grab = true;
on_download = true;
on_rename = true;
on_upgrade = true;
}
// (lib.optionalAttrs (lib.elem service ["radarr" "whisparr"]) {
onMovieDelete = true;
on_movie_delete = true;
});
};
@ -244,7 +250,8 @@ in {
};
"${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) {
"main" = {
"main" =
{
name = "SABnzbd";
enable = true;
priority = 1;
@ -252,7 +259,16 @@ in {
api_key = lib.tfRef "var.sabnzbd_api_key";
url_base = "/";
port = 2090;
};
}
// ({
radarr = {movie_category = "movies";};
sonarr = {tv_category = "tv";};
lidarr = {music_category = "audio";};
whisparr = {movie_category = "movies";};
readarr = {book_category = "Default";};
}.${
service
});
};
}
// (lib.optionalAttrs (service == "prowlarr") (

View file

@ -24,6 +24,8 @@ in {
};
config = mkIf hasHosts {
networking.firewall.allowedTCPPorts = [80 443];
services.caddy = {
enable = cfg.enable;

View file

@ -1,5 +1,9 @@
{ config, lib, namespace, ... }:
let
{
config,
lib,
namespace,
...
}: let
inherit (builtins) toString;
inherit (lib) mkEnableOption mkIf;
@ -9,8 +13,7 @@ let
otlpGrpcPort = 9071;
otlpHttpPort = 9072;
tempoOtlpGrpcPort = 9062;
in
{
in {
options.${namespace}.services.observability.alloy = {
enable = mkEnableOption "enable Grafana Alloy";
};
@ -21,7 +24,7 @@ in
configPath = "/etc/alloy";
extraFlags = [
"--disable-reporting"
"--server.http.listen-addr=0.0.0.0:${toString httpPort}"
"--server.http.listen-addr=[::]:${toString httpPort}"
"--storage.path=/var/lib/alloy"
];
};
@ -29,11 +32,11 @@ in
environment.etc."alloy/config.alloy".text = ''
otelcol.receiver.otlp "default" {
grpc {
endpoint = "127.0.0.1:${toString otlpGrpcPort}"
endpoint = "[::1]:${toString otlpGrpcPort}"
}
http {
endpoint = "127.0.0.1:${toString otlpHttpPort}"
endpoint = "[::1]:${toString otlpHttpPort}"
}
output {
@ -60,13 +63,13 @@ in
prometheus.remote_write "local" {
endpoint {
url = "http://127.0.0.1:${toString config.services.prometheus.port}/api/v1/write"
url = "http://[::1]:${toString config.services.prometheus.port}/api/v1/write"
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "127.0.0.1:${toString tempoOtlpGrpcPort}"
endpoint = "[::1]:${toString tempoOtlpGrpcPort}"
tls {
insecure = true
@ -75,6 +78,6 @@ in
}
'';
networking.firewall.allowedTCPPorts = [ httpPort ];
networking.firewall.allowedTCPPorts = [httpPort];
};
}

View file

@ -26,7 +26,7 @@ in {
settings = {
server = {
http_port = 9010;
http_addr = "0.0.0.0";
http_addr = "::";
domain = "ulmo";
};
@ -102,40 +102,40 @@ in {
};
datasources.settings.datasources = [
{
name = "Prometheus";
uid = "prometheus";
type = "prometheus";
url = "http://localhost:9020";
isDefault = true;
editable = false;
}
# {
# name = "Prometheus";
# uid = "prometheus";
# type = "prometheus";
# url = "http://[::1]:9020";
# isDefault = true;
# editable = false;
# }
{
name = "Loki";
uid = "loki";
type = "loki";
url = "http://localhost:9030";
editable = false;
}
# {
# name = "Loki";
# uid = "loki";
# type = "loki";
# url = "http://[::1]:9030";
# editable = false;
# }
{
name = "Tempo";
uid = "tempo";
type = "tempo";
url = "http://localhost:9060";
editable = false;
jsonData = {
nodeGraph.enabled = true;
serviceMap.datasourceUid = "prometheus";
tracesToLogsV2 = {
datasourceUid = "loki";
filterByTraceID = true;
spanStartTimeShift = "-1h";
spanEndTimeShift = "1h";
};
};
}
# {
# name = "Tempo";
# uid = "tempo";
# type = "tempo";
# url = "http://localhost:9060";
# editable = false;
# jsonData = {
# nodeGraph.enabled = true;
# serviceMap.datasourceUid = "prometheus";
# tracesToLogsV2 = {
# datasourceUid = "loki";
# filterByTraceID = true;
# spanStartTimeShift = "-1h";
# spanEndTimeShift = "1h";
# };
# };
# }
];
};
};

View file

@ -1,5 +1,9 @@
{ config, lib, namespace, ... }:
let
{
config,
lib,
namespace,
...
}: let
inherit (lib) mkEnableOption mkIf;
cfg = config.${namespace}.services.observability.tempo;
@ -8,8 +12,7 @@ let
grpcPort = 9061;
otlpGrpcPort = 9062;
otlpHttpPort = 9063;
in
{
in {
options.${namespace}.services.observability.tempo = {
enable = mkEnableOption "enable Grafana Tempo";
};
@ -22,15 +25,15 @@ in
search_enabled = true;
server = {
http_listen_address = "0.0.0.0";
http_listen_address = "[::]";
http_listen_port = httpPort;
grpc_listen_address = "127.0.0.1";
grpc_listen_address = "[::1]";
grpc_listen_port = grpcPort;
};
distributor.receivers.otlp.protocols = {
grpc.endpoint = "127.0.0.1:${builtins.toString otlpGrpcPort}";
http.endpoint = "127.0.0.1:${builtins.toString otlpHttpPort}";
grpc.endpoint = "[::1]:${builtins.toString otlpGrpcPort}";
http.endpoint = "[::1]:${builtins.toString otlpHttpPort}";
};
storage.trace = {
@ -43,6 +46,6 @@ in
};
};
networking.firewall.allowedTCPPorts = [ httpPort ];
networking.firewall.allowedTCPPorts = [httpPort];
};
}

View file

@ -106,6 +106,18 @@ in {
example = {};
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
File containing environment variables to be passed to the arrtrix service.
If an environment variable `ARRTRIX_BRIDGE_LOGIN_SHARED_SECRET` is set,
then its value will be used in the configuration file for the option
`double_puppet.secrets` without leaking it to the store, using the configured
`homeserver.domain` as key.
'';
};
serviceDependencies = lib.mkOption {
type = with lib.types; listOf str;
default =
@ -168,6 +180,7 @@ in {
StateDirectory = baseNameOf dataDir;
WorkingDirectory = dataDir;
EnvironmentFile = cfg.environmentFile;
ExecStart = ''
${lib.getExe cfg.package} --config='${settingsFile}' --registration='${registrationFile}'

View file

@ -36,12 +36,16 @@ func updateConfigFromEnv(cfg, networkData any, prefix string) error {
}
key = strings.ToLower(key)
lookupKey := key
if !strings.ContainsRune(key, '.') {
key = strings.ReplaceAll(key, "__", ".")
}
path := strings.Split(key, ".")
field, ok := reflectGetFromMainOrNetwork(cfgVal, networkVal, path)
if !ok && !strings.ContainsRune(lookupKey, '.') {
field, ok = reflectGetFromMainOrNetworkTokens(cfgVal, networkVal, strings.Split(lookupKey, "_"))
}
if !ok {
return fmt.Errorf("%s not found", formatKey(path))
}
@ -80,6 +84,13 @@ func reflectGetFromMainOrNetwork(main, network reflect.Value, path []string) (*r
return reflectGetYAML(main, path)
}
func reflectGetFromMainOrNetworkTokens(main, network reflect.Value, tokens []string) (*reflectedField, bool) {
if len(tokens) > 0 && normalizeKey(tokens[0]) == "network" {
return reflectGetYAMLTokens(network, tokens[1:])
}
return reflectGetYAMLTokens(main, tokens)
}
func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool) {
if len(path) == 0 {
return &reflectedField{value: value, valueKind: value.Kind()}, true
@ -108,6 +119,41 @@ func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool)
return nil, false
}
func reflectGetYAMLTokens(value reflect.Value, tokens []string) (*reflectedField, bool) {
if len(tokens) == 0 {
return &reflectedField{value: value, valueKind: value.Kind()}, true
}
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
switch value.Kind() {
case reflect.Map:
return &reflectedField{
value: value,
valueKind: value.Type().Elem().Kind(),
remainingPath: []string{strings.Join(tokens, "_")},
}, true
case reflect.Struct:
fields := reflect.VisibleFields(value.Type())
for _, field := range fields {
name := yamlFieldName(field)
if name == "" {
continue
}
normalizedFieldName := normalizeKey(name)
for i := len(tokens); i >= 1; i-- {
if normalizeKey(strings.Join(tokens[:i], "_")) != normalizedFieldName {
continue
}
return reflectGetYAMLTokens(value.FieldByIndex(field.Index), tokens[i:])
}
}
}
return nil, false
}
func yamlFieldName(field reflect.StructField) string {
parts := strings.SplitN(field.Tag.Get("yaml"), ",", 2)
switch name := parts[0]; {
@ -120,6 +166,10 @@ func yamlFieldName(field reflect.StructField) string {
}
}
func normalizeKey(value string) string {
return strings.ReplaceAll(strings.ToLower(value), "_", "")
}
func setReflectedValue(field *reflectedField, path []string, raw string) error {
parsed, err := parseValue(field.valueKind, raw, path)
if err != nil {

View file

@ -0,0 +1,57 @@
package runtime
import (
"os"
"testing"
"maunium.net/go/mautrix/bridgev2/bridgeconfig"
"sneeuwvlok/packages/arrtrix/pkg/connector"
)
func TestUpdateConfigFromEnvSupportsFlatUnderscorePaths(t *testing.T) {
t.Setenv("ARRTRIX_NETWORK_CONTENT_MOVIES_APIKEY", "radarr-secret")
cfg := &bridgeconfig.Config{}
network := &connector.Config{}
if err := updateConfigFromEnv(cfg, network, "ARRTRIX_"); err != nil {
t.Fatalf("updateConfigFromEnv returned error: %v", err)
}
if network.Content.Movies.APIKey != "radarr-secret" {
t.Fatalf("expected movies api key to be overridden, got %q", network.Content.Movies.APIKey)
}
}
func TestUpdateConfigFromEnvSupportsExplicitUnderscoredFieldNames(t *testing.T) {
t.Setenv("ARRTRIX_NETWORK_CONTENT_MOVIES_ROOT_FOLDER_PATH", "/data/movies")
cfg := &bridgeconfig.Config{}
network := &connector.Config{}
if err := updateConfigFromEnv(cfg, network, "ARRTRIX_"); err != nil {
t.Fatalf("updateConfigFromEnv returned error: %v", err)
}
if network.Content.Movies.RootFolderPath != "/data/movies" {
t.Fatalf("expected root folder path to be overridden, got %q", network.Content.Movies.RootFolderPath)
}
}
func TestUpdateConfigFromEnvSupportsDoubleUnderscorePaths(t *testing.T) {
t.Setenv("ARRTRIX_NETWORK__CONTENT__SERIES__API_KEY", "sonarr-secret")
cfg := &bridgeconfig.Config{}
network := &connector.Config{}
if err := updateConfigFromEnv(cfg, network, "ARRTRIX_"); err != nil {
t.Fatalf("updateConfigFromEnv returned error: %v", err)
}
if network.Content.Series.APIKey != "sonarr-secret" {
t.Fatalf("expected series api key to be overridden, got %q", network.Content.Series.APIKey)
}
}
func TestMain(m *testing.M) {
code := m.Run()
os.Exit(code)
}

View file

@ -39,31 +39,6 @@
};
};
# virtualisation = {
# containers.enable = true;
# podman = {
# enable = true;
# dockerCompat = true;
# };
# oci-containers = {
# backend = "podman";
# containers = {
# homey = {
# image = "ghcr.io/athombv/homey-shs:latest";
# autoStart = true;
# privileged = true;
# volumes = [
# "/home/chris/.homey-shs:/homey/user"
# ];
# ports = [
# "4859:4859"
# ];
# };
# };
# };
# };
sneeuwvlok = {
services = {
backup.borg.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

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

View file

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

View file

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