npm package: @open-xchange/relic
Description: GitLab group backup CLI for disaster recovery
Latest version: 0.1.2

# Relic

[![pipeline status](https://gitlab.com/openxchange/appsuite/web-foundation/relic/badges/main/pipeline.svg)](https://gitlab.com/openxchange/appsuite/web-foundation/relic/-/commits/main)
[![coverage report](https://gitlab.com/openxchange/appsuite/web-foundation/relic/badges/main/coverage.svg)](https://gitlab.com/openxchange/appsuite/web-foundation/relic/-/commits/main)

GitLab group backup CLI for disaster recovery. Published as `@open-xchange/relic`. Backs up all git repositories (mirror clones) and project/group settings from a GitLab instance.

## Features

- Mirror clones of all repositories (`--mirror`) with `gc.auto=0` to preserve force-pushed history
- Wiki repository backup (separate `.wiki.git` mirrors)
- Empty repositories are detected (`empty_repo: true`) and the git mirror step is skipped — settings are still backed up
- Project settings: CI variables, protected branches/tags, webhooks, labels, milestones, deploy keys/tokens, pipeline schedules, approval rules, integrations, releases
- Optional issue + issue-note backup (per-issue notes saved to `issue_notes/<iid>.json`); both default to off because they can be large
- Group settings: CI variables, webhooks, labels, milestones, deploy tokens
- Incremental backups — hard-link copies previous mirror, then `git fetch --prune`
- Recursive group/project discovery with glob-based exclusion patterns
- Hierarchical rate limiting (global + per-endpoint token buckets)
- Configurable concurrency (separate pools for git and API operations)
- Backup verification (`git fsck` + JSON validation)
- Retention-based pruning of old runs
- YAML config with environment variable substitution
- `.env` files in the working directory are auto-loaded by the CLI

## Requirements

- Node.js >= 22
- Git
- pnpm

## Installation

```sh
pnpm install
pnpm build
```

The CLI is available at `dist/cli/index.js`, or via `pnpm dev` during development:

```sh
# development
pnpm dev -- backup --help

# after build
node dist/cli/index.js backup --help
```

## Configuration

Copy the example config and edit it:

```sh
cp config/relic.example.yaml relic.yaml
```

```yaml
gitlab_url: "https://gitlab.com"
gitlab_token: "${RELIC_GITLAB_TOKEN}"   # reads from environment variable
groups:
  - "openxchange"
clone_method: "https"                     # "https" or "ssh"
backup_dir: "/data/backups/gitlab"
concurrency:
  git_operations: 4                       # parallel git clone/fetch
  api_requests: 10                        # parallel API calls
backup_scope:
  repository: true
  wiki: true
  ci_variables: true
  protected_branches: true
  protected_tags: true
  webhooks: true
  labels: true
  milestones: true
  deploy_keys: true
  deploy_tokens: true
  pipeline_schedules: true
  approval_rules: true                    # requires Premium/Ultimate
  integrations: true
  releases: true
  issues: false                           # can be large; off by default
  issue_notes: false                      # comments on issues; requires issues: true
group_backup_scope:
  ci_variables: true
  webhooks: true                          # requires Premium/Ultimate
  labels: true
  milestones: true
  deploy_tokens: true
exclude: []                               # e.g. ["openxchange/archived-*"]
retention_count: 0                        # 0 = keep all runs
log_level: "info"
```

Set your GitLab token:

```sh
export RELIC_GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx"
```

The token needs `read_api` scope. For backing up CI variables, `api` scope is required.

## Usage

### List discovered groups and projects

```sh
relic list -c relic.yaml
relic list -c relic.yaml --format json
relic list -c relic.yaml --format csv
```

### Run a backup

```sh
# full backup
relic backup -c relic.yaml

# dry run (discover only, no writes)
relic backup -c relic.yaml --dry-run

# single project
relic backup -c relic.yaml --project openxchange/some-project

# repos only (skip API settings)
relic backup -c relic.yaml --repo-only

# settings only (skip git clones)
relic backup -c relic.yaml --settings-only

# verbose logging
relic backup -c relic.yaml -v
```

### Verify a backup

```sh
# verify latest run (git fsck + JSON validation)
relic verify -c relic.yaml

# verify a specific run
relic verify -c relic.yaml --run 2026-03-04T08-30-00Z

# only check repos or settings
relic verify -c relic.yaml --check-repos
relic verify -c relic.yaml --check-settings
```

## Backup Directory Layout

```
/data/backups/gitlab/
├── runs/
│   └── 2026-03-04T08-30-00Z/
│       ├── manifest.json
│       ├── groups/
│       │   └── openxchange/
│       │       ├── _settings/
│       │       └── subgroup/
│       │           └── _settings/
│       └── projects/
│           └── openxchange/subgroup/project/
│               ├── repo.git/
│               ├── wiki.git/
│               ├── _settings/
│               │   ├── project.json
│               │   ├── …                          # other settings JSON files
│               │   └── issue_notes/<iid>.json     # when issue_notes is enabled
│               └── _meta.json
├── latest -> runs/2026-03-04T08-30-00Z
└── state.json
```

## Exit Codes

| Code | Meaning |
|------|---------|
| 0 | All projects backed up successfully |
| 1 | Some projects failed (partial backup) |
| 2 | Fatal error (e.g. disk full, config invalid) |

## Development

```sh
pnpm dev -- list -c relic.yaml          # run without building
pnpm test                                # run tests with coverage (output/coverage/)
pnpm test:watch                          # watch mode
pnpm build                               # compile TypeScript
pnpm typecheck                           # type-check without emitting
```

### Layout

- `src/` — library + CLI source
- `spec/` — vitest test suites, mirroring the `src/` layout. Imports use the `@/` alias that maps to `src/`.
- `config/` — example config files
- `dist/` — compiled output (gitignored)
- `output/coverage/` — coverage reports (gitignored)