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-anon-clone.sh · 4.95 KB

raw
#!/usr/bin/env bash
# Anonymous, unauthenticated read-only `git clone` over HTTPS -- the same
# pattern GitHub/GitLab/Bitbucket use for public repos: git-http-backend
# behind the web server, no credentials needed for fetch/clone, push stays
# SSH-key-only (git-http-backend disables receive-pack over HTTP unless a
# repo explicitly sets http.receivepack=true, which nothing here does).
#
# Since foxygit already serves every repo's full history/contents to
# anyone who browses git.kristoffersson.info with zero auth, this doesn't
# change what's exposed -- it just adds a second, more useful way (`git
# clone` instead of clicking through a web UI) to get at the same data.
# All repos under /var/git/repos become clonable (GIT_HTTP_EXPORT_ALL=1);
# there's no per-repo opt-in.
#
# What it does:
#   1. apt-get install fcgiwrap
#   2. Installs a dedicated fcgiwrap instance (server/fcgiwrap-git.socket
#      + .service) running as the `git` user -- same reasoning as the
#      php-fpm pool in ../deploy.sh: /var/git/repos is 0700 git:git.
#   3. Adds a `handle_path /repos/*` route to the git.kristoffersson.info
#      Caddy block that proxies to git-http-backend via that socket.
#      Backs up the Caddyfile first. Idempotent -- safe to re-run.
#
# Run as root:
#   sudo bash /home/mrfox/foxygit/server/setup-anon-clone.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"
SOCK="/run/fcgiwrap-git.sock"

echo "==> 1/4  installing fcgiwrap"
if ! command -v fcgiwrap >/dev/null 2>&1; then
    apt-get update -qq
    apt-get install -y fcgiwrap
else
    echo "    already installed"
fi

# The Debian package enables its own default fcgiwrap.socket (as www-data,
# can't read /var/git). We don't use it -- disable so it's not just an idle
# unused (but still exposed-to-www-data) FastCGI worker sitting around.
if systemctl is-enabled --quiet fcgiwrap.socket 2>/dev/null; then
    systemctl disable --now fcgiwrap.socket >/dev/null 2>&1 || true
    echo "    disabled the default fcgiwrap.socket (unused, replaced by fcgiwrap-git)"
fi

echo "==> 2/4  installing the fcgiwrap-git service (runs as the git user)"
install -m 0644 "$ROOT/fcgiwrap-git.socket"  /etc/systemd/system/fcgiwrap-git.socket
install -m 0644 "$ROOT/fcgiwrap-git.service" /etc/systemd/system/fcgiwrap-git.service
systemctl daemon-reload
systemctl enable --now fcgiwrap-git.socket

if [ ! -S "$SOCK" ]; then
    echo "WARNING: $SOCK did not appear, check: systemctl status fcgiwrap-git.socket" >&2
fi

echo "==> 3/4  updating Caddyfile ($CADDYFILE)"

python3 - "$CADDYFILE" <<'PYEOF'
import sys, subprocess, datetime

path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
    content = f.read()

old_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}
}'''

new_block = '''git.kristoffersson.info {
\troot * /var/www/foxygit/

\thandle_path /repos/* {
\t\treverse_proxy unix//run/fcgiwrap-git.sock {
\t\t\ttransport fastcgi {
\t\t\t\tenv SCRIPT_FILENAME /usr/lib/git-core/git-http-backend
\t\t\t\tenv GIT_PROJECT_ROOT /var/git/repos
\t\t\t\tenv GIT_HTTP_EXPORT_ALL 1
\t\t\t\tenv PATH_INFO {http.request.uri.path}
\t\t\t}
\t\t}
\t}

\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 (add the handle_path block from this "
          + "script's source). 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_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 "==> 4/4  reloading caddy"
systemctl reload caddy

cat <<'EOF'

Done. Try it:
  git clone https://git.kristoffersson.info/repos/<reponame>.git

No credentials needed. Push still requires an SSH key (unchanged) --
git-http-backend only serves receive-pack (push) over HTTP if a repo's own
config explicitly sets http.receivepack=true, which none of them do.
EOF