8 Commits

Author SHA1 Message Date
ismailmasseran 1a7dead730 DONE: use static volume files
Build Docker Image / build-backend (push) Successful in 1m27s
2026-10-25 18:10:49 +08:00
ismailmasseran 7cd6976114 DONE: update provider to clear cache
Build Docker Image / build-backend (push) Successful in 1m19s
2026-10-25 16:44:52 +08:00
ismailmasseran a627afb6c5 DONE: implement best practice for cicd, separate docker compose
Build Docker Image / build-backend (push) Successful in 1m18s
2026-10-25 15:23:55 +08:00
ismailmasseran 3ff62ccec6 DONE: update cicd for backend
Build Docker Image / build-backend (push) Successful in 30s
2026-10-25 14:38:26 +08:00
ismailmasseran bd75a0ed03 DONE: remove opcache as it is build-in
Build Docker Image / build-backend (push) Successful in 4m9s
2026-10-25 12:12:27 +08:00
ismailmasseran 5047f32118 DONE: upgrade maatwebsite excel to based on latest php 8.5
Build Docker Image / build-backend (push) Failing after 59s
2026-10-25 12:05:48 +08:00
ismailmasseran 337be0f332 DONE: separate repo for be and fe, update cicd
Build Docker Image / build-backend (push) Successful in 1m18s
2026-10-25 11:59:58 +08:00
ismailmasseran b0475e3371 DONE: fix issue email redirect to localhost, move action button in user to first column, update cicd 2026-10-25 11:15:09 +08:00
22 changed files with 567 additions and 314 deletions
+2
View File
@@ -5,6 +5,8 @@
/docker-images
/.vscode
.env
.env.*
.gitea
NOTE.md
/node_modules
/vendor
+42
View File
@@ -0,0 +1,42 @@
name: Build Docker Image
on:
push:
tags:
- "v*"
jobs:
build-backend:
runs-on: docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Login to Docker registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.koppkb.com \
-u "${{ secrets.REGISTRY_USERNAME }}" \
--password-stdin
- name: Build & push backend image
run: |
set -e
IMAGE="git.koppkb.com/kopkb/my-kopkb-be"
TAGS="-t ${IMAGE}:${{ gitea.sha }}"
if [ "${{ gitea.ref_type }}" = "tag" ] && echo "${{ gitea.ref_name }}" | grep -q '^v'; then
TAGS="${TAGS} -t ${IMAGE}:${{ gitea.ref_name }}"
fi
echo "Building and pushing backend: ${TAGS}"
docker buildx build \
--target production \
--platform linux/amd64 \
--cache-from type=registry,ref=${IMAGE}:buildcache,ignore-error=true \
--cache-to type=registry,ref=${IMAGE}:buildcache,mode=max \
-f docker/common/unified/Dockerfile \
${TAGS} \
--push .
+84
View File
@@ -0,0 +1,84 @@
name: Deploy Production Data Services
# Manual deploy for Postgres and Redis. A backend release does not run this.
on:
workflow_dispatch:
jobs:
deploy:
runs-on: mykopkb-production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy data services over SSH
env:
PROD_HOST: ${{ secrets.PROD_HOST }}
PROD_USER: ${{ secrets.PROD_USER }}
PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
PROD_DEPLOY_DIR: ${{ secrets.PROD_DEPLOY_DIR }}
run: |
set -euo pipefail
if [ ! -f .env.production ]; then
echo "ERROR: .env.production is missing from the repository checkout"
exit 1
fi
if ! command -v ssh >/dev/null 2>&1; then
apt-get update && apt-get install -y openssh-client
fi
mkdir -p ~/.ssh
umask 077
if printf '%s' "$PROD_SSH_KEY" | grep -q -- '-----BEGIN'; then
printf '%b\n' "$PROD_SSH_KEY" | tr -d '\r' > ~/.ssh/id_ed25519
else
printf '%s' "$PROD_SSH_KEY" | tr -d '[:space:]' | base64 -d > ~/.ssh/id_ed25519
fi
chmod 600 ~/.ssh/id_ed25519
if ! ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null; then
echo "ERROR: PROD_SSH_KEY is not a readable private key."
exit 1
fi
ssh-keyscan -H "$PROD_HOST" >> ~/.ssh/known_hosts
SSH=(ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes "${PROD_USER}@${PROD_HOST}")
"${SSH[@]}" "bash -s" << EOF
set -euo pipefail
for name in mykopkb-postgres-production mykopkb-redis-production; do
if docker inspect "\$name" >/dev/null 2>&1; then
project=\$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project"}}' "\$name" 2>/dev/null || true)
if [ "\$project" != "mykopkb-data" ]; then
echo "ERROR: \$name is already running under compose project '\${project:-unknown}'."
echo "This workflow does not replace the existing database or Redis containers."
exit 1
fi
fi
done
EOF
"${SSH[@]}" "mkdir -p '${PROD_DEPLOY_DIR}/redis-config' '${PROD_DEPLOY_DIR}/docker/postgres/conf.d'"
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
docker/production/docker-compose.data.yml \
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/docker-compose.data.yml"
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
.env.production \
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/.env"
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
docker/production/redis/redis.conf \
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/redis-config/redis.conf"
"${SSH[@]}" "bash -s" << EOF
set -euo pipefail
chmod 600 "${PROD_DEPLOY_DIR}/.env"
docker network inspect mykopkb-production-network >/dev/null 2>&1 \
|| docker network create mykopkb-production-network
cd "${PROD_DEPLOY_DIR}"
docker compose -p mykopkb-data -f docker-compose.data.yml up -d
docker compose -p mykopkb-data -f docker-compose.data.yml ps
EOF
+155
View File
@@ -0,0 +1,155 @@
name: Deploy to Production
# Manual production deploy — pulls pre-built images from the registry
on:
workflow_dispatch:
inputs:
image_tag:
description: "Docker image tag to deploy (commit SHA or version tag, e.g. v1.0.0)"
required: true
default: "latest"
type: string
env:
BACKEND_IMAGE: git.koppkb.com/kopkb/my-kopkb-be
jobs:
deploy:
runs-on: mykopkb-production
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.image_tag }}
- name: Verify image exists in registry
run: |
set -e
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.koppkb.com \
-u "${{ secrets.REGISTRY_USERNAME }}" \
--password-stdin
IMAGE="${BACKEND_IMAGE}:${{ inputs.image_tag }}"
echo "Checking if image exists: ${IMAGE}"
docker manifest inspect "${IMAGE}" > /dev/null
echo "✓ ${IMAGE} found"
- name: Deploy over SSH
env:
PROD_HOST: ${{ secrets.PROD_HOST }}
PROD_USER: ${{ secrets.PROD_USER }}
PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
PROD_DEPLOY_DIR: ${{ secrets.PROD_DEPLOY_DIR }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
IMAGE_TAG: ${{ inputs.image_tag }}
run: |
set -euo pipefail
if [ ! -f .env.production ]; then
echo "ERROR: .env.production is missing from the repository checkout"
exit 1
fi
if [ ! -f docker/production/nginx/nginx.conf ]; then
echo "ERROR: docker/production/nginx/nginx.conf is missing from the repository checkout"
exit 1
fi
if ! command -v ssh >/dev/null 2>&1; then
apt-get update && apt-get install -y openssh-client
fi
mkdir -p ~/.ssh
umask 077
# Gitea often stores a multiline key as one line. Prefer a base64
# secret (one line). A raw key that still contains BEGIN is accepted too.
if printf '%s' "$PROD_SSH_KEY" | grep -q -- '-----BEGIN'; then
printf '%b\n' "$PROD_SSH_KEY" | tr -d '\r' > ~/.ssh/id_ed25519
else
printf '%s' "$PROD_SSH_KEY" | tr -d '[:space:]' | base64 -d > ~/.ssh/id_ed25519
fi
chmod 600 ~/.ssh/id_ed25519
if ! ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null; then
echo "ERROR: PROD_SSH_KEY is not a readable private key."
echo "On the runner, run: base64 -w0 ~/.ssh/mykopkb_prod"
echo "Put that single line in the PROD_SSH_KEY secret."
exit 1
fi
ssh-keyscan -H "$PROD_HOST" >> ~/.ssh/known_hosts
SSH=(ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes "${PROD_USER}@${PROD_HOST}")
IMAGE="${BACKEND_IMAGE}:${IMAGE_TAG}"
NGINX_CONF="${PROD_DEPLOY_DIR}/nginx/nginx.conf"
"${SSH[@]}" "mkdir -p '${PROD_DEPLOY_DIR}/nginx'"
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
docker/production/docker-compose.production.yml \
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/docker-compose.yml"
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
.env.production \
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/.env"
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
docker/production/nginx/nginx.conf \
"${PROD_USER}@${PROD_HOST}:${NGINX_CONF}"
"${SSH[@]}" "bash -s" << EOF
set -euo pipefail
echo "${REGISTRY_PASSWORD}" | docker login git.koppkb.com \
-u "${REGISTRY_USERNAME}" --password-stdin
echo "Checking if image exists: ${IMAGE}"
docker manifest inspect "${IMAGE}" > /dev/null
echo "✓ ${IMAGE} found"
echo "Deploying backend IMAGE=${IMAGE}"
chmod 600 "${PROD_DEPLOY_DIR}/.env"
docker network inspect mykopkb-production-network >/dev/null 2>&1 \
|| docker network create mykopkb-production-network
cd "${PROD_DEPLOY_DIR}"
export IMAGE_TAG="${IMAGE_TAG}"
export NGINX_CONF="${NGINX_CONF}"
docker rm -f mykopkb-be-production || true
docker compose -f docker-compose.yml pull backend
docker compose -f docker-compose.yml up -d backend
echo "Waiting for the backend to start..."
sleep 10
STATUS=\$(docker compose -f docker-compose.yml ps --status running --format '{{.Name}}' backend || true)
if [ -z "\$STATUS" ]; then
echo "ERROR: backend is not running"
docker compose -f docker-compose.yml ps
docker compose -f docker-compose.yml logs --tail=50 backend || true
exit 1
fi
echo "✓ backend is running (\$STATUS)"
BE_CONTAINER=\$(docker compose -f docker-compose.yml ps -q backend)
if docker exec "\$BE_CONTAINER" test -f /var/www/.env; then
echo "✓ Backend .env file exists"
else
echo "WARNING: Backend .env file not found"
fi
API_PORT=\$(grep -E '^API_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true)
API_PORT=\${API_PORT:-8080}
for i in 1 2 3 4 5 6; do
if curl -sf "http://127.0.0.1:\${API_PORT}/health" > /dev/null; then
echo "✓ backend health check passed (http://127.0.0.1:\${API_PORT}/health)"
echo "✓ Deployed backend IMAGE_TAG=${IMAGE_TAG}"
exit 0
fi
echo "Waiting for backend... attempt \${i}/6"
sleep 5
done
echo "ERROR: backend health check failed (http://127.0.0.1:\${API_PORT}/health)"
docker compose -f docker-compose.yml logs --tail=50 backend || true
exit 1
EOF
@@ -141,6 +141,10 @@ class ActivityServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -116,6 +116,10 @@ class ActivityLogServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -38,14 +38,6 @@ class VerifyEmailNotification extends Notification
protected function verificationUrl(User $notifiable): string
{
$expireMinutes = (int) config('auth.verification.expire_minutes', 60);
$root = rtrim((string) config('app.url'), '/');
// Prefer APP_URL over the current request host so queued/local proxy
// contexts do not embed http://localhost in production emails.
URL::forceRootUrl($root);
if ($scheme = parse_url($root, PHP_URL_SCHEME)) {
URL::forceScheme($scheme);
}
return URL::temporarySignedRoute(
'verification.verify',
@@ -127,6 +127,10 @@ class AuthServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -126,6 +126,10 @@ class DashboardServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -113,6 +113,10 @@ class ExternalSystemServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -117,6 +117,10 @@ class FeedbackServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -143,6 +143,10 @@ class MembershipApplicationServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -111,6 +111,10 @@ class NotificationServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -117,6 +117,10 @@ class RoleServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
@@ -179,6 +179,10 @@ class UserServiceProvider extends ServiceProvider
*/
protected function merge_config_from(string $path, string $key): void
{
if ($this->app->configurationIsCached()) {
return;
}
$existing = config($key, []);
$module_config = require $path;
+1 -1
View File
@@ -17,7 +17,7 @@
"laravel/horizon": "^5.36",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.10.1",
"maatwebsite/excel": "^3.1",
"maatwebsite/excel": "^4.0",
"nwidart/laravel-modules": "^12.0",
"spatie/browsershot": "*",
"spatie/laravel-activitylog": "^4.10",
Generated
+154 -200
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d542997c95e6b07d8e6db6e796b81d4f",
"content-hash": "a932ee899bc7a8724f347b219b440ac8",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -268,16 +268,16 @@
},
{
"name": "composer/semver",
"version": "3.4.4",
"version": "3.5.2",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
"reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
"url": "https://api.github.com/repos/composer/semver/zipball/9dfce66f1e97360ea5e6d3e38ab14277e4cc591e",
"reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e",
"shasum": ""
},
"require": {
@@ -319,7 +319,7 @@
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"description": "Version comparison library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
@@ -329,7 +329,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.4"
"source": "https://github.com/composer/semver/tree/3.5.2"
},
"funding": [
{
@@ -341,7 +341,7 @@
"type": "github"
}
],
"time": "2025-08-20T19:15:30+00:00"
"time": "2026-10-22T15:37:26+00:00"
},
{
"name": "dasprid/enum",
@@ -969,67 +969,6 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.19.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
},
"time": "2025-10-17T16:34:55+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.4.0",
@@ -2947,30 +2886,38 @@
},
{
"name": "maatwebsite/excel",
"version": "3.1.69",
"version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760"
"reference": "90b012ef8680e0f0e20208ce7f05400a798b1c0b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760",
"reference": "ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/90b012ef8680e0f0e20208ce7f05400a798b1c0b",
"reference": "90b012ef8680e0f0e20208ce7f05400a798b1c0b",
"shasum": ""
},
"require": {
"composer/semver": "^3.3",
"ext-json": "*",
"illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0||^13.0",
"php": "^7.0||^8.0",
"phpoffice/phpspreadsheet": "^1.30.4",
"psr/simple-cache": "^1.0||^2.0||^3.0"
"composer/semver": "^3.4",
"illuminate/support": "^12.0 || ^13.0",
"php": "^8.3",
"phpoffice/phpspreadsheet": "^5.9",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"laravel/scout": "^7.0||^8.0||^9.0||^10.0||^11.0",
"orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0||^11.0",
"predis/predis": "^1.1"
"brianium/paratest": "^7.20",
"driftingly/rector-laravel": "^2.5",
"ext-sqlite3": "*",
"larastan/larastan": "^3.10",
"laravel/pint": "^1.29",
"laravel/scout": "^10.25 || ^11.2",
"orchestra/testbench": "^10.11 || ^11.1",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-mockery": "^2.0",
"phpunit/phpunit": "^12.5 || ~13.1.14",
"predis/predis": "^2.3 || ^3.0",
"rector/rector": "^2.4.2"
},
"type": "library",
"extra": {
@@ -3012,7 +2959,7 @@
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.69"
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/4.0.6"
},
"funding": [
{
@@ -3024,7 +2971,7 @@
"type": "github"
}
],
"time": "2026-04-30T20:03:58+00:00"
"time": "2026-10-23T11:35:56+00:00"
},
{
"name": "maennchen/zipstream-php",
@@ -3646,20 +3593,19 @@
},
{
"name": "nikic/php-parser",
"version": "v5.7.0",
"version": "v5.9.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
"reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def",
"reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*",
"php": ">=7.4"
@@ -3698,9 +3644,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0"
},
"time": "2025-12-06T11:56:16+00:00"
"time": "2026-09-13T18:51:52+00:00"
},
{
"name": "nunomaduro/termwind",
@@ -3949,20 +3895,25 @@
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
"version": "2.2.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
"reference": "ee15a5a2022c8383906b225055e9496a6a5e6e3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/ee15a5a2022c8383906b225055e9496a6a5e6e3b",
"reference": "ee15a5a2022c8383906b225055e9496a6a5e6e3b",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.8",
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "^4.25"
},
"type": "library",
"extra": {
@@ -3986,7 +3937,7 @@
}
],
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
"homepage": "http://www.phpdoc.org",
"homepage": "https://www.phpdoc.org",
"keywords": [
"FQSEN",
"phpDocumentor",
@@ -3996,9 +3947,9 @@
],
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.2.1"
},
"time": "2020-06-27T09:03:43+00:00"
"time": "2025-11-10T21:21:55+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
@@ -4067,16 +4018,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "2.0.0",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9"
"reference": "dbe2133bb23e2e33a6834f4ed417f24a1b167769"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9",
"reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dbe2133bb23e2e33a6834f4ed417f24a1b167769",
"reference": "dbe2133bb23e2e33a6834f4ed417f24a1b167769",
"shasum": ""
},
"require": {
@@ -4119,22 +4070,22 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/2.1.0"
},
"time": "2026-01-06T21:53:42+00:00"
"time": "2026-09-27T19:32:54+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.30.5",
"version": "5.10.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "97bcabd32a64924688487dcd64aceaf158affb5c"
"reference": "eb18727acf6b1f4cc67145a52ab04f9fd4c28d53"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/97bcabd32a64924688487dcd64aceaf158affb5c",
"reference": "97bcabd32a64924688487dcd64aceaf158affb5c",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/eb18727acf6b1f4cc67145a52ab04f9fd4c28d53",
"reference": "eb18727acf6b1f4cc67145a52ab04f9fd4c28d53",
"shasum": ""
},
"require": {
@@ -4142,6 +4093,7 @@
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
@@ -4152,30 +4104,32 @@
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.15",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": ">=7.4.0 <8.5.0",
"php": "^8.2",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"doctrine/instantiator": "^1.5",
"dompdf/dompdf": "^1.0 || ^2.0 || ^3.0",
"dompdf/dompdf": "^2.0 || ^3.0",
"ext-intl": "*",
"ext-openssl": "*",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mitoteam/jpgraph": "^10.5",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"phpstan/phpstan": "^1.1 || ^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": "^10.5 || ^11.0",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
"tecnickcom/tcpdf": ">=6.8.0 <7.0.0"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard and StringHelper::setLocale()",
"ext-openssl": "Handline Agile-encrypted Xlsx spreadsheets",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
@@ -4227,9 +4181,9 @@
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.5"
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.10.0"
},
"time": "2026-05-31T05:13:11+00:00"
"time": "2026-09-17T03:54:50+00:00"
},
{
"name": "phpoption/phpoption",
@@ -4308,16 +4262,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "2.3.2",
"version": "2.3.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
"reference": "1427af4647235b4a73b13940b1f1258d3584f36e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
"reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1427af4647235b4a73b13940b1f1258d3584f36e",
"reference": "1427af4647235b4a73b13940b1f1258d3584f36e",
"shasum": ""
},
"require": {
@@ -4349,9 +4303,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.6"
},
"time": "2026-01-25T14:56:51+00:00"
"time": "2026-09-27T20:07:45+00:00"
},
{
"name": "pragmarx/google2fa",
@@ -5799,16 +5753,16 @@
},
{
"name": "symfony/console",
"version": "v7.4.13",
"version": "v7.4.21",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217"
"reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217",
"reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217",
"url": "https://api.github.com/repos/symfony/console/zipball/21f1bc2296f7ff6043241a8dcbc09559b3732820",
"reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820",
"shasum": ""
},
"require": {
@@ -5873,7 +5827,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.4.13"
"source": "https://github.com/symfony/console/tree/v7.4.21"
},
"funding": [
{
@@ -5893,7 +5847,7 @@
"type": "tidelift"
}
],
"time": "2026-05-24T08:56:14+00:00"
"time": "2026-10-12T18:45:38+00:00"
},
{
"name": "symfony/css-selector",
@@ -5966,16 +5920,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.7.0",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
"reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
"reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
@@ -6013,7 +5967,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -6033,7 +5987,7 @@
"type": "tidelift"
}
],
"time": "2026-04-13T15:52:40+00:00"
"time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/error-handler",
@@ -6205,16 +6159,16 @@
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v3.7.0",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32"
"reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32",
"reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e",
"reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e",
"shasum": ""
},
"require": {
@@ -6261,7 +6215,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0"
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -6281,7 +6235,7 @@
"type": "tidelift"
}
],
"time": "2026-01-05T13:30:16+00:00"
"time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/finder",
@@ -6810,16 +6764,16 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.38.1",
"version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "e9247d281d694a5120554d9afaf54e070e88a603"
"reference": "5cac8f4db97208823aa353f59a01faad2da52c75"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603",
"reference": "e9247d281d694a5120554d9afaf54e070e88a603",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75",
"reference": "5cac8f4db97208823aa353f59a01faad2da52c75",
"shasum": ""
},
"require": {
@@ -6868,7 +6822,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1"
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0"
},
"funding": [
{
@@ -6888,7 +6842,7 @@
"type": "tidelift"
}
],
"time": "2026-05-26T05:58:03+00:00"
"time": "2026-09-25T15:46:47+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
@@ -6979,16 +6933,16 @@
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.38.0",
"version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b"
"reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b",
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b",
"reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b",
"shasum": ""
},
"require": {
@@ -7040,7 +6994,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0"
},
"funding": [
{
@@ -7060,20 +7014,20 @@
"type": "tidelift"
}
],
"time": "2026-05-25T13:48:31+00:00"
"time": "2026-09-23T22:02:09+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.38.2",
"version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
"reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72",
"reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72",
"shasum": ""
},
"require": {
@@ -7125,7 +7079,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0"
},
"funding": [
{
@@ -7145,20 +7099,20 @@
"type": "tidelift"
}
],
"time": "2026-05-27T06:59:30+00:00"
"time": "2026-09-24T15:19:04+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.37.0",
"version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
"reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74",
"reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74",
"shasum": ""
},
"require": {
@@ -7209,7 +7163,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0"
},
"funding": [
{
@@ -7229,7 +7183,7 @@
"type": "tidelift"
}
],
"time": "2026-04-10T16:19:22+00:00"
"time": "2026-09-25T15:50:05+00:00"
},
{
"name": "symfony/polyfill-php83",
@@ -7393,16 +7347,16 @@
},
{
"name": "symfony/polyfill-php85",
"version": "v1.38.1",
"version": "v1.43.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php85.git",
"reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1"
"reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1",
"reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6",
"reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6",
"shasum": ""
},
"require": {
@@ -7449,7 +7403,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1"
"source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0"
},
"funding": [
{
@@ -7469,7 +7423,7 @@
"type": "tidelift"
}
],
"time": "2026-05-26T02:25:22+00:00"
"time": "2026-09-25T15:46:47+00:00"
},
{
"name": "symfony/polyfill-uuid",
@@ -7972,16 +7926,16 @@
},
{
"name": "symfony/service-contracts",
"version": "v3.7.0",
"version": "v3.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a"
"reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a",
"reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257",
"reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257",
"shasum": ""
},
"require": {
@@ -8035,7 +7989,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.7.0"
"source": "https://github.com/symfony/service-contracts/tree/v3.7.3"
},
"funding": [
{
@@ -8055,20 +8009,20 @@
"type": "tidelift"
}
],
"time": "2026-03-28T09:44:51+00:00"
"time": "2026-07-27T15:39:01+00:00"
},
{
"name": "symfony/string",
"version": "v8.1.0",
"version": "v8.1.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9"
"reference": "07fb208558f941eab50ec49c89c2a6a93fb11f01"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9",
"reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9",
"url": "https://api.github.com/repos/symfony/string/zipball/07fb208558f941eab50ec49c89c2a6a93fb11f01",
"reference": "07fb208558f941eab50ec49c89c2a6a93fb11f01",
"shasum": ""
},
"require": {
@@ -8125,7 +8079,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.1.0"
"source": "https://github.com/symfony/string/tree/v8.1.9"
},
"funding": [
{
@@ -8145,7 +8099,7 @@
"type": "tidelift"
}
],
"time": "2026-05-29T05:06:50+00:00"
"time": "2026-10-03T15:16:03+00:00"
},
{
"name": "symfony/translation",
@@ -8242,16 +8196,16 @@
},
{
"name": "symfony/translation-contracts",
"version": "v3.7.0",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
"reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d"
"reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d",
"reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621",
"reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621",
"shasum": ""
},
"require": {
@@ -8300,7 +8254,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/translation-contracts/tree/v3.7.0"
"source": "https://github.com/symfony/translation-contracts/tree/v3.7.1"
},
"funding": [
{
@@ -8320,7 +8274,7 @@
"type": "tidelift"
}
],
"time": "2026-01-05T13:30:16+00:00"
"time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/type-info",
@@ -9084,16 +9038,16 @@
},
{
"name": "webmozart/assert",
"version": "2.4.1",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
"reference": "8a163706bbcac613733dba03f51e13b5eb1bb819"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/8a163706bbcac613733dba03f51e13b5eb1bb819",
"reference": "8a163706bbcac613733dba03f51e13b5eb1bb819",
"shasum": ""
},
"require": {
@@ -9144,9 +9098,9 @@
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/2.4.1"
"source": "https://github.com/webmozarts/assert/tree/2.5.0"
},
"time": "2026-06-15T15:31:57+00:00"
"time": "2026-10-05T23:14:19+00:00"
},
{
"name": "wikimedia/composer-merge-plugin",
@@ -11659,5 +11613,5 @@
"php": "^8.2"
},
"platform-dev": {},
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.6.0"
}
+2 -2
View File
@@ -4,11 +4,11 @@ services:
# amd64 emulation avoids linux_arm + linux64 chrome-headless-shell mismatch.
platform: linux/amd64
build:
context: "./vendor/laravel/sail/runtimes/8.4"
context: "./vendor/laravel/sail/runtimes/8.5"
dockerfile: Dockerfile
args:
WWWGROUP: "${WWWGROUP}"
image: "sail-8.4/app"
image: "sail-8.5/app"
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
+10 -11
View File
@@ -1,5 +1,5 @@
# Stage 1: Build environment and Composer dependencies
FROM php:8.4-fpm AS builder
FROM php:8.5.12-fpm AS builder
LABEL maintainer="Topaz"
@@ -22,7 +22,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
pdo_mysql \
pdo_pgsql \
pgsql \
opcache \
intl \
zip \
bcmath \
@@ -37,7 +36,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
WORKDIR /var/www
# Copy the entire Laravel application code into the container
COPY be/ /var/www
COPY . /var/www
# Copy Composer from official image (avoids flaky getcomposer.org in CI)
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
@@ -56,7 +55,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
unzip \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
COPY be/package.json be/package-lock.json ./
COPY package.json package-lock.json ./
ENV PUPPETEER_SKIP_DOWNLOAD=true \
PUPPETEER_CACHE_DIR=/var/www/.cache/puppeteer
@@ -66,7 +65,7 @@ RUN npm ci --omit=dev
RUN npx puppeteer browsers install chrome-headless-shell
# Stage 2: Unified production image with PHP-FPM + Nginx
FROM php:8.4-fpm AS base
FROM php:8.5.12-fpm AS base
LABEL maintainer="Topaz"
@@ -135,11 +134,11 @@ RUN curl -o /usr/local/bin/php-fpm-healthcheck \
&& chmod +x /usr/local/bin/php-fpm-healthcheck
# Copy the initialization script
COPY be/docker/common/unified/entrypoint.sh /usr/local/bin/entrypoint.sh
COPY docker/common/unified/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Copy supervisor configuration files
COPY be/docker/common/unified/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
COPY docker/common/unified/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
# Create supervisor conf.d directory
RUN mkdir -p /etc/supervisor/conf.d
@@ -149,11 +148,11 @@ RUN mkdir -p /etc/supervisor/conf.d
# by mounting files into:
# - /etc/nginx/nginx.conf
# - /etc/supervisor/conf.d/
COPY be/docker/common/unified/supervisor/unified.conf /etc/supervisor/conf.d/unified.conf
COPY be/docker/common/unified/nginx/nginx.conf /etc/nginx/nginx.conf
COPY docker/common/unified/supervisor/unified.conf /etc/supervisor/conf.d/unified.conf
COPY docker/common/unified/nginx/nginx.conf /etc/nginx/nginx.conf
# Copy the initial storage structure
COPY be/storage /var/www/storage-init
COPY storage /var/www/storage-init
# Copy PHP extensions and libraries from the builder stage
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
@@ -164,7 +163,7 @@ COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Custom PHP overrides (upload limits, etc.) — official image loads conf.d/*.ini
COPY be/docker/common/unified/php/uploads.ini /usr/local/etc/php/conf.d/uploads.ini
COPY docker/common/unified/php/uploads.ini /usr/local/etc/php/conf.d/uploads.ini
# Enable PHP-FPM status page via separate config file (avoid modifying zz-docker.conf)
RUN echo '[www]' > /usr/local/etc/php-fpm.d/zzz-status.conf && \
+70
View File
@@ -0,0 +1,70 @@
# Stateful stack. Deploy this only when Postgres or Redis should change.
# The backend reaches these services by the network aliases db and redis.
services:
db:
image: postgres:17
container_name: mykopkb-postgres-production
restart: unless-stopped
ports:
- "${DB_PORT:-5433}:5432"
environment:
- POSTGRES_USER=${DB_USERNAME}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_DATABASE}
volumes:
- db-data-production:/var/lib/postgresql/data
- ./docker/postgres/conf.d:/etc/postgres/conf.d:ro
networks:
mykopkb-production-network:
aliases:
- db
healthcheck:
test:
[
"CMD-SHELL",
'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" || exit 1',
]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:8
container_name: mykopkb-redis-production
restart: unless-stopped
volumes:
- redis-data-production:/data
- ./redis-config/redis.conf:/usr/local/etc/redis/redis.conf:ro
command: redis-server /usr/local/etc/redis/redis.conf
networks:
mykopkb-production-network:
aliases:
- redis
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
mykopkb-production-network:
name: mykopkb-production-network
external: true
volumes:
db-data-production:
name: mykopkb-db-data-production
redis-data-production:
name: mykopkb-redis-data-production
@@ -1,3 +1,4 @@
# Application stack. Postgres and Redis live in docker-compose.data.yml.
services:
backend:
image: git.koppkb.com/kopkb/my-kopkb-be:${IMAGE_TAG:-latest}
@@ -14,15 +15,11 @@ services:
- SESSION_DRIVER=redis
- QUEUE_CONNECTION=redis
volumes:
- app-storage-production:/var/www/storage
- app-logs-production:/var/www/storage/logs
- /var/lib/mykopkb/storage:/var/www/storage
- ./.env:/var/www/.env:ro
- ${NGINX_CONF:-./be/docker/production/nginx/nginx.conf}:/etc/nginx/nginx.conf:ro
- ${NGINX_CONF:-./docker/production/nginx/nginx.conf}:/etc/nginx/nginx.conf:ro
networks:
- mykopkb-production-network
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
@@ -35,89 +32,7 @@ services:
max-size: "10m"
max-file: "3"
frontend:
image: git.koppkb.com/kopkb/my-kopkb-fe:${IMAGE_TAG:-latest}
container_name: mykopkb-fe-production
restart: unless-stopped
ports:
- "${FE_PORT:-8081}:80"
networks:
- mykopkb-production-network
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:17
container_name: mykopkb-postgres-production
restart: unless-stopped
ports:
- "${DB_PORT:-5433}:5432"
environment:
- POSTGRES_USER=${DB_USERNAME}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_DATABASE}
volumes:
- db-data-production:/var/lib/postgresql/data
- ./be/docker/postgres/conf.d:/etc/postgres/conf.d:ro
networks:
- mykopkb-production-network
healthcheck:
test:
[
"CMD-SHELL",
'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" || exit 1',
]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:8
container_name: mykopkb-redis-production
restart: unless-stopped
volumes:
- redis-data-production:/data
- ./redis-config/redis.conf:/usr/local/etc/redis/redis.conf:ro
command: redis-server /usr/local/etc/redis/redis.conf
networks:
- mykopkb-production-network
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
mykopkb-production-network:
driver: bridge
volumes:
app-storage-production:
driver: local
app-logs-production:
driver: local
db-data-production:
driver: local
redis-data-production:
driver: local
name: mykopkb-production-network
external: true
+2 -2
View File
@@ -2,7 +2,7 @@ services:
app:
build:
context: .
dockerfile: ./be/docker/common/unified/Dockerfile
dockerfile: ./docker/common/unified/Dockerfile
args:
ENV: staging
platforms:
@@ -87,7 +87,7 @@ services:
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres-data-staging:/var/lib/postgres
- ./be/docker/postgres/conf.d:/etc/postgres/conf.d:ro
- ./docker/postgres/conf.d:/etc/postgres/conf.d:ro
networks:
- sutera-staging-network
healthcheck: