From 8c5deb74e8c32b7c457b2d8b51be070d8d2530f7 Mon Sep 17 00:00:00 2001 From: buenosairesam Date: Tue, 27 Jan 2026 02:26:03 -0300 Subject: [PATCH] fixed network issue with multiple managed rooms --- .gitignore | 4 +- cfg/.gitignore | 6 +- cfg/sample/config.json | 15 ++ cfg/sample/ctrl/start.sh | 28 +++ cfg/sample/ctrl/stop.sh | 22 +++ cfg/sample/sample/.env | 15 ++ cfg/sample/sample/docker-compose.yml | 19 ++ cfg/sample/sample/index.html | 35 ++++ cfg/sample/soleprint/.env | 40 +++++ cfg/sample/soleprint/docker-compose.yml | 36 ++++ cfg/standalone/soleprint/docker-compose.yml | 3 + docs/architecture/05-sidebar-injection.md | 181 ++++++++++++++++++++ 12 files changed, 401 insertions(+), 3 deletions(-) create mode 100644 cfg/sample/config.json create mode 100755 cfg/sample/ctrl/start.sh create mode 100755 cfg/sample/ctrl/stop.sh create mode 100644 cfg/sample/sample/.env create mode 100644 cfg/sample/sample/docker-compose.yml create mode 100644 cfg/sample/sample/index.html create mode 100644 cfg/sample/soleprint/.env create mode 100644 cfg/sample/soleprint/docker-compose.yml create mode 100644 docs/architecture/05-sidebar-injection.md diff --git a/.gitignore b/.gitignore index 339ff2b..6140377 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ venv/ gen/ # Room configurations (separate repo - contains credentials and room-specific data) -# Keep cfg/standalone/ as sample, ignore actual rooms +# Keep cfg/standalone/ and cfg/sample/ as templates, ignore actual rooms cfg/amar/ cfg/dlt/ +# Add new rooms here as they are created +# cfg// diff --git a/cfg/.gitignore b/cfg/.gitignore index 4db29b3..8dcd0a1 100644 --- a/cfg/.gitignore +++ b/cfg/.gitignore @@ -1,13 +1,15 @@ # Environment files with credentials (use .env.example as template) **/.env +!sample/**/.env # Database dumps (sensitive data) -*/dumps/*.sql +**/dumps/*.sql # Python __pycache__/ *.pyc *.pyo -# Standalone is kept in main soleprint repo as sample +# These are kept in main soleprint repo as templates standalone/ +sample/ diff --git a/cfg/sample/config.json b/cfg/sample/config.json new file mode 100644 index 0000000..49a1cca --- /dev/null +++ b/cfg/sample/config.json @@ -0,0 +1,15 @@ +{ + "managed": { + "name": "sample", + "repos": { + "frontend": "/path/to/your/frontend/repo", + "backend": "/path/to/your/backend/repo" + } + }, + "framework": { + "name": "soleprint" + }, + "auth": { + "enabled": false + } +} diff --git a/cfg/sample/ctrl/start.sh b/cfg/sample/ctrl/start.sh new file mode 100755 index 0000000..bc40696 --- /dev/null +++ b/cfg/sample/ctrl/start.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Start all sample services +# Usage: ./ctrl/start.sh [-d] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +DETACH="" +if [[ "$1" == "-d" ]]; then + DETACH="-d" +fi + +echo "=== Starting sample services ===" + +# Start soleprint +echo "Starting soleprint..." +cd "$ROOT_DIR/soleprint" +docker compose up $DETACH & + +# Start sample app +if [[ -f "$ROOT_DIR/sample/docker-compose.yml" ]]; then + echo "Starting sample app..." + cd "$ROOT_DIR/sample" + docker compose up $DETACH & +fi + +wait +echo "=== All services started ===" diff --git a/cfg/sample/ctrl/stop.sh b/cfg/sample/ctrl/stop.sh new file mode 100755 index 0000000..d45169b --- /dev/null +++ b/cfg/sample/ctrl/stop.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Stop all sample services +# Usage: ./ctrl/stop.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +echo "=== Stopping sample services ===" + +# Stop sample app +if [[ -f "$ROOT_DIR/sample/docker-compose.yml" ]]; then + echo "Stopping sample app..." + cd "$ROOT_DIR/sample" + docker compose down +fi + +# Stop soleprint +echo "Stopping soleprint..." +cd "$ROOT_DIR/soleprint" +docker compose down + +echo "=== All services stopped ===" diff --git a/cfg/sample/sample/.env b/cfg/sample/sample/.env new file mode 100644 index 0000000..d3032ae --- /dev/null +++ b/cfg/sample/sample/.env @@ -0,0 +1,15 @@ +# ============================================================================= +# Sample Managed App - Environment Configuration +# ============================================================================= +# Copy this to cfg///.env and customize + +# ============================================================================= +# DEPLOYMENT +# ============================================================================= +DEPLOYMENT_NAME=sample +NETWORK_NAME=sample_network + +# ============================================================================= +# PORTS +# ============================================================================= +FRONTEND_PORT=3020 diff --git a/cfg/sample/sample/docker-compose.yml b/cfg/sample/sample/docker-compose.yml new file mode 100644 index 0000000..596122c --- /dev/null +++ b/cfg/sample/sample/docker-compose.yml @@ -0,0 +1,19 @@ +# Sample Mock Frontend +# Simple nginx serving static HTML +# +# For a real app, customize this with your actual services + +services: + frontend: + image: nginx:alpine + container_name: ${DEPLOYMENT_NAME}_frontend + volumes: + - ./index.html:/usr/share/nginx/html/index.html:ro + ports: + - "${FRONTEND_PORT}:80" + networks: + - default + +networks: + default: + name: ${NETWORK_NAME} diff --git a/cfg/sample/sample/index.html b/cfg/sample/sample/index.html new file mode 100644 index 0000000..90bf11d --- /dev/null +++ b/cfg/sample/sample/index.html @@ -0,0 +1,35 @@ + + + + + + Sample App + + + +
+

Sample App

+

This is a sample managed room for Soleprint.

+

Replace this with your actual frontend.

+
+ + diff --git a/cfg/sample/soleprint/.env b/cfg/sample/soleprint/.env new file mode 100644 index 0000000..2e18aad --- /dev/null +++ b/cfg/sample/soleprint/.env @@ -0,0 +1,40 @@ +# ============================================================================= +# SOLEPRINT - Sample Room Configuration +# ============================================================================= +# Copy this to cfg//soleprint/.env and customize + +# ============================================================================= +# DEPLOYMENT +# ============================================================================= +DEPLOYMENT_NAME=sample_spr + +# ============================================================================= +# NETWORK (unique per room to allow concurrent operation) +# ============================================================================= +NETWORK_NAME=sample_network + +# ============================================================================= +# PORTS (choose unique ports for each room) +# ============================================================================= +SOLEPRINT_PORT=12020 + +# ============================================================================= +# GOOGLE OAUTH (optional - for auth) +# ============================================================================= +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_REDIRECT_URI=http://sample.spr.local.ar/spr/artery/google/oauth/callback + +# ============================================================================= +# AUTH +# ============================================================================= +AUTH_SESSION_SECRET=change-this-in-production + +# ============================================================================= +# DATABASE (optional - if your app uses a database) +# ============================================================================= +DB_HOST=sample_db +DB_PORT=5432 +DB_NAME=sampledb +DB_USER=postgres +DB_PASSWORD=localdev123 diff --git a/cfg/sample/soleprint/docker-compose.yml b/cfg/sample/soleprint/docker-compose.yml new file mode 100644 index 0000000..254e035 --- /dev/null +++ b/cfg/sample/soleprint/docker-compose.yml @@ -0,0 +1,36 @@ +# Soleprint Services - Docker Compose +# +# Runs soleprint hub as a single service +# Artery, atlas, station are accessed via path-based routing +# +# Usage: +# cd gen//soleprint && docker compose up -d + +name: ${DEPLOYMENT_NAME} + +services: + soleprint: + build: + context: . + dockerfile: Dockerfile + container_name: ${DEPLOYMENT_NAME} + user: "${UID:-1000}:${GID:-1000}" + volumes: + - .:/app + ports: + - "${SOLEPRINT_PORT}:8000" + env_file: + - .env + environment: + # For single-port mode, all subsystems are internal routes + - ARTERY_EXTERNAL_URL=/artery + - ATLAS_EXTERNAL_URL=/atlas + - STATION_EXTERNAL_URL=/station + networks: + - default + # Use run.py for single-port bare-metal mode + command: uvicorn run:app --host 0.0.0.0 --port 8000 --reload + +networks: + default: + name: ${NETWORK_NAME} diff --git a/cfg/standalone/soleprint/docker-compose.yml b/cfg/standalone/soleprint/docker-compose.yml index dc6d9e9..44d57d9 100644 --- a/cfg/standalone/soleprint/docker-compose.yml +++ b/cfg/standalone/soleprint/docker-compose.yml @@ -5,12 +5,15 @@ # Usage: # cd gen/standalone && docker compose up -d +name: soleprint_standalone + services: soleprint: build: context: . dockerfile: Dockerfile container_name: soleprint + user: "${UID:-1000}:${GID:-1000}" volumes: - .:/app ports: diff --git a/docs/architecture/05-sidebar-injection.md b/docs/architecture/05-sidebar-injection.md new file mode 100644 index 0000000..b9f2c12 --- /dev/null +++ b/docs/architecture/05-sidebar-injection.md @@ -0,0 +1,181 @@ +# Sidebar Injection Architecture + +## Overview + +The soleprint sidebar is injected into managed app pages using a hybrid nginx + JavaScript approach. This allows any frontend framework (React, Next.js, static HTML) to receive the sidebar without code modifications. + +## How It Works + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Browser Request │ +│ http://room.spr.local.ar/ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Nginx │ +│ │ +│ 1. Routes /spr/* → soleprint:PORT (sidebar assets + API) │ +│ 2. Routes /* → frontend:PORT (app pages) │ +│ 3. Injects CSS+JS into HTML responses via sub_filter │ +│ │ +│ sub_filter '' │ +│ ' │ +│ '; │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Browser Renders │ +│ │ +│ 1. Page loads with injected CSS (sidebar styles ready) │ +│ 2. sidebar.js executes (deferred, after DOM ready) │ +│ 3. JS fetches /spr/api/sidebar/config (room name, auth, etc) │ +│ 4. JS creates sidebar DOM elements and injects into page │ +│ 5. Sidebar appears on left side, pushes content with margin │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Key Design Decisions + +### Why nginx sub_filter instead of modifying app code? + +- **Framework agnostic**: Works with any frontend (Next.js, React, Vue, static HTML) +- **No app changes needed**: The managed app doesn't need to know about soleprint +- **Easy to disable**: Just access `room.local.ar` instead of `room.spr.local.ar` + +### Why inject into `` instead of ``? + +Next.js and other streaming SSR frameworks may not include `` in the initial HTML response. The `` tag is always present, so we inject both CSS and JS there using `defer` to ensure JS runs after DOM is ready. + +### Why JavaScript injection instead of iframe? + +- **No iframe isolation issues**: Sidebar can interact with page if needed +- **Better UX**: No double scrollbars, native feel +- **Simpler CSS**: Just push content with `margin-left` + +## Nginx Configuration + +### For Local Development (system nginx) + +Each room needs entries in `/etc/nginx/sites-enabled/`: + +```nginx +# room.spr.local.ar - app with sidebar +server { + listen 80; + server_name room.spr.local.ar; + + # Soleprint assets and API + location /spr/ { + proxy_pass http://127.0.0.1:SOLEPRINT_PORT/; + 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; + } + + # Backend API (if applicable) + location /api/ { + proxy_pass http://127.0.0.1:BACKEND_PORT/api/; + # ... headers + } + + # Frontend with sidebar injection + location / { + proxy_pass http://127.0.0.1:FRONTEND_PORT; + proxy_set_header Host $host; + proxy_set_header Accept-Encoding ""; # Required for sub_filter + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Inject sidebar + sub_filter '' ''; + sub_filter_once off; + sub_filter_types text/html; + } +} + +# room.local.ar - app without sidebar (direct access) +server { + listen 80; + server_name room.local.ar; + # ... same as above but without sub_filter +} +``` + +### For Docker/AWS (nginx container) + +The nginx config lives in `cfg//soleprint/nginx/local.conf` and uses docker service names instead of localhost ports: + +```nginx +location /spr/ { + proxy_pass http://soleprint:8000/spr/; +} + +location / { + proxy_pass http://frontend:3000; + # ... sub_filter same as above +} +``` + +## Port Allocation + +Each room should use unique ports to allow concurrent operation: + +| Room | Soleprint | Frontend | Backend | +|--------|-----------|----------|---------| +| amar | 12000 | 3000 | 8001 | +| dlt | 12010 | 3010 | - | +| sample | 12020 | 3020 | 8020 | + +## Sidebar Assets + +The sidebar consists of two files served by soleprint: + +- `/sidebar.css` - Styles for the sidebar (dark theme, positioning) +- `/sidebar.js` - Self-contained JS that fetches config and renders sidebar + +Source location: `soleprint/station/tools/sbwrapper/` + +## Sidebar Config API + +The sidebar JS fetches configuration from `/spr/api/sidebar/config`: + +```json +{ + "room": "amar", + "soleprint_base": "/spr", + "auth_enabled": true, + "tools": { + "artery": "/spr/artery", + "atlas": "/spr/atlas", + "station": "/spr/station" + }, + "auth": { + "login_url": "/spr/artery/google/oauth/login", + "logout_url": "/spr/artery/google/oauth/logout" + } +} +``` + +## Troubleshooting + +### Sidebar not appearing + +1. Check if soleprint is running: `curl http://room.spr.local.ar/spr/sidebar.js` +2. Check browser console for `[Soleprint]` messages +3. Verify nginx has `Accept-Encoding ""` set (required for sub_filter) +4. Hard refresh (Ctrl+Shift+R) to clear cached HTML + +### Wrong room config showing + +Each room needs its own docker network (`room_network`) to isolate services. Check `NETWORK_NAME` in `.env` files and ensure containers are on correct networks. + +### sub_filter not working + +- Ensure `proxy_set_header Accept-Encoding ""` is set +- Check that response is `text/html` (sub_filter_types) +- Streaming SSR may not have ``, use `` injection instead