mirror of
https://github.com/blackboxprogramming/alexa-amundson-resume.git
synced 2026-03-18 02:03:58 -05:00
feat: add real Stripe integration, e2e tests, and Pi deployment
Replace documentation-only repo with working code: - Stripe integration: webhook handler (8 event types), billing API (customers, checkout, payments, subscriptions, invoices) - Express API server with health endpoint, structured logging - E2E tests (Playwright): health, webhook signature verification, billing API validation - Unit tests: webhook event handler coverage for all event types - Pi deployment: deploy.sh (rsync + systemd), NGINX load balancer across Pi cluster, Docker support - CI/CD: test workflow, Pi deploy workflow, updated auto-deploy and self-healing to run real tests before deploying - Move resume docs to docs/ to separate code from documentation https://claude.ai/code/session_01Mf5Pg82fV6BTRS9GnpV7nr
This commit is contained in:
175
deploy/pi/deploy.sh
Executable file
175
deploy/pi/deploy.sh
Executable file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# BlackRoad OS → Raspberry Pi Deployment Script
|
||||
# Deploys the Stripe integration service to Pi nodes
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Load config from .env or environment
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
set -a
|
||||
source "$PROJECT_ROOT/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
PI_USER="${PI_USER:-pi}"
|
||||
PI_DEPLOY_PATH="${PI_DEPLOY_PATH:-/opt/blackroad}"
|
||||
PI_SSH_KEY="${PI_SSH_KEY:-$HOME/.ssh/id_ed25519}"
|
||||
PI_HOSTS=("${PI_HOST_1:-}" "${PI_HOST_2:-}" "${PI_HOST_3:-}")
|
||||
|
||||
# Filter out empty hosts
|
||||
ACTIVE_HOSTS=()
|
||||
for host in "${PI_HOSTS[@]}"; do
|
||||
if [ -n "$host" ]; then
|
||||
ACTIVE_HOSTS+=("$host")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#ACTIVE_HOSTS[@]} -eq 0 ]; then
|
||||
echo "ERROR: No Pi hosts configured. Set PI_HOST_1, PI_HOST_2, PI_HOST_3 in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "========================================="
|
||||
echo " BlackRoad OS → Pi Deployment"
|
||||
echo "========================================="
|
||||
echo " Hosts: ${ACTIVE_HOSTS[*]}"
|
||||
echo " Path: $PI_DEPLOY_PATH"
|
||||
echo " User: $PI_USER"
|
||||
echo "========================================="
|
||||
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i $PI_SSH_KEY"
|
||||
|
||||
deploy_to_host() {
|
||||
local host="$1"
|
||||
echo ""
|
||||
echo "--- Deploying to $host ---"
|
||||
|
||||
# Create deploy directory
|
||||
ssh $SSH_OPTS "$PI_USER@$host" "mkdir -p $PI_DEPLOY_PATH"
|
||||
|
||||
# Sync project files (exclude dev stuff)
|
||||
rsync -avz --delete \
|
||||
--exclude 'node_modules' \
|
||||
--exclude '.git' \
|
||||
--exclude 'tests' \
|
||||
--exclude '.env' \
|
||||
--exclude 'docs/' \
|
||||
--exclude '*.md' \
|
||||
-e "ssh $SSH_OPTS" \
|
||||
"$PROJECT_ROOT/" "$PI_USER@$host:$PI_DEPLOY_PATH/"
|
||||
|
||||
# Copy .env if it exists (separately so rsync --delete doesn't remove it)
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
scp $SSH_OPTS "$PROJECT_ROOT/.env" "$PI_USER@$host:$PI_DEPLOY_PATH/.env"
|
||||
fi
|
||||
|
||||
# Install dependencies and restart service
|
||||
ssh $SSH_OPTS "$PI_USER@$host" bash <<REMOTE
|
||||
cd $PI_DEPLOY_PATH
|
||||
export NODE_ENV=production
|
||||
|
||||
# Install Node.js if not present
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "Installing Node.js 20..."
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
# Install production dependencies
|
||||
npm ci --production 2>/dev/null || npm install --production
|
||||
|
||||
# Set up systemd service
|
||||
sudo tee /etc/systemd/system/blackroad-stripe.service > /dev/null <<EOF
|
||||
[Unit]
|
||||
Description=BlackRoad Stripe Integration
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$PI_USER
|
||||
WorkingDirectory=$PI_DEPLOY_PATH
|
||||
ExecStart=/usr/bin/node src/server.js
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment=NODE_ENV=production
|
||||
EnvironmentFile=$PI_DEPLOY_PATH/.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable blackroad-stripe
|
||||
sudo systemctl restart blackroad-stripe
|
||||
|
||||
echo "Service status on $HOSTNAME:"
|
||||
sudo systemctl status blackroad-stripe --no-pager || true
|
||||
REMOTE
|
||||
|
||||
echo "--- $host: deployment complete ---"
|
||||
}
|
||||
|
||||
# Health check after deployment
|
||||
check_health() {
|
||||
local host="$1"
|
||||
local port="${PORT:-3000}"
|
||||
echo "Checking health on $host:$port..."
|
||||
|
||||
for i in 1 2 3 4 5; do
|
||||
if ssh $SSH_OPTS "$PI_USER@$host" "curl -sf http://localhost:$port/api/health" 2>/dev/null; then
|
||||
echo ""
|
||||
echo " $host: HEALTHY"
|
||||
return 0
|
||||
fi
|
||||
echo " Attempt $i/5 - waiting..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo " $host: UNHEALTHY"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Deploy to all active hosts
|
||||
FAILED=()
|
||||
for host in "${ACTIVE_HOSTS[@]}"; do
|
||||
if deploy_to_host "$host"; then
|
||||
echo ""
|
||||
else
|
||||
FAILED+=("$host")
|
||||
echo "WARN: Deployment to $host failed, continuing..."
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Health Checks"
|
||||
echo "========================================="
|
||||
|
||||
UNHEALTHY=()
|
||||
for host in "${ACTIVE_HOSTS[@]}"; do
|
||||
if ! check_health "$host"; then
|
||||
UNHEALTHY+=("$host")
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Deployment Summary"
|
||||
echo "========================================="
|
||||
echo " Total hosts: ${#ACTIVE_HOSTS[@]}"
|
||||
echo " Deploy failed: ${#FAILED[@]}"
|
||||
echo " Unhealthy: ${#UNHEALTHY[@]}"
|
||||
|
||||
if [ ${#FAILED[@]} -gt 0 ] || [ ${#UNHEALTHY[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo " FAILED: ${FAILED[*]:-none}"
|
||||
echo " UNHEALTHY: ${UNHEALTHY[*]:-none}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " All Pis deployed and healthy!"
|
||||
echo "========================================="
|
||||
102
deploy/pi/setup-nginx.sh
Executable file
102
deploy/pi/setup-nginx.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Sets up NGINX as a reverse proxy + load balancer across Pi nodes
|
||||
# Run this on a Pi that will act as the entry point (or any Pi with NGINX)
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
set -a
|
||||
source "$PROJECT_ROOT/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
PORT="${PORT:-3000}"
|
||||
PI_HOSTS=("${PI_HOST_1:-}" "${PI_HOST_2:-}" "${PI_HOST_3:-}")
|
||||
|
||||
# Filter out empty hosts
|
||||
ACTIVE_HOSTS=()
|
||||
for host in "${PI_HOSTS[@]}"; do
|
||||
if [ -n "$host" ]; then
|
||||
ACTIVE_HOSTS+=("$host")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Setting up NGINX load balancer for ${#ACTIVE_HOSTS[@]} Pi nodes..."
|
||||
|
||||
# Install nginx if needed
|
||||
if ! command -v nginx &> /dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y nginx
|
||||
fi
|
||||
|
||||
# Build upstream block
|
||||
UPSTREAM=""
|
||||
for host in "${ACTIVE_HOSTS[@]}"; do
|
||||
UPSTREAM+=" server ${host}:${PORT};\n"
|
||||
done
|
||||
|
||||
# Write nginx config
|
||||
sudo tee /etc/nginx/sites-available/blackroad-stripe > /dev/null <<EOF
|
||||
upstream blackroad_stripe {
|
||||
least_conn;
|
||||
$(printf " server %s:${PORT};\n" "${ACTIVE_HOSTS[@]}")
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Health check endpoint — no rate limiting
|
||||
location /api/health {
|
||||
proxy_pass http://blackroad_stripe;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
|
||||
# Stripe webhooks — higher body size limit, raw body passthrough
|
||||
location /api/webhooks/stripe {
|
||||
proxy_pass http://blackroad_stripe;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header Stripe-Signature \$http_stripe_signature;
|
||||
client_max_body_size 5m;
|
||||
}
|
||||
|
||||
# All other API routes
|
||||
location /api/ {
|
||||
proxy_pass http://blackroad_stripe;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
client_max_body_size 1m;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404 '{"error":"Not found"}';
|
||||
add_header Content-Type application/json;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo ln -sf /etc/nginx/sites-available/blackroad-stripe /etc/nginx/sites-enabled/
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
echo ""
|
||||
echo "NGINX load balancer configured:"
|
||||
echo " Upstream nodes: ${ACTIVE_HOSTS[*]}"
|
||||
echo " Listening on: port 80"
|
||||
echo " Routes:"
|
||||
echo " /api/health → health check"
|
||||
echo " /api/webhooks/stripe → Stripe webhooks"
|
||||
echo " /api/* → billing API"
|
||||
echo ""
|
||||
echo "Point your Stripe webhook URL to: http://<this-pi-ip>/api/webhooks/stripe"
|
||||
Reference in New Issue
Block a user