← Engineering log
RobOS Preferences robos-lib/config-sync Google Drive Six-scenario E2E

Config Sync & Sharing: one RobOS setup on every machine, and in the hands of the people who need it

Setting up RobOS a second time used to mean doing it all again: movie folders, playlists, lights, the private network, every repository and MCP server. Now RobOS Preferences keeps ~/.config/robos and ~/.robos in a private Google Drive folder that every machine you own syncs with, and lets you hand chosen parts of it to someone else: your family, a new hire on day one, a coworker taking over a project. This post is about the decisions behind it, and the three things the end-to-end runs caught that a design review would not have. The how-to is the Config management guide.

RobOS Preferences on a new laptop: first sync with the vault, Load the vault onto this machine recommended, 23 files coming from the vault grouped by category
A new laptop meets the vault for the first time. RobOS sees that the laptop has much less configuration than the vault and recommends loading it. Every file is listed before anything is written.

Two kinds of folder, on purpose. The obvious design is one sync mechanism with permissions on top. We tried describing it that way and it got confusing fast: is my coworker editing my config? Does leaving a share delete my files? So there are two things with different rules. A vault is yours: one private folder, every machine reads and writes it, and it behaves like the IntelliJ settings repository, a per-file three-way compare between this machine, the vault, and the two as they were at the last sync. A share goes one way: you publish, they apply, and applying merges into their lists instead of replacing their files. Both use the same folder format (robos-config.json plus content-addressed blobs/), so the engine is one module. Only the merge rules differ.

The first sync is the dangerous one. With no shared history there is no way to tell which side changed a file, so RobOS asks: load the vault onto this machine, upload this machine, or choose file by file. Whichever you pick, files that exist on one side only are copied, and nothing is deleted. Every change to local files is preceded by a restore point. Deletions only happen after a common history exists, when the three-way compare can prove the other side deleted the file and this side did not touch it.

Credentials never travel, and pulling never signs you out. Credentials are stripped by key name (accessToken, password, keyPem, *_TOKEN…) before anything is uploaded, and references like pass paths are kept. The subtle part is the way back: a pulled cloud-accounts.json has no tokens, so writing it as-is would sign the machine out of the very Google account it is syncing through. On pull, RobOS puts this machine's credentials back into the incoming file, matching list items by id. A sign-in that exists only on this machine is kept and uploaded next time without its token. The one exception to "never travels" is opt-in: the Private Network join secret can ride inside a share encrypted with a passphrase, so family can join the home network without a separate invite.

Review on the desktop: movie-playlists.json comes from the vault, settings.json changed on both machines with Keep this machine's and Take the vault's from alex-laptop
Both machines changed the default IDE. The playlist the laptop added just comes over; settings.json waits for a decision and says which machine wrote the other version.
Sam's preview of the Rivera family home share: files marked New or Merge per category, and the storage section mapping Alex's Google Drive to Sam's own sign-in
Sam's preview of the family share. Merge means Sam's own rooms and playlists stay. Storage is honest: the movie files stay in Alex's Drive.

What the end-to-end runs caught. The scenarios run through the real Preferences window, each person and machine in their own home directory, against an in-memory Google Drive that enforces sharing the way Drive does. Three findings changed the design:

  • A config file that changes every time it is read. Cloud Accounts rewrites cloud-accounts.json with fresh updatedAt timestamps and recomputed login state on every read. Two machines would conflict on every sync forever. The fix is a per-file list of machine-local keys: login status, last-checked times, live TV casts, the mesh listen port. They are left out of the stored copy and always kept from the local one. A regression test now refreshes Cloud Accounts between syncs and asserts there is nothing to do.
  • A fresh machine is not empty. The first heuristic for recommending "load the vault" was "this machine has almost no files". Then the laptop scenario recommended the cautious file-by-file option: a brand-new install already had eleven files, because the default KGraph packages had been scaffolded. (That turned out to be a bug of its own: Preferences was starting a hidden copy of KGraph Explorer in its own process, fixed the same day. Other RobOS apps also scaffold those packages, so the heuristic stays.) The recommendation now ignores sign-in, profile and KGraph packages the vault also has, and compares the rest with the vault's size.
  • "Restore" was too blunt. A restore point covers everything one sync changed. When the test took back only the IDE setting, it also took back the playlist the laptop had added. Restore points now restore one file or all of them.

Shares remember what they added. Joining a share merges list items by id (rooms, lights, playlists, repositories, peers) and records each item it added. Leaving removes exactly those, plus any file the share created that you have not edited since. Priya's own notes project survives leaving Alex's handoff share, while the three projects the share brought are removed. Items you already had are never removed, even if the share updated them. Data sources are rebound to the recipient's own sign-in of the same kind, and the preview says plainly that the files themselves stay in the publisher's storage.

Jordan's Shared with me tab: Acme Platform onboarding from Alex Rivera with Update available version 2 and an Apply update button
Onboarding is not one-shot. The team adds a service, Alex publishes version 2, Jordan applies it.
Eve's Shared with me tab: Google Drive could not find that folder, or this account cannot see it
A forwarded link is not access. Eve was never added, so Drive refuses her account and nothing is written.

What is not done yet. Sync runs when you click it, or on an interval with the CLI's --watch; there is no background daemon. Google Drive and local (synced) folders are the two storage options; direct OneDrive, Dropbox and S3 backends would follow the same interface in config-sync-backends.js. Removing someone's access is done in Drive, not yet from RobOS. A conflict is resolved per file; merging two edits inside the same JSON file is a future step.

# Engine tests and the six end-to-end scenarios cd packages/robos-test Xvfb :99 & ROBOS_TEST=1 DISPLAY=:99 npm run test:config-sync ✔ exportBuffer removes credentials and writes the home directory as ${HOME}; importBuffer reverses it ✔ vault: machine 1 seeds a folder vault, machine 2 does a full load, then changes flow both ways ✔ an encrypted vault rejects a wrong passphrase and stores no plaintext ✔ shares over Google Drive: access is enforced, items merge by id, updates arrive, leaving removes only shared items ✔ 1. Full backup: Alex creates a Google Drive vault from the desktop and uploads everything except secrets and caches ✔ 2. Second machine: the laptop pastes the vault link, reviews, and loads the whole setup ✔ 3. Two-way sync: laptop edits reach the desktop, a both-sides edit is a conflict, and a restore point undoes it ✔ 4. Family: Alex shares the home network, movies and smart home with Sam; Sam keeps their own setup ✔ 5. Onboarding: Jordan joins the team share, sees what to sign in to, and later applies an update ✔ 6. Coworker handoff: Eve cannot open it, Priya joins and later leaves, keeping only her own projects # Full load of a headless machine node packages/robos-lib/config-sync-cli.js vault connect https://drive.google.com/drive/folders/<id> --account <google-account-id> node packages/robos-lib/config-sync-cli.js vault sync --use-vault