omnipresent peer-to-peer file store
See DESIGN.md for the full architecture: iroh-based hierarchy-agnostic
networking, mptsync (Merkle-Patricia Trie anti-entropy) for metadata, bao/BLAKE3
hash-tree content addressing with verified random reads, static + DNSSEC-based
membership, per-node published versions, and SQLite-backed local metadata.
Install Rust and the pinned Lean 4.33.1 toolchain through elan. The executable Lean core owns the migrated Trie and CAS operations and is statically linked into every build; no feature flag or Rust fallback exists. Remaining Rust orchestration and the scoped proof claims are documented in the Rust/Lean architecture and proof contract. Supported platforms are Linux GNU and macOS (x86-64/arm64), and Windows x86-64 using the GNU/LLVM toolchain. OpenBSD and Linux musl are unsupported. See Lean core build instructions for Windows setup and the remaining verification boundaries.
cargo build --release # both binaries
cargo test --workspace # the full suite; binds loopback only, no network
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --checkThe binaries are target/release/synch (CLI and daemon) and
target/release/synch-s3 (S3 gateway). SQLite is compiled in and TLS is rustls,
so neither needs a system SQLite or TLS library. Lean's runtime is statically
linked; platform system libraries (including libc) are still required.
ghcr.io/afk-surf/synchronicity/tools, built from Dockerfile
by .github/workflows/tools-image.yml for
linux/amd64 and linux/arm64. latest follows main; tagged releases get
X.Y.Z and X.Y; every build is also tagged sha-<commit> and carries signed
build provenance (gh attestation verify oci://… / cosign verify-attestation).
The control plane is a separate service in a separate language and keeps its own
image, ghcr.io/afk-surf/synchronicity/control-plane.
One image holds all three Linux programs — synch, synch-s3 and synch-dp —
because they are deployed together: the gateway is a control client of the
daemon and opens no database of its own, and the data plane embeds the same
engine. There is no entrypoint wrapper, so the command names the tool:
docker volume create synch-data
# One node, its data directory on a named volume. A fresh named volume
# inherits the image's ownership, which is how the non-root service (uid
# 10001) gets a writable data directory on first run.
docker run --rm -v synch-data:/var/lib/synch \
ghcr.io/afk-surf/synchronicity/tools \
synch init --domain cluster.example.com
# The gateway's port is published here, not when it is started: a
# container's ports are fixed at `docker run`.
docker run -d --name synch-node \
-v synch-data:/var/lib/synch \
-v /srv/media:/srv/media \
-p 9000:9000 \
ghcr.io/afk-surf/synchronicity/tools \
synch daemon run
# Every other command is a control client of that daemon, so it runs in
# the same container against the same data directory.
docker exec synch-node synch source add media /srv/media
docker exec synch-node synch source scan media
docker exec synch-node synch-s3 bucket add media media --read-write
printf '%s' "$S3_SECRET" |
docker exec -i synch-node synch-s3 access-key add AKIAEXAMPLE --secret-stdin
docker exec -d synch-node synch-s3 serve --listen 0.0.0.0:9000- Volumes.
/var/lib/synchis the data directory — the database, the CAS and the control socket — andSYNCH_DATA_DIRpoints at it. That default is a path and not a policy, but it is not optional either: without it the CLI asks the platform for a data directory and a container with noHOMEhas none. Source directories are mounted separately, wherever you like. The data plane'sSYNCH_DP_BASE_DIRis/var/lib/synch-dp, a plain directory rather than a volume, because that state is genuinely ephemeral — one directory per hosted tenant, restored from object storage after a reschedule. - Ports. Only the gateway has a fixed one: 9000/tcp, and
synch-s3 servebinds127.0.0.1unless--listensays otherwise, so a published port needs--listen 0.0.0.0:9000and real access keys (--anonymousrefuses anything but loopback). The daemon's QUIC endpoint takes an ephemeral UDP port unless--bindnames one. - Nothing else is configured. Every setting that decides what a container is — the membership domain, the CAS backend and its credentials, the data plane's control URL and token — stays unset and required, so a misconfigured container fails at startup rather than running as something nobody asked for.
- glibc, including Linux release tarballs. These are long-running servers; the image is built against the runtime's own glibc 2.36, which is the configuration the test suite runs on.
Every published image is then run as a node:
ops/image-smoke.sh does what this section describes —
init, daemon run, a source scanned and published, synch cat reading it
back, and synch-s3 serving the same object over HTTP — and checks what only
running it can check: all three binaries present and built for this
architecture, synch-dp reaching its configuration check, a data directory the
uid-10001 service can write, the gateway finding the control socket, and a
clean shutdown on synch daemon stop. It runs after the push, against the
image the registry now serves, so an image is available as soon as it builds
and the workflow says whether that image runs.
To build and test it locally, from the repository root:
docker build -t synch-tools:dev .
./ops/image-smoke.sh synch-tools:devSet up a node. synch init creates the datadir, synch daemon start launches
the owner process in the background, and operational commands are gRPC calls to
that daemon over a local control socket:
synch init --domain cluster.example.com # or plain `synch init` for a key identity
synch daemon start # returns when the control socket is ready
synch source add media /srv/media
synch source scan media # hash, publish a signed root
synch id # print the origin, device key, and address
synch daemon status # what the running node is doing
synch daemon stop # ask it to shut downThe socket is <data-dir>/control.sock (a \\.\pipe\synchronicity-… named pipe
on Windows), 0600 inside a 0700 data directory, authenticated by a token
regenerated on every daemon start and sent as a header on every call. The service
schema is crates/synch-cli/proto/control.proto. With no daemon running, every
operational/client command fails with a message naming that socket.
During startup recovery, the bound control socket responds with a retryable
Unavailable error naming the current stage and elapsed time. Commands become
available after the separate control ready: announcement; socket existence
alone is not readiness. Recovery progress is printed every five seconds. With
SYNCH_LOG=info, peer diagnostics identify dial and exchange waits and failures.
Recovery contacts up to eight peers concurrently, with per-peer deadlines;
publishing remains blocked until own-head and cloud durability recovery finishes.
Under launchd or another supervisor, inspect its captured daemon output and retry
synch daemon status until it succeeds. synch daemon start performs this
authenticated readiness check itself.
synch mcp serves the Model Context Protocol over stdin and stdout, so an
editor or agent runner can browse and publish into the tree. It is a client of
the daemon like every other command — it holds no key, no store and no endpoint
— and the daemon does not have to be running when it starts.
synch mcp # read-only, every space
synch mcp --allow-write # plus the tools that change state
synch mcp --space media # confined to one spaceThe surface is read-only by default and the tool list says so, so a client is
shown exactly the authority it was given. Paths are also addressable as
resources at synch://<space>/<path>. Both protocol eras are served: the
stateless 2026-07-28 revision and the older initialize handshake.
docs/MCP.md has the tool table, the failure semantics, and the bounds.
Serverless mode runs the daemon on an ephemeral local volume while keeping file content in S3, Google Cloud Storage, or Azure Blob through OpenDAL. SQLite is still the metadata database; run Litestream beside the daemon so that database, including the device identity, survives replacement. This is a long-running, scale-to-one peer, not request-scoped FaaS.
An S3-backed node can be bootstrapped like this (standard AWS environment or workload credentials are used for authentication):
export SYNCH_DATA_DIR=/run/synch
export SYNCH_CAS_BACKEND=s3
export SYNCH_S3_BUCKET=my-synch-cas
export SYNCH_S3_REGION=us-east-1
export SYNCH_CAS_ROOT=nodes/production
export SYNCH_CAS_CACHE_BYTES=10737418240 # 10 GiB maintenance target
export SYNCH_REPLICA_CONCURRENCY=16 # concurrent CAS roots per replica
synch init --domain cluster.example.com # once, only if restore found no database
synch daemon start
synch source add media --apiUse SYNCH_CAS_BACKEND=gcs with SYNCH_GCS_BUCKET (and optionally
SYNCH_GCS_CREDENTIAL_PATH), or SYNCH_CAS_BACKEND=azblob with
SYNCH_AZBLOB_CONTAINER, SYNCH_AZBLOB_ACCOUNT_NAME, and credentials supplied
by the Azure environment or SYNCH_AZBLOB_ACCOUNT_KEY. S3-compatible stores can
set SYNCH_S3_ENDPOINT; GCS and Azure have corresponding
SYNCH_GCS_ENDPOINT and SYNCH_AZBLOB_ENDPOINT overrides. Run synch --help
for the complete provider options. The storage-policy flags are --cas-root,
--cas-cache-bytes, and --cas-upload when environment variables are not
used.
Only API sources are valid on a cloud-backed node. They have no scanner,
watcher, or local directory: gateway writes, synch put, synch fetch, and
synch adopt path ingest directly into the cloud CAS, while cat, get, and
gateway reads fill the ephemeral range cache on demand. A durable-disk node can
configure a replica checkout when a filesystem projection is wanted elsewhere.
The default SYNCH_CAS_UPLOAD=own+pinned uploads content created by this node
and peer content it pins. Use own to upload only locally created content, or
all to make the node a durable replica of every object it fetches completely.
SYNCH_CAS_CACHE_BYTES is a maintenance target, not a per-request hard limit;
on Unix, omitting it targets at least 20% free space.
Replica convergence fetches 16 distinct CAS objects concurrently by default;
set SYNCH_REPLICA_CONCURRENCY or --replica-concurrency to tune it from 1 to
256.
For production deployment:
- Restore
<data-dir>/synchronicity.dbfrom Litestream before starting the daemon; initialize only when no database exists. - Replicate that database continuously while the daemon runs. The replica contains device secret keys, so protect it separately from the CAS prefix.
- Run exactly one daemon for a data directory/identity (
replicas: 1, with a recreate rather than rolling-update strategy) and allow at least 30 seconds for SIGTERM shutdown. - Do not apply expiration or deletion lifecycle rules to the final
cas/prefix. Synchronicity deliberately leaves final cloud objects append-only; local deletion removes only metadata and cache files.
A write is acknowledged only after its payload and Bao outboard are stored in the provider and the durable metadata and published records commit. Provider errors therefore fail writes closed; cold reads fall back to cache or peers when possible. The provider is trusted to preserve acknowledged bytes—Synchronicity does not independently detect corruption at rest. The cloud CAS is sufficient as the only durable store for file bytes, but Litestream and signed peer heads remain necessary to recover filenames, versions, and pins.
To convert an existing node, first ensure every local source is API-only, stop the daemon, supply the destination provider settings above, and run:
synch cas migrate --to s3 # or gcs / azblob / localMigration copies every candidate before atomically changing the configured backend and is safe to retry after interruption. See docs/SERVERLESS.md for the full durability, recovery, failure, and Kubernetes deployment contract.
synch-dp is the managed side of the same idea: one process hosting a fleet of
serverless replica nodes, one per customer network, each joining that network as
an ordinary zone-named device and replicating everything it publishes into
object storage. A customer turns it on with one per-network switch in the
control plane and needs no new protocol, no configuration and no upgrade — they
gain a member that fetches eagerly and serves well.
Each data plane is named in the control plane (controlplane dataplane register dp-1) and its key names it, so which networks a pod hosts is one
column there rather than arithmetic each pod does for itself. The pod is told
only where the control plane is and which token to present.
export SYNCH_DP_CONTROL_URL=https://cp.example
export SYNCH_DP_TOKEN=synchdp_… # `controlplane dataplane-key mint <name> --dp <dp-id>`
export SYNCH_DP_BASE_DIR=/run/synch-dp # ephemeral; nothing here survives a restart
export SYNCH_DP_CAS_BACKEND=s3
export SYNCH_DP_S3_BUCKET=synch-hosted
export SYNCH_DP_S3_REGION=us-east-1
synch-dpMembership uses DNSSEC plus Rekor zone-key transparency by default. A private
deployment whose DNSSEC root is intentionally absent from the public log must
state that trust choice explicitly with SYNCH_DP_REKOR=off, just as a regular
node would use synch --rekor off.
It runs on pods with no durable disk, so it replicates each tenant's SQLite
database to the bucket itself — Litestream's LTX format via the celld-ltx
library, driven in-process rather than by a sidecar — and restores it on every
reschedule. Everything durable about a tenant is keyed by network rather than
by pod, so a rescheduled data plane resumes the same identities with no zone change
at all. Those streams carry device secret keys, so give the bucket encryption
at rest and do not grant the db/ prefix more widely than the tenants/ one.
docs/CLOUD-DATAPLANE.md is the design: the control-plane API it polls, the tenancy and storage model, the failure matrix, and what hosting does and does not promise about privacy.
Admit a peer. Trust is unilateral, so each side runs this for the other:
synch trust add <their-device-key> # the key is the identity; names come from zones
synch domain set cluster.example.com # or DNSSEC membership instead
synch domain refresh # re-resolve the membership zone nowDelegate space-restricted access. A node whose own key is already trusted — statically or through DNS — can admit one other device key to a named list of spaces, without touching the zone and without anyone else's configuration:
synch delegate add <their-device-key> --space photos --space incoming --until 7d
synch delegate ls # every delegation this cluster honors
synch delegate rm <their-device-key>Nothing is handed to the delegate: the grant is a record in the issuer's own trie, so
every member learns it through ordinary replication and admits the key on its own.
The delegate joins from the other side with the commands it would use anyway —
synch init, then synch domain set <domain> or synch trust add <issuer-key> —
because trust is unilateral and the two directions are separate problems.
A delegate sees the spaces it was delegated and nothing else, down to the filenames: peers serve it a projection of each trie covering those spaces, and it verifies the same signed root everyone else does. Withdrawing a delegation deletes the record, and that propagates the way every deletion does. A delegate cannot delegate further — a grant is only read from an origin whose own trust is static or DNS.
Membership from DNS refreshes itself: the daemon re-resolves each configured domain when its TTL runs out, and again — rate-limited — when a peer this node holds no binding for tries to connect, which is what the far side of a lagging key rotation looks like. A resolver outage fails closed: the cached bindings keep their own expiry, and the member set shrinks toward static-only rather than falling open.
Resolution travels DNS-over-HTTP(S) only — https://1.1.1.1/dns-query by
default, or any endpoint named with synch daemon run --doh <url>
(SYNCH_DOH). Plain http:// endpoints are accepted for internal networks:
answers are DNSSEC-validated in process either way, so the transport carries
nothing trusted. For an internal zone signed by its own root,
--dnssec-anchor /path/to/root.key (SYNCH_DNSSEC_ANCHOR) replaces the
ICANN trust anchor with that file of DNSKEY records — and then nothing signed
under the real root validates: an override is a different universe, not an
addition.
DNSSEC answers is this key authorized for this zone? by delegation, and a
compromised or coerced parent can substitute the key quietly. By default the
zone key that signed an answer must additionally appear in the public
Sigstore Rekor v2 transparency log — the production log keys are built in —
with the proof carried inside the zone and verified offline: a substituted
key then has to be a public substitution, where the zone's operator can
see it, or fail validation. The zone key is logged as a genuine
hashedrekord entry whose verifier is a self-signed certificate naming the
apex — Rekor validates certificates not at all and copies the DER into the
Merkle leaf verbatim, which is what puts a monitorable zone name inside the
log — carrying the zone's DNSSEC chain as a custom extension. A real
published entry is checked in as a conformance fixture, so an entry minted by
log2025-1.rekor.sigstore.dev verifies end to end. synch-monitor is the
other half: it walks the log's tiles, indexes every leaf by the name in its
certificate, and reports every newly authorized key for the zones you watch —
the CT-monitor posture, where the log tells you a key exists and your own
record of what you published tells you whether to worry.
--rekor off (SYNCH_REKOR) states the opt-out;
--rekor-key <file> (SYNCH_REKOR_KEY) points at a self-hosted log's
verification key, with the same different-universe semantics as the trust
anchor. See docs/REKOR-ZONE-KEY.md.
Sigstore rotates those log keys, so the pinned set follows them on its own.
Once a day the daemon walks Sigstore's TUF repository, verifies the chain
offline against a TUF root built into the binary, and adopts the log keys it
authenticates, persisting them in <data-dir>/rekor-pins.json. Nothing about
it can fail a refresh — an unreachable, stale or invalid repository simply
leaves the current pins standing — and the versions only ever move forward,
so a hostile mirror can withhold an update but never walk one back.
--no-tuf (SYNCH_NO_TUF) turns the walk off, and --rekor-key turns the
whole mechanism off: a named key file is a static universe in both
directions.
Rotate a device key. Every step is an explicit command: a node never polls its own domain and never switches signing keys on its own.
synch key rotate # generate K_new, print the TXT record
# publish the record, wait for it to propagate
synch key activate <K_new> # re-sign the head, serve on both keys
# remove the old record
synch key retire <K_old> # drop that endpoint, delete the secret
synch key ls # this node's keys, and which peers hold each boundsynch key ls is what answers the only question the middle step really turns
on — have my peers picked up the new record yet? It asks every reachable
trusted peer which of our device keys it currently holds bound and reports the
tally per key, naming the peers it could not reach rather than counting their
silence either way. A key-identified origin has no name to rebind, so synch key rotate refuses it outright rather than generating a key that could never
be activated.
Recover an origin whose device key and database are gone. The node keeps its name, comes up on a fresh key, and finds that its peers hold history it does not — so it refuses to publish until an operator says how far to skip ahead:
synch recover # collect peer summaries for an hour
synch recover --wait 90m --gap 5000 # or wait longer, or skip further
synch doctor # says it is in recovery, and how far peers gotPublishing resumes at <highest seq any peer advertised> + gap (default 1000),
which is what makes a collision with history held only by an unreachable peer
improbable. If such a peer turns up later, its pre-recovery heads are kept as
provable fork evidence and synch doctor reports them on both sides.
Read across the cluster. What you see is one tree aggregated from every node, in which each path carries one version per distinct content published for it. Content is fetched on demand and verified per 16 KiB group, so a range read costs a range:
synch ls media/talks # the unified tree; divergent paths marked ⑂2
synch ls media/talks --all # every version of every path, with attestors
synch ls nas@cluster.example.com:media # one origin's view instead
synch status media/talks/keynote.mp4 # every version, side by side
synch cat media/talks/keynote.mp4 --range 0..1048576
synch cat media/notes.txt --select origin=nas@cluster.example.com # pin one origin
synch cat media/notes.txt --select strict # refuse a divergent path, list its versions
synch get media/notes.txt -o notes.txt
synch adopt path nas@cluster.example.com:media/notes.txt # adopt their version as ours
synch adopt path nas@cluster.example.com:media/gone.txt # …including their deletion
synch doctor # membership, heads, equivocation, storageReading a bare <space>/<path> has to pick one of the versions, and does it by
an explicit policy: newest (the default — the greatest (mtime, content root, origin), so every node picks the same one), origin=<id>, or strict.
Selection is presentation, not resolution: nothing is written, no assertion
changes, and the other versions stay visible until a synch adopt path ends the
divergence. Deletions are adoptable the same way: taking a tombstone version
removes the local copy and publishes our own tombstone, and once every
publisher has done so the path leaves the tree.
Write without a checkout. put streams a local file, or stdin, through the same
typed write the S3 gateway uses and publishes it as this node's own version;
delete publishes this node's tombstone. Both work on an API source, where
nothing is materialized. The end of stdin is the end of the payload — put cannot
tell a producer that finished from one that died — so when that matters, write
to a file first and put the file:
synch put notes.txt media/notes.txt # this node's version of the path
synch put report.pdf media/documents/ # a trailing slash keeps the file name
tar cz src | synch put - media/backups/src.tgz # stdin, with an explicit name
synch fetch https://example.com/1.txt media/documents/ # an http(s) URL, the same way
synch delete media/notes.txt # our tombstone; other origins' versions stayAdopt a tree into a filesystem source — the directory synch source add named — with
the content of the unified tree. One pass, and additive: a path missing here is
written, a path whose bytes already match is left alone, and a path whose bytes
differ is reported rather than overwritten. Nothing is ever removed.
synch adopt tree media # newest, by default
synch adopt tree media/talks # one directory of it
synch adopt tree media --select origin=nas@cluster.example.com # that origin's versions
synch adopt tree media --select strict # report divergent paths, skip them
synch adopt tree media --dry-run # decide everything, write nothing
synch adopt tree media --replace # replace local files that differsynch replica sync is the other half of the same wish and a different thing:
replication holds the bytes of every version in the store and materializes
nothing, while adoption writes files, one selected version per path, into a
source. When the same namespace is also a replica, the two compose: retained objects need no network
read during adoption.
Tree adoption scans, publishes, and pushes successful changes before returning.
Written files carry the selected version's mtime and masked mode so adoption
restates that version rather than minting a wall-clock winner. A node in key-loss
recovery refuses adoption before writing anything; run synch recover first.
Materialize a replica's newest view into one managed checkout:
synch replica add media --checkout /mnt/media
synch replica set media --checkout /mnt/new-media
synch replica sync media
synch replica set media --no-checkout # leaves files in placeKeep bytes around regardless of retention. A pin names an object root, or a path — in which case the version the reading policy selects supplies the root. Pinning content this node has never read fetches it first: the pin is a promise the bytes stay available here, and it starts by getting them.
synch pin add media/talks/keynote.mp4
synch pin add nas@cluster.example.com:media/notes.txt # that origin's version
synch pin add 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
synch pin ls
synch pin rm media/talks/keynote.mp4Hold a whole namespace instead of naming objects one at a time. A replica keeps every version — every origin's version of every path, not the one a policy selects — fetched as it appears and held for as long as its retention says. A replica may optionally materialize one newest checkout.
synch source add media /srv/media # publish my copy
synch replica add media # independently hold every current version
synch replica add cold --retention forever # never release observed roots
synch ls # namespace plus local roles
synch replica ls media # held, wanted, releasing, unreachable
synch replica set media --grace 90d # deleted-root recovery window
synch replica rm media # stop and release replica holdsUnder the default current retention a replica holds what the tree names and lets a
root go once nothing names it — after --grace, which is the whole recovery
story for an accidental deletion. --retention forever releases nothing and
costs the sum of every version ever published rather than the size of the tree.
See docs/REPLICATION.md.
Read a version that no path names any more — what synch log prints:
synch log media/talks/keynote.mp4 # every version, with its content root
synch cat --root 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
synch get --root 9f86d081… -o keynote-v1.mp4Serve the same data over S3. The gateway is a control client of the
daemon — it opens no database of its own — so a synch daemon run must be
live on the same data directory for any synch-s3 command to work:
synch-s3 bucket add media media --read-write
synch-s3 bucket add nas-media media --read-only --select origin=nas@cluster.example.com
synch-s3 bucket add safe-media media --read-only --select strict
printf '%s' "$S3_SECRET" | synch-s3 access-key add AKIAEXAMPLE --secret-stdin
synch-s3 serve --listen 127.0.0.1:9000
# or, for local development only:
synch-s3 serve --anonymousA bucket is explicitly read-only or read-write. Read-only buckets may select
newest, strict, or one origin; a strict read answers a divergent key with
409 Conflict. Read-write buckets require a local source and read this node's
own view, following its current origin across identity adoption, so a successful
mutation is immediately visible through that bucket.
ETags are the selected version's quoted BLAKE3 root.
On a read-write bucket, DELETE removes this node's copy and publishes a
tombstone, the same thing an rm in the source directory does.
A key another origin still publishes stays readable until that origin tombstones
it too, and the gateway says so in its log rather than in a status code S3 has
no room for.
Multipart upload is supported, which is what makes the gateway writable from
Mountpoint for Amazon S3 — it wraps
every write in one, whatever the file's size. The upload lives in the daemon, so
one gateway process can create it and another complete it; its parts are staged
under <data-dir>/s3-uploads/ and swept if nobody finishes them. Bodies that
arrive aws-chunked are unframed and their trailing checksum verified, so a
client that checksums while it streams is actually checked rather than taken at
its word. Read-only buckets reject every mutation before consuming its body.
Expose a program instead of a file. A socket is a file in this node's published tree whose content is an eBPF ELF object; a peer that connects to it runs it here, one invocation per incoming stream, under async-ebpf:
synch socket build git.c -o code/git.sock # C in, eBPF out; nothing to install
synch socket build git.c --clang -o git.o # optimized; needs clang + llc on PATH
synch socket inspect code/git.sock # stateless: root, manifest, load check
synch socket activate code/git.sock # the path is a socket until deactivated
synch source scan # publish it as kind=Socket
synch socket ls -l # published root, manifest, validity
synch socket sdk > synch.h # the header a program is built againstOn supported builds the compiler is in the binary — a build of
tinycc that targets eBPF — so writing a
socket costs a text editor and nothing else, rather than a clang built with a
BPF backend that macOS does not ship. Windows MSVC builds report the command as
unsupported unless --clang selects a compatible system clang/llc toolchain.
The system path compiles at -O2 for programs that benefit from optimized
code. Six worked examples are in
crates/synch-sock/examples/, and the test
suite runs every one of them.
From the other side, synch socket connect is a byte pump and nothing else — it names
a path, and everything that decides what runs is state the named node already
holds:
synch socket connect nas@cluster.example.com:code/git.sock
synch socket connect nas@cluster.example.com:code/git.sock --listen 127.0.0.1:9418A node executes only eBPF that is present in its own published tree, at a
path it activated. So the connecting side ships no code, needs no runtime,
and works anywhere — while adopting somebody's socket with synch adopt path
adopts its bytes and not its socket-ness, because the entry kind comes from a
local activation and is never taken from a peer. What a program may reach is
declared as data in the object itself — a JSON manifest in a non-executable
ELF section — so synch socket inspect answers "what would this deployment
do?" without running anything, and every write to an activated path is an
intentional deployment that serves immediately under its own manifest.
Serving needs Linux or macOS on x86-64 or arm64, which is where
async-ebpf runs. See docs/SOCKETS.md.
| Crate | What it holds |
|---|---|
synch-core |
OriginId, Hash, records, signed heads, the postcard wire schemas |
synch-mpt |
the Merkle-Patricia Trie: nodes, hashing, diff, proofs, cursors |
synch-store |
the SQLite schema and the content-addressed blob store |
synch-net |
the iroh endpoint, both ALPNs, reconciliation, the DNSSEC resolver, and the zone-key transparency verifier |
synch-engine |
the embeddable node: scanner, publisher, anti-entropy, fetcher, checkouts |
synch-cli |
the synch binary: the daemon, the control service, the CLI client, and the MCP bridge |
synch-s3 |
the synch-s3 binary and the gateway library |
synch-sock |
the socket runtime: the eBPF host APIs, the endpoint reactor, the program cache |
synch-cc |
the embedded C-to-eBPF compiler, so writing a socket needs no toolchain |
synch-monitor |
the synch-monitor binary: walks the transparency log's tiles and classifies every entry that names a watched zone |
synch-dp |
the synch-dp binary: the multi-tenant cloud data plane, one embedded replica node per hosted network |
All logic lives in the library crates, so any Rust application can embed a full
node by depending on synch-engine. synch-dp is the largest such embedder in
this repository, and a worked example of what embedding many nodes at once
asks for.
docs/IMPLEMENTATION-NOTES.md records where this implementation differs from the design and why.