Home

ugit @fe579711de5654fde3fb82d8bcfa2f4837689f75 - refs - log -
-
https://git.jolheiser.com/ugit.git
The code powering this h*ckin' site
drwxrwxrwx
146 B
assets/
drwxrwxrwx
32 B
cmd/
drwxrwxrwx
72 B
contrib/
drwxrwxrwx
122 B
internal/
drwxrwxrwx
146 B
nix/
-rw-r--r--
419 B
.air.toml
-rw-r--r--
36 B
.gitignore
-rw-r--r--
332 B
.woodpecker.yaml
-rw-r--r--
1.1 kB
LICENSE
-rw-r--r--
8.9 kB
README.md
-rw-r--r--
1.8 kB
flake.lock
-rw-r--r--
2.3 kB
flake.nix
-rw-r--r--
4.4 kB
go.mod
-rw-r--r--
51 B
go.mod.sri
-rw-r--r--
20 kB
go.sum

ugit logo

µgit

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.

Features

Push-to-create

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

Push options

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 a lightweight, git grep-style regex scan over the files at the tip of the default branch, implemented in-process rather than shelling out to git. If you need more advanced code search, consider running something like zoekt alongside µgit rather than relying on the built-in search.

Installation

NixOS

µgit ships a flake.nix with a NixOS module that can run one or more independent instances via services.ugit.<name>. Each instance gets its own user/group, home directory, and systemd service. 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";
            config = {
              show-private = false;
              http.port = 8449;
            };
          };
        }
      ];
    };
  };
}

The config option is written out as the instance's YAML config file (see Configuration below), so any flag documented there can be set through it.

Everything else

µgit is a single statically-linked Go binary with no runtime dependencies beyond git itself on $PATH.

Build it from source with a recent Go toolchain: go build ./cmd/ugitd, or go install go.jolheiser.com/ugit/cmd/ugitd@latest

Run the resulting ugitd binary directly, or under your process supervisor of choice (systemd, runit, etc). µgit needs write access to its repo directory, SSH host key path, and (if generated) authorized_keys path.

Configuration

µgit is configured via CLI flags, environment variables, and/or a YAML config file, in that order of precedence.

Flag Default Description
--config ugit.yaml 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.

Running public and private instances

The setup I run is two ugit instances pointed at the same --repo-dir:

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.

Getting your public SSH keys from another forge

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

License

MIT

Lots of inspiration and some starting code used from gitea (MIT), wish (MIT), and legit (MIT).