183 lines
4.3 KiB
Python
Executable file
183 lines
4.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
import argparse
|
|
import json
|
|
import pathlib
|
|
import shutil
|
|
import socket
|
|
import time
|
|
|
|
from subprocess import check_output
|
|
|
|
ROOT_DIR = pathlib.Path(__file__).parent.parent
|
|
KEYS_FILE = ROOT_DIR / "secrets" / "keys.json"
|
|
|
|
|
|
def _get_available_machines() -> list:
|
|
output = check_output(["nix", "flake", "show", "--json"])
|
|
parsed_output = json.loads(output)
|
|
machines = parsed_output.get("nixosConfigurations", dict()).keys()
|
|
return list(machines)
|
|
|
|
|
|
def _is_valid_ip(ip: str) -> bool:
|
|
try:
|
|
socket.inet_aton(ip)
|
|
return True
|
|
except socket.error:
|
|
return False
|
|
|
|
|
|
def _check_ssh_connection(ip: str) -> bool:
|
|
try:
|
|
check_output(["ssh", f"krop@{ip}", "echo", "Connected"])
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def add_key_to_secrets(machine_name: str, key: str):
|
|
keys = json.loads(KEYS_FILE.read_text())
|
|
if keys.get("servers").get(machine_name):
|
|
raise ValueError(f"Key for {machine_name} already exists, remove it first")
|
|
|
|
keys["servers"][machine_name] = key
|
|
|
|
for secret in keys.get("secrets"):
|
|
keys["secrets"][secret].append(f"servers:{machine_name}")
|
|
|
|
KEYS_FILE.write_text(json.dumps(keys, indent=2))
|
|
|
|
|
|
def rekey_secrets():
|
|
agenix_bin = shutil.which("agenix")
|
|
check_output([agenix_bin, "-r"], cwd=ROOT_DIR / "secrets")
|
|
|
|
|
|
def bootstrap_machine(ip: str):
|
|
check_output(
|
|
[
|
|
"nix",
|
|
"run",
|
|
"github:nix-community/nixos-anywhere",
|
|
"--",
|
|
"--flake",
|
|
".#bootstrap",
|
|
"--target-host",
|
|
f"root@{ip}",
|
|
"--build-on-remote",
|
|
]
|
|
)
|
|
|
|
|
|
def install_machine(machine_name: str, ip: str):
|
|
check_output(
|
|
[
|
|
"nixos-rebuild",
|
|
"boot",
|
|
"--flake",
|
|
f".#{machine_name}",
|
|
"--fast",
|
|
"--target-host",
|
|
f"krop@{ip}",
|
|
"--build-host",
|
|
f"krop@{ip}",
|
|
"--use-remote-sudo",
|
|
]
|
|
)
|
|
|
|
|
|
def get_ssh_key(ip: str) -> str:
|
|
"""
|
|
This function uses machines ssh-keyscan to get the ssh key and then get the ed25519 key
|
|
"""
|
|
ssh_keys = (
|
|
check_output(
|
|
[
|
|
"ssh-keyscan",
|
|
"-q",
|
|
"-t",
|
|
"ed25519",
|
|
ip,
|
|
]
|
|
)
|
|
.decode("utf-8")
|
|
.strip()
|
|
.splitlines()
|
|
)
|
|
|
|
if len(ssh_keys) != 1:
|
|
raise ValueError("Exactly one key should be returned")
|
|
|
|
key = ssh_keys.pop().lstrip(f"{ip} ").strip()
|
|
|
|
return key
|
|
|
|
|
|
def get_machine_config(machine_name: str) -> dict:
|
|
output = check_output(
|
|
[
|
|
"nix",
|
|
"eval",
|
|
"--json",
|
|
f".#nixosConfigurations.{machine_name}.config.kropcloud",
|
|
]
|
|
)
|
|
return json.loads(output)
|
|
|
|
|
|
def reboot_machine(ip: str):
|
|
check_output(
|
|
[
|
|
"ssh",
|
|
f"krop@{ip}",
|
|
"sudo",
|
|
"reboot",
|
|
]
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Install a machine")
|
|
parser.add_argument(
|
|
"machine_name", type=str, help="The name of the machine to install"
|
|
)
|
|
parser.add_argument("machine_ip", type=str, help="The ip of the machine to install")
|
|
args = parser.parse_args()
|
|
|
|
machine_name = args.machine_name
|
|
if machine_name not in _get_available_machines():
|
|
raise ValueError(
|
|
f"Machine {machine_name} not found, available machines are: {_get_available_machines()}"
|
|
)
|
|
|
|
machine_ip = args.machine_ip
|
|
if not _is_valid_ip(machine_ip):
|
|
raise ValueError(f"Invalid IP address {machine_ip}")
|
|
|
|
print(f"Bootstrapping machine {machine_ip}")
|
|
bootstrap_machine(machine_ip)
|
|
print("Machine bootstrapped")
|
|
|
|
print("Waiting for ssh connection")
|
|
while not _check_ssh_connection(machine_ip):
|
|
time.sleep(5)
|
|
print("Machine is up and running")
|
|
|
|
print("Getting ssh key")
|
|
ssh_key = get_ssh_key(machine_ip)
|
|
|
|
print(f"SSH key: {ssh_key}")
|
|
print("Adding ssh key to secrets")
|
|
add_key_to_secrets(machine_name, ssh_key)
|
|
rekey_secrets()
|
|
|
|
print("Installing machine")
|
|
install_machine(machine_name, machine_ip)
|
|
print("Machine installed, rebooting")
|
|
reboot_machine(machine_ip)
|
|
print("")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|