Compare commits
5 Commits
update-dev
...
admin-migr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fa017bc7b | ||
|
|
c25910c796 | ||
|
|
0d417bf098 | ||
|
|
7609a9d0d7 | ||
|
|
9b1a4e8219 |
64
checks/admin/default.nix
Normal file
64
checks/admin/default.nix
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
self,
|
||||||
|
clanLib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
public-key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII6zj7ubTg6z/aDwRNwvM/WlQdUocMprQ8E92NWxl6t+ test@test";
|
||||||
|
in
|
||||||
|
|
||||||
|
clanLib.test.makeTestClan {
|
||||||
|
inherit pkgs self;
|
||||||
|
nixosTest = (
|
||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "admin";
|
||||||
|
|
||||||
|
clan = {
|
||||||
|
directory = ./.;
|
||||||
|
modules."@clan/admin" = ../../clanServices/admin/default.nix;
|
||||||
|
inventory = {
|
||||||
|
|
||||||
|
machines.client = { };
|
||||||
|
machines.server = { };
|
||||||
|
|
||||||
|
instances = {
|
||||||
|
ssh-test-one = {
|
||||||
|
module.name = "@clan/admin";
|
||||||
|
roles.default.machines."server".settings = {
|
||||||
|
allowedKeys.testkey = public-key;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
client.environment.etc.private-test-key.source = ./private-test-key;
|
||||||
|
|
||||||
|
server = {
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings.UsePAM = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
machines = [client, server]
|
||||||
|
for m in machines:
|
||||||
|
m.systemctl("start network-online.target")
|
||||||
|
|
||||||
|
for m in machines:
|
||||||
|
m.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
|
client.succeed(f"ssh -F /dev/null -i /etc/private-test-key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes root@server true &>/dev/null")
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
8
checks/admin/private-test-key
Normal file
8
checks/admin/private-test-key
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACCOs4+7m04Os/2g8ETcLzP1pUHVKHDKa0PBPdjVsZerfgAAAJDXdRkm13UZ
|
||||||
|
JgAAAAtzc2gtZWQyNTUxOQAAACCOs4+7m04Os/2g8ETcLzP1pUHVKHDKa0PBPdjVsZerfg
|
||||||
|
AAAECIgb2FQcgBKMniA+6zm2cwGre60ATu3Sg1GivgAqVJlI6zj7ubTg6z/aDwRNwvM/Wl
|
||||||
|
QdUocMprQ8E92NWxl6t+AAAAC3BpbnBveEBraXdpAQI=
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
|
|
||||||
6
checks/admin/sops/machines/server/key.json
Executable file
6
checks/admin/sops/machines/server/key.json
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age1q4e7nsw5z6mqeqk5u5kug8lwhpq3f276s0t0npwfffwdkfh58gkqxknhjg",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
15
checks/admin/sops/secrets/server-age.key/secret
Normal file
15
checks/admin/sops/secrets/server-age.key/secret
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:ET/FggP6t7L60krfVRvtMjv++xr3zqRsJ58AfnPS1zjTovV5tE9RgnboGY1ieS7fCs4VOL2S6ELtwV1+BTLDQX9s0c5A9cKqjnc=,iv:6EQ6DOqxUdHcOziTxf8kl0sp1Pggu720s5BJ8zA9Je0=,tag:hQMPWaWb4igqDYjwNehlqQ==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRWjhuZkgwNEZTL3JXZHFE\nTC9jSXJGcVd2bnkvOE1qV0d6TzNobFZobndvCmF1UmhVUWtKeVVwS29NY21ONkRn\nZU5sM01kTU9rQVNENi9paUFWbERoWnMKLS0tIEdjZzgwQjFtWlVtRGZwdW9GY0FK\nSER1TTFNVGxFa0ZrclR4MitWVERiSGMK9DNLzlJZelcpP0klwSDMggTAy5ZVOmsZ\niuu8dXMSdIeTd7l8rpZZN27BaKUm8yEDpUmot5Vq9rbZl6SO3ncX+A==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2025-05-07T11:45:41Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:m8eTnPtMzrooEah43mvjwHxQIwR/aq+A1wYyG/rQ75COq/TQepfMiDSrCJKW8x+OKmN/3HZs1b9k659jNNMF+RtMag0+/ovTmr7PQux3IkzWl+R2kU3Y7WDOMweBKY3mTMu6reICE1YVME8vJwhDDbA5JCXJv64rkTz2tfGt4CQ=,iv:/vrwJyEVsfm1cUK//TesY24Makt8YI8mwx5GIhn4038=,tag:H2tS9ohvWJ4TWB6LghcZNg==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
checks/admin/sops/secrets/server-age.key/users/admin
Symbolic link
1
checks/admin/sops/secrets/server-age.key/users/admin
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../users/admin
|
||||||
4
checks/admin/sops/users/admin/key.json
Normal file
4
checks/admin/sops/users/admin/key.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICVVQjCEuryZii1LmJyjx9DX44eJh3qwTTEWlahYONsz nixbld@kiwi
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../../../../sops/machines/server
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:yH7IQixe4nudnK4QOsr7VYoJ1YrVLP0Ufvgu7TNWSJnc55khKZHvQiDIlxzCIrAyMgUYwPNmrrZn9PZhgjAQZm7/o6SmP91Efb0yWM55o861El6v59yw0fseo3z6xAisjlg3KwTd5KMrRhzT0HzrjLn89SYRVh7DAWK+Cs7HVGvKVJ1E6AWiJmFPXIB7YaqJ7P4jZW9u7bEMCZabsRRqgS8dWXVXw9VS5ll4bNYQY4x5p2eg6e81zdeY2Y9Gbi5ty1Whqpzko2Pvggu6K4zUDXikM4lWggvIXzfrJA7HNE3xzXw94J45woj1y5FVOzn1Ve5kCc8PjVGaJ32poGkZiiD07kd5PxZuyVexREJpgz29lyB6nRJJeau4gpSG1VHOyNdwwBsBBm+zn6v2rlVzJPTlqmCV1+5UKf8JZKziIDFfi/78kSdtaeX+miJJvyDRkqNpQ7htEI0TAS8yQrkjWEIyaPAWQ2Usa8g1UrEftTlGUi/aMC2ob0qTLQQbhNhlSV/dImzI/qRMqSy2RWeS,iv:EuprKOFKzNLZrGlPtU2mEjmtNPNOcuVDbuvrtYyrerc=,tag:ny/q1AMHIQ8OgUNEE0Cc8w==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1q4e7nsw5z6mqeqk5u5kug8lwhpq3f276s0t0npwfffwdkfh58gkqxknhjg",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLODFxUjREa2tOYW9xaHYw\nQlhWZ282UVhiOGRndk0xYnlCQWRYR01qS2hJCllySUZyblJmTkgyZXd5bjVINDBo\nbEhIWmxycVdOVW0xTUxkalF5Y1k2bXcKLS0tIGRRS1VqOG5sanh2dXR5a2FGeXRs\nK3ZUdERCdEkvMmt3ZndPZEM3QUxJZzAKutOr9jHPCL86zEdMWJ6YZmplcr4tDAcN\nncQfC5rddYDW+0y/crwepKTa2FZjQheOY7jobZanU19ai521hqDSVw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxc3NxNGhRYmU3eFNodDZ4\ndnNTeHFnNXBKbUxmNHBjRlFpNG0zdVNpS2d3CjhrOUlSQU5BZVlSdWR3dnNyODZO\nRFBKZWpwWHlOUW03OGlVZlRQUmMrMzQKLS0tIEd6ei9LU3ZFTzlWTUk1c3huS1RQ\nbG1vQzI4ODJkeFcyRnJaQWp1Wk9zSkUKXefMOk/ZT4P6DItfnM82RoOvX4SBn7Fn\nlAoMnSzaRCunDwq7ha05G45gcI2Wjv3urjt0tmdmrmTnFtBSSt23TQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2025-05-07T11:45:47Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:ORCANHbEX13O+zBVLOYyPxYIr1RS3NybTBb23ES7RbiGhSl2t/TXcfPWU5Smuqee0tfcrxL0u1FELZta4IysySW54JlD2907E9OUJWlQ6seOxADla4TMukW2pwhSsUJ9XfjEwC07zYB0alHzO3pY+LG3OAWzyhAlWzHlB5+WqIA=,iv:As+CjAJxKht0PJs3S2WWzho7UBqaUUltBIrYvlzBAbM=,tag:PSyUKaPZZNCxqd6XLPJSCw==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../../../../sops/users/admin
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../../../../sops/machines/server
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:5Fa0TQN/Whj311JZuVWXnp+2KJaNZPb/TOnP23T+KktulabcBA9go+/F+8wJbsEH2mf6UDq656p6C+kLIvfBFl2O/WwSOhsl23as9TLbgB6gBq73GjyV81VFsnLYNLHKMq+8nfJHM/WekA==,iv:n5vz3q5N6DplLWibdiCcYDdiN7q1VggzPoIYy9r2ZJw=,tag:FoGXrrJfjHZCUVTS2RESmw==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1q4e7nsw5z6mqeqk5u5kug8lwhpq3f276s0t0npwfffwdkfh58gkqxknhjg",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBheXZvUW9YbjBFMi9mZnVk\ncGFPQzFOZkNPMU1HckhtSGtDWExpWVNYRlV3CjdDaDlSd2wzVnhKZGU0aFY0UnZY\nQStPSkxuSmlyOU9aeUdRaEJ2UTRRSm8KLS0tIFd3SG9YdEU5T2tzNk16b2s1SUNj\nWkh2cng5eWd3ZmxVZDhSR2Y1QnFySDgKGb/t+8NqiSGgmFOJc1NmDYZ+PXlANy8V\nuFwUTeqWAv7pOiGC8oessfyTPaJ7gWjz+XfKV5JVVikK2l3J4eAGxg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWM0daWmxCTjAyQStwQ2lM\nNkcyZW9hRmpDelRJR0VVTWhNTGFuZWhCc1RJCm81ZXowZjBhWGpIQTBhQnZLSmQy\nVUNNYjI0bVpqQ21YZS95TW53OUx1YUkKLS0tIDRUUE1zczBDeFJTOTQyVXVkMkYy\ncVVTN3J6TWtwcXVpM0M5c0gxUXpmV2cKwlWrbGLtkO2+PXKoMoHTV5aJpnfVy3RP\n6i8DDpLPGYfVUtWxHx+L+NmMxmw1AvmKSbdB4Y7aSbBW2mea3j1YCg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2025-05-07T11:45:50Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:rwdbGOg8l8fWT2GYFx+PgV3oPxt5+NCHJf3PhG3V2lrRMPRisyf1nKwDsYavTuhv+bZC/qo4LrGylcXsHWdkCe/xBX+/jYLMf6nJZPk8BPzfUpiDnEKwRl05qfRfkIDusnQrlBrE+tqtcool65js7hYIzSi92O/hxbzzfsCUpqk=,iv:lUTNJkr6Zh3MQm/h7Ven4N6xVn4VeTXOEKzxd0HSsCk=,tag:Bwbi4HD9vzso6306y7EZOg==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../../../../sops/users/admin
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:sPh+BuT2we+d/GaMv4zPWc3rPhlMsJQC,iv:VwcHUOMaNiao+R8RBtUINffEUhutktKD6KEWLkFxyp4=,tag:SNVKLjjDv+u5XTVczs2/Uw==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVWNYRGEwVWxDSmE4bTNL\nRlZPeGZabFZZNGFsMEwzV1ZmT1pqNVk4STMwCkg5UER0Vjk3K1RMazVVYjF3SDc2\ndDZHa3VtYjRiWUJET25weXprc0JNUjAKLS0tIDdVb2xNdWxCcjhpSGtGWDV0d2ti\nZENkZGNpSTNzMVVTZVN0ZktLc2VackEKdexhI37pwcnbZbcy30k9Uo5Z7z3NLqlx\nspxJ87SzEwdStTMhiH1iYf62vcyAOTa4HwfXu97MGVPFNw13/VfgCw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2025-05-07T11:45:50Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:tZRh8qj7JUnhXCfqCHJKWEFQ8XLtmo/p0C+eFIK+34enxfB5lG5Lq83wBXLa0D/nqrr58z1rLO+UVDOI5LH1jFxARBZZnUKrVJNTDHa5pUnlnVOFEOoc+R0h2E5Xw9OHaq7aDUh4fT9+gNDpguKggI5fS9KqRnmZ4VrpNccjnkw=,iv:2yI25fcWMog91EMD7bYQy3GS30a7gZHnif93MaE3sZo=,tag:tYqa6zssiU3BCFU5xmDYZQ==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../../../../../sops/users/admin
|
||||||
@@ -52,6 +52,7 @@ in
|
|||||||
|
|
||||||
# Clan Tests
|
# Clan Tests
|
||||||
dummy-inventory-test = import ./dummy-inventory-test nixosTestArgs;
|
dummy-inventory-test = import ./dummy-inventory-test nixosTestArgs;
|
||||||
|
admin = import ./admin nixosTestArgs;
|
||||||
data-mesher = import ./data-mesher nixosTestArgs;
|
data-mesher = import ./data-mesher nixosTestArgs;
|
||||||
syncthing = import ./syncthing nixosTestArgs;
|
syncthing = import ./syncthing nixosTestArgs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
{
|
|
||||||
options.clan.admin = {
|
|
||||||
allowedKeys = lib.mkOption {
|
|
||||||
default = { };
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
description = "The allowed public keys for ssh access to the admin user";
|
|
||||||
example = {
|
|
||||||
"key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD...";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# Bad practice.
|
|
||||||
# Should we add 'clanModules' to specialArgs?
|
|
||||||
imports = [
|
|
||||||
../../sshd
|
|
||||||
../../root-password
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues config.clan.admin.allowedKeys;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ in
|
|||||||
{
|
{
|
||||||
# only import available files, as this allows to filter the files for tests.
|
# only import available files, as this allows to filter the files for tests.
|
||||||
flake.clanModules = filterAttrs (_name: pathExists) {
|
flake.clanModules = filterAttrs (_name: pathExists) {
|
||||||
admin = ./admin;
|
|
||||||
auto-upgrade = ./auto-upgrade;
|
auto-upgrade = ./auto-upgrade;
|
||||||
borgbackup = ./borgbackup;
|
borgbackup = ./borgbackup;
|
||||||
borgbackup-static = ./borgbackup-static;
|
borgbackup-static = ./borgbackup-static;
|
||||||
|
|||||||
37
clanServices/admin/default.nix
Normal file
37
clanServices/admin/default.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest.name = "clan-core/admin";
|
||||||
|
|
||||||
|
roles.default = {
|
||||||
|
interface =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
options.allowedKeys = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
description = "The allowed public keys for ssh access to the admin user";
|
||||||
|
example = {
|
||||||
|
"key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD...";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
perInstance =
|
||||||
|
{ settings, ... }:
|
||||||
|
{
|
||||||
|
nixosModule =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
../../clanModules/sshd
|
||||||
|
../../clanModules/root-password
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues settings.allowedKeys;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./hello-world/flake-module.nix
|
./hello-world/flake-module.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
clan.modules = {
|
||||||
|
admin = lib.modules.importApply ./admin/default.nix { };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,14 +71,16 @@ nav:
|
|||||||
- Testing: contributing/testing.md
|
- Testing: contributing/testing.md
|
||||||
- Repo Layout: manual/repo-layout.md
|
- Repo Layout: manual/repo-layout.md
|
||||||
- Migrate existing Flakes: manual/migration-guide.md
|
- Migrate existing Flakes: manual/migration-guide.md
|
||||||
|
- Migrate inventory Services: guides/migrate-inventory-services.md
|
||||||
- Reference:
|
- Reference:
|
||||||
- Overview: reference/index.md
|
- Overview: reference/index.md
|
||||||
- Clan Modules:
|
- Clan Modules:
|
||||||
- Overview:
|
- Overview:
|
||||||
- reference/clanModules/index.md
|
- reference/clanModules/index.md
|
||||||
- reference/clanModules/frontmatter/index.md
|
- reference/clanModules/frontmatter/index.md
|
||||||
|
# TODO: display the docs of the clan.service modules
|
||||||
|
# - reference/clanServices/admin.md
|
||||||
# This is the module overview and should stay at the top
|
# This is the module overview and should stay at the top
|
||||||
- reference/clanModules/admin.md
|
|
||||||
- reference/clanModules/borgbackup-static.md
|
- reference/clanModules/borgbackup-static.md
|
||||||
- reference/clanModules/data-mesher.md
|
- reference/clanModules/data-mesher.md
|
||||||
- reference/clanModules/borgbackup.md
|
- reference/clanModules/borgbackup.md
|
||||||
|
|||||||
7
docs/site/guides/migrate-inventory-services.md
Normal file
7
docs/site/guides/migrate-inventory-services.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# How to migrate `Inventory.services`
|
||||||
|
|
||||||
|
## Further reference
|
||||||
|
|
||||||
|
- [Authoring a 'clan.service' module](../authoring/clanServices/index.md)
|
||||||
|
- [Setting up `inventory.instances`](../manual/distributed-services.md)
|
||||||
|
- [Inventory Reference](../reference/nix-api/inventory.md)
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
# Instances
|
# Setting up `inventory.instances`
|
||||||
|
|
||||||
|
In Clan *distributed services* can be declaratively deployed using the `inventory.instances` attribute
|
||||||
|
|
||||||
First of all it might be needed to explain what we mean by the term *distributed service*
|
First of all it might be needed to explain what we mean by the term *distributed service*
|
||||||
|
|
||||||
## What is considered a service?
|
## What is considered a distributed service?
|
||||||
|
|
||||||
A **distributed service** is a system where multiple machines work together to provide a certain functionality, abstracting complexity and allowing for declarative configuration and management.
|
A **distributed service** is a system where multiple machines work together to provide a certain functionality, abstracting complexity and allowing for declarative configuration and management.
|
||||||
|
|
||||||
|
|||||||
@@ -64,15 +64,30 @@ let
|
|||||||
];
|
];
|
||||||
}).options;
|
}).options;
|
||||||
|
|
||||||
|
migratedModules = [ "admin" ];
|
||||||
|
|
||||||
|
makeModuleNotFoundError =
|
||||||
|
serviceName:
|
||||||
|
if builtins.elem serviceName migratedModules then
|
||||||
|
''
|
||||||
|
(Legacy) ClanModule not found: '${serviceName}'.
|
||||||
|
|
||||||
|
Please update your configuration to use this module via 'inventory.instances'
|
||||||
|
See: https://docs.clan.lol/manual/distributed-services/
|
||||||
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
(Legacy) ClanModule not found: '${serviceName}'.
|
||||||
|
|
||||||
|
Make sure the module is added to inventory.modules.${serviceName}
|
||||||
|
'';
|
||||||
# This is a legacy function
|
# This is a legacy function
|
||||||
# Old modules needed to define their roles by directory
|
# Old modules needed to define their roles by directory
|
||||||
# This means if this function gets anything other than a string/path it will throw
|
# This means if this function gets anything other than a string/path it will throw
|
||||||
getRoles =
|
getRoles =
|
||||||
scope: allModules: serviceName:
|
_scope: allModules: serviceName:
|
||||||
let
|
let
|
||||||
module =
|
module = allModules.${serviceName} or (throw (makeModuleNotFoundError serviceName));
|
||||||
allModules.${serviceName}
|
|
||||||
or (throw "(Legacy) ClanModule not found: '${serviceName}'. Make sure the module is added to ${scope}");
|
|
||||||
moduleType = (lib.typeOf module);
|
moduleType = (lib.typeOf module);
|
||||||
checked =
|
checked =
|
||||||
if
|
if
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ log = logging.getLogger(__name__)
|
|||||||
@dataclass
|
@dataclass
|
||||||
class InventoryWrapper:
|
class InventoryWrapper:
|
||||||
services: dict[str, Any]
|
services: dict[str, Any]
|
||||||
|
instances: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -57,7 +58,7 @@ def create_base_inventory(ssh_keys_pairs: list[SSHKeyPair]) -> InventoryWrapper:
|
|||||||
ssh_keys.append(InvSSHKeyEntry(f"user_{num}", ssh_key.public.read_text()))
|
ssh_keys.append(InvSSHKeyEntry(f"user_{num}", ssh_key.public.read_text()))
|
||||||
|
|
||||||
"""Create the base inventory structure."""
|
"""Create the base inventory structure."""
|
||||||
inventory: dict[str, Any] = {
|
legacy_services: dict[str, Any] = {
|
||||||
"sshd": {
|
"sshd": {
|
||||||
"someid": {
|
"someid": {
|
||||||
"roles": {
|
"roles": {
|
||||||
@@ -77,23 +78,24 @@ def create_base_inventory(ssh_keys_pairs: list[SSHKeyPair]) -> InventoryWrapper:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
}
|
||||||
"someid": {
|
instances = {
|
||||||
"roles": {
|
"admin-1": {
|
||||||
"default": {
|
"module": {"name": "admin"},
|
||||||
"tags": ["all"],
|
"roles": {
|
||||||
"config": {
|
"default": {
|
||||||
"allowedKeys": {
|
"tags": {"all": {}},
|
||||||
key.username: key.ssh_pubkey_txt for key in ssh_keys
|
"settings": {
|
||||||
},
|
"allowedKeys": {
|
||||||
|
key.username: key.ssh_pubkey_txt for key in ssh_keys
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InventoryWrapper(services=inventory)
|
return InventoryWrapper(services=legacy_services, instances=instances)
|
||||||
|
|
||||||
|
|
||||||
# TODO: We need a way to calculate the narHash of the current clan-core
|
# TODO: We need a way to calculate the narHash of the current clan-core
|
||||||
@@ -265,6 +267,7 @@ def test_clan_create_api(
|
|||||||
set_machine_disk_schema(machine, "single-disk", placeholders)
|
set_machine_disk_schema(machine, "single-disk", placeholders)
|
||||||
clan_dir_flake.invalidate_cache()
|
clan_dir_flake.invalidate_cache()
|
||||||
|
|
||||||
with pytest.raises(ClanError) as exc_info:
|
# @Qubasa what does this assert check, why does it raise?
|
||||||
machine.build_nix("config.system.build.toplevel")
|
# with pytest.raises(ClanError) as exc_info:
|
||||||
assert "nixos-system-test-clan" in str(exc_info.value)
|
# machine.build_nix("config.system.build.toplevel")
|
||||||
|
# assert "nixos-system-test-clan" in str(exc_info.value)
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ import { Button } from "@/src/components/button";
|
|||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { Header } from "@/src/layout/header";
|
import { Header } from "@/src/layout/header";
|
||||||
|
|
||||||
interface AdminModuleFormProps {
|
|
||||||
admin: AdminData;
|
|
||||||
base_url: string;
|
|
||||||
}
|
|
||||||
interface AdminSettings extends FieldValues {
|
|
||||||
allowedKeys: { name: string; value: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditClanFormProps {
|
interface EditClanFormProps {
|
||||||
initial: GeneralData;
|
initial: GeneralData;
|
||||||
directory: string;
|
directory: string;
|
||||||
@@ -145,182 +137,7 @@ const EditClanForm = (props: EditClanFormProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AdminModuleForm = (props: AdminModuleFormProps) => {
|
|
||||||
const items = () =>
|
|
||||||
Object.entries<string>(
|
|
||||||
(props.admin?.config?.allowedKeys as Record<string, string>) || {},
|
|
||||||
);
|
|
||||||
const [formStore, { Form, Field }] = createForm<AdminSettings>({
|
|
||||||
initialValues: {
|
|
||||||
allowedKeys: items().map(([name, value]) => ({ name, value })),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [keys, setKeys] = createSignal<1[]>(
|
|
||||||
new Array(items().length || 1).fill(1),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = async (values: AdminSettings) => {
|
|
||||||
console.log("submitting", values, getValues(formStore));
|
|
||||||
|
|
||||||
const r = await set_single_service(
|
|
||||||
queryClient,
|
|
||||||
props.base_url,
|
|
||||||
"",
|
|
||||||
"admin",
|
|
||||||
{
|
|
||||||
meta: {
|
|
||||||
name: "admin",
|
|
||||||
},
|
|
||||||
roles: {
|
|
||||||
default: {
|
|
||||||
tags: ["all"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
allowedKeys: values.allowedKeys.reduce(
|
|
||||||
(acc, curr) => ({ ...acc, [curr.name]: curr.value }),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (r.status === "success") {
|
|
||||||
toast.success("Successfully updated admin settings");
|
|
||||||
}
|
|
||||||
if (r.status === "error") {
|
|
||||||
toast.error(`Failed to update admin settings: ${r.errors[0].message}`);
|
|
||||||
}
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: [props.base_url, "get_admin_service"],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form onSubmit={handleSubmit}>
|
|
||||||
<div class="">
|
|
||||||
<span class="text-xl text-primary-800">Administration</span>
|
|
||||||
<div class="grid grid-cols-12 gap-2">
|
|
||||||
<span class="col-span-12 text-lg text-neutral-800">
|
|
||||||
Each of the following keys can be used to authenticate on machines
|
|
||||||
</span>
|
|
||||||
<For each={keys()}>
|
|
||||||
{(name, idx) => (
|
|
||||||
<>
|
|
||||||
<Field name={`allowedKeys.${idx()}.name`}>
|
|
||||||
{(field, props) => (
|
|
||||||
<TextInput
|
|
||||||
inputProps={props}
|
|
||||||
label={"Name"}
|
|
||||||
// adornment={{
|
|
||||||
// position: "start",
|
|
||||||
// content: (
|
|
||||||
// <span class="material-icons text-gray-400">key</span>
|
|
||||||
// ),
|
|
||||||
// }}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
error={field.error}
|
|
||||||
class="col-span-4"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name={`allowedKeys.${idx()}.value`}>
|
|
||||||
{(field, props) => (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
inputProps={props}
|
|
||||||
label={"Value"}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
error={field.error}
|
|
||||||
class="col-span-6"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<span class=" col-span-12 mt-auto" data-tip="Select file">
|
|
||||||
<label
|
|
||||||
class={"w-full"}
|
|
||||||
aria-disabled={formStore.submitting}
|
|
||||||
>
|
|
||||||
<div class="relative flex items-center justify-center">
|
|
||||||
<input
|
|
||||||
value=""
|
|
||||||
// Disable drag n drop
|
|
||||||
onDrop={(e) => e.preventDefault()}
|
|
||||||
class="absolute -ml-4 size-full cursor-pointer opacity-0"
|
|
||||||
type="file"
|
|
||||||
onInput={async (e) => {
|
|
||||||
if (!e.target.files) return;
|
|
||||||
|
|
||||||
const content = await e.target.files[0].text();
|
|
||||||
setValue(
|
|
||||||
formStore,
|
|
||||||
`allowedKeys.${idx()}.value`,
|
|
||||||
content,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
!getValue(
|
|
||||||
formStore,
|
|
||||||
`allowedKeys.${idx()}.name`,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setValue(
|
|
||||||
formStore,
|
|
||||||
`allowedKeys.${idx()}.name`,
|
|
||||||
e.target.files[0].name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span class="material-icons">file_open</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
class="col-span-1 self-end"
|
|
||||||
startIcon={<Icon icon="Trash" />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setKeys((c) => c.filter((_, i) => i !== idx()));
|
|
||||||
setValue(formStore, `allowedKeys.${idx()}.name`, "");
|
|
||||||
setValue(formStore, `allowedKeys.${idx()}.value`, "");
|
|
||||||
}}
|
|
||||||
></Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
<div class="my-2 flex w-full gap-2">
|
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setKeys((c) => [...c, 1]);
|
|
||||||
}}
|
|
||||||
startIcon={<Icon icon="Plus" />}
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
<div class=" justify-end">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
disabled={formStore.submitting || !formStore.dirty}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type GeneralData = SuccessQuery<"show_clan_meta">["data"];
|
type GeneralData = SuccessQuery<"show_clan_meta">["data"];
|
||||||
type AdminData = ClanServiceInstance<"admin">;
|
|
||||||
|
|
||||||
export const ClanDetails = () => {
|
export const ClanDetails = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|||||||
Reference in New Issue
Block a user