First working version for RasPi Zero 2W
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Configuration
|
||||
# ==============================================================================
|
||||
|
||||
VERSION="3.24.1"
|
||||
BRANCH="3.24"
|
||||
CONFIG_FILE="NenjimHub.conf"
|
||||
|
||||
IMAGE="alpine-rpi-${VERSION}-aarch64.img"
|
||||
HEADLESS_OVERLAY="headless.apkovl.tar.gz"
|
||||
WPA_CONFIG="wpa_supplicant.conf"
|
||||
UNATTENDED_SCRIPT="unattended.sh"
|
||||
ZEROTIER_INSTALLER="installZeroTier.sh"
|
||||
BUILD_TIME_FILE="nenjim-build-time"
|
||||
|
||||
BUILDER_IMAGE="alpine:${BRANCH}"
|
||||
KERNEL_MKSQUASHFS_MEMORY="128M"
|
||||
|
||||
BOOT_MOUNT="/mnt/usb"
|
||||
|
||||
TEMP_DIRECTORY=""
|
||||
SOURCE_MOUNT=""
|
||||
LOOP_DEVICE=""
|
||||
ZEROTIER_PREBUILT_AVAILABLE=0
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Helpers
|
||||
# ==============================================================================
|
||||
|
||||
die()
|
||||
{
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_uint()
|
||||
{
|
||||
local name="$1"
|
||||
local value="${!name-}"
|
||||
|
||||
[[ "$value" =~ ^[0-9]+$ ]] ||
|
||||
die "$name must be a non-negative integer."
|
||||
}
|
||||
|
||||
require_swap_priority()
|
||||
{
|
||||
local name="$1"
|
||||
local value="${!name-}"
|
||||
|
||||
require_uint "$name"
|
||||
(( value <= 32767 )) ||
|
||||
die "$name must be between 0 and 32767."
|
||||
}
|
||||
|
||||
partition_path()
|
||||
{
|
||||
local number="$1"
|
||||
|
||||
if [[ "$DEVICE" =~ [0-9]$ ]]; then
|
||||
printf '%sp%s\n' "$DEVICE" "$number"
|
||||
else
|
||||
printf '%s%s\n' "$DEVICE" "$number"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_block_device()
|
||||
{
|
||||
local path="$1"
|
||||
local attempt
|
||||
|
||||
for attempt in {1..50}; do
|
||||
[[ -b "$path" ]] && return 0
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
set +e
|
||||
|
||||
if [[ -n "$SOURCE_MOUNT" ]] && mountpoint -q "$SOURCE_MOUNT" 2>/dev/null; then
|
||||
umount "$SOURCE_MOUNT"
|
||||
fi
|
||||
|
||||
if mountpoint -q "$BOOT_MOUNT" 2>/dev/null; then
|
||||
umount "$BOOT_MOUNT"
|
||||
fi
|
||||
|
||||
if [[ -n "$LOOP_DEVICE" ]]; then
|
||||
losetup -d "$LOOP_DEVICE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -n "$TEMP_DIRECTORY" && -d "$TEMP_DIRECTORY" ]]; then
|
||||
case "$TEMP_DIRECTORY" in
|
||||
/tmp/nenjim-flash.*)
|
||||
rm -rf -- "$TEMP_DIRECTORY"
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Refusing to remove unexpected path: $TEMP_DIRECTORY" >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
preserve_sysop_home()
|
||||
{
|
||||
local overlay overlay_count extract_root
|
||||
local -a final_overlays=()
|
||||
|
||||
mapfile -t final_overlays < <(
|
||||
find "$BOOT_MOUNT" \
|
||||
-maxdepth 1 \
|
||||
-type f \
|
||||
-name '*.apkovl.tar.gz' \
|
||||
! -name 'headless.apkovl.tar.gz' \
|
||||
-print
|
||||
)
|
||||
|
||||
overlay_count="${#final_overlays[@]}"
|
||||
|
||||
if (( overlay_count == 0 )); then
|
||||
echo "No final APKoVL found; this appears to be a fresh installation."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( overlay_count > 1 )); then
|
||||
printf 'Found more than one final APKoVL:\n' >&2
|
||||
printf ' %s\n' "${final_overlays[@]}" >&2
|
||||
die "Keep only the active final APKoVL before updating partition 1."
|
||||
fi
|
||||
|
||||
overlay="${final_overlays[0]}"
|
||||
extract_root="$TEMP_DIRECTORY/old-overlay"
|
||||
mkdir -p "$extract_root"
|
||||
|
||||
echo "Preserving /sysop from $(basename "$overlay")"
|
||||
|
||||
tar \
|
||||
--extract \
|
||||
--gzip \
|
||||
--file "$overlay" \
|
||||
--directory "$extract_root" \
|
||||
--no-same-owner
|
||||
|
||||
if [[ ! -d "$extract_root/sysop" ]]; then
|
||||
echo "The previous APKoVL does not contain /sysop; nothing to preserve."
|
||||
return 0
|
||||
fi
|
||||
|
||||
tar \
|
||||
--create \
|
||||
--gzip \
|
||||
--file "$TEMP_DIRECTORY/sysop-preserve.tar.gz" \
|
||||
--directory "$extract_root" \
|
||||
sysop
|
||||
}
|
||||
|
||||
run_kernel_builder()
|
||||
{
|
||||
local runtime="$1"
|
||||
local kernel_work="$TEMP_DIRECTORY/kernel-work"
|
||||
|
||||
mkdir -p "$kernel_work"
|
||||
|
||||
"$runtime" run \
|
||||
--rm \
|
||||
--volume "$BOOT_MOUNT:/media/boot" \
|
||||
--volume "$kernel_work:/work" \
|
||||
--env KERNEL_MKSQUASHFS_MEMORY="$KERNEL_MKSQUASHFS_MEMORY" \
|
||||
"$BUILDER_IMAGE" \
|
||||
/bin/sh -euxc '
|
||||
apk add --no-cache alpine-conf alpine-keys squashfs-tools xz
|
||||
|
||||
# update-kernel creates a temporary aarch64 APK root. The
|
||||
# architecture-specific keys are required when the builder
|
||||
# container itself is x86_64. The minimal Docker image can have
|
||||
# the shared key files removed even though alpine-keys is marked
|
||||
# as installed, so restore all package-owned key files first.
|
||||
apk fix --no-cache alpine-keys
|
||||
|
||||
APK_KEYS_DIR=/usr/share/apk/keys
|
||||
find "$APK_KEYS_DIR" -maxdepth 1 -type f -name "*.rsa.pub" -print
|
||||
test -n "$(find "$APK_KEYS_DIR" -maxdepth 1 -type f -name "*.rsa.pub" -print -quit)"
|
||||
apk \
|
||||
--arch aarch64 \
|
||||
--keys-dir "$APK_KEYS_DIR" \
|
||||
update
|
||||
|
||||
MKSQUASHFS_OPTS="-processors 1 -mem $KERNEL_MKSQUASHFS_MEMORY" \
|
||||
TMPDIR=/work \
|
||||
update-kernel \
|
||||
-a aarch64 \
|
||||
-f rpi \
|
||||
-p zfs-rpi \
|
||||
--keys-dir "$APK_KEYS_DIR" \
|
||||
--repositories-file /etc/apk/repositories \
|
||||
-M /media/boot
|
||||
|
||||
test -s /media/boot/boot/vmlinuz-rpi
|
||||
test -s /media/boot/boot/initramfs-rpi
|
||||
test -s /media/boot/boot/modloop-rpi
|
||||
|
||||
unsquashfs -ll /media/boot/boot/modloop-rpi |
|
||||
grep -E "/zfs[.]ko([.](gz|xz|zst))?$"
|
||||
'
|
||||
}
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Initialization and validation
|
||||
# ==============================================================================
|
||||
|
||||
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
|
||||
[[ -f "$CONFIG_FILE" ]] || die "Missing shared configuration: $CONFIG_FILE"
|
||||
# shellcheck source=NenjimHub.conf
|
||||
source "./$CONFIG_FILE"
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "This script must be run as root."
|
||||
|
||||
for name in \
|
||||
BOOT_PARTITION_SIZE_MIB VIRT_ZVOL_SIZE_MIB \
|
||||
SWAP_SIZE_MIB_PART SWAP_SIZE_MIB_ZFS SWAP_SIZE_PCT_ZRAM \
|
||||
DISKLESS_ROOT_SIZE_MIB ZFS_ARC_MAX_MIB ZEROTIER_REQUIRED_SWAP_MIB \
|
||||
ZEROTIER_MAKE_JOBS ZEROTIER_ENABLE_SSO
|
||||
do
|
||||
require_uint "$name"
|
||||
done
|
||||
|
||||
for name in SWAP_PRI_ZRAM SWAP_PRI_ZFS SWAP_PRI_PART; do
|
||||
require_swap_priority "$name"
|
||||
done
|
||||
|
||||
(( BOOT_PARTITION_SIZE_MIB > 0 )) ||
|
||||
die "BOOT_PARTITION_SIZE_MIB must be greater than zero."
|
||||
(( VIRT_ZVOL_SIZE_MIB > 0 )) ||
|
||||
die "VIRT_ZVOL_SIZE_MIB must be greater than zero."
|
||||
(( DISKLESS_ROOT_SIZE_MIB > 0 )) ||
|
||||
die "DISKLESS_ROOT_SIZE_MIB must be greater than zero."
|
||||
(( ZFS_ARC_MAX_MIB > 0 )) ||
|
||||
die "ZFS_ARC_MAX_MIB must be greater than zero."
|
||||
(( ZEROTIER_REQUIRED_SWAP_MIB > 0 )) ||
|
||||
die "ZEROTIER_REQUIRED_SWAP_MIB must be greater than zero."
|
||||
(( ZEROTIER_MAKE_JOBS > 0 )) ||
|
||||
die "ZEROTIER_MAKE_JOBS must be greater than zero."
|
||||
(( ZEROTIER_ENABLE_SSO == 0 || ZEROTIER_ENABLE_SSO == 1 )) ||
|
||||
die "ZEROTIER_ENABLE_SSO must be either 0 or 1."
|
||||
(( SWAP_SIZE_PCT_ZRAM <= 100 )) ||
|
||||
die "SWAP_SIZE_PCT_ZRAM must be between 0 and 100."
|
||||
|
||||
[[ "$NENJIMHUB_HOSTNAME" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$ ]] ||
|
||||
die "NENJIMHUB_HOSTNAME must be one 1-63 character DNS hostname label."
|
||||
[[ "$CONSOLE_KEYMAP_LAYOUT" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]] ||
|
||||
die "Invalid CONSOLE_KEYMAP_LAYOUT: $CONSOLE_KEYMAP_LAYOUT"
|
||||
[[ "$CONSOLE_KEYMAP_VARIANT" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]] ||
|
||||
die "Invalid CONSOLE_KEYMAP_VARIANT: $CONSOLE_KEYMAP_VARIANT"
|
||||
[[ "$ZEROTIER_VERSION" =~ ^[0-9]+([.][0-9]+){2}([._-][A-Za-z0-9]+)*$ ]] ||
|
||||
die "Invalid ZEROTIER_VERSION: $ZEROTIER_VERSION"
|
||||
[[ "$ZEROTIER_PREBUILT_BINARY" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] ||
|
||||
die "ZEROTIER_PREBUILT_BINARY must be a plain filename without directories."
|
||||
|
||||
DISKLESS_ROOT_SIZE="${DISKLESS_ROOT_SIZE_MIB}M"
|
||||
|
||||
for command in \
|
||||
blkid blockdev cp date find install losetup lsblk mkfs.vfat mount mountpoint \
|
||||
sed sync tar udevadm umount
|
||||
do
|
||||
command -v "$command" >/dev/null 2>&1 ||
|
||||
die "Missing required command: $command"
|
||||
done
|
||||
|
||||
for file in \
|
||||
"$CONFIG_FILE" \
|
||||
"$IMAGE" \
|
||||
"$HEADLESS_OVERLAY" \
|
||||
"$WPA_CONFIG" \
|
||||
"$UNATTENDED_SCRIPT" \
|
||||
"$ZEROTIER_INSTALLER"
|
||||
do
|
||||
[[ -f "$file" ]] || die "Missing input file: $file"
|
||||
done
|
||||
|
||||
if [[ -f "$ZEROTIER_PREBUILT_BINARY" ]]; then
|
||||
[[ -s "$ZEROTIER_PREBUILT_BINARY" ]] ||
|
||||
die "Optional ZeroTier binary is empty: $ZEROTIER_PREBUILT_BINARY"
|
||||
ZEROTIER_PREBUILT_AVAILABLE=1
|
||||
echo "Using optional precompiled ZeroTier binary: $ZEROTIER_PREBUILT_BINARY"
|
||||
elif [[ -e "$ZEROTIER_PREBUILT_BINARY" ]]; then
|
||||
die "Optional ZeroTier binary is not a regular file: $ZEROTIER_PREBUILT_BINARY"
|
||||
else
|
||||
echo "No precompiled ZeroTier binary found; the Pi will compile it on first boot."
|
||||
fi
|
||||
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
CONTAINER_RUNTIME="podman"
|
||||
elif command -v docker >/dev/null 2>&1; then
|
||||
CONTAINER_RUNTIME="docker"
|
||||
else
|
||||
die "Podman or Docker is required to build the matched ARM64 kernel/modloop."
|
||||
fi
|
||||
|
||||
[[ -b "$DEVICE" ]] || die "Not a block device: $DEVICE"
|
||||
|
||||
[[ "$(lsblk -dnro PTTYPE "$DEVICE" 2>/dev/null || true)" == "dos" ]] ||
|
||||
die "The card does not use the Raspberry Pi-compatible MBR layout. Run the updated prepare-storage.sh first."
|
||||
|
||||
if mountpoint -q "$BOOT_MOUNT" 2>/dev/null; then
|
||||
die "$BOOT_MOUNT is already in use. Unmount it before continuing."
|
||||
fi
|
||||
|
||||
if lsblk -nrpo MOUNTPOINT "$DEVICE" | grep -q '[^[:space:]]'; then
|
||||
die "A partition on $DEVICE is mounted. Export rpool and unmount the card first."
|
||||
fi
|
||||
|
||||
BOOT_PARTITION="$(partition_path 1)"
|
||||
SWAP_PARTITION="$(partition_path 2)"
|
||||
ZFS_PARTITION="$(partition_path 3)"
|
||||
|
||||
[[ -b "$BOOT_PARTITION" ]] || die "Missing partition 1: run prepare-storage.sh first."
|
||||
[[ -b "$ZFS_PARTITION" ]] || die "Missing ZFS partition 3: run the updated prepare-storage.sh first."
|
||||
|
||||
EXPECTED_BOOT_PARTITION_BYTES=$((BOOT_PARTITION_SIZE_MIB * 1024 * 1024))
|
||||
ACTUAL_BOOT_PARTITION_BYTES="$(blockdev --getsize64 "$BOOT_PARTITION")"
|
||||
[[ "$ACTUAL_BOOT_PARTITION_BYTES" == "$EXPECTED_BOOT_PARTITION_BYTES" ]] ||
|
||||
die "$BOOT_PARTITION is $ACTUAL_BOOT_PARTITION_BYTES bytes, but $CONFIG_FILE specifies $EXPECTED_BOOT_PARTITION_BYTES bytes."
|
||||
|
||||
[[ "$(blkid -s TYPE -o value "$BOOT_PARTITION" 2>/dev/null || true)" == "vfat" ]] ||
|
||||
die "$BOOT_PARTITION is not the expected FAT32 Alpine partition."
|
||||
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
[[ -b "$SWAP_PARTITION" ]] ||
|
||||
die "Missing configured raw swap partition 2: run prepare-storage.sh first."
|
||||
EXPECTED_SWAP_PARTITION_BYTES=$((SWAP_SIZE_MIB_PART * 1024 * 1024))
|
||||
ACTUAL_SWAP_PARTITION_BYTES="$(blockdev --getsize64 "$SWAP_PARTITION")"
|
||||
[[ "$ACTUAL_SWAP_PARTITION_BYTES" == "$EXPECTED_SWAP_PARTITION_BYTES" ]] ||
|
||||
die "$SWAP_PARTITION is $ACTUAL_SWAP_PARTITION_BYTES bytes, but $CONFIG_FILE specifies $EXPECTED_SWAP_PARTITION_BYTES bytes."
|
||||
[[ "$(blkid -s TYPE -o value "$SWAP_PARTITION" 2>/dev/null || true)" == "swap" ]] ||
|
||||
die "$SWAP_PARTITION is not a Linux swap area."
|
||||
[[ "$(blkid -s LABEL -o value "$SWAP_PARTITION" 2>/dev/null || true)" == "$SWAP_PART_LABEL" ]] ||
|
||||
die "$SWAP_PARTITION does not have the expected label $SWAP_PART_LABEL."
|
||||
elif [[ -b "$SWAP_PARTITION" ]]; then
|
||||
die "Partition 2 exists although SWAP_SIZE_MIB_PART=0; rerun prepare-storage.sh with the selected layout."
|
||||
fi
|
||||
|
||||
[[ "$(blkid -s TYPE -o value "$ZFS_PARTITION" 2>/dev/null || true)" == "zfs_member" ]] ||
|
||||
die "$ZFS_PARTITION is not an OpenZFS pool member."
|
||||
|
||||
TEMP_DIRECTORY="$(mktemp -d /tmp/nenjim-flash.XXXXXX)"
|
||||
SOURCE_MOUNT="$TEMP_DIRECTORY/image-boot"
|
||||
mkdir -p "$SOURCE_MOUNT" "$BOOT_MOUNT"
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Confirmation: only partition 1 is replaced
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "Partition 1 (${BOOT_PARTITION}) will be replaced with Alpine ${VERSION}."
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
echo "Partition 2 (${SWAP_PARTITION}) is raw swap and will NOT be changed."
|
||||
else
|
||||
echo "Partition 2 is unused."
|
||||
fi
|
||||
echo "Partition 3 (${ZFS_PARTITION}) and $POOL_NAME will NOT be changed."
|
||||
echo
|
||||
lsblk -f "$DEVICE"
|
||||
echo
|
||||
read -r -p "Type NENJIMHUB to update partition 1: " answer
|
||||
|
||||
[[ "$answer" == "NENJIMHUB" ]] || die "Aborted."
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Attach the Alpine image read-only
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Reading the Alpine image ==="
|
||||
|
||||
LOOP_DEVICE="$(losetup --find --show --partscan --read-only "$IMAGE")"
|
||||
SOURCE_PARTITION="${LOOP_DEVICE}p1"
|
||||
|
||||
wait_for_block_device "$SOURCE_PARTITION" ||
|
||||
die "The image partition did not appear: $SOURCE_PARTITION"
|
||||
|
||||
mount -o ro "$SOURCE_PARTITION" "$SOURCE_MOUNT"
|
||||
|
||||
[[ -f "$SOURCE_MOUNT/cmdline.txt" ]] ||
|
||||
die "The image does not contain a Raspberry Pi cmdline.txt."
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Preserve the rescue home from the previous Alpine overlay
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Preserving the sysop rescue home, if present ==="
|
||||
|
||||
mount "$BOOT_PARTITION" "$BOOT_MOUNT"
|
||||
preserve_sysop_home
|
||||
|
||||
ACTUAL_BOOT_LABEL="$(blkid -s LABEL -o value "$BOOT_PARTITION" 2>/dev/null || true)"
|
||||
BOOT_UUID="$(blkid -s UUID -o value "$BOOT_PARTITION" 2>/dev/null || true)"
|
||||
|
||||
[[ "$ACTUAL_BOOT_LABEL" == "$BOOT_LABEL" ]] ||
|
||||
die "$BOOT_PARTITION has label '${ACTUAL_BOOT_LABEL:-none}', expected '$BOOT_LABEL'."
|
||||
|
||||
umount "$BOOT_MOUNT"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Replace only the FAT32 Alpine partition
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Replacing Alpine partition 1 ==="
|
||||
|
||||
BOOT_VOLUME_ID="$(printf '%s' "$BOOT_UUID" | tr -d '-')"
|
||||
|
||||
if [[ "$BOOT_VOLUME_ID" =~ ^[[:xdigit:]]{8}$ ]]; then
|
||||
mkfs.vfat -F 32 -n "$BOOT_LABEL" -i "$BOOT_VOLUME_ID" "$BOOT_PARTITION"
|
||||
else
|
||||
mkfs.vfat -F 32 -n "$BOOT_LABEL" "$BOOT_PARTITION"
|
||||
fi
|
||||
|
||||
mount "$BOOT_PARTITION" "$BOOT_MOUNT"
|
||||
cp -a "$SOURCE_MOUNT/." "$BOOT_MOUNT/"
|
||||
|
||||
CMDLINE_FILE="$BOOT_MOUNT/cmdline.txt"
|
||||
CMDLINE_WITHOUT_ROOTFLAGS="$(
|
||||
sed 's/rootflags=[^[:space:]]*[[:space:]]*//g' "$CMDLINE_FILE"
|
||||
)"
|
||||
|
||||
printf '%s rootflags=size=%s\n' \
|
||||
"$CMDLINE_WITHOUT_ROOTFLAGS" \
|
||||
"$DISKLESS_ROOT_SIZE" \
|
||||
>"$CMDLINE_FILE"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Build a matched Raspberry Pi kernel/modloop with zfs-rpi on the PC
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Adding Alpine's matched Raspberry Pi OpenZFS module ==="
|
||||
|
||||
if ! run_kernel_builder "$CONTAINER_RUNTIME"; then
|
||||
die "Kernel/modloop build failed. The SD card is not ready to boot."
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Install first-boot inputs
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Installing unattended first-boot files ==="
|
||||
|
||||
install -m 0644 \
|
||||
"$HEADLESS_OVERLAY" \
|
||||
"$BOOT_MOUNT/headless.apkovl.tar.gz"
|
||||
|
||||
install -m 0600 \
|
||||
"$WPA_CONFIG" \
|
||||
"$BOOT_MOUNT/wpa_supplicant.conf"
|
||||
|
||||
install -m 0755 \
|
||||
"$UNATTENDED_SCRIPT" \
|
||||
"$BOOT_MOUNT/unattended.sh"
|
||||
|
||||
install -m 0755 \
|
||||
"$ZEROTIER_INSTALLER" \
|
||||
"$BOOT_MOUNT/installZeroTier.sh"
|
||||
|
||||
install -m 0644 \
|
||||
"$CONFIG_FILE" \
|
||||
"$BOOT_MOUNT/NenjimHub.conf"
|
||||
|
||||
if (( ZEROTIER_PREBUILT_AVAILABLE == 1 )); then
|
||||
install -m 0755 \
|
||||
"$ZEROTIER_PREBUILT_BINARY" \
|
||||
"$BOOT_MOUNT/$ZEROTIER_PREBUILT_BINARY"
|
||||
fi
|
||||
|
||||
# Raspberry Pi Zero 2 W has no battery-backed RTC. Give first boot a recent,
|
||||
# trusted starting point before it contacts HTTPS package repositories.
|
||||
date -u '+%Y-%m-%d %H:%M:%S' >"$BOOT_MOUNT/$BUILD_TIME_FILE"
|
||||
chmod 0644 "$BOOT_MOUNT/$BUILD_TIME_FILE"
|
||||
|
||||
if [[ -f "$TEMP_DIRECTORY/sysop-preserve.tar.gz" ]]; then
|
||||
install -m 0600 \
|
||||
"$TEMP_DIRECTORY/sysop-preserve.tar.gz" \
|
||||
"$BOOT_MOUNT/sysop-preserve.tar.gz"
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Verification and finish
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Verifying and syncing partition 1 ==="
|
||||
|
||||
for boot_file in \
|
||||
bootcode.bin \
|
||||
config.txt \
|
||||
fixup.dat \
|
||||
start.elf \
|
||||
boot/vmlinuz-rpi \
|
||||
boot/initramfs-rpi \
|
||||
boot/modloop-rpi \
|
||||
headless.apkovl.tar.gz \
|
||||
unattended.sh \
|
||||
installZeroTier.sh \
|
||||
NenjimHub.conf \
|
||||
nenjim-build-time \
|
||||
wpa_supplicant.conf
|
||||
do
|
||||
[[ -s "$BOOT_MOUNT/$boot_file" ]] ||
|
||||
die "Missing prepared boot file: $boot_file"
|
||||
done
|
||||
|
||||
|
||||
if (( ZEROTIER_PREBUILT_AVAILABLE == 1 )); then
|
||||
[[ -s "$BOOT_MOUNT/$ZEROTIER_PREBUILT_BINARY" ]] ||
|
||||
die "Missing copied ZeroTier binary: $ZEROTIER_PREBUILT_BINARY"
|
||||
fi
|
||||
|
||||
sync
|
||||
umount "$SOURCE_MOUNT"
|
||||
umount "$BOOT_MOUNT"
|
||||
losetup -d "$LOOP_DEVICE"
|
||||
LOOP_DEVICE=""
|
||||
|
||||
cleanup
|
||||
trap - EXIT
|
||||
|
||||
echo
|
||||
echo "============================================================================"
|
||||
echo "Alpine partition 1 is ready. $POOL_NAME on partition 3 was left untouched."
|
||||
if (( ZEROTIER_PREBUILT_AVAILABLE == 1 )); then
|
||||
echo "ZeroTier $ZEROTIER_VERSION will use the supplied precompiled binary."
|
||||
else
|
||||
echo "ZeroTier $ZEROTIER_VERSION will be compiled by the Pi."
|
||||
fi
|
||||
echo
|
||||
echo "First boot performs the unattended setup and reboots."
|
||||
echo "After the second boot, use the sysop rescue/admin account:"
|
||||
echo " user: sysop"
|
||||
echo " password: sysop"
|
||||
echo "============================================================================"
|
||||
Executable
+507
@@ -0,0 +1,507 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Configuration (may be overridden by unattended.sh)
|
||||
# ==============================================================================
|
||||
|
||||
ZEROTIER_VERSION="${ZEROTIER_VERSION:-1.16.2}"
|
||||
SOFTWARE_ROOT="${SOFTWARE_ROOT:-/usr/local/software}"
|
||||
SYSOP_DATA_ROOT="${SYSOP_DATA_ROOT:-/media/sysop}"
|
||||
REQUIRED_SWAP_MIB="${REQUIRED_SWAP_MIB:-2048}"
|
||||
ROOTFS_BUILD_SIZE="${ROOTFS_BUILD_SIZE:-1400M}"
|
||||
MAKE_JOBS="${MAKE_JOBS:-1}"
|
||||
ENABLE_SSO="${ENABLE_SSO:-0}"
|
||||
PREBUILT_BINARY="${PREBUILT_BINARY:-}"
|
||||
|
||||
ZEROTIER_ROOT="$SOFTWARE_ROOT/zerotier"
|
||||
ZEROTIER_VERSIONS="$ZEROTIER_ROOT/versions"
|
||||
ZEROTIER_BUILD_PROFILE="${ZEROTIER_VERSION}-linux-musl-aarch64-sso${ENABLE_SSO}"
|
||||
ZEROTIER_INSTALL="$ZEROTIER_VERSIONS/$ZEROTIER_BUILD_PROFILE"
|
||||
ZEROTIER_STATE="$SYSOP_DATA_ROOT/zerotier"
|
||||
|
||||
BUILD_DIRECTORY="$SOFTWARE_ROOT/.zerotier-build-$ZEROTIER_BUILD_PROFILE"
|
||||
SOURCE_DIRECTORY="$BUILD_DIRECTORY/ZeroTierOne"
|
||||
|
||||
BUILD_DEPS=(
|
||||
build-base
|
||||
linux-headers
|
||||
openssl-dev
|
||||
pkgconf
|
||||
)
|
||||
|
||||
if [[ "$ENABLE_SSO" == "1" ]]; then
|
||||
BUILD_DEPS+=(cargo)
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Helpers and cleanup
|
||||
# ==============================================================================
|
||||
|
||||
BUILD_DEPS_INSTALLED=0
|
||||
BUILD_REQUIRED=1
|
||||
PREBUILT_AVAILABLE=0
|
||||
STATE_WAS_PRESENT=0
|
||||
|
||||
die()
|
||||
{
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
safe_remove_build_directory()
|
||||
{
|
||||
case "$BUILD_DIRECTORY" in
|
||||
"$SOFTWARE_ROOT"/.zerotier-build-*)
|
||||
rm -rf -- "$BUILD_DIRECTORY"
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Refusing to remove unexpected path: $BUILD_DIRECTORY" >&2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
release_build_resources()
|
||||
{
|
||||
local result=0
|
||||
|
||||
if (( BUILD_DEPS_INSTALLED == 1 )); then
|
||||
echo "=== Removing ZeroTier build dependencies ==="
|
||||
|
||||
if apk del .zerotier-builddeps; then
|
||||
BUILD_DEPS_INSTALLED=0
|
||||
else
|
||||
result=1
|
||||
fi
|
||||
fi
|
||||
|
||||
safe_remove_build_directory
|
||||
return "$result"
|
||||
}
|
||||
|
||||
cleanup_on_exit()
|
||||
{
|
||||
local status=$?
|
||||
|
||||
trap - EXIT
|
||||
set +e
|
||||
release_build_resources
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
migrate_directory_to_state()
|
||||
{
|
||||
local source="$1"
|
||||
|
||||
if [[ -L "$source" ]]; then
|
||||
rm -f -- "$source"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -d "$source" ]]; then
|
||||
cp -a "$source/." "$ZEROTIER_STATE/" 2>/dev/null || true
|
||||
rm -rf -- "$source"
|
||||
elif [[ -e "$source" ]]; then
|
||||
die "Refusing to replace non-directory path: $source"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup_on_exit EXIT
|
||||
trap 'exit 129' HUP
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Validate the Alpine diskless/ZFS environment
|
||||
# ==============================================================================
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "This installer must be run as root."
|
||||
|
||||
for command in apk git lbu mount rc-update
|
||||
do
|
||||
command -v "$command" >/dev/null 2>&1 ||
|
||||
die "Missing required command: $command"
|
||||
done
|
||||
|
||||
[[ -d "$SOFTWARE_ROOT" ]] || die "Missing software dataset mount: $SOFTWARE_ROOT"
|
||||
[[ -d "$SYSOP_DATA_ROOT" ]] || die "Missing sysop dataset mount: $SYSOP_DATA_ROOT"
|
||||
|
||||
SOFTWARE_FILESYSTEM="$({
|
||||
awk -v path="$SOFTWARE_ROOT" '$2 == path { print $3; exit }' /proc/mounts
|
||||
} || true)"
|
||||
|
||||
[[ "$SOFTWARE_FILESYSTEM" == "zfs" ]] ||
|
||||
die "$SOFTWARE_ROOT must be a mounted ZFS dataset, not '${SOFTWARE_FILESYSTEM:-unknown}'."
|
||||
|
||||
case "$REQUIRED_SWAP_MIB" in
|
||||
''|*[!0-9]*) die "REQUIRED_SWAP_MIB must be a positive integer." ;;
|
||||
esac
|
||||
|
||||
case "$MAKE_JOBS" in
|
||||
''|*[!0-9]*) die "MAKE_JOBS must be a positive integer." ;;
|
||||
esac
|
||||
|
||||
case "$ENABLE_SSO" in
|
||||
0|1) ;;
|
||||
*) die "ENABLE_SSO must be either 0 or 1." ;;
|
||||
esac
|
||||
|
||||
(( REQUIRED_SWAP_MIB > 0 )) || die "REQUIRED_SWAP_MIB must be greater than zero."
|
||||
(( MAKE_JOBS > 0 )) || die "MAKE_JOBS must be greater than zero."
|
||||
|
||||
if [[ -x "$ZEROTIER_INSTALL/zerotier-one" ]]; then
|
||||
EXISTING_VERSION="$(
|
||||
"$ZEROTIER_INSTALL/zerotier-one" -v 2>/dev/null || true
|
||||
)"
|
||||
|
||||
if [[ "$EXISTING_VERSION" == "$ZEROTIER_VERSION" ]]; then
|
||||
BUILD_REQUIRED=0
|
||||
else
|
||||
echo "WARNING: Existing ZeroTier binary reports version '${EXISTING_VERSION:-unknown}'; replacing it."
|
||||
fi
|
||||
fi
|
||||
|
||||
if (( BUILD_REQUIRED == 1 )) &&
|
||||
[[ -n "$PREBUILT_BINARY" && -s "$PREBUILT_BINARY" ]]
|
||||
then
|
||||
PREBUILT_AVAILABLE=1
|
||||
BUILD_REQUIRED=0
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Check swap and free space when compilation is needed
|
||||
# ==============================================================================
|
||||
|
||||
if (( BUILD_REQUIRED == 1 )); then
|
||||
ACTIVE_SWAP_KB="$(awk 'NR > 1 { total += $3 } END { print total + 0 }' /proc/swaps)"
|
||||
REQUIRED_SWAP_KB=$((REQUIRED_SWAP_MIB * 1024))
|
||||
|
||||
(( ACTIVE_SWAP_KB >= REQUIRED_SWAP_KB )) ||
|
||||
die "ZeroTier compilation requires at least ${REQUIRED_SWAP_MIB} MiB active swap."
|
||||
|
||||
AVAILABLE_KB="$(df -Pk "$SOFTWARE_ROOT" | awk 'NR == 2 { print $4 }')"
|
||||
REQUIRED_FREE_KB=$((1200 * 1024))
|
||||
|
||||
(( AVAILABLE_KB >= REQUIRED_FREE_KB )) ||
|
||||
die "At least 1200 MB must be free on $SOFTWARE_ROOT for the ZeroTier build."
|
||||
|
||||
echo
|
||||
echo "=== Reusing all active swap devices for compilation ==="
|
||||
grep -E 'MemTotal|SwapTotal|SwapFree' /proc/meminfo
|
||||
elif (( PREBUILT_AVAILABLE == 1 )); then
|
||||
echo
|
||||
echo "=== Using precompiled ZeroTier ${ZEROTIER_VERSION} binary ==="
|
||||
echo "Source: $PREBUILT_BINARY"
|
||||
else
|
||||
echo
|
||||
echo "=== Reusing the existing ZeroTier ${ZEROTIER_VERSION} binary ==="
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Enlarge the temporary RAM root filesystem
|
||||
# ==============================================================================
|
||||
|
||||
if (( BUILD_REQUIRED == 1 )); then
|
||||
ROOT_FILESYSTEM="$(awk '$2 == "/" { print $3; exit }' /proc/mounts)"
|
||||
|
||||
if [[ "$ROOT_FILESYSTEM" == "tmpfs" ]]; then
|
||||
echo
|
||||
echo "=== Enlarging temporary rootfs limit to $ROOTFS_BUILD_SIZE ==="
|
||||
mount -o "remount,size=$ROOTFS_BUILD_SIZE" /
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Install build-only packages
|
||||
# ==============================================================================
|
||||
|
||||
if (( BUILD_REQUIRED == 1 )); then
|
||||
echo
|
||||
echo "=== Installing ZeroTier build dependencies ==="
|
||||
|
||||
if [[ "$ENABLE_SSO" == "1" ]]; then
|
||||
echo "ZeroTier SSO is enabled; Rust/Cargo will be installed."
|
||||
else
|
||||
echo "ZeroTier SSO is disabled; Rust/Cargo are not required."
|
||||
fi
|
||||
|
||||
# --no-cache keeps the compiler toolchain out of the persistent APK cache.
|
||||
# Retry transient Wi-Fi/download failures, cleaning a partial virtual
|
||||
# package before each new attempt.
|
||||
APK_ADD_SUCCEEDED=0
|
||||
for attempt in 1 2 3; do
|
||||
if apk add \
|
||||
--no-cache \
|
||||
--virtual .zerotier-builddeps \
|
||||
"${BUILD_DEPS[@]}"
|
||||
then
|
||||
APK_ADD_SUCCEEDED=1
|
||||
break
|
||||
fi
|
||||
|
||||
echo "WARNING: Build dependency installation attempt $attempt failed."
|
||||
apk del .zerotier-builddeps >/dev/null 2>&1 || true
|
||||
|
||||
if (( attempt < 3 )); then
|
||||
echo "Retrying after $((attempt * 10)) seconds..."
|
||||
sleep $((attempt * 10))
|
||||
fi
|
||||
done
|
||||
|
||||
(( APK_ADD_SUCCEEDED == 1 )) ||
|
||||
die "Could not install ZeroTier build dependencies after 3 attempts."
|
||||
|
||||
BUILD_DEPS_INSTALLED=1
|
||||
|
||||
for command in gcc g++ make pkgconf strip
|
||||
do
|
||||
command -v "$command" >/dev/null 2>&1 ||
|
||||
die "Build dependency did not provide required command: $command"
|
||||
done
|
||||
|
||||
if [[ "$ENABLE_SSO" == "1" ]]; then
|
||||
command -v cargo >/dev/null 2>&1 ||
|
||||
die "SSO build dependency did not provide cargo."
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Download the exact pinned ZeroTier source tag
|
||||
# ==============================================================================
|
||||
|
||||
if (( BUILD_REQUIRED == 1 )); then
|
||||
echo
|
||||
echo "=== Downloading ZeroTier ${ZEROTIER_VERSION} source ==="
|
||||
|
||||
safe_remove_build_directory
|
||||
mkdir -p "$BUILD_DIRECTORY"
|
||||
|
||||
git init "$SOURCE_DIRECTORY"
|
||||
git -C "$SOURCE_DIRECTORY" remote add \
|
||||
origin \
|
||||
https://github.com/zerotier/ZeroTierOne.git
|
||||
|
||||
# Fetch the exact tag ref. This avoids both the earlier git-describe error
|
||||
# on a shallow checkout and ambiguity between branch and tag names.
|
||||
git -C "$SOURCE_DIRECTORY" fetch \
|
||||
--depth 1 \
|
||||
origin \
|
||||
"refs/tags/$ZEROTIER_VERSION:refs/tags/$ZEROTIER_VERSION"
|
||||
|
||||
git -C "$SOURCE_DIRECTORY" checkout \
|
||||
--detach \
|
||||
"refs/tags/$ZEROTIER_VERSION"
|
||||
|
||||
ACTUAL_COMMIT="$(git -C "$SOURCE_DIRECTORY" rev-parse HEAD)"
|
||||
TAG_COMMIT="$(
|
||||
git -C "$SOURCE_DIRECTORY" \
|
||||
rev-parse --verify "refs/tags/$ZEROTIER_VERSION^{commit}"
|
||||
)"
|
||||
|
||||
[[ "$ACTUAL_COMMIT" == "$TAG_COMMIT" ]] ||
|
||||
die "Checked-out commit does not match tag $ZEROTIER_VERSION."
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Build conservatively for the Raspberry Pi Zero 2W
|
||||
# ==============================================================================
|
||||
|
||||
if (( BUILD_REQUIRED == 1 )); then
|
||||
echo
|
||||
echo "=== Building ZeroTier with ${MAKE_JOBS} make job(s) ==="
|
||||
|
||||
(
|
||||
cd "$SOURCE_DIRECTORY"
|
||||
|
||||
# BCM2710A1 does not provide the optional ARMv8 crypto extensions that
|
||||
# ZeroTier's aarch64 makefile otherwise enables by default.
|
||||
sed -i \
|
||||
's/-march=armv8-a+crypto/-march=armv8-a/g' \
|
||||
make-linux.mk
|
||||
|
||||
if grep -q -- '-march=armv8-a+crypto' make-linux.mk; then
|
||||
die "Could not disable ARM crypto-extension instructions."
|
||||
fi
|
||||
|
||||
export TMPDIR="$BUILD_DIRECTORY/tmp"
|
||||
|
||||
if [[ "$ENABLE_SSO" == "1" ]]; then
|
||||
export CARGO_BUILD_JOBS=1
|
||||
export CARGO_INCREMENTAL=0
|
||||
export CARGO_HOME="$BUILD_DIRECTORY/cargo-home"
|
||||
mkdir -p "$CARGO_HOME"
|
||||
fi
|
||||
|
||||
mkdir -p "$TMPDIR"
|
||||
|
||||
make \
|
||||
-j "$MAKE_JOBS" \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
ZT_CONTROLLER=0 \
|
||||
ZT_SSO_SUPPORTED="$ENABLE_SSO" \
|
||||
one
|
||||
|
||||
strip zerotier-one
|
||||
)
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Install the binary on rpool/software
|
||||
# ==============================================================================
|
||||
|
||||
if (( PREBUILT_AVAILABLE == 1 )); then
|
||||
echo
|
||||
echo "=== Installing precompiled ZeroTier on rpool/software ==="
|
||||
|
||||
mkdir -p "$ZEROTIER_INSTALL"
|
||||
install -m 0755 \
|
||||
"$PREBUILT_BINARY" \
|
||||
"$ZEROTIER_INSTALL/zerotier-one"
|
||||
elif (( BUILD_REQUIRED == 1 )); then
|
||||
echo
|
||||
echo "=== Installing ZeroTier on rpool/software ==="
|
||||
|
||||
mkdir -p "$ZEROTIER_INSTALL"
|
||||
install -m 0755 \
|
||||
"$SOURCE_DIRECTORY/zerotier-one" \
|
||||
"$ZEROTIER_INSTALL/zerotier-one"
|
||||
fi
|
||||
|
||||
mkdir -p "$ZEROTIER_ROOT"
|
||||
ln -sfn "versions/$ZEROTIER_BUILD_PROFILE" "$ZEROTIER_ROOT/current"
|
||||
|
||||
rm -f \
|
||||
/usr/sbin/zerotier-one \
|
||||
/usr/sbin/zerotier-cli \
|
||||
/usr/sbin/zerotier-idtool
|
||||
|
||||
ln -s "$ZEROTIER_ROOT/current/zerotier-one" /usr/sbin/zerotier-one
|
||||
ln -s zerotier-one /usr/sbin/zerotier-cli
|
||||
ln -s zerotier-one /usr/sbin/zerotier-idtool
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Keep identity and future network state under sysop's ZFS-backed area
|
||||
# ==============================================================================
|
||||
|
||||
if [[ -d "$ZEROTIER_STATE" ]] &&
|
||||
find "$ZEROTIER_STATE" -mindepth 1 -print -quit | grep -q .
|
||||
then
|
||||
STATE_WAS_PRESENT=1
|
||||
fi
|
||||
|
||||
mkdir -p "$ZEROTIER_STATE" /sysop /var/lib
|
||||
chmod 0700 "$ZEROTIER_STATE"
|
||||
|
||||
if id sysop >/dev/null 2>&1; then
|
||||
chown sysop:sysop "$ZEROTIER_STATE"
|
||||
fi
|
||||
|
||||
migrate_directory_to_state /sysop/zerotier
|
||||
migrate_directory_to_state /var/lib/zerotier-one
|
||||
|
||||
ln -s "$ZEROTIER_STATE" /sysop/zerotier
|
||||
ln -s /sysop/zerotier /var/lib/zerotier-one
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# OpenRC service
|
||||
# ==============================================================================
|
||||
|
||||
cat >/etc/init.d/zerotier-one <<'OPENRC'
|
||||
#!/sbin/openrc-run
|
||||
|
||||
description="ZeroTier One network virtualization service"
|
||||
|
||||
command="/usr/sbin/zerotier-one"
|
||||
command_background="yes"
|
||||
pidfile="/run/${RC_SVCNAME}.pid"
|
||||
|
||||
depend()
|
||||
{
|
||||
need zfs-mount
|
||||
use net logger
|
||||
after networking
|
||||
}
|
||||
|
||||
start_pre()
|
||||
{
|
||||
if [ ! -d /var/lib/zerotier-one ]; then
|
||||
eerror "Persistent ZeroTier state is unavailable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
modprobe tun >/dev/null 2>&1 || true
|
||||
}
|
||||
OPENRC
|
||||
|
||||
chmod 0755 /etc/init.d/zerotier-one
|
||||
|
||||
grep -qxF tun /etc/modules 2>/dev/null || echo tun >>/etc/modules
|
||||
rc-update add zerotier-one default
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Persist diskless symlinks and service files
|
||||
# ==============================================================================
|
||||
|
||||
for path in \
|
||||
/usr/sbin/zerotier-one \
|
||||
/usr/sbin/zerotier-cli \
|
||||
/usr/sbin/zerotier-idtool \
|
||||
/sysop/zerotier \
|
||||
/var/lib/zerotier-one \
|
||||
/etc/init.d/zerotier-one
|
||||
do
|
||||
lbu include "$path"
|
||||
done
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Verify without joining a network
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Verifying the ZeroTier installation ==="
|
||||
|
||||
if ! INSTALLED_VERSION="$(/usr/sbin/zerotier-one -v 2>/dev/null)"; then
|
||||
die "The ZeroTier binary cannot run on this Raspberry Pi/Alpine system."
|
||||
fi
|
||||
|
||||
echo "$INSTALLED_VERSION"
|
||||
[[ "$INSTALLED_VERSION" == "$ZEROTIER_VERSION" ]] ||
|
||||
die "ZeroTier reports version '$INSTALLED_VERSION', expected '$ZEROTIER_VERSION'."
|
||||
|
||||
if ldd /usr/sbin/zerotier-one 2>&1 | grep -q 'not found'; then
|
||||
ldd /usr/sbin/zerotier-one >&2 || true
|
||||
die "The ZeroTier binary has missing runtime libraries."
|
||||
fi
|
||||
|
||||
if (( STATE_WAS_PRESENT == 0 )) && [[ -d "$ZEROTIER_STATE/networks.d" ]]; then
|
||||
die "A fresh installation unexpectedly contains joined-network configuration."
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Remove toolchain and source tree
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
release_build_resources
|
||||
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
echo
|
||||
echo "ZeroTier ${ZEROTIER_VERSION} is installed and enabled for the next boot."
|
||||
echo "Existing identity/configuration is preserved; a fresh install is not joined."
|
||||
Executable
+530
@@ -0,0 +1,530 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Configuration
|
||||
# ==============================================================================
|
||||
|
||||
CONFIG_FILE="NenjimHub.conf"
|
||||
|
||||
JAVA_ARCHIVE="zulu25.36.205-ca-jre25.0.4.1-linux_musl_aarch64.tar.gz"
|
||||
JAVA_DIRECTORY="zulu25.36.205-ca-jre25.0.4.1-linux_musl_aarch64"
|
||||
JAVA_VERSION_DIRECTORY="jre-25.0.4.1"
|
||||
JAVA_MAJOR_DIRECTORY="jre-25"
|
||||
|
||||
ZREPL_VERSION="v0.7.0"
|
||||
ZREPL_ASSET="zrepl-linux-arm64"
|
||||
ZREPL_LOCAL_BINARY="$ZREPL_ASSET"
|
||||
|
||||
# Cards marketed as 16 GB are commonly a little smaller than 16,000,000,000
|
||||
# usable bytes. 14 GiB actual capacity is the baseline; the configured
|
||||
# partition and zvol sizes are also checked below.
|
||||
MINIMUM_DEVICE_BYTES=$((14 * 1024 * 1024 * 1024))
|
||||
|
||||
# Raspberry Pi Zero 2 W boots most reliably from a classic MBR partition table.
|
||||
# 0x0c is FAT32 LBA, 0x82 is Linux swap, and 0xbf identifies Solaris/ZFS.
|
||||
BOOT_PARTITION_TYPE="c"
|
||||
SWAP_PARTITION_TYPE="82"
|
||||
ZFS_PARTITION_TYPE="bf"
|
||||
|
||||
TEMP_DIRECTORY=""
|
||||
STAGING_ROOT=""
|
||||
VIRT_MOUNT=""
|
||||
POOL_CREATED=0
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Helpers
|
||||
# ==============================================================================
|
||||
|
||||
die()
|
||||
{
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_uint()
|
||||
{
|
||||
local name="$1"
|
||||
local value="${!name-}"
|
||||
|
||||
[[ "$value" =~ ^[0-9]+$ ]] ||
|
||||
die "$name must be a non-negative integer."
|
||||
}
|
||||
|
||||
require_swap_priority()
|
||||
{
|
||||
local name="$1"
|
||||
local value="${!name-}"
|
||||
|
||||
require_uint "$name"
|
||||
(( value <= 32767 )) ||
|
||||
die "$name must be between 0 and 32767."
|
||||
}
|
||||
|
||||
partition_path()
|
||||
{
|
||||
local number="$1"
|
||||
|
||||
if [[ "$DEVICE" =~ [0-9]$ ]]; then
|
||||
printf '%sp%s\n' "$DEVICE" "$number"
|
||||
else
|
||||
printf '%s%s\n' "$DEVICE" "$number"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_block_device()
|
||||
{
|
||||
local path="$1"
|
||||
local attempt
|
||||
|
||||
for attempt in {1..50}; do
|
||||
[[ -b "$path" ]] && return 0
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
set +e
|
||||
|
||||
if [[ -n "$VIRT_MOUNT" ]] && mountpoint -q "$VIRT_MOUNT" 2>/dev/null; then
|
||||
umount "$VIRT_MOUNT"
|
||||
fi
|
||||
|
||||
if (( POOL_CREATED == 1 )) && zpool list -H -o name "$POOL_NAME" >/dev/null 2>&1; then
|
||||
zpool export "$POOL_NAME"
|
||||
fi
|
||||
|
||||
if [[ -n "$TEMP_DIRECTORY" && -d "$TEMP_DIRECTORY" ]]; then
|
||||
case "$TEMP_DIRECTORY" in
|
||||
/tmp/nenjim-storage.*)
|
||||
rm -rf -- "$TEMP_DIRECTORY"
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Refusing to remove unexpected path: $TEMP_DIRECTORY" >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
download_zrepl()
|
||||
{
|
||||
local destination="$1"
|
||||
local metadata asset_data asset_url asset_digest actual_digest
|
||||
|
||||
echo "Downloading official zrepl ${ZREPL_VERSION} ARM64 binary"
|
||||
|
||||
metadata="$({
|
||||
curl \
|
||||
--fail \
|
||||
--location \
|
||||
--proto '=https' \
|
||||
--silent \
|
||||
--show-error \
|
||||
"https://api.github.com/repos/zrepl/zrepl/releases/tags/${ZREPL_VERSION}"
|
||||
})"
|
||||
|
||||
asset_data="$({
|
||||
printf '%s' "$metadata" |
|
||||
ZREPL_ASSET="$ZREPL_ASSET" python3 -c '
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
release = json.load(sys.stdin)
|
||||
name = os.environ["ZREPL_ASSET"]
|
||||
|
||||
for asset in release.get("assets", []):
|
||||
if asset.get("name") == name:
|
||||
print(asset.get("browser_download_url", ""))
|
||||
print(asset.get("digest", ""))
|
||||
break
|
||||
else:
|
||||
raise SystemExit(f"Release asset not found: {name}")
|
||||
'
|
||||
})" || die "Could not read the zrepl release metadata."
|
||||
|
||||
asset_url="$(printf '%s\n' "$asset_data" | sed -n '1p')"
|
||||
asset_digest="$(printf '%s\n' "$asset_data" | sed -n '2p')"
|
||||
|
||||
[[ "$asset_url" == https://github.com/zrepl/zrepl/releases/download/* ]] ||
|
||||
die "Unexpected zrepl download URL returned by GitHub."
|
||||
|
||||
[[ "$asset_digest" =~ ^sha256:[[:xdigit:]]{64}$ ]] ||
|
||||
die "GitHub did not provide a SHA-256 digest for $ZREPL_ASSET."
|
||||
|
||||
curl \
|
||||
--fail \
|
||||
--location \
|
||||
--proto '=https' \
|
||||
--silent \
|
||||
--show-error \
|
||||
--output "$destination" \
|
||||
"$asset_url"
|
||||
|
||||
actual_digest="sha256:$(sha256sum "$destination" | awk '{print $1}')"
|
||||
|
||||
[[ "$actual_digest" == "$asset_digest" ]] ||
|
||||
die "SHA-256 validation failed for the downloaded zrepl binary."
|
||||
}
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Initialization and validation
|
||||
# ==============================================================================
|
||||
|
||||
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
|
||||
[[ -f "$CONFIG_FILE" ]] || die "Missing shared configuration: $CONFIG_FILE"
|
||||
# shellcheck source=NenjimHub.conf
|
||||
source "./$CONFIG_FILE"
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "This script must be run as root."
|
||||
|
||||
for command in \
|
||||
awk blkid blockdev curl file findmnt install lsblk mkfs.ext4 mkfs.vfat mkswap \
|
||||
mount mountpoint partprobe python3 sed sfdisk sha256sum tar udevadm \
|
||||
umount wipefs zfs zpool
|
||||
do
|
||||
command -v "$command" >/dev/null 2>&1 ||
|
||||
die "Missing required command: $command"
|
||||
done
|
||||
|
||||
[[ -f "$JAVA_ARCHIVE" ]] || die "Missing input file: $JAVA_ARCHIVE"
|
||||
[[ -b "$DEVICE" ]] || die "Not a block device: $DEVICE"
|
||||
|
||||
for name in \
|
||||
BOOT_PARTITION_SIZE_MIB VIRT_ZVOL_SIZE_MIB \
|
||||
SWAP_SIZE_MIB_PART SWAP_SIZE_MIB_ZFS SWAP_SIZE_PCT_ZRAM
|
||||
do
|
||||
require_uint "$name"
|
||||
done
|
||||
|
||||
for name in SWAP_PRI_ZRAM SWAP_PRI_ZFS SWAP_PRI_PART; do
|
||||
require_swap_priority "$name"
|
||||
done
|
||||
|
||||
(( BOOT_PARTITION_SIZE_MIB > 0 )) ||
|
||||
die "BOOT_PARTITION_SIZE_MIB must be greater than zero."
|
||||
(( VIRT_ZVOL_SIZE_MIB > 0 )) ||
|
||||
die "VIRT_ZVOL_SIZE_MIB must be greater than zero."
|
||||
(( SWAP_SIZE_PCT_ZRAM <= 100 )) ||
|
||||
die "SWAP_SIZE_PCT_ZRAM must be between 0 and 100."
|
||||
|
||||
[[ "$NENJIMHUB_HOSTNAME" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$ ]] ||
|
||||
die "NENJIMHUB_HOSTNAME must be one 1-63 character DNS hostname label."
|
||||
[[ "$CONSOLE_KEYMAP_LAYOUT" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]] ||
|
||||
die "Invalid CONSOLE_KEYMAP_LAYOUT: $CONSOLE_KEYMAP_LAYOUT"
|
||||
[[ "$CONSOLE_KEYMAP_VARIANT" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]] ||
|
||||
die "Invalid CONSOLE_KEYMAP_VARIANT: $CONSOLE_KEYMAP_VARIANT"
|
||||
[[ "$BOOT_LABEL" =~ ^[[:alnum:]_-]{1,11}$ ]] ||
|
||||
die "BOOT_LABEL must be 1-11 letters, digits, underscores or hyphens."
|
||||
[[ "$SWAP_PART_LABEL" =~ ^[[:alnum:]_-]{1,15}$ ]] ||
|
||||
die "SWAP_PART_LABEL must be 1-15 letters, digits, underscores or hyphens."
|
||||
|
||||
DEVICE_BYTES="$(blockdev --getsize64 "$DEVICE")"
|
||||
|
||||
CONFIGURED_MINIMUM_DEVICE_BYTES=$((
|
||||
(1 + BOOT_PARTITION_SIZE_MIB + SWAP_SIZE_MIB_PART +
|
||||
VIRT_ZVOL_SIZE_MIB + SWAP_SIZE_MIB_ZFS + 2048) * 1024 * 1024
|
||||
))
|
||||
|
||||
if (( CONFIGURED_MINIMUM_DEVICE_BYTES > MINIMUM_DEVICE_BYTES )); then
|
||||
REQUIRED_DEVICE_BYTES="$CONFIGURED_MINIMUM_DEVICE_BYTES"
|
||||
else
|
||||
REQUIRED_DEVICE_BYTES="$MINIMUM_DEVICE_BYTES"
|
||||
fi
|
||||
|
||||
(( DEVICE_BYTES >= REQUIRED_DEVICE_BYTES )) ||
|
||||
die "The card has $DEVICE_BYTES bytes; the selected layout needs at least $REQUIRED_DEVICE_BYTES bytes."
|
||||
|
||||
if lsblk -nrpo MOUNTPOINT "$DEVICE" | grep -q '[^[:space:]]'; then
|
||||
die "A partition on $DEVICE is mounted. Unmount it before continuing."
|
||||
fi
|
||||
|
||||
if zpool list -H -o name "$POOL_NAME" >/dev/null 2>&1; then
|
||||
die "A pool named $POOL_NAME is already imported on this PC."
|
||||
fi
|
||||
|
||||
if zpool status -P 2>/dev/null | grep -Fq "$DEVICE"; then
|
||||
die "$DEVICE belongs to a pool that is currently imported. Export it first."
|
||||
fi
|
||||
|
||||
HOST_ZFS_VERSION="$(zpool --version | sed -n 's/^zfs-\([^ -]*\).*/\1/p' | head -1)"
|
||||
|
||||
[[ "$HOST_ZFS_VERSION" == "$EXPECTED_ZFS_VERSION" ]] ||
|
||||
die "This design expects OpenZFS $EXPECTED_ZFS_VERSION; the PC has ${HOST_ZFS_VERSION:-unknown}."
|
||||
|
||||
COMPATIBILITY_FILE="/usr/share/zfs/compatibility.d/$POOL_COMPATIBILITY"
|
||||
[[ -f "$COMPATIBILITY_FILE" ]] ||
|
||||
die "Missing OpenZFS compatibility file: $COMPATIBILITY_FILE"
|
||||
|
||||
BOOT_PARTITION="$(partition_path 1)"
|
||||
SWAP_PARTITION="$(partition_path 2)"
|
||||
ZFS_PARTITION="$(partition_path 3)"
|
||||
|
||||
TEMP_DIRECTORY="$(mktemp -d /tmp/nenjim-storage.XXXXXX)"
|
||||
STAGING_ROOT="$TEMP_DIRECTORY/root"
|
||||
VIRT_MOUNT="$TEMP_DIRECTORY/virt"
|
||||
|
||||
mkdir -p "$STAGING_ROOT" "$VIRT_MOUNT"
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Destructive-action confirmation
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "WARNING: ALL DATA ON ${DEVICE} WILL BE DESTROYED."
|
||||
echo
|
||||
echo "The new layout will be:"
|
||||
echo " partition 1: ${BOOT_PARTITION_SIZE_MIB} MiB FAT32 Alpine boot partition"
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
echo " partition 2: ${SWAP_SIZE_MIB_PART} MiB raw Linux swap (priority ${SWAP_PRI_PART})"
|
||||
else
|
||||
echo " partition 2: omitted (raw swap is disabled)"
|
||||
fi
|
||||
echo " partition 3: remaining space, OpenZFS pool $POOL_NAME"
|
||||
if (( SWAP_SIZE_MIB_ZFS > 0 )); then
|
||||
echo " $POOL_NAME/swap: ${SWAP_SIZE_MIB_ZFS} MiB (priority ${SWAP_PRI_ZFS})"
|
||||
else
|
||||
echo " $POOL_NAME/swap: disabled"
|
||||
fi
|
||||
echo " ZRAM: ${SWAP_SIZE_PCT_ZRAM}% of RAM (priority ${SWAP_PRI_ZRAM})"
|
||||
echo
|
||||
lsblk "$DEVICE"
|
||||
echo
|
||||
read -r -p "Type YES to create the new storage layout: " answer
|
||||
|
||||
[[ "$answer" == "YES" ]] || die "Aborted."
|
||||
[[ -b "$DEVICE" ]] || die "The block device disappeared: $DEVICE"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Create the physical partitions
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Creating FAT32, optional raw swap and OpenZFS partitions ==="
|
||||
|
||||
wipefs --all "$DEVICE"
|
||||
|
||||
LOGICAL_SECTOR_BYTES="$(blockdev --getss "$DEVICE")"
|
||||
|
||||
case "$LOGICAL_SECTOR_BYTES" in
|
||||
512|4096)
|
||||
;;
|
||||
*)
|
||||
die "Unsupported logical sector size: $LOGICAL_SECTOR_BYTES bytes."
|
||||
;;
|
||||
esac
|
||||
|
||||
BOOT_START_SECTOR=$((1024 * 1024 / LOGICAL_SECTOR_BYTES))
|
||||
BOOT_SIZE_SECTORS=$((BOOT_PARTITION_SIZE_MIB * 1024 * 1024 / LOGICAL_SECTOR_BYTES))
|
||||
SWAP_SIZE_SECTORS=$((SWAP_SIZE_MIB_PART * 1024 * 1024 / LOGICAL_SECTOR_BYTES))
|
||||
NEXT_START_SECTOR=$((BOOT_START_SECTOR + BOOT_SIZE_SECTORS))
|
||||
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
SWAP_START_SECTOR="$NEXT_START_SECTOR"
|
||||
ZFS_START_SECTOR=$((SWAP_START_SECTOR + SWAP_SIZE_SECTORS))
|
||||
else
|
||||
ZFS_START_SECTOR="$NEXT_START_SECTOR"
|
||||
fi
|
||||
|
||||
{
|
||||
echo 'label: dos'
|
||||
echo 'unit: sectors'
|
||||
echo
|
||||
echo "$BOOT_PARTITION : start=$BOOT_START_SECTOR, size=$BOOT_SIZE_SECTORS, type=$BOOT_PARTITION_TYPE, bootable"
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
echo "$SWAP_PARTITION : start=$SWAP_START_SECTOR, size=$SWAP_SIZE_SECTORS, type=$SWAP_PARTITION_TYPE"
|
||||
fi
|
||||
echo "$ZFS_PARTITION : start=$ZFS_START_SECTOR, type=$ZFS_PARTITION_TYPE"
|
||||
} | sfdisk --wipe always "$DEVICE"
|
||||
|
||||
partprobe "$DEVICE"
|
||||
udevadm settle
|
||||
|
||||
wait_for_block_device "$BOOT_PARTITION" ||
|
||||
die "Boot partition did not appear: $BOOT_PARTITION"
|
||||
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
wait_for_block_device "$SWAP_PARTITION" ||
|
||||
die "Swap partition did not appear: $SWAP_PARTITION"
|
||||
fi
|
||||
|
||||
wait_for_block_device "$ZFS_PARTITION" ||
|
||||
die "ZFS partition did not appear: $ZFS_PARTITION"
|
||||
|
||||
mkfs.vfat -F 32 -n "$BOOT_LABEL" "$BOOT_PARTITION"
|
||||
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
mkswap -L "$SWAP_PART_LABEL" "$SWAP_PARTITION"
|
||||
fi
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Create the ZFS pool and datasets
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Creating OpenZFS pool $POOL_NAME ==="
|
||||
|
||||
zpool create \
|
||||
-f \
|
||||
-R "$STAGING_ROOT" \
|
||||
-o "compatibility=$POOL_COMPATIBILITY" \
|
||||
-o ashift=12 \
|
||||
-O xattr=sa \
|
||||
-O compression=lz4 \
|
||||
-O atime=off \
|
||||
-O canmount=off \
|
||||
-O mountpoint=none \
|
||||
"$POOL_NAME" \
|
||||
"$ZFS_PARTITION"
|
||||
|
||||
POOL_CREATED=1
|
||||
|
||||
zfs create -o mountpoint=/home "$POOL_NAME/home"
|
||||
zfs create -o mountpoint=/usr/local/software "$POOL_NAME/software"
|
||||
zfs create -o mountpoint=/media/sysop "$POOL_NAME/sysop"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Install Java on rpool/software
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Installing Azul Zulu JRE on rpool/software ==="
|
||||
|
||||
SOFTWARE_ROOT="$STAGING_ROOT/usr/local/software"
|
||||
mkdir -p "$SOFTWARE_ROOT"
|
||||
|
||||
tar -xzf "$JAVA_ARCHIVE" -C "$SOFTWARE_ROOT"
|
||||
|
||||
[[ -d "$SOFTWARE_ROOT/$JAVA_DIRECTORY" ]] ||
|
||||
die "The Java archive did not create $JAVA_DIRECTORY."
|
||||
|
||||
ln -s "$JAVA_DIRECTORY" "$SOFTWARE_ROOT/$JAVA_VERSION_DIRECTORY"
|
||||
ln -s "$JAVA_VERSION_DIRECTORY" "$SOFTWARE_ROOT/$JAVA_MAJOR_DIRECTORY"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Install the official static zrepl ARM64 binary on rpool/software
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Installing zrepl ${ZREPL_VERSION} on rpool/software ==="
|
||||
|
||||
ZREPL_VERSION_NUMBER="${ZREPL_VERSION#v}"
|
||||
ZREPL_VERSION_DIRECTORY="$SOFTWARE_ROOT/zrepl/versions/$ZREPL_VERSION_NUMBER"
|
||||
ZREPL_TEMP_BINARY="$TEMP_DIRECTORY/$ZREPL_ASSET"
|
||||
|
||||
mkdir -p "$ZREPL_VERSION_DIRECTORY"
|
||||
|
||||
if [[ -f "$ZREPL_LOCAL_BINARY" ]]; then
|
||||
echo "Using local zrepl binary: $ZREPL_LOCAL_BINARY"
|
||||
cp "$ZREPL_LOCAL_BINARY" "$ZREPL_TEMP_BINARY"
|
||||
else
|
||||
download_zrepl "$ZREPL_TEMP_BINARY"
|
||||
fi
|
||||
|
||||
file "$ZREPL_TEMP_BINARY" | grep -Eq 'ELF 64-bit.*ARM aarch64' ||
|
||||
die "$ZREPL_ASSET is not an ARM64 ELF binary."
|
||||
|
||||
install -m 0755 "$ZREPL_TEMP_BINARY" "$ZREPL_VERSION_DIRECTORY/zrepl"
|
||||
ln -s "versions/$ZREPL_VERSION_NUMBER" "$SOFTWARE_ROOT/zrepl/current"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Create and format the shared Docker/containerd zvol
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Creating ${POOL_NAME}/virt (${VIRT_ZVOL_SIZE_MIB} MiB, ext4) ==="
|
||||
|
||||
zfs create \
|
||||
-V "${VIRT_ZVOL_SIZE_MIB}M" \
|
||||
-o volmode=dev \
|
||||
-o logbias=throughput \
|
||||
-o primarycache=metadata \
|
||||
-o secondarycache=none \
|
||||
"$POOL_NAME/virt"
|
||||
|
||||
udevadm settle
|
||||
|
||||
VIRT_DEVICE="/dev/zvol/$POOL_NAME/virt"
|
||||
wait_for_block_device "$VIRT_DEVICE" ||
|
||||
die "The zvol device did not appear: $VIRT_DEVICE"
|
||||
|
||||
mkfs.ext4 -F -L NENJIMVIRT "$VIRT_DEVICE"
|
||||
mount -t ext4 -o rw,noatime "$VIRT_DEVICE" "$VIRT_MOUNT"
|
||||
|
||||
mkdir -p "$VIRT_MOUNT/docker" "$VIRT_MOUNT/containerd"
|
||||
chmod 0711 "$VIRT_MOUNT/docker" "$VIRT_MOUNT/containerd"
|
||||
|
||||
sync
|
||||
umount "$VIRT_MOUNT"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Verification and clean export
|
||||
# ==============================================================================
|
||||
|
||||
echo
|
||||
echo "=== Verifying and exporting the pool ==="
|
||||
|
||||
zpool get -H -o value compatibility "$POOL_NAME" |
|
||||
grep -qx "$POOL_COMPATIBILITY" ||
|
||||
die "The pool compatibility setting is incorrect."
|
||||
|
||||
for dataset in home software sysop virt; do
|
||||
zfs list "$POOL_NAME/$dataset" >/dev/null ||
|
||||
die "Missing ZFS object: $POOL_NAME/$dataset"
|
||||
done
|
||||
|
||||
JAVA_BINARY="$SOFTWARE_ROOT/$JAVA_DIRECTORY/bin/java"
|
||||
|
||||
[[ -x "$JAVA_BINARY" ]] ||
|
||||
die "The Java archive did not provide an executable bin/java."
|
||||
|
||||
# The preparation PC is normally amd64 and therefore cannot execute this
|
||||
# Raspberry Pi binary. Verify its format and target architecture instead.
|
||||
file "$JAVA_BINARY" | grep -Eq 'ELF 64-bit.*ARM aarch64' ||
|
||||
die "The installed Java binary is not an ARM64 ELF executable."
|
||||
|
||||
zpool set cachefile=none "$POOL_NAME"
|
||||
zpool export "$POOL_NAME"
|
||||
POOL_CREATED=0
|
||||
|
||||
sync
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Finished
|
||||
# ==============================================================================
|
||||
|
||||
cleanup
|
||||
trap - EXIT
|
||||
|
||||
echo
|
||||
echo "============================================================================"
|
||||
echo "Storage preparation is complete."
|
||||
echo
|
||||
echo "Partition 1 is ready for flash.sh."
|
||||
if (( SWAP_SIZE_MIB_PART > 0 )); then
|
||||
echo "Partition 2 is raw swap and will not be touched by flash.sh."
|
||||
else
|
||||
echo "Partition 2 is intentionally unused."
|
||||
fi
|
||||
echo "Partition 3 contains $POOL_NAME and will not be touched by flash.sh."
|
||||
if (( SWAP_SIZE_MIB_ZFS > 0 )); then
|
||||
echo "The Pi will create ${POOL_NAME}/swap (${SWAP_SIZE_MIB_ZFS} MiB) using its real page size."
|
||||
fi
|
||||
echo "============================================================================"
|
||||
Executable
+1712
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user