Skip to content

Push / pull / versions ​

The day-to-day rhythm.

vsync push <env> ​

[1/5] zipping infra/vault/dev/
[2/5] sealing manifest ts=20260516-113304
[3/5] encrypting
[4/5] uploading 646 bytes → s3://muthuishere-vsync-e2e/vsync/dev/versions/20260516-113304.enc
[5/5] updating pointer → s3://muthuishere-vsync-e2e/vsync/dev/latest
✅ pushed vsync/dev (version: 20260516-113304)

Five steps:

  1. Zip the resolved vault folder (infra/vault/<env>/ by default).
  2. Manifest-seal — embeds the timestamp inside the encrypted plaintext (anti-rollback; see Crypto envelopes).
  3. Encrypt with AES-256-GCM using your per-(repo, env) AES key.
  4. Upload to s3://<bucket>/<repo>/<env>/versions/<ts>.enc.
  5. Update pointer — atomically replaces s3://<bucket>/<repo>/<env>/latest with the new version's timestamp.

If anyone's pulling concurrently, they'll see either the old version or the new — never a half-written one.

vsync pull <env> ​

[1/6] backing up local infra/vault/dev/ (if any)
      → /Users/muthu/.config/vsync/backups/dev-20260516-113414.zip.enc
[2/6] reading pointer s3://…/vsync/dev/latest
[3/6] downloading version 20260516-113343 (vsync/dev/versions/20260516-113343.enc)
[4/6] decrypting
[5/6] verifying manifest ts
[6/6] unzipping into /Users/muthu/projects/myrepo
✅ pulled vsync/dev version 20260516-113343

Six steps:

  1. Backup the current vault folder (encrypted with the same key) to ~/.config/vsync/backups/. Two-deep rolling buffer — older backups auto-pruned. See Recovering a local backup below.
  2. Read pointer — fetches the latest object to learn which version to pull.
  3. Download the version's .enc blob.
  4. Decrypt with the keychain key + PBKDF2 over the per-repo salt.
  5. Verify manifest ts — refuses to install a bundle whose embedded timestamp doesn't match the pointer. This rejects "pointer flipped to old version" attacks from anyone with bucket-write but not the key.
  6. Unzip into the resolved vault folder. Existing files are overwritten.

vsync versions <env> ​

s3://muthuishere-vsync-e2e/vsync/regression/versions/  (5 versions)
 * 20260516-180858    646 B    2h ago
   20260516-174208    644 B    5h ago
   20260515-201410    640 B    1d ago
   20260515-180858    646 B    1d ago
   20260515-152233    640 B    2d ago

Read-only list of every version on S3 for this (repo, env), newest-first. The * marker shows which version latest currently points at. No decrypt needed; only the disk config (S3 creds) is read.

Useful for:

  • Verifying your push landed.
  • Confirming a teammate's recent push before you pull.
  • Spotting unexpected versions (someone else's machine pushed?).

Switching envs mid-day ​

bash
vsync use production       # repoint ./.env → infra/vault/production/.env.production
bun run dev                # restart with prod creds (be careful!)
vsync use dev              # back to dev

See Switching envs.

Backups & recovery ​

Before each pull, vsync writes the existing vault folder to ~/.config/vsync/backups/<env>-<ts>.zip.enc (two-deep rolling buffer). The format is AES-256-GCM with the same per-(repo, env) keychain key + salt.

Time travel ​

Every push writes an immutable versions/<ts>.enc and none are ever pruned, so any version vsync versions lists can be pulled back:

bash
vsync versions prod                            # find the timestamp
vsync pull prod --at=20260523-100000 --backup  # retrieve it

--at is read-only against the remote — the latest pointer is never moved, and the manifest seal is still verified, so a version renamed on the bucket is still refused.

The one footgun

--at overwrites your local vault with older content. If you then run vsync push, that old content becomes the new latest — a silent revert. To just look at a past value, copy it out and then vsync pull prod to return to current.

Pair --at with --backup so your current vault is snapshotted before it's replaced.

Decrypting a bundle by hand ​

To decrypt one by hand (rare — pull itself is the recovery path 99% of the time):

  1. Get the key:
    • macOS: security find-generic-password -s tools.vsync -a <repo>/<env> -w
    • Linux: secret-tool lookup service tools.vsync account <repo>/<env>
    • Windows: Credential Manager UI under "tools.vsync"
  2. Get the salt: gunzip -c ~/.config/vsync/<repo>/env_<env> | jq -r .encryption.salt
  3. Envelope is RQE1 (4-byte magic) + 12-byte IV + AES-GCM ciphertext. Derive: AES-GCM key = PBKDF2-SHA256(keychain-key, salt, 600k iters).

In practice, don't lose the keychain entry, and you'll never need this.


Next: Onboarding teammates →

Released under the MIT License.