# Openhub Openhub 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. It ships as a single Rust binary with SQLite for storage, so a whole installation is one executable plus one data directory. ![Repository page](docs/screenshots/repository.png) ## Features - **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. - **Read-only git over HTTP**: anonymous clones of public repositories, and password-authenticated clones of private ones. - **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. - **History**: paginated commit logs (per branch, tag or path), commit pages with diffs, branches with ahead/behind counts, and tags. - **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. - **Accounts and access**: public and private repositories, plus collaborators with write access. The first account created is the site administrator. - **Comfortable to use**: light and dark themes, works on phone-sized screens, and needs no JavaScript for core features.
Pull request conversation Diff view in the dark theme
## Quick start ### With Docker ```sh docker compose up -d ``` Open 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. ### From source You need Rust 1.88 or newer, a C compiler (to build the bundled libgit2 and SQLite), and `git` installed at runtime. ```sh cargo build --release ./target/release/openhub --public-url http://localhost:3000 ``` By default the web interface listens on port `3000` and the SSH server on port `2222`. Everything is stored in `./data`. ## Pushing your first repository 1. Sign in and open **Settings → SSH keys**. Paste your public key, for example the contents of `~/.ssh/id_ed25519.pub`. 2. Check that the server accepts the key: ```sh ssh -T -p 2222 git@your-host # Hi alice! You've successfully authenticated, but Openhub does not provide shell access. ``` 3. Create a repository with **New**, then push to it: ```sh git remote add origin ssh://git@your-host:2222/alice/project.git git push -u origin main ``` When 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. ## Configuration Each option can be set as a command-line flag or as an environment variable. | Flag | Environment variable | Default | Description | | --- | --- | --- | --- | | `--data-dir` | `OPENHUB_DATA_DIR` | `./data` | Holds the database, repositories and SSH host key | | `--http-addr` | `OPENHUB_HTTP_ADDR` | `0.0.0.0:3000` | Listen address for the web interface | | `--ssh-addr` | `OPENHUB_SSH_ADDR` | `0.0.0.0:2222` | Listen address for the SSH server | | `--public-url` | `OPENHUB_PUBLIC_URL` | `http://localhost:3000` | External URL of the web interface, used for links and clone URLs | | `--ssh-host` | `OPENHUB_SSH_HOST` | host of the public URL | Host shown in SSH clone URLs | | `--ssh-port` | `OPENHUB_SSH_PORT` | the SSH listen port | Port shown in SSH clone URLs | | `--disable-registration` | `OPENHUB_DISABLE_REGISTRATION` | `false` | Turns off sign-ups (the first account can still be created) | | `--git-bin` | `OPENHUB_GIT_BIN` | `git` | Path to the `git` executable | Logging is controlled with `RUST_LOG`, for example `RUST_LOG=openhub=debug`. ## Deployment notes - **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. - **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. - **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 `git@git.example.com:alice/project.git`. If the host already runs its own `sshd` on port 22, keep Openhub on another port. - **Host key**: the SSH host key is created on first start at `/ssh_host_ed25519_key`. Keep it with your backups so clients don't get host-key warnings after a restore. - **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. - **Registration**: once everyone has an account, set `OPENHUB_DISABLE_REGISTRATION=true`. ## How it works ``` browser ──▶ web UI (axum + askama) ──┐ git (HTTP) ──▶ smart HTTP, read-only ──┼──▶ bare repositories (libgit2 + git) git (SSH) ──▶ SSH server (russh) ──┘ │ └──▶ SQLite: users, SSH keys, repositories, pull requests ``` - Repositories are stored as bare git repositories named by their database ID (`repos/42.git`), so renaming a repository only changes the database. - Fetches and pushes run the standard `git upload-pack` and `git receive-pack`, so protocol support (including protocol v2) matches the installed git. - 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. - Each pull request's head commit is kept at `refs/pull//head`, so its history survives after the branch is deleted. You can fetch it with `git fetch origin pull//head`. These refs cannot be pushed to. - Web merges happen in memory with libgit2. The base branch is only updated if it hasn't moved since the merge started. ### Source layout | Path | Purpose | | --- | --- | | `src/main.rs` | Startup: configuration, database, web and SSH servers | | `src/ssh.rs` | SSH server, key authentication, git command dispatch | | `src/git.rs` | Repository access: refs, trees, history, diffs, merging | | `src/sync.rs` | Post-push bookkeeping for HEAD and pull requests | | `src/db.rs`, `migrations/` | SQLite schema and queries | | `src/web/` | HTTP handlers: accounts, code browsing, pull requests, smart HTTP | | `src/highlight.rs`, `src/markdown.rs` | Syntax highlighting and sanitised Markdown | | `templates/`, `static/` | HTML templates, stylesheet and script (compiled into the binary) | ## Security - Passwords are hashed with Argon2id. Session tokens are random, and only their SHA-256 hashes are stored. - Cookies are `HttpOnly` and `SameSite=Lax`, and cross-origin form posts are rejected. - Pages are served with a strict Content-Security-Policy, and no inline scripts are used. - 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. - 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. - Private repositories return 404 to anyone without access. Openhub 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. ## Development ```sh cargo test # unit tests cargo clippy --all-targets # lints (kept warning-free) scripts/smoke-test.sh # end-to-end: push, browse, pull requests, merges, permissions cargo run -- --data-dir ./data ``` The 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`. How 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). ## Out of scope Openhub deliberately covers only git hosting. It has no issues, CI, wikis, releases, forks, organisations or line-level review comments. ## License MIT. See [LICENSE](LICENSE). Icons 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).