This is our internal server and site registration tool. It works in tandem with our
ITK sites server harvester.
The harvester is installed by default on all servers, and runs at intervals and collects
information about sites and installations running on the server. These are sent as
DetectionResults to ITKsites where they are analysed and processed.
This allows us to monitor
- What is installed and running
- Which sites/domains we are hosting
- What docker images we are running
- What packages and modules we are running
- If there are known CVE's for the packages/modules
- What git repositories we are hosting
Additionally we can register and document
- All OpenID Connect setups
- All Services Certificates
Servers, OpenID Connect setups, Services Certificates must be created and maintained manually. All other information is kept up to date by analysing the DetectionResults.
This is a Symfony 6 project build with api-platform 3.x and EasyAdmin.
Api-platform provides a simple REST api for POST'ing the DetectionResults. These are then processed asynchronously by a series of message handlers.
EasyAdmin is used to provide an interface to view and search the analyzed data, as well as editing the data that must updated manually.
The system is build so that all analyzed data can be truncated safely and rebuild by "replaying" the DetectionResults. This means that care must be taken when manually maintained data and auto updated data must have cross references.
Authenticated users can access a simple read-only API – see the API documentation on /api/docs for details.
Run the app:user:set-api-key console command to set the API for a user:
docker compose exec phpfpm php bin/console app:user:set-api-key <user-id>Use the API key to make an authenticated request, e.g.
curl --header 'accept: application/json' --header 'authorization: Apikey <the API key>' https://itksites.local.itkdev.dk/api/sitesThree endpoints report on the application, in increasing order of detail:
| Endpoint | Access | Checks |
|---|---|---|
/health/live |
Public | Nothing – only that the app responds |
/health/ready |
Public | All checks, aggregated status only |
/health/detail |
ITKBasicAuth in Traefik |
Per-check results and timings |
/health/ready answers 200 when everything is well and 503 when it is not.
It deliberately does not say what is wrong – point monitoring at this one and
read /health/detail when it goes red:
curl --silent https://itksites.local.itkdev.dk/health/detail | jqThe checks cover the database, the RabbitMQ messenger transport, the freshness of the most recent detection result and the expiry of the OIDC client secret. The freshness check catches an ingest pipeline that has stopped while the application itself is still serving requests. The client secret check catches the expiry that breaks every login at once.
HEALTH_INGEST_MAX_AGE sets how old the most recent detection result may be
before ingest is reported as degraded.
AZURE_AZ_OIDC_CLIENT_SECRET_EXPIRES_AT is where the client secret check reads
the date. It reports degraded only once that date has passed, so watch
days_remaining in the detail payload rather than waiting for it to go red. With
no date configured the check reports skipped, which means nothing is watching the
secret.
Results are cached for HEALTH_CACHE_TTL seconds so that polling does not turn
into load on the dependencies. The cache is the dedicated, filesystem-backed
cache.health pool in config/packages/cache.yaml – it has to keep working
while the database and the broker are down, and the adapter can be swapped
there without touching code.
^/health is excluded from the Symfony firewalls: both user providers are
Doctrine entity providers, so an authenticated endpoint would fail to
authenticate during a database outage and answer 500 rather than reporting
that the database is down.
docker compose pull
docker compose up --detach
docker compose exec phpfpm composer install
docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interactionThen create a .env.local file to set secrets for your local setup.
All user access is controlled by OpenID Connect. Locally the login runs against a
mock identity provider, defined as the idp service in docker-compose.override.yml
— the real provider has no redirect URI registered for a developer machine.
Start it with the rest of the stack:
docker compose up --detachThen log in as admin or editor: the mock shows a form where you type the subject,
and hands back the claims for it. Both identities are defined in the compose file, and
their claims must include name and upn, which AzureOIDCAuthenticator reads.
.env.dev carries the settings, so there is nothing to add to .env.local for an
ordinary setup. To develop against a real provider instead, override them there:
###> itk-dev/openid-connect-bundle ###
AZURE_AZ_OIDC_METADATA_URL=<value>
AZURE_AZ_OIDC_CLIENT_ID=<value>
AZURE_AZ_OIDC_CLIENT_SECRET=<value>
AZURE_AZ_OIDC_REDIRECT_URI=https://itksites.local.itkdev.dk/openid-connect/generic
AZURE_AZ_OIDC_ALLOW_HTTP=false
###< itk-dev/openid-connect-bundle ###Note
AZURE_AZ_OIDC_ALLOW_HTTP=true in .env.dev is what lets the application talk to
the mock over http inside the docker network. It must never be true anywhere else:
since itk-dev/openid-connect 5.1 it governs every endpoint the discovery document
announces, not only the metadata URL.
The mock accepts the PKCE challenge the bundle sends but does not verify it, so a successful login here does not prove PKCE works against Azure.
There are not implemented on
- sites
- installations
- domains
This is due to automated processes and scripts that listen from sites and data is therefore not relevant to have. The architecture makes it possible to delete all the above data.
docker compose exec phpfpm composer fixturesAfter loading fixtures you can sign in as an admin user:
docker compose exec phpfpm bin/console itk-dev:openid-connect:login admin@example.comAll processing of Detctionresults is done in a series of message handlers. To run these do either:
docker compose exec phpfpm composer queuesor
docker compose exec phpfpm bin/console messenger:consume async --failure-limit=1 -vvvWe use Webpack Encore to build assets:
docker compose run --rm node yarn install
docker compose run --rm node yarn buildUse
docker compose run --rm node yarn watchduring development to automatically rebuild assets when source files change.
docker compose run --rm node yarn coding-standards-checkThis project includes an claude.md file that provides project
context for Claude Code. The file describes the project architecture,
technology stack, development commands, CI/CD setup, and coding conventions.
Tool-specific configuration (permissions, hooks, plugins) lives in .claude/
and is not portable across tools.
Note
agents.md is a vendor-neutral standard supported by tools such as
OpenCode and others. Claude Code doesn't currently support
agents.md, claude.md should be renamed to a vendor neutral standard when Claude supports it.
The following plugins are enabled in .claude/settings.json:
| Plugin | Purpose | Source |
|---|---|---|
php-lsp |
PHP language server for type-aware code intelligence | claude-plugins-official |
context7 |
Up-to-date documentation lookup for Symfony, Doctrine, API Platform, etc. | claude-plugins-official |
code-review |
Pull request code review | claude-plugins-official |
code-simplifier |
Suggests clarity and maintainability improvements | claude-plugins-official |
security-guidance |
Flags potential security issues (OWASP, injection, etc.) | claude-plugins-official |
playwright |
Browser automation for debugging and testing the EasyAdmin UI | claude-plugins-official |
feature-dev |
Guided feature development with codebase exploration and architecture focus | claude-plugins-official |
Note: The
php-lspplugin requires Intelephense installed globally:npm install -g intelephense. All other plugins work without additional dependencies.
Custom agents in .claude/agents/ automate multi-step workflows:
| Agent | Purpose |
|---|---|
pr-readiness |
Runs all CI-equivalent checks locally before creating a PR |
create-migration |
Generates and validates a Doctrine migration after entity changes |
Custom skills in .claude/skills/ provide repeatable task shortcuts:
| Skill | Invocation | Purpose |
|---|---|---|
update-api-spec |
/update-api-spec |
Regenerate and stage OpenAPI spec files after changes |
Hooks in .claude/settings.json run automatically on tool events:
| Hook | Trigger | Purpose |
|---|---|---|
| Docker start | SessionStart |
Starts Docker services on session start |
| PHP-CS-Fixer | PostToolUse |
Auto-formats PHP files on edit |
| PHPStan | PostToolUse |
Runs static analysis on edited PHP files |
| Twig-CS-Fixer | PostToolUse |
Auto-formats Twig templates on edit |
| Composer norm | PostToolUse |
Normalizes composer.json on edit |
| Prettier | PostToolUse |
Auto-formats JS, CSS, YAML, and Markdown files on edit |
| Lock guard | PreToolUse |
Blocks edits to lock files and .env.local |
| Container lint | Stop |
Validates Symfony DI container before stopping |
A shared .mcp.json provides team-wide MCP server configuration:
| Server | Purpose |
|---|---|
context7 |
Live documentation lookup for Symfony, Doctrine, API Platform, and others |