tomo docs
home github

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 | sh

It 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 sh
Checksums. Every release publishes a SHA256SUMS 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/:

PathWhat it holds
config.tomlYour 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 | sh

You 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.