Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Created June 6, 2025 11:14
Show Gist options
  • Save dzogrim/33a62d4e0b1fefc4ce8265c69f1ed3c2 to your computer and use it in GitHub Desktop.
Save dzogrim/33a62d4e0b1fefc4ce8265c69f1ed3c2 to your computer and use it in GitHub Desktop.
Safely deploy latest nixos image to USB stick after build for hardware tests
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
# after-builder.sh β€” Safely deploy latest sebos image to USB stick after build for hardware tests
# Author: SΓ©bastien L.
# Description:
# This script installs the latest sebos_*.raw image from ./result/full to a USB stick mounted under /run/media/<user>/DATA.
# It writes metadata, computes hash, verifies it, and cleanly ejects the device.
# Dry-run by default, requires "APPLY" to take action.
set -euo pipefail
# Customize here
REPO_NAME="sebos"
USB_MOUNTPOINT_LABELS=(DATA RESCUE1102)
USER_NAME=$(logname)
EXPECTED_DIR="$HOME/Documents/Workspaces/${REPO_NAME}"
MOUNT_BASE="/run/media/$USER_NAME"
DRY_RUN=true
[[ "${1:-}" == "APPLY" ]] && DRY_RUN=false
# Used later
MOUNTPOINT=""
DEST_RAW=""
DEST_TXT=""
DEST_SHA=""
SKIP_COPY=0
errors=0
######################################
# 1. Environment checks
######################################
# Check current directory
if [[ "$(pwd)" != "$EXPECTED_DIR" ]]; then
echo "❌ Wrong working directory: $(pwd)"
echo "πŸ‘‰ Expected: $EXPECTED_DIR"
((errors++))
else
echo "βœ… Working directory OK"
fi
# Locate mountpoint by label
for label in "${USB_MOUNTPOINT_LABELS[@]}"; do
if [[ -d "$MOUNT_BASE/$label" ]]; then
MOUNTPOINT="$MOUNT_BASE/$label"
echo "βœ… USB stick mounted at: $MOUNTPOINT"
break
fi
done
if [[ -z "$MOUNTPOINT" ]]; then
echo "❌ USB stick not found under: ${USB_MOUNTPOINT_LABELS[*]}"
((errors++))
fi
# Find .raw file
RAW_FILE=$(find ./result/full/ -maxdepth 1 -name "${REPO_NAME}_*.raw" -print -quit)
if [[ -z "${RAW_FILE:-}" ]]; then
echo "❌ No .raw file found in ./result/full/"
((errors++))
else
echo "βœ… .raw file detected: $RAW_FILE"
fi
# Abort if error(s)
if ((errors > 0)); then
echo
echo "❌ $errors error(s) found, aborting."
exit 1
fi
######################################
# 2. Set derived variables
######################################
VERSION=$(basename "$RAW_FILE" | sed -E "s/^${REPO_NAME}_(.+)\.raw$/\1/")
DEST_RAW="$MOUNTPOINT/image.raw"
DEST_TXT="$MOUNTPOINT/image.raw.txt"
DEST_SHA="$MOUNTPOINT/image.raw.sha256"
echo
echo "πŸ” Detected version: $VERSION"
######################################
# 3. Check for existing image
######################################
if [ -f "$DEST_RAW" ]; then
echo "⚠️ Warning: $DEST_RAW already exists."
if [ -f "$DEST_TXT" ]; then
echo "ℹ️ Existing metadata:"
echo "----------------------------------"
cat "$DEST_TXT"
echo "----------------------------------"
else
echo "ℹ️ No metadata file found."
fi
if $DRY_RUN; then
echo "πŸ’‘ Dry-run: would check and maybe replace existing image."
else
echo "πŸ” Comparing source and destination hashes..."
RAW_HASH=$(sha256sum "$RAW_FILE" | awk '{print $1}')
DEST_HASH=""
if [ -f "$DEST_SHA" ]; then
DEST_HASH=$(awk '{print $1}' "$DEST_SHA")
fi
if [[ "$RAW_HASH" == "$DEST_HASH" ]]; then
echo "βœ… File already up-to-date on USB, skipping copy."
SKIP_COPY=1
else
echo -n "❓ Do you want to delete and replace the existing image? [y/N] "
read -r confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "❌ Aborted by user."
exit 10
fi
echo "πŸ—‘οΈ Removing old image and metadata..."
rm -f "$DEST_RAW" "$DEST_TXT" "$DEST_SHA"
fi
fi
fi
echo
######################################
# 4. Perform operations
######################################
if $DRY_RUN; then
echo "πŸ’‘ Dry-run mode: planned actions:"
if [[ "$SKIP_COPY" == 0 ]]; then
echo "β†’ Copy $RAW_FILE to $DEST_RAW"
echo "β†’ Write version info to $DEST_TXT"
echo "β†’ Compute SHA256 and write to $DEST_SHA"
echo "β†’ Verify SHA256 after copy"
fi
echo "β†’ Eject USB key (labels: ${USB_MOUNTPOINT_LABELS[*]})"
else
echo "πŸš€ APPLY mode: executing actions..."
if [[ "$SKIP_COPY" == 0 ]]; then
echo "πŸ“€ Copying image to USB..."
cp "$RAW_FILE" "$DEST_RAW"
echo "πŸ“ Writing version info..."
echo "Version $VERSION $(date)" > "$DEST_TXT"
echo "πŸ” Computing SHA256..."
HASH=$(sha256sum "$DEST_RAW" | awk '{print $1}')
echo "$HASH image.raw" > "$DEST_SHA"
echo "πŸ” Verifying SHA256..."
VERIFY_HASH=$(sha256sum "$DEST_RAW" | awk '{print $1}')
if [[ "$HASH" != "$VERIFY_HASH" ]]; then
echo "❌ Hash mismatch after copy! Aborting."
exit 2
fi
echo "βœ… Copy and verification successful."
fi
echo "⏏️ Ejecting USB device..."
DISK=$(lsblk -no PKNAME "$DEST_RAW" 2>/dev/null || true)
for label in "${USB_MOUNTPOINT_LABELS[@]}"; do
if [ -e "/dev/disk/by-label/$label" ]; then
echo "πŸ”Œ Unmounting /dev/disk/by-label/$label..."
udisksctl unmount -b "/dev/disk/by-label/$label" || true
fi
done
if [ -n "$DISK" ]; then
echo "πŸ›‘ Powering off /dev/$DISK..."
udisksctl power-off -b "/dev/$DISK" || true
fi
echo "βœ… Done."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment