# lighttpd configuration for cgit. # # This is a complete lighttpd.conf rather than a snippet for conf-enabled, so # nothing here is included from elsewhere and no distro base config is # assumed. Check it and run it with # lighttpd -tt -f /path/to/lighttpd.conf # check the syntax and modules # lighttpd -D -f /path/to/lighttpd.conf # run it in the foreground # # lighttpd runs the cgit.cgi binary directly through mod_cgi, so no FastCGI # bridge is needed. This is cgit's classic reference deployment, mod_cgi with # mod_alias and mod_setenv. # # Paths assumed below, edit them to match your install. # cgit CGI binary /usr/lib/cgit/cgit.cgi # static assets /usr/share/cgit (cgit.css cgit.js cgit.png favicon.ico robots.txt) # cgit config /etc/cgitrc # public URL https://git.example.org/ (cgit at the domain root) # # cgit is one CGI executable. It learns the repository and the page from # PATH_INFO and reads page options such as h= and id= from QUERY_STRING. cgit # builds its own link base from SCRIPT_NAME. The five static assets are served # straight off disk and must never be routed through cgit. # A plain assignment rather than "+=", since this config stands on its own and # there is no distro base list to append to. mod_alias maps URL paths onto # files, mod_setenv injects CGIT_CONFIG and the response headers, and mod_cgi # runs cgit.cgi. Adding mod_accesslog here is what the access log below needs. server.modules = ( "mod_alias", "mod_setenv", "mod_cgi", "mod_accesslog", ) server.port = 80 server.username = "http" # Debian and Ubuntu use www-data server.groupname = "http" server.document-root = "/usr/share/cgit" # a valid docroot must exist, the # alias rules below do the routing server.pid-file = "/run/lighttpd.pid" server.errorlog = "/var/log/lighttpd/error.log" accesslog.filename = "/var/log/lighttpd/access.log" # Drop the version number from the Server header and from error pages. server.tag = "lighttpd" # The only request body cgit ever reads is the auth-filter login form, and it # stops after 4096 bytes. Nothing else here accepts an upload. The value is in # kilobytes and the default of 0 means unlimited. server.max-request-size = 64 # mod_alias serves the assets off disk, so lighttpd must know their content # types. Without this the stylesheet is sent as application/octet-stream and # the browser ignores it. Only these five files are served off disk, so this # short table is the whole of it and no external mime file is needed. mimetype.assign = ( ".css" => "text/css", ".js" => "text/javascript", ".png" => "image/png", ".ico" => "image/vnd.microsoft.icon", ".txt" => "text/plain", ) # The vhost, as a top-level conditional so it matches on both the port 80 # socket and the optional TLS socket at the end of this file. $HTTP["host"] == "git.example.org" { # Which config cgit reads. It falls back to the compiled-in /etc/cgitrc, # the same path used here, but setting it makes the location explicit. setenv.add-environment = ( "CGIT_CONFIG" => "/etc/cgitrc" ) # Site-wide security headers are set here so they also cover the static # assets lighttpd serves. cgit itself sends only the headers the server # cannot supply. Those are Status, Content-Type, Content-Length and # Content-Disposition on downloads, a no-store Cache-Control on # unauthenticated responses, the auth filter's Set-Cookie, and on raw # repository bytes a nosniff of its own next to the stricter policy # "default-src 'none'". Everything else, this policy included, is the # server's job. # # The add in add-response-header is load bearing. It appends a second # copy next to what cgit emitted, so a raw page carries both policies and # the browser enforces the stricter one, while the doubled nosniff line # is harmless. The set-response-header directive would instead replace # what cgit sent, swapping the strict policy on raw repository content # for this looser site one. Never switch add to set. # # script-src stays self because cgit loads only its own cgit.js, and # style-src allows inline for the diffstat bars. form-action self covers # the login form, the only form cgit renders. If you enable the gravatar # or libravatar avatar filter, add its host to img-src. # # Permissions-Policy refuses the browser features a git viewer never asks # for, camera and location among them, for everything served here, # repository files included. The opener policy cuts any window.opener # link between cgit and pages that open it, and the resource policy stops # other origins from embedding what cgit serves, a hotlinked raw file for # example. git clients are not browsers and ignore both, so clone and # snapshot downloads keep working. The stricter # Cross-Origin-Embedder-Policy is deliberately absent because it would # break the avatar filters mentioned above. setenv.add-response-header = ( "Content-Security-Policy" => "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'", "X-Content-Type-Options" => "nosniff", "Referrer-Policy" => "no-referrer", "Permissions-Policy" => "accelerometer=(), autoplay=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), usb=()", "Cross-Origin-Opener-Policy" => "same-origin", "Cross-Origin-Resource-Policy" => "same-origin" ) # Two years of forced HTTPS. Enable this together with the TLS socket at # the end of this file and only once you serve HTTPS exclusively, since # it is hard to undo once browsers have seen it. #setenv.add-response-header += ( "Strict-Transport-Security" => "max-age=63072000; includeSubDomains" ) # Register the cgit binary as a CGI program. The key cgit.cgi matches the # binary's name and the empty value means the file is itself the program, # with no interpreter in front of it. This is what makes lighttpd split # the trailing path off as PATH_INFO, so never drop it. cgi.assign = ( "cgit.cgi" => "" ) # Routing. lighttpd's alias.url is first-match in declaration order, not # longest prefix, so the five static entries must come before the / entry. # If / came first it would swallow every request and recent lighttpd # refuses to start. The static entries are served off disk and the / entry # hands everything else to cgit. # # The trailing slash on cgit.cgi/ is load bearing. lighttpd builds the # physical path by stripping the matched key off the front of the URL and # appending the rest to the value. For the key / the remainder carries no # leading slash, so without the trailing slash a request for # /linux/tree/kernel/sched.c glues onto the binary name as # /usr/lib/cgit/cgit.cgilinux/tree/... and 404s. The trailing slash # restores the separator, giving cgit SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi # and PATH_INFO /linux/tree/kernel/sched.c. alias.url = ( "/cgit.css" => "/usr/share/cgit/cgit.css", "/cgit.js" => "/usr/share/cgit/cgit.js", "/cgit.png" => "/usr/share/cgit/cgit.png", "/favicon.ico" => "/usr/share/cgit/favicon.ico", "/robots.txt" => "/usr/share/cgit/robots.txt", "/" => "/usr/lib/cgit/cgit.cgi/", ) # Served at / the SCRIPT_NAME is empty and cgit derives its link base # correctly. For a sub-path install use a key without a trailing slash # mapped to the binary without a trailing slash, for example # "/git" => "/usr/lib/cgit/cgit.cgi" # so /git/linux/tree resolves to /usr/lib/cgit/cgit.cgi/linux/tree, giving # SCRIPT_NAME /git and PATH_INFO /linux/tree. cgit auto-detects the prefix. # If links come out wrong, pin it in cgitrc with virtual-root=/git. } # Uncomment this whole block to enable TLS on 443. The host block above is # socket independent, so it serves cgit over this socket too once the crypto # is set. #server.modules += ( "mod_openssl" ) # #$SERVER["socket"] == ":443" { # ssl.engine = "enable" # ssl.pemfile = "/etc/lighttpd/certs/git.example.org.crt" # ssl.privkey = "/etc/lighttpd/certs/git.example.org.key" # ssl.ca-file = "/etc/lighttpd/certs/git.example.org.chain.pem" # ssl.openssl.ssl-conf-cmd = ( "MinProtocol" => "TLSv1.2" ) #} # # Redirect plain HTTP to HTTPS, scoped to the port 80 socket. Needs # mod_redirect. #server.modules += ( "mod_redirect" ) #$SERVER["socket"] == ":80" { # $HTTP["host"] == "git.example.org" { # url.redirect = ( "^/(.*)" => "https://git.example.org/$1" ) # } #}