mirror your GitHub repos to tangled.org automatically
1# synchub.to
2
3> mirror your GitHub repos to [tangled.org](https://tangled.org) automatically.
4
5- [👉 Check it out](https://synchub.to/)
6
7Install the GitHub App, connect your tangled identity, and every commit,
8branch, and tag will be mirrored to tangled.
9
10## Features
11
12- no workflow file required
13- one-time OAuth connection to tangled
14- per-push sync of branches and tags
15- a dashboard where you can resync, pause, or rotate keys
16
17> [!IMPORTANT]
18> Only public repositories are synced (tangled does not yet support private repositories).
19
20## Run it locally
21
22You will need [Node 24+](https://nodejs.org), [pnpm 10+](https://pnpm.io)
23(`corepack enable`), a [Neon](https://neon.tech) database (free tier is fine),
24and the [Smee CLI](https://smee.io) for webhook proxying
25(`pnpm add -g smee-client`).
26
27```bash
28corepack enable
29pnpm install
30cp .env.example .env # fill in the values, see below
31pnpm db:migrate
32pnpm dev
33```
34
35`.env.example` documents every variable. Generate the secrets with the bundled
36helpers:
37
38```bash
39pnpm gen:jwk # NUXT_ATPROTO_PRIVATE_JWK
40pnpm gen:encryption-key # NUXT_ENCRYPTION_KEY and NUXT_SESSION_PASSWORD
41pnpm gen:cron-secret # CRON_SECRET
42```
43
44The rest (`NUXT_DATABASE_URL`, the `NUXT_GITHUB_APP_*` values) come from your
45Neon dashboard and a [new GitHub App](https://github.com/settings/apps/new).
46The App needs `contents:read` and `metadata:read` permissions plus the `push`,
47`create`, `delete`, and `repository` events, with its webhook pointed at your
48Smee URL. Set the **Setup URL** to `<origin>/connect` (tick *Redirect on
49update*) and the **Callback URL** to `<origin>/api/github/oauth/callback`; the
50latter drives the user-OAuth that verifies a connecting user administers the
51installation before any handle is bound. Copy the App's **Client ID** and a
52generated **client secret** into `NUXT_GITHUB_APP_CLIENT_ID` /
53`NUXT_GITHUB_APP_CLIENT_SECRET`.
54
55In separate terminals, proxy webhooks and drain the job queue:
56
57```bash
58smee --url <your-smee-url> --target http://127.0.0.1:3000/api/github/webhook
59pnpm jobs:tick # run as needed; in production Vercel Cron does this
60```
61
62## Deploy to Vercel
63
64synchub.to runs on Vercel with a Neon Postgres database.
65
661. Apply migrations against your production database:
67 ```bash
68 NUXT_DATABASE_URL="<pooled neon connection string>" pnpm db:migrate
69 ```
702. Import the repo into Vercel (the Nuxt preset is auto-detected) and set every
71 variable from `.env.example` under **Settings > Environment Variables**.
72 Mark the secrets (`NUXT_DATABASE_URL`, `NUXT_GITHUB_APP_PRIVATE_KEY`,
73 `NUXT_GITHUB_APP_CLIENT_SECRET`, `NUXT_ATPROTO_PRIVATE_JWK`,
74 `NUXT_ENCRYPTION_KEY`, `NUXT_SESSION_PASSWORD`,
75 `NUXT_GITHUB_WEBHOOK_SECRET`, `CRON_SECRET`) as **Sensitive**.
763. Set `NUXT_PUBLIC_URL` to your real origin, point the GitHub App webhook at
77 `https://<your-domain>/api/github/webhook`, and set the App's Setup +
78 Callback URLs to `https://<your-domain>/connect` and
79 `https://<your-domain>/api/github/oauth/callback`.
804. Deploy.
81
82The worker runs on a Vercel Cron (declared in `nuxt.config.ts`, so no
83`vercel.json` is needed) and appears under **Settings > Cron Jobs** after the
84first deploy.
85
86> [!NOTE]
87> The GitHub App private key is multi-line, but Vercel env values are single
88> line. Collapse the newlines to literal `\n` before pasting:
89>
90> ```bash
91> awk 'NF {printf "%s\\n", $0}' your-app.private-key.pem
92> ```
93>
94> Locally, keep the real newlines as shown in `.env.example`. Migrations are
95> manual: re-run `pnpm db:migrate` against production whenever you ship a
96> schema change.
97
98## License
99
100Made with ❤️
101
102Published under [MIT License](./LICENCE).