DVCS mirror setup¶
This is the owner's walk-through for standing up a DVCS deployment against a Confluence space. You run it once per space. After it ships, anyone on your team can git clone the mirror with vanilla git (the mirror-only consumer pattern), and writers can reposix attach to round-trip back to the SoT (the round-tripper pattern).
The mechanism: a GitHub Action workflow living in the mirror repository runs on repository_dispatch (fired by a Confluence webhook) plus a 30-minute cron safety net. Each run does reposix init confluence::SPACE, then git push --force-with-lease to the mirror.
What you need before you start¶
- An Atlassian Cloud account that owns (or can edit) the Confluence space you want to mirror.
- An Atlassian API token. Generate at https://id.atlassian.com/manage-profile/security/api-tokens.
- A GitHub account with permission to create a repository under your org or username.
- The
ghCLI installed and authenticated (gh auth login). - About 15 minutes for the first walk-through; 5 minutes per subsequent space.
If you also want the agent-flow real-backend tests against the mirrored space, set REPOSIX_ALLOWED_ORIGINS and the credentials in your shell after this setup completes.
Workflow template location: the GitHub Action workflow this guide installs lives at
docs/guides/dvcs-mirror-setup-template.ymlin the reposix repo. Step 2 below curls it frommain.
Step 1 — Create the mirror repository¶
The mirror repo holds the rendered Markdown of the SoT plus the refs/mirrors/... annotation refs. Make it public so vanilla-git mirror-only consumers can clone without auth, or private if your tracker is internal.
Pick the name carefully — by convention it ends in -mirror. Examples: acme/engineering-mirror, reubenjohn/reposix-tokenworld-mirror.
Tip: start with one space. Adding a second space is a second walk-through; do not try to mirror multiple spaces into a single GH repository.
Step 2 — Drop the workflow into the mirror repo¶
The workflow template lives at docs/guides/dvcs-mirror-setup-template.yml in the reposix repo. Copy it into your new mirror repo at .github/workflows/reposix-mirror-sync.yml.
git clone git@github.com:<org>/<space>-mirror.git /tmp/<space>-mirror
cd /tmp/<space>-mirror
mkdir -p .github/workflows
curl -sSfL https://raw.githubusercontent.com/reubenjohn/reposix/main/docs/guides/dvcs-mirror-setup-template.yml \
-o .github/workflows/reposix-mirror-sync.yml
git add .github/workflows/reposix-mirror-sync.yml
git commit -m 'ci: add reposix-mirror-sync workflow'
git push origin main
Open the file. The two pieces you might want to edit:
- Cron cadence — line 22 is the literal
'*/30 * * * *'schedule. GitHub Actions evaluatesschedule:before any${{ vars.* }}substitution, so the cron interval cannot be templated. To change the cadence, edit this line directly. Every 30 minutes is conservative for low-edit Confluence spaces; aggressive for high-churn projects. Tune to taste. - Space name —
${{ vars.CONFLUENCE_SPACE || 'TokenWorld' }}lets you set the space via repo variables (Step 3 below) or fall back to a default. If you only ever mirror one space, hard-code it for clarity.
Step 3 — Configure secrets and variables on the mirror repo¶
The workflow reads three secrets and one variable. Set them with gh:
cd /tmp/<space>-mirror
# Secrets — encrypted, only visible to the workflow.
gh secret set ATLASSIAN_API_KEY # paste the API token from prerequisites
gh secret set ATLASSIAN_EMAIL # your Atlassian account email
gh secret set REPOSIX_CONFLUENCE_TENANT # e.g. 'acme' for acme.atlassian.net
# Variables — readable, can be templated into env: blocks.
gh variable set CONFLUENCE_SPACE --body '<SPACE-KEY>'
Note:
gh secret set <NAME>(without--body) prompts interactively for the secret value. For non-TTY contexts (CI, automation), pipe the value via stdin or pass--body:printf '%s' "$TOKEN" | gh secret set ATLASSIAN_API_KEYorgh secret set ATLASSIAN_API_KEY --body "$TOKEN".
Verify:
The workflow's env: block builds REPOSIX_ALLOWED_ORIGINS automatically from the tenant secret — you do not set it directly. The egress allowlist is fail-closed by default; the workflow opens exactly one origin (https://${REPOSIX_CONFLUENCE_TENANT}.atlassian.net) and keeps loopback open for any sidecar testing.
Step 4 — Smoke-test with a manual run¶
Before you wire up the webhook, confirm the workflow works by triggering it manually:
Expected outcome:
- The job installs
reposix-cliviacargo binstall(~10 seconds; faster thancargo install). - It runs
reposix init confluence::<SPACE> /tmp/sotagainst your space. - It pushes the rendered Markdown to
refs/heads/mainof the mirror repo with--force-with-lease. - It pushes
refs/mirrors/<sot-host>-headandrefs/mirrors/<sot-host>-synced-at. - The job exits 0.
If the run fails, jump to Troubleshooting → DVCS push/pull issues for the common causes (egress allowlist, wrong tenant slug, missing API token scope).
After the first successful run, git fetch origin && git log --oneline -5 from a fresh clone will show the rendered issues; git for-each-ref refs/mirrors/ will show the two annotation refs.
Step 5 — Configure the Confluence webhook¶
Webhooks fire repository_dispatch on the mirror repo, which the workflow's on: block listens for. With this in place, an edit in Confluence triggers the mirror sync within ~30 seconds (rather than waiting up to 30 minutes for the cron).
- Mint a fine-grained GitHub PAT with
reposcope on the mirror repo. (Atlassian's webhook needs to callPOST https://api.github.com/repos/<org>/<space>-mirror/dispatches.) - In Atlassian admin, navigate to Settings → System → Webhooks → Create webhook.
- Configure:
- URL:
https://api.github.com/repos/<org>/<space>-mirror/dispatches - Method:
POST - Headers:
Authorization: token <your-PAT>Accept: application/vnd.github+json- Body (JSON):
- Events: subscribe to
Page created,Page updated,Page removedfor your space.
- URL:
- Save.
Atlassian's webhook docs: https://confluence.atlassian.com/doc/manage-webhooks-1021225606.html.
Edit a page in Confluence; within ~30 seconds, gh run list -R <org>/<space>-mirror -L 1 should show a fresh run kicked off by repository_dispatch.
Backends without webhooks (cron-only mode)¶
Some backends do not emit webhooks (or emit them on plans you do not have). For those, the cron schedule is the only sync mechanism. The workflow already supports this — just remove the repository_dispatch: trigger from on::
# .github/workflows/reposix-mirror-sync.yml — cron-only variant.
on:
schedule:
- cron: '*/30 * * * *'
workflow_dispatch:
Trade-off:
- Latency floor is the cron interval (every 30 minutes by default). A Confluence edit at 14:31:00 lands in the mirror at 15:00 cron tick — up to 29 minutes of staleness in the worst case.
refs/mirrors/<sot-host>-synced-atstill tells the truth — it advances each cron tick when there are SoT-side changes to apply.
For Confluence specifically, webhooks are available on Standard / Premium / Enterprise plans; if your space is on Free, cron-only is your only option.
Updating the cron cadence¶
To change */30 to a different frequency:
cd /tmp/<space>-mirror
sed -i "s|'\*/30 \* \* \* \*'|'\*/15 \* \* \* \*'|" .github/workflows/reposix-mirror-sync.yml
git add .github/workflows/reposix-mirror-sync.yml
git commit -m 'ci(mirror-sync): tighten cron to every 15 minutes'
git push origin main
GitHub Actions has a documented quirk: scheduled workflows in low-traffic repos may skip ticks. If precise interval matters, configure the webhook (Step 5) and treat the cron as a safety net.
Cleanup procedure (tear-down)¶
When you no longer need the mirror — the space was archived, the team moved off the tracker, or you are migrating to a different SoT — tear it down cleanly so you do not leave orphan refs or zombie cron jobs.
# 1. Disable the workflow so no further runs fire.
gh workflow disable reposix-mirror-sync.yml -R <org>/<space>-mirror
# 2. Remove the Confluence webhook.
# Atlassian admin → Settings → System → Webhooks → delete the entry from Step 5.
# 3. Delete the mirror repo (DESTRUCTIVE — confirm you want this).
gh repo delete <org>/<space>-mirror --yes
# 4. (Optional) Revoke the PAT used by the webhook at
# https://github.com/settings/tokens.
# 5. (Optional) Locally, delete the cache directory for the SoT.
rm -rf ~/.cache/reposix/confluence-<SPACE>.git
If you only want to pause rather than tear down, do steps 1 and 2 (disable workflow + remove webhook). The mirror repo and its refs stay intact; re-enabling later just runs gh workflow enable and re-creates the webhook.
Troubleshooting¶
If a run fails, the most common causes are:
| Symptom | Fix |
|---|---|
Error::InvalidOrigin in the run log |
REPOSIX_ALLOWED_ORIGINS does not include the tenant; check REPOSIX_CONFLUENCE_TENANT secret. |
couldn't find remote ref main on first run |
Truly-empty mirror — workflow handles it; subsequent run should pass. |
--force-with-lease rejected |
Race with a concurrent bus push; cron will retry the next tick. |
cargo binstall cannot find a release |
Pinned to reposix-cli; check the published version on crates.io. |
| Webhook fires but no run starts | PAT lacks repo scope on the mirror; rotate at GitHub settings. |
The full troubleshooting matrix — including bus-remote fetch first rejections, attach reconciliation warnings, and cache-desync recovery — lives at Troubleshooting → DVCS push/pull issues.
See also¶
- DVCS topology — three roles — the mental model this guide implements.
dvcs-mirror-setup-template.yml— the workflow template you copied in Step 2.- Testing targets — TokenWorld is the reference Confluence space; the same setup applies to your space.
- Troubleshooting — diagnosis catalog when something goes wrong.