5 Commits

Author SHA1 Message Date
9b535f621b also update visibility when syncing 2023-04-05 20:48:25 +02:00
494be7abe2 doc for token scope 2023-04-05 20:47:54 +02:00
cf19e698b3 stable version 2023-03-09 02:34:01 +01:00
a58e59c43b readme 2023-03-09 02:30:33 +01:00
9abca53ab3 workflow 2023-03-09 02:24:53 +01:00
9 changed files with 74 additions and 15 deletions

View File

@@ -1,5 +1,4 @@
GITHUB_TOKEN= GITHUB_TOKEN=
GITHUB_SCOPE=
GITEA_HOST= GITEA_HOST=
GITEA_TOKEN= GITEA_TOKEN=

View File

@@ -20,7 +20,8 @@ jobs:
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: | images: |
ghcr.io/${{ github.repository }} cupcakearmy/gitea-sync
# ghcr.io/${{ github.repository }}
# This assumes your repository is also github.com/foo/bar # This assumes your repository is also github.com/foo/bar
# You could also use ghcr.io/foo/some-package as long as you are the user/org "foo" # You could also use ghcr.io/foo/some-package as long as you are the user/org "foo"
tags: | tags: |
@@ -28,12 +29,17 @@ jobs:
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
- name: Log in to the Container registry - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io username: ${{ secrets.DOCKER_USERNAME }}
username: ${{ github.actor }} password: ${{ secrets.DOCKER_TOKEN }}
password: ${{ secrets.GITHUB_TOKEN }} # - name: Log in to the Container registry
# uses: docker/login-action@v2
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3

View File

@@ -1,4 +1,39 @@
# Backup Github repos to Gitea # Backup GitHub repos to Gitea
Simple docker image that syncs your GitHub repos to a given Gitea server. It takes all the repos (public and private) and sets up a mirror. Private repos will stay a private mirror. Repos that are already set up will be skipped.
## Quick Start
Create a `docker-compose.yaml` and `.env` (see `.env.sample`). Create and insert tokens, and you are ready to go.
```yaml
version: '3.8'
services:
sync:
image: cupcakearmy/gitea-sync:1
restart: unless-stopped
env_file: .env
```
## Configuration
```sh
# Github PAT (deprecated)
# or
# Fine Grained token (Metadata and Content read-only scopes required)
GITHUB_TOKEN=
# Host of the Gitea server
GITEA_HOST=
# Gitea token (scopes: repo)
GITEA_TOKEN=
# OPTIONAL
# Cron schedule
CRON="0 */2 * * *"
```
## Known limitations ## Known limitations

View File

@@ -1,7 +1,11 @@
# FOR DEVELOPMENT ONLY
# See README.md for an example
version: "3.8" version: "3.8"
services: services:
job: job:
image: cupcakearmy/gitea-sync
build: . build: .
env_file: .env env_file: .env

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.0.0-rc.0", "version": "1.1.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node .", "start": "node .",

View File

@@ -3,6 +3,7 @@ import axios, { AxiosError } from 'axios'
import { Config } from '../config.js' import { Config } from '../config.js'
import { logger } from '../logger.js' import { logger } from '../logger.js'
import { ListRepositoriesResponse } from './gitea.types.js' import { ListRepositoriesResponse } from './gitea.types.js'
import { Repository } from './github.types.js'
const Base = axios.create({ const Base = axios.create({
baseURL: new URL('/api/v1', Config.gitea.host).href, baseURL: new URL('/api/v1', Config.gitea.host).href,
@@ -77,3 +78,12 @@ export async function listAllRepositories() {
logger.debug('Listed all repositories in Gitea', { data: repos }) logger.debug('Listed all repositories in Gitea', { data: repos })
return repos return repos
} }
export async function updateRepository(owner: string, repo: string, body: Partial<Repository>) {
logger.debug('Updating repository', { owner, repo, body })
await Base({
url: `/repos/${owner}/${repo}`,
method: 'PATCH',
data: body,
})
}

View File

@@ -38,12 +38,12 @@ export const Config = {
level: getEnv('LOG_LEVEL', 'info'), level: getEnv('LOG_LEVEL', 'info'),
}, },
github: { github: {
scope: simple('GITHUB_SCOPE'),
token: simple('GITHUB_TOKEN'), token: simple('GITHUB_TOKEN'),
}, },
gitea: { gitea: {
host: simple('GITEA_HOST'), host: simple('GITEA_HOST'),
token: simple('GITEA_TOKEN'), token: simple('GITEA_TOKEN'),
}, },
cron: getEnv('CRON', '0 */2 * * *'),
version: getEnv('npm_package_version', 'unknown'), version: getEnv('npm_package_version', 'unknown'),
} }

View File

@@ -1,4 +1,4 @@
import { listAllRepositories as giteaRepos, mirror, MirrorOptions } from './api/gitea.js' import { listAllRepositories as giteaRepos, mirror, MirrorOptions, updateRepository } from './api/gitea.js'
import { listAllRepositories as githubRepos } from './api/github.js' import { listAllRepositories as githubRepos } from './api/github.js'
import { Config } from './config.js' import { Config } from './config.js'
import { logger } from './logger.js' import { logger } from './logger.js'
@@ -20,7 +20,12 @@ export async function sync() {
const sameName = syncedRepos.find((r) => r.name === repo.name || r.original_url === repo.clone_url) const sameName = syncedRepos.find((r) => r.name === repo.name || r.original_url === repo.clone_url)
if (sameName) { if (sameName) {
if (sameName.original_url === repo.clone_url) { if (sameName.original_url === repo.clone_url) {
logger.info('Already synced, skipping', { name: repo.name }) if (sameName.private === repo.private) logger.info('Already synced, skipping', { name: repo.name })
else {
logger.info('Visibility changed, updating', { name: repo.name })
const [owner, repository] = sameName.full_name.split('/')
await updateRepository(owner, repository, { private: repo.private })
}
} else { } else {
logger.error('Repo with same name but different url', { logger.error('Repo with same name but different url', {
name: repo.name, name: repo.name,
@@ -42,9 +47,9 @@ export async function sync() {
logger.info('Mirrored repository', { name: repo.name }) logger.info('Mirrored repository', { name: repo.name })
} }
logger.info('Finished sync') logger.info('Finished sync')
} catch (e) { } catch (error) {
logger.error(e) logger.debug(error)
logger.error('Failed to sync') logger.error('Failed to sync', { error: error instanceof Error ? error.message : 'Unknown error' })
} finally { } finally {
running = false running = false
} }

View File

@@ -7,4 +7,4 @@ import { logger } from './logger.js'
logger.info(`Mirror manager - ${Config.version}`, { version: Config.version }) logger.info(`Mirror manager - ${Config.version}`, { version: Config.version })
await sync() await sync()
cron.schedule('0/5 * * * *', sync) cron.schedule(Config.cron, sync)