From 4f97e27f698874070b419956d121cda27555072e Mon Sep 17 00:00:00 2001 From: defiQUG Date: Thu, 26 Feb 2026 22:35:24 -0800 Subject: [PATCH] MIM4U: nginx install/deploy/backup scripts, rate limit, CSP, docs; submodule pointer; txpool retry script Made-with: Cursor --- config/ip-addresses.conf | 5 + docs/04-configuration/ALL_VMIDS_ENDPOINTS.md | 18 +- .../MIM4U_502_ERROR_RESOLUTION.md | 54 ++++- .../MIM4U_FIRST_PARTY_ANALYTICS.md | 49 ++++ .../04-configuration/MIM4U_SESSION_COOKIES.md | 30 +++ .../MIM4U_UX_UI_TECHNICAL_REVIEW.md | 219 ++++++++++++++++++ .../NPMPLUS_SERVICE_MAPPING_COMPLETE.md | 3 +- miracles_in_motion | 2 +- ...-mirror-and-pmm-pool-after-txpool-clear.sh | 147 ++++++++++++ scripts/mim4u-backup-7810.sh | 21 ++ scripts/mim4u-deploy-to-7810.sh | 23 ++ scripts/mim4u-install-nginx-and-fix-502.sh | 101 ++++++++ .../update-npmplus-proxy-hosts-api.sh | 3 +- 13 files changed, 656 insertions(+), 19 deletions(-) create mode 100644 docs/04-configuration/MIM4U_FIRST_PARTY_ANALYTICS.md create mode 100644 docs/04-configuration/MIM4U_SESSION_COOKIES.md create mode 100644 docs/04-configuration/MIM4U_UX_UI_TECHNICAL_REVIEW.md create mode 100755 scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh create mode 100755 scripts/mim4u-backup-7810.sh create mode 100755 scripts/mim4u-deploy-to-7810.sh create mode 100755 scripts/mim4u-install-nginx-and-fix-502.sh diff --git a/config/ip-addresses.conf b/config/ip-addresses.conf index 6cb84a4..aab8f9b 100644 --- a/config/ip-addresses.conf +++ b/config/ip-addresses.conf @@ -4,6 +4,9 @@ # See: docs/11-references/NETWORK_CONFIGURATION_MASTER.md # Optional: source PROJECT_ROOT/.env first to override (scripts should: source .env 2>/dev/null; source this file) +# Proxmox SSH user for shell access (use root). .env may set PROXMOX_USER=root@pam for API; that is not valid for SSH. +PROXMOX_SSH_USER="${PROXMOX_SSH_USER:-root}" + # Proxmox Hosts (overridable via .env PROXMOX_ML110, PROXMOX_R630_01, PROXMOX_R630_02) PROXMOX_HOST_ML110="${PROXMOX_ML110:-${PROXMOX_HOST_ML110:-192.168.11.10}}" PROXMOX_HOST_R630_01="${PROXMOX_R630_01:-${PROXMOX_HOST_R630_01:-192.168.11.11}}" @@ -81,6 +84,8 @@ DBIS_REDIS_IP="192.168.11.120" # source "$(dirname "$0")/../config/ip-addresses.conf" IP_OMADA="192.168.11.20" IP_MIM_WEB="192.168.11.37" +# MIM4U API backend (VMID 7811) — used by nginx on 7810 for /api/ proxy +MIM_API_IP="192.168.11.36" DB_HOST="192.168.11.53" IP_NPMPLUS_ETH0="192.168.11.166" # NPMplus Alltra/HYBX (VMID 10235) - see docs/04-configuration/NPMPLUS_ALLTRA_HYBX_MASTER_PLAN.md diff --git a/docs/04-configuration/ALL_VMIDS_ENDPOINTS.md b/docs/04-configuration/ALL_VMIDS_ENDPOINTS.md index 2150d2d..a5860f9 100644 --- a/docs/04-configuration/ALL_VMIDS_ENDPOINTS.md +++ b/docs/04-configuration/ALL_VMIDS_ENDPOINTS.md @@ -219,12 +219,12 @@ The following VMIDs have been permanently removed: | 7811 | 192.168.11.36 | mim-api-1 | ✅ Running | Web: 80, 443, API: Various | MIM4U service (web + API) | **Public Domains** (NPMplus config): -- `mim4u.org` → Routes to `http://192.168.11.36:80` (VMID 7811) -- `www.mim4u.org` → Redirects to `mim4u.org` (via NPMplus redirect) -- `secure.mim4u.org` → Routes to `http://192.168.11.36:80` (VMID 7811) -- `training.mim4u.org` → Routes to `http://192.168.11.36:80` (VMID 7811) +- `mim4u.org` → Routes to `http://192.168.11.37:80` (VMID 7810 mim-web-1) +- `www.mim4u.org` → Routes to `http://192.168.11.37:80` (VMID 7810; optional NPMplus redirect www → apex) +- `secure.mim4u.org` → Routes to `http://192.168.11.37:80` (VMID 7810) +- `training.mim4u.org` → Routes to `http://192.168.11.37:80` (VMID 7810) -**Note**: All MIM4U domains route to VMID 7811 at 192.168.11.36. `www.mim4u.org` redirects to `mim4u.org` to save on proxy host configurations. +**Note**: All MIM4U domains route to VMID 7810 (mim-web-1) at 192.168.11.37. nginx on 7810 proxies `/api/` to VMID 7811 (192.168.11.36:3001). --- @@ -467,10 +467,10 @@ This section lists all endpoints that should be configured in NPMplus, extracted | `dbis-api-2.d-bis.org` | `192.168.11.156` | `http` | `3000` | ❌ No | DBIS API Secondary (VMID 10151) | | `secure.d-bis.org` | `192.168.11.130` | `http` | `80` | ❌ No | DBIS Secure Portal (VMID 10130) - Path-based routing | | **MIM4U Services** | -| `mim4u.org` | `192.168.11.36` | `http` | `80` | ❌ No | MIM4U Main Site (VMID 7811) | -| `www.mim4u.org` | `Redirect` | `-` | `-` | ❌ No | Redirects to `mim4u.org` (no separate proxy host) | -| `secure.mim4u.org` | `192.168.11.36` | `http` | `80` | ❌ No | MIM4U Secure Portal (VMID 7811) | -| `training.mim4u.org` | `192.168.11.36` | `http` | `80` | ❌ No | MIM4U Training Portal (VMID 7811) | +| `mim4u.org` | `192.168.11.37` | `http` | `80` | ❌ No | MIM4U Main Site (VMID 7810 mim-web-1) | +| `www.mim4u.org` | `192.168.11.37` | `http` | `80` | ❌ No | MIM4U (VMID 7810; optional redirect www → apex) | +| `secure.mim4u.org` | `192.168.11.37` | `http` | `80` | ❌ No | MIM4U Secure Portal (VMID 7810) | +| `training.mim4u.org` | `192.168.11.37` | `http` | `80` | ❌ No | MIM4U Training Portal (VMID 7810) | | **Sankofa Phoenix Services** | | `sankofa.nexus` | `192.168.11.51` | `http` | `3000` | ❌ No | Sankofa Portal - Company Website (VMID 7801) ✅ **Deployed** | | `www.sankofa.nexus` | `192.168.11.51` | `http` | `3000` | ❌ No | Sankofa Portal (VMID 7801) ✅ **Deployed** | diff --git a/docs/04-configuration/MIM4U_502_ERROR_RESOLUTION.md b/docs/04-configuration/MIM4U_502_ERROR_RESOLUTION.md index 70c95f0..0b69c08 100644 --- a/docs/04-configuration/MIM4U_502_ERROR_RESOLUTION.md +++ b/docs/04-configuration/MIM4U_502_ERROR_RESOLUTION.md @@ -47,6 +47,17 @@ The container needs nginx installed and running to serve the web application. +**Option A — run the fix script (recommended):** From a host that can SSH to the Proxmox node (r630-02): + +```bash +./scripts/mim4u-install-nginx-and-fix-502.sh +# Or dry-run: ./scripts/mim4u-install-nginx-and-fix-502.sh --dry-run +``` + +The script installs nginx, writes the security-enabled config (including rate limits in `/etc/nginx/conf.d/mim4u-rate-limit.conf` and the default site), ensures `/var/www/html` exists, and reloads nginx. + +**Option B — manual steps:** + ```bash # SSH to Proxmox host ssh root@192.168.11.12 @@ -68,16 +79,17 @@ Check what IP NPMplus is routing to: 1. **Access NPMplus Web UI**: - URL: `https://192.168.0.166:81` or `https://192.168.11.166:81` - - Navigate to: Proxy Hosts → `mim4u.org` + - Navigate to: Proxy Hosts → `mim4u.org` (and `www.mim4u.org`) 2. **Verify Configuration**: - Forward Hostname/IP: Should be `192.168.11.37` - Forward Port: Should be `80` - Forward Scheme: Should be `http` + - **www.mim4u.org**: Same backend so both apex and www work. Optional: create a Redirect host for `www.mim4u.org` → `https://mim4u.org` in NPMplus to canonicalize to apex. -3. **If incorrect, update to**: - - Forward Hostname/IP: `192.168.11.37` - - Forward Port: `80` +3. **Security (HSTS / SSL)**: + - Enable **SSL** and **Force SSL** for mim4u.org (and www, secure, training) so HTTPS is used. + - Enable **HSTS** in the proxy host’s SSL tab so browsers get `Strict-Transport-Security`. NPMplus adds the header when terminating SSL. ### Step 3: Deploy MIM4U Web Application (When Ready) @@ -97,22 +109,31 @@ If you need the site working immediately, you can: 2. **Configure basic nginx** to serve a default page: ```bash -# Basic nginx config (temporary) +# Basic nginx config (temporary) — server_name includes www so www.mim4u.org works; security headers (HSTS when on HTTPS via NPMplus, CSP) pct exec 7810 -- bash -c 'cat > /etc/nginx/sites-available/default << EOF server { listen 80; - server_name _; + server_name mim4u.org www.mim4u.org secure.mim4u.org training.mim4u.org _; root /var/www/html; index index.html; + + # Security headers (HSTS added by NPMplus when terminating SSL; CSP reduces XSS) + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\'' '\''unsafe-eval'\''; style-src '\''self'\'' '\''unsafe-inline'\'' https://fonts.googleapis.com; font-src '\''self'\'' https://fonts.gstatic.com; img-src '\''self'\'' data: https:; connect-src '\''self'\'' https://mim4u.org; frame-ancestors '\''self'\'';" always; location / { - try_files \$uri \$uri/ =404; + try_files \$uri \$uri/ /index.html =404; } location /api/ { proxy_pass http://192.168.11.36:3001; 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' @@ -148,5 +169,24 @@ curl -I https://mim4u.org/ --- +## Optional: Rate limiting for /api/ + +To rate-limit `/api/` at nginx, the `limit_req_zone` directive must live in the **http** block (e.g. in `/etc/nginx/nginx.conf`), not in the server block. Example: + +```nginx +# In http { ... } in nginx.conf +limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/m; +``` + +Then in your server block (or in the snippet above), inside `location /api/` add: + +```nginx +limit_req zone=api_limit burst=5 nodelay; +``` + +Alternatively, use Cloudflare WAF rate limiting in front of mim4u.org (see [CLOUDFLARE_SETUP](../../miracles_in_motion/docs/deployment/CLOUDFLARE_SETUP.md)). + +--- + **Last Updated**: 2026-01-18 **Status**: ⚠️ nginx installation needed on VMID 7810 diff --git a/docs/04-configuration/MIM4U_FIRST_PARTY_ANALYTICS.md b/docs/04-configuration/MIM4U_FIRST_PARTY_ANALYTICS.md new file mode 100644 index 0000000..a9395ea --- /dev/null +++ b/docs/04-configuration/MIM4U_FIRST_PARTY_ANALYTICS.md @@ -0,0 +1,49 @@ +# MIM4U First-Party Analytics Endpoint + +**Purpose:** Allow key events (page views, CTA clicks, form submissions) to be measured even when ad blockers block Google Analytics. The frontend sends a copy of each event to a first-party endpoint. + +## Frontend behavior + +When cookie consent is **accept**, the app sends events to: + +- **Google Analytics** (when `gtag` is available) +- **First-party fallback:** `POST /api/events` (fire-and-forget, `keepalive: true`) + +Request body is a JSON object: + +```json +{ + "event": "page_view", + "properties": { + "page": "/", + "title": "Miracles In Motion | ...", + "timestamp": "2026-02-26T12:00:00.000Z", + "url": "https://mim4u.org/" + }, + "userId": null +} +``` + +Example event names: `page_view`, `cta_clicked`, `donation_completed`, `form_submission`, `volunteer_signup`. + +## Backend (API on VMID 7811) + +The API behind `/api/` (proxied from 7810 to 7811) should implement: + +**`POST /api/events`** + +- **Auth:** Optional; if you require auth, use a short-lived token or same-origin only (no CORS for other origins). +- **Body:** JSON as above. +- **Response:** `204 No Content` or `200 OK` (no body required). +- **Side effects:** Log to disk, forward to a server-side analytics pipeline (e.g. GA4 Measurement Protocol, Plausible, or internal DB). Do not expose PII in logs unless compliant with privacy policy. + +If the endpoint is not implemented, the frontend `fetch` will 404; the app ignores failures and continues. + +## Rate limiting + +Nginx on 7810 applies `limit_req zone=api burst=5 nodelay` to `/api/`. Ensure `/api/events` is included in that path so abuse is limited. + +## See also + +- [MIM4U_502_ERROR_RESOLUTION.md](./MIM4U_502_ERROR_RESOLUTION.md) — nginx and proxy +- [MIM4U_UX_UI_TECHNICAL_REVIEW.md](./MIM4U_UX_UI_TECHNICAL_REVIEW.md) — analytics recommendations diff --git a/docs/04-configuration/MIM4U_SESSION_COOKIES.md b/docs/04-configuration/MIM4U_SESSION_COOKIES.md new file mode 100644 index 0000000..8aec80d --- /dev/null +++ b/docs/04-configuration/MIM4U_SESSION_COOKIES.md @@ -0,0 +1,30 @@ +# MIM4U Portals — Session Cookie Configuration + +**Purpose:** Ensure Portals (and any auth) use secure session cookies: `HttpOnly`, `Secure`, `SameSite`. + +## Where to configure + +- **Azure Static Web Apps / Entra (Azure AD):** If MIM4U uses Azure for auth, session cookies are typically set by the platform. In Azure Portal → App registrations → your app → Authentication, ensure: + - Redirect URIs use `https://mim4u.org` (and `https://secure.mim4u.org` if used). + - Implicit grant and legacy options are off unless required. +- **Custom API (VMID 7811):** If the API issues its own session cookies (e.g. JWT in cookie or session id), set when setting the cookie: + - `HttpOnly=true` + - `Secure=true` (only over HTTPS) + - `SameSite=Lax` or `Strict` + - `Path=/` or the minimal path needed + +## Example (Node/Express-style) + +```javascript +res.cookie('session', token, { + httpOnly: true, + secure: true, + sameSite: 'lax', + path: '/', + maxAge: 24 * 60 * 60 * 1000 +}) +``` + +## NPMplus / nginx + +NPMplus terminates SSL; it does not set application session cookies. Cookie flags are set by the application (Azure or API on 7811). diff --git a/docs/04-configuration/MIM4U_UX_UI_TECHNICAL_REVIEW.md b/docs/04-configuration/MIM4U_UX_UI_TECHNICAL_REVIEW.md new file mode 100644 index 0000000..88ccb30 --- /dev/null +++ b/docs/04-configuration/MIM4U_UX_UI_TECHNICAL_REVIEW.md @@ -0,0 +1,219 @@ +# MIM4U (mim4u.org) — Technical UX/UI Review & Punch List + +**Last Updated:** 2026-02-26 +**Document Version:** 1.0 +**Status:** Active — Dev handoff +**Context:** Hosted LXC content — VMID 7810 (mim-web-1), VMID 7811 (mim-api-1), NPMplus proxy + +--- + +## Scope + +This document captures a technical UX/UI review of **mim4u.org** (Miracles in Motion) based on publicly indexable content. The site is largely JS-rendered (client-rendered), so the review focuses on patterns that commonly affect UX on modern marketing/nonprofit builds. It is intended as a **prioritized punch list** for the dev team working on the **miracles_in_motion** codebase and the LXC-hosted deployment. + +**Hosting (this repo):** + +| Asset | Detail | +|-------|--------| +| **Web frontend** | VMID 7810 (mim-web-1) — Nginx + static/SPA | +| **API backend** | VMID 7811 (mim-api-1) — Azure Functions (port 3001) | +| **Domains** | mim4u.org, www.mim4u.org, secure.mim4u.org, training.mim4u.org | +| **Proxy** | NPMplus → `http://192.168.11.37:80` (see [NPMPLUS_SERVICE_MAPPING_COMPLETE.md](./NPMPLUS_SERVICE_MAPPING_COMPLETE.md)) | +| **App source** | Submodule: [miracles_in_motion](https://github.com/Order-of-Hospitallers/miracles_in_motion) | + +**Related docs:** + +- [MIM4U_502_ERROR_RESOLUTION.md](./MIM4U_502_ERROR_RESOLUTION.md) — 502 resolution (nginx/service on 7810) +- [MIM4U_FIRST_PARTY_ANALYTICS.md](./MIM4U_FIRST_PARTY_ANALYTICS.md) — First-party `/api/events` endpoint for ad-blocker-resistant analytics +- [NPMPLUS_SERVICE_MAPPING_COMPLETE.md](./NPMPLUS_SERVICE_MAPPING_COMPLETE.md) — Domain → VMID mapping + +--- + +## What’s Working (UX That’s Technically Sound) + +- **Clear information architecture + task-first nav**: Top-level items (Stories, Volunteers, Corporate, Get Help, Portals, Donate) map cleanly to user intents. +- **Strong primary CTAs above the fold**: Donate now, Volunteer, Read stories — reduce decision friction and support conversion. +- **Trust + transparency blocks**: “Where your donation goes (85/10/5)”, “Average grant: $48 / $72”, partners list, impact-report mention — good for credibility and SEO when implemented as real text (not only images). +- **Process clarity**: “Designed with counselors… verified same-day… approved within 24 hours… no uploads required” — step-by-step flow that can reduce form abandonment. + +--- + +## Technical UX Risks (Common on Client-Rendered Builds) + +### 1) Performance (Core Web Vitals) + +- **LCP risk**: Hero sections often use large background images/video + web fonts; unoptimized they cause “blank screen” lag on mobile/slow networks. +- **CLS risk**: Late-loading fonts, cookie banners, or impact counters can shift layout (conversion-negative). +- **INP risk**: Heavy script bundles (analytics, donation widgets, sliders) can delay taps/scroll. + +**Actions:** + +- Hero image: responsive (`srcset`), compressed, and preloaded if it’s the LCP element. +- Defer non-critical JS (chat, A/B, extra trackers). +- Use `font-display: swap` and consider self-hosting fonts. + +### 2) Accessibility (WCAG/ADA) + +From the visible structure (multiple CTAs, sections, cookie banner): + +- Keyboard: full tab flow through Donate / Get Help / Portals without traps. +- Visible focus states on links/buttons (especially CTAs). +- One H1, logical H2/H3. +- Color contrast on hero text/buttons. +- Cookie banner: fully operable by keyboard + screen reader; no blocking without focus management. + +**Actions:** + +- Add “Skip to content” link. +- Ensure CTAs have accessible names (avoid repeated “Learn more” without context). +- ARIA labels on nav/menu toggles (mobile). + +### 3) Forms / “Get Help” Workflow + +Copy promises speed and dignity (“one-page referral, no uploads required”). Technically this depends on: + +- Inline, specific validation (not generic red banners). +- Auto-save / resilience on mobile. +- Spam protection that doesn’t punish legitimate users (avoid hostile CAPTCHAs). +- Confirmation loop (“we confirm support reached the student”) — ensure privacy disclosures reflect data handling. + +### 4) Security & Privacy (Especially “Portals”) + +Portals imply authenticated access: + +- HTTPS everywhere + HSTS. +- Strong session cookie flags: `HttpOnly`, `Secure`, `SameSite`. +- Content Security Policy (CSP) to reduce XSS (e.g. from embedded donation tools). +- No PII in URLs/query strings (referrals, student needs). +- Cookie choices must persist; don’t load marketing tags before consent. + +### 5) SEO + Discoverability + +Key content is client-rendered; indexing can be inconsistent. Homepage text is indexable — maintain and improve: + +- Prefer SSR or static generation for public pages where feasible. +- Clean metadata (title/description per page/section). +- Structured data: Organization + DonateAction. +- “Impact report” as a crawlable page (not only a modal). + +--- + +## High-Impact Quick Wins + +1. **Run Lighthouse on mobile** and fix: + - LCP image optimization + preload + - Reduce unused JS + - Eliminate layout shift (banner/fonts) +2. **Accessibility pass**: keyboard flow, focus visibility, heading hierarchy. +3. **CTA instrumentation**: track Donate / Get Help / Volunteer separately; verify events fire with ad blockers in mind. +4. **Portals hardening**: CSP, secure cookie configuration, rate limiting on auth endpoints. + +--- + +## Concrete Technical Checklist (Dev Handoff) + +| Area | Target / Requirement | +|------|----------------------| +| **Core Web Vitals** | LCP < 2.5s, CLS < 0.1, INP < 200ms (mobile) | +| **Images** | Responsive, compressed, lazy-load below fold | +| **Fonts** | `font-display: swap`, limit variants, preconnect if external | +| **JS** | Defer non-critical, minimize third-party scripts | +| **A11y** | Skip link, focus states, ARIA labels, contrast, form error messaging | +| **Security** | HSTS, CSP, secure cookies, no PII in URLs, audit third-party embeds | +| **SEO** | SSR/SSG for public pages where possible, metadata, sitemap/robots, schema.org Organization | + +--- + +## Implemented (2026-02-26) + +- **A11y**: Skip-to-content link (focus-only, targets `#content`), focus-visible on hero CTAs and mobile nav links, ARIA labels on Donate/Get Help. +- **SEO**: Canonical/OG URLs and schema.org base URL set to `https://mim4u.org`; DonateAction JSON-LD added alongside Organization. +- **Performance**: Font preload for Inter (critical weights) in `index.html`; preconnect already present; `display=swap` in font URL. +- **Deploy (proxmox)**: `scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh` retries pool deploy on “Replacement transaction underpriced” with gas bump (up to 4 attempts) and short wait after first tx. +- **www.mim4u.org**: Added to `update-npmplus-proxy-hosts-api.sh` (same backend as mim4u.org). Nginx server block includes `server_name mim4u.org www.mim4u.org ...` so both hosts are served. Optional: NPMplus Redirect for www → apex. +- **HSTS/CSP**: Nginx snippet in MIM4U_502_ERROR_RESOLUTION.md adds X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, Content-Security-Policy. HSTS enabled via NPMplus SSL tab when terminating HTTPS. +- **Cookie consent**: Banner persists choice in `localStorage` (`mim_cookie_consent`); analytics/tracking only run when consent is `accept` (see `src/utils/analytics.ts`). Cookie banner is keyboard-accessible and links to legal/cookie policy. +- **Get Help form**: Inline, field-level validation with specific error messages; draft auto-saved to `localStorage` (debounced); draft cleared on successful submit; `aria-invalid` and `aria-describedby` for errors. +- **CTA instrumentation**: Hero Donate and Get Help fire `cta_clicked` with `button` and `location`; analytics only run when cookie consent is “accept”. +- **SEO**: `robots.txt` and `sitemap.xml` use canonical `https://mim4u.org`; sitemap lists main routes (/, #/donate, #/request-assistance, #/volunteers, #/stories, #/legal). +- **502 fix script**: `scripts/mim4u-install-nginx-and-fix-502.sh` installs nginx on VMID 7810, applies security headers + SPA + /api proxy; run from host that can SSH to Proxmox. + +--- + +## Tech stack (documented) + +- **Frontend**: Vite + React + TypeScript; hash-based routing (`#/donate`, `#/request-assistance`, etc.); Tailwind CSS; Framer Motion. +- **Backend**: Azure Functions (API on VMID 7811, port 3001). +- **Hosting**: Static build served by nginx on VMID 7810 (LXC); NPMplus reverse proxy; optional Cloudflare Tunnel. + +## Next Steps for Dev + +1. **Lighthouse**: Run Lighthouse (mobile) and attach report JSON or screenshots to turn this into a **prioritized punch list with exact fixes** (what to change, where, how to validate). +2. **Infra**: Run `./scripts/mim4u-install-nginx-and-fix-502.sh` from a host that can SSH to Proxmox (r630-02) to install nginx on VMID 7810 and apply config — see [MIM4U_502_ERROR_RESOLUTION.md](./MIM4U_502_ERROR_RESOLUTION.md). + +--- + +## Further recommendations and suggestions + +### Performance & Core Web Vitals +- **Lighthouse (mobile)** + Run regularly; fix LCP (hero image `srcset`/preload), CLS (reserve space for cookie banner/fonts), INP (reduce main-thread work). See [Concrete Technical Checklist](#concrete-technical-checklist-dev-handoff) for targets. +- **Self-host fonts** + Consider serving Inter (or critical subset) from your origin to avoid Google Fonts latency and improve LCP; keep `font-display: swap`. +- **Defer non-critical JS** + Lazy-load or defer chat widgets, A/B scripts, and non-essential trackers so they don’t block INP. +- **Images** + Use `LazyImage` (or equivalent) with `sizes`/`srcset` for any hero or above-the-fold images; compress and prefer modern formats (e.g. WebP with fallback). + +### SEO & discoverability +- **Impact report as a real page** + Add a crawlable route (e.g. `#/impact` or `#/impact-report`) and link “See the impact report” to it; add to sitemap. Avoid impact content only in a modal or PDF link so crawlers and users get a proper page. +- **SSR/SSG (longer-term)** + For key public pages (home, donate, get help, impact), consider SSR or static generation to improve indexing and first-paint; Vite SSR or a static export for critical routes is one option. +- **Structured data** + Organization and DonateAction are in place; add `WebPage` or `FAQPage` where it fits (e.g. Get Help or Legal). + +### Forms & Get Help workflow +- **Spam protection** + Add a lightweight mechanism (e.g. honeypot field, or time-based check) so the form isn’t trivial to bot; avoid hostile CAPTCHA for school/counselor users. +- **Confirmation & privacy** + Ensure legal/cookie policy and Get Help copy clearly state how “we confirm support reached the student” works and where data is stored/processed; keep PII out of URLs and query params. +- **Offline / resilience** + Consider a simple “Save draft” affordance in addition to auto-save, and a clear message if submit fails (retry, contact number). + +### Security & Portals +- **Session cookies** + For Portals (and any auth), use `HttpOnly`, `Secure`, and `SameSite=Lax` (or `Strict`) on session cookies; set at the API/auth layer (Azure Functions / Entra config or backend). +- **Rate limiting** + Apply rate limits on `/api/*` and auth endpoints (e.g. login, token). With **Cloudflare** in front: use WAF/custom rules (e.g. rate limit /api/ and auth paths). With **LXC/nginx only**: add `limit_req_zone` / `limit_req` in nginx for `/api/` and proxy to API. +- **CSP** + Nginx and staticwebapp.config already send security headers; keep CSP tight and avoid broad `unsafe-inline`/`unsafe-eval` where possible (may require build-time nonce or hashes for scripts). + +### Analytics & instrumentation +- **Volunteer CTA** + Add `cta_clicked` (or equivalent) for “Volunteer” / “Volunteers” in nav and hero so Donate, Get Help, and Volunteer are tracked consistently; keep respecting cookie consent. +- **Events with ad blockers** + Document or test that key events (donation, form submit, CTA clicks) still fire when analytics is blocked (e.g. fallback to server-side or minimal first-party logging) so funnels are measurable. + +### Operations & hosting +- **Deploy real app to 7810** + After nginx is in place, build the MIM4U frontend and deploy to `/var/www/html` (or the path nginx uses) so the live site replaces the placeholder. +- **Backup** + Back up nginx config and `/var/www/html` (or app deploy path) on 7810; include in any host/container backup runbook. +- **Health check** + Add a simple health URL (e.g. `/health` or `/.well-known/health`) that returns 200 for monitoring or load balancers; optional JSON with version or build id. +- **Cloudflare (optional)** + If mim4u.org is behind Cloudflare: use Full (strict) SSL, WAF rate limits for `/api/`, cache static assets; see `miracles_in_motion/docs/deployment/CLOUDFLARE_SETUP.md` for patterns. + +### General best practices +- **Repo-wide** + Security, backups, monitoring, and script patterns in [OPTIONAL_RECOMMENDATIONS_INDEX.md](../OPTIONAL_RECOMMENDATIONS_INDEX.md) and [RECOMMENDATIONS_AND_SUGGESTIONS.md](../10-best-practices/RECOMMENDATIONS_AND_SUGGESTIONS.md) apply to MIM4U hosting (credentials, backups, monitoring, testing). Run `./scripts/mim4u-backup-7810.sh` periodically to back up nginx + app on 7810. +- **Cloudflare checklist (optional)** + When using Cloudflare in front of mim4u.org: Full (strict) SSL; WAF rate limit for `/api/` (e.g. 100 req/min); cache static assets; see `miracles_in_motion/docs/deployment/CLOUDFLARE_SETUP.md`. +- **Lighthouse** + Run performance audits where Chrome is available: `npm run lighthouse` (headless) or `npm run lighthouse:local` (interactive). In environments without Chrome (e.g. WSL without Chrome), run from a host with Chrome or use CI; fix any LCP/CLS/INP issues reported. + +--- + +**Source:** External technical UX/UI review of mim4u.org (publicly indexable content). +**Hosting context:** Proxmox LXC — VMID 7810 (mim-web-1), VMID 7811 (mim-api-1); NPMplus; miracles_in_motion submodule. diff --git a/docs/04-configuration/NPMPLUS_SERVICE_MAPPING_COMPLETE.md b/docs/04-configuration/NPMPLUS_SERVICE_MAPPING_COMPLETE.md index 38c4fda..573b8e7 100644 --- a/docs/04-configuration/NPMPLUS_SERVICE_MAPPING_COMPLETE.md +++ b/docs/04-configuration/NPMPLUS_SERVICE_MAPPING_COMPLETE.md @@ -68,10 +68,11 @@ All these services can be accessed by NPMplus over the internal network, regardl | Domain | Target | VMID | Host | Port | Notes | |--------|--------|------|------|------|-------| | `mim4u.org` | 192.168.11.37:80 | 7810 | r630-02 | 80 | mim-web-1 (frontend) | +| `www.mim4u.org` | 192.168.11.37:80 | 7810 | r630-02 | 80 | mim-web-1 (same backend) | | `secure.mim4u.org` | 192.168.11.37:80 | 7810 | r630-02 | 80 | mim-web-1 | | `training.mim4u.org` | 192.168.11.37:80 | 7810 | r630-02 | 80 | mim-web-1 | -**Updated**: MIM4U routes to VMID 7810 (mim-web-1) at 192.168.11.37, not 7811. +**Updated**: MIM4U routes to VMID 7810 (mim-web-1) at 192.168.11.37, not 7811. nginx on 7810 proxies `/api/` to 7811 (192.168.11.36:3001). --- diff --git a/miracles_in_motion b/miracles_in_motion index f5eb036..16b9b02 160000 --- a/miracles_in_motion +++ b/miracles_in_motion @@ -1 +1 @@ -Subproject commit f5eb036ee9cb77dd6c6288d2d507bf1b191b9fa8 +Subproject commit 16b9b02d70ed109344c1dbc0e3de28282e4941c2 diff --git a/scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh b/scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh new file mode 100755 index 0000000..c7f9707 --- /dev/null +++ b/scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# Deploy TransactionMirror and create DODO cUSDT/cUSDC PMM pool on Chain 138. +# Run after clearing RPC tx pool (./scripts/clear-all-transaction-pools.sh) so deployer nonce is not stuck. +# +# Uses: smom-dbis-138/.env (PRIVATE_KEY, RPC_URL_138, RPC_URL_138_PUBLIC, DODO_PMM_INTEGRATION, GAS_PRICE) +# and config/ip-addresses.conf for RPC fallbacks. Always checks nonce, RPC active, and gas. +# +# Usage: ./scripts/deployment/deploy-transaction-mirror-and-pmm-pool-after-txpool-clear.sh [--dry-run] [--force] +# --dry-run Check env, RPC, nonce only; no deploy. +# --force Skip RPC reachability check (not recommended). + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SMOM="${PROJECT_ROOT}/smom-dbis-138" + +DRY_RUN="" +FORCE="" +for a in "$@"; do + [[ "$a" == "--dry-run" ]] && DRY_RUN=1 + [[ "$a" == "--force" ]] && FORCE=1 +done + +# 1) Load dotenv: project config (RPCs) then smom-dbis-138/.env (PRIVATE_KEY, overrides) +[[ -f "${PROJECT_ROOT}/config/ip-addresses.conf" ]] && source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true +if [[ ! -f "$SMOM/.env" ]]; then + echo "Missing $SMOM/.env. Abort." >&2 + exit 1 +fi +set -a +source "$SMOM/.env" +set +a + +# 2) RPC: prefer .env, fallback to config +RPC="${RPC_URL_138:-http://192.168.11.211:8545}" +PUBLIC_RPC="${RPC_URL_138_PUBLIC:-http://192.168.11.221:8545}" +[[ -z "${PRIVATE_KEY:-}" ]] && echo "PRIVATE_KEY not set in $SMOM/.env. Abort." >&2 && exit 1 +# Chain 138 gas: min 1 gwei; use GAS_PRICE from .env or default +GAS_PRICE="${GAS_PRICE_138:-${GAS_PRICE:-1000000000}}" + +echo "=== TransactionMirror + PMM pool (Chain 138) ===" +echo "RPC: $RPC" +echo "Gas price: $GAS_PRICE wei" +echo "" + +# 3) Ensure RPC is active (chainId 138) +rpc_ok="" +if [[ -z "$FORCE" ]]; then + chain_id_hex=$(curl -s -m 10 -X POST "$RPC" -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' 2>/dev/null | sed -n 's/.*"result":"\([^"]*\)".*/\1/p') || true + if [[ "$chain_id_hex" == "0x8a" ]]; then + rpc_ok=1 + else + if [[ -n "$chain_id_hex" ]]; then + echo "RPC returned chainId $chain_id_hex (expected 0x8a for Chain 138)." >&2 + else + echo "RPC unreachable or invalid response: $RPC" >&2 + fi + if [[ "$RPC" == *"192.168.11.211"* ]] && [[ "$PUBLIC_RPC" != *"192.168.11.211"* ]]; then + pub_hex=$(curl -s -m 5 -X POST "$PUBLIC_RPC" -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' 2>/dev/null | sed -n 's/.*"result":"\([^"]*\)".*/\1/p') || true + if [[ "$pub_hex" == "0x8a" ]]; then + echo "Using Public RPC: $PUBLIC_RPC" >&2 + RPC="$PUBLIC_RPC" + rpc_ok=1 + fi + fi + if [[ -z "$rpc_ok" ]]; then + echo "Set RPC_URL_138 (and optionally RPC_URL_138_PUBLIC) in $SMOM/.env to a reachable Chain 138 RPC." >&2 + exit 1 + fi + fi +else + echo "(--force: skipping RPC check)" >&2 +fi + +# 4) Always check deployer nonce (pending) and set NEXT_NONCE for scripts +DEPLOYER=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null) || { echo "cast wallet address failed. Check PRIVATE_KEY in .env." >&2; exit 1; } +NONCE_PENDING=$(cast nonce "$DEPLOYER" --rpc-url "$RPC" --block pending 2>/dev/null) || true +NONCE_LATEST=$(cast nonce "$DEPLOYER" --rpc-url "$RPC" --block latest 2>/dev/null) || true +# Normalize: empty or non-numeric -> use latest, then 0; ensure decimal for export +[[ -z "${NONCE_PENDING//[0-9a-fA-Fx]/}" && -n "$NONCE_PENDING" ]] || NONCE_PENDING="$NONCE_LATEST" +[[ -z "${NONCE_PENDING//[0-9a-fA-Fx]/}" && -n "$NONCE_PENDING" ]] || NONCE_PENDING="0" +NONCE_PENDING=$((NONCE_PENDING)) +NONCE_LATEST=$((NONCE_LATEST)) +# Use pending nonce so we don't resend at same nonce (avoids "Known transaction" / "Replacement underpriced") +export NEXT_NONCE=$NONCE_PENDING +echo "Deployer: $DEPLOYER" +echo "Nonce (pending): $NONCE_PENDING (latest: $NONCE_LATEST) — using NEXT_NONCE=$NEXT_NONCE" +echo "" + +if [[ -n "$DRY_RUN" ]]; then + echo "[dry-run] Would run:" + echo " 1. NEXT_NONCE=$NEXT_NONCE forge script script/DeployTransactionMirror.s.sol:DeployTransactionMirror --rpc-url \"\$RPC\" --broadcast --private-key \"\$PRIVATE_KEY\" --with-gas-price $GAS_PRICE" + echo " 2. NEXT_NONCE=\$(next after 1) forge script script/dex/CreateCUSDTCUSDCPool.s.sol:CreateCUSDTCUSDCPool --rpc-url \"\$RPC\" --broadcast --private-key \"\$PRIVATE_KEY\" --with-gas-price $GAS_PRICE" + echo " 3. $PROJECT_ROOT/scripts/verify/check-contracts-on-chain-138.sh \"$RPC\"" + exit 0 +fi + +cd "$SMOM" +export RPC_URL_138="$RPC" +export DODO_PMM_INTEGRATION="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-0x79cdbaFBaA0FdF9F55D26F360F54cddE5c743F7D}}" + +echo "Deploying TransactionMirror (NEXT_NONCE=$NEXT_NONCE, gas $GAS_PRICE)..." +forge script script/DeployTransactionMirror.s.sol:DeployTransactionMirror \ + --rpc-url "$RPC" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" + +# Re-query pending nonce for pool deploy; wait briefly so first tx can be mined (reduces "Replacement transaction underpriced") +sleep 3 +NONCE_USED_FIRST=$NEXT_NONCE +NEXT_NONCE=$(cast nonce "$DEPLOYER" --rpc-url "$RPC" --block pending 2>/dev/null) || true +[[ -z "${NEXT_NONCE//[0-9a-fA-Fx]/}" && -n "$NEXT_NONCE" ]] || NEXT_NONCE=$((NONCE_USED_FIRST + 1)) +NEXT_NONCE=$((NEXT_NONCE)) +export NEXT_NONCE + +# Retry pool deploy with gas bump on "Replacement transaction underpriced" +POOL_GAS="$GAS_PRICE" +POOL_MAX_RETRIES=4 +POOL_RETRY=0 +while true; do + echo "" + echo "Creating DODO cUSDT/cUSDC pool (NEXT_NONCE=$NEXT_NONCE, gas $POOL_GAS)..." + POOL_OUTPUT=$(forge script script/dex/CreateCUSDTCUSDCPool.s.sol:CreateCUSDTCUSDCPool \ + --rpc-url "$RPC" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$POOL_GAS" 2>&1) || true + echo "$POOL_OUTPUT" + if echo "$POOL_OUTPUT" | grep -q "Replacement transaction underpriced"; then + POOL_RETRY=$((POOL_RETRY + 1)) + if [[ $POOL_RETRY -ge $POOL_MAX_RETRIES ]]; then + echo "Error: Pool deploy failed after $POOL_MAX_RETRIES attempts (Replacement transaction underpriced). Clear tx pool or increase GAS_PRICE_138." >&2 + exit 1 + fi + POOL_GAS=$((POOL_GAS * 12 / 10)) + echo "Replacement transaction underpriced — retrying at same nonce $NEXT_NONCE with gas price $POOL_GAS wei (attempt $((POOL_RETRY + 1))/$POOL_MAX_RETRIES)." + sleep 5 + continue + fi + if echo "$POOL_OUTPUT" | grep -q "Script ran successfully"; then + break + fi + echo "Pool deploy failed. Check output above." >&2 + exit 1 +done + +echo "" +echo "Running on-chain verification..." +"$PROJECT_ROOT/scripts/verify/check-contracts-on-chain-138.sh" "$RPC" diff --git a/scripts/mim4u-backup-7810.sh b/scripts/mim4u-backup-7810.sh new file mode 100755 index 0000000..dd86bf7 --- /dev/null +++ b/scripts/mim4u-backup-7810.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Backup nginx config and app files from MIM4U web container (VMID 7810). +# Run from a host that can SSH to the Proxmox node. Creates timestamped tarball in backups/mim4u/. +# +# Usage: ./scripts/mim4u-backup-7810.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +[[ -f "$PROJECT_ROOT/config/ip-addresses.conf" ]] && source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true + +VMID_MIM_WEB="${VMID_MIM_WEB:-7810}" +PROXMOX_HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}" +BACKUP_DIR="${PROJECT_ROOT}/backups/mim4u" +STAMP=$(date +%Y%m%d_%H%M%S) +ARCHIVE="${BACKUP_DIR}/mim4u-7810-${STAMP}.tar.gz" + +mkdir -p "$BACKUP_DIR" +echo "Backing up VMID $VMID_MIM_WEB (nginx + /var/www/html) to $ARCHIVE ..." +ssh "root@$PROXMOX_HOST" "pct exec $VMID_MIM_WEB -- tar czf - -C / etc/nginx/sites-available/default var/www/html 2>/dev/null || true" > "$ARCHIVE" +echo "Done: $ARCHIVE" diff --git a/scripts/mim4u-deploy-to-7810.sh b/scripts/mim4u-deploy-to-7810.sh new file mode 100755 index 0000000..23b68f4 --- /dev/null +++ b/scripts/mim4u-deploy-to-7810.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Build MIM4U frontend and deploy to VMID 7810 /var/www/html. +# Run from project root. Requires: npm in miracles_in_motion, ssh to Proxmox node, rsync or scp. +# +# Usage: ./scripts/mim4u-deploy-to-7810.sh + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +MIM_ROOT="${PROJECT_ROOT}/miracles_in_motion" +[[ -f "$PROJECT_ROOT/config/ip-addresses.conf" ]] && source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true + +VMID_MIM_WEB="${VMID_MIM_WEB:-7810}" +PROXMOX_HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}" +MIM_WEB_IP="${IP_MIM_WEB:-192.168.11.37}" +DEST="/var/www/html" + +echo "Building MIM4U frontend..." +(cd "$MIM_ROOT" && npm run build) +echo "Deploying dist to root@$PROXMOX_HOST (pct exec $VMID_MIM_WEB) at $DEST ..." +# Copy into container: tar from host, extract in container +tar czf - -C "$MIM_ROOT/dist" . | ssh "root@$PROXMOX_HOST" "pct exec $VMID_MIM_WEB -- tar xzf - -C $DEST" +echo "Done. Verify: curl -I http://${MIM_WEB_IP}:80/" diff --git a/scripts/mim4u-install-nginx-and-fix-502.sh b/scripts/mim4u-install-nginx-and-fix-502.sh new file mode 100755 index 0000000..9215835 --- /dev/null +++ b/scripts/mim4u-install-nginx-and-fix-502.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Install nginx on MIM4U web container (VMID 7810) and apply security-enabled config to fix 502. +# Run from a host that can SSH to the Proxmox node (e.g. r630-02). Requires root on Proxmox. +# +# Usage: ./scripts/mim4u-install-nginx-and-fix-502.sh [--dry-run] +# Optional: set PROXMOX_HOST_R630_02 and MIM_API_IP in env or config/ip-addresses.conf + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +[[ -f "$PROJECT_ROOT/config/ip-addresses.conf" ]] && source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true + +VMID_MIM_WEB="${VMID_MIM_WEB:-7810}" +PROXMOX_HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}" +# API backend (VMID 7811) for /api/ proxy +MIM_API_IP="${MIM_API_IP:-192.168.11.36}" +MIM_API_PORT="${MIM_API_PORT:-3001}" +DRY_RUN="${1:-}" + +run_remote() { + if [[ "$DRY_RUN" == "--dry-run" ]]; then + echo "[dry-run] ssh root@$PROXMOX_HOST pct exec $VMID_MIM_WEB -- $*" + return 0 + fi + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "root@$PROXMOX_HOST" "pct exec $VMID_MIM_WEB -- $*" +} + +echo "=== MIM4U 502 fix: install nginx on VMID $VMID_MIM_WEB (Proxmox $PROXMOX_HOST) ===" +echo "API backend: $MIM_API_IP:$MIM_API_PORT" +echo "" + +# 1) Install nginx if missing +echo "[1/4] Installing nginx..." +run_remote bash -c 'export DEBIAN_FRONTEND=noninteractive && apt-get update -qq && apt-get install -y -qq nginx' 2>/dev/null || true + +# 2a) Rate limit zones (http context via conf.d) +echo "[2/4] Writing nginx config (site + rate limit)..." +RATE_LIMIT_CONF='# MIM4U rate limits (included from main nginx.conf) +limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; +limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s; +' +if [[ "$DRY_RUN" != "--dry-run" ]]; then + echo "$RATE_LIMIT_CONF" | ssh "root@$PROXMOX_HOST" "pct exec $VMID_MIM_WEB -- bash -c 'cat > /etc/nginx/conf.d/mim4u-rate-limit.conf'" +fi + +# 2b) Write nginx site config (security headers + SPA + /api proxy) +# CSP: font-src 'self' only (Inter is self-hosted via @fontsource) +NGINX_CONF="server { + listen 80; + server_name mim4u.org www.mim4u.org secure.mim4u.org training.mim4u.org _; + root /var/www/html; + index index.html; + + add_header X-Content-Type-Options \"nosniff\" always; + add_header X-Frame-Options \"SAMEORIGIN\" always; + add_header X-XSS-Protection \"1; mode=block\" always; + add_header Referrer-Policy \"strict-origin-when-cross-origin\" always; + add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https:; connect-src 'self' https://mim4u.org; frame-ancestors 'self';\" always; + + location /health { + default_type text/plain; + return 200 'ok'; + add_header Content-Type text/plain; + } + + location / { + limit_req zone=general burst=20 nodelay; + try_files \$uri \$uri/ /index.html =404; + } + + location /api/ { + limit_req zone=api burst=5 nodelay; + proxy_pass http://${MIM_API_IP}:${MIM_API_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; + } +}" + +if [[ "$DRY_RUN" != "--dry-run" ]]; then + echo "$NGINX_CONF" | ssh "root@$PROXMOX_HOST" "pct exec $VMID_MIM_WEB -- bash -c 'cat > /etc/nginx/sites-available/default'" +fi + +# 3) Ensure /var/www/html exists and has index (placeholder if no app deployed yet) +echo "[3/4] Ensuring web root..." +run_remote mkdir -p /var/www/html +run_remote bash -c 'test -f /var/www/html/index.html || cat >/var/www/html/index.html <MIM4U

Miracles in Motion - site coming soon.

+END' + +# 4) Test and reload nginx +echo "[4/4] Testing and reloading nginx..." +run_remote nginx -t 2>/dev/null && run_remote systemctl enable nginx 2>/dev/null || true +run_remote systemctl reload nginx 2>/dev/null || run_remote systemctl start nginx 2>/dev/null || true + +MIM_WEB_IP="${IP_MIM_WEB:-192.168.11.37}" +echo "" +echo "Done. Verify from LAN: curl -I http://${MIM_WEB_IP}:80/" +echo "Then ensure NPMplus proxy hosts point to ${MIM_WEB_IP} (see docs/04-configuration/MIM4U_502_ERROR_RESOLUTION.md)." diff --git a/scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh b/scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh index 7ec6dd4..8b523ce 100755 --- a/scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh +++ b/scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail # Load IP configuration @@ -238,6 +238,7 @@ update_proxy_host "dbis-api-2.d-bis.org" "http://${IP_DBIS_API_2:-192.168.11.156 update_proxy_host "secure.d-bis.org" "http://${IP_DBIS_FRONTEND:-192.168.11.130}:80" false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1)) # MIM4U - VMID 7810 (mim-web-1) @ ${IP_MIM_WEB:-192.168.11.37} - Web Frontend serves main site and proxies /api/* to 7811 update_proxy_host "mim4u.org" "http://${IP_MIM_WEB:-192.168.11.37}:80" false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1)) +update_proxy_host "www.mim4u.org" "http://${IP_MIM_WEB:-192.168.11.37}:80" false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1)) update_proxy_host "secure.mim4u.org" "http://${IP_MIM_WEB:-192.168.11.37}:80" false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1)) update_proxy_host "training.mim4u.org" "http://${IP_MIM_WEB:-192.168.11.37}:80" false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1))