samjeston / openhub Public
167 lines· 10.3 KB ·Markdown
1# Openhub
2
3Openhub is a minimal, self-hostable git server with a familiar web interface. Push over SSH, browse code and history, and review changes with pull requests. That is the whole product: no issue tracker, no CI, no wiki.
4
5It ships as a single Rust binary with SQLite for storage, so a whole installation is one executable plus one data directory.
6
7![Repository page](docs/screenshots/repository.png)
8
9## Features
10
11- **Git over SSH**: a built-in SSH server authenticates `git push` and `git fetch` with the public keys users add in their settings. No system `sshd` configuration is needed.
12- **Read-only git over HTTP**: anonymous clones of public repositories, and password-authenticated clones of private ones.
13- **Code browsing**: file trees with the latest commit for each entry, syntax-highlighted files with linkable line numbers, rendered Markdown READMEs, raw files, and ZIP/tar.gz downloads.
14- **History**: paginated commit logs (per branch, tag or path), commit pages with diffs, branches with ahead/behind counts, and tags.
15- **Pull requests**: compare branches, open a pull request, discuss it in Markdown comments, and review the commits and the diff. Merge with a merge commit, a squash or a rebase, then delete the branch. Pull requests merged with a plain `git push` are detected automatically.
16- **Accounts and access**: public and private repositories, plus collaborators with write access. The first account created is the site administrator.
17- **Comfortable to use**: light and dark themes, works on phone-sized screens, and needs no JavaScript for core features.
18
19<table>
20 <tr>
21 <td><img src="docs/screenshots/pull-request.png" alt="Pull request conversation"></td>
22 <td><img src="docs/screenshots/diff-dark.png" alt="Diff view in the dark theme"></td>
23 </tr>
24</table>
25
26## Quick start
27
28### With Docker
29
30```sh
31docker compose up -d
32```
33
34Open <http://localhost:3000> and create an account; the first account becomes the administrator. Before deploying, edit `OPENHUB_PUBLIC_URL` in `docker-compose.yml` to the URL people will use.
35
36### From source
37
38You need Rust 1.88 or newer, a C compiler (to build the bundled libgit2 and SQLite), and `git` installed at runtime.
39
40```sh
41cargo build --release
42./target/release/openhub --public-url http://localhost:3000
43```
44
45By default the web interface listens on port `3000` and the SSH server on port `2222`. Everything is stored in `./data`.
46
47### On a Linux server with systemd
48
49`scripts/deploy-server.sh` installs Openhub on a Linux machine as a systemd service, over SSH. It copies your checkout to the machine, builds it there (installing Rust with rustup the first time) and runs `packaging/linux/install.sh`. The machine needs `git`, `curl` and a C compiler, and your SSH user needs `sudo`.
50
51```sh
52scripts/deploy-server.sh user@host
53```
54
55The service runs as the `openhub` user, keeps its data in `/var/lib/openhub`, starts at boot and restarts whenever it stops. The web interface listens on `127.0.0.1:3000` for a reverse proxy or tunnel on the same machine. SSH listens on port `2222` on every interface. Put your settings in `/etc/default/openhub`, then run `sudo systemctl restart openhub`:
56
57```sh
58OPENHUB_PUBLIC_URL=https://git.example.com
59OPENHUB_DISABLE_REGISTRATION=true
60```
61
62To upgrade, run the same command again. See [Operations → Running under systemd](docs/operations.md#running-under-systemd).
63
64## Pushing your first repository
65
661. Sign in and open **Settings → SSH keys**. Paste your public key, for example the contents of `~/.ssh/id_ed25519.pub`.
672. Check that the server accepts the key:
68 ```sh
69 ssh -T -p 2222 git@your-host
70 # Hi alice! You've successfully authenticated, but Openhub does not provide shell access.
71 ```
723. Create a repository with **New**, then push to it:
73 ```sh
74 git remote add origin ssh://git@your-host:2222/alice/project.git
75 git push -u origin main
76 ```
77
78When you push a branch other than the default branch, the server prints a link for opening a pull request. Once a pull request exists, the link points to it instead.
79
80## Configuration
81
82Each option can be set as a command-line flag or as an environment variable.
83
84| Flag | Environment variable | Default | Description |
85| --- | --- | --- | --- |
86| `--data-dir` | `OPENHUB_DATA_DIR` | `./data` | Holds the database, repositories and SSH host key |
87| `--http-addr` | `OPENHUB_HTTP_ADDR` | `0.0.0.0:3000` | Listen address for the web interface |
88| `--ssh-addr` | `OPENHUB_SSH_ADDR` | `0.0.0.0:2222` | Listen address for the SSH server |
89| `--public-url` | `OPENHUB_PUBLIC_URL` | `http://localhost:3000` | External URL of the web interface, used for links and clone URLs |
90| `--ssh-host` | `OPENHUB_SSH_HOST` | host of the public URL | Host shown in SSH clone URLs |
91| `--ssh-port` | `OPENHUB_SSH_PORT` | the SSH listen port | Port shown in SSH clone URLs |
92| `--disable-registration` | `OPENHUB_DISABLE_REGISTRATION` | `false` | Turns off sign-ups (the first account can still be created) |
93| `--git-bin` | `OPENHUB_GIT_BIN` | `git` | Path to the `git` executable |
94
95Logging is controlled with `RUST_LOG`, for example `RUST_LOG=openhub=debug`.
96
97## Deployment notes
98
99- **HTTPS**: put Openhub behind a reverse proxy such as Caddy or nginx and set `OPENHUB_PUBLIC_URL=https://git.example.com`. Session cookies are marked `Secure` whenever the public URL uses HTTPS. SSH traffic goes straight to the SSH port, not through the proxy.
100- **Public URL must match**: form submissions are only accepted from the origin in `OPENHUB_PUBLIC_URL`, or from one matching the request's `Host` header. If sign-in or other forms fail with "Cross-origin request rejected", check that the public URL matches exactly what appears in the browser's address bar, including scheme and port.
101- **Using port 22 for SSH**: forward port 22 to Openhub's SSH port (for example `"22:2222"` in Docker) and set `OPENHUB_SSH_PORT=22`. Clone URLs then take the short form `[email protected]:alice/project.git`. If the host already runs its own `sshd` on port 22, keep Openhub on another port.
102- **Host key**: the SSH host key is created on first start at `<data-dir>/ssh_host_ed25519_key`. Keep it with your backups so clients don't get host-key warnings after a restore.
103- **Backups**: back up the whole data directory. To copy the database while the server is running, use `sqlite3 openhub.db ".backup backup.db"`; otherwise stop the server first.
104- **Registration**: once everyone has an account, set `OPENHUB_DISABLE_REGISTRATION=true`.
105
106## How it works
107
108```
109browser ──▶ web UI (axum + askama) ──┐
110git (HTTP) ──▶ smart HTTP, read-only ──┼──▶ bare repositories (libgit2 + git)
111git (SSH) ──▶ SSH server (russh) ──┘
112 │
113 └──▶ SQLite: users, SSH keys, repositories, pull requests
114```
115
116- Repositories are stored as bare git repositories named by their database ID (`repos/42.git`), so renaming a repository only changes the database.
117- Fetches and pushes run the standard `git upload-pack` and `git receive-pack`, so protocol support (including protocol v2) matches the installed git.
118- After each push Openhub corrects `HEAD` if the default branch is missing, tracks the new tips of open pull requests, and marks pull requests whose commits reached the base branch as merged.
119- Each pull request's head commit is kept at `refs/pull/<n>/head`, so its history survives after the branch is deleted. You can fetch it with `git fetch origin pull/<n>/head`. These refs cannot be pushed to.
120- Web merges happen in memory with libgit2. The base branch is only updated if it hasn't moved since the merge started.
121
122### Source layout
123
124| Path | Purpose |
125| --- | --- |
126| `src/main.rs` | Startup: configuration, database, web and SSH servers |
127| `src/ssh.rs` | SSH server, key authentication, git command dispatch |
128| `src/git.rs` | Repository access: refs, trees, history, diffs, merging |
129| `src/sync.rs` | Post-push bookkeeping for HEAD and pull requests |
130| `src/db.rs`, `migrations/` | SQLite schema and queries |
131| `src/web/` | HTTP handlers: accounts, code browsing, pull requests, smart HTTP |
132| `src/highlight.rs`, `src/markdown.rs` | Syntax highlighting and sanitised Markdown |
133| `templates/`, `static/` | HTML templates, stylesheet and script (compiled into the binary) |
134
135## Security
136
137- Passwords are hashed with Argon2id. Session tokens are random, and only their SHA-256 hashes are stored.
138- Cookies are `HttpOnly` and `SameSite=Lax`, and cross-origin form posts are rejected.
139- Pages are served with a strict Content-Security-Policy, and no inline scripts are used.
140- Markdown (READMEs, pull requests and comments) is sanitised with `ammonia`. Raw files are served with a sandboxing CSP so they can never run script on the Openhub origin.
141- The SSH server accepts only public-key authentication and runs only `git-upload-pack` and `git-receive-pack`, for repositories the key's owner can access. Shell sessions are refused.
142- Private repositories return 404 to anyone without access.
143
144Openhub doesn't yet rate-limit sign-in attempts. If the server is exposed to the internet, add rate limiting at your reverse proxy. [docs/security.md](docs/security.md) lists the known gaps and hardening advice, including what to change before running an instance with open registration.
145
146## Development
147
148```sh
149cargo test # unit tests
150cargo clippy --all-targets # lints (kept warning-free)
151scripts/smoke-test.sh # end-to-end: push, browse, pull requests, merges, permissions
152cargo run -- --data-dir ./data
153```
154
155The smoke test starts a throwaway instance on ports 38080 and 38022 (override with `HTTP_PORT` and `SSH_PORT`) and drives it with `curl`, `ssh`, `ssh-keygen` and `git`.
156
157How the system works, including the architecture, data model, permissions, git transport, pull request tracking, security model and operations, is documented in [`docs/`](docs/README.md). Coding conventions and known pitfalls for contributors are in [`CLAUDE.md`](CLAUDE.md), with template-specific notes in [`templates/CLAUDE.md`](templates/CLAUDE.md).
158
159## Out of scope
160
161Openhub deliberately covers only git hosting. It has no issues, CI, wikis, releases, forks, organisations or line-level review comments.
162
163## License
164
165MIT. See [LICENSE](LICENSE).
166
167Icons are from [Lucide](https://lucide.dev) (ISC License). Syntax definitions come from [syntect](https://github.com/trishume/syntect) and [two-face](https://github.com/CosmicHorrorDev/two-face).