Add pull-image.sh and flash-image.sh helper scripts
- pull-image.sh: Auto-detects SD card, copies with pv progress, runs pishrink, compresses with xz - flash-image.sh: Auto-detects SD card, flashes .img.xz/.img with pv progress - Both scripts auto-detect 8-128GB USB drives and skip root filesystem - Safety confirmations before destructive operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
206
rpi/flash-image.sh
Executable file
206
rpi/flash-image.sh
Executable file
@@ -0,0 +1,206 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Flash Stegasoo image to SD card
|
||||||
|
# Auto-detects SD card, decompresses and writes with progress
|
||||||
|
#
|
||||||
|
# Usage: ./flash-image.sh <image.img.xz>
|
||||||
|
# ./flash-image.sh <image.img>
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Check for required tools
|
||||||
|
for cmd in dd pv lsblk; do
|
||||||
|
if ! command -v $cmd &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: $cmd is required but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check for root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: Must run as root (sudo)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for image argument
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo -e "${RED}Usage: $0 <image.img.xz|image.img>${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 stegasoo-rpi-20260103.img.xz"
|
||||||
|
echo " $0 stegasoo-rpi-20260103.img"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IMAGE="$1"
|
||||||
|
|
||||||
|
if [ ! -f "$IMAGE" ]; then
|
||||||
|
echo -e "${RED}Error: Image file not found: $IMAGE${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect compression
|
||||||
|
COMPRESSED=false
|
||||||
|
if [[ "$IMAGE" == *.xz ]]; then
|
||||||
|
COMPRESSED=true
|
||||||
|
if ! command -v xzcat &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: xz is required for .xz files but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [[ "$IMAGE" == *.gz ]]; then
|
||||||
|
COMPRESSED=true
|
||||||
|
COMP_CMD="zcat"
|
||||||
|
if ! command -v zcat &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: gzip is required for .gz files but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}"
|
||||||
|
echo "╔═══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Stegasoo SD Card Flasher ║"
|
||||||
|
echo "╚═══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
|
||||||
|
echo -e "Image: ${YELLOW}$IMAGE${NC}"
|
||||||
|
echo -e "Size: ${YELLOW}$(du -h "$IMAGE" | awk '{print $1}')${NC}"
|
||||||
|
if [ "$COMPRESSED" = true ]; then
|
||||||
|
echo -e "Type: ${YELLOW}Compressed (will decompress on-the-fly)${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Auto-detect SD card candidates
|
||||||
|
echo -e "${BOLD}Scanning for SD cards...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
declare -a CANDIDATES
|
||||||
|
declare -a CANDIDATE_INFO
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
DEV=$(echo "$line" | awk '{print $1}')
|
||||||
|
SIZE=$(echo "$line" | awk '{print $2}')
|
||||||
|
TYPE=$(echo "$line" | awk '{print $3}')
|
||||||
|
TRAN=$(echo "$line" | awk '{print $4}')
|
||||||
|
MODEL=$(echo "$line" | awk '{print $5" "$6" "$7}' | xargs)
|
||||||
|
|
||||||
|
# Skip if it's the root filesystem
|
||||||
|
if mount | grep -q "^/dev/${DEV}[0-9]* on / "; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip if any partition is mounted as root
|
||||||
|
ROOT_DEV=$(mount | grep " on / " | awk '{print $1}' | sed 's/[0-9]*$//')
|
||||||
|
if [[ "/dev/$DEV" == "$ROOT_DEV" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse size to bytes for comparison
|
||||||
|
SIZE_NUM=$(echo "$SIZE" | sed 's/[^0-9.]//g')
|
||||||
|
SIZE_UNIT=$(echo "$SIZE" | sed 's/[0-9.]//g')
|
||||||
|
|
||||||
|
case $SIZE_UNIT in
|
||||||
|
G) SIZE_GB=$SIZE_NUM ;;
|
||||||
|
T) SIZE_GB=$(echo "$SIZE_NUM * 1024" | bc) ;;
|
||||||
|
M) SIZE_GB=$(echo "scale=2; $SIZE_NUM / 1024" | bc) ;;
|
||||||
|
*) SIZE_GB=0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if size is in SD card range (8GB - 128GB)
|
||||||
|
if (( $(echo "$SIZE_GB >= 8" | bc -l) )) && (( $(echo "$SIZE_GB <= 128" | bc -l) )); then
|
||||||
|
CANDIDATES+=("/dev/$DEV")
|
||||||
|
CANDIDATE_INFO+=("$SIZE $TYPE ${TRAN:-???} $MODEL")
|
||||||
|
fi
|
||||||
|
done < <(lsblk -d -o NAME,SIZE,TYPE,TRAN,MODEL -n | grep "disk")
|
||||||
|
|
||||||
|
if [ ${#CANDIDATES[@]} -eq 0 ]; then
|
||||||
|
echo -e "${RED}No SD card candidates found.${NC}"
|
||||||
|
echo "Looking for USB/removable disks between 8GB and 128GB."
|
||||||
|
echo ""
|
||||||
|
echo "Available disks:"
|
||||||
|
lsblk -d -o NAME,SIZE,TYPE,TRAN,MODEL
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Found ${#CANDIDATES[@]} candidate(s):${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for i in "${!CANDIDATES[@]}"; do
|
||||||
|
echo -e " ${BOLD}[$((i+1))]${NC} ${CANDIDATES[$i]} - ${CANDIDATE_INFO[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ${#CANDIDATES[@]} -eq 1 ]; then
|
||||||
|
SELECTED="${CANDIDATES[0]}"
|
||||||
|
echo -e "Auto-selected: ${YELLOW}$SELECTED${NC}"
|
||||||
|
else
|
||||||
|
read -p "Select device [1-${#CANDIDATES[@]}]: " -r
|
||||||
|
if [[ ! $REPLY =~ ^[0-9]+$ ]] || [ "$REPLY" -lt 1 ] || [ "$REPLY" -gt ${#CANDIDATES[@]} ]; then
|
||||||
|
echo -e "${RED}Invalid selection.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SELECTED="${CANDIDATES[$((REPLY-1))]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show current partitions
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}Current partitions on $SELECTED:${NC}"
|
||||||
|
lsblk "$SELECTED" -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Unmount any mounted partitions
|
||||||
|
MOUNTED=$(mount | grep "^${SELECTED}" | awk '{print $1}' || true)
|
||||||
|
if [ -n "$MOUNTED" ]; then
|
||||||
|
echo -e "${YELLOW}Unmounting partitions...${NC}"
|
||||||
|
for part in $MOUNTED; do
|
||||||
|
umount "$part" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final confirmation
|
||||||
|
echo -e "${RED}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${RED}║ WARNING: ALL DATA ON THIS DEVICE WILL BE DESTROYED! ║${NC}"
|
||||||
|
echo -e "${RED}║ $SELECTED ║${NC}"
|
||||||
|
echo -e "${RED}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Type 'yes' to continue: " -r
|
||||||
|
if [[ ! $REPLY == "yes" ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Flashing image to $SELECTED...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$COMPRESSED" = true ]; then
|
||||||
|
if [[ "$IMAGE" == *.xz ]]; then
|
||||||
|
pv "$IMAGE" | xzcat | dd of="$SELECTED" bs=4M conv=fsync 2>/dev/null
|
||||||
|
else
|
||||||
|
pv "$IMAGE" | zcat | dd of="$SELECTED" bs=4M conv=fsync 2>/dev/null
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pv "$IMAGE" | dd of="$SELECTED" bs=4M conv=fsync 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Syncing...${NC}"
|
||||||
|
sync
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${GREEN}║ Flash Complete! ║${NC}"
|
||||||
|
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "You can now remove the SD card and boot your Raspberry Pi."
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Tip:${NC} On first boot, SSH in and the setup wizard will run automatically."
|
||||||
|
echo ""
|
||||||
182
rpi/pull-image.sh
Executable file
182
rpi/pull-image.sh
Executable file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Pull Stegasoo image from SD card
|
||||||
|
# Auto-detects SD card, copies with progress, shrinks, and compresses
|
||||||
|
#
|
||||||
|
# Usage: ./pull-image.sh [output-name]
|
||||||
|
# Output will be: stegasoo-rpi-YYYYMMDD.img.xz (or custom name)
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Check for required tools
|
||||||
|
for cmd in dd pv xz lsblk; do
|
||||||
|
if ! command -v $cmd &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: $cmd is required but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check for root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: Must run as root (sudo)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output filename
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
OUTPUT="$1"
|
||||||
|
else
|
||||||
|
OUTPUT="stegasoo-rpi-$(date +%Y%m%d).img.xz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove .xz extension for intermediate file
|
||||||
|
IMG_FILE="${OUTPUT%.xz}"
|
||||||
|
if [[ "$IMG_FILE" == "$OUTPUT" ]]; then
|
||||||
|
IMG_FILE="${OUTPUT}.img"
|
||||||
|
OUTPUT="${OUTPUT}.img.xz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}"
|
||||||
|
echo "╔═══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Stegasoo SD Card Image Puller ║"
|
||||||
|
echo "╚═══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
|
||||||
|
# Auto-detect SD card candidates
|
||||||
|
# Looking for: USB/removable, 8-128GB, not mounted as root filesystem
|
||||||
|
echo -e "${BOLD}Scanning for SD cards...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
declare -a CANDIDATES
|
||||||
|
declare -a CANDIDATE_INFO
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
DEV=$(echo "$line" | awk '{print $1}')
|
||||||
|
SIZE=$(echo "$line" | awk '{print $2}')
|
||||||
|
TYPE=$(echo "$line" | awk '{print $3}')
|
||||||
|
TRAN=$(echo "$line" | awk '{print $4}')
|
||||||
|
MODEL=$(echo "$line" | awk '{print $5" "$6" "$7}' | xargs)
|
||||||
|
|
||||||
|
# Skip if it's the root filesystem
|
||||||
|
if mount | grep -q "^/dev/${DEV}[0-9]* on / "; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip if any partition is mounted as root
|
||||||
|
ROOT_DEV=$(mount | grep " on / " | awk '{print $1}' | sed 's/[0-9]*$//')
|
||||||
|
if [[ "/dev/$DEV" == "$ROOT_DEV" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse size to bytes for comparison
|
||||||
|
SIZE_NUM=$(echo "$SIZE" | sed 's/[^0-9.]//g')
|
||||||
|
SIZE_UNIT=$(echo "$SIZE" | sed 's/[0-9.]//g')
|
||||||
|
|
||||||
|
case $SIZE_UNIT in
|
||||||
|
G) SIZE_GB=$SIZE_NUM ;;
|
||||||
|
T) SIZE_GB=$(echo "$SIZE_NUM * 1024" | bc) ;;
|
||||||
|
M) SIZE_GB=$(echo "scale=2; $SIZE_NUM / 1024" | bc) ;;
|
||||||
|
*) SIZE_GB=0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if size is in SD card range (8GB - 128GB)
|
||||||
|
if (( $(echo "$SIZE_GB >= 8" | bc -l) )) && (( $(echo "$SIZE_GB <= 128" | bc -l) )); then
|
||||||
|
CANDIDATES+=("/dev/$DEV")
|
||||||
|
CANDIDATE_INFO+=("$SIZE $TYPE ${TRAN:-???} $MODEL")
|
||||||
|
fi
|
||||||
|
done < <(lsblk -d -o NAME,SIZE,TYPE,TRAN,MODEL -n | grep "disk")
|
||||||
|
|
||||||
|
if [ ${#CANDIDATES[@]} -eq 0 ]; then
|
||||||
|
echo -e "${RED}No SD card candidates found.${NC}"
|
||||||
|
echo "Looking for USB/removable disks between 8GB and 128GB."
|
||||||
|
echo ""
|
||||||
|
echo "Available disks:"
|
||||||
|
lsblk -d -o NAME,SIZE,TYPE,TRAN,MODEL
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Found ${#CANDIDATES[@]} candidate(s):${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for i in "${!CANDIDATES[@]}"; do
|
||||||
|
echo -e " ${BOLD}[$((i+1))]${NC} ${CANDIDATES[$i]} - ${CANDIDATE_INFO[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ${#CANDIDATES[@]} -eq 1 ]; then
|
||||||
|
SELECTED="${CANDIDATES[0]}"
|
||||||
|
echo -e "Auto-selected: ${YELLOW}$SELECTED${NC}"
|
||||||
|
else
|
||||||
|
read -p "Select device [1-${#CANDIDATES[@]}]: " -r
|
||||||
|
if [[ ! $REPLY =~ ^[0-9]+$ ]] || [ "$REPLY" -lt 1 ] || [ "$REPLY" -gt ${#CANDIDATES[@]} ]; then
|
||||||
|
echo -e "${RED}Invalid selection.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SELECTED="${CANDIDATES[$((REPLY-1))]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show partitions
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}Partitions on $SELECTED:${NC}"
|
||||||
|
lsblk "$SELECTED" -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Final confirmation
|
||||||
|
echo -e "${RED}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${RED}║ WARNING: This will read the ENTIRE device: ║${NC}"
|
||||||
|
echo -e "${RED}║ $SELECTED ║${NC}"
|
||||||
|
echo -e "${RED}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Output: ${YELLOW}$OUTPUT${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get device size for pv
|
||||||
|
DEV_SIZE=$(blockdev --getsize64 "$SELECTED")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}[1/3]${NC} Copying image from $SELECTED..."
|
||||||
|
dd if="$SELECTED" bs=4M status=none | pv -s "$DEV_SIZE" > "$IMG_FILE"
|
||||||
|
sync
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}[2/3]${NC} Shrinking image..."
|
||||||
|
if command -v pishrink.sh &> /dev/null; then
|
||||||
|
pishrink.sh "$IMG_FILE"
|
||||||
|
elif [ -f "./pishrink.sh" ]; then
|
||||||
|
bash ./pishrink.sh "$IMG_FILE"
|
||||||
|
elif [ -f "../pishrink.sh" ]; then
|
||||||
|
bash ../pishrink.sh "$IMG_FILE"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}pishrink.sh not found, skipping shrink step.${NC}"
|
||||||
|
echo "Download from: https://github.com/Drewsif/PiShrink"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}[3/3]${NC} Compressing with xz..."
|
||||||
|
pv "$IMG_FILE" | xz -9 -T0 > "$OUTPUT"
|
||||||
|
rm -f "$IMG_FILE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
FINAL_SIZE=$(du -h "$OUTPUT" | awk '{print $1}')
|
||||||
|
echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${GREEN}║ Image Complete! ║${NC}"
|
||||||
|
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Output: ${YELLOW}$OUTPUT${NC}"
|
||||||
|
echo -e "Size: ${YELLOW}$FINAL_SIZE${NC}"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user