From c3e1883d4914c6acfcd22560bf8c71a9cecdcd25 Mon Sep 17 00:00:00 2001 From: user <> Date: Sat, 6 Jun 2026 14:13:57 +0200 Subject: [PATCH] Update --- VPS.md | 218 +++++++++++++++++++++++++++++ frontend/public/logo.png | Bin 0 -> 1293892 bytes frontend/src/App.vue | 6 + frontend/src/components/Navbar.vue | 2 +- frontend/src/stores/theme.js | 2 +- frontend/src/views/Login.vue | 16 +-- 6 files changed, 234 insertions(+), 10 deletions(-) create mode 100644 VPS.md create mode 100644 frontend/public/logo.png diff --git a/VPS.md b/VPS.md new file mode 100644 index 0000000..8a17505 --- /dev/null +++ b/VPS.md @@ -0,0 +1,218 @@ +# Infrastruktura produkcyjna + +## Architektura + +``` +Internet → VPS (publiczne IP, nginx + SSL) + ↕ WireGuard tunnel (szyfrowany, domowe IP ukryte) + 192.168.2.66 (nginx, port 80) + ↓ LAN + 192.168.2.33 (docker-compose, port 80) +``` + +- Domowe IP nigdy nie jest widoczne publicznie +- SSL terminuje na VPS +- `.66` inicjuje połączenie WireGuard do VPS — zero port-forwardingu na domowym routerze + +--- + +## 1. WireGuard + +### Instalacja (VPS i .66) + +```bash +apt install wireguard +``` + +### VPS — generowanie kluczy + +```bash +wg genkey | tee /etc/wireguard/vps_private.key | wg pubkey > /etc/wireguard/vps_public.key +``` + +### .66 — generowanie kluczy + +```bash +wg genkey | tee /etc/wireguard/home_private.key | wg pubkey > /etc/wireguard/home_public.key +``` + +### `/etc/wireguard/wg0.conf` na VPS + +```ini +[Interface] +Address = 10.10.0.1/24 +ListenPort = 51820 +PrivateKey = + +[Peer] +# .66 home server +PublicKey = +AllowedIPs = 10.10.0.2/32 +``` + +### `/etc/wireguard/wg0.conf` na .66 + +```ini +[Interface] +Address = 10.10.0.2/24 +PrivateKey = + +[Peer] +# VPS +PublicKey = +Endpoint = :51820 +AllowedIPs = 10.10.0.1/32 +PersistentKeepalive = 25 +``` + +> `PersistentKeepalive = 25` — utrzymuje tunel przez NAT domowego routera + +### Uruchomienie (obie maszyny) + +```bash +systemctl enable --now wg-quick@wg0 +``` + +### Weryfikacja + +```bash +wg show # status tunelu +ping 10.10.0.1 # z .66 do VPS +ping 10.10.0.2 # z VPS do .66 +``` + +--- + +## 2. Nginx na VPS + +### Certyfikat SSL + +```bash +apt install certbot python3-certbot-nginx +certbot certonly --nginx -d q2dropzone.xyz +``` + +### `/etc/nginx/sites-available/q2dropzone.xyz` + +```nginx +server { + listen 80; + server_name q2dropzone.xyz; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name q2dropzone.xyz; + + ssl_certificate /etc/letsencrypt/live/q2dropzone.xyz/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/q2dropzone.xyz/privkey.pem; + + client_max_body_size 6g; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_connect_timeout 60s; + + location / { + proxy_pass http://10.10.0.2:80; + proxy_http_version 1.1; + 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 https; + proxy_buffering off; + proxy_request_buffering off; + } +} +``` + +```bash +ln -s /etc/nginx/sites-available/q2dropzone.xyz /etc/nginx/sites-enabled/ +nginx -t && systemctl reload nginx +``` + +--- + +## 3. Nginx na .66 + +### `/etc/nginx/sites-available/q2dropzone.xyz` + +```nginx +server { + listen 80; + server_name q2dropzone.xyz; + + client_max_body_size 6g; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_connect_timeout 60s; + + location / { + proxy_pass http://192.168.2.33:80; + proxy_http_version 1.1; + 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 https; + proxy_buffering off; + proxy_request_buffering off; + } +} +``` + +```bash +ln -s /etc/nginx/sites-available/q2dropzone.xyz /etc/nginx/sites-enabled/ +nginx -t && systemctl reload nginx +``` + +--- + +## 4. Firewall + +### VPS + +```bash +ufw allow 51820/udp # WireGuard +ufw allow 80/tcp # HTTP (redirect do HTTPS) +ufw allow 443/tcp # HTTPS +ufw enable +``` + +### .66 + +```bash +# port 80 tylko z WireGuard IP VPS-a +ufw allow in on wg0 from 10.10.0.1 to any port 80 +ufw deny 80 +ufw enable +``` + +--- + +## 5. .33 — uruchomienie aplikacji + +```bash +git clone q2dropzone && cd q2dropzone +cp .env.example .env +nano .env # uzupełnij DB_PASSWORD, JWT_SECRET, DISK_ROOT +docker-compose up -d --build +``` + +--- + +## DNS + +Rekord A dla `q2dropzone.xyz` → publiczne IP VPS (nie domowe). + +--- + +## Przepływ requestu + +``` +klient + → VPS:443 (SSL terminate, proxy_pass przez WireGuard) + → .66:80 (proxy_pass przez LAN) + → .33:80 (nginx w docker → backend Go) +``` + +Każdy nginx ma `proxy_buffering off` i `proxy_request_buffering off` — duże pliki streamowane bez buforowania na dysk. diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..db8d2385da5c309e07710ef03611ac81ec780302 GIT binary patch literal 1293892 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