I’ve been using cgit for hosting my Git repositories since about the end of 2018. One minor thing that annoys me about cgit is that the landing page for repositories is not the about page (which shows the readme), but the summary page (which shows the last commits and repository activity). This is a useful default if you are visiting the page of a project you already know (so you can see what’s new), but not so much for someone casually browsing your repos.
There were (at least) two ways I could solve this:
I could change the cgit source code to use the about page as a default. This would require figuring out all parts to change, and keeping my own patched version of cgit. Or…
I could come up with a pile of nginx rules to map the external URLs I want (e.g., /code/<repo-name>/
) into cgit’s own URLs (e.g., /cgit/<repo-name>/about/
).
I went with the second option.
Things are not so simple, however: even if I map my external URLs into cgit’s internal ones, cgit will still generate links pointing to its own version of the URLs. So the evil masterplan is to have two root locations:
/code/
, which exposes the URLs the way I want them, translates them to cgit’s URLs, and passes the translated versions to cgit;/cgit/
, which redirects cgit-style URLs to the corresponding /code/
URLs. This way, cgit can still generate links to its own version of the URLs, and they will get translated back to the external URLs when the user follows the links.In the rest of this post, I will go through the nginx rules I used. You can find them here if you would like to see them all at once.
We will need four different rules for the /code
location. All of them involve passing a translated URL to cgit, which involves setting up a bunch of FastCGI variables, most of which are identical in all rules. So let’s start by creating a file in /etc/nginx/snippets/fastcgi-cgit.conf
which we can reuse in all rules:
fastcgi_pass unix:/run/fcgiwrap.socket; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # Tell fcgiwrap about the binary we’d like to execute and cgit about # the path we’d like to access. fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; fastcgi_param DOCUMENT_ROOT /usr/lib/cgit;
These are standard CGI parameters; the only thing specific to cgit here is the SCRIPT_FILENAME
and DOCUMENT_ROOT
variables. Change those according to where you have cgit installed in your system.
/code/
rulesNow come the interesting rules. These go in the server { ... }
nginx section for your website (likely in /etc/nginx/sites-enabled/site-name
, if you are using a Debian derivative). Let’s begin by the rule for the landing page: we want /code/<repo-name>/
to map to cgit’s /<repo-name>/about/
:
location ~ ^/code/([^/]+)/?$ { include snippets/fastcgi-cgit.conf; fastcgi_param SCRIPT_NAME /cgit; fastcgi_param PATH_INFO $1/about/; }
The location
line matches any URL beginning with /code/
, followed by one or more characters other than /
, followed by an optional /
. So it matches /code/foo/
, but not /code/foo/bar
(because foo/bar
has a /
, i.e., it is not “one or more characters other than /
”). The foo
part (i.e., the repo name) will be accessible as $1
inside the rule (because that part of the URL string is captured by the parentheses in the regex).
Inside the rule, we include the snippet we defined before, and then we set two variables: SCRIPT_NAME
, which is the base URL cgit will use for its own links; and PATH_INFO
, which tells cgit which page we want (i.e., the <repo-name>/about
page). Note that the base URL we pass to cgit is not /code
, but /cgit
, so cgit will generate links to URLs like /cgit/<repo-name>/about/
. This is important because later on we will define rules to redirect /cgit
URLs to their corresponding /code
URLs.
The second rule we want is to expose the summary page as /code/<repo-name>/summary/
, which will map to cgit’s repo landing page:
location ~ ^/code/([^/]+)/summary/$ { include snippets/fastcgi-cgit.conf; fastcgi_param SCRIPT_NAME /cgit; fastcgi_param PATH_INFO $1/; }
Again, the principle is the same: we match /code/foo/summary/
, extract the foo
part, and pass a modified URL to cgit. In this case, we just pass foo/
without the summary
, since cgit’s repo landing page is the summary.
The third rule is a catch-all rule for all the other URLs that don’t require translation:
location ~ ^/code/(.*) { include snippets/fastcgi-cgit.conf; fastcgi_param SCRIPT_NAME /cgit; fastcgi_param PATH_INFO $1; }
That is, /code/
followed by anything else not matched by the previous rules is passed as is (removing the /code/
part) to cgit.
/cgit/
rulesNow we need to do the mapping in reverse: we want cgit’s links (e.g., /cgit/<repo-name>/about
) to redirect to our external version of them (e.g., /code/<repo-name>/
). These rules are straightforward: for each of the translation rules we created in the previous session, we add a corresponding redirect here.
location ~ ^/cgit/([^/]+)/about/$ { return 302 /code/$1/; } location ~ ^/cgit/([^/]+)/?$ { return 302 /code/$1/summary/; } location ~ ^/cgit/(.*)$ { return 302 /code/$1$is_args$args; }
[Update (2020-11-05): The last rule must have a $is_args$args
at the end, so that query parameters are passed on in the redirect.]
This set of rules will already work if all you want is to expose cgit’s URLs in a different form. But there is one thing missing: if we go to the cgit initial page (the repository list), all the links to repositories will be of the form /cgit/<repo-name>/
, which our rules will translate to /code/<repo-name>/summary/
. But we don’t want that! We want the links in the repository list to lead to the repo about page (i.e., /code/<repo-name>/
, not /cgit/<repo-name>/
). So what do we do now?
The solution is to pass a different base URL to cgit just for the initial page. So we add a zeroth rule (it has to come before all other /code/
rules so it matches first):
location ~ ^/code/$ { include snippets/fastcgi-cgit.conf; fastcgi_param SCRIPT_NAME /code; fastcgi_param PATH_INFO /; }
The difference between these and the other rules is that we pass SCRIPT_NAME
with the value of /code
instead of /cgit
, so that in the initial page, the links are of the form /code/<repo-name>/
instead of /cgit/<repo-name>/
, which means they will render cgit’s /<repo-name>/about/
page instead of /<repo-name>/
.
Beautiful, huh?
One thing you have to ensure with these rules is that every repo has an about page; cgit only generates about pages for repos with a README, so your links will break if your repo doesn’t have one. One solution for this is to create a default README which cgit will use if the repo does not have a README itself. For this, I have the following settings in my /etc/cgitrc
:
# Use the repo readme if available. readme=:README.md # Default README file. Make sure to put this file in a folder of its own, # because all files in the folder become accessible via cgit. readme=/home/elmord/cgit-default-readme/README.md
That’s all I have for today, folks. If you have comments, feel free to, well, leave a comment.
thank you so much for this!! i would've been wrestling with things for hours if i hadn't found this
@boringcactus You're welcome! I'm having a look at your blog and it's quite interesting, by the way :)
cool :>
Copyright © 2010-2023 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.
Powered by Blognir.
Robert Goodall, 2021-02-23 06:42:29 +0000 #
Great write up! I appreciate the greater detail around your thought process and choice of regular expressions.