Tryb demonstracyjny — płatności w trybie testowym Stripe (karta 4242 4242 4242 4242).

Spareload — instrukcja wdrożenia na serwer

Wersja 0.1 · 14 listopada 2026 Dla osoby z dostępem SSH do serwera i podstawową znajomością Linuksa. Wszystkie polecenia uruchamiasz na serwerze, chyba że zaznaczono inaczej.


1. Co zostanie uruchomione

Internet ──► nginx na hoście (80/443, HTTPS z Let's Encrypt przez certbot)
               ├── /static/*  → pliki statyczne z /usr/share/nginx/html/transport/staticfiles (bezpośrednio z nginx)
               └── reszta     → 127.0.0.1:8007 → web: Django + gunicorn (3 procesy, kontener)
                                 ├── db: PostgreSQL 18 (wolumen pgdata)
                                 └── media: zdjęcia CMR, polisy (wolumen media, dostęp tylko po zalogowaniu)
             scheduler: co 15 min `run_automations` (auto-potwierdzenia po 48 h, eskalacje reklamacji)

Aplikacja, baza i scheduler działają w Dockerze, z pliku docker-compose.prod.yml. Nginx działa bezpośrednio na serwerze, więc może obsługiwać też inne strony. Konfiguracja aplikacji jest w jednym pliku .env.prod, a nginx w deploy/nginx.conf.

2. Wymagania

Element Minimum Uwagi
Serwer VPS 2 vCPU / 4 GB RAM / 40 GB SSD Pilot. Później osobna, zarządzana baza danych.
Lokalizacja UE (np. Hetzner DE/FI, OVH PL/FR, Scaleway FR) RODO: dane kierowców, GPS i dokumenty zostają w UE.
System Ubuntu 24.04 LTS Inne dystrybucje też zadziałają, polecenia mogą się różnić.
Domena rekord A (i ewentualnie AAAA) → IP serwera Musi działać przed uruchomieniem certbota (punkt 8).
Konta Stripe (z włączonym Connect), klucz GUS BIR 1.1 Patrz punkty 6 i 7.

3. Przygotowanie serwera (jednorazowo)

# 3.1 Aktualizacja i użytkownik do wdrożeń (nie pracuj jako root)
apt update && apt -y upgrade
adduser deploy && usermod -aG sudo deploy
# skopiuj swój klucz SSH: na KOMPUTERZE LOKALNYM:  ssh-copy-id deploy@IP_SERWERA

# 3.2 Zabezpieczenie SSH: tylko klucze, bez logowania roota
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/; s/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart ssh

# 3.3 Zapora: tylko SSH, HTTP, HTTPS
sudo ufw allow OpenSSH && sudo ufw allow 80,443/tcp && sudo ufw enable

# 3.4 Automatyczne poprawki bezpieczeństwa
sudo apt -y install unattended-upgrades && sudo dpkg-reconfigure -plow unattended-upgrades

# 3.5 Docker (oficjalne repozytorium)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker deploy   # wyloguj się i zaloguj ponownie

# 3.6 Nginx i certbot (pomiń, jeśli nginx już działa na serwerze)
sudo apt -y install nginx certbot python3-certbot-nginx

Uwaga: Docker publikuje porty z pominięciem reguł ufw. Na zewnątrz wystawiony jest tylko nginx (80/443). Kontener web publikuje port 8007 wyłącznie na 127.0.0.1, a baza danych nie ma opublikowanego portu, więc to bezpieczne. Nie zmieniaj 127.0.0.1:8007 na samo 8007 i nie dodawaj ports: do usługi db.

4. Pobranie aplikacji

sudo mkdir -p /usr/share/nginx/html/transport && sudo chown deploy:deploy /usr/share/nginx/html/transport
cd /usr/share/nginx/html/transport
git clone <ADRES_REPOZYTORIUM> .
# (bez repozytorium: na komputerze lokalnym  rsync -av --exclude .env --exclude media ./ deploy@IP:/usr/share/nginx/html/transport/)

Uwaga — katalog w drzewie WWW. /usr/share/nginx/html to domyślny root nginx. Jeśli któryś blok server (np. domyślny lub innego projektu) ma root /usr/share/nginx/html;, to kod Spareload, a w najgorszym razie .env.prod, mogą być dostępne pod http://serwer/transport/…. Plik deploy/nginx.conf celowo nie ustawia root na katalog projektu. Po wdrożeniu sprawdź, że oba polecenia zwracają 403 lub 404:

bash curl -sI http://127.0.0.1/transport/.env.prod | head -1 curl -sI http://127.0.0.1/transport/docker-compose.prod.yml | head -1

Jeśli dostaniesz 200, dodaj do bloku server, który ma ten root: location ^~ /transport/ { deny all; } i przeładuj nginx. chmod 600 .env.prod (punkt 5) dodatkowo blokuje odczyt sekretów przez użytkownika nginx.

5. Konfiguracja .env.prod

cp .env.prod.example .env.prod
chmod 600 .env.prod
nano .env.prod

Uzupełnij co najmniej:

Zmienna Wartość
SITE_URL, DJANGO_ALLOWED_HOSTS Twoja domena, np. spareload.obsydian.eu / https://spareload.obsydian.eu
DJANGO_SECRET_KEY wynik python3 -c "import secrets; print(secrets.token_urlsafe(50))"
POSTGRES_PASSWORD długie losowe hasło (np. openssl rand -base64 32)
ADMIN_URL Adres panelu administracyjnego Django, np. zaplecze-7f3k/ → https://spareload.obsydian.eu/zaplecze-7f3k/. Dowolny tekst bez / na początku. Puste = admin/. Ustaw własny, żeby boty nie trafiały w /admin/.
DEMO_MODE 0 na produkcji. 1 tylko na serwerze pokazowym: tworzy konta demo z hasłem demo12345.
PAYMENT_BACKEND stripe (albo fake na serwerze pokazowym bez Stripe)
STRIPE_* punkt 6
GUS_ENV, GUS_API_KEY punkt 7

Plik .env.prod zawiera sekrety. Nie commituj go do repozytorium i nie wysyłaj e-mailem. Przechowuj jego kopię w menedżerze haseł.

6. Stripe Connect

6.1 Konfiguracja konta platformy (Dashboard Stripe)

  1. Settings → Connect → Get started: włącz Connect dla platformy (typ Platform / marketplace).
  2. Connect → Settings → Branding: nazwa, logo, kolor (widoczne w onboardingu przewoźników).
  3. Connect → Settings → Onboarding options → Countries: włącz kraje przewoźników (PL, DE, CZ, SK, FR, LT…).
  4. Developers → API keys: skopiuj sk_live_… → STRIPE_SECRET_KEY i pk_live_… → STRIPE_PUBLISHABLE_KEY. Na serwerze testowym użyj kluczy sk_test_… / pk_test_….
  5. Developers → Webhooks → Add endpoint:
  6. URL: https://spareload.obsydian.eu/stripe/webhook/
  7. Zaznacz Listen to events on Connected accounts oraz zdarzenia konta platformy.
  8. Zdarzenia: checkout.session.completed, account.updated, payment_intent.canceled.
  9. Skopiuj Signing secret whsec_… → STRIPE_WEBHOOK_SECRET.

6.2 Jak przepływają pieniądze

Krok Wywołanie Stripe
Zleceniodawca rezerwuje Checkout, capture_method=manual (blokada na karcie) albo obciążenie, gdy załadunek za > 7 dni
Przewoźnik zapisuje kartę do kaucji Checkout mode=setup
Przewoźnik przyjmuje zlecenie PaymentIntent off-session na zapisanej karcie (kaucja)
Potwierdzenie dostawy capture (częściowy przy reklamacji) → Transfer na konto Express przewoźnika (source_transaction)
Prowizja zostaje w saldzie platformy
Kara umowna z kaucji Transfer na konto zleceniodawcy. Jeśli nie podłączył konta wypłat, transfer czeka i wykona się automatycznie po podłączeniu.

6.3 Przed przejściem na sk_live_

  • [ ] Przejdź pełny scenariusz na kluczach testowych, z webhookiem ustawionym na serwer testowy.
  • [ ] Ustaw opis transakcji na wyciągu (Settings → Public details → Statement descriptor).
  • [ ] Skonsultuj z prawnikiem regulamin i OWP (patrz docs/01-…).
  • [ ] Ustal z księgowością fakturowanie prowizji (odwrotne obciążenie dla przewoźników z UE).

7. GUS BIR 1.1 i VIES

  • VIES (sprawdzanie VAT UE) nie wymaga klucza. Wystarczy VIES_ENABLED=1. Gdy VIES jest niedostępny (to się zdarza), rejestracja przechodzi, a operator sprawdza numer później przyciskiem „Sprawdź VIES”.
  • GUS: wpisz produkcyjny klucz BIR do GUS_API_KEY i ustaw GUS_ENV=prod. Przy pustym kluczu aplikacja używa środowiska testowego GUS z danymi zanonimizowanymi (np. „ul. Test-Krucza”).
  • Zapytania do rejestrów z formularza rejestracji są ograniczone do 20 na godzinę na adres IP, co chroni klucz GUS przed nadużyciem.

Szybki test po wdrożeniu (powinien zwrócić nazwę firmy i "source": ["VIES", "GUS"]):

curl -s "https://spareload.obsydian.eu/api/rejestry/?vat=PL<NIP_TWOJEJ_FIRMY>"

8. Pierwsze uruchomienie

8.1 Aplikacja

cd /usr/share/nginx/html/transport
# Katalog na pliki statyczne: zapisuje go kontener (użytkownik uid 1000), czyta nginx
mkdir -p staticfiles && sudo chown 1000:1000 staticfiles

docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

# Sprawdź, czy wszystko wstało (db: healthy; web, scheduler: Up)
docker compose -f docker-compose.prod.yml --env-file .env.prod ps
docker compose -f docker-compose.prod.yml --env-file .env.prod logs -f web   # Ctrl+C aby wyjść
curl -sI http://127.0.0.1:8007/ | head -1     # odpowiedź z gunicorna, np. HTTP/1.1 400 (Host spoza ALLOWED_HOSTS — to OK)

8.2 Nginx i HTTPS

sudo cp /usr/share/nginx/html/transport/deploy/nginx.conf /etc/nginx/sites-available/spareload
sudo ln -s /etc/nginx/sites-available/spareload /etc/nginx/sites-enabled/spareload
sudo rm -f /etc/nginx/sites-enabled/default   # tylko jeśli nie używasz domyślnej strony nginx
sudo nginx -t && sudo systemctl reload nginx

# Certyfikat Let's Encrypt; certbot dopisze blok 443 i przekierowanie http → https,
# a odnawianie ustawi sam (systemctl list-timers | grep certbot)
sudo certbot --nginx -d spareload.obsydian.eu

Nginx musi mieć prawo odczytu /usr/share/nginx/html/transport/staticfiles (użytkownik www-data). Przy domyślnych uprawnieniach (755 na katalogach) działa od razu. Jeśli CSS się nie ładuje, sprawdź sudo -u www-data ls /usr/share/nginx/html/transport/staticfiles i sudo tail /var/log/nginx/error.log.

8.3 Konto operatora

# Konto operatora platformy (dostęp do /operator/ i panelu admina pod adresem z ADMIN_URL)
docker compose -f docker-compose.prod.yml --env-file .env.prod exec web python manage.py createsuperuser

Kontener web przy każdym starcie sam wykonuje migracje bazy, ładuje słownik miast i zbiera pliki statyczne.

Najczęstsze czynności obsłużysz przez make (punkt 11). Do pozostałych dodaj alias w ~/.bashrc:

alias sl='docker compose -f /usr/share/nginx/html/transport/docker-compose.prod.yml --env-file /usr/share/nginx/html/transport/.env.prod'
# potem: sl ps · sl logs -f web · sl exec web python manage.py createsuperuser

9. Weryfikacja po wdrożeniu

  • [ ] https://spareload.obsydian.eu otwiera się z ważnym certyfikatem, a http:// przekierowuje na https://.
  • [ ] Logowanie operatora i panel /operator/ działają.
  • [ ] Rejestracja testowej firmy pobiera dane z VIES i GUS.
  • [ ] Przewoźnik: Firma → Podłącz konto Stripe przechodzi onboarding; Dodaj kartę zapisuje kartę.
  • [ ] Pełny przewóz testowy na kluczach testowych: rezerwacja → Checkout → przyjęcie → załadunek → dostawa ze zdjęciem → potwierdzenie → w Stripe widać capture i transfer.
  • [ ] Stripe Dashboard → Webhooks: zdarzenia mają status 200.
  • [ ] sl logs scheduler pokazuje co 15 minut „Auto-potwierdzone dostawy: …”.
  • [ ] Zdjęcie CMR otwarte w oknie prywatnym (bez logowania) nie jest dostępne.

10. Kopie zapasowe

Zabezpieczasz dwie rzeczy: bazę danych i pliki (zdjęcia CMR, polisy).

sudo mkdir -p /opt/backup && sudo chown deploy:deploy /opt/backup
cat > /usr/share/nginx/html/transport/backup.sh <<'EOF'
#!/bin/sh
set -e
cd /usr/share/nginx/html/transport
TS=$(date +%Y%m%d-%H%M)
C="docker compose -f docker-compose.prod.yml --env-file .env.prod"
$C exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -Fc "$POSTGRES_DB"' > /opt/backup/db-$TS.dump
$C exec -T web tar czf - -C /app media > /opt/backup/media-$TS.tgz
find /opt/backup -type f -mtime +14 -delete      # lokalnie trzymaj 14 dni
EOF
chmod +x /usr/share/nginx/html/transport/backup.sh
( crontab -l 2>/dev/null; echo "15 3 * * * /usr/share/nginx/html/transport/backup.sh >> /opt/backup/backup.log 2>&1" ) | crontab -

Kopia poza serwerem jest obowiązkowa. Wysyłaj /opt/backup np. do Hetzner Storage Box, OVH Object Storage lub Backblaze B2 (region UE), np. przez rclone sync. Kopia leżąca tylko na tym samym serwerze nie chroni przed jego utratą.

Odtworzenie (przećwicz przynajmniej raz przed startem):

sl stop web scheduler
sl exec -T db sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' < /opt/backup/db-RRRRMMDD-GGMM.dump
sl exec -T -u root web sh -c 'tar xzf - -C /app && chown -R app:app /app/media' < /opt/backup/media-RRRRMMDD-GGMM.tgz
sl start web scheduler

11. Aktualizacja aplikacji

cd /usr/share/nginx/html/transport
make deploy

make deploy po kolei: robi kopię zapasową (jeśli jest backup.sh), pobiera zmiany (git pull --ff-only), buduje obraz, wykonuje migracje bazy, restartuje web i scheduler, sprawdza, czy aplikacja odpowiada, i pokazuje stan kontenerów. Gdy którykolwiek krok się nie uda, zatrzymuje się i nie restartuje aplikacji.

Inne polecenia (make bez argumentów pokazuje pełną listę): make ps, make logs, make restart, make migrate, make superuser, make shell, make test, make backup. Makefile sam rozpoznaje serwer po obecności pliku .env.prod.

Wycofanie zmian: git checkout <poprzedni_commit> && sl up -d --build (wróć potem na gałąź main: git checkout main, zanim znów użyjesz make deploy). Jeśli nowa wersja zmieniła strukturę bazy, odtwórz także kopię bazy sprzed aktualizacji (punkt 10).

12. Monitoring i utrzymanie

  • Dostępność: darmowy monitor HTTP (np. UptimeRobot, Better Stack) na https://spareload.obsydian.eu/.
  • Logi: sl logs -f web (żądania gunicorna) oraz /var/log/nginx/access.log i error.log. Docker trzyma je lokalnie. Gdy zajmują dużo miejsca, ustaw rotację w /etc/docker/daemon.json: {"log-driver":"json-file","log-opts":{"max-size":"20m","max-file":"5"}}.
  • Błędy aplikacji: w kolejnym etapie podłącz Sentry (region UE).
  • Stripe: Dashboard → Webhooks (nieudane dostarczenia), Connect → Accounts (przewoźnicy z niedokończonym onboardingiem).
  • Miejsce na dysku: df -h oraz docker system df. Stare obrazy usuniesz poleceniem docker image prune -f.

13. Lista kontrolna bezpieczeństwa

  • [ ] DJANGO_DEBUG=0, DEMO_MODE=0, długi DJANGO_SECRET_KEY.
  • [ ] .env.prod ma uprawnienia 600 i nie trafił do repozytorium.
  • [ ] Logowanie SSH tylko kluczem, root wyłączony, ufw aktywny.
  • [ ] Baza nie ma opublikowanego portu.
  • [ ] Silne hasło operatora. 2FA dla operatorów to kolejny etap.
  • [ ] Kopie zapasowe działają i trafiają poza serwer; odtworzenie zostało przećwiczone.
  • [ ] Klucze Stripe sk_live_ znają tylko osoby, które muszą.

14. Serwer pokazowy (opcjonalnie)

Do prezentacji dla inwestorów i klientów postaw osobną instancję, np. demo.spareload.obsydian.eu, z ustawieniami: DEMO_MODE=1, PAYMENT_BACKEND=fake (albo stripe z kluczami testowymi) i bez klucza GUS (środowisko testowe GUS). Dane demo przywrócisz poleceniem:

sl exec web python manage.py seed_demo --reset

15. Lokalnie: Stripe w trybie testowym (dla programisty)

# .env:  PAYMENT_BACKEND=stripe, STRIPE_SECRET_KEY=sk_test_…, STRIPE_PUBLISHABLE_KEY=pk_test_…
docker compose --profile stripe up              # uruchamia też stripe-cli (przekazuje webhooki)
docker compose logs stripe-cli | grep whsec     # skopiuj whsec_… do STRIPE_WEBHOOK_SECRET, potem: docker compose up -d web

Karta testowa: 4242 4242 4242 4242, dowolna przyszła data i CVC. W onboardingu Connect w trybie testowym używaj danych testowych Stripe (np. kod SMS 000000).