https://github.com/davidmalko87/jira-confluence-full-instance-backup
Automated full-instance backup for Jira & Confluence Cloud (Standard plan). Jenkins + CLI/menu; encrypted 7-Zip archives to GCS, S3, Azure or local; notify via Slack, Teams, email or webhook.
https://github.com/davidmalko87/jira-confluence-full-instance-backup
7zip atlassian atlassian-cloud automation azure backup cli confluence devops disaster-recovery gcs jenkins jira python s3 sre
Last synced: 28 days ago
JSON representation
Automated full-instance backup for Jira & Confluence Cloud (Standard plan). Jenkins + CLI/menu; encrypted 7-Zip archives to GCS, S3, Azure or local; notify via Slack, Teams, email or webhook.
- Host: GitHub
- URL: https://github.com/davidmalko87/jira-confluence-full-instance-backup
- Owner: davidmalko87
- License: mit
- Created: 2026-05-26T15:40:26.000Z (28 days ago)
- Default Branch: master
- Last Pushed: 2026-05-27T00:11:41.000Z (28 days ago)
- Last Synced: 2026-05-27T00:14:00.938Z (28 days ago)
- Topics: 7zip, atlassian, atlassian-cloud, automation, azure, backup, cli, confluence, devops, disaster-recovery, gcs, jenkins, jira, python, s3, sre
- Language: Python
- Homepage:
- Size: 183 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Jira & Confluence Full-Instance Backup
[](https://github.com/davidmalko87/jira-confluence-full-instance-backup/actions/workflows/ci.yml)
[](https://pypi.org/project/jira-confluence-full-instance-backup/)
[](https://pypi.org/project/jira-confluence-full-instance-backup/)
[](LICENSE)
[](https://pypi.org/project/jira-confluence-full-instance-backup/)
[](https://www.atlassian.com/software/jira)
[](https://www.atlassian.com/software/confluence)
[](#storage-backends)
[](#notification-channels)
[](https://github.com/davidmalko87/jira-confluence-full-instance-backup/commits/master)
Automated **full-instance backup** of **Jira Cloud** and **Confluence Cloud** for
the Atlassian **Standard plan**. Run it by hand from an interactive menu, or
unattended from Jenkins/cron. Backups are encrypted and shipped to **the cloud
of your choice** — Google Cloud Storage, AWS S3 (and S3-compatible stores),
Azure Blob, or a local/mounted directory — with notifications to **any channel**
you use: Slack, Microsoft Teams, Discord, Google Chat, email, or a generic
webhook.
---
## Why?
On **March 30, 2026**, Atlassian [deprecated the Backup Manager API](https://community.atlassian.com/forums/Jira-questions/Backup-Manager-API-deprecation-is-there-going-to-be-a/qaq-p/3120079) for Jira Cloud. Direct API-token calls to the backup endpoint now return:
```
HTTP 403
{"error":"This feature is only accessible from the UI."}
```
The replacement [v2 Backup & Restore API](https://developer.atlassian.com/cloud/admin/backup/) is Premium/Enterprise only, leaving Standard-plan customers with no automation path for full-instance backup — only the manual UI button. This tool restores that automation by **replaying the browser UI session** for Jira, while Confluence uses the OBM REST API (which still accepts API tokens). Both flow into one pipeline that archives, encrypts, uploads, and notifies.
For per-project Jira backup/restore, see the sibling project [`jira-project-backup-restore`](https://github.com/davidmalko87/jira-project-backup-restore).
---
## Features
| Feature | Description |
|---|---|
| **Full-instance backup** | Jira (all projects, attachments, avatars, logos) + Confluence (all spaces, attachments) in one run |
| **Run anywhere** | Interactive **menu** for VMs/manual use **and** **CLI flags** for Jenkins/cron — same codebase; the `Jenkinsfile` runs on **Linux and Windows** agents |
| **One-paste Jenkins setup** | Export a Script Console script from your config that creates all credentials + the pipeline job — no manual credential forms |
| **Pluggable storage** | **GCS · AWS S3 / S3-compatible (R2, B2, MinIO, Spaces) · Azure Blob · local** — pick one flag; only that SDK is needed |
| **Pluggable notifications** | **Slack · Teams · Discord · Google Chat · email (SMTP) · generic webhook** — any combination, no extra deps |
| **Optional encryption** | 7-Zip AES-256 with encrypted headers — on by default, switch off with `--no-encrypt` |
| **Configurable compression** | `0` (store) … `9` (ultra) |
| **Custom filenames** | Name templates: `{product} {site} {date} {time} {datetime} {timestamp}` |
| **Integrity & housekeeping** | `manifest.json` with sha256 + `--validate`, `--cleanup`, `--skip-existing`, `--dry-run` |
| **Cooldown-aware** | Atlassian's 48h throttle (HTTP 412) is detected and skipped cleanly — no false failures |
| **Connection test** | Validates Jira cookies + Confluence token, warns when the Jira session is near expiry |
---
## Install
```bash
pip install jira-confluence-full-instance-backup # core (requests)
# add the storage backend you use:
pip install "jira-confluence-full-instance-backup[s3]" # or [gcs] / [azure]
# optional nicer interactive output (progress bars, color):
pip install "jira-confluence-full-instance-backup[ui]"
# everything:
pip install "jira-confluence-full-instance-backup[all]"
```
Or from source:
```bash
git clone https://github.com/davidmalko87/jira-confluence-full-instance-backup.git
cd jira-confluence-full-instance-backup
pip install -r requirements.txt # + requirements-.txt as needed
```
Requirements: Python 3.10+, and `7z` on PATH (`apt install p7zip-full`, or set `SEVEN_ZIP_PATH`).
---
## Quick Start
### 1. Configure
```bash
cp .env.example .env # fill in real values — .env is gitignored
```
Or use the guided menu (writes `.env` for you): `jira-confluence-backup` → **Configure credentials**.
It explains every field — including how to obtain the Jira cookie blob — and validates what you paste.
> **Hidden input is intentional.** When entering secrets (API token, cookie blob, passwords),
> nothing is echoed to the screen — the prompt shows `[input hidden — paste, then Enter]`. Paste
> and press Enter; the tool confirms back (e.g. *"captured 5 cookie(s); all required present"*) so
> you know it registered, without ever displaying the value. Use **Test connections** to verify.
### 2. Run — interactive menu
```bash
jira-confluence-backup # or: python main.py / python -m backup
```
```
=== Atlassian Full-Instance Backup ===
Jira https://.atlassian.net
Storage s3:my-backups
Notify slack
1) Backup Jira 7) Validate backup
2) Backup Confluence 8) Cleanup backups
3) Backup both 9) Test connections
4) Full run 10) Configure credentials
5) Archive ./out 11) Show configuration
6) Upload ./archive 12) List local backups
13) Export Jenkins setup
14) Refresh Jira cookies
0) Exit
```
### 3. Run — CLI (automation)
```bash
jira-confluence-backup --all # backup both -> archive -> upload -> notify
jira-confluence-backup --all --dry-run # preview only, no API calls / no cooldown burn
jira-confluence-backup --backup jira,confluence --archive --upload --notify
jira-confluence-backup --validate # check the archive against its manifest
jira-confluence-backup --cleanup --keep-days 28 # prune incomplete + old local backups
jira-confluence-backup --test-connection # exit 0 if both auth paths are OK
```
> Output is plain ASCII (`[INFO]/[OK]`) by default — safe on any console, including legacy Windows. Install the `ui` extra for colored output and progress bars.
---
## How It Works
```
Setup -> Jira -> Confluence -> Archive -> Upload -> Notify
(venv) (cookies) (API token) (7z, opt. (:// (your
AES-256) /Y/M/D/) channels)
```
Stages are independent: a Jira cookie expiry does not stop the Confluence stage.
### Auth model
| Product | Endpoint | Auth | Why |
|---|---|---|---|
| Jira | `/rest/backup/1/export/runbackup` | **Session cookies + UI headers** | Atlassian gates this endpoint to UI sessions only — API tokens return 403 |
| Confluence | `/wiki/rest/obm/1.0/runbackup` | **Basic** (email + API token) | OBM never received the UI-only lockdown |
> **Do not replace the Jira side with an API token** — it is gated to browser
> sessions and returns `403 "This feature is only accessible from the UI."`.
> One dedicated Atlassian **admin account** supplies both: its API token (for
> Confluence) and its browser session cookies (for Jira).
---
## Storage backends
Set `STORAGE_PROVIDER` + `STORAGE_DEST` (or `--provider` / `--dest`). Only the chosen SDK is imported; a missing one prints a `pip install` hint.
| Provider | `STORAGE_DEST` | Optional SDK | Credentials |
|---|---|---|---|
| `gcs` | bucket | `requirements-gcs.txt` | `GOOGLE_APPLICATION_CREDENTIALS` (SA JSON) |
| `s3` | bucket | `requirements-s3.txt` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION` |
| `azure` | container | `requirements-azure.txt` | `AZURE_STORAGE_CONNECTION_STRING` |
| `local` | directory | *(none)* | *(none)* |
**S3-compatible stores** (Cloudflare R2, Backblaze B2, MinIO, DigitalOcean Spaces): use `s3` plus `S3_ENDPOINT_URL`. Objects are written to `/YYYY/MM/DD/`. Retention is the bucket's job (set a lifecycle rule); use a **write-only** identity where possible.
---
## Notification channels
`NOTIFY_CHANNELS` is a comma list — pick any. One report is built and rendered per channel; a failed channel is logged but never blocks the rest.
| Channel | Needs | Notes |
|---|---|---|
| `slack` / `discord` / `teams` / `google-chat` | `NOTIFY_WEBHOOK_URL` | platform-specific incoming webhook |
| `email` | `SMTP_*` | stdlib SMTP; port 465 → SSL, else STARTTLS |
| `webhook` | `NOTIFY_WEBHOOK_URL` | raw JSON POST (PagerDuty / Opsgenie / your API) |
---
## Archiving: encryption, compression, names
- **Encryption** (default on): 7-Zip AES-256 with encrypted headers, password from `ARCHIVE_PASSWORD`. Turn it off with `--no-encrypt` or by leaving the password blank.
- **Compression**: `ARCHIVE_COMPRESSION` / `--compression` `0`–`9`.
- **Names**: `PRODUCT_NAME_TEMPLATE` (the per-product `.zip`) and `ARCHIVE_NAME_TEMPLATE` (the `.7z`). Tokens: `{product} {site} {date} {time} {datetime} {timestamp} {year} {month} {day}`. Defaults: `{product}-{date}` and `atlassian-backup-{date}`.
## Integrity & housekeeping
Each successful run writes a `manifest.json` (timestamp, products, per-file + archive **sha256**, `complete: true`, `encrypted`) next to the archive and uploads it too.
- `--validate` — re-checksum the local archive against the manifest.
- `--cleanup [--keep-days N]` — remove incomplete backups (no manifest) and, optionally, ones older than N days.
- `--skip-existing` — skip a product already backed up today.
- `--dry-run` — preview any flow without API calls, archiving, or uploads.
---
## Jenkins
The `Jenkinsfile` is **cross-platform** — it runs on both Linux (`sh`) and Windows (`powershell`) agents. The agent needs **Python 3.10+** and **7-Zip** (`apt install p7zip-full`, or install 7-Zip on Windows). It runs weekly (`cron('H 2 * * 4')`) and installs only the selected provider's SDK.
> 📖 **Full step-by-step guide (prerequisites, plugins, every credential, troubleshooting): [docs/JENKINS_SETUP.md](docs/JENKINS_SETUP.md).** The summary below is the quick version.
### Fast setup — one paste (recommended)
1. Configure locally: `python main.py` → **Configure credentials** (writes `.env`).
2. Generate the setup script: `python main.py` → **Export Jenkins setup** (or `python main.py --export-jenkins`). This writes a gitignored `jenkins-setup.groovy`.
3. In Jenkins: **Manage Jenkins → Script Console**, paste the file's contents, **Run**. It creates every credential (with the exact IDs) **and** the `atlassian-full-backup` pipeline job pointing at this repo.
4. Open the job → **Build Now**, then **delete `jenkins-setup.groovy`** (it contains secrets).
> The job uses *Pipeline script from SCM*, so Jenkins reads the `Jenkinsfile` straight from the repo at build time — there is no Jenkinsfile to generate or maintain inside Jenkins, and updates flow automatically from the repo.
### Manual setup
Edit the `Jenkinsfile` `environment` block for your site / provider / channels:
```groovy
environment {
SITE_JIRA = 'https://.atlassian.net'
SITE_CONFLUENCE = 'https://.atlassian.net/wiki'
STORAGE_PROVIDER = 'gcs' // gcs | s3 | azure | local
STORAGE_DEST = ''
NOTIFY_CHANNELS = 'slack' // any comma list
}
```
Create a **Pipeline** job → *Pipeline script from SCM* → Git → this repo → `*/master` → Script Path `Jenkinsfile`, and add the credentials below (IDs **must match exactly** — the pipeline binds only the ones your config needs):
| Credential ID | Kind | Used when |
|---|---|---|
| `jira-cookies` | Secret text | always (Jira) |
| `atlassian-email`, `atlassian-api-token` | Secret text | always (Confluence) |
| `archive-password` | Secret text | always (may be blank = unencrypted) |
| `gcp-backup-sa-key` | Secret file | `STORAGE_PROVIDER=gcs` |
| `aws-access-key-id`, `aws-secret-access-key` | Secret text | `STORAGE_PROVIDER=s3` |
| `azure-storage-connection-string` | Secret text | `STORAGE_PROVIDER=azure` |
| `notify-webhook-url` | Secret text | chat/webhook channels |
| `smtp-host/from/to/user/password` | Secret text | `email` channel |
No secrets live in the repo — they stay in the Jenkins Credentials store.
---
## Cookie Refresh Procedure
The Jira `tenant.session.token` JWT expires roughly every 30 days. When it does, the Jira stage exits with code 2 (`Cookie auth rejected — cookies likely expired`). Refresh takes ~60 seconds:
1. Log into Atlassian as the backup admin account.
2. Open `https://.atlassian.net/secure/admin/CloudExport.jspa`.
3. **F12 → Network**, reload the page, right-click the `CloudExport.jspa` request (or any `/rest/backup/1/export/...` request) → **Copy → Copy as cURL**.
4. Paste the whole cURL into the menu's **Configure credentials** (it extracts the cookies), or into the `jira-cookies` Jenkins credential / `JIRA_COOKIES` in `.env`.
The cookie blob must contain at least `tenant.session.token` and `atlassian.xsrf.token`. Other cookies such as `JSESSIONID` / `AWSALB` / `AWSALBCORS` are load-balancer/servlet cookies that some instances set and others don't — they're forwarded automatically when present, and not required. Using a real request's "Copy as cURL" (rather than the Application→Cookies list) ensures any that *are* needed come along.
`Test connections` warns you in advance when the token is within a few days of expiry.
---
## Response Code Semantics
| Code | Meaning | Behavior |
|---|---|---|
| 200 | Backup queued / status returned | Continue polling |
| 403 | Auth rejected (UI-only gate) | Exit 2 — refresh Jira cookies |
| 412 | 48-hour cooldown active | Exit 0 + marker — stays green |
| 400 | Body schema rejected | Investigate body (Atlassian schema change) |
| 406 | Confluence cosmetic error | Ignore — backup actually started |
---
## Configuration Reference
All values come from environment variables, optionally loaded from `.env` (see `.env.example`). In Jenkins they are bound from the Credentials store at runtime — never from a file in the repo.
| Env var | Purpose |
|---|---|
| `SITE_JIRA` / `SITE_CONFLUENCE` | Atlassian base URLs |
| `JIRA_COOKIES` | Browser session cookie blob for Jira |
| `ATL_EMAIL` / `ATL_TOKEN` | Confluence Basic auth |
| `ARCHIVE_PASSWORD` | 7-Zip AES-256 passphrase (blank = unencrypted) |
| `ARCHIVE_COMPRESSION` | 0–9 |
| `PRODUCT_NAME_TEMPLATE` / `ARCHIVE_NAME_TEMPLATE` | Filename templates |
| `STORAGE_PROVIDER` / `STORAGE_DEST` | Backend + bucket/container/dir (aligned **comma lists** for multiple targets, e.g. `gcs,s3` + `bucketA,bucketB`) |
| `BACKUP_CRON` | Jenkins schedule (default `H 2 * * 4`) |
| `PYTHON_BIN` | Jenkins agent Python path (blank = auto-detect; set if `python` isn't on the service PATH) |
| `S3_ENDPOINT_URL` | S3-compatible endpoint (s3 only) |
| `GOOGLE_APPLICATION_CREDENTIALS` / `AWS_*` / `AZURE_STORAGE_CONNECTION_STRING` | Provider credentials |
| `NOTIFY_CHANNELS` | Comma list of channels |
| `NOTIFY_WEBHOOK_URL` / `SMTP_*` | Notification delivery |
---
## Project Structure
```
jira-confluence-full-instance-backup/
├── main.py # Convenience shim (python main.py)
├── Jenkinsfile # Declarative pipeline (provider/channel driven)
├── pyproject.toml # Packaging + console script + ruff config
├── .env.example # Local-testing template (real .env is gitignored)
├── requirements.txt # Core (requests)
├── requirements-{gcs,s3,azure,ui}.txt # Optional extras
├── docs/JENKINS_SETUP.md # Full Jenkins setup guide
└── backup/
├── cli.py # Dual-mode entrypoint (menu + CLI)
├── jira.py # Cookie-authenticated Jira backup
├── confluence.py # OBM Basic-auth Confluence backup
├── archive.py # 7-Zip (optional AES-256, configurable level)
├── upload.py # Multi-provider upload (gcs/s3/azure/local)
├── notify.py # Multi-channel notifier
├── manifest.py # manifest.json: completeness + sha256 integrity
├── config.py # Env/.env config + Configure-menu persistence
├── jenkins_export.py # Generate Script Console Groovy (creds + job)
├── naming.py # Filename templating
└── ui.py # Console UI (rich-optional, ASCII-safe)
```
---
## Known Limitations
These are Atlassian platform constraints, not tool limitations:
- **48h Jira cooldown** between full-instance backups (weekly cadence is fine).
- **Cookie lifetime** ~30 days; monthly manual refresh required.
- **Confluence Filestore retention** ~14 days (the tool downloads immediately, so this affects only the source file).
- **No restore automation** — restoring a full-instance backup is manual via Atlassian's UI; for per-project restore use [`jira-project-backup-restore`](https://github.com/davidmalko87/jira-project-backup-restore).
---
## Troubleshooting
Run **Test connections** (menu option 9) first — it pinpoints which of Jira /
Confluence / storage is misconfigured. Common errors and fixes (403 vs the 204
"success", cookie refresh, the 48-hour cooldown, storage permissions, Jenkins)
are in **[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)**.
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). In short: no secrets in the repo, keep
cloud SDKs optional, ASCII-only console output, and test auth/backup changes
against a non-prod Atlassian instance. **Do not** switch Jira to API-token auth
(see [Auth model](#auth-model)).
## License
MIT — see [LICENSE](LICENSE).
## Related
* [`jira-project-backup-restore`](https://github.com/davidmalko87/jira-project-backup-restore) — per-project Jira Cloud backup/restore via REST API.