foxygit / foxygit Log in
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) ?> &lt;word&gt;</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 &lt;reponame&gt; "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 &lt;reponame&gt; &lt;reponame&gt;</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>