A tiny read-only git web frontend — browse bare repos with just PHP and git, no database, no framework.
#!/usr/bin/env bash
# Authenticated `git push` over HTTPS, using API keys created on the
# website (https://<host>/?a=account) as the credential -- the HTTPS
# equivalent of an SSH key, for people who'd rather not manage one.
#
# Anonymous clone/fetch (server/setup-anon-clone.sh) is untouched: this only
# adds an auth check in front of git-http-backend for push (receive-pack)
# requests. It works by swapping one env var in the existing /repos/* Caddy
# block so it runs a small wrapper (server/git-http-backend-auth) instead of
# calling git-http-backend directly:
#
# - clone/fetch requests: the wrapper execs straight into git-http-backend,
# unchanged from today.
# - push requests: the wrapper checks the request's HTTP Basic Auth
# password against the API keys created via the web app (stored, hashed,
# in /var/git/foxygit-auth.json) before allowing it through.
#
# Requires server/setup-anon-clone.sh to have been run first (this script
# assumes the /repos/* block and the fcgiwrap-git service already exist).
#
# What it does:
# 1. Creates /var/git/foxygit-auth.json if missing (git:git, 0600).
# 2. Installs server/git-http-backend-auth + server/verify-api-key.php to
# /usr/local/lib/foxygit/ (root:root, 0755 -- root-owned so the
# git-user-owned web app can't rewrite its own auth gate).
# 3. Points the /repos/* block's SCRIPT_FILENAME at the wrapper instead of
# git-http-backend directly. Backs up the Caddyfile first, validates
# before reloading. Idempotent -- safe to re-run.
# 4. Sets http.receivepack=true on every existing bare repo under
# /var/git/repos (new repos get this automatically from
# server/git-shell-commands/create and the web admin panel's
# create_bare_repo()).
#
# Run as root:
# sudo bash /home/mrfox/foxygit/server/setup-http-push.sh
set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then
echo "run as root: sudo bash $0" >&2
exit 1
fi
ROOT="$(cd "$(dirname "$0")" && pwd)"
CADDYFILE="/etc/caddy/Caddyfile"
REPOS_DIR="/var/git/repos"
AUTH_STORE="/var/git/foxygit-auth.json"
LIB_DIR="/usr/local/lib/foxygit"
echo "==> 1/4 auth store: $AUTH_STORE"
if [ ! -e "$AUTH_STORE" ]; then
printf '{"users":{},"keys":{}}' > "$AUTH_STORE"
chown git:git "$AUTH_STORE"
chmod 0600 "$AUTH_STORE"
echo " created"
else
echo " already exists, left untouched"
fi
echo "==> 2/4 installing wrapper scripts to $LIB_DIR"
mkdir -p "$LIB_DIR"
install -m 0755 -o root -g root "$ROOT/git-http-backend-auth" "$LIB_DIR/git-http-backend-auth"
install -m 0755 -o root -g root "$ROOT/verify-api-key.php" "$LIB_DIR/verify-api-key.php"
echo "==> 3/4 updating Caddyfile ($CADDYFILE)"
python3 - "$CADDYFILE" "$LIB_DIR/git-http-backend-auth" <<'PYEOF'
import sys, subprocess, datetime
path, wrapper = sys.argv[1], sys.argv[2]
with open(path, "r", encoding="utf-8") as f:
content = f.read()
old_line = "\t\t\t\tenv SCRIPT_FILENAME /usr/lib/git-core/git-http-backend\n"
new_line = "\t\t\t\tenv SCRIPT_FILENAME " + wrapper + "\n"
if new_line in content:
print(" already up to date, nothing to change")
sys.exit(0)
if old_line not in content:
print("ERROR: expected line not found verbatim in " + path
+ " -- has the /repos/* block changed since setup-anon-clone.sh "
+ "wrote it? Edit the Caddyfile by hand instead: point that "
+ "block's SCRIPT_FILENAME at " + wrapper + ". No changes made.",
file=sys.stderr)
sys.exit(1)
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_line, new_line, 1)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
print(" SCRIPT_FILENAME repointed at " + wrapper)
PYEOF
echo "==> validating Caddy config"
caddy validate --config "$CADDYFILE"
echo "==> reloading caddy"
systemctl reload caddy
echo "==> 4/4 enabling HTTPS push on existing repos under $REPOS_DIR"
count=0
for dest in "$REPOS_DIR"/*.git; do
[ -d "$dest" ] || continue
git -C "$dest" config http.receivepack true
count=$((count + 1))
done
echo " done ($count repo(s))"
cat <<'EOF'
Done. To push over HTTPS:
1. Log in at https://<host>/, go to "Account", create an API key.
2. git clone https://<host>/repos/<reponame>.git (still anonymous)
3. git push -- prompts for
username (anything) and password: paste the API key.
Or embed it: git remote set-url origin https://<host>/repos/<reponame>.git
and use a credential helper, or https://<user>:<key>@<host>/repos/<reponame>.git
Any valid API key can push to any repo, the same trust level SSH keyholders
already have.
EOF