# Git transport: HTTP(S) and SSH — how it works & how to use it

OpenGit exposes the same Git repositories over **two optional transports**:

| Transport | Where it runs | Typical URL shape |
|-----------|---------------|-------------------|
| **Smart HTTP(S)** | Inside the **Flask / Gunicorn** app | `https://your-host/Owner/repo.git` |
| **SSH** | Separate **AsyncSSH** process (`opengit-git-ssh.service`) | `ssh://git@host:2222/Owner/repo.git` or `git@host:Owner/repo.git` |

Both use the **same on-disk repos** (`storage/git/<owner_user_id>/<repo_name>`) and the **same authorization rules** in code (`routes/git/git_transport_auth.py`). Admin can enable either or both under **System Settings** (`git_transport_http_enabled`, `git_transport_ssh_enabled`).

**Operational SSH details** (systemd, firewall, host keys): see [git-ssh-transport.md](git-ssh-transport.md).

---

## Architecture (high level)

```
                    ┌─────────────────────────────────────┐
                    │           OpenGit users              │
                    └──────────────┬──────────────────────┘
                                   │
          ┌────────────────────────┼────────────────────────┐
          │                        │                        │
          ▼                        ▼                        ▼
   git clone/push           nginx (optional)         git clone/push
   https://…/O/R.git         reverse proxy          ssh://git@host:port/…
          │                        │                        │
          ▼                        ▼                        ▼
┌─────────────────────┐  ┌─────────────────────┐  ┌──────────────────────┐
│  Smart HTTP         │  │  Gunicorn workers   │  │  opengit-git-ssh       │
│  routes/git_        │→ │  Flask app          │  │  scripts/daemons/      │
│  http_backend.py    │  │  (normal web + API) │  │  opengit_git_ssh.py    │
└─────────┬───────────┘  └─────────────────────┘  └──────────┬───────────┘
          │                                                    │
          └────────────────────┬───────────────────────────────┘
                               ▼
                    ┌──────────────────────┐
                    │  git_transport_auth   │
                    │  + storage/git/…      │
                    │  + git upload/receive  │
                    └──────────────────────┘
```

---

## HTTP(S) backend (smart Git over HTTP)

### Code

- **Blueprint:** `routes/git_http_backend.py` → registered on the app as `git_http_bp` (no URL prefix; routes sit at the site root next to normal pages).
- **Auth:** `authorize_git_operation_http_basic()` — OpenGit **username + password** (password verified with the same hash as web login).
- **Push size / limits:** `routes/git/git_transport_limits.py` can apply checks on HTTP push paths where the stack supports it.

### Git client protocol (what Git actually calls)

1. **Discovery:** `GET /Owner/repo.git/info/refs?service=git-upload-pack` (fetch) or `…git-receive-pack` (push).  
   Response is a **smart HTTP advertisement** (`Content-Type: application/x-git-upload-pack-advertisement`, etc.).

2. **Data:** `POST /Owner/repo.git/git-upload-pack` or `…/git-receive-pack` with the appropriate `Content-Type` and request body (pack data).

The backend runs **`git upload-pack`** / **`git receive-pack`** as subprocesses against the repo directory, streaming stdin/stdout to the client (similar to `git-http-backend`, but integrated with OpenGit’s DB and auth).

### When HTTP auth is required

| Operation | Public repo | Private repo |
|-----------|-------------|--------------|
| **Fetch / clone** | No Basic Auth | Yes — valid user with read access |
| **Push** | **Yes** — always Basic Auth | Yes |

So **anonymous `git clone` of a public repo over HTTP** is supported; **push always requires** credentials.

### Admin toggle

If **Git over HTTP** is disabled in Admin, these routes return **503** and Git operations over HTTP fail.

---

## SSH backend (Git over SSH)

### Code & process

- **Entrypoint:** `scripts/daemons/opengit_git_ssh.py` (often run under **`opengit-git-ssh.service`**).
- **Library:** **AsyncSSH** (not OpenSSH server). Listens on `git_ssh_listen_port` (e.g. **2222**).
- **Sessions:** `encoding=None` so the Git pack protocol is handled as **binary** (required for `git-upload-pack` / `git-receive-pack` I/O).
- **Auth:** **Public keys only** — keys registered in **User Settings → SSH keys**. The server maps the key to a user, then calls the same **`authorize_git_operation()`** as HTTP (visibility, contributor, push rules).

### Allowed commands

Only:

- `git-upload-pack '<path>'` / `git-receive-pack '<path>'`
- Optional form: `git upload-pack …` (three tokens)

Path may be `Owner/repo.git`, `Owner/repo`, or **`/Owner/repo.git`** (leading slash from `ssh://` URLs — normalized server-side). **No shell**, no arbitrary commands.

### Hooks on push

SSH push runs `git receive-pack` with **`core.hooksPath`** set to OpenGit’s hooks directory (aligned with HTTP behavior).

### Admin toggle

If **Git over SSH** is disabled, the SSH service should reject or exit per your deployment; the in-app setting is read from the DB on each auth attempt.

**Install, systemd, firewall:** [git-ssh-transport.md](git-ssh-transport.md).

---

## Shared authorization (`git_transport_auth.py`)

Rough rules (see code for exact checks):

- **Repo must exist** under the given owner username + repo name.
- **Push:** only **owner** or **contributor**.
- **Fetch:** **public** repo → allowed (HTTP: without login; SSH: still requires a **registered key** so the server knows *which* identity to apply for “any authenticated user” style rules). **Private** → only owner/contributor.

SSH user name on the wire is typically **`git`**; identity comes from the **key**, not from the SSH username.

---

## How to use (clone URLs)

### From the UI

On a repository page, **Code** shows clone URLs when the admin has enabled the transport and set **SSH hostname** / port as needed. Preference: `git_clone_url_preference` (`https`, `ssh`, or `both`).

### HTTP(S)

```bash
git clone https://your-host/Owner/Repo.git
# Private or push: embed user (password often via credential helper)
git clone https://YourUsername@your-host/Owner/Repo.git
```

### SSH (non-default port)

Git does not read `-p 2222` from a `git@host:path` URL; use either:

```bash
git clone 'ssh://git@your-host:2222/Owner/Repo.git'
# or
GIT_SSH_COMMAND='ssh -p 2222' git clone git@your-host:Owner/Repo.git
```

---

## Testing end-to-end

From an OpenGit checkout:

```bash
bash scripts/test_git_transport.sh
```

This walks through **curl** `info/refs`, **`git ls-remote`**, optional **clone**, and optional **push** on throwaway branches (HTTP push needs username/password; SSH needs agent + registered key).

---

## Quick reference — files

| Piece | Location |
|-------|----------|
| HTTP smart Git routes | `routes/git_http_backend.py` |
| SSH AsyncSSH server | `scripts/daemons/opengit_git_ssh.py` |
| Shared auth | `routes/git/git_transport_auth.py` |
| SSH key fingerprints | `routes/git/ssh_key_utils.py` |
| HTTP push limits helper | `routes/git/git_transport_limits.py` |
| systemd unit (example) | `scripts/opengit-git-ssh.service` |
| Install/update Git SSH unit | `scripts/ensure_git_ssh_systemd.sh` |

---

## See also

- [git-ssh-transport.md](git-ssh-transport.md) — SSH service lifecycle, `update_local`, binary sessions, troubleshooting.
- [README.md](../README.md) — project overview and install pointers.
