Compare commits
39 Commits
clan/cli/f
...
init/manpa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
165c04f4bf | ||
|
|
132066b319 | ||
|
|
d138e29a53 | ||
|
|
a7febba9c8 | ||
|
|
f0f97baa65 | ||
|
|
c2dc94507e | ||
|
|
7c0aaab463 | ||
|
|
5dcac604d1 | ||
|
|
96746b7c98 | ||
|
|
2ae50b7398 | ||
|
|
3c905c5072 | ||
|
|
5b926f57cc | ||
|
|
b9788a5dba | ||
|
|
7078f09872 | ||
|
|
1aa7808c02 | ||
|
|
ba8a51101d | ||
|
|
de69c970aa | ||
|
|
fe5fa6a85d | ||
|
|
de74febf64 | ||
|
|
3b6483e819 | ||
|
|
dcd6ad0983 | ||
|
|
567d979243 | ||
|
|
c81a8681b0 | ||
|
|
31cde90819 | ||
|
|
a77bf5bf21 | ||
|
|
4befa80eb8 | ||
|
|
52584662a8 | ||
|
|
de147f63e9 | ||
|
|
96c33dec7a | ||
|
|
3c0b5f0867 | ||
|
|
c252f11c1f | ||
|
|
f1f040397d | ||
|
|
418e9937cb | ||
|
|
c34664429c | ||
|
|
6fe5928297 | ||
|
|
eee99730d1 | ||
|
|
9394760e3b | ||
|
|
a0b0e1a0ac | ||
|
|
e2d7e6e86c |
@@ -145,14 +145,14 @@
|
||||
machine.succeed("echo testing > /var/test-backups/somefile")
|
||||
|
||||
# create
|
||||
machine.succeed("clan --debug --flake ${self} backups create test-backup")
|
||||
machine.succeed("clan backups create --debug --flake ${self} test-backup")
|
||||
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
|
||||
machine.succeed("test -f /run/mount-external-disk")
|
||||
machine.succeed("test -f /run/unmount-external-disk")
|
||||
|
||||
# list
|
||||
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
|
||||
out = machine.succeed("clan --debug --flake ${self} backups list test-backup").strip()
|
||||
out = machine.succeed("clan backups list --debug --flake ${self} test-backup").strip()
|
||||
print(out)
|
||||
assert backup_id in out, f"backup {backup_id} not found in {out}"
|
||||
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
|
||||
@@ -160,14 +160,14 @@
|
||||
|
||||
## borgbackup restore
|
||||
machine.succeed("rm -f /var/test-backups/somefile")
|
||||
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
||||
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
||||
machine.succeed("test -f /var/test-service/pre-restore-command")
|
||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||
|
||||
## localbackup restore
|
||||
machine.succeed("rm -f /var/test-backups/somefile /var/test-service/{pre,post}-restore-command")
|
||||
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup localbackup '{localbackup_id}' >&2")
|
||||
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup localbackup '{localbackup_id}' >&2")
|
||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
||||
machine.succeed("test -f /var/test-service/pre-restore-command")
|
||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||
|
||||
@@ -1,33 +1,58 @@
|
||||
{ ... }:
|
||||
{ self, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{ ... }:
|
||||
{
|
||||
# checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||
# flash = (import ../lib/test-base.nix) {
|
||||
# name = "flash";
|
||||
# nodes.target = {
|
||||
# virtualisation.emptyDiskImages = [ 4096 ];
|
||||
# virtualisation.memorySize = 3000;
|
||||
# environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||
# environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||
nodes,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
self
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.jq
|
||||
pkgs.disko
|
||||
pkgs.stdenvNoCC.drvPath
|
||||
pkgs.openssl
|
||||
pkgs.curl
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.toplevel
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.pkgs.disko
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
# Currently disabled...
|
||||
checks = pkgs.lib.mkIf (false && pkgs.stdenv.isLinux) {
|
||||
flash = (import ../lib/test-base.nix) {
|
||||
name = "flash";
|
||||
nodes.target = {
|
||||
virtualisation.emptyDiskImages = [ 4096 ];
|
||||
virtualisation.memorySize = 3000;
|
||||
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||
|
||||
# nix.settings = {
|
||||
# substituters = lib.mkForce [ ];
|
||||
# hashed-mirrors = null;
|
||||
# connect-timeout = lib.mkForce 3;
|
||||
# flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
# experimental-features = [
|
||||
# "nix-command"
|
||||
# "flakes"
|
||||
# ];
|
||||
# };
|
||||
# };
|
||||
# testScript = ''
|
||||
# start_all()
|
||||
# machine.succeed("clan --debug --flake ${../..} flash --yes --disk main /dev/vdb test_install_machine")
|
||||
# '';
|
||||
# } { inherit pkgs self; };
|
||||
# };
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("nix-store --verify-path ${
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript
|
||||
}")
|
||||
machine.execute("timeout 30 clan flash --debug --flake ${../..} --yes --disk main /dev/vdb test_install_machine")
|
||||
'';
|
||||
} { inherit pkgs self; };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
clan.machines.test_install_machine = {
|
||||
clan.networking.targetHost = "test_install_machine";
|
||||
fileSystems."/".device = lib.mkDefault "/dev/null";
|
||||
boot.loader.grub.device = lib.mkDefault "/dev/null";
|
||||
fileSystems."/".device = lib.mkDefault "/dev/vdb";
|
||||
boot.loader.grub.device = lib.mkDefault "/dev/vdb";
|
||||
|
||||
imports = [ self.nixosModules.test_install_machine ];
|
||||
};
|
||||
@@ -98,7 +98,7 @@
|
||||
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
||||
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
||||
|
||||
client.succeed("clan --debug --flake ${../..} machines install --yes test_install_machine root@target >&2")
|
||||
client.succeed("clan machines install --debug --flake ${../..} --yes test_install_machine root@target >&2")
|
||||
try:
|
||||
target.shutdown()
|
||||
except BrokenPipeError:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# - cli frontend: https://github.com/localsend/localsend/issues/11
|
||||
# - ipv6 support: https://github.com/localsend/localsend/issues/549
|
||||
options.clan.localsend = {
|
||||
enable = lib.mkEnableOption (lib.mdDoc "enable the localsend module");
|
||||
enable = lib.mkEnableOption "enable the localsend module";
|
||||
defaultLocation = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "The default download location";
|
||||
|
||||
4
docs/.gitignore
vendored
4
docs/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/site/reference
|
||||
/site/reference
|
||||
/site/static/Roboto-Regular.ttf
|
||||
/site/static/FiraCode-VF.ttf
|
||||
@@ -22,9 +22,9 @@ Let's get your development environment up and running:
|
||||
|
||||
2. **Install direnv**:
|
||||
|
||||
- Download the direnv package from [here](https://direnv.net/docs/installation.html) or run the following command:
|
||||
- To automatically setup a devshell on entering the directory
|
||||
```bash
|
||||
curl -sfL https://direnv.net/install.sh | bash
|
||||
nix profile install nixpkgs#nix-direnv-flakes
|
||||
```
|
||||
|
||||
3. **Add direnv to your shell**:
|
||||
@@ -36,9 +36,14 @@ Let's get your development environment up and running:
|
||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||
```
|
||||
|
||||
4. **Clone the Repository and Navigate**:
|
||||
|
||||
- Clone this repository and navigate to it.
|
||||
4. **Create a Gitea Account**:
|
||||
- Register an account on https://git.clan.lol
|
||||
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
||||
- Clone the repository and navigate to it
|
||||
- Add a new remote called upstream:
|
||||
```bash
|
||||
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||
```
|
||||
|
||||
5. **Allow .envrc**:
|
||||
|
||||
@@ -48,59 +53,68 @@ Let's get your development environment up and running:
|
||||
```
|
||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||
|
||||
# Setting Up Your Git Workflow
|
||||
|
||||
Let's set up your Git workflow to collaborate effectively:
|
||||
|
||||
1. **Register Your Gitea Account Locally**:
|
||||
|
||||
- Execute the following command to add your Gitea account locally:
|
||||
```bash
|
||||
tea login add
|
||||
```
|
||||
- Fill out the prompt as follows:
|
||||
- URL of Gitea instance: `https://git.clan.lol`
|
||||
- Name of new Login [gitea.gchq.icu]: `gitea.gchq.icu:7171`
|
||||
- Do you have an access token? No
|
||||
- Username: YourUsername
|
||||
- Password: YourPassword
|
||||
- Set Optional settings: No
|
||||
|
||||
2. **Git Workflow**:
|
||||
|
||||
1. Add your changes to Git using `git add <file1> <file2>`.
|
||||
2. Run `nix fmt` to lint your files.
|
||||
3. Commit your changes with a descriptive message: `git commit -a -m "My descriptive commit message"`.
|
||||
4. Make sure your branch has the latest changes from upstream by executing:
|
||||
6. **(Optional) Install Git Hooks**:
|
||||
- To syntax check your code you can run:
|
||||
```bash
|
||||
git fetch && git rebase origin/main --autostash
|
||||
nix fmt
|
||||
```
|
||||
- To make this automatic install the git hooks
|
||||
```bash
|
||||
./scripts/pre-commit
|
||||
```
|
||||
5. Use `git status` to check for merge conflicts.
|
||||
6. If conflicts exist, resolve them. Here's a tutorial for resolving conflicts in [VSCode](https://code.visualstudio.com/docs/sourcecontrol/overview#_merge-conflicts).
|
||||
7. After resolving conflicts, execute `git merge --continue` and repeat step 5 until there are no conflicts.
|
||||
|
||||
3. **Create a Pull Request**:
|
||||
|
||||
- To automatically open a pull request that gets merged if all tests pass, execute:
|
||||
```bash
|
||||
merge-after-ci
|
||||
```
|
||||
|
||||
4. **Review Your Pull Request**:
|
||||
|
||||
- Visit https://git.clan.lol and go to the project page. Check under "Pull Requests" for any issues with your pull request.
|
||||
|
||||
5. **Push Your Changes**:
|
||||
- If there are issues, fix them and redo step 2. Afterward, execute:
|
||||
```bash
|
||||
git push origin HEAD:YourUsername-main
|
||||
```
|
||||
- This will directly push to your open pull request.
|
||||
7. **Open a Pull Request**:
|
||||
- Go to the webinterface and open up a pull request
|
||||
|
||||
# Debugging
|
||||
|
||||
Here are some methods for debugging and testing the clan-cli:
|
||||
|
||||
## See all possible packages and tests
|
||||
|
||||
To quickly show all possible packages and tests execute:
|
||||
|
||||
```bash
|
||||
nix flake show --system no-eval
|
||||
```
|
||||
|
||||
Under `checks` you will find all tests that are executed in our CI. Under `packages` you find all our projects.
|
||||
|
||||
```
|
||||
git+file:///home/lhebendanz/Projects/clan-core
|
||||
├───apps
|
||||
│ └───x86_64-linux
|
||||
│ ├───install-vm: app
|
||||
│ └───install-vm-nogui: app
|
||||
├───checks
|
||||
│ └───x86_64-linux
|
||||
│ ├───borgbackup omitted (use '--all-systems' to show)
|
||||
│ ├───check-for-breakpoints omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-age omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-bash omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-e2fsprogs omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-fakeroot omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-git omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-nix omitted (use '--all-systems' to show)
|
||||
│ ├───clan-dep-openssh omitted (use '--all-systems' to show)
|
||||
│ ├───"clan-dep-python3.11-mypy" omitted (use '--all-systems' to show)
|
||||
├───packages
|
||||
│ └───x86_64-linux
|
||||
│ ├───clan-cli omitted (use '--all-systems' to show)
|
||||
│ ├───clan-cli-docs omitted (use '--all-systems' to show)
|
||||
│ ├───clan-ts-api omitted (use '--all-systems' to show)
|
||||
│ ├───clan-vm-manager omitted (use '--all-systems' to show)
|
||||
│ ├───default omitted (use '--all-systems' to show)
|
||||
│ ├───deploy-docs omitted (use '--all-systems' to show)
|
||||
│ ├───docs omitted (use '--all-systems' to show)
|
||||
│ ├───editor omitted (use '--all-systems' to show)
|
||||
└───templates
|
||||
├───default: template: Initialize a new clan flake
|
||||
└───new-clan: template: Initialize a new clan flake
|
||||
```
|
||||
|
||||
You can execute every test separately by following the tree path `nix build .#checks.x86_64-linux.clan-pytest` for example.
|
||||
|
||||
## Test Locally in Devshell with Breakpoints
|
||||
|
||||
To test the cli locally in a development environment and set breakpoints for debugging, follow these steps:
|
||||
@@ -150,12 +164,14 @@ If you need to inspect the Nix sandbox while running tests, follow these steps:
|
||||
2. Use `cntr` and `psgrep` to attach to the Nix sandbox. This allows you to interactively debug your code while it's paused. For example:
|
||||
|
||||
```bash
|
||||
cntr exec -w your_sandbox_name
|
||||
psgrep -a -x your_python_process_name
|
||||
cntr attach <container id, container name or process id>
|
||||
```
|
||||
|
||||
Or you can also use the [nix breakpoint hook](https://nixos.org/manual/nixpkgs/stable/#breakpointhook)
|
||||
|
||||
|
||||
# Standards
|
||||
|
||||
Every new module name should be in kebab-case.
|
||||
Every fact definition, where possible should be in kebab-case.
|
||||
- Every new module name should be in kebab-case.
|
||||
- Every fact definition, where possible should be in kebab-case.
|
||||
|
||||
@@ -39,7 +39,7 @@ exclude_docs: |
|
||||
|
||||
nav:
|
||||
- Blog:
|
||||
- blog/index.md
|
||||
- blog/index.md
|
||||
- Getting started:
|
||||
- index.md
|
||||
- Installer: getting-started/installer.md
|
||||
@@ -94,6 +94,7 @@ docs_dir: site
|
||||
site_dir: out
|
||||
|
||||
theme:
|
||||
font: false
|
||||
logo: https://clan.lol/static/logo/clan-white.png
|
||||
favicon: https://clan.lol/static/logo/clan-dark.png
|
||||
name: material
|
||||
@@ -105,8 +106,6 @@ theme:
|
||||
- content.tabs.link
|
||||
icon:
|
||||
repo: fontawesome/brands/git
|
||||
font:
|
||||
code: Roboto Mono
|
||||
|
||||
palette:
|
||||
# Palette toggle for light mode
|
||||
@@ -128,6 +127,7 @@ theme:
|
||||
name: Switch to light mode
|
||||
|
||||
extra_css:
|
||||
- static/extra.css
|
||||
- static/asciinema-player/custom-theme.css
|
||||
- static/asciinema-player/asciinema-player.css
|
||||
|
||||
@@ -142,7 +142,6 @@ extra:
|
||||
- icon: fontawesome/solid/rss
|
||||
link: /feed_rss_created.xml
|
||||
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- blog
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
clan-cli-docs,
|
||||
asciinema-player-js,
|
||||
asciinema-player-css,
|
||||
roboto,
|
||||
fira-code,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -33,6 +35,10 @@ pkgs.stdenv.mkDerivation {
|
||||
mkdir -p ./site/static/asciinema-player
|
||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||
|
||||
# Link to fonts
|
||||
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||
ln -snf ${fira-code}/share/fonts/truetype/FiraCode-VF.ttf ./site/static/
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
|
||||
@@ -40,13 +40,14 @@ def sanitize(text: str) -> str:
|
||||
return text.replace(">", "\\>")
|
||||
|
||||
|
||||
def replace_store_path(text: str) -> Path:
|
||||
def replace_store_path(text: str) -> tuple[str, str]:
|
||||
res = text
|
||||
if text.startswith("/nix/store/"):
|
||||
res = "https://git.clan.lol/clan/clan-core/src/branch/main/" + str(
|
||||
Path(*Path(text).parts[4:])
|
||||
)
|
||||
return Path(res)
|
||||
name = Path(res).name
|
||||
return (res, name)
|
||||
|
||||
|
||||
def render_option_header(name: str) -> str:
|
||||
@@ -108,9 +109,10 @@ def render_option(name: str, option: dict[str, Any], level: int = 3) -> str:
|
||||
"""
|
||||
|
||||
decls = option.get("declarations", [])
|
||||
source_path = replace_store_path(decls[0])
|
||||
source_path, name = replace_store_path(decls[0])
|
||||
print(source_path, name)
|
||||
res += f"""
|
||||
:simple-git: [{source_path.name}]({source_path})
|
||||
:simple-git: [{name}]({source_path})
|
||||
"""
|
||||
res += "\n"
|
||||
|
||||
@@ -160,7 +162,7 @@ def produce_clan_core_docs() -> None:
|
||||
for option_name, info in options.items():
|
||||
outfile = f"{module_name}/index.md"
|
||||
|
||||
# Create seperate files for nested options
|
||||
# Create separate files for nested options
|
||||
if len(option_name.split(".")) <= 2:
|
||||
# i.e. clan-core.clanDir
|
||||
output = core_outputs.get(
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
clan-cli-docs,
|
||||
asciinema-player-js,
|
||||
asciinema-player-css,
|
||||
roboto,
|
||||
fira-code,
|
||||
...
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
@@ -18,7 +20,12 @@ pkgs.mkShell {
|
||||
echo "Generated API documentation in './site/reference/' "
|
||||
|
||||
mkdir -p ./site/static/asciinema-player
|
||||
|
||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||
|
||||
# Link to fonts
|
||||
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||
ln -snf ${fira-code}/share/fonts/truetype/FiraCode-VF.ttf ./site/static/
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ sudo umount /dev/sdb1
|
||||
It also includes the language and keymap currently used into the installer image.
|
||||
|
||||
```bash
|
||||
clan --flake git+https://git.clan.lol/clan/clan-core flash flash-installer --disk main /dev/sd<X>
|
||||
clan flash --flake git+https://git.clan.lol/clan/clan-core flash-installer --disk main /dev/sd<X>
|
||||
```
|
||||
|
||||
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
|
||||
|
||||
@@ -106,17 +106,20 @@ In your nixos configuration you can get a path to secrets like this `config.sops
|
||||
|
||||
### Assigning Access
|
||||
|
||||
By default, secrets are encrypted for your key. To specify which users and machines can access a secret:
|
||||
When using `clan secrets set <secret>` without arguments, secrets are encrypted for the key of the user named like your current $USER.
|
||||
|
||||
```bash
|
||||
clan secrets set --machine <machine1> --machine <machine2> --user <user1> --user <user2> <secret_name>
|
||||
```
|
||||
You can also just add machines/users to existing secrets:
|
||||
To add machines/users to an existing secret use:
|
||||
|
||||
```bash
|
||||
clan secrets machines add-secret <machine_name> <secret_name>
|
||||
```
|
||||
|
||||
Alternatively specify users and machines while creating a secret:
|
||||
|
||||
```bash
|
||||
clan secrets set --machine <machine1> --machine <machine2> --user <user1> --user <user2> <secret_name>
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
In this section we go into more advanced secret management topics.
|
||||
@@ -188,11 +191,9 @@ Since our clan secret module will auto-import secrets that are encrypted for a p
|
||||
you can now remove `sops.secrets.<secrets> = { };` unless you need to specify more options for the secret like owner/group of the secret file.
|
||||
|
||||
|
||||
|
||||
## Indepth Explanation
|
||||
|
||||
|
||||
|
||||
The secrets system conceptually knows two different entities:
|
||||
|
||||
- **Machine**: consumes secrets
|
||||
|
||||
13
docs/site/static/extra.css
Normal file
13
docs/site/static/extra.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@font-face {
|
||||
font-family: "Roboto";
|
||||
src: url(./Roboto-Regular.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src: url(./FiraCode-VF.ttf) format('truetype');
|
||||
}
|
||||
|
||||
:root {
|
||||
--md-text-font: "Roboto";
|
||||
--md-code-font: "Fira Code";
|
||||
}
|
||||
@@ -122,7 +122,7 @@ in
|
||||
cores = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 1;
|
||||
description = lib.mdDoc ''
|
||||
description = ''
|
||||
Specify the number of cores the guest is permitted to use.
|
||||
The number can be higher than the available cores on the
|
||||
host system.
|
||||
@@ -132,7 +132,7 @@ in
|
||||
memorySize = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 1024;
|
||||
description = lib.mdDoc ''
|
||||
description = ''
|
||||
The memory size in megabytes of the virtual machine.
|
||||
'';
|
||||
};
|
||||
@@ -140,7 +140,7 @@ in
|
||||
graphics = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = lib.mdDoc ''
|
||||
description = ''
|
||||
Whether to run QEMU with a graphics window, or in nographic mode.
|
||||
Serial console will be enabled on both settings, but this will
|
||||
change the preferred console.
|
||||
@@ -150,7 +150,7 @@ in
|
||||
waypipe = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = lib.mdDoc ''
|
||||
description = ''
|
||||
Whether to use waypipe for native wayland passthrough, or not.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ in
|
||||
};
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
description = lib.mdDoc "override the network config in /var/lib/zerotier/bla/$network.json";
|
||||
description = "override the network config in /var/lib/zerotier/bla/$network.json";
|
||||
type = lib.types.submodule { freeformType = (pkgs.formats.json { }).type; };
|
||||
};
|
||||
};
|
||||
|
||||
0
pkgs/clan-cli/clan.1
Normal file
0
pkgs/clan-cli/clan.1
Normal file
@@ -51,19 +51,14 @@ class AppendOptionAction(argparse.Action):
|
||||
lst.append(values[1])
|
||||
|
||||
|
||||
def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=prog,
|
||||
description="The clan cli tool.",
|
||||
epilog=(
|
||||
"""
|
||||
Online reference for the clan cli tool: https://docs.clan.lol/reference/cli/
|
||||
For more detailed information, visit: https://docs.clan.lol
|
||||
"""
|
||||
),
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
def flake_path(arg: str) -> str | Path:
|
||||
flake_dir = Path(arg).resolve()
|
||||
if flake_dir.exists() and flake_dir.is_dir():
|
||||
return flake_dir
|
||||
return arg
|
||||
|
||||
|
||||
def add_common_flags(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
help="Enable debug logging",
|
||||
@@ -80,12 +75,6 @@ For more detailed information, visit: https://docs.clan.lol
|
||||
default=[],
|
||||
)
|
||||
|
||||
def flake_path(arg: str) -> str | Path:
|
||||
flake_dir = Path(arg).resolve()
|
||||
if flake_dir.exists() and flake_dir.is_dir():
|
||||
return flake_dir
|
||||
return arg
|
||||
|
||||
parser.add_argument(
|
||||
"--flake",
|
||||
help="path to the flake where the clan resides in, can be a remote flake or local, can be set through the [CLAN_DIR] environment variable",
|
||||
@@ -94,6 +83,30 @@ For more detailed information, visit: https://docs.clan.lol
|
||||
type=flake_path,
|
||||
)
|
||||
|
||||
|
||||
def register_common_flags(parser: argparse.ArgumentParser) -> None:
|
||||
has_subparsers = False
|
||||
for action in parser._actions:
|
||||
if isinstance(action, argparse._SubParsersAction):
|
||||
for choice, child_parser in action.choices.items():
|
||||
has_subparsers = True
|
||||
register_common_flags(child_parser)
|
||||
if not has_subparsers:
|
||||
add_common_flags(parser)
|
||||
|
||||
|
||||
def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=prog,
|
||||
description="The clan cli tool.",
|
||||
epilog=(
|
||||
"""
|
||||
Online reference for the clan cli tool: https://docs.clan.lol/reference/cli/
|
||||
For more detailed information, visit: https://docs.clan.lol
|
||||
"""
|
||||
),
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
parser_backups = subparsers.add_parser(
|
||||
@@ -156,6 +169,7 @@ For more detailed information, visit: https://docs.clan.lol/getting-started
|
||||
parser_ssh = subparsers.add_parser(
|
||||
"ssh",
|
||||
help="ssh to a remote machine",
|
||||
description="ssh to a remote machine",
|
||||
epilog=(
|
||||
"""
|
||||
This subcommand allows seamless ssh access to the nixos-image builders.
|
||||
@@ -207,7 +221,7 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/secr
|
||||
|
||||
This subcommand provides an interface to facts of clan machines.
|
||||
Facts are artifacts that a service can generate.
|
||||
There are public and secret facts.
|
||||
There are public and secret facts.
|
||||
Public facts can be referenced by other machines directly.
|
||||
Public facts can include: ip addresses, public keys.
|
||||
Secret facts can include: passwords, private keys.
|
||||
@@ -222,7 +236,7 @@ Examples:
|
||||
|
||||
$ clan facts generate
|
||||
Will generate facts for all machines.
|
||||
|
||||
|
||||
$ clan facts generate --service [SERVICE] --regenerate
|
||||
Will regenerate facts, if they are already generated for a specific service.
|
||||
This is especially useful for resetting certain passwords while leaving the rest
|
||||
@@ -249,7 +263,7 @@ Examples:
|
||||
List all the machines managed by clan.
|
||||
|
||||
$ clan machines update [MACHINES]
|
||||
Will update the specified machine [MACHINE], if [MACHINE] is omitted, the command
|
||||
Will update the specified machine [MACHINE], if [MACHINE] is omitted, the command
|
||||
will attempt to update every configured machine.
|
||||
|
||||
$ clan machines install [MACHINES] [TARGET_HOST]
|
||||
@@ -262,20 +276,30 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/depl
|
||||
)
|
||||
machines.register_parser(parser_machine)
|
||||
|
||||
parser_vms = subparsers.add_parser("vms", help="manage virtual machines")
|
||||
parser_vms = subparsers.add_parser(
|
||||
"vms", help="manage virtual machines", description="manage virtual machines"
|
||||
)
|
||||
vms.register_parser(parser_vms)
|
||||
|
||||
parser_history = subparsers.add_parser("history", help="manage history")
|
||||
parser_history = subparsers.add_parser(
|
||||
"history",
|
||||
help="manage history",
|
||||
description="manage history",
|
||||
)
|
||||
history.register_parser(parser_history)
|
||||
|
||||
parser_flash = subparsers.add_parser(
|
||||
"flash", help="flash machines to usb sticks or into isos"
|
||||
"flash",
|
||||
help="flash machines to usb sticks or into isos",
|
||||
description="flash machines to usb sticks or into isos",
|
||||
)
|
||||
flash.register_parser(parser_flash)
|
||||
|
||||
if argcomplete:
|
||||
argcomplete.autocomplete(parser)
|
||||
|
||||
register_common_flags(parser)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ def create_backup(machine: Machine, provider: str | None = None) -> None:
|
||||
|
||||
|
||||
def create_command(args: argparse.Namespace) -> None:
|
||||
if args.flake is None:
|
||||
raise ClanError("Could not find clan flake toplevel directory")
|
||||
machine = Machine(name=args.machine, flake=args.flake)
|
||||
create_backup(machine=machine, provider=args.provider)
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ def list_backups(machine: Machine, provider: str | None = None) -> list[Backup]:
|
||||
|
||||
|
||||
def list_command(args: argparse.Namespace) -> None:
|
||||
if args.flake is None:
|
||||
raise ClanError("Could not find clan flake toplevel directory")
|
||||
machine = Machine(name=args.machine, flake=args.flake)
|
||||
backups = list_backups(machine=machine, provider=args.provider)
|
||||
for backup in backups:
|
||||
|
||||
@@ -62,6 +62,8 @@ def restore_backup(
|
||||
|
||||
|
||||
def restore_command(args: argparse.Namespace) -> None:
|
||||
if args.flake is None:
|
||||
raise ClanError("Could not find clan flake toplevel directory")
|
||||
machine = Machine(name=args.machine, flake=args.flake)
|
||||
restore_backup(
|
||||
machine=machine,
|
||||
|
||||
@@ -140,3 +140,23 @@ def run(
|
||||
raise ClanCmdError(cmd_out)
|
||||
|
||||
return cmd_out
|
||||
|
||||
|
||||
def run_no_stdout(
|
||||
cmd: list[str],
|
||||
*,
|
||||
env: dict[str, str] | None = None,
|
||||
cwd: Path = Path.cwd(),
|
||||
log: Log = Log.STDERR,
|
||||
check: bool = True,
|
||||
error_msg: str | None = None,
|
||||
) -> CmdOut:
|
||||
"""
|
||||
Like run, but automatically suppresses stdout, if not in DEBUG log level.
|
||||
If in DEBUG log level the stdout of commands will be shown.
|
||||
"""
|
||||
if logging.getLogger(__name__.split(".")[0]).isEnabledFor(logging.DEBUG):
|
||||
return run(cmd, env=env, log=log, check=check, error_msg=error_msg)
|
||||
else:
|
||||
log = Log.NONE
|
||||
return run(cmd, env=env, log=log, check=check, error_msg=error_msg)
|
||||
|
||||
@@ -16,10 +16,54 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||
required=True,
|
||||
)
|
||||
|
||||
check_parser = subparser.add_parser("check", help="check if facts are up to date")
|
||||
check_parser = subparser.add_parser(
|
||||
"check",
|
||||
help="check if facts are up to date",
|
||||
epilog=(
|
||||
"""
|
||||
This subcommand allows checking if all facts are up to date.
|
||||
|
||||
Examples:
|
||||
|
||||
$ clan facts check [MACHINE]
|
||||
Will check facts for the specified machine.
|
||||
|
||||
|
||||
For more detailed information, visit: https://docs.clan.lol/getting-started/secrets/
|
||||
"""
|
||||
),
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
register_check_parser(check_parser)
|
||||
|
||||
list_parser = subparser.add_parser("list", help="list all facts")
|
||||
list_parser = subparser.add_parser(
|
||||
"list",
|
||||
help="list all facts",
|
||||
epilog=(
|
||||
"""
|
||||
This subcommand allows listing all public facts for a specific machine.
|
||||
|
||||
The resulting list will be a json string with the name of the fact as its key
|
||||
and the fact itself as it's value.
|
||||
|
||||
This is how an example output might look like:
|
||||
```
|
||||
{
|
||||
"[FACT_NAME]": "[FACT]"
|
||||
}
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
$ clan facts list [MACHINE]
|
||||
Will list facts for the specified machine.
|
||||
|
||||
|
||||
For more detailed information, visit: https://docs.clan.lol/getting-started/secrets/
|
||||
"""
|
||||
),
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
register_list_parser(list_parser)
|
||||
|
||||
parser_generate = subparser.add_parser(
|
||||
@@ -62,5 +106,26 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/secr
|
||||
)
|
||||
register_generate_parser(parser_generate)
|
||||
|
||||
parser_upload = subparser.add_parser("upload", help="upload secrets for machines")
|
||||
parser_upload = subparser.add_parser(
|
||||
"upload",
|
||||
help="upload secrets for machines",
|
||||
epilog=(
|
||||
"""
|
||||
This subcommand allows uploading secrets to remote machines.
|
||||
|
||||
If using sops as a secret backend it will upload the private key to the machine.
|
||||
If using password store it uploads all the secrets you manage to the machine.
|
||||
|
||||
The default backend is sops.
|
||||
|
||||
Examples:
|
||||
|
||||
$ clan facts upload [MACHINE]
|
||||
Will upload secrets to a specific machine.
|
||||
|
||||
For more detailed information, visit: https://docs.clan.lol/getting-started/secrets/
|
||||
"""
|
||||
),
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
register_upload_parser(parser_upload)
|
||||
|
||||
@@ -209,9 +209,9 @@ def generate_facts(
|
||||
|
||||
def generate_command(args: argparse.Namespace) -> None:
|
||||
if len(args.machines) == 0:
|
||||
machines = get_all_machines(args.flake)
|
||||
machines = get_all_machines(args.flake, args.option)
|
||||
else:
|
||||
machines = get_selected_machines(args.flake, args.machines)
|
||||
machines = get_selected_machines(args.flake, args.option, args.machines)
|
||||
generate_facts(machines, args.service, args.regenerate)
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,9 @@ def flash_machine(
|
||||
disks: dict[str, str],
|
||||
system_config: dict[str, Any],
|
||||
dry_run: bool,
|
||||
write_efi_boot_entries: bool,
|
||||
debug: bool,
|
||||
extra_args: list[str] = [],
|
||||
) -> None:
|
||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||
secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore(
|
||||
@@ -112,6 +114,8 @@ def flash_machine(
|
||||
disko_install.append("sudo")
|
||||
|
||||
disko_install.append("disko-install")
|
||||
if write_efi_boot_entries:
|
||||
disko_install.append("--write-efi-boot-entries")
|
||||
if dry_run:
|
||||
disko_install.append("--dry-run")
|
||||
if debug:
|
||||
@@ -128,6 +132,8 @@ def flash_machine(
|
||||
json.dumps(system_config),
|
||||
]
|
||||
)
|
||||
disko_install.extend(["--option", "dry-run", "true"])
|
||||
disko_install.extend(extra_args)
|
||||
|
||||
cmd = nix_shell(
|
||||
["nixpkgs#disko"],
|
||||
@@ -148,6 +154,8 @@ class FlashOptions:
|
||||
mode: str
|
||||
language: str
|
||||
keymap: str
|
||||
write_efi_boot_entries: bool
|
||||
nix_options: list[str]
|
||||
|
||||
|
||||
class AppendDiskAction(argparse.Action):
|
||||
@@ -178,6 +186,8 @@ def flash_command(args: argparse.Namespace) -> None:
|
||||
mode=args.mode,
|
||||
language=args.lang,
|
||||
keymap=args.keymap,
|
||||
write_efi_boot_entries=args.write_efi_boot_entries,
|
||||
nix_options=args.options,
|
||||
)
|
||||
|
||||
machine = Machine(opts.machine, flake=opts.flake)
|
||||
@@ -233,6 +243,8 @@ def flash_command(args: argparse.Namespace) -> None:
|
||||
system_config=extra_config,
|
||||
dry_run=opts.dry_run,
|
||||
debug=opts.debug,
|
||||
write_efi_boot_entries=opts.write_efi_boot_entries,
|
||||
extra_args=opts.nix_options,
|
||||
)
|
||||
|
||||
|
||||
@@ -251,12 +263,14 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||
help="device to flash to",
|
||||
default={},
|
||||
)
|
||||
mode_help = textwrap.dedent("""\
|
||||
mode_help = textwrap.dedent(
|
||||
"""\
|
||||
Specify the mode of operation. Valid modes are: format, mount."
|
||||
Format will format the disk before installing.
|
||||
Mount will mount the disk before installing.
|
||||
Mount is useful for updating an existing system without losing data.
|
||||
""")
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
type=str,
|
||||
@@ -293,4 +307,16 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--write-efi-boot-entries",
|
||||
help=textwrap.dedent(
|
||||
"""
|
||||
Write EFI boot entries to the NVRAM of the system for the installed system.
|
||||
Specify this option if you plan to boot from this disk on the current machine,
|
||||
but not if you plan to move the disk to another machine.
|
||||
"""
|
||||
).strip(),
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
parser.set_defaults(func=flash_command)
|
||||
|
||||
@@ -26,6 +26,7 @@ def install_nixos(
|
||||
debug: bool = False,
|
||||
password: str | None = None,
|
||||
no_reboot: bool = False,
|
||||
extra_args: list[str] = [],
|
||||
) -> None:
|
||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||
log.info(f"installing {machine.name}")
|
||||
@@ -56,6 +57,7 @@ def install_nixos(
|
||||
f"{machine.flake}#{machine.name}",
|
||||
"--extra-files",
|
||||
str(tmpdir),
|
||||
*extra_args,
|
||||
]
|
||||
|
||||
if no_reboot:
|
||||
@@ -95,6 +97,7 @@ class InstallOptions:
|
||||
debug: bool
|
||||
no_reboot: bool
|
||||
json_ssh_deploy: dict[str, str] | None
|
||||
nix_options: list[str]
|
||||
|
||||
|
||||
def install_command(args: argparse.Namespace) -> None:
|
||||
@@ -127,6 +130,7 @@ def install_command(args: argparse.Namespace) -> None:
|
||||
debug=args.debug,
|
||||
no_reboot=args.no_reboot,
|
||||
json_ssh_deploy=json_ssh_deploy,
|
||||
nix_options=args.option,
|
||||
)
|
||||
machine = Machine(opts.machine, flake=opts.flake)
|
||||
machine.target_host_address = opts.target_host
|
||||
@@ -142,6 +146,7 @@ def install_command(args: argparse.Namespace) -> None:
|
||||
debug=opts.debug,
|
||||
password=password,
|
||||
no_reboot=opts.no_reboot,
|
||||
extra_args=opts.nix_options,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from .machines import Machine
|
||||
|
||||
|
||||
# function to speedup eval if we want to evauluate all machines
|
||||
def get_all_machines(flake_dir: Path) -> list[Machine]:
|
||||
def get_all_machines(flake_dir: Path, nix_options: list[str]) -> list[Machine]:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
json_path = run(
|
||||
@@ -19,13 +19,20 @@ def get_all_machines(flake_dir: Path) -> list[Machine]:
|
||||
machines = []
|
||||
for name, machine_data in machines_json.items():
|
||||
machines.append(
|
||||
Machine(name=name, flake=flake_dir, deployment_info=machine_data)
|
||||
Machine(
|
||||
name=name,
|
||||
flake=flake_dir,
|
||||
deployment_info=machine_data,
|
||||
nix_options=nix_options,
|
||||
)
|
||||
)
|
||||
return machines
|
||||
|
||||
|
||||
def get_selected_machines(flake_dir: Path, machine_names: list[str]) -> list[Machine]:
|
||||
def get_selected_machines(
|
||||
flake_dir: Path, nix_options: list[str], machine_names: list[str]
|
||||
) -> list[Machine]:
|
||||
machines = []
|
||||
for name in machine_names:
|
||||
machines.append(Machine(name=name, flake=flake_dir))
|
||||
machines.append(Machine(name=name, flake=flake_dir, nix_options=nix_options))
|
||||
return machines
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
from clan_cli.api import API
|
||||
|
||||
from ..cmd import Log, run
|
||||
from ..cmd import run_no_stdout
|
||||
from ..nix import nix_config, nix_eval
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -34,10 +34,7 @@ def list_machines(flake_url: str | Path, debug: bool) -> dict[str, MachineInfo]:
|
||||
]
|
||||
)
|
||||
|
||||
if not debug:
|
||||
proc = run(cmd, log=Log.NONE)
|
||||
else:
|
||||
proc = run(cmd)
|
||||
proc = run_no_stdout(cmd)
|
||||
|
||||
res = proc.stdout.strip()
|
||||
machines_dict = json.loads(res)
|
||||
|
||||
@@ -10,7 +10,7 @@ from clan_cli.clan_uri import ClanURI, MachineData
|
||||
from clan_cli.dirs import vm_state_dir
|
||||
from clan_cli.qemu.qmp import QEMUMonitorProtocol
|
||||
|
||||
from ..cmd import run
|
||||
from ..cmd import run_no_stdout
|
||||
from ..errors import ClanError
|
||||
from ..nix import nix_build, nix_config, nix_eval, nix_metadata
|
||||
from ..ssh import Host, parse_deployment_address
|
||||
@@ -41,9 +41,10 @@ class QMPWrapper:
|
||||
|
||||
|
||||
class Machine:
|
||||
flake: str | Path
|
||||
name: str
|
||||
flake: str | Path
|
||||
data: MachineData
|
||||
nix_options: list[str]
|
||||
eval_cache: dict[str, str]
|
||||
build_cache: dict[str, Path]
|
||||
_flake_path: Path | None
|
||||
@@ -55,6 +56,7 @@ class Machine:
|
||||
name: str,
|
||||
flake: Path | str,
|
||||
deployment_info: dict | None = None,
|
||||
nix_options: list[str] = [],
|
||||
machine: MachineData | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -76,6 +78,7 @@ class Machine:
|
||||
self.build_cache: dict[str, Path] = {}
|
||||
self._flake_path: Path | None = None
|
||||
self._deployment_info: None | dict = deployment_info
|
||||
self.nix_options = nix_options
|
||||
|
||||
state_dir = vm_state_dir(flake_url=str(self.flake), vm_name=self.data.name)
|
||||
|
||||
@@ -197,7 +200,7 @@ class Machine:
|
||||
config_json.flush()
|
||||
|
||||
file_info = json.loads(
|
||||
run(
|
||||
run_no_stdout(
|
||||
nix_eval(
|
||||
[
|
||||
"--impure",
|
||||
@@ -242,15 +245,15 @@ class Machine:
|
||||
flake = f"path:{self.flake_dir}"
|
||||
|
||||
args += [
|
||||
f'{flake}#clanInternals.machines."{system}".{self.data.name}.{attr}',
|
||||
*nix_options,
|
||||
f'{flake}#clanInternals.machines."{system}".{self.data.name}.{attr}'
|
||||
]
|
||||
args += nix_options + self.nix_options
|
||||
|
||||
if method == "eval":
|
||||
output = run(nix_eval(args)).stdout.strip()
|
||||
output = run_no_stdout(nix_eval(args)).stdout.strip()
|
||||
return output
|
||||
elif method == "build":
|
||||
outpath = run(nix_build(args)).stdout.strip()
|
||||
outpath = run_no_stdout(nix_build(args)).stdout.strip()
|
||||
return Path(outpath)
|
||||
else:
|
||||
raise ValueError(f"Unknown method {method}")
|
||||
|
||||
@@ -110,11 +110,9 @@ def deploy_nixos(machines: MachineGroup) -> None:
|
||||
|
||||
ssh_arg += " -i " + host.key if host.key else ""
|
||||
|
||||
extra_args = host.meta.get("extra_args", [])
|
||||
cmd = [
|
||||
"nixos-rebuild",
|
||||
"switch",
|
||||
*extra_args,
|
||||
"--fast",
|
||||
"--option",
|
||||
"keep-going",
|
||||
@@ -124,6 +122,7 @@ def deploy_nixos(machines: MachineGroup) -> None:
|
||||
"true",
|
||||
"--build-host",
|
||||
"",
|
||||
*machine.nix_options,
|
||||
"--flake",
|
||||
f"{path}#{machine.name}",
|
||||
]
|
||||
@@ -143,7 +142,9 @@ def update(args: argparse.Namespace) -> None:
|
||||
raise ClanError("Could not find clan flake toplevel directory")
|
||||
machines = []
|
||||
if len(args.machines) == 1 and args.target_host is not None:
|
||||
machine = Machine(name=args.machines[0], flake=args.flake)
|
||||
machine = Machine(
|
||||
name=args.machines[0], flake=args.flake, nix_options=args.option
|
||||
)
|
||||
machine.target_host_address = args.target_host
|
||||
machines.append(machine)
|
||||
|
||||
@@ -153,7 +154,7 @@ def update(args: argparse.Namespace) -> None:
|
||||
else:
|
||||
if len(args.machines) == 0:
|
||||
ignored_machines = []
|
||||
for machine in get_all_machines(args.flake):
|
||||
for machine in get_all_machines(args.flake, args.option):
|
||||
if machine.deployment_info.get("requireExplicitUpdate", False):
|
||||
continue
|
||||
try:
|
||||
@@ -173,7 +174,7 @@ def update(args: argparse.Namespace) -> None:
|
||||
print(machine, file=sys.stderr)
|
||||
|
||||
else:
|
||||
machines = get_selected_machines(args.flake, args.machines)
|
||||
machines = get_selected_machines(args.flake, args.option, args.machines)
|
||||
|
||||
deploy_nixos(MachineGroup(machines))
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class Option:
|
||||
md_li += indent_next(
|
||||
f"\n{self.description.strip()}" if self.description else ""
|
||||
)
|
||||
md_li += indent_next(f"\n{self.epilog.strip()}" if self.epilog else "")
|
||||
# md_li += indent_next(f"\n{self.epilog.strip()}" if self.epilog else "")
|
||||
|
||||
return md_li
|
||||
|
||||
@@ -82,13 +82,54 @@ class Category:
|
||||
md_li += indent_all(
|
||||
f"{self.description.strip()}\n" if self.description else "", 4
|
||||
)
|
||||
md_li += "\n"
|
||||
md_li += indent_all(f"{self.epilog.strip()}\n" if self.epilog else "", 4)
|
||||
md_li += "\n"
|
||||
|
||||
return md_li
|
||||
|
||||
|
||||
def epilog_to_md(text: str) -> str:
|
||||
"""
|
||||
Convert the epilog to md
|
||||
"""
|
||||
after_examples = False
|
||||
md = ""
|
||||
# md += text
|
||||
for line in text.split("\n"):
|
||||
if line.strip() == "Examples:":
|
||||
after_examples = True
|
||||
md += "### Examples"
|
||||
md += "\n"
|
||||
else:
|
||||
if after_examples:
|
||||
if line.strip().startswith("$"):
|
||||
md += f"`{line}`"
|
||||
md += "\n"
|
||||
md += "\n"
|
||||
else:
|
||||
if contains_https_link(line):
|
||||
line = convert_to_markdown_link(line)
|
||||
md += line
|
||||
md += "\n"
|
||||
else:
|
||||
md += line
|
||||
md += "\n"
|
||||
return md
|
||||
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def contains_https_link(line: str) -> bool:
|
||||
pattern = r"https://\S+"
|
||||
return re.search(pattern, line) is not None
|
||||
|
||||
|
||||
def convert_to_markdown_link(line: str) -> str:
|
||||
pattern = r"(https://\S+)"
|
||||
|
||||
# Replacement pattern to convert it to a Markdown link
|
||||
return re.sub(pattern, r"[\1](\1)", line)
|
||||
|
||||
|
||||
def indent_next(text: str, indent_size: int = 4) -> str:
|
||||
"""
|
||||
Indent all lines in a string except the first line.
|
||||
@@ -136,7 +177,7 @@ def get_subcommands(
|
||||
continue
|
||||
|
||||
if isinstance(action, argparse._SubParsersAction):
|
||||
continue # Subparsers handled sperately
|
||||
continue # Subparsers handled separately
|
||||
|
||||
option_strings = ", ".join(action.option_strings)
|
||||
if option_strings:
|
||||
@@ -178,7 +219,7 @@ def get_subcommands(
|
||||
Category(
|
||||
title=f"{parent} {name}",
|
||||
description=subparser.description,
|
||||
epilog=subparser.epilog,
|
||||
# epilog=subparser.epilog,
|
||||
level=level,
|
||||
options=_options,
|
||||
positionals=_positionals,
|
||||
@@ -222,6 +263,7 @@ def collect_commands() -> list[Category]:
|
||||
options=_options,
|
||||
positionals=_positionals,
|
||||
subcommands=_subcommands,
|
||||
epilog=subparser.epilog,
|
||||
level=1,
|
||||
)
|
||||
)
|
||||
@@ -242,6 +284,139 @@ def collect_commands() -> list[Category]:
|
||||
return result
|
||||
|
||||
|
||||
class ManPage:
|
||||
def __init__(self, name: str, section: int) -> None:
|
||||
self.name = name
|
||||
self.section = section
|
||||
|
||||
def add_description(self, description: str) -> None:
|
||||
self.description = description
|
||||
|
||||
# def add_option(self, option, description):
|
||||
# self.options[option] = description
|
||||
|
||||
def control(self, control: str, content: str | None = None) -> None:
|
||||
if content:
|
||||
self.manpage = self.manpage + f".{control} {content}"
|
||||
else:
|
||||
self.manpage = self.manpage + f".{control}"
|
||||
self.manpage = self.manpage + "\n"
|
||||
|
||||
def line(self, content: str | None = None) -> None:
|
||||
self.manpage = self.manpage + f"{content}"
|
||||
self.manpage = self.manpage + "\n"
|
||||
|
||||
def newline(self) -> None:
|
||||
self.manpage = self.manpage + "\n"
|
||||
|
||||
def paragraph(self) -> None:
|
||||
self.newline()
|
||||
self.control("PP")
|
||||
|
||||
def contribute(self) -> None:
|
||||
"""
|
||||
Contributing section of the manpages
|
||||
should only be shown on the root page.
|
||||
"""
|
||||
self.control("SH", "CONTRIBUTE")
|
||||
self.control(
|
||||
"PP",
|
||||
)
|
||||
self.line("Bug reports, contributions and forks are welcome.")
|
||||
self.newline()
|
||||
self.control(
|
||||
"PP",
|
||||
)
|
||||
self.line(
|
||||
"The code lives on gitea, you can use the issue tracker to file bugs, or issues and give feedback."
|
||||
)
|
||||
self.newline()
|
||||
self.line(self.link("https://git.clan.lol/clan/clan-core"))
|
||||
|
||||
self.paragraph()
|
||||
self.line(
|
||||
"There is a matrix channel available where you can give feedback, or get feedback."
|
||||
)
|
||||
self.line("Share your usage patterns or share tips and tricks.")
|
||||
self.newline()
|
||||
self.line(self.link("https://matrix.to/#/#clan:lassul.us"))
|
||||
|
||||
def link(self, link: str) -> str:
|
||||
"""
|
||||
Format a link
|
||||
"""
|
||||
return "\[la]" + link + "\[ra]"
|
||||
|
||||
def render(self) -> str:
|
||||
self.manpage = ""
|
||||
self.control("nh")
|
||||
self.control("TH", 'CLAN 1 2023 clan "User Manuals"')
|
||||
self.control("SH", "NAME")
|
||||
self.control(
|
||||
"PP",
|
||||
)
|
||||
# overview
|
||||
self.line("clan - the clan cli tool")
|
||||
self.newline()
|
||||
|
||||
# synopsis
|
||||
self.control("SH", "SYNOPSIS")
|
||||
self.control(
|
||||
"PP",
|
||||
)
|
||||
self.line("clan [OPTIONS] [SUBCOMMAND]")
|
||||
self.newline()
|
||||
|
||||
# description
|
||||
self.control("SH", "DESCRIPTION")
|
||||
self.control(
|
||||
"PP",
|
||||
)
|
||||
self.line("clan is a distributed systems manager for your fingertips.")
|
||||
self.line("It knows how to update your machines.")
|
||||
self.line(
|
||||
"It knows how to generate and manage secrets and facts for your services."
|
||||
)
|
||||
self.line(
|
||||
"It knows which services have state in which directories and can manage backups for machines and services."
|
||||
)
|
||||
self.paragraph()
|
||||
|
||||
# manpage = f"NAME\n\t{self.name} - {self.description}\n\nDESCRIPTION\n\t{self.description}\n\nOPTIONS\n"
|
||||
|
||||
# for options in self.options.items():
|
||||
# print(options)
|
||||
|
||||
# for option, desc in self.options.items():
|
||||
# manpage += f"\t-{option}\n\t\t{desc}\n"
|
||||
self.contribute()
|
||||
|
||||
return self.manpage
|
||||
|
||||
|
||||
def build_manpage() -> None:
|
||||
"""
|
||||
Build the reference and write to the out path.
|
||||
"""
|
||||
cmds = collect_commands()
|
||||
# print(cmds)
|
||||
# start the manpage with information collect_commands() doesn't expose
|
||||
man = ManPage("clan", 1)
|
||||
man.add_description("The clan cli tool.")
|
||||
|
||||
print(man.render())
|
||||
|
||||
for command in cmds:
|
||||
if command.title == "facts":
|
||||
print(command)
|
||||
|
||||
# TODO: gather all level one subcommands
|
||||
|
||||
# folder = Path("out")
|
||||
# folder.mkdir(parents=True, exist_ok=True)
|
||||
pass
|
||||
|
||||
|
||||
def build_command_reference() -> None:
|
||||
"""
|
||||
Function that will build the reference
|
||||
@@ -253,13 +428,12 @@ def build_command_reference() -> None:
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Index file
|
||||
markdown = "# CLI Overview\n\n"
|
||||
markdown = "#Overview\n\n"
|
||||
categories_fmt = ""
|
||||
for cat in cmds:
|
||||
categories_fmt += f"{cat.to_md_li()}\n\n" if cat.to_md_li() else ""
|
||||
|
||||
if categories_fmt:
|
||||
markdown += """## Overview\n\n"""
|
||||
markdown += '<div class="grid cards" markdown>\n\n'
|
||||
markdown += categories_fmt
|
||||
markdown += "</div>"
|
||||
@@ -280,6 +454,7 @@ def build_command_reference() -> None:
|
||||
markdown = files.get(folder / f"{filename}.md", "")
|
||||
|
||||
markdown += f"{'#'*(cmd.level)} {cmd.title.capitalize()}\n\n"
|
||||
|
||||
markdown += f"{cmd.description}\n\n" if cmd.description else ""
|
||||
|
||||
# usage: clan vms run [-h] machine
|
||||
@@ -320,6 +495,8 @@ def build_command_reference() -> None:
|
||||
markdown += indent_all(commands_fmt)
|
||||
markdown += "\n"
|
||||
|
||||
markdown += f"{epilog_to_md(cmd.epilog)}\n\n" if cmd.epilog else ""
|
||||
|
||||
files[folder / f"{filename}.md"] = markdown
|
||||
|
||||
for fname, content in files.items():
|
||||
@@ -330,13 +507,15 @@ def build_command_reference() -> None:
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python docs.py <command>")
|
||||
print("Available commands: reference")
|
||||
print("Available commands: reference, manpage")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "reference":
|
||||
build_command_reference()
|
||||
if command == "manpage":
|
||||
build_manpage()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -11,10 +11,10 @@ def test_backups(
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"backups",
|
||||
"list",
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"vm1",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -39,9 +39,9 @@ def test_set_some_option(
|
||||
cli = Cli()
|
||||
cli.run(
|
||||
[
|
||||
"config",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"config",
|
||||
"--quiet",
|
||||
"--options-file",
|
||||
example_options,
|
||||
@@ -64,9 +64,9 @@ def test_configure_machine(
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"config",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"config",
|
||||
"-m",
|
||||
"machine1",
|
||||
"clan.jitsi.enable",
|
||||
@@ -78,9 +78,9 @@ def test_configure_machine(
|
||||
# read a option value
|
||||
cli.run(
|
||||
[
|
||||
"config",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"config",
|
||||
"-m",
|
||||
"machine1",
|
||||
"clan.jitsi.enable",
|
||||
|
||||
@@ -15,10 +15,10 @@ def test_flakes_inspect(
|
||||
cli = Cli()
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"flakes",
|
||||
"inspect",
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"--machine",
|
||||
"vm1",
|
||||
]
|
||||
|
||||
@@ -21,55 +21,55 @@ def test_import_sops(
|
||||
monkeypatch.setenv("SOPS_AGE_KEY", age_keys[1].privkey)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"machine1",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user1",
|
||||
age_keys[1].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user2",
|
||||
age_keys[2].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"user2",
|
||||
]
|
||||
@@ -78,10 +78,10 @@ def test_import_sops(
|
||||
# To edit:
|
||||
# SOPS_AGE_KEY=AGE-SECRET-KEY-1U5ENXZQAY62NC78Y2WC0SEGRRMAEEKH79EYY5TH4GPFWJKEAY0USZ6X7YQ sops --age age14tva0txcrl0zes05x7gkx56qd6wd9q3nwecjac74xxzz4l47r44sv3fz62 ./data/secrets.yaml
|
||||
cmd = [
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"import-sops",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"--group",
|
||||
"group1",
|
||||
"--machine",
|
||||
@@ -91,10 +91,10 @@ def test_import_sops(
|
||||
|
||||
cli.run(cmd)
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "users", "list"])
|
||||
cli.run(["secrets", "users", "list", "--flake", str(test_flake.path)])
|
||||
users = sorted(capsys.readouterr().out.rstrip().split())
|
||||
assert users == ["user1", "user2"]
|
||||
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "secret-key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "secret-key"])
|
||||
assert capsys.readouterr().out == "secret-value"
|
||||
|
||||
@@ -9,11 +9,11 @@ def test_machine_subcommands(
|
||||
) -> None:
|
||||
cli = Cli()
|
||||
cli.run(
|
||||
["--flake", str(test_flake_with_core.path), "machines", "create", "machine1"]
|
||||
["machines", "create", "--flake", str(test_flake_with_core.path), "machine1"]
|
||||
)
|
||||
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake_with_core.path), "machines", "list"])
|
||||
cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)])
|
||||
|
||||
out = capsys.readouterr()
|
||||
|
||||
@@ -22,11 +22,11 @@ def test_machine_subcommands(
|
||||
assert "vm2" in out.out
|
||||
|
||||
cli.run(
|
||||
["--flake", str(test_flake_with_core.path), "machines", "delete", "machine1"]
|
||||
["machines", "delete", "--flake", str(test_flake_with_core.path), "machine1"]
|
||||
)
|
||||
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake_with_core.path), "machines", "list"])
|
||||
cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)])
|
||||
out = capsys.readouterr()
|
||||
|
||||
assert "machine1" not in out.out
|
||||
|
||||
@@ -27,11 +27,11 @@ def _test_identities(
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
what,
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"foo",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
@@ -41,11 +41,11 @@ def _test_identities(
|
||||
with pytest.raises(ClanError): # raises "foo already exists"
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
what,
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"foo",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
@@ -54,11 +54,11 @@ def _test_identities(
|
||||
# rotate the key
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
what,
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"-f",
|
||||
"foo",
|
||||
age_keys[1].privkey,
|
||||
@@ -68,11 +68,11 @@ def _test_identities(
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
what,
|
||||
"get",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"foo",
|
||||
]
|
||||
)
|
||||
@@ -80,18 +80,18 @@ def _test_identities(
|
||||
assert age_keys[1].pubkey in out.out
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", what, "list"])
|
||||
cli.run(["secrets", what, "list", "--flake", str(test_flake.path)])
|
||||
out = capsys.readouterr() # empty the buffer
|
||||
assert "foo" in out.out
|
||||
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", what, "remove", "foo"])
|
||||
cli.run(["secrets", what, "remove", "--flake", str(test_flake.path), "foo"])
|
||||
assert not (sops_folder / what / "foo" / "key.json").exists()
|
||||
|
||||
with pytest.raises(ClanError): # already removed
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", what, "remove", "foo"])
|
||||
cli.run(["secrets", what, "remove", "--flake", str(test_flake.path), "foo"])
|
||||
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", what, "list"])
|
||||
cli.run(["secrets", what, "list", "--flake", str(test_flake.path)])
|
||||
out = capsys.readouterr()
|
||||
assert "foo" not in out.out
|
||||
|
||||
@@ -113,17 +113,17 @@ def test_groups(
|
||||
) -> None:
|
||||
cli = Cli()
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "groups", "list"])
|
||||
cli.run(["secrets", "groups", "list", "--flake", str(test_flake.path)])
|
||||
assert capsys.readouterr().out == ""
|
||||
|
||||
with pytest.raises(ClanError): # machine does not exist yet
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-machine",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"machine1",
|
||||
]
|
||||
@@ -131,33 +131,33 @@ def test_groups(
|
||||
with pytest.raises(ClanError): # user does not exist yet
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"groupb1",
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"machine1",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-machine",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"machine1",
|
||||
]
|
||||
@@ -166,11 +166,11 @@ def test_groups(
|
||||
# Should this fail?
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-machine",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"machine1",
|
||||
]
|
||||
@@ -178,51 +178,51 @@ def test_groups(
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user1",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "groups", "list"])
|
||||
cli.run(["secrets", "groups", "list", "--flake", str(test_flake.path)])
|
||||
out = capsys.readouterr().out
|
||||
assert "user1" in out
|
||||
assert "machine1" in out
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"remove-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"remove-machine",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"group1",
|
||||
"machine1",
|
||||
]
|
||||
@@ -251,90 +251,90 @@ def test_secrets(
|
||||
) -> None:
|
||||
cli = Cli()
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "list"])
|
||||
cli.run(["secrets", "list", "--flake", str(test_flake.path)])
|
||||
assert capsys.readouterr().out == ""
|
||||
|
||||
monkeypatch.setenv("SOPS_NIX_SECRET", "foo")
|
||||
monkeypatch.setenv("SOPS_AGE_KEY_FILE", str(test_flake.path / ".." / "age.key"))
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "key", "generate"])
|
||||
cli.run(["secrets", "key", "generate", "--flake", str(test_flake.path)])
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "key", "show"])
|
||||
cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)])
|
||||
key = capsys.readouterr().out
|
||||
assert key.startswith("age1")
|
||||
cli.run(
|
||||
["--flake", str(test_flake.path), "secrets", "users", "add", "testuser", key]
|
||||
["secrets", "users", "add", "--flake", str(test_flake.path), "testuser", key]
|
||||
)
|
||||
|
||||
with pytest.raises(ClanError): # does not exist yet
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "nonexisting"])
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "set", "initialkey"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "nonexisting"])
|
||||
cli.run(["secrets", "set", "--flake", str(test_flake.path), "initialkey"])
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "initialkey"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "initialkey"])
|
||||
assert capsys.readouterr().out == "foo"
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "users", "list"])
|
||||
cli.run(["secrets", "users", "list", "--flake", str(test_flake.path)])
|
||||
users = capsys.readouterr().out.rstrip().split("\n")
|
||||
assert len(users) == 1, f"users: {users}"
|
||||
owner = users[0]
|
||||
|
||||
monkeypatch.setenv("EDITOR", "cat")
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "set", "--edit", "initialkey"])
|
||||
cli.run(["secrets", "set", "--edit", "--flake", str(test_flake.path), "initialkey"])
|
||||
monkeypatch.delenv("EDITOR")
|
||||
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "rename", "initialkey", "key"])
|
||||
cli.run(["secrets", "rename", "--flake", str(test_flake.path), "initialkey", "key"])
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "list"])
|
||||
cli.run(["secrets", "list", "--flake", str(test_flake.path)])
|
||||
assert capsys.readouterr().out == "key\n"
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "list", "nonexisting"])
|
||||
cli.run(["secrets", "list", "--flake", str(test_flake.path), "nonexisting"])
|
||||
assert capsys.readouterr().out == ""
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "list", "key"])
|
||||
cli.run(["secrets", "list", "--flake", str(test_flake.path), "key"])
|
||||
assert capsys.readouterr().out == "key\n"
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"machine1",
|
||||
age_keys[1].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"add-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"machine1",
|
||||
"key",
|
||||
]
|
||||
)
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "machines", "list"])
|
||||
cli.run(["secrets", "machines", "list", "--flake", str(test_flake.path)])
|
||||
assert capsys.readouterr().out == "machine1\n"
|
||||
|
||||
with use_key(age_keys[1].privkey, monkeypatch):
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
||||
|
||||
assert capsys.readouterr().out == "foo"
|
||||
|
||||
# rotate machines key
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"-f",
|
||||
"machine1",
|
||||
age_keys[0].privkey,
|
||||
@@ -344,17 +344,17 @@ def test_secrets(
|
||||
# should also rotate the encrypted secret
|
||||
with use_key(age_keys[0].privkey, monkeypatch):
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
||||
|
||||
assert capsys.readouterr().out == "foo"
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"remove-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"machine1",
|
||||
"key",
|
||||
]
|
||||
@@ -362,37 +362,37 @@ def test_secrets(
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user1",
|
||||
age_keys[1].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user1",
|
||||
"key",
|
||||
]
|
||||
)
|
||||
capsys.readouterr()
|
||||
with use_key(age_keys[1].privkey, monkeypatch):
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
||||
assert capsys.readouterr().out == "foo"
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"remove-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user1",
|
||||
"key",
|
||||
]
|
||||
@@ -401,44 +401,44 @@ def test_secrets(
|
||||
with pytest.raises(ClanError): # does not exist yet
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
"key",
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
owner,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
"key",
|
||||
]
|
||||
@@ -447,10 +447,10 @@ def test_secrets(
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"set",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"--group",
|
||||
"admin-group",
|
||||
"key2",
|
||||
@@ -459,28 +459,28 @@ def test_secrets(
|
||||
|
||||
with use_key(age_keys[1].privkey, monkeypatch):
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
||||
assert capsys.readouterr().out == "foo"
|
||||
|
||||
# extend group will update secrets
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"user2",
|
||||
age_keys[2].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
"user2",
|
||||
]
|
||||
@@ -488,16 +488,16 @@ def test_secrets(
|
||||
|
||||
with use_key(age_keys[2].privkey, monkeypatch): # user2
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
||||
assert capsys.readouterr().out == "foo"
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"remove-user",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
"user2",
|
||||
]
|
||||
@@ -505,24 +505,24 @@ def test_secrets(
|
||||
with pytest.raises(ClanError), use_key(age_keys[2].privkey, monkeypatch):
|
||||
# user2 is not in the group anymore
|
||||
capsys.readouterr()
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
||||
print(capsys.readouterr().out)
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"remove-secret",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"admin-group",
|
||||
"key",
|
||||
]
|
||||
)
|
||||
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "remove", "key"])
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "remove", "key2"])
|
||||
cli.run(["secrets", "remove", "--flake", str(test_flake.path), "key"])
|
||||
cli.run(["secrets", "remove", "--flake", str(test_flake.path), "key2"])
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["--flake", str(test_flake.path), "secrets", "list"])
|
||||
cli.run(["secrets", "list", "--flake", str(test_flake.path)])
|
||||
assert capsys.readouterr().out == ""
|
||||
|
||||
@@ -24,27 +24,27 @@ def test_generate_secret(
|
||||
cli = Cli()
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"user1",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
)
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"secrets",
|
||||
"groups",
|
||||
"add-user",
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"admins",
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
cmd = ["--flake", str(test_flake_with_core.path), "facts", "generate", "vm1"]
|
||||
cmd = ["facts", "generate", "--flake", str(test_flake_with_core.path), "vm1"]
|
||||
cli.run(cmd)
|
||||
has_secret(test_flake_with_core.path, "vm1-age.key")
|
||||
has_secret(test_flake_with_core.path, "vm1-zerotier-identity-secret")
|
||||
@@ -60,7 +60,7 @@ def test_generate_secret(
|
||||
secret1_mtime = identity_secret.lstat().st_mtime_ns
|
||||
|
||||
# test idempotency for vm1 and also generate for vm2
|
||||
cli.run(["facts", "generate"])
|
||||
cli.run(["facts", "generate", "--flake", str(test_flake_with_core.path)])
|
||||
assert age_key.lstat().st_mtime_ns == age_key_mtime
|
||||
assert identity_secret.lstat().st_mtime_ns == secret1_mtime
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ def test_secrets_upload(
|
||||
cli = Cli()
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"user1",
|
||||
age_keys[0].pubkey,
|
||||
]
|
||||
@@ -35,18 +35,18 @@ def test_secrets_upload(
|
||||
|
||||
cli.run(
|
||||
[
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"secrets",
|
||||
"machines",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake_with_core.path),
|
||||
"vm1",
|
||||
age_keys[1].pubkey,
|
||||
]
|
||||
)
|
||||
monkeypatch.setenv("SOPS_NIX_SECRET", age_keys[0].privkey)
|
||||
cli.run(
|
||||
["--flake", str(test_flake_with_core.path), "secrets", "set", "vm1-age.key"]
|
||||
["secrets", "set", "--flake", str(test_flake_with_core.path), "vm1-age.key"]
|
||||
)
|
||||
|
||||
flake = test_flake_with_core.path.joinpath("flake.nix")
|
||||
@@ -55,7 +55,7 @@ def test_secrets_upload(
|
||||
new_text = flake.read_text().replace("__CLAN_TARGET_ADDRESS__", addr)
|
||||
|
||||
flake.write_text(new_text)
|
||||
cli.run(["--flake", str(test_flake_with_core.path), "facts", "upload", "vm1"])
|
||||
cli.run(["facts", "upload", "--flake", str(test_flake_with_core.path), "vm1"])
|
||||
|
||||
# the flake defines this path as the location where the sops key should be installed
|
||||
sops_key = test_flake_with_core.path.joinpath("key.txt")
|
||||
|
||||
@@ -86,7 +86,7 @@ def test_inspect(
|
||||
test_flake_with_core: FlakeForTest, capsys: pytest.CaptureFixture
|
||||
) -> None:
|
||||
cli = Cli()
|
||||
cli.run(["--flake", str(test_flake_with_core.path), "vms", "inspect", "vm1"])
|
||||
cli.run(["vms", "inspect", "--flake", str(test_flake_with_core.path), "vm1"])
|
||||
out = capsys.readouterr() # empty the buffer
|
||||
assert "Cores" in out.out
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
},
|
||||
"options": {
|
||||
"nixos": {
|
||||
"expr": "(builtins.getFlake \"github:nixos/nixpkgs\").nixosConfigurations.<name>.options"
|
||||
"expr": "(let pkgs = import <nixpkgs> { }; in (pkgs.lib.evalModules { modules
|
||||
= (import <nixpkgs/nixos/modules/module-list.nix>) ++ [ ({...}: {
|
||||
nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options"
|
||||
},
|
||||
"home-manager": {
|
||||
"expr": "(builtins.getFlake \"github:nix-community/home-manager\").homeConfigurations.<name>.options"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
./installer/flake-module.nix
|
||||
./schemas/flake-module.nix
|
||||
./webview-ui/flake-module.nix
|
||||
./gui-installer/flake-module.nix
|
||||
];
|
||||
|
||||
perSystem =
|
||||
|
||||
33
pkgs/gui-installer/flake-module.nix
Normal file
33
pkgs/gui-installer/flake-module.nix
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
nfpmConfig = pkgs.writeText "clan-nfpm-config.yaml" (
|
||||
|
||||
builtins.toJSON {
|
||||
name = "clan-gui-installer";
|
||||
contents = [
|
||||
{
|
||||
src = "${./gui-installer.sh}";
|
||||
dst = "/usr/bin/clan-vm-manager";
|
||||
}
|
||||
];
|
||||
}
|
||||
);
|
||||
installerFor =
|
||||
packager:
|
||||
pkgs.runCommand "gui-installer" { } ''
|
||||
mkdir build
|
||||
cd build
|
||||
${pkgs.nfpm}/bin/nfpm package --config ${nfpmConfig} --packager ${packager}
|
||||
mkdir $out
|
||||
mv * $out/
|
||||
'';
|
||||
in
|
||||
{
|
||||
packages.gui-installer-apk = installerFor "apk";
|
||||
packages.gui-installer-archlinux = installerFor "archlinux";
|
||||
packages.gui-installer-deb = installerFor "deb";
|
||||
packages.gui-installer-rpm = installerFor "rpm";
|
||||
};
|
||||
}
|
||||
54
pkgs/gui-installer/gui-installer.sh
Executable file
54
pkgs/gui-installer/gui-installer.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#! /bin/sh
|
||||
|
||||
# create temp dir and ensure it is always cleaned
|
||||
trap 'clean_temp_dir' EXIT
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
clean_temp_dir() {
|
||||
rm -rf "$temp_dir"
|
||||
}
|
||||
|
||||
is_nix_installed() {
|
||||
if [ -n "$(command -v nix)" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_nix() {
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L \
|
||||
https://install.determinate.systems/nix \
|
||||
> "$temp_dir"/install_nix.sh
|
||||
NIX_INSTALLER_DIAGNOSTIC_ENDPOINT="" sh "$temp_dir"/install_nix.sh install
|
||||
}
|
||||
|
||||
ask_then_install_nix() {
|
||||
echo "Clan requires Nix to be installed. Would you like to install it now? (y/n)"
|
||||
read -r response
|
||||
if [ "$response" = "y" ]; then
|
||||
install_nix
|
||||
else
|
||||
echo "Clan cannot run without Nix. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_nix_installed() {
|
||||
if ! is_nix_installed; then
|
||||
ask_then_install_nix
|
||||
fi
|
||||
}
|
||||
|
||||
start_clan_gui() {
|
||||
exec nix run \
|
||||
https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-vm-manager \
|
||||
--extra-experimental-features flakes nix-command -- "$@"
|
||||
}
|
||||
|
||||
main() {
|
||||
ensure_nix_installed
|
||||
start_clan_gui "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
213
templates/new-clan/flake.lock
generated
Normal file
213
templates/new-clan/flake.lock
generated
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"nodes": {
|
||||
"clan-core": {
|
||||
"inputs": {
|
||||
"disko": "disko",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixos-generators": "nixos-generators",
|
||||
"nixos-images": "nixos-images",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"sops-nix": "sops-nix",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1716971153,
|
||||
"narHash": "sha256-3QXK2SJRLMBOB1urY64WpXPKIWcq1LilBO8xeD6cBZg=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "d138e29a53678c94c600a4aeb125c2ece0267271",
|
||||
"revCount": 2911,
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/clan-core"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/clan-core"
|
||||
}
|
||||
},
|
||||
"disko": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"clan-core",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1716394172,
|
||||
"narHash": "sha256-B+pNhV8GFeCj9/MoH+qtGqKbgv6fU4hGaw2+NoYYtB0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "23c63fb09334c3e8958b57e2ddc3870b75b9111d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"clan-core",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715865404,
|
||||
"narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixlib": {
|
||||
"locked": {
|
||||
"lastModified": 1712450863,
|
||||
"narHash": "sha256-K6IkdtMtq9xktmYPj0uaYc8NsIqHuaAoRBaMgu9Fvrw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "3c62b6a12571c9a7f65ab037173ee153d539905f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-2311": {
|
||||
"locked": {
|
||||
"lastModified": 1715818734,
|
||||
"narHash": "sha256-WvAJWCwPj/6quKcsgsvQYyZRxV8ho/yUzj0HZQ34DVU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "95742536dc6debb5a8b8b78b27001c38f369f1e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-generators": {
|
||||
"inputs": {
|
||||
"nixlib": "nixlib",
|
||||
"nixpkgs": [
|
||||
"clan-core",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1716123454,
|
||||
"narHash": "sha256-U2o4UPM/UsEyIX2p11+YEQgR9HY3PmjZ2mRl/x5e4xo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-generators",
|
||||
"rev": "a63e0c83dd83fe28cc571b97129e13373436bd82",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-generators",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-images": {
|
||||
"inputs": {
|
||||
"nixos-2311": "nixos-2311",
|
||||
"nixos-unstable": [
|
||||
"clan-core",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1716132123,
|
||||
"narHash": "sha256-rATSWbPaKQfZGaemu0tHL2xfCzVIVwpuTjk+KSBC+k4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-images",
|
||||
"rev": "8c9cab8c44434c12dafc465fbf61a710c5bceb08",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-images",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1716127062,
|
||||
"narHash": "sha256-2rk8FqB/iQV2d0vQLs684/Tj5PUHaS1sFwG7fng5vXE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8a2555763c48e2410054de3f52f7310ce3241ec5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"clan-core": "clan-core"
|
||||
}
|
||||
},
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"clan-core",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": [
|
||||
"clan-core"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1716087663,
|
||||
"narHash": "sha256-zuSAGlx8Qk0OILGCC2GUyZ58/SJ5R3GZdeUNQ6IS0fQ=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "0bf1808e70ce80046b0cff821c019df2b19aabf5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"clan-core",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715940852,
|
||||
"narHash": "sha256-wJqHMg/K6X3JGAE9YLM0LsuKrKb4XiBeVaoeMNlReZg=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "2fba33a182602b9d49f0b2440513e5ee091d838b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
Reference in New Issue
Block a user