- Add hermes-gateway.service with restart=always and security hardening - Integrate service setup into deploy.sh - Add --service flag for standalone install - Add make service target Resolves #2
329 lines
9.9 KiB
Bash
329 lines
9.9 KiB
Bash
#!/bin/bash
|
|
# ================================================================
|
|
# The Door — Deploy Script
|
|
# ================================================================
|
|
# The crisis front door. Deploy to VPS.
|
|
#
|
|
# Usage:
|
|
# bash deploy/deploy.sh # Full deploy (swap + nginx + site + firewall + hermes service)
|
|
# bash deploy/deploy.sh --site # Site files only (fast update)
|
|
# bash deploy/deploy.sh --ssl # SSL setup only
|
|
# bash deploy/deploy.sh --service # Install/restart hermes-gateway systemd service
|
|
# bash deploy/deploy.sh --check # Verify deployment health
|
|
#
|
|
# This script is IDEMPOTENT — safe to run repeatedly.
|
|
# Run on VPS as root: bash deploy/deploy.sh
|
|
# ================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
DOMAIN="alexanderwhitestone.com"
|
|
SITE_ROOT="/var/www/the-door"
|
|
DEPLOY_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
VPS_IP=$(curl -sf --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
err() { echo -e "${RED}[-]${NC} $*" >&2; }
|
|
|
|
# ================================================================
|
|
# FUNCTIONS
|
|
# ================================================================
|
|
|
|
setup_swap() {
|
|
log "Checking swap..."
|
|
if swapon --show 2>/dev/null | grep -q swap; then
|
|
log "Swap already configured: $(swapon --show | head -1 | awk '{print $3}')"
|
|
return 0
|
|
fi
|
|
|
|
if [ -f /swapfile ]; then
|
|
warn "Swapfile exists but not active — activating..."
|
|
swapon /swapfile 2>/dev/null && log "Swap activated" || err "Failed to activate swap"
|
|
return 0
|
|
fi
|
|
|
|
log "Creating 2GB swap file..."
|
|
fallocate -l 2G /swapfile
|
|
chmod 600 /swapfile
|
|
mkswap /swapfile
|
|
swapon /swapfile
|
|
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
|
log "Swap configured: $(free -h | awk '/Swap/{print $2}')"
|
|
}
|
|
|
|
install_packages() {
|
|
log "Installing packages..."
|
|
apt-get update -qq
|
|
apt-get install -y -qq nginx certbot python3-certbot-nginx ufw curl
|
|
log "Packages installed"
|
|
}
|
|
|
|
deploy_site() {
|
|
log "Deploying site files to ${SITE_ROOT}..."
|
|
mkdir -p "${SITE_ROOT}"
|
|
|
|
# Copy static files
|
|
for f in index.html manifest.json sw.js about.html testimony.html; do
|
|
if [ -f "${DEPLOY_DIR}/${f}" ]; then
|
|
cp "${DEPLOY_DIR}/${f}" "${SITE_ROOT}/${f}"
|
|
fi
|
|
done
|
|
|
|
# Copy system prompt (reference, not served)
|
|
cp "${DEPLOY_DIR}/system-prompt.txt" "${SITE_ROOT}/system-prompt.txt"
|
|
|
|
chown -R www-data:www-data "${SITE_ROOT}"
|
|
chmod -R 755 "${SITE_ROOT}"
|
|
|
|
log "Site files deployed: $(ls -la ${SITE_ROOT} | wc -l) files"
|
|
}
|
|
|
|
configure_nginx() {
|
|
log "Configuring nginx..."
|
|
|
|
# Deploy site config
|
|
cp "${DEPLOY_DIR}/deploy/nginx.conf" /etc/nginx/sites-available/the-door
|
|
|
|
# Add rate limit zone if not present
|
|
if ! grep -q "limit_req_zone.*the_door_api" /etc/nginx/nginx.conf 2>/dev/null; then
|
|
sed -i '/http {/a \ limit_req_zone $binary_remote_addr zone=the_door_api:10m rate=10r/m;' /etc/nginx/nginx.conf
|
|
fi
|
|
|
|
# Enable site, disable default
|
|
ln -sf /etc/nginx/sites-available/the-door /etc/nginx/sites-enabled/
|
|
rm -f /etc/nginx/sites-enabled/default
|
|
|
|
# Test and reload
|
|
if nginx -t 2>&1; then
|
|
systemctl enable nginx
|
|
systemctl reload nginx || systemctl start nginx
|
|
log "nginx configured and running"
|
|
else
|
|
err "nginx config test failed!"
|
|
nginx -t
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
setup_firewall() {
|
|
log "Configuring firewall..."
|
|
ufw allow 22/tcp comment 'SSH'
|
|
ufw allow 80/tcp comment 'HTTP'
|
|
ufw allow 443/tcp comment 'HTTPS'
|
|
ufw --force enable
|
|
log "Firewall configured: $(ufw status | grep -c ALLOW) rules active"
|
|
}
|
|
|
|
setup_ssl() {
|
|
log "Checking SSL certificate..."
|
|
if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
|
|
log "SSL certificate already exists"
|
|
# Check expiry
|
|
EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" 2>/dev/null | cut -d= -f2)
|
|
log "Certificate expires: ${EXPIRY}"
|
|
return 0
|
|
fi
|
|
|
|
warn "No SSL certificate found."
|
|
warn "Ensure DNS is pointed: ${DOMAIN} A record → ${VPS_IP}"
|
|
warn ""
|
|
warn "Then run manually:"
|
|
warn " certbot --nginx -d ${DOMAIN} -d www.${DOMAIN}"
|
|
warn ""
|
|
|
|
# Attempt automated cert if DNS resolves correctly
|
|
RESOLVED_IP=$(dig +short "${DOMAIN}" @8.8.8.8 2>/dev/null | head -1)
|
|
if [ "${RESOLVED_IP}" = "${VPS_IP}" ]; then
|
|
log "DNS resolves correctly — obtaining SSL certificate..."
|
|
certbot --nginx -d "${DOMAIN}" -d "www.${DOMAIN}" \
|
|
--non-interactive --agree-tos --register-unsafely-without-email \
|
|
&& log "SSL certificate obtained!" \
|
|
|| warn "certbot failed — run manually"
|
|
else
|
|
warn "DNS not pointed yet (resolved: ${RESOLVED_IP}, expected: ${VPS_IP})"
|
|
fi
|
|
}
|
|
|
|
setup_hermes_service() {
|
|
log "Setting up Hermes Gateway systemd service..."
|
|
|
|
# Create hermes user if it doesn't exist
|
|
if ! id -u hermes >/dev/null 2>&1; then
|
|
log "Creating hermes user..."
|
|
useradd --system --shell /usr/sbin/nologin --home-dir /opt/hermes --create-home hermes
|
|
fi
|
|
|
|
# Create working directory
|
|
mkdir -p /opt/hermes
|
|
chown hermes:hermes /opt/hermes
|
|
|
|
# Deploy systemd unit file
|
|
cp "${DEPLOY_DIR}/deploy/hermes-gateway.service" /etc/systemd/system/hermes-gateway.service
|
|
systemctl daemon-reload
|
|
systemctl enable hermes-gateway
|
|
|
|
# Start or restart the service
|
|
if systemctl is-active --quiet hermes-gateway; then
|
|
log "Restarting hermes-gateway service..."
|
|
systemctl restart hermes-gateway
|
|
else
|
|
log "Starting hermes-gateway service..."
|
|
systemctl start hermes-gateway || warn "Service start failed — ensure hermes binary is installed at /usr/local/bin/hermes"
|
|
fi
|
|
|
|
# Verify
|
|
sleep 2
|
|
if systemctl is-active --quiet hermes-gateway; then
|
|
log "hermes-gateway service is running"
|
|
else
|
|
warn "hermes-gateway service not running — check: journalctl -u hermes-gateway"
|
|
fi
|
|
}
|
|
|
|
check_deployment() {
|
|
echo ""
|
|
echo "================================"
|
|
echo " The Door — Deployment Status"
|
|
echo "================================"
|
|
echo ""
|
|
|
|
# Swap
|
|
echo -n "Swap: "
|
|
if swapon --show 2>/dev/null | grep -q swap; then
|
|
echo -e "${GREEN}OK${NC} — $(swapon --show | head -1 | awk '{print $3}')"
|
|
else
|
|
echo -e "${RED}MISSING${NC}"
|
|
fi
|
|
|
|
# nginx
|
|
echo -n "nginx: "
|
|
if systemctl is-active --quiet nginx 2>/dev/null; then
|
|
echo -e "${GREEN}RUNNING${NC}"
|
|
else
|
|
echo -e "${RED}STOPPED${NC}"
|
|
fi
|
|
|
|
# Site files
|
|
echo -n "Site files: "
|
|
if [ -f "${SITE_ROOT}/index.html" ]; then
|
|
echo -e "${GREEN}OK${NC} — $(ls -la ${SITE_ROOT}/index.html | awk '{print $5}') bytes"
|
|
else
|
|
echo -e "${RED}MISSING${NC}"
|
|
fi
|
|
|
|
# SSL
|
|
echo -n "SSL cert: "
|
|
if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
|
|
EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" | cut -d= -f2)
|
|
echo -e "${GREEN}OK${NC} — expires ${EXPIRY}"
|
|
else
|
|
echo -e "${YELLOW}NOT SET${NC}"
|
|
fi
|
|
|
|
# Firewall
|
|
echo -n "Firewall: "
|
|
if ufw status 2>/dev/null | grep -q "Status: active"; then
|
|
echo -e "${GREEN}ACTIVE${NC} — $(ufw status | grep -c ALLOW) rules"
|
|
else
|
|
echo -e "${RED}INACTIVE${NC}"
|
|
fi
|
|
|
|
# HTTP test
|
|
echo -n "HTTP test: "
|
|
if curl -sf --max-time 5 "http://localhost/" -o /dev/null 2>/dev/null; then
|
|
echo -e "${GREEN}OK${NC}"
|
|
else
|
|
echo -e "${YELLOW}N/A${NC}"
|
|
fi
|
|
|
|
# API proxy test
|
|
echo -n "API proxy: "
|
|
if curl -sf --max-time 5 "http://localhost/health" -o /dev/null 2>/dev/null; then
|
|
echo -e "${GREEN}OK${NC}"
|
|
else
|
|
echo -e "${YELLOW}Hermes not responding${NC} (may be expected)"
|
|
fi
|
|
|
|
# DNS
|
|
echo -n "DNS: "
|
|
RESOLVED_IP=$(dig +short "${DOMAIN}" @8.8.8.8 2>/dev/null | head -1)
|
|
if [ "${RESOLVED_IP}" = "${VPS_IP}" ]; then
|
|
echo -e "${GREEN}OK${NC} — ${DOMAIN} → ${RESOLVED_IP}"
|
|
else
|
|
echo -e "${YELLOW}NOT POINTED${NC} (resolved: ${RESOLVED_IP:-nothing}, expected: ${VPS_IP})"
|
|
fi
|
|
|
|
# Hermes gateway service
|
|
echo -n "Hermes service: "
|
|
if systemctl is-active --quiet hermes-gateway 2>/dev/null; then
|
|
echo -e "${GREEN}RUNNING${NC}"
|
|
elif systemctl is-enabled --quiet hermes-gateway 2>/dev/null; then
|
|
echo -e "${YELLOW}ENABLED but not running${NC}"
|
|
else
|
|
echo -e "${RED}NOT INSTALLED${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
echo "IP: ${VPS_IP}"
|
|
echo "Domain: ${DOMAIN}"
|
|
echo "Site root: ${SITE_ROOT}"
|
|
}
|
|
|
|
# ================================================================
|
|
# MAIN
|
|
# ================================================================
|
|
|
|
echo ""
|
|
echo "=== The Door — Deployment ==="
|
|
echo "Deploy dir: ${DEPLOY_DIR}"
|
|
echo "VPS IP: ${VPS_IP}"
|
|
echo ""
|
|
|
|
case "${1:-full}" in
|
|
--site)
|
|
deploy_site
|
|
configure_nginx
|
|
;;
|
|
--ssl)
|
|
setup_ssl
|
|
;;
|
|
--service)
|
|
setup_hermes_service
|
|
;;
|
|
--check)
|
|
check_deployment
|
|
;;
|
|
--full|"")
|
|
setup_swap
|
|
install_packages
|
|
deploy_site
|
|
configure_nginx
|
|
setup_firewall
|
|
setup_ssl
|
|
setup_hermes_service
|
|
check_deployment
|
|
;;
|
|
*)
|
|
echo "Usage: $0 [--site|--ssl|--service|--check|--full]"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "=== Deployment complete ==="
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Point DNS: ${DOMAIN} A record → ${VPS_IP}"
|
|
echo " 2. If SSL not set: certbot --nginx -d ${DOMAIN} -d www.${DOMAIN}"
|
|
echo " 3. Test: curl -I https://${DOMAIN}"
|
|
echo " 4. Test API: curl https://${DOMAIN}/api/health"
|
|
echo ""
|
|
echo "The Door is open."
|