summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRATDAD <lambda@disroot.org>2025-12-13 21:00:08 -0500
committerRATDAD <lambda@disroot.org>2025-12-13 21:00:08 -0500
commit00dffa8cf5cac8322cb47f2222e424d3960e7939 (patch)
tree7db0460694037b063f97058ecb9eb664b6162b45
parentde8d06726cae205ead43f8b1ac07ecc59a07363b (diff)
downloadcgit-docker-00dffa8cf5cac8322cb47f2222e424d3960e7939.tar.gz
cgit-docker-00dffa8cf5cac8322cb47f2222e424d3960e7939.tar.bz2
cgit-docker-00dffa8cf5cac8322cb47f2222e424d3960e7939.zip
Added Git Smart HTTP Support
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile61
-rw-r--r--README.md16
-rw-r--r--cgit.conf2
-rw-r--r--compose.yml13
-rw-r--r--entrypoint.sh95
-rw-r--r--etc/cgitrc14
-rw-r--r--etc/httpd/conf.d/git-http-apcf.conf27
-rw-r--r--etc/httpd/conf.d/git-http-cf.conf26
-rw-r--r--etc/httpd/conf.d/git-http-p.conf35
-rw-r--r--etc/httpd/conf.d/git-http-pcf.conf42
-rw-r--r--etc/httpd/conf/httpd.conf76
12 files changed, 357 insertions, 52 deletions
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 <lambda@disroot.org>"
+# 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 <lambda@disroot.org>"
-# 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 <<EOF
+AuthType Basic
+AuthName "CGit"
+AuthUserFile /srv/www/htdocs/cgit/.htpasswd
+Require valid-user
+EOF
+
+ if [ ! -f /srv/www/htdocs/cgit/.htpasswd ]; then
+ htpasswd -c -b /srv/www/htdocs/cgit/.htpasswd \
+ "$HTTP_AUTH_USER" "$HTTP_AUTH_PASSWORD"
+ else
+ htpasswd -b /srv/www/htdocs/cgit/.htpasswd \
+ "$HTTP_AUTH_USER" "$HTTP_AUTH_PASSWORD"
+ fi
+
+ # Ensure correct permissions for .htpasswd
+ chown root:apache /srv/www/htdocs/cgit/.htpasswd
+ chmod 640 /srv/www/htdocs/cgit/.htpasswd
+fi
+
+exec /usr/sbin/httpd -DFOREGROUND
diff --git a/etc/cgitrc b/etc/cgitrc
index 6d04348..c47986c 100644
--- a/etc/cgitrc
+++ b/etc/cgitrc
@@ -9,13 +9,20 @@ robots=noindex, nofollow
snapshots=tar.gz tar.bz2 zip
source-filter=/opt/highlight.sh
+# Assets
+css=/cgit.css
+logo=/cgit.png
+favicon=/favicon.ico
+
#
-# Site Settings
+# Behavior Settings
enable-index-links=1
enable-remote-branches=1
enable-log-filecount=1
enable-log-linecount=1
+enable-http-clone=1
enable-git-config=1
+remove-suffix=1
#
# Cache Settings
@@ -23,8 +30,7 @@ cache-root=/var/cache/cgit
cache-size=1000
#
-# List of common mimetypes
-#
+# Mimetypes
mimetype.gif=image/gif
mimetype.html=text/html
mimetype.jpg=image/jpeg
@@ -65,6 +71,6 @@ readme=:install.txt
readme=:INSTALL
readme=:install
-# NOTE: According to the cgit mailing list, these options should go last.
+# Don't change.
virtual-root=/
scan-path=/srv/git \ No newline at end of file
diff --git a/etc/httpd/conf.d/git-http-apcf.conf b/etc/httpd/conf.d/git-http-apcf.conf
new file mode 100644
index 0000000..d781aea
--- /dev/null
+++ b/etc/httpd/conf.d/git-http-apcf.conf
@@ -0,0 +1,27 @@
+#
+#
+# Git Smart HTTP Support (authenticated read/write)
+# git push, clone, fetch — ALL authenticated
+
+SetEnv GIT_PROJECT_ROOT /srv/git
+SetEnv GIT_HTTP_EXPORT_ALL 1
+
+# Expose all of the endpoints.
+ScriptAliasMatch "^(/.+/(git-upload-pack|git-receive-pack|info/refs))$" \
+ /usr/libexec/git-core/git-http-backend$1
+
+# Globally authenticate for each endpoint.
+<LocationMatch "^/.+/(git-upload-pack|git-receive-pack|info/refs)$">
+ AuthType Basic
+ AuthName "Git Access"
+ AuthUserFile ${GIT_HTTP_AUTH_FILE}
+ Require valid-user
+</LocationMatch>
+
+# info/refs strictly scoped
+<LocationMatch "^/.+/info/refs$">
+ <RequireAny>
+ Require expr %{QUERY_STRING} == "service=git-upload-pack"
+ Require expr %{QUERY_STRING} == "service=git-receive-pack"
+ </RequireAny>
+</LocationMatch>
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.
+<LocationMatch "^/.+/git-upload-pack$">
+ Require all granted
+</LocationMatch>
+
+# Only allow info/refs for git clone/fetch.
+<LocationMatch "^/.+/info/refs$">
+ Require expr %{QUERY_STRING} == "service=git-upload-pack"
+</LocationMatch>
+
+# Explicitly deny git push just in case.
+<LocationMatch "^/.+/git-receive-pack$">
+ Require all denied
+</LocationMatch>
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.
+<LocationMatch "^/.+/(git-receive-pack)$">
+ AuthType Basic
+ AuthName "Git Push Access"
+ AuthUserFile ${GIT_HTTP_AUTH_FILE}
+ Require valid-user
+</LocationMatch>
+
+# Deny info/refs push unless it's for git push.
+<LocationMatch "^/.+/info/refs$">
+ AuthType Basic
+ AuthName "Git Push Access"
+ AuthUserFile ${GIT_HTTP_AUTH_FILE}
+ <RequireAll>
+ Require expr %{QUERY_STRING} == "service=git-receive-pack"
+ Require valid-user
+ </RequireAll>
+</LocationMatch>
+
+# Explicitly deny git clone/fetch just in case.
+<LocationMatch "^/.+/git-upload-pack$">
+ Require all denied
+</LocationMatch>
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.
+<LocationMatch "^/.+/git-receive-pack$">
+ AuthType Basic
+ AuthName "Git Push Access"
+ AuthUserFile ${GIT_HTTP_AUTH_FILE}
+ Require valid-user
+</LocationMatch>
+
+# Only allow git-upload-pack or git-receive-pack services and nothing else.
+<LocationMatch "^/.+/info/refs$">
+ AuthType Basic
+ AuthName "Git Push Access"
+ AuthUserFile ${GIT_HTTP_AUTH_FILE}
+
+ <RequireAny>
+ # git clone/fetch, no auth
+ Require expr %{QUERY_STRING} == "service=git-upload-pack"
+
+ # git push, authenticated
+ <RequireAll>
+ Require expr %{QUERY_STRING} == "service=git-receive-pack"
+ Require valid-user
+ </RequireAll>
+ </RequireAny>
+</LocationMatch>
+
+# Allow git clone/fetch w/o auth.
+<LocationMatch "^/.+/git-upload-pack$">
+ Require all granted
+</LocationMatch> \ 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
<IfModule !mpm_prefork_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
</IfModule>
-# 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.
<Directory />
- Require all denied
+ Require all granted
</Directory>
-# 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/"
-<Directory "/srv/www/htdocs/cgit/">
- DirectoryIndex cgit.cgi
- AllowOverride None
- Options +ExecCGI +FollowSymLinks
- SetHandler cgi-script
+#
+# ALSO: cgitrc must have this: virtual-root=/
+DocumentRoot "/srv/www/htdocs/cgit"
+<Directory "/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
+ <FilesMatch "\.(css|js|png|ico)$">
+ ExpiresDefault "access plus 30 days"
+ Header set Cache-Control "public, max-age=2592000, immutable"
+ </FilesMatch>
</Directory>
-# Deny access to .htaccess/.htpasswd
+# Deny access to .htaccess/.htpasswd.
<Files ".ht">
Require all denied
-</Files> \ No newline at end of file
+</Files>