# swarmfile-mirror

`swarmfile-mirror` copies a project (or a subtree) from one Swarmfile hub to another over the public HTTP API - for example, moving a project off the hosted hub onto your own self-hosted [`swarmfile-server`](https://swarmfile.com/docs/guides/self-hosting), or keeping a second copy. It is the counterpart to [`swarmfile-migrate`](https://swarmfile.com/docs/cli/swarmfile-migrate): migrate brings a local tree or an S3 bucket *in*; mirror copies a *project* to *another project*.

A mirror is normally a copy, not a signed replica: files are re-chunked/re-sealed for the destination, so it has no origin commit or checkpoint. With `--provenance-out` on a plaintext whole-project mirror, the copy preserves the source's block CIDs, manifests and entry lineage, and the origin's signed manifest is written beside it, so `swarmfile-verify-history --pin-key <key> --provenance <file>` can prove the destination's confirmed head commit or tree is the origin's (the manifest's key ring is untrusted input, hence the pin). A managed/E2E **mirror** copy re-seals under the destination's key, so its tree differs and the flag refuses; the community server's optional `admin copy-project` is the same-server exact-history alternative. `swarmfile-verify-history --mirror-*` likewise matches only a replica that preserves the origin's confirmed head commit. See [Verifiable History](https://swarmfile.com/docs/guides/verifiable-history). It ships as a standalone open-source binary (install with `curl -fsSL https://get.swarmfile.com/mirror | sh`; source and releases at [swarmfile-mirror](https://swarmfile.com/public/swarmfile/swarmfile-mirror)); no engine or mount is needed. See the [Mirroring a Project Between Hubs](https://swarmfile.com/docs/guides/mirroring-projects) guide for the workflow.

![Two community servers, and the standalone swarmfile-mirror copies a project between them over the HTTP API: run it again and the state file resumes the copy, re-uploading nothing, and the destination then passes the same doctor checks as the source.](https://swarmfile.com/demo/oss-two-hubs.cast)

## Flags

| Flag | Description |
|---|---|
| `--source-hub-url <url>` | Source hub base URL. |
| `--source-org-id <org>` | Source organization id or slug. |
| `--source-project-id <id>` | Source project id. |
| `--source-token <token>` | Source token (`sf_pat_…` / org key). Defaults to `$SWARMFILE_SOURCE_API_KEY`, then `$SWARMFILE_API_KEY`. |
| `--dest-hub-url <url>` | Destination hub base URL. |
| `--dest-org-id <org>` | Destination organization id or slug. |
| `--dest-project-id <id>` | Destination project id. |
| `--dest-token <token>` | Destination token. Defaults to `$SWARMFILE_DEST_API_KEY`, then `$SWARMFILE_API_KEY`. |
| `--path <path>` | Subtree to mirror (default: the whole project). |
| `--branch <name>` | Branch to read on the source (default `main`). |
| `--cdc` | Content-defined chunking (FastCDC) instead of fixed-size. |
| `--auto-profile` | Auto-select the chunking strategy from each file's type (AEC profiles). Conflicts with `--cdc`. |
| `--exclude <glob>` | Skip entries by name (repeatable, `*`/`?`). |
| `--dry-run` | List what would be copied; touch nothing. With `--provenance-out`/`--preserve-tree` it still fetches the confirmed head tree and verifies the live source listing, but writes no manifest. |
| `--json` | Print a JSON summary (files, bytes, dirs, symlinks, resumed, skipped). |
| `--state <file>` | Resume file: records each mirrored source path → (cid, size) atomically; a re-run skips already-mirrored files. Refuses a state file from a different run. |
| `--concurrency <n>` | Files copied in flight (default 4; `1` = sequential). Ignored with `--state` and in `--dry-run`. |
| `--preserve-tree` | Plaintext whole-project copy that keeps the source's block CIDs and entry lineage (no manifest). Use when the origin has no signed history but the exact tree matters. Same constraints as `--provenance-out`: empty destination (or matching resume state), no `--path`/`--exclude`, plaintext both sides; managed/E2E re-seals and is refused. `--dry-run` performs the tree/listing verification without writing. |
| `--provenance-out <file>` | Write the origin's portable provenance manifest after a successful **plaintext whole-project** mirror. Implies a tree-preserving copy: the source's block CIDs, manifest/segments and entry **lineage** are kept, so the destination's tree (and usually its confirmed commit) matches. Requires an empty destination branch (or a resume with the same `--state`), a source with a confirmed head and zero unconfirmed commits, no `--exclude`/`--path`, and a credential that can read the whole tree; sync-ignored entries are included (`showIgnored=1`). It fetches the confirmed head's canonical tree and refuses if the source or destination live listing differs (lineage/content/kind/target), and a resume state is pinned to the source head. Refused for a managed/E2E source or destination, which re-seals and cannot match. `--concurrency`/`--cdc`/`--auto-profile` do not affect this mode (source chunks are copied verbatim). With `--dry-run` it performs only the source tree/listing verification and writes no manifest. Verify with `swarmfile-verify-history --pin-key <key> --provenance <file>`. |

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success. |
| `1` | A failure (including a refused collision or an absolute symlink target). |
| `2` | A usage error. |

## Examples

```bash
export SWARMFILE_SOURCE_API_KEY=sf_pat_…    # source hub token
export SWARMFILE_DEST_API_KEY=sf_key_…      # destination hub token

swarmfile-mirror \
  --source-hub-url https://hub.swarmfile.com \
  --source-org-id my-org --source-project-id <id> \
  --dest-hub-url https://your-server \
  --dest-org-id my-org --dest-project-id <id> \
  --path /Projects --state /tmp/mirror.json
```
