From 33356ec9bc1a1571491894c6edef73df181f9ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Tue, 9 Aug 2022 09:20:02 +0200 Subject: Add local.sh script to update local machine Yes there is nixos-rebuild but this turns out to be for me much more handy when updating and testing system. --- .gitignore | 1 + common.sh | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ devices.sh | 125 ++---------------------------------------------- local.sh | 58 +++++++++++++++++++++++ 4 files changed, 221 insertions(+), 120 deletions(-) create mode 100644 common.sh create mode 100755 local.sh diff --git a/.gitignore b/.gitignore index fcfc4a1..fd1b4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ result* +.result-* diff --git a/common.sh b/common.sh new file mode 100644 index 0000000..995a57e --- /dev/null +++ b/common.sh @@ -0,0 +1,157 @@ +# Common Bash functions for helper scripts in this repository +set -eu + +## Logging ##################################################################### +_print() { + local color="\e[$1m" + local clrcolor="\e[0m" + shift + if [ ! -t 1 ]; then + color="" + clrcolor="" + fi + printf "${color}%s${clrcolor}\n" "$*" >&2 +} + +stage() { + _print '1;32' "$@" +} + +info() { + _print '1;35' "$@" +} + +error() { + _print '1;31' "$@" +} +warning() { + _print '1;33' "$@" +} + +## SSH access helper ########################################################### + +# Convert hostname to the SSH destination +sshdest() { + awk -F- 'NF > 1 { print $2"."$1; exit } { print $1 }' <<<"$1" +} + +_ssh() { + local device="$1" + shift + if [ "$device" != "$(hostname)" ]; then + local -a args + [ "$1" = "sudo" ] && \ + args+=('-t') # Crude detection for terminal need + ssh "${args[@]}" "$(sshdest "$device")" -- "$@" + else + "$@" + fi +} + +## Evalutions and queries ###################################################### + +# The path where build result is linked to +result() { + echo ".result-$1" +} + +# Get system of the device +device_system() { + nix eval --raw ".#nixosConfigurations.$1.config.nixpkgs.system" +} + +# Validates if link is valid. +build_validate() { + local device="$1" + [ -L "$(result "$device")" ] && [ -e "$(result "$device")" ] +} + +## Build NixOS system ########################################################## +# $1: device name +# All other arguments are passed to the nix build command +build() { + local device="$1" + shift + + local toplevel=".config.system.build.toplevel" + if [ "$(device_system "$device")" = "armv7l-linux" ]; then + toplevel=".config.system.build.cross.x86_64-linux${toplevel}" + fi + + stage "Building system for device: $device" + nix build \ + -o "$(result "${device}")" \ + --keep-going \ + "$@" \ + "${0%/*}#nixosConfigurations.${device}${toplevel}" +} + +## Copy NixOS system ########################################################### +# $1: device name +copy() { + local device="$1" + if ! build_validate "$device"; then + warning "System for device '$device' seems to be not build." >&2 + return 1 + fi + local store + store="$(readlink -f "$(result "$device")")" + + local freespace required; + freespace="$(_ssh "$device" df -B 1 /nix | awk 'NR == 2 { print $4 }')" + required="$(nix path-info -S "$store" | awk '{ print $2 }')" + info "Free space on device: $(numfmt --to=iec "$freespace")" + info "Required space: $(numfmt --to=iec "$required")" + if [ "$required" -ge "$freespace" ]; then + error "There is not enough space to copy clousure to: $device" >&2 + return 1 + fi + + stage "Copy closure to: $device" + nix copy -s --to "ssh://$(sshdest "$device")" "$store" +} + +## Switch Nix encironment ###################################################### +# $1: switch operation to be performed +# $2: device name +# TODO possibly really query if switch is or is not required +setenv() { + local switchop="$1" + local device="$2" + if ! build_validate "$device"; then + warning "System '$device' seems to be not build." >&2 + return 1 + fi + local store + store="$(readlink -f "$(result "$device")")" + + stage "${switchop^} system: $device" + local cursystem + cursystem="$(_ssh "$device" readlink -f /nix/var/nix/profiles/system)" + if [ "$cursystem" != "$store" ]; then + info "-----------------------------------------------------------------" + _ssh "$device" \ + nix store diff-closures "$cursystem" "$store" + info "-----------------------------------------------------------------" + # TODO we should call here switch-to-configuration as well to use single + # sudo session and remove one prompt. + _ssh "$device" \ + sudo sh -c \ + 'nix-env --profile /nix/var/nix/profiles/system --set "$store" && /nix/var/nix/profiles/system/bin/switch-to-configuration "$1"' \ + "$switchop" + else + warning "The latest system might have been already set." + fi +} + +boot() { + setenv boot "$1" +} + +switch() { + setenv switch "$1" +} + +switch_test() { + setenv test "$1" +} diff --git a/devices.sh b/devices.sh index 25632db..3a2dce5 100755 --- a/devices.sh +++ b/devices.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -eu +source "${0%/*}/common.sh" declare -a devices ################################################################################ ## aarch64 @@ -24,134 +24,18 @@ valid_device() { return 1 } -device_system() { - nix eval --raw ".#nixosConfigurations.$1.config.nixpkgs.system" -} - -sshdev() { - echo "$1" | awk -F- 'NF > 1 { print $2"."$1; exit } { print $1 }' -} - - -build() { - local system="$1" - echo "Building $system" - local -a args - local toplevel=".config.system.build.toplevel" - args+=("--keep-going") - if [ "$(device_system "$1")" = "armv7l-linux" ]; then - toplevel=".config.system.build.cross.x86_64-linux${toplevel}" - fi - nix build \ - -o "result-${system}" \ - "${args[@]}" \ - "${0%/*}#nixosConfigurations.${system}${toplevel}" -} - -build_validate() { - local system="$1" - [ -L "result-$system" ] && [ -e "result-$system" ] -} - -copy() { - local system="$1" - if ! build_validate "$system"; then - echo "System '$system' seems to be not build." >&2 - return 1 - fi - local store="$(readlink -f "result-$system")" - local host="$(sshdev "$system")" - - local freespace="$(ssh "$host" -- df -B 1 /nix | awk 'NR == 2 { print $4 }')" - local required="$(nix path-info -S "$store" | awk '{ print $2 }')" - echo "Free space on device: $(numfmt --to=iec "$freespace")" - echo "Required space: $(numfmt --to=iec "$required")" - if [ "$required" -ge "$freespace" ]; then - echo "There is not enough space to copy clousure to: $system" >&2 - return 1 - fi - - echo "Copy closure to: $system" - nix copy -s --to "ssh://$host" "$store" -} - -setenv() { - local system="$1" - if ! build_validate "$system"; then - echo "System '$system' seems to be not build." >&2 - return 1 - fi - local store="$(readlink -f "result-$system")" - local host="$(sshdev "$system")" - - echo "Update system: $system" - if [ "$(ssh "$host" -- readlink -f /nix/var/nix/profiles/system)" != "$store" ]; then - ssh -t "$host" -- \ - sudo nix-env --profile /nix/var/nix/profiles/system --set "$store" - fi -} - -boot() { - local system="$1" - setenv "$system" || return 1 - - local store="$(readlink -f "result-$system")" - local host="$(sshdev "$system")" - - echo "Setting boot system: $system" - ssh -t "$host" -- \ - sudo /nix/var/nix/profiles/system/bin/switch-to-configuration boot -} - -is_current() { - ssh "$1" -- \ - '[ "$(readlink -f /run/current-system)" != "$(readlink -f /nix/var/nix/profiles/system)" ]' -} - -switch() { - local system="$1" - setenv "$system" || return 1 - - local store="$(readlink -f "result-$system")" - local host="$(sshdev "$system")" - - if is_current "$host"; then - echo "Switching: $system" - ssh -t "$host" -- \ - sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch - else - echo "This system is already running: $system" - fi -} - -switch_test() { - local system="$1" - setenv "$system" || return 1 - - local store="$(readlink -f "result-$system")" - local host="$(sshdev "$system")" - - if is_current "$host"; then - echo "Testing: $system" - ssh -t "$host" -- \ - sudo /nix/var/nix/profiles/system/bin/switch-to-configuration test - else - echo "This system is already running: $system" - fi -} - for_devices() { for device in "${selected_devices[@]}"; do for op in "$@"; do if ! "$op" "$device"; then - echo "Operation '$op' failed for: $device" >&2 + error "Operation '$op' failed for: $device" >&2 break fi done done } - +################################################################################ operation="${1:-}" [ $# -gt 0 ] && shift @@ -159,7 +43,7 @@ declare -a selected_devices if [ $# -gt 0 ]; then for device in "$@"; do if ! valid_device "$device"; then - echo "No such device: $device" >&2 + error "No such device: $device" >&2 exit 2 fi selected_devices+=("$device") @@ -168,6 +52,7 @@ else selected_devices=("${devices[@]}") fi + case "$operation" in help|h) cat <<-EOF diff --git a/local.sh b/local.sh new file mode 100755 index 0000000..37985c3 --- /dev/null +++ b/local.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +source "${0%/*}/common.sh" + +operations() { + for op in "$@"; do + if ! "$op" "$(hostname)"; then + error "Operation '$op' failed" >&2 + break + fi + done +} + +################################################################################ +operation="${1:-}" +if [ $# -gt 1 ]; then + echo "Invalid argument: $2" >&2 + exit 2 +fi + +case "$operation" in + help|h) + cat <<-EOF + Usage $0 operation [device]... + Local system builder and updater for remote devices. + + Operations: + build: build device system + boot: set built system to be boot default on the device + switch: switch to the built system on the target device + test: test the built system on the target device + EOF + ;; + build|b) + operations build + ;; + boot) + operations boot + ;; + switch|s) + operations switch + ;; + test|t) + operations switch_test + ;; + build-switch|bs|"") + operations build switch + ;; + build-test|bt) + operations build switch_test + ;; + build-boot|bb) + operations build boot + ;; + default) + echo "Unknown operation: $operation" >&2 + exit 2 + ;; +esac -- cgit v1.2.3