1713 lines
48 KiB
Bash
Executable File
1713 lines
48 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
set -eu
|
|
|
|
# Monotonic time is unaffected by the RTC seed and later network time updates.
|
|
INSTALL_START_UPTIME_SECONDS="$(awk '{ print int($1); exit }' /proc/uptime)"
|
|
|
|
|
|
# ==============================================================================
|
|
# Configuration
|
|
# ==============================================================================
|
|
|
|
NENJIM_USER="nenjim"
|
|
NENJIM_PASS="nenjim"
|
|
NENJIM_UID="1001"
|
|
|
|
SYSOP_USER="sysop"
|
|
SYSOP_PASS="sysop"
|
|
SYSOP_UID="1000"
|
|
|
|
MY_IFACE="wlan0"
|
|
MY_TIMEZONE="Europe/Copenhagen"
|
|
|
|
ALPINE_BRANCH="3.24"
|
|
ALPINE_MIRROR="https://dl-cdn.alpinelinux.org/alpine"
|
|
APK_MAIN_REPOSITORY="$ALPINE_MIRROR/v$ALPINE_BRANCH/main"
|
|
APK_COMMUNITY_REPOSITORY="$ALPINE_MIRROR/v$ALPINE_BRANCH/community"
|
|
BUILD_TIME_FILE_NAME="nenjim-build-time"
|
|
CONFIG_FILE_NAME="NenjimHub.conf"
|
|
|
|
SOFTWARE_ROOT="/usr/local/software"
|
|
SYSOP_DATA_ROOT="/media/sysop"
|
|
VIRT_MOUNT="/media/virt"
|
|
|
|
ZRAM_SWAP_DEVICE="/dev/zram0"
|
|
ZRAM_INSTALL_MEMORY_PERCENT="100"
|
|
|
|
ZREPL_CONFIG_DIRECTORY="$SYSOP_DATA_ROOT/zrepl"
|
|
ZREPL_CONFIG_FILE="$ZREPL_CONFIG_DIRECTORY/zrepl.yml"
|
|
|
|
EARLY_PACKAGES="
|
|
btop
|
|
eudev
|
|
eudev-openrc
|
|
htop
|
|
kitty-terminfo
|
|
musl-locales
|
|
tmux
|
|
udev-init-scripts
|
|
udev-init-scripts-openrc
|
|
zfs~2.4.3
|
|
zfs-openrc~2.4.3
|
|
zfs-scripts~2.4.3
|
|
zfs-udev~2.4.3
|
|
zram-init
|
|
zram-init-openrc
|
|
"
|
|
|
|
RUNTIME_PACKAGES="
|
|
bash
|
|
ca-certificates
|
|
containerd
|
|
containerd-openrc
|
|
doas
|
|
docker-cli
|
|
docker-engine
|
|
docker-openrc
|
|
e2fsprogs
|
|
git
|
|
libcrypto3
|
|
libssl3
|
|
libstdc++
|
|
openssh-server
|
|
openssh-server-common-openrc
|
|
wpa_supplicant
|
|
wpa_supplicant-openrc
|
|
"
|
|
|
|
|
|
# ==============================================================================
|
|
# Helpers
|
|
# ==============================================================================
|
|
|
|
die()
|
|
{
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_uint()
|
|
{
|
|
name="$1"
|
|
value="$2"
|
|
|
|
case "$value" in
|
|
''|*[!0-9]*) die "$name must be a non-negative integer." ;;
|
|
esac
|
|
}
|
|
|
|
require_swap_priority()
|
|
{
|
|
name="$1"
|
|
value="$2"
|
|
|
|
require_uint "$name" "$value"
|
|
[ "$value" -le 32767 ] ||
|
|
die "$name must be between 0 and 32767."
|
|
}
|
|
|
|
format_duration()
|
|
{
|
|
total_seconds="$1"
|
|
hours=$((total_seconds / 3600))
|
|
minutes=$(((total_seconds % 3600) / 60))
|
|
seconds=$((total_seconds % 60))
|
|
|
|
printf '%02d:%02d:%02d' "$hours" "$minutes" "$seconds"
|
|
}
|
|
|
|
is_mounted()
|
|
{
|
|
awk -v path="$1" '$2 == path { found=1 } END { exit !found }' /proc/mounts
|
|
}
|
|
|
|
wait_for_block_device()
|
|
{
|
|
path="$1"
|
|
attempt=0
|
|
|
|
while [ "$attempt" -lt 50 ]; do
|
|
[ -b "$path" ] && return 0
|
|
sleep 1
|
|
attempt=$((attempt + 1))
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
blkid_value()
|
|
{
|
|
blkid_device="$1"
|
|
blkid_key="$2"
|
|
blkid_output="$(blkid "$blkid_device" 2>/dev/null || true)"
|
|
|
|
printf '%s\n' "$blkid_output" |
|
|
sed -n "s/.*[[:space:]]${blkid_key}=\"\([^\"]*\)\".*/\1/p" |
|
|
head -1
|
|
}
|
|
|
|
enable_service()
|
|
{
|
|
service="$1"
|
|
runlevel="$2"
|
|
|
|
[ -x "/etc/init.d/$service" ] ||
|
|
die "Missing OpenRC service: $service"
|
|
|
|
rc-update add "$service" "$runlevel"
|
|
}
|
|
|
|
write_zram_config()
|
|
{
|
|
memory_percent="$1"
|
|
|
|
case "$memory_percent" in
|
|
''|*[!0-9]*) die "Invalid ZRAM memory percentage: $memory_percent" ;;
|
|
esac
|
|
|
|
mkdir -p /etc/conf.d
|
|
|
|
cat >/etc/conf.d/zram-init <<EOF
|
|
load_on_start=yes
|
|
unload_on_stop=yes
|
|
num_devices=1
|
|
type0=swap
|
|
flag0=$SWAP_PRI_ZRAM
|
|
size0="\$(awk -v percent=$memory_percent '/^MemTotal:/ { print int((\$2 / 1024) * percent / 100); exit }' /proc/meminfo)"
|
|
mlim0=
|
|
back0=
|
|
icmp0=
|
|
idle0=
|
|
wlim0=
|
|
notr0=
|
|
algo0=
|
|
para0=
|
|
labl0=nenjim_zram
|
|
uuid0=
|
|
args0=
|
|
EOF
|
|
}
|
|
|
|
ensure_empty_path_can_be_linked()
|
|
{
|
|
path="$1"
|
|
|
|
if [ -L "$path" ]; then
|
|
rm -f "$path"
|
|
return 0
|
|
fi
|
|
|
|
if [ -d "$path" ]; then
|
|
if find "$path" -mindepth 1 -print -quit | grep -q .; then
|
|
die "Refusing to replace non-empty directory: $path"
|
|
fi
|
|
|
|
rmdir "$path"
|
|
elif [ -e "$path" ]; then
|
|
die "Refusing to replace non-directory path: $path"
|
|
fi
|
|
}
|
|
|
|
create_or_validate_user()
|
|
{
|
|
username="$1"
|
|
uid="$2"
|
|
home="$3"
|
|
shell="$4"
|
|
|
|
if id "$username" >/dev/null 2>&1; then
|
|
actual_uid="$(id -u "$username")"
|
|
[ "$actual_uid" = "$uid" ] ||
|
|
die "$username has UID $actual_uid, expected $uid."
|
|
else
|
|
adduser \
|
|
-D \
|
|
-u "$uid" \
|
|
-h "$home" \
|
|
-s "$shell" \
|
|
"$username"
|
|
fi
|
|
|
|
sed -i \
|
|
"s#^\($username:[^:]*:[^:]*:[^:]*:[^:]*:\)[^:]*:[^:]*#\1$home:$shell#" \
|
|
/etc/passwd
|
|
}
|
|
|
|
|
|
# ==============================================================================
|
|
# Locate the FAT boot media and first-boot inputs
|
|
# ==============================================================================
|
|
|
|
ovl="$(
|
|
dmesg |
|
|
grep -o 'Loading user settings from .*:' |
|
|
awk '{print $5}' |
|
|
sed 's/:.*$//' |
|
|
head -1
|
|
)"
|
|
|
|
if [ -n "${ovl:-}" ] && [ -f "$ovl" ]; then
|
|
BOOT_MEDIA="$(dirname "$ovl")"
|
|
else
|
|
BOOT_MEDIA="$(
|
|
find /media \
|
|
-maxdepth 3 \
|
|
-type f \
|
|
-name headless.apkovl.tar.gz \
|
|
-exec dirname {} \; |
|
|
head -1
|
|
)"
|
|
fi
|
|
|
|
[ -n "${BOOT_MEDIA:-}" ] && [ -d "$BOOT_MEDIA" ] ||
|
|
die "Could not locate the Alpine boot media."
|
|
|
|
mount -o remount,rw "$BOOT_MEDIA" 2>/dev/null || true
|
|
|
|
[ -f "$BOOT_MEDIA/wpa_supplicant.conf" ] ||
|
|
die "Missing wpa_supplicant.conf on the boot partition."
|
|
|
|
[ -f "$BOOT_MEDIA/installZeroTier.sh" ] ||
|
|
die "Missing installZeroTier.sh on the boot partition."
|
|
|
|
[ -f "$BOOT_MEDIA/$CONFIG_FILE_NAME" ] ||
|
|
die "Missing $CONFIG_FILE_NAME on the boot partition; rerun flash.sh."
|
|
|
|
|
|
# ==============================================================================
|
|
# Logging
|
|
# ==============================================================================
|
|
|
|
INSTALL_LOG="$BOOT_MEDIA/NenjimHub-Install.log"
|
|
LEGACY_INSTALL_LOG="$BOOT_MEDIA/nenjim-install.log"
|
|
|
|
if [ -f "$LEGACY_INSTALL_LOG" ] && [ ! -e "$INSTALL_LOG" ]; then
|
|
mv "$LEGACY_INSTALL_LOG" "$INSTALL_LOG"
|
|
fi
|
|
|
|
exec >>"$INSTALL_LOG" 2>&1
|
|
|
|
logger -t nenjim-install "Starting unattended Alpine installation"
|
|
|
|
echo
|
|
echo "============================================================================"
|
|
echo "Nenjim Alpine/ZFS bootstrap"
|
|
echo "Boot media: $BOOT_MEDIA"
|
|
echo "Log: $INSTALL_LOG"
|
|
echo "============================================================================"
|
|
|
|
|
|
# ==============================================================================
|
|
# Load and validate the shared provisioning configuration
|
|
# ==============================================================================
|
|
|
|
CONFIG_PATH="$BOOT_MEDIA/$CONFIG_FILE_NAME"
|
|
# shellcheck source=NenjimHub.conf
|
|
. "$CONFIG_PATH"
|
|
|
|
require_uint BOOT_PARTITION_SIZE_MIB "${BOOT_PARTITION_SIZE_MIB:-}"
|
|
require_uint VIRT_ZVOL_SIZE_MIB "${VIRT_ZVOL_SIZE_MIB:-}"
|
|
require_uint SWAP_SIZE_MIB_PART "${SWAP_SIZE_MIB_PART:-}"
|
|
require_uint SWAP_SIZE_MIB_ZFS "${SWAP_SIZE_MIB_ZFS:-}"
|
|
require_uint SWAP_SIZE_PCT_ZRAM "${SWAP_SIZE_PCT_ZRAM:-}"
|
|
require_uint DISKLESS_ROOT_SIZE_MIB "${DISKLESS_ROOT_SIZE_MIB:-}"
|
|
require_uint ZFS_ARC_MAX_MIB "${ZFS_ARC_MAX_MIB:-}"
|
|
require_uint ZEROTIER_REQUIRED_SWAP_MIB "${ZEROTIER_REQUIRED_SWAP_MIB:-}"
|
|
require_uint ZEROTIER_MAKE_JOBS "${ZEROTIER_MAKE_JOBS:-}"
|
|
require_uint ZEROTIER_ENABLE_SSO "${ZEROTIER_ENABLE_SSO:-}"
|
|
|
|
require_swap_priority SWAP_PRI_ZRAM "${SWAP_PRI_ZRAM:-}"
|
|
require_swap_priority SWAP_PRI_ZFS "${SWAP_PRI_ZFS:-}"
|
|
require_swap_priority SWAP_PRI_PART "${SWAP_PRI_PART:-}"
|
|
|
|
[ "$BOOT_PARTITION_SIZE_MIB" -gt 0 ] ||
|
|
die "BOOT_PARTITION_SIZE_MIB must be greater than zero."
|
|
[ "$VIRT_ZVOL_SIZE_MIB" -gt 0 ] ||
|
|
die "VIRT_ZVOL_SIZE_MIB must be greater than zero."
|
|
[ "$DISKLESS_ROOT_SIZE_MIB" -gt 0 ] ||
|
|
die "DISKLESS_ROOT_SIZE_MIB must be greater than zero."
|
|
[ "$ZFS_ARC_MAX_MIB" -gt 0 ] ||
|
|
die "ZFS_ARC_MAX_MIB must be greater than zero."
|
|
[ "$ZEROTIER_REQUIRED_SWAP_MIB" -gt 0 ] ||
|
|
die "ZEROTIER_REQUIRED_SWAP_MIB must be greater than zero."
|
|
[ "$ZEROTIER_MAKE_JOBS" -gt 0 ] ||
|
|
die "ZEROTIER_MAKE_JOBS must be greater than zero."
|
|
case "$ZEROTIER_ENABLE_SSO" in
|
|
0|1) ;;
|
|
*) die "ZEROTIER_ENABLE_SSO must be either 0 or 1." ;;
|
|
esac
|
|
[ -n "${ZEROTIER_VERSION:-}" ] ||
|
|
die "ZEROTIER_VERSION must not be empty."
|
|
printf '%s\n' "$ZEROTIER_VERSION" |
|
|
grep -Eq '^[0-9]+([.][0-9]+){2}([._-][[:alnum:]]+)*$' ||
|
|
die "Invalid ZEROTIER_VERSION: $ZEROTIER_VERSION"
|
|
printf '%s\n' "${ZEROTIER_PREBUILT_BINARY:-}" |
|
|
grep -Eq '^[[:alnum:]][[:alnum:]._-]*$' ||
|
|
die "ZEROTIER_PREBUILT_BINARY must be a plain filename without directories."
|
|
[ "$SWAP_SIZE_PCT_ZRAM" -le 100 ] ||
|
|
die "SWAP_SIZE_PCT_ZRAM must be between 0 and 100."
|
|
|
|
printf '%s\n' "${NENJIMHUB_HOSTNAME:-}" |
|
|
grep -Eq '^[[:alnum:]]([[:alnum:]-]{0,61}[[:alnum:]])?$' ||
|
|
die "NENJIMHUB_HOSTNAME must be one 1-63 character DNS hostname label."
|
|
printf '%s\n' "${CONSOLE_KEYMAP_LAYOUT:-}" |
|
|
grep -Eq '^[A-Za-z0-9][A-Za-z0-9_-]*$' ||
|
|
die "Invalid CONSOLE_KEYMAP_LAYOUT: ${CONSOLE_KEYMAP_LAYOUT:-empty}"
|
|
printf '%s\n' "${CONSOLE_KEYMAP_VARIANT:-}" |
|
|
grep -Eq '^[A-Za-z0-9][A-Za-z0-9_-]*$' ||
|
|
die "Invalid CONSOLE_KEYMAP_VARIANT: ${CONSOLE_KEYMAP_VARIANT:-empty}"
|
|
printf '%s\n' "${SYSTEM_LOCALE:-}" |
|
|
grep -Eq '^[A-Za-z][A-Za-z0-9_.@-]*$' ||
|
|
die "Invalid SYSTEM_LOCALE: ${SYSTEM_LOCALE:-empty}"
|
|
printf '%s\n' "$POOL_NAME" | grep -Eq '^[A-Za-z][A-Za-z0-9_.:-]*$' ||
|
|
die "Invalid POOL_NAME: $POOL_NAME"
|
|
printf '%s\n' "$SWAP_PART_LABEL" | grep -Eq '^[[:alnum:]_-]{1,15}$' ||
|
|
die "Invalid SWAP_PART_LABEL: $SWAP_PART_LABEL"
|
|
|
|
SWAP_ZVOL="$POOL_NAME/swap"
|
|
SWAP_SIZE_BYTES=$((SWAP_SIZE_MIB_ZFS * 1024 * 1024))
|
|
SWAP_DEVICE="/dev/zvol/$POOL_NAME/swap"
|
|
SWAP_PART_DEVICE="/dev/disk/by-label/$SWAP_PART_LABEL"
|
|
VIRT_ZVOL="$POOL_NAME/virt"
|
|
VIRT_DEVICE="/dev/zvol/$POOL_NAME/virt"
|
|
VIRT_SIZE_BYTES=$((VIRT_ZVOL_SIZE_MIB * 1024 * 1024))
|
|
DISKLESS_ROOT_SIZE="${DISKLESS_ROOT_SIZE_MIB}M"
|
|
ZFS_ARC_MAX_BYTES=$((ZFS_ARC_MAX_MIB * 1024 * 1024))
|
|
|
|
echo "Swap profile:"
|
|
echo " ZRAM: ${SWAP_SIZE_PCT_ZRAM}% at priority $SWAP_PRI_ZRAM"
|
|
echo " raw p2: ${SWAP_SIZE_MIB_PART} MiB at priority $SWAP_PRI_PART"
|
|
echo " ZFS zvol: ${SWAP_SIZE_MIB_ZFS} MiB at priority $SWAP_PRI_ZFS"
|
|
|
|
if [ "$SWAP_SIZE_MIB_PART" -gt 0 ] && \
|
|
[ "$SWAP_SIZE_MIB_ZFS" -gt 0 ] && \
|
|
[ "$SWAP_PRI_ZFS" -gt "$SWAP_PRI_PART" ]; then
|
|
echo "WARNING: ZFS swap has higher priority than raw partition swap."
|
|
echo " This is allowed, but raw swap is safer under severe memory pressure."
|
|
fi
|
|
|
|
|
|
# ==============================================================================
|
|
# Activate stable raw swap before setup-alpine and package installation
|
|
# ==============================================================================
|
|
|
|
if [ "$SWAP_SIZE_MIB_PART" -gt 0 ]; then
|
|
echo
|
|
echo "=== Activating early raw partition swap ==="
|
|
|
|
BOOT_BLOCK_DEVICE="$(
|
|
awk -v path="$BOOT_MEDIA" '$2 == path { print $1; exit }' /proc/mounts
|
|
)"
|
|
|
|
case "$BOOT_BLOCK_DEVICE" in
|
|
*p1) EARLY_SWAP_PARTITION="${BOOT_BLOCK_DEVICE%p1}p2" ;;
|
|
*1) EARLY_SWAP_PARTITION="${BOOT_BLOCK_DEVICE%1}2" ;;
|
|
*) die "Could not derive partition 2 from boot device $BOOT_BLOCK_DEVICE." ;;
|
|
esac
|
|
|
|
wait_for_block_device "$EARLY_SWAP_PARTITION" ||
|
|
die "Raw swap partition did not appear: $EARLY_SWAP_PARTITION"
|
|
|
|
[ "$(blkid_value "$EARLY_SWAP_PARTITION" TYPE)" = "swap" ] ||
|
|
die "$EARLY_SWAP_PARTITION is not formatted as Linux swap."
|
|
[ "$(blkid_value "$EARLY_SWAP_PARTITION" LABEL)" = "$SWAP_PART_LABEL" ] ||
|
|
die "$EARLY_SWAP_PARTITION does not have label $SWAP_PART_LABEL."
|
|
|
|
EARLY_SWAP_KERNEL_DEVICE="$(readlink -f "$EARLY_SWAP_PARTITION")"
|
|
if ! awk -v device="$EARLY_SWAP_KERNEL_DEVICE" \
|
|
'NR > 1 && $1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
swapon -p "$SWAP_PRI_PART" "$EARLY_SWAP_PARTITION"
|
|
fi
|
|
fi
|
|
|
|
|
|
# ==============================================================================
|
|
# Seed a trustworthy clock before HTTPS (the Raspberry Pi has no RTC)
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Seeding the clock for HTTPS package access ==="
|
|
|
|
BUILD_TIME_FILE="$BOOT_MEDIA/$BUILD_TIME_FILE_NAME"
|
|
[ -f "$BUILD_TIME_FILE" ] ||
|
|
die "Missing $BUILD_TIME_FILE_NAME; rerun the updated flash.sh."
|
|
|
|
BUILD_TIME="$(sed -n '1p' "$BUILD_TIME_FILE")"
|
|
printf '%s\n' "$BUILD_TIME" | grep -Eq \
|
|
'^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$' ||
|
|
die "Invalid UTC build time in $BUILD_TIME_FILE."
|
|
|
|
echo "Clock before seed: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
|
date -u -s "$BUILD_TIME" >/dev/null
|
|
echo "Clock after seed: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
|
|
|
|
|
# ==============================================================================
|
|
# Persistent diskless root size
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring diskless root size: $DISKLESS_ROOT_SIZE ==="
|
|
|
|
CMDLINE_FILE="$BOOT_MEDIA/cmdline.txt"
|
|
[ -f "$CMDLINE_FILE" ] || die "Missing Raspberry Pi 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"
|
|
|
|
|
|
# ==============================================================================
|
|
# Base Alpine diskless configuration
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Validating console keymap: $CONSOLE_KEYMAP_LAYOUT $CONSOLE_KEYMAP_VARIANT ==="
|
|
|
|
# setup-keymap installs this package itself, but an invalid non-interactive
|
|
# variant would fall back to an interactive prompt. Keep a temporary reference
|
|
# while validating so setup-keymap can use the same already-installed files.
|
|
apk add \
|
|
--quiet \
|
|
--no-cache \
|
|
--virtual .nenjim-keymap-check \
|
|
kbd-bkeymaps
|
|
|
|
KEYMAP_SOURCE_FOUND=0
|
|
for keymap_source in \
|
|
"/usr/share/bkeymaps/$CONSOLE_KEYMAP_LAYOUT/$CONSOLE_KEYMAP_VARIANT.bmap" \
|
|
"/usr/share/bkeymaps/$CONSOLE_KEYMAP_LAYOUT/$CONSOLE_KEYMAP_VARIANT.bmap.gz"
|
|
do
|
|
if [ -f "$keymap_source" ]; then
|
|
KEYMAP_SOURCE_FOUND=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$KEYMAP_SOURCE_FOUND" -ne 1 ]; then
|
|
echo "Available variants for layout $CONSOLE_KEYMAP_LAYOUT:" >&2
|
|
ls -1 "/usr/share/bkeymaps/$CONSOLE_KEYMAP_LAYOUT" 2>/dev/null >&2 || true
|
|
die "Console keymap does not exist: $CONSOLE_KEYMAP_LAYOUT $CONSOLE_KEYMAP_VARIANT"
|
|
fi
|
|
|
|
cat >/tmp/ANSWERFILE <<EOF
|
|
KEYMAPOPTS="$CONSOLE_KEYMAP_LAYOUT $CONSOLE_KEYMAP_VARIANT"
|
|
HOSTNAMEOPTS="$NENJIMHUB_HOSTNAME"
|
|
DEVDOPTS=udev
|
|
|
|
INTERFACESOPTS="auto lo
|
|
iface lo inet loopback
|
|
|
|
auto $MY_IFACE
|
|
iface $MY_IFACE inet dhcp
|
|
"
|
|
|
|
DNSOPTS=""
|
|
TIMEZONEOPTS="$MY_TIMEZONE"
|
|
PROXYOPTS=none
|
|
|
|
APKREPOSOPTS="$BOOT_MEDIA/apks $APK_MAIN_REPOSITORY $APK_COMMUNITY_REPOSITORY"
|
|
USEROPTS=none
|
|
|
|
SSHDOPTS=openssh
|
|
NTPOPTS=chrony
|
|
|
|
DISKOPTS=none
|
|
LBUOPTS="$BOOT_MEDIA"
|
|
APKCACHEOPTS="$BOOT_MEDIA/cache"
|
|
EOF
|
|
|
|
# Keep the headless bootstrap SSH session alive while setup-alpine configures
|
|
# the final OpenSSH service.
|
|
SSH_CONNECTION="FAKE" setup-alpine -ef /tmp/ANSWERFILE
|
|
|
|
[ -s /etc/conf.d/loadkmap ] ||
|
|
die "setup-keymap did not create /etc/conf.d/loadkmap."
|
|
apk del --quiet --no-progress .nenjim-keymap-check
|
|
|
|
for repository in \
|
|
"$APK_MAIN_REPOSITORY" \
|
|
"$APK_COMMUNITY_REPOSITORY"
|
|
do
|
|
grep -qxF "$repository" /etc/apk/repositories ||
|
|
die "Required APK repository is missing: $repository"
|
|
done
|
|
|
|
echo "Configured APK repositories:"
|
|
cat /etc/apk/repositories
|
|
|
|
|
|
# ==============================================================================
|
|
# Persistent APK cache and LBU destination
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring persistent APK cache and LBU ==="
|
|
|
|
mkdir -p "$BOOT_MEDIA/cache" /etc/lbu
|
|
touch "$BOOT_MEDIA/cache/.boot_repository"
|
|
|
|
BOOT_AVAILABLE_KB="$(df -Pk "$BOOT_MEDIA" | awk 'NR == 2 { print $4 }')"
|
|
[ "$BOOT_AVAILABLE_KB" -ge $((400 * 1024)) ] ||
|
|
die "Partition 1 needs at least 400 MB free for the APK cache."
|
|
|
|
rm -f /etc/apk/cache
|
|
ln -s "$BOOT_MEDIA/cache" /etc/apk/cache
|
|
|
|
cat >/etc/lbu/lbu.conf <<EOF
|
|
DEFAULT_CIPHER=aes-256-cbc
|
|
LBU_BACKUPDIR=$BOOT_MEDIA
|
|
BACKUP_LIMIT=3
|
|
EOF
|
|
|
|
install -m 0644 "$CONFIG_PATH" /etc/NenjimHub.conf
|
|
|
|
|
|
# ==============================================================================
|
|
# Install ZFS, udev and ZRAM before the larger runtime package transaction
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Installing early OpenZFS, udev and ZRAM packages ==="
|
|
|
|
apk update
|
|
|
|
# Intentional word splitting of the newline-separated package list.
|
|
# shellcheck disable=SC2086
|
|
apk add $EARLY_PACKAGES
|
|
|
|
echo
|
|
echo "=== Configuring UTF-8 terminals ==="
|
|
|
|
mkdir -p /etc/profile.d
|
|
cat >/etc/profile.d/99-nenjim-utf8.sh <<EOF
|
|
export LANG="$SYSTEM_LOCALE"
|
|
export LC_CTYPE="$SYSTEM_LOCALE"
|
|
EOF
|
|
chmod 0644 /etc/profile.d/99-nenjim-utf8.sh
|
|
|
|
# Alpine's console setup reads this OpenRC setting during normal boots.
|
|
touch /etc/rc.conf
|
|
sed -i '/^[[:space:]]*unicode=/d' /etc/rc.conf
|
|
echo 'unicode="YES"' >>/etc/rc.conf
|
|
|
|
export LANG="$SYSTEM_LOCALE"
|
|
export LC_CTYPE="$SYSTEM_LOCALE"
|
|
|
|
lbu include /etc/profile.d/99-nenjim-utf8.sh
|
|
lbu include /etc/rc.conf
|
|
|
|
echo "Early monitoring tools are ready:"
|
|
echo " tmux new-session -s install"
|
|
echo " htop"
|
|
echo " btop"
|
|
echo " tail -f $INSTALL_LOG"
|
|
|
|
mkdir -p /etc/modprobe.d /etc/zfs
|
|
cat >/etc/modprobe.d/zfs.conf <<EOF
|
|
options zfs zfs_arc_max=$ZFS_ARC_MAX_BYTES
|
|
EOF
|
|
|
|
modprobe zfs
|
|
udevadm settle
|
|
|
|
ZFS_VERSION_OUTPUT="$(zfs version)"
|
|
printf '%s\n' "$ZFS_VERSION_OUTPUT"
|
|
|
|
printf '%s\n' "$ZFS_VERSION_OUTPUT" |
|
|
grep -Eq "^zfs-${EXPECTED_ZFS_VERSION}([.-]|$)" ||
|
|
die "OpenZFS userspace is not $EXPECTED_ZFS_VERSION."
|
|
|
|
printf '%s\n' "$ZFS_VERSION_OUTPUT" |
|
|
grep -Eq "^zfs-kmod-${EXPECTED_ZFS_VERSION}([.-]|$)" ||
|
|
die "The Raspberry Pi OpenZFS kernel module is not $EXPECTED_ZFS_VERSION."
|
|
|
|
[ -f "/usr/share/zfs/compatibility.d/$POOL_COMPATIBILITY" ] ||
|
|
die "Missing pool compatibility definition: $POOL_COMPATIBILITY"
|
|
|
|
|
|
# ==============================================================================
|
|
# Activate the optional raw swap partition before importing ZFS
|
|
# ==============================================================================
|
|
|
|
echo
|
|
if [ "$SWAP_SIZE_MIB_PART" -gt 0 ]; then
|
|
echo "=== Activating raw partition swap at priority $SWAP_PRI_PART ==="
|
|
|
|
udevadm trigger --subsystem-match=block 2>/dev/null || true
|
|
udevadm settle
|
|
wait_for_block_device "$SWAP_PART_DEVICE" ||
|
|
die "Raw swap partition did not appear as $SWAP_PART_DEVICE."
|
|
|
|
PART_SWAP_KERNEL_DEVICE="$(readlink -f "$SWAP_PART_DEVICE")"
|
|
[ -b "$PART_SWAP_KERNEL_DEVICE" ] ||
|
|
die "Could not resolve raw swap partition $SWAP_PART_DEVICE."
|
|
|
|
[ "$(blkid_value "$SWAP_PART_DEVICE" TYPE)" = "swap" ] ||
|
|
die "$SWAP_PART_DEVICE is not formatted as Linux swap."
|
|
|
|
if ! awk -v device="$PART_SWAP_KERNEL_DEVICE" \
|
|
'NR > 1 && $1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
swapon -p "$SWAP_PRI_PART" "$SWAP_PART_DEVICE"
|
|
fi
|
|
|
|
cat >/etc/init.d/nenjim-part-swap <<EOF
|
|
#!/sbin/openrc-run
|
|
|
|
description="Nenjim raw partition swap"
|
|
device_link="$SWAP_PART_DEVICE"
|
|
priority="$SWAP_PRI_PART"
|
|
|
|
depend()
|
|
{
|
|
after udev-settle
|
|
before zfs-import containerd docker zrepl
|
|
}
|
|
|
|
start()
|
|
{
|
|
ebegin "Enabling raw partition swap"
|
|
device="\$(readlink -f "\$device_link")"
|
|
|
|
[ -b "\$device" ] || {
|
|
eend 1 "Could not resolve \$device_link"
|
|
return 1
|
|
}
|
|
|
|
if awk -v device="\$device" \
|
|
'NR > 1 && \$1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
swapon -p "\$priority" "\$device_link"
|
|
eend \$?
|
|
}
|
|
|
|
stop()
|
|
{
|
|
ebegin "Disabling raw partition swap"
|
|
device="\$(readlink -f "\$device_link")"
|
|
|
|
if [ ! -b "\$device" ] || ! awk -v device="\$device" \
|
|
'NR > 1 && \$1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
swapoff "\$device"
|
|
eend \$?
|
|
}
|
|
EOF
|
|
|
|
chmod 0755 /etc/init.d/nenjim-part-swap
|
|
enable_service nenjim-part-swap boot
|
|
lbu include /etc/init.d/nenjim-part-swap
|
|
else
|
|
echo "=== Raw partition swap is disabled ==="
|
|
fi
|
|
|
|
|
|
# ==============================================================================
|
|
# Start optional provisioning ZRAM before importing ZFS
|
|
# ==============================================================================
|
|
|
|
if [ "$SWAP_SIZE_PCT_ZRAM" -gt 0 ]; then
|
|
echo
|
|
echo "=== Starting temporary $ZRAM_INSTALL_MEMORY_PERCENT% provisioning ZRAM ==="
|
|
|
|
write_zram_config "$ZRAM_INSTALL_MEMORY_PERCENT"
|
|
enable_service zram-init boot
|
|
|
|
if ! rc-service zram-init start; then
|
|
# Some kernels need a short moment after loading the zram module before
|
|
# /dev/zram0 becomes available.
|
|
sleep 1
|
|
rc-service zram-init start
|
|
fi
|
|
|
|
awk -v device="$ZRAM_SWAP_DEVICE" \
|
|
'NR > 1 && $1 == device { found=1 } END { exit !found }' /proc/swaps ||
|
|
die "$ZRAM_SWAP_DEVICE did not become active."
|
|
|
|
zramctl
|
|
else
|
|
echo
|
|
echo "=== ZRAM swap is disabled ==="
|
|
fi
|
|
|
|
|
|
# ==============================================================================
|
|
# Import rpool without enabling any newer feature flags
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Importing $POOL_NAME from partition 3 ==="
|
|
|
|
if ! zpool list -H -o name "$POOL_NAME" >/dev/null 2>&1; then
|
|
zpool import -N -d /dev "$POOL_NAME"
|
|
fi
|
|
|
|
[ "$(zpool get -H -o value compatibility "$POOL_NAME")" = "$POOL_COMPATIBILITY" ] ||
|
|
die "$POOL_NAME does not use compatibility=$POOL_COMPATIBILITY."
|
|
|
|
zpool set cachefile=/etc/zfs/zpool.cache "$POOL_NAME"
|
|
|
|
for dataset_mount in \
|
|
"$POOL_NAME/home:/home" \
|
|
"$POOL_NAME/software:$SOFTWARE_ROOT" \
|
|
"$POOL_NAME/sysop:$SYSOP_DATA_ROOT"
|
|
do
|
|
dataset="${dataset_mount%%:*}"
|
|
expected_mount="${dataset_mount#*:}"
|
|
actual_mount="$(zfs get -H -o value mountpoint "$dataset")"
|
|
|
|
[ "$actual_mount" = "$expected_mount" ] ||
|
|
die "$dataset has mountpoint $actual_mount, expected $expected_mount."
|
|
done
|
|
|
|
zfs mount -a
|
|
|
|
for mount_path in /home "$SOFTWARE_ROOT" "$SYSOP_DATA_ROOT"; do
|
|
is_mounted "$mount_path" || die "ZFS dataset is not mounted at $mount_path."
|
|
done
|
|
|
|
|
|
# ==============================================================================
|
|
# Create, format and activate optional ZFS swap using the Pi's page size
|
|
# ==============================================================================
|
|
|
|
if [ "$SWAP_SIZE_MIB_ZFS" -gt 0 ]; then
|
|
echo
|
|
echo "=== Configuring ${SWAP_SIZE_MIB_ZFS} MiB ZFS swap zvol ==="
|
|
|
|
PAGE_SIZE="$(getconf PAGESIZE)"
|
|
|
|
case "$PAGE_SIZE" in
|
|
''|*[!0-9]*) die "Could not determine the Raspberry Pi page size." ;;
|
|
esac
|
|
|
|
if ! zfs list -H "$SWAP_ZVOL" >/dev/null 2>&1; then
|
|
zfs create \
|
|
-V "${SWAP_SIZE_MIB_ZFS}M" \
|
|
-b "$PAGE_SIZE" \
|
|
-o volmode=dev \
|
|
-o compression=off \
|
|
-o dedup=off \
|
|
-o sync=always \
|
|
-o logbias=throughput \
|
|
-o primarycache=metadata \
|
|
-o secondarycache=none \
|
|
-o com.sun:auto-snapshot=false \
|
|
"$SWAP_ZVOL"
|
|
else
|
|
[ "$(zfs get -Hp -o value volsize "$SWAP_ZVOL")" = "$SWAP_SIZE_BYTES" ] ||
|
|
die "$SWAP_ZVOL is not exactly ${SWAP_SIZE_MIB_ZFS} MiB."
|
|
|
|
[ "$(zfs get -Hp -o value volblocksize "$SWAP_ZVOL")" = "$PAGE_SIZE" ] ||
|
|
die "$SWAP_ZVOL volblocksize does not match getconf PAGESIZE ($PAGE_SIZE)."
|
|
fi
|
|
|
|
zfs set volmode=dev "$SWAP_ZVOL"
|
|
|
|
udevadm trigger --subsystem-match=block 2>/dev/null || true
|
|
udevadm settle
|
|
wait_for_block_device "$SWAP_DEVICE" ||
|
|
die "Swap zvol device did not appear: $SWAP_DEVICE"
|
|
|
|
SWAP_KERNEL_DEVICE="$(readlink -f "$SWAP_DEVICE")"
|
|
[ -b "$SWAP_KERNEL_DEVICE" ] ||
|
|
die "Could not resolve $SWAP_DEVICE to its kernel block device."
|
|
|
|
if [ "$(blkid_value "$SWAP_DEVICE" TYPE)" != "swap" ]; then
|
|
mkswap "$SWAP_DEVICE"
|
|
fi
|
|
|
|
if ! awk -v device="$SWAP_KERNEL_DEVICE" \
|
|
'NR > 1 && $1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
swapon -p "$SWAP_PRI_ZFS" "$SWAP_DEVICE"
|
|
fi
|
|
|
|
cat >/etc/init.d/nenjim-zfs-swap <<EOF
|
|
#!/sbin/openrc-run
|
|
|
|
description="Nenjim swap on $SWAP_ZVOL"
|
|
zvol="$SWAP_ZVOL"
|
|
device_link="$SWAP_DEVICE"
|
|
priority="$SWAP_PRI_ZFS"
|
|
wait_seconds=60
|
|
|
|
depend()
|
|
{
|
|
need zfs-import
|
|
after udev-settle
|
|
before containerd docker zrepl
|
|
}
|
|
|
|
start()
|
|
{
|
|
ebegin "Enabling ZFS swap"
|
|
|
|
if ! zfs list -H -o name "\$zvol" >/dev/null 2>&1; then
|
|
eend 1 "Required zvol does not exist: \$zvol"
|
|
return 1
|
|
fi
|
|
|
|
udevadm trigger --subsystem-match=block >/dev/null 2>&1 || true
|
|
|
|
attempt=0
|
|
while [ "\$attempt" -lt "\$wait_seconds" ]; do
|
|
[ -b "\$device_link" ] && break
|
|
sleep 1
|
|
attempt=\$((attempt + 1))
|
|
done
|
|
|
|
device="\$(readlink -f "\$device_link")"
|
|
|
|
[ -b "\$device" ] || {
|
|
eend 1 "Could not resolve \$device_link"
|
|
return 1
|
|
}
|
|
|
|
if awk -v device="\$device" \\
|
|
'NR > 1 && \$1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
swapon -p "\$priority" "\$device_link"
|
|
eend \$?
|
|
}
|
|
|
|
stop()
|
|
{
|
|
ebegin "Disabling ZFS swap"
|
|
|
|
device="\$(readlink -f "\$device_link")"
|
|
|
|
if [ ! -b "\$device" ]; then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
if ! awk -v device="\$device" \\
|
|
'NR > 1 && \$1 == device { found=1 } END { exit !found }' /proc/swaps
|
|
then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
swapoff "\$device"
|
|
eend \$?
|
|
}
|
|
EOF
|
|
|
|
chmod 0755 /etc/init.d/nenjim-zfs-swap
|
|
enable_service nenjim-zfs-swap boot
|
|
lbu include /etc/init.d/nenjim-zfs-swap
|
|
else
|
|
echo
|
|
echo "=== ZFS swap is disabled ==="
|
|
if zfs list -H "$SWAP_ZVOL" >/dev/null 2>&1; then
|
|
echo "An existing $SWAP_ZVOL is left intact but is not enabled as swap."
|
|
fi
|
|
fi
|
|
|
|
echo "Active swap devices before runtime package installation:"
|
|
cat /proc/swaps
|
|
if [ "$SWAP_SIZE_PCT_ZRAM" -gt 0 ]; then
|
|
zramctl
|
|
fi
|
|
|
|
ROOT_FILESYSTEM="$(awk '$2 == "/" { print $3; exit }' /proc/mounts)"
|
|
if [ "$ROOT_FILESYSTEM" = "tmpfs" ]; then
|
|
mount -o "remount,size=$DISKLESS_ROOT_SIZE" /
|
|
fi
|
|
|
|
grep -E 'MemTotal|SwapTotal|SwapFree' /proc/meminfo
|
|
|
|
|
|
# ==============================================================================
|
|
# Install the remaining runtime packages with swap active
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Installing runtime packages ==="
|
|
|
|
# shellcheck disable=SC2086
|
|
apk add $RUNTIME_PACKAGES
|
|
|
|
# A blanket apk upgrade is deliberately avoided. Diskless kernel and modloop
|
|
# updates belong in flash.sh so kernel and zfs-rpi remain matched.
|
|
|
|
|
|
# ==============================================================================
|
|
# Wi-Fi, DHCP and SSH persistence
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring Wi-Fi and DHCP ==="
|
|
|
|
mkdir -p /etc/wpa_supplicant /etc/conf.d /etc/network /etc/modprobe.d
|
|
|
|
# Load the Raspberry Pi Broadcom Wi-Fi driver deterministically. The headless
|
|
# bootstrap uses these stability options only on first boot and then removes
|
|
# its temporary file, so keep the final system's own copy.
|
|
grep -qxF brcmfmac /etc/modules 2>/dev/null || echo brcmfmac >>/etc/modules
|
|
|
|
cat >/etc/modprobe.d/nenjim-brcmfmac.conf <<'EOF'
|
|
options brcmfmac roamoff=1 feature_disable=0x282000
|
|
EOF
|
|
|
|
cat >/etc/init.d/nenjim-wifi-ready <<EOF
|
|
#!/sbin/openrc-run
|
|
|
|
description="Wait for the Nenjim Wi-Fi interface"
|
|
interface="$MY_IFACE"
|
|
wait_seconds=60
|
|
|
|
depend()
|
|
{
|
|
need modules
|
|
after udev-settle
|
|
before wpa_supplicant networking
|
|
}
|
|
|
|
start()
|
|
{
|
|
ebegin "Waiting for Wi-Fi interface \$interface"
|
|
|
|
if ! modprobe brcmfmac; then
|
|
eend 1 "Could not load brcmfmac"
|
|
return 1
|
|
fi
|
|
|
|
udevadm trigger --subsystem-match=net >/dev/null 2>&1 || true
|
|
|
|
attempt=0
|
|
while [ "\$attempt" -lt "\$wait_seconds" ]; do
|
|
if [ -d "/sys/class/net/\$interface" ]; then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
sleep 1
|
|
attempt=\$((attempt + 1))
|
|
done
|
|
|
|
eerror "Wi-Fi interface \$interface did not appear after \$wait_seconds seconds"
|
|
dmesg | grep -Ei 'brcm|firmware|wlan' | tail -20 >&2 || true
|
|
eend 1
|
|
return 1
|
|
}
|
|
EOF
|
|
|
|
chmod 0755 /etc/init.d/nenjim-wifi-ready
|
|
enable_service nenjim-wifi-ready boot
|
|
lbu include /etc/init.d/nenjim-wifi-ready
|
|
|
|
install \
|
|
-m 0600 \
|
|
"$BOOT_MEDIA/wpa_supplicant.conf" \
|
|
/etc/wpa_supplicant/wpa_supplicant.conf
|
|
|
|
cat >/etc/conf.d/wpa_supplicant <<EOF
|
|
wpa_supplicant_args="-i$MY_IFACE"
|
|
rc_need="nenjim-wifi-ready"
|
|
EOF
|
|
|
|
touch /etc/conf.d/networking
|
|
sed -i '/^[[:space:]]*rc_need=/d' /etc/conf.d/networking
|
|
echo 'rc_need="nenjim-wifi-ready"' >>/etc/conf.d/networking
|
|
|
|
cat >/etc/network/interfaces <<EOF
|
|
auto lo
|
|
iface lo inet loopback
|
|
|
|
auto $MY_IFACE
|
|
iface $MY_IFACE inet dhcp
|
|
EOF
|
|
|
|
enable_service wpa_supplicant boot
|
|
enable_service networking boot
|
|
|
|
lbu include /etc/conf.d/wpa_supplicant
|
|
lbu include /etc/conf.d/networking
|
|
lbu include /etc/modprobe.d/nenjim-brcmfmac.conf
|
|
|
|
|
|
# ==============================================================================
|
|
# OpenZFS boot services
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Enabling OpenZFS services ==="
|
|
|
|
grep -qxF zfs /etc/modules 2>/dev/null || echo zfs >>/etc/modules
|
|
|
|
enable_service modules boot
|
|
enable_service zfs-import boot
|
|
enable_service zfs-mount boot
|
|
enable_service zfs-zed default
|
|
|
|
|
|
# ==============================================================================
|
|
# Mount rpool/virt as ext4 before Docker and containerd
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring shared Docker/containerd storage ==="
|
|
|
|
zfs list -H "$VIRT_ZVOL" >/dev/null 2>&1 ||
|
|
die "Missing zvol created by prepare-storage.sh: $VIRT_ZVOL"
|
|
|
|
[ "$(zfs get -Hp -o value volsize "$VIRT_ZVOL")" = "$VIRT_SIZE_BYTES" ] ||
|
|
die "$VIRT_ZVOL does not match VIRT_ZVOL_SIZE_MIB=$VIRT_ZVOL_SIZE_MIB."
|
|
|
|
# This is a host block device, not a guest disk with partitions. Persist an
|
|
# explicit visible device mode instead of relying on the platform default.
|
|
zfs set volmode=dev "$VIRT_ZVOL"
|
|
|
|
udevadm trigger --subsystem-match=block 2>/dev/null || true
|
|
udevadm settle
|
|
wait_for_block_device "$VIRT_DEVICE" ||
|
|
die "Virt zvol device did not appear: $VIRT_DEVICE"
|
|
|
|
FSCK_STATUS=0
|
|
e2fsck -p "$VIRT_DEVICE" || FSCK_STATUS=$?
|
|
[ "$FSCK_STATUS" -le 1 ] ||
|
|
die "e2fsck could not repair $VIRT_DEVICE (status $FSCK_STATUS)."
|
|
|
|
mkdir -p "$VIRT_MOUNT"
|
|
if ! is_mounted "$VIRT_MOUNT"; then
|
|
mount -t ext4 -o rw,noatime "$VIRT_DEVICE" "$VIRT_MOUNT"
|
|
fi
|
|
|
|
mkdir -p "$VIRT_MOUNT/docker" "$VIRT_MOUNT/containerd"
|
|
chmod 0711 "$VIRT_MOUNT/docker" "$VIRT_MOUNT/containerd"
|
|
|
|
if [ -f /etc/fstab ]; then
|
|
sed -i '\|[[:space:]]/media/virt[[:space:]]|d' /etc/fstab
|
|
fi
|
|
echo "$VIRT_DEVICE $VIRT_MOUNT ext4 noauto,rw,noatime 0 0" >>/etc/fstab
|
|
|
|
cat >/etc/init.d/nenjim-virt <<EOF
|
|
#!/sbin/openrc-run
|
|
|
|
description="Nenjim Docker/containerd ext4 zvol"
|
|
pool="$POOL_NAME"
|
|
zvol="$VIRT_ZVOL"
|
|
device="$VIRT_DEVICE"
|
|
mountpoint="$VIRT_MOUNT"
|
|
wait_seconds=60
|
|
|
|
depend()
|
|
{
|
|
need zfs-import
|
|
after udev-settle
|
|
before containerd docker
|
|
}
|
|
|
|
start()
|
|
{
|
|
ebegin "Checking and mounting Nenjim virt storage"
|
|
|
|
if mountpoint -q "\$mountpoint"; then
|
|
eend 0
|
|
return 0
|
|
fi
|
|
|
|
if ! zpool list -H -o name "\$pool" >/dev/null 2>&1; then
|
|
eerror "ZFS pool \$pool was not imported by zfs-import"
|
|
eend 1
|
|
return 1
|
|
fi
|
|
|
|
if ! zfs list -H -o name "\$zvol" >/dev/null 2>&1; then
|
|
eerror "Required zvol does not exist: \$zvol"
|
|
eend 1
|
|
return 1
|
|
fi
|
|
|
|
# ZFS creates the kernel zvol asynchronously and udev subsequently creates
|
|
# /dev/zvol/<pool>/<name>. Wait for the exact link with a finite timeout so
|
|
# a udev problem cannot block the boot forever.
|
|
udevadm trigger --subsystem-match=block >/dev/null 2>&1 || true
|
|
|
|
attempt=0
|
|
while [ "\$attempt" -lt "\$wait_seconds" ]; do
|
|
[ -b "\$device" ] && break
|
|
sleep 1
|
|
attempt=\$((attempt + 1))
|
|
done
|
|
|
|
if [ ! -b "\$device" ]; then
|
|
eerror "Zvol device \$device did not appear after \$wait_seconds seconds"
|
|
ls -l /dev/zd* /dev/zvol/"\$pool" 2>/dev/null >&2 || true
|
|
eend 1
|
|
return 1
|
|
fi
|
|
|
|
e2fsck -p "\$device"
|
|
status=\$?
|
|
[ "\$status" -le 1 ] || {
|
|
eend "\$status"
|
|
return "\$status"
|
|
}
|
|
|
|
mount -t ext4 -o rw,noatime "\$device" "\$mountpoint"
|
|
eend \$?
|
|
}
|
|
|
|
stop()
|
|
{
|
|
ebegin "Unmounting Nenjim virt storage"
|
|
umount "\$mountpoint"
|
|
eend \$?
|
|
}
|
|
EOF
|
|
|
|
chmod 0755 /etc/init.d/nenjim-virt
|
|
enable_service nenjim-virt boot
|
|
lbu include /etc/init.d/nenjim-virt
|
|
|
|
|
|
# ==============================================================================
|
|
# User accounts and persistent homes
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring sysop and nenjim accounts ==="
|
|
|
|
if [ -f "$BOOT_MEDIA/sysop-preserve.tar.gz" ]; then
|
|
echo "Restoring the preserved /sysop rescue home"
|
|
tar -xzf "$BOOT_MEDIA/sysop-preserve.tar.gz" -C /
|
|
fi
|
|
|
|
mkdir -p /sysop "/home/$NENJIM_USER" "$SYSOP_DATA_ROOT"
|
|
|
|
create_or_validate_user "$SYSOP_USER" "$SYSOP_UID" /sysop /bin/bash
|
|
create_or_validate_user "$NENJIM_USER" "$NENJIM_UID" "/home/$NENJIM_USER" /bin/bash
|
|
|
|
addgroup "$SYSOP_USER" wheel 2>/dev/null || true
|
|
addgroup "$SYSOP_USER" docker 2>/dev/null || true
|
|
delgroup "$NENJIM_USER" wheel 2>/dev/null || true
|
|
delgroup "$NENJIM_USER" docker 2>/dev/null || true
|
|
|
|
echo "$SYSOP_USER:$SYSOP_PASS" | chpasswd
|
|
echo "$NENJIM_USER:$NENJIM_PASS" | chpasswd
|
|
|
|
chown "$SYSOP_USER:$SYSOP_USER" /sysop
|
|
chmod 0750 /sysop
|
|
|
|
chown "$NENJIM_USER:$NENJIM_USER" "/home/$NENJIM_USER"
|
|
chmod 0750 "/home/$NENJIM_USER"
|
|
|
|
cat >/etc/profile.d/nenjim-java.sh <<'PROFILE'
|
|
export JAVA_HOME=/usr/local/software/jre-25
|
|
export PATH="${JAVA_HOME}/bin:${PATH}"
|
|
PROFILE
|
|
chmod 0644 /etc/profile.d/nenjim-java.sh
|
|
|
|
mkdir -p /var/log
|
|
rm -f /var/log/NenjimHub-Install.log
|
|
ln -s "$INSTALL_LOG" /var/log/NenjimHub-Install.log
|
|
|
|
lbu include /sysop
|
|
lbu include /var/log/NenjimHub-Install.log
|
|
|
|
|
|
# ==============================================================================
|
|
# Password-required doas for sysop only
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring password-required doas for sysop ==="
|
|
|
|
mkdir -p /etc/doas.d
|
|
rm -f /etc/doas.d/20-wheel.conf /etc/doas.d/nenjim.conf /etc/doas.d/99-nenjim.conf
|
|
|
|
cat >/etc/doas.d/99-sysop.conf <<EOF
|
|
permit persist $SYSOP_USER as root
|
|
EOF
|
|
|
|
chmod 0400 /etc/doas.d/99-sysop.conf
|
|
doas -C /etc/doas.d/99-sysop.conf
|
|
|
|
id "$SYSOP_USER" | grep -qw wheel || die "sysop is not in wheel."
|
|
id "$SYSOP_USER" | grep -qw docker || die "sysop is not in the docker group."
|
|
if id "$NENJIM_USER" | grep -qw wheel; then
|
|
die "nenjim must not be in wheel."
|
|
fi
|
|
if id "$NENJIM_USER" | grep -qw docker; then
|
|
die "nenjim must not be in the docker group."
|
|
fi
|
|
|
|
|
|
# ==============================================================================
|
|
# Disable direct root login from console and SSH
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Locking direct root login ==="
|
|
|
|
ROOT_PASSWORD_FIELD="$(awk -F: '$1 == "root" { print $2; exit }' /etc/shadow)"
|
|
case "$ROOT_PASSWORD_FIELD" in
|
|
'!'*|'*'*)
|
|
;;
|
|
*)
|
|
sed -i 's/^root:[^:]*:/root:!:/' /etc/shadow
|
|
;;
|
|
esac
|
|
|
|
mkdir -p /etc/ssh/sshd_config.d
|
|
|
|
if ! grep -Eq \
|
|
'^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config.d/\*\.conf' \
|
|
/etc/ssh/sshd_config
|
|
then
|
|
{
|
|
echo 'Include /etc/ssh/sshd_config.d/*.conf'
|
|
cat /etc/ssh/sshd_config
|
|
} >/etc/ssh/sshd_config.new
|
|
|
|
mv /etc/ssh/sshd_config.new /etc/ssh/sshd_config
|
|
fi
|
|
|
|
cat >/etc/ssh/sshd_config.d/10-nenjim.conf <<EOF
|
|
PermitRootLogin no
|
|
PasswordAuthentication yes
|
|
PubkeyAuthentication yes
|
|
AllowUsers $SYSOP_USER $NENJIM_USER
|
|
EOF
|
|
|
|
chmod 0600 /etc/ssh/sshd_config.d/10-nenjim.conf
|
|
ssh-keygen -A
|
|
sshd -t
|
|
enable_service sshd default
|
|
rc-service sshd restart
|
|
|
|
|
|
# ==============================================================================
|
|
# Remove MOTD on every diskless boot before login services start
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Removing /etc/motd ==="
|
|
|
|
rm -f /etc/motd
|
|
|
|
cat >/etc/init.d/nenjim-clean-login <<'OPENRC'
|
|
#!/sbin/openrc-run
|
|
|
|
description="Remove the default Alpine MOTD"
|
|
|
|
depend()
|
|
{
|
|
before sshd
|
|
}
|
|
|
|
start()
|
|
{
|
|
rm -f /etc/motd
|
|
}
|
|
OPENRC
|
|
|
|
chmod 0755 /etc/init.d/nenjim-clean-login
|
|
enable_service nenjim-clean-login boot
|
|
lbu include /etc/init.d/nenjim-clean-login
|
|
|
|
# Alpine starts its physical console login directly from BusyBox inittab; it is
|
|
# not an OpenRC service. Ensure tty1 has a respawning login prompt and persist
|
|
# the inittab entry in the diskless overlay. Root remains locked, while sysop
|
|
# can use the physical console for recovery.
|
|
if ! grep -Eq '^[[:space:]]*tty1::respawn:/sbin/(a?getty|busybox[[:space:]]+getty)([[:space:]]|$)' /etc/inittab; then
|
|
echo 'tty1::respawn:/sbin/getty 38400 tty1' >>/etc/inittab
|
|
fi
|
|
lbu include /etc/inittab
|
|
|
|
|
|
# ==============================================================================
|
|
# Docker and standalone containerd on rpool/virt
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring Docker and containerd ==="
|
|
|
|
mkdir -p /var/lib /etc/conf.d
|
|
|
|
ensure_empty_path_can_be_linked /var/lib/docker
|
|
ensure_empty_path_can_be_linked /var/lib/containerd
|
|
|
|
ln -s "$VIRT_MOUNT/docker" /var/lib/docker
|
|
ln -s "$VIRT_MOUNT/containerd" /var/lib/containerd
|
|
|
|
for service in docker containerd; do
|
|
conf_file="/etc/conf.d/$service"
|
|
touch "$conf_file"
|
|
sed -i '/^[[:space:]]*rc_need=/d' "$conf_file"
|
|
echo 'rc_need="nenjim-virt"' >>"$conf_file"
|
|
done
|
|
|
|
lbu include /var/lib/docker
|
|
lbu include /var/lib/containerd
|
|
lbu include /etc/conf.d/docker
|
|
lbu include /etc/conf.d/containerd
|
|
|
|
enable_service cgroups default
|
|
enable_service containerd default
|
|
enable_service docker default
|
|
|
|
# Docker group access is root-equivalent. It is granted only to sysop; nenjim
|
|
# deliberately remains outside both wheel and docker.
|
|
|
|
|
|
# ==============================================================================
|
|
# Verify Java installed by prepare-storage.sh
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Verifying Java on rpool/software ==="
|
|
|
|
[ -x "$SOFTWARE_ROOT/jre-25/bin/java" ] ||
|
|
die "Java is missing from $SOFTWARE_ROOT; rerun prepare-storage.sh on a fresh card."
|
|
|
|
"$SOFTWARE_ROOT/jre-25/bin/java" -version
|
|
|
|
|
|
# ==============================================================================
|
|
# Build/install ZeroTier; state lives in sysop's ZFS-backed area
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Running the ZeroTier build/install module ==="
|
|
echo "nenjim can follow this with: tail -f /var/log/NenjimHub-Install.log"
|
|
|
|
SOFTWARE_ROOT="$SOFTWARE_ROOT" \
|
|
SYSOP_DATA_ROOT="$SYSOP_DATA_ROOT" \
|
|
ZEROTIER_VERSION="$ZEROTIER_VERSION" \
|
|
REQUIRED_SWAP_MIB="$ZEROTIER_REQUIRED_SWAP_MIB" \
|
|
ROOTFS_BUILD_SIZE="$DISKLESS_ROOT_SIZE" \
|
|
MAKE_JOBS="$ZEROTIER_MAKE_JOBS" \
|
|
ENABLE_SSO="$ZEROTIER_ENABLE_SSO" \
|
|
PREBUILT_BINARY="$BOOT_MEDIA/$ZEROTIER_PREBUILT_BINARY" \
|
|
/bin/bash "$BOOT_MEDIA/installZeroTier.sh"
|
|
|
|
|
|
# ==============================================================================
|
|
# zrepl binary, no-op configuration and daemon
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Configuring zrepl with an empty job list ==="
|
|
|
|
ZREPL_BINARY="$SOFTWARE_ROOT/zrepl/current/zrepl"
|
|
[ -x "$ZREPL_BINARY" ] ||
|
|
die "zrepl is missing from $SOFTWARE_ROOT; rerun prepare-storage.sh on a fresh card."
|
|
|
|
mkdir -p /usr/local/bin "$ZREPL_CONFIG_DIRECTORY" /etc/zrepl /sysop
|
|
|
|
rm -f /usr/local/bin/zrepl
|
|
ln -s "$ZREPL_BINARY" /usr/local/bin/zrepl
|
|
|
|
if [ -e /sysop/zrepl ] || [ -L /sysop/zrepl ]; then
|
|
if [ -L /sysop/zrepl ]; then
|
|
rm -f /sysop/zrepl
|
|
elif [ -d /sysop/zrepl ] &&
|
|
! find /sysop/zrepl -mindepth 1 -print -quit | grep -q .
|
|
then
|
|
rmdir /sysop/zrepl
|
|
else
|
|
die "Refusing to replace non-empty /sysop/zrepl."
|
|
fi
|
|
fi
|
|
|
|
ln -s "$ZREPL_CONFIG_DIRECTORY" /sysop/zrepl
|
|
|
|
if [ ! -f "$ZREPL_CONFIG_FILE" ]; then
|
|
cat >"$ZREPL_CONFIG_FILE" <<'ZREPL'
|
|
jobs: []
|
|
ZREPL
|
|
fi
|
|
|
|
chown -R "$SYSOP_USER:$SYSOP_USER" "$ZREPL_CONFIG_DIRECTORY"
|
|
chmod 0750 "$ZREPL_CONFIG_DIRECTORY"
|
|
chmod 0640 "$ZREPL_CONFIG_FILE"
|
|
|
|
rm -f /etc/zrepl/zrepl.yml
|
|
ln -s /sysop/zrepl/zrepl.yml /etc/zrepl/zrepl.yml
|
|
|
|
/usr/local/bin/zrepl configcheck
|
|
|
|
cat >/etc/init.d/zrepl <<'OPENRC'
|
|
#!/sbin/openrc-run
|
|
|
|
description="zrepl ZFS replication daemon"
|
|
|
|
command="/usr/local/bin/zrepl"
|
|
command_args="daemon"
|
|
command_background="yes"
|
|
pidfile="/run/${RC_SVCNAME}.pid"
|
|
|
|
depend()
|
|
{
|
|
need zfs-mount
|
|
use net logger
|
|
after networking
|
|
}
|
|
|
|
start_pre()
|
|
{
|
|
mkdir -p /run/zrepl
|
|
chmod 0700 /run/zrepl
|
|
"$command" configcheck
|
|
}
|
|
OPENRC
|
|
|
|
chmod 0755 /etc/init.d/zrepl
|
|
enable_service zrepl default
|
|
lbu include /etc/init.d/zrepl
|
|
|
|
echo "Starting zrepl with the no-op configuration"
|
|
rc-service zrepl start
|
|
|
|
ZREPL_CONTROL_SOCKET="/var/run/zrepl/control"
|
|
ZREPL_READY=0
|
|
attempt=0
|
|
while [ "$attempt" -lt 30 ]; do
|
|
if [ -S "$ZREPL_CONTROL_SOCKET" ]; then
|
|
ZREPL_READY=1
|
|
break
|
|
fi
|
|
|
|
sleep 1
|
|
attempt=$((attempt + 1))
|
|
done
|
|
|
|
[ "$ZREPL_READY" -eq 1 ] ||
|
|
die "zrepl started, but its control socket did not appear: $ZREPL_CONTROL_SOCKET"
|
|
|
|
lbu include /usr/local/bin/zrepl
|
|
lbu include /sysop/zrepl
|
|
|
|
|
|
# ==============================================================================
|
|
# Synchronize the diskless package cache and verify the finished design
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Synchronizing the persistent APK cache ==="
|
|
|
|
# setup-alpine may have installed eudev before /etc/apk/cache pointed at the
|
|
# boot partition. `apk add` then sees it as already installed and does not
|
|
# download its APK. A full cache sync is therefore required so every installed
|
|
# package can be reconstructed in RAM before networking exists on the next boot.
|
|
apk cache -v sync
|
|
|
|
echo
|
|
echo "=== Verifying packages, pool, users and services ==="
|
|
|
|
for package in \
|
|
bash btop containerd containerd-openrc doas docker-cli docker-engine \
|
|
docker-openrc e2fsprogs eudev eudev-openrc git htop kitty-terminfo \
|
|
musl-locales openssh-server tmux \
|
|
udev-init-scripts udev-init-scripts-openrc \
|
|
wpa_supplicant zfs zfs-openrc zfs-scripts zfs-udev \
|
|
zram-init zram-init-openrc
|
|
do
|
|
apk info -e "$package" >/dev/null ||
|
|
die "Required package is not installed: $package"
|
|
done
|
|
|
|
for service in udev udev-trigger udev-settle
|
|
do
|
|
[ -x "/etc/init.d/$service" ] ||
|
|
die "Required udev service is missing after APK cache sync: $service"
|
|
done
|
|
|
|
zpool status "$POOL_NAME"
|
|
zfs list -r "$POOL_NAME"
|
|
|
|
is_mounted /home || die "$POOL_NAME/home is not mounted."
|
|
is_mounted "$SOFTWARE_ROOT" || die "$POOL_NAME/software is not mounted."
|
|
is_mounted "$SYSOP_DATA_ROOT" || die "$POOL_NAME/sysop is not mounted."
|
|
is_mounted "$VIRT_MOUNT" || die "$POOL_NAME/virt ext4 is not mounted."
|
|
|
|
if [ "$SWAP_SIZE_MIB_PART" -gt 0 ]; then
|
|
PART_SWAP_KERNEL_DEVICE="$(readlink -f "$SWAP_PART_DEVICE")"
|
|
[ -b "$PART_SWAP_KERNEL_DEVICE" ] ||
|
|
die "Could not resolve $SWAP_PART_DEVICE during final verification."
|
|
|
|
ACTIVE_PART_PRIORITY="$(
|
|
awk -v device="$PART_SWAP_KERNEL_DEVICE" \
|
|
'NR > 1 && $1 == device { print $5; exit }' /proc/swaps
|
|
)"
|
|
|
|
[ "$ACTIVE_PART_PRIORITY" = "$SWAP_PRI_PART" ] ||
|
|
die "$SWAP_PART_DEVICE has priority ${ACTIVE_PART_PRIORITY:-inactive}, expected $SWAP_PRI_PART."
|
|
fi
|
|
|
|
if [ "$SWAP_SIZE_PCT_ZRAM" -gt 0 ]; then
|
|
ACTIVE_ZRAM_PRIORITY="$(
|
|
awk -v device="$ZRAM_SWAP_DEVICE" \
|
|
'NR > 1 && $1 == device { print $5; exit }' /proc/swaps
|
|
)"
|
|
|
|
[ "$ACTIVE_ZRAM_PRIORITY" = "$SWAP_PRI_ZRAM" ] ||
|
|
die "$ZRAM_SWAP_DEVICE has priority ${ACTIVE_ZRAM_PRIORITY:-inactive}, expected $SWAP_PRI_ZRAM."
|
|
fi
|
|
|
|
if [ "$SWAP_SIZE_MIB_ZFS" -gt 0 ]; then
|
|
SWAP_KERNEL_DEVICE="$(readlink -f "$SWAP_DEVICE")"
|
|
[ -b "$SWAP_KERNEL_DEVICE" ] ||
|
|
die "Could not resolve $SWAP_DEVICE during final verification."
|
|
|
|
ACTIVE_ZFS_PRIORITY="$(
|
|
awk -v device="$SWAP_KERNEL_DEVICE" \
|
|
'NR > 1 && $1 == device { print $5; exit }' /proc/swaps
|
|
)"
|
|
|
|
[ "$ACTIVE_ZFS_PRIORITY" = "$SWAP_PRI_ZFS" ] ||
|
|
die "$SWAP_DEVICE has priority ${ACTIVE_ZFS_PRIORITY:-inactive}, expected $SWAP_PRI_ZFS."
|
|
fi
|
|
|
|
[ "$(readlink /var/lib/docker)" = "$VIRT_MOUNT/docker" ] ||
|
|
die "Docker data is not linked to rpool/virt."
|
|
|
|
[ "$(readlink /var/lib/containerd)" = "$VIRT_MOUNT/containerd" ] ||
|
|
die "containerd data is not linked to rpool/virt."
|
|
|
|
[ "$(readlink /sysop/zerotier)" = "$SYSOP_DATA_ROOT/zerotier" ] ||
|
|
die "ZeroTier state is not linked into /sysop."
|
|
|
|
[ "$(readlink /sysop/zrepl)" = "$ZREPL_CONFIG_DIRECTORY" ] ||
|
|
die "zrepl configuration is not linked into /sysop."
|
|
|
|
ROOT_PASSWORD_FIELD="$(awk -F: '$1 == "root" { print $2; exit }' /etc/shadow)"
|
|
case "$ROOT_PASSWORD_FIELD" in
|
|
'!'*|'*'*) echo "Root password login is locked." ;;
|
|
*) die "Root password login is not locked." ;;
|
|
esac
|
|
|
|
REQUIRED_SERVICES="
|
|
cgroups
|
|
containerd
|
|
docker
|
|
nenjim-clean-login
|
|
nenjim-virt
|
|
nenjim-wifi-ready
|
|
networking
|
|
sshd
|
|
wpa_supplicant
|
|
zerotier-one
|
|
zfs-import
|
|
zfs-mount
|
|
zfs-zed
|
|
zrepl
|
|
"
|
|
|
|
if [ "$SWAP_SIZE_MIB_PART" -gt 0 ]; then
|
|
REQUIRED_SERVICES="$REQUIRED_SERVICES nenjim-part-swap"
|
|
fi
|
|
if [ "$SWAP_SIZE_MIB_ZFS" -gt 0 ]; then
|
|
REQUIRED_SERVICES="$REQUIRED_SERVICES nenjim-zfs-swap"
|
|
fi
|
|
if [ "$SWAP_SIZE_PCT_ZRAM" -gt 0 ]; then
|
|
REQUIRED_SERVICES="$REQUIRED_SERVICES zram-init"
|
|
fi
|
|
|
|
for service in $REQUIRED_SERVICES
|
|
do
|
|
[ -x "/etc/init.d/$service" ] ||
|
|
die "Required OpenRC service file is missing: /etc/init.d/$service"
|
|
rc-update show | grep -Eq "^[[:space:]]*$service[[:space:]]+\\|" ||
|
|
die "Required boot service is not enabled: $service"
|
|
done
|
|
|
|
/usr/sbin/zerotier-one -v
|
|
/usr/local/bin/zrepl configcheck
|
|
/usr/local/bin/zrepl version
|
|
|
|
|
|
# ==============================================================================
|
|
# Persist the requested final ZRAM size for normal boots
|
|
# ==============================================================================
|
|
|
|
if [ "$SWAP_SIZE_PCT_ZRAM" -gt 0 ]; then
|
|
echo
|
|
echo "=== Persisting final $SWAP_SIZE_PCT_ZRAM% ZRAM policy ==="
|
|
|
|
# The active provisioning ZRAM remains larger until the imminent reboot.
|
|
# OpenRC reads the requested final policy on the next boot.
|
|
write_zram_config "$SWAP_SIZE_PCT_ZRAM"
|
|
else
|
|
rm -f /etc/conf.d/zram-init
|
|
fi
|
|
|
|
|
|
# ==============================================================================
|
|
# Commit the final APKoVL
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Creating final APKoVL ==="
|
|
|
|
lbu status -a || true
|
|
lbu commit -d
|
|
sync
|
|
|
|
|
|
# ==============================================================================
|
|
# Remove one-time bootstrap inputs
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "=== Removing one-time bootstrap files ==="
|
|
|
|
rm -f "$BOOT_MEDIA/headless.apkovl.tar.gz"
|
|
rm -f "$BOOT_MEDIA/unattended.sh"
|
|
rm -f "$BOOT_MEDIA/installZeroTier.sh"
|
|
rm -f "$BOOT_MEDIA/$ZEROTIER_PREBUILT_BINARY"
|
|
rm -f "$BOOT_MEDIA/$BUILD_TIME_FILE_NAME"
|
|
rm -f "$BOOT_MEDIA/wpa_supplicant.conf"
|
|
rm -f "$BOOT_MEDIA/sysop-preserve.tar.gz"
|
|
|
|
sync
|
|
|
|
|
|
# ==============================================================================
|
|
# Finished
|
|
# ==============================================================================
|
|
|
|
echo
|
|
echo "============================================================================"
|
|
echo "Installation complete."
|
|
echo
|
|
echo "sysop/sysop is the rescue, password-doas and Docker administrator account."
|
|
echo "nenjim/nenjim cannot use doas, su or Docker directly."
|
|
echo "Root password login and SSH root login are disabled."
|
|
echo "ZeroTier is enabled but a fresh installation is not joined to a network."
|
|
echo "Docker, containerd, OpenZFS and no-op zrepl are enabled for next boot."
|
|
echo "The system will now reboot."
|
|
echo "============================================================================"
|
|
|
|
logger -t nenjim-install "Installation complete - rebooting"
|
|
INSTALL_END_UPTIME_SECONDS="$(awk '{ print int($1); exit }' /proc/uptime)"
|
|
INSTALL_DURATION_SECONDS=$((INSTALL_END_UPTIME_SECONDS - INSTALL_START_UPTIME_SECONDS))
|
|
INSTALL_DURATION="$(format_duration "$INSTALL_DURATION_SECONDS")"
|
|
echo "Total installation time: $INSTALL_DURATION (HH:MM:SS, $INSTALL_DURATION_SECONDS seconds)."
|
|
sync
|
|
reboot
|