# Self-hosting simplegit simplegit is one static binary: it serves smart-HTTP git and a server-rendered web UI. TLS is terminated by a reverse proxy; the binary listens on plain HTTP and requires the `git` binary on `PATH` at runtime. # 1. Install (recommended) `scripts/install.sh` does steps 1-5 for you (binary, system user, data dir, config, proxy example conf, systemd unit): ```sh sudo scripts/install.sh https://git.example.com apache # or: ... https://git.example.com caddy | none ``` The second argument picks the reverse-proxy example conf, with the domain substituted: `apache` installs `/etc/apache2/sites-available/simplegit.conf` (and enables modules/site), `caddy` installs the site into the Caddyfile (or a `Caddyfile.simplegit` to `import`), `none` skips it. The unit is enabled but not started; the config is written only if absent. Manual equivalent, if you prefer to do it by hand: ### 1a. Build ```sh go build -o /usr/local/bin/simplegit ./cmd/simplegit ``` The binary embeds templates and static assets, so there is nothing else to copy. It needs `git` (with `http-backend`) installed on the host. ## 2. Config Create `simplegit.toml` (see `simplegit.toml.example`). Every key is optional. ```toml data_dir = "/var/lib/simplegit" # bare repos, SQLite DB, uploads listen_addr = "127.0.0.1:8080" # plain HTTP; the proxy fronts it base_url = "https://git.example.com" # public URL; builds clone URLs/links ``` `base_url` must be the public HTTPS URL, or clone URLs and `Secure` session cookies will be wrong. ## 3. Account and data dir ```sh simplegit adduser -config simplegit.toml ``` Flags come **before** the positional username (Go's flag parsing stops at the first non-flag). In a terminal you get a hidden password prompt (asked twice); for scripting, pipe the password on stdin so it never appears in the process list: ```sh printf '%s\n' "$PASSWORD" | simplegit adduser -config simplegit.toml ``` `serve` and `adduser` create the data-dir layout on first run, mode 0700. If the directory already exists from an older install, tighten it once: `chmod -R go-rwx /var/lib/simplegit`. Setting `UMask=0077` in the systemd unit keeps everything the service creates private as well. ## 4. Run it ```sh simplegit serve -config /etc/simplegit/simplegit.toml ``` ### systemd unit ```ini [Unit] Description=simplegit After=network.target [Service] ExecStart=/usr/local/bin/simplegit serve -config /etc/simplegit/simplegit.toml Restart=on-failure User=simplegit Group=simplegit [Install] WantedBy=multi-user.target ``` Run it as a dedicated user that owns `data_dir`. It does not need root and binds only the loopback address the proxy forwards to. ## 5. Reverse proxy (TLS) Caddy terminates TLS automatically: ``` git.example.com { reverse_proxy 127.0.0.1:8080 } ``` Two proxy requirements: - **Do not rewrite or buffer git request/response bodies.** Pushes stream a packfile both ways. - **Do not pass `X-Forwarded-For` expectations to the app** — the guest-write rate limiter keys on the socket `RemoteAddr`, so behind a proxy all clients share one bucket. (Trusting the forwarded header is a deliberate future decision, not current behavior.) nginx: `proxy_pass http://127.0.0.1:8080;` with `proxy_request_buffering off;` and a generous `client_max_body_size` (pushes and release uploads can be large). A request body that reaches the app while a response is being written is handled safely (the CGI stdin is drained before the response), but proxies that buffer or retry bodies are still discouraged. ### Apache httpd + certbot Apache streams request bodies by default, so large pushes work once the timeouts are raised. On Debian/Ubuntu: ```sh apt install apache2 certbot python3-certbot-apache a2enmod ssl proxy proxy_http headers rewrite ``` Point the DNS record at the host, then use a hand-owned vhost so certbot never rewrites your proxy config — `/etc/apache2/sites-available/simplegit.conf`: ```apache ServerName git.example.com DocumentRoot /var/www/html Require all granted RewriteEngine On RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/ RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L] ServerName git.example.com SSLEngine on SSLCertificateFile /etc/letsencrypt/live/git.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/git.example.com/privkey.pem ProxyPreserveHost On ProxyRequests Off # big pushes / release uploads ProxyTimeout 600s Timeout 600 # pass %2f through instead of 404ing AllowEncodedSlashes NoDecode # never re-encode the git stream SetEnv no-gzip 1 SetEnv dont-vary 1 # unlimited (the default, but explicit) LimitRequestBody 0 ProxyPass / http://[IP_ADDRESS]:8080/ retry=0 ProxyPassReverse / http://[IP_ADDRESS]:8080/ ErrorLog ${APACHE_LOG_DIR}/simplegit-error.log CustomLog ${APACHE_LOG_DIR}/simplegit-access.log combined ``` ```sh a2ensite simplegit && apachectl configtest && systemctl reload apache2 certbot certonly --webroot -w /var/www/html -d git.example.com ``` (`certbot --apache -d git.example.com` also works, but writes its own `-le-ssl.conf` that you would then have to add the proxy block to; the `certonly` route keeps the file yours.) Renewal is automatic via the systemd timer — verify with `certbot renew --dry-run`. Apache-specific gotchas, all load-bearing: - **`ProxyPreserveHost On`** is mandatory, or Apache sends `Host: [IP_ADDRESS]` and clone URLs/redirects come out wrong. - **`SetEnv no-gzip 1`** — `mod_deflate` re-encoding the streaming git response breaks some clients. - **`ProxyTimeout` / `Timeout`** default to 60s and kill big pushes. - **`AllowEncodedSlashes NoDecode`** — Apache's default 404s any URL with `%2f`. - **Keep comments on their own lines.** On a 2.4.66/Ubuntu build, inline `# …` comments on `ProxyTimeout`/`AllowEncodedSlashes` lines made `apachectl configtest` fail with "takes one argument" (the comment text parsed as a second argument); the same vhost passed once the comments moved to their own lines. The example above is comment-free on directive lines for that reason. - **The rate limiter sees `[IP_ADDRESS]`.** `mod_proxy` sets `X-Forwarded-For` automatically, but the app deliberately doesn't trust it (see above). - Firewall only 80/443; keep the app on loopback. ## 6. Dogfood (host simplegit on itself) ```sh # create the repo in the web UI, then: git remote add origin https://git.example.com//simplegit.git git push -u origin main git tag v1.0.0 && git push origin v1.0.0 ``` Then on the repo page: file an issue, open a pull request from a branch, and publish a release (Releases → new release, pick the tag, attach a binary). ## 7. Local UAT (single machine, no proxy) To exercise the whole UI and git flows locally, without TLS or a reverse proxy, use the helper script: ```sh scripts/local-uat.sh # default port 8090 scripts/local-uat.sh 9000 # or pick a port ``` It builds the binary, writes a throwaway config, creates the user (`josie` / `hunter2`), and serves on `127.0.0.1:`. The binary and the whole data dir live in `.uat/` (gitignored); delete that directory to reset. Override with `UAT_DIR`, `UAT_USER`, `UAT_PASS`. Manual equivalent: ```sh go build -o .uat/simplegit ./cmd/simplegit mkdir -p .uat/data printf 'data_dir = ".uat/data"\nlisten_addr = "127.0.0.1:8090"\nbase_url = "http://127.0.0.1:8090"\n' > .uat/sg.toml printf '%s\n' hunter2 | .uat/simplegit adduser -config .uat/sg.toml josie .uat/simplegit serve -config .uat/sg.toml ``` `base_url` is plain `http://` here, so the session cookie is not `Secure` — correct for localhost, wrong for production. Create a repo in the UI, then push with: ```sh git -c credential.helper= push http://josie:hunter2@127.0.0.1:8090/josie/.git main ``` ## 8. Backups Everything lives under `data_dir`: - `repos//.git` — the bare repositories (authoritative history) - `simplegit.db` (+ `-wal`, `-shm`) — users, sessions, tokens, issues, PRs, releases metadata - `uploads/releases//` — release attachments Back up all three together; the DB and uploads reference each other. SQLite is in WAL mode, so snapshot with `sqlite3 simplegit.db ".backup "` (or copy the DB plus its `-wal`) rather than copying a live file blindly.