Repository navigation
Architecture
hub2lab is an Express HTTP server that receives webhooks from GitHub and GitLab and replays events on the opposite side.
┌──────────┐ Webhook ┌──────────┐ API Calls ┌──────────┐
│ GitHub │ ──────────────→ │ hub2lab │ ────────────────→ │ GitLab │
│ (Sender) │ │ Server │ │ (Mirror) │
└──────────┘ └──────────┘ └──────────┘
↑ │ ↑
│ │ │
│ API Calls │
│ ↓ │
│ ┌──────────┐ │
└─────────────────────│ GitLab │─────────────────────────┘
│ (Sender) │
└──────────┘
src/
├── index.ts # Express server entry point
├── types.ts # Shared type definitions
├── config/
│ ├── index.ts # Config loader (env vars + mapping file)
│ ├── logger.ts # Winston logger setup
│ └── mapper.ts # Org/group ↔ repo resolution
├── github/
│ ├── client.ts # GitHub API helpers via Octokit
│ └── webhook.ts # GitHub signature verification & event parsing
├── gitlab/
│ ├── client.ts # GitLab API helpers via @gitbeaker/rest
│ └── webhook.ts # GitLab token verification & event parsing
├── handlers/
│ ├── github.ts # Webhook handlers for GitHub events
│ └── gitlab.ts # Webhook handlers for GitLab events
└── sync/
├── engine.ts # Event routing dispatcher
├── mirror.ts # Git mirror-based push via simple-git
└── deletion.ts # Deletion marker file builders
The Express server listens on the configured port (default: 3000). Three routes:
| Route | Method | Purpose |
|---|---|---|
/webhook/github |
POST | Receives GitHub webhook payloads |
/webhook/gitlab |
POST | Receives GitLab webhook payloads |
/health |
GET | Returns {"status":"ok"}
|
The server captures req.rawBody for signature verification before JSON parsing.
-
config/index.ts— loads.envvia dotenv, validates required vars, parsesmapping.json -
config/logger.ts— creates a Winston logger with console + file transports (10 MB rotation, 5 files) -
config/mapper.ts— resolves GitHub org/repo to GitLab group/project and vice versa
GitHub (src/github/client.ts) uses Octokit to:
- Create/rename/delete repositories
- Create/delete branches and tags
- Get default branch SHA
- Push files via the Git Data API (blobs → tree → commit → update ref)
GitLab (src/gitlab/client.ts) uses @gitbeaker/rest to:
- Search/find groups and projects
- Create/delete projects
- Create/delete branches and tags
- Get default branch SHA
- Push files via the Commits API (with fallback from create to update)
GitHub: HMAC-SHA256 signature verification using X-Hub-Signature-256 header. Uses crypto.timingSafeEqual to prevent timing attacks.
GitLab: Shared secret token verification using X-GitLab-Token header.
Both handlers/github.ts and handlers/gitlab.ts check incoming payloads for the SYNC_MARKER string in commit messages. If found, the event is silently dropped.
The marker is appended to every commit message hub2lab creates (e.g., [hub2lab] Record deletion of branch feature-x from GitHub).
For push events, hub2lab uses simple-git to maintain a local bare-mirror clone of the source repository and force-push the ref to the target:
- Check if a mirror already exists at
{MIRROR_DIR}/{remoteHash} - If yes, fetch origin; if no, clone
--mirrorfrom source - Force-push the specific ref to the target (authenticated with x-access-token)
hub2lab never automatically deletes branches, tags, or repositories on the target side. Instead, it writes a marker file containing the deletion metadata. This prevents accidental data loss.
GitHub webhook (push) → verify signature → parse event
→ check sync marker (drop if found)
→ resolve org/repo to GitLab group/project
→ find or create GitLab project
→ mirror-push (clone → fetch → force-push)
→ log
GitLab webhook (push) → verify token → parse event
→ check sync marker (drop if found)
→ resolve group/project to GitHub org/repo
→ mirror-push (clone → fetch → force-push)
→ log
GitHub create event → resolve mapping → find GitLab project
→ get default branch SHA → create branch/tag on GitLab via API
GitHub delete event → resolve mapping → find GitLab project
→ build deletion marker .md file
→ push deletion file to GitLab via API
GitHub repository event (created) → resolve mapping
→ find or create GitLab project (mirrors visibility)
| Concern | Mitigation |
|---|---|
| Infinite sync loops | Sync marker string in commit messages |
| Accidental deletion | Marker files instead of automatic deletion |
| Unauthorized webhooks | HMAC-SHA256 (GitHub) / shared secret (GitLab) |
| Missing target repos | Auto-create on GitLab if not found |
| Stale mirror directories |
initMirrorCleanup() removes stale mirrors on startup |