You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.0 KiB
4.0 KiB
Implementation Guide for git-http-backend Integration
Overview
The git-http-backend integration needs to be implemented in /src/routes/api/git/[...path]/+server.ts. This route will handle all git HTTP operations (clone, push, pull).
URL Structure
All git requests will follow this pattern:
GET /api/git/{npub}/{repo-name}.git/info/refs?service=git-upload-pack(clone/fetch)GET /api/git/{npub}/{repo-name}.git/info/refs?service=git-receive-pack(push capability check)POST /api/git/{npub}/{repo-name}.git/git-upload-pack(fetch)POST /api/git/{npub}/{repo-name}.git/git-receive-pack(push)
Implementation Steps
1. Parse Request Path
Extract npub and repo-name from the path parameter:
const match = params.path.match(/^([^\/]+)\/([^\/]+)\.git\/(.+)$/);
if (!match) return new Response('Invalid path', { status: 400 });
const [, npub, repoName, gitPath] = match;
2. Authenticate with NIP-98
For push operations, verify NIP-98 authentication:
import { verifyEvent } from 'nostr-tools';
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Nostr ')) {
return new Response('Unauthorized', { status: 401 });
}
const nostrEvent = JSON.parse(authHeader.slice(7));
if (!verifyEvent(nostrEvent)) {
return new Response('Invalid signature', { status: 401 });
}
// Verify pubkey matches repo owner
if (nostrEvent.pubkey !== expectedPubkey) {
return new Response('Unauthorized', { status: 403 });
}
3. Map to Git Repository Path
Use RepoManager to get the full path:
import { RepoManager } from '$lib/services/git/repo-manager.js';
const repoManager = new RepoManager(process.env.GIT_REPO_ROOT || '/repos');
const repoPath = join(repoManager.repoRoot, npub, `${repoName}.git`);
if (!repoManager.repoExists(repoPath)) {
return new Response('Repository not found', { status: 404 });
}
4. Proxy to git-http-backend
Execute git-http-backend as a subprocess:
import { spawn } from 'child_process';
import { env } from '$env/dynamic/private';
const gitHttpBackend = '/usr/lib/git-core/git-http-backend'; // or wherever it's installed
const envVars = {
...process.env,
GIT_PROJECT_ROOT: repoManager.repoRoot,
GIT_HTTP_EXPORT_ALL: '1',
REQUEST_METHOD: request.method,
PATH_INFO: `/${npub}/${repoName}.git/${gitPath}`,
QUERY_STRING: url.searchParams.toString(),
CONTENT_TYPE: request.headers.get('Content-Type') || '',
CONTENT_LENGTH: request.headers.get('Content-Length') || '0',
};
const gitProcess = spawn(gitHttpBackend, [], {
env: envVars,
stdio: ['pipe', 'pipe', 'pipe']
});
// Pipe request body to git-http-backend
if (request.body) {
request.body.pipeTo(gitProcess.stdin);
}
// Return git-http-backend response
return new Response(gitProcess.stdout, {
headers: {
'Content-Type': 'application/x-git-upload-pack-result',
// or 'application/x-git-receive-pack-result' for push
}
});
5. Post-Receive Hook
After successful push, sync to other remotes:
// After successful git-receive-pack
if (gitPath === 'git-receive-pack' && request.method === 'POST') {
// Fetch NIP-34 announcement for this repo
const announcement = await fetchRepoAnnouncement(npub, repoName);
if (announcement) {
const cloneUrls = extractCloneUrls(announcement);
const otherUrls = cloneUrls.filter(url => !url.includes('git.imwald.eu'));
await repoManager.syncToRemotes(repoPath, otherUrls);
}
}
Alternative: Use a Git Server Library
Instead of calling git-http-backend directly, you could use a Node.js git server library:
isomorphic-gitwith@isomorphic-git/http-servernode-git-server- Custom implementation using
dugiteorsimple-git
Testing
Test with:
# Clone
git clone https://git.imwald.eu/{npub}/{repo-name}.git
# Push (requires NIP-98 auth)
git push origin main
For NIP-98 authentication, you'll need a git credential helper that:
- Intercepts git HTTP requests
- Signs a Nostr event with the user's key
- Adds
Authorization: Nostr {event}header