Skip to content

Commit 9fd97bc

Browse files
authored
Merge pull request #16 from zelti/feat/php-versions-selectable
Pick the PHP versions you install, like the databases
2 parents 2f0fa85 + 702d6f6 commit 9fd97bc

17 files changed

Lines changed: 561 additions & 48 deletions

File tree

‎.env.example‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,24 @@ USERNAME=${USER}
2222
USER_DEV=php-devforge
2323
PASSWD_DEV=php-devforge01
2424

25-
##### DATABASES AND EXTRAS #####
25+
##### DATABASES, PHP VERSIONS AND EXTRAS #####
2626
# Comma separated. Nothing here starts unless listed. Manage with:
2727
# forge db list forge db on pg18 forge mail on
28-
# Available: pg16 pg17 pg18 mariadb11 mariadb12 mail search nginx
29-
COMPOSE_PROFILES=
28+
# forge php list forge php on 8.3
29+
# Available: php83 php84 php85 pg16 pg17 pg18 mariadb11 mariadb12 mail search nginx
30+
#
31+
# The default version below must be listed here, or nothing answers a plain
32+
# host name. `forge php off` refuses to remove it for that reason.
33+
COMPOSE_PROFILES=php84
3034

3135
##### LOCAL DNS PORT #####
3236
# Port on 127.0.0.1 where dnsmasq listens. Not 53: it is usually taken
3337
# (systemd-resolved, Pi-hole). Change it if 5354 is busy on your machine.
3438
DNS_PORT=5354
3539

3640
##### DEFAULT PHP VERSION #####
41+
# Answers host names with no --pNN suffix. Change it with `forge use 8.5`,
42+
# which also installs that version if you do not have it yet.
3743
PHP_VERSION=84
3844

3945
##### HOST USER #####
44 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

‎.github/scripts/drive-install.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222

2323
# What to answer, in order: (text that means the menu is up, what to pick).
2424
# A single-choice menu takes one name; a multi-choice menu takes a list.
25+
# 84 comes preselected, so naming it here toggles it off: the run installs 8.3
26+
# and 8.5 only, which is the point of picking versions at all.
2527
STEPS = [
26-
("Default PHP version", "85"),
28+
("PHP versions", ["84", "83", "85"]),
29+
("Default version", "85"),
2730
("Images", "pull"),
2831
("space toggles", ["pg18", "mail"]),
2932
]

‎.github/workflows/ci.yml‎

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ jobs:
8686
# reproduce the bug: --profiles bypassed validation entirely, so this
8787
# passed even while every typed name was being rejected.
8888
./install.sh --yes --skip-dns --skip-cert --skip-link --profiles=pg18,mail >/dev/null
89-
grep -q '^COMPOSE_PROFILES=pg18,mail$' .env
89+
# The default PHP version leads the line: versions are profiles too.
90+
grep -q '^COMPOSE_PROFILES=php84,pg18,mail$' .env
9091
rm -f .env .env.backup
9192
9293
# a name that does not must stop a scripted install, not write a dead profile
@@ -108,7 +109,9 @@ jobs:
108109
./install.sh --skip-cert --skip-dns --skip-link --domain=phpforge.dev
109110
grep -q '^PHP_VERSION=85$' .env # moved off the default 8.4
110111
grep -q '^IMAGE_MODE=missing$' .env # kept 'pull'
111-
grep -q '^COMPOSE_PROFILES=pg18,mail$' .env
112+
# 8.4 was toggled off and 8.3/8.5 on, so the versions are a choice and
113+
# the second question decided which of them answers a plain host name.
114+
grep -q '^COMPOSE_PROFILES=php83,php85,pg18,mail$' .env
112115
rm -f .env .env.backup
113116
echo "the menus work end to end"
114117
@@ -137,7 +140,26 @@ jobs:
137140
# --skip-dns: the runner's resolver is left alone; curl --resolve is used
138141
# instead. Certificates are not skipped, so real TLS is exercised.
139142
- name: run the installer
140-
run: ./install.sh --yes --skip-dns
143+
run: ./install.sh --yes --skip-dns --php=84
144+
145+
# Every version used to be installed whether or not you wanted it: 1.9 GB
146+
# each. What is not picked must not reach the default set at all.
147+
- name: only the PHP versions you picked are installed
148+
run: |
149+
# Not an exact match on the line: --yes also accepts the mail catcher.
150+
grep -qE '^COMPOSE_PROFILES=php84(,|$)' .env \
151+
|| { grep '^COMPOSE_PROFILES=' .env; exit 1; }
152+
if grep '^COMPOSE_PROFILES=' .env | grep -qE 'php(83|85)'; then
153+
echo "a version nobody asked for was enabled"
154+
grep '^COMPOSE_PROFILES=' .env; exit 1
155+
fi
156+
for s in php83dev php85dev; do
157+
if docker compose config --services | grep -qx "$s"; then
158+
echo "$s starts by default and should not"; exit 1
159+
fi
160+
done
161+
docker compose config --services | grep -qx php84dev
162+
echo "one version picked, one version installed"
141163
142164
- name: .env was written
143165
run: |
@@ -152,7 +174,7 @@ jobs:
152174
test -L "$HOME/.local/bin/forge" || { echo "installer did not link forge"; exit 1; }
153175
export PATH="$HOME/.local/bin:$PATH"
154176
cd /tmp # it must work from anywhere, not just the checkout
155-
forge help | grep -q "8.3 8.4 8.5" || { echo "versions not discovered"; exit 1; }
177+
forge help | grep -q "out of 8.3, 8.4, 8.5" || { echo "versions not discovered"; exit 1; }
156178
forge status | grep -q "PHP DevForge"
157179
forge images status
158180
# unknown versions must be refused, not passed to docker
@@ -164,6 +186,21 @@ jobs:
164186
# built-in and docker-php-ext-install started failing on it.
165187
run: docker compose build php83dev php84dev php85dev apachedev
166188

189+
# The rest of the run needs all three. Adding them through forge rather than
190+
# by naming services proves the command people are told to use, and keeps
191+
# .env and the containers agreeing -- Apache reads COMPOSE_PROFILES to know
192+
# what exists.
193+
- name: forge php on adds a version
194+
run: |
195+
export PATH="$HOME/.local/bin:$PATH"
196+
forge php on 8.3
197+
forge php on 8.5
198+
for v in 83 84 85; do
199+
grep -q "^COMPOSE_PROFILES=.*php$v" .env || { grep '^COMPOSE_PROFILES=' .env; exit 1; }
200+
done
201+
forge php list | grep -qE '^\s+8\.4\s+ON\s+default'
202+
echo "versions added"
203+
167204
- name: start the stack
168205
run: |
169206
docker compose up -d apachedev php83dev php84dev php85dev
@@ -255,6 +292,63 @@ jobs:
255292
|| { echo "expected PHP ${v:0:1}.${v:1:1}"; exit 1; }
256293
done
257294
295+
# A version that is not installed used to proxy to a host that does not
296+
# resolve: a bare 503, indistinguishable from a typo. The trap puts 8.3 back
297+
# however this ends, because every step after this one uses it.
298+
- name: an uninstalled version says so instead of failing blankly
299+
run: |
300+
export PATH="$HOME/.local/bin:$PATH"
301+
trap 'forge php on 8.3 >/dev/null 2>&1 || true' EXIT
302+
303+
# the default cannot be removed: nothing would answer a plain host name
304+
out="$(forge php off 8.4 2>&1)" && { echo "removing the default was allowed"; exit 1; }
305+
printf '%s\n' "$out" | grep -q 'forge use' || { echo "$out"; exit 1; }
306+
307+
forge php off 8.3
308+
docker compose ps --services | grep -qx php83dev \
309+
&& { echo "php83dev still running"; exit 1; }
310+
311+
host=my-app--sites--p83.phpforge.dev
312+
code="$(curl -sk -o /tmp/gone.html -w '%{http_code}' \
313+
--resolve "$host:443:127.0.0.1" "https://$host/")"
314+
test "$code" = 503 || { echo "expected 503, got $code"; exit 1; }
315+
# "/" reaches PHP through DirectoryIndex, which is where an earlier
316+
# version of this page was swallowed and served as an empty 200.
317+
grep -q 'PHP 8.3 is not installed' /tmp/gone.html || { cat /tmp/gone.html; exit 1; }
318+
grep -q 'forge php on 8.3' /tmp/gone.html || { cat /tmp/gone.html; exit 1; }
319+
echo "it explains itself"
320+
321+
forge php on 8.3
322+
# -k here only: TLS trust is what several other steps are for, and this
323+
# way the step also runs on a machine that skipped ./install_cert.sh.
324+
for i in $(seq 1 30); do
325+
curl -fsSk --max-time 5 --resolve "$host:443:127.0.0.1" "https://$host/" \
326+
| grep -q 'PHP 8.3' && { echo "8.3 serves again after ${i}s"; exit 0; }
327+
sleep 1
328+
done
329+
echo "8.3 never came back"; exit 1
330+
331+
# Upgrading is a git pull, and a .env written before task 41 lists no php
332+
# profile at all. Dropping to one version would silently break every --pNN
333+
# host that person had.
334+
- name: an older .env keeps the versions it had
335+
run: |
336+
export PATH="$HOME/.local/bin:$PATH"
337+
cp .env /tmp/env.keep
338+
trap 'cp /tmp/env.keep .env' EXIT
339+
sed -i 's/^COMPOSE_PROFILES=.*/COMPOSE_PROFILES=/' .env
340+
341+
out="$(forge status)"
342+
printf '%s\n' "$out" | grep -q 'PHP versions are now picked' \
343+
|| { echo "$out"; echo "no notice printed"; exit 1; }
344+
grep -q '^COMPOSE_PROFILES=php83,php84,php85$' .env \
345+
|| { grep '^COMPOSE_PROFILES=' .env; exit 1; }
346+
347+
# and it says it once: the write is the migration
348+
forge status | grep -q 'PHP versions are now picked' \
349+
&& { echo "the notice repeats"; exit 1; }
350+
echo "older .env kept its versions"
351+
258352
# Neither is installed by us any more: the base images already provide them.
259353
# If that ever stops being true, this catches it instead of a user finding out.
260354
- name: opcache and mbstring are present in every version

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This is the core mechanism and it spans three files.
4141

4242
1. **DNS** — `dnsmasq` resolves `*.${DEV_DOMAIN}` (default `phpforge.dev`) to `127.0.0.1`. `setup-local-dns.sh` inserts `127.0.0.1` as the system's first nameserver (NetworkManager → systemd-resolved → raw `resolv.conf`, in that order of preference).
4343
2. **Docroot resolution** — `docker-library/httpd/config_files/resolve_docroot.lua` runs as `LuaHookTranslateName`. It strips the `--pNN` suffix and the base domain from the Host header, splits the remainder on `--` (or on `.`), **reverses the segments**, and joins with `/` under `/home/php-devforge/public_html`. So `public--site--laravel.phpforge.dev` → `/home/php-devforge/public_html/laravel/site/public`.
44-
3. **PHP version routing** — `devlocal.conf` / `devlocal_https.conf` set `PHP_VERSION` from the env (via `PassEnv`), then override it per-request with `SetEnvIf Host ".*--p83.*"`. A `<FilesMatch \.php$>` block picks `proxy:fcgi://php83dev:9000` or `php84dev:9000` accordingly. Both PHP containers are always running; the hostname decides which one handles the request.
44+
3. **PHP version routing** — `resolve_docroot.lua` (`set_php_handler`, a `LuaHookFixups`) picks `proxy:fcgi://phpNNdev:9000` from the `--pNN` suffix, falling back to `PHP_VERSION`. Each version is a compose profile (`php83`, `php84`, `php85`) listed in `COMPOSE_PROFILES`, so only the installed ones run; the same variable reaches Apache and nginx as `ENABLED_PROFILES`, and the Lua answers a request for a version that is not installed with a page instead of a bare 503. `COMPOSE_PROFILES` always contains the default version — `forge php off` refuses to remove it, and a `.env` with no `php*` entry is migrated by `forge`.
4545

4646
`docker-library/httpd/config_files/php-fpm.conf` is a legacy/unused fallback — the active handler selection lives in the two vhost files. Both vhosts are duplicates apart from the `SSLEngine` block; **changes to routing must be applied to both**.
4747

‎README.es.md‎

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ https://mi-app--p83.phpforge.dev # esta petición en PHP 8.3
6060
https://mi-app--p85.phpforge.dev # esta petición en PHP 8.5
6161
```
6262

63-
El mismo código en tres versiones, sin reiniciar y sin cambiar nada. Útil para
64-
comprobar una actualización antes de comprometerte con ella.
63+
El mismo código en las versiones que instalaste, sin reiniciar y sin cambiar nada.
64+
Útil para comprobar una actualización antes de comprometerte con ella.
6565

6666
Todo se sirve por **HTTPS real**, con un certificado en el que tu equipo confía.
6767

@@ -83,7 +83,7 @@ pensado para tener muchos proyectos sobre un mismo entorno, sin configurar ningu
8383

8484
## 📖 Qué incluye
8585

86-
- **Apache + PHP-FPM 8.3, 8.4 y 8.5**, los tres activos, elegidos por petición
86+
- **Apache + PHP-FPM 8.3, 8.4 y 8.5** — instalas las que quieras, elegidas por petición
8787
- **HTTPS automático** con una CA local de confianza
8888
- **DNS local con comodín** que solo afecta a tu dominio de desarrollo
8989
- **Edición en vivo** — los archivos están montados, no hay que sincronizar nada
@@ -124,14 +124,16 @@ Y abre **https://welcome.phpforge.dev**.
124124
```bash
125125
./install.sh
126126
```
127-
Pregunta por tu dominio, dónde vivirán tus proyectos y la versión de PHP por
128-
defecto; busca un puerto DNS libre, detecta tu usuario, escribe `.env`, crea la
129-
carpeta de proyectos, y ofrece generar los certificados y configurar el DNS.
127+
Pregunta por tu dominio, dónde vivirán tus proyectos, qué versiones de PHP
128+
instalar y cuál de ellas es la de por defecto; busca un puerto DNS libre, detecta
129+
tu usuario, escribe `.env`, crea la carpeta de proyectos, y ofrece generar los
130+
certificados y configurar el DNS.
130131

131132
Se puede volver a ejecutar sin miedo: tu `.env` actual da los valores por defecto.
132133
Para uso desatendido:
133134
```bash
134135
./install.sh --yes --domain=midominio.dev --projects-dir=~/code
136+
./install.sh --yes --php=84,83 # dos versiones, 8.4 la de por defecto
135137
./install.sh --yes --profiles=pg18,mail # bases de datos y correo, desatendido
136138
./install.sh --help # todas las opciones
137139
```
@@ -161,7 +163,10 @@ forge restart
161163
forge status # qué corre y cómo está configurado
162164

163165
forge link ~/code/app/public # publicar un proyecto en app.<dominio>
164-
forge use 8.5 # cambiar la versión por defecto
166+
167+
forge php list # versiones de PHP, y cuáles están instaladas
168+
forge php on|off 8.3 # instalar una, o liberar los ~2 GB que ocupa
169+
forge use 8.5 # cambiar la versión por defecto (la instala si falta)
165170
forge shell 8.4 # entrar a un contenedor
166171
forge logs 8.4 # seguir sus logs
167172

@@ -242,6 +247,25 @@ ln -s ../projects/mi-app/public ~/php-devforge/sites/mi-app
242247
El enlace debe apuntar a la carpeta que contiene el `index.php` (el `public/` del
243248
framework). El `.htaccess` funciona: `AllowOverride All` está activado.
244249

250+
### 🐘 Versiones de PHP
251+
252+
Se eligen al instalar, igual que las bases de datos, porque cada una es una imagen
253+
aparte de unos 2 GB:
254+
255+
```bash
256+
forge php list # 8.3 off / 8.4 ON default / 8.5 off
257+
forge php on 8.3 # descargarla y arrancarla
258+
forge php off 8.3 # pararla; la imagen sigue en disco hasta que la borres
259+
```
260+
261+
Una de ellas es la **de por defecto**: responde a todos los nombres de host sin
262+
sufijo `--pNN`. `forge use 8.5` la cambia, instalando esa versión antes si no la
263+
tienes, y `forge php off` se niega a quitar la de por defecto — si no, ningún
264+
nombre de host normal tendría quién lo atienda.
265+
266+
Pedir una versión que no instalaste devuelve una página que lo explica y dice el
267+
comando para añadirla, en vez de un 503 pelado.
268+
245269
### 🔄 Día a día
246270

247271
- Edita el código en tu editor — los archivos están montados, los cambios son inmediatos
@@ -407,7 +431,8 @@ las imágenes que arranca ese perfil.
407431

408432
| Perfil | Servicios | Notas |
409433
|---|---|---|
410-
| *(ninguno)* | apachedev, php83dev, php84dev, php85dev, dnsmasq | los levanta `forge start` |
434+
| *(ninguno)* | apachedev, dnsmasq | los levanta `forge start` |
435+
| `php83` `php84` `php85` | php83dev, php84dev, php85dev | se eligen al instalar, o `forge php on 8.3` |
411436
| `pg16` `pg17` `pg18` | postgres | ver la sección Bases de datos y correo |
412437
| `mariadb11` `mariadb12` | mariadb | igual |
413438
| `mail` | mailpit | interfaz en `https://mail.<dominio>` |
@@ -471,6 +496,8 @@ dentro de un contenedor; tu editor debe escuchar en el puerto **9003**.
471496
adoptan esos ids al arrancar, así que lo que escriben es tuyo.
472497
- **No cambia la versión de PHP**: revisa `PHP_VERSION` en `.env`, o añade
473498
`--p83`/`--p84`/`--p85` al nombre del host. `forge status` muestra la de por defecto.
499+
- **"PHP 8.3 is not installed"**: esa versión no se eligió al instalar.
500+
`forge php on 8.3` la añade; `forge php list` muestra las que tienes.
474501
- **`npm` muestra un aviso sobre pnpm**: es a propósito. npm sigue funcionando; aquí se
475502
prefiere pnpm.
476503
- **Un `.ini` de `custom/php.d/` parece ignorado**: recrea el contenedor con

0 commit comments

Comments
 (0)