Repository navigation
Installation
| Requirement | Why | Check |
|---|---|---|
| PHP ≥ 8.5 |
src/bootstrap.php answers HTTP 500 below the floor |
php -v |
mbstring, json, dom, libxml
|
the bootstrap refuses to run without them | php -m |
zip |
backups and the self-updater | php -m | grep zip |
curl |
update check, release download | php -m | grep curl |
vendor/autoload.php |
the bootstrap exits when it is missing | test -f vendor/autoload.php |
storage/ writable by the PHP user |
cache, index, logs, backups, rate-limit state | test -w storage |
| content root readable | documents live in content/ unless BROWSE_DIR says otherwise |
test -r content |
PHP 8.3 and 8.4 are not supported: 3.4.0 dropped them. Any 8.5.x patch is accepted, so a host on the 8.5 line is never bricked by a patch-level floor; 8.5.12 is the version the project is verified on.
server {
server_name docs.example.com;
root /var/www/docs/public;
index index.php;
location / { try_files $uri $uri/ /index.php?$args; }
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}.md.ini stays above the document root, so it cannot be downloaded.
Works out of the box: the root index.php, md.php and updater.php forward to the same engine
with asset URLs computed for the /public prefix. The shipped .htaccess must be honoured —
verify that GET /.md.ini returns 403/404, otherwise switch to layout A immediately.
php -S localhost:8080 -t public public/router.phpMounting the app under a prefix (https://host/docs/ or a symlink inside another site's docroot)
needs no configuration: the front controller derives its web prefix from SCRIPT_NAME, so assets,
links and the service-worker scope stay inside the prefix.
On a docroot-at-public install the updater is reached through the shipped public/updater.php
bridge, so /updater.php answers in both layouts.
chown -R www-data:www-data storage
chmod -R ug+rwX storage
chmod 640 .md.inistorage/logs/setup.log (the one-time setup code) is created 0600 and should stay that way.
?api=manifest returns the manifest with Content-Type: application/manifest+json
and the layout links to it, so nothing depends on the host knowing .webmanifest.
Do not "fix" the static public/manifest.webmanifest with a types { } block:
that block replaces the whole inherited MIME table (Debian's mime.types is
itself wrapped in one), and every .css/.js/.svg on the host becomes
application/octet-stream — the site then renders unstyled. Measured on nginx
1.24. If you need the static file typed, map only the extension:
location ~ \.webmanifest$ {
default_type application/manifest+json;
try_files $uri =404;
}| Host | Layout | PHP | Checked |
|---|---|---|---|
| Ubuntu 26.04, nginx, PHP-FPM per-site pool | A, mounted in a sub-directory next to another application | 8.5.12 | viewer, library, tags, search operators, missing document → 404, ?api=manifest answering application/manifest+json, sw.js served, tables fitting the reading column (734px in a 736px column, 0 clipped cells), machine API status / conflicts / install dry_run (949 files verified against the release archive), public/updater.php bridge |
Copy the new tree over the old one and keep content/, storage/ and .md.ini. Or let the
application do it: see Updates.
MD.Scribe 3.5.0 "Loom" · PHP 8.5 (verified on 8.5.12) · MIT · repository · releases