- In-app notifications: a bell in the top bar backed by Laravel's database
notification channel, alongside existing e-mail notifications (same
per-trigger toggle drives both; ticket links now correctly point into the
recipient's own area instead of always linking to the client view).
- Drag-and-drop attachments on every upload form, plus inline image
thumbnails in the message thread instead of a plain download link.
- Customer satisfaction (CSAT) rating: clients rate a closed ticket 1-5 stars
with an optional comment; shown read-only to operators, surfaced as a KPI
on the stats dashboard, and linked from the "ticket closed" e-mail.
- Saved queue views: operators can save/apply/delete named filter+sort+
column presets in the ticket queue and mark one as their default.
- Full-text search (MySQL FULLTEXT, portable LIKE fallback) across ticket
subject/body and reply message bodies, now also on the client's own ticket
list.
- Stats CSV export for the currently filtered ticket set.
- Optional BookStack knowledge-base integration (off by default): suggests
articles by category/subcategory while creating a ticket and in a separate
sidebar for operators on an existing ticket (with a copy-link button).
Configurable connection/SSL bypass/search-type filter, plus two
independent per-shelf allow-lists so nothing is ever searched until an
admin opts specific shelves in.
- Closed tickets no longer show in "Moje zgłoszenia"/"Nieprzypisane"/team
queue tabs, only under "Zamknięte" (matching how "Otwarte" already worked).
- Wired up the Admin > About "Wersja" field to config('app.version')/VERSION
in .env instead of a stale hardcoded string.
- Fixed: TicketService::setStatus() now checks a status's stage rather than
the literal key 'closed' to decide whether to fire the "ticket closed"
notification/stop the timer.
- Updated README/ARCHITECTURE/CHANGELOG/install/SECURITY docs and all three
wiki/ role guides for the above; documented a root-vs-www-data file
ownership gotcha in CLAUDE.md (running artisan commands via a plain
`docker exec` can leave root-owned Blade cache files that later break
recompilation for the www-data Apache process).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
15 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.1.0 # rezerwa na przyszłość, jeszcze nigdzie nie wyświetlane
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.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:
- 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)
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.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) 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ść.
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.