diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 00000000..92e2b2fb --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,47 @@ +name: Deploy Nexus Preview to Pages + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Prepare static assets + run: | + mkdir -p _site + cp index.html app.js style.css boot.js gofai_worker.js _site/ + cp service-worker.js manifest.json robots.txt help.html _site/ + cp portals.json vision.json _site/ + cp -r nexus/ _site/nexus/ + cp -r icons/ _site/icons/ 2>/dev/null || true + cp -r assets/ _site/assets/ 2>/dev/null || true + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '_site' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/Dockerfile.preview b/Dockerfile.preview new file mode 100644 index 00000000..07e93b1c --- /dev/null +++ b/Dockerfile.preview @@ -0,0 +1,22 @@ +FROM nginx:alpine + +RUN rm /etc/nginx/conf.d/default.conf +COPY preview/nginx.conf /etc/nginx/conf.d/default.conf + +COPY index.html /usr/share/nginx/html/ +COPY app.js /usr/share/nginx/html/ +COPY style.css /usr/share/nginx/html/ +COPY boot.js /usr/share/nginx/html/ +COPY gofai_worker.js /usr/share/nginx/html/ +COPY service-worker.js /usr/share/nginx/html/ +COPY manifest.json /usr/share/nginx/html/ +COPY robots.txt /usr/share/nginx/html/ +COPY help.html /usr/share/nginx/html/ +COPY portals.json /usr/share/nginx/html/ +COPY vision.json /usr/share/nginx/html/ + +COPY nexus/ /usr/share/nginx/html/nexus/ +COPY icons/ /usr/share/nginx/html/icons/ +COPY assets/ /usr/share/nginx/html/assets/ + +EXPOSE 8080 diff --git a/PREVIEW.md b/PREVIEW.md new file mode 100644 index 00000000..14768fe7 --- /dev/null +++ b/PREVIEW.md @@ -0,0 +1,42 @@ +# Nexus Preview Deployment + +Deploy The Nexus to a proper URL so ES module imports work. + +## Problem + +`file://` and raw Forge URLs break ES module imports (CORS + wrong Content-Type). +`boot.js` already detects this: _"Serve this world over HTTP to initialize Three.js."_ + +## Quick Start + +```bash +./preview.sh # http://localhost:8080 (Python, no deps) +./preview.sh docker # http://localhost:8080 (nginx + WS proxy) +docker compose up -d nexus-preview nexus-backend # full stack +``` + +## Deploy Options + +| Method | Command | URL | +|--------|---------|-----| +| Local Python | `./preview.sh` | http://localhost:8080 | +| Docker nginx | `docker compose up nexus-preview` | http://localhost:8080 | +| GitHub Pages | push to main | auto-deploy | +| VPS nginx | copy files + nginx | your-domain.com | + +## Architecture + +``` +Browser ──► nginx :8080 ──► index.html, app.js, style.css, nexus/ + │ + └── /api/world/ws ──► proxy ──► nexus-backend :8765 +``` + +## Files + +| File | Purpose | +|------|---------| +| `Dockerfile.preview` | nginx preview container | +| `preview/nginx.conf` | MIME types + WebSocket proxy | +| `preview.sh` | Python preview server | +| `.github/workflows/pages.yml` | GitHub Pages auto-deploy | diff --git a/deploy.sh b/deploy.sh index 76f1fd3b..1140c418 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,7 +1,9 @@ #!/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 — spin up (or update) the Nexus environment +# Usage: ./deploy.sh — rebuild nexus-main (port 8765) +# ./deploy.sh staging — rebuild nexus-staging (port 8766) +# ./deploy.sh preview — deploy static preview (port 8080) +# ./deploy.sh full — deploy preview + backend set -euo pipefail SERVICE="${1:-nexus-main}" @@ -9,6 +11,21 @@ SERVICE="${1:-nexus-main}" case "$SERVICE" in staging) SERVICE="nexus-staging" ;; main) SERVICE="nexus-main" ;; + preview) + echo "==> Deploying Nexus preview on http://localhost:8080" + docker compose build nexus-preview + docker compose up -d --force-recreate nexus-preview + echo "==> Preview at http://localhost:8080" + exit 0 + ;; + full) + echo "==> Deploying full Nexus stack (preview + backend)" + docker compose build nexus-preview nexus-backend + docker compose up -d --force-recreate nexus-preview nexus-backend + echo "==> Preview: http://localhost:8080" + echo "==> Backend WS: nexus-backend:8765" + exit 0 + ;; esac echo "==> Deploying $SERVICE …" diff --git a/docker-compose.yml b/docker-compose.yml index ab351d5c..209aff9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,28 @@ services: restart: unless-stopped ports: - "8765:8765" + nexus-staging: build: . container_name: nexus-staging restart: unless-stopped ports: - - "8766:8765" \ No newline at end of file + - "8766:8765" + + nexus-backend: + build: . + container_name: nexus-backend + restart: unless-stopped + expose: + - "8765" + + nexus-preview: + build: + context: . + dockerfile: Dockerfile.preview + container_name: nexus-preview + restart: unless-stopped + ports: + - "8080:8080" + depends_on: + - nexus-backend diff --git a/preview.sh b/preview.sh new file mode 100755 index 00000000..e1b949ea --- /dev/null +++ b/preview.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# preview.sh — One-command preview server for The Nexus +# Serves static files with correct MIME types so ES modules load. +# +# Usage: +# ./preview.sh — http://localhost:8080 +# ./preview.sh 3000 — http://localhost:3000 +# ./preview.sh docker — Docker/nginx instead + +set -euo pipefail + +PORT="${1:-8080}" + +if [ "$PORT" = "docker" ]; then + echo "==> Starting Nexus preview via Docker/nginx..." + docker compose up -d nexus-preview + echo "==> Preview at http://localhost:8080" + exit 0 +fi + +if ! command -v python3 &> /dev/null; then + echo "Error: python3 not found. Use './preview.sh docker' instead." + exit 1 +fi + +echo "==> Nexus preview on http://localhost:$PORT" +echo "==> Ctrl+C to stop" +echo "" + +python3 -c " +import http.server, socketserver + +PORT = $PORT + +class Handler(http.server.SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header('Access-Control-Allow-Origin', '*') + super().end_headers() + + def guess_type(self, path): + if path.endswith('.js') or path.endswith('.mjs'): + return 'application/javascript' + if path.endswith('.css'): + return 'text/css' + if path.endswith('.json'): + return 'application/json' + return super().guess_type(path) + +with socketserver.TCPServer(('', PORT), Handler) as httpd: + print(f'Serving at http://localhost:{PORT}') + httpd.serve_forever() +" diff --git a/preview/nginx.conf b/preview/nginx.conf new file mode 100644 index 00000000..c4cc03d0 --- /dev/null +++ b/preview/nginx.conf @@ -0,0 +1,57 @@ +server { + listen 8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Serve static assets with correct MIME types + location / { + try_files $uri $uri/ /index.html; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + } + + # ES modules need correct Content-Type + location ~* \.js$ { + types { application/javascript js; } + add_header Cache-Control "public, max-age=3600"; + } + + location ~* \.css$ { + types { text/css css; } + add_header Cache-Control "public, max-age=3600"; + } + + location ~* \.json$ { + types { application/json json; } + add_header Cache-Control "no-cache"; + } + + # Proxy WebSocket to backend (app.js connects to /api/world/ws) + location /api/world/ws { + proxy_pass http://nexus-backend: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_read_timeout 86400; + } + + # Direct WebSocket proxy + location /ws { + proxy_pass http://nexus-backend:8765; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 86400; + } + + # Health check + location /health { + return 200 '{"status":"ok","service":"nexus-preview"}'; + add_header Content-Type application/json; + } +}