forgelab-me / ci4-updater
Self-update system for CodeIgniter 4 apps: an admin panel that checks a remote update server for new releases, downloads, diffs, backs up, and applies them, with automatic DB migrations. No SSH/git pull required.
Requires
- php: ^8.2
- ext-zip: *
- codeigniter4/framework: ^4.4
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.0
Suggests
- codeigniter4/shield: To protect the admin routes with group/permission-based authorization if you don't already have an auth system in place — see the Security section in the README.
README
A drop-in self-update system for CodeIgniter 4 apps: an admin panel that checks a remote update server (or GitHub releases) for new versions, downloads the release ZIP, diffs it against the live install (SHA-256 manifest), backs up changed files, applies the update, and runs pending DB migrations — all from the browser, no SSH/git pull required.
Every update leaves a backup the panel restores in one click, and releases can be signed so a compromised update server can't publish code to your apps.
Current version, PHP/CI/DB info, migration status |
A new release was found |
File-level diff, before anything is written |
Applied, with the DB migration run automatically |
Requirements
PHP 8.2+ · ext-zip · CodeIgniter 4.4+
Install
composer require forgelab-me/ci4-updater php spark updater:setup
updater:setup is a one-time step per app. It publishes an editable
app/Config/Updater.php, adds service('updater')->routes($routes); to
app/Config/Routes.php, and creates writable/updater_settings.json with the
two keys you have to fill in. Re-running it is safe: existing files are only
replaced after confirmation (or with -f), the routes line is added once, and
settings already set are left alone.
The panel itself is rendered from the package, so its improvements arrive with
composer update; point $layout at your admin layout and set $appName.
Pass --views if you'd rather own the markup.
Then, at minimum:
-
Set
VERSION,DATEandUSER_AGENTinapp/Config/Updater.php. -
Set
$layoutto your admin layout and$appNamein the same file. -
Make sure the route filter really restricts access — read Security first.
-
Point the app at a feed. Either run:
php spark updater:config --url https://updates.example.com/api/my-app
or edit
writable/updater_settings.jsondirectly:{ "update_server_url": "https://updates.example.com/api/my-app", "update_server_token": "" }Leave the token empty for a public feed.
updater:configwith no options prints what is currently set — the quickest answer to "why does the panel say no update server is configured?". See Update server for what that URL has to serve.
If your app uses Shield, running migrations creates a
settingstable — that belongs tocodeigniter4/settings, a Shield dependency, and has nothing to do with this package. ci4-updater ships no migrations and creates no tables.
Full details: Configuration.
Security
These routes can overwrite any file under app//public/ and run DB
migrations. Treat them as deploy access, not as an ordinary admin page: the
filter you pass to routes() is the entire boundary, so gate it on an
admin-only group or permission rather than "is logged in", and serve
update_server_url over HTTPS only — everything it returns gets written over
your application files.
The manifest's SHA-256 check catches a corrupted download, not a
malicious server — it comes from that same server. To close that gap, sign
your releases: set Config\Updater::$publicKeys and unsigned releases are
refused from then on. See Signing releases and
Security.
How it works
- Before cutting a release you run
php spark update:manifest. It hashes every file inSCAN_DIRS(SHA-256), writesmanifest.json— recording which directories the release covers — and bundles arelease_X.Y.Z_*.zipwith the manifest embedded. - You publish that ZIP and a
latest.jsondescribing it —ci4-update-serveris a ready-made server for this, or use GitHub Releases. - In the app,
/admin/updateschecks the feed, downloads and diffs the release, and applies it on confirmation — backing up every changed file towritable/backups/and running pending migrations. - If it goes wrong, the same panel restores that backup: files go back as
they were and files the update added are removed. Older backups are pruned
automatically (
$keepBackups, five by default). A restore reverts code and never the database — the panel flags backups whose update shipped migrations.
A release covers app/ and public/ by default and says so in its manifest,
so an app only ever touches what a release actually declares. Dependencies can
be shipped too — see
Shipping vendor/.
Step by step: Releasing an update.
Documentation
- Configuration — config reference, routes, custom settings storage, permissions
- Update server — the
latest.jsoncontract, your own server or GitHub Releases - Security — trust model, filters, recovery
- Signing releases — optional signature verification, off by default
- Releasing an update — release workflow and the full update pipeline
Contributing
Issues and PRs are welcome.
composer install
composer test
composer validate --strict
Keep changes focused, add or update tests for behavior changes, and note anything user-facing in CHANGELOG.md.
License
MIT — see LICENSE.



