Search by

ad-go / sync

ad-go

Active-active database synchronisation for two CodeIgniter 4 instances over HTTPS (no JSON, no schema changes to existing tables).

v1.0.0 2026-10-21 00:28 UTC

This package is auto-updated.

Last update: 2026-10-21 00:33:56 UTC


README

Active-active synchronisation of CodeIgniter 4 applications over HTTPS: two or more nodes keep their databases and a shared directory identical, including nodes behind NAT. No JSON on the wire, no schema changes to existing tables, no SSH or root needed.

  • Database sync. A change journal and a per-row registry (uuid, revision counters, last agreed state) drive a three-way reconciliation: fast-forward, field-level merge, last-writer-wins for tolerant tables, quarantine for sensitive ones.
  • File sync. A directory (with sub-directories) is mirrored with a manifest, SHA-256 verification and atomic writes; concurrent edits keep both versions.
  • Topologies. Two dial-in nodes, a hub with passive nodes behind NAT (outbound-only), and relaying hubs.
  • Operations. Conflict admin UI, Telegram and e-mail alerts, a reachability check (sync:check), a distributed edit lock for Shield users.
  • Built on the official packages. Shield, Tasks, Queue, CodeIgniter's Email, Migrations, Language and Services.

Requires PHP 8.2+ and CodeIgniter 4.7. Tested with Shield 1.4, Tasks 1.0, Queue 1.0. Portable across MySQL/MariaDB, PostgreSQL and SQLite (query builder and Forge only).

Quick start

On every node:

composer require ad-go/sync
php spark sync:install            # publishes app/Config/Sync.php, migrates, seeds the registry

.env (the secret is identical on all nodes; node names are not):

sync.node     = site1
sync.peerNode = site2
sync.peerUrl  = https://www.example-b.com
sync.secret   = <64+ random characters>

Declare what is synchronised in app/Config/Sync.php:

public array $tables = [
    'categories' => ['policy' => 'tolerant'],
    'products'   => [
        'policy'           => 'tolerant',
        'sensitive_fields' => ['price'],
        'fk'               => ['category_id' => 'categories'],
    ],
];

Journal writes made through models by adding the trait:

class ProductModel extends \CodeIgniter\Model
{
    use \Q1\Sync\Traits\Syncable;
}

Schedule the jobs in app/Config/Tasks.php:

public function init(Scheduler $schedule)
{
    \Q1\Sync\Tasks\SyncTasks::register($schedule);
}

and keep the usual single cron entry: * * * * * php /path/to/spark tasks:run.

Exempt the API from CSRF ('csrf' => ['except' => ['sync-api/*']]) and serve it over HTTPS only.

Documentation

Document Contents
docs/ARCHITECTURE.md Data model, reconciliation algorithm, transport, file sync
docs/CONFIGURATION.md Every .env key and table option
docs/INTEGRATION.md Shield, Tasks, Queue, Email, Settings, Services, Language
docs/OPERATIONS.md Commands, NAT and relays, reachability check, locks, conflicts, housekeeping
docs/sync-db.html Illustrated walkthrough of the database sync
docs/sync-files.html Illustrated walkthrough of the file sync

Tests

composer install
composer test

The suite runs against in-memory SQLite. The real HTTPS round trip, the Telegram and SMTP calls and the admin views are exercised by hand.

Limits

  • Two nodes cannot be strictly consistent under a network partition without a third voter; the design minimises, logs and quarantines conflicts instead of hiding them.
  • Last-writer-wins depends on the nodes' clocks: keep NTP running, and mark data where that matters as sensitive.
  • Fields in ignore_fields are not transferred and need database defaults.
  • Out-of-band writes (query builder, raw SQL, insertBatch) are found by the scanner, not instantly.

License

MIT