mirror of
https://github.com/Cian-H/My_NixOS_Config.git
synced 2026-05-08 22:21:42 +01:00
217 lines
7.1 KiB
Python
Executable File
217 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import socket
|
|
import getpass
|
|
import shutil
|
|
|
|
REPO_URL = "https://github.com/Cian-H/My_NixOS_Config"
|
|
TARGET_DIR = os.path.expanduser("~/.config/nix")
|
|
|
|
|
|
def command_exists(cmd: str) -> bool:
|
|
"""Check if a command is available on the system."""
|
|
return shutil.which(cmd) is not None
|
|
|
|
|
|
def check_preconditions(target_dir: str):
|
|
"""Ensure the system is in a valid state to begin bootstrapping."""
|
|
if not command_exists("git"):
|
|
print(
|
|
"Error: 'git' is not installed. Please install git before bootstrapping.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
if os.path.exists(target_dir):
|
|
print(
|
|
f"Error: {target_dir} already exists. Aborting bootstrap.", file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
def clone_repository(repo_url: str, target_dir: str):
|
|
"""Clone the configuration repository and enter the directory."""
|
|
print(f">> Cloning {repo_url} into {target_dir}...")
|
|
try:
|
|
subprocess.run(["git", "clone", "--recursive", repo_url, target_dir], check=True)
|
|
except subprocess.CalledProcessError:
|
|
print("Error: Failed to clone repository.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
os.chdir(target_dir)
|
|
|
|
|
|
def detect_nixos() -> bool:
|
|
"""Check if the current host system is NixOS."""
|
|
if os.path.exists("/etc/os-release"):
|
|
with open("/etc/os-release", "r") as f:
|
|
if "ID=nixos" in f.read():
|
|
return True
|
|
return False
|
|
|
|
|
|
def has_config(sys_name: str, is_nixos: bool) -> bool:
|
|
"""Determine if system configuration directories exist for the given hostname."""
|
|
has_nixos_dir = os.path.isdir(f"nixos/{sys_name}")
|
|
has_hm_dir = os.path.isdir(f"home-manager/{sys_name}")
|
|
return (has_nixos_dir and has_hm_dir) if is_nixos else has_hm_dir
|
|
|
|
|
|
def get_available_systems() -> list:
|
|
"""Discover existing system configurations in the repository."""
|
|
opts = set()
|
|
if os.path.exists("nixos"):
|
|
opts.update([d for d in os.listdir("nixos") if os.path.isdir(f"nixos/{d}")])
|
|
if os.path.exists("home-manager"):
|
|
opts.update(
|
|
[
|
|
d
|
|
for d in os.listdir("home-manager")
|
|
if os.path.isdir(f"home-manager/{d}")
|
|
]
|
|
)
|
|
|
|
# Filter out base/core directories and hidden folders, then sort for consistent output
|
|
return sorted([o for o in opts if o not in ["core"] and not o.startswith(".")])
|
|
|
|
|
|
def setup_new_config(target_sys: str, target_dir: str, is_nixos: bool):
|
|
"""Create boilerplate directories for a missing system and open an editor."""
|
|
print(">> Creating directories...")
|
|
if is_nixos:
|
|
os.makedirs(f"nixos/{target_sys}", exist_ok=True)
|
|
os.makedirs(f"home-manager/{target_sys}", exist_ok=True)
|
|
|
|
if command_exists("yazi"):
|
|
print(">> Opening yazi. Please add your configs and exit yazi when finished.")
|
|
subprocess.run(["yazi", target_dir])
|
|
else:
|
|
print(
|
|
f">> Yazi not found. Please manually configure your files in {target_dir} in another terminal."
|
|
)
|
|
input("Press Enter when you have finished adding your configurations...")
|
|
|
|
|
|
def prompt_for_existing_config(current_sys: str, current_user: str) -> tuple[str, str]:
|
|
"""Prompt the user to select an existing configuration from the repo."""
|
|
opts = get_available_systems()
|
|
if not opts:
|
|
print(
|
|
"Error: No existing configurations found. Cannot proceed.", file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
print("\nAvailable systems:")
|
|
for o in opts:
|
|
print(f" - {o}")
|
|
|
|
chosen_sys = input(f"\nEnter system to use [default: {current_sys}]: ").strip()
|
|
if chosen_sys:
|
|
if chosen_sys not in opts:
|
|
print(f"Error: '{chosen_sys}' is not a valid option.")
|
|
sys.exit(1)
|
|
target_sys = chosen_sys
|
|
else:
|
|
target_sys = current_sys
|
|
# Failsafe: if they press enter, but the default hostname isn't actually a valid config
|
|
if target_sys not in opts:
|
|
print(
|
|
f"Error: Default '{target_sys}' is not in available systems. You must type a valid option."
|
|
)
|
|
sys.exit(1)
|
|
|
|
chosen_user = input(
|
|
f"Enter user configuration to use [default: {current_user}]: "
|
|
).strip()
|
|
target_user = chosen_user if chosen_user else current_user
|
|
|
|
return target_sys, target_user
|
|
|
|
|
|
def resolve_configuration(
|
|
target_sys: str, target_user: str, target_dir: str, is_nixos: bool
|
|
) -> tuple[str, str]:
|
|
"""Ensure a valid configuration target exists, prompting the user for resolution if necessary."""
|
|
if has_config(target_sys, is_nixos):
|
|
return target_sys, target_user
|
|
|
|
print(
|
|
f"\n>> Configuration for system '{target_sys}' (user: '{target_user}') not found."
|
|
)
|
|
ans = (
|
|
input("Do you want to create the missing directories now? [y/N]: ")
|
|
.strip()
|
|
.lower()
|
|
)
|
|
|
|
if ans == "y":
|
|
setup_new_config(target_sys, target_dir, is_nixos)
|
|
return target_sys, target_user
|
|
|
|
ans = (
|
|
input("Do you want to switch to one of the existing configs instead? [y/N]: ")
|
|
.strip()
|
|
.lower()
|
|
)
|
|
if ans == "y":
|
|
return prompt_for_existing_config(target_sys, target_user)
|
|
|
|
print(
|
|
"Error: Cannot proceed without a valid configuration. Aborting.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
def execute_installation(target_sys: str, target_user: str, is_nixos: bool):
|
|
"""Execute the final NixOS or Home Manager commands to build the system."""
|
|
print(
|
|
f"\n>> Proceeding with installation for user '{target_user}' on system '{target_sys}'..."
|
|
)
|
|
try:
|
|
if is_nixos:
|
|
print(
|
|
">> [NixOS detected] Performing full system and home-manager install..."
|
|
)
|
|
subprocess.run(
|
|
["sudo", "nixos-rebuild", "switch", "--flake", f".#{target_sys}"],
|
|
check=True,
|
|
)
|
|
subprocess.run(
|
|
["home-manager", "switch", "--flake", f".#{target_user}@{target_sys}"],
|
|
check=True,
|
|
)
|
|
else:
|
|
print(">> [Non-NixOS detected] Performing home-manager install only...")
|
|
subprocess.run(
|
|
["home-manager", "switch", "--flake", f".#{target_user}@{target_sys}"],
|
|
check=True,
|
|
)
|
|
print("\n>> Bootstrap complete! Welcome to your new environment.")
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"\nError: Command '{' '.join(e.cmd)}' failed with exit status {e.returncode}.", file=sys.stderr)
|
|
sys.exit(e.returncode)
|
|
|
|
|
|
def main():
|
|
"""Main orchestration loop."""
|
|
check_preconditions(TARGET_DIR)
|
|
clone_repository(REPO_URL, TARGET_DIR)
|
|
|
|
is_nixos = detect_nixos()
|
|
hostname = socket.gethostname()
|
|
username = getpass.getuser()
|
|
|
|
# The returned tuple contains the final resolved system/user strings
|
|
final_sys, final_user = resolve_configuration(
|
|
hostname, username, TARGET_DIR, is_nixos
|
|
)
|
|
|
|
execute_installation(final_sys, final_user, is_nixos)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|