Minimal git server
µgit allows cloning via HTTPS/SSH, but can only be pushed to via SSH.
There are no plans to directly support issues or PR workflows.
If you wish to collaborate, please send me patches via github or tangled.
For issue/bug-tracking, I recommend git-bug.
Currently all HTML is allowed in markdown, µgit is intended to be run by/for a trusted user.
Pushing to a repo path that doesn't exist yet creates it automatically, there's no separate "create repo" step. Combined with push options, this means a repo can be created and configured in the same command as its first push:
git push -o "private=false" ssh://git@example.com:8448/new-repo.git main
A handful of git push -o <key>=<value> (--push-option) options are read on every push to update the repo's metadata, in addition to actually pushing the ref(s):
| Option | Value | Effect |
|---|---|---|
desc/description |
any string | Sets the repo description |
private |
true/false |
Sets the repo's private tag (see Configuration) |
tags |
comma-separated list, prefix an entry with - to remove it |
Adds/removes tags on the repo, for filtering |
For example:
git push -o desc="My project" -o "private=false" -o "tags=go,-wip" origin main
Push options can be repeated (-o key=value -o key2=value2) and are applied in order; unrecognized options are ignored.
The web UI's repo search is just a wrapper around git grep in-process.
If you need more advanced code search, consider running something like zoekt.
µgit ships a flake.nix with a NixOS module that can run one or more independent instances via services.ugit.<name>.
See nix/module.nix for the full list of options.
{
inputs.ugit.url = "git+https://git.jolheiser.com/ugit";
outputs = { self, nixpkgs, ugit, ... }: {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
modules = [
ugit.nixosModules.default
{
services.ugit.public = {
enable = true;
authorizedKeysFile = "/etc/ugit/authorized_keys";
showPrivate = false;
http.port = 8449;
};
}
];
};
};
}
Build it from source with a recent Go toolchain: go build ./cmd/ugitd, or go install go.jolheiser.com/ugit/cmd/ugitd@latest
µgit needs write access to its repo directory, SSH host key path, and (if generated) authorized_keys path.
µgit is configured via CLI flags, environment variables, and/or a JSONnet config file, in that order of precedence.
--ssh.port=2222. and - replaced by _, prefixed with UGIT_,
e.g. ssh.port becomes UGIT_SSH_PORT
The config file is JSONnet, with dotted flag names expanded into nested keys, e.g.
{
ssh: {
port: 2222
}
}
| Flag | Default | Description |
|---|---|---|
--config |
ugit.jsonnet |
Path to config file |
--repo-dir |
.ugit |
Path to directory containing repositories |
--show-private |
false |
Show private repos in the web interface |
--log.level |
error |
Logging level: debug, info, warn, error |
--log.json |
false |
Print logs in JSON(L) format |
--ssh.enable |
true |
Enable the SSH server |
--ssh.authorized-keys |
.ssh/authorized_keys |
Path to authorized_keys |
--ssh.clone-url |
ssh://localhost:8448 |
SSH clone URL base, shown to clients |
--ssh.port |
8448 |
SSH port |
--ssh.host-key |
.ssh/ugit_ed25519 |
SSH host key path (created if it doesn't exist) |
--http.enable |
true |
Enable the HTTP server |
--http.clone-url |
http://localhost:8449 |
HTTP clone URL base, shown to clients |
--http.port |
8449 |
HTTP port |
--meta.title |
ugit |
App title, shown in the web interface |
--meta.description |
Minimal git server |
App description, shown in the web interface |
--profile.username |
Username shown on the index page | |
--profile.email |
Email shown on the index page | |
--profile.links |
name,url pair for the index page; repeat the flag for more |
Each repository also has its own private flag, which is just a tag on the repo and is set to true by default when a repo is created.
It has no effect on its own; --show-private is what decides whether repos tagged private are shown in the web interface, and it also defaults to false.
Out of the box, newly created repos are private and --show-private is off, meaning repos are hidden from the web UI by default until you opt in one way or the other.
Neither setting affects SSH access: any user in authorized_keys can always push/pull a repo, private or not.
The setup I run is two ugit instances pointed at the same --repo-dir:
--show-private=false (the default), reverse-proxied to the internet--show-private=true, reachable only over Tailscale (or another private network)Both instances see the same repositories, but only the private instance's web UI will list/serve repos marked private.
Pushing/cloning over SSH is unaffected by --show-private, it only gates the web interface.
Using GitHub as an example (although Gitea/GitLab should have the same URL scheme)
Ba/sh
curl https://github.com/<username>.keys > path/to/authorized_keys
Nushell
http get https://github.com/<username>.keys | save --force path/to/authorized_keys
Lots of inspiration and some starting code used from gitea (MIT), wish (MIT), and legit (MIT).