Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/ops/git/nginx/git.conf
blob: 71c5b754a7fae61beb4caee7a5506f0b10de5e31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
# 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 ~ ^/(?<git_repo>[A-Za-z0-9._-]+?)(?:\.git)?/(?<git_path>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 "" "";
        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;
    }
}