Skip to content

DACore · coming December 2026

How the DACore plugin installer works

DACore can install, update, and uninstall other modules from a ZIP in the browser. That is the difference between “copy a folder onto the server” and “an operator with 2FA applies a package, watches a live log, and can take it back out without leftover rights and menu rows”. The installer is picky on purpose. A package that looks like a framework one-shot install is rejected. A package that cannot uninstall cleanly is a bug in the module, not in DACore.

Common mistakes

Wrong Right
ZIP contains root install.php. ZIP contains dainstall.php. Framework installers are copied by hand, never through this UI.
Module name in the namespace disagrees with Installation::module('Name'). PascalCase, matching, not DACore, max 64 characters, pattern [A-Za-z][A-Za-z0-9]*.
Skip init/module.init.php because “the root file is enough”. DACore copies init/ to the module root after a successful run. That is the activation step.
Migrations that fail halfway with no rollback story. A throw during dainstall.php fails the install and rolls back the copied folder.
Uninstall that only deletes the folder. Run uninstaller() first: rights group, menu prefix, AI tools, DROP your tables.

Required files (there is no module.json)

Validation is file-based. ModulePackage::validatePayload() looks for:

  • dainstall.php — calls Installation::module('Name')->install()
  • Installation.php — versioned installer() / uninstaller()
  • init/module.init.php — canonical routes and bootstrap
  • init/module.listeners.php — canonical listeners (may be an empty register())

Version is the highest semver key in installer(). Root stubs of module.init.php may exist until activation copies init/ over them.

<?php
use Dotsystems\App\Modules\TestInstalatora\Installation;

Installation::module('TestInstalatora')->install();

Install, update, uninstall

Upload is capped (50 MB). The ZIP is checked for magic bytes, entry count, compression ratio (zip bombs), symlinks, and path traversal. Extract happens under app/runtime/dacore-packages/, not directly into modules.

  1. New install: copy to app/modules/{Name}/, run dainstall.php, copy init/ to the root, upsert dacore_modules.
  2. Update: overwrite files, re-run dainstall, refresh init copies. A confirmation token may be required if the folder already exists.
  3. Uninstall: module uninstaller() callbacks (descending version), delete dacore_installations rows for that module, delete the registry row, recursive delete of the folder. DACore itself cannot be removed this way.

Audit goes to dacore_plugin_logs and a live trace in the UI. The Plugins screen requires dotapp.root or DACore.admin.installer.

Step-up 2FA

A setting (on by default) requires a TOTP, email, or SMS challenge before destructive install actions. Success unlocks a time-limited flag in DSM (on the order of 30 minutes). Stolen admin cookies should not be enough to drop a ZIP into production. Pair this with the login 2FA rules from login and devices.

Idempotent migrations

Updates re-run dainstall.php. Every version callback must check DACore:Installations@exist! and no-op if already applied, then @insert! with a status payload. CREATE TABLE IF NOT EXISTS is not enough if you also seed rows — seed must be safe to skip. The TestInstalatora installer follows that pattern; copy it rather than inventing a second log table.

What not to ship

  • Edits to app/parts/ or app/modules/DACore/
  • A second copy of DotApp
  • Secrets; those stay in the target app’s config.php
  • Tables named items, dotapp_*, or dacore_* for your data

FAQ

Why 50 MB?

Admin packages are PHP, views, and assets — not video dumps. If you need large media, store it outside the module ZIP.

What if dainstall.php throws?

The copied module folder is rolled back. Fix the installer, rebuild the ZIP, try again. Do not leave a half module in app/modules/ by copying by hand to “save time”.

When do I blank the root init files?

When the operator asked to export a package. DACore unpacks, runs dainstall.php, then copies init/ into the root. Do not blank DACore itself that way.

See also