2026-04-11 12:59:02 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# bootstrap.sh — one-shot setup for the Agent0 box.
|
|
|
|
|
#
|
|
|
|
|
# Run once after cloning. Handles everything that can be automated:
|
|
|
|
|
# - Ollama install + model pulls
|
|
|
|
|
# - SSH key generation (tunnel key + Gitea key)
|
2026-04-11 13:35:35 +02:00
|
|
|
# - gutasktool clone (into agent-zero-data, mounted into the agent0 container)
|
|
|
|
|
# - Docker containers (gutasktool installed inside agent0 container)
|
2026-04-11 12:59:02 +02:00
|
|
|
#
|
|
|
|
|
# After this script: add two SSH keys (printed at the end), then open
|
|
|
|
|
# https://agent0.glitch.university and enter your API keys in Settings.
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
2026-04-11 13:08:32 +02:00
|
|
|
OS="$(uname -s)"
|
2026-04-11 13:22:26 +02:00
|
|
|
GUTASK_DIR="${REPO_DIR}/agent-zero-data/gutasktool"
|
2026-04-11 12:59:02 +02:00
|
|
|
GITEA_HOST="ramanujan.glitch.university"
|
|
|
|
|
GITEA_PORT=2222
|
|
|
|
|
VPS_HOST="glitch.university"
|
|
|
|
|
VPS_TUNNEL_USER="tunnel"
|
|
|
|
|
TUNNEL_KEY="${REPO_DIR}/tunnel/id_ed25519"
|
|
|
|
|
GITEA_KEY="${HOME}/.ssh/gitea_ed25519"
|
|
|
|
|
|
|
|
|
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
|
|
|
info() { echo -e "${GREEN}✓${NC} $*"; }
|
|
|
|
|
prompt() { echo -e "${CYAN}?${NC} $*"; }
|
|
|
|
|
section() { echo ""; echo -e "${YELLOW}── $* ──────────────────────────────────────────${NC}"; }
|
|
|
|
|
|
|
|
|
|
# ── Prerequisites ─────────────────────────────────────────────────────────────
|
|
|
|
|
section "Checking prerequisites"
|
|
|
|
|
|
|
|
|
|
command -v docker >/dev/null 2>&1 || { echo "docker not found. Install Docker first."; exit 1; }
|
|
|
|
|
command -v ssh-keygen >/dev/null 2>&1 || { echo "ssh-keygen not found."; exit 1; }
|
|
|
|
|
command -v ssh-keyscan >/dev/null 2>&1 || { echo "ssh-keyscan not found."; exit 1; }
|
|
|
|
|
docker compose version >/dev/null 2>&1 || { echo "docker compose not found."; exit 1; }
|
|
|
|
|
|
|
|
|
|
info "Prerequisites OK"
|
|
|
|
|
|
2026-04-11 13:22:26 +02:00
|
|
|
# ── Credentials (collected upfront before any private repo clones) ─────────────
|
|
|
|
|
section "Credentials"
|
|
|
|
|
|
2026-04-11 16:21:47 +02:00
|
|
|
# Load previously saved values from .env if it exists
|
|
|
|
|
if [[ -f "${REPO_DIR}/.env" ]]; then
|
|
|
|
|
source "${REPO_DIR}/.env"
|
|
|
|
|
fi
|
2026-04-11 13:22:26 +02:00
|
|
|
|
2026-04-11 16:21:47 +02:00
|
|
|
if [[ -n "${CONTENT_API_KEY:-}" ]]; then
|
|
|
|
|
info "CONTENT_API_KEY already set: ${CONTENT_API_KEY}"
|
|
|
|
|
else
|
|
|
|
|
echo ""
|
|
|
|
|
prompt "Enter CONTENT_API_KEY for glitch.university (find it in the VPS .env): "
|
|
|
|
|
read -r -s CONTENT_API_KEY
|
|
|
|
|
echo ""
|
|
|
|
|
fi
|
2026-04-11 13:22:26 +02:00
|
|
|
|
2026-04-11 16:21:47 +02:00
|
|
|
if [[ -n "${GITEA_TOKEN_BOOTSTRAP:-}" ]]; then
|
|
|
|
|
info "Gitea token already set: ${GITEA_TOKEN_BOOTSTRAP}"
|
|
|
|
|
else
|
|
|
|
|
echo ""
|
|
|
|
|
prompt "Enter Gitea personal access token (needs repo read access — create at https://${GITEA_HOST}/user/settings/applications): "
|
|
|
|
|
read -r -s GITEA_TOKEN_BOOTSTRAP
|
|
|
|
|
echo ""
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ -n "${AUTH_LOGIN:-}" ]]; then
|
|
|
|
|
info "Agent0 login already set: ${AUTH_LOGIN}"
|
|
|
|
|
else
|
|
|
|
|
echo ""
|
|
|
|
|
prompt "Enter Agent0 login username: "
|
|
|
|
|
read -r AUTH_LOGIN
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ -n "${AUTH_PASSWORD:-}" ]]; then
|
|
|
|
|
info "Agent0 password already set"
|
|
|
|
|
else
|
|
|
|
|
prompt "Enter Agent0 login password: "
|
|
|
|
|
read -r -s AUTH_PASSWORD
|
|
|
|
|
echo ""
|
|
|
|
|
fi
|
2026-04-11 14:26:19 +02:00
|
|
|
|
2026-04-11 16:21:47 +02:00
|
|
|
info "Credentials ready"
|
2026-04-11 13:22:26 +02:00
|
|
|
|
2026-04-11 12:59:02 +02:00
|
|
|
# ── Ollama ────────────────────────────────────────────────────────────────────
|
|
|
|
|
section "Ollama"
|
|
|
|
|
|
|
|
|
|
if command -v ollama >/dev/null 2>&1; then
|
|
|
|
|
info "Ollama already installed"
|
|
|
|
|
else
|
|
|
|
|
info "Installing Ollama..."
|
|
|
|
|
curl -fsSL https://ollama.com/install.sh | sh
|
|
|
|
|
fi
|
|
|
|
|
|
2026-04-11 13:06:08 +02:00
|
|
|
# Ensure the Ollama server is running
|
|
|
|
|
if [[ "$OS" == "Linux" ]]; then
|
|
|
|
|
if systemctl is-active --quiet ollama 2>/dev/null; then
|
|
|
|
|
info "Ollama service running"
|
|
|
|
|
else
|
|
|
|
|
info "Starting Ollama service..."
|
|
|
|
|
sudo systemctl enable --now ollama
|
|
|
|
|
fi
|
2026-04-11 12:59:02 +02:00
|
|
|
else
|
2026-04-11 13:06:08 +02:00
|
|
|
# macOS — check if server is already answering, otherwise start in background
|
|
|
|
|
if curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then
|
|
|
|
|
info "Ollama server already running"
|
|
|
|
|
else
|
|
|
|
|
info "Starting Ollama server..."
|
|
|
|
|
ollama serve >/tmp/ollama.log 2>&1 &
|
|
|
|
|
sleep 2
|
|
|
|
|
curl -sf http://localhost:11434/api/tags >/dev/null 2>&1 \
|
|
|
|
|
&& info "Ollama server started" \
|
|
|
|
|
|| echo " Warning: Ollama server didn't respond — start it manually with: ollama serve"
|
|
|
|
|
fi
|
2026-04-11 12:59:02 +02:00
|
|
|
fi
|
|
|
|
|
|
2026-04-11 13:22:26 +02:00
|
|
|
# Returns 0 if model is already pulled, 1 if not
|
|
|
|
|
model_installed() {
|
|
|
|
|
ollama list 2>/dev/null | awk 'NR>1 {print $1}' | grep -qx "$1"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pull_model() {
|
|
|
|
|
local model="$1"
|
|
|
|
|
if model_installed "$model"; then
|
|
|
|
|
info "Already downloaded: $model"
|
|
|
|
|
else
|
|
|
|
|
info "Pulling $model..."
|
|
|
|
|
ollama pull "$model"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo " ── Coding ──────────────────────────────────────"
|
|
|
|
|
echo " [1] qwen2.5-coder:7b ~5 GB fast, good for quick tasks"
|
|
|
|
|
echo " [2] qwen2.5-coder:32b ~20 GB strong coding (recommended)"
|
|
|
|
|
echo " [3] qwen2.5-coder:72b ~45 GB best coding quality"
|
|
|
|
|
echo " [4] deepseek-coder-v2:16b ~9 GB strong alternative"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " ── General ─────────────────────────────────────"
|
|
|
|
|
echo " [5] llama3.1:8b ~5 GB fast general-purpose"
|
|
|
|
|
echo " [6] llama3.1:70b ~40 GB strong general-purpose"
|
|
|
|
|
echo " [7] mistral:7b ~4 GB fast, widely used"
|
|
|
|
|
echo " [8] mixtral:8x7b ~26 GB MoE, strong at instruction following"
|
|
|
|
|
echo " [9] gemma2:9b ~6 GB Google, good at reasoning"
|
|
|
|
|
echo " [10] gemma2:27b ~16 GB Google, stronger"
|
|
|
|
|
echo " [11] qwen2.5:72b ~45 GB strong general + multilingual"
|
2026-04-11 12:59:02 +02:00
|
|
|
echo ""
|
2026-04-11 13:22:26 +02:00
|
|
|
echo " ── Reasoning ───────────────────────────────────"
|
|
|
|
|
echo " [12] deepseek-r1:7b ~5 GB chain-of-thought, fast"
|
|
|
|
|
echo " [13] deepseek-r1:32b ~20 GB chain-of-thought, strong"
|
|
|
|
|
echo " [14] deepseek-r1:70b ~43 GB chain-of-thought, best"
|
2026-04-11 12:59:02 +02:00
|
|
|
echo ""
|
2026-04-11 13:22:26 +02:00
|
|
|
prompt "Which models to pull? (e.g. 1 3 7 or 'all' or Enter to skip): "
|
2026-04-11 12:59:02 +02:00
|
|
|
read -r MODEL_CHOICES
|
|
|
|
|
|
2026-04-11 13:22:26 +02:00
|
|
|
# Expand 'all' to full list
|
|
|
|
|
if [[ "$MODEL_CHOICES" == "all" ]]; then
|
|
|
|
|
MODEL_CHOICES="1 2 3 4 5 6 7 8 9 10 11 12 13 14"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-04-11 12:59:02 +02:00
|
|
|
for choice in $MODEL_CHOICES; do
|
|
|
|
|
case "$choice" in
|
2026-04-11 13:22:26 +02:00
|
|
|
1) pull_model "qwen2.5-coder:7b" ;;
|
|
|
|
|
2) pull_model "qwen2.5-coder:32b" ;;
|
|
|
|
|
3) pull_model "qwen2.5-coder:72b" ;;
|
|
|
|
|
4) pull_model "deepseek-coder-v2:16b" ;;
|
|
|
|
|
5) pull_model "llama3.1:8b" ;;
|
|
|
|
|
6) pull_model "llama3.1:70b" ;;
|
|
|
|
|
7) pull_model "mistral:7b" ;;
|
|
|
|
|
8) pull_model "mixtral:8x7b" ;;
|
|
|
|
|
9) pull_model "gemma2:9b" ;;
|
|
|
|
|
10) pull_model "gemma2:27b" ;;
|
|
|
|
|
11) pull_model "qwen2.5:72b" ;;
|
|
|
|
|
12) pull_model "deepseek-r1:7b" ;;
|
|
|
|
|
13) pull_model "deepseek-r1:32b" ;;
|
|
|
|
|
14) pull_model "deepseek-r1:70b" ;;
|
2026-04-11 12:59:02 +02:00
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# ── SSH keys ──────────────────────────────────────────────────────────────────
|
|
|
|
|
section "SSH key: glitch-tunnel → VPS"
|
|
|
|
|
|
|
|
|
|
mkdir -p "${REPO_DIR}/tunnel"
|
|
|
|
|
chmod 700 "${REPO_DIR}/tunnel"
|
|
|
|
|
|
|
|
|
|
if [[ -f "$TUNNEL_KEY" ]]; then
|
|
|
|
|
info "Tunnel key already exists"
|
|
|
|
|
else
|
|
|
|
|
ssh-keygen -t ed25519 -f "$TUNNEL_KEY" -C "glitch-tunnel@$(hostname)" -N ""
|
|
|
|
|
info "Tunnel key generated at ./tunnel/id_ed25519"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
info "Scanning VPS host key..."
|
|
|
|
|
ssh-keyscan -p 22 "$VPS_HOST" > "${REPO_DIR}/tunnel/known_hosts" 2>/dev/null \
|
|
|
|
|
|| { echo "Could not reach ${VPS_HOST} — check network. Re-run when online."; exit 1; }
|
|
|
|
|
|
|
|
|
|
section "SSH key: Gitea (git operations)"
|
|
|
|
|
|
|
|
|
|
mkdir -p "${HOME}/.ssh"
|
|
|
|
|
chmod 700 "${HOME}/.ssh"
|
|
|
|
|
|
|
|
|
|
if [[ -f "$GITEA_KEY" ]]; then
|
2026-04-11 13:35:35 +02:00
|
|
|
info "Gitea key already exists (local only — verify it is registered on the server)"
|
2026-04-11 12:59:02 +02:00
|
|
|
else
|
|
|
|
|
ssh-keygen -t ed25519 -f "$GITEA_KEY" -C "gunnar@$(hostname)" -N ""
|
|
|
|
|
info "Gitea key generated at ${GITEA_KEY}"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-04-11 13:35:35 +02:00
|
|
|
echo ""
|
|
|
|
|
echo " This public key must be registered at https://${GITEA_HOST} → Settings → SSH Keys:"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " $(cat "${GITEA_KEY}.pub")"
|
|
|
|
|
echo ""
|
|
|
|
|
|
2026-04-11 12:59:02 +02:00
|
|
|
# Add Gitea to known_hosts to avoid interactive prompt during git operations
|
|
|
|
|
if ! ssh-keygen -F "[${GITEA_HOST}]:${GITEA_PORT}" -f "${HOME}/.ssh/known_hosts" >/dev/null 2>&1; then
|
|
|
|
|
ssh-keyscan -p "$GITEA_PORT" "$GITEA_HOST" >> "${HOME}/.ssh/known_hosts" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Write SSH config entry for Gitea
|
|
|
|
|
if ! grep -q "Host ${GITEA_HOST}" "${HOME}/.ssh/config" 2>/dev/null; then
|
|
|
|
|
cat >> "${HOME}/.ssh/config" << EOF
|
|
|
|
|
|
|
|
|
|
Host ${GITEA_HOST}
|
|
|
|
|
HostName ${GITEA_HOST}
|
|
|
|
|
Port ${GITEA_PORT}
|
|
|
|
|
User git
|
|
|
|
|
IdentityFile ${GITEA_KEY}
|
|
|
|
|
EOF
|
|
|
|
|
info "SSH config entry added for ${GITEA_HOST}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── gutasktool ────────────────────────────────────────────────────────────────
|
|
|
|
|
section "gutasktool"
|
|
|
|
|
|
2026-04-11 13:22:26 +02:00
|
|
|
mkdir -p "${REPO_DIR}/agent-zero-data"
|
|
|
|
|
|
2026-04-11 12:59:02 +02:00
|
|
|
if [[ -d "$GUTASK_DIR" ]]; then
|
|
|
|
|
info "gutasktool already at ${GUTASK_DIR}"
|
|
|
|
|
else
|
|
|
|
|
info "Cloning gutasktool..."
|
|
|
|
|
git clone "ssh://git@${GITEA_HOST}:${GITEA_PORT}/glitch-university/gutasktool.git" "$GUTASK_DIR" \
|
2026-04-11 13:22:26 +02:00
|
|
|
|| git clone "https://oauth2:${GITEA_TOKEN_BOOTSTRAP}@${GITEA_HOST}/glitch-university/gutasktool.git" "$GUTASK_DIR"
|
2026-04-11 12:59:02 +02:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ ! -f "${GUTASK_DIR}/.env" ]]; then
|
|
|
|
|
cat > "${GUTASK_DIR}/.env" << EOF
|
|
|
|
|
API_URL=https://glitch.university
|
2026-04-11 13:22:26 +02:00
|
|
|
CONTENT_API_KEY=${CONTENT_API_KEY}
|
2026-04-11 12:59:02 +02:00
|
|
|
GITEA_URL=https://${GITEA_HOST}
|
|
|
|
|
GITEA_OWNER=glitch-university
|
|
|
|
|
# AGENT_ID, AGENT_NAME, AGENT_PASSWORD, and GITEA_TOKEN are per-agent.
|
|
|
|
|
# They are passed through each agent's thread context, not set here.
|
|
|
|
|
EOF
|
|
|
|
|
info "gutasktool .env created at ${GUTASK_DIR}/.env"
|
|
|
|
|
else
|
|
|
|
|
info "gutasktool .env already exists"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── Docker containers ─────────────────────────────────────────────────────────
|
|
|
|
|
section "Docker containers"
|
|
|
|
|
|
|
|
|
|
cd "$REPO_DIR"
|
2026-04-11 14:26:19 +02:00
|
|
|
|
2026-04-11 16:21:47 +02:00
|
|
|
cat > "${REPO_DIR}/.env" << EOF
|
2026-04-11 14:26:19 +02:00
|
|
|
AUTH_LOGIN=${AUTH_LOGIN}
|
|
|
|
|
AUTH_PASSWORD=${AUTH_PASSWORD}
|
2026-04-11 16:21:47 +02:00
|
|
|
CONTENT_API_KEY=${CONTENT_API_KEY}
|
|
|
|
|
GITEA_TOKEN_BOOTSTRAP=${GITEA_TOKEN_BOOTSTRAP}
|
2026-04-11 14:26:19 +02:00
|
|
|
EOF
|
2026-04-11 16:21:47 +02:00
|
|
|
info "Agent0 .env written"
|
2026-04-11 12:59:02 +02:00
|
|
|
info "Building glitch-tunnel image..."
|
|
|
|
|
docker compose build --quiet
|
|
|
|
|
|
|
|
|
|
info "Starting containers..."
|
|
|
|
|
docker compose up -d
|
|
|
|
|
|
2026-04-11 13:22:26 +02:00
|
|
|
info "Installing gutasktool inside agent0 container..."
|
2026-04-11 16:24:03 +02:00
|
|
|
docker exec agent0 bash -c "while [ -f /var/lib/apt/lists/lock ] || [ -f /var/lib/dpkg/lock-frontend ]; do sleep 2; done && apt-get update -q && apt-get install -y -q python3-pip && python3 -m pip install --break-system-packages -q -e /a0/usr/gutasktool"
|
2026-04-11 13:22:26 +02:00
|
|
|
|
2026-04-11 12:59:02 +02:00
|
|
|
echo ""
|
|
|
|
|
docker compose ps
|
|
|
|
|
|
|
|
|
|
# ── Manual steps summary ──────────────────────────────────────────────────────
|
|
|
|
|
TUNNEL_PUBKEY=$(cat "${TUNNEL_KEY}.pub")
|
|
|
|
|
GITEA_PUBKEY=$(cat "${GITEA_KEY}.pub")
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
|
|
|
|
|
echo -e "${YELLOW} Two manual steps remaining:${NC}"
|
|
|
|
|
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${CYAN}1. Add tunnel key to VPS${NC}"
|
|
|
|
|
echo " Run on the VPS:"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " echo '${TUNNEL_PUBKEY}' >> /home/tunnel/.ssh/authorized_keys"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${CYAN}2. Add Gitea key to Gitea${NC}"
|
|
|
|
|
echo " Log into https://${GITEA_HOST} as gunnar"
|
|
|
|
|
echo " Settings → SSH Keys → Add key:"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " ${GITEA_PUBKEY}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " After adding both keys:"
|
|
|
|
|
echo " • Tunnel connects automatically (check: docker logs glitch-tunnel)"
|
|
|
|
|
echo " • Open https://agent0.glitch.university"
|
|
|
|
|
echo " • Enter basic auth password"
|
|
|
|
|
echo " • Settings → add Anthropic / OpenAI API keys"
|
|
|
|
|
echo " • Ollama is pre-configured at http://host.docker.internal:11434"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${GREEN}Bootstrap complete.${NC}"
|