Files
r35157_nenjimhub-os/installZeroTier.sh
T

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."