- Snipe-IT asset inventory integration (Admin > Integracje), optional and off by default: connect by API address + personal token (+ SSL-verification bypass). Three independent toggles: client can pick which of their own Snipe-IT assets a ticket concerns (scoped to admin-selected subcategories, empty = never shows), operator sees the requester's assets in a ticket-view sidebar, operator can search the whole inventory from that same sidebar (not a separate page) for shared equipment. Assets shown as "numer środka - numer seryjny - producent model" + category; a linked asset's live status is fetched fresh on the ticket page, and unlinking stays available to an operator even with both view/search toggles off. - AI summary: a "Wygeneruj teraz" button for an immediate on-demand refresh, plus a new admin toggle to regenerate right after every new reply/note instead of only on the next scheduled sweep. The transcript sent to the model now also includes the ticket's own opening body, fixing summaries missing the original request on long threads. - Fixed: the status dropdown in the operator ticket view could keep showing the pre-change status after a status-changing quick action until the next page load (Livewire/Alpine-morph quirk for wire:change-bound selects). - Docs: README/ARCHITECTURE/CHANGELOG/install/wiki updated for all of the above, including correcting the AI-summary refresh description left over from the 1.2.1 release notes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
20 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 wcompose.yaml(docker network create traefik_public, jeśli jeszcze nie istnieje). Bez Traefika trzeba samodzielnie zmapować porty serwisuservicedeskna 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łyHost(...)w etykietachservicedesk).MYSQL_*— dane bazy dla konteneramariadb;MYSQL_USER/MYSQL_PASSWORDto te same wartości, które za chwilę wpiszesz dosrc/.envjakoDB_USERNAME/DB_PASSWORD.IMAGE_TAG— który tag obrazuservicedeskwdrożyć (patrz sekcja 1.3a); domyślnielatest, 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.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 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 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 ./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:
- Wygeneruj token: Ustawienia użytkownika > Aplikacje > Generate New Token,
z uprawnieniami co najmniej
write:packageiread:package. - 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/.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 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, 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 crona 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). 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:
- Zaloguj się lokalnym kontem
admin@example.com/admin. - Wejdź w Admin > Konfiguracja i wpisz prawdziwe dane LDAP/SMTP (albo wyczyść
pole hosta LDAP, żeby wrócić do wartości z
.env). - Użyj przycisków „Testuj połączenie” przy obu sekcjach, zanim zaczniesz polegać na logowaniu przez katalog.
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).
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.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 > 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_aliasApache definiujeAlias /icons/ "/usr/share/apache2/icons/"(FancyIndexing), który po cichu przechwytuje/icons/*przed dotarciem do Laravela. Ten projekt celowo używapublic/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, 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/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.