Instalacja
Uruchom Sancho na własnym serwerze za pojedynczą subdomeną (np. sancho.example.com) przy użyciu pre-zbudowanych obrazów GHCR i Docker Compose.
Wymagania
Section titled “Wymagania”- docker
- Plugin Docker Compose (v2.20+)
Zawartość katalogu
Section titled “Zawartość katalogu”| Plik | Przeznaczenie |
|---|---|
docker-compose.yaml |
Definicje usług (postgres, rabbitmq, redis, api, app, caddy + opcjonalnie postgres-backup, garage) |
Caddyfile |
Konfiguracja zewnętrznego Caddy — terminacja TLS i reverse proxy |
garage.toml |
Konfiguracja single-node Garage; używana tylko gdy aktywny jest profil garage |
.env.example |
Szablon zmiennych środowiskowych runtime; skopiuj do .env |
sancho-update.service |
Opcjonalny unit systemd: pobiera i restartuje kontenery api i app |
sancho-update.timer |
Opcjonalny timer systemd: uruchamia serwis aktualizacji codziennie o 04:00 UTC |
Usługi rdzeniowe (zawsze działają): postgres, rabbitmq, redis, api, app, caddy.
Profile opcjonalne (domyślnie wyłączone, włączane przez COMPOSE_PROFILES w .env):
backup— codziennypg_dumpdo S3 przez serwispostgres-backup. Patrz Kopie zapasowe.garage— wewnętrzny backend S3, gdy nie chcesz zewnętrznego dostawcy. Patrz Wewnętrzny backend S3 (Garage).
Ustaw COMPOSE_PROFILES=backup,garage (albo dowolny podzbiór) w .env przed docker compose up -d.
Architektura
Section titled “Architektura” Browser │ https://sancho.example.com ▼ ┌─────────────────┐ │ Caddy (80/443) │ auto-TLS via Let's Encrypt └────────┬────────┘ │ ┌─────────┼──────────┐ │ │ │ /api/* /v1/* /analytics/* everything else │ │ │ ▼ ▼ ▼ api:3100 PostHog app:80 │ (EU) (static SPA) │ ┌─────┼─────┐ │ │ │ ▼ ▼ ▼postgres rabbitmq redis
api → external: SMTP relay (or Cloudflare Email Send), Google OAuth, Nextcloud OAuth, S3, Parseur, [Stripe]Pojedyncza subdomena, routing oparty o ścieżkę. Cookies są ograniczone do DEPLOY_HOSTNAME; HTTPS jest obowiązkowe (cookies sesji są oznaczone SameSite=Strict; Secure; HttpOnly).
Wymagania wstępne
Section titled “Wymagania wstępne”- Serwer Linux, Docker 24.0.6+ z Docker Compose v2.20+ (flaga
required: falsewdepends_onzostała dodana w Compose v2.20) - Publiczna domena z rekordem DNS A/AAAA wskazującym na serwer
- Otwarte porty 80 i 443 (port 80 jest potrzebny do wyzwania ACME HTTP)
- ≥ 4 vCPU / 16 GB RAM (zalecane)
Usługi zewnętrzne
Section titled “Usługi zewnętrzne”| Usługa | Przeznaczenie | Wymagana? |
|---|---|---|
| Google Cloud OAuth | Logowanie użytkowników | Wymagana, jeśli używasz SSO Google |
| Nextcloud OAuth (built-in) | Logowanie przez własny Nextcloud (wbudowany klient OAuth 2.0) | Opcjonalna (alternatywa dla Google) |
| Nextcloud OIDC | Logowanie przez Nextcloud z zewnętrzną aplikacją OIDC | Opcjonalna (alternatywa wobec bloku OAuth) |
| Relay SMTP (lub Cloudflare Email Send) | Email transakcyjny (reset hasła, weryfikacja) | Jeden z dwóch wymagany |
| Storage kompatybilny z S3 | Przechowywanie awatarów i obrazów dokumentów dostawy | Wymagana (zewnętrzny dostawca lub wewnętrzny profil garage) |
| Parseur | OCR dokumentów dostawy | Tylko dla OCR_PROVIDER=parseur |
| Anthropic | OCR dokumentów dostawy modelem Claude | Tylko dla OCR_PROVIDER=anthropic |
| PostHog | Analityka produktowa | Opcjonalna |
| Axiom | Agregacja logów | Opcjonalna |
Generowanie sekretów
Section titled “Generowanie sekretów”openssl rand -hex 32 # API_SESSION_SECRETopenssl rand -hex 32 # API_COOKIE_SECRETopenssl rand -base64 24 # PG_PASSWORD, RABBITMQ_ADMIN_PASSWORD, ROOT_PASSWORDKonfiguracja pliku .env
Section titled “Konfiguracja pliku .env”cp .env.example .envZabezpiecz uprawnienia pliku
Section titled “Zabezpiecz uprawnienia pliku”Zabezpiecz uprawnienia zanim wkleisz jakikolwiek sekret. Domyślna umaska zostawia .env z prawami światoodczytu (0644). Plik będzie zawierał ROOT_PASSWORD, sekrety sesji/cookie, hasła Postgresa i RabbitMQ, klucze S3, sekrety klientów OAuth oraz tokeny Cloudflare/Parseur/Axiom w postaci jawnej — ogranicz uprawnienia, zanim wpiszesz dane.
chmod 600 .envchown root:root .envchmod 600 sprawia, że plik jest czytelny tylko dla właściciela; grupa i inni nie mają żadnych uprawnień. Przy 0600 własność grupowa jest nieistotna, więc root:root jest w porządku nawet jeśli inne konta są w grupie docker. Po zapisaniu sekretów zweryfikuj ls -l .env — wynik musi zaczynać się od -rw-------.
Szablon grupuje zmienne według przeznaczenia. Wypełniaj kolejne grupy w podanej kolejności.
Podstawowe (wymagane)
Section titled “Podstawowe (wymagane)”DEPLOY_HOSTNAME— publiczny FQDN, np.sancho.example.com. API używa tej wartości także do ograniczania zakresu cookies sesji oraz do budowania URL-i callback OAuth i wiadomości email, więc musi się zgadzać z URL-em odwiedzanym przez użytkowników.ACME_EMAIL— adres kontaktowy, który Caddy rejestruje w Let’s Encrypt do powiadomień o wygasaniu certyfikatów.SANCHO_CHANNEL— kanał wydań. Domyślnieprod(obrazy z master-a:sancho-wms-{api,app}-prod). Ustawdev, aby śledzić pre-release-y z gałęzi develop (sancho-wms-{api,app}-dev).DEPLOY_ENV— sufiks nazw bucketów S3 (sancho-env-${DEPLOY_ENV}isancho-env-${DEPLOY_ENV}-account-1). Domyślnieprod. Nie zmieniaj po pierwszym uruchomieniu — nazwy bucketów są z niego wyprowadzane i nie są migrowane.
Sekrety (wymagane)
Section titled “Sekrety (wymagane)”Wszystkie te sekrety są obowiązkowe — schemat konfiguracji API odrzuca brakujące wartości na starcie i kontener natychmiast kończy działanie. Wklej wartości z sekcji Generowanie sekretów.
API_SESSION_SECRET— wartość hex (min. 32 znaki), podpisuje cookies sesji.API_COOKIE_SECRET— wartość hex (min. 32 znaki), podpisuje pozostałe cookies.PG_PASSWORD— wartość base64, hasło użytkownika Postgres.RABBITMQ_ADMIN_PASSWORD— wartość base64, hasło administratora RabbitMQ.ROOT_PASSWORD— wartość base64, używana do utworzenia użytkownika root przy pierwszym uruchomieniu. Musi pozostać ustawiona przy każdym restarcie (konfiguracja jest ponownie walidowana); zmiana wartości w.envwpływa tylko na przyszłe seedy, nie na hasło istniejącego konta root.
Baza danych i kolejka (wymagane)
Section titled “Baza danych i kolejka (wymagane)”Pozostaw wartości domyślne, chyba że masz powód, by je zmienić.
PG_USER— domyślniesancho.PG_DB— domyślniesancho.RABBITMQ_ADMIN_USER— domyślniesancho.
Logowanie (wymagane)
Section titled “Logowanie (wymagane)”Obie zmienne są wymagane — brak którejkolwiek zatrzymuje start API.
API_LOG_LEVEL— poziom logów API:trace,debug,info,warn,errorlubfatal. Szablon ustawiainfo.API_LOG_PRETTY—trueformatuje logi czytelnie dla człowieka; na produkcji pozostawfalse(JSON).
Transport email (wymagany jeden z dwóch)
Section titled “Transport email (wymagany jeden z dwóch)”Jeśli ustawione są oba, Cloudflare wygrywa.
SMTP_HOST— hostname relay-a.SMTP_PORT— domyślnie587.SMTP_USER— opcjonalne; pozostaw puste dla relay-ów uwierzytelniających samym hasłem.SMTP_PASSWORD— wymagane razem zSMTP_HOST.
CLOUDFLARE_EMAIL_ACCOUNT_ID— ID konta Cloudflare.CLOUDFLARE_EMAIL_API_TOKEN— token z zakresem konta i uprawnieniemEmail Routing: Send.
Metody uwierzytelniania
Section titled “Metody uwierzytelniania”Logowanie email/hasło i Google konfiguruje się w .env — pusty blok ukrywa odpowiednią opcję logowania, a aplikacja webowa odpytuje API w runtime, które metody są aktywne, więc przełączanie wymaga jedynie restartu kontenera api — bez przebudowy obrazu. Logowanie Nextcloud (OAuth i OIDC) konfiguruje się natomiast w UI Sancho (Konto → Konfiguracja) — poświadczenia, listy dozwolonych domen i mapowanie grup na role są zapisywane w bazie danych, a przycisk logowania pojawia się automatycznie, bez zmiennych .env i bez restartu. Logowanie Nextcloud musi startować z email-first formularza Sancho: API wymaga rozwiązanego adresu email jako login_hint i odrzuca callback, jeśli Nextcloud zwróci inny email. Wybierz jedną ścieżkę Nextcloud (OAuth lub OIDC); włączenie obu działa, ale wyświetla dwa identyczne przyciski i ryzykuje kolizję providerId, jeśli ten sam użytkownik zaloguje się obiema drogami.
ENABLE_EMAIL_AUTH—truezostawia logowanie email/hasło włączone; ustawfalse, aby wymusić SSO.
GOOGLE_ID/GOOGLE_SECRET— klient OAuth z Google Cloud Console → APIs & Services → Credentials → Create OAuth client → Web application.- Authorized redirect URI:
https://<DEPLOY_HOSTNAME>/api/login/google/callback. - Aby przycisk się pojawił, obie wartości muszą być ustawione.
Wbudowany klient OAuth 2.0 — dostępny w każdej instalacji Nextcloud bez dodatkowych aplikacji. Profil użytkownika pobierany jest przez OCS provisioning API; access token jest tokenem Bearer dla całego konta.
- Konfiguracja odbywa się w UI Sancho: Konto → Konfiguracja — podaj bazowy URL Nextcloud (np.
https://cloud.example.com) oraz dane klienta OAuth 2.0. Ustawienia są zapisywane w bazie danych — bez zmiennych.envi bez restartu API. - Klienta utwórz w Nextcloud: Ustawienia administracyjne → Bezpieczeństwo → Klienci OAuth 2.0 → Dodaj klienta.
- Redirection URI:
https://<DEPLOY_HOSTNAME>/api/login/nextcloud/<accountName>/callback(nazwa konta Sancho, dla self-hosted — konta root).
Dla instalacji Nextcloud z zewnętrzną aplikacją OIDC (oidc lub user_oidc, instalowane z katalogu aplikacji). Klienta zarejestruj w panelu tej aplikacji, nie w wbudowanej liście klientów OAuth 2.0 — to dwa osobne magazyny. API żąda zakresów openid profile email groups offline_access, weryfikuje id_token i odczytuje standardowy endpoint /userinfo. Discovery wykonywany jest leniwie przy pierwszym kliknięciu pod <URL Nextcloud>/.well-known/openid-configuration, więc niedostępny Nextcloud nie blokuje startu API.
- Konfiguracja odbywa się w UI Sancho: Konto → Konfiguracja — podaj bazowy URL Nextcloud oraz dane klienta z panelu aplikacji OIDC. Ustawienia są zapisywane w bazie danych — bez zmiennych
.envi bez restartu API. - Redirection URI:
https://<DEPLOY_HOSTNAME>/api/login/nextcloud-oidc/<accountName>/callback(nazwa konta Sancho, dla self-hosted — konta root). - Automatyczne przypisywanie ról z grup — opcja w tej samej konfiguracji. Gdy włączona, przy każdym logowaniu OIDC API odczytuje claim
groups[]i synchronizuje rolę użytkownika: przypisuje pierwszą własną rolę Sancho, której nazwa pasuje do nazwy grupy; gdy żadna grupa nie pasuje, cofa użytkownika do wbudowanej roliMember. KontaSuperAdminnie są modyfikowane, a nazwy ról wbudowanych (Member,SuperAdmin) są pomijane przy dopasowaniu. Własne role muszą najpierw istnieć w Sancho z nazwą identyczną z nazwą grupy w Nextcloud.
S3 (wymagane) — wybierz jeden z dwóch backendów
Section titled “S3 (wymagane) — wybierz jeden z dwóch backendów”-
Zewnętrzny dostawca (zalecane dla produkcji) — AWS S3, Cloudflare R2, Backblaze B2, MinIO itp. Bucketów dwa sposoby na zaprowizjonowanie:
- Utwórz buckety z wyprzedzeniem i przyznaj kluczowi dostęp na poziomie obiektów.
- Albo nadaj kluczowi uprawnienie
CreateBucketi pozwól API utworzyć je przy pierwszym starcie.
-
Wewnętrzny Garage — ustaw
COMPOSE_PROFILES=garageorazS3_ENDPOINT=http://garage:3900. WygenerujS3_API_KEY/S3_API_SECRET/GARAGE_RPC_SECRETprzezopenssl rand. Bez dodatkowych komend — API tworzy swoje buckety przy pierwszym starcie. Patrz Wewnętrzny backend S3 (Garage).
Zmienne:
S3_ENDPOINT— URL endpointu kompatybilnego z S3 (http://garage:3900dla wewnętrznego Garage).S3_API_KEY— access key.S3_API_SECRET— secret key.GARAGE_RPC_SECRET— wymagany tylko gdygaragejest wCOMPOSE_PROFILES. Wygenerujopenssl rand -hex 32.
OCR_PROVIDER wybiera silnik rozpoznawania: parseur (domyślnie), custom — własna usługa HTTP, albo anthropic — ekstrakcja modelem Claude (Anthropic). Opis kontraktu dla wariantu custom znajdziesz w Własne OCR.
Dla OCR_PROVIDER=parseur zarejestruj się na parseur.com, utwórz parser dopasowany do layoutu Twoich dokumentów dostaw, skopiuj klucz API i ID parsera z ustawień parsera.
PARSEUR_API_KEY— z dashboardu Parseur. Wymagany tylko dlaOCR_PROVIDER=parseur.PARSEUR_GOODS_ISSUED_NOTE_PARSER_ID— ID docelowego parsera. Pozostaw puste, aby wyłączyć OCR dokumentów dostaw.
Dla OCR_PROVIDER=custom wymagany jest OCR_CUSTOM_BASE_URL; opcjonalnie OCR_CUSTOM_SUBMIT_PATH, OCR_CUSTOM_STATUS_PATH i OCR_CUSTOM_API_KEY. Niepoprawna kombinacja zatrzymuje start API.
Dla OCR_PROVIDER=anthropic wymagany jest ANTHROPIC_API_KEY; opcjonalnie OCR_ANTHROPIC_MODEL (domyślnie claude-opus-5). Nie ma konfiguracji parsera — skany dokumentów są wysyłane do API Anthropic w celu ekstrakcji.
Profile opcjonalne
Section titled “Profile opcjonalne”Włącz przez COMPOSE_PROFILES (CSV: backup, garage lub oba).
COMPOSE_PROFILES— np.backup,garagelubbackup,garage. Puste = uruchamiają się tylko serwisy rdzeniowe.BACKUP_S3_BUCKET— nazwa bucketu na codziennypg_dump. Wymagany tylko gdybackupjest wCOMPOSE_PROFILES. API nie tworzy tego bucketu — utwórz go na zewnętrznym S3 z wyprzedzeniem, a dla wewnętrznego Garage uruchom razdocker compose exec garage /garage bucket create $BACKUP_S3_BUCKET. Patrz Kopie zapasowe.
Pojedyncze konto i kontrolowana rejestracja
Section titled “Pojedyncze konto i kontrolowana rejestracja”Sancho self-hosted działa w trybie pojedynczego najemcy: logowania SSO (Google i Nextcloud) trafiają do konta root. Dodatkowi użytkownicy dołączają do konta root jednym z dwóch sposobów:
- Lista dozwolonych domen — skonfiguruj w UI Sancho: Konto → Konfiguracja → Dozwolone domeny e-mail (wartości zapisywane są w bazie danych, nie w
.env). Użytkownicy logujący się przez Google lub Nextcloud są automatycznie dodawani do konta root z roląMember(bez uprawnień admina). Jeśli lista jest niepusta, domena email musi do niej pasować — próby spoza listy są odrzucane przekierowaniem na/sign-in?error=signup_disabled. Pusta lista oznacza brak ograniczenia domen dla SSO. - Zaproszenia — admin root może zaprosić dowolny adres email z wewnętrznej strony członków; zaproszony akceptuje przez standardowy link i dołącza do konta root z rolą wybraną przez admina. Zaproszenia omijają listę domen.
Użytkownicy Member nie mają polityki admina — mogą używać aplikacji, ale nie mogą wykonywać operacji root-only. Admin może promować ich na inną rolę z poziomu strony członków. Rejestracją email+hasło steruje ENABLE_EMAIL_AUTH (lista domen jej nie dotyczy) — ustaw false, aby ograniczyć logowanie wyłącznie do SSO.
Opcjonalne
Section titled “Opcjonalne”Pozostaw puste, aby wyłączyć.
ROOT_EMAIL— nadpisuje adres email użytkownika root; domyślnieroot_${DEPLOY_ENV}@sancho-wms.pl(dlaDEPLOY_ENV=prod:root_prod@sancho-wms.pl). Ustaw przed pierwszym uruchomieniem — seed tworzy użytkownika root tylko raz.POSTHOG_KEY— klucz analityki produktowej dostarczany do aplikacji webowej.AXIOM_DATASET— nazwa datasetu logów Axiom.AXIOM_TOKEN— token API Axiom.
Logowanie do GHCR
Section titled “Logowanie do GHCR”Obrazy api i app są publikowane w ghcr.io/sancho-wms/* jako pakiety prywatne, więc docker compose pull wymaga poświadczeń — zarówno przy pierwszym wdrożeniu, jak i przy każdej kolejnej aktualizacji. Sancho udostępnia ci token tylko-do-odczytu ograniczony do tych pakietów; nie musisz tworzyć własnego GitHub PAT.
Zaloguj się na hoście jako użytkownik, który będzie uruchamiał docker compose. Timer aktualizacji systemd uruchamia się jako root, więc jeśli planujesz go włączyć, wykonaj poniższe jako root:
echo <TOKEN> | docker login ghcr.io -u sancho-wms --password-stdinUżyj nazwy użytkownika i tokenu otrzymanych od Sancho. Poświadczenia trafiają do ~/.docker/config.json i przeżywają restart — ponowne logowanie potrzebne jest tylko po rotacji tokenu.
Pierwsze wdrożenie
Section titled “Pierwsze wdrożenie”-
Pobierz obrazy:
Terminal window docker compose pull -
Uruchom cały stack:
Terminal window docker compose up -d
API automatycznie aplikuje migracje i seeduje użytkownika root przy każdym starcie. Pierwsza wizyta na https://<DEPLOY_HOSTNAME> wyzwoli Caddy do żądania certyfikatu Let’s Encrypt — zwykle 10–30 sekund.
Pierwsze logowanie
Section titled “Pierwsze logowanie”Seed tworzy użytkownika root pod adresem root_prod@sancho-wms.pl (domyślnie root_${DEPLOY_ENV}@sancho-wms.pl; nadpisywalny przez ROOT_EMAIL w .env ustawiony przed pierwszym uruchomieniem) z hasłem ROOT_PASSWORD, które ustawiłeś. Dla codziennych użytkowników preferuj SSO Google lub Nextcloud.
Funkcje opcjonalne
Section titled “Funkcje opcjonalne”Analityka PostHog
Section titled “Analityka PostHog”Ustaw POSTHOG_KEY w .env na klucz swojego projektu PostHog, a następnie docker compose up -d, aby zrestartować API. Klucz jest dostarczany do aplikacji webowej w runtime (przez /api/trpc/getAppConfig) — bez przebudowy, a usunięcie wartości później wyłączy analitykę przy następnym starcie API. Zewnętrzny Caddy już proxyuje /analytics/* do PostHog EU, więc nie jest wymagana inna konfiguracja.
Wewnętrzny backend S3 (Garage)
Section titled “Wewnętrzny backend S3 (Garage)”Patrz Wewnętrzny backend S3 (Garage) — pełny przewodnik: kiedy używać, generowanie sekretów, aktywacja profilu, weryfikacja, rotacja poświadczeń, odtwarzanie i troubleshooting.
Eksploatacja
Section titled “Eksploatacja”Dane trwałe
Section titled “Dane trwałe”Cały stan trwały żyje pod ./data/ obok pliku compose. Docker auto-tworzy te katalogi przy pierwszym up; entrypointy postgres/rabbitmq/redis chownują je do właściwego uid przy pierwszym starcie, więc nie jest wymagana żadna ręczna konfiguracja. Pliki kończą jako własność uid kontenera (postgres=999, redis=999, rabbitmq=100, caddy=root), więc do tar/rsync po stronie hosta zwykle potrzebny jest sudo.
Folderdata/
Folderpostgres/
- …
Folderrabbitmq/
- …
Folderredis/
- …
Foldercaddy/
- …
Foldercaddy-config/
- …
Kopie zapasowe
Section titled “Kopie zapasowe”Patrz Self-hosted: kopie zapasowe — automatyczny serwis backupu do S3, dumpy ad-hoc i procedury odtwarzania.
Aktualizacje
Section titled “Aktualizacje”Patrz Self-hosted: aktualizacja — wybór kanału wydań, manualny upgrade i timer auto-aktualizacji systemd.
Rozwiązywanie problemów
Section titled “Rozwiązywanie problemów”Uwagi bezpieczeństwa
Section titled “Uwagi bezpieczeństwa”.envmusi miećchmod 600i być własnością użytkownika, który uruchamiadocker compose. Patrz Zabezpiecz uprawnienia pliku.- Zmień każde domyślne hasło w
.env(Postgres, RabbitMQ, użytkownik root). - Wystaw na firewallu tylko porty 80 i 443. Postgres, RabbitMQ, Redis i API są osiągalne wyłącznie wewnątrz sieci compose.
- Interfejs zarządzania RabbitMQ (port 15672) nie jest wystawiony przez plik compose. Jeśli go potrzebujesz, dodaj handler w
Caddyfilezbasic_auth. - Rotowanie
ROOT_PASSWORDw.envzmienia tylko seed dla świeżych instalacji — dla działającego wdrożenia zmień hasło z poziomu UI jako użytkownik root. Wartość w.envmusi pozostać ustawiona, bo API re-waliduje config przy każdym restarcie. - Rotowanie
API_SESSION_SECRETlubAPI_COOKIE_SECRETunieważnia wszystkie istniejące sesje — użytkownicy będą musieli zalogować się ponownie. - Rotowanie
PG_PASSWORD/RABBITMQ_ADMIN_PASSWORDmusi się odbyć najpierw w bazie/brokerze, potem w.env, potemdocker compose up -d. Rotowanie samego.envzostawi API bez możliwości uwierzytelnienia. - Backupuj
./data/caddy; utrata klucza konta ACME może opóźnić ponowne wystawienie certyfikatu.