commit fb6d5d5749b90f49c9bcf7632361b855fc7d5fa6
parent 26f271b922b2189c77fea1e47269dfcbe4b3705c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 26 Aug 2026 11:06:26 -0700
Replace repository setup script and add landing pages
Diffstat:
8 files changed, 60 insertions(+), 58 deletions(-)
diff --git a/README.md b/README.md
@@ -59,11 +59,12 @@ Everything on the server lives under `~/repos/`:
### Add a repo
```sh
-ssh $GIT_HOST ~/repos/bin/add-repo <name> [--public] [-d "description"]
+ryops repos add [--public] [-d "description"] <name>
```
-Creates `~/repos/<name>.git`, symlinks the `post-receive` hook, writes
-`description` and `url`, and kicks off an initial `stagit-update`. The `url`
+Connects to `debian-wedding`, creates `~/repos/<name>.git`, symlinks the
+`post-receive` hook, writes `description` and `url`, and kicks off an initial
+`stagit-update`. The `url`
file determines the clone URL shown on the stagit page:
- private → `ssh://USER@$GIT_HOSTNAME/~/repos/<name>.git`
- public → `https://$PUBLIC_DOMAIN/git/<name>.git`
@@ -80,7 +81,9 @@ git push -u wedding main
Normal `git push`. The `post-receive` hook runs:
1. `git update-server-info` (for dumb-HTTP clone on public repos)
2. `stagit-update <name>` — regenerates HTML in `www/`, and if the repo has a
- `public` marker, also in `www-public/` + mirrors the bare repo into
+ `public` marker, also in `www-public/`. Each repo gets an `index.html` that
+ redirects to its rendered README when present and to `log.html` otherwise.
+ The update also mirrors the bare repo into
`www-public/git/<name>.git/` (excluding `hooks/` and the `public` marker).
3. `publish-public` — PUTs everything in `www-public/` to Bunny via HTTPS.
@@ -103,20 +106,12 @@ Public requires: the `public` marker file, and (for clean clone URL display) a
correct `url` file.
```sh
-# make public
-ssh $GIT_HOST 'touch ~/repos/<name>.git/public \
- && echo https://git.ryansepassi.com/git/<name>.git > ~/repos/<name>.git/url \
- && ~/repos/bin/stagit-update <name>'
-
-# make private again
-ssh $GIT_HOST 'rm ~/repos/<name>.git/public \
- && echo ssh://user@debian-wedding/~/repos/<name>.git > ~/repos/<name>.git/url \
- && ~/repos/bin/stagit-update <name>'
+ryops repos publish <name>
+ryops repos unpublish <name>
```
-`stagit-update` removes the public mirror when the marker is gone; the next
-`publish-public` run on the next push will still have stale files on Bunny.
-Run `~/repos/bin/publish-public-prune` to clean them up.
+Both commands regenerate the stagit output and publish it. `unpublish` also
+runs `publish-public-prune` so the stale public mirror is removed from Bunny.
### Update tooling
@@ -131,7 +126,7 @@ reloads caddy, and runs `stagit-update` if stagit is already installed.
| `./push` from laptop | sync tooling, write `config.env`, reload caddy, stagit-update |
| `git push <repo>` to the server | `post-receive` → `update-server-info` + `stagit-update` + `publish-public` |
| `bringup` on the server | one-time install (stagit, caddy snippet) |
-| `add-repo` | creates bare repo + hook + initial HTML |
+| `ryops repos add` | creates bare repo + hook + initial HTML |
| `remove-repo` | deletes bare repo + HTML + archive + Bunny orphans |
| `publish-public-prune` | deletes Bunny objects absent from local `www-public/` |
| Bunny CDN | serves public repos automatically; edge rules enforce no-cache on git refs + stagit HTML |
@@ -145,6 +140,8 @@ Everything is push-driven.
Caddy's `remote_ip` matcher rejects anything outside `100.64.0.0/10` +
`fd7a:115c:a1e0::/48` with a 403.
- **Public**: `https://$PUBLIC_DOMAIN/` — lists only public repos.
+ `https://$PUBLIC_DOMAIN/<name>` opens the rendered README when one exists and
+ the commit log otherwise.
## Cloning
diff --git a/bin/add-repo b/bin/add-repo
@@ -1,39 +0,0 @@
-#!/bin/sh
-# Create a bare repo with hook + metadata.
-# Usage: add-repo <name> [--public] [-d "description"]
-set -eu
-
-. "$HOME/repos/config.env" 2>/dev/null || true
-: "${GIT_HOSTNAME:=$(hostname)}"
-: "${PUBLIC_DOMAIN:=git.example.com}"
-
-name=""
-public=0
-desc=""
-while [ $# -gt 0 ]; do
- case $1 in
- --public) public=1 ;;
- -d) desc=$2; shift ;;
- -*) echo "unknown flag: $1" >&2; exit 2 ;;
- *) name=$1 ;;
- esac
- shift
-done
-[ -n "$name" ] || { echo "usage: add-repo <name> [--public] [-d desc]" >&2; exit 2; }
-
-dir=$HOME/repos/$name.git
-[ -e "$dir" ] && { echo "already exists: $dir" >&2; exit 1; }
-
-git init --bare "$dir"
-ln -s "$HOME/repos/bin/post-receive" "$dir/hooks/post-receive"
-echo "${desc:-$name}" > "$dir/description"
-
-if [ "$public" = "1" ]; then
- touch "$dir/public"
- echo "https://$PUBLIC_DOMAIN/git/$name.git" > "$dir/url"
-else
- echo "ssh://$USER@$GIT_HOSTNAME/~/repos/$name.git" > "$dir/url"
-fi
-
-"$HOME/repos/bin/stagit-update" "$name"
-echo "ok: $dir"
diff --git a/bin/bringup b/bin/bringup
@@ -47,4 +47,4 @@ sudo systemctl reload caddy
# Initial build over anything already present
"$REPOS/bin/stagit-update"
-echo "ok: server ready. next: ~/repos/bin/add-repo <name> [--public]"
+echo "ok: server ready. next, from the laptop: ryops repos add [--public] <name>"
diff --git a/bin/repo-index b/bin/repo-index
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Generate the default page for a stagit repository. Prefer the rendered
+# README and fall back to the commit log.
+set -eu
+
+out=${1:-}
+name=${2:-}
+[ -d "$out" ] && [ -n "$name" ] || { echo "usage: repo-index <output-dir> <repo-name>" >&2; exit 2; }
+case $name in *[!A-Za-z0-9._-]*) echo "invalid repo name: $name" >&2; exit 2 ;; esac
+
+target=/$name/log.html
+if [ -f "$out/file/README.md.html" ]; then
+ target=/$name/file/README.md.html
+fi
+
+cat > "$out/index.html" <<EOF
+<!doctype html>
+<meta charset="utf-8">
+<meta http-equiv="refresh" content="0; url=$target">
+<link rel="canonical" href="$target">
+<title>Redirecting</title>
+<a href="$target">Continue to the repository</a>
+EOF
diff --git a/bin/stagit-update b/bin/stagit-update
@@ -92,6 +92,7 @@ build_one() {
link_assets "$out"
render_markdown "$out" "$repo"
render_file_tree "$out" "$repo"
+ "$REPOS/bin/repo-index" "$out" "$name"
if [ -f "$repo/public" ]; then
out=$PUB/$name
@@ -100,6 +101,7 @@ build_one() {
link_assets "$out"
render_markdown "$out" "$repo"
render_file_tree "$out" "$repo"
+ "$REPOS/bin/repo-index" "$out" "$name"
# bare repo mirror for `git clone` (skip server-side bits)
mkdir -p "$PUB/git/$name.git"
rsync -a --delete --exclude=hooks --exclude=public "$repo/" "$PUB/git/$name.git/"
diff --git a/dev/test.sh b/dev/test.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -eu
+
+ROOT=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
+TMP=$(mktemp -d)
+trap 'rm -rf "$TMP"' EXIT
+OUT=$TMP/boot2
+mkdir -p "$OUT/file"
+
+: > "$OUT/log.html"
+: > "$OUT/file/README.md.html"
+"$ROOT/bin/repo-index" "$OUT" boot2
+grep -F 'url=/boot2/file/README.md.html' "$OUT/index.html" >/dev/null
+
+rm "$OUT/file/README.md.html" "$OUT/index.html"
+"$ROOT/bin/repo-index" "$OUT" boot2
+grep -F 'url=/boot2/log.html' "$OUT/index.html" >/dev/null
diff --git a/justfile b/justfile
@@ -0,0 +1,2 @@
+test:
+ ./dev/test.sh
diff --git a/push b/push
@@ -18,7 +18,7 @@ rsync -av --delete "$DIR/bin/" "$GIT_HOST:repos/bin/"
rsync -av --delete "$DIR/assets/" "$GIT_HOST:repos/assets/"
rsync -av --delete "$DIR/caddy/" "$GIT_HOST:repos/caddy/"
-# Server-side config for scripts (add-repo, etc.)
+# Server-side config for publishing scripts and ryops-managed repo metadata.
ssh "$GIT_HOST" "cat > ~/repos/config.env" <<EOF
GIT_HOSTNAME=$GIT_HOSTNAME
PUBLIC_DOMAIN=$PUBLIC_DOMAIN