From 19d00a08e2217167918aa7c32c056c1a70f22021 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 23 Dec 2025 10:59:06 +0100 Subject: [PATCH 01/59] fix relative paths not being resolved correctly by spc (#2093) closes #2092 closes #2064 --- build-static.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/build-static.sh b/build-static.sh index 3f3efc3354..c22d53f017 100755 --- a/build-static.sh +++ b/build-static.sh @@ -178,7 +178,11 @@ fi # Embed PHP app, if any if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then - SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app=${EMBED}" + if [[ "${EMBED}" != /* ]]; then + EMBED="${CURRENT_DIR}/${EMBED}" + fi + # shellcheck disable=SC2089 + SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app='${EMBED}'" fi SPC_OPT_INSTALL_ARGS="go-xcaddy" @@ -204,7 +208,7 @@ done # shellcheck disable=SC2086 ${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS} export FRANKENPHP_SOURCE_PATH="${CURRENT_DIR}" -# shellcheck disable=SC2086 +# shellcheck disable=SC2086,SC2090 ${spcCommand} build --enable-zts --build-embed --build-frankenphp ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" if [ -n "$CI" ]; then From 14c7db1cd068baed21921a73d608a7b3f718dd00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 24 Dec 2025 17:33:15 +0100 Subject: [PATCH 02/59] docs: add hot reload docs (#2094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Dunglas Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 1 + docs/config.md | 8 ++- docs/extensions.md | 28 ++++----- docs/hot-reload.md | 139 ++++++++++++++++++++++++++++++++++++++++++++ docs/hot-reload.png | Bin 0 -> 395441 bytes docs/worker.md | 2 + 6 files changed, 161 insertions(+), 17 deletions(-) create mode 100644 docs/hot-reload.md create mode 100644 docs/hot-reload.png diff --git a/README.md b/README.md index a7553fd4d4..d2a7297433 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Go to `https://localhost`, and enjoy! - [Worker mode](https://frankenphp.dev/docs/worker/) - [Early Hints support (103 HTTP status code)](https://frankenphp.dev/docs/early-hints/) - [Real-time](https://frankenphp.dev/docs/mercure/) +- [Hot reloading](https://frankenphp.dev/docs/hot-reload/) - [Efficiently Serving Large Static Files](https://frankenphp.dev/docs/x-sendfile/) - [Configuration](https://frankenphp.dev/docs/config/) - [Writing PHP Extensions in Go](https://frankenphp.dev/docs/extensions/) diff --git a/docs/config.md b/docs/config.md index 65bd5354a6..a600c23b5e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -213,8 +213,10 @@ This is useful for development environments. } ``` -If the `watch` directory is not specified, it will fall back to `./**/*.{php,yaml,yml,twig,env}`, -which watches all `.php`, `.yaml`, `.yml`, `.twig` and `.env` files in the directory and subdirectories +This feature is often used in combination with [hot reload](hot-reload.md). + +If the `watch` directory is not specified, it will fall back to `./**/*.{env,php,twig,yaml,yml}`, +which watches all `.env`, `.php`, `.twig`, `.yaml` and `.yml` files in the directory and subdirectories where the FrankenPHP process was started. You can instead also specify one or more directories via a [shell filename pattern](https://pkg.go.dev/path/filepath#Match): @@ -239,7 +241,7 @@ where the FrankenPHP process was started. You can instead also specify one or mo The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher). -## Matching the worker to a path +## Matching the Worker To a Path In traditional PHP applications, scripts are always placed in the public directory. This is also true for worker scripts, which are treated like any other PHP script. diff --git a/docs/extensions.md b/docs/extensions.md index 30f434eb58..0dbc020ba9 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -88,20 +88,20 @@ While some variable types have the same memory representation between C/PHP and This table summarizes what you need to know: | PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support | -|--------------------|-------------------------------|-------------------|-----------------------------------|------------------------------------|-----------------------| -| `int` | `int64` | ✅ | - | - | ✅ | -| `?int` | `*int64` | ✅ | - | - | ✅ | -| `float` | `float64` | ✅ | - | - | ✅ | -| `?float` | `*float64` | ✅ | - | - | ✅ | -| `bool` | `bool` | ✅ | - | - | ✅ | -| `?bool` | `*bool` | ✅ | - | - | ✅ | -| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ | -| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ | -| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ | -| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ | -| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ | -| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | -| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ | +| ------------------ | ----------------------------- | ----------------- | --------------------------------- | ---------------------------------- | --------------------- | +| `int` | `int64` | ✅ | - | - | ✅ | +| `?int` | `*int64` | ✅ | - | - | ✅ | +| `float` | `float64` | ✅ | - | - | ✅ | +| `?float` | `*float64` | ✅ | - | - | ✅ | +| `bool` | `bool` | ✅ | - | - | ✅ | +| `?bool` | `*bool` | ✅ | - | - | ✅ | +| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ | +| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ | +| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ | +| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ | +| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ | +| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | +| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ | > [!NOTE] > diff --git a/docs/hot-reload.md b/docs/hot-reload.md new file mode 100644 index 0000000000..9219461563 --- /dev/null +++ b/docs/hot-reload.md @@ -0,0 +1,139 @@ +# Hot Reload + +FrankenPHP includes a built-in **hot reload** feature designed to vastly improve the developer experience. + +![Mercure](hot-reload.png) + +This feature provides a workflow similar to **Hot Module Replacement (HMR)** found in modern JavaScript tooling (like Vite or webpack). +Instead of manually refreshing the browser after every file change (PHP code, templates, JavaScript and CSS files...), +FrankenPHP updates the content in real-time. + +Hot Reload natively works with WordPress, Laravel, Symfony, and any other PHP application or framework. + +When enabled, FrankenPHP watches your current working directory for filesystem changes. +When a file is modified, it pushes a [Mercure](mercure.md) update to the browser. + +Depending on your setup, the browser will either: + +- **Morph the DOM** (preserving scroll position and input state) if [Idiomorph](https://github.com/bigskysoftware/idiomorph) is loaded. +- **Reload the page** (standard live reload) if Idiomorph is not present. + +## Configuration + +To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`. + +> [!WARNING] +> This feature is intended for **development environments only**. +> Do not enable `hot_reload` in production, as watching the filesystem incurs performance overhead and exposes internal endpoints. + +```caddyfile +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload +} +``` + +By default, FrankenPHP will watch all files in the current working directory matching this glob pattern: `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}` + +It's possible to explicitly set the files to watch using the glob syntax: + +```caddyfile +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload src/**/*{.php,.js} config/**/*.yaml +} +``` + +Use the long form to specify the Mercure topic to use as well as which directories or files to watch by providing paths to the `hot_reload` option: + +```caddyfile +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload { + topic hot-reload-topic + watch src/**/*.php + watch assets/**/*.{ts,json} + watch templates/ + watch public/css/ + } +} +``` + +## Client-Side Integration + +While the server detects changes, the browser needs to subscribe to these events to update the page. +FrankenPHP exposes the Mercure Hub URL to use for subscribing to file changes via the `$_SERVER['FRANKENPHP_HOT_RELOAD']` environment variable. + +A convenience JavaScript library, [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload), is also available to handle the client-side logic. +To use it, add the following to your main layout: + +```php + +FrankenPHP Hot Reload + + + + + +``` + +The library will automatically subscribe to the Mercure hub, fetch the current URL in the background when a file change is detected and morph the DOM. +It is available as a [npm](https://www.npmjs.com/package/frankenphp-hot-reload) package and on [GitHub](https://github.com/dunglas/frankenphp-hot-reload). + +Alternatively, you can implement your own client-side logic by subscribing directly to the Mercure hub using the `EventSource` native JavaScript class. + +### Worker Mode + +If you are running your application in [Worker Mode](https://frankenphp.dev/docs/worker/), your application script remains in memory. +This means changes to your PHP code will not be reflected immediately, even if the browser reloads. + +For the best developer experience, you should combine `hot_reload` with [the `watch` sub-directive in the `worker` directive](config.md#watching-for-file-changes). + +- `hot_reload`: refreshes the **browser** when files change +- `worker.watch`: restarts the worker when files change + +```caddy +localhost + +mercure { + anonymous +} + +root public/ +php_server { + hot_reload + worker { + file /path/to/my_worker.php + watch + } +} +``` + +### How it works + +1. **Watch**: FrankenPHP monitors the filesystem for modifications using [the `e-dant/watcher` library](https://github.com/e-dant/watcher) under the hood (we contributed the Go binding). +2. **Restart (Worker Mode)**: if `watch` is enabled in the worker config, the PHP worker is restarted to load the new code. +3. **Push**: a JSON payload containing the list of changed files is sent to the built-in [Mercure hub](https://mercure.rocks). +4. **Receive**: The browser, listening via the JavaScript library, receives the Mercure event. +5. **Update**: + +- If **Idiomorph** is detected, it fetches the updated content and morphs the current HTML to match the new state, applying changes instantly without losing state. +- Otherwise, `window.location.reload()` is called to refresh the page. diff --git a/docs/hot-reload.png b/docs/hot-reload.png new file mode 100644 index 0000000000000000000000000000000000000000..7ba2068b68fbad9575f19fd0c9fdc35faf9d62a7 GIT binary patch literal 395441 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

NtNgoOS@DtCZy8RhHRNRj&Xv-qO++n zzw0nmsEXmJFw-$Gm8FTYtqx+ySeEJ%%AfBF6Dx;sY6cJd%Hu>ZFV_R2IKaQ|xInmi6HYION!pA7|>)S(H?aElCw64l|0DzfLwX1u)i zE&YL?bG-%;Mig>+PBR2#eZgg-en^KYb?}K|)CWP+zi~5YW<6;YD`nS^J<;G1>I3P*h_Q=9_c`^RM5Q6gis%4|<{d z4i?&r;DYP-vJLf6U->=OVbtcBocoIF#%xqq`Qv(zCbMF56ShpO#+mR5b4NTeJMZmdgAn zTUEA@F|92E_?%{-Uo3;AGpep&?v;;8YjK@s&ApmcH2vg!BIZXo zh$N-L7AgY~BHq%s@9DzqVtucq*+rf!qdZim4~ox4dZipqKxOMM`h$)U9V;YdhbFy~ zo3rtCD9%$;GAz?RVBwLn&td_a#izl#4$S2ji1~_cUh@&7727st7LHV z=ak-1=c|2ceeBl^GizJJ8$kfR(9hv&UB~)h1x<)A9AfxgEJ@E)J?>N?y>v!yc0L6v z#YJoV3u614HY-Rq~s4WA!krOzG{bL!MqT5p^% zWmE5Pv=<{lXIw$Cm5cbr_YZoqy?Oj{QIp#?o!fAgL z4y6>zbcPV%b0z3oi~}^M$=i>T&7`*(p=CaGZ|~PR^%O@>m)*NUPFoy4TR(0?7ly=P z#D{Xh!Cw4}Pw^Oij5=GbEoVV%2Jc|zZGy3cSM+lD=6rKDdxVKP8+#4P^r)o05L73 zr3Nb$uT&(mth($6#vp#0(s94=#(fN#DVN>1H8VRNc=Vs~w|Sq&_XU8lE`R*f2{^49 z=pwq8;ZMy5!*i1j)i`5j zPF2r7QIdU(ni6Z)bhlf+@#%LVe3f6>pEsIlxt;NTKC8zEgTmd`rp;tR;F%gTKon%) zWa)D5s9GXU6cYv5{rBHX2Uj}fJA$aJ(-EC%EF8INu2|=EIhX&Me=0s(%dp9E;cyxk zFo_2UL{4+KoCi4>WM)mjBJs?)Gits^^!|lnT~l03M5rjz96s9G&!vOvd8?+j@ihO2 z-;eg!igPqZhNLiJl>K`uU^cA4MfG|X2@OTy<+5(otVl_PXs`{MWL^RE-Z^I|6BlqpiTX zjnVbhUojW#qvmSB);22KA+!ul-xr|7!U4|L4aX)RuRSdf+cE=;Pn^Jcn>G?&%zTjSQsZL)B+?L3#Zh~>8! z>lP7@YkDW$0gK!{cb*`tM;A- z$5<=XdXtMw^j?Oh@9=xrP8=&pI&~n2U~m(qawd{B^{1akz0i~o2k~HR*Iqcv_DzX?Xu!e^ShML3D446beo#b zS&7)Zq%dBq`YUh4WDU2-EQa0sEMhP?&GvSzgs{tXD*fCPQCjc(ntjg6DaVU@lk4f_ ztz^|Kh}y0axF?qn6m%$c@I2%`Z=^>v_B=)tbq(DS$f{K*FCUoOZ?0!(hSYUGYCln$ z-_Kwe>J(6!OAj~NQu{ZzH4EOb&K_rVQ#<^n7_U(;;5)0{k-CkyzIlUs59HFTbEYE0 zDi5@%yR5wSqJ}S4UDln1z0rJ%gXw0ihp!qSTTM&B1;oHmaz`13^ zDHig^ok*|oxac7t4agv^t}`2bW5ez#wG_}SX24fVYbjdQJ?hxW3G_wI1kSrX3zGmM z?D;PdDuxByoF2N-LFte|A(_~=9@=G-gQb@8Yl{#}+jnSoT%xTo}O z@QTsJ*@}8P^<7)BZIp`w0=5BHNgve*ZdPc6aYo0lxxCZ5!D_r zx__D!v^EE;eg5#iMQ&2@pa&n+aOQwKvHs^YjYnm>nY%=s)j^N&>B!kM^sJ%6)u#wO zNtF>$8AG?|P*LB$R`VlP<#{f;TqdFS{OZP`_@a!dtXr?`Ep@FapFsRYt1#B%MwNUB z8O{Kj`WjI$2r+~TFdYS=nfX-y(+n6hNdROPnJ{8!7+M;JT;LK_FDMJqq4*QhatdAO zAX?zWz#d;^@kq>|93$)10S7o0RngL(u$h=yyvwNdOr+@hVuI`)+>zgxbyCQ+GL>Si z_HrzgsmjUE`J1DG6?nBn(Qnhr6H>ZNz~N1bMtG&7e!)#K_D^%i6W^$POD1GbBt;SD ztASaUJ6i2i`TDMTXgf(yRnE8}e)m;%{BK~kZ~CfN-(5R7|1Dd$E}U_kTdr*93Vzk5 zfC-GVQ^SS15}fP{W9oH0W}{iL z1-{TL&62&PpkRSyxAG&~&I+OyzY3$@X6qKZ1@{LCI5FK5~6 z0m~o}vC%S4SQDWEK)wnk8WS0?c|PkIkgHl&h!PyU!c~;uKcoJpR34mWaaK;okIf@(Ku?F}BgG_~EkNmHF}cl7tCZg#s|(gO*Y2z%>3MK&ep zWE_nve1JA32WC+N(4dg6Pgo4_pBI+_79PJ##OQ0jx{`s$P!BXE`3f^D?<@?+`jVzN zKyk#wPxT>g5ZMe65}*=d^FZ$jB?I7%2d<{us>k;TLd#UH~n+=ISfSgfobQ2;^EgzWC z=Z|EHBe|(E?eHTx5zv<*7*iUm6w;$vZM!VAh0XY%UyNQGGW{b zPmY#vE@_9%Vn)zDISqA8Dr?)+kB=VDe@`yu5l{_%h95mV?|bu!0y9D!YR!_Ll9o!$ z2K&#TLqcls$8iziB|`)IsgmynDXO7G7ZhJu%HVo;8O=sgt>IHYpe7f`>;pLEa+@1| zn*J#v!O2k_^k}zZbZ)l?@Bt5*impc*%^BJn4k=cO(ii1MwH3^z@@CN=@0z@iCiBk8%Kzb6uUxo%q*If6C-r zo@v}68yE1J4{N0HcNkrS*Z0X^l0Xl5Ubx{bjKz4W!ll2O25RZYYYB!_ zfdP?Y`i)3tDByw9UtT@6-zH;O#zNo!do=`zKf(qh%U|Do=?4Rlu$uFuhjv1jKA&-h z^~uJYuBbzDT1V2#(kxJ822W8|KkxBSC72R3XU8zuO~d_w7>y$vW)%xBq3`O)t0XhL z8l>JPZo~l}=wqVNvJBH;6u%kkyI=cYO-_4Zu0Icw*~;<3|74@gl^B$PX4>_6-a9hg z&#|jz=J#vQL(Kk4i6w7vR}Sxq`!T%BX9!{TU@x4K%bZ&bvKL2N`9ZW=O7<7Id4iC- zTS1TEHYR*{PGE-L1u21O)eDK=wLjw`Etrp0BE2H(>@@>gJLa|jU2~(@TTm2fG(~ks zLsPq3%F~%HJ#=PS(HFk$8xfUrTwbf2c-w8@os0L88DL>8g6Z{CN+wAG6f*kf=NJCB zYcdWmZLO8D!qXAViODo7D>_0;UNzj%!8LbdrsD3ct9*mCqR=Fc=`WtyCo=usAgk|d zkD>7mPj$FXY~n6)X!8Geqage3R2x)EMP?ZWf<&E>5J_VNI&9(GJ=`&yP#kfaHf@bw zKM0Z*qnx$Y(php~FZU|d%$Bek&KJRiVHHv{2n*iOO=KNa&G7O1(lAO~eTG)SNmD{f zlF4ynSP3T$I~hG7Gn28d08wTfzrWvq>$n9e7sdp~q|10pEu$k2mDy2X#__Qfa;uX)`9%k&uw&6_Z~PGh-S& zT6GUxjCSmHxz}BgkarOsc9ZT$bm5+_Q#Z1|kLQUDd-4e|g4o9AVp%L#*}o3v((;n& zUbL<@ToKSs%sc1NJ;@jz(4+jZkG&`pO>s%wxF8zq8cf?;$wyLrCitR+;EFkdzihhD zk8>@t?EmEm1gp;-liF*VTS_J`krUeDgx)#$|%AmVSKC1vq9_BBpCrdMaA zKw@HSS&3uXAG6AbDBaRS-n!UesiFEn`TfJ#I+5MxUyRgRxF>fKS}_;L;Z zz+O*PzW$yL>Aw`Al-yrhMz$Ruy}GC-3a&66WK8uceE;FJ@vn8PVHqdQt*M~jGeA=v zJLiKf#^obM&@uy&L!*CEgU{jZ>3p!-*s#7$x^O`1uc zcSy(Hip)cBq*xI$u*{VMIixm|c>t$DzKDU#sJI5ht5}s=kJ*$n z-p27$Hr;1Ol^1AW*(WQG>e)^4BJ)}d41JdVmHlid~di432-r zfqobuuQBzP8d_{X4{0&NtyERjm?}ZRh|6)0M=&Cdg*ARYqwCqlqqG)3ZXG`ggD= zQuz;%6SwM`Eq3`cXljF#?s#TQR)_Q3l($~_do*u1Ley5HG7FAixvkk>iVYK-Q=Y=@ ziCirkveK6Umy<-!QaQ%_CwCZ)gK+zOQ8cc#7MQ$y_~Ix}p3W_o_H0&RfwMzVBpEWk zO`$V8JER6s7dY^C1`z&CPbw6*ip;F9SrBK3A-MM&aXM=Yri(Sqh`}H>ex~~P;V9dyA&(aIAOLMgfsd<#eTD$luw&S>tNK5^ZBgXTG9B2D`f;v z?!i-KYd$oj8Z^R$RoHOOKqW7qwG}8Y9_$W9yZVa0$rwx;;TqdCr&$Tt*Sy?gwl71c z0dMD{`I543DD0ju+Su_$(e;DYMm`UX{SyszwHBePG}YW{s(dEQ1UGYA2e+S-Wec)g z6N%m~a+BIBR2>#AX)0bz9WFX-bVd6Q+TvvMzVCg`^dFGH6IA&2Paum34CDB>03pI0 zZh_3qhp1Ix3Tf0*T4qGTjy^jsLB>E65~p8vgfdHq5e79DaaT$&X;U&UQZP4rj52-m znI*0_WZPw?D-qTwS8)CM)`X<9h__Su}+RVZq$@fxHS@d*p1$W+I!`?sI ze&O>}d9`cU@MuV9ce=bkr7iLNTL?xE0r+zDY2|KMZQ~^IjKz*F${}lH?U%GPd*-xO z1X5z1C{&u2Ik2@UkPi!&bdihtv6(RvNM^341rLtEV`*~_%8+C| zZduqyzUZ!`tkTKFIw_|D>0lx4@AGS$v;NzwmWk>Y#Ry+S_k74~LXF$(H>!ppO-iR> zHrNve!n%mg=-!5=RqiPPd-| z=Y7YRI765Bc|w*^)1u#d^%YS$;U(+_a(Yvk0djS(ugUK+V@^+_oSe6^iso6$I;33M zXKV6BlatrkI`DAL%ICFv{%g(k zwtr?r+;yc=(&~vbrpbvkZS>t<)`UrW3;0nu8LxR~Qz+T%Ik<{ui8L;zSBkWS|ev8fIZeWMB5eWyXw99-Kl6pKI%sfgPS zmwJ-P0SSN#KUmYOpAx>(gaP zCsj_3jOLBkD9>jo5j!nrV4rd2)RYjpV1_w|kf3??&Xj&!YLsZ?ndsHl^K-f>q_nbT zAUzVH#p+m0(17ww!$p}coY0}RRn(|WcWylXh3V509GmeiHdd0Fk2`PKzrtKofSTuDJfLX=aG-+X8#_k%a!+tSI3vETs6~M#iNBjIhtrqIaIuz8^2|sm$tB_ z2|;PUSjni$|8laquJA`Il+}-x_g)*3z)fuR<;s^)ej0djbvLFMJ046(Z{*mT6_Wv` zD@BgK9$qR48!(&4*Y+mW)}VUm@_JK-&_+)z-+hRK=GSR2Fc)LVzCBx!yLrd_8yuil zJ`$YsX|3VG^R>(DsqH{cZdOf>dV=T3kYp4HPgdRDa`O0^u(J$Qnw;bqN*VnAy`69^ z?L?AdZjax3{s z{H4`Ij&rQN1uyuAs{UQ}A*Bi%Yd(#|O%30pmyE`DU*G;HA1_Y~t|%OHb2Xbi-?5}$ z&hQv;gG5x<$!1b+QPKBrl-44p)$l%M2Ax3?jbR;ORWC_<)?>4$cvIyD&}NKQ0A00; zir27|^LB*`&+Ro(A)Y@CGUojxa#E;qXuXBGmBF52%55Oo#{fBrU#z6|{=3B{(VFX|-U zF-CHVvW!5vxnFVf%`Jh6Q$Tcxxfq!pp{PQEae^WV_rq00L%N8r^u7jSG9@Xu-FS(e zD%OD{V|mU6@qFF$8%?2YE>P7CHTt4(H+{iVb5$gO4IN7r0nYxtpJhn2q)U*;kF7a) z6p>Uz%Y?(iaq-g|gIwwMI^2IefyyAqRMa6Jm-dHg?ZdY=wtmG6KJi&vewVCOYT#j4dpQBYN%&8r>y z=9C)wb7v$_qdOw}Q7Dm&@A((9f&!S6y+L6w$IIp5gVMzv5pQ|^>SuSP@G4lp7=#{g zd_0Kse`tF+*lZL(Rb3Q_c>RFH<-O}*rh;-ptj7XOZtko~f=qH{ib}5v4VHQ6N<3wE z2lM`cpfc(2;Fy62%t0_BKIHsoDkzu2=aHgHy?975{N#YN>NudXxWG1KXLBJ)JXYEB$3I?Aj@x>UyxyFR9ib(OkQJ*>a+`(dRxS9_*Vkk+iYlF#!U9o zRkME$tT@gaN%k5-Mbg5u9AXw%{ME+r{$sFE$eZeKh?sE7hSu|{4K34Jtd_)>L-OJF)JE5zsQt}a_NEe9 zJ<3j-Pw)vT`V`E_p@5${dqq8rG+-pq00b zLs>KxgS5hU{(~OFZdj^rlYAT!_Cw==Qes?x0q-WT|0l7tQ5Efjk7(F|8>a?~5$uPFI??Lf;O zG%XZs+Fd;AUR1Q+-w}y@qi22pWU?SVnefEkneEx>C&i+S_?+=Kb8{(BBz4Z6Wd79W zAnWyRF~=TXE=E%3YWnr?<*#G;6&VmWRrXB$tS}fjfE$Tt#i?WksS^1Of~g9q$%@aj z;uhxll)TX}(#UvBf3GU^e(!}yz%0HptoA17_<39HH8{%HnL3j%ZAmLDbO7ap<-+rP z=)!%lJDgXap{dJ)sbIreGjGm9gw0C;7gJh(w996@&q@&?E7+bn)JV8_ke_fnz5n0Dg%{%*VB7MI=^Cnig6Fxj#of82^-?&r~!kY&6CE zvtLF?5*^a$Q<#$*3#NLqXh4!0%01F(VC7}g!8Bqhxg6bPd_pBZiTgh`1$ zU}Fr7_MYj##_yiAq5c>pMrEk{v?RA~g1sd%jA(fm*r zr^qUcjfJqo3X7v=#MgjWziN2>Zelf0iHU9JAPi_!)k?SSfen?fGiBQ?_)A~POXka} zZtj*ZCe-#pGNMCX$JvOEyn~WV^8C(y@EFeeL{ zsp(pi=UCelZubvOkDg>fDj3u3!@W}R2iR(g=o#suRt)!qFW*7FZp6`gOpUDy>QJ!>VQde(>c z*XJ%c+To>ws~-)w_rUjOJ-e{HqqRKNNS=E8h}AEeyH#(xq*3d-JNceQkI7MOOT`+P zukA-ojUFP@u~dDk5EQ2KKYK0QEs_1tJ|l{RU3AGlBfR35Du&t&9tN7qs?k$Z4~+xX zFG_Ke!!V?rVA^5*oo~&dxsmB{rI3%<&U38R4Jtf&El}RE8-}8BQn>qY` zq+qKtGR2e2vy;xWGj$lCiNVzL0N-A-5H&%u7i?U7(y_lhP_x1vcC67Ir*Ue$-{J1; z|M7H|L2-3mlnw+E2=4Cg?iySghsGtiyE}p465Js`945{NfK) z-0pMtS^HUQ_3jtrb7?H#{OM}0U{Q@U#%0=^n?(}mn54D-&9eXYSwz4W+2EpzquyR6 z{Rs|prQ=a_O)c$|-_GBJZ@Gt8Q^c!!YQDwW1EY%YhVY-PuKSg!g=VbmH-s_yNk>^^ zJm21+#)*->Mw76ypdItRN;KnWYtu5FM3?)ro_Wjw?I1;HQzEfD6zHStm!rM>s5FSSjK9e(<{QZZzG;#lw>Gy=> z?23Elp&PQ?htg}fQbxZ#9mOO#qM{sf5~l;wwrwZt_k9A#;!a3G8;pC7fN1(z)UVVr{+lj=KXa`A z6ypHMvm~cu1C>p!2LVZtw(KC$E=d#9>FLl(`vJRgsaq#gB24*8$&2ZTx7b~_#mI8M z=9jiMr;B3jSkB+|7g&k3pV9^_?uvLuYWB_}=vLH)i%<<;#xrZqBk_XW^qzi8KA!uK z^r$_Y5(}o-h7E#xEu*ZU-;5u{?G)aZKF&?Kael4bhGM~6 z+-^UD132aO-0g7x8jH^;x0pd1OZX&5C`@=6`_;|AoH}Zfp8tbG^M|;m>UnkML{Haks;s|W4Gi_p zd!!v&(t3O;-}Bnu7pk|fx;m@6Q}2~xSG*05s4M8E&gWC+(%bvxe}{jh`8a2Jy|C^2 zOKy2Ncn^YfR!8UTXs$J9?PG(L+J6;KUX8GqmeWktAuE(cQlhecg1VBhM2yYH{&&cJ zM&os7ghpYybkBVD&EeinL%nqgV@v^gI!l1}rQ41QFVfYtLqG3?vOCeWgj1m>f_$;X z72EipV6U*yJF@jLar#)|RV!);q9b@wNv7;2rjyortzQ?ai-9zC)`QEYqiu$e9ZG11igysrM8`bf)RXOUi+D`an>%)vo+r~ zW*x?fW;a|ciqTXBG~+uvg z2HIDYMap`B0#*56=+7N$bOU=5gf!3`64s-xAh{l&yUZiLuaxjX$NXpSVHCe+^nZu{ z?ORZZhFtNbuQsEq{$<@npl$MnhYqmrPmaeVYgO2fxq<~~XL%A+D(XQ>>yKJe=rIng zGG8EGX}QdbdQ)fm6^t$;WIFA==DcV(a!K<*nK4rdo&_5UF*RyF>7UuV_o-NQU*Jca z6u?4GB&Xb}-qRp4|Ab3i!eW>IX4dG~6T0|coMiD3k+8^k%KW*Z|K7uOSByo11O|F3 zEO0^mi?B^0aR!*Xzp!FjL0oUNanKO4u)e@I=i%rR;hJjD%rk~kC;ii%FHm7#3gGZQ z>6x{hnxUU)p}Cc+)b-g+qgIm-IEQUA0d`uvJBFXr_bzSG!ik5EWRfC&aOPf$z)C_kE;XskBC1>B2> zHNtbho$OI`Mi`A81%RTe4QXf7RJw28$;u)r0rt-0(q2_Uk1k{iV z`oD`P1F2)pZ$a|JHDIXB5e?m2xC}!TEr-TZp9HWqK>{QzQRqTiL^W9Fnvl=Jv5U=c zgb8;3*$mMTS~mHUA}EPhzSW8oan9rCnHPQoQ|i|xv4F^{0p9FOcnZGgOMJgBqk+E8 z>$XY>;53tivPYi8q-??_wPj4R;+J7l%aGX|IbWsnot_;ARcpBVhH}kQ(ENAWfA0A{ zv2GK#1@La<*$vBfNzvNaIn*&TW<=IG<)jtBoVN+8xEQTg_4&fjm403H$Ft8Xtm&q~|Z1PhznC>0Xp zpvYMUfU0U#Pj>nC^NU}uZeS=**umrH`41q_W{8~^1SBri0gQm<| z(z!C`^xhl_Wbd{3USQxw|{jzvjYN6l|ziS;T2=O~UT&743Yu4k!3qP`J!Td9) zB071tmNRrXLBbumwVn!`SY<|Bu5~XzHFOkCW_oGZcaq$emmAxjP@1@Ll8r2TEbH8_l77Pl5IjedFG`<9SAq)DyQB1j{$ z!u==Kl{6+`gzz=Zz!TafGSv zm8{MSadz!sY`_TI7Bw_0=jyYi$gXT64S zlqEAbLC$1N|5<2xAisJ%B6zZp&fW{M_GUIO;y%Skc8)Y^Jn6*mgM(!-X7$@8XP zZ?yZ|ytetpZ=J~CmS#&Fm7mM!<5PAa6x_BKxlyS;slO;Jk*3y1B z&gIfQIp$tZKA;VpffCY0jlrtRGWw+@AJSb%Bp`SYJKI>u9_l=VIWv8z36_)p5zhV5 zw$+|*!lWoc!aIRK{m|c=Pu>>R&>?a}jZ6T6_GjgcLe-oz>+pz121s4rdzyDdPKjE; zN@N7G(fW?hHxIdYzUJG^RL045*r}+Jvg)8+CK9-DO`0BchXw2`MnV@U2U`}HrG5|V5nI?^=@d# zc`=-*Co?(PMl>ldE^XS z=P&%OGQI2T-LrA>*&u9RtRvJXyL-5Ib$x8N3Bl*Uo*047)jCKZD(H7z8@>*|fre^; zvkVvk>eda>K;9G(&GJK#I3@*$fib%%*`Ls)l-r&p)ycQol}fqzCBg@G_uffiP2P+Kt;3K}Z3xyktQHcWbQ*P6qs^R*J*g$&v zvcB-H#}zubrv{czUDta`y=S}|6At(1vt|=%q_<`Z#QB(${=Zj3T0)IDiay2-@?KwY z3%-@Tr)hSaj6C?c>B~`?Z!-+(Sj;6|wqR{wE}oUQ)S&xQijcD^aSBM?w5xHM|O!BGc~#+?+m}M6*sfv_DP61&|KBNj`o0`wlt!TjB|a))ju6q74uTtm2a~T`!Zi%q z9i_ho;Fa7f#cgU-9W+8yb6E?%%;3!78#pklmw2KM{pbuAHsyh6dSmBT81w(eS89sx z8+`>Da$H>n3H*4Ab~@9HDTcpGuw_dT5#U3&6G&bhCR8t+{0K!$2j@x}?p#!F0$JdP^0QQTTd z`wlOq>Y!qfLYxTluo0nHGKl8jJejtEDf%N5x~?hpG7Ll?6XJSigm15UlbB@{J?j4Q zrptBdQGL~W>eYW-`J94FTwMX8jvxqwqTr8hsl5qUrlEoGPt8ZEsWPCb>0HxXZ=v9t z@}Ramn*YNcQkgB%Oyzb-6F$dKHlxrPnWz(mxP-(tc1pe^H3h{- z^^Y6Q&eC|LUwAP%#_6mEC(f<^uKiIoS9H(ri`jXL5y;$tcHs51=XfG|TkKSuHYew_ zD&-ep36Fp+4i99I_Nbv{vNJD3YZ_;WPrX@tRz46k3&#`6BNtq6AodZOK`fn&mYeW8 z(c>r^kKlh@pOu6G6)ZGDGt;1AWV0d!ncircH!vLk&v&RD99UeAWaobqJXupVbGqx= zqO1<&mnq^S+&z|DNCF;X*X27?@?~+*kH+t^`dK8GYb3^3NpVa`L(>a->S*#9+0+%MSbR>C`-Q#C%uVi)ng@wy#L$V(;Q;I}u*zUZ{V(#i04Bx%Xe zNQ^~|=PN&iIgWzCEB#dMx|?pe9D9~MCn_V5uARQ|LuuhvoSZ}Va%>woMu075j8fjt|cyW6ZYW6%eqieC$Q*>&$ zbo46%N#_9nYG5+F6shJ0fQn zh-*{hwz~Tqw|U8s%-cHG41b$YJ1i^S$cM#cezwcLdP#LQB1{o67H>py?zDZ`N&k}G z*qZ-=IcHH)mq&2Ok6bOrMjL2S)!L^8_T9xbeST0{cZYJf6^#8@O{1yJ952%0kof(m9l>|hT`TxocJbX8dI}mYP8#qR81#QkbjW3R;(u)MhsA(DLFo`=1<%Hv z#l{09ENj|-b!=@FTrKj9^KFSv%CZxa!F33$dhOd;u1YLJS&4yHSGzf2OrCNj0>Cl+ zo)(zySo69|7x|2zkkkekqpvcwyT^TYH2|GT{i>5kOP<03dS0v=yTHk1=9bqC* ziL-P&VWT1+5-Ii;qpEGL1ulO(p=?gwH+Xh<>|cHX@9JjP;ho4E^h1Q`UMAtw%)rkA z%+PH~4c^;n9jWUzL&xUkW1Wd%YHgE2!unt2G6WwHr^oZO+;BCNJ>E~UkjfDUC&6&P4t&0?~)>vPSQs+T5PjTCn0-dQJ`s}qEk zX286_0nhcs1~GhqH;x5tx5KW=LCOo-PxzCu>RjtUmU zu_CR_?Im=8(KkxN_(g%;IUY#{V5j znbJy&%#dLY1RJ#WY|D9MDo%;+!<7>+Rb$P(fZnFlU~Mc(wL(M3bd>l|Sp_DHC@e0< zS+g77A*YIVqD3<~-X>+|k63O$*R0F-zq!*u8wgzM4+jJ!`NzFvIoUH{8_tB-J4OqOhvS;OZ)dm zEfCLKbJu(gb8}=?T3bHy3{G5cQVSQZNF_QJ*RV#;*?0$Udok>DQ%?8Wg5ocy11p_+ zzkNU3?J;`2_c<5DIvyMbey}D0)u8x+!!_asZ(N+XDIn!%PHxhGlf2pQ`@;cb##?NKLL`Dbr-Mh+0!O3-TYu z)3E^W5l~S+w<&tkup%F|?N1qHa9>gJN9pY# z32&)V{`8j=_{lBG6431!)HN?3`Tm}z4TeOK*I{G^Ec6xHrFXrO@<6r1Yx>ABn^v=L z>0fB-UBq7ZGz~#-wP!S*feAUgX+5jw@0rM(kB!dtgaBc@(xiwxZ9(eVZR3+Q3}Y;L zPur`%vRbb9pb#(g>{A*p8R~@8Sr-xOW>k@kSJue02&dr?KksFA3=)?$ERI`Fw&Rhi zR#$wzA5WL}GzJo*Apna%F=a1frAyLlo0f+)E5zNa?1$tWEiL{1&yK*Uud5s0#P|0L znMI(PKMnP!bMi(~1V_}+w~=KBwP4rJIb>otfi!m;@rT}b=PrOrKFnuluc?y~mz-%` z{juw}@I=hCsM=DG2r^S%21dhaJxvIj8!@qdgN;@RZDIux<-U!jGLm0HoJCd zU;XZ5N=rs_u606;@iqtoB^L+FOztubk_+v$w8nI%pwlZP^4LTW0?u{FWI+~zGD=tj zm*gJ?{C}SAHs?^tC_f1@EY}&bl|!Q|X!AQOu@;oRlh=uos=+<7{~Fr$A#F<i&sNaOGEHZ7Q0Yj)=o0lsby#mtj_K1QY)H>-H20~#KUxW zU}R}Orw&C1`hSVS*piz=Y}D*?c!QvHv0uaV-KQ>^WmUz;E^Nq9xwS0tf^4)~@{tlh zNX@}%vO7~rp2kUY@x!apI{Gao5x0nSKDw&j3G%u+)IaZf?TW}}7#+zuKjpDP4Bx)g z2BR!2yPL`NU#?0~vw}B1SADZe2uS*wLoQXMtyxr}N%|8PDwjw2D!JM8*g`u(TSGtF z8}?JOYxg`Zw`ly5cgY{rr~$yqM+Od(E-&E79hSzxJM8l7q1O0aPJ??sGgs06Ck5J? z@+NE^9=&K51-~x<$6YbGq9Mj|XOro%5xqleoo8i{VmYk%BcxJY&UE08q)9I-t@|V7 zaa8N34=1MxcwYuBf1W}w=?++#7;wtH_c>zSVZ1dOGBQt#<1lQ*%Y?8}iK!7F?a(<=ZE! zWk$c0b-&OLdY(B|j6uwAi|^6*!^)Wy-+M+Dqjt%+1W)||&%4h8!YzzBrDbt-Z4-cE zA_#k3=%=|oDq$Y$;byi>sDIY<7_}uU1{&7_`Bk{#B~$!hD4U~85!!9kqCBDKM!?xm zmaeI*!;F<_vJWno)qZ?i4*P?}ShN_&P&=u{(l0-!@07tnTl=t8vHej?t3EfW|EP;Z z(TOXFhnhY2M8_IGkMFk^dU;gSx0GjP)et`HWB7M1PFQOveq~9J8&#`#Kd8)=hRMb? zv=m`rdw@hgdik$&K{@svUSM~T^!-VtS>5^<39legPUsf4kGN|Twf{@&1Bh-WGjaP~ zi`#2k$(D&w&o2Hbl{rQ=%14qpKbBI}Mn>-y-*qfnt1Ue-+jqXYOB>VA>1K7=Nvzm2 zwO%&eb2$=hTT0eD`!t~*pQNEV<+p%KrCh{jbHVC?)wQS zaWty@H&IzEE&gOxZB|4b8!^mrIuQfIatFv~yg&9S{%+^}A-KQ*9w*jNJ)@!4T`q}) z=6{BK8AS0>5059rMqp{`^~68z(STnvf1 z_vljEkfX-y?dr^MGidieAgZZ_7&J`pE(1~1i()|~dZefrP`L{PH_h6g4_LILGn^XC zHFB*xd`dQ-O0oBy?P4V1KGP|((~-x>RDnNKwR_>bU2XLnQ57z$L!1gR!+R*g#o3Fk z+rd#ou&saZcb0~ayHiQ*!aex6oZjH+3vs`l8`He3lk)36TRABm^KwAlLEHy5m2Jo* z8O`vp63(W&{Kus-*4LhSyRwlg6^I@B5Tw|r*Lax~+aD-oPUCE2CCSt15&PAkWR8_1 zyD<<`)7fj7*%d~afpeBNTmXitzOdL2waZ$l|#9(pX|D}0d zq46%F4)noOD}e?bgv%#cwU6kTAz8mcX(^{-(`9sGAyVN<1jt}WuuI#rLJEK;C;^Xj zf<_|norIFgxJR$x8MO3(=+O09ztV2g6AXAcm`b}WCLC3Bav9zDe6JQgxUzuqI6F=h zdk%%ufdhvdEy}-Rl9H18?Id~NsjuDR2<&u*^?sU_6B!qAox57h)D|5=-ncs@)k`g!Ap6r&^ zQICybp`IVAp2zK+TMbfXji&Hfxg5a=yZl4an0KUXJrZuQY;MfcKk=gedXuK%s#7Y= zAROnN$k0V^J=-EP9*JB$HPmSC_TKA9<~B0UnZcYP_x;0$BzCHYk){&Y_c!d`g0Dc{ zRwwr*o9O|Nc|mckG4*`jlmPL%qjyy*yAMlIYbI;$FFXLUv+bmUf4z{gPpaUI|qL?8LTJiBcN%5KV8Kt%gpoOO(Dg zK$1xI!Q;a{g-A1;O;uX*gt??HKH8!EgV9dFgB2qeyU%)E+vV9KiVa0w)CDvYWR4?O zgOwqVg7A@~s75gO$UmIc`5+6AJ?o(Y_;)bdGIg@56-_exmNFbvK%l|(OZ^p%-R^vX ze$N~r=cin<70b>q&pcZejGCzklc?oWWGhw1RAW=A2R5AhD29>W1e5F`vf0B6l!+N+ zM{4VuvBSBpjDtJY1NfSC!=4c``)s(~LMceSu~Y9^LXoq(?`+gxrO|V;?o%dD%+vKSp@_Au>b^QN zQ;`|ih&j-K=(3H3w6TV)1L;rY)W6)pt?nk6B#=fK9oWu?$);_T4S(XWhx+NBX4c42 z{WQVyFEiNcCR@u9X-#FwJH?bVhl^|Lvrlu=?q|iCo9hDHJMW!OghEZ$9`UQE4E+la zU3d<7&!4J@nvjrH?aahg3x*(FO=jYnj20$4RoEKbZZOj%JW9#m$kkL~_LJ*_*El>p zY0b_12qn1_Gq9-Ev{b>;f=8;`PA$m$R%KWLbo49xoeIx=@F>#R; z-d;Q5emNjFV5=D0j=C(iS5n`E;vTHOInS3_Xm|N5G<)6%*5F+Gdv;EGiX#!hs4LL; z3pxij`#k^s?uKEip!i`eJ&M=+oSH!r7G$pu$rP?tMjQMehK>k>OpKm5$!0X1BQ9|b z`|)eWO9VCJtCNN!;0t?RI#L;+aqg%HD^tRrEl*Y;92JiOEi*50w@iV*%A@|!Fz`wZ z(<*efbfHL(UP+uL(P$V4aJhKx>C(s9FO4J@}gjvHb`9dd%1-qUeE@2OMPWG6lEMl_x?DfVxd}D%hQ?TA-d0WL zgw02o-SyiYbnD9YbzhdTKb4fJo5~SDC$8yBz$BbZ6XTqsxzVqx$C!{;@pp+Y?@PlM zYHKXwI2U9leSbMB@XRDEVB@X1IgLOLfRAO(AJ=Z_MRV1kz48mbk{ z;3xnEdO7ThQVxDaq=q@bI%ms2QNs#E{!`C2K`!P7e%b|5Dz%gI?r{nLE zLmxqk6y@Y=UrxDFeq3O!di!#9{*n3wJYu_^6?O@a0XrG!{f+7H+>E+O%+SOgY^czY zv~c}c&YX?^I#=UYATwqej9rx(ezE4q6`%f|zfn2sUq>LDcgLk_v3yOAtNBOaYL}mN zSg2;|jaRdYYI`*MG8t_t{sk|^-&k@wyt`-+fAa>hLxhsve?&9ku%0&+7A|SerxwH> zw`~sW#b#fhvt@@%+hn9RPT+|TMO9uMK_;4N9IUc1Jk<_m@-_KRlnE5QDTVX)IU;LM zOF@h@MS`*1{;rV-!5rTMqZR|dg}^LCmCS*TPU|InWlYVK$r)R60eVIGRVHFn;eAz# z1&JUy$_B&mq^e=`@*}C%RrRFo5vK@LGn}k;Mor3IRHNy{HN>0$gsa4isXoYJVkOTd zi$_1A8m%rdUBbNn?3LKbPkM*!9v1pYtowj%18J=_YC^j6ZpX%fW{l-e@Us;;JH!6(@fV9x@Kw&jQBR@}eqq(jw z%Gz)fl-p5S6SQw7>lKsgJ$bk3_O|oVoNZ=UQY_k4K;rP!t|UIzeQXlIXl{=?GT;e< z4%5Y&%O!(qf6nZ6OFOiMtV<@c0Avfa^Gk961pt3Pc&lR=z_SY*@|ZPjNJedeg+7KP z*%k3;NihG15tEX2pPS)iQ&Hz}IPujyj~()+oNgz<$6aTJ{J$&!PL5$rdlsSycuZsLhP<{pvy$l1-9feM!u6u4k> z4mzuNx>4q_+QvK6Is2f3wsIs3#ECyTart$pOjcI>kUrgkV9y^2$wnwRbNsgYH_Din zJyGQq36K_l-2Wn$MUO;qx&|O>7>F#=b#CW=7u@mqJ zo{^C+2-^9hmHGCfzqKk(%Wn)>A!r}&+&&1Ptbubmb63+<{i!Z0tQ}m!%*d{T4CT(H ztGn4fan9x#qSL_72-XV#Pm#Kpu57XN;rf)D-(>$JtO#MccZTVUkqRymden_>ui6 zoiIRhywsO{Y53LbE>Ge+eXf=AtrcFP-Vj54&Xa^U`O^*vv>pTWLbt*N%KU-CY4Y{E zT7;-dG2}ZKTUFHaX8G6FlWvenhe@40SF~FhX3UjIS4%HMhW1d!OV9CtvqE#LE2iF$VXL}$S#T-;+n#P>Y5ak++V^;kk>Be(B0IyP-FiZ zmo}t#+lT@0d?=v7QHOI7a8H`kZnlqyRV%;J6Uh`?O{E(QH>)DJdc|y{q=fY1|0cqJ(`l`}!(^moI27~t6Tx3q zYk%tH2hmsF>+g%M%>&DQxDB!`|kPm|047ci8 z5oyqAPKt?$Za^lchQ8?(FktH`Y4n$Pvy_m`M-CEYgh`mB?x&}U{jUfwNF}oS_otXq zO=&vC+SYM#A$SsX~zT|jOfgn(_jFQQTf_moddqxo~ zX0dKV=@0ZJrip)l{uqr_KAz8S!WNLFTD|@g@xV3Tvl_+k)iksUZwi9O11^TAZQ^$R z4A677D|1yaQL~x!ADr4cql~7W%3Bp8xT)2xtgYP{>Lj9(bKtKmw4HRXM1l*NJMB5p zDD)dO}26kg~5bVJ)L zo^GMCGs5XxPNIIFrX4C$TbL;2#%AO%p;s~8EsYa<;pyn zDlr)<_NfFl^;RgQZRoK<>j3eg|0p2>00eq_KU=;WCC9z=$jFV7dX39qKqb)Adwrrq ztSlJ_Cke(!^zKWt`uEu=Hld`EJ2&GntkvULOFZ!SUI(aV>->v;_Ko3IefF_m~gwv zDt8iJJDs?9(&|W=zVL`dQ4`AtO%?xH^yxTV&+oY{uV>FQ58wE@vvV00A|Y`&IPnF zSFZd%f9j3iD-IOT;?0=1C0-erBDAValWzv9M+1+g$tCH^kzlqTj#&Ezzy3tL5tEE> zh5Yf(Ctw!DN}hhMMNGb_GQZ;y?(*Gd?s54y zB73|7g65)R{q8<6c7u+nxF0|GN{H`I%z$f!tO-=S{y(My1}c}uO_eGK%7%p(2liuB z_V!-w;Z^D|GsF%2aB?{RWvo*zUXqFnemcic2{OYT3? zEmaPvJNm2)rflEGi4^&cAJPm?`&O7CbJr^jW?J8GmSqspI$64$tP(JGzZOrZuEjyb z3nCVh!E1!#5@wEN_ItOd%i@c!U*#);%pxM~#2Z;}ytZD}R_6K&kv({#fjbt=lwur& zkQHyI_3cv!rnt-PK%N=-)h`S3#{02&;XBl*&Tx$92+;1CN>#EgU`agQrGw?J^= z;!e6ErMXZ`8JE9`)VBx>gn7k*e3xT3lVM(R);TW?BdUkm*UH7W)o-@`U?}N_GF{HP z^RlJ1CTrtQRYp@F9hkJfmo?3tJ^~g(6-3L77`U z$Un0N_y5KXiX&=Q*f@=Eg%JWtz&*~aMO1zVJ4-pd?j&ts#4vGml_N3-n^#DKZ&3W1 zpW5kxFn&CEt)T)9m$Ez^k#~Rbt+>uY5}utjtm&0My03gYum$z z@DmEQlI;r7ZbBGe13ObWc7~#{wAjB#GYMvOyIvXLe(Cz_M+#wvY!GpYfTsGeyX=AJ zcaaE0=7wJST)U8`T!@^qbQaJd>~5oRhypSu+xf>d1ku5S2Z6AKA_PF26? z?>D*6ZzN^I>!CcXlL%b*bEa!?n3a-qIfZ~s10+jr0ry{8SMFH$7pFLgyM$YjNpfGH zrv%9N*r5TyNoM=h*2R66-|_xD*JApd=L2skI#}ZFIL|s4w#)m!1hh;e(`jC~Htob8 z=+%x7TC z&08l28S^wMwf-;=Nz)(V1f+D<39wT|YWt#m%Mg5wJ_iT0{SRqr{u7Szzs&_VG*Vbk z^QPb#N>Hs$-MWgT8D3@m7*=v22morbk6k#r@&2PkFK^i8{vg9mP64LFD;#pgZZ$&T z{j^#&0#ayfMdY@5u?3xCSYeWpwqU22lu(~=y;l>8Ow>YF-IGZb{{KaOgvWh&f2T9R z+CESX7}#?Uo=aQGuzZ@Q7Aiw8+z8{*pl>S*sCr){`)3fr5RgMtf{fNv+#M=h6IsJn zGrw`C0^1fNqN+2)!p>KPw*mWI?0HX{rLdmijYpD9S~}i{Gm$!M^GhGy&5q#r3esf~ zcv=T{_9o~u-~okUEW+(1Bwc&Hd*zSOVu5St>kpX=`LTy9evNjY+BxlW%K{Yf#r+y0 zrk4Umlj}&@oviPZ^OknRSXbkGRjrQ=(M_0OVxI<#T_y!j&vKy+^x`Y6hP~7r=cF}% zo;XGNSQ~BswQt`uu31^McyGYa%@q#W`K&{w1cW{Jt?2Sbu6jDfAJ(W08jmbPl-RsH z$5u5K4k`-17K5cID|%0gb3TNoI7WAa)O3>lI+GPqQP|GtSC$G8=1~m9A;z&xH!d8EcKu%X@84uWW?9ofFG)nT1zs*TXEy{4j#=_a{u(N@i$V zNm?rh^D6-JBwMzV^jt#gxD}6wglYaxEUZ}qd_}e(a^3h@{cxldpZlR@9|bV?DWJB& z&((mW_7WAMFC9sAPu4;q0?KKbsT0$eGPt+zXE15%f=(Bx=*%>$*2bA31;d)&RK3&t z;hQ6w9$QKaOwvvY(N~~B7sK4fqnHF|zE9rZ#=0%Rwp0ZvQ=mGI@&2|BAWJ2p%&DKY z_`+Rvh1U`-WfDiiBlB}{tlJK4zr;#cC)E=?;eB+JEmyh?ebp&X_!@hiKr+S^m5Lvh9(31 z|5}DtB?I+G&2_gQ{Z6rg?kEgx;YbPeY&g@uMiPu&{tnye1vsFzKwxa}>~~4-7UXdb zT48fR-!#s~6~da{pqKBSExS8_1Qd_oe4^e7x`urFq}jzb5D~{p+hY5e(QBUeuAE9B zwVRf@+DE`RtS_9J%g#Mh!gYTs{1OzQM(tlPNn=S)quAiFJ0O;pg^M^9q4Faz-rkHx zDKqk@&caN{A59{M#^SIw9PlYkl;fb>YvhIP z6B5QRUcdlPi~<&a*a_9u7Fsa3iZ50EX_^t3&lIW0)C{ndTppGH8edbZnxh_GT$zj} zex90NMX(6^C}{@kxH}r+tQ3YrLm*J&A&-%P;(>2Z8lN^OcIm%<87cV#I*1p7oG-_z z#q%=$PkuvyW~xMUb(2;GS_^6CKssNIwUKF=(%UKL3MCNDo<6X74N=OZVFsQOPaGT- zCkKGQl+YY&beZ7*209o%U~w>36#cqOXi~NXybMV)EO{2M@z2& z-j4nnA*Z$<3+1rjZw?!KZqwTp&;T3v=Jr`AxcGfZV5FkCP3hH!M;HRQPE*>i0t!xH z%0ce!zN_^HpCj7;4n`C%PIIBjg|m-J)NGb4#*%^HoG{C%g*)_>KN7Lg|F`&(d_5gW&@U zB;Y?i?>0}MwxhSD?6x#j^TU1FWT$b>XSk zrA&;+Vq``Zsi=gG-nv8f9W{ypjw7a)v%-eb*s^C3H>y>99;$gXJB;tAmK1%r32#g+ zoC6qdcbHt{GHvZ7P*V(xCiyN+LkGv(_Y_JEOW`wG)*>c@4{7pN(_?bJdmau0C?eM) z-qLXKVgo@Q8};}+zGg_T=Ept`>73P5WlJp*Yz*G#f`LE{x>JLP z>|9+d^<-_sNdgw4;s?5$L8|~mB>QC-taY^}SckH8g;;(`{Bbz-T_0tY^LS&x`AKCE z&7aRc63=bzVVH@pdC-sKiZDIMp1q3jKxqS9=1dfO0lOEgs5AZ+kzFy15}3*$=d9b9 z-%jsZM>FBLQ$W0p+QgB|LV@amw5#{rj++nBWh3j4JX9S#2|q*DeX8k6$y*y6JoNQP z0~R!De^s?Ilvg3?`CN1uX{w8o*8dU6qY4W7DJFB_wjXeT01ppfKT{a@xMSnESo1k= z>EcNTtUpZfk)3a&zdDi!i3o2Tv7@P(-g~+eVKX{yJM&?|hYLE|Qi?)Det;M{${E@u>Uibs;W8jdoo z-}`@vd;Kmwqh6?%$ZT9;sY~FlQ;Cfb2FDg$=e8Ra3Z_0UKd=p%ICIRb()CP5myc`} z4SbqSN@X|_Fp~h0qc;i4H7p|NL^;Be+~nnqi!DXaPEudFm|7+-H)<4DP(296 zZ{qvP&F?&~Apa5U-9aIo;?pKs6;thR&<90*coCLa*lJpy4BDBh+YX1L*?*5C}U zv}j-^v8QIfZxOFhS&3p2d%qiB@i};nIL7HS67k5Ank=X!J`;7Qy;M|QiC-4P=7inL zZkj^BxO=dpby7p2lR_cD$&k&UDHtRpg%YOZxtYXI`Rtn=U2FFE{^&%nCGPIWS zX{3TwSF25;NE7(JY;~-`e5y`?v-o8U{o)-vuWrvmj6p(K_y5rK6+m&V%hrJm9^4&< z1Pu_}CAho06C8rO2e;tv?(XjH?hxGF;UDfh_tm@ioWE)p#ioYYGyCga-MxDCw>?OM zHC$8IS1z4X04;G|t;mVoZEdXXa}rj6t^!nIH2L!Ue%I0W?eLM-q}{xXL|Nq(H0TVB zMwOnOTRKrcxVSxZLAG#M#WoyF(Rt#kHbbhC9OpaMZ&!JSRy!0Z-#Ye&l^@JbIsx68 z_1viF=oytoS*S?%55w4Jpuxl~A&J=r-K+ffkC2MW1#_gCP+r85kCev(rD)w)9Ii^c z9;bBg3pb?saun*hD?=;Hk!cJM7s@8GQ28!8I6U(L7ty60$jlg7iMQ5zt^<17#G~oP zTQ7x@tz(*7B0l&_oN?r9Aj3p@fn?PoGgS^ubS;&+ak)reljVDY)#5C_LaH=rIoq^% zZun092~CFX8+abf+U|QV5(H z7*J`A&;l?$@@iFB=uYU#w{Z&|bJ9pqJw*sRQ;xKJzeWrF)AYyaboe*W15Q^9*6_Ek zvUyp9%r5lUXqIs{Yy5606%VRK7W>)&+VXIp#CUOk!M zPEuuHk@!KgbAHD3^$q8vI;5>~CiNzj^`&oYAZ#1K_ucx%JFX!Br8V5tZNt@#v`+Pj zc=Ht+`c}}{1|GI*II6Aq1N~|(Nm7dAsX8X`>r%55yH9NW@~5z$kFFUiD_b6?Y9qAg z4s{yot|;p&8k)){P-Qe?qo{$NQxTA9~uemN6%h)d_(=4<_t$G5_NV{uS)GBhBs6kG^gAQ zTCxx_ctrk%IqJ^h?=2-*p0$Y_k=YidGnEMHy&hc4t_p|Mp>eTdu3^B_q{2AK0uza6Gcvs-Kgy8BT1OTGDVD5wfBRe?+B_Rxm=YK|r12z< zlc#C!SW!7VuUG!DoAk@ZBqB|R@zwn1L>iy#D4Su6glx0+%u391SBoq(<_$)^5MEE{ zfZa8Mxs?1NnlNjSCSXWlCRc)?Wq$z)4OVs;H>m`j(?Li&G~u`3raa07HC;`&;b1UR zqAOgb^CEGHwj0v2l2IW_Bh9qYxbux$&t3SwwI#tmJ0Rtmn;dh!>h-gLgle2u7(j#1 zi!V>S!d4EnEYf5|)vZQ`xD)xxPaN***d z@R=25V)hQsNn+hnQEm}77l!m8&wWFomGrH9O4K3x2e zX2^3|@CUSP*^YTX8>Ft0@{?SKJ3J>IbR3u@0h&0$)Jf=f!g!2RxTeh>N_%b5<-a-R zHmtD^uL@Ee+4F_6ys=bq$M`_3F>Rdc|l@Yo4AC z5#GrUaC%^jwYiZ0QA$t_syPvkN>vKo%2ouDM3S3KO?qVE_SQ(Mx0Fcm%3`Da$LZ9#Pszc~0w&sG)1 zfH5zhg+ghp$pw0>Ne|jU%tAc51!Kxpq^3&KW6wz;W(K%ZZ&&i{(T>EzAgfMIL$12$ zBRI3SN}C?wt*?n`Vv#$1e5wNu<4%Il$zS{cB-Tr-CxBzFIo(7>u?7Q!O{DP}qY(81 zwD@*J9YgjA5|ryx_={&+x`@XZO12U}_;ZW!bkn4+gSg6Y#9u3<>C5wacLoCa#r`98W2Z%d0V|LYguU9R^Vx_b zjLTaG*NtfCW&#hGs@XN28NLxpF+E83V!rnFLswEz$*kljJG1^<){+2vGhpo1Z!P?f zzLOl5OGhkOxMgj|KqFW%`QmC%Nn+&J1ozQzhGqq3T}sHAji>dWO70y9#bAphkSyE9 zh1OP|9w7oxHdoh0m&%jMN09eV@rdQo=B6|%2(Iput^)c+J0=0-2JA`SPNF}+I8l55 zTEbC4Q+B$3{B-_Z)n7|M^{`S{NY!$7^Bno9y}*?bjs2_+V31@qpcaD>cxXYwrVb(V zrAj_9g&zI*f?Qe^H)hlWoH%MTt1PF)+e{{FB3a(6b5a>)vlmT3q{xkMQ7$s)V^!dksQx2 zvbn5%naEyo(bdS1$0eZw1$Xm9q=|!Cr$4P%==x=742CX?zx8JW18rPhzS;9O1))pcc#RB%xjD(;cL=W2P6>aY zA9o}Ris{_-&k`7(HtO=nP-Le)$eM@eHB)#s#;pTTkUYEIioS2(99{5L@{?uRpj1SD zXs=X<4qg@U0*HXJFjvHVx{|j+*VU`qP(YEz`Sxlq(Dn?Km~ucG1?-0PGL@eQkcW$;zWPxct_q9 z=7*r>zGqNVDA%L#R(Ae;ksRh2M*2_0Sh;~32#sVqet{QxM++by^l&hwj=iY11nLcg zbi_zUMh19DZ!cvOq++mpW0r?=CfYNO51MzGN}wHmYNZAJSlFLup@uhN=MnRTiSW+VYJvn6(4!qJ*Mc^`BCwQ!$`_5cSR^iZ z^7W#&wM;N48hr0UWGpUlZ0?6NozmQ$dEr%I6y%~B%mB1%V`u*}1cu->C&A+4R|l~u zRp@kTV8JE1|HLlGmO7cu6{oZB@n@G=gy$Ap8*%tLR-a?Q46bKfN0$NVF=adwHNJlh`fzhA{O)1iM4O_a_ zFGE!oDiOW`_6u#|fpO|g1!XpfV7>GGbv!ei4_x2ZTLZk%$X`B61=FMX7*`LF%H<}R z&<|BB9-GT|gMOlAX${*f#-9MZl_up^VkS*c3F(46oH>iC*_x_VH#k*4DNdm{nvPsR z4O3AVu&2=*6M;jFLEy-jZq0e#*O+kC_4QaKI_x?XBGN=#Kbnv3bVO^PtETmST2D4OG3W&t zb5cSTK)$z84RLfaTAchf{9K{Ryaww}_9)<&v}9Ki;q5Z#TH?ptf_5?HCK)HS(ysT8!u}20ErKq+h@OB;tUxkv21H6mmGL%%sNDlI41dPELKMQFPF< zz9Qeb5*4azJF{By|y*YpB>gk}B)#UG9NEOfCY{)T;XPxvc0w zNnuf6<#V>!=afIBv^0Zne`ZNaTYNsh@esUH^pfR9ASJ}|Z>3{I72G83X9r5)<3(>9 zihVUvskHfhvdgw(!;G?@;ML+}qyS)s6O_iTBwsT-F?Fp&Wg0wJb$KF6nuDoIlyMUYtl_{?3QMYaXJ1&liyPs0Kn!^;X|776^2|$2Ewv%nP}z*Z z=dFDcu5Su6`jRLI1v`=m#6ZQo=Z8{EmWcNZ@lFQ@;e?`REQVwuDaDY!X<5yP-r^|r zYP>7g({a3}beiTk#9q7_d7W%(DYw!SlZ42sswz7uB(X2x)A@_u$24}+0DOcmVEITO zM?@P*@dG)cIt(5v6It}u3@W{0jG<;WVzAgXtDQLYAOl?%vCH5=Kl#bu)oj zeMZP)cMD|xoel?Jp;KuNdMsbzvox}x^tH*!GH8dD{y29sfC5EUur1A7d{&8GwJJdN z5s$F=equMlX=v9QfEHC+i5%BbyTfKhDO%e5$-Af63L8ha}^nl%rza2^Gyr zNPWYu4zykdAv6YmvY8tWe4KC^Ci<1z`H2X48PNoh>6dzsfN$F!1u)x4$U99lKaEyw zuQ$hg0n*DHqM{BT=vjoa=6m#9B^MbzHdOl~FT=NwTJYDj{n++Ayb+mKKRFYpjj{m@ z0DJ1iRvpnSFQJM&1p9VVfhzs&i0t+q;s`?ns?7E<0}7StJ5Bjsd8cBM>e2SZ}rBKs9k?$ucRTJOwrI`wtqxB0D`t@y=Wg9CV`Zfc$yIt z6aYbGRtU6#fhh5GR$0Csz3Fv9$sCnz929+$nd<4{85c4Km`9xU1`mW(GCm|`?y1GL z&tQJ={bvB;;@4JQ)2T=?j_qia&22MM^n5Yf#^}VBW4z69;AeM}T;kL=u`2R6Zlx{FKI`Dt5Q0M~sz= zkwmG)h-gOCbHwY;ZEcNEV@plHb)IDlln|+M!F1xh>A57lj#PYwk&lbAHJ6j%ncHH9V}Tzt$?K@NVFYAVp}WXp%AkXl`UOqQX-P-@+1Z@ znr}wh<*D@h_mw%+Fqteq_Al2RC zv(nJ;a1U|4hKiI>=`Y9T$1y$RR$`Ld(`UDg6rCZ&g1<(PM|EXB7b<1F88GH7#3=Oq zOb<+mk)#5e8|Ke(ZwZPYR%28uRFd=$zjhfd_wQb<;8iZ0^DnSBMqp7z45AfE>J5)t zbW|B=_eu%B`T2|K%0s6_@hyZR`Q$r=qD2gm(&LC$$|Li1`wa+lm7!<HcNgEXR3E*90r6u@`j4CiL|McoTDA zSS0qAQRe8oPC=xwM5n7{^?VyN!fm07N?4e{+Dtpv;`WVn^wjr!pEm^3C?8#a)e2h$ zICkJ|@qiJgOEymQ?eLmFtW`txNvV`Hn@wBIjmhId2TCikdBl83HBstzolp~68i`IF z>mhh4RetO=Gw`*U1z(U?)(zGu){k@^Lg9oV`wNx`*In`wxI9SO<|i2RJG9;Kgtr}y z5hu2TUDC}jNK;C%#M3Z@&Gt-jhN9Lm(6s3a!HI8=8{l^0pE~1d&C#%_lHN9cBYS#! zK1$GPx7x;&O}nU38i+;h*OBFA-yt1Ykw!A1?2wY&8gTVE5~};36eDstR;EZQf?vuF zD&9%-oJdPqwK~hs!uVBQS>XgB%cI!5`D>H#6p!RMAN0GIkNj%Rk7hb(S)kyVCmlP^ z#9OO=AU8D&3k*d(DB!qrLlD&UQF>_#W zTJOb(8dmjV>5V{OJGz1i8a;LQLL!lIBEZj^(<8Z2mwm3vZjc1RYXE&83$w1V z^;8El?I~Y4PZ+aOx#?k=G9HF5d1D%_3dg^lYS<00MtIo-kw_^Y{71$f&dFgSKfok> zIiC}jP&a`6oi4^iuc)jm!8YtpAl`vPJUs)%#l`6%JcBPN`7DG1>93`f&q4 zdSH^;iV<}J-}^<#ki)6qYSFMsjlxK%F0aNSefL(tRGnz#qo6c}UNDg^GsqY)k@mj= z<<}bMEpAnn@(CO}=~L);q`np;g+B41$R@s3IP&plLnd=eb|v2BEt?JHT^K)a6HO@# zW`CVu&%202IOHjM@TtmuSsbSW7&Hx037&8<_ajA`xJb8Ke|Wt&jTnS6Se-I%Z~v5? zf5Yg!l9sxs!SM-I&~({({>?ybh$YHIeU@9_&q2-M%R&a>{;RUfWl z+_=3X%4MBU9)|fC2DPCwk zN*?XnZo-zH#!3=*NSwC^g292iGS1eWw3$g{BxqiYZCU8pICO=Z6Vajpb#rb9I$7Mj@*Ipz$KOD7BJwySoxP4@2k z(=GA5FN|+~MU>y9+si%w?#f?zJ!@yE(6xbVyI+SiS2xraTleH-nWl(JAY%xVgzdcw zDeG!4f6xjVX*rLRZP->llVmDIzblqvWe|m~R~(R_61L;p9~8M?5{GenTM}|rp8E{j zX4603$PCM`|%9_}=W*4D>?7T@dMr0vIeQFkbbk*O5^6IBP zF2Wd$R#h_0MDW_ee3cRdH-f&#!3*F&*l;y`ZQ&A^{h)D@bHBM#Sa^!F_Fk_SrLnGsa5Z8nw={iRU**87 zML*PfO|Y)BK$f8a>l78`g#e$X#RfDsR%yDQ3FOXgzn!X?S0@zSO>QMUk!?0Tkrk76 ziP_lrh z^HvX7k}&duOOa-rvN)4*j32ybGhTp|crT&cv(ZfC&B)}!IpB}F;1s@N#(^%ktW;J~ zx#?g{YIi?8Sp8-0eh9gp1}tOFw4pltmVK(XXwTL`o2f?%_ocv+pO(h;Wt0YWDI~^M zw>1x@M=eyqY1!x8xu|$61!h~@`;gyeWOB=}n+z?g_n}A!=ICGwq87^$3u+ubC9Id+bp6` zf|eIs=wao{l1FO+za#^@H*4e+lv%#FSuL~#@MepgyiBdOXXRuJtqEe+PPqMLn;<&n zzpd);C2`>lJeLQUjCGxctWop)*GdWs)H#qc$jWv!h`N2F0=@p{M?b-LmSUm0{2J%kLTrx zG&0n%(RuP>YaBKuUQ3AW#(qrz671UA-{JtLmG z4N18gK!Ht5Gi2&d)~b_n#Oo&O&x%j6KwyGi9&S20l}7YIv3Bbn3*JJKS#JSCGmW;d zF`}bW43tQRBuvALVr+9ijl0M**iO|7`qra$#uz5*>h<#^H6p2umYQsapQC-nuZ|2a z>(=t}*uf+AZ$|x=`q4rP@C-|L@oo55%N#XD^eiGG{FZO3#%Ju**tA)-5R%w-t$!<=GD?ZdZkV;|IZ*}1h2_P0D8@^9z}M#3!| zmU`O?7+X>FOlPoq-z))YxxI^eLiy9U;NW`27T8CZbZRpp<3aNLyD7&i7Lpc%?iQK> zpKiEa);l(c<~6?C(Zg9KTh5o=RK~^{maZ2@@^O)q@N*VnSfFZv@$qque#ls#m<`lK%POwY_z7=1e%cU1YZ3^0FT2iti7_N@JK z0QDZM$Hk48zy0P18SBjF^Ui29A{N^dkba{ZL_Gamd`SaMN1vRfEA?gKr{s8Hv>L-=3f=JUUd1Alxlmng zkQx5>u~2JFZE8x1jzl*Zw7+Ugo{2|BWI2#{v9+r@h!2+N1C@q$N69rcX=8Sm{?X zw9R&RSws)*NO2E;yqoXG$ zj}Hv|p|0t92(7~$3O@0B&%r6zTL@1o&N@%*BZMS<103y(A|2V#1qz)b*>e)c?|i|3 z9!(#-^ZJAgW81hr_M--pe$A!)#J8gjdMy$xr*UoLF%?@SrxaM3TXlD86#1X`dp zXM5C=LT|`^|61+yO$0ztLbUruybH!8n&=VIUPN>S`BEs`g740k<-IoT> zpfF(GUAuqFPk%J}*2|iovhN}HKaVg;cvwFzAc}mBE-4ZfbV=WIzrbC63D%T}GKJo2 zWrkG?To5`OdVlD6e~6 zS>kOfncc!oY|F~#U+v`1>+GWYBI7Kktg+KGLswz!h=-K-+45+4H>BxW?y#g@n1g5M z*$J<80>`!daGkYf<$PCsdkcz9Uc(FP%$ny2`r6CyVbeKlVQ*?jS^gB#1<25PqE9A( zQnL_dkq&ggeS8X4fvwbkTO1Ks0Lr&u84lk}7}=-`Xv7ekeT_9!jfh{yxSO`cD(Uv7 z+EBc5n7z8^CE(Y|N(i!a*X-$Z5Tf1SbjBd>+zt z79?-tHfCDwrRAn$qBysvk5Bb~mdx{`y=#Fy_Yj?VXA_>inI z01YXS_GDNZ4FB(5FO$qGukQ=gJG`+}h?hMPWbaHPcr;F)uxn1B)CfB$3UR+UYUyk^ z?o9=L5`_<`Tdt@x%2&$&Zg+>YO!i0QuO+oc_9l4guCyG~?YRs483mQbb;3ehZiP-K ztra8zmV9~TVR&^hojG2i0uso`v@zU6Jn~U>5hwcvYiOy2WwpQ>5zs+a!bu7iXGNMJneAMT%^oFn^B4-RoS<>?_1 zIZga*ywwQu%7gpz#-qQzE}7R#EE(kItu%=Py@@@x^m0OGK^spFegDhRK7^*mdDU&OLk(V0jImngI8rWzRN*b`xA+&xzwQG42g{iL7Rjkc?JZuBzkCn?<$$_t$7Z2Gd5SUXal| z5+ZQ^MMZ^idizOu*)=iSd$WbwsjlS35|mw!f9_~M=Dcc|H2b^vpjg9tD~uG+cQ{2P zMPuVI-Jh;)7JRK~lgd@|xDQ}dGc3xhB7nb3W*?;19dN@~e{ zHK5TzI@hiFc|y#K&xi_^oA`f%A!(83*}wM!_|NdTAJ_pof=QXS%Y%vmV&M?^@vi{X0R~_M-u;m0Z~geyHGUApFX+C-X&|b` z7)62AM$c_0N+8-KSpN1>1l?yf|B%$b$1RbJBa{N+18|a1jv3O5lVrf>_7!D|>eMAT zXfs|BmLqV9h#iHE)_!G2(|D+VUv(8GG%01nghD8l3$NIaJc6$ z>guHg{b2uIR~mpFHxAlPTw@4_<(}}|n*Kt==hc&E)1yepBx+!TLl^sqHqmYMxrsWf z+o}D#QLR$!`22l&MLKK%a^F0gD_#>wj3V`RgE61jPJU{Xi_XC%{#>P9X$6e0H)1Io z&I#JF`(RtV!zSqPq6R!71l1U-#A97ucq#`!u38qu$C>K1EMs~{hkKG8q3rpE94~<` z>u_>4J_}MG{TGHrdd&h*qS&-QshQmH!aUMWKyg@P*+lfRq&17Zm+g>Lk>$9(@K?p4>(_DS z@9VS>a9UD*oERq#{nY~LcznEPNX3j|{~hN4cnR$!;q{js^NK07+#I7>tCZ{e+>G7-^yY!cs3B0M3!(I9mBHb(-p^XxUI& z%o6*>XM70_BEZNzu8x}pTpI0h2BKrMmolpL^GTS)Rx*Q;YL%>hicvl`0*FQVdqiQC z;?1TTbF#TAF>v*k=yKmP&3KvB;=m7!I93f4*Iw{1SX0EeuXOe7^ww$d+yOM7sF?BQ z&+F~(_+HfK8xv8|EG%t;RHFZ5Uk<3`lm$Y9{>ynXwXW`SSOt{j-7Qq@U6b9GvK$JO zBOzfEZYtg#+H!Oe!PTH zn&1okBn6mpQOt$=n%n^ z?t1QusNf62R0AF`%ld8CWqbZ0Qo?+YpdNMv`qpZ zn_2)NAx>~f7&7Zs8{<7Bv;EX^9l!Bfba*I}?2jVH*O#?i|JelB&3Px|Rc)kXG}C2p z*5JPo()_cGwkDa-uLV}MRb4rml}dKFup7yJ4j_U}Yu9XQhaVGm(8Csw$M-&|SxZV6 zaS3^e-{-Hy+9 zDekUVYgR}{VZ?1e3~Z^nHx&=4ApGkc3;2l2@?8THgr|bPz10!Q%2@+bpEE9C8uZ4{ zu5k_3sm*M?E)ilkdO0w^a~Dneu{P>+`Me>FFcj!UFoTdJ35NAeiU0RMn&UYoxY>n< zAW6~~YRHiNpDwWoz{YfYc<0s|f`Ddj)18rY%jP#1X&Q%|r_`mjI(oQbPtXI}{~9j& zydePI3MrJ;RyP^MkjAdauZgPF_B8JR%sZ~Nl1E5uuq0mdaH60TH;l}7p?*kK;B@F! zE(%JRVvuEd<_=CcOeIkRdSigVR$b&g>K|g7NQV)4@_D;?#(`UuQ0bHrN!%wiwxxIC zx1J^mfSqf?7znVSWDFCzzunt&AZa=a5%ay@geFR`uzM!81o_jc9oknrj>~EZWA(^(%=zaAM8vc=iky4U zlgS1@s@q=N*TP-A>JQ7RuV(Bv@n{F^#TA-F*8S_=?yunYclZ z&(XlQvUY@zYQP za$ssL2YVSK=UW>3F%$Q+U^pDtN#AQ;{#|yTi?J_m6HlMS8(thi?^IA*e|?uT$8w-E z*y?T*gv4F*(h3PE36v!r!E|jrhW5>LwHL?ts)!A{zv`%#t*le8;lUzF^3=f_ugURh`PNjuCmlL zNa?mG))L(FO!mB^4+lltkiN+C2%flX*m+0UsUQ*6yC$maXT;Te1oV=J#$!|YC6vv7 z#&c+mpZqo;Gd1L}km>tz5+)Oir5e!%{^S>Eti(?UqDy$iYieO4Cnqyj&+yip50vQk zds{R{9+=_JGh&AP7$L+nPLC7KFnd=l)sFKh{~Ln;g1`4p2m}nP+CYqme}Z!<5(5v0 zbSkOQkaKs_yx#Q=8cLwGwJaTP$Sd4zMQHZB+Kz<+jE%ihOq&&O|SX z`6f?Cisz51>0uj<>YhEPG2LmQXSMbTZ-~-bAKb~n9LY9jct}_9Azwg@=ig(JOhAVV z=UpOl60RSUFFOWxwOrg$#Gm-bt=UESi9(%7UPl-t-tb^|Fo=z z1TQoOf4C|wBj0LGI!4o&A46xSxZ&~RobVuC7VYVE|5uaPk=$O`vn`F~lP;H#W}Lp` zkeWC)9!RKjw!UO)a_8{k@n(nlN_QoXhDYcAT@5zJin}UnHKFWzOXrZTdN|&Gk)Z8Z z9j_{*Tvh-0Uz~%bSjYVQ!yvBbiw;#Nu(v*-F4P5!Mzoi6d78&*zw3Y*hBXNs;XRaNaXNpUu|>ej}>kiunFyt zE&1%hDb|R}gIhDe+=~a)4MDd4PE&WL}uX@fI zK#|yL{b=QN!AWPFP`%5<79s0<>E>x*1uuM<#ai-KL3aC05Rj6pOFjlGAQ@C1h%m`} zX6!Xq?;aB>VPR0+R|?C zQi8-oPN)eeethSf zWaimRZ&4@BzJ_{cz>3DAhleLe_)g4r*`6_RrMBOen1$j;rPl_+^il1Ik=8oH+Gyq+ zFUN!R{-&SKH0c~70z`{b27Wtch}8W@ZyMnQAG0THWf*eK(j%Kb1-Qt zPX{8VY53OQ3t#+Ej!!@<=3wgZeFy3cdhcINWAf3(Osjk2rMIle@+;vrGl6}@%$xpt zwX72epqOC@(0H;~p(k7n`Nxbz9Qyf}vcdj_WVFp24)0Zw)9Kk4aoYuY1BLa+UMu1@ zWmdNr`vG)tRetyA)hl<Z^a^t7y75NNr%hZUgbOutS?CKkJgc}<0x_!MjqPe3=-X!&g{jV z`_nLLP1M~B#bmL?BUqwz&S)QtVuWVU__^r&yOjcJ)cS`Y1f9|E{}d{gUO9I}fBIN6 zE%)sWIXHp9x^6#f9>w~YEP7sVyX!sK_}g*(r2-7j;gR@9C~{K#Np0^%9dcx$M zIj(spIe#?r7YIf2kz&7+g9FRbs*$Fptnf>1AqQ^vVIZhJ<1mF?`j=R-8sO7~7ymgk zvkazu&4}Dco8gmMs>s~lmXa#vy2t6{C1U*M>RU;dCfwLh#DIMW5L+1!lu6L`Kezmj zpjs`vGH}`VF~r9DPBvgcdGd#E#Z4Q+QvTe=VmIYdE(OTdIH11)rxBd+{;}rGK=4NU zszL)K{DrOSrlev3PibGFW?*=h$4PATxr4nA<4Fq^)RWaBUD3#$jKd0U;CkXWx|q}teSE zcZ24J;{?PKf?%d0$*Ux&g^3L=;0`lWE5|7;98u^^wi1i(i)>Jz^tvLnyN=hP<=iae zM(vYF|7~ny(Xvu!#wSG0&vpJc7d_Wm9J^10DPnG60xBq4_{wry! z2E21}lP0&HS3JE7WWSIwYG&Id-pn+!A1}s);0m>eh`(`T?5#7RfTrpsXk_w2G_S^y>cB*A3)VJvd+D@11gyODy?h@+B|KGe-cQfefS>`hJzUxzX3)3 zuDG4V$XX|9OAra4U_UD((WDc2KccKWFv2b4r+oGT?;Ic|l{U9nZWtGkje~_4jCR}H zTgMOAjS~fS@nu0b3kn$AMRYh=N;TrCySk(w1Q{@J3WJGHFT5~;FH zTYqoKJYd{#9Dr(c(+3ho)${}UK_AxcJNPAJSOASy;7nMfQu*4vb_BNar*1ZuP(Fng zdloqnFxy#2S2X16`yaWpw#8h$AG`g~INL=nnrnnX^sgH@`<}GPYH0M9)iWI9zTqnQ z?Z1Zf|N9ezCLV=+Bj)xzelJeiIIg4z8FV9;tl9%8tiEj2pxGEZ6r5MLF9=LPB zQXtNQVmD~Y1Iyk81>DyizbYps6or$V=KQR36TF=t@37~`Sf?B^_Rf3p=QtG-@V>+w z0B=w$yy5A195f}p(IZ8b#>9jZSxG`D&b{jG+Z@i9Jxf%j9w{eWO~T;Mwxja+Al1Ts z=V-2x5}s7_7~aHg-+o_v*?2z|c`zf&1`6?B{LmFbdHfYq|5DifcXsHN4Q<|GEAD@i zi+aJE)xiIR5#?|TdC^p#y_>DMhph544Q7r}yHeI*#!1txkJpuTEN-)~@n9p=3yz zjK&ZRDL|r?gL@W-1=%VBMI+ybv7@*lW9rQ@(^2EthzBavHg*9$@S&%xyP>La<}+CN zjrH-@;s1bIoB>{Bd2vkq$_y~J`GFyL^C2Gbfz*8xZ!|Wn@IDk$+vgl=S$e?NVAcNsO zZTZ{5jP$Ln|G8E#WnuTlow6tocJNT|;!#=;_lGZ61OduiD@L^CP*-5cWyQ<#smkN= zhX@U6cMFm@_g3Y_Yi_k`T6EKnI#1{!qvl3we8R* z%Mn{5P5LzZNAB?ZT)9{QEdJSLLl^OLVsOPa@gnFCTW887g`PRWY2xglc)P(0UPZd= zzUR4P56ts4U_u!m;9a^SG50a{Bb~_e#uEU&!~wtttkv;iXv)G&e z_pb-~iv!Y+ALir?IPmZe3D)N_qbx_~E5)V~KXC|-C!xC+vm$rxWND8Wb*uAn99Bn# z1R_kYlT>^%J%@z12Iw;+k#-5k;X8I|QC3F{BW{dWm=;;hEOA01;b9qkWFj#_r%1pG zSa(Bync^Q7-<-o)ny&&~l}MVT(Q!=e@1N%;hgj7yX#JP-+ZxeG^3FAux+3&NI&fhI zaPfBo?W&+039pKF9su*YD=D$$9BBSr>0!0@7Rn{v&;E4?yR0Bbzau8H!nHlBDcy>o zy2})l{=azpzrUyOb~I>~ioRz9U31O-EE0WOFhchdXkEKlA49YK*H|R*jxy)m#_AYe z%07j*e8vRR#n?qU^QRf(g1mv@&rNPeg;0$U@p_F@Ae&h476WEg2%H2EUWs9@S&dob8p@D;*78JhluzDfM#tnW4 z1<^@#S$_D4tt&CbOX5FzsuzMZPSV4jI4*Cgt5=;f8!Yt`H8ZM!?WWNPs1u6$bYhuJ zJ1z@+V`umbQ4g24k{y_;-e$n4ZCo;fpUh2$Uv*nhdJ@`B*yFuJM?QX6KPUjACwDd9 zK3-PkZ6`gEE*kjyigXZw_y43i_ssVU)n`KwPOJ@?y4dDeKEJMy&r;7i$%^LUW-~Ph z!K&Nywc7K=4Z%QqYhCGEfy(nqAvTtyQF&c+>aAOJ!P`?hu7^)cXJ>Mfwo@2T53)~z za(L>YcF%HDt@fMCJnmFdZ#5`*VZR422fjwU{|*KJqD79Nk9&M5>GYN_N~1fkYNlmz z)F23D6J_V6f-b%Dig12CQadRH$EVgfFAWyR%;06!)5)jTOUN|ryS8YI$2&n1ACmD z$Bh#3elU5HSv}?|_LyU*)NL04E!!^pYen{PC##nA3C)Ue&O1HlbvCJzCl)|Wh~ytt z2>*|*w+yOlYqo|1!AWp}!w&B5?(Xgo*tiA_4k3oSY~0=5A;C5tJU9vN4#B_0dGGz| zxrgTuMOCP*T62y$di3a?y^j30{6Z8SxV14k{qEDxTy=r8>x$GQqS9oUR=B_SYY<-j z>Y)X`*8;yRTW$W1FQGw({ymfsqD`^(0?XL|cJ%s_`@+T~+kagPtSX=iA;GR{YvZJG zPn|0+>2IqQf)zrh?`RvUd^Iptu=F))ZA<1$7B559WQQ$jym(0&-A=JmXy4Yol~e0{ zZbL=>)?X|re`-TPvIFEQiscIAs`57D?;o>Dj41t?4X6y{8XI2qhctv{JbeqmQLD{y2YO9NkpHZNLM*pGiKDMSbS8Wy56lR7Q< z=w!0;k%^%OXi{SleT<3p6Dfb#d0`x2%?HjaYOHTM`O3B(dhqk6E~;zlx*@<}qvd7U z{@5WvaE|h#NuP?(7N>Yx{%PL*Hw*scLbm7q7{Ai~en-gprrFF0ue7>BEr;9k>fKBj zaetMYKO+sIP0t17zckfa2MkTRhqi!@)NL*1ggfnqxgHVW<;KEg>>`!Xyy>#q>e__NG+-}{RH{a zaa3ZagZz%VNva2~S*pDIGNWaSf#CiMny?b0S-@`Bu%oT78}a3aIy`f3!3~jTnPJag z*ma@ugXc26W@t+C3}^G96xpF|2VomzvdAEO2lsffQ1I_|a#mP5=FOUK@KlX_FLG$YI^KDF&hKR7g%N zW~PRdz$XZL{tq5H?2c@%I+2ke7jIaAVKJ}I|kjg%^ zQ{z&&s|gbB?PJFheRi(9>3_*fnZwaPx=p7((h{X>XtIEmzT9z9e9`v-FhcDOy{o3v za5mNbmA)z>^P8dA^*4OnSsloU^7zxJ;^FRm-g78Kt2P;Ilo25C~>*iWvK_KH>C3p(NS5pQgg593p z|K$1qM&D{WoU#7bYuC2-Hh=hsy{juiE-RE!-d*LFeNvXxDj829tb^)ee?Ctbmo*{z zylXZ@5qj567|mPrs+|Te_wii(Q(rp9eMxCbRe)8(w+Y*hJ21vLm(2>gjB@C$jQvHL z>c_CHqmKba2VC>R>8U_*@9%D;8ae%pbp2W$@{in?@B3Y?%r3dQmHGcHCaHsbk3>pi zI?ibCVEtH6_Jx~nmP7E_J6c^y_=Ho#8(Ph}h~(?C=nx+s&R-e2M=gmY7AX4kc6@1} zHssi6A|7dAgcamnBpZA5rFyz7zjtx86#o@(ld-RZ=^E{XgPs{bZtNfo(9Pzn+q^v& zuOwu9ig{e1v_suCz0gI2Z&Me1zZ!O{FeJ3-@)vhn%x<)@8E45Zrgs;6CU(FMXaL{m z9DQ!ZM>mWok$Ddu_^A}A9d6J$qH*vY1}b|m$6 z%Q2KAj*+cDDyk60Xuc+>#-FQgnVgTQ=R(+DQ2Tt-f}LYGp255S=#68muvGa8g7bB( zX$?eenDkn2VRGEz=#TE*`7wr;-M2{ocErOpkx42p5MD=(T(JZl#nL=C0mNg}z59KC zEe9r{S4r|@L*@J!U_8H?sEkZGKj_dI`~EqAWLhnWx&5&ekLM4UTIzVp8=TFrTLH}z z7JuBWItDeXoP=V_B$EjxDf2l}O#+QWV9`)6B#e}DWpNFH`@UdjMvR_P8Rt=T-jqAA za@tfK>`B_+2#%K!>Dr%g&^EvdS(2)%M;e^XwfIJk%>4?$;gPzZdt<`)1<2Fef!-&# z$}=&i|6fu4=PxfRaoB18bX4nG^OU~-C}WZsJQuaE(;cs2FN^6~iNUe=sR(cr-8O%( z`$KQc12N<-m|Bf)ejSC24=2Y30d;ug3yO%1*nloe?E2dy~4iVb+WPbFaq@)sa2@_=-GY|5l~!vPC~`=0zllW{}XFCtE}aW(MkZt0DQn9WeiC&}9u_9uE>iB&o4Y55n)R@>h9+H$=CP5#Na%4|>eb$tU% zK{&-~lMvx}&+v9quz;qY7E@jO^m4)&xcF73r5+7XMmqS?2iIlKumr&~`lU=YqLAxy##D;XAxLnG*+jpc+`JF?tgyID#bn?{HCZz zb|&=R%t?D~efe|*LGsGoaVm?@pNEUEIzR`Pens#?ubY z&^U9RAGzM1xso(e;hM22eNBLTF_MN^guCNQGL8@#(%n3Z-VaGa{}LXqS$co6K`Iz< zWd=;R#H%lyw0qxcwDx{7w@^&_sCWr2pR{`fTSQc2EkxGT)fHOTSjp5$3fMW2z1_OC zei!Nl{cviYbypbup}_`mwa#PBCT@i9?2zFINPPDK3VzQw$1(0y z*plfXo<)4sF}>aGE}6fOG-prW$E`fXATQY9vE!Vvd%rKr8hMB+2Hcb%jJlztv)=H} z70Z=$AA~B~XcQ1c5vYdz&BkT5-K%!Z9nNzMQ1J5QwheQ*X`5$KA3|$fd8rQwVP`Ht zC07W)O#8>fP<x_XQUnTB z(`o!OEb8mtPaD6%UlEz7n9FakPEMUUsUr!Ze%DpR`}eON-ifA$UX+#yNmpZ~!y8PW zYmawfdzfEjMA}|Z6j07v6dst;89zA8_j6xUQ|Oo&dzVkDpEg{y9=OtV`2Ag}|JA@g zl$XrwA&7-+sobPOD(rzc2|1rt!2srfCT;1>hqPts<5Sgmp?@_ zG`Sr?io!-Tk9-->n{N&Cd>uk!<8w^&i(+*3)Wi1c%!qMfB5xZvbF&|&p!1QNtc@n2 zrL!WuQX!-9e`?wf5}JpqjuVu)Q`LHgsb)cd=`zA?PeR+DR19gBwXl|`5*bd$}G#UUh?>p7M%b0?@qlgxo;yiR%7{V zCm-RHFWt%ho?j-N{O6i#6mjDXOoU(NBRYgeS73@4C}pRW@G4UPLaGE9E>uh?PxxQW zW7UwS1=nD^a(kOyt{xoKMk};NdItNI(3Njq+EJS%65JxAKbu&8b7K?pa|nIBx`TVx z&92&F%sftZ>F;d-qS>s<$z}3MQ#CNr=SQccn`NV9DtT4>g+E1(q)EpSdh0Cn%|vdN?mDjzs1(K(feEZWF$AbNIi z5PU9vZ|MyYCXUp9TsaPP7f~!wdd~vD4i$MjmeoB2b!%(t-{SKB*;&r)L9b<^Pj3eV zgs$DOlv@q|1~d@b;RiT0$jtO})?Y?D3;EvH(=s@6y8oiOO-Tu-WwMnVs78=g;{g3^ zi7&O~E1xD{GnIb(F=sEPSy;VTrZCaLv%&S;na|(fWa#g2b7bl}2j_Ky!GRut#@t5+>G@~Nb49sKcgkX(7U)Uy@Xk$XsTnB+0@n2{}6 z+3_%j8{MJcV=tOXCnYa5eAqoLcVvq?X?}fCf&}TkHCKa}8@PE>omXy<=TE$O{71|9 zJ!zRtSF!)@6r~`4$d2aQaIEHR-7}XLWG9>y>GAv2xv61!+bQ;)|I?=(+>@WHyazqD zh(tu;v~SD`7JYp`XA!IuigEbM!rv$}iEsAFP9OzI zR?t4ZBQSQwpb;wUF@g@{gXtv|Q=m@=I5B^?{H$Pr?@Yf-lC>VJKK-NLc45t)(d>tL z%6_2G3t_;&JN>Q#*FGp@=sv3S4eE-K+~MLHIJ`ch@(B#d_cRcFRQ8C@luW6!=+v&_gFo5KT^d4g zaPA41aB6Q;xi3F}%uWODok3RbkE_p>j#wWTxl1RZ4fT`bXUh^f?;q{9bPdDQQA#}1 z?;%*zzhacE_ODK+fhz#`=fwD*S!3pTc!$vwEZk{XO1{Uqsb#vWytSntnVoU}R7qHl zXJIkAwqP;2aadz)1)41nvahEw($RHwW-58rhn_oeu$Y(d6P3UJ$I6KWFysd zmq6m7>wJ{Wj>67VPl#)AX>#W=C7+))*KSptRb0)yS4_`LqKg^ao8w`r6?3p7q4how zl_;jzlBLZ8=c$DO=)`5D9tV*t@vsc_08I)%=A~@7^MTqH`?y+%$fH{~(&EF)PV|Ma zfUST=6`$lA#T~$>6)BGM$sM_Hj>@Iz|N?7TepMg~f z;T?jA>>D3MKLqPrYE`>Bo4iJenrGoB(_2DB>>#lzRlx)7SrdDb;r9R^9g20iXVx56 z`+JN17keG}12pQa%&Zx>iZaiF8AATr+^ccZpHL8(v?!>oax=1AS~Ajdzq~I-LcfRV z`YrV+qCUeO?|f|q3`gW!8#lVd&Y$S5{-KTiIQ{doqS6}vK;ZwoU0lD-!>(PdKu00v zgBJQRd_KO|XwQ}qm=S*aB5th-%`br3De~7R#dSuEEI3QCn9xLG>w(LJ%1rQ3qQjsL zpYH6B$+*lMrkx4@2KP$)p$0I6u67QsdLB~j3$bWPP>AGu6|^;MM4;JZI#7WzP-#*_ zXmaW7=!ocWUs1<>x-zx)@69kD)x8i!vrD!2L0`K{dZJNO22T5-Wi@yEmvz4tUuDwk z?=W5mP8HDtD;=6|DjnjA2EB>Z);@??)b3{OCX$i438vQTx^nxa>ALG1dyS17R;gNn zBOP1XuKKRXVp1O~v1@K`Yr$9Omhw(k8`}$!k7@M

PxdV1G=u2_-H9AQQQ8L7X1 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 literal 0 HcmV?d00001 diff --git a/docs/worker.md b/docs/worker.md index 3006a05309..f344becacd 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -35,6 +35,8 @@ The following command will trigger a restart if any file ending in `.php` in the frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php" ``` +This feature is often used in combination with [hot reloading](hot-reload.md). + ## Symfony Runtime > [!TIP] From 89c97fda9395a99b0dadb7dd1c51675e9e2361b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 24 Dec 2025 18:17:40 +0100 Subject: [PATCH 03/59] docs: logging (#2097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Dunglas Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 1 + docs/logging.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 docs/logging.md diff --git a/README.md b/README.md index d2a7297433..2f32eb5306 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Go to `https://localhost`, and enjoy! - [Worker mode](https://frankenphp.dev/docs/worker/) - [Early Hints support (103 HTTP status code)](https://frankenphp.dev/docs/early-hints/) - [Real-time](https://frankenphp.dev/docs/mercure/) +- [Logging](https://frankenphp.dev/docs/logging/) - [Hot reloading](https://frankenphp.dev/docs/hot-reload/) - [Efficiently Serving Large Static Files](https://frankenphp.dev/docs/x-sendfile/) - [Configuration](https://frankenphp.dev/docs/config/) diff --git a/docs/logging.md b/docs/logging.md new file mode 100644 index 0000000000..372779d60f --- /dev/null +++ b/docs/logging.md @@ -0,0 +1,73 @@ +# Logging + +FrankenPHP integrates seamlessly with [Caddy's logging system](https://caddyserver.com/docs/logging). +You can log messages using standard PHP functions or leverage the dedicated `frankenphp_log()` function for advanced +structured logging capabilities. + +## `frankenphp_log()` + +The `frankenphp_log()` function allows you to emit structured logs directly from your PHP application, +making ingestion into platforms like Datadog, Grafana Loki, or Elastic, as well as OpenTelemetry support, much easier. + +Under the hood, `frankenphp_log()` wraps [Go's `log/slog` package](https://pkg.go.dev/log/slog) to provide rich logging +features. + +These logs include the severity level and optional context data. + +```php +function frankenphp_log(string $message, int $level = FRANKENPHP_LOG_LEVEL_INFO, array $context = []): void +``` + +### Parameters + +- **`message`**: The log message string. +- **`level`**: The severity level of the log. Can be any arbitrary integer. Convenience constants are provided for common levels: `FRANKENPHP_LOG_LEVEL_DEBUG` (`-4`), `FRANKENPHP_LOG_LEVEL_INFO` (`0`), `FRANKENPHP_LOG_LEVEL_WARN` (`4`) and `FRANKENPHP_LOG_LEVEL_ERROR` (`8`)). Default is `FRANKENPHP_LOG_LEVEL_INFO`. +- **`context`**: An associative array of additional data to include in the log entry. + +### Example + +```php + memory_get_usage(), + 'peak_usage' => memory_get_peak_usage(), + ], +); + +``` + +When viewing the logs (e.g., via `docker compose logs`), the output will appear as structured JSON: + +```json +{"level":"info","ts":1704067200,"logger":"frankenphp","msg":"Hello from FrankenPHP!"} +{"level":"warn","ts":1704067200,"logger":"frankenphp","msg":"Memory usage high","current_usage":10485760,"peak_usage":12582912} +``` + +## `error_log()` + +FrankenPHP also allows logging using the standard `error_log()` function. If the `$message_type` parameter is `4` (SAPI), +these messages are routed to the Caddy logger. + +By default, messages sent via `error_log()` are treated as unstructured text. +They are useful for compatibility with existing applications or libraries that rely on the standard PHP library. + +### Example + +```php +error_log("Database connection failed", 4); +``` + +This will appear in the Caddy logs, often prefixed to indicate it originated from PHP. + +> [!TIP] +> For better observability in production environments, prefer `frankenphp_log()` +> as it allows you to filter logs by level (Debug, Error, etc.) +> and query specific fields in your logging infrastructure. From f4667e3b68493917913ccfbc7560a60d70b6f7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 24 Dec 2025 18:26:17 +0100 Subject: [PATCH 04/59] docs: WordPress (#2098) https://github.com/user-attachments/assets/73bbf707-d34c-4518-82be-bf8a162ca969 --- README.md | 1 + docs/wordpress.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 docs/wordpress.md diff --git a/README.md b/README.md index 2f32eb5306..8586a02707 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ Go to `https://localhost`, and enjoy! - [Create static binaries](https://frankenphp.dev/docs/static/) - [Compile from sources](https://frankenphp.dev/docs/compile/) - [Monitoring FrankenPHP](https://frankenphp.dev/docs/metrics/) +- [WordPress integration](https://frankenphp.dev/docs/wordpress/) - [Laravel integration](https://frankenphp.dev/docs/laravel/) - [Known issues](https://frankenphp.dev/docs/known-issues/) - [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo) diff --git a/docs/wordpress.md b/docs/wordpress.md new file mode 100644 index 0000000000..3bca903d24 --- /dev/null +++ b/docs/wordpress.md @@ -0,0 +1,57 @@ +# WordPress + +Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high-performance stack with automatic HTTPS, HTTP/3, and Zstandard compression. + +## Minimal Installation + +1. [Download WordPress](https://wordpress.org/download/) +2. Extract the ZIP archive and open a terminal in the extracted directory +3. Run: + ```console + frankenphp php-server + ``` +4. Go to `http://localhost/wp-admin/` and follow the installation instructions +5. Enjoy! + +For a production-ready setup, prefer using `frankenphp run` with a `Caddyfile` like this one: + +```caddyfile +example.com + +php_server +encode zstd br gzip +log +``` + +## Hot Reload + +To use the [hot reload](hot-reload.md) feature with WordPress, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`: + +```caddyfile +localhost + +mercure { + anonymous +} + +php_server { + hot_reload +} +``` + +Then, add the code needed to load the JavaScript libraries in the `functions.php` file of your WordPress theme: + +```php +function hot_reload() { + ?> + + + + + + Date: Mon, 29 Dec 2025 18:00:38 +0100 Subject: [PATCH 05/59] chore(docs): fix linter errors (#2103) --- docs/logging.md | 2 +- docs/wordpress.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/logging.md b/docs/logging.md index 372779d60f..d591bf3630 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -59,7 +59,7 @@ these messages are routed to the Caddy logger. By default, messages sent via `error_log()` are treated as unstructured text. They are useful for compatibility with existing applications or libraries that rely on the standard PHP library. -### Example +### Example with error_log() ```php error_log("Database connection failed", 4); diff --git a/docs/wordpress.md b/docs/wordpress.md index 3bca903d24..bc9f2756fc 100644 --- a/docs/wordpress.md +++ b/docs/wordpress.md @@ -7,9 +7,11 @@ Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high- 1. [Download WordPress](https://wordpress.org/download/) 2. Extract the ZIP archive and open a terminal in the extracted directory 3. Run: + ```console frankenphp php-server ``` + 4. Go to `http://localhost/wp-admin/` and follow the installation instructions 5. Enjoy! From e0f01d12d6efc41d6d1d68c7dfc689d34e74d9cf Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Fri, 2 Jan 2026 10:23:16 +0100 Subject: [PATCH 06/59] Handle symlinking edge cases (#1660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This one is interesting — though I’m not sure the best way to provide a test. I will have to look into maybe an integration test because it is a careful dance between how we resolve paths in the Caddy module vs. workers. I looked into making a proper change (literally using the same logic everywhere), but I think it is best to wait until #1646 is merged. But anyway, this change deals with some interesting edge cases. I will use gherkin to describe them: ```gherkin Feature: FrankenPHP symlinked edge cases Background: Given a `test` folder And a `public` folder linked to `test` And a worker script located at `test/index.php` And a `test/nested` folder And a worker script located at `test/nested/index.php` Scenario: neighboring worker script Given frankenphp located in the test folder When I execute `frankenphp php-server --listen localhost:8080 -w index.php` from `public` Then I expect to see the worker script executed successfully Scenario: nested worker script Given frankenphp located in the test folder When I execute `frankenphp --listen localhost:8080 -w nested/index.php` from `public` Then I expect to see the worker script executed successfully Scenario: outside the symlinked folder Given frankenphp located in the root folder When I execute `frankenphp --listen localhost:8080 -w public/index.php` from the root folder Then I expect to see the worker script executed successfully Scenario: specified root directory Given frankenphp located in the root folder When I execute `frankenphp --listen localhost:8080 -w public/index.php -r public` from the root folder Then I expect to see the worker script executed successfully ``` Trying to write that out in regular English would be more complex IMHO. These scenarios should all pass now with this PR. --------- Signed-off-by: Marc Co-authored-by: henderkes Co-authored-by: Kévin Dunglas --- caddy/caddy_test.go | 250 +++++++++++++++++++++++ caddy/module.go | 14 +- internal/testext/exttest.go | 7 +- testdata/symlinks/public | 1 + testdata/symlinks/test/document-root.php | 13 ++ testdata/symlinks/test/index.php | 13 ++ testdata/symlinks/test/nested/index.php | 13 ++ worker.go | 9 +- 8 files changed, 315 insertions(+), 5 deletions(-) create mode 120000 testdata/symlinks/public create mode 100644 testdata/symlinks/test/document-root.php create mode 100644 testdata/symlinks/test/index.php create mode 100644 testdata/symlinks/test/nested/index.php diff --git a/caddy/caddy_test.go b/caddy/caddy_test.go index e359e3d4bc..4233fc3111 100644 --- a/caddy/caddy_test.go +++ b/caddy/caddy_test.go @@ -1500,3 +1500,253 @@ func TestLog(t *testing.T) { "", ) } + +// TestSymlinkWorkerPaths tests different ways to reference worker scripts in symlinked directories +func TestSymlinkWorkerPaths(t *testing.T) { + cwd, _ := os.Getwd() + publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public") + + t.Run("NeighboringWorkerScript", func(t *testing.T) { + // Scenario: neighboring worker script + // Given frankenphp located in the test folder + // When I execute `frankenphp php-server --listen localhost:8080 -w index.php` from `public` + // Then I expect to see the worker script executed successfully + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + + frankenphp { + worker `+publicDir+`/index.php 1 + } + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink true + } + } + } + `, "caddyfile") + + tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, "Request: 0\n") + }) + + t.Run("NestedWorkerScript", func(t *testing.T) { + // Scenario: nested worker script + // Given frankenphp located in the test folder + // When I execute `frankenphp --listen localhost:8080 -w nested/index.php` from `public` + // Then I expect to see the worker script executed successfully + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + + frankenphp { + worker `+publicDir+`/nested/index.php 1 + } + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink true + } + } + } + `, "caddyfile") + + tester.AssertGetResponse("http://localhost:"+testPort+"/nested/index.php", http.StatusOK, "Nested request: 0\n") + }) + + t.Run("OutsideSymlinkedFolder", func(t *testing.T) { + // Scenario: outside the symlinked folder + // Given frankenphp located in the root folder + // When I execute `frankenphp --listen localhost:8080 -w public/index.php` from the root folder + // Then I expect to see the worker script executed successfully + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + + frankenphp { + worker { + name outside_worker + file `+publicDir+`/index.php + num 1 + } + } + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink true + } + } + } + `, "caddyfile") + + tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, "Request: 0\n") + }) + + t.Run("SpecifiedRootDirectory", func(t *testing.T) { + // Scenario: specified root directory + // Given frankenphp located in the root folder + // When I execute `frankenphp --listen localhost:8080 -w public/index.php -r public` from the root folder + // Then I expect to see the worker script executed successfully + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + + frankenphp { + worker { + name specified_root_worker + file `+publicDir+`/index.php + num 1 + } + } + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink true + } + } + } + `, "caddyfile") + + tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, "Request: 0\n") + }) +} + +// TestSymlinkResolveRoot tests the resolve_root_symlink directive behavior +func TestSymlinkResolveRoot(t *testing.T) { + cwd, _ := os.Getwd() + testDir := filepath.Join(cwd, "..", "testdata", "symlinks", "test") + publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public") + + t.Run("ResolveRootSymlink", func(t *testing.T) { + // Tests that resolve_root_symlink directive works correctly + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + + frankenphp { + worker `+publicDir+`/document-root.php 1 + } + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink true + } + } + } + `, "caddyfile") + + // DOCUMENT_ROOT should be the resolved path (testDir) + tester.AssertGetResponse("http://localhost:"+testPort+"/document-root.php", http.StatusOK, "DOCUMENT_ROOT="+testDir+"\n") + }) + + t.Run("NoResolveRootSymlink", func(t *testing.T) { + // Tests that symlinks are preserved when resolve_root_symlink is false (non-worker mode) + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink false + } + } + } + `, "caddyfile") + + // DOCUMENT_ROOT should be the symlink path (publicDir) when resolve_root_symlink is false + tester.AssertGetResponse("http://localhost:"+testPort+"/document-root.php", http.StatusOK, "DOCUMENT_ROOT="+publicDir+"\n") + }) +} + +// TestSymlinkWorkerBehavior tests worker behavior with symlinked directories +func TestSymlinkWorkerBehavior(t *testing.T) { + cwd, _ := os.Getwd() + publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public") + + t.Run("WorkerScriptFailsWithoutWorkerMode", func(t *testing.T) { + // Tests that accessing a worker-only script without configuring it as a worker actually results in an error + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + } + } + } + `, "caddyfile") + + // Accessing the worker script without worker configuration MUST fail + // The script checks $_SERVER['FRANKENPHP_WORKER'] and dies if not set + tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, "Error: This script must be run in worker mode (FRANKENPHP_WORKER not set to '1')\n") + }) + + t.Run("MultipleRequests", func(t *testing.T) { + // Tests that symlinked workers handle multiple requests correctly + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port `+testPort+` + } + + localhost:`+testPort+` { + route { + php { + root `+publicDir+` + resolve_root_symlink true + worker index.php 1 + } + } + } + `, "caddyfile") + + // Make multiple requests - each should increment the counter + for i := 0; i < 5; i++ { + tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, fmt.Sprintf("Request: %d\n", i)) + } + }) +} diff --git a/caddy/module.go b/caddy/module.go index b116a5e1ac..6416362694 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -138,6 +138,15 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { } f.resolvedDocumentRoot = root + + // Also resolve symlinks in worker file paths when resolve_root_symlink is true + for i, wc := range f.Workers { + if !filepath.IsAbs(wc.FileName) { + continue + } + resolvedPath, _ := filepath.EvalSymlinks(wc.FileName) + f.Workers[i].FileName = resolvedPath + } } } @@ -181,7 +190,10 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c if documentRoot == "" && frankenphp.EmbeddedAppPath != "" { documentRoot = frankenphp.EmbeddedAppPath } - documentRootOption = frankenphp.WithRequestDocumentRoot(documentRoot, *f.ResolveRootSymlink) + // If we do not have a resolved document root, then we cannot resolve the symlink of our cwd because it may + // resolve to a different directory than the one we are currently in. + // This is especially important if there are workers running. + documentRootOption = frankenphp.WithRequestDocumentRoot(documentRoot, false) } else { documentRoot = f.resolvedDocumentRoot documentRootOption = frankenphp.WithRequestResolvedDocumentRoot(documentRoot) diff --git a/internal/testext/exttest.go b/internal/testext/exttest.go index abebee4c1d..1a8477d4a8 100644 --- a/internal/testext/exttest.go +++ b/internal/testext/exttest.go @@ -11,13 +11,14 @@ package testext // #include "extension.h" import "C" import ( - "github.com/dunglas/frankenphp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "io" "net/http/httptest" "testing" "unsafe" + + "github.com/dunglas/frankenphp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func testRegisterExtension(t *testing.T) { diff --git a/testdata/symlinks/public b/testdata/symlinks/public new file mode 120000 index 0000000000..30d74d2584 --- /dev/null +++ b/testdata/symlinks/public @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/testdata/symlinks/test/document-root.php b/testdata/symlinks/test/document-root.php new file mode 100644 index 0000000000..c21b2fc7fc --- /dev/null +++ b/testdata/symlinks/test/document-root.php @@ -0,0 +1,13 @@ + Date: Wed, 7 Jan 2026 09:21:03 +0100 Subject: [PATCH 07/59] fix: segmentation fault when registering multiple extensions (#2112) This PR fixes a segmentation fault when using frankenphp.RegisterExtension with more than one extension. The issue was a type mismatch between Go and C: Go passes a slice of pointers, but the C code was treating it as a contiguous array of structs. This caused invalid memory access when iterating past the first element. I created a minimal reproduction repo here: [https://github.com/y-l-g/frankenphp-extensions-segfault-repro](https://www.google.com/url?sa=E&q=https%3A%2F%2Fgithub.com%2Fy-l-g%2Ffrankenphp-extensions-segfault-repro) --- ext.go | 2 +- frankenphp.c | 6 +++--- frankenphp.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext.go b/ext.go index 8d565d4bff..1c0656c820 100644 --- a/ext.go +++ b/ext.go @@ -23,7 +23,7 @@ func registerExtensions() { } registerOnce.Do(func() { - C.register_extensions(extensions[0], C.int(len(extensions))) + C.register_extensions((**C.zend_module_entry)(unsafe.Pointer(&extensions[0])), C.int(len(extensions))) extensions = nil }) } diff --git a/frankenphp.c b/frankenphp.c index d048e576bd..fd487edb8e 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1262,7 +1262,7 @@ int frankenphp_reset_opcache(void) { int frankenphp_get_current_memory_limit() { return PG(memory_limit); } -static zend_module_entry *modules = NULL; +static zend_module_entry **modules = NULL; static int modules_len = 0; static int (*original_php_register_internal_extensions_func)(void) = NULL; @@ -1273,7 +1273,7 @@ PHPAPI int register_internal_extensions(void) { } for (int i = 0; i < modules_len; i++) { - if (zend_register_internal_module(&modules[i]) == NULL) { + if (zend_register_internal_module(modules[i]) == NULL) { return FAILURE; } } @@ -1284,7 +1284,7 @@ PHPAPI int register_internal_extensions(void) { return SUCCESS; } -void register_extensions(zend_module_entry *m, int len) { +void register_extensions(zend_module_entry **m, int len) { modules = m; modules_len = len; diff --git a/frankenphp.h b/frankenphp.h index efbd5fc48f..c833c44f97 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -76,6 +76,6 @@ void frankenphp_register_bulk( ht_key_value_pair auth_type, ht_key_value_pair remote_ident, ht_key_value_pair request_uri, ht_key_value_pair ssl_cipher); -void register_extensions(zend_module_entry *m, int len); +void register_extensions(zend_module_entry **m, int len); #endif From ecad5ec0a0af3ba122040ffd6b29c9b28230d476 Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:40:54 +0100 Subject: [PATCH 08/59] fix: prevent segfault on early shutdown. (#2120) Fixes #2114 This early [Shutdown](https://github.com/php/frankenphp/blob/11160fb7b31171d706cf9933abb9102dbb1cdb3c/frankenphp.go#L281) introduced in #2031 segfaults instead of returning an error since threads have not started yet. --- phpmainthread.go | 3 +++ phpmainthread_test.go | 2 +- watcher.go | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/phpmainthread.go b/phpmainthread.go index cecadc1653..e10ce9e42f 100644 --- a/phpmainthread.go +++ b/phpmainthread.go @@ -79,6 +79,9 @@ func initPHPThreads(numThreads int, numMaxThreads int, phpIni map[string]string) } func drainPHPThreads() { + if mainThread == nil { + return // mainThread was never initialized + } doneWG := sync.WaitGroup{} doneWG.Add(len(phpThreads)) mainThread.state.Set(state.ShuttingDown) diff --git a/phpmainthread_test.go b/phpmainthread_test.go index 7e6bf32c1e..b54647ae07 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -96,7 +96,7 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) { var ( isDone atomic.Bool - wg sync.WaitGroup + wg sync.WaitGroup ) numThreads := 10 diff --git a/watcher.go b/watcher.go index eb2b09e29f..cfe133e5ab 100644 --- a/watcher.go +++ b/watcher.go @@ -10,7 +10,7 @@ import ( ) type hotReloadOpt struct { - hotReload []*watcher.PatternGroup + hotReload []*watcher.PatternGroup } var restartWorkers atomic.Bool From c6b2b02277c44673c7e2ef5a2c9cdc14021432db Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:44:46 +0100 Subject: [PATCH 09/59] fix(extgen): correctly handle const blocks to declare iota constants (#2086) While continuing the work on #2011, I realized that constant declarations have a problem when using `iota`. I mean, it technically works, but const *blocks* we not supported which means that setting all constants to `iota` as shown in the documentation was non-sensical, as `iota` resets every time outside of const blocks. So, this is between the bug fix and the feature. To me, it's a bug fix as the behavior wasn't the one intended when creating extgen. --- docs/extensions.md | 26 +++-- docs/fr/extensions.md | 26 +++-- internal/extgen/constparser.go | 88 +++++++++++++- internal/extgen/constparser_test.go | 175 +++++++++++++++++++++++++++- internal/extgen/integration_test.go | 1 + testdata/integration/constants.go | 17 ++- 6 files changed, 301 insertions(+), 32 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index 0dbc020ba9..b4b83e87e9 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -406,12 +406,15 @@ const MAX_CONNECTIONS = 100 const API_VERSION = "1.2.3" //export_php:const -const STATUS_OK = iota - -//export_php:const -const STATUS_ERROR = iota +const ( + STATUS_OK = iota + STATUS_ERROR +) ``` +> [!NOTE] +> PHP constants will take the name of the Go constant, thus using upper case letters is recommended. + #### Class Constants Use the `//export_php:classconst ClassName` directive to create constants that belong to a specific PHP class: @@ -429,15 +432,16 @@ const STATUS_INACTIVE = 0 const ROLE_ADMIN = "admin" //export_php:classconst Order -const STATE_PENDING = iota - -//export_php:classconst Order -const STATE_PROCESSING = iota - -//export_php:classconst Order -const STATE_COMPLETED = iota +const ( + STATE_PENDING = iota + STATE_PROCESSING + STATE_COMPLETED +) ``` +> [!NOTE] +> Just like global constants, the class constants will take the name of the Go constant. + Class constants are accessible using the class name scope in PHP: ```php diff --git a/docs/fr/extensions.md b/docs/fr/extensions.md index caf50e9ca7..488a28c5de 100644 --- a/docs/fr/extensions.md +++ b/docs/fr/extensions.md @@ -402,12 +402,15 @@ const MAX_CONNECTIONS = 100 const API_VERSION = "1.2.3" //export_php:const -const STATUS_OK = iota - -//export_php:const -const STATUS_ERROR = iota +const ( + STATUS_OK = iota + STATUS_ERROR +) ``` +> [!NOTE] +> Les constantes PHP prennent le nom de la constante Go, d'où l'utilisation de majuscules pour les noms des constants en Go. + #### Constantes de Classe Utilisez la directive `//export_php:classconst ClassName` pour créer des constantes qui appartiennent à une classe PHP spécifique : @@ -425,15 +428,16 @@ const STATUS_INACTIVE = 0 const ROLE_ADMIN = "admin" //export_php:classconst Order -const STATE_PENDING = iota - -//export_php:classconst Order -const STATE_PROCESSING = iota - -//export_php:classconst Order -const STATE_COMPLETED = iota +const ( + STATE_PENDING = iota + STATE_PROCESSING + STATE_COMPLETED +) ``` +> [!NOTE] +> Comme les constantes globales, les constantes de classe prennent le nom de la constante Go. + Les constantes de classe sont accessibles en utilisant la portée du nom de classe en PHP : ```php diff --git a/internal/extgen/constparser.go b/internal/extgen/constparser.go index 2f304895d9..86f80337cf 100644 --- a/internal/extgen/constparser.go +++ b/internal/extgen/constparser.go @@ -34,6 +34,10 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e expectClassConstDecl := false currentClassName := "" currentConstantValue := 0 + inConstBlock := false + exportAllInBlock := false + lastConstValue := "" + lastConstWasIota := false for scanner.Scan() { lineNumber++ @@ -55,7 +59,26 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e continue } - if (expectConstDecl || expectClassConstDecl) && strings.HasPrefix(line, "const ") { + if strings.HasPrefix(line, "const (") { + inConstBlock = true + if expectConstDecl || expectClassConstDecl { + exportAllInBlock = true + } + continue + } + + if inConstBlock && line == ")" { + inConstBlock = false + exportAllInBlock = false + expectConstDecl = false + expectClassConstDecl = false + currentClassName = "" + lastConstValue = "" + lastConstWasIota = false + continue + } + + if (expectConstDecl || expectClassConstDecl) && strings.HasPrefix(line, "const ") && !inConstBlock { matches := constDeclRegex.FindStringSubmatch(line) if len(matches) == 3 { name := matches[1] @@ -72,10 +95,11 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e constant.PhpType = determineConstantType(value) if constant.IsIota { - // affect a default value because user didn't give one constant.Value = fmt.Sprintf("%d", currentConstantValue) constant.PhpType = phpInt currentConstantValue++ + lastConstWasIota = true + lastConstValue = constant.Value } constants = append(constants, constant) @@ -84,7 +108,65 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e } expectConstDecl = false expectClassConstDecl = false - } else if (expectConstDecl || expectClassConstDecl) && !strings.HasPrefix(line, "//") && line != "" { + } else if inConstBlock && (expectConstDecl || expectClassConstDecl || exportAllInBlock) { + constBlockDeclRegex := regexp.MustCompile(`^(\w+)\s*=\s*(.+)$`) + if matches := constBlockDeclRegex.FindStringSubmatch(line); len(matches) == 3 { + name := matches[1] + value := strings.TrimSpace(matches[2]) + + constant := phpConstant{ + Name: name, + Value: value, + IsIota: value == "iota", + lineNumber: lineNumber, + ClassName: currentClassName, + } + + constant.PhpType = determineConstantType(value) + + if constant.IsIota { + constant.Value = fmt.Sprintf("%d", currentConstantValue) + constant.PhpType = phpInt + currentConstantValue++ + lastConstWasIota = true + lastConstValue = constant.Value + } else { + lastConstWasIota = false + lastConstValue = value + } + + constants = append(constants, constant) + expectConstDecl = false + expectClassConstDecl = false + } else { + constNameRegex := regexp.MustCompile(`^(\w+)$`) + if matches := constNameRegex.FindStringSubmatch(line); len(matches) == 2 { + name := matches[1] + + constant := phpConstant{ + Name: name, + Value: "", + IsIota: lastConstWasIota, + lineNumber: lineNumber, + ClassName: currentClassName, + } + + if lastConstWasIota { + constant.Value = fmt.Sprintf("%d", currentConstantValue) + constant.PhpType = phpInt + currentConstantValue++ + lastConstValue = constant.Value + } else { + constant.Value = lastConstValue + constant.PhpType = determineConstantType(lastConstValue) + } + + constants = append(constants, constant) + expectConstDecl = false + expectClassConstDecl = false + } + } + } else if (expectConstDecl || expectClassConstDecl) && !strings.HasPrefix(line, "//") && line != "" && !inConstBlock { // we expected a const declaration but found something else, reset expectConstDecl = false expectClassConstDecl = false diff --git a/internal/extgen/constparser_test.go b/internal/extgen/constparser_test.go index 29f0e38fdf..7c5ce89f27 100644 --- a/internal/extgen/constparser_test.go +++ b/internal/extgen/constparser_test.go @@ -221,7 +221,7 @@ func TestConstantParserIotaSequence(t *testing.T) { //export_php:const const FirstIota = iota -//export_php:const +//export_php:const const SecondIota = iota //export_php:const @@ -244,6 +244,179 @@ const ThirdIota = iota` } } +func TestConstantParserConstBlock(t *testing.T) { + input := `package main + +const ( + // export_php:const + STATUS_PENDING = iota + + // export_php:const + STATUS_PROCESSING + + // export_php:const + STATUS_COMPLETED +)` + + tmpDir := t.TempDir() + fileName := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(fileName, []byte(input), 0644)) + + parser := &ConstantParser{} + constants, err := parser.parse(fileName) + assert.NoError(t, err, "parse() error") + + assert.Len(t, constants, 3, "Expected 3 constants") + + expectedNames := []string{"STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED"} + expectedValues := []string{"0", "1", "2"} + + for i, c := range constants { + assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i]) + assert.True(t, c.IsIota, "Expected constant %d to be iota", i) + assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i]) + assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i) + } +} + +func TestConstantParserConstBlockWithBlockLevelDirective(t *testing.T) { + input := `package main + +// export_php:const +const ( + STATUS_PENDING = iota + STATUS_PROCESSING + STATUS_COMPLETED +)` + + tmpDir := t.TempDir() + fileName := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(fileName, []byte(input), 0644)) + + parser := &ConstantParser{} + constants, err := parser.parse(fileName) + assert.NoError(t, err, "parse() error") + + assert.Len(t, constants, 3, "Expected 3 constants") + + expectedNames := []string{"STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED"} + expectedValues := []string{"0", "1", "2"} + + for i, c := range constants { + assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i]) + assert.True(t, c.IsIota, "Expected constant %d to be iota", i) + assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i]) + assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i) + } +} + +func TestConstantParserMixedConstBlockAndIndividual(t *testing.T) { + input := `package main + +// export_php:const +const INDIVIDUAL = 42 + +const ( + // export_php:const + BLOCK_ONE = iota + + // export_php:const + BLOCK_TWO +) + +// export_php:const +const ANOTHER_INDIVIDUAL = "test"` + + tmpDir := t.TempDir() + fileName := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(fileName, []byte(input), 0644)) + + parser := &ConstantParser{} + constants, err := parser.parse(fileName) + assert.NoError(t, err, "parse() error") + + assert.Len(t, constants, 4, "Expected 4 constants") + + assert.Equal(t, "INDIVIDUAL", constants[0].Name) + assert.Equal(t, "42", constants[0].Value) + assert.Equal(t, phpInt, constants[0].PhpType) + + assert.Equal(t, "BLOCK_ONE", constants[1].Name) + assert.Equal(t, "0", constants[1].Value) + assert.True(t, constants[1].IsIota) + + assert.Equal(t, "BLOCK_TWO", constants[2].Name) + assert.Equal(t, "1", constants[2].Value) + assert.True(t, constants[2].IsIota) + + assert.Equal(t, "ANOTHER_INDIVIDUAL", constants[3].Name) + assert.Equal(t, `"test"`, constants[3].Value) + assert.Equal(t, phpString, constants[3].PhpType) +} + +func TestConstantParserClassConstBlock(t *testing.T) { + input := `package main + +// export_php:classconst Config +const ( + MODE_DEBUG = 1 + MODE_PRODUCTION = 2 + MODE_TEST = 3 +)` + + tmpDir := t.TempDir() + fileName := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(fileName, []byte(input), 0644)) + + parser := &ConstantParser{} + constants, err := parser.parse(fileName) + assert.NoError(t, err, "parse() error") + + assert.Len(t, constants, 3, "Expected 3 class constants") + + expectedNames := []string{"MODE_DEBUG", "MODE_PRODUCTION", "MODE_TEST"} + expectedValues := []string{"1", "2", "3"} + + for i, c := range constants { + assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i]) + assert.Equal(t, "Config", c.ClassName, "Expected constant %d to belong to Config class", i) + assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i]) + assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i) + } +} + +func TestConstantParserClassConstBlockWithIota(t *testing.T) { + input := `package main + +// export_php:classconst Status +const ( + STATUS_PENDING = iota + STATUS_ACTIVE + STATUS_COMPLETED +)` + + tmpDir := t.TempDir() + fileName := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(fileName, []byte(input), 0644)) + + parser := &ConstantParser{} + constants, err := parser.parse(fileName) + assert.NoError(t, err, "parse() error") + + assert.Len(t, constants, 3, "Expected 3 class constants") + + expectedNames := []string{"STATUS_PENDING", "STATUS_ACTIVE", "STATUS_COMPLETED"} + expectedValues := []string{"0", "1", "2"} + + for i, c := range constants { + assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i]) + assert.Equal(t, "Status", c.ClassName, "Expected constant %d to belong to Status class", i) + assert.True(t, c.IsIota, "Expected constant %d to be iota", i) + assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i]) + assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i) + } +} + func TestConstantParserTypeDetection(t *testing.T) { tests := []struct { name string diff --git a/internal/extgen/integration_test.go b/internal/extgen/integration_test.go index 86723fc395..4c35399069 100644 --- a/internal/extgen/integration_test.go +++ b/internal/extgen/integration_test.go @@ -480,6 +480,7 @@ func TestConstants(t *testing.T) { []string{ "TEST_MAX_RETRIES", "TEST_API_VERSION", "TEST_ENABLED", "TEST_PI", "STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED", + "ONE", "TWO", }, ) require.NoError(t, err, "all constants, functions, and classes should be accessible from PHP") diff --git a/testdata/integration/constants.go b/testdata/integration/constants.go index 9bb6bab9a4..b40c1a5883 100644 --- a/testdata/integration/constants.go +++ b/testdata/integration/constants.go @@ -21,13 +21,18 @@ const TEST_ENABLED = true const TEST_PI = 3.14159 // export_php:const -const STATUS_PENDING = iota - -// export_php:const -const STATUS_PROCESSING = iota +const ( + STATUS_PENDING = iota + STATUS_PROCESSING + STATUS_COMPLETED +) -// export_php:const -const STATUS_COMPLETED = iota +const ( + // export_php:const + ONE = 1 + // export_php:const + TWO = 2 +) // export_php:class Config type ConfigStruct struct { From 0ccfb827fcfd15780efdf7ce714528bc8dad1252 Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:06:48 +0100 Subject: [PATCH 10/59] docs: LLM translations Adds a workflow that triggers every time the English docs are updated to translate to other languages. --------- Co-authored-by: henderkes --- .github/workflows/translate.yaml | 68 +++++++++++++++++ docs/translate.php | 126 +++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 .github/workflows/translate.yaml create mode 100644 docs/translate.php diff --git a/.github/workflows/translate.yaml b/.github/workflows/translate.yaml new file mode 100644 index 0000000000..274dc75857 --- /dev/null +++ b/.github/workflows/translate.yaml @@ -0,0 +1,68 @@ +name: Translate Docs +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} +on: + push: + branches: + - main + paths: + - "docs/*" +permissions: + contents: write + pull-requests: write +jobs: + build: + name: Translate Docs + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + - id: md_files + run: | + FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'docs/*.md') + FILES=$(echo "$FILES" | xargs -n1 basename | tr '\n' ' ') + [ -z "$FILES" ] && echo "found=false" >> "$GITHUB_OUTPUT" || echo "found=true" >> "$GITHUB_OUTPUT" + echo "files=$FILES" >> "$GITHUB_OUTPUT" + - name: Set up PHP + if: steps.md_files.outputs.found == 'true' + uses: shivammathur/setup-php@v2 + with: + php-version: "8.5" + - name: run translation script + if: steps.md_files.outputs.found == 'true' + env: + GEMINI_API_KEY: "${{ secrets.GEMINI_API_KEY }}" + MD_FILES: "${{ steps.md_files.outputs.files }}" + run: | + php ./docs/translate.php "$MD_FILES" + - name: Run Linter + if: steps.md_files.outputs.found == 'true' + uses: super-linter/super-linter/slim@v8 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LINTER_RULES_PATH: / + MARKDOWN_CONFIG_FILE: .markdown-lint.yaml + FIX_NATURAL_LANGUAGE: true + FIX_MARKDOWN: true + - name: Create Pull Request + if: steps.md_files.outputs.found == 'true' + uses: peter-evans/create-pull-request@v8 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + title: "docs: update translations" + commit-message: "docs: update translations" + committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + branch: translations/${{ github.run_id }} + delete-branch: true + body: | + Translation updates for: ${{ steps.md_files.outputs.files }}. + labels: | + translations + bot + draft: false diff --git a/docs/translate.php b/docs/translate.php new file mode 100644 index 0000000000..d491b58e8c --- /dev/null +++ b/docs/translate.php @@ -0,0 +1,126 @@ + 'Chinese', + 'fr' => 'French', + 'ja' => 'Japanese', + 'pt-br' => 'Portuguese (Brazilian)', + 'ru' => 'Russian', + 'tr' => 'Turkish', +]; + +function makeGeminiRequest(string $systemPrompt, string $userPrompt, string $model, string $apiKey, int $reties = 2): string +{ + $url = "https://generativelanguage.googleapis.com/v1beta/models/$model:generateContent"; + $body = json_encode([ + "contents" => [ + ["role" => "model", "parts" => ['text' => $systemPrompt]], + ["role" => "user", "parts" => ['text' => $userPrompt]] + ], + ]); + + $response = @file_get_contents($url, false, stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-Type: application/json\r\nX-Goog-Api-Key: $apiKey\r\nContent-Length: " . strlen($body) . "\r\n", + 'content' => $body, + 'timeout' => 300, + ] + ])); + $generatedDocs = json_decode($response, true)['candidates'][0]['content']['parts'][0]['text'] ?? ''; + + if (!$response || !$generatedDocs) { + print_r(error_get_last()); + print_r($response); + if ($reties > 0) { + echo "Retrying... ($reties retries left)\n"; + sleep(SLEEP_SECONDS_BETWEEN_REQUESTS); + return makeGeminiRequest($systemPrompt, $userPrompt, $model, $apiKey, $reties - 1); + } + exit(1); + } + + return $generatedDocs; +} + +function createPrompt(string $language, string $englishFile, string $currentTranslation): array +{ + $systemPrompt = << str_ends_with($filename, '.md')); +foreach ($files as $file) { + $englishFile = file_get_contents(__DIR__ . "/$file"); + if ($fileToTranslate && !in_array($file, $fileToTranslate)) { + continue; + } + foreach (LANGUAGES as $language => $languageName) { + echo "Translating $file to $languageName\n"; + $currentTranslation = file_get_contents(__DIR__ . "/$language/$file") ?: ''; + [$systemPrompt, $userPrompt] = createPrompt($language, $englishFile, $currentTranslation); + $markdown = makeGeminiRequest($systemPrompt, $userPrompt, MODEL, $apiKey); + + echo "Writing translated file to $language/$file\n"; + file_put_contents(__DIR__ . "/$language/$file", sanitizeMarkdown($markdown)); + + echo "sleeping to avoid rate limiting...\n"; + sleep(SLEEP_SECONDS_BETWEEN_REQUESTS); + } +} From f4080c0d0441fd486b950195420514f9551dd614 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:14:24 +0100 Subject: [PATCH 11/59] ci: bump the github-actions group with 3 updates (#2123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 3 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/download-artifact](https://github.com/actions/download-artifact) and [actions/cache](https://github.com/actions/cache). Updates `actions/upload-artifact` from 5 to 6
Commits
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • e516bc8 docs: correct description of Node.js 24 support in README
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • 38d4c79 chore: rebuild dist
  • 7d27270 chore: add missing license cache files for @​actions/core, @​actions/io, and mi...
  • 5f643d3 chore: update license files for @​actions/artifact@​5.0.1 dependencies
  • 1df1684 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • b5b1a91 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • Additional commits viewable in compare view

Updates `actions/download-artifact` from 6 to 7
Release notes

Sourced from actions/download-artifact's releases.

v7.0.0

v7 - What's new

[!IMPORTANT] actions/download-artifact@v7 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v6 had preliminary support for Node 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

New Contributors

Full Changelog: https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0

Commits
  • 37930b1 Merge pull request #452 from actions/download-artifact-v7-release
  • 72582b9 doc: update readme
  • 0d2ec9d chore: release v7.0.0 for Node.js 24 support
  • fd7ae8f Merge pull request #451 from actions/fix-storage-blob
  • d484700 chore: restore minimatch.dep.yml license file
  • 03a8080 chore: remove obsolete dependency license files
  • 56fe6d9 chore: update @​actions/artifact license file to 5.0.1
  • 8e3ebc4 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • 1e3c4b4 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • 458627d chore: use local @​actions/artifact package for Node.js 24 testing
  • Additional commits viewable in compare view

Updates `actions/cache` from 4 to 5
Release notes

Sourced from actions/cache's releases.

v5.0.0

[!IMPORTANT] actions/cache@v5 runs on the Node.js 24 runtime and requires a minimum Actions Runner version of 2.327.1.

If you are using self-hosted runners, ensure they are updated before upgrading.


What's Changed

Full Changelog: https://github.com/actions/cache/compare/v4.3.0...v5.0.0

v4.3.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/cache/compare/v4...v4.3.0

v4.2.4

What's Changed

New Contributors

Full Changelog: https://github.com/actions/cache/compare/v4...v4.2.4

v4.2.3

What's Changed

  • Update to use @​actions/cache 4.0.3 package & prepare for new release by @​salmanmkc in actions/cache#1577 (SAS tokens for cache entries are now masked in debug logs)

New Contributors

Full Changelog: https://github.com/actions/cache/compare/v4.2.2...v4.2.3

... (truncated)

Changelog

Sourced from actions/cache's changelog.

Releases

Changelog

5.0.1

  • Update @azure/storage-blob to ^12.29.1 via @actions/cache@5.0.1 #1685

5.0.0

[!IMPORTANT] actions/cache@v5 runs on the Node.js 24 runtime and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

4.3.0

  • Bump @actions/cache to v4.1.0

4.2.4

  • Bump @actions/cache to v4.0.5

4.2.3

  • Bump @actions/cache to v4.0.3 (obfuscates SAS token in debug logs for cache entries)

4.2.2

  • Bump @actions/cache to v4.0.2

4.2.1

  • Bump @actions/cache to v4.0.1

4.2.0

TLDR; The cache backend service has been rewritten from the ground up for improved performance and reliability. actions/cache now integrates with the new cache service (v2) APIs.

The new service will gradually roll out as of February 1st, 2025. The legacy service will also be sunset on the same date. Changes in these release are fully backward compatible.

We are deprecating some versions of this action. We recommend upgrading to version v4 or v3 as soon as possible before February 1st, 2025. (Upgrade instructions below).

If you are using pinned SHAs, please use the SHAs of versions v4.2.0 or v3.4.0

If you do not upgrade, all workflow runs using any of the deprecated actions/cache will fail.

Upgrading to the recommended versions will not break your workflows.

4.1.2

... (truncated)

Commits
  • 9255dc7 Merge pull request #1686 from actions/cache-v5.0.1-release
  • 8ff5423 chore: release v5.0.1
  • 9233019 Merge pull request #1685 from salmanmkc/node24-storage-blob-fix
  • b975f2b fix: add peer property to package-lock.json for dependencies
  • d0a0e18 fix: update license files for @​actions/cache, fast-xml-parser, and strnum
  • 74de208 fix: update @​actions/cache to ^5.0.1 for Node.js 24 punycode fix
  • ac7f115 peer
  • b0f846b fix: update @​actions/cache with storage-blob fix for Node.js 24 punycode depr...
  • a783357 Merge pull request #1684 from actions/prepare-cache-v5-release
  • 3bb0d78 docs: highlight v5 runner requirement in releases
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yaml | 6 +++--- .github/workflows/sanitizers.yaml | 2 +- .github/workflows/static.yaml | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 73d212c6c7..01928c32d9 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -208,7 +208,7 @@ jobs: VARIANT: ${{ matrix.variant }} - name: Upload builder metadata if: fromJson(needs.prepare.outputs.push) - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: metadata-builder-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }} path: /tmp/metadata/builder/* @@ -216,7 +216,7 @@ jobs: retention-days: 1 - name: Upload runner metadata if: fromJson(needs.prepare.outputs.push) - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: metadata-runner-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }} path: /tmp/metadata/runner/* @@ -248,7 +248,7 @@ jobs: target: ["builder", "runner"] steps: - name: Download metadata - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: pattern: metadata-${{ matrix.target }}-${{ matrix.variant }}-* path: /tmp/metadata diff --git a/.github/workflows/sanitizers.yaml b/.github/workflows/sanitizers.yaml index 49815aaca0..85bf76cb4e 100644 --- a/.github/workflows/sanitizers.yaml +++ b/.github/workflows/sanitizers.yaml @@ -57,7 +57,7 @@ jobs: echo archive="$(jq -r '.[] .source[] | select(.filename |endswith(".xz")) | "https://www.php.net/distributions/" + .filename' version.json)" >> "$GITHUB_OUTPUT" - name: Cache PHP id: cache-php - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: php/target key: php-sanitizers-${{ matrix.sanitizer }}-${{ runner.arch }}-${{ steps.determine-php-version.outputs.version }} diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index cb97a302be..9b671727c2 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -170,7 +170,7 @@ jobs: METADATA: ${{ steps.build.outputs.metadata }} - name: Upload metadata if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }} path: /tmp/metadata/* @@ -188,7 +188,7 @@ jobs: PLATFORM: ${{ matrix.platform }} - name: Upload artifact if: ${{ !fromJson(needs.prepare.outputs.push) }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} @@ -320,7 +320,7 @@ jobs: METADATA: ${{ steps.build.outputs.metadata }} - name: Upload metadata if: fromJson(needs.prepare.outputs.push) - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: metadata-static-builder-gnu-${{ steps.prepare.outputs.sanitized_platform }} path: /tmp/metadata-gnu/* @@ -344,7 +344,7 @@ jobs: PLATFORM: ${{ matrix.platform }} - name: Upload artifact if: ${{ !fromJson(needs.prepare.outputs.push) }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu-files path: gh-output/* @@ -380,13 +380,13 @@ jobs: if: fromJson(needs.prepare.outputs.push) steps: - name: Download metadata - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: pattern: metadata-static-builder-musl-* path: /tmp/metadata merge-multiple: true - name: Download GNU metadata - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: pattern: metadata-static-builder-gnu-* path: /tmp/metadata-gnu @@ -475,7 +475,7 @@ jobs: NO_COMPRESS: ${{ github.event_name == 'pull_request' && '1' || '' }} - name: Upload logs if: ${{ failure() }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: path: dist/static-php-cli/log name: static-php-cli-log-${{ matrix.platform }}-${{ github.sha }} @@ -485,7 +485,7 @@ jobs: subject-path: ${{ github.workspace }}/dist/frankenphp-mac-* - name: Upload artifact if: github.ref_type == 'branch' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: frankenphp-mac-${{ matrix.platform }} path: dist/frankenphp-mac-${{ matrix.platform }} From 0b470ab27c359f2c13938c11dfe5e2784c6f9a42 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:14:56 +0100 Subject: [PATCH 12/59] ci: bump the github-actions group (#2111) Supersedes #2110 From 1bfa3db09625f1437adbf2cf576834007fdc9bdc Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:20:54 +0100 Subject: [PATCH 13/59] fix(extgen): use fast ZPP (#2088) Fast ZPP is being more and more widely used in php-src with PR such as https://github.com/php/php-src/pull/20441 or https://github.com/php/php-src/pull/20644, let's use it here as well. --- internal/extgen/paramparser.go | 4 +--- internal/extgen/paramparser_test.go | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/extgen/paramparser.go b/internal/extgen/paramparser.go index bd7bb3532a..3f298904be 100644 --- a/internal/extgen/paramparser.go +++ b/internal/extgen/paramparser.go @@ -88,9 +88,7 @@ func (pp *ParameterParser) getDefaultValue(param phpParameter, fallback string) func (pp *ParameterParser) generateParamParsing(params []phpParameter, requiredCount int) string { if len(params) == 0 { - return ` if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - }` + return ` ZEND_PARSE_PARAMETERS_NONE();` } var builder strings.Builder diff --git a/internal/extgen/paramparser_test.go b/internal/extgen/paramparser_test.go index b0cdf6b9bb..8d19e53f00 100644 --- a/internal/extgen/paramparser_test.go +++ b/internal/extgen/paramparser_test.go @@ -223,9 +223,7 @@ func TestParameterParser_GenerateParamParsing(t *testing.T) { name: "no parameters", params: []phpParameter{}, requiredCount: 0, - expected: ` if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - }`, + expected: ` ZEND_PARSE_PARAMETERS_NONE();`, }, { name: "single required string parameter", From d9ba18fd239700fab0541c372e44d99d58779285 Mon Sep 17 00:00:00 2001 From: Jellyfrog Date: Fri, 16 Jan 2026 18:15:13 +0100 Subject: [PATCH 14/59] fix: correct path to composer installed.json (#2127) Signed-off-by: Jellyfrog --- build-static.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-static.sh b/build-static.sh index c22d53f017..3485e8c5f4 100755 --- a/build-static.sh +++ b/build-static.sh @@ -150,7 +150,7 @@ fi # Extensions to build if [ -z "${PHP_EXTENSIONS}" ]; then # enable EMBED mode, first check if project has dumped extensions - if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ] && [ -f "${EMBED}/composer.lock" ] && [ -f "${EMBED}/vendor/installed.json" ]; then + if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ] && [ -f "${EMBED}/composer.lock" ] && [ -f "${EMBED}/vendor/composer/installed.json" ]; then cd "${EMBED}" # read the extensions using spc dump-extensions PHP_EXTENSIONS=$(${spcCommand} dump-extensions "${EMBED}" --format=text --no-dev --no-ext-output="${defaultExtensions}") From 38bcace957b8ea039cd210168e42f882e6ad64a0 Mon Sep 17 00:00:00 2001 From: Xavier Leune Date: Fri, 23 Jan 2026 12:10:05 +0100 Subject: [PATCH 15/59] fix(doc): missing export for tests (#2138) Documentation misses an export we can find in [this comment](https://github.com/php/frankenphp/issues/1758#issuecomment-3092470632) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87750875b6..f42eab7df2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,7 @@ If your Docker version is lower than 23.0, the build will fail due to dockerigno ## Running the test suite ```console +export CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go test -tags watcher -race -v ./... ``` From 07518a743c6ccb2d43acda2b61bfc095e898d8f6 Mon Sep 17 00:00:00 2001 From: Marc Date: Fri, 23 Jan 2026 20:46:34 +0100 Subject: [PATCH 16/59] don't overwrite SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES unconditionally (#2135) fixes https://github.com/php/frankenphp/issues/2134 Signed-off-by: Marc --- build-static.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build-static.sh b/build-static.sh index 3485e8c5f4..638688c0a2 100755 --- a/build-static.sh +++ b/build-static.sh @@ -198,7 +198,9 @@ else SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS} -fPIE -fstack-protector-strong -O2 -w -s" fi export SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS -export SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" +if [ -z "$SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES" ]; then + export SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" +fi # Build FrankenPHP ${spcCommand} doctor --auto-fix From 0c2a0105b56d29b30d9373cf935eb098ea95ca1b Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:42:32 +0100 Subject: [PATCH 17/59] fix: let PHP handle basic auth. (#2142) I noticed that PHP likes to handle and free basic auth parameters internally (see [here](https://github.com/php/php-src/blob/9f774e3a85d34f4aa1558613a9df7703ad3bb513/main/main.c#L2739) and [here](https://github.com/php/php-src/blob/9f774e3a85d34f4aa1558613a9df7703ad3bb513/main/SAPI.c#L514-L525)). This PR changes it so the basic auth header is forwarded to PHP instead of resolving it in go. I suspect that this might fix some crashes in shutdown functions (like #2121 and #1841) since it allows us freeing the `request_info` after shutdown is finished. I haven't been able to reproduce these crashes yet though. --- cgi.go | 21 +++++++++------------ frankenphp.c | 15 ++++++++------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/cgi.go b/cgi.go index 63fb1339b9..9804c969df 100644 --- a/cgi.go +++ b/cgi.go @@ -277,23 +277,13 @@ func splitPos(path string, splitPath []string) int { // See: https://github.com/php/php-src/blob/345e04b619c3bc11ea17ee02cdecad6ae8ce5891/main/SAPI.h#L72 // //export go_update_request_info -func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) { +func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) *C.char { thread := phpThreads[threadIndex] fc := thread.frankenPHPContext() request := fc.request if request == nil { - return - } - - authUser, authPassword, ok := request.BasicAuth() - if ok { - if authPassword != "" { - info.auth_password = thread.pinCString(authPassword) - } - if authUser != "" { - info.auth_user = thread.pinCString(authUser) - } + return nil } info.request_method = thread.pinCString(request.Method) @@ -311,6 +301,13 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) info.request_uri = thread.pinCString(request.URL.RequestURI()) info.proto_num = C.int(request.ProtoMajor*1000 + request.ProtoMinor) + + authorizationHeader := request.Header.Get("Authorization") + if authorizationHeader == "" { + return nil + } + + return thread.pinCString(authorizationHeader) } // SanitizedPathJoin performs filepath.Join(root, reqPath) that diff --git a/frankenphp.c b/frankenphp.c index fd487edb8e..cb910c81ae 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -85,7 +85,11 @@ static void frankenphp_update_request_context() { /* status It is not reset by zend engine, set it to 200. */ SG(sapi_headers).http_response_code = 200; - go_update_request_info(thread_index, &SG(request_info)); + char *authorization_header = + go_update_request_info(thread_index, &SG(request_info)); + + /* let PHP handle basic auth */ + php_handle_auth_data(authorization_header); } static void frankenphp_free_request_context() { @@ -95,8 +99,6 @@ static void frankenphp_free_request_context() { } /* freed via thread.Unpin() */ - SG(request_info).auth_password = NULL; - SG(request_info).auth_user = NULL; SG(request_info).request_method = NULL; SG(request_info).query_string = NULL; SG(request_info).content_type = NULL; @@ -187,9 +189,9 @@ static void frankenphp_worker_request_shutdown() { zend_end_try(); /* SAPI related shutdown (free stuff) */ - frankenphp_free_request_context(); zend_try { sapi_deactivate(); } zend_end_try(); + frankenphp_free_request_context(); zend_set_memory_limit(PG(memory_limit)); } @@ -609,8 +611,8 @@ static zend_module_entry frankenphp_module = { STANDARD_MODULE_PROPERTIES}; static void frankenphp_request_shutdown() { - frankenphp_free_request_context(); php_request_shutdown((void *)0); + frankenphp_free_request_context(); } static int frankenphp_startup(sapi_module_struct *sapi_module) { @@ -1055,8 +1057,7 @@ static int frankenphp_request_startup() { return SUCCESS; } - frankenphp_free_request_context(); - php_request_shutdown((void *)0); + frankenphp_request_shutdown(); return FAILURE; } From d2b941833d65ed59ec3d5ad55270ec19103ae4a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:35:01 +0100 Subject: [PATCH 18/59] chore(caddy): bump the go-modules group across 1 directory with 3 updates (#2144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the go-modules group with 3 updates in the /caddy directory: [github.com/caddyserver/certmagic](https://github.com/caddyserver/certmagic), [github.com/dunglas/mercure](https://github.com/dunglas/mercure) and [github.com/dunglas/mercure/caddy](https://github.com/dunglas/mercure). Updates `github.com/caddyserver/certmagic` from 0.25.0 to 0.25.1
Commits
  • d2a7286 Upgrade dependencies, esp. zerossl
  • 20b57b0 Bump golang.org/x/crypto from 0.41.0 to 0.45.0 (#358)
  • 80e9a59 Explicitly allow small RSA key sizes for testing
  • d66689d Add TryLock for use with optional tasks like ARI updates to reduce lock conte...
  • aba1313 Fix edge case panic in case of repeated account recreation failure (fix #354)
  • 14972fd Don't log about OCSP when disabled (Fixes #353)
  • See full diff in compare view

Updates `github.com/dunglas/mercure` from 0.21.4 to 0.21.5
Release notes

Sourced from github.com/dunglas/mercure's releases.

helm-chart-0.21.5

A Helm chart to install a Mercure Hub in a Kubernetes cluster. Mercure is a protocol to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-efficient way.

v0.21.5

What's Changed

Full Changelog: https://github.com/dunglas/mercure/compare/v0.21.4...v0.21.5

Commits
  • 142944d chore: prepare release 0.21.5
  • 124f2f8 ci: bump io.gatling:gatling-maven-plugin in /gatling
  • e444581 chore: bump deps (#1152)
  • aa6539d fix: prevent context cancellation for critical write (#1151)
  • 19b3850 ci: bump actions/upload-artifact from 5 to 6
  • 4c684fe ci: bump net.alchim31.maven:scala-maven-plugin in /gatling
  • See full diff in compare view

Updates `github.com/dunglas/mercure/caddy` from 0.21.4 to 0.21.5
Release notes

Sourced from github.com/dunglas/mercure/caddy's releases.

helm-chart-0.21.5

A Helm chart to install a Mercure Hub in a Kubernetes cluster. Mercure is a protocol to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-efficient way.

v0.21.5

What's Changed

Full Changelog: https://github.com/dunglas/mercure/compare/v0.21.4...v0.21.5

Commits
  • 142944d chore: prepare release 0.21.5
  • 124f2f8 ci: bump io.gatling:gatling-maven-plugin in /gatling
  • e444581 chore: bump deps (#1152)
  • aa6539d fix: prevent context cancellation for critical write (#1151)
  • 19b3850 ci: bump actions/upload-artifact from 5 to 6
  • 4c684fe ci: bump net.alchim31.maven:scala-maven-plugin in /gatling
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- caddy/go.mod | 66 ++++++++++++------------- caddy/go.sum | 136 +++++++++++++++++++++++++-------------------------- 2 files changed, 101 insertions(+), 101 deletions(-) diff --git a/caddy/go.mod b/caddy/go.mod index ae29d7565c..3e92ea1af6 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -8,11 +8,11 @@ retract v1.0.0-rc.1 // Human error require ( github.com/caddyserver/caddy/v2 v2.10.2 - github.com/caddyserver/certmagic v0.25.0 + github.com/caddyserver/certmagic v0.25.1 github.com/dunglas/caddy-cbrotli v1.0.1 github.com/dunglas/frankenphp v1.11.1 - github.com/dunglas/mercure v0.21.4 - github.com/dunglas/mercure/caddy v0.21.4 + github.com/dunglas/mercure v0.21.5 + github.com/dunglas/mercure/caddy v0.21.5 github.com/dunglas/vulcain/caddy v1.2.1 github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 @@ -29,7 +29,7 @@ require ( dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect - github.com/BurntSushi/toml v1.5.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect github.com/KimMachineGun/automemlimit v0.7.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect @@ -38,18 +38,18 @@ require ( github.com/MicahParks/jwkset v0.11.0 // indirect github.com/MicahParks/keyfunc/v3 v3.7.0 // indirect github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect - github.com/alecthomas/chroma/v2 v2.21.0 // indirect + github.com/alecthomas/chroma/v2 v2.22.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/caddyserver/zerossl v0.1.4 // indirect github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.2 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -67,7 +67,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/go-chi/chi/v5 v5.2.3 // indirect + github.com/go-chi/chi/v5 v5.2.4 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -75,7 +75,7 @@ require ( github.com/go-openapi/jsonpointer v0.22.4 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gofrs/uuid/v5 v5.4.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect @@ -84,20 +84,20 @@ require ( github.com/google/brotli/go/cbrotli v1.1.0 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/certificate-transparency-go v1.3.2 // indirect - github.com/google/go-tpm v0.9.7 // indirect + github.com/google/go-tpm v0.9.8 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect - github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.6 // indirect + github.com/jackc/pgx/v5 v5.8.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.2 // indirect @@ -108,10 +108,10 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/maypok86/otter/v2 v2.2.1 // indirect + github.com/maypok86/otter/v2 v2.3.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mholt/acmez/v3 v3.1.4 // indirect - github.com/miekg/dns v1.1.69 // indirect + github.com/miekg/dns v1.1.70 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -127,17 +127,17 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.57.1 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/slackhq/nebula v1.9.7 // indirect github.com/smallstep/certificates v0.29.0 // indirect github.com/smallstep/cli-utils v0.12.2 // indirect @@ -163,7 +163,7 @@ require ( github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect - github.com/yuin/goldmark v1.7.13 // indirect + github.com/yuin/goldmark v1.7.16 // indirect github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.3 // indirect @@ -188,22 +188,22 @@ require ( go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/crypto/x509roots/fallback v0.0.0-20251210140736-7dacc380ba00 // indirect - golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.40.0 // indirect - google.golang.org/api v0.257.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect - google.golang.org/grpc v1.77.0 // indirect + golang.org/x/tools v0.41.0 // indirect + google.golang.org/api v0.260.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect + google.golang.org/grpc v1.78.0 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/caddy/go.sum b/caddy/go.sum index f190ab0fde..b622ef7f41 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -21,8 +21,9 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk= github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -44,8 +45,8 @@ github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+a github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.21.0 h1:YVW9qQAFnQm2OFPPFQg6G/TpMxKSsUr/KUPDi/BEqtY= -github.com/alecthomas/chroma/v2 v2.21.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/chroma/v2 v2.22.0 h1:PqEhf+ezz5F5owoDeOUKFzW+W3ZJDShNCaHg4sZuItI= +github.com/alecthomas/chroma/v2 v2.22.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -90,10 +91,10 @@ github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoG github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8= github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0= -github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= -github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY= +github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY= +github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw= +github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0= github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -111,8 +112,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= +github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= @@ -145,10 +146,10 @@ github.com/dunglas/caddy-cbrotli v1.0.1 h1:mkg7EB1GmoyfBt3kY3mq4o/0bfnBeq7ZLQjmV github.com/dunglas/caddy-cbrotli v1.0.1/go.mod h1:uXABy3tjy1FABF+3JWKVh1ajFvIO/kfpwHaeZGSBaAY= github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= -github.com/dunglas/mercure v0.21.4 h1:mXPXHfB+4cYfFFCRRDY198mfef5+MQcdCpUnAHBUW2M= -github.com/dunglas/mercure v0.21.4/go.mod h1:l/dglCjp/OQx8/quRyceRPx2hqZQ3CNviwoLMRQiJ/k= -github.com/dunglas/mercure/caddy v0.21.4 h1:7o+6rDqfwj1EOmXOgfBFsayvJvOUP37xujQHaxuX4ps= -github.com/dunglas/mercure/caddy v0.21.4/go.mod h1:EM2s+OVGExbSXObUdAPDwPsbQw4t/icLtQv9CFylDvY= +github.com/dunglas/mercure v0.21.5 h1:IvbPBer5dT2LwnOMJ5rRnlU434dnrT23AYWdeYkgVOI= +github.com/dunglas/mercure v0.21.5/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= +github.com/dunglas/mercure/caddy v0.21.5 h1:oUd7iLv3gYjCYdbk3xD4hs4udqubmoVAJC4yRhEBTnE= +github.com/dunglas/mercure/caddy v0.21.5/go.mod h1:T/GebeQAODJq7Kkp3Fshx6JG3V8gWTHsJjWSJdgEnYw= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= github.com/dunglas/vulcain v1.2.1 h1:pkPwvIfoa/xmWSVUyhntbIKT+XO2VFMyhLKv1gA61O8= @@ -171,8 +172,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= +github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= @@ -192,8 +193,8 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1 github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= @@ -219,8 +220,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA= -github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= +github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws= github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= @@ -229,16 +230,16 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= +github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= @@ -251,8 +252,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -284,14 +285,14 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI= -github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs= +github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs8w= +github.com/maypok86/otter/v2 v2.3.0/go.mod h1:XgIdlpmL6jYz882/CAx1E4C1ukfgDKSaw4mWq59+7l8= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= -github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= +github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA= +github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -333,14 +334,14 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= -github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= @@ -358,8 +359,8 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/slackhq/nebula v1.9.7 h1:v5u46efIyYHGdfjFnozQbRRhMdaB9Ma1SSTcUcE2lfE= github.com/slackhq/nebula v1.9.7/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= @@ -450,8 +451,8 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= -github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -519,19 +520,19 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/crypto/x509roots/fallback v0.0.0-20251210140736-7dacc380ba00 h1:qObov2/X4yIpr98j5t6samg3mMF12Rl4taUJd1rWj+c= -golang.org/x/crypto/x509roots/fallback v0.0.0-20251210140736-7dacc380ba00/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -540,8 +541,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -561,7 +562,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -570,8 +570,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -581,8 +581,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -592,8 +592,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -602,21 +602,21 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= -google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= +google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= +google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 h1:X9z6obt+cWRX8XjDVOn+SZWhWe5kZHm46TThU9j+jss= +google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3/go.mod h1:dd646eSK+Dk9kxVBl1nChEOhJPtMXriCcVb4x3o6J+E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From 3ed8723a8646d85d5e55e9b5384dc058e4b6cae0 Mon Sep 17 00:00:00 2001 From: Marc Date: Mon, 26 Jan 2026 16:13:38 +0100 Subject: [PATCH 19/59] Require subscriber_jwt when publisher_jwt is set (#2141) Added subscriber_jwt configuration requirement. Signed-off-by: Marc --- docs/mercure.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/mercure.md b/docs/mercure.md index 06b42c513f..25a619b878 100644 --- a/docs/mercure.md +++ b/docs/mercure.md @@ -19,6 +19,8 @@ localhost mercure { # The secret key used to sign the JWT tokens for publishers publisher_jwt !ChangeThisMercureHubJWTSecretKey! + # When publisher_jwt is set, you must set subscriber_jwt too! + subscriber_jwt !ChangeThisMercureHubJWTSecretKey! # Allows anonymous subscribers (without JWT) anonymous } From 227977ec19a3f6c55621ff24ae9cf64f789983cd Mon Sep 17 00:00:00 2001 From: Damien Fernandes Date: Mon, 26 Jan 2026 18:15:12 +0100 Subject: [PATCH 20/59] docs(docker): add an example of building distroless image (#1900) Related to https://github.com/php/frankenphp/issues/151, this PR adds an example about how building a distroless image for a Frankenphp project. FYI, on https://github.com/dunglas/symfony-docker, it only saves ~24MB in the image size. I think we could go even further by building our own distroless image with Bazel like they do [here](https://github.com/GoogleContainerTools/distroless) but for now, the doc is a good start. --------- Co-authored-by: a.stecher Co-authored-by: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> --- docs/docker.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/docker.md b/docs/docker.md index 23af3fa027..bb3f66d606 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -203,6 +203,79 @@ The Docker images are built: - when a new release is tagged - daily at 4 am UTC, if new versions of the official PHP images are available +## Hardening Images + +To further reduce the attack surface and size of your FrankenPHP Docker images, it's also possible to build them on top of a +[Google distroless](https://github.com/GoogleContainerTools/distroless) or +[Docker hardened](https://www.docker.com/products/hardened-images) image. + +> [!WARNING] +> These minimal base images do not include a shell or package manager, which makes debugging more difficult. +> They are therefore recommended only for production if security is a high priority. + +When adding additional PHP extensions, you will need an intermediate build stage: + +```dockerfile +FROM dunglas/frankenphp AS builder + +# Add additional PHP extensions here +RUN install-php-extensions pdo_mysql pdo_pgsql #... + +# Copy shared libs of frankenphp and all installed extensions to temporary location +# You can also do this step manually by analyzing ldd output of frankenphp binary and each extension .so file +RUN apt-get update && apt-get install -y libtree && \ + EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \ + FRANKENPHP_BIN="$(which frankenphp)"; \ + LIBS_TMP_DIR="/tmp/libs"; \ + mkdir -p "$LIBS_TMP_DIR"; \ + for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \ + libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \ + [ -z "$lib" ] && continue; \ + base=$(basename "$lib"); \ + destfile="$LIBS_TMP_DIR/$base"; \ + if [ ! -f "$destfile" ]; then \ + cp "$lib" "$destfile"; \ + fi; \ + done; \ + done + + +# Distroless debian base image, make sure this is the same debian version as the base image +FROM gcr.io/distroless/base-debian13 +# Docker hardened image alternative +# FROM dhi.io/debian:13 + +# Location of your app and Caddyfile to be copied into the container +ARG PATH_TO_APP="." +ARG PATH_TO_CADDYFILE="./Caddyfile" + +# Copy your app into /app +# For further hardening make sure only writable paths are owned by the nonroot user +COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app +COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile + +# Copy frankenphp and necessary libs +COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp +COPY --from=builder --chown=nonroot:nonroot /usr/local/lib/php/extensions /usr/local/lib/php/extensions +COPY --from=builder /tmp/libs /usr/lib + +# Copy php.ini configuration files +COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d +COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini + +# Create necessary caddy dirs +# These dirs also need to be writable in case of a read-only root filesystem +COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy +COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy + +USER nonroot + +WORKDIR /app + +# entrypoint to run frankenphp with the provided Caddyfile +ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"] +``` + ## Development Versions Development versions are available in the [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker repository. From ddb11c1f72f5765d4eacae200ba5b1b8267b4dbe Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 27 Jan 2026 00:02:19 +0100 Subject: [PATCH 21/59] run translation even if linter failed (#2145) if it can't fix anything, it currently just fails instead, it should open the PR and we can fix manually --------- Signed-off-by: Marc --- .github/workflows/translate.yaml | 1 + .gitleaksignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/translate.yaml b/.github/workflows/translate.yaml index 274dc75857..94f7826730 100644 --- a/.github/workflows/translate.yaml +++ b/.github/workflows/translate.yaml @@ -41,6 +41,7 @@ jobs: php ./docs/translate.php "$MD_FILES" - name: Run Linter if: steps.md_files.outputs.found == 'true' + continue-on-error: true uses: super-linter/super-linter/slim@v8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitleaksignore b/.gitleaksignore index 0f25d7c4c9..c36609fe58 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -1 +1,2 @@ /github/workspace/docs/mercure.md:jwt:88 +/github/workspace/docs/mercure.md:jwt:90 From 0e8de8f56f3ed26b6869438789b64c9ec5559222 Mon Sep 17 00:00:00 2001 From: Xavier Leune Date: Thu, 29 Jan 2026 06:56:45 +0100 Subject: [PATCH 22/59] fix(worker): initialize $_RESUEST (#2136) Hi, This PR fixes #1931, it handles $_REQUEST in worker mode correctly when `auto_globals_jit` is enabled (default configuration for PHP). Some concerns were raised in the comments of the issue regarding performance. This implementation should make sure that request is created only if used. However if a previous execution plan already used `_REQUEST`, all subsequent requests will create it. So the concern is "kindof" mitigated. Let me know if you have any suggestion to improve this. --------- Signed-off-by: Xavier Leune Co-authored-by: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> --- frankenphp.c | 16 ++++-- frankenphp_test.go | 50 +++++++++++++++++++ ...equest-superglobal-conditional-include.php | 20 ++++++++ testdata/request-superglobal-conditional.php | 16 ++++++ testdata/request-superglobal.php | 14 ++++++ 5 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 testdata/request-superglobal-conditional-include.php create mode 100644 testdata/request-superglobal-conditional.php create mode 100644 testdata/request-superglobal.php diff --git a/frankenphp.c b/frankenphp.c index cb910c81ae..d98b1d7b81 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -128,13 +128,19 @@ static void frankenphp_reset_super_globals() { if (auto_global->name == _env) { /* skip $_ENV */ } else if (auto_global->name == _server) { - /* always reimport $_SERVER */ + /* always reimport $_SERVER */ auto_global->armed = auto_global->auto_global_callback(auto_global->name); } else if (auto_global->jit) { - /* globals with jit are: $_SERVER, $_ENV, $_REQUEST, $GLOBALS, - * jit will only trigger on script parsing and therefore behaves - * differently in worker mode. We will skip all jit globals - */ + /* JIT globals ($_REQUEST, $GLOBALS) need special handling: + * - $GLOBALS will always be handled by the application, we skip it + * For $_REQUEST: + * - If in symbol_table: re-initialize with current request data + * - If not: do nothing, it may be armed by jit later */ + if (auto_global->name == ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_REQUEST) && + zend_hash_exists(&EG(symbol_table), auto_global->name)) { + auto_global->armed = + auto_global->auto_global_callback(auto_global->name); + } } else if (auto_global->auto_global_callback) { /* $_GET, $_POST, $_COOKIE, $_FILES are reimported here */ auto_global->armed = auto_global->auto_global_callback(auto_global->name); diff --git a/frankenphp_test.go b/frankenphp_test.go index 8c6f3c90da..937853f676 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -306,6 +306,56 @@ func testPostSuperGlobals(t *testing.T, opts *testOptions) { }, opts) } +func TestRequestSuperGlobal_module(t *testing.T) { testRequestSuperGlobal(t, nil) } +func TestRequestSuperGlobal_worker(t *testing.T) { + phpIni := make(map[string]string) + phpIni["auto_globals_jit"] = "1" + testRequestSuperGlobal(t, &testOptions{workerScript: "request-superglobal.php", phpIni: phpIni}) +} +func testRequestSuperGlobal(t *testing.T, opts *testOptions) { + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { + // Test with both GET and POST parameters + // $_REQUEST should contain merged data from both + formData := url.Values{"post_key": {fmt.Sprintf("post_value_%d", i)}} + req := httptest.NewRequest("POST", fmt.Sprintf("http://example.com/request-superglobal.php?get_key=get_value_%d", i), strings.NewReader(formData.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + body, _ := testRequest(req, handler, t) + + // Verify $_REQUEST contains both GET and POST data for the current request + assert.Contains(t, body, fmt.Sprintf("'get_key' => 'get_value_%d'", i)) + assert.Contains(t, body, fmt.Sprintf("'post_key' => 'post_value_%d'", i)) + }, opts) +} + +func TestRequestSuperGlobalConditional_worker(t *testing.T) { + // This test verifies that $_REQUEST works correctly when accessed conditionally + // in worker mode. The first request does NOT access $_REQUEST, but subsequent + // requests do. This tests the "re-arm" mechanism for JIT auto globals. + // + // The bug scenario: + // - Request 1 (i=1): includes file, $_REQUEST initialized with val=1 + // - Request 3 (i=3): includes file from cache, $_REQUEST should have val=3 + // If the bug exists, $_REQUEST would still have val=1 from request 1. + phpIni := make(map[string]string) + phpIni["auto_globals_jit"] = "1" + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { + if i%2 == 0 { + // Even requests: don't use $_REQUEST + body, _:= testGet(fmt.Sprintf("http://example.com/request-superglobal-conditional.php?val=%d", i), handler, t) + assert.Contains(t, body, "SKIPPED") + assert.Contains(t, body, fmt.Sprintf("'val' => '%d'", i)) + } else { + // Odd requests: use $_REQUEST + body, _ := testGet(fmt.Sprintf("http://example.com/request-superglobal-conditional.php?use_request=1&val=%d", i), handler, t) + assert.Contains(t, body, "REQUEST:") + assert.Contains(t, body, "REQUEST_COUNT:2", "$_REQUEST should have ONLY current request's data (2 keys: use_request and val)") + assert.Contains(t, body, fmt.Sprintf("'val' => '%d'", i), "request data is not present") + assert.Contains(t, body, "'use_request' => '1'") + assert.Contains(t, body, "VAL_CHECK:MATCH", "BUG: $_REQUEST contains stale data from previous request! Body: "+body) + } + }, &testOptions{workerScript: "request-superglobal-conditional.php", phpIni: phpIni}) +} + func TestCookies_module(t *testing.T) { testCookies(t, nil) } func TestCookies_worker(t *testing.T) { testCookies(t, &testOptions{workerScript: "cookies.php"}) } func testCookies(t *testing.T, opts *testOptions) { diff --git a/testdata/request-superglobal-conditional-include.php b/testdata/request-superglobal-conditional-include.php new file mode 100644 index 0000000000..f41bb98adf --- /dev/null +++ b/testdata/request-superglobal-conditional-include.php @@ -0,0 +1,20 @@ + Date: Thu, 29 Jan 2026 16:29:00 +0100 Subject: [PATCH 23/59] chore: bump deps --- caddy/go.mod | 30 ++++++------- caddy/go.sum | 124 +++++++++++++++++++++++++-------------------------- go.mod | 20 ++++----- go.sum | 40 ++++++++--------- 4 files changed, 107 insertions(+), 107 deletions(-) diff --git a/caddy/go.mod b/caddy/go.mod index 3e92ea1af6..86ba713a31 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -11,8 +11,8 @@ require ( github.com/caddyserver/certmagic v0.25.1 github.com/dunglas/caddy-cbrotli v1.0.1 github.com/dunglas/frankenphp v1.11.1 - github.com/dunglas/mercure v0.21.5 - github.com/dunglas/mercure/caddy v0.21.5 + github.com/dunglas/mercure v0.21.7 + github.com/dunglas/mercure/caddy v0.21.7 github.com/dunglas/vulcain/caddy v1.2.1 github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 @@ -23,7 +23,7 @@ require github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca require ( cel.dev/expr v0.25.1 // indirect - cloud.google.com/go/auth v0.18.0 // indirect + cloud.google.com/go/auth v0.18.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.2 // indirect @@ -38,7 +38,7 @@ require ( github.com/MicahParks/jwkset v0.11.0 // indirect github.com/MicahParks/keyfunc/v3 v3.7.0 // indirect github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect - github.com/alecthomas/chroma/v2 v2.22.0 // indirect + github.com/alecthomas/chroma/v2 v2.23.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -49,7 +49,7 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.6.2 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -62,7 +62,7 @@ require ( github.com/dunglas/skipfilter v1.0.0 // indirect github.com/dunglas/vulcain v1.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 // indirect + github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -78,7 +78,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gofrs/uuid/v5 v5.4.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/brotli/go/cbrotli v1.1.0 // indirect @@ -92,7 +92,7 @@ require ( github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -100,7 +100,7 @@ require ( github.com/jackc/pgx/v5 v5.8.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/compress v1.18.3 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/libdns/libdns v1.1.1 // indirect @@ -111,7 +111,7 @@ require ( github.com/maypok86/otter/v2 v2.3.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mholt/acmez/v3 v3.1.4 // indirect - github.com/miekg/dns v1.1.70 // indirect + github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -123,7 +123,7 @@ require ( github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pires/go-proxyproto v0.8.1 // indirect + github.com/pires/go-proxyproto v0.9.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -181,7 +181,7 @@ require ( go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.step.sm/crypto v0.75.0 // indirect + go.step.sm/crypto v0.76.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect @@ -200,9 +200,9 @@ require ( golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.41.0 // indirect - google.golang.org/api v0.260.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect + google.golang.org/api v0.263.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/caddy/go.sum b/caddy/go.sum index b622ef7f41..198b93a422 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -2,16 +2,16 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= -cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= +cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= +cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= -cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= -cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= -cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/kms v1.24.0 h1:SWltUuoPhTdv9q/P0YEAWQfoYT32O5HdfPgTiWMvrH8= +cloud.google.com/go/kms v1.24.0/go.mod h1:QDH3z2SJ50lfNOE8EokKC1G40i7I0f8xTMCoiptcb5g= cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -45,8 +45,8 @@ github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+a github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.22.0 h1:PqEhf+ezz5F5owoDeOUKFzW+W3ZJDShNCaHg4sZuItI= -github.com/alecthomas/chroma/v2 v2.22.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= +github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -55,36 +55,36 @@ github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmO github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= -github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/config v1.32.1 h1:iODUDLgk3q8/flEC7ymhmxjfoAnBDwEEYEVyKZ9mzjU= -github.com/aws/aws-sdk-go-v2/config v1.32.1/go.mod h1:xoAgo17AGrPpJBSLg81W+ikM0cpOZG8ad04T2r+d5P0= -github.com/aws/aws-sdk-go-v2/credentials v1.19.1 h1:JeW+EwmtTE0yXFK8SmklrFh/cGTTXsQJumgMZNlbxfM= -github.com/aws/aws-sdk-go-v2/credentials v1.19.1/go.mod h1:BOoXiStwTF+fT2XufhO0Efssbi1CNIO/ZXpZu87N0pw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= +github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -github.com/aws/aws-sdk-go-v2/service/kms v1.48.0 h1:pQgVxqqNOacqb19+xaoih/wNLil4d8tgi+FxtBi/qQY= -github.com/aws/aws-sdk-go-v2/service/kms v1.48.0/go.mod h1:VJcNH6BLr+3VJwinRKdotLOMglHO8mIKlD3ea5c7hbw= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9 h1:LU8S9W/mPDAU9q0FjCLi0TrCheLMGwzbRpvUMwYspcA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= -github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= -github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.5 h1:DKibav4XF66XSeaXcrn9GlWGHos6D/vJ4r7jsK7z5CE= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.5/go.mod h1:1SdcmEGUEQE1mrU2sIgeHtcMSxHuybhPvuEPANzIDfI= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= @@ -112,8 +112,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= -github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= @@ -146,10 +146,10 @@ github.com/dunglas/caddy-cbrotli v1.0.1 h1:mkg7EB1GmoyfBt3kY3mq4o/0bfnBeq7ZLQjmV github.com/dunglas/caddy-cbrotli v1.0.1/go.mod h1:uXABy3tjy1FABF+3JWKVh1ajFvIO/kfpwHaeZGSBaAY= github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= -github.com/dunglas/mercure v0.21.5 h1:IvbPBer5dT2LwnOMJ5rRnlU434dnrT23AYWdeYkgVOI= -github.com/dunglas/mercure v0.21.5/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= -github.com/dunglas/mercure/caddy v0.21.5 h1:oUd7iLv3gYjCYdbk3xD4hs4udqubmoVAJC4yRhEBTnE= -github.com/dunglas/mercure/caddy v0.21.5/go.mod h1:T/GebeQAODJq7Kkp3Fshx6JG3V8gWTHsJjWSJdgEnYw= +github.com/dunglas/mercure v0.21.7 h1:QtxQr9IMqJdlLxpsCjdBoDkJm4r4HXIEQCLl5obzI+o= +github.com/dunglas/mercure v0.21.7/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= +github.com/dunglas/mercure/caddy v0.21.7 h1:+Mx9zzQPnGsF3Jwwp7hFXravkv5TUWqjifZFJ6yArQk= +github.com/dunglas/mercure/caddy v0.21.7/go.mod h1:VOrsyc4RJf8a9Ucr/cLjiODr6nd/BinLi02dOvyWXi4= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= github.com/dunglas/vulcain v1.2.1 h1:pkPwvIfoa/xmWSVUyhntbIKT+XO2VFMyhLKv1gA61O8= @@ -159,8 +159,8 @@ github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjsp github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a h1:e/m9m8cJgjzw2Ol7tKTu4B/lM5F3Ym7ryKI+oyw0T8Y= +github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -199,8 +199,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1 github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -238,8 +238,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 h1:1ufTZkFXIQQ9EmgPjcIPIi2krfxG03lQ8OLoY1MJ3UM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= @@ -260,8 +260,8 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -291,8 +291,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA= -github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -320,8 +320,8 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= -github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= -github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= +github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U= +github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -495,8 +495,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= -go.step.sm/crypto v0.75.0 h1:UAHYD6q6ggYyzLlIKHv1MCUVjZIesXRZpGTlRC/HSHw= -go.step.sm/crypto v0.75.0/go.mod h1:wwQ57+ajmDype9mrI/2hRyrvJd7yja5xVgWYqpUN3PE= +go.step.sm/crypto v0.76.0 h1:K23BSaeoiY7Y5dvvijTeYC9EduDBetNwQYMBwMhi1aA= +go.step.sm/crypto v0.76.0/go.mod h1:PXYJdKkK8s+GHLwLguFaLxHNAFsFL3tL1vSBrYfey5k= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -607,14 +607,14 @@ golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= -google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= +google.golang.org/api v0.263.0 h1:UFs7qn8gInIdtk1ZA6eXRXp5JDAnS4x9VRsRVCeKdbk= +google.golang.org/api v0.263.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= -google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 h1:X9z6obt+cWRX8XjDVOn+SZWhWe5kZHm46TThU9j+jss= -google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3/go.mod h1:dd646eSK+Dk9kxVBl1nChEOhJPtMXriCcVb4x3o6J+E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= diff --git a/go.mod b/go.mod index 56bb84b591..dae3654927 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,12 @@ retract v1.0.0-rc.1 // Human error require ( github.com/Masterminds/sprig/v3 v3.3.0 - github.com/dunglas/mercure v0.21.4 - github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 - github.com/maypok86/otter/v2 v2.2.1 + github.com/dunglas/mercure v0.21.7 + github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a + github.com/maypok86/otter/v2 v2.3.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.48.0 + golang.org/x/net v0.49.0 ) require ( @@ -27,9 +27,9 @@ require ( github.com/dunglas/skipfilter v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gofrs/uuid/v5 v5.4.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect @@ -42,7 +42,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -58,9 +58,9 @@ require ( go.etcd.io/bbolt v1.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index af58b44cb3..db5f8ec818 100644 --- a/go.sum +++ b/go.sum @@ -18,24 +18,24 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dunglas/mercure v0.21.4 h1:mXPXHfB+4cYfFFCRRDY198mfef5+MQcdCpUnAHBUW2M= -github.com/dunglas/mercure v0.21.4/go.mod h1:l/dglCjp/OQx8/quRyceRPx2hqZQ3CNviwoLMRQiJ/k= +github.com/dunglas/mercure v0.21.7 h1:QtxQr9IMqJdlLxpsCjdBoDkJm4r4HXIEQCLl5obzI+o= +github.com/dunglas/mercure v0.21.7/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a h1:e/m9m8cJgjzw2Ol7tKTu4B/lM5F3Ym7ryKI+oyw0T8Y= +github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -54,8 +54,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI= -github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs= +github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs8w= +github.com/maypok86/otter/v2 v2.3.0/go.mod h1:XgIdlpmL6jYz882/CAx1E4C1ukfgDKSaw4mWq59+7l8= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -72,8 +72,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -108,16 +108,16 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 8e1641b81c6392e306ac2c42404aa6e69b386ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 29 Jan 2026 17:23:50 +0100 Subject: [PATCH 24/59] ci: disable codespell linter (#2153) --- .codespellrc | 3 +++ .github/workflows/lint.yaml | 4 ++-- docs/config.md | 2 +- internal/extgen/arginfo.go | 2 +- internal/state/state.go | 2 +- types.go | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 .codespellrc diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000000..15e29a7bfc --- /dev/null +++ b/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +check-hidden = +skip = .git,docs/*/*,docs,*/go.mod,*/go.sum,./internal/phpheaders/phpheaders.go diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f465e3a155..28eea80c33 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -47,5 +47,5 @@ jobs: VALIDATE_BIOME_LINT: false # Conflicts with MARKDOWN VALIDATE_MARKDOWN_PRETTIER: false - # To re-enable when https://github.com/super-linter/super-linter/issues/7244 will be closed - VALIDATE_EDITORCONFIG: false + # To re-enable when https://github.com/super-linter/super-linter/issues/7466 will be closed + VALIDATE_SPELL_CODESPELL: false diff --git a/docs/config.md b/docs/config.md index a600c23b5e..0252f54256 100644 --- a/docs/config.md +++ b/docs/config.md @@ -12,7 +12,7 @@ A minimal `Caddyfile` to serve a PHP application is shown below: # The hostname to respond to localhost -# Optionaly, the directory to serve files from, otherwise defaults to the current directory +# Optionally, the directory to serve files from, otherwise defaults to the current directory #root public/ php_server ``` diff --git a/internal/extgen/arginfo.go b/internal/extgen/arginfo.go index 7d6aa08a8a..494b1e0b9b 100644 --- a/internal/extgen/arginfo.go +++ b/internal/extgen/arginfo.go @@ -20,7 +20,7 @@ func (ag *arginfoGenerator) generate() error { } if _, err := os.Stat(genStubPath); err != nil { - return fmt.Errorf(`the PHP "gen_stub.php" file couldn't be found under %q, you can set the "GEN_STUB_SCRIPT" environement variable to set a custom location`, genStubPath) + return fmt.Errorf(`the PHP "gen_stub.php" file couldn't be found under %q, you can set the "GEN_STUB_SCRIPT" environment variable to set a custom location`, genStubPath) } stubFile := ag.generator.BaseName + ".stub.php" diff --git a/internal/state/state.go b/internal/state/state.go index b15c008bce..0a3030384e 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -10,7 +10,7 @@ import ( type State string const ( - // livecycle States of a thread + // lifecycle States of a thread Reserved State = "reserved" Booting State = "booting" BootRequested State = "boot requested" diff --git a/types.go b/types.go index 17a2d55060..30764ecba5 100644 --- a/types.go +++ b/types.go @@ -257,7 +257,7 @@ func PHPPackedArray[T any](slice []T) unsafe.Pointer { // EXPERIMENTAL: GoValue converts a PHP zval to a Go value // // Zval having the null, bool, long, double, string and array types are currently supported. -// Arrays can curently only be converted to any[] and AssociativeArray[any]. +// Arrays can currently only be converted to any[] and AssociativeArray[any]. // Any other type will cause an error. // More types may be supported in the future. func GoValue[T any](zval unsafe.Pointer) (T, error) { From 86b9ffcff86e7998be8fdc81df8d87b5b89082d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 11:00:05 +0100 Subject: [PATCH 25/59] ci: don't compress binary artifacts --- .github/workflows/static.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index 9b671727c2..98b078b4e3 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -192,6 +192,7 @@ jobs: with: name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} + compression-level: 0 - name: Upload assets if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag') run: gh release upload "${REF}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber @@ -489,6 +490,7 @@ jobs: with: name: frankenphp-mac-${{ matrix.platform }} path: dist/frankenphp-mac-${{ matrix.platform }} + compression-level: 0 - name: Run sanity checks run: | "${BINARY}" version From 0f410a2e37109ba2254ffa91ded4c7834ce85446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 3 Feb 2026 12:29:37 +0100 Subject: [PATCH 26/59] docs: remove outdated build tags --- CONTRIBUTING.md | 6 +++--- docs/cn/CONTRIBUTING.md | 6 +++--- docs/fr/CONTRIBUTING.md | 6 +++--- docs/ja/CONTRIBUTING.md | 6 +++--- docs/pt-br/CONTRIBUTING.md | 6 +++--- docs/ru/CONTRIBUTING.md | 6 +++--- docs/tr/CONTRIBUTING.md | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f42eab7df2..73d8b19ab8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ If your Docker version is lower than 23.0, the build will fail due to dockerigno ```console export CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Caddy module @@ -43,7 +43,7 @@ Build Caddy with the FrankenPHP Caddy module: ```console cd caddy/frankenphp/ -go build -tags watcher,brotli,nobadger,nomysql,nopgx +go build -tags nobadger,nomysql,nopgx cd ../../ ``` @@ -176,7 +176,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. In the container, you can use GDB and the like: ```console - go test -tags watcher -c -ldflags=-w + go test -tags -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` diff --git a/docs/cn/CONTRIBUTING.md b/docs/cn/CONTRIBUTING.md index 8b6666bd46..b72c63fc98 100644 --- a/docs/cn/CONTRIBUTING.md +++ b/docs/cn/CONTRIBUTING.md @@ -33,7 +33,7 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 - ## 运行测试套件 ```console -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Caddy 模块 @@ -42,7 +42,7 @@ go test -tags watcher -race -v ./... ```console cd caddy/frankenphp/ -go build -tags watcher,brotli,nobadger,nomysql,nopgx +go build -tags nobadger,nomysql,nopgx cd ../../ ``` @@ -175,7 +175,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. 在容器中,可以使用 GDB 和以下: ```console - go test -tags watcher -c -ldflags=-w + go test -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` diff --git a/docs/fr/CONTRIBUTING.md b/docs/fr/CONTRIBUTING.md index 77e889b2fa..eeacb7bb15 100644 --- a/docs/fr/CONTRIBUTING.md +++ b/docs/fr/CONTRIBUTING.md @@ -33,7 +33,7 @@ Si votre version de Docker est inférieure à 23.0, la construction échouera à ## Exécution de la suite de tests ```console -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Module Caddy @@ -42,7 +42,7 @@ Construire Caddy avec le module FrankenPHP : ```console cd caddy/frankenphp/ -go build -tags watcher,brotli,nobadger,nomysql,nopgx +go build -tags nobadger,nomysql,nopgx cd ../../ ``` @@ -176,7 +176,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. Dans le conteneur, vous pouvez utiliser GDB et similaires : ```console - go test -tags watcher -c -ldflags=-w + go test -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` diff --git a/docs/ja/CONTRIBUTING.md b/docs/ja/CONTRIBUTING.md index 210368499a..b7a0da5a97 100644 --- a/docs/ja/CONTRIBUTING.md +++ b/docs/ja/CONTRIBUTING.md @@ -33,7 +33,7 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 - ## テストスイートの実行 ```console -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Caddyモジュール @@ -42,7 +42,7 @@ FrankenPHPのCaddyモジュール付きでCaddyをビルドします: ```console cd caddy/frankenphp/ -go build -tags watcher,brotli,nobadger,nomysql,nopgx +go build -tags nobadger,nomysql,nopgx cd ../../ ``` @@ -175,7 +175,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. コンテナ内で、GDBなどを使用できます: ```console - go test -tags watcher -c -ldflags=-w + go test -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` diff --git a/docs/pt-br/CONTRIBUTING.md b/docs/pt-br/CONTRIBUTING.md index 7183424df7..a24a9718a9 100644 --- a/docs/pt-br/CONTRIBUTING.md +++ b/docs/pt-br/CONTRIBUTING.md @@ -39,7 +39,7 @@ a flag de configuração `--debug`. ## Executando a suite de testes ```console -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Módulo Caddy @@ -48,7 +48,7 @@ Construa o Caddy com o módulo Caddy FrankenPHP: ```console cd caddy/frankenphp/ -go build -tags watcher,brotli,nobadger,nomysql,nopgx +go build -tags nobadger,nomysql,nopgx cd ../../ ``` @@ -186,7 +186,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. No contêiner, você pode usar o GDB e similares: ```console - go test -tags watcher -c -ldflags=-w + go test -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` diff --git a/docs/ru/CONTRIBUTING.md b/docs/ru/CONTRIBUTING.md index 55d39d66e2..58b9f5ad0f 100644 --- a/docs/ru/CONTRIBUTING.md +++ b/docs/ru/CONTRIBUTING.md @@ -33,7 +33,7 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 - ## Запуск тестов ```console -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Модуль Caddy @@ -42,7 +42,7 @@ go test -tags watcher -race -v ./... ```console cd caddy/frankenphp/ -go build -tags watcher,brotli,nobadger,nomysql,nopgx +go build -tags nobadger,nomysql,nopgx cd ../../ ``` @@ -175,7 +175,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. В контейнере используйте GDB и другие инструменты: ```console - go test -tags watcher -c -ldflags=-w + go test -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` diff --git a/docs/tr/CONTRIBUTING.md b/docs/tr/CONTRIBUTING.md index ea71dbff1a..ec60d2b340 100644 --- a/docs/tr/CONTRIBUTING.md +++ b/docs/tr/CONTRIBUTING.md @@ -33,7 +33,7 @@ Docker sürümünüz 23.0'dan düşükse, derleme dockerignore [pattern issue](h ## Test senaryolarını çalıştırma ```console -go test -tags watcher -race -v ./... +go test -race -v ./... ``` ## Caddy modülü @@ -175,7 +175,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push 8. Konteynerde GDB ve benzerlerini kullanabilirsiniz: ```console - go test -tags watcher -c -ldflags=-w + go test -c -ldflags=-w gdb --args frankenphp.test -test.run ^MyTest$ ``` From ad7e4f146d59a8236331d3590d31c76d2125f262 Mon Sep 17 00:00:00 2001 From: Xavier Leune Date: Tue, 3 Feb 2026 16:27:35 +0100 Subject: [PATCH 27/59] fix(worker): reset ini settinfs and session if changed during worker request --- frankenphp.c | 216 +++++++++++++++++++++++ frankenphp_test.go | 180 ++++++++++++++++++- testdata/ini-leak.php | 67 +++++++ testdata/session-handler.php | 115 ++++++++++++ testdata/worker-with-ini.php | 63 +++++++ testdata/worker-with-session-handler.php | 98 ++++++++++ 6 files changed, 738 insertions(+), 1 deletion(-) create mode 100644 testdata/ini-leak.php create mode 100644 testdata/session-handler.php create mode 100644 testdata/worker-with-ini.php create mode 100644 testdata/worker-with-session-handler.php diff --git a/frankenphp.c b/frankenphp.c index d98b1d7b81..cb3943772a 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,42 @@ bool should_filter_var = 0; __thread uintptr_t thread_index; __thread bool is_worker_thread = false; __thread zval *os_environment = NULL; +__thread HashTable *worker_ini_snapshot = NULL; + +/* Session user handler names (same structure as PS(mod_user_names)). + * In PHP 8.2, mod_user_names is a union with .name.ps_* access. + * In PHP 8.3+, mod_user_names is a direct struct with .ps_* access. */ +typedef struct { + zval ps_open; + zval ps_close; + zval ps_read; + zval ps_write; + zval ps_destroy; + zval ps_gc; + zval ps_create_sid; + zval ps_validate_sid; + zval ps_update_timestamp; +} session_user_handlers; + +/* Macro to access PS(mod_user_names) handlers across PHP versions */ +#if PHP_VERSION_ID >= 80300 +#define PS_MOD_USER_NAMES(handler) PS(mod_user_names).handler +#else +#define PS_MOD_USER_NAMES(handler) PS(mod_user_names).name.handler +#endif + +#define FOR_EACH_SESSION_HANDLER(op) \ + op(ps_open); \ + op(ps_close); \ + op(ps_read); \ + op(ps_write); \ + op(ps_destroy); \ + op(ps_gc); \ + op(ps_create_sid); \ + op(ps_validate_sid); \ + op(ps_update_timestamp) + +__thread session_user_handlers *worker_session_handlers_snapshot = NULL; void frankenphp_update_local_thread_context(bool is_worker) { is_worker_thread = is_worker; @@ -174,6 +211,164 @@ static void frankenphp_release_temporary_streams() { ZEND_HASH_FOREACH_END(); } +/* Destructor for INI snapshot hash table entries */ +static void frankenphp_ini_snapshot_dtor(zval *zv) { + zend_string_release((zend_string *)Z_PTR_P(zv)); +} + +/* Save the current state of modified INI entries. + * This captures INI values set by the framework before the worker loop. */ +static void frankenphp_snapshot_ini(void) { + if (worker_ini_snapshot != NULL) { + return; /* Already snapshotted */ + } + + if (EG(modified_ini_directives) == NULL) { + /* Allocate empty table to mark as snapshotted */ + ALLOC_HASHTABLE(worker_ini_snapshot); + zend_hash_init(worker_ini_snapshot, 0, NULL, frankenphp_ini_snapshot_dtor, 0); + return; + } + + uint32_t num_modified = zend_hash_num_elements(EG(modified_ini_directives)); + ALLOC_HASHTABLE(worker_ini_snapshot); + zend_hash_init(worker_ini_snapshot, num_modified, NULL, frankenphp_ini_snapshot_dtor, 0); + + zend_ini_entry *ini_entry; + ZEND_HASH_FOREACH_PTR(EG(modified_ini_directives), ini_entry) { + if (ini_entry->value) { + zend_hash_add_ptr(worker_ini_snapshot, ini_entry->name, + zend_string_copy(ini_entry->value)); + } + } + ZEND_HASH_FOREACH_END(); +} + +/* Restore INI values to the state captured by frankenphp_snapshot_ini(). + * - Entries in snapshot with changed values: restore to snapshot value + * - Entries not in snapshot: restore to startup default */ +static void frankenphp_restore_ini(void) { + if (worker_ini_snapshot == NULL || EG(modified_ini_directives) == NULL) { + return; + } + + zend_ini_entry *ini_entry; + zend_string *snapshot_value; + zend_string *entry_name; + + /* Collect entries to restore to default in a separate array. + * We cannot call zend_restore_ini_entry() during iteration because + * it calls zend_hash_del() on EG(modified_ini_directives). */ + uint32_t max_entries = zend_hash_num_elements(EG(modified_ini_directives)); + zend_string **entries_to_restore = + max_entries ? emalloc(max_entries * sizeof(zend_string *)) : NULL; + size_t restore_count = 0; + + ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name, + ini_entry) { + snapshot_value = zend_hash_find_ptr(worker_ini_snapshot, entry_name); + + if (snapshot_value == NULL) { + /* Entry was not in snapshot: collect for restore to startup default */ + entries_to_restore[restore_count++] = zend_string_copy(entry_name); + } else if (!zend_string_equals(ini_entry->value, snapshot_value)) { + /* Entry was in snapshot but value changed: restore to snapshot value. + * zend_alter_ini_entry() does not delete from modified_ini_directives. */ + zend_alter_ini_entry(entry_name, snapshot_value, PHP_INI_USER, + PHP_INI_STAGE_RUNTIME); + } + /* else: Entry in snapshot with same value, nothing to do */ + } + ZEND_HASH_FOREACH_END(); + + /* Now restore entries to default outside of iteration */ + for (size_t i = 0; i < restore_count; i++) { + zend_restore_ini_entry(entries_to_restore[i], PHP_INI_STAGE_RUNTIME); + zend_string_release(entries_to_restore[i]); + } + if (entries_to_restore) { + efree(entries_to_restore); + } +} + +/* Save session user handlers set before the worker loop. + * This allows frameworks to define custom session handlers that persist. */ +static void frankenphp_snapshot_session_handlers(void) { + if (worker_session_handlers_snapshot != NULL) { + return; /* Already snapshotted */ + } + + /* Check if session module is loaded */ + if (zend_hash_str_find_ptr(&module_registry, "session", + sizeof("session") - 1) == NULL) { + return; /* Session module not available */ + } + + /* Check if user session handlers are defined */ + if (Z_ISUNDEF(PS_MOD_USER_NAMES(ps_open))) { + return; /* No user handlers to snapshot */ + } + + worker_session_handlers_snapshot = emalloc(sizeof(session_user_handlers)); + + /* Copy each handler zval with incremented reference count */ +#define SNAPSHOT_HANDLER(h) \ + if (!Z_ISUNDEF(PS_MOD_USER_NAMES(h))) { \ + ZVAL_COPY(&worker_session_handlers_snapshot->h, &PS_MOD_USER_NAMES(h)); \ + } else { \ + ZVAL_UNDEF(&worker_session_handlers_snapshot->h); \ + } + + FOR_EACH_SESSION_HANDLER(SNAPSHOT_HANDLER); + +#undef SNAPSHOT_HANDLER +} + +/* Restore session user handlers from snapshot after RSHUTDOWN freed them. */ +static void frankenphp_restore_session_handlers(void) { + if (worker_session_handlers_snapshot == NULL) { + return; + } + + /* Restore each handler zval. + * Session RSHUTDOWN already freed the handlers via zval_ptr_dtor and set + * them to UNDEF, so we don't need to destroy them again. We simply copy + * from the snapshot (which holds its own reference). */ +#define RESTORE_HANDLER(h) \ + if (!Z_ISUNDEF(worker_session_handlers_snapshot->h)) { \ + ZVAL_COPY(&PS_MOD_USER_NAMES(h), &worker_session_handlers_snapshot->h); \ + } + + FOR_EACH_SESSION_HANDLER(RESTORE_HANDLER); + +#undef RESTORE_HANDLER +} + +/* Free worker state when the worker script terminates. */ +static void frankenphp_cleanup_worker_state(void) { + /* Free INI snapshot */ + if (worker_ini_snapshot != NULL) { + zend_hash_destroy(worker_ini_snapshot); + FREE_HASHTABLE(worker_ini_snapshot); + worker_ini_snapshot = NULL; + } + + /* Free session handlers snapshot */ + if (worker_session_handlers_snapshot != NULL) { +#define FREE_HANDLER(h) \ + if (!Z_ISUNDEF(worker_session_handlers_snapshot->h)) { \ + zval_ptr_dtor(&worker_session_handlers_snapshot->h); \ + } + + FOR_EACH_SESSION_HANDLER(FREE_HANDLER); + +#undef FREE_HANDLER + + efree(worker_session_handlers_snapshot); + worker_session_handlers_snapshot = NULL; + } +} + /* Adapted from php_request_shutdown */ static void frankenphp_worker_request_shutdown() { /* Flush all output buffers */ @@ -208,6 +403,12 @@ bool frankenphp_shutdown_dummy_request(void) { return false; } + /* Snapshot INI and session handlers BEFORE shutdown. + * The framework has set these up before the worker loop, and we want + * to preserve them. Session RSHUTDOWN will free the handlers. */ + frankenphp_snapshot_ini(); + frankenphp_snapshot_session_handlers(); + frankenphp_worker_request_shutdown(); return true; @@ -263,6 +464,12 @@ static int frankenphp_worker_request_startup() { frankenphp_reset_super_globals(); + /* Restore INI values changed during the previous request back to their + * snapshot state (captured in frankenphp_shutdown_dummy_request). + * This ensures framework settings persist while request-level changes + * are reset. */ + frankenphp_restore_ini(); + const char **module_name; zend_module_entry *module; for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) { @@ -272,6 +479,12 @@ static int frankenphp_worker_request_startup() { module->request_startup_func(module->type, module->module_number); } } + + /* Restore session handlers AFTER session RINIT. + * Session RSHUTDOWN frees mod_user_names callbacks, so we must restore + * them before user code runs. This must happen after RINIT because + * session RINIT may reset some state. */ + frankenphp_restore_session_handlers(); } zend_catch { retval = FAILURE; } zend_end_try(); @@ -617,6 +830,9 @@ static zend_module_entry frankenphp_module = { STANDARD_MODULE_PROPERTIES}; static void frankenphp_request_shutdown() { + if (is_worker_thread) { + frankenphp_cleanup_worker_state(); + } php_request_shutdown((void *)0); frankenphp_free_request_context(); } diff --git a/frankenphp_test.go b/frankenphp_test.go index 937853f676..83a151e445 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -341,7 +341,7 @@ func TestRequestSuperGlobalConditional_worker(t *testing.T) { runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { if i%2 == 0 { // Even requests: don't use $_REQUEST - body, _:= testGet(fmt.Sprintf("http://example.com/request-superglobal-conditional.php?val=%d", i), handler, t) + body, _ := testGet(fmt.Sprintf("http://example.com/request-superglobal-conditional.php?val=%d", i), handler, t) assert.Contains(t, body, "SKIPPED") assert.Contains(t, body, fmt.Sprintf("'val' => '%d'", i)) } else { @@ -1128,3 +1128,181 @@ func FuzzRequest(f *testing.F) { }, &testOptions{workerScript: "request-headers.php"}) }) } + +func TestSessionHandlerReset_worker(t *testing.T) { + runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { + // Request 1: Set a custom session handler and start session + resp1, err := http.Get(ts.URL + "/session-handler.php?action=set_handler_and_start&value=test1") + assert.NoError(t, err) + body1, _ := io.ReadAll(resp1.Body) + _ = resp1.Body.Close() + + body1Str := string(body1) + assert.Contains(t, body1Str, "HANDLER_SET_AND_STARTED") + assert.Contains(t, body1Str, "session.save_handler=user") + + // Request 2: Start session without setting a custom handler + // After the fix: session.save_handler should be reset to "files" + // and session_start() should work normally + resp2, err := http.Get(ts.URL + "/session-handler.php?action=start_without_handler") + assert.NoError(t, err) + body2, _ := io.ReadAll(resp2.Body) + _ = resp2.Body.Close() + + body2Str := string(body2) + + // session.save_handler should be reset to "files" (default) + assert.Contains(t, body2Str, "save_handler_before=files", + "session.save_handler INI should be reset to 'files' between requests.\nResponse: %s", body2Str) + + // session_start() should succeed + assert.Contains(t, body2Str, "SESSION_START_RESULT=true", + "session_start() should succeed after INI reset.\nResponse: %s", body2Str) + + // No errors or exceptions should occur + assert.NotContains(t, body2Str, "ERROR:", + "No errors expected.\nResponse: %s", body2Str) + assert.NotContains(t, body2Str, "EXCEPTION:", + "No exceptions expected.\nResponse: %s", body2Str) + + }, &testOptions{ + workerScript: "session-handler.php", + nbWorkers: 1, + nbParallelRequests: 1, + realServer: true, + }) +} + +func TestIniLeakBetweenRequests_worker(t *testing.T) { + runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { + // Request 1: Change INI values + resp1, err := http.Get(ts.URL + "/ini-leak.php?action=change_ini") + assert.NoError(t, err) + body1, _ := io.ReadAll(resp1.Body) + _ = resp1.Body.Close() + + assert.Contains(t, string(body1), "INI_CHANGED") + + // Request 2: Check if INI values leaked from request 1 + resp2, err := http.Get(ts.URL + "/ini-leak.php?action=check_ini") + assert.NoError(t, err) + body2, _ := io.ReadAll(resp2.Body) + _ = resp2.Body.Close() + + body2Str := string(body2) + t.Logf("Response: %s", body2Str) + + // If INI values leak, this test will fail + assert.Contains(t, body2Str, "NO_LEAKS", + "INI values should not leak between requests.\nResponse: %s", body2Str) + assert.NotContains(t, body2Str, "LEAKS_DETECTED", + "INI leaks detected.\nResponse: %s", body2Str) + + }, &testOptions{ + workerScript: "ini-leak.php", + nbWorkers: 1, + nbParallelRequests: 1, + realServer: true, + }) +} + +func TestSessionHandlerPreLoopPreserved_worker(t *testing.T) { + runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { + // Request 1: Check that the pre-loop session handler is preserved + resp1, err := http.Get(ts.URL + "/worker-with-session-handler.php?action=check") + assert.NoError(t, err) + body1, _ := io.ReadAll(resp1.Body) + _ = resp1.Body.Close() + + body1Str := string(body1) + t.Logf("Request 1 response: %s", body1Str) + assert.Contains(t, body1Str, "HANDLER_PRESERVED", + "Session handler set before loop should be preserved") + assert.Contains(t, body1Str, "save_handler=user", + "session.save_handler should remain 'user'") + + // Request 2: Use the session - should work with pre-loop handler + resp2, err := http.Get(ts.URL + "/worker-with-session-handler.php?action=use_session") + assert.NoError(t, err) + body2, _ := io.ReadAll(resp2.Body) + _ = resp2.Body.Close() + + body2Str := string(body2) + t.Logf("Request 2 response: %s", body2Str) + assert.Contains(t, body2Str, "SESSION_OK", + "Session should work with pre-loop handler.\nResponse: %s", body2Str) + assert.NotContains(t, body2Str, "ERROR:", + "No errors expected.\nResponse: %s", body2Str) + assert.NotContains(t, body2Str, "EXCEPTION:", + "No exceptions expected.\nResponse: %s", body2Str) + + // Request 3: Check handler is still preserved after using session + resp3, err := http.Get(ts.URL + "/worker-with-session-handler.php?action=check") + assert.NoError(t, err) + body3, _ := io.ReadAll(resp3.Body) + _ = resp3.Body.Close() + + body3Str := string(body3) + t.Logf("Request 3 response: %s", body3Str) + assert.Contains(t, body3Str, "HANDLER_PRESERVED", + "Session handler should still be preserved after use") + + }, &testOptions{ + workerScript: "worker-with-session-handler.php", + nbWorkers: 1, + nbParallelRequests: 1, + realServer: true, + }) +} + +func TestIniPreLoopPreserved_worker(t *testing.T) { + runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { + // Request 1: Check that pre-loop INI values are present + resp1, err := http.Get(ts.URL + "/worker-with-ini.php?action=check") + assert.NoError(t, err) + body1, _ := io.ReadAll(resp1.Body) + _ = resp1.Body.Close() + + body1Str := string(body1) + t.Logf("Request 1 response: %s", body1Str) + assert.Contains(t, body1Str, "precision=8", + "Pre-loop precision should be 8") + assert.Contains(t, body1Str, "display_errors=0", + "Pre-loop display_errors should be 0") + assert.Contains(t, body1Str, "PRELOOP_INI_PRESERVED", + "Pre-loop INI values should be preserved") + + // Request 2: Change INI values during request + resp2, err := http.Get(ts.URL + "/worker-with-ini.php?action=change_ini") + assert.NoError(t, err) + body2, _ := io.ReadAll(resp2.Body) + _ = resp2.Body.Close() + + body2Str := string(body2) + t.Logf("Request 2 response: %s", body2Str) + assert.Contains(t, body2Str, "INI_CHANGED") + assert.Contains(t, body2Str, "precision=5", + "INI should be changed during request") + + // Request 3: Check that pre-loop INI values are restored + resp3, err := http.Get(ts.URL + "/worker-with-ini.php?action=check") + assert.NoError(t, err) + body3, _ := io.ReadAll(resp3.Body) + _ = resp3.Body.Close() + + body3Str := string(body3) + t.Logf("Request 3 response: %s", body3Str) + assert.Contains(t, body3Str, "precision=8", + "Pre-loop precision should be restored to 8.\nResponse: %s", body3Str) + assert.Contains(t, body3Str, "display_errors=0", + "Pre-loop display_errors should be restored to 0.\nResponse: %s", body3Str) + assert.Contains(t, body3Str, "PRELOOP_INI_PRESERVED", + "Pre-loop INI values should be restored after request changes.\nResponse: %s", body3Str) + + }, &testOptions{ + workerScript: "worker-with-ini.php", + nbWorkers: 1, + nbParallelRequests: 1, + realServer: true, + }) +} diff --git a/testdata/ini-leak.php b/testdata/ini-leak.php new file mode 100644 index 0000000000..286f56aa9d --- /dev/null +++ b/testdata/ini-leak.php @@ -0,0 +1,67 @@ +getMessage(); + } + + restore_error_handler(); + + // Now output everything + $output[] = "save_handler_before=" . $saveHandlerBefore; + $output[] = "SESSION_START_RESULT=" . ($result ? "true" : "false"); + if ($error) { + $output[] = "ERROR:" . $error; + } + if ($exception) { + $output[] = "EXCEPTION:" . $exception; + } + break; + + case 'just_start': + // Simple session start without any custom handler + // This should always work + session_id('test-session-id-3'); + session_start(); + $_SESSION['test'] = 'value'; + session_write_close(); + $output[] = "SESSION_STARTED_OK"; + break; + + default: + $output[] = "UNKNOWN_ACTION"; + } + + echo implode("\n", $output); +}; diff --git a/testdata/worker-with-ini.php b/testdata/worker-with-ini.php new file mode 100644 index 0000000000..31e52395dc --- /dev/null +++ b/testdata/worker-with-ini.php @@ -0,0 +1,63 @@ +getMessage(); + } + + restore_error_handler(); + + if ($error) { + $output[] = "ERROR:" . $error; + } + break; + + case 'check': + default: + $saveHandler = ini_get('session.save_handler'); + $output[] = "save_handler=$saveHandler"; + if ($saveHandler === 'user') { + $output[] = "HANDLER_PRESERVED"; + } else { + $output[] = "HANDLER_LOST"; + } + } + + echo implode("\n", $output); + }); +} while ($ok); From be2d6b96eb48881b8ff578dd94ffdcc778ba0e5a Mon Sep 17 00:00:00 2001 From: Tim Nelles Date: Wed, 4 Feb 2026 13:09:44 +0100 Subject: [PATCH 28/59] Merge commit from fork Compute base-image fingerprint from bake metadata and store it under a vendor label. Add a local test script to reproduce the fingerprint and compare runs. Co-authored-by: Tim Nelles --- .github/workflows/docker.yaml | 50 +++--------------- docker-bake.hcl | 7 +++ scripts/ci/compute-fingerprint.sh | 85 +++++++++++++++++++++++++++++++ scripts/ci/verify_image_tags.sh | 81 +++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 42 deletions(-) create mode 100755 scripts/ci/compute-fingerprint.sh create mode 100755 scripts/ci/verify_image_tags.sh diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 01928c32d9..7073fbfc37 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -50,54 +50,19 @@ jobs: php85_version: ${{ steps.check.outputs.php85_version }} skip: ${{ steps.check.outputs.skip }} ref: ${{ steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }} + base_fingerprint: ${{ steps.check.outputs.base_fingerprint }} steps: - - name: Check PHP versions - id: check - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_84_LATEST=$(skopeo inspect docker://docker.io/library/php:8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_85_LATEST=$(skopeo inspect docker://docker.io/library/php:8.5 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - { - echo php_version="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST},${PHP_85_LATEST}" - echo php82_version="${PHP_82_LATEST//./-}" - echo php83_version="${PHP_83_LATEST//./-}" - echo php84_version="${PHP_84_LATEST//./-}" - echo php85_version="${PHP_85_LATEST//./-}" - } >> "${GITHUB_OUTPUT}" - - # Check if the Docker images must be rebuilt - if [[ "${GITHUB_EVENT_NAME}" != "schedule" ]]; then - echo skip=false >> "${GITHUB_OUTPUT}" - exit 0 - fi - - FRANKENPHP_LATEST_TAG=$(gh release view --repo php/frankenphp --json tagName --jq '.tagName') - FRANKENPHP_LATEST_TAG_NO_PREFIX="${FRANKENPHP_LATEST_TAG#v}" - FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - FRANKENPHP_84_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-php8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - FRANKENPHP_85_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-php8.5 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - - if [[ "${FRANKENPHP_82_LATEST}" == "${PHP_82_LATEST}" ]] && [[ "${FRANKENPHP_83_LATEST}" == "${PHP_83_LATEST}" ]] && [[ "${FRANKENPHP_84_LATEST}" == "${PHP_84_LATEST}" ]] && [[ "${FRANKENPHP_85_LATEST}" == "${PHP_85_LATEST}" ]]; then - echo skip=true >> "${GITHUB_OUTPUT}" - exit 0 - fi - - { - echo ref="${FRANKENPHP_LATEST_TAG}" - echo skip=false - } >> "${GITHUB_OUTPUT}" - uses: actions/checkout@v6 - if: ${{ !fromJson(steps.check.outputs.skip) }} with: - ref: ${{ steps.check.outputs.ref }} + fetch-depth: 0 persist-credentials: false - name: Set up Docker Buildx - if: ${{ !fromJson(steps.check.outputs.skip) }} uses: docker/setup-buildx-action@v3 + - name: Check PHP versions and base image fingerprint + id: check + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./scripts/ci/compute-fingerprint.sh - name: Create variants matrix if: ${{ !fromJson(steps.check.outputs.skip) }} id: matrix @@ -192,6 +157,7 @@ jobs: SHA: ${{ github.sha }} VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }} PHP_VERSION: ${{ needs.prepare.outputs.php_version }} + BASE_FINGERPRINT: ${{ needs.prepare.outputs.base_fingerprint }} - # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600 name: Export metadata if: fromJson(needs.prepare.outputs.push) diff --git a/docker-bake.hcl b/docker-bake.hcl index ec4a8d52a7..9a397fc314 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -14,6 +14,10 @@ variable "GO_VERSION" { default = "1.25" } +variable "BASE_FINGERPRINT" { + default = "" +} + variable "SPC_OPT_BUILD_ARGS" { default = "" } @@ -120,6 +124,7 @@ target "default" { "org.opencontainers.image.created" = "${timestamp()}" "org.opencontainers.image.version" = VERSION "org.opencontainers.image.revision" = SHA + "dev.frankenphp.base.fingerprint" = BASE_FINGERPRINT } args = { FRANKENPHP_VERSION = VERSION @@ -146,6 +151,7 @@ target "static-builder-musl" { "org.opencontainers.image.created" = "${timestamp()}" "org.opencontainers.image.version" = VERSION "org.opencontainers.image.revision" = SHA + "dev.frankenphp.base.fingerprint" = BASE_FINGERPRINT } args = { FRANKENPHP_VERSION = VERSION @@ -171,6 +177,7 @@ target "static-builder-gnu" { "org.opencontainers.image.created" = "${timestamp()}" "org.opencontainers.image.version" = VERSION "org.opencontainers.image.revision" = SHA + "dev.frankenphp.base.fingerprint" = BASE_FINGERPRINT } args = { FRANKENPHP_VERSION = VERSION diff --git a/scripts/ci/compute-fingerprint.sh b/scripts/ci/compute-fingerprint.sh new file mode 100755 index 0000000000..731d8e1c2e --- /dev/null +++ b/scripts/ci/compute-fingerprint.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +write_output() { + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "$1" >> "${GITHUB_OUTPUT}" + else + echo "$1" + fi +} + +get_php_version() { + local version="$1" + skopeo inspect "docker://docker.io/library/php:${version}" \ + --override-os linux \ + --override-arch amd64 | + jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")' +} + +PHP_82_LATEST="$(get_php_version 8.2)" +PHP_83_LATEST="$(get_php_version 8.3)" +PHP_84_LATEST="$(get_php_version 8.4)" +PHP_85_LATEST="$(get_php_version 8.5)" + +PHP_VERSION="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST},${PHP_85_LATEST}" +write_output "php_version=${PHP_VERSION}" +write_output "php82_version=${PHP_82_LATEST//./-}" +write_output "php83_version=${PHP_83_LATEST//./-}" +write_output "php84_version=${PHP_84_LATEST//./-}" +write_output "php85_version=${PHP_85_LATEST//./-}" + +if [[ "${GITHUB_EVENT_NAME:-}" == "schedule" ]]; then + FRANKENPHP_LATEST_TAG="$(gh release view --repo php/frankenphp --json tagName --jq '.tagName')" + git checkout "${FRANKENPHP_LATEST_TAG}" +fi + +METADATA="$(PHP_VERSION="${PHP_VERSION}" docker buildx bake --print | jq -c)" + +BASE_IMAGES=() +while IFS= read -r image; do + BASE_IMAGES+=("${image}") +done < <(jq -r ' + .target[]?.contexts? | to_entries[]? + | select(.value | startswith("docker-image://")) + | .value + | sub("^docker-image://"; "") +' <<< "${METADATA}" | sort -u) + +BASE_IMAGE_DIGESTS=() +for image in "${BASE_IMAGES[@]}"; do + if [[ "${image}" == */* ]]; then + ref="docker://docker.io/${image}" + else + ref="docker://docker.io/library/${image}" + fi + digest="$(skopeo inspect "${ref}" \ + --override-os linux \ + --override-arch amd64 \ + --format '{{.Digest}}')" + BASE_IMAGE_DIGESTS+=("${image}@${digest}") +done + +BASE_FINGERPRINT="$(printf '%s\n' "${BASE_IMAGE_DIGESTS[@]}" | sort | sha256sum | awk '{print $1}')" +write_output "base_fingerprint=${BASE_FINGERPRINT}" + +if [[ "${GITHUB_EVENT_NAME:-}" != "schedule" ]]; then + write_output "skip=false" + exit 0 +fi + +FRANKENPHP_LATEST_TAG_NO_PREFIX="${FRANKENPHP_LATEST_TAG#v}" +EXISTING_FINGERPRINT=$( + skopeo inspect "docker://docker.io/dunglas/frankenphp:${FRANKENPHP_LATEST_TAG_NO_PREFIX}" \ + --override-os linux \ + --override-arch amd64 | + jq -r '.Labels["dev.frankenphp.base.fingerprint"] // empty' +) + +if [[ -n "${EXISTING_FINGERPRINT}" ]] && [[ "${EXISTING_FINGERPRINT}" == "${BASE_FINGERPRINT}" ]]; then + write_output "skip=true" + exit 0 +fi + +write_output "ref=${FRANKENPHP_LATEST_TAG}" +write_output "skip=false" diff --git a/scripts/ci/verify_image_tags.sh b/scripts/ci/verify_image_tags.sh new file mode 100755 index 0000000000..60745ab001 --- /dev/null +++ b/scripts/ci/verify_image_tags.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +PHP_VERSION="${PHP_VERSION:-}" +GO_VERSION="${GO_VERSION:-}" +USE_LATEST_PHP="${USE_LATEST_PHP:-0}" + +if [[ -z "${GO_VERSION}" ]]; then + GO_VERSION="$(awk -F'"' '/variable "GO_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" + GO_VERSION="${GO_VERSION:-1.25}" +fi + +if [[ -z "${PHP_VERSION}" ]]; then + PHP_VERSION="$(awk -F'"' '/variable "PHP_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" + PHP_VERSION="${PHP_VERSION:-8.2,8.3,8.4,8.5}" +fi + +if [[ "${USE_LATEST_PHP}" == "1" ]]; then + PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_84_LATEST=$(skopeo inspect docker://docker.io/library/php:8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_85_LATEST=$(skopeo inspect docker://docker.io/library/php:8.5 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_VERSION="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST},${PHP_85_LATEST}" +fi + +OS_LIST=() +while IFS= read -r os; do + OS_LIST+=("${os}") +done < <(python3 - <<'PY' +import re + +with open("docker-bake.hcl", "r", encoding="utf-8") as f: + data = f.read() + +# Find the first "os = [ ... ]" block and extract quoted values +m = re.search(r'os\s*=\s*\[(.*?)\]', data, re.S) +if not m: + raise SystemExit(1) + +vals = re.findall(r'"([^"]+)"', m.group(1)) +for v in vals: + print(v) +PY +) + +IFS=',' read -r -a PHP_VERSIONS <<< "${PHP_VERSION}" + +BASE_IMAGES=() +for os in "${OS_LIST[@]}"; do + BASE_IMAGES+=("golang:${GO_VERSION}-${os}") + for pv in "${PHP_VERSIONS[@]}"; do + BASE_IMAGES+=("php:${pv}-zts-${os}") + done +done + +BASE_IMAGES=($(printf '%s\n' "${BASE_IMAGES[@]}" | sort -u)) + +BASE_IMAGE_DIGESTS=() +for image in "${BASE_IMAGES[@]}"; do + if [[ "${image}" == */* ]]; then + ref="docker://docker.io/${image}" + else + ref="docker://docker.io/library/${image}" + fi + digest="$(skopeo inspect "${ref}" --override-os linux --override-arch amd64 --format '{{.Digest}}')" + BASE_IMAGE_DIGESTS+=("${image}@${digest}") +done + +hash_cmd="sha256sum" +if ! command -v "${hash_cmd}" >/dev/null 2>&1; then + hash_cmd="shasum -a 256" +fi + +fingerprint="$(printf '%s\n' "${BASE_IMAGE_DIGESTS[@]}" | sort | ${hash_cmd} | awk '{print $1}')" + +echo "PHP_VERSION=${PHP_VERSION}" +echo "GO_VERSION=${GO_VERSION}" +echo "OS_LIST=${OS_LIST[*]}" +echo "Base images:" +printf ' %s\n' "${BASE_IMAGES[@]}" +echo "Fingerprint: ${fingerprint}" From 3f2ed374c86823d92983a435b9ce71a1becc358c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 4 Feb 2026 17:22:01 +0100 Subject: [PATCH 29/59] ci: disable Docker cache when building prod images --- .github/workflows/docker.yaml | 12 ++++++------ .github/workflows/static.yaml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 7073fbfc37..af7dc54049 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -146,12 +146,12 @@ jobs: ${{ (github.event_name == 'pull_request') && '*.args.NO_COMPRESS=1' || '' }} *.tags= *.platform=${{ matrix.platform }} - builder-${{ matrix.variant }}.cache-from=type=gha,scope=builder-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }} - builder-${{ matrix.variant }}.cache-from=type=gha,scope=refs/heads/main-builder-${{ matrix.variant }}-${{ matrix.platform }} - builder-${{ matrix.variant }}.cache-to=type=gha,scope=builder-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true - runner-${{ matrix.variant }}.cache-from=type=gha,scope=runner-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }} - runner-${{ matrix.variant }}.cache-from=type=gha,scope=refs/heads/main-runner-${{ matrix.variant }}-${{ matrix.platform }} - runner-${{ matrix.variant }}.cache-to=type=gha,scope=runner-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true + ${{ fromJson(needs.prepare.outputs.push) && '' || format('builder-{0}.cache-from=type=gha,scope=builder-{0}-{1}-{2}', matrix.variant, needs.prepare.outputs.ref || github.ref, matrix.platform) }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('builder-{0}.cache-from=type=gha,scope=refs/heads/main-builder-{0}-{1}', matrix.variant, matrix.platform) }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('builder-{0}.cache-to=type=gha,scope=builder-{0}-{1}-{2},ignore-error=true', matrix.variant, needs.prepare.outputs.ref || github.ref, matrix.platform) }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('runner-{0}.cache-from=type=gha,scope=runner-{0}-{1}-{2}', matrix.variant, needs.prepare.outputs.ref || github.ref, matrix.platform) }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('runner-{0}.cache-from=type=gha,scope=refs/heads/main-runner-{0}-{1}', matrix.variant, matrix.platform) }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('runner-{0}.cache-to=type=gha,scope=runner-{0}-{1}-{2},ignore-error=true', matrix.variant, needs.prepare.outputs.ref || github.ref, matrix.platform) }} ${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} env: SHA: ${{ github.sha }} diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index 98b078b4e3..3908a64e3d 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -150,9 +150,9 @@ jobs: ${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-musl.args.NO_COMPRESS=1' || '' }} *.tags= *.platform=${{ matrix.platform }} - *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} - *.cache-from=type=gha,scope=refs/heads/main-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} - *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }},ignore-error=true + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope={0}-static-builder-musl{1}{2}', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope=refs/heads/main-static-builder-musl{0}{1}', matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-to=type=gha,scope={0}-static-builder-musl{1}{2},ignore-error=true', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} env: SHA: ${{ github.sha }} @@ -301,9 +301,9 @@ jobs: ${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-gnu.args.NO_COMPRESS=1' || '' }} *.tags= *.platform=${{ matrix.platform }} - *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu - *.cache-from=type=gha,scope=refs/heads/main-static-builder-gnu - *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu,ignore-error=true + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope={0}-static-builder-gnu', needs.prepare.outputs.ref || github.ref) }} + ${{ fromJson(needs.prepare.outputs.push) && '' || '*.cache-from=type=gha,scope=refs/heads/main-static-builder-gnu' }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-to=type=gha,scope={0}-static-builder-gnu,ignore-error=true', needs.prepare.outputs.ref || github.ref) }} ${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} env: SHA: ${{ github.sha }} From db59edb590e5738b618191e3cb758ce0b65b6436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 5 Feb 2026 12:48:25 +0100 Subject: [PATCH 30/59] ci: fix shellcheck errors and improve consistency (#2165) --- .../scripts/docker-compute-fingerprints.sh | 72 ++++++++-------- .github/scripts/docker-verify-fingerprints.sh | 82 +++++++++++++++++++ .github/workflows/docker.yaml | 2 +- frankenphp.c | 6 +- scripts/ci/verify_image_tags.sh | 81 ------------------ 5 files changed, 123 insertions(+), 120 deletions(-) rename scripts/ci/compute-fingerprint.sh => .github/scripts/docker-compute-fingerprints.sh (51%) create mode 100755 .github/scripts/docker-verify-fingerprints.sh delete mode 100755 scripts/ci/verify_image_tags.sh diff --git a/scripts/ci/compute-fingerprint.sh b/.github/scripts/docker-compute-fingerprints.sh similarity index 51% rename from scripts/ci/compute-fingerprint.sh rename to .github/scripts/docker-compute-fingerprints.sh index 731d8e1c2e..709b45e55a 100755 --- a/scripts/ci/compute-fingerprint.sh +++ b/.github/scripts/docker-compute-fingerprints.sh @@ -2,19 +2,19 @@ set -euo pipefail write_output() { - if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - echo "$1" >> "${GITHUB_OUTPUT}" - else - echo "$1" - fi + if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "$1" >>"${GITHUB_OUTPUT}" + else + echo "$1" + fi } get_php_version() { - local version="$1" - skopeo inspect "docker://docker.io/library/php:${version}" \ - --override-os linux \ - --override-arch amd64 | - jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")' + local version="$1" + skopeo inspect "docker://docker.io/library/php:${version}" \ + --override-os linux \ + --override-arch amd64 | + jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")' } PHP_82_LATEST="$(get_php_version 8.2)" @@ -30,55 +30,55 @@ write_output "php84_version=${PHP_84_LATEST//./-}" write_output "php85_version=${PHP_85_LATEST//./-}" if [[ "${GITHUB_EVENT_NAME:-}" == "schedule" ]]; then - FRANKENPHP_LATEST_TAG="$(gh release view --repo php/frankenphp --json tagName --jq '.tagName')" - git checkout "${FRANKENPHP_LATEST_TAG}" + FRANKENPHP_LATEST_TAG="$(gh release view --repo php/frankenphp --json tagName --jq '.tagName')" + git checkout "${FRANKENPHP_LATEST_TAG}" fi METADATA="$(PHP_VERSION="${PHP_VERSION}" docker buildx bake --print | jq -c)" BASE_IMAGES=() while IFS= read -r image; do - BASE_IMAGES+=("${image}") + BASE_IMAGES+=("${image}") done < <(jq -r ' - .target[]?.contexts? | to_entries[]? - | select(.value | startswith("docker-image://")) - | .value - | sub("^docker-image://"; "") -' <<< "${METADATA}" | sort -u) + .target[]?.contexts? | to_entries[]? + | select(.value | startswith("docker-image://")) + | .value + | sub("^docker-image://"; "") +' <<<"${METADATA}" | sort -u) BASE_IMAGE_DIGESTS=() for image in "${BASE_IMAGES[@]}"; do - if [[ "${image}" == */* ]]; then - ref="docker://docker.io/${image}" - else - ref="docker://docker.io/library/${image}" - fi - digest="$(skopeo inspect "${ref}" \ - --override-os linux \ - --override-arch amd64 \ - --format '{{.Digest}}')" - BASE_IMAGE_DIGESTS+=("${image}@${digest}") + if [[ "${image}" == */* ]]; then + ref="docker://docker.io/${image}" + else + ref="docker://docker.io/library/${image}" + fi + digest="$(skopeo inspect "${ref}" \ + --override-os linux \ + --override-arch amd64 \ + --format '{{.Digest}}')" + BASE_IMAGE_DIGESTS+=("${image}@${digest}") done BASE_FINGERPRINT="$(printf '%s\n' "${BASE_IMAGE_DIGESTS[@]}" | sort | sha256sum | awk '{print $1}')" write_output "base_fingerprint=${BASE_FINGERPRINT}" if [[ "${GITHUB_EVENT_NAME:-}" != "schedule" ]]; then - write_output "skip=false" - exit 0 + write_output "skip=false" + exit 0 fi FRANKENPHP_LATEST_TAG_NO_PREFIX="${FRANKENPHP_LATEST_TAG#v}" EXISTING_FINGERPRINT=$( - skopeo inspect "docker://docker.io/dunglas/frankenphp:${FRANKENPHP_LATEST_TAG_NO_PREFIX}" \ - --override-os linux \ - --override-arch amd64 | - jq -r '.Labels["dev.frankenphp.base.fingerprint"] // empty' + skopeo inspect "docker://docker.io/dunglas/frankenphp:${FRANKENPHP_LATEST_TAG_NO_PREFIX}" \ + --override-os linux \ + --override-arch amd64 | + jq -r '.Labels["dev.frankenphp.base.fingerprint"] // empty' ) if [[ -n "${EXISTING_FINGERPRINT}" ]] && [[ "${EXISTING_FINGERPRINT}" == "${BASE_FINGERPRINT}" ]]; then - write_output "skip=true" - exit 0 + write_output "skip=true" + exit 0 fi write_output "ref=${FRANKENPHP_LATEST_TAG}" diff --git a/.github/scripts/docker-verify-fingerprints.sh b/.github/scripts/docker-verify-fingerprints.sh new file mode 100755 index 0000000000..c33e00cfc1 --- /dev/null +++ b/.github/scripts/docker-verify-fingerprints.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +PHP_VERSION="${PHP_VERSION:-}" +GO_VERSION="${GO_VERSION:-}" +USE_LATEST_PHP="${USE_LATEST_PHP:-0}" + +if [[ -z "${GO_VERSION}" ]]; then + GO_VERSION="$(awk -F'"' '/variable "GO_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" + GO_VERSION="${GO_VERSION:-1.25}" +fi + +if [[ -z "${PHP_VERSION}" ]]; then + PHP_VERSION="$(awk -F'"' '/variable "PHP_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" + PHP_VERSION="${PHP_VERSION:-8.2,8.3,8.4,8.5}" +fi + +if [[ "${USE_LATEST_PHP}" == "1" ]]; then + PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_84_LATEST=$(skopeo inspect docker://docker.io/library/php:8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_85_LATEST=$(skopeo inspect docker://docker.io/library/php:8.5 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') + PHP_VERSION="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST},${PHP_85_LATEST}" +fi + +OS_LIST=() +while IFS= read -r os; do + OS_LIST+=("${os}") +done < <( + python3 - <<'PY' +import re + +with open("docker-bake.hcl", "r", encoding="utf-8") as f: + data = f.read() + +# Find the first "os = [ ... ]" block and extract quoted values +m = re.search(r'os\s*=\s*\[(.*?)\]', data, re.S) +if not m: + raise SystemExit(1) + +vals = re.findall(r'"([^"]+)"', m.group(1)) +for v in vals: + print(v) +PY +) + +IFS=',' read -r -a PHP_VERSIONS <<<"${PHP_VERSION}" + +BASE_IMAGES=() +for os in "${OS_LIST[@]}"; do + BASE_IMAGES+=("golang:${GO_VERSION}-${os}") + for pv in "${PHP_VERSIONS[@]}"; do + BASE_IMAGES+=("php:${pv}-zts-${os}") + done +done + +mapfile -t BASE_IMAGES < <(printf '%s\n' "${BASE_IMAGES[@]}" | sort -u) + +BASE_IMAGE_DIGESTS=() +for image in "${BASE_IMAGES[@]}"; do + if [[ "${image}" == */* ]]; then + ref="docker://docker.io/${image}" + else + ref="docker://docker.io/library/${image}" + fi + digest="$(skopeo inspect "${ref}" --override-os linux --override-arch amd64 --format '{{.Digest}}')" + BASE_IMAGE_DIGESTS+=("${image}@${digest}") +done + +hash_cmd="sha256sum" +if ! command -v "${hash_cmd}" >/dev/null 2>&1; then + hash_cmd="shasum -a 256" +fi + +fingerprint="$(printf '%s\n' "${BASE_IMAGE_DIGESTS[@]}" | sort | ${hash_cmd} | awk '{print $1}')" + +echo "PHP_VERSION=${PHP_VERSION}" +echo "GO_VERSION=${GO_VERSION}" +echo "OS_LIST=${OS_LIST[*]}" +echo "Base images:" +printf ' %s\n' "${BASE_IMAGES[@]}" +echo "Fingerprint: ${fingerprint}" diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index af7dc54049..5eaa3cda98 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -62,7 +62,7 @@ jobs: id: check env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: ./scripts/ci/compute-fingerprint.sh + run: ./.github/scripts/docker-compute-fingerprints.sh - name: Create variants matrix if: ${{ !fromJson(steps.check.outputs.skip) }} id: matrix diff --git a/frankenphp.c b/frankenphp.c index cb3943772a..3206bf36df 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -226,13 +226,15 @@ static void frankenphp_snapshot_ini(void) { if (EG(modified_ini_directives) == NULL) { /* Allocate empty table to mark as snapshotted */ ALLOC_HASHTABLE(worker_ini_snapshot); - zend_hash_init(worker_ini_snapshot, 0, NULL, frankenphp_ini_snapshot_dtor, 0); + zend_hash_init(worker_ini_snapshot, 0, NULL, frankenphp_ini_snapshot_dtor, + 0); return; } uint32_t num_modified = zend_hash_num_elements(EG(modified_ini_directives)); ALLOC_HASHTABLE(worker_ini_snapshot); - zend_hash_init(worker_ini_snapshot, num_modified, NULL, frankenphp_ini_snapshot_dtor, 0); + zend_hash_init(worker_ini_snapshot, num_modified, NULL, + frankenphp_ini_snapshot_dtor, 0); zend_ini_entry *ini_entry; ZEND_HASH_FOREACH_PTR(EG(modified_ini_directives), ini_entry) { diff --git a/scripts/ci/verify_image_tags.sh b/scripts/ci/verify_image_tags.sh deleted file mode 100755 index 60745ab001..0000000000 --- a/scripts/ci/verify_image_tags.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -PHP_VERSION="${PHP_VERSION:-}" -GO_VERSION="${GO_VERSION:-}" -USE_LATEST_PHP="${USE_LATEST_PHP:-0}" - -if [[ -z "${GO_VERSION}" ]]; then - GO_VERSION="$(awk -F'"' '/variable "GO_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" - GO_VERSION="${GO_VERSION:-1.25}" -fi - -if [[ -z "${PHP_VERSION}" ]]; then - PHP_VERSION="$(awk -F'"' '/variable "PHP_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" - PHP_VERSION="${PHP_VERSION:-8.2,8.3,8.4,8.5}" -fi - -if [[ "${USE_LATEST_PHP}" == "1" ]]; then - PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_84_LATEST=$(skopeo inspect docker://docker.io/library/php:8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_85_LATEST=$(skopeo inspect docker://docker.io/library/php:8.5 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') - PHP_VERSION="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST},${PHP_85_LATEST}" -fi - -OS_LIST=() -while IFS= read -r os; do - OS_LIST+=("${os}") -done < <(python3 - <<'PY' -import re - -with open("docker-bake.hcl", "r", encoding="utf-8") as f: - data = f.read() - -# Find the first "os = [ ... ]" block and extract quoted values -m = re.search(r'os\s*=\s*\[(.*?)\]', data, re.S) -if not m: - raise SystemExit(1) - -vals = re.findall(r'"([^"]+)"', m.group(1)) -for v in vals: - print(v) -PY -) - -IFS=',' read -r -a PHP_VERSIONS <<< "${PHP_VERSION}" - -BASE_IMAGES=() -for os in "${OS_LIST[@]}"; do - BASE_IMAGES+=("golang:${GO_VERSION}-${os}") - for pv in "${PHP_VERSIONS[@]}"; do - BASE_IMAGES+=("php:${pv}-zts-${os}") - done -done - -BASE_IMAGES=($(printf '%s\n' "${BASE_IMAGES[@]}" | sort -u)) - -BASE_IMAGE_DIGESTS=() -for image in "${BASE_IMAGES[@]}"; do - if [[ "${image}" == */* ]]; then - ref="docker://docker.io/${image}" - else - ref="docker://docker.io/library/${image}" - fi - digest="$(skopeo inspect "${ref}" --override-os linux --override-arch amd64 --format '{{.Digest}}')" - BASE_IMAGE_DIGESTS+=("${image}@${digest}") -done - -hash_cmd="sha256sum" -if ! command -v "${hash_cmd}" >/dev/null 2>&1; then - hash_cmd="shasum -a 256" -fi - -fingerprint="$(printf '%s\n' "${BASE_IMAGE_DIGESTS[@]}" | sort | ${hash_cmd} | awk '{print $1}')" - -echo "PHP_VERSION=${PHP_VERSION}" -echo "GO_VERSION=${GO_VERSION}" -echo "OS_LIST=${OS_LIST[*]}" -echo "Base images:" -printf ' %s\n' "${BASE_IMAGES[@]}" -echo "Fingerprint: ${fingerprint}" From fba79a6ac8d5f2a83cd8d8bda98f227f66e2762b Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:50:28 +0100 Subject: [PATCH 31/59] feat(extgen): make the generator idempotent and avoid touching the original source (#2011) --- docs/extensions.md | 13 +- internal/extgen/gofile.go | 171 +++-- internal/extgen/gofile_test.go | 711 +++++++++++++++++---- internal/extgen/phpfunc.go | 11 +- internal/extgen/phpfunc_test.go | 6 +- internal/extgen/srcanalyzer.go | 19 +- internal/extgen/srcanalyzer_test.go | 110 +++- internal/extgen/templates/extension.c.tpl | 9 + internal/extgen/templates/extension.go.tpl | 45 +- internal/extgen/templates/extension.h.tpl | 9 + internal/extgen/templates/stub.php.tpl | 9 + 11 files changed, 885 insertions(+), 228 deletions(-) diff --git a/docs/extensions.md b/docs/extensions.md index b4b83e87e9..a81c2355a5 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -591,7 +591,18 @@ GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extensio > [!NOTE] > Don't forget to set the `GEN_STUB_SCRIPT` environment variable to the path of the `gen_stub.php` file in the PHP sources you downloaded earlier. This is the same `gen_stub.php` script mentioned in the manual implementation section. -If everything went well, a new directory named `build` should have been created. This directory contains the generated files for your extension, including the `my_extension.go` file with the generated PHP function stubs. +If everything went well, your project directory should contain the following files for your extension: + +- **`my_extension.go`** - Your original source file (remains unchanged) +- **`my_extension_generated.go`** - Generated file with CGO wrappers that call your functions +- **`my_extension.stub.php`** - PHP stub file for IDE autocompletion +- **`my_extension_arginfo.h`** - PHP argument information +- **`my_extension.h`** - C header file +- **`my_extension.c`** - C implementation file +- **`README.md`** - Documentation + +> [!IMPORTANT] +> **Your source file (`my_extension.go`) is never modified.** The generator creates a separate `_generated.go` file containing CGO wrappers that call your original functions. This means you can safely version control your source file without worrying about generated code polluting it. ### Integrating the Generated Extension into FrankenPHP diff --git a/internal/extgen/gofile.go b/internal/extgen/gofile.go index da015fe461..044696a47f 100644 --- a/internal/extgen/gofile.go +++ b/internal/extgen/gofile.go @@ -4,8 +4,9 @@ import ( "bytes" _ "embed" "fmt" - "os" + "go/format" "path/filepath" + "strings" "text/template" "github.com/Masterminds/sprig/v3" @@ -21,7 +22,7 @@ type GoFileGenerator struct { type goTemplateData struct { PackageName string BaseName string - Imports []string + SanitizedBaseName string Constants []phpConstant Variables []string InternalFunctions []string @@ -30,16 +31,7 @@ type goTemplateData struct { } func (gg *GoFileGenerator) generate() error { - filename := filepath.Join(gg.generator.BuildDir, gg.generator.BaseName+".go") - - if _, err := os.Stat(filename); err == nil { - backupFilename := filename + ".bak" - if err := os.Rename(filename, backupFilename); err != nil { - return fmt.Errorf("backing up existing Go file: %w", err) - } - - gg.generator.SourceFile = backupFilename - } + filename := filepath.Join(gg.generator.BuildDir, gg.generator.BaseName+"_generated.go") content, err := gg.buildContent() if err != nil { @@ -51,38 +43,18 @@ func (gg *GoFileGenerator) generate() error { func (gg *GoFileGenerator) buildContent() (string, error) { sourceAnalyzer := SourceAnalyzer{} - imports, variables, internalFunctions, err := sourceAnalyzer.analyze(gg.generator.SourceFile) + packageName, variables, internalFunctions, err := sourceAnalyzer.analyze(gg.generator.SourceFile) if err != nil { return "", fmt.Errorf("analyzing source file: %w", err) } - filteredImports := make([]string, 0, len(imports)) - for _, imp := range imports { - if imp != `"C"` && imp != `"unsafe"` && imp != `"github.com/dunglas/frankenphp"` && imp != `"runtime/cgo"` { - filteredImports = append(filteredImports, imp) - } - } - classes := make([]phpClass, len(gg.generator.Classes)) copy(classes, gg.generator.Classes) - if len(classes) > 0 { - hasCgo := false - for _, imp := range imports { - if imp == `"runtime/cgo"` { - hasCgo = true - break - } - } - if !hasCgo { - filteredImports = append(filteredImports, `"runtime/cgo"`) - } - } - templateContent, err := gg.getTemplateContent(goTemplateData{ - PackageName: SanitizePackageName(gg.generator.BaseName), + PackageName: packageName, BaseName: gg.generator.BaseName, - Imports: filteredImports, + SanitizedBaseName: SanitizePackageName(gg.generator.BaseName), Constants: gg.generator.Constants, Variables: variables, InternalFunctions: internalFunctions, @@ -94,7 +66,12 @@ func (gg *GoFileGenerator) buildContent() (string, error) { return "", fmt.Errorf("executing template: %w", err) } - return templateContent, nil + fc, err := format.Source([]byte(templateContent)) + if err != nil { + return "", fmt.Errorf("formatting source: %w", err) + } + + return string(fc), nil } func (gg *GoFileGenerator) getTemplateContent(data goTemplateData) (string, error) { @@ -106,6 +83,10 @@ func (gg *GoFileGenerator) getTemplateContent(data goTemplateData) (string, erro funcMap["isVoid"] = func(t phpType) bool { return t == phpVoid } + funcMap["extractGoFunctionName"] = extractGoFunctionName + funcMap["extractGoFunctionSignatureParams"] = extractGoFunctionSignatureParams + funcMap["extractGoFunctionSignatureReturn"] = extractGoFunctionSignatureReturn + funcMap["extractGoFunctionCallParams"] = extractGoFunctionCallParams tmpl := template.Must(template.New("gofile").Funcs(funcMap).Parse(goFileContent)) @@ -128,7 +109,7 @@ type GoParameter struct { Type string } -var phpToGoTypeMap= map[phpType]string{ +var phpToGoTypeMap = map[phpType]string{ phpString: "string", phpInt: "int64", phpFloat: "float64", @@ -146,3 +127,119 @@ func (gg *GoFileGenerator) phpTypeToGoType(phpT phpType) string { return "any" } + +// extractGoFunctionName extracts the Go function name from a Go function signature string. +func extractGoFunctionName(goFunction string) string { + idx := strings.Index(goFunction, "func ") + if idx == -1 { + return "" + } + + start := idx + len("func ") + + end := start + for end < len(goFunction) && goFunction[end] != '(' { + end++ + } + + if end >= len(goFunction) { + return "" + } + + return strings.TrimSpace(goFunction[start:end]) +} + +// extractGoFunctionSignatureParams extracts the parameters from a Go function signature. +func extractGoFunctionSignatureParams(goFunction string) string { + start := strings.IndexByte(goFunction, '(') + if start == -1 { + return "" + } + start++ + + depth := 1 + end := start + for end < len(goFunction) && depth > 0 { + switch goFunction[end] { + case '(': + depth++ + case ')': + depth-- + } + if depth > 0 { + end++ + } + } + + if end >= len(goFunction) { + return "" + } + + return strings.TrimSpace(goFunction[start:end]) +} + +// extractGoFunctionSignatureReturn extracts the return type from a Go function signature. +func extractGoFunctionSignatureReturn(goFunction string) string { + start := strings.IndexByte(goFunction, '(') + if start == -1 { + return "" + } + + depth := 1 + pos := start + 1 + for pos < len(goFunction) && depth > 0 { + switch goFunction[pos] { + case '(': + depth++ + case ')': + depth-- + } + pos++ + } + + if pos >= len(goFunction) { + return "" + } + + end := strings.IndexByte(goFunction[pos:], '{') + if end == -1 { + return "" + } + end += pos + + returnType := strings.TrimSpace(goFunction[pos:end]) + return returnType +} + +// extractGoFunctionCallParams extracts just the parameter names for calling a function. +func extractGoFunctionCallParams(goFunction string) string { + params := extractGoFunctionSignatureParams(goFunction) + if params == "" { + return "" + } + + var names []string + parts := strings.Split(params, ",") + for _, part := range parts { + part = strings.TrimSpace(part) + if len(part) == 0 { + continue + } + + words := strings.Fields(part) + if len(words) > 0 { + names = append(names, words[0]) + } + } + + var result strings.Builder + for i, name := range names { + if i > 0 { + result.WriteString(", ") + } + + result.WriteString(name) + } + + return result.String() +} diff --git a/internal/extgen/gofile_test.go b/internal/extgen/gofile_test.go index 754d95c0e1..a64504a422 100644 --- a/internal/extgen/gofile_test.go +++ b/internal/extgen/gofile_test.go @@ -1,9 +1,11 @@ package extgen import ( + "bytes" + "crypto/sha256" + "encoding/hex" "os" "path/filepath" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -69,16 +71,20 @@ func anotherHelper() { goGen := GoFileGenerator{generator} require.NoError(t, goGen.generate()) - expectedFile := filepath.Join(tmpDir, "test.go") - require.FileExists(t, expectedFile) + sourceStillExists := filepath.Join(tmpDir, "test.go") + require.FileExists(t, sourceStillExists) + sourceStillContent, err := readFile(sourceStillExists) + require.NoError(t, err) + assert.Equal(t, sourceContent, sourceStillContent, "Source file should not be modified") + + generatedFile := filepath.Join(tmpDir, "test_generated.go") + require.FileExists(t, generatedFile) - content, err := readFile(expectedFile) + generatedContent, err := readFile(generatedFile) require.NoError(t, err) - testGoFileBasicStructure(t, content, "test") - testGoFileImports(t, content) - testGoFileExportedFunctions(t, content, generator.Functions) - testGoFileInternalFunctions(t, content) + testGeneratedFileBasicStructure(t, generatedContent, "main", "test") + testGeneratedFileWrappers(t, generatedContent, generator.Functions) } func TestGoFileGenerator_BuildContent(t *testing.T) { @@ -87,6 +93,7 @@ func TestGoFileGenerator_BuildContent(t *testing.T) { baseName string sourceFile string functions []phpFunction + classes []phpClass contains []string notContains []string }{ @@ -107,13 +114,14 @@ func test() { }, }, contains: []string{ - "package simple", + "package main", `#include "simple.h"`, `import "C"`, "func init()", "frankenphp.RegisterExtension(", - "//export test", - "func test()", + "//export go_test", + "func go_test()", + "test()", // wrapper calls original function }, }, { @@ -142,12 +150,10 @@ func process(data *go_string) *go_value { }, }, contains: []string{ - "package complex", - `"fmt"`, - `"strings"`, - `"encoding/json"`, - "//export process", + "package main", + "//export go_process", `"C"`, + "process(", // wrapper calls original function }, }, { @@ -173,9 +179,81 @@ func internalFunc2(data string) { }, }, contains: []string{ + "//export go_publicFunc", + "func go_publicFunc()", + "publicFunc()", // wrapper calls original function + }, + notContains: []string{ "func internalFunc1() string", "func internalFunc2(data string)", - "//export publicFunc", + }, + }, + { + name: "runtime/cgo blank import without classes", + baseName: "no_classes", + sourceFile: createTempSourceFile(t, `package main + +//export_php: getValue(): string +func getValue() string { + return "test" +}`), + functions: []phpFunction{ + { + Name: "getValue", + ReturnType: phpString, + GoFunction: `func getValue() string { + return "test" +}`, + }, + }, + classes: nil, + contains: []string{ + `_ "runtime/cgo"`, + "func init()", + "frankenphp.RegisterExtension(", + }, + notContains: []string{ + "cgo.NewHandle", + "registerGoObject", + "getGoObject", + "removeGoObject", + }, + }, + { + name: "runtime/cgo normal import with classes", + baseName: "with_classes", + sourceFile: createTempSourceFile(t, `package main + +//export_php:class TestClass +type TestStruct struct { + value string +} + +//export_php:method TestClass::getValue(): string +func (ts *TestStruct) GetValue() string { + return ts.value +}`), + functions: []phpFunction{}, + classes: []phpClass{ + { + Name: "TestClass", + GoStruct: "TestStruct", + Methods: []phpClassMethod{ + { + Name: "GetValue", + ReturnType: phpString, + }, + }, + }, + }, + contains: []string{ + `"runtime/cgo"`, + "cgo.NewHandle", + "func registerGoObject", + "func getGoObject", + }, + notContains: []string{ + `_ "runtime/cgo"`, }, }, } @@ -186,6 +264,7 @@ func internalFunc2(data string) { BaseName: tt.baseName, SourceFile: tt.sourceFile, Functions: tt.functions, + Classes: tt.classes, } goGen := GoFileGenerator{generator} @@ -195,6 +274,10 @@ func internalFunc2(data string) { for _, expected := range tt.contains { assert.Contains(t, content, expected, "Generated Go content should contain %q", expected) } + + for _, notExpected := range tt.notContains { + assert.NotContains(t, content, notExpected, "Generated Go content should NOT contain %q", notExpected) + } }) } } @@ -204,11 +287,11 @@ func TestGoFileGenerator_PackageNameSanitization(t *testing.T) { baseName string expectedPackage string }{ - {"simple", "simple"}, - {"my-extension", "my_extension"}, - {"ext.with.dots", "ext_with_dots"}, - {"123invalid", "_123invalid"}, - {"valid_name", "valid_name"}, + {"simple", "main"}, + {"my-extension", "main"}, + {"ext.with.dots", "main"}, + {"123invalid", "main"}, + {"valid_name", "main"}, } for _, tt := range tests { @@ -275,57 +358,6 @@ func TestGoFileGenerator_ErrorHandling(t *testing.T) { } } -func TestGoFileGenerator_ImportFiltering(t *testing.T) { - sourceContent := `package main - -import ( - "C" - "fmt" - "strings" - "github.com/dunglas/frankenphp/internal/extensions/types" - "github.com/other/package" - originalPkg "github.com/test/original" -) - -//export_php: test(): void -func test() {}` - - sourceFile := createTempSourceFile(t, sourceContent) - - generator := &Generator{ - BaseName: "importtest", - SourceFile: sourceFile, - Functions: []phpFunction{ - {Name: "test", ReturnType: phpVoid, GoFunction: "func test() {}"}, - }, - } - - goGen := GoFileGenerator{generator} - content, err := goGen.buildContent() - require.NoError(t, err) - - expectedImports := []string{ - `"fmt"`, - `"strings"`, - `"github.com/other/package"`, - } - - for _, imp := range expectedImports { - assert.Contains(t, content, imp, "Generated content should contain import: %s", imp) - } - - forbiddenImports := []string{ - `"C"`, - } - - cImportCount := strings.Count(content, `"C"`) - assert.Equal(t, 1, cImportCount, "Expected exactly 1 occurrence of 'import \"C\"'") - - for _, imp := range forbiddenImports[1:] { - assert.NotContains(t, content, imp, "Generated content should NOT contain import: %s", imp) - } -} - func TestGoFileGenerator_ComplexScenario(t *testing.T) { sourceContent := `package example @@ -398,26 +430,12 @@ func debugPrint(msg string) { goGen := GoFileGenerator{generator} content, err := goGen.buildContent() require.NoError(t, err) - assert.Contains(t, content, "package complex_example", "Package name should be sanitized") - - internalFuncs := []string{ - "func internalProcess(data string) string", - "func validateFormat(input string) bool", - "func jsonHelper(data any) ([]byte, error)", - "func debugPrint(msg string)", - } - - for _, fn := range internalFuncs { - assert.Contains(t, content, fn, "Generated content should contain internal function: %s", fn) - } + assert.Contains(t, content, "package example", "Package name should match source package") for _, fn := range functions { - exportDirective := "//export " + fn.Name + exportDirective := "//export go_" + fn.Name assert.Contains(t, content, exportDirective, "Generated content should contain export directive: %s", exportDirective) } - - assert.False(t, strings.Contains(content, "types.Array") || strings.Contains(content, "types.Bool"), "Types should be replaced (types.* should not appear)") - assert.True(t, strings.Contains(content, "return Array(") && strings.Contains(content, "return Bool("), "Replaced types should appear without types prefix") } func TestGoFileGenerator_MethodWrapperWithNullableParams(t *testing.T) { @@ -602,6 +620,434 @@ func (as *ArrayStruct) FilterData(data frankenphp.AssociativeArray, filter strin assert.Contains(t, content, "//export FilterData_wrapper", "Generated content should contain FilterData export directive") } +func TestGoFileGenerator_Idempotency(t *testing.T) { + tmpDir := t.TempDir() + + sourceContent := `package main + +import ( + "fmt" + "strings" +) + +//export_php: greet(name string): string +func greet(name *go_string) *go_value { + return String("Hello " + CStringToGoString(name)) +} + +func internalHelper(data string) string { + return strings.ToUpper(data) +}` + + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644)) + + generator := &Generator{ + BaseName: "test", + SourceFile: sourceFile, + BuildDir: tmpDir, + Functions: []phpFunction{ + { + Name: "greet", + ReturnType: phpString, + GoFunction: `func greet(name *go_string) *go_value { + return String("Hello " + CStringToGoString(name)) +}`, + }, + }, + } + + goGen := GoFileGenerator{generator} + require.NoError(t, goGen.generate(), "First generation should succeed") + + generatedFile := filepath.Join(tmpDir, "test_generated.go") + require.FileExists(t, generatedFile, "Generated file should exist after first run") + + firstRunContent, err := os.ReadFile(generatedFile) + require.NoError(t, err) + firstRunSourceContent, err := os.ReadFile(sourceFile) + require.NoError(t, err) + + require.NoError(t, goGen.generate(), "Second generation should succeed") + + secondRunContent, err := os.ReadFile(generatedFile) + require.NoError(t, err) + secondRunSourceContent, err := os.ReadFile(sourceFile) + require.NoError(t, err) + + assert.True(t, bytes.Equal(firstRunContent, secondRunContent), "Generated file content should be identical between runs") + assert.True(t, bytes.Equal(firstRunSourceContent, secondRunSourceContent), "Source file should remain unchanged after both runs") + assert.Equal(t, sourceContent, string(secondRunSourceContent), "Source file content should match original") +} + +func TestGoFileGenerator_HeaderComments(t *testing.T) { + tmpDir := t.TempDir() + + sourceContent := `package main + +//export_php: test(): void +func test() { + // simple function +}` + + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644)) + + generator := &Generator{ + BaseName: "test", + SourceFile: sourceFile, + BuildDir: tmpDir, + Functions: []phpFunction{ + { + Name: "test", + ReturnType: phpVoid, + GoFunction: "func test() {\n\t// simple function\n}", + }, + }, + } + + goGen := GoFileGenerator{generator} + require.NoError(t, goGen.generate()) + + generatedFile := filepath.Join(tmpDir, "test_generated.go") + require.FileExists(t, generatedFile) + + assertContainsHeaderComment(t, generatedFile) +} + +func TestExtractGoFunctionName(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "simple function", + input: "func test() {}", + expected: "test", + }, + { + name: "function with params", + input: "func calculate(a int, b int) int {}", + expected: "calculate", + }, + { + name: "function with complex params", + input: "func process(data *go_string, opts *go_nullable) *go_value {}", + expected: "process", + }, + { + name: "function with whitespace", + input: "func spacedName () {}", + expected: "spacedName", + }, + { + name: "no func keyword", + input: "test() {}", + expected: "", + }, + { + name: "empty string", + input: "", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractGoFunctionName(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestExtractGoFunctionSignatureParams(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "no parameters", + input: "func test() {}", + expected: "", + }, + { + name: "single parameter", + input: "func test(name string) {}", + expected: "name string", + }, + { + name: "multiple parameters", + input: "func test(a int, b string, c bool) {}", + expected: "a int, b string, c bool", + }, + { + name: "pointer parameters", + input: "func test(data *go_string) {}", + expected: "data *go_string", + }, + { + name: "nested parentheses", + input: "func test(fn func(int) string) {}", + expected: "fn func(int) string", + }, + { + name: "variadic parameters", + input: "func test(args ...string) {}", + expected: "args ...string", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractGoFunctionSignatureParams(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestExtractGoFunctionSignatureReturn(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "no return type", + input: "func test() {}", + expected: "", + }, + { + name: "single return type", + input: "func test() string {}", + expected: "string", + }, + { + name: "pointer return type", + input: "func test() *go_value {}", + expected: "*go_value", + }, + { + name: "multiple return types", + input: "func test() (string, error) {}", + expected: "(string, error)", + }, + { + name: "named return values", + input: "func test() (result string, err error) {}", + expected: "(result string, err error)", + }, + { + name: "complex return type", + input: "func test() unsafe.Pointer {}", + expected: "unsafe.Pointer", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractGoFunctionSignatureReturn(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestExtractGoFunctionCallParams(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "no parameters", + input: "func test() {}", + expected: "", + }, + { + name: "single parameter", + input: "func test(name string) {}", + expected: "name", + }, + { + name: "multiple parameters", + input: "func test(a int, b string, c bool) {}", + expected: "a, b, c", + }, + { + name: "pointer parameters", + input: "func test(data *go_string, opts *go_nullable) {}", + expected: "data, opts", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractGoFunctionCallParams(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestGoFileGenerator_SourceFilePreservation(t *testing.T) { + tmpDir := t.TempDir() + + sourceContent := `package main + +import "fmt" + +//export_php: greet(name string): string +func greet(name *go_string) *go_value { + return String(fmt.Sprintf("Hello, %s!", CStringToGoString(name))) +} + +func internalHelper() { + fmt.Println("internal") +}` + + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644)) + + hashBefore := computeFileHash(t, sourceFile) + + generator := &Generator{ + BaseName: "test", + SourceFile: sourceFile, + BuildDir: tmpDir, + Functions: []phpFunction{ + { + Name: "greet", + ReturnType: phpString, + GoFunction: `func greet(name *go_string) *go_value { + return String(fmt.Sprintf("Hello, %s!", CStringToGoString(name))) +}`, + }, + }, + } + + goGen := GoFileGenerator{generator} + require.NoError(t, goGen.generate()) + + hashAfter := computeFileHash(t, sourceFile) + + assert.Equal(t, hashBefore, hashAfter, "Source file hash should remain unchanged after generation") + + contentAfter, err := os.ReadFile(sourceFile) + require.NoError(t, err) + assert.Equal(t, sourceContent, string(contentAfter), "Source file content should be byte-for-byte identical") +} + +func TestGoFileGenerator_WrapperParameterForwarding(t *testing.T) { + tmpDir := t.TempDir() + + sourceContent := `package main + +import "fmt" + +//export_php: process(name string, count int): string +func process(name *go_string, count long) *go_value { + n := CStringToGoString(name) + return String(fmt.Sprintf("%s: %d", n, count)) +} + +//export_php: simple(): void +func simple() {}` + + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644)) + + functions := []phpFunction{ + { + Name: "process", + ReturnType: phpString, + GoFunction: `func process(name *go_string, count long) *go_value { + n := CStringToGoString(name) + return String(fmt.Sprintf("%s: %d", n, count)) +}`, + }, + { + Name: "simple", + ReturnType: phpVoid, + GoFunction: "func simple() {}", + }, + } + + generator := &Generator{ + BaseName: "wrapper_test", + SourceFile: sourceFile, + BuildDir: tmpDir, + Functions: functions, + } + + goGen := GoFileGenerator{generator} + content, err := goGen.buildContent() + require.NoError(t, err) + + assert.Contains(t, content, "//export go_process", "Should have wrapper export directive") + assert.Contains(t, content, "func go_process(", "Should have wrapper function") + assert.Contains(t, content, "process(", "Wrapper should call original function") + + assert.Contains(t, content, "//export go_simple", "Should have simple wrapper export directive") + assert.Contains(t, content, "func go_simple()", "Should have simple wrapper function") + assert.Contains(t, content, "simple()", "Simple wrapper should call original function") +} + +func TestGoFileGenerator_MalformedSource(t *testing.T) { + tests := []struct { + name string + sourceContent string + expectError bool + }{ + { + name: "missing package declaration", + sourceContent: "func test() {}", + expectError: true, + }, + { + name: "syntax error", + sourceContent: "package main\nfunc test( {}", + expectError: true, + }, + { + name: "incomplete function", + sourceContent: "package main\nfunc test() {", + expectError: true, + }, + { + name: "valid minimal source", + sourceContent: "package main\nfunc test() {}", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(tt.sourceContent), 0644)) + + generator := &Generator{ + BaseName: "test", + SourceFile: sourceFile, + BuildDir: tmpDir, + } + + goGen := GoFileGenerator{generator} + _, err := goGen.buildContent() + + if tt.expectError { + assert.Error(t, err, "Expected error for malformed source") + } else { + assert.NoError(t, err, "Should not error for valid source") + } + + contentAfter, readErr := os.ReadFile(sourceFile) + require.NoError(t, readErr) + assert.Equal(t, tt.sourceContent, string(contentAfter), "Source file should remain unchanged even on error") + }) + } +} + func TestGoFileGenerator_MethodWrapperWithNullableArrayParams(t *testing.T) { tmpDir := t.TempDir() @@ -672,37 +1118,6 @@ func createTempSourceFile(t *testing.T, content string) string { return tmpFile } -func testGoFileBasicStructure(t *testing.T, content, baseName string) { - requiredElements := []string{ - "package " + SanitizePackageName(baseName), - "// #include ", - `// #include "` + baseName + `.h"`, - `import "C"`, - "func init() {", - "frankenphp.RegisterExtension(", - "}", - } - - for _, element := range requiredElements { - assert.Contains(t, content, element, "Go file should contain: %s", element) - } -} - -func testGoFileImports(t *testing.T, content string) { - cImportCount := strings.Count(content, `"C"`) - assert.Equal(t, 1, cImportCount, "Expected exactly 1 C import") -} - -func testGoFileExportedFunctions(t *testing.T, content string, functions []phpFunction) { - for _, fn := range functions { - exportDirective := "//export " + fn.Name - assert.Contains(t, content, exportDirective, "Go file should contain export directive: %s", exportDirective) - - funcStart := "func " + fn.Name + "(" - assert.Contains(t, content, funcStart, "Go file should contain function definition: %s", funcStart) - } -} - func TestGoFileGenerator_MethodWrapperWithCallableParams(t *testing.T) { tmpDir := t.TempDir() @@ -822,22 +1237,54 @@ func TestGoFileGenerator_phpTypeToGoType(t *testing.T) { }) } -func testGoFileInternalFunctions(t *testing.T, content string) { - internalIndicators := []string{ - "func internalHelper", - "func anotherHelper", +func testGeneratedFileBasicStructure(t *testing.T, content, expectedPackage, baseName string) { + requiredElements := []string{ + "package " + expectedPackage, + "// #include ", + `// #include "` + baseName + `.h"`, + `import "C"`, + "func init() {", + "frankenphp.RegisterExtension(", + "}", } - foundInternal := false - for _, indicator := range internalIndicators { - if strings.Contains(content, indicator) { - foundInternal = true + for _, element := range requiredElements { + assert.Contains(t, content, element, "Generated file should contain: %s", element) + } - break + assert.NotContains(t, content, "func internalHelper", "Generated file should not contain internal functions from source") + assert.NotContains(t, content, "func anotherHelper", "Generated file should not contain internal functions from source") +} + +func testGeneratedFileWrappers(t *testing.T, content string, functions []phpFunction) { + for _, fn := range functions { + exportDirective := "//export go_" + fn.Name + assert.Contains(t, content, exportDirective, "Generated file should contain export directive: %s", exportDirective) + + wrapperFunc := "func go_" + fn.Name + "(" + assert.Contains(t, content, wrapperFunc, "Generated file should contain wrapper function: %s", wrapperFunc) + + funcName := extractGoFunctionName(fn.GoFunction) + if funcName != "" { + assert.Contains(t, content, funcName+"(", "Generated wrapper should call original function: %s", funcName) } } +} - if !foundInternal { - t.Log("No internal functions found (this may be expected)") - } +// computeFileHash returns SHA256 hash of file +func computeFileHash(t *testing.T, filename string) string { + content, err := os.ReadFile(filename) + require.NoError(t, err) + hash := sha256.Sum256(content) + return hex.EncodeToString(hash[:]) +} + +// assertContainsHeaderComment verifies file has autogenerated header +func assertContainsHeaderComment(t *testing.T, filename string) { + content, err := os.ReadFile(filename) + require.NoError(t, err) + + headerSection := string(content[:min(len(content), 500)]) + assert.Contains(t, headerSection, "AUTOGENERATED FILE - DO NOT EDIT", "File should contain autogenerated header comment") + assert.Contains(t, headerSection, "FrankenPHP extension generator", "File should mention FrankenPHP extension generator") } diff --git a/internal/extgen/phpfunc.go b/internal/extgen/phpfunc.go index 13cad8206b..1e11172a20 100644 --- a/internal/extgen/phpfunc.go +++ b/internal/extgen/phpfunc.go @@ -37,24 +37,25 @@ func (pfg *PHPFuncGenerator) generate(fn phpFunction) string { func (pfg *PHPFuncGenerator) generateGoCall(fn phpFunction) string { callParams := pfg.paramParser.generateGoCallParams(fn.Params) + goFuncName := "go_" + fn.Name if fn.ReturnType == phpVoid { - return fmt.Sprintf(" %s(%s);", fn.Name, callParams) + return fmt.Sprintf(" %s(%s);", goFuncName, callParams) } if fn.ReturnType == phpString { - return fmt.Sprintf(" zend_string *result = %s(%s);", fn.Name, callParams) + return fmt.Sprintf(" zend_string *result = %s(%s);", goFuncName, callParams) } if fn.ReturnType == phpArray { - return fmt.Sprintf(" zend_array *result = %s(%s);", fn.Name, callParams) + return fmt.Sprintf(" zend_array *result = %s(%s);", goFuncName, callParams) } if fn.ReturnType == phpMixed { - return fmt.Sprintf(" zval *result = %s(%s);", fn.Name, callParams) + return fmt.Sprintf(" zval *result = %s(%s);", goFuncName, callParams) } - return fmt.Sprintf(" %s result = %s(%s);", pfg.getCReturnType(fn.ReturnType), fn.Name, callParams) + return fmt.Sprintf(" %s result = %s(%s);", pfg.getCReturnType(fn.ReturnType), goFuncName, callParams) } func (pfg *PHPFuncGenerator) getCReturnType(returnType phpType) string { diff --git a/internal/extgen/phpfunc_test.go b/internal/extgen/phpfunc_test.go index 9725dc6bdc..3a0365ccd5 100644 --- a/internal/extgen/phpfunc_test.go +++ b/internal/extgen/phpfunc_test.go @@ -26,7 +26,7 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) { "PHP_FUNCTION(greet)", "zend_string *name = NULL;", "Z_PARAM_STR(name)", - "zend_string *result = greet(name);", + "zend_string *result = go_greet(name);", "RETURN_STR(result)", }, }, @@ -61,7 +61,7 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) { }, contains: []string{ "PHP_FUNCTION(doSomething)", - "doSomething(action);", + "go_doSomething(action);", }, }, { @@ -109,7 +109,7 @@ func TestPHPFunctionGenerator_Generate(t *testing.T) { "PHP_FUNCTION(process_array)", "zend_array *input = NULL;", "Z_PARAM_ARRAY_HT(input)", - "zend_array *result = process_array(input);", + "zend_array *result = go_process_array(input);", "RETURN_ARR(result)", }, }, diff --git a/internal/extgen/srcanalyzer.go b/internal/extgen/srcanalyzer.go index a7f4b1cf4e..32ebff4040 100644 --- a/internal/extgen/srcanalyzer.go +++ b/internal/extgen/srcanalyzer.go @@ -10,33 +10,24 @@ import ( type SourceAnalyzer struct{} -func (sa *SourceAnalyzer) analyze(filename string) (imports []string, variables []string, internalFunctions []string, err error) { +func (sa *SourceAnalyzer) analyze(filename string) (packageName string, variables []string, internalFunctions []string, err error) { fset := token.NewFileSet() node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { - return nil, nil, nil, fmt.Errorf("parsing file: %w", err) + return "", nil, nil, fmt.Errorf("parsing file: %w", err) } - for _, imp := range node.Imports { - if imp.Path != nil { - importPath := imp.Path.Value - if imp.Name != nil { - imports = append(imports, fmt.Sprintf("%s %s", imp.Name.Name, importPath)) - } else { - imports = append(imports, importPath) - } - } - } + packageName = node.Name.Name sourceContent, err := os.ReadFile(filename) if err != nil { - return nil, nil, nil, fmt.Errorf("reading source file: %w", err) + return "", nil, nil, fmt.Errorf("reading source file: %w", err) } variables = sa.extractVariables(string(sourceContent)) internalFunctions = sa.extractInternalFunctions(string(sourceContent)) - return imports, variables, internalFunctions, nil + return packageName, variables, internalFunctions, nil } func (sa *SourceAnalyzer) extractVariables(content string) []string { diff --git a/internal/extgen/srcanalyzer_test.go b/internal/extgen/srcanalyzer_test.go index 717f99b5f9..74207b1b7c 100644 --- a/internal/extgen/srcanalyzer_test.go +++ b/internal/extgen/srcanalyzer_test.go @@ -227,7 +227,7 @@ func testFunction() { require.NoError(t, os.WriteFile(filename, []byte(tt.sourceContent), 0644)) analyzer := &SourceAnalyzer{} - imports, variables, functions, err := analyzer.analyze(filename) + _, variables, functions, err := analyzer.analyze(filename) if tt.expectError { assert.Error(t, err, "expected error") @@ -236,10 +236,6 @@ func testFunction() { assert.NoError(t, err, "unexpected error") - if len(imports) != 0 && len(tt.expectedImports) != 0 { - assert.Equal(t, tt.expectedImports, imports, "imports mismatch") - } - assert.Equal(t, tt.expectedVariables, variables, "variables mismatch") assert.Len(t, functions, len(tt.expectedFunctions), "function count mismatch") @@ -385,6 +381,110 @@ var x = 10`, } } +func TestSourceAnalyzer_InternalFunctionPreservation(t *testing.T) { + tmpDir := t.TempDir() + + sourceContent := `package main + +import ( + "fmt" + "strings" +) + +//export_php: exported1(): string +func exported1() *go_value { + return String(internal1()) +} + +func internal1() string { + return "helper1" +} + +//export_php: exported2(): void +func exported2() { + internal2() +} + +func internal2() { + fmt.Println("helper2") +} + +func internal3(data string) string { + return strings.ToUpper(data) +}` + + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644)) + + analyzer := &SourceAnalyzer{} + packageName, variables, internalFuncs, err := analyzer.analyze(sourceFile) + require.NoError(t, err) + + assert.Equal(t, "main", packageName) + + assert.Len(t, internalFuncs, 3, "Should extract exactly 3 internal functions") + + expectedInternalFuncs := []string{ + `func internal1() string { + return "helper1" +}`, + `func internal2() { + fmt.Println("helper2") +}`, + `func internal3(data string) string { + return strings.ToUpper(data) +}`, + } + + for i, expected := range expectedInternalFuncs { + assert.Equal(t, expected, internalFuncs[i], "Internal function %d should match", i) + } + + assert.Empty(t, variables, "Should not have variables") +} + +func TestSourceAnalyzer_VariableBlockPreservation(t *testing.T) { + tmpDir := t.TempDir() + + sourceContent := `package main + +import ( + "sync" +) + +var ( + mu sync.RWMutex + cache = make(map[string]string) +) + +var globalCounter int = 0 + +//export_php: test(): void +func test() {}` + + sourceFile := filepath.Join(tmpDir, "test.go") + require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644)) + + analyzer := &SourceAnalyzer{} + packageName, variables, internalFuncs, err := analyzer.analyze(sourceFile) + require.NoError(t, err) + + assert.Equal(t, "main", packageName) + + assert.Len(t, variables, 2, "Should extract exactly 2 variable declarations") + + expectedVar1 := `var ( + mu sync.RWMutex + cache = make(map[string]string) +)` + expectedVar2 := `var globalCounter int = 0` + + assert.Equal(t, expectedVar1, variables[0], "First variable block should match") + assert.Equal(t, expectedVar2, variables[1], "Second variable declaration should match") + + assert.Empty(t, internalFuncs, "Should not have internal functions (only exported function)") +} + func BenchmarkSourceAnalyzer_Analyze(b *testing.B) { content := `package main diff --git a/internal/extgen/templates/extension.c.tpl b/internal/extgen/templates/extension.c.tpl index f511a1baf0..87b67475bb 100644 --- a/internal/extgen/templates/extension.c.tpl +++ b/internal/extgen/templates/extension.c.tpl @@ -1,3 +1,12 @@ +// AUTOGENERATED FILE - DO NOT EDIT. +// +// This file has been automatically generated by FrankenPHP extension generator +// and should not be edited as it will be overwritten when running the +// extension generator again. +// +// You may edit the file and remove this comment if you plan to manually maintain +// this file going forward. + #include #include #include diff --git a/internal/extgen/templates/extension.go.tpl b/internal/extgen/templates/extension.go.tpl index 24b665700e..313ec46f3e 100644 --- a/internal/extgen/templates/extension.go.tpl +++ b/internal/extgen/templates/extension.go.tpl @@ -1,43 +1,32 @@ package {{.PackageName}} +// AUTOGENERATED FILE - DO NOT EDIT. +// +// This file has been automatically generated by FrankenPHP extension generator +// and should not be edited as it will be overwritten when running the +// extension generator again. +// +// You may edit the file and remove this comment if you plan to manually maintain +// this file going forward. + // #include // #include "{{.BaseName}}.h" import "C" import ( + {{if not .Classes}}_ {{end}}"runtime/cgo" "unsafe" "github.com/dunglas/frankenphp" -{{- range .Imports}} - {{.}} -{{- end}} ) func init() { - frankenphp.RegisterExtension(unsafe.Pointer(&C.{{.BaseName}}_module_entry)) + frankenphp.RegisterExtension(unsafe.Pointer(&C.{{.SanitizedBaseName}}_module_entry)) } -{{ range .Constants}} -const {{.Name}} = {{.Value}} - -{{- end}} -{{- range .Variables}} - -{{.}} -{{- end}} -{{- range .InternalFunctions}} -{{.}} - -{{- end}} {{- range .Functions}} -//export {{.Name}} -{{.GoFunction}} - -{{- end}} -{{- range .Classes}} -type {{.GoStruct}} struct { -{{- range .Properties}} - {{.Name}} {{.GoType}} -{{- end}} +//export go_{{.Name}} +func go_{{.Name}}({{extractGoFunctionSignatureParams .GoFunction}}) {{extractGoFunctionSignatureReturn .GoFunction}} { + {{if not (isVoid .ReturnType)}}return {{end}}{{extractGoFunctionName .GoFunction}}({{extractGoFunctionCallParams .GoFunction}}) } {{- end}} @@ -68,12 +57,6 @@ func create_{{.GoStruct}}_object() C.uintptr_t { return registerGoObject(obj) } -{{- range .Methods}} -{{- if .GoFunction}} -{{.GoFunction}} -{{- end}} - -{{- end}} {{- range .Methods}} //export {{.Name}}_wrapper func {{.Name}}_wrapper(handle C.uintptr_t{{range .Params}}{{if eq .PhpType "string"}}, {{.Name}} *C.zend_string{{else if eq .PhpType "array"}}, {{.Name}} *C.zval{{else if eq .PhpType "callable"}}, {{.Name}} *C.zval{{else}}, {{.Name}} {{if .IsNullable}}*{{end}}{{phpTypeToGoType .PhpType}}{{end}}{{end}}){{if not (isVoid .ReturnType)}}{{if isStringOrArray .ReturnType}} unsafe.Pointer{{else}} {{phpTypeToGoType .ReturnType}}{{end}}{{end}} { diff --git a/internal/extgen/templates/extension.h.tpl b/internal/extgen/templates/extension.h.tpl index 2607185222..eca5b92508 100644 --- a/internal/extgen/templates/extension.h.tpl +++ b/internal/extgen/templates/extension.h.tpl @@ -1,3 +1,12 @@ +// AUTOGENERATED FILE - DO NOT EDIT. +// +// This file has been automatically generated by FrankenPHP extension generator +// and should not be edited as it will be overwritten when running the +// extension generator again. +// +// You may edit the file and remove this comment if you plan to manually maintain +// this file going forward. + #ifndef _{{.HeaderGuard}} #define _{{.HeaderGuard}} diff --git a/internal/extgen/templates/stub.php.tpl b/internal/extgen/templates/stub.php.tpl index cd16115d99..0fe0637c19 100644 --- a/internal/extgen/templates/stub.php.tpl +++ b/internal/extgen/templates/stub.php.tpl @@ -1,6 +1,15 @@ Date: Fri, 6 Feb 2026 11:11:58 +0100 Subject: [PATCH 32/59] Add apk repository, update debian repository instructions (#2099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/php/frankenphp/pull/1753 closes https://github.com/php/frankenphp/issues/2156 As per discussion here https://github.com/php/frankenphp/discussions/2060#discussioncomment-15299936 I went ahead with different repos for different php versions. Versioned support with stuff like `apt install frankenphp8.5` or `apk add frankenphp85` are technically also ready, but I'm not running any CI for that yet. I don't think it's worth it at this point as it would double the amount of runs. The old debian repository with only 8.4 is deprecated but will receive updates for a few more months. Every update/installation will print this notice, though, which will hopefully make everyone aware: ```console # running update from 8.4.15... Unpacking php-zts-cli (8.4.16-1) ... Setting up php-zts-cli (8.4.16-1) ... ================================================================================ ⚠️ DEPRECATION NOTICE ================================================================================ The single-version php-zts repository is deprecated and will no longer receive updates. Please migrate to the new repository with different PHP versions available. More information: https://pkgs.henderkes.com ================================================================================ ``` Updated the installer to version 8.5. --- README.md | 27 ++++++++++++-- docs/performance.md | 6 ++-- install.sh | 58 ++++++++++++++++++++++--------- package/alpine/frankenphp.openrc | 29 ++++++++++++++++ package/alpine/post-deinstall.sh | 13 +++++++ package/alpine/post-install.sh | 43 +++++++++++++++++++++++ package/alpine/pre-deinstall.sh | 11 ++++++ package/debian/frankenphp.service | 7 ++-- package/debian/postinst.sh | 24 +++++++------ package/rhel/frankenphp.service | 6 ++-- package/rhel/postinstall.sh | 24 +++++++++---- 11 files changed, 206 insertions(+), 42 deletions(-) create mode 100755 package/alpine/frankenphp.openrc create mode 100755 package/alpine/post-deinstall.sh create mode 100755 package/alpine/post-install.sh create mode 100755 package/alpine/pre-deinstall.sh diff --git a/README.md b/README.md index 8586a02707..4668b076f5 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ sudo pie-zts install asgrim/example-pie-extension Our maintainers offer deb packages for all systems using `apt`. To install, run: ```console -sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \ -echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \ +VERSION=85 # 82-85 available +sudo curl https://pkg.henderkes.com/api/packages/${VERSION}/debian/repository.key -o /etc/apt/keyrings/static-php${VERSION}.asc +echo "deb [signed-by=/etc/apt/keyrings/static-php${VERSION}.asc] https://pkg.henderkes.com/api/packages/${VERSION}/debian php-zts main" | sudo tee -a /etc/apt/sources.list.d/static-php${VERSION}.list sudo apt update sudo apt install frankenphp ``` @@ -75,6 +76,28 @@ sudo apt install pie-zts sudo pie-zts install asgrim/example-pie-extension ``` +### apk Packages + +Our maintainers offer apk packages for all systems using `apk`. To install, run: + +```console +VERSION=85 # 82-85 available +echo "https://pkg.henderkes.com/api/packages/${VERSION}/alpine/main/php-zts" | sudo tee -a /etc/apk/repositories +KEYFILE=$(curl -sJOw '%{filename_effective}' https://pkg.henderkes.com/api/packages/${VERSION}/alpine/key) +sudo mv ${KEYFILE} /etc/apk/keys/ && +sudo apk update && +sudo apk add frankenphp +``` + +**Installing extensions:** `sudo apk add php-zts-` + +For extensions not available by default, use [PIE](https://github.com/php/pie): + +```console +sudo apk add pie-zts +sudo pie-zts install asgrim/example-pie-extension +``` + ### Homebrew FrankenPHP is also available as a [Homebrew](https://brew.sh) package for macOS and Linux. diff --git a/docs/performance.md b/docs/performance.md index 7910b26513..b2caab6c1e 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -7,7 +7,7 @@ However, it is possible to substantially improve performance using an appropriat By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available numbers of CPU. -The appropriate values depend heavily on how your application is written, what it does and your hardware. +The appropriate values depend heavily on how your application is written, what it does, and your hardware. We strongly recommend changing these values. For best system stability, it is recommended to have `num_threads` x `memory_limit` < `available_memory`. To find the right values, it's best to run load tests simulating real traffic. @@ -43,7 +43,7 @@ Also, [some bugs only happen when using musl](https://github.com/php/php-src/iss In production environments, we recommend using FrankenPHP linked against glibc, compiled with an appropriate optimization level. -This can be achieved by using the Debian Docker images, using our maintainers [.deb](https://debs.henderkes.com) or [.rpm](https://rpms.henderkes.com) packages, or by [compiling FrankenPHP from sources](compile.md). +This can be achieved by using the Debian Docker images, using [our maintainers .deb, .rpm, or .apk packages](https://pkgs.henderkes.com), or by [compiling FrankenPHP from sources](compile.md). ## Go Runtime Configuration @@ -146,7 +146,7 @@ All usual PHP-related performance optimizations apply with FrankenPHP. In particular: -- check that [OPcache](https://www.php.net/manual/en/book.opcache.php) is installed, enabled and properly configured +- check that [OPcache](https://www.php.net/manual/en/book.opcache.php) is installed, enabled, and properly configured - enable [Composer autoloader optimizations](https://getcomposer.org/doc/articles/autoloader-optimization.md) - ensure that the `realpath` cache is big enough for the needs of your application - use [preloading](https://www.php.net/manual/en/opcache.preloading.php) diff --git a/install.sh b/install.sh index eea9fe2251..0213ab6c81 100755 --- a/install.sh +++ b/install.sh @@ -3,9 +3,6 @@ set -e SUDO="" -if [ "$(id -u)" -ne 0 ]; then - SUDO="sudo" -fi if [ -z "${BIN_DIR}" ]; then BIN_DIR=$(pwd) @@ -34,12 +31,13 @@ Linux*) if [ "${ARCH}" = "aarch64" ] || [ "${ARCH}" = "x86_64" ]; then if command -v dnf >/dev/null 2>&1; then echo "📦 Detected dnf. Installing FrankenPHP from RPM repository..." - if [ -n "${SUDO}" ]; then + if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" echo "❗ Enter your password to grant sudo powers for package installation" ${SUDO} -v || true fi - ${SUDO} dnf -y install https://rpm.henderkes.com/static-php-1-0.noarch.rpm - ${SUDO} dnf -y module enable php-zts:static-8.4 || true + ${SUDO} dnf -y install https://rpm.henderkes.com/static-php-1-1.noarch.rpm + ${SUDO} dnf -y module enable php-zts:static-8.5 || true ${SUDO} dnf -y install frankenphp echo echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully." @@ -50,24 +48,50 @@ Linux*) exit 0 fi - if command -v apt >/dev/null 2>&1 || command -v apt-get >/dev/null 2>&1; then - echo "📦 Detected apt. Installing FrankenPHP from DEB repository..." - if [ -n "${SUDO}" ]; then + if command -v apt-get >/dev/null 2>&1; then + echo "📦 Detected apt-get. Installing FrankenPHP from DEB repository..." + if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" + echo "❗ Enter your password to grant sudo powers for package installation" + ${SUDO} -v || true + fi + ${SUDO} sh -c 'curl -fsSL https://pkg.henderkes.com/api/packages/85/debian/repository.key -o /etc/apt/keyrings/static-php85.asc' + ${SUDO} sh -c 'echo "deb [signed-by=/etc/apt/keyrings/static-php85.asc] https://pkg.henderkes.com/api/packages/85/debian php-zts main" | sudo tee -a /etc/apt/sources.list.d/static-php85.list' + ${SUDO} apt-get update + ${SUDO} apt-get -y install frankenphp + echo + echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully." + echo "❗ The systemd service uses the Caddyfile in ${italic}/etc/frankenphp/Caddyfile${normal}" + echo "❗ Your php.ini is found in ${italic}/etc/php-zts/php.ini${normal}" + echo + echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}" + exit 0 + fi + + if command -v apk >/dev/null 2>&1; then + echo "📦 Detected apk. Installing FrankenPHP from APK repository..." + if [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" echo "❗ Enter your password to grant sudo powers for package installation" ${SUDO} -v || true fi - ${SUDO} sh -c 'curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg' - ${SUDO} sh -c 'echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" > /etc/apt/sources.list.d/static-php.list' - if command -v apt >/dev/null 2>&1; then - ${SUDO} apt update - ${SUDO} apt -y install frankenphp + + KEY_URL="https://pkg.henderkes.com/api/packages/85/alpine/key" + ${SUDO} sh -c "cd /etc/apk/keys && curl -JOsS \"$KEY_URL\" 2>/dev/null || true" + + REPO_URL="https://pkg.henderkes.com/api/packages/85/alpine/main/php-zts" + if grep -q "$REPO_URL" /etc/apk/repositories 2>/dev/null; then + echo "Repository already exists in /etc/apk/repositories" else - ${SUDO} apt-get update - ${SUDO} apt-get -y install frankenphp + ${SUDO} sh -c "echo \"$REPO_URL\" >> /etc/apk/repositories" + ${SUDO} apk update + echo "Repository added to /etc/apk/repositories" fi + + ${SUDO} apk add frankenphp echo echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully." - echo "❗ The systemd service uses the Caddyfile in ${italic}/etc/frankenphp/Caddyfile${normal}" + echo "❗ The OpenRC service uses the Caddyfile in ${italic}/etc/frankenphp/Caddyfile${normal}" echo "❗ Your php.ini is found in ${italic}/etc/php-zts/php.ini${normal}" echo echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}" diff --git a/package/alpine/frankenphp.openrc b/package/alpine/frankenphp.openrc new file mode 100755 index 0000000000..aeca1743cf --- /dev/null +++ b/package/alpine/frankenphp.openrc @@ -0,0 +1,29 @@ +#!/sbin/openrc-run + +name="FrankenPHP" +description="The modern PHP app server" + +command="/usr/bin/frankenphp" +command_args="run --environ --config /etc/frankenphp/Caddyfile" +command_user="frankenphp:frankenphp" +command_background="yes" +capabilities="^cap_net_bind_service" +pidfile="/run/frankenphp/frankenphp.pid" +start_stop_daemon_args="--chdir /var/lib/frankenphp" + +depend() { + need net + after firewall +} + +start_pre() { + checkpath --directory --owner frankenphp:frankenphp --mode 0755 /run/frankenphp + + $command validate --config /etc/frankenphp/Caddyfile +} + +reload() { + ebegin "Reloading $name configuration" + $command reload --config /etc/frankenphp/Caddyfile --force + eend $? +} diff --git a/package/alpine/post-deinstall.sh b/package/alpine/post-deinstall.sh new file mode 100755 index 0000000000..fd102fc3fa --- /dev/null +++ b/package/alpine/post-deinstall.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +if getent passwd frankenphp >/dev/null; then + deluser frankenphp +fi + +if getent group frankenphp >/dev/null; then + delgroup frankenphp +fi + +rmdir /var/lib/frankenphp 2>/dev/null || true + +exit 0 diff --git a/package/alpine/post-install.sh b/package/alpine/post-install.sh new file mode 100755 index 0000000000..03bd879209 --- /dev/null +++ b/package/alpine/post-install.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +if ! getent group frankenphp >/dev/null; then + addgroup -S frankenphp +fi + +if ! getent passwd frankenphp >/dev/null; then + adduser -S -h /var/lib/frankenphp -s /sbin/nologin -G frankenphp -g "FrankenPHP web server" frankenphp +fi + +chown -R frankenphp:frankenphp /var/lib/frankenphp +chmod 755 /var/lib/frankenphp + +# allow binding to privileged ports +if command -v setcap >/dev/null 2>&1; then + setcap cap_net_bind_service=+ep /usr/bin/frankenphp || true +fi + +# check if 0.0.0.0:2019 or 127.0.0.1:2019 are in use +port_in_use() { + port_hex=$(printf '%04X' "$1") + grep -qE "(00000000|0100007F):${port_hex}" /proc/net/tcp 2>/dev/null +} + +# trust frankenphp certificates if the admin api can start +if [ -x /usr/bin/frankenphp ]; then + if ! port_in_use 2019; then + HOME=/var/lib/frankenphp /usr/bin/frankenphp run >/dev/null 2>&1 & + FRANKENPHP_PID=$! + sleep 2 + HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || true + kill -TERM $FRANKENPHP_PID 2>/dev/null || true + + chown -R frankenphp:frankenphp /var/lib/frankenphp + fi +fi + +if command -v rc-update >/dev/null 2>&1; then + rc-update add frankenphp default + rc-service frankenphp start +fi + +exit 0 diff --git a/package/alpine/pre-deinstall.sh b/package/alpine/pre-deinstall.sh new file mode 100755 index 0000000000..59713ea01c --- /dev/null +++ b/package/alpine/pre-deinstall.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +if command -v rc-service >/dev/null 2>&1; then + rc-service frankenphp stop || true +fi + +if command -v rc-update >/dev/null 2>&1; then + rc-update del frankenphp default || true +fi + +exit 0 diff --git a/package/debian/frankenphp.service b/package/debian/frankenphp.service index ead2f7da6f..0b2dfe4a0f 100644 --- a/package/debian/frankenphp.service +++ b/package/debian/frankenphp.service @@ -1,5 +1,5 @@ [Unit] -Description=FrankenPHP +Description=FrankenPHP - The modern PHP app server Documentation=https://frankenphp.dev/docs/ After=network.target network-online.target Requires=network-online.target @@ -8,12 +8,15 @@ Requires=network-online.target Type=notify User=frankenphp Group=frankenphp +ExecStartPre=/usr/bin/frankenphp validate --config /etc/frankenphp/Caddyfile ExecStart=/usr/bin/frankenphp run --environ --config /etc/frankenphp/Caddyfile -ExecReload=/usr/bin/frankenphp reload --config /etc/frankenphp/Caddyfile --force +ExecReload=/usr/bin/frankenphp reload --config /etc/frankenphp/Caddyfile +WorkingDirectory=/var/lib/frankenphp TimeoutStopSec=5s LimitNOFILE=1048576 LimitNPROC=512 PrivateTmp=true +ProtectHome=true ProtectSystem=full AmbientCapabilities=CAP_NET_BIND_SERVICE diff --git a/package/debian/postinst.sh b/package/debian/postinst.sh index 97c6f26482..e6c7a154a6 100755 --- a/package/debian/postinst.sh +++ b/package/debian/postinst.sh @@ -19,17 +19,29 @@ if [ "$1" = "configure" ]; then usermod -aG www-data frankenphp fi + # trust frankenphp certificates before starting the systemd service + if [ -z "$2" ] && [ -x /usr/bin/frankenphp ]; then + HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null & + FRANKENPHP_PID=$! + sleep 2 + HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || true + kill "$FRANKENPHP_PID" || true + wait "$FRANKENPHP_PID" 2>/dev/null || true + + chown -R frankenphp:frankenphp /var/lib/frankenphp + fi + # Handle cases where package was installed and then purged; # user and group will still exist but with no home dir if [ ! -d /var/lib/frankenphp ]; then mkdir -p /var/lib/frankenphp - chown frankenphp:frankenphp /var/lib/frankenphp + chown -R frankenphp:frankenphp /var/lib/frankenphp fi # Add log directory with correct permissions if [ ! -d /var/log/frankenphp ]; then mkdir -p /var/log/frankenphp - chown frankenphp:frankenphp /var/log/frankenphp + chown -R frankenphp:frankenphp /var/log/frankenphp fi fi @@ -61,11 +73,3 @@ fi if command -v setcap >/dev/null 2>&1; then setcap cap_net_bind_service=+ep /usr/bin/frankenphp || true fi - -if [ -x /usr/bin/frankenphp ]; then - HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null & - FRANKENPHP_PID=$! - HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || true - kill "$FRANKENPHP_PID" || true - wait "$FRANKENPHP_PID" 2>/dev/null || true -fi diff --git a/package/rhel/frankenphp.service b/package/rhel/frankenphp.service index 40311d6a89..0b2dfe4a0f 100644 --- a/package/rhel/frankenphp.service +++ b/package/rhel/frankenphp.service @@ -1,6 +1,8 @@ [Unit] -Description=FrankenPHP server -After=network.target +Description=FrankenPHP - The modern PHP app server +Documentation=https://frankenphp.dev/docs/ +After=network.target network-online.target +Requires=network-online.target [Service] Type=notify diff --git a/package/rhel/postinstall.sh b/package/rhel/postinstall.sh index 5905e29599..551523a8fd 100755 --- a/package/rhel/postinstall.sh +++ b/package/rhel/postinstall.sh @@ -32,10 +32,22 @@ if command -v setcap >/dev/null 2>&1; then setcap cap_net_bind_service=+ep /usr/bin/frankenphp || : fi -if [ -x /usr/bin/frankenphp ]; then - HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null & - FRANKENPHP_PID=$! - HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || : - kill "$FRANKENPHP_PID" || : - wait "$FRANKENPHP_PID" 2>/dev/null || : +# check if 0.0.0.0:2019 or 127.0.0.1:2019 are in use +port_in_use() { + port_hex=$(printf '%04X' "$1") + grep -qE "(00000000|0100007F):${port_hex}" /proc/net/tcp 2>/dev/null +} + +# trust frankenphp certificates if the admin api can start +if [ "$1" -eq 1 ] && [ -x /usr/bin/frankenphp ]; then + if ! port_in_use 2019; then + HOME=/var/lib/frankenphp /usr/bin/frankenphp run --config /dev/null & + FRANKENPHP_PID=$! + sleep 2 + HOME=/var/lib/frankenphp /usr/bin/frankenphp trust || : + kill "$FRANKENPHP_PID" || : + wait "$FRANKENPHP_PID" 2>/dev/null || : + + chown -R frankenphp:frankenphp /var/lib/frankenphp + fi fi From bcbd22a69850ff409bfa382f571488def893304e Mon Sep 17 00:00:00 2001 From: Marc Date: Sat, 7 Feb 2026 20:08:58 +0100 Subject: [PATCH 33/59] attempt to fix create-pr action from translation CI (#2169) https://github.com/php/frankenphp/actions/runs/21746819419 Signed-off-by: Marc --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 21bd3b59b4..f8fb0452fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /internal/testserver/testserver /internal/testcli/testcli /dist +/github_conf +/super-linter-output .DS_Store .idea/ .vscode/ From 46fa426c0d8659c5db7e7ec18ef4428a1c3f03a1 Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:09:13 +0000 Subject: [PATCH 34/59] fix: translations (#2171) Fixes translations in case of trailing slashes in the input see https://github.com/php/frankenphp/actions/runs/21746819419 --- docs/translate.php | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/translate.php b/docs/translate.php index d491b58e8c..d4575c7cd6 100644 --- a/docs/translate.php +++ b/docs/translate.php @@ -99,6 +99,7 @@ function sanitizeMarkdown(string $markdown): string $fileToTranslate = $argv; array_shift($fileToTranslate); +$fileToTranslate = array_map(fn($filename) => trim($filename), $fileToTranslate); $apiKey = $_SERVER['GEMINI_API_KEY'] ?? $_ENV['GEMINI_API_KEY'] ?? ''; if (!$apiKey) { echo 'Enter gemini api key ($GEMINI_API_KEY): '; From c30fef09d3db7209d2fe15dfa51c8ac54d86578c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 9 Feb 2026 13:51:55 +0100 Subject: [PATCH 35/59] fix: ensure $SERVER["PHP_SELF"] always starts with a slash (#2172) Closes #2166. --- cgi.go | 11 ++++++++++- cgi_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 cgi_test.go diff --git a/cgi.go b/cgi.go index 9804c969df..c79a416020 100644 --- a/cgi.go +++ b/cgi.go @@ -145,7 +145,7 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { packCgiVariable(keys["REMOTE_PORT"], port), packCgiVariable(keys["DOCUMENT_ROOT"], fc.documentRoot), packCgiVariable(keys["PATH_INFO"], fc.pathInfo), - packCgiVariable(keys["PHP_SELF"], request.URL.Path), + packCgiVariable(keys["PHP_SELF"], ensureLeadingSlash(request.URL.Path)), packCgiVariable(keys["DOCUMENT_URI"], fc.docURI), packCgiVariable(keys["SCRIPT_FILENAME"], fc.scriptFilename), packCgiVariable(keys["SCRIPT_NAME"], fc.scriptName), @@ -340,7 +340,16 @@ func sanitizedPathJoin(root, reqPath string) string { const separator = string(filepath.Separator) +func ensureLeadingSlash(path string) string { + if path == "" || path[0] == '/' { + return path + } + + return "/" + path +} + func toUnsafeChar(s string) *C.char { sData := unsafe.StringData(s) + return (*C.char)(unsafe.Pointer(sData)) } diff --git a/cgi_test.go b/cgi_test.go new file mode 100644 index 0000000000..a328863308 --- /dev/null +++ b/cgi_test.go @@ -0,0 +1,33 @@ +package frankenphp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnsureLeadingSlash(t *testing.T) { + t.Parallel() + + tests := []struct { + input string + expected string + }{ + {"/index.php", "/index.php"}, + {"index.php", "/index.php"}, + {"/", "/"}, + {"", ""}, + {"/path/to/script.php", "/path/to/script.php"}, + {"path/to/script.php", "/path/to/script.php"}, + {"/index.php/path/info", "/index.php/path/info"}, + {"index.php/path/info", "/index.php/path/info"}, + } + + for _, tt := range tests { + t.Run(tt.input + "-" + tt.expected, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.expected, ensureLeadingSlash(tt.input), "ensureLeadingSlash(%q)", tt.input) + }) + } +} From 6eef0d30aa10f7e3895b6d05f625b38515671132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 5 Feb 2026 14:45:58 +0100 Subject: [PATCH 36/59] chore: bump deps --- caddy/go.mod | 23 +++++++++++----------- caddy/go.sum | 55 +++++++++++++++++++++++++--------------------------- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/caddy/go.mod b/caddy/go.mod index 86ba713a31..8c6676ec1c 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -82,14 +82,14 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/brotli/go/cbrotli v1.1.0 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/google/cel-go v0.27.0 // indirect github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/go-tpm v0.9.8 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect - github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 // indirect @@ -150,7 +150,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747 // indirect @@ -168,18 +167,18 @@ require ( github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/aws v1.39.0 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.39.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 // indirect go.opentelemetry.io/contrib/propagators/ot v1.39.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.step.sm/crypto v0.76.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect @@ -200,11 +199,11 @@ require ( golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.41.0 // indirect - google.golang.org/api v0.263.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/api v0.265.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.78.0 // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/caddy/go.sum b/caddy/go.sum index 198b93a422..468e94ee46 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -211,8 +211,8 @@ github.com/google/brotli/go/cbrotli v1.1.0 h1:YwHD/rwSgUSL4b2S3ZM2jnNymm+tmwKQqj github.com/google/brotli/go/cbrotli v1.1.0/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= @@ -232,8 +232,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -401,8 +401,6 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -413,7 +411,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -467,8 +464,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 h1:VVrb1ErDD0Tlh/0K0rUqjky1e8AekjspTFN9sU2ekaA= go.opentelemetry.io/contrib/propagators/autoprop v0.64.0/go.mod h1:QCsOQk+9Ep8Mkp4/aPtSzUT0dc8SaPYzBAE6o1jYuSE= go.opentelemetry.io/contrib/propagators/aws v1.39.0 h1:IvNR8pAVGpkK1CHMjU/YE6B6TlnAPGFvogkMWRWU6wo= @@ -479,20 +476,20 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9 go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8= go.opentelemetry.io/contrib/propagators/ot v1.39.0 h1:vKTve1W/WKPVp1fzJamhCDDECt+5upJJ65bPyWoddGg= go.opentelemetry.io/contrib/propagators/ot v1.39.0/go.mod h1:FH5VB2N19duNzh1Q8ks6CsZFyu3LFhNLiA9lPxyEkvU= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.step.sm/crypto v0.76.0 h1:K23BSaeoiY7Y5dvvijTeYC9EduDBetNwQYMBwMhi1aA= @@ -607,18 +604,18 @@ golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.263.0 h1:UFs7qn8gInIdtk1ZA6eXRXp5JDAnS4x9VRsRVCeKdbk= -google.golang.org/api v0.263.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= -google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= -google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/api v0.265.0 h1:FZvfUdI8nfmuNrE34aOWFPmLC+qRBEiNm3JdivTvAAU= +google.golang.org/api v0.265.0/go.mod h1:uAvfEl3SLUj/7n6k+lJutcswVojHPp2Sp08jWCu8hLY= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= +google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.mod b/go.mod index dae3654927..8d93c08df8 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ retract v1.0.0-rc.1 // Human error require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/dunglas/mercure v0.21.7 - github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a + github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f github.com/maypok86/otter/v2 v2.3.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index db5f8ec818..896982e4ae 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/dunglas/mercure v0.21.7 h1:QtxQr9IMqJdlLxpsCjdBoDkJm4r4HXIEQCLl5obzI+ github.com/dunglas/mercure v0.21.7/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= -github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a h1:e/m9m8cJgjzw2Ol7tKTu4B/lM5F3Ym7ryKI+oyw0T8Y= -github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f h1:UDB5nhFRW7IOOpLk/eP1UGj7URmPimFGV+01/EG9qR8= +github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= From 711d03256eddfbf168d48c222909f23d1c5384b1 Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:12:15 +0100 Subject: [PATCH 37/59] docs: performance updates (#2173) Some updates to the performance docs. Mainly creating this PR to test the automatic translations. --- docs/performance.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/performance.md b/docs/performance.md index b2caab6c1e..5ca732d7f7 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -5,7 +5,7 @@ However, it is possible to substantially improve performance using an appropriat ## Number of Threads and Workers -By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available numbers of CPU. +By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available number of CPU cores. The appropriate values depend heavily on how your application is written, what it does, and your hardware. We strongly recommend changing these values. For best system stability, it is recommended to have `num_threads` x `memory_limit` < `available_memory`. @@ -45,6 +45,8 @@ In production environments, we recommend using FrankenPHP linked against glibc, This can be achieved by using the Debian Docker images, using [our maintainers .deb, .rpm, or .apk packages](https://pkgs.henderkes.com), or by [compiling FrankenPHP from sources](compile.md). +For leaner or more secure containers, you may want to consider [a hardened Debian image](docker.md#hardening-images) rather than Alpine. + ## Go Runtime Configuration FrankenPHP is written in Go. @@ -87,6 +89,18 @@ php_server { ``` This can significantly reduce the number of unnecessary file operations. +A worker equivalent of the previous configuration would be: + +```caddyfile +route { + php_server { # use "php" instead of "php_server" if you don't need the file server at all + root /root/to/your/app + worker /path/to/worker.php { + match * # send all requests directly to the worker + } + } +} +``` An alternate approach with 0 unnecessary file system operations would be to instead use the `php` directive and split files from PHP by path. This approach works well if your entire application is served by one entry file. @@ -164,22 +178,18 @@ limits the concurrency of requests going towards the slow endpoint, similar to a connection pool. ```caddyfile -{ - frankenphp { - max_threads 100 # max 100 threads shared by all workers - } -} - example.com { php_server { root /app/public # the root of your application worker index.php { match /slow-endpoint/* # all requests with path /slow-endpoint/* are handled by this thread pool - num 10 # minimum 10 threads for requests matching /slow-endpoint/* + num 1 # minimum 1 threads for requests matching /slow-endpoint/* + max_threads 20 # allow up to 20 threads for requests matching /slow-endpoint/*, if needed } worker index.php { match * # all other requests are handled separately - num 20 # minimum 20 threads for other requests, even if the slow endppoints start hanging + num 1 # minimum 1 threads for other requests, even if the slow endpoints start hanging + max_threads 20 # allow up to 20 threads for other requests, if needed } } } From 04fdc0c1e8fde94e2c1ad86217e962c88d27c53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 26 Jan 2026 17:21:34 +0100 Subject: [PATCH 38/59] fix: path confusion via unicode casing in CGI path splitting --- caddy/mercure.go | 3 +- caddy/module.go | 57 +++++-------- cgi.go | 59 ++++++++++++-- cgi_test.go | 177 ++++++++++++++++++++++++++++++++++++++++ request_options.go | 30 +++++++ request_options_test.go | 69 ++++++++++++++++ 6 files changed, 349 insertions(+), 46 deletions(-) create mode 100644 request_options_test.go diff --git a/caddy/mercure.go b/caddy/mercure.go index 081a6d3430..9624f50ea4 100644 --- a/caddy/mercure.go +++ b/caddy/mercure.go @@ -22,8 +22,7 @@ func (f *FrankenPHPModule) assignMercureHub(ctx caddy.Context) { return } - opt := frankenphp.WithMercureHub(f.mercureHub) - f.mercureHubRequestOption = &opt + f.requestOptions = append(f.requestOptions, frankenphp.WithMercureHub(f.mercureHub)) for i, wc := range f.Workers { wc.mercureHub = f.mercureHub diff --git a/caddy/module.go b/caddy/module.go index 6416362694..d1ab43a2e4 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -51,7 +51,7 @@ type FrankenPHPModule struct { preparedEnv frankenphp.PreparedEnv preparedEnvNeedsReplacement bool logger *slog.Logger - mercureHubRequestOption *frankenphp.RequestOption + requestOptions []frankenphp.RequestOption } // CaddyModule returns the Caddy module information. @@ -118,6 +118,7 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { if len(f.SplitPath) == 0 { f.SplitPath = []string{".php"} } + f.requestOptions = append(f.requestOptions, frankenphp.WithRequestSplitPath(f.SplitPath)) if f.ResolveRootSymlink == nil { rrs := true @@ -148,6 +149,8 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { f.Workers[i].FileName = resolvedPath } } + + f.requestOptions = append(f.requestOptions, frankenphp.WithRequestResolvedDocumentRoot(f.resolvedDocumentRoot)) } if f.preparedEnv == nil { @@ -162,6 +165,10 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { } } + if !f.preparedEnvNeedsReplacement { + f.requestOptions = append(f.requestOptions, frankenphp.WithRequestPreparedEnv(f.preparedEnv)) + } + if err := f.configureHotReload(fapp); err != nil { return err } @@ -180,31 +187,26 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c origReq := ctx.Value(caddyhttp.OriginalRequestCtxKey).(http.Request) repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - var ( - documentRootOption frankenphp.RequestOption - documentRoot string - ) - - if f.resolvedDocumentRoot == "" { + documentRoot := f.resolvedDocumentRoot + if documentRoot == "" { documentRoot = repl.ReplaceKnown(f.Root, "") if documentRoot == "" && frankenphp.EmbeddedAppPath != "" { documentRoot = frankenphp.EmbeddedAppPath } + // If we do not have a resolved document root, then we cannot resolve the symlink of our cwd because it may // resolve to a different directory than the one we are currently in. // This is especially important if there are workers running. - documentRootOption = frankenphp.WithRequestDocumentRoot(documentRoot, false) - } else { - documentRoot = f.resolvedDocumentRoot - documentRootOption = frankenphp.WithRequestResolvedDocumentRoot(documentRoot) + f.requestOptions = append(f.requestOptions, frankenphp.WithRequestDocumentRoot(documentRoot, false)) } - env := f.preparedEnv if f.preparedEnvNeedsReplacement { - env = make(frankenphp.PreparedEnv, len(f.Env)) + env := make(frankenphp.PreparedEnv, len(f.Env)) for k, v := range f.preparedEnv { env[k] = repl.ReplaceKnown(v, "") } + + f.requestOptions = append(f.requestOptions, frankenphp.WithRequestPreparedEnv(env)) } workerName := "" @@ -215,31 +217,14 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c } } - var ( - err error - fr *http.Request - ) - - if f.mercureHubRequestOption == nil { - fr, err = frankenphp.NewRequestWithContext( - r, - documentRootOption, - frankenphp.WithRequestSplitPath(f.SplitPath), - frankenphp.WithRequestPreparedEnv(env), - frankenphp.WithOriginalRequest(&origReq), - frankenphp.WithWorkerName(workerName), - ) - } else { - fr, err = frankenphp.NewRequestWithContext( - r, - documentRootOption, - frankenphp.WithRequestSplitPath(f.SplitPath), - frankenphp.WithRequestPreparedEnv(env), + fr, err := frankenphp.NewRequestWithContext( + r, + append( + f.requestOptions, frankenphp.WithOriginalRequest(&origReq), frankenphp.WithWorkerName(workerName), - *f.mercureHubRequestOption, - ) - } + )..., + ) if err != nil { return caddyhttp.Error(http.StatusInternalServerError, err) diff --git a/cgi.go b/cgi.go index c79a416020..2411d2e7bf 100644 --- a/cgi.go +++ b/cgi.go @@ -18,9 +18,12 @@ import ( "net/http" "path/filepath" "strings" + "unicode/utf8" "unsafe" "github.com/dunglas/frankenphp/internal/phpheaders" + "golang.org/x/text/language" + "golang.org/x/text/search" ) // Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html @@ -252,24 +255,64 @@ func splitCgiPath(fc *frankenPHPContext) { fc.worker = getWorkerByPath(fc.scriptFilename) } -// splitPos returns the index where path should -// be split based on SplitPath. +var splitSearchNonASCII = search.New(language.Und, search.IgnoreCase) + +// splitPos returns the index where path should be split based on splitPath. // example: if splitPath is [".php"] // "/path/to/script.php/some/path": ("/path/to/script.php", "/some/path") -// -// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go -// Copyright 2015 Matthew Holt and The Caddy Authors func splitPos(path string, splitPath []string) int { if len(splitPath) == 0 { return 0 } - lowerPath := strings.ToLower(path) + pathLen := len(path) + + // We are sure that split strings are all ASCII-only and lower-case because of validation and normalization in WithRequestSplitPath for _, split := range splitPath { - if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 { - return idx + len(split) + splitLen := len(split) + + for i := 0; i < pathLen; i++ { + if path[i] >= utf8.RuneSelf { + if _, end := splitSearchNonASCII.IndexString(path, split); end > -1 { + return end + } + + break + } + + if i+splitLen > pathLen { + continue + } + + match := true + for j := 0; j < splitLen; j++ { + c := path[i+j] + + if c >= utf8.RuneSelf { + if _, end := splitSearchNonASCII.IndexString(path, split); end > -1 { + return end + } + + break + } + + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + + if c != split[j] { + match = false + + break + } + } + + if match { + return i + splitLen + } } } + return -1 } diff --git a/cgi_test.go b/cgi_test.go index a328863308..d7c0e854d7 100644 --- a/cgi_test.go +++ b/cgi_test.go @@ -1,6 +1,7 @@ package frankenphp import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -31,3 +32,179 @@ func TestEnsureLeadingSlash(t *testing.T) { }) } } + +func TestSplitPos(t *testing.T) { + tests := []struct { + name string + path string + splitPath []string + wantPos int + }{ + { + name: "simple php extension", + path: "/path/to/script.php", + splitPath: []string{".php"}, + wantPos: 19, + }, + { + name: "php extension with path info", + path: "/path/to/script.php/some/path", + splitPath: []string{".php"}, + wantPos: 19, + }, + { + name: "case insensitive match", + path: "/path/to/script.PHP", + splitPath: []string{".php"}, + wantPos: 19, + }, + { + name: "mixed case match", + path: "/path/to/script.PhP/info", + splitPath: []string{".php"}, + wantPos: 19, + }, + { + name: "no match", + path: "/path/to/script.txt", + splitPath: []string{".php"}, + wantPos: -1, + }, + { + name: "empty split path", + path: "/path/to/script.php", + splitPath: []string{}, + wantPos: 0, + }, + { + name: "multiple split paths first match", + path: "/path/to/script.php", + splitPath: []string{".php", ".phtml"}, + wantPos: 19, + }, + { + name: "multiple split paths second match", + path: "/path/to/script.phtml", + splitPath: []string{".php", ".phtml"}, + wantPos: 21, + }, + // Unicode case-folding tests (security fix for GHSA-g966-83w7-6w38) + // U+023A (Ⱥ) lowercases to U+2C65 (ⱥ), which has different UTF-8 byte length + // Ⱥ: 2 bytes (C8 BA), ⱥ: 3 bytes (E2 B1 A5) + { + name: "unicode path with case-folding length expansion", + path: "/ȺȺȺȺshell.php", + splitPath: []string{".php"}, + wantPos: 18, // correct position in original string + }, + { + name: "unicode path with extension after expansion chars", + path: "/ȺȺȺȺshell.php/path/info", + splitPath: []string{".php"}, + wantPos: 18, + }, + { + name: "unicode in filename with multiple php occurrences", + path: "/ȺȺȺȺshell.php.txt.php", + splitPath: []string{".php"}, + wantPos: 18, // should match first .php, not be confused by byte offset shift + }, + { + name: "unicode case insensitive extension", + path: "/ȺȺȺȺshell.PHP", + splitPath: []string{".php"}, + wantPos: 18, + }, + { + name: "unicode in middle of path", + path: "/path/Ⱥtest/script.php", + splitPath: []string{".php"}, + wantPos: 23, // Ⱥ is 2 bytes, so path is 23 bytes total, .php ends at byte 23 + }, + { + name: "unicode only in directory not filename", + path: "/Ⱥ/script.php", + splitPath: []string{".php"}, + wantPos: 14, + }, + // Additional Unicode characters that expand when lowercased + // U+0130 (İ - Turkish capital I with dot) lowercases to U+0069 + U+0307 + { + name: "turkish capital I with dot", + path: "/İtest.php", + splitPath: []string{".php"}, + wantPos: 11, + }, + // Ensure standard ASCII still works correctly + { + name: "ascii only path with case variation", + path: "/PATH/TO/SCRIPT.PHP/INFO", + splitPath: []string{".php"}, + wantPos: 19, + }, + { + name: "path at root", + path: "/index.php", + splitPath: []string{".php"}, + wantPos: 10, + }, + { + name: "extension in middle of filename", + path: "/test.php.bak", + splitPath: []string{".php"}, + wantPos: 9, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotPos := splitPos(tt.path, tt.splitPath) + assert.Equal(t, tt.wantPos, gotPos, "splitPos(%q, %v)", tt.path, tt.splitPath) + + // Verify that the split produces valid substrings + if gotPos > 0 && gotPos <= len(tt.path) { + scriptName := tt.path[:gotPos] + pathInfo := tt.path[gotPos:] + + // The script name should end with one of the split extensions (case-insensitive) + hasValidEnding := false + for _, split := range tt.splitPath { + if strings.HasSuffix(strings.ToLower(scriptName), split) { + hasValidEnding = true + + break + } + } + assert.True(t, hasValidEnding, "script name %q should end with one of %v", scriptName, tt.splitPath) + + // Original path should be reconstructable + assert.Equal(t, tt.path, scriptName+pathInfo, "path should be reconstructable from split parts") + } + }) + } +} + +// TestSplitPosUnicodeSecurityRegression specifically tests the vulnerability +// described in GHSA-g966-83w7-6w38 where Unicode case-folding caused +// incorrect SCRIPT_NAME/PATH_INFO splitting +func TestSplitPosUnicodeSecurityRegression(t *testing.T) { + // U+023A: Ⱥ (UTF-8: C8 BA). Lowercase is ⱥ (UTF-8: E2 B1 A5), longer in bytes. + path := "/ȺȺȺȺshell.php.txt.php" + split := []string{".php"} + + pos := splitPos(path, split) + + // The vulnerable code would return 22 (computed on lowercased string) + // The correct code should return 18 (position in original string) + expectedPos := strings.Index(path, ".php") + len(".php") + assert.Equal(t, expectedPos, pos, "split position should match first .php in original string") + assert.Equal(t, 18, pos, "split position should be 18, not 22") + + if pos > 0 && pos <= len(path) { + scriptName := path[:pos] + pathInfo := path[pos:] + + assert.Equal(t, "/ȺȺȺȺshell.php", scriptName, "script name should be the path up to first .php") + assert.Equal(t, ".txt.php", pathInfo, "path info should be the remainder after first .php") + } +} diff --git a/request_options.go b/request_options.go index 2227b06eed..7f1e6b4f28 100644 --- a/request_options.go +++ b/request_options.go @@ -1,11 +1,14 @@ package frankenphp import ( + "errors" "log/slog" "net/http" "path/filepath" + "strings" "sync" "sync/atomic" + "unicode/utf8" "github.com/dunglas/frankenphp/internal/fastabs" ) @@ -14,6 +17,8 @@ import ( type RequestOption func(h *frankenPHPContext) error var ( + ErrInvalidSplitPath = errors.New("split path contains non-ASCII characters") + documentRootCache sync.Map documentRootCacheLen atomic.Uint32 ) @@ -71,11 +76,36 @@ func WithRequestResolvedDocumentRoot(documentRoot string) RequestOption { // actual resource (CGI script) name, and the second piece will be set to // PATH_INFO for the CGI script to use. // +// Split paths can only contain ASCII characters. +// Comparison is case-insensitive. +// // Future enhancements should be careful to avoid CVE-2019-11043, // which can be mitigated with use of a try_files-like behavior // that 404s if the FastCGI path info is not found. func WithRequestSplitPath(splitPath []string) RequestOption { return func(o *frankenPHPContext) error { + var b strings.Builder + + for i, split := range splitPath { + b.Grow(len(split)) + + for j := 0; j < len(split); j++ { + c := split[j] + if c >= utf8.RuneSelf { + return ErrInvalidSplitPath + } + + if 'A' <= c && c <= 'Z' { + b.WriteByte(c + 'a' - 'A') + } else { + b.WriteByte(c) + } + } + + splitPath[i] = b.String() + b.Reset() + } + o.splitPath = splitPath return nil diff --git a/request_options_test.go b/request_options_test.go new file mode 100644 index 0000000000..9da421f7d8 --- /dev/null +++ b/request_options_test.go @@ -0,0 +1,69 @@ +package frankenphp + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWithRequestSplitPath(t *testing.T) { + tests := []struct { + name string + splitPath []string + wantErr error + wantSplitPath []string + }{ + { + name: "valid lowercase split path", + splitPath: []string{".php"}, + wantErr: nil, + wantSplitPath: []string{".php"}, + }, + { + name: "valid uppercase split path normalized", + splitPath: []string{".PHP"}, + wantErr: nil, + wantSplitPath: []string{".php"}, + }, + { + name: "valid mixed case split path normalized", + splitPath: []string{".PhP", ".PHTML"}, + wantErr: nil, + wantSplitPath: []string{".php", ".phtml"}, + }, + { + name: "empty split path", + splitPath: []string{}, + wantErr: nil, + wantSplitPath: []string{}, + }, + { + name: "non-ASCII character in split path rejected", + splitPath: []string{".php", ".Ⱥphp"}, + wantErr: ErrInvalidSplitPath, + }, + { + name: "unicode character in split path rejected", + splitPath: []string{".phpⱥ"}, + wantErr: ErrInvalidSplitPath, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := &frankenPHPContext{} + opt := WithRequestSplitPath(tt.splitPath) + err := opt(ctx) + + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + + return + } + + require.NoError(t, err) + assert.Equal(t, tt.wantSplitPath, ctx.splitPath) + }) + } +} From 24d6c991a7761b638190eb081deae258143e9735 Mon Sep 17 00:00:00 2001 From: Xavier Leune Date: Wed, 28 Jan 2026 21:40:44 +0100 Subject: [PATCH 39/59] fix(worker): session leak between requests --- frankenphp.c | 7 +++ frankenphp_test.go | 109 ++++++++++++++++++++++++++++++++++++++ testdata/session-leak.php | 62 ++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 testdata/session-leak.php diff --git a/frankenphp.c b/frankenphp.c index 3206bf36df..20b36130b6 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -155,6 +155,13 @@ static void frankenphp_reset_super_globals() { zval *files = &PG(http_globals)[TRACK_VARS_FILES]; zval_ptr_dtor_nogc(files); memset(files, 0, sizeof(*files)); + + /* $_SESSION must be explicitly deleted from the symbol table. + * Unlike other superglobals, $_SESSION is stored in EG(symbol_table) + * with a reference to PS(http_session_vars). The session RSHUTDOWN + * only decrements the refcount but doesn't remove it from the symbol + * table, causing data to leak between requests. */ + zend_hash_str_del(&EG(symbol_table), "_SESSION", sizeof("_SESSION") - 1); } zend_end_try(); diff --git a/frankenphp_test.go b/frankenphp_test.go index 83a151e445..c1120b6c88 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -27,6 +27,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/dunglas/frankenphp" "github.com/dunglas/frankenphp/internal/fastabs" @@ -1306,3 +1307,111 @@ func TestIniPreLoopPreserved_worker(t *testing.T) { realServer: true, }) } + +func TestSessionNoLeakBetweenRequests_worker(t *testing.T) { + runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { + // Client A: Set a secret value in session + clientA := &http.Client{} + resp1, err := clientA.Get(ts.URL + "/session-leak.php?action=set&value=secret_A&client_id=clientA") + assert.NoError(t, err) + body1, _ := io.ReadAll(resp1.Body) + _ = resp1.Body.Close() + + body1Str := string(body1) + t.Logf("Client A set session: %s", body1Str) + assert.Contains(t, body1Str, "SESSION_SET") + assert.Contains(t, body1Str, "secret=secret_A") + + // Client B: Check that session is empty (no cookie, should not see Client A's data) + clientB := &http.Client{} + resp2, err := clientB.Get(ts.URL + "/session-leak.php?action=check_empty") + assert.NoError(t, err) + body2, _ := io.ReadAll(resp2.Body) + _ = resp2.Body.Close() + + body2Str := string(body2) + t.Logf("Client B check empty: %s", body2Str) + assert.Contains(t, body2Str, "SESSION_CHECK") + assert.Contains(t, body2Str, "SESSION_EMPTY=true", + "Client B should have empty session, not see Client A's data.\nResponse: %s", body2Str) + assert.NotContains(t, body2Str, "secret_A", + "Client A's secret should not leak to Client B.\nResponse: %s", body2Str) + + // Client C: Read session without cookie (should also be empty) + clientC := &http.Client{} + resp3, err := clientC.Get(ts.URL + "/session-leak.php?action=get") + assert.NoError(t, err) + body3, _ := io.ReadAll(resp3.Body) + _ = resp3.Body.Close() + + body3Str := string(body3) + t.Logf("Client C get session: %s", body3Str) + assert.Contains(t, body3Str, "SESSION_READ") + assert.Contains(t, body3Str, "secret=NOT_FOUND", + "Client C should not find any secret.\nResponse: %s", body3Str) + assert.Contains(t, body3Str, "client_id=NOT_FOUND", + "Client C should not find any client_id.\nResponse: %s", body3Str) + + }, &testOptions{ + workerScript: "session-leak.php", + nbWorkers: 1, + nbParallelRequests: 1, + realServer: true, + }) +} + +func TestSessionNoLeakAfterExit_worker(t *testing.T) { + runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { + // Client A: Set a secret value in session and call exit(1) + clientA := &http.Client{} + resp1, err := clientA.Get(ts.URL + "/session-leak.php?action=set_and_exit&value=exit_secret&client_id=exitClient") + assert.NoError(t, err) + body1, _ := io.ReadAll(resp1.Body) + _ = resp1.Body.Close() + + body1Str := string(body1) + t.Logf("Client A set and exit: %s", body1Str) + // The response may be incomplete due to exit(1) + assert.Contains(t, body1Str, "BEFORE_EXIT") + + // Client B: Check that session is empty (should not see Client A's data) + // Retry until the worker has restarted after exit(1) + clientB := &http.Client{} + var body2Str string + assert.Eventually(t, func() bool { + resp2, err := clientB.Get(ts.URL + "/session-leak.php?action=check_empty") + if err != nil { + return false + } + body2, _ := io.ReadAll(resp2.Body) + _ = resp2.Body.Close() + body2Str = string(body2) + return strings.Contains(body2Str, "SESSION_CHECK") + }, 2*time.Second, 10*time.Millisecond, "Worker did not restart in time after exit(1)") + + t.Logf("Client B check empty after exit: %s", body2Str) + assert.Contains(t, body2Str, "SESSION_EMPTY=true", + "Client B should have empty session after Client A's exit(1).\nResponse: %s", body2Str) + assert.NotContains(t, body2Str, "exit_secret", + "Client A's secret should not leak to Client B after exit(1).\nResponse: %s", body2Str) + + // Client C: Try to read session (should also be empty) + clientC := &http.Client{} + resp3, err := clientC.Get(ts.URL + "/session-leak.php?action=get") + assert.NoError(t, err) + body3, _ := io.ReadAll(resp3.Body) + _ = resp3.Body.Close() + + body3Str := string(body3) + t.Logf("Client C get session after exit: %s", body3Str) + assert.Contains(t, body3Str, "SESSION_READ") + assert.Contains(t, body3Str, "secret=NOT_FOUND", + "Client C should not find any secret after exit(1).\nResponse: %s", body3Str) + + }, &testOptions{ + workerScript: "session-leak.php", + nbWorkers: 1, + nbParallelRequests: 1, + realServer: true, + }) +} diff --git a/testdata/session-leak.php b/testdata/session-leak.php new file mode 100644 index 0000000000..d28292fae1 --- /dev/null +++ b/testdata/session-leak.php @@ -0,0 +1,62 @@ + Date: Wed, 11 Feb 2026 13:07:09 +0100 Subject: [PATCH 40/59] fix: race condition introduced in 04fdc0c (#2180) Fix issue introduced in 04fdc0c1e8fde94e2c1ad86217e962c88d27c53e --- caddy/module.go | 17 ++++++-- go.mod | 2 +- request_options.go => requestoptions.go | 42 +++++++++---------- ..._options_test.go => requestoptions_test.go | 8 +++- 4 files changed, 41 insertions(+), 28 deletions(-) rename request_options.go => requestoptions.go (90%) rename request_options_test.go => requestoptions_test.go (92%) diff --git a/caddy/module.go b/caddy/module.go index d1ab43a2e4..908129f881 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -118,7 +118,12 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { if len(f.SplitPath) == 0 { f.SplitPath = []string{".php"} } - f.requestOptions = append(f.requestOptions, frankenphp.WithRequestSplitPath(f.SplitPath)) + + if opt, err := frankenphp.WithRequestSplitPath(f.SplitPath); err == nil { + f.requestOptions = append(f.requestOptions, opt) + } else { + f.requestOptions = append(f.requestOptions, opt) + } if f.ResolveRootSymlink == nil { rrs := true @@ -188,6 +193,10 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) documentRoot := f.resolvedDocumentRoot + + opts := make([]frankenphp.RequestOption, 0, len(f.requestOptions)+4) + opts = append(opts, f.requestOptions...) + if documentRoot == "" { documentRoot = repl.ReplaceKnown(f.Root, "") if documentRoot == "" && frankenphp.EmbeddedAppPath != "" { @@ -197,7 +206,7 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c // If we do not have a resolved document root, then we cannot resolve the symlink of our cwd because it may // resolve to a different directory than the one we are currently in. // This is especially important if there are workers running. - f.requestOptions = append(f.requestOptions, frankenphp.WithRequestDocumentRoot(documentRoot, false)) + opts = append(opts, frankenphp.WithRequestDocumentRoot(documentRoot, false)) } if f.preparedEnvNeedsReplacement { @@ -206,7 +215,7 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c env[k] = repl.ReplaceKnown(v, "") } - f.requestOptions = append(f.requestOptions, frankenphp.WithRequestPreparedEnv(env)) + opts = append(opts, frankenphp.WithRequestPreparedEnv(env)) } workerName := "" @@ -220,7 +229,7 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c fr, err := frankenphp.NewRequestWithContext( r, append( - f.requestOptions, + opts, frankenphp.WithOriginalRequest(&origReq), frankenphp.WithWorkerName(workerName), )..., diff --git a/go.mod b/go.mod index 8d93c08df8..84af3ce910 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.49.0 + golang.org/x/text v0.33.0 ) require ( @@ -60,7 +61,6 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.47.0 // indirect golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/request_options.go b/requestoptions.go similarity index 90% rename from request_options.go rename to requestoptions.go index 7f1e6b4f28..41b3e5a079 100644 --- a/request_options.go +++ b/requestoptions.go @@ -82,34 +82,34 @@ func WithRequestResolvedDocumentRoot(documentRoot string) RequestOption { // Future enhancements should be careful to avoid CVE-2019-11043, // which can be mitigated with use of a try_files-like behavior // that 404s if the FastCGI path info is not found. -func WithRequestSplitPath(splitPath []string) RequestOption { - return func(o *frankenPHPContext) error { - var b strings.Builder - - for i, split := range splitPath { - b.Grow(len(split)) - - for j := 0; j < len(split); j++ { - c := split[j] - if c >= utf8.RuneSelf { - return ErrInvalidSplitPath - } - - if 'A' <= c && c <= 'Z' { - b.WriteByte(c + 'a' - 'A') - } else { - b.WriteByte(c) - } +func WithRequestSplitPath(splitPath []string) (RequestOption, error) { + var b strings.Builder + + for i, split := range splitPath { + b.Grow(len(split)) + + for j := 0; j < len(split); j++ { + c := split[j] + if c >= utf8.RuneSelf { + return nil, ErrInvalidSplitPath } - splitPath[i] = b.String() - b.Reset() + if 'A' <= c && c <= 'Z' { + b.WriteByte(c + 'a' - 'A') + } else { + b.WriteByte(c) + } } + splitPath[i] = b.String() + b.Reset() + } + + return func(o *frankenPHPContext) error { o.splitPath = splitPath return nil - } + }, nil } type PreparedEnv = map[string]string diff --git a/request_options_test.go b/requestoptions_test.go similarity index 92% rename from request_options_test.go rename to requestoptions_test.go index 9da421f7d8..b98e8bfeff 100644 --- a/request_options_test.go +++ b/requestoptions_test.go @@ -8,6 +8,8 @@ import ( ) func TestWithRequestSplitPath(t *testing.T) { + t.Parallel() + tests := []struct { name string splitPath []string @@ -52,9 +54,10 @@ func TestWithRequestSplitPath(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := &frankenPHPContext{} - opt := WithRequestSplitPath(tt.splitPath) - err := opt(ctx) + opt, err := WithRequestSplitPath(tt.splitPath) if tt.wantErr != nil { require.ErrorIs(t, err, tt.wantErr) @@ -63,6 +66,7 @@ func TestWithRequestSplitPath(t *testing.T) { } require.NoError(t, err) + require.NoError(t, opt(ctx)) assert.Equal(t, tt.wantSplitPath, ctx.splitPath) }) } From d704e60bb079e706a351b0077f398d86d6d28028 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:55:57 +0100 Subject: [PATCH 41/59] chore: bump to Go 1.26 (#2178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump and free perf boost with Green tea GC 🍵 --- .github/scripts/docker-verify-fingerprints.sh | 2 +- .github/workflows/sanitizers.yaml | 4 +- .github/workflows/static.yaml | 2 +- .github/workflows/tests.yaml | 6 +- caddy/go.mod | 56 ++++----- caddy/go.sum | 108 +++++++++--------- dev-alpine.Dockerfile | 2 +- dev.Dockerfile | 2 +- docker-bake.hcl | 2 +- go.mod | 12 +- go.sum | 20 ++-- internal/extgen/integration_test.go | 2 +- 12 files changed, 109 insertions(+), 109 deletions(-) diff --git a/.github/scripts/docker-verify-fingerprints.sh b/.github/scripts/docker-verify-fingerprints.sh index c33e00cfc1..2d50ff7175 100755 --- a/.github/scripts/docker-verify-fingerprints.sh +++ b/.github/scripts/docker-verify-fingerprints.sh @@ -7,7 +7,7 @@ USE_LATEST_PHP="${USE_LATEST_PHP:-0}" if [[ -z "${GO_VERSION}" ]]; then GO_VERSION="$(awk -F'"' '/variable "GO_VERSION"/ {f=1} f && /default/ {print $2; exit}' docker-bake.hcl)" - GO_VERSION="${GO_VERSION:-1.25}" + GO_VERSION="${GO_VERSION:-1.26}" fi if [[ -z "${PHP_VERSION}" ]]; then diff --git a/.github/workflows/sanitizers.yaml b/.github/workflows/sanitizers.yaml index 85bf76cb4e..fe2f2d2518 100644 --- a/.github/workflows/sanitizers.yaml +++ b/.github/workflows/sanitizers.yaml @@ -35,7 +35,7 @@ jobs: USE_ZEND_ALLOC: 0 LIBRARY_PATH: ${{ github.workspace }}/php/target/lib:${{ github.workspace }}/watcher/target/lib LD_LIBRARY_PATH: ${{ github.workspace }}/php/target/lib - # PHP doesn't free some memory on purpose, we have to disable leaks detection: https://go.dev/doc/go1.25#go-command + # PHP doesn't free some memory on purpose, we have to disable leaks detection: https://go.dev/doc/go1.26#go-command ASAN_OPTIONS: detect_leaks=0 steps: - name: Remove local PHP @@ -45,7 +45,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" cache-dependency-path: | go.sum caddy/go.sum diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index 3908a64e3d..21bc9cf216 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -450,7 +450,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" cache-dependency-path: | go.sum caddy/go.sum diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1d48784abc..656b8bedad 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -41,7 +41,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" cache-dependency-path: | go.sum caddy/go.sum @@ -106,7 +106,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" cache-dependency-path: | go.sum caddy/go.sum @@ -147,7 +147,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@v6 with: - go-version: "1.25" + go-version: "1.26" cache-dependency-path: | go.sum caddy/go.sum diff --git a/caddy/go.mod b/caddy/go.mod index 8c6676ec1c..a194bba17c 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -1,6 +1,6 @@ module github.com/dunglas/frankenphp/caddy -go 1.25.4 +go 1.26.0 replace github.com/dunglas/frankenphp => ../ @@ -11,8 +11,8 @@ require ( github.com/caddyserver/certmagic v0.25.1 github.com/dunglas/caddy-cbrotli v1.0.1 github.com/dunglas/frankenphp v1.11.1 - github.com/dunglas/mercure v0.21.7 - github.com/dunglas/mercure/caddy v0.21.7 + github.com/dunglas/mercure v0.21.8 + github.com/dunglas/mercure/caddy v0.21.8 github.com/dunglas/vulcain/caddy v1.2.1 github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 @@ -67,7 +67,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/go-chi/chi/v5 v5.2.4 // indirect + github.com/go-chi/chi/v5 v5.2.5 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -88,11 +88,11 @@ require ( github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -100,7 +100,7 @@ require ( github.com/jackc/pgx/v5 v5.8.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/libdns/libdns v1.1.1 // indirect @@ -123,7 +123,7 @@ require ( github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pires/go-proxyproto v0.9.2 // indirect + github.com/pires/go-proxyproto v0.11.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -168,14 +168,14 @@ require ( go.etcd.io/bbolt v1.4.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 // indirect - go.opentelemetry.io/contrib/propagators/aws v1.39.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.39.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 // indirect - go.opentelemetry.io/contrib/propagators/ot v1.39.0 // indirect + go.opentelemetry.io/contrib/propagators/autoprop v0.65.0 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.40.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.40.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.40.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect @@ -187,21 +187,21 @@ require ( go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 // indirect - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e // indirect + golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.41.0 // indirect - google.golang.org/api v0.265.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/api v0.266.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/caddy/go.sum b/caddy/go.sum index 468e94ee46..cacf4b0d39 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -146,10 +146,10 @@ github.com/dunglas/caddy-cbrotli v1.0.1 h1:mkg7EB1GmoyfBt3kY3mq4o/0bfnBeq7ZLQjmV github.com/dunglas/caddy-cbrotli v1.0.1/go.mod h1:uXABy3tjy1FABF+3JWKVh1ajFvIO/kfpwHaeZGSBaAY= github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= -github.com/dunglas/mercure v0.21.7 h1:QtxQr9IMqJdlLxpsCjdBoDkJm4r4HXIEQCLl5obzI+o= -github.com/dunglas/mercure v0.21.7/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= -github.com/dunglas/mercure/caddy v0.21.7 h1:+Mx9zzQPnGsF3Jwwp7hFXravkv5TUWqjifZFJ6yArQk= -github.com/dunglas/mercure/caddy v0.21.7/go.mod h1:VOrsyc4RJf8a9Ucr/cLjiODr6nd/BinLi02dOvyWXi4= +github.com/dunglas/mercure v0.21.8 h1:D+SxSq0VqdB29lfMXrsvDkFvq/cTL94aKCC0R4heKV0= +github.com/dunglas/mercure v0.21.8/go.mod h1:kt4RJpixJOcPN+x9Z53VBhpJYSdyEEzuu9/99vJIocQ= +github.com/dunglas/mercure/caddy v0.21.8 h1:jfWSRUoialL3iH1AlmrVAIoU8EbGrLLGd4r+nhbBalg= +github.com/dunglas/mercure/caddy v0.21.8/go.mod h1:rU3iqkU44FASio9Fqqmwn50I0l7w67XDsXuKsqzSDrE= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= github.com/dunglas/vulcain v1.2.1 h1:pkPwvIfoa/xmWSVUyhntbIKT+XO2VFMyhLKv1gA61O8= @@ -172,8 +172,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= -github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= @@ -230,16 +230,16 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= -github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 h1:1ufTZkFXIQQ9EmgPjcIPIi2krfxG03lQ8OLoY1MJ3UM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 h1:NpbJl/eVbvrGE0MJ6X16X9SAifesl6Fwxg/YmCvubRI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8/go.mod h1:mi7YA+gCzVem12exXy46ZespvGtX/lZmD/RLnQhVW7U= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= @@ -260,8 +260,8 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -320,8 +320,8 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= -github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U= -github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= +github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= +github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -466,22 +466,22 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 h1:VVrb1ErDD0Tlh/0K0rUqjky1e8AekjspTFN9sU2ekaA= -go.opentelemetry.io/contrib/propagators/autoprop v0.64.0/go.mod h1:QCsOQk+9Ep8Mkp4/aPtSzUT0dc8SaPYzBAE6o1jYuSE= -go.opentelemetry.io/contrib/propagators/aws v1.39.0 h1:IvNR8pAVGpkK1CHMjU/YE6B6TlnAPGFvogkMWRWU6wo= -go.opentelemetry.io/contrib/propagators/aws v1.39.0/go.mod h1:TUsFCERuGM4IGhJG9w+9l0nzmHUKHuaDYYNF6mtNgjY= -go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk= -go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY= -go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo= -go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8= -go.opentelemetry.io/contrib/propagators/ot v1.39.0 h1:vKTve1W/WKPVp1fzJamhCDDECt+5upJJ65bPyWoddGg= -go.opentelemetry.io/contrib/propagators/ot v1.39.0/go.mod h1:FH5VB2N19duNzh1Q8ks6CsZFyu3LFhNLiA9lPxyEkvU= +go.opentelemetry.io/contrib/propagators/autoprop v0.65.0 h1:kTaCycF9Xkm8VBBvH0rJ4wFeRjtIV55Erk3uuVsIs5s= +go.opentelemetry.io/contrib/propagators/autoprop v0.65.0/go.mod h1:rooPzAbXfxMX9fsPJjmOBg2SN4RhFEV8D7cfGK+N3tE= +go.opentelemetry.io/contrib/propagators/aws v1.40.0 h1:4VIrh75jW4RTimUNx1DSk+6H9/nDr1FvmKoOVDh3K04= +go.opentelemetry.io/contrib/propagators/aws v1.40.0/go.mod h1:B0dCov9KNQGlut3T8wZZjDnLXEXdBroM7bFsHh/gRos= +go.opentelemetry.io/contrib/propagators/b3 v1.40.0 h1:xariChe8OOVF3rNlfzGFgQc61npQmXhzZj/i82mxMfg= +go.opentelemetry.io/contrib/propagators/b3 v1.40.0/go.mod h1:72WvbdxbOfXaELEQfonFfOL6osvcVjI7uJEE8C2nkrs= +go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 h1:aXl9uobjJs5vquMLt9ZkI/3zIuz8XQ3TqOKSWx0/xdU= +go.opentelemetry.io/contrib/propagators/jaeger v1.40.0/go.mod h1:ioMePqe6k6c/ovXSkmkMr1mbN5qRBGJxNTVop7/2XO0= +go.opentelemetry.io/contrib/propagators/ot v1.40.0 h1:Lon8J5SPmWaL1Ko2TIlCNHJ42/J1b5XbJlgJaE/9m7I= +go.opentelemetry.io/contrib/propagators/ot v1.40.0/go.mod h1:dKWtJTlp1Yj+8Cneye5idO46eRPIbi23qVuJYKjNnvY= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= @@ -517,19 +517,19 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ= -golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e h1:G2pNJMnWEb60ip90TMo++m9fpt+d3YIZa0er3buPKRo= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -538,10 +538,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -567,8 +567,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -578,8 +578,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -589,8 +589,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -599,19 +599,19 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.265.0 h1:FZvfUdI8nfmuNrE34aOWFPmLC+qRBEiNm3JdivTvAAU= -google.golang.org/api v0.265.0/go.mod h1:uAvfEl3SLUj/7n6k+lJutcswVojHPp2Sp08jWCu8hLY= +google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk= +google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= -google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= diff --git a/dev-alpine.Dockerfile b/dev-alpine.Dockerfile index b929c0a1b0..04efda04e8 100644 --- a/dev-alpine.Dockerfile +++ b/dev-alpine.Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 #checkov:skip=CKV_DOCKER_2 #checkov:skip=CKV_DOCKER_3 -FROM golang:1.25-alpine +FROM golang:1.26-alpine ENV GOTOOLCHAIN=local ENV CFLAGS="-ggdb3" diff --git a/dev.Dockerfile b/dev.Dockerfile index 62e83e3821..a0d1496b98 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 #checkov:skip=CKV_DOCKER_2 #checkov:skip=CKV_DOCKER_3 -FROM golang:1.25 +FROM golang:1.26 ENV GOTOOLCHAIN=local ENV CFLAGS="-ggdb3" diff --git a/docker-bake.hcl b/docker-bake.hcl index 9a397fc314..0d7cc1656f 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -11,7 +11,7 @@ variable "PHP_VERSION" { } variable "GO_VERSION" { - default = "1.25" + default = "1.26" } variable "BASE_FINGERPRINT" { diff --git a/go.mod b/go.mod index 84af3ce910..3d3510bac7 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,18 @@ module github.com/dunglas/frankenphp -go 1.25.4 +go 1.26.0 retract v1.0.0-rc.1 // Human error require ( github.com/Masterminds/sprig/v3 v3.3.0 - github.com/dunglas/mercure v0.21.7 + github.com/dunglas/mercure v0.21.8 github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f github.com/maypok86/otter/v2 v2.3.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.49.0 - golang.org/x/text v0.33.0 + golang.org/x/net v0.50.0 + golang.org/x/text v0.34.0 ) require ( @@ -59,8 +59,8 @@ require ( go.etcd.io/bbolt v1.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/sys v0.41.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 896982e4ae..bb6259131d 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dunglas/mercure v0.21.7 h1:QtxQr9IMqJdlLxpsCjdBoDkJm4r4HXIEQCLl5obzI+o= -github.com/dunglas/mercure v0.21.7/go.mod h1:jze6G0/ha0j/YJnAbQRcMLE58/LoRogquwaRQPGjOuA= +github.com/dunglas/mercure v0.21.8 h1:D+SxSq0VqdB29lfMXrsvDkFvq/cTL94aKCC0R4heKV0= +github.com/dunglas/mercure v0.21.8/go.mod h1:kt4RJpixJOcPN+x9Z53VBhpJYSdyEEzuu9/99vJIocQ= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f h1:UDB5nhFRW7IOOpLk/eP1UGj7URmPimFGV+01/EG9qR8= @@ -108,16 +108,16 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/extgen/integration_test.go b/internal/extgen/integration_test.go index 4c35399069..c8cdd90c9a 100644 --- a/internal/extgen/integration_test.go +++ b/internal/extgen/integration_test.go @@ -75,7 +75,7 @@ func (s *IntegrationTestSuite) createGoModule(sourceFile string) (string, error) goModContent := fmt.Sprintf(`module %s -go 1.25 +go 1.26 require github.com/dunglas/frankenphp v0.0.0 From 040ce55e17885cc316ccdeab9d24901cd261e6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 11 Feb 2026 15:21:55 +0100 Subject: [PATCH 42/59] perf: various optimizations (#2175) --- caddy/admin_test.go | 5 +- caddy/module.go | 23 +++++-- caddy/workerconfig.go | 26 ++++++-- cgi.go | 33 ++++++++-- context.go | 10 ++- frankenphp.go | 35 +++++----- internal/fastabs/filepath_unix.go | 2 +- internal/state/state.go | 104 +++++++++++++++++------------- phpmainthread.go | 3 + phpmainthread_test.go | 12 +++- phpthread.go | 6 +- requestoptions.go | 2 +- scaling.go | 20 +++--- scaling_test.go | 2 +- worker.go | 32 ++++----- 15 files changed, 192 insertions(+), 123 deletions(-) diff --git a/caddy/admin_test.go b/caddy/admin_test.go index ad0b5a8e1b..09576d3f84 100644 --- a/caddy/admin_test.go +++ b/caddy/admin_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "strings" "sync" "testing" @@ -249,7 +250,7 @@ func TestAddModuleWorkerViaAdminApi(t *testing.T) { initialDebugState := getDebugState(t, tester) initialWorkerCount := 0 for _, thread := range initialDebugState.ThreadDebugStates { - if thread.Name != "" && thread.Name != "ready" { + if strings.HasPrefix(thread.Name, "Worker PHP Thread") { initialWorkerCount++ } } @@ -286,7 +287,7 @@ func TestAddModuleWorkerViaAdminApi(t *testing.T) { workerFound := false filename, _ := fastabs.FastAbs("../testdata/worker-with-counter.php") for _, thread := range updatedDebugState.ThreadDebugStates { - if thread.Name != "" && thread.Name != "ready" { + if strings.HasPrefix(thread.Name, "Worker PHP Thread") { updatedWorkerCount++ if thread.Name == "Worker PHP Thread - "+filename { workerFound = true diff --git a/caddy/module.go b/caddy/module.go index 908129f881..332c45bc1c 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -130,6 +130,11 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { f.ResolveRootSymlink = &rrs } + // Always pre-compute absolute file names for fallback matching + for i := range f.Workers { + f.Workers[i].absFileName, _ = fastabs.FastAbs(f.Workers[i].FileName) + } + if !needReplacement(f.Root) { root, err := fastabs.FastAbs(f.Root) if err != nil { @@ -145,13 +150,21 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { f.resolvedDocumentRoot = root - // Also resolve symlinks in worker file paths when resolve_root_symlink is true + // Resolve symlinks in worker file paths for i, wc := range f.Workers { - if !filepath.IsAbs(wc.FileName) { - continue + if filepath.IsAbs(wc.FileName) { + resolvedPath, _ := filepath.EvalSymlinks(wc.FileName) + f.Workers[i].FileName = resolvedPath + f.Workers[i].absFileName = resolvedPath } - resolvedPath, _ := filepath.EvalSymlinks(wc.FileName) - f.Workers[i].FileName = resolvedPath + } + } + + // Pre-compute relative match paths for all workers (requires resolved document root) + docRootWithSep := f.resolvedDocumentRoot + string(filepath.Separator) + for i := range f.Workers { + if strings.HasPrefix(f.Workers[i].absFileName, docRootWithSep) { + f.Workers[i].matchRelPath = filepath.ToSlash(f.Workers[i].absFileName[len(f.resolvedDocumentRoot):]) } } diff --git a/caddy/workerconfig.go b/caddy/workerconfig.go index c22cf89448..c50f0d0688 100644 --- a/caddy/workerconfig.go +++ b/caddy/workerconfig.go @@ -2,6 +2,7 @@ package caddy import ( "net/http" + "path" "path/filepath" "strconv" @@ -43,6 +44,8 @@ type workerConfig struct { options []frankenphp.WorkerOption requestOptions []frankenphp.RequestOption + absFileName string + matchRelPath string // pre-computed relative URL path for fast matching } func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) { @@ -171,15 +174,28 @@ func (wc *workerConfig) inheritEnv(env map[string]string) { } func (wc *workerConfig) matchesPath(r *http.Request, documentRoot string) bool { - // try to match against a pattern if one is assigned if len(wc.MatchPath) != 0 { return (caddyhttp.MatchPath)(wc.MatchPath).Match(r) } - // if there is no pattern, try to match against the actual path (in the public directory) - fullScriptPath, _ := fastabs.FastAbs(documentRoot + "/" + r.URL.Path) - absFileName, _ := fastabs.FastAbs(wc.FileName) + // fast path: compare the request URL path against the pre-computed relative path + if wc.matchRelPath != "" { + reqPath := r.URL.Path + if reqPath == wc.matchRelPath { + return true + } + + // ensure leading slash for relative paths (see #2166) + if reqPath == "" || reqPath[0] != '/' { + reqPath = "/" + reqPath + } + + return path.Clean(reqPath) == wc.matchRelPath + } + + // fallback when documentRoot is dynamic (contains placeholders) + fullPath, _ := fastabs.FastAbs(filepath.Join(documentRoot, r.URL.Path)) - return fullScriptPath == absFileName + return fullPath == wc.absFileName } diff --git a/cgi.go b/cgi.go index 2411d2e7bf..4668feb039 100644 --- a/cgi.go +++ b/cgi.go @@ -67,6 +67,20 @@ var knownServerKeys = []string{ "REQUEST_URI", } +// cStringHTTPMethods caches C string versions of common HTTP methods +// to avoid allocations in pinCString on every request. +var cStringHTTPMethods = map[string]*C.char{ + "GET": C.CString("GET"), + "HEAD": C.CString("HEAD"), + "POST": C.CString("POST"), + "PUT": C.CString("PUT"), + "DELETE": C.CString("DELETE"), + "CONNECT": C.CString("CONNECT"), + "OPTIONS": C.CString("OPTIONS"), + "TRACE": C.CString("TRACE"), + "PATCH": C.CString("PATCH"), +} + // computeKnownVariables returns a set of CGI environment variables for the request. // // TODO: handle this case https://github.com/caddyserver/caddy/issues/3718 @@ -84,8 +98,9 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { } // Remove [] from IPv6 addresses - ip = strings.Replace(ip, "[", "", 1) - ip = strings.Replace(ip, "]", "", 1) + if len(ip) > 0 && ip[0] == '[' { + ip = ip[1 : len(ip)-1] + } var https, sslProtocol, sslCipher, rs string @@ -98,7 +113,7 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { rs = "https" https = "on" - // and pass the protocol details in a manner compatible with apache's mod_ssl + // and pass the protocol details in a manner compatible with Apache's mod_ssl // (which is why these have an SSL_ prefix and not TLS_). if v, ok := tlsProtocolStrings[request.TLS.Version]; ok { sslProtocol = v @@ -138,7 +153,7 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { if fc.originalRequest != nil { requestURI = fc.originalRequest.URL.RequestURI() } else { - requestURI = request.URL.RequestURI() + requestURI = fc.requestURI } C.frankenphp_register_bulk( @@ -252,7 +267,7 @@ func splitCgiPath(fc *frankenPHPContext) { // TODO: is it possible to delay this and avoid saving everything in the context? // SCRIPT_FILENAME is the absolute path of SCRIPT_NAME fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName) - fc.worker = getWorkerByPath(fc.scriptFilename) + fc.worker = workersByPath[fc.scriptFilename] } var splitSearchNonASCII = search.New(language.Und, search.IgnoreCase) @@ -329,7 +344,11 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) return nil } - info.request_method = thread.pinCString(request.Method) + if m, ok := cStringHTTPMethods[request.Method]; ok { + info.request_method = m + } else { + info.request_method = thread.pinCString(request.Method) + } info.query_string = thread.pinCString(request.URL.RawQuery) info.content_length = C.zend_long(request.ContentLength) @@ -341,7 +360,7 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) info.path_translated = thread.pinCString(sanitizedPathJoin(fc.documentRoot, fc.pathInfo)) // See: http://www.oreilly.com/openbook/cgi/ch02_04.html } - info.request_uri = thread.pinCString(request.URL.RequestURI()) + info.request_uri = thread.pinCString(fc.requestURI) info.proto_num = C.int(request.ProtoMajor*1000 + request.ProtoMinor) diff --git a/context.go b/context.go index 3126e8f9aa..92f3b7471c 100644 --- a/context.go +++ b/context.go @@ -28,13 +28,15 @@ type frankenPHPContext struct { pathInfo string scriptName string scriptFilename string + requestURI string // Whether the request is already closed by us isDone bool - responseWriter http.ResponseWriter - handlerParameters any - handlerReturn any + responseWriter http.ResponseWriter + responseController *http.ResponseController + handlerParameters any + handlerReturn any done chan any startedAt time.Time @@ -93,6 +95,8 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques splitCgiPath(fc) } + fc.requestURI = r.URL.RequestURI() + c := context.WithValue(r.Context(), contextKey, fc) return r.WithContext(c), nil diff --git a/frankenphp.go b/frankenphp.go index 693870e1d0..c651de3cf1 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -611,7 +611,10 @@ func go_sapi_flush(threadIndex C.uintptr_t) bool { return true } - if err := http.NewResponseController(fc.responseWriter).Flush(); err != nil { + if fc.responseController == nil { + fc.responseController = http.NewResponseController(fc.responseWriter) + } + if err := fc.responseController.Flush(); err != nil { ctx := thread.context() if globalLogger.Enabled(ctx, slog.LevelWarn) { @@ -683,34 +686,28 @@ func getLogger(threadIndex C.uintptr_t) (*slog.Logger, context.Context) { func go_log(threadIndex C.uintptr_t, message *C.char, level C.int) { logger, ctx := getLogger(threadIndex) - m := C.GoString(message) le := syslogLevelInfo - if level >= C.int(syslogLevelEmerg) && level <= C.int(syslogLevelDebug) { le = syslogLevel(level) } + var slogLevel slog.Level switch le { case syslogLevelEmerg, syslogLevelAlert, syslogLevelCrit, syslogLevelErr: - if logger.Enabled(ctx, slog.LevelError) { - logger.LogAttrs(ctx, slog.LevelError, m, slog.String("syslog_level", le.String())) - } - + slogLevel = slog.LevelError case syslogLevelWarn: - if logger.Enabled(ctx, slog.LevelWarn) { - logger.LogAttrs(ctx, slog.LevelWarn, m, slog.String("syslog_level", le.String())) - } - + slogLevel = slog.LevelWarn case syslogLevelDebug: - if logger.Enabled(ctx, slog.LevelDebug) { - logger.LogAttrs(ctx, slog.LevelDebug, m, slog.String("syslog_level", le.String())) - } - + slogLevel = slog.LevelDebug default: - if logger.Enabled(ctx, slog.LevelInfo) { - logger.LogAttrs(ctx, slog.LevelInfo, m, slog.String("syslog_level", le.String())) - } + slogLevel = slog.LevelInfo + } + + if !logger.Enabled(ctx, slogLevel) { + return } + + logger.LogAttrs(ctx, slogLevel, C.GoString(message), slog.String("syslog_level", le.String())) } //export go_log_attrs @@ -805,6 +802,8 @@ func resetGlobals() { globalCtx = context.Background() globalLogger = slog.Default() workers = nil + workersByName = nil + workersByPath = nil watcherIsEnabled = false globalMu.Unlock() } diff --git a/internal/fastabs/filepath_unix.go b/internal/fastabs/filepath_unix.go index 13cde6ce96..47fbc47d68 100644 --- a/internal/fastabs/filepath_unix.go +++ b/internal/fastabs/filepath_unix.go @@ -19,7 +19,7 @@ func init() { } canonicalWD, err := filepath.EvalSymlinks(wd) - if err != nil { + if err == nil { wd = canonicalWD } } diff --git a/internal/state/state.go b/internal/state/state.go index 0a3030384e..62d14e503f 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -4,40 +4,71 @@ import "C" import ( "slices" "sync" + "sync/atomic" "time" ) -type State string +type State int const ( // lifecycle States of a thread - Reserved State = "reserved" - Booting State = "booting" - BootRequested State = "boot requested" - ShuttingDown State = "shutting down" - Done State = "done" + Reserved State = iota + Booting + BootRequested + ShuttingDown + Done // these States are 'stable' and safe to transition from at any time - Inactive State = "inactive" - Ready State = "ready" + Inactive + Ready // States necessary for restarting workers - Restarting State = "restarting" - Yielding State = "yielding" + Restarting + Yielding // States necessary for transitioning between different handlers - TransitionRequested State = "transition requested" - TransitionInProgress State = "transition in progress" - TransitionComplete State = "transition complete" + TransitionRequested + TransitionInProgress + TransitionComplete ) +func (s State) String() string { + switch s { + case Reserved: + return "reserved" + case Booting: + return "booting" + case BootRequested: + return "boot requested" + case ShuttingDown: + return "shutting down" + case Done: + return "done" + case Inactive: + return "inactive" + case Ready: + return "ready" + case Restarting: + return "restarting" + case Yielding: + return "yielding" + case TransitionRequested: + return "transition requested" + case TransitionInProgress: + return "transition in progress" + case TransitionComplete: + return "transition complete" + default: + return "unknown" + } +} + type ThreadState struct { currentState State mu sync.RWMutex subscribers []stateSubscriber - // how long threads have been waiting in stable states - waitingSince time.Time - isWaiting bool + // how long threads have been waiting in stable states (unix ms, 0 = not waiting) + waitingSince atomic.Int64 } type stateSubscriber struct { @@ -74,7 +105,7 @@ func (ts *ThreadState) CompareAndSwap(compareTo State, swapTo State) bool { } func (ts *ThreadState) Name() string { - return string(ts.Get()) + return ts.Get().String() } func (ts *ThreadState) Get() State { @@ -97,20 +128,17 @@ func (ts *ThreadState) notifySubscribers(nextState State) { return } - var newSubscribers []stateSubscriber - - // notify subscribers to the state change + n := 0 for _, sub := range ts.subscribers { if !slices.Contains(sub.states, nextState) { - newSubscribers = append(newSubscribers, sub) - + ts.subscribers[n] = sub + n++ continue } - close(sub.ch) } - ts.subscribers = newSubscribers + ts.subscribers = ts.subscribers[:n] } // block until the thread reaches a certain state @@ -156,39 +184,27 @@ func (ts *ThreadState) RequestSafeStateChange(nextState State) bool { // MarkAsWaiting hints that the thread reached a stable state and is waiting for requests or shutdown func (ts *ThreadState) MarkAsWaiting(isWaiting bool) { - ts.mu.Lock() if isWaiting { - ts.isWaiting = true - ts.waitingSince = time.Now() + ts.waitingSince.Store(time.Now().UnixMilli()) } else { - ts.isWaiting = false + ts.waitingSince.Store(0) } - ts.mu.Unlock() } // IsInWaitingState returns true if a thread is waiting for a request or shutdown func (ts *ThreadState) IsInWaitingState() bool { - ts.mu.RLock() - isWaiting := ts.isWaiting - ts.mu.RUnlock() - - return isWaiting + return ts.waitingSince.Load() != 0 } // WaitTime returns the time since the thread is waiting in a stable state in ms func (ts *ThreadState) WaitTime() int64 { - ts.mu.RLock() - waitTime := int64(0) - if ts.isWaiting { - waitTime = time.Now().UnixMilli() - ts.waitingSince.UnixMilli() + since := ts.waitingSince.Load() + if since == 0 { + return 0 } - ts.mu.RUnlock() - - return waitTime + return time.Now().UnixMilli() - since } func (ts *ThreadState) SetWaitTime(t time.Time) { - ts.mu.Lock() - ts.waitingSince = t - ts.mu.Unlock() + ts.waitingSince.Store(t.UnixMilli()) } diff --git a/phpmainthread.go b/phpmainthread.go index e10ce9e42f..1ba7dc3d44 100644 --- a/phpmainthread.go +++ b/phpmainthread.go @@ -198,6 +198,9 @@ func go_get_custom_php_ini(disableTimeouts C.bool) *C.char { // Pass the php.ini overrides to PHP before startup // TODO: if needed this would also be possible on a per-thread basis var overrides strings.Builder + + // 32 is an over-estimate for php.ini settings + overrides.Grow(len(mainThread.phpIni) * 32) for k, v := range mainThread.phpIni { overrides.WriteString(k) overrides.WriteByte('=') diff --git a/phpmainthread_test.go b/phpmainthread_test.go index b54647ae07..337fbe17f1 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -185,8 +185,12 @@ func TestFinishBootingAWorkerScript(t *testing.T) { func TestReturnAnErrorIf2WorkersHaveTheSameFileName(t *testing.T) { workers = []*worker{} + workersByName = map[string]*worker{} + workersByPath = map[string]*worker{} w, err1 := newWorker(workerOpt{fileName: testDataPath + "/index.php"}) workers = append(workers, w) + workersByName[w.name] = w + workersByPath[w.fileName] = w _, err2 := newWorker(workerOpt{fileName: testDataPath + "/index.php"}) assert.NoError(t, err1) @@ -195,8 +199,12 @@ func TestReturnAnErrorIf2WorkersHaveTheSameFileName(t *testing.T) { func TestReturnAnErrorIf2ModuleWorkersHaveTheSameName(t *testing.T) { workers = []*worker{} + workersByName = map[string]*worker{} + workersByPath = map[string]*worker{} w, err1 := newWorker(workerOpt{fileName: testDataPath + "/index.php", name: "workername"}) workers = append(workers, w) + workersByName[w.name] = w + workersByPath[w.fileName] = w _, err2 := newWorker(workerOpt{fileName: testDataPath + "/hello.php", name: "workername"}) assert.NoError(t, err1) @@ -242,9 +250,9 @@ func allPossibleTransitions(worker1Path string, worker2Path string) []func(*phpT thread.boot() } }, - func(thread *phpThread) { convertToWorkerThread(thread, getWorkerByPath(worker1Path)) }, + func(thread *phpThread) { convertToWorkerThread(thread, workersByPath[worker1Path]) }, convertToInactiveThread, - func(thread *phpThread) { convertToWorkerThread(thread, getWorkerByPath(worker2Path)) }, + func(thread *phpThread) { convertToWorkerThread(thread, workersByPath[worker2Path]) }, convertToInactiveThread, } } diff --git a/phpthread.go b/phpthread.go index 1726cf9d18..40d04effa4 100644 --- a/phpthread.go +++ b/phpthread.go @@ -19,7 +19,7 @@ type phpThread struct { threadIndex int requestChan chan contextHolder drainChan chan struct{} - handlerMu sync.Mutex + handlerMu sync.RWMutex handler threadHandler state *state.ThreadState sandboxedEnv map[string]*C.zend_string @@ -120,9 +120,9 @@ func (thread *phpThread) context() context.Context { } func (thread *phpThread) name() string { - thread.handlerMu.Lock() + thread.handlerMu.RLock() name := thread.handler.name() - thread.handlerMu.Unlock() + thread.handlerMu.RUnlock() return name } diff --git a/requestoptions.go b/requestoptions.go index 41b3e5a079..42cc3cf7c0 100644 --- a/requestoptions.go +++ b/requestoptions.go @@ -158,7 +158,7 @@ func WithRequestLogger(logger *slog.Logger) RequestOption { func WithWorkerName(name string) RequestOption { return func(o *frankenPHPContext) error { if name != "" { - o.worker = getWorkerByName(name) + o.worker = workersByName[name] } return nil diff --git a/scaling.go b/scaling.go index 37e081abb9..5ac07fe73b 100644 --- a/scaling.go +++ b/scaling.go @@ -84,6 +84,11 @@ func addWorkerThread(worker *worker) (*phpThread, error) { // scaleWorkerThread adds a worker PHP thread automatically func scaleWorkerThread(worker *worker) { + // probe CPU usage before acquiring the lock (avoids holding lock during 120ms sleep) + if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) { + return + } + scalingMu.Lock() defer scalingMu.Unlock() @@ -91,11 +96,6 @@ func scaleWorkerThread(worker *worker) { return } - // probe CPU usage before scaling - if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) { - return - } - thread, err := addWorkerThread(worker) if err != nil { if globalLogger.Enabled(globalCtx, slog.LevelWarn) { @@ -114,6 +114,11 @@ func scaleWorkerThread(worker *worker) { // scaleRegularThread adds a regular PHP thread automatically func scaleRegularThread() { + // probe CPU usage before acquiring the lock (avoids holding lock during 120ms sleep) + if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) { + return + } + scalingMu.Lock() defer scalingMu.Unlock() @@ -121,11 +126,6 @@ func scaleRegularThread() { return } - // probe CPU usage before scaling - if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) { - return - } - thread, err := addRegularThread() if err != nil { if globalLogger.Enabled(globalCtx, slog.LevelWarn) { diff --git a/scaling_test.go b/scaling_test.go index f7ecc05e05..5092a0178e 100644 --- a/scaling_test.go +++ b/scaling_test.go @@ -47,7 +47,7 @@ func TestScaleAWorkerThreadUpAndDown(t *testing.T) { autoScaledThread := phpThreads[2] // scale up - scaleWorkerThread(getWorkerByPath(workerPath)) + scaleWorkerThread(workersByPath[workerPath]) assert.Equal(t, state.Ready, autoScaledThread.state.Get()) // on down-scale, the thread will be marked as inactive diff --git a/worker.go b/worker.go index 35deec2be2..edba017218 100644 --- a/worker.go +++ b/worker.go @@ -37,6 +37,8 @@ type worker struct { var ( workers []*worker + workersByName map[string]*worker + workersByPath map[string]*worker watcherIsEnabled bool startupFailChan chan error ) @@ -52,6 +54,8 @@ func initWorkers(opt []workerOpt) error { ) workers = make([]*worker, 0, len(opt)) + workersByName = make(map[string]*worker, len(opt)) + workersByPath = make(map[string]*worker, len(opt)) for _, o := range opt { w, err := newWorker(o) @@ -61,6 +65,10 @@ func initWorkers(opt []workerOpt) error { totalThreadsToStart += w.num workers = append(workers, w) + workersByName[w.name] = w + if w.allowPathMatching { + workersByPath[w.fileName] = w + } } startupFailChan = make(chan error, totalThreadsToStart) @@ -90,25 +98,6 @@ func initWorkers(opt []workerOpt) error { return nil } -func getWorkerByName(name string) *worker { - for _, w := range workers { - if w.name == name { - return w - } - } - - return nil -} - -func getWorkerByPath(path string) *worker { - for _, w := range workers { - if w.fileName == path && w.allowPathMatching { - return w - } - } - - return nil -} func newWorker(o workerOpt) (*worker, error) { // Order is important! @@ -118,6 +107,7 @@ func newWorker(o workerOpt) (*worker, error) { if err != nil { return nil, fmt.Errorf("worker filename is invalid %q: %w", o.fileName, err) } + absFileName, err = fastabs.FastAbs(absFileName) if err != nil { return nil, fmt.Errorf("worker filename is invalid %q: %w", o.fileName, err) @@ -135,10 +125,10 @@ func newWorker(o workerOpt) (*worker, error) { // they can only be matched by their name, not by their path allowPathMatching := !strings.HasPrefix(o.name, "m#") - if w := getWorkerByPath(absFileName); w != nil && allowPathMatching { + if w := workersByPath[absFileName]; w != nil && allowPathMatching { return w, fmt.Errorf("two workers cannot have the same filename: %q", absFileName) } - if w := getWorkerByName(o.name); w != nil { + if w := workersByName[o.name]; w != nil { return w, fmt.Errorf("two workers cannot have the same name: %q", o.name) } From 49d738012a7d828f2ac9e15ba945a7f6d8836ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 11 Feb 2026 15:36:18 +0100 Subject: [PATCH 43/59] chore(caddy): modernize for Go 1.26 (#2183) --- caddy/module.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/caddy/module.go b/caddy/module.go index 332c45bc1c..e30535093b 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -108,8 +108,7 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { } else { f.Root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot) - var rrs bool - f.ResolveRootSymlink = &rrs + f.ResolveRootSymlink = new(false) } } else if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(f.Root) { f.Root = filepath.Join(frankenphp.EmbeddedAppPath, f.Root) @@ -126,8 +125,7 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { } if f.ResolveRootSymlink == nil { - rrs := true - f.ResolveRootSymlink = &rrs + f.ResolveRootSymlink = new(true) } // Always pre-compute absolute file names for fallback matching @@ -202,7 +200,6 @@ func needReplacement(s string) bool { // ServeHTTP implements caddyhttp.MiddlewareHandler. func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error { ctx := r.Context() - origReq := ctx.Value(caddyhttp.OriginalRequestCtxKey).(http.Request) repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) documentRoot := f.resolvedDocumentRoot @@ -243,7 +240,7 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c r, append( opts, - frankenphp.WithOriginalRequest(&origReq), + frankenphp.WithOriginalRequest(new(ctx.Value(caddyhttp.OriginalRequestCtxKey).(http.Request))), frankenphp.WithWorkerName(workerName), )..., ) @@ -480,8 +477,7 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) if phpsrv.Root == "" { phpsrv.Root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot) fsrv.Root = phpsrv.Root - rrs := false - phpsrv.ResolveRootSymlink = &rrs + phpsrv.ResolveRootSymlink = new(false) } else if filepath.IsLocal(fsrv.Root) { phpsrv.Root = filepath.Join(frankenphp.EmbeddedAppPath, phpsrv.Root) fsrv.Root = phpsrv.Root From 82d6696c9d152c0e5c52a56ebb159ad9bbc8a870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 11 Feb 2026 15:18:55 +0100 Subject: [PATCH 44/59] fix: race condition in thread shutdown during drain --- phpthread.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpthread.go b/phpthread.go index 40d04effa4..5ff15b645f 100644 --- a/phpthread.go +++ b/phpthread.go @@ -66,9 +66,11 @@ func (thread *phpThread) boot() { // shutdown the underlying PHP thread func (thread *phpThread) shutdown() { if !thread.state.RequestSafeStateChange(state.ShuttingDown) { - // already shutting down or done + // already shutting down or done, wait for the C thread to finish + thread.state.WaitFor(state.Done, state.Reserved) return } + close(thread.drainChan) thread.state.WaitFor(state.Done) thread.drainChan = make(chan struct{}) From e2062af083d665fac68344a09ee81fd45c22faba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 11 Feb 2026 15:42:57 +0100 Subject: [PATCH 45/59] chore: cs improvements --- caddy/admin.go | 6 ++++-- internal/state/state.go | 6 ++++-- phpthread.go | 9 +++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/caddy/admin.go b/caddy/admin.go index ec6d7d7c51..8515f11326 100644 --- a/caddy/admin.go +++ b/caddy/admin.go @@ -3,12 +3,14 @@ package caddy import ( "encoding/json" "fmt" + "net/http" + "github.com/caddyserver/caddy/v2" "github.com/dunglas/frankenphp" - "net/http" ) -type FrankenPHPAdmin struct{} +type FrankenPHPAdmin struct { +} // if the id starts with "admin.api" the module will register AdminRoutes via module.Routes() func (FrankenPHPAdmin) CaddyModule() caddy.ModuleInfo { diff --git a/internal/state/state.go b/internal/state/state.go index 62d14e503f..6457c2dde2 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -141,13 +141,15 @@ func (ts *ThreadState) notifySubscribers(nextState State) { ts.subscribers = ts.subscribers[:n] } -// block until the thread reaches a certain state +// WaitFor blocks until the thread reaches a certain state func (ts *ThreadState) WaitFor(states ...State) { ts.mu.Lock() if slices.Contains(states, ts.currentState) { ts.mu.Unlock() + return } + sub := stateSubscriber{ states: states, ch: make(chan struct{}), @@ -157,7 +159,7 @@ func (ts *ThreadState) WaitFor(states ...State) { <-sub.ch } -// safely request a state change from a different goroutine +// RequestSafeStateChange safely requests a state change from a different goroutine func (ts *ThreadState) RequestSafeStateChange(nextState State) bool { ts.mu.Lock() switch ts.currentState { diff --git a/phpthread.go b/phpthread.go index 5ff15b645f..90007cbe17 100644 --- a/phpthread.go +++ b/phpthread.go @@ -25,7 +25,7 @@ type phpThread struct { sandboxedEnv map[string]*C.zend_string } -// interface that defines how the callbacks from the C thread should be handled +// threadHandler defines how the callbacks from the C thread should be handled type threadHandler interface { name() string beforeScriptExecution() string @@ -68,6 +68,7 @@ func (thread *phpThread) shutdown() { if !thread.state.RequestSafeStateChange(state.ShuttingDown) { // already shutting down or done, wait for the C thread to finish thread.state.WaitFor(state.Done, state.Reserved) + return } @@ -81,17 +82,19 @@ func (thread *phpThread) shutdown() { } } -// change the thread handler safely +// setHandler changes the thread handler safely // must be called from outside the PHP thread func (thread *phpThread) setHandler(handler threadHandler) { thread.handlerMu.Lock() defer thread.handlerMu.Unlock() + if !thread.state.RequestSafeStateChange(state.TransitionRequested) { // no state change allowed == shutdown or done return } close(thread.drainChan) + thread.state.WaitFor(state.TransitionInProgress) thread.handler = handler thread.drainChan = make(chan struct{}) @@ -125,6 +128,7 @@ func (thread *phpThread) name() string { thread.handlerMu.RLock() name := thread.handler.name() thread.handlerMu.RUnlock() + return name } @@ -135,6 +139,7 @@ func (thread *phpThread) pinString(s string) *C.char { if sData == nil { return nil } + thread.Pin(sData) return (*C.char)(unsafe.Pointer(sData)) From 3aa71fd4282f017b8a953332548b0bf89950119c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 12 Feb 2026 10:58:02 +0100 Subject: [PATCH 46/59] chore: prepare release 1.11.2 --- caddy/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caddy/go.mod b/caddy/go.mod index a194bba17c..0711252655 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -10,7 +10,7 @@ require ( github.com/caddyserver/caddy/v2 v2.10.2 github.com/caddyserver/certmagic v0.25.1 github.com/dunglas/caddy-cbrotli v1.0.1 - github.com/dunglas/frankenphp v1.11.1 + github.com/dunglas/frankenphp v1.11.2 github.com/dunglas/mercure v0.21.8 github.com/dunglas/mercure/caddy v0.21.8 github.com/dunglas/vulcain/caddy v1.2.1 From 86539ffe34ff80ee5fd4207c6195492d83118303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 16 Feb 2026 11:53:56 +0100 Subject: [PATCH 47/59] ci: fix Docker builds --- .github/workflows/docker.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 5eaa3cda98..cbd4778033 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -191,8 +191,10 @@ jobs: - name: Run tests if: ${{ !fromJson(needs.prepare.outputs.push) }} run: | + # TODO: remove "containerimage.config.digest" fallback once all runners use buildx v0.18+ + # which replaced it with "containerimage.digest" and "containerimage.descriptor" docker run --platform="${PLATFORM}" --rm \ - "$(jq -r ".\"builder-${VARIANT}\".\"containerimage.config.digest\"" <<< "${METADATA}")" \ + "$(jq -r ".\"builder-${VARIANT}\" | .\"containerimage.config.digest\" // .\"containerimage.digest\"" <<< "${METADATA}")" \ sh -c "./go.sh test ${RACE} -v $(./go.sh list ./... | grep -v github.com/dunglas/frankenphp/internal/testext | grep -v github.com/dunglas/frankenphp/internal/extgen | tr '\n' ' ') && cd caddy && ../go.sh test ${RACE} -v ./..." env: METADATA: ${{ steps.build.outputs.metadata }} From f068912deebbcf59e93a7acda0c93380b13d2ecd Mon Sep 17 00:00:00 2001 From: Marc Date: Mon, 16 Feb 2026 20:45:49 +0700 Subject: [PATCH 48/59] Add restart policies to frankenphp service (#2191) interestingly Caddy doesn't have restart policies defined - we can't afford this as php may segfault --------- Signed-off-by: Marc --- package/alpine/frankenphp.openrc | 2 ++ package/debian/frankenphp.service | 2 ++ package/rhel/frankenphp.service | 2 ++ 3 files changed, 6 insertions(+) diff --git a/package/alpine/frankenphp.openrc b/package/alpine/frankenphp.openrc index aeca1743cf..be87e8c7f1 100755 --- a/package/alpine/frankenphp.openrc +++ b/package/alpine/frankenphp.openrc @@ -10,6 +10,8 @@ command_background="yes" capabilities="^cap_net_bind_service" pidfile="/run/frankenphp/frankenphp.pid" start_stop_daemon_args="--chdir /var/lib/frankenphp" +respawn_delay=3 +respawn_max=10 depend() { need net diff --git a/package/debian/frankenphp.service b/package/debian/frankenphp.service index 0b2dfe4a0f..f74303de1a 100644 --- a/package/debian/frankenphp.service +++ b/package/debian/frankenphp.service @@ -12,6 +12,8 @@ ExecStartPre=/usr/bin/frankenphp validate --config /etc/frankenphp/Caddyfile ExecStart=/usr/bin/frankenphp run --environ --config /etc/frankenphp/Caddyfile ExecReload=/usr/bin/frankenphp reload --config /etc/frankenphp/Caddyfile WorkingDirectory=/var/lib/frankenphp +Restart=on-failure +RestartSec=3s TimeoutStopSec=5s LimitNOFILE=1048576 LimitNPROC=512 diff --git a/package/rhel/frankenphp.service b/package/rhel/frankenphp.service index 0b2dfe4a0f..f74303de1a 100644 --- a/package/rhel/frankenphp.service +++ b/package/rhel/frankenphp.service @@ -12,6 +12,8 @@ ExecStartPre=/usr/bin/frankenphp validate --config /etc/frankenphp/Caddyfile ExecStart=/usr/bin/frankenphp run --environ --config /etc/frankenphp/Caddyfile ExecReload=/usr/bin/frankenphp reload --config /etc/frankenphp/Caddyfile WorkingDirectory=/var/lib/frankenphp +Restart=on-failure +RestartSec=3s TimeoutStopSec=5s LimitNOFILE=1048576 LimitNPROC=512 From 832b3b585e1f29d1761cc7c4fcbf561c44f03d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 16 Feb 2026 17:35:41 +0100 Subject: [PATCH 49/59] ci: fix static builds --- .github/workflows/static.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index 21bc9cf216..2feedbad62 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -179,7 +179,9 @@ jobs: - name: Copy binary run: | # shellcheck disable=SC2034 - digest=$(jq -r '."static-builder-musl"."${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}") + # TODO: remove "containerimage.config.digest" fallback once all runners use buildx v0.18+ + # which replaced it with "containerimage.digest" and "containerimage.descriptor" + digest=$(jq -r '."static-builder-musl" | ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '."containerimage.digest"' || '(."containerimage.config.digest" // ."containerimage.digest")' }}' <<< "${METADATA}") docker create --platform="${PLATFORM}" --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}" env: @@ -330,7 +332,9 @@ jobs: - name: Copy all frankenphp* files run: | # shellcheck disable=SC2034 - digest=$(jq -r '."static-builder-gnu"."${{ fromJson(needs.prepare.outputs.push) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}") + # TODO: remove "containerimage.config.digest" fallback once all runners use buildx v0.18+ + # which replaced it with "containerimage.digest" and "containerimage.descriptor" + digest=$(jq -r '."static-builder-gnu" | ${{ fromJson(needs.prepare.outputs.push) && '."containerimage.digest"' || '(."containerimage.config.digest" // ."containerimage.digest")' }}' <<< "${METADATA}") container_id=$(docker create --platform="${PLATFORM}" "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}") mkdir -p gh-output cd gh-output From 0febfae509a8228b4d96010b4b4aefc18f5ffb1c Mon Sep 17 00:00:00 2001 From: Marc Date: Wed, 18 Feb 2026 07:54:48 +0700 Subject: [PATCH 50/59] fix relative embed paths early enough (#2199) fix #1164 --- build-static.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/build-static.sh b/build-static.sh index 638688c0a2..068cef7019 100755 --- a/build-static.sh +++ b/build-static.sh @@ -147,6 +147,13 @@ else spcCommand="./bin/spc" fi +# turn potentially relative EMBED path into absolute path +if [ -n "${EMBED}" ]; then + if [[ "${EMBED}" != /* ]]; then + EMBED="${CURRENT_DIR}/${EMBED}" + fi +fi + # Extensions to build if [ -z "${PHP_EXTENSIONS}" ]; then # enable EMBED mode, first check if project has dumped extensions @@ -178,9 +185,6 @@ fi # Embed PHP app, if any if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then - if [[ "${EMBED}" != /* ]]; then - EMBED="${CURRENT_DIR}/${EMBED}" - fi # shellcheck disable=SC2089 SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app='${EMBED}'" fi From abba64b0ff1a215b5db89ca690f50d5ffa5220c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 18 Feb 2026 14:20:56 +0100 Subject: [PATCH 51/59] chore: fix golangci-lint --- internal/extgen/paramparser.go | 2 +- internal/extgen/phpfunc.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/extgen/paramparser.go b/internal/extgen/paramparser.go index 3f298904be..6dcc365e3a 100644 --- a/internal/extgen/paramparser.go +++ b/internal/extgen/paramparser.go @@ -92,7 +92,7 @@ func (pp *ParameterParser) generateParamParsing(params []phpParameter, requiredC } var builder strings.Builder - builder.WriteString(fmt.Sprintf(" ZEND_PARSE_PARAMETERS_START(%d, %d)", requiredCount, len(params))) + _, _ = fmt.Fprintf(&builder, " ZEND_PARSE_PARAMETERS_START(%d, %d)", requiredCount, len(params)) optionalStarted := false for _, param := range params { diff --git a/internal/extgen/phpfunc.go b/internal/extgen/phpfunc.go index 1e11172a20..66a0123eeb 100644 --- a/internal/extgen/phpfunc.go +++ b/internal/extgen/phpfunc.go @@ -16,7 +16,7 @@ func (pfg *PHPFuncGenerator) generate(fn phpFunction) string { paramInfo := pfg.paramParser.analyzeParameters(fn.Params) funcName := NamespacedName(pfg.namespace, fn.Name) - builder.WriteString(fmt.Sprintf("PHP_FUNCTION(%s)\n{\n", funcName)) + _, _ = fmt.Fprintf(&builder, "PHP_FUNCTION(%s)\n{\n", funcName) if decl := pfg.paramParser.generateParamDeclarations(fn.Params); decl != "" { builder.WriteString(decl + "\n") From 151ea1c87da406b454179f116b21e81f7708dbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 18 Feb 2026 15:26:31 +0100 Subject: [PATCH 52/59] test(caddy): fix flaky tests on macOS --- caddy/caddy_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/caddy/caddy_test.go b/caddy/caddy_test.go index 4233fc3111..57bfde42b2 100644 --- a/caddy/caddy_test.go +++ b/caddy/caddy_test.go @@ -11,6 +11,7 @@ import ( "sync" "sync/atomic" "testing" + "time" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddytest" @@ -19,6 +20,26 @@ import ( "github.com/stretchr/testify/require" ) +// waitForServerReady polls the server with retries until it responds to HTTP requests. +// This handles a race condition during Caddy config reload on macOS where SO_REUSEPORT +// can briefly route connections to the old listener being shut down, +// resulting in "connection reset by peer". +func waitForServerReady(t *testing.T, url string) { + t.Helper() + + client := &http.Client{Timeout: 1 * time.Second} + for range 10 { + resp, err := client.Get(url) + if err == nil { + require.NoError(t, resp.Body.Close()) + + return + } + + time.Sleep(100 * time.Millisecond) + } +} + var testPort = "9080" func TestPHP(t *testing.T) { @@ -406,6 +427,7 @@ func TestPHPServerDirective(t *testing.T) { } `, "caddyfile") + waitForServerReady(t, "http://localhost:"+testPort) tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)") tester.AssertGetResponse("http://localhost:"+testPort+"/hello.txt", http.StatusOK, "Hello\n") tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)") @@ -431,6 +453,7 @@ func TestPHPServerDirectiveDisableFileServer(t *testing.T) { } `, "caddyfile") + waitForServerReady(t, "http://localhost:"+testPort) tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)") tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)") } From bb3f7308f2d01db07dfdc5b9997497e9f666fd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 17 Feb 2026 14:42:30 +0100 Subject: [PATCH 53/59] feat: allow to customize EmbeddedAppPath --- embed.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/embed.go b/embed.go index b99ae6a7b9..33c0118148 100644 --- a/embed.go +++ b/embed.go @@ -17,7 +17,15 @@ import ( "time" ) -// EmbeddedAppPath contains the path of the embedded PHP application (empty if none) +// EmbeddedAppPath contains the path of the embedded PHP application (empty if none). +// It can be set at build time using -ldflags to override the default extraction path: +// +// go build -ldflags "-X github.com/dunglas/frankenphp.EmbeddedAppPath=/app" ... +// +// When set, the embedded app is extracted to this fixed path instead of a temp +// directory with a checksum suffix. This is useful when the app contains +// pre-compiled artifacts (e.g. OPcache file cache) that reference absolute paths +// and need a predictable extraction location. var EmbeddedAppPath string //go:embed app.tar @@ -32,14 +40,14 @@ func init() { return } - appPath := filepath.Join(os.TempDir(), "frankenphp_"+string(embeddedAppChecksum)) + if EmbeddedAppPath == "" { + EmbeddedAppPath = filepath.Join(os.TempDir(), "frankenphp_"+string(embeddedAppChecksum)) + } - if err := untar(appPath); err != nil { - _ = os.RemoveAll(appPath) + if err := untar(EmbeddedAppPath); err != nil { + _ = os.RemoveAll(EmbeddedAppPath) panic(err) } - - EmbeddedAppPath = appPath } // untar reads the tar file from r and writes it into dir. From 7c563d2567e945607b14f670b8706555dbe5239f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 19 Feb 2026 10:15:45 +0100 Subject: [PATCH 54/59] chore: minor cleanup in Caddy module tests (#2202) --- caddy/caddy_test.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/caddy/caddy_test.go b/caddy/caddy_test.go index 57bfde42b2..a9fef03efd 100644 --- a/caddy/caddy_test.go +++ b/caddy/caddy_test.go @@ -512,7 +512,9 @@ func TestMetrics(t *testing.T) { // Fetch metrics resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) @@ -585,7 +587,9 @@ func TestWorkerMetrics(t *testing.T) { // Fetch metrics resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) @@ -677,7 +681,9 @@ func TestNamedWorkerMetrics(t *testing.T) { // Fetch metrics resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) @@ -768,7 +774,9 @@ func TestAutoWorkerConfig(t *testing.T) { // Fetch metrics resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) @@ -927,8 +935,9 @@ func testSingleIniConfiguration(tester *caddytest.Tester, key string, value stri } func TestOsEnv(t *testing.T) { - os.Setenv("ENV1", "value1") - os.Setenv("ENV2", "value2") + require.NoError(t, os.Setenv("ENV1", "value1")) + require.NoError(t, os.Setenv("ENV2", "value2")) + tester := caddytest.NewTester(t) tester.InitServer(` { @@ -1073,9 +1082,11 @@ func TestMaxWaitTimeWorker(t *testing.T) { func getStatusCode(url string, t *testing.T) int { req, err := http.NewRequest("GET", url, nil) require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) require.NoError(t, err) - defer resp.Body.Close() + require.NoError(t, resp.Body.Close()) + return resp.StatusCode } @@ -1134,7 +1145,9 @@ func TestMultiWorkersMetrics(t *testing.T) { // Fetch metrics resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) @@ -1240,7 +1253,9 @@ func TestDisabledMetrics(t *testing.T) { // Fetch metrics resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) @@ -1299,7 +1314,9 @@ func TestWorkerRestart(t *testing.T) { resp, err := http.Get("http://localhost:2999/metrics") require.NoError(t, err, "failed to fetch metrics") - defer resp.Body.Close() + t.Cleanup(func() { + require.NoError(t, resp.Body.Close()) + }) // Read and parse metrics metrics := new(bytes.Buffer) From b02d99ae8a958efa9707f47164e8cfcc07876c67 Mon Sep 17 00:00:00 2001 From: Alexander Stecher <45872305+AlliBalliBaba@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:55:39 +0100 Subject: [PATCH 55/59] feat: always ignore user abort (#2189) Automatically sets `ignore_user_abort` to true in worker mode as mentioned in #2186, removing the requirement to change it via ini. Would also be possible to expose something like an explicit `frankenphp_client_has_closed()` function for in-between critical sections. --------- Co-authored-by: Marc --- docs/worker.md | 3 --- frankenphp.c | 6 ++++++ worker_test.go | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/worker.md b/docs/worker.md index f344becacd..f952c305c5 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -72,9 +72,6 @@ The following example shows how to create your own worker script without relying Date: Fri, 20 Feb 2026 21:16:21 +0700 Subject: [PATCH 56/59] Update static binary PHP version to 8.5 (#2168) Signed-off-by: Marc --- build-static.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-static.sh b/build-static.sh index 068cef7019..a507e722b9 100755 --- a/build-static.sh +++ b/build-static.sh @@ -72,11 +72,11 @@ if [ -z "${PHP_VERSION}" ]; then fi } - PHP_VERSION="$(get_latest_php_version "8.4")" + PHP_VERSION="$(get_latest_php_version "8.5")" export PHP_VERSION fi # default extension set -defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcache,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xsl,xz,zip,zlib,yaml,zstd" +defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xsl,xz,zip,zlib,yaml,zstd" defaultExtensionLibs="libavif,nghttp2,nghttp3,ngtcp2,watcher" if [ -z "${FRANKENPHP_VERSION}" ]; then From 2bdf85866c9262111c33081e07c261f21a28de3e Mon Sep 17 00:00:00 2001 From: Marc Date: Sat, 21 Feb 2026 23:29:47 +0700 Subject: [PATCH 57/59] Upgrade to Cady v2.11.1 (#2214) fixes failing integration tests and closes https://github.com/php/frankenphp/issues/2213 --- caddy/go.mod | 52 ++++++++++++------- caddy/go.sum | 124 +++++++++++++++++++++++++++++--------------- caddy/php-server.go | 2 +- go.mod | 1 + go.sum | 4 +- 5 files changed, 120 insertions(+), 63 deletions(-) diff --git a/caddy/go.mod b/caddy/go.mod index 0711252655..bd6d7b8125 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -7,8 +7,8 @@ replace github.com/dunglas/frankenphp => ../ retract v1.0.0-rc.1 // Human error require ( - github.com/caddyserver/caddy/v2 v2.10.2 - github.com/caddyserver/certmagic v0.25.1 + github.com/caddyserver/caddy/v2 v2.11.1 + github.com/caddyserver/certmagic v0.25.2 github.com/dunglas/caddy-cbrotli v1.0.1 github.com/dunglas/frankenphp v1.11.2 github.com/dunglas/mercure v0.21.8 @@ -23,27 +23,29 @@ require github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca require ( cel.dev/expr v0.25.1 // indirect - cloud.google.com/go/auth v0.18.1 // indirect + cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.2 // indirect - filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/bigmod v0.1.0 // indirect + filippo.io/edwards25519 v1.2.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/BurntSushi/toml v1.6.0 // indirect + github.com/DeRuina/timberjack v1.3.9 // indirect github.com/KimMachineGun/automemlimit v0.7.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect github.com/MicahParks/jwkset v0.11.0 // indirect - github.com/MicahParks/keyfunc/v3 v3.7.0 // indirect + github.com/MicahParks/keyfunc/v3 v3.8.0 // indirect github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect github.com/alecthomas/chroma/v2 v2.23.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect - github.com/caddyserver/zerossl v0.1.4 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -92,7 +94,7 @@ require ( github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -110,7 +112,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/maypok86/otter/v2 v2.3.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/mholt/acmez/v3 v3.1.4 // indirect + github.com/mholt/acmez/v3 v3.1.6 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect @@ -128,6 +130,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect @@ -138,8 +141,8 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect - github.com/slackhq/nebula v1.9.7 // indirect - github.com/smallstep/certificates v0.29.0 // indirect + github.com/slackhq/nebula v1.10.3 // indirect + github.com/smallstep/certificates v0.30.0-rc2.0.20260211214201-20608299c29c // indirect github.com/smallstep/cli-utils v0.12.2 // indirect github.com/smallstep/linkedca v0.25.0 // indirect github.com/smallstep/nosql v0.7.0 // indirect @@ -167,6 +170,8 @@ require ( github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect go.opentelemetry.io/contrib/propagators/autoprop v0.65.0 // indirect go.opentelemetry.io/contrib/propagators/aws v1.40.0 // indirect @@ -174,13 +179,25 @@ require ( go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 // indirect go.opentelemetry.io/contrib/propagators/ot v1.40.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect + go.opentelemetry.io/otel/log v0.16.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.step.sm/crypto v0.76.0 // indirect + go.step.sm/crypto v0.76.2 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect @@ -188,8 +205,8 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e // indirect - golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20260213171211-a408498e5541 // indirect + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect @@ -199,13 +216,12 @@ require ( golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.42.0 // indirect - google.golang.org/api v0.266.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/api v0.267.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect + google.golang.org/grpc v1.79.1 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 // indirect google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect howett.net/plist v1.0.1 // indirect ) diff --git a/caddy/go.sum b/caddy/go.sum index cacf4b0d39..fc0fc23ba6 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -1,29 +1,35 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= -cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= -cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= +cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/kms v1.24.0 h1:SWltUuoPhTdv9q/P0YEAWQfoYT32O5HdfPgTiWMvrH8= -cloud.google.com/go/kms v1.24.0/go.mod h1:QDH3z2SJ50lfNOE8EokKC1G40i7I0f8xTMCoiptcb5g= -cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= -cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/kms v1.25.0 h1:gVqvGGUmz0nYCmtoxWmdc1wli2L1apgP8U4fghPGSbQ= +cloud.google.com/go/kms v1.25.0/go.mod h1:XIdHkzfj0bUO3E+LvwPg+oc7s58/Ns8Nd8Sdtljihbk= +cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= +cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8= +filippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo= +github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE= github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk= github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -36,8 +42,8 @@ github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg= github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ= github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= -github.com/MicahParks/keyfunc/v3 v3.7.0 h1:pdafUNyq+p3ZlvjJX1HWFP7MA3+cLpDtg69U3kITJGM= -github.com/MicahParks/keyfunc/v3 v3.7.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= +github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8guNBfds= +github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw= @@ -89,12 +95,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8= -github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0= -github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY= -github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY= -github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw= -github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/caddyserver/caddy/v2 v2.11.1 h1:C7sQpsFOC5CH+31KqJc7EoOf8mXrOEkFyYd6GpIqm/s= +github.com/caddyserver/caddy/v2 v2.11.1/go.mod h1:EOKnXuSSGlq2SuItwQuEVIsY5bRRi7tPJNHDm99XQXo= +github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= +github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0= github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -163,6 +169,8 @@ github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a h1:e/m9m github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -238,8 +246,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 h1:NpbJl/eVbvrGE0MJ6X16X9SAifesl6Fwxg/YmCvubRI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8/go.mod h1:mi7YA+gCzVem12exXy46ZespvGtX/lZmD/RLnQhVW7U= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= @@ -274,6 +282,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= +github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -289,8 +301,8 @@ github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs github.com/maypok86/otter/v2 v2.3.0/go.mod h1:XgIdlpmL6jYz882/CAx1E4C1ukfgDKSaw4mWq59+7l8= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= -github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -336,6 +348,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= +github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= @@ -361,12 +375,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/slackhq/nebula v1.9.7 h1:v5u46efIyYHGdfjFnozQbRRhMdaB9Ma1SSTcUcE2lfE= -github.com/slackhq/nebula v1.9.7/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ= +github.com/slackhq/nebula v1.10.3 h1:EstYj8ODEcv6T0R9X5BVq1zgWZnyU5gtPzk99QF1PMU= +github.com/slackhq/nebula v1.10.3/go.mod h1:IL5TUQm4x9IFx2kCKPYm1gP47pwd5b8QGnnBH2RHnvs= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= -github.com/smallstep/certificates v0.29.0 h1:f90szTKYTW62bmCc+qE5doGqIGPVxTQb8Ba37e/K8Zs= -github.com/smallstep/certificates v0.29.0/go.mod h1:27WI0od6gu84mvE4mYQ/QZGyYwHXvhsiSRNC+y3t+mo= +github.com/smallstep/certificates v0.30.0-rc2.0.20260211214201-20608299c29c h1:XQpX0IPYUAoJ661YlgfOJmY48ZOhIbglw4E2gw9mcyc= +github.com/smallstep/certificates v0.30.0-rc2.0.20260211214201-20608299c29c/go.mod h1:75NRLmYJq6ZcCb8ApJc+W1eL4oMYwjeufMJDHpv4rx4= github.com/smallstep/cli-utils v0.12.2 h1:lGzM9PJrH/qawbzMC/s2SvgLdJPKDWKwKzx9doCVO+k= github.com/smallstep/cli-utils v0.12.2/go.mod h1:uCPqefO29goHLGqFnwk0i8W7XJu18X3WHQFRtOm/00Y= github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4= @@ -462,6 +476,10 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= +go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= +go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= +go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= @@ -478,22 +496,46 @@ go.opentelemetry.io/contrib/propagators/ot v1.40.0 h1:Lon8J5SPmWaL1Ko2TIlCNHJ42/ go.opentelemetry.io/contrib/propagators/ot v1.40.0/go.mod h1:dKWtJTlp1Yj+8Cneye5idO46eRPIbi23qVuJYKjNnvY= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= +go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= +go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= +go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= +go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= -go.step.sm/crypto v0.76.0 h1:K23BSaeoiY7Y5dvvijTeYC9EduDBetNwQYMBwMhi1aA= -go.step.sm/crypto v0.76.0/go.mod h1:PXYJdKkK8s+GHLwLguFaLxHNAFsFL3tL1vSBrYfey5k= +go.step.sm/crypto v0.76.2 h1:JJ/yMcs/rmcCAwlo+afrHjq74XBFRTJw5B2y4Q4Z4c4= +go.step.sm/crypto v0.76.2/go.mod h1:m6KlB/HzIuGFep0UWI5e0SYi38UxpoKeCg6qUaHV6/Q= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -519,10 +561,10 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e h1:G2pNJMnWEb60ip90TMo++m9fpt+d3YIZa0er3buPKRo= -golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= -golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= -golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260213171211-a408498e5541 h1:FmKxj9ocLKn45jiR2jQMwCVhDvaK7fKQFzfuT9GvyK8= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260213171211-a408498e5541/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -604,16 +646,16 @@ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk= -google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= +google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= -google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s= +google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= @@ -622,8 +664,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/caddy/php-server.go b/caddy/php-server.go index 612e4c5ecd..6b66756c7f 100644 --- a/caddy/php-server.go +++ b/caddy/php-server.go @@ -113,7 +113,7 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) { } if _, err := os.Stat("Caddyfile"); err == nil { - config, _, err := caddycmd.LoadConfig("Caddyfile", "caddyfile") + config, _, _, err := caddycmd.LoadConfig("Caddyfile", "caddyfile") if err != nil { return caddy.ExitCodeFailedStartup, err } diff --git a/go.mod b/go.mod index 3d3510bac7..40798825a7 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect diff --git a/go.sum b/go.sum index bb6259131d..0053b53351 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= From 755db861168341e6295e4bf3b649c68aa37e75cc Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Sat, 21 Feb 2026 17:34:35 +0100 Subject: [PATCH 58/59] metrics: only report workers ready when actually ready (#2210) In #2205 it appears that workers could be reported in metrics as "ready" before they are actually ready. This changes the reporting so that workers are only reported ready once they have completed booting. Signed-off-by: Robert Landers --- metrics.go | 10 +++++++--- threadworker.go | 17 ++++++++--------- worker.go | 1 - 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/metrics.go b/metrics.go index 4de6d5e4b7..b6b4ca119a 100644 --- a/metrics.go +++ b/metrics.go @@ -11,7 +11,7 @@ import ( const ( StopReasonCrash = iota StopReasonRestart - //StopReasonShutdown + StopReasonBootFailure // worker crashed before reaching frankenphp_handle_request ) type StopReason int @@ -125,10 +125,14 @@ func (m *PrometheusMetrics) StopWorker(name string, reason StopReason) { } m.totalWorkers.WithLabelValues(name).Dec() - m.readyWorkers.WithLabelValues(name).Dec() + + // only decrement readyWorkers if the worker actually reached frankenphp_handle_request + if reason != StopReasonBootFailure { + m.readyWorkers.WithLabelValues(name).Dec() + } switch reason { - case StopReasonCrash: + case StopReasonCrash, StopReasonBootFailure: m.workerCrashes.WithLabelValues(name).Inc() case StopReasonRestart: m.workerRestarts.WithLabelValues(name).Inc() diff --git a/threadworker.go b/threadworker.go index ae7e4545f2..e309340a7b 100644 --- a/threadworker.go +++ b/threadworker.go @@ -101,10 +101,6 @@ func (handler *workerThread) name() string { func setupWorkerScript(handler *workerThread, worker *worker) { metrics.StartWorker(worker.name) - if handler.state.Is(state.Ready) { - metrics.ReadyWorker(handler.worker.name) - } - // Create a dummy request to set up the worker fc, err := newDummyContext( filepath.Base(worker.fileName), @@ -152,7 +148,11 @@ func tearDownWorkerScript(handler *workerThread, exitStatus int) { } // worker has thrown a fatal error or has not reached frankenphp_handle_request - metrics.StopWorker(worker.name, StopReasonCrash) + if handler.isBootingScript { + metrics.StopWorker(worker.name, StopReasonBootFailure) + } else { + metrics.StopWorker(worker.name, StopReasonCrash) + } if !handler.isBootingScript { // fatal error (could be due to exit(1), timeouts, etc.) @@ -207,13 +207,12 @@ func (handler *workerThread) waitForWorkerRequest() (bool, any) { if !C.frankenphp_shutdown_dummy_request() { panic("Not in CGI context") } + + // worker is truly ready only after reaching frankenphp_handle_request() + metrics.ReadyWorker(handler.worker.name) } - // worker threads are 'ready' after they first reach frankenphp_handle_request() - // 'state.TransitionComplete' is only true on the first boot of the worker script, - // while 'isBootingScript' is true on every boot of the worker script if handler.state.Is(state.TransitionComplete) { - metrics.ReadyWorker(handler.worker.name) handler.state.Set(state.Ready) } diff --git a/worker.go b/worker.go index edba017218..d87848bad2 100644 --- a/worker.go +++ b/worker.go @@ -98,7 +98,6 @@ func initWorkers(opt []workerOpt) error { return nil } - func newWorker(o workerOpt) (*worker, error) { // Order is important! // This order ensures that FrankenPHP started from inside a symlinked directory will properly resolve any paths. From 6d86ea84bc48e32c90735a890c5e7ccd8a5de6c6 Mon Sep 17 00:00:00 2001 From: Rob Landers Date: Sat, 21 Feb 2026 17:38:51 +0100 Subject: [PATCH 59/59] chore: run go fmt (#2211) A few files were not formatted correctly. Signed-off-by: Robert Landers Co-authored-by: Marc --- cgi_test.go | 2 +- internal/fastabs/filepath_unix.go | 4 ++-- internal/state/state.go | 2 +- internal/testserver/main.go | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cgi_test.go b/cgi_test.go index d7c0e854d7..c4c7a7701c 100644 --- a/cgi_test.go +++ b/cgi_test.go @@ -25,7 +25,7 @@ func TestEnsureLeadingSlash(t *testing.T) { } for _, tt := range tests { - t.Run(tt.input + "-" + tt.expected, func(t *testing.T) { + t.Run(tt.input+"-"+tt.expected, func(t *testing.T) { t.Parallel() assert.Equal(t, tt.expected, ensureLeadingSlash(tt.input), "ensureLeadingSlash(%q)", tt.input) diff --git a/internal/fastabs/filepath_unix.go b/internal/fastabs/filepath_unix.go index 47fbc47d68..38ed07c75b 100644 --- a/internal/fastabs/filepath_unix.go +++ b/internal/fastabs/filepath_unix.go @@ -6,8 +6,9 @@ import ( "os" "path/filepath" ) + var ( - wd string + wd string wderr error ) @@ -38,4 +39,3 @@ func FastAbs(path string) (string, error) { return filepath.Join(wd, path), nil } - diff --git a/internal/state/state.go b/internal/state/state.go index 6457c2dde2..7bdf9c064a 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -12,7 +12,7 @@ type State int const ( // lifecycle States of a thread - Reserved State = iota + Reserved State = iota Booting BootRequested ShuttingDown diff --git a/internal/testserver/main.go b/internal/testserver/main.go index 249be64737..a28e408eeb 100644 --- a/internal/testserver/main.go +++ b/internal/testserver/main.go @@ -13,7 +13,6 @@ func main() { ctx := context.Background() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) - if err := frankenphp.Init(frankenphp.WithContext(ctx), frankenphp.WithLogger(logger)); err != nil { panic(err) }
Release notes

Sourced from actions/upload-artifact's releases.

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0