#!/usr/bin/env bash # ============================================================================ # Charles — server installer # For a fresh Ubuntu 22.04 / 24.04 server (e.g. a DigitalOcean droplet), as root: # curl -fsSL https://charles-bot.xyz/install.sh -o install.sh && bash install.sh # Safe to re-run: finished steps are skipped, the code is updated with git pull. # ============================================================================ set -euo pipefail REPO="https://github.com/nswansonprojects/charles.git" BOT_DIR="$HOME/charles" CONDA_DIR="$HOME/miniconda3" ENV_NAME="charles" PY="$CONDA_DIR/envs/$ENV_NAME/bin/python" green='\033[0;32m'; yellow='\033[1;33m'; blue='\033[0;34m'; red='\033[0;31m'; nc='\033[0m' step() { echo -e "\n${blue}▶ $1${nc}"; } ok() { echo -e "${green}✓${nc} $1"; } note() { echo -e "${yellow}!${nc} $1"; } die() { echo -e "${red}✗ $1${nc}"; exit 1; } [ "$(id -u)" -eq 0 ] || die "Run this as root (on a fresh droplet you log in as root)." command -v apt-get >/dev/null || die "This installer supports Ubuntu/Debian only." step "1/7 System packages" export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq git curl ufw tmux nano >/dev/null ok "git, curl, ufw, tmux, nano" step "2/7 Swap space (keeps small servers from running out of memory)" if swapon --show | grep -q .; then ok "swap already on" else fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap -q /swapfile && swapon /swapfile grep -q '^/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab ok "2 GB swap added" fi step "3/7 Python 3.11 (Miniconda)" if [ ! -x "$CONDA_DIR/bin/conda" ]; then curl -fsSL https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -o /tmp/miniconda.sh bash /tmp/miniconda.sh -b -p "$CONDA_DIR" >/dev/null rm -f /tmp/miniconda.sh fi if [ ! -x "$PY" ]; then # conda-forge only: no Anaconda terms-of-service prompt "$CONDA_DIR/bin/conda" create -y -q -n "$ENV_NAME" -c conda-forge --override-channels python=3.11 >/dev/null fi ok "$("$PY" --version)" step "4/7 Charles code from GitHub" if [ -d "$BOT_DIR/.git" ]; then git -C "$BOT_DIR" pull --ff-only || note "git pull failed (local changes?) — keeping your current code" elif [ -e "$BOT_DIR" ] && [ -n "$(ls -A "$BOT_DIR" 2>/dev/null)" ]; then die "$BOT_DIR exists and isn't a git checkout. Move it aside and re-run." else git clone -q "$REPO" "$BOT_DIR" fi mkdir -p "$BOT_DIR/data" "$BOT_DIR/logs" "$BOT_DIR/reports" ok "code in $BOT_DIR" step "5/7 Python packages (5–15 minutes)" "$PY" -m pip install -q --upgrade pip # CPU-only PyTorch: same features, ~2 GB smaller than the default download "$PY" -m pip install -q torch --index-url https://download.pytorch.org/whl/cpu "$PY" -m pip install -q -r "$BOT_DIR/requirements.txt" ok "packages installed" step "6/7 Keys file, firewall, service" if [ ! -f "$BOT_DIR/.env" ]; then cp "$BOT_DIR/.env.example" "$BOT_DIR/.env" note "created $BOT_DIR/.env — add your API keys to it next" fi chmod 600 "$BOT_DIR/.env" ufw allow OpenSSH >/dev/null ufw allow 8501/tcp >/dev/null # dashboard ufw allow 8502/tcp >/dev/null # first-run setup page ufw --force enable >/dev/null ok "firewall: SSH, 8501 (dashboard), 8502 (setup page)" cat > /etc/systemd/system/charles.service < "/usr/local/bin/$1"; chmod +x "/usr/local/bin/$1"; } ip_cmd='$(curl -s --max-time 3 ifconfig.me || hostname -I | cut -d" " -f1)' mk charles-start "systemctl start charles && echo \"✅ Charles started — dashboard: http://$ip_cmd:8501 (first run: setup page on :8502)\"" mk charles-stop 'systemctl stop charles && echo "🛑 Charles stopped"' mk charles-restart 'systemctl restart charles && echo "🔄 Charles restarted"' mk charles-status "systemctl --no-pager status charles | head -5; echo; echo \"📊 Dashboard: http://$ip_cmd:8501\"" mk charles-logs "tail -n 50 -f $BOT_DIR/logs/bot.log" mk charles-update "set -e; git -C $BOT_DIR pull --ff-only; $PY -m pip install -q -r $BOT_DIR/requirements.txt; systemctl restart charles; echo \"⬆️ Charles updated and restarted\"" ok "charles-start · stop · restart · status · logs · update" IP=$(curl -s --max-time 3 ifconfig.me || hostname -I | cut -d' ' -f1) echo -e "\n${green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${nc}" echo -e "${green} Charles is installed.${nc}" echo -e "${green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${nc}" echo " 1. Add your keys: nano $BOT_DIR/.env" echo " 2. Start the bot: charles-start" echo " 3. First run only: open http://$IP:8502 and click Launch" echo " 4. Dashboard: http://$IP:8501" echo " Logs: charles-logs · Guide: https://charles-bot.xyz/setup.html"