Merge pull request 'docs(website): Phase 2 PR 9 — the modules mount, and Phase 2 complete' (#134) from docs/module-compose-volume into main
Reviewed-on: #134
This commit is contained in:
@@ -703,7 +703,13 @@ half needs rethinking before Phase 2 builds on it.
|
||||
|
||||
`app.js` scans `modules/*/module.json` with `fs.readdirSync` at require time and mounts what it
|
||||
finds (`MODULE_SYSTEM.md` §1.12). The database is not consulted. `MODULES_DIR` defaults to
|
||||
`<repo>/modules` and is overridable by env for tests and for the Docker volume mount.
|
||||
`<repo>/modules` and is overridable by env for tests and for the Docker mount. Under Compose it is
|
||||
set to `/app/modules`, where `./modules` is bind-mounted read-write (`MODULE_SYSTEM.md` §2.5); the
|
||||
image itself carries that directory empty and owned by the container user, and `.dockerignore`
|
||||
excludes any local one so a module can never be baked in.
|
||||
|
||||
**A missing modules directory is not an error.** The scan catches and returns, because "no modules
|
||||
installed" is the normal state of bare core and the loader must not make the mount mandatory to boot.
|
||||
|
||||
**The trigger is one explicit call, and there is no lazy self-scan** (§7.6). `app.js` calls
|
||||
|
||||
|
||||
@@ -428,8 +428,12 @@ leaves the row `disabled`, which the reset never touches.
|
||||
|
||||
### 2.5 Install, uninstall, purge
|
||||
|
||||
Modules live on a **mounted volume**, not in the image — the same treatment `uploads` already gets in
|
||||
`docker-compose.yml`. That is what makes the WordPress model work against a pull-only image.
|
||||
Modules live on a **mounted volume**, not in the image. That is what makes the WordPress model work
|
||||
against a pull-only image. *(Settled in Phase 2 PR 9: it is a **bind mount** of `./modules`, not the
|
||||
named volume this sentence originally reached for by analogy with `uploads` — hand-placing a module
|
||||
directory is a supported install below, and a named volume would route it through `docker cp`. The
|
||||
image's copy is excluded by `.dockerignore`, so a module in a builder's working tree can never ship
|
||||
inside an image; see `modules/README.md` in the website repo.)*
|
||||
|
||||
**Install:** admin selects the module → bundle downloaded from the module repo's release and verified
|
||||
against its `sha256` → unpacked into `modules/<id>/` on the volume → `installed_modules` row written →
|
||||
@@ -511,14 +515,14 @@ too (API §7.2).
|
||||
7. Client `src/modules/registry.js`, the `window.__rg` shared-dependency global, the chunk's static
|
||||
mount and the `htmlShell` script injection — empty registry, no visible change.
|
||||
8. `MOD_PATHS` → `roles`-derived (§1.4); the generic feature-provider seam (§1.5).
|
||||
9. `docker-compose.yml` gains the `modules` volume.
|
||||
9. `docker-compose.yml` gains the `modules` mount.
|
||||
|
||||
Exit criterion: no **existing** URL moves and every existing test passes. If Phase 2 changes one URL,
|
||||
it is wrong. PR 6 is the single deliberate exception in the phase and it *adds*: `routes.manifest.json`
|
||||
gains exactly one line, `GET /api/v1/public/modules`, and nothing else in the file moves. Every other
|
||||
PR in Phase 2 produces a zero-line diff.
|
||||
|
||||
**Progress: PRs 1-7 done.**
|
||||
**Progress: complete — all nine PRs landed.**
|
||||
|
||||
- **PR 1** — `installed_modules` and the state machine, with the stored shape and the boot rules
|
||||
settled in §2.4 above.
|
||||
@@ -678,6 +682,54 @@ fixed enum because the leg set is whatever has been registered.
|
||||
admin can relabel a module row in Admin → Navigation and have it persist and apply — the whole
|
||||
point of merging before the override layer. Zero CSP reports, zero console errors.
|
||||
|
||||
- **PR 9** — the mount itself, which closes the phase: `docker-compose.yml` gains `./modules` at
|
||||
`/app/modules`, the `Dockerfile` creates that directory node-owned, `.dockerignore` keeps any local
|
||||
one out of the image and `modules/README.md` documents the directory for whoever opens it.
|
||||
|
||||
It is a **bind mount, not the named volume** §2.5 assumed by analogy with `uploads`. Placing a
|
||||
module directory by hand is a supported install, and a named volume makes that a `docker cp` into a
|
||||
running container — the one install path an operator without the admin panel has, routed through
|
||||
the least discoverable mechanism Docker offers. A bind mount makes it `tar -xf … -C ./modules`, and
|
||||
makes the installed set something an operator can *see*. §2.5 is amended above; nothing else about
|
||||
install, uninstall or purge changes.
|
||||
|
||||
Two consequences worth stating, because both are silent failures rather than errors. The directory
|
||||
is **tracked** — via its README, the same shape `brand/` already uses — because Docker recreates a
|
||||
missing bind-mount source as `root:root`, and the container runs as uid 1000: delete `modules/` from
|
||||
a checkout and the next install fails on a permission error that names no cause. And the mount is
|
||||
**read-write**, since §2.5's install unpacks into it from inside the container; deferring that to
|
||||
Phase 4 would have bought nothing, as a mount-mode change is a redeploy either way.
|
||||
|
||||
`.dockerignore` matters more than it looks. `COPY . .` would otherwise bake whatever module the
|
||||
builder had checked out into every image — and because Docker seeds a *fresh* named volume from
|
||||
the image's contents, that module could have appeared on a production deployment that never
|
||||
installed it. The exclusion is what makes "modules live on a mount, never in the image" true rather
|
||||
than merely intended.
|
||||
|
||||
Verified against a **running container**, which is the only thing that can check any of the above —
|
||||
a compose file that parses proves nothing about ownership, and nothing about what the image
|
||||
contains. The image carries an empty, node-owned `/app/modules` despite a module sitting in the
|
||||
build context. A module on the mount loads, mounts, runs `onBoot` and reaches `started`;
|
||||
`/api/v1/public/modules` lists it; its chunk serves from the entry's directory alone, with the
|
||||
module's own server source and `module.json` both `404`. The [§7.7](MODULE_API.md#77-the-client-half-has-to-be-verified-in-a-browser--the-timing-bug-no-test-could-see)
|
||||
browser smoke was re-run against the containerised stack rather than a working tree: in Chrome, the
|
||||
page renders on first paint inside core's `PublicLayout`, drawing React and the UI kit off
|
||||
`window.__rg`, with its nav row interleaved into core's public nav — under the enforced
|
||||
`script-src 'self'`, with zero CSP reports and no console errors. Removing the directory by hand and
|
||||
restarting reconciles the row to `startup_failed`/`require` exactly as §2.4 says, and leaves core
|
||||
healthy with no script injected and `{"modules":[]}` published.
|
||||
|
||||
**933 server tests** and **160 client tests** pass, both unchanged — this PR ships no application
|
||||
code. `routes.manifest.json` stays at 230 routes and the OpenAPI spec regenerates byte-identical.
|
||||
The one source change is a comment: `scripts/routeManifest.js` enumerated the filesystem-conditional
|
||||
mounts it excludes and had never been told about `/modules`. Its filter is an allowlist, so the
|
||||
behaviour was always right and only the explanation was stale.
|
||||
|
||||
**Phase 2 is complete.** Core can discover, validate, mount, migrate, boot, publish, serve and
|
||||
navigate a module it does not contain, on a deployment that builds nothing — and it does all of that
|
||||
while no existing URL has moved. The exit criterion held: `routes.manifest.json` went 229 → 230 across
|
||||
the whole phase, and the one added line is PR 6's deliberate `GET /api/v1/public/modules`.
|
||||
|
||||
**Phase 3 — Extract `module-uo`.** Moves out of `website/`: the 8 model directories and their 25
|
||||
tables; the nine UO `utils/` files plus `newsGump.js`; the 13 router/controller files;
|
||||
`scripts/importSpawnAtlas.js` and `db/spawnAtlas.art.json`; `usersShard.controller.js` **minus
|
||||
|
||||
Reference in New Issue
Block a user