#!/usr/bin/env bash set -euo pipefail # Interactive installer for Docker + (optional) Certbot SSL + Calagopus Wings (calagopus/wings) # Intended usage: # curl -fsSL https://example.com/install-wings.sh | bash # # Notes: # - Prompts read from /dev/tty so it still works when piped from curl. # - Default answers are conservative (Docker install: NO, SSL setup: NO, reboot: NO). ######################################## # helpers ######################################## TTY="/dev/tty" say() { printf "\n\033[1m%s\033[0m\n" "$*"; } warn() { printf "\n\033[33m[warn]\033[0m %s\n" "$*"; } err() { printf "\n\033[31m[error]\033[0m %s\n" "$*"; } have() { command -v "$1" >/dev/null 2>&1; } need_tty() { if [[ ! -r "$TTY" || ! -w "$TTY" ]]; then err "No TTY available for interactive prompts. Run in an interactive shell." exit 1 fi } prompt() { local msg="$1" local out printf "%s" "$msg" >"$TTY" IFS= read -r out <"$TTY" printf "%s" "$out" } confirm_default_no() { local msg="$1" local ans ans="$(prompt "$msg [y/N]: ")" [[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] } require_root_or_sudo() { if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then if have sudo; then SUDO="sudo" else err "This script needs root privileges (or sudo). Re-run as root or install sudo." exit 1 fi else SUDO="" fi } apt_install() { local pkgs=("$@") $SUDO apt-get update -y $SUDO apt-get install -y "${pkgs[@]}" } update_wings_ssl_config() { local domain="$1" local cfg="/etc/calagopus/config.yml" local cert="/etc/letsencrypt/live/${domain}/fullchain.pem" local key="/etc/letsencrypt/live/${domain}/privkey.pem" if [[ ! -f "$cfg" ]]; then warn "Calagopus config not found at $cfg; skipping SSL config update." return 0 fi if [[ ! -f "$cert" || ! -f "$key" ]]; then warn "Certbot files not found ($cert / $key); skipping SSL config update." return 0 fi say "Updating Calagopus SSL settings in $cfg (backup: ${cfg}.bak)..." $SUDO cp -a "$cfg" "${cfg}.bak" # Update the 'ssl:' block values (assumes the block exists with standard indentation) $SUDO awk -v cert="$cert" -v key="$key" ' BEGIN { inssl=0 } { if ($0 ~ /^ ssl:[[:space:]]*$/) { inssl=1; print; next } if (inssl==1) { # leave ssl block when we hit the next top-level (two-space) section if ($0 ~ /^ [^[:space:]]/ && $0 !~ /^ ssl:/) { inssl=0 } else if ($0 ~ /^ enabled:/) { print " enabled: true"; next } else if ($0 ~ /^ cert:/) { print " cert: " cert; next } else if ($0 ~ /^ key:/) { print " key: " key; next } } print } ' "$cfg" | $SUDO tee "$cfg" >/dev/null say "Calagopus SSL block updated." } ######################################## # checks ######################################## need_tty require_root_or_sudo if ! have apt-get; then err "This script currently supports Debian/Ubuntu (apt)." exit 1 fi say "Calagopus Wings bootstrap (Docker + optional SSL + Wings binary)" ######################################## # Docker ######################################## if have docker; then say "Docker detected: $(docker --version || true)" else warn "Docker is NOT installed." if confirm_default_no "Install Docker now?"; then say "Installing Docker via get.docker.com (CHANNEL=stable)..." apt_install ca-certificates curl curl -fsSL https://get.docker.com/ | CHANNEL=stable $SUDO bash say "Docker installed: $(docker --version || true)" else err "Docker is required for Wings. Exiting because you chose not to install Docker." exit 1 fi fi ######################################## # Optional SSL (Certbot standalone) ######################################## DO_SSL=false DOMAIN="" if confirm_default_no "Set up SSL with certbot (standalone) now?"; then DO_SSL=true DOMAIN="$(prompt "Enter the domain name to issue a cert for (e.g. node1.example.com): ")" if [[ -z "$DOMAIN" ]]; then err "No domain entered. Skipping SSL." DO_SSL=false fi fi if $DO_SSL; then say "Installing certbot..." apt_install certbot say "Requesting certificate for: $DOMAIN" warn "Certbot standalone requires ports 80/443 to be reachable and not in use." $SUDO certbot certonly --standalone -d "$DOMAIN" # Update Calagopus config.yml ssl section update_wings_ssl_config "$DOMAIN" fi ######################################## # Download Wings binary (calagopus/wings) ######################################## say "Downloading Wings binary..." ARCH="$(uname -m)" case "$ARCH" in x86_64|amd64) WINGS_ARCH="x86_64" ;; aarch64|arm64) WINGS_ARCH="aarch64" ;; *) err "Unsupported architecture: $ARCH (expected x86_64/amd64 or aarch64/arm64)." exit 1 ;; esac WINGS_URL="https://github.com/calagopus/wings/releases/latest/download/wings-rs-${WINGS_ARCH}-linux" WINGS_BIN="/usr/local/bin/wings" say "Detected arch: $ARCH -> using asset: wings-rs-${WINGS_ARCH}-linux" say "Downloading: $WINGS_URL" $SUDO curl -fL "$WINGS_URL" -o "$WINGS_BIN" $SUDO chmod +x "$WINGS_BIN" say "Wings installed to $WINGS_BIN" "$WINGS_BIN" version || true ######################################## # Configure Wings (join-data) ######################################## say "Wings configuration" JOIN_DATA="" if confirm_default_no "Run 'wings configure --join-data ...' now?"; then JOIN_DATA="$(prompt "Paste the join-data string from your Calagopus panel (it can be long): ")" if [[ -z "$JOIN_DATA" ]]; then err "No join-data provided; skipping wings configure." else $SUDO mkdir -p /etc/calagopus $SUDO "$WINGS_BIN" configure --join-data "$JOIN_DATA" say "Wings configured." fi else warn "Skipping 'wings configure'. You'll need to configure manually later." fi if [[ -t 0 ]] && [[ -t 1 ]] && confirm_default_no "Open /etc/calagopus/config.yml in nano now?"; then $SUDO nano /etc/calagopus/config.yml fi ######################################## # Install & start service ######################################## if confirm_default_no "Install Wings as a systemd service now? (wings service-install)"; then $SUDO "$WINGS_BIN" service-install say "Service status:" $SUDO systemctl --no-pager status wings || true else warn "Skipping service installation." fi ######################################## # Optional reboot ######################################## if confirm_default_no "Reboot now?"; then say "Rebooting..." $SUDO reboot else say "Done. No reboot performed." fi