Commit f591848f8a

f591848f8a4b01356b0d537d4d678ecd6e0f8eb0

parent: b04b2221fd

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-24 01:36 UTC

add README and 0BSD license

Layout: unified · split

LICENSE added +10
@@ -0,0 +1,10 @@
1Permission to use, copy, modify, and/or distribute this software for any
2purpose with or without fee is hereby granted.
3
4THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
5WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
6MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
7ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
8WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
9ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
10OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
README.org added +118
@@ -0,0 +1,118 @@
1#+title: gitbay
2#+author: Christian Cleberg
3
4A CLI-first git forge. One binary, SQLite, and the system =git= — designed
5so the command line is the product and the web UI is a rendering of state
6the CLI already manages. Runs at [[https://gitbay.org]].
7
8* Design
9
10SSH is the API. The server authenticates by public key, then dispatches the
11requested command: =git-upload-pack= / =git-receive-pack= stream the git
12transport, anything else is a control command. The control plane is fully
13usable from stock OpenSSH with no client installed:
14
15#+begin_src sh
16ssh git@gitbay.org repo create you/project --private
17ssh git@gitbay.org issue create you/project --title "bug" --file - < body.md
18ssh git@gitbay.org repo log you/project --json
19#+end_src
20
21The =gitbay= CLI is ergonomics on top — instance profiles, repo inference
22from the origin remote, =$EDITOR= for long text — never a requirement. A
23registry test enforces that every command stays reachable over bare ssh.
24
25Properties that follow from the design:
26
27- pushing is SSH-only. HTTPS and =git://= serve anonymous reads of public
28 repositories; a push over HTTPS is answered with a pkt-line ERR that
29 every git version prints as =remote error:= — no credential prompt,
30 ever. Private repositories answer 404/not-found identically to
31 nonexistent ones on every surface.
32- commit signatures (OpenPGP and SSHSIG) are verified against registered
33 keys and verified emails, with six distinct states — =verified=,
34 =signed_unknown_key=, =signed_email_mismatch=, =signed_key_expired=,
35 =signed_key_revoked=, =bad_signature=, =unsigned= — cached and
36 invalidated by a global key epoch, so registering a key retroactively
37 verifies old commits.
38- there is no server signing key. Server-created commits (web edits,
39 merge/squash/rebase commits) display honestly as unsigned, and branches
40 with =require_signed_commits= accept only fast-forward merges of
41 verified commits — enforced at push time and merge time.
42- the web UI is server-rendered with no JavaScript required. In
43 =view_only= mode the mutating routes are never registered on the mux;
44 browser sessions, where enabled, are minted over SSH (=web login=) —
45 there are no passwords.
46
47* Features
48
49- repositories with per-branch protection, forks, and organizations
50 (shared owner namespace, membership-derived access)
51- issues and merge requests (fast-forward, merge-commit, squash, rebase)
52 entirely over ssh, with reviews that go stale on force-push
53- merge request heads are fetched /into/ the target repository, so an MR
54 survives deletion of its source fork
55- =repo import= mirrors from any http(s)/git URL, tokens via stdin only
56- registration modes: =closed= (admin creates users), =invite=, =open=
57 with SMTP email verification
58- signed outbound webhooks with retries, dead-lettering, and SSRF
59 guarding; a JSON API (=POST /api/v1/cmd=) fronting the same command
60 registry, with bearer tokens mintable only over SSH
61- built-in ACME (Let's Encrypt) TLS; =admin backup= produces one
62 restore-tested archive (database snapshot first, then repositories)
63
64* Server quickstart
65
66#+begin_src sh
67# /etc/gitbay/config.toml
68[server]
69root = "/var/lib/gitbay"
70site_url = "https://forge.example.org"
71
72[http]
73acme_email = "you@example.org"
74#+end_src
75
76#+begin_src sh
77gitbayd --config /etc/gitbay/config.toml check-config
78gitbayd --config /etc/gitbay/config.toml admin user create you \
79 --key ~/.ssh/id_ed25519.pub --email you@example.org --verified --admin
80gitbayd --config /etc/gitbay/config.toml serve
81#+end_src
82
83The embedded SSH listener takes port 22 (move the host sshd, or set
84=ssh.mode = "system"= to run under it via =AuthorizedKeysCommand=). See
85=deploy/= for a cloud-init file, hardened systemd unit, and nightly
86backup timer.
87
88* Client quickstart
89
90#+begin_src sh
91gitbay remote add myforge forge.example.org --default
92gitbay auth whoami
93gitbay repo create you/project
94gitbay repo clone you/project && cd project
95gitbay issue create --title "first issue" # repo inferred from origin
96gitbay mr checkout 4 # fetches refs/merge-requests/4/head
97#+end_src
98
99Every read command takes =--json=; stdout is data, stderr is messages;
100exit codes are stable (0 ok, 2 usage, 3 not found, 4 denied). Man pages
101via =gitbay man=, completions via =gitbay completion <shell>=.
102
103* Development
104
105#+begin_src sh
106go build ./...
107go test ./... # e2e drives real git, ssh, sshd, and gpg binaries
108#+end_src
109
110Layout: =cmd/gitbay= (CLI), =cmd/gitbayd= (daemon, hooks, admin),
111=internal/control= (command registry — the single source of truth fronted
112by ssh and the JSON API), =internal/sshd= / =httpd= / =gitd= (transports),
113=internal/sig= (signature verification), =internal/policy= (access rules),
114=internal/store= (SQLite, migrations), =e2e/= (integration tests).
115
116* License
117
1180BSD.