simlock
병렬 코딩 에이전트가 동일 시뮬레이터를 놓고 충돌하지 않도록 제어 평면 제공
단일 바이너리와 객체 저장소로 Git 저장소를 호스팅해 푸시·풀을 지원합니다
Host git repositories using a single binary and an object store, enabling push, pull
AI가 요약 · README에서
개발 도구Rust · TypeScriptGitHub @tobi직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
설치형 프로젝트는 배포 도메인 없이도 확인할 수 있습니다. 개인 저장소는 GitHub 로그인으로, 조직 저장소는 위의 저장소 확인 파일로 직접 확인해 주세요.
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
walgit — a git server that is one binary in front of an object store
walgit hosts git repositories with no database, no leader and no local state that matters. You run a
single binary, point it at an S3 or GCS bucket, and you have: smart HTTP (v0/v2) fetch and push, Git LFS, a browsing web UI, a JSON API with an SDK, per-repository push policy,
webhooks — and a server that scales to repositories larger than the machine it runs on. Every machine that
runs walgit is a disposable cache; the bucket is the repository.README 전문 보기 ↗walgit hosts git repositories with no database, no leader and no local state that matters. You run a single binary, point it at an S3 or GCS bucket, and you have: smart HTTP (v0/v2) fetch and push, Git LFS, a browsing web UI, a JSON API with an SDK, per-repository push policy, webhooks — and a server that scales to repositories larger than the machine it runs on. Every machine that runs walgit is a disposable cache; the bucket is the repository.
# 1. a bucket (any S3-compatible store or GCS) and a config
cat > walgit.toml <<'EOF'
[server]
listen = "0.0.0.0:8080"
public_url = "https://git.example.com"
auto_create_on_push = true
[server.auth]
mode = "token"
anonymous_read = false
tokens = [{ principal = "me", token_env = "WALGIT_TOKEN_ME", write = true }]
[store]
backend = "s3"
bucket = "my-walgit"
[store.s3]
endpoint = "https://s3.us-east-1.amazonaws.com"
region = "us-east-1"
EOF
# 2. run it
WALGIT_TOKEN_ME=$(openssl rand -hex 24) walgit serve --config walgit.toml
# 3. use it — a push to a new name creates the repository
git -c http.extraHeader="Authorization: Bearer $WALGIT_TOKEN_ME" push https://git.example.com/acme/app.git main
That is the whole deployment. Add more machines pointed at the same bucket and they serve the same repositories, consistently, with nothing to coordinate. Kill them all and you lose warmth, nothing else.
It is a Rust implementation of the architecture Cursor described in
Git at any scale (the system they call Continuity), with the changes
needed to run it on machines that are smaller than the repository. The post is worth reading first; it is kept
verbatim in docs/reference/cursor-git-at-any-scale.md.
Git is distributed, and that makes hosting it miserable for one reason: packfiles. Everything in a repository
is compressed into large binary packs laid out to be small, not to be read in order; every git operation is a
random walk over gigabytes. That is fine on a laptop with the file in page cache and catastrophic over a network
filesystem, which is why "just put the repositories on NFS" failed at every large host that tried it. The design
that survived (GitHub's Spokes) keeps real repositories on local NVMe so upstream git does the work, and
replicates at the packfile level with strict consistency — paid for with three-phase commit across a fixed replica
set, a database that maps every repository to its machines, and a fleet of pets.
Continuity's insight changes the economics: make a write-ahead log in object storage the source of truth, and make every on-disk repository a cache. A push is stored as an immutable object in the bucket and becomes visible only when a tiny manifest is rewritten with a compare-and-swap. That CAS is the consensus — no election, no quorum, no primary. Any instance may accept a push; two racing instances cannot both win. A replica that has never seen a repository reads the log and has it. Reads are consistent without coordination because every read first asks the store whether anything changed (a conditional GET, usually a 304). Compaction is done once by whoever holds a lease and published into the log, so replicas download compacted packs instead of repacking. And because the WAL is the truth, there is complete provenance: every push and every repack, replayable to any point.
walgit takes that as-is, and adds what a monorepo on small machines needs: serving refs and web pages for a repository whose packs will never fit on the instance (a remote reader over HTTP range requests), keeping commits and trees local while blobs stay in the bucket (the history pack).
Packfile migration: bundle runtime has been removed. Native Git can deliver proven reusable packs plus the uncovered graph for anonymous-read repositories. Protected repositories and remote/gix serving use dynamic upload-pack; protected URI clients and large-repository performance remain release gates. Read the design and migration guide, especially saved settings and existing client catch-up config.
| git | smart HTTP v0/v2: ls-refs with prefixes, fetch with filter/shallow/deepen/sideband-all, receive-pack (atomic, deletes, tags, push options, report-status-v2), <owner>/<repo> namespaces, sha1 and sha256 repositories. Upstream git does upload-pack/repack; walgit does receive-pack, the WAL and the plumbing. |
| LFS | Batch API + basic transfer, objects in the bucket, optional read-through from an upstream LFS server for imported repositories. |
| web UI + API | A React UI (tree, blob, commits, diffs, the WAL's own health page) on a read-mostly JSON API under /{owner}/{repo}/api/*; sha-addressed answers are immutable and cached everywhere; long answers stream progress as SSE. repos.js is a dependency-free SDK for pages, agents and scripts. |
| policy | Per-repository push rules (policy.json): protected refs, groups, fast-forward only, bypass lists. docs/POLICY.md. |
| settings | Per-repository config (maintenance, compaction, upstream follow) published into the WAL with history. |
| events | A small bridge tails the WAL and POSTs ref events to a webhook, exactly-once per (repo, seq, ref) with a durable cursor. docs/EVENTS.md. |
| maintenance | Checkpoints, geometric compaction, connectivity audits and repairs — one loop that computes the desired state from (config, WAL) every pass and does one bounded unit of the most important missing work. Manual compact --base rebuilds the base on a host with sufficient disk. |
| auth | none (loopback), token (static tokens), oidc (any OpenID Connect issuer: browser sign-in, ID tokens, and walgit-issued access tokens for git). /services/public/install.sh sets a developer's machine up in one idempotent command. |
| stores | S3 and S3-compatible (AWS, MinIO, rustfs, R2, Ceph, …) and GCS, first class; an in-memory store for tests. |
The repository is a WAL in the bucket. Under repos/<owner>/<repo>/: manifest.pb (tiny, CAS-rewritten:
head sequence, the live pack set, checkpoint pointer, settings — the linearization point), log/<seq>.pb
(immutable entries: PUSH, COMPACT, CHECKPOINT, SETTINGS), wal/<checksum>.pack|.idx|.rev|.bitmap|.commit-graph
(immutable, content-addressed packs with their side-files), checkpoints/<seq>/ (folded ref snapshot + pack
inventory so a cold start is snapshot + tail), leases/ (CAS with TTL — the only cross-instance
mutex), policy.json, lfs/objects/, events/cursor.json.
A push: our receive-pack indexes the pack (git index-pack --fix-thin --rev-index in a scratch dir), checks
connectivity and policy, uploads pack ∥ idx ∥ log entry, then CASes the manifest. On a 412 it re-reads,
re-validates every ref's old value and retries. Concurrent pushes to one repository on one instance are group
committed into one CAS. The client sees ok only after the bucket does.
A read: one conditional GET of the manifest; 304 → serve from the local copy, 200 → apply the new entries.
What "apply" means depends on what the request needs: refs (snapshot + log → packed-refs, no packs:
advertisements, the API), serve (the pack set as this machine can hold it: small packs and the
history pack local, a too-large base read by range), full (everything local, for repacks), objects (the
remote reader, for the UI on a repository that does not fit). Pack downloads run on their own runtime and never
block a refs request.
Placement is configuration. [placement] serve / maintain globs say which repositories a host does object
work for; refs-level reads work everywhere. One box: leave the defaults. Several: put the monorepo on the host with
the SSD (cache.mode = "disk"), everything else on the small ones, and route by /<owner>/<repo> in front.
Nothing waits silently. Anything slow is a task with an id, a log and a progress stream — narrated to git on
sideband 2 (remote: * …) and to the browser as SSE.
AGENTS.md is the full architecture and operating manual: constraints, the WAL strategies, every design decision
with its reasoning, the invariants, and the cost model (round trips to the bucket are the budget).
# build (needs rust per rust-toolchain.toml, protoc, node 24 + pnpm for the web UI)
just web-build && cargo build --release -p walgit-cli
# or: nix build .#walgit or: podman build -t walgit -f Containerfile .
# one box, TLS by walgit itself, a local S3 store (rustfs in a container)
just dev-store
./target/release/walgit-server --config walgit.standalone.toml
open https://walgit.localhost:8080/
walgit.standalone.toml — the one-machine shape (self-signed TLS, rustfs, every role). Start here.walgit.example.toml — every key with its default and a comment.Containerfile, flake.nix — an OCI image and a Nix package/devshell.deploy/nginx.conf.example — an optional nginx in front: public TLS, one auth_request per credential, and
byte offload: walgit answers LFS downloads with X-Accel-Redirect and nginx streams + caches the
object from the bucket itself (S3 presigned or GCS with walgit's bearer). The file documents the contract.Roles (server.roles): serve (git, API, UI, LFS), maintain (checkpoints, compaction,
fsck/repair), events (the webhook bridge). Empty = all. Any number of serve hosts may point at one bucket; give
each repository one maintainer (placement globs) and you are done.
| mode | who gets in | how git authenticates |
|---|---|---|
none | everyone is anon with write and admin — loopback experiments | nothing |
token | static tokens in the config (token_env reads the secret from the environment) | Authorization: Bearer <token>, or the token as an HTTP Basic password |
oidc | any OpenID Connect issuer (issuer, oauth_client_id/secret, allowed_domains/allowed_emails): Google, Entra, Okta, Auth0, Keycloak, Dex, GitLab… | a walgit access token: sign in once in the browser, create one at /_auth/tokens, paste it into the installer. Stateless (HMAC with session_secret, access_token_ttl); rotating the secret revokes all. ID tokens from the issuer (audiences) and static tokens work too. |
Developer setup is one idempotent command — sh -c "$(curl -fsSL 'https://git.example.com/services/public/install.sh')" —
which stores the token in a file only the user can read, installs a tiny git credential helper (git ≥ 2.46: it
answers get with authtype=Bearer, and on a real 401 erases the token and says where a new one comes from),
and configures HTTPS URI negotiation. ?repo=owner/name clones right after.
just test # fast hermetic tier (< 1 min): unit + quick integration, in-memory store, real git
just e2e # real git against the server (~20 s)
just warnings # zero rustc warnings across all targets
just clippy # the [workspace.lints] set across all targets, warnings are errors
just ci # warnings, clippy, test, e2e: everything that must be green before a merge
cargo test -p walgit-server --test sim # fault-injection simulation (crashes, partitions, stale reads)
just test-s3 # store contract against local rustfs
Code map:
crates/
walgit-proto protobuf schema (wal.proto), log framing, store keys
walgit-store ObjectStore trait (CAS versions, conditional GET, range, compose); backends s3, gcs, memory; leases
walgit-git bare repos on disk, receive-pack, pack ingest, refs ↔ packed-refs, advertisements, upload-pack drivers
walgit-wal RepoHandle: sync levels, publish (group commit + CAS), checkpoints, log reader, remote reader, tasks
walgit-server axum: smart HTTP, LFS, auth (none/token/oidc), the maintainer loop, upstream follow,
web/ (API, UI, SDK routes, SSE), setup.rs (installer + recipes), events bridge
walgit-config walgit.toml (+ WALGIT__ env overrides), per-repo settings merge, fail-closed validation
walgit-cli `walgit serve|import|compact|wal|mirror|synth|config|repo`; `walgit-server` = `walgit serve`
web/ React SPA (Vite) + sdk/repos.ts, built into the binary; the wire contract is web/API.md
docs/ PACKFILE_URI_DESIGN (target), PACKFILE_MIGRATION, ROUNDTRIPS (the cost model), POLICY, LFS, INTEGRITY, EVENTS, CONTRACT
AGENTS.md (manifest, leases, settings-related control and caches).docs/ROUNDTRIPS.md).MIT — see LICENSE.