Merge pull request 'secrets: add password-store & deploy command' (#263) from lassulus-pass-secrets into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/263
This commit is contained in:
@@ -7,6 +7,7 @@ let
|
|||||||
float = "number";
|
float = "number";
|
||||||
int = "integer";
|
int = "integer";
|
||||||
str = "string";
|
str = "string";
|
||||||
|
path = "string"; # TODO add prober path checks
|
||||||
};
|
};
|
||||||
|
|
||||||
# remove _module attribute from options
|
# remove _module attribute from options
|
||||||
@@ -103,6 +104,13 @@ rec {
|
|||||||
type = "string";
|
type = "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# parse string
|
||||||
|
else if option.type.name == "path"
|
||||||
|
# return jsonschema property definition for path
|
||||||
|
then default // description // {
|
||||||
|
type = "string";
|
||||||
|
}
|
||||||
|
|
||||||
# parse enum
|
# parse enum
|
||||||
else if option.type.name == "enum"
|
else if option.type.name == "enum"
|
||||||
# return jsonschema property definition for enum
|
# return jsonschema property definition for enum
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
imports = [
|
imports = [
|
||||||
./secrets
|
./secrets
|
||||||
./zerotier.nix
|
./zerotier.nix
|
||||||
|
./networking.nix
|
||||||
inputs.sops-nix.nixosModules.sops
|
inputs.sops-nix.nixosModules.sops
|
||||||
# just some example options. Can be removed later
|
# just some example options. Can be removed later
|
||||||
./bloatware
|
./bloatware
|
||||||
|
|||||||
15
nixosModules/clanCore/networking.nix
Normal file
15
nixosModules/clanCore/networking.nix
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
{
|
||||||
|
options.clan.networking = {
|
||||||
|
deploymentAddress = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
The target SSH node for deployment.
|
||||||
|
|
||||||
|
By default, the node's attribute name will be used.
|
||||||
|
If set to null, only local deployment will be supported.
|
||||||
|
'';
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = "root@${config.networking.hostName}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
|
options.clanCore.secretStore = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "sops" "password-store" "custom" ];
|
||||||
|
default = "sops";
|
||||||
|
description = ''
|
||||||
|
method to store secrets
|
||||||
|
custom can be used to define a custom secret store.
|
||||||
|
one would have to define system.clan.generateSecrets and system.clan.uploadSecrets
|
||||||
|
'';
|
||||||
|
};
|
||||||
options.clanCore.secrets = lib.mkOption {
|
options.clanCore.secrets = lib.mkOption {
|
||||||
|
default = { };
|
||||||
type = lib.types.attrsOf
|
type = lib.types.attrsOf
|
||||||
(lib.types.submodule (secret: {
|
(lib.types.submodule (secret: {
|
||||||
options = {
|
options = {
|
||||||
@@ -49,10 +59,10 @@
|
|||||||
description = ''
|
description = ''
|
||||||
path to a fact which is generated by the generator
|
path to a fact which is generated by the generator
|
||||||
'';
|
'';
|
||||||
default = "${config.clanCore.clanDir}/machines/${config.clanCore.machineName}/facts/${fact.config._module.args.name}";
|
default = "machines/${config.clanCore.machineName}/facts/${fact.config._module.args.name}";
|
||||||
};
|
};
|
||||||
value = lib.mkOption {
|
value = lib.mkOption {
|
||||||
default = builtins.readFile fact.config.path;
|
default = builtins.readFile "${config.clanCore.clanDir}/fact.config.path";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
@@ -60,7 +70,12 @@
|
|||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
config.system.build.generateUploadSecrets = pkgs.writeScript "generate_upload_secrets" ''
|
||||||
|
${config.system.clan.generateSecrets}
|
||||||
|
${config.system.clan.uploadSecrets}
|
||||||
|
'';
|
||||||
imports = [
|
imports = [
|
||||||
./sops.nix # for now we have only one implementation, thats why we import it here and not in clanModules
|
./sops.nix
|
||||||
|
./password-store.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
118
nixosModules/clanCore/secrets/password-store.nix
Normal file
118
nixosModules/clanCore/secrets/password-store.nix
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
passwordstoreDir = "\${PASSWORD_STORE_DIR:-$HOME/.password-store}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.clan.password-store.targetDirectory = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "/etc/secrets";
|
||||||
|
description = ''
|
||||||
|
The directory where the password store is uploaded to.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
config = lib.mkIf (config.clanCore.secretStore == "password-store") {
|
||||||
|
system.clan.generateSecrets = pkgs.writeScript "generate-secrets" ''
|
||||||
|
#!/bin/sh
|
||||||
|
set -efu
|
||||||
|
|
||||||
|
test -d "$CLAN_DIR"
|
||||||
|
PATH=${lib.makeBinPath [
|
||||||
|
pkgs.pass
|
||||||
|
]}:$PATH
|
||||||
|
|
||||||
|
# TODO maybe initialize password store if it doesn't exist yet
|
||||||
|
|
||||||
|
${lib.foldlAttrs (acc: n: v: ''
|
||||||
|
${acc}
|
||||||
|
# ${n}
|
||||||
|
# if any of the secrets are missing, we regenerate all connected facts/secrets
|
||||||
|
(if ! ${lib.concatMapStringsSep " && " (x: "pass show machines/${config.clanCore.machineName}/${x.name} >/dev/null") (lib.attrValues v.secrets)}; then
|
||||||
|
|
||||||
|
facts=$(mktemp -d)
|
||||||
|
trap "rm -rf $facts" EXIT
|
||||||
|
secrets=$(mktemp -d)
|
||||||
|
trap "rm -rf $secrets" EXIT
|
||||||
|
${v.generator}
|
||||||
|
|
||||||
|
${lib.concatMapStrings (fact: ''
|
||||||
|
mkdir -p "$(dirname ${fact.path})"
|
||||||
|
cp "$facts"/${fact.name} "$CLAN_DIR"/${fact.path}
|
||||||
|
'') (lib.attrValues v.facts)}
|
||||||
|
|
||||||
|
${lib.concatMapStrings (secret: ''
|
||||||
|
cat "$secrets"/${secret.name} | pass insert -m machines/${config.clanCore.machineName}/${secret.name}
|
||||||
|
'') (lib.attrValues v.secrets)}
|
||||||
|
fi)
|
||||||
|
'') "" config.clanCore.secrets}
|
||||||
|
'';
|
||||||
|
system.clan.uploadSecrets = pkgs.writeScript "upload-secrets" ''
|
||||||
|
#!/bin/sh
|
||||||
|
set -efu
|
||||||
|
|
||||||
|
target=$1
|
||||||
|
|
||||||
|
umask 0077
|
||||||
|
|
||||||
|
PATH=${lib.makeBinPath [
|
||||||
|
pkgs.pass
|
||||||
|
pkgs.git
|
||||||
|
pkgs.findutils
|
||||||
|
pkgs.rsync
|
||||||
|
]}:$PATH:${lib.getBin pkgs.openssh}
|
||||||
|
|
||||||
|
if test -e ${passwordstoreDir}/.git; then
|
||||||
|
local_pass_info=$(
|
||||||
|
git -C ${passwordstoreDir} log -1 --format=%H machines/${config.clanCore.machineName}
|
||||||
|
# we append a hash for every symlink, otherwise we would miss updates on
|
||||||
|
# files where the symlink points to
|
||||||
|
find ${passwordstoreDir}/machines/${config.clanCore.machineName} -type l \
|
||||||
|
-exec realpath {} + |
|
||||||
|
sort |
|
||||||
|
xargs -r -n 1 git -C ${passwordstoreDir} log -1 --format=%H
|
||||||
|
)
|
||||||
|
remote_pass_info=$(ssh "$target" -- ${lib.escapeShellArg ''
|
||||||
|
cat ${config.clan.password-store.targetDirectory}/.pass_info || :
|
||||||
|
''})
|
||||||
|
|
||||||
|
if test "$local_pass_info" = "$remote_pass_info"; then
|
||||||
|
echo secrets already match
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_dir=$(mktemp -dt populate-pass.XXXXXXXX)
|
||||||
|
trap cleanup EXIT
|
||||||
|
cleanup() {
|
||||||
|
rm -fR "$tmp_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
find ${passwordstoreDir}/machines/${config.clanCore.machineName} -type f -follow ! -name .gpg-id |
|
||||||
|
while read -r gpg_path; do
|
||||||
|
|
||||||
|
rel_name=''${gpg_path#${passwordstoreDir}}
|
||||||
|
rel_name=''${rel_name%.gpg}
|
||||||
|
|
||||||
|
pass_date=$(
|
||||||
|
if test -e ${passwordstoreDir}/.git; then
|
||||||
|
git -C ${passwordstoreDir} log -1 --format=%aI "$gpg_path"
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
pass_name=$rel_name
|
||||||
|
tmp_path=$tmp_dir/$(basename $rel_name)
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$tmp_path")"
|
||||||
|
pass show "$pass_name" > "$tmp_path"
|
||||||
|
if [ -n "$pass_date" ]; then
|
||||||
|
touch -d "$pass_date" "$tmp_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if test -n "''${local_pass_info-}"; then
|
||||||
|
echo "$local_pass_info" > "$tmp_dir"/.pass_info
|
||||||
|
fi
|
||||||
|
|
||||||
|
rsync --mkpath --delete -a "$tmp_dir"/ "$target":${config.clan.password-store.targetDirectory}/
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -21,11 +21,12 @@ let
|
|||||||
secrets = filterDir containsMachineOrGroups secretsDir;
|
secrets = filterDir containsMachineOrGroups secretsDir;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = {
|
config = lib.mkIf (config.clanCore.secretStore == "sops") {
|
||||||
system.clan.generateSecrets = pkgs.writeScript "generate-secrets" ''
|
system.clan.generateSecrets = pkgs.writeScript "generate-secrets" ''
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -efu
|
set -efu
|
||||||
set -x # remove for prod
|
|
||||||
|
test -d "$CLAN_DIR"
|
||||||
|
|
||||||
PATH=$PATH:${lib.makeBinPath [
|
PATH=$PATH:${lib.makeBinPath [
|
||||||
config.clanCore.clanPkgs.clan-cli
|
config.clanCore.clanPkgs.clan-cli
|
||||||
@@ -55,7 +56,7 @@ in
|
|||||||
|
|
||||||
${lib.concatMapStrings (fact: ''
|
${lib.concatMapStrings (fact: ''
|
||||||
mkdir -p "$(dirname ${fact.path})"
|
mkdir -p "$(dirname ${fact.path})"
|
||||||
cp "$facts"/${fact.name} ${fact.path}
|
cp "$facts"/${fact.name} "$CLAN_DIR"/${fact.path}
|
||||||
'') (lib.attrValues v.facts)}
|
'') (lib.attrValues v.facts)}
|
||||||
|
|
||||||
${lib.concatMapStrings (secret: ''
|
${lib.concatMapStrings (secret: ''
|
||||||
@@ -64,6 +65,9 @@ in
|
|||||||
fi)
|
fi)
|
||||||
'') "" config.clanCore.secrets}
|
'') "" config.clanCore.secrets}
|
||||||
'';
|
'';
|
||||||
|
system.clan.uploadSecrets = pkgs.writeScript "upload-secrets" ''
|
||||||
|
echo upload is not needed for sops secret store, since the secrets are part of the flake
|
||||||
|
'';
|
||||||
sops.secrets = builtins.mapAttrs
|
sops.secrets = builtins.mapAttrs
|
||||||
(name: _: {
|
(name: _: {
|
||||||
sopsFile = config.clanCore.clanDir + "/sops/secrets/${name}/secret";
|
sopsFile = config.clanCore.clanDir + "/sops/secrets/${name}/secret";
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..dirs import get_clan_flake_toplevel
|
||||||
|
from ..nix import nix_eval
|
||||||
|
from ..secrets.generate import generate_secrets
|
||||||
|
from ..secrets.upload import upload_secrets
|
||||||
from ..ssh import Host, HostGroup, HostKeyCheck
|
from ..ssh import Host, HostGroup, HostKeyCheck
|
||||||
|
|
||||||
|
|
||||||
@@ -13,11 +19,13 @@ def deploy_nixos(hosts: HostGroup) -> None:
|
|||||||
def deploy(h: Host) -> None:
|
def deploy(h: Host) -> None:
|
||||||
target = f"{h.user or 'root'}@{h.host}"
|
target = f"{h.user or 'root'}@{h.host}"
|
||||||
ssh_arg = f"-p {h.port}" if h.port else ""
|
ssh_arg = f"-p {h.port}" if h.port else ""
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["NIX_SSHOPTS"] = ssh_arg
|
||||||
res = h.run_local(
|
res = h.run_local(
|
||||||
["nix", "flake", "archive", "--to", f"ssh://{target}", "--json"],
|
["nix", "flake", "archive", "--to", f"ssh://{target}", "--json"],
|
||||||
check=True,
|
check=True,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
extra_env=dict(NIX_SSHOPTS=ssh_arg),
|
extra_env=env,
|
||||||
)
|
)
|
||||||
data = json.loads(res.stdout)
|
data = json.loads(res.stdout)
|
||||||
path = data["path"]
|
path = data["path"]
|
||||||
@@ -29,6 +37,9 @@ def deploy_nixos(hosts: HostGroup) -> None:
|
|||||||
|
|
||||||
ssh_arg += " -i " + h.key if h.key else ""
|
ssh_arg += " -i " + h.key if h.key else ""
|
||||||
|
|
||||||
|
generate_secrets(h.host)
|
||||||
|
upload_secrets(h.host)
|
||||||
|
|
||||||
flake_attr = h.meta.get("flake_attr", "")
|
flake_attr = h.meta.get("flake_attr", "")
|
||||||
if flake_attr:
|
if flake_attr:
|
||||||
flake_attr = "#" + flake_attr
|
flake_attr = "#" + flake_attr
|
||||||
@@ -67,20 +78,36 @@ def deploy_nixos(hosts: HostGroup) -> None:
|
|||||||
|
|
||||||
# FIXME: we want some kind of inventory here.
|
# FIXME: we want some kind of inventory here.
|
||||||
def update(args: argparse.Namespace) -> None:
|
def update(args: argparse.Namespace) -> None:
|
||||||
meta = {}
|
clan_dir = get_clan_flake_toplevel().as_posix()
|
||||||
if args.flake_uri:
|
host = json.loads(
|
||||||
meta["flake_uri"] = args.flake_uri
|
subprocess.run(
|
||||||
if args.flake_attr:
|
nix_eval(
|
||||||
meta["flake_attr"] = args.flake_attr
|
[
|
||||||
deploy_nixos(HostGroup([Host(args.host, user=args.user, meta=meta)]))
|
f'{clan_dir}#nixosConfigurations."{args.machine}".config.clan.networking.deploymentAddress'
|
||||||
|
]
|
||||||
|
),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
).stdout
|
||||||
|
)
|
||||||
|
parts = host.split("@")
|
||||||
|
user: Optional[str] = None
|
||||||
|
if len(parts) > 1:
|
||||||
|
user = parts[0]
|
||||||
|
hostname = parts[1]
|
||||||
|
else:
|
||||||
|
hostname = parts[0]
|
||||||
|
maybe_port = hostname.split(":")
|
||||||
|
port = None
|
||||||
|
if len(maybe_port) > 1:
|
||||||
|
hostname = maybe_port[0]
|
||||||
|
port = int(maybe_port[1])
|
||||||
|
print(f"deploying {host}")
|
||||||
|
deploy_nixos(HostGroup([Host(host=hostname, port=port, user=user)]))
|
||||||
|
|
||||||
|
|
||||||
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
# TODO pass all args we don't parse into ssh_args, currently it fails if arg starts with -
|
parser.add_argument("--target-host", type=str, default="root")
|
||||||
parser.add_argument("--flake-uri", type=str, default=".#", help="nix flake uri")
|
parser.add_argument("machine", type=str)
|
||||||
parser.add_argument(
|
|
||||||
"--flake-attr", type=str, help="nixos configuration in the flake"
|
|
||||||
)
|
|
||||||
parser.add_argument("--user", type=str, default="root")
|
|
||||||
parser.add_argument("host", type=str)
|
|
||||||
parser.set_defaults(func=update)
|
parser.set_defaults(func=update)
|
||||||
|
|||||||
@@ -1,43 +1,18 @@
|
|||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from .dirs import get_clan_flake_toplevel, nixpkgs_flake, nixpkgs_source, unfree_nixpkgs
|
from .dirs import nixpkgs_flake, nixpkgs_source, unfree_nixpkgs
|
||||||
|
|
||||||
|
|
||||||
def nix_build_machine(
|
def nix_build(
|
||||||
machine: str, attr: list[str], flake_url: Path | None = None
|
flags: list[str],
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
if flake_url is None:
|
|
||||||
flake_url = get_clan_flake_toplevel()
|
|
||||||
payload = json.dumps(
|
|
||||||
dict(
|
|
||||||
clan_flake=flake_url,
|
|
||||||
machine=machine,
|
|
||||||
attr=attr,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
escaped_payload = json.dumps(payload)
|
|
||||||
return [
|
return [
|
||||||
"nix",
|
"nix",
|
||||||
"build",
|
"build",
|
||||||
"--impure",
|
"--no-link",
|
||||||
"--print-out-paths",
|
"--print-out-paths",
|
||||||
"--expr",
|
] + flags
|
||||||
f"let args = builtins.fromJSON {escaped_payload}; in "
|
|
||||||
"""
|
|
||||||
let
|
|
||||||
flake = builtins.getFlake args.clan_flake;
|
|
||||||
config = flake.nixosConfigurations.${args.machine}.extendModules {
|
|
||||||
modules = [{
|
|
||||||
clanCore.clanDir = args.clan_flake;
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
flake.inputs.nixpkgs.lib.getAttrFromPath args.attr config
|
|
||||||
""",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def nix_eval(flags: list[str]) -> list[str]:
|
def nix_eval(flags: list[str]) -> list[str]:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from .import_sops import register_import_sops_parser
|
|||||||
from .key import register_key_parser
|
from .key import register_key_parser
|
||||||
from .machines import register_machines_parser
|
from .machines import register_machines_parser
|
||||||
from .secrets import register_secrets_parser
|
from .secrets import register_secrets_parser
|
||||||
|
from .upload import register_upload_parser
|
||||||
from .users import register_users_parser
|
from .users import register_users_parser
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +37,9 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
)
|
)
|
||||||
register_generate_parser(parser_generate)
|
register_generate_parser(parser_generate)
|
||||||
|
|
||||||
|
parser_upload = subparser.add_parser("upload", help="upload secrets for machines")
|
||||||
|
register_upload_parser(parser_upload)
|
||||||
|
|
||||||
parser_key = subparser.add_parser("key", help="create and show age keys")
|
parser_key = subparser.add_parser("key", help="create and show age keys")
|
||||||
register_key_parser(parser_key)
|
register_key_parser(parser_key)
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
|
|
||||||
|
from ..dirs import get_clan_flake_toplevel
|
||||||
|
from ..nix import nix_build
|
||||||
|
|
||||||
|
|
||||||
|
def generate_secrets(machine: str) -> None:
|
||||||
|
clan_dir = get_clan_flake_toplevel().as_posix().strip()
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["CLAN_DIR"] = clan_dir
|
||||||
|
|
||||||
def get_secret_script(machine: str) -> None:
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
|
nix_build(
|
||||||
[
|
[
|
||||||
"nix",
|
f'path:{clan_dir}#nixosConfigurations."{machine}".config.system.clan.generateSecrets'
|
||||||
"build",
|
]
|
||||||
"--impure",
|
),
|
||||||
"--print-out-paths",
|
|
||||||
"--expr",
|
|
||||||
"let f = builtins.getFlake (toString ./.); in "
|
|
||||||
f"(f.nixosConfigurations.{machine}.extendModules "
|
|
||||||
"{ modules = [{ clanCore.clanDir = toString ./.; }]; })"
|
|
||||||
".config.system.clan.generateSecrets",
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
@@ -30,7 +31,7 @@ def get_secret_script(machine: str) -> None:
|
|||||||
print(secret_generator_script)
|
print(secret_generator_script)
|
||||||
secret_generator = subprocess.run(
|
secret_generator = subprocess.run(
|
||||||
[secret_generator_script],
|
[secret_generator_script],
|
||||||
check=True,
|
env=env,
|
||||||
)
|
)
|
||||||
|
|
||||||
if secret_generator.returncode != 0:
|
if secret_generator.returncode != 0:
|
||||||
@@ -40,7 +41,7 @@ def get_secret_script(machine: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def generate_command(args: argparse.Namespace) -> None:
|
def generate_command(args: argparse.Namespace) -> None:
|
||||||
get_secret_script(args.machine)
|
generate_secrets(args.machine)
|
||||||
|
|
||||||
|
|
||||||
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
|||||||
60
pkgs/clan-cli/clan_cli/secrets/upload.py
Normal file
60
pkgs/clan-cli/clan_cli/secrets/upload.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from clan_cli.errors import ClanError
|
||||||
|
|
||||||
|
from ..dirs import get_clan_flake_toplevel
|
||||||
|
from ..nix import nix_build, nix_eval
|
||||||
|
|
||||||
|
|
||||||
|
def upload_secrets(machine: str) -> None:
|
||||||
|
clan_dir = get_clan_flake_toplevel().as_posix()
|
||||||
|
|
||||||
|
proc = subprocess.run(
|
||||||
|
nix_build(
|
||||||
|
[
|
||||||
|
f'{clan_dir}#nixosConfigurations."{machine}".config.system.clan.uploadSecrets'
|
||||||
|
]
|
||||||
|
),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
host = json.loads(
|
||||||
|
subprocess.run(
|
||||||
|
nix_eval(
|
||||||
|
[
|
||||||
|
f'{clan_dir}#nixosConfigurations."{machine}".config.clan.networking.deploymentAddress'
|
||||||
|
]
|
||||||
|
),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
).stdout
|
||||||
|
)
|
||||||
|
|
||||||
|
secret_upload_script = proc.stdout.strip()
|
||||||
|
secret_upload = subprocess.run(
|
||||||
|
[
|
||||||
|
secret_upload_script,
|
||||||
|
host,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
if secret_upload.returncode != 0:
|
||||||
|
raise ClanError("failed to upload secrets")
|
||||||
|
else:
|
||||||
|
print("successfully uploaded secrets")
|
||||||
|
|
||||||
|
|
||||||
|
def upload_command(args: argparse.Namespace) -> None:
|
||||||
|
upload_secrets(args.machine)
|
||||||
|
|
||||||
|
|
||||||
|
def register_upload_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.add_argument(
|
||||||
|
"machine",
|
||||||
|
help="The machine to upload secrets to",
|
||||||
|
)
|
||||||
|
parser.set_defaults(func=upload_command)
|
||||||
Reference in New Issue
Block a user