README.md
socle๐
A read-only web host for local git repositories.
Point it at some repos via a small config file, and it serves them:
browse trees and files at any ref, with markdown rendered to HTML,
and git clone works anonymously over smart HTTP.
No javascript required, no push, no auth, no database, no dynamic anything beyond rendering.
socle shows committed state at refs, never the working tree. (That's the line between socle and its sibling vitrine: vitrine shows the filesystem as it is right now; socle shows what git has committed.)
Config๐
socle takes exactly one config file, via the required -config flag
(there is no default location and no search).
The format is YAML:
sitename: parnassus
baseurl: https://code.example
repos:
parnass:
path: "%"
work/infra/deploy:
path: ../deploy
description: "deploy scripts"
-
sitename(optional) is the display identity of this socle: it appears in the header, page titles, the entrance masthead, and the footer, and it seeds the accent color. When unset, the identity derives from thebaseurlhost's first label when one is configured, else from the machine hostname -- but a published socle usually wants to say its own name, not its machine's. -
baseurl(optional) is the canonical outside address of this socle: an absolute http(s) URL naming only a host. When set, it becomes the displayed clone URL (otherwise that's derived from each request, which a rewriting proxy can fool) and enables serving Go vanity imports (see Go modules below). -
reposmaps URL names to local git repositories. A name may contain slashes, so the URL space can have depth. Name segments are limited to[A-Za-z0-9._-], with no leading dot or dash, and no name may be a path-prefix of another (that keeps URL dispatch unambiguous). -
path(required) is the repository to serve: a worktree checkout or a bare repo, both work. The placeholder"%"means "same as the repo name". Relative paths resolve against the config file's directory, which makes a config portable alongside the repos it describes. -
description(optional) shows on the index and the repo's home page.
A bad config (parse error, unknown key, invalid name, nothing configured at all) is a startup error: socle refuses to run rather than serve the wrong thing. A configured directory that's absent or not a git repository is only a warning and a skip, so one config can describe repos that only some hosts happen to have.
Config changes apply live: socle watches the config file (inotify for immediate pickup, plus a 5-second stat poll as backstop, so a change lands within ~5s worst case, usually instantly). Edit freely; a typo mid-edit logs a rejection and leaves the running server untouched, as does a reload in which not one configured repo can be opened. Requests in flight during a reload finish against the repo set they started with.
Browsing๐
Every repo gets the same small set of pages:
/ index of all repos
/<name>/ repo home: rendered README, clone URL, branches, tags
/<name>/tree/<ref>/<path> directory listings and file views
/<name>/raw/<ref>/<path> the file's raw bytes
/<name>/log/<ref> commit log, newest first
/<name>/commit/<ref> one commit: metadata, message, and its diff
<ref> is a branch, tag, HEAD, or any commit sha.
File views render markdown, show text in a code block, and inline images;
anything binary or oversized degrades to a link to the raw bytes.
URLs that address a commit by its full sha are served with immutable cache headers:
they can never mean anything else.
Cloning๐
The repo's home URL is its clone URL:
git clone http://host/name
Fetch is anonymous, over git's smart HTTP transport (protocol v2 included);
a trailing .git on the URL also works, since git clients and habits both produce it.
There is no push: socle never runs anything but read-only git subcommands,
and a push attempt is refused explicitly.
Go modules๐
With baseurl configured, repos are go get-able under their socle URLs as vanity import paths:
go get code.example/name
socle answers the toolchain's ?go-get=1 probes with the go-import meta tag
(pointing at its own clone URL, so the fetch is the ordinary smart-HTTP transport above)
and a go-source tag, so pkg.go.dev links source views back to socle's tree pages.
The repo's go.mod must of course declare the matching module path, code.example/name.
Without baseurl this is all inert: an import prefix is a public claim
that ends up in module caches and trackers, so socle only makes it on purpose.
Note the go toolchain requires https on the probe (barring GOINSECURE),
so in practice this means socle behind a TLS-terminating proxy.
Rendering and theming๐
Pages are rendered by vitrine's display package (a sibling project, wired in via a replace directive in go.mod), so socle inherits
its look and its markdown dialect: GFM with callout boxes and server-rendered
dot diagrams, heading anchors, and light/dark following the browser's
prefers-color-scheme.
Each socle wears an accent color derived from its sitename
(the -theme-seed flag recolors without renaming, mostly useful for tests),
so several socle instances in several tabs stay recognizable at a glance.
Known limits๐
- Branch and tag names containing
/aren't addressable in URLs (they still work by sha), and the accepted ref charset is narrower than git's own rules. - The log page shows the newest 100 commits; there is no pagination past that.
- Without
baseurlconfigured, the clone URL on a repo's home page reflects the scheme and host of the direct request; behind a rewriting or TLS-terminating proxy it would be wrong.
Deployment๐
Run socle as your own user, under a systemd user unit. No root anywhere.
-
Build and install the binary somewhere on your user's path:
go build -o ~/bin/socle . -
Write your config, e.g.
~/fusion/.socle.cfg, with paths relative to that directory (see Config above). -
Install the unit file from this repo as a user unit:
mkdir -p ~/.config/systemd/user cp socle.service ~/.config/systemd/user/ systemctl --user daemon-reload systemctl --user enable --now socleAdjust
ExecStartin the unit if your binary, config, or port should differ. -
Keep it running after logout (user units otherwise stop with your session):
loginctl enable-linger $USER
Check on it with systemctl --user status socle
and journalctl --user -u socle -f.
README.md ยท 6.6 KiB ยท 9f3639044e