#!/usr/bin/env bash # Updates NixOS from a throwaway worktree of this repo, so the checkout you edit in # is left alone until the update is done. Started from the "Desktop" half of the # control centre's update tile in a kitty window (installed as `nixos-update`, # modules/home/packages.nix, which sets DOT_REPO). # # 1. a temporary worktree of main, with the commits git.granstrm.com made since # (the nightly CachyOS kernel, flake.nix's `ci`) # 2. nix flake update of every input but the kernel's, committed there: the kernel # is the one git.granstrm.com built, and comes from its binary cache # 3. nixos-rebuild build, then nvd's list of what changed against the running system # 4. asks before going on; then pushes the commit, installs it as the next boot # entry (nixos-rebuild boot), brings your checkout's main up to date and reboots # # If anything fails, the window stays open until you press Enter, and nothing is # rebooted. set -E repo=${DOT_REPO:-$HOME/git/dot} tmp="" step() { printf '\n\e[1;34m==> %s\e[0m\n' "$*"; } note() { printf '\e[1;33m%s\e[0m\n' "$*"; } wait_close() { read -rp "Press Enter to close. " _ || true; } die() { trap - ERR printf '\n\e[1;31m%s\e[0m\n' "$*" wait_close exit 1 } cleanup() { if [ -n "$tmp" ]; then git -C "$repo" worktree remove --force "$tmp" 2>/dev/null || true rm -rf "$tmp" tmp="" fi } trap 'die "The update failed (see above). Nothing was rebooted."' ERR trap cleanup EXIT step "Fetching $repo" git -C "$repo" worktree prune git -C "$repo" fetch origin main main=$(git -C "$repo" rev-parse main) # git.granstrm.com's own commits (the kernel) are taken as they are; your own on # main that it doesn't have yet are pushed with the update. if git -C "$repo" merge-base --is-ancestor main origin/main; then base=$(git -C "$repo" rev-parse origin/main) elif git -C "$repo" merge-base --is-ancestor origin/main main; then base=$main else die "main in $repo and origin/main have diverged; rebase main first." fi ahead=$(git -C "$repo" rev-list --count origin/main..main) dirty=$(git -C "$repo" status --porcelain) tmp=$(mktemp -d -t dot-update.XXXXXX) step "Creating a worktree of main in $tmp" git -C "$repo" worktree add --detach "$tmp" "$base" cd "$tmp" || die "Could not enter $tmp." step "Updating flake inputs (all but the kernel's)" mapfile -t inputs < <(jq -r '.nodes.root.inputs | keys[] | select(. != "nix-cachyos-kernel")' flake.lock) nix flake update --commit-lock-file "${inputs[@]}" kernel=$(nix eval --raw "$tmp#nixosConfigurations.desktop.config.boot.kernelPackages.kernel") if ! nix path-info "$kernel" >/dev/null 2>&1 && ! nix path-info --store https://cache.git.granstrm.com "$kernel" >/dev/null 2>&1; then note "The kernel isn't in git.granstrm.com's cache: its CI hasn't built it yet, or failed" note "(https://git.granstrm.com/dot). It would compile here, which takes a while." read -rp "Compile it here? [y/N] " answer || answer="" case $answer in [yY]*) ;; *) echo "Nothing was changed." sleep 1 exit 0 ;; esac fi step "Building" nixos-rebuild build --flake "$tmp" if [ "$(readlink -f "$tmp/result")" = "$(readlink -f /run/current-system)" ]; then printf '\nEverything is already up to date.\n' wait_close exit 0 fi step "What changes" git --no-pager log --format='%s%n%n%b' "$main..HEAD" nvd diff /run/current-system "$tmp/result" echo [ -z "$dirty" ] || note "Uncommitted changes in $repo are not part of this update." [ "$ahead" -eq 0 ] || note "main has $ahead commit(s) not on git.granstrm.com yet; they are pushed too." read -rp "Commit, push, install and reboot? [y/N] " answer || answer="" case $answer in [yY]*) ;; *) echo "Nothing was changed." sleep 1 exit 0 ;; esac step "Pushing" git push origin HEAD:main step "Installing as the next boot entry" nixos-rebuild boot --sudo --flake "$tmp" step "Updating main in $repo" commit=$(git rev-parse HEAD) if [ "$(git -C "$repo" symbolic-ref --quiet --short HEAD)" = main ]; then git -C "$repo" merge --ff-only "$commit" else git -C "$repo" update-ref refs/heads/main "$commit" "$main" fi cd "$repo" cleanup trap 'printf "\nReboot cancelled. The update boots next time.\n"; wait_close; exit 0' INT step "Rebooting in 5 seconds (Ctrl+C to cancel)" sleep 5 systemctl reboot