2026-02-05 18:05:06 -05:00

530 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
"""Gentoo installation orchestrator for Legion S7 15ACH6.
Usage:
python setup.py [command]
python setup.py --install # Full install up to first reboot
python setup.py --desktop # Post-reboot desktop setup
Major Phases:
--install Full installation to first reboot (auto-detects chroot state)
Outside chroot: disk -> stage3 -> config -> fstab -> chroot
Inside chroot: sync -> world -> firmware -> kernel -> services
-> users -> nvidia -> bootloader
--desktop Post-reboot desktop setup (Hyprland + optional fingerprint)
Individual Commands:
disk Partition, encrypt, and mount disks
stage3 Download and extract stage3 tarball
chroot Prepare and enter chroot environment
config Copy portage and system configuration
fstab Generate fstab, crypttab, and dmcrypt config
sync Initial portage sync, profile, and overlays (inside chroot)
world Update @world (inside chroot)
firmware Install linux-firmware (inside chroot)
kernel Install kernel (inside chroot)
services Install and configure system services (inside chroot)
users Configure shell, sudo, and create user (inside chroot)
nvidia Install NVIDIA drivers and configure (inside chroot)
bootloader Install GRUB and verify initramfs (inside chroot)
fingerprint Set up fingerprint authentication (post-install)
swap-on Activate encrypted swap (inside chroot)
swap-off Deactivate encrypted swap (inside chroot)
all Run pre-chroot steps (disk -> chroot)
Examples:
python setup.py --install # Run full install phase (pre-reboot)
python setup.py --desktop # Run desktop setup (post-reboot)
python setup.py disk # Just partition disks
python setup.py # Interactive menu
"""
import argparse
import sys
from pathlib import Path
from install.utils import info, success, error, fatal, check_root, check_uefi, prompt, emerge
from install.disk import DiskConfig, DiskLayout, prepare_disk
from install.stage3 import Stage3Config, fetch_stage3
from install.chroot import prepare_chroot, enter_chroot
from install.fstab import generate_all as generate_fstab_all
from install.portage import copy_portage_config
from install.sync import initial_sync, activate_swap, deactivate_swap
from install.services import setup_services
from install.users import setup_users
from install.nvidia import setup_nvidia
from install.bootloader import setup_bootloader
# Path to config files (same directory as this script)
SCRIPT_DIR = Path(__file__).parent.resolve()
CONFIG_DIR = SCRIPT_DIR # Config files live alongside setup.py
# Global state (set during disk preparation)
_disk_layout: DiskLayout | None = None
_disk_config: DiskConfig | None = None
def cmd_disk() -> tuple[DiskLayout, DiskConfig]:
"""Partition, encrypt, and mount disks."""
global _disk_layout, _disk_config
check_uefi()
layout, config = prepare_disk()
_disk_layout = layout
_disk_config = config
return layout, config
def cmd_stage3() -> None:
"""Download and extract stage3."""
config = Stage3Config(
init_system="openrc",
mount_root=Path("/mnt/gentoo"),
)
fetch_stage3(config)
def cmd_config() -> None:
"""Copy portage configuration."""
copy_portage_config(
source_dir=CONFIG_DIR,
mount_root=Path("/mnt/gentoo"),
)
def cmd_fstab(layout: DiskLayout | None = None, config: DiskConfig | None = None) -> None:
"""Generate filesystem configuration files."""
if layout is None or config is None:
fatal("Disk layout not available. Run 'disk' command first.")
generate_fstab_all(layout, config)
def cmd_chroot() -> None:
"""Prepare and enter chroot."""
prepare_chroot()
print()
response = prompt("Enter chroot now? (y/n): ")
if response.lower() == "y":
enter_chroot()
def cmd_sync() -> None:
"""Initial portage sync and profile setup (run inside chroot)."""
initial_sync()
def cmd_swap_on() -> None:
"""Activate encrypted swap (inside chroot)."""
activate_swap()
def cmd_swap_off() -> None:
"""Deactivate encrypted swap (inside chroot)."""
deactivate_swap()
def cmd_world() -> None:
"""Update @world (inside chroot)."""
info("=== Updating @world ===")
emerge("@world", extra_args=["--update", "--deep", "--newuse"])
success("@world update complete.")
# Handle circular dependencies - rebuild with full USE flags
circ_deps_file = Path("/etc/portage/package.use/circular-dependencies")
if circ_deps_file.exists():
packages = parse_circular_deps(circ_deps_file)
if packages:
print()
info("=== Rebuilding Circular Dependency Packages ===")
info(f"Packages: {', '.join(packages)}")
# Clear the file (keep header)
clear_circular_deps(circ_deps_file)
# Rebuild with full USE flags
emerge(*packages, extra_args=["--oneshot"], ask=False)
success("Circular dependency packages rebuilt with full USE flags.")
def parse_circular_deps(filepath: Path) -> list[str]:
"""Parse package names from circular-dependencies file."""
packages = []
for line in filepath.read_text().splitlines():
line = line.strip()
# Skip comments and empty lines
if not line or line.startswith("#"):
continue
# Extract package name (first token before USE flags)
parts = line.split()
if parts:
packages.append(parts[0])
return packages
def clear_circular_deps(filepath: Path) -> None:
"""Clear circular-dependencies file, keeping header comments."""
lines = filepath.read_text().splitlines()
header = []
for line in lines:
# Keep comments and blank lines in header
if line.startswith("#") or line.strip() == "":
header.append(line)
else:
# Stop at first package line
break
# Write header, ensure trailing newline
if header:
filepath.write_text("\n".join(header) + "\n")
else:
filepath.write_text("")
def cmd_firmware() -> None:
"""Install linux-firmware (inside chroot)."""
info("=== Installing Linux Firmware ===")
emerge("sys-kernel/linux-firmware")
success("Linux firmware installed.")
def cmd_kernel() -> None:
"""Install kernel (inside chroot)."""
info("=== Installing Kernel ===")
print()
info("Options:")
print(" 1) gentoo-kernel-bin - Precompiled, fastest")
print(" 2) gentoo-kernel - Compiled locally with your USE flags")
print()
choice = prompt("Select kernel [1]: ").strip()
if choice == "2":
emerge("sys-kernel/gentoo-kernel")
else:
emerge("sys-kernel/gentoo-kernel-bin")
success("Kernel installed.")
print()
info("Kernel modules installed. Dracut will generate initramfs.")
def cmd_services() -> None:
"""Install and configure services (inside chroot)."""
setup_services(source_dir=CONFIG_DIR)
def cmd_users() -> None:
"""Configure shell, sudo, and create user (inside chroot)."""
setup_users(source_dir=CONFIG_DIR)
def cmd_nvidia() -> None:
"""Install NVIDIA drivers and configure (inside chroot)."""
setup_nvidia(source_dir=CONFIG_DIR)
def cmd_bootloader() -> None:
"""Install GRUB and verify initramfs (inside chroot)."""
setup_bootloader(source_dir=CONFIG_DIR)
def cmd_fingerprint() -> None:
"""Set up fingerprint authentication (post-install)."""
from install.fingerprint import setup_fingerprint
setup_fingerprint(source_dir=CONFIG_DIR)
def is_in_chroot() -> bool:
"""Detect if we're running inside chroot vs live environment."""
# If /mnt/gentoo is mounted, we're in the live environment
# If not, we're either in chroot or on the installed system
from install.utils import is_mounted
return not is_mounted(Path("/mnt/gentoo"))
def cmd_install() -> None:
"""Full installation up to first reboot."""
if is_in_chroot():
# Inside chroot: run sync through bootloader
info("=== Install Phase (inside chroot) ===")
print()
info("Running: sync -> world -> firmware -> kernel -> services -> users -> nvidia -> bootloader")
print()
cmd_sync()
print()
cmd_world()
print()
cmd_firmware()
print()
cmd_kernel()
print()
cmd_services()
print()
cmd_users()
print()
cmd_nvidia()
print()
cmd_bootloader()
print()
success("=== Install phase complete ===")
print()
print("Next steps:")
print(" 1. Exit chroot: exit")
print(" 2. Unmount: umount -R /mnt/gentoo")
print(" 3. Reboot: reboot")
print(" 4. After reboot, run: python setup.py --desktop")
else:
# Outside chroot: run disk through chroot, then prompt
info("=== Install Phase (pre-chroot) ===")
print()
layout, config = cmd_disk()
print()
cmd_stage3()
print()
cmd_config()
print()
cmd_fstab(layout, config)
print()
cmd_chroot()
# After chroot exits, remind user
print()
success("=== Pre-chroot phase complete ===")
print()
print("You exited the chroot. To continue installation:")
print(" 1. Re-enter chroot: chroot /mnt/gentoo /bin/bash")
print(" 2. Source profile: source /etc/profile && export PS1='(chroot) $PS1'")
print(" 3. Run: python /root/gentoo/v4/setup.py --install")
def cmd_desktop() -> None:
"""Post-reboot desktop setup."""
info("=== Desktop Setup (post-reboot) ===")
print()
# Install Hyprland desktop
info("Installing Hyprland desktop environment...")
emerge("@hyprland", extra_args=["--update", "--deep", "--newuse"])
success("Hyprland desktop installed.")
print()
# Offer fingerprint setup
response = prompt("Set up fingerprint authentication? [y/N]: ").strip().lower()
if response in ("y", "yes"):
print()
cmd_fingerprint()
else:
info("Skipped fingerprint setup. Run 'python setup.py fingerprint' later if needed.")
print()
success("=== Desktop setup complete ===")
print()
print("Next steps:")
print(" 1. Reboot to apply display manager")
print(" 2. Log in via SDDM")
print(" 3. Copy Hyprland configs:")
print(" cp ~/gentoo/v4/hypr/ENVariables.conf ~/.config/hypr/UserConfigs/")
print(" cp ~/gentoo/v4/hypr/monitors.conf ~/.config/hypr/")
def cmd_all() -> None:
"""Full installation workflow."""
info("=== Gentoo Full Installation ===")
print()
# Step 1: Disk preparation
info("Step 1: Disk Preparation")
layout, config = cmd_disk()
# Step 2: Stage3
print()
info("Step 2: Stage3 Download & Extract")
cmd_stage3()
# Step 3: Configuration
print()
info("Step 3: Copy Configuration")
cmd_config()
# Step 4: Generate fstab/crypttab
print()
info("Step 4: Generate Filesystem Config")
cmd_fstab(layout, config)
# Step 5: Chroot
print()
info("Step 5: Chroot Preparation")
cmd_chroot()
def interactive_menu() -> None:
"""Interactive command selection."""
print()
info("=== Gentoo Installer for Legion S7 15ACH6 ===")
print()
print("Commands (pre-chroot):")
print(" 1) disk - Partition, encrypt, and mount disks")
print(" 2) stage3 - Download and extract stage3")
print(" 3) config - Copy portage configuration")
print(" 4) fstab - Generate fstab/crypttab")
print(" 5) chroot - Prepare and enter chroot")
print(" 6) all - Full pre-chroot installation")
print()
print("Commands (inside chroot):")
print(" 7) sync - Portage sync, profile, overlays (activates swap)")
print(" 8) world - emerge -avuDN @world")
print(" 9) firmware - Install linux-firmware")
print(" 10) kernel - Install kernel")
print(" 11) services - Install and configure services")
print(" 12) users - Configure shell, sudo, create user")
print(" 13) nvidia - Install NVIDIA drivers")
print(" 14) bootloader - Install GRUB, verify initramfs")
print()
print("Post-install:")
print(" 15) fingerprint - Set up fingerprint authentication")
print()
print("Utilities:")
print(" 16) swap-on - Activate encrypted swap")
print(" 17) swap-off - Deactivate encrypted swap")
print()
print("Full workflows:")
print(" i) install - Full install to first reboot (auto-detects chroot)")
print(" d) desktop - Post-reboot desktop setup (Hyprland + fingerprint)")
print()
print(" q) quit")
print()
choice = prompt("Select command: ").strip().lower()
if choice in ("1", "disk"):
cmd_disk()
elif choice in ("2", "stage3"):
cmd_stage3()
elif choice in ("3", "config"):
cmd_config()
elif choice in ("4", "fstab"):
if _disk_layout is None or _disk_config is None:
error("Disk layout not available.")
error("Run 'disk' command first in this session, or use 'all' for full installation.")
return
cmd_fstab(_disk_layout, _disk_config)
elif choice in ("5", "chroot"):
cmd_chroot()
elif choice in ("6", "all"):
cmd_all()
elif choice in ("7", "sync"):
cmd_sync()
elif choice in ("8", "world"):
cmd_world()
elif choice in ("9", "firmware"):
cmd_firmware()
elif choice in ("10", "kernel"):
cmd_kernel()
elif choice in ("11", "services"):
cmd_services()
elif choice in ("12", "users"):
cmd_users()
elif choice in ("13", "nvidia"):
cmd_nvidia()
elif choice in ("14", "bootloader"):
cmd_bootloader()
elif choice in ("15", "fingerprint"):
cmd_fingerprint()
elif choice in ("16", "swap-on"):
cmd_swap_on()
elif choice in ("17", "swap-off"):
cmd_swap_off()
elif choice in ("i", "install"):
cmd_install()
elif choice in ("d", "desktop"):
cmd_desktop()
elif choice == "q":
sys.exit(0)
else:
error("Invalid choice")
interactive_menu()
def main() -> None:
parser = argparse.ArgumentParser(
description="Gentoo installation orchestrator",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"command",
nargs="?",
choices=["disk", "stage3", "chroot", "config", "fstab", "sync", "world", "firmware", "kernel", "services", "users", "nvidia", "bootloader", "fingerprint", "swap-on", "swap-off", "all"],
help="Command to run (interactive menu if not specified)",
)
parser.add_argument(
"--install",
action="store_true",
help="Full installation up to first reboot (auto-detects chroot state)",
)
parser.add_argument(
"--desktop",
action="store_true",
help="Post-reboot desktop setup (Hyprland + fingerprint)",
)
args = parser.parse_args()
check_root()
# Handle major phase flags first
if args.install:
cmd_install()
elif args.desktop:
cmd_desktop()
elif args.command is None:
interactive_menu()
elif args.command == "disk":
cmd_disk()
elif args.command == "stage3":
cmd_stage3()
elif args.command == "config":
cmd_config()
elif args.command == "fstab":
# For standalone fstab, we need disk info
error("Run 'disk' first, or use 'all' for full installation")
sys.exit(1)
elif args.command == "chroot":
cmd_chroot()
elif args.command == "sync":
cmd_sync()
elif args.command == "world":
cmd_world()
elif args.command == "firmware":
cmd_firmware()
elif args.command == "kernel":
cmd_kernel()
elif args.command == "services":
cmd_services()
elif args.command == "users":
cmd_users()
elif args.command == "nvidia":
cmd_nvidia()
elif args.command == "bootloader":
cmd_bootloader()
elif args.command == "fingerprint":
cmd_fingerprint()
elif args.command == "swap-on":
cmd_swap_on()
elif args.command == "swap-off":
cmd_swap_off()
elif args.command == "all":
cmd_all()
if __name__ == "__main__":
main()