install.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env sh
# hy2scale installer.
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/FrankoonG/hy2scale/main/install.sh | sudo sh
#
# Re-running upgrades in place. Pin a version with:
# curl -fsSL https://raw.githubusercontent.com/FrankoonG/hy2scale/main/install.sh | sudo HY2SCALE_VERSION=v1.3.1 sh
#
# Uninstall:
# curl -fsSL https://raw.githubusercontent.com/FrankoonG/hy2scale/main/install.sh | sudo sh -s -- --uninstall
#
# Env overrides:
# HY2SCALE_VERSION release tag to install (default: latest)
# HY2SCALE_DATA_DIR data dir (default /var/lib/hy2scale)
# HY2SCALE_BIN binary path (default /usr/local/bin/hy2scale)
# HY2SCALE_PLATFORM auto (default) | linux | unifios
# HY2SCALE_SCRIPT_REF git ref the platform files come from (default main)
#
# UniFi OS (detected automatically) installs everything under /data/hy2scale,
# which survives firmware updates, and is managed afterwards with
# /data/hy2scale/manage.sh (status / update / uninstall). Files under
# platform/unifios/ in the repository.
set -eu
REPO="FrankoonG/hy2scale"
VERSION="${HY2SCALE_VERSION:-}"
DATA_DIR="${HY2SCALE_DATA_DIR:-/var/lib/hy2scale}"
BIN_PATH="${HY2SCALE_BIN:-/usr/local/bin/hy2scale}"
SVC_NAME="hy2scale"
SVC_PATH="/etc/systemd/system/${SVC_NAME}.service"
PORT=5565
PLATFORM="${HY2SCALE_PLATFORM:-auto}"
SCRIPT_REF="${HY2SCALE_SCRIPT_REF:-main}"
UNIFI_ROOT="/data/hy2scale"
UNIFI_FILES="manage.sh units/hy2scale.service units/hy2scale-ensure.service units/hy2scale-ensure.timer"
UNINSTALL=0
for arg in "$@"; do
case "$arg" in
--uninstall|--remove) UNINSTALL=1 ;;
-h|--help)
sed -n 's/^# \{0,1\}//p' "$0" | sed -n '2,22p'
exit 0 ;;
esac
done
err() { printf '\033[31m✗\033[0m %s\n' "$*" >&2; exit 1; }
info() { printf '\033[32m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[33m!\033[0m %s\n' "$*" >&2; }
# ─── 1. preflight ─────────────────────────────────────────────────────────
[ "$(id -u)" -eq 0 ] || err "must run as root — re-run with sudo (curl … | sudo sh)"
case "$(uname -s)" in
Linux) ;;
*) err "only Linux is supported (this script ran on: $(uname -s))" ;;
esac
for cmd in curl tar gzip uname mktemp install grep; do
command -v "$cmd" >/dev/null 2>&1 || err "$cmd not found in PATH"
done
# ─── 1b. platform ──────────────────────────────────────────────────────────
case "$PLATFORM" in
auto)
if command -v ubnt-device-info >/dev/null 2>&1 && [ -d /data ]; then
PLATFORM=unifios
else
PLATFORM=linux
fi ;;
linux | unifios) ;;
*) err "unknown HY2SCALE_PLATFORM: $PLATFORM (auto, linux or unifios)" ;;
esac
if [ "$PLATFORM" = unifios ]; then
[ -d /run/systemd/system ] || err "UniFi OS install needs systemd"
DATA_DIR="${UNIFI_ROOT}/data"
BIN_PATH="${UNIFI_ROOT}/hy2scale"
info "Platform: UniFi OS (installing under ${UNIFI_ROOT})"
fi
# ─── 2. architecture detection ────────────────────────────────────────────
RAW_ARCH="$(uname -m)"
case "$RAW_ARCH" in
x86_64 | amd64)
ARCH="linux-amd64" ;;
aarch64 | arm64)
ARCH="linux-arm64" ;;
armv7* | armv8l | armhf | arm)
ARCH="linux-armv7" ;;
mips64le | mips64el)
ARCH="linux-mips64le" ;;
mipsel | mipsle)
ARCH="linux-mipsle-softfloat" ;;
*)
err "unsupported architecture: $RAW_ARCH (supported: x86_64, aarch64, armv7, mips64le, mipsel)" ;;
esac
# ─── 3. uninstall path ────────────────────────────────────────────────────
if [ "$UNINSTALL" -eq 1 ] && [ "$PLATFORM" = unifios ]; then
[ -f "${UNIFI_ROOT}/manage.sh" ] || err "${UNIFI_ROOT}/manage.sh not found — nothing to uninstall"
exec sh "${UNIFI_ROOT}/manage.sh" uninstall
fi
if [ "$UNINSTALL" -eq 1 ]; then
info "Uninstalling hy2scale…"
if command -v systemctl >/dev/null 2>&1; then
systemctl stop "$SVC_NAME" 2>/dev/null || true
systemctl disable "$SVC_NAME" 2>/dev/null || true
fi
rm -f "$SVC_PATH"
command -v systemctl >/dev/null 2>&1 && systemctl daemon-reload 2>/dev/null || true
rm -f "$BIN_PATH"
info "Removed binary and service unit."
info "Data dir at $DATA_DIR is preserved — delete manually with: rm -rf $DATA_DIR"
exit 0
fi
# ─── 4. resolve version (default = latest release) ────────────────────────
if [ -z "$VERSION" ]; then
info "Resolving latest release tag from GitHub…"
VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
| grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \
| head -n1 \
| sed 's/.*"\([^"]*\)".*/\1/')"
[ -n "$VERSION" ] || err "could not determine latest version (rate-limited or network issue)"
fi
info "Installing hy2scale ${VERSION} for ${ARCH}…"
# ─── 5. download tarball ──────────────────────────────────────────────────
TARBALL="hy2scale-${ARCH}.tar.gz"
URL="https://github.com/${REPO}/releases/download/${VERSION}/${TARBALL}"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
info "Fetching $URL"
curl -fL --progress-bar "$URL" -o "${TMP_DIR}/${TARBALL}" \
|| err "download failed — check the version tag and network"
# Verify against the release's checksums-sha256.txt (required)
CHECKSUM_URL="https://github.com/${REPO}/releases/download/${VERSION}/checksums-sha256.txt"
curl -fsSL "$CHECKSUM_URL" -o "${TMP_DIR}/checksums.txt" \
|| err "could not download checksums-sha256.txt for ${VERSION}"
EXPECTED="$(grep -E "[[:space:]]\\*?${TARBALL}\$" "${TMP_DIR}/checksums.txt" | awk '{print $1}')"
[ -n "$EXPECTED" ] || err "${TARBALL} is not listed in checksums-sha256.txt"
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL="$(sha256sum "${TMP_DIR}/${TARBALL}" | awk '{print $1}')"
elif command -v openssl >/dev/null 2>&1; then
ACTUAL="$(openssl dgst -sha256 "${TMP_DIR}/${TARBALL}" | awk '{print $NF}')"
else
err "need sha256sum or openssl to verify the download"
fi
[ "$ACTUAL" = "$EXPECTED" ] || err "checksum mismatch — expected $EXPECTED, got $ACTUAL"
info "Checksum OK"
# ─── 6. extract ───────────────────────────────────────────────────────────
tar -xzf "${TMP_DIR}/${TARBALL}" -C "${TMP_DIR}"
EXTRACTED="${TMP_DIR}/hy2scale-${ARCH}"
[ -f "$EXTRACTED" ] || err "expected binary at ${EXTRACTED} not found in tarball"
# ─── 6b. UniFi OS: binary, platform files and units under /data ──────────
if [ "$PLATFORM" = unifios ]; then
# Platform files come from the repository (HY2SCALE_PLATFORM_SRC: a local
# directory with the same layout, for offline installs and tests).
mkdir -p "${TMP_DIR}/platform/units"
for f in $UNIFI_FILES; do
name="$(basename "$f")"
if [ -n "${HY2SCALE_PLATFORM_SRC:-}" ]; then
cp "${HY2SCALE_PLATFORM_SRC}/${name}" "${TMP_DIR}/platform/${f}" \
|| err "${HY2SCALE_PLATFORM_SRC}/${name} not found"
else
curl -fsSL "https://raw.githubusercontent.com/${REPO}/${SCRIPT_REF}/platform/unifios/${name}" \
-o "${TMP_DIR}/platform/${f}" || err "could not download platform/unifios/${name} (ref ${SCRIPT_REF})"
fi
done
if systemctl is-active --quiet "$SVC_NAME" 2>/dev/null; then
info "Stopping existing $SVC_NAME service for in-place upgrade…"
systemctl stop "$SVC_NAME"
fi
mkdir -p "${UNIFI_ROOT}/units" "$DATA_DIR"
install -m 0755 "$EXTRACTED" "$BIN_PATH"
if [ -f "${EXTRACTED}.sig" ]; then
install -m 0644 "${EXTRACTED}.sig" "${BIN_PATH}.sig"
else
rm -f "${BIN_PATH}.sig"
fi
install -m 0755 "${TMP_DIR}/platform/manage.sh" "${UNIFI_ROOT}/manage.sh"
for u in hy2scale.service hy2scale-ensure.service hy2scale-ensure.timer; do
install -m 0644 "${TMP_DIR}/platform/units/${u}" "${UNIFI_ROOT}/units/${u}"
done
printf '%s\n' "$SCRIPT_REF" > "${UNIFI_ROOT}/.script-ref"
info "Binary installed at $BIN_PATH"
sh "${UNIFI_ROOT}/manage.sh" setup
sleep 2
if systemctl is-active --quiet "$SVC_NAME"; then
info "Service is active."
else
warn "Service did not stay active. Inspect with: journalctl -u $SVC_NAME -n 50 --no-pager"
fi
# The LAN side: addresses of the gateway's bridges (its networks).
LAN_IPS="$(ip -4 -o addr show 2>/dev/null | awk '$2 ~ /^br/ {split($4, a, "/"); print a[1]}')"
[ -n "$LAN_IPS" ] || LAN_IPS="$(hostname -I 2>/dev/null | awk '{print $1}')"
[ -n "$LAN_IPS" ] || LAN_IPS="<host>"
printf '\n'
printf '\033[32m✓ hy2scale %s installed (UniFi OS).\033[0m\n' "$(echo "$VERSION" | sed 's/^v//')"
printf '\n'
printf ' Binary %s\n' "$BIN_PATH"
printf ' Data dir %s\n' "$DATA_DIR"
printf ' Service %s (enabled, running; restored by hy2scale-ensure.timer)\n' "$SVC_NAME"
printf ' Logs journalctl -u %s -f\n' "$SVC_NAME"
for ip in $LAN_IPS; do
printf ' Web UI https://%s:%s/scale\n' "$ip" "$PORT"
done
printf ' Login admin / admin (a new password is required at first login)\n'
printf ' Manage %s/manage.sh status | update | uninstall\n' "$UNIFI_ROOT"
printf '\n'
warn "Open UDP $PORT to the gateway in UniFi Network (firewall policy) if other nodes connect to this one."
if command -v ss >/dev/null 2>&1 && ss -lun 2>/dev/null | grep -qE ':(500|4500)[[:space:]]'; then
warn "UDP 500/4500 is in use by UniFi's own VPN — leave hy2scale's IKEv2 and L2TP off."
fi
exit 0
fi
# ─── 7. install binary (stop service first if upgrading) ──────────────────
if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet "$SVC_NAME" 2>/dev/null; then
info "Stopping existing $SVC_NAME service for in-place upgrade…"
systemctl stop "$SVC_NAME"
fi
mkdir -p "$(dirname "$BIN_PATH")"
install -m 0755 "$EXTRACTED" "$BIN_PATH"
# Release signature next to the binary (the node shows itself as an official build)
if [ -f "${EXTRACTED}.sig" ]; then
install -m 0644 "${EXTRACTED}.sig" "${BIN_PATH}.sig"
else
rm -f "${BIN_PATH}.sig"
fi
info "Binary installed at $BIN_PATH"
# ─── 8. data dir ──────────────────────────────────────────────────────────
if [ ! -d "$DATA_DIR" ]; then
mkdir -p "$DATA_DIR"
info "Created data dir $DATA_DIR"
fi
# ─── 9. systemd unit ──────────────────────────────────────────────────────
HAS_SYSTEMD=0
if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then
HAS_SYSTEMD=1
info "Writing systemd unit $SVC_PATH"
# NOTE: we run as root deliberately. hy2scale needs CAP_NET_ADMIN
# for iptables / TUN management and CAP_NET_RAW for some rule
# probes; running as root yields these without ambient-capability
# gymnastics, matching the documented Docker deploy.
cat > "$SVC_PATH" <<UNIT
[Unit]
Description=hy2scale — Hysteria 2 mesh relay
Documentation=https://github.com/${REPO}
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=${BIN_PATH} --data ${DATA_DIR}
Restart=always
RestartSec=3
LimitNOFILE=1048576
KillMode=mixed
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable "$SVC_NAME" >/dev/null 2>&1 || true
info "Starting $SVC_NAME…"
systemctl restart "$SVC_NAME"
# Give it a moment to crash if it's going to
sleep 2
if systemctl is-active --quiet "$SVC_NAME"; then
info "Service is active."
else
warn "Service did not stay active. Inspect with: journalctl -u $SVC_NAME -n 50 --no-pager"
fi
else
warn "systemd not detected — skipping service registration."
warn "Run manually: $BIN_PATH --data $DATA_DIR"
fi
# ─── 10. final info ───────────────────────────────────────────────────────
HOST_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
[ -n "$HOST_IP" ] || HOST_IP="<host>"
VER_NUM="$(echo "$VERSION" | sed 's/^v//')"
printf '\n'
printf '\033[32m✓ hy2scale %s installed.\033[0m\n' "$VER_NUM"
printf '\n'
printf ' Binary %s\n' "$BIN_PATH"
printf ' Data dir %s\n' "$DATA_DIR"
if [ "$HAS_SYSTEMD" -eq 1 ]; then
printf ' Service %s (enabled, running)\n' "$SVC_PATH"
printf ' Logs journalctl -u %s -f\n' "$SVC_NAME"
fi
printf ' Web UI https://%s:%s/scale (login: admin / admin — forces password change on first login)\n' "$HOST_IP" "$PORT"
printf '\n'
printf ' Uninstall curl -fsSL https://raw.githubusercontent.com/%s/main/install.sh | sudo sh -s -- --uninstall\n' "$REPO"
printf '\n'
if command -v ufw >/dev/null 2>&1 || command -v firewall-cmd >/dev/null 2>&1; then
warn "A host firewall is present. Allow TCP+UDP $PORT (and 500/4500/1194/51820/UDP if you use VPN services)."
fi