docs: aktualizacja architektury po migracji na OVH VPS + e-deklaracja w roadmap

- ROADMAP: dodano funkcję #2 (e-deklaracja PZ) z analizą flow PDF + samodzielny podpis
- architecture/03,07,08,09,11 + flows/06: aktualizacja pod OVH VPS (IP, user maciejpi zamiast www-data, brak NPM dla prod)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-04-15 07:00:14 +02:00
parent 5893645c46
commit bfd48a6e20
7 changed files with 263 additions and 297 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
```

View File

@ -809,7 +809,7 @@ SMTP_PASSWORD=<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=<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 <npm-container-id>
- ✅ 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)

View File

@ -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 '%<script%';
SELECT * FROM companies WHERE description LIKE '%<script%';
@ -2421,7 +2371,7 @@ SELECT * FROM companies WHERE description LIKE '%<script%';
# - API keys
# 2. Revoke compromised sessions
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
psql -h localhost -U nordabiz_app -d nordabiz
DELETE FROM flask_sessions; -- Force all users to re-login
# 3. Update all API keys
@ -2449,7 +2399,7 @@ curl -I https://nordabiznes.pl/health && \
echo -e "\n=== Service Status ===" && \
ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes --no-pager | head -5" && \
echo -e "\n=== Database Connection ===" && \
ssh maciejpi@57.128.200.27 "sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c 'SELECT count(*) FROM companies;'" && \
ssh maciejpi@57.128.200.27 "psql -h localhost -U nordabiz_app -d nordabiz -c 'SELECT count(*) FROM companies;'" && \
echo -e "\n=== Server Load ===" && \
ssh maciejpi@57.128.200.27 "uptime"
```
@ -2476,7 +2426,7 @@ ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health"
```bash
# Database quick stats
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
SELECT 'Companies' AS metric, count(*) AS value FROM companies
UNION ALL SELECT 'Users', count(*) FROM users
UNION ALL SELECT 'Active sessions', count(*) FROM pg_stat_activity
@ -2484,7 +2434,7 @@ UNION ALL SELECT 'DB size (MB)', pg_database_size('nordabiz')/1024/1024;
EOF
# Find slow queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > 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'

View File

@ -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<br/>85.237.177.83
participant NPM as 🔒 NPM Reverse Proxy<br/>10.22.68.250:443
participant Flask as 🌐 Flask/Gunicorn<br/>57.128.200.27:5000
participant Nginx as 🔒 Nginx Reverse Proxy<br/>57.128.200.27:443
participant Flask as 🌐 Flask/Gunicorn<br/>127.0.0.1:5000
participant DB as 💾 PostgreSQL<br/>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<br/>85.237.177.83:443 → 10.22.68.250:443
Browser->>Nginx: HTTPS GET / (Port 443)
Note over Nginx: SSL/TLS Termination<br/>Let's Encrypt Certificate (certbot)
Fortigate->>NPM: HTTPS GET / (Port 443)
Note over NPM: SSL/TLS Termination<br/>Let's Encrypt Certificate<br/>nordabiznes.pl (Cert ID: 27)
Note over Nginx: Request Processing<br/>• Validate certificate<br/>• Decrypt HTTPS<br/>• Extract headers<br/>• proxy_pass to localhost:5000
Note over NPM: Request Processing<br/>• Validate certificate<br/>• Decrypt HTTPS<br/>• Extract headers<br/>• Check routing rules
NPM->>Flask: HTTP GET / (Port 5000) ✓
Note over NPM,Flask: ⚠️ CRITICAL: Port 5000<br/>NOT port 80!
Nginx->>Flask: HTTP GET / (127.0.0.1:5000)
Note over Flask: Flask Request Handling<br/>• WSGI via Gunicorn<br/>• Route matching (app.py)<br/>• Session validation<br/>• CSRF check (if POST)
@ -71,26 +60,27 @@ sequenceDiagram
Note over Flask: Template Rendering<br/>• Jinja2 template: index.html<br/>• Inject company data<br/>• Apply filters & sorting
Flask->>NPM: HTTP 200 OK<br/>Content-Type: text/html<br/>Set-Cookie: session=...<br/>HTML content
Flask->>Nginx: HTTP 200 OK<br/>Content-Type: text/html<br/>Set-Cookie: session=...<br/>HTML content
Note over NPM: Response Processing<br/>• Encrypt response (HTTPS)<br/>• Add security headers<br/>• HSTS, CSP, X-Frame-Options
Note over Nginx: Response Processing<br/>• Encrypt response (HTTPS)<br/>• Add security headers<br/>• 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<br/>10.22.68.250:443
participant NginxSys as ⚠️ Nginx System<br/>57.128.200.27:80
participant Flask as 🌐 Flask/Gunicorn<br/>57.128.200.27:5000
participant NginxSys as ⚠️ Nginx System<br/>10.22.68.249:80
participant Flask as 🌐 Flask/Gunicorn<br/>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<br/>HTML content (50 KB)
Flask->>NPM: HTTP/1.1 200 OK<br/>Content-Type: text/html; charset=utf-8<br/>Content-Length: 51234<br/>Set-Cookie: session=...<br/><br/>[HTML content]
Flask->>Nginx: HTTP/1.1 200 OK<br/>Content-Type: text/html; charset=utf-8<br/>Content-Length: 51234<br/>Set-Cookie: session=...<br/><br/>[HTML content]
Note over NPM: Response Processing
NPM->>NPM: Add security headers:<br/>• Strict-Transport-Security<br/>• X-Frame-Options: SAMEORIGIN<br/>• X-Content-Type-Options: nosniff<br/>• Referrer-Policy: strict-origin
@ -499,7 +489,7 @@ sequenceDiagram
NPM->>NPM: Encrypt response (TLS 1.3)<br/>Certificate: Let's Encrypt
NPM->>Fortigate: HTTPS 200 OK<br/>Encrypted response<br/>Size: 12 KB (gzip)
Nginx->>Browser: HTTPS 200 OK<br/>Encrypted response<br/>Size: 12 KB (gzip)
Note over Fortigate: NAT reverse translation
Fortigate->>Browser: HTTPS 200 OK<br/>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)