v8: Hermes + Paperclip, Tailscale-only, systemd, backups, UFW lockdown
This commit is contained in:
384
setup_timmy.sh
384
setup_timmy.sh
@@ -1,21 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Sovereign Agent Stack — VPS Deployment Script v7
|
# Sovereign Agent Stack — VPS Deployment Script v8
|
||||||
#
|
#
|
||||||
# Paperclip only. No fluff.
|
# Hermes Agent + Paperclip, Tailscale-only.
|
||||||
|
# - Hermes Agent (Nous Research) — persistent AI agent
|
||||||
# - Paperclip in local_trusted mode (127.0.0.1:3100)
|
# - Paperclip in local_trusted mode (127.0.0.1:3100)
|
||||||
# - Nginx reverse proxy on port 80
|
# - Nginx reverse proxy on Tailscale IP (port 80)
|
||||||
# - Cookie-based auth gate — login once, 7-day session
|
# - Cookie-based auth gate for Paperclip
|
||||||
# - PostgreSQL backend
|
# - PostgreSQL backend
|
||||||
|
# - UFW locked to Tailscale + SSH only
|
||||||
|
# - systemd services for auto-restart
|
||||||
|
# - Daily backups with 30-day retention
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# curl -O https://raw.githubusercontent.com/AlexanderWhitestone/Timmy-time-dashboard/main/setup_timmy.sh
|
# ./setup_timmy.sh install # Full install (run once on fresh VPS)
|
||||||
# chmod +x setup_timmy.sh
|
# ./setup_timmy.sh start # Start all services
|
||||||
# ./setup_timmy.sh install
|
# ./setup_timmy.sh stop # Stop all services
|
||||||
# ./setup_timmy.sh start
|
# ./setup_timmy.sh restart # Stop + start
|
||||||
|
# ./setup_timmy.sh status # Health check
|
||||||
|
# ./setup_timmy.sh logs # Tail all logs
|
||||||
#
|
#
|
||||||
# Dashboard: http://YOUR_DOMAIN (behind auth)
|
# Prerequisites:
|
||||||
|
# - Ubuntu 22.04+ VPS with root access
|
||||||
|
# - Tailscale installed and joined to tailnet
|
||||||
|
# - SSH key added
|
||||||
|
#
|
||||||
|
# Access (Tailscale only):
|
||||||
|
# Paperclip: http://<TAILSCALE_IP>
|
||||||
|
# Hermes: ssh to VPS, then `hermes`
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -25,8 +38,10 @@ PROJECT_DIR="${PROJECT_DIR:-$HOME/sovereign-stack}"
|
|||||||
PAPERCLIP_DIR="$PROJECT_DIR/paperclip"
|
PAPERCLIP_DIR="$PROJECT_DIR/paperclip"
|
||||||
LOG_DIR="$PROJECT_DIR/logs"
|
LOG_DIR="$PROJECT_DIR/logs"
|
||||||
PID_DIR="$PROJECT_DIR/pids"
|
PID_DIR="$PROJECT_DIR/pids"
|
||||||
|
BACKUP_DIR="$HOME/backups/hermes"
|
||||||
|
|
||||||
DOMAIN="${DOMAIN:-$(curl -s ifconfig.me)}"
|
# Tailscale IP (auto-detected)
|
||||||
|
TSIP="${TSIP:-$(tailscale ip -4 2>/dev/null || echo '127.0.0.1')}"
|
||||||
|
|
||||||
DB_USER="paperclip"
|
DB_USER="paperclip"
|
||||||
DB_PASS="paperclip"
|
DB_PASS="paperclip"
|
||||||
@@ -47,51 +62,53 @@ warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|||||||
fail() { echo -e "${RED}✘${NC} $1"; exit 1; }
|
fail() { echo -e "${RED}✘${NC} $1"; exit 1; }
|
||||||
info() { echo -e "${BOLD}$1${NC}"; }
|
info() { echo -e "${BOLD}$1${NC}"; }
|
||||||
|
|
||||||
# --- Secrets ---
|
# =============================================================================
|
||||||
load_or_create_secrets() {
|
# INSTALL
|
||||||
if [ -f "$SECRETS_FILE" ]; then
|
# =============================================================================
|
||||||
source "$SECRETS_FILE"
|
|
||||||
step "Loaded secrets"
|
|
||||||
else
|
|
||||||
BETTER_AUTH_SECRET="sovereign-$(openssl rand -hex 16)"
|
|
||||||
PAPERCLIP_AGENT_JWT_SECRET="agent-$(openssl rand -hex 16)"
|
|
||||||
cat > "$SECRETS_FILE" <<SECRETS
|
|
||||||
BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET"
|
|
||||||
PAPERCLIP_AGENT_JWT_SECRET="$PAPERCLIP_AGENT_JWT_SECRET"
|
|
||||||
SECRETS
|
|
||||||
chmod 600 "$SECRETS_FILE"
|
|
||||||
step "Generated secrets"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Preflight ---
|
|
||||||
check_preflight() {
|
check_preflight() {
|
||||||
banner "Preflight"
|
banner "Preflight"
|
||||||
|
|
||||||
|
# Tailscale must be connected
|
||||||
|
if [ "$TSIP" = "127.0.0.1" ]; then
|
||||||
|
fail "Tailscale not connected. Run: tailscale up --authkey=YOUR_KEY"
|
||||||
|
fi
|
||||||
|
step "Tailscale IP: $TSIP"
|
||||||
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
step "Installing dependencies..."
|
step "Installing system packages..."
|
||||||
sudo apt-get update -y > /dev/null 2>&1
|
apt-get update -y > /dev/null 2>&1
|
||||||
sudo apt-get install -y curl git postgresql postgresql-contrib build-essential nginx > /dev/null 2>&1
|
apt-get install -y curl git postgresql postgresql-contrib build-essential nginx lsof > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Node.js 20
|
||||||
if ! command -v node >/dev/null 2>&1; then
|
if ! command -v node >/dev/null 2>&1; then
|
||||||
step "Installing Node.js 20..."
|
step "Installing Node.js 20..."
|
||||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - > /dev/null 2>&1
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - > /dev/null 2>&1
|
||||||
sudo apt-get install -y nodejs > /dev/null 2>&1
|
apt-get install -y nodejs > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
step "Node $(node -v)"
|
step "Node $(node -v)"
|
||||||
|
|
||||||
|
# pnpm
|
||||||
if ! command -v pnpm >/dev/null 2>&1; then
|
if ! command -v pnpm >/dev/null 2>&1; then
|
||||||
step "Installing pnpm..."
|
step "Installing pnpm..."
|
||||||
sudo npm install -g pnpm > /dev/null 2>&1
|
npm install -g pnpm > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
step "pnpm $(pnpm -v)"
|
step "pnpm $(pnpm -v)"
|
||||||
|
|
||||||
|
# uv (for Hermes)
|
||||||
|
if ! command -v uv >/dev/null 2>&1 && [ ! -f "$HOME/.local/bin/uv" ]; then
|
||||||
|
step "Installing uv..."
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
step "uv ready"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Database ---
|
# --- Database ---
|
||||||
setup_database() {
|
setup_database() {
|
||||||
banner "Database"
|
banner "Database"
|
||||||
sudo systemctl start postgresql || sudo service postgresql start || true
|
systemctl start postgresql || service postgresql start || true
|
||||||
|
|
||||||
sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || \
|
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;"
|
sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS' SUPERUSER;"
|
||||||
@@ -102,6 +119,43 @@ setup_database() {
|
|||||||
step "Database ready"
|
step "Database ready"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Hermes Agent ---
|
||||||
|
install_hermes() {
|
||||||
|
banner "Hermes Agent"
|
||||||
|
|
||||||
|
if [ -d "$HOME/.hermes/hermes-agent" ]; then
|
||||||
|
step "Hermes already cloned, updating..."
|
||||||
|
cd "$HOME/.hermes/hermes-agent" && git pull origin main 2>/dev/null || true
|
||||||
|
cd - > /dev/null
|
||||||
|
else
|
||||||
|
step "Installing Hermes (one-liner)..."
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure venv exists
|
||||||
|
export PATH="$HOME/.local/bin:$HOME/.hermes/node/bin:$PATH"
|
||||||
|
if [ ! -d "$HOME/.hermes/hermes-agent/.venv" ]; then
|
||||||
|
step "Creating Hermes venv..."
|
||||||
|
cd "$HOME/.hermes/hermes-agent"
|
||||||
|
uv venv .venv --python 3.12 2>/dev/null || uv venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
uv pip install -e ".[all]" 2>&1 | tail -3
|
||||||
|
cd - > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add hermes to PATH in bashrc if not already there
|
||||||
|
if ! grep -q 'hermes-agent/.venv/bin' "$HOME/.bashrc" 2>/dev/null; then
|
||||||
|
cat >> "$HOME/.bashrc" <<'BASHRC'
|
||||||
|
|
||||||
|
# Sovereign Stack — Hermes Agent
|
||||||
|
export PATH="$HOME/.local/bin:$HOME/.hermes/node/bin:$HOME/.hermes/hermes-agent/.venv/bin:$PATH"
|
||||||
|
alias hermes='cd ~/.hermes/hermes-agent && source .venv/bin/activate && python hermes'
|
||||||
|
BASHRC
|
||||||
|
fi
|
||||||
|
|
||||||
|
step "Hermes installed"
|
||||||
|
}
|
||||||
|
|
||||||
# --- Auth Gate ---
|
# --- Auth Gate ---
|
||||||
install_auth_gate() {
|
install_auth_gate() {
|
||||||
step "Installing auth gate..."
|
step "Installing auth gate..."
|
||||||
@@ -158,14 +212,13 @@ AUTHGATE
|
|||||||
chmod +x "$PROJECT_DIR/auth-gate.py"
|
chmod +x "$PROJECT_DIR/auth-gate.py"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Nginx ---
|
# --- Nginx (Tailscale-only) ---
|
||||||
install_nginx() {
|
install_nginx() {
|
||||||
step "Configuring nginx..."
|
step "Configuring nginx (Tailscale-only: $TSIP)..."
|
||||||
|
|
||||||
cat > /etc/nginx/sites-available/paperclip <<NGINX
|
cat > /etc/nginx/sites-available/paperclip <<NGINX
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen ${TSIP}:80;
|
||||||
server_name $DOMAIN;
|
|
||||||
|
|
||||||
location = /_auth {
|
location = /_auth {
|
||||||
internal;
|
internal;
|
||||||
@@ -209,9 +262,39 @@ NGINX
|
|||||||
nginx -t || fail "Nginx config failed"
|
nginx -t || fail "Nginx config failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Install ---
|
# --- UFW (SSH first, then lockdown) ---
|
||||||
install_stack() {
|
setup_firewall() {
|
||||||
banner "Installing Paperclip"
|
banner "Firewall"
|
||||||
|
step "Allowing SSH before lockdown..."
|
||||||
|
ufw allow 22/tcp
|
||||||
|
ufw allow in on tailscale0
|
||||||
|
ufw allow in on lo
|
||||||
|
ufw default deny incoming
|
||||||
|
ufw default allow outgoing
|
||||||
|
ufw --force enable
|
||||||
|
step "Firewall locked: SSH + Tailscale only"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Secrets ---
|
||||||
|
load_or_create_secrets() {
|
||||||
|
if [ -f "$SECRETS_FILE" ]; then
|
||||||
|
source "$SECRETS_FILE"
|
||||||
|
step "Loaded secrets"
|
||||||
|
else
|
||||||
|
BETTER_AUTH_SECRET="sovereign-$(openssl rand -hex 16)"
|
||||||
|
PAPERCLIP_AGENT_JWT_SECRET="agent-$(openssl rand -hex 16)"
|
||||||
|
cat > "$SECRETS_FILE" <<SECRETS
|
||||||
|
BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET"
|
||||||
|
PAPERCLIP_AGENT_JWT_SECRET="$PAPERCLIP_AGENT_JWT_SECRET"
|
||||||
|
SECRETS
|
||||||
|
chmod 600 "$SECRETS_FILE"
|
||||||
|
step "Generated secrets"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Paperclip ---
|
||||||
|
install_paperclip() {
|
||||||
|
banner "Paperclip"
|
||||||
mkdir -p "$PROJECT_DIR" "$LOG_DIR" "$PID_DIR"
|
mkdir -p "$PROJECT_DIR" "$LOG_DIR" "$PID_DIR"
|
||||||
|
|
||||||
if [ ! -d "$PAPERCLIP_DIR" ]; then
|
if [ ! -d "$PAPERCLIP_DIR" ]; then
|
||||||
@@ -225,95 +308,181 @@ install_stack() {
|
|||||||
|
|
||||||
install_auth_gate
|
install_auth_gate
|
||||||
install_nginx
|
install_nginx
|
||||||
|
step "Paperclip installed"
|
||||||
banner "Installed"
|
|
||||||
info "Run: ./setup_timmy.sh start"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Cleanup ---
|
# --- systemd services ---
|
||||||
|
install_systemd() {
|
||||||
|
banner "systemd Services"
|
||||||
|
|
||||||
|
# Auth gate
|
||||||
|
cat > /etc/systemd/system/sovereign-auth-gate.service <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Sovereign Auth Gate
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/python3 $PROJECT_DIR/auth-gate.py
|
||||||
|
Environment=AUTH_USER=$AUTH_USER
|
||||||
|
Environment=AUTH_PASS=$AUTH_PASS
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
|
||||||
|
# Paperclip
|
||||||
|
cat > /etc/systemd/system/sovereign-paperclip.service <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Paperclip Dashboard
|
||||||
|
After=network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=$PAPERCLIP_DIR
|
||||||
|
ExecStart=$(which pnpm) dev
|
||||||
|
Environment=HOST=127.0.0.1
|
||||||
|
Environment=DATABASE_URL=$DATABASE_URL
|
||||||
|
Environment=PAPERCLIP_DEPLOYMENT_MODE=local_trusted
|
||||||
|
Environment=BETTER_AUTH_URL=http://$TSIP
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
|
||||||
|
# Hermes gateway
|
||||||
|
cat > /etc/systemd/system/hermes-gateway.service <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Hermes Agent Gateway
|
||||||
|
After=network-online.target tailscaled.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=$HOME/.hermes/hermes-agent
|
||||||
|
ExecStart=$HOME/.hermes/hermes-agent/.venv/bin/python hermes gateway
|
||||||
|
Environment=HERMES_BIND_HOST=$TSIP
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
|
||||||
|
# Load secrets into paperclip service
|
||||||
|
if [ -f "$SECRETS_FILE" ]; then
|
||||||
|
source "$SECRETS_FILE"
|
||||||
|
mkdir -p /etc/systemd/system/sovereign-paperclip.service.d
|
||||||
|
cat > /etc/systemd/system/sovereign-paperclip.service.d/secrets.conf <<OVERRIDE
|
||||||
|
[Service]
|
||||||
|
Environment=BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET
|
||||||
|
Environment=PAPERCLIP_AGENT_JWT_SECRET=$PAPERCLIP_AGENT_JWT_SECRET
|
||||||
|
OVERRIDE
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable sovereign-auth-gate sovereign-paperclip hermes-gateway
|
||||||
|
step "systemd services installed and enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Backups ---
|
||||||
|
install_backups() {
|
||||||
|
banner "Backups"
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
cat > "$HOME/scripts/hermes-backup.sh" <<'BACKUP'
|
||||||
|
#!/bin/bash
|
||||||
|
BACKUP_DIR=$HOME/backups/hermes
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
HERMES_HOME=$HOME/.hermes
|
||||||
|
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
|
tar czf $BACKUP_DIR/hermes_$TIMESTAMP.tar.gz \
|
||||||
|
$HERMES_HOME/config.yaml \
|
||||||
|
$HERMES_HOME/.env \
|
||||||
|
$HERMES_HOME/hermes-agent/skills/ \
|
||||||
|
$HERMES_HOME/hermes-agent/sessions/ \
|
||||||
|
$HERMES_HOME/cron/ \
|
||||||
|
2>/dev/null || true
|
||||||
|
|
||||||
|
# Prune backups older than 30 days
|
||||||
|
find $BACKUP_DIR -name 'hermes_*.tar.gz' -mtime +30 -delete
|
||||||
|
|
||||||
|
echo "$(date): Backup complete: hermes_$TIMESTAMP.tar.gz"
|
||||||
|
BACKUP
|
||||||
|
mkdir -p "$HOME/scripts"
|
||||||
|
chmod +x "$HOME/scripts/hermes-backup.sh"
|
||||||
|
|
||||||
|
# Cron: daily at 3 AM
|
||||||
|
(crontab -l 2>/dev/null | grep -v hermes-backup; echo "0 3 * * * $HOME/scripts/hermes-backup.sh >> /var/log/hermes-backup.log 2>&1") | crontab -
|
||||||
|
|
||||||
|
step "Daily backup at 3 AM, 30-day retention"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RUNTIME
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
kill_zombies() {
|
kill_zombies() {
|
||||||
step "Cleaning stale processes..."
|
step "Cleaning stale processes..."
|
||||||
for port in $(seq 3100 3110); do
|
for port in 3100 3101 3102 3103 3104 3105 9876; do
|
||||||
pid=$(lsof -ti :$port 2>/dev/null || true)
|
pid=$(lsof -ti :$port 2>/dev/null || true)
|
||||||
[ -n "$pid" ] && kill -9 $pid 2>/dev/null && step "Killed port $port" || true
|
[ -n "$pid" ] && kill -9 $pid 2>/dev/null && step "Killed port $port" || true
|
||||||
done
|
done
|
||||||
pid=$(lsof -ti :9876 2>/dev/null || true)
|
|
||||||
[ -n "$pid" ] && kill -9 $pid 2>/dev/null || true
|
|
||||||
sleep 1
|
sleep 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Start ---
|
|
||||||
start_services() {
|
start_services() {
|
||||||
banner "Starting"
|
banner "Starting"
|
||||||
load_or_create_secrets
|
load_or_create_secrets
|
||||||
kill_zombies
|
kill_zombies
|
||||||
|
|
||||||
# Stop Docker Caddy if conflicting on port 80
|
systemctl restart sovereign-auth-gate
|
||||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q timmy-caddy; then
|
|
||||||
step "Stopping Docker Caddy (port 80)..."
|
|
||||||
docker stop timmy-caddy > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
step "Nginx..."
|
|
||||||
systemctl restart nginx
|
systemctl restart nginx
|
||||||
|
systemctl restart sovereign-paperclip
|
||||||
step "Paperclip (local_trusted → 127.0.0.1:3100)..."
|
|
||||||
cd "$PAPERCLIP_DIR"
|
|
||||||
HOST=127.0.0.1 \
|
|
||||||
DATABASE_URL="$DATABASE_URL" \
|
|
||||||
PAPERCLIP_DEPLOYMENT_MODE=local_trusted \
|
|
||||||
BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
|
|
||||||
PAPERCLIP_AGENT_JWT_SECRET="$PAPERCLIP_AGENT_JWT_SECRET" \
|
|
||||||
BETTER_AUTH_URL="http://$DOMAIN" \
|
|
||||||
nohup pnpm dev > "$LOG_DIR/paperclip.log" 2>&1 &
|
|
||||||
echo $! > "$PID_DIR/paperclip.pid"
|
|
||||||
cd - > /dev/null
|
|
||||||
|
|
||||||
step "Waiting for Paperclip..."
|
step "Waiting for Paperclip..."
|
||||||
for i in $(seq 1 30); do
|
for i in $(seq 1 30); do
|
||||||
if grep -q "Server listening on" "$LOG_DIR/paperclip.log" 2>/dev/null; then
|
if systemctl is-active --quiet sovereign-paperclip && curl -s -o /dev/null http://127.0.0.1:3100 2>/dev/null; then
|
||||||
echo ""
|
break
|
||||||
info "══════════════════════════════════════"
|
|
||||||
info " Dashboard: http://$DOMAIN"
|
|
||||||
info " Auth: $AUTH_USER / ********"
|
|
||||||
info "══════════════════════════════════════"
|
|
||||||
return
|
|
||||||
fi
|
fi
|
||||||
printf "."; sleep 2
|
printf "."; sleep 2
|
||||||
done
|
done
|
||||||
echo ""; warn "Slow start. Check: tail -f $LOG_DIR/paperclip.log"
|
echo ""
|
||||||
|
|
||||||
|
info "══════════════════════════════════════"
|
||||||
|
info " Paperclip: http://$TSIP"
|
||||||
|
info " Hermes: ssh root@$TSIP → hermes"
|
||||||
|
info " Auth: $AUTH_USER / ********"
|
||||||
|
info "══════════════════════════════════════"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Stop ---
|
|
||||||
stop_services() {
|
stop_services() {
|
||||||
banner "Stopping"
|
banner "Stopping"
|
||||||
for service in paperclip auth-gate; do
|
systemctl stop sovereign-paperclip 2>/dev/null || true
|
||||||
pid_file="$PID_DIR/$service.pid"
|
systemctl stop sovereign-auth-gate 2>/dev/null || true
|
||||||
if [ -f "$pid_file" ]; then
|
systemctl stop hermes-gateway 2>/dev/null || true
|
||||||
pid=$(cat "$pid_file")
|
step "All services stopped"
|
||||||
ps -p "$pid" > /dev/null 2>&1 && kill "$pid" 2>/dev/null && step "Stopped $service" || true
|
|
||||||
rm -f "$pid_file"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Status ---
|
|
||||||
show_status() {
|
show_status() {
|
||||||
banner "Status"
|
banner "Status"
|
||||||
for s in paperclip auth-gate; do
|
for svc in sovereign-auth-gate sovereign-paperclip hermes-gateway nginx postgresql; do
|
||||||
pf="$PID_DIR/$s.pid"
|
if systemctl is-active --quiet $svc 2>/dev/null; then
|
||||||
if [ -f "$pf" ] && ps -p $(cat "$pf") > /dev/null 2>&1; then
|
echo -e " ${GREEN}●${NC} $svc"
|
||||||
echo -e " ${GREEN}●${NC} $s"
|
|
||||||
else
|
else
|
||||||
echo -e " ${RED}○${NC} $s"
|
echo -e " ${RED}○${NC} $svc"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
|
echo -e " Tailscale: $TSIP"
|
||||||
|
echo -e " Paperclip: http://$TSIP"
|
||||||
|
echo ""
|
||||||
for port in 80 3100 9876; do
|
for port in 80 3100 9876; do
|
||||||
if lsof -ti :$port > /dev/null 2>&1; then
|
if lsof -ti :$port > /dev/null 2>&1; then
|
||||||
echo -e " ${GREEN}●${NC} :$port"
|
echo -e " ${GREEN}●${NC} :$port"
|
||||||
@@ -321,17 +490,32 @@ show_status() {
|
|||||||
echo -e " ${RED}○${NC} :$port"
|
echo -e " ${RED}○${NC} :$port"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo ""
|
}
|
||||||
systemctl is-active --quiet nginx && echo -e " ${GREEN}●${NC} nginx" || echo -e " ${RED}○${NC} nginx"
|
|
||||||
|
# =============================================================================
|
||||||
|
# FULL INSTALL (run once)
|
||||||
|
# =============================================================================
|
||||||
|
full_install() {
|
||||||
|
check_preflight
|
||||||
|
setup_database
|
||||||
|
install_hermes
|
||||||
|
install_paperclip
|
||||||
|
load_or_create_secrets
|
||||||
|
install_systemd
|
||||||
|
install_backups
|
||||||
|
setup_firewall
|
||||||
|
|
||||||
|
banner "Install Complete"
|
||||||
|
info "Run: ./setup_timmy.sh start"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- CLI ---
|
# --- CLI ---
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
install) check_preflight; setup_database; install_stack ;;
|
install) full_install ;;
|
||||||
start) start_services ;;
|
start) start_services ;;
|
||||||
stop) stop_services ;;
|
stop) stop_services ;;
|
||||||
restart) stop_services; sleep 2; start_services ;;
|
restart) stop_services; sleep 2; start_services ;;
|
||||||
status) show_status ;;
|
status) show_status ;;
|
||||||
logs) tail -f "$LOG_DIR"/*.log ;;
|
logs) journalctl -u sovereign-paperclip -u sovereign-auth-gate -u hermes-gateway -f ;;
|
||||||
*) echo "Usage: $0 {install|start|stop|restart|status|logs}"; exit 1 ;;
|
*) echo "Usage: $0 {install|start|stop|restart|status|logs}"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Reference in New Issue
Block a user