#!/usr/bin/env bash # # outline-distro installer (Ubuntu) # # Builds from source and installs outline-distro as a systemd service # with Shadowsocks + optional AmneziaWG (HTTP/3 mux for DPI resistance). # # Usage: # curl -sSL /install.sh | bash # SS only # curl -sSL /install.sh | bash -s -- --awg # SS + AmneziaWG # curl -sSL /install.sh | bash -s -- --awg --domain vpn.example.com # # Requirements: Go 1.22+, root, git, Ubuntu 20.04+ # set -euo pipefail # --- Paths --- INSTALL_DIR="/opt/outline-distro" CONFIG_DIR="/etc/outline-distro" DATA_DIR="/var/lib/outline-distro" BINARY="/usr/local/bin/outline-distro" SERVICE_NAME="outline-distro" SERVICE_USER="outline-distro" UNIT_FILE="/etc/systemd/system/${SERVICE_NAME}.service" REPO_URL="https://sourcecraft.dev/bigbes/outline-distro.git" # --- Config defaults --- SERVER_NAME="Outline Server" HOSTNAME="" API_PORT="8081" METRICS_PORT="9091" SS_PORT="0" SS_CIPHER="chacha20-ietf-poly1305" AWG_ENABLED="false" AWG_PORT="443" AWG_SUBNET="10.14.0.0/24" AWG_DNS="1.1.1.1, 8.8.8.8" AWG_DOMAIN="" AWG_MTU="1420" AWG_TUN="awg0" # --- Parse arguments --- while [[ $# -gt 0 ]]; do case "$1" in --awg) AWG_ENABLED="true"; shift ;; --domain) AWG_DOMAIN="$2"; shift 2 ;; --hostname) HOSTNAME="$2"; shift 2 ;; --name) SERVER_NAME="$2"; shift 2 ;; --ss-port) SS_PORT="$2"; shift 2 ;; --awg-port) AWG_PORT="$2"; shift 2 ;; --awg-subnet) AWG_SUBNET="$2"; shift 2 ;; --awg-dns) AWG_DNS="$2"; shift 2 ;; --api-port) API_PORT="$2"; shift 2 ;; --metrics-port) METRICS_PORT="$2"; shift 2 ;; --help|-h) cat <<'USAGE' Usage: install.sh [OPTIONS] Options: --awg Enable AmneziaWG protocol (with HTTP/3 mux) --domain DOMAIN Domain for Let's Encrypt (enables HTTP/3 cover) --hostname HOST Server hostname/IP for access key URLs --name NAME Server display name --ss-port PORT Shadowsocks port (0 = random) --awg-port PORT AmneziaWG + HTTP/3 listen port (default: 443) --awg-subnet CIDR AWG peer subnet (default: 10.14.0.0/24) --awg-dns DNS DNS for AWG clients (default: 1.1.1.1, 8.8.8.8) --api-port PORT Management API port (default: 8081) --metrics-port PORT Prometheus metrics port (default: 9091) USAGE exit 0 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done # --- Helpers --- info() { echo -e "\033[1;34m==>\033[0m $*"; } ok() { echo -e "\033[1;32m==>\033[0m $*"; } warn() { echo -e "\033[1;33mWARN:\033[0m $*"; } fail() { echo -e "\033[1;31mERROR:\033[0m $*" >&2; exit 1; } # --- Preflight --- [[ $EUID -eq 0 ]] || fail "This script must be run as root." . /etc/os-release 2>/dev/null || true if [[ "${ID:-}" != "ubuntu" && "${ID_LIKE:-}" != *"ubuntu"* && "${ID_LIKE:-}" != *"debian"* ]]; then warn "This script is designed for Ubuntu. Proceeding anyway..." fi command -v go >/dev/null 2>&1 || fail "Go is not installed. Install Go 1.22+: https://go.dev/dl/" command -v git >/dev/null 2>&1 || fail "git is not installed: apt install git" GO_VER=$(go version | grep -oP 'go\K[0-9]+\.[0-9]+') info "Go $GO_VER detected" if [[ -z "$HOSTNAME" ]]; then HOSTNAME=$(curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null || hostname -f 2>/dev/null || echo "localhost") info "Auto-detected hostname: $HOSTNAME" fi # --- Create system user --- if ! id "$SERVICE_USER" &>/dev/null; then info "Creating system user $SERVICE_USER..." useradd --system --home-dir "$DATA_DIR" --shell /usr/sbin/nologin \ --comment "Outline Distro VPN" "$SERVICE_USER" ok "User $SERVICE_USER created" fi # --- Build from source --- info "Cloning/updating repository..." if [[ -d "$INSTALL_DIR/.git" ]]; then cd "$INSTALL_DIR" git pull --ff-only 2>/dev/null || git fetch --all else rm -rf "$INSTALL_DIR" git clone "$REPO_URL" "$INSTALL_DIR" cd "$INSTALL_DIR" fi VERSION=$(git describe --tags --always 2>/dev/null || echo "dev") info "Building $VERSION..." CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$VERSION" \ -o "$BINARY" ./cmd/outline-distro/ ok "Binary: $BINARY ($VERSION)" # --- Directories --- mkdir -p "$CONFIG_DIR" "$DATA_DIR/certs" chown "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR" "$DATA_DIR/certs" chmod 750 "$DATA_DIR" # Ensure TUN device node exists (needed inside PrivateDevices=no). if [[ ! -c /dev/net/tun ]]; then mkdir -p /dev/net mknod /dev/net/tun c 10 200 chmod 0666 /dev/net/tun fi # --- Generate config --- CONFIG_FILE="$CONFIG_DIR/config.yaml" if [[ -f "$CONFIG_FILE" ]]; then warn "Config exists: $CONFIG_FILE — skipping generation." else info "Generating config..." API_SECRET=$(head -c 16 /dev/urandom | xxd -p) cat > "$CONFIG_FILE" < "$UNIT_FILE" </dev/null 2>&1 && ufw status | grep -q "active"; then info "Configuring ufw firewall rules..." ufw allow "$API_PORT"/tcp comment "outline-distro API" >/dev/null 2>&1 || true if [[ "$AWG_ENABLED" == "true" ]]; then ufw allow "$AWG_PORT"/udp comment "outline-distro AWG+HTTP3" >/dev/null 2>&1 || true ufw allow 80/tcp comment "outline-distro ACME" >/dev/null 2>&1 || true fi ok "Firewall rules added" else warn "ufw is not active. Ensure these ports are open:" warn " TCP ${API_PORT} (management API)" [[ "$AWG_ENABLED" == "true" ]] && warn " UDP ${AWG_PORT} (AWG + HTTP/3) | TCP 80 (ACME)" fi # --- IP forwarding for AWG --- if [[ "$AWG_ENABLED" == "true" ]]; then info "Enabling IPv4 forwarding..." sysctl -q -w net.ipv4.ip_forward=1 # Persist via sysctl.d drop-in (idempotent). cat > /etc/sysctl.d/99-outline-distro.conf <<'SYSCTL' # Enable IPv4 forwarding for AmneziaWG VPN net.ipv4.ip_forward=1 SYSCTL ok "IPv4 forwarding enabled" fi # --- Start service --- info "Starting ${SERVICE_NAME}..." # Stop if already running (upgrade case). systemctl stop "$SERVICE_NAME" 2>/dev/null || true systemctl start "$SERVICE_NAME" # Wait for journal to confirm startup. for i in 1 2 3 4 5; do if systemctl is-active --quiet "$SERVICE_NAME"; then break fi sleep 1 done if systemctl is-active --quiet "$SERVICE_NAME"; then ok "Service is running" else echo "" echo "Service failed to start. Recent logs:" journalctl -u "$SERVICE_NAME" -n 20 --no-pager fail "Check full logs: journalctl -u $SERVICE_NAME" fi # --- Summary --- API_SECRET_DISPLAY=$(grep -oP 'secret:\s*"\K[^"]+' "$CONFIG_FILE" 2>/dev/null || echo "") cat </awg-config EOF fi echo " Open the Shadowsocks port once keys are created (see 'key list')." echo ""