Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ jobs:
fail-fast: false
matrix:
node: [22, 24]
services:
# S3-compatible server for the S3 integration tests. The credentials are fixed test
# values for this throwaway container, not secrets.
s3:
image: andrewgaul/s3proxy:latest
ports:
- 9000:80
env:
S3PROXY_AUTHORIZATION: aws-v2-or-v4
S3PROXY_IDENTITY: test-access
S3PROXY_CREDENTIAL: test-secret
JCLOUDS_PROVIDER: filesystem
JCLOUDS_FILESYSTEM_BASEDIR: /data
env:
GHBACKUP_TEST_S3_ENDPOINT: http://localhost:9000
GHBACKUP_TEST_S3_ACCESS_KEY_ID: test-access
GHBACKUP_TEST_S3_SECRET_ACCESS_KEY: test-secret
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -20,5 +37,15 @@ jobs:
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- name: Wait for the S3 server
run: |
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' "$GHBACKUP_TEST_S3_ENDPOINT/" || true)
# An unauthenticated request is answered with 403 once S3Proxy is up.
if [ "$code" = "403" ]; then echo "S3 server ready after ${i}s"; exit 0; fi
sleep 1
done
echo "S3 server did not become ready" >&2
exit 1
- run: npm test
- run: npm run build
6 changes: 6 additions & 0 deletions LEARNINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@

## 2026-10-14 — Bulut oturumları kullanıcı düzeyindeki CLAUDE.md'yi yüklemez
Claude Code bulut oturumlarında `~/.claude/CLAUDE.md` bulunmaz. Yalnızca repodaki `CLAUDE.md`, `.claude/skills` ve `.claude/settings.json` gelir. Bu yüzden planlama ve geliştirme skill'leri uyulması gereken kuralları (credential'lar, git, doküman güncelliği, geriye dönük uyumluluk) kendi içinde taşır. Bulut ortamı `CLAUDE_CODE_REMOTE=true` ile tanınır.

## 2026-10-14 — MinIO resmi imajı kalktı; CI'da S3Proxy
`minio/minio` Docker Hub'dan kaldırıldı (MinIO ücretsiz imaj dağıtımını durdurdu, repo arşivlendi). S3 entegrasyon testleri CI'da `andrewgaul/s3proxy` (filesystem backend, kimlik env ile) servis container'ına karşı çalışır; yerelde aynı sunucu Maven Central'daki `org.gaul:s3proxy` fat jar'ıyla (Java yeterli, Docker gerekmez) başlatılır. Kullanıcıların kendi barındırdığı MinIO hâlâ desteklenen bir S3 uç noktasıdır.

## 2026-10-14 — AWS SDK v3 varsayılan olarak CRC32 sağlama toplamı ekler
Üçüncü parti S3 servisleri bu başlıkları reddedebildiği için istemci `requestChecksumCalculation` ve `responseChecksumValidation` için `WHEN_REQUIRED` ile oluşturulur (K-035); `DeleteObjects` gibi zorunlu kılan işlemler yine sağlama toplamı gönderir.
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A Node.js/TypeScript CLI (`github-backup`) that backs up every repository you can reach through the GitHub CLI (`gh`): your personal repositories and those of all organizations you belong to, with wikis and Git LFS objects. Backups go to a local directory and/or any S3-compatible bucket (AWS S3, Cloudflare R2, Backblaze B2, MinIO) as git bundle snapshots, and a new snapshot is created only when refs change.

Status: under development. `list` and `backup` to local targets work; S3 targets and Git LFS are not implemented yet. See the plan (Turkish) in [plans/README.md](plans/README.md).
Status: under development. `list` and `backup` to local and S3 targets work; Git LFS is not implemented yet. See the plan (Turkish) in [plans/README.md](plans/README.md).

## Requirements

Expand All @@ -28,6 +28,41 @@ github-backup backup --local ~/git-backup [--keep 7] [--concurrency 4] [--no-wik

Exit codes: `0` success, `1` at least one repository failed (the others are still backed up), `2` configuration or prerequisite error, `3` another run is in progress, `130` interrupted.

## S3 targets

Any S3-compatible service works (AWS S3, Cloudflare R2, Backblaze B2, MinIO, ...). Add a target of type `s3` to the config file:

```json
{
"type": "s3",
"name": "r2",
"bucket": "my-backups",
"prefix": "git",
"endpoint": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
"region": "auto",
"forcePathStyle": false,
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"keep": 14
}
```

| Field | Meaning |
| --- | --- |
| `bucket` | Bucket name (required). |
| `prefix` | Key prefix inside the bucket; leading, trailing and doubled `/` are ignored. Default: bucket root. |
| `endpoint` | Service URL; leave out for AWS S3. |
| `region` | Default `us-east-1`; R2 uses `auto`. |
| `forcePathStyle` | Use `https://host/bucket/key` instead of `https://bucket.host/key` (MinIO and some self-hosted services need it). Default `false`. |
| `accessKeyId`, `secretAccessKey` | Static credentials. If both are left out, `GHBACKUP_S3_ACCESS_KEY_ID` and `GHBACKUP_S3_SECRET_ACCESS_KEY` are used, and then the AWS SDK's default chain (environment, shared credentials file, instance role). Setting only one of the two is an error. |
| `keep` | Snapshots kept per repository on this target; overrides the global `keep` and `--keep`. |

The key needs `s3:ListBucket` (also used by the start-up bucket check), `s3:GetObject`, `s3:PutObject` and `s3:DeleteObject`; to clean up multipart uploads that were interrupted, also `s3:ListBucketMultipartUploads` and `s3:AbortMultipartUpload` (without them the cleanup is skipped). Restrict the key to this one bucket.

Before any work starts, `backup` checks every S3 target with a `HeadBucket` request. If the bucket is missing or the credentials are rejected, it prints the target name and the reason (with keys masked) and exits with code 2. Credentials in the config file are readable by anyone who can read that file: `chmod 600` it (a warning is printed otherwise).

Objects are uploaded as streaming multipart uploads, so large bundles are not held in memory, and an object appears only when its upload completes. The manifest is uploaded last.

## Snapshot layout

```text
Expand All @@ -36,7 +71,7 @@ Exit codes: `0` success, `1` at least one repository failed (the others are stil
<root>/<source>/<owner>/<repo>.wiki/... # wiki, same layout
```

`<stamp>` is the UTC time as `YYYYMMDDTHHMMSSZ`. A snapshot without its `.json` manifest is incomplete and is removed on the next run.
`<root>` is the target's `path`, or `s3://<bucket>/<prefix>` for S3 targets; both use the same layout. `<stamp>` is the UTC time as `YYYYMMDDTHHMMSSZ`. A snapshot without its `.json` manifest is incomplete and is removed on the next run.

## Restoring

Expand Down
4 changes: 2 additions & 2 deletions developer-todos.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Sıradaki fazı engelleyen işler.

### Faz 3 — `s3` hedefi

- [ ] 👤 S3 uyumlu servisi seç, bucket oluştur, yalnızca o bucket'a yetkili erişim anahtarı üret ve config'ine ekle.
- [ ] 💻 Gerçek bucket'a yedek alıp bir bundle'ı indirerek doğrula.
- [ ] 👤 S3 uyumlu servisi seç, bucket oluştur, yalnızca o bucket'a yetkili (`s3:ListBucket`, `GetObject`, `PutObject`, `DeleteObject`, isteğe bağlı `ListBucketMultipartUploads`/`AbortMultipartUpload`) erişim anahtarı üret ve config'ine ekle (`chmod 600`).
- [ ] 💻 Gerçek bucket'a `node dist/cli.js backup --target <s3-adın> --include '<kendi-kullanıcın>/*'` çalıştır, bir bundle'ı indirip `git clone <bundle> /tmp/x` ile doğrula; komutu tekrarlayınca özetin `unchanged` göstermesini kontrol et: gerçek bucket ve `gh` oturumu gerekir, bulutta yok.

### Faz 4 — Git LFS

Expand Down
Loading
Loading