@@ -216,9 +216,50 @@ approach.
216216
217217## 💬 Needs discussion before implementing
218218
219- ### 7. Interactive installer 💬 DISCUSS FIRST
219+ ### 7. Interactive installer — DONE
220+
221+ - [x] ** Implemented as ` install.sh ` **
222+
223+ Asks for domain, projects folder and default PHP version; finds a free DNS port;
224+ detects `PUID`/`PGID` with `id -u`/`id -g`; writes `.env`; creates the projects
225+ folder; and offers to run the certificate and DNS steps. Re-running is safe: the
226+ current `.env` supplies the defaults, and the old one is kept as `.env.backup`.
227+ Flags for unattended use: `--domain=`, `--projects-dir=`, `--php=`, `--dns-port=`,
228+ `--yes`, `--skip-cert`, `--skip-dns`. With no terminal it refuses to run rather
229+ than silently accepting defaults.
230+
231+ **Config split:** `.env` is now generated and gitignored; `.env.example` is the
232+ tracked template. This also fixes `forge:use:php83` dirtying the git tree on every
233+ PHP version switch, and stops personal values (domain, uid, paths) being committed.
234+ Anyone with an existing clone will see `.env` drop out of version control; their
235+ local file is untouched.
236+
237+ **Projects folder:** `PROJECTS_DIR` on the host, `~/php-devforge` by default,
238+ mounted at the fixed `/home/php-devforge/public_html` inside the containers — so
239+ Apache, the Lua resolver and PHP need no changes. Only 2 lines in compose. The
240+ installer creates `projects/` for code and `sites/` for symlinks, plus a
241+ `sites/welcome` test page. No `sudo` at any point, thanks to task 8.
242+
243+ **Bug found while testing — hyphens in project names were broken.** The Lua split
244+ the host with `[^%-%-]+`, which is "not a hyphen", not "split on `--`". So:
245+
246+ | Host | Resolved to | |
247+ |---|---|---|
248+ | `mi-app--sites` | `sites/app/mi` | wrong |
249+ | `laravel-app--sites` | `sites/app/laravel` | wrong |
250+ | `miapp--sites` | `sites/miapp` | fine |
251+
252+ **All three examples in the README** (`laravel-app`, `symfony-app`, `plain-php`)
253+ were broken, and had been all along. Fixed by replacing `--` with a sentinel that
254+ cannot occur in a hostname before splitting. Verified: `mi-app--sites`,
255+ `laravel-app--sites`, `a--b--c` and `mi-app--sites--p83` all resolve correctly.
220256
221- - [ ] ** Agree on the approach** (questions below), then implement
257+ **README updated:** installer flow, the new folder layout, the warning that
258+ symlinks must point inside `PROJECTS_DIR`, and the clone path is no longer forced
259+ (aliases resolve their own location since task 3).
260+
261+ <details >
262+ <summary >Original open questions (kept for the record)</summary >
222263
223264Goal: replace the manual README steps with ` ./install.sh ` , which asks for the
224265settings and does the setup.
@@ -267,12 +308,66 @@ container and does not need to change:
267308 ```
268309
269310 The same idea applies to ports 80 and 443.
311+
312+ </details>
270313- `aliases.bash` hardcodes `$HOME/php-devforge-config`. Should the installer generate
271314 the aliases file with the real path, or should the aliases derive it themselves?
272315
273- ### 8. Container user / file ownership 💬 DISCUSS FIRST
274-
275- - [ ] **Choose an option** (A / B / C below), then implement
316+ ### 8. Container user / file ownership — DONE
317+
318+ - [x] **Implemented: the container adopts the host uid at startup (PUID/PGID)**
319+
320+ Measured first, rather than reasoned about. The host user is uid 1000, the
321+ container ran as uid 33 (`www-data`), and:
322+ - a normal user-owned folder was **read-only** to the container
323+ - `chgrp 33` on your own folder fails without root — which is *why* the README
324+ demanded `sudo chown`, not laziness
325+ - `--user 1000:1000` at runtime **killed the container**: uid 1000 had no passwd
326+ entry, so `$HOME` was empty and the entrypoint failed writing `/.bashrc`
327+ - `pnpm install` in a plain user folder produced nothing at all
328+
329+ Publishing prebuilt images was wanted (task 16), so baking the host uid at build
330+ time was ruled out. Chosen instead: keep the image generic and adjust the uid when
331+ the container starts.
332+
333+ **What changed**
334+ - Dockerfile: the dev user gets **its own group** and defaults to uid/gid 1000
335+ (the usual first Linux user, so most hosts need no adjustment at all). The
336+ `sed` rewriting `www-data`'s home in `/etc/passwd` was dropped as vestigial.
337+ - `USER php-devforge` removed. The container starts as root, which is how the
338+ official `php:fpm` image works: a root master dropping workers to an
339+ unprivileged user. That `USER` line was what forced the old `exec sudo` hack.
340+ - `zz-docker.conf`: workers run as `php-devforge`.
341+ - The entrypoint adjusts uid/gid, fixes ownership, and no longer needs `sudo`.
342+
343+ **Two traps found while testing, both from the user asking the right questions**
344+ - `usermod -u` does **not** chown files outside the home dir, and nvm lives in
345+ `/usr/local/nvm`. Without an explicit chown, `nvm install` and global npm/pnpm
346+ packages break — the container would start fine and fail later, confusingly.
347+ - The ownership fix must run on **every start**, not only when the uid changes. A
348+ named volume keeps the ownership it was created with: the existing
349+ `dataphp-devforge` volume was still `www-data 700`, so workers could not
350+ traverse `/home/php-devforge` and every page returned `File not found`.
351+ `$HOMEDIR` also needs mode 755, because Apache is a different uid and still has
352+ to traverse it.
353+ - The recursive chown deliberately **excludes `public_html`** — that is the
354+ mounted host folder, and a `chown -R` there would rewrite the ownership of the
355+ user's real projects.
356+
357+ **Verified**: containers start; workers run as uid 1000 while the master stays
358+ root; a file written by PHP over the web is owned by `yostinv` and deletable
359+ without sudo; `pnpm install` leaves `node_modules` owned by you; `nvm install 22`
360+ works (v22.23.2); `sudo` still works inside (the sudoers rule is by name, not
361+ uid); all three host forms serve over HTTPS; it works from a wiped volume; and
362+ with `PUID=1501` the user, `NVM_DIR` and created files all land on 1501.
363+
364+ README updated: the `sudo chown yourUser:www-data` step is gone.
365+
366+ **Unblocks task 7** — the installer can now create the projects folder with no
367+ sudo at all.
368+
369+ <details>
370+ <summary>Options considered (kept for the record)</summary>
276371
277372The problem this needs to solve: today the README tells users to run
278373`sudo chown yourUser:www-data -R /home/php-devforge`. That is confusing for people
@@ -303,6 +398,11 @@ Options (**none chosen yet**):
303398- macOS handling is different (Docker Desktop maps ownership automatically) — should
304399 the two platforms behave differently, or pick one approach that works for both?
305400
401+ </details>
402+
403+ **Still open:** none of this was tested on macOS. Docker Desktop maps ownership on its
404+ own there, so PUID/PGID should be harmless, but it is unverified — see task 15 (CI).
405+
306406---
307407
308408## 🟡 Quality and maintenance
0 commit comments