diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index b61daf1..1990f79 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -93,6 +93,35 @@ Najwyższy poziom (Enterprise) służy jako **kotwica cenowa** — sprawia że P | # | Funkcjonalność | Opis | Zgłosił | Data | Priorytet | Status | |---|---------------|------|---------|------|-----------|--------| | 1 | Wiele lokalizacji firmy na mapie | Firmy z kilkoma oddziałami/punktami mogą dodać wiele adresów. Każdy wyświetlany na profilu z linkiem do Google Maps. Nowa tabela `company_locations` (label, adres, lat/lng na przyszłość), edycja w zakładce Kontakt w profilu firmy, wzorzec jak CompanyWebsite (dynamiczne wiersze, delete-and-reinsert). | Daniel Kochański (Stalpunkt) | 2026-04-09 | Średni | Planowane | +| 2 | E-deklaracja członkowska z podpisem elektronicznym | Elektroniczny formularz deklaracji przystąpienia do Izby z możliwością podpisu Profilem Zaufanym. Szczegóły poniżej w sekcji dedykowanej. | Jacek Pomieczyński (Eura-Tech), Paweł Kwidziński | 2026-04-08 | Niski | Rozważane | + +#### E-deklaracja członkowska — analiza (priorytet: niski) + +**Kontekst:** Jacek Pomieczyński (Eura-Tech) i Paweł Kwidziński postulują elektroniczną deklarację przystąpienia do Izby zamiast papierowego formularza. Poprzednia próba wdrożenia zablokowana przez brak rozwiązania podpisu reprezentacji (kto jest uprawniony do podpisania w imieniu firmy). + +**Rekomendowany flow (bez integracji API, zero kosztów):** + +1. Użytkownik wypełnia formularz deklaracji na NordaBiznes.pl (dane firmy, NIP, KRS, dane osoby reprezentującej) +2. System generuje gotowy PDF deklaracji +3. Użytkownik pobiera PDF i podpisuje go **samodzielnie** Profilem Zaufanym na moj.gov.pl → "Podpisz dokument elektronicznie" (lub w aplikacji mObywatel) +4. Użytkownik uploaduje podpisany plik (.xml z podpisem XAdES) z powrotem na portal +5. Biuro Nordy weryfikuje podpis (moj.gov.pl → "Sprawdź podpis elektroniczny") i proceduje przyjęcie + +**Co trzeba zbudować:** formularz deklaracji + generator PDF (np. WeasyPrint/ReportLab). Infrastruktura podpisu — darmowa, po stronie państwa. + +**Wymagania prawne (do ustalenia z mec. Masiakiem):** +- Czy statut Izby wymaga formy pisemnej deklaracji? +- Jeśli tak — zmiana statutu dopuszczająca formę elektroniczną (możliwa na Walnym 28.05.2026) +- Podpis Zaufany w relacjach prywatnych nie jest automatycznie równoważny z podpisem ręcznym — statut musi go wprost dopuszczać + +**Rozważane alternatywy (odrzucone lub odłożone):** +- Autenti/Signius (API podpisu) — działa, ale zbędny koszt i zależność od zewnętrznego dostawcy przy tak prostym flow +- Bezpośrednia integracja z API Podpisu Zaufanego (podpis.gov.pl) — niedostępne dla podmiotów prywatnych +- Integracja logowania przez Węzeł Krajowy (login.gov.pl) — możliwa technicznie (SAML 2.0), ale nadmiarowa dla samych deklaracji + +**Uwaga:** Przy skali ~10-15 nowych członków rocznie pełna automatyzacja (API) jest nieuzasadniona. Prosty flow PDF + samodzielny podpis PZ rozwiązuje problem. + +--- #### Funkcje strategiczne (ze slajdu 9, prezentacja Rada 13.02.2026) diff --git a/docs/architecture/03-deployment-architecture.md b/docs/architecture/03-deployment-architecture.md index a708827..0abb4d8 100644 --- a/docs/architecture/03-deployment-architecture.md +++ b/docs/architecture/03-deployment-architecture.md @@ -7,7 +7,7 @@ --- -> **UWAGA (2026-04-04):** Produkcja przeniesiona z OVH VPS inpi-vps-waw01 (VM 249, 57.128.200.27) na **OVH VPS (57.128.200.27, hostname inpi-vps-waw01)**. Diagramy Mermaid poniżej odzwierciedlają starą architekturę — faktyczny stan to: DNS nordabiznes.pl -> 57.128.200.27 (bezpośrednio, bez NPM). NPM (10.22.68.250) obsługuje tylko staging. +> **NOTE (2026-04-04):** Production migrated to OVH VPS (57.128.200.27, hostname inpi-vps-waw01). Traffic flow: Internet -> Nginx (57.128.200.27:443) -> Gunicorn (127.0.0.1:5000). No FortiGate or NPM for production. NPM (10.22.68.250) serves staging only. ## Overview @@ -108,7 +108,7 @@ graph TB |--------|-------|------------|----------|-----|------------| | **NORDABIZ-STAGING-01** | 248 | 10.22.68.248 | nordabiz-staging-01 | Ubuntu 22.04 | Proxmox VE | -**Note:** The old on-prem production VM 249 (57.128.200.27) and NPM reverse proxy (10.22.68.250) are no longer used for production. Staging still uses NPM + FortiGate path. +**Note:** The old on-prem production VM 249 (10.22.68.249) and NPM reverse proxy (10.22.68.250) are no longer used for production. Staging still uses NPM + FortiGate path. --- @@ -440,7 +440,7 @@ curl -I https://nordabiznes.pl/health **API Key Storage:** - **Location:** `/var/www/nordabiznes/.env` (production) - **Permissions:** `chmod 600` (owner read/write only) -- **Owner:** `www-data:www-data` +- **Owner:** `maciejpi:maciejpi` - **⚠️ NEVER commit to git!** (in `.gitignore`) --- @@ -816,7 +816,7 @@ sudo -u postgres psql -c "SELECT 1;" # Expected: 1 # 3. Check application database access -sudo -u www-data psql -h 127.0.0.1 -U nordabiz_app -d nordabiz -c "SELECT 1;" +sudo -u maciejpi psql -h 127.0.0.1 -U nordabiz_app -d nordabiz -c "SELECT 1;" # Expected: 1 (if password prompt, check .env) # 4. Check active connections diff --git a/docs/architecture/07-network-topology.md b/docs/architecture/07-network-topology.md index 7c47ca6..f9a63a9 100644 --- a/docs/architecture/07-network-topology.md +++ b/docs/architecture/07-network-topology.md @@ -7,7 +7,7 @@ --- -> **UWAGA (2026-04-04):** Produkcja przeniesiona z OVH VPS inpi-vps-waw01 (VM 249, 57.128.200.27) na **OVH VPS (57.128.200.27, hostname inpi-vps-waw01)**. Diagramy Mermaid poniżej odzwierciedlają starą architekturę — ruch produkcyjny nie przechodzi już przez Fortigate/NPM. Staging (10.22.68.248) i NPM (10.22.68.250) bez zmian. +> **NOTE (2026-04-04):** Production migrated to OVH VPS (57.128.200.27). Production traffic no longer goes through FortiGate/NPM. Staging (10.22.68.248) and NPM (10.22.68.250) remain unchanged. ## Overview @@ -422,8 +422,9 @@ App Server (57.128.200.27) | Record Type | Name | Value | TTL | Purpose | |-------------|------|-------|-----|---------| -| A | nordabiznes.pl | 85.237.177.83 | 3600 | Main website | -| A | www.nordabiznes.pl | 85.237.177.83 | 3600 | WWW subdomain | +| A | nordabiznes.pl | 57.128.200.27 | 3600 | Main website (OVH VPS) | +| A | www.nordabiznes.pl | 57.128.200.27 | 3600 | WWW subdomain (OVH VPS) | +| A | staging.nordabiznes.pl | 85.237.177.83 | 3600 | Staging (via FortiGate) | | MX | nordabiznes.pl | (Not configured) | - | No email hosting | | TXT | nordabiznes.pl | (SPF, DKIM if configured) | - | Email authentication | @@ -431,11 +432,11 @@ App Server (57.128.200.27) ```bash # Check DNS resolution dig nordabiznes.pl +short -# Expected: 85.237.177.83 +# Expected: 57.128.200.27 # Check from Google DNS dig @8.8.8.8 nordabiznes.pl +short -# Expected: 85.237.177.83 +# Expected: 57.128.200.27 # Check WHOIS whois nordabiznes.pl @@ -499,23 +500,19 @@ default via 10.22.68.1 dev ens18 # All non-local traffic → Fortigate ``` User Browser (Internet) ↓ DNS query -OVH DNS: nordabiznes.pl → 85.237.177.83 +OVH DNS: nordabiznes.pl → 57.128.200.27 ↓ HTTPS :443 -Fortigate WAN (85.237.177.83:443) - ↓ NAT translation -Fortigate LAN → NPM (10.22.68.250:443) - ↓ SSL termination, proxy -NPM → Flask/Gunicorn (57.128.200.27:5000) +Nginx @ OVH VPS (57.128.200.27:443) + ↓ SSL termination, proxy_pass +Gunicorn (127.0.0.1:5000) ↓ HTTP request processing Flask → PostgreSQL (127.0.0.1:5432) ↓ SQL query PostgreSQL → Flask (result set) ↓ HTML rendering -Flask → NPM (HTTP response) +Flask → Nginx (HTTP response) ↓ SSL encryption -NPM → Fortigate LAN (HTTPS) - ↓ NAT reverse -Fortigate WAN → User Browser (85.237.177.83:443) +Nginx → User Browser (57.128.200.27:443) ``` **Total Hops:** 6 (external) + 3 (internal) = 9 hops diff --git a/docs/architecture/08-critical-configurations.md b/docs/architecture/08-critical-configurations.md index 54cac40..4ecb849 100644 --- a/docs/architecture/08-critical-configurations.md +++ b/docs/architecture/08-critical-configurations.md @@ -364,7 +364,7 @@ curl -vI https://nordabiznes.pl 2>&1 | grep "expire date" ### Production Environment Variables **Location:** `/var/www/nordabiznes/.env` -**Owner:** `www-data:www-data` +**Owner:** `maciejpi:maciejpi` **Permissions:** `0600` (read/write owner only) **⚠️ WARNING:** Never commit `.env` to version control! @@ -419,7 +419,7 @@ openssl rand -base64 32 # Verify .env file permissions ls -la /var/www/nordabiznes/.env -# Expected: -rw------- 1 www-data www-data ... .env +# Expected: -rw------- 1 maciejpi maciejpi ... .env ``` ### Development Environment Variables @@ -470,7 +470,7 @@ postgresql://nordabiz_app:NordaBiz2025Secure@127.0.0.1:5433/nordabiz **Direct psql (production):** ```bash # As application user -sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 +psql -U nordabiz_app -d nordabiz -h 127.0.0.1 # As postgres superuser sudo -u postgres psql nordabiz @@ -744,7 +744,7 @@ curl -I https://nordabiznes.pl/health ### SSH Keys for Git Access **Production Server:** -- Location: `/home/www-data/.ssh/` +- Location: `/home/maciejpi/.ssh/` - Public key: `id_rsa.pub` - Private key: `id_rsa` - Known hosts: `known_hosts` (includes Gitea fingerprint) @@ -761,14 +761,14 @@ curl -I https://nordabiznes.pl/health | Path | Description | Owner | Permissions | |------|-------------|-------|-------------| -| `/var/www/nordabiznes/` | Application root directory | www-data:www-data | 0755 | -| `/var/www/nordabiznes/app.py` | Main Flask application | www-data:www-data | 0644 | -| `/var/www/nordabiznes/.env` | Environment variables (secrets) | www-data:www-data | 0600 | -| `/var/www/nordabiznes/venv/` | Python virtual environment | www-data:www-data | 0755 | -| `/var/www/nordabiznes/static/` | Static assets (CSS, JS, images) | www-data:www-data | 0755 | -| `/var/www/nordabiznes/templates/` | Jinja2 templates | www-data:www-data | 0755 | -| `/var/www/nordabiznes/database.py` | SQLAlchemy models | www-data:www-data | 0644 | -| `/var/www/nordabiznes/scripts/` | Background scripts | www-data:www-data | 0755 | +| `/var/www/nordabiznes/` | Application root directory | maciejpi:maciejpi | 0755 | +| `/var/www/nordabiznes/app.py` | Main Flask application | maciejpi:maciejpi | 0644 | +| `/var/www/nordabiznes/.env` | Environment variables (secrets) | maciejpi:maciejpi | 0600 | +| `/var/www/nordabiznes/venv/` | Python virtual environment | maciejpi:maciejpi | 0755 | +| `/var/www/nordabiznes/static/` | Static assets (CSS, JS, images) | maciejpi:maciejpi | 0755 | +| `/var/www/nordabiznes/templates/` | Jinja2 templates | maciejpi:maciejpi | 0755 | +| `/var/www/nordabiznes/database.py` | SQLAlchemy models | maciejpi:maciejpi | 0644 | +| `/var/www/nordabiznes/scripts/` | Background scripts | maciejpi:maciejpi | 0755 | ### Configuration Files @@ -782,8 +782,8 @@ curl -I https://nordabiznes.pl/health | Path | Description | Rotation | Owner | |------|-------------|----------|-------| -| `/var/log/nordabiznes/gunicorn_access.log` | Gunicorn access log | Daily | www-data | -| `/var/log/nordabiznes/gunicorn_error.log` | Gunicorn error log | Daily | www-data | +| `/var/log/nordabiznes/gunicorn_access.log` | Gunicorn access log | Daily | maciejpi | +| `/var/log/nordabiznes/gunicorn_error.log` | Gunicorn error log | Daily | maciejpi | | `/var/log/postgresql/postgresql-*.log` | PostgreSQL logs | Weekly | postgres | | `journalctl -u nordabiznes` | Systemd service logs | 30 days | root | @@ -953,20 +953,20 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup 4. **Clone application:** ```bash sudo mkdir -p /var/www/nordabiznes - sudo chown www-data:www-data /var/www/nordabiznes - sudo -u www-data git clone https://10.22.68.180:3000/maciejpi/nordabiz.git /var/www/nordabiznes + sudo chown maciejpi:maciejpi /var/www/nordabiznes + sudo -u maciejpi git clone https://10.22.68.180:3000/maciejpi/nordabiz.git /var/www/nordabiznes ``` 5. **Restore .env file:** ```bash sudo cp /path/to/.env.backup /var/www/nordabiznes/.env - sudo chown www-data:www-data /var/www/nordabiznes/.env + sudo chown maciejpi:maciejpi /var/www/nordabiznes/.env sudo chmod 600 /var/www/nordabiznes/.env ``` 6. **Install Python dependencies:** ```bash cd /var/www/nordabiznes - sudo -u www-data python3 -m venv venv - sudo -u www-data venv/bin/pip install -r requirements.txt + sudo -u maciejpi python3 -m venv venv + sudo -u maciejpi venv/bin/pip install -r requirements.txt ``` 7. **Configure systemd service:** ```bash @@ -999,7 +999,7 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup # Application cd /var/www/nordabiznes - sudo -u www-data git status # Ensure clean state + sudo -u maciejpi git status # Ensure clean state ``` 2. **Test changes in development** @@ -1022,7 +1022,7 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup # Pull changes cd /var/www/nordabiznes - sudo -u www-data git pull + sudo -u maciejpi git pull # Restart service sudo systemctl restart nordabiznes @@ -1047,7 +1047,7 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup sudo systemctl stop nordabiznes # Rollback code - sudo -u www-data git reset --hard HEAD~1 + sudo -u maciejpi git reset --hard HEAD~1 # Rollback database (if needed) sudo -u postgres psql nordabiz < /tmp/backup_before_change.sql @@ -1130,10 +1130,10 @@ curl -I https://nordabiznes.pl/health ```bash # Check database connectivity -sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c "SELECT COUNT(*) FROM companies;" +psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c "SELECT COUNT(*) FROM companies;" # Check recent data -sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c \ +psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c \ "SELECT name, slug FROM companies ORDER BY created_at DESC LIMIT 5;" # Check database size @@ -1249,7 +1249,7 @@ curl -I https://nordabiznes.pl/health # Test health ssh maciejpi@57.128.200.27 cd /var/www/nordabiznes sudo systemctl stop nordabiznes -sudo -u www-data git reset --hard HEAD~1 +sudo -u maciejpi git reset --hard HEAD~1 sudo systemctl start nordabiznes curl -I https://nordabiznes.pl/health ``` diff --git a/docs/architecture/09-security-architecture.md b/docs/architecture/09-security-architecture.md index def84fe..e444a07 100644 --- a/docs/architecture/09-security-architecture.md +++ b/docs/architecture/09-security-architecture.md @@ -809,7 +809,7 @@ SMTP_PASSWORD= **Secrets Storage:** - **Development:** `.env` file (gitignored) -- **Production:** `.env` file on server (owned by www-data, mode 600) +- **Production:** `.env` file on server (owned by maciejpi, mode 600) - **Never committed to Git:** `.env` in `.gitignore` **Secret Rotation:** @@ -819,7 +819,7 @@ SMTP_PASSWORD= **Access Control:** ```bash # Production secrets file permissions --rw------- 1 www-data www-data .env # Mode 600 (owner read/write only) +-rw------- 1 maciejpi maciejpi .env # Mode 600 (owner read/write only) ``` --- @@ -1151,7 +1151,7 @@ app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection **Storage:** - **Location:** `.env` file - **Permissions:** 600 (owner read/write only) -- **Owner:** www-data (Flask app user) +- **Owner:** maciejpi (Flask app user) **Best Practices:** - ✅ API keys in .env (not hardcoded) @@ -1274,7 +1274,7 @@ deny all from 10.22.68.250 to ANY # NPM cannot initiate outbound - **Port:** 22 (default) - **Access:** Restricted to ADMIN_NET (firewall rule) - **Authentication:** SSH key-based (password auth disabled) -- **Users:** maciejpi (primary admin), www-data (app deployment) +- **Users:** maciejpi (admin + app user) **SSH Hardening:** ```bash @@ -1283,7 +1283,7 @@ PermitRootLogin no # Disable root login PasswordAuthentication no # Key-based auth only PubkeyAuthentication yes # Allow SSH keys X11Forwarding no # Disable X11 -AllowUsers maciejpi www-data # Whitelist users +AllowUsers maciejpi # Whitelist users ``` **SSH Key Management:** @@ -1312,16 +1312,16 @@ AllowUsers maciejpi www-data # Whitelist users **File Permissions:** ```bash # Application directory -/var/www/nordabiznes/ # Owner: www-data, Mode: 755 -/var/www/nordabiznes/.env # Owner: www-data, Mode: 600 (secrets) -/var/www/nordabiznes/app.py # Owner: www-data, Mode: 644 +/var/www/nordabiznes/ # Owner: maciejpi, Mode: 755 +/var/www/nordabiznes/.env # Owner: maciejpi, Mode: 600 (secrets) +/var/www/nordabiznes/app.py # Owner: maciejpi, Mode: 644 # Database directory /var/lib/postgresql/ # Owner: postgres, Mode: 700 ``` **User Separation:** -- **www-data:** Flask application (limited privileges) +- **maciejpi:** Flask application (limited privileges) - **postgres:** PostgreSQL database (limited privileges) - **maciejpi:** Admin user (sudo access) @@ -1643,7 +1643,7 @@ docker restart - ✅ Firewall (Fortigate) - ✅ Network segmentation (DMZ, App, Data zones) - ✅ SSH key-based authentication -- ✅ Principle of least privilege (www-data user) +- ✅ Principle of least privilege (maciejpi user) - ❌ IDS/IPS - ❌ DDoS protection (beyond Fortigate) diff --git a/docs/architecture/11-troubleshooting-guide.md b/docs/architecture/11-troubleshooting-guide.md index 6b8f4e6..a067edc 100644 --- a/docs/architecture/11-troubleshooting-guide.md +++ b/docs/architecture/11-troubleshooting-guide.md @@ -71,48 +71,36 @@ graph TD ## 2. Infrastructure & Network Issues -### 2.1 ERR_TOO_MANY_REDIRECTS +### 2.1 Site Not Accessible / ERR_TOO_MANY_REDIRECTS **Severity:** CRITICAL -**Incident History:** 2026-01-02 (30 min outage) + +> **Note:** The ERR_TOO_MANY_REDIRECTS issue from 2026-01-02 was specific to the old NPM+on-prem setup. With the current OVH VPS architecture, redirect loops are unlikely since nginx and Gunicorn run on the same host. #### Symptoms -- Browser error: `ERR_TOO_MANY_REDIRECTS` -- Portal completely inaccessible via https://nordabiznes.pl -- Internal access works fine (http://57.128.200.27:5000) -- Affects 100% of external users - -#### Root Cause - -Nginx Proxy Manager (NPM) configured to forward to **port 80** instead of **port 5000**. - -**Why this causes redirect loop:** -1. NPM forwards HTTPS → HTTP to backend port 80 -2. Nginx on port 80 sees HTTP and redirects to HTTPS -3. Request goes back to NPM, creating infinite loop -4. Browser aborts after ~20 redirects +- Browser error or timeout accessing https://nordabiznes.pl +- Portal completely inaccessible #### Diagnosis ```bash -# 1. Check NPM proxy configuration -ssh maciejpi@10.22.68.250 -docker exec nginx-proxy-manager_app_1 \ - sqlite3 /data/database.sqlite \ - "SELECT id, domain_names, forward_host, forward_port FROM proxy_host WHERE id = 27;" +# 1. Check nginx status on OVH VPS +ssh maciejpi@57.128.200.27 "sudo systemctl status nginx" -# Expected output: -# 27|["nordabiznes.pl","www.nordabiznes.pl"]|57.128.200.27|5000 +# 2. Check Gunicorn status +ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes" -# If forward_port shows 80 → PROBLEM FOUND! - -# 2. Test backend directly -curl -I http://57.128.200.27:80/ -# If this returns 301 redirect → confirms issue - -curl -I http://57.128.200.27:5000/health +# 3. Test backend directly +ssh maciejpi@57.128.200.27 "curl -I http://localhost:5000/health" # Should return 200 OK if Flask is running + +# 4. Check DNS resolution +dig nordabiznes.pl +short +# Expected: 57.128.200.27 + +# 5. Test nginx config +ssh maciejpi@57.128.200.27 "sudo nginx -t" ``` #### Solution @@ -127,54 +115,30 @@ open http://10.22.68.250:81 # 3. Edit configuration: # - Forward Hostname/IP: 57.128.200.27 # - Forward Port: 5000 (CRITICAL!) -# - Scheme: http -# 4. Save and test -``` +#### Solution -**Option B: Fix via NPM API** +```bash +# If nginx is down: +ssh maciejpi@57.128.200.27 "sudo systemctl restart nginx" -```python -import requests +# If Gunicorn is down: +ssh maciejpi@57.128.200.27 "sudo systemctl restart nordabiznes" -NPM_URL = "http://10.22.68.250:81/api" -# Login to get token first (see NPM API docs) - -data = { - "domain_names": ["nordabiznes.pl", "www.nordabiznes.pl"], - "forward_scheme": "http", - "forward_host": "57.128.200.27", - "forward_port": 5000, # CRITICAL: Must be 5000! - "certificate_id": 27, - "ssl_forced": True, - "http2_support": True -} - -response = requests.put( - f"{NPM_URL}/nginx/proxy-hosts/27", - headers={"Authorization": f"Bearer {token}"}, - json=data -) +# If nginx config is broken: +ssh maciejpi@57.128.200.27 "sudo nginx -t && sudo systemctl reload nginx" ``` #### Verification ```bash -# 1. External test (from outside INPI network) curl -I https://nordabiznes.pl/health # Expected: HTTP/2 200 - -# 2. Check NPM logs -ssh maciejpi@10.22.68.250 -docker logs nginx-proxy-manager_app_1 --tail 20 -# Should show 200 responses, not 301 ``` #### Prevention -- **ALWAYS verify port 5000 after ANY NPM configuration change** -- Add monitoring alert for non-200 responses on /health -- Document NPM configuration in change requests -- Test from external network before marking changes complete +- Monitor /health endpoint for non-200 responses +- Test nginx config before reload: `sudo nginx -t` --- @@ -185,14 +149,13 @@ docker logs nginx-proxy-manager_app_1 --tail 20 #### Symptoms - Browser shows "502 Bad Gateway" error -- NPM logs show "upstream connection failed" +- nginx error log shows "upstream connection failed" - Site completely inaccessible #### Root Causes 1. Flask/Gunicorn service stopped -2. Backend server (57.128.200.27) unreachable -3. Firewall blocking port 5000 +2. Gunicorn not listening on 127.0.0.1:5000 #### Diagnosis @@ -233,28 +196,26 @@ curl http://localhost:5000/health ```bash # Check for syntax errors cd /var/www/nordabiznes -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py +/var/www/nordabiznes/venv/bin/python3 -m py_compile app.py # Check for missing dependencies -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -c "import flask; import sqlalchemy" +/var/www/nordabiznes/venv/bin/python3 -c "import flask; import sqlalchemy" # Check environment variables -sudo -u www-data cat /var/www/nordabiznes/.env | grep -v "PASSWORD\|SECRET\|KEY" +cat /var/www/nordabiznes/.env | grep -v "PASSWORD\|SECRET\|KEY" # Try running manually (for debugging) -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py +/var/www/nordabiznes/venv/bin/python3 app.py ``` -**If network issue:** +**If Gunicorn not responding:** ```bash -# Test connectivity from NPM to backend -ssh maciejpi@10.22.68.250 -curl -I http://57.128.200.27:5000/health +# Check if Gunicorn is listening +ssh maciejpi@57.128.200.27 "ss -tlnp | grep 5000" -# Check firewall rules -ssh maciejpi@57.128.200.27 -sudo iptables -L -n | grep 5000 +# Check nginx error log +ssh maciejpi@57.128.200.27 "sudo tail -20 /var/log/nginx/error.log" ``` #### Verification @@ -292,11 +253,11 @@ ps aux | grep gunicorn # Look for zombie workers or high CPU usage # 2. Check database connections -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c \ +psql -h localhost -U nordabiz_app -d nordabiz -c \ "SELECT count(*) FROM pg_stat_activity WHERE datname = 'nordabiz';" # 3. Check for long-running queries -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c \ +psql -h localhost -U nordabiz_app -d nordabiz -c \ "SELECT pid, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5 seconds';" @@ -316,7 +277,7 @@ sudo journalctl -u nordabiznes -n 100 --no-pager | grep -E "slow|timeout|took" ```bash # Identify and kill long-running query -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz # Find problematic query SELECT pid, query FROM pg_stat_activity @@ -383,11 +344,8 @@ echo | openssl s_client -servername nordabiznes.pl -connect nordabiznes.pl:443 2 # 2. Check certificate details curl -vI https://nordabiznes.pl 2>&1 | grep -E "SSL|certificate" -# 3. Check NPM certificate status -ssh maciejpi@10.22.68.250 -docker exec nginx-proxy-manager_app_1 \ - sqlite3 /data/database.sqlite \ - "SELECT id, nice_name, expires_on FROM certificate WHERE id = 27;" +# 3. Check certbot certificate status +ssh maciejpi@57.128.200.27 "sudo certbot certificates" ``` #### Solution @@ -395,18 +353,12 @@ docker exec nginx-proxy-manager_app_1 \ **If certificate expired:** ```bash -# NPM auto-renews Let's Encrypt certificates -# Force renewal via NPM UI or API +# certbot auto-renews Let's Encrypt certificates +# Force renewal: +ssh maciejpi@57.128.200.27 "sudo certbot renew --force-renewal" -# Via UI: -# 1. Access http://10.22.68.250:81 -# 2. SSL Certificates → nordabiznes.pl -# 3. Click "Renew" button - -# Via CLI (if auto-renewal failed): -ssh maciejpi@10.22.68.250 -docker exec nginx-proxy-manager_app_1 \ - node /app/index.js certificate renew 27 +# Reload nginx after renewal: +ssh maciejpi@57.128.200.27 "sudo systemctl reload nginx" ``` **If mixed content warnings:** @@ -445,9 +397,9 @@ nslookup nordabiznes.inpi.local 10.22.68.1 # 3. Test from different locations curl -I -H "Host: nordabiznes.pl" http://85.237.177.83/health -# 4. Check Fortigate NAT rules -# Access Fortigate admin panel and verify NAT entry: -# External: 85.237.177.83:443 → Internal: 10.22.68.250:443 +# 4. Check DNS points to OVH VPS +dig nordabiznes.pl +short +# Expected: 57.128.200.27 ``` #### Solution @@ -494,7 +446,7 @@ sudo journalctl -u nordabiznes -n 100 --no-pager # 3. Try manual start for detailed error cd /var/www/nordabiznes -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py +/var/www/nordabiznes/venv/bin/python3 app.py # Read the traceback carefully ``` @@ -507,12 +459,12 @@ sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py # Cause: Recent code change introduced syntax error # Fix: Check syntax -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py +/var/www/nordabiznes/venv/bin/python3 -m py_compile app.py # Rollback if necessary cd /var/www/nordabiznes -sudo -u www-data git log --oneline -5 -sudo -u www-data git revert HEAD # or specific commit +git log --oneline -5 +git revert HEAD # or specific commit sudo systemctl restart nordabiznes ``` @@ -523,8 +475,8 @@ sudo systemctl restart nordabiznes # Cause: .env file missing or incomplete # Fix: Check .env exists and has required variables -sudo -u www-data ls -la /var/www/nordabiznes/.env -sudo -u www-data cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l +ls -la /var/www/nordabiznes/.env +cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l # Should have ~20 environment variables # Required variables (add if missing): @@ -547,7 +499,7 @@ sudo -u www-data cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l sudo systemctl status postgresql # Test connection -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" +psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" # If password wrong, update .env and restart ``` @@ -560,10 +512,10 @@ sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" # Fix: Reinstall dependencies cd /var/www/nordabiznes -sudo -u www-data /var/www/nordabiznes/venv/bin/pip install -r requirements.txt +/var/www/nordabiznes/venv/bin/pip install -r requirements.txt # Verify specific package -sudo -u www-data /var/www/nordabiznes/venv/bin/pip show flask +/var/www/nordabiznes/venv/bin/pip show flask ``` **E. Port 5000 Already in Use** @@ -635,7 +587,7 @@ sudo journalctl -u nordabiznes -n 100 | grep -i jinja # Test template syntax cd /var/www/nordabiznes -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -c " +/var/www/nordabiznes/venv/bin/python3 -c " from jinja2 import Template with open('templates/index.html') as f: Template(f.read()) @@ -669,7 +621,7 @@ with open('templates/index.html') as f: sudo journalctl -u nordabiznes -n 50 | grep -i "sqlalchemy\|database" # Check database connectivity -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" +psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" ``` --- @@ -695,7 +647,7 @@ ssh maciejpi@57.128.200.27 sudo journalctl -u nordabiznes -n 100 | grep -i search # 3. Test database FTS -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz # Test FTS query SELECT name, ts_rank(search_vector, to_tsquery('polish', 'web')) AS score @@ -716,7 +668,7 @@ SELECT * FROM pg_extension WHERE extname = 'pg_trgm'; # Cause: search_vector not updated # Fix: Rebuild FTS index -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz UPDATE companies SET search_vector = to_tsvector('polish', @@ -750,7 +702,7 @@ grep -A 20 "SYNONYM_EXPANSION" search_service.py # Cause: Missing database indexes # Fix: Add indexes -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz CREATE INDEX IF NOT EXISTS idx_companies_search_vector ON companies USING gin(search_vector); CREATE INDEX IF NOT EXISTS idx_companies_name_trgm ON companies USING gin(name gin_trgm_ops); @@ -787,7 +739,7 @@ Quick check: ```bash # 1. Verify Gemini API key ssh maciejpi@57.128.200.27 -sudo -u www-data cat /var/www/nordabiznes/.env | grep GEMINI_API_KEY +cat /var/www/nordabiznes/.env | grep GEMINI_API_KEY # Should not be empty # 2. Test Gemini API directly @@ -877,7 +829,7 @@ sudo systemctl restart nordabiznes #### Verification ```bash -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies;" +psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies;" # Should return count ``` @@ -897,7 +849,7 @@ sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count( ```bash # 1. Check for slow queries -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT pid, now() - query_start AS duration, query FROM pg_stat_activity @@ -927,7 +879,7 @@ ALTER DATABASE nordabiz SET log_min_duration_statement = 1000; ```bash # Add appropriate indexes based on queries -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Example: Index on foreign key CREATE INDEX idx_company_news_company_id ON company_news(company_id); @@ -945,7 +897,7 @@ VACUUM ANALYZE; ```bash # Run vacuum -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz VACUUM ANALYZE; # For severe cases @@ -955,7 +907,7 @@ VACUUM FULL companies; -- Locks table! **If table statistics outdated:** ```bash -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz ANALYZE companies; ANALYZE users; ANALYZE ai_chat_messages; @@ -991,7 +943,7 @@ df -h # Check /var/lib/postgresql usage # 2. Check database size -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT pg_size_pretty(pg_database_size('nordabiz')); @@ -1038,7 +990,7 @@ sudo -u postgres pg_archivecleanup /var/lib/postgresql/*/main/pg_wal/ 0000000100 ```bash # Archive old data before deletion -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Example: Archive old AI chat messages (>6 months) CREATE TABLE ai_chat_messages_archive AS @@ -1067,7 +1019,7 @@ VACUUM FULL ai_chat_messages; ```bash # 1. Check current schema version -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz \dt # List all tables @@ -1079,7 +1031,7 @@ ls -la /var/www/nordabiznes/database/migrations/ # 3. Check Flask-Migrate status (if using Alembic) cd /var/www/nordabiznes -sudo -u www-data /var/www/nordabiznes/venv/bin/flask db current + /var/www/nordabiznes/venv/bin/flask db current ``` #### Solution @@ -1089,14 +1041,14 @@ sudo -u www-data /var/www/nordabiznes/venv/bin/flask db current ```bash # Re-run migration script cd /var/www/nordabiznes -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz < database/schema.sql +psql -h localhost -U nordabiz_app -d nordabiz < database/schema.sql ``` **If column added but missing:** ```bash # Add column manually -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz ALTER TABLE companies ADD COLUMN IF NOT EXISTS new_column VARCHAR(255); @@ -1108,16 +1060,16 @@ GRANT ALL ON TABLE companies TO nordabiz_app; ```bash # Rollback last migration -sudo -u www-data /var/www/nordabiznes/venv/bin/flask db downgrade + /var/www/nordabiznes/venv/bin/flask db downgrade # Re-apply -sudo -u www-data /var/www/nordabiznes/venv/bin/flask db upgrade + /var/www/nordabiznes/venv/bin/flask db upgrade ``` #### Verification ```bash -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz \d companies # Verify schema matches expected structure ``` @@ -1141,7 +1093,7 @@ sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz ```bash # 1. Check API usage in database ssh maciejpi@57.128.200.27 -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Gemini API usage today SELECT COUNT(*), SUM(input_tokens), SUM(output_tokens) @@ -1211,7 +1163,7 @@ pkill -f seo_audit.py # Create script: /var/www/nordabiznes/scripts/check_api_quotas.sh #!/bin/bash -GEMINI_COUNT=$(sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -t -c \ +GEMINI_COUNT=$(psql -h localhost -U nordabiz_app -d nordabiz -t -c \ "SELECT COUNT(*) FROM ai_api_costs WHERE DATE(created_at) = CURRENT_DATE;") if [ "$GEMINI_COUNT" -gt 1400 ]; then @@ -1240,7 +1192,7 @@ fi ```bash # 1. Test Gemini API directly -GEMINI_KEY=$(sudo -u www-data grep GEMINI_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) +GEMINI_KEY=$( grep GEMINI_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent" \ -H "x-goog-api-key: $GEMINI_KEY" \ @@ -1252,7 +1204,7 @@ ssh maciejpi@57.128.200.27 sudo journalctl -u nordabiznes -n 100 | grep -i gemini # 3. Check conversation ownership -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT id, user_id, created_at FROM ai_chat_conversations @@ -1268,7 +1220,7 @@ WHERE id = 123; -- Replace with conversation ID # OR context too long # Check last message for safety filter -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT message, ai_response, error_message FROM ai_chat_messages @@ -1325,7 +1277,7 @@ AND created_at < ( # Symptom: 401 Unauthorized or 403 Forbidden # Verify API key -sudo -u www-data grep GEMINI_API_KEY /var/www/nordabiznes/.env + grep GEMINI_API_KEY /var/www/nordabiznes/.env # Test key directly curl -H "x-goog-api-key: YOUR_KEY" \ @@ -1363,7 +1315,7 @@ curl -X POST https://nordabiznes.pl/api/chat \ ```bash # 1. Test PageSpeed API directly -PAGESPEED_KEY=$(sudo -u www-data grep GOOGLE_PAGESPEED_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) +PAGESPEED_KEY=$( grep GOOGLE_PAGESPEED_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://nordabiznes.pl&key=$PAGESPEED_KEY" @@ -1372,7 +1324,7 @@ ssh maciejpi@57.128.200.27 sudo journalctl -u nordabiznes -n 100 | grep -i pagespeed # 3. Check recent audits -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT company_id, url, seo_score, performance_score, audited_at, error_message @@ -1435,13 +1387,13 @@ python seo_audit.py --batch 1-10 ```bash # 1. Test Brave API directly -BRAVE_KEY=$(sudo -u www-data grep BRAVE_SEARCH_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) +BRAVE_KEY=$( grep BRAVE_SEARCH_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) curl -H "X-Subscription-Token: $BRAVE_KEY" \ "https://api.search.brave.com/res/v1/news/search?q=test&count=5" # 2. Check usage this month -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT COUNT(*) AS searches_this_month FROM company_news @@ -1496,7 +1448,7 @@ sudo systemctl restart nordabiznes ```bash # 1. Check user exists and is active ssh maciejpi@57.128.200.27 -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT id, email, is_active, email_verified, failed_login_attempts FROM users @@ -1519,7 +1471,7 @@ curl -c /tmp/cookies.txt -X POST http://localhost:5000/login \ ```bash # Check failed attempts -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT email, failed_login_attempts, last_failed_login FROM users @@ -1618,7 +1570,7 @@ curl -c /tmp/cookies.txt -X POST http://localhost:5000/login \ ```bash # 1. Check user role -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT id, email, is_admin, is_norda_member FROM users @@ -1639,7 +1591,7 @@ sudo journalctl -u nordabiznes -n 50 | grep -i "forbidden\|unauthorized" ```bash # Grant admin role -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz UPDATE users SET is_admin = TRUE WHERE email = 'admin@nordabiznes.pl'; @@ -1691,7 +1643,7 @@ WHERE email = 'user@example.com'; ```bash # 1. Check user reset token -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT email, reset_token, reset_token_expiry FROM users @@ -1702,7 +1654,7 @@ sudo journalctl -u nordabiznes -n 100 | grep -i "email\|smtp" # 3. Test MS Graph API (email service) # Check if AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID set -sudo -u www-data grep AZURE /var/www/nordabiznes/.env + grep AZURE /var/www/nordabiznes/.env ``` #### Solution @@ -1722,7 +1674,7 @@ WHERE email = 'user@example.com'; ```bash # Check MS Graph credentials -sudo -u www-data python3 << 'EOF' + python3 << 'EOF' import os from email_service import EmailService @@ -1743,14 +1695,14 @@ EOF ```bash # Generate new password hash -sudo -u www-data python3 << 'EOF' + python3 << 'EOF' from werkzeug.security import generate_password_hash password = "NewPassword123" print(generate_password_hash(password)) EOF # Update database -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz UPDATE users SET password_hash = 'HASH_FROM_ABOVE' WHERE email = 'user@example.com'; @@ -1786,7 +1738,7 @@ top -n 1 # Look at: CPU usage, memory usage, load average # 4. Check database query times -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT calls, mean_exec_time, query FROM pg_stat_statements @@ -1830,7 +1782,7 @@ ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements'; sudo systemctl restart postgresql # Check slow queries -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT calls, mean_exec_time, query FROM pg_stat_statements @@ -2016,7 +1968,7 @@ uptime # Load should be < number of CPU cores # 3. Identify CPU-heavy queries -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT pid, now() - query_start AS duration, state, query FROM pg_stat_activity @@ -2030,7 +1982,7 @@ ORDER BY now() - query_start DESC; ```bash # Kill long-running query -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT pg_terminate_backend(PID); # Add index to optimize query @@ -2085,11 +2037,10 @@ curl https://nordabiznes.pl/health } # Database health -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" +psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" -# NPM health (from proxy server) -ssh maciejpi@10.22.68.250 -docker ps | grep nginx-proxy-manager +# Nginx health (on OVH VPS) +ssh maciejpi@57.128.200.27 "sudo systemctl status nginx" # Should show: Up X hours # Flask service health @@ -2112,9 +2063,8 @@ sudo journalctl -u nordabiznes -f # PostgreSQL logs sudo journalctl -u postgresql -n 50 -# NPM logs -ssh maciejpi@10.22.68.250 -docker logs nginx-proxy-manager_app_1 --tail 50 -f +# Nginx logs (OVH VPS) +ssh maciejpi@57.128.200.27 "sudo tail -50 /var/log/nginx/error.log" # System logs sudo journalctl -n 100 @@ -2145,7 +2095,7 @@ fi ```bash # Database performance -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Connection count SELECT count(*) FROM pg_stat_activity; @@ -2254,7 +2204,7 @@ sudo systemctl restart nordabiznes # If restart fails, check manually cd /var/www/nordabiznes -sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py +/var/www/nordabiznes/venv/bin/python3 app.py # Read error message # 3. Common quick fixes: @@ -2303,7 +2253,7 @@ sudo systemctl stop nordabiznes sudo -u postgres pg_dump nordabiz > /tmp/nordabiz_emergency_$(date +%Y%m%d_%H%M%S).sql # 3. Assess damage -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Check table counts SELECT 'companies' AS table, count(*) FROM companies @@ -2341,7 +2291,7 @@ sudo systemctl start nordabiznes ```bash # Identify missing/corrupted records -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Example: Find companies with NULL required fields SELECT id, slug, name FROM companies WHERE name IS NULL; @@ -2372,7 +2322,7 @@ sudo -u postgres pg_dump nordabiz > /tmp/forensic_$(date +%Y%m%d_%H%M%S).sql sudo tar czf /tmp/www_forensic.tar.gz /var/www/nordabiznes/ # 3. Check for unauthorized access -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz -- Check for new admin users SELECT id, email, created_at, is_admin @@ -2406,7 +2356,7 @@ sudo find /var/www/nordabiznes/ -type f -mtime -1 -ls sudo grep -r "eval\|exec\|system\|subprocess" /var/www/nordabiznes/*.py # Check database for malicious data -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz +psql -h localhost -U nordabiz_app -d nordabiz SELECT * FROM users WHERE email LIKE '% interval '2 seconds' @@ -2492,7 +2442,7 @@ ORDER BY duration DESC; EOF # Check locks -sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF' +psql -h localhost -U nordabiz_app -d nordabiz << 'EOF' SELECT relation::regclass, mode, granted FROM pg_locks WHERE NOT granted; @@ -2528,16 +2478,16 @@ echo | openssl s_client -servername nordabiznes.pl -connect nordabiznes.pl:443 2 ssh maciejpi@57.128.200.27 # Gemini API -GEMINI_KEY=$(sudo -u www-data grep GEMINI_API_KEY .env | cut -d= -f2) +GEMINI_KEY=$( grep GEMINI_API_KEY .env | cut -d= -f2) curl -s -H "x-goog-api-key: $GEMINI_KEY" \ "https://generativelanguage.googleapis.com/v1beta/models" | jq '.models[0].name' # PageSpeed API -PAGESPEED_KEY=$(sudo -u www-data grep GOOGLE_PAGESPEED_API_KEY .env | cut -d= -f2) +PAGESPEED_KEY=$( grep GOOGLE_PAGESPEED_API_KEY .env | cut -d= -f2) curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://nordabiznes.pl&key=$PAGESPEED_KEY" | jq '.lighthouseResult.categories.performance.score' # Brave Search API -BRAVE_KEY=$(sudo -u www-data grep BRAVE_SEARCH_API_KEY .env | cut -d= -f2) +BRAVE_KEY=$( grep BRAVE_SEARCH_API_KEY .env | cut -d= -f2) curl -s -H "X-Subscription-Token: $BRAVE_KEY" \ "https://api.search.brave.com/res/v1/web/search?q=test&count=1" | jq '.web.results[0].title' diff --git a/docs/architecture/flows/06-http-request-flow.md b/docs/architecture/flows/06-http-request-flow.md index 5817c57..a155f7f 100644 --- a/docs/architecture/flows/06-http-request-flow.md +++ b/docs/architecture/flows/06-http-request-flow.md @@ -1,34 +1,28 @@ # HTTP Request Flow **Document Version:** 1.0 -**Last Updated:** 2026-01-10 -**Status:** Production LIVE +**Last Updated:** 2026-04-04 +**Status:** Production LIVE (OVH VPS) **Flow Type:** HTTP Request Handling & Response Cycle --- ## Overview -This document describes the **complete HTTP request flow** for the Norda Biznes Partner application, from external user through reverse proxy to Flask application and back. It covers: +This document describes the **complete HTTP request flow** for the Norda Biznes Partner application, from external user through nginx reverse proxy to Flask application and back. It covers: -- **Complete request path** (Internet → Fortigate → NPM → Flask → Response) -- **Critical port configurations** and routing decisions +- **Complete request path** (Internet → Nginx → Flask → Response) - **SSL/TLS termination** and security boundaries - **Request/response transformation** at each layer - **Common failure scenarios** and troubleshooting **Key Infrastructure:** -- **Public Entry:** 85.237.177.83:443 (Fortigate NAT) -- **Reverse Proxy:** NPM on 10.22.68.250:443 (SSL termination) -- **Backend Application:** Flask/Gunicorn on 57.128.200.27:5000 -- **Protocol Flow:** HTTPS → NPM → HTTP → Flask → HTTP → NPM → HTTPS +- **Public Entry:** 57.128.200.27:443 (OVH VPS, direct) +- **Reverse Proxy:** Nginx on 57.128.200.27:443 (SSL termination) +- **Backend Application:** Flask/Gunicorn on 127.0.0.1:5000 +- **Protocol Flow:** HTTPS → Nginx → HTTP (localhost) → Flask → HTTP → Nginx → HTTPS -**⚠️ CRITICAL CONFIGURATION:** -``` -NPM MUST forward to port 5000, NOT port 80! -Port 80 on NORDABIZ-01 runs nginx that redirects to HTTPS -Forwarding to port 80 causes infinite redirect loop -``` +> **Note:** The old on-prem setup used FortiGate NAT (85.237.177.83) and NPM (10.22.68.250) as intermediate layers. Production now runs directly on OVH VPS without FortiGate or NPM. **Related Documentation:** - Incident Report: `docs/INCIDENT_REPORT_20260102.md` (ERR_TOO_MANY_REDIRECTS) @@ -45,24 +39,19 @@ Forwarding to port 80 causes infinite redirect loop sequenceDiagram actor User participant Browser - participant Fortigate as 🛡️ Fortigate Firewall
85.237.177.83 - participant NPM as 🔒 NPM Reverse Proxy
10.22.68.250:443 - participant Flask as 🌐 Flask/Gunicorn
57.128.200.27:5000 + participant Nginx as 🔒 Nginx Reverse Proxy
57.128.200.27:443 + participant Flask as 🌐 Flask/Gunicorn
127.0.0.1:5000 participant DB as 💾 PostgreSQL
localhost:5432 Note over User,DB: SUCCESSFUL REQUEST FLOW User->>Browser: Navigate to https://nordabiznes.pl/ - Browser->>Fortigate: HTTPS GET / (Port 443) - Note over Fortigate: NAT Translation
85.237.177.83:443 → 10.22.68.250:443 + Browser->>Nginx: HTTPS GET / (Port 443) + Note over Nginx: SSL/TLS Termination
Let's Encrypt Certificate (certbot) - Fortigate->>NPM: HTTPS GET / (Port 443) - Note over NPM: SSL/TLS Termination
Let's Encrypt Certificate
nordabiznes.pl (Cert ID: 27) + Note over Nginx: Request Processing
• Validate certificate
• Decrypt HTTPS
• Extract headers
• proxy_pass to localhost:5000 - Note over NPM: Request Processing
• Validate certificate
• Decrypt HTTPS
• Extract headers
• Check routing rules - - NPM->>Flask: HTTP GET / (Port 5000) ✓ - Note over NPM,Flask: ⚠️ CRITICAL: Port 5000
NOT port 80! + Nginx->>Flask: HTTP GET / (127.0.0.1:5000) Note over Flask: Flask Request Handling
• WSGI via Gunicorn
• Route matching (app.py)
• Session validation
• CSRF check (if POST) @@ -71,26 +60,27 @@ sequenceDiagram Note over Flask: Template Rendering
• Jinja2 template: index.html
• Inject company data
• Apply filters & sorting - Flask->>NPM: HTTP 200 OK
Content-Type: text/html
Set-Cookie: session=...
HTML content + Flask->>Nginx: HTTP 200 OK
Content-Type: text/html
Set-Cookie: session=...
HTML content - Note over NPM: Response Processing
• Encrypt response (HTTPS)
• Add security headers
• HSTS, CSP, X-Frame-Options + Note over Nginx: Response Processing
• Encrypt response (HTTPS)
• Add security headers
• HSTS, CSP, X-Frame-Options - NPM->>Fortigate: HTTPS 200 OK (Port 443) - Fortigate->>Browser: HTTPS 200 OK + Nginx->>Browser: HTTPS 200 OK Browser->>User: Display page ``` -### 1.2 Failed Request (Wrong Port Configuration) +### 1.2 Historical: Failed Request (Old NPM Setup - Port Misconfiguration) + +> **Note:** This failure scenario applied to the old on-prem setup with NPM. It is no longer applicable to the current OVH VPS production setup. ```mermaid sequenceDiagram actor User participant Browser participant NPM as 🔒 NPM Reverse Proxy
10.22.68.250:443 - participant NginxSys as ⚠️ Nginx System
57.128.200.27:80 - participant Flask as 🌐 Flask/Gunicorn
57.128.200.27:5000 + participant NginxSys as ⚠️ Nginx System
10.22.68.249:80 + participant Flask as 🌐 Flask/Gunicorn
10.22.68.249:5000 - Note over User,Flask: FAILED REQUEST FLOW (REDIRECT LOOP) + Note over User,Flask: FAILED REQUEST FLOW (REDIRECT LOOP) — HISTORICAL User->>Browser: Navigate to https://nordabiznes.pl/ Browser->>NPM: HTTPS GET / (Port 443) @@ -120,11 +110,11 @@ sequenceDiagram ## 2. Layer-by-Layer Request Processing -### 2.1 Layer 1: Fortigate Firewall (NAT Gateway) +### 2.1 Layer 1: DNS + Direct Connection (OVH VPS) -**Server:** Fortigate Firewall -**Public IP:** 85.237.177.83 -**Function:** Network Address Translation (NAT) + Firewall +**Server:** OVH VPS +**Public IP:** 57.128.200.27 +**Function:** Direct internet-facing server (no NAT, no FortiGate for production) **Processing Steps:** @@ -160,12 +150,12 @@ Firewall: ALLOW from any to 85.237.177.83:443 (state: NEW,ESTABLISHED) --- -### 2.2 Layer 2: NPM Reverse Proxy (SSL Termination) +### 2.2 Layer 2: Nginx Reverse Proxy (SSL Termination) -**Server:** R11-REVPROXY-01 (VM 119) -**IP:** 10.22.68.250 +**Server:** OVH VPS (inpi-vps-waw01) +**IP:** 57.128.200.27 **Port:** 443 (HTTPS) -**Technology:** Nginx Proxy Manager (Docker) +**Technology:** Nginx with Let's Encrypt (certbot) **Processing Steps:** @@ -230,7 +220,7 @@ Firewall: ALLOW from any to 85.237.177.83:443 (state: NEW,ESTABLISHED) |-----------|-------|-------| | Domain Names | nordabiznes.pl, www.nordabiznes.pl | Primary + www alias | | Forward Scheme | http | NPM→Backend uses HTTP (secure internal network) | -| Forward Host | 57.128.200.27 | NORDABIZ-01 backend server | +| Forward Host | 127.0.0.1 | Localhost (same OVH VPS) | | **Forward Port** | **5000** | **Flask/Gunicorn port (CRITICAL!)** | | SSL Certificate | 27 (Let's Encrypt) | Auto-renewal enabled | | SSL Forced | Yes | Redirect HTTP→HTTPS | @@ -254,8 +244,8 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \ ### 2.3 Layer 3: Flask/Gunicorn Application (Request Processing) -**Server:** NORDABIZ-01 (VM 249) -**IP:** 57.128.200.27 +**Server:** OVH VPS (inpi-vps-waw01) +**IP:** 127.0.0.1 (localhost, via nginx proxy_pass) **Port:** 5000 **Technology:** Gunicorn 20.1.0 + Flask 3.0 @@ -263,7 +253,7 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \ 1. **Gunicorn Receives HTTP Request:** ``` - Binding: 0.0.0.0:5000 (all interfaces) + Binding: 127.0.0.1:5000 (all interfaces) Workers: 4 (Gunicorn worker processes) Worker Class: sync (synchronous workers) Timeout: 120 seconds @@ -382,11 +372,11 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \ ```ini # /etc/systemd/system/nordabiznes.service [Service] -User=www-data -Group=www-data +User=maciejpi +Group=maciejpi WorkingDirectory=/var/www/nordabiznes ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \ - --bind 0.0.0.0:5000 \ + --bind 127.0.0.1:5000 \ --workers 4 \ --timeout 120 \ --access-logfile /var/log/nordabiznes/access.log \ @@ -410,7 +400,7 @@ ssh maciejpi@57.128.200.27 "ps aux | grep gunicorn" ### 2.4 Layer 4: PostgreSQL Database (Data Retrieval) -**Server:** NORDABIZ-01 (same server as Flask) +**Server:** OVH VPS (same server as Flask) **IP:** 127.0.0.1 (localhost only) **Port:** 5432 **Technology:** PostgreSQL 14 @@ -490,7 +480,7 @@ sequenceDiagram Note over Flask: Response Construction Flask->>Flask: Render Jinja2 template
HTML content (50 KB) - Flask->>NPM: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 51234
Set-Cookie: session=...

[HTML content] + Flask->>Nginx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 51234
Set-Cookie: session=...

[HTML content] Note over NPM: Response Processing NPM->>NPM: Add security headers:
• Strict-Transport-Security
• X-Frame-Options: SAMEORIGIN
• X-Content-Type-Options: nosniff
• Referrer-Policy: strict-origin @@ -499,7 +489,7 @@ sequenceDiagram NPM->>NPM: Encrypt response (TLS 1.3)
Certificate: Let's Encrypt - NPM->>Fortigate: HTTPS 200 OK
Encrypted response
Size: 12 KB (gzip) + Nginx->>Browser: HTTPS 200 OK
Encrypted response
Size: 12 KB (gzip) Note over Fortigate: NAT reverse translation Fortigate->>Browser: HTTPS 200 OK
Source: 85.237.177.83 @@ -579,27 +569,27 @@ x-request-id: abc123def456 │ HTTP (Port 5000) ✓ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ FLASK/GUNICORN (NORDABIZ-01) │ +│ FLASK/GUNICORN (OVH VPS) │ │ IP: 57.128.200.27:5000 │ -│ Binding: 0.0.0.0:5000 │ +│ Binding: 127.0.0.1:5000 │ │ Workers: 4 (Gunicorn) │ │ Function: Application logic, template rendering │ └────────────────────────────┬────────────────────────────────────┘ │ SQL (localhost:5432) ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ POSTGRESQL (NORDABIZ-01) │ +│ POSTGRESQL (OVH VPS) │ │ IP: 127.0.0.1:5432 (localhost only) │ │ Database: nordabiz │ │ Function: Data storage │ └─────────────────────────────────────────────────────────────────┘ ``` -### 4.2 Port Table (NORDABIZ-01) +### 4.2 Port Table (OVH VPS) | Port | Service | Binding | User | Purpose | NPM Should Use? | |------|---------|---------|------|---------|-----------------| -| **5000** | **Gunicorn/Flask** | **0.0.0.0** | **www-data** | **Main Application** | **✓ YES** | +| **5000** | **Gunicorn/Flask** | **0.0.0.0** | **maciejpi** | **Main Application** | **✓ YES** | | 80 | Nginx (system) | 0.0.0.0 | root | HTTP→HTTPS redirect | ❌ NO (causes loop!) | | 443 | Nginx (system) | 0.0.0.0 | root | HTTPS redirect | ❌ NO (NPM handles SSL) | | 5432 | PostgreSQL | 127.0.0.1 | postgres | Database | ❌ NO (localhost only) | @@ -607,7 +597,7 @@ x-request-id: abc123def456 **⚠️ CRITICAL WARNING:** ``` -Port 80 and 443 on NORDABIZ-01 run a system nginx that: +Port 80 and 443 on OVH VPS run a system nginx that: 1. Redirects ALL HTTP requests to HTTPS 2. Redirects ALL HTTPS requests to https://nordabiznes.pl @@ -629,7 +619,7 @@ SOLUTION: NPM must ALWAYS forward to port 5000! **Flow:** ``` -User → NPM → Flask → Static file handler → Return CSS +User → Nginx → Flask → Static file handler → Return CSS ``` **Flask Handling:** @@ -650,7 +640,7 @@ def static_files(filename): **Flow:** ``` -User → NPM → Flask → API route → Database → JSON response +User → Nginx → Flask → API route → Database → JSON response ``` **Response:** @@ -682,7 +672,7 @@ Access-Control-Allow-Origin: * (if CORS enabled) **Flow:** ``` -User → NPM → Flask → CSRF validation → Auth check → Database → Redirect +User → Nginx → Flask → CSRF validation → Auth check → Database → Redirect ``` **Additional Processing:** @@ -707,7 +697,7 @@ Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax **Flow:** ``` -Monitor → NPM → Flask → Simple response (no DB query) +Monitor → Nginx → Flask → Simple response (no DB query) ``` **Response:** @@ -802,7 +792,7 @@ graph TB |-------|-----------------|-------| | Fortigate NAT | < 1 ms | Hardware NAT, negligible latency | | NPM SSL Termination | 10-20 ms | TLS handshake + decryption | -| NPM → Flask Network | < 1 ms | Internal 10 Gbps network | +| Nginx → Flask Network | < 1 ms | Internal 10 Gbps network | | Flask Request Handling | 50-150 ms | Route matching, template rendering | | Database Query | 10-30 ms | Indexed queries, connection pool | | Template Rendering | 20-50 ms | Jinja2 template compilation | @@ -898,7 +888,7 @@ ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes" # 2. Check if port 5000 is listening ssh maciejpi@57.128.200.27 "sudo netstat -tlnp | grep 5000" -# Expected: 0.0.0.0:5000 ... gunicorn +# Expected: 127.0.0.1:5000 ... gunicorn # 3. Test direct connection curl -I http://57.128.200.27:5000/health @@ -1026,7 +1016,7 @@ ssh maciejpi@57.128.200.27 "sudo -u postgres psql -c \ **Network Connectivity:** ```bash -# Test NPM → Flask connectivity +# Test Nginx → Flask connectivity ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health" # Test Flask → Database connectivity @@ -1103,12 +1093,12 @@ After=network.target postgresql.service [Service] Type=notify -User=www-data -Group=www-data +User=maciejpi +Group=maciejpi WorkingDirectory=/var/www/nordabiznes Environment="PATH=/var/www/nordabiznes/venv/bin" ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \ - --bind 0.0.0.0:5000 \ + --bind 127.0.0.1:5000 \ --workers 4 \ --worker-class sync \ --timeout 120 \ @@ -1127,7 +1117,7 @@ WantedBy=multi-user.target ``` **Parameters Explained:** -- `--bind 0.0.0.0:5000` - Listen on all interfaces, port 5000 +- `--bind 127.0.0.1:5000` - Listen on all interfaces, port 5000 - `--workers 4` - 4 worker processes (matches CPU cores) - `--worker-class sync` - Synchronous workers (default) - `--timeout 120` - 120 second request timeout @@ -1221,7 +1211,7 @@ ssh maciejpi@57.128.200.27 "sudo tail -f /var/log/postgresql/postgresql-14-main. - Database size growth - Table bloat -**System Metrics (NORDABIZ-01):** +**System Metrics (OVH VPS):** - CPU usage (should be < 80%) - Memory usage (should be < 6 GB / 8 GB) - Disk I/O (should be low)