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.
| Side | SYNC_SOURCE | Captures | Applies | How it runs |
|---|---|---|---|---|
| School's on-site database | LOCAL | UP tables | DOWN changes | Resident, --watch |
| Online database | ONLINE | DOWN tables | UP changes | One-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 TRIGGERand 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
UPtables. - Online database — creates triggers for the
DOWNtables.
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.sqlThe 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
└── .envOnly 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:
MONITORING_URL=https://monitoring.example.com
SCHOOL_CODE=PAC001
SYNC_TOKEN=your-sync-token
SYNC_SOURCE=LOCALThe 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.exeA healthy cycle prints one line and exits 0:
15:35:50 DOWN applied 3, skipped 1 · UP pushed 12 1.2sOnly 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.
pnpm compile:windows # dist/sync-agent-windows-x64.exe
pnpm compile:linux # dist/sync-agent-linux-x64Cross-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:
pnpm release # release/sync-agent-<version>-windows-x64.exe, -linux-x64, manifest.jsonIt 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.