diff --git a/setup_timmy.sh b/setup_timmy.sh index 147f6230..4b2f50ee 100755 --- a/setup_timmy.sh +++ b/setup_timmy.sh @@ -1,68 +1,57 @@ #!/usr/bin/env bash # ============================================================================= -# Sovereign Agent Stack — VPS Deployment Script v6 +# Sovereign Agent Stack — VPS Deployment Script v7 # -# Bootstraps Paperclip + OpenFang + Obsidian Vault on a VPS with: +# Paperclip only. No fluff. # - Paperclip in local_trusted mode (127.0.0.1:3100) -# - Nginx reverse proxy on port 80 with cookie-based auth gate -# - One login prompt, 7-day session cookie — no repeated auth popups +# - Nginx reverse proxy on port 80 +# - Cookie-based auth gate — login once, 7-day session +# - PostgreSQL backend # # Usage: -# 1. curl -O https://raw.githubusercontent.com/AlexanderWhitestone/Timmy-time-dashboard/main/setup_timmy.sh -# 2. chmod +x setup_timmy.sh -# 3. ./setup_timmy.sh install -# 4. ./setup_timmy.sh start +# curl -O https://raw.githubusercontent.com/AlexanderWhitestone/Timmy-time-dashboard/main/setup_timmy.sh +# chmod +x setup_timmy.sh +# ./setup_timmy.sh install +# ./setup_timmy.sh start # -# Dashboard: http://YOUR_DOMAIN (behind auth gate) +# Dashboard: http://YOUR_DOMAIN (behind auth) # ============================================================================= set -euo pipefail # --- Configuration --- PROJECT_DIR="${PROJECT_DIR:-$HOME/sovereign-stack}" -VAULT_DIR="${VAULT_DIR:-$HOME/TimmyVault}" PAPERCLIP_DIR="$PROJECT_DIR/paperclip" -AGENTS_DIR="$PROJECT_DIR/agents" LOG_DIR="$PROJECT_DIR/logs" PID_DIR="$PROJECT_DIR/pids" -# Domain / IP — set DOMAIN env var or edit here DOMAIN="${DOMAIN:-$(curl -s ifconfig.me)}" -# Database DB_USER="paperclip" DB_PASS="paperclip" DB_NAME="paperclip" DATABASE_URL="postgresql://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME" -# Auth gate credentials — change these! AUTH_USER="${AUTH_USER:-Rockachopa}" AUTH_PASS="${AUTH_PASS:-Iamrockachopathegend}" -# Paperclip secrets (generated once, persisted in secrets file) SECRETS_FILE="$PROJECT_DIR/.secrets" # --- Colors --- -CYAN='\033[0;36m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -BOLD='\033[1m' -NC='\033[0m' +CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m' -# --- Helper Functions --- banner() { echo -e "\n${CYAN}═══════════════════════════════════════════════${NC}\n${BOLD}$1${NC}\n${CYAN}═══════════════════════════════════════════════${NC}\n"; } step() { echo -e "${GREEN}▸${NC} $1"; } warn() { echo -e "${YELLOW}⚠${NC} $1"; } fail() { echo -e "${RED}✘${NC} $1"; exit 1; } info() { echo -e "${BOLD}$1${NC}"; } -# --- Secrets Management --- +# --- Secrets --- load_or_create_secrets() { if [ -f "$SECRETS_FILE" ]; then source "$SECRETS_FILE" - step "Loaded existing secrets" + step "Loaded secrets" else BETTER_AUTH_SECRET="sovereign-$(openssl rand -hex 16)" PAPERCLIP_AGENT_JWT_SECRET="agent-$(openssl rand -hex 16)" @@ -71,24 +60,18 @@ BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" PAPERCLIP_AGENT_JWT_SECRET="$PAPERCLIP_AGENT_JWT_SECRET" SECRETS chmod 600 "$SECRETS_FILE" - step "Generated and saved new secrets" + step "Generated secrets" fi } # --- Preflight --- check_preflight() { - banner "Preflight Checks" - - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - step "OS: Linux detected" - else - warn "OS: $OSTYPE — this script targets Ubuntu/Debian" - fi + banner "Preflight" if command -v apt-get >/dev/null 2>&1; then - step "Installing system dependencies..." + step "Installing dependencies..." sudo apt-get update -y > /dev/null 2>&1 - sudo apt-get install -y curl git postgresql postgresql-contrib build-essential nginx apache2-utils > /dev/null 2>&1 + sudo apt-get install -y curl git postgresql postgresql-contrib build-essential nginx > /dev/null 2>&1 fi if ! command -v node >/dev/null 2>&1; then @@ -107,11 +90,9 @@ check_preflight() { # --- Database --- setup_database() { - banner "Database Setup" - step "Ensuring PostgreSQL is running..." + banner "Database" sudo systemctl start postgresql || sudo service postgresql start || true - step "Configuring database..." sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || \ sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS' SUPERUSER;" @@ -121,56 +102,13 @@ setup_database() { step "Database ready" } -# --- Install Stack --- -install_stack() { - banner "Installing Sovereign Stack" - mkdir -p "$PROJECT_DIR" "$AGENTS_DIR" "$LOG_DIR" "$PID_DIR" - - # OpenFang - if ! command -v openfang >/dev/null 2>&1; then - step "Installing OpenFang..." - curl -fsSL https://openfang.sh/install | sh - fi - step "OpenFang ready" - - # Paperclip - if [ ! -d "$PAPERCLIP_DIR" ]; then - step "Cloning Paperclip..." - git clone --depth 1 https://github.com/paperclipai/paperclip.git "$PAPERCLIP_DIR" - fi - cd "$PAPERCLIP_DIR" - step "Installing Paperclip dependencies..." - pnpm install --frozen-lockfile 2>/dev/null || pnpm install - cd - > /dev/null - step "Paperclip ready" - - # Obsidian Vault - mkdir -p "$VAULT_DIR" - if [ ! -f "$VAULT_DIR/Genesis.md" ]; then - cat > "$VAULT_DIR/Genesis.md" < "$PROJECT_DIR/auth-gate.py" <<'AUTHGATE' #!/usr/bin/env python3 -"""Cookie-based auth gate for nginx auth_request. Login once, session lasts 7 days.""" +"""Cookie-based auth gate. Login once, 7-day session.""" import hashlib, hmac, http.server, time, base64, os SECRET = os.environ.get("AUTH_GATE_SECRET", "sovereign-timmy-gate-2026") @@ -180,75 +118,55 @@ COOKIE_NAME = "sovereign_gate" COOKIE_MAX_AGE = 86400 * 7 def make_token(ts): - msg = f"{USER}:{ts}".encode() - return hmac.new(SECRET.encode(), msg, hashlib.sha256).hexdigest()[:32] + return hmac.new(SECRET.encode(), f"{USER}:{ts}".encode(), hashlib.sha256).hexdigest()[:32] def verify_token(token): try: parts = token.split(".") - if len(parts) != 2: - return False + if len(parts) != 2: return False ts, sig = int(parts[0]), parts[1] - if time.time() - ts > COOKIE_MAX_AGE: - return False + if time.time() - ts > COOKIE_MAX_AGE: return False return sig == make_token(ts) - except: - return False + except: return False class Handler(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass - def do_GET(self): - cookies = self.headers.get("Cookie", "") - for c in cookies.split(";"): + for c in self.headers.get("Cookie", "").split(";"): c = c.strip() - if c.startswith(f"{COOKIE_NAME}="): - token = c[len(COOKIE_NAME)+1:] - if verify_token(token): - self.send_response(200) - self.end_headers() - return - + if c.startswith(f"{COOKIE_NAME}=") and verify_token(c[len(COOKIE_NAME)+1:]): + self.send_response(200); self.end_headers(); return auth = self.headers.get("Authorization", "") if auth.startswith("Basic "): try: - decoded = base64.b64decode(auth[6:]).decode() - u, p = decoded.split(":", 1) + u, p = base64.b64decode(auth[6:]).decode().split(":", 1) if u == USER and p == PASS: ts = int(time.time()) - token = f"{ts}.{make_token(ts)}" self.send_response(200) self.send_header("Set-Cookie", - f"{COOKIE_NAME}={token}; Path=/; Max-Age={COOKIE_MAX_AGE}; HttpOnly; SameSite=Lax") - self.end_headers() - return - except: - pass - + f"{COOKIE_NAME}={ts}.{make_token(ts)}; Path=/; Max-Age={COOKIE_MAX_AGE}; HttpOnly; SameSite=Lax") + self.end_headers(); return + except: pass self.send_response(401) self.send_header("WWW-Authenticate", 'Basic realm="Sovereign Stack"') self.end_headers() if __name__ == "__main__": s = http.server.HTTPServer(("127.0.0.1", 9876), Handler) - print("Auth gate listening on 127.0.0.1:9876") - s.serve_forever() + print("Auth gate on 127.0.0.1:9876"); s.serve_forever() AUTHGATE - chmod +x "$PROJECT_DIR/auth-gate.py" - step "Auth gate installed" } -# --- Nginx Reverse Proxy --- +# --- Nginx --- install_nginx() { - step "Configuring nginx reverse proxy..." + step "Configuring nginx..." cat > /etc/nginx/sites-available/paperclip </dev/null || pnpm install + cd - > /dev/null + + install_auth_gate + install_nginx + + banner "Installed" + info "Run: ./setup_timmy.sh start" +} + +# --- Cleanup --- kill_zombies() { - step "Cleaning up stale processes..." - # Kill any existing Paperclip/node processes on ports 3100-3110 + step "Cleaning stale processes..." for port in $(seq 3100 3110); do pid=$(lsof -ti :$port 2>/dev/null || true) - if [ -n "$pid" ]; then - kill -9 $pid 2>/dev/null || true - step "Killed process on port $port" - fi + [ -n "$pid" ] && kill -9 $pid 2>/dev/null && step "Killed port $port" || true done - # Kill any stale auth gate pid=$(lsof -ti :9876 2>/dev/null || true) - if [ -n "$pid" ]; then - kill -9 $pid 2>/dev/null || true - fi + [ -n "$pid" ] && kill -9 $pid 2>/dev/null || true sleep 1 } -# --- Start Services --- +# --- Start --- start_services() { - banner "Starting Services" - + banner "Starting" load_or_create_secrets kill_zombies - # Stop Docker Caddy if it's hogging port 80 + # Stop Docker Caddy if conflicting on port 80 if docker ps --format '{{.Names}}' 2>/dev/null | grep -q timmy-caddy; then - step "Stopping Docker Caddy (port 80 conflict)..." + step "Stopping Docker Caddy (port 80)..." docker stop timmy-caddy > /dev/null 2>&1 || true fi - # 1. Auth Gate - step "Starting auth gate..." + step "Auth gate..." AUTH_USER="$AUTH_USER" AUTH_PASS="$AUTH_PASS" \ nohup python3 "$PROJECT_DIR/auth-gate.py" > "$LOG_DIR/auth-gate.log" 2>&1 & echo $! > "$PID_DIR/auth-gate.pid" - # 2. Nginx - step "Starting nginx..." + step "Nginx..." systemctl restart nginx - # 3. Paperclip — local_trusted mode, bound to 127.0.0.1 - step "Starting Paperclip (local_trusted → 127.0.0.1:3100)..." + step "Paperclip (local_trusted → 127.0.0.1:3100)..." cd "$PAPERCLIP_DIR" HOST=127.0.0.1 \ DATABASE_URL="$DATABASE_URL" \ @@ -348,115 +274,64 @@ start_services() { echo $! > "$PID_DIR/paperclip.pid" cd - > /dev/null - # 4. OpenFang - if command -v openfang >/dev/null 2>&1; then - step "Starting OpenFang..." - nohup openfang start > "$LOG_DIR/openfang.log" 2>&1 & - echo $! > "$PID_DIR/openfang.pid" - fi - - # Wait for Paperclip - step "Waiting for Paperclip to initialize..." + step "Waiting for Paperclip..." for i in $(seq 1 30); do if grep -q "Server listening on" "$LOG_DIR/paperclip.log" 2>/dev/null; then echo "" - info "╔═══════════════════════════════════════════════╗" - info "║ Sovereign Stack is LIVE ║" - info "║ ║" - info "║ Dashboard: http://$DOMAIN" - info "║ Auth: $AUTH_USER / ******** ║" - info "║ Mode: local_trusted + nginx proxy ║" - info "╚═══════════════════════════════════════════════╝" + info "══════════════════════════════════════" + info " Dashboard: http://$DOMAIN" + info " Auth: $AUTH_USER / ********" + info "══════════════════════════════════════" return - fi - printf "." - sleep 2 + fi + printf "."; sleep 2 done - echo "" - warn "Startup taking longer than expected. Check: tail -f $LOG_DIR/paperclip.log" + echo ""; warn "Slow start. Check: tail -f $LOG_DIR/paperclip.log" } -# --- Stop Services --- +# --- Stop --- stop_services() { - banner "Stopping Services" - for service in paperclip openfang auth-gate; do + banner "Stopping" + for service in paperclip auth-gate; do pid_file="$PID_DIR/$service.pid" if [ -f "$pid_file" ]; then pid=$(cat "$pid_file") - if ps -p "$pid" > /dev/null 2>&1; then - step "Stopping $service (PID: $pid)..." - kill "$pid" 2>/dev/null || true - # Also kill child processes - pkill -P "$pid" 2>/dev/null || true - fi + ps -p "$pid" > /dev/null 2>&1 && kill "$pid" 2>/dev/null && step "Stopped $service" || true rm -f "$pid_file" fi done - step "Services stopped" } # --- Status --- show_status() { - banner "Stack Status" - for s in paperclip openfang auth-gate; do - pid_file="$PID_DIR/$s.pid" - if [ -f "$pid_file" ] && ps -p $(cat "$pid_file") > /dev/null 2>&1; then - echo -e " ${GREEN}●${NC} $s (PID $(cat "$pid_file"))" + banner "Status" + for s in paperclip auth-gate; do + pf="$PID_DIR/$s.pid" + if [ -f "$pf" ] && ps -p $(cat "$pf") > /dev/null 2>&1; then + echo -e " ${GREEN}●${NC} $s" else echo -e " ${RED}○${NC} $s" fi done - echo "" - # Port check for port in 80 3100 9876; do - pid=$(lsof -ti :$port 2>/dev/null || true) - if [ -n "$pid" ]; then - echo -e " ${GREEN}●${NC} Port $port in use" + if lsof -ti :$port > /dev/null 2>&1; then + echo -e " ${GREEN}●${NC} :$port" else - echo -e " ${RED}○${NC} Port $port free" + echo -e " ${RED}○${NC} :$port" fi done - echo "" - if systemctl is-active --quiet nginx; then - echo -e " ${GREEN}●${NC} nginx active" - else - echo -e " ${RED}○${NC} nginx inactive" - fi + systemctl is-active --quiet nginx && echo -e " ${GREEN}●${NC} nginx" || echo -e " ${RED}○${NC} nginx" } -# --- Restart --- -restart_services() { - stop_services - sleep 2 - start_services -} - -# --- Main CLI --- +# --- CLI --- case "${1:-}" in - install) - check_preflight - setup_database - install_stack - ;; - start) - start_services - ;; - stop) - stop_services - ;; - restart) - restart_services - ;; - status) - show_status - ;; - logs) - tail -f "$LOG_DIR"/*.log - ;; - *) - echo "Usage: $0 {install|start|stop|restart|status|logs}" - exit 1 - ;; + install) check_preflight; setup_database; install_stack ;; + start) start_services ;; + stop) stop_services ;; + restart) stop_services; sleep 2; start_services ;; + status) show_status ;; + logs) tail -f "$LOG_DIR"/*.log ;; + *) echo "Usage: $0 {install|start|stop|restart|status|logs}"; exit 1 ;; esac