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). Kontenerwebpublikuje port 8007 wyłącznie na127.0.0.1, a baza danych nie ma opublikowanego portu, więc to bezpieczne. Nie zmieniaj127.0.0.1:8007na samo8007i nie dodawajports:do usługidb.
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/htmlto domyślnyrootnginx. Jeśli któryś blokserver(np. domyślny lub innego projektu) maroot /usr/share/nginx/html;, to kod Spareload, a w najgorszym razie.env.prod, mogą być dostępne podhttp://serwer/transport/…. Plikdeploy/nginx.confcelowo nie ustawiarootna 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 -1Jeśli dostaniesz
200, dodaj do blokuserver, który ma tenroot: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.prodzawiera 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)
- Settings → Connect → Get started: włącz Connect dla platformy (typ Platform / marketplace).
- Connect → Settings → Branding: nazwa, logo, kolor (widoczne w onboardingu przewoźników).
- Connect → Settings → Onboarding options → Countries: włącz kraje przewoźników (PL, DE, CZ, SK, FR, LT…).
- Developers → API keys: skopiuj
sk_live_…→STRIPE_SECRET_KEYipk_live_…→STRIPE_PUBLISHABLE_KEY. Na serwerze testowym użyj kluczysk_test_…/pk_test_…. - Developers → Webhooks → Add endpoint:
- URL:
https://spareload.obsydian.eu/stripe/webhook/ - Zaznacz Listen to events on Connected accounts oraz zdarzenia konta platformy.
- Zdarzenia:
checkout.session.completed,account.updated,payment_intent.canceled. - 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_KEYi ustawGUS_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.euotwiera się z ważnym certyfikatem, ahttp://przekierowuje nahttps://. - [ ] 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 schedulerpokazuje 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.logierror.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 -horazdocker system df. Stare obrazy usuniesz poleceniemdocker image prune -f.
13. Lista kontrolna bezpieczeństwa
- [ ]
DJANGO_DEBUG=0,DEMO_MODE=0, długiDJANGO_SECRET_KEY. - [ ]
.env.prodma uprawnienia 600 i nie trafił do repozytorium. - [ ] Logowanie SSH tylko kluczem, root wyłączony,
ufwaktywny. - [ ] 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).