Rust · Bacula 9+ · BSD-2-Clause

Bacula GitHub Cloud

Complete GitHub backup through Bacula — code history, wikis, LFS, releases, metadata, gists, and package registries protected on your own infrastructure.

20+ Data types
2 Static binaries
0 Bacula patches
100% Resumable restore

What it is

GitHub backup that goes beyond git clone

Bacula GitHub Cloud is a production-grade Rust orchestrator that plugs into your existing Bacula deployment via standard ClientRunBeforeJob hooks — no custom patches, no extra daemons, no schema changes.

Before each Bacula backup run, it mirrors every repository in your configured GitHub organisations to a local staging directory. Bacula’s FileDaemon then picks up the staged data and writes it to tape or long-term storage, exactly as it would any other FileSet.

What sets it apart is coverage. A plain git clone preserves code history. Bacula GitHub Cloud also captures wikis, Git LFS objects, release binaries, issues, pull requests, comments, labels, milestones, Actions workflows and run logs, Projects v2 boards, organisation metadata, security scanning alerts, gists, and package registry content — everything GitHub exposes through its API, stored alongside the repository mirror.

# /etc/github-backup/config.yaml

github:
  auth:
    token_env: GITHUB_BACKUP_TOKEN
  targets:
    - type:            organization
      name:            my-org
      backup_wiki:     true
      backup_lfs:      true
      backup_releases: true
      backup_actions:  true
      backup_packages_content: false
    - type:            user
      name:            my-github-username
      backup_gists:   true

# last_run.json — written after each run
{
  "repos_total":        87,
  "repos_failed":       0,
  "api_calls_total":    4320,
  "duration_seconds":   1842.7,
  "staging_size_bytes": 42949672960
}

Why use it

GitHub is not a backup

Repositories can be deleted, corrupted, or made inaccessible in seconds. Bacula GitHub Cloud brings your GitHub data under the same retention policies, access controls, and disaster recovery guarantees as your on-premises infrastructure.

More than code history

A plain git mirror misses wikis, Git LFS objects, releases, issues, pull requests, Actions workflows, Projects v2 boards, gists, package registries, and organisation metadata. Bacula GitHub Cloud captures all of it in one run.

No Bacula patches required

Integrates via standard ClientRunBeforeJob and ClientRunAfterJob hooks. Works with Bacula Community 9+ and Bacula Enterprise 9+. Nothing to patch, fork, or maintain.

Incremental from the second run

HTTP ETags and since= timestamps eliminate redundant API calls. Only genuinely changed data is re-fetched, even across repositories with thousands of issues and comments.

Rate-limit safe

All parallel workers share an async-safe request budget. When remaining allowance drops to a configurable threshold, every task pauses and resumes automatically after GitHub’s reset window — staying safely inside the 5,000 req/hr limit.

Resumable disaster recovery

The restore binary records every completed step in SQLite. An interrupted restore picks up exactly where it stopped — no risk of duplicate issues or half-pushed repositories on retry.

Built-in observability

Prometheus metrics written after every run, structured JSON logging, a per-run summary in last_run.json, and optional Slack or PagerDuty failure alerts.

Coverage

What gets backed up

Everything GitHub exposes through its API, captured and written to a structured staging directory before Bacula’s FileDaemon runs. The restore binary pushes it all back to GitHub automatically.

Backed up & restorable

  • Git repository history — full bare mirror, all branches and tags
  • Wikis — separate bare mirror per repository
  • Git LFS objects — content-addressed, only missing objects downloaded
  • Releases and binary assets — streamed, skips already-downloaded files
  • Issues, comments, labels, and milestones — JSON, incremental via since=
  • Pull request metadata, review comments, and approval records
  • GitHub Actions workflows, run history, and per-job logs
  • Actions secrets (names), variables (values), and artifact metadata
  • Environments and per-environment variable values
  • Code scanning, Dependabot, and secret scanning alerts
  • Repository traffic (views & clones, 14-day rolling window)
  • Discussions, discussion comments, and replies
  • Branch protection rules and rulesets
  • Collaborators and repository custom properties
  • Projects v2 boards and columns — via GraphQL
  • Organisation metadata — members, teams, webhooks, secrets, variables, org roles, outside collaborators
  • Gists — bare git mirror + metadata.json + comments.json (user targets, opt-in)
  • Package registry binaries — OCI layers, npm, Maven, RubyGems, NuGet (org targets, opt-in)

Backed up as reference only

  • Actions secrets — the GitHub API never returns secret values; stored as placeholder JSON with names only
  • Webhooks — saved as JSON reference; must be manually recreated on restore to avoid unintended triggers
  • Deploy keys — private key material never accessible via API
  • Pull requests on restore — API limitation; PRs require live source and target branches and cannot be recreated verbatim

Use cases

Built for teams that treat source code as infrastructure

If you already run Bacula to protect servers, databases, and storage, your GitHub repositories should live under the same policy.

Compliance

SOC 2 & ISO 27001 retention

Place GitHub data under the same retention schedules, volume encryption, and audit trails as the rest of your Bacula volumes — without writing custom tooling.

Disaster Recovery

Full organisation restore

Select a point-in-time from bconsole and the restore binary automatically pushes code, wikis, LFS objects, releases, labels, milestones, and issues back to GitHub.

Multi-org

Unified backup policy

Back up any mix of GitHub organisations and personal accounts from a single config file and a single Bacula job. No per-organisation tooling or separate schedules.

Regulated industries

Air-gapped & offline storage

Bacula’s PKI Data Encryption encrypts volumes at rest. Pair with tape storage for a fully air-gapped GitHub archive that satisfies strict data sovereignty requirements.

Large organisations

Hundreds of repositories

Configurable parallelism (1–64 workers) with automatic rate-limit management. Scales from a handful of private repos to organisations with hundreds of active repositories.

DevOps / SRE

Prometheus & alerting integration

Metrics written after every run for ingestion via Node Exporter’s textfile collector. Slack and PagerDuty failure alerts. Drop into your existing monitoring stack with no extra configuration.

Features

Everything included, nothing bolted on

Two self-contained static binaries. No runtime dependencies beyond git and git-lfs. SQLite is bundled.

Full git mirror

Bare --mirror clone on first run; incremental git remote update --prune on every subsequent run.

Wiki mirror

Separate bare mirror for each repository’s wiki. Gracefully skips repositories with wikis disabled.

Git LFS support

Content-addressed LFS object store. Only missing objects are downloaded; existing objects are never re-fetched.

Release assets

Binary assets streamed to local storage. Skips already-downloaded files by comparing remote and local sizes before downloading.

Full metadata export

Issues, pull requests, comments, labels, milestones, Actions workflows, Projects v2 boards, environments, branch protection rules, rulesets, collaborators, discussions, and traffic exported as structured JSON.

Security alert export

Code scanning, Dependabot, and secret scanning alerts exported per repository alongside the rest of the metadata.

Organisation metadata

Members, teams, webhooks, secrets (names), variables, packages, org roles, and outside collaborators captured as JSON alongside repository data in the staging directory.

Gist mirror

Bare git mirror of each user gist plus metadata.json and comments.json. Opt-in per user target with backup_gists: true.

Package registry backup

OCI image layers (ghcr.io), npm tarballs, Maven JARs, RubyGems, and NuGet packages downloaded and stored under packages/content/. Opt-in per org target with backup_packages_content: true.

Mixed public/private auth

Per-repo auto-detection — the token is sent only to private repositories. Public repos in the same organisation are cloned without credentials, and targets can be set to use_token: false for fully public sources.

Incremental sync

HTTP ETags and since= timestamps stored in a bundled SQLite database. Changed data only, from the second backup run onwards.

Resumable restore

A separate SQLite step-tracker records every completed restore action. Interrupted restores pick up exactly where they stopped without duplicating work.

Prometheus metrics

Repos processed, failed, API calls made, rate-limit pauses, run duration, and staging directory size — written to metrics.prom after every run.

Slack & PagerDuty alerts

Set GITHUB_BACKUP_SLACK_WEBHOOK or GITHUB_BACKUP_PAGERDUTY_KEY to receive failure notifications automatically.

Graceful shutdown

SIGTERM and SIGINT are caught at the orchestrator level — in-progress repositories finish cleanly before the process exits, so no partial staging data is left behind.

Service account isolation

Dedicated bacula-github user with no login shell, no sudo access. Staging directory created at mode 0o750; state database at 0o700.

Security-hardened

Token redaction in all log output, HTTPS-only clone enforcement, strict repository name allowlist, config file permission check on startup.

Quick start

Up in three steps

Requires git 2.40+ and git-lfs 3.0+ on the host running the Bacula FileDaemon. Works with Bacula 9+ (Community or Enterprise).

1

Install

Download the pre-built binary tarball from the release page, verify the GPG signature, and install the two binaries.

V=v0.10.0 OS=linux-x86_64 AR=bacula-ghe-cloud-${V}-${OS}.tar.gz # Verify checksum + GPG signature sha256sum -c ${AR}.sha256 gpg --verify ${AR}.asc ${AR} # Extract and install tar -xzf ${AR} DIR=bacula-ghe-cloud-${V} sudo cp ${DIR}/github-backup \ ${DIR}/github-restore \ /usr/local/bin/
2

Configure

Copy the example config, lock down permissions, and store your GitHub PAT.

sudo mkdir -p /etc/github-backup sudo cp config.yaml.example \ /etc/github-backup/config.yaml sudo chmod 0600 \ /etc/github-backup/config.yaml # Store PAT — token file must be 0600 echo "ghp_..." | sudo tee \ /etc/github-backup/token sudo chmod 0600 \ /etc/github-backup/token
3

Wire into Bacula

Add the hook to your Bacula Director job definition and run a test backup from bconsole.

Job { Name = "GitHubBackup" Client = backup-client-fd Client Run Before Job = "github-backup \ --config \ /etc/github-backup/config.yaml" FileSet = "GitHubStaging" Schedule = "WeeklyCycle" Pool = GitHubPool } # bconsole> run job=GitHubBackup

Protect your source code with the infrastructure you already run.

GitHub repositories, wikis, releases, gists, and metadata — backed up by Bacula, retained on your terms, restorable on demand.