530 lines
16 KiB
Python
Executable File
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()
|