From 6cc0362b60b762a96fc6125df90415584b8c2efd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:06:27 +0000 Subject: [PATCH 1/3] Clarify --help texts with defaults and add a help flag test Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VfKrT9Df5CR6n4ZEVb8rmH --- plans/README.md | 2 +- plans/faz-05-dokuman-ve-son-kontrol.md | 2 +- src/program.ts | 34 ++++++++++++++++++++------ test/unit/commands.test.ts | 27 ++++++++++++++++++++ 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/plans/README.md b/plans/README.md index e3e2345..d75cc1d 100644 --- a/plans/README.md +++ b/plans/README.md @@ -48,7 +48,7 @@ Fazlar tablodaki sırayla uygulanır; sıranın tek kaynağı bu tablodur. Her f | 2 | Mirror, snapshot ve `local` hedefi | Tamamlandı | [faz-02-mirror-ve-local.md](faz-02-mirror-ve-local.md) | | 3 | `s3` hedefi | Tamamlandı | [faz-03-s3.md](faz-03-s3.md) | | 4 | Git LFS | Tamamlandı | [faz-04-lfs.md](faz-04-lfs.md) | -| 5 | Dokümantasyon ve son kontrol | Bekliyor | [faz-05-dokuman-ve-son-kontrol.md](faz-05-dokuman-ve-son-kontrol.md) | +| 5 | Dokümantasyon ve son kontrol | Devam ediyor | [faz-05-dokuman-ve-son-kontrol.md](faz-05-dokuman-ve-son-kontrol.md) | ## Çalışma kuralları diff --git a/plans/faz-05-dokuman-ve-son-kontrol.md b/plans/faz-05-dokuman-ve-son-kontrol.md index ec28bd1..df64886 100644 --- a/plans/faz-05-dokuman-ve-son-kontrol.md +++ b/plans/faz-05-dokuman-ve-son-kontrol.md @@ -1,6 +1,6 @@ # Faz 5 — Dokümantasyon ve son kontrol -**Durum:** Bekliyor +**Durum:** Devam ediyor ## Amaç diff --git a/src/program.ts b/src/program.ts index d669931..c54a854 100644 --- a/src/program.ts +++ b/src/program.ts @@ -15,9 +15,17 @@ function collect(value: string, previous: string[] = []): string[] { function addCommonOptions(command: Command): Command { return command - .option("--config ", "path to the config file") - .option("--include ", "only repositories matching owner/repo glob (repeatable)", collect) - .option("--exclude ", "skip repositories matching owner/repo glob (repeatable)", collect) + .option("--config ", "config file (default: ~/.config/github-backup-cli/config.json)") + .option( + "--include ", + "only repositories matching this owner/repo glob (repeatable; default */*)", + collect, + ) + .option( + "--exclude ", + "skip repositories matching this owner/repo glob (repeatable)", + collect, + ) .option("--no-archived", "skip archived repositories") .option("--no-forks", "skip forks") .option("--json", "machine-readable output on stdout") @@ -61,13 +69,23 @@ export function createProgram(deps: CommandDeps): Program { }); addCommonOptions(program.command("backup").description("back up repositories to the targets")) - .option("--cache-dir ", "directory for mirrors and temporary files") - .option("--concurrency ", "repositories processed in parallel") - .option("--keep ", "snapshots to keep per repository and target") + .option( + "--cache-dir ", + "directory for mirrors and temporary files (default: ~/.cache/github-backup-cli)", + ) + .option("--concurrency ", "repositories processed in parallel (default 4)") + .option( + "--keep ", + "snapshots to keep per repository and target (default 7; a target's own keep wins)", + ) .option("--no-wiki", "skip wikis") .option("--no-lfs", "skip Git LFS objects") - .option("--target ", "run only this target (repeatable)", collect) - .option("--local ", "add a temporary local target at this path") + .option( + "--target ", + "run only the configured target with this name (repeatable)", + collect, + ) + .option("--local ", 'add a temporary local target named "local" at this directory') .action(async (_opts, command: Command) => { exitCode = await runBackup(explicitOptions(command), deps); }); diff --git a/test/unit/commands.test.ts b/test/unit/commands.test.ts index c2f763f..e1e4d24 100644 --- a/test/unit/commands.test.ts +++ b/test/unit/commands.test.ts @@ -288,3 +288,30 @@ describe("renderTable", () => { expect(renderTable([])).toBe("REPO ARCHIVED FORK WIKI EMPTY\n\n0 repositories"); }); }); + +describe("backup --help", () => { + it("documents every flag the README lists", async () => { + const { run, out } = setup(); + expect(await run(["backup", "--help"])).toBe(0); + const text = out.join(""); + for (const flag of [ + "--config", + "--include", + "--exclude", + "--no-archived", + "--no-forks", + "--json", + "--verbose", + "--quiet", + "--cache-dir", + "--concurrency", + "--keep", + "--no-wiki", + "--no-lfs", + "--target", + "--local", + ]) { + expect(text).toContain(flag); + } + }); +}); From d7579d7d3b8fe25e36d6c2235fb113e5d36cf103 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:07:59 +0000 Subject: [PATCH 2/3] Write the full README and add CONTRIBUTING guide for sources and targets Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VfKrT9Df5CR6n4ZEVb8rmH --- CONTRIBUTING.md | 55 ++++++++++ README.md | 273 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 313 insertions(+), 15 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..55c596b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +## Setup and checks + +```sh +npm ci +npm run lint # Biome (npm run format fixes formatting) +npm run typecheck # tsc --noEmit +npm test # Vitest: unit tests and integration tests with real temporary git repositories +npm run build # tsc -> dist/ +``` + +All four must pass before a change is merged; CI runs them on Node 22 and 24. You need `git` and `git-lfs` locally (LFS tests are skipped without `git-lfs`). S3 integration tests run only when `GHBACKUP_TEST_S3_ENDPOINT`, `GHBACKUP_TEST_S3_ACCESS_KEY_ID` and `GHBACKUP_TEST_S3_SECRET_ACCESS_KEY` point at an S3-compatible server such as S3Proxy (see `.github/workflows/ci.yml` for the container CI uses). + +## Ground rules + +- The core (`src/core`) knows nothing about a specific source or target type. Type-specific code lives only in `src/sources/*` and `src/targets/*`. +- Call external programs only through `Exec` from `src/util/exec.ts`, with an argument array (no shell). Modules take `exec` as a dependency so tests can pass a fake. +- Never change the user's global git config. Authentication for git child processes goes through the environment returned by `Source.gitEnv()`. +- Never log tokens or access keys, and keep them out of error messages. Register secrets with the logger (`log.addSecret`) and strip credentials from URLs in messages. +- New behavior comes with tests. Test git operations against real temporary repositories (`test/helpers/git.ts`: `TestRemote`, `fakeSource`, `isolatedExec`), and pure logic with table-driven unit tests. +- Code, comments, the README and commit messages are in English. `plans/` is in Turkish. +- The configuration schema is strict. When you change it, update `config.example.json`, the configuration reference in `README.md` and the Configuration section of `plans/mimari.md` in the same change. The same goes for flags and environment variables (`src/program.ts`, `src/config/load.ts`, README, `plans/mimari.md`). + +## Adding a source + +A source is a git service the backup reads repositories from (the first one is `github`, using the `gh` CLI). + +1. Implement the `Source` interface from `src/sources/types.ts` in `src/sources/.ts`: + - `id`: the source type. It becomes the first segment of every repo key (`//`) and the folder name in the cache and in the targets. + - `checkPrerequisites()`: throw `PrerequisiteError` when a tool or login is missing. + - `listRepos()`: return `{ repos, failures }`. An owner that cannot be listed goes into `failures`; do not throw for it. + - `gitEnv()`: environment for git child processes (credential helper, `GIT_TERMINAL_PROMPT=0`). +2. Add a `type` branch to `sourceSchema` in `src/config/schema.ts` (a strict object with the source's own filters). `load.ts` applies flag and environment overrides to `github` sources only; extend `applyOverrides` if the new source should honor them too. +3. Register it in `createSource` in `src/sources/index.ts`. +4. Add tests (parsing of the service's output with a fake `exec`, as `test/unit/github-source.test.ts` does) and update `config.example.json`, the README and `plans/mimari.md`. + +## Adding a target + +A target is a storage that receives snapshots (`local` and `s3` exist). + +1. Implement the `Target` interface from `src/targets/types.ts` in `src/targets/.ts`. Keep the shared layout `////.{bundle,lfs.tar,json}` and reuse the file name parsing in `src/targets/names.ts`. The contract that the core relies on: + - `putSnapshot` writes the bundle and LFS tar first and the manifest last; a snapshot without its manifest is incomplete. Files must not appear under their final name before they are complete. + - `listSnapshots` returns only snapshots that have a manifest, oldest first. + - `deleteSnapshot` removes the manifest first. + - `cleanupIncomplete` removes leftovers that have no manifest. + - `checkAccess` (optional) fails early with a readable reason when the storage is unreachable. +2. Add a `type` branch to `targetSchema` in `src/config/schema.ts` (strict object with `name`, an optional `keep` and the type's own fields). If a field needs expansion or a credential fallback, handle it in `resolveTarget` or the credential step of `loadConfig` in `src/config/load.ts`. +3. Register it in `createTarget` in `src/targets/index.ts`. +4. Add unit tests with a fake client and integration tests against a real service or a local stand-in, in the style of `test/integration/local-target.test.ts` and `test/integration/s3-target.test.ts`. +5. Update `config.example.json`, the README (target reference and snapshot layout), `plans/mimari.md` and, if the layout or manifest changes, `src/core/snapshot.ts`. + +## Commits and pull requests + +Write commit messages in English with a short imperative subject. Keep a pull request to one logical change, and describe what changed and how you verified it. diff --git a/README.md b/README.md index 279e129..74b9770 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,167 @@ # github-backup-cli -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. +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, including 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. A new snapshot is created only when branches or tags changed, and each target keeps the newest N snapshots per repository. -Status: under development. `list` and `backup` to local and S3 targets work, including wikis and Git LFS objects (`.lfs.tar` next to each bundle; needs `git-lfs`, or pass `--no-lfs`). See the plan (Turkish) in [plans/README.md](plans/README.md). +The goal is an independent, restorable copy of your complete git history in case you lose a repository or your GitHub account. The project plan (in Turkish) lives in [plans/README.md](plans/README.md). + +- Private, archived and forked repositories are included by default; narrow the set with glob filters. +- Branches and repositories deleted on GitHub stay in the backup (mirrors never prune). +- Runs once and exits: schedule it with cron or launchd. ## Requirements -Node.js 22 or newer, `git`, and the GitHub CLI (`gh`) signed in with at least the `repo` and `read:org` scopes. Linux and macOS. +- Node.js 22 or newer. +- `git`. +- The GitHub CLI `gh`, signed in with at least the `repo` and `read:org` scopes (`gh auth login`). If an organization enforces SAML SSO, authorize the token for it. +- `git-lfs` when LFS backup is enabled (the default). Without it `backup` stops with exit code 2 and tells you to install it or pass `--no-lfs`. +- Linux or macOS. Windows is not supported. + +## Install -## Build +The tool is installed from the repository; it is not published to npm. ```sh -npm ci && npm run build -node dist/cli.js list +git clone https://github.com/sqaft/github-backup-cli.git +cd github-backup-cli +npm ci +npm run build +npm link # optional: puts `github-backup` on your PATH ``` -## Usage +Without `npm link`, run `node dist/cli.js` instead of `github-backup`. + +## Quick start ```sh -github-backup list [--include ] [--exclude ] [--no-archived] [--no-forks] [--json] -github-backup backup --local ~/git-backup [--keep 7] [--concurrency 4] [--no-wiki] [--json] +github-backup list # which repositories would be backed up +github-backup backup --local ~/backups # back them all up to a directory +github-backup backup --local ~/backups # run again: unchanged repositories are skipped +``` + +The first run mirrors every repository into the cache directory (`~/.cache/github-backup-cli`) and writes one snapshot per repository. Later runs fetch only what changed. + +## Commands + +```text +github-backup list [options] +github-backup backup [options] +``` + +| Flag | Commands | Meaning | +| --- | --- | --- | +| `--config ` | both | Config file. Default `~/.config/github-backup-cli/config.json` (`$XDG_CONFIG_HOME/github-backup-cli/config.json` if set). | +| `--include ` | both | Only repositories whose `owner/repo` matches the glob. Repeatable. Default `*/*`. | +| `--exclude ` | both | Skip repositories matching the glob. Repeatable. Applied after `--include`. | +| `--no-archived` | both | Skip archived repositories. | +| `--no-forks` | both | Skip forks. | +| `--json` | both | Machine-readable output on stdout (`list`: the repositories; `backup`: the run summary). | +| `--verbose` | both | Also log git commands and details. Cannot be combined with `--quiet`. | +| `--quiet` | both | Log only errors and the final summary. | +| `--cache-dir ` | `backup` | Directory for mirrors and temporary files. Default `~/.cache/github-backup-cli`. | +| `--concurrency ` | `backup` | Repositories processed in parallel (1-64). Default 4. | +| `--keep ` | `backup` | Snapshots kept per repository and target. Default 7; a target's own `keep` wins. | +| `--no-wiki` | `backup` | Skip wikis. | +| `--no-lfs` | `backup` | Skip Git LFS objects (also skips the `git-lfs` check). | +| `--target ` | `backup` | Run only the configured target with this name. Repeatable. | +| `--local ` | `backup` | Add a temporary `local` target named `local` at this directory. | + +`--no-archived`, `--no-forks`, `--no-wiki` and `--no-lfs` override the environment and the config file only when you type them. + +`list` prints a table (`REPO`, `ARCHIVED`, `FORK`, `WIKI`, `EMPTY`) and a total. `backup` writes log lines to stderr, a summary at the end, and with `--json` a machine-readable summary to stdout. Without any target (`--local`, `--target` or a configured one) `backup` exits with code 2. + +### Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success (unchanged and skipped repositories count as success). | +| `1` | At least one repository, wiki, target or repository owner failed; the others were still backed up. | +| `2` | Configuration, flag or prerequisite error (missing tool, `gh` not signed in, bucket not reachable, no target). | +| `3` | Another `backup` run is in progress (lock in the cache directory). | +| `130` | Interrupted by SIGINT/SIGTERM; running git processes are stopped and the lock is released. | + +A lock file (`/.lock`, containing the PID) prevents overlapping runs; a lock left by a dead process is taken over. + +## Configuration + +Settings come from, in order of priority: command-line flag, environment variable, config file, built-in default. The config file is optional if flags and environment variables are enough. A ready-to-edit example is in [config.example.json](config.example.json). + +```json +{ + "cacheDir": "~/.cache/github-backup-cli", + "concurrency": 4, + "wiki": true, + "lfs": true, + "keep": 7, + "sources": [ + { + "type": "github", + "include": ["*/*"], + "exclude": [], + "archived": true, + "forks": true + } + ], + "targets": [ + { "type": "local", "name": "disk", "path": "/backups/git" }, + { + "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 + } + ] +} ``` -`backup` mirrors every repository (and its wiki) into the cache directory (`~/.cache/github-backup-cli` by default), and writes a new snapshot to each target when the branches or tags changed since the last one. Mirrors only ever grow: branches deleted on GitHub stay in the backup. Each target keeps the newest `keep` snapshots per repository (default 7; a target's own `keep` wins over `--keep`). +The schema is strict: an unknown field is an error, and the message names the field path (for example `targets.0.bucket`). Target names must be unique. + +### Top-level fields + +| Field | Default | Environment variable | Flag | +| --- | --- | --- | --- | +| `cacheDir` | `~/.cache/github-backup-cli` | `GHBACKUP_CACHE_DIR` | `--cache-dir` | +| `concurrency` (1-64) | `4` | `GHBACKUP_CONCURRENCY` | `--concurrency` | +| `wiki` | `true` | `GHBACKUP_WIKI` | `--no-wiki` | +| `lfs` | `true` | `GHBACKUP_LFS` | `--no-lfs` | +| `keep` (positive integer) | `7` | `GHBACKUP_KEEP` | `--keep` | +| `sources` | one `github` source with the defaults below | - | - | +| `targets` | none | `GHBACKUP_TARGETS` (names to run, comma separated) | `--target`, `--local` | + +Other environment variables: `GHBACKUP_CONFIG` (config file path, same as `--config`), `GHBACKUP_INCLUDE` and `GHBACKUP_EXCLUDE` (comma-separated globs), `GHBACKUP_S3_ACCESS_KEY_ID` and `GHBACKUP_S3_SECRET_ACCESS_KEY` (see [S3 targets](#s3-targets)). Booleans accept `true`, `false`, `1` and `0`. Empty variables are ignored. + +`cacheDir` and a local target's `path` expand `~`, `$VAR` and `${VAR}`; relative paths are resolved against the current directory. -`--target ` runs only the named targets from the config file; `--local ` adds a temporary `local` target. Without any target the command exits with code 2. Log lines go to stderr; `--json` prints a machine-readable summary to stdout. +### `github` source -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. +| Field | Default | Meaning | +| --- | --- | --- | +| `type` | - | Must be `"github"`. | +| `include` | `["*/*"]` | Globs on `owner/repo`; a repository must match at least one. An empty list accepts everything. | +| `exclude` | `[]` | Globs on `owner/repo`; matching repositories are skipped. | +| `archived` | `true` | `false` skips archived repositories. | +| `forks` | `true` | `false` skips forks. | + +Globs match `owner/repo` case-insensitively: `*` and `?` do not cross `/`, `**` does, and a pattern without `/` matches nothing. So `acme/*` is every repository of `acme`, `*/*` is everything, and `*/legacy-*` is every repository whose name starts with `legacy-`. The flags and variables `--include`, `--exclude`, `--no-archived`, `--no-forks`, `GHBACKUP_INCLUDE` and `GHBACKUP_EXCLUDE` override these fields of every `github` source. + +The source lists your own repositories plus those of every organization you are a member of. Repositories of other users that you merely collaborate on are not included. If an organization's repositories cannot be listed (for example SSO is not authorized), a warning is printed, the other owners are backed up, and the exit code is 1. + +### Targets + +Every target has a unique `name` and an optional `keep` that overrides the global `keep` and `--keep`. + +`local`: + +| Field | Meaning | +| --- | --- | +| `path` | Directory that receives the snapshots (created if missing). | + +`s3`: see the next section. ## S3 targets @@ -57,28 +192,136 @@ Any S3-compatible service works (AWS S3, Cloudflare R2, Backblaze B2, MinIO, ... | `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`. | +Provider settings: + +| Provider | `endpoint` | `region` | `forcePathStyle` | +| --- | --- | --- | --- | +| AWS S3 | omit | your bucket's region, e.g. `eu-central-1` | `false` | +| Cloudflare R2 | `https://.r2.cloudflarestorage.com` | `auto` | `false` | +| Backblaze B2 | `https://s3..backblazeb2.com` (the endpoint shown for the bucket) | the region in the endpoint, e.g. `us-west-004` | `false` | +| MinIO | `http://host:9000` or your HTTPS URL | `us-east-1` | `true` | + 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. +## What a backup contains + +- All branches and tags with their full history (`refs/heads/*` and `refs/tags/*`). Pull request refs (`refs/pull/*`) are not fetched. +- The wiki repository of every repository that has one (a separate unit named `.wiki`). +- Git LFS objects: every snapshot of a repository that uses LFS gets a `.lfs.tar` with all LFS objects of the mirror. + +Mirrors in the cache directory are only ever fetched into, never pruned, so branches and repositories deleted on GitHub stay in the backup. Each bundle contains the whole repository, not just the changes since the previous snapshot. + ## Snapshot layout ```text ////.bundle +////.lfs.tar # only if the repository has LFS objects ////.json # manifest, written last -///.wiki/... # wiki, same layout +///.wiki/... # wiki, same layout (never has an .lfs.tar) ``` -`` is the target's `path`, or `s3:///` for S3 targets; both use the same layout. `` is the UTC time as `YYYYMMDDTHHMMSSZ`. A snapshot without its `.json` manifest is incomplete and is removed on the next run. +`` is the target's `path`, or `s3:///` for S3 targets; both use the same layout. `` is `github`. `` is the UTC time as `YYYYMMDDTHHMMSSZ`. + +The manifest is JSON: `version`, `source`, `fullName`, `stamp`, `refsHash` (SHA-256 of the sorted ` ` list), `refs` (name and SHA of every ref), `bundleBytes`, `lfsTarBytes` (if any) and `tool` (name and version). A snapshot without its `.json` manifest is incomplete: it is not counted, and it is removed on the next run. + +A new snapshot is written to a target only when the repository's `refsHash` differs from the newest manifest on that target, so unchanged repositories take no extra space. After writing, each target keeps the newest `keep` snapshots per repository (bundle, LFS tar and manifest are deleted together). + +## Scheduling + +`github-backup` runs once and exits; use your system scheduler. Make sure `gh`, `git` and `git-lfs` are on the scheduler's `PATH` and that `gh` is signed in for the same user. + +### cron + +```cron +# Every day at 02:30; log lines go to a file, exit code 1/2/3 shows up in the log. +30 2 * * * PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin github-backup backup --quiet >> "$HOME/github-backup.log" 2>&1 +``` + +Put targets and credentials in the config file. If a run may outlast the interval, nothing needs to be done: a second run exits with code 3 while the first holds the lock. + +### launchd (macOS) + +Save as `~/Library/LaunchAgents/com.example.github-backup.plist`, then run `launchctl load ~/Library/LaunchAgents/com.example.github-backup.plist`: + +```xml + + + + + Label + com.example.github-backup + ProgramArguments + + /usr/local/bin/github-backup + backup + --quiet + + EnvironmentVariables + + PATH + /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin + + StartCalendarInterval + + Hour + 2 + Minute + 30 + + StandardErrorPath + /tmp/github-backup.log + + +``` + +Use the path printed by `which github-backup` (or `node` with the full path to `dist/cli.js`). ## Restoring +There is no `restore` command; a snapshot is restored with git. Copy the files of one snapshot from the target first (from S3, for example `aws s3 cp --recursive s3://my-backups/git/github/acme/app/ . --exclude '*' --include '20260101T020000Z.*'`; add `--endpoint-url` for non-AWS services). + A bundle is a normal git repository history: ```sh git clone ~/git-backup/github/acme/app/20260101T020000Z.bundle app ``` -To get every branch and tag as local refs instead, use `git clone --mirror app.git`. +To get every branch and tag as local refs instead, use `git clone --mirror app.git`. A wiki is restored the same way from `.wiki/.bundle`. + +If the snapshot has an `.lfs.tar`, unpack it into the clone's `.git` directory and check the LFS files out: + +```sh +git clone 20260101T020000Z.bundle restored +tar -xf 20260101T020000Z.lfs.tar -C restored/.git # lfs/objects/... lands in .git/lfs/objects +cd restored && git lfs install --local && git lfs checkout +``` + +To push a restored repository to a new remote, add the remote and push the refs you need (`git push --mirror ` for a mirror clone; for LFS repositories also `git lfs push --all `). + +## Limitations + +- Only git data, wikis and LFS objects are backed up. Issues, pull requests, comments, releases and release assets are not. +- Only github.com; GitHub Enterprise is not supported. +- Only your own repositories and those of organizations you belong to. +- `refs/pull/*` is not backed up. +- Nothing is ever deleted from the backup because it disappeared on GitHub; only old snapshots beyond `keep` are removed. +- No built-in scheduler and no `restore` command. +- Linux and macOS only. + +## Development + +```sh +npm ci +npm run lint # Biome +npm run typecheck # tsc --noEmit +npm test # Vitest +npm run build # tsc -> dist/ +``` + +S3 integration tests run only when `GHBACKUP_TEST_S3_ENDPOINT` (plus `GHBACKUP_TEST_S3_ACCESS_KEY_ID` and `GHBACKUP_TEST_S3_SECRET_ACCESS_KEY`) point at an S3-compatible server; CI starts S3Proxy for this. LFS tests need `git-lfs` and are skipped without it. + +To add a new source (another git service) or target (another storage), see [CONTRIBUTING.md](CONTRIBUTING.md). Architecture and decisions are in [plans/mimari.md](plans/mimari.md) and [plans/kararlar.md](plans/kararlar.md) (Turkish). From 8fda944d78899f27dea8ee2b3815c1f7cd4d65c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:08:07 +0000 Subject: [PATCH 3/3] Complete phase 5 in the plan and link the docs from the architecture notes Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VfKrT9Df5CR6n4ZEVb8rmH --- REPO.md | 1 + developer-todos.md | 2 +- plans/README.md | 2 +- plans/faz-05-dokuman-ve-son-kontrol.md | 18 ++++++++++++------ plans/mimari.md | 3 +++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/REPO.md b/REPO.md index d125c5d..3a8857f 100644 --- a/REPO.md +++ b/REPO.md @@ -18,4 +18,5 @@ GitHub repolarını (wiki ve LFS dahil) yerel diske ve S3 uyumlu depolamaya git - Config şeması (`src/config/schema.ts`) değişirse: `config.example.json`, kök `README.md` config referansı, `plans/mimari.md` Yapılandırma bölümü. - Bayrak veya env değişkeni eklenirse: `src/cli.ts`, `src/config/load.ts`, kök `README.md`, `plans/mimari.md`. - Hedef düzeni veya manifest değişirse: `src/core/snapshot.ts`, `src/targets/*`, kök `README.md` geri yükleme bölümü, `plans/mimari.md`. +- `--help` metinleri (`src/program.ts`) değişirse: kök `README.md` bayrak tablosu ve `test/unit/commands.test.ts` içindeki yardım testi. - Yeni kaynak/hedef eklenirse: `src/sources/index.ts` veya `src/targets/index.ts`, şema, `CONTRIBUTING.md`, README. diff --git a/developer-todos.md b/developer-todos.md index bf6b7c9..964cb4f 100644 --- a/developer-todos.md +++ b/developer-todos.md @@ -32,4 +32,4 @@ Sıradaki fazı engelleyen işler. ### Faz 5 — Dokümantasyon ve son kontrol -- [ ] 💻 README'deki cron örneğiyle zamanlanmış yedeği kur ve ertesi gün özeti kontrol et. +- [ ] 💻 README'deki "Scheduling" bölümündeki cron (macOS için launchd) örneğiyle zamanlanmış yedeği kur ve ertesi gün özeti ile çıkış kodunu kontrol et: gerçek makine ve `gh` oturumu gerekir, bulutta yok. diff --git a/plans/README.md b/plans/README.md index d75cc1d..c5df08a 100644 --- a/plans/README.md +++ b/plans/README.md @@ -48,7 +48,7 @@ Fazlar tablodaki sırayla uygulanır; sıranın tek kaynağı bu tablodur. Her f | 2 | Mirror, snapshot ve `local` hedefi | Tamamlandı | [faz-02-mirror-ve-local.md](faz-02-mirror-ve-local.md) | | 3 | `s3` hedefi | Tamamlandı | [faz-03-s3.md](faz-03-s3.md) | | 4 | Git LFS | Tamamlandı | [faz-04-lfs.md](faz-04-lfs.md) | -| 5 | Dokümantasyon ve son kontrol | Devam ediyor | [faz-05-dokuman-ve-son-kontrol.md](faz-05-dokuman-ve-son-kontrol.md) | +| 5 | Dokümantasyon ve son kontrol | Tamamlandı | [faz-05-dokuman-ve-son-kontrol.md](faz-05-dokuman-ve-son-kontrol.md) | ## Çalışma kuralları diff --git a/plans/faz-05-dokuman-ve-son-kontrol.md b/plans/faz-05-dokuman-ve-son-kontrol.md index df64886..8cae094 100644 --- a/plans/faz-05-dokuman-ve-son-kontrol.md +++ b/plans/faz-05-dokuman-ve-son-kontrol.md @@ -1,6 +1,6 @@ # Faz 5 — Dokümantasyon ve son kontrol -**Durum:** Devam ediyor +**Durum:** Tamamlandı ## Amaç @@ -23,10 +23,10 @@ Kullanıcı README'yi okuyarak aracı kurup, yapılandırıp, cron ile zamanlay ## Görevler -- [ ] README. -- [ ] `CONTRIBUTING.md`. -- [ ] `--help` çıktılarını gözden geçir. -- [ ] Planı ve `REPO.md`'yi kodla karşılaştır; tutarsızlıkları düzelt. +- [x] README. +- [x] `CONTRIBUTING.md`. +- [x] `--help` çıktılarını gözden geçir. +- [x] Planı ve `REPO.md`'yi kodla karşılaştır; tutarsızlıkları düzelt. ## 👤 Kullanıcı aksiyonları @@ -44,4 +44,10 @@ Kullanıcı README'yi okuyarak aracı kurup, yapılandırıp, cron ile zamanlay ## Devam notları -_Henüz yok._ +Faz tamamlandı; kalan tek iş yukarıdaki 💻 maddesi (cron örneğiyle zamanlanmış yedeği gerçek makinede kurmak) ve Faz 1–4'ten kalan 💻 elle doğrulamalar (`developer-todos.md`). + +Yapılan: kök `README.md` baştan yazıldı (kurulum, hızlı başlangıç, bayrak ve çıkış kodu tabloları, config ve env referansı, S3 sağlayıcı tablosu, snapshot düzeni ve manifest, cron ve launchd örnekleri, geri yükleme, sınırlamalar); `CONTRIBUTING.md` eklendi (yeni kaynak/hedef rehberi); `src/program.ts` yardım metinlerine varsayılanlar eklendi ve `test/unit/commands.test.ts`'e yardım bayrağı testi konuldu; `mimari.md` dizin yapısına belgeler, `REPO.md`'ye `--help` ↔ README bağı eklendi. + +Doğrulananlar: README'deki config örneği ve `config.example.json` `configSchema`'dan geçer; README'deki `GHBACKUP_*` değişkenleri `load.ts`'dekilerle birebir aynı; bayraklar `--help` çıktısıyla eşleşir; geri yükleme adımları `test/integration/lfs.test.ts` (klon, tar'ı `.git`'e açma, `git lfs checkout`) ve `local-target.test.ts` ile aynıdır. lint, typecheck, test, build temiz. S3 testleri bu ortamda sunucu olmadığı için atlandı (CI'da çalışır). + +Doğrulanamayanlar: cron ve launchd örnekleri gerçek bir makinede denenmedi (README'deki `aws s3 cp` geri yükleme komutu ve B2/R2 endpoint biçimleri de gerçek servisle denenmedi). diff --git a/plans/mimari.md b/plans/mimari.md index a08b2a6..17a601e 100644 --- a/plans/mimari.md +++ b/plans/mimari.md @@ -16,6 +16,9 @@ ## Dizin yapısı ```text +README.md # kurulum, config referansı, zamanlama, geri yükleme (İngilizce) +CONTRIBUTING.md # geliştirme kuralları, yeni kaynak/hedef ekleme rehberi +config.example.json # şemadan geçen örnek config src/ cli.ts # giriş noktası (bin: github-backup); programı çalıştırıp çıkış kodunu verir program.ts # commander tanımı, bayraklar, hata → çıkış kodu eşlemesi