{ pkgs , profile , backupFiles ? [ ".config/mimeapps.list.backup" ] , ... }: let backupFilesString = pkgs.lib.strings.concatStringsSep " " backupFiles; # Create the get-doom script as a dependency get-doom-script = pkgs.writeShellScriptBin "get-doom" '' #!/usr/bin/env bash set -euo pipefail # --- Icons --- ICON_CHECK="✔" ICON_INFO="ℹ" ICON_ROCKET="🚀" # --- Helper Functions --- print_status() { echo echo "--- $ICON_INFO $1 ---" } print_success() { echo "--- $ICON_CHECK $1 ---" } print_banner() { echo "===============================" echo " Doom Emacs Installer $ICON_ROCKET" echo "===============================" } is_doom_installed() { local dir="$1" [[ -x "$dir/bin/doom" ]] && [[ -f "$dir/core/doom.el" ]] } emacsdir_is_empty() { local dir="$1" [[ -d "$dir" ]] && [[ -z "$(ls -A "$dir" 2>/dev/null)" ]] } # --- Main Script --- print_banner EMACSDIR="$HOME/.emacs.d" if is_doom_installed "$EMACSDIR"; then print_success "Doom Emacs is already installed." exit 0 fi if [[ -d "$EMACSDIR" ]]; then if emacsdir_is_empty "$EMACSDIR"; then print_status "Found empty $EMACSDIR; proceeding to install Doom Emacs into it..." else echo "Error: Found $EMACSDIR but it does not look like a Doom Emacs installation." >&2 echo "Refusing to overwrite a non-empty directory. Move it away and re-run, e.g.:" >&2 echo " mv \"$EMACSDIR\" \"$EMACSDIR.bak\"" >&2 exit 1 fi fi print_status "Cloning Doom Emacs..." ${pkgs.git}/bin/git clone --depth 1 https://github.com/doomemacs/doomemacs "$EMACSDIR" print_success "Doom Emacs cloned." print_status "Running Doom install..." "$EMACSDIR/bin/doom" install print_success "Doom install complete." print_status "Running doom sync..." "$EMACSDIR/bin/doom" sync print_success "Doom sync complete." echo print_success "All done! Doom Emacs is ready to use." ''; in pkgs.writeShellScriptBin "zcli" '' #!${pkgs.bash}/bin/bash set -euo pipefail # --- Program info --- # # zcli - NixOS System Management CLI # ================================== # # Purpose: NixOS system management utility for ZaneyOS distribution # Author: Don Williams (ddubs) & Zaney # Start Date: June 7th, 2025 # Version: 1.0.2 # # Architecture: # - Nix-generated shell script using writeShellScriptBin # - Configuration via Nix parameters (profile, backupFiles) # - Uses 'nh' tool for NixOS operations, 'inxi' for diagnostics # - Git integration for host configuration versioning # # Helper Functions: # verify_hostname() - Validates current hostname against flake.nix host variable # Exits with error if mismatch or missing host directory # detect_gpu_profile() - Parses lspci output to identify GPU hardware # Returns: nvidia/nvidia-laptop/amd/intel/vm/empty # handle_backups() - Removes files listed in BACKUP_FILES array from $HOME # parse_nh_args() - Parses command-line arguments for nh operations # print_help() - Outputs command usage and available operations # # Command Functions: # cleanup - Interactive cleanup of old generations via 'nh clean' # diag - Generate system report using 'inxi --full' # list-gens - Display user/system generations via nix-env and nix profile # rebuild - NixOS rebuild using 'nh os switch' # rebuild-boot - NixOS rebuild for next boot using 'nh os boot' # trim - SSD optimization via 'sudo fstrim -v /' # update - Flake update + rebuild using 'nh os switch --update' # update-host - Modify flake.nix host/profile variables via sed # add-host - Add new host configuration # del-host - Delete host configuration # doom [sub] - Doom Emacs management (install/status/remove/update) # # Variables: # PROJECT - Base directory name (ddubsos/zaneyos) # PROFILE - Hardware profile from Nix parameter # BACKUP_FILES - Array of backup file paths to clean # FLAKE_NIX_PATH - Path to flake.nix for host/profile updates # # --- Configuration --- PROJECT="zaneyos" #ddubos or zaneyos PROFILE="${profile}" BACKUP_FILES_STR="${backupFilesString}" VERSION="1.0.2" 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)" # Extract the host value from flake.nix 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 'zcli 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 # Also check if host folder exists 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 "ZaneyOS CLI Utility -- version $VERSION" echo "" echo "Usage: zcli [command] [options]" echo "" echo "Commands:" echo " cleanup - Clean up old system generations. Can specify a number to keep." echo " diag - Create a system diagnostic report." echo " (Filename: homedir/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: zcli update-host [hostname] [profile])" 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 VMs)" echo " --verbose, -v - Show verbose output" echo " --no-nom - Don't use nix-output-monitor" echo "" echo "Doom Emacs:" echo " doom install - Install Doom Emacs using get-doom script." echo " doom status - Check if Doom Emacs is installed." echo " doom remove - Remove Doom Emacs installation." echo " doom update - Update Doom Emacs (runs doom sync)." echo "" echo " help - Show this help message." } handle_backups() { if [ ''${#BACKUP_FILES[@]} -eq 0 ]; then echo "No backup files configured to check." return fi echo "Checking for backup files to remove..." 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_nvidia=false local has_intel=false local has_amd=false local has_vm=false if ${pkgs.pciutils}/bin/lspci &> /dev/null; then # Check if lspci is available 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 'nvidia'; then has_nvidia=true elif 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_nvidia" && "$has_intel"; then detected_profile="nvidia-laptop" elif "$has_nvidia"; then detected_profile="nvidia" 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" # Return the detected profile } # --- Helper function to parse additional arguments --- parse_nh_args() { local args_string="" local options_selected=() shift # Remove the main command (rebuild, rebuild-boot, update) 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 # Print friendly confirmation of selected options to stderr so it doesn't interfere with return value if [[ ''${#options_selected[@]} -gt 0 ]]; then echo "Options selected:" >&2 for option in "''${options_selected[@]}"; do echo " ✓ $option" >&2 done echo >&2 fi # Return only the args string 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/zcli-cleanup-logs" mkdir -p "$LOG_DIR" LOG_FILE="$LOG_DIR/zcli-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 # Parse additional arguments 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 # Parse additional arguments 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 # move to a new line 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 # Parse additional arguments 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 # zcli update-host target_hostname="$2" target_profile="$3" elif [ "$#" -eq 1 ]; then # zcli update-host (auto-detect) 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: zcli 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: zcli update-host [hostname] [profile]" >&2 exit 1 fi echo "Updating $FLAKE_NIX_PATH..." # Update host 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 # Update profile 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|nvidia|nvidia-hybrid|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, nvidia, nvidia-hybrid, vm): " new_profile while [[ ! "$new_profile" =~ ^(intel|amd|nvidia|nvidia-hybrid|vm)$ ]]; do echo "Invalid profile. Please enter one of the following: intel, amd, nvidia, nvidia-hybrid, 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 ;; doom) if [ "$#" -lt 2 ]; then echo "Error: doom command requires a subcommand." >&2 echo "Usage: zcli doom [install|status|remove|update]" >&2 exit 1 fi # Ensure we're acting on a valid host and we can locate variables.nix verify_hostname current_hostname="$(hostname)" host_vars_file="$HOME/$PROJECT/hosts/$current_hostname/variables.nix" if [ ! -f "$host_vars_file" ]; then echo "Error: Host variables file not found: $host_vars_file" >&2 echo "Please ensure your host folder exists and contains variables.nix." >&2 exit 1 fi is_doom_enabled() { # Return 0 if doomEmacsEnable = true; appears (ignoring leading spaces) ${pkgs.gnugrep}/bin/grep -Eq '^[[:space:]]*doomEmacsEnable[[:space:]]*=[[:space:]]*true[[:space:]]*;' "$host_vars_file" } ensure_doom_enabled() { # If the variable is present but false, flip it; if missing, append it if ${pkgs.gnugrep}/bin/grep -Eq '^[[:space:]]*doomEmacsEnable[[:space:]]*=' "$host_vars_file"; then ${pkgs.gnused}/bin/sed -i 's/^[[:space:]]*doomEmacsEnable[[:space:]]*=.*/ doomEmacsEnable = true;/' "$host_vars_file" else echo "" >> "$host_vars_file" echo " # Enabled by zcli doom on $(date)" >> "$host_vars_file" echo " doomEmacsEnable = true;" >> "$host_vars_file" fi } doom_subcommand="$2" case "$doom_subcommand" in install) if ! is_doom_enabled; then echo "✗ Doom Emacs is disabled for host '$current_hostname' (doomEmacsEnable = false)." >&2 echo "To enable, set doomEmacsEnable = true; in:" >&2 echo " $host_vars_file" >&2 echo "and rebuild your system before installing Doom." >&2 echo read -p "Enable Doom for this host now and rebuild? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Enabling Doom Emacs in $host_vars_file..." ensure_doom_enabled echo "Rebuilding system so user modules are applied..." if ${pkgs.nh}/bin/nh os switch --hostname "$PROFILE"; then echo "Rebuild complete. Proceeding with Doom installation." else echo "Error: Rebuild failed. Please fix the build and re-run 'zcli doom install'." >&2 exit 1 fi else echo "Aborting. Please enable doomEmacsEnable and rebuild before installing." >&2 exit 1 fi fi echo "Installing Doom Emacs..." ${get-doom-script}/bin/get-doom ;; status) if [ -x "$HOME/.emacs.d/bin/doom" ] && [ -f "$HOME/.emacs.d/core/doom.el" ]; then echo "✔ Doom Emacs appears installed at $HOME/.emacs.d" if [ -f "$HOME/.doom.d/init.el" ]; then echo " • User config found: $HOME/.doom.d/init.el" else echo " • Warning: User config (~/.doom.d) not found" fi echo "Version information:" "$HOME/.emacs.d/bin/doom" version 2>/dev/null || echo "Could not get version information" else if [ -d "$HOME/.emacs.d" ]; then if [ -z "$(ls -A "$HOME/.emacs.d" 2>/dev/null)" ]; then echo "✗ Found empty ~/.emacs.d (not a valid Doom installation)" else echo "✗ ~/.emacs.d exists but Doom was not detected" fi else echo "✗ Doom Emacs is not installed" fi echo "Run 'zcli doom install' to install it" fi ;; remove) if [ ! -d "$HOME/.emacs.d" ]; then echo "Doom Emacs is not installed" exit 0 fi echo "Warning: This will completely remove Doom Emacs and all your configuration!" read -p "Are you sure you want to continue? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Removing Doom Emacs..." ${pkgs.coreutils}/bin/rm -rf "$HOME/.emacs.d" echo "✔ Doom Emacs has been removed" else echo "Removal cancelled" fi ;; update) if [ ! -x "$HOME/.emacs.d/bin/doom" ] || [ ! -f "$HOME/.emacs.d/core/doom.el" ]; then echo "Error: Doom Emacs is not installed correctly. Run 'zcli doom install' first." >&2 exit 1 fi echo "Updating Doom Emacs..." "$HOME/.emacs.d/bin/doom" sync echo "✔ Doom Emacs update complete" ;; *) echo "Error: Invalid doom subcommand '$doom_subcommand'" >&2 echo "Usage: zcli doom [install|status|remove|update]" >&2 exit 1 ;; esac ;; *) echo "Error: Invalid command '$1'" >&2 print_help exit 1 ;; esac ''