Skip to content

Instantly share code, notes, and snippets.

@ojensen5115
Created August 14, 2026 20:13
Show Gist options
  • Select an option

  • Save ojensen5115/fbf496f228e0adf63ab0bdeb753db7ac to your computer and use it in GitHub Desktop.

Select an option

Save ojensen5115/fbf496f228e0adf63ab0bdeb753db7ac to your computer and use it in GitHub Desktop.
My rebuild script for nixos
#!/usr/bin/env bash
set -euo pipefail
# Process options
UPDATE=0
BOOT=0
IGNORE_DIFF=0
ROLLBACK=0
ALT_HOST=0
while getopts ":ubirah" opt; do
case "$opt" in
u) UPDATE=1 ;;
b) BOOT=1 ;;
i) IGNORE_DIFF=1 ;;
r) ROLLBACK=1 ;;
a) ALT_HOST=1 ;;
h) echo "Usage: rebuild [-u] [-b] [-i] [-r] [-a] [flake-target]
-u Update this target's nixpkgs input before rebuilding
-b Use 'boot' instead of 'switch' (activate on next boot)
-i Skip diff check
-r Roll back to the previous generation
-a Reach web via its native droplet IP instead of the
reserved IP (workaround for flaky reserved-IP routing)
flake-target Optional nixosConfigurations.<name>; defaults to \$(hostname)
"; exit 0 ;;
\?) echo "Unknown option: -$OPTARG" >&2; exit 2 ;;
esac
done
shift $((OPTIND-1))
TARGET="${1:-$(hostname)}"
# Per-target config: ssh destination (empty = local) and which flake input
# holds this target's nixpkgs, so hosts can be updated independently.
REMOTE=""
ALT_REMOTE=""
INPUT="nixpkgs"
case "$TARGET" in
web)
REMOTE="root@0x3c.net"
ALT_REMOTE="root@64.227.12.77" # native droplet IP
INPUT="nixpkgs-web"
;;
esac
if (( ALT_HOST )); then
if [[ -z "$ALT_REMOTE" ]]; then
echo "-a is not valid for target '${TARGET}'" >&2
exit 2
fi
echo "Using alternate host ${ALT_REMOTE}"
REMOTE="$ALT_REMOTE"
fi
# Decide which subcommand to use
SUBCOMMAND="switch"
if (( BOOT )); then
SUBCOMMAND="boot"
fi
# Enter the directory
pushd ~/flake >/dev/null
# Rollback short-circuits everything else
if (( ROLLBACK )); then
echo "Rolling back ${TARGET}..."
if [[ -n "$REMOTE" ]]; then
nixos-rebuild switch --rollback --flake ".#${TARGET}" --target-host "$REMOTE"
else
sudo -v
sudo nixos-rebuild switch --rollback --flake ".#${TARGET}"
fi
popd >/dev/null
notify-send "NixOS rolled back (${TARGET})" --icon=software-update-available
exit 0
fi
# Conditionally update this target's nixpkgs input
if (( UPDATE )); then
echo "Updating flake input ${INPUT}..."
nix flake update "$INPUT"
fi
# Autoformat nix files
alejandra . &>/dev/null || ( alejandra . ; echo "formatting failed!" && exit 1)
# Early return if no changes after update or format.
# Skipped for remote targets: config may be unchanged locally but not yet
# deployed, e.g. redeploying after fixing something on the box by hand.
git add -A
if ! (( IGNORE_DIFF )) && [[ -z "$REMOTE" ]]; then
if git diff --quiet HEAD; then
echo "No changes detected, exiting."
popd >/dev/null
exit 0
fi
fi
# Shows changes
git diff -U0 HEAD
if [[ -n "$REMOTE" ]]; then
echo "Building locally & deploying to ${REMOTE} (target: ${TARGET})..."
if ! nixos-rebuild "${SUBCOMMAND}" --flake ".#${TARGET}" --target-host "$REMOTE"; then
notify-send -e "NixOS deploy to ${TARGET} failed"
popd >/dev/null
exit 1
fi
else
# Get sudo permissions
echo "Acquiring sudo..."
sudo -v
echo "Building & activating with nixos-rebuild ${SUBCOMMAND} (target: ${TARGET})..."
if ! sudo nixos-rebuild "${SUBCOMMAND}" --flake ".#${TARGET}"; then
notify-send -e "NixOS build/activation failed"
popd >/dev/null
exit 1
fi
fi
# Build the commit message.
if [[ -n "$REMOTE" ]]; then
sys_target="$(ssh "$REMOTE" 'readlink /nix/var/nix/profiles/system')"
else
sys_target="$(readlink /nix/var/nix/profiles/system)"
fi
gen_num="${sys_target#system-}"
gen_num="${gen_num%-link}"
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
message="$TARGET -- Gen $gen_num ($timestamp)"
# Commit all changes with the generation metadata (no-op if nothing changed)
git commit -am "$message" || true
popd >/dev/null
# Tailor the notification to whether we switched or staged for boot
if (( BOOT )); then
notify-send "NixOS Built for Next Boot (${TARGET})" --icon=software-update-available
else
notify-send "NixOS Rebuilt OK! (${TARGET})" --icon=software-update-available
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment