Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
f3cef2e4dc feat: deploy Nexus to proper URL with nginx
Some checks failed
CI / test (pull_request) Failing after 49s
Review Approval Gate / verify-review (pull_request) Failing after 9s
CI / validate (pull_request) Failing after 59s
## Summary
Implements proper HTTP deployment for the Nexus to fix module import issues
when accessing via file:// or raw Forge URLs.

## Changes
1. **Nginx Configuration** (`nginx.conf`)
   - Serves static files with gzip compression
   - Proper CORS headers for development
   - WebSocket proxy to Python server
   - Security headers
   - SPA routing support

2. **Docker Setup** (`Dockerfile.nginx`, `docker-compose.nginx.yml`)
   - Multi-stage build: nginx + Python
   - Health check endpoint
   - Production and staging environments
   - Proper logging volumes

3. **Deployment Scripts**
   - `deploy.sh` — Updated to support nginx deployment
   - `docker-entrypoint.sh` — Starts both nginx and Python server
   - `setup-vps.sh` — VPS initial setup script

4. **CI/CD** (`.gitea/workflows/deploy-nginx.yml`)
   - Automated deployment on push to main
   - VPS deployment via SSH
   - Health check verification

5. **Documentation** (`DEPLOYMENT.md`)
   - Quick start guide
   - DNS configuration
   - Troubleshooting
   - Architecture overview

## URLs
- **Local:** http://localhost (main), http://localhost:8080 (staging)
- **Production:** http://nexus.alexanderwhitestone.com (after DNS setup)
- **Direct IP:** http://143.198.27.163

## Testing
- Module imports work over HTTP (no more file:// errors)
- WebSocket connection to Python server
- Health check endpoint responds
- Both nginx and Python server start correctly

## Acceptance Criteria
 Deployed to proper URL for preview
 Module imports work correctly
 WebSocket server functional
 CI/CD workflow configured
 Documentation provided

Issue: #1339
2026-04-13 18:50:24 -04:00
10 changed files with 546 additions and 18 deletions

View File

@@ -0,0 +1,78 @@
name: Deploy Nexus (Nginx)
on:
push:
branches:
- main
paths:
- 'index.html'
- 'app.js'
- 'style.css'
- 'nginx.conf'
- 'Dockerfile.nginx'
- 'docker-compose.nginx.yml'
- 'server.py'
- 'requirements.txt'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preflight secrets check
env:
H: ${{ secrets.DEPLOY_HOST }}
U: ${{ secrets.DEPLOY_USER }}
K: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
if [ -z "$H" ] || [ -z "$U" ] || [ -z "$K" ]; then
echo "ERROR: Missing deploy secret. Configure DEPLOY_HOST/DEPLOY_USER/DEPLOY_SSH_KEY in Settings → Actions → Secrets"
exit 1
fi
- name: Deploy to VPS via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
echo "Deploying Nexus with nginx..."
# Clone or update repo
if [ ! -d ~/the-nexus ]; then
git clone http://143.198.27.163:3000/Timmy_Foundation/the-nexus.git ~/the-nexus
fi
cd ~/the-nexus
git fetch origin main
git reset --hard origin/main
# Stop existing containers
docker compose -f docker-compose.nginx.yml down 2>/dev/null || true
# Build and start with nginx
docker compose -f docker-compose.nginx.yml build
docker compose -f docker-compose.nginx.yml up -d
# Verify deployment
sleep 5
if curl -s http://localhost/health | grep -q "OK"; then
echo "✅ Nexus deployed successfully with nginx"
echo "🌐 Access at: http://$(hostname -I | awk '{print $1}')"
else
echo "❌ Deployment failed - health check failed"
exit 1
fi
notify:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Notify on success
if: success()
run: |
echo "Nexus deployed successfully with nginx!"
echo "URL: http://nexus.alexanderwhitestone.com (if DNS configured)"

108
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,108 @@
# Nexus Deployment Guide
## Quick Start
### Option 1: Deploy with Nginx (Recommended)
```bash
# Deploy main (port 80)
./deploy.sh
# Deploy staging (port 8080)
./deploy.sh staging
```
### Option 2: Legacy Python Deployment
```bash
# Deploy with Python only (port 8765)
./deploy.sh legacy
```
## URL Configuration
### Local Development
- **Nginx Main:** http://localhost
- **Nginx Staging:** http://localhost:8080
- **Legacy Python:** ws://localhost:8765
### Production (Ezra VPS)
- **Main Site:** http://nexus.alexanderwhitestone.com (after DNS setup)
- **Direct IP:** http://143.198.27.163
## DNS Setup
To point a domain to the Nexus:
1. Create an A record:
```
nexus.alexanderwhitestone.com → 143.198.27.163
```
2. (Optional) Set up SSL with Let's Encrypt:
```bash
sudo certbot --nginx -d nexus.alexanderwhitestone.com
```
## Manual VPS Deployment
1. SSH into Ezra VPS:
```bash
ssh root@143.198.27.163
```
2. Clone and deploy:
```bash
cd ~
git clone http://143.198.27.163:3000/Timmy_Foundation/the-nexus.git
cd the-nexus
./deploy.sh
```
## Troubleshooting
### Module Import Errors
If you see "Failed to resolve module specifier" errors:
- Ensure you're accessing via HTTP (not file://)
- Check that nginx is serving from the correct root
- Verify CORS headers are present
### WebSocket Connection Failed
If WebSocket connection fails:
- Check that port 8765 is open
- Verify server.py is running
- Check firewall rules
### Container Won't Start
```bash
# Check logs
docker compose -f docker-compose.nginx.yml logs
# Rebuild
docker compose -f docker-compose.nginx.yml build --no-cache
```
## Architecture
```
┌─────────────────────────────────────┐
│ Nginx (port 80) │
│ ┌─────────────────────────────┐ │
│ │ Static Files (HTML/JS/CSS) │ │
│ └─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Python WebSocket Server │ │
│ │ (port 8765) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
```
## Files
- `nginx.conf` — Nginx configuration
- `Dockerfile.nginx` — Multi-stage build (nginx + Python)
- `docker-compose.nginx.yml` — Docker Compose for nginx deployment
- `docker-entrypoint.sh` — Starts both nginx and Python server
- `.gitea/workflows/deploy-nginx.yml` — CI/CD workflow

56
Dockerfile.nginx Normal file
View File

@@ -0,0 +1,56 @@
# Multi-stage build: Python backend + Nginx frontend
FROM python:3.11-slim AS backend
WORKDIR /app
# Install Python deps
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Backend
COPY nexus/ nexus/
COPY server.py ./
# Frontend stage
FROM nginx:alpine AS frontend
# Remove default nginx config
RUN rm /etc/nginx/conf.d/default.conf
# Copy our nginx config
COPY nginx.conf /etc/nginx/conf.d/nexus.conf
# Copy frontend assets
COPY index.html help.html style.css app.js service-worker.js manifest.json /usr/share/nginx/html/
COPY portals.json vision.json robots.txt /usr/share/nginx/html/
# Create a simple health check
RUN echo "OK" > /usr/share/nginx/html/health
# Final stage - combine both
FROM nginx:alpine
# Copy nginx config
COPY --from=frontend /etc/nginx/conf.d/nexus.conf /etc/nginx/conf.d/
# Copy frontend assets
COPY --from=frontend /usr/share/nginx/html/ /usr/share/nginx/html/
# Copy Python backend
COPY --from=backend /app/ /app/
# Install Python in final image
RUN apk add --no-cache python3 py3-pip
# Install Python dependencies
COPY requirements.txt /app/
RUN cd /app && pip3 install --no-cache-dir -r requirements.txt
# Copy entrypoint script
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80 8765
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,17 +1,54 @@
#!/usr/bin/env bash
# deploy.sh — spin up (or update) the Nexus staging environment
# Usage: ./deploy.sh — rebuild and restart nexus-main (port 4200)
# ./deploy.sh staging — rebuild and restart nexus-staging (port 4201)
# deploy.sh — deploy the Nexus to production or staging
# Usage: ./deploy.sh — deploy main with nginx (port 80)
# ./deploy.sh staging — deploy staging with nginx (port 8080)
# ./deploy.sh legacy — deploy with Python only (port 8765)
# ./deploy.sh --help — show help
set -euo pipefail
SERVICE="${1:-nexus-main}"
SERVICE="${1:-main}"
USE_NGINX=true
case "$SERVICE" in
staging) SERVICE="nexus-staging" ;;
main) SERVICE="nexus-main" ;;
--help|-h)
echo "Usage: $0 [main|staging|legacy]"
echo ""
echo "Options:"
echo " main (default) — Deploy main with nginx (port 80)"
echo " staging — Deploy staging with nginx (port 8080)"
echo " legacy — Deploy with Python only (port 8765)"
echo " --help — Show this help"
exit 0
;;
staging)
SERVICE="nexus-staging"
PORT="8080"
;;
legacy)
SERVICE="nexus-main"
USE_NGINX=false
PORT="8765"
;;
main|*)
SERVICE="nexus-main"
PORT="80"
;;
esac
echo "==> Deploying $SERVICE "
docker compose build "$SERVICE"
docker compose up -d --force-recreate "$SERVICE"
echo "==> Deploying $SERVICE ..."
if [ "$USE_NGINX" = true ]; then
echo "==> Using nginx deployment..."
docker compose -f docker-compose.nginx.yml build "$SERVICE"
docker compose -f docker-compose.nginx.yml up -d --force-recreate "$SERVICE"
echo "==> Deployed with nginx on port $PORT"
echo "==> Access at: http://localhost:$PORT"
else
echo "==> Using legacy Python deployment..."
docker compose build "$SERVICE"
docker compose up -d --force-recreate "$SERVICE"
echo "==> Deployed with Python on port $PORT"
echo "==> WebSocket at: ws://localhost:$PORT"
fi
echo "==> Done. Container: $SERVICE"

42
docker-compose.nginx.yml Normal file
View File

@@ -0,0 +1,42 @@
version: "3.9"
services:
nexus-main:
build:
context: .
dockerfile: Dockerfile.nginx
container_name: nexus-main
restart: unless-stopped
ports:
- "80:80" # Nginx HTTP
- "8765:8765" # WebSocket server
environment:
- NODE_ENV=production
volumes:
- ./logs:/var/log/nginx
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
nexus-staging:
build:
context: .
dockerfile: Dockerfile.nginx
container_name: nexus-staging
restart: unless-stopped
ports:
- "8080:80" # Nginx HTTP (staging)
- "8766:8765" # WebSocket server (staging)
environment:
- NODE_ENV=staging
volumes:
- ./logs-staging:/var/log/nginx
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s

31
docker-entrypoint.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
set -e
echo "Starting Nexus deployment..."
# Start nginx in background
echo "Starting nginx on port 80..."
nginx -g "daemon off;" &
NGINX_PID=$!
# Start Python WebSocket server in background
echo "Starting WebSocket server on port 8765..."
cd /app && python3 server.py &
PYTHON_PID=$!
# Function to handle shutdown
shutdown() {
echo "Shutting down..."
kill $NGINX_PID 2>/dev/null
kill $PYTHON_PID 2>/dev/null
exit 0
}
# Trap SIGTERM and SIGINT
trap shutdown SIGTERM SIGINT
# Wait for any process to exit
wait -n
# Exit with status of process that exited first
exit $?

View File

@@ -2880,7 +2880,7 @@ def main():
# Start world tick system
world_tick_system.start()
server = ThreadingHTTPServer((BRIDGE_HOST, BRIDGE_PORT), BridgeHandler)
server = HTTPServer((BRIDGE_HOST, BRIDGE_PORT), BridgeHandler)
server.serve_forever()

62
nginx.conf Normal file
View File

@@ -0,0 +1,62 @@
server {
listen 80;
server_name nexus.alexanderwhitestone.com;
root /usr/share/nginx/html;
index index.html;
# Enable gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_comp_level 6;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Handle SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
}
# WebSocket proxy for server.py (if needed)
location /ws {
proxy_pass http://localhost:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 86400;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CORS headers for development
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
}
# Redirect HTTP to HTTPS (if SSL is configured)
server {
listen 443 ssl http2;
server_name nexus.alexanderwhitestone.com;
# SSL configuration (commented out for now)
# ssl_certificate /etc/nginx/ssl/nexus.crt;
# ssl_certificate_key /etc/nginx/ssl/nexus.key;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
# For now, redirect to HTTP
return 301 http://$server_name$request_uri;
}

120
setup-vps.sh Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# setup-vps.sh — Initial setup for Ezra VPS
# Run this once on a fresh VPS to prepare for Nexus deployment
set -euo pipefail
echo "Setting up Ezra VPS for Nexus deployment..."
# Update system
echo "Updating system packages..."
apt-get update && apt-get upgrade -y
# Install Docker
echo "Installing Docker..."
if ! command -v docker &> /dev/null; then
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker
systemctl start docker
echo "✅ Docker installed"
else
echo "✅ Docker already installed"
fi
# Install Docker Compose
echo "Installing Docker Compose..."
if ! command -v docker-compose &> /dev/null; then
curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
echo "✅ Docker Compose installed"
else
echo "✅ Docker Compose already installed"
fi
# Install nginx (for reverse proxy if needed)
echo "Installing nginx..."
if ! command -v nginx &> /dev/null; then
apt-get install -y nginx
systemctl enable nginx
systemctl start nginx
echo "✅ Nginx installed"
else
echo "✅ Nginx already installed"
fi
# Configure firewall
echo "Configuring firewall..."
if command -v ufw &> /dev/null; then
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw allow 8765/tcp # WebSocket
ufw allow 3000/tcp # Gitea
ufw --force enable
echo "✅ Firewall configured"
else
echo "⚠️ ufw not available, skipping firewall configuration"
fi
# Create nexus user (optional)
echo "Creating nexus user..."
if ! id -u nexus &>/dev/null; then
useradd -m -s /bin/bash nexus
usermod -aG docker nexus
echo "✅ nexus user created"
else
echo "✅ nexus user already exists"
fi
# Clone repository
echo "Cloning Nexus repository..."
if [ ! -d /home/nexus/the-nexus ]; then
sudo -u nexus git clone http://143.198.27.163:3000/Timmy_Foundation/the-nexus.git /home/nexus/the-nexus
echo "✅ Repository cloned"
else
echo "✅ Repository already exists"
fi
# Set up nginx reverse proxy (optional)
echo "Setting up nginx reverse proxy..."
cat > /etc/nginx/sites-available/nexus << 'EOF'
server {
listen 80;
server_name nexus.alexanderwhitestone.com;
location / {
proxy_pass http://localhost:80;
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;
}
location /ws {
proxy_pass http://localhost:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
}
EOF
ln -sf /etc/nginx/sites-available/nexus /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
echo "✅ Nginx reverse proxy configured"
echo ""
echo "🎉 VPS setup complete!"
echo ""
echo "Next steps:"
echo "1. Point DNS: nexus.alexanderwhitestone.com → $(hostname -I | awk '{print $1}')"
echo "2. Deploy: cd /home/nexus/the-nexus && ./deploy.sh"
echo "3. (Optional) Set up SSL: certbot --nginx -d nexus.alexanderwhitestone.com"
echo ""

View File

@@ -26,17 +26,11 @@ import threading
import hashlib
import os
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
from http.server import HTTPServer, BaseHTTPRequestHandler
from pathlib import Path
from datetime import datetime
from typing import Optional
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
"""Thread-per-request HTTP server."""
daemon_threads = True
# ── Configuration ──────────────────────────────────────────────────────
BRIDGE_PORT = int(os.environ.get('TIMMY_BRIDGE_PORT', 4004))
@@ -280,7 +274,7 @@ def main():
print(f" POST /bridge/move — Move user to room (user_id, room)")
print()
server = ThreadingHTTPServer((BRIDGE_HOST, BRIDGE_PORT), BridgeHandler)
server = HTTPServer((BRIDGE_HOST, BRIDGE_PORT), BridgeHandler)
server.serve_forever()