diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3670fea..4af6197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,22 @@ jobs: docker-library/php/config_files/xdebug \ docker-library/httpd/config_files/docker-entrypoint.sh + # `forge` exists so the common commands are one word; a README that teaches + # `docker compose up` instead is how it stayed undiscovered until someone + # installed the project and never found `forge help`. Prose about what CI + # validates may still say `docker compose config` -- that is not an instruction. + - name: the READMEs do not teach docker compose + run: | + fail=0 + for pattern in 'docker compose up' 'docker compose logs' \ + 'docker compose stop' 'docker compose --profile' \ + 'docker compose build'; do + if grep -n "$pattern" README.md README.es.md; then + echo "^ use the forge command instead"; fail=1 + fi + done + exit $fail + - name: the helper scripts compile run: | python3 -m py_compile .github/scripts/drive-install.py @@ -141,6 +157,7 @@ jobs: # unknown versions must be refused, not passed to docker if forge use 9.9 2>/dev/null; then echo "9.9 should have been rejected"; exit 1; fi + - name: build images # Every PHP version is built. 8.5 broke silently once: opcache became # built-in and docker-php-ext-install started failing on it. @@ -198,6 +215,29 @@ jobs: # Hyphens in project names were broken for a long time: mi-app--sites # resolved to sites/app/mi. + # Needs running containers, so it sits after the page is confirmed served + # rather than with the other forge checks, which run before the stack is up. + - name: forge logs reaches every container + run: | + export PATH="$HOME/.local/bin:$PATH" + # Counting distinct services, not looking for one name: compose prints + # logs for stopped containers too, so "apachedev appears" would pass even + # if the command had regressed to following a single container. + named() { grep -oE '^[a-z0-9]+ *\|' "$1" | tr -d ' |' | sort -u; } + + timeout 5 forge logs > /tmp/all.log 2>&1 || true + all="$(named /tmp/all.log | wc -l)" + test "$all" -ge 3 || { echo "forge logs named $all services, expected the stack"; exit 1; } + + # naming one narrows it to exactly that -- a service, not just a PHP + # version, which is what sent the README to `docker compose logs` + timeout 5 forge logs apachedev > /tmp/one.log 2>&1 || true + test "$(named /tmp/one.log)" = apachedev || { + echo "forge logs apachedev named: $(named /tmp/one.log | tr '\n' ' ')"; exit 1; } + + if forge logs nope 2>/dev/null; then echo "unknown service accepted"; exit 1; fi + echo "forge logs: $all services with no argument, one when named" + - name: a hyphenated project name resolves run: | curl -fsS --resolve my-app--sites.phpforge.dev:443:127.0.0.1 \ diff --git a/README.es.md b/README.es.md index c757516..6880d5a 100644 --- a/README.es.md +++ b/README.es.md @@ -133,12 +133,14 @@ Y abre **https://welcome--sites.phpforge.dev**. 4. **Levanta el entorno:** ```bash - docker compose up -d + forge start ``` Comprueba que funciona: `https://welcome--sites.phpforge.dev` ## 💻 Uso +### ▶️ Arrancar y parar + ```bash forge start # levantar todo forge stop @@ -150,6 +152,11 @@ forge use 8.5 # cambiar la versión por defecto forge shell 8.4 # entrar a un contenedor forge logs 8.4 # seguir sus logs +forge profile list # servicios opcionales, y cuáles están encendidos +forge profile on|off # encender o apagar uno +forge db list # las bases de datos entre ellos +forge mail on|off # un buzón de pruebas en mail. + forge images build|pull # construir en local, o usar las publicadas forge certs # regenerar los certificados forge dns status # ver el DNS local @@ -162,6 +169,12 @@ enlaza en `~/.local/bin`. Los argumentos de versión aceptan `8.5` u `85`, y las versiones disponibles salen de `docker-compose.yml` — añade un servicio y `forge use 8.6` funciona solo. +Es un envoltorio de `docker compose`, no un reemplazo: no esconde nada y los +comandos crudos siguen funcionando desde la carpeta del proyecto. Existe para que +lo que haces a diario sea una palabra, y para que lo que tiene truco — un enlace +relativo que los contenedores puedan seguir, nginx y Apache sin pelearse por el +puerto 80 — salga bien sin que tengas que acordarte de por qué. + ### 🌐 Tus proyectos Viven en la carpeta que elegiste al instalar (`PROJECTS_DIR` en `.env`, @@ -216,6 +229,13 @@ ln -s ../projects/mi-app/public ~/php-devforge/sites/mi-app El enlace debe apuntar a la carpeta que contiene el `index.php` (el `public/` del framework). El `.htaccess` funciona: `AllowOverride All` está activado. +### 🔄 Día a día + +- Edita el código en tu editor — los archivos están montados, los cambios son inmediatos +- No hay que reiniciar nada por un cambio de código +- Entra a un contenedor con `forge shell 8.4` +- Mira qué está pasando con `forge logs` + ### 🐞 Xdebug Dentro del contenedor: @@ -235,12 +255,15 @@ del stack; el resto de tu DNS no se toca, así que apagar los contenedores nunca sin internet. ```bash -./setup-local-dns.sh # aplicar -./setup-local-dns.sh --status # ver la configuración -./setup-local-dns.sh --test # comprobar la resolución -./setup-local-dns.sh --remove # deshacer: borra un archivo +forge dns setup # aplicar +forge dns status # ver la configuración +forge dns test # comprobar la resolución +forge dns remove # deshacer: borra un archivo ``` +Llaman a `./setup-local-dns.sh`, que también puedes ejecutar directamente con las +mismas opciones como `--banderas`. + dnsmasq escucha en `127.0.0.1:${DNS_PORT}` en vez del puerto 53, que suele estar ocupado por systemd-resolved, Pi-hole o similar. El instalador busca uno libre y lo escribe en `.env`. @@ -264,7 +287,7 @@ upload_max_filesize = 100M ``` ```bash -docker compose up -d --force-recreate php84dev +forge restart php84dev ``` Se lee *además* de la configuración de la imagen, así que no pisa nada y el interruptor @@ -312,7 +335,8 @@ IMAGE_MODE=build # construir siempre en local IMAGE_MODE=always # volver a descargar, para forzar la última publicada ``` -Después, `docker compose up -d`. +Después, `forge start`. O sáltate la edición: `forge images build` y +`forge images pull` ponen esa línea y reinician por ti. Elige `build` si editas algo dentro de `docker-library/`: tus cambios se recogen solos, y la caché de Docker hace que no cueste nada cuando no hay cambios. @@ -360,11 +384,13 @@ Algunos no se levantan por defecto. Llevan un `profile` de compose, así que los cuando los quieras: ```bash -docker compose --profile search up -d # Elasticsearch + Kibana +forge profile list # qué existe, y qué está encendido +forge profile on search # Elasticsearch + Kibana +forge profile off search ``` -Los perfiles son cosa de compose, así que este sigue siendo un comando de -`docker compose`; `forge start` cubre el conjunto por defecto. +El listado lee los archivos de compose, así que se mantiene solo: cada fila muestra +las imágenes que arranca ese perfil. | Perfil | Servicios | Notas | |---|---|---| @@ -393,8 +419,8 @@ prefieres nginx, está disponible — como alternativa, no como añadido, porque quieren los puertos 80 y 443: ```bash -docker compose stop apachedev -docker compose --profile nginx up -d nginxdev +forge profile on nginx # para apachedev; no pueden compartir los puertos +forge profile off nginx # y lo devuelve ``` Sirve las mismas URLs, incluidas las rutas anidadas y el sufijo `--pNN`. @@ -420,21 +446,28 @@ dentro de un contenedor; tu editor debe escuchar en el puerto **9003**. ## 🐛 Problemas comunes -- **El DNS no resuelve**: ejecuta `./setup-local-dns.sh --status` y `--test`. Puede - hacer falta reiniciar el navegador. -- **El certificado no es de confianza**: vuelve a ejecutar `./install_cert.sh` y - reinicia el navegador. Firefox tiene su propio almacén en Linux: instala `nss` (Arch) - o `libnss3-tools` (Debian/Ubuntu) y repite. +- **El DNS no resuelve**: ejecuta `forge dns status` y `forge dns test`. Puede hacer + falta reiniciar el navegador. +- **El certificado no es de confianza**: vuelve a ejecutar `forge certs` y reinicia el + navegador. Firefox tiene su propio almacén en Linux: instala `nss` (Arch) o + `libnss3-tools` (Debian/Ubuntu) y repite. - **Los contenedores no arrancan**: comprueba que Docker esté corriendo y que los puertos 80 y 443 estén libres. -- **Problemas de permisos**: revisa que `PUID`/`PGID` en `.env` coincidan con los tuyos - (`id -u`, `id -g`) y recrea los contenedores. -- **No cambia la versión de PHP**: revisa `PHP_VERSION` en `.env` (83, 84 u 85), o añade - `--p83`/`--p84`/`--p85` al host. -- **`npm` muestra un aviso sobre pnpm**: es a propósito. npm sigue funcionando. +- **Problemas de permisos bajo `public_html`**: revisa que `PUID`/`PGID` en `.env` + coincidan con los tuyos (`id -u`, `id -g`) y haz `forge restart`. Los contenedores + adoptan esos ids al arrancar, así que lo que escriben es tuyo. +- **No cambia la versión de PHP**: revisa `PHP_VERSION` en `.env`, o añade + `--p83`/`--p84`/`--p85` al nombre del host. `forge status` muestra la de por defecto. +- **`npm` muestra un aviso sobre pnpm**: es a propósito. npm sigue funcionando; aquí se + prefiere pnpm. - **Un `.ini` de `custom/php.d/` parece ignorado**: recrea el contenedor con - `docker compose up -d --force-recreate php84dev`. Reiniciar no basta. -- **Los alias no funcionan**: asegúrate de haber hecho `source aliases.bash`. + `forge restart php84dev`. Reiniciar no basta. +- **`forge: command not found`**: el instalador lo enlaza en `~/.local/bin`, que + algunos shells no tienen en el `PATH`. Añádelo, o haz `source aliases.bash` desde la + carpeta del proyecto. + +Para más ayuda, mira los logs con `forge logs` — o `forge logs apachedev` para un +servicio — o abre un issue en GitHub. ## 🤝 Contribuir diff --git a/README.md b/README.md index feabb66..c6990e8 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Then open **https://welcome--sites.phpforge.dev**. 4. **Start the containers:** ```bash - docker compose up -d + forge start ``` Or `forge start`, which works from anywhere. @@ -151,6 +151,11 @@ forge use 8.5 # set the default PHP version forge shell 8.4 # open a shell in a container forge logs 8.4 # follow its logs +forge profile list # optional services, and which are on +forge profile on|off # turn one on or off +forge db list # the databases among them +forge mail on|off # a mail catcher at mail. + forge images build|pull # build locally, or use the published images forge certs # regenerate the certificates forge dns status # inspect the local DNS @@ -162,6 +167,12 @@ forge help `~/.local/bin`. Version arguments accept `8.5` or `85`, and the available versions come from `docker-compose.yml` — add a service and `forge use 8.6` just works. +It is a wrapper around `docker compose`, not a replacement for it: nothing is +hidden and the raw commands still work from the project directory. It exists so +the things you do daily are one word, and so the ones with a trick to them — a +relative symlink the containers can follow, nginx and Apache not fighting over +port 80 — are right without you remembering why. + ### 🌐 Accessing Your Projects 1. **Create project structure:** @@ -224,10 +235,22 @@ come from `docker-compose.yml` — add a service and `forge use 8.6` just works. ### 🔄 Development Workflow -- Edit code in your IDE (files are volume-mounted for live updates) -- Changes appear immediately without restarting containers -- Use Xdebug for debugging (configure your IDE to listen on port **9003**, the Xdebug 3 default) +- Edit code in your editor — the files are volume-mounted, so changes are live +- Nothing to restart for a code change - Open a shell in a container with `forge shell 8.4` +- Watch what is happening with `forge logs` + +### 🐞 Xdebug + +Inside a container: + +```bash +xdebug # toggle it on or off +xdebug --force-activate +xdebug /path/script.php # run one script with it on, then turn it back off +``` + +Point your editor at port **9003**, the Xdebug 3 default. ## 🌐 Local DNS @@ -236,12 +259,15 @@ the stack's dnsmasq; the rest of your DNS is untouched, so stopping the containe never costs you internet access. ```bash -./setup-local-dns.sh # apply -./setup-local-dns.sh --status # show what is configured -./setup-local-dns.sh --test # check resolution -./setup-local-dns.sh --remove # undo: deletes one file +forge dns setup # apply +forge dns status # show what is configured +forge dns test # check resolution +forge dns remove # undo: deletes one file ``` +These call `./setup-local-dns.sh`, which you can also run directly with the same +options as `--flags`. + dnsmasq listens on `127.0.0.1:${DNS_PORT}` rather than port 53, which is usually taken by systemd-resolved, Pi-hole or similar. The installer picks a free port and writes it to `.env`; change it there if you need to. @@ -266,7 +292,7 @@ upload_max_filesize = 100M ``` ```bash -docker compose up -d --force-recreate php84dev +forge restart php84dev ``` It is scanned *in addition to* the image's own config, so nothing is shadowed and @@ -315,12 +341,13 @@ IMAGE_MODE=build # always build locally IMAGE_MODE=always # re-pull every time, to force the newest published image ``` -Then `docker compose up -d`. +Then `forge start`. Or skip the editing: `forge images build` and `forge images pull` +set that line and restart for you. Pick `build` if you edit anything under `docker-library/`: your changes are picked up automatically, and Docker's layer cache makes it cheap when nothing changed. -Either way `docker compose build` still builds on demand, and compose falls back -to building if an image cannot be pulled. +Either way an image still gets built on demand, and compose falls back to building +if one cannot be pulled. **Note:** settings baked in at build time — `NODE_VERSION`, for instance — only apply when you build. A pulled image already has them fixed. @@ -365,11 +392,13 @@ Some services are not started by default. They carry a compose `profile`, so you ask for them when you want them: ```bash -docker compose --profile search up -d # Elasticsearch + Kibana +forge profile list # what exists, and what is on +forge profile on search # Elasticsearch + Kibana +forge profile off search ``` -Profiles are a compose feature, so this one stays a `docker compose` command; -`forge start` covers the default set. +The listing reads the compose files, so it stays right on its own: each row shows +the images that profile starts. | Profile | Services | Notes | |---|---|---| @@ -398,8 +427,8 @@ would rather run nginx, it is there — as an alternative, not an addition, sinc want ports 80 and 443: ```bash -docker compose stop apachedev -docker compose --profile nginx up -d nginxdev +forge profile on nginx # stops apachedev; they cannot share the ports +forge profile off nginx # and brings it back ``` It serves the same URLs, including nested paths and the `--pNN` version suffix. @@ -425,18 +454,28 @@ container; your IDE should listen on port **9003**. ## 🐛 Troubleshooting -### ❓ Common Issues - -- **DNS resolution not working**: Ensure you ran `./setup-local-dns.sh` and restarted your browser or system. You may need to flush DNS cache. -- **PHP version not switching**: check `PHP_VERSION` in `.env` (83, 84 or 85), or append `--p83`/`--p84`/`--p85` to the host. `forge status` prints the default. -- **`npm` prints a message about pnpm**: intentional. npm still runs; pnpm is preferred here. -- **An `.ini` in `custom/php.d/` seems ignored**: recreate the container, `docker compose up -d --force-recreate php84dev`. A restart alone does not re-read it. -- **SSL certificate not trusted**: Make sure you ran `./install_cert.sh` and restart your browser afterwards. Check the CA is present with `openssl verify -CAfile .caroot/rootCA.pem certificates/php-devforge.pem`. Firefox keeps its own trust store on Linux, so install `nss` (Arch) or `libnss3-tools` (Debian/Ubuntu) and re-run the script if Firefox still complains. -- **Containers not starting**: Check that Docker and Docker Compose are installed and running. Ensure ports 80 and 443 are not in use by other services. -- **Permission issues with public_html**: Check that `PUID`/`PGID` in `.env` match your own ids (`id -u`, `id -g`), then recreate the containers with `docker compose up -d --force-recreate`. The containers adopt those ids on startup, so files they write are owned by you. -- **Aliases not working**: Ensure you sourced `aliases.bash` or added it to your `~/.bashrc`. - -For more help, check the logs with `docker compose logs` or create an issue on GitHub. +- **DNS not resolving**: run `forge dns status` and `forge dns test`. You may need to + restart your browser. +- **SSL certificate not trusted**: re-run `forge certs` and restart your browser. + Firefox keeps its own trust store on Linux: install `nss` (Arch) or `libnss3-tools` + (Debian/Ubuntu) and try again. +- **Containers not starting**: check Docker is running and that ports 80 and 443 are + free. +- **Permission issues under `public_html`**: check `PUID`/`PGID` in `.env` match your + own (`id -u`, `id -g`), then `forge restart`. The containers adopt those ids on + startup, so files they write belong to you. +- **PHP version not switching**: check `PHP_VERSION` in `.env`, or append + `--p83`/`--p84`/`--p85` to the host name. `forge status` prints the default. +- **`npm` prints a notice about pnpm**: deliberate. npm still works; pnpm is preferred + here. +- **An `.ini` in `custom/php.d/` seems ignored**: recreate the container with + `forge restart php84dev`. A plain restart does not re-read it. +- **`forge: command not found`**: the installer links it into `~/.local/bin`, which + some shells do not have on `PATH`. Add it, or `source aliases.bash` from the project + directory. + +For more help, check the logs with `forge logs` — or `forge logs apachedev` for one +service — or open an issue on GitHub. ## 🤝 Contributing diff --git a/TASKS.md b/TASKS.md index 0cb29a0..1c7ec2d 100644 --- a/TASKS.md +++ b/TASKS.md @@ -1106,7 +1106,7 @@ what the README explains first. keypresses, so adding a database cannot silently break it — the first version did count, and would have. -- [ ] **34. The installer's last words send you to `docker compose`, not `forge`** +- [x] **34. The installer's last words send you to `docker compose`, not `forge`** — FIXED It ends with: ``` @@ -1121,7 +1121,7 @@ what the README explains first. that one already ends with `forge start`. The installer calls the certificate script last, so its closing line is what you actually read. -- [ ] **35. The README teaches `docker compose` in 11 places** +- [x] **35. The README teaches `docker compose` in 11 places** — FIXED Same problem, wider. `README.md` lines 133, 269, 318, 322, 368, 401, 433, 436, 439 and the Spanish equivalents reach for compose where a `forge` subcommand exists. @@ -1146,6 +1146,30 @@ what the README explains first. whoever edits one cannot tell what the other has. Counting `##` and calling them aligned is exactly what hid this. + Done together. Every `docker compose` instruction is now a `forge` command in + both languages; the two that remain are prose, not instructions — the sentence + saying `forge` wraps compose, and the one describing what CI validates. + + The pass needed one more command. `forge logs` followed a single PHP container, + so troubleshooting still sent you to `docker compose logs`; naming a container + is not something you can do when you do not yet know what broke. No argument now + means every service, and an argument can be a service as well as a version. + + The subsections were worse than the heading list suggested. English was missing + the `xdebug` commands entirely; Spanish was missing three troubleshooting entries + and had no subsection for its command block. Both now carry the union, in the + same order, so the two files are translations rather than variants. + + Also switched to `forge` where the READMEs called the scripts directly: + `forge dns setup|status|test|remove` and `forge certs`, each noting the script + still works. And the stale "Aliases not working" entry became + `forge: command not found`, which is the thing people actually hit — the aliases + it referred to were removed when `forge` replaced them. + + CI guards it: a lint step greps both READMEs for `docker compose up|logs|stop| + --profile|build` and fails. Verified locally that it fails when a violation is + added, not just that it passes today. + - [ ] **36. There is no uninstall** `install.sh` writes `.env`, generates certificates, installs a CA into the system trust store and the NSS stores, symlinks `forge` into `~/.local/bin`, diff --git a/bin/forge b/bin/forge index 41865b1..fabb904 100755 --- a/bin/forge +++ b/bin/forge @@ -86,10 +86,26 @@ cmd_shell() { docker exec -it -w "$wd" -u php-devforge "php${v}dev" bash } +# No argument means every service: when something breaks you rarely know which +# container to look at, and picking one for you is how `docker compose logs` +# ended up in the troubleshooting section instead of this command. cmd_logs() { need_env - local v; v="$(normalise "${1:-$(current_version)}")" - docker compose logs -f "php${v}dev" + local arg="${1:-}" + if [ -z "$arg" ]; then + docker compose logs -f + elif printf '%s' "$arg" | grep -qE '^[0-9]+\.?[0-9]+$'; then + docker compose logs -f "php$(normalise "$arg")dev" + else + # Plain compose here, not compose_with_env: that one clears + # COMPOSE_PROFILES, which would hide mailpit and the databases. + docker compose config --services | grep -qx "$arg" || { + err "Unknown service: $arg" + echo " available: $(docker compose config --services | sort | paste -sd' ' -)" >&2 + exit 1 + } + docker compose logs -f "$arg" + fi } cmd_mail() { @@ -291,7 +307,7 @@ PHP DevForge forge use set the default PHP version forge shell [version] open a shell in a PHP container - forge logs [version] follow a container's logs + forge logs [version|service] follow the logs; everything if you name nothing forge link [name] publish a project: symlinks it under sites/ diff --git a/install_cert.sh b/install_cert.sh index 885fcae..ab3828e 100755 --- a/install_cert.sh +++ b/install_cert.sh @@ -141,4 +141,4 @@ fi echo "" echo "🎉 Done. Restart your browser so it picks up the new CA." -echo " Start the environment with: docker compose up -d" +echo " Start the environment with: forge start"