A tiny read-only git web frontend — browse bare repos with just PHP and git, no database, no framework.
#!/usr/bin/env bash
# One-time deploy of foxygit, replacing stagit on git.kristoffersson.info.
#
# Run as root from anywhere:
# sudo bash /home/mrfox/foxygit/deploy.sh
#
# What it does:
# 1. Copies index.php, inc/, views/, assets/ and themes/ to /var/www/foxygit/
# 2. Installs a dedicated php-fpm pool running as the `git` user (the bare
# repos at /var/git/repos are 0700 git:git — the default www-data pool
# can't read them, so this needs its own pool rather than loosening
# those permissions).
# 3. Swaps the git.kristoffersson.info block in /etc/caddy/Caddyfile from
# static-file-serving (stagit's output) to php_fastcgi against the new
# pool. A timestamped backup of the Caddyfile is kept.
# 4. Reloads php-fpm and Caddy.
#
# Does NOT touch /var/git/repos, does NOT delete stagit or its generated
# HTML in /var/www/git, and does NOT touch the post-receive hooks that
# regenerate it — so stagit's output is simply orphaned (Caddy stops
# serving it) rather than destroyed. See the printed notes at the end for
# how to retire it once you're happy with foxygit.
set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then
echo "run as root: sudo bash $0" >&2
exit 1
fi
SRC_DIR="/home/mrfox/foxygit"
WEB_ROOT="/var/www/foxygit"
POOL_FILE="/etc/php/8.4/fpm/pool.d/foxygit.conf"
SOCK="/run/php/foxygit.sock"
CADDYFILE="/etc/caddy/Caddyfile"
echo "==> 1/5 web root: $WEB_ROOT"
mkdir -p "$WEB_ROOT"
install -m 0644 -o root -g root "$SRC_DIR/index.php" "$WEB_ROOT/index.php"
# wipe+recopy so files removed from the source don't linger on the server
for d in inc views assets themes; do
rm -rf "${WEB_ROOT:?}/$d"
cp -r "$SRC_DIR/$d" "$WEB_ROOT/$d"
done
chown -R root:root "$WEB_ROOT"
find "$WEB_ROOT" -type d -exec chmod 0755 {} +
find "$WEB_ROOT" -type f -exec chmod 0644 {} +
echo "==> 2/5 php-fpm pool: $POOL_FILE"
cat > "$POOL_FILE" <<EOF
[foxygit]
user = git
group = git
listen = $SOCK
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 4
pm.process_idle_timeout = 10s
EOF
echo "==> 3/5 testing php-fpm config"
php-fpm8.4 -t
echo "==> reloading php8.4-fpm"
systemctl reload php8.4-fpm
if [ ! -S "$SOCK" ]; then
echo "WARNING: $SOCK did not appear after reload, restarting php8.4-fpm instead" >&2
systemctl restart php8.4-fpm
fi
echo "==> 4/5 updating Caddyfile ($CADDYFILE)"
python3 - "$CADDYFILE" <<'PYEOF'
import sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
content = f.read()
old_block = '''git.kristoffersson.info {
\troot * /var/www/git/
\tfile_server
\tencode gzip zstd
\theader {
\t\tStrict-Transport-Security "max-age=31536000; includeSubDomains"
\t\tX-Content-Type-Options "nosniff"
\t\tX-Frame-Options "DENY"
\t\t-Server
\t}
\tlog {
\t\toutput file /var/log/caddy/git.kristoffersson.info.log
\t}
}'''
new_block = '''git.kristoffersson.info {
\troot * /var/www/foxygit/
\tphp_fastcgi unix//run/php/foxygit.sock
\tfile_server
\tencode gzip zstd
\theader {
\t\tStrict-Transport-Security "max-age=31536000; includeSubDomains"
\t\tX-Content-Type-Options "nosniff"
\t\tX-Frame-Options "DENY"
\t\t-Server
\t}
\tlog {
\t\toutput file /var/log/caddy/git.kristoffersson.info.log
\t}
}'''
if new_block in content:
print(" already up to date, nothing to change")
sys.exit(0)
if old_block not in content:
print("ERROR: expected git.kristoffersson.info block not found verbatim in "
+ path + " -- Caddyfile has changed since this script was written, "
+ "edit it by hand instead. No changes made.", file=sys.stderr)
sys.exit(1)
import subprocess, datetime
backup = path + ".bak." + datetime.datetime.now().strftime("%Y%m%d%H%M%S")
subprocess.run(["cp", path, backup], check=True)
print(" backup saved to " + backup)
content = content.replace(old_block, new_block, 1)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
print(" block replaced")
PYEOF
echo "==> validating Caddy config"
caddy validate --config "$CADDYFILE"
echo "==> 5/5 reloading caddy"
systemctl reload caddy
cat <<'EOF'
Done. https://git.kristoffersson.info should now be served by foxygit.
If stagit is still installed, it's just orphaned now (Caddy no longer
serves /var/www/git) -- see retire-stagit.sh to remove it fully.
EOF