From d94e534ee45056261fa7a0191119d177f489016c Mon Sep 17 00:00:00 2001 From: Cookiez Date: Wed, 27 May 2026 14:34:25 +0200 Subject: [PATCH 1/3] Split ncli across multiple files in its own directory --- modules/home.nix | 2 +- modules/ncli.nix | 761 -------------------------- modules/ncli/builder.nix | 133 +++++ modules/ncli/commands/dev.nix | 367 +++++++++++++ modules/ncli/commands/git.nix | 48 ++ modules/ncli/commands/maintenance.nix | 83 +++ modules/ncli/commands/rebuild.nix | 132 +++++ modules/ncli/commands/switch.nix | 40 ++ modules/ncli/default.nix | 12 + modules/ncli/lib.nix | 76 +++ 10 files changed, 892 insertions(+), 762 deletions(-) delete mode 100644 modules/ncli.nix create mode 100644 modules/ncli/builder.nix create mode 100644 modules/ncli/commands/dev.nix create mode 100644 modules/ncli/commands/git.nix create mode 100644 modules/ncli/commands/maintenance.nix create mode 100644 modules/ncli/commands/rebuild.nix create mode 100644 modules/ncli/commands/switch.nix create mode 100644 modules/ncli/default.nix create mode 100644 modules/ncli/lib.nix diff --git a/modules/home.nix b/modules/home.nix index d51cc02..01241ba 100644 --- a/modules/home.nix +++ b/modules/home.nix @@ -14,7 +14,7 @@ home.username = username; home.homeDirectory = "/home/${username}"; home.packages = [ - (import ./ncli.nix { + (import ./ncli/default.nix { inherit pkgs host project; backupFiles = [ ".gtkrc-2.0.backup" diff --git a/modules/ncli.nix b/modules/ncli.nix deleted file mode 100644 index a31ffd6..0000000 --- a/modules/ncli.nix +++ /dev/null @@ -1,761 +0,0 @@ -{ - pkgs, - host, - backupFiles ? [".config/mimeapps.list.backup"], - project, - ... -}: let - backupFilesString = pkgs.lib.strings.concatStringsSep " " backupFiles; -in - pkgs.writeShellScriptBin "ncli" '' - #!${pkgs.bash}/bin/bash - set -euo pipefail - - # --- Configuration --- - PROJECT="${project}" - HOST="${host}" - BACKUP_FILES_STR="${backupFilesString}" - VERSION="2.1.3" - FLAKE_NIX_PATH="$HOME/$PROJECT/flake.nix" - - read -r -a BACKUP_FILES <<< "$BACKUP_FILES_STR" - - # --- Read Colors file --- - source /$HOME/$PROJECT/other/colors.sh - - # --- Helper Functions --- - print_help() { - echo "NixOS CLI Utility -- version $VERSION" - echo "" - echo "Usage: ncli [command]" - echo "" - echo "System Commands:" - echo " rebuild - Rebuild the NixOS system configuration." - echo " update - Update the flake and rebuild the system." - echo "" - echo "Maintenance Commands:" - echo " cleanup - Clean up old system generations. Can specify a number to keep." - echo " diag - Create a system diagnostic report (saves to ~/diag.txt)." - echo " list-gens - List user and system generations." - echo " trim - Trim filesystems to improve SSD performance." - echo "" - echo "Git Commands:" - echo " commit [msg] - Add all changes and commit with message." - echo " push - Push changes to origin." - echo " pull - Pull latest changes from origin." - echo " status - Show git status." - echo "" - echo "Development Commands:" - echo " dev - Initialize a Nix development environment (flake.nix + direnv)." - echo " dev track - Remove assume-unchanged flag from flake files." - echo " dev untrack - Mark flake files as assume-unchanged in a directory." - echo "" - echo " help - Show this help message." - echo "" - } - - 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 - } - - # --- Dev Init Helper Functions --- - print_header() { - echo "" - echo -e "''${BLUE}==============================================''${NOCOLOR}" - echo -e "''${BLUE} Nix Flake Development Environment Initializer''${NOCOLOR}" - echo -e "''${BLUE}==============================================''${NOCOLOR}" - echo "" - } - - print_success() { - echo -e "''${GREEN}[OK]''${NOCOLOR} $1" - } - - print_error() { - echo -e "''${RED}[ERR]''${NOCOLOR} $1" - } - - print_info() { - echo -e "''${YELLOW}[->]''${NOCOLOR} $1" - } - - handle_build_error() { - local exit_code=$? - # Exit code 137 = 128+9 = SIGKILL, almost always OOM killer - if [ "$exit_code" -eq 137 ]; then - echo "" - echo -e "''${RED}╔══════════════════════════════════════════════════════════╗''${NOCOLOR}" - echo -e "''${RED}║ BUILD KILLED — Signal 9 (SIGKILL) detected ║''${NOCOLOR}" - echo -e "''${RED}╚══════════════════════════════════════════════════════════╝''${NOCOLOR}" - echo "" - echo -e "''${YELLOW}What happened:''${NOCOLOR}" - echo " The build process was forcefully terminated by the OS." - echo " This is almost always the Linux kernel OOM killer running out" - echo " of RAM + swap during Nix evaluation or compilation." - echo "" - echo -e "''${YELLOW}Suggested fixes (try in order):''${NOCOLOR}" - echo "" - echo -e " ''${GREEN}1. Reduce parallel jobs''${NOCOLOR} (lowest RAM usage):" - echo " sudo nixos-rebuild switch --flake . --max-jobs 1 --cores 1" - echo "" - echo -e " ''${GREEN}2. Confirm OOM killer fired:''${NOCOLOR}" - echo " journalctl -k --since '5 minutes ago' | grep -i oom" - echo "" - fi - } - - # --- 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 - 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 - nh clean all -k "$keep_count" -v - else - echo "Cleanup cancelled." - fi - fi - - LOG_DIR="$HOME/ncli-cleanup-logs" - mkdir -p "$LOG_DIR" - LOG_FILE="$LOG_DIR/ncli-cleanup-$(date +%Y-%m-%d_%H-%M-%S).log" - echo "Cleaning up old log files..." >> "$LOG_FILE" - 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..." - { - echo "=== NixOS System Diagnostic Report ===" - echo "Generated: $(date)" - echo "" - echo "=== System Information ===" - inxi --full 2>/dev/null || echo "inxi not available" - echo "" - echo "=== Git Status ===" - cd "$HOME/$PROJECT" 2>/dev/null && git status 2>/dev/null || echo "Git status not available" - echo "" - } > "$HOME/diag.txt" - echo "Diagnostic report saved to $HOME/diag.txt" - ;; - help) - print_help - ;; - list-gens) - echo "--- User Generations ---" - nix-env --list-generations | cat || echo "Could not list user generations." - echo "" - echo "--- System Generations ---" - nix profile history --profile /nix/var/nix/profiles/system | cat || echo "Could not list system generations." - ;; - rebuild) - handle_backups - geno=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') - echo -e "Starting NixOS rebuild for current host: $HOST on generation: $YELLOW$geno$NOCOLOR" - cd "$HOME/$PROJECT" || { echo "Error: Could not change to $HOME/$PROJECT"; exit 1; } - - current="" - if [ -f /etc/nixos-tags ]; then - current=$(cat /etc/nixos-tags) - fi - - sudo nixos-rebuild switch --flake . ; _rebuild_exit=$? - if [ "$_rebuild_exit" -eq 0 ]; then - echo "✓ Rebuild finished successfully for $HOST" - - if [ -n "$current" ]; then - sudo /run/current-system/specialisation/$current/bin/switch-to-configuration test - else - echo "No specialization tag found, staying on default system." - fi - - genn=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') - echo -e "Running on new generation: $YELLOW $geno $NOCOLOR-> $GREEN$genn$NOCOLOR" - - else - ( exit "$_rebuild_exit" ); handle_build_error - echo "✗ Rebuild failed for $HOST" >&2 - exit 1 - fi - ;; - update) - handle_backups - geno=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') - echo -e "Updating flake and rebuilding system for current host: $HOST on generation: $YELLOW$geno$NOCOLOR" - cd "$HOME/$PROJECT" || { echo "Error: Could not change to $HOME/$PROJECT"; exit 1; } - - # --- Selective flake update --- - read -rp "Update [a]ll inputs or [s]elect manually? (a/s): " choice - - case "$choice" in - a|A) - echo "Updating all inputs..." - if nix flake update --flake .; then - echo "✓ Flake updated successfully" - else - echo "✗ Flake update failed" >&2 - exit 1 - fi - ;; - s|S) - echo "Fetching available updates (this may take a moment)..." - TEMP_LOCK=$(mktemp) - trap 'rm -f "$TEMP_LOCK"' EXIT - - nix flake update --output-lock-file "$TEMP_LOCK" --flake . 2>/dev/null - - outdated=$(jq -r --slurpfile new "$TEMP_LOCK" ' - .nodes as $old | - $new[0].nodes as $newn | - ($old | keys[]) | - select(. != "root") | - select( - ($old[.].locked.lastModified // 0) != - ($newn[.].locked.lastModified // 0) - ) - ' flake.lock) - - if [[ -z "$outdated" ]]; then - echo "✓ All inputs are already up to date, skipping flake update." - else - echo - echo "Updates available for:" - printf '%s\n' "$outdated" - echo - echo "Tab to select, Enter to update, Esc to cancel." - selected=$(printf '%s\n' "$outdated" | fzf --multi) || { - echo "No inputs selected, skipping flake update." - selected="" - } - if [[ -n "$selected" ]]; then - if nix flake update --flake . $selected; then - echo "✓ Flake updated successfully" - else - echo "✗ Flake update failed" >&2 - exit 1 - fi - fi - fi - ;; - *) - echo "Invalid choice, skipping flake update." - ;; - esac - # --- End selective flake update --- - - - current="" - if [ -f /etc/nixos-tags ]; then - current=$(cat /etc/nixos-tags) - fi - - if [ -n "$current" ]; then - echo "Rebuilding system... Current specialization: $current" - else - echo "Rebuilding system... Staying on current specialization" - fi - - sudo nixos-rebuild switch --flake . ; _rebuild_exit=$? - if [ "$_rebuild_exit" -eq 0 ]; then - echo "✓ Update and rebuild finished successfully for $HOST" - - if [ -n "$current" ]; then - sudo /run/current-system/specialisation/$current/bin/switch-to-configuration test - else - echo "No specialization tag found, staying on default system." - fi - - genn=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') - echo -e "Running on new generation: $YELLOW $geno $NOCOLOR-> $GREEN$genn$NOCOLOR" - - else - ( exit "$_rebuild_exit" ); handle_build_error - echo "✗ Update and rebuild failed for $HOST" >&2 - exit 1 - fi - ;; - commit) - cd "$HOME/$PROJECT" || { echo "Error: Could not change to $HOME/$PROJECT"; exit 1; } - if [ "$#" -lt 2 ]; then - read -p "Enter commit message: " commit_msg - else - shift - commit_msg="$*" - fi - - if [ -z "$commit_msg" ]; then - echo "Error: Commit message cannot be empty" >&2 - exit 1 - fi - - git add -A && git commit -m "$commit_msg" - ;; - home-backups) - ls -a ~ | grep backup - ;; - switch) - current="" - if [ -f /etc/nixos-tags ]; then - current=$(cat /etc/nixos-tags) - fi - - if [ "$#" -ge 2 ]; then - spec_name="$2" - if [ -n "$current" ]; then - echo "Already on specialization: $current. Cannot switch directly to '$spec_name'. Please reboot or return to default first." - else - if [ -d "/run/current-system/specialisation/$spec_name" ]; then - echo "Switching to specialization: $spec_name" - sudo /run/current-system/specialisation/$spec_name/bin/switch-to-configuration test - else - echo "Error: Specialization '$spec_name' not found." - echo "Available specializations:" - ls /run/current-system/specialisation/ - fi - fi - else - if [ -n "$current" ]; then - echo "Already on a specialization: $current. To switch, please reboot, or use 'sudo nixos-rebuild switch --flake .' to get back to default, and then switch after." - else - specs=$(ls /run/current-system/specialisation/) - echo "Specializations available:" - echo "$specs" - echo "" - echo "To switch to a specialization, run: 'ncli switch '" - fi - fi - ;; - push) - cd "$HOME/$PROJECT" || { echo "Error: Could not change to $HOME/$PROJECT"; exit 1; } - git push origin $(git branch --show-current) - ;; - pull) - cd "$HOME/$PROJECT" || { echo "Error: Could not change to $HOME/$PROJECT"; exit 1; } - git pull origin $(git branch --show-current) - ;; - status) - cd "$HOME/$PROJECT" || { echo "Error: Could not change to $HOME/$PROJECT"; exit 1; } - git status - ;; - format) - nix fmt . - ;; - trim) - echo "Running 'sudo fstrim -v /' may take a few minutes and impact system performance." - read -p "Enter 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 fstrim -v / - echo "fstrim complete." - else - echo "Trim operation cancelled." - fi - ;; - dev) - # Only flake files need assume-unchanged (Nix requires them in the index). - # .envrc and .direnv are handled via .git/info/exclude instead. - DEV_FILES=(flake.nix flake.lock) - DIRENV_LOCAL_FILES=(.envrc .direnv) - - _dev_resolve_dir() { - read -rp " Enter target directory [./]: " TARGET_DIR - TARGET_DIR="''${TARGET_DIR:-./}" - if [[ ! -d "$TARGET_DIR" ]]; then - print_error "Directory '$TARGET_DIR' does not exist." - exit 1 - fi - TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" - if ! git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then - print_error "No Git repository found at '$TARGET_ABS'." - exit 1 - fi - } - - # Add .envrc and .direnv to .git/info/exclude (local-only, never committed). - _add_local_excludes() { - local repo_abs="$1" - local git_dir exclude_file - git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" - exclude_file="$git_dir/info/exclude" - mkdir -p "$(dirname "$exclude_file")" - touch "$exclude_file" - - for entry in "''${DIRENV_LOCAL_FILES[@]}"; do - if ! grep -qxF "$entry" "$exclude_file" 2>/dev/null; then - echo "$entry" >> "$exclude_file" - print_success "$entry → ignored via .git/info/exclude (local only)" - else - print_info "$entry already in .git/info/exclude, skipping." - fi - done - } - - _remove_local_excludes() { - local repo_abs="$1" - local git_dir exclude_file tmp_file - git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" - exclude_file="$git_dir/info/exclude" - - [[ -f "$exclude_file" ]] || return 0 - - tmp_file="$(mktemp)" - cp "$exclude_file" "$tmp_file" - - for entry in "''${DIRENV_LOCAL_FILES[@]}"; do - if grep -qxF "$entry" "$tmp_file" 2>/dev/null; then - grep -vxF "$entry" "$tmp_file" > "''${tmp_file}.new" || true - mv "''${tmp_file}.new" "$tmp_file" - print_success "$entry → removed from .git/info/exclude" - else - print_info "$entry not present in .git/info/exclude, skipping." - fi - done - - mv "$tmp_file" "$exclude_file" - } - - case "''${2:-init}" in - - untrack) - echo "" - echo -e "''${BLUE}--- Dev: Track Files (assume-unchanged) ---''${NOCOLOR}" - echo "" - _dev_resolve_dir - - acted=false - for f in "''${DEV_FILES[@]}"; do - full="$TARGET_ABS/$f" - if [[ ! -f "$full" ]]; then - print_info "$f not found in $TARGET_ABS — skipping." - continue - fi - if ! git -C "$TARGET_ABS" ls-files --error-unmatch "$f" > /dev/null 2>&1; then - git -C "$TARGET_ABS" add --intent-to-add "$f" - fi - git -C "$TARGET_ABS" update-index --assume-unchanged "$f" - print_success "$f → untracked (assume-unchanged)" - acted=true - done - - _add_local_excludes "$TARGET_ABS" - - if ! $acted; then - print_info "No dev files found in '$TARGET_ABS' to track." - fi - ;; - - track) - echo "" - echo -e "''${BLUE}--- Dev: Untrack Files (remove assume-unchanged) ---''${NOCOLOR}" - echo "" - _dev_resolve_dir - - acted=false - for f in "''${DEV_FILES[@]}"; do - if git -C "$TARGET_ABS" ls-files -v "$f" 2>/dev/null | grep -q "^h "; then - git -C "$TARGET_ABS" update-index --no-assume-unchanged "$f" - print_success "$f → tracked (assume-unchanged removed)" - acted=true - else - print_info "$f is not marked assume-unchanged — skipping." - fi - done - - _remove_local_excludes "$TARGET_ABS" - - if ! $acted; then - print_info "No files in '$TARGET_ABS' had the assume-unchanged bit set." - fi - ;; - - init|*) - # ---- Defaults ----- - SOURCE_FLAKE="$HOME/$PROJECT/other/dev-template.nix" - - # ---- Check source flake exists ------------------------------ - if [[ ! -f "$SOURCE_FLAKE" ]]; then - print_error "Source flake template not found: $SOURCE_FLAKE" - echo "Please ensure dev-template.nix exists in $HOME/$PROJECT/other/" - exit 1 - fi - - print_header - - # ---- Step 1: Target Directory ----------------------------------- - echo -e "''${YELLOW}Step 1/5: Target Directory''${NOCOLOR}" - echo "" - read -p " Enter target directory [./]: " TARGET_DIR - - if [[ -z "$TARGET_DIR" ]]; then - TARGET_DIR="./" - fi - - if [[ ! -d "$TARGET_DIR" ]]; then - print_error "Directory '$TARGET_DIR' does not exist. Exiting." - exit 1 - fi - - print_success "Target directory: $TARGET_DIR" - echo "" - - # ---- Check for existing flake ---------------------------------- - if [[ -f "$TARGET_DIR/flake.nix" ]]; then - echo "" - print_error "A flake.nix already exists in '$TARGET_DIR'!" - echo "" - read -p " Would you like to just place the .envrc instead? [y/N]: " ENVRC_ONLY - ENVRC_ONLY="''${ENVRC_ONLY:-N}" - - if [[ "$ENVRC_ONLY" =~ ^[Yy]$ ]]; then - TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" - echo "" - print_info "Placing .envrc only..." - if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then - echo "use flake" >> "$TARGET_ABS/.envrc" - print_success ".envrc created at $TARGET_ABS/" - else - print_info ".envrc already contains 'use flake', nothing to do." - fi - - echo "" - print_info "Next steps:" - echo " cd $TARGET_DIR" - echo " direnv allow" - echo "" - exit 0 - else - print_info "Aborting. No files were changed." - exit 1 - fi - fi - - # ---- Step 2: Project Name --------------------------------------- - echo -e "''${YELLOW}Step 2/5: Project Name''${NOCOLOR}" - echo "" - read -p " Enter development environment name [devShell]: " PROJECT_NAME - - if [[ -z "$PROJECT_NAME" ]]; then - PROJECT_NAME="devShell" - fi - - print_success "Project name: $PROJECT_NAME" - echo "" - - # ---- Step 3: Select template ---------------------------------------- - echo -e "''${YELLOW}Step 3/5: Select Template''${NOCOLOR}" - echo "" - echo " 1) Empty - Basic shell, add packages yourself" - echo " 2) Python - Python 3, pip, venv setup" - echo " 3) Node.js - Node.js, npm" - echo " 4) Rust - Rustc, cargo, rust-analyzer" - echo " 5) Go - Go, gopls, golangci-lint" - echo " 6) C/C++ - GCC, CMake, GDB, pkg-config" - echo " 7) Java - JDK 21, Maven" - echo "" - - read -p " Select template [1-7] (default: 1): " TEMPLATE_CHOICE - - case "$TEMPLATE_CHOICE" in - 2) - TEMPLATE="python3" - PACKAGES="python3 python3Packages.pip python3Packages.virtualenv" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA=" - # Create venv if it doesn't exist - if [ ! -d .venv ]; then - echo 'Creating Python virtual environment...' - python3 -m venv .venv - fi - - # Activate venv - source .venv/bin/activate - - echo 'Python venv activated.' - " - ;; - 3) - TEMPLATE="node" - PACKAGES="nodejs_22" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA='echo "Node.js $(node --version) ready."' - ;; - 4) - TEMPLATE="rust" - PACKAGES="rustc cargo rust-analyzer" - BUILD_INPUTS="openssl pkg-config" - SHELL_HOOK_EXTRA='echo "Rust $(rustc --version) ready."' - ;; - 5) - TEMPLATE="go" - PACKAGES="go gopls golangci-lint" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA=' - export GOPATH="$PWD/.gopath" - export PATH="$GOPATH/bin:$PATH" - mkdir -p "$GOPATH" - echo "Go $(go version) ready. GOPATH: $GOPATH" - ' - ;; - 6) - TEMPLATE="cpp" - PACKAGES="gcc gdb cmake gnumake" - BUILD_INPUTS="pkg-config" - SHELL_HOOK_EXTRA='echo "GCC $(gcc --version | head -1) ready."' - ;; - 7) - TEMPLATE="java" - PACKAGES="jdk21 maven" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA='echo "Java $(java --version | head -1) ready."' - ;; - *) - TEMPLATE="empty" - PACKAGES="" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA="" - ;; - esac - - print_success "Selected template: $TEMPLATE" - echo "" - - # ---- Step 4: Copy and modify flake ---------------------------------- - echo -e "''${YELLOW}Step 4/5: Generating flake.nix''${NOCOLOR}" - echo "" - - cp "$SOURCE_FLAKE" "$TARGET_DIR/flake.nix" - print_success "Copied flake.nix to $TARGET_DIR/" - - sed -i "s|name = \"replaceNameHere\";|name = \"''${PROJECT_NAME}\";|" "$TARGET_DIR/flake.nix" - print_success "Set project name: $PROJECT_NAME" - - if [[ -n "$PACKAGES" ]]; then - sed -i "s|# replacePackagesHere|$PACKAGES|" "$TARGET_DIR/flake.nix" - print_success "Added packages: $PACKAGES" - fi - - if [[ -n "$BUILD_INPUTS" ]]; then - BUILD_INPUTS_LINE="buildInputs = with pkgs; [ $BUILD_INPUTS ];" - sed -i "s|# replaceBuildInputsHere|$BUILD_INPUTS_LINE|" "$TARGET_DIR/flake.nix" - print_success "Added build inputs: $BUILD_INPUTS" - fi - - if [[ -n "$SHELL_HOOK_EXTRA" ]]; then - awk -v hook="$SHELL_HOOK_EXTRA" '/# replaceShellHookHere/ { print hook; next } { print }' \ - "$TARGET_DIR/flake.nix" > "$TARGET_DIR/flake.nix.tmp" \ - && mv "$TARGET_DIR/flake.nix.tmp" "$TARGET_DIR/flake.nix" - print_success "Added template-specific shell hook" - fi - - # ---- Step 5: Git Integration & .envrc -------------------------------- - echo -e "''${YELLOW}Step 5/5: Git Integration''${NOCOLOR}" - echo "" - - TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" - - echo "Creating .envrc for direnv to automatically work!" - if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then - echo "use flake" >> "$TARGET_ABS/.envrc" - print_success ".envrc created at $TARGET_ABS/" - else - print_info ".envrc already contains 'use flake', skipping." - fi - - if git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then - echo " A Git repository was detected at:" - echo " $(git -C "$TARGET_ABS" rev-parse --show-toplevel)" - echo "" - - # Always add .envrc and .direnv to local exclude — no prompt needed, - # these should never be committed regardless. - _add_local_excludes "$TARGET_ABS" - echo "" - - echo " Nix flakes require flake.nix (and flake.lock) to be Git-tracked." - echo " This script can stage them as 'assume-unchanged' so Nix sees them," - echo " but they will NEVER be committed or show up in git status." - echo "" - read -p " Set up flake files as tracked-but-invisible to Git? [y/N]: " GIT_CHOICE - - GIT_CHOICE="''${GIT_CHOICE:-N}" - - if [[ "$GIT_CHOICE" =~ ^[Yy]$ ]]; then - cat > "$TARGET_ABS/flake.lock" << 'EOF' - { - "nodes": { - "root": {} - }, - "root": "root", - "version": 7 - } - EOF - print_success "flake.lock ready (Nix will populate it on first run if empty)" - - git -C "$TARGET_ABS" add --intent-to-add flake.nix flake.lock - git -C "$TARGET_ABS" update-index --assume-unchanged flake.nix flake.lock - print_success "flake.nix + flake.lock → tracked (assume-unchanged)" - else - print_info "Skipped. You can do this manually later:" - echo "" - echo " git -C \"$TARGET_ABS\" add --intent-to-add flake.nix flake.lock" - echo " git -C \"$TARGET_ABS\" update-index --assume-unchanged flake.nix flake.lock" - echo "" - fi - else - print_info "No Git repository detected — skipping Git integration." - echo "" - fi - - # ---- Done --------------------------------------------------- - echo -e "''${GREEN}==============================================''${NOCOLOR}" - echo -e "''${GREEN} Done! Your flake is ready at:''${NOCOLOR}" - echo -e "''${GREEN} $TARGET_DIR/flake.nix''${NOCOLOR}" - echo -e "''${GREEN}==============================================''${NOCOLOR}" - echo "" - print_info "Next steps:" - echo " cd $TARGET_DIR" - echo " direnv allow # Trust the .envrc so direnv auto-activates" - echo " nix develop # Or just: cd out and back in" - echo "" - ;; - - esac - ;; - *) - echo "Error: Invalid command '$1'" >&2 - print_help - exit 1 - ;; - esac - '' diff --git a/modules/ncli/builder.nix b/modules/ncli/builder.nix new file mode 100644 index 0000000..bf77664 --- /dev/null +++ b/modules/ncli/builder.nix @@ -0,0 +1,133 @@ +# ncli/builder.nix — Assembles all modules into the final script +{ + pkgs, + host, + backupFiles ? [".config/mimeapps.list.backup"], + project, + ... +}: let + # --- Shared library --- + lib = import ./lib.nix {inherit pkgs host project backupFiles;}; + + # --- Command modules --- + rebuild = import ./commands/rebuild.nix lib; + maintenance = import ./commands/maintenance.nix lib; + git = import ./commands/git.nix lib; + switch = import ./commands/switch.nix lib; + dev = import ./commands/dev.nix lib; + + # --- Help text --- + print_help = '' + echo "NixOS CLI Utility -- version 2.1.3" + echo "" + echo "Usage: ncli [command]" + echo "" + echo "System Commands:" + echo " rebuild - Rebuild the NixOS system configuration." + echo " update - Update the flake and rebuild the system." + echo "" + echo "Maintenance Commands:" + echo " cleanup - Clean up old system generations. Can specify a number to keep." + echo " diag - Create a system diagnostic report (saves to ~/diag.txt)." + echo " list-gens - List user and system generations." + echo " trim - Trim filesystems to improve SSD performance." + echo "" + echo "Git Commands:" + echo " commit [msg] - Add all changes and commit with message." + echo " push - Push changes to origin." + echo " pull - Pull latest changes from origin." + echo " status - Show git status." + echo "" + echo "Development Commands:" + echo " dev - Initialize a Nix development environment (flake.nix + direnv)." + echo " dev track - Remove assume-unchanged flag from flake files." + echo " dev untrack - Mark flake files as assume-unchanged in a directory." + echo "" + echo " help - Show this help message." + echo "" + ''; +in + pkgs.writeShellScriptBin "ncli" '' + #!${pkgs.bash}/bin/bash + set -euo pipefail + + # --- Configuration --- + PROJECT="${project}" + HOST="${host}" + BACKUP_FILES_STR="${lib.backupFilesString}" + VERSION="2.2.0" + FLAKE_NIX_PATH="$HOME/$PROJECT/flake.nix" + + read -r -a BACKUP_FILES <<< "$BACKUP_FILES_STR" + + # --- Read Colors file --- + source ${lib.colorsSource} + + # --- Helper Functions --- + ${lib.handle_backups} + ${lib.handle_build_error} + ${lib.print_header} + ${lib.print_success} + ${lib.print_error} + ${lib.print_info} + + # --- Main Logic --- + if [ "$#" -eq 0 ]; then + echo "Error: No command provided." >&2 + print_help + exit 1 + fi + + case "$1" in + rebuild) + ${rebuild.rebuild_case} + ;; + update) + ${rebuild.update_case} + ;; + cleanup) + ${maintenance.cleanup_case} + ;; + diag) + ${maintenance.diag_case} + ;; + list-gens) + ${maintenance.list_gens_case} + ;; + trim) + ${maintenance.trim_case} + ;; + home-backups) + ${maintenance.home_backups_case} + ;; + commit) + ${git.commit_case} + ;; + push) + ${git.push_case} + ;; + pull) + ${git.pull_case} + ;; + status) + ${git.status_case} + ;; + format) + ${git.format_case} + ;; + switch) + ${switch.switch_case} + ;; + dev) + ${dev.dev_case} + ;; + help) + ${print_help} + ;; + *) + echo "Error: Invalid command '$1'" >&2 + print_help + exit 1 + ;; + esac + '' diff --git a/modules/ncli/commands/dev.nix b/modules/ncli/commands/dev.nix new file mode 100644 index 0000000..8cf9e62 --- /dev/null +++ b/modules/ncli/commands/dev.nix @@ -0,0 +1,367 @@ +# ncli/commands/dev.nix — dev init / track / untrack +lib: + +{ + dev_case = '' + # Only flake files need assume-unchanged (Nix requires them in the index). + # .envrc and .direnv are handled via .git/info/exclude instead. + DEV_FILES=(flake.nix flake.lock) + DIRENV_LOCAL_FILES=(.envrc .direnv) + + _dev_resolve_dir() { + read -rp " Enter target directory [./]: " TARGET_DIR + TARGET_DIR="''${TARGET_DIR:-./}" + if [[ ! -d "$TARGET_DIR" ]]; then + print_error "Directory '$TARGET_DIR' does not exist." + exit 1 + fi + TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" + if ! git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then + print_error "No Git repository found at '$TARGET_ABS'." + exit 1 + fi + } + + _add_local_excludes() { + local repo_abs="$1" + local git_dir exclude_file + git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" + exclude_file="$git_dir/info/exclude" + mkdir -p "$(dirname "$exclude_file")" + touch "$exclude_file" + + for entry in "''${DIRENV_LOCAL_FILES[@]}"; do + if ! grep -qxF "$entry" "$exclude_file" 2>/dev/null; then + echo "$entry" >> "$exclude_file" + print_success "$entry → ignored via .git/info/exclude (local only)" + else + print_info "$entry already in .git/info/exclude, skipping." + fi + done + } + + _remove_local_excludes() { + local repo_abs="$1" + local git_dir exclude_file tmp_file + git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" + exclude_file="$git_dir/info/exclude" + + [[ -f "$exclude_file" ]] || return 0 + + tmp_file="$(mktemp)" + cp "$exclude_file" "$tmp_file" + + for entry in "''${DIRENV_LOCAL_FILES[@]}"; do + if grep -qxF "$entry" "$tmp_file" 2>/dev/null; then + grep -vxF "$entry" "$tmp_file" > "''${tmp_file}.new" || true + mv "''${tmp_file}.new" "$tmp_file" + print_success "$entry → removed from .git/info/exclude" + else + print_info "$entry not present in .git/info/exclude, skipping." + fi + done + + mv "$tmp_file" "$exclude_file" + } + + case "''${2:-init}" in + + untrack) + echo "" + echo -e "''${BLUE}--- Dev: Track Files (assume-unchanged) ---''${NOCOLOR}" + echo "" + _dev_resolve_dir + + acted=false + for f in "''${DEV_FILES[@]}"; do + full="$TARGET_ABS/$f" + if [[ ! -f "$full" ]]; then + print_info "$f not found in $TARGET_ABS — skipping." + continue + fi + if ! git -C "$TARGET_ABS" ls-files --error-unmatch "$f" > /dev/null 2>&1; then + git -C "$TARGET_ABS" add --intent-to-add "$f" + fi + git -C "$TARGET_ABS" update-index --assume-unchanged "$f" + print_success "$f → untracked (assume-unchanged)" + acted=true + done + + _add_local_excludes "$TARGET_ABS" + + if ! $acted; then + print_info "No dev files found in '$TARGET_ABS' to track." + fi + ;; + + track) + echo "" + echo -e "''${BLUE}--- Dev: Untrack Files (remove assume-unchanged) ---''${NOCOLOR}" + echo "" + _dev_resolve_dir + + acted=false + for f in "''${DEV_FILES[@]}"; do + if git -C "$TARGET_ABS" ls-files -v "$f" 2>/dev/null | grep -q "^h "; then + git -C "$TARGET_ABS" update-index --no-assume-unchanged "$f" + print_success "$f → tracked (assume-unchanged removed)" + acted=true + else + print_info "$f is not marked assume-unchanged — skipping." + fi + done + + _remove_local_excludes "$TARGET_ABS" + + if ! $acted; then + print_info "No files in '$TARGET_ABS' had the assume-unchanged bit set." + fi + ;; + + init|*) + SOURCE_FLAKE="$HOME/${lib.project}/other/dev-template.nix" + + if [[ ! -f "$SOURCE_FLAKE" ]]; then + print_error "Source flake template not found: $SOURCE_FLAKE" + echo "Please ensure dev-template.nix exists in $HOME/${lib.project}/other/" + exit 1 + fi + + print_header + + # ---- Step 1: Target Directory ---- + echo -e "''${YELLOW}Step 1/5: Target Directory''${NOCOLOR}" + echo "" + read -p " Enter target directory [./]: " TARGET_DIR + + if [[ -z "$TARGET_DIR" ]]; then + TARGET_DIR="./" + fi + + if [[ ! -d "$TARGET_DIR" ]]; then + print_error "Directory '$TARGET_DIR' does not exist. Exiting." + exit 1 + fi + + print_success "Target directory: $TARGET_DIR" + echo "" + + # ---- Check for existing flake ---- + if [[ -f "$TARGET_DIR/flake.nix" ]]; then + echo "" + print_error "A flake.nix already exists in '$TARGET_DIR'!" + echo "" + read -p " Would you like to just place the .envrc instead? [y/N]: " ENVRC_ONLY + ENVRC_ONLY="''${ENVRC_ONLY:-N}" + + if [[ "$ENVRC_ONLY" =~ ^[Yy]$ ]]; then + TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" + echo "" + print_info "Placing .envrc only..." + if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then + echo "use flake" >> "$TARGET_ABS/.envrc" + print_success ".envrc created at $TARGET_ABS/" + else + print_info ".envrc already contains 'use flake', nothing to do." + fi + + echo "" + print_info "Next steps:" + echo " cd $TARGET_DIR" + echo " direnv allow" + echo "" + exit 0 + else + print_info "Aborting. No files were changed." + exit 1 + fi + fi + + # ---- Step 2: Project Name ---- + echo -e "''${YELLOW}Step 2/5: Project Name''${NOCOLOR}" + echo "" + read -p " Enter development environment name [devShell]: " PROJECT_NAME + + if [[ -z "$PROJECT_NAME" ]]; then + PROJECT_NAME="devShell" + fi + + print_success "Project name: $PROJECT_NAME" + echo "" + + # ---- Step 3: Select template ---- + echo -e "''${YELLOW}Step 3/5: Select Template''${NOCOLOR}" + echo "" + echo " 1) Empty - Basic shell, add packages yourself" + echo " 2) Python - Python 3, pip, venv setup" + echo " 3) Node.js - Node.js, npm" + echo " 4) Rust - Rustc, cargo, rust-analyzer" + echo " 5) Go - Go, gopls, golangci-lint" + echo " 6) C/C++ - GCC, CMake, GDB, pkg-config" + echo " 7) Java - JDK 21, Maven" + echo "" + + read -p " Select template [1-7] (default: 1): " TEMPLATE_CHOICE + + case "$TEMPLATE_CHOICE" in + 2) + TEMPLATE="python3" + PACKAGES="python3 python3Packages.pip python3Packages.virtualenv" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA=" + if [ ! -d .venv ]; then + echo 'Creating Python virtual environment...' + python3 -m venv .venv + fi + source .venv/bin/activate + echo 'Python venv activated.' + " + ;; + 3) + TEMPLATE="node" + PACKAGES="nodejs_22" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA='echo "Node.js $(node --version) ready."' + ;; + 4) + TEMPLATE="rust" + PACKAGES="rustc cargo rust-analyzer" + BUILD_INPUTS="openssl pkg-config" + SHELL_HOOK_EXTRA='echo "Rust $(rustc --version) ready."' + ;; + 5) + TEMPLATE="go" + PACKAGES="go gopls golangci-lint" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA=' + export GOPATH="$PWD/.gopath" + export PATH="$GOPATH/bin:$PATH" + mkdir -p "$GOPATH" + echo "Go $(go version) ready. GOPATH: $GOPATH" + ' + ;; + 6) + TEMPLATE="cpp" + PACKAGES="gcc gdb cmake gnumake" + BUILD_INPUTS="pkg-config" + SHELL_HOOK_EXTRA='echo "GCC $(gcc --version | head -1) ready."' + ;; + 7) + TEMPLATE="java" + PACKAGES="jdk21 maven" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA='echo "Java $(java --version | head -1) ready."' + ;; + *) + TEMPLATE="empty" + PACKAGES="" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA="" + ;; + esac + + print_success "Selected template: $TEMPLATE" + echo "" + + # ---- Step 4: Copy and modify flake ---- + echo -e "''${YELLOW}Step 4/5: Generating flake.nix''${NOCOLOR}" + echo "" + + cp "$SOURCE_FLAKE" "$TARGET_DIR/flake.nix" + print_success "Copied flake.nix to $TARGET_DIR/" + + sed -i "s|name = \"replaceNameHere\";|name = \"''${PROJECT_NAME}\";|" "$TARGET_DIR/flake.nix" + print_success "Set project name: $PROJECT_NAME" + + if [[ -n "$PACKAGES" ]]; then + sed -i "s|# replacePackagesHere|$PACKAGES|" "$TARGET_DIR/flake.nix" + print_success "Added packages: $PACKAGES" + fi + + if [[ -n "$BUILD_INPUTS" ]]; then + BUILD_INPUTS_LINE="buildInputs = with pkgs; [ $BUILD_INPUTS ];" + sed -i "s|# replaceBuildInputsHere|$BUILD_INPUTS_LINE|" "$TARGET_DIR/flake.nix" + print_success "Added build inputs: $BUILD_INPUTS" + fi + + if [[ -n "$SHELL_HOOK_EXTRA" ]]; then + awk -v hook="$SHELL_HOOK_EXTRA" '/# replaceShellHookHere/ { print hook; next } { print }' \ + "$TARGET_DIR/flake.nix" > "$TARGET_DIR/flake.nix.tmp" \ + && mv "$TARGET_DIR/flake.nix.tmp" "$TARGET_DIR/flake.nix" + print_success "Added template-specific shell hook" + fi + + # ---- Step 5: Git Integration & .envrc ---- + echo -e "''${YELLOW}Step 5/5: Git Integration''${NOCOLOR}" + echo "" + + TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" + + echo "Creating .envrc for direnv to automatically work!" + if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then + echo "use flake" >> "$TARGET_ABS/.envrc" + print_success ".envrc created at $TARGET_ABS/" + else + print_info ".envrc already contains 'use flake', skipping." + fi + + if git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then + echo " A Git repository was detected at:" + echo " $(git -C "$TARGET_ABS" rev-parse --show-toplevel)" + echo "" + + _add_local_excludes "$TARGET_ABS" + echo "" + + echo " Nix flakes require flake.nix (and flake.lock) to be Git-tracked." + echo " This script can stage them as 'assume-unchanged' so Nix sees them," + echo " but they will NEVER be committed or show up in git status." + echo "" + read -p " Set up flake files as tracked-but-invisible to Git? [y/N]: " GIT_CHOICE + + GIT_CHOICE="''${GIT_CHOICE:-N}" + + if [[ "$GIT_CHOICE" =~ ^[Yy]$ ]]; then + cat > "$TARGET_ABS/flake.lock" << 'FLAKE_LOCK_EOF' +{ +"nodes": { + "root": {} +}, +"root": "root", +"version": 7 +} +FLAKE_LOCK_EOF + print_success "flake.lock ready (Nix will populate it on first run if empty)" + + git -C "$TARGET_ABS" add --intent-to-add flake.nix flake.lock + git -C "$TARGET_ABS" update-index --assume-unchanged flake.nix flake.lock + print_success "flake.nix + flake.lock → tracked (assume-unchanged)" + else + print_info "Skipped. You can do this manually later:" + echo "" + echo " git -C \"$TARGET_ABS\" add --intent-to-add flake.nix flake.lock" + echo " git -C \"$TARGET_ABS\" update-index --assume-unchanged flake.nix flake.lock" + echo "" + fi + else + print_info "No Git repository detected — skipping Git integration." + echo "" + fi + + # ---- Done ---- + echo -e "''${GREEN}==============================================''${NOCOLOR}" + echo -e "''${GREEN} Done! Your flake is ready at:''${NOCOLOR}" + echo -e "''${GREEN} $TARGET_DIR/flake.nix''${NOCOLOR}" + echo -e "''${GREEN}==============================================''${NOCOLOR}" + echo "" + print_info "Next steps:" + echo " cd $TARGET_DIR" + echo " direnv allow # Trust the .envrc so direnv auto-activates" + echo " nix develop # Or just: cd out and back in" + echo "" + ;; + + esac + ;; + ''; +} diff --git a/modules/ncli/commands/git.nix b/modules/ncli/commands/git.nix new file mode 100644 index 0000000..27406c5 --- /dev/null +++ b/modules/ncli/commands/git.nix @@ -0,0 +1,48 @@ +# ncli/commands/git.nix — commit, push, pull, status, format +lib: + +let + inherit (lib) project; +in +{ + commit_case = '' + cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } + if [ "$#" -lt 2 ]; then + read -p "Enter commit message: " commit_msg + else + shift + commit_msg="$*" + fi + + if [ -z "$commit_msg" ]; then + echo "Error: Commit message cannot be empty" >&2 + exit 1 + fi + + git add -A && git commit -m "$commit_msg" + ;; + ''; + + push_case = '' + cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } + git push origin $(git branch --show-current) + ;; + ''; + + pull_case = '' + cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } + git pull origin $(git branch --show-current) + ;; + ''; + + status_case = '' + cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } + git status + ;; + ''; + + format_case = '' + nix fmt . + ;; + ''; +} diff --git a/modules/ncli/commands/maintenance.nix b/modules/ncli/commands/maintenance.nix new file mode 100644 index 0000000..e2ddf04 --- /dev/null +++ b/modules/ncli/commands/maintenance.nix @@ -0,0 +1,83 @@ +# ncli/commands/maintenance.nix — cleanup, diag, list-gens, trim, home-backups +lib: + +let + inherit (lib) project; +in +{ + cleanup_case = '' + 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 + 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 + nh clean all -k "$keep_count" -v + else + echo "Cleanup cancelled." + fi + fi + + LOG_DIR="$HOME/ncli-cleanup-logs" + mkdir -p "$LOG_DIR" + LOG_FILE="$LOG_DIR/ncli-cleanup-$(date +%Y-%m-%d_%H-%M-%S).log" + echo "Cleaning up old log files..." >> "$LOG_FILE" + find "$LOG_DIR" -type f -mtime +3 -name "*.log" -delete >> "$LOG_FILE" 2>&1 + echo "Cleanup process logged to $LOG_FILE" + ;; + ''; + + diag_case = '' + echo "Generating system diagnostic report..." + { + echo "=== NixOS System Diagnostic Report ===" + echo "Generated: $(date)" + echo "" + echo "=== System Information ===" + inxi --full 2>/dev/null || echo "inxi not available" + echo "" + echo "=== Git Status ===" + cd "$HOME/${project}" 2>/dev/null && git status 2>/dev/null || echo "Git status not available" + echo "" + } > "$HOME/diag.txt" + echo "Diagnostic report saved to $HOME/diag.txt" + ;; + ''; + + list_gens_case = '' + echo "--- User Generations ---" + nix-env --list-generations | cat || echo "Could not list user generations." + echo "" + echo "--- System Generations ---" + nix profile history --profile /nix/var/nix/profiles/system | cat || echo "Could not list system generations." + ;; + ''; + + trim_case = '' + echo "Running 'sudo fstrim -v /' may take a few minutes and impact system performance." + read -p "Enter to run now or enter to exit (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Running fstrim..." + sudo fstrim -v / + echo "fstrim complete." + else + echo "Trim operation cancelled." + fi + ;; + ''; + + home_backups_case = '' + ls -a ~ | grep backup + ;; + ''; +} diff --git a/modules/ncli/commands/rebuild.nix b/modules/ncli/commands/rebuild.nix new file mode 100644 index 0000000..8531806 --- /dev/null +++ b/modules/ncli/commands/rebuild.nix @@ -0,0 +1,132 @@ +# ncli/commands/rebuild.nix — Rebuild + Update logic +lib: + +let + inherit (lib) host project handle_backups handle_build_error; +in +{ + # Shared rebuild snippet used by both `rebuild` and `update` + rebuild_logic = '' + handle_backups + geno=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') + cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } + + current="" + if [ -f /etc/nixos-tags ]; then + current=$(cat /etc/nixos-tags) + fi + ''; + + rebuild_case = '' + ${rebuild_logic} + echo -e "Starting NixOS rebuild for current host: ${host} on generation: $YELLOW$geno$NOCOLOR" + + sudo nixos-rebuild switch --flake . ; _rebuild_exit=$? + if [ "$_rebuild_exit" -eq 0 ]; then + echo "✓ Rebuild finished successfully for ${host}" + + if [ -n "$current" ]; then + sudo /run/current-system/specialisation/"$current"/bin/switch-to-configuration test + else + echo "No specialization tag found, staying on default system." + fi + + genn=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') + echo -e "Running on new generation: $YELLOW $geno $NOCOLOR-> $GREEN$genn$NOCOLOR" + else + ( exit "$_rebuild_exit" ); handle_build_error + echo "✗ Rebuild failed for ${host}" >&2 + exit 1 + fi + ;; + ''; + + update_case = '' + ${rebuild_logic} + echo -e "Updating flake and rebuilding system for current host: ${host} on generation: $YELLOW$geno$NOCOLOR" + + # --- Selective flake update --- + read -rp "Update [a]ll inputs or [s]elect manually? (a/s): " choice + + case "$choice" in + a|A) + echo "Updating all inputs..." + if nix flake update --flake .; then + echo "✓ Flake updated successfully" + else + echo "✗ Flake update failed" >&2 + exit 1 + fi + ;; + s|S) + echo "Fetching available updates (this may take a moment)..." + TEMP_LOCK=$(mktemp) + trap 'rm -f "$TEMP_LOCK"' EXIT + + nix flake update --output-lock-file "$TEMP_LOCK" --flake . 2>/dev/null + + outdated=$(jq -r --slurpfile new "$TEMP_LOCK" ' + .nodes as $old | + $new[0].nodes as $newn | + ($old | keys[]) | + select(. != "root") | + select( + ($old[.].locked.lastModified // 0) != + ($newn[.].locked.lastModified // 0) + ) + ' flake.lock) + + if [[ -z "$outdated" ]]; then + echo "✓ All inputs are already up to date, skipping flake update." + else + echo + echo "Updates available for:" + printf '%s\n' "$outdated" + echo + echo "Tab to select, Enter to update, Esc to cancel." + selected=$(printf '%s\n' "$outdated" | fzf --multi) || { + echo "No inputs selected, skipping flake update." + selected="" + } + if [[ -n "$selected" ]]; then + if nix flake update --flake . $selected; then + echo "✓ Flake updated successfully" + else + echo "✗ Flake update failed" >&2 + exit 1 + fi + fi + fi + ;; + *) + echo "Invalid choice, skipping flake update." + ;; + esac + # --- End selective flake update --- + + if [ -n "$current" ]; then + echo "Rebuilding system... Current specialization: $current" + else + echo "Rebuilding system... Staying on current specialization" + fi + + sudo nixos-rebuild switch --flake . ; _rebuild_exit=$? + if [ "$_rebuild_exit" -eq 0 ]; then + echo "✓ Update and rebuild finished successfully for ${host}" + + if [ -n "$current" ]; then + sudo /run/current-system/specialisation/"$current"/bin/switch-to-configuration test + else + echo "No specialization tag found, staying on default system." + fi + + genn=$(sudo nix-env --list-generations --profile /nix/var/nix/profiles/system | grep current | awk '{print $1}') + echo -e "Running on new generation: $YELLOW $geno $NOCOLOR-> $GREEN$genn$NOCOLOR" + else + ( exit "$_rebuild_exit" ); handle_build_error + echo "✗ Update and rebuild failed for ${host}" >&2 + exit 1 + fi + ;; + ''; +} diff --git a/modules/ncli/commands/switch.nix b/modules/ncli/commands/switch.nix new file mode 100644 index 0000000..3ef909d --- /dev/null +++ b/modules/ncli/commands/switch.nix @@ -0,0 +1,40 @@ +# ncli/commands/switch.nix — Specialization switching +lib: + +{}: + +{ + switch_case = '' + current="" + if [ -f /etc/nixos-tags ]; then + current=$(cat /etc/nixos-tags) + fi + + if [ "$#" -ge 2 ]; then + spec_name="$2" + if [ -n "$current" ]; then + echo "Already on specialization: $current. Cannot switch directly to '$spec_name'. Please reboot or return to default first." + else + if [ -d "/run/current-system/specialisation/$spec_name" ]; then + echo "Switching to specialization: $spec_name" + sudo /run/current-system/specialisation/"$spec_name"/bin/switch-to-configuration test + else + echo "Error: Specialization '$spec_name' not found." + echo "Available specializations:" + ls /run/current-system/specialisation/ + fi + fi + else + if [ -n "$current" ]; then + echo "Already on a specialization: $current. To switch, please reboot, or use 'sudo nixos-rebuild switch --flake .' to get back to default, and then switch after." + else + specs=$(ls /run/current-system/specialisation/) + echo "Specializations available:" + echo "$specs" + echo "" + echo "To switch to a specialization, run: 'ncli switch '" + fi + fi + ;; + ''; +} diff --git a/modules/ncli/default.nix b/modules/ncli/default.nix new file mode 100644 index 0000000..cb4ead6 --- /dev/null +++ b/modules/ncli/default.nix @@ -0,0 +1,12 @@ +# ncli/default.nix — Entry point +# Usage in your flake: +# ncli = import ./ncli { inherit pkgs host project; }; +# environment.systemPackages = [ ncli ]; +{ + pkgs, + host, + backupFiles ? [".config/mimeapps.list.backup"], + project, + ... +}: +import ./builder.nix {inherit pkgs host backupFiles project;} diff --git a/modules/ncli/lib.nix b/modules/ncli/lib.nix new file mode 100644 index 0000000..e58dc79 --- /dev/null +++ b/modules/ncli/lib.nix @@ -0,0 +1,76 @@ +# ncli/lib.nix — Shared helpers and utilities +{ pkgs, host, project, backupFiles }: + +let + backupFilesString = pkgs.lib.strings.concatStringsSep " " backupFiles; +in +{ + # --- Injected config --- + inherit pkgs host project backupFiles backupFilesString; + + # --- Color / banner source --- + colorsSource = "$HOME/${project}/other/colors.sh"; + + # --- Backup helper --- + 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 + ''; + + # --- OOM / build error handler --- + handle_build_error = '' + local exit_code=$? + if [ "$exit_code" -eq 137 ]; then + echo "" + echo -e "''${RED}╔══════════════════════════════════════════════════════════╗''${NOCOLOR}" + echo -e "''${RED}║ BUILD KILLED — Signal 9 (SIGKILL) detected ║''${NOCOLOR}" + echo -e "''${RED}╚══════════════════════════════════════════════════════════╝''${NOCOLOR}" + echo "" + echo -e "''${YELLOW}What happened:''${NOCOLOR}" + echo " The build process was forcefully terminated by the OS." + echo " This is almost always the Linux kernel OOM killer running out" + echo " of RAM + swap during Nix evaluation or compilation." + echo "" + echo -e "''${YELLOW}Suggested fixes (try in order):''${NOCOLOR}" + echo "" + echo -e " ''${GREEN}1. Reduce parallel jobs''${NOCOLOR} (lowest RAM usage):" + echo " sudo nixos-rebuild switch --flake . --max-jobs 1 --cores 1" + echo "" + echo -e " ''${GREEN}2. Confirm OOM killer fired:''${NOCOLOR}" + echo " journalctl -k --since '5 minutes ago' | grep -i oom" + echo "" + fi + ''; + + # --- Print helpers (used by dev init) --- + print_header = '' + echo "" + echo -e "''${BLUE}==============================================''${NOCOLOR}" + echo -e "''${BLUE} Nix Flake Development Environment Initializer''${NOCOLOR}" + echo -e "''${BLUE}==============================================''${NOCOLOR}" + echo "" + ''; + + print_success = '' + echo -e "''${GREEN}[OK]''${NOCOLOR} $1" + ''; + + print_error = '' + echo -e "''${RED}[ERR]''${NOCOLOR} $1" + ''; + + print_info = '' + echo -e "''${YELLOW}[->]''${NOCOLOR} $1" + ''; +} From 134fc441a540d07290d34fe025e11e6902a24d13 Mon Sep 17 00:00:00 2001 From: Cookiez Date: Fri, 29 May 2026 09:29:31 +0200 Subject: [PATCH 2/3] - Simplified command cases by removing unnecessary semicolons. - Improved error handling in rebuild.nix for clarity and consistency. --- modules/ncli/builder.nix | 59 +-- modules/ncli/commands/dev.nix | 675 +++++++++++++------------- modules/ncli/commands/git.nix | 12 +- modules/ncli/commands/maintenance.nix | 12 +- modules/ncli/commands/rebuild.nix | 19 +- modules/ncli/commands/switch.nix | 7 +- modules/ncli/default.nix | 4 - modules/ncli/lib.nix | 94 ++-- 8 files changed, 435 insertions(+), 447 deletions(-) diff --git a/modules/ncli/builder.nix b/modules/ncli/builder.nix index bf77664..8ea887b 100644 --- a/modules/ncli/builder.nix +++ b/modules/ncli/builder.nix @@ -18,33 +18,35 @@ # --- Help text --- print_help = '' - echo "NixOS CLI Utility -- version 2.1.3" - echo "" - echo "Usage: ncli [command]" - echo "" - echo "System Commands:" - echo " rebuild - Rebuild the NixOS system configuration." - echo " update - Update the flake and rebuild the system." - echo "" - echo "Maintenance Commands:" - echo " cleanup - Clean up old system generations. Can specify a number to keep." - echo " diag - Create a system diagnostic report (saves to ~/diag.txt)." - echo " list-gens - List user and system generations." - echo " trim - Trim filesystems to improve SSD performance." - echo "" - echo "Git Commands:" - echo " commit [msg] - Add all changes and commit with message." - echo " push - Push changes to origin." - echo " pull - Pull latest changes from origin." - echo " status - Show git status." - echo "" - echo "Development Commands:" - echo " dev - Initialize a Nix development environment (flake.nix + direnv)." - echo " dev track - Remove assume-unchanged flag from flake files." - echo " dev untrack - Mark flake files as assume-unchanged in a directory." - echo "" - echo " help - Show this help message." - echo "" + print_help() { + echo "NixOS CLI Utility -- version 2.1.3" + echo "" + echo "Usage: ncli [command]" + echo "" + echo "System Commands:" + echo " rebuild - Rebuild the NixOS system configuration." + echo " update - Update the flake and rebuild the system." + echo "" + echo "Maintenance Commands:" + echo " cleanup - Clean up old system generations. Can specify a number to keep." + echo " diag - Create a system diagnostic report (saves to ~/diag.txt)." + echo " list-gens - List user and system generations." + echo " trim - Trim filesystems to improve SSD performance." + echo "" + echo "Git Commands:" + echo " commit [msg] - Add all changes and commit with message." + echo " push - Push changes to origin." + echo " pull - Pull latest changes from origin." + echo " status - Show git status." + echo "" + echo "Development Commands:" + echo " dev - Initialize a Nix development environment (flake.nix + direnv)." + echo " dev track - Remove assume-unchanged flag from flake files." + echo " dev untrack - Mark flake files as assume-unchanged in a directory." + echo "" + echo " help - Show this help message." + echo "" + } ''; in pkgs.writeShellScriptBin "ncli" '' @@ -70,6 +72,7 @@ in ${lib.print_success} ${lib.print_error} ${lib.print_info} + ${print_help} # --- Main Logic --- if [ "$#" -eq 0 ]; then @@ -122,7 +125,7 @@ in ${dev.dev_case} ;; help) - ${print_help} + print_help ;; *) echo "Error: Invalid command '$1'" >&2 diff --git a/modules/ncli/commands/dev.nix b/modules/ncli/commands/dev.nix index 8cf9e62..0916676 100644 --- a/modules/ncli/commands/dev.nix +++ b/modules/ncli/commands/dev.nix @@ -1,367 +1,364 @@ # ncli/commands/dev.nix — dev init / track / untrack -lib: - -{ +lib: { dev_case = '' - # Only flake files need assume-unchanged (Nix requires them in the index). - # .envrc and .direnv are handled via .git/info/exclude instead. - DEV_FILES=(flake.nix flake.lock) - DIRENV_LOCAL_FILES=(.envrc .direnv) + # Only flake files need assume-unchanged (Nix requires them in the index). + # .envrc and .direnv are handled via .git/info/exclude instead. + DEV_FILES=(flake.nix flake.lock) + DIRENV_LOCAL_FILES=(.envrc .direnv) - _dev_resolve_dir() { - read -rp " Enter target directory [./]: " TARGET_DIR - TARGET_DIR="''${TARGET_DIR:-./}" - if [[ ! -d "$TARGET_DIR" ]]; then - print_error "Directory '$TARGET_DIR' does not exist." - exit 1 - fi - TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" - if ! git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then - print_error "No Git repository found at '$TARGET_ABS'." - exit 1 - fi - } - - _add_local_excludes() { - local repo_abs="$1" - local git_dir exclude_file - git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" - exclude_file="$git_dir/info/exclude" - mkdir -p "$(dirname "$exclude_file")" - touch "$exclude_file" - - for entry in "''${DIRENV_LOCAL_FILES[@]}"; do - if ! grep -qxF "$entry" "$exclude_file" 2>/dev/null; then - echo "$entry" >> "$exclude_file" - print_success "$entry → ignored via .git/info/exclude (local only)" - else - print_info "$entry already in .git/info/exclude, skipping." - fi - done - } - - _remove_local_excludes() { - local repo_abs="$1" - local git_dir exclude_file tmp_file - git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" - exclude_file="$git_dir/info/exclude" - - [[ -f "$exclude_file" ]] || return 0 - - tmp_file="$(mktemp)" - cp "$exclude_file" "$tmp_file" - - for entry in "''${DIRENV_LOCAL_FILES[@]}"; do - if grep -qxF "$entry" "$tmp_file" 2>/dev/null; then - grep -vxF "$entry" "$tmp_file" > "''${tmp_file}.new" || true - mv "''${tmp_file}.new" "$tmp_file" - print_success "$entry → removed from .git/info/exclude" - else - print_info "$entry not present in .git/info/exclude, skipping." - fi - done - - mv "$tmp_file" "$exclude_file" - } - - case "''${2:-init}" in - - untrack) - echo "" - echo -e "''${BLUE}--- Dev: Track Files (assume-unchanged) ---''${NOCOLOR}" - echo "" - _dev_resolve_dir - - acted=false - for f in "''${DEV_FILES[@]}"; do - full="$TARGET_ABS/$f" - if [[ ! -f "$full" ]]; then - print_info "$f not found in $TARGET_ABS — skipping." - continue - fi - if ! git -C "$TARGET_ABS" ls-files --error-unmatch "$f" > /dev/null 2>&1; then - git -C "$TARGET_ABS" add --intent-to-add "$f" - fi - git -C "$TARGET_ABS" update-index --assume-unchanged "$f" - print_success "$f → untracked (assume-unchanged)" - acted=true - done - - _add_local_excludes "$TARGET_ABS" - - if ! $acted; then - print_info "No dev files found in '$TARGET_ABS' to track." - fi - ;; - - track) - echo "" - echo -e "''${BLUE}--- Dev: Untrack Files (remove assume-unchanged) ---''${NOCOLOR}" - echo "" - _dev_resolve_dir - - acted=false - for f in "''${DEV_FILES[@]}"; do - if git -C "$TARGET_ABS" ls-files -v "$f" 2>/dev/null | grep -q "^h "; then - git -C "$TARGET_ABS" update-index --no-assume-unchanged "$f" - print_success "$f → tracked (assume-unchanged removed)" - acted=true - else - print_info "$f is not marked assume-unchanged — skipping." - fi - done - - _remove_local_excludes "$TARGET_ABS" - - if ! $acted; then - print_info "No files in '$TARGET_ABS' had the assume-unchanged bit set." - fi - ;; - - init|*) - SOURCE_FLAKE="$HOME/${lib.project}/other/dev-template.nix" - - if [[ ! -f "$SOURCE_FLAKE" ]]; then - print_error "Source flake template not found: $SOURCE_FLAKE" - echo "Please ensure dev-template.nix exists in $HOME/${lib.project}/other/" + _dev_resolve_dir() { + read -rp " Enter target directory [./]: " TARGET_DIR + TARGET_DIR="''${TARGET_DIR:-./}" + if [[ ! -d "$TARGET_DIR" ]]; then + print_error "Directory '$TARGET_DIR' does not exist." exit 1 - fi - - print_header - - # ---- Step 1: Target Directory ---- - echo -e "''${YELLOW}Step 1/5: Target Directory''${NOCOLOR}" - echo "" - read -p " Enter target directory [./]: " TARGET_DIR - - if [[ -z "$TARGET_DIR" ]]; then - TARGET_DIR="./" - fi - - if [[ ! -d "$TARGET_DIR" ]]; then - print_error "Directory '$TARGET_DIR' does not exist. Exiting." + fi + TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" + if ! git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then + print_error "No Git repository found at '$TARGET_ABS'." exit 1 - fi + fi + } - print_success "Target directory: $TARGET_DIR" - echo "" + _add_local_excludes() { + local repo_abs="$1" + local git_dir exclude_file + git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" + exclude_file="$git_dir/info/exclude" + mkdir -p "$(dirname "$exclude_file")" + touch "$exclude_file" - # ---- Check for existing flake ---- - if [[ -f "$TARGET_DIR/flake.nix" ]]; then - echo "" - print_error "A flake.nix already exists in '$TARGET_DIR'!" - echo "" - read -p " Would you like to just place the .envrc instead? [y/N]: " ENVRC_ONLY - ENVRC_ONLY="''${ENVRC_ONLY:-N}" - - if [[ "$ENVRC_ONLY" =~ ^[Yy]$ ]]; then - TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" - echo "" - print_info "Placing .envrc only..." - if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then - echo "use flake" >> "$TARGET_ABS/.envrc" - print_success ".envrc created at $TARGET_ABS/" - else - print_info ".envrc already contains 'use flake', nothing to do." - fi - - echo "" - print_info "Next steps:" - echo " cd $TARGET_DIR" - echo " direnv allow" - echo "" - exit 0 + for entry in "''${DIRENV_LOCAL_FILES[@]}"; do + if ! grep -qxF "$entry" "$exclude_file" 2>/dev/null; then + echo "$entry" >> "$exclude_file" + print_success "$entry → ignored via .git/info/exclude (local only)" else - print_info "Aborting. No files were changed." - exit 1 + print_info "$entry already in .git/info/exclude, skipping." fi - fi + done + } - # ---- Step 2: Project Name ---- - echo -e "''${YELLOW}Step 2/5: Project Name''${NOCOLOR}" - echo "" - read -p " Enter development environment name [devShell]: " PROJECT_NAME + _remove_local_excludes() { + local repo_abs="$1" + local git_dir exclude_file tmp_file + git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" + exclude_file="$git_dir/info/exclude" - if [[ -z "$PROJECT_NAME" ]]; then - PROJECT_NAME="devShell" - fi + [[ -f "$exclude_file" ]] || return 0 - print_success "Project name: $PROJECT_NAME" - echo "" + tmp_file="$(mktemp)" + cp "$exclude_file" "$tmp_file" - # ---- Step 3: Select template ---- - echo -e "''${YELLOW}Step 3/5: Select Template''${NOCOLOR}" - echo "" - echo " 1) Empty - Basic shell, add packages yourself" - echo " 2) Python - Python 3, pip, venv setup" - echo " 3) Node.js - Node.js, npm" - echo " 4) Rust - Rustc, cargo, rust-analyzer" - echo " 5) Go - Go, gopls, golangci-lint" - echo " 6) C/C++ - GCC, CMake, GDB, pkg-config" - echo " 7) Java - JDK 21, Maven" - echo "" + for entry in "''${DIRENV_LOCAL_FILES[@]}"; do + if grep -qxF "$entry" "$tmp_file" 2>/dev/null; then + grep -vxF "$entry" "$tmp_file" > "''${tmp_file}.new" || true + mv "''${tmp_file}.new" "$tmp_file" + print_success "$entry → removed from .git/info/exclude" + else + print_info "$entry not present in .git/info/exclude, skipping." + fi + done - read -p " Select template [1-7] (default: 1): " TEMPLATE_CHOICE + mv "$tmp_file" "$exclude_file" + } - case "$TEMPLATE_CHOICE" in - 2) - TEMPLATE="python3" - PACKAGES="python3 python3Packages.pip python3Packages.virtualenv" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA=" - if [ ! -d .venv ]; then - echo 'Creating Python virtual environment...' - python3 -m venv .venv - fi - source .venv/bin/activate - echo 'Python venv activated.' - " - ;; - 3) - TEMPLATE="node" - PACKAGES="nodejs_22" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA='echo "Node.js $(node --version) ready."' - ;; - 4) - TEMPLATE="rust" - PACKAGES="rustc cargo rust-analyzer" - BUILD_INPUTS="openssl pkg-config" - SHELL_HOOK_EXTRA='echo "Rust $(rustc --version) ready."' - ;; - 5) - TEMPLATE="go" - PACKAGES="go gopls golangci-lint" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA=' - export GOPATH="$PWD/.gopath" - export PATH="$GOPATH/bin:$PATH" - mkdir -p "$GOPATH" - echo "Go $(go version) ready. GOPATH: $GOPATH" - ' - ;; - 6) - TEMPLATE="cpp" - PACKAGES="gcc gdb cmake gnumake" - BUILD_INPUTS="pkg-config" - SHELL_HOOK_EXTRA='echo "GCC $(gcc --version | head -1) ready."' - ;; - 7) - TEMPLATE="java" - PACKAGES="jdk21 maven" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA='echo "Java $(java --version | head -1) ready."' - ;; - *) - TEMPLATE="empty" - PACKAGES="" - BUILD_INPUTS="" - SHELL_HOOK_EXTRA="" - ;; - esac + case "''${2:-init}" in - print_success "Selected template: $TEMPLATE" - echo "" - - # ---- Step 4: Copy and modify flake ---- - echo -e "''${YELLOW}Step 4/5: Generating flake.nix''${NOCOLOR}" - echo "" - - cp "$SOURCE_FLAKE" "$TARGET_DIR/flake.nix" - print_success "Copied flake.nix to $TARGET_DIR/" - - sed -i "s|name = \"replaceNameHere\";|name = \"''${PROJECT_NAME}\";|" "$TARGET_DIR/flake.nix" - print_success "Set project name: $PROJECT_NAME" - - if [[ -n "$PACKAGES" ]]; then - sed -i "s|# replacePackagesHere|$PACKAGES|" "$TARGET_DIR/flake.nix" - print_success "Added packages: $PACKAGES" - fi - - if [[ -n "$BUILD_INPUTS" ]]; then - BUILD_INPUTS_LINE="buildInputs = with pkgs; [ $BUILD_INPUTS ];" - sed -i "s|# replaceBuildInputsHere|$BUILD_INPUTS_LINE|" "$TARGET_DIR/flake.nix" - print_success "Added build inputs: $BUILD_INPUTS" - fi - - if [[ -n "$SHELL_HOOK_EXTRA" ]]; then - awk -v hook="$SHELL_HOOK_EXTRA" '/# replaceShellHookHere/ { print hook; next } { print }' \ - "$TARGET_DIR/flake.nix" > "$TARGET_DIR/flake.nix.tmp" \ - && mv "$TARGET_DIR/flake.nix.tmp" "$TARGET_DIR/flake.nix" - print_success "Added template-specific shell hook" - fi - - # ---- Step 5: Git Integration & .envrc ---- - echo -e "''${YELLOW}Step 5/5: Git Integration''${NOCOLOR}" - echo "" - - TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" - - echo "Creating .envrc for direnv to automatically work!" - if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then - echo "use flake" >> "$TARGET_ABS/.envrc" - print_success ".envrc created at $TARGET_ABS/" - else - print_info ".envrc already contains 'use flake', skipping." - fi - - if git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then - echo " A Git repository was detected at:" - echo " $(git -C "$TARGET_ABS" rev-parse --show-toplevel)" + untrack) echo "" + echo -e "''${BLUE}--- Dev: Track Files (assume-unchanged) ---''${NOCOLOR}" + echo "" + _dev_resolve_dir + + acted=false + for f in "''${DEV_FILES[@]}"; do + full="$TARGET_ABS/$f" + if [[ ! -f "$full" ]]; then + print_info "$f not found in $TARGET_ABS — skipping." + continue + fi + if ! git -C "$TARGET_ABS" ls-files --error-unmatch "$f" > /dev/null 2>&1; then + git -C "$TARGET_ABS" add --intent-to-add "$f" + fi + git -C "$TARGET_ABS" update-index --assume-unchanged "$f" + print_success "$f → untracked (assume-unchanged)" + acted=true + done _add_local_excludes "$TARGET_ABS" + + if ! $acted; then + print_info "No dev files found in '$TARGET_ABS' to track." + fi + ;; + + track) + echo "" + echo -e "''${BLUE}--- Dev: Untrack Files (remove assume-unchanged) ---''${NOCOLOR}" + echo "" + _dev_resolve_dir + + acted=false + for f in "''${DEV_FILES[@]}"; do + if git -C "$TARGET_ABS" ls-files -v "$f" 2>/dev/null | grep -q "^h "; then + git -C "$TARGET_ABS" update-index --no-assume-unchanged "$f" + print_success "$f → tracked (assume-unchanged removed)" + acted=true + else + print_info "$f is not marked assume-unchanged — skipping." + fi + done + + _remove_local_excludes "$TARGET_ABS" + + if ! $acted; then + print_info "No files in '$TARGET_ABS' had the assume-unchanged bit set." + fi + ;; + + init|*) + SOURCE_FLAKE="$HOME/${lib.project}/other/dev-template.nix" + + if [[ ! -f "$SOURCE_FLAKE" ]]; then + print_error "Source flake template not found: $SOURCE_FLAKE" + echo "Please ensure dev-template.nix exists in $HOME/${lib.project}/other/" + exit 1 + fi + + print_header + + # ---- Step 1: Target Directory ---- + echo -e "''${YELLOW}Step 1/5: Target Directory''${NOCOLOR}" + echo "" + read -p " Enter target directory [./]: " TARGET_DIR + + if [[ -z "$TARGET_DIR" ]]; then + TARGET_DIR="./" + fi + + if [[ ! -d "$TARGET_DIR" ]]; then + print_error "Directory '$TARGET_DIR' does not exist. Exiting." + exit 1 + fi + + print_success "Target directory: $TARGET_DIR" echo "" - echo " Nix flakes require flake.nix (and flake.lock) to be Git-tracked." - echo " This script can stage them as 'assume-unchanged' so Nix sees them," - echo " but they will NEVER be committed or show up in git status." - echo "" - read -p " Set up flake files as tracked-but-invisible to Git? [y/N]: " GIT_CHOICE - - GIT_CHOICE="''${GIT_CHOICE:-N}" - - if [[ "$GIT_CHOICE" =~ ^[Yy]$ ]]; then - cat > "$TARGET_ABS/flake.lock" << 'FLAKE_LOCK_EOF' -{ -"nodes": { - "root": {} -}, -"root": "root", -"version": 7 -} -FLAKE_LOCK_EOF - print_success "flake.lock ready (Nix will populate it on first run if empty)" - - git -C "$TARGET_ABS" add --intent-to-add flake.nix flake.lock - git -C "$TARGET_ABS" update-index --assume-unchanged flake.nix flake.lock - print_success "flake.nix + flake.lock → tracked (assume-unchanged)" - else - print_info "Skipped. You can do this manually later:" + # ---- Check for existing flake ---- + if [[ -f "$TARGET_DIR/flake.nix" ]]; then echo "" - echo " git -C \"$TARGET_ABS\" add --intent-to-add flake.nix flake.lock" - echo " git -C \"$TARGET_ABS\" update-index --assume-unchanged flake.nix flake.lock" + print_error "A flake.nix already exists in '$TARGET_DIR'!" + echo "" + read -p " Would you like to just place the .envrc instead? [y/N]: " ENVRC_ONLY + ENVRC_ONLY="''${ENVRC_ONLY:-N}" + + if [[ "$ENVRC_ONLY" =~ ^[Yy]$ ]]; then + TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" + echo "" + print_info "Placing .envrc only..." + if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then + echo "use flake" >> "$TARGET_ABS/.envrc" + print_success ".envrc created at $TARGET_ABS/" + else + print_info ".envrc already contains 'use flake', nothing to do." + fi + + echo "" + print_info "Next steps:" + echo " cd $TARGET_DIR" + echo " direnv allow" + echo "" + exit 0 + else + print_info "Aborting. No files were changed." + exit 1 + fi + fi + + # ---- Step 2: Project Name ---- + echo -e "''${YELLOW}Step 2/5: Project Name''${NOCOLOR}" + echo "" + read -p " Enter development environment name [devShell]: " PROJECT_NAME + + if [[ -z "$PROJECT_NAME" ]]; then + PROJECT_NAME="devShell" + fi + + print_success "Project name: $PROJECT_NAME" + echo "" + + # ---- Step 3: Select template ---- + echo -e "''${YELLOW}Step 3/5: Select Template''${NOCOLOR}" + echo "" + echo " 1) Empty - Basic shell, add packages yourself" + echo " 2) Python - Python 3, pip, venv setup" + echo " 3) Node.js - Node.js, npm" + echo " 4) Rust - Rustc, cargo, rust-analyzer" + echo " 5) Go - Go, gopls, golangci-lint" + echo " 6) C/C++ - GCC, CMake, GDB, pkg-config" + echo " 7) Java - JDK 21, Maven" + echo "" + + read -p " Select template [1-7] (default: 1): " TEMPLATE_CHOICE + + case "$TEMPLATE_CHOICE" in + 2) + TEMPLATE="python3" + PACKAGES="python3 python3Packages.pip python3Packages.virtualenv" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA=" + if [ ! -d .venv ]; then + echo 'Creating Python virtual environment...' + python3 -m venv .venv + fi + source .venv/bin/activate + echo 'Python venv activated.' + " + ;; + 3) + TEMPLATE="node" + PACKAGES="nodejs_22" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA='echo "Node.js $(node --version) ready."' + ;; + 4) + TEMPLATE="rust" + PACKAGES="rustc cargo rust-analyzer" + BUILD_INPUTS="openssl pkg-config" + SHELL_HOOK_EXTRA='echo "Rust $(rustc --version) ready."' + ;; + 5) + TEMPLATE="go" + PACKAGES="go gopls golangci-lint" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA=' + export GOPATH="$PWD/.gopath" + export PATH="$GOPATH/bin:$PATH" + mkdir -p "$GOPATH" + echo "Go $(go version) ready. GOPATH: $GOPATH" + ' + ;; + 6) + TEMPLATE="cpp" + PACKAGES="gcc gdb cmake gnumake" + BUILD_INPUTS="pkg-config" + SHELL_HOOK_EXTRA='echo "GCC $(gcc --version | head -1) ready."' + ;; + 7) + TEMPLATE="java" + PACKAGES="jdk21 maven" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA='echo "Java $(java --version | head -1) ready."' + ;; + *) + TEMPLATE="empty" + PACKAGES="" + BUILD_INPUTS="" + SHELL_HOOK_EXTRA="" + ;; + esac + + print_success "Selected template: $TEMPLATE" + echo "" + + # ---- Step 4: Copy and modify flake ---- + echo -e "''${YELLOW}Step 4/5: Generating flake.nix''${NOCOLOR}" + echo "" + + cp "$SOURCE_FLAKE" "$TARGET_DIR/flake.nix" + print_success "Copied flake.nix to $TARGET_DIR/" + + sed -i "s|name = \"replaceNameHere\";|name = \"''${PROJECT_NAME}\";|" "$TARGET_DIR/flake.nix" + print_success "Set project name: $PROJECT_NAME" + + if [[ -n "$PACKAGES" ]]; then + sed -i "s|# replacePackagesHere|$PACKAGES|" "$TARGET_DIR/flake.nix" + print_success "Added packages: $PACKAGES" + fi + + if [[ -n "$BUILD_INPUTS" ]]; then + BUILD_INPUTS_LINE="buildInputs = with pkgs; [ $BUILD_INPUTS ];" + sed -i "s|# replaceBuildInputsHere|$BUILD_INPUTS_LINE|" "$TARGET_DIR/flake.nix" + print_success "Added build inputs: $BUILD_INPUTS" + fi + + if [[ -n "$SHELL_HOOK_EXTRA" ]]; then + awk -v hook="$SHELL_HOOK_EXTRA" '/# replaceShellHookHere/ { print hook; next } { print }' \ + "$TARGET_DIR/flake.nix" > "$TARGET_DIR/flake.nix.tmp" \ + && mv "$TARGET_DIR/flake.nix.tmp" "$TARGET_DIR/flake.nix" + print_success "Added template-specific shell hook" + fi + + # ---- Step 5: Git Integration & .envrc ---- + echo -e "''${YELLOW}Step 5/5: Git Integration''${NOCOLOR}" + echo "" + + TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" + + echo "Creating .envrc for direnv to automatically work!" + if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then + echo "use flake" >> "$TARGET_ABS/.envrc" + print_success ".envrc created at $TARGET_ABS/" + else + print_info ".envrc already contains 'use flake', skipping." + fi + + if git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then + echo " A Git repository was detected at:" + echo " $(git -C "$TARGET_ABS" rev-parse --show-toplevel)" + echo "" + + _add_local_excludes "$TARGET_ABS" + echo "" + + echo " Nix flakes require flake.nix (and flake.lock) to be Git-tracked." + echo " This script can stage them as 'assume-unchanged' so Nix sees them," + echo " but they will NEVER be committed or show up in git status." + echo "" + read -p " Set up flake files as tracked-but-invisible to Git? [y/N]: " GIT_CHOICE + + GIT_CHOICE="''${GIT_CHOICE:-N}" + + if [[ "$GIT_CHOICE" =~ ^[Yy]$ ]]; then + cat > "$TARGET_ABS/flake.lock" << 'FLAKE_LOCK_EOF' + { + "nodes": { + "root": {} + }, + "root": "root", + "version": 7 + } + FLAKE_LOCK_EOF + print_success "flake.lock ready (Nix will populate it on first run if empty)" + + git -C "$TARGET_ABS" add --intent-to-add flake.nix flake.lock + git -C "$TARGET_ABS" update-index --assume-unchanged flake.nix flake.lock + print_success "flake.nix + flake.lock → tracked (assume-unchanged)" + else + print_info "Skipped. You can do this manually later:" + echo "" + echo " git -C \"$TARGET_ABS\" add --intent-to-add flake.nix flake.lock" + echo " git -C \"$TARGET_ABS\" update-index --assume-unchanged flake.nix flake.lock" + echo "" + fi + else + print_info "No Git repository detected — skipping Git integration." echo "" fi - else - print_info "No Git repository detected — skipping Git integration." + + # ---- Done ---- + echo -e "''${GREEN}==============================================''${NOCOLOR}" + echo -e "''${GREEN} Done! Your flake is ready at:''${NOCOLOR}" + echo -e "''${GREEN} $TARGET_DIR/flake.nix''${NOCOLOR}" + echo -e "''${GREEN}==============================================''${NOCOLOR}" echo "" - fi + print_info "Next steps:" + echo " cd $TARGET_DIR" + echo " direnv allow # Trust the .envrc so direnv auto-activates" + echo " nix develop # Or just: cd out and back in" + echo "" + ;; - # ---- Done ---- - echo -e "''${GREEN}==============================================''${NOCOLOR}" - echo -e "''${GREEN} Done! Your flake is ready at:''${NOCOLOR}" - echo -e "''${GREEN} $TARGET_DIR/flake.nix''${NOCOLOR}" - echo -e "''${GREEN}==============================================''${NOCOLOR}" - echo "" - print_info "Next steps:" - echo " cd $TARGET_DIR" - echo " direnv allow # Trust the .envrc so direnv auto-activates" - echo " nix develop # Or just: cd out and back in" - echo "" - ;; - - esac - ;; + esac ''; } diff --git a/modules/ncli/commands/git.nix b/modules/ncli/commands/git.nix index 27406c5..118f50e 100644 --- a/modules/ncli/commands/git.nix +++ b/modules/ncli/commands/git.nix @@ -1,10 +1,7 @@ # ncli/commands/git.nix — commit, push, pull, status, format -lib: - -let +lib: let inherit (lib) project; -in -{ +in { commit_case = '' cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } if [ "$#" -lt 2 ]; then @@ -20,29 +17,24 @@ in fi git add -A && git commit -m "$commit_msg" - ;; ''; push_case = '' cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } git push origin $(git branch --show-current) - ;; ''; pull_case = '' cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } git pull origin $(git branch --show-current) - ;; ''; status_case = '' cd "$HOME/${project}" || { echo "Error: Could not change to $HOME/${project}"; exit 1; } git status - ;; ''; format_case = '' nix fmt . - ;; ''; } diff --git a/modules/ncli/commands/maintenance.nix b/modules/ncli/commands/maintenance.nix index e2ddf04..1e4957b 100644 --- a/modules/ncli/commands/maintenance.nix +++ b/modules/ncli/commands/maintenance.nix @@ -1,10 +1,7 @@ # ncli/commands/maintenance.nix — cleanup, diag, list-gens, trim, home-backups -lib: - -let +lib: let inherit (lib) project; -in -{ +in { cleanup_case = '' echo "Warning! This will remove old generations of your system." read -p "How many generations to keep (default: all)? " keep_count @@ -33,7 +30,6 @@ in echo "Cleaning up old log files..." >> "$LOG_FILE" find "$LOG_DIR" -type f -mtime +3 -name "*.log" -delete >> "$LOG_FILE" 2>&1 echo "Cleanup process logged to $LOG_FILE" - ;; ''; diag_case = '' @@ -50,7 +46,6 @@ in echo "" } > "$HOME/diag.txt" echo "Diagnostic report saved to $HOME/diag.txt" - ;; ''; list_gens_case = '' @@ -59,7 +54,6 @@ in echo "" echo "--- System Generations ---" nix profile history --profile /nix/var/nix/profiles/system | cat || echo "Could not list system generations." - ;; ''; trim_case = '' @@ -73,11 +67,9 @@ in else echo "Trim operation cancelled." fi - ;; ''; home_backups_case = '' ls -a ~ | grep backup - ;; ''; } diff --git a/modules/ncli/commands/rebuild.nix b/modules/ncli/commands/rebuild.nix index 8531806..74a8cf7 100644 --- a/modules/ncli/commands/rebuild.nix +++ b/modules/ncli/commands/rebuild.nix @@ -1,10 +1,7 @@ # ncli/commands/rebuild.nix — Rebuild + Update logic -lib: - -let +lib: let inherit (lib) host project handle_backups handle_build_error; -in -{ +in rec { # Shared rebuild snippet used by both `rebuild` and `update` rebuild_logic = '' handle_backups @@ -21,7 +18,10 @@ in ${rebuild_logic} echo -e "Starting NixOS rebuild for current host: ${host} on generation: $YELLOW$geno$NOCOLOR" - sudo nixos-rebuild switch --flake . ; _rebuild_exit=$? + set +e + sudo nixos-rebuild switch --flake . + _rebuild_exit=$? + set -e if [ "$_rebuild_exit" -eq 0 ]; then echo "✓ Rebuild finished successfully for ${host}" @@ -38,7 +38,6 @@ in echo "✗ Rebuild failed for ${host}" >&2 exit 1 fi - ;; ''; update_case = '' @@ -110,7 +109,10 @@ in echo "Rebuilding system... Staying on current specialization" fi - sudo nixos-rebuild switch --flake . ; _rebuild_exit=$? + set +e + sudo nixos-rebuild switch --flake . + _rebuild_exit=$? + set -e if [ "$_rebuild_exit" -eq 0 ]; then echo "✓ Update and rebuild finished successfully for ${host}" @@ -127,6 +129,5 @@ in echo "✗ Update and rebuild failed for ${host}" >&2 exit 1 fi - ;; ''; } diff --git a/modules/ncli/commands/switch.nix b/modules/ncli/commands/switch.nix index 3ef909d..6c2c64f 100644 --- a/modules/ncli/commands/switch.nix +++ b/modules/ncli/commands/switch.nix @@ -1,9 +1,5 @@ # ncli/commands/switch.nix — Specialization switching -lib: - -{}: - -{ +lib: { switch_case = '' current="" if [ -f /etc/nixos-tags ]; then @@ -35,6 +31,5 @@ lib: echo "To switch to a specialization, run: 'ncli switch '" fi fi - ;; ''; } diff --git a/modules/ncli/default.nix b/modules/ncli/default.nix index cb4ead6..c49bad6 100644 --- a/modules/ncli/default.nix +++ b/modules/ncli/default.nix @@ -1,7 +1,3 @@ -# ncli/default.nix — Entry point -# Usage in your flake: -# ncli = import ./ncli { inherit pkgs host project; }; -# environment.systemPackages = [ ncli ]; { pkgs, host, diff --git a/modules/ncli/lib.nix b/modules/ncli/lib.nix index e58dc79..2004e2e 100644 --- a/modules/ncli/lib.nix +++ b/modules/ncli/lib.nix @@ -13,64 +13,76 @@ in # --- Backup helper --- 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" + handle_backups() { + echo "Checking for backup files to remove..." + found=0 + 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" + found=1 + fi + done + if [ "$found" -eq 0 ]; then + echo "No stale backup files found." fi - done + } ''; # --- OOM / build error handler --- handle_build_error = '' - local exit_code=$? - if [ "$exit_code" -eq 137 ]; then - echo "" - echo -e "''${RED}╔══════════════════════════════════════════════════════════╗''${NOCOLOR}" - echo -e "''${RED}║ BUILD KILLED — Signal 9 (SIGKILL) detected ║''${NOCOLOR}" - echo -e "''${RED}╚══════════════════════════════════════════════════════════╝''${NOCOLOR}" - echo "" - echo -e "''${YELLOW}What happened:''${NOCOLOR}" - echo " The build process was forcefully terminated by the OS." - echo " This is almost always the Linux kernel OOM killer running out" - echo " of RAM + swap during Nix evaluation or compilation." - echo "" - echo -e "''${YELLOW}Suggested fixes (try in order):''${NOCOLOR}" - echo "" - echo -e " ''${GREEN}1. Reduce parallel jobs''${NOCOLOR} (lowest RAM usage):" - echo " sudo nixos-rebuild switch --flake . --max-jobs 1 --cores 1" - echo "" - echo -e " ''${GREEN}2. Confirm OOM killer fired:''${NOCOLOR}" - echo " journalctl -k --since '5 minutes ago' | grep -i oom" - echo "" - fi + handle_build_error() { + local exit_code=$? + if [ "$exit_code" -eq 137 ]; then + echo "" + echo -e "''${RED}╔══════════════════════════════════════════════════════════╗''${NOCOLOR}" + echo -e "''${RED}║ BUILD KILLED — Signal 9 (SIGKILL) detected ║''${NOCOLOR}" + echo -e "''${RED}╚══════════════════════════════════════════════════════════╝''${NOCOLOR}" + echo "" + echo -e "''${YELLOW}What happened:''${NOCOLOR}" + echo " The build process was forcefully terminated by the OS." + echo " This is almost always the Linux kernel OOM killer running out" + echo " of RAM + swap during Nix evaluation or compilation." + echo "" + echo -e "''${YELLOW}Suggested fixes (try in order):''${NOCOLOR}" + echo "" + echo -e " ''${GREEN}1. Reduce parallel jobs''${NOCOLOR} (lowest RAM usage):" + echo " sudo nixos-rebuild switch --flake . --max-jobs 1 --cores 1" + echo "" + echo -e " ''${GREEN}2. Confirm OOM killer fired:''${NOCOLOR}" + echo " journalctl -k --since '5 minutes ago' | grep -i oom" + echo "" + fi + } ''; # --- Print helpers (used by dev init) --- print_header = '' - echo "" - echo -e "''${BLUE}==============================================''${NOCOLOR}" - echo -e "''${BLUE} Nix Flake Development Environment Initializer''${NOCOLOR}" - echo -e "''${BLUE}==============================================''${NOCOLOR}" - echo "" + print_header() { + echo "" + echo -e "''${BLUE}==============================================''${NOCOLOR}" + echo -e "''${BLUE} Nix Flake Development Environment Initializer''${NOCOLOR}" + echo -e "''${BLUE}==============================================''${NOCOLOR}" + echo "" + } ''; print_success = '' - echo -e "''${GREEN}[OK]''${NOCOLOR} $1" + print_success() { + echo -e "''${GREEN}[OK]''${NOCOLOR} $1" + } ''; print_error = '' - echo -e "''${RED}[ERR]''${NOCOLOR} $1" + print_error() { + echo -e "''${RED}[ERR]''${NOCOLOR} $1" + } ''; print_info = '' - echo -e "''${YELLOW}[->]''${NOCOLOR} $1" + print_info() { + echo -e "''${YELLOW}[->]''${NOCOLOR} $1" + } ''; } From dbf7c1cd2e86ac5d1335f5b2f0638377c67cd521 Mon Sep 17 00:00:00 2001 From: Cookiez Date: Fri, 29 May 2026 11:13:35 +0200 Subject: [PATCH 3/3] Refactored ncli to rebuild boot first, and then switch to new configuration after. --- modules/ncli/commands/rebuild.nix | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/modules/ncli/commands/rebuild.nix b/modules/ncli/commands/rebuild.nix index 74a8cf7..1450ef2 100644 --- a/modules/ncli/commands/rebuild.nix +++ b/modules/ncli/commands/rebuild.nix @@ -18,15 +18,21 @@ in rec { ${rebuild_logic} echo -e "Starting NixOS rebuild for current host: ${host} on generation: $YELLOW$geno$NOCOLOR" + # Step 1: Build the configuration and safely update the bootloader first set +e - sudo nixos-rebuild switch --flake . + sudo nixos-rebuild boot --flake . _rebuild_exit=$? set -e + if [ "$_rebuild_exit" -eq 0 ]; then - echo "✓ Rebuild finished successfully for ${host}" + echo "✓ Rebuild and bootloader update finished successfully for ${host}" + + # Step 2: Now that boot entries are safe, activate the system live + echo "Activating new configuration..." + sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch if [ -n "$current" ]; then - sudo /run/current-system/specialisation/"$current"/bin/switch-to-configuration test + sudo /nix/var/nix/profiles/system/specialisation/"$current"/bin/switch-to-configuration test else echo "No specialization tag found, staying on default system." fi @@ -109,15 +115,21 @@ in rec { echo "Rebuilding system... Staying on current specialization" fi + # Step 1: Build the configuration and safely update the bootloader first set +e - sudo nixos-rebuild switch --flake . + sudo nixos-rebuild boot --flake . _rebuild_exit=$? set -e + if [ "$_rebuild_exit" -eq 0 ]; then - echo "✓ Update and rebuild finished successfully for ${host}" + echo "✓ Update, rebuild and bootloader update finished successfully for ${host}" + + # Step 2: Now that boot entries are safe, activate the system live + echo "Activating new configuration..." + sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch if [ -n "$current" ]; then - sudo /run/current-system/specialisation/"$current"/bin/switch-to-configuration test + sudo /nix/var/nix/profiles/system/specialisation/"$current"/bin/switch-to-configuration test else echo "No specialization tag found, staying on default system." fi