508 lines
14 KiB
Bash
Executable File
508 lines
14 KiB
Bash
Executable File
#!/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."
|