diff --git a/.dockerignore b/.dockerignore index b0b9b9a..5ff405f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,9 @@ target **/node_modules web/dist .env* +deploy/.env +**/.env +**/.env.* *.senv .senv .dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed244d5..237132d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,21 +119,31 @@ jobs: steps: - uses: actions/checkout@v5 - uses: useblacksmith/setup-docker-builder@v1 + - name: Validate the WAL Compose stack + run: | + cp deploy/walgit.env.example deploy/.env + sed -i 's/^WALGIT_TOKEN=$/WALGIT_TOKEN=ci/' deploy/.env + sed -i 's/^WALGIT__STORE__BUCKET=$/WALGIT__STORE__BUCKET=ci/' deploy/.env + docker compose -f deploy/compose.yaml config -q + rm -f deploy/.env - name: Build release image without publishing uses: useblacksmith/build-push-action@v2 with: context: . - file: Containerfile + file: Dockerfile platforms: linux/amd64 push: false load: true provenance: false - build-args: WALGIT_BUILD_SHA=${{ github.sha }} + build-args: | + WALGIT_BUILD_SHA=${{ github.sha }} + WALGIT_CONFIG_FILE=deploy/walgithub.toml + WALGIT_HTTP_PORT=8097 tags: walgithub:smoke - name: Start S3-compatible test store run: | - docker compose up -d --wait rustfs - docker compose run --rm create-bucket + docker compose -f deploy/compose.rustfs.yaml up -d --wait rustfs + docker compose -f deploy/compose.rustfs.yaml run --rm create-bucket - name: Start packaged facade run: | docker run -d --name walgithub-smoke --network host \ @@ -156,7 +166,7 @@ jobs: run: | mkdir -p smoke-logs docker logs walgithub-smoke > smoke-logs/facade.log 2>&1 || true - docker compose logs rustfs > smoke-logs/store.log 2>&1 || true + docker compose -f deploy/compose.rustfs.yaml logs rustfs > smoke-logs/store.log 2>&1 || true - uses: actions/upload-artifact@v4 if: always() with: @@ -167,4 +177,4 @@ jobs: if: always() run: | docker rm -fv walgithub-smoke || true - docker compose down -v + docker compose -f deploy/compose.rustfs.yaml down -v diff --git a/AGENTS.md b/AGENTS.md index bce355d..b3057bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,8 +39,9 @@ machines whose "disk" is 20 GiB of tmpfs, next to a long tail of small repositor | `web/README.md` | Frontend engineers changing the React SPA, Vite build, SDK adapter, static assets, loading states. | | `walgit.example.toml` | Every config key with its default and a comment. Change it with the code. | | `walgit.standalone.toml` | The one-machine shape: `walgit-server --config walgit.standalone.toml` → `https://walgit.localhost:8080/`. | +| `docs/HOSTING.md` | Anyone running walgit as a service: the image, `deploy/compose.yaml` (WAL-Git + WAL-Github), auth/TLS, roles and placement, upgrades, monitoring. | | `deploy/nginx.conf.example` | An optional nginx in front; documents the `X-Accel-Redirect` byte-offload contract. | -| `Containerfile`, `flake.nix` | An OCI image; a Nix package, image and devshell. | +| `Dockerfile`, `deploy/compose.yaml`, `deploy/walgit.env.example`, `deploy/compose.rustfs.yaml` | The image (both services via build args); the WAL stack; its shared environment; the optional, separate rustfs dev store. `flake.nix` is the Nix package, image and devshell. | --- @@ -451,6 +452,12 @@ long-lived access keys. Explicit pairs still support local object stores; partia **D45 — Facade runtime configuration (2026-09-09).** The walgithub image packages `deploy/walgithub.toml`; facade behavior changes ship with the image. The deployment supplies only the store bindings (bucket, region, public origin) and secrets through `WALGIT__` overrides. +**(2026-10-31)** One `Dockerfile` builds both shapes: the default bakes `deploy/host.toml` for +WAL-Git, while WAL-Github selects its config with +`--build-arg WALGIT_CONFIG_FILE=deploy/walgithub.toml --build-arg WALGIT_HTTP_PORT=8097`. The WAL +deployment (`deploy/compose.yaml`, `docs/HOSTING.md`) runs both services and deliberately starts no object +store; `deploy/compose.rustfs.yaml` is a separate, optional dev store. The `Containerfile` name is +gone; the build file is `Dockerfile`. **D46 — Dynamic GitHub App integrations (2026-09-09).** Replaces D42's global GitHub callback/secret/ installation config with `github/integrations.json`, a CAS'd bucket registry managed by the private diff --git a/Containerfile b/Dockerfile similarity index 51% rename from Containerfile rename to Dockerfile index 59390a5..2439767 100644 --- a/Containerfile +++ b/Dockerfile @@ -1,18 +1,23 @@ -# walgithub image — the GitHub Enterprise Server facade for local development and E2Es. +# walgit — one binary in front of an object store. # -# podman build -t walgit -f Containerfile . -# podman run --rm -p 127.0.0.1:8097:8097 \ -# -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \ -# -e WALGIT__STORE__BUCKET=your-bucket \ -# -v ./walgit.toml:/etc/walgit/walgit.toml:ro \ +# docker build -t walgit . +# docker run --rm --stop-timeout 60 -p 127.0.0.1:8080:8080 \ +# -e WALGIT__STORE__BUCKET=my-bucket \ +# -e WALGIT_TOKEN="$(openssl rand -hex 32)" \ # -v walgit-cache:/var/lib/walgit \ # walgit # -# The image carries git (upload-pack, repack, bundle, index-pack run as subprocesses), -# git-lfs, CA certificates and tini. Config comes from /etc/walgit/walgit.toml or -# WALGIT__SECTION__KEY environment overrides; the local cache (materialized repositories, -# a self-signed TLS cert) lives under /var/lib/walgit and can be wiped at any time — the -# bucket is the only durable state. flake.nix provides separate standalone walgit packaging. +# The image bakes `deploy/host.toml` at /etc/walgit/walgit.toml and takes the bucket, token and +# every other setting from the environment (`WALGIT__SECTION__KEY`). docs/HOSTING.md is the host +# guide; the Compose stack is deploy/compose.yaml (WAL-Git + WAL-Github). +# +# The runtime image carries git (upload-pack, repack, bundle, index-pack run as subprocesses), +# git-lfs, CA certificates and tini. /var/lib/walgit is a disposable cache: materialized packs, +# a self-signed certificate, task logs. The bucket is the only durable state. +# +# The facade E2E image (docs/GITHUB.md) is this same build with a different baked config: +# docker build --build-arg WALGIT_CONFIG_FILE=deploy/walgithub.toml \ +# --build-arg WALGIT_HTTP_PORT=8097 -t walgithub:smoke . # ---- 1. web UI (embedded into the binary at compile time) --------------------------- FROM docker.io/library/node:24-bookworm-slim AS web @@ -23,7 +28,7 @@ RUN pnpm install --frozen-lockfile COPY web/ ./ RUN pnpm run build && test -f dist/index.html && test -f dist/repos.js -# ---- 2. rust build ------------------------------------------------------------------------ +# ---- 2. rust build ------------------------------------------------------------------ FROM docker.io/library/rust:1.97-bookworm AS build RUN apt-get update && apt-get install -y --no-install-recommends protobuf-compiler libprotobuf-dev pkg-config cmake perl python3 \ && rm -rf /var/lib/apt/lists/* @@ -39,7 +44,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ && install -D target/release/walgit /out/bin/walgit \ && install -D target/release/walgit-server /out/bin/walgit-server -# ---- 3. runtime ----------------------------------------------------------------------------- +# ---- 3. runtime --------------------------------------------------------------------- # trixie ships git 2.47+: walgit wants >= 2.47 on the server (pack.writeReverseIndex, bundle-uri, # `index-pack --rev-index`); clients need >= 2.46. FROM docker.io/library/debian:trixie-slim @@ -49,11 +54,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends git git-lfs ca- RUN useradd --uid 1000 --create-home --shell /bin/sh walgit \ && mkdir -p /etc/walgit /var/lib/walgit && chown walgit:walgit /var/lib/walgit COPY --from=build /out/bin/walgit /out/bin/walgit-server /usr/local/bin/ -COPY deploy/walgithub.toml /etc/walgit/walgit.toml -ENV WALGIT_CONFIG=/etc/walgit/walgit.toml +ARG WALGIT_CONFIG_FILE=deploy/host.toml +ARG WALGIT_HTTP_PORT=8080 +COPY ${WALGIT_CONFIG_FILE} /etc/walgit/walgit.toml +LABEL org.opencontainers.image.title="walgit" \ + org.opencontainers.image.description="git hosting on an object store: smart HTTP, bundle-uri, LFS, web UI" \ + org.opencontainers.image.licenses="MIT" +ENV WALGIT_CONFIG=/etc/walgit/walgit.toml \ + WALGIT_HTTP_PORT=${WALGIT_HTTP_PORT} USER walgit WORKDIR /home/walgit -EXPOSE 8097 +EXPOSE ${WALGIT_HTTP_PORT} VOLUME ["/var/lib/walgit"] -HEALTHCHECK --interval=30s --timeout=5s CMD curl -fsS http://127.0.0.1:8097/readyz || exit 1 +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD port="${PORT:-${WALGIT__SERVER__LISTEN##*:}}"; port="${port:-${WALGIT_HTTP_PORT}}"; \ + curl -fsS "http://127.0.0.1:${port}/readyz" || curl -fsSk "https://127.0.0.1:${port}/readyz" || exit 1 +STOPSIGNAL SIGTERM ENTRYPOINT ["tini", "--", "walgit-server"] diff --git a/README.md b/README.md index 049bc60..ad348b3 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ with its reasoning, the invariants, and the cost model (round trips to the bucke ```sh # 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 . +# or: nix build .#walgit or: docker build -t walgit . # one box, TLS by walgit itself, a local S3 store (rustfs in a container) just dev-store @@ -125,9 +125,25 @@ just dev-store open https://walgit.localhost:8080/ ``` +For a deployment, Compose runs **WAL-Git** and **WAL-Github** against the bucket you provide: + +```sh +cp deploy/walgit.env.example deploy/.env && "${EDITOR:-vi}" deploy/.env # token, bucket, public origins +docker compose -f deploy/compose.yaml up -d --build +# WAL-Git http://127.0.0.1:8080 token auth +# WAL-Github http://127.0.0.1:8097 GHES facade, loopback only +``` + +The stack starts no S3 service: set `WALGIT__STORE__*` in `.env` to your bucket. If you want a +local S3-compatible endpoint for development, `deploy/compose.rustfs.yaml` is a separate Compose +project (`just dev-store`). + * `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. +* `docs/HOSTING.md` — running it as a service: the image, `deploy/compose.yaml`, auth/TLS, roles and + placement, upgrades, monitoring. +* `Dockerfile`, `deploy/compose.yaml` (WAL-Git + WAL-Github), `deploy/compose.rustfs.yaml` (optional dev S3), + `flake.nix` — an OCI image, the WAL stack, a separate store, 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 bundle/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. @@ -174,7 +190,7 @@ crates/ walgit-config walgit.toml (+ WALGIT__ env overrides), per-repo settings merge, fail-closed validation walgit-cli `walgit serve|import|compact|bundle|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/ BUNDLE_URI_DESIGN, ROUNDTRIPS (the cost model), POLICY, LFS, INTEGRITY, EVENTS, CONTRACT, patches/ +docs/ BUNDLE_URI_DESIGN, ROUNDTRIPS (the cost model), HOSTING, POLICY, LFS, INTEGRITY, EVENTS, CONTRACT, patches/ ``` ## Invariants worth memorising diff --git a/crates/walgit-config/src/lib.rs b/crates/walgit-config/src/lib.rs index ca70455..267b4c7 100644 --- a/crates/walgit-config/src/lib.rs +++ b/crates/walgit-config/src/lib.rs @@ -209,7 +209,8 @@ pub enum AuthMode { #[serde(deny_unknown_fields)] pub struct StaticToken { pub principal: String, - /// Read from env var if set, else literal. + /// Read from env var if set, else literal. Missing when `token_env` is the only source. + #[serde(default)] pub token: String, #[serde(default)] pub token_env: Option, @@ -1985,6 +1986,22 @@ audiences = ["walgit-cli", "https://git.example.com"] let err = Config::parse("[store]\nbucket = \"b\"\n[server.auth]\nmode = \"token\"\n") .unwrap_err(); assert!(err.to_string().contains("tokens"), "{err}"); + // `token_env` alone is a complete source: the literal `token` may be absent. + let env_only = Config::parse( + r#" +[store] +bucket = "b" +[server.auth] +mode = "token" +tokens = [{ principal = "admin", token_env = "WALGIT_TEST_TOKEN" }] +"#, + ) + .unwrap(); + assert_eq!( + env_only.server.auth.tokens[0].token_env.as_deref(), + Some("WALGIT_TEST_TOKEN") + ); + assert!(env_only.server.auth.tokens[0].token.is_empty()); // oidc: anonymous_read off, an allowlist, and a way in. let err = Config::parse("[store]\nbucket = \"b\"\n[server.auth]\nmode = \"oidc\"\nanonymous_read = false\nallowed_domains = [\"example.com\"]\n").unwrap_err(); assert!(err.to_string().contains("way in"), "{err}"); diff --git a/crates/walgit-server/src/web/ui.rs b/crates/walgit-server/src/web/ui.rs index f34ccc6..4d3bc53 100644 --- a/crates/walgit-server/src/web/ui.rs +++ b/crates/walgit-server/src/web/ui.rs @@ -21,7 +21,7 @@ use crate::error::ApiError; #[folder = "../../web/dist"] // Debug builds read the folder at runtime; `allow_missing` lets the crate // compile where the web build is absent (distributed sccache workers). -// Release images assert the optimized artefacts exist (Containerfile). +// Release images assert the optimized artefacts exist (Dockerfile). #[allow_missing = true] struct Assets; diff --git a/compose.yaml b/deploy/compose.rustfs.yaml similarity index 68% rename from compose.yaml rename to deploy/compose.rustfs.yaml index 26552e7..795bfda 100644 --- a/compose.yaml +++ b/deploy/compose.rustfs.yaml @@ -1,13 +1,20 @@ -# compose file for walgit local dev (podman compose): rustfs (S3-compatible) + bucket creation. +# Optional S3-compatible store for local development — a SEPARATE Compose project. # -# Usage: -# podman compose up -d rustfs # start rustfs -# podman compose run --rm create-bucket # create the walgit-test bucket +# The WAL deployment (deploy/compose.yaml) deliberately starts no store: point it at your own +# bucket through `.env`. This file is the one we recommend when you want a local S3 endpoint: +# +# podman compose -f deploy/compose.rustfs.yaml up -d rustfs +# podman compose -f deploy/compose.rustfs.yaml run --rm create-bucket # just dev-store # convenience wrapper # +# Then set WALGIT__STORE__S3__ENDPOINT=http://host.docker.internal:9000 and +# WALGIT__STORE__S3__FORCE_PATH_STYLE=true in the WAL deployment's `.env`. +# # rustfs exposes the S3 API on :9000 and the console on :9001. # Credentials are fixed for local dev — do not use in production. +name: walgit-rustfs + services: rustfs: image: rustfs/rustfs:latest diff --git a/deploy/compose.yaml b/deploy/compose.yaml new file mode 100644 index 0000000..768cc03 --- /dev/null +++ b/deploy/compose.yaml @@ -0,0 +1,87 @@ +# WAL deployment: WAL-Git and WAL-Github in front of an object store you provide. +# +# cp deploy/walgit.env.example deploy/.env && "${EDITOR:-vi}" deploy/.env +# docker compose -f deploy/compose.yaml up -d --build +# +# There is deliberately no S3 backend here. Both services read WALGIT__STORE__* from `.env`, +# so the bucket is your decision. An optional local rustfs dev store lives in +# deploy/compose.rustfs.yaml and is a separate Compose project (docs/HOSTING.md). +# +# WAL-Git http://127.0.0.1:8080 token auth; serve + maintain +# WAL-Github http://127.0.0.1:8097 GHES facade; auth-free, loopback only; serve + events +# +# The bucket is the repository. Both cache volumes are disposable. + +name: walgit + +x-walgit-common: &walgit-common + restart: unless-stopped + # SIGTERM starts the serving drain (D31): phase 1 is bounded at 30s and phase 2 gets + # server.drain_timeout (20s), so Docker must not SIGKILL before both finish. + stop_grace_period: 60s + env_file: + - .env + # The image's root filesystem is only the OS, git and the binaries; the process never writes + # to it. /tmp is scratch; the cache volume is the one writable mount that holds state. + read_only: true + tmpfs: + - /tmp:rw,size=256m + # Reach an object store published on the host (optional; Docker Desktop resolves it anyway). + extra_hosts: + - "host.docker.internal:host-gateway" + logging: + driver: json-file + options: + max-size: "20m" + max-file: "5" + +services: + walgit: + <<: *walgit-common + image: ${WALGIT_IMAGE:-walgit:latest} + build: + context: .. + dockerfile: Dockerfile + args: + WALGIT_CONFIG_FILE: deploy/host.toml + WALGIT_HTTP_PORT: "8080" + WALGIT_BUILD_SHA: ${WALGIT_BUILD_SHA:-dev} + environment: + # Fail fast instead of creating objects in a bucket nobody named. + WALGIT__STORE__BUCKET: "${WALGIT__STORE__BUCKET:?set WALGIT__STORE__BUCKET in .env}" + WALGIT_TOKEN: "${WALGIT_TOKEN:?set WALGIT_TOKEN in .env}" + WALGIT__SERVER__AUTH__MODE: "${WALGIT_AUTH_MODE:-token}" + WALGIT__SERVER__ROLES: '["serve","maintain"]' + WALGIT__SERVER__LISTEN: "${WALGIT_LISTEN:-0.0.0.0:8080}" + WALGIT__SERVER__PUBLIC_URL: "${WALGIT_PUBLIC_URL:-http://127.0.0.1:8080}" + ports: + - "${WALGIT_BIND:-127.0.0.1}:${WALGIT_PORT:-8080}:${WALGIT_CONTAINER_PORT:-8080}" + volumes: + - walgit-cache:/var/lib/walgit + + walgithub: + <<: *walgit-common + image: ${WALGITHUB_IMAGE:-walgithub:latest} + build: + context: .. + dockerfile: Dockerfile + args: + WALGIT_CONFIG_FILE: deploy/walgithub.toml + WALGIT_HTTP_PORT: "8097" + WALGIT_BUILD_SHA: ${WALGIT_BUILD_SHA:-dev} + environment: + WALGIT__STORE__BUCKET: "${WALGIT__STORE__BUCKET:?set WALGIT__STORE__BUCKET in .env}" + # The facade is auth-free by construction (docs/GITHUB.md) and must stay that way. + WALGIT__SERVER__AUTH__MODE: "none" + WALGIT__SERVER__TLS__MODE: "off" + WALGIT__SERVER__ROLES: '["serve","events"]' + WALGIT__SERVER__LISTEN: "${WALGITHUB_LISTEN:-0.0.0.0:8097}" + WALGIT__SERVER__PUBLIC_URL: "${WALGITHUB_PUBLIC_URL:-http://127.0.0.1:8097}" + ports: + - "${WALGITHUB_BIND:-127.0.0.1}:${WALGITHUB_PORT:-8097}:${WALGITHUB_CONTAINER_PORT:-8097}" + volumes: + - walgithub-cache:/var/lib/walgit + +volumes: + walgit-cache: + walgithub-cache: diff --git a/deploy/host.toml b/deploy/host.toml new file mode 100644 index 0000000..4ef1481 --- /dev/null +++ b/deploy/host.toml @@ -0,0 +1,46 @@ +# host.toml — the host defaults baked into the container image (Dockerfile). +# +# docker build -t walgit . +# docker compose -f deploy/compose.yaml up -d --build # WAL-Git + WAL-Github +# +# This is not a complete deployment: the bucket and the admin token come from the environment. +# Every key can be overridden with `WALGIT__SECTION__KEY`; the Compose stack reads +# `deploy/walgit.env.example`. Guidance: docs/HOSTING.md. Key-by-key reference: walgit.example.toml. +# +# Durable state is the bucket. /var/lib/walgit is a cache volume: wipe it and a host is cold, +# never wrong. + +[server] +listen = "0.0.0.0:8080" +auto_create_on_push = true # first push to a new owner/name creates the repository +roles = [] # one box: serve + maintain + events + +[server.auth] +mode = "token" +anonymous_read = false +# The admin principal's bearer: export WALGIT_TOKEN (>= 32 random bytes) on the host. +tokens = [{ principal = "admin", token_env = "WALGIT_TOKEN", write = true, admin = true }] + +[server.tls] +mode = "off" # terminate TLS at the edge, or mount a cert and use mode = "files" + +[store] +backend = "s3" +bucket = "" # required: WALGIT__STORE__BUCKET +prefix = "" + +[store.s3] +region = "us-east-1" # WALGIT__STORE__S3__REGION +# Empty = the AWS SDK endpoint for the region. Set it for MinIO, rustfs, R2, Ceph. +endpoint = "" +force_path_style = false +# Credentials: leave unset to use the instance/task role (D43). Set both AWS_ACCESS_KEY_ID and +# AWS_SECRET_ACCESS_KEY to use a static pair; one alone fails closed. + +[cache] +dir = "/var/lib/walgit" +max_bytes = "20GiB" # budget mode: every pack, index and bundle on this disk lives here + +[telemetry] +log_format = "json" +log_filter = "info" diff --git a/deploy/walgit.env.example b/deploy/walgit.env.example new file mode 100644 index 0000000..11f9c30 --- /dev/null +++ b/deploy/walgit.env.example @@ -0,0 +1,47 @@ +# Environment shared by both services in deploy/compose.yaml. +# +# cp deploy/walgit.env.example deploy/.env +# "${EDITOR:-vi}" deploy/.env +# docker compose -f deploy/compose.yaml up -d --build +# +# Compose loads `.env` for ${…} interpolation and passes it to both containers as environment. +# `WALGIT__SECTION__KEY` overrides deploy/host.toml or deploy/walgithub.toml. Guidance: +# docs/HOSTING.md. + +# ---- object store (you provide it; this stack starts no store) ------------------------------ +WALGIT__STORE__BUCKET= +WALGIT__STORE__S3__REGION=us-east-1 +# For an S3-compatible endpoint (MinIO, rustfs, R2, Ceph). Empty = the AWS endpoint for the region. +# The optional deploy/compose.rustfs.yaml publishes one on the host; see docs/HOSTING.md §3. +# WALGIT__STORE__S3__ENDPOINT=http://host.docker.internal:9000 +# WALGIT__STORE__S3__FORCE_PATH_STYLE=true +# Credentials: leave both unset to use the instance/task role (D43). If you need a static pair, +# set BOTH; one alone fails closed. +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= + +# ---- WAL-Git (:8080) ------------------------------------------------------------------------ +# Bearer for the admin principal. Generate one: openssl rand -hex 32 +WALGIT_TOKEN= +# token (default) or oidc. The facade is always auth-free and is not affected by this. +# WALGIT_AUTH_MODE=token +WALGIT_PUBLIC_URL=http://127.0.0.1:8080 +WALGIT_PORT=8080 +WALGIT_LISTEN=0.0.0.0:8080 +WALGIT_CONTAINER_PORT=8080 + +# ---- WAL-Github (:8097) --------------------------------------------------------------------- +# The GHES facade is auth-free; keep it on loopback or a private network (docs/GITHUB.md). +WALGITHUB_PUBLIC_URL=http://127.0.0.1:8097 +WALGITHUB_PORT=8097 +WALGITHUB_LISTEN=0.0.0.0:8097 +WALGITHUB_CONTAINER_PORT=8097 +# Keep this loopback: the facade is auth-free. It does not follow WALGIT_BIND. +WALGITHUB_BIND=127.0.0.1 + +# ---- both ----------------------------------------------------------------------------------- +# Only publish beyond loopback behind a firewall or on a private network. +WALGIT_BIND=127.0.0.1 +# Optional prebuilt images instead of building this checkout. +# WALGIT_IMAGE=registry.example.com/walgit:latest +# WALGITHUB_IMAGE=registry.example.com/walgithub:latest diff --git a/deploy/walgithub.toml b/deploy/walgithub.toml index 65dbaf1..33a3a46 100644 --- a/deploy/walgithub.toml +++ b/deploy/walgithub.toml @@ -1,6 +1,8 @@ -# Runtime defaults for the facade image (Containerfile). -# The deployment supplies the bucket, region and public origin (WALGIT__ overrides). Integrations are -# registered through the `_dev` API (docs/GITHUB.md §11). +# Runtime defaults for WAL-Github: the Dockerfile with +# --build-arg WALGIT_CONFIG_FILE=deploy/walgithub.toml --build-arg WALGIT_HTTP_PORT=8097 +# (deploy/compose.yaml's `walgithub` service does this). The deployment supplies the bucket, region and +# public origin (WALGIT__ overrides). Integrations are registered through the `_dev` API +# (docs/GITHUB.md §11). [server] listen = "0.0.0.0:8097" auto_create_on_push = true diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 6be60c0..0a19140 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -295,5 +295,6 @@ where configured. Do not derive scheduling behavior from this interface catalog. ## walgit-cli (owner: Cli) `walgit --config walgit.toml `: `serve` | `compact [owner/name|--all] [--once]` | `bundle run [--repo] [--strategy]` | `repo create|list|info` | `wal ls|show|materialize --at-seq` | `synth --out DIR --size s|m|l [--commits N --files M]` -| `import --from GITDIR owner/name` | `config check|dump`. Also `Containerfile`, `compose.yaml` (rustfs + -walgit), `justfile`, `walgit.example.toml`, `tests/e2e.sh` (real git vs. server on memory store and on rustfs). +| `import --from GITDIR owner/name` | `config check|dump`. Also `Dockerfile`, `deploy/compose.yaml` (WAL-Git + +WAL-Github; no store), `deploy/walgit.env.example`, `deploy/compose.rustfs.yaml` (separate dev store), `justfile`, +`walgit.example.toml`, `tests/e2e.sh` (real git vs. server on memory store and on rustfs). diff --git a/docs/HOSTING.md b/docs/HOSTING.md new file mode 100644 index 0000000..f72d877 --- /dev/null +++ b/docs/HOSTING.md @@ -0,0 +1,335 @@ +# Hosting walgit + +**Audience:** operators running walgit as a service, not developers running it on a laptop. +`README.md` is the introduction, `walgit.standalone.toml` is the one-box developer shape, and +`docs/GITHUB.md` describes the GitHub Enterprise Server facade — which is auth-free and stays on a +loopback or private network. + +A deployment is two services in front of one object store: + +| Service | What it is | Default | +|---|---|---| +| **WAL-Git** | git smart HTTP, the JSON API, the web UI, bundles and LFS. Token or OIDC auth. | `127.0.0.1:8080` | +| **WAL-Github** | the GHES facade (`/api/v3`, `/api/graphql`, `/login/oauth`) over the same repositories. Auth-free by construction; loopback only. | `127.0.0.1:8097` | + +The bucket is the repository; both services' disks are caches. There is no database, no shared +filesystem, no node identity and no stable IP. You provide the object store; this deployment starts +no S3 service. + +The artefacts this page describes: + +| File | What it is | +|---|---| +| `Dockerfile` | one image for both services: web UI embedded, git + git-lfs + tini, uid 1000, health-checked | +| `deploy/host.toml` | WAL-Git's config (`/etc/walgit/walgit.toml`); env-overridable | +| `deploy/walgithub.toml` | WAL-Github's config; the same image built with `WALGIT_CONFIG_FILE` + `WALGIT_HTTP_PORT` | +| `deploy/compose.yaml` | the WAL deployment: WAL-Git + WAL-Github, two cache volumes, JSON logs | +| `deploy/walgit.env.example` | the shared environment; copy it to `deploy/.env` | +| `deploy/compose.rustfs.yaml` | an **optional, separate** S3-compatible dev store (`just dev-store`) | +| `deploy/nginx.conf.example` | an optional edge: public TLS, `auth_request`, `X-Accel-Redirect` byte offload | + +## 1. Prerequisites + +- **An object store bucket.** S3 or S3-compatible (AWS, MinIO, rustfs, R2, Ceph) or GCS. The bucket + holds every durable byte; size it for all repositories, their bundles and LFS objects. It must exist + before the first request (`walgit repo create` and the manifest CAS create keys, not buckets). +- **Credentials** that can read, write, delete and list objects under the configured prefix. + Prefer an instance role / ECS task role / workload identity: the S3 backend uses the AWS SDK chain + when neither static-key variable is set, and refreshes role credentials automatically (D43). If you + must use a static pair, set **both** `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`; one alone fails + closed. For GCS the credentials are Application Default Credentials. +- **Docker Engine 24+ with Compose v2**, or any orchestrator that can run the image with an + environment and a writable `/var/lib/walgit`. +- **A TLS terminator** unless you deliberately run private HTTP. See §4. + +Recommended, not required: bucket versioning (the few objects rewritten in place — `manifest.pb`, +leases, bundle lists, the events cursor — are recoverable from a previous version) and a lifecycle rule +that does not expire `repos/`, `bundles/`, `lfs/` or `events/` objects. + +## 2. Build the images + +`deploy/compose.yaml` builds both services from one `Dockerfile`: + +```sh +docker compose build +# or, to build and start in one step +docker compose up -d --build +``` + +The build is multi-stage: the Vite SPA and `repos.js` SDK are embedded into the binary, then +`walgit` and `walgit-server` are compiled, and the runtime layer is Debian with git, git-lfs, CA +certificates, tini and curl. The web artifacts are required — the build fails without them. + +WAL-Git uses the default build (`deploy/host.toml`, port 8080). WAL-Github is the same build with two +arguments, which Compose passes for you: + +```sh +docker build --build-arg WALGIT_CONFIG_FILE=deploy/walgithub.toml \ + --build-arg WALGIT_HTTP_PORT=8097 -t walgithub:latest . +``` + +`WALGIT_BUILD_SHA` is embedded and shown on `/readyz` and in the UI footer; pass your commit id in CI. + +## 3. Start it with Compose + +```sh +cp deploy/walgit.env.example deploy/.env +"${EDITOR:-vi}" deploy/.env # WALGIT_TOKEN, WALGIT__STORE__BUCKET, the two public origins +docker compose up -d --build +docker compose ps +curl -fsS http://127.0.0.1:8080/readyz +curl -fsS http://127.0.0.1:8097/healthz +``` + +`deploy/.env` is loaded for Compose interpolation and passed to both containers. The services publish on +loopback by default — keep them there and put a proxy in front, or set `WALGIT_BIND` for WAL-Git once +you have a firewall in place. `WALGITHUB_BIND` is separate on purpose: the facade is auth-free. Each service has its own cache volume; remove either at any time: + +```sh +docker compose down # stop; the bucket keeps everything +docker compose down -v # also lose warmth (both caches only) +``` + +Use WAL-Git (token auth): + +```sh +# the token in deploy/.env is the admin principal's bearer +git -c http.extraHeader="Authorization: Bearer $WALGIT_TOKEN" \ + push http://127.0.0.1:8080/acme/app.git main # auto_create_on_push creates the repository +``` + +Use WAL-Github (no auth, local only): + +```sh +git clone http://127.0.0.1:8097/acme/app.git +curl http://127.0.0.1:8097/api/v3/repos/acme/app +``` + +A developer sets her machine up against WAL-Git with the installer (open, no credential needed): + +```sh +sh -c "$(curl -fsSL 'https://git.example.com/services/public/install.sh')" +``` + +It pins the host's CA in self-signed mode, stores the token in `~/.config/git/-token`, installs +the git credential helper and turns on `transfer.bundleURI`. + +### The optional S3-compatible dev store + +`deploy/compose.rustfs.yaml` is a **separate Compose project** for a local rustfs endpoint. It is not +part of the WAL deployment and starts no service implicitly: + +```sh +docker compose -f deploy/compose.rustfs.yaml up -d --wait rustfs +docker compose -f deploy/compose.rustfs.yaml run --rm create-bucket +# or simply: just dev-store +``` + +Point both WAL services at it in `deploy/.env`: + +```dotenv +WALGIT__STORE__S3__ENDPOINT=http://host.docker.internal:9000 +WALGIT__STORE__S3__FORCE_PATH_STYLE=true +AWS_ACCESS_KEY_ID=walgit-dev +AWS_SECRET_ACCESS_KEY=walgit-dev-secret +``` + +Its data is a local volume, not a durable bucket. Do not host repositories you care about on it. +For GCS instead of S3, set `WALGIT__STORE__BACKEND=gcs` and provide Application Default Credentials +(mount a service-account JSON and set `GOOGLE_APPLICATION_CREDENTIALS`); the `[store.gcs]` keys are in +`walgit.example.toml`. + +## 4. Configuration + +`deploy/host.toml` is WAL-Git's starting point and `deploy/walgithub.toml` is WAL-Github's; +`walgit.example.toml` documents every key with its default. Precedence is **baked config < +`WALGIT__SECTION__KEY` environment** (values are parsed as TOML, falling back to strings). The Compose +file passes `deploy/.env` through and then sets the per-service facts (roles, listener, public URL) in its +`environment:` block. + +The two required settings are enforced by the stack: + +| Variable | Meaning | +|---|---| +| `WALGIT_TOKEN` | WAL-Git bearer for the `admin` principal (generate with `openssl rand -hex 32`) | +| `WALGIT__STORE__BUCKET` | the bucket that is the repository (shared by both services) | + +Important overrides in `deploy/.env`: + +| Variable | Default | Notes | +|---|---|---| +| `WALGIT_PUBLIC_URL` | `http://127.0.0.1:8080` | WAL-Git origin; **set it behind a proxy**. bundle-uri, LFS, clone recipes and OAuth callbacks use absolute URLs | +| `WALGITHUB_PUBLIC_URL` | `http://127.0.0.1:8097` | WAL-Github origin; the facade's response URLs are built from it | +| `WALGIT_PORT` / `WALGIT_CONTAINER_PORT` | `8080` | host port / container port for WAL-Git | +| `WALGITHUB_PORT` / `WALGITHUB_CONTAINER_PORT` | `8097` | host port / container port for WAL-Github | +| `WALGIT_LISTEN` / `WALGITHUB_LISTEN` | `0.0.0.0:8080` / `0.0.0.0:8097` | the in-container listeners | +| `WALGIT_BIND` | `127.0.0.1` | host interface WAL-Git publishes on | +| `WALGITHUB_BIND` | `127.0.0.1` | host interface WAL-Github publishes on; keep it loopback (the facade is auth-free) | +| `WALGIT_AUTH_MODE` | `token` | WAL-Git only (`token` or `oidc`); WAL-Github is always auth-free | +| `WALGIT__STORE__S3__REGION` | `us-east-1` | | +| `WALGIT__STORE__S3__ENDPOINT` | empty | AWS endpoint for the region; set for MinIO/rustfs/R2/Ceph | +| `WALGIT__STORE__PREFIX` | empty | share one bucket between deployments | +| `WALGIT__CACHE__MAX_BYTES` | `20GiB` | budget for every materialized pack, index and bundle; leave the volume room | +| `WALGIT__TELEMETRY__LOG_FORMAT` | `json` | container-native; `pretty` for an interactive host | + +The service roles are part of the deployment shape, not `deploy/.env`: WAL-Git runs `["serve","maintain"]`, +WAL-Github runs `["serve","events"]`. To change them, edit `deploy/compose.yaml` (or a Compose override); see §6. + +`deploy/walgit.env.example` is deliberately small. Add any other key from `walgit.example.toml` as +`WALGIT__
__`. Unknown keys and unparsable values are ignored with a WARN rather than +crash-looping an older image, so read the startup log when an override seems not to apply; values that +reach the config are still validated and fail closed. + +### Authentication + +WAL-Git uses `mode = "token"` with one admin bearer. For a team, use OIDC instead, which keeps git on +walgit-issued access tokens and needs no shared secret on developer machines: + +```dotenv +WALGIT_AUTH_MODE=oidc +WALGIT__SERVER__AUTH__ISSUER=https://id.example.com +WALGIT__SERVER__AUTH__ALLOWED_DOMAINS=["example.com"] +WALGIT__SERVER__AUTH__OAUTH_CLIENT_ID=… +WALGIT__SERVER__AUTH__OAUTH_CLIENT_SECRET=… # keep it in deploy/.env, not the image +WALGIT__SERVER__AUTH__SESSION_SECRET=… # >= 32 bytes; every host must share it +WALGIT__SERVER__AUTH__ANONYMOUS_READ=false +``` + +`session_secret` signs session cookies and walgit access tokens: share it across hosts, and rotate it to +revoke every issued token (it signs everyone out). Static `tokens` work in OIDC mode too, for robots. +`mode = "none"` is loopback-only and exists for experiments; `validate` refuses it on a public bind. + +**WAL-Github has no authentication by construction.** It accepts any bearer, resolves one hardcoded +user, and is admin on every repository (`docs/GITHUB.md` §1). Its Compose service forces +`server.auth.mode = "none"` and `server.tls.mode = "off"`; keep it on loopback or a private network. +Never publish `:8097` to the internet. + +### TLS + +TLS applies to **WAL-Git**. WAL-Github is forced to plain HTTP because the facade is a local +development surface (Node clients reject the self-signed cert otherwise). Three shapes, pick one: + +1. **Edge termination (usual).** Run nginx (or any proxy) on the host, terminate TLS there, and keep + the container on `127.0.0.1:8080`. Set `WALGIT_PUBLIC_URL` to the public origin and forward + `Host`. `deploy/nginx.conf.example` is the reference: it also documents one `auth_request` per + credential and `X-Accel-Redirect`, which lets the edge stream bundle/LFS bytes from the bucket + itself. Only enable `server.accel_redirect` when that edge announces + `X-Walgit-Capabilities: accel-redirect`; the response carries a store credential. +2. **walgit terminates TLS.** Mount a PEM chain and key, switch the listener and public URL: + + ```dotenv + WALGIT__SERVER__TLS__MODE=files + WALGIT__SERVER__TLS__CERT=/etc/walgit/cert.pem + WALGIT__SERVER__TLS__KEY=/etc/walgit/key.pem + WALGIT_LISTEN=0.0.0.0:8443 + WALGIT_CONTAINER_PORT=8443 + WALGIT_PORT=8443 + WALGIT_PUBLIC_URL=https://git.example.com + ``` + + Mount the certificate and key read-only (the image runs as uid 1000). The image healthcheck reads + the port from `WALGIT__SERVER__LISTEN` and falls back to HTTPS, so it follows the change. +3. **Self-signed, private networks only.** `mode = "self_signed"` generates a certificate under + `/tls/` and publishes the CA at `/services/public/ca.pem`; the installer pins it for git. + Browsers and GitHub-integration clients need `NODE_EXTRA_CA_CERTS` or an exception; do not use this + for a public host. + +## 5. The cache volume and the root filesystem + +`/var/lib/walgit` holds materialized repositories, pack indexes, a self-signed certificate, task logs and +scratch space for `git index-pack`. It is a cache in the strict sense: the bucket is revalidated on every +read, and losing the volume costs warmth, not data. + +- Each service has its own named volume (`walgit-cache`, `walgithub-cache`). +- Both services run with `read_only: true`: the image's root filesystem is only the OS, git and the + binaries, and the process never writes to it. `/tmp` is a 256 MiB tmpfs; `/var/lib/walgit` is the one + writable mount that holds state. +- Size the filesystem above `cache.max_bytes` (default 20 GiB) plus headroom for scratch; budget mode + refuses to pull a pack set that cannot fit and serves it remotely instead. +- `cache.mode = "disk"` removes the budget and fully materializes repositories (`auto`, the default, + chooses disk when `maintenance.disk = "ssd"`); it is for the host with a real SSD, not for a tmpfs box. +- A bind mount must be writable by uid 1000 (`chown -R 1000:1000 /srv/walgit-cache`); the named volumes + inherit ownership from the image. + +## 6. Two services, several hosts + +Every service may point at one bucket. They coordinate only through the manifest CAS and leases, so there +is no leader and no gossip; "which host serves this repository" is decided by configuration and the URL +prefix, never by a registry. + +- **This Compose stack:** WAL-Git is `serve + maintain`; WAL-Github is `serve + events`. That is one + maintainer and one events host for the deployment, with the facade as the GitHub webhook sink. If you + want the walgit-native events bridge on WAL-Git instead, change the roles and keep exactly one events + host. +- **Placement:** `[placement] serve`/`serve_exclude` and `maintain`/`maintain_exclude` are globs. A host + that does not serve a repository answers its object work with `503` + `Retry-After` before any sync; + refs-level reads (`info/refs`, `ls-refs`, the API, bundle lists, the UI) stay available everywhere. + Route by `//` in the edge. +- **Scale-out** is "start another container with the same environment". The second host is useful as soon + as `/readyz` is 200; it does not need a warm cache. +- **Push broker (optional):** a host that maintains nothing can forward receive-pack to a warm writer + with `[wal] push_broker_url` and a broker token. Publish is CAS-safe, so this is an optimisation, not a + correctness requirement. +- **The SSD host:** a base rebuild (`compact --base`, the maintainer's weekly `BaseRebuild` unit) needs a + real disk. Keep those on one maintainer with `maintenance.disk = "ssd"`; the weekly bundle is then a + bucket-side compose, and everything newer than the base stays in small packs that every tmpfs host can + hold (`docs/BUNDLE_URI_DESIGN.md`). + +Example: two stateless servers and one maintainer, all from the same image. These roles are config, not +the Compose defaults, so set them where the service's config lives: + +```toml +# server-a / server-b +[server] +roles = ["serve"] + +# maintainer-1 +[server] +roles = ["serve", "maintain"] +[maintenance] +disk = "ssd" +``` + +## 7. Operations + +**Health and metrics.** `/healthz` is liveness (the process is up). `/readyz` is readiness: 503 while a +configured prewarm runs, while the instance is draining (D31 phase 2); it carries the instance id, +build revision, roles and placement rules. `/metrics` is Prometheus when `telemetry.metrics = true` (the +default) and sits behind the same auth as the API — scrape WAL-Git with a bearer token. The TLS +fingerprint is logged at startup. Logs are JSON on stdout; the Compose stack rotates them with the +`json-file` driver. + +**Deploying a new image.** Build (or pull) the new tag, then `docker compose up -d`. SIGTERM starts the +drain: the maintenance loop starts no new unit, the running unit is interrupted at once, and serving +continues (phase 1, bounded at 30s); then `/readyz` turns 503, new fetch/push/LFS are refused with 503 + +`Retry-After`, in-flight requests get `server.drain_timeout` (20s), and the process exits. +`stop_grace_period: 60s` in the Compose file leaves room for both phases. On several hosts, one at a time. + +**Backups** are bucket backups. There is nothing on either host worth copying. Versioning the bucket is +the cheap insurance for the objects rewritten in place (`manifest.pb`, leases, bundle lists, the events +cursor); everything else is immutable and content-addressed. Restoring a bucket restores every +repository, including the WAL needed to reproduce checkpoints and bundles. + +**Secrets.** `deploy/.env` is gitignored; keep credentials out of the image. Rotating `WALGIT_TOKEN` requires +recreating the container (static tokens are resolved at startup); rotating `session_secret` revokes every +walgit-issued access token immediately. The facade integration registry (`github/integrations.json`) +contains webhook secrets and lives in the bucket — scope the IAM policy and the bucket's readers +accordingly (`docs/GITHUB.md` §11). + +**Moving work.** If a host dies, start another with the same environment; if you move a repository's +maintainer, change the placement globs on both hosts. There is no data to migrate: the new maintainer +reads the WAL and recomputes the desired state (`AGENTS.md` §2.5b). + +## 8. Troubleshooting + +| Symptom | Likely cause | +|---|---| +| `/readyz` 503 at startup | prewarm still running, or the instance is draining; read the JSON body | +| WAL-Git returns 401 | `WALGIT_TOKEN` was not set (an empty token is dropped at startup); check `docker compose config` | +| WAL-Github answers everything | expected: it is auth-free by construction; keep `:8097` on loopback/private networks | +| `503` + `Retry-After` on clone/fetch | this host is not placed to serve that repository (`[placement]`), or is draining; route to a serving host | +| push rejected with a policy error | per-repository `policy.json` (`docs/POLICY.md`), not the host config | +| `connectivity: missing object` on push | see `docs/INTEGRITY.md`; the maintainer's `fsck`/`repair` units address history damage | +| clone too large / 503 `too large` | the repository does not fit this host and bundle-uri was bypassed; use the installer's recipes, or check `bundles.require` and the bundle list | +| S3 `AccessDenied` only on some operations | the policy lacks delete or list under the prefix; object work needs both | +| container exits with a config error | `Config::validate` failed closed; the message names the key | diff --git a/docs/announcing-walgit.md b/docs/announcing-walgit.md index 1d83681..c7e5060 100644 --- a/docs/announcing-walgit.md +++ b/docs/announcing-walgit.md @@ -71,7 +71,7 @@ bytes: walgit answers a bundle or LFS download with `X-Accel-Redirect` and nginx from the bucket itself (S3 presigned or GCS with walgit's bearer). The example config documents the whole contract. ```sh -nix run github:tobi/walgit -- --config walgit.toml # or: podman build -f Containerfile . +nix run github:tobi/walgit -- --config walgit.toml # or: docker build -t walgit . ``` The repository is at **github.com/tobi/walgit**, MIT licensed. `README.md` is the introduction; `AGENTS.md` is diff --git a/flake.nix b/flake.nix index bfbce8c..f93d2dd 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ # # nix run github:/walgit -- --config walgit.toml # run it # nix build .#walgit # result/bin/{walgit,walgit-server} - # nix build .#image && podman load < result # an OCI image (same bytes as the Containerfile) + # nix build .#image && podman load < result # an OCI image (same bytes as the Dockerfile) # nix develop # rust, protobuf, pnpm, git, just, rustfs-compatible tools # # The web UI is built from web/ with pnpm and embedded into the binary at compile time diff --git a/justfile b/justfile index f2125ad..8737422 100644 --- a/justfile +++ b/justfile @@ -20,7 +20,7 @@ web-build: # https://walgit.localhost:$PORT (default 8080) against local rustfs. Self-contained: starts rustfs (+ bucket) if # it is not answering on :9000 and builds the SPA if web/dist is missing, then runs the server. # `config` defaults to walgit.standalone.toml; point it at a real bucket by editing [store] there. The rustfs -# keys come from the environment (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY; compose.yaml fixes them). +# keys come from the environment (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY; deploy/compose.rustfs.yaml fixes them). # Optional: export WALGIT__SERVER__AUTH__* (OIDC client, session secret) to try browser sign-in locally. dev-local config="walgit.standalone.toml": #!/usr/bin/env bash @@ -40,7 +40,8 @@ dev-local config="walgit.standalone.toml": exec ./target/release/walgit-server --config {{config}} # Start rustfs (S3-compatible) for local dev via podman compose (rootless, no daemon group needed; -# `podman compose` drives compose.yaml through the docker-compose binary dev.yml installs). +# `podman compose` drives deploy/compose.rustfs.yaml through the docker-compose binary dev.yml installs). +# This is a store-only Compose project, separate from the WAL deployment (deploy/compose.yaml). # `podman compose` talks to the podman API socket; rootless nix podman has no systemd unit for it, so # `podman system service` is started (detached, idle-timeout 0) when the socket is missing. dev-store: @@ -58,16 +59,16 @@ dev-store: for _ in $(seq 1 50); do [ -S "$sock" ] && break; sleep 0.2; done [ -S "$sock" ] || { echo "podman API socket did not appear; see /tmp/walgit-podman-service.log"; exit 1; } fi - podman compose up -d rustfs + podman compose -f deploy/compose.rustfs.yaml up -d rustfs echo "Waiting for rustfs to be healthy..." - podman compose run --rm create-bucket + podman compose -f deploy/compose.rustfs.yaml run --rm create-bucket echo "rustfs is running on http://127.0.0.1:9000 (console :9001)" echo "Credentials: walgit-dev / walgit-dev-secret" echo "Bucket: walgit-test" # Stop rustfs. dev-store-stop: - podman compose down + podman compose -f deploy/compose.rustfs.yaml down # --- tests ------------------------------------------------------------------- # Tiers (all hermetic: in-memory store, tempdir caches, real `git` binary): diff --git a/site/index.html b/site/index.html index f15ee21..af8e304 100644 --- a/site/index.html +++ b/site/index.html @@ -137,7 +137,7 @@

One box

Get it

nix run github:tobi/walgit -- --config walgit.toml
 # or
-podman build -t walgit -f Containerfile . && podman run -p 8080:8080 walgit
+podman build -t walgit . && podman run -p 8080:8080 walgit
 # or
 cargo build --release -p walgit-cli      # after `just web-build`

Roles (server.roles): serve, maintain, events. Empty = all. diff --git a/walgit.standalone.toml b/walgit.standalone.toml index 211ac90..fdb45d5 100644 --- a/walgit.standalone.toml +++ b/walgit.standalone.toml @@ -50,16 +50,16 @@ mode = "none" [store] # The one durable thing; every host pointed at it serves the same repositories. -# This shape: rustfs (S3-compatible) in a container, started by `just dev-store` (compose.yaml; -# bucket walgit-test, keys walgit-dev / walgit-dev-secret). Any S3 (AWS, MinIO, R2, Ceph…) is the -# same: set endpoint/region, point the key envs at your credentials. +# This shape: rustfs (S3-compatible) in a container, started by `just dev-store` +# (deploy/compose.rustfs.yaml; bucket walgit-test, keys walgit-dev / walgit-dev-secret). Any S3 +# (AWS, MinIO, R2, Ceph…) is the same: set endpoint/region, point the key envs at your credentials. backend = "s3" bucket = "walgit-test" prefix = "" [store.s3] endpoint = "http://127.0.0.1:9000" region = "us-east-1" -access_key_env = "AWS_ACCESS_KEY_ID" # compose.yaml fixes walgit-dev / walgit-dev-secret for rustfs +access_key_env = "AWS_ACCESS_KEY_ID" # deploy/compose.rustfs.yaml fixes walgit-dev / walgit-dev-secret for rustfs secret_key_env = "AWS_SECRET_ACCESS_KEY" force_path_style = true # A GCS bucket instead (Application Default Credentials):