Rust · Debian · RPM · Forgejo · Codeberg · Bacula Community & Enterprise

Bacula Forgejo

Back up every Forgejo repository, wiki, issue, release, and database on the schedule Bacula already runs — no plugins, no daemons, no surprises.

13+ Data types backed up
3 Database backends
0 Plugins required
Full / Diff / Incr. Backup levels

What it is

Forgejo backup the way Bacula already works

bacula-forgejo adds first-class Forgejo support to an existing Bacula deployment — Community or Enterprise. The package installs two binaries: forgejo-backup runs as a ClientRunBeforeJob hook and pulls all Forgejo data into a local staging directory; forgejo-restore runs as a ClientRunAfterJob hook and pushes recovered data back to a live Forgejo instance.

The Bacula FileDaemon then sweeps the staging directory just as it sweeps any other path. No Bacula plugins are required, no schema changes, and no new daemons. Your existing FileSets, pools, retention policies, and schedules continue to apply — and because no enterprise-only features are used, the tool works identically on Bacula Community and Bacula Enterprise.

Restore comes in two modes: api mode stages every object individually so you can recover a single repository, a single release asset, or a single user without touching anything else; dump mode uses forgejo admin dump for the fastest possible full-instance disaster recovery. A remote_mode flag caps concurrency and disables filesystem-level operations, making the tool safe to run against hosted Forgejo instances such as Codeberg without an SSH connection to the server.

# Staging layout after a full backup
/opt/forgejo-backup/data/
├── _state/      ← ETags, timestamps, run manifest
├── _db/         ← SQLite / MySQL / PostgreSQL dump
├── _dump/       ← forgejo admin dump ZIP (full only)
├── _config/     ← redacted app.ini + custom/
├── _users/      ← profiles, SSH keys, GPG keys
├── _orgs/       ← org JSON, members, teams
├── _registry/   ← OCI blobs + manifests (full only)
├── _packages/   ← package registry (full only)
└── myorg/myrepo/
    ├── repository.git/   ← bare mirror
    ├── wiki.git/
    ├── lfs/              ← content-addressable blobs
    ├── metadata/         ← issues, PRs, labels, etc.
    └── releases/         ← release metadata + assets

# Hook this directory into a standard Bacula FileSet
FileSet {
  Name = "ForgejoPrimary"
  Include { Options { signature = MD5 }
    File = /opt/forgejo-backup/data }
}

Why use it

Everything Forgejo holds — recovered in one restore job

A bare git clone --mirror gets the code. bacula-forgejo gets the rest: issue history, contributor keys, package registry, container images, and the database that ties it all together.

No Bacula plugins

The staging-directory approach means the FileDaemon treats Forgejo data exactly like local files. No plugins to install, no compatibility matrix between Bacula versions and plugin versions, no additional moving parts to maintain.

True incremental backups

Git repositories mirror with git remote update --prune; LFS pulls only new content-addressable blobs; issue and release metadata uses since= parameters. Daily jobs touch only what changed — weekly Full jobs capture everything.

Secure by default

The API token never appears in process arguments. It is loaded from an environment variable or a 0600 file. The package runs under a dedicated forgejo-backup system user with no login shell and no write access outside the staging directory.

Three database backends

SQLite, MySQL, and PostgreSQL are supported out of the box. PostgreSQL backups use pg_dump --format=directory with configurable parallelism so dump time scales with available CPU cores.

Two restore modes

API mode restores individual repositories, organisations, or users — pass --repos myorg/my-repo and only that repository is pushed back. Dump mode uses forgejo admin dump for the fastest possible full-instance disaster recovery when speed matters more than granularity.

Works with your existing schedule

The hook reads the Bacula job level via --level %l and adjusts its module selection automatically. Configure once; the backup level drives the rest. Your existing Full/Differential/Incremental schedule applies without modification.

Coverage

What gets backed up

All data is pulled before the FileDaemon sweep. The staging directory holds a consistent snapshot of every object listed below at the time the job runs.

Every backup (Full / Differential / Incremental)

  • Git repositories — bare mirror via git remote update --prune
  • Git LFS objects — content-addressable; only new blobs transferred
  • Wikis — bare mirror
  • Issues, PRs, and comments — incremental via since= timestamp
  • Labels, milestones, and branch protections
  • Release metadata and binary assets — size-matched skip for unchanged assets
  • Forgejo Actions workflows and run logs — configurable log cap
  • User profiles, SSH keys, and GPG keys
  • Organisation structure — members, teams, webhooks, secrets (names only)
  • Database dump — SQLite, MySQL, or PostgreSQL
  • Redacted app.ini and custom/ directory

Full backups only

  • Container / OCI registry — filesystem copy or OCI Distribution API
  • Package registry — all Forgejo package types
  • forgejo admin dump ZIP — optional; written to a separate Bacula pool

Explicitly does not

  • Modify any data on the live Forgejo instance during backup — read-only throughout
  • Replace the Bacula schedule, storage, or retention configuration — all existing policies apply
  • Require any Bacula plugin — all data flows through standard FileSets and the FileDaemon

Use cases

Built for teams running Forgejo and Bacula in production

Whether you operate a single Forgejo instance or manage git infrastructure for multiple teams, bacula-forgejo fits into the backup architecture you already have.

Infrastructure teams

Full Forgejo disaster recovery

Back up every repository, issue, release, container image, and the database in a single coordinated Bacula job. Restore to a fresh Forgejo instance in the same coordinated fashion — no manual API calls, no ad-hoc scripts.

DevOps

Daily incrementals, weekly fulls

Daily Differential jobs back up changed metadata and new LFS blobs in minutes. Weekly Full jobs capture the registry and package content. The hook reads the Bacula job level and selects modules automatically.

Compliance

Issue and PR audit trail

Every comment, label change, and branch protection is preserved in the staging directory with the timestamp it was pulled. Restoring a repository brings back its full issue history, not just the code.

Codeberg

Back up your Codeberg account

Enable remote_mode and point forgejo_url at codeberg.org. The tool caps concurrency, skips filesystem operations, and uses only the Forgejo-compatible API — no SSH access to Codeberg’s servers required. Your repositories, wikis, issues, and release assets land in Bacula just like local data.

MSPs

Per-customer Forgejo isolation

Run separate Bacula jobs targeting separate Forgejo instances with separate API tokens and staging directories. Each customer’s data lives in its own FileSet and pool — no cross-contamination at the storage level.

Platform teams

Container and package registry backup

OCI image layers and Forgejo package content (npm, pip, Maven, Debian, RPM, and more) are captured on Full jobs alongside the code that produced them. Recover a historical release with its exact binary artefacts intact.

Features

Everything in the package — no tiers, no licences

BSD 2-Clause licence. The complete feature set ships in one package — .deb for Debian/Ubuntu, .rpm for RHEL/Rocky/Fedora/SUSE — because the staging-directory approach has no per-feature runtime costs.

Pre/post job hook integration

ClientRunBeforeJob and ClientRunAfterJob directives in the Bacula Director config. No daemon, no persistent process, no port to open.

Incremental-aware metadata sync

Issues, PRs, comments, releases, and Actions logs use since= timestamp parameters to pull only what changed since the last run. ETag caching skips unchanged API responses.

Content-addressable LFS backup

Git LFS objects are identified by their SHA-256 content hash. Incremental jobs transfer only blobs not already present in the staging directory — no re-downloading of unchanged objects.

Parallel PostgreSQL dump

pg_dump --format=directory --jobs=N with a configurable job count. Large databases complete in a fraction of the time of a single-threaded dump.

OCI registry backup

Container images backed up via OCI Distribution API or direct filesystem copy. Each layer stored once (content-addressable) regardless of how many images reference it.

Two restore modes

API mode: forgejo-restore --repos myorg/my-repo restores a single repository without touching anything else. Flags for org, user, database, and registry restore run independently. Dump mode: uses forgejo admin dump for the fastest possible full-instance disaster recovery.

Remote-forge support

remote_mode: true in config.yaml disables filesystem-level operations and caps API concurrency. Safe to run against hosted Forgejo instances (Codeberg and similar) without SSH access to the server — only the Forgejo API is required.

Module-level control

Enable or disable any module (registry, packages, dump, LFS, wikis) in config.yaml. Override the Full-only default on any module without touching the Bacula director config.

Dedicated service account

The package creates a forgejo-backup system user with no login shell and ownership of only the staging and log directories. The Bacula FileDaemon runs the hook via sudo -u forgejo-backup.

Run manifest and Prometheus metrics

Each backup run writes a JSON manifest to _state/ with per-module timing and item counts. A live Prometheus metrics file in the same directory is ready for node_exporter’s textfile collector.

Redacted configuration backup

app.ini is copied with secrets replaced by REDACTED placeholders before it reaches the staging directory. The custom/ directory (themes, templates, static files) is copied verbatim.

GPG-signed packages

Both the .deb (Debian/Ubuntu) and .rpm (RHEL/Rocky/Fedora/SUSE) release packages are GPG-signed and carry SHA-256 checksums. Install directly — no third-party repository required.

Written in Rust

A single statically-linked binary per command. No runtime dependencies beyond git, git-lfs, and the appropriate database dump tool. Starts in milliseconds; adds negligible overhead to the Bacula job.

Quick start

Running in four steps

Requires Forgejo 7.x or later, Bacula Community or Enterprise, and Debian 12+ (Bookworm) or RHEL 9+ / Rocky / Fedora / SUSE. git and git-lfs must be installed on the backup client.

1

Install the package

Install the signed package for your distribution. The package creates the forgejo-backup system user, the staging directory at /opt/forgejo-backup/data/, and the log directory at /var/log/forgejo-backup/.

# Debian / Ubuntu apt install ./bacula-forgejo_0.1.1_amd64.deb # RHEL / Rocky / Fedora dnf install ./bacula-forgejo-0.1.1-1.x86_64.rpm # SUSE zypper install ./bacula-forgejo-0.1.1-1.x86_64.rpm
2

Create the configuration file

Copy the example config and set permissions before editing. The file must be owned by forgejo-backup and not world-readable — it may reference secret file paths.

cp /usr/share/doc/bacula-forgejo/config.yaml.example \ /etc/forgejo-backup/config.yaml chmod 0600 /etc/forgejo-backup/config.yaml chown forgejo-backup:forgejo-backup \ /etc/forgejo-backup/config.yaml $EDITOR /etc/forgejo-backup/config.yaml
3

Wire the hooks into Bacula

Add the ClientRunBeforeJob and ClientRunAfterJob directives to your Bacula Job resource. The %l macro passes the current backup level to the hook.

Job { Name = "ForgejoPrimary" ... Client Run Before Job = "sudo -u forgejo-backup \ env FORGEJO_BACKUP_TOKEN=<token> \ forgejo-backup \ --config /etc/forgejo-backup/config.yaml \ --level %l" Client Run After Job = "sudo -u forgejo-backup \ forgejo-restore \ --config /etc/forgejo-backup/config.yaml \ --verify-only" }
4

Run a test backup

Trigger a manual Full job and confirm the staging directory is populated before the FileDaemon sweep begins.

# Trigger a manual Full run sudo -u forgejo-backup \ env FORGEJO_BACKUP_TOKEN=<token> \ forgejo-backup \ --config /etc/forgejo-backup/config.yaml \ --level F # Confirm staging directory contents ls -lh /opt/forgejo-backup/data/ # Then run the Bacula job normally via bconsole # run job=ForgejoPrimary level=Full yes

Your Forgejo data belongs in your backup infrastructure.

BSD 2-Clause, open source, and slots into the Bacula deployment you already operate. Two binaries. No plugins.