Skip to content

Commit f833911

Browse files
Add Git support documentation
1 parent 1acd377 commit f833911

1 file changed

Lines changed: 283 additions & 0 deletions

File tree

docs/git-support.md

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
# Adding Git Support to a Solid Server
2+
3+
This guide explains how to add Git HTTP backend support to a Solid server, enabling `git clone` and `git push` operations on pod containers.
4+
5+
## Overview
6+
7+
The Git HTTP protocol allows clients to clone and push to repositories over HTTP. This is implemented using Git's built-in `git http-backend` CGI program - the same one used by Apache and Nginx.
8+
9+
### How It Works
10+
11+
```
12+
┌─────────────┐ HTTP ┌──────────────┐ CGI ┌─────────────────┐
13+
│ Git Client │ ─────────────▶│ Solid Server │ ────────────▶│ git http-backend│
14+
│ │◀───────────── │ │◀──────────── │ │
15+
└─────────────┘ └──────────────┘ └─────────────────┘
16+
```
17+
18+
**Clone flow:**
19+
1. `GET /repo/info/refs?service=git-upload-pack` - Discovery
20+
2. `POST /repo/git-upload-pack` - Fetch objects
21+
22+
**Push flow:**
23+
1. `GET /repo/info/refs?service=git-receive-pack` - Discovery
24+
2. `POST /repo/git-receive-pack` - Send objects
25+
26+
## Implementation
27+
28+
### 1. Detect Git Requests
29+
30+
Git protocol requests are identified by URL patterns:
31+
32+
```javascript
33+
function isGitRequest(urlPath) {
34+
return urlPath.includes('/info/refs') ||
35+
urlPath.includes('/git-upload-pack') ||
36+
urlPath.includes('/git-receive-pack');
37+
}
38+
39+
function isGitWriteOperation(urlPath) {
40+
return urlPath.includes('/git-receive-pack');
41+
}
42+
```
43+
44+
### 2. Security: Block Direct .git Access
45+
46+
**Important:** Git protocol requests should be allowed, but direct file access to `.git/` contents must be blocked:
47+
48+
```javascript
49+
// BLOCK: Direct access to .git contents (security risk)
50+
GET /.git/config → 403 Forbidden
51+
GET /.git/objects/abc123 → 403 Forbidden
52+
53+
// ALLOW: Git protocol (handled by git http-backend)
54+
GET /repo/info/refs?service=git-upload-pack → 200 OK
55+
POST /repo/git-upload-pack → 200 OK
56+
```
57+
58+
### 3. Authorization with WAC
59+
60+
Check permissions before allowing git operations:
61+
62+
```javascript
63+
// Clone/fetch requires Read access
64+
// Push requires Write access
65+
66+
const needsWrite = isGitWriteOperation(request.url);
67+
const requiredMode = needsWrite ? 'write' : 'read';
68+
69+
const { allowed } = await checkAccess({
70+
resourceUrl,
71+
resourcePath,
72+
agentWebId: request.webId,
73+
requiredMode
74+
});
75+
76+
if (!allowed) {
77+
return reply.code(needsWrite ? 403 : 401).send({
78+
error: needsWrite ? 'Write access required' : 'Read access required'
79+
});
80+
}
81+
```
82+
83+
### 4. Git HTTP Backend Handler
84+
85+
The core handler spawns `git http-backend` with CGI environment variables:
86+
87+
```javascript
88+
import { spawn } from 'child_process';
89+
90+
async function handleGit(request, reply) {
91+
const urlPath = decodeURIComponent(request.url.split('?')[0]);
92+
const queryString = request.url.split('?')[1] || '';
93+
94+
// Build CGI environment
95+
const env = {
96+
...process.env,
97+
GIT_PROJECT_ROOT: dataRoot, // Where repos are stored
98+
GIT_HTTP_EXPORT_ALL: '', // Allow read access
99+
GIT_HTTP_RECEIVE_PACK: 'true', // Enable push
100+
PATH_INFO: urlPath,
101+
REQUEST_METHOD: request.method,
102+
CONTENT_TYPE: request.headers['content-type'] || '',
103+
QUERY_STRING: queryString,
104+
CONTENT_LENGTH: request.headers['content-length'] || '0',
105+
};
106+
107+
// For non-bare repos, set GIT_DIR to .git subdirectory
108+
if (isRegularRepo) {
109+
env.GIT_DIR = path.join(repoPath, '.git');
110+
}
111+
112+
// Spawn git http-backend
113+
const child = spawn('git', ['http-backend'], { env });
114+
115+
// Send request body (for POST requests)
116+
if (request.body && request.body.length > 0) {
117+
child.stdin.write(request.body);
118+
}
119+
child.stdin.end();
120+
121+
// Parse CGI response and send to client
122+
// ... (see full implementation below)
123+
}
124+
```
125+
126+
### 5. CGI Response Parsing
127+
128+
Git http-backend outputs CGI format (headers + body). Parse and forward:
129+
130+
```javascript
131+
let buffer = Buffer.alloc(0);
132+
let headersSent = false;
133+
134+
child.stdout.on('data', (data) => {
135+
buffer = Buffer.concat([buffer, data]);
136+
137+
if (!headersSent) {
138+
// Find header/body separator (try both \r\n\r\n and \n\n)
139+
let headerEnd = buffer.indexOf('\r\n\r\n');
140+
let sep = '\r\n';
141+
let sepLen = 4;
142+
143+
if (headerEnd === -1) {
144+
headerEnd = buffer.indexOf('\n\n');
145+
sep = '\n';
146+
sepLen = 2;
147+
}
148+
149+
if (headerEnd !== -1) {
150+
const headerSection = buffer.subarray(0, headerEnd).toString();
151+
const bodySection = buffer.subarray(headerEnd + sepLen);
152+
153+
// Parse CGI headers
154+
for (const line of headerSection.split(sep)) {
155+
const colonIdx = line.indexOf(':');
156+
if (colonIdx > 0) {
157+
const key = line.substring(0, colonIdx).trim();
158+
const value = line.substring(colonIdx + 1).trim();
159+
160+
if (key.toLowerCase() === 'status') {
161+
statusCode = parseInt(value.split(' ')[0], 10);
162+
} else {
163+
reply.raw.setHeader(key, value);
164+
}
165+
}
166+
}
167+
168+
reply.raw.writeHead(statusCode);
169+
reply.raw.write(bodySection);
170+
headersSent = true;
171+
}
172+
} else {
173+
reply.raw.write(buffer);
174+
}
175+
buffer = Buffer.alloc(0);
176+
});
177+
178+
child.stdout.on('end', () => {
179+
reply.raw.end();
180+
});
181+
```
182+
183+
## Repository Setup
184+
185+
### Regular Repository (with working directory)
186+
187+
```bash
188+
cd /path/to/pod/myrepo
189+
git init
190+
echo "# My Project" > README.md
191+
git add .
192+
git commit -m "Initial commit"
193+
```
194+
195+
### Bare Repository (server-only, more efficient)
196+
197+
```bash
198+
cd /path/to/pod
199+
git init --bare myrepo.git
200+
```
201+
202+
### ACL for Public Clone
203+
204+
Create `/path/to/pod/myrepo/.acl`:
205+
206+
```turtle
207+
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
208+
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
209+
210+
<#public>
211+
a acl:Authorization;
212+
acl:agentClass foaf:Agent;
213+
acl:accessTo <./>;
214+
acl:default <./>;
215+
acl:mode acl:Read.
216+
```
217+
218+
### ACL for Authenticated Push
219+
220+
```turtle
221+
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
222+
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
223+
224+
<#owner>
225+
a acl:Authorization;
226+
acl:agent <https://alice.example.com/#me>;
227+
acl:accessTo <./>;
228+
acl:default <./>;
229+
acl:mode acl:Read, acl:Write, acl:Control.
230+
231+
<#public>
232+
a acl:Authorization;
233+
acl:agentClass foaf:Agent;
234+
acl:accessTo <./>;
235+
acl:default <./>;
236+
acl:mode acl:Read.
237+
```
238+
239+
## Usage
240+
241+
### Server
242+
243+
```bash
244+
# Start server with git support enabled
245+
jss start --git
246+
247+
# Or via environment variable
248+
JSS_GIT=true jss start
249+
```
250+
251+
### Client
252+
253+
```bash
254+
# Clone
255+
git clone http://localhost:3000/myrepo
256+
257+
# Clone with authentication (if required)
258+
git clone http://localhost:3000/myrepo
259+
# Git will prompt for credentials
260+
261+
# Push (requires write access)
262+
cd myrepo
263+
echo "New content" >> README.md
264+
git add .
265+
git commit -m "Update readme"
266+
git push
267+
```
268+
269+
## Complete Handler Code
270+
271+
See `src/handlers/git.js` in the JSS repository for the full implementation.
272+
273+
## References
274+
275+
- [Git HTTP Protocol](https://git-scm.com/book/en/v2/Git-on-the-Server-Smart-HTTP)
276+
- [git-http-backend documentation](https://git-scm.com/docs/git-http-backend)
277+
- [CGI Specification](https://www.rfc-editor.org/rfc/rfc3875)
278+
- [Web Access Control (WAC)](https://solidproject.org/TR/wac)
279+
280+
## Prior Art
281+
282+
- [nosdav/server](https://github.com/nosdav/server) - Git support implementation
283+
- [QuitStore](https://github.com/AKSW/QuitStore) - Git + RDF versioning

0 commit comments

Comments
 (0)