#!/usr/bin/env bash set -euo pipefail REPO_URL="${REPO_URL:-}" BRANCH="${BRANCH:-main}" PORT="${PORT:-6969}" PORT_SET=0 OPEN_FIREWALL="${OPEN_FIREWALL:-yes}" INSTALL_DIR="/opt/dragoncore-bridge" CONFIG_DIR="/etc/dragoncore-bridge" BIN="/usr/local/bin/dragoncore-bridge" SERVICE="/etc/systemd/system/dragoncore-bridge.service" while [ $# -gt 0 ]; do case "$1" in --repo) REPO_URL="$2"; shift 2 ;; --branch) BRANCH="$2"; shift 2 ;; --port) PORT="$2"; PORT_SET=1; shift 2 ;; --open-firewall) OPEN_FIREWALL="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done if [ "$(id -u)" != "0" ]; then echo "Run as root." exit 1 fi need_cmd() { command -v "$1" >/dev/null 2>&1; } rand_hex() { openssl rand -hex "$1"; } extract_listen_port() { cfg="$1" [ -f "$cfg" ] || return 1 listen="$(grep -o '"listen"[[:space:]]*:[[:space:]]*"[^"]*"' "$cfg" | head -1 | cut -d '"' -f4 || true)" port="${listen##*:}" case "$port" in ''|*[!0-9]*) return 1 ;; *) printf '%s\n' "$port" ;; esac } port_in_use() { p="$1" if need_cmd ss; then ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${p}$" return $? fi if need_cmd netstat; then netstat -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${p}$" return $? fi if need_cmd lsof; then lsof -iTCP:"$p" -sTCP:LISTEN -Pn >/dev/null 2>&1 return $? fi return 1 } choose_free_port() { base="$1" case "$base" in ''|*[!0-9]*) base=6969 ;; esac i=0 while [ "$i" -le 100 ]; do cand=$((base + i)) if ! port_in_use "$cand"; then printf '%s\n' "$cand" return 0 fi i=$((i + 1)) done echo "No free TCP port found from $base to $((base + 100))." >&2 exit 1 } # The bridge source is intentionally Go 1.13-compatible so old VPS images # such as Ubuntu 20.04 can compile it with their distro package. # If the distro Go is older than 1.13, install a known-good official Go. GO_MIN_MAJOR=1 GO_MIN_MINOR=13 GO_VERSION="${GO_VERSION:-1.21.13}" GO_BIN="" go_version_ok() { if ! need_cmd go; then return 1 fi ver="$(go version 2>/dev/null | awk '{print $3}' | sed 's/^go//; s/[^0-9.].*$//')" major="$(printf '%s' "$ver" | cut -d. -f1)" minor="$(printf '%s' "$ver" | cut -d. -f2)" case "$major:$minor" in *[!0-9:]*|:*) return 1 ;; esac if [ "$major" -gt "$GO_MIN_MAJOR" ]; then return 0 fi if [ "$major" -eq "$GO_MIN_MAJOR" ] && [ "$minor" -ge "$GO_MIN_MINOR" ]; then return 0 fi return 1 } install_official_go() { arch="$(uname -m)" case "$arch" in x86_64|amd64) goarch="amd64" ;; aarch64|arm64) goarch="arm64" ;; armv6l|armv7l) goarch="armv6l" ;; i386|i686) goarch="386" ;; *) echo "Unsupported CPU architecture for automatic Go install: $arch"; exit 1 ;; esac url="https://go.dev/dl/go${GO_VERSION}.linux-${goarch}.tar.gz" tmp="/tmp/go${GO_VERSION}.linux-${goarch}.tar.gz" echo "Installing Go ${GO_VERSION} from ${url} ..." curl -fsSL "$url" -o "$tmp" rm -rf /usr/local/go tar -C /usr/local -xzf "$tmp" ln -sf /usr/local/go/bin/go /usr/local/bin/go ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt GO_BIN="/usr/local/go/bin/go" } export DEBIAN_FRONTEND=noninteractive if need_cmd apt-get; then apt-get update -y apt-get install -y ca-certificates curl wget git openssl build-essential sqlite3 lsof iproute2 if ! need_cmd go; then apt-get install -y golang-go || true fi elif need_cmd yum; then yum install -y ca-certificates curl wget git openssl gcc make golang sqlite lsof iproute || yum install -y ca-certificates curl wget git openssl gcc make sqlite lsof iproute elif need_cmd dnf; then dnf install -y ca-certificates curl wget git openssl gcc make golang sqlite lsof iproute || dnf install -y ca-certificates curl wget git openssl gcc make sqlite lsof iproute fi if go_version_ok; then GO_BIN="$(command -v go)" else install_official_go fi if ! "$GO_BIN" version >/dev/null 2>&1; then echo "Go installation failed. Cannot build dragoncore-bridge." exit 1 fi mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" chmod 700 "$CONFIG_DIR" if [ -n "$REPO_URL" ]; then rm -rf "$INSTALL_DIR/src" git clone --depth 1 --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR/src" else # Local install: useful if this script is executed from inside the repository. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" rm -rf "$INSTALL_DIR/src" mkdir -p "$INSTALL_DIR/src" cp -a "$SCRIPT_DIR"/. "$INSTALL_DIR/src/" fi cd "$INSTALL_DIR/src" if need_cmd systemctl; then systemctl stop dragoncore-bridge 2>/dev/null || true; fi "$GO_BIN" build -trimpath -ldflags "-s -w" -o "$BIN" . chmod 755 "$BIN" USER_NAME="admin" PASSWORD="$(rand_hex 10)" TOKEN="$(rand_hex 24)" PANEL_URL="" PANEL_SERVER_ID="0" PANEL_SERVER_IP="" PANEL_PUSH_INTERVAL="60" if [ -f "$CONFIG_DIR/config.json" ]; then echo "Existing config found: $CONFIG_DIR/config.json" USER_NAME="$(grep -o '"username"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo admin)" PASSWORD="$(grep -o '"password"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || rand_hex 10)" TOKEN="$(grep -o '"token"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || rand_hex 24)" PANEL_URL="$(grep -o '"panel_url"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo '')" PANEL_SERVER_ID="$(grep -o '"panel_server_id"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_DIR/config.json" | head -1 | grep -o '[0-9]*$' || echo 0)" PANEL_SERVER_IP="$(grep -o '"panel_server_ip"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_DIR/config.json" | head -1 | cut -d '"' -f4 || echo '')" PANEL_PUSH_INTERVAL="$(grep -o '"panel_push_interval_seconds"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_DIR/config.json" | head -1 | grep -o '[0-9]*$' || echo 60)" EXISTING_PORT="$(extract_listen_port "$CONFIG_DIR/config.json" || true)" if [ "$PORT_SET" = "0" ] && [ -n "$EXISTING_PORT" ]; then PORT="$EXISTING_PORT" fi fi REQUESTED_PORT="$PORT" PORT="$(choose_free_port "$PORT")" if [ "$PORT" != "$REQUESTED_PORT" ]; then echo "Port $REQUESTED_PORT is already in use. Using free port $PORT instead." fi cat > "$CONFIG_DIR/config.json" < "$SERVICE" <&2 journalctl -u dragoncore-bridge -n 40 --no-pager >&2 || true exit 1 fi if [ "$OPEN_FIREWALL" = "yes" ] || [ "$OPEN_FIREWALL" = "true" ] || [ "$OPEN_FIREWALL" = "1" ]; then if need_cmd ufw && ufw status 2>/dev/null | grep -qi "Status: active"; then ufw allow "$PORT"/tcp || true fi if need_cmd firewall-cmd && firewall-cmd --state >/dev/null 2>&1; then firewall-cmd --permanent --add-port="$PORT/tcp" || true firewall-cmd --reload || true fi if need_cmd iptables; then iptables -C INPUT -p tcp --dport "$PORT" -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport "$PORT" -j ACCEPT || true if need_cmd netfilter-persistent; then netfilter-persistent save || true; fi if need_cmd iptables-save && [ -d /etc/iptables ]; then iptables-save > /etc/iptables/rules.v4 || true; fi fi fi PUBLIC_IP="$(curl -fsS --max-time 4 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')" echo printf '%s\n' '============================================================' printf '%s\n' 'DragonCore Bridge installed.' printf '%s\n' 'Use these values in the DragonCore Panel:' printf 'API Type : %s\n' 'Bridge Generic' printf 'IP : %s\n' "$PUBLIC_IP" printf 'API Port : %s\n' "$PORT" printf 'User : %s\n' "$USER_NAME" printf 'Password : %s\n' "$PASSWORD" printf 'Legacy token/Senha header: %s\n' "$TOKEN" printf '%s\n' 'Service commands:' printf '%s\n' ' systemctl status dragoncore-bridge' printf '%s\n' ' journalctl -u dragoncore-bridge -f' printf '%s\n' '============================================================'