# SPDX-FileCopyrightText: 2026 Progmasoft # SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 # https://git.progmasoft.com: cgit over read-only mirrors, and clones over smart HTTP with # git-http-backend (upload-pack only). Installed as /etc/nginx/conf.d/git.conf. limit_req_zone $binary_remote_addr zone=git_pages:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=git_clones:10m rate=1r/s; limit_conn_zone $binary_remote_addr zone=git_connections:10m; server { listen 80; listen [::]:80; server_name git.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://git.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name git.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; # Nothing is uploaded here: a clone sends only the list of what it wants. client_max_body_size 1m; limit_conn git_connections 8; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; # cgit writes no scripts; its diffstat bars set their width in style attributes. add_header Content-Security-Policy "default-src 'none'; style-src-elem 'self'; style-src-attr 'unsafe-inline'; img-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always; location ^~ /static/ { alias /srv/git/www/static/; expires 1d; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header Content-Security-Policy "default-src 'none'; style-src 'self'" always; } location = /robots.txt { default_type text/plain; return 200 "User-agent: *\nDisallow: /*/snapshot/\nDisallow: /*/log/\nDisallow: /*/diff/\nDisallow: /*/patch/\n"; } # Pushing is never possible: no receive-pack, and the dumb protocol is off. location ~ ^/[A-Za-z0-9._-]+/git-receive-pack$ { return 403; } location ~ ^/(?[A-Za-z0-9._-]+?)(?:\.git)?/(?info/refs|git-upload-pack)$ { set $git_service ""; if ($git_path = "info/refs") { set $git_service $arg_service; } if ($git_path = "git-upload-pack") { set $git_service "git-upload-pack"; } if ($git_service != "git-upload-pack") { return 403; } limit_req zone=git_clones burst=10 nodelay; fastcgi_pass unix:/run/fcgiwrap/fcgiwrap-cgit.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend; fastcgi_param GIT_PROJECT_ROOT /srv/git/mirrors; # No GIT_HTTP_EXPORT_ALL: only mirrors git-mirror marks with git-daemon-export-ok are served. fastcgi_param PATH_INFO /$git_repo.git/$git_path; fastcgi_param REMOTE_USER ""; # The mirrors belong to gitmirror and are read as cgit; only this backend trusts them. fastcgi_param GIT_CONFIG_COUNT 1; fastcgi_param GIT_CONFIG_KEY_0 safe.directory; fastcgi_param GIT_CONFIG_VALUE_0 /srv/git/mirrors/*; fastcgi_read_timeout 300s; # Nginx buffers the pack (to a file when it is large), so a slow reader never stalls # git-http-backend behind fcgiwrap. fastcgi_buffering on; fastcgi_max_temp_file_size 2048m; } location / { limit_req zone=git_pages burst=40 nodelay; # The menus of cgit (branch, diff options) submit themselves from inline handlers, which the # policy above blocks; their buttons ("switch", "reload") do the same without a script. sub_filter " onchange='this.form.submit();'" ""; sub_filter_once off; fastcgi_pass unix:/run/fcgiwrap/fcgiwrap-cgit.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/cgi-bin/cgit; fastcgi_param CGIT_CONFIG /etc/cgitrc; fastcgi_param PATH_INFO $uri; fastcgi_param QUERY_STRING $args; fastcgi_param HTTP_HOST $server_name; fastcgi_read_timeout 60s; } }