# 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: ```bash cp compose.yaml.example compose.yaml cp .env.example .env ``` `.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) ```bash cp src/.env.example src/.env ``` Kluczowe wartości do zmiany względem `.env.example`: ```env 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.2.1 # 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ć: ```env 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: ```bash 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 ``. 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: ```bash 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): ```bash docker login gitea.kzbikowski.pl -u ``` 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: ```yaml 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: ```env 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 ```bash 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: ```bash 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**: ```cron * * * * * 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, 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 ```bash cd /var/www git clone servicedesk cd servicedesk/src composer install --no-dev --optimize-autoloader ``` ### 2.2. Konfiguracja `.env` ```bash 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: ```env 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.2.1 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). ```bash php artisan key:generate php artisan migrate --seed php artisan storage:link ``` ### 2.3. Budowa zasobów ```bash 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 ```bash 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`): ```apache ServerName servicedesk.twoja-domena.pl DocumentRoot /var/www/servicedesk/src/public AllowOverride All Require all granted 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 ``` ```bash 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): ```nginx 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): ```cron * * * * * 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: ```ini # /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 ``` ```bash 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) ```bash 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.