forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
12.8 KiB
1# forge
2
3forge is a self-hosted git forge. You run it on your own server, so your code stays on machines you control and no third-party service sits in the middle. It ships as one binary, `forge`, which serves the web UI, git over HTTPS, and git over SSH. Repositories are plain bare git repositories on disk, and account data lives in one SQLite database.
4
5Try the live instance at https://git.tahinli.com.
6
7forge is a working name, and it is not released yet.
8
9## Features
10
11- **Accounts:** local accounts with argon2id passwords, two-factor login (TOTP) with recovery codes, SSH keys, and personal access tokens.
12- **Repositories:** public or private repos, collaborators, archiving, and a trash for deleted repos.
13- **Git:** clone and push over HTTPS and SSH. The default branch cannot be deleted or force-pushed, and archived repos reject pushes.
14- **Browsing:** rendered READMEs, syntax-highlighted files with line links, Markdown source view, commit history and diffs, branches and tags, and `.tar.gz` or `.zip` downloads.
15- **Collaboration:** issues, pull requests with reviews, notifications, and releases.
16- **Automation:** webhooks, push mirrors, replication to a standby, and CI runners.
17- **Large files:** Git LFS.
18- **Backups:** scheduled backup sets with verification and restore.
19
20## Quick start
21
22You need Rust (stable, edition 2024) and git 2.38 or newer. forge refuses to start with an older git.
23
241. Copy the example config. forge reads `config/forge.toml` by default.
25
26 ```sh
27 cp config/forge.example.toml config/forge.toml
28 ```
29
302. Start the server.
31
32 ```sh
33 cargo run -p forge-web
34 ```
35
363. Open http://127.0.0.1:7660/setup and create the admin account (its password must be at least 12 characters). This page works only while there are no users. You can also create the account from the shell:
37
38 ```sh
39 cargo run -p forge-web -- admin create-user tahinli --admin
40 ```
41
42 The command reads the password from `FORGE_PASSWORD` if it is set, or asks for it twice. Passwords must be at least 12 characters.
43
444. Add an SSH key at `/settings/keys`. Use an ed25519 key:
45
46 ```sh
47 ssh-keygen -t ed25519
48 ```
49
50 RSA keys are rejected, because forge turns off SHA-1 signatures.
51
525. Create a token at `/settings/tokens` for HTTPS pushes. It starts with `forge_` and is shown only once.
53
546. Create a repo at `/new`, then push to it:
55
56 ```sh
57 # SSH
58 git remote add origin ssh://git@127.0.0.1:2222/tahinli/my-repo.git
59 git push -u origin main
60
61 # HTTPS: use your username, and the token as the password
62 git remote add origin http://127.0.0.1:7660/tahinli/my-repo.git
63 git push -u origin main
64 ```
65
66Account settings live at `/settings/security`. There you can change your password or email, or turn on two-factor login (save the ten recovery codes it shows). Git over HTTPS uses access tokens, not your password or 2FA codes, so revoke a token at `/settings/tokens` if it leaks. You can change only the capitalization of your username, at `/settings/profile`.
67
68## Deploy on a server
69
70Run forge behind a TLS reverse proxy for any use beyond localhost. The session cookie is marked `Secure`, so logging in over plain HTTP will not work outside localhost.
71
72The easiest way is a system service, using the unit in `deploy/forge.service`. You can also run forge as a dedicated user with a systemd user service. Then enable linger (`sudo loginctl enable-linger forge`) so it keeps running after logout.
73
74System service setup:
75
76```sh
77sudo useradd --system --home /var/lib/forge --shell /usr/sbin/nologin forge
78sudo install -d -o forge -g forge /var/lib/forge /etc/forge
79sudo install -m 0640 -g forge config/forge.example.toml /etc/forge/forge.toml
80sudo install -m 0755 target/release/forge /usr/local/bin/forge
81sudo install -m 0644 deploy/forge.service /etc/systemd/system/forge.service
82sudo systemctl daemon-reload && sudo systemctl enable forge
83```
84
85Before you start it, edit `/etc/forge/forge.toml`:
86
87- `base_url`: your public `https://` URL.
88- `user_content_base_url`: a separate domain, not a subdomain of `base_url` (see below).
89- `ssh_public_host` and `ssh_public_port`: your public host name and port.
90- `database` and `data_dir`: absolute paths under `/var/lib/forge`.
91- `trusted_proxies = ["127.0.0.1"]` if the reverse proxy runs on the same host. Without this, every client shares the proxy's IP for login throttling.
92- `[backup] dir`: under `/var/lib/forge` or `/var/backups/forge`.
93
94Create the admin account (password at least 12 characters) before the reverse proxy makes the site public. `/setup` lets the first visitor claim the admin account while there are no users.
95
96```sh
97sudo -u forge FORGE_CONFIG=/etc/forge/forge.toml /usr/local/bin/forge admin create-user tahinli --admin
98sudo systemctl start forge
99```
100
101Open the SSH port (2222 by default) in your firewall. The reverse proxy does not forward SSH, so forge serves it directly.
102
103### Reverse proxy
104
105Git needs a proxy that does not buffer and allows long silent periods, because `git upload-pack` can pause while it builds a pack. This example uses [Ferron]https://ferron.sh 3.0.0-rc.7, a pre-release. Ferron uses its own `ferron.conf` format. A bare host block gets automatic TLS.
106
107```
108forge.example.com {
109 buffer_request 0
110 buffer_response 0
111 proxy {
112 upstream http://127.0.0.1:7660 {
113 idle_timeout "3600s"
114 }
115 }
116}
117
118usercontent.example.net {
119 buffer_request 0
120 buffer_response 0
121 proxy {
122 upstream http://127.0.0.1:7661
123 }
124}
125```
126
127Set `base_url = "https://forge.example.com"` and `user_content_base_url = "https://usercontent.example.net"` to match. Turn off compression on git paths too. forge does not compress responses yet.
128
129#### User content domain
130
131Raw repo files are served from `user_content_base_url`, which must be a different domain from `base_url` (for example `usercontent.example.net` next to `forge.example.com`). Browsers then treat it as a separate site, so a file a user uploads cannot run script against your app. The raw origin reads no cookies and sends risky file types as downloads. Links to private repo files carry a `?token=` that expires after 10 minutes.
132
133### Automatic updates
134
135forge can update itself. When CI passes on a push to the forge repo, the server swaps in the new binary and restarts. Runners do the same when they are idle. This needs the user units in `deploy/user/`, because the binary lives in `~/bin`. Use `forge` for the server and `ci` for a runner, each a dedicated user with linger enabled. The system unit `deploy/forge.service` is for a root-managed install with the binary in `/usr/local/bin`, which the `forge` user cannot write, so it does not self-update. Use it or the user units, not both.
136
137As the `forge` user, install the server units (the runner host does the same as `ci`, with `forge-runner.service` and `forge-runner-rollback.service`):
138
139```sh
140install -d ~/bin ~/.config/systemd/user
141install -m 0755 target/release/forge ~/bin/forge
142install -m 0755 deploy/user/forge-rollback ~/bin/forge-rollback
143install -m 0644 deploy/user/forge.service deploy/user/forge-rollback.service ~/.config/systemd/user/
144systemctl --user daemon-reload && systemctl --user enable --now forge.service
145```
146
147Turn on updates in `forge.toml`:
148
149```toml
150[self_update]
151enabled = true
152repo = "Tahinli/forge"
153branch = "main"
154job = "build"
155artifact = "dist_forge"
156```
157
158Runners update by default (`auto_update = true` in `runner.toml`). Set it to `false` to pin a runner's binary by hand.
159
160If a new binary crashes on start three times within a minute, systemd gives up and runs `forge-rollback`. That script keeps the old binary as `~/bin/forge.prev`, so it puts it back, records the bad build's sha256 in `~/bin/forge.bad`, and starts the unit again. A forge or runner skips any hash listed there, so it will not install the same build again. Check the journal (`journalctl --user -u forge -e`) to see what happened.
161
162Rollback does not undo database migrations. If the new version migrated the database, the old binary runs against the new schema. Take a backup before you rely on updates (see [Data and backups]#data-and-backups).
163
164## Configuration
165
166Settings live in TOML. `config/forge.example.toml` lists every key with comments. Relative paths are relative to the working directory. The keys you are most likely to change:
167
168| Key | Example | Meaning |
169|---|---|---|
170| `listen` | `"127.0.0.1:7660"` | Address for the web UI and git over HTTPS. |
171| `base_url` | `"https://forge.example.com"` | Public URL used in links and clone addresses. |
172| `user_content_base_url` | `"https://usercontent.example.net"` | Public URL of the raw file origin. |
173| `user_content_listen` | `"127.0.0.1:7661"` | Address of the raw file listener. |
174| `ssh_listen` | `"0.0.0.0:2222"` | Address for git over SSH. |
175| `ssh_public_host` / `ssh_public_port` | `"forge.example.com"` / `2222` | Host and port shown in SSH clone addresses. |
176| `registration` | `"closed"` | `closed`, `invite`, or `open`. Accounts come from `/setup`, the CLI, or `/admin/users`. |
177| `trusted_proxies` | `["127.0.0.1"]` | Proxy addresses whose `X-Forwarded-For` header is trusted. Leave empty if there is no proxy. |
178
179The config file is `config/forge.toml` by default. Pick another path with `--config` or the `FORGE_CONFIG` variable.
180
181## Admin commands
182
183Run `forge admin <command>`, or `cargo run -p forge-web -- admin <command>` from a checkout.
184
185| Command | What it does |
186|---|---|
187| `admin create-user <name> [--admin]` | Create a user. The first user is always an admin. |
188| `admin set-username <current> <NewCase>` | Change only the capitalization of a username. |
189| `admin reset-password <name>` | Set a random temporary password and sign the user out everywhere. |
190| `admin delete-user <name> [--yes]` | Delete a user. |
191| `admin trash-list` | List deleted repos, oldest first. |
192| `admin trash-purge --older-than-days <N>` | Permanently remove trashed repos older than N days. |
193| `admin audit-purge --older-than-days <N>` | Delete audit log entries older than N days. |
194| `admin backup --out <DIR> [--include-key] [--repos-only \| --db-only]` | Write a backup set. |
195| `admin backup-verify <PATH>` | Check a backup set. |
196| `admin restore --from <SET> --data-dir <NEW_DIR>` | Restore a backup set into a new data directory. |
197| `admin fsck [--full]` | Run `git fsck` on every repo and check the database. |
198| `admin lfs-gc` | Delete unused LFS objects that are at least a day old. |
199
200`serve` is the default command. `hook` and `runner` are for git hooks and CI runners. Set `RUST_LOG` to change the log level. `GET /healthz` returns `ok` and the build commit.
201
202## Data and backups
203
204Everything lives under `data_dir`:
205
206| Path | What it is |
207|---|---|
208| `forge.db` | Metadata: users, sessions, keys, tokens, repos. |
209| `forge.key` | Instance key. It encrypts stored secrets. Created on first boot. |
210| `ssh_host_ed25519_key` | SSH host key. Created on first boot. |
211| `repos/` | Bare repositories. |
212| `trash/` | Repos deleted from their settings page, until you purge them. |
213
214Back up `forge.key` separately, and keep it offline. Without it, encrypted values such as mirror credentials cannot be read again.
215
216Never copy a live database file with `cp` or `rsync`. Take a consistent copy with `VACUUM INTO`, which is safe while forge runs:
217
218```sql
219VACUUM INTO '/var/backups/forge/forge-2026-10-15.db';
220```
221
222For a full backup, use `forge admin backup --out <DIR>`. See [`docs/backup.md`]docs/backup.md for what a backup set contains, how to ship it off-site, how to restore, and the quarterly restore drill.
223
224#### Recovering a deleted repo
225
226Deleted repos stay in the trash until `admin trash-purge` runs. Find one with `forge admin trash-list`, then clone it with `git clone --mirror <path> recovered.git`. This restores all branches and tags, but not the owner, visibility, description, or collaborators.
227
228## CI runners
229
230CI workflows are TOML files in `.forge/ci.toml` in your repo. Jobs run on runners, which are `forge runner` processes on machines you control. The forge never connects to a runner; runners pull jobs over HTTPS. Each job runs in its own rootless sandbox, with its own workspace and a read-only token, and no container tool is needed. See [`docs/ci.md`]docs/ci.md for the workflow format, the security model, and runner setup.
231
232## Security
233
234- Passwords use argon2id. Tokens are stored as SHA-256 hashes.
235- Private repos you cannot see return 404, so their existence is not revealed.
236- No shell runs for git, and pushes are checked for object integrity. Users cannot add hooks or repo-level git config.
237- Login is throttled: 10 failed attempts per username and 30 per IP, over 15 minutes.
238- Run forge as a dedicated user, keep the git binary patched, and keep `forge.key` and `data_dir` private.
239
240## License
241
242forge is free software, licensed under the GNU Affero General Public License, version 3 or later (AGPL-3.0-or-later).
243
244The web UI bundles the Figtree and IBM Plex Mono fonts, both under the SIL Open Font License 1.1. Licenses and checksums are in `style/fonts/`.