#!/usr/bin/env bash # ╔═══════════════════════════════════════════════════════════════╗ # ║ ATLAS OS — ONE-COMMAND INSTALLER ║ # ║ github.com/atlascorporation/atlas-os ║ # ╚═══════════════════════════════════════════════════════════════╝ set -euo pipefail # ── Colors ────────────────────────────────────────────────────── RED='\033[0;31m'; GRN='\033[0;32m'; YLW='\033[1;33m' BLU='\033[0;34m'; PRP='\033[0;35m'; CYN='\033[0;36m' WHT='\033[1;37m'; RST='\033[0m' ok() { echo -e " ${GRN}✓${RST} $1"; } warn() { echo -e " ${YLW}⚠${RST} $1"; } err() { echo -e " ${RED}✗${RST} $1"; exit 1; } step() { echo -e "\n${BLU}▸${RST} ${WHT}$1${RST}"; } # ── Banner ─────────────────────────────────────────────────────── echo -e "${PRP}" cat << 'BANNER' █████╗ ████████╗██╗ █████╗ ███████╗ ██████╗ ███████╗ ██╔══██╗╚══██╔══╝██║ ██╔══██╗██╔════╝ ██╔═══██╗██╔════╝ ███████║ ██║ ██║ ███████║███████╗ ██║ ██║███████╗ ██╔══██║ ██║ ██║ ██╔══██║╚════██║ ██║ ██║╚════██║ ██║ ██║ ██║ ███████╗██║ ██║███████║ ╚██████╔╝███████║ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚══════╝ BANNER echo -e "${RST}" echo -e " ${CYN}Your business on one box — space grade.${RST}" echo "" # ── Args ───────────────────────────────────────────────────────── DOMAIN="${1:-}" EMAIL="${2:-}" INSTALL_DIR="${3:-/opt/atlas-os}" for arg in "$@"; do case "$arg" in --domain=*) DOMAIN="${arg#*=}" ;; --email=*) EMAIL="${arg#*=}" ;; --dir=*) INSTALL_DIR="${arg#*=}" ;; --domain) shift; DOMAIN="$1" ;; --email) shift; EMAIL="$1" ;; esac done [ -z "$DOMAIN" ] && read -rp " Domain (e.g. example.com): " DOMAIN [ -z "$EMAIL" ] && read -rp " Admin email: " EMAIL [[ "$DOMAIN" =~ \. ]] || err "Invalid domain: $DOMAIN" [[ "$EMAIL" =~ @ ]] || err "Invalid email: $EMAIL" # ── Preflight ──────────────────────────────────────────────────── step "Preflight checks" [[ $EUID -eq 0 ]] || err "Run as root: sudo $0" command -v apt-get &>/dev/null || err "Requires a Debian/Ubuntu system" [[ $(uname -m) == "x86_64" ]] || warn "Tested on x86_64 — other arches may need adjustments" ok "Running as root on Debian/Ubuntu x86_64" ok "Domain: $DOMAIN · Email: $EMAIL" # ── System update ──────────────────────────────────────────────── step "Updating system packages" apt-get update -qq && apt-get upgrade -y -qq apt-get install -y -qq curl wget git jq python3 python3-pip unzip ca-certificates gnupg lsb-release ok "System packages updated" # ── Docker ─────────────────────────────────────────────────────── step "Installing Docker" if command -v docker &>/dev/null; then ok "Docker already installed ($(docker --version | cut -d' ' -f3 | tr -d ','))" else curl -fsSL https://get.docker.com | sh systemctl enable --now docker ok "Docker installed" fi docker compose version &>/dev/null || { apt-get install -y docker-compose-plugin; ok "Docker Compose plugin installed"; } # ── Caddy ──────────────────────────────────────────────────────── step "Installing Caddy" if command -v caddy &>/dev/null; then ok "Caddy already installed" else curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list apt-get update -qq && apt-get install -y -qq caddy ok "Caddy installed" fi # ── Tailscale ──────────────────────────────────────────────────── step "Installing Tailscale" if command -v tailscale &>/dev/null; then ok "Tailscale already installed" else curl -fsSL https://tailscale.com/install.sh | sh warn "Run: tailscale up --accept-routes (then re-run this installer)" fi # ── Directory structure ─────────────────────────────────────────── step "Creating /opt/atlas directory structure" mkdir -p "$INSTALL_DIR"/{compose,caddy,data,secrets,scripts,logs} mkdir -p /opt/atlas/{brain,cockpit,data,cache,operations} chmod 700 /opt/atlas/secrets ok "Directory structure created at $INSTALL_DIR" # ── Copy compose files ──────────────────────────────────────────── step "Deploying compose stacks" REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" cp "$REPO_DIR/deploy/compose/atlas-core.yml" "$INSTALL_DIR/compose/" cp "$REPO_DIR/deploy/compose/atlas-ai.yml" "$INSTALL_DIR/compose/" 2>/dev/null || true ok "Compose files staged at $INSTALL_DIR/compose/" # ── Caddy config ───────────────────────────────────────────────── step "Generating Caddy configuration" sed "s/{DOMAIN}/$DOMAIN/g; s/{EMAIL}/$EMAIL/g" \ "$REPO_DIR/deploy/caddy/Caddyfile.template" > /etc/caddy/Caddyfile caddy validate --config /etc/caddy/Caddyfile && ok "Caddyfile valid" # ── Generate secrets ───────────────────────────────────────────── step "Generating secrets" SECRET_FILE="/opt/atlas/secrets/atlas.env" if [ ! -f "$SECRET_FILE" ]; then cat > "$SECRET_FILE" << ENVEOF # Atlas OS — generated secrets # DO NOT commit this file POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32) AUTHENTIK_SECRET_KEY=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 48) NEXTCLOUD_ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24) VAULTWARDEN_ADMIN_TOKEN=$(openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32) DOMAIN=$DOMAIN ADMIN_EMAIL=$EMAIL ENVEOF chmod 600 "$SECRET_FILE" ok "Secrets generated at $SECRET_FILE (600)" else ok "Secrets file already exists — skipping" fi # ── Start core services ─────────────────────────────────────────── step "Starting core services" cd "$INSTALL_DIR/compose" docker compose -f atlas-core.yml --env-file "$SECRET_FILE" up -d ok "Core services started" # ── Start Caddy ─────────────────────────────────────────────────── step "Starting Caddy" systemctl enable --now caddy caddy reload --config /etc/caddy/Caddyfile ok "Caddy running and config reloaded" # ── Copy server scripts ─────────────────────────────────────────── step "Deploying Atlas Brain and server scripts" cp "$REPO_DIR/server/"*.py /opt/atlas/ 2>/dev/null || warn "No server scripts found in repo server/" cp "$REPO_DIR/server/systemd/"*.service /etc/systemd/system/ 2>/dev/null || true systemctl daemon-reload ok "Server scripts deployed to /opt/atlas/" # ── Done ───────────────────────────────────────────────────────── echo "" echo -e "${GRN}╔═══════════════════════════════════════════════════════╗${RST}" echo -e "${GRN}║ ATLAS OS INSTALLED SUCCESSFULLY ║${RST}" echo -e "${GRN}╚═══════════════════════════════════════════════════════╝${RST}" echo "" echo -e " ${WHT}Post-install checklist:${RST}" echo -e " ${CYN}1.${RST} Point DNS for $DOMAIN and *.${DOMAIN} → this server's IP" echo -e " ${CYN}2.${RST} Open https://auth.${DOMAIN} → complete Authentik setup wizard" echo -e " ${CYN}3.${RST} Create your Authentik admin user" echo -e " ${CYN}4.${RST} Open https://command.${DOMAIN}/os/ → Atlas OS desktop" echo -e " ${CYN}5.${RST} Configure atlas_brain.py mail accounts in /opt/atlas/" echo -e " ${CYN}6.${RST} Enable Banking: connect bank accounts at https://fidi.${DOMAIN}" echo "" echo -e " ${YLW}Secrets stored at:${RST} $SECRET_FILE (chmod 600)" echo -e " ${YLW}Logs:${RST} journalctl -u caddy -f | docker compose logs -f" echo ""