Compare commits
1 Commits
hgl-site
...
test-updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71407f88bf |
33
clanServices/yggdrasil/README.md
Normal file
33
clanServices/yggdrasil/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across
|
||||
your clan.
|
||||
|
||||
Yggdrasil is designed to be a future-proof and decentralised alternative to
|
||||
the structured routing protocols commonly used today on the internet. Inside
|
||||
your clan, it will allow you reaching all of your machines.
|
||||
|
||||
## Example Usage
|
||||
|
||||
While you can specify statically configured peers for each host, yggdrasil does
|
||||
auto-discovery of local peers.
|
||||
|
||||
```nix
|
||||
inventory = {
|
||||
|
||||
machines = {
|
||||
peer1 = { };
|
||||
peer2 = { };
|
||||
};
|
||||
|
||||
instances = {
|
||||
yggdrasil = {
|
||||
|
||||
# Deploy on all machines
|
||||
roles.default.tags.all = { };
|
||||
|
||||
# Or individual hosts
|
||||
roles.default.machines.peer1 = { };
|
||||
roles.default.machines.peer2 = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
116
clanServices/yggdrasil/default.nix
Normal file
116
clanServices/yggdrasil/default.nix
Normal file
@@ -0,0 +1,116 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/yggdrasil";
|
||||
manifest.description = "Yggdrasil encrypted IPv6 routing overlay network";
|
||||
|
||||
roles.default = {
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options.extraMulticastInterfaces = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Additional interfaces to use for Multicast. See
|
||||
https://yggdrasil-network.github.io/configurationref.html#multicastinterfaces
|
||||
for reference.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
Regex = "(wg).*";
|
||||
Beacon = true;
|
||||
Listen = true;
|
||||
Port = 5400;
|
||||
Priority = 1020;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
options.peers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Static peers to configure for this host.
|
||||
If not set, local peers will be auto-discovered
|
||||
'';
|
||||
example = [
|
||||
"tcp://192.168.1.1:6443"
|
||||
"quic://192.168.1.1:6443"
|
||||
"tls://192.168.1.1:6443"
|
||||
"ws://192.168.1.1:6443"
|
||||
];
|
||||
};
|
||||
};
|
||||
perInstance =
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule =
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
clan.core.vars.generators.yggdrasil = {
|
||||
|
||||
files.privateKey = { };
|
||||
files.publicKey.secret = false;
|
||||
files.address.secret = false;
|
||||
|
||||
runtimeInputs = with pkgs; [
|
||||
yggdrasil
|
||||
jq
|
||||
openssl
|
||||
];
|
||||
|
||||
script = ''
|
||||
# Generate private key
|
||||
openssl genpkey -algorithm Ed25519 -out $out/privateKey
|
||||
|
||||
# Generate corresponding public key
|
||||
openssl pkey -in $out/privateKey -pubout -out $out/publicKey
|
||||
|
||||
# Derive IPv6 address from key
|
||||
echo "{ \"PrivateKeyPath\": \"$out/privateKey\"}" | yggdrasil -useconf -address > $out/address
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.yggdrasil.serviceConfig.BindReadOnlyPaths = [
|
||||
"${config.clan.core.vars.generators.yggdrasil.files.privateKey.path}:/var/lib/yggdrasil/key"
|
||||
];
|
||||
|
||||
services.yggdrasil = {
|
||||
enable = true;
|
||||
openMulticastPort = true;
|
||||
persistentKeys = true;
|
||||
settings = {
|
||||
PrivateKeyPath = "/var/lib/yggdrasil/key";
|
||||
IfName = "ygg";
|
||||
Peers = settings.peers;
|
||||
MulticastInterfaces = [
|
||||
# Ethernet is preferred over WIFI
|
||||
{
|
||||
Regex = "(eth|en).*";
|
||||
Beacon = true;
|
||||
Listen = true;
|
||||
Port = 5400;
|
||||
Priority = 1024;
|
||||
}
|
||||
{
|
||||
Regex = "(wl).*";
|
||||
Beacon = true;
|
||||
Listen = true;
|
||||
Port = 5400;
|
||||
Priority = 1025;
|
||||
}
|
||||
]
|
||||
++ settings.extraMulticastInterfaces;
|
||||
};
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 5400 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
24
clanServices/yggdrasil/flake-module.nix
Normal file
24
clanServices/yggdrasil/flake-module.nix
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
self,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
module = lib.modules.importApply ./default.nix {
|
||||
inherit (self) packages;
|
||||
};
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
yggdrasil = module;
|
||||
};
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
clan.nixosTests.yggdrasil = {
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules.yggdrasil = module;
|
||||
};
|
||||
};
|
||||
}
|
||||
93
clanServices/yggdrasil/tests/vm/default.nix
Normal file
93
clanServices/yggdrasil/tests/vm/default.nix
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
name = "yggdrasil";
|
||||
|
||||
clan = {
|
||||
test.useContainers = false;
|
||||
directory = ./.;
|
||||
inventory = {
|
||||
|
||||
machines.peer1 = { };
|
||||
machines.peer2 = { };
|
||||
|
||||
instances."yggdrasil" = {
|
||||
module.name = "yggdrasil";
|
||||
module.input = "self";
|
||||
|
||||
# Assign the roles to the two machines
|
||||
roles.default.machines.peer1 = { };
|
||||
roles.default.machines.peer2 = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# TODO remove after testing, this is just to make @pinpox' life easier
|
||||
nodes =
|
||||
let
|
||||
c =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = with pkgs; [ net-tools ];
|
||||
console = {
|
||||
font = "Lat2-Terminus16";
|
||||
keyMap = "colemak";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
peer1 = c;
|
||||
peer2 = c;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# Wait for both machines to be ready
|
||||
peer1.wait_for_unit("multi-user.target")
|
||||
peer2.wait_for_unit("multi-user.target")
|
||||
|
||||
# Check that yggdrasil service is running on both machines
|
||||
peer1.wait_for_unit("yggdrasil")
|
||||
peer2.wait_for_unit("yggdrasil")
|
||||
|
||||
peer1.succeed("systemctl is-active yggdrasil")
|
||||
peer2.succeed("systemctl is-active yggdrasil")
|
||||
|
||||
# Check that both machines have yggdrasil network interfaces
|
||||
# Yggdrasil creates a tun interface (usually tun0)
|
||||
peer1.wait_until_succeeds("ip link show | grep -E 'ygg'", 30)
|
||||
peer2.wait_until_succeeds("ip link show | grep -E 'ygg'", 30)
|
||||
|
||||
# Get yggdrasil IPv6 addresses from both machines
|
||||
peer1_ygg_ip = peer1.succeed("yggdrasilctl -json getself | jq -r '.address'").strip()
|
||||
peer2_ygg_ip = peer2.succeed("yggdrasilctl -json getself | jq -r '.address'").strip()
|
||||
|
||||
|
||||
# TODO: enable this check. Values don't match up yet, but I can't
|
||||
# update-vars to test, because the script is borken.
|
||||
|
||||
# Compare runtime addresses with saved addresses from vars
|
||||
# expected_peer1_ip = "${builtins.readFile ./vars/per-machine/peer1/yggdrasil/address/value}"
|
||||
# expected_peer2_ip = "${builtins.readFile ./vars/per-machine/peer2/yggdrasil/address/value}"
|
||||
|
||||
print(f"peer1 yggdrasil IP: {peer1_ygg_ip}")
|
||||
print(f"peer2 yggdrasil IP: {peer2_ygg_ip}")
|
||||
|
||||
# print(f"peer1 expected IP: {expected_peer1_ip}")
|
||||
# print(f"peer2 expected IP: {expected_peer2_ip}")
|
||||
#
|
||||
# # Verify that runtime addresses match expected addresses
|
||||
# assert peer1_ygg_ip == expected_peer1_ip, f"peer1 runtime IP {peer1_ygg_ip} != expected IP {expected_peer1_ip}"
|
||||
# assert peer2_ygg_ip == expected_peer2_ip, f"peer2 runtime IP {peer2_ygg_ip} != expected IP {expected_peer2_ip}"
|
||||
|
||||
# Wait a bit for the yggdrasil network to establish connectivity
|
||||
import time
|
||||
time.sleep(10)
|
||||
|
||||
# Test connectivity: peer1 should be able to ping peer2 via yggdrasil
|
||||
peer1.succeed(f"ping -6 -c 3 {peer2_ygg_ip}")
|
||||
|
||||
# Test connectivity: peer2 should be able to ping peer1 via yggdrasil
|
||||
peer2.succeed(f"ping -6 -c 3 {peer1_ygg_ip}")
|
||||
|
||||
'';
|
||||
}
|
||||
6
clanServices/yggdrasil/tests/vm/sops/machines/peer1/key.json
Executable file
6
clanServices/yggdrasil/tests/vm/sops/machines/peer1/key.json
Executable file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1r264u9yngfq8qkrveh4tn0rhfes02jfgrtqufdx4n4m3hs4rla2qx0rk4d",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
6
clanServices/yggdrasil/tests/vm/sops/machines/peer2/key.json
Executable file
6
clanServices/yggdrasil/tests/vm/sops/machines/peer2/key.json
Executable file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1p8kuf8s0nfekwreh4g38cgghp4nzszenx0fraeyky2me0nly2scstqunx8",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:3dolkgdLC4y5fps4gGb9hf4QhwkUUBodlMOKT+/+erO70FB/pzYBg0mQjQy/uqjINzfIiM32iwVDnx3/Yyz5BDRo2CK+83UGEi4=,iv:FRp1HqlU06JeyEXXFO5WxJWxeLnmUJRWGuFKcr4JFOM=,tag:rbi30HJuqPHdU/TqInGXmg==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoYXBxS1JuNW9NeC9YU0xY\nK2xQWDhUYjZ4VzZmeUw1aG9UN2trVnBGQ0J3Ckk0V3d0UFBkT0RnZjBoYjNRVEVW\nN2VEdCtUTUUwenhJSEErT0MyWDA2bHMKLS0tIHJJSzVtR3NCVXozbzREWjltN2ZG\nZm44Y1c4MWNIblcxbmt2YkdxVE10Z1UKmJKEjiYZ9U47QACkbacNTirQIcCvFjM/\nwVxSEVq524sK8LCyIEvsG4e3I3Kn0ybZjoth7J/jg7J4gb8MVw+leQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-16T08:13:06Z",
|
||||
"mac": "ENC[AES256_GCM,data:6HJDkg0AWz+zx5niSIyBAGGaeemwPOqTCA/Fa6VjjyCh1wOav3OTzy/DRBOCze4V52hMGV3ULrI2V7G7DdvQy6LqiKBTQX5ZbWm3IxLASamJBjUJ1LvTm97WvyL54u/l2McYlaUIC8bYDl1UQUqDMo9pN4GwdjsRNCIl4O0Z7KY=,iv:zkWfYuhqwKpZk/16GlpKdAi2qS6LiPvadRJmxp2ZW+w=,tag:qz1gxVnT3OjWxKRKss5W8w==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../users/admin
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:BW15ydnNpr0NIXu92nMsD/Y52BDEOsdZg2/fiM8lwSTJN3lEymrIBYsRrcPAnGpFb52d7oN8zdNz9WoW3f/Xwl136sWDz/sc0k4=,iv:7m77nOR/uXLMqXB5QmegtoYVqByJVFFqZIVOtlAonzg=,tag:8sUo9DRscNRajrk+CzHzHw==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLVWpnSlJOTVU4NWRMSCto\nS0RaR2RCTUJjT1J0VzRPVTdPL2N5Yjl3c0EwCmlabm1aSzdlV29nb3lrZFBEZXR6\nRjI2TGZUNW1KQ3pLbDFscUlKSnVBNWcKLS0tIDlLR1VFSTRHeWNiQ29XK1pUUnlr\nVkVHOXdJeHhpcldYNVhpK1V6Nng0eW8KSsqJejY1kll6bUBUngiolCB7OhjyI0Gc\nH+9OrORt/nLnc51eo/4Oh9vp/dvSZzuW9MOF9m0f6B3WOFRVMAbukQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-16T08:13:15Z",
|
||||
"mac": "ENC[AES256_GCM,data:dyLnGXBC4nGOgX2TrGhf8kI/+Et0PRy+Ppr228y3LYzgcmUunZl9R8+QXJN51OJSQ63gLun5TBw0v+3VnRVBodlhqTDtfACJ7eILCiArPJqeZoh5MR6HkF31yfqTRlXl1i6KHRPVWvjRIdwJ9yZVN1XNAUsxc7xovqS6kkkGPsA=,iv:7yXnpbU7Zf7GH1+Uimq8eXDUX1kO/nvTaGx4nmTrKdM=,tag:WNn9CUOdCAlksC0Qln5rVg==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../users/admin
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
25.11
|
||||
@@ -0,0 +1 @@
|
||||
200:91bb:f1ec:c580:6d52:70b3:4d60:7bf2
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:/YoEoYY8CmqK4Yk4fmZieIHIvRn779aikoo3+6SWI5SxuU8TLJVY9+Q7mRmnbCso/8RPMICWkZMIkfbxYi6Dwc4UFmLwPqCoeAYsFBiHsJ6QUoTm1qtDDfXcruFs8Mo93ZmJb7oJIC0a+sVbB5L1NsGmG3g+a+g=,iv:KrMjRIQXutv9WdNzI5VWD6SMDnGzs9LFWcG2d9a6XDg=,tag:x5gQN9FaatRBcHOyS2cicw==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwQ0FNU1c4RDNKTHRtMy8z\nSEtQRzFXTVFvcitMWjVlMURPVkxsZC9wU25nCmt4TS81bnJidzFVZkxEY0ovWUtm\nVk5PMjZEWVJCei9rVTJ2bG1ZNWJoZGMKLS0tIHgyTEhIdUQ3YnlKVi9lNVpUZ0dI\nd3BLL05oMXFldGVKbkpoaklscDJMR3MKpUl/KNPrtyt4/bu3xXUAQIkugQXWjlPf\nFqFc1Vnqxynd+wJkkd/zYs4XcOraogOUj/WIRXkqXgdDDoEqb/VIBg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1r264u9yngfq8qkrveh4tn0rhfes02jfgrtqufdx4n4m3hs4rla2qx0rk4d",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArOUdkd3VVSTU3NHZ6aURB\na2dYMXhyMmVLMDVlM0dzVHpxbUw3K3BFcVNzCm1LczFyd3BubGwvRVUwQ1Q0aWZR\nL1hlb1VpZ3JnTVQ4Zm9wVnlJYVNuL00KLS0tIHlMRVMyNW9rWG45bVVtczF3MVNq\nL2d2RXhEeVcyRVNmSUF6cks5VStxVkUKugI1iDei32852wNV/zPlyVwKJH1UXOlY\nFQq7dqMJMWI6a5F+z4UdaHvzyKxF2CWBG7DVnaUSpq7Q3uGmibsSOQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-16T08:13:07Z",
|
||||
"mac": "ENC[AES256_GCM,data:LIlgQgiQt9aHXagpXphxSnpju+DOxuBvPpz5Rr43HSwgbWFgZ8tqlH2C1xo2xsJIexWkc823J9txpy+PLFXSm4/NbQGbKSymjHNEIYaU1tBSQ0KZ+s22X3/ku3Hug7/MkEKv5JsroTEcu3FK6Fv7Mo0VWqUggenl9AsJ5BocUO4=,iv:LGOnpWsod1ek4isWVrHrS+ZOCPrhwlPliPOTiMVY0zY=,tag:tRuHBSd9HxOswNcqjvzg0w==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAtyIHCZ0/yVbHpllPwgaWIFQ3Kb4fYMcOujgVmttA7gM=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
25.11
|
||||
@@ -0,0 +1 @@
|
||||
200:bb1f:6f1c:1852:173a:cb5e:5726:870
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/peer2
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:b1dbaJQGr8mnISch0iej+FhMnYOIFxOJYCvWDQseiczltXsBetbYr+89co5Sp7wmhQrH3tlWaih3HZe294Y9j8XvwpNUtmW3RZHsU/6EWA50LKcToFGFCcEBM/Nz9RStQXnjwLbRSLFuMlfoQttUATB2XYSm+Ng=,iv:YCeE3KbHaBhR0q10qO8Og1LBT5OUjsIDxfclpcLJh6I=,tag:M7y9HAC+fh8Fe8HoqQrnbg==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1p8kuf8s0nfekwreh4g38cgghp4nzszenx0fraeyky2me0nly2scstqunx8",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3NTVOT2MxaDJsTXloVVcv\nellUdnVxSVdnZ1NBUGEwLzBiTGoyZENJdm1RClp5eHY3dkdVSzVJYk52dWFCQnlG\nclIrQUJ5RXRYTythWTFHR1NhVHlyMVkKLS0tIEFza3YwcUNiYUV5VWJQcTljY2ZR\nUnc3U1VubmZRTCtTTC9rd1kydnNYa00KqdwV3eRHA6Y865JXQ7lxbS6aTIGf/kQM\nqDFdiUdvEDqo19Df3QBJ7amQ1YjPqSIRbO8CJNPI8JqQJKTaBOgm9g==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzTmV0Skd5Zzk1SXc4ZDc3\nRi9wTVdDM1lTc3N0MXpNNVZjUWJ6VDZHd3hzCkpRZnNtSU14clkybWxvSEhST2py\nR29jcHdXSCtFRE02ejB0dzN1eGVQZ1kKLS0tIE9YVjJBRTg1SGZ5S3lYdFRUM3RW\nOGZjUEhURnJIVTBnZG43UFpTZkdseFUKOgHC10Rqf/QnzfCHUMEPb1PVo9E6qlpo\nW/F1I8ZqkFI8sWh54nilXeR8i8w+QCthliBxsxdDTv2FSxdnKNHu3A==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-16T08:13:15Z",
|
||||
"mac": "ENC[AES256_GCM,data:0byytsY3tFK3r4qhM1+iYe9KYYKJ8cJO/HonYflbB0iTD+oRBnnDUuChPdBK50tQxH8aInlvgIGgi45OMk7IrFBtBYQRgFBUR5zDujzel9hJXQvpvqgvRMkzA542ngjxYmZ74mQB+pIuFhlVJCfdTN+smX6N4KyDRj9d8aKK0Qs=,iv:DC8nwgUAUSdOCr8TlgJX21SxOPOoJKYeNoYvwj5b9OI=,tag:cbJ8M+UzaghkvtEnRCp+GA==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAonBIcfPW9GKaUNRs+8epsgQOShNbR9v26+3H80an2/c=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -113,6 +113,7 @@ nav:
|
||||
- reference/clanServices/users.md
|
||||
- reference/clanServices/wifi.md
|
||||
- reference/clanServices/wireguard.md
|
||||
- reference/clanServices/yggdrasil.md
|
||||
- reference/clanServices/zerotier.md
|
||||
- API: reference/clanServices/clan-service-author-interface.md
|
||||
|
||||
|
||||
Reference in New Issue
Block a user