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"; description = "Nixos config flake";
nixConfig = {
warn-dirty = false;
};
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

View file

@ -1,9 +1,10 @@
{ config, lib, pkgs, namespace, system, inputs, ... }: { config, lib, pkgs, namespace, system, inputs, ... }:
let 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; inherit (lib.${namespace}.strings) toSnakeCase;
cfg = config.${namespace}.services.authentication.zitadel; cfg = config.${namespace}.services.authentication.zitadel;
port = 3010;
database = "zitadel"; database = "zitadel";
in in
@ -543,12 +544,12 @@ in
networking.caddy = { networking.caddy = {
hosts = { hosts = {
"auth.kruining.eu" = '' "auth.kruining.eu" = ''
reverse_proxy h2c://[::1]:9092 reverse_proxy h2c://[::1]:${toString port}
''; '';
}; };
extraConfig = '' extraConfig = ''
(auth) { (auth) {
forward_auth h2c://[::1]:9092 { forward_auth h2c://[::1]:${toString port} {
uri /api/authz/forward-auth uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
} }
@ -612,7 +613,7 @@ in
masterKeyFile = config.sops.secrets."zitadel/masterKey".path; masterKeyFile = config.sops.secrets."zitadel/masterKey".path;
tlsMode = "external"; tlsMode = "external";
settings = { settings = {
Port = 9092; Port = port;
ExternalDomain = "auth.kruining.eu"; ExternalDomain = "auth.kruining.eu";
ExternalPort = 443; ExternalPort = 443;
@ -698,8 +699,6 @@ in
}; };
}; };
networking.firewall.allowedTCPPorts = [ 80 443 ];
# Secrets # Secrets
sops = { sops = {
secrets = { secrets = {

View file

@ -112,9 +112,29 @@ in {
(mkMautrix "mautrix-telegram" 2 {}) (mkMautrix "mautrix-telegram" 2 {})
(mkMautrix "mautrix-whatsapp" 3 {}) (mkMautrix "mautrix-whatsapp" 3 {})
(mkMautrix "arrtrix" 4 { (mkMautrix "arrtrix" 4 {
settings.observability = { environmentFile = config.sops.templates."arrtrix/secrets".path;
otlp_grpc_endpoint = "http://[::1]:1000";
service_name = "arrtrix"; 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;
};
};
}; };
}) })
{ {
@ -167,7 +187,7 @@ in {
}; };
sso = { 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; update_profile_information = true;
}; };
@ -365,6 +385,14 @@ in {
''; '';
restartUnits = ["matrix-synapse.service"]; 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 { config = mkIf cfg.enable {
${namespace}.services.networking.caddy.hosts = { # ${namespace}.services.networking.caddy.hosts = {
"https://${config.networking.hostName}:443" = '' # "https://${config.networking.hostName}.arda:443" = ''
reverse_proxy http://[::1]:2000 # reverse_proxy http://[::1]:2000
''; # '';
}; # };
services.glance = { services.glance = {
enable = true; enable = true;

View file

@ -56,7 +56,8 @@ in {
auth.authenticationMethod = "External"; auth.authenticationMethod = "External";
server = { server = {
bindaddress = "0.0.0.0"; # bindaddress = "0.0.0.0";
bindaddress = "[::]";
port = port; port = port;
}; };
@ -194,7 +195,7 @@ in {
source = "devopsarr/${service}"; source = "devopsarr/${service}";
version = version =
{ {
radarr = "2.3.3"; radarr = "2.3.5";
sonarr = "3.4.0"; sonarr = "3.4.0";
prowlarr = "3.1.0"; prowlarr = "3.1.0";
lidarr = "1.13.0"; lidarr = "1.13.0";
@ -217,10 +218,15 @@ in {
{ {
method = 1; # HTTP METHOD 1=POST, 2=PUT method = 1; # HTTP METHOD 1=POST, 2=PUT
name = "Arrtrix"; 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"]) { // (lib.optionalAttrs (lib.elem service ["radarr" "whisparr"]) {
onMovieDelete = true; on_movie_delete = true;
}); });
}; };
@ -244,15 +250,25 @@ in {
}; };
"${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { "${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) {
"main" = { "main" =
name = "SABnzbd"; {
enable = true; name = "SABnzbd";
priority = 1; enable = true;
host = "localhost"; priority = 1;
api_key = lib.tfRef "var.sabnzbd_api_key"; host = "localhost";
url_base = "/"; api_key = lib.tfRef "var.sabnzbd_api_key";
port = 2090; 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") ( // (lib.optionalAttrs (service == "prowlarr") (

View file

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

View file

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

View file

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

View file

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

View file

@ -106,6 +106,18 @@ in {
example = {}; 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 { serviceDependencies = lib.mkOption {
type = with lib.types; listOf str; type = with lib.types; listOf str;
default = default =
@ -168,6 +180,7 @@ in {
StateDirectory = baseNameOf dataDir; StateDirectory = baseNameOf dataDir;
WorkingDirectory = dataDir; WorkingDirectory = dataDir;
EnvironmentFile = cfg.environmentFile;
ExecStart = '' ExecStart = ''
${lib.getExe cfg.package} --config='${settingsFile}' --registration='${registrationFile}' ${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) key = strings.ToLower(key)
lookupKey := key
if !strings.ContainsRune(key, '.') { if !strings.ContainsRune(key, '.') {
key = strings.ReplaceAll(key, "__", ".") key = strings.ReplaceAll(key, "__", ".")
} }
path := strings.Split(key, ".") path := strings.Split(key, ".")
field, ok := reflectGetFromMainOrNetwork(cfgVal, networkVal, path) field, ok := reflectGetFromMainOrNetwork(cfgVal, networkVal, path)
if !ok && !strings.ContainsRune(lookupKey, '.') {
field, ok = reflectGetFromMainOrNetworkTokens(cfgVal, networkVal, strings.Split(lookupKey, "_"))
}
if !ok { if !ok {
return fmt.Errorf("%s not found", formatKey(path)) 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) 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) { func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool) {
if len(path) == 0 { if len(path) == 0 {
return &reflectedField{value: value, valueKind: value.Kind()}, true return &reflectedField{value: value, valueKind: value.Kind()}, true
@ -108,6 +119,41 @@ func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool)
return nil, false 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 { func yamlFieldName(field reflect.StructField) string {
parts := strings.SplitN(field.Tag.Get("yaml"), ",", 2) parts := strings.SplitN(field.Tag.Get("yaml"), ",", 2)
switch name := parts[0]; { 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 { func setReflectedValue(field *reflectedField, path []string, raw string) error {
parsed, err := parseValue(field.valueKind, raw, path) parsed, err := parseValue(field.valueKind, raw, path)
if err != nil { 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 = { sneeuwvlok = {
services = { services = {
backup.borg.enable = true; 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