multipleof4andClaude Opus 5.5 474b475f18 Sync descriptions and issues, run workflows nightly
mirror.js now keeps repo descriptions in step with GitHub. A new
sync-issues workflow copies issues, PR conversations and comments onto
the mirrors with GitHub's numbering. Shared API helpers move to
gitea-mirrors.js. Every workflow now runs once a night, 1.5 hours apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:37:50 +00:00

gitea-github-cron

GitHub Actions that back up every GitHub repo I own (personal and org) to a self-hosted Gitea as pull mirrors.

Gitea version: 1.27.*

Workflows

Workflow Schedule (UTC) Script Does
Mirror to Gitea 05:00 (12 AM CDT) mirror.js Creates missing orgs and pull mirrors, syncs repo/org visibility, descriptions and org avatars
Sync releases to Gitea 06:30 (1:30 AM CDT) sync-releases.js Copies GitHub releases (title, notes, assets) onto the mirrors
Rename deleted GitHub mirrors 08:00 (3 AM CDT) rename-gone-mirrors.js Renames mirrors whose GitHub repo was deleted or moved to <name>-goneN and freezes them
Sync issues to Gitea 09:30 (4:30 AM CDT) sync-issues.js Copies GitHub issues, PR conversations and their comments onto the mirrors

Each runs once a day, 1.5 hours apart, so each one starts with a fresh hourly GitHub API budget. All can also be run by hand from the Actions tab. Each run posts a summary to ntfy through notify.js. gitea-mirrors.js holds the API helpers the two sync scripts share.

Releases on mirrors

Gitea's migrate API drops releases: true for pull mirrors, so a mirror only gets bare tag entries. sync-releases.js fills in the real releases:

  • Matches releases by tag, creates missing ones, updates changed title/notes/prerelease, uploads missing assets by name.
  • Skips GitHub drafts.
  • Skips a release until its tag has reached Gitea through a mirror sync. Otherwise Gitea would create the tag on the default branch.
  • Streams each asset from GitHub straight into Gitea. Uploads are capped by [repository.release] FILE_MAX_SIZE (default 2048 MB, which matches GitHub's 2 GiB per-file limit) and need [attachment] ENABLED.
  • Skips frozen mirrors (sync interval 0).
  • Never deletes anything on Gitea.

This relies on Gitea ≥ 1.21.5, where a mirror sync only touches tag-only entries and keeps real releases (go-gitea/gitea#28817).

Issues on mirrors

Gitea drops issues for pull mirrors too, but the API still lets you create them. sync-issues.js copies them over through the API:

  • Gitea issue numbers always match GitHub's, so #N in commits and comments points at the right thing:
    • PRs become issues labelled pull request, closed or open like on GitHub. Only their conversation comes along, not code review comments or diffs.
    • Numbers that aren't issues on GitHub (deleted, transferred, or used by a discussion) stay gaps on Gitea too. Gitea never reuses a deleted issue's number, so an issue that lands on a gap is deleted and made again until it gets GitHub's number.
    • If Gitea's counter is already past a GitHub number, or #N on Gitea wasn't made by the sync, that repo stops with a failure instead of guessing.
  • Everything is posted as the Gitea token user. Each issue and comment opens with a quoted line naming the GitHub author, the date, and a link back. The link is how comments are matched on later runs, so don't edit it away.
  • Title, body, open/closed state, labels and comment edits follow GitHub. Nothing is deleted on Gitea.
  • Images uploaded to GitHub stay as links to GitHub.

Secrets

Secret Used for
GH_PAT Classic GitHub token with repo scope
GITEA_TOKEN Gitea token for the account that owns the mirrors
GITEA_URL Gitea host, with or without https://
NTFY_TOPIC_URL ntfy topic that gets run summaries
S
Description
No description provided
Readme
174 KiB
Languages
JavaScript 100%