Index
# Deploying the demo to IONOS The demo (`demo/www`) is deployed to **https://mmoframework.fr**, hosted on an IONOS *Hébergement Web Premium* webspace. That is a **managed webspace**: ssh works, and nothing else does — no `sudo`, no `systemctl`, no vhost, no `/etc/apache2`. The only way in is to send files over ssh. That costs nothing here, because `mmo.admin` has always deployed exactly that way: a release is one `.tar.gz`, uploaded, checksummed *on the target*, extracted beside the running release, and switched onto with a single atomic symlink swap. Nothing in that needs a privileged host. Three things do, and all three are declared rather than coded — see mmo.admin's [deploy.md](../../admin/docs/deploy.md), *Managed webspaces*, for the mechanism: | the problem | what `deploy.json` says | |---|---| | no service to reload after the swap | `"reload": null`, and `"managed": true` on the server so nothing warns about it | | `127.0.0.1` on a webspace is not this site | `"managed": true` moves the post-activation probe to **your** machine, against `https://mmoframework.fr/` | | the document root is fixed in the IONOS panel | `"webRoot": null` — on a managed server that is `{folder}/web`, which `install` makes a symlink onto `{folder}/current` | The server is `ionos` in `deploy.json`, the target `demo`. ## What the target looks like `folder` is `mmoframework.fr/www`, relative to the server's `root` (the webspace, what `pwd` prints there): ``` /homepages/38/d520689115/htdocs/ the webspace: the server's `root` mmoframework.fr/www/ ← folder: everything of this target, in one place web -> current/demo/www ← webRoot: what the IONOS panel serves. Linked ONCE, by `install` releases/3.1.2/ one extracted artifact, never edited in place releases/3.1.1/ what a rollback returns to deploy/ the journal of its deployments, and the lock current -> releases/3.1.2 the swap. This is the deploy ``` Only what `web` leads to is served for mmoframework.fr: `releases/` and `deploy/` beside it are not. **`demo/www` is the document root**: `"publicPath": "demo/www"`, so `web` points at `current/demo/www`, and nothing above it — `configs/`, `packages/`, `vendor/`, `mmo.json` — is in the served tree at all. `demo/www/.htaccess` routes every request that is not an existing file to `index.php`, and refuses any other `.php`. It sets no `MMO_MODE_REWRITE`: `index.php` is beside it. (A working copy is still reached through the `.htaccess` at the root of this repository, which does set it, and rewrites into `demo/www/index.php`.) `index.php` finds the phars in `../../packages/`, the release root, and the demo's configuration in `../configs/`: both are read from disk, never over http. `"healthFile": "demo/www/index.php"`: a release package without that file is refused before it is uploaded. --- ## One-time setup `php scripts/admin.php setup` asks for everything below, step by step, and writes it — the host key it reads and pins itself. What follows is what it asks for, and what to check by hand. ### 1. How ssh reaches the webspace In the IONOS control panel, under **SSH access**, note the host and the username (`uXXXXXXXX`). They are in `deploy.json`, under `servers.ionos`: `host`, `user`, and `port` (`null`: 22). The webspace takes a **password**, `"auth": "password"`. It is never in `deploy.json`: it lives in `deploy.credentials.json` beside it — git-ignored by `*.credentials.json`, mode `0600`, per machine. Every command refuses to start if that file is readable by anyone else, or tracked by git. ```bash install -m 600 /dev/null deploy.credentials.json # then write {"servers": {"ionos": {"password": "..."}}} ``` To move to a key instead, register your public key in the panel and set `"auth": "agent"` (your ssh agent holds the key) or `"auth": "key"` (its path in `deploy.credentials.json`) — see mmo.admin's *Credentials*. ### 2. Pin the server's host key Public data, and it belongs in the repository: without it the deployment trusts whatever answers at that address. `setup` reads it for you. By hand — MD5 or SHA1 in hex, which is what the ssh2 extension computes, not the `SHA256:` form `ssh-keygen -l` prints by default: ```bash ssh-keyscan home520689115.1and1-data.host 2>/dev/null | ssh-keygen -lf - -E md5 ``` It goes in `servers.ionos.fingerprint`, without the `MD5:` prefix. ### 3. The webspace path `servers.ionos.root` is the absolute path `pwd` prints on the webspace (`setup` reads it). The target's `folder`, and `webRoot` when it is set, are relative to it. ### 4. Point the domain In the IONOS panel, set the destination directory of `mmoframework.fr` to **`/mmoframework.fr/www/web`** — the `webRoot` link `install` makes, written relative to the webspace, as the panel writes it. If the panel lets you point it at `/mmoframework.fr/www/current/demo/www` instead, do that and set `"webRoot": "none"`: one link fewer in the chain. Either way the path Apache resolves goes through a symlink, which needs `FollowSymLinks` (on by default at IONOS). If it turns out not to be, `Options +FollowSymLinks` in `demo/www/.htaccess` is the one place you can say so — and the health check at the end of a release is what tells you whether it worked. ### 5. Set the PHP version In the IONOS panel, set PHP for the webspace (or for `mmoframework.fr/`) to **8.4 or later**. The framework uses typed class constants and property hooks; an older interpreter fails at parse time, before anything of ours runs. ### 6. Check everything before touching the server ```bash cd libs/client php scripts/admin.php doctor ``` Every line should be green or grey. It checks the declaration, the credentials file, the host key, that the target answers, and — because this is a shared host and its toolchain is the provider's — that `tar` and a sha256 tool are actually there. ### 7. Create the layout ```bash php scripts/admin.php install --targets=demo ``` It prints the plan first and asks before doing anything. If something is already at `web`, it is moved aside (`web.before-mmo.`), never deleted, and the plan says so. On a managed server `install` writes no `installTemplates` and runs no `postInstall`: there is no vhost of ours to write. --- ## Releasing A release is built from the commit `deploy.json` names (`"ref": "develop"`), and the build refuses to run unless your working tree is **clean and already checked out at that ref** — the `.phar` dependencies are built from the working tree, so they have to match what is exported. ```bash cd libs/client git checkout develop git status # must be empty: untracked files count too ``` Then: ```bash php scripts/admin.php plan --targets=demo ``` This changes nothing. It reads the target and prints, step by step, what a release would do. Read it. ```bash php scripts/admin.php release --targets=demo ``` `demo` is tagged `prod`, so it asks you to type its name before anything is changed — `--yes` does not satisfy that, deliberately. From a script, `--yes --i-understand`. The pipeline is: **install → build → assert → ship → activate → prune**. - **build** exports the commit, builds the phars into it — mmo.client's `package` hook (`Theme::BuildHook`) compiles the theme into `demo/www/bin` while it does, so the release never ships a stale one — overlays `demo/www/bin` (generated, never committed), strips the dev-only paths `scripts/admin.php` lists, and packages it as `build/mmo.client--.tar.gz`. It then reads the tarball back and refuses it if `demo/www/index.php` is not in it. - **assert** reads that tarball back and refuses to ship it if it contains `deploy.json`, a key, `src/`, `.git*` or anything else on its deny-list. Nothing is uploaded until it passes. - **ship** uploads it, verifies the sha256 **on the webspace**, extracts it into `releases/` beside the running release. - **activate** swaps `current`, then asks `https://mmoframework.fr/` — from your machine — whether it answers. It keeps asking for three minutes (`"healthTimeout": 180`): there is no interpreter to restart on a webspace, so the swap is picked up as each php-fpm worker's realpath cache expires (120s by default). If it never answers, the release is rolled back automatically. Afterwards: ```bash php scripts/admin.php status --targets=demo # what is live, and whether the web root still points at it php scripts/admin.php history --targets=demo # every deployment, recorded on the server itself php scripts/admin.php rollback --targets=demo # back to the previous release, in one symlink swap ``` --- ## Notes **The demo's data.** `demo/www/personnes.json` and `demo/www/clients.json` ship in every release and are not `shared`: each release starts from the committed copies, so a deploy resets whatever visitors added, and a rollback returns to the data of the release it goes back to. To keep the data across releases instead, list those two paths in the target's `shared`: they then live in `shared/`, symlinked into every release, and the first release seeds them. **What is not served.** The domain is served through `web`, so `releases/` and `deploy/` beside it are not reachable from mmoframework.fr. Inside a release only `demo/www` is served: `configs/`, `packages/`, `vendor/` and `mmo.json` sit above it. In `demo/www`, `index.php` is the one script run over http. **Assets from `vendor/`.** The demo's config names prism as `{basepath}/../../vendor/prism/*`, not `{vendor}/prism/*`: in a release `{vendor}` resolves inside `mmo.client.phar`, which holds `src` only, and every page would fail on the missing folder. The files are copied into `bin/` by the theme build; the page only needs their names, read from the release's own `vendor/`. **Build-only tooling is not shipped.** `vendor/google` (the closure compiler: a 30 MB jar plus two zips) is gone entirely since 3.0.13 — `MMO\client\JSCompiler` bundles the javascript in PHP, so there is no jar and no jvm to keep off the server. `node_modules` (28 MB of jsdom, for `npm test`) is excluded instead: the tests run here, never on the server. **The build's warnings.** `build` warns that it overlays `demo/www/bin` with no `buildCommands` configured: expected here, the theme is compiled by mmo.client's `package` hook instead. If it warns that the generated assets are older than the commit being released, that hook did not write them — run `php scripts/build-theme.php all --prod` by hand and read what it says. A "permission denied" on `demo/www/bin/*` means the dev web server wrote them as `www-data`: `sudo chown -R $USER demo/www/bin` once, and it stops happening. ### If another domain serves the webspace root `folder` is under `htdocs`, the webspace itself. If another domain of the webspace points at `htdocs` (or at `mmoframework.fr/`), then `mmoframework.fr/www/releases/` and `deploy/` are reachable through it: every release, and the journal. Guard each of them with its own deny-all — a copy of `scripts/deploy-deny.htaccess` as the `.htaccess` of `releases/` and of `deploy/`. **Never at the root of `folder`**: Apache applies every `.htaccess` on the path it serves, so a deny-all there makes mmoframework.fr answer 403 too.