foxygit / foxygit Log in
A tiny read-only git web frontend — browse bare repos with just PHP and git, no database, no framework.
commits tags

/server/setup-http-push.sh · 4.67 KB

raw
#!/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