Skip to content

F1.3 — Git Repository Integration

What This Feature Does

Allows users to connect a GitHub, GitLab, or Bitbucket repository to a Vexlyx project. It clones the repo into an isolated workspace directory, generates Ed25519 SSH keys for private repos, and provides a webhook URL (secret pre-generated) for future auto-deploy integration (F2.7).


Architecture

Dashboard (GitSettings.tsx)

    │  POST /api/projects/:id/git/connect
    │  POST /api/projects/:id/git/ssh-key
    │  GET  /api/projects/:id/git

API (apps/api/src/modules/git/)
    ├── routes.ts   — Fastify route handlers (auth-gated)
    ├── service.ts  — Business logic, spawns Python
    └── schema.ts   — Re-exports ConnectRepoSchema from @vexlyx/shared

         │  stdin: JSON payload
         │  stdout: JSON result

    system/python/git_manager.py

         ├── cmd_clone()          — git clone / git pull
         └── cmd_generate_ssh_key() — ssh-keygen Ed25519

Data Flow

  1. User enters git URL + branch in GitSettings.tsx
  2. Dashboard calls POST /api/projects/:id/git/connect (JSON body)
  3. API gitRoutes validates with ConnectRepoSchema → calls GitService.connectRepo()
  4. GitService spawns git_manager.py clone with a JSON payload on stdin
  5. Python script runs git clone and writes {"success": true, "path": "..."} to stdout
  6. Service persists gitUrl, branch, webhookSecret to Project record in PostgreSQL
  7. Dashboard shows success toast + renders SSH key and webhook URL sections

Key Files

FileRole
apps/api/src/modules/git/routes.ts3 authenticated Fastify routes
apps/api/src/modules/git/service.tsGitService class, runGitManager() helper
apps/api/src/modules/git/schema.tsRe-exports ConnectRepoSchema
system/python/git_manager.pyPython subprocess: clone, ssh-keygen
packages/shared/src/schemas/projects.tsConnectRepoSchema, GitMetadata type
apps/dashboard/src/hooks/useGitSettings.tsReact hook: fetch + mutate git state
apps/dashboard/src/components/projects/GitSettings.tsxUI panel (repo form, SSH key, webhook URL)

Database Changes

Three new optional columns on the Project model:

ColumnTypePurpose
webhook_secretString?32-byte random hex HMAC secret (used by F2.7)
ssh_public_keyString?Ed25519 public key shown to user
ssh_private_key_pathString?Absolute disk path to private key

Migration: 20260829183245_add_git_fields_to_project


Environment Variables

VariableDefault (dev)Description
PROJECTS_DIR./workspaces/projectsRoot for cloned repos
SSH_KEYS_DIR./workspaces/keysRoot for SSH key pairs
API_BASE_URLhttp://localhost:5000Prefix for webhook URL

API Reference

GET /api/projects/:id/git

Returns git metadata for a project.

Response:

json
{
  "gitUrl": "https://github.com/user/repo",
  "branch": "main",
  "sshPublicKey": "ssh-ed25519 AAAA...",
  "webhookUrl": "http://localhost:5000/api/webhooks/github?projectId=xxx",
  "isPrivate": true
}

POST /api/projects/:id/git/connect

Clones (or re-pulls) the repository.

Body:

json
{ "gitUrl": "https://github.com/user/repo", "branch": "main", "isPrivate": false }

Response: Same shape as GET.

POST /api/projects/:id/git/ssh-key

Generates a new Ed25519 key pair. Overwrites any existing key for this project.

Response:

json
{ "publicKey": "ssh-ed25519 AAAA..." }

How to Test

1. Public repo clone

bash
# Start the API
pnpm dev

# Connect a public repo to an existing project
curl -X POST http://localhost:5000/api/projects/<PROJECT_ID>/git/connect \
  -H "Content-Type: application/json" \
  -d '{"gitUrl":"https://github.com/torvalds/linux","branch":"master"}' \
  --cookie "session=<YOUR_SESSION>"

# Verify the clone exists
ls ./workspaces/projects/<PROJECT_ID>

2. SSH key generation

bash
curl -X POST http://localhost:5000/api/projects/<PROJECT_ID>/git/ssh-key \
  --cookie "session=<YOUR_SESSION>"
# Returns { "publicKey": "ssh-ed25519 AAAA..." }

# Add this key to your GitHub repo → Settings → Deploy Keys
# Then reconnect with the SSH URL:
curl -X POST http://localhost:5000/api/projects/<PROJECT_ID>/git/connect \
  -H "Content-Type: application/json" \
  -d '{"gitUrl":"git@github.com:user/private-repo.git","branch":"main"}' \
  --cookie "session=<YOUR_SESSION>"

3. Invalid URL rejection

bash
curl -X POST http://localhost:5000/api/projects/<PROJECT_ID>/git/connect \
  -H "Content-Type: application/json" \
  -d '{"gitUrl":"https://notgithub.com/user/repo","branch":"main"}' \
  --cookie "session=<YOUR_SESSION>"
# → 422 with { "error": "Must be a GitHub, GitLab, or Bitbucket URL..." }

How to Extend

Add a new Git host (e.g. Codeberg)

Edit GIT_URL_REGEX in packages/shared/src/schemas/projects.ts:

ts
const GIT_URL_REGEX =
  /^(https?:\/\/|git@)(github\.com|gitlab\.com|bitbucket\.org|codeberg\.org)[:/].+\/.+$/;

Add a git pull / refresh command

  1. Add a new route POST /api/projects/:id/git/pull in routes.ts
  2. Add pullRepo() to GitService — it calls runGitManager({ command: "clone", ... }) (the Python clone command already handles pull when .git exists)

Wire up webhook delivery (F2.7)

The webhookSecret is already generated and stored. In F2.7:

  1. Add POST /api/webhooks/github route
  2. Verify HMAC-SHA256 signature using webhookSecret
  3. Queue a deployment job via BullMQ

Released under the MIT license.