diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..509e45b
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,16 @@
+# Copy to .env (gitignored) — this is the Docker Compose–level env file, read by
+# compose.yaml only. It is separate from src/.env, which configures the Laravel
+# app itself (see src/.env.example and install.md).
+
+HOSTNAME=servicedesk.twoja-domena.pl
+
+# Database
+MYSQL_ROOT_PASSWORD=wygeneruj-silne-haslo
+MYSQL_DATABASE=servicedesk
+MYSQL_USER=servicedesk
+MYSQL_PASSWORD=wygeneruj-inne-silne-haslo
+
+# Which image tag to deploy (built by CI, see .gitea/workflows/build.yml).
+# Defaults to "latest" if unset — pin to a specific commit SHA tag to control
+# exactly when this host picks up a new image.
+# IMAGE_TAG=latest
diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
new file mode 100644
index 0000000..92202f4
--- /dev/null
+++ b/.gitea/workflows/build.yml
@@ -0,0 +1,42 @@
+name: Build and push image
+
+# The app image only provides PHP/Apache/extensions — application code is bind-
+# mounted from ./src at deploy time (see compose.yaml), so this only needs to
+# run when the image definition itself changes, not on every app code change.
+on:
+ push:
+ branches: [main]
+ paths:
+ - Dockerfile
+ - .gitea/workflows/build.yml
+
+permissions:
+ contents: read
+ packages: write
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Gitea container registry
+ uses: docker/login-action@v3
+ with:
+ registry: gitea.kzbikowski.pl
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: |
+ gitea.kzbikowski.pl/kzbkowski/servicedesk:latest
+ gitea.kzbikowski.pl/kzbkowski/servicedesk:${{ github.sha }}
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..a4dd703
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,112 @@
+# Architecture
+
+Server-rendered Laravel + Livewire app (no SPA/API-driven frontend for the app
+itself — the REST API in `routes/api.php` exists purely for external
+integrations). See [README.md](README.md) for the feature list and tech stack;
+this doc covers how the pieces fit together.
+
+## Request flow
+
+1. `routes/web.php` gates every area behind `auth` + a role middleware
+ (`role:client`, `role:operator`, `role:admin` → `App\Http\Middleware\EnsureRole`),
+ which checks the role against `$user->roles`. A user can hold multiple roles at
+ once; the router just requires *one* of the listed roles per route group.
+2. Each route resolves to a full-page Livewire component under
+ `app/Livewire/{Client,Operator,Admin,Auth}/` — there are no traditional
+ controllers rendering Blade views for these areas (the REST API in
+ `routes/api.php` is the exception, backed by `app/Http/Controllers/Api/`).
+3. Livewire components call into `app/Services/TicketService.php` for anything
+ that mutates ticket state (create/transition/reply/notify) rather than
+ mutating models directly — keep that convention when adding new mutations so
+ notification/history/SLA side effects stay in one place.
+4. `App\Providers\AppServiceProvider::boot()` runs a settings override pass on
+ every request (`applyLdapSettingsOverride`, `applyMailSettingsOverride`,
+ `applySessionSettingsOverride`, `applyTimezoneSettingsOverride`) — see
+ "Settings override" below.
+
+## Data model
+
+Core tables/models (`app/Models/`):
+
+```
+Category ─< Subcategory ─< CustomField (per-subcategory custom fields)
+ │
+ └──< Ticket >── Team (subcategory routes to a team)
+ │
+ ├──< TicketMessage (public replies + internal notes)
+ ├──< TicketAttachment
+ ├──< TicketHistory
+ ├── customer/assignee → User
+ ├── status → Status (fixed stages: new/open/closed)
+ └── priority → Priority → SlaRule (response/resolution minutes)
+
+User ─< UserFieldValue >─ UserField
+ApiClient (Sanctum token owner, ability-scoped)
+Setting (single-row-per-key config store, see below)
+ReplyQuickAction, ResponseTemplate, EmailTemplate, NotificationSetting
+```
+
+`Ticket` (`app/Models/Ticket.php`) is the largest model — it owns SLA math
+(`slaInfo()`, `isOverdue()`, `resolutionDeadline()`), status/priority display
+helpers (`statusLabel()`, `tagStyleFromColor()`), operator-visibility scoping
+(`scopeVisibleToOperator`, `isVisibleToOperator` — a team member sees their team's
+queue + unassigned + anything assigned to them, an admin sees everything), and
+work-timer tracking (`timerElapsedSeconds()`). Keep ticket-shaped logic here
+rather than spreading it across Livewire components.
+
+## Roles & permissions
+
+Roles are a plain array on the user (`$user->roles`), not a separate pivot-backed
+package — checked via `EnsureRole` at the route level. Every account gets
+`client` by default (`App\Ldap\Handlers\AssignDefaultRole` for LDAP-provisioned
+accounts); staff switch areas via the header role switcher, but always land on
+`/client` first after login.
+
+## Authentication
+
+`config/auth.php` defines the default `web` guard against an LDAP-backed user
+provider (LdapRecord); a plain Eloquent provider is kept alongside it only for
+local tooling/tests that don't hit a directory. In production, LDAP bind is the
+primary path; the local fallback account (`admin@example.com` from the seeder)
+authenticates against a local password when the LDAP bind doesn't match — this is
+the account used for first login after a fresh install (see
+[install.md](install.md)).
+
+`app/Ldap/Handlers/` hooks into LdapRecord's import/sync events:
+`AssignDefaultRole` grants the `client` role to new LDAP-provisioned accounts,
+`SyncUserFieldsFromLdap` keeps `UserFieldValue` rows in sync with directory
+attributes.
+
+## Settings override ("live config")
+
+`App\Support\Settings` (`app/Support/Settings.php`) is a cached key/value reader
+over the `settings` table, with hardcoded defaults for every key (company name,
+LDAP/SMTP connection details, attachment limits, session lifetime, timezone,
+branding/email HTML, etc.). Admin > Konfiguracja writes to this table, and
+`AppServiceProvider::boot()` re-applies the relevant subset of it over
+`config()` on every request — meaning **`Setting` rows win over `.env`** for
+LDAP, mail, session lifetime and timezone once they're non-empty. This is by
+design (lets an admin reconfigure LDAP/SMTP without a redeploy) but is also the
+source of the "seeded placeholder overrides real `.env` values" gotcha
+documented in [install.md](install.md) — anything touching LDAP/mail/session/
+timezone config should go through `Settings`, not raw `config()`/`.env` reads.
+
+## SLA
+
+`SlaRule` holds per-priority response/resolution targets in minutes. The
+scheduled command `tickets:check-sla-breaches` (registered in
+`routes/console.php`, run every 15 minutes via `schedule:run`) flags overdue
+tickets and can notify the assigned operator — see [install.md](install.md) for
+why this requires an external cron entry (the Docker image ships no
+cron/supervisor of its own).
+
+## API
+
+`routes/api.php` + `app/Http/Controllers/Api/` expose a small ability-scoped REST
+surface over Sanctum tokens (`tickets:read`, `tickets:write`,
+`dictionaries:read`, `users:read`), issued via admin-managed `ApiClient` records.
+Rate limiting is configured per-client (120 req/min keyed by client ID) vs. a
+tighter per-IP limit for unauthenticated requests
+(`AppServiceProvider::configureApiRateLimiting()`). Interactive docs are
+generated by L5-Swagger at `/admin/api-docs`; there is no static Markdown API
+reference in-repo.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..60005b0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,43 @@
+# Changelog
+
+All notable changes to this project are documented in this file. Format loosely
+follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
+
+## [1.0.1] - 2026-07-22
+
+Documentation and deployment/CI overhaul — no application behavior changes.
+
+- Added project docs: `TESTING.md`, `CONTRIBUTING.md`, `ARCHITECTURE.md`,
+ `SECURITY.md`, `CLAUDE.md`; removed the unmaintained stock Laravel
+ `src/README.md`.
+- Added `.gitea/workflows/build.yml`: Gitea Actions now builds and pushes the
+ `servicedesk` image to the Gitea container registry whenever `Dockerfile`
+ changes on `main`.
+- `compose.yaml` now pulls `image: gitea.kzbikowski.pl/kzbkowski/servicedesk:${IMAGE_TAG:-latest}`
+ instead of building locally; `mariadb` pinned to `mariadb:12.3`; added a
+ `mariadb` healthcheck and `depends_on: condition: service_healthy` for
+ `servicedesk`.
+- Added tracked templates `compose.yaml.example` and `.env.example` (root) for
+ the previously-untracked `compose.yaml`/`.env`.
+- Documented the new pull-based deploy flow and one-time registry login in
+ `install.md`.
+
+## [1.0.0] - 2026-07-21
+
+Initial release.
+
+- Ticketing core: categories/subcategories with per-subcategory custom fields,
+ statuses, priorities, teams, attachments, message threads (public replies +
+ internal notes), history log, merge/delete.
+- SLA rules per priority with a scheduled breach check (`tickets:check-sla-breaches`,
+ every 15 minutes).
+- Response templates, quick actions, and per-event HTML email templates/
+ notification toggles.
+- Operator statistics dashboard (`/operator/stats`) with KPI tiles and breakdowns.
+- Branding/config panel: company identity, LDAP connection + user sync, SMTP,
+ attachment limits, session lifetime, timezone.
+- LDAP authentication (LdapRecord) with local-account fallback.
+- REST API (`/api/v1/...`) via Sanctum, ability-scoped (`tickets:read`,
+ `tickets:write`, `dictionaries:read`, `users:read`), with Swagger docs at
+ `/admin/api-docs`.
+- Installable PWA manifest/icons for the client-facing area.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..f3b4ff1
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,78 @@
+# CLAUDE.md
+
+Guidance for AI coding agents working in this repo. See [README.md](README.md)
+for the feature/tech overview, [ARCHITECTURE.md](ARCHITECTURE.md) for how the app
+is put together, and [install.md](install.md) for full deployment instructions.
+
+## This is a live production system
+
+The only real user today is the owner's own account (client+operator+admin
+roles). Treat the running database as production, not a sandbox:
+
+- Never create/modify/delete real DB records (users, tickets, settings, etc.)
+ without the user explicitly asking. Don't spin up throwaway test users via
+ `php artisan tinker` against this DB.
+- For UI/CSS verification, prefer an offline static-HTML harness reproducing the
+ real markup+CSS, or ask for test credentials, rather than poking at prod data.
+- Production is fronted by Traefik with a **private-CA TLS cert** (not publicly
+ trusted) — tools like `curl`/Playwright need `-k` / `ignore_https_errors` to
+ hit it directly.
+
+## Container operations: use `sudo`, never build the image locally
+
+All Docker commands against this stack need `sudo` (e.g.
+`sudo docker compose exec servicedesk ...`, `sudo docker exec servicedesk-servicedesk-1 ...`).
+
+**Never run `docker build`, `docker compose build`, or `--build`.** The
+`servicedesk` image is built by CI (`.gitea/workflows/build.yml`, triggered on
+`Dockerfile` changes on `main`) and pushed to the Gitea container registry;
+`compose.yaml` only ever `pull`s a tag (`sudo docker compose pull && sudo docker
+compose up -d`, see [install.md](install.md) 1.3/1.3a) — building locally would
+just diverge from what CI produces. The app container
+(`servicedesk-servicedesk-1`) mounts `./src` from the host over NFS
+(`/mnt/rabbit-containers` → NFS export), so plain file edits already take effect
+with no rebuild or restart:
+
+- Blade/PHP/route edits: just edit the files — live instantly, no restart needed.
+- `resources/css/app.css` / `resources/js/*` (Vite/Tailwind entrypoints) **do**
+ need a rebuild — `public/build/assets/` is static compiled output. Neither the
+ host nor the app container has Node installed; rebuild with a throwaway
+ container instead of touching the app image:
+ ```bash
+ sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run build
+ ```
+ (run from the repo root; `node_modules` already exists, no install needed).
+ Verify via `public/build/manifest.json` picking up a new hash.
+- After running artisan cache commands for diagnostics (`config:cache`,
+ `view:cache`), clear them again afterward (`config:clear`/`view:clear`) — this
+ app normally runs uncached so edits apply live; leaving a cache on silently
+ breaks that workflow.
+
+## Apache `/icons/` alias trap
+
+The stock `php:apache` image enables `mods-enabled/alias.conf`, which defines
+`Alias /icons/ "/usr/share/apache2/icons/"`. Any app-level static directory at
+`public/icons/` is silently shadowed by Apache's own stock icon set — requests
+404 before ever reaching Laravel, with no useful log line. This project
+deliberately uses `public/pwa-icons/` for PWA manifest icons for exactly this
+reason — never add a `public/icons/` directory.
+
+## Established UI conventions
+
+- **Post-login redirect** always lands on `/client` (`User::defaultArea()` in
+ `app/Models/User.php`), even for accounts holding operator/admin roles too —
+ staff switch areas via the role switcher in the header
+ (`resources/views/components/profile-menu.blade.php`). Keep `/client`
+ first-priority in `defaultArea()` if you add new roles/areas.
+- **Mobile tables**: don't rely on horizontal scroll alone for wide data tables.
+ The established pattern (`.table-cards-mobile` in `resources/css/app.css`,
+ `@media (max-width:640px)`) turns each `
` into a card with `data-label`
+ attributes on `| `s. Currently applied to the operator ticket queue; apply
+ the same treatment to any other wide table you add or make mobile-relevant
+ (admin panel tables don't have it yet).
+
+## Testing & code style
+
+See [TESTING.md](TESTING.md) and [CONTRIBUTING.md](CONTRIBUTING.md) — run
+`php artisan test` and `./vendor/bin/pint` before considering a change done.
+There is no CI; local test runs are the only gate.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..4515546
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing
+
+Internal helpdesk project for a single production deployment — this guide covers
+the practical local workflow, not an open-source contribution process.
+
+## Local setup
+
+See [install.md](install.md) for full environment setup (Docker Compose or
+bare-metal). For day-to-day development the app container mounts `./src`
+directly, so PHP/Blade/route changes apply immediately — no rebuild or restart
+needed. CSS/JS changes need a Vite build (see the "Local/dev notes" section of
+[README.md](README.md)); Node isn't installed on the host or in the app image, so
+build through a throwaway `node:22` container as documented there.
+
+## Before opening a PR / merging
+
+1. **Run the test suite** — see [TESTING.md](TESTING.md) for details:
+ ```bash
+ docker compose exec servicedesk php artisan test
+ ```
+2. **Run Pint** (Laravel's code-style fixer, default preset, no project overrides):
+ ```bash
+ docker compose exec servicedesk ./vendor/bin/pint
+ ```
+3. If you changed anything under `resources/`, rebuild the frontend bundle and
+ commit the result if `public/build/` is tracked, or confirm the deploy step
+ will rebuild it (see [install.md](install.md)).
+4. If you touched the data model, add/update a migration rather than editing an
+ existing one that has already shipped, and update
+ [ARCHITECTURE.md](ARCHITECTURE.md) if the change affects the ticket lifecycle
+ or role/permission model.
+
+## Commit messages
+
+Short, imperative summary line (e.g. `Add SLA breach email toggle`); add a body
+only when the *why* isn't obvious from the diff.
+
+## Code organization
+
+Follow the existing structure rather than introducing new patterns:
+
+- `app/Livewire/{Client,Operator,Admin,Auth}/` — one component per screen/area,
+ gated by the matching route middleware (`role:client`, `role:operator`,
+ `role:admin`).
+- `app/Services/TicketService.php` — ticket lifecycle logic (create/transition/
+ notify) lives here, not in Livewire components.
+- `app/Models/` — one Eloquent model per table; keep query scopes and display/
+ formatting helpers (labels, style/color helpers) on the model as done for
+ `Ticket` (`statusLabel()`, `slaInfo()`, etc.) rather than duplicating them in views.
+- `database/migrations/` — one migration per table group, representing final
+ shape (not an incremental history to replay for intuition).
+- `routes/web.php` / `routes/api.php` — keep role/ability gating at the route
+ group level, matching the existing pattern.
+
+See [ARCHITECTURE.md](ARCHITECTURE.md) for how these pieces fit together.
diff --git a/README.md b/README.md
index 406a625..c4aed18 100644
--- a/README.md
+++ b/README.md
@@ -66,9 +66,12 @@ and **[wiki/admin](wiki/admin/README.md)** for role-specific how-to guides.
dashboard is hand-rolled inline-styled bar/column charts, so it needs no client
build step beyond the CSS bundle.
- **Database**: MariaDB.
-- **Deployment**: `compose.yaml` — `servicedesk` (php:apache, source bind-mounted
- from `./src`, no image rebuild needed for PHP/Blade/route changes) + `mariadb`,
- fronted by Traefik with a private-CA TLS cert.
+- **Deployment**: `compose.yaml` — `servicedesk` (source bind-mounted from `./src`,
+ no image rebuild needed for PHP/Blade/route changes) + `mariadb`, fronted by
+ Traefik with a private-CA TLS cert. The `servicedesk` image itself is built and
+ pushed by Gitea Actions (`.gitea/workflows/build.yml`) to the Gitea container
+ registry whenever `Dockerfile` changes — `compose.yaml` just pulls a tag, it
+ never builds locally.
See **[install.md](install.md)** for full step-by-step deployment instructions —
both via Docker Compose (this stack) and directly on a server with Apache/Nginx,
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..32eb4b8
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,54 @@
+# Security Policy
+
+This is an internally-run production helpdesk (not an open-source project with a
+public disclosure program). If you find a vulnerability affecting this
+deployment, report it directly rather than opening a public GitHub issue:
+
+- **Contact**: the address configured as `AUTHOR_CONTACT` in `src/.env`
+ (surfaced in the app under Admin > O aplikacji).
+- Please include steps to reproduce and, if applicable, which role/area
+ (Client/Operator/Admin/API) is affected.
+
+Do not test against the production instance beyond what's needed to demonstrate
+the issue — no automated scanning, load testing, or bulk data extraction.
+
+## Scope & sensitive data
+
+This system holds real ticket content, user PII (names/emails, and anything
+submitted in ticket bodies/custom fields), and LDAP/SMTP connection credentials.
+Treat access to the `admin` area, the `settings` table, and any `.env` file as
+credential-equivalent.
+
+## Notable design points relevant to security review
+
+- **Auth**: LDAP bind is the primary login path (`config/auth.php`,
+ `directorytree/ldaprecord-laravel`); a local fallback account
+ (`admin@example.com`, seeded — **must** have its password changed after
+ install, see [install.md](install.md)) exists for when LDAP is unavailable or
+ misconfigured.
+- **Settings override**: Admin > Konfiguracja values in the `settings` table
+ override `.env` for LDAP/SMTP/session/timezone config at runtime (see
+ [ARCHITECTURE.md](ARCHITECTURE.md) — "Settings override"). This means a
+ compromised admin account can redirect LDAP/SMTP traffic without touching the
+ filesystem — restrict Admin-role accounts accordingly.
+- **API**: Sanctum tokens are ability-scoped (`tickets:read`, `tickets:write`,
+ `dictionaries:read`, `users:read`) and issued per `ApiClient` via the admin
+ panel; rate-limited per-client (authenticated) or per-IP (unauthenticated) —
+ see `AppServiceProvider::configureApiRateLimiting()`.
+- **Role model**: roles are checked via `EnsureRole` middleware against
+ `$user->roles`; there's no per-object ACL beyond team-based ticket visibility
+ (`Ticket::scopeVisibleToOperator`) — any change to that scope directly changes
+ what an operator can see across teams.
+- **Attachments**: size/count/type limits are admin-configurable
+ (`attachment_max_size_kb`, `attachment_allowed_types`, etc. in `Settings`) —
+ don't bypass them when adding new upload paths.
+- **TLS**: production traffic terminates at Traefik with a private CA
+ certificate (not publicly trusted) — this is expected for this deployment, not
+ a misconfiguration.
+
+## Dependencies
+
+No automated dependency-vulnerability scanning (e.g. Dependabot, `composer
+audit` in CI) is currently configured — there is no CI pipeline for this repo at
+all (see [TESTING.md](TESTING.md)). Run `composer audit` / `npm audit` manually
+before major dependency bumps.
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 0000000..e71455f
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,64 @@
+# Testing
+
+Tests live in `src/tests/` and run on [Pest 4](https://pestphp.com) (with the Laravel
+plugin), backed by PHPUnit's runner. `src/phpunit.xml` configures two suites,
+`Unit` and `Feature` (almost everything lives in `Feature/` — end-to-end Livewire
+component and API tests), running against an in-memory SQLite database
+(`DB_CONNECTION=sqlite`, `DB_DATABASE=:memory:`) so tests never touch the real
+MariaDB instance. Mail, queue, cache, session and broadcasting are all swapped for
+fast in-process fakes (`array`/`sync`) for the same reason.
+
+## Running the suite
+
+From inside the app container (or on a bare-metal install, from `src/`):
+
+```bash
+docker compose exec servicedesk php artisan test
+```
+
+or directly with Pest:
+
+```bash
+docker compose exec servicedesk ./vendor/bin/pest
+```
+
+Run a single file or filter by name:
+
+```bash
+docker compose exec servicedesk php artisan test --filter=SlaBreachNotificationTest
+docker compose exec servicedesk ./vendor/bin/pest tests/Feature/TicketApiTest.php
+```
+
+There is no CI pipeline configured for this repository — running the suite
+locally/in-container before merging or deploying is the only gate today.
+
+## What's covered
+
+The `Feature/` suite is organized by area and is the main source of truth for
+expected behavior — read the relevant test before changing logic in that area:
+
+- **Admin**: category/subcategory + custom fields, teams, users, branding, status/
+ priority reordering, response templates, email templates & notification toggles,
+ SMTP/timezone/session-lifetime config.
+- **Operator**: ticket queue (search/sort/columns, bulk actions, closed tab, team
+ scoping), ticket detail editing, quick actions, time tracking.
+- **Client**: new ticket submission.
+- **Auth/LDAP**: LDAP login, LDAP-restricted guest ticket creation + local-account
+ fallback, account-creation toggles.
+- **API**: ability-scoped Sanctum enforcement (`ApiAbilityEnforcementTest`), API key
+ management, tickets/messages/users/dictionaries endpoints.
+- **Cross-cutting**: role middleware, SLA breach notifications, attachment limits,
+ ticket business rules (status/priority transitions, merging, history log).
+
+`tests/Pest.php` wires up `Tests\TestCase` (Laravel's `RefreshDatabase`-style base)
+for the whole suite — new test files under `tests/Feature/` pick this up
+automatically without extra boilerplate.
+
+## Code style
+
+`laravel/pint` is installed as a dev dependency (no custom `pint.json`, so it runs
+on Laravel's default preset). Run it before committing:
+
+```bash
+docker compose exec servicedesk ./vendor/bin/pint
+```
diff --git a/compose.yaml.example b/compose.yaml.example
new file mode 100644
index 0000000..5f4e557
--- /dev/null
+++ b/compose.yaml.example
@@ -0,0 +1,50 @@
+# Copy to compose.yaml (gitignored — the real file stays untracked, same as .env)
+# and adjust HOSTNAME/image/registry as needed for your deployment.
+services:
+ servicedesk:
+ image: gitea.kzbikowski.pl/kzbkowski/servicedesk:${IMAGE_TAG:-latest}
+ 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.servicedesk.rule=Host(`${HOSTNAME}`)
+ - traefik.http.routers.servicedesk.entrypoints=websecure
+ # - traefik.http.routers.servicedesk.tls.certresolver=tls-resolver
+ - traefik.http.routers.servicedesk.tls=true
+
+ mariadb:
+ image: mariadb:12.3
+ restart: unless-stopped
+ networks:
+ - internal
+ command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
+ volumes:
+ - ./mysql:/var/lib/mysql
+ ports:
+ - "3605:3306"
+ environment:
+ - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
+ - MYSQL_DATABASE=${MYSQL_DATABASE}
+ - MYSQL_USER=${MYSQL_USER}
+ - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+ healthcheck:
+ test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+ labels:
+ - com.centurylinklabs.watchtower.enable=true
+
+networks:
+ internal:
+ traefik_public:
+ external: true
diff --git a/install.md b/install.md
index 67b658c..3457101 100644
--- a/install.md
+++ b/install.md
@@ -28,9 +28,17 @@ osobne pliki, w dwóch różnych miejscach.
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. Plik `.env` w katalogu głównym (Docker Compose)
+### 1.1. Pliki `compose.yaml` i `.env` w katalogu głównym
-Skopiuj/utwórz `.env` obok `compose.yaml`:
+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
@@ -38,6 +46,7 @@ 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
@@ -45,6 +54,8 @@ MYSQL_PASSWORD=wygeneruj-inne-silne-haslo
- `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)
@@ -100,17 +111,52 @@ MAIL_FROM_ADDRESS=noreply@twoja-domena.pl
MAIL_FROM_NAME="${APP_NAME}"
```
-### 1.3. Budowa i start kontenerów
+### 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 up -d --build
+docker compose pull
+docker compose up -d
```
-`--build` jest potrzebny **tylko przy pierwszym uruchomieniu** (budowa obrazu z
-`Dockerfile`). Później, przy zwykłych zmianach w kodzie PHP/Blade — źródło jest
-zamontowane z `./src`, więc `docker compose restart` (albo nic — Laravel odświeża
-się natychmiast) wystarczy; `--build`/`docker compose build` uruchamiaj tylko gdy
-zmienia się sam `Dockerfile`.
+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 ``.
+
+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.4. Instalacja aplikacji wewnątrz kontenera
diff --git a/src/.env.example b/src/.env.example
index d1416d8..40c1c79 100644
--- a/src/.env.example
+++ b/src/.env.example
@@ -5,7 +5,7 @@ APP_DEBUG=true
APP_URL=http://localhost
AUTHOR_CONTACT=helpdesk@kzbikowski.pl
-VERSION=1.0.0
+VERSION=1.0.1
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
diff --git a/src/README.md b/src/README.md
deleted file mode 100644
index 5ad1377..0000000
--- a/src/README.md
+++ /dev/null
@@ -1,58 +0,0 @@
- 
-
-
-
-
-
-
-
-
-## About Laravel
-
-Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
-
-- [Simple, fast routing engine](https://laravel.com/docs/routing).
-- [Powerful dependency injection container](https://laravel.com/docs/container).
-- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
-- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
-- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
-- [Robust background job processing](https://laravel.com/docs/queues).
-- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
-
-Laravel is accessible, powerful, and provides tools required for large, robust applications.
-
-## Learning Laravel
-
-Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
-
-In addition, [Laracasts](https://laracasts.com) contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
-
-You can also watch bite-sized lessons with real-world projects on [Laravel Learn](https://laravel.com/learn), where you will be guided through building a Laravel application from scratch while learning PHP fundamentals.
-
-## Agentic Development
-
-Laravel's predictable structure and conventions make it ideal for AI coding agents like Claude Code, Cursor, and GitHub Copilot. Install [Laravel Boost](https://laravel.com/docs/ai) to supercharge your AI workflow:
-
-```bash
-composer require laravel/boost --dev
-
-php artisan boost:install
-```
-
-Boost provides your agent 15+ tools and skills that help agents build Laravel applications while following best practices.
-
-## Contributing
-
-Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
-
-## Code of Conduct
-
-In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
-
-## Security Vulnerabilities
-
-If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
-
-## License
-
-The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|