forked from clan/clan-core
Compare commits
2 Commits
main
...
a-kenji-im
Author | SHA1 | Date | |
---|---|---|---|
3a73b4cb74 | |||
968749ee63 |
@ -145,14 +145,14 @@
|
|||||||
machine.succeed("echo testing > /var/test-backups/somefile")
|
machine.succeed("echo testing > /var/test-backups/somefile")
|
||||||
|
|
||||||
# create
|
# create
|
||||||
machine.succeed("clan backups create --debug --flake ${self} test-backup")
|
machine.succeed("clan --debug --flake ${self} backups create test-backup")
|
||||||
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
|
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/mount-external-disk")
|
||||||
machine.succeed("test -f /run/unmount-external-disk")
|
machine.succeed("test -f /run/unmount-external-disk")
|
||||||
|
|
||||||
# list
|
# list
|
||||||
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
|
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
|
||||||
out = machine.succeed("clan backups list --debug --flake ${self} test-backup").strip()
|
out = machine.succeed("clan --debug --flake ${self} backups list test-backup").strip()
|
||||||
print(out)
|
print(out)
|
||||||
assert backup_id in out, f"backup {backup_id} not found in {out}"
|
assert backup_id in out, f"backup {backup_id} not found in {out}"
|
||||||
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
|
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
|
||||||
@ -160,14 +160,14 @@
|
|||||||
|
|
||||||
## borgbackup restore
|
## borgbackup restore
|
||||||
machine.succeed("rm -f /var/test-backups/somefile")
|
machine.succeed("rm -f /var/test-backups/somefile")
|
||||||
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
||||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
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/pre-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||||
|
|
||||||
## localbackup restore
|
## localbackup restore
|
||||||
machine.succeed("rm -f /var/test-backups/somefile /var/test-service/{pre,post}-restore-command")
|
machine.succeed("rm -f /var/test-backups/somefile /var/test-service/{pre,post}-restore-command")
|
||||||
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup localbackup '{localbackup_id}' >&2")
|
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup localbackup '{localbackup_id}' >&2")
|
||||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
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/pre-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||||
|
@ -1,50 +1,33 @@
|
|||||||
{ self, ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
perSystem =
|
perSystem =
|
||||||
|
{ ... }:
|
||||||
{
|
{
|
||||||
nodes,
|
# checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||||
pkgs,
|
# flash = (import ../lib/test-base.nix) {
|
||||||
lib,
|
# name = "flash";
|
||||||
...
|
# nodes.target = {
|
||||||
}:
|
# virtualisation.emptyDiskImages = [ 4096 ];
|
||||||
let
|
# virtualisation.memorySize = 3000;
|
||||||
dependencies = [
|
# environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||||
pkgs.disko
|
# environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||||
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.build.diskoScript.drvPath
|
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file
|
|
||||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
|
||||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# Currently disabled...
|
|
||||||
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";
|
|
||||||
|
|
||||||
nix.settings = {
|
# nix.settings = {
|
||||||
substituters = lib.mkForce [ ];
|
# substituters = lib.mkForce [ ];
|
||||||
hashed-mirrors = null;
|
# hashed-mirrors = null;
|
||||||
connect-timeout = lib.mkForce 3;
|
# connect-timeout = lib.mkForce 3;
|
||||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
# flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||||
experimental-features = [
|
# experimental-features = [
|
||||||
"nix-command"
|
# "nix-command"
|
||||||
"flakes"
|
# "flakes"
|
||||||
];
|
# ];
|
||||||
};
|
# };
|
||||||
};
|
# };
|
||||||
testScript = ''
|
# testScript = ''
|
||||||
start_all()
|
# start_all()
|
||||||
|
# machine.succeed("clan --debug --flake ${../..} flash --yes --disk main /dev/vdb test_install_machine")
|
||||||
machine.succeed("clan flash --debug --flake ${../..} --yes --disk main /dev/vdb test_install_machine")
|
# '';
|
||||||
'';
|
# } { inherit pkgs self; };
|
||||||
} { inherit pkgs self; };
|
# };
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
clan.machines.test_install_machine = {
|
clan.machines.test_install_machine = {
|
||||||
clan.networking.targetHost = "test_install_machine";
|
clan.networking.targetHost = "test_install_machine";
|
||||||
fileSystems."/".device = lib.mkDefault "/dev/vdb";
|
fileSystems."/".device = lib.mkDefault "/dev/null";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vdb";
|
boot.loader.grub.device = lib.mkDefault "/dev/null";
|
||||||
|
|
||||||
imports = [ self.nixosModules.test_install_machine ];
|
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.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.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
||||||
|
|
||||||
client.succeed("clan machines install --debug --flake ${../..} --yes test_install_machine root@target >&2")
|
client.succeed("clan --debug --flake ${../..} machines install --yes test_install_machine root@target >&2")
|
||||||
try:
|
try:
|
||||||
target.shutdown()
|
target.shutdown()
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
|
@ -10,7 +10,6 @@ in
|
|||||||
hostPkgs = pkgs;
|
hostPkgs = pkgs;
|
||||||
# speed-up evaluation
|
# speed-up evaluation
|
||||||
defaults = {
|
defaults = {
|
||||||
nix.package = pkgs.nixVersions.latest;
|
|
||||||
documentation.enable = lib.mkDefault false;
|
documentation.enable = lib.mkDefault false;
|
||||||
boot.isContainer = true;
|
boot.isContainer = true;
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ in
|
|||||||
defaults = {
|
defaults = {
|
||||||
documentation.enable = lib.mkDefault false;
|
documentation.enable = lib.mkDefault false;
|
||||||
nix.settings.min-free = 0;
|
nix.settings.min-free = 0;
|
||||||
nix.package = pkgs.nixVersions.latest;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# to accept external dependencies such as disko
|
# to accept external dependencies such as disko
|
||||||
|
@ -3,15 +3,9 @@
|
|||||||
options.clan.static-hosts = {
|
options.clan.static-hosts = {
|
||||||
excludeHosts = lib.mkOption {
|
excludeHosts = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default =
|
default = [ config.clanCore.machineName ];
|
||||||
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clanCore.machineName ];
|
|
||||||
description = "Hosts that should be excluded";
|
description = "Hosts that should be excluded";
|
||||||
};
|
};
|
||||||
topLevelDomain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "";
|
|
||||||
description = "Top level domain to reach hosts";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config.networking.hosts =
|
config.networking.hosts =
|
||||||
@ -30,15 +24,7 @@
|
|||||||
let
|
let
|
||||||
path = zerotierIpMachinePath machine;
|
path = zerotierIpMachinePath machine;
|
||||||
in
|
in
|
||||||
if builtins.pathExists path then
|
if builtins.pathExists path then lib.nameValuePair (builtins.readFile path) [ machine ] else null
|
||||||
lib.nameValuePair (builtins.readFile path) (
|
|
||||||
if (config.clan.static-hosts.topLevelDomain == "") then
|
|
||||||
[ machine ]
|
|
||||||
else
|
|
||||||
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
|
|
||||||
)
|
|
||||||
else
|
|
||||||
null
|
|
||||||
) filteredMachines
|
) filteredMachines
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ let
|
|||||||
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
||||||
) machines;
|
) machines;
|
||||||
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
||||||
networkId = if builtins.length networkIds == 0 then null else builtins.elemAt networkIds 0;
|
networkId = builtins.elemAt networkIds 0;
|
||||||
in
|
in
|
||||||
#TODO:trace on multiple found network-ids
|
#TODO:trace on multiple found network-ids
|
||||||
#TODO:trace on no single found networkId
|
#TODO:trace on no single found networkId
|
||||||
@ -38,7 +38,7 @@ in
|
|||||||
machines = builtins.readDir machineDir;
|
machines = builtins.readDir machineDir;
|
||||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||||
filteredMachines = lib.filterAttrs (
|
filteredMachines = lib.filterAttrs (
|
||||||
name: _: !(lib.elem name config.clan.zerotier-static-peers.excludeHosts)
|
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
||||||
) machines;
|
) machines;
|
||||||
hosts = lib.mapAttrsToList (host: _: host) (
|
hosts = lib.mapAttrsToList (host: _: host) (
|
||||||
lib.mapAttrs' (
|
lib.mapAttrs' (
|
||||||
|
@ -27,8 +27,7 @@
|
|||||||
packages = [
|
packages = [
|
||||||
select-shell
|
select-shell
|
||||||
pkgs.tea
|
pkgs.tea
|
||||||
# Better error messages than nix 2.18
|
pkgs.nix
|
||||||
pkgs.nixVersions.latest
|
|
||||||
self'.packages.tea-create-pr
|
self'.packages.tea-create-pr
|
||||||
self'.packages.merge-after-ci
|
self'.packages.merge-after-ci
|
||||||
self'.packages.pending-reviews
|
self'.packages.pending-reviews
|
||||||
|
@ -15,74 +15,56 @@ Let's get your development environment up and running:
|
|||||||
|
|
||||||
1. **Install Nix Package Manager**:
|
1. **Install Nix Package Manager**:
|
||||||
|
|
||||||
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
||||||
```bash
|
```bash
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Install direnv**:
|
2. **Install direnv**:
|
||||||
|
|
||||||
- To automatically setup a devshell on entering the directory
|
- To automatically setup a devshell on entering the directory
|
||||||
```bash
|
```bash
|
||||||
nix profile install nixpkgs#nix-direnv-flakes
|
nix profile install nixpkgs#nix-direnv-flakes
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Add direnv to your shell**:
|
3. **Add direnv to your shell**:
|
||||||
|
|
||||||
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||||
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Create a Gitea Account**:
|
4. **Create a Gitea Account**:
|
||||||
- Register an account on https://git.clan.lol
|
- Register an account on https://git.clan.lol
|
||||||
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
||||||
- Clone the repository and navigate to it
|
- Clone the repository and navigate to it
|
||||||
- Add a new remote called upstream:
|
- Add a new remote called upstream:
|
||||||
```bash
|
```bash
|
||||||
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||||
```
|
|
||||||
|
|
||||||
5. **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 [git.clan.lol]:
|
|
||||||
- Do you have an access token? No
|
|
||||||
- Username: YourUsername
|
|
||||||
- Password: YourPassword
|
|
||||||
- Set Optional settings: No
|
|
||||||
|
|
||||||
|
|
||||||
6. **Allow .envrc**:
|
|
||||||
|
|
||||||
- When you enter the directory, you'll receive an error message like this:
|
|
||||||
```bash
|
|
||||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
|
||||||
```
|
|
||||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
|
||||||
|
|
||||||
7. **(Optional) Install Git Hooks**:
|
|
||||||
- To syntax check your code you can run:
|
|
||||||
```bash
|
|
||||||
nix fmt
|
|
||||||
```
|
|
||||||
- To make this automatic install the git hooks
|
|
||||||
```bash
|
|
||||||
./scripts/pre-commit
|
|
||||||
```
|
|
||||||
|
|
||||||
8. **Open a Pull Request**:
|
|
||||||
- To automatically open up a pull request you can use our tool called:
|
|
||||||
```
|
```
|
||||||
merge-after-ci --reviewers Mic92 Lassulus Qubasa
|
|
||||||
|
5. **Allow .envrc**:
|
||||||
|
|
||||||
|
- When you enter the directory, you'll receive an error message like this:
|
||||||
|
```bash
|
||||||
|
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||||
|
```
|
||||||
|
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||||
|
|
||||||
|
6. **(Optional) Install Git Hooks**:
|
||||||
|
- To syntax check your code you can run:
|
||||||
|
```bash
|
||||||
|
nix fmt
|
||||||
```
|
```
|
||||||
|
- To make this automatic install the git hooks
|
||||||
|
```bash
|
||||||
|
./scripts/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Open a Pull Request**:
|
||||||
|
- Go to the webinterface and open up a pull request
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
|
|
||||||
|
29
docs/main.py
29
docs/main.py
@ -16,26 +16,15 @@ def define_env(env: Any) -> None:
|
|||||||
@env.macro
|
@env.macro
|
||||||
def asciinema(name: str) -> str:
|
def asciinema(name: str) -> str:
|
||||||
return f"""<div id="{name}">
|
return f"""<div id="{name}">
|
||||||
|
<script src="{asciinema_dir}/asciinema-player.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Function to load the script and then create the Asciinema player
|
AsciinemaPlayer.create('{video_dir + name}',
|
||||||
function loadAsciinemaPlayer() {{
|
document.getElementById("{name}"), {{
|
||||||
var script = document.createElement('script');
|
loop: true,
|
||||||
script.src = "{asciinema_dir}/asciinema-player.min.js";
|
autoPlay: true,
|
||||||
script.onload = function() {{
|
controls: false,
|
||||||
AsciinemaPlayer.create('{video_dir + name}', document.getElementById("{name}"), {{
|
speed: 1.5,
|
||||||
loop: true,
|
theme: "solarized-light"
|
||||||
autoPlay: true,
|
}});
|
||||||
controls: false,
|
|
||||||
speed: 1.5,
|
|
||||||
theme: "solarized-light"
|
|
||||||
}});
|
|
||||||
}};
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Load the Asciinema player script
|
|
||||||
loadAsciinemaPlayer();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{asciinema_dir}/asciinema-player.css" />
|
|
||||||
</div>"""
|
</div>"""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
site_name: Clan Documentation
|
site_name: Clan Docs
|
||||||
site_url: https://docs.clan.lol
|
site_url: https://docs.clan.lol
|
||||||
repo_url: https://git.clan.lol/clan/clan-core/
|
repo_url: https://git.clan.lol/clan/clan-core/
|
||||||
repo_name: clan-core
|
repo_name: clan-core
|
||||||
@ -96,7 +96,7 @@ site_dir: out
|
|||||||
theme:
|
theme:
|
||||||
font: false
|
font: false
|
||||||
logo: https://clan.lol/static/logo/clan-white.png
|
logo: https://clan.lol/static/logo/clan-white.png
|
||||||
favicon: https://clan.lol/static/dark-favicon/128x128.png
|
favicon: https://clan.lol/static/logo/clan-dark.png
|
||||||
name: material
|
name: material
|
||||||
features:
|
features:
|
||||||
- navigation.instant
|
- navigation.instant
|
||||||
@ -105,8 +105,7 @@ theme:
|
|||||||
- content.code.copy
|
- content.code.copy
|
||||||
- content.tabs.link
|
- content.tabs.link
|
||||||
icon:
|
icon:
|
||||||
repo: fontawesome/brands/git-alt
|
repo: fontawesome/brands/git
|
||||||
custom_dir: overrides
|
|
||||||
|
|
||||||
palette:
|
palette:
|
||||||
# Palette toggle for light mode
|
# Palette toggle for light mode
|
||||||
@ -129,6 +128,8 @@ theme:
|
|||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- static/extra.css
|
- static/extra.css
|
||||||
|
- static/asciinema-player/custom-theme.css
|
||||||
|
- static/asciinema-player/asciinema-player.css
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
social:
|
social:
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block extrahead %}
|
|
||||||
<meta property="og:title" content="Clan - Documentation, Blog & Getting Started Guide" />
|
|
||||||
<meta property="og:description" content="Documentation for Clan. The peer-to-peer machine deployment framework." />
|
|
||||||
<meta property="og:image" content="https://clan.lol/static/dark-favicon/128x128.png" />
|
|
||||||
<meta property="og:url" content="https://docs.clan.lol" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:site_name" content="Clan" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -42,22 +42,13 @@ sudo umount /dev/sdb1
|
|||||||
=== "**Linux OS**"
|
=== "**Linux OS**"
|
||||||
### Step 2. Flash Custom Installer
|
### Step 2. Flash Custom Installer
|
||||||
|
|
||||||
Using clan flash enables the inclusion of ssh public keys.
|
Using clan flash enables the inclusion of ssh public keys and disables ssh password authentication.
|
||||||
It also allows to set language and keymap currently in the installer image.
|
It also includes the language and keymap currently used into the installer image.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clan flash --flake git+https://git.clan.lol/clan/clan-core \
|
clan --flake git+https://git.clan.lol/clan/clan-core flash flash-installer --disk main /dev/sd<X>
|
||||||
--ssh-pubkey $HOME/.ssh/id_ed25519.pub \
|
|
||||||
--keymap en \
|
|
||||||
--language en \
|
|
||||||
--disk main /dev/sd<X> \
|
|
||||||
flash-installer
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `--ssh-pubkey`, `--language` and `--keymap` are optional.
|
|
||||||
Replace `$HOME/.ssh/id_ed25519.pub` with a path to your SSH public key.
|
|
||||||
If you do not have an ssh key yet, you can generate one with `ssh-keygen -t ed25519` command.
|
|
||||||
|
|
||||||
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
|
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
|
||||||
|
|
||||||
The `clan flash` utility will erase the disk. Make sure to specify the correct device
|
The `clan flash` utility will erase the disk. Make sure to specify the correct device
|
||||||
|
48
flake.lock
48
flake.lock
@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717177033,
|
"lastModified": 1716394172,
|
||||||
"narHash": "sha256-G3CZJafCO8WDy3dyA2EhpUJEmzd5gMJ2IdItAg0Hijw=",
|
"narHash": "sha256-B+pNhV8GFeCj9/MoH+qtGqKbgv6fU4hGaw2+NoYYtB0=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "0274af4c92531ebfba4a5bd493251a143bc51f3c",
|
"rev": "23c63fb09334c3e8958b57e2ddc3870b75b9111d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -27,11 +27,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717285511,
|
"lastModified": 1715865404,
|
||||||
"narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=",
|
"narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8",
|
"rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -57,11 +57,11 @@
|
|||||||
},
|
},
|
||||||
"nixos-2311": {
|
"nixos-2311": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717017538,
|
"lastModified": 1715818734,
|
||||||
"narHash": "sha256-S5kltvDDfNQM3xx9XcvzKEOyN2qk8Sa+aSOLqZ+1Ujc=",
|
"narHash": "sha256-WvAJWCwPj/6quKcsgsvQYyZRxV8ho/yUzj0HZQ34DVU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "64e468fd2652105710d86cd2ae3e65a5a6d58dec",
|
"rev": "95742536dc6debb5a8b8b78b27001c38f369f1e7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -79,11 +79,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716210724,
|
"lastModified": 1716123454,
|
||||||
"narHash": "sha256-iqQa3omRcHGpWb1ds75jS9ruA5R39FTmAkeR3J+ve1w=",
|
"narHash": "sha256-U2o4UPM/UsEyIX2p11+YEQgR9HY3PmjZ2mRl/x5e4xo=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-generators",
|
"repo": "nixos-generators",
|
||||||
"rev": "d14b286322c7f4f897ca4b1726ce38cb68596c94",
|
"rev": "a63e0c83dd83fe28cc571b97129e13373436bd82",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -100,11 +100,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717040312,
|
"lastModified": 1716132123,
|
||||||
"narHash": "sha256-yI/en4IxuCEClIUpIs3QTyYCCtmSPLOhwLJclfNwdeg=",
|
"narHash": "sha256-rATSWbPaKQfZGaemu0tHL2xfCzVIVwpuTjk+KSBC+k4=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-images",
|
"repo": "nixos-images",
|
||||||
"rev": "47bfb55316e105390dd761e0b6e8e0be09462b67",
|
"rev": "8c9cab8c44434c12dafc465fbf61a710c5bceb08",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -115,11 +115,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717298511,
|
"lastModified": 1716127062,
|
||||||
"narHash": "sha256-9sXuJn/nL+9ImeYtlspTvjt83z1wIgU+9AwfNbnq+tI=",
|
"narHash": "sha256-2rk8FqB/iQV2d0vQLs684/Tj5PUHaS1sFwG7fng5vXE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6634a0509e9e81e980b129435fbbec518ab246d0",
|
"rev": "8a2555763c48e2410054de3f52f7310ce3241ec5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -148,11 +148,11 @@
|
|||||||
"nixpkgs-stable": []
|
"nixpkgs-stable": []
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717297459,
|
"lastModified": 1716087663,
|
||||||
"narHash": "sha256-cZC2f68w5UrJ1f+2NWGV9Gx0dEYmxwomWN2B0lx0QRA=",
|
"narHash": "sha256-zuSAGlx8Qk0OILGCC2GUyZ58/SJ5R3GZdeUNQ6IS0fQ=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "ab2a43b0d21d1d37d4d5726a892f714eaeb4b075",
|
"rev": "0bf1808e70ce80046b0cff821c019df2b19aabf5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -168,11 +168,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717278143,
|
"lastModified": 1715940852,
|
||||||
"narHash": "sha256-u10aDdYrpiGOLoxzY/mJ9llST9yO8Q7K/UlROoNxzDw=",
|
"narHash": "sha256-wJqHMg/K6X3JGAE9YLM0LsuKrKb4XiBeVaoeMNlReZg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "3eb96ca1ae9edf792a8e0963cc92fddfa5a87706",
|
"rev": "2fba33a182602b9d49f0b2440513e5ee091d838b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -24,14 +24,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{ flake-parts, self, ... }:
|
inputs@{ flake-parts, ... }:
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
clan = {
|
|
||||||
# meta.name = "clan-core";
|
|
||||||
directory = self;
|
|
||||||
};
|
|
||||||
systems = [
|
systems = [
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
"aarch64-linux"
|
"aarch64-linux"
|
||||||
|
@ -17,33 +17,6 @@ let
|
|||||||
cfg = config.clan;
|
cfg = config.clan;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
|
||||||
# TODO: figure out how to print the deprecation warning
|
|
||||||
# "${inputs.nixpkgs}/nixos/modules/misc/assertions.nix"
|
|
||||||
(lib.mkRenamedOptionModule
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"clanName"
|
|
||||||
]
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"meta"
|
|
||||||
"name"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
(lib.mkRenamedOptionModule
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"clanIcon"
|
|
||||||
]
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"meta"
|
|
||||||
"icon"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
options.clan = {
|
options.clan = {
|
||||||
directory = mkOption {
|
directory = mkOption {
|
||||||
type = types.path;
|
type = types.path;
|
||||||
@ -60,27 +33,15 @@ in
|
|||||||
default = { };
|
default = { };
|
||||||
description = "Allows to include machine-specific modules i.e. machines.\${name} = { ... }";
|
description = "Allows to include machine-specific modules i.e. machines.\${name} = { ... }";
|
||||||
};
|
};
|
||||||
|
clanName = mkOption {
|
||||||
# Checks are performed in 'buildClan'
|
type = types.str;
|
||||||
# Not everyone uses flake-parts
|
description = "Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.";
|
||||||
meta = {
|
};
|
||||||
name = lib.mkOption {
|
clanIcon = mkOption {
|
||||||
type = types.nullOr types.str;
|
type = types.nullOr types.path;
|
||||||
default = null;
|
default = null;
|
||||||
description = "Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.";
|
description = "A path to an icon to be used for the clan, should be the same for all machines";
|
||||||
};
|
|
||||||
icon = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default = null;
|
|
||||||
description = "A path to an icon to be used for the clan in the GUI";
|
|
||||||
};
|
|
||||||
description = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = "A short description of the clan";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pkgsForSystem = mkOption {
|
pkgsForSystem = mkOption {
|
||||||
type = types.functionTo types.raw;
|
type = types.functionTo types.raw;
|
||||||
default = _system: null;
|
default = _system: null;
|
||||||
@ -91,7 +52,6 @@ in
|
|||||||
clanInternals = lib.mkOption {
|
clanInternals = lib.mkOption {
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
meta = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
|
||||||
all-machines-json = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
all-machines-json = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
||||||
machines = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
machines = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
||||||
machinesFunc = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
machinesFunc = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
||||||
@ -105,8 +65,9 @@ in
|
|||||||
directory
|
directory
|
||||||
specialArgs
|
specialArgs
|
||||||
machines
|
machines
|
||||||
|
clanName
|
||||||
|
clanIcon
|
||||||
pkgsForSystem
|
pkgsForSystem
|
||||||
meta
|
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -7,58 +7,16 @@
|
|||||||
directory, # The directory containing the machines subdirectory
|
directory, # The directory containing the machines subdirectory
|
||||||
specialArgs ? { }, # Extra arguments to pass to nixosSystem i.e. useful to make self available
|
specialArgs ? { }, # Extra arguments to pass to nixosSystem i.e. useful to make self available
|
||||||
machines ? { }, # allows to include machine-specific modules i.e. machines.${name} = { ... }
|
machines ? { }, # allows to include machine-specific modules i.e. machines.${name} = { ... }
|
||||||
# DEPRECATED: use meta.name instead
|
clanName, # Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.
|
||||||
clanName ? null, # Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.
|
|
||||||
# DEPRECATED: use meta.icon instead
|
|
||||||
clanIcon ? null, # A path to an icon to be used for the clan, should be the same for all machines
|
clanIcon ? null, # A path to an icon to be used for the clan, should be the same for all machines
|
||||||
meta ? { }, # A set containing clan meta: name :: string, icon :: string, description :: string
|
|
||||||
pkgsForSystem ? (_system: null), # A map from arch to pkgs, if specified this nixpkgs will be only imported once for each system.
|
pkgsForSystem ? (_system: null), # A map from arch to pkgs, if specified this nixpkgs will be only imported once for each system.
|
||||||
# This improves performance, but all nipxkgs.* options will be ignored.
|
# This improves performance, but all nipxkgs.* options will be ignored.
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
deprecationWarnings = [
|
|
||||||
(lib.warnIf (
|
|
||||||
clanName != null
|
|
||||||
) "clanName is deprecated, please use meta.name instead. ${clanName}" null)
|
|
||||||
(lib.warnIf (clanIcon != null) "clanIcon is deprecated, please use meta.icon instead" null)
|
|
||||||
];
|
|
||||||
|
|
||||||
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (
|
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (
|
||||||
builtins.readDir (directory + /machines)
|
builtins.readDir (directory + /machines)
|
||||||
);
|
);
|
||||||
|
|
||||||
mergedMeta =
|
|
||||||
let
|
|
||||||
metaFromFile =
|
|
||||||
if (builtins.pathExists "${directory}/clan/meta.json") then
|
|
||||||
let
|
|
||||||
settings = builtins.fromJSON (builtins.readFile "${directory}/clan/meta.json");
|
|
||||||
in
|
|
||||||
settings
|
|
||||||
else
|
|
||||||
{ };
|
|
||||||
legacyMeta = lib.filterAttrs (_: v: v != null) {
|
|
||||||
name = clanName;
|
|
||||||
icon = clanIcon;
|
|
||||||
};
|
|
||||||
optionsMeta = lib.filterAttrs (_: v: v != null) meta;
|
|
||||||
|
|
||||||
warnings =
|
|
||||||
builtins.map (
|
|
||||||
name:
|
|
||||||
if
|
|
||||||
metaFromFile.${name} or null != optionsMeta.${name} or null && optionsMeta.${name} or null != null
|
|
||||||
then
|
|
||||||
lib.warn "meta.${name} is set in different places. (exlicit option meta.${name} overrides ${directory}/clan/meta.json)" null
|
|
||||||
else
|
|
||||||
null
|
|
||||||
) (builtins.attrNames metaFromFile)
|
|
||||||
++ [ (if (res.name or null == null) then (throw "meta.name should be set") else null) ];
|
|
||||||
res = metaFromFile // legacyMeta // optionsMeta;
|
|
||||||
in
|
|
||||||
# Print out warnings before returning the merged result
|
|
||||||
builtins.deepSeq warnings res;
|
|
||||||
|
|
||||||
machineSettings =
|
machineSettings =
|
||||||
machineName:
|
machineName:
|
||||||
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
|
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
|
||||||
@ -100,15 +58,11 @@ let
|
|||||||
(machines.${name} or { })
|
(machines.${name} or { })
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
# Settings
|
|
||||||
clanCore.clanDir = directory;
|
|
||||||
# Inherited from clan wide settings
|
|
||||||
clanCore.clanName = meta.name or clanName;
|
|
||||||
clanCore.clanIcon = meta.icon or clanIcon;
|
|
||||||
|
|
||||||
# Machine specific settings
|
|
||||||
clanCore.machineName = name;
|
|
||||||
networking.hostName = lib.mkDefault name;
|
networking.hostName = lib.mkDefault name;
|
||||||
|
clanCore.clanName = clanName;
|
||||||
|
clanCore.clanIcon = clanIcon;
|
||||||
|
clanCore.clanDir = directory;
|
||||||
|
clanCore.machineName = name;
|
||||||
nixpkgs.hostPlatform = lib.mkDefault system;
|
nixpkgs.hostPlatform = lib.mkDefault system;
|
||||||
|
|
||||||
# speeds up nix commands by using the nixpkgs from the host system (especially useful in VMs)
|
# speeds up nix commands by using the nixpkgs from the host system (especially useful in VMs)
|
||||||
@ -173,15 +127,10 @@ let
|
|||||||
) supportedSystems
|
) supportedSystems
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
builtins.deepSeq deprecationWarnings {
|
{
|
||||||
inherit nixosConfigurations;
|
inherit nixosConfigurations;
|
||||||
|
|
||||||
clanInternals = {
|
clanInternals = {
|
||||||
# Evaluated clan meta
|
|
||||||
# Merged /clan/meta.json with overrides from buildClan
|
|
||||||
meta = mergedMeta;
|
|
||||||
|
|
||||||
# machine specifics
|
|
||||||
machines = configsPerSystem;
|
machines = configsPerSystem;
|
||||||
machinesFunc = configsFuncPerSystem;
|
machinesFunc = configsFuncPerSystem;
|
||||||
all-machines-json = lib.mapAttrs (
|
all-machines-json = lib.mapAttrs (
|
||||||
|
@ -51,14 +51,19 @@ class AppendOptionAction(argparse.Action):
|
|||||||
lst.append(values[1])
|
lst.append(values[1])
|
||||||
|
|
||||||
|
|
||||||
def flake_path(arg: str) -> str | Path:
|
def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
||||||
flake_dir = Path(arg).resolve()
|
parser = argparse.ArgumentParser(
|
||||||
if flake_dir.exists() and flake_dir.is_dir():
|
prog=prog,
|
||||||
return flake_dir
|
description="The clan cli tool.",
|
||||||
return arg
|
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 add_common_flags(parser: argparse.ArgumentParser) -> None:
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--debug",
|
"--debug",
|
||||||
help="Enable debug logging",
|
help="Enable debug logging",
|
||||||
@ -75,6 +80,12 @@ def add_common_flags(parser: argparse.ArgumentParser) -> None:
|
|||||||
default=[],
|
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(
|
parser.add_argument(
|
||||||
"--flake",
|
"--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",
|
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",
|
||||||
@ -83,30 +94,6 @@ def add_common_flags(parser: argparse.ArgumentParser) -> None:
|
|||||||
type=flake_path,
|
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()
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
parser_backups = subparsers.add_parser(
|
parser_backups = subparsers.add_parser(
|
||||||
@ -152,13 +139,12 @@ For more detailed information, visit: https://docs.clan.lol/getting-started
|
|||||||
),
|
),
|
||||||
formatter_class=argparse.RawTextHelpFormatter,
|
formatter_class=argparse.RawTextHelpFormatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
flakes.register_parser(parser_flake)
|
flakes.register_parser(parser_flake)
|
||||||
|
|
||||||
parser_config = subparsers.add_parser(
|
parser_config = subparsers.add_parser(
|
||||||
"config",
|
"config",
|
||||||
help="read a nixos configuration option",
|
help="set nixos configuration",
|
||||||
description="read a nixos configuration option",
|
description="set nixos configuration",
|
||||||
epilog=(
|
epilog=(
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
@ -222,7 +208,7 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/secr
|
|||||||
|
|
||||||
This subcommand provides an interface to facts of clan machines.
|
This subcommand provides an interface to facts of clan machines.
|
||||||
Facts are artifacts that a service can generate.
|
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 be referenced by other machines directly.
|
||||||
Public facts can include: ip addresses, public keys.
|
Public facts can include: ip addresses, public keys.
|
||||||
Secret facts can include: passwords, private keys.
|
Secret facts can include: passwords, private keys.
|
||||||
@ -237,7 +223,7 @@ Examples:
|
|||||||
|
|
||||||
$ clan facts generate
|
$ clan facts generate
|
||||||
Will generate facts for all machines.
|
Will generate facts for all machines.
|
||||||
|
|
||||||
$ clan facts generate --service [SERVICE] --regenerate
|
$ clan facts generate --service [SERVICE] --regenerate
|
||||||
Will regenerate facts, if they are already generated for a specific service.
|
Will regenerate facts, if they are already generated for a specific service.
|
||||||
This is especially useful for resetting certain passwords while leaving the rest
|
This is especially useful for resetting certain passwords while leaving the rest
|
||||||
@ -264,7 +250,7 @@ Examples:
|
|||||||
List all the machines managed by clan.
|
List all the machines managed by clan.
|
||||||
|
|
||||||
$ clan machines update [MACHINES]
|
$ 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.
|
will attempt to update every configured machine.
|
||||||
|
|
||||||
$ clan machines install [MACHINES] [TARGET_HOST]
|
$ clan machines install [MACHINES] [TARGET_HOST]
|
||||||
@ -299,8 +285,6 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/depl
|
|||||||
if argcomplete:
|
if argcomplete:
|
||||||
argcomplete.autocomplete(parser)
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
register_common_flags(parser)
|
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -34,17 +33,13 @@ def create_backup(machine: Machine, provider: str | None = None) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def create_command(args: argparse.Namespace) -> 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)
|
machine = Machine(name=args.machine, flake=args.flake)
|
||||||
create_backup(machine=machine, provider=args.provider)
|
create_backup(machine=machine, provider=args.provider)
|
||||||
|
|
||||||
|
|
||||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine", type=str, help="machine in the flake to create backups of"
|
"machine", type=str, help="machine in the flake to create backups of"
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument("--provider", type=str, help="backup provider to use")
|
parser.add_argument("--provider", type=str, help="backup provider to use")
|
||||||
parser.set_defaults(func=create_command)
|
parser.set_defaults(func=create_command)
|
||||||
|
@ -3,7 +3,6 @@ import json
|
|||||||
import subprocess
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -49,8 +48,6 @@ def list_backups(machine: Machine, provider: str | None = None) -> list[Backup]:
|
|||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
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)
|
machine = Machine(name=args.machine, flake=args.flake)
|
||||||
backups = list_backups(machine=machine, provider=args.provider)
|
backups = list_backups(machine=machine, provider=args.provider)
|
||||||
for backup in backups:
|
for backup in backups:
|
||||||
@ -58,9 +55,8 @@ def list_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine", type=str, help="machine in the flake to show backups of"
|
"machine", type=str, help="machine in the flake to show backups of"
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
parser.add_argument("--provider", type=str, help="backup provider to filter by")
|
parser.add_argument("--provider", type=str, help="backup provider to filter by")
|
||||||
parser.set_defaults(func=list_command)
|
parser.set_defaults(func=list_command)
|
||||||
|
@ -62,8 +62,6 @@ def restore_backup(
|
|||||||
|
|
||||||
|
|
||||||
def restore_command(args: argparse.Namespace) -> None:
|
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)
|
machine = Machine(name=args.machine, flake=args.flake)
|
||||||
restore_backup(
|
restore_backup(
|
||||||
machine=machine,
|
machine=machine,
|
||||||
|
@ -56,7 +56,7 @@ def handle_output(process: subprocess.Popen, log: Log) -> tuple[str, str]:
|
|||||||
sys.stderr.buffer.write(ret)
|
sys.stderr.buffer.write(ret)
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
stderr_buf += ret
|
stderr_buf += ret
|
||||||
return stdout_buf.decode("utf-8", "replace"), stderr_buf.decode("utf-8", "replace")
|
return stdout_buf.decode("utf-8"), stderr_buf.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
class TimeTable:
|
class TimeTable:
|
||||||
@ -101,19 +101,13 @@ TIME_TABLE = TimeTable()
|
|||||||
def run(
|
def run(
|
||||||
cmd: list[str],
|
cmd: list[str],
|
||||||
*,
|
*,
|
||||||
input: bytes | None = None, # noqa: A002
|
|
||||||
env: dict[str, str] | None = None,
|
env: dict[str, str] | None = None,
|
||||||
cwd: Path = Path.cwd(),
|
cwd: Path = Path.cwd(),
|
||||||
log: Log = Log.STDERR,
|
log: Log = Log.STDERR,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
error_msg: str | None = None,
|
error_msg: str | None = None,
|
||||||
) -> CmdOut:
|
) -> CmdOut:
|
||||||
if input:
|
glog.debug(f"$: {shlex.join(cmd)} \nCaller: {get_caller()}")
|
||||||
glog.debug(
|
|
||||||
f"""$: echo "{input.decode('utf-8', 'replace')}" | {shlex.join(cmd)} \nCaller: {get_caller()}"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
glog.debug(f"$: {shlex.join(cmd)} \nCaller: {get_caller()}")
|
|
||||||
tstart = datetime.now()
|
tstart = datetime.now()
|
||||||
|
|
||||||
# Start the subprocess
|
# Start the subprocess
|
||||||
@ -126,10 +120,7 @@ def run(
|
|||||||
)
|
)
|
||||||
stdout_buf, stderr_buf = handle_output(process, log)
|
stdout_buf, stderr_buf = handle_output(process, log)
|
||||||
|
|
||||||
if input:
|
rc = process.wait()
|
||||||
process.communicate(input)
|
|
||||||
else:
|
|
||||||
process.wait()
|
|
||||||
tend = datetime.now()
|
tend = datetime.now()
|
||||||
|
|
||||||
global TIME_TABLE
|
global TIME_TABLE
|
||||||
@ -145,7 +136,7 @@ def run(
|
|||||||
msg=error_msg,
|
msg=error_msg,
|
||||||
)
|
)
|
||||||
|
|
||||||
if check and process.returncode != 0:
|
if check and rc != 0:
|
||||||
raise ClanCmdError(cmd_out)
|
raise ClanCmdError(cmd_out)
|
||||||
|
|
||||||
return cmd_out
|
return cmd_out
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
import threading
|
|
||||||
from collections.abc import Callable, Iterable
|
|
||||||
from types import ModuleType
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from .cmd import run
|
|
||||||
from .nix import nix_eval
|
|
||||||
|
|
||||||
"""
|
|
||||||
This module provides dynamic completions.
|
|
||||||
The completions should feel fast.
|
|
||||||
We target a maximum of 1second on our average machine.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
argcomplete: ModuleType | None = None
|
|
||||||
try:
|
|
||||||
import argcomplete # type: ignore[no-redef]
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# The default completion timeout for commands
|
|
||||||
COMPLETION_TIMEOUT: int = 3
|
|
||||||
|
|
||||||
|
|
||||||
def clan_dir(flake: str | None) -> str | None:
|
|
||||||
from .dirs import get_clan_flake_toplevel_or_env
|
|
||||||
|
|
||||||
path_result = get_clan_flake_toplevel_or_env()
|
|
||||||
return str(path_result) if path_result is not None else None
|
|
||||||
|
|
||||||
|
|
||||||
def complete_machines(
|
|
||||||
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
|
|
||||||
) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Provides completion functionality for machine names configured in the clan.
|
|
||||||
"""
|
|
||||||
machines: list[str] = []
|
|
||||||
|
|
||||||
def run_cmd() -> None:
|
|
||||||
try:
|
|
||||||
# In my tests this was consistently faster than:
|
|
||||||
# nix eval .#nixosConfigurations --apply builtins.attrNames
|
|
||||||
cmd = ["nix", "flake", "show", "--system", "no-eval", "--json"]
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
|
||||||
cmd.append(clan_dir_result)
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
data = json.loads(result.stdout)
|
|
||||||
try:
|
|
||||||
machines.extend(data.get("nixosConfigurations").keys())
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
thread = threading.Thread(target=run_cmd)
|
|
||||||
thread.start()
|
|
||||||
thread.join(timeout=COMPLETION_TIMEOUT)
|
|
||||||
|
|
||||||
if thread.is_alive():
|
|
||||||
return iter([])
|
|
||||||
|
|
||||||
machines_dict = {name: "machine" for name in machines}
|
|
||||||
return machines_dict
|
|
||||||
|
|
||||||
|
|
||||||
def complete_services_for_machine(
|
|
||||||
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
|
|
||||||
) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Provides completion functionality for machine facts generation services.
|
|
||||||
"""
|
|
||||||
services: list[str] = []
|
|
||||||
# TODO: consolidate, if multiple machines are used
|
|
||||||
machines: list[str] = parsed_args.machines
|
|
||||||
|
|
||||||
def run_cmd() -> None:
|
|
||||||
try:
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
|
||||||
flake = clan_dir_result
|
|
||||||
else:
|
|
||||||
flake = "."
|
|
||||||
services_result = json.loads(
|
|
||||||
run(
|
|
||||||
nix_eval(
|
|
||||||
flags=[
|
|
||||||
f"{flake}#nixosConfigurations.{machines[0]}.config.clanCore.facts.services",
|
|
||||||
"--apply",
|
|
||||||
"builtins.attrNames",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).stdout.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
services.extend(services_result)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
thread = threading.Thread(target=run_cmd)
|
|
||||||
thread.start()
|
|
||||||
thread.join(timeout=COMPLETION_TIMEOUT)
|
|
||||||
|
|
||||||
if thread.is_alive():
|
|
||||||
return iter([])
|
|
||||||
|
|
||||||
services_dict = {name: "service" for name in services}
|
|
||||||
return services_dict
|
|
||||||
|
|
||||||
|
|
||||||
def complete_secrets(
|
|
||||||
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
|
|
||||||
) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Provides completion functionality for clan secrets
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from .secrets.secrets import ListSecretsOptions, list_secrets
|
|
||||||
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
|
||||||
flake = clan_dir_result
|
|
||||||
else:
|
|
||||||
flake = "."
|
|
||||||
|
|
||||||
options = ListSecretsOptions(
|
|
||||||
flake=Path(flake),
|
|
||||||
pattern=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
secrets = list_secrets(options.flake, options.pattern)
|
|
||||||
|
|
||||||
secrets_dict = {name: "secret" for name in secrets}
|
|
||||||
return secrets_dict
|
|
||||||
|
|
||||||
|
|
||||||
def add_dynamic_completer(
|
|
||||||
action: argparse.Action,
|
|
||||||
completer: Callable[..., Iterable[str]],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Add a completion function to an argparse action, this will only be added,
|
|
||||||
if the argcomplete module is loaded.
|
|
||||||
"""
|
|
||||||
if argcomplete:
|
|
||||||
# mypy doesn't check this correctly, so we ignore it
|
|
||||||
action.completer = completer # type: ignore[attr-defined]
|
|
@ -150,15 +150,6 @@ def read_machine_option_value(
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def get_option(args: argparse.Namespace) -> None:
|
|
||||||
print(
|
|
||||||
read_machine_option_value(
|
|
||||||
args.flake, args.machine, args.option, args.show_trace
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Currently writing is disabled
|
|
||||||
def get_or_set_option(args: argparse.Namespace) -> None:
|
def get_or_set_option(args: argparse.Namespace) -> None:
|
||||||
if args.value == []:
|
if args.value == []:
|
||||||
print(
|
print(
|
||||||
@ -316,7 +307,7 @@ def register_parser(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# inject callback function to process the input later
|
# inject callback function to process the input later
|
||||||
parser.set_defaults(func=get_option)
|
parser.set_defaults(func=get_or_set_option)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--machine",
|
"--machine",
|
||||||
"-m",
|
"-m",
|
||||||
@ -354,6 +345,13 @@ def register_parser(
|
|||||||
type=str,
|
type=str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"value",
|
||||||
|
# force this arg to be set
|
||||||
|
nargs="*",
|
||||||
|
help="option value to set (if omitted, the current value is printed)",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> None:
|
def main(argv: list[str] | None = None) -> None:
|
||||||
if argv is None:
|
if argv is None:
|
||||||
|
@ -16,54 +16,10 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
check_parser = subparser.add_parser(
|
check_parser = subparser.add_parser("check", help="check if facts are up to date")
|
||||||
"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)
|
register_check_parser(check_parser)
|
||||||
|
|
||||||
list_parser = subparser.add_parser(
|
list_parser = subparser.add_parser("list", help="list all facts")
|
||||||
"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)
|
register_list_parser(list_parser)
|
||||||
|
|
||||||
parser_generate = subparser.add_parser(
|
parser_generate = subparser.add_parser(
|
||||||
@ -106,26 +62,5 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/secr
|
|||||||
)
|
)
|
||||||
register_generate_parser(parser_generate)
|
register_generate_parser(parser_generate)
|
||||||
|
|
||||||
parser_upload = subparser.add_parser(
|
parser_upload = subparser.add_parser("upload", help="upload secrets for machines")
|
||||||
"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)
|
register_upload_parser(parser_upload)
|
||||||
|
@ -2,7 +2,6 @@ import argparse
|
|||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -55,12 +54,10 @@ def check_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_check_parser(parser: argparse.ArgumentParser) -> None:
|
def register_check_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
help="The machine to check secrets for",
|
help="The machine to check secrets for",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--service",
|
"--service",
|
||||||
help="the service to check",
|
help="the service to check",
|
||||||
|
@ -9,11 +9,6 @@ from tempfile import TemporaryDirectory
|
|||||||
|
|
||||||
from clan_cli.cmd import run
|
from clan_cli.cmd import run
|
||||||
|
|
||||||
from ..completions import (
|
|
||||||
add_dynamic_completer,
|
|
||||||
complete_machines,
|
|
||||||
complete_services_for_machine,
|
|
||||||
)
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..git import commit_files
|
from ..git import commit_files
|
||||||
from ..machines.inventory import get_all_machines, get_selected_machines
|
from ..machines.inventory import get_all_machines, get_selected_machines
|
||||||
@ -32,7 +27,6 @@ def read_multiline_input(prompt: str = "Finish with Ctrl-D") -> str:
|
|||||||
"""
|
"""
|
||||||
print(prompt, flush=True)
|
print(prompt, flush=True)
|
||||||
proc = subprocess.run(["cat"], stdout=subprocess.PIPE, text=True)
|
proc = subprocess.run(["cat"], stdout=subprocess.PIPE, text=True)
|
||||||
log.info("Input received. Processing...")
|
|
||||||
return proc.stdout
|
return proc.stdout
|
||||||
|
|
||||||
|
|
||||||
@ -215,30 +209,26 @@ def generate_facts(
|
|||||||
|
|
||||||
def generate_command(args: argparse.Namespace) -> None:
|
def generate_command(args: argparse.Namespace) -> None:
|
||||||
if len(args.machines) == 0:
|
if len(args.machines) == 0:
|
||||||
machines = get_all_machines(args.flake, args.option)
|
machines = get_all_machines(args.flake)
|
||||||
else:
|
else:
|
||||||
machines = get_selected_machines(args.flake, args.option, args.machines)
|
machines = get_selected_machines(args.flake, args.machines)
|
||||||
generate_facts(machines, args.service, args.regenerate)
|
generate_facts(machines, args.service, args.regenerate)
|
||||||
|
|
||||||
|
|
||||||
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machines",
|
"machines",
|
||||||
type=str,
|
type=str,
|
||||||
help="machine to generate facts for. if empty, generate facts for all machines",
|
help="machine to generate facts for. if empty, generate facts for all machines",
|
||||||
nargs="*",
|
nargs="*",
|
||||||
default=[],
|
default=[],
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
parser.add_argument(
|
||||||
|
|
||||||
service_parser = parser.add_argument(
|
|
||||||
"--service",
|
"--service",
|
||||||
type=str,
|
type=str,
|
||||||
help="service to generate facts for, if empty, generate facts for every service",
|
help="service to generate facts for, if empty, generate facts for every service",
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
add_dynamic_completer(service_parser, complete_services_for_machine)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--regenerate",
|
"--regenerate",
|
||||||
type=bool,
|
type=bool,
|
||||||
|
@ -3,7 +3,6 @@ import importlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -38,10 +37,8 @@ def get_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
help="The machine to print facts for",
|
help="The machine to print facts for",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.set_defaults(func=get_command)
|
parser.set_defaults(func=get_command)
|
||||||
|
@ -2,7 +2,6 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from clan_cli.cmd import Log, run
|
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
from clan_cli.nix import nix_shell
|
from clan_cli.nix import nix_shell
|
||||||
|
|
||||||
@ -16,25 +15,25 @@ class SecretStore(SecretStoreBase):
|
|||||||
def set(
|
def set(
|
||||||
self, service: str, name: str, value: bytes, groups: list[str]
|
self, service: str, name: str, value: bytes, groups: list[str]
|
||||||
) -> Path | None:
|
) -> Path | None:
|
||||||
run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#pass"],
|
["nixpkgs#pass"],
|
||||||
["pass", "insert", "-m", f"machines/{self.machine.name}/{name}"],
|
["pass", "insert", "-m", f"machines/{self.machine.name}/{name}"],
|
||||||
),
|
),
|
||||||
input=value,
|
input=value,
|
||||||
log=Log.BOTH,
|
check=True,
|
||||||
error_msg=f"Failed to insert secret {name}",
|
|
||||||
)
|
)
|
||||||
return None # we manage the files outside of the git repo
|
return None # we manage the files outside of the git repo
|
||||||
|
|
||||||
def get(self, service: str, name: str) -> bytes:
|
def get(self, service: str, name: str) -> bytes:
|
||||||
return run(
|
return subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#pass"],
|
["nixpkgs#pass"],
|
||||||
["pass", "show", f"machines/{self.machine.name}/{name}"],
|
["pass", "show", f"machines/{self.machine.name}/{name}"],
|
||||||
),
|
),
|
||||||
error_msg=f"Failed to get secret {name}",
|
check=True,
|
||||||
).stdout.encode("utf-8")
|
stdout=subprocess.PIPE,
|
||||||
|
).stdout
|
||||||
|
|
||||||
def exists(self, service: str, name: str) -> bool:
|
def exists(self, service: str, name: str) -> bool:
|
||||||
password_store = os.environ.get(
|
password_store = os.environ.get(
|
||||||
@ -49,7 +48,7 @@ class SecretStore(SecretStoreBase):
|
|||||||
)
|
)
|
||||||
hashes = []
|
hashes = []
|
||||||
hashes.append(
|
hashes.append(
|
||||||
run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#git"],
|
["nixpkgs#git"],
|
||||||
[
|
[
|
||||||
@ -62,15 +61,13 @@ class SecretStore(SecretStoreBase):
|
|||||||
f"machines/{self.machine.name}",
|
f"machines/{self.machine.name}",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
check=False,
|
stdout=subprocess.PIPE,
|
||||||
)
|
).stdout.strip()
|
||||||
.stdout.encode("utf-8")
|
|
||||||
.strip()
|
|
||||||
)
|
)
|
||||||
for symlink in Path(password_store).glob(f"machines/{self.machine.name}/**/*"):
|
for symlink in Path(password_store).glob(f"machines/{self.machine.name}/**/*"):
|
||||||
if symlink.is_symlink():
|
if symlink.is_symlink():
|
||||||
hashes.append(
|
hashes.append(
|
||||||
run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#git"],
|
["nixpkgs#git"],
|
||||||
[
|
[
|
||||||
@ -83,10 +80,8 @@ class SecretStore(SecretStoreBase):
|
|||||||
str(symlink),
|
str(symlink),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
check=False,
|
stdout=subprocess.PIPE,
|
||||||
)
|
).stdout.strip()
|
||||||
.stdout.encode("utf-8")
|
|
||||||
.strip()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# we sort the hashes to make sure that the order is always the same
|
# we sort the hashes to make sure that the order is always the same
|
||||||
|
@ -5,7 +5,6 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
|
|
||||||
@ -33,8 +32,6 @@ def upload_secrets(machine: Machine) -> None:
|
|||||||
" ".join(["ssh"] + ssh_cmd[2:]),
|
" ".join(["ssh"] + ssh_cmd[2:]),
|
||||||
"-az",
|
"-az",
|
||||||
"--delete",
|
"--delete",
|
||||||
"--chown=root:root",
|
|
||||||
"--chmod=D700,F600",
|
|
||||||
f"{tempdir!s}/",
|
f"{tempdir!s}/",
|
||||||
f"{host.user}@{host.host}:{machine.secrets_upload_directory}/",
|
f"{host.user}@{host.host}:{machine.secrets_upload_directory}/",
|
||||||
],
|
],
|
||||||
@ -49,10 +46,8 @@ def upload_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_upload_parser(parser: argparse.ArgumentParser) -> None:
|
def register_upload_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
help="The machine to upload secrets to",
|
help="The machine to upload secrets to",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.set_defaults(func=upload_command)
|
parser.set_defaults(func=upload_command)
|
||||||
|
@ -3,6 +3,7 @@ import importlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
@ -12,7 +13,6 @@ from tempfile import TemporaryDirectory
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .cmd import Log, run
|
from .cmd import Log, run
|
||||||
from .completions import add_dynamic_completer, complete_machines
|
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
from .facts.secret_modules import SecretStoreBase
|
from .facts.secret_modules import SecretStoreBase
|
||||||
from .machines.machines import Machine
|
from .machines.machines import Machine
|
||||||
@ -21,6 +21,63 @@ from .nix import nix_shell
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def list_available_ssh_keys(ssh_dir: Path = Path("~/.ssh").expanduser()) -> list[Path]:
|
||||||
|
"""
|
||||||
|
Function to list all available SSH public keys in the default .ssh directory.
|
||||||
|
Returns a list of paths to available public key files.
|
||||||
|
"""
|
||||||
|
public_key_patterns = ["*.pub"]
|
||||||
|
available_keys: list[Path] = []
|
||||||
|
|
||||||
|
# Check for public key files
|
||||||
|
for pattern in public_key_patterns:
|
||||||
|
for key_path in ssh_dir.glob(pattern):
|
||||||
|
if key_path.is_file():
|
||||||
|
available_keys.append(key_path)
|
||||||
|
|
||||||
|
return available_keys
|
||||||
|
|
||||||
|
|
||||||
|
def read_public_key_contents(public_keys: list[Path]) -> list[str]:
|
||||||
|
"""
|
||||||
|
Function to read and return the contents of available SSH public keys.
|
||||||
|
Returns a list containing the contents of each public key.
|
||||||
|
"""
|
||||||
|
public_key_contents = []
|
||||||
|
|
||||||
|
for key_path in public_keys:
|
||||||
|
try:
|
||||||
|
with open(key_path.expanduser()) as key_file:
|
||||||
|
public_key_contents.append(key_file.read().strip())
|
||||||
|
except FileNotFoundError:
|
||||||
|
log.error(f"Public key file not found: {key_path}")
|
||||||
|
|
||||||
|
return public_key_contents
|
||||||
|
|
||||||
|
|
||||||
|
def get_keymap_and_locale() -> dict[str, str]:
|
||||||
|
locale = "en_US.UTF-8"
|
||||||
|
keymap = "en"
|
||||||
|
|
||||||
|
# Execute the `localectl status` command
|
||||||
|
result = run(["localectl", "status"])
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
output = result.stdout
|
||||||
|
|
||||||
|
# Extract the Keymap (X11 Layout)
|
||||||
|
keymap_match = re.search(r"X11 Layout:\s+(.*)", output)
|
||||||
|
if keymap_match:
|
||||||
|
keymap = keymap_match.group(1)
|
||||||
|
|
||||||
|
# Extract the System Locale (LANG only)
|
||||||
|
locale_match = re.search(r"System Locale:\s+LANG=(.*)", output)
|
||||||
|
if locale_match:
|
||||||
|
locale = locale_match.group(1)
|
||||||
|
|
||||||
|
return {"keymap": keymap, "locale": locale}
|
||||||
|
|
||||||
|
|
||||||
def flash_machine(
|
def flash_machine(
|
||||||
machine: Machine,
|
machine: Machine,
|
||||||
*,
|
*,
|
||||||
@ -28,9 +85,7 @@ def flash_machine(
|
|||||||
disks: dict[str, str],
|
disks: dict[str, str],
|
||||||
system_config: dict[str, Any],
|
system_config: dict[str, Any],
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
write_efi_boot_entries: bool,
|
|
||||||
debug: bool,
|
debug: bool,
|
||||||
extra_args: list[str] = [],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||||
secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore(
|
secret_facts_store: SecretStoreBase = secret_facts_module.SecretStore(
|
||||||
@ -57,8 +112,6 @@ def flash_machine(
|
|||||||
disko_install.append("sudo")
|
disko_install.append("sudo")
|
||||||
|
|
||||||
disko_install.append("disko-install")
|
disko_install.append("disko-install")
|
||||||
if write_efi_boot_entries:
|
|
||||||
disko_install.append("--write-efi-boot-entries")
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
disko_install.append("--dry-run")
|
disko_install.append("--dry-run")
|
||||||
if debug:
|
if debug:
|
||||||
@ -75,8 +128,6 @@ def flash_machine(
|
|||||||
json.dumps(system_config),
|
json.dumps(system_config),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
disko_install.extend(["--option", "dry-run", "true"])
|
|
||||||
disko_install.extend(extra_args)
|
|
||||||
|
|
||||||
cmd = nix_shell(
|
cmd = nix_shell(
|
||||||
["nixpkgs#disko"],
|
["nixpkgs#disko"],
|
||||||
@ -97,8 +148,6 @@ class FlashOptions:
|
|||||||
mode: str
|
mode: str
|
||||||
language: str
|
language: str
|
||||||
keymap: str
|
keymap: str
|
||||||
write_efi_boot_entries: bool
|
|
||||||
nix_options: list[str]
|
|
||||||
|
|
||||||
|
|
||||||
class AppendDiskAction(argparse.Action):
|
class AppendDiskAction(argparse.Action):
|
||||||
@ -127,10 +176,8 @@ def flash_command(args: argparse.Namespace) -> None:
|
|||||||
confirm=not args.yes,
|
confirm=not args.yes,
|
||||||
debug=args.debug,
|
debug=args.debug,
|
||||||
mode=args.mode,
|
mode=args.mode,
|
||||||
language=args.language,
|
language=args.lang,
|
||||||
keymap=args.keymap,
|
keymap=args.keymap,
|
||||||
write_efi_boot_entries=args.write_efi_boot_entries,
|
|
||||||
nix_options=args.option,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = Machine(opts.machine, flake=opts.flake)
|
machine = Machine(opts.machine, flake=opts.flake)
|
||||||
@ -144,22 +191,40 @@ def flash_command(args: argparse.Namespace) -> None:
|
|||||||
if ask != "y":
|
if ask != "y":
|
||||||
return
|
return
|
||||||
|
|
||||||
extra_config: dict[str, Any] = {}
|
root_keys = read_public_key_contents(opts.ssh_keys_path)
|
||||||
if opts.ssh_keys_path:
|
if opts.confirm and not root_keys:
|
||||||
root_keys = []
|
msg = "Should we add your SSH public keys to the root user? [y/N] "
|
||||||
for key_path in opts.ssh_keys_path:
|
ask = input(msg)
|
||||||
try:
|
if ask == "y":
|
||||||
root_keys.append(key_path.read_text())
|
pubkeys = list_available_ssh_keys()
|
||||||
except OSError as e:
|
root_keys.extend(read_public_key_contents(pubkeys))
|
||||||
raise ClanError(f"Cannot read SSH public key file: {key_path}: {e}")
|
else:
|
||||||
extra_config["users"] = {
|
raise ClanError(
|
||||||
"users": {"root": {"openssh": {"authorizedKeys": {"keys": root_keys}}}}
|
"No SSH public keys provided. Use --ssh-pubkey to add keys."
|
||||||
}
|
)
|
||||||
if opts.keymap:
|
elif not opts.confirm and not root_keys:
|
||||||
extra_config["console"] = {"keyMap": opts.keymap}
|
pubkeys = list_available_ssh_keys()
|
||||||
|
root_keys.extend(read_public_key_contents(pubkeys))
|
||||||
|
# If ssh-pubkeys set, we don't need to ask for confirmation
|
||||||
|
elif opts.confirm and root_keys:
|
||||||
|
pass
|
||||||
|
elif not opts.confirm and root_keys:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ClanError("Invalid state")
|
||||||
|
|
||||||
if opts.language:
|
localectl = get_keymap_and_locale()
|
||||||
extra_config["i18n"] = {"defaultLocale": opts.language}
|
extra_config = {
|
||||||
|
"users": {
|
||||||
|
"users": {"root": {"openssh": {"authorizedKeys": {"keys": root_keys}}}}
|
||||||
|
},
|
||||||
|
"console": {
|
||||||
|
"keyMap": opts.keymap if opts.keymap else localectl["keymap"],
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"defaultLocale": opts.language if opts.language else localectl["locale"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
flash_machine(
|
flash_machine(
|
||||||
machine,
|
machine,
|
||||||
@ -168,19 +233,15 @@ def flash_command(args: argparse.Namespace) -> None:
|
|||||||
system_config=extra_config,
|
system_config=extra_config,
|
||||||
dry_run=opts.dry_run,
|
dry_run=opts.dry_run,
|
||||||
debug=opts.debug,
|
debug=opts.debug,
|
||||||
write_efi_boot_entries=opts.write_efi_boot_entries,
|
|
||||||
extra_args=opts.nix_options,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_parser(parser: argparse.ArgumentParser) -> None:
|
def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
type=str,
|
type=str,
|
||||||
help="machine to install",
|
help="machine to install",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--disk",
|
"--disk",
|
||||||
type=str,
|
type=str,
|
||||||
@ -190,14 +251,12 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
help="device to flash to",
|
help="device to flash to",
|
||||||
default={},
|
default={},
|
||||||
)
|
)
|
||||||
mode_help = textwrap.dedent(
|
mode_help = textwrap.dedent("""\
|
||||||
"""\
|
|
||||||
Specify the mode of operation. Valid modes are: format, mount."
|
Specify the mode of operation. Valid modes are: format, mount."
|
||||||
Format will format the disk before installing.
|
Format will format the disk before installing.
|
||||||
Mount will mount the disk before installing.
|
Mount will mount the disk before installing.
|
||||||
Mount is useful for updating an existing system without losing data.
|
Mount is useful for updating an existing system without losing data.
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--mode",
|
"--mode",
|
||||||
type=str,
|
type=str,
|
||||||
@ -213,7 +272,7 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
help="ssh pubkey file to add to the root user. Can be used multiple times",
|
help="ssh pubkey file to add to the root user. Can be used multiple times",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--language",
|
"--lang",
|
||||||
type=str,
|
type=str,
|
||||||
help="system language",
|
help="system language",
|
||||||
)
|
)
|
||||||
@ -234,16 +293,4 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
default=False,
|
default=False,
|
||||||
action="store_true",
|
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)
|
parser.set_defaults(func=flash_command)
|
||||||
|
@ -5,7 +5,6 @@ from .create import register_create_parser
|
|||||||
from .delete import register_delete_parser
|
from .delete import register_delete_parser
|
||||||
from .install import register_install_parser
|
from .install import register_install_parser
|
||||||
from .list import register_list_parser
|
from .list import register_list_parser
|
||||||
from .show import register_show_parser
|
|
||||||
from .update import register_update_parser
|
from .update import register_update_parser
|
||||||
|
|
||||||
|
|
||||||
@ -63,17 +62,6 @@ Examples:
|
|||||||
)
|
)
|
||||||
register_list_parser(list_parser)
|
register_list_parser(list_parser)
|
||||||
|
|
||||||
show_parser = subparser.add_parser(
|
|
||||||
"show",
|
|
||||||
help="Show a machine",
|
|
||||||
epilog=(
|
|
||||||
"""
|
|
||||||
This subcommand shows the details of a machine managed by this clan like icon, description, etc
|
|
||||||
"""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
register_show_parser(show_parser)
|
|
||||||
|
|
||||||
install_parser = subparser.add_parser(
|
install_parser = subparser.add_parser(
|
||||||
"install",
|
"install",
|
||||||
help="Install a machine",
|
help="Install a machine",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..dirs import specific_machine_dir
|
from ..dirs import specific_machine_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
|
||||||
@ -15,7 +14,5 @@ def delete_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_delete_parser(parser: argparse.ArgumentParser) -> None:
|
def register_delete_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument("host", type=str)
|
parser.add_argument("host", type=str)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.set_defaults(func=delete_command)
|
parser.set_defaults(func=delete_command)
|
||||||
|
@ -8,7 +8,6 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..facts.generate import generate_facts
|
from ..facts.generate import generate_facts
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
@ -27,7 +26,6 @@ def install_nixos(
|
|||||||
debug: bool = False,
|
debug: bool = False,
|
||||||
password: str | None = None,
|
password: str | None = None,
|
||||||
no_reboot: bool = False,
|
no_reboot: bool = False,
|
||||||
extra_args: list[str] = [],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||||
log.info(f"installing {machine.name}")
|
log.info(f"installing {machine.name}")
|
||||||
@ -58,7 +56,6 @@ def install_nixos(
|
|||||||
f"{machine.flake}#{machine.name}",
|
f"{machine.flake}#{machine.name}",
|
||||||
"--extra-files",
|
"--extra-files",
|
||||||
str(tmpdir),
|
str(tmpdir),
|
||||||
*extra_args,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if no_reboot:
|
if no_reboot:
|
||||||
@ -98,7 +95,6 @@ class InstallOptions:
|
|||||||
debug: bool
|
debug: bool
|
||||||
no_reboot: bool
|
no_reboot: bool
|
||||||
json_ssh_deploy: dict[str, str] | None
|
json_ssh_deploy: dict[str, str] | None
|
||||||
nix_options: list[str]
|
|
||||||
|
|
||||||
|
|
||||||
def install_command(args: argparse.Namespace) -> None:
|
def install_command(args: argparse.Namespace) -> None:
|
||||||
@ -131,7 +127,6 @@ def install_command(args: argparse.Namespace) -> None:
|
|||||||
debug=args.debug,
|
debug=args.debug,
|
||||||
no_reboot=args.no_reboot,
|
no_reboot=args.no_reboot,
|
||||||
json_ssh_deploy=json_ssh_deploy,
|
json_ssh_deploy=json_ssh_deploy,
|
||||||
nix_options=args.option,
|
|
||||||
)
|
)
|
||||||
machine = Machine(opts.machine, flake=opts.flake)
|
machine = Machine(opts.machine, flake=opts.flake)
|
||||||
machine.target_host_address = opts.target_host
|
machine.target_host_address = opts.target_host
|
||||||
@ -147,7 +142,6 @@ def install_command(args: argparse.Namespace) -> None:
|
|||||||
debug=opts.debug,
|
debug=opts.debug,
|
||||||
password=password,
|
password=password,
|
||||||
no_reboot=opts.no_reboot,
|
no_reboot=opts.no_reboot,
|
||||||
extra_args=opts.nix_options,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -189,14 +183,11 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
help="do not ask for confirmation",
|
help="do not ask for confirmation",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
machines_parser = parser.add_argument(
|
|
||||||
"machine",
|
"machine",
|
||||||
type=str,
|
type=str,
|
||||||
help="machine to install",
|
help="machine to install",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"target_host",
|
"target_host",
|
||||||
type=str,
|
type=str,
|
||||||
|
@ -7,7 +7,7 @@ from .machines import Machine
|
|||||||
|
|
||||||
|
|
||||||
# function to speedup eval if we want to evauluate all machines
|
# function to speedup eval if we want to evauluate all machines
|
||||||
def get_all_machines(flake_dir: Path, nix_options: list[str]) -> list[Machine]:
|
def get_all_machines(flake_dir: Path) -> list[Machine]:
|
||||||
config = nix_config()
|
config = nix_config()
|
||||||
system = config["system"]
|
system = config["system"]
|
||||||
json_path = run(
|
json_path = run(
|
||||||
@ -19,20 +19,13 @@ def get_all_machines(flake_dir: Path, nix_options: list[str]) -> list[Machine]:
|
|||||||
machines = []
|
machines = []
|
||||||
for name, machine_data in machines_json.items():
|
for name, machine_data in machines_json.items():
|
||||||
machines.append(
|
machines.append(
|
||||||
Machine(
|
Machine(name=name, flake=flake_dir, deployment_info=machine_data)
|
||||||
name=name,
|
|
||||||
flake=flake_dir,
|
|
||||||
deployment_info=machine_data,
|
|
||||||
nix_options=nix_options,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return machines
|
return machines
|
||||||
|
|
||||||
|
|
||||||
def get_selected_machines(
|
def get_selected_machines(flake_dir: Path, machine_names: list[str]) -> list[Machine]:
|
||||||
flake_dir: Path, nix_options: list[str], machine_names: list[str]
|
|
||||||
) -> list[Machine]:
|
|
||||||
machines = []
|
machines = []
|
||||||
for name in machine_names:
|
for name in machine_names:
|
||||||
machines.append(Machine(name=name, flake=flake_dir, nix_options=nix_options))
|
machines.append(Machine(name=name, flake=flake_dir))
|
||||||
return machines
|
return machines
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -11,15 +12,24 @@ from ..nix import nix_config, nix_eval
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class MachineInfo:
|
||||||
|
machine_name: str
|
||||||
|
machine_description: str | None
|
||||||
|
machine_icon: str | None
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
@API.register
|
||||||
def list_machines(flake_url: str | Path, debug: bool) -> list[str]:
|
def list_machines(flake_url: str | Path, debug: bool) -> dict[str, MachineInfo]:
|
||||||
config = nix_config()
|
config = nix_config()
|
||||||
system = config["system"]
|
system = config["system"]
|
||||||
cmd = nix_eval(
|
cmd = nix_eval(
|
||||||
[
|
[
|
||||||
f"{flake_url}#clanInternals.machines.{system}",
|
f"{flake_url}#clanInternals.machines.{system}",
|
||||||
"--apply",
|
"--apply",
|
||||||
"builtins.attrNames",
|
"""builtins.mapAttrs (name: attrs: {
|
||||||
|
inherit (attrs.config.clanCore) machineDescription machineIcon machineName;
|
||||||
|
})""",
|
||||||
"--json",
|
"--json",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -27,13 +37,27 @@ def list_machines(flake_url: str | Path, debug: bool) -> list[str]:
|
|||||||
proc = run_no_stdout(cmd)
|
proc = run_no_stdout(cmd)
|
||||||
|
|
||||||
res = proc.stdout.strip()
|
res = proc.stdout.strip()
|
||||||
return json.loads(res)
|
machines_dict = json.loads(res)
|
||||||
|
|
||||||
|
return {
|
||||||
|
k: MachineInfo(
|
||||||
|
machine_name=v.get("machineName"),
|
||||||
|
machine_description=v.get("machineDescription", None),
|
||||||
|
machine_icon=v.get("machineIcon", None),
|
||||||
|
)
|
||||||
|
for k, v in machines_dict.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
def list_command(args: argparse.Namespace) -> None:
|
||||||
flake_path = Path(args.flake).resolve()
|
flake_path = Path(args.flake).resolve()
|
||||||
for name in list_machines(flake_path, args.debug):
|
print("Listing all machines:\n")
|
||||||
print(name)
|
print("Source: ", flake_path)
|
||||||
|
print("-" * 40)
|
||||||
|
for name, machine in list_machines(flake_path, args.debug).items():
|
||||||
|
description = machine.machine_description or "[no description]"
|
||||||
|
print(f"{name}\n: {description}\n")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
@ -41,10 +41,9 @@ class QMPWrapper:
|
|||||||
|
|
||||||
|
|
||||||
class Machine:
|
class Machine:
|
||||||
name: str
|
|
||||||
flake: str | Path
|
flake: str | Path
|
||||||
|
name: str
|
||||||
data: MachineData
|
data: MachineData
|
||||||
nix_options: list[str]
|
|
||||||
eval_cache: dict[str, str]
|
eval_cache: dict[str, str]
|
||||||
build_cache: dict[str, Path]
|
build_cache: dict[str, Path]
|
||||||
_flake_path: Path | None
|
_flake_path: Path | None
|
||||||
@ -56,7 +55,6 @@ class Machine:
|
|||||||
name: str,
|
name: str,
|
||||||
flake: Path | str,
|
flake: Path | str,
|
||||||
deployment_info: dict | None = None,
|
deployment_info: dict | None = None,
|
||||||
nix_options: list[str] = [],
|
|
||||||
machine: MachineData | None = None,
|
machine: MachineData | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@ -78,7 +76,6 @@ class Machine:
|
|||||||
self.build_cache: dict[str, Path] = {}
|
self.build_cache: dict[str, Path] = {}
|
||||||
self._flake_path: Path | None = None
|
self._flake_path: Path | None = None
|
||||||
self._deployment_info: None | dict = deployment_info
|
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)
|
state_dir = vm_state_dir(flake_url=str(self.flake), vm_name=self.data.name)
|
||||||
|
|
||||||
@ -245,9 +242,9 @@ class Machine:
|
|||||||
flake = f"path:{self.flake_dir}"
|
flake = f"path:{self.flake_dir}"
|
||||||
|
|
||||||
args += [
|
args += [
|
||||||
f'{flake}#clanInternals.machines."{system}".{self.data.name}.{attr}'
|
f'{flake}#clanInternals.machines."{system}".{self.data.name}.{attr}',
|
||||||
|
*nix_options,
|
||||||
]
|
]
|
||||||
args += nix_options + self.nix_options
|
|
||||||
|
|
||||||
if method == "eval":
|
if method == "eval":
|
||||||
output = run_no_stdout(nix_eval(args)).stdout.strip()
|
output = run_no_stdout(nix_eval(args)).stdout.strip()
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import dataclasses
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from clan_cli.api import API
|
|
||||||
|
|
||||||
from ..cmd import run_no_stdout
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..nix import nix_config, nix_eval
|
|
||||||
from .types import machine_name_type
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class MachineInfo:
|
|
||||||
machine_name: str
|
|
||||||
machine_description: str | None
|
|
||||||
machine_icon: str | None
|
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
|
||||||
def show_machine(flake_url: str | Path, machine_name: str, debug: bool) -> MachineInfo:
|
|
||||||
config = nix_config()
|
|
||||||
system = config["system"]
|
|
||||||
cmd = nix_eval(
|
|
||||||
[
|
|
||||||
f"{flake_url}#clanInternals.machines.{system}.{machine_name}",
|
|
||||||
"--apply",
|
|
||||||
"machine: { inherit (machine.config.clanCore) machineDescription machineIcon machineName; }",
|
|
||||||
"--json",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
proc = run_no_stdout(cmd)
|
|
||||||
res = proc.stdout.strip()
|
|
||||||
machine = json.loads(res)
|
|
||||||
|
|
||||||
return MachineInfo(
|
|
||||||
machine_name=machine.get("machineName"),
|
|
||||||
machine_description=machine.get("machineDescription", None),
|
|
||||||
machine_icon=machine.get("machineIcon", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def show_command(args: argparse.Namespace) -> None:
|
|
||||||
flake_path = Path(args.flake).resolve()
|
|
||||||
machine = show_machine(flake_path, args.machine, args.debug)
|
|
||||||
print(f"Name: {machine.machine_name}")
|
|
||||||
print(f"Description: {machine.machine_description or ''}")
|
|
||||||
print(f"Icon: {machine.machine_icon or ''}")
|
|
||||||
|
|
||||||
|
|
||||||
def register_show_parser(parser: argparse.ArgumentParser) -> None:
|
|
||||||
parser.set_defaults(func=show_command)
|
|
||||||
machine_parser = parser.add_argument(
|
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
|
||||||
)
|
|
||||||
add_dynamic_completer(machine_parser, complete_machines)
|
|
@ -3,10 +3,9 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ..cmd import run
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..facts.generate import generate_facts
|
from ..facts.generate import generate_facts
|
||||||
from ..facts.upload import upload_secrets
|
from ..facts.upload import upload_secrets
|
||||||
@ -54,7 +53,11 @@ def upload_sources(
|
|||||||
path,
|
path,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
run(cmd, env=env, error_msg="failed to upload sources")
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, env=env, check=False)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise ClanError(
|
||||||
|
f"failed to upload sources: {shlex.join(cmd)} failed with {proc.returncode}"
|
||||||
|
)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
# Slow path: we need to upload all sources to the remote machine
|
# Slow path: we need to upload all sources to the remote machine
|
||||||
@ -70,13 +73,16 @@ def upload_sources(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
log.info("run %s", shlex.join(cmd))
|
log.info("run %s", shlex.join(cmd))
|
||||||
proc = run(cmd, error_msg="failed to upload sources")
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, check=False)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise ClanError(
|
||||||
|
f"failed to upload sources: {shlex.join(cmd)} failed with {proc.returncode}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
return json.loads(proc.stdout)["path"]
|
return json.loads(proc.stdout)["path"]
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
raise ClanError(
|
raise ClanError(
|
||||||
f"failed to parse output of {shlex.join(cmd)}: {e}\nGot: {proc.stdout}"
|
f"failed to parse output of {shlex.join(cmd)}: {e}\nGot: {proc.stdout.decode('utf-8', 'replace')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -104,9 +110,11 @@ def deploy_nixos(machines: MachineGroup) -> None:
|
|||||||
|
|
||||||
ssh_arg += " -i " + host.key if host.key else ""
|
ssh_arg += " -i " + host.key if host.key else ""
|
||||||
|
|
||||||
|
extra_args = host.meta.get("extra_args", [])
|
||||||
cmd = [
|
cmd = [
|
||||||
"nixos-rebuild",
|
"nixos-rebuild",
|
||||||
"switch",
|
"switch",
|
||||||
|
*extra_args,
|
||||||
"--fast",
|
"--fast",
|
||||||
"--option",
|
"--option",
|
||||||
"keep-going",
|
"keep-going",
|
||||||
@ -116,7 +124,6 @@ def deploy_nixos(machines: MachineGroup) -> None:
|
|||||||
"true",
|
"true",
|
||||||
"--build-host",
|
"--build-host",
|
||||||
"",
|
"",
|
||||||
*machine.nix_options,
|
|
||||||
"--flake",
|
"--flake",
|
||||||
f"{path}#{machine.name}",
|
f"{path}#{machine.name}",
|
||||||
]
|
]
|
||||||
@ -136,9 +143,7 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
raise ClanError("Could not find clan flake toplevel directory")
|
raise ClanError("Could not find clan flake toplevel directory")
|
||||||
machines = []
|
machines = []
|
||||||
if len(args.machines) == 1 and args.target_host is not None:
|
if len(args.machines) == 1 and args.target_host is not None:
|
||||||
machine = Machine(
|
machine = Machine(name=args.machines[0], flake=args.flake)
|
||||||
name=args.machines[0], flake=args.flake, nix_options=args.option
|
|
||||||
)
|
|
||||||
machine.target_host_address = args.target_host
|
machine.target_host_address = args.target_host
|
||||||
machines.append(machine)
|
machines.append(machine)
|
||||||
|
|
||||||
@ -148,7 +153,7 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
else:
|
else:
|
||||||
if len(args.machines) == 0:
|
if len(args.machines) == 0:
|
||||||
ignored_machines = []
|
ignored_machines = []
|
||||||
for machine in get_all_machines(args.flake, args.option):
|
for machine in get_all_machines(args.flake):
|
||||||
if machine.deployment_info.get("requireExplicitUpdate", False):
|
if machine.deployment_info.get("requireExplicitUpdate", False):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@ -168,13 +173,13 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
print(machine, file=sys.stderr)
|
print(machine, file=sys.stderr)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
machines = get_selected_machines(args.flake, args.option, args.machines)
|
machines = get_selected_machines(args.flake, args.machines)
|
||||||
|
|
||||||
deploy_nixos(MachineGroup(machines))
|
deploy_nixos(MachineGroup(machines))
|
||||||
|
|
||||||
|
|
||||||
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machines",
|
"machines",
|
||||||
type=str,
|
type=str,
|
||||||
nargs="*",
|
nargs="*",
|
||||||
@ -182,9 +187,6 @@ def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
metavar="MACHINE",
|
metavar="MACHINE",
|
||||||
help="machine to update. If no machine is specified, all machines will be updated.",
|
help="machine to update. If no machine is specified, all machines will be updated.",
|
||||||
)
|
)
|
||||||
|
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--target-host",
|
"--target-host",
|
||||||
type=str,
|
type=str,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..git import commit_files
|
from ..git import commit_files
|
||||||
from ..machines.types import machine_name_type, validate_hostname
|
from ..machines.types import machine_name_type, validate_hostname
|
||||||
@ -148,28 +147,25 @@ def register_machines_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
|
|
||||||
# Parser
|
# Parser
|
||||||
get_parser = subparser.add_parser("get", help="get a machine public key")
|
get_parser = subparser.add_parser("get", help="get a machine public key")
|
||||||
get_machine_parser = get_parser.add_argument(
|
get_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the machine", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(get_machine_parser, complete_machines)
|
|
||||||
get_parser.set_defaults(func=get_command)
|
get_parser.set_defaults(func=get_command)
|
||||||
|
|
||||||
# Parser
|
# Parser
|
||||||
remove_parser = subparser.add_parser("remove", help="remove a machine")
|
remove_parser = subparser.add_parser("remove", help="remove a machine")
|
||||||
remove_machine_parser = remove_parser.add_argument(
|
remove_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the machine", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(remove_machine_parser, complete_machines)
|
|
||||||
remove_parser.set_defaults(func=remove_command)
|
remove_parser.set_defaults(func=remove_command)
|
||||||
|
|
||||||
# Parser
|
# Parser
|
||||||
add_secret_parser = subparser.add_parser(
|
add_secret_parser = subparser.add_parser(
|
||||||
"add-secret", help="allow a machine to access a secret"
|
"add-secret", help="allow a machine to access a secret"
|
||||||
)
|
)
|
||||||
machine_add_secret_parser = add_secret_parser.add_argument(
|
add_secret_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the machine", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machine_add_secret_parser, complete_machines)
|
|
||||||
add_secret_parser.add_argument(
|
add_secret_parser.add_argument(
|
||||||
"secret", help="the name of the secret", type=secret_name_type
|
"secret", help="the name of the secret", type=secret_name_type
|
||||||
)
|
)
|
||||||
@ -179,10 +175,9 @@ def register_machines_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
remove_secret_parser = subparser.add_parser(
|
remove_secret_parser = subparser.add_parser(
|
||||||
"remove-secret", help="remove a group's access to a secret"
|
"remove-secret", help="remove a group's access to a secret"
|
||||||
)
|
)
|
||||||
machine_remove_parser = remove_secret_parser.add_argument(
|
remove_secret_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the group", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machine_remove_parser, complete_machines)
|
|
||||||
remove_secret_parser.add_argument(
|
remove_secret_parser.add_argument(
|
||||||
"secret", help="the name of the secret", type=secret_name_type
|
"secret", help="the name of the secret", type=secret_name_type
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,6 @@ from pathlib import Path
|
|||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
from .. import tty
|
from .. import tty
|
||||||
from ..completions import add_dynamic_completer, complete_secrets
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..git import commit_files
|
from ..git import commit_files
|
||||||
from .folders import (
|
from .folders import (
|
||||||
@ -154,12 +153,8 @@ def remove_command(args: argparse.Namespace) -> None:
|
|||||||
remove_secret(Path(args.flake), args.secret)
|
remove_secret(Path(args.flake), args.secret)
|
||||||
|
|
||||||
|
|
||||||
def add_secret_argument(parser: argparse.ArgumentParser, autocomplete: bool) -> None:
|
def add_secret_argument(parser: argparse.ArgumentParser) -> None:
|
||||||
secrets_parser = parser.add_argument(
|
parser.add_argument("secret", help="the name of the secret", type=secret_name_type)
|
||||||
"secret", help="the name of the secret", type=secret_name_type
|
|
||||||
)
|
|
||||||
if autocomplete:
|
|
||||||
add_dynamic_completer(secrets_parser, complete_secrets)
|
|
||||||
|
|
||||||
|
|
||||||
def machines_folder(flake_dir: Path, group: str) -> Path:
|
def machines_folder(flake_dir: Path, group: str) -> Path:
|
||||||
@ -328,11 +323,11 @@ def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
|
|||||||
parser_list.set_defaults(func=list_command)
|
parser_list.set_defaults(func=list_command)
|
||||||
|
|
||||||
parser_get = subparser.add_parser("get", help="get a secret")
|
parser_get = subparser.add_parser("get", help="get a secret")
|
||||||
add_secret_argument(parser_get, True)
|
add_secret_argument(parser_get)
|
||||||
parser_get.set_defaults(func=get_command)
|
parser_get.set_defaults(func=get_command)
|
||||||
|
|
||||||
parser_set = subparser.add_parser("set", help="set a secret")
|
parser_set = subparser.add_parser("set", help="set a secret")
|
||||||
add_secret_argument(parser_set, False)
|
add_secret_argument(parser_set)
|
||||||
parser_set.add_argument(
|
parser_set.add_argument(
|
||||||
"--group",
|
"--group",
|
||||||
type=str,
|
type=str,
|
||||||
@ -364,10 +359,10 @@ def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
|
|||||||
parser_set.set_defaults(func=set_command)
|
parser_set.set_defaults(func=set_command)
|
||||||
|
|
||||||
parser_rename = subparser.add_parser("rename", help="rename a secret")
|
parser_rename = subparser.add_parser("rename", help="rename a secret")
|
||||||
add_secret_argument(parser_rename, True)
|
add_secret_argument(parser_rename)
|
||||||
parser_rename.add_argument("new_name", type=str, help="the new name of the secret")
|
parser_rename.add_argument("new_name", type=str, help="the new name of the secret")
|
||||||
parser_rename.set_defaults(func=rename_command)
|
parser_rename.set_defaults(func=rename_command)
|
||||||
|
|
||||||
parser_remove = subparser.add_parser("remove", help="remove a secret")
|
parser_remove = subparser.add_parser("remove", help="remove a secret")
|
||||||
add_secret_argument(parser_remove, True)
|
add_secret_argument(parser_remove)
|
||||||
parser_remove.set_defaults(func=remove_command)
|
parser_remove.set_defaults(func=remove_command)
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
mypy,
|
mypy,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
clan-core-path,
|
clan-core-path,
|
||||||
gitMinimal,
|
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
# Dependencies that are directly used in the project
|
# Dependencies that are directly used in the project
|
||||||
@ -114,13 +113,7 @@ python3.pkgs.buildPythonApplication {
|
|||||||
format = "pyproject";
|
format = "pyproject";
|
||||||
|
|
||||||
# Arguments for the wrapper to unset LD_LIBRARY_PATH to avoid glibc version issues
|
# Arguments for the wrapper to unset LD_LIBRARY_PATH to avoid glibc version issues
|
||||||
makeWrapperArgs = [
|
makeWrapperArgs = [ "--unset LD_LIBRARY_PATH" ];
|
||||||
"--unset LD_LIBRARY_PATH"
|
|
||||||
"--suffix"
|
|
||||||
"PATH"
|
|
||||||
":"
|
|
||||||
"${gitMinimal}/bin/git"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Build-time dependencies.
|
# Build-time dependencies.
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
@ -11,10 +11,10 @@ def test_backups(
|
|||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"backups",
|
|
||||||
"list",
|
|
||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake_with_core.path),
|
str(test_flake_with_core.path),
|
||||||
|
"backups",
|
||||||
|
"list",
|
||||||
"vm1",
|
"vm1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import json
|
||||||
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from cli import Cli
|
from cli import Cli
|
||||||
@ -11,6 +14,46 @@ from clan_cli.errors import ClanError
|
|||||||
example_options = f"{Path(config.__file__).parent}/jsonschema/options.json"
|
example_options = f"{Path(config.__file__).parent}/jsonschema/options.json"
|
||||||
|
|
||||||
|
|
||||||
|
# use pytest.parametrize
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"args,expected",
|
||||||
|
[
|
||||||
|
(["name", "DavHau"], {"name": "DavHau"}),
|
||||||
|
(
|
||||||
|
["kernelModules", "foo", "bar", "baz"],
|
||||||
|
{"kernelModules": ["foo", "bar", "baz"]},
|
||||||
|
),
|
||||||
|
(["services.opt", "test"], {"services": {"opt": "test"}}),
|
||||||
|
(["userIds.DavHau", "42"], {"userIds": {"DavHau": 42}}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_set_some_option(
|
||||||
|
args: list[str],
|
||||||
|
expected: dict[str, Any],
|
||||||
|
test_flake: FlakeForTest,
|
||||||
|
) -> None:
|
||||||
|
# create temporary file for out_file
|
||||||
|
with tempfile.NamedTemporaryFile() as out_file:
|
||||||
|
with open(out_file.name, "w") as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
cli = Cli()
|
||||||
|
cli.run(
|
||||||
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
|
"config",
|
||||||
|
"--quiet",
|
||||||
|
"--options-file",
|
||||||
|
example_options,
|
||||||
|
"--settings-file",
|
||||||
|
out_file.name,
|
||||||
|
*args,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
json_out = json.loads(open(out_file.name).read())
|
||||||
|
assert json_out == expected
|
||||||
|
|
||||||
|
|
||||||
def test_configure_machine(
|
def test_configure_machine(
|
||||||
test_flake: FlakeForTest,
|
test_flake: FlakeForTest,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
@ -19,14 +62,25 @@ def test_configure_machine(
|
|||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
|
|
||||||
|
cli.run(
|
||||||
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
|
"config",
|
||||||
|
"-m",
|
||||||
|
"machine1",
|
||||||
|
"clan.jitsi.enable",
|
||||||
|
"true",
|
||||||
|
]
|
||||||
|
)
|
||||||
# clear the output buffer
|
# clear the output buffer
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
# read a option value
|
# read a option value
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"config",
|
|
||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake.path),
|
str(test_flake.path),
|
||||||
|
"config",
|
||||||
"-m",
|
"-m",
|
||||||
"machine1",
|
"machine1",
|
||||||
"clan.jitsi.enable",
|
"clan.jitsi.enable",
|
||||||
@ -34,7 +88,7 @@ def test_configure_machine(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# read the output
|
# read the output
|
||||||
assert capsys.readouterr().out == "false\n"
|
assert capsys.readouterr().out == "true\n"
|
||||||
|
|
||||||
|
|
||||||
def test_walk_jsonschema_all_types() -> None:
|
def test_walk_jsonschema_all_types() -> None:
|
||||||
|
@ -17,12 +17,10 @@ def test_create_flake(
|
|||||||
capsys: pytest.CaptureFixture,
|
capsys: pytest.CaptureFixture,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
clan_core: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
flake_dir = temporary_home / "test-flake"
|
flake_dir = temporary_home / "test-flake"
|
||||||
|
|
||||||
url = f"{clan_core}#default"
|
cli.run(["flakes", "create", str(flake_dir)])
|
||||||
cli.run(["flakes", "create", str(flake_dir), f"--url={url}"])
|
|
||||||
assert (flake_dir / ".clan-flake").exists()
|
assert (flake_dir / ".clan-flake").exists()
|
||||||
monkeypatch.chdir(flake_dir)
|
monkeypatch.chdir(flake_dir)
|
||||||
cli.run(["machines", "create", "machine1"])
|
cli.run(["machines", "create", "machine1"])
|
||||||
@ -49,34 +47,16 @@ def test_create_flake(
|
|||||||
flake_outputs["nixosConfigurations"]["machine1"]
|
flake_outputs["nixosConfigurations"]["machine1"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
||||||
|
# configure machine1
|
||||||
|
capsys.readouterr()
|
||||||
@pytest.mark.impure
|
cli.run(["config", "--machine", "machine1", "services.openssh.enable", ""])
|
||||||
def test_ui_template(
|
capsys.readouterr()
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
cli.run(
|
||||||
capsys: pytest.CaptureFixture,
|
[
|
||||||
temporary_home: Path,
|
"config",
|
||||||
cli: Cli,
|
"--machine",
|
||||||
clan_core: Path,
|
"machine1",
|
||||||
) -> None:
|
"services.openssh.enable",
|
||||||
flake_dir = temporary_home / "test-flake"
|
"true",
|
||||||
url = f"{clan_core}#empty"
|
]
|
||||||
cli.run(["flakes", "create", str(flake_dir), f"--url={url}"])
|
|
||||||
assert (flake_dir / ".clan-flake").exists()
|
|
||||||
monkeypatch.chdir(flake_dir)
|
|
||||||
cli.run(["machines", "create", "machine1"])
|
|
||||||
capsys.readouterr() # flush cache
|
|
||||||
|
|
||||||
cli.run(["machines", "list"])
|
|
||||||
assert "machine1" in capsys.readouterr().out
|
|
||||||
flake_show = subprocess.run(
|
|
||||||
["nix", "flake", "show", "--json"],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
)
|
||||||
flake_outputs = json.loads(flake_show.stdout)
|
|
||||||
try:
|
|
||||||
flake_outputs["nixosConfigurations"]["machine1"]
|
|
||||||
except KeyError:
|
|
||||||
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
|
||||||
|
@ -15,10 +15,10 @@ def test_flakes_inspect(
|
|||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"flakes",
|
|
||||||
"inspect",
|
|
||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake_with_core.path),
|
str(test_flake_with_core.path),
|
||||||
|
"flakes",
|
||||||
|
"inspect",
|
||||||
"--machine",
|
"--machine",
|
||||||
"vm1",
|
"vm1",
|
||||||
]
|
]
|
||||||
|
@ -21,55 +21,55 @@ def test_import_sops(
|
|||||||
monkeypatch.setenv("SOPS_AGE_KEY", age_keys[1].privkey)
|
monkeypatch.setenv("SOPS_AGE_KEY", age_keys[1].privkey)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"machine1",
|
"machine1",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
age_keys[1].pubkey,
|
age_keys[1].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user2",
|
"user2",
|
||||||
age_keys[2].pubkey,
|
age_keys[2].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"user2",
|
"user2",
|
||||||
]
|
]
|
||||||
@ -78,10 +78,10 @@ def test_import_sops(
|
|||||||
# To edit:
|
# To edit:
|
||||||
# SOPS_AGE_KEY=AGE-SECRET-KEY-1U5ENXZQAY62NC78Y2WC0SEGRRMAEEKH79EYY5TH4GPFWJKEAY0USZ6X7YQ sops --age age14tva0txcrl0zes05x7gkx56qd6wd9q3nwecjac74xxzz4l47r44sv3fz62 ./data/secrets.yaml
|
# SOPS_AGE_KEY=AGE-SECRET-KEY-1U5ENXZQAY62NC78Y2WC0SEGRRMAEEKH79EYY5TH4GPFWJKEAY0USZ6X7YQ sops --age age14tva0txcrl0zes05x7gkx56qd6wd9q3nwecjac74xxzz4l47r44sv3fz62 ./data/secrets.yaml
|
||||||
cmd = [
|
cmd = [
|
||||||
"secrets",
|
|
||||||
"import-sops",
|
|
||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake.path),
|
str(test_flake.path),
|
||||||
|
"secrets",
|
||||||
|
"import-sops",
|
||||||
"--group",
|
"--group",
|
||||||
"group1",
|
"group1",
|
||||||
"--machine",
|
"--machine",
|
||||||
@ -91,10 +91,10 @@ def test_import_sops(
|
|||||||
|
|
||||||
cli.run(cmd)
|
cli.run(cmd)
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "users", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "users", "list"])
|
||||||
users = sorted(capsys.readouterr().out.rstrip().split())
|
users = sorted(capsys.readouterr().out.rstrip().split())
|
||||||
assert users == ["user1", "user2"]
|
assert users == ["user1", "user2"]
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "secret-key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "secret-key"])
|
||||||
assert capsys.readouterr().out == "secret-value"
|
assert capsys.readouterr().out == "secret-value"
|
||||||
|
@ -9,11 +9,11 @@ def test_machine_subcommands(
|
|||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(
|
cli.run(
|
||||||
["machines", "create", "--flake", str(test_flake_with_core.path), "machine1"]
|
["--flake", str(test_flake_with_core.path), "machines", "create", "machine1"]
|
||||||
)
|
)
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)])
|
cli.run(["--flake", str(test_flake_with_core.path), "machines", "list"])
|
||||||
|
|
||||||
out = capsys.readouterr()
|
out = capsys.readouterr()
|
||||||
|
|
||||||
@ -21,19 +21,12 @@ def test_machine_subcommands(
|
|||||||
assert "vm1" in out.out
|
assert "vm1" in out.out
|
||||||
assert "vm2" in out.out
|
assert "vm2" in out.out
|
||||||
|
|
||||||
capsys.readouterr()
|
|
||||||
cli.run(["machines", "show", "--flake", str(test_flake_with_core.path), "machine1"])
|
|
||||||
out = capsys.readouterr()
|
|
||||||
assert "machine1" in out.out
|
|
||||||
assert "Description" in out.out
|
|
||||||
print(out)
|
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
["machines", "delete", "--flake", str(test_flake_with_core.path), "machine1"]
|
["--flake", str(test_flake_with_core.path), "machines", "delete", "machine1"]
|
||||||
)
|
)
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["machines", "list", "--flake", str(test_flake_with_core.path)])
|
cli.run(["--flake", str(test_flake_with_core.path), "machines", "list"])
|
||||||
out = capsys.readouterr()
|
out = capsys.readouterr()
|
||||||
|
|
||||||
assert "machine1" not in out.out
|
assert "machine1" not in out.out
|
||||||
|
@ -27,11 +27,11 @@ def _test_identities(
|
|||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
what,
|
what,
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"foo",
|
"foo",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
@ -41,11 +41,11 @@ def _test_identities(
|
|||||||
with pytest.raises(ClanError): # raises "foo already exists"
|
with pytest.raises(ClanError): # raises "foo already exists"
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
what,
|
what,
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"foo",
|
"foo",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
@ -54,11 +54,11 @@ def _test_identities(
|
|||||||
# rotate the key
|
# rotate the key
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
what,
|
what,
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"-f",
|
"-f",
|
||||||
"foo",
|
"foo",
|
||||||
age_keys[1].privkey,
|
age_keys[1].privkey,
|
||||||
@ -68,11 +68,11 @@ def _test_identities(
|
|||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
what,
|
what,
|
||||||
"get",
|
"get",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"foo",
|
"foo",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -80,18 +80,18 @@ def _test_identities(
|
|||||||
assert age_keys[1].pubkey in out.out
|
assert age_keys[1].pubkey in out.out
|
||||||
|
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", what, "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", what, "list"])
|
||||||
out = capsys.readouterr() # empty the buffer
|
out = capsys.readouterr() # empty the buffer
|
||||||
assert "foo" in out.out
|
assert "foo" in out.out
|
||||||
|
|
||||||
cli.run(["secrets", what, "remove", "--flake", str(test_flake.path), "foo"])
|
cli.run(["--flake", str(test_flake.path), "secrets", what, "remove", "foo"])
|
||||||
assert not (sops_folder / what / "foo" / "key.json").exists()
|
assert not (sops_folder / what / "foo" / "key.json").exists()
|
||||||
|
|
||||||
with pytest.raises(ClanError): # already removed
|
with pytest.raises(ClanError): # already removed
|
||||||
cli.run(["secrets", what, "remove", "--flake", str(test_flake.path), "foo"])
|
cli.run(["--flake", str(test_flake.path), "secrets", what, "remove", "foo"])
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", what, "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", what, "list"])
|
||||||
out = capsys.readouterr()
|
out = capsys.readouterr()
|
||||||
assert "foo" not in out.out
|
assert "foo" not in out.out
|
||||||
|
|
||||||
@ -113,17 +113,17 @@ def test_groups(
|
|||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "groups", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "groups", "list"])
|
||||||
assert capsys.readouterr().out == ""
|
assert capsys.readouterr().out == ""
|
||||||
|
|
||||||
with pytest.raises(ClanError): # machine does not exist yet
|
with pytest.raises(ClanError): # machine does not exist yet
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-machine",
|
"add-machine",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"machine1",
|
"machine1",
|
||||||
]
|
]
|
||||||
@ -131,33 +131,33 @@ def test_groups(
|
|||||||
with pytest.raises(ClanError): # user does not exist yet
|
with pytest.raises(ClanError): # user does not exist yet
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"groupb1",
|
"groupb1",
|
||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"machine1",
|
"machine1",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-machine",
|
"add-machine",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"machine1",
|
"machine1",
|
||||||
]
|
]
|
||||||
@ -166,11 +166,11 @@ def test_groups(
|
|||||||
# Should this fail?
|
# Should this fail?
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-machine",
|
"add-machine",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"machine1",
|
"machine1",
|
||||||
]
|
]
|
||||||
@ -178,51 +178,51 @@ def test_groups(
|
|||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "groups", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "groups", "list"])
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "user1" in out
|
assert "user1" in out
|
||||||
assert "machine1" in out
|
assert "machine1" in out
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"remove-user",
|
"remove-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"remove-machine",
|
"remove-machine",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"group1",
|
"group1",
|
||||||
"machine1",
|
"machine1",
|
||||||
]
|
]
|
||||||
@ -251,90 +251,90 @@ def test_secrets(
|
|||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "list"])
|
||||||
assert capsys.readouterr().out == ""
|
assert capsys.readouterr().out == ""
|
||||||
|
|
||||||
monkeypatch.setenv("SOPS_NIX_SECRET", "foo")
|
monkeypatch.setenv("SOPS_NIX_SECRET", "foo")
|
||||||
monkeypatch.setenv("SOPS_AGE_KEY_FILE", str(test_flake.path / ".." / "age.key"))
|
monkeypatch.setenv("SOPS_AGE_KEY_FILE", str(test_flake.path / ".." / "age.key"))
|
||||||
cli.run(["secrets", "key", "generate", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "key", "generate"])
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "key", "show"])
|
||||||
key = capsys.readouterr().out
|
key = capsys.readouterr().out
|
||||||
assert key.startswith("age1")
|
assert key.startswith("age1")
|
||||||
cli.run(
|
cli.run(
|
||||||
["secrets", "users", "add", "--flake", str(test_flake.path), "testuser", key]
|
["--flake", str(test_flake.path), "secrets", "users", "add", "testuser", key]
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ClanError): # does not exist yet
|
with pytest.raises(ClanError): # does not exist yet
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "nonexisting"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "nonexisting"])
|
||||||
cli.run(["secrets", "set", "--flake", str(test_flake.path), "initialkey"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "set", "initialkey"])
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "initialkey"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "initialkey"])
|
||||||
assert capsys.readouterr().out == "foo"
|
assert capsys.readouterr().out == "foo"
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "users", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "users", "list"])
|
||||||
users = capsys.readouterr().out.rstrip().split("\n")
|
users = capsys.readouterr().out.rstrip().split("\n")
|
||||||
assert len(users) == 1, f"users: {users}"
|
assert len(users) == 1, f"users: {users}"
|
||||||
owner = users[0]
|
owner = users[0]
|
||||||
|
|
||||||
monkeypatch.setenv("EDITOR", "cat")
|
monkeypatch.setenv("EDITOR", "cat")
|
||||||
cli.run(["secrets", "set", "--edit", "--flake", str(test_flake.path), "initialkey"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "set", "--edit", "initialkey"])
|
||||||
monkeypatch.delenv("EDITOR")
|
monkeypatch.delenv("EDITOR")
|
||||||
|
|
||||||
cli.run(["secrets", "rename", "--flake", str(test_flake.path), "initialkey", "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "rename", "initialkey", "key"])
|
||||||
|
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "list"])
|
||||||
assert capsys.readouterr().out == "key\n"
|
assert capsys.readouterr().out == "key\n"
|
||||||
|
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "list", "--flake", str(test_flake.path), "nonexisting"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "list", "nonexisting"])
|
||||||
assert capsys.readouterr().out == ""
|
assert capsys.readouterr().out == ""
|
||||||
|
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "list", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "list", "key"])
|
||||||
assert capsys.readouterr().out == "key\n"
|
assert capsys.readouterr().out == "key\n"
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"machine1",
|
"machine1",
|
||||||
age_keys[1].pubkey,
|
age_keys[1].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"add-secret",
|
"add-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"machine1",
|
"machine1",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "machines", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "machines", "list"])
|
||||||
assert capsys.readouterr().out == "machine1\n"
|
assert capsys.readouterr().out == "machine1\n"
|
||||||
|
|
||||||
with use_key(age_keys[1].privkey, monkeypatch):
|
with use_key(age_keys[1].privkey, monkeypatch):
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||||
|
|
||||||
assert capsys.readouterr().out == "foo"
|
assert capsys.readouterr().out == "foo"
|
||||||
|
|
||||||
# rotate machines key
|
# rotate machines key
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"-f",
|
"-f",
|
||||||
"machine1",
|
"machine1",
|
||||||
age_keys[0].privkey,
|
age_keys[0].privkey,
|
||||||
@ -344,17 +344,17 @@ def test_secrets(
|
|||||||
# should also rotate the encrypted secret
|
# should also rotate the encrypted secret
|
||||||
with use_key(age_keys[0].privkey, monkeypatch):
|
with use_key(age_keys[0].privkey, monkeypatch):
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||||
|
|
||||||
assert capsys.readouterr().out == "foo"
|
assert capsys.readouterr().out == "foo"
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"remove-secret",
|
"remove-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"machine1",
|
"machine1",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
@ -362,37 +362,37 @@ def test_secrets(
|
|||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
age_keys[1].pubkey,
|
age_keys[1].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add-secret",
|
"add-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
with use_key(age_keys[1].privkey, monkeypatch):
|
with use_key(age_keys[1].privkey, monkeypatch):
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||||
assert capsys.readouterr().out == "foo"
|
assert capsys.readouterr().out == "foo"
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"remove-secret",
|
"remove-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
@ -401,44 +401,44 @@ def test_secrets(
|
|||||||
with pytest.raises(ClanError): # does not exist yet
|
with pytest.raises(ClanError): # does not exist yet
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-secret",
|
"add-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
owner,
|
owner,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-secret",
|
"add-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
@ -447,10 +447,10 @@ def test_secrets(
|
|||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"secrets",
|
|
||||||
"set",
|
|
||||||
"--flake",
|
"--flake",
|
||||||
str(test_flake.path),
|
str(test_flake.path),
|
||||||
|
"secrets",
|
||||||
|
"set",
|
||||||
"--group",
|
"--group",
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"key2",
|
"key2",
|
||||||
@ -459,28 +459,28 @@ def test_secrets(
|
|||||||
|
|
||||||
with use_key(age_keys[1].privkey, monkeypatch):
|
with use_key(age_keys[1].privkey, monkeypatch):
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||||
assert capsys.readouterr().out == "foo"
|
assert capsys.readouterr().out == "foo"
|
||||||
|
|
||||||
# extend group will update secrets
|
# extend group will update secrets
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"user2",
|
"user2",
|
||||||
age_keys[2].pubkey,
|
age_keys[2].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"user2",
|
"user2",
|
||||||
]
|
]
|
||||||
@ -488,16 +488,16 @@ def test_secrets(
|
|||||||
|
|
||||||
with use_key(age_keys[2].privkey, monkeypatch): # user2
|
with use_key(age_keys[2].privkey, monkeypatch): # user2
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||||
assert capsys.readouterr().out == "foo"
|
assert capsys.readouterr().out == "foo"
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"remove-user",
|
"remove-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"user2",
|
"user2",
|
||||||
]
|
]
|
||||||
@ -505,24 +505,24 @@ def test_secrets(
|
|||||||
with pytest.raises(ClanError), use_key(age_keys[2].privkey, monkeypatch):
|
with pytest.raises(ClanError), use_key(age_keys[2].privkey, monkeypatch):
|
||||||
# user2 is not in the group anymore
|
# user2 is not in the group anymore
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "get", "key"])
|
||||||
print(capsys.readouterr().out)
|
print(capsys.readouterr().out)
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"remove-secret",
|
"remove-secret",
|
||||||
"--flake",
|
|
||||||
str(test_flake.path),
|
|
||||||
"admin-group",
|
"admin-group",
|
||||||
"key",
|
"key",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
cli.run(["secrets", "remove", "--flake", str(test_flake.path), "key"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "remove", "key"])
|
||||||
cli.run(["secrets", "remove", "--flake", str(test_flake.path), "key2"])
|
cli.run(["--flake", str(test_flake.path), "secrets", "remove", "key2"])
|
||||||
|
|
||||||
capsys.readouterr() # empty the buffer
|
capsys.readouterr() # empty the buffer
|
||||||
cli.run(["secrets", "list", "--flake", str(test_flake.path)])
|
cli.run(["--flake", str(test_flake.path), "secrets", "list"])
|
||||||
assert capsys.readouterr().out == ""
|
assert capsys.readouterr().out == ""
|
||||||
|
@ -24,27 +24,27 @@ def test_generate_secret(
|
|||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake_with_core.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake_with_core.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake_with_core.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"groups",
|
"groups",
|
||||||
"add-user",
|
"add-user",
|
||||||
"--flake",
|
|
||||||
str(test_flake_with_core.path),
|
|
||||||
"admins",
|
"admins",
|
||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cmd = ["facts", "generate", "--flake", str(test_flake_with_core.path), "vm1"]
|
cmd = ["--flake", str(test_flake_with_core.path), "facts", "generate", "vm1"]
|
||||||
cli.run(cmd)
|
cli.run(cmd)
|
||||||
has_secret(test_flake_with_core.path, "vm1-age.key")
|
has_secret(test_flake_with_core.path, "vm1-age.key")
|
||||||
has_secret(test_flake_with_core.path, "vm1-zerotier-identity-secret")
|
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
|
secret1_mtime = identity_secret.lstat().st_mtime_ns
|
||||||
|
|
||||||
# test idempotency for vm1 and also generate for vm2
|
# test idempotency for vm1 and also generate for vm2
|
||||||
cli.run(["facts", "generate", "--flake", str(test_flake_with_core.path)])
|
cli.run(["facts", "generate"])
|
||||||
assert age_key.lstat().st_mtime_ns == age_key_mtime
|
assert age_key.lstat().st_mtime_ns == age_key_mtime
|
||||||
assert identity_secret.lstat().st_mtime_ns == secret1_mtime
|
assert identity_secret.lstat().st_mtime_ns == secret1_mtime
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ def test_secrets_upload(
|
|||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake_with_core.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"users",
|
"users",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake_with_core.path),
|
|
||||||
"user1",
|
"user1",
|
||||||
age_keys[0].pubkey,
|
age_keys[0].pubkey,
|
||||||
]
|
]
|
||||||
@ -35,18 +35,18 @@ def test_secrets_upload(
|
|||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake_with_core.path),
|
||||||
"secrets",
|
"secrets",
|
||||||
"machines",
|
"machines",
|
||||||
"add",
|
"add",
|
||||||
"--flake",
|
|
||||||
str(test_flake_with_core.path),
|
|
||||||
"vm1",
|
"vm1",
|
||||||
age_keys[1].pubkey,
|
age_keys[1].pubkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
monkeypatch.setenv("SOPS_NIX_SECRET", age_keys[0].privkey)
|
monkeypatch.setenv("SOPS_NIX_SECRET", age_keys[0].privkey)
|
||||||
cli.run(
|
cli.run(
|
||||||
["secrets", "set", "--flake", str(test_flake_with_core.path), "vm1-age.key"]
|
["--flake", str(test_flake_with_core.path), "secrets", "set", "vm1-age.key"]
|
||||||
)
|
)
|
||||||
|
|
||||||
flake = test_flake_with_core.path.joinpath("flake.nix")
|
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)
|
new_text = flake.read_text().replace("__CLAN_TARGET_ADDRESS__", addr)
|
||||||
|
|
||||||
flake.write_text(new_text)
|
flake.write_text(new_text)
|
||||||
cli.run(["facts", "upload", "--flake", str(test_flake_with_core.path), "vm1"])
|
cli.run(["--flake", str(test_flake_with_core.path), "facts", "upload", "vm1"])
|
||||||
|
|
||||||
# the flake defines this path as the location where the sops key should be installed
|
# 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")
|
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
|
test_flake_with_core: FlakeForTest, capsys: pytest.CaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(["vms", "inspect", "--flake", str(test_flake_with_core.path), "vm1"])
|
cli.run(["--flake", str(test_flake_with_core.path), "vms", "inspect", "vm1"])
|
||||||
out = capsys.readouterr() # empty the buffer
|
out = capsys.readouterr() # empty the buffer
|
||||||
assert "Cores" in out.out
|
assert "Cores" in out.out
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class MainApplication(Adw.Application):
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.window: MainWindow | None = None
|
self.window: "MainWindow" | None = None
|
||||||
self.connect("activate", self.on_activate)
|
self.connect("activate", self.on_activate)
|
||||||
self.connect("shutdown", self.on_shutdown)
|
self.connect("shutdown", self.on_shutdown)
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class GKVStore(GObject.GObject, Gio.ListModel, Generic[K, V]):
|
|||||||
self.gtype = gtype
|
self.gtype = gtype
|
||||||
self.key_gen = key_gen
|
self.key_gen = key_gen
|
||||||
# From Python 3.7 onwards dictionaries are ordered by default
|
# From Python 3.7 onwards dictionaries are ordered by default
|
||||||
self._items: dict[K, V] = dict()
|
self._items: "dict[K, V]" = dict()
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
# #
|
# #
|
||||||
|
@ -46,12 +46,6 @@ class WebView:
|
|||||||
self.method_registry: dict[str, Callable] = methods
|
self.method_registry: dict[str, Callable] = methods
|
||||||
|
|
||||||
self.webview = WebKit.WebView()
|
self.webview = WebKit.WebView()
|
||||||
|
|
||||||
settings = self.webview.get_settings()
|
|
||||||
# settings.
|
|
||||||
settings.set_property("enable-developer-extras", True)
|
|
||||||
self.webview.set_settings(settings)
|
|
||||||
|
|
||||||
self.manager = self.webview.get_user_content_manager()
|
self.manager = self.webview.get_user_content_manager()
|
||||||
# Can be called with: window.webkit.messageHandlers.gtk.postMessage("...")
|
# Can be called with: window.webkit.messageHandlers.gtk.postMessage("...")
|
||||||
# Important: it seems postMessage must be given some payload, otherwise it won't trigger the event
|
# Important: it seems postMessage must be given some payload, otherwise it won't trigger the event
|
||||||
|
@ -37,4 +37,6 @@ writeShellApplication {
|
|||||||
|
|
||||||
exec ${lib.getExe codium} --user-data-dir "$DATA_DIR" "$@"
|
exec ${lib.getExe codium} --user-data-dir "$DATA_DIR" "$@"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
derivationArgs.passthru.completion-options = import ./completion-options.nix;
|
||||||
}
|
}
|
||||||
|
17
pkgs/editor/completion-options.nix
Normal file
17
pkgs/editor/completion-options.nix
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
let
|
||||||
|
flake = builtins.getFlake "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||||
|
nixpkgs = flake.inputs.nixpkgs;
|
||||||
|
pkgs = nixpkgs.legacyPackages.${builtins.currentSystem};
|
||||||
|
clanCore = flake.outputs.nixosModules.clanCore;
|
||||||
|
clanModules = flake.outputs.clanModules;
|
||||||
|
allNixosModules = (import "${nixpkgs}/nixos/modules/module-list.nix") ++ [
|
||||||
|
"${nixpkgs}/nixos/modules/misc/assertions.nix"
|
||||||
|
{ nixpkgs.hostPlatform = "x86_64-linux"; }
|
||||||
|
];
|
||||||
|
clanCoreNixosModules = [
|
||||||
|
clanCore
|
||||||
|
# { clanCore.clanDir = ./.; }
|
||||||
|
] ++ allNixosModules ++ (builtins.attrValues clanModules);
|
||||||
|
clanCoreNixos = pkgs.nixos { imports = clanCoreNixosModules; };
|
||||||
|
in
|
||||||
|
clanCoreNixos.options
|
@ -14,9 +14,12 @@
|
|||||||
= (import <nixpkgs/nixos/modules/module-list.nix>) ++ [ ({...}: {
|
= (import <nixpkgs/nixos/modules/module-list.nix>) ++ [ ({...}: {
|
||||||
nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options"
|
nixpkgs.hostPlatform = builtins.currentSystem;} ) ] ; })).options"
|
||||||
},
|
},
|
||||||
"home-manager": {
|
"clan": {
|
||||||
"expr": "(builtins.getFlake \"github:nix-community/home-manager\").homeConfigurations.<name>.options"
|
"expr": "let pkgs = import <nixpkgs> { }; flake = builtins.getFlake \"https://git.clan.lol/clan/clan-core/archive/main.tar.gz\"; clanCore = flake.outputs.nixosModules.clanCore; clanModules = flake.outputs.clanModules; allNixosModules = (import <nixpkgs/nixos/modules/module-list.nix>) ++ [<nixpkgs/nixos/modules/misc/assertions.nix> {nixpkgs.hostPlatform = \"x86_64-linux\";}]; clanCoreNixosModules = [clanCore] ++ allNixosModules ++ (builtins.attrValues clanModules); clanCoreNixos = pkgs.nixos { imports = clanCoreNixosModules;}; in clanCoreNixos.options"
|
||||||
}
|
},
|
||||||
|
"clan-core": {
|
||||||
|
"expr": "(builtins.getFlake \"/home/kenji/git/clan-projects/clan-core\").packages.x86_64-linux.editor.passthru.completion-options",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
editor = pkgs.callPackage ./editor/clan-edit-codium.nix { };
|
editor = pkgs.callPackage ./editor/clan-edit-codium.nix { };
|
||||||
}
|
}
|
||||||
// lib.optionalAttrs pkgs.stdenv.isLinux {
|
// lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
|
wayland-proxy-virtwl = pkgs.callPackage ./wayland-proxy-virtwl { };
|
||||||
# halalify zerotierone
|
# halalify zerotierone
|
||||||
zerotierone = pkgs.zerotierone.overrideAttrs (_old: {
|
zerotierone = pkgs.zerotierone.overrideAttrs (_old: {
|
||||||
meta = _old.meta // {
|
meta = _old.meta // {
|
||||||
|
@ -8,9 +8,8 @@ clean_temp_dir() {
|
|||||||
rm -rf "$temp_dir"
|
rm -rf "$temp_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
is_installed() {
|
is_nix_installed() {
|
||||||
name=$1
|
if [ -n "$(command -v nix)" ]; then
|
||||||
if [ -n "$(command -v "$name")" ]; then
|
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
@ -18,18 +17,9 @@ is_installed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_nix() {
|
install_nix() {
|
||||||
if is_installed curl; then
|
curl --proto '=https' --tlsv1.2 -sSf -L \
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L \
|
https://install.determinate.systems/nix \
|
||||||
https://install.determinate.systems/nix \
|
> "$temp_dir"/install_nix.sh
|
||||||
> "$temp_dir"/install_nix.sh
|
|
||||||
elif is_installed wget; then
|
|
||||||
wget -qO- \
|
|
||||||
https://install.determinate.systems/nix \
|
|
||||||
> "$temp_dir"/install_nix.sh
|
|
||||||
else
|
|
||||||
echo "Either curl or wget is required to install Nix. Exiting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
NIX_INSTALLER_DIAGNOSTIC_ENDPOINT="" sh "$temp_dir"/install_nix.sh install
|
NIX_INSTALLER_DIAGNOSTIC_ENDPOINT="" sh "$temp_dir"/install_nix.sh install
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,17 +35,15 @@ ask_then_install_nix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure_nix_installed() {
|
ensure_nix_installed() {
|
||||||
if ! is_installed nix; then
|
if ! is_nix_installed; then
|
||||||
ask_then_install_nix
|
ask_then_install_nix
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
start_clan_gui() {
|
start_clan_gui() {
|
||||||
PATH="${PATH:+$PATH:}/nix/var/nix/profiles/default/bin" \
|
exec nix run \
|
||||||
exec nix run \
|
https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-vm-manager \
|
||||||
https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-vm-manager \
|
--extra-experimental-features flakes nix-command -- "$@"
|
||||||
--no-accept-flake-config \
|
|
||||||
--extra-experimental-features flakes nix-command -- "$@"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
@ -98,6 +98,9 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan = {
|
clan = {
|
||||||
|
clanName = "clan-core";
|
||||||
|
directory = self;
|
||||||
|
|
||||||
# To build a generic installer image (without ssh pubkeys),
|
# To build a generic installer image (without ssh pubkeys),
|
||||||
# use the following command:
|
# use the following command:
|
||||||
# $ nix build .#iso-installer
|
# $ nix build .#iso-installer
|
||||||
|
26
pkgs/merge-after-ci/merge-after-ci.py
Executable file → Normal file
26
pkgs/merge-after-ci/merge-after-ci.py
Executable file → Normal file
@ -1,5 +1,4 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import shlex
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@ -12,17 +11,14 @@ args = parser.parse_args()
|
|||||||
if not args.reviewers and not args.no_review:
|
if not args.reviewers and not args.no_review:
|
||||||
parser.error("either --reviewers or --no-review must be given")
|
parser.error("either --reviewers or --no-review must be given")
|
||||||
|
|
||||||
cmd = [
|
subprocess.run(
|
||||||
"tea-create-pr",
|
[
|
||||||
"origin",
|
"tea-create-pr",
|
||||||
"upstream",
|
"origin",
|
||||||
"main",
|
"main",
|
||||||
"--assignees",
|
"--assignees",
|
||||||
",".join(["clan-bot", *args.reviewers]),
|
",".join(["clan-bot", *args.reviewers]),
|
||||||
*(["--labels", "needs-review"] if not args.no_review else []),
|
*(["--labels", "needs-review"] if not args.no_review else []),
|
||||||
*args.args,
|
*args.args,
|
||||||
]
|
]
|
||||||
|
)
|
||||||
print("Running:", shlex.join(cmd))
|
|
||||||
|
|
||||||
subprocess.run(cmd)
|
|
||||||
|
49
pkgs/tea-create-pr/script.sh
Executable file → Normal file
49
pkgs/tea-create-pr/script.sh
Executable file → Normal file
@ -1,46 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
remoteFork="${1:-origin}"
|
remoteName="${1:-origin}"
|
||||||
remoteUpstream="${2:-upstream}"
|
targetBranch="${2:-main}"
|
||||||
targetBranch="${3:-main}"
|
shift && shift
|
||||||
shift && shift && shift
|
|
||||||
TMPDIR="$(mktemp -d)"
|
TMPDIR="$(mktemp -d)"
|
||||||
currentBranch="$(git rev-parse --abbrev-ref HEAD)"
|
currentBranch="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
user_unparsed="$(tea whoami)"
|
user="$(tea login list -o simple | cut -d" " -f4 | head -n1)"
|
||||||
user="$(echo "$user_unparsed" | tr -d '\n' | cut -f4 -d' ')"
|
|
||||||
tempRemoteBranch="$user-$currentBranch"
|
tempRemoteBranch="$user-$currentBranch"
|
||||||
root_dir=$(git rev-parse --show-toplevel)
|
|
||||||
|
|
||||||
|
nix fmt -- --fail-on-change
|
||||||
|
|
||||||
# Function to check if a remote exists
|
git log --reverse --pretty="format:%s%n%n%b%n%n" "$remoteName/$targetBranch..HEAD" > "$TMPDIR"/commit-msg
|
||||||
check_remote() {
|
|
||||||
if git remote get-url "$1" > /dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the remote 'upstream' is defined
|
|
||||||
if ! check_remote "$remoteUpstream"; then
|
|
||||||
echo "Error: $remoteUpstream remote is not defined."
|
|
||||||
echo "Please fork the repository and add the $remoteUpstream remote."
|
|
||||||
echo "$ git remote add $remoteUpstream <upstream-url>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
treefmt --fail-on-change -C "$root_dir"
|
|
||||||
|
|
||||||
upstream_url=$(git remote get-url "$remoteUpstream")
|
|
||||||
set -x
|
|
||||||
git fetch "$remoteUpstream" && git rebase "$remoteUpstream"/main --autostash
|
|
||||||
set +x
|
|
||||||
repo=$(echo "$upstream_url" | sed -E 's#.*:([^/]+/[^.]+)\.git#\1#')
|
|
||||||
|
|
||||||
|
|
||||||
git log --reverse --pretty="format:%s%n%n%b%n%n" "$remoteUpstream/$targetBranch..HEAD" > "$TMPDIR"/commit-msg
|
|
||||||
|
|
||||||
|
|
||||||
$EDITOR "$TMPDIR"/commit-msg
|
$EDITOR "$TMPDIR"/commit-msg
|
||||||
|
|
||||||
@ -52,13 +23,11 @@ rest=$(echo "$COMMIT_MSG" | tail -n+2)
|
|||||||
if [[ "$firstLine" == "$rest" ]]; then
|
if [[ "$firstLine" == "$rest" ]]; then
|
||||||
rest=""
|
rest=""
|
||||||
fi
|
fi
|
||||||
|
git push --force -u "$remoteName" HEAD:refs/heads/"$tempRemoteBranch"
|
||||||
git push --force -u "$remoteFork" HEAD:refs/heads/"$tempRemoteBranch"
|
|
||||||
|
|
||||||
|
|
||||||
tea pr create \
|
tea pr create \
|
||||||
--repo "$repo" \
|
|
||||||
--head "$user:$tempRemoteBranch" \
|
|
||||||
--title "$firstLine" \
|
--title "$firstLine" \
|
||||||
--description "$rest" \
|
--description "$rest" \
|
||||||
|
--head "$tempRemoteBranch" \
|
||||||
|
--base "$targetBranch" \
|
||||||
"$@"
|
"$@"
|
||||||
|
35
pkgs/wayland-proxy-virtwl/default.nix
Normal file
35
pkgs/wayland-proxy-virtwl/default.nix
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
wayland-proxy-virtwl,
|
||||||
|
fetchFromGitHub,
|
||||||
|
libdrm,
|
||||||
|
ocaml-ng,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
ocaml-wayland = ocaml-ng.ocamlPackages_5_0.wayland.overrideAttrs (_old: {
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "Mic92";
|
||||||
|
repo = "ocaml-wayland";
|
||||||
|
rev = "f6910aa5b626fa582cc000d4fe7b50182d11b439";
|
||||||
|
hash = "sha256-cg3HLezWTxWoYWSrirOV12gv1CRz1gMIOT7j3j3v5EA=";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
in
|
||||||
|
wayland-proxy-virtwl.overrideAttrs (_old: {
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "Mic92";
|
||||||
|
repo = "wayland-proxy-virtwl";
|
||||||
|
rev = "652fca9d4e006a2bdeba920dfaf53190c5373a7d";
|
||||||
|
hash = "sha256-VgpqxjHgueK9eQSX987PF0KvscpzkScOzFkW3haYCOw=";
|
||||||
|
};
|
||||||
|
buildInputs =
|
||||||
|
[ libdrm ]
|
||||||
|
++ (with ocaml-ng.ocamlPackages_5_0; [
|
||||||
|
ocaml-wayland
|
||||||
|
dune-configurator
|
||||||
|
eio_main
|
||||||
|
ppx_cstruct
|
||||||
|
cmdliner
|
||||||
|
logs
|
||||||
|
ppx_cstruct
|
||||||
|
]);
|
||||||
|
})
|
@ -19,7 +19,6 @@ export default tseslint.config(
|
|||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
callees: ["cx"],
|
callees: ["cx"],
|
||||||
whitelist: ["material-icons"],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -5,9 +5,6 @@ const distPath = path.resolve(__dirname, "dist");
|
|||||||
const manifestPath = path.join(distPath, ".vite/manifest.json");
|
const manifestPath = path.join(distPath, ".vite/manifest.json");
|
||||||
const outputPath = path.join(distPath, "index.html");
|
const outputPath = path.join(distPath, "index.html");
|
||||||
|
|
||||||
const postcss = require("postcss");
|
|
||||||
const css_url = require("postcss-url");
|
|
||||||
|
|
||||||
fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return console.error("Failed to read manifest:", err);
|
return console.error("Failed to read manifest:", err);
|
||||||
@ -28,48 +25,19 @@ fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
|||||||
|
|
||||||
// Add linked stylesheets
|
// Add linked stylesheets
|
||||||
assets.forEach((asset) => {
|
assets.forEach((asset) => {
|
||||||
// console.log(asset);
|
asset.css.forEach((cssEntry) => {
|
||||||
if (asset.src === "index.html") {
|
htmlContent += `\n <link rel="stylesheet" href="${cssEntry}">`;
|
||||||
asset.css.forEach((cssEntry) => {
|
});
|
||||||
// css to be processed
|
|
||||||
|
|
||||||
const css = fs.readFileSync(`dist/${cssEntry}`, "utf8");
|
|
||||||
|
|
||||||
// process css
|
|
||||||
postcss()
|
|
||||||
.use(
|
|
||||||
css_url({
|
|
||||||
url: (asset, dir) => {
|
|
||||||
const res = path.basename(asset.url);
|
|
||||||
console.log(`Rewriting CSS url(): ${asset.url} to ${res}`);
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.process(css, {
|
|
||||||
from: `dist/${cssEntry}`,
|
|
||||||
to: `dist/${cssEntry}`,
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
fs.writeFileSync(`dist/${cssEntry}`, result.css, "utf8");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extend the HTML content with the linked stylesheet
|
|
||||||
console.log(`Relinking html css stylesheet: ${cssEntry}`);
|
|
||||||
htmlContent += `\n <link rel="stylesheet" href="${cssEntry}">`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
htmlContent += `
|
htmlContent += `
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
`;
|
`;
|
||||||
// Add scripts
|
// Add scripts
|
||||||
assets.forEach((asset) => {
|
assets.forEach((asset) => {
|
||||||
if (asset.file.endsWith(".js")) {
|
if (asset.file.endsWith(".js")) {
|
||||||
console.log(`Relinking js script: ${asset.file}`);
|
|
||||||
htmlContent += `\n <script src="${asset.file}"></script>`;
|
htmlContent += `\n <script src="${asset.file}"></script>`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { JSONSchemaFaker } from "json-schema-faker";
|
|
||||||
import { schema } from "./api/index";
|
|
||||||
import { OperationNames } from "./src/message";
|
|
||||||
|
|
||||||
const faker = JSONSchemaFaker;
|
|
||||||
|
|
||||||
faker.option({
|
|
||||||
alwaysFakeOptionals: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getFakeResponse = (method: OperationNames, data: any) => {
|
|
||||||
const fakeData = faker.generate(schema.properties[method].properties.return);
|
|
||||||
return fakeData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { getFakeResponse };
|
|
227
pkgs/webview-ui/app/package-lock.json
generated
227
pkgs/webview-ui/app/package-lock.json
generated
@ -9,12 +9,10 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"material-icons": "^1.13.12",
|
|
||||||
"solid-js": "^1.8.11"
|
"solid-js": "^1.8.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.3.0",
|
"@eslint/js": "^9.3.0",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
|
||||||
"@types/node": "^20.12.12",
|
"@types/node": "^20.12.12",
|
||||||
"@typescript-eslint/parser": "^7.10.0",
|
"@typescript-eslint/parser": "^7.10.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
@ -22,10 +20,8 @@
|
|||||||
"daisyui": "^4.11.1",
|
"daisyui": "^4.11.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.0",
|
"eslint-plugin-tailwindcss": "^3.17.0",
|
||||||
"json-schema-faker": "^0.5.6",
|
|
||||||
"json-schema-to-ts": "^3.1.0",
|
"json-schema-to-ts": "^3.1.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"postcss-url": "^10.1.3",
|
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"solid-devtools": "^0.29.2",
|
"solid-devtools": "^0.29.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
@ -1486,34 +1482,6 @@
|
|||||||
"solid-js": "^1.6.12"
|
"solid-js": "^1.6.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/typography": {
|
|
||||||
"version": "0.5.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
|
|
||||||
"integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"lodash.castarray": "^4.4.0",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.merge": "^4.6.2",
|
|
||||||
"postcss-selector-parser": "6.0.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"tailwindcss": ">=3.0.0 || insiders"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
|
||||||
"version": "6.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
|
||||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"cssesc": "^3.0.0",
|
|
||||||
"util-deprecate": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@ -2022,12 +1990,6 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-me-maybe": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@ -2221,12 +2183,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||||
},
|
},
|
||||||
"node_modules/cuint": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/culori": {
|
"node_modules/culori": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
|
||||||
@ -2667,19 +2623,6 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esprima": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"esparse": "bin/esparse.js",
|
|
||||||
"esvalidate": "bin/esvalidate.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/esquery": {
|
"node_modules/esquery": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
|
||||||
@ -2859,12 +2802,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/format-util": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz",
|
|
||||||
"integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
@ -3260,53 +3197,6 @@
|
|||||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json-schema-faker": {
|
|
||||||
"version": "0.5.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.6.tgz",
|
|
||||||
"integrity": "sha512-u/cFC26/GDxh2vPiAC8B8xVvpXAW+QYtG2mijEbKrimCk8IHtiwQBjCE8TwvowdhALWq9IcdIWZ+/8ocXvdL3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"json-schema-ref-parser": "^6.1.0",
|
|
||||||
"jsonpath-plus": "^7.2.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"jsf": "bin/gen.cjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-parser": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz",
|
|
||||||
"integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==",
|
|
||||||
"deprecated": "Please switch to @apidevtools/json-schema-ref-parser",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"call-me-maybe": "^1.0.1",
|
|
||||||
"js-yaml": "^3.12.1",
|
|
||||||
"ono": "^4.0.11"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-parser/node_modules/argparse": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"sprintf-js": "~1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-parser/node_modules/js-yaml": {
|
|
||||||
"version": "3.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"argparse": "^1.0.7",
|
|
||||||
"esprima": "^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"js-yaml": "bin/js-yaml.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-to-ts": {
|
"node_modules/json-schema-to-ts": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.0.tgz",
|
||||||
@ -3344,15 +3234,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonpath-plus": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -3433,18 +3314,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.castarray": {
|
|
||||||
"version": "4.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
|
||||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -3497,11 +3366,6 @@
|
|||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/material-icons": {
|
|
||||||
"version": "1.13.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz",
|
|
||||||
"integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA=="
|
|
||||||
},
|
|
||||||
"node_modules/merge-anything": {
|
"node_modules/merge-anything": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
|
||||||
@ -3687,15 +3551,6 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ono": {
|
|
||||||
"version": "4.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz",
|
|
||||||
"integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"format-util": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@ -4012,73 +3867,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-url": {
|
|
||||||
"version": "10.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz",
|
|
||||||
"integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"make-dir": "~3.1.0",
|
|
||||||
"mime": "~2.5.2",
|
|
||||||
"minimatch": "~3.0.4",
|
|
||||||
"xxhashjs": "~0.2.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/brace-expansion": {
|
|
||||||
"version": "1.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/make-dir": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/mime": {
|
|
||||||
"version": "2.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
|
|
||||||
"integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"mime": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/minimatch": {
|
|
||||||
"version": "3.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
|
|
||||||
"integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^1.1.7"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-value-parser": {
|
"node_modules/postcss-value-parser": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
@ -4501,12 +4289,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sprintf-js": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
@ -5226,15 +5008,6 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/xxhashjs": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"cuint": "^0.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
@ -13,28 +13,24 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.3.0",
|
"@eslint/js": "^9.3.0",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
|
||||||
"@types/node": "^20.12.12",
|
"@types/node": "^20.12.12",
|
||||||
"@typescript-eslint/parser": "^7.10.0",
|
"@typescript-eslint/parser": "^7.10.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"daisyui": "^4.11.1",
|
"daisyui": "^4.11.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.0",
|
|
||||||
"json-schema-faker": "^0.5.6",
|
|
||||||
"json-schema-to-ts": "^3.1.0",
|
"json-schema-to-ts": "^3.1.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"postcss-url": "^10.1.3",
|
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"solid-devtools": "^0.29.2",
|
"solid-devtools": "^0.29.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"typescript-eslint": "^7.10.0",
|
"typescript-eslint": "^7.10.0",
|
||||||
"vite": "^5.0.11",
|
"vite": "^5.0.11",
|
||||||
"vite-plugin-solid": "^2.8.2"
|
"vite-plugin-solid": "^2.8.2",
|
||||||
|
"eslint-plugin-tailwindcss": "^3.17.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"material-icons": "^1.13.12",
|
|
||||||
"solid-js": "^1.8.11"
|
"solid-js": "^1.8.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,59 @@
|
|||||||
import { createSignal, type Component } from "solid-js";
|
import { Match, Switch, createSignal, type Component } from "solid-js";
|
||||||
import { CountProvider } from "./Config";
|
import { CountProvider } from "./Config";
|
||||||
|
// import { Nested } from "./nested";
|
||||||
import { Layout } from "./layout/layout";
|
import { Layout } from "./layout/layout";
|
||||||
import { Route, Router } from "./Routes";
|
import cx from "classnames";
|
||||||
|
import { Nested } from "./nested";
|
||||||
|
|
||||||
// Global state
|
type Route = "home" | "machines";
|
||||||
const [route, setRoute] = createSignal<Route>("machines");
|
|
||||||
|
|
||||||
export { route, setRoute };
|
|
||||||
|
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
|
const [route, setRoute] = createSignal<Route>("home");
|
||||||
return (
|
return (
|
||||||
<CountProvider>
|
<CountProvider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Router route={route} />
|
<div class="col-span-1">
|
||||||
|
<div class={cx("text-zinc-500")}>Navigation</div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setRoute("home")}
|
||||||
|
classList={{ "bg-blue-500": route() === "home" }}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{" "}
|
||||||
|
<button
|
||||||
|
onClick={() => setRoute("machines")}
|
||||||
|
classList={{ "bg-blue-500": route() === "machines" }}
|
||||||
|
>
|
||||||
|
Machines
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-7">
|
||||||
|
<div>{route()}</div>
|
||||||
|
<Switch fallback={<p>{route()} not found</p>}>
|
||||||
|
<Match when={route() == "home"}>
|
||||||
|
<Nested />
|
||||||
|
</Match>
|
||||||
|
<Match when={route() == "machines"}>
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<div class="h-10 w-20 bg-red-500">red</div>
|
||||||
|
<div class="h-10 w-20 bg-green-500">green</div>
|
||||||
|
<div class="h-10 w-20 bg-blue-500">blue</div>
|
||||||
|
<div class="h-10 w-20 bg-yellow-500">yellow</div>
|
||||||
|
<div class="h-10 w-20 bg-purple-500">purple</div>
|
||||||
|
<div class="h-10 w-20 bg-cyan-500">cyan</div>
|
||||||
|
<div class="h-10 w-20 bg-pink-500">pink</div>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
</CountProvider>
|
</CountProvider>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,7 @@ import { OperationResponse, pyApi } from "./message";
|
|||||||
export const makeCountContext = () => {
|
export const makeCountContext = () => {
|
||||||
const [machines, setMachines] = createSignal<
|
const [machines, setMachines] = createSignal<
|
||||||
OperationResponse<"list_machines">
|
OperationResponse<"list_machines">
|
||||||
>([]);
|
>({});
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
|
||||||
pyApi.list_machines.receive((machines) => {
|
pyApi.list_machines.receive((machines) => {
|
||||||
@ -41,7 +41,7 @@ export const CountContext = createContext<CountContextType>([
|
|||||||
loading: () => false,
|
loading: () => false,
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
machines: () => ([]),
|
machines: () => ({}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { Accessor, For, Match, Switch } from "solid-js";
|
|
||||||
import { MachineListView } from "./routes/machines/view";
|
|
||||||
import { colors } from "./routes/colors/view";
|
|
||||||
|
|
||||||
export type Route = keyof typeof routes;
|
|
||||||
|
|
||||||
export const routes = {
|
|
||||||
machines: {
|
|
||||||
child: MachineListView,
|
|
||||||
label: "Machines",
|
|
||||||
icon: "devices_other",
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
child: colors,
|
|
||||||
label: "Colors",
|
|
||||||
icon: "color_lens",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
interface RouterProps {
|
|
||||||
route: Accessor<Route>;
|
|
||||||
}
|
|
||||||
export const Router = (props: RouterProps) => {
|
|
||||||
const { route } = props;
|
|
||||||
return (
|
|
||||||
<Switch fallback={<p>route {route()} not found</p>}>
|
|
||||||
<For each={Object.entries(routes)}>
|
|
||||||
{([key, { child }]) => <Match when={route() === key}>{child}</Match>}
|
|
||||||
</For>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,33 +0,0 @@
|
|||||||
import { Accessor, For, Setter } from "solid-js";
|
|
||||||
import { Route, routes } from "./Routes";
|
|
||||||
|
|
||||||
interface SidebarProps {
|
|
||||||
route: Accessor<Route>;
|
|
||||||
setRoute: Setter<Route>;
|
|
||||||
}
|
|
||||||
export const Sidebar = (props: SidebarProps) => {
|
|
||||||
const { route, setRoute } = props;
|
|
||||||
return (
|
|
||||||
<aside class="min-h-screen w-80 bg-base-100">
|
|
||||||
<div class="sticky top-0 z-20 hidden items-center gap-2 bg-base-100/90 px-4 py-2 shadow-sm backdrop-blur lg:flex">
|
|
||||||
Icon
|
|
||||||
</div>
|
|
||||||
<ul class="menu px-4 py-0">
|
|
||||||
<For each={Object.entries(routes)}>
|
|
||||||
{([key, { label, icon }]) => (
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
onClick={() => setRoute(key as Route)}
|
|
||||||
class="group"
|
|
||||||
classList={{ "bg-blue-500": route() === key }}
|
|
||||||
>
|
|
||||||
<span class="material-icons">{icon}</span>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,5 +1,3 @@
|
|||||||
@import 'material-icons/iconfont/filled.css';
|
|
||||||
/* List of icons: https://marella.me/material-icons/demo/ */
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
@ -3,7 +3,7 @@ import { render } from "solid-js/web";
|
|||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { getFakeResponse } from "../mock";
|
|
||||||
const root = document.getElementById("app");
|
const root = document.getElementById("app");
|
||||||
|
|
||||||
window.clan = window.clan || {};
|
window.clan = window.clan || {};
|
||||||
@ -14,26 +14,5 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(import.meta.env);
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
console.log("Development mode");
|
|
||||||
window.webkit = window.webkit || {
|
|
||||||
messageHandlers: {
|
|
||||||
gtk: {
|
|
||||||
postMessage: (postMessage) => {
|
|
||||||
const { method, data } = postMessage;
|
|
||||||
console.debug("Python API call", { method, data });
|
|
||||||
setTimeout(() => {
|
|
||||||
const mock = getFakeResponse(method, data);
|
|
||||||
console.log("mock", { mock });
|
|
||||||
|
|
||||||
window.clan[method](JSON.stringify(mock));
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
render(() => <App />, root!);
|
render(() => <App />, root!);
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
export const Header = () => {
|
|
||||||
return (
|
|
||||||
<div class="navbar bg-base-100">
|
|
||||||
<div class="flex-none">
|
|
||||||
<button class="btn btn-square btn-ghost">
|
|
||||||
<span class="material-icons">home</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<a class="btn btn-ghost text-xl">Clan</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex-none">
|
|
||||||
<button class="btn btn-square btn-ghost">
|
|
||||||
<span class="material-icons">menu</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,35 +1,9 @@
|
|||||||
import { Component, JSXElement } from "solid-js";
|
import { Component, JSXElement } from "solid-js";
|
||||||
import { Header } from "./header";
|
|
||||||
import { Sidebar } from "../Sidebar";
|
|
||||||
import { route, setRoute } from "../App";
|
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: JSXElement;
|
children: JSXElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Layout: Component<LayoutProps> = (props) => {
|
export const Layout: Component<LayoutProps> = (props) => {
|
||||||
return (
|
return <div class="grid grid-cols-8">{props.children}</div>;
|
||||||
<>
|
|
||||||
<div class="drawer bg-base-100 lg:drawer-open">
|
|
||||||
<input
|
|
||||||
id="toplevel-drawer"
|
|
||||||
type="checkbox"
|
|
||||||
class="drawer-toggle hidden"
|
|
||||||
/>
|
|
||||||
<div class="drawer-content">
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
<div class="drawer-side z-40">
|
|
||||||
<label
|
|
||||||
for="toplevel-drawer"
|
|
||||||
aria-label="close sidebar"
|
|
||||||
class="drawer-overlay"
|
|
||||||
></label>
|
|
||||||
<Sidebar route={route} setRoute={setRoute} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
39
pkgs/webview-ui/app/src/nested.tsx
Normal file
39
pkgs/webview-ui/app/src/nested.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { For, Match, Switch, createEffect, type Component } from "solid-js";
|
||||||
|
import { useCountContext } from "./Config";
|
||||||
|
|
||||||
|
export const Nested: Component = () => {
|
||||||
|
const [{ machines, loading }, { getMachines }] = useCountContext();
|
||||||
|
|
||||||
|
const list = () => Object.values(machines());
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log("1", list());
|
||||||
|
});
|
||||||
|
createEffect(() => {
|
||||||
|
console.log("2", machines());
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => getMachines()} class="btn btn-primary">
|
||||||
|
Get machines
|
||||||
|
</button>
|
||||||
|
<div></div>
|
||||||
|
<Switch>
|
||||||
|
<Match when={loading()}>Loading...</Match>
|
||||||
|
<Match when={!loading() && Object.entries(machines()).length === 0}>
|
||||||
|
No machines found
|
||||||
|
</Match>
|
||||||
|
<Match when={!loading()}>
|
||||||
|
<For each={list()}>
|
||||||
|
{(entry, i) => (
|
||||||
|
<li>
|
||||||
|
{i() + 1}: {entry.machine_name}{" "}
|
||||||
|
{entry.machine_description || "No description"}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +0,0 @@
|
|||||||
export const colors = () => {
|
|
||||||
return (
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
|
||||||
<div class="h-10 w-20 bg-red-500">red</div>
|
|
||||||
<div class="h-10 w-20 bg-green-500">green</div>
|
|
||||||
<div class="h-10 w-20 bg-blue-500">blue</div>
|
|
||||||
<div class="h-10 w-20 bg-yellow-500">yellow</div>
|
|
||||||
<div class="h-10 w-20 bg-purple-500">purple</div>
|
|
||||||
<div class="h-10 w-20 bg-cyan-500">cyan</div>
|
|
||||||
<div class="h-10 w-20 bg-pink-500">pink</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,78 +0,0 @@
|
|||||||
import { For, Match, Switch, createEffect, type Component } from "solid-js";
|
|
||||||
import { useCountContext } from "../../Config";
|
|
||||||
import { route } from "@/src/App";
|
|
||||||
|
|
||||||
export const MachineListView: Component = () => {
|
|
||||||
const [{ machines, loading }, { getMachines }] = useCountContext();
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (route() === "machines") getMachines();
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div class="max-w-screen-lg">
|
|
||||||
<div class="tooltip" data-tip="Refresh ">
|
|
||||||
<button class="btn btn-ghost" onClick={() => getMachines()}>
|
|
||||||
<span class="material-icons ">refresh</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Switch>
|
|
||||||
<Match when={loading()}>
|
|
||||||
{/* Loading skeleton */}
|
|
||||||
<div>
|
|
||||||
<div class="card card-side m-2 bg-base-100 shadow-lg">
|
|
||||||
<figure class="pl-2">
|
|
||||||
<div class="skeleton size-12"></div>
|
|
||||||
</figure>
|
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="card-title">
|
|
||||||
<div class="skeleton h-12 w-80"></div>
|
|
||||||
</h2>
|
|
||||||
<div class="skeleton h-8 w-72"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={!loading() && machines().length === 0}>
|
|
||||||
No machines found
|
|
||||||
</Match>
|
|
||||||
<Match when={!loading()}>
|
|
||||||
<ul>
|
|
||||||
<For each={machines()}>
|
|
||||||
{(entry) => (
|
|
||||||
<li>
|
|
||||||
<div class="card card-side m-2 bg-base-100 shadow-lg">
|
|
||||||
<figure class="pl-2">
|
|
||||||
<span class="material-icons content-center text-5xl">
|
|
||||||
devices_other
|
|
||||||
</span>
|
|
||||||
</figure>
|
|
||||||
<div class="card-body flex-row justify-between">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h2 class="card-title">{entry}</h2>
|
|
||||||
{/*
|
|
||||||
<p
|
|
||||||
classList={{
|
|
||||||
"text-gray-400": !entry.machine_description,
|
|
||||||
"text-gray-600": !!entry.machine_description,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{entry.machine_description || "No description"}
|
|
||||||
</p>
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button class="btn btn-ghost">
|
|
||||||
<span class="material-icons">more_vert</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,11 +1,9 @@
|
|||||||
const typography = require("@tailwindcss/typography");
|
|
||||||
const daisyui = require("daisyui");
|
const daisyui = require("daisyui");
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [typography, daisyui],
|
plugins: [daisyui],
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
npmDeps = pkgs.fetchNpmDeps {
|
npmDeps = pkgs.fetchNpmDeps {
|
||||||
src = ./app;
|
src = ./app;
|
||||||
hash = "sha256-EadzSkIsV/cJtdxpIUvvpQhu5h3VyF8bLMpwfksNmWQ=";
|
hash = "sha256-E0++hupVKnDqmLk7ljoMcqcI4w+DIMlfRYRPbKUsT2c=";
|
||||||
};
|
};
|
||||||
# The prepack script runs the build script, which we'd rather do in the build phase.
|
# The prepack script runs the build script, which we'd rather do in the build phase.
|
||||||
npmPackFlags = [ "--ignore-scripts" ];
|
npmPackFlags = [ "--ignore-scripts" ];
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# DO NOT DELETE
|
|
||||||
# This file is used by the clan cli to discover a clan flake
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "My Empty Clan",
|
|
||||||
"description": "some nice description",
|
|
||||||
"icon": "A path to the png"
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
# Clan configuration file
|
|
||||||
# TODO: This file is used as a template for the simple GUI workflow
|
|
||||||
{
|
|
||||||
inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core";
|
|
||||||
outputs =
|
|
||||||
{ self, clan-core, ... }:
|
|
||||||
let
|
|
||||||
clan = clan-core.lib.buildClan {
|
|
||||||
# This clan builds all its configuration out of the current directory
|
|
||||||
directory = self;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (clan) nixosConfigurations clanInternals;
|
|
||||||
};
|
|
||||||
}
|
|
@ -5,10 +5,6 @@
|
|||||||
description = "Initialize a new clan flake";
|
description = "Initialize a new clan flake";
|
||||||
path = ./new-clan;
|
path = ./new-clan;
|
||||||
};
|
};
|
||||||
empty = {
|
|
||||||
description = "A empty clan template. Primarily for usage with the clan ui";
|
|
||||||
path = ./empty;
|
|
||||||
};
|
|
||||||
default = self.templates.new-clan;
|
default = self.templates.new-clan;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user