Compare commits

..

8 Commits

Author SHA1 Message Date
4474dbad90 remove ignis desktop 2025-11-27 16:08:18 +01:00
eb16cd96fa add atuin key as clan var 2025-11-27 16:07:36 +01:00
b917f503da Update vars via generator atuin for machine haze 2025-11-27 15:56:54 +01:00
f7700cadd5 add pixel-7a to wireguard network
Hard-coded for now until clan-core/wireguard
supports external peers.
2025-11-27 14:56:53 +01:00
b84078220c remove unbound dns auth-zone
This was moved to coredns to avoid confusion
between the authoritative server and the local
resolver.
2025-11-27 14:55:26 +01:00
09f57a1e6f clan: migrate internal DNS to coredns service
Currently using a patched version of the upstream
coredns service, with hard-coded IPs until
wireguard exports are supported.

Zerotier connections were flaky and wireguard
seems more stable (although it seems to have a bit
less throughput).
2025-11-27 14:52:45 +01:00
de99dad887 clan: add temporary patched coredns service
Needed for IPv6 support, and to set the host names
in the auth zone.
2025-11-27 14:50:44 +01:00
e1219f26c3 borg: accept new ssh host keys 2025-11-27 14:48:44 +01:00
21 changed files with 408 additions and 235 deletions

View File

@@ -114,7 +114,7 @@
repo = "${user}@${host}:./borgbackup/${config.networking.hostName}"; repo = "${user}@${host}:./borgbackup/${config.networking.hostName}";
rsh = "ssh -oPort=23 -i ${ rsh = "ssh -oPort=23 -i ${
config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"; } -oStrictHostKeyChecking=accept-new";
}; };
} }
); );

View File

@@ -32,4 +32,35 @@
genepi = { }; genepi = { };
}; };
}; };
# Temporarily patched version of clan-core/coredns for AAAA records support
clan.inventory.instances.coredns = {
module.name = "@rpqt/coredns";
module.input = "self";
roles.default.tags.all = { };
roles.server.machines.verbena = {
settings.ip = "fd28:387a:90:c400::1";
};
roles.server.machines.crocus = {
settings.ip = "fd28:387a:90:c400:6db2:dfc3:c376:9956";
};
roles.server.settings = {
tld = "home.rpqt.fr";
};
roles.default.machines.genepi.settings = {
ip = "fd28:387a:90:c400:ab23:3d38:a148:f539"; # FIXME: IPv4 expected (A record)
services = [
"actual"
"assistant"
"glance"
"grafana"
"images"
"lounge"
"pinchflat"
"rss"
];
};
};
} }

View File

@@ -0,0 +1,73 @@
!!! Danger "Experimental"
This service is experimental and will change in the future.
This module enables hosting clan-internal services easily, which can be resolved
inside your VPN. This allows defining a custom top-level domain (e.g. `.clan`)
and exposing endpoints from a machine to others, which will be
accessible under `http://<service>.clan` in your browser.
The service consists of two roles:
- A `server` role: This is the DNS-server that will be queried when trying to
resolve clan-internal services. It defines the top-level domain.
- A `default` role: This does two things. First, it sets up the nameservers so
that clan-internal queries are resolved via the `server` machine, while
external queries are resolved as normal via DHCP. Second, it allows exposing
services (see example below).
## Example Usage
Here the machine `dnsserver` is designated as internal DNS-server for the TLD
`.foo`. `server01` will host an application that shall be reachable at
`http://one.foo` and `server02` is going to be reachable at `http://two.foo`.
`client` is any other machine that is part of the clan but does not host any
services.
When `client` tries to resolve `http://one.foo`, the DNS query will be
routed to `dnsserver`, which will answer with `192.168.1.3`. If it tries to
resolve some external domain (e.g. `https://clan.lol`), the query will not be
routed to `dnsserver` but resolved as before, via the nameservers advertised by
DHCP.
```nix
inventory = {
machines = {
dnsserver = { }; # 192.168.1.2
server01 = { }; # 192.168.1.3
server02 = { }; # 192.168.1.4
client = { }; # 192.168.1.5
};
instances = {
coredns = {
module.name = "@clan/coredns";
module.input = "self";
# Add the default role to all machines, including `client`
roles.default.tags.all = { };
# DNS server queries to http://<name>.foo are resolved here
roles.server.machines."dnsserver".settings = {
ip = "192.168.1.2";
tld = "foo";
};
# First service
# Registers http://one.foo will resolve to 192.168.1.3
# underlying service runs on server01
roles.default.machines."server01".settings = {
ip = "192.168.1.3";
services = [ "one" ];
};
# Second service
roles.default.machines."server02".settings = {
ip = "192.168.1.4";
services = [ "two" ];
};
};
};
};
```

View File

@@ -0,0 +1,233 @@
{ ... }:
{
_class = "clan.service";
manifest.name = "coredns";
manifest.description = "Clan-internal DNS and service exposure";
manifest.categories = [ "Network" ];
manifest.readme = builtins.readFile ./README.md;
roles.server = {
description = "A DNS server that resolves services in the clan network.";
interface =
{ lib, ... }:
{
options.tld = lib.mkOption {
type = lib.types.str;
default = "clan";
description = ''
Top-level domain for this instance. All services below this will be
resolved internally.
'';
};
options.ip = lib.mkOption {
type = lib.types.str;
# TODO: Set a default
description = "IP for the DNS to listen on";
};
options.dnsPort = lib.mkOption {
type = lib.types.int;
default = 1053;
description = "Port of the clan-internal DNS server";
};
};
perInstance =
{
roles,
settings,
...
}:
{
nixosModule =
{
lib,
pkgs,
...
}:
let
hostServiceEntries =
host:
lib.strings.concatStringsSep "\n" (
map (
service:
let
ip = roles.default.machines.${host}.settings.ip;
isIPv4 = addr: (builtins.match "\\." addr) != null;
recordType = if (isIPv4 ip) then "A" else "AAAA";
in
"${service} IN ${recordType} ${ip} ; ${host}"
) roles.default.machines.${host}.settings.services
);
hostnameEntries = ''
crocus 10800 IN AAAA fd28:387a:90:c400:6db2:dfc3:c376:9956
genepi 10800 IN AAAA fd28:387a:90:c400:ab23:3d38:a148:f539
verbena 10800 IN AAAA fd28:387a:90:c400::1
haze 10800 IN AAAA fd28:387a:90:c400:840e:e9db:4c08:b920
'';
zonefile = builtins.toFile "${settings.tld}.zone" (
''
$TTL 3600 ; 1 Hour
$ORIGIN ${settings.tld}.
${settings.tld}. IN SOA ns1 admin.rpqt.fr. (
2025112300 ; serial
10800 ; refresh
3600 ; retry
604800 ; expire
300 ; minimum
)
${builtins.concatStringsSep "\n" (
lib.lists.imap1 (i: _m: "@ 1D IN NS ns${toString i}.${settings.tld}.") (
lib.attrNames roles.server.machines
)
)}
${builtins.concatStringsSep "\n" (
lib.lists.imap1 (i: m: "ns${toString i} 10800 IN CNAME ${m}.${settings.tld}.") (
lib.attrNames roles.server.machines
)
)}
''
+ hostnameEntries
+ "\n"
+ (lib.strings.concatStringsSep "\n" (
map (host: hostServiceEntries host) (lib.attrNames roles.default.machines)
))
);
in
{
networking.firewall.interfaces.wireguard = {
allowedTCPPorts = [ settings.dnsPort ];
allowedUDPPorts = [ settings.dnsPort ];
};
services.coredns = {
enable = true;
config =
let
dnsPort = builtins.toString settings.dnsPort;
in
''
.:${dnsPort} {
forward . 1.1.1.1
cache 30
}
${settings.tld}:${dnsPort} {
file ${zonefile}
}
'';
};
};
};
};
roles.default = {
description = "A machine that registers the 'server' role as resolver and registers services under the configured TLD in the resolver.";
interface =
{ lib, ... }:
{
options.services = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Service endpoints this host exposes (without TLD). Each entry will
be resolved to <entry>.<tld> using the configured top-level domain.
'';
};
options.ip = lib.mkOption {
type = lib.types.str;
# TODO: Set a default
description = "IP on which the services will listen";
};
options.dnsPort = lib.mkOption {
type = lib.types.int;
default = 1053;
description = "Port of the clan-internal DNS server";
};
};
perInstance =
{ roles, settings, ... }:
{
nixosModule =
{ config, lib, ... }:
{
networking.nameservers = map (
m:
let
port = config.services.unbound.settings.port or 53;
in
"127.0.0.1:${toString port}#${roles.server.machines.${m}.settings.tld}"
) (lib.attrNames roles.server.machines);
services.resolved.domains = map (m: "~${roles.server.machines.${m}.settings.tld}") (
lib.attrNames roles.server.machines
);
services.unbound = {
enable = true;
resolveLocalQueries = true;
checkconf = true;
settings = {
server = {
# port = 5353;
verbosity = 2;
interface = [ "127.0.0.1" ];
access-control = [ "127.0.0.0/8 allow" ];
do-not-query-localhost = "no";
domain-insecure = map (m: "${roles.server.machines.${m}.settings.tld}.") (
lib.attrNames roles.server.machines
);
};
# Default: forward everything else to DHCP-provided resolvers
# forward-zone = [
# {
# name = ".";
# forward-addr = "127.0.0.53@53"; # Forward to systemd-resolved
# }
# ];
forward-zone = [
{
name = ".";
forward-tls-upstream = true;
forward-addr = [
"9.9.9.9#dns.quad9.net"
"149.112.112.112#dns.quad9.net"
"1.1.1.1@853#cloudflare-dns.com"
"1.0.0.1@853#cloudflare-dns.com"
"2606:4700:4700::1111@853#cloudflare-dns.com"
"2606:4700:4700::1001@853#cloudflare-dns.com"
"8.8.8.8#dns.google"
"8.8.4.4#dns.google"
"2001:4860:4860::8888#dns.google"
"2001:4860:4860::8844#dns.google"
];
}
];
stub-zone = {
name = "${roles.server.machines.${(lib.head (lib.attrNames roles.server.machines))}.settings.tld}.";
stub-addr = map (
m: "${roles.server.machines.${m}.settings.ip}@${builtins.toString settings.dnsPort}"
) (lib.attrNames roles.server.machines);
};
};
};
};
};
};
}

View File

@@ -0,0 +1,18 @@
{ ... }:
let
module = ./default.nix;
in
{
clan.modules = {
"@rpqt/coredns" = module;
};
# perSystem =
# { ... }:
# {
# clan.nixosTests.coredns = {
# imports = [ ./tests/vm/default.nix ];
# clan.modules."@rpqt/coredns" = module;
# };
# };
}

View File

@@ -1,6 +1,7 @@
{ {
imports = [ imports = [
./buildbot/flake-module.nix ./buildbot/flake-module.nix
./coredns/flake-module.nix
./prometheus/flake-module.nix ./prometheus/flake-module.nix
]; ];
} }

43
flake.lock generated
View File

@@ -250,48 +250,6 @@
"type": "github" "type": "github"
} }
}, },
"ignis": {
"inputs": {
"ignis-gvc": "ignis-gvc",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762970543,
"narHash": "sha256-7ipFVC9pvI564c22b1dIEzSQ8dZXK3cxh/tF/4tX38c=",
"owner": "ignis-sh",
"repo": "ignis",
"rev": "ba8b0e11c2462afc9fdc30ce6a72b4e94e8ee7c4",
"type": "github"
},
"original": {
"owner": "ignis-sh",
"repo": "ignis",
"type": "github"
}
},
"ignis-gvc": {
"inputs": {
"nixpkgs": [
"ignis",
"nixpkgs"
]
},
"locked": {
"lastModified": 1754064086,
"narHash": "sha256-ft5KvY2OYrWF+jEsfBL/Zx8Iuo2C10C6COk8wHwZw34=",
"owner": "ignis-sh",
"repo": "ignis-gvc",
"rev": "f2c9f10d8b49cc38106a2f07a51ea959c6aa4e63",
"type": "github"
},
"original": {
"owner": "ignis-sh",
"repo": "ignis-gvc",
"type": "github"
}
},
"impermanence": { "impermanence": {
"locked": { "locked": {
"lastModified": 1737831083, "lastModified": 1737831083,
@@ -467,7 +425,6 @@
"disko": "disko_2", "disko": "disko_2",
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_2",
"home-manager": "home-manager", "home-manager": "home-manager",
"ignis": "ignis",
"impermanence": "impermanence", "impermanence": "impermanence",
"matugen": "matugen", "matugen": "matugen",
"nixos-generators": "nixos-generators", "nixos-generators": "nixos-generators",

View File

@@ -49,9 +49,6 @@
clan-core.inputs.nixpkgs.follows = "nixpkgs"; clan-core.inputs.nixpkgs.follows = "nixpkgs";
clan-core.inputs.flake-parts.follows = "flake-parts"; clan-core.inputs.flake-parts.follows = "flake-parts";
ignis.url = "github:ignis-sh/ignis";
ignis.inputs.nixpkgs.follows = "nixpkgs";
matugen.url = "github:InioX/Matugen"; matugen.url = "github:InioX/Matugen";
matugen.inputs.nixpkgs.follows = "nixpkgs"; matugen.inputs.nixpkgs.follows = "nixpkgs";

View File

@@ -1,6 +1,7 @@
{ {
self, self,
config, config,
osConfig,
pkgs, pkgs,
... ...
}: }:
@@ -32,9 +33,12 @@
programs.zoxide.enable = true; programs.zoxide.enable = true;
programs.starship.enable = true; programs.starship.enable = true;
programs.atuin.enable = true;
programs.bat.enable = true; programs.bat.enable = true;
programs.atuin.enable = true;
xdg.dataFile."atuin/key".source =
config.lib.file.mkOutOfStoreSymlink osConfig.clan.core.vars.generators.atuin.files.key.path;
programs.zsh = { programs.zsh = {
enable = true; enable = true;
syntaxHighlighting.enable = true; syntaxHighlighting.enable = true;

View File

@@ -1,38 +0,0 @@
{
self,
config,
inputs,
pkgs,
...
}:
{
imports = [
self.homeManagerModules.dotfiles
inputs.ignis.homeManagerModules.default
];
home.packages = [
pkgs.brightnessctl
pkgs.swaybg
pkgs.swaylock
pkgs.tofi
pkgs.wl-gammarelay-rs
inputs.matugen.packages.${pkgs.system}.default
];
programs.ignis = {
enable = true;
addToPythonEnv = false;
sass.enable = true;
sass.useDartSass = true;
services.bluetooth.enable = true;
services.audio.enable = true;
services.network.enable = true;
};
xdg.configFile."ignis".source =
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/rep/heath";
}

View File

@@ -7,8 +7,6 @@
./radicle.nix ./radicle.nix
self.nixosModules.nix-defaults self.nixosModules.nix-defaults
../../modules/remote-builder.nix ../../modules/remote-builder.nix
../../modules/unbound.nix
../../modules/unbound-auth.nix
self.inputs.srvos.nixosModules.server self.inputs.srvos.nixosModules.server
self.inputs.srvos.nixosModules.hardware-hetzner-cloud self.inputs.srvos.nixosModules.hardware-hetzner-cloud
]; ];

View File

@@ -20,8 +20,6 @@
../../modules/acme-home.nix ../../modules/acme-home.nix
../../modules/lounge.nix ../../modules/lounge.nix
../../modules/unbound.nix
../../modules/unbound-auth.nix
self.nixosModules.nix-defaults self.nixosModules.nix-defaults
self.nixosModules.user-rpqt self.nixosModules.user-rpqt

View File

@@ -17,6 +17,7 @@
./syncthing.nix ./syncthing.nix
self.nixosModules.desktop self.nixosModules.desktop
self.nixosModules.dev
self.nixosModules.nix-defaults self.nixosModules.nix-defaults
self.inputs.home-manager.nixosModules.home-manager self.inputs.home-manager.nixosModules.home-manager

View File

@@ -2,12 +2,28 @@
{ {
imports = [ imports = [
self.nixosModules.nix-defaults self.nixosModules.nix-defaults
../../modules/unbound.nix
../../modules/unbound-auth.nix
self.nixosModules.nextcloud self.nixosModules.nextcloud
self.nixosModules.gitea self.nixosModules.gitea
self.inputs.srvos.nixosModules.server self.inputs.srvos.nixosModules.server
{
# Add Pixel-7a as external device for clan wireguard network
networking.wireguard.interfaces.wireguard = {
ips = [ "100.42.42.1/32" ];
peers = [
{
publicKey = "BVgDQM18SfNofQsWs7m6fblaTB04Gk74VxR/zK8AKQ4=";
allowedIPs =
let
suffix = "cafe:cafe";
in
[ "fd28:387a:90:c400:${suffix}::/96" ];
persistentKeepalive = 25;
}
];
};
}
]; ];
nixpkgs.hostPlatform = "x86_64-linux"; nixpkgs.hostPlatform = "x86_64-linux";

6
modules/dev.nix Normal file
View File

@@ -0,0 +1,6 @@
{
clan.core.vars.generators.atuin = {
prompts.key.persist = true;
files.key.owner = "rpqt";
};
}

View File

@@ -9,6 +9,7 @@
./desktop.nix ./desktop.nix
]; ];
dev.imports = [ ./dev.nix ];
nix-defaults.imports = [ ./nix-defaults.nix ]; nix-defaults.imports = [ ./nix-defaults.nix ];
tailscale.imports = [ ./tailscale.nix ]; tailscale.imports = [ ./tailscale.nix ];
user-rpqt.imports = [ ./user-rpqt.nix ]; user-rpqt.imports = [ ./user-rpqt.nix ];

View File

@@ -1,35 +0,0 @@
{
services.unbound = {
settings = {
auth-zone = [
{
name = "home.rpqt.fr.";
zonefile = builtins.toFile "home.rpqt.fr.zone" ''
$TTL 3600 ; 1 Hour
$ORIGIN home.rpqt.fr.
home.rpqt.fr. IN SOA ns1 admin.rpqt.fr. (
2025063000 ; serial
10800 ; refresh
3600 ; retry
604800 ; expire
300 ; minimum
)
@ 1D IN NS ns1.home.rpqt.fr.
@ 1D IN NS ns2.home.rpqt.fr.
@ 1D IN NS ns3.home.rpqt.fr.
ns1 10800 IN CNAME crocus.home.rpqt.fr.
ns2 10800 IN CNAME genepi.home.rpqt.fr.
ns3 10800 IN CNAME verbena.home.rpqt.fr.
crocus 10800 IN AAAA fd80:150d:17cc:2ae:6999:9380:150d:17cc
genepi 10800 IN AAAA fd80:150d:17cc:2ae:6999:9358:3e0e:d738
verbena 10800 IN AAAA fd80:150d:17cc:2ae:6999:9306:9a0e:c197
haze 10800 IN AAAA fd80:150d:17cc:2ae:6999:935a:e8:b04d
'';
}
];
};
};
}

View File

@@ -1,108 +0,0 @@
{
self,
config,
lib,
...
}:
let
domain = "home.rpqt.fr";
machines = {
genepi = {
subdomains = [
"actual"
"assistant"
"glance"
"grafana"
"images"
"lounge"
"pinchflat"
"rss"
];
};
crocus = {
subdomains = [
"cloud"
];
};
};
zerotierInterface = "zts7mq7onf";
machinesZerotierIpRecords =
lib.map
(
host:
''"${host}.infra.rpqt.fr. 10800 IN AAAA ${
self.nixosConfigurations.${host}.config.clan.core.vars.generators.zerotier.files.zerotier-ip.value
}"''
)
[
"crocus"
"genepi"
];
in
{
services.resolved.enable = false;
networking.firewall.interfaces.${zerotierInterface} = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
services.unbound = {
enable = true;
resolveLocalQueries = true;
checkconf = true;
settings = {
server = {
interface = [
"127.0.0.1"
"::1"
"::0"
];
access-control = [
"127.0.0.1 allow"
"${config.clan.core.networking.zerotier.subnet} allow"
];
local-zone = [
''"*.home.rpqt.fr." redirect''
];
local-data =
# machinesZerotierIpRecords ++
lib.concatMap (
host:
lib.map (
subdomain:
''"${subdomain}.${domain}. 10800 IN AAAA ${
self.nixosConfigurations.${host}.config.clan.core.vars.generators.zerotier.files.zerotier-ip.value
}"''
) machines.${host}.subdomains
) (lib.attrNames machines);
private-address = [
"127.0.0.1/8"
"${config.clan.core.networking.zerotier.subnet}"
];
private-domain = [
"home.rpqt.fr"
];
};
forward-zone = [
{
name = ".";
forward-tls-upstream = true;
forward-addr = [
"9.9.9.9#dns.quad9.net"
"149.112.112.112#dns.quad9.net"
"1.1.1.1@853#cloudflare-dns.com"
"1.0.0.1@853#cloudflare-dns.com"
"2606:4700:4700::1111@853#cloudflare-dns.com"
"2606:4700:4700::1001@853#cloudflare-dns.com"
"8.8.8.8#dns.google"
"8.8.4.4#dns.google"
"2001:4860:4860::8888#dns.google"
"2001:4860:4860::8844#dns.google"
];
}
];
};
};
}

View File

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

View File

@@ -0,0 +1,18 @@
{
"data": "ENC[AES256_GCM,data:fF1De3CumRtONLJXCxgV/DDy0DbWdEDcgTEf9PB8nI/czxsoe+iQ1nbbHrNkG9ZqJQl72nXL3+y6g4OssZ44aQ==,iv:0oCUhhAJNj7fQLxqLlSiF+rWpDrk/C/WX9+fwWnNeM8=,tag:FzXudt/aGN93XSfaLRMHwQ==,type:str]",
"sops": {
"age": [
{
"recipient": "age1mqnmzn203hyj200psc982ehcedjmcdz8s0ncc50fm9jszjx7rgmqqmppw5",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsUjB3RG5rcXg1cVpRR3Zw\nUTNQNDRLSDdzbW9NbXpxUXJjT0s0RUxzRGx3CkVrc0kyRm8xVmFYOHpGZEhwV1RJ\ncWpNN050TjRTYzVCSk00QmdBa2ZQTDgKLS0tIGpCa0wyWW84RmpTZEw4aHg0ZWps\nYU9tZWRLUG5RMWF6bzJaNDJlV1BzY2cKoYyLtxVzwpqwaajgVgQvuDKHM+uU38vT\n/dVUjz54J4+/HQPd65TO6CIvMRQXu7ebsWxsuPAvJYIgeHD7mEbpgA==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1xkp0rmm5xwxurdxq3a0lxc77pjh5z4dylddvnf6ktrghyfhcxq4sdk3ysn",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaREM4NFh3Kzh0cTd5eitK\nV0VpbndEMkNQaW91aHRaY29PTGVWWEJOQmhBCkU3eXpYdnNPZ0hBQjk0Z2huRXdj\nYmxsNUZ4cnlyUzhsTnkvaFBaWUZsZVEKLS0tIFNoU0FQWFZjcnp0M0JaREZxc3Zi\neUxxUDc2R0lkckp5V1p2SXlqNlBjTVkK+9utF0LvMNO3bTw2Ky3Eprna0rBIR83y\nGgKN6buuPN6xp5RNUixmvc9lEvmX+RwSQzs8MTnhCOQEmcP1vvg+aw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-11-27T14:56:54Z",
"mac": "ENC[AES256_GCM,data:T9Z5jMebdm0UuRsnrFXYxg0Yylvn5So0ZqaEo+3axRMfjq5MS/sikz/nRGhgD5h9OTRk3tWndBB8aUO9u8QE+s+L4jM+wHSD1cI0+mc4/UuQgcs09tVxtCSFED0ETp8Iay0lhl7+yKpImPys0RpGNd4Wjn1qqExXqQ4M5aYcBWQ=,iv:85NKSnnrOGUtrriky0twzhLstnkjFkL11YxXjsgF+Js=,tag:VXb0q5eZFLxW7yE+SLZlYQ==,type:str]",
"version": "3.11.0"
}
}

View File

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