socle

README and a systemd user unit

commit 30b113ea56f82ffdb189ea431c0acb9f887a1a20 · parent 543c549514 · browse this tree
Ximianth, 2026-10-26 20:22:25 UTC

The README follows vitrine's: what it is, the config schema, the page
set, the clone story, and honest known limits (slash-y refs, the log
cap, proxy-blind clone URLs).  The unit file mirrors vitrine's user-unit
deployment idiom.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..48c9dce
--- /dev/null
+++ b/README.md
@@ -0,0 +1,132 @@
+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:
+
+```yaml
+repos:
+  parnass:
+    path: "%"
+  work/infra/deploy:
+    path: ../deploy
+    description: "deploy scripts"
+```
+
+- `repos` maps 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.
+
+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
+```
+
+`<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.
+
+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 host wears an accent color derived from its hostname
+(the `-theme-seed` flag substitutes any string),
+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.
+- 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.
+
+1. Build and install the binary somewhere on your user's path:
+
+   ```
+   go build -o ~/bin/socle .
+   ```
+
+2. Write your config, e.g. `~/fusion/.socle.cfg`,
+   with paths relative to that directory (see Config above).
+
+3. 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 socle
+   ```
+
+   Adjust `ExecStart` in the unit if your binary, config, or port should differ.
+
+4. 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`.
diff --git a/socle.service b/socle.service
new file mode 100644
index 0000000..f2fecf2
--- /dev/null
+++ b/socle.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=socle: read-only web host for local git repositories
+After=network.target
+
+[Service]
+ExecStart=%h/bin/socle -listen :8090 -config %h/fusion/.socle.cfg
+Restart=on-failure
+
+[Install]
+WantedBy=default.target