From 08a7004dda03d1b247733040de3d96fe2f08f86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Thu, 2 Jan 2025 16:21:43 +0100 Subject: [PATCH 1/9] bootstrapping default bootstrap password think this is required pw update pseudoterm change pw --- flake.nix | 9 ++++----- hosts/base/hardware-config.nix | 2 -- hosts/bootstrap/default.nix | 7 +++++++ hosts/etcd0/default.nix | 18 +++++++++++++++++ lib.nix | 3 +-- nixosModules/users/default.nix | 19 +++++++++++++----- scripts/bootstrap.sh | 23 ++++++++++++++++++++++ scripts/install.sh | 34 +++++++++++++++++++++++++++++++++ scripts/update.sh | 25 ++++++++++++++++++++++++ secrets/mypassword.age | Bin 505 -> 505 bytes 10 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 hosts/bootstrap/default.nix create mode 100644 hosts/etcd0/default.nix create mode 100755 scripts/bootstrap.sh create mode 100755 scripts/install.sh create mode 100755 scripts/update.sh diff --git a/flake.nix b/flake.nix index c6e0123..e048442 100644 --- a/flake.nix +++ b/flake.nix @@ -23,17 +23,16 @@ inputs@{ self, nixpkgs, ... }: let kclib = import ./lib.nix { - nixpkgs = inputs.nixpkgs; inputs = inputs; }; in { nixosConfigurations = { - tailscale-proxy = kclib.mkHost { - name = "tailscale-proxy"; + bootstrap = kclib.mkHost { + name = "bootstrap"; }; - entrypoint = kclib.mkHost { - name = "entrypoint"; + etcd0 = kclib.mkHost { + name = "etcd0"; }; hydra = kclib.mkHost { name = "hydra"; diff --git a/hosts/base/hardware-config.nix b/hosts/base/hardware-config.nix index 781026e..8c4d9f2 100644 --- a/hosts/base/hardware-config.nix +++ b/hosts/base/hardware-config.nix @@ -1,7 +1,5 @@ { - config, lib, - pkgs, modulesPath, ... }: diff --git a/hosts/bootstrap/default.nix b/hosts/bootstrap/default.nix new file mode 100644 index 0000000..c36a525 --- /dev/null +++ b/hosts/bootstrap/default.nix @@ -0,0 +1,7 @@ +{ ... }: +{ + kropcloud = { + networking.enable = false; + admin.password = "changeme"; + }; +} diff --git a/hosts/etcd0/default.nix b/hosts/etcd0/default.nix new file mode 100644 index 0000000..5ef2949 --- /dev/null +++ b/hosts/etcd0/default.nix @@ -0,0 +1,18 @@ +{ ... }: +{ + kropcloud = + let + serverIp = "192.168.1.161"; + in + { + services = { + }; + networking = { + ipv4 = { + address = serverIp; + prefixLength = 24; + defaultGateway = "192.168.1.1"; + }; + }; + }; +} diff --git a/lib.nix b/lib.nix index 95c26d5..8c55d24 100644 --- a/lib.nix +++ b/lib.nix @@ -1,5 +1,4 @@ { - nixpkgs, inputs, }: { @@ -8,7 +7,7 @@ name, arch ? "x86_64-linux", }: - nixpkgs.lib.nixosSystem { + inputs.nixpkgs.lib.nixosSystem { system = arch; modules = [ ./hosts/base diff --git a/nixosModules/users/default.nix b/nixosModules/users/default.nix index 9add441..d19618e 100644 --- a/nixosModules/users/default.nix +++ b/nixosModules/users/default.nix @@ -14,6 +14,11 @@ in default = [ ]; description = "List of SSH public keys to authorize for the admin user."; }; + password = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Password for the admin user. Should be used only for initial setup."; + }; }; config = { @@ -21,11 +26,15 @@ in age.secrets.mypassword.file = ../../secrets/mypassword.age; # Define the admin user - users.users.${cfg.user} = { - passwordFile = config.age.secrets.mypassword.path; - isNormalUser = true; - extraGroups = [ "wheel" ]; - openssh.authorizedKeys.keys = cfg.sshKeys; + users = { + mutableUsers = false; + users.${cfg.user} = { + password = if cfg.password != null then cfg.password else null; + hashedPasswordFile = if cfg.password != null then null else config.age.secrets.mypassword.path; + isNormalUser = true; + extraGroups = [ "wheel" ]; + openssh.authorizedKeys.keys = cfg.sshKeys; + }; }; }; } diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..07707bc --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Parameters +ip=$1 +if [ -z "$ip" ]; then + echo "Usage: $0 " + exit 1 +fi + +# This script is used to bootstrap nixos machine so I can get their ssh keys + +nix run \ + github:nix-community/nixos-anywhere \ + -- \ + --flake '.#bootstrap' \ + --target-host root@$ip \ + --build-on-remote + +ret=$? +if [ $ret -ne 0 ]; then + echo "Failed to bootstrap $ip" + exit $ret +fi \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..9c5ad04 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Parameters +ip=$1 +host=$2 +if [ -z "$ip" ] || [ -z "$host" ]; then + echo "Usage: $0 " + exit 1 +fi + +# This script is used to bootstrap nixos machine so I can get their ssh keys + +nixos-rebuild boot \ + --flake ".#$host" \ + --fast \ + --target-host krop@$ip \ + --build-host krop@$ip \ + --use-remote-sudo + +ret=$? +if [ $ret -ne 0 ]; then + echo "Failed to install $host" + exit $ret +fi + +echo "Successfully installed $host, rebooting" + +ssh -t krop@$ip "sudo reboot now" + +ret=$? +if [ $ret -ne 0 ]; then + echo "Failed to reboot $host" + exit $ret +fi \ No newline at end of file diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..c12b64b --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# Parameters +ip=$1 +if [ -z "$ip" ]; then + echo "Usage: $0 " + exit 1 +fi + +# This script is used to bootstrap nixos machine so I can get their ssh keys + +nixos-rebuild switch \ + --flake ".#$host" \ + --fast \ + --target-host krop@$ip \ + --build-host krop@$ip \ + --use-remote-sudo + +ret=$? +if [ $ret -ne 0 ]; then + echo "Failed to update $ip" + exit $ret +fi + +echo "Successfully updated $ip, rebooting" \ No newline at end of file diff --git a/secrets/mypassword.age b/secrets/mypassword.age index 6c6df8432058208d91abf72d181dbc44463fba16..2752c16996c0a2373d2e9df3cedf7e07523e7800 100644 GIT binary patch delta 451 zcmV;!0X+Ws1Nj4xEPqXFGIcmkYj{dFW=?ZWZ&Of1Y<72KQbjPIWj{HdIeCRc=XDbapFpX-P~^Ic#A?H*_*qV+t)k zAaiqQEoEdfH8n9gAWdjQM{IW>T3T*1V_8LPS!_d1I5S3WdPZ?8dQ(qALsfQ4Q$tNN zZ8vUlY)@}8GI4A$3N$ZPR#;3jLSk!8P(n;nZDvPPaCTKrR8vw_ZEHeyT1Zkzac*RA zNl`{~k?|LQIAv;CSVu!OIV);pVRKDGFH|sMb4h4&cs5vMN=$KUT3S?2MMZ8Z%O;c=nOhZ>}Nl;ZXT5)bjI7&$|D@Zg|a#}AjSZXy&``R{o7<~Y(mUa9EM7P~d)OPQ!i~YG-3dX<1E3K?*HC zAaiqQEoEdfH8n9gAWdjQM{IW>H&=0SIbkqOM@L~rRYyZua7JTKVNz06P*F!_Ff>|9 zRZ>G?HaSLjaWO_k3VL-lPGfXgM^GznNknN#c4JdjG-6?RPFZSnPd933G*eSTcXvfA zQaDpjcX2m)WjSI-Wprj`L}xi_a8FQjP(foiG;>sI3Y8LdwuwvdAdHb@ zY7|q8#^y)(jUvYw!14|*OReQeWX$9s$E~SUiuWmfMW~;VHc&* t28#oN)B-O`Tv{4=_b Date: Mon, 6 Jan 2025 21:51:14 +0100 Subject: [PATCH 2/9] updated lock --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index e4c83c5..6ae2311 100644 --- a/flake.lock +++ b/flake.lock @@ -30,11 +30,11 @@ ] }, "locked": { - "lastModified": 1735048446, - "narHash": "sha256-Tc35Y8H+krA6rZeOIczsaGAtobSSBPqR32AfNTeHDRc=", + "lastModified": 1736165297, + "narHash": "sha256-OT+sF4eNDFN/OdyUfIQwyp28+CFQL7PAdWn0wGU7F0U=", "owner": "nix-community", "repo": "disko", - "rev": "3a4de9fa3a78ba7b7170dda6bd8b4cdab87c0b21", + "rev": "76816af65d5294761636a838917e335992a52e0c", "type": "github" }, "original": { @@ -45,11 +45,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1735444284, - "narHash": "sha256-U0Vw+ZrjbfvmHqeyJKM7lXZWUXIYdaOa32VtNKkfKo8=", + "lastModified": 1736165148, + "narHash": "sha256-AdKOlljgcTLOrJb3HFpaaoHWJhFrkVeT9HbRm0JvcwE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cf802984d6b41ea45044455724d5835a4f5fcf81", + "rev": "9f46f57b78d2ef865cd8c58eff8d430bb62a471a", "type": "github" }, "original": { -- 2.45.2 From 22e5ffb4b42e491b724e99c769710a1ad13ac88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Mon, 6 Jan 2025 22:44:27 +0100 Subject: [PATCH 3/9] wip install script --- scripts/fresh_install.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 scripts/fresh_install.py diff --git a/scripts/fresh_install.py b/scripts/fresh_install.py new file mode 100755 index 0000000..a0de220 --- /dev/null +++ b/scripts/fresh_install.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +import argparse +import json +import ipaddress + +from subprocess import check_output + +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 _validate_ip(ip: str) -> bool: + try: + ipaddress.ip_address(ip) + return True + except ValueError: + return False + +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 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 not machine_name in _get_available_machines(): + raise ValueError(f'Machine {machine_name} not found, available machines are: {_get_available_machines()}') + + if _validate_ip(args.machine_ip): + raise ValueError(f'Invalid IP address {args.machine_ip}') + + machine_config = get_machine_config(machine_name) + print(machine_config) + # We are bootstraping the machine first because we need their ssh keys + bootstrap_machine() + + # while not check_ssh_connection(): + # time.sleep(5) + + # # connect and get ssh keys + + # ssh_key = get_ssh_key() + + # install_machine() + + return 0 + +if __name__ == '__main__': + raise SystemExit(main()) \ No newline at end of file -- 2.45.2 From be8f8fbbf9cc40b888fe9920136c52fc658ed7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Tue, 7 Jan 2025 16:16:58 +0100 Subject: [PATCH 4/9] WiP --- scripts/fresh_install.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/scripts/fresh_install.py b/scripts/fresh_install.py index a0de220..35a5afe 100755 --- a/scripts/fresh_install.py +++ b/scripts/fresh_install.py @@ -18,6 +18,35 @@ def _validate_ip(ip: str) -> bool: except ValueError: return False +def _check_ssh_connection(ip: str) -> bool: + try: + check_output(['ssh', f'root@{ip}', 'echo', 'Connected']) + return True + except Exception: + return False + +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 get_ssh_key(ip: str) -> str: + """ + This function uses machines ssh-keyscan to get the ssh key and then get the ed25519 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) @@ -41,12 +70,15 @@ def main() -> int: # We are bootstraping the machine first because we need their ssh keys bootstrap_machine() - # while not check_ssh_connection(): - # time.sleep(5) + while not _check_ssh_connection(): + time.sleep(5) # # connect and get ssh keys - # ssh_key = get_ssh_key() + ssh_key = get_ssh_key() + + # Add the ssh key to keys in secrets/secrets.nix + # and rekey the secrets # install_machine() -- 2.45.2 From 6b54f0bc42980b0a502b01ba8d87366e19c263b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Tue, 7 Jan 2025 16:18:04 +0100 Subject: [PATCH 5/9] reformatted --- scripts/fresh_install.py | 61 +++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/scripts/fresh_install.py b/scripts/fresh_install.py index 35a5afe..bccf412 100755 --- a/scripts/fresh_install.py +++ b/scripts/fresh_install.py @@ -1,16 +1,19 @@ #!/usr/bin/env python3 import argparse import json -import ipaddress +import ipaddress +import time from subprocess import check_output + def _get_available_machines() -> list: - output = check_output(['nix', 'flake', 'show', '--json']) + output = check_output(["nix", "flake", "show", "--json"]) parsed_output = json.loads(output) - machines = parsed_output.get('nixosConfigurations', dict()).keys() + machines = parsed_output.get("nixosConfigurations", dict()).keys() return list(machines) + def _validate_ip(ip: str) -> bool: try: ipaddress.ip_address(ip) @@ -18,25 +21,27 @@ def _validate_ip(ip: str) -> bool: except ValueError: return False + def _check_ssh_connection(ip: str) -> bool: try: - check_output(['ssh', f'root@{ip}', 'echo', 'Connected']) + check_output(["ssh", f"root@{ip}", "echo", "Connected"]) return True except Exception: return False + def bootstrap_machine(ip: str): check_output( [ - 'nix', - 'run', - 'github:nix-community/nixos-anywhere', - '--', - '--flake', + "nix", + "run", + "github:nix-community/nixos-anywhere", + "--", + "--flake", '".#bootstrap"', - '--target-host', - f'root@{ip}', - '--build-on-remote' + "--target-host", + f"root@{ip}", + "--build-on-remote", ] ) @@ -48,22 +53,33 @@ def get_ssh_key(ip: str) -> str: def get_machine_config(machine_name: str) -> dict: - output = check_output(['nix', 'eval', '--json', f'.#nixosConfigurations.{machine_name}.config.kropcloud']) + output = check_output( + [ + "nix", + "eval", + "--json", + f".#nixosConfigurations.{machine_name}.config.kropcloud", + ] + ) return json.loads(output) 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') + 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 not machine_name in _get_available_machines(): - raise ValueError(f'Machine {machine_name} not found, available machines are: {_get_available_machines()}') + if machine_name not in _get_available_machines(): + raise ValueError( + f"Machine {machine_name} not found, available machines are: {_get_available_machines()}" + ) if _validate_ip(args.machine_ip): - raise ValueError(f'Invalid IP address {args.machine_ip}') + raise ValueError(f"Invalid IP address {args.machine_ip}") machine_config = get_machine_config(machine_name) print(machine_config) @@ -75,7 +91,7 @@ def main() -> int: # # connect and get ssh keys - ssh_key = get_ssh_key() + # ssh_key = get_ssh_key() # Add the ssh key to keys in secrets/secrets.nix # and rekey the secrets @@ -84,5 +100,6 @@ def main() -> int: return 0 -if __name__ == '__main__': - raise SystemExit(main()) \ No newline at end of file + +if __name__ == "__main__": + raise SystemExit(main()) -- 2.45.2 From 8afcfba1d9444df221b01cf3ca2365878e91fd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Tue, 7 Jan 2025 20:05:09 +0100 Subject: [PATCH 6/9] functional HELL --- hosts/bootstrap/default.nix | 5 ++++- nixosModules/users/default.nix | 7 ++++++ scripts/fresh_install.py | 39 ++++++++++++++++++++++++++------- secrets/keys.json | 13 +++++++++++ secrets/mypassword.age | Bin 505 -> 505 bytes secrets/secrets.nix | 19 ++++++---------- 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 secrets/keys.json diff --git a/hosts/bootstrap/default.nix b/hosts/bootstrap/default.nix index c36a525..482a431 100644 --- a/hosts/bootstrap/default.nix +++ b/hosts/bootstrap/default.nix @@ -2,6 +2,9 @@ { kropcloud = { networking.enable = false; - admin.password = "changeme"; + admin = { + password = "changeme"; + sudoRequirePassword = false; + }; }; } diff --git a/nixosModules/users/default.nix b/nixosModules/users/default.nix index d19618e..af557f8 100644 --- a/nixosModules/users/default.nix +++ b/nixosModules/users/default.nix @@ -19,12 +19,19 @@ in default = null; description = "Password for the admin user. Should be used only for initial setup."; }; + sudoRequirePassword = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Require password for sudo. Should be used only for initial setup."; + }; }; config = { age.secrets.mypassword.file = ../../secrets/mypassword.age; + security.sudo.wheelNeedsPassword = cfg.sudoRequirePassword; + # Define the admin user users = { mutableUsers = false; diff --git a/scripts/fresh_install.py b/scripts/fresh_install.py index bccf412..a706023 100755 --- a/scripts/fresh_install.py +++ b/scripts/fresh_install.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import argparse import json -import ipaddress +import socket import time from subprocess import check_output @@ -16,10 +16,10 @@ def _get_available_machines() -> list: def _validate_ip(ip: str) -> bool: try: - ipaddress.ip_address(ip) - return True - except ValueError: + socket.inet_aton(ip) return False + except socket.error: + return True def _check_ssh_connection(ip: str) -> bool: @@ -29,6 +29,8 @@ def _check_ssh_connection(ip: str) -> bool: except Exception: return False +def add_key_to_secrets(key: str): + def bootstrap_machine(ip: str): check_output( @@ -50,7 +52,22 @@ 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( @@ -82,18 +99,24 @@ def main() -> int: raise ValueError(f"Invalid IP address {args.machine_ip}") machine_config = get_machine_config(machine_name) - print(machine_config) # We are bootstraping the machine first because we need their ssh keys + print(f"Bootstrapping machine {args.machine_ip}") bootstrap_machine() + print("Machine bootstrapped") + print("Waiting for ssh connection") while not _check_ssh_connection(): time.sleep(5) + print("Machine is up and running") - # # connect and get ssh keys + print("Getting ssh key") + ssh_key = get_ssh_key(args.machine_ip) - # ssh_key = get_ssh_key() + print("Adding ssh key to secrets") + add_key_to_secrets(ssh_key) + rekey_secrets() - # Add the ssh key to keys in secrets/secrets.nix + # Add the ssh key to keys in secrets/keys.json # and rekey the secrets # install_machine() diff --git a/secrets/keys.json b/secrets/keys.json new file mode 100644 index 0000000..9c54dae --- /dev/null +++ b/secrets/keys.json @@ -0,0 +1,13 @@ + +{ + "hosts": { + "wenar-nix": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJl0Rdo2kHliBeIiPuiO4kYO5M0VZFNXw4siepV1p6Pj", + "lenar": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOUnlAjPnMwJYgZb7YuholdTxifOEFnAyXVqI+xFlHw6" + }, + "servers" : { + "test-server": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID4ioqiTzYe6Y6H0YfFkWyDBbCB25wYs3gKNZIufE/Sn" + }, + "secrets": { + "mypassword.age": ["hosts:wenar-nix", "hosts:lenar", "servers:test-server"] + } +} diff --git a/secrets/mypassword.age b/secrets/mypassword.age index 2752c16996c0a2373d2e9df3cedf7e07523e7800..f372b0a799e6f3c51b1c627e60033bda54bee2e8 100644 GIT binary patch delta 451 zcmV;!0X+Ws1Nj4xEPqfhdQEq7R&g>pZ7*XsR&Q%ZGfFr`XH7CNX-Q#bbVou$V{Ug& zGD&S$YYJLPSVLM_YI0{#V_7ybc}8V3XJt`oMQ%ezYj!zzb2Lj~L`-l{Nmy`iGzu*~ zAaiqQEoEdfH8n9gAWdjQM{IW>c2{miNn~PSX*M%yF=0?gWkxntM>0q-aBVY7LU}<{ zVNObDbvHRdVs}VI3NU7GNq9#}F*a{-c2_e*RBK3LY)43JXlz(&Q*~KIV_8X8RylZh zL19c$k?|LQO+i_AG;TpPaCTu(OKo;RRAhHacymTJR!cBSH&bJHMR8a-dTl{jcSKPN zbTV2Hp zL2P(!Q8;64Wp6PxZ!~u}Ms#>>P+3fFVMswwF;-P_H8ylba9S}q3W1vcA#T30P1ZyM zs}QEPxrv5!Ac8e#o`eE>5BC!E;4`+MDd%>-Orc~UZD%u#!{&M1*Z&X;pd0F@{ocj$ t^JII_R*769_bBgj@NUeX@p?j=n#kj&o8{{1|EBE~e?4DEE6#NV!J!l!tQG(O delta 451 zcmV;!0X+Ws1Nj4xEPqXFGIcmkYj{dFW=?ZWZ&Of1Y<72KQbjPIWj{HdIeCRc=XDbapFpX-P~^Ic#A?H*_*qV+t)k zAaiqQEoEdfH8n9gAWdjQM{IW>T3T*1V_8LPS!_d1I5S3WdPZ?8dQ(qALsfQ4Q$tNN zZ8vUlY)@}8GI4A$3N$ZPR#;3jLSk!8P(n;nZDvPPaCTKrR8vw_ZEHeyT1Zkzac*RA zNl`{~k?|LQIAv;CSVu!OIV);pVRKDGFH|sMb4h4&cs5vMN=$KUT3S?2MMZ8Z%O;c=nOhZ>}Nl;ZXT5)bjI7&$|D@Zg|a#}AjSZXy&``R{o7<~Y(mUa9EM7P~d)OP Date: Thu, 9 Jan 2025 17:34:18 +0100 Subject: [PATCH 7/9] formatting --- scripts/fresh_install.py | 115 +++++++++++++++++++++++++++++---------- secrets/keys.json | 27 +++++---- secrets/mypassword.age | Bin 505 -> 505 bytes 3 files changed, 100 insertions(+), 42 deletions(-) diff --git a/scripts/fresh_install.py b/scripts/fresh_install.py index a706023..d49b419 100755 --- a/scripts/fresh_install.py +++ b/scripts/fresh_install.py @@ -1,11 +1,16 @@ #!/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"]) @@ -14,23 +19,39 @@ def _get_available_machines() -> list: return list(machines) -def _validate_ip(ip: str) -> bool: +def _is_valid_ip(ip: str) -> bool: try: socket.inet_aton(ip) - return False - except socket.error: return True + except socket.error: + return False def _check_ssh_connection(ip: str) -> bool: try: - check_output(["ssh", f"root@{ip}", "echo", "Connected"]) + check_output(["ssh", f"krop@{ip}", "echo", "Connected"]) return True except Exception: return False -def add_key_to_secrets(key: str): - + +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( @@ -40,7 +61,7 @@ def bootstrap_machine(ip: str): "github:nix-community/nixos-anywhere", "--", "--flake", - '".#bootstrap"', + ".#bootstrap", "--target-host", f"root@{ip}", "--build-on-remote", @@ -48,19 +69,41 @@ def bootstrap_machine(ip: str): ) +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() + 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") @@ -69,6 +112,7 @@ def get_ssh_key(ip: str) -> str: return key + def get_machine_config(machine_name: str) -> dict: output = check_output( [ @@ -81,6 +125,17 @@ def get_machine_config(machine_name: str) -> dict: 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( @@ -95,32 +150,32 @@ def main() -> int: f"Machine {machine_name} not found, available machines are: {_get_available_machines()}" ) - if _validate_ip(args.machine_ip): - raise ValueError(f"Invalid IP address {args.machine_ip}") + machine_ip = args.machine_ip + if not _is_valid_ip(machine_ip): + raise ValueError(f"Invalid IP address {machine_ip}") - machine_config = get_machine_config(machine_name) - # We are bootstraping the machine first because we need their ssh keys - print(f"Bootstrapping machine {args.machine_ip}") - bootstrap_machine() + print(f"Bootstrapping machine {machine_ip}") + bootstrap_machine(machine_ip) print("Machine bootstrapped") print("Waiting for ssh connection") - while not _check_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(args.machine_ip) + ssh_key = get_ssh_key(machine_ip) + print(f"SSH key: {ssh_key}") print("Adding ssh key to secrets") - add_key_to_secrets(ssh_key) + add_key_to_secrets(machine_name, ssh_key) rekey_secrets() - # Add the ssh key to keys in secrets/keys.json - # and rekey the secrets - - # install_machine() - + print("Installing machine") + install_machine(machine_name, machine_ip) + print("Machine installed, rebooting") + reboot_machine(machine_ip) + print("") return 0 diff --git a/secrets/keys.json b/secrets/keys.json index 9c54dae..53e6e45 100644 --- a/secrets/keys.json +++ b/secrets/keys.json @@ -1,13 +1,16 @@ - { - "hosts": { - "wenar-nix": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJl0Rdo2kHliBeIiPuiO4kYO5M0VZFNXw4siepV1p6Pj", - "lenar": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOUnlAjPnMwJYgZb7YuholdTxifOEFnAyXVqI+xFlHw6" - }, - "servers" : { - "test-server": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID4ioqiTzYe6Y6H0YfFkWyDBbCB25wYs3gKNZIufE/Sn" - }, - "secrets": { - "mypassword.age": ["hosts:wenar-nix", "hosts:lenar", "servers:test-server"] - } -} + "hosts": { + "wenar-nix": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJl0Rdo2kHliBeIiPuiO4kYO5M0VZFNXw4siepV1p6Pj", + "lenar": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOUnlAjPnMwJYgZb7YuholdTxifOEFnAyXVqI+xFlHw6" + }, + "servers": { + "test-server": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID4ioqiTzYe6Y6H0YfFkWyDBbCB25wYs3gKNZIufE/Sn" + }, + "secrets": { + "mypassword.age": [ + "hosts:wenar-nix", + "hosts:lenar", + "servers:test-server" + ] + } +} \ No newline at end of file diff --git a/secrets/mypassword.age b/secrets/mypassword.age index f372b0a799e6f3c51b1c627e60033bda54bee2e8..6ae2b0515b274ffac94cbe122168b274498e67fe 100644 GIT binary patch delta 451 zcmV~$OK8(z007|W*d+&#t|CH(U8EM;enxs9sws|*gnm?gQVeTLv zRFJACPhJ#v61)vhGEh&7%#$$iCVJE3WCy?RZ1rq)dGo+`BGQcgTo`dGtVL?Cqi;b% z@O+X|>b7E!ZrPBvd>mIpEc6ky+*ksx+!1`5m~nX}B?ES`2h$V{T`SK+g^}`znPS!L zGD2R}9swM$Q)S8Hgz>!0>)D(IwE;ad7@(O2v)e~%5p1$lt%zD&!O9Ae0X{V(eU2%0 zLvjpL9LPyA)uw@_cb!2uYX@YL*8n5-Tlmm-XfUgHpZr}vnBXoMvFt>tPzq(a0z0N9 zjs%&_!%7SGVPx}(-lx^6OJkH+XkE{2$4wi_qp-s_3ul%NO;fBSj@TouRBt7SaLggX zs?};a;l1HvF2ZgNwB^BkGo@>NM_^(U=7!jrR+34a1eqUfc^Z#>MLWyG2C;v7yYc*^ z^&%S|ZM?tyVeil3yZ8rtci-Ik9p>M!^7*s#hik{DZ?BE+yG7m@Y5Vn!()C|U?pZ7*XsR&Q%ZGfFr`XH7CNX-Q#bbVou$V{Ug& zGD&S$YYJLPSVLM_YI0{#V_7ybc}8V3XJt`oMQ%ezYj!zzb2Lj~L`-l{Nmy`iGzu*~ zAaiqQEoEdfH8n9gAWdjQM{IW>c2{miNn~PSX*M%yF=0?gWkxntM>0q-aBVY7LU}<{ zVNObDbvHRdVs}VI3NU7GNq9#}F*a{-c2_e*RBK3LY)43JXlz(&Q*~KIV_8X8RylZh zL19c$k?|LQO+i_AG;TpPaCTu(OKo;RRAhHacymTJR!cBSH&bJHMR8a-dTl{jcSKPN zbTV2Hp zL2P(!Q8;64Wp6PxZ!~u}Ms#>>P+3fFVMswwF;-P_H8ylba9S}q3W1vcA#T30P1ZyM zs}QEPxrv5!Ac8e#o`eE>5BC!E;4`+MDd%>-Orc~UZD%u#!{&M1*Z&X;pd0F@{ocj$ t^JII_R*769_bBgj@NUeX@p?j=n#kj&o8{{1|EBE~e?4DEE6#NV!J!l!tQG(O -- 2.45.2 From cfe20fe39bd2006a9bd13ee44ce927ed9ce91287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Fri, 10 Jan 2025 23:11:21 +0100 Subject: [PATCH 8/9] added some nodes --- .envrc | 1 + flake.nix | 24 +++++++++-- hosts/node0/default.nix | 22 ++++++++++ hosts/node1/default.nix | 22 ++++++++++ hosts/node2/default.nix | 22 ++++++++++ lib.nix | 13 +++++- nixosModules/networking/default.nix | 4 ++ nixosModules/services/default.nix | 1 + nixosModules/services/k3s/default.nix | 58 +++++++++++++++++++++++++++ 9 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 .envrc create mode 100644 hosts/node0/default.nix create mode 100644 hosts/node1/default.nix create mode 100644 hosts/node2/default.nix create mode 100644 nixosModules/services/k3s/default.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/flake.nix b/flake.nix index e048442..d865005 100644 --- a/flake.nix +++ b/flake.nix @@ -31,13 +31,31 @@ bootstrap = kclib.mkHost { name = "bootstrap"; }; - etcd0 = kclib.mkHost { - name = "etcd0"; - }; hydra = kclib.mkHost { name = "hydra"; }; + node0 = kclib.mkHost { + name = "node0"; + }; + node1 = kclib.mkHost { + name = "node1"; + }; + node2 = kclib.mkHost { + name = "node2"; + }; }; formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-rfc-style; + devShells.x86_64-linux.default = + let + pkgs = import nixpkgs { + system = "x86_64-linux"; + allowUnfree = true; + }; + in + pkgs.mkShellNoCC { + packages = with pkgs; [ + cfssl + ]; + }; }; } diff --git a/hosts/node0/default.nix b/hosts/node0/default.nix new file mode 100644 index 0000000..aa4c27c --- /dev/null +++ b/hosts/node0/default.nix @@ -0,0 +1,22 @@ +{ ... }: +{ + kropcloud = + let + serverIp = "192.168.1.170"; + in + { + services = { + k3s = { + enable = true; + isMaster = true; + }; + }; + networking = { + ipv4 = { + address = serverIp; + prefixLength = 24; + defaultGateway = "192.168.1.1"; + }; + }; + }; +} diff --git a/hosts/node1/default.nix b/hosts/node1/default.nix new file mode 100644 index 0000000..6d37b63 --- /dev/null +++ b/hosts/node1/default.nix @@ -0,0 +1,22 @@ +{ ... }: +{ + kropcloud = + let + serverIp = "192.168.1.171"; + in + { + services = { + k3s = { + enable = true; + master = "node0"; + }; + }; + networking = { + ipv4 = { + address = serverIp; + prefixLength = 24; + defaultGateway = "192.168.1.1"; + }; + }; + }; +} diff --git a/hosts/node2/default.nix b/hosts/node2/default.nix new file mode 100644 index 0000000..f4119f1 --- /dev/null +++ b/hosts/node2/default.nix @@ -0,0 +1,22 @@ +{ ... }: +{ + kropcloud = + let + serverIp = "192.168.1.172"; + in + { + services = { + k3s = { + enable = true; + master = "node0"; + }; + }; + networking = { + ipv4 = { + address = serverIp; + prefixLength = 24; + defaultGateway = "192.168.1.1"; + }; + }; + }; +} diff --git a/lib.nix b/lib.nix index 8c55d24..3a31c0a 100644 --- a/lib.nix +++ b/lib.nix @@ -6,12 +6,13 @@ { name, arch ? "x86_64-linux", + config_name ? name, }: inputs.nixpkgs.lib.nixosSystem { system = arch; modules = [ ./hosts/base - ./hosts/${name} + ./hosts/${config_name} ./nixosModules ( { ... }: @@ -28,4 +29,14 @@ inherit inputs; }; }; + # TODO: this will actually be nice, so I can see IPs in main flake.nix, + # but also dont have three directories with only default.nix in it + # mkK3Snode = { + # name_prefix, + # id, + # ip + # }: mkHost { + # name = "${name_prefix}-${id}"; + # config_name = "k3snode"; + # }; } diff --git a/nixosModules/networking/default.nix b/nixosModules/networking/default.nix index aa1878c..d52c704 100644 --- a/nixosModules/networking/default.nix +++ b/nixosModules/networking/default.nix @@ -49,6 +49,10 @@ in } ]; + services.avahi = { + enable = true; + }; + networking = { nftables.enable = true; firewall = { diff --git a/nixosModules/services/default.nix b/nixosModules/services/default.nix index edad497..5a9d92d 100644 --- a/nixosModules/services/default.nix +++ b/nixosModules/services/default.nix @@ -4,5 +4,6 @@ ./ssh ./tailscale ./hydra + ./k3s ]; } diff --git a/nixosModules/services/k3s/default.nix b/nixosModules/services/k3s/default.nix new file mode 100644 index 0000000..ff242bf --- /dev/null +++ b/nixosModules/services/k3s/default.nix @@ -0,0 +1,58 @@ +{ + config, + lib, + ... +}: +let + cfg = config.kropcloud.services.k3s; +in +{ + options.kropcloud.services.k3s = { + enable = lib.mkEnableOption "Whence to enable k3s service."; + isMaster = lib.mkEnableOption "Whence to configure k3s as master."; + master = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + example = "node0"; + description = "The master node to connect to"; + }; + }; + config = lib.mkIf cfg.enable { + + assertions = [ + { + assertion = (!cfg.isMaster && cfg.master == null); + message = '' + You need to provide a valid value for `master` in `kropcloud.services.k3s` + when `isMaster` is not set. + ''; + } + ]; + + age.secrets.k3stoken.file = ../../secrets/k3stoken.age; + + services.k3s = { + enable = true; + role = "server"; + tokenFile = config.age.secrets.k3stoken.path; + extraFlags = toString ( + [ + "--write-kubeconfig-mode \"0644\"" + "--cluster-init" + "--disable servicelb" + "--disable traefik" + "--disable local-storage" + ] + ++ ( + if cfg.isMaster && cfg.master != null then + [ ] + else + [ + "--server https://${cfg.master}:6443" + ] + ) + ); + clusterInit = cfg.isMaster; + }; + }; +} -- 2.45.2 From f4cd66b8d97f039eb6393e4e0be0023c2c919175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Krop=C3=A1=C4=8Dek?= Date: Fri, 10 Jan 2025 23:23:20 +0100 Subject: [PATCH 9/9] preparing k3s bootstrapping --- nixosModules/services/k3s/default.nix | 2 +- secrets/k3stoken.age | 7 +++++++ secrets/keys.json | 6 +++++- secrets/secrets.nix | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 secrets/k3stoken.age diff --git a/nixosModules/services/k3s/default.nix b/nixosModules/services/k3s/default.nix index ff242bf..258f16b 100644 --- a/nixosModules/services/k3s/default.nix +++ b/nixosModules/services/k3s/default.nix @@ -29,7 +29,7 @@ in } ]; - age.secrets.k3stoken.file = ../../secrets/k3stoken.age; + age.secrets.k3stoken.file = ../../../secrets/k3stoken.age; services.k3s = { enable = true; diff --git a/secrets/k3stoken.age b/secrets/k3stoken.age new file mode 100644 index 0000000..c7da7fc --- /dev/null +++ b/secrets/k3stoken.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 5k28aQ wUKJk8gcxcCqbdXsfuod3dvEtj+pXRe8rLYVv/uyND4 +aHOXSUwP5+AJZ5etU+dj9ssVNQNcDuXSpq+wvIYsoyE +-> ssh-ed25519 MhDGlw Ln5f8TTQFDlp+KGQpRRPNgn/+fzoY7Bnl7FlDg5ZSSs +uJbxZFjjcSxhIPHvregG1tD8BKKfHHMlvfZ6itDIppY +--- MGApTU7O6xSlpanV9LC22ZX2u7bwULpBMaTLg01SO/0 +Y J#ž6/ 6 wTF fԶ xם5^ \ No newline at end of file diff --git a/secrets/keys.json b/secrets/keys.json index 53e6e45..2f613a1 100644 --- a/secrets/keys.json +++ b/secrets/keys.json @@ -11,6 +11,10 @@ "hosts:wenar-nix", "hosts:lenar", "servers:test-server" + ], + "k3stoken.age": [ + "hosts:wenar-nix", + "hosts:lenar" ] } -} \ No newline at end of file +} diff --git a/secrets/secrets.nix b/secrets/secrets.nix index ba08120..fdb4274 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -9,4 +9,5 @@ let in { "mypassword.age".publicKeys = getKeys "mypassword.age"; + "k3stoken.age".publicKeys = getKeys "k3stoken.age"; } -- 2.45.2