- 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>
973 lines
35 KiB
Markdown
973 lines
35 KiB
Markdown
# Deployment Architecture Diagram
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** 2026-04-04
|
|
**Status:** Production LIVE
|
|
**Diagram Type:** Infrastructure / Deployment View
|
|
|
|
---
|
|
|
|
> **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
|
|
|
|
This diagram shows the **physical deployment architecture** of the Norda Biznes Partner system. It illustrates:
|
|
|
|
- **Physical servers** and their infrastructure details (VMs, IPs, ports)
|
|
- **Network topology** and connectivity
|
|
- **Service distribution** across servers
|
|
- **Critical port mappings** and firewall rules
|
|
- **External access points** and NAT configuration
|
|
|
|
**Abstraction Level:** Infrastructure / Deployment
|
|
**Audience:** DevOps, System Administrators, Infrastructure Team
|
|
**Purpose:** Understanding physical deployment, troubleshooting connectivity issues, incident response
|
|
|
|
---
|
|
|
|
## Deployment Architecture Diagram
|
|
|
|
```mermaid
|
|
graph TB
|
|
%% External actors and entry points
|
|
Internet["🌐 INTERNET<br/>External Users"]
|
|
|
|
%% Production server (OVH VPS)
|
|
subgraph "OVH VPS [inpi-vps-waw01 | 57.128.200.27]"
|
|
subgraph "Reverse Proxy"
|
|
Nginx["🔒 NGINX<br/>Ports: 443 (HTTPS), 80 (redirect)<br/><br/>SSL: Let's Encrypt (certbot)<br/>Domains: nordabiznes.pl"]
|
|
end
|
|
|
|
subgraph "Application Layer"
|
|
Gunicorn["🌐 GUNICORN + FLASK<br/>Port: 127.0.0.1:5000<br/>Workers: 4<br/>User: maciejpi<br/><br/>App: /var/www/nordabiznes<br/>Service: nordabiznes.service<br/>Timeout: 120s"]
|
|
end
|
|
|
|
subgraph "Data Layer"
|
|
PostgreSQL["💾 POSTGRESQL 14<br/>Port: 5432<br/>Listen: 127.0.0.1 ONLY<br/><br/>Database: nordabiz<br/>User: nordabiz_app<br/>Tables: 36<br/><br/>No external connections!"]
|
|
end
|
|
|
|
subgraph "Background Jobs"
|
|
Scripts["⚙️ PYTHON SCRIPTS<br/>Execution: Cron / Manual<br/><br/>• seo_audit.py<br/>• social_media_audit.py<br/>• gbp_audit.py<br/>• fetch_company_news.py"]
|
|
end
|
|
end
|
|
|
|
%% External services
|
|
subgraph "External APIs (HTTPS)"
|
|
Gemini["🤖 Google Gemini AI<br/>generativelanguage.googleapis.com<br/>Auth: API Key"]
|
|
BraveAPI["🔍 Brave Search API<br/>api.search.brave.com<br/>Auth: API Key"]
|
|
PageSpeed["📊 Google PageSpeed API<br/>googleapis.com/pagespeedonline<br/>Auth: API Key"]
|
|
Places["📍 Google Places API<br/>maps.googleapis.com/maps/api<br/>Auth: API Key"]
|
|
KRS["🏛️ KRS Open API<br/>api-krs.ms.gov.pl<br/>Auth: Public"]
|
|
MSGraph["📧 Microsoft Graph API<br/>graph.microsoft.com<br/>Auth: OAuth 2.0"]
|
|
end
|
|
|
|
%% User traffic flow
|
|
Internet -->|"HTTPS :443<br/>HTTP :80"| Nginx
|
|
|
|
%% Nginx to backend
|
|
Nginx -->|"HTTP<br/>127.0.0.1:5000"| Gunicorn
|
|
|
|
%% Application to database (localhost only)
|
|
Gunicorn <-->|"SQL<br/>localhost:5432<br/>SQLAlchemy ORM"| PostgreSQL
|
|
Scripts <-->|"SQL<br/>127.0.0.1:5432<br/>Direct connection"| PostgreSQL
|
|
|
|
%% External API calls
|
|
Gunicorn -->|"HTTPS<br/>API calls"| Gemini
|
|
Gunicorn -->|"HTTPS<br/>API calls"| BraveAPI
|
|
Gunicorn -->|"HTTPS<br/>API calls"| Places
|
|
Gunicorn -->|"HTTPS<br/>API calls"| KRS
|
|
Gunicorn -->|"HTTPS<br/>OAuth 2.0"| MSGraph
|
|
Scripts -->|"HTTPS<br/>Audit requests"| PageSpeed
|
|
Scripts -->|"HTTPS<br/>News search"| BraveAPI
|
|
|
|
%% Styling
|
|
classDef appStyle fill:#1168bd,stroke:#0b4884,color:#ffffff,stroke-width:3px
|
|
classDef dbStyle fill:#438dd5,stroke:#2e6295,color:#ffffff,stroke-width:3px
|
|
classDef proxyStyle fill:#e74c3c,stroke:#c0392b,color:#ffffff,stroke-width:4px
|
|
classDef externalStyle fill:#95a5a6,stroke:#7f8c8d,color:#ffffff,stroke-width:2px
|
|
|
|
class Nginx proxyStyle
|
|
class Gunicorn,Scripts appStyle
|
|
class PostgreSQL dbStyle
|
|
class Gemini,BraveAPI,PageSpeed,Places,KRS,MSGraph externalStyle
|
|
```
|
|
|
|
---
|
|
|
|
## Infrastructure Inventory
|
|
|
|
### Production Server
|
|
|
|
| Server | IP Address | Hostname | OS | vCPU | RAM | Disk | Provider |
|
|
|--------|------------|----------|-----|------|-----|------|----------|
|
|
| **OVH VPS** | 57.128.200.27 | inpi-vps-waw01 | Ubuntu 22.04 | 4 | 8 GB | 80 GB SSD | OVH Cloud (Warsaw) |
|
|
|
|
### Staging (on-prem, unchanged)
|
|
|
|
| Server | VM ID | IP Address | Hostname | OS | Hypervisor |
|
|
|--------|-------|------------|----------|-----|------------|
|
|
| **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.
|
|
|
|
---
|
|
|
|
## Server Details
|
|
|
|
### OVH VPS (Production Server)
|
|
|
|
**Infrastructure:**
|
|
- **IP Address:** 57.128.200.27
|
|
- **Hostname:** inpi-vps-waw01
|
|
- **OS:** Ubuntu 22.04 LTS
|
|
- **Resources:** 4 vCPU, 8 GB RAM, 80 GB SSD
|
|
- **Provider:** OVH Cloud (Warsaw datacenter)
|
|
- **DNS:** nordabiznes.pl -> 57.128.200.27 (A record in OVH DNS)
|
|
|
|
**Services Running:**
|
|
|
|
| Service | Port | Binding | User | Status | Purpose |
|
|
|---------|------|---------|------|--------|---------|
|
|
| **Nginx** | **443** | **0.0.0.0** | **root** | **Active** | **SSL termination + reverse proxy** ✓ |
|
|
| **Nginx** | **80** | **0.0.0.0** | **root** | **Active** | **HTTP→HTTPS redirect** |
|
|
| **Gunicorn/Flask** | **5000** | **127.0.0.1** | **maciejpi** | **Active** | **Main Application** ✓ |
|
|
| PostgreSQL 14 | 5432 | 127.0.0.1 | postgres | Active | Database |
|
|
| SSH | 22 | 0.0.0.0 | - | Active | Remote administration |
|
|
|
|
**Application Paths:**
|
|
```
|
|
/var/www/nordabiznes/ # Application root
|
|
├── app.py # Main Flask application (13,144 lines)
|
|
├── database.py # SQLAlchemy models (36 tables)
|
|
├── venv/ # Python virtual environment
|
|
├── templates/ # Jinja2 templates
|
|
├── static/ # CSS, JS, images
|
|
├── scripts/ # Background jobs
|
|
└── .env # Environment variables (secrets)
|
|
|
|
/var/log/nordabiznes/ # Application logs
|
|
├── access.log # Gunicorn access log
|
|
└── error.log # Gunicorn error log
|
|
|
|
/etc/systemd/system/
|
|
└── nordabiznes.service # Systemd unit file
|
|
```
|
|
|
|
**Service Configuration:**
|
|
```bash
|
|
# Service management
|
|
sudo systemctl status nordabiznes
|
|
sudo systemctl restart nordabiznes
|
|
sudo systemctl start nordabiznes
|
|
sudo systemctl stop nordabiznes
|
|
|
|
# View logs
|
|
sudo journalctl -u nordabiznes -f # Follow logs
|
|
sudo journalctl -u nordabiznes -n 100 # Last 100 lines
|
|
tail -f /var/log/nordabiznes/access.log # Access log
|
|
tail -f /var/log/nordabiznes/error.log # Error log
|
|
```
|
|
|
|
**SSH Access:**
|
|
```bash
|
|
ssh maciejpi@57.128.200.27
|
|
# CRITICAL: Always use 'maciejpi' user, NEVER 'root'!
|
|
```
|
|
|
|
**Gunicorn Configuration:**
|
|
```ini
|
|
# /etc/systemd/system/nordabiznes.service
|
|
[Service]
|
|
User=maciejpi
|
|
Group=maciejpi
|
|
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
|
|
--bind 127.0.0.1:5000 \
|
|
--workers 4 \
|
|
--timeout 120 \
|
|
--access-logfile /var/log/nordabiznes/access.log \
|
|
--error-logfile /var/log/nordabiznes/error.log \
|
|
app:app
|
|
```
|
|
|
|
**Nginx Configuration:**
|
|
```nginx
|
|
# /etc/nginx/sites-available/nordabiznes.pl
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name nordabiznes.pl www.nordabiznes.pl;
|
|
ssl_certificate /etc/letsencrypt/live/nordabiznes.pl/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/nordabiznes.pl/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5000;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name nordabiznes.pl www.nordabiznes.pl;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
```
|
|
|
|
**SSL Certificate Management:**
|
|
```bash
|
|
# Let's Encrypt via certbot
|
|
sudo certbot certificates # Check certificate status
|
|
sudo certbot renew # Renew certificates
|
|
sudo certbot renew --dry-run # Test renewal
|
|
```
|
|
|
|
---
|
|
|
|
## Network Topology
|
|
|
|
### Network Segments
|
|
|
|
| Segment | CIDR/Address | Purpose | Security Level |
|
|
|---------|--------------|---------|----------------|
|
|
| Public Internet | 0.0.0.0/0 | External user access | Untrusted |
|
|
| OVH VPS | 57.128.200.27/32 | Production server (direct) | Production |
|
|
| INPI LAN | 10.22.68.0/24 | Staging + internal services | Trusted |
|
|
| Localhost | 127.0.0.1/8 | Server-local services | Isolated |
|
|
|
|
### DNS Configuration
|
|
|
|
**External DNS (OVH):**
|
|
|
|
| Type | Name | Value | TTL | Purpose |
|
|
|------|------|-------|-----|---------|
|
|
| A | nordabiznes.pl | 57.128.200.27 | 3600 | Main domain (OVH VPS) |
|
|
| A | www.nordabiznes.pl | 57.128.200.27 | 3600 | WWW subdomain |
|
|
| A | staging.nordabiznes.pl | 85.237.177.83 | 3600 | Staging (on-prem via FortiGate) |
|
|
|
|
---
|
|
|
|
## Port Mappings
|
|
|
|
### OVH VPS (57.128.200.27) Port Matrix
|
|
|
|
| Port | Protocol | Service | Binding | Access | Purpose | Security Notes |
|
|
|------|----------|---------|---------|--------|---------|----------------|
|
|
| 22 | TCP | SSH | 0.0.0.0 | Public (key-only) | Server administration | Key-based auth |
|
|
| 80 | TCP | Nginx | 0.0.0.0 | Public | HTTP→HTTPS redirect | Auto-redirect |
|
|
| 443 | TCP | Nginx | 0.0.0.0 | Public | HTTPS (SSL termination) | Let's Encrypt |
|
|
| **5000** | **TCP** | **Gunicorn/Flask** | **127.0.0.1** | **Localhost only** | **Main Application** | **Nginx proxy_pass** |
|
|
| 5432 | TCP | PostgreSQL | 127.0.0.1 | Localhost only | Database | No external access |
|
|
|
|
**Traffic flow:** Internet -> nginx (:443) -> proxy_pass -> Gunicorn (127.0.0.1:5000)
|
|
|
|
---
|
|
|
|
### Port Matrix (historical on-prem, now staging only)
|
|
|
|
| Port | Protocol | Service | Binding | Access | Purpose | Security Notes |
|
|
|------|----------|---------|---------|--------|---------|----------------|
|
|
| 22 | TCP | SSH | 0.0.0.0 | Internal only | Server administration | Key-based auth |
|
|
| 80 | TCP | NPM | 0.0.0.0 | Public (via NAT) | HTTP→HTTPS redirect | Auto-redirect |
|
|
| 81 | TCP | NPM Admin UI | 0.0.0.0 | Internal only | NPM management | Auth required |
|
|
| 443 | TCP | NPM | 0.0.0.0 | Public (via NAT) | HTTPS traffic | SSL termination |
|
|
|
|
**Note:** R11-REVPROXY-01 (10.22.68.250) with NPM is now used for staging only (staging.nordabiznes.pl).
|
|
|
|
---
|
|
|
|
### r11-git-inpi (10.22.68.180) Port Matrix (internal, for staging)
|
|
|
|
| Port | Protocol | Service | Binding | Access | Purpose | Security Notes |
|
|
|------|----------|---------|---------|--------|---------|----------------|
|
|
| 22 | TCP | SSH | 0.0.0.0 | Internal only | Server administration | Key-based auth |
|
|
| 3000 | TCP | Gitea (HTTPS) | 0.0.0.0 | Internal only | Git repository hosting | Self-signed SSL |
|
|
|
|
---
|
|
|
|
### Fortigate Firewall NAT Rules (staging only)
|
|
|
|
| External Port | Protocol | Internal IP | Internal Port | Purpose | Traffic |
|
|
|---------------|----------|-------------|---------------|---------|---------|
|
|
| 443 | TCP | 10.22.68.250 | 443 | HTTPS staging access | Incoming |
|
|
| 80 | TCP | 10.22.68.250 | 80 | HTTP redirect | Incoming |
|
|
|
|
**Note:** FortiGate NAT rules are now only used for staging (staging.nordabiznes.pl). Production traffic goes directly to OVH VPS (57.128.200.27) without FortiGate.
|
|
|
|
---
|
|
|
|
## Network Flow Diagrams
|
|
|
|
### Successful Production Request Flow
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 1. USER BROWSER │
|
|
│ https://nordabiznes.pl │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│ DNS: nordabiznes.pl → 57.128.200.27
|
|
│ HTTPS :443
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 2. NGINX @ OVH VPS (57.128.200.27:443) │
|
|
│ • Accept HTTPS connection │
|
|
│ • TLS handshake (Let's Encrypt certificate via certbot) │
|
|
│ • Terminate SSL/TLS │
|
|
│ • Add security headers (HSTS, etc.) │
|
|
│ • Proxy pass to Gunicorn │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│ http://127.0.0.1:5000
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 3. GUNICORN @ OVH VPS (127.0.0.1:5000) │
|
|
│ • Receive HTTP request (decrypted) │
|
|
│ • Load balance across 4 workers │
|
|
│ • Pass to Flask application │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│ Process request
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 4. FLASK APP (app.py) │
|
|
│ • Rate limiting check (Flask-Limiter) │
|
|
│ • Session validation (Flask-Login) │
|
|
│ • CSRF protection (Flask-WTF) │
|
|
│ • Route matching (90+ routes) │
|
|
│ • Business logic execution │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│ Database query
|
|
│ localhost:5432
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 5. POSTGRESQL @ localhost:5432 │
|
|
│ • Execute SQL query (SQLAlchemy ORM) │
|
|
│ • Apply constraints and indexes │
|
|
│ • Return result set │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│ Results
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ 6. FLASK APP (render response) │
|
|
│ • Jinja2 template rendering │
|
|
│ • JSON serialization (API routes) │
|
|
│ • Apply response headers │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│ HTTP response
|
|
▼
|
|
GUNICORN → NGINX (encrypt with TLS) → USER BROWSER
|
|
```
|
|
|
|
**Timing Breakdown:**
|
|
- DNS resolution: ~50ms
|
|
- SSL handshake: ~100ms
|
|
- Nginx proxy: ~5ms
|
|
- Flask processing: ~50-500ms (depends on query complexity)
|
|
- Database query: ~10-100ms
|
|
- Template rendering: ~20-50ms
|
|
- **Total:** ~235-805ms (typical range)
|
|
|
|
---
|
|
|
|
### Historical: Failed Request Flow (Port 80 Misconfiguration)
|
|
|
|
**This applied to the old on-prem setup (pre-OVH migration). See `docs/INCIDENT_REPORT_20260102.md` for details.**
|
|
|
|
The old setup used NPM (10.22.68.250) forwarding to on-prem VM (57.128.200.27). Misconfiguring the forward port to 80 instead of 5000 caused an infinite redirect loop. This is no longer applicable to the current OVH VPS production setup.
|
|
|
|
---
|
|
|
|
## SSL/TLS Configuration
|
|
|
|
### Let's Encrypt Certificate
|
|
|
|
**Certificate Details:**
|
|
- **Provider:** Let's Encrypt
|
|
- **Managed By:** certbot on OVH VPS
|
|
- **Domains:**
|
|
- nordabiznes.pl
|
|
- www.nordabiznes.pl
|
|
- **Key Type:** RSA 2048-bit
|
|
- **Validity:** 90 days (auto-renewed)
|
|
- **Renewal:** Automatic via certbot cron/timer
|
|
|
|
**TLS Configuration:**
|
|
- **Protocols:** TLS 1.2, TLS 1.3 (TLS 1.0/1.1 disabled)
|
|
- **Cipher Suites:** Modern ciphers only
|
|
- **HTTP/2:** Enabled
|
|
- **HSTS:** Enabled (max-age=31536000, includeSubDomains)
|
|
|
|
**Security Headers (Added by nginx):**
|
|
```http
|
|
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
|
X-Frame-Options: SAMEORIGIN
|
|
X-Content-Type-Options: nosniff
|
|
X-XSS-Protection: 1; mode=block
|
|
```
|
|
|
|
**Certificate Verification:**
|
|
```bash
|
|
# Check certificate details
|
|
openssl s_client -connect nordabiznes.pl:443 -servername nordabiznes.pl < /dev/null 2>/dev/null | \
|
|
openssl x509 -noout -dates -subject -issuer
|
|
|
|
# Check certificate status on server
|
|
ssh maciejpi@57.128.200.27 "sudo certbot certificates"
|
|
|
|
# Test SSL configuration
|
|
curl -I https://nordabiznes.pl/health
|
|
# Expected: HTTP/2 200 OK
|
|
```
|
|
|
|
---
|
|
|
|
## External API Connectivity
|
|
|
|
### Outbound HTTPS Connections
|
|
|
|
| API | Endpoint | Port | Authentication | Purpose | Rate Limit |
|
|
|-----|----------|------|----------------|---------|------------|
|
|
| **Google Gemini AI** | generativelanguage.googleapis.com | 443 | API Key | AI chat, image analysis | Cost-limited |
|
|
| **Brave Search** | api.search.brave.com | 443 | API Key | News monitoring, social discovery | 2000/month |
|
|
| **Google PageSpeed** | googleapis.com/pagespeedonline | 443 | API Key | SEO/performance audits | 25,000/day |
|
|
| **Google Places** | maps.googleapis.com/maps/api | 443 | API Key | Business profiles, reviews | Quota-based |
|
|
| **KRS Open API** | api-krs.ms.gov.pl | 443 | Public | Company verification | Unlimited |
|
|
| **Microsoft Graph** | graph.microsoft.com | 443 | OAuth 2.0 | Email notifications | Tenant-based |
|
|
|
|
**Firewall Requirements:**
|
|
- **Outbound HTTPS (443):** Must be allowed for all external API calls
|
|
- **DNS Resolution:** Must be allowed (port 53)
|
|
- **NTP:** Recommended for time sync (port 123)
|
|
|
|
**API Key Storage:**
|
|
- **Location:** `/var/www/nordabiznes/.env` (production)
|
|
- **Permissions:** `chmod 600` (owner read/write only)
|
|
- **Owner:** `maciejpi:maciejpi`
|
|
- **⚠️ NEVER commit to git!** (in `.gitignore`)
|
|
|
|
---
|
|
|
|
## Deployment Workflow
|
|
|
|
### Rsync-Based Deployment (OVH VPS)
|
|
|
|
Production deployment uses rsync (no git on OVH VPS).
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────────┐
|
|
│ DEVELOPMENT (Mac) │
|
|
│ │
|
|
│ • Code changes │
|
|
│ • Local testing (localhost:5000) │
|
|
│ • Git commit │
|
|
└───────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ git push origin master
|
|
│ git push inpi master
|
|
▼
|
|
┌────────────────────────────────────────────────────────────────┐
|
|
│ GIT REPOSITORIES │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ GITHUB @ github.com │ │
|
|
│ │ Repository: pienczyn/nordabiz │ │
|
|
│ │ Purpose: Cloud backup, collaboration │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ GITEA @ r11-git-inpi (10.22.68.180:3000) │ │
|
|
│ │ Repository: maciejpi/nordabiz │ │
|
|
│ │ Purpose: Internal backup, staging deploy │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
└───────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ rsync (from dev Mac)
|
|
▼
|
|
┌────────────────────────────────────────────────────────────────┐
|
|
│ PRODUCTION (OVH VPS 57.128.200.27) │
|
|
│ │
|
|
│ 1. Deploy via rsync: │
|
|
│ rsync -avz --exclude='.env' --exclude='venv/' \ │
|
|
│ ./ maciejpi@57.128.200.27:/var/www/nordabiznes/ │
|
|
│ │
|
|
│ 2. Restart service: │
|
|
│ ssh maciejpi@57.128.200.27 \ │
|
|
│ "sudo systemctl reload nordabiznes" │
|
|
│ │
|
|
│ 3. Verify deployment: │
|
|
│ curl -I https://nordabiznes.pl/health │
|
|
└────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Deployment Checklist:**
|
|
1. ✅ Code tested locally
|
|
2. ✅ Git commit with descriptive message
|
|
3. ✅ Push to both remotes (GitHub + Gitea)
|
|
4. ✅ Rsync to OVH VPS
|
|
5. ✅ Reload `nordabiznes` service
|
|
6. ✅ Verify health endpoint (nordabiznes.pl)
|
|
7. ✅ Check logs for errors (`journalctl -u nordabiznes`)
|
|
8. ✅ Update release notes in `app.py`
|
|
|
|
---
|
|
|
|
## Monitoring and Health Checks
|
|
|
|
### Health Check Endpoint
|
|
|
|
**Endpoint:** `GET /health`
|
|
**Authentication:** None (public)
|
|
**Purpose:** Service availability monitoring
|
|
|
|
**Response (Healthy):**
|
|
```http
|
|
HTTP/2 200 OK
|
|
Content-Type: application/json
|
|
|
|
```
|
|
|
|
**Health Check Commands:**
|
|
```bash
|
|
# External check (via nginx)
|
|
curl -I https://nordabiznes.pl/health
|
|
# Expected: HTTP/2 200 OK
|
|
|
|
# Localhost check (from OVH VPS)
|
|
ssh maciejpi@57.128.200.27 "curl -I http://localhost:5000/health"
|
|
# Expected: HTTP/1.1 200 OK
|
|
```
|
|
|
|
### Service Status Checks
|
|
|
|
**Application Service:**
|
|
```bash
|
|
# Check systemd service
|
|
sudo systemctl status nordabiznes
|
|
# Active: active (running)
|
|
|
|
# Check process
|
|
ps aux | grep gunicorn
|
|
# Should show 5 processes (1 master + 4 workers)
|
|
|
|
# Check listening ports
|
|
ss -tlnp | grep :5000
|
|
# LISTEN 0 128 0.0.0.0:5000 0.0.0.0:*
|
|
```
|
|
|
|
**Database Service:**
|
|
```bash
|
|
# Check PostgreSQL service
|
|
sudo systemctl status postgresql
|
|
# Active: active (running)
|
|
|
|
# Check database connectivity
|
|
sudo -u postgres psql -c "SELECT version();"
|
|
|
|
# Check active connections
|
|
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='nordabiz';"
|
|
```
|
|
|
|
**Nginx Service (OVH VPS):**
|
|
```bash
|
|
# Check nginx status
|
|
ssh maciejpi@57.128.200.27 "sudo systemctl status nginx"
|
|
|
|
# Check nginx logs
|
|
ssh maciejpi@57.128.200.27 "sudo tail -20 /var/log/nginx/error.log"
|
|
|
|
# Test nginx config
|
|
ssh maciejpi@57.128.200.27 "sudo nginx -t"
|
|
```
|
|
|
|
### Planned Monitoring (Not Yet Implemented)
|
|
|
|
**Zabbix Monitoring (Planned):**
|
|
- HTTPS endpoint monitoring (5-minute intervals)
|
|
- Response time tracking
|
|
- SSL certificate expiry alerts (30-day warning)
|
|
- Service availability alerts
|
|
- Database connection monitoring
|
|
- Disk space monitoring
|
|
|
|
---
|
|
|
|
## Backup and Disaster Recovery
|
|
|
|
### PostgreSQL Database Backups
|
|
|
|
**Current Status:** Manual backups only
|
|
**Backup Location:** `/backup/nordabiz/` (on OVH VPS inpi-vps-waw01)
|
|
|
|
**Manual Backup Procedure:**
|
|
```bash
|
|
# Create backup
|
|
sudo -u postgres pg_dump nordabiz | gzip > \
|
|
/backup/nordabiz/nordabiz_$(date +%Y%m%d_%H%M%S).sql.gz
|
|
|
|
# Verify backup
|
|
gunzip -c /backup/nordabiz/nordabiz_20260110_120000.sql.gz | head -n 20
|
|
```
|
|
|
|
**Restore Procedure:**
|
|
```bash
|
|
# Stop application
|
|
sudo systemctl stop nordabiznes
|
|
|
|
# Drop existing database (CAUTION!)
|
|
sudo -u postgres psql -c "DROP DATABASE nordabiz;"
|
|
sudo -u postgres psql -c "CREATE DATABASE nordabiz OWNER nordabiz_app;"
|
|
|
|
# Restore from backup
|
|
gunzip -c /backup/nordabiz/nordabiz_20260110_120000.sql.gz | \
|
|
sudo -u postgres psql nordabiz
|
|
|
|
# Grant permissions
|
|
sudo -u postgres psql -d nordabiz -c "GRANT ALL ON ALL TABLES IN SCHEMA public TO nordabiz_app;"
|
|
sudo -u postgres psql -d nordabiz -c "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nordabiz_app;"
|
|
|
|
# Restart application
|
|
sudo systemctl start nordabiznes
|
|
|
|
# Verify
|
|
curl -I http://localhost:5000/health
|
|
```
|
|
|
|
**Planned:** Automated daily backups via cron
|
|
|
|
---
|
|
|
|
### OVH VPS Backups
|
|
|
|
**OVH Automated Backups:**
|
|
- OVH VPS snapshot backups (configured via OVH panel)
|
|
- Application code backed up via git (GitHub + Gitea)
|
|
- Database backed up via pg_dump + offsite copy
|
|
|
|
---
|
|
|
|
## Security Configuration
|
|
|
|
### OVH VPS Firewall
|
|
|
|
**The OVH VPS uses ufw (Uncomplicated Firewall) and/or OVH firewall:**
|
|
|
|
**Inbound Rules:**
|
|
```
|
|
ALLOW 22/tcp (SSH - key-based auth only)
|
|
ALLOW 80/tcp (HTTP - redirects to HTTPS)
|
|
ALLOW 443/tcp (HTTPS - production traffic)
|
|
DENY all other inbound
|
|
```
|
|
|
|
### SSH Access Control
|
|
|
|
**Allowed Users:**
|
|
- `maciejpi` - Administrator account (sudo access)
|
|
|
|
**Authentication:**
|
|
- SSH key-based authentication (required)
|
|
- Password authentication disabled
|
|
|
|
**SSH Configuration:**
|
|
```bash
|
|
# /etc/ssh/sshd_config
|
|
PermitRootLogin no
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
AllowUsers maciejpi
|
|
```
|
|
|
|
**SSH Access:**
|
|
```bash
|
|
ssh maciejpi@57.128.200.27
|
|
```
|
|
|
|
### Database Security
|
|
|
|
**PostgreSQL Access Control:**
|
|
```conf
|
|
# postgresql.conf
|
|
listen_addresses = 'localhost' # NO external connections!
|
|
port = 5432
|
|
|
|
# pg_hba.conf
|
|
local nordabiz nordabiz_app md5 # Local socket
|
|
host nordabiz nordabiz_app 127.0.0.1/32 md5 # Localhost only
|
|
```
|
|
|
|
**Connection Restrictions:**
|
|
- ✅ Localhost (127.0.0.1): Allowed
|
|
- ❌ Internal network (10.22.68.0/24): Denied
|
|
- ❌ External network: Denied
|
|
|
|
**User Privileges:**
|
|
```sql
|
|
-- nordabiz_app user (application)
|
|
GRANT CONNECT ON DATABASE nordabiz TO nordabiz_app;
|
|
GRANT USAGE ON SCHEMA public TO nordabiz_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO nordabiz_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nordabiz_app;
|
|
|
|
-- postgres user (admin)
|
|
-- Full privileges on all databases
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting Guide
|
|
|
|
### Common Issues and Solutions
|
|
|
|
#### Issue 1: ERR_TOO_MANY_REDIRECTS
|
|
|
|
**Symptoms:**
|
|
- Browser shows "ERR_TOO_MANY_REDIRECTS"
|
|
- Site inaccessible via https://nordabiznes.pl
|
|
- Internal access (localhost:5000) works fine
|
|
|
|
**Cause:** NPM forwarding to port 80 instead of port 5000
|
|
|
|
**Solution:**
|
|
```bash
|
|
# 1. Verify NPM configuration
|
|
ssh maciejpi@10.22.68.250
|
|
docker exec nginx-proxy-manager_app_1 \
|
|
sqlite3 /data/database.sqlite \
|
|
"SELECT id, forward_host, forward_port FROM proxy_host WHERE id = 27;"
|
|
|
|
# 2. If forward_port != 5000, fix it via NPM Web UI:
|
|
# - Access http://10.22.68.250:81
|
|
# - Edit Proxy Host #27
|
|
# - Set Forward Port to 5000
|
|
# - Save
|
|
|
|
# 3. Verify fix
|
|
curl -I https://nordabiznes.pl/health
|
|
# Expected: HTTP/2 200 OK
|
|
```
|
|
|
|
**Prevention:** Always verify `forward_port = 5000` after NPM changes
|
|
|
|
---
|
|
|
|
#### Issue 2: 502 Bad Gateway
|
|
|
|
**Symptoms:**
|
|
- NPM returns 502 Bad Gateway
|
|
- External access fails
|
|
- Internal access may or may not work
|
|
|
|
**Possible Causes:**
|
|
1. Flask/Gunicorn service down
|
|
2. Port 5000 not listening
|
|
3. Network connectivity issue
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# 1. Check Gunicorn service
|
|
ssh maciejpi@57.128.200.27
|
|
sudo systemctl status nordabiznes
|
|
# If not running: sudo systemctl start nordabiznes
|
|
|
|
# 2. Check port 5000 is listening
|
|
ss -tlnp | grep :5000
|
|
# Expected: LISTEN 0.0.0.0:5000
|
|
|
|
# 3. Test internal access
|
|
curl -I http://localhost:5000/health
|
|
# Expected: HTTP/1.1 200 OK
|
|
|
|
# 4. Test from NPM server
|
|
ssh maciejpi@10.22.68.250
|
|
curl -I http://57.128.200.27:5000/health
|
|
# Expected: HTTP/1.1 200 OK
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Restart Gunicorn service
|
|
sudo systemctl restart nordabiznes
|
|
|
|
# Check logs for errors
|
|
sudo journalctl -u nordabiznes -n 50
|
|
```
|
|
|
|
---
|
|
|
|
#### Issue 3: Database Connection Errors
|
|
|
|
**Symptoms:**
|
|
- Application shows "Database connection error"
|
|
- Health endpoint returns error
|
|
- Logs show "could not connect to server"
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# 1. Check PostgreSQL service
|
|
sudo systemctl status postgresql
|
|
# If not running: sudo systemctl start postgresql
|
|
|
|
# 2. Test database connection
|
|
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;"
|
|
# Expected: 1 (if password prompt, check .env)
|
|
|
|
# 4. Check active connections
|
|
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='nordabiz';"
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Restart PostgreSQL
|
|
sudo systemctl restart postgresql
|
|
|
|
# Verify connection string in .env
|
|
cat /var/www/nordabiznes/.env | grep DATABASE_URL
|
|
# Expected: postgresql://nordabiz_app:PASSWORD@localhost:5432/nordabiz
|
|
```
|
|
|
|
---
|
|
|
|
#### Issue 4: SSL Certificate Expired
|
|
|
|
**Symptoms:**
|
|
- Browser shows "Your connection is not private"
|
|
- SSL certificate warning
|
|
- Cert expired error
|
|
|
|
**Diagnosis:**
|
|
```bash
|
|
# Check certificate expiry
|
|
openssl s_client -connect nordabiznes.pl:443 -servername nordabiznes.pl < /dev/null 2>/dev/null | \
|
|
openssl x509 -noout -dates
|
|
|
|
# Check NPM logs for renewal errors
|
|
docker logs nginx-proxy-manager_app_1 | grep -i letsencrypt
|
|
```
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Manual certificate renewal via NPM
|
|
# 1. Access NPM Web UI: http://10.22.68.250:81
|
|
# 2. Go to SSL Certificates
|
|
# 3. Find certificate ID 27
|
|
# 4. Click "Renew" button
|
|
|
|
# OR restart NPM to trigger auto-renewal
|
|
docker restart nginx-proxy-manager_app_1
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
### Architecture Documentation
|
|
|
|
- **[System Context Diagram](./01-system-context.md)** - High-level system overview
|
|
- **[Container Diagram](./02-container-diagram.md)** - Major containers and technology stack
|
|
- **[Flask Application Components](./04-flask-components.md)** - Internal application structure (planned)
|
|
- **[Database Schema](./05-database-schema.md)** - Entity relationships (planned)
|
|
- **[Network Topology](./07-network-topology.md)** - Network diagram (planned)
|
|
|
|
### Data Flow Documentation
|
|
|
|
- **[HTTP Request Flow](./flows/06-http-request-flow.md)** - Detailed HTTP request path (planned)
|
|
- **[Authentication Flow](./flows/01-authentication-flow.md)** - User login sequence (planned)
|
|
|
|
### Incident Documentation
|
|
|
|
- **[Incident Report 2026-01-02](../INCIDENT_REPORT_20260102.md)** - ERR_TOO_MANY_REDIRECTS incident
|
|
|
|
### Infrastructure Documentation
|
|
|
|
- **[CLAUDE.md](../../CLAUDE.md)** - Complete project documentation
|
|
- **[Infrastructure Analysis](./.auto-claude/specs/003-.../analysis/infrastructure-components.md)** - Detailed infrastructure analysis (internal)
|
|
|
|
---
|
|
|
|
## Maintenance Notes
|
|
|
|
### When to Update This Diagram
|
|
|
|
**✏️ UPDATE when:**
|
|
- New server added to infrastructure
|
|
- IP address changes
|
|
- Port configuration changes
|
|
- Network topology changes (new firewall rules, NAT changes)
|
|
- SSL/TLS configuration updates
|
|
- Deployment procedure changes
|
|
- Critical configuration changes (like NPM proxy settings)
|
|
|
|
**❌ DON'T UPDATE for:**
|
|
- Application code changes (not infrastructure)
|
|
- Database schema changes (covered in database diagram)
|
|
- New Flask routes (covered in component diagram)
|
|
- Dependency updates (same infrastructure)
|
|
|
|
### Review Frequency
|
|
|
|
- **After incidents:** Immediate review and update
|
|
- **After infrastructure changes:** Within 24 hours
|
|
- **Quarterly:** Verify accuracy against actual configuration
|
|
- **Before major deployments:** Review deployment section
|
|
|
|
### Verification Checklist
|
|
|
|
- [ ] All IP addresses are current
|
|
- [ ] All port numbers are correct
|
|
- [ ] Nginx proxy_pass configuration verified (127.0.0.1:5000)
|
|
- [ ] DNS records match actual configuration
|
|
- [ ] SSL certificate status checked
|
|
- [ ] Firewall rules documented accurately
|
|
- [ ] Service status verified on all servers
|
|
- [ ] Health check endpoints tested
|
|
- [ ] Backup procedures tested
|
|
|
|
---
|
|
|
|
## Glossary
|
|
|
|
| Term | Definition |
|
|
|------|------------|
|
|
| **NPM** | Nginx Proxy Manager - Docker-based reverse proxy with web UI |
|
|
| **NAT** | Network Address Translation - maps public IP to internal IPs |
|
|
| **SSL Termination** | Decrypting HTTPS at proxy, forwarding HTTP to backend |
|
|
| **Fortigate** | Enterprise firewall/router device |
|
|
| **Gunicorn** | Python WSGI HTTP server for running Flask applications |
|
|
| **Let's Encrypt** | Free automated SSL/TLS certificate authority |
|
|
| **HSTS** | HTTP Strict Transport Security - forces HTTPS |
|
|
| **Proxmox VE** | Virtualization platform for managing VMs |
|
|
| **Systemd** | Linux init system and service manager |
|
|
| **WSGI** | Web Server Gateway Interface - Python web server standard |
|
|
| **VM** | Virtual Machine |
|
|
| **vCPU** | Virtual CPU core |
|
|
| **ORM** | Object-Relational Mapping (SQLAlchemy) |
|
|
|
|
---
|
|
|
|
**Document Status:** ✅ Complete
|
|
**Diagram Validated:** 2026-04-04
|
|
**Production Verified:** 2026-04-04 (OVH VPS migration)
|
|
**Mermaid Syntax:** v10.6+
|
|
**Renders in:** GitHub, GitLab, VS Code (with Mermaid extension)
|
|
|
|
---
|
|
|
|
**Production traffic flow:**
|
|
```
|
|
Internet → Nginx (57.128.200.27:443) → Gunicorn (127.0.0.1:5000)
|
|
```
|
|
|
|
**Verify production:**
|
|
```bash
|
|
curl -I https://nordabiznes.pl/health
|
|
# Expected: HTTP/2 200 OK
|
|
```
|