Sync Monitoring

Installation

Install and run the sync agent on a school's local database and on the online database.

The sync agent is a single self-contained executable. It reads pending changes out of a MySQL outbox table, pushes them to the monitoring server, and applies whatever the other side has queued for it.

Every school runs two agents — one against the school's on-site database and one against the online database. They use the same binary; only SYNC_SOURCE differs.

SideSYNC_SOURCECapturesAppliesHow it runs
School's on-site databaseLOCALUP tablesDOWN changesResident, --watch
Online databaseONLINEDOWN tablesUP changesOne-shot, under the task scheduler

Before you start

  • The school exists in the monitoring dashboard, and you have its code and sync token.
  • Its sync tables are configured, each with a direction and an apply order.
  • You can reach the MySQL database with a user that can CREATE TRIGGER and read every synced table.

The token is shown once

The sync token is only visible in the Add School dialog before saving. If it was not copied, issue a new one from the school's actions menu — the old token stops working immediately.

Install

Run the capture setup SQL

Changes are captured by database triggers, so each database needs its outbox table and triggers before an agent can do anything.

In the dashboard open the school, go to Sync tables, and press Setup SQL. Pick the database you are setting up — the script differs per side, because each side only captures the tables it owns:

  • Local database — creates triggers for the UP tables.
  • Online database — creates triggers for the DOWN tables.

Press Copy and paste it into a client that understands DELIMITER (SQLyog, HeidiSQL), or press Download .sql — the file is named <school code>-<side>-outbox.sql — and pipe it in:

mysql -u root -p school_database < riv001-local-outbox.sql

The script creates a sync_outbox table plus three triggers per synced table (sync_<table>_ai, _au, _ad). It is safe to re-run — it uses CREATE TABLE IF NOT EXISTS and replaces triggers.

The agent's database user needs DELETE on sync_outbox as well as reading and writing the synced tables: it clears out old pushed rows each cycle. The application's own user normally has this already.

Re-run this after adding a table

Adding a sync table in the dashboard does not create its triggers. Until you re-run the setup SQL on that database, the new table is configured but nothing is ever captured. The agent checks for this every cycle; the school's overview and the agent's own output both say when the triggers are out of date.

Place the binary

Download it from the dashboard: open the school, then Download agent on the Overview or next to Setup SQL. Pick the side and the platform. The dialog shows each file's SHA-256 checksum to compare against, and offers a .env for that side with the server URL, school code and SYNC_SOURCE already filled in.

Put the executable in a sync-agent/ folder inside the application root:

C:\laragon\www\pac_local\
├── app\
├── public\
├── sync-agent\
│   └── sync-agent-windows-x64.exe
└── .env

Only public/ is web-served, so the binary is not reachable over HTTP. Add sync-agent/ to .gitignore if the application root is a git repository.

Configure the environment

The agent reads a .env file from the directory it is run from. It does not search parent directories.

If the host application already has a .env with database credentials — a Laravel app, for example — append the four monitoring settings to it rather than creating a second file:

.env
MONITORING_URL=https://monitoring.example.com
SCHOOL_CODE=PAC001
SYNC_TOKEN=your-sync-token
SYNC_SOURCE=LOCAL

The agent accepts Laravel's spelling for the database settings, so DB_USERNAME and DB_DATABASE are read as-is and nothing needs duplicating. If the file has no database settings of its own, add them too — Command line lists every variable the agent reads.

Run it once

From the directory holding the .env:

cd C:\laragon\www\pac_local
.\sync-agent\sync-agent-windows-x64.exe

A healthy cycle prints one line and exits 0:

15:35:50  DOWN applied 3, skipped 1 · UP pushed 12                          1.2s

Only non-zero numbers appear, so a cycle that moved nothing reads idle rather than a row of zeros. Piped or redirected output switches to the timestamped long form instead — see Output.

If a required variable is missing the agent exits before connecting to anything, naming the variable it wanted.

Keep it running

The agent does one cycle per invocation, so something has to invoke it repeatedly. On the school's machine that is usually watch mode; on the online host it is the scheduler that is already there.

Verify the install

Open the school in the dashboard. Within a cycle or two you should see:

  • Last seen updating — the agent is reaching the server and authenticating.
  • Last sync updating — it is pushing.
  • Agent logs empty of errors, and Sync tables showing row counts for both sides.
  • Local agent or Online agent showing the version you installed.

If Last seen stays blank, the agent never authenticated: check MONITORING_URL, SCHOOL_CODE, and that the token matches. If it updates but nothing ever syncs, the capture setup SQL has most likely not been run on that database.

If every cycle fails with Table '<database>.sync_outbox' doesn't exist, that side has tables to capture but no outbox. Run the Setup SQL tab for that side — Online database for an ONLINE agent — against the database named in the message. A side with nothing to capture never reads the outbox, so the error only appears once it has tables of its own.

Building the binary

Compiled with Bun, which bundles the runtime into one file — the target machine needs no Node.js, no node_modules, and no install step.

apps/agent
pnpm compile:windows   # dist/sync-agent-windows-x64.exe
pnpm compile:linux     # dist/sync-agent-linux-x64

Cross-compiling works, so both targets can be built from either platform. The output is around 117 MB because it carries the runtime; that is expected.

The version comes from apps/agent/package.json, and the build writes it into the binary. Bump it there before building a release. The server is built from the same repository, so it treats that version as the latest. An agent reporting anything older, or no version at all, shows as out of date on the school's Overview and in the schools list.

Publishing it for download

The dashboard offers whatever pnpm release built for the server's own agent version:

apps/agent
pnpm release   # release/sync-agent-<version>-windows-x64.exe, -linux-x64, manifest.json

It builds both platforms, names them with the version, and writes a manifest with each file's size and checksum. The server reads that folder, apps/agent/release, unless AGENT_DOWNLOADS_DIR points elsewhere, and serves the files only to signed-in dashboard users.

  • Only the matching version is offered. If the folder holds another version, the dialog says so instead of handing out an agent that would show as out of date the moment it runs.
  • A half-copied file is never offered. Each file must be present at the size the manifest recorded.
  • Under Docker Compose the folder is mounted into the server read-only, and kept out of the image, so publishing a new agent needs no server rebuild. It is ignored by git too.

Agents before 1.0.0 report no version

They show as older than 1.0.0. They also lack Retry now on the Queue tab and the capture trigger check, and the dashboard says so where those would appear.

On this page