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— callsInstallation::module('Name')->install()Installation.php— versionedinstaller()/uninstaller()init/module.init.php— canonical routes and bootstrapinit/module.listeners.php— canonical listeners (may be an emptyregister())
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.
- New install: copy to
app/modules/{Name}/, rundainstall.php, copyinit/to the root, upsertdacore_modules. - Update: overwrite files, re-run dainstall, refresh init copies. A confirmation token may be required if the folder already exists.
- Uninstall: module
uninstaller()callbacks (descending version), deletedacore_installationsrows 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/orapp/modules/DACore/ - A second copy of DotApp
- Secrets; those stay in the target app’s
config.php - Tables named
items,dotapp_*, ordacore_*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.