Compare commits
No commits in common. "910ab4267689b7d18b1d5e40a97f6f2279479e84" and "5893645c46c491bddb97fef25e48d139b461d2e9" have entirely different histories.
910ab42676
...
5893645c46
10
.gitignore
vendored
@ -92,13 +92,3 @@ data/ceidg_json/
|
||||
# Poufne materiały - NIGDY nie commitować
|
||||
.private/
|
||||
docs/MEETING_*.md
|
||||
|
||||
# Claude Code / narzędzia AI - lokalne, nie commitować
|
||||
# (.claude/commands/ i .claude/settings.json są tracked - patrz poniżej)
|
||||
.claude/worktrees/
|
||||
.claude/scheduled_tasks.lock
|
||||
.remember/
|
||||
.superpowers/
|
||||
|
||||
# Runtime uploads (zarządzane przez aplikację)
|
||||
static/uploads/announcements/
|
||||
|
||||
@ -93,35 +93,6 @@ 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)
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
---
|
||||
|
||||
> **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.
|
||||
> **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.
|
||||
|
||||
## 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 (10.22.68.249) 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 (57.128.200.27) 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:** `maciejpi:maciejpi`
|
||||
- **Owner:** `www-data:www-data`
|
||||
- **⚠️ 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 maciejpi psql -h 127.0.0.1 -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
sudo -u www-data 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
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
---
|
||||
|
||||
> **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.
|
||||
> **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.
|
||||
|
||||
## Overview
|
||||
|
||||
@ -422,9 +422,8 @@ App Server (57.128.200.27)
|
||||
|
||||
| Record Type | Name | Value | TTL | Purpose |
|
||||
|-------------|------|-------|-----|---------|
|
||||
| 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) |
|
||||
| A | nordabiznes.pl | 85.237.177.83 | 3600 | Main website |
|
||||
| A | www.nordabiznes.pl | 85.237.177.83 | 3600 | WWW subdomain |
|
||||
| MX | nordabiznes.pl | (Not configured) | - | No email hosting |
|
||||
| TXT | nordabiznes.pl | (SPF, DKIM if configured) | - | Email authentication |
|
||||
|
||||
@ -432,11 +431,11 @@ App Server (57.128.200.27)
|
||||
```bash
|
||||
# Check DNS resolution
|
||||
dig nordabiznes.pl +short
|
||||
# Expected: 57.128.200.27
|
||||
# Expected: 85.237.177.83
|
||||
|
||||
# Check from Google DNS
|
||||
dig @8.8.8.8 nordabiznes.pl +short
|
||||
# Expected: 57.128.200.27
|
||||
# Expected: 85.237.177.83
|
||||
|
||||
# Check WHOIS
|
||||
whois nordabiznes.pl
|
||||
@ -500,19 +499,23 @@ default via 10.22.68.1 dev ens18 # All non-local traffic → Fortigate
|
||||
```
|
||||
User Browser (Internet)
|
||||
↓ DNS query
|
||||
OVH DNS: nordabiznes.pl → 57.128.200.27
|
||||
OVH DNS: nordabiznes.pl → 85.237.177.83
|
||||
↓ HTTPS :443
|
||||
Nginx @ OVH VPS (57.128.200.27:443)
|
||||
↓ SSL termination, proxy_pass
|
||||
Gunicorn (127.0.0.1:5000)
|
||||
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)
|
||||
↓ HTTP request processing
|
||||
Flask → PostgreSQL (127.0.0.1:5432)
|
||||
↓ SQL query
|
||||
PostgreSQL → Flask (result set)
|
||||
↓ HTML rendering
|
||||
Flask → Nginx (HTTP response)
|
||||
Flask → NPM (HTTP response)
|
||||
↓ SSL encryption
|
||||
Nginx → User Browser (57.128.200.27:443)
|
||||
NPM → Fortigate LAN (HTTPS)
|
||||
↓ NAT reverse
|
||||
Fortigate WAN → User Browser (85.237.177.83:443)
|
||||
```
|
||||
|
||||
**Total Hops:** 6 (external) + 3 (internal) = 9 hops
|
||||
|
||||
@ -364,7 +364,7 @@ curl -vI https://nordabiznes.pl 2>&1 | grep "expire date"
|
||||
### Production Environment Variables
|
||||
|
||||
**Location:** `/var/www/nordabiznes/.env`
|
||||
**Owner:** `maciejpi:maciejpi`
|
||||
**Owner:** `www-data:www-data`
|
||||
**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 maciejpi maciejpi ... .env
|
||||
# Expected: -rw------- 1 www-data www-data ... .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
|
||||
psql -U nordabiz_app -d nordabiz -h 127.0.0.1
|
||||
sudo -u www-data 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/maciejpi/.ssh/`
|
||||
- Location: `/home/www-data/.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 | 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 |
|
||||
| `/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 |
|
||||
|
||||
### 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 | maciejpi |
|
||||
| `/var/log/nordabiznes/gunicorn_error.log` | Gunicorn error log | Daily | maciejpi |
|
||||
| `/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/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 maciejpi:maciejpi /var/www/nordabiznes
|
||||
sudo -u maciejpi git clone https://10.22.68.180:3000/maciejpi/nordabiz.git /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
|
||||
```
|
||||
5. **Restore .env file:**
|
||||
```bash
|
||||
sudo cp /path/to/.env.backup /var/www/nordabiznes/.env
|
||||
sudo chown maciejpi:maciejpi /var/www/nordabiznes/.env
|
||||
sudo chown www-data:www-data /var/www/nordabiznes/.env
|
||||
sudo chmod 600 /var/www/nordabiznes/.env
|
||||
```
|
||||
6. **Install Python dependencies:**
|
||||
```bash
|
||||
cd /var/www/nordabiznes
|
||||
sudo -u maciejpi python3 -m venv venv
|
||||
sudo -u maciejpi venv/bin/pip install -r requirements.txt
|
||||
sudo -u www-data python3 -m venv venv
|
||||
sudo -u www-data 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 maciejpi git status # Ensure clean state
|
||||
sudo -u www-data 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 maciejpi git pull
|
||||
sudo -u www-data 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 maciejpi git reset --hard HEAD~1
|
||||
sudo -u www-data 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
|
||||
psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c "SELECT COUNT(*) FROM companies;"
|
||||
sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c "SELECT COUNT(*) FROM companies;"
|
||||
|
||||
# Check recent data
|
||||
psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c \
|
||||
sudo -u www-data 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 maciejpi git reset --hard HEAD~1
|
||||
sudo -u www-data git reset --hard HEAD~1
|
||||
sudo systemctl start nordabiznes
|
||||
curl -I https://nordabiznes.pl/health
|
||||
```
|
||||
|
||||
@ -809,7 +809,7 @@ SMTP_PASSWORD=<password>
|
||||
|
||||
**Secrets Storage:**
|
||||
- **Development:** `.env` file (gitignored)
|
||||
- **Production:** `.env` file on server (owned by maciejpi, mode 600)
|
||||
- **Production:** `.env` file on server (owned by www-data, 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 maciejpi maciejpi .env # Mode 600 (owner read/write only)
|
||||
-rw------- 1 www-data www-data .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:** maciejpi (Flask app user)
|
||||
- **Owner:** www-data (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 (admin + app user)
|
||||
- **Users:** maciejpi (primary admin), www-data (app deployment)
|
||||
|
||||
**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 # Whitelist users
|
||||
AllowUsers maciejpi www-data # Whitelist users
|
||||
```
|
||||
|
||||
**SSH Key Management:**
|
||||
@ -1312,16 +1312,16 @@ AllowUsers maciejpi # Whitelist users
|
||||
**File Permissions:**
|
||||
```bash
|
||||
# Application directory
|
||||
/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
|
||||
/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
|
||||
|
||||
# Database directory
|
||||
/var/lib/postgresql/ # Owner: postgres, Mode: 700
|
||||
```
|
||||
|
||||
**User Separation:**
|
||||
- **maciejpi:** Flask application (limited privileges)
|
||||
- **www-data:** 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 (maciejpi user)
|
||||
- ✅ Principle of least privilege (www-data user)
|
||||
- ❌ IDS/IPS
|
||||
- ❌ DDoS protection (beyond Fortigate)
|
||||
|
||||
|
||||
@ -71,36 +71,48 @@ graph TD
|
||||
|
||||
## 2. Infrastructure & Network Issues
|
||||
|
||||
### 2.1 Site Not Accessible / ERR_TOO_MANY_REDIRECTS
|
||||
### 2.1 ERR_TOO_MANY_REDIRECTS
|
||||
|
||||
**Severity:** CRITICAL
|
||||
|
||||
> **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.
|
||||
**Incident History:** 2026-01-02 (30 min outage)
|
||||
|
||||
#### Symptoms
|
||||
|
||||
- Browser error or timeout accessing https://nordabiznes.pl
|
||||
- Portal completely inaccessible
|
||||
- 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
|
||||
|
||||
#### Diagnosis
|
||||
|
||||
```bash
|
||||
# 1. Check nginx status on OVH VPS
|
||||
ssh maciejpi@57.128.200.27 "sudo systemctl status nginx"
|
||||
# 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;"
|
||||
|
||||
# 2. Check Gunicorn status
|
||||
ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes"
|
||||
# Expected output:
|
||||
# 27|["nordabiznes.pl","www.nordabiznes.pl"]|57.128.200.27|5000
|
||||
|
||||
# 3. Test backend directly
|
||||
ssh maciejpi@57.128.200.27 "curl -I http://localhost:5000/health"
|
||||
# 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
|
||||
# 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
|
||||
@ -115,30 +127,54 @@ open http://10.22.68.250:81
|
||||
# 3. Edit configuration:
|
||||
# - Forward Hostname/IP: 57.128.200.27
|
||||
# - Forward Port: 5000 (CRITICAL!)
|
||||
#### Solution
|
||||
# - Scheme: http
|
||||
# 4. Save and test
|
||||
```
|
||||
|
||||
```bash
|
||||
# If nginx is down:
|
||||
ssh maciejpi@57.128.200.27 "sudo systemctl restart nginx"
|
||||
**Option B: Fix via NPM API**
|
||||
|
||||
# If Gunicorn is down:
|
||||
ssh maciejpi@57.128.200.27 "sudo systemctl restart nordabiznes"
|
||||
```python
|
||||
import requests
|
||||
|
||||
# If nginx config is broken:
|
||||
ssh maciejpi@57.128.200.27 "sudo nginx -t && sudo systemctl reload nginx"
|
||||
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
|
||||
)
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
- Monitor /health endpoint for non-200 responses
|
||||
- Test nginx config before reload: `sudo nginx -t`
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
@ -149,13 +185,14 @@ curl -I https://nordabiznes.pl/health
|
||||
#### Symptoms
|
||||
|
||||
- Browser shows "502 Bad Gateway" error
|
||||
- nginx error log shows "upstream connection failed"
|
||||
- NPM logs show "upstream connection failed"
|
||||
- Site completely inaccessible
|
||||
|
||||
#### Root Causes
|
||||
|
||||
1. Flask/Gunicorn service stopped
|
||||
2. Gunicorn not listening on 127.0.0.1:5000
|
||||
2. Backend server (57.128.200.27) unreachable
|
||||
3. Firewall blocking port 5000
|
||||
|
||||
#### Diagnosis
|
||||
|
||||
@ -196,26 +233,28 @@ curl http://localhost:5000/health
|
||||
```bash
|
||||
# Check for syntax errors
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/python3 -m py_compile app.py
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py
|
||||
|
||||
# Check for missing dependencies
|
||||
/var/www/nordabiznes/venv/bin/python3 -c "import flask; import sqlalchemy"
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -c "import flask; import sqlalchemy"
|
||||
|
||||
# Check environment variables
|
||||
cat /var/www/nordabiznes/.env | grep -v "PASSWORD\|SECRET\|KEY"
|
||||
sudo -u www-data cat /var/www/nordabiznes/.env | grep -v "PASSWORD\|SECRET\|KEY"
|
||||
|
||||
# Try running manually (for debugging)
|
||||
/var/www/nordabiznes/venv/bin/python3 app.py
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py
|
||||
```
|
||||
|
||||
**If Gunicorn not responding:**
|
||||
**If network issue:**
|
||||
|
||||
```bash
|
||||
# Check if Gunicorn is listening
|
||||
ssh maciejpi@57.128.200.27 "ss -tlnp | grep 5000"
|
||||
# Test connectivity from NPM to backend
|
||||
ssh maciejpi@10.22.68.250
|
||||
curl -I http://57.128.200.27:5000/health
|
||||
|
||||
# Check nginx error log
|
||||
ssh maciejpi@57.128.200.27 "sudo tail -20 /var/log/nginx/error.log"
|
||||
# Check firewall rules
|
||||
ssh maciejpi@57.128.200.27
|
||||
sudo iptables -L -n | grep 5000
|
||||
```
|
||||
|
||||
#### Verification
|
||||
@ -253,11 +292,11 @@ ps aux | grep gunicorn
|
||||
# Look for zombie workers or high CPU usage
|
||||
|
||||
# 2. Check database connections
|
||||
psql -h localhost -U nordabiz_app -d nordabiz -c \
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c \
|
||||
"SELECT count(*) FROM pg_stat_activity WHERE datname = 'nordabiz';"
|
||||
|
||||
# 3. Check for long-running queries
|
||||
psql -h localhost -U nordabiz_app -d nordabiz -c \
|
||||
sudo -u www-data 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';"
|
||||
@ -277,7 +316,7 @@ sudo journalctl -u nordabiznes -n 100 --no-pager | grep -E "slow|timeout|took"
|
||||
|
||||
```bash
|
||||
# Identify and kill long-running query
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
# Find problematic query
|
||||
SELECT pid, query FROM pg_stat_activity
|
||||
@ -344,8 +383,11 @@ 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 certbot certificate status
|
||||
ssh maciejpi@57.128.200.27 "sudo certbot certificates"
|
||||
# 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;"
|
||||
```
|
||||
|
||||
#### Solution
|
||||
@ -353,12 +395,18 @@ ssh maciejpi@57.128.200.27 "sudo certbot certificates"
|
||||
**If certificate expired:**
|
||||
|
||||
```bash
|
||||
# certbot auto-renews Let's Encrypt certificates
|
||||
# Force renewal:
|
||||
ssh maciejpi@57.128.200.27 "sudo certbot renew --force-renewal"
|
||||
# NPM auto-renews Let's Encrypt certificates
|
||||
# Force renewal via NPM UI or API
|
||||
|
||||
# Reload nginx after renewal:
|
||||
ssh maciejpi@57.128.200.27 "sudo systemctl reload nginx"
|
||||
# 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
|
||||
```
|
||||
|
||||
**If mixed content warnings:**
|
||||
@ -397,9 +445,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 DNS points to OVH VPS
|
||||
dig nordabiznes.pl +short
|
||||
# Expected: 57.128.200.27
|
||||
# 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
|
||||
```
|
||||
|
||||
#### Solution
|
||||
@ -446,7 +494,7 @@ sudo journalctl -u nordabiznes -n 100 --no-pager
|
||||
|
||||
# 3. Try manual start for detailed error
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/python3 app.py
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py
|
||||
# Read the traceback carefully
|
||||
```
|
||||
|
||||
@ -459,12 +507,12 @@ cd /var/www/nordabiznes
|
||||
# Cause: Recent code change introduced syntax error
|
||||
|
||||
# Fix: Check syntax
|
||||
/var/www/nordabiznes/venv/bin/python3 -m py_compile app.py
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py
|
||||
|
||||
# Rollback if necessary
|
||||
cd /var/www/nordabiznes
|
||||
git log --oneline -5
|
||||
git revert HEAD # or specific commit
|
||||
sudo -u www-data git log --oneline -5
|
||||
sudo -u www-data git revert HEAD # or specific commit
|
||||
sudo systemctl restart nordabiznes
|
||||
```
|
||||
|
||||
@ -475,8 +523,8 @@ sudo systemctl restart nordabiznes
|
||||
# Cause: .env file missing or incomplete
|
||||
|
||||
# Fix: Check .env exists and has required variables
|
||||
ls -la /var/www/nordabiznes/.env
|
||||
cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l
|
||||
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
|
||||
# Should have ~20 environment variables
|
||||
|
||||
# Required variables (add if missing):
|
||||
@ -499,7 +547,7 @@ cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Test connection
|
||||
psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
|
||||
# If password wrong, update .env and restart
|
||||
```
|
||||
@ -512,10 +560,10 @@ psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
|
||||
# Fix: Reinstall dependencies
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/pip install -r requirements.txt
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/pip install -r requirements.txt
|
||||
|
||||
# Verify specific package
|
||||
/var/www/nordabiznes/venv/bin/pip show flask
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/pip show flask
|
||||
```
|
||||
|
||||
**E. Port 5000 Already in Use**
|
||||
@ -587,7 +635,7 @@ sudo journalctl -u nordabiznes -n 100 | grep -i jinja
|
||||
|
||||
# Test template syntax
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/python3 -c "
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -c "
|
||||
from jinja2 import Template
|
||||
with open('templates/index.html') as f:
|
||||
Template(f.read())
|
||||
@ -621,7 +669,7 @@ with open('templates/index.html') as f:
|
||||
sudo journalctl -u nordabiznes -n 50 | grep -i "sqlalchemy\|database"
|
||||
|
||||
# Check database connectivity
|
||||
psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
```
|
||||
|
||||
---
|
||||
@ -647,7 +695,7 @@ ssh maciejpi@57.128.200.27
|
||||
sudo journalctl -u nordabiznes -n 100 | grep -i search
|
||||
|
||||
# 3. Test database FTS
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
# Test FTS query
|
||||
SELECT name, ts_rank(search_vector, to_tsquery('polish', 'web')) AS score
|
||||
@ -668,7 +716,7 @@ SELECT * FROM pg_extension WHERE extname = 'pg_trgm';
|
||||
# Cause: search_vector not updated
|
||||
|
||||
# Fix: Rebuild FTS index
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
UPDATE companies SET search_vector =
|
||||
to_tsvector('polish',
|
||||
@ -702,7 +750,7 @@ grep -A 20 "SYNONYM_EXPANSION" search_service.py
|
||||
# Cause: Missing database indexes
|
||||
|
||||
# Fix: Add indexes
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data 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);
|
||||
@ -739,7 +787,7 @@ Quick check:
|
||||
```bash
|
||||
# 1. Verify Gemini API key
|
||||
ssh maciejpi@57.128.200.27
|
||||
cat /var/www/nordabiznes/.env | grep GEMINI_API_KEY
|
||||
sudo -u www-data cat /var/www/nordabiznes/.env | grep GEMINI_API_KEY
|
||||
# Should not be empty
|
||||
|
||||
# 2. Test Gemini API directly
|
||||
@ -829,7 +877,7 @@ sudo systemctl restart nordabiznes
|
||||
#### Verification
|
||||
|
||||
```bash
|
||||
psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies;"
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies;"
|
||||
# Should return count
|
||||
```
|
||||
|
||||
@ -849,7 +897,7 @@ psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies
|
||||
|
||||
```bash
|
||||
# 1. Check for slow queries
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT pid, now() - query_start AS duration, query
|
||||
FROM pg_stat_activity
|
||||
@ -879,7 +927,7 @@ ALTER DATABASE nordabiz SET log_min_duration_statement = 1000;
|
||||
|
||||
```bash
|
||||
# Add appropriate indexes based on queries
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data 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);
|
||||
@ -897,7 +945,7 @@ VACUUM ANALYZE;
|
||||
|
||||
```bash
|
||||
# Run vacuum
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
VACUUM ANALYZE;
|
||||
|
||||
# For severe cases
|
||||
@ -907,7 +955,7 @@ VACUUM FULL companies; -- Locks table!
|
||||
**If table statistics outdated:**
|
||||
|
||||
```bash
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
ANALYZE companies;
|
||||
ANALYZE users;
|
||||
ANALYZE ai_chat_messages;
|
||||
@ -943,7 +991,7 @@ df -h
|
||||
# Check /var/lib/postgresql usage
|
||||
|
||||
# 2. Check database size
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT pg_size_pretty(pg_database_size('nordabiz'));
|
||||
|
||||
@ -990,7 +1038,7 @@ sudo -u postgres pg_archivecleanup /var/lib/postgresql/*/main/pg_wal/ 0000000100
|
||||
|
||||
```bash
|
||||
# Archive old data before deletion
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
-- Example: Archive old AI chat messages (>6 months)
|
||||
CREATE TABLE ai_chat_messages_archive AS
|
||||
@ -1019,7 +1067,7 @@ VACUUM FULL ai_chat_messages;
|
||||
|
||||
```bash
|
||||
# 1. Check current schema version
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
\dt
|
||||
# List all tables
|
||||
|
||||
@ -1031,7 +1079,7 @@ ls -la /var/www/nordabiznes/database/migrations/
|
||||
|
||||
# 3. Check Flask-Migrate status (if using Alembic)
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/flask db current
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/flask db current
|
||||
```
|
||||
|
||||
#### Solution
|
||||
@ -1041,14 +1089,14 @@ cd /var/www/nordabiznes
|
||||
```bash
|
||||
# Re-run migration script
|
||||
cd /var/www/nordabiznes
|
||||
psql -h localhost -U nordabiz_app -d nordabiz < database/schema.sql
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz < database/schema.sql
|
||||
```
|
||||
|
||||
**If column added but missing:**
|
||||
|
||||
```bash
|
||||
# Add column manually
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS new_column VARCHAR(255);
|
||||
|
||||
@ -1060,16 +1108,16 @@ GRANT ALL ON TABLE companies TO nordabiz_app;
|
||||
|
||||
```bash
|
||||
# Rollback last migration
|
||||
/var/www/nordabiznes/venv/bin/flask db downgrade
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/flask db downgrade
|
||||
|
||||
# Re-apply
|
||||
/var/www/nordabiznes/venv/bin/flask db upgrade
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/flask db upgrade
|
||||
```
|
||||
|
||||
#### Verification
|
||||
|
||||
```bash
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
\d companies
|
||||
# Verify schema matches expected structure
|
||||
```
|
||||
@ -1093,7 +1141,7 @@ psql -h localhost -U nordabiz_app -d nordabiz
|
||||
```bash
|
||||
# 1. Check API usage in database
|
||||
ssh maciejpi@57.128.200.27
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
-- Gemini API usage today
|
||||
SELECT COUNT(*), SUM(input_tokens), SUM(output_tokens)
|
||||
@ -1163,7 +1211,7 @@ pkill -f seo_audit.py
|
||||
# Create script: /var/www/nordabiznes/scripts/check_api_quotas.sh
|
||||
|
||||
#!/bin/bash
|
||||
GEMINI_COUNT=$(psql -h localhost -U nordabiz_app -d nordabiz -t -c \
|
||||
GEMINI_COUNT=$(sudo -u www-data 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
|
||||
@ -1192,7 +1240,7 @@ fi
|
||||
|
||||
```bash
|
||||
# 1. Test Gemini API directly
|
||||
GEMINI_KEY=$( grep GEMINI_API_KEY /var/www/nordabiznes/.env | cut -d= -f2)
|
||||
GEMINI_KEY=$(sudo -u www-data 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" \
|
||||
@ -1204,7 +1252,7 @@ ssh maciejpi@57.128.200.27
|
||||
sudo journalctl -u nordabiznes -n 100 | grep -i gemini
|
||||
|
||||
# 3. Check conversation ownership
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT id, user_id, created_at
|
||||
FROM ai_chat_conversations
|
||||
@ -1220,7 +1268,7 @@ WHERE id = 123; -- Replace with conversation ID
|
||||
# OR context too long
|
||||
|
||||
# Check last message for safety filter
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT message, ai_response, error_message
|
||||
FROM ai_chat_messages
|
||||
@ -1277,7 +1325,7 @@ AND created_at < (
|
||||
# Symptom: 401 Unauthorized or 403 Forbidden
|
||||
|
||||
# Verify API key
|
||||
grep GEMINI_API_KEY /var/www/nordabiznes/.env
|
||||
sudo -u www-data grep GEMINI_API_KEY /var/www/nordabiznes/.env
|
||||
|
||||
# Test key directly
|
||||
curl -H "x-goog-api-key: YOUR_KEY" \
|
||||
@ -1315,7 +1363,7 @@ curl -X POST https://nordabiznes.pl/api/chat \
|
||||
|
||||
```bash
|
||||
# 1. Test PageSpeed API directly
|
||||
PAGESPEED_KEY=$( grep GOOGLE_PAGESPEED_API_KEY /var/www/nordabiznes/.env | cut -d= -f2)
|
||||
PAGESPEED_KEY=$(sudo -u www-data 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"
|
||||
|
||||
@ -1324,7 +1372,7 @@ ssh maciejpi@57.128.200.27
|
||||
sudo journalctl -u nordabiznes -n 100 | grep -i pagespeed
|
||||
|
||||
# 3. Check recent audits
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT company_id, url, seo_score, performance_score,
|
||||
audited_at, error_message
|
||||
@ -1387,13 +1435,13 @@ python seo_audit.py --batch 1-10
|
||||
|
||||
```bash
|
||||
# 1. Test Brave API directly
|
||||
BRAVE_KEY=$( grep BRAVE_SEARCH_API_KEY /var/www/nordabiznes/.env | cut -d= -f2)
|
||||
BRAVE_KEY=$(sudo -u www-data 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
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT COUNT(*) AS searches_this_month
|
||||
FROM company_news
|
||||
@ -1448,7 +1496,7 @@ sudo systemctl restart nordabiznes
|
||||
```bash
|
||||
# 1. Check user exists and is active
|
||||
ssh maciejpi@57.128.200.27
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT id, email, is_active, email_verified, failed_login_attempts
|
||||
FROM users
|
||||
@ -1471,7 +1519,7 @@ curl -c /tmp/cookies.txt -X POST http://localhost:5000/login \
|
||||
|
||||
```bash
|
||||
# Check failed attempts
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT email, failed_login_attempts, last_failed_login
|
||||
FROM users
|
||||
@ -1570,7 +1618,7 @@ curl -c /tmp/cookies.txt -X POST http://localhost:5000/login \
|
||||
|
||||
```bash
|
||||
# 1. Check user role
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT id, email, is_admin, is_norda_member
|
||||
FROM users
|
||||
@ -1591,7 +1639,7 @@ sudo journalctl -u nordabiznes -n 50 | grep -i "forbidden\|unauthorized"
|
||||
|
||||
```bash
|
||||
# Grant admin role
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
UPDATE users SET is_admin = TRUE
|
||||
WHERE email = 'admin@nordabiznes.pl';
|
||||
@ -1643,7 +1691,7 @@ WHERE email = 'user@example.com';
|
||||
|
||||
```bash
|
||||
# 1. Check user reset token
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT email, reset_token, reset_token_expiry
|
||||
FROM users
|
||||
@ -1654,7 +1702,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
|
||||
grep AZURE /var/www/nordabiznes/.env
|
||||
sudo -u www-data grep AZURE /var/www/nordabiznes/.env
|
||||
```
|
||||
|
||||
#### Solution
|
||||
@ -1674,7 +1722,7 @@ WHERE email = 'user@example.com';
|
||||
|
||||
```bash
|
||||
# Check MS Graph credentials
|
||||
python3 << 'EOF'
|
||||
sudo -u www-data python3 << 'EOF'
|
||||
import os
|
||||
from email_service import EmailService
|
||||
|
||||
@ -1695,14 +1743,14 @@ EOF
|
||||
|
||||
```bash
|
||||
# Generate new password hash
|
||||
python3 << 'EOF'
|
||||
sudo -u www-data python3 << 'EOF'
|
||||
from werkzeug.security import generate_password_hash
|
||||
password = "NewPassword123"
|
||||
print(generate_password_hash(password))
|
||||
EOF
|
||||
|
||||
# Update database
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
UPDATE users SET password_hash = 'HASH_FROM_ABOVE'
|
||||
WHERE email = 'user@example.com';
|
||||
@ -1738,7 +1786,7 @@ top -n 1
|
||||
# Look at: CPU usage, memory usage, load average
|
||||
|
||||
# 4. Check database query times
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT calls, mean_exec_time, query
|
||||
FROM pg_stat_statements
|
||||
@ -1782,7 +1830,7 @@ ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';
|
||||
sudo systemctl restart postgresql
|
||||
|
||||
# Check slow queries
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT calls, mean_exec_time, query
|
||||
FROM pg_stat_statements
|
||||
@ -1968,7 +2016,7 @@ uptime
|
||||
# Load should be < number of CPU cores
|
||||
|
||||
# 3. Identify CPU-heavy queries
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT pid, now() - query_start AS duration, state, query
|
||||
FROM pg_stat_activity
|
||||
@ -1982,7 +2030,7 @@ ORDER BY now() - query_start DESC;
|
||||
|
||||
```bash
|
||||
# Kill long-running query
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
SELECT pg_terminate_backend(PID);
|
||||
|
||||
# Add index to optimize query
|
||||
@ -2037,10 +2085,11 @@ curl https://nordabiznes.pl/health
|
||||
}
|
||||
|
||||
# Database health
|
||||
psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
|
||||
|
||||
# Nginx health (on OVH VPS)
|
||||
ssh maciejpi@57.128.200.27 "sudo systemctl status nginx"
|
||||
# NPM health (from proxy server)
|
||||
ssh maciejpi@10.22.68.250
|
||||
docker ps | grep nginx-proxy-manager
|
||||
# Should show: Up X hours
|
||||
|
||||
# Flask service health
|
||||
@ -2063,8 +2112,9 @@ sudo journalctl -u nordabiznes -f
|
||||
# PostgreSQL logs
|
||||
sudo journalctl -u postgresql -n 50
|
||||
|
||||
# Nginx logs (OVH VPS)
|
||||
ssh maciejpi@57.128.200.27 "sudo tail -50 /var/log/nginx/error.log"
|
||||
# NPM logs
|
||||
ssh maciejpi@10.22.68.250
|
||||
docker logs nginx-proxy-manager_app_1 --tail 50 -f
|
||||
|
||||
# System logs
|
||||
sudo journalctl -n 100
|
||||
@ -2095,7 +2145,7 @@ fi
|
||||
|
||||
```bash
|
||||
# Database performance
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
-- Connection count
|
||||
SELECT count(*) FROM pg_stat_activity;
|
||||
@ -2204,7 +2254,7 @@ sudo systemctl restart nordabiznes
|
||||
|
||||
# If restart fails, check manually
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/python3 app.py
|
||||
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py
|
||||
# Read error message
|
||||
|
||||
# 3. Common quick fixes:
|
||||
@ -2253,7 +2303,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
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
-- Check table counts
|
||||
SELECT 'companies' AS table, count(*) FROM companies
|
||||
@ -2291,7 +2341,7 @@ sudo systemctl start nordabiznes
|
||||
|
||||
```bash
|
||||
# Identify missing/corrupted records
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data 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;
|
||||
@ -2322,7 +2372,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
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
-- Check for new admin users
|
||||
SELECT id, email, created_at, is_admin
|
||||
@ -2356,7 +2406,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
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
|
||||
SELECT * FROM users WHERE email LIKE '%<script%';
|
||||
SELECT * FROM companies WHERE description LIKE '%<script%';
|
||||
@ -2371,7 +2421,7 @@ SELECT * FROM companies WHERE description LIKE '%<script%';
|
||||
# - API keys
|
||||
|
||||
# 2. Revoke compromised sessions
|
||||
psql -h localhost -U nordabiz_app -d nordabiz
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
|
||||
DELETE FROM flask_sessions; -- Force all users to re-login
|
||||
|
||||
# 3. Update all API keys
|
||||
@ -2399,7 +2449,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 "psql -h localhost -U nordabiz_app -d nordabiz -c 'SELECT count(*) FROM companies;'" && \
|
||||
ssh maciejpi@57.128.200.27 "sudo -u www-data 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"
|
||||
```
|
||||
@ -2426,7 +2476,7 @@ ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health"
|
||||
|
||||
```bash
|
||||
# Database quick stats
|
||||
psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
|
||||
sudo -u www-data 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
|
||||
@ -2434,7 +2484,7 @@ UNION ALL SELECT 'DB size (MB)', pg_database_size('nordabiz')/1024/1024;
|
||||
EOF
|
||||
|
||||
# Find slow queries
|
||||
psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
|
||||
sudo -u www-data 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'
|
||||
@ -2442,7 +2492,7 @@ ORDER BY duration DESC;
|
||||
EOF
|
||||
|
||||
# Check locks
|
||||
psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
|
||||
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
|
||||
SELECT relation::regclass, mode, granted
|
||||
FROM pg_locks
|
||||
WHERE NOT granted;
|
||||
@ -2478,16 +2528,16 @@ echo | openssl s_client -servername nordabiznes.pl -connect nordabiznes.pl:443 2
|
||||
ssh maciejpi@57.128.200.27
|
||||
|
||||
# Gemini API
|
||||
GEMINI_KEY=$( grep GEMINI_API_KEY .env | cut -d= -f2)
|
||||
GEMINI_KEY=$(sudo -u www-data 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=$( grep GOOGLE_PAGESPEED_API_KEY .env | cut -d= -f2)
|
||||
PAGESPEED_KEY=$(sudo -u www-data 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=$( grep BRAVE_SEARCH_API_KEY .env | cut -d= -f2)
|
||||
BRAVE_KEY=$(sudo -u www-data 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'
|
||||
|
||||
|
||||
@ -1,28 +1,34 @@
|
||||
# HTTP Request Flow
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2026-04-04
|
||||
**Status:** Production LIVE (OVH VPS)
|
||||
**Last Updated:** 2026-01-10
|
||||
**Status:** Production LIVE
|
||||
**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 nginx 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 reverse proxy to Flask application and back. It covers:
|
||||
|
||||
- **Complete request path** (Internet → Nginx → Flask → Response)
|
||||
- **Complete request path** (Internet → Fortigate → NPM → Flask → Response)
|
||||
- **Critical port configurations** and routing decisions
|
||||
- **SSL/TLS termination** and security boundaries
|
||||
- **Request/response transformation** at each layer
|
||||
- **Common failure scenarios** and troubleshooting
|
||||
|
||||
**Key Infrastructure:**
|
||||
- **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
|
||||
- **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
|
||||
|
||||
> **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.
|
||||
**⚠️ 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
|
||||
```
|
||||
|
||||
**Related Documentation:**
|
||||
- Incident Report: `docs/INCIDENT_REPORT_20260102.md` (ERR_TOO_MANY_REDIRECTS)
|
||||
@ -39,19 +45,24 @@ This document describes the **complete HTTP request flow** for the Norda Biznes
|
||||
sequenceDiagram
|
||||
actor User
|
||||
participant Browser
|
||||
participant Nginx as 🔒 Nginx Reverse Proxy<br/>57.128.200.27:443
|
||||
participant Flask as 🌐 Flask/Gunicorn<br/>127.0.0.1:5000
|
||||
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 DB as 💾 PostgreSQL<br/>localhost:5432
|
||||
|
||||
Note over User,DB: SUCCESSFUL REQUEST FLOW
|
||||
|
||||
User->>Browser: Navigate to https://nordabiznes.pl/
|
||||
Browser->>Nginx: HTTPS GET / (Port 443)
|
||||
Note over Nginx: SSL/TLS Termination<br/>Let's Encrypt Certificate (certbot)
|
||||
Browser->>Fortigate: HTTPS GET / (Port 443)
|
||||
Note over Fortigate: NAT Translation<br/>85.237.177.83:443 → 10.22.68.250:443
|
||||
|
||||
Note over Nginx: Request Processing<br/>• Validate certificate<br/>• Decrypt HTTPS<br/>• Extract headers<br/>• proxy_pass to localhost:5000
|
||||
Fortigate->>NPM: HTTPS GET / (Port 443)
|
||||
Note over NPM: SSL/TLS Termination<br/>Let's Encrypt Certificate<br/>nordabiznes.pl (Cert ID: 27)
|
||||
|
||||
Nginx->>Flask: HTTP GET / (127.0.0.1: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!
|
||||
|
||||
Note over Flask: Flask Request Handling<br/>• WSGI via Gunicorn<br/>• Route matching (app.py)<br/>• Session validation<br/>• CSRF check (if POST)
|
||||
|
||||
@ -60,27 +71,26 @@ sequenceDiagram
|
||||
|
||||
Note over Flask: Template Rendering<br/>• Jinja2 template: index.html<br/>• Inject company data<br/>• Apply filters & sorting
|
||||
|
||||
Flask->>Nginx: HTTP 200 OK<br/>Content-Type: text/html<br/>Set-Cookie: session=...<br/>HTML content
|
||||
Flask->>NPM: HTTP 200 OK<br/>Content-Type: text/html<br/>Set-Cookie: session=...<br/>HTML content
|
||||
|
||||
Note over Nginx: Response Processing<br/>• Encrypt response (HTTPS)<br/>• Add security headers<br/>• HSTS, CSP, X-Frame-Options
|
||||
Note over NPM: Response Processing<br/>• Encrypt response (HTTPS)<br/>• Add security headers<br/>• HSTS, CSP, X-Frame-Options
|
||||
|
||||
Nginx->>Browser: HTTPS 200 OK
|
||||
NPM->>Fortigate: HTTPS 200 OK (Port 443)
|
||||
Fortigate->>Browser: HTTPS 200 OK
|
||||
Browser->>User: Display page
|
||||
```
|
||||
|
||||
### 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.
|
||||
### 1.2 Failed Request (Wrong Port Configuration)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User
|
||||
participant Browser
|
||||
participant NPM as 🔒 NPM Reverse Proxy<br/>10.22.68.250:443
|
||||
participant NginxSys as ⚠️ Nginx System<br/>10.22.68.249:80
|
||||
participant Flask as 🌐 Flask/Gunicorn<br/>10.22.68.249:5000
|
||||
participant NginxSys as ⚠️ Nginx System<br/>57.128.200.27:80
|
||||
participant Flask as 🌐 Flask/Gunicorn<br/>57.128.200.27:5000
|
||||
|
||||
Note over User,Flask: FAILED REQUEST FLOW (REDIRECT LOOP) — HISTORICAL
|
||||
Note over User,Flask: FAILED REQUEST FLOW (REDIRECT LOOP)
|
||||
|
||||
User->>Browser: Navigate to https://nordabiznes.pl/
|
||||
Browser->>NPM: HTTPS GET / (Port 443)
|
||||
@ -110,11 +120,11 @@ sequenceDiagram
|
||||
|
||||
## 2. Layer-by-Layer Request Processing
|
||||
|
||||
### 2.1 Layer 1: DNS + Direct Connection (OVH VPS)
|
||||
### 2.1 Layer 1: Fortigate Firewall (NAT Gateway)
|
||||
|
||||
**Server:** OVH VPS
|
||||
**Public IP:** 57.128.200.27
|
||||
**Function:** Direct internet-facing server (no NAT, no FortiGate for production)
|
||||
**Server:** Fortigate Firewall
|
||||
**Public IP:** 85.237.177.83
|
||||
**Function:** Network Address Translation (NAT) + Firewall
|
||||
|
||||
**Processing Steps:**
|
||||
|
||||
@ -150,12 +160,12 @@ Firewall: ALLOW from any to 85.237.177.83:443 (state: NEW,ESTABLISHED)
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Layer 2: Nginx Reverse Proxy (SSL Termination)
|
||||
### 2.2 Layer 2: NPM Reverse Proxy (SSL Termination)
|
||||
|
||||
**Server:** OVH VPS (inpi-vps-waw01)
|
||||
**IP:** 57.128.200.27
|
||||
**Server:** R11-REVPROXY-01 (VM 119)
|
||||
**IP:** 10.22.68.250
|
||||
**Port:** 443 (HTTPS)
|
||||
**Technology:** Nginx with Let's Encrypt (certbot)
|
||||
**Technology:** Nginx Proxy Manager (Docker)
|
||||
|
||||
**Processing Steps:**
|
||||
|
||||
@ -220,7 +230,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 | 127.0.0.1 | Localhost (same OVH VPS) |
|
||||
| Forward Host | 57.128.200.27 | NORDABIZ-01 backend server |
|
||||
| **Forward Port** | **5000** | **Flask/Gunicorn port (CRITICAL!)** |
|
||||
| SSL Certificate | 27 (Let's Encrypt) | Auto-renewal enabled |
|
||||
| SSL Forced | Yes | Redirect HTTP→HTTPS |
|
||||
@ -244,8 +254,8 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \
|
||||
|
||||
### 2.3 Layer 3: Flask/Gunicorn Application (Request Processing)
|
||||
|
||||
**Server:** OVH VPS (inpi-vps-waw01)
|
||||
**IP:** 127.0.0.1 (localhost, via nginx proxy_pass)
|
||||
**Server:** NORDABIZ-01 (VM 249)
|
||||
**IP:** 57.128.200.27
|
||||
**Port:** 5000
|
||||
**Technology:** Gunicorn 20.1.0 + Flask 3.0
|
||||
|
||||
@ -253,7 +263,7 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \
|
||||
|
||||
1. **Gunicorn Receives HTTP Request:**
|
||||
```
|
||||
Binding: 127.0.0.1:5000 (all interfaces)
|
||||
Binding: 0.0.0.0:5000 (all interfaces)
|
||||
Workers: 4 (Gunicorn worker processes)
|
||||
Worker Class: sync (synchronous workers)
|
||||
Timeout: 120 seconds
|
||||
@ -372,11 +382,11 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \
|
||||
```ini
|
||||
# /etc/systemd/system/nordabiznes.service
|
||||
[Service]
|
||||
User=maciejpi
|
||||
Group=maciejpi
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=/var/www/nordabiznes
|
||||
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
|
||||
--bind 127.0.0.1:5000 \
|
||||
--bind 0.0.0.0:5000 \
|
||||
--workers 4 \
|
||||
--timeout 120 \
|
||||
--access-logfile /var/log/nordabiznes/access.log \
|
||||
@ -400,7 +410,7 @@ ssh maciejpi@57.128.200.27 "ps aux | grep gunicorn"
|
||||
|
||||
### 2.4 Layer 4: PostgreSQL Database (Data Retrieval)
|
||||
|
||||
**Server:** OVH VPS (same server as Flask)
|
||||
**Server:** NORDABIZ-01 (same server as Flask)
|
||||
**IP:** 127.0.0.1 (localhost only)
|
||||
**Port:** 5432
|
||||
**Technology:** PostgreSQL 14
|
||||
@ -480,7 +490,7 @@ sequenceDiagram
|
||||
Note over Flask: Response Construction
|
||||
Flask->>Flask: Render Jinja2 template<br/>HTML content (50 KB)
|
||||
|
||||
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]
|
||||
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]
|
||||
|
||||
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
|
||||
@ -489,7 +499,7 @@ sequenceDiagram
|
||||
|
||||
NPM->>NPM: Encrypt response (TLS 1.3)<br/>Certificate: Let's Encrypt
|
||||
|
||||
Nginx->>Browser: HTTPS 200 OK<br/>Encrypted response<br/>Size: 12 KB (gzip)
|
||||
NPM->>Fortigate: 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
|
||||
@ -569,27 +579,27 @@ x-request-id: abc123def456
|
||||
│ HTTP (Port 5000) ✓
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ FLASK/GUNICORN (OVH VPS) │
|
||||
│ FLASK/GUNICORN (NORDABIZ-01) │
|
||||
│ IP: 57.128.200.27:5000 │
|
||||
│ Binding: 127.0.0.1:5000 │
|
||||
│ Binding: 0.0.0.0:5000 │
|
||||
│ Workers: 4 (Gunicorn) │
|
||||
│ Function: Application logic, template rendering │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│ SQL (localhost:5432)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ POSTGRESQL (OVH VPS) │
|
||||
│ POSTGRESQL (NORDABIZ-01) │
|
||||
│ IP: 127.0.0.1:5432 (localhost only) │
|
||||
│ Database: nordabiz │
|
||||
│ Function: Data storage │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Port Table (OVH VPS)
|
||||
### 4.2 Port Table (NORDABIZ-01)
|
||||
|
||||
| Port | Service | Binding | User | Purpose | NPM Should Use? |
|
||||
|------|---------|---------|------|---------|-----------------|
|
||||
| **5000** | **Gunicorn/Flask** | **0.0.0.0** | **maciejpi** | **Main Application** | **✓ YES** |
|
||||
| **5000** | **Gunicorn/Flask** | **0.0.0.0** | **www-data** | **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) |
|
||||
@ -597,7 +607,7 @@ x-request-id: abc123def456
|
||||
|
||||
**⚠️ CRITICAL WARNING:**
|
||||
```
|
||||
Port 80 and 443 on OVH VPS run a system nginx that:
|
||||
Port 80 and 443 on NORDABIZ-01 run a system nginx that:
|
||||
1. Redirects ALL HTTP requests to HTTPS
|
||||
2. Redirects ALL HTTPS requests to https://nordabiznes.pl
|
||||
|
||||
@ -619,7 +629,7 @@ SOLUTION: NPM must ALWAYS forward to port 5000!
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
User → Nginx → Flask → Static file handler → Return CSS
|
||||
User → NPM → Flask → Static file handler → Return CSS
|
||||
```
|
||||
|
||||
**Flask Handling:**
|
||||
@ -640,7 +650,7 @@ def static_files(filename):
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
User → Nginx → Flask → API route → Database → JSON response
|
||||
User → NPM → Flask → API route → Database → JSON response
|
||||
```
|
||||
|
||||
**Response:**
|
||||
@ -672,7 +682,7 @@ Access-Control-Allow-Origin: * (if CORS enabled)
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
User → Nginx → Flask → CSRF validation → Auth check → Database → Redirect
|
||||
User → NPM → Flask → CSRF validation → Auth check → Database → Redirect
|
||||
```
|
||||
|
||||
**Additional Processing:**
|
||||
@ -697,7 +707,7 @@ Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
Monitor → Nginx → Flask → Simple response (no DB query)
|
||||
Monitor → NPM → Flask → Simple response (no DB query)
|
||||
```
|
||||
|
||||
**Response:**
|
||||
@ -792,7 +802,7 @@ graph TB
|
||||
|-------|-----------------|-------|
|
||||
| Fortigate NAT | < 1 ms | Hardware NAT, negligible latency |
|
||||
| NPM SSL Termination | 10-20 ms | TLS handshake + decryption |
|
||||
| Nginx → Flask Network | < 1 ms | Internal 10 Gbps network |
|
||||
| NPM → 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 |
|
||||
@ -888,7 +898,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: 127.0.0.1:5000 ... gunicorn
|
||||
# Expected: 0.0.0.0:5000 ... gunicorn
|
||||
|
||||
# 3. Test direct connection
|
||||
curl -I http://57.128.200.27:5000/health
|
||||
@ -1016,7 +1026,7 @@ ssh maciejpi@57.128.200.27 "sudo -u postgres psql -c \
|
||||
|
||||
**Network Connectivity:**
|
||||
```bash
|
||||
# Test Nginx → Flask connectivity
|
||||
# Test NPM → Flask connectivity
|
||||
ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health"
|
||||
|
||||
# Test Flask → Database connectivity
|
||||
@ -1093,12 +1103,12 @@ After=network.target postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
User=maciejpi
|
||||
Group=maciejpi
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=/var/www/nordabiznes
|
||||
Environment="PATH=/var/www/nordabiznes/venv/bin"
|
||||
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
|
||||
--bind 127.0.0.1:5000 \
|
||||
--bind 0.0.0.0:5000 \
|
||||
--workers 4 \
|
||||
--worker-class sync \
|
||||
--timeout 120 \
|
||||
@ -1117,7 +1127,7 @@ WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
**Parameters Explained:**
|
||||
- `--bind 127.0.0.1:5000` - Listen on all interfaces, port 5000
|
||||
- `--bind 0.0.0.0: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
|
||||
@ -1211,7 +1221,7 @@ ssh maciejpi@57.128.200.27 "sudo tail -f /var/log/postgresql/postgresql-14-main.
|
||||
- Database size growth
|
||||
- Table bloat
|
||||
|
||||
**System Metrics (OVH VPS):**
|
||||
**System Metrics (NORDABIZ-01):**
|
||||
- CPU usage (should be < 80%)
|
||||
- Memory usage (should be < 6 GB / 8 GB)
|
||||
- Disk I/O (should be low)
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Warstwa_2" viewBox="0 0 129.47 40.19"><defs><style>.cls-1{fill:#666;}.cls-2{fill:#006299;}</style></defs><g id="Warstwa_1-2"><rect class="cls-1" x="35.22" width=".7" height="40.19"></rect><path class="cls-1" d="M50.76,13.96v-6.75s-3.36,5.59-3.36,5.59h-.84l-3.35-5.5v6.67h-1.8V3.77h1.56l4.05,6.75,3.97-6.75h1.56v10.19s-1.78,0-1.78,0Z"></path><path class="cls-1" d="M61.63,9.4v4.56h-1.72v-.95c-.44.67-1.28,1.05-2.45,1.05-1.78,0-2.9-.98-2.9-2.33s.87-2.31,3.23-2.31h2.01v-.12c0-1.06-.64-1.69-1.94-1.69-.87,0-1.78.29-2.36.77l-.71-1.32c.83-.64,2.04-.96,3.29-.96,2.24,0,3.54,1.06,3.54,3.3ZM59.81,11.53v-.9h-1.88c-1.24,0-1.57.47-1.57,1.03,0,.65.55,1.08,1.49,1.08s1.66-.41,1.96-1.21Z"></path><path class="cls-1" d="M63.07,13.19l.7-1.38c.68.45,1.73.77,2.71.77,1.15,0,1.63-.32,1.63-.86,0-1.48-4.8-.09-4.8-3.19,0-1.47,1.32-2.43,3.42-2.43,1.03,0,2.21.25,2.91.67l-.7,1.38c-.74-.44-1.49-.58-2.23-.58-1.11,0-1.63.36-1.63.87,0,1.57,4.8.17,4.8,3.22,0,1.46-1.34,2.4-3.52,2.4-1.3,0-2.59-.38-3.29-.87Z"></path><path class="cls-1" d="M77.53,12.5v1.46h-6.71v-1.15l4.27-5.17h-4.18v-1.46h6.51v1.15l-4.28,5.17h4.4Z"></path><path class="cls-1" d="M86.33,6.19l-3.64,8.4c-.71,1.76-1.7,2.3-3,2.3-.77,0-1.59-.26-2.08-.71l.73-1.34c.35.33.84.54,1.34.54.64,0,1.02-.31,1.37-1.08l.13-.31-3.39-7.8h1.89l2.45,5.75,2.46-5.75h1.75Z"></path><path class="cls-1" d="M95.12,9.51v4.45h-1.82v-4.22c0-1.38-.68-2.05-1.85-2.05-1.3,0-2.17.79-2.17,2.34v3.93h-1.82v-7.77h1.73v1c.6-.71,1.54-1.09,2.68-1.09,1.86,0,3.25,1.06,3.25,3.41Z"></path><path class="cls-1" d="M104.57,6.19l-3.64,8.4c-.71,1.76-1.7,2.3-3,2.3-.77,0-1.59-.26-2.08-.71l.73-1.34c.35.33.84.54,1.34.54.64,0,1.02-.31,1.37-1.08l.13-.31-3.39-7.8h1.89l2.45,5.75,2.46-5.75h1.75Z"></path><path class="cls-2" d="M55.07,19.36v15.08h-2.87l-7.52-9.16v9.16h-3.45v-15.08h2.89l7.5,9.16v-9.16h3.45Z"></path><path class="cls-2" d="M58.77,26.9c0-4.48,3.49-7.8,8.25-7.8s8.25,3.3,8.25,7.8-3.51,7.8-8.25,7.8-8.25-3.32-8.25-7.8ZM71.74,26.9c0-2.87-2.03-4.83-4.72-4.83s-4.72,1.96-4.72,4.83,2.03,4.83,4.72,4.83,4.72-1.96,4.72-4.83Z"></path><path class="cls-2" d="M88.59,34.44l-2.91-4.2h-3.21v4.2h-3.49v-15.08h6.53c4.03,0,6.55,2.09,6.55,5.47,0,2.26-1.14,3.92-3.1,4.76l3.38,4.85h-3.75ZM85.31,22.21h-2.84v5.26h2.84c2.13,0,3.21-.99,3.21-2.63s-1.08-2.63-3.21-2.63Z"></path><path class="cls-2" d="M95.89,19.36h6.85c4.93,0,8.32,2.97,8.32,7.54s-3.38,7.54-8.32,7.54h-6.85v-15.08ZM102.57,31.58c3,0,4.96-1.79,4.96-4.68s-1.96-4.68-4.96-4.68h-3.19v9.35h3.19Z"></path><path class="cls-2" d="M124.47,31.21h-7l-1.33,3.23h-3.58l6.72-15.08h3.45l6.74,15.08h-3.66l-1.34-3.23ZM123.37,28.56l-2.39-5.77-2.39,5.77h4.78Z"></path><path class="cls-1" d="M20.62,26.16c-1.12,0-2.18-.52-2.86-1.39-.28-.35-.55-.72-.78-1.1V3.12c0-.67-.54-1.21-1.21-1.21s-1.21.54-1.21,1.21v29.51H1.21c-.67,0-1.21.54-1.21,1.21s.54,1.21,1.21,1.21h15.88c-.08-.39-.11-.79-.11-1.21,0-3.78,3.09-6.87,6.87-6.87,2.87,0,5.33,1.78,6.36,4.28v-5.09h-9.59Z"></path><path class="cls-1" d="M26.73,1.9c-1.3,0-2.43.88-2.75,2.14l-4.55,18.18c-.09.36,0,.75.22,1.04.23.29.58.47.96.47h9.59V1.9h-3.48Z"></path><circle class="cls-1" cx="23.85" cy="33.84" r="4.45" transform="translate(-5.97 5.12) rotate(-10.9)"></circle><path class="cls-2" d="M12.13,6.75h-7.68c-.66,0-1.21.55-1.21,1.21v9.7h8.89V6.75Z"></path><rect class="cls-2" x="3.23" y="20.1" width="8.89" height="10.11"></rect></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |