Skip to content

Architecture

wildfirebill edited this page Jul 20, 2026 · 1 revision

Architecture

hub2lab is an Express HTTP server that receives webhooks from GitHub and GitLab and replays events on the opposite side.

High-Level Flow

┌──────────┐     Webhook      ┌──────────┐     API Calls     ┌──────────┐
│  GitHub   │ ──────────────→ │  hub2lab  │ ────────────────→ │  GitLab   │
│ (Sender)  │                 │  Server   │                   │ (Mirror)  │
└──────────┘                 └──────────┘                   └──────────┘
       ↑                          │                               ↑
       │                          │                               │
       │                     API Calls                            │
       │                          ↓                               │
       │                     ┌──────────┐                         │
       └─────────────────────│  GitLab   │─────────────────────────┘
                             │ (Sender)  │
                             └──────────┘

Project Structure

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

Layer Breakdown

1. HTTP Server (src/index.ts)

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.

2. Configuration (src/config/)

  • config/index.ts — loads .env via dotenv, validates required vars, parses mapping.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

3. API Clients

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)

4. Webhook Verification

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.

5. Loop Prevention

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).

6. Mirror Push (src/sync/mirror.ts)

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:

  1. Check if a mirror already exists at {MIRROR_DIR}/{remoteHash}
  2. If yes, fetch origin; if no, clone --mirror from source
  3. Force-push the specific ref to the target (authenticated with x-access-token)

7. Deletion Safety (src/sync/deletion.ts)

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.

Event Flow Maps

GitHub Push → GitLab

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 Push → GitHub

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

Branch/Tag Create

GitHub create event → resolve mapping → find GitLab project
  → get default branch SHA → create branch/tag on GitLab via API

Branch/Tag Delete

GitHub delete event → resolve mapping → find GitLab project
  → build deletion marker .md file
  → push deletion file to GitLab via API

Repo Created on GitHub

GitHub repository event (created) → resolve mapping
  → find or create GitLab project (mirrors visibility)

Safety Design

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