spinney

A small, read-only web viewer for git repositories.

In the spirit of cgit, with a sourcehut-style look. One Go binary with its templates and CSS embedded. Pages need no JavaScript.

It is meant for a personal git server: you push over SSH, and everyone else can browse and clone over HTTPS. It is not a forge: there are no accounts, issues or pull requests.

https://git.example.com/
git
  • index
  • activity
  • about
NameDescriptionOwnerIdle
spinneyA read-only web viewer for gityou2 hours
dotfilesShell, editor and terminal setupyou3 days
tools
tools/backupNightly snapshots to object storagetools1 week
tools/notesMarkdown notes with imagestools1 month

What it does

Browse

An index of every repository, grouped by folder. Summaries with the README, trees with syntax highlighting, rendered markdown, raw files, blame, history, commits with diffs and .patch downloads, refs and tarballs.

Clone

Anything public clones over HTTPS with git's smart HTTP protocol. Pushing over HTTP is never possible: spinney has no write path at all.

Push

Over SSH to a locked-down git account that runs nothing but git. Pushing to a name that doesn't exist yet creates the repository.

Deploy

One command from your own machine sets up spinney, HTTPS, bot protection, pushing and a hardened server, optionally with SSH on a private Tailscale network.

Feeds and activity

Atom feeds of commits and tags for every repository, and an optional activity tab with a year of commits as a GitHub-style graph.

Themes

Light and dark colour themes (GitHub, Ayu, Night Owl, Catppuccin) or your own, and Google Fonts that spinney downloads and serves itself, so visitors never contact Google.

Private repositories

ssh gitserver private NAME on takes a repository off the web site within 15 seconds. Over SSH you push and fetch as usual.

Mirrors

Follow repositories hosted elsewhere under [mirrors]. Visitors browse them here and are pointed to the upstream to clone.

Try it locally

The demo needs only Go and git, and works on Linux, macOS and Windows.

# sample repositories in demo/repos
go run ./demo
# http://127.0.0.1:8080
go run . -config demo/spinney.toml

The demo has a C project with releases, merges and branches, a .dotfiles repository with symlinks, markdown notes with images, a nested repository, an empty one, and one hidden by the config.

To serve your own folder of repositories:

go build
./spinney -root ~/src -listen 127.0.0.1:8080

Deploy

From the spinney folder on your machine, with Go and SSH access to the server:

./install.sh root@git.example.com

It asks a few questions; the defaults are the most secure choices. It builds spinney for the server, uploads it and installs:

internet --> Caddy :443 (HTTPS) --> Anubis (bot check) --> spinney 127.0.0.1:8080
you      --> SSH (over Tailscale) --> git account (git-shell) --> /srv/git

Before finishing it checks that a fresh SSH login and pushing still work, and undoes the SSH changes if they don't. Later, ./install.sh … --yes re-deploys with the same answers.

Who can reach what

No hardening--harden--harden --tailscale
Web site and git clone over HTTPSInternetInternetInternet
Push over HTTPNeverNeverNever
Push over SSHInternet, push keys onlyInternet, push keys onlyTailnet only, push keys only
Admin SSH loginsThe system's own sshd settingsKeys only, listed accounts onlyTailnet only, keys only
Other portsFirewall left as it isBlockedBlocked from the internet
spinney and AnubisThis machine onlyThis machine onlyThis machine only

Day to day

The git account runs a handful of commands and nothing else: no shell, no terminal, no port forwarding.

ssh gitserver list                          # all repositories, private ones marked
ssh gitserver create NAME [description]     # an empty repository
ssh gitserver create NAME --private         # one that stays off the web site
ssh gitserver edit-desc NAME "New text"     # set its description
ssh gitserver private NAME on               # take it off the web site

By default the server refuses force-pushes and deleted branches, and checks every pushed object.

Documentation

DeployingPutting it on a server, from a fresh VPS to a hardened setup with backups
UsingDay to day: cloning, pushing, pulling, new repositories, a short SSH name for the server
ConfigurationEvery setting in config/spinney.toml
CustomisingThemes, your own colours and fonts, step by step
SecurityHow the server is protected, layer by layer
DevelopmentRunning and testing it on your own machine, including Windows