Mirror lore.kernel.org mailing-list archives to lore-kernel-org-archives. Each archive epoch gets a repository: /lkml/git/20.git becomes lkml.20.
The service discovers archives automatically and streams Git packs directly to GitHub. It keeps no local archive or database. GitHub repositories are mirrors: upstream ref changes and removals are propagated, so do not edit them directly. Removing an archive from Lore's manifest does not delete its GitHub repository.
lkml.1 is maintained manually. Its history omits Lore commit 299dbd50b6995c6debe2275f0df984ce697fb4cc because GitHub rejects a NUL byte in the commit message. Later commits have different IDs from Lore.
The deployment uses project kernelorg-mirror and a size-one managed instance group (MIG), lore-mirror, in us-central1-a. Its e2-medium VM has 4 GiB RAM and a disposable 20 GiB boot disk, costing approximately $29/month before network traffic and other usage.
Terraform in infra/ is managed by Infrastructure Manager. Cloud Build builds the image and Artifact Registry stores it. The deployment scripts use an authenticated gcloud CLI, flock, and GNU coreutils.
# Build and deploy a new release.
deploy/release.sh
# Apply configuration changes without rebuilding.
deploy/apply.shImage or configuration changes replace the VM, stopping the old worker before starting its replacement. Terraform waits for the group update to finish. Infrastructure Manager stores Terraform state; a local Terraform installation is unnecessary.
deploy/release.sh records the image digest in infra/image.auto.tfvars. Edit infra/runtime.auto.tfvars.json to configure the worker.
| Setting | Meaning |
|---|---|
enabled |
Run one VM when true; scale to zero when false |
lists |
Comma-separated inbox names, such as cgroups; empty selects all inboxes |
Start with one inbox, check the resulting repository and logs, then expand the selection.
Authentication uses the lore-kernel-org-mirror GitHub App, installed for all repositories in lore-kernel-org-archives. It needs Administration, Contents, and Repository creation write permissions, plus Metadata read permission.
To replace its private key in Secret Manager:
python3 - 5551934 177121167 /absolute/path/to/key.pem <<'PY' | \
gcloud secrets versions add github-app --project=kernelorg-mirror --data-file=-
import json, pathlib, sys
print(json.dumps({
"app_id": sys.argv[1],
"installation_id": int(sys.argv[2]),
"private_key": pathlib.Path(sys.argv[3]).read_text(),
}))
PYThe VM reads github-app/versions/latest using its service account. After rotating the key, replace the running VM to load it:
gcloud compute instance-groups managed rolling-action replace lore-mirror \
--project=kernelorg-mirror --zone=us-central1-a \
--max-surge=0 --max-unavailable=1 --replacement-method=recreateList the current VM and its health; use its generated name for SSH over IAP:
gcloud compute instance-groups managed list-instances lore-mirror \
--project=kernelorg-mirror --zone=us-central1-a
gcloud compute ssh VM_NAME --project=kernelorg-mirror --zone=us-central1-a \
--tunnel-through-iap --command='sudo systemctl status lore-mirror --no-pager'Application logs are in Cloud Logging. OpenTelemetry metrics are in Cloud Monitoring, under prometheus.googleapis.com/lore_mirror_*. Use them to track sync latency, throughput, errors, pending work, worker utilization, and Go memory. Metrics are exported every minute and have no per-repository labels.
On the VM, curl -f http://127.0.0.1:8080/healthz checks health. With an explicit lists setting, it returns HTTP 503 until the first valid manifest confirms every requested inbox. If an inbox is missing, the worker exits with an error. An active discovery or sync with no progress for five minutes returns HTTP 503 and sets lore_mirror_stuck to 1. After startup, idle time and retry backoff stay healthy. GCP probes this endpoint every 30 seconds and recreates the VM after three failed checks, with a five-minute startup grace period. The firewall permits port 8080 only from GCP health-check ranges.
Health transitions are recorded in Cloud Logging:
gcloud logging read 'resource.type="gce_instance_group_manager"
resource.labels.instance_group_manager_name="lore-mirror"
log_id("compute.googleapis.com/instance_group_manager_events")
jsonPayload.instanceHealthStateChange:*' \
--project=kernelorg-mirror --freshness=1h --limit=10Discovery and successful repository rechecks use the same -recheck interval (default one hour), plus jitter. Each new archive gets an independent initial delay between zero and that interval. One worker chooses randomly among ready archives and retries failures with backoff; -once also uses a shuffled order. Discovering new mail makes an archive due immediately while preserving failure backoff. Restarting is safe: each sync compares against GitHub's current refs and verifies the result after pushing.
When Lore renames its default branch, the mirror publishes the replacement branch, sets it as the GitHub default, then removes the old branch if Lore deleted it.
Pushes are limited to 1.75 GiB. The service retries oversized pushes after 24 hours. GitHub also rejects blobs larger than 100 MiB.
Requires Go 1.27 or newer. The application uses go-git and builds with CGO disabled.
CGO_ENABLED=0 go test ./...
CGO_ENABLED=0 go vet ./...
go test -race ./...
CGO_ENABLED=0 go build -buildvcs=false -o /tmp/lore-mirror ./cmd/lore-mirror
/tmp/lore-mirror -discover -lists cgroups-discover is read-only and needs no credentials. A sync requires the VM identity and GITHUB_APP_SECRET=projects/kernelorg-mirror/secrets/github-app/versions/latest. Use -once for one pass and -h for available flags.