Instalacja
Rozwiązywanie problemów z instalacją
Typowe problemy z wdrażaniem QuoteNode związane z linkami, CORS, SMTP, DNS, kontrolami stanu Docker oraz utratą kluczy tajnych.
Rozwiązywanie problemów z instalacją
Większość problemów z instalacją wynika z niezgodności adresu URL, braku SMTP, zablokowanych portów lub utraty kluczy tajnych. Zacznij od widocznego adresu URL w przeglądarce i pracuj w głąb systemu.
Publiczne linki otwierają się na moim komputerze, ale nie u klientów
Sprawdź, jaki adres URL został skopiowany lub wysłany.
http://localhost/...działa tylko na tym samym komputerze.http://192.168.x.x/...działa tylko w sieci LAN lub VPN. VPN może być prawidłowym rozwiązaniem dla zespołów wewnętrznych, ale nie stanowi strategii publicznych linków dla klientów.https://crm.example.com/...działa publicznie tylko wtedy, gdy DNS, HTTPS, proxy i firewall są skonfigurowane poprawnie.
W przypadku publicznych linków dla klientów, wdrażaj za pomocą publicznej domeny, używając publicznego serwera Ubuntu lub Coolify.
Przeglądarka wyświetla błędy CORS
CORS_ALLOWED_ORIGINS musi dokładnie zgadzać się z originem przeglądarki. Poniższe adresy to różne originy:
https://crm.example.com
https://www.crm.example.com
http://crm.example.com
https://crm.example.com:8443
Popraw zmienną, ponownie wdroż aplikację i spróbuj ponownie w nowej sesji przeglądarki.
Wiadomości e-mail nie są dostarczane
Najpierw sprawdź:
SMTP_HOSTiSMTP_PORTsą poprawne.SMTP_AUTH=true, gdy Twój dostawca wymaga uwierzytelniania.SMTP_STARTTLS=truedla portu 587.SMTP_USERNAMEiSMTP_PASSWORDsą prawidłowe.- Dostawca zezwala na logowanie SMTP/hasło aplikacji.
NOTIFICATIONS_EMAIL_ENABLED=true, gdy wiadomości e-mail z powiadomieniami powinny być wysyłane.
Ręczne udostępnianie linków publicznych i pobieranie PDF mogą nadal działać bez SMTP.
Linki do preferencji powiadomień są nieprawidłowe
Ustaw:
NOTIFICATIONS_PUBLIC_PREFERENCES_URL=https://<frontend-domain>/notifications/preferences
Nie zostawiaj go wskazującego na localhost w publicznym wdrożeniu.
Kontenery Docker są w stanie unhealthy
Uruchom:
docker compose ps
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f gotenberg
Typowe przyczyny:
- PostgreSQL wciąż się uruchamia.
DB_PASSWORDróżni się między PostgreSQL a backendem.DB_ENCRYPTION_KEYjest brakujący lub ma nieprawidłowy format.- Gotenberg nie jest w stanie healthy, więc generowanie PDF nie może się rozpocząć.
- Porty hosta 80/443 są już używane przez inną usługę.
Po naprawieniu konfiguracji, zrestartuj dotkniętą usługę lub cały stos:
docker compose restart backend
docker compose up -d
Moje IP jest zablokowane — nie mogę się zalogować
Są dwie typowe przyczyny: lista dozwolonych IP zablokowała dostęp lub ochrona przed brute-force zablokowała IP po zbyt wielu nieudanych próbach logowania.
Blokada przez listę dozwolonych IP
QuoteNode posiada listę dozwolonych adresów IP konfigurowalną w Ustawienia > Bezpieczeństwo > Lista dozwolonych IP (lub przez zmienną SECURITY_IP_WHITELIST). Po włączeniu obejmuje ona całą powierzchnię administracyjną — łącznie z ekranami logowania i 2FA — więc wykluczony adres dostaje 403 IP_NOT_ALLOWED i nie może się nawet zalogować. (Health-checki oraz publiczne linki do ofert klientów pozostają dostępne.)
Najpierw poczekaj dwie minuty, jeśli właśnie zmieniłeś listę w aplikacji. Włączenie lub zawężenie w aplikacji jest chronione przed blokadą: musi zostać potwierdzone przez Twoją sesję w krótkim oknie (domyślnie 120 s), a jeśli Twój bieżący adres nie jest objęty, lista automatycznie wraca do poprzedniej. Przypadkowa samoblokada z ekranu Bezpieczeństwa sama się więc naprawia — poczekaj i spróbuj ponownie. (Ta siatka bezpieczeństwa nie dotyczy zmiennej SECURITY_IP_WHITELIST, która jest egzekwowana przy starcie.)
Odzyskiwanie (override operatora): jeśli nadal nie masz dostępu — np. operatorska SECURITY_IP_WHITELIST, nieprawidłowa zapisana lista lub nie możesz czekać — dodaj tę zmienną do środowiska kontenera backend (.env lub sekcja environment: w docker-compose) i zrestartuj:
SECURITY_IP_WHITELIST_FORCE_DISABLE=true
docker compose restart backend
To wyłącza listę dozwolonych IP i pozwala się zalogować. Po zalogowaniu popraw listę w Ustawienia > Bezpieczeństwo, a następnie usuń SECURITY_IP_WHITELIST_FORCE_DISABLE i zrestartuj ponownie.
Nie zostawiaj
SECURITY_IP_WHITELIST_FORCE_DISABLE=truew produkcji. Wyłącza to całą kontrolę dostępu po IP.
Blokada przez ochronę brute-force
QuoteNode blokuje adresy IP po zbyt wielu nieudanych próbach logowania w ciągu godziny:
| Próg | Niepowodzenia w 1 h | Efekt |
|---|---|---|
| Soft | 10 | Postępujące opóźnienie (1 s na każdą dodatkową próbę, maks. 30 s) |
| Tymczasowa blokada | 20 | Zablokowany na 5 minut |
| Trwała blokada | 50 | Zablokowany do ręcznego odblokowania |
Jak odblokować:
- Panel admina — inny administrator otwiera Ustawienia > Panel bezpieczeństwa > Zablokowane IP i klika Odblokuj.
- Czekanie — tymczasowe blokady wygasają po
SECURITY_TEMP_LOCK_MINUTES(domyślnie 5 minut). Trwałe blokady nie wygasają samoistnie. - Tymczasowe podniesienie progów jeśli jesteś zablokowany i żaden inny admin nie może pomóc. Dodaj do
.env, zrestartuj, zaloguj się, a potem usuń:
SECURITY_MAX_FAILED_LOGINS_SOFT=9999
SECURITY_MAX_FAILED_LOGINS_TEMP_LOCK=9999
SECURITY_MAX_FAILED_LOGINS_HARD_LOCK=9999
Przywróć domyślne wartości po odzyskaniu dostępu.
Blokady IP linków publicznych
Rate limiter linków publicznych blokuje adresy IP po przekroczeniu limitów prób. Te blokady są przechowywane w pamięci i wygasają po PUBLIC_RATE_LIMIT_IP_BLOCK_MINUTES (domyślnie 60 minut) lub po restarcie backendu.
Aktualizacja zatrzymuje się na “cannot enforce offer-section ownership”
Po aktualizacji backend nie startuje, a docker compose logs backend pokazuje:
Migration V145 cannot enforce offer-section ownership: 3 offer_items row(s) point at a
section of another offer or tenant.
Co to oznacza
Oferta składa się z sekcji (“Sprzęt”, “Usługi”) i pozycji w nich umieszczonych. Ta wersja zaostrza to powiązanie: pozycja może należeć wyłącznie do sekcji własnej oferty. Kilka starych wierszy w Twojej bazie łamie tę regułę, więc aktualizacja zatrzymuje się i prosi o decyzję, zamiast samodzielnie przepisywać Twoje oferty.
Nic nie jest uszkodzone i sama odmowa niczego nie zmieniła. Ta jedna migracja wycofała się w całości — brak zmian zastosowanych połowicznie i nic do naprawiania w historii migracji. Pamiętaj jednak, że wcześniejsze migracje z tej samej aktualizacji mogły już zostać wykonane: baza stoi tuż przed odrzuconym krokiem, niekoniecznie na wersji, od której zaczynałeś. To znaczy również, że poprzednie kontenery mogą, ale nie muszą z nią działać — zależy to od tego, co zmieniły tamte wcześniejsze migracje. Nie licz na cofnięcie obrazów jako drogę wyjścia.
Wybierz, co ma się stać
| Wariant | Co robi | Co się dla Ciebie zmienia |
|---|---|---|
| Sprawdź problem (domyślny) | Wypisuje dotknięte oferty i pozycje. | Nic. Tylko odczyt. |
| Odepnij błędne przypisania | Czyści sekcję wyłącznie tych pozycji. | Pozycje zostają w swojej ofercie z tą samą ceną, ilością i opisem. Tracą nagłówek sekcji i stają się pozycjami bez grupy. |
| Wybierz sekcję dla każdej pozycji | Pyta o każdą pozycję i proponuje wyłącznie sekcje jej własnej oferty. | Zachowujesz grupowanie, którego chcesz. Pominięte pozycje pozostają nierozwiązane i aktualizacja znowu się zatrzyma. |
Oferty już wysłane do klientów nadal pokazują dokładnie to, co klient zobaczył — treść wysłanej oferty jest zamrażana w momencie wysyłki, więc ani link publiczny, ani PDF się nie zmieniają.
Najpierw znajdź właściwą instalację (Coolify lub kilka stacków na jednym serwerze)
Każde poniższe polecenie musi trafić w Twoją instalację. Jeden serwer może ich mieć kilka, a usługę o nazwie backend może mieć więcej niż jedna, więc najpierw wypisz kandydatów — każda linia to jedna instalacja, z nazwą projektu i plikiem Compose, z którego ją uruchomiono:
docker inspect $(docker ps -aq --filter "label=com.docker.compose.service=backend" \
--filter "label=com.docker.compose.oneoff=False") \
--format '{{index .Config.Labels "com.docker.compose.project"}} | {{index .Config.Labels "com.docker.compose.project.config_files"}}'
Jeśli dostaniesz więcej niż jedną linię, wybierz swoją; nie zgaduj. Następnie każde polecenie uruchamiaj z jawnie podanym projektem i plikiem, zamiast samego docker compose, żeby nie zadziałać na niewłaściwej instalacji:
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections
Podstaw <project> i <compose-file> z Twojej linii i zostaw je w każdym poniższym poleceniu — to właśnie sprawia, że każde z nich działa na Twojej instalacji, a nie na innej. Jeśli linia pokazuje kilka plików rozdzielonych przecinkami, powtórz -f dla każdego, w tej kolejności. Jeśli lista jest pusta, kontenery nie zostały uruchomione przez Docker Compose — użyj katalogu wskazanego przez dostawcę lub instrukcję instalacji albo zapytaj osobę, która konfigurowała serwer. W Coolify uruchom to w terminalu serwera, nie kontenera: terminal kontenera wymaga działającego backendu, a Twój nie działa. (Komunikaty samego narzędzia pokazują skrócone docker compose … bez tych opcji, dla czytelności — kopiując je, dopisz swoje -p i -f.)
Najpierw kopia zapasowa
PostgreSQL działa nawet wtedy, gdy backend nie startuje, więc kopię zapasową wykonasz bez aplikacji. Polecenie odczytuje nazwę bazy i użytkownika z samego kontenera, więc zadziała niezależnie od tego, jak je nazwałeś, a hasło nie pojawia się na ekranie:
docker compose -p <project> -f <compose-file> exec -T postgres sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom -f /tmp/before-repair.dump \
&& pg_restore --list /tmp/before-repair.dump | head -3'
docker compose -p <project> -f <compose-file> cp postgres:/tmp/before-repair.dump ./before-repair.dump
ls -lh before-repair.dump
Linia pg_restore --list wypisuje pierwsze wpisy archiwum: jeśli je widzisz, a ls -lh pokazuje niepusty plik, kopia jest czytelna. Trzymaj before-repair.dump obok swojego docker-compose.yml — leży na dysku serwera, nie w kontenerze, więc przetrwa wszystko poniżej.
Sprawdź problem
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections
To polecenie tylko czyta. Wypisuje dotknięte oferty, co jest nie tak z każdą pozycją i dostępne warianty:
Found 3 offer item(s) in 2 offer(s) assigned to a section that does not belong to that offer.
Offer OF-2026-014 [DRAFT]
item: Mounting frame
section: section "Installation" — section belongs to another offer
Podgląd, potem wykonanie
Bez --apply=true każde polecenie jest podglądem: pokazuje dokładnie, co by zrobiło, i kończy się, niczego nie zmieniając — w każdym terminalu.
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink
Dodaj --apply=true --acknowledge-backup=true, aby je wykonać. To jedyna postać, która zapisuje:
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink --apply=true --acknowledge-backup=true
Aby zdecydować o każdej pozycji samodzielnie, użyj --plan=choose. Narzędzie zadaje jedno pytanie na pozycję i wypisuje sekcje tej oferty, więc nigdy nie wpisujesz identyfikatora z bazy. Bez --apply=true nadal tylko pokazuje plan:
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=choose --apply=true --acknowledge-backup=true
Dokończ aktualizację
Gdy narzędzie zgłosi, że żadna pozycja nie wskazuje już błędnej sekcji:
docker compose -p <project> -f <compose-file> up -d
Zatrzymana migracja niczego po sobie nie zostawiła, więc przy kolejnym starcie po prostu się wykona. Nie potrzebujesz polecenia flyway repair ani baseline — i nie powinieneś go używać, bo ukryłoby problem z danymi zamiast go rozwiązać.
Zweryfikuj jak zwykle: otwórz /health, a następnie sprawdź numer wersji w Admin > Licencja.
Jeśli komunikat mówi, że dane zmieniły się od podglądu
Narzędzie wykonuje plan, który zatwierdziłeś, a nie to, co zastanie w chwili zapisu. Jeśli podczas potwierdzania pojawiło się nowe błędne przypisanie, jakieś się przesunęło albo wybrana przez Ciebie sekcja została usunięta, narzędzie odmawia wykonania całości i niczego nie zmienia. Uruchom sprawdzenie ponownie, żeby zobaczyć aktualny stan, i wybierz jeszcze raz.
Jeśli komunikat mówi, że naprawa została zapisana albo że nie dało się tego potwierdzić
Naprawa wykonuje się w jednej transakcji, więc albo zapisuje się w całości, albo wcale. Dwa zakończenia wymagają uwagi. Jeśli zawiedzie późniejszy krok — zapis raportu albo kontrolny odczyt potwierdzający wynik — narzędzie pozostawia w komunikacie słowo SAVED: naprawa się udała, więc nie przywracaj kopii zapasowej. Jeśli natomiast pojawi się UNCONFIRMED, baza nie odpowiedziała i nikt nie jest w stanie powiedzieć, jak się skończyło; nie przywracaj kopii i nie powtarzaj naprawy. W obu przypadkach uruchom ponownie sprawdzenie w trybie tylko do odczytu: jeśli zgłosi, że nie ma nic do naprawy, gotowe — możesz uruchomić stack; jeśli nadal wypisuje te same pozycje, nic nie zostało zapisane i możesz powtórzyć swój wybór.
docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections
Jedno wymaga uważnego przeczytania: jeśli użyłeś wariantu Wybierz sekcję dla każdej pozycji i część pominąłeś, te pominięte pozycje nadal będą wypisywane przez sprawdzenie. Tak ma być i nie znaczy to, że naprawa się nie udała. Porównaj listę z pozycjami, które faktycznie wybrałeś do naprawy: jeśli zostały wyłącznie te pominięte, naprawa zadziałała, a to, co zostało, to odłożona przez Ciebie decyzja — uruchom narzędzie dla nich, gdy będziesz gotów. Jeśli nadal są wypisywane pozycje, które wybrałeś do naprawy, nic nie zostało zapisane.
Co zostało zmienione i co zrobić, jeśli nadal się nie udaje
Każde uruchomienie, które coś zmienia, zapisuje wykaz identyfikatorów pozycji, ofert i sekcji do /app/data/backups/ w wolumenie backendu. Nie zawiera danych klientów i można go dołączyć do zgłoszenia serwisowego. Jeśli pliku nie da się zapisać, narzędzie wypisuje ten sam wykaz na ekranie — skopiuj go przed zamknięciem terminala:
docker compose -p <project> -f <compose-file> run --rm --entrypoint sh backend \
-c 'ls -1 /app/data/backups/offer-section-repair-*.json'
Jeśli aktualizacja nadal się zatrzymuje:
- uruchom sprawdzenie ponownie — pominięte pozycje nadal są wypisywane;
- jeśli komunikat wskazuje inną migrację, to narzędzie jej nie dotyczy. Naprawia wyłącznie przypisania pozycji do sekcji oferty;
- jeśli komunikat się nie zmienił, a sprawdzenie nie zgłasza nic do naprawy, zgłoś problem, dołączając log backendu i powyższy wykaz.
Certyfikat HTTPS nie zostaje wystawiony
W przypadku publicznych wdrożeń:
- DNS musi wskazywać na serwer wdrożeniowy.
- Porty 80 i 443 muszą być osiągalne z internetu.
- Tylko jeden proxy powinien zakończyć HTTPS dla tej samej domeny.
- W Coolify, przypisz domenę do usługi
frontend.
Zgubiłem plik .env
Jeśli zgubiłeś plik .env, zatrzymaj się i oceń sytuację przed rotacją wartości.
DB_PASSWORDzazwyczaj można zresetować z dostępem administratora PostgreSQL.TIMING_TOKEN_SECRETiPUBLIC_LINK_PASSWORD_SESSION_SECRETmogą zostać zregenerowane, ale istniejące sesje/tokeny mogą wygasnąć.DB_ENCRYPTION_KEYjest krytyczny. Bez oryginalnego klucza zaszyfrowane wartości nie mogą zostać odszyfrowane.- Jeśli szyfrowanie PII jest włączone, zaszyfrowane pola klienta/kontaktu zależą od
DB_ENCRYPTION_KEY.
Przywróć plik .env z kopii zapasowej, gdy tylko to możliwe.
Odzyskiwanie po utracie .env przy posiadaniu kopii zapasowej bazy danych
- Utwórz nowy
.envz nowymi sekretami. - Przywróć bazę danych z kopii zapasowej (
pg_dump/pg_restore). - Wszystkie dane biznesowe (klienci, oferty, produkty, użytkownicy) będą w pełni dostępne.
- Utracone zostaną tylko nieliczne pola: kody MFA (użytkownicy ponownie się rejestrują), hasło SMTP (wpisz ponownie w ustawieniach), klucz FX API (wpisz ponownie). Jeśli
ENCRYPT_PII=truebyło włączone, zaszyfrowane dane klientów również przepadają.
Jeśli kopie zapasowe są szyfrowane age (ustawienie domyślne), potrzebujesz pliku tożsamości age z zestawu odzyskiwania, aby je odszyfrować. Zapoznaj się z przewodnikiem Kopie zapasowe i odzyskiwanie.
Zapomniałem hasła administratora
Użyj niedestrukcyjnej ścieżki odzyskiwania:
- Jeśli SMTP jest skonfigurowany, skorzystaj ze strony logowania: Zapomniałeś hasła?
- Jeśli inny administrator może się zalogować, otwórz Użytkownicy w panelu administratora i zresetuj hasło.
- Jeśli żaden administrator nie może się zalogować, a procedura resetowania przez e-mail jest niedostępna, przywróć dostęp z dobrze działającej kopii zapasowej lub zastosuj wewnętrzną procedurę konserwacyjną.
docker compose down -vnie jest procedurą resetowania hasła. Usuwa wszystkie dane — użytkowników, oferty, klientów, ustawienia i kopie zapasowe. Używaj tylko dla jednorazowych środowisk testowych.
Weryfikacja licencji nie powiodła się
W przypadku rzeczywistych wdrożeń, zachowaj:
LICENSE_SKIP_SIGNATURE_CHECK=false
Jeśli prawidłowa licencja nadal nie działa, sprawdź czy wdrożenie ma oczekiwaną wersję aplikacji i zsynchronizowany z NTP zegar systemowy. Potwierdź, że licencja odpowiada wdrożonej edycji/wersji, a następnie skontaktuj się z pomocą techniczną lub zastąp plik licencji poprzez udokumentowaną ścieżkę operatora. Nie ustawiaj pomijania podpisu na true jako obejścia produkcyjnego.