├── .envrc ├── .gitea └── workflows │ ├── checks.yaml │ ├── deploy.yaml │ └── update-clan-core-for-checks.yml ├── .github ├── dependabot.yml └── workflows │ └── repo-sync.yml ├── .gitignore ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── checks ├── admin │ ├── default.nix │ ├── private-test-key │ ├── sops │ │ ├── machines │ │ │ └── server │ │ │ │ └── key.json │ │ ├── secrets │ │ │ └── server-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ └── admin │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ └── vars │ │ └── per-machine │ │ └── server │ │ ├── openssh │ │ ├── ssh.id_ed25519.pub │ │ │ └── value │ │ └── ssh.id_ed25519 │ │ │ ├── machines │ │ │ └── server │ │ │ ├── secret │ │ │ └── users │ │ │ └── admin │ │ └── root-password │ │ ├── password-hash │ │ ├── machines │ │ │ └── server │ │ ├── secret │ │ └── users │ │ │ └── admin │ │ └── password │ │ ├── secret │ │ └── users │ │ └── admin ├── app-ocr │ └── default.nix ├── assets │ └── ssh │ │ ├── privkey │ │ └── pubkey ├── backups │ └── flake-module.nix ├── borgbackup-legacy │ └── default.nix ├── borgbackup │ ├── default.nix │ ├── sops │ │ ├── machines │ │ │ └── clientone │ │ │ │ └── key.json │ │ ├── secrets │ │ │ └── clientone-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ └── admin │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ └── vars │ │ └── per-machine │ │ └── clientone │ │ └── borgbackup │ │ ├── borgbackup.repokey │ │ ├── machines │ │ │ └── clientone │ │ ├── secret │ │ └── users │ │ │ └── admin │ │ ├── borgbackup.ssh.pub │ │ └── value │ │ └── borgbackup.ssh │ │ ├── machines │ │ └── clientone │ │ ├── secret │ │ └── users │ │ └── admin ├── clan-core-for-checks.nix ├── container │ └── default.nix ├── data-mesher │ ├── default.nix │ ├── sops │ │ ├── machines │ │ │ ├── admin │ │ │ │ └── key.json │ │ │ ├── peer │ │ │ │ └── key.json │ │ │ └── signer │ │ │ │ └── key.json │ │ ├── secrets │ │ │ ├── admin-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ ├── peer-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ └── signer-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ └── admin │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ └── vars │ │ ├── per-machine │ │ ├── admin │ │ │ └── data-mesher-host-key │ │ │ │ ├── private_key │ │ │ │ ├── machines │ │ │ │ │ └── admin │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ │ └── public_key │ │ │ │ └── value │ │ ├── peer │ │ │ └── data-mesher-host-key │ │ │ │ ├── private_key │ │ │ │ ├── machines │ │ │ │ │ └── peer │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ │ └── public_key │ │ │ │ └── value │ │ └── signer │ │ │ └── data-mesher-host-key │ │ │ ├── private_key │ │ │ ├── machines │ │ │ │ └── signer │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ │ └── public_key │ │ │ └── value │ │ └── shared │ │ └── data-mesher-network-key │ │ ├── private_key │ │ ├── machines │ │ │ ├── admin │ │ │ ├── peer │ │ │ └── signer │ │ ├── secret │ │ └── users │ │ │ └── admin │ │ └── public_key │ │ └── value ├── deltachat │ └── default.nix ├── devshell │ └── flake-module.nix ├── dont-depend-on-repo-root.nix ├── dummy-inventory-test │ ├── default.nix │ ├── legacy-module │ │ ├── README.md │ │ ├── roles │ │ │ ├── admin.nix │ │ │ └── peer.nix │ │ └── shared.nix │ ├── sops │ │ ├── machines │ │ │ ├── admin1 │ │ │ │ └── key.json │ │ │ └── peer1 │ │ │ │ └── key.json │ │ ├── secrets │ │ │ ├── admin1-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ └── peer1-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ └── admin │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ └── vars │ │ └── per-machine │ │ ├── admin1 │ │ └── dummy-generator │ │ │ ├── generated-password │ │ │ ├── machines │ │ │ │ └── admin1 │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ │ └── host-id │ │ │ └── value │ │ └── peer1 │ │ ├── dummy-generator │ │ ├── generated-password │ │ │ ├── machines │ │ │ │ └── peer1 │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ └── host-id │ │ │ └── value │ │ └── new-service │ │ ├── a-secret │ │ ├── machines │ │ │ └── peer1 │ │ ├── secret │ │ └── users │ │ │ └── admin │ │ └── not-a-secret │ │ └── value ├── flake-module.nix ├── flash │ └── flake-module.nix ├── impure │ └── flake-module.nix ├── installation │ └── flake-module.nix ├── matrix-synapse │ ├── default.nix │ └── synapse-registration_shared_secret ├── morph │ ├── flake-module.nix │ └── template │ │ └── configuration.nix ├── mumble │ ├── default.nix │ ├── machines │ │ ├── peer1 │ │ │ ├── facts │ │ │ │ └── mumble-cert │ │ │ ├── key.age │ │ │ ├── peer_1_test_cert │ │ │ └── peer_1_test_key │ │ └── peer2 │ │ │ ├── facts │ │ │ └── mumble-cert │ │ │ ├── peer_2_test_cert │ │ │ └── peer_2_test_key │ ├── peer_1 │ │ ├── key.age │ │ ├── peer_1_test_cert │ │ └── peer_1_test_key │ ├── peer_2 │ │ ├── peer_2_test_cert │ │ └── peer_2_test_key │ ├── sops │ │ ├── machines │ │ │ ├── peer1 │ │ │ │ └── key.json │ │ │ └── peer2 │ │ │ │ └── key.json │ │ ├── secrets │ │ │ ├── peer1-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ └── peer2-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ └── admin │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ └── vars │ │ └── per-machine │ │ ├── peer1 │ │ └── mumble │ │ │ ├── mumble-cert │ │ │ └── value │ │ │ └── mumble-key │ │ │ ├── machines │ │ │ └── peer1 │ │ │ ├── secret │ │ │ └── users │ │ │ └── admin │ │ └── peer2 │ │ └── mumble │ │ ├── mumble-cert │ │ └── value │ │ └── mumble-key │ │ ├── machines │ │ └── peer2 │ │ ├── secret │ │ └── users │ │ └── admin ├── nixos-documentation │ └── flake-module.nix ├── postgresql │ └── default.nix ├── secrets │ ├── .clan-flake │ ├── clan-secrets │ ├── default.nix │ ├── key.age │ └── sops │ │ ├── groups │ │ └── group │ │ │ └── machines │ │ │ └── machine │ │ ├── machines │ │ └── machine │ │ │ └── key.json │ │ ├── secrets │ │ ├── group-secret │ │ │ ├── groups │ │ │ │ └── group │ │ │ └── secret │ │ └── secret │ │ │ ├── machines │ │ │ └── machine │ │ │ ├── secret │ │ │ └── users │ │ │ └── admin │ │ └── users │ │ └── admin │ │ └── key.json ├── syncthing │ ├── default.nix │ ├── sops │ │ ├── machines │ │ │ ├── introducer │ │ │ │ └── key.json │ │ │ ├── peer1 │ │ │ │ └── key.json │ │ │ └── peer2 │ │ │ │ └── key.json │ │ ├── secrets │ │ │ ├── introducer-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ ├── peer1-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ │ └── admin │ │ │ └── peer2-age.key │ │ │ │ ├── secret │ │ │ │ └── users │ │ │ │ └── admin │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ └── vars │ │ └── per-machine │ │ ├── introducer │ │ └── syncthing │ │ │ ├── apikey │ │ │ ├── machines │ │ │ │ └── introducer │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ │ ├── cert │ │ │ ├── machines │ │ │ │ └── introducer │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ │ ├── id │ │ │ └── value │ │ │ └── key │ │ │ ├── machines │ │ │ └── introducer │ │ │ ├── secret │ │ │ └── users │ │ │ └── admin │ │ ├── peer1 │ │ └── syncthing │ │ │ ├── apikey │ │ │ ├── machines │ │ │ │ └── peer1 │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ │ ├── cert │ │ │ ├── machines │ │ │ │ └── peer1 │ │ │ ├── secret │ │ │ └── users │ │ │ │ └── admin │ │ │ ├── id │ │ │ └── value │ │ │ └── key │ │ │ ├── machines │ │ │ └── peer1 │ │ │ ├── secret │ │ │ └── users │ │ │ └── admin │ │ └── peer2 │ │ └── syncthing │ │ ├── apikey │ │ ├── machines │ │ │ └── peer2 │ │ ├── secret │ │ └── users │ │ │ └── admin │ │ ├── cert │ │ ├── machines │ │ │ └── peer2 │ │ ├── secret │ │ └── users │ │ │ └── admin │ │ ├── id │ │ └── value │ │ └── key │ │ ├── machines │ │ └── peer2 │ │ ├── secret │ │ └── users │ │ └── admin ├── wayland-proxy-virtwl │ └── default.nix └── zt-tcp-relay │ └── default.nix ├── clanModules ├── admin │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── auto-upgrade │ ├── README.md │ └── roles │ │ └── default.nix ├── borgbackup-static │ ├── README.md │ └── default.nix ├── borgbackup │ ├── README.md │ ├── default.nix │ └── roles │ │ ├── client.nix │ │ └── server.nix ├── data-mesher │ ├── README.md │ ├── lib.nix │ ├── roles │ │ ├── admin.nix │ │ ├── peer.nix │ │ └── signer.nix │ └── shared.nix ├── deltachat │ ├── README.md │ └── default.nix ├── disk-id │ ├── README.md │ ├── default.nix │ └── roles │ │ ├── default.nix │ │ └── uuid4.sh ├── dyndns │ ├── README.md │ └── default.nix ├── ergochat │ ├── README.md │ └── default.nix ├── flake-module.nix ├── garage │ ├── README.md │ └── default.nix ├── golem-provider │ ├── README.md │ ├── default.nix │ ├── interface.nix │ └── test │ │ └── vm.nix ├── heisenbridge │ ├── README.md │ └── default.nix ├── importer │ ├── README.md │ └── roles │ │ └── default.nix ├── iwd │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── localbackup │ ├── README.md │ └── default.nix ├── localsend │ ├── README.md │ ├── default.nix │ └── localsend-ensure-config │ │ ├── default.nix │ │ └── localsend-ensure-config.py ├── machine-id │ ├── README.md │ ├── default.nix │ └── roles │ │ ├── default.nix │ │ └── uuid4.sh ├── matrix-synapse │ ├── README.md │ └── default.nix ├── moonlight │ ├── README.md │ └── default.nix ├── mumble │ ├── README.md │ ├── default.nix │ └── roles │ │ ├── mumble-populate-channels.py │ │ └── server.nix ├── mycelium │ ├── README.md │ └── roles │ │ └── peer.nix ├── nginx │ ├── README.md │ └── default.nix ├── packages │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── postgresql │ ├── README.md │ └── default.nix ├── root-password │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── single-disk │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── sshd │ ├── README.md │ ├── default.nix │ ├── roles │ │ ├── client.nix │ │ └── server.nix │ └── shared.nix ├── state-version │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── static-hosts │ ├── README.md │ └── default.nix ├── sunshine │ ├── README.md │ └── default.nix ├── syncthing-static-peers │ ├── README.md │ └── default.nix ├── syncthing │ ├── README.md │ ├── default.nix │ ├── roles │ │ ├── introducer.nix │ │ └── peer.nix │ └── shared.nix ├── thelounge │ ├── README.md │ └── default.nix ├── trusted-nix-caches │ ├── README.md │ └── default.nix ├── user-password │ ├── README.md │ ├── default.nix │ └── roles │ │ └── default.nix ├── vaultwarden │ ├── README.md │ └── default.nix ├── xfce │ ├── README.md │ └── default.nix ├── zerotier-static-peers │ ├── README.md │ └── default.nix ├── zerotier │ ├── README.md │ ├── roles │ │ ├── controller.nix │ │ ├── moon.nix │ │ └── peer.nix │ └── shared.nix └── zt-tcp-relay │ ├── README.md │ └── default.nix ├── clanServices ├── admin │ ├── default.nix │ └── flake-module.nix ├── borgbackup │ ├── README.md │ ├── default.nix │ └── flake-module.nix ├── flake-module.nix ├── hello-world │ ├── default.nix │ ├── flake-module.nix │ └── tests │ │ ├── eval-tests.nix │ │ └── vm │ │ ├── default.nix │ │ ├── sops │ │ └── users │ │ │ └── admin │ │ │ └── key.json │ │ └── vars │ │ └── per-machine │ │ └── peer1 │ │ └── hello │ │ └── hello │ │ └── value └── wifi │ ├── default.nix │ ├── flake-module.nix │ └── tests │ ├── eval-tests.nix │ └── vm │ ├── default.nix │ ├── sops │ ├── machines │ │ └── test │ │ │ └── key.json │ ├── secrets │ │ └── test-age.key │ │ │ ├── secret │ │ │ └── users │ │ │ └── admin │ └── users │ │ └── admin │ │ └── key.json │ └── vars │ └── shared │ └── wifi.one │ ├── network-name │ ├── machines │ │ └── test │ ├── secret │ └── users │ │ └── admin │ └── password │ ├── machines │ └── test │ ├── secret │ └── users │ └── admin ├── devShell.nix ├── docs ├── .envrc ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── _internal │ └── use-cases │ │ ├── 01-self-host.md │ │ ├── 02-join-network.md │ │ ├── 03-maintaining-clan-modules.md │ │ └── _template.md ├── code-examples │ ├── disko-raid.nix │ └── disko-single-disk.nix ├── main.py ├── mkdocs.yml ├── nix │ ├── default.nix │ ├── deploy-docs.nix │ ├── flake-module.nix │ ├── get-module-docs.nix │ ├── render_options │ │ └── __init__.py │ └── shell.nix ├── overrides │ └── main.html └── site │ ├── decisions │ ├── 01-ClanModules.md │ ├── 02-clan-api.md │ ├── 03-adr-numbering-process.md │ ├── 04-fetching-nix-from-python.md │ ├── 05-deployment-parameters.md │ ├── README.md │ └── _template.md │ ├── guides │ ├── authoring │ │ ├── clanModules │ │ │ └── index.md │ │ ├── clanServices │ │ │ └── index.md │ │ └── templates │ │ │ └── disk │ │ │ └── disko-templates.md │ ├── backups.md │ ├── clanServices.md │ ├── contributing │ │ ├── CONTRIBUTING.md │ │ ├── debugging.md │ │ └── testing.md │ ├── disk-encryption.md │ ├── flake-parts.md │ ├── getting-started │ │ ├── add-machines.md │ │ ├── check.md │ │ ├── deploy.md │ │ ├── index.md │ │ ├── installer.md │ │ └── secrets.md │ ├── inventory.md │ ├── macos.md │ ├── mesh-vpn.md │ ├── migrations │ │ ├── migrate-inventory-services.md │ │ ├── migration-facts-vars.md │ │ └── migration-guide.md │ ├── more-machines.md │ ├── secrets.md │ ├── secure-boot.md │ └── vars-backend.md │ ├── index.md │ ├── reference │ ├── glossary.md │ └── index.md │ └── static │ └── extra.css ├── flake.lock ├── flake.nix ├── flakeModules ├── clan.nix ├── demo_iso.nix ├── demo_template │ └── configuration.nix └── flake-module.nix ├── formatter.nix ├── inventory.json ├── lib ├── README.md ├── build-clan │ ├── computed-tags.nix │ ├── default.nix │ ├── eval-docs.nix │ ├── flake-module.nix │ ├── function-adapter.nix │ ├── interface.nix │ ├── machineModules │ │ ├── forName.nix │ │ └── overridePkgs.nix │ ├── module.nix │ ├── public.nix │ ├── secrets │ │ └── interface.nix │ ├── templates │ │ └── interface.nix │ └── tests.nix ├── clanTest │ └── flake-module.nix ├── default.nix ├── facts.nix ├── filter-clan-core │ ├── flake-module.nix │ └── nix-filter.nix ├── flake-module.nix ├── introspection │ ├── default.nix │ ├── flake-module.nix │ └── test.nix ├── inventory │ ├── README.md │ ├── build-inventory │ │ ├── assertions.nix │ │ ├── builder │ │ │ ├── default.nix │ │ │ ├── interface.nix │ │ │ └── roles.nix │ │ ├── default.nix │ │ ├── interface.nix │ │ ├── inventory-introspection.nix │ │ ├── meta-interface.nix │ │ └── service-list-from-inputs.nix │ ├── constraints │ │ ├── default.nix │ │ └── interface.nix │ ├── default.nix │ ├── distributed-service │ │ ├── api-feature.nix │ │ ├── flake-module.nix │ │ ├── inventory-adapter.nix │ │ ├── manifest │ │ │ └── default.nix │ │ ├── service-module.nix │ │ └── tests │ │ │ ├── default.nix │ │ │ ├── import_module_spec.nix │ │ │ ├── per_instance_args.nix │ │ │ └── per_machine_args.nix │ ├── eval-clan-modules │ │ └── default.nix │ ├── flake-module.nix │ ├── frontmatter │ │ ├── default.nix │ │ └── interface.nix │ ├── schemas │ │ ├── default.nix │ │ └── render_schema.py │ └── tests │ │ ├── default.nix │ │ └── legacyModule │ │ ├── README.md │ │ └── roles │ │ └── default.nix ├── jsonschema │ ├── default.nix │ ├── example-data.json │ ├── example-interface.nix │ ├── example-schema.json │ ├── flake-module.nix │ ├── gen-options-json.sh │ ├── options.json │ ├── test.nix │ ├── test_parseOption.nix │ └── test_parseOptions.nix ├── test │ ├── container-test-driver │ │ ├── driver-module.nix │ │ ├── nixos-module.nix │ │ ├── package.nix │ │ ├── pyproject.toml │ │ ├── test-script-prepend.py │ │ └── test_driver │ │ │ ├── __init__.py │ │ │ ├── logger.py │ │ │ └── py.typed │ ├── container-test.nix │ ├── default.nix │ ├── flakeModules.nix │ ├── minify.nix │ ├── sops.nix │ └── test-base.nix └── types │ ├── default.nix │ ├── flake-module.nix │ └── tests.nix ├── machines ├── test-backup │ └── facts │ │ └── borgbackup.ssh.pub └── test-inventory-machine │ └── facter.json ├── nixosModules ├── bcachefs.nix ├── clanCore │ ├── backups.nix │ ├── default.nix │ ├── defaults.nix │ ├── facts │ │ ├── compat.nix │ │ ├── default.nix │ │ ├── public │ │ │ ├── in_repo.nix │ │ │ └── vm.nix │ │ └── secret │ │ │ ├── password-store.nix │ │ │ ├── sops.nix │ │ │ └── vm.nix │ ├── inventory │ │ ├── default.nix │ │ ├── implementation.nix │ │ └── interface.nix │ ├── meta │ │ └── interface.nix │ ├── metadata.nix │ ├── networking.nix │ ├── nix-settings.nix │ ├── nixos-facter.nix │ ├── options.nix │ ├── outputs.nix │ ├── serial.nix │ ├── sops.nix │ ├── state.nix │ ├── vars │ │ ├── default.nix │ │ ├── eval-tests │ │ │ └── default.nix │ │ ├── flake-module.nix │ │ ├── generator.nix │ │ ├── interface.nix │ │ ├── public │ │ │ └── in_repo.nix │ │ ├── secret │ │ │ ├── fs.nix │ │ │ ├── on-machine.nix │ │ │ ├── password-store.nix │ │ │ ├── sops │ │ │ │ ├── default.nix │ │ │ │ ├── eval-tests │ │ │ │ │ ├── default.nix │ │ │ │ │ └── populated │ │ │ │ │ │ └── vars │ │ │ │ │ │ └── my_machine │ │ │ │ │ │ └── my_generator │ │ │ │ │ │ └── my_secret │ │ │ │ └── funcs.nix │ │ │ └── vm.nix │ │ └── settings-opts.nix │ ├── vm.nix │ ├── wayland-proxy-virtwl.nix │ ├── waypipe.nix │ ├── zerotier │ │ ├── default.nix │ │ ├── generate.py │ │ └── genmoon.py │ └── zfs.nix ├── flake-module.nix ├── hidden-ssh-announce.nix └── installer │ ├── default.nix │ └── zfs-latest.nix ├── pkgs ├── builders │ └── script-writers.nix ├── clan-app │ ├── .envrc │ ├── README.md │ ├── bin │ │ ├── clan-app │ │ ├── reload-python-api.sh │ │ ├── start-qemu-vm.sh │ │ └── start-vm │ ├── clan-app.code-workspace │ ├── clan_app │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ └── file_gtk.py │ │ ├── app.py │ │ ├── assets │ │ │ ├── __init__.py │ │ │ └── white-favicons │ │ │ │ ├── 128x128 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ ├── 16x16 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ ├── 32x32 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ ├── 48x48 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ └── 64x64 │ │ │ │ └── apps │ │ │ │ └── clan-white.png │ │ └── deps │ │ │ ├── __init__.py │ │ │ └── webview │ │ │ ├── __init__.py │ │ │ ├── _webview_ffi.py │ │ │ └── webview.py │ ├── default.nix │ ├── flake-module.nix │ ├── fonts.nix │ ├── install-desktop.sh │ ├── process-compose.yaml │ ├── pyproject.toml │ ├── shell.nix │ ├── tests │ │ ├── command.py │ │ ├── conftest.py │ │ ├── helpers │ │ │ ├── __init__.py │ │ │ └── cli.py │ │ ├── root.py │ │ ├── temporary_dir.py │ │ ├── test_cli.py │ │ ├── test_join.py │ │ └── wayland.py │ ├── ui.nix │ ├── ui │ │ ├── .gitignore │ │ ├── .storybook │ │ │ ├── main.ts │ │ │ ├── preview.css │ │ │ ├── preview.ts │ │ │ └── test-runner.ts │ │ ├── .vscode │ │ │ └── settings.json │ │ ├── README.md │ │ ├── eslint.config.mjs │ │ ├── gtk.webview.js │ │ ├── icons │ │ │ ├── arrow-bottom.svg │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── arrow-top.svg │ │ │ ├── attention.svg │ │ │ ├── caret-down.svg │ │ │ ├── caret-left.svg │ │ │ ├── caret-right.svg │ │ │ ├── caret-up.svg │ │ │ ├── checkmark.svg │ │ │ ├── clan-icon.svg │ │ │ ├── clan-logo.svg │ │ │ ├── close.svg │ │ │ ├── download.svg │ │ │ ├── edit.svg │ │ │ ├── expand.svg │ │ │ ├── eye-close.svg │ │ │ ├── eye-open.svg │ │ │ ├── filter.svg │ │ │ ├── flash.svg │ │ │ ├── folder.svg │ │ │ ├── grid.svg │ │ │ ├── info.svg │ │ │ ├── list.svg │ │ │ ├── load.svg │ │ │ ├── more.svg │ │ │ ├── paperclip.svg │ │ │ ├── plus.svg │ │ │ ├── reload.svg │ │ │ ├── report.svg │ │ │ ├── search.svg │ │ │ ├── settings.svg │ │ │ ├── trash.svg │ │ │ ├── update.svg │ │ │ └── warning.svg │ │ ├── index.html │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── prettier.config.js │ │ ├── src │ │ │ ├── Form │ │ │ │ ├── base │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── label.tsx │ │ │ │ ├── fields │ │ │ │ │ ├── FormSection.tsx │ │ │ │ │ ├── Select.tsx │ │ │ │ │ ├── TextInput.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── layout.tsx │ │ │ │ ├── fieldset │ │ │ │ │ └── index.tsx │ │ │ │ └── form │ │ │ │ │ └── index.tsx │ │ │ ├── api │ │ │ │ ├── index.tsx │ │ │ │ ├── manual_types.tsx │ │ │ │ └── wifi.ts │ │ │ ├── api_test.tsx │ │ │ ├── components │ │ │ │ ├── BackButton.tsx │ │ │ │ ├── Button │ │ │ │ │ ├── Button-Base.css │ │ │ │ │ ├── Button-Dark.css │ │ │ │ │ ├── Button-Ghost.css │ │ │ │ │ ├── Button-Light.css │ │ │ │ │ └── Button.tsx │ │ │ │ ├── FileInput.tsx │ │ │ │ ├── Helpers │ │ │ │ │ ├── List.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Menu.tsx │ │ │ │ ├── SelectInput.tsx │ │ │ │ ├── Sidebar │ │ │ │ │ ├── SidebarFlyout │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── SidebarHeader.tsx │ │ │ │ │ ├── SidebarListItem.tsx │ │ │ │ │ ├── css │ │ │ │ │ │ ├── sidebar-flyout.css │ │ │ │ │ │ ├── sidebar-header.css │ │ │ │ │ │ ├── sidebar-list-item.css │ │ │ │ │ │ ├── sidebar-profile.css │ │ │ │ │ │ └── sidebar.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── TagList │ │ │ │ │ ├── TagList.css │ │ │ │ │ ├── TagList.stories.tsx │ │ │ │ │ ├── TagList.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── TagList.stories.tsx.snap │ │ │ │ ├── Typography │ │ │ │ │ ├── css │ │ │ │ │ │ ├── typography-color.css │ │ │ │ │ │ ├── typography-hierarchy │ │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ │ ├── typography-body.css │ │ │ │ │ │ │ ├── typography-headline.css │ │ │ │ │ │ │ ├── typography-label.css │ │ │ │ │ │ │ └── typography-title.css │ │ │ │ │ │ └── typography.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── accordion │ │ │ │ │ ├── accordion.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── badge │ │ │ │ │ └── index.tsx │ │ │ │ ├── fileSelect │ │ │ │ │ └── index.tsx │ │ │ │ ├── group │ │ │ │ │ └── index.tsx │ │ │ │ ├── icon │ │ │ │ │ └── index.tsx │ │ │ │ ├── inputBase │ │ │ │ │ └── index.tsx │ │ │ │ ├── machine-list-item │ │ │ │ │ ├── css │ │ │ │ │ │ └── index.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── modal │ │ │ │ │ └── index.tsx │ │ │ │ ├── noiseThumbnail │ │ │ │ │ └── index.tsx │ │ │ │ ├── toast │ │ │ │ │ └── index.tsx │ │ │ │ └── v2 │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Typography │ │ │ │ │ ├── Typography.css │ │ │ │ │ ├── Typography.mdx │ │ │ │ │ ├── Typography.stories.tsx │ │ │ │ │ ├── Typography.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── Typography.stories.tsx.snap │ │ │ │ │ └── index.css │ │ │ ├── contexts │ │ │ │ └── clan.tsx │ │ │ ├── floating │ │ │ │ └── index.tsx │ │ │ ├── hooks │ │ │ │ └── index.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── layout │ │ │ │ ├── header.tsx │ │ │ │ └── layout.tsx │ │ │ ├── queries │ │ │ │ ├── clan-meta.ts │ │ │ │ └── index.ts │ │ │ ├── routes │ │ │ │ ├── clans │ │ │ │ │ ├── create.tsx │ │ │ │ │ ├── details.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── list.tsx │ │ │ │ ├── colors │ │ │ │ │ └── view.tsx │ │ │ │ ├── components │ │ │ │ │ └── index.tsx │ │ │ │ ├── deploy │ │ │ │ │ └── index.tsx │ │ │ │ ├── disk │ │ │ │ │ └── view.tsx │ │ │ │ ├── flash │ │ │ │ │ └── view.tsx │ │ │ │ ├── hosts │ │ │ │ │ └── view.tsx │ │ │ │ ├── machines │ │ │ │ │ ├── avatar.tsx │ │ │ │ │ ├── create.tsx │ │ │ │ │ ├── details.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── install │ │ │ │ │ │ ├── disk-step.tsx │ │ │ │ │ │ ├── hardware-step.tsx │ │ │ │ │ │ ├── summary-step.tsx │ │ │ │ │ │ └── vars-step.tsx │ │ │ │ │ └── list.tsx │ │ │ │ ├── modules │ │ │ │ │ ├── add.tsx │ │ │ │ │ ├── details.tsx │ │ │ │ │ └── list.tsx │ │ │ │ └── welcome │ │ │ │ │ └── index.tsx │ │ │ ├── stores │ │ │ │ └── clan.tsx │ │ │ ├── three.tsx │ │ │ └── types │ │ │ │ └── index.d.ts │ │ ├── stylelint.config.js │ │ ├── tailwind.config.ts │ │ ├── tailwind │ │ │ ├── core-plugin.ts │ │ │ └── typography.ts │ │ ├── tests │ │ │ └── types.test.ts │ │ ├── tsconfig.json │ │ ├── util.ts │ │ └── vite.config.ts │ └── webview-lib │ │ └── default.nix ├── clan-cli │ ├── .envrc │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── README.md │ ├── api.py │ ├── bin │ │ ├── clan │ │ └── clan-config │ ├── clan_cli │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── arg_actions.py │ │ ├── backups │ │ │ ├── __init__.py │ │ │ ├── create.py │ │ │ ├── list.py │ │ │ └── restore.py │ │ ├── clan │ │ │ ├── __init__.py │ │ │ ├── create.py │ │ │ ├── inspect.py │ │ │ ├── list.py │ │ │ ├── show.py │ │ │ └── update.py │ │ ├── completions.py │ │ ├── conftest.py │ │ ├── facts │ │ │ ├── __init__.py │ │ │ ├── check.py │ │ │ ├── cli.py │ │ │ ├── generate.py │ │ │ ├── list.py │ │ │ ├── public_modules │ │ │ │ ├── __init__.py │ │ │ │ ├── in_repo.py │ │ │ │ └── vm.py │ │ │ ├── secret_modules │ │ │ │ ├── __init__.py │ │ │ │ ├── password_store.py │ │ │ │ ├── sops.py │ │ │ │ └── vm.py │ │ │ └── upload.py │ │ ├── flash │ │ │ ├── __init__.py │ │ │ ├── automount.py │ │ │ ├── cli.py │ │ │ ├── flash.py │ │ │ ├── flash_cmd.py │ │ │ ├── inhibit.sh │ │ │ └── list.py │ │ ├── hyperlink.py │ │ ├── machines │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ ├── create.py │ │ │ ├── delete.py │ │ │ ├── hardware.py │ │ │ ├── install.py │ │ │ ├── list.py │ │ │ ├── machines_test.py │ │ │ ├── morph.py │ │ │ ├── types.py │ │ │ └── update.py │ │ ├── profiler.py │ │ ├── py.typed │ │ ├── qemu │ │ │ ├── __init__.py │ │ │ ├── qga.py │ │ │ └── qmp.py │ │ ├── secrets │ │ │ ├── __init__.py │ │ │ ├── filters.py │ │ │ ├── folders.py │ │ │ ├── groups.py │ │ │ ├── import_sops.py │ │ │ ├── key.py │ │ │ ├── machines.py │ │ │ ├── secrets.py │ │ │ ├── sops.py │ │ │ ├── types.py │ │ │ └── users.py │ │ ├── select.py │ │ ├── ssh │ │ │ ├── __init__.py │ │ │ ├── deploy_info.py │ │ │ ├── host_key.py │ │ │ ├── results.py │ │ │ ├── tor.py │ │ │ └── upload.py │ │ ├── state │ │ │ ├── __init__.py │ │ │ └── list.py │ │ ├── tests │ │ │ ├── age_keys.py │ │ │ ├── command.py │ │ │ ├── data │ │ │ │ ├── gnupg-home │ │ │ │ │ ├── openpgp-revocs.d │ │ │ │ │ │ └── 9A9B2741C8062D3D3DF1302D8B049E262A5CA255.rev │ │ │ │ │ ├── private-keys-v1.d │ │ │ │ │ │ └── 893F0D3827CC473BAEFE4A6B3E910245CD2CCFF9.key │ │ │ │ │ ├── pubring.kbx │ │ │ │ │ ├── random_seed │ │ │ │ │ └── trustdb.gpg │ │ │ │ ├── gnupg.conf │ │ │ │ ├── password-store │ │ │ │ │ └── .gpg-id │ │ │ │ ├── secrets.yaml │ │ │ │ ├── ssh_host_ed25519_key │ │ │ │ ├── ssh_host_ed25519_key.pub │ │ │ │ └── sshd_config │ │ │ ├── fixture_error.py │ │ │ ├── fixtures_flakes.py │ │ │ ├── getpwnam-preload.c │ │ │ ├── git_repo.py │ │ │ ├── gpg_keys.py │ │ │ ├── helpers │ │ │ │ ├── __init__.py │ │ │ │ ├── cli.py │ │ │ │ ├── nixos_config.py │ │ │ │ └── validator.py │ │ │ ├── hosts.py │ │ │ ├── machines │ │ │ │ ├── vm1 │ │ │ │ │ └── default.nix │ │ │ │ ├── vm_with_secrets │ │ │ │ │ └── default.nix │ │ │ │ └── vm_without_secrets │ │ │ │ │ └── default.nix │ │ │ ├── nix_config.py │ │ │ ├── ports.py │ │ │ ├── root.py │ │ │ ├── runtime.py │ │ │ ├── sshd.py │ │ │ ├── stdout.py │ │ │ ├── temporary_dir.py │ │ │ ├── test_api_dataclass_compat.py │ │ │ ├── test_backups.py │ │ │ ├── test_clan_nix_attrset.py │ │ │ ├── test_cli.py │ │ │ ├── test_create_flake.py │ │ │ ├── test_dirs.py │ │ │ ├── test_flake │ │ │ │ ├── .clan-flake │ │ │ │ ├── fake-module.nix │ │ │ │ ├── flake.nix │ │ │ │ └── nixosModules │ │ │ │ │ └── machine1.nix │ │ │ ├── test_flake_with_core │ │ │ │ ├── .clan-flake │ │ │ │ └── flake.nix │ │ │ ├── test_flake_with_core_and_pass │ │ │ │ ├── .clan-flake │ │ │ │ └── flake.nix │ │ │ ├── test_flake_with_core_dynamic_machines │ │ │ │ ├── .clan-flake │ │ │ │ └── flake.nix │ │ │ ├── test_flakes_cli.py │ │ │ ├── test_git.py │ │ │ ├── test_import_sops_cli.py │ │ │ ├── test_inventory.py │ │ │ ├── test_inventory_serde.py │ │ │ ├── test_machines_cli.py │ │ │ ├── test_modules.py │ │ │ ├── test_secrets_cli.py │ │ │ ├── test_secrets_generate.py │ │ │ ├── test_ssh_local.py │ │ │ ├── test_upload_single_file.py │ │ │ ├── test_vars.py │ │ │ ├── test_vars_deployment.py │ │ │ └── test_vms_cli.py │ │ ├── vars │ │ │ ├── _types.py │ │ │ ├── check.py │ │ │ ├── cli.py │ │ │ ├── fix.py │ │ │ ├── generate.py │ │ │ ├── get.py │ │ │ ├── graph.py │ │ │ ├── keygen.py │ │ │ ├── list.py │ │ │ ├── migration.py │ │ │ ├── prompt.py │ │ │ ├── public_modules │ │ │ │ ├── __init__.py │ │ │ │ ├── in_repo.py │ │ │ │ └── vm.py │ │ │ ├── secret_modules │ │ │ │ ├── __init__.py │ │ │ │ ├── fs.py │ │ │ │ ├── password_store.py │ │ │ │ ├── sops.py │ │ │ │ └── vm.py │ │ │ ├── set.py │ │ │ ├── upload.py │ │ │ └── var.py │ │ └── vms │ │ │ ├── __init__.py │ │ │ ├── inspect.py │ │ │ ├── mimetypes │ │ │ └── applications │ │ │ │ ├── mimeapps.list │ │ │ │ └── remote-viewer.desktop │ │ │ ├── qemu.py │ │ │ ├── run.py │ │ │ ├── virtiofsd.py │ │ │ └── waypipe.py │ ├── clan_lib │ │ ├── __init__.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ ├── directory.py │ │ │ ├── disk.py │ │ │ ├── mdns_discovery.py │ │ │ ├── modules.py │ │ │ ├── network.py │ │ │ ├── serde.py │ │ │ ├── serde_deserialize_test.py │ │ │ ├── serde_serialize_test.py │ │ │ └── util.py │ │ ├── async_run │ │ │ └── __init__.py │ │ ├── backups │ │ │ ├── __init__.py │ │ │ ├── create.py │ │ │ ├── list.py │ │ │ └── restore.py │ │ ├── bwrap │ │ │ ├── __init__.py │ │ │ └── tests │ │ │ │ └── test_bwrap.py │ │ ├── clan │ │ │ ├── __init__.py │ │ │ └── create.py │ │ ├── cmd │ │ │ └── __init__.py │ │ ├── colors │ │ │ └── __init__.py │ │ ├── conftest.py │ │ ├── custom_logger │ │ │ └── __init__.py │ │ ├── dirs │ │ │ └── __init__.py │ │ ├── errors │ │ │ └── __init__.py │ │ ├── flake │ │ │ ├── __init__.py │ │ │ ├── flake.py │ │ │ └── flake_test.py │ │ ├── git │ │ │ └── __init__.py │ │ ├── inventory │ │ │ └── __init__.py │ │ ├── jsonrpc │ │ │ └── __init__.py │ │ ├── locked_open │ │ │ └── __init__.py │ │ ├── machines │ │ │ ├── __init__.py │ │ │ ├── actions.py │ │ │ └── machines.py │ │ ├── nix │ │ │ ├── __init__.py │ │ │ └── allowed-packages.json │ │ ├── nix_models │ │ │ ├── clan.py │ │ │ └── update.sh │ │ ├── persist │ │ │ ├── __init__.py │ │ │ ├── fixtures │ │ │ │ ├── 1.json │ │ │ │ ├── 1.nix │ │ │ │ ├── deferred.json │ │ │ │ ├── deferred.nix │ │ │ │ ├── lists.json │ │ │ │ └── lists.nix │ │ │ ├── inventory_store.py │ │ │ ├── inventory_store_test.py │ │ │ ├── util.py │ │ │ └── util_test.py │ │ ├── py.typed │ │ ├── ssh │ │ │ ├── __init__.py │ │ │ ├── parse.py │ │ │ ├── remote.py │ │ │ ├── remote_test.py │ │ │ ├── sudo_askpass_proxy.py │ │ │ └── sudo_askpass_proxy.sh │ │ ├── templates │ │ │ └── __init__.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ ├── assets │ │ │ └── facter.json │ │ │ └── test_create.py │ ├── conftest.py │ ├── default.nix │ ├── deps-flake.nix │ ├── docs.py │ ├── flake-module.nix │ ├── pyproject.toml │ └── shell.nix ├── clan-core-flake │ └── flake-module.nix ├── clan-vm-manager │ ├── .envrc │ ├── .gitignore │ ├── .vscode │ │ └── lhebendanz.weaudit │ ├── README.md │ ├── bin │ │ └── clan-vm-manager │ ├── clan-vm-manager.code-workspace │ ├── clan_vm_manager │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── app.py │ │ ├── assets │ │ │ ├── __init__.py │ │ │ ├── clan_black_notext.png │ │ │ ├── style.css │ │ │ └── white-favicons │ │ │ │ ├── 128x128 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ ├── 16x16 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ ├── 32x32 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ ├── 48x48 │ │ │ │ └── apps │ │ │ │ │ └── clan-white.png │ │ │ │ └── 64x64 │ │ │ │ └── apps │ │ │ │ └── clan-white.png │ │ ├── clan_uri.py │ │ ├── components │ │ │ ├── __init__.py │ │ │ ├── executor.py │ │ │ ├── gkvstore.py │ │ │ ├── interfaces.py │ │ │ ├── list_splash.py │ │ │ ├── trayicon.py │ │ │ └── vmobj.py │ │ ├── history.py │ │ ├── singletons │ │ │ ├── __init__.py │ │ │ ├── toast.py │ │ │ ├── use_join.py │ │ │ ├── use_views.py │ │ │ └── use_vms.py │ │ ├── views │ │ │ ├── __init__.py │ │ │ ├── details.py │ │ │ ├── list.py │ │ │ └── logs.py │ │ └── windows │ │ │ ├── __init__.py │ │ │ └── main_window.py │ ├── default.nix │ ├── demo.sh │ ├── flake-module.nix │ ├── install-desktop.sh │ ├── notes.md │ ├── pyproject.toml │ ├── screenshots │ │ └── image.png │ ├── shell.nix │ └── tests │ │ ├── command.py │ │ ├── conftest.py │ │ ├── helpers │ │ └── cli.py │ │ ├── root.py │ │ ├── stdout.py │ │ ├── temporary_dir.py │ │ ├── test_clan_uri.py │ │ ├── test_cli.py │ │ ├── test_join.py │ │ └── wayland.py ├── classgen │ ├── __init__.py │ ├── default.nix │ └── main.py ├── distro-packages │ ├── flake-module.nix │ ├── gui-installer │ │ ├── flake-module.nix │ │ └── gui-installer.sh │ └── vagrant_insecure_key ├── editor │ ├── clan-edit-codium.nix │ ├── default.nix │ └── settings.json ├── flake-module.nix ├── generate-test-vars │ ├── default.nix │ ├── flake-module.nix │ ├── generate_test_vars │ │ ├── __init_.py │ │ └── cli.py │ ├── pyproject.toml │ └── test │ │ └── vars.nix ├── go-ssb │ └── default.nix ├── icon-update │ ├── .envrc │ ├── .gitignore │ ├── README.md │ ├── default.nix │ ├── deno.json │ ├── deno.lock │ ├── figma.types.ts │ ├── flake-module.nix │ ├── main.ts │ └── shell.nix ├── installer │ ├── base64.nix │ ├── flake-module.nix │ └── iwd.nix ├── merge-after-ci │ ├── default.nix │ └── merge-after-ci.py ├── minifakeroot │ ├── default.nix │ └── main.c ├── moonlight-sunshine-accept │ ├── .envrc │ ├── default.nix │ ├── moonlight_sunshine_accept │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── errors.py │ │ ├── moonlight │ │ │ ├── __init__.py │ │ │ ├── init_certificates.py │ │ │ ├── init_config.py │ │ │ ├── join.py │ │ │ ├── run.py │ │ │ ├── state.py │ │ │ └── uri.py │ │ └── sunshine │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ ├── config.py │ │ │ ├── init_certificates.py │ │ │ ├── init_state.py │ │ │ ├── listen.py │ │ │ └── state.py │ └── pyproject.toml ├── pending-reviews │ ├── default.nix │ └── script.sh ├── scripts │ └── select-shell.py ├── tea-create-pr │ ├── default.nix │ └── script.sh ├── testing │ └── flake-module.nix ├── update-clan-core-for-checks │ └── default.nix ├── yagna │ └── default.nix ├── zerotier-members │ ├── default.nix │ └── zerotier-members.py ├── zerotierone │ └── default.nix └── zt-tcp-relay │ └── default.nix ├── pyproject.toml ├── reference └── cli │ └── index.md ├── renovate.json ├── scripts └── pre-commit ├── sops ├── machines │ └── test-backup │ │ └── key.json └── secrets │ └── test-backup-age.key │ └── secret ├── templates ├── clan │ ├── flake-parts │ │ ├── .envrc │ │ ├── flake.nix │ │ ├── machines │ │ │ ├── jon │ │ │ │ └── configuration.nix │ │ │ └── sara │ │ │ │ └── configuration.nix │ │ └── modules │ │ │ ├── disko.nix │ │ │ └── shared.nix │ ├── minimal-flake-parts │ │ ├── .gitignore │ │ ├── checks.nix │ │ ├── clan.nix │ │ ├── devshells.nix │ │ ├── flake.nix │ │ └── formatter.nix │ ├── minimal │ │ ├── flake.nix │ │ └── inventory.json │ └── new-clan │ │ ├── .clan-flake │ │ ├── .envrc │ │ ├── flake.nix │ │ ├── machines │ │ ├── jon │ │ │ └── configuration.nix │ │ └── sara │ │ │ └── configuration.nix │ │ └── modules │ │ ├── disko.nix │ │ ├── gnome.nix │ │ └── shared.nix ├── disk │ └── single-disk │ │ ├── README.md │ │ └── default.nix ├── flake-module.nix ├── flake.nix └── machine │ ├── flash-installer │ ├── configuration.nix │ └── disko.nix │ └── new-machine │ └── configuration.nix └── vars └── per-machine └── test-backup └── openssh ├── ssh.id_ed25519.pub └── value └── ssh.id_ed25519 ├── machines └── test-backup └── secret /.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then 3 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" 4 | fi 5 | 6 | watch_file .direnv/selected-shell 7 | watch_file formatter.nix 8 | 9 | if [ -e .direnv/selected-shell ]; then 10 | use flake ".#$(cat .direnv/selected-shell)" 11 | else 12 | use flake 13 | fi 14 | -------------------------------------------------------------------------------- /.gitea/workflows/checks.yaml: -------------------------------------------------------------------------------- 1 | name: checks 2 | on: 3 | pull_request: 4 | jobs: 5 | checks-impure: 6 | runs-on: nix 7 | steps: 8 | - uses: actions/checkout@v4 9 | - run: nix run .#impure-checks 10 | -------------------------------------------------------------------------------- /.gitea/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy-docs: 8 | runs-on: nix 9 | steps: 10 | - uses: actions/checkout@v4 11 | - run: nix run .#deploy-docs 12 | env: 13 | SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }} 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | nixosModules/clanCore/vars/.* @lopter 2 | pkgs/clan-cli/clan_cli/(secrets|vars)/.* @lopter 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Clan 2 | 3 | 4 | Go to the Contributing guide at https://docs.clan.lol/guides/contributing/CONTRIBUTING -------------------------------------------------------------------------------- /checks/admin/private-test-key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACCOs4+7m04Os/2g8ETcLzP1pUHVKHDKa0PBPdjVsZerfgAAAJDXdRkm13UZ 4 | JgAAAAtzc2gtZWQyNTUxOQAAACCOs4+7m04Os/2g8ETcLzP1pUHVKHDKa0PBPdjVsZerfg 5 | AAAECIgb2FQcgBKMniA+6zm2cwGre60ATu3Sg1GivgAqVJlI6zj7ubTg6z/aDwRNwvM/Wl 6 | QdUocMprQ8E92NWxl6t+AAAAC3BpbnBveEBraXdpAQI= 7 | -----END OPENSSH PRIVATE KEY----- 8 | 9 | -------------------------------------------------------------------------------- /checks/admin/sops/machines/server/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age1q4e7nsw5z6mqeqk5u5kug8lwhpq3f276s0t0npwfffwdkfh58gkqxknhjg", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/admin/sops/secrets/server-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/admin/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/admin/vars/per-machine/server/openssh/ssh.id_ed25519.pub/value: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICVVQjCEuryZii1LmJyjx9DX44eJh3qwTTEWlahYONsz nixbld@kiwi 2 | -------------------------------------------------------------------------------- /checks/admin/vars/per-machine/server/openssh/ssh.id_ed25519/machines/server: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/server -------------------------------------------------------------------------------- /checks/admin/vars/per-machine/server/openssh/ssh.id_ed25519/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/admin/vars/per-machine/server/root-password/password-hash/machines/server: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/server -------------------------------------------------------------------------------- /checks/admin/vars/per-machine/server/root-password/password-hash/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/admin/vars/per-machine/server/root-password/password/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/assets/ssh/privkey: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACASG8CFZy8vrqA2erivzgnNUCuOkiBngt5lXPOXai2EMAAAAJAOOON0Djjj 4 | dAAAAAtzc2gtZWQyNTUxOQAAACASG8CFZy8vrqA2erivzgnNUCuOkiBngt5lXPOXai2EMA 5 | AAAEDTjUOWSYeU3Xu+Ol1731b9rXeEVXSdrhVOraA+7/35JBIbwIVnLy+uoDZ6uK/OCc1Q 6 | K46SIGeC3mVc85dqLYQwAAAADGxhc3NAaWduYXZpYQE= 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /checks/assets/ssh/pubkey: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBIbwIVnLy+uoDZ6uK/OCc1QK46SIGeC3mVc85dqLYQw lass@ignavia 2 | -------------------------------------------------------------------------------- /checks/borgbackup/sops/machines/clientone/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age1tyyx2ratu8s9ugyre36xyksnquth9gxeh7wjdhvsk89rtf8yu5wq0pk04c", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/borgbackup/sops/secrets/clientone-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/borgbackup/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/borgbackup/vars/per-machine/clientone/borgbackup/borgbackup.repokey/machines/clientone: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/clientone -------------------------------------------------------------------------------- /checks/borgbackup/vars/per-machine/clientone/borgbackup/borgbackup.repokey/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/borgbackup/vars/per-machine/clientone/borgbackup/borgbackup.ssh.pub/value: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE3clYF6BDZ0PxfDdprx7YYM4U4PKEZkWUuhpre0wb7w nixbld@kiwi 2 | -------------------------------------------------------------------------------- /checks/borgbackup/vars/per-machine/clientone/borgbackup/borgbackup.ssh/machines/clientone: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/clientone -------------------------------------------------------------------------------- /checks/borgbackup/vars/per-machine/clientone/borgbackup/borgbackup.ssh/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/clan-core-for-checks.nix: -------------------------------------------------------------------------------- 1 | { fetchgit }: 2 | fetchgit { 3 | url = "https://git.clan.lol/clan/clan-core.git"; 4 | rev = "13a9b1719835ef4510e4adb6941ddfe9a91d41cb"; 5 | sha256 = "sha256-M+pLnpuX+vIsxTFtbBZaNA1OwGQPeSbsMbTiDl1t4vY="; 6 | } 7 | -------------------------------------------------------------------------------- /checks/data-mesher/sops/machines/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age10zxkj45fah3qa8uyg3a36jsd06d839xfq64nrez9etrsf4km0gtsp45gsz", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/data-mesher/sops/machines/peer/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1faqrml2ukc6unfm75d3v2vnaf62v92rdxaagg3ty3cfna7vt99gqlzs43l", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/data-mesher/sops/machines/signer/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age153mke8v2qksyqjc7vta7wglzdqr5epazt83nch0ur5v7kl87cfdsr07qld", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/data-mesher/sops/secrets/admin-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/data-mesher/sops/secrets/peer-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/data-mesher/sops/secrets/signer-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/data-mesher/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/admin/data-mesher-host-key/private_key/machines/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/admin -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/admin/data-mesher-host-key/private_key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/admin/data-mesher-host-key/public_key/value: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VwAyEAm204bpSFi4jOjZuXDpIZ/rcJBrbG4zAc7OSA4rAVSYE= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/peer/data-mesher-host-key/private_key/machines/peer: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/peer/data-mesher-host-key/private_key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/peer/data-mesher-host-key/public_key/value: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VwAyEAv5dICFue2fYO0Zi1IyfYjoNfR6713WpISo7+2bSjL18= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/signer/data-mesher-host-key/private_key/machines/signer: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/signer -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/signer/data-mesher-host-key/private_key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/data-mesher/vars/per-machine/signer/data-mesher-host-key/public_key/value: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VwAyEAeUkW5UIwA1svbNY71ePyJKX68UhxrqIUGQ2jd06w5WM= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /checks/data-mesher/vars/shared/data-mesher-network-key/private_key/machines/admin: -------------------------------------------------------------------------------- 1 | ../../../../../sops/machines/admin -------------------------------------------------------------------------------- /checks/data-mesher/vars/shared/data-mesher-network-key/private_key/machines/peer: -------------------------------------------------------------------------------- 1 | ../../../../../sops/machines/peer -------------------------------------------------------------------------------- /checks/data-mesher/vars/shared/data-mesher-network-key/private_key/machines/signer: -------------------------------------------------------------------------------- 1 | ../../../../../sops/machines/signer -------------------------------------------------------------------------------- /checks/data-mesher/vars/shared/data-mesher-network-key/private_key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/data-mesher/vars/shared/data-mesher-network-key/public_key/value: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VwAyEA/5j+Js7oxwWvZdfjfEO/3UuRqMxLKXsaNc3/5N2WSaw= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /checks/deltachat/default.nix: -------------------------------------------------------------------------------- 1 | (import ../lib/container-test.nix) ( 2 | { pkgs, ... }: 3 | { 4 | name = "deltachat"; 5 | 6 | nodes.machine = 7 | { self, ... }: 8 | { 9 | imports = [ 10 | self.clanModules.deltachat 11 | self.nixosModules.clanCore 12 | { 13 | clan.core.settings.directory = ./.; 14 | } 15 | ]; 16 | }; 17 | testScript = '' 18 | start_all() 19 | machine.wait_for_unit("maddy") 20 | # imap 21 | machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 143") 22 | # smtp submission 23 | machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 587") 24 | # smtp 25 | machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 25") 26 | ''; 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /checks/devshell/flake-module.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | perSystem = 4 | { self', pkgs, ... }: 5 | { 6 | checks.devshell = 7 | pkgs.runCommand "check-devshell-not-depends-on-clan-cli" 8 | { 9 | exportReferencesGraph = [ 10 | "graph" 11 | self'.devShells.default 12 | ]; 13 | } 14 | '' 15 | if grep -q "${self'.packages.clan-cli}" ./graph; then 16 | echo "devshell depends on clan-cli, which is not allowed"; 17 | exit 1; 18 | fi 19 | mkdir $out 20 | ''; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/legacy-module/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Set up dummy-module" 3 | categories = ["System"] 4 | features = [ "inventory" ] 5 | 6 | [constraints] 7 | roles.admin.min = 1 8 | roles.admin.max = 1 9 | --- 10 | 11 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/legacy-module/roles/admin.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ../shared.nix 4 | ]; 5 | } 6 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/legacy-module/roles/peer.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ../shared.nix 4 | ]; 5 | } 6 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/sops/machines/admin1/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age12yt078p9ewxy2sh0a36nxdpgglv8wqqftmj4dkj9rgy5fuyn4p0q5nje9m", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/sops/machines/peer1/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age12w2ld4vxfyf3hdq2d8la4cu0tye4pq97egvv3me4wary7xkdnq2snh0zx2", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/sops/secrets/admin1-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/dummy-inventory-test/sops/secrets/peer1-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/dummy-inventory-test/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/admin1/dummy-generator/generated-password/machines/admin1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/admin1 -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/admin1/dummy-generator/generated-password/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/admin1/dummy-generator/host-id/value: -------------------------------------------------------------------------------- 1 | 18650 2 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/peer1/dummy-generator/generated-password/machines/peer1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer1 -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/peer1/dummy-generator/generated-password/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/peer1/dummy-generator/host-id/value: -------------------------------------------------------------------------------- 1 | 6745 2 | -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/peer1/new-service/a-secret/machines/peer1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer1 -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/peer1/new-service/a-secret/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/dummy-inventory-test/vars/per-machine/peer1/new-service/not-a-secret/value: -------------------------------------------------------------------------------- 1 | not-a-secret -------------------------------------------------------------------------------- /checks/matrix-synapse/synapse-registration_shared_secret: -------------------------------------------------------------------------------- 1 | registration_shared_secret: supersecret 2 | -------------------------------------------------------------------------------- /checks/morph/template/configuration.nix: -------------------------------------------------------------------------------- 1 | { modulesPath, ... }: 2 | { 3 | imports = [ 4 | # we need these 2 modules always to be able to run the tests 5 | (modulesPath + "/testing/test-instrumentation.nix") 6 | (modulesPath + "/virtualisation/qemu-vm.nix") 7 | 8 | (modulesPath + "/profiles/minimal.nix") 9 | ]; 10 | 11 | virtualisation.useNixStoreImage = true; 12 | virtualisation.writableStore = true; 13 | 14 | clan.core.enableRecommendedDefaults = false; 15 | } 16 | -------------------------------------------------------------------------------- /checks/mumble/machines/peer1/key.age: -------------------------------------------------------------------------------- 1 | AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX 2 | -------------------------------------------------------------------------------- /checks/mumble/machines/peer1/peer_1_test_key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDA14Nqo17Xs/xRLGH2KLuyzjKp4eW9iWFobVNM93RZZbECT++W3XcQc 3 | cEc5WVtiPmWgBwYFK4EEACKhZANiAAQECvUKxyLAJrS+Lt4LrHG5IaKNje3FuO2z 4 | IVqd5z9+B7igkEPetWlosoURNvdO8cey69uXMSVw/jzcwRWroUxSjHC4v0LNO2km 5 | tGG3BKYCzwAcsW7yKtWfyxmOCQuxcyE= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /checks/mumble/machines/peer2/peer_2_test_key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDCXHGpvumKjjDRxB6SsjZOb7duw3w+rdlGQCJTIvRThLjD6zwjnyImi 3 | 7c3PD5nWtLqgBwYFK4EEACKhZANiAARWUzLeEX7HwbntL2u0LjXY31zCOB32cyQh 4 | HBvm/TLVexZQ5sDCl+X4BspA/RQWwu8os2t/sQqG3TG+W2pM9amCe51BQr9ZsEg6 5 | NnjTPv1xPqyZpa3vDcJMBpr85Ydboco= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /checks/mumble/peer_1/key.age: -------------------------------------------------------------------------------- 1 | AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX 2 | -------------------------------------------------------------------------------- /checks/mumble/sops/machines/peer1/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age1987metkajgdefk0sfhjqjjtczy9eu2lsg700rwcac6hhy2alhdsshjmpw8", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/mumble/sops/machines/peer2/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age1fndalxxeduekn5s8q3znl73vjfx2n8kydylyrc2j3aurc93pypvs6pcql4", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/mumble/sops/secrets/peer1-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/mumble/sops/secrets/peer2-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/mumble/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/mumble/vars/per-machine/peer1/mumble/mumble-key/machines/peer1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer1 -------------------------------------------------------------------------------- /checks/mumble/vars/per-machine/peer1/mumble/mumble-key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/mumble/vars/per-machine/peer2/mumble/mumble-key/machines/peer2: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer2 -------------------------------------------------------------------------------- /checks/mumble/vars/per-machine/peer2/mumble/mumble-key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/secrets/.clan-flake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/checks/secrets/.clan-flake -------------------------------------------------------------------------------- /checks/secrets/clan-secrets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux -o pipefail 4 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 5 | export SOPS_AGE_KEY_FILE="${SCRIPT_DIR}/key.age" 6 | nix run .# -- secrets "$@" 7 | -------------------------------------------------------------------------------- /checks/secrets/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | name = "secrets"; 3 | 4 | nodes.machine = 5 | { self, config, ... }: 6 | { 7 | environment.etc."privkey.age".source = ./key.age; 8 | imports = [ (self.nixosModules.clanCore) ]; 9 | environment.etc."secret".source = config.sops.secrets.secret.path; 10 | environment.etc."group-secret".source = config.sops.secrets.group-secret.path; 11 | sops.age.keyFile = "/etc/privkey.age"; 12 | 13 | clan.core.settings.directory = "${./.}"; 14 | 15 | networking.hostName = "machine"; 16 | }; 17 | testScript = '' 18 | machine.succeed("cat /etc/secret >&2") 19 | machine.succeed("cat /etc/group-secret >&2") 20 | ''; 21 | } 22 | -------------------------------------------------------------------------------- /checks/secrets/key.age: -------------------------------------------------------------------------------- 1 | AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX 2 | -------------------------------------------------------------------------------- /checks/secrets/sops/groups/group/machines/machine: -------------------------------------------------------------------------------- 1 | ../../../machines/machine -------------------------------------------------------------------------------- /checks/secrets/sops/machines/machine/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age15x8u838dwqflr3t6csf4tlghxm4tx77y379ncqxav7y2n8qp7yzqgrwt00", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/secrets/sops/secrets/group-secret/groups/group: -------------------------------------------------------------------------------- 1 | ../../../groups/group -------------------------------------------------------------------------------- /checks/secrets/sops/secrets/secret/machines/machine: -------------------------------------------------------------------------------- 1 | ../../../machines/machine -------------------------------------------------------------------------------- /checks/secrets/sops/secrets/secret/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/secrets/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age15x8u838dwqflr3t6csf4tlghxm4tx77y379ncqxav7y2n8qp7yzqgrwt00", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/syncthing/sops/machines/introducer/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age1wjp0vvvy4d2c0pdrth0kl505rzpz37804swf6rrny9xa208mrg2s0r5m67", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/syncthing/sops/machines/peer1/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age14faw2l6rskw2gcv3rrkygmwmrp2ev9yclzq4fh8xf8sjeke8p97sw4dxuq", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/syncthing/sops/machines/peer2/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age1dutdww4x48f0e3tzmjlye9n852wx0qqhhcghsrefsq9m8c5flpfs2lxexf", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /checks/syncthing/sops/secrets/introducer-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/syncthing/sops/secrets/peer1-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/syncthing/sops/secrets/peer2-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /checks/syncthing/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/apikey/machines/introducer: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/introducer -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/apikey/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/cert/machines/introducer: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/introducer -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/cert/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/id/value: -------------------------------------------------------------------------------- 1 | Y45RCSC-Y2OENC7-OI2NQ6Y-VUU6W7X-TQDROMD-JZNYC3B-BZJA2TI-7IMW4Q4 2 | -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/key/machines/introducer: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/introducer -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/introducer/syncthing/key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/apikey/machines/peer1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer1 -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/apikey/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/cert/machines/peer1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer1 -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/cert/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/id/value: -------------------------------------------------------------------------------- 1 | BZMMAO2-WNUTFFN-GMRL6TM-QZX57TS-Q3NXHVU-V3VPNGX-QHQMBIX-LWQUQQA 2 | -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/key/machines/peer1: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer1 -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer1/syncthing/key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/apikey/machines/peer2: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer2 -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/apikey/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/cert/machines/peer2: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer2 -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/cert/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/id/value: -------------------------------------------------------------------------------- 1 | TB7HCPA-CIEXTMA-KXMBEL3-V2UIPND-C2VBRVP-BJTYZP3-G62QKYA-HFF2CQ4 2 | -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/key/machines/peer2: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/peer2 -------------------------------------------------------------------------------- /checks/syncthing/vars/per-machine/peer2/syncthing/key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/users/admin -------------------------------------------------------------------------------- /checks/zt-tcp-relay/default.nix: -------------------------------------------------------------------------------- 1 | ( 2 | { pkgs, ... }: 3 | { 4 | name = "zt-tcp-relay"; 5 | 6 | nodes.machine = 7 | { self, ... }: 8 | { 9 | imports = [ 10 | self.nixosModules.clanCore 11 | self.clanModules.zt-tcp-relay 12 | { 13 | clan.core.settings.directory = ./.; 14 | } 15 | ]; 16 | }; 17 | testScript = '' 18 | start_all() 19 | machine.wait_for_unit("zt-tcp-relay.service") 20 | out = machine.succeed("${pkgs.netcat}/bin/nc -z -v localhost 4443") 21 | print(out) 22 | ''; 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /clanModules/admin/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Convenient Administration for the Clan App" 3 | categories = ["Utility"] 4 | features = [ "inventory", "deprecated" ] 5 | --- 6 | -------------------------------------------------------------------------------- /clanModules/admin/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ ./roles/default.nix ]; 3 | } 4 | -------------------------------------------------------------------------------- /clanModules/admin/roles/default.nix: -------------------------------------------------------------------------------- 1 | { lib, config, ... }: 2 | { 3 | 4 | options.clan.admin = { 5 | allowedKeys = lib.mkOption { 6 | default = { }; 7 | type = lib.types.attrsOf lib.types.str; 8 | description = "The allowed public keys for ssh access to the admin user"; 9 | example = { 10 | "key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD..."; 11 | }; 12 | }; 13 | }; 14 | # Bad practice. 15 | # Should we add 'clanModules' to specialArgs? 16 | imports = [ 17 | ../../sshd 18 | ../../root-password 19 | ]; 20 | config = { 21 | users.users.root.openssh.authorizedKeys.keys = builtins.attrValues config.clan.admin.allowedKeys; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /clanModules/auto-upgrade/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Set up automatic upgrades" 3 | categories = ["System"] 4 | features = [ "inventory" ] 5 | --- 6 | 7 | Whether to periodically upgrade NixOS to the latest version. If enabled, a 8 | systemd timer will run `nixos-rebuild switch --upgrade` once a day. 9 | -------------------------------------------------------------------------------- /clanModules/auto-upgrade/roles/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | let 7 | cfg = config.clan.auto-upgrade; 8 | in 9 | { 10 | options.clan.auto-upgrade = { 11 | flake = lib.mkOption { 12 | type = lib.types.str; 13 | description = "Flake reference"; 14 | }; 15 | }; 16 | config = { 17 | system.autoUpgrade = { 18 | inherit (cfg) flake; 19 | enable = true; 20 | dates = "02:00"; 21 | randomizedDelaySec = "45min"; 22 | }; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /clanModules/borgbackup-static/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Statically configure borgbackup with sane defaults." 3 | --- 4 | !!! Danger "Deprecated" 5 | Use [borgbackup](borgbackup.md) instead. 6 | 7 | Don't use borgbackup-static through [inventory](../../guides/inventory.md). 8 | 9 | This module implements the `borgbackup` backend and implements sane defaults 10 | for backup management through `borgbackup` for members of the clan. 11 | 12 | Configure target machines where the backups should be sent to through `targets`. 13 | 14 | Configure machines that should be backuped either through `includeMachines` 15 | which will exclusively add the included machines to be backuped, or through 16 | `excludeMachines`, which will add every machine except the excluded machine to the backup. 17 | -------------------------------------------------------------------------------- /clanModules/borgbackup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Efficient, deduplicating backup program with optional compression and secure encryption." 3 | categories = ["System"] 4 | features = [ "inventory", "deprecated" ] 5 | --- 6 | BorgBackup (short: Borg) gives you: 7 | 8 | - Space efficient storage of backups. 9 | - Secure, authenticated encryption. 10 | - Compression: lz4, zstd, zlib, lzma or none. 11 | - Mountable backups with FUSE. 12 | - Easy installation on multiple platforms: Linux, macOS, BSD, … 13 | - Free software (BSD license). 14 | - Backed by a large and active open-source community. 15 | -------------------------------------------------------------------------------- /clanModules/borgbackup/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/client.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/data-mesher/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Set up data-mesher" 3 | categories = ["System"] 4 | features = [ "inventory" ] 5 | 6 | [constraints] 7 | roles.admin.min = 1 8 | roles.admin.max = 1 9 | --- 10 | 11 | -------------------------------------------------------------------------------- /clanModules/data-mesher/lib.nix: -------------------------------------------------------------------------------- 1 | lib: { 2 | 3 | machines = 4 | config: 5 | let 6 | instanceNames = builtins.attrNames config.clan.inventory.services.data-mesher; 7 | instanceName = builtins.head instanceNames; 8 | dataMesherInstances = config.clan.inventory.services.data-mesher.${instanceName}; 9 | 10 | uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list); 11 | in 12 | rec { 13 | admins = dataMesherInstances.roles.admin.machines or [ ]; 14 | signers = dataMesherInstances.roles.signer.machines or [ ]; 15 | peers = dataMesherInstances.roles.peer.machines or [ ]; 16 | bootstrap = uniqueStrings (admins ++ signers); 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /clanModules/data-mesher/roles/peer.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ../shared.nix 4 | ]; 5 | } 6 | -------------------------------------------------------------------------------- /clanModules/data-mesher/roles/signer.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ../shared.nix 4 | ]; 5 | } 6 | -------------------------------------------------------------------------------- /clanModules/deltachat/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Email-based instant messaging for Desktop." 3 | --- 4 | 5 | !!! info 6 | This module will automatically configure an email server on the machine for handling the e-mail messaging seamlessly. 7 | 8 | ## Features 9 | 10 | - [x] **Email-based**: Uses any email account as its backend. 11 | - [x] **End-to-End Encryption**: Supports Autocrypt to automatically encrypt messages. 12 | - [x] **No Phone Number Required**: Uses your email address instead of a phone number. 13 | - [x] **Cross-Platform**: Available on desktop and mobile platforms. 14 | - [x] **Automatic Server Setup**: Includes your own DeltaChat server for enhanced control and privacy. 15 | - [ ] **Bake a cake**: This module cannot cake a bake. 16 | -------------------------------------------------------------------------------- /clanModules/disk-id/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Generates a uuid for use in disk device naming" 3 | features = [ "inventory" ] 4 | categories = [ "System" ] 5 | --- 6 | -------------------------------------------------------------------------------- /clanModules/disk-id/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/disk-id/roles/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | ... 5 | }: 6 | 7 | { 8 | 9 | config = { 10 | clan.core.vars.generators.disk-id = { 11 | files.diskId.secret = false; 12 | runtimeInputs = [ 13 | pkgs.coreutils 14 | pkgs.bash 15 | ]; 16 | script = '' 17 | uuid=$(bash ${./uuid4.sh}) 18 | 19 | # Remove the hyphens from the UUID 20 | uuid_no_hyphens=$(echo -n "$uuid" | tr -d '-') 21 | 22 | echo -n "$uuid_no_hyphens" > "$out/diskId" 23 | ''; 24 | }; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /clanModules/disk-id/roles/uuid4.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Read 16 bytes from /dev/urandom 4 | uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n') 5 | 6 | # Break the UUID into pieces and apply the required modifications 7 | byte6=${uuid:12:2} 8 | byte8=${uuid:16:2} 9 | 10 | # Construct the correct version and variant 11 | hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40))) 12 | hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80))) 13 | 14 | # Rebuild the UUID with the correct fields 15 | uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}" 16 | 17 | # Format the UUID correctly 8-4-4-4-12 18 | uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}" 19 | 20 | echo -n "$uuid_formatted" -------------------------------------------------------------------------------- /clanModules/dyndns/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A dynamic DNS service to update domain IPs" 3 | --- 4 | 5 | To understand the possible options that can be set visit the documentation of [ddns-updater](https://github.com/qdm12/ddns-updater?tab=readme-ov-file#versioned-documentation) 6 | 7 | -------------------------------------------------------------------------------- /clanModules/ergochat/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A modern IRC server" 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/ergochat/default.nix: -------------------------------------------------------------------------------- 1 | _: { 2 | services.ergochat = { 3 | enable = true; 4 | 5 | settings = { 6 | datastore = { 7 | autoupgrade = true; 8 | path = "/var/lib/ergo/ircd.db"; 9 | }; 10 | }; 11 | }; 12 | 13 | clan.core.state.ergochat.folders = [ "/var/lib/ergo" ]; 14 | } 15 | -------------------------------------------------------------------------------- /clanModules/garage/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "S3-compatible object store for small self-hosted geo-distributed deployments" 3 | --- 4 | 5 | This module generates garage specific keys automatically. 6 | Also shares the `rpc_secret` between instances. 7 | 8 | Options: [NixosModuleOptions](https://search.nixos.org/options?channel=unstable&size=50&sort=relevance&type=packages&query=garage) 9 | Documentation: https://garagehq.deuxfleurs.fr/ 10 | -------------------------------------------------------------------------------- /clanModules/golem-provider/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Golem Provider for the Golem Network, an open-source and decentralized platform where everyone can use and share each other's computing power without relying on centralized entities like cloud computing corporations" 3 | --- 4 | 5 | By running a golem provider your machine's compute resources are offered via the golem network which will allow other members to execute compute tasks on your machine. If this happens, you will be compensated with GLM, an ERC20 token. 6 | 7 | More about golem providers: https://docs.golem.network/docs/golem/overview 8 | -------------------------------------------------------------------------------- /clanModules/golem-provider/interface.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) mkOption; 4 | 5 | inherit (lib.types) nullOr str; 6 | 7 | in 8 | { 9 | options.clan.golem-provider = { 10 | account = mkOption { 11 | type = nullOr str; 12 | description = '' 13 | Ethereum address for payouts. 14 | 15 | Leave empty to automatically generate a new address upon first start. 16 | ''; 17 | default = null; 18 | }; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /clanModules/golem-provider/test/vm.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | imports = [ ../. ]; 4 | } 5 | -------------------------------------------------------------------------------- /clanModules/heisenbridge/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A matrix bridge to communicate with IRC" 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /clanModules/heisenbridge/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | { 7 | imports = [ 8 | (lib.mkRemovedOptionModule [ 9 | "clan" 10 | "heisenbridge" 11 | "enable" 12 | ] "Importing the module will already enable the service.") 13 | ]; 14 | config = { 15 | services.heisenbridge = { 16 | enable = true; 17 | homeserver = "http://localhost:8008"; # TODO: Sync with matrix-synapse 18 | }; 19 | services.matrix-synapse.settings.app_service_config_files = [ 20 | "/var/lib/heisenbridge/registration.yml" 21 | ]; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /clanModules/importer/roles/default.nix: -------------------------------------------------------------------------------- 1 | { } 2 | -------------------------------------------------------------------------------- /clanModules/iwd/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Automatically provisions wifi credentials" 3 | features = [ "inventory", "deprecated" ] 4 | categories = [ "Network" ] 5 | --- 6 | 7 | !!! Warning 8 | If you've been using network manager + wpa_supplicant and now are switching to IWD read this migration guide: 9 | https://archive.kernel.org/oldwiki/iwd.wiki.kernel.org/networkmanager.html#converting_network_profiles 10 | -------------------------------------------------------------------------------- /clanModules/iwd/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/localbackup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Automatically backups current machine to local directory." 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/localsend/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Securely sharing files and messages over a local network without internet connectivity." 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/localsend/localsend-ensure-config/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | writers, 4 | writeShellScriptBin, 5 | localsend, 6 | alias ? null, 7 | }: 8 | let 9 | localsend-ensure-config = writers.writePython3 "localsend-ensure-config" { 10 | flakeIgnore = [ 11 | # We don't live in the dark ages anymore. 12 | # Languages like Python that are whitespace heavy will overrun 13 | # 79 characters.. 14 | "E501" 15 | ]; 16 | } (builtins.readFile ./localsend-ensure-config.py); 17 | in 18 | writeShellScriptBin "localsend" '' 19 | set -xeu 20 | ${localsend-ensure-config} ${lib.optionalString (alias != null) alias} 21 | ${lib.getExe localsend} 22 | '' 23 | -------------------------------------------------------------------------------- /clanModules/machine-id/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Sets the /etc/machine-id and exposes it as a nix option" 3 | features = [ "inventory" ] 4 | --- 5 | -------------------------------------------------------------------------------- /clanModules/machine-id/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/machine-id/roles/uuid4.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Read 16 bytes from /dev/urandom 4 | uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n') 5 | 6 | # Break the UUID into pieces and apply the required modifications 7 | byte6=${uuid:12:2} 8 | byte8=${uuid:16:2} 9 | 10 | # Construct the correct version and variant 11 | hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40))) 12 | hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80))) 13 | 14 | # Rebuild the UUID with the correct fields 15 | uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}" 16 | 17 | # Format the UUID correctly 8-4-4-4-12 18 | uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}" 19 | 20 | echo -n "$uuid_formatted" -------------------------------------------------------------------------------- /clanModules/matrix-synapse/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A federated messaging server with end-to-end encryption." 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/moonlight/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A desktop streaming client optimized for remote gaming and synchronized movie viewing." 3 | --- 4 | 5 | **Warning**: This module was written with our VM integration in mind likely won't work outside of this context. They will be generalized in future. 6 | -------------------------------------------------------------------------------- /clanModules/mumble/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Open Source, Low Latency, High Quality Voice Chat." 3 | categories = ["Audio", "Social"] 4 | features = [ "inventory" ] 5 | 6 | [constraints] 7 | roles.server.min = 1 8 | --- 9 | 10 | The mumble clan module gives you: 11 | 12 | - True low latency voice communication. 13 | - Secure, authenticated encryption. 14 | - Free software. 15 | - Backed by a large and active open-source community. 16 | 17 | This all set up in a way that allows peer-to-peer hosting. 18 | Every machine inside the clan can be a host for mumble, 19 | and thus it doesn't matter who in the network is online - as long as two people are online they are able to chat with each other. 20 | -------------------------------------------------------------------------------- /clanModules/mumble/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/server.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/nginx/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Good defaults for the nginx webserver" 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/packages/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Define package sets from nixpkgs and install them on one or more machines" 3 | categories = ["System"] 4 | features = [ "inventory" ] 5 | --- 6 | -------------------------------------------------------------------------------- /clanModules/packages/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/packages/roles/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | { 8 | options.clan.packages = { 9 | packages = lib.mkOption { 10 | type = lib.types.listOf lib.types.str; 11 | description = "The packages to install on the machine"; 12 | }; 13 | }; 14 | config = { 15 | environment.systemPackages = map ( 16 | pName: lib.getAttrFromPath (lib.splitString "." pName) pkgs 17 | ) config.clan.packages.packages; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /clanModules/postgresql/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A free and open-source relational database management system (RDBMS) emphasizing extensibility and SQL compliance." 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/root-password/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Automatically generates and configures a password for the root user." 3 | categories = ["System"] 4 | features = [ "inventory" ] 5 | --- 6 | 7 | After the system was installed/deployed the following command can be used to display the root-password: 8 | 9 | ```bash 10 | clan vars get [machine_name] root-password/root-password 11 | ``` 12 | 13 | See also: [Vars](../../guides/vars-backend.md) 14 | 15 | To regenerate the password run: 16 | ``` 17 | clan vars generate --regenerate [machine_name] --generator root-password 18 | ``` 19 | -------------------------------------------------------------------------------- /clanModules/root-password/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/single-disk/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ ./roles/default.nix ]; 3 | } 4 | -------------------------------------------------------------------------------- /clanModules/sshd/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Enables secure remote access to the machine over ssh." 3 | categories = ["System", "Network"] 4 | features = [ "inventory" ] 5 | --- 6 | 7 | This module will setup the opensshd service. 8 | It will generate a host key for each machine 9 | 10 | 11 | ## Roles 12 | -------------------------------------------------------------------------------- /clanModules/sshd/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/server.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/sshd/roles/client.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | imports = [ 4 | ../shared.nix 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/state-version/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/state-version/roles/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | var = config.clan.core.vars.generators.state-version.files.version or { }; 4 | in 5 | { 6 | system.stateVersion = lib.mkDefault (lib.removeSuffix "\n" var.value); 7 | 8 | clan.core.vars.generators.state-version = { 9 | files.version = { 10 | secret = false; 11 | value = lib.mkDefault config.system.nixos.release; 12 | }; 13 | runtimeInputs = [ ]; 14 | script = '' 15 | echo -n ${config.system.stateVersion} > "$out"/version 16 | ''; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /clanModules/static-hosts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Statically configure the host names of machines based on their respective zerotier-ip." 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/sunshine/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A desktop streaming server optimized for remote gaming and synchronized movie viewing." 3 | --- 4 | 5 | **Warning**: This module was written with our VM integration in mind likely won't work outside of this context. They will be generalized in future. 6 | -------------------------------------------------------------------------------- /clanModules/syncthing-static-peers/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Statically configure syncthing peers through clan" 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/syncthing/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/peer.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/syncthing/roles/introducer.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | imports = [ 4 | ../shared.nix 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/syncthing/roles/peer.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | instanceNames = builtins.attrNames config.clan.inventory.services.syncthing; 4 | instanceName = builtins.head instanceNames; 5 | instance = config.clan.inventory.services.syncthing.${instanceName}; 6 | introducer = builtins.head instance.roles.introducer.machines; 7 | 8 | introducerId = "${config.clan.core.settings.directory}/vars/per-machine/${introducer}/syncthing/id/value"; 9 | in 10 | { 11 | imports = [ 12 | ../shared.nix 13 | ]; 14 | clan.syncthing.introducer = lib.strings.removeSuffix "\n" ( 15 | if builtins.pathExists introducerId then 16 | builtins.readFile introducerId 17 | else 18 | throw "${introducerId} does not exists. Please run `clan vars generate ${introducer}` to generate the introducer device id" 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /clanModules/thelounge/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Modern web IRC client" 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/thelounge/default.nix: -------------------------------------------------------------------------------- 1 | _: { 2 | services.thelounge = { 3 | enable = true; 4 | public = true; 5 | extraConfig = { 6 | prefetch = true; 7 | defaults = { 8 | port = 6667; 9 | tls = false; 10 | }; 11 | }; 12 | }; 13 | 14 | clan.core.state.thelounde.folders = [ "/var/lib/thelounge" ]; 15 | } 16 | -------------------------------------------------------------------------------- /clanModules/trusted-nix-caches/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "This module sets the `clan.lol` and `nix-community` cache up as a trusted cache." 3 | ---- 4 | -------------------------------------------------------------------------------- /clanModules/trusted-nix-caches/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | nix.settings.trusted-substituters = [ 3 | "https://cache.clan.lol" 4 | "https://nix-community.cachix.org" 5 | ]; 6 | nix.settings.trusted-public-keys = [ 7 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 8 | "cache.clan.lol-1:3KztgSAB5R1M+Dz7vzkBGzXdodizbgLXGXKXlcQLA28=" 9 | ]; 10 | } 11 | -------------------------------------------------------------------------------- /clanModules/user-password/default.nix: -------------------------------------------------------------------------------- 1 | # Dont import this file 2 | # It is only here for backwards compatibility. 3 | # Dont author new modules with this file. 4 | { 5 | imports = [ ./roles/default.nix ]; 6 | } 7 | -------------------------------------------------------------------------------- /clanModules/vaultwarden/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "The server for the centralized password manager bitwarden" 3 | --- 4 | 5 | After enabling the clan module, user accounts have to be created manually in the webinterface. 6 | This is done by visiting `vaultwarden.example.com/admin` and typing in the admin password. 7 | You can get the admin password for vaultwarden by executing: 8 | ```bash 9 | clan vars get vaultwarden-admin/vaultwarden-admin 10 | ``` 11 | To see all secrets tied to vaultwarden execute: 12 | ```bash 13 | clan vars get vaultwarden-admin/vaultwarden-admin 14 | clan vars get vaultwarden-smtp/vaultwarden-smtp 15 | ``` 16 | -------------------------------------------------------------------------------- /clanModules/xfce/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "A lightweight desktop manager" 3 | --- 4 | -------------------------------------------------------------------------------- /clanModules/xfce/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | services.xserver = { 3 | enable = true; 4 | desktopManager.xfce.enable = true; 5 | layout = "us"; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /clanModules/zerotier-static-peers/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Statically configure the `zerotier` peers of a clan network." 3 | --- 4 | Statically configure the `zerotier` peers of a clan network. 5 | 6 | Requires a machine, that is the zerotier controller configured in the network. 7 | -------------------------------------------------------------------------------- /clanModules/zerotier/roles/moon.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | { 3 | imports = [ 4 | ../shared.nix 5 | ]; 6 | options.clan.zerotier.moon.stableEndpoints = lib.mkOption { 7 | type = lib.types.listOf lib.types.str; 8 | description = '' 9 | Make this machine a moon. 10 | Other machines can join this moon by adding this moon in their config. 11 | It will be reachable under the given stable endpoints. 12 | ''; 13 | example = '' 14 | [ 1.2.3.4" "10.0.0.3/9993" "2001:abcd:abcd::3/9993" ] 15 | ''; 16 | }; 17 | # TODO, we want to remove these options from clanCore 18 | config.clan.core.networking.zerotier.moon.stableEndpoints = 19 | config.clan.zerotier.moon.stableEndpoints; 20 | } 21 | -------------------------------------------------------------------------------- /clanModules/zerotier/roles/peer.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ../shared.nix 4 | ]; 5 | } 6 | -------------------------------------------------------------------------------- /clanModules/zt-tcp-relay/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Enable ZeroTier VPN over TCP for networks where UDP is blocked." 3 | --- 4 | -------------------------------------------------------------------------------- /clanServices/admin/flake-module.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | clan.modules = { 4 | admin = lib.modules.importApply ./default.nix { }; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /clanServices/borgbackup/README.md: -------------------------------------------------------------------------------- 1 | BorgBackup (short: Borg) gives you: 2 | 3 | - Space efficient storage of backups. 4 | - Secure, authenticated encryption. 5 | - Compression: lz4, zstd, zlib, lzma or none. 6 | - Mountable backups with FUSE. 7 | - Easy installation on multiple platforms: Linux, macOS, BSD, … 8 | - Free software (BSD license). 9 | - Backed by a large and active open-source community. 10 | -------------------------------------------------------------------------------- /clanServices/borgbackup/flake-module.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | clan.modules = { 4 | borgbackup = lib.modules.importApply ./default.nix { }; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /clanServices/flake-module.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | imports = [ 4 | ./admin/flake-module.nix 5 | ./hello-world/flake-module.nix 6 | ./wifi/flake-module.nix 7 | ./borgbackup/flake-module.nix 8 | ]; 9 | } 10 | -------------------------------------------------------------------------------- /clanServices/hello-world/default.nix: -------------------------------------------------------------------------------- 1 | { packages }: 2 | { ... }: 3 | { 4 | _class = "clan.service"; 5 | manifest.name = "clan-core/hello-word"; 6 | manifest.description = "This is a test"; 7 | 8 | roles.peer = { 9 | interface = 10 | { lib, ... }: 11 | { 12 | options.foo = lib.mkOption { 13 | type = lib.types.str; 14 | # default = ""; 15 | description = "Some option"; 16 | }; 17 | }; 18 | }; 19 | 20 | perMachine = 21 | { machine, ... }: 22 | { 23 | nixosModule = { 24 | clan.core.vars.generators.hello = { 25 | files.hello = { 26 | secret = false; 27 | }; 28 | script = '' 29 | echo "Hello world from ${machine.name}" > $out/hello 30 | ''; 31 | }; 32 | }; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /clanServices/hello-world/tests/vm/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /clanServices/hello-world/tests/vm/vars/per-machine/peer1/hello/hello/value: -------------------------------------------------------------------------------- 1 | Hello world from peer1 2 | -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/sops/machines/test/key.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "publickey": "age13ahclyps97532zt2sfta5zrfx976d3r2jmctj8d36vj9x5v5ffqq304fqf", 4 | "type": "age" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/sops/secrets/test-age.key/users/admin: -------------------------------------------------------------------------------- 1 | ../../../users/admin -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/sops/users/admin/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/vars/shared/wifi.one/network-name/machines/test: -------------------------------------------------------------------------------- 1 | ../../../../../sops/machines/test -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/vars/shared/wifi.one/network-name/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../sops/users/admin -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/vars/shared/wifi.one/password/machines/test: -------------------------------------------------------------------------------- 1 | ../../../../../sops/machines/test -------------------------------------------------------------------------------- /clanServices/wifi/tests/vm/vars/shared/wifi.one/password/users/admin: -------------------------------------------------------------------------------- 1 | ../../../../../sops/users/admin -------------------------------------------------------------------------------- /docs/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | source_up 3 | 4 | mapfile -d '' -t nix_files < <(find ./nix -name "*.nix" -print0) 5 | watch_file "${nix_files[@]}" 6 | 7 | # Because we depend on nixpkgs sources, uploading to builders takes a long time 8 | use flake .#docs --builders '' 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /site/reference 2 | /site/static 3 | !/site/static/extra.css 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Serve documentation locally 2 | 3 | ``` 4 | $ nix develop .#docs -c mkdocs serve 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/_internal/use-cases/_template.md: -------------------------------------------------------------------------------- 1 | # (TITLE) 2 | 3 | ## General Description 4 | 5 | ## Stories 6 | 7 | ### Story 1: Some Description 8 | 9 | Alice... 10 | 11 | ### Story 2: Some Description 12 | 13 | Bob... 14 | 15 | ## Challenges 16 | 17 | ... 18 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block extrahead %} 2 | 6 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /docs/site/decisions/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records 2 | 3 | This section contains the architecture decisions that have been reviewed and generally agreed upon 4 | 5 | ## What is an ADR? 6 | 7 | > An architecture decision record (ADR) is a document that captures an important architecture decision made along with its context and consequences. 8 | 9 | !!! Note 10 | For further reading about adr's we recommend [architecture-decision-record](https://github.com/joelparkerhenderson/architecture-decision-record) 11 | 12 | ## Crafting a new ADR 13 | 14 | 1. Use the [template](./_template.md) 15 | 2. Create the Pull request and gather feedback 16 | 3. Retreive your adr-number (see: [numbering](./03-adr-numbering-process.md)) 17 | -------------------------------------------------------------------------------- /docs/site/decisions/_template.md: -------------------------------------------------------------------------------- 1 | # Decision record template by Michael Nygard 2 | 3 | This is the template in [Documenting architecture decisions - Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). 4 | You can use [adr-tools](https://github.com/npryce/adr-tools) for managing the ADR files. 5 | 6 | In each ADR file, write these sections: 7 | 8 | # Title 9 | 10 | ## Status 11 | 12 | What is the status, such as proposed, accepted, rejected, deprecated, superseded, etc.? 13 | 14 | ## Context 15 | 16 | What is the issue that we're seeing that is motivating this decision or change? 17 | 18 | ## Decision 19 | 20 | What is the change that we're proposing and/or doing? 21 | 22 | ## Consequences 23 | 24 | What becomes easier or more difficult to do because of this change? 25 | -------------------------------------------------------------------------------- /docs/site/guides/contributing/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ../../../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/site/reference/index.md: -------------------------------------------------------------------------------- 1 | # :material-api: Overview 2 | 3 | This section of the site provides an overview of available options and commands within the Clan Framework. 4 | 5 | --- 6 | 7 | - Learn how to use the [Clan CLI](./cli/index.md) 8 | - Explore available services and application [modules](./clanModules/index.md) 9 | - Discover [configuration options](./clan.core/index.md) that manage essential features 10 | - Find descriptions of the [Nix interfaces](./nix-api/buildclan.md) for defining a Clan 11 | 12 | --- 13 | 14 | !!! Note 15 | This documentation is always built for the main branch. 16 | If you need documentation for a specific commit you can build it on your own 17 | 18 | ```bash 19 | nix build 'git+https://git.clan.lol/clan/clan-core?ref=0324f4d4b87d932163f351e53b23b0b17f2b5e15#docs' 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/site/static/extra.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Roboto"; 3 | src: url(./Roboto-Regular.ttf) format("truetype"); 4 | } 5 | @font-face { 6 | font-family: "Fira Code"; 7 | src: url(./FiraCode-VF.ttf) format("truetype"); 8 | } 9 | 10 | :root { 11 | --md-text-font: "Roboto"; 12 | --md-code-font: "Fira Code"; 13 | } 14 | 15 | .md-header img { 16 | filter: invert(100%) brightness(100%); 17 | } 18 | 19 | .md-nav__title, 20 | .md-nav__item.md-nav__item--section > label > span { 21 | color: var(--md-typeset-a-color); 22 | } 23 | -------------------------------------------------------------------------------- /flakeModules/flake-module.nix: -------------------------------------------------------------------------------- 1 | { self, config, ... }: 2 | { 3 | flake.flakeModules = { 4 | clan = import ./clan.nix self; 5 | default = config.flake.flakeModules.clan; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /lib/build-clan/computed-tags.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | ... 4 | }: 5 | { 6 | # Add the computed tags to machine tags for displaying them 7 | inventory = { 8 | tags = ( 9 | { machines, ... }: 10 | { 11 | # Only compute the default value 12 | # The option MUST be defined in ./build-inventory/interface.nix 13 | all = lib.mkDefault (builtins.attrNames machines); 14 | nixos = lib.mkDefault ( 15 | builtins.attrNames (lib.filterAttrs (_n: m: m.machineClass == "nixos") machines) 16 | ); 17 | darwin = lib.mkDefault ( 18 | builtins.attrNames (lib.filterAttrs (_n: m: m.machineClass == "darwin") machines) 19 | ); 20 | } 21 | ); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /lib/build-clan/eval-docs.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | clanLib, 5 | }: 6 | let 7 | eval = lib.evalModules { 8 | class = "nixos"; 9 | modules = [ 10 | (lib.modules.importApply ./interface.nix { inherit clanLib; }) 11 | ]; 12 | }; 13 | evalDocs = pkgs.nixosOptionsDoc { 14 | options = eval.options; 15 | warningsAreErrors = false; 16 | }; 17 | in 18 | { 19 | inherit (evalDocs) optionsJSON optionsNix; 20 | inherit eval; 21 | } 22 | -------------------------------------------------------------------------------- /lib/build-clan/function-adapter.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | nixpkgs, 4 | nix-darwin ? null, 5 | clan-core, 6 | self, 7 | specialArgs ? { }, 8 | }: 9 | # Returns a function that takes self, which should point to the directory of the flake 10 | module: 11 | (lib.evalModules { 12 | specialArgs = { 13 | inherit 14 | self 15 | clan-core 16 | nixpkgs 17 | nix-darwin 18 | ; 19 | }; 20 | modules = [ 21 | (lib.modules.importApply ./interface.nix { inherit (clan-core) clanLib; }) 22 | module 23 | { 24 | inherit specialArgs; 25 | } 26 | ]; 27 | }).config 28 | -------------------------------------------------------------------------------- /lib/build-clan/machineModules/forName.nix: -------------------------------------------------------------------------------- 1 | { 2 | name, 3 | directory, 4 | meta, 5 | }: 6 | { 7 | _class, 8 | lib, 9 | ... 10 | }: 11 | { 12 | imports = builtins.filter builtins.pathExists ( 13 | [ 14 | "${directory}/machines/${name}/configuration.nix" 15 | ] 16 | ++ lib.optionals (_class == "nixos") [ 17 | "${directory}/machines/${name}/hardware-configuration.nix" 18 | "${directory}/machines/${name}/disko.nix" 19 | ] 20 | ); 21 | 22 | clan.core.settings = { 23 | inherit (meta) name icon; 24 | inherit directory; 25 | machine = { 26 | inherit name; 27 | }; 28 | }; 29 | 30 | # TODO: move into nixosModules 31 | networking.hostName = lib.mkDefault name; 32 | } 33 | -------------------------------------------------------------------------------- /lib/build-clan/machineModules/overridePkgs.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | }: 4 | { 5 | lib, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | ({ 11 | # For vars we need to ensure that the system so we run vars generate on 12 | # is in sync with the pkgs of the system 13 | nixpkgs.hostPlatform = lib.mkForce pkgs.system; 14 | nixpkgs.pkgs = lib.mkForce pkgs; 15 | }) 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /lib/build-clan/public.nix: -------------------------------------------------------------------------------- 1 | /** 2 | Publicly exported attribute names 3 | These are mapped from 'options.clan.{name}' into 'flake.{name}' 4 | For example "clanInternals" will be exposed as "flake.clan.clanInternals" 5 | This list is used to guarantee equivalent attribute sets for both flake-parts and buildClan users. 6 | */ 7 | { 8 | # flake.clan.{name} <- clanInternals.{name} 9 | clan = [ 10 | "templates" 11 | "modules" 12 | ]; 13 | # flake.{name} <- clan.{name} 14 | topLevel = [ 15 | "clanInternals" 16 | "nixosConfigurations" 17 | "darwinConfigurations" 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/build-clan/secrets/interface.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | ... 4 | }: 5 | let 6 | inherit (lib) types; 7 | in 8 | { 9 | options = { 10 | age.plugins = lib.mkOption { 11 | type = types.listOf (types.strMatching "age-plugin-.*"); 12 | default = [ ]; 13 | description = '' 14 | A list of age plugins which must be available in the shell when encrypting and decrypting secrets. 15 | ''; 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /lib/filter-clan-core/flake-module.nix: -------------------------------------------------------------------------------- 1 | { self, ... }: 2 | let 3 | nixFilter = import ./nix-filter.nix; 4 | in 5 | { 6 | flake.filter = 7 | { 8 | name ? "source", 9 | include ? [ ], 10 | exclude ? [ ], 11 | }: 12 | nixFilter.filter { 13 | inherit name exclude; 14 | include = include ++ [ 15 | "flake.nix" 16 | ]; 17 | root = self; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /lib/flake-module.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | inputs, 4 | self, 5 | ... 6 | }: 7 | rec { 8 | # TODO: automatically generate this from the directory conventions 9 | imports = [ 10 | ./build-clan/flake-module.nix 11 | ./clanTest/flake-module.nix 12 | ./introspection/flake-module.nix 13 | ./inventory/flake-module.nix 14 | ./jsonschema/flake-module.nix 15 | ./types/flake-module.nix 16 | ]; 17 | flake.clanLib = import ./default.nix { 18 | inherit lib inputs self; 19 | inherit (inputs) nixpkgs nix-darwin; 20 | }; 21 | # TODO: remove this legacy alias 22 | flake.lib = flake.clanLib; 23 | } 24 | -------------------------------------------------------------------------------- /lib/inventory/build-inventory/inventory-introspection.nix: -------------------------------------------------------------------------------- 1 | { clanLib }: 2 | { 3 | config, 4 | options, 5 | lib, 6 | ... 7 | }: 8 | { 9 | options.introspection = lib.mkOption { 10 | readOnly = true; 11 | # TODO: use options.inventory instead of the evaluate config attribute 12 | default = 13 | builtins.removeAttrs (clanLib.introspection.getPrios { options = config.inventory.options; }) 14 | # tags are freeformType which is not supported yet. 15 | [ "tags" ]; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /lib/inventory/tests/legacyModule/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | features = [ "inventory" ] 3 | --- 4 | Description -------------------------------------------------------------------------------- /lib/inventory/tests/legacyModule/roles/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | clan-core, 4 | ... 5 | }: 6 | { 7 | # Just some random stuff 8 | options.test = lib.mapAttrs clan-core; 9 | } 10 | -------------------------------------------------------------------------------- /lib/jsonschema/example-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John Doe", 3 | "age": 42, 4 | "isAdmin": false, 5 | "kernelModules": ["usbhid", "usb_storage"], 6 | "userIds": { 7 | "mic92": 1, 8 | "lassulus": 2, 9 | "davhau": 3 10 | }, 11 | "services": { 12 | "opt": "this option doesn't make sense" 13 | }, 14 | "destinations": { 15 | "test-backup": { 16 | "name": "John Doe", 17 | "repo": "test-backup" 18 | } 19 | }, 20 | "userModules": { 21 | "some-user": { 22 | "foo": {} 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/jsonschema/gen-options-json.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | expr='let pkgs = import {}; lib = pkgs.lib; in (pkgs.nixosOptionsDoc {options = (lib.evalModules {modules=[./example-interface.nix];}).options;}).optionsJSON.options' 5 | 6 | jq < "$(nix eval --impure --raw --expr "$expr")" > options.json 7 | -------------------------------------------------------------------------------- /lib/jsonschema/test.nix: -------------------------------------------------------------------------------- 1 | # run these tests via `nix-unit ./test.nix` 2 | { 3 | lib ? (import { }).lib, 4 | slib ? (import ./. { inherit lib; } { }), 5 | }: 6 | { 7 | parseOption = import ./test_parseOption.nix { inherit lib slib; }; 8 | parseOptions = import ./test_parseOptions.nix { inherit lib slib; }; 9 | } 10 | -------------------------------------------------------------------------------- /lib/test/container-test-driver/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "nixos-test-driver" 7 | version = "0.0.0" 8 | 9 | [project.scripts] 10 | nixos-test-driver = "test_driver:main" 11 | 12 | [tool.setuptools.packages] 13 | find = {} 14 | 15 | [tool.setuptools.package-data] 16 | test_driver = ["py.typed"] 17 | [tool.mypy] 18 | python_version = "3.12" 19 | warn_redundant_casts = true 20 | disallow_untyped_calls = true 21 | disallow_untyped_defs = true 22 | no_implicit_optional = true 23 | -------------------------------------------------------------------------------- /lib/test/container-test-driver/test-script-prepend.py: -------------------------------------------------------------------------------- 1 | # This file contains type hints that can be prepended to Nix test scripts so they can be type 2 | # checked. 3 | 4 | from collections.abc import Callable 5 | 6 | from test_driver import Machine 7 | 8 | start_all: Callable[[], None] 9 | machines: list[Machine] 10 | -------------------------------------------------------------------------------- /lib/test/container-test-driver/test_driver/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/lib/test/container-test-driver/test_driver/py.typed -------------------------------------------------------------------------------- /lib/test/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | clanLib, 3 | ... 4 | }: 5 | { 6 | containerTest = import ./container-test.nix; 7 | baseTest = import ./test-base.nix; 8 | 9 | flakeModules = clanLib.callLib ./flakeModules.nix { }; 10 | 11 | minifyModule = ./minify.nix; 12 | sopsModule = ./sops.nix; 13 | } 14 | -------------------------------------------------------------------------------- /lib/test/minify.nix: -------------------------------------------------------------------------------- 1 | # This is a module to reduce the size of the NixOS configuration 2 | # Used by the tests 3 | # It unsets some unnecessary options 4 | { lib, ... }: 5 | { 6 | nixpkgs.flake.setFlakeRegistry = false; 7 | nixpkgs.flake.setNixPath = false; 8 | nix.registry = lib.mkForce { }; 9 | documentation.doc.enable = false; 10 | documentation.man.enable = false; 11 | } 12 | -------------------------------------------------------------------------------- /lib/test/sops.nix: -------------------------------------------------------------------------------- 1 | # nixosModule 2 | { config, lib, ... }: 3 | { 4 | # configures a static age-key to skip the age-key generation 5 | sops.age.keyFile = "/run/age-key.txt"; 6 | system.activationScripts = 7 | let 8 | # https://github.com/Mic92/sops-nix/blob/61154300d945f0b147b30d24ddcafa159148026a/modules/sops/default.nix#L27 9 | hasRegularSecrets = lib.filterAttrs (_: v: v.neededForUsers) config.sops.secrets != { }; 10 | in 11 | { 12 | age-key.text = '' 13 | echo AGE-SECRET-KEY-1PL0M9CWRCG3PZ9DXRTTLMCVD57U6JDFE8K7DNVQ35F4JENZ6G3MQ0RQLRV > /run/age-key.txt 14 | ''; 15 | } 16 | // lib.optionalAttrs (hasRegularSecrets) { 17 | setupSecrets.deps = [ "age-key" ]; 18 | setupSecretsForUsers.deps = [ "age-key" ]; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /lib/types/flake-module.nix: -------------------------------------------------------------------------------- 1 | { self, inputs, ... }: 2 | { 3 | perSystem = 4 | { ... }: 5 | let 6 | # Module that contains the tests 7 | # This module adds: 8 | # - legacyPackages..eval-tests-hello-world 9 | # - checks..eval-tests-hello-world 10 | test-types-module = ( 11 | self.clanLib.test.flakeModules.makeEvalChecks { 12 | module = throw ""; 13 | inherit self inputs; 14 | testName = "types"; 15 | tests = ./tests.nix; 16 | # Optional arguments passed to the test 17 | testArgs = { }; 18 | } 19 | ); 20 | in 21 | { 22 | imports = [ test-types-module ]; 23 | legacyPackages.xxx = { }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /machines/test-backup/facts/borgbackup.ssh.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEOdlvNTJPxTpMjuuNytGEUAO8NUuvL2nm9dpWZULCR6 nixbld@turingmachine 2 | -------------------------------------------------------------------------------- /machines/test-inventory-machine/facter.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "system": "x86_64-linux" 4 | } 5 | -------------------------------------------------------------------------------- /nixosModules/bcachefs.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | ... 5 | }: 6 | { 7 | # If we also need zfs, we can use the unstable version as we otherwise don't have a new enough kernel version 8 | boot.zfs.package = pkgs.zfsUnstable; 9 | 10 | # Enable bcachefs support 11 | boot.supportedFilesystems.bcachefs = lib.mkDefault true; 12 | } 13 | -------------------------------------------------------------------------------- /nixosModules/clanCore/default.nix: -------------------------------------------------------------------------------- 1 | { _class, lib, ... }: 2 | { 3 | imports = 4 | [ 5 | ./backups.nix 6 | ./defaults.nix 7 | ./facts 8 | ./inventory 9 | ./meta/interface.nix 10 | ./metadata.nix 11 | ./networking.nix 12 | ./nix-settings.nix 13 | ./options.nix 14 | ./outputs.nix 15 | ./sops.nix 16 | ./vars 17 | ] 18 | ++ lib.optionals (_class == "nixos") [ 19 | ./nixos-facter.nix 20 | ./vm.nix 21 | ./wayland-proxy-virtwl.nix 22 | ./zerotier 23 | ./zfs.nix 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /nixosModules/clanCore/facts/public/in_repo.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | { 3 | config = lib.mkIf (config.clan.core.facts.publicStore == "in_repo") { 4 | clan.core.facts.publicModule = "clan_cli.facts.public_modules.in_repo"; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /nixosModules/clanCore/facts/public/vm.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | { 3 | config = lib.mkIf (config.clan.core.facts.publicStore == "vm") { 4 | clan.core.facts.publicModule = "clan_cli.facts.public_modules.vm"; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /nixosModules/clanCore/facts/secret/password-store.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | { 3 | options.clan.password-store.targetDirectory = lib.mkOption { 4 | type = lib.types.path; 5 | default = "/etc/secrets"; 6 | description = '' 7 | The directory where the password store is uploaded to. 8 | ''; 9 | }; 10 | 11 | config = lib.mkIf (config.clan.core.facts.secretStore == "password-store") { 12 | clan.core.facts.secretPathFunction = 13 | secret: "${config.clan.password-store.targetDirectory}/${secret.config.name}"; 14 | clan.core.facts.secretUploadDirectory = config.clan.password-store.targetDirectory; 15 | clan.core.facts.secretModule = "clan_cli.facts.secret_modules.password_store"; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /nixosModules/clanCore/facts/secret/vm.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | { 3 | config = lib.mkIf (config.clan.core.facts.secretStore == "vm") { 4 | clan.core.facts.secretPathFunction = secret: "/etc/secrets/${secret.config.name}"; 5 | clan.core.facts.secretUploadDirectory = "/etc/secrets"; 6 | clan.core.facts.secretModule = "clan_cli.facts.secret_modules.vm"; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /nixosModules/clanCore/inventory/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ./interface.nix 4 | ./implementation.nix 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /nixosModules/clanCore/inventory/implementation.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | config.assertions = builtins.attrValues ( 4 | builtins.mapAttrs (_id: value: value // { inherit _id; }) config.clan.inventory.assertions 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /nixosModules/clanCore/meta/interface.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | optStr = lib.types.nullOr lib.types.str; 4 | in 5 | { 6 | options.clan.meta.name = lib.mkOption { 7 | description = "The name of the clan"; 8 | type = lib.types.str; 9 | }; 10 | options.clan.meta.description = lib.mkOption { 11 | description = "The description of the clan"; 12 | type = optStr; 13 | }; 14 | options.clan.meta.icon = lib.mkOption { 15 | description = "The location of the clan icon"; 16 | type = optStr; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /nixosModules/clanCore/options.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | ... 4 | }: 5 | { 6 | imports = [ 7 | (lib.mkRenamedOptionModule 8 | [ "clanCore" ] 9 | [ 10 | "clan" 11 | "core" 12 | ] 13 | ) 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /nixosModules/clanCore/sops.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | options = { 4 | clan.core.sops.defaultGroups = lib.mkOption { 5 | type = lib.types.listOf lib.types.str; 6 | default = [ ]; 7 | example = [ "admins" ]; 8 | description = "The default groups to for encryption use when no groups are specified."; 9 | }; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /nixosModules/clanCore/vars/secret/fs.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | { 7 | config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "fs") { 8 | fileModule = file: { 9 | path = 10 | if file.config.neededFor == "partitioning" then 11 | throw "${file.config.generatorName}/${file.config.name}: FS backend does not support partitioning." 12 | else 13 | "/run/secrets/${file.config.generatorName}/${file.config.name}"; 14 | }; 15 | secretModule = "clan_cli.vars.secret_modules.fs"; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /nixosModules/clanCore/vars/secret/on-machine.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | sortedGenerators = lib.toposort (a: b: builtins.elem a.name b.dependencies) ( 9 | lib.attrValues config.clan.core.vars.generators 10 | ); 11 | generateSecrets = '' 12 | ${lib.concatStringsSep "\n" (_gen: '' 13 | v 14 | '') sortedGenerators} 15 | ''; 16 | in 17 | { 18 | config = lib.mkIf (config.clan.core.vars.settings.secretStore == "on-machine") { 19 | environment.systemPackages = [ 20 | (pkgs.writeShellApplication { 21 | text = generateSecrets; 22 | }) 23 | ]; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /nixosModules/clanCore/vars/secret/sops/eval-tests/populated/vars/my_machine/my_generator/my_secret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/nixosModules/clanCore/vars/secret/sops/eval-tests/populated/vars/my_machine/my_generator/my_secret -------------------------------------------------------------------------------- /nixosModules/clanCore/vars/secret/vm.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | { 7 | config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "vm") { 8 | fileModule = file: { 9 | path = lib.mkIf (file.config.secret == true) ( 10 | if file.config.neededFor == "partitioning" then 11 | "/run/partitioning-secrets/${file.config.generatorName}/${file.config.name}" 12 | else 13 | "/etc/secrets/${file.config.generatorName}/${file.config.name}" 14 | ); 15 | }; 16 | secretModule = "clan_cli.vars.secret_modules.vm"; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /nixosModules/clanCore/zfs.nix: -------------------------------------------------------------------------------- 1 | { lib, config, ... }: 2 | { 3 | _class = "nixos"; 4 | 5 | # Use the same default hostID as the NixOS install ISO and nixos-anywhere. 6 | # This allows us to import zfs pool without using a force import. 7 | # ZFS has this as a safety mechanism for networked block storage (ISCSI), but 8 | # in practice we found it causes more breakages like unbootable machines, 9 | # while people using ZFS on ISCSI is quite rare. 10 | networking.hostId = lib.mkDefault "8425e349"; 11 | 12 | services.zfs = lib.mkIf (config.boot.zfs.enabled) { 13 | autoSnapshot.enable = true; 14 | # defaults to 12, which is a bit much given how much data is written 15 | autoSnapshot.monthly = lib.mkDefault 1; 16 | autoScrub.enable = true; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/clan-app/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | source_up 3 | 4 | watch_file flake-module.nix shell.nix webview-ui/flake-module.nix 5 | 6 | # Because we depend on nixpkgs sources, uploading to builders takes a long time 7 | use flake .#clan-app --builders '' 8 | -------------------------------------------------------------------------------- /pkgs/clan-app/bin/clan-app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from pathlib import Path 4 | 5 | module_path = Path(__file__).parent.parent.absolute() 6 | 7 | 8 | sys.path.insert(0, str(module_path)) 9 | sys.path.insert(0, str(module_path.parent / "clan_cli")) 10 | 11 | from clan_app import main # NOQA 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /pkgs/clan-app/bin/reload-python-api.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux -o pipefail 4 | 5 | script_dir=$(dirname "$(readlink -f "$0")") 6 | cd "$script_dir/.." 7 | 8 | trap 'rm -rf "$tmpdir"' EXIT 9 | tmpdir=$(mktemp -d) 10 | 11 | python "../clan-cli/api.py" > "$tmpdir/API.json" 12 | json2ts --input "$tmpdir/API.json" > "$tmpdir/API.ts" 13 | 14 | # compare sha256 sums of old and new API.ts 15 | old_api_hash=$(sha256sum "./ui/api/API.ts" | cut -d ' ' -f 1) 16 | new_api_hash=$(sha256sum "$tmpdir/API.ts" | cut -d ' ' -f 1) 17 | if [ "$old_api_hash" != "$new_api_hash" ]; then 18 | cp "$tmpdir/API.json" "./ui/api/API.json" 19 | cp "$tmpdir/API.ts" "./ui/api/API.ts" 20 | fi 21 | -------------------------------------------------------------------------------- /pkgs/clan-app/bin/start-vm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | script_dir=$(dirname "$(realpath "$0")") 6 | 7 | trap 'rm -rf "$tmpdir"' EXIT 8 | tmpdir=$(mktemp -d) 9 | cd "$tmpdir" 10 | 11 | number_vms="$1" 12 | 13 | for i in $(seq 1 "$number_vms"); do 14 | "$script_dir/start-qemu-vm.sh" "$i" & 15 | done 16 | 17 | while true; do sleep 1; done 18 | -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from . import main 4 | 5 | if __name__ == "__main__": 6 | sys.exit(main()) 7 | -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/api/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/assets/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | loc: Path = Path(__file__).parent 4 | 5 | 6 | def get_asset(name: str | Path) -> Path: 7 | return loc / name 8 | -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/assets/white-favicons/128x128/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/assets/white-favicons/128x128/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/assets/white-favicons/16x16/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/assets/white-favicons/16x16/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/assets/white-favicons/32x32/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/assets/white-favicons/32x32/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/assets/white-favicons/48x48/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/assets/white-favicons/48x48/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/assets/white-favicons/64x64/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/assets/white-favicons/64x64/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/deps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/deps/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-app/clan_app/deps/webview/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/clan_app/deps/webview/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-app/install-desktop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | if ! command -v xdg-mime &> /dev/null; then 5 | echo "Warning: 'xdg-mime' is not available. The desktop file cannot be installed." 6 | fi 7 | 8 | ALREADY_INSTALLED=$(nix profile list --json | jq 'has("elements") and (.elements | has("clan-app"))') 9 | 10 | if [ "$ALREADY_INSTALLED" = "true" ]; then 11 | echo "Upgrading installed clan-app" 12 | nix profile upgrade clan-app 13 | else 14 | nix profile install .#clan-app 15 | fi 16 | 17 | 18 | # install desktop file 19 | set -eou pipefail 20 | DESKTOP_FILE_NAME=org.clan.app.desktop 21 | 22 | xdg-mime default "$DESKTOP_FILE_NAME" x-scheme-handler/clan 23 | -------------------------------------------------------------------------------- /pkgs/clan-app/tests/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-app/tests/helpers/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-app/tests/helpers/cli.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shlex 4 | 5 | from clan_app import main 6 | from clan_lib.custom_logger import get_callers 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | def print_trace(msg: str) -> None: 12 | trace_depth = int(os.environ.get("TRACE_DEPTH", "0")) 13 | callers = get_callers(2, 2 + trace_depth) 14 | 15 | if "run_no_stdout" in callers[0]: 16 | callers = get_callers(3, 3 + trace_depth) 17 | callers_str = "\n".join(f"{i + 1}: {caller}" for i, caller in enumerate(callers)) 18 | log.debug(f"{msg} \nCallers: \n{callers_str}") 19 | 20 | 21 | def run(args: list[str]) -> None: 22 | cmd = shlex.join(["clan", *args]) 23 | print_trace(f"$ {cmd}") 24 | main(args) 25 | -------------------------------------------------------------------------------- /pkgs/clan-app/tests/root.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | TEST_ROOT = Path(__file__).parent.resolve() 7 | PROJECT_ROOT = TEST_ROOT.parent 8 | if CLAN_CORE_ := os.environ.get("CLAN_CORE_PATH"): 9 | CLAN_CORE = Path(CLAN_CORE_) 10 | else: 11 | CLAN_CORE = PROJECT_ROOT.parent.parent 12 | 13 | 14 | @pytest.fixture(scope="session") 15 | def project_root() -> Path: 16 | """ 17 | Root directory the clan-cli 18 | """ 19 | return PROJECT_ROOT 20 | 21 | 22 | @pytest.fixture(scope="session") 23 | def test_root() -> Path: 24 | """ 25 | Root directory of the tests 26 | """ 27 | return TEST_ROOT 28 | 29 | 30 | @pytest.fixture(scope="session") 31 | def clan_core() -> Path: 32 | """ 33 | Directory of the clan-core flake 34 | """ 35 | return CLAN_CORE 36 | -------------------------------------------------------------------------------- /pkgs/clan-app/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from helpers import cli 3 | 4 | 5 | def test_help() -> None: 6 | with pytest.raises(SystemExit): 7 | cli.run(["clan-app", "--help"]) 8 | -------------------------------------------------------------------------------- /pkgs/clan-app/tests/test_join.py: -------------------------------------------------------------------------------- 1 | from wayland import GtkProc 2 | 3 | 4 | def test_open(app: GtkProc) -> None: 5 | assert app.poll() is None 6 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/.gitignore: -------------------------------------------------------------------------------- 1 | app/api 2 | app/.fonts 3 | 4 | .vite 5 | storybook-static -------------------------------------------------------------------------------- /pkgs/clan-app/ui/.storybook/preview.css: -------------------------------------------------------------------------------- 1 | html { 2 | /* revert this which only makes sense when rendering inside the clan app webview */ 3 | overflow-x: revert; 4 | overflow-y: revert; 5 | } 6 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@kachurun/storybook-solid"; 2 | 3 | import "@/src/components/v2/index.css"; 4 | import "./preview.css"; 5 | import "../src/index.css"; 6 | 7 | export const preview: Preview = { 8 | tags: ["autodocs"], 9 | parameters: { 10 | docs: { toc: true }, 11 | backgrounds: { 12 | values: [ 13 | { name: "Dark", value: "#333" }, 14 | { name: "Light", value: "#ffffff" }, 15 | ], 16 | default: "Light", 17 | }, 18 | // automatically create action args for all props that start with "on" 19 | actions: { argTypesRegex: "^on.*" }, 20 | controls: { 21 | matchers: { 22 | color: /(background|color)$/i, 23 | date: /Date$/, 24 | }, 25 | }, 26 | }, 27 | }; 28 | 29 | export default preview; 30 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/.storybook/test-runner.ts: -------------------------------------------------------------------------------- 1 | import type { TestRunnerConfig } from "@storybook/test-runner"; 2 | 3 | const config: TestRunnerConfig = { 4 | async postVisit(page, context) { 5 | // the #storybook-root element wraps the story. In Storybook 6.x, the selector is #root 6 | const elementHandler = await page.$("#storybook-root"); 7 | const innerHTML = await elementHandler.innerHTML(); 8 | expect(innerHTML).toMatchSnapshot(); 9 | }, 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "tailwindCSS.experimental.classRegex": [ 4 | ["cx\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"] 5 | ], 6 | "editor.wordWrap": "on" 7 | } 8 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/arrow-bottom.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/arrow-top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/attention.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/caret-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/caret-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/caret-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/caret-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/checkmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/expand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/eye-close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/filter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/flash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/grid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/load.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/paperclip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/reload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/report.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/update.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/icons/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Solid App 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/prettier.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://prettier.io/docs/en/configuration.html 3 | * @type {import("prettier").Config} 4 | */ 5 | const config = { 6 | trailingComma: "all", 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/Form/base/label.tsx: -------------------------------------------------------------------------------- 1 | import type { JSX } from "solid-js"; 2 | interface LabelProps { 3 | label: JSX.Element; 4 | required?: boolean; 5 | } 6 | export const Label = (props: LabelProps) => ( 7 | 13 | {props.label} 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/Form/fields/FormSection.tsx: -------------------------------------------------------------------------------- 1 | import { JSX } from "solid-js"; 2 | 3 | interface FormSectionProps { 4 | children: JSX.Element; 5 | } 6 | export const FormSection = (props: FormSectionProps) => { 7 | return
{props.children}
; 8 | }; 9 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/Form/fields/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./FormSection"; 2 | export * from "./TextInput"; 3 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/Form/fields/layout.tsx: -------------------------------------------------------------------------------- 1 | import { JSX, splitProps } from "solid-js"; 2 | import cx from "classnames"; 3 | 4 | interface LayoutProps extends JSX.HTMLAttributes { 5 | field?: JSX.Element; 6 | label?: JSX.Element; 7 | error?: JSX.Element; 8 | } 9 | export const FieldLayout = (props: LayoutProps) => { 10 | const [intern, divProps] = splitProps(props, [ 11 | "field", 12 | "label", 13 | "error", 14 | "class", 15 | ]); 16 | return ( 17 |
21 |
{props.label}
22 |
{props.field}
23 | {props.error && {props.error}} 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/api/manual_types.tsx: -------------------------------------------------------------------------------- 1 | export interface Machine { 2 | machine: { 3 | name: string; 4 | flake: { 5 | identifier: string; 6 | }; 7 | override_target_host: string | null; 8 | private_key: string | null; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/api/wifi.ts: -------------------------------------------------------------------------------- 1 | import { callApi } from "."; 2 | import { Schema as Inventory } from "@/api/Inventory"; 3 | 4 | export const instance_name = (machine_name: string) => 5 | `${machine_name}_wifi_0` as const; 6 | 7 | export async function get_iwd_service(base_path: string, machine_name: string) { 8 | const r = await callApi("get_inventory", { 9 | flake: { identifier: base_path }, 10 | }); 11 | if (r.status == "error") { 12 | return null; 13 | } 14 | // @FIXME: Clean this up once we implement the feature 15 | // @ts-expect-error: This doesn't check currently 16 | const inventory: Inventory = r.data; 17 | 18 | const instance_key = instance_name(machine_name); 19 | return inventory.services?.iwd?.[instance_key] || null; 20 | } 21 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/BackButton.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "@solidjs/router"; 2 | import { Button } from "./Button/Button"; 3 | import Icon from "./icon"; 4 | 5 | export const BackButton = () => { 6 | const navigate = useNavigate(); 7 | return ( 8 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Button/Button-Ghost.css: -------------------------------------------------------------------------------- 1 | .button--ghost-hover:hover { 2 | @apply hover:bg-secondary-100 hover:text-secondary-900; 3 | } 4 | 5 | .button--ghost-focus:focus { 6 | @apply focus:bg-secondary-200 focus:text-secondary-900; 7 | } 8 | 9 | .button--ghost-active:active { 10 | @apply active:bg-secondary-200 active:text-secondary-900 active:shadow-inner-primary-active; 11 | } 12 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Helpers/List.tsx: -------------------------------------------------------------------------------- 1 | import { type JSX } from "solid-js"; 2 | 3 | type sizes = "small" | "medium" | "large"; 4 | 5 | const gapSizes: Record = { 6 | small: "gap-2", 7 | medium: "gap-4", 8 | large: "gap-6", 9 | }; 10 | 11 | interface List { 12 | children: JSX.Element; 13 | gapSize: sizes; 14 | } 15 | 16 | export const List = (props: List) => { 17 | const { children, gapSize } = props; 18 | 19 | return
    {children}
; 20 | }; 21 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Helpers/index.tsx: -------------------------------------------------------------------------------- 1 | export { List } from "./List"; 2 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Sidebar/SidebarFlyout/index.tsx: -------------------------------------------------------------------------------- 1 | import { List } from "@/src/components/Helpers"; 2 | import { SidebarListItem } from "../SidebarListItem"; 3 | 4 | export const SidebarFlyout = () => { 5 | return ( 6 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Sidebar/SidebarListItem.tsx: -------------------------------------------------------------------------------- 1 | import { A } from "@solidjs/router"; 2 | import { Typography } from "@/src/components/Typography"; 3 | import "./css/sidebar.css"; 4 | 5 | interface SidebarListItem { 6 | title: string; 7 | href: string; 8 | } 9 | 10 | export const SidebarListItem = (props: SidebarListItem) => { 11 | const { title, href } = props; 12 | 13 | return ( 14 |
  • 15 | 16 | 25 | {title} 26 | 27 | 28 |
  • 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Sidebar/css/sidebar-flyout.css: -------------------------------------------------------------------------------- 1 | .sidebar__flyout { 2 | top: 0; 3 | position: absolute; 4 | z-index: theme(zIndex.30); 5 | 6 | padding: theme(padding[1]); 7 | width: 100%; 8 | height: auto; 9 | } 10 | 11 | .sidebar__flyout__inner { 12 | position: relative; 13 | width: inherit; 14 | height: inherit; 15 | 16 | padding: theme(padding.12) theme(padding.3) theme(padding.3); 17 | background-color: var(--clr-bg-inv-4); 18 | /* / 0.95); */ 19 | border: 1px solid var(--clr-border-inv-4); 20 | border-radius: theme(borderRadius.lg); 21 | } 22 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Sidebar/css/sidebar-header.css: -------------------------------------------------------------------------------- 1 | .sidebar__header { 2 | position: relative; 3 | padding: 1px 1px 0; 4 | cursor: pointer; 5 | 6 | &:after { 7 | content: ""; 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | 12 | width: 100%; 13 | height: 100%; 14 | background: var(--clr-bg-inv-3); 15 | 16 | border-bottom: 1px solid var(--clr-border-inv-3); 17 | border-top-left-radius: theme(borderRadius.xl); 18 | border-top-right-radius: theme(borderRadius.xl); 19 | } 20 | } 21 | 22 | .sidebar__header__inner { 23 | position: relative; 24 | z-index: theme(zIndex.40); 25 | display: flex; 26 | align-items: center; 27 | gap: 0 theme(gap.3); 28 | 29 | padding: theme(padding.3) theme(padding.3); 30 | } 31 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Sidebar/css/sidebar-profile.css: -------------------------------------------------------------------------------- 1 | .sidebar__profile { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | 6 | width: theme(width.8); 7 | height: theme(height.8); 8 | 9 | background: var(--clr-bg-inv-4); 10 | border-radius: 50%; 11 | } 12 | 13 | .sidebar__profile--flyout { 14 | background: var(--clr-bg-def-2); 15 | } 16 | 17 | .sidebar__profile--flyout > .sidebar__profile__character { 18 | color: var(--clr-fg-def-1) !important; 19 | } 20 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Sidebar/css/sidebar.css: -------------------------------------------------------------------------------- 1 | /* Sidebar Elements */ 2 | 3 | @import "./sidebar-header"; 4 | @import "./sidebar-flyout"; 5 | @import "./sidebar-list-item"; 6 | @import "./sidebar-profile"; 7 | 8 | /* Sidebar Structure */ 9 | 10 | .sidebar { 11 | @apply bg-inv-2 h-full border border-solid border-inv-2 min-w-72 rounded-xl; 12 | display: flex; 13 | flex-direction: column; 14 | } 15 | 16 | .sidebar__body { 17 | display: flex; 18 | flex-direction: column; 19 | gap: theme(padding.2); 20 | padding: theme(padding.4) theme(padding.2); 21 | } 22 | 23 | .sidebar__section { 24 | @apply bg-primary-800/90; 25 | 26 | padding: theme(padding.2); 27 | border-radius: theme(borderRadius.md); 28 | 29 | ::marker { 30 | content: ""; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/TagList/TagList.css: -------------------------------------------------------------------------------- 1 | div.tag-list { 2 | @apply flex flex-wrap gap-2; 3 | 4 | span.tag { 5 | @apply w-fit rounded-full px-3 py-2 bg-inv-4 fg-inv-1; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/TagList/TagList.tsx: -------------------------------------------------------------------------------- 1 | import { Component, For } from "solid-js"; 2 | import { Typography } from "@/src/components/Typography"; 3 | import "./TagList.css"; 4 | 5 | export interface TagListProps { 6 | values: string[]; 7 | } 8 | 9 | export const TagList: Component = (props) => { 10 | return ( 11 |
    12 | 13 | {(tag) => ( 14 | 15 | {tag} 16 | 17 | )} 18 | 19 |
    20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography-color.css: -------------------------------------------------------------------------------- 1 | .fnt-clr-primary { 2 | color: var(--clr-fg-def-1); 3 | } 4 | 5 | .fnt-clr-secondary { 6 | color: var(--clr-fg-def-2); 7 | } 8 | 9 | .fnt-clr-tertiary { 10 | color: var(--clr-fg-def-3); 11 | } 12 | 13 | .fnt-clr-primary.fnt-clr--inverted { 14 | color: var(--clr-fg-inv-1); 15 | } 16 | 17 | .fnt-clr-secondary.fnt-clr--inverted { 18 | color: var(--clr-fg-inv-2); 19 | } 20 | 21 | .fnt-clr-tertiary.fnt-clr--inverted { 22 | color: var(--clr-fg-inv-3); 23 | } 24 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography-hierarchy/index.css: -------------------------------------------------------------------------------- 1 | @import "./typography-label.css"; 2 | @import "./typography-body.css"; 3 | @import "./typography-title.css"; 4 | @import "./typography-headline.css"; 5 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography-hierarchy/typography-body.css: -------------------------------------------------------------------------------- 1 | .fnt-body-default { 2 | font-size: 1rem; 3 | line-height: 132%; 4 | letter-spacing: 3%; 5 | } 6 | 7 | .fnt-body-s { 8 | font-size: 0.925rem; 9 | line-height: 132%; 10 | letter-spacing: 3%; 11 | } 12 | 13 | .fnt-body-xs { 14 | font-size: 0.875rem; 15 | line-height: 132%; 16 | letter-spacing: 3%; 17 | } 18 | 19 | .fnt-body-xxs { 20 | font-size: 0.75rem; 21 | line-height: 132%; 22 | letter-spacing: 0.00688rem; 23 | } 24 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography-hierarchy/typography-headline.css: -------------------------------------------------------------------------------- 1 | .fnt-headline-default { 2 | font-size: 1.5rem; 3 | line-height: 116%; 4 | letter-spacing: 1%; 5 | } 6 | 7 | .fnt-headline-m { 8 | font-size: 1.75rem; 9 | line-height: 116%; 10 | letter-spacing: 1%; 11 | } 12 | 13 | .fnt-headline-l { 14 | font-size: 2rem; 15 | line-height: 116%; 16 | letter-spacing: 1%; 17 | } 18 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography-hierarchy/typography-label.css: -------------------------------------------------------------------------------- 1 | .fnt-label-default { 2 | font-size: 0.8125rem; 3 | line-height: 100%; 4 | } 5 | 6 | .fnt-label-s { 7 | font-size: 0.75rem; 8 | line-height: 100%; 9 | } 10 | 11 | .fnt-label-xs { 12 | font-size: 0.6875rem; 13 | line-height: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography-hierarchy/typography-title.css: -------------------------------------------------------------------------------- 1 | .fnt-title-default { 2 | font-size: 1.125rem; 3 | line-height: 124%; 4 | letter-spacing: 3%; 5 | } 6 | 7 | .fnt-title-m { 8 | font-size: 1.25rem; 9 | line-height: 124%; 10 | letter-spacing: 3%; 11 | } 12 | 13 | .fnt-title-l { 14 | font-size: 1.375rem; 15 | line-height: 124%; 16 | letter-spacing: 3%; 17 | } 18 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/Typography/css/typography.css: -------------------------------------------------------------------------------- 1 | @import "./typography-hierarchy/"; 2 | @import "./typography-color.css"; 3 | 4 | .fnt-weight-normal { 5 | font-weight: 300; 6 | } 7 | 8 | .fnt-weight-medium { 9 | font-weight: 500; 10 | } 11 | 12 | .fnt-weight-bold { 13 | font-weight: 700; 14 | } 15 | 16 | .fnt-weight-normal.fnt-clr--inverted { 17 | font-weight: 300; 18 | } 19 | 20 | .fnt-weight-medium.fnt-clr--inverted { 21 | font-weight: 400; 22 | } 23 | 24 | .fnt-weight-bold.fnt-clr--inverted { 25 | font-weight: 700; 26 | } 27 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/accordion/accordion.css: -------------------------------------------------------------------------------- 1 | .accordion { 2 | @apply flex flex-col gap-y-5; 3 | } 4 | 5 | .accordion__title { 6 | @apply flex h-5 cursor-pointer items-center justify-end gap-x-0.5 px-1 font-medium; 7 | } 8 | 9 | .accordion__body { 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/components/v2/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | We will be updating existing components and developing new components in line with the latest designs inside this 4 | folder. As they become ready, they can be copied into the root `components` folder, replacing any existing components as 5 | necessary. 6 | 7 | This is to avoid merge hell and allow us to rapidly match the latest designs without the burden of integration. -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/routes/clans/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./list"; 2 | export * from "./create"; 3 | export * from "./details"; 4 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/routes/colors/view.tsx: -------------------------------------------------------------------------------- 1 | export const colors = () => { 2 | return ( 3 |
    4 |
    red
    5 |
    green
    6 |
    blue
    7 |
    yellow
    8 |
    purple
    9 |
    cyan
    10 |
    pink
    11 |
    12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/routes/deploy/index.tsx: -------------------------------------------------------------------------------- 1 | import { callApi } from "@/src/api"; 2 | import { createQuery } from "@tanstack/solid-query"; 3 | 4 | export const Deploy = () => { 5 | return
    Deloy view
    ; 6 | }; 7 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/routes/machines/avatar.tsx: -------------------------------------------------------------------------------- 1 | import { RndThumbnail } from "@/src/components/noiseThumbnail"; 2 | import cx from "classnames"; 3 | interface AvatarProps { 4 | name?: string; 5 | class?: string; 6 | } 7 | export const MachineAvatar = (props: AvatarProps) => { 8 | return ( 9 |
    10 |
    11 |
    17 | 18 |
    19 |
    20 |
    21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/src/routes/machines/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./details"; 2 | export * from "./create"; 3 | export * from "./list"; 4 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["stylelint-config-standard", "stylelint-config-tailwindcss"], 3 | rules: { 4 | // You can adjust rules here 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import typography from "@tailwindcss/typography"; 2 | import core from "./tailwind/core-plugin"; 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | const config = { 6 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 7 | theme: {}, 8 | plugins: [typography, core], 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/tests/types.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from "vitest"; 2 | 3 | describe.concurrent("API types work properly", () => { 4 | // Test some basic types 5 | it("distinct success/error unions", async () => {}); 6 | }); 7 | -------------------------------------------------------------------------------- /pkgs/clan-app/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite-plugin-solid-svg/types-component-solid", "vite/client"], 12 | "noEmit": true, 13 | "resolveJsonModule": true, 14 | "allowJs": true, 15 | "isolatedModules": true, 16 | "paths": { 17 | "@/*": ["./*"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkgs/clan-cli/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | source_up 3 | 4 | watch_file flake-module.nix shell.nix default.nix 5 | 6 | # Because we depend on nixpkgs sources, uploading to builders takes a long time 7 | use flake .#clan-cli --builders '' 8 | -------------------------------------------------------------------------------- /pkgs/clan-cli/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Clan Webui", 9 | "type": "python", 10 | "request": "launch", 11 | "module": "clan_cli.webui", 12 | "justMyCode": false, 13 | "args": ["--reload", "--no-open", "--log-level", "debug"] 14 | }, 15 | { 16 | "name": "Clan Cli VMs", 17 | "type": "python", 18 | "request": "launch", 19 | "module": "clan_cli", 20 | "justMyCode": false, 21 | "args": ["vms"] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /pkgs/clan-cli/bin/clan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | sys.path.insert( 6 | 0, os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) 7 | ) 8 | 9 | from clan_cli import main # NOQA 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /pkgs/clan-cli/bin/clan-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | sys.path.insert( 6 | 0, os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) 7 | ) 8 | 9 | from clan_cli import config # NOQA 10 | 11 | if __name__ == "__main__": 12 | config.main() 13 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/__main__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/clan/update.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from clan_lib.api import API 4 | from clan_lib.flake import Flake 5 | from clan_lib.nix_models.clan import InventoryMeta as Meta 6 | from clan_lib.persist.inventory_store import InventorySnapshot, InventoryStore 7 | from clan_lib.persist.util import set_value_by_path 8 | 9 | 10 | @dataclass 11 | class UpdateOptions: 12 | flake: Flake 13 | meta: Meta 14 | 15 | 16 | @API.register 17 | def update_clan_meta(options: UpdateOptions) -> InventorySnapshot: 18 | inventory_store = InventoryStore(options.flake) 19 | inventory = inventory_store.read() 20 | set_value_by_path(inventory, "meta", options.meta) 21 | inventory_store.write(inventory, message="Update clan metadata") 22 | 23 | return inventory 24 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/conftest.py: -------------------------------------------------------------------------------- 1 | pytest_plugins = [ 2 | "clan_cli.tests.temporary_dir", 3 | "clan_cli.tests.root", 4 | "clan_cli.tests.age_keys", 5 | "clan_cli.tests.gpg_keys", 6 | "clan_cli.tests.git_repo", 7 | "clan_cli.tests.sshd", 8 | "clan_cli.tests.command", 9 | "clan_cli.tests.ports", 10 | "clan_cli.tests.hosts", 11 | "clan_cli.tests.runtime", 12 | "clan_cli.tests.fixtures_flakes", 13 | "clan_cli.tests.stdout", 14 | "clan_cli.tests.nix_config", 15 | ] 16 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/facts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/facts/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/flash/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/flash/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/machines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/machines/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/machines/types.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | 4 | VALID_HOSTNAME = re.compile(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", re.IGNORECASE) 5 | 6 | 7 | def validate_hostname(hostname: str) -> bool: 8 | if len(hostname) > 63: 9 | return False 10 | return VALID_HOSTNAME.match(hostname) is not None 11 | 12 | 13 | def machine_name_type(arg_value: str) -> str: 14 | if len(arg_value) > 63: 15 | msg = "Machine name must be less than 63 characters long" 16 | raise argparse.ArgumentTypeError(msg) 17 | if not VALID_HOSTNAME.match(arg_value): 18 | msg = "Invalid character in machine name. Allowed characters are a-z, 0-9, ., and -. Must not start with a number" 19 | raise argparse.ArgumentTypeError(msg) 20 | return arg_value 21 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/py.typed -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/qemu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/qemu/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/select.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | 4 | from clan_lib.flake import Flake 5 | 6 | 7 | def select_command(args: argparse.Namespace) -> None: 8 | flake: Flake = args.flake 9 | print(json.dumps(flake.select(args.selector), indent=4)) 10 | 11 | 12 | def register_parser(parser: argparse.ArgumentParser) -> None: 13 | parser.set_defaults(func=select_command) 14 | parser.add_argument( 15 | "selector", 16 | help="select from a flake", 17 | ) 18 | parser.add_argument( 19 | "--impure", 20 | action="store_true", 21 | default=False, 22 | ) 23 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/ssh/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | T = TypeVar("T") 4 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/gnupg-home/pubring.kbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/data/gnupg-home/pubring.kbx -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/gnupg-home/random_seed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/data/gnupg-home/random_seed -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/gnupg-home/trustdb.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/data/gnupg-home/trustdb.gpg -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/gnupg.conf: -------------------------------------------------------------------------------- 1 | Key-Type: 1 2 | Key-Length: 1024 3 | Name-Real: Root Superuser 4 | Name-Email: test@local 5 | Expire-Date: 0 6 | %no-protection 7 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/password-store/.gpg-id: -------------------------------------------------------------------------------- 1 | test@local 2 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/ssh_host_ed25519_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACDonlRWMYxHTtnOeeiurKA1j26EfVZWeozuqSrtCYScFwAAAJje9J1V3vSd 4 | VQAAAAtzc2gtZWQyNTUxOQAAACDonlRWMYxHTtnOeeiurKA1j26EfVZWeozuqSrtCYScFw 5 | AAAEBxDpEXwhlJB/f6ZJOT9BbSqXeLy9S6qeuc25hXu5kpbuieVFYxjEdO2c556K6soDWP 6 | boR9VlZ6jO6pKu0JhJwXAAAAE2pvZXJnQHR1cmluZ21hY2hpbmUBAg== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/ssh_host_ed25519_key.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOieVFYxjEdO2c556K6soDWPboR9VlZ6jO6pKu0JhJwX joerg@turingmachine 2 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/data/sshd_config: -------------------------------------------------------------------------------- 1 | HostKey $host_key 2 | LogLevel DEBUG3 3 | # In the nix build sandbox we don't get any meaningful PATH after login 4 | MaxStartups 64:30:256 5 | AuthorizedKeysFile $host_key.pub 6 | AcceptEnv REALPATH 7 | PasswordAuthentication no 8 | Subsystem sftp $sftp_server -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/fixture_error.py: -------------------------------------------------------------------------------- 1 | class FixtureError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/git_repo.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from pathlib import Path 3 | 4 | import pytest 5 | from clan_lib.nix import nix_shell 6 | 7 | 8 | # fixture for git_repo 9 | @pytest.fixture 10 | def git_repo(temp_dir: Path) -> Path: 11 | # initialize a git repository 12 | cmd = nix_shell(["git"], ["git", "init"]) 13 | subprocess.run(cmd, cwd=temp_dir, check=True) 14 | # set user.name and user.email 15 | cmd = nix_shell(["git"], ["git", "config", "user.name", "test"]) 16 | subprocess.run(cmd, cwd=temp_dir, check=True) 17 | cmd = nix_shell(["git"], ["git", "config", "user.email", "test@test.test"]) 18 | subprocess.run(cmd, cwd=temp_dir, check=True) 19 | # return the path to the git repository 20 | return temp_dir 21 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/gpg_keys.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from dataclasses import dataclass 3 | from pathlib import Path 4 | 5 | import pytest 6 | 7 | 8 | @dataclass 9 | class GpgKey: 10 | fingerprint: str 11 | gpg_home: Path 12 | 13 | 14 | @pytest.fixture 15 | def gpg_key( 16 | temp_dir: Path, 17 | monkeypatch: pytest.MonkeyPatch, 18 | test_root: Path, 19 | ) -> GpgKey: 20 | gpg_home = temp_dir / "gnupghome" 21 | 22 | shutil.copytree(test_root / "data" / "gnupg-home", gpg_home) 23 | monkeypatch.setenv("GNUPGHOME", str(gpg_home)) 24 | 25 | return GpgKey("9A9B2741C8062D3D3DF1302D8B049E262A5CA255", gpg_home) 26 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/helpers/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/helpers/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import shlex 4 | 5 | from clan_cli import create_parser 6 | from clan_lib.custom_logger import print_trace 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | def run(args: list[str]) -> argparse.Namespace: 12 | parser = create_parser(prog="clan") 13 | parsed = parser.parse_args(args) 14 | cmd = shlex.join(["clan", *args]) 15 | 16 | print_trace(f"$ {cmd}", log, "localhost") 17 | if hasattr(parsed, "func"): 18 | parsed.func(parsed) 19 | return parsed 20 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/helpers/nixos_config.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from collections.abc import Callable 3 | from typing import Any 4 | 5 | 6 | def def_value() -> defaultdict: 7 | return defaultdict(def_value) 8 | 9 | 10 | # allows defining nested dictionary in a single line 11 | nested_dict: Callable[[], dict[str, Any]] = lambda: defaultdict(def_value) 12 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/helpers/validator.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | class Error(Exception): 5 | pass 6 | 7 | 8 | def is_valid_age_key(secret_key: str) -> bool: 9 | # Run the age-keygen command with the -y flag to check the key format 10 | result = subprocess.run( 11 | ["age-keygen", "-y"], 12 | input=secret_key, 13 | capture_output=True, 14 | text=True, 15 | check=False, 16 | ) 17 | 18 | if result.returncode == 0: 19 | return True 20 | msg = f"Invalid age key: {secret_key}" 21 | raise Error(msg) 22 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/hosts.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pwd 3 | from pathlib import Path 4 | 5 | import pytest 6 | from clan_cli.ssh.host_key import HostKeyCheck 7 | from clan_cli.tests.sshd import Sshd 8 | from clan_lib.ssh.remote import Remote 9 | 10 | 11 | @pytest.fixture 12 | def hosts(sshd: Sshd) -> list[Remote]: 13 | login = pwd.getpwuid(os.getuid()).pw_name 14 | group = [ 15 | Remote( 16 | "127.0.0.1", 17 | port=sshd.port, 18 | user=login, 19 | private_key=Path(sshd.key), 20 | host_key_check=HostKeyCheck.NONE, 21 | command_prefix="local_test", 22 | ) 23 | ] 24 | 25 | return group 26 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/machines/vm1/default.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | clan.core.networking.targetHost = "__CLAN_TARGET_ADDRESS__"; 4 | system.stateVersion = config.system.nixos.release; 5 | sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; 6 | clan.core.facts.secretUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; 7 | clan.virtualisation.graphics = false; 8 | 9 | clan.core.facts.networking.zerotier.controller.enable = true; 10 | networking.useDHCP = false; 11 | 12 | systemd.services.shutdown-after-boot = { 13 | enable = true; 14 | wantedBy = [ "multi-user.target" ]; 15 | after = [ "multi-user.target" ]; 16 | script = '' 17 | #!/usr/bin/env bash 18 | shutdown -h now 19 | ''; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/machines/vm_with_secrets/default.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | clan.core.networking.targetHost = "__CLAN_TARGET_ADDRESS__"; 4 | system.stateVersion = config.system.nixos.release; 5 | sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; 6 | clan.core.facts.secretUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; 7 | clan.virtualisation.graphics = false; 8 | 9 | clan.core.networking.zerotier.controller.enable = true; 10 | networking.useDHCP = false; 11 | 12 | systemd.services.shutdown-after-boot = { 13 | enable = true; 14 | wantedBy = [ "multi-user.target" ]; 15 | after = [ "multi-user.target" ]; 16 | script = '' 17 | #!/usr/bin/env bash 18 | shutdown -h now 19 | ''; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/machines/vm_without_secrets/default.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | clan.core.networking.targetHost = "__CLAN_TARGET_ADDRESS__"; 4 | system.stateVersion = config.system.nixos.release; 5 | clan.virtualisation.graphics = false; 6 | 7 | networking.useDHCP = false; 8 | 9 | systemd.services.shutdown-after-boot = { 10 | enable = true; 11 | wantedBy = [ "multi-user.target" ]; 12 | after = [ "multi-user.target" ]; 13 | script = '' 14 | #!/usr/bin/env bash 15 | shutdown -h now 16 | ''; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/nix_config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import subprocess 3 | from dataclasses import dataclass 4 | 5 | import pytest 6 | 7 | 8 | @dataclass 9 | class ConfigItem: 10 | aliases: list[str] 11 | defaultValue: bool # noqa: N815 12 | description: str 13 | documentDefault: bool # noqa: N815 14 | experimentalFeature: str # noqa: N815 15 | value: str | bool | list[str] | dict[str, str] 16 | 17 | 18 | @pytest.fixture(scope="session") 19 | def nix_config() -> dict[str, ConfigItem]: 20 | proc = subprocess.run( 21 | ["nix", "config", "show", "--json"], check=True, stdout=subprocess.PIPE 22 | ) 23 | data = json.loads(proc.stdout) 24 | return {name: ConfigItem(**c) for name, c in data.items()} 25 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/runtime.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from clan_lib.async_run import AsyncRuntime 3 | 4 | 5 | @pytest.fixture 6 | def runtime() -> AsyncRuntime: 7 | return AsyncRuntime() 8 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_backups.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from clan_cli.tests.fixtures_flakes import FlakeForTest 3 | from clan_cli.tests.helpers import cli 4 | 5 | 6 | @pytest.mark.impure 7 | def test_backups( 8 | test_flake_with_core: FlakeForTest, 9 | ) -> None: 10 | cli.run( 11 | [ 12 | "backups", 13 | "list", 14 | "--flake", 15 | str(test_flake_with_core.path), 16 | "vm1", 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from clan_cli.tests.helpers import cli 3 | from clan_cli.tests.stdout import CaptureOutput 4 | 5 | 6 | def test_help(capture_output: CaptureOutput) -> None: 7 | with capture_output as output, pytest.raises(SystemExit): 8 | cli.run(["--help"]) 9 | assert output.out.startswith("usage:") 10 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake/.clan-flake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/test_flake/.clan-flake -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake/fake-module.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | options.clan.fake-module.fake-flag = lib.mkOption { 4 | type = lib.types.bool; 5 | default = false; 6 | description = '' 7 | A useless fake flag fro testing purposes. 8 | ''; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake/nixosModules/machine1.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | options.clan.jitsi.enable = lib.mkOption { 4 | type = lib.types.bool; 5 | default = false; 6 | description = "Enable jitsi on this machine"; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake_with_core/.clan-flake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/test_flake_with_core/.clan-flake -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake_with_core_and_pass/.clan-flake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/test_flake_with_core_and_pass/.clan-flake -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake_with_core_dynamic_machines/.clan-flake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/tests/test_flake_with_core_dynamic_machines/.clan-flake -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flake_with_core_dynamic_machines/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | # Use this path to our repo root e.g. for UI test 3 | # inputs.clan-core.url = "../../../../."; 4 | 5 | # this placeholder is replaced by the path to nixpkgs 6 | inputs.clan-core.url = "__CLAN_CORE__"; 7 | 8 | outputs = 9 | { self, clan-core }: 10 | let 11 | clan = clan-core.clanLib.buildClan { 12 | inherit self; 13 | meta.name = "test_flake_with_core_dynamic_machines"; 14 | machines = 15 | let 16 | machineModules = builtins.readDir (self + "/machines"); 17 | in 18 | builtins.mapAttrs (name: _type: import (self + "/machines/${name}")) machineModules; 19 | }; 20 | in 21 | { 22 | inherit (clan) nixosConfigurations clanInternals; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_flakes_cli.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import pytest 4 | from clan_cli.tests.fixtures_flakes import FlakeForTest 5 | from clan_cli.tests.helpers import cli 6 | from clan_cli.tests.stdout import CaptureOutput 7 | 8 | if TYPE_CHECKING: 9 | pass 10 | 11 | 12 | @pytest.mark.impure 13 | def test_flakes_inspect( 14 | test_flake_with_core: FlakeForTest, capture_output: CaptureOutput 15 | ) -> None: 16 | with capture_output as output: 17 | cli.run( 18 | [ 19 | "flakes", 20 | "inspect", 21 | "--flake", 22 | str(test_flake_with_core.path), 23 | "--machine", 24 | "vm1", 25 | ] 26 | ) 27 | assert "Icon" in output.out 28 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/tests/test_upload_single_file.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | from clan_cli.ssh.upload import upload 5 | from clan_lib.ssh.remote import Remote 6 | 7 | 8 | @pytest.mark.with_core 9 | def test_upload_single_file( 10 | temporary_home: Path, 11 | hosts: list[Remote], 12 | ) -> None: 13 | host = hosts[0] 14 | 15 | src_file = temporary_home / "test.txt" 16 | src_file.write_text("test") 17 | dest_file = temporary_home / "test_dest.txt" 18 | with host.ssh_control_master() as host: 19 | upload(host, src_file, dest_file) 20 | 21 | assert dest_file.exists() 22 | assert dest_file.read_text() == "test" 23 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/vars/public_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/vars/public_modules/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/vars/secret_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_cli/vars/secret_modules/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/vms/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from .inspect import register_inspect_parser 4 | from .run import register_run_parser 5 | 6 | 7 | def register_parser(parser: argparse.ArgumentParser) -> None: 8 | subparser = parser.add_subparsers( 9 | title="command", 10 | description="command to execute", 11 | help="the command to execute", 12 | required=True, 13 | ) 14 | 15 | register_inspect_parser( 16 | subparser.add_parser("inspect", help="inspect the vm configuration") 17 | ) 18 | register_run_parser(subparser.add_parser("run", help="run a VM from a machine")) 19 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/vms/mimetypes/applications/mimeapps.list: -------------------------------------------------------------------------------- 1 | [Default Applications] 2 | x-scheme-handler/spice=remote-viewer.desktop 3 | x-scheme-handler/spice+unix=remote-viewer.desktop 4 | x-scheme-handler/spice+tls=remote-viewer.desktop 5 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_cli/vms/mimetypes/applications/remote-viewer.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Remote Viewer 3 | Type=Application 4 | Exec=remote-viewer %u 5 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/api/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import json 5 | 6 | from . import API 7 | 8 | if __name__ == "__main__": 9 | parser = argparse.ArgumentParser(description="Debug the API.") 10 | args = parser.parse_args() 11 | 12 | schema = API.to_json_schema() 13 | print(json.dumps(schema, indent=4)) 14 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/backups/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/backups/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/bwrap/tests/test_bwrap.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | from clan_lib.bwrap import bubblewrap_works 5 | 6 | 7 | @pytest.mark.skipif(sys.platform != "linux", reason="bubblewrap only works on linux") 8 | def test_bubblewrap_works_on_linux() -> None: 9 | assert bubblewrap_works() is True 10 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/clan/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/clan/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/conftest.py: -------------------------------------------------------------------------------- 1 | pytest_plugins = [ 2 | "clan_cli.tests.fixtures_flakes", 3 | "clan_cli.tests.hosts", 4 | "clan_cli.tests.sshd", 5 | "clan_cli.tests.runtime", 6 | ] 7 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/flake/__init__.py: -------------------------------------------------------------------------------- 1 | from .flake import Flake # noqa 2 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/inventory/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEPRECATED: 3 | 4 | Don't use this module anymore 5 | 6 | Instead use: 7 | 'clan_lib.persist.inventoryStore' 8 | 9 | Which is an abstraction over the inventory 10 | 11 | Interacting with 'clan_lib.inventory' is NOT recommended and will be removed 12 | """ 13 | 14 | from clan_lib.api import API 15 | from clan_lib.flake import Flake 16 | from clan_lib.persist.inventory_store import InventorySnapshot, InventoryStore 17 | 18 | 19 | @API.register 20 | def get_inventory(flake: Flake) -> InventorySnapshot: 21 | inventory_store = InventoryStore(flake) 22 | inventory = inventory_store.read() 23 | return inventory 24 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/jsonrpc/__init__.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import json 3 | from typing import Any 4 | 5 | 6 | class ClanJSONEncoder(json.JSONEncoder): 7 | def default(self, o: Any) -> Any: 8 | # Check if the object has a to_json method 9 | if hasattr(o, "to_json") and callable(o.to_json): 10 | return o.to_json() 11 | # Check if the object is a dataclass 12 | if dataclasses.is_dataclass(o): 13 | return dataclasses.asdict(o) # type: ignore 14 | # Otherwise, use the default serialization 15 | return super().default(o) 16 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/machines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/machines/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/nix/allowed-packages.json: -------------------------------------------------------------------------------- 1 | [ 2 | "age", 3 | "age-plugin-1p", 4 | "age-plugin-fido2-hmac", 5 | "age-plugin-ledger", 6 | "age-plugin-se", 7 | "age-plugin-sss", 8 | "age-plugin-tpm", 9 | "age-plugin-yubikey", 10 | "avahi", 11 | "bash", 12 | "bubblewrap", 13 | "disko", 14 | "e2fsprogs", 15 | "git", 16 | "gnupg", 17 | "dialog", 18 | "mypy", 19 | "netcat", 20 | "nix", 21 | "nixos-anywhere", 22 | "openssh", 23 | "pass", 24 | "qemu", 25 | "rsync", 26 | "shellcheck-minimal", 27 | "sops", 28 | "sshpass", 29 | "tor", 30 | "util-linux", 31 | "virt-viewer", 32 | "virtiofsd", 33 | "waypipe", 34 | "zbar", 35 | "zenity" 36 | ] 37 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/nix_models/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | clanSchema=$(nix build .#schemas.clan-schema-abstract --print-out-paths)/schema.json 6 | SCRIPT_DIR=$(dirname "$0") 7 | cd "$SCRIPT_DIR" 8 | nix run .#classgen -- "$clanSchema" "./clan.py" 9 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/persist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/persist/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/persist/fixtures/1.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/persist/fixtures/deferred.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/persist/fixtures/deferred.nix: -------------------------------------------------------------------------------- 1 | { clanLib, lib, ... }: 2 | let 3 | eval = lib.evalModules { 4 | modules = [ 5 | { 6 | # Trying to write into the default 7 | options.foo = lib.mkOption { 8 | type = lib.types.attrsOf clanLib.types.uniqueDeferredSerializableModule; 9 | }; 10 | } 11 | { 12 | foo = { 13 | a = { }; 14 | b = { }; 15 | }; 16 | } 17 | 18 | # Merge the "inventory.json" 19 | (builtins.fromJSON (builtins.readFile ./deferred.json)) 20 | ]; 21 | }; 22 | in 23 | { 24 | clanInternals.inventoryClass.inventory = eval.config; 25 | clanInternals.inventoryClass.introspection = clanLib.introspection.getPrios { 26 | options = eval.options; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/persist/fixtures/lists.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/py.typed -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/ssh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/ssh/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/clan_lib/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-cli/clan_lib/tests/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-cli/conftest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | from clan_lib.custom_logger import setup_logging 5 | 6 | # Every fixture registered here will be available in clan_cli and clan_lib 7 | pytest_plugins = [ 8 | "clan_cli.tests.temporary_dir", 9 | "clan_cli.tests.root", 10 | "clan_cli.tests.sshd", 11 | "clan_cli.tests.hosts", 12 | "clan_cli.tests.command", 13 | "clan_cli.tests.ports", 14 | ] 15 | 16 | 17 | # Executed on pytest session start 18 | def pytest_sessionstart(session: pytest.Session) -> None: 19 | # This function will be called once at the beginning of the test session 20 | print("Starting pytest session") 21 | # You can access the session config, items, testsfailed, etc. 22 | print(f"Session config: {session.config}") 23 | 24 | setup_logging(logging.DEBUG) 25 | -------------------------------------------------------------------------------- /pkgs/clan-cli/deps-flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "dependencies for the clan-cli"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs"; 6 | }; 7 | 8 | outputs = _inputs: { }; 9 | } 10 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | source_up 3 | 4 | watch_file flake-module.nix shell.nix default.nix 5 | 6 | # Because we depend on nixpkgs sources, uploading to builders takes a long time 7 | use flake .#clan-vm-manager --builders '' 8 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/.gitignore: -------------------------------------------------------------------------------- 1 | **/.vscode -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/.vscode/lhebendanz.weaudit: -------------------------------------------------------------------------------- 1 | { 2 | "clientRemote": "", 3 | "gitRemote": "", 4 | "gitSha": "", 5 | "treeEntries": [], 6 | "auditedFiles": [], 7 | "resolvedEntries": [] 8 | } -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/bin/clan-vm-manager: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from pathlib import Path 4 | 5 | module_path = Path(__file__).parent.parent.absolute() 6 | 7 | sys.path.insert(0, str(module_path)) 8 | sys.path.insert(0, str(module_path.parent / "clan_cli")) 9 | 10 | from clan_vm_manager import main # NOQA 11 | 12 | if __name__ == "__main__": 13 | main() 14 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | from clan_cli.profiler import profile 5 | 6 | from clan_vm_manager.app import MainApplication 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | @profile 12 | def main(argv: list[str] = sys.argv) -> int: 13 | app = MainApplication() 14 | return app.run(argv) 15 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from . import main 4 | 5 | if __name__ == "__main__": 6 | sys.exit(main()) 7 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | loc: Path = Path(__file__).parent 4 | 5 | 6 | def get_asset(name: str | Path) -> Path: 7 | return loc / name 8 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/clan_black_notext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/assets/clan_black_notext.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/128x128/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/128x128/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/16x16/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/16x16/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/32x32/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/32x32/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/48x48/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/48x48/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/64x64/apps/clan-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/assets/white-favicons/64x64/apps/clan-white.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/components/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/components/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/components/interfaces.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import gi 4 | 5 | gi.require_version("Gtk", "4.0") 6 | 7 | 8 | @dataclass 9 | class ClanConfig: 10 | initial_view: str 11 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/singletons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/singletons/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/views/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/clan_vm_manager/windows/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/clan_vm_manager/windows/__init__.py -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/flake-module.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | perSystem = 4 | { 5 | config, 6 | pkgs, 7 | lib, 8 | system, 9 | ... 10 | }: 11 | { 12 | devShells.clan-vm-manager = pkgs.callPackage ./shell.nix { 13 | inherit (config.packages) clan-vm-manager; 14 | }; 15 | } 16 | // lib.optionalAttrs (system != lib.platforms.darwin) { 17 | packages.clan-vm-manager = pkgs.python3.pkgs.callPackage ./default.nix { 18 | inherit (config.packages) clan-cli; 19 | }; 20 | 21 | checks = config.packages.clan-vm-manager.tests; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/install-desktop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | if ! command -v xdg-mime &> /dev/null; then 5 | echo "Warning: 'xdg-mime' is not available. The desktop file cannot be installed." 6 | fi 7 | 8 | ALREADY_INSTALLED=$(nix profile list --json | jq 'has("elements") and (.elements | has("clan-vm-manager"))') 9 | 10 | if [ "$ALREADY_INSTALLED" = "true" ]; then 11 | echo "Upgrading installed clan-vm-manager" 12 | nix profile upgrade clan-vm-manager 13 | else 14 | nix profile install .#clan-vm-manager --priority 4 15 | fi 16 | 17 | 18 | # install desktop file 19 | set -eou pipefail 20 | DESKTOP_FILE_NAME=org.clan.vm-manager.desktop 21 | 22 | xdg-mime default "$DESKTOP_FILE_NAME" x-scheme-handler/clan 23 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/notes.md: -------------------------------------------------------------------------------- 1 | # Webkit GTK doesn't interop flawless with Solid.js build result 2 | 3 | 1. Webkit expects script tag to be in `body` only solid.js puts the in the head. 4 | 2. script and css files are loaded with type="module" and crossorigin tags being set. WebKit silently fails to load then. 5 | 3. Paths to resiources are not allowed to start with "/" because webkit interprets them relative to the system and not the base url. 6 | 4. webkit doesn't support native features such as directly handling external urls (i.e opening them in the default browser) 7 | 6. Other problems to be found? 8 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/screenshots/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/clan-vm-manager/screenshots/image.png -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/tests/helpers/cli.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shlex 4 | 5 | from clan_lib.custom_logger import get_callers 6 | from clan_vm_manager import main 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | def print_trace(msg: str) -> None: 12 | trace_depth = int(os.environ.get("TRACE_DEPTH", "0")) 13 | callers = get_callers(2, 2 + trace_depth) 14 | 15 | if "run_no_stdout" in callers[0]: 16 | callers = get_callers(3, 3 + trace_depth) 17 | callers_str = "\n".join(f"{i + 1}: {caller}" for i, caller in enumerate(callers)) 18 | log.debug(f"{msg} \nCallers: \n{callers_str}") 19 | 20 | 21 | class Cli: 22 | def run(self, args: list[str]) -> None: 23 | cmd = shlex.join(["clan", *args]) 24 | print_trace(f"$ {cmd}") 25 | main(args) 26 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/tests/root.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | TEST_ROOT = Path(__file__).parent.resolve() 7 | PROJECT_ROOT = TEST_ROOT.parent 8 | if CLAN_CORE_ := os.environ.get("CLAN_CORE_PATH"): 9 | CLAN_CORE = Path(CLAN_CORE_) 10 | else: 11 | CLAN_CORE = PROJECT_ROOT.parent.parent 12 | 13 | 14 | @pytest.fixture(scope="session") 15 | def project_root() -> Path: 16 | """ 17 | Root directory the clan-cli 18 | """ 19 | return PROJECT_ROOT 20 | 21 | 22 | @pytest.fixture(scope="session") 23 | def test_root() -> Path: 24 | """ 25 | Root directory of the tests 26 | """ 27 | return TEST_ROOT 28 | 29 | 30 | @pytest.fixture(scope="session") 31 | def clan_core() -> Path: 32 | """ 33 | Directory of the clan-core flake 34 | """ 35 | return CLAN_CORE 36 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cli import Cli 3 | 4 | 5 | def test_help(capfd: pytest.CaptureFixture) -> None: 6 | cli = Cli() 7 | with pytest.raises(SystemExit): 8 | cli.run(["clan-vm-manager", "--help"]) 9 | -------------------------------------------------------------------------------- /pkgs/clan-vm-manager/tests/test_join.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from wayland import GtkProc 4 | 5 | 6 | def test_open(app: GtkProc) -> None: 7 | time.sleep(0.5) 8 | assert app.poll() is None 9 | -------------------------------------------------------------------------------- /pkgs/classgen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/classgen/__init__.py -------------------------------------------------------------------------------- /pkgs/classgen/default.nix: -------------------------------------------------------------------------------- 1 | { writers }: writers.writePython3Bin "classgen" { flakeIgnore = [ "E501" ]; } ./main.py 2 | -------------------------------------------------------------------------------- /pkgs/editor/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | { 3 | clan-edit-codium = pkgs.callPackage ./clan-edit-codium.nix; 4 | } 5 | -------------------------------------------------------------------------------- /pkgs/editor/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "security.workspace.trust.enabled": false, 3 | "nix.enableLanguageServer": true, 4 | "nix.serverPath": "nixd", 5 | "nix.formatterPath": "nixfmt", 6 | "nix.serverSettings": { 7 | "nixd": { 8 | "formatting": { 9 | "command": "nixfmt" 10 | }, 11 | "options": { 12 | "nixos": { 13 | "expr": "(let pkgs = import { }; in (pkgs.lib.evalModules { modules = (import ) ++ [ ({...}: { nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options" 14 | }, 15 | "home-manager": { 16 | "expr": "(builtins.getFlake \"github:nix-community/home-manager\").homeConfigurations..options" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkgs/generate-test-vars/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | buildPythonApplication, 3 | python, 4 | clan-cli, 5 | }: 6 | buildPythonApplication { 7 | name = "generate-test-vars"; 8 | src = ./.; 9 | format = "pyproject"; 10 | dependencies = [ (python.pkgs.toPythonModule clan-cli) ]; 11 | nativeBuildInputs = [ 12 | (python.withPackages (ps: [ ps.setuptools ])) 13 | ]; 14 | checkPhase = '' 15 | runHook preCheck 16 | $out/bin/generate-test-vars --help 17 | runHook preCheck 18 | ''; 19 | } 20 | -------------------------------------------------------------------------------- /pkgs/generate-test-vars/flake-module.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | perSystem = 4 | { 5 | config, 6 | pkgs, 7 | ... 8 | }: 9 | { 10 | # devShells.vars-generator = pkgs.callPackage ./shell.nix { 11 | 12 | # }; 13 | packages.generate-test-vars = pkgs.python3.pkgs.callPackage ./default.nix { 14 | inherit (config.packages) clan-cli; 15 | }; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /pkgs/generate-test-vars/generate_test_vars/__init_.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/pkgs/generate-test-vars/generate_test_vars/__init_.py -------------------------------------------------------------------------------- /pkgs/generate-test-vars/test/vars.nix: -------------------------------------------------------------------------------- 1 | # Test that we can generate vars 2 | { 3 | vars.generators = { 4 | test_generator_1 = { 5 | files.hello = { 6 | secret = false; 7 | }; 8 | script = '' 9 | echo "hello world 1" > $out/hello 10 | ''; 11 | }; 12 | test_generator_2 = { 13 | files.hello = { 14 | secret = false; 15 | }; 16 | script = '' 17 | echo "hello world 2" > $out/hello 18 | ''; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /pkgs/icon-update/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | source_up 3 | 4 | watch_file flake-module.nix default.nix 5 | 6 | # Because we depend on nixpkgs sources, uploading to builders takes a long time 7 | use flake .#icon-update --builders '' 8 | -------------------------------------------------------------------------------- /pkgs/icon-update/.gitignore: -------------------------------------------------------------------------------- 1 | icons/ 2 | .vscode/ -------------------------------------------------------------------------------- /pkgs/icon-update/README.md: -------------------------------------------------------------------------------- 1 | # Clan Icons 2 | 3 | This script updates the clan icons. 4 | 5 | To update the icons 6 | 7 | https://help.figma.com/hc/en-us/articles/8085703771159-Manage-personal-access-tokens 8 | 9 | - `export FIGMA_TOKEN=YOUR_TOKEN` 10 | - `nix run .#icon-update` 11 | 12 | ## Development on the script 13 | 14 | - Vscode: open this folder as project root. This will configure vscode to use deno instead of nodejs. 15 | 16 | - Non-vscode: Use the deno lsp of your editor 17 | -------------------------------------------------------------------------------- /pkgs/icon-update/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, deno }: 2 | let 3 | src = ./.; 4 | in 5 | pkgs.writeShellApplication { 6 | name = "update"; 7 | 8 | runtimeInputs = [ deno ]; 9 | runtimeEnv = { 10 | FIGMA_ICON_FILE_ID = "KJgLnsBI9nvUt44qKJXmVm"; 11 | FRAME_ID = "709-324"; 12 | }; 13 | 14 | text = '' 15 | REPO_ROOT="$(git rev-parse --show-toplevel)" 16 | OUT_DIR="$(realpath "$REPO_ROOT"/pkgs/clan-app/ui/app/icons)" 17 | export OUT_DIR 18 | deno run --allow-all ${src}/main.ts 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /pkgs/icon-update/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "dev": "deno run --watch main.ts" 4 | }, 5 | "imports": { 6 | "@libs/diff": "jsr:@libs/diff@^2.0.0", 7 | "@std/assert": "jsr:@std/assert@1", 8 | "svgo": "npm:svgo@^3.3.2", 9 | "typescript": "npm:typescript@^5.6.3" 10 | }, 11 | "compilerOptions": { 12 | "lib": ["dom", "deno.ns"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkgs/icon-update/flake-module.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | perSystem = 4 | { 5 | pkgs, 6 | system, 7 | lib, 8 | ... 9 | }: 10 | lib.optionalAttrs (system == "x86_64-linux") { 11 | packages.icon-update = pkgs.callPackage ./default.nix { }; 12 | 13 | devShells.icon-update = pkgs.callPackage ./shell.nix { }; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /pkgs/icon-update/shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | pkgs.mkShell { 3 | name = "clan-icon-update"; 4 | packages = with pkgs; [ deno ]; 5 | env = { 6 | FIGMA_ICON_FILE_ID = "KJgLnsBI9nvUt44qKJXmVm"; 7 | FRAME_ID = "709-324"; 8 | OUT_DIR = "./icons"; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /pkgs/merge-after-ci/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | bash, 3 | callPackage, 4 | coreutils, 5 | git, 6 | lib, 7 | nix, 8 | openssh, 9 | tea, 10 | tea-create-pr, 11 | ... 12 | }: 13 | let 14 | writers = callPackage ../builders/script-writers.nix { }; 15 | in 16 | writers.writePython3Bin "merge-after-ci" { 17 | makeWrapperArgs = [ 18 | "--prefix" 19 | "PATH" 20 | ":" 21 | (lib.makeBinPath [ 22 | bash 23 | coreutils 24 | git 25 | nix 26 | openssh 27 | tea 28 | tea-create-pr 29 | ]) 30 | ]; 31 | } ./merge-after-ci.py 32 | -------------------------------------------------------------------------------- /pkgs/merge-after-ci/merge-after-ci.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import shlex 3 | import subprocess 4 | 5 | parser = argparse.ArgumentParser() 6 | parser.add_argument("--reviewers", nargs="*", default=[]) 7 | parser.add_argument("--no-review", action="store_true") 8 | parser.add_argument("args", nargs="*") 9 | args = parser.parse_args() 10 | 11 | # complain if neither --reviewers nor --no-review is given 12 | if not args.reviewers and not args.no_review: 13 | parser.error("either --reviewers or --no-review must be given") 14 | 15 | cmd = [ 16 | "tea-create-pr", 17 | "origin", 18 | "upstream", 19 | "main", 20 | *(["--labels", "needs-review"] if not args.no_review else []), 21 | *args.args, 22 | ] 23 | 24 | print("Running:", shlex.join(cmd)) 25 | 26 | subprocess.run(cmd, check=True) 27 | -------------------------------------------------------------------------------- /pkgs/minifakeroot/default.nix: -------------------------------------------------------------------------------- 1 | { stdenv }: 2 | let 3 | varName = if stdenv.isDarwin then "DYLD_INSERT_LIBRARIES" else "LD_PRELOAD"; 4 | linkerFlags = if stdenv.isDarwin then "-dynamiclib" else "-shared"; 5 | sharedLibrary = stdenv.hostPlatform.extensions.sharedLibrary; 6 | in 7 | stdenv.mkDerivation { 8 | name = "minifakeroot"; 9 | dontUnpack = true; 10 | installPhase = '' 11 | mkdir -p $out/lib 12 | $CC ${linkerFlags} -o $out/lib/libfakeroot${sharedLibrary} ${./main.c} 13 | mkdir -p $out/share/minifakeroot 14 | cat > $out/share/minifakeroot/rc < None: 8 | init_state(args.certificate.read_text(), args.key.read_text()) 9 | print("Finished initializing moonlight state.") 10 | 11 | 12 | def register_config_initialization_parser(parser: argparse.ArgumentParser) -> None: 13 | parser.add_argument("--certificate", type=Path) 14 | parser.add_argument("--key", type=Path) 15 | parser.set_defaults(func=init_config) 16 | -------------------------------------------------------------------------------- /pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/moonlight/uri.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse 2 | 3 | from moonlight_sunshine_accept.errors import Error 4 | 5 | 6 | def parse_moonlight_uri(uri: str) -> tuple[str, int | None]: 7 | print(uri) 8 | if uri.startswith("moonlight:"): 9 | # Fixes a bug where moonlight:// is not parsed correctly 10 | uri = uri[10:] 11 | uri = "moonlight://" + uri 12 | print(uri) 13 | parsed = urlparse(uri) 14 | if parsed.scheme != "moonlight": 15 | msg = f"Invalid moonlight URI: {uri}" 16 | raise Error(msg) 17 | hostname = parsed.hostname 18 | if hostname is None: 19 | msg = f"Invalid moonlight URI: {uri}" 20 | raise Error(msg) 21 | port = parsed.port 22 | return (hostname, port) 23 | -------------------------------------------------------------------------------- /pkgs/moonlight-sunshine-accept/moonlight_sunshine_accept/sunshine/init_state.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from .state import init_state 4 | 5 | 6 | def init_state_file(args: argparse.Namespace) -> None: 7 | uuid = args.uuid 8 | state_file = args.state_file 9 | init_state(uuid, state_file) 10 | print("Finished initializing sunshine state file.") 11 | 12 | 13 | def register_state_initialization_parser(parser: argparse.ArgumentParser) -> None: 14 | parser.add_argument("--uuid") 15 | parser.add_argument("--state-file") 16 | parser.set_defaults(func=init_state_file) 17 | -------------------------------------------------------------------------------- /pkgs/moonlight-sunshine-accept/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "moonlight-sunshine-accept" 7 | description = "Moonlight Sunshine Bridge" 8 | dynamic = ["version"] 9 | scripts = { moonlight-sunshine-accept = "moonlight_sunshine_accept:main" } 10 | -------------------------------------------------------------------------------- /pkgs/pending-reviews/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | writeShellApplication, 3 | bash, 4 | curl, 5 | }: 6 | writeShellApplication { 7 | name = "pending-reviews"; 8 | runtimeInputs = [ 9 | bash 10 | curl 11 | ]; 12 | text = builtins.readFile ./script.sh; 13 | } 14 | -------------------------------------------------------------------------------- /pkgs/pending-reviews/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | display_pr() { jq -r '.[] | "- \(.url) | \(.title) from @\(.user.login)"'; } 5 | 6 | echo "# Review needed" 7 | curl -s 'https://git.clan.lol/api/v1/repos/clan/clan-core/pulls?state=closed&sort=leastupdate&labels=8' | display_pr 8 | 9 | echo "# Changes requested" 10 | curl -s 'https://git.clan.lol/api/v1/repos/clan/clan-core/pulls?sort=leastupdate&labels=9' | display_pr 11 | -------------------------------------------------------------------------------- /pkgs/tea-create-pr/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | writeShellApplication, 3 | bash, 4 | coreutils, 5 | git, 6 | tea, 7 | openssh, 8 | }: 9 | writeShellApplication { 10 | name = "tea-create-pr"; 11 | runtimeInputs = [ 12 | bash 13 | coreutils 14 | git 15 | tea 16 | openssh 17 | ]; 18 | text = builtins.readFile ./script.sh; 19 | } 20 | -------------------------------------------------------------------------------- /pkgs/testing/flake-module.nix: -------------------------------------------------------------------------------- 1 | { 2 | perSystem = { 3 | legacyPackages.setupNixInNix = '' 4 | export HOME=$TMPDIR 5 | export NIX_STATE_DIR=$TMPDIR/nix 6 | export IN_NIX_SANDBOX=1 7 | export CLAN_TEST_STORE=$TMPDIR/store 8 | # required to prevent concurrent 'nix flake lock' operations 9 | export LOCK_NIX=$TMPDIR/nix_lock 10 | mkdir -p "$CLAN_TEST_STORE/nix/store" 11 | mkdir -p "$CLAN_TEST_STORE/nix/var/nix/gcroots" 12 | if [[ -n "''${closureInfo-}" ]]; then 13 | xargs cp --recursive --target "$CLAN_TEST_STORE/nix/store" < "$closureInfo/store-paths" 14 | nix-store --load-db --store "$CLAN_TEST_STORE" < "$closureInfo/registration" 15 | fi 16 | ''; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/zerotier-members/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenv, 3 | python3, 4 | lib, 5 | }: 6 | 7 | stdenv.mkDerivation { 8 | name = "zerotier-members"; 9 | src = ./.; 10 | buildInputs = [ python3 ]; 11 | installPhase = '' 12 | install -Dm755 ${./zerotier-members.py} $out/bin/zerotier-members 13 | ''; 14 | meta = with lib; { 15 | description = "A tool to list/allow members of a ZeroTier network"; 16 | license = licenses.mit; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/zerotierone/default.nix: -------------------------------------------------------------------------------- 1 | { zerotierone, lib }: 2 | # halalify zerotierone 3 | zerotierone.overrideAttrs (_old: { 4 | meta = _old.meta // { 5 | license = lib.licenses.apsl20; 6 | }; 7 | }) 8 | -------------------------------------------------------------------------------- /pkgs/zt-tcp-relay/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | rustPlatform, 4 | fetchFromGitHub, 5 | }: 6 | 7 | rustPlatform.buildRustPackage { 8 | pname = "zt-tcp-relay"; 9 | version = "unstable-2023-07-11"; 10 | 11 | src = fetchFromGitHub { 12 | owner = "alexander-akhmetov"; 13 | repo = "zt-tcp-relay"; 14 | rev = "b8d3a892c60581e938724a1d5dfb0e1884a9fa6f"; 15 | hash = "sha256-7ZNWyPf/b4dJqyPQFTBrv2RvY9dDz990CvwcHpaCKSA="; 16 | }; 17 | 18 | useFetchCargoVendor = true; 19 | cargoHash = "sha256-gGKiPmvDJFiUec1RHI8D2QwOxL2kyHz49tEmyjetXpw="; 20 | 21 | meta = with lib; { 22 | description = "ZeroTier One TCP relay"; 23 | homepage = "https://github.com/alexander-akhmetov/zt-tcp-relay"; 24 | license = licenses.mit; 25 | maintainers = with maintainers; [ mic92 ]; 26 | mainProgram = "zt-tcp-relay"; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /reference/cli/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clan-lol/clan-core/51b28a92657b099fcc874b1c0bedf9811b2e9f07/reference/cli/index.md -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended"], 4 | "lockFileMaintenance": { "enabled": true }, 5 | "nix": { 6 | "enabled": true 7 | }, 8 | "packageRules": [ 9 | { 10 | "matchManagers": ["npm"], 11 | "matchPaths": ["pkgs/clan-app/ui/**"], 12 | "enabled": false 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /sops/machines/test-backup/key.json: -------------------------------------------------------------------------------- 1 | { 2 | "publickey": "age1ez6xlcxl5k2uekcjvsu5wjca29f0j3lml0kq8fnvnkugvnj4pyjsyzuc93", 3 | "type": "age" 4 | } 5 | -------------------------------------------------------------------------------- /templates/clan/flake-parts/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | use flake 3 | -------------------------------------------------------------------------------- /templates/clan/flake-parts/modules/shared.nix: -------------------------------------------------------------------------------- 1 | { 2 | clan-core, 3 | # Optional, if you want to access other flakes: 4 | # self, 5 | ... 6 | }: 7 | { 8 | imports = [ 9 | clan-core.clanModules.sshd 10 | clan-core.clanModules.root-password 11 | # You can access other flakes imported in your flake via `self` like this: 12 | # self.inputs.nix-index-database.nixosModules.nix-index 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /templates/clan/minimal-flake-parts/.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | .direnv/ 3 | -------------------------------------------------------------------------------- /templates/clan/minimal-flake-parts/checks.nix: -------------------------------------------------------------------------------- 1 | { self, ... }: 2 | { 3 | perSystem = 4 | { 5 | self', 6 | lib, 7 | system, 8 | ... 9 | }: 10 | { 11 | checks = 12 | let 13 | nixosMachines = lib.mapAttrs' ( 14 | name: config: lib.nameValuePair "nixos-${name}" config.config.system.build.toplevel 15 | ) ((lib.filterAttrs (_: config: config.pkgs.system == system)) self.nixosConfigurations); 16 | 17 | packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages; 18 | devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells; 19 | in 20 | nixosMachines // packages // devShells; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /templates/clan/minimal-flake-parts/clan.nix: -------------------------------------------------------------------------------- 1 | { self, inputs, ... }: 2 | { 3 | imports = [ 4 | inputs.clan.flakeModules.default 5 | ]; 6 | clan = { 7 | meta.name = "__CHANGE_ME__"; 8 | inherit self; 9 | specialArgs = { 10 | inherit inputs; 11 | }; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /templates/clan/minimal-flake-parts/devshells.nix: -------------------------------------------------------------------------------- 1 | _: { 2 | perSystem = 3 | { 4 | pkgs, 5 | inputs', 6 | ... 7 | }: 8 | { 9 | devShells = { 10 | default = pkgs.mkShellNoCC { 11 | packages = [ 12 | inputs'.clan.packages.default 13 | ]; 14 | }; 15 | }; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /templates/clan/minimal-flake-parts/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | 3 | inputs = { 4 | clan.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; 5 | nixpkgs.follows = "clan/nixpkgs"; 6 | 7 | flake-parts.url = "github:hercules-ci/flake-parts"; 8 | flake-parts.inputs.nixpkgs-lib.follows = "clan/nixpkgs"; 9 | }; 10 | 11 | outputs = 12 | inputs@{ flake-parts, ... }: 13 | flake-parts.lib.mkFlake { inherit inputs; } ( 14 | { ... }: 15 | { 16 | systems = [ 17 | "x86_64-linux" 18 | "aarch64-linux" 19 | "x86_64-darwin" 20 | "aarch64-darwin" 21 | ]; 22 | 23 | imports = [ 24 | ./checks.nix 25 | ./clan.nix 26 | ./devshells.nix 27 | ./formatter.nix 28 | ]; 29 | } 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /templates/clan/minimal-flake-parts/formatter.nix: -------------------------------------------------------------------------------- 1 | _: { 2 | perSystem = 3 | { 4 | pkgs, 5 | ... 6 | }: 7 | { 8 | formatter = pkgs.nixfmt; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /templates/clan/minimal/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"; 3 | inputs.nixpkgs.follows = "clan-core/nixpkgs"; 4 | 5 | outputs = 6 | { self, clan-core, ... }: 7 | let 8 | # Usage see: https://docs.clan.lol 9 | clan = clan-core.clanLib.buildClan { inherit self; }; 10 | in 11 | { 12 | # all machines managed by Clan 13 | inherit (clan) nixosConfigurations clanInternals; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /templates/clan/minimal/inventory.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { "name": "__CHANGE_ME__" }, 3 | "machines": {}, 4 | "services": {} 5 | } 6 | -------------------------------------------------------------------------------- /templates/clan/new-clan/.clan-flake: -------------------------------------------------------------------------------- 1 | # DO NOT DELETE 2 | # This file is used by the clan cli to discover a clan flake 3 | -------------------------------------------------------------------------------- /templates/clan/new-clan/.envrc: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | use flake 3 | -------------------------------------------------------------------------------- /templates/clan/new-clan/modules/gnome.nix: -------------------------------------------------------------------------------- 1 | { 2 | services.xserver.enable = true; 3 | services.xserver.desktopManager.gnome.enable = true; 4 | services.xserver.displayManager.gdm.enable = true; 5 | } 6 | -------------------------------------------------------------------------------- /templates/disk/single-disk/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description = "Simple single disk schema" 3 | --- 4 | # Description 5 | 6 | This schema defines a GPT-based disk layout. 7 | 8 | ### **Disk Overview** 9 | 10 | - **Name**: `main-{{uuid}}` 11 | - **Device**: `{{mainDisk}}` 12 | 13 | ### **Partitions** 14 | 15 | 1. **EFI System Partition (ESP)** 16 | - Size: `500M`. 17 | - Filesystem: `vfat`. 18 | - Mount Point: `/boot` (secure `umask=0077`). 19 | 20 | 2. **Root Partition** 21 | - Size: Remaining disk space (`100%`). 22 | - Filesystem: `ext4`. 23 | - Mount Point: `/`. 24 | -------------------------------------------------------------------------------- /templates/machine/flash-installer/configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | clan-core, 4 | inputs, 5 | ... 6 | }: 7 | { 8 | imports = [ 9 | ./disko.nix 10 | clan-core.nixosModules.installer 11 | clan-core.clanModules.trusted-nix-caches 12 | clan-core.clanModules.disk-id 13 | clan-core.clanModules.iwd 14 | ]; 15 | 16 | clan.core.deployment.requireExplicitUpdate = true; 17 | 18 | nixpkgs.pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux; 19 | system.stateVersion = config.system.nixos.release; 20 | } 21 | -------------------------------------------------------------------------------- /templates/machine/new-machine/configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | 4 | ]; 5 | 6 | # New machine! 7 | } 8 | -------------------------------------------------------------------------------- /vars/per-machine/test-backup/openssh/ssh.id_ed25519.pub/value: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILnWhu8zt/mD+TlIKr8Req4c+BCgqYuDOcZfmzj6kflF nixbld@turingmachine 2 | -------------------------------------------------------------------------------- /vars/per-machine/test-backup/openssh/ssh.id_ed25519/machines/test-backup: -------------------------------------------------------------------------------- 1 | ../../../../../../sops/machines/test-backup --------------------------------------------------------------------------------