Designlof CMS user guide
A practical guide for this self-hosted core preview. Begin with an empty installation, then build a publishing workflow you can maintain. Menu names below refer to the included English interface.
1. What is included
Prerequisites: this core package and hosting you control. Steps: read this chapter before selecting a host or promising features to a client. Result: a clear boundary for the product you are installing. Recovery: if a feature described for an older full Designlof deployment is absent here, use this guide and the core package rather than copying premium modules.
The core includes posts, pages, structured rich text, nine visual page elements, image media, categories/tags, menus, Editorial and Journal layouts, five roles, revisions, recovery autosave, moderated comments, smart forms, SEO metadata, sitemap/RSS and optional AI/mail adapters. Each installation manages one website. No default account, site data, photographs, private configuration or customer records are shipped.
This is not compatible with WordPress plugins or themes. Storefront/payments, premium layouts, public signup, SSO, two-factor authentication, automatic updates, emailed account recovery, collaborative cursors and media compression/cropping are outside this core. Scheduled publication is query-time eligibility; it sends no timed email. Keep third-party notices and artwork/font licenses with any redistributed package. This distribution is version 0.4.0-core-preview; review it on an isolated installation before updating an existing website.
2. Requirements and hosting layout
Prerequisites: PHP 8.2+, JSON, sessions, fileinfo, PDO plus either pdo_sqlite or pdo_mysql; a private application directory writable by the application owner; terminal access to PHP CLI for ownership preparation and upgrades. ZIP is optional for declarative theme import/export; cURL is optional for AI. Steps: extract the package outside the document root; point the web server only at public/; configure HTTPS and private writable storage; run php dl doctor. Result: required checks pass and private files cannot be fetched over HTTP. Recovery: resolve failed extensions/permissions with the host; do not use world-writable 0777 permissions.
Use 0700 private directories and 0600 private files where the same filesystem user runs PHP and CLI. On a host with a separate web-service user, have the operator configure narrowly scoped ownership/group access; do not make credentials readable by everyone. PHP session storage must also be private. Configure upload_max_filesize/post_max_size above the intended image limit (the application limit is 5 MB); a lower host limit still applies.
Local loopback PHP testing and isolated Apache/PHP plus SQLite/MySQL/MariaDB lifecycles are tested. Existing-server Apache/MariaDB runtime rehearsal is separate evidence; it does not certify every hosting provider. Shared hosting must let you select public/ as the document root or provide an equivalent private split layout, run private CLI commands and protect storage. No specific cPanel/shared-host provider or one-click deployment is verified. On a VPS the operator must configure Apache rewrite/PHP, TLS and private permissions; this package does not provision a server or firewall.
For local testing only, set APP_HTTPS=false, APP_URL to your loopback URL and APP_SITE_MODE=cms, then run the PHP development server:
php -S 127.0.0.1:8080 -t public public/router.php
Production requires HTTPS and APP_HTTPS=true. Keep APP_INDEXABLE=false until you intend search indexing. Set a trusted APP_URL and any APP_BASE_PATH privately; never derive them from untrusted request data. Test /api/health and verify /.env, /storage/ and source files are unavailable from the web. The development server is not a production server.
3. Prepare the database and ownership access
Prerequisites: hosting checks pass; an empty selected database; privileges to create the CMS schema and read/write its data. Steps: choose SQLite for a private local file, or create an empty MySQL/MariaDB database and dedicated database user through your host; then run php dl cms:setup-key as the application filesystem owner. Read storage/cms-setup.key privately. Result: a 0600 one-time ownership key exists; no database tables were created. Recovery: an installed, unrelated, unavailable or unowned incomplete database is refused; restore its connection or inspect the original installation privately rather than clearing it.
SQLite uses the fixed private storage/cms.sqlite path. MySQL and MariaDB both use the PDO mysql driver and utf8mb4. Obtain host/port or an explicit Unix socket, database name, username and password from your host. Initial setup/upgrades need CREATE/ALTER/INDEX and runtime needs SELECT/INSERT/UPDATE/DELETE on that one database. Do not grant privileges on unrelated databases through this guide.
The command cms:prepare remains a compatibility alias for ownership preparation in the generic core; it no longer performs initial DDL there. Existing databases are upgraded with cms:upgrade, never reinstalled. Existing nonempty CMS_DB_DSN takes precedence; legacy DB_DSN is preserved when CMS_DB_DSN is absent. Conflicting nonempty current/legacy DSNs or credentials fail closed. If the server supplies a database, the wizard tests that configuration and does not replace it.
4. Complete the setup wizard
Prerequisites: private ownership key ready; browser session cookies enabled; required hosting checks pass. Result: one administrator and the default Editorial website are installed; setup closes. Recovery: use the original key to resume a recorded interrupted installation; never delete its journal or tables to force a fresh attempt.
The five stages below are the actual wizard labels: Welcome, Database, Your website, Review and Ready. Screenshots come from a real, disposable installation using MySQL / MariaDB. Desktop originals and readable mobile captures are included. Numbered markers in the browser guide point to the matching field rows; original image files are unedited.
Example values only: designlof_demo, cms_demo_user, The Publishing Notebook and publisher@example.test illustrate the fields. They are not bundled accounts or credentials. Your hosting provider supplies your own database values; you choose your own site identity. Passwords, setup keys and hidden session tokens are not shown. A database password is separate from your CMS administrator password.
Step 1 — Welcome

Check every hosting requirement, then prove ownership with the private key. Run php dl cms:setup-key in the application directory as its filesystem owner, then read storage/cms-setup.key privately through your authorized terminal or private file manager. The command reports the key's location; it does not print the key or create database tables. If the key has not been prepared, setup shows a preparation note instead of the input. Do not paste a key into a URL, support ticket, screenshot or chat.
| # / Field or control | Purpose | Safe example | Where to obtain it | Required / optional | Validation and recovery |
|---|---|---|---|---|---|
| 1. Hosting requirements | Confirm PHP, private writable storage, fileinfo and a supported PDO database driver | All checks show Ready | Your PHP/hosting configuration; run php dl doctor privately | All listed checks must pass; either SQLite or MySQL PDO is enough | Ask the operator to resolve Needs attention. Do not use 0777. Database choice comes after ownership proof. |
| 2. One-time setup key | Prove access to this fresh installation before choosing a database | Blank in the screenshot; never an example secret | Run php dl cms:setup-key and privately read storage/cms-setup.key | Required to unlock setup | Paste the exact 64-character key. A wrong key is refused; after too many attempts, wait 15 minutes. Keep the original key for interrupted-install recovery. |
| 3. Continue to database | Start the owned browser setup session | Continue to database | The button on this screen | Required to advance | A 419 means reload the form; do not reuse an old CSRF request. Setup sessions expire after 30 minutes. |
Step 2 — Database

Choose the database family before filling its connection fields. MySQL and MariaDB use the same MySQL / MariaDB option and PDO mysql driver. Create an empty database and dedicated database user through your hosting provider or authorized database administrator first. This wizard does not provision a MySQL server, create its database or create its database user.
SQLite alternative: choose SQLite — private local file. The fixed target is storage/cms.sqlite; the MySQL host, port, database name, user, password and socket fields do not apply. No host password is needed for SQLite. It needs the PDO SQLite driver and private writable storage. Connection testing does not create the SQLite file; the final installation does. On a MySQL-only PHP host, SQLite is disabled and MySQL / MariaDB is selected.
| # / Field or control | Purpose | Safe example | Where to obtain it | Required / optional | Validation and recovery |
|---|---|---|---|---|---|
| 1. Database type | Select the storage engine | MySQL / MariaDB, or SQLite — private local file | Choose a supported driver with your host/operator | Required | A missing driver is disabled. Ask the host to enable the matching PDO extension; do not switch targets to bypass an existing-site outage. |
| 2. Host | Address of the database server for TCP | 127.0.0.1 for local TCP; a provider may give db.example.com | Hosting database panel or administrator | Required with TCP; blank with an explicit Unix socket | Use the provider's actual host. 127.0.0.1 means this host; localhost can select a driver-default socket. Do not paste a full DSN or URL. |
| 3. Port | TCP listener port | 3306 | Hosting database panel or administrator | Required with TCP; blank with an explicit Unix socket | Enter digits from 1 to 65535. Use the provider's custom port if it differs. |
| 4. Database name | Select the existing empty database | designlof_demo | The database you created in your host panel | Required for MySQL / MariaDB; unused by SQLite | Use 1–64 letters, numbers or underscores. Include any host-added prefix. Existing unrelated tables or views are refused. |
| 5. Database user | Authenticate to that selected database | cms_demo_user | The dedicated user assigned to the database | Use the provider's required username; unused by SQLite | Up to 128 bytes. Blank works only if your provider's authentication explicitly permits it. Check this user's permissions on this database; this is not your CMS sign-in email. |
| 6. Database password | Authenticate the database user | Blank in the screenshot; enter your own private value | Your provider or private password manager | Required when the database user's authentication requires it; unused by SQLite | Never reuse a displayed example. It is not echoed; re-enter it when changing/testing the connection. Keep it out of URLs and logs. |
| 7. Socket path | Use a local Unix socket instead of TCP | /run/mysqld/mysqld.sock is an illustrative path | Your hosting operator; do not guess | Optional alternative for MySQL / MariaDB | Expand Connect with a Unix socket instead. Use an absolute provider-supplied path and leave Host and Port blank. Do not combine a socket with TCP fields. |
| 8. Test connection and continue | Validate the selected connection and inspect emptiness | Test connection and continue | The button on this screen | Required to advance | Reads metadata only: no schema/account creation and no clearing of existing data. Cannot connect means check driver, target, private credentials and privileges. A successful test still needs the final Install my website action. |
If the server already supplies CMS_DB_DSN or legacy DB_DSN, the screen explains that its configuration is preserved and does not show replacement connection fields. Test that existing connection; change its settings privately with the operator if needed. Conflicting current/legacy database settings fail closed. Never attempt a fresh installation against an installed database.
Step 3 — Your website

Choose the site's identity and the first administrator. The email is the future sign-in identifier, not a database username. No shared default administrator/password is shipped. Both secret fields are blank in the screenshot; enter your own password privately on your installation.
| # / Field or control | Purpose | Safe example | Where to obtain it | Required / optional | Validation and recovery |
|---|---|---|---|---|---|
| 1. Site title | Name displayed for your website | The Publishing Notebook | Choose your site's name | Required | Up to 190 bytes in this form. Non-ASCII characters can occupy multiple bytes. Keep a meaningful name; you can change it later in Settings. |
| 2. Your name | Display name for the administrator | Demo Publisher | Your chosen publishing identity | Required | Up to 100 bytes. Non-ASCII characters can occupy multiple bytes. This is a name, not a password or database account. |
| 3. Administrator email | Identify your CMS sign-in account | publisher@example.test is a non-deliverable example | Use your own valid email address | Required | Must be a valid email; it is stored lowercase. No activation/reset email is sent, so record the chosen identifier privately. |
| 4. Administrator password | Protect the new CMS administrator | Blank in the screenshot; choose a unique private password | Your private password manager | Required | Exactly 12–200 bytes, no null byte. Spaces count and are preserved; non-ASCII characters can occupy multiple bytes. It is never echoed. |
| 5. Confirm password | Prevent an accidental password mismatch | Blank in the screenshot | Re-enter the exact password you chose | Required | Must match every byte, including spaces. On a mismatch, both inputs stay blank: enter both again. |
| 6. Review installation | Save the checked identity privately and open the summary | Review installation | The button on this screen | Required to advance | This does not create the account or publish the site. Re-enter passwords when changing identity. Back to database returns to the connection step. |
Step 4 — Review

Check the summary against the choices you made. This capture follows the genuine tested example MySQL / MariaDB connection. The default layout is Editorial; Journal can be selected later in Appearance. Passwords are intentionally absent from the summary.
| # / Field or control | Purpose | Safe example | Where to obtain it | Required / optional | Validation and recovery |
|---|---|---|---|---|---|
| 1. Site title / Administrator | Confirm the identity being installed | The Publishing Notebook / Demo Publisher | Your previous Your website step | Required review; read-only here | Use Back to your details to correct it before installation starts. |
| 2. Sign-in email | Confirm the exact future CMS identifier | publisher@example.test | Your previous Administrator email field | Required review; read-only here | Check for spelling errors. This is separate from the database username. |
| 3. Database | Confirm the selected storage family and tested connection | MySQL / MariaDB — tested connection | Your successful Database step | Required review; read-only here | A tested connection is not yet an installed site. To change a fresh target, go Back through your details to Database and retest privately. |
| 4. Passwords | Confirm that secrets stay private | Kept private; never displayed | The passwords entered in prior steps | No input on this screen | The summary never reveals the key, database password, administrator password or stored hash. |
| 5. Install my website | Perform the final owned installation | Install my website | The final confirmation button | Required to finish a fresh installation | Creates the CMS schema and one administrator, records the unique installed claim, saves private configuration and closes setup. If interrupted, return with the same original key and use Resume installation; do not delete files/tables or switch targets. |
An interrupted installation shows Resume installation rather than a new-install action. Its database and administrator fields are locked. Completed work is verified and resumed; an existing account/password is not replaced. Repeated final requests cannot create a second owner. Back to your details is available only before the final installation has started.
Step 5 — Ready

This is the real success screen after the final installation, not a decorative confirmation mockup. Setup is closed. There are no connection, key or password fields here.
| # / Field or control | Purpose | Safe example | Where to obtain it | Required / optional | Validation and recovery |
|---|---|---|---|---|---|
| 1. Setup is closed | Confirm final installation succeeded | Your publishing space is ready. | The result of Install my website | No input | A completed installation must not be reinitialized. The success screen belongs to the finishing session; other visits to setup redirect to sign-in. |
| 2. Sign in to your CMS | Open the account's sign-in page | /admin/login; use the email you chose | The button on this screen and your private password manager | Next step to start publishing | Sign in with your chosen CMS email/password, not the database user. A database outage needs operator connection recovery; do not run setup again. |
| 3. Hosting and backup reminder | Prepare the installed site for normal use | HTTPS, protected private directories and a recoverable backup | Your operator and chapter 14 | Required before sharing a production site | Keep private configuration/backups out of the web root. There is no emailed password reset; private operator recovery is documented in chapter 11. |
Validation, expiry and interrupted-install recovery
Back, refresh and validation errors preserve the last safe server-side step; password inputs always stay blank. Re-enter administrator passwords when changing identity. Setup sessions expire after 30 minutes: unlock again with the original private key. A 419 means reload the form; 429 means wait for the setup rate-limit window. Never place credentials into a retry URL.
Cannot connect means verify the selected driver, host/port/socket, permissions and private credentials. A missing selected driver is a requirement failure. Existing unrelated tables/views are refused. Incomplete schema without the matching private journal and database claim requires operator review. An outage on a closed installation never unlocks fresh setup.
Setup state/configuration remain private in storage/cms-installation.json and storage/cms-config.json; neither belongs in source control, demos or releases. Keep the original journal during recovery. After success it contains closure metadata rather than account/password material. Configuration contains the necessary database credential and must remain private with 0600 permissions.
If the process stops after account commit or configuration promotion, resume with the same original key; completed work is verified rather than repeated. If setup is already closed but one-time-key cleanup was interrupted, the operator may remove the remaining private key after verifying the installed marker and closure record. A changed database target, mismatched ownership, unrelated schema or corrupt private state requires private investigation with a backup first. There is no automatic destructive cleanup.
5. First sign-in and settings
Prerequisites: installation succeeded and you have the private administrator password. Steps: open /admin/login, sign in, select Settings, change Site title and Description, choose New comments, then Save settings. Open View website / Visit site to inspect the public result. Result: saved identity appears on your website. Recovery: check exact email/password and browser secure-cookie settings; an HTTP-only local test needs APP_HTTPS=false, while production needs HTTPS. An installed database outage is an operator connection issue, not a reason to run setup again.
Use Profile for your own account changes. Account/password edits revoke existing CMS sessions. Configure origin/indexing privately; Settings does not change the trusted URL or search-indexing environment. New comments defaults to Closed site-wide. Forms, Appearance, Menus, Users, Plugins and Tools & health are administrator workspaces.
6. Write and publish your first post
Prerequisites: an administrator, editor, author or contributor account; optimized images you have rights to publish. Steps: create a category/tag if needed, upload your image in Media library, then open Posts and Add post. Fill Title; use Add content and Text & formatting to write. Add an Excerpt, choose a Featured image, assign Categories & tags and set URL slug. Keep Status at Draft and click Save. Result: a saved draft with an assigned ID; Preview saved shows the saved version. Recovery: preview reflects the last saved content, so Save before checking a change.
Upload through Media library, open Edit details and write meaningful alternative text and a caption. In the post editor use Image for media you uploaded. Text formatting supports headings, lists, quotes, links and tables. Pasted HTML retains only supported formatting. Raw HTML/scripts/iframes/arbitrary CSS are not accepted. Without JavaScript the Content source (JSON) fallback remains available; it requires validated block JSON rather than arbitrary markup.
When ready, review Preview saved, select Published under Status and a current/past Publication date (UTC), choose the intended Visibility, then Save. Contributors choose Pending review for an editor instead. Open the public permalink and check title, body, image alt text and URL. If it does not appear publicly, check status, publication time and visibility; do not change permissions merely to expose private content.
7. Scheduling, review, visibility and recovery
Prerequisites: saved content and a role permitted for the action. Steps: under Publish choose Scheduled and a future Publication date (UTC), then Save. Or select Pending review and ask an editor to review the saved draft through the normal team workflow. Result: scheduled public content becomes discoverable when its UTC timestamp is reached; no worker is required. Recovery: check the host clock and UTC conversion. The record remains Scheduled; no notification job changes it to Published.
Public content is discoverable through eligible lists/API/feed/sitemap. Private content requires authorized editors. Password protected content requires its content password; the permalink title/existence remain public. Uploaded media URLs remain public independently of a post’s visibility. Do not upload confidential files expecting post privacy to protect them.
Autosave stores a recovery copy after 15 seconds of typing inactivity; it does not publish or replace saved settings. On reload choose Restore unsaved text, inspect it, then Save. Revisions keeps the latest 20 prior saved versions; Restore as draft restores content/SEO fields without blindly replacing taxonomy or publishing permissions. Copy your unsaved text before resolving a concurrent-change 409 response, reload the newer version and merge deliberately.
Trash is reversible and restored content returns to Draft. Posts/pages use reversible trash and have no permanent content deletion interface. Unreferenced images can be permanently removed with Delete image. Non-administrator users with no owned content/media/revisions can be removed with Delete user; these operations are not a trash workflow and need a backup. Deleting a category/tag removes relationships, not posts. Keep backups; trash and revisions are not a full recovery strategy.
8. Build a page with nine core elements
Prerequisites: administrator/editor access, a page draft and any required images/forms. Steps: open Pages, choose Add page, then choose Visual (which first saves the text version), add a section, then add an element from the palette. Configure it, preview and save. Result: a structured page rendered through the selected standard layout. Recovery: use the source fallback only with valid structured JSON; copy work before reloading a version conflict.
The core palette provides Heading, Text, Image, Button, Form, Divider, Content, Posts and Categories. Images use Media library IDs and alternative text. Button links must be valid URLs. Form embeds a saved form; publish/open the form before expecting public submissions. Content contains supported rich-text blocks. Posts/Categories collections use eligible public content and taxonomy. Premium hospitality, agency, commerce, video and animated widgets are outside this package.
Switch between Text and Visual thoughtfully: switching saves current work first. Save a page as Draft, use Preview saved, review mobile layout and only then select Published. A collection can be empty when its posts are drafts, future-dated or non-public; publish suitable content rather than embedding private results.
9. Media, categories and tags
Prerequisites: an upload-capable role (administrator/editor/author) and rights to use your image. Steps: open Media library, expand upload, choose a JPEG/PNG/WebP/GIF image and submit; use search/filter, then Edit details to update alt text/caption or Copy URL. Result: a MIME-validated image served at its media URL. Recovery: check the host request limits, 5 MB application limit, 40-megapixel limit and writable private media storage. SVG, scripts and other file types are rejected.
Media displays 24 items per page. Authors manage their own media; editors/administrators manage the library. There are no generated thumbnails, crop tools or image compression. Optimize files before uploading. Never paste an upload path or private storage filename into a public page.
Open Categories & tags, create a term with its name/slug/taxonomy, then check it in the post editor and Save. Public taxonomy browsing uses eligible posts. Hierarchical taxonomies and page-parent trees are not implemented. If a term appears empty, check its relationships and public content eligibility.
10. Appearance and navigation
Prerequisites: administrator access and saved public content. Steps: open Appearance, preview Editorial or Journal, choose the layout and inspect View website. Open Menus, create a named menu, add valid links, assign header/footer locations and save. Result: the chosen layout/navigation appears publicly. Recovery: preview before changing an active layout; a layout change does not move data or create missing images/content.
Menus support two-level links. Empty menus/collections remain empty until configured. Import/export supports reviewed declarative DL Theme v1 packages when PHP ZIP is available; it is not a PHP/JavaScript plugin installer or a WordPress theme uploader. Keep package size/path/asset restrictions. The distribution includes Editorial/Journal only; no premium theme or remote marketplace is promised.
11. Roles and account recovery
Prerequisites: administrator access to Users for account management, or private server access for recovery. Steps: create the required named account and role in Users; test its own workflow through a separate browser session. Result: roles restrict actions and protected requests recheck current account version/role. Recovery: keep an administrator available; the UI does not demote administrators. Server migrations for role changes require deliberate operator review.
| Role | Publishing and administration |
|---|---|
| Administrator | All content, site settings, menus/themes, forms, users, plugins, tools and own profile |
| Editor | All posts/pages, categories/tags, media and comment moderation |
| Author | Create/publish/edit own posts and upload/edit own media metadata |
| Contributor | Own post drafts and review submission; cannot publish or edit published posts |
| Subscriber | Dashboard and own profile; no publishing |
There is no public registration, invitation system, custom capabilities, SSO or 2FA. The CMS identity is separate from the framework example authentication. Owner recovery uses php dl cms:password EMAIL with the new password on private stdin; never include passwords in shell arguments, chat, screenshots or tickets. Supply the exact intended bytes; adding a newline changes the password. This operation increments the account version and revokes prior sessions. There is no emailed password reset. A real credential change requires the owner/operator to handle the password privately.
12. Forms, comments and mail
Prerequisites: administrator access for Forms and private mail configuration if delivery is needed. Steps: choose Create form in Forms, define supported fields, set its button/success text and status, save, then embed the saved form on a page or use its public route. Submit only synthetic data while testing. Result: accepted entries appear under the form’s Responses action. Recovery: check published/open state, honeypot/CSRF/session expiry, rate limits and current schema; do not retry a real submission repeatedly without inspecting its existing receipt.
Notifications require the operator’s explicit private MAIL_* configuration; the clean default disables mail. Configure/test SMTP privately with a synthetic recipient before relying on delivery. The required private SMTP fields are MAIL_TRANSPORT=smtp, MAIL_HOST, MAIL_PORT, MAIL_ENCRYPTION (tls or ssl), MAIL_USERNAME, MAIL_PASSWORD and MAIL_FROM. The defaults are TLS/587 or SSL/465; use the values supplied by the provider and keep the password private. cURL is required. Stored entries and email delivery status are separate. Open Responses; review the status and use Retry email when available and CSV export only as an authorized administrator. The Export button produces CSV (up to 10,000 responses). CSV/export contains user data and belongs in private storage, not a demo or release. No real SMTP delivery is claimed by the local preview.
For comments, enable New comments as Open on opted-in posts in Settings and Allow comments on the post, then Save. New submissions are Pending. Editors/administrators use Comments to approve, hold, mark spam/trash and manage threads. Author email is not public; replies support three levels. Honeypot/rate limits/CSRF are protections, not an anti-spam guarantee. There are no notification emails or external anti-spam service in this core.
13. SEO, feeds and optional AI
Prerequisites: a trusted private APP_URL, intended indexing policy and eligible public posts. Steps: add SEO title and Meta description under Search appearance; review the SEO Meter and saved preview; check /sitemap.xml (also /cms-sitemap.xml) and /feed.xml. Result: eligible published content has structured search/feed metadata. Recovery: check visibility, timestamp, canonical origin and APP_INDEXABLE. SEO Meter is an editorial checklist, not a ranking prediction or promise of traffic.
Drafts, future and non-public content are excluded from public discovery. Turning indexing off is a crawler instruction, not access control. Public API endpoints only expose eligible public content. Use post privacy/roles for authorization, and keep uploaded confidential media off this public media system.
Optional AI needs privately configured AI_MODEL, AI_BASE_URL and AI_API_KEY plus cURL. Writers choose Suggestion type and Generate suggestion; review the result before using it. It never publishes automatically. Calls may incur charges and are limited by adapter budgets and per-user limits (3/minute, 30/day). Keys never go to the browser. The release tests use synthetic adapters, not a real provider request; verify your provider privately before relying on it.
14. Upgrades, backup, restore and troubleshooting
Prerequisites: a recoverable consistent backup, matching code and control of all writers. Steps: pause site writers/CLI jobs, snapshot the selected database plus media/themes/private configuration and matching code, test restoration into an isolated private instance, then run php dl cms:upgrade as the application owner using the existing database. Result: versioned additive migrations finish and php dl cms:status reports the schema. Recovery: keep writes paused after a migration error; MySQL DDL is not transactional. Resume only after diagnosis with the same code/database. Never run initial setup/prepare/install against an installed database.
There is no automatic updater. Review the new package/manifest and dependency notices, preserve all private configuration/runtime files and data, compare PHP requirements, and follow its upgrade notes. The browser does not run ordinary schema upgrades. Export in Tools & health is supported content transfer, not a full backup of accounts, media, form responses or all settings.
For SQLite, create a consistent backup with SQLite’s backup API or equivalent while coordinating writers; do not copy a live main file and ignore WAL. For MySQL/MariaDB use a transaction-consistent InnoDB dump with private existing credentials; keep passwords off command lines/logs. Coordinate shared media/theme writers across every site using the same storage. Protect backup directories/files at 0700/0600, retain internal hashes privately and never copy customer records to screenshots/demo fixtures.
Restore to a separate private database/storage location with no public routing, disabled mail/AI/indexing and matching code. Verify schema, counts, account roles/session versions, instance, content, media and forms privately. Before replacing live state, drain writers, preserve the newest state and decide how to reconcile newer writes. A snapshot restore discards later changes; do not silently restore over them or over a companion site’s shared storage.
Old code may reject tagged account AND protected-content password hashes introduced by the current core. After new account/password/content changes, code-only downgrade is not automatically compatible. Prefer a reviewed fix on the current core; if restoration is necessary, preserve new writes and review the reconciliation/data-loss decision with the owner. Never reset accounts merely to make a rollback work.
Troubleshooting: missing driver or fileinfo → enable the matching PHP extension; setup key unavailable → run private ownership preparation as the application user; 419 → reload and resubmit through a new CSRF form; 429 → wait for the rate-limit window; connection error → verify the selected target/driver/credentials privately; schema-not-current → backup and run private cms:upgrade; 409 content conflict → copy your work, reload and merge; empty public listing → check status/visibility/UTC; upload failure → check MIME/dimensions/host limits and storage; failed mail → inspect authorized entry delivery status and test SMTP privately. Logs should contain safe operational metadata, not credentials or form/customer records. An installed-site outage must never trigger reinstallation.