#!/usr/bin/env bash # # VPSora server installer — Sprint 1.5 TECHNICAL SPIKE. # # Verifies a VPS is compatible and prepares it for VPSora management: # OS / architecture / CPU / RAM / disk / internet / port checks, # Docker install, and (optionally) a unique VPSora SSH public key for root # (root key per ADR 008 — Dokploy remote setup requires root access). # # NOT production onboarding: no installation tokens, no API registration, # no user linkage. Those arrive in Sprint 3 after ownership/authorization # (Sprint 2) exist. Do not point real customers at this script. # # Usage: # sudo bash vpsora-install.sh # check + install # bash vpsora-install.sh --check-only # checks only # sudo bash vpsora-install.sh --token vpst_... --api https://api.vpsora.dev # → registers the server with VPSora using a one-time installation token # and installs the unique VPSora SSH key returned by the API. # set -euo pipefail # ---- requirements (calibrate after real-VPS testing) ---- MIN_CPU=1 MIN_RAM_MB=960 # nominal 1 GB VPS reports ~960 MB usable MIN_DISK_GB=10 RECOMMENDED_RAM_MB=1900 CHECK_ONLY=0 TOKEN="" API_URL="https://api.vpsora.dev" while [ $# -gt 0 ]; do case "$1" in --check-only) CHECK_ONLY=1 ;; --token) TOKEN="${2:-}"; shift ;; --token=*) TOKEN="${1#*=}" ;; --api) API_URL="${2:-}"; shift ;; --api=*) API_URL="${1#*=}" ;; *) echo "unknown option: $1" >&2; exit 2 ;; esac shift done FAILURES=0 WARNINGS=0 ok() { printf ' [ ok ] %s\n' "$1"; } warn() { printf ' [warn] %s\n' "$1"; WARNINGS=$((WARNINGS + 1)); } fail() { printf ' [FAIL] %s\n' "$1"; FAILURES=$((FAILURES + 1)); } echo "VPSora server check (spike build)" echo "---------------------------------" # ---- root ---- if [ "$(id -u)" -ne 0 ]; then if [ "$CHECK_ONLY" -eq 1 ]; then warn "not running as root — some checks are limited" else echo "Run as root (sudo) to install. Use --check-only to only inspect." >&2 exit 1 fi fi # ---- OS ---- if [ -r /etc/os-release ]; then # shellcheck disable=SC1091 . /etc/os-release case "${ID:-} ${VERSION_ID:-}" in 'ubuntu 22.04' | 'ubuntu 24.04' | 'debian 12') ok "operating system: ${PRETTY_NAME}" ;; *) fail "unsupported OS: ${PRETTY_NAME:-unknown} (need Ubuntu 22.04/24.04 or Debian 12)" ;; esac else fail "cannot read /etc/os-release — is this a Linux server?" fi # ---- architecture ---- ARCH="$(uname -m)" if [ "$ARCH" = "x86_64" ]; then ok "architecture: x86_64" else fail "unsupported architecture: $ARCH (need x86_64/AMD64)" fi # ---- CPU ---- CPUS="$(nproc 2>/dev/null || echo 0)" if [ "$CPUS" -ge "$MIN_CPU" ]; then ok "cpu cores: $CPUS" else fail "cpu cores: $CPUS (need >= $MIN_CPU)" fi # ---- RAM ---- RAM_MB="$(awk '/MemTotal/ {printf "%d", $2 / 1024}' /proc/meminfo 2>/dev/null || echo 0)" if [ "$RAM_MB" -ge "$MIN_RAM_MB" ]; then if [ "$RAM_MB" -lt "$RECOMMENDED_RAM_MB" ]; then warn "memory: ${RAM_MB} MB (works, 2 GB recommended for builds)" else ok "memory: ${RAM_MB} MB" fi else fail "memory: ${RAM_MB} MB (need >= ${MIN_RAM_MB} MB)" fi # ---- disk ---- DISK_GB="$(df -BG --output=avail / 2>/dev/null | tail -1 | tr -dc '0-9' || echo 0)" if [ "${DISK_GB:-0}" -ge "$MIN_DISK_GB" ]; then ok "free disk on /: ${DISK_GB} GB" else fail "free disk on /: ${DISK_GB:-0} GB (need >= ${MIN_DISK_GB} GB)" fi # ---- internet ---- if curl -fsS --max-time 10 https://download.docker.com >/dev/null 2>&1; then ok "internet access" else fail "no internet access (cannot reach download.docker.com)" fi # ---- ports 80/443 (Dokploy's Traefik will need both) ---- for PORT in 80 443; do LISTENER="$(ss -ltnpH "sport = :$PORT" 2>/dev/null | head -1 || true)" if [ -n "$LISTENER" ]; then PROC="$(printf '%s' "$LISTENER" | grep -oP '(?<=")[^"]+' | head -1 || echo 'unknown process')" fail "port $PORT is in use by ${PROC} — VPSora needs 80/443 for HTTPS routing" else ok "port $PORT is free" fi done # ---- docker ---- if command -v docker >/dev/null 2>&1; then if docker info >/dev/null 2>&1; then ok "docker installed and running: $(docker --version | cut -d, -f1)" else if [ "$CHECK_ONLY" -eq 1 ]; then warn "docker installed but daemon not reachable (root needed?)" else fail "docker installed but the daemon is not running (systemctl start docker)" fi fi elif [ "$CHECK_ONLY" -eq 1 ]; then warn "docker not installed (would be installed by the full run)" elif [ "$FAILURES" -gt 0 ]; then fail "docker not installed — skipping install because checks above failed" else echo " [....] installing docker via get.docker.com ..." if curl -fsSL https://get.docker.com | sh >/dev/null 2>&1; then systemctl enable --now docker >/dev/null 2>&1 || true ok "docker installed: $(docker --version | cut -d, -f1)" else fail "docker installation failed" fi fi # ---- VPSora SSH key (root per ADR 008; unique per server, revocable) ---- if [ -n "${VPSORA_PUBLIC_KEY:-}" ]; then if [ "$CHECK_ONLY" -eq 1 ]; then warn "VPSORA_PUBLIC_KEY provided but --check-only set — not installing" elif [ "$FAILURES" -gt 0 ]; then fail "skipping SSH key install because checks failed" else install -d -m 700 /root/.ssh touch /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys if grep -qF "$VPSORA_PUBLIC_KEY" /root/.ssh/authorized_keys; then ok "VPSora SSH key already installed" else printf '%s\n' "$VPSORA_PUBLIC_KEY" >>/root/.ssh/authorized_keys ok "VPSora SSH key installed for root" fi fi else warn "no VPSORA_PUBLIC_KEY provided — skipping key install (fine for the spike)" fi # ---- register with VPSora (one-time token flow) ---- if [ -n "$TOKEN" ] && [ "$CHECK_ONLY" -eq 0 ] && [ "$FAILURES" -eq 0 ]; then echo " [....] registering server with VPSora ..." DOCKER_VERSION="$(docker --version 2>/dev/null | cut -d' ' -f3 | tr -d , || echo '')" PAYLOAD=$(printf '{"token":"%s","hostname":"%s","osName":"%s","osVersion":"%s","kernel":"%s","arch":"%s","cpuCores":%s,"ramMb":%s,"diskGb":%s,"dockerVersion":"%s"}' \ "$TOKEN" "$(hostname)" "${ID:-}" "${VERSION_ID:-}" "$(uname -r)" "$ARCH" \ "${CPUS:-0}" "${RAM_MB:-0}" "${DISK_GB:-0}" "$DOCKER_VERSION") RESPONSE="$(curl -sS -X POST -H 'content-type: application/json' \ -d "$PAYLOAD" -w '\n%{http_code}' "$API_URL/api/v1/servers/register" || true)" HTTP_CODE="${RESPONSE##*$'\n'}" BODY="${RESPONSE%$'\n'*}" if [ "$HTTP_CODE" = "201" ]; then SERVER_KEY="$(printf '%s' "$BODY" | sed -n 's/.*"publicKey":"\([^"]*\)".*/\1/p')" if [ -n "$SERVER_KEY" ]; then install -d -m 700 /root/.ssh touch /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys grep -qF "$SERVER_KEY" /root/.ssh/authorized_keys || printf '%s\n' "$SERVER_KEY" >>/root/.ssh/authorized_keys ok "server registered and VPSora key installed" else fail "registration succeeded but no key in response" fi else fail "registration failed (HTTP ${HTTP_CODE:-?}): $BODY" fi fi # ---- summary ---- echo "---------------------------------" if [ "$FAILURES" -gt 0 ]; then echo "RESULT: NOT READY — $FAILURES check(s) failed, $WARNINGS warning(s)." exit 1 fi if [ -n "$TOKEN" ] && [ "$CHECK_ONLY" -eq 0 ]; then echo "RESULT: CONNECTED — server registered with VPSora, $WARNINGS warning(s)." else echo "RESULT: READY — all checks passed, $WARNINGS warning(s)." fi exit 0