4.12 Moving the Legacy Farm Clients
Every farm PC running the agent of the farm MVP moves to the new farm sync client through the MVP's own update channel, silently and without losing a night of data (decided 2026-11-10). The MVP's agent receives one last Runtime, the bridge, which installs the new client from releases, hands it the farm's enrolment and source settings, waits until the new client has synced, and then retires itself. The MVP (moonitoring-legacy-farm_mvp) is otherwise frozen and is deleted once no farm depends on it.
What exists today
The facts about the MVP's updater come from its code on 2026-11-10, read for the security review of that day. Nothing on this page is built yet.
4.12.1 What the Legacy Channel Can Do¶
- On every farm PC the Launcher starts each night, from a per-user scheduled task, and runs the Runtime named by a manifest it downloads from the MVP's API. The Runtime can be replaced; the Launcher, the scheduled task and the URLs built into the Launcher cannot: a new Launcher only arrives when the farmer runs a new Setup.
- Everything runs as the farmer's Windows user, never elevated. Only AfiMilk farms have one task that runs as
SYSTEM, installed by Setup with a UAC prompt. - The channel is trusted by TLS alone: the manifest carries a SHA-256 but no signature, and the executables are not code-signed. The bridge therefore trusts nothing it downloads through this channel except itself.
4.12.2 Requirements on the New Client¶
The move is silent only if the farm sync client meets these from its first release (moonitoring-app-farm_sync):
- It installs and runs per user, without elevation: files in
%LOCALAPPDATA%\MooNitoring\SyncClient, a per-user scheduled task or log-on start; a Windows service only as an optional, elevated upgrade later. - Its installer is published on a
releaseschannel, so its manifests are covered by the channel's TUF trust chain (Signing). Executables are not code-signed for now, in prod too (decided 2026-11-10): Windows may show its SmartScreen warning at the first run; Authenticode signing is added when it is decided. - It accepts a hand-over bundle at first start (below), redeems the enrolment code with a key it generates itself, and deletes the bundle.
- It reads the same export folders the legacy agent writes for each vendor, so the first raw upload needs no new set-up at the farm.
4.12.3 How a Farm Moves¶
- Before a farm moves, staff give it an Organization in
accessand a sync client infarm(the move of the MVP's farms and users into the platform is its own procedure), and enable the move for that farm in the MVP. Farms move in waves: one pilot farm, then a tenth of them, then the rest, each wave after the previous one has synced for a week. - The bridge is a normal Runtime release of the MVP: on a farm not yet enabled it behaves exactly like the current Runtime, so publishing it changes nothing until staff enable a farm.
- On an enabled farm, the bridge asks the MVP's API, with the device's own secret, for its move ticket: the
releaseschannel to install from and a one-time enrolment code that the MVP's API obtains fromfarm(POST /sync-clients/{client_id}/enrolment-codes) with a staff service account. The code lasts 24 hours; an unused one is replaced the next night. - The bridge downloads the new client from
releasesand verifies it against the channel's TUF root, which is built into the bridge (and the Authenticode signature, once executables are signed). A failed check stops the move and is reported; the legacy agent goes on as before. - It installs the client per user and writes the hand-over bundle: the enrolment code, the farm's vendor sources with their export folders, and the source credentials the legacy agent holds, encrypted with Windows DPAPI for the farmer's user and readable only by that user. The bundle is never passed on a command line or logged.
- The new client redeems the code, deletes the bundle, and starts syncing. Both agents now run each night, each to its own backend, so no night is lost while the new path is proved; staff compare the two.
- After seven nights in which the new client has completed a sync run, the bridge retires the legacy agent: it deletes its scheduled task, its device secret and its source credentials, keeps its logs for 30 days, and tells the MVP's API, which revokes the device. The Launcher's file stays until the logs go, since a running process cannot remove itself.
- If the new client has not synced within 14 nights, the move is reported and paused for that farm; the legacy agent keeps running until staff resolve it.
AfiMilk farms need one elevated step: the legacy SYSTEM task cannot be removed without administrator rights, and the new client's AfiMilk exporter needs them too. The new client asks for it once, with a UAC prompt, at a moment the farmer is at the PC; until then the old task stays.
4.12.4 Security of the Move¶
- The bridge itself arrives through the unsigned legacy channel, as every Runtime does today. Before it is published, the MVP's release environment gets required reviewers and a branch rule (security finding MVP-H3), so only a reviewed build can become the bridge.
- Everything the bridge installs is verified against keys the MVP never held: the TUF root of
releases, built into the bridge (and the Authenticode certificate, once executables are signed). - The enrolment code reaches the bridge only over the device's authenticated session with the MVP's API, is single-use, and lasts 24 hours.
- Secrets cross from the old agent to the new one only on the PC, encrypted for the same Windows user, and are deleted from the legacy agent once the move completes.
4.12.5 What Each Part Needs¶
| Part | Work |
|---|---|
moonitoring-app-farm_sync |
The requirements of 4.12.2; reading the hand-over bundle |
releases |
A channel for the client, its installer, and the public manifest path the bridge reads; the installer also installs for a signed-in person and writes the same bundle (4.12.6) |
farm |
Nothing new: sync clients, enrolment codes and sync runs exist |
| The MVP | The move ticket endpoint, the per-farm switch and waves, the bridge Runtime, the protected release environment |
| Staff tooling | Creating the Organizations and sync clients of the MVP's farms; following each wave |
4.12.6 The Hand-over Bundle¶
The bridge hands the farm to the new client in one file (decided 2026-11-10, read by the farm sync client): %LOCALAPPDATA%\MooNitoring\SyncClient\handover.bin, written after the bridge installed the client and before it starts it, at most 1 MiB.
The installer writes the same file when a person installs the client and chooses a farm (decided 2026-11-10): it signs the person in through the browser, registers the farm's sync client, issues its enrolment code with the person's token and puts that code in the bundle, with sources empty, so the client starts already connected and the person signs in only once (Installer and Client Library).
- It is the output of
CryptProtectDatafor the current user (never the machine scope), with the entropyMooNitoring.SyncClient.HandOver.v1, so only the same Windows user on the same PC can read it. It is never passed on a command line, in an environment variable or in a log. - Its content is UTF-8 JSON, and unknown fields are refused, so a bridge and a client of different formats never half-understand each other:
{
"format": "moonitoring.farm_sync.handover/v1",
"enrolment_code": "7K2QD-M9XBA-4RT6W",
"sources": [
{"vendor": "delpro", "export_folder": "C:\\DelPro\\Export",
"source_feed": "<farm feed id>", "origin_version": "<origin version id>",
"credential": {"user": "…", "password": "…"}}
]
}
| Field | Required | What it is |
|---|---|---|
format |
yes | moonitoring.farm_sync.handover/v1 |
enrolment_code |
yes | The one-time code of the move ticket |
sources[].vendor |
yes | The legacy agent's key for the vendor: letters, digits, -, _ |
sources[].export_folder |
yes | The absolute folder of that vendor's exports |
sources[].source_feed, origin_version |
no | The feed of farm and the origin version, when the move ticket names them |
sources[].credential |
no | The fields of the source credential, by name |
At its first start the client unprotects and checks the bundle (one of another user, entropy or format, or with an unknown field, is deleted and reported), redeems the code with a key it makes itself, keeps each credential in the operating system's credential store, assigns each folder to its feed, and deletes the bundle. If farm cannot be reached the bundle stays for the next start; if the code is refused the bundle is deleted.