From 00dffa8cf5cac8322cb47f2222e424d3960e7939 Mon Sep 17 00:00:00 2001 From: RATDAD Date: Sat, 13 Dec 2025 21:00:08 -0500 Subject: Added Git Smart HTTP Support --- .gitignore | 2 + Dockerfile | 61 +++++++++++++++++------- README.md | 16 ++++++- cgit.conf | 2 +- compose.yml | 13 ++--- entrypoint.sh | 95 +++++++++++++++++++++++++++++++++++-- etc/cgitrc | 14 ++++-- etc/httpd/conf.d/git-http-apcf.conf | 27 +++++++++++ etc/httpd/conf.d/git-http-cf.conf | 26 ++++++++++ etc/httpd/conf.d/git-http-p.conf | 35 ++++++++++++++ etc/httpd/conf.d/git-http-pcf.conf | 42 ++++++++++++++++ etc/httpd/conf/httpd.conf | 76 +++++++++++++++++++++-------- 12 files changed, 357 insertions(+), 52 deletions(-) create mode 100644 .gitignore create mode 100644 etc/httpd/conf.d/git-http-apcf.conf create mode 100644 etc/httpd/conf.d/git-http-cf.conf create mode 100644 etc/httpd/conf.d/git-http-p.conf create mode 100644 etc/httpd/conf.d/git-http-pcf.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a7daae --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Don't include the test files +srv/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f510d84..448e1c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,64 @@ # # -# cgit Docker Container - -FROM rockylinux:9 -LABEL MAINTAINER="RATDAD " +# cgit-http-server Docker Container +################### +# Build Stage +################### +FROM rockylinux:9 AS builder # Update everything; install dependencies. -RUN dnf -y update && dnf -y upgrade \ - && dnf install -y git gcc make openssl-devel zlib-devel zip \ - highlight httpd pip \ +RUN dnf -y update && dnf -y install \ + git gcc make \ + openssl-devel zlib-devel zip \ + highlight \ && dnf clean all -# Install cgit. -RUN git clone https://git.zx2c4.com/cgit -ADD cgit.conf cgit -RUN cd cgit \ - && git submodule init \ +# Build cgit. +RUN git clone https://git.zx2c4.com/cgit /build/cgit +WORKDIR /build/cgit +# Add compile-time config (cgit.conf). +ADD cgit.conf . +RUN git submodule init \ && git submodule update \ && make NO_LUA=1 \ - && make install \ - && cd .. \ - && rm -rf cgit + && make install DESTDIR=/build/install + +################### +# Runtime Stage +################### +FROM rockylinux:9 +LABEL MAINTAINER="RATDAD " -# Configure. +# Runtime dependencies +RUN dnf -y update && dnf -y install \ + httpd git highlight \ + openssl zlib zip \ + && dnf clean all + +# Install cgit artifacts. +COPY --from=builder /build/install / + +# If set to 0, the container will not \ +# handle git-http-backend for you. +ENV GIT_HTTP_MODE=0 + +# Configure Apache and cgit. ADD etc/cgitrc /etc/cgitrc ADD etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf +# Configure Git HTTP Modes. +ADD etc/httpd/conf.d/git-http-p.conf /etc/httpd/conf.d/git-http-p.conf +ADD etc/httpd/conf.d/git-http-cf.conf /etc/httpd/conf.d/git-http-cf.conf +ADD etc/httpd/conf.d/git-http-pcf.conf /etc/httpd/conf.d/git-http-pcf.conf +ADD etc/httpd/conf.d/git-http-apcf.conf /etc/httpd/conf.d/git-http-apcf.conf + # Add helper scripts. COPY opt/ /opt RUN chmod +x /opt/* +# Prevent git-http-backend safe.directory errors. +RUN git config --system --add safe.directory /srv/git + # Entrypoint. COPY ./entrypoint.sh / RUN chmod +x /entrypoint.sh diff --git a/README.md b/README.md index 2b82c84..60bbc55 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,21 @@ You can optionally run with HTTP Basic Auth with these options. docker run --name cgit -d -p 80:80 -v git/repo/location:/srv/git -e HTTP_AUTH_PASSWORD=pass HTTP_AUTH_USER=user ``` +## Docker Build +```bash +# Push-only +docker build --build-arg GIT_HTTP_MODE=w -t cgit:push-only . + +# Full RW +docker build --build-arg GIT_HTTP_MODE=rw -t cgit:rw . + +# Read-only +docker build --build-arg GIT_HTTP_MODE=ro -t cgit:ro . + +# No Git HTTP at all +docker build --build-arg GIT_HTTP_MODE=off -t cgit:no-http . +``` + ## Docker Compose You can use Docker Compose to create an instance of the server. ```yaml @@ -53,4 +68,3 @@ This container runs Apache Web Server. I made this decision because it's one of **To configure**, just mount your custom `httpd.conf` to `/etc/httpd/conf/httpd.conf` inside the container. Just keep in mind that cgit is compiled to serve its files in `/srv/www/htdocs/cgit/` and not the default location of `/var/www/htdocs/cgit/` as the documentation states. This is a decision I made deliberately because `/srv/` is where server files should go. - diff --git a/cgit.conf b/cgit.conf index 0a031e1..c40dafb 100644 --- a/cgit.conf +++ b/cgit.conf @@ -4,5 +4,5 @@ # This will be included by the Makefile upon compilation. # Use the standard dir for serving web content. -CGIT_SCRIPT_PATH = /srv/www/htdocs/cgit/ CGIT_CONFIG = /etc/cgitrc +CGIT_SCRIPT_PATH = /srv/www/htdocs/cgit \ No newline at end of file diff --git a/compose.yml b/compose.yml index 4eea970..82b5ce8 100644 --- a/compose.yml +++ b/compose.yml @@ -3,12 +3,14 @@ # cgit-docker compose example services: cgit: - build: - context: . - dockerfile: Dockerfile # You can also use the pre-built containers hosted at ghcr and docker. # image: ghcr.io/bigratdad/cgit:latest # image: ratdad/cgit:latest + build: + context: . + dockerfile: Dockerfile + # args: + # - ENABLE_GIT_HTTP=0 env_file: - .env ports: @@ -16,6 +18,5 @@ services: volumes: # - ./favicon.ico:/srv/www/htdocs/cgit/favicon.ico # custom favicon # - ./cgit.css:/srv/www/htdocs/cgit/cgit.css # custom cgit.css - - ./etc/httpd/conf/httpd.conf:/etc/httpd/conf/httpd.conf # You may want to change the httpd config on the server. - - ./etc/cgitrc:/etc/cgitrc # You can (and should) bind your own cgitrc. - - ./srv/git/:/srv/git # Bind the location of your git repos to /srv/git in the container. + - ./etc/cgitrc:/etc/cgitrc # You can (and should) bindmount your own cgitrc. + - ./srv/git/:/srv/git:ro # I do not recommend creating repos from inside the container. Make it read-only. \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index a5eadf1..c00077f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,4 +1,93 @@ -#!/usr/bin/env bash +#!/bin/bash +set -e -/opt/auth.sh -httpd -DFOREGROUND \ No newline at end of file +export GIT_HTTP_MODE="${GIT_HTTP_MODE:-0}" # Default: disabled +export GIT_HTTP_AUTH=0 # whether http auth is enabled for git-http-backend +export GIT_HTTP_AUTH_FILE="${GIT_HTTP_AUTH_FILE:-/srv/git/.htpasswd}" +# Explicitly override for single .htpasswd for cgit/git-http-server +# GIT_HTTP_AUTH_FILE=/srv/www/htdocs/cgit/.htpasswd +CONF_DIR="/etc/httpd/conf.d" +ACTIVE_CONF="${CONF_DIR}/git-http.conf" + +# Remove any previously selected config +rm -f "${ACTIVE_CONF}" + +# HTTP BASIC AUTH FOR GIT +# Options are 0=disable +# 1=push only, 2=clone/fetch only, +# 3=push,clone/fetch, 4=push,clone/fetch w/auth +case "${GIT_HTTP_MODE}" in + 0) ;; + 1) + ln -s "${CONF_DIR}/git-http-p.conf" "${ACTIVE_CONF}" + echo "Using [git-http] Using Mode 1: git push (auth)" + GIT_HTTP_AUTH=1 + ;; + 2) + ln -s "${CONF_DIR}/git-http-cf.conf" "${ACTIVE_CONF}" + echo "[git-http] Using Mode 2: git clone/fetch only (no auth)" + ;; + 3) + ln -s "${CONF_DIR}/git-http-pcf.conf" "${ACTIVE_CONF}" + echo "[git-http] Using Mode 3: git push (auth) + git clone/fetch (no auth)" + GIT_HTTP_AUTH=1 + ;; + 4) + ln -s "${CONF_DIR}/git-http-apcf.conf" "${ACTIVE_CONF}" + echo "[git-http] Using Mode 4: git push + git clone/fetch (auth)" + GIT_HTTP_AUTH=1 + ;; + *) + echo "[git-http] ERROR: invalid GIT_HTTP_MODE=${GIT_HTTP_MODE}" >&2 + exit 1 + ;; +esac + +# Set up auth credentials for git-http-backend +if [ "$GIT_HTTP_AUTH" -eq 1 ]; then + if [ -z "$GIT_HTTP_AUTH_USER" ] || [ -z "$GIT_HTTP_AUTH_PASSWORD" ]; then + echo "[git-http] ERROR: Auth Enabled, but GIT_HTTP_AUTH_USER/PASSWORD is missing." >&2 + exit 1 + fi + # If .htpasswd exists already, don't recreate it. + if [ ! -f "$GIT_HTTP_AUTH_FILE" ]; then + htpasswd -c -b "$GIT_HTTP_AUTH_FILE" \ + "$GIT_HTTP_AUTH_USER" "$GIT_HTTP_AUTH_PASSWORD" + echo "[git-http] INFO: Credentials written to ${GIT_HTTP_AUTH_FILE}." + else + htpasswd -b "$GIT_HTTP_AUTH_FILE" \ + "$GIT_HTTP_AUTH_USER" "$GIT_HTTP_AUTH_PASSWORD" + echo "[git-http] INFO: Using ${GIT_HTTP_AUTH_FILE} for auth credentials." + fi + + # Ensure proper permissions for /srv/git/.htpasswd + chown root:apache "$GIT_HTTP_AUTH_FILE" + chmod 640 "$GIT_HTTP_AUTH_FILE" +fi + +# HTTP BASIC AUTH FOR CGIT +if [ -n "$HTTP_AUTH_PASSWORD" ]; then + HTTP_AUTH_USER="${HTTP_AUTH_USER:-admin}" + + # Create a .htaccess file. + cat > /srv/www/htdocs/cgit/.htaccess < + AuthType Basic + AuthName "Git Access" + AuthUserFile ${GIT_HTTP_AUTH_FILE} + Require valid-user + + +# info/refs strictly scoped + + + Require expr %{QUERY_STRING} == "service=git-upload-pack" + Require expr %{QUERY_STRING} == "service=git-receive-pack" + + diff --git a/etc/httpd/conf.d/git-http-cf.conf b/etc/httpd/conf.d/git-http-cf.conf new file mode 100644 index 0000000..0d4302e --- /dev/null +++ b/etc/httpd/conf.d/git-http-cf.conf @@ -0,0 +1,26 @@ +# +# +# Git Smart HTTP Support (readonly) +# clone/fetch ONLY + +SetEnv GIT_PROJECT_ROOT /srv/git +SetEnv GIT_HTTP_EXPORT_ALL 1 + +# Expose git-upload-pack and info/refs. +ScriptAliasMatch "^(/.+/(git-upload-pack|info/refs))$" \ + /usr/libexec/git-core/git-http-backend$1 + +# No authentication needed for git clone/fetch. + + Require all granted + + +# Only allow info/refs for git clone/fetch. + + Require expr %{QUERY_STRING} == "service=git-upload-pack" + + +# Explicitly deny git push just in case. + + Require all denied + diff --git a/etc/httpd/conf.d/git-http-p.conf b/etc/httpd/conf.d/git-http-p.conf new file mode 100644 index 0000000..b30ad47 --- /dev/null +++ b/etc/httpd/conf.d/git-http-p.conf @@ -0,0 +1,35 @@ +# +# +# Git Smart HTTP Support +# git push ONLY + +SetEnv GIT_PROJECT_ROOT /srv/git +SetEnv GIT_HTTP_EXPORT_ALL 1 + +# Expose git-receive-pack and info/refs. +ScriptAliasMatch "^(/.+/(git-receive-pack|info/refs))$" \ + /usr/libexec/git-core/git-http-backend$1 + +# Authenticate against git push. + + AuthType Basic + AuthName "Git Push Access" + AuthUserFile ${GIT_HTTP_AUTH_FILE} + Require valid-user + + +# Deny info/refs push unless it's for git push. + + AuthType Basic + AuthName "Git Push Access" + AuthUserFile ${GIT_HTTP_AUTH_FILE} + + Require expr %{QUERY_STRING} == "service=git-receive-pack" + Require valid-user + + + +# Explicitly deny git clone/fetch just in case. + + Require all denied + diff --git a/etc/httpd/conf.d/git-http-pcf.conf b/etc/httpd/conf.d/git-http-pcf.conf new file mode 100644 index 0000000..d343443 --- /dev/null +++ b/etc/httpd/conf.d/git-http-pcf.conf @@ -0,0 +1,42 @@ +# +# +# Git Smart HTTP Support (read/write) +# git push, clone and fetch allowed + +SetEnv GIT_PROJECT_ROOT /srv/git +SetEnv GIT_HTTP_EXPORT_ALL 1 + +# Expose git-upload/receive-pack and info/refs. +ScriptAliasMatch "^(/.+/(git-upload-pack|git-receive-pack|info/refs))$" \ + /usr/libexec/git-core/git-http-backend$1 + +# Authenticate against git push. + + AuthType Basic + AuthName "Git Push Access" + AuthUserFile ${GIT_HTTP_AUTH_FILE} + Require valid-user + + +# Only allow git-upload-pack or git-receive-pack services and nothing else. + + AuthType Basic + AuthName "Git Push Access" + AuthUserFile ${GIT_HTTP_AUTH_FILE} + + + # git clone/fetch, no auth + Require expr %{QUERY_STRING} == "service=git-upload-pack" + + # git push, authenticated + + Require expr %{QUERY_STRING} == "service=git-receive-pack" + Require valid-user + + + + +# Allow git clone/fetch w/o auth. + + Require all granted + \ No newline at end of file diff --git a/etc/httpd/conf/httpd.conf b/etc/httpd/conf/httpd.conf index 3b6da71..675241d 100644 --- a/etc/httpd/conf/httpd.conf +++ b/etc/httpd/conf/httpd.conf @@ -16,7 +16,14 @@ LoadModule dir_module modules/mod_dir.so LoadModule rewrite_module modules/mod_rewrite.so LoadModule alias_module modules/mod_alias.so LoadModule mpm_prefork_module modules/mod_mpm_prefork.so - +LoadModule env_module modules/mod_env.so +LoadModule headers_module modules/mod_headers.so +LoadModule expires_module modules/mod_expires.so +# And Basic Auth Modules +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authz_user_module modules/mod_authz_user.so # Load CGI Module LoadModule cgid_module modules/mod_cgid.so @@ -25,41 +32,68 @@ LoadModule mpm_prefork_module modules/mod_mpm_prefork.so LoadModule cgi_module modules/mod_cgi.so -# And Basic Auth Modules -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authz_user_module modules/mod_authz_user.so - # # Server config -Listen 0.0.0.0:80 +Listen 80 ServerName localhost -ServerAdmin root@localhost EnableSendFile on AddDefaultCharset UTF-8 TypesConfig /etc/mime.types MIMEMagicFile conf/magic AddHandler cgi-script .cgi +# +# Log Config +LogLevel warn +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %b" common +ErrorLog "logs/error_log" +ScriptLog logs/git-http-debug.log +CustomLog "logs/access_log" combined + +# +# Git Smart HTTP Support (if enabled) +PassEnv GIT_HTTP_AUTH_FILE +IncludeOptional conf.d/git-http.conf + +# # Always wear protection. - Require all denied + Require all granted -# NOTE: Alias matcher MUST end in /, not /cgit.cgi. It WILL break otherwise. -# ALSO: "cgitrc must have a virtual-root=/". -# Remove /cgit.cgi/ from url paths. -ScriptAlias "/" "/srv/www/htdocs/cgit/cgit.cgi/" - - DirectoryIndex cgit.cgi - AllowOverride None - Options +ExecCGI +FollowSymLinks - SetHandler cgi-script +# +# ALSO: cgitrc must have this: virtual-root=/ +DocumentRoot "/srv/www/htdocs/cgit" + Require all granted + # -Indexes here is not strictly necessary; + # Added for good hygiene + Options +ExecCGI -Indexes + DirectoryIndex cgit.cgi + AllowOverride All + + RewriteEngine On + + # Hard stop: never rewrite Git HTTP requests. + RewriteRule ^.+/(git-upload-pack|git-receive-pack|info/refs)$ - [END] + + # Serve static files directly. + RewriteCond %{REQUEST_FILENAME} -f + RewriteRule ^ - [END] + + # Let cgit handle everything else (and stay off my url). + RewriteRule ^(.*)$ cgit.cgi/$1 [END] + + # Cache static assets + ExpiresActive On + + ExpiresDefault "access plus 30 days" + Header set Cache-Control "public, max-age=2592000, immutable" + -# Deny access to .htaccess/.htpasswd +# Deny access to .htaccess/.htpasswd. Require all denied - \ No newline at end of file + -- cgit v1.2.3-70-g09d2