{"id":49041925,"url":"https://github.com/julian-corbet/dotkeeper","last_synced_at":"2026-05-24T02:01:32.197Z","repository":{"id":355832411,"uuid":"1229798946","full_name":"julian-corbet/dotkeeper","owner":"julian-corbet","description":"P2P file sync with git history — embedded Syncthing + staggered git auto-backup across any number of machines","archived":false,"fork":false,"pushed_at":"2026-05-17T17:37:02.000Z","size":1152,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-17T19:55:46.534Z","etag":null,"topics":["dotfiles","git","golang","p2p-sync","self-hosted","syncthing"],"latest_commit_sha":null,"homepage":"https://dotkeeper.corbet.ch","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/julian-corbet.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-05T11:51:50.000Z","updated_at":"2026-05-17T17:36:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/julian-corbet/dotkeeper","commit_stats":null,"previous_names":["julian-corbet/dotkeeper"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/julian-corbet/dotkeeper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julian-corbet%2Fdotkeeper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julian-corbet%2Fdotkeeper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julian-corbet%2Fdotkeeper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julian-corbet%2Fdotkeeper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/julian-corbet","download_url":"https://codeload.github.com/julian-corbet/dotkeeper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/julian-corbet%2Fdotkeeper/sbom","scorecard":{"id":1246993,"data":{"date":"2026-05-05T12:32:44Z","repo":{"name":"github.com/julian-corbet/dotkeeper","commit":"cf0a20302debf88ff8dfb300c4a9ad0f4504d451"},"scorecard":{"version":"v5.3.0","commit":"c22063e786c11f9dd714d777a687ff7c4599b600"},"score":7,"checks":[{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#security-policy"}},{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#dependency-update-tool"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"project was created within the last 90 days. Please review its contents carefully","details":["Warn: Repository was created within the last 90 days."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":10,"reason":"all dependencies are pinned","details":["Info:  21 out of  21 GitHub-owned GitHubAction dependencies pinned","Info:   5 out of   5 third-party GitHubAction dependencies pinned","Info:   2 out of   2 containerImage dependencies pinned","Info:   2 out of   2 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#pinned-dependencies"}},{"name":"CI-Tests","score":-1,"reason":"no pull request found","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#ci-tests"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":10,"reason":"SAST tool detected: CodeQL","details":["Info: SAST configuration detected: CodeQL","Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#sast"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql.yml:27","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:29","Info: jobLevel 'contents' permission set to 'read': .github/workflows/docker.yml:26","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:203","Info: jobLevel 'actions' permission set to 'read': .github/workflows/scorecard.yml:22","Info: jobLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:24","Info: topLevel permissions set to 'read-all': .github/workflows/aur.yml:22","Info: topLevel permissions set to 'read-all': .github/workflows/ci.yml:12","Info: topLevel permissions set to 'read-all': .github/workflows/codeql.yml:15","Info: topLevel permissions set to 'read-all': .github/workflows/docker.yml:18","Info: topLevel permissions set to 'read-all': .github/workflows/homebrew.yml:21","Info: topLevel permissions set to 'read-all': .github/workflows/release.yml:9","Info: topLevel permissions set to 'read-all': .github/workflows/scorecard.yml:10"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#token-permissions"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.5.0 not signed: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317776512","Warn: release artifact v0.4.0 not signed: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317776013","Warn: release artifact v0.3.0 not signed: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317775472","Warn: release artifact v0.2.0 not signed: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317775064","Warn: release artifact v0.1.2 not signed: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317774713","Warn: release artifact v0.5.0 does not have provenance: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317776512","Warn: release artifact v0.4.0 does not have provenance: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317776013","Warn: release artifact v0.3.0 does not have provenance: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317775472","Warn: release artifact v0.2.0 does not have provenance: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317775064","Warn: release artifact v0.1.2 does not have provenance: https://api.github.com/repos/julian-corbet/dotkeeper/releases/317774713"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: GNU Affero General Public License v3.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#license"}},{"name":"CII-Best-Practices","score":5,"reason":"badge detected: Passing","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":7,"reason":"3 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-pjcq-xvwq-hhpj","Warn: Project is vulnerable to: GO-2025-4017 / GHSA-47m2-4cr7-mhcw","Warn: Project is vulnerable to: GO-2025-4233 / GHSA-g754-hx8w-x2g6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":10,"reason":"project is fuzzed","details":["Info: GoBuiltInFuzzer integration found: internal/config/fuzz_test.go:12","Info: GoBuiltInFuzzer integration found: internal/config/fuzz_test.go:50","Info: GoBuiltInFuzzer integration found: internal/config/fuzz_test.go:69"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#fuzzing"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#branch-protection"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/docker.yml:21"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#packaging"}},{"name":"Contributors","score":0,"reason":"project has 0 contributing companies or organizations -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#contributors"}}]},"last_synced_at":"2026-05-05T14:18:59.234Z","repository_id":355832411,"created_at":"2026-05-05T14:18:59.235Z","updated_at":"2026-05-05T14:18:59.235Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33418550,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotfiles","git","golang","p2p-sync","self-hosted","syncthing"],"created_at":"2026-04-19T15:06:35.288Z","updated_at":"2026-05-24T02:01:32.191Z","avatar_url":"https://github.com/julian-corbet.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dotkeeper\n\n[![Release](https://img.shields.io/github/v/release/julian-corbet/dotkeeper?sort=semver\u0026logo=github)](https://github.com/julian-corbet/dotkeeper/releases/latest)\n[![CI](https://github.com/julian-corbet/dotkeeper/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/julian-corbet/dotkeeper/actions/workflows/ci.yml)\n[![License](https://img.shields.io/github/license/julian-corbet/dotkeeper)](LICENSE)\n[![Go Reference](https://pkg.go.dev/badge/github.com/julian-corbet/dotkeeper.svg)](https://pkg.go.dev/github.com/julian-corbet/dotkeeper)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12584/badge)](https://www.bestpractices.dev/projects/12584)\n[![Built with Syncthing](https://img.shields.io/badge/built%20with-Syncthing-6eabe0?logo=syncthing\u0026logoColor=white)](https://syncthing.net/)\n\nSync your repos and dotfiles across machines. Close the laptop, open the desktop, keep working.\n\n## What it does\n\ndotkeeper keeps your code, configs, and dotfiles in sync across however many machines you use. It combines **embedded Syncthing** for real-time P2P file sync with **staggered git backup** for history and rollback. You declare what you want synced in plain TOML files; a reconciler loop continuously converges the live system to match. Single binary, no external dependencies beyond git.\n\nEach local repo copy you want managed gets a `.dotkeeper.toml` at its root. That file is local machine state: dotkeeper writes Git and Syncthing excludes for it, and it must not be committed or synced. It says which machines should receive the repo, how git backup should run, and which Syncthing folder backs it. Per-machine settings — your machine's name, slot number, peer roster, defaults, and which directories to scan for repos — live in `~/.config/dotkeeper/machine.toml`. You edit files; dotkeeper handles the rest.\n\n## Why it exists\n\nWorking across multiple machines is awkward when code, dotfiles, and editor configs drift. Git gives rollback and auditability, but only after commits and pushes. Syncthing gives real-time P2P sync, but not git history. dotkeeper wires the two together with a declarative config model and little ongoing maintenance.\n\nRepo-specific dotkeeper metadata stays out of Git and out of Syncthing. Public project history stays clean; private topology can be generated by Nix/Home Manager or created locally with `dotkeeper track`.\n\n## How it works\n\ndotkeeper runs as a daemon. At its core is a reconcile loop:\n\n```\ndesired  = read_config()      # machine.toml + local .dotkeeper.toml files\nobserved = query_state()      # Syncthing REST API + git + filesystem\nactions  = diff(desired, observed)\nfor action in actions:\n    apply(action)\n```\n\n`diff()` is a pure function. Given the same inputs it always returns the same list of actions. `apply()` is where side effects happen — adding a Syncthing folder, auto-committing a dirty repo, pushing a backup — and every action is idempotent. The daemon triggers reconcile on inotify events (file changes land in milliseconds), on a periodic timer (default five minutes, safety net), and on explicit `dotkeeper reconcile` invocations.\n\nThe mesh is P2P — no central hub. Every peer reaches every other peer directly. Each machine also runs staggered git backup: machines are assigned slot numbers (0, 1, 2, ...) and commit at different offsets within the configured interval, so no two machines ever race to push the same branch.\n\ndotkeeper v1.0 introduced a **multi-transport** layer. Two ways to move a change between peers coexist: Syncthing's BEP gossip (the universal fallback, present in every install) and `git push` over SSH (used when a peer-resolution path is configured — currently Tailscale; mDNS and static-hub variants are planned). A cost-model picks per change based on the change's size: small text payloads typically route via git, larger payloads via Syncthing. The model learns from observed transfer durations, so the per-change crossover is empirical, not hardcoded. See [`dotkeeper transport list`](#commands) for what's available on a given host.\n\n`.git/` directories are excluded from Syncthing sync. Each machine keeps its own independent git history; histories converge via the chosen git transport or via the configured remote, never through syncing raw git objects. This keeps the Syncthing mesh fast and avoids the class of problems that come from syncing `.git/` directly.\n\n## Quick start\n\n### Install\n\n**Arch Linux (AUR):**\n\n```bash\nparu -S dotkeeper-bin          # pre-built binary\nparu -S dotkeeper-git          # builds from main HEAD\n```\n\n**macOS / Linux (Homebrew):**\n\n```bash\nbrew tap julian-corbet/dotkeeper\nbrew install dotkeeper\n```\n\n**From source — use `make`, not `go build` directly:**\n\n```bash\ngit clone https://github.com/julian-corbet/dotkeeper.git\ncd dotkeeper\nmake build \u0026\u0026 make install\n```\n\nOr download a pre-built binary from [Releases](https://github.com/julian-corbet/dotkeeper/releases).\n\n\u003e **Why `make build` and not `go build ./...`?**\n\u003e\n\u003e dotkeeper embeds Syncthing as a library. Syncthing's `lib/api` package\n\u003e expects generated web-GUI assets (`gui.files.go`) that dotkeeper has no\n\u003e use for and does not ship. A plain `go build ./cmd/dotkeeper` therefore\n\u003e fails with `undefined: auto.Assets`. Pass `-tags noassets` to skip the\n\u003e assets path:\n\u003e\n\u003e ```bash\n\u003e go build  -tags noassets ./cmd/dotkeeper        # build the daemon\n\u003e go test   -tags noassets ./...                  # run the test suite\n\u003e go install -tags noassets github.com/julian-corbet/dotkeeper/cmd/dotkeeper@latest\n\u003e ```\n\u003e\n\u003e The `Makefile`, `Dockerfile`, CI workflows, and `release.yml` all set\n\u003e this tag automatically — when contributing, prefer `make build`/`make\n\u003e test` so the tag is never forgotten. If your IDE or linter shows the\n\u003e `auto.Assets` error, configure it to pass `-tags noassets` to gopls.\n\n### First machine\n\n```bash\n# Generate Syncthing identity, write initial state.toml, scaffold machine.toml.\ndotkeeper init\n\n# Print this machine's Syncthing device ID — you'll need it when adding peers.\ndotkeeper identity\n\n# Edit machine.toml to set your name, slot, peers, and scan roots.\n$EDITOR ~/.config/dotkeeper/machine.toml\n```\n\nIf `machine.toml` is generated by Home Manager or another declarative system,\nrun `dotkeeper init --no-service` after activation. dotkeeper preserves the\ngenerated file and only creates runtime Syncthing identity/state.\n\nA minimal `machine.toml` looks like:\n\n```toml\nschema_version = 2\nname = \"desktop\"\nslot = 0\n\n[discovery]\nscan_roots = [\n  \"~/Documents/GitHub\",\n  \"~/.config/nvim\",\n]\n\n[[peers]]\nname = \"laptop\"\ndevice_id = \"\u003cLAPTOP-DEVICE-ID\u003e\"\nlearned_at = 2026-01-01T00:00:00Z\n```\n\nFor each repo you want to manage, run `dotkeeper track`:\n\n```bash\ndotkeeper track ~/Documents/GitHub/my-project\n```\n\nThat writes a local `.dotkeeper.toml`, adds dotkeeper/Syncthing control files to `.git/info/exclude`, and writes a managed `.stignore` so those files do not cross the mesh.\n\n```toml\n# ~/Documents/GitHub/my-project/.dotkeeper.toml\nschema_version = 2\n\n[repo]\nname = \"my-project\"\nadded = \"2026-01-01T00:00:00Z\"\nadded_by = \"desktop\"\n\n[sync]\nsyncthing_folder_id = \"my-project-a1b2c3\"\nshare_with = [\"desktop\", \"laptop\"]\n```\n\nThen run the daemon:\n\n```bash\ndotkeeper start    # or: systemctl --user start dotkeeper-syncthing.service\n```\n\ndotkeeper walks your scan roots, finds local `.dotkeeper.toml` files, and starts managing those repos.\n\n### Each additional machine\n\n```bash\n# On the new machine:\ndotkeeper init\ndotkeeper identity    # copy this device ID\n\n# On an existing machine, add the peer declaratively in machine.toml\n# or imperatively:\ndotkeeper peer add laptop \u003cDEVICE-ID\u003e\ndotkeeper reconcile\n\n# Back on the new machine, materialize local repo configs with Nix/Home Manager\n# or run dotkeeper track for the repo paths this machine should manage:\ndotkeeper track ~/Documents/GitHub/my-project\ndotkeeper reconcile\n```\n\nBecause `.dotkeeper.toml` is intentionally not synced, each machine needs its own local copy. Nix/Home Manager is the intended low-friction path for repeatable personal topology; `dotkeeper track` is the portable manual path.\n\n### Nix / Home Manager topology\n\nFor declarative setups, keep the public dotkeeper program generic and put your\nprivate sync topology in your own flake. A practical pattern is denylist-first:\nscan a small set of roots for Git repos, generate local `.dotkeeper.toml` files\nfor everything discovered, and list only the exceptions.\n\n```nix\n{\n  scanRoots = [\n    \"~/Documents/GitHub\"\n    \"~/.config\"\n  ];\n\n  dontSync = {\n    all = [\n      \"~/Documents/GitHub/dotkeeper\"\n      \"~/Documents/GitHub/archive\"\n      \"~/Documents/GitHub/example-rag/workspace\"\n    ];\n\n    laptop = [\n      \"~/Documents/GitHub/video-renderer\"\n    ];\n  };\n}\n```\n\nWhole denied repos are left unmanaged on that machine. Denied paths inside a\nmanaged repo should be emitted as `[sync].ignore` patterns, so generated data can\nstay local while the repo itself still syncs. See\n[`docs/examples/home-manager-denylist.nix`](docs/examples/home-manager-denylist.nix)\nfor a full generic activation sketch.\n\n## Commands\n\n| Command | Purpose |\n|---------|---------|\n| `dotkeeper init` | First-run setup: generate Syncthing identity, write `state.toml`, scaffold `machine.toml` |\n| `dotkeeper start` | Run the daemon (invoked by systemd) |\n| `dotkeeper reconcile` | Force a reconcile pass now |\n| `dotkeeper status` | Snapshot: last reconcile time, tracked repos, mesh peers, pending work |\n| `dotkeeper identity` | Print this machine's Syncthing device ID and name |\n| `dotkeeper peer add/list/remove` | Manage imperative peers in `state.toml`; declarative peers can live in `machine.toml` |\n| `dotkeeper track \u003cpath\u003e` | Bootstrap local `.dotkeeper.toml`, local excludes, and tracked override state |\n| `dotkeeper untrack \u003cpath\u003e` | Deregister a tracked override |\n| `dotkeeper doctor` | Run self-diagnostic health checks; `--json` for machine-readable output |\n| `dotkeeper conflict list` | List Syncthing sync-conflict files across all managed folders |\n| `dotkeeper conflict resolve-all` | Batch auto-resolve trivial conflicts (dedup + text merge) |\n| `dotkeeper conflict keep \u003cpath\u003e` | Delete the conflict variant, keep the current file |\n| `dotkeeper conflict accept \u003cpath\u003e` | Replace current file with the conflict variant and commit |\n| `dotkeeper transport list` | List configured transports and which are currently available |\n| `dotkeeper transport status [peer]` | Per-peer reachability + learned cost-model parameters |\n| `dotkeeper bare-init [--peer=NAME] [--host=USER@ADDR]` | Configure peer-side git repos for direct `git push` (sets `receive.denyCurrentBranch=updateInstead`) |\n| `dotkeeper version` | Print dotkeeper version |\n\n## Architecture\n\n- [docs/architecture.md](docs/architecture.md) — full walkthrough: reconciler model, config file roles, discovery, command reference\n- [ADR 0001](docs/adr/0001-per-repo-config.md) — why local per-repo `.dotkeeper.toml` is authoritative\n- [ADR 0002](docs/adr/0002-machine-state-split.md) — `machine.toml` vs `state.toml`: declarative vs tool-owned\n- [ADR 0003](docs/adr/0003-reconciler-loop.md) — pure `Diff(desired, observed) → Plan` reconciler\n- [ADR 0004](docs/adr/0004-scan-root-discovery.md) — how repos are discovered without `dotkeeper add`\n- [ADR 0005](docs/adr/0005-state-toml-locking.md) — cross-process state.toml locking\n- [ADR 0006](docs/adr/0006-syncthing-v2-migration.md) — Syncthing v2 migration and the pseudo-version pin\n\n## Status\n\n**v1.0.0** is the current release. It adds the multi-transport framework (Manager, cost-model routing, GitSSHTransport, Tailscale resolver, `dotkeeper transport` + `bare-init` CLIs) on top of the v0.9-era performance and correctness improvements (auto-pause for idle folders, smart rescan with cross-platform filesystem classification, the v0.9.5 schedule-drift detector that pushed steady-state CPU into the single digits). See [CHANGELOG.md](CHANGELOG.md) for full release notes.\n\n## Port isolation\n\ndotkeeper runs its embedded Syncthing on separate ports so it does not conflict with a system-installed Syncthing instance.\n\n| Resource | System Syncthing | dotkeeper |\n|----------|-----------------|-----------|\n| API | 127.0.0.1:8384 | 127.0.0.1:18384 |\n| Sync | :22000 | :12000 |\n| Discovery | :21027 | :11027 |\n| Config | `~/.config/syncthing` | `~/.local/share/dotkeeper/syncthing` |\n\n## Requirements\n\n- **Go 1.26+** (build only)\n- **git**\n- **Service manager** — systemd (Linux), launchd (macOS), or cron (BSD/fallback)\n- Internet access for cross-network sync (LAN-only also works)\n\n## License\n\nCopyright (C) 2026 Julian Corbet\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.\n\nSee [LICENSE](LICENSE) for the full text.\n\nSyncthing (embedded) is licensed under the Mozilla Public License 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjulian-corbet%2Fdotkeeper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjulian-corbet%2Fdotkeeper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjulian-corbet%2Fdotkeeper/lists"}