Repository navigation
Updates
The updater installs the release archive — the .zip asset attached to a GitHub release, which
ships vendor/. It never uses a branch tarball: a branch tarball cannot contain vendor/, so it can
never satisfy the manifest inside it, and the update would refuse itself.
GET /admin.php?action=update.check (or the Updates page) reads the GitHub releases API with a
15-minute cache and reports the version delta, release notes, and whether the release carries an
installable archive (archive, installable). The panel disables Apply when it does not.
POST updater.php?api_key=<key>&action=install&format=json&dry_run=1
The dry run downloads the release archive, refuses entries with unsafe paths, verifies every file
against the manifest embedded in the archive, and reports the files that would be replaced (to,
asset, verified, unchanged, steps). It writes nothing to the project — the archive lives in a
temporary directory under storage/state/ and is removed before the answer. It fails loudly when the
release has no archive instead of reporting success.
Because it judges the release and not the local manifest, "nothing to replace" means the release matches this installation, not merely the files match themselves.
ALLOW_UPDATE = true is required for the panel button; the machine API treats the key as the
credential.
- Resolve the release asset and download it once.
- Refuse any archive entry with an absolute path,
..or control characters. - Verify each file against the archive's own sha256 manifest — the first mismatch refuses the whole update.
- Back up every file about to be replaced to
storage/backups/app-<timestamp>/with abackup.jsondescribing the run. - Copy only the files that actually changed.
content/, storage/ and .md.ini are never touched. The updater does not run migrations,
does not change the database (there is none), does not touch services, cron, the firewall, DNS or the
PHP configuration.
ALLOW_RESTORE = true enables restoring application files from an app-* backup
(?restore=latest or ?restore=app-YYYYmmdd-HHMMSS), and restoring content from a content backup in
the panel. A restore always writes a …-pre-restore snapshot first.
With opcache.validate_timestamps = 0 (the recommended production posture) a code change is
invisible until PHP-FPM is reloaded:
systemctl reload php-fpm8.5 # or the pool your site uses| Message | Cause |
|---|---|
| "The latest release has no .zip archive attached" | the release was published without the asset — attach MD.Scribe-<version>.zip
|
| "Integrity check failed for …" | the archive does not match its embedded manifest; refused by design, retry or install manually |
| "ALLOW_UPDATE is false in .md.ini" | the flag is off |
| "The PHP zip extension is required" | install php8.5-zip and reload the pool |
MD.Scribe 3.5.0 "Loom" · PHP 8.5 (verified on 8.5.12) · MIT · repository · releases