360 lines
9.3 KiB
Bash
360 lines
9.3 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
|
||
|
|
# DcRouter Installer Script
|
||
|
|
# Installs the self-extracting Linux binary by default, or builds the NodeNext
|
||
|
|
# source package when --source is specified.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# Binary install:
|
||
|
|
# curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash
|
||
|
|
#
|
||
|
|
# Source install:
|
||
|
|
# curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash -s -- --source
|
||
|
|
#
|
||
|
|
# Options:
|
||
|
|
# -h, --help Show this help message
|
||
|
|
# --version VERSION Install a specific tag/version (e.g. vX.Y.Z)
|
||
|
|
# --install-dir DIR Installation directory (default: /opt/dcrouter)
|
||
|
|
# --binary Install release binary (default)
|
||
|
|
# --source Clone the tag and build the NodeNext package locally
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
SHOW_HELP=0
|
||
|
|
SPECIFIED_VERSION=""
|
||
|
|
INSTALL_DIR="/opt/dcrouter"
|
||
|
|
INSTALL_MODE="binary"
|
||
|
|
GITEA_BASE_URL="https://code.foss.global"
|
||
|
|
GITEA_REPO="serve.zone/dcrouter"
|
||
|
|
SERVICE_NAME="dcrouter"
|
||
|
|
BIN_DIR="/usr/local/bin"
|
||
|
|
|
||
|
|
while [[ $# -gt 0 ]]; do
|
||
|
|
case "$1" in
|
||
|
|
-h|--help)
|
||
|
|
SHOW_HELP=1
|
||
|
|
shift
|
||
|
|
;;
|
||
|
|
--version)
|
||
|
|
if [[ $# -lt 2 ]]; then
|
||
|
|
echo "Error: --version requires a value"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
SPECIFIED_VERSION="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--install-dir)
|
||
|
|
if [[ $# -lt 2 ]]; then
|
||
|
|
echo "Error: --install-dir requires a value"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
INSTALL_DIR="$2"
|
||
|
|
shift 2
|
||
|
|
;;
|
||
|
|
--binary)
|
||
|
|
INSTALL_MODE="binary"
|
||
|
|
shift
|
||
|
|
;;
|
||
|
|
--source)
|
||
|
|
INSTALL_MODE="source"
|
||
|
|
shift
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "Unknown option: $1"
|
||
|
|
echo "Use -h or --help for usage information"
|
||
|
|
exit 1
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
if [[ $SHOW_HELP -eq 1 ]]; then
|
||
|
|
echo "DcRouter Installer Script"
|
||
|
|
echo "Installs DcRouter as a self-extracting binary or NodeNext source build."
|
||
|
|
echo ""
|
||
|
|
echo "Usage: $0 [options]"
|
||
|
|
echo ""
|
||
|
|
echo "Options:"
|
||
|
|
echo " -h, --help Show this help message"
|
||
|
|
echo " --version VERSION Install a specific tag/version (e.g. vX.Y.Z)"
|
||
|
|
echo " --install-dir DIR Installation directory (default: /opt/dcrouter)"
|
||
|
|
echo " --binary Install release binary (default)"
|
||
|
|
echo " --source Clone the tag and build the NodeNext package locally"
|
||
|
|
echo ""
|
||
|
|
echo "Examples:"
|
||
|
|
echo " curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash"
|
||
|
|
echo " curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash -s -- --source"
|
||
|
|
echo " curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash -s -- --version vX.Y.Z"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$EUID" -ne 0 ]]; then
|
||
|
|
echo "Please run as root (sudo bash install.sh or pipe to sudo bash)"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
case "$INSTALL_DIR" in
|
||
|
|
""|"/")
|
||
|
|
echo "Error: unsafe install directory: $INSTALL_DIR"
|
||
|
|
exit 1
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
require_command() {
|
||
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||
|
|
echo "Error: required command not found: $1"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
ensure_pnpm() {
|
||
|
|
if command -v pnpm >/dev/null 2>&1; then
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
if command -v corepack >/dev/null 2>&1; then
|
||
|
|
corepack enable
|
||
|
|
fi
|
||
|
|
if ! command -v pnpm >/dev/null 2>&1; then
|
||
|
|
echo "Error: pnpm is required for --source installs. Install Node.js with corepack/pnpm first."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
make_executable_if_present() {
|
||
|
|
if [[ -f "$1" ]]; then
|
||
|
|
chmod 0755 "$1"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
get_latest_version() {
|
||
|
|
echo "Fetching latest release version from Gitea..." >&2
|
||
|
|
|
||
|
|
local api_url="${GITEA_BASE_URL}/api/v1/repos/${GITEA_REPO}/releases/latest"
|
||
|
|
local response
|
||
|
|
if ! response=$(curl -fsSL "$api_url" 2>/dev/null); then
|
||
|
|
echo "Error: Failed to fetch latest release information from Gitea API" >&2
|
||
|
|
echo "URL: $api_url" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
local version
|
||
|
|
version=$(printf '%s' "$response" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
||
|
|
if [[ -z "$version" ]]; then
|
||
|
|
echo "Error: Could not determine latest version from API response" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "$version"
|
||
|
|
}
|
||
|
|
|
||
|
|
detect_binary_name() {
|
||
|
|
local os
|
||
|
|
local arch
|
||
|
|
os=$(uname -s)
|
||
|
|
arch=$(uname -m)
|
||
|
|
|
||
|
|
if [[ "$os" != "Linux" ]]; then
|
||
|
|
echo "Error: binary installer currently supports Linux only. Use --source for this platform." >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
case "$arch" in
|
||
|
|
x86_64|amd64)
|
||
|
|
echo "dcrouter-linux-x64"
|
||
|
|
;;
|
||
|
|
aarch64|arm64)
|
||
|
|
echo "dcrouter-linux-arm64"
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "Error: unsupported architecture for binary install: $arch. Use --source." >&2
|
||
|
|
exit 1
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "================================================"
|
||
|
|
echo " DcRouter Installation Script"
|
||
|
|
echo "================================================"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
require_command curl
|
||
|
|
require_command sed
|
||
|
|
|
||
|
|
if [[ -n "$SPECIFIED_VERSION" ]]; then
|
||
|
|
VERSION="$SPECIFIED_VERSION"
|
||
|
|
echo "Installing specified version: $VERSION"
|
||
|
|
else
|
||
|
|
VERSION=$(get_latest_version)
|
||
|
|
echo "Installing latest version: $VERSION"
|
||
|
|
fi
|
||
|
|
echo "Install mode: $INSTALL_MODE"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
SOURCE_REF="$VERSION"
|
||
|
|
REPO_URL="${GITEA_BASE_URL}/${GITEA_REPO}.git"
|
||
|
|
TEMP_DIR=$(mktemp -d)
|
||
|
|
SOURCE_DIR="$TEMP_DIR/source"
|
||
|
|
BACKUP_DIR=""
|
||
|
|
SERVICE_WAS_RUNNING=0
|
||
|
|
SERVICE_STOPPED=0
|
||
|
|
SYSTEMD_AVAILABLE=0
|
||
|
|
|
||
|
|
cleanup_temp() {
|
||
|
|
rm -rf "$TEMP_DIR"
|
||
|
|
}
|
||
|
|
trap cleanup_temp EXIT
|
||
|
|
|
||
|
|
if command -v systemctl >/dev/null 2>&1; then
|
||
|
|
SYSTEMD_AVAILABLE=1
|
||
|
|
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
|
||
|
|
SERVICE_WAS_RUNNING=1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
restore_previous_installation() {
|
||
|
|
if [[ -n "$BACKUP_DIR" && -d "$BACKUP_DIR" ]]; then
|
||
|
|
echo "Restoring previous installation from $BACKUP_DIR..."
|
||
|
|
rm -rf "$INSTALL_DIR" || true
|
||
|
|
mv "$BACKUP_DIR" "$INSTALL_DIR" || true
|
||
|
|
if [[ -f "$INSTALL_DIR/dcrouter" ]]; then
|
||
|
|
mkdir -p "$BIN_DIR" || true
|
||
|
|
ln -sf "$INSTALL_DIR/dcrouter" "$BIN_DIR/dcrouter" || true
|
||
|
|
elif [[ -f "$INSTALL_DIR/cli.js" ]]; then
|
||
|
|
mkdir -p "$BIN_DIR" || true
|
||
|
|
ln -sf "$INSTALL_DIR/cli.js" "$BIN_DIR/dcrouter" || true
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
restart_previous_service_on_error() {
|
||
|
|
if [[ $SERVICE_STOPPED -eq 1 && $SYSTEMD_AVAILABLE -eq 1 ]]; then
|
||
|
|
echo "Installation failed after stopping DcRouter; restarting previous service..."
|
||
|
|
systemctl start "$SERVICE_NAME" || true
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
handle_install_error() {
|
||
|
|
trap - ERR
|
||
|
|
restore_previous_installation
|
||
|
|
restart_previous_service_on_error
|
||
|
|
}
|
||
|
|
trap handle_install_error ERR
|
||
|
|
|
||
|
|
stop_service_if_running() {
|
||
|
|
if [[ $SERVICE_WAS_RUNNING -eq 1 && $SYSTEMD_AVAILABLE -eq 1 ]] && systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
|
||
|
|
echo "Stopping DcRouter service..."
|
||
|
|
systemctl stop "$SERVICE_NAME"
|
||
|
|
SERVICE_STOPPED=1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
move_previous_installation() {
|
||
|
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
||
|
|
if [[ -d "$INSTALL_DIR" ]]; then
|
||
|
|
BACKUP_DIR="${INSTALL_DIR}.previous.$$"
|
||
|
|
echo "Moving previous installation to $BACKUP_DIR"
|
||
|
|
mv "$INSTALL_DIR" "$BACKUP_DIR"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
install_source_build() {
|
||
|
|
require_command git
|
||
|
|
require_command node
|
||
|
|
ensure_pnpm
|
||
|
|
|
||
|
|
echo "Cloning DcRouter source from $REPO_URL ($SOURCE_REF)..."
|
||
|
|
git clone --depth 1 --branch "$SOURCE_REF" "$REPO_URL" "$SOURCE_DIR"
|
||
|
|
|
||
|
|
echo "Installing dependencies..."
|
||
|
|
pnpm --dir "$SOURCE_DIR" install --frozen-lockfile
|
||
|
|
|
||
|
|
echo "Building DcRouter..."
|
||
|
|
pnpm --dir "$SOURCE_DIR" run build
|
||
|
|
|
||
|
|
echo "Validating built CLI..."
|
||
|
|
node "$SOURCE_DIR/cli.js" --version >/dev/null
|
||
|
|
|
||
|
|
stop_service_if_running
|
||
|
|
move_previous_installation
|
||
|
|
|
||
|
|
echo "Installing source build to $INSTALL_DIR"
|
||
|
|
mv "$SOURCE_DIR" "$INSTALL_DIR"
|
||
|
|
make_executable_if_present "$INSTALL_DIR/cli.js"
|
||
|
|
make_executable_if_present "$INSTALL_DIR/cli.ts.js"
|
||
|
|
make_executable_if_present "$INSTALL_DIR/cli.child.js"
|
||
|
|
|
||
|
|
mkdir -p "$BIN_DIR"
|
||
|
|
ln -sf "$INSTALL_DIR/cli.js" "$BIN_DIR/dcrouter"
|
||
|
|
}
|
||
|
|
|
||
|
|
install_release_binary() {
|
||
|
|
local binary_name
|
||
|
|
local download_url
|
||
|
|
local temp_file
|
||
|
|
|
||
|
|
binary_name=$(detect_binary_name)
|
||
|
|
download_url="${GITEA_BASE_URL}/${GITEA_REPO}/releases/download/${VERSION}/${binary_name}"
|
||
|
|
temp_file="$TEMP_DIR/$binary_name"
|
||
|
|
|
||
|
|
echo "Downloading DcRouter binary: $download_url"
|
||
|
|
curl -fSL "$download_url" -o "$temp_file"
|
||
|
|
chmod 0755 "$temp_file"
|
||
|
|
|
||
|
|
echo "Validating downloaded binary..."
|
||
|
|
"$temp_file" --version >/dev/null
|
||
|
|
|
||
|
|
stop_service_if_running
|
||
|
|
move_previous_installation
|
||
|
|
|
||
|
|
echo "Installing binary to $INSTALL_DIR"
|
||
|
|
mkdir -p "$INSTALL_DIR"
|
||
|
|
install -m 0755 "$temp_file" "$INSTALL_DIR/dcrouter"
|
||
|
|
|
||
|
|
mkdir -p "$BIN_DIR"
|
||
|
|
ln -sf "$INSTALL_DIR/dcrouter" "$BIN_DIR/dcrouter"
|
||
|
|
}
|
||
|
|
|
||
|
|
if [[ "$INSTALL_MODE" == "source" ]]; then
|
||
|
|
install_source_build
|
||
|
|
else
|
||
|
|
install_release_binary
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "Symlink created: $BIN_DIR/dcrouter"
|
||
|
|
|
||
|
|
if ! "$BIN_DIR/dcrouter" --version >/dev/null; then
|
||
|
|
echo "Error: Installed DcRouter CLI failed validation"
|
||
|
|
restore_previous_installation
|
||
|
|
restart_previous_service_on_error
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -n "$BACKUP_DIR" && -d "$BACKUP_DIR" ]]; then
|
||
|
|
rm -rf "$BACKUP_DIR"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ $SERVICE_WAS_RUNNING -eq 1 && $SYSTEMD_AVAILABLE -eq 1 ]]; then
|
||
|
|
echo "Restarting DcRouter service..."
|
||
|
|
systemctl restart "$SERVICE_NAME"
|
||
|
|
SERVICE_STOPPED=0
|
||
|
|
echo "Service restarted successfully."
|
||
|
|
echo ""
|
||
|
|
fi
|
||
|
|
|
||
|
|
trap - ERR
|
||
|
|
|
||
|
|
echo "================================================"
|
||
|
|
echo " DcRouter Installation Complete!"
|
||
|
|
echo "================================================"
|
||
|
|
echo ""
|
||
|
|
echo "Installation details:"
|
||
|
|
echo " Install directory: $INSTALL_DIR"
|
||
|
|
echo " Symlink location: $BIN_DIR/dcrouter"
|
||
|
|
echo " Version: $VERSION"
|
||
|
|
echo " Mode: $INSTALL_MODE"
|
||
|
|
echo ""
|
||
|
|
echo "Get started:"
|
||
|
|
echo ""
|
||
|
|
echo " dcrouter --version"
|
||
|
|
echo " dcrouter --help"
|
||
|
|
echo ""
|