summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@hornquist.se>2021-06-29 18:41:59 +0200
committerHugo Hörnquist <hugo@hornquist.se>2021-06-29 18:41:59 +0200
commit33019276818542ac0513d60228ff48b192c5067f (patch)
treeff48b872460e8732fa64111ac0f58ef1998fc5b2
downloadcgit-33019276818542ac0513d60228ff48b192c5067f.tar.gz
cgit-33019276818542ac0513d60228ff48b192c5067f.tar.xz
Initial commit.
-rw-r--r--files/filters/hugo-authentication.lua331
-rwxr-xr-xfiles/filters/hugo-highlighting.sh138
-rwxr-xr-xfiles/filters/hugo-pre.sh15
-rw-r--r--files/logo.pngbin0 -> 16244 bytes
-rw-r--r--files/logo_large.pngbin0 -> 105311 bytes
-rw-r--r--files/root_readme17
-rw-r--r--manifests/init.pp62
-rw-r--r--templates/cgitrc.epp64
8 files changed, 627 insertions, 0 deletions
diff --git a/files/filters/hugo-authentication.lua b/files/filters/hugo-authentication.lua
new file mode 100644
index 0000000..df97579
--- /dev/null
+++ b/files/filters/hugo-authentication.lua
@@ -0,0 +1,331 @@
+-- This script may be used with the auth-filter. Be sure to configure it as you wish.
+--
+-- Requirements:
+-- luaossl
+-- <http://25thandclement.com/~william/projects/luaossl.html>
+-- luaposix
+-- <https://github.com/luaposix/luaposix>
+--
+local sysstat = require("posix.sys.stat")
+local unistd = require("posix.unistd")
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
+
+--
+--
+-- Configure these variables for your settings.
+--
+--
+
+-- A list of password protected repositories along with the users who can access them.
+local protected_repos = {
+ glouglou = { laurent = true, jason = true },
+ qt = { jason = true, bob = true }
+}
+
+local public_repos = {}
+public_repos["wiki-public" ] = true
+public_repos["lyslib" ] = true
+public_repos["calp" ] = true
+public_repos["texttv" ] = true
+public_repos["scheme-monad" ] = true
+public_repos["scheme/math-parse" ] = true
+public_repos["file-descriptor-graph"] = true
+
+-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
+local users = {
+ hugo = "$6$13z1Pf.U8itrCRX6$g2BZpaMk1CLiT6117paWXB2qdQFRc3rsGWL4iF5h5QbHo27oljTdHk69oQWAvqlVf13aLTUF3nYw65lEp88r/1"
+}
+
+-- Set this to a path this script can write to for storing a persistent
+-- cookie secret, which should be guarded.
+local secret_filename = "/var/cache/cgit/auth-secret"
+
+--
+--
+-- Authentication functions follow below. Swap these out if you want different authentication semantics.
+--
+--
+
+-- Sets HTTP cookie headers based on post and sets up redirection.
+function authenticate_post()
+ local hash = users[post["username"]]
+ local redirect = validate_value("redirect", post["redirect"])
+
+ if redirect == nil then
+ not_found()
+ return 0
+ end
+
+ redirect_to(redirect)
+
+ if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
+ set_cookie("cgitauth", "")
+ else
+ -- One week expiration time
+ local username = secure_value("username", post["username"], os.time() + 604800)
+ set_cookie("cgitauth", username)
+ end
+
+ html("\n")
+ return 0
+end
+
+
+-- Returns 1 if the cookie is valid and 0 if it is not.
+function authenticate_cookie()
+
+ -- Everyone has access to the index page.
+ -- printenv(os.getenv("CGIT_REPO_NAME"));
+ if cgit["repo"] == "" then
+ return 1
+ end
+
+ ispublic = public_repos[cgit["repo"]]
+ -- accepted_users = protected_repos[cgit["repo"]]
+ if ispublic == true then
+ -- We return as valid if the repo is public
+ return 1
+ end
+
+ local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
+ if username == nil then
+ return 0
+ else
+ return 1
+ end
+end
+
+-- Prints the html for the login form.
+function body()
+ html("<h2>Authentication Required</h2>")
+ -- html("HTML = ")
+ -- html(tostring(cgit["repo"] == ""))
+ html("<form method='post' action='")
+ html_attr(cgit["login"])
+ html("'>")
+ html("<input type='hidden' name='redirect' value='")
+ html_attr(secure_value("redirect", cgit["url"], 0))
+ html("' />")
+ html("<table>")
+ html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
+ html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
+ html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
+ html("</table></form>")
+
+ return 0
+end
+
+
+
+--
+--
+-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
+--
+--
+
+local actions = {}
+actions["authenticate-post"] = authenticate_post
+actions["authenticate-cookie"] = authenticate_cookie
+actions["body"] = body
+
+function filter_open(...)
+ action = actions[select(1, ...)]
+
+ http = {}
+ http["cookie"] = select(2, ...)
+ http["method"] = select(3, ...)
+ http["query"] = select(4, ...)
+ http["referer"] = select(5, ...)
+ http["path"] = select(6, ...)
+ http["host"] = select(7, ...)
+ http["https"] = select(8, ...)
+
+ cgit = {}
+ cgit["repo"] = select(9, ...)
+ cgit["page"] = select(10, ...)
+ cgit["url"] = select(11, ...)
+ cgit["login"] = select(12, ...)
+
+end
+
+function filter_close()
+ return action()
+end
+
+function filter_write(str)
+ post = parse_qs(str)
+end
+
+
+--
+--
+-- Utility functions based on keplerproject/wsapi.
+--
+--
+
+function url_decode(str)
+ if not str then
+ return ""
+ end
+ str = string.gsub(str, "+", " ")
+ str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
+ str = string.gsub(str, "\r\n", "\n")
+ return str
+end
+
+function url_encode(str)
+ if not str then
+ return ""
+ end
+ str = string.gsub(str, "\n", "\r\n")
+ str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
+ str = string.gsub(str, " ", "+")
+ return str
+end
+
+function parse_qs(qs)
+ local tab = {}
+ for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
+ tab[url_decode(key)] = url_decode(val)
+ end
+ return tab
+end
+
+function get_cookie(cookies, name)
+ cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
+ return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
+end
+
+function tohex(b)
+ local x = ""
+ for i = 1, #b do
+ x = x .. string.format("%.2x", string.byte(b, i))
+ end
+ return x
+end
+
+--
+--
+-- Cookie construction and validation helpers.
+--
+--
+
+local secret = nil
+
+-- Loads a secret from a file, creates a secret, or returns one from memory.
+function get_secret()
+ if secret ~= nil then
+ return secret
+ end
+ local secret_file = io.open(secret_filename, "r")
+ if secret_file == nil then
+ local old_umask = sysstat.umask(63)
+ local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
+ local temporary_file = io.open(temporary_filename, "w")
+ if temporary_file == nil then
+ os.exit(177)
+ end
+ temporary_file:write(tohex(rand.bytes(32)))
+ temporary_file:close()
+ unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
+ unistd.unlink(temporary_filename)
+ sysstat.umask(old_umask)
+ secret_file = io.open(secret_filename, "r")
+ end
+ if secret_file == nil then
+ os.exit(177)
+ end
+ secret = secret_file:read()
+ secret_file:close()
+ if secret:len() ~= 64 then
+ os.exit(177)
+ end
+ return secret
+end
+
+-- Returns value of cookie if cookie is valid. Otherwise returns nil.
+function validate_value(expected_field, cookie)
+ local i = 0
+ local value = ""
+ local field = ""
+ local expiration = 0
+ local salt = ""
+ local chmac = ""
+
+ if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
+ return nil
+ end
+
+ for component in string.gmatch(cookie, "[^|]+") do
+ if i == 0 then
+ field = component
+ elseif i == 1 then
+ value = component
+ elseif i == 2 then
+ expiration = tonumber(component)
+ if expiration == nil then
+ expiration = -1
+ end
+ elseif i == 3 then
+ salt = component
+ elseif i == 4 then
+ chmac = component
+ else
+ break
+ end
+ i = i + 1
+ end
+
+ if chmac == nil or chmac:len() == 0 then
+ return nil
+ end
+
+ -- Lua hashes strings, so these comparisons are time invariant.
+ if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
+ return nil
+ end
+
+ if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
+ return nil
+ end
+
+ if url_decode(field) ~= expected_field then
+ return nil
+ end
+
+ return url_decode(value)
+end
+
+function secure_value(field, value, expiration)
+ if value == nil or value:len() <= 0 then
+ return ""
+ end
+
+ local authstr = ""
+ local salt = tohex(rand.bytes(16))
+ value = url_encode(value)
+ field = url_encode(field)
+ authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
+ authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
+ return authstr
+end
+
+function set_cookie(cookie, value)
+ html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
+ if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
+ html("; secure")
+ end
+ html("\n")
+end
+
+function redirect_to(url)
+ html("Status: 302 Redirect\n")
+ html("Cache-Control: no-cache, no-store\n")
+ html("Location: " .. url .. "\n")
+end
+
+function not_found()
+ html("Status: 404 Not Found\n")
+ html("Cache-Control: no-cache, no-store\n\n")
+end
diff --git a/files/filters/hugo-highlighting.sh b/files/filters/hugo-highlighting.sh
new file mode 100755
index 0000000..603b8ab
--- /dev/null
+++ b/files/filters/hugo-highlighting.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+# This script can be used to implement syntax highlighting in the cgit
+# tree-view by refering to this file with the source-filter or repo.source-
+# filter options in cgitrc.
+#
+# This script requires a shell supporting the ${var##pattern} syntax.
+# It is supported by at least dash and bash, however busybox environments
+# might have to use an external call to sed instead.
+#
+# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
+# highlighting, so you'll probably want something like the following included
+# in your css file:
+#
+# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
+#
+# table.blob .num { color:#2928ff; }
+# table.blob .esc { color:#ff00ff; }
+# table.blob .str { color:#ff0000; }
+# table.blob .dstr { color:#818100; }
+# table.blob .slc { color:#838183; font-style:italic; }
+# table.blob .com { color:#838183; font-style:italic; }
+# table.blob .dir { color:#008200; }
+# table.blob .sym { color:#000000; }
+# table.blob .kwa { color:#000000; font-weight:bold; }
+# table.blob .kwb { color:#830000; }
+# table.blob .kwc { color:#000000; font-weight:bold; }
+# table.blob .kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/
+#
+# body.hl { background-color:#ffffff; }
+# pre.hl { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';}
+# .hl.num { color:#2928ff; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#ff0000; }
+# .hl.dstr { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.dir { color:#008200; }
+# .hl.sym { color:#000000; }
+# .hl.line { color:#555555; }
+# .hl.mark { background-color:#ffffbb;}
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#830000; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 3.8, http://www.andre-simon.de/
+#
+# body.hl { background-color:#e0eaee; }
+# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';}
+# .hl.num { color:#b07e00; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#bf0303; }
+# .hl.pps { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.ppc { color:#008200; }
+# .hl.opt { color:#000000; }
+# .hl.lin { color:#555555; }
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#0057ae; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 3.13, http://www.andre-simon.de/
+#
+# body.hl { background-color:#e0eaee; }
+# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
+# .hl.num { color:#b07e00; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#bf0303; }
+# .hl.pps { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.ppc { color:#008200; }
+# .hl.opt { color:#000000; }
+# .hl.ipl { color:#0057ae; }
+# .hl.lin { color:#555555; }
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#0057ae; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL ( = repo.url setting )
+# CGIT_REPO_NAME ( = repo.name setting )
+# CGIT_REPO_PATH ( = repo.path setting )
+# CGIT_REPO_OWNER ( = repo.owner setting )
+# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
+# CGIT_REPO_SECTION ( = section setting )
+# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
+#
+
+# store filename and extension in local vars
+BASENAME="$1"
+EXTENSION="${BASENAME##*.}"
+
+[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
+[ -z "${EXTENSION}" ] && EXTENSION=txt
+
+# map Makefile and Makefile.* to .mk
+[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
+
+# highlight versions 2 and 3 have different commandline options. Specifically,
+# the -X option that is used for version 2 is replaced by the -O xhtml option
+# for version 3.
+#
+# Version 2 can be found (for example) on EPEL 5, while version 3 can be
+# found (for example) on EPEL 6.
+#
+# This is for version 2
+#exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
+
+# env
+
+extension=${REQUEST_URI: -3}
+
+case $extension in
+ org)
+ temp=$(mktemp)
+ cat - > $temp.org
+ emacs $temp.org \
+ --quick \
+ --batch \
+ --funcall org-html-export-to-html \
+ --kill
+ tail -n+10 $temp.html
+ exit 0
+ ;;
+esac
+
+exec highlight --force -f -I -O xhtml -S "$EXTENSION" # 2>/dev/null
diff --git a/files/filters/hugo-pre.sh b/files/filters/hugo-pre.sh
new file mode 100755
index 0000000..de94b26
--- /dev/null
+++ b/files/filters/hugo-pre.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+name=$1
+
+extension=${1: -3}
+
+case $extension in
+ .md) markdown ;;
+ *)
+ cat <<- EOF
+ <pre>$(cat -)</pre>
+ EOF
+ ;;
+esac
+
diff --git a/files/logo.png b/files/logo.png
new file mode 100644
index 0000000..5c39e4a
--- /dev/null
+++ b/files/logo.png
Binary files differ
diff --git a/files/logo_large.png b/files/logo_large.png
new file mode 100644
index 0000000..bbfa2b5
--- /dev/null
+++ b/files/logo_large.png
Binary files differ
diff --git a/files/root_readme b/files/root_readme
new file mode 100644
index 0000000..aabf4df
--- /dev/null
+++ b/files/root_readme
@@ -0,0 +1,17 @@
+ _______________________
+< Den som gör bestämmer >
+ -----------------------
+\ . .
+ \ / `. .' "
+ \ .---. < > < > .---.
+ \ | \ \ - ~ ~ - / / |
+ _____ ..-~ ~-..-~
+ | | \~~~\.' `./~~~/
+ --------- \__/ \__/
+ .' O \ / / \ "
+ (_____, `._.' | } \/~~~/
+ `----. / } | / \__/
+ `-. | / | / `. ,~~|
+ ~-.__| /_ - ~ ^| /- _ `..-'
+ | / | / ~-. `-. _ _ _
+ |_____| |_____| ~ - . _ _ _ _ _>
diff --git a/manifests/init.pp b/manifests/init.pp
new file mode 100644
index 0000000..c1a981b
--- /dev/null
+++ b/manifests/init.pp
@@ -0,0 +1,62 @@
+class cgit (
+ String $root = '/var/www/cgit',
+ String $filterpath = '/usr/lib/cgit/extra-filters',
+ String $root_title,
+ String $root_desc,
+ String $about_filter,
+ String $auth_filter,
+ String $source_filter,
+ String $scan_path,
+ Array[String] $clone_url,
+) {
+
+ # TODO figure out where CSS comes from
+
+ ensure_packages([
+ 'cgit',
+ ], { ensure => installed })
+
+ file { '/etc/cgitrc':
+ ensure => file,
+ content => epp('cgit/cgitrc.epp'),
+ }
+
+ file { "${root}/logo":
+ ensure => directory,
+ }
+
+ file { "${root}/logo/logo.png":
+ ensure => file,
+ source => 'puppet:///modules/cgit/logo.png',
+ }
+
+ file { "${root}/logo/logo_large.png":
+ ensure => file,
+ source => 'puppet:///modules/cgit/logo_large.png',
+ }
+
+ file { "${root}/root_readme":
+ ensure => file,
+ source => 'puppet:///modules/cgit/root_readme',
+ }
+
+ file { dirname($filterpath):
+ ensure => directory,
+ }
+
+ file { $filterpath:
+ ensure => directory,
+ }
+
+ [$about_filter, $source_filter, $auth_filter].each |$f| {
+ file { "${filterpath}/${f}":
+ ensure => file,
+ source => "puppet:///modules/cgit/filters/${f}",
+ mode => stdlib::extname($f) ? {
+ '.lua' => '0444',
+ default => '0555',
+ },
+ }
+ }
+
+}
diff --git a/templates/cgitrc.epp b/templates/cgitrc.epp
new file mode 100644
index 0000000..9434213
--- /dev/null
+++ b/templates/cgitrc.epp
@@ -0,0 +1,64 @@
+#
+# cgit config
+# see cgitrc(5) for details
+#
+# FILE MANAGED BY PUPPET
+#
+
+# css=/cgit.css
+logo=/logo/logo.png
+# favicon=/favicon.ico
+
+enable-index-owner=1
+
+root-title=<%= $cgit::root_title %>
+#root-desc=Tändes endast mot lådans plån
+root-desc=<%= $cgit::root_desc %>
+# Also causes the `about' page to exist
+# /usr/lib/cgit/readme
+root-readme=<%= $cgit::root %>/root_readme
+
+#source-filter=/srv/filters/dispatch.sh
+
+# about-filter=/usr/local/lib/cgit/filters/hugo-pre.sh
+# auth-filter=lua:/usr/local/lib/cgit/filters/hugo-authentication.lua
+# source-filter=/usr/local/lib/cgit/filters/hugo-highlighting.sh
+about-filter=<%= $cgit::filterpath %>/<%= $cgit::about_filter %>
+auth-filter=<% if stdlib::extname($cgit::auth_filter) == '.lua' {
+ -%>lua:<%
+ } -%><%= $cgit::filterpath %>/<%= $cgit::auth_filter %>
+source-filter=<%= $cgit::filterpath %>/<%= $cgit::source_filter %>
+
+enable-follow-links=1
+enable-subject-links=1 # show commit summary for parrent
+
+#side-by-side-diffs=1
+enable-commit-graph=1
+enable-index-links=1
+enable-remote-branches=1
+local-time=1
+
+case-sensative-sort=0
+
+max-repo-count=100
+
+enable-http-clone=1
+clone-url=<%= $cgit::clone_url.join(' ') %>
+
+readme=:README
+readme=:README.md
+readme=:README.txt
+readme=:readme
+readme=:readme.md
+readme=:readme.txt
+
+virtual-root=
+remove-suffix=1
+section-from-path=1
+enable-git-config=1
+
+# section=~/git
+snapshots=tar.gz tar.xz
+scan-path=<%= $cgit::scan_path %>
+
+#scan-path=/var/www/git/repositories/