~ober/jerboa-gitsite

Imported from ~/mine/jerboa-gitsite

download snapshot

about

# gitsite

A minimal SourceHut-style git forge built in Jerboa.

## Features

- **Git hosting**: Public, unlisted, and private repositories
- **Web UI**: Browse repositories, view commits, trees, and blobs
- **Git over HTTPS**: Clone and fetch via HTTP(S) with per-IP rate limiting
- **Git over SSH**: Push via SSH with public key authentication
- **CI/CD**: SourceHut-style build manifests with job queue and artifact collection
- **Minimal authentication**: Register/login with Argon2id password hashing
- **API token management**: Generate and revoke tokens from `/settings/tokens`
- **Security headers**: CSP, HSTS, X-Content-Type-Options, X-Frame-Options, etc. on every response
- **Session/token garbage collection**: Periodic sweep of expired records

## Quick Start

### Build Release Bundle (Production)

```bash
make binary
```

Creates `dist/gitsite` with native runtime libraries in `dist/lib/`.
The binary has these subcommands:
`serve`, `ssh-auth`, `buildd`, `add-user`, `migrate`.

No Jerboa install, SQLite3 library, or Scheme interpreter is required on the
target machine. Run the binary with `JERBOA_HOME` pointing at the bundled
`dist` directory so the packaged native runtime can be loaded.

### Create First User

```bash
dist/gitsite add-user <username> <email> <password>
```

### Run (Development)

```bash
make run
```

The server starts on `http://127.0.0.1:8080` with `mode development`,
registration open, and TLS disabled.

### Prerequisites for Development

- Jerboa (>= 0.2.0)
- Git (>= 2.28)
- OpenSSH (for SSH push support)
- sendmail or compatible MTA (for build notifications)

## Configuration

Edit `etc/gitsite.sexp`:

```scheme
((listen-port 8080)
 (listen-address "127.0.0.1")
 (var-root "/srv/gitsite/var")
 (public-url "https://git.example.org")
 (registration "open")
 (session-secret "change-this-to-a-random-secret-in-production")
 (tls-cert "/etc/ssl/private/fullchain.pem")
 (tls-key "/etc/ssl/private/key.pem")
 (builds-mode "async")
 (buildd-workers 2)
 (mode development))
```

- `listen-port`: HTTP/HTTPS port (default: 8080)
- `listen-address`: Bind address (default: 127.0.0.1)
- `var-root`: Data directory (default: var)
- `public-url`: Public URL for hooks and links
- `registration`: "open" or "closed" (default: closed)
- `session-secret`: Random secret used to sign session cookies — **must** be set
  in production
- `tls-cert` / `tls-key`: PEM cert + key paths. When **both** are set the server
  terminates TLS in-process (`httpsd-start`), sends
  `Strict-Transport-Security`, and marks session cookies `Secure`. When absent,
  plain HTTP is served (dev / behind-proxy mode).
- `builds-mode`: `"async"` (default) spawns a separate `gitsite buildd` process;
  `"sync"` runs builds inline during push.
- `buildd-workers`: Number of concurrent build workers (default: 2).
- `mode`: `development` (default) or `production`. In `production`, `tls-cert`
  and `tls-key` are required and `registration` is forced to `closed`.

## Deployment

### Production Setup

1. **Create dedicated user**:
   ```bash
   sudo useradd -r -m -d /srv/gitsite -s /bin/bash gitsite
   sudo useradd -r -m -d /srv/gitsite-build -s /bin/bash gitsite-build
   ```

2. **Install release bundle**:
   ```bash
   sudo install -d -m 755 /usr/local/libexec/gitsite
   sudo install -m 755 dist/gitsite /usr/local/libexec/gitsite/
   sudo cp -R dist/lib /usr/local/libexec/gitsite/
   sudo ln -sf /usr/local/libexec/gitsite/gitsite /usr/local/bin/gitsite
   ```

3. **Create directories**:
   ```bash
   sudo mkdir -p /srv/gitsite/{var,etc}
   sudo chown -R gitsite:gitsite /srv/gitsite
   sudo chown gitsite-build:gitsite-build /srv/gitsite-build
   ```

4. **Configure**:
   ```bash
   sudo -u gitsite cp etc/gitsite.sexp /srv/gitsite/etc/
   # Edit /srv/gitsite/etc/gitsite.sexp for production
   # Set tls-cert/tls-key + (mode production) to terminate TLS in-process,
   # or leave TLS unset and terminate at a reverse proxy.
   # Set session-secret to a long random string.
   ```

5. **Reverse proxy** (nginx example, only if not using in-process TLS):
   ```nginx
   server {
       listen 443 ssl http2;
       server_name git.example.org;

       ssl_certificate /path/to/cert.pem;
       ssl_certificate_key /path/to/key.pem;

       location / {
           proxy_pass http://127.0.0.1:8080;
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto $scheme;
       }
   }
   ```

6. **Systemd service**:
   ```ini
   [Unit]
   Description=gitsite git forge
   After=network.target

   [Service]
   Type=simple
   User=gitsite
   WorkingDirectory=/srv/gitsite
   Environment=JERBOA_HOME=/usr/local/libexec/gitsite
   Environment=JERBOA_GIT_LIB_PATH=/usr/local/libexec/gitsite/lib/libjerboa_git_shim.so
   ExecStart=/usr/local/bin/gitsite serve
   Restart=on-failure

   [Install]
   WantedBy=multi-user.target
   ```

### SSH Push Setup

1. **Create SSH user**:
   ```bash
   sudo useradd -r -m -d /srv/gitsite-ssh -s /usr/local/bin/gitsite ssh-auth gitsite-ssh
   ```

2. **Configure sshd** (add to /etc/ssh/sshd_config):
   ```
   Match User gitsite-ssh
       AuthorizedKeysFile /srv/gitsite/var/ssh/authorized_keys
       ForceCommand /usr/local/bin/gitsite ssh-auth
       PermitTTY no
       AllowAgentForwarding no
       AllowTcpForwarding no
       X11Forwarding no
   ```

3. **Restart sshd**:
   ```bash
   sudo systemctl restart sshd
   ```

4. **Add SSH keys** via web UI at `/settings/keys`

### Build Daemon

For asynchronous build execution:

```bash
sudo -u gitsite \
  JERBOA_HOME=/usr/local/libexec/gitsite \
  JERBOA_GIT_LIB_PATH=/usr/local/libexec/gitsite/lib/libjerboa_git_shim.so \
  /usr/local/bin/gitsite buildd &
```

Or create a separate systemd service:

```ini
[Unit]
Description=gitsite build daemon
After=network.target gitsite.service

[Service]
Type=simple
User=gitsite
WorkingDirectory=/srv/gitsite
ExecStart=/usr/local/bin/gitsite buildd
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

### FreeBSD Jail Setup

Recommended layout on FreeBSD 14+ with a dedicated jail:

1. **Create the jail and ZFS dataset**:
   ```bash
   sudo zfs create -o mountpoint=/jails/gitsite tank/jails/gitsite
   # use bsdinstall or a template to populate the jail
   ```

2. **Install runtime dependencies inside the jail**:
   ```bash
   pkg install git
   ```

3. **Create users**:
   ```bash
   pw useradd -n gitsite -d /srv/gitsite -s /bin/sh -m
   pw useradd -n gitsite-build -d /srv/gitsite-build -s /bin/sh -m
   ```

4. **Deploy the binary and service files**:
   ```bash
   install -m 755 dist/gitsite /usr/local/bin/gitsite
   install -m 555 files/rc.d/gitsite /usr/local/etc/rc.d/gitsite
   install -m 644 files/newsyslog.d/gitsite /usr/local/etc/newsyslog.conf.d/gitsite
   mkdir -p /srv/gitsite/{var,etc}
   chown -R gitsite:gitsite /srv/gitsite
   chown gitsite-build:gitsite-build /srv/gitsite-build
   cp etc/gitsite.sexp /srv/gitsite/etc/
   # edit /srv/gitsite/etc/gitsite.sexp: set session-secret, tls-cert/tls-key, mode production
   ```

5. **Configure sshd for SSH push** (add to `/etc/ssh/sshd_config` inside the jail):
   ```
   Match User git
       AuthorizedKeysFile /srv/gitsite/var/ssh/authorized_keys
       ForceCommand /usr/local/bin/gitsite ssh-auth
       PermitTTY no
       AllowAgentForwarding no
       AllowTcpForwarding no
       X11Forwarding no
   ```

6. **Enable and start services**:
   ```bash
   sysrc gitsite_enable=YES
   service gitsite start
   service sshd restart
   ```

7. **Log rotation**:
   The included `files/newsyslog.d/gitsite` rotates `/srv/gitsite/var/gitsite.log`.

### Backup and Restore

All persistent state lives under `var-root` (`/srv/gitsite/var` by default):

- `gitsite.db` — SQLite database (users, repos, sessions, tokens, jobs)
- `repos/` — bare git repositories
- `builds/` — build logs
- `artifacts/` — build artifacts
- `spool/` — async build queue

Back up the entire `var` directory while the server and buildd are stopped:

```bash
service gitsite stop
service gitsite_buildd stop 2>/dev/null || true
tar czf gitsite-var-$(date +%Y%m%d).tar.gz -C /srv/gitsite var
service gitsite start
```

Restore by stopping services, extracting the archive, and fixing ownership:

```bash
service gitsite stop
tar xzf gitsite-var-YYYYMMDD.tar.gz -C /srv/gitsite
chown -R gitsite:gitsite /srv/gitsite/var
service gitsite start
```

### Upgrade Path

1. Build the new binary: `make binary`
2. Stop `gitsite serve` and `gitsite buildd`
3. Run migrations if the release notes require it: `gitsite migrate`
4. Install the new binary: `install -m 755 dist/gitsite /usr/local/bin/gitsite`
5. Start services again

Database migrations are idempotent; `gitsite migrate` only creates tables that do not already exist.

## Usage

### Create Repository

1. Register/login at `/register` or `/login`
2. Click "new repo" in the navigation
3. Fill in name, description, and visibility
4. Click "create"

### Clone Repository

**HTTPS** (read-only for public/unlisted):
```bash
git clone http://git.example.org/~username/repo.git
```

**SSH** (requires SSH key):
```bash
git clone git@git.example.org:~username/repo.git
```

### Push Changes

**SSH** (recommended):
```bash
git remote set-url origin git@git.example.org:~username/repo.git
git push origin master
```

### Build Manifest

Create `.build.yml` in your repository:

```yaml
image: alpine/latest
packages:
  - build-base
tasks:
  - build: |
      cd repo
      make
  - test: |
      cd repo
      make test
artifacts:
  - repo/dist/app.bin
```

Builds are triggered automatically on push or manually via the web UI.

## Architecture

- **Web server**: `(std net httpd)` with custom routing
- **Database**: SQLite via `(std db sqlite)` (jsqlite pure-Scheme implementation)
- **Authentication**: Argon2id password hashing, server-side session records
- **Git operations**: System `git` binary for wire protocol, libgit2 for browsing (via jerboa-git Rust shim)
- **Builds**: Async file-spool queue; `gitsite buildd` workers claim and run jobs out-of-process
- **SSH**: System OpenSSH with forced-command binary (`gitsite ssh-auth`)
- **TLS**: In-process termination via `httpsd-start` (optional, config-driven)

## Limitations

- No merge requests or code review
- No issue tracker
- No webhooks
- No LFS support
- No syntax highlighting
- Builds run as the `gitsite-build` user with no further sandboxing; runaway builds must be killed manually
- HTTPS push not supported (use SSH); the HTTP stack decodes request bodies as UTF-8 strings
- jsqlite keeps the database image in memory, so only one process may write to a given `gitsite.db` at a time. Run `gitsite add-user` and `gitsite migrate` while the server is stopped
- Rate limiting on `/git/*` is 30 requests per 60 seconds per IP (sliding window); tunable at build time
- No organizations or teams

## License

MIT

recent commits