rename deployment address to target address

This is a prepares having a build server for deployment
This commit is contained in:
Jörg Thalheim
2024-02-02 11:32:48 +07:00
parent 3183a3e2fc
commit 584299e199
16 changed files with 69 additions and 43 deletions

View File

@@ -5,6 +5,7 @@ let
directory = ../..; directory = ../..;
machines = { machines = {
test_backup_client = { test_backup_client = {
clan.networking.targetHost = "client";
imports = [ self.nixosModules.test_backup_client ]; imports = [ self.nixosModules.test_backup_client ];
fileSystems."/".device = "/dev/null"; fileSystems."/".device = "/dev/null";
boot.loader.grub.device = "/dev/null"; boot.loader.grub.device = "/dev/null";

View File

@@ -5,6 +5,7 @@ let
directory = ../..; directory = ../..;
machines = { machines = {
test_install_machine = { test_install_machine = {
clan.networking.targetHost = "test_install_machine";
imports = [ self.nixosModules.test_install_machine ]; imports = [ self.nixosModules.test_install_machine ];
}; };
}; };

View File

@@ -88,17 +88,18 @@ $ clan machines install my-machine <target_host>
## Update Your Machines ## Update Your Machines
Clan CLI enables you to remotely update your machines over SSH. This requires setting up a deployment address for each target machine. Clan CLI enables you to remotely update your machines over SSH. This requires setting up a target address for each target machine.
### Setting the Deployment Address ### Setting the Target Host
Replace `host_or_ip` with the actual hostname or IP address of your target machine: Replace `host_or_ip` with the actual hostname or IP address of your target machine:
```shellSession ```shellSession
$ clan config --machine my-machine clan.networking.deploymentAddress root@host_or_ip $ clan config --machine my-machine clan.networking.targetHost root@host_or_ip
``` ```
_Note: The use of `root@` in the deployment address implies SSH access as the root user. Ensure that the root login is secured and only used when necessary._ _Note: The use of `root@` in the target address implies SSH access as the root user.
Ensure that the root login is secured and only used when necessary._
### Updating Machine Configurations ### Updating Machine Configurations

View File

@@ -1,7 +1,7 @@
{ config, lib, ... }: { config, lib, ... }:
{ {
options.clan.networking = { options.clan.networking = {
deploymentAddress = lib.mkOption { targetHost = lib.mkOption {
description = '' description = ''
The target SSH node for deployment. The target SSH node for deployment.
@@ -14,10 +14,27 @@
- user@machine2.example.com - user@machine2.example.com
- root@example.com:2222&IdentityFile=/path/to/private/key - root@example.com:2222&IdentityFile=/path/to/private/key
''; '';
type = lib.types.str;
};
buildHost = lib.mkOption {
description = ''
The build SSH node where nixos-rebuild will be executed.
If set to null, the targetHost will be used.
format: user@host:port&SSH_OPTION=SSH_VALUE
examples:
- machine.example.com
- user@machine2.example.com
- root@example.com:2222&IdentityFile=/path/to/private/key
'';
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = "root@${config.networking.hostName}"; default = null;
}; };
}; };
imports = [
(lib.mkRenamedOptionModule [ "clan" "networking" "deploymentAddress" ] [ "clan" "networking" "buildHost" ])
];
config = { config = {
# conflicts with systemd-resolved # conflicts with systemd-resolved
networking.useHostResolvConf = false; networking.useHostResolvConf = false;

View File

@@ -19,10 +19,16 @@
the location of the deployment.json file the location of the deployment.json file
''; '';
}; };
deploymentAddress = lib.mkOption { deployment.buildHost = lib.mkOption {
type = lib.types.str; type = lib.types.str;
description = '' description = ''
the address of the deployment server the hostname of the build host where nixos-rebuild is run
'';
};
deployment.targetHost = lib.mkOption {
type = lib.types.str;
description = ''
the hostname of the target host to be deployed to
''; '';
}; };
secretsUploadDirectory = lib.mkOption { secretsUploadDirectory = lib.mkOption {
@@ -66,10 +72,10 @@
config = { config = {
system.clan.deployment.data = { system.clan.deployment.data = {
inherit (config.system.clan) secretsModule secretsData; inherit (config.system.clan) secretsModule secretsData;
inherit (config.clan.networking) deploymentAddress; inherit (config.clan.networking) targetHost buildHost;
inherit (config.clanCore) secretsUploadDirectory; inherit (config.clanCore) secretsUploadDirectory;
}; };
system.clan.deployment.file = pkgs.writeText "deployment.json" (builtins.toJSON config.system.clan.deployment.data); system.clan.deployment.file = pkgs.writeText "deployment.json" (builtins.toJSON config.system.clan.deployment.data);
};
};
} }

View File

@@ -14,14 +14,12 @@ log = logging.getLogger(__name__)
def install_nixos(machine: Machine, kexec: str | None = None) -> None: def install_nixos(machine: Machine, kexec: str | None = None) -> None:
log.info(f"deployment address1: {machine.deployment_info['deploymentAddress']}")
secrets_module = importlib.import_module(machine.secrets_module) secrets_module = importlib.import_module(machine.secrets_module)
log.info(f"installing {machine.name}") log.info(f"installing {machine.name}")
log.info(f"using secret store: {secrets_module.SecretStore}") log.info(f"using secret store: {secrets_module.SecretStore}")
secret_store = secrets_module.SecretStore(machine=machine) secret_store = secrets_module.SecretStore(machine=machine)
h = machine.host h = machine.host
log.info(f"deployment address2: {machine.deployment_info['deploymentAddress']}")
target_host = f"{h.user or 'root'}@{h.host}" target_host = f"{h.user or 'root'}@{h.host}"
log.info(f"target host: {target_host}") log.info(f"target host: {target_host}")
@@ -77,10 +75,7 @@ def install_command(args: argparse.Namespace) -> None:
kexec=args.kexec, kexec=args.kexec,
) )
machine = Machine(opts.machine, flake=opts.flake) machine = Machine(opts.machine, flake=opts.flake)
machine.get_deployment_info() machine.target_host = opts.target_host
machine.deployment_info["deploymentAddress"] = opts.target_host
log.info(f"target host: {opts.target_host}")
log.info(f"deployment address: {machine.deployment_info['deploymentAddress']}")
install_nixos(machine, kexec=opts.kexec) install_nixos(machine, kexec=opts.kexec)

View File

@@ -28,8 +28,7 @@ class Machine:
self.eval_cache: dict[str, str] = {} self.eval_cache: dict[str, str] = {}
self.build_cache: dict[str, Path] = {} self.build_cache: dict[str, Path] = {}
if deployment_info is not None: self._deployment_info: None | dict[str, str] = deployment_info
self.deployment_info = deployment_info
def __str__(self) -> str: def __str__(self) -> str:
return f"Machine(name={self.name}, flake={self.flake})" return f"Machine(name={self.name}, flake={self.flake})"
@@ -37,29 +36,34 @@ class Machine:
def __repr__(self) -> str: def __repr__(self) -> str:
return str(self) return str(self)
def get_deployment_info(self) -> None: @property
self.deployment_info = json.loads( def deployment_info(self) -> dict[str, str]:
if self._deployment_info is not None:
return self._deployment_info
self._deployment_info = json.loads(
self.build_nix("config.system.clan.deployment.file").read_text() self.build_nix("config.system.clan.deployment.file").read_text()
) )
print(f"self_deployment_info: {self.deployment_info}") print(f"self_deployment_info: {self.deployment_info}")
return self._deployment_info
@property @property
def deployment_address(self) -> str: def target_host(self) -> str:
if not hasattr(self, "deployment_info"): # deploymentAddress is deprecated.
self.get_deployment_info() return (
return self.deployment_info["deploymentAddress"] self.deployment_info.get("targetHost")
or self.deployment_info["deploymentAddress"]
)
@target_host.setter
def target_host(self, value: str) -> None:
self.deployment_info["targetHost"] = value
@property @property
def secrets_module(self) -> str: def secrets_module(self) -> str:
if not hasattr(self, "deployment_info"):
self.get_deployment_info()
print(f"self_deployment_info2: {self.deployment_info}")
return self.deployment_info["secretsModule"] return self.deployment_info["secretsModule"]
@property @property
def secrets_data(self) -> dict: def secrets_data(self) -> dict:
if not hasattr(self, "deployment_info"):
self.get_deployment_info()
if self.deployment_info["secretsData"]: if self.deployment_info["secretsData"]:
try: try:
return json.loads(Path(self.deployment_info["secretsData"]).read_text()) return json.loads(Path(self.deployment_info["secretsData"]).read_text())
@@ -72,8 +76,6 @@ class Machine:
@property @property
def secrets_upload_directory(self) -> str: def secrets_upload_directory(self) -> str:
if not hasattr(self, "deployment_info"):
self.get_deployment_info()
return self.deployment_info["secretsUploadDirectory"] return self.deployment_info["secretsUploadDirectory"]
@property @property
@@ -90,7 +92,7 @@ class Machine:
@property @property
def host(self) -> Host: def host(self) -> Host:
return parse_deployment_address( return parse_deployment_address(
self.name, self.deployment_address, meta={"machine": self} self.name, self.target_host, meta={"machine": self}
) )
def eval_nix(self, attr: str, refresh: bool = False) -> str: def eval_nix(self, attr: str, refresh: bool = False) -> str:

View File

@@ -91,7 +91,7 @@ def get_all_machines(clan_dir: Path) -> HostGroup:
# very hacky. would be better to do a MachinesGroup instead # very hacky. would be better to do a MachinesGroup instead
host = parse_deployment_address( host = parse_deployment_address(
name, name,
machine_data["deploymentAddress"], machine_data.get("targetHost") or machine_data.get("deploymentAddress"),
meta={ meta={
"machine": Machine( "machine": Machine(
name=name, flake=clan_dir, deployment_info=machine_data name=name, flake=clan_dir, deployment_info=machine_data
@@ -116,7 +116,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")
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(name=args.machines[0], flake=args.flake) machine = Machine(name=args.machines[0], flake=args.flake)
machine.deployment_info["deploymentAddress"] = args.target_host machine.target_host = args.target_host
host = parse_deployment_address( host = parse_deployment_address(
args.machines[0], args.machines[0],
args.target_host, args.target_host,

View File

@@ -1,5 +1,5 @@
{ lib, ... }: { { lib, ... }: {
clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; clan.networking.targetHost = "__CLAN_TARGET_ADDRESS__";
system.stateVersion = lib.version; system.stateVersion = lib.version;
sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__";
clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__";

View File

@@ -1,5 +1,5 @@
{ lib, ... }: { { lib, ... }: {
clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; clan.networking.targetHost = "__CLAN_TARGET_ADDRESS__";
system.stateVersion = lib.version; system.stateVersion = lib.version;
sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__";
clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__";

View File

@@ -1,5 +1,5 @@
{ lib, ... }: { { lib, ... }: {
clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; clan.networking.targetHost = "__CLAN_TARGET_ADDRESS__";
system.stateVersion = lib.version; system.stateVersion = lib.version;
clan.virtualisation.graphics = false; clan.virtualisation.graphics = false;

View File

@@ -12,7 +12,7 @@
clanName = "test_flake_with_core"; clanName = "test_flake_with_core";
machines = { machines = {
vm1 = { lib, ... }: { vm1 = { lib, ... }: {
clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; clan.networking.targetHost = "__CLAN_TARGET_ADDRESS__";
system.stateVersion = lib.version; system.stateVersion = lib.version;
sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__";
clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__";
@@ -32,7 +32,7 @@
}; };
}; };
vm2 = { lib, ... }: { vm2 = { lib, ... }: {
clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; clan.networking.targetHost = "__CLAN_TARGET_ADDRESS__";
system.stateVersion = lib.version; system.stateVersion = lib.version;
sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__";
clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__";

View File

@@ -12,7 +12,7 @@
clanName = "test_flake_with_core_and_pass"; clanName = "test_flake_with_core_and_pass";
machines = { machines = {
vm1 = { lib, ... }: { vm1 = { lib, ... }: {
clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; clan.networking.targetHost = "__CLAN_TARGET_ADDRESS__";
system.stateVersion = lib.version; system.stateVersion = lib.version;
clanCore.secretStore = "password-store"; clanCore.secretStore = "password-store";
clanCore.secretsUploadDirectory = lib.mkForce "__CLAN_SOPS_KEY_DIR__/secrets"; clanCore.secretsUploadDirectory = lib.mkForce "__CLAN_SOPS_KEY_DIR__/secrets";

View File

@@ -60,7 +60,7 @@ def test_upload_secret(
flake = test_flake_with_core_and_pass.path.joinpath("flake.nix") flake = test_flake_with_core_and_pass.path.joinpath("flake.nix")
host = host_group.hosts[0] host = host_group.hosts[0]
addr = f"{host.user}@{host.host}:{host.port}?StrictHostKeyChecking=no&UserKnownHostsFile=/dev/null&IdentityFile={host.key}" addr = f"{host.user}@{host.host}:{host.port}?StrictHostKeyChecking=no&UserKnownHostsFile=/dev/null&IdentityFile={host.key}"
new_text = flake.read_text().replace("__CLAN_DEPLOYMENT_ADDRESS__", addr) new_text = flake.read_text().replace("__CLAN_TARGET_ADDRESS__", addr)
flake.write_text(new_text) flake.write_text(new_text)
cli.run(["secrets", "upload", "vm1"]) cli.run(["secrets", "upload", "vm1"])
zerotier_identity_secret = ( zerotier_identity_secret = (

View File

@@ -52,7 +52,7 @@ def test_secrets_upload(
flake = test_flake_with_core.path.joinpath("flake.nix") flake = test_flake_with_core.path.joinpath("flake.nix")
host = host_group.hosts[0] host = host_group.hosts[0]
addr = f"{host.user}@{host.host}:{host.port}?StrictHostKeyChecking=no&UserKnownHostsFile=/dev/null&IdentityFile={host.key}" addr = f"{host.user}@{host.host}:{host.port}?StrictHostKeyChecking=no&UserKnownHostsFile=/dev/null&IdentityFile={host.key}"
new_text = flake.read_text().replace("__CLAN_DEPLOYMENT_ADDRESS__", addr) new_text = flake.read_text().replace("__CLAN_TARGET_ADDRESS__", addr)
flake.write_text(new_text) flake.write_text(new_text)
cli.run(["--flake", str(test_flake_with_core.path), "secrets", "upload", "vm1"]) cli.run(["--flake", str(test_flake_with_core.path), "secrets", "upload", "vm1"])

View File

@@ -154,7 +154,10 @@ def test_vm_persistence(
), ),
) )
), ),
clan=dict(virtualisation=dict(graphics=False)), clan=dict(
virtualisation=dict(graphics=False),
networking=dict(targetHost="client"),
),
) )
), ),
) )