{ pkgs , profile , backupFiles ? [ ".config/mimeapps.list.backup" ] , ... }: let backupFilesString = pkgs.lib.strings.concatStringsSep " " backupFiles; in pkgs.writeShellScriptBin "saugcli" '' #!${pkgs.bash}/bin/bash set -euo pipefail # --- Configuration --- PROJECT="zaneyos/SaugOS" PROFILE="${profile}" BACKUP_FILES_STR="${backupFilesString}" VERSION="1.0.0" FLAKE_NIX_PATH="$HOME/$PROJECT/flake.nix" read -r -a BACKUP_FILES <<< "$BACKUP_FILES_STR" # --- Helper Functions --- verify_hostname() { local current_hostname local flake_hostname current_hostname="$(hostname)" if [ -f "$FLAKE_NIX_PATH" ]; then flake_hostname=$(${pkgs.gnugrep}/bin/grep -E '^[[:space:]]*host[[:space:]]*=' "$FLAKE_NIX_PATH" | ${pkgs.gnused}/bin/sed 's/.*=[[:space:]]*"\([^"]*\)".*/\1/') if [ -z "$flake_hostname" ]; then echo "Error: Could not find 'host' variable in $FLAKE_NIX_PATH" >&2 exit 1 fi if [ "$current_hostname" != "$flake_hostname" ]; then echo "Error: Hostname mismatch!" >&2 echo " Current hostname: '$current_hostname'" >&2 echo " Flake.nix host: '$flake_hostname'" >&2 echo "" >&2 echo "Hint: Run 'saugcli update-host' to automatically update flake.nix" >&2 echo " or manually edit $FLAKE_NIX_PATH" >&2 exit 1 fi else echo "Error: Flake.nix not found at $FLAKE_NIX_PATH" >&2 exit 1 fi local folder="$HOME/$PROJECT/hosts/$current_hostname" if [ ! -d "$folder" ]; then echo "Error: Matching host not found in $PROJECT, Missing folder: $folder" >&2 exit 1 fi } print_help() { echo "SaugOS CLI Utility -- version $VERSION" echo "" echo "Usage: saugcli [command] [options]" echo "" echo "Commands:" echo " cleanup - Clean up old system generations." echo " diag - Create a system diagnostic report (~diag.txt)." echo " list-gens - List user and system generations." echo " rebuild - Rebuild the NixOS system configuration." echo " rebuild-boot - Rebuild and set as boot default (activates on next restart)." echo " trim - Trim filesystems to improve SSD performance." echo " update - Update the flake and rebuild the system." echo " update-host - Auto set host and profile in flake.nix." echo " (Opt: saugcli update-host [hostname] [profile])" echo " add-host - Add a new host configuration." echo " del-host - Delete a host configuration." echo "" echo "Options for rebuild, rebuild-boot, and update commands:" echo " --dry, -n - Show what would be done without doing it" echo " --ask, -a - Ask for confirmation before proceeding" echo " --cores N - Limit build to N cores (useful for low-resource machines)" echo " --verbose, -v - Show verbose output" echo " --no-nom - Don't use nix-output-monitor" echo "" echo " help - Show this help message." } handle_backups() { if [ ''${#BACKUP_FILES[@]} -eq 0 ]; then return fi for file_path in "''${BACKUP_FILES[@]}"; do full_path="$HOME/$file_path" if [ -f "$full_path" ]; then echo "Removing stale backup file: $full_path" rm "$full_path" fi done } detect_gpu_profile() { local detected_profile="" local has_intel=false local has_amd=false local has_vm=false if ${pkgs.pciutils}/bin/lspci &> /dev/null; then if ${pkgs.pciutils}/bin/lspci | ${pkgs.gnugrep}/bin/grep -qi 'vga\|3d'; then while read -r line; do if echo "$line" | ${pkgs.gnugrep}/bin/grep -qi 'amd'; then has_amd=true elif echo "$line" | ${pkgs.gnugrep}/bin/grep -qi 'intel'; then has_intel=true elif echo "$line" | ${pkgs.gnugrep}/bin/grep -qi 'virtio\|vmware'; then has_vm=true fi done < <(${pkgs.pciutils}/bin/lspci | ${pkgs.gnugrep}/bin/grep -i 'vga\|3d') if "$has_vm"; then detected_profile="vm" elif "$has_amd"; then detected_profile="amd" elif "$has_intel"; then detected_profile="intel" fi fi else echo "Warning: lspci command not found. Cannot auto-detect GPU profile." >&2 fi echo "$detected_profile" } parse_nh_args() { local args_string="" local options_selected=() shift while [[ $# -gt 0 ]]; do case $1 in --dry|-n) args_string="$args_string --dry" options_selected+=("dry run mode (showing what would be done)") shift ;; --ask|-a) args_string="$args_string --ask" options_selected+=("confirmation prompts enabled") shift ;; --cores) if [[ -n $2 && $2 =~ ^[0-9]+$ ]]; then args_string="$args_string -- --cores $2" options_selected+=("limited to $2 CPU cores") shift 2 else echo "Error: --cores requires a numeric argument" >&2 exit 1 fi ;; --verbose|-v) args_string="$args_string --verbose" options_selected+=("verbose output enabled") shift ;; --no-nom) args_string="$args_string --no-nom" options_selected+=("nix-output-monitor disabled") shift ;; --) shift args_string="$args_string -- $*" options_selected+=("additional arguments: $*") break ;; -*) echo "Warning: Unknown flag '$1' - passing through to nh" >&2 args_string="$args_string $1" options_selected+=("unknown flag '$1' passed through") shift ;; *) echo "Error: Unexpected argument '$1'" >&2 exit 1 ;; esac done if [[ ''${#options_selected[@]} -gt 0 ]]; then echo "Options selected:" >&2 for option in "''${options_selected[@]}"; do echo " ✓ $option" >&2 done echo >&2 fi echo "$args_string" } # --- Main Logic --- if [ "$#" -eq 0 ]; then echo "Error: No command provided." >&2 print_help exit 1 fi case "$1" in cleanup) echo "Warning! This will remove old generations of your system." read -p "How many generations to keep (default: all)? " keep_count if [ -z "$keep_count" ]; then read -p "This will remove all but the current generation. Continue (y/N)? " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then ${pkgs.nh}/bin/nh clean all -v else echo "Cleanup cancelled." fi else read -p "This will keep the last $keep_count generations. Continue (y/N)? " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then ${pkgs.nh}/bin/nh clean all -k "$keep_count" -v else echo "Cleanup cancelled." fi fi LOG_DIR="$HOME/saugcli-cleanup-logs" mkdir -p "$LOG_DIR" LOG_FILE="$LOG_DIR/saugcli-cleanup-$(${pkgs.coreutils}/bin/date +%Y-%m-%d_%H-%M-%S).log" echo "Cleaning up old log files..." >> "$LOG_FILE" ${pkgs.findutils}/bin/find "$LOG_DIR" -type f -mtime +3 -name "*.log" -delete >> "$LOG_FILE" 2>&1 echo "Cleanup process logged to $LOG_FILE" ;; diag) echo "Generating system diagnostic report..." ${pkgs.inxi}/bin/inxi --full > "$HOME/diag.txt" echo "Diagnostic report saved to $HOME/diag.txt" ;; help) print_help ;; list-gens) echo "--- User Generations ---" ${pkgs.nix}/bin/nix-env --list-generations | ${pkgs.coreutils}/bin/cat || echo "Could not list user generations." echo "" echo "--- System Generations ---" ${pkgs.nix}/bin/nix profile history --profile /nix/var/nix/profiles/system | ${pkgs.coreutils}/bin/cat || echo "Could not list system generations." ;; rebuild) verify_hostname handle_backups extra_args=$(parse_nh_args "$@") echo "Starting NixOS rebuild for host: $(${pkgs.nettools}/bin/hostname)" if eval "${pkgs.nh}/bin/nh os switch --hostname '$PROFILE' $extra_args"; then echo "Rebuild finished successfully" else echo "Rebuild Failed" >&2 exit 1 fi ;; rebuild-boot) verify_hostname handle_backups extra_args=$(parse_nh_args "$@") echo "Starting NixOS rebuild (boot) for host: $(${pkgs.nettools}/bin/hostname)" echo "Note: Configuration will be activated on next reboot" if eval "${pkgs.nh}/bin/nh os boot --hostname '$PROFILE' $extra_args"; then echo "Rebuild-boot finished successfully" echo "New configuration set as boot default - restart to activate" else echo "Rebuild-boot Failed" >&2 exit 1 fi ;; trim) echo "Running 'sudo fstrim -v /' may take a few minutes and impact system performance." read -p "Enter (y/Y) to run now or enter to exit (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Running fstrim..." sudo ${pkgs.util-linux}/bin/fstrim -v / echo "fstrim complete." else echo "Trim operation cancelled." fi ;; update) verify_hostname handle_backups extra_args=$(parse_nh_args "$@") echo "Updating flake and rebuilding system for host: $(${pkgs.nettools}/bin/hostname)" if eval "${pkgs.nh}/bin/nh os switch --hostname '$PROFILE' --update $extra_args"; then echo "Update and rebuild finished successfully" else echo "Update and rebuild Failed" >&2 exit 1 fi ;; update-host) target_hostname="" target_profile="" if [ "$#" -eq 3 ]; then target_hostname="$2" target_profile="$3" elif [ "$#" -eq 1 ]; then echo "Attempting to auto-detect hostname and GPU profile..." target_hostname=$(${pkgs.nettools}/bin/hostname) target_profile=$(detect_gpu_profile) if [ -z "$target_profile" ]; then echo "Error: Could not auto-detect a specific GPU profile. Please provide it manually." >&2 echo "Usage: saugcli update-host [hostname] [profile]" >&2 exit 1 fi echo "Auto-detected Hostname: $target_hostname" echo "Auto-detected Profile: $target_profile" else echo "Error: Invalid number of arguments for 'update-host'." >&2 echo "Usage: saugcli update-host [hostname] [profile]" >&2 exit 1 fi echo "Updating $FLAKE_NIX_PATH..." if ${pkgs.gnused}/bin/sed -i "s/^[[:space:]]*host[[:space:]]*=[[:space:]]*\".*\"/ host = \"$target_hostname\"/" "$FLAKE_NIX_PATH"; then echo "Successfully updated host to: $target_hostname" else echo "Error: Failed to update host in $FLAKE_NIX_PATH" >&2 exit 1 fi if ${pkgs.gnused}/bin/sed -i "s/^[[:space:]]*profile[[:space:]]*=[[:space:]]*\".*\"/ profile = \"$target_profile\"/" "$FLAKE_NIX_PATH"; then echo "Successfully updated profile to: $target_profile" else echo "Error: Failed to update profile in $FLAKE_NIX_PATH" >&2 exit 1 fi echo "Flake.nix updated successfully!" ;; add-host) hostname="" profile_arg="" if [ "$#" -ge 2 ]; then hostname="$2" fi if [ "$#" -eq 3 ]; then profile_arg="$3" fi if [ -z "$hostname" ]; then read -p "Enter the new hostname: " hostname fi if [ -d "$HOME/$PROJECT/hosts/$hostname" ]; then echo "Error: Host '$hostname' already exists." >&2 exit 1 fi echo "Copying default host configuration..." ${pkgs.coreutils}/bin/cp -r "$HOME/$PROJECT/hosts/default" "$HOME/$PROJECT/hosts/$hostname" detected_profile="" if [[ -n "$profile_arg" && "$profile_arg" =~ ^(intel|amd|vm)$ ]]; then detected_profile="$profile_arg" else echo "Detecting GPU profile..." detected_profile=$(detect_gpu_profile) echo "Detected GPU profile: $detected_profile" read -p "Is this correct? (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Nn]$ ]]; then read -p "Enter the correct profile (intel, amd, vm): " new_profile while [[ ! "$new_profile" =~ ^(intel|amd|vm)$ ]]; do echo "Invalid profile. Please enter one of the following: intel, amd, vm" read -p "Enter the correct profile: " new_profile done detected_profile=$new_profile fi fi echo "Setting profile to '$detected_profile'..." ${pkgs.gnused}/bin/sed -i "s/profile = .*/profile = \"$detected_profile\";/" "$HOME/$PROJECT/hosts/$hostname/default.nix" read -p "Generate new hardware.nix? (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Generating hardware.nix..." sudo nixos-generate-config --show-hardware-config > "$HOME/$PROJECT/hosts/$hostname/hardware.nix" echo "hardware.nix generated." fi echo "Adding new host to git..." ${pkgs.git}/bin/git -C "$HOME/$PROJECT" add . echo "hostname: $hostname added" ;; del-host) hostname="" if [ "$#" -eq 2 ]; then hostname="$2" else read -p "Enter the hostname to delete: " hostname fi if [ ! -d "$HOME/$PROJECT/hosts/$hostname" ]; then echo "Error: Host '$hostname' does not exist." >&2 exit 1 fi read -p "Are you sure you want to delete the host '$hostname'? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Deleting host '$hostname'..." ${pkgs.coreutils}/bin/rm -rf "$HOME/$PROJECT/hosts/$hostname" ${pkgs.git}/bin/git -C "$HOME/$PROJECT" add . echo "hostname: $hostname removed" else echo "Deletion cancelled." fi ;; *) echo "Error: Invalid command '$1'" >&2 print_help exit 1 ;; esac ''