commit 0cf3d0425466c7a76796243d96672214a5d19b28 from: vincent delft date: Sat Feb 21 22:33:46 2026 UTC Via this change the better manage the folder element. Moreover we simplify the usage since the word "/file/" at correct place of the URL is enough to have the cgi script doing his job commit - 34e14143150d6417ef2563bf5c2cd1b8b7406668 commit + 0cf3d0425466c7a76796243d96672214a5d19b28 blob - 26c1f09c6ab6e6c916d3f92e6820f16bdb717ac6 (mode 644) blob + /dev/null --- last_commit_cgi/REAMDE.md +++ /dev/null @@ -1,168 +0,0 @@ -## Always Display the Latest Version of a File from a Got Repository - -When browsing a repository through a web interface, it is common to want a stable link that always shows the most recent version of a specific file. Unfortunately, the default web interface for Game of Trees repositories links files to a precise commit. As a result, each update to the file produces a new URL, which is inconvenient for documentation, external references, or automated integrations. - -For example, a standard link generated by the web interface points to a fixed commit hash, meaning it will never change even if the file is updated later. - -To solve this limitation, we can introduce a small CGI gateway that dynamically resolves the latest commit containing the requested file and then redirects the browser to the correct URL. This article explains how to deploy such a mechanism on an OpenBSD server where the repository daemon and web interface are already installed. - - -## Principle of the Solution - -Instead of linking directly to a commit-specific URL, users will access a stable endpoint such as: - - https:///file/?file=README.md&path=myrepo.git - -The CGI script receives: - -* the file path inside the repository (`file`) -* the repository name (`path`) - -It then determines the latest commit that modified the file and issues an HTTP redirect to the proper web interface URL. The browser ultimately lands on the correct page, while the public link remains constant over time. - - -## Prerequisites - -Before installing the script, ensure that Python 3 is available inside the web server chroot environment. On OpenBSD, CGI programs run in a restricted filesystem, so Python must be copied there explicitly. You can check [my other blog post which explain how to proceed](/post/post_20260217) - - -## Installation of the CGI Script - -Get the script via [my own got repository](https://repo.vincentdelft.be/?action=summary&path=mygot.git) by doing this: - - got clone ssh://anon@repo.vincentdelft.be/mygot - got checkout mygot.git - or - git clone ssh://anon@repo.vincentdelft.be/mygot mygot - cd mygot - -Please edit the script `got_last_commit_file.py` and adapt the BASE_REPO to your specific context. - -You could also see the `DEBUG` variable wich will help you to understand problematic situations. - -Copy the script `got_last_commit_file.py` into the CGI directory and set the proper ownership and permissions so that the web server can execute it: - - cp got_last_commit_file.py /var/www/cgi-bin - chown www:www /var/www/cgi-bin/got_last_commit_file.py - chmod 755 /var/www/cgi-bin/got_last_commit_file.py - - -## Web Server Configuration (nginx) - -In my specific case, I'm using nginx as frontend web server. [httpd](https://man.openbsd.org/httpd) could also surely do the job. - -In my nginx.onf file I add a dedicated location block for the `/file/` endpoint. The complete server configuration below includes both the gotwebd repository interface and the CGI redirect handler for clarity. - - server { - listen 443 ssl; - server_name ; - access_log /var/log/nginx/.access.log; - error_log /var/log/nginx/error.log; - - ssl_certificate ; - ssl_certificate_key ; - ssl_protocols TLSv1.2 TLSv1.3; - - # Main FastCGI handler for the repository interface - location / { - include fastcgi_params; - fastcgi_pass unix:/run/gotweb.sock; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param GATEWAY_INTERFACE CGI/1.1; - fastcgi_read_timeout 300; - } - - # Static assets - location ~ ^/(.*\.(css|png|svg|ico|jpg|gif|js))$ { - alias /htdocs/gotwebd/$1; - expires 30d; - access_log off; - } - - # CGI endpoint returning the latest version of a file - location /file/ { - fastcgi_pass unix:/run/slowcgi.sock; - fastcgi_param SCRIPT_FILENAME /cgi-bin/got_last_commit_file.py; - include fastcgi_params; - } - } - - -## Permissions and Access Control - -It's important to undestand that slowcgi runs strictly as `www:www`. - -Because of that we will a perform some small modifications in our Group so _gotd keep and read/write access tot he repository, but other systmes gotwebd and slowcgi will only have read access. Other user have no access at all. - -Add _gotwebd to the `www` group: - - usermod -G www _gotwebd - -Adjust repository permissions so they remain writable by the daemon but readable by the web layer: - - cd /var/www - chgrp -R www path/to/repos - chmod -R 750 path/to/repos - -At the end the whole repository will be owned by the user _gotd with "rwx" permissions and group "www" with permission "r-x". Other will have nothing "---". This the representation of the permission "750" - - -## Enabling the CGI Service - -Activate the CGI wrapper and start it: - - rcctl enable slowcgi - rcctl start slowcgi - -## Validating the nginx Configuration - -Before reloading the server, verify the configuration: - - nginx -t - -A successful validation should produce output similar to: - - nginx: the configuration file /etc/nginx/nginx.conf syntax is ok - nginx: configuration file /etc/nginx/nginx.conf test is successful - - -## Testing the Setup - -We are now ready for some tests. -You can now request any file from a published repository using the stable endpoint: - - https:///file/?file=testfile.txt&path=myrepo.git - -Where: - -* `path` is the repository name exposed by the web interface -* `file` is the path to the file inside that repository - -Example: - - https://repo.vincentdelft.be/file/?file=README.md&path=zsh.git - -If everything is configured correctly, the request will automatically redirect to the page displaying the latest committed version of the file. - -If you do it via [curl](xx), you will have such result: - - curl "https://repo.vincentdelft.be/file/?file=README.md&path=mypekwm.git" - Redirecting to latest version... - -Which is a success ;) - -## Logging - -This cgi script write logging data into the file: /logs/cgi.log (in the chroot environment for sure). -If the `DEBUG` variable is bigger than 0, you will see more details. -If `DEBUG` is set to 0, you will see in the log the request and the response - - -## Conclusion - -This lightweight CGI approach provides a simple yet powerful improvement to repository browsing. It enables permanent links to files that always reflect the newest content without modifying the repository web interface itself. - -Such stable URLs are especially useful for documentation, configuration management, automation tools, or any scenario where referencing “the current version” of a file is required. - -With minimal configuration and no invasive changes, you gain a clean, reliable mechanism for publishing live repository content. - blob - /dev/null blob + b1d17c038272852c40396058f306228c7ff236b0 (mode 644) --- /dev/null +++ last_commit_cgi/README.md @@ -0,0 +1,184 @@ +## Always Display the Latest Version of a File from a Got Repository + +When browsing a repository through a web interface, it is common to want a stable link that always shows the most recent version of a specific file. Unfortunately, the default web interface for Game of Trees repositories links files to a precise commit. As a result, each update to the file produces a new URL, which is inconvenient for documentation, external references, or automated integrations. + +For example, a standard link generated by the web interface points to a fixed commit hash, meaning it will never change even if the file is updated later. + +To solve this limitation, we can introduce a small CGI gateway that dynamically resolves the latest commit containing the requested file and then redirects the browser to the correct URL. This article explains how to deploy such a mechanism on an OpenBSD server where the repository daemon and web interface are already installed. + + +## Principle of the Solution + +Instead of linking directly to a commit-specific URL, users will access a stable endpoint such as: + + https:///file/?file=README.md&folder=path&path=myrepo.git + +The CGI script receives: + +* the file name inside the repository (`file`) +* the folder path inside the repository (`folder`) +* the repository name (`path`) + +It then determines the latest commit that modified the file and issues an HTTP redirect to the proper web interface URL. The browser ultimately lands on the correct page, while the public link remains constant over time. + +What is interesting with this cgi script, you just insert `/file/` inside the url of a file to have his last commit. + +For example, the reame file associated to this cgi script is located at: + + https://repo.vincentdelft.be/?action=blob&commit=34e14143150d6417ef2563bf5c2cd1b8b7406668&file=REAMDE.md&folder=%2Flast_commit_cgi&path=mygot.git + +By just adding `/file/` at the good place of the url, you will have his last commit. In fact the parameters `file`, `folder` and `path` are taken into account. The others are skipped. + + https://repo.vincentdelft.be/file/?action=blob&commit=34e14143150d6417ef2563bf5c2cd1b8b7406668&file=REAMDE.md&folder=%2Flast_commit_cgi&path=mygot.git + +We could write it like this: + + https://repo.vincentdelft.be/file/?file=REAMDE.md&folder=%2Flast_commit_cgi&path=mygot.git + + +## Prerequisites + +Before installing the script, ensure that Python 3 is available inside the web server chroot environment. On OpenBSD, CGI programs run in a restricted filesystem, so Python must be copied there explicitly. You can check [my other blog post which explain how to proceed](/post/post_20260217) + + +## Installation of the CGI Script + +Get the script via [my own got repository](https://repo.vincentdelft.be/?action=summary&path=mygot.git) by doing this: + + got clone ssh://anon@repo.vincentdelft.be/mygot + got checkout mygot.git + or + git clone ssh://anon@repo.vincentdelft.be/mygot mygot + cd mygot + +Please edit the script `got_last_commit_file.py` and adapt the BASE_REPO to your specific context. + +You could also see the `DEBUG` variable wich will help you to understand problematic situations. + +Copy the script `got_last_commit_file.py` into the CGI directory and set the proper ownership and permissions so that the web server can execute it: + + cp got_last_commit_file.py /var/www/cgi-bin + chown www:www /var/www/cgi-bin/got_last_commit_file.py + chmod 755 /var/www/cgi-bin/got_last_commit_file.py + + +## Web Server Configuration (nginx) + +In my specific case, I'm using nginx as frontend web server. [httpd](https://man.openbsd.org/httpd) could also surely do the job. + +In my nginx.onf file I add a dedicated location block for the `/file/` endpoint. The complete server configuration below includes both the gotwebd repository interface and the CGI redirect handler for clarity. + + server { + listen 443 ssl; + server_name ; + access_log /var/log/nginx/.access.log; + error_log /var/log/nginx/error.log; + + ssl_certificate ; + ssl_certificate_key ; + ssl_protocols TLSv1.2 TLSv1.3; + + # Main FastCGI handler for the repository interface + location / { + include fastcgi_params; + fastcgi_pass unix:/run/gotweb.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param GATEWAY_INTERFACE CGI/1.1; + fastcgi_read_timeout 300; + } + + # Static assets + location ~ ^/(.*\.(css|png|svg|ico|jpg|gif|js))$ { + alias /htdocs/gotwebd/$1; + expires 30d; + access_log off; + } + + # CGI endpoint returning the latest version of a file + location /file/ { + fastcgi_pass unix:/run/slowcgi.sock; + fastcgi_param SCRIPT_FILENAME /cgi-bin/got_last_commit_file.py; + include fastcgi_params; + } + } + + +## Permissions and Access Control + +It's important to undestand that slowcgi runs strictly as `www:www`. + +Because of that we will a perform some small modifications in our Group so _gotd keep and read/write access tot he repository, but other systmes gotwebd and slowcgi will only have read access. Other user have no access at all. + +Add _gotwebd to the `www` group: + + usermod -G www _gotwebd + +Adjust repository permissions so they remain writable by the daemon but readable by the web layer: + + cd /var/www + chgrp -R www path/to/repos + chmod -R 750 path/to/repos + +At the end the whole repository will be owned by the user _gotd with "rwx" permissions and group "www" with permission "r-x". Other will have nothing "---". This the representation of the permission "750" + + +## Enabling the CGI Service + +Activate the CGI wrapper and start it: + + rcctl enable slowcgi + rcctl start slowcgi + +## Validating the nginx Configuration + +Before reloading the server, verify the configuration: + + nginx -t + +A successful validation should produce output similar to: + + nginx: the configuration file /etc/nginx/nginx.conf syntax is ok + nginx: configuration file /etc/nginx/nginx.conf test is successful + + +## Testing the Setup + +We are now ready for some tests. +You can now request any file from a published repository using the stable endpoint: + + https:///file/?file=testfile.txt&path=myrepo.git + +Where: + +* `path` is the repository name exposed by the web interface +* `file` is the file name inside that repository +* `folder` is optional and inform about the path name inside that repository + +Example: + + https://repo.vincentdelft.be/file/?file=README.md&path=zsh.git + +If everything is configured correctly, the request will automatically redirect to the page displaying the latest committed version of the file. + +If you do it via [curl](xx), you will have such result: + + curl "https://repo.vincentdelft.be/file/?file=README.md&path=mypekwm.git" + Redirecting to latest version... + +Which is a success ;) + +## Logging + +This cgi script write logging data into the file: /logs/cgi.log (in the chroot environment for sure). +If the `DEBUG` variable is bigger than 0, you will see more details. +If `DEBUG` is set to 0, you will see in the log the request and the response + + +## Conclusion + +This lightweight CGI approach provides a simple yet powerful improvement to repository browsing. It enables permanent links to files that always reflect the newest content without modifying the repository web interface itself. + +Such stable URLs are especially useful for documentation, configuration management, automation tools, or any scenario where referencing “the current version” of a file is required. + +With minimal configuration and no invasive changes, you gain a clean, reliable mechanism for publishing live repository content. + blob - 244a84f4c373ea11c1540417110e0b739f73276a blob + 8fb2e93282d5e269102372a1d884c3ced11452c4 --- last_commit_cgi/got_last_commit_file.py +++ last_commit_cgi/got_last_commit_file.py @@ -1,7 +1,7 @@ #!/usr/local/bin/python3 """ CGI script to redirect to the latest version of a file in gotwebd -URL pattern: /file/?path=&file= +URL pattern: /file/?path=&file=&folder= """ @@ -43,10 +43,10 @@ def get_query_params(): # Get first value for each param (parse_qs returns lists) repo = params.get('path', [''])[0] - file_path = params.get('file', [''])[0] + file = params.get('file', [''])[0] + folder = params.get('folder', [''])[0] - #return unquote(repo), unquote(file_path) - return repo, file_path + return repo, file, folder def get_latest_commit(repo_path): """Get the latest commit hash from a got repository""" @@ -82,7 +82,7 @@ def main(): env = os.environ log(0,f"{time.ctime()} - {env.get('REMOTE_ADDR','NA')} - {env.get('HTTP_USER_AGENT','NA')} - {env.get('REQUEST_METHOD','NA')} {env.get('SERVER_NAME','NA')}/?{env.get('QUERY_STRING','NA')}") # Parse parameters - repo, file_path = get_query_params() + repo, file, folder = get_query_params() log(2,'env: %s' % (os.environ)) log(3,'uid: %s' % (os.getuid())) log(3,'gid: %s' % (os.getgroups())) @@ -91,10 +91,10 @@ def main(): log(1,'listdir %s: %s' % (REPO_BASE, os.listdir(REPO_BASE))) except Exception as e: log(0,f"An error occurred: {e}") - log(1,'repo, file_path:%s %s' % (repo, file_path)) + log(1,'repo, folder, file:%s %s %s' % (repo, folder, file)) # Validate inputs - if not repo or not file_path: + if not repo or not file: log(0, '%s - reply 400 - ERROR:missing path and/or file parameters' % (time.ctime())) send_response( "400 Bad Request", @@ -131,21 +131,17 @@ def main(): return # Build gotwebd URL - folder, filename = os.path.split(file_path) - if folder in ("/"): - folder = "" params = { "action": "blobraw", "commit": commit, "folder": folder, - "file": filename, + "file": file, "path": repo } log(1, 'params:%s' % (params)) gotwebd_url = "/?" + urlencode(params) log(0, '%s - reply 302 - %s' % (time.ctime(), gotwebd_url)) - #/?action=blobraw&commit={commit}&folder=&file={file_path}&path={repo}" - + # Send redirect send_response( "302 Found",