diff --git a/.gitignore b/.gitignore index 7691243d4d..cdaaf64cbb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ public/fonts public/images public/javascripts public/stylesheets +public/version.json theme/node_modules vendor diff --git a/CHANGELOG.md b/CHANGELOG.md index d97a0395b9..702a0014f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,36 @@ The following feedback pages previously returned HTTP **200 OK** and now return Maintenance: * Removed the `openconext` theme. The `skeune` theme is now the only supported theme and the default. (#1980) +### Actuator +There are now two additional endpoints available to retrieve information about the running software; +`/version.json` and `/actuator/info`. Ensure that those endpoints are restricted for only administrator access. + +example /version.json: +```json +{ + "name": "openconext/engineblock", + "description": "OpenConext SAML proxy", + "version": "7.0.0-dev", + "time": "2026-05-19T12:06:37.581Z" +} +``` + +example /actuator/info: +```json +{ + "build": { + "name": "openconext/engineblock", + "description": "OpenConext SAML proxy", + "version": "7.0.0-dev", + "time": "2026-05-18T11:59:07.992Z" + }, + "days_since_release": 1 +} +``` + +The time is set during composer install and thus is latest build time of the software. Using this you can ensure that you are running +the correct version of the software. + ## UNRELEASED 7.2.0 Upgrade to Symfony 7.4 Upgrade to `doctrine/dbal` 4 diff --git a/bin/extractVersion.sh b/bin/extractVersion.sh new file mode 100755 index 0000000000..c863f342f5 --- /dev/null +++ b/bin/extractVersion.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -e + +if [ ! -f "composer.json" ]; then + echo "composer.json not found" >&2 + exit 1 +fi + +# Extract simple fields (works for standard composer.json formatting) +NAME=$(grep -m1 '"name"' composer.json | sed -E 's/.*"name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') +DESCRIPTION=$(grep -m1 '"description"' composer.json | sed -E 's/.*"description"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/') +VERSION=$(grep -m1 '"version"' composer.json | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/') + +DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + +printf '{\n' +printf ' "name": "%s",\n' "$NAME" +printf ' "description": "%s",\n' "$DESCRIPTION" +printf ' "version": "%s",\n' "$VERSION" +printf ' "time": "%s"\n' "$DATE" +printf '}\n' diff --git a/composer.json b/composer.json index 1dbb587bab..a3a7dd7591 100644 --- a/composer.json +++ b/composer.json @@ -92,10 +92,12 @@ "assets:install %PUBLIC_DIR%": "symfony-cmd" }, "post-install-cmd": [ - "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters" + "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", + "bin/extractVersion.sh > public/version.json" ], "post-update-cmd": [ - "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters" + "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", + "bin/extractVersion.sh > public/version.json" ], "pre-archive-cmd": [ "bash -c 'if [ -z \"${ASSET_TAG}\" ]; then export ASSET_TAG=$(head -c16 /dev/urandom | od --format=x1 --address-radix=none | tr -d \" \"); echo \"ASSET_TAG not set, using random value: ${ASSET_TAG}\"; fi; sed -i -e \"s,#ASSET_VERSION#,${ASSET_TAG},g\" ./config/packages/framework.yaml'" diff --git a/config/services/controllers/authentication.yml b/config/services/controllers/authentication.yml index b086129041..1eb4fd0f89 100644 --- a/config/services/controllers/authentication.yml +++ b/config/services/controllers/authentication.yml @@ -77,3 +77,8 @@ services: arguments: - '@symfony.mailer' - '%email_request_access_address%' + + OpenConext\EngineBlockBundle\Controller\ActuatorController: + arguments: + - '@engineblock.compat.logger' + - '%kernel.project_dir%' diff --git a/src/OpenConext/EngineBlockBundle/Controller/ActuatorController.php b/src/OpenConext/EngineBlockBundle/Controller/ActuatorController.php new file mode 100644 index 0000000000..7e12892802 --- /dev/null +++ b/src/OpenConext/EngineBlockBundle/Controller/ActuatorController.php @@ -0,0 +1,77 @@ +logger = $logger; + $this->projectDir = $projectDir; + } + + #[Route( + path: '/actuator/info', + name: 'actuator_info', + methods: ['GET'] + )] + public function getBuildInfo(): JsonResponse + { + try { + $versionFile = $this->projectDir . DIRECTORY_SEPARATOR . self::PUBLIC_DIR . + DIRECTORY_SEPARATOR . self::VERSION_FILE; + + $versionContent = json_decode(file_get_contents($versionFile), true); + + // Calculate days since release + $buildTimeString = $versionContent["time"]; + $buildTime = DateTime::createFromFormat(self::BUILD_TIME_FORMAT, $buildTimeString); + $daysSinceRelease = (new DateTime())->diff($buildTime)->days; + + $buildInfo = [ + 'build' => $versionContent ?? [], + 'days_since_release' => $daysSinceRelease + ]; + } catch (Throwable $e) { + $this->logger->error("Failed to build actuator info: " . $e->getMessage(), $e->getTrace()); + $buildInfo = []; + } + + return $this->json($buildInfo); + } +}