Files
servicedesk/install.md
Kacper ab90abcaa3 v1.1.3
- Triggers (Admin > Wyzwalacze): event-driven rules that fire immediately on
  a ticket lifecycle event (created/updated/status/priority/assignee/team/
  category changed, new reply), with AND-conditions and ordered actions
  (set status/priority/team/assignee, send e-mail). Ships its own dedicated,
  freely add/edit/delete-able e-mail templates, kept separate from the fixed
  system templates.
- Ticket watching: operators can star/"Obserwuj" any ticket to follow it
  regardless of assignment/team.
- Real-time notification bell (private per-user broadcast channel, 30s
  fallback poll) with an opt-in in-tab browser push notification.
- Per-user notification preferences (/settings/notifications): scope
  (mine/unassigned/watched/all) and e-mail toggle per event category.
- Admin > Integracje: new tab for LDAP/AD + BookStack config, split out of
  Konfiguracja.
- Operator queue: Podkategoria/Zespół/Utworzono columns (off by default).
- Obserwuj button moved next to the auto-refresh countdown; trigger
  condition builder shows subcategory/zgłaszający as name dropdowns instead
  of raw IDs; /settings/notifications got a back link, full-width push
  card, and a bordered table container; admin panel tab and operator queue
  view now persist across a plain page refresh.
- Docs: README/ARCHITECTURE/wiki updated for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 23:43:01 +02:00

18 KiB

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
.env (w katalogu głównym repo, obok compose.yaml) Zmienne dla Docker Compose — hasła do MariaDB, nazwa hosta dla Traefika. Czyta go compose.yaml, nie czyta go Laravel.
src/.env (w katalogu aplikacji 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 src/.env muszą odpowiadać wartościom MYSQL_* z głównego .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 compose.yaml (docker network create traefik_public, jeśli jeszcze nie istnieje). Bez Traefika trzeba samodzielnie zmapować porty serwisu servicedesk 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 głównym

Oba są celowo .gitignore'owane (podobnie jak src/.env) — kopiujesz je z szablonów przy pierwszym wdrożeniu, a potem edytujesz lokalnie:

cp compose.yaml.example compose.yaml
cp .env.example .env

.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 src/.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 src/.env (Laravel)

cp src/.env.example src/.env

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

AUTHOR_CONTACT=helpdesk@twoja-domena.pl        # widoczne w Admin > O aplikacji
VERSION=1.1.3                                   # 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 głównego .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 src/.env są tylko wartościami startowymi/awaryjnymi. Docelowo LDAP i SMTP konfiguruje się wygodniej z poziomu Admin > Konfiguracja w samej aplikacji (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 lokalniecompose.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 ./src, Laravel odświeża się natychmiast. docker compose pull uruchamiaj ponownie tylko wtedy, gdy chcesz podnieść nowszy tag obrazu (np. po zmianie w 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 Dockerfile (celowo nie odpala się na zwykłe zmiany w src/ — 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 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 servicedesk — 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:
    - ./src:/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 src/.envBROADCAST_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 servicedesk composer install --no-dev --optimize-autoloader
docker compose exec servicedesk php artisan key:generate
docker compose exec servicedesk php artisan migrate --seed
docker compose exec servicedesk 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, branding oraz jedno konto lokalne admin@example.com / admin — zmień jego hasło od razu po pierwszym zalogowaniu (Admin > Użytkownicy).

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

Ani host, ani kontener servicedesk 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)/src":/app -w /app node:22 npm ci
docker run --rm -v "$(pwd)/src":/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) i kolejka

routes/console.php planuje tickets:check-sla-breaches co 15 minut, ale obraz Dockera nie ma wbudowanego cron/supervisora — bez dodatkowego kroku to zadanie nigdy się nie uruchomi. Najprościej dodać wpis crona na hoście:

* * * * * cd /ścieżka/do/repo && docker compose exec -T servicedesk php artisan schedule:run >> /dev/null 2>&1

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 > Konfiguracja pole host LDAP albo SMTP włączony + host jest ustawione, te wartości wygrywają z .env, bez potrzeby restartu czy redeployu.

Po świeżym migrate --seed te pola zawierają przykładowe placeholdery (ldap.example.com, smtp.example.com, changeme-*-password) — to znaczy, że zaraz po instalacji LDAP/SMTP faktycznie próbują łączyć się z tymi fałszywymi adresami, nawet jeśli w .env wpisałeś prawdziwe dane! Zanim oddasz system do użytku:

  1. Zaloguj się lokalnym kontem admin@example.com / admin.
  2. Wejdź w Admin > Konfiguracja i wpisz prawdziwe dane LDAP/SMTP (albo wyczyść pole hosta LDAP, żeby wrócić do wartości z .env).
  3. Użyj przycisków „Testuj połączenie” przy obu sekcjach, zanim zaczniesz polegać na logowaniu przez katalog.

Integracje opcjonalne (BookStack)

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 > Konfiguracja: adres instancji, Token ID/Secret (rola/użytkownik właściciela tokenu musi mieć w BookStacku uprawnienie „Access System API”), 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.


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/src
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

AUTHOR_CONTACT=helpdesk@twoja-domena.pl
VERSION=1.1.3

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 > Konfiguracja 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/src/public

    <Directory /var/www/servicedesk/src/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/src/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) i kolejka

Crontab użytkownika, pod którym stoi aplikacja (np. www-data):

* * * * * cd /var/www/servicedesk/src && 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/src
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 kroku 1.6 — zaloguj się admin@example.com / admin, zmień hasło, uzupełnij prawdziwe LDAP/SMTP w Admin > Konfiguracja (placeholdery z seeda inaczej realnie próbują łączyć się z fałszywymi adresami), przetestuj oba połączenia przyciskiem „Testuj połączenie”.

2.8. Aktualizacje (bez przestoju)

cd /var/www/servicedesk/src
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.