Getting started
Install Tomo, set up a project, and start a two-way sync with another machine over SSH. Mostly one command.
Install #
Tomo is a single static binary with no runtime dependencies. The installer detects your OS and architecture, downloads the matching binary from GitHub Releases, verifies its SHA-256, and drops it in ~/.local/bin:
$ curl -fsSL https://tomo-sync.dev/install.sh | shIt needs only curl and a SHA-256 tool (sha256sum on Linux, shasum on macOS) — no sudo, no package manager. Install somewhere else with TOMO_INSTALL_DIR:
$ curl -fsSL https://tomo-sync.dev/install.sh | TOMO_INSTALL_DIR=/usr/local/bin shSHA256SUMS file next to the binaries. The installer downloads it and refuses to install on a mismatch. To verify by hand, compare sha256sum tomo against the entry for your asset (e.g. tomo-linux-x86_64, tomo-macos-arm64) in the release's SHA256SUMS.Build from source #
If you have a Rust toolchain, build it yourself. The workspace bundles SQLite, zstd, and rustls — there is no OpenSSL or system SQLite to install.
$ git clone https://github.com/jakequist/tomo $ cd tomo $ cargo build --release # the binary lands at target/release/tomo
For a fully static Linux musl build (the kind the bootstrap pushes to servers), see docs/RELEASING.md.
Sync with a peer #
tomo sync is the primary command. Name the peer once — an SSH target and the project path on that machine — and Tomo initializes the project, records the peer, bootstraps the remote binary, and starts a live two-way session in a single step:
$ tomo sync you@gpu-box:/home/you/proj initialized .tomo/ (private)
On the first run in a fresh folder, sync creates the .tomo/ state directory automatically (a starter config.toml and a stable replica id — nothing else in your tree is touched), then continues straight into the session. As a safety net it refuses to auto-initialize your entire home directory or a filesystem root.
Initialize a project (optional) #
You rarely need it, but tomo init still exists to create .tomo/ ahead of time — to pre-configure path rules, or to initialize a directory (like your $HOME) that auto-init deliberately refuses. It is idempotent.
$ tomo init initialized Tomo project in /home/you/proj/.tomo
On a terminal this opens a live view of the session — the event stream, a status heartbeat, and a conflict center a keypress away. Detach and it keeps syncing; re-attach from anywhere. See The interface for the full tour, or --plain for the classic line stream.
What lives in .tomo/ #
All of Tomo's state is project-scoped — there is no global config, nothing written to your home directory, /etc, or XDG folders. Everything is under <project>/.tomo/:
| Path | What it holds |
|---|---|
config.toml | Your path rules, history mode, and the recorded [remote] peer. |
db/ | The SQLite metadata and the content-addressed chunk store — your full file history. |
staging/ | In-flight transfers, written here and atomically renamed into place so a crash never leaves a torn file. |
state/ | The persisted index, the startup-scan cache, and the single-session lock. |
bin/ | On the remote side only: the pushed tomo-<version>-<triple> binary. |
logs/ | Session logs. |
.tomo/** is hardcoded-ignored at the lowest layer of Tomo — it is never watched, synced, or versioned, and no configuration can change that. (Same idea as git never syncing .git.)
One session at a time #
A live session holds an advisory lock on .tomo/state/session.lock for its lifetime, so a single project can never have two sessions racing its tree, index, and history. A second tomo sync is refused fast, naming the holder's pid and age:
$ tomo sync error: another session is already running (pid 48120, started 3m ago)
Upgrading #
Re-run the installer to get the latest release:
$ curl -fsSL https://tomo-sync.dev/install.sh | shYou don't need to touch the server. On the next tomo sync, the bootstrap checks the remote binary's version against your local one; on any mismatch it pushes a fresh matching binary over SFTP, verifies its SHA-256, and execs it. The version is re-checked in the handshake before any index is exchanged, so both ends always end up speaking the same protocol.