Compare commits

...

40 Commits

Author SHA1 Message Date
Mic92
2bd9141d2d Merge pull request 'pinned-clan-core' (#3574) from pinned-clan-core into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3574
2025-05-12 12:02:58 +00:00
Jörg Thalheim
f788313e97 morph: don't depend on clan-core 2025-05-12 13:56:10 +02:00
Jörg Thalheim
89b70ffa6f checks/backup: depend on pinned clan core 2025-05-12 13:52:21 +02:00
Mic92
ed1692574f Merge pull request 'ci/update-clan-core-for-checks: bump gitea-create-pull-request' (#3573) from bump-update-flake into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3573
2025-05-12 11:46:01 +00:00
Jörg Thalheim
1106c50924 ci/update-clan-core-for-checks: bump gitea-create-pull-request 2025-05-12 13:41:16 +02:00
Mic92
e99e47da10 Merge pull request 'use a clan-core snapshots for ci tests' (#3572) from misc-fixes-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3572
2025-05-12 11:26:31 +00:00
Jörg Thalheim
67def050fd use a clan-core snapshots for ci tests
We currently have to re-run our integration tests a lot because they are
depending on the whole repository. This pull request changes locks the
clan-core used for vm tests. This has the caveat that we might not run
the latest NixOS machine of our profiles. On the upside we can test
behaviour against an older clan-core version and capture breakages and
make it backwards compatible. If we actually want to test the latest
version, the PR that changes the exposed flake api, could also bump the
clan-core snapshot.
2025-05-12 13:21:25 +02:00
Jörg Thalheim
c0d2787dee docs/testing: update to latest api 2025-05-12 13:03:06 +02:00
Mic92
ecc327277c Merge pull request 'Data-mesher: don't set owner for public vars' (#3571) from misc-fixes-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3571
2025-05-12 10:27:05 +00:00
Mic92
0064a8bfbc Merge pull request 'Add /bin/sh to bubblewrap sandbox' (#3551) from jfly/clan-core:bin-sh-in-sandbox into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3551
2025-05-12 10:07:56 +00:00
Jörg Thalheim
1e8b9def2a data-mesher: don't set owner for public_key 2025-05-12 12:06:05 +02:00
Jörg Thalheim
f0983ede5e move single dont-depend-on-repo-root check into checks
Doesn't seem to be a pattern yet with a single check.
2025-05-12 12:06:05 +02:00
Jörg Thalheim
10bc9e3e44 vars: improve warnings for non-public secrets 2025-05-12 12:06:05 +02:00
DavHau
556fd8845e Merge pull request 'GUI/machines: enable grid view by default' (#3570) from DavHau/clan-core:dave into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3570
2025-05-12 08:52:03 +00:00
DavHau
fab079af71 GUI/machines: enable grid view by default 2025-05-12 15:41:31 +07:00
DavHau
0370c1cf02 Merge pull request 'vars: cleanup sops file module' (#3569) from DavHau/clan-core:dave into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3569
2025-05-12 08:40:35 +00:00
DavHau
aa557f3a96 vars: cleanup sops file module 2025-05-12 15:30:23 +07:00
DavHau
e8699e68b5 Merge pull request 'gui/vars: fix vars screen doesn't appear on update' (#3567) from DavHau/clan-core:dave into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3567
2025-05-12 07:23:51 +00:00
hsjobeki
f8f31d430d Merge pull request 'Docs: update 'clan.service' examples' (#3568) from hsjobeki/clan-core:docs-1 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3568
2025-05-12 07:23:41 +00:00
DavHau
3d345e0bca gui/vars: fix vars screen doesn't appear on update 2025-05-12 13:52:16 +07:00
Michael Hoang
80711fcf72 Merge pull request 'cli: allow age-plugin-1p' (#3565) from push-uzmrpnklrmxw into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3565
2025-05-12 06:47:42 +00:00
Michael Hoang
35684090e3 cli: allow age-plugin-1p 2025-05-12 16:36:24 +10:00
renovate[bot]
8069b137f3 chore(deps): update data-mesher digest to 2666bb1 2025-05-12 00:50:11 +00:00
renovate[bot]
2fba6b15e8 chore(deps): update data-mesher digest to 20e20ac 2025-05-11 22:30:11 +00:00
renovate[bot]
cddee0ca86 chore(deps): update data-mesher digest to 61da4b5 2025-05-11 19:30:10 +00:00
renovate[bot]
0f3ab641d9 chore(deps): update treefmt-nix digest to 708ec80 2025-05-11 19:00:11 +00:00
Johannes Kirschbauer
d5f90b2730 Docs: update 'clan.service' examples 2025-05-11 15:46:16 +02:00
renovate[bot]
54335221d8 chore(deps): update data-mesher digest to f664c98 2025-05-11 11:30:21 +00:00
renovate[bot]
76b13476a5 chore(deps): update treefmt-nix digest to 4819332 2025-05-11 11:10:10 +00:00
Michael Hoang
b933dcf2e2 Merge pull request 'cli: fix machines update not outputting stdout of nixos-rebuild' (#3552) from push-zmlxvwnvrpuk into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3552
2025-05-10 07:15:23 +00:00
Michael Hoang
8a755fff8c cli: fix machines update not outputting stdout of nixos-rebuild 2025-05-10 17:02:56 +10:00
Jeremy Fleischman
5726dd1010 Add /bin/sh to bubblewrap sandbox
I ran into this error when trying to run `opendkim-genkey` in a vars
generator:

```console
=========================================================================== Command ===========================================================================
nix \
    --extra-experimental-features 'nix-command flakes' \
    shell \
    --inputs-from /nix/store/9r3ddw80dz4qzci9pj57ppbh6gy2pgv9-clan-cli/lib/python3.12/site-packages/clan_cli/nixpkgs \
    'nixpkgs#bash' \
    'nixpkgs#bubblewrap' \
    -c bwrap \
    --unshare-all --tmpfs \
    / \
    --ro-bind /nix/store \
    /nix/store \
    --dev /dev \
    --bind /tmp/nix-shell.ClOjgJ/vars-kh4qrnas \
    /tmp/nix-shell.ClOjgJ/vars-kh4qrnas \
    --chdir / \
    --bind /proc \
    /proc \
    --uid 1000 \
    --gid 1000 \
    -- bash \
    -c /nix/store/p0089w4y1w3h535g7ipv4jl4r6mb2hs2-generator-dkim-playground.jflei.com.mail

=========================================================================== Stderr ============================================================================
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
	LANGUAGE = (unset),
	LC_ALL = (unset),
	LC_CTYPE = (unset),
	LC_NUMERIC = (unset),
	LC_COLLATE = (unset),
	LC_TIME = (unset),
	LC_MESSAGES = (unset),
	LC_MONETARY = (unset),
	LC_ADDRESS = (unset),
	LC_IDENTIFICATION = (unset),
	LC_MEASUREMENT = (unset),
	LC_PAPER = (unset),
	LC_TELEPHONE = (unset),
	LC_NAME = (unset),
	LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
Can't exec "/bin/sh": No such file or directory at /nix/store/nfawbww80p1hgpymfgq1vq8wqlak75yh-opendkim-2.11.0-Beta2/sbin/.opendkim-genkey-wrapped line 139.
.opendkim-genkey-wrapped: openssl died with signal %d
127
Return Code: 1

1 hosts failed with an error. Check the logs above
```

As we allow `/bin/sh` in the nix build sandbox, I assume we're OK
allowing it here as well?
2025-05-09 18:33:08 -07:00
Luis Hebendanz
b306c748b8 Merge pull request 'clan-cli: Use machine object everywhere instead of name + flake' (#3541) from Qubasa/clan-core:replace_machine_name_with_machine_obj2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3541
2025-05-09 14:01:25 +00:00
Qubasa
2682581c09 clan-cli: Use machine object everywhere instead of name + flake 2025-05-09 13:13:14 +02:00
renovate[bot]
a0a5827157 chore(deps): update disko digest to 85555d2 2025-05-08 18:40:10 +00:00
hsjobeki
8638861a87 Merge pull request 'Refactor(inventory/modules): add support for local clan.modules and migrate all checks' (#3547) from hsjobeki/clan-core:clan-services into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3547
2025-05-08 15:50:26 +00:00
Johannes Kirschbauer
c5a28e2655 fix: make sure legacyModules dont end up in localModules for inventory.instances 2025-05-08 17:38:28 +02:00
Johannes Kirschbauer
0af36d0a4d Refactor(inventory/modules): add support for local clan.modules and migrate all checks 2025-05-08 17:17:46 +02:00
Qubasa
9867b6a894 clan-cli: Fix clan not finding vendored packages when running from git repo 2025-05-08 12:31:21 +02:00
Qubasa
7459566c2b clan-cli: remove useless run_no_stdout function 2025-05-08 12:30:16 +02:00
59 changed files with 339 additions and 283 deletions

View File

@@ -0,0 +1,29 @@
name: "Update pinned clan-core for checks"
on:
repository_dispatch:
workflow_dispatch:
schedule:
- cron: "51 2 * * *"
jobs:
update-pinned-clan-core:
runs-on: nix
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Update clan-core for checks
run: nix run .#update-clan-core-for-checks
- name: Create pull request
run: |
git commit -am ""
git push origin HEAD:update-clan-core-for-checks
curl -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"head": "update-clan-core-branch",
"base": "main",
"title": "Automated Update: Clan Core",
"body": "This PR updates the pinned clan-core for checks."
}' \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/pulls"

View File

@@ -147,25 +147,7 @@
perSystem =
{ pkgs, ... }:
let
clanCore = self.filter {
include = [
"checks/backups"
"checks/flake-module.nix"
"clanModules/borgbackup"
"clanModules/flake-module.nix"
"clanModules/localbackup"
"clanModules/packages"
"clanModules/single-disk"
"clanModules/zerotier"
"flake.lock"
"flakeModules"
"inventory.json"
"nixosModules"
# Just include everything in 'lib'
# If anything changes in /lib that may affect everything
"lib"
];
};
clanCore = self.checks.x86_64-linux.clan-core-for-checks;
in
{
checks = pkgs.lib.mkIf pkgs.stdenv.isLinux {
@@ -182,11 +164,6 @@
# import the inventory generated nixosModules
self.clanInternals.inventoryClass.machines.test-backup.machineImports;
clan.core.settings.directory = ./.;
environment.systemPackages = [
(pkgs.writeShellScriptBin "foo" ''
echo ${clanCore}
'')
];
};
testScript = ''

View File

@@ -0,0 +1,6 @@
{ fetchgit }:
fetchgit {
url = "https://git.clan.lol/clan/clan-core.git";
rev = "1e8b9def2a021877342491ca1f4c45533a580759";
sha256 = "0f12vwr1abwa1iwjbb5z5xx8jlh80d9njwdm6iaw1z1h2m76xgzc";
}

View File

@@ -34,33 +34,33 @@ clanLib.test.makeTestClan {
modules = {
legacy-module = ./legacy-module;
new-service = {
_class = "clan.service";
manifest.name = "new-service";
roles.peer = { };
perMachine = {
nixosModule = {
# This should be generated by:
# nix run .#generate-test-vars -- checks/dummy-inventory-test dummy-inventory-test
clan.core.vars.generators.new-service = {
files.not-a-secret = {
secret = false;
deploy = true;
};
files.a-secret = {
secret = true;
deploy = true;
owner = "nobody";
group = "users";
mode = "0644";
};
script = ''
# This is a dummy script that does nothing
echo -n "not-a-secret" > $out/not-a-secret
echo -n "a-secret" > $out/a-secret
'';
};
};
};
modules.new-service = {
_class = "clan.service";
manifest.name = "new-service";
roles.peer = { };
perMachine = {
nixosModule = {
# This should be generated by:
# nix run .#generate-test-vars -- checks/dummy-inventory-test dummy-inventory-test
clan.core.vars.generators.new-service = {
files.not-a-secret = {
secret = false;
deploy = true;
};
files.a-secret = {
secret = true;
deploy = true;
owner = "nobody";
group = "users";
mode = "0644";
};
script = ''
# This is a dummy script that does nothing
echo -n "not-a-secret" > $out/not-a-secret
echo -n "a-secret" > $out/a-secret
'';
};
};
};

View File

@@ -14,7 +14,7 @@ in
./installation/flake-module.nix
./morph/flake-module.nix
./nixos-documentation/flake-module.nix
./sanity-checks/dont-depend-on-repo-root.nix
./dont-depend-on-repo-root.nix
];
perSystem =
{
@@ -101,6 +101,12 @@ in
mkdir -p $out
cat $schemaFile > $out/allSchemas.json
'';
clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } ''
cp -r ${pkgs.callPackage ./clan-core-for-checks.nix { }} $out
chmod +w $out/flake.lock
cp ${../flake.lock} $out/flake.lock
'';
};
legacyPackages = {
nixosTests =

View File

@@ -43,6 +43,7 @@
let
dependencies = [
pkgs.disko
pkgs.buildPackages.xorg.lndir
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp
@@ -80,7 +81,7 @@
# Some distros like to automount disks with spaces
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdb && mount /dev/vdb "/mnt/with spaces"')
machine.succeed("clan flash write --debug --flake ${../..} --yes --disk main /dev/vdb test-flash-machine-${pkgs.hostPlatform.system}")
machine.succeed("clan flash write --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdb test-flash-machine-${pkgs.hostPlatform.system}")
'';
} { inherit pkgs self; };
};

View File

@@ -15,6 +15,7 @@ let
pkgs.bash.drvPath
pkgs.nixos-anywhere
pkgs.bubblewrap
pkgs.buildPackages.xorg.lndir
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
@@ -197,7 +198,7 @@ in
installer.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../assets/ssh/privkey} /root/.ssh/id_ed25519")
installer.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v nonrootuser@localhost hostname")
installer.succeed("cp -r ${../..} test-flake && chmod -R +w test-flake")
installer.succeed("cp -r ${self.checks.x86_64-linux.clan-core-for-checks} test-flake && chmod -R +w test-flake")
installer.succeed("clan machines install --no-reboot --debug --flake test-flake --yes test-install-machine-without-system --target-host nonrootuser@localhost --update-hardware-config nixos-facter >&2")
installer.shutdown()
@@ -217,7 +218,7 @@ in
installer.start()
installer.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../assets/ssh/privkey} /root/.ssh/id_ed25519")
installer.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v nonrootuser@localhost hostname")
installer.succeed("cp -r ${../..} test-flake && chmod -R +w test-flake")
installer.succeed("cp -r ${self.checks.x86_64-linux.clan-core-for-checks} test-flake && chmod -R +w test-flake")
installer.fail("test -f test-flake/machines/test-install-machine/hardware-configuration.nix")
installer.fail("test -f test-flake/machines/test-install-machine/facter.json")

View File

@@ -32,7 +32,6 @@
{ pkgs, ... }:
let
dependencies = [
self
pkgs.stdenv.drvPath
pkgs.stdenvNoCC
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
@@ -55,7 +54,7 @@
testScript = ''
start_all()
actual.fail("cat /etc/testfile")
actual.succeed("env CLAN_DIR=${self} clan machines morph test-morph-template --i-will-be-fired-for-using-this --debug --name test-morph-machine")
actual.succeed("env CLAN_DIR=${self.checks.x86_64-linux.clan-core-for-checks} clan machines morph test-morph-template --i-will-be-fired-for-using-this --debug --name test-morph-machine")
assert actual.succeed("cat /etc/testfile") == "morphed"
'';
} { inherit pkgs self; };

View File

@@ -105,10 +105,7 @@ in
private_key = {
inherit owner;
};
public_key = {
inherit owner;
secret = false;
};
public_key.secret = false;
};
runtimeInputs = [
@@ -134,10 +131,7 @@ in
private_key = {
inherit owner;
};
public_key = {
inherit owner;
secret = false;
};
public_key.secret = false;
};
runtimeInputs = [

View File

@@ -18,7 +18,7 @@ let
};
# Register the module for the test
inventory.modules.hello-world = module;
modules.hello-world = module;
# Use the module in the test
inventory.instances = {

View File

@@ -14,6 +14,9 @@ clanLib.test.makeTestClan {
clan = {
directory = ./.;
modules = {
hello-service = module;
};
inventory = {
machines.peer1 = { };
@@ -21,10 +24,6 @@ clanLib.test.makeTestClan {
module.name = "hello-service";
roles.peer.machines.peer1 = { };
};
modules = {
hello-service = module;
};
};
};

View File

@@ -58,7 +58,7 @@ nav:
- Autoincludes: manual/adding-machines.md
- Inventory:
- Inventory: manual/inventory.md
- Instances: manual/distributed-services.md
- Services: manual/distributed-services.md
- Secure Boot: manual/secure-boot.md
- Flake-parts: manual/flake-parts.md
- Authoring:

View File

@@ -12,7 +12,7 @@ We discussed the initial architecture in [01-clan-service-modules](https://git.c
### A Minimal module
First of all we need to register our module into the `inventory.modules` attribute. Make sure to choose a unique name so the module doesn't have a name collision with any of the core modules.
First of all we need to register our module into the `clan.modules` attribute. Make sure to choose a unique name so the module doesn't have a name collision with any of the core modules.
While not required we recommend to prefix your module attribute name.
@@ -22,20 +22,15 @@ i.e. `@hsjobeki/customNetworking`
```nix title="flake.nix"
# ...
outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } ({
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } ({
imports = [ inputs.clan-core.flakeModules.default ];
# ...
clan = {
inventory = {
# We could also inline the complete module spec here
# For example
# {...}: { _class = "clan.service"; ... };
modules."@hsjobeki/customNetworking" = import ./service-modules/networking.nix;
};
# If needed: Exporting the module for other people
modules."@hsjobeki/customNetworking" = import ./service-modules/networking.nix;
# We could also inline the complete module spec here
# For example
# {...}: { _class = "clan.service"; ... };
};
})
```
@@ -221,9 +216,6 @@ outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } ({self, lib, ...}:
# ...
clan = {
# Register the module
inventory.modules."@hsjobeki/messaging" = lib.importApply ./service-modules/messaging.nix { inherit self; };
# Expose the module for downstream users, 'self' would always point to this flake.
modules."@hsjobeki/messaging" = lib.importApply ./service-modules/messaging.nix { inherit self; };
};
})
@@ -250,7 +242,7 @@ outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } ({self, lib, ...}:
# ...
clan = {
# Register the module
inventory.modules."@hsjobeki/messaging" = {
modules."@hsjobeki/messaging" = {
# Create an option 'myClan' and assign it to 'self'
options.myClan = lib.mkOption {
default = self;

View File

@@ -32,7 +32,7 @@ VM tests should be avoided wherever it is possible to implement a cheaper unit t
Existing nixos vm tests in clan-core can be found by using ripgrep:
```shellSession
rg "import.*/lib/test-base.nix"
rg self.clanLib.test.baseTest
```
### Locating definitions of failing VM tests
@@ -50,7 +50,7 @@ example: locating the vm test named `borgbackup`:
```shellSession
$ rg "borgbackup =" ./checks
./checks/flake-module.nix
41: borgbackup = import ./borgbackup nixosTestArgs;
44- wayland-proxy-virtwl = self.clanLib.test.baseTest ./wayland-proxy-virtwl nixosTestArgs;
```
-> the location of that test is `/checks/flake-module.nix` line `41`.
@@ -99,15 +99,15 @@ Basically everything stated under the NixOS VM tests sections applies here, exce
Limitations:
- does not yet support networking
- supports only one machine as of now
- Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the container.
- setuid binaries don't work
### Where to find examples for NixOS container tests
Existing nixos container tests in clan-core can be found by using ripgrep:
```shellSession
rg "import.*/lib/container-test.nix"
rg self.clanLib.test.containerTest
```

20
flake.lock generated
View File

@@ -16,11 +16,11 @@
]
},
"locked": {
"lastModified": 1746628829,
"narHash": "sha256-q77HWbHlJTXFVRD2kRnYwqGfbGmCm9XYPG0ZgLLVh8M=",
"rev": "9a3856421bd6733192b57037cdf26712d73c0871",
"lastModified": 1747008053,
"narHash": "sha256-rob/qftmEuk+/JVGCIrOpv+LWjdmayFtebEKqRZXVAI=",
"rev": "2666bb11f4287cfbdf3b7c5f55231c6b5772a436",
"type": "tarball",
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/9a3856421bd6733192b57037cdf26712d73c0871.tar.gz"
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/2666bb11f4287cfbdf3b7c5f55231c6b5772a436.tar.gz"
},
"original": {
"type": "tarball",
@@ -34,11 +34,11 @@
]
},
"locked": {
"lastModified": 1746695594,
"narHash": "sha256-pAAWYs3S+/tY65vemHZdVSXpeIz4JINEJZoPoBjr8JU=",
"lastModified": 1746729224,
"narHash": "sha256-9R4sOLAK1w3Bq54H3XOJogdc7a6C2bLLmatOQ+5pf5w=",
"owner": "nix-community",
"repo": "disko",
"rev": "6bb82b77ce140137177e30df067759931ab60a73",
"rev": "85555d27ded84604ad6657ecca255a03fd878607",
"type": "github"
},
"original": {
@@ -184,11 +184,11 @@
]
},
"locked": {
"lastModified": 1746216483,
"narHash": "sha256-4h3s1L/kKqt3gMDcVfN8/4v2jqHrgLIe4qok4ApH5x4=",
"lastModified": 1746989248,
"narHash": "sha256-uoQ21EWsAhyskNo8QxrTVZGjG/dV4x5NM1oSgrmNDJY=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "29ec5026372e0dec56f890e50dbe4f45930320fd",
"rev": "708ec80ca82e2bbafa93402ccb66a35ff87900c5",
"type": "github"
},
"original": {

View File

@@ -37,7 +37,7 @@ let
done
if ! test -e ~/clan-core; then
# git clone https://git.clan.lol/clan/clan-core.git ~/clan-core
cp -rv ${self} clan-core
cp -rv ${self.checks.x86_64-linux.clan-core-for-checks} clan-core
fi
cd clan-core
clan machines morph demo-template --i-will-be-fired-for-using-this

View File

@@ -45,7 +45,9 @@ let
inherit inventory directory;
flakeInputs = config.self.inputs;
prefix = config._prefix ++ [ "inventoryClass" ];
localModuleSet = config.modules;
# TODO: remove inventory.modules, this is here for backwards compatibility
localModuleSet =
lib.filterAttrs (n: _: !inventory._legacyModules ? ${n}) inventory.modules // config.modules;
}
);

View File

@@ -33,6 +33,7 @@ let
distributedServices = clanLib.inventory.mapInstances {
inherit (config) inventory;
inherit localModuleSet;
inherit flakeInputs;
prefix = prefix ++ [ "distributedServices" ];
};

View File

@@ -54,7 +54,7 @@ let
)
}
To import a local module from 'inventory.modules' remove the 'input' attribute from the module definition
To import a local module from 'clan.modules' remove the 'input' attribute from the module definition
Remove the following line from the module definition:
...
@@ -81,6 +81,7 @@ in
flakeInputs,
# The clan inventory
inventory,
localModuleSet,
prefix ? [ ],
}:
let
@@ -92,7 +93,7 @@ in
let
resolvedModule = resolveModule {
moduleSpec = instance.module;
localModuleSet = inventory.modules;
inherit localModuleSet;
inherit flakeInputs;
};

View File

@@ -41,9 +41,13 @@ let
callInventoryAdapter =
inventoryModule:
let
inventory = evalInventory inventoryModule;
in
clanLib.inventory.mapInstances {
flakeInputs = flakeInputsFixture;
inventory = evalInventory inventoryModule;
inherit inventory;
localModuleSet = inventory.modules;
};
in
{

View File

@@ -58,7 +58,16 @@ in
)
)
''
The config.clan.core.vars.generators.${generator.name}.files.${file.name} is not secret, but has non-default owner/group/mode set.
The config.clan.core.vars.generators.${generator.name}.files.${file.name} is not secret:
${lib.optionalString (file.owner != "root") ''
The owner is set to ${file.owner}, but should be root.
''}
${lib.optionalString (file.group != (if _class == "darwin" then "wheel" else "root")) ''
The group is set to ${file.group}, but should be ${if _class == "darwin" then "wheel" else "root"}.
''}
${lib.optionalString (file.mode != "0400") ''
The mode is set to ${file.mode}, but should be 0400.
''}
This doesn't work because the file will be added to the nix store
''
) [ ] (lib.attrValues generator.files)

View File

@@ -18,7 +18,7 @@ let
config.clan.core.settings.directory
+ "/vars/per-machine/${machineName}/${secret.generator}/${secret.name}/secret";
vars = collectFiles config.clan.core.vars;
vars = collectFiles config.clan.core.vars.generators;
in
{
config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") {

View File

@@ -13,7 +13,7 @@ in
{
collectFiles =
vars:
generators:
let
relevantFiles =
generator:
@@ -30,7 +30,7 @@ in
inherit (generator) share;
inherit (file) owner group mode;
}) (relevantFiles generator)
) vars.generators
) generators
);
in
allFiles;

View File

@@ -6,8 +6,9 @@ from urllib.parse import urlparse
from clan_lib.api import API
from clan_cli.cmd import run_no_stdout
from clan_cli.cmd import run
from clan_cli.errors import ClanCmdError, ClanError
from clan_cli.flake import Flake
from clan_cli.inventory import Meta
from clan_cli.nix import nix_eval
@@ -15,26 +16,26 @@ log = logging.getLogger(__name__)
@API.register
def show_clan_meta(uri: str) -> Meta:
if uri.startswith("/") and not Path(uri).exists():
msg = f"Path {uri} does not exist"
def show_clan_meta(flake: Flake) -> Meta:
if flake.is_local and not flake.path.exists():
msg = f"Path {flake} does not exist"
raise ClanError(msg, description="clan directory does not exist")
cmd = nix_eval(
[
f"{uri}#clanInternals.inventory.meta",
f"{flake}#clanInternals.inventory.meta",
"--json",
]
)
res = "{}"
try:
proc = run_no_stdout(cmd)
proc = run(cmd)
res = proc.stdout.strip()
except ClanCmdError as e:
msg = "Evaluation failed on meta attribute"
raise ClanError(
msg,
location=f"show_clan {uri}",
location=f"show_clan {flake}",
description=str(e.cmd),
) from e
@@ -53,16 +54,16 @@ def show_clan_meta(uri: str) -> Meta:
msg = "Invalid absolute path"
raise ClanError(
msg,
location=f"show_clan {uri}",
location=f"show_clan {flake}",
description="Icon path must be a URL or a relative path",
)
icon_path = str((Path(uri) / meta_icon).resolve())
icon_path = str((flake.path / meta_icon).resolve())
else:
msg = "Invalid schema"
raise ClanError(
msg,
location=f"show_clan {uri}",
location=f"show_clan {flake}",
description="Icon path must be a URL or a relative path",
)

View File

@@ -403,23 +403,3 @@ def run(
raise ClanCmdError(cmd_out)
return cmd_out
def run_no_stdout(
cmd: list[str],
opts: RunOpts | None = None,
) -> CmdOut:
"""
Like run, but automatically suppresses all output, if not in DEBUG log level.
If in DEBUG log level the stdout of commands will be shown.
"""
if opts is None:
opts = RunOpts()
if cmdlog.isEnabledFor(logging.DEBUG):
opts.log = opts.log if opts.log.value > Log.STDERR.value else Log.STDERR
return run(
cmd,
opts,
)

View File

@@ -10,6 +10,7 @@ from .errors import ClanError
if TYPE_CHECKING:
from clan_cli.flake import Flake
from clan_cli.machines.machines import Machine
log = logging.getLogger(__name__)
@@ -144,8 +145,8 @@ def machines_dir(flake: "Flake") -> Path:
return Path(store_path) / "machines"
def specific_machine_dir(flake: "Flake", machine: str) -> Path:
return machines_dir(flake) / machine
def specific_machine_dir(machine: "Machine") -> Path:
return machines_dir(machine.flake) / machine.name
def module_root() -> Path:

View File

@@ -48,6 +48,7 @@ def bubblewrap_cmd(generator: str, facts_dir: Path, secrets_dir: Path) -> list[s
"--unshare-all",
"--tmpfs", "/",
"--ro-bind", "/nix/store", "/nix/store",
"--ro-bind", "/bin/sh", "/bin/sh",
"--dev", "/dev",
# not allowed to bind procfs in some sandboxes
"--bind", str(facts_dir), str(facts_dir),

View File

@@ -5,28 +5,27 @@ from tempfile import TemporaryDirectory
from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_cli.machines.machines import Machine
from clan_cli.ssh.host import Host
from clan_cli.ssh.upload import upload
log = logging.getLogger(__name__)
def upload_secrets(machine: Machine, host: Host) -> None:
if not machine.secret_facts_store.needs_upload(host):
machine.info("Secrets already uploaded")
return
def upload_secrets(machine: Machine) -> None:
with machine.target_host() as host:
if not machine.secret_facts_store.needs_upload(host):
machine.info("Secrets already uploaded")
return
with TemporaryDirectory(prefix="facts-upload-") as _tempdir:
local_secret_dir = Path(_tempdir).resolve()
machine.secret_facts_store.upload(local_secret_dir)
remote_secret_dir = Path(machine.secrets_upload_directory)
upload(host, local_secret_dir, remote_secret_dir)
with TemporaryDirectory(prefix="facts-upload-") as _tempdir:
local_secret_dir = Path(_tempdir).resolve()
machine.secret_facts_store.upload(local_secret_dir)
remote_secret_dir = Path(machine.secrets_upload_directory)
upload(host, local_secret_dir, remote_secret_dir)
def upload_command(args: argparse.Namespace) -> None:
machine = Machine(name=args.machine, flake=args.flake)
with machine.target_host() as host:
upload_secrets(machine, host)
upload_secrets(machine)
def register_upload_parser(parser: argparse.ArgumentParser) -> None:

View File

@@ -21,7 +21,7 @@ from typing import Any
from clan_lib.api import API, dataclass_to_dict, from_dict
from clan_cli.cmd import run_no_stdout
from clan_cli.cmd import run
from clan_cli.errors import ClanCmdError, ClanError
from clan_cli.flake import Flake
from clan_cli.git import commit_file
@@ -80,7 +80,7 @@ def load_inventory_eval(flake_dir: Flake) -> Inventory:
]
)
proc = run_no_stdout(cmd)
proc = run(cmd)
try:
res = proc.stdout.strip()
@@ -380,7 +380,7 @@ def get_inventory_current_priority(flake: Flake) -> dict:
]
)
proc = run_no_stdout(cmd)
proc = run(cmd)
try:
res = proc.stdout.strip()

View File

@@ -110,7 +110,7 @@ def create_machine(opts: CreateOptions, commit: bool = True) -> None:
new_machine["deploy"] = {"targetHost": target_host}
patch_inventory_with(
Flake(str(clan_dir)), f"machines.{machine_name}", dataclass_to_dict(new_machine)
opts.clan_dir, f"machines.{machine_name}", dataclass_to_dict(new_machine)
)
# Commit at the end in that order to avoid committing halve-baked machines

View File

@@ -5,9 +5,10 @@ from pathlib import Path
from clan_lib.api import API
from clan_cli import Flake, inventory
from clan_cli import inventory
from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_cli.dirs import specific_machine_dir
from clan_cli.machines.machines import Machine
from clan_cli.secrets.folders import sops_secrets_folder
from clan_cli.secrets.machines import has_machine as secrets_has_machine
from clan_cli.secrets.machines import remove_machine as secrets_machine_remove
@@ -15,49 +16,46 @@ from clan_cli.secrets.secrets import (
list_secrets,
)
from .machines import Machine
log = logging.getLogger(__name__)
@API.register
def delete_machine(flake: Flake, name: str) -> None:
def delete_machine(machine: Machine) -> None:
try:
inventory.delete(flake, {f"machines.{name}"})
inventory.delete(machine.flake, {f"machines.{machine.name}"})
except KeyError as exc:
# louis@(2025-03-09): test infrastructure does not seem to set the
# inventory properly, but more importantly only one machine in my
# personal clan ended up in the inventory for some reason, so I think
# it makes sense to eat the exception here.
log.warning(
f"{name} was missing or already deleted from the machines inventory: {exc}"
f"{machine.name} was missing or already deleted from the machines inventory: {exc}"
)
changed_paths: list[Path] = []
folder = specific_machine_dir(flake, name)
folder = specific_machine_dir(machine)
if folder.exists():
changed_paths.append(folder)
shutil.rmtree(folder)
# louis@(2025-02-04): clean-up legacy (pre-vars) secrets:
sops_folder = sops_secrets_folder(flake.path)
filter_fn = lambda secret_name: secret_name.startswith(f"{name}-")
for secret_name in list_secrets(flake.path, filter_fn):
sops_folder = sops_secrets_folder(machine.flake.path)
filter_fn = lambda secret_name: secret_name.startswith(f"{machine.name}-")
for secret_name in list_secrets(machine.flake.path, filter_fn):
secret_path = sops_folder / secret_name
changed_paths.append(secret_path)
shutil.rmtree(secret_path)
machine = Machine(name, flake)
changed_paths.extend(machine.public_vars_store.delete_store())
changed_paths.extend(machine.secret_vars_store.delete_store())
# Remove the machine's key, and update secrets & vars that referenced it:
if secrets_has_machine(flake.path, name):
secrets_machine_remove(flake.path, name)
if secrets_has_machine(machine.flake.path, machine.name):
secrets_machine_remove(machine.flake.path, machine.name)
def delete_command(args: argparse.Namespace) -> None:
delete_machine(args.flake, args.name)
delete_machine(Machine(flake=args.flake, name=args.name))
def register_delete_parser(parser: argparse.ArgumentParser) -> None:

View File

@@ -7,11 +7,10 @@ from pathlib import Path
from clan_lib.api import API
from clan_cli.cmd import RunOpts, run_no_stdout
from clan_cli.cmd import RunOpts, run
from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_cli.dirs import specific_machine_dir
from clan_cli.errors import ClanCmdError, ClanError
from clan_cli.flake import Flake
from clan_cli.git import commit_file
from clan_cli.machines.machines import Machine
from clan_cli.nix import nix_config, nix_eval
@@ -26,39 +25,35 @@ class HardwareConfig(Enum):
NIXOS_GENERATE_CONFIG = "nixos-generate-config"
NONE = "none"
def config_path(self, flake: Flake, machine_name: str) -> Path:
machine_dir = specific_machine_dir(flake, machine_name)
def config_path(self, machine: Machine) -> Path:
machine_dir = specific_machine_dir(machine)
if self == HardwareConfig.NIXOS_FACTER:
return machine_dir / "facter.json"
return machine_dir / "hardware-configuration.nix"
@classmethod
def detect_type(
cls: type["HardwareConfig"], flake: Flake, machine_name: str
) -> "HardwareConfig":
hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path(
flake, machine_name
)
def detect_type(cls: type["HardwareConfig"], machine: Machine) -> "HardwareConfig":
hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path(machine)
if hardware_config.exists() and "throw" not in hardware_config.read_text():
return HardwareConfig.NIXOS_GENERATE_CONFIG
if HardwareConfig.NIXOS_FACTER.config_path(flake, machine_name).exists():
if HardwareConfig.NIXOS_FACTER.config_path(machine).exists():
return HardwareConfig.NIXOS_FACTER
return HardwareConfig.NONE
@API.register
def show_machine_hardware_config(flake: Flake, machine_name: str) -> HardwareConfig:
def show_machine_hardware_config(machine: Machine) -> HardwareConfig:
"""
Show hardware information for a machine returns None if none exist.
"""
return HardwareConfig.detect_type(flake, machine_name)
return HardwareConfig.detect_type(machine)
@API.register
def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | None:
def show_machine_hardware_platform(machine: Machine) -> str | None:
"""
Show hardware information for a machine returns None if none exist.
"""
@@ -66,13 +61,13 @@ def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | Non
system = config["system"]
cmd = nix_eval(
[
f"{flake}#clanInternals.machines.{system}.{machine_name}",
f"{machine.flake}#clanInternals.machines.{system}.{machine.name}",
"--apply",
"machine: { inherit (machine.pkgs) system; }",
"--json",
]
)
proc = run_no_stdout(cmd, RunOpts(prefix=machine_name))
proc = run(cmd, RunOpts(prefix=machine.name))
res = proc.stdout.strip()
host_platform = json.loads(res)
@@ -81,11 +76,8 @@ def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | Non
@dataclass
class HardwareGenerateOptions:
flake: Flake
machine: str
machine: Machine
backend: HardwareConfig
target_host: str | None = None
keyfile: str | None = None
password: str | None = None
@@ -96,14 +88,9 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon
and place the resulting *.nix file in the machine's directory.
"""
machine = Machine(
opts.machine,
flake=opts.flake,
private_key=Path(opts.keyfile) if opts.keyfile else None,
override_target_host=opts.target_host,
)
machine = opts.machine
hw_file = opts.backend.config_path(opts.flake, opts.machine)
hw_file = opts.backend.config_path(opts.machine)
hw_file.parent.mkdir(parents=True, exist_ok=True)
if opts.backend == HardwareConfig.NIXOS_FACTER:
@@ -148,11 +135,11 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon
commit_file(
hw_file,
opts.flake.path,
opts.machine.flake.path,
f"machines/{opts.machine}/{hw_file.name}: update hardware configuration",
)
try:
show_machine_hardware_platform(opts.flake, opts.machine)
show_machine_hardware_platform(opts.machine)
if backup_file:
backup_file.unlink(missing_ok=True)
except ClanCmdError as e:
@@ -173,10 +160,13 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon
def update_hardware_config_command(args: argparse.Namespace) -> None:
opts = HardwareGenerateOptions(
machine = Machine(
flake=args.flake,
machine=args.machine,
target_host=args.target_host,
name=args.machine,
override_target_host=args.target_host,
)
opts = HardwareGenerateOptions(
machine=machine,
password=args.password,
backend=HardwareConfig(args.backend),
)

View File

@@ -111,11 +111,7 @@ def install_machine(opts: InstallOptions) -> None:
[
"--generate-hardware-config",
str(opts.update_hardware_config.value),
str(
opts.update_hardware_config.config_path(
machine.flake, machine.name
)
),
str(opts.update_hardware_config.config_path(machine)),
]
)

View File

@@ -67,9 +67,9 @@ def get_machine_details(machine: Machine) -> MachineDetails:
msg = f"Machine {machine.name} not found in inventory"
raise ClanError(msg)
hw_config = HardwareConfig.detect_type(machine.flake, machine.name)
hw_config = HardwareConfig.detect_type(machine)
machine_dir = specific_machine_dir(machine.flake, machine.name)
machine_dir = specific_machine_dir(machine)
disk_schema: MachineDiskMatter | None = None
disk_path = machine_dir / "disko.nix"
if disk_path.exists():

View File

@@ -9,7 +9,7 @@ from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Any
from clan_cli.cmd import Log, RunOpts, run_no_stdout
from clan_cli.cmd import Log, RunOpts, run
from clan_cli.errors import ClanCmdError, ClanError
from clan_cli.facts import public_modules as facts_public_modules
from clan_cli.facts import secret_modules as facts_secret_modules
@@ -188,7 +188,7 @@ class Machine:
# however there is a soon to be merged PR that requires deployment
# as root to match NixOS: https://github.com/nix-darwin/nix-darwin/pull/1341
return json.loads(
run_no_stdout(
run(
nix_eval(
[
f"{self.flake}#darwinConfigurations.{self.name}.options.system",

View File

@@ -10,7 +10,7 @@ from contextlib import ExitStack
from clan_lib.api import API
from clan_cli.async_run import AsyncContext, AsyncOpts, AsyncRuntime, is_async_cancelled
from clan_cli.cmd import MsgColor, RunOpts, run
from clan_cli.cmd import Log, MsgColor, RunOpts, run
from clan_cli.colors import AnsiColor
from clan_cli.completions import (
add_dynamic_completer,
@@ -141,7 +141,7 @@ def deploy_machine(machine: Machine) -> None:
generate_facts([machine], service=None, regenerate=False)
generate_vars([machine], generator_name=None, regenerate=False)
upload_secrets(machine, target_host)
upload_secrets(machine)
upload_secret_vars(machine, target_host)
path = upload_sources(machine, host)
@@ -182,16 +182,16 @@ def deploy_machine(machine: Machine) -> None:
remote_env = host.nix_ssh_env(None, local_ssh=False)
ret = host.run(
switch_cmd,
RunOpts(check=False, msg_color=MsgColor(stderr=AnsiColor.DEFAULT)),
RunOpts(
check=False,
log=Log.BOTH,
msg_color=MsgColor(stderr=AnsiColor.DEFAULT),
needs_user_terminal=True,
),
extra_env=remote_env,
become_root=become_root,
)
# Last output line (config store path) is printed to stdout instead of stderr
lines = ret.stdout.splitlines()
if lines:
print(lines[-1])
if is_async_cancelled():
return
@@ -206,6 +206,7 @@ def deploy_machine(machine: Machine) -> None:
ret = host.run(
test_cmd if is_mobile else switch_cmd,
RunOpts(
log=Log.BOTH,
msg_color=MsgColor(stderr=AnsiColor.DEFAULT),
needs_user_terminal=True,
),

View File

@@ -1,12 +1,13 @@
import json
import logging
import os
import shutil
import tempfile
from functools import cache
from pathlib import Path
from typing import Any
from clan_cli.cmd import run, run_no_stdout
from clan_cli.cmd import run
from clan_cli.dirs import nixpkgs_flake, nixpkgs_source
from clan_cli.errors import ClanError
from clan_cli.locked_open import locked_open
@@ -55,7 +56,7 @@ def nix_add_to_gcroots(nix_path: Path, dest: Path) -> None:
@cache
def nix_config() -> dict[str, Any]:
cmd = nix_command(["config", "show", "--json"])
proc = run_no_stdout(cmd)
proc = run(cmd)
data = json.loads(proc.stdout)
config = {}
for key, value in data.items():
@@ -131,7 +132,16 @@ class Packages:
cls.static_packages = set(
os.environ.get("CLAN_PROVIDED_PACKAGES", "").split(":")
)
return program in cls.static_packages
if program in cls.static_packages:
if shutil.which(program) is None:
log.warning(
"Program %s is not in the path even though it should be shipped with clan",
program,
)
return False
return True
return False
# Features:

View File

@@ -6,6 +6,7 @@
"age-plugin-sss",
"age-plugin-tpm",
"age-plugin-yubikey",
"age-plugin-1p",
"avahi",
"bash",
"bubblewrap",

View File

@@ -63,7 +63,8 @@ def upload(
for mdir in dirs:
dir_path = Path(root) / mdir
tarinfo = tar.gettarinfo(
dir_path, arcname=str(dir_path.relative_to(str(local_src)))
dir_path,
arcname=str(dir_path.relative_to(str(local_src))),
)
tarinfo.mode = dir_mode
tarinfo.uname = file_user

View File

@@ -3,7 +3,7 @@ import json
import logging
from pathlib import Path
from clan_cli.cmd import RunOpts, run_no_stdout
from clan_cli.cmd import RunOpts, run
from clan_cli.completions import (
add_dynamic_completer,
complete_machines,
@@ -32,7 +32,7 @@ def list_state_folders(machine: Machine, service: None | str = None) -> None:
res = "{}"
try:
proc = run_no_stdout(cmd, opts=RunOpts(prefix=machine.name))
proc = run(cmd, RunOpts(prefix=machine.name))
res = proc.stdout.strip()
except ClanCmdError as e:
msg = "Clan might not have meta attributes"

View File

@@ -2,7 +2,7 @@ import json
from pathlib import Path
from typing import Any
from clan_cli.cmd import run_no_stdout
from clan_cli.cmd import run
from clan_cli.errors import ClanError
from clan_cli.nix import nix_eval
@@ -18,7 +18,7 @@ def list_tagged_machines(flake_url: str | Path) -> dict[str, Any]:
"--json",
]
)
proc = run_no_stdout(cmd)
proc = run(cmd)
try:
res = proc.stdout.strip()

View File

@@ -10,8 +10,15 @@ from pathlib import Path
from typing import Any, NamedTuple
import pytest
from clan_cli.dirs import TemplateType, clan_templates, nixpkgs_source
from clan_cli.dirs import (
TemplateType,
clan_templates,
nixpkgs_source,
specific_machine_dir,
)
from clan_cli.flake import Flake
from clan_cli.locked_open import locked_open
from clan_cli.machines.machines import Machine
from clan_cli.nix import nix_test_store
from clan_cli.tests import age_keys
from clan_cli.tests.fixture_error import FixtureError
@@ -70,11 +77,10 @@ class FlakeForTest(NamedTuple):
def set_machine_settings(
flake: Path,
machine_name: str,
machine: Machine,
machine_settings: dict,
) -> None:
config_path = flake / "machines" / machine_name / "configuration.json"
config_path = specific_machine_dir(machine) / "configuration.json"
config_path.write_text(json.dumps(machine_settings, indent=2))
@@ -202,7 +208,8 @@ class ClanFlake:
}}
"""
)
set_machine_settings(self.path, machine_name, machine_config)
machine = Machine(name=machine_name, flake=Flake(str(self.path)))
set_machine_settings(machine, machine_config)
sp.run(["git", "add", "."], cwd=self.path, check=True)
sp.run(
["git", "commit", "-a", "-m", "Update by flake generator"],

View File

@@ -11,7 +11,7 @@ from clan_cli.inventory import (
set_inventory,
)
from clan_cli.machines.create import CreateOptions, create_machine
from clan_cli.nix import nix_eval, run_no_stdout
from clan_cli.nix import nix_eval, run
from clan_cli.tests.fixtures_flakes import FlakeForTest
from clan_lib.api.modules import list_modules
@@ -120,7 +120,7 @@ def test_add_module_to_inventory(
"--json",
]
)
proc = run_no_stdout(cmd)
proc = run(cmd)
res = json.loads(proc.stdout.strip())
assert res["machine1"]["authorizedKeys"] == [ssh_key.decode()]

View File

@@ -103,6 +103,7 @@ def bubblewrap_cmd(generator: str, tmpdir: Path) -> list[str]:
"--unshare-all",
"--tmpfs", "/",
"--ro-bind", "/nix/store", "/nix/store",
"--ro-bind", "/bin/sh", "/bin/sh",
*(["--ro-bind", str(test_store), str(test_store)] if test_store else []),
"--dev", "/dev",
# not allowed to bind procfs in some sandboxes

View File

@@ -4,9 +4,10 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Literal
from clan_cli.cmd import RunOpts
from clan_cli.cmd import RunOpts, run
from clan_cli.errors import ClanError
from clan_cli.nix import nix_shell, run_no_stdout
from clan_cli.flake import Flake
from clan_cli.nix import nix_shell
from . import API
@@ -52,8 +53,8 @@ class Directory:
@API.register
def get_directory(current_path: str) -> Directory:
curr_dir = Path(current_path)
def get_directory(flake: Flake) -> Directory:
curr_dir = flake.path
directory = Directory(path=str(curr_dir))
if not curr_dir.is_dir():
@@ -135,7 +136,7 @@ def show_block_devices() -> Blockdevices:
"PATH,NAME,RM,SIZE,RO,MOUNTPOINTS,TYPE,ID-LINK",
],
)
proc = run_no_stdout(cmd, RunOpts(needs_user_terminal=True))
proc = run(cmd, RunOpts(needs_user_terminal=True))
res = proc.stdout.strip()
blk_info: dict[str, Any] = json.loads(res)

View File

@@ -7,9 +7,9 @@ from uuid import uuid4
from clan_cli.dirs import TemplateType, clan_templates
from clan_cli.errors import ClanError
from clan_cli.flake import Flake
from clan_cli.git import commit_file
from clan_cli.machines.hardware import HardwareConfig, show_machine_hardware_config
from clan_cli.machines.machines import Machine
from clan_lib.api import API
from clan_lib.api.modules import Frontmatter, extract_frontmatter
@@ -74,9 +74,7 @@ templates: dict[str, dict[str, Callable[[dict[str, Any]], Placeholder]]] = {
@API.register
def get_disk_schemas(
flake: Flake, machine_name: str | None = None
) -> dict[str, DiskSchema]:
def get_disk_schemas(machine: Machine) -> dict[str, DiskSchema]:
"""
Get the available disk schemas
"""
@@ -84,13 +82,12 @@ def get_disk_schemas(
disk_schemas = {}
hw_report = {}
if machine_name is not None:
hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(flake, machine_name)
if not hw_report_path.exists():
msg = "Hardware configuration missing"
raise ClanError(msg)
with hw_report_path.open("r") as hw_report_file:
hw_report = json.load(hw_report_file)
hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(machine)
if not hw_report_path.exists():
msg = "Hardware configuration missing"
raise ClanError(msg)
with hw_report_path.open("r") as hw_report_file:
hw_report = json.load(hw_report_file)
for disk_template in disk_templates.iterdir():
if disk_template.is_dir():
@@ -130,8 +127,7 @@ class MachineDiskMatter(TypedDict):
@API.register
def set_machine_disk_schema(
flake: Flake,
machine_name: str,
machine: Machine,
schema_name: str,
# Placeholders are used to fill in the disk schema
# Use get disk schemas to get the placeholders and their options
@@ -142,8 +138,8 @@ def set_machine_disk_schema(
Set the disk placeholders of the template
"""
# Assert the hw-config must exist before setting the disk
hw_config = show_machine_hardware_config(flake, machine_name)
hw_config_path = hw_config.config_path(flake, machine_name)
hw_config = show_machine_hardware_config(machine)
hw_config_path = hw_config.config_path(machine)
if not hw_config_path.exists():
msg = "Hardware configuration must exist before applying disk schema"
@@ -160,7 +156,7 @@ def set_machine_disk_schema(
raise ClanError(msg)
# Check that the placeholders are valid
disk_schema = get_disk_schemas(flake, machine_name)[schema_name]
disk_schema = get_disk_schemas(machine)[schema_name]
# check that all required placeholders are present
for placeholder_name, schema_placeholder in disk_schema.placeholders.items():
if schema_placeholder.required and placeholder_name not in placeholders:
@@ -221,6 +217,6 @@ def set_machine_disk_schema(
commit_file(
disko_file_path,
flake.path,
commit_message=f"Set disk schema of machine: {machine_name} to {schema_name}",
machine.flake.path,
commit_message=f"Set disk schema of machine: {machine.name} to {schema_name}",
)

View File

@@ -2,7 +2,7 @@ import argparse
import re
from dataclasses import dataclass
from clan_cli.cmd import run_no_stdout
from clan_cli.cmd import run
from clan_cli.nix import nix_shell
from . import API
@@ -100,7 +100,7 @@ def show_mdns() -> DNSInfo:
"--terminate",
],
)
proc = run_no_stdout(cmd)
proc = run(cmd)
data = parse_avahi_output(proc.stdout)
return data

View File

@@ -240,7 +240,7 @@ def test_clan_create_api(
facter_json = test_lib_root / "assets" / "facter.json"
assert facter_json.exists(), f"Source facter file not found: {facter_json}"
dest_dir = specific_machine_dir(Flake(str(clan_dir_flake.path)), machine.name)
dest_dir = specific_machine_dir(machine)
# specific_machine_dir should create the directory, but ensure it exists just in case
dest_dir.mkdir(parents=True, exist_ok=True)
@@ -253,10 +253,7 @@ def test_clan_create_api(
)
# ===== Create Disko Config ======
facter_path = (
specific_machine_dir(Flake(str(clan_dir_flake.path)), machine.name)
/ "facter.json"
)
facter_path = specific_machine_dir(machine) / "facter.json"
with facter_path.open("r") as f:
facter_report = json.load(f)
@@ -265,7 +262,7 @@ def test_clan_create_api(
assert disk_devs is not None
placeholders = {"mainDisk": disk_devs[0]}
set_machine_disk_schema(clan_dir_flake, machine.name, "single-disk", placeholders)
set_machine_disk_schema(machine, "single-disk", placeholders)
clan_dir_flake.invalidate_cache()
with pytest.raises(ClanError) as exc_info:

View File

@@ -36,6 +36,7 @@
classgen = pkgs.callPackage ./classgen { };
zerotierone = pkgs.callPackage ./zerotierone { };
webview-lib = pkgs.callPackage ./webview-lib { };
update-clan-core-for-checks = pkgs.callPackage ./update-clan-core-for-checks { };
};
};
}

View File

@@ -0,0 +1,35 @@
{
writeShellApplication,
git,
jq,
nix-prefetch-git,
}:
writeShellApplication {
name = "update-clan-core-for-checks";
runtimeInputs = [
git
jq
nix-prefetch-git
];
text = ''
reporoot=$(git rev-parse --show-toplevel)
if [ -z "$reporoot" ]; then
echo "Not in a git repository. Please run this script from the root of the repository."
exit 1
fi
cd "$reporoot"
# get latest commit of clan-core
json=$(nix-prefetch-git "$(pwd)")
sha256=$(jq -r '.sha256' <<< "$json")
rev=$(jq -r '.rev' <<< "$json")
cat > ./checks/clan-core-for-checks.nix <<EOF
{ fetchgit }:
fetchgit {
url = "https://git.clan.lol/clan/clan-core.git";
rev = "$rev";
sha256 = "$sha256";
}
EOF
'';
}

View File

@@ -22,7 +22,9 @@ export { clanList, setClanList };
(async function () {
const curr = activeURI();
if (curr) {
const result = await callApi("show_clan_meta", { uri: curr });
const result = await callApi("show_clan_meta", {
flake: { identifier: curr },
});
console.log("refetched meta for ", curr);
if (result.status === "error") {
result.errors.forEach((error) => {

View File

@@ -52,7 +52,9 @@ export const Sidebar = (props: RouteSectionProps) => {
queryFn: async () => {
const curr = activeURI();
if (curr) {
const result = await callApi("show_clan_meta", { uri: curr });
const result = await callApi("show_clan_meta", {
flake: { identifier: curr },
});
console.log("refetched meta for ", curr);
if (result.status === "error") throw new Error("Failed to fetch data");

View File

@@ -329,7 +329,9 @@ export const ClanDetails = () => {
const clanQuery = createQuery(() => ({
queryKey: [clan_dir, "inventory", "meta"],
queryFn: async () => {
const result = await callApi("show_clan_meta", { uri: clan_dir });
const result = await callApi("show_clan_meta", {
flake: { identifier: clan_dir },
});
if (result.status === "error") throw new Error("Failed to fetch data");
return result.data;
},

View File

@@ -18,7 +18,9 @@ const ClanItem = (props: ClanItemProps) => {
const details = createQuery(() => ({
queryKey: [clan_dir, "meta"],
queryFn: async () => {
const result = await callApi("show_clan_meta", { uri: clan_dir });
const result = await callApi("show_clan_meta", {
flake: { identifier: clan_dir },
});
if (result.status === "error") throw new Error("Failed to fetch data");
return result.data;
},

View File

@@ -117,8 +117,10 @@ const InstallMachine = (props: InstallMachineProps) => {
if (shouldRunDisk) {
setProgressText("Setting up disk ... (1/5)");
const disk_response = await callApi("set_machine_disk_schema", {
flake: { identifier: curr_uri },
machine_name: props.name,
machine: {
flake: { identifier: curr_uri },
name: props.name,
},
placeholders: diskValues.placeholders,
schema_name: diskValues.schema,
force: true,
@@ -559,7 +561,7 @@ const MachineForm = (props: MachineDetailsProps) => {
variant="light"
class="w-full"
size="s"
onClick={() => handleUpdate()}
onClick={() => handleUpdateButton()}
endIcon={<Icon size={12} icon="Update" />}
>
Update

View File

@@ -37,10 +37,12 @@ export const DiskStep = (props: StepProps<DiskValues>) => {
queryKey: [props.dir, props.machine_id, "disk_schemas"],
queryFn: async () => {
const result = await callApi("get_disk_schemas", {
flake: {
identifier: props.dir,
machine: {
flake: {
identifier: props.dir,
},
name: props.machine_id,
},
machine_name: props.machine_id,
});
if (result.status === "error") throw new Error("Failed to fetch data");
return result.data;

View File

@@ -52,10 +52,12 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
queryKey: [props.dir, props.machine_id, "hw_report"],
queryFn: async () => {
const result = await callApi("show_machine_hardware_config", {
flake: {
identifier: props.dir,
machine: {
flake: {
identifier: props.dir,
},
name: props.machine_id,
},
machine_name: props.machine_id,
});
if (result.status === "error") throw new Error("Failed to fetch data");
return result.data;
@@ -85,9 +87,13 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
setIsGenerating(true);
const r = await callApi("generate_machine_hardware_info", {
opts: {
flake: { identifier: curr_uri },
machine: props.machine_id,
target_host: target,
machine: {
name: props.machine_id,
override_target_host: target,
flake: {
identifier: curr_uri,
},
},
backend: "nixos-facter",
},
});

View File

@@ -68,7 +68,7 @@ export const MachineListView: Component = () => {
});
const navigate = useNavigate();
const [view, setView] = makePersisted(createSignal<"list" | "grid">("list"), {
const [view, setView] = makePersisted(createSignal<"list" | "grid">("grid"), {
name: "machines_view",
storage: localStorage,
});