A tiny read-only git web frontend — browse bare repos with just PHP and git, no database, no framework.
commit 3ffa0aa6fbdf36c3a35af8edeac65aabff931dab
Author: mrfox <jens.kristoffersson.se@gmail.com>
AuthorDate: Wed Aug 12 21:34:32 2026 +0200
Commit: mrfox <jens.kristoffersson.se@gmail.com>
CommitDate: Wed Aug 12 21:34:32 2026 +0200
Add a self-serve repo delete command over SSH
git has no built-in concept of deleting a repo (it's just a directory), so
mirror create/describe with a git-shell-commands/delete script -- installed
automatically by setup-server.sh since it loops over everything in that
directory. Requires the repo name typed twice as a confirmation guard
against a stray typo, since this is irreversible. Documented in the help
page and README alongside create/describe.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
README.md | 7 ++++---
server/git-shell-commands/delete | 39 +++++++++++++++++++++++++++++++++++++++
views/help.php | 15 +++++++++++----
3 files changed, 54 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 4a7ebe0..6d139ef 100644
--- a/README.md
+++ b/README.md
@@ -38,8 +38,9 @@ views/
*.php one file per page (tree, log, commit, refs, blob, atom, ...)
assets/ structural CSS (base.css) + the one bit of JS (clone-to-clipboard)
themes/ *.css files, each just a set of CSS custom properties
-server/ git hosting over SSH: user setup, key management, self-serve `create`,
- and the anonymous-HTTPS-clone route — independent of the web frontend
+server/ git hosting over SSH: user setup, key management, self-serve
+ `create`/`describe`/`delete`, and the anonymous-HTTPS-clone route —
+ independent of the web frontend
```
`inc/functions.php` never emits HTML; `views/*.php` never talks to git. `index.php` is the only
@@ -49,7 +50,7 @@ place that knows both sides exist.
1. `cp inc/config.example.php inc/config.php` and fill in your domain and paths.
2. `sudo bash install.sh` — sets up git hosting over SSH (dedicated user, bare-repo storage,
- self-serve repo creation) *and* the web frontend (a php-fpm pool running as that user, since
+ self-serve repo creation/description/deletion) *and* the web frontend (a php-fpm pool running as that user, since
the bare repos aren't readable by the default `www-data` pool, plus the Caddy block). Both
halves are idempotent and can be run standalone too — `server/setup-server.sh` for just the
git side, `deploy.sh` for just the web frontend.
diff --git a/server/git-shell-commands/delete b/server/git-shell-commands/delete
new file mode 100755
index 0000000..398c29a
--- /dev/null
+++ b/server/git-shell-commands/delete
@@ -0,0 +1,39 @@
+#!/bin/sh
+# usage (over ssh, as the git user): ssh git@<host> delete <reponame> <reponame>
+#
+# Permanently deletes a bare repo under $reposdir. Irreversible, so as a guard
+# against a stray typo the name must be given twice -- both must match exactly
+# or nothing is deleted. Paths below must match server/setup-server.sh.
+
+reposdir="/var/git/repos"
+
+name="$1"
+confirm="$2"
+if [ -z "$name" ] || [ -z "$confirm" ]; then
+ echo "usage: delete <reponame> <reponame> (name given twice, as confirmation)" >&2
+ exit 1
+fi
+
+name=$(basename "$name" ".git")
+confirm=$(basename "$confirm" ".git")
+
+if [ "$name" != "$confirm" ]; then
+ echo "repo name mismatch -- nothing deleted" >&2
+ exit 1
+fi
+
+case "$name" in
+ */* | .* | -*)
+ echo "invalid repo name: $name" >&2
+ exit 1
+ ;;
+esac
+
+dest="${reposdir}/${name}.git"
+if [ ! -d "$dest" ]; then
+ echo "no such repo: ${name}.git" >&2
+ exit 1
+fi
+
+rm -rf -- "$dest"
+echo "deleted repos/${name}.git"
diff --git a/views/help.php b/views/help.php
index 046b99c..309316c 100644
--- a/views/help.php
+++ b/views/help.php
@@ -8,10 +8,12 @@
<p>Two kinds of commands below: plain <code>git</code> commands (work with any git server, nothing
special about this one) and <code>ssh git@<?= h($host) ?> <word></code> ones — those run a small
-set of admin commands this server defines for itself (create a repo, set its description). They
-look like git subcommands but aren't; git itself has no <code>create</code> or <code>describe</code>
-built in that does this. (git *does* have its own unrelated <code>git describe</code> — it prints
-the nearest tag reachable from a commit. Different thing entirely from the SSH one below.)</p>
+set of admin commands this server defines for itself (create a repo, set its description, delete
+it). They look like git subcommands but aren't; git itself has no <code>create</code>,
+<code>describe</code>, or <code>delete</code> built in that does this — there's no such thing as
+"deleting a repo" in git, a repo is just a directory. (git *does* have its own unrelated
+<code>git describe</code> — it prints the nearest tag reachable from a commit. Different thing
+entirely from the SSH one below.)</p>
<h2>First time on this machine? Set your identity</h2>
<p>Every commit records who made it. Git won't guess — set this once per machine (or drop
@@ -66,6 +68,11 @@ git push</code></pre>
server's own <code>describe</code> SSH command (see the note up top) — not <code>git describe</code>.</p>
<pre><code>ssh git@<?= h($host) ?> describe <reponame> "A short description"</code></pre>
+<h2>Deleting a repo</h2>
+<p>Permanent — this removes the bare repo and its whole history, no trash/undo. The name has to be
+typed twice as a confirmation; if the two don't match, nothing is deleted.</p>
+<pre><code>ssh git@<?= h($host) ?> delete <reponame> <reponame></code></pre>
+
<h2>Getting push access</h2>
<p>Push (and repo creation) needs your SSH public key added to the server. Send it to the admin,
or if you're the admin: <code>sudo bash server/add-key.sh</code> from the foxygit checkout.</p>