Files
kzbkowskiandClaude Opus 5.5 71b3a11a18 v2.1.1
Zmiany:
- Usunięty wiersz „Kontakt wsparcia” z Admin > O aplikacji razem ze zmienną
  AUTHOR_CONTACT w .env, która służyła tylko jemu.

Poprawki:
- Pole wyboru pliku ustawień (instalator oraz Admin > Eksport / import)
  wyświetlało się jako sam tekst, bez widocznego pola i przycisku.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rz61PznDTKUACA7sVTB8H3
2026-10-01 13:34:19 +02:00

24 KiB
Raw Permalink Blame History

Instalacja / wdrożenie

Dwa sposoby uruchomienia Servicedesk: przez Docker Compose (tak jak działa ten projekt obecnie) albo bezpośrednio na serwerze z Apache/Nginx + PHP-FPM, bez kontenerów.

Zanim zaczniesz, ważne rozróżnienie — w projekcie są dwa różne pliki .env:

Plik Do czego służy
docker/.env (obok docker/compose.yaml) Zmienne dla Docker Compose — hasła do MariaDB, nazwa hosta dla Traefika. Czyta go compose.yaml, nie czyta go Laravel.
.env (w katalogu głównym repo — tam jest aplikacja Laravel) Cała konfiguracja aplikacji — połączenie z bazą, adres URL, klucz szyfrowania, LDAP, SMTP, itd. Czyta go wyłącznie Laravel.

Oba istnieją niezależnie od siebie (nawet w wersji Docker) — wartości DB_* w głównym .env muszą odpowiadać wartościom MYSQL_* z docker/.env, ale to dwa osobne pliki, w dwóch różnych miejscach.


1. Wdrożenie przez Docker Compose

Wymagania

  • Docker + wtyczka docker compose.
  • Zewnętrzna sieć Docker traefik_public, jeśli używasz Traefika tak jak w docker/compose.yaml (docker network create traefik_public, jeśli jeszcze nie istnieje). Bez Traefika trzeba samodzielnie zmapować porty serwisu app na hosta (ports: ["8080:80"]) i obsłużyć TLS inaczej (patrz sekcja 2 niżej, w razie potrzeby reverse-proxy przed kontenerem).

1.1. Pliki compose.yaml i .env w katalogu docker/

Wszystko, co dotyczy samego wdrożenia kontenerowego, leży w docker/: Dockerfile, compose.yaml, jego .env oraz dane bazy (docker/mysql/). Aplikacja Laravel jest w katalogu głównym repo.

compose.yaml i .env są celowo .gitignore'owane (podobnie jak główny .env) — kopiujesz je z szablonów przy pierwszym wdrożeniu, a potem edytujesz lokalnie:

cd docker
cp compose.yaml.example compose.yaml
cp .env.example .env

Wszystkie polecenia docker compose w tej instrukcji uruchamiaj z katalogu docker/ (albo z katalogu głównego, dodając -f docker/compose.yaml). Nazwa projektu jest przypięta w pliku (name: servicedesk), więc kontenery nazywają się servicedesk-app-1 itd. niezależnie od nazwy katalogu.

docker/.env:

HOSTNAME=servicedesk.twoja-domena.pl
MYSQL_ROOT_PASSWORD=wygeneruj-silne-haslo
MYSQL_DATABASE=servicedesk
MYSQL_USER=servicedesk
MYSQL_PASSWORD=wygeneruj-inne-silne-haslo
# IMAGE_TAG=latest
  • HOSTNAME — domena, pod którą Traefik wystawi aplikację (trafia do reguły Host(...) w etykietach servicedesk).
  • MYSQL_* — dane bazy dla kontenera mariadb; MYSQL_USER/MYSQL_PASSWORD to te same wartości, które za chwilę wpiszesz do głównego .env jako DB_USERNAME/ DB_PASSWORD.
  • IMAGE_TAG — który tag obrazu servicedesk wdrożyć (patrz sekcja 1.3a); domyślnie latest, jeśli zmienna nie jest ustawiona.

1.2. Plik .env w katalogu głównym (Laravel)

cp .env.example .env        # w katalogu głównym repo, nie w docker/

Kluczowe wartości do zmiany względem .env.example:

APP_NAME=Servicedesk
APP_ENV=production
APP_DEBUG=false
APP_URL=https://servicedesk.twoja-domena.pl   # to samo co HOSTNAME wyżej, z https://
APP_LOCALE=pl
APP_FALLBACK_LOCALE=pl

VERSION=1.3.0                                   # widoczne w Admin > O aplikacji

DB_CONNECTION=mysql
DB_HOST=mariadb                                 # nazwa serwisu z compose.yaml, NIE 127.0.0.1
DB_PORT=3306
DB_DATABASE=servicedesk                         # = MYSQL_DATABASE z docker/.env
DB_USERNAME=servicedesk                         # = MYSQL_USER
DB_PASSWORD=wygeneruj-inne-silne-haslo           # = MYSQL_PASSWORD

SESSION_DRIVER=database
QUEUE_CONNECTION=database

APP_KEY wygenerujesz komendą artisan (krok 1.4) — zostaw puste w pliku.

LDAP_* i MAIL_* w .env są tylko wartościami startowymi/awaryjnymi. Docelowo LDAP konfiguruje się wygodniej z poziomu Admin > Integracje, a SMTP z Admin > Poczta (patrz ramka ostrzegawcza w kroku 1.6) — ale jeśli chcesz mieć sensowny fallback zanim ktokolwiek się zaloguje do panelu admina, warto je od razu uzupełnić:

LDAP_HOST=ldap.twoja-domena.pl
LDAP_USERNAME=cn=admin,dc=twoja-domena,dc=pl
LDAP_PASSWORD=haslo-bind-ldap
LDAP_BASE_DN=dc=twoja-domena,dc=pl
LDAP_PORT=389

MAIL_MAILER=smtp
MAIL_HOST=smtp.twoja-domena.pl
MAIL_PORT=587
MAIL_USERNAME=noreply@twoja-domena.pl
MAIL_PASSWORD=haslo-smtp
MAIL_FROM_ADDRESS=noreply@twoja-domena.pl
MAIL_FROM_NAME="${APP_NAME}"

1.3. Start kontenerów

Obraz servicedesk nie jest budowany lokalnie — compose.yaml odwołuje się do obrazu zbudowanego przez CI i wypchniętego do rejestru kontenerów Gitea (patrz 1.3a). Uruchomienie stacka to więc zawsze:

docker compose pull
docker compose up -d

Przy zwykłych zmianach w kodzie PHP/Blade nic więcej nie trzeba robić — źródło jest zamontowane z katalogu głównego repo, Laravel odświeża się natychmiast. docker compose pull uruchamiaj ponownie tylko wtedy, gdy chcesz podnieść nowszy tag obrazu (np. po zmianie w docker/Dockerfile i przebudowie przez CI).

1.3a. Automatyczne budowanie obrazu (CI, Gitea Actions)

.gitea/workflows/build.yml buduje i wypycha obraz do wbudowanego rejestru kontenerów Gitea (gitea.kzbikowski.pl/kzbkowski/servicedesk) po każdym pushu na main, który zmienia docker/Dockerfile (celowo nie odpala się na zwykłe zmiany w kodzie aplikacji — obraz nie zawiera kodu aplikacji, tylko PHP/Apache/rozszerzenia, więc przebudowa dla samego kodu byłaby marnowaniem czasu CI). Wypycha dwa tagi: latest i <sha commita>.

Zanim to zadziała, workflow potrzebuje sekretu REGISTRY_TOKEN — Gitei nie ufaj domyślnemu secrets.GITHUB_TOKEN do logowania w jej własnym rejestrze kontenerów, w praktyce kończy się to błędem unauthorized przy docker login. Zamiast tego:

  1. Wygeneruj token: Ustawienia użytkownika > Aplikacje > Generate New Token, z uprawnieniami co najmniej write:package i read:package.
  2. Dodaj go jako sekret repo: Ustawienia repo > Actions > Secrets → nazwa REGISTRY_TOKEN, wartość = wygenerowany token.

Jeśli ten sekret nie jest ustawiony, workflow celowo przerywa się przed próbą logowania z czytelnym komunikatem błędu (::error::), zamiast wysyłać puste/nieautoryzowane dane do rejestru i kończyć na niejasnym unauthorized z demona Dockera.

To tylko build + push — świadomie bez auto-deployu na produkcję. Po tym jak CI skończy, wdrożenie nowego obrazu na serwerze wciąż jest ręcznym krokiem:

cd /ścieżka/do/repo
docker compose pull
docker compose up -d

Zanim to zadziała po raz pierwszy, potrzebne jest jednorazowe zalogowanie hosta produkcyjnego do rejestru Gitea (żeby docker compose pull miał czym autoryzować pobranie obrazu, jeśli repozytorium/paczka nie są publiczne):

docker login gitea.kzbikowski.pl -u <twoja-nazwa-uzytkownika>

Jeśli to zupełnie pierwsze wdrożenie (rejestr jeszcze nie ma żadnego wypchniętego obrazu servicedesk) — poczekaj, aż workflow CI przejdzie choć raz (np. przez push/PR zmieniający docker/Dockerfile, albo ręczne odpalenie z zakładki Actions w Gitea), zanim spróbujesz docker compose pull na serwerze.

1.3b. Real-time (Laravel Reverb)

Realtime (kolejka operatora, czat na żywo) wymaga trzeciej usługi w compose.yaml, reverb — tego samego obrazu servicedesk, tylko z innym command: php artisan reverb:start --host=0.0.0.0 --port=8080. Obraz musi mieć rozszerzenia PHP pcntl/posix (potrzebne serwerowi Reverb do obsługi sygnałów) — jeśli Dockerfile ich nie instaluje, reverb będzie się zapętlać w restartach z błędem Undefined constant "...SIGINT"; dodaj pcntl posix do listy w docker-php-ext-install i poczekaj na przebudowanie obrazu przez CI.

Traefik musi kierować ścieżkę websocketu (/app*) do reverb, a resztę do app — na tej samej domenie, więc bez dodatkowego wpisu DNS/certyfikatu:

reverb:
  image: gitea.kzbikowski.pl/kzbkowski/servicedesk:${IMAGE_TAG:-latest}
  command: php artisan reverb:start --host=0.0.0.0 --port=8080
  volumes:
    - ..:/var/www/html
  restart: unless-stopped
  depends_on:
    mariadb:
      condition: service_healthy
  networks:
    - internal
    - traefik_public
  labels:
    - traefik.enable=true
    - traefik.docker.network=traefik_public
    - traefik.http.routers.reverb.rule=Host(`${HOSTNAME}`) && PathPrefix(`/app`)
    - traefik.http.routers.reverb.priority=1000
    - traefik.http.routers.reverb.entrypoints=websecure
    - traefik.http.routers.reverb.tls=true
    - traefik.http.services.reverb.loadbalancer.server.port=8080

priority=1000 jest ważne — Traefik domyślnie liczy priorytet reguły na podstawie długości jej zapisu, co może dać samemu Host(...) z servicedesk wyższy priorytet niż oczekiwano, przez co ścieżkowa reguła reverb przegrywa i nic nie działa mimo poprawnej konfiguracji.

W głównym .env — BROADCAST_CONNECTION=reverb plus:

REVERB_APP_ID=wygeneruj-losowy-id
REVERB_APP_KEY=wygeneruj-losowy-klucz
REVERB_APP_SECRET=wygeneruj-losowy-sekret
REVERB_HOST=reverb          # nazwa usługi compose — ruch serwer-serwer po sieci internal
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST=servicedesk.twoja-domena.pl   # publiczna domena — to, z czym łączy się przeglądarka
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https

REVERB_HOST (serwer→serwer, wewnętrzna sieć Docker) i VITE_REVERB_HOST (przeglądarka→Traefik, publiczna domena) to celowo dwie różne wartości — pomylenie ich to najczęstszy błąd przy pierwszym wdrożeniu tej funkcji.

Po zmianie zmiennych VITE_REVERB_* trzeba przebudować front-end (krok 1.5) — te wartości są wypiekane w zbudowany bundle JS, nie czytane w runtime.

1.4. Instalacja aplikacji wewnątrz kontenera

docker compose exec app composer install --no-dev --optimize-autoloader
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate --seed
docker compose exec app php artisan storage:link

migrate --seed (bez --fresh) na pustej bazie utworzy wszystkie tabele i wypełni dane referencyjne: kategorie/podkategorie, pola dodatkowe, statusy, priorytety/SLA, zespoły, szybkie akcje, szablony odpowiedzi/e-mail i branding. Nie tworzy żadnego konta — pierwsze powstaje w instalatorze (niżej).

Instalator przy pierwszym uruchomieniu

Dopóki w bazie nie ma żadnego konta, każda strona aplikacji przekierowuje na /setup. Instalator daje trzy drogi:

  • LDAP / Active Directory — podajesz dane serwera (jest przycisk „Testuj połączenie”), a potem logujesz się swoim kontem z katalogu. To konto dostaje role administratora, operatora i klienta; pozostali użytkownicy katalogu mogą logować się od razu (jako klienci).
  • Konto lokalne — zakładasz administratora z własnym hasłem, bez LDAP. LDAP można włączyć później w Admin > Integracje; konto lokalne zostaje jako dostęp awaryjny.
  • Import ustawień — wgrywasz plik z innej instalacji (patrz „Przenoszenie konfiguracji” niżej); logujesz się potem kontami z tamtej instalacji.

Instalator jest dostępny bez logowania — bo nie ma jeszcze kim się zalogować — więc przejdź go od razu po uruchomieniu aplikacji, zanim adres będzie osiągalny dla kogokolwiek innego. Zamyka się na stałe, gdy tylko powstanie pierwsze konto (także po późniejszym usunięciu wszystkich kont). Na serwerze bez przeglądarki to samo da php artisan settings:import <plik>.

1.5. Zbudowanie zasobów front-endowych (CSS/Tailwind)

Ani host, ani kontener app nie mają zainstalowanego Node.js — buduj przez jednorazowy kontener node:22 zamiast dorzucać Node do obrazu aplikacji:

cd /ścieżka/do/repo
docker run --rm -v "$(pwd)":/app -w /app node:22 npm ci
docker run --rm -v "$(pwd)":/app -w /app node:22 npm run build

Powtarzaj drugi krok po każdej zmianie w resources/css/ lub resources/js/.

1.6. Zadanie cykliczne (SLA, automatyzacje, poczta IMAP, AI) i kolejka

routes/console.php planuje tickets:check-sla-breaches i automation:run-rules co 15 minut, oraz emails:fetch-imap (odbieranie zgłoszeń/odpowiedzi e-mailem — patrz Admin > Poczta) i ai:run-ticket-automation (opcjonalna automatyczna kategoryzacja/podsumowania AI zgłoszeń — patrz Admin > Integracje) co 5 minut, ale obraz Dockera nie ma wbudowanego cron/supervisora — bez dodatkowego kroku żadne z tych zadań nigdy się nie uruchomi (poczta IMAP nadal da się sprawdzić ręcznie przyciskiem „Pobierz teraz”, ale bez tego nic nie dzieje się samo). Wszystkie cztery interwały są też konfigurowalne z poziomu Admin > Konfiguracja (bez potrzeby edycji kodu czy restartu — nowa wartość obowiązuje od najbliższego tyknięcia harmonogramu).

compose.yaml rozwiązuje to czwartą usługą, cron — tego samego obrazu servicedesk, tylko z innym poleceniem:

cron:
  image: gitea.kzbikowski.pl/kzbkowski/servicedesk:${IMAGE_TAG:-latest}
  command: php artisan schedule:work
  volumes:
    - ..:/var/www/html
  restart: unless-stopped
  depends_on:
    mariadb:
      condition: service_healthy
  networks:
    - internal

schedule:work to własna, pierwszoplanowa pętla harmonogramu Laravela — odpowiednik odpalania schedule:run co minutę, ale bez potrzeby zewnętrznego triggera. Ten kontener nie musi być widoczny w Traefiku (nie obsługuje ruchu HTTP), stąd tylko sieć internal. Sprawdź, że działa: docker compose ps cron oraz docker compose logs -f cron (loguje każde odpalenie zaplanowanego zadania).

Powiadomienia e-mail wysyłają się synchronicznie (nie trafiają do kolejki), więc php artisan queue:work nie jest obowiązkowy — QUEUE_CONNECTION=database w .env wystarcza jako bezpieczny domyślny driver, gdyby coś w przyszłości zaczęło kolejkować zadania.

⚠️ Ważne: LDAP/SMTP z panelu Admina nadpisują .env w locie

AppServiceProvider na starcie żądania sprawdza tabelę settings — jeśli w Admin > Integracje pole host LDAP albo w Admin > Poczta SMTP włączony + host jest ustawione, te wartości wygrywają z .env, bez potrzeby restartu czy redeployu.

LDAP po świeżym migrate --seed jest pusty i wyłączony — ustawia go instalator. SMTP natomiast zawiera przykładowe placeholdery (smtp.example.com, changeme-*-password) — to znaczy, że zaraz po instalacji poczta faktycznie próbuje łączyć się z tym fałszywym adresem, nawet jeśli w .env wpisałeś prawdziwe dane! Zanim oddasz system do użytku:

  1. Zaloguj się kontem utworzonym w instalatorze.
  2. W Admin > Poczta wpisz prawdziwe dane SMTP. LDAP, jeśli nie był ustawiony w instalatorze, konfigurujesz w Admin > Integracje (wybierz właściwy Typ katalogu — LLDAP/OpenLDAP albo Active Directory; puste pole hosta LDAP oznacza powrót do wartości z .env).
  3. Użyj przycisku „Testuj połączenie” w Integracje i „Wyślij testową wiadomość” w Poczta, zanim zaczniesz polegać na logowaniu przez katalog albo na powiadomieniach e-mail.

Integracje opcjonalne (BookStack, AI)

Podpowiedzi artykułów z bazy wiedzy BookStack (przy tworzeniu zgłoszenia i w panelu operatora) są domyślnie wyłączone i nie wymagają żadnej zmiennej w .env — całość konfiguruje się w Admin > Integracje: adres instancji, Token ID/Secret (rola/użytkownik właściciela tokenu musi mieć w BookStacku uprawnienie „Access System API”), filtr typu treści (książki/strony/rozdziały, niezależne checkboxy), tryb wyszukiwania (po nazwie / po tagach / oba), oraz osobne listy dozwolonych półek dla podpowiedzi przy tworzeniu zgłoszenia i dla panelu operatora — dopóki żadna półka nie jest zaznaczona, wyszukiwanie nic nie zwraca.

Integracja AI (Admin > Integracje > „Integracja AI”) jest domyślnie wyłączona i tak samo nie wymaga żadnej zmiennej w .env — adres API (dowolny dostawca kompatybilny z OpenAI: Groq, OpenAI, lokalny Ollama), opcjonalny klucz API, nazwa modelu i przełącznik weryfikacji SSL. Sama w sobie nic nie robi — dopiero po jej włączeniu można włączyć automatyczne tagowanie treści BookStack (przyciski przy integracji BookStack) oraz automatyczną kategoryzację/podsumowania AI zgłoszeń (Admin > Integracje > „Automatyzacja AI dla zgłoszeń”, wymaga też wpisu crona z kroku 1.6/2.6 powyżej — to ten sam harmonogram co SLA/automatyzacje/IMAP).

Przenoszenie konfiguracji między instalacjami (eksport / import ustawień)

Całą konfigurację — ustawienia i integracje, wygląd (z logo i favicon), użytkowników z rolami, zespoły, kategorie/podkategorie, pola dodatkowe, statusy, priorytety/SLA, szablony, powiadomienia, wyzwalacze, automatyzacje, skrzynki IMAP i klucze API — można przenieść jednym plikiem JSON. Zgłoszenia nie są częścią eksportu.

Na starej instalacji: Admin > Eksport / import > „Pobierz plik ustawień”, albo:

docker compose exec app php artisan settings:export            # -> storage/app/servicedesk-ustawienia-<data>.json

Na nowej, po kroku 1.4 (migrate --seed): wybierz „Import ustawień” w instalatorze (/setup) i wgraj plik, albo:

docker compose exec app php artisan settings:import storage/app/servicedesk-ustawienia-<data>.json
  • Import zastępuje konfigurację nowej instalacji — po imporcie logujesz się kontami ze starej.
  • Import działa tylko w instalacji bez zgłoszeń; zgłoszenia dodaje się dopiero potem (np. importem z Heska poniżej, który wymaga, żeby konta użytkowników już istniały).
  • Plik zawiera hasła i tokeny integracji (LDAP, SMTP, IMAP, AI, BookStack, Snipe-IT) jawnym tekstem — w bazie są szyfrowane kluczem APP_KEY danej instalacji, więc inaczej nie dałoby się ich odczytać na nowym serwerze. Przechowuj plik jak hasło i usuń po przeniesieniu.
  • Poza zakresem: .env i docker/compose.yaml (w tym APP_URL, dane bazy, Reverb) — te ustawia się na nowym serwerze osobno, jak w krokach 1.1–1.2.

Import historycznych zgłoszeń z Heska (opcjonalnie)

Jeśli migrujesz z helpdesku Hesk 3.x, scripts/hesk-import/ zawiera jednorazowe (nie ciągłe) narzędzie migracyjne — importuje zgłoszenia wraz z pełną historią odpowiedzi/notatek, ograniczone do jednej domeny e-mail, w trybie dry-run domyślnie. Nie dotyka bazy Heska poza odczytem. Zobacz scripts/hesk-import/README.md po pełną instrukcję.


2. Wdrożenie bezpośrednio na serwerze (Apache/Nginx, bez Dockera)

Wymagania

  • PHP 8.3+ z rozszerzeniami: curl, mysqli, pdo_mysql, ldap, zip, mbstring, openssl, tokenizer, xml, ctype, bcmath, fileinfo.
  • Composer 2.
  • MariaDB/MySQL (osobny serwer lub lokalny).
  • Node.js 20+ — tylko do jednorazowej (lub przy update'ach) budowy zasobów Tailwind/Vite; niepotrzebny w runtime.
  • Apache z mod_rewrite (+ mod_ssl) lub Nginx + PHP-FPM.

2.1. Pobranie kodu i zależności

cd /var/www
git clone <adres-repo> servicedesk
cd servicedesk
composer install --no-dev --optimize-autoloader

2.2. Konfiguracja .env

cp .env.example .env

Te same zmienne co w sekcji 1.2, z jedną różnicą — DB_HOST wskazuje na prawdziwy adres serwera bazy, nie na nazwę serwisu Compose:

APP_NAME=Servicedesk
APP_ENV=production
APP_DEBUG=false
APP_URL=https://servicedesk.twoja-domena.pl
APP_LOCALE=pl
APP_FALLBACK_LOCALE=pl

VERSION=1.3.0

DB_CONNECTION=mysql
DB_HOST=127.0.0.1        # albo adres IP/hostname prawdziwego serwera DB
DB_PORT=3306
DB_DATABASE=servicedesk
DB_USERNAME=servicedesk
DB_PASSWORD=wygeneruj-silne-haslo

SESSION_DRIVER=database
QUEUE_CONNECTION=database

Uzupełnij też LDAP_*/MAIL_* jak w sekcji 1.2 (to samo ostrzeżenie o Admin > Integracje/Poczta nadpisującym te wartości w locie dotyczy tu identycznie).

php artisan key:generate
php artisan migrate --seed
php artisan storage:link

2.3. Budowa zasobów

npm ci
npm run build

(Na maszynie bez Node.js zbuduj public/build/ gdzie indziej i skopiuj cały katalog — to jedyne, co Vite generuje poza źródłem.)

2.4. Uprawnienia plików

chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache

(Zmień www-data na użytkownika, pod którym faktycznie działa Apache/PHP-FPM w Twojej dystrybucji, np. apache na RHEL/CentOS.)

2.5. Konfiguracja serwera WWW

DocumentRoot musi wskazywać na public/, nigdy na katalog główny aplikacji.

Apache (/etc/apache2/sites-available/servicedesk.conf):

<VirtualHost *:443>
    ServerName servicedesk.twoja-domena.pl
    DocumentRoot /var/www/servicedesk/public

    <Directory /var/www/servicedesk/public>
        AllowOverride All
        Require all granted
    </Directory>

    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/servicedesk.crt
    SSLCertificateKeyFile   /etc/ssl/private/servicedesk.key

    ErrorLog  ${APACHE_LOG_DIR}/servicedesk-error.log
    CustomLog ${APACHE_LOG_DIR}/servicedesk-access.log combined
</VirtualHost>
a2enmod rewrite ssl
a2ensite servicedesk
systemctl reload apache2

Jeśli kiedykolwiek dodasz własny katalog public/icons/ — nie rób tego. Stockowy moduł mod_alias Apache definiuje Alias /icons/ "/usr/share/apache2/icons/" (FancyIndexing), który po cichu przechwytuje /icons/* przed dotarciem do Laravela. Ten projekt celowo używa public/pwa-icons/ z tego właśnie powodu.

Nginx (+ PHP-FPM):

server {
    listen 443 ssl http2;
    server_name servicedesk.twoja-domena.pl;
    root /var/www/servicedesk/public;

    ssl_certificate     /etc/ssl/certs/servicedesk.crt;
    ssl_certificate_key /etc/ssl/private/servicedesk.key;

    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

2.6. Zadanie cykliczne (SLA, automatyzacje, poczta IMAP, AI) i kolejka

Crontab użytkownika, pod którym stoi aplikacja (np. www-data) — obsługuje też automation:run-rules, emails:fetch-imap i ai:run-ticket-automation (patrz 1.6 wyżej, w tym konfigurowalne interwały w Admin > Konfiguracja):

* * * * * cd /var/www/servicedesk && php artisan schedule:run >> /dev/null 2>&1

Jak w wersji Docker — kolejka (php artisan queue:work) nie jest obowiązkowa, skoro powiadomienia wysyłają się synchronicznie; zostaw QUEUE_CONNECTION=database jako bezpieczny domyślny driver na przyszłość.

Realtime (patrz 1.3b) potrzebuje tu długo działającego procesu php artisan reverb:start — PHP musi mieć rozszerzenia pcntl/posix (standardowo dostępne, ale sprawdź php -m). Najprościej pod systemd:

# /etc/systemd/system/servicedesk-reverb.service
[Unit]
Description=Servicedesk Reverb websocket server
After=network.target

[Service]
User=www-data
WorkingDirectory=/var/www/servicedesk
ExecStart=/usr/bin/php artisan reverb:start --host=0.0.0.0 --port=8080
Restart=always

[Install]
WantedBy=multi-user.target
systemctl enable --now servicedesk-reverb

Ustaw też te same zmienne .env co w 1.3b (BROADCAST_CONNECTION=reverb, REVERB_*, VITE_REVERB_* — tu REVERB_HOST to po prostu 127.0.0.1, nie nazwa usługi compose) i skieruj serwer WWW tak, by ścieżka /app* trafiała do portu 8080 zamiast do PHP-FPM/Apache (osobny location/VirtualHost dla tej jednej ścieżki, analogicznie do reguły Traefika w 1.3b).

2.7. Pierwsze logowanie i dalsza konfiguracja

Identycznie jak w wersji Docker (krok 1.4) — otwórz aplikację, przejdź instalator (/setup: LDAP, konto lokalne albo import ustawień), a potem uzupełnij prawdziwe SMTP w Admin > Poczta (placeholder z seeda inaczej realnie próbuje łączyć się z fałszywym adresem) i przetestuj połączenia.

2.8. Aktualizacje (bez przestoju)

cd /var/www/servicedesk
git pull
composer install --no-dev --optimize-autoloader
php artisan migrate --force
npm ci && npm run build
php artisan config:clear   # tylko jeśli wcześniej użyto config:cache

Nie ma potrzeby restartu Apache/PHP-FPM dla samych zmian w PHP/Blade — restart przyda się tylko po zmianie w .env lub konfiguracji samego serwera WWW.