aboutsummaryrefslogtreecommitdiff
path: root/modules/services/nixos/cgit
diff options
context:
space:
mode:
Diffstat (limited to 'modules/services/nixos/cgit')
-rw-r--r--modules/services/nixos/cgit/cgit.css385
-rw-r--r--modules/services/nixos/cgit/default.nix139
-rw-r--r--modules/services/nixos/cgit/favicon.svg1
3 files changed, 525 insertions, 0 deletions
diff --git a/modules/services/nixos/cgit/cgit.css b/modules/services/nixos/cgit/cgit.css
new file mode 100644
index 0000000..6d55e02
--- /dev/null
+++ b/modules/services/nixos/cgit/cgit.css
@@ -0,0 +1,385 @@
+html {
+ height: 100vh;
+ width: 100vw;
+}
+
+body {
+ /* Ensure the parent allows the child to center */
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+div#cgit {
+ padding: 4px;
+ margin: 0 auto;
+
+ width: 95%;
+ max-width: 1200px;
+
+ font-family: sans-serif;
+ font-size: 10pt;
+ color: #333;
+ background: white;
+
+ /* Global element styles within cgit */
+ a {
+ color: blue;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ img {
+ border: none;
+ }
+
+ input#switch-btn {
+ margin: 2px 0px 0px 0px;
+ }
+
+ .oid {
+ font-family: monospace;
+ font-size: 90%;
+ }
+
+ /* Utility Classes */
+ .left { text-align: left; }
+ .right { text-align: right; }
+
+ /* Header Table */
+ table#header {
+ width: 100%;
+ margin-bottom: 1em;
+ border-collapse: collapse;
+
+ td {
+ &.logo {
+ width: 96px;
+ vertical-align: top;
+ }
+
+ &.main {
+ font-size: 250%;
+ padding-left: 10px;
+ white-space: nowrap;
+ a { color: #000; }
+ }
+
+ &.form {
+ text-align: right;
+ vertical-align: bottom;
+ padding-right: 1em;
+ padding-bottom: 2px;
+ white-space: nowrap;
+
+ form, input, select {
+ font-size: 90%;
+ }
+ }
+
+ &.sub {
+ color: #777;
+ border-top: solid 1px #ccc;
+ padding-left: 10px;
+ }
+ }
+ }
+
+ /* Tabs Table */
+ table.tabs {
+ border-bottom: solid 3px #ccc;
+ border-collapse: collapse;
+ margin-top: 2em;
+ margin-bottom: 0px;
+ width: 100%;
+
+ td {
+ padding: 0px 1em;
+ vertical-align: bottom;
+
+ a {
+ padding: 2px 0.75em;
+ color: #777;
+ font-size: 110%;
+
+ &.active {
+ color: #000;
+ background-color: #ccc;
+ }
+ }
+
+ &.form {
+ text-align: right;
+ form {
+ padding-bottom: 2px;
+ font-size: 90%;
+ white-space: nowrap;
+ }
+ input, select { font-size: 90%; }
+ }
+ }
+ }
+
+ /* Content Sections */
+ div.path {
+ margin: 0px;
+ padding: 5px 2em 2px 2em;
+ color: #000;
+ background-color: #eee;
+ }
+
+ div.content {
+ margin: 0px;
+ padding: 2em;
+ border-bottom: solid 3px #ccc;
+ }
+
+ /* List Tables (Repo index, log, etc) */
+ table.list {
+ width: 100%;
+ border: none;
+ border-collapse: collapse;
+
+ tr {
+ background: white;
+
+ &.logheader { background: #eee; }
+ &:nth-child(even) { background: #f7f7f7; }
+ &:nth-child(odd) { background: white; }
+ &:hover { background: #eee; }
+
+ &.nohover {
+ background: white;
+ &:hover { background: white; }
+ }
+
+ &.nohover-highlight:hover {
+ &:nth-child(even) { background: #f7f7f7; }
+ &:nth-child(odd) { background: white; }
+ }
+ }
+
+ th {
+ font-weight: bold;
+ padding: 0.1em 0.5em 0.05em 0.5em;
+ vertical-align: baseline;
+ }
+
+ td {
+ border: none;
+ padding: 0.1em 0.5em 0.1em 0.5em;
+
+ a {
+ color: black;
+ &:hover { color: #00f; }
+ &.ls-dir { font-weight: bold; color: #00f; }
+ }
+
+ &.commitgraph {
+ font-family: monospace;
+ white-space: pre;
+ .column1 { color: #a00; }
+ .column2 { color: #0a0; }
+ .column3 { color: #aa0; }
+ .column4 { color: #00a; }
+ .column5 { color: #a0a; }
+ .column6 { color: #0aa; }
+ }
+
+ &.logsubject { font-family: monospace; font-weight: bold; }
+ &.logmsg { font-family: monospace; white-space: pre; padding: 0 0.5em; }
+ &.reposection { font-style: italic; color: #888; }
+ &.sublevel-repo { padding-left: 1.5em; }
+ }
+ }
+
+ /* File Blob / Blame Views */
+ table.blob, table.ssdiff, table.blame {
+ td.linenumbers, td.lineno {
+ a {
+ color: gray;
+ text-align: right;
+ text-decoration: none;
+ &:hover { color: black; }
+ }
+ }
+ }
+
+ table.blob {
+ margin-top: 0.5em;
+ border-top: solid 1px black;
+
+ td.hashes, td.lines {
+ margin: 0; padding: 0 0 0 0.5em;
+ vertical-align: top;
+ color: black;
+ }
+
+ td.linenumbers {
+ margin: 0; padding: 0 0.5em 0 0.5em;
+ vertical-align: top;
+ text-align: right;
+ border-right: 1px solid gray;
+ }
+
+ pre { padding: 0; margin: 0; }
+ }
+
+ table.blame {
+ td.hashes, td.lines, td.linenumbers { padding: 0; }
+ td.hashes div.alt, td.lines div.alt { padding: 0 0.5em; }
+ td.linenumbers div.alt { padding: 0 0.5em 0 0; }
+
+ div.alt {
+ &:nth-child(even) { background: #eee; }
+ &:nth-child(odd) { background: white; }
+ }
+
+ td.lines > div {
+ position: relative;
+ & > pre {
+ padding: 0 0 0 0.5em;
+ position: absolute;
+ top: 0;
+ }
+ }
+ }
+
+ /* Diffstat and Panels */
+ table.diffstat {
+ border-collapse: collapse;
+ border: solid 1px #aaa;
+ background-color: #eee;
+
+ th {
+ font-weight: normal;
+ text-align: left;
+ text-decoration: underline;
+ padding: 0.1em 1em 0.1em 0.1em;
+ }
+
+ td {
+ padding: 0.2em 0.2em 0.1em 0.1em;
+ border: none;
+
+ &.mode { white-space: nowrap; }
+ span.modechange { padding-left: 1em; color: red; }
+ &.add a { color: green; }
+ &.del a { color: red; }
+ &.upd a { color: blue; }
+
+ &.graph {
+ width: 500px;
+ vertical-align: middle;
+ table { border: none; }
+ td {
+ padding: 0px;
+ border: 0px;
+ height: 7pt;
+ &.add { background-color: #5c5; }
+ &.rem { background-color: #c55; }
+ }
+ }
+ }
+ }
+
+ /* Side-by-side Diff (ssdiff) */
+ table.ssdiff {
+ width: 100%;
+ td {
+ font-size: 75%;
+ font-family: monospace;
+ white-space: pre;
+ padding: 1px 4px;
+ border-left: solid 1px #aaa;
+ border-right: solid 1px #aaa;
+
+ &.add { color: black; background: #cfc; min-width: 50%; }
+ &.add_dark { color: black; background: #aca; min-width: 50%; }
+ &.del { color: black; background: #fcc; min-width: 50%; }
+ &.del_dark { color: black; background: #caa; min-width: 50%; }
+ &.changed { color: black; background: #ffc; min-width: 50%; }
+ &.changed_dark { color: black; background: #cca; min-width: 50%; }
+ &.lineno { color: black; background: #eee; text-align: right; width: 3em; }
+ &.hunk { color: black; background: #ccf; border-top: solid 1px #aaa; border-bottom: solid 1px #aaa; }
+ &.head {
+ border-top: solid 1px #aaa; border-bottom: solid 1px #aaa;
+ div.head { font-weight: bold; color: black; }
+ }
+ &.foot { border-top: solid 1px #aaa; border-left: none; border-right: none; border-bottom: none; }
+ &.space { border: none; div { min-height: 3em; } }
+ }
+ span.add { background: #cfc; font-weight: bold; }
+ span.del { background: #fcc; font-weight: bold; }
+ }
+
+ /* Footer & Pager */
+ ul.pager {
+ list-style-type: none;
+ text-align: center;
+ margin: 1em 0 0 0;
+ padding: 0;
+ li { display: inline-block; margin: 0.25em 0.5em; }
+ a { color: #777; }
+ .current { font-weight: bold; }
+ }
+
+ div.footer {
+ margin-top: 0.5em;
+ text-align: center;
+ font-size: 80%;
+ color: #ccc;
+ a {
+ color: #ccc;
+ text-decoration: none;
+ &:hover { text-decoration: underline; }
+ }
+ }
+
+ /* Decorators (Branches/Tags) */
+ a.branch-deco, a.tag-deco, a.tag-annotated-deco, a.remote-deco, a.deco {
+ color: #000;
+ margin: 0px 0.5em;
+ padding: 0px 0.25em;
+ border: solid 1px;
+ }
+ a.branch-deco { background-color: #88ff88; border-color: #007700; }
+ a.tag-deco { background-color: #ffff88; border-color: #777700; }
+ a.tag-annotated-deco { background-color: #ffcc88; border-color: #777700; }
+ a.remote-deco { background-color: #ccccff; border-color: #000077; }
+ a.deco { background-color: #ff8888; border-color: #770000; }
+
+ div.commit-subject {
+ font-weight: bold;
+ font-size: 125%;
+ margin: 1.5em 0em 0.5em 0em;
+ padding: 0em;
+
+ a.branch-deco, a.tag-deco, a.tag-annotated-deco, a.remote-deco, a.deco {
+ margin-left: 1em;
+ font-size: 75%;
+ }
+ }
+
+ /* Misc specific IDs */
+ td#sidebar input.txt { width: 100%; margin: 2px 0 0 0; }
+ div#summary { vertical-align: top; margin-bottom: 1em; }
+ div.error { color: red; font-weight: bold; margin: 1em 2em; }
+
+ /* Age Spans */
+ span {
+ &.age-mins, &.age-hours { color: #080; }
+ &.age-mins { font-weight: bold; }
+ &.age-days { color: #040; }
+ &.age-weeks { color: #444; }
+ &.age-months { color: #888; }
+ &.age-years { color: #bbb; }
+ &.insertions { color: #080; }
+ &.deletions { color: #800; }
+ }
+}
diff --git a/modules/services/nixos/cgit/default.nix b/modules/services/nixos/cgit/default.nix
new file mode 100644
index 0000000..0f81958
--- /dev/null
+++ b/modules/services/nixos/cgit/default.nix
@@ -0,0 +1,139 @@
+{
+ pkgs,
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.collinux.services.cgit;
+in {
+ config = lib.mkIf cfg.enable {
+ environment.shells = ["${pkgs.git}/bin/git-shell"];
+ users.groups."git" = {};
+ users.users."git" = {
+ isSystemUser = true;
+ group = "git";
+ shell = "${pkgs.git}/bin/git-shell";
+
+ home = "/var/lib/cgit";
+ createHome = true;
+ homeMode = "755";
+
+ openssh.authorizedKeys.keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC3SjzIs3YI8PWJaNrAuaEeRcTcvIVHOKyCh2VwHTHEF"];
+ };
+ hjem.users."git".files = {
+ "git-shell-commands/set-description" = {
+ executable = true;
+ text = ''
+ #!/usr/bin/env bash
+ set -euo pipefail
+ repo="$1"
+ desc="$2"
+ base="/var/lib/cgit"
+ repo_path="$base/$repo"
+
+ test -d "$repo_path" || { echo "Repository does not exist"; exit 1; }
+
+ # Prevent path traversal
+ real=$(realpath "$repo_path")
+ if [[ "$real" != "$base/"* ]]; then
+ echo "Invalid path"
+ exit 1
+ fi
+
+ echo "$desc" | head -n 1 > "$repo_path/description"
+
+ echo "Description updated for '$repo'"
+ '';
+ };
+ "git-shell-commands/create-repo" = {
+ executable = true;
+ text = ''
+ #!/usr/bin/env bash
+ set -euo pipefail
+ repo="$1"
+ base="/var/lib/cgit"
+ repo_path="$base/$repo"
+
+ test -d "$repo_path" && { echo "Repository already exists."; exit 1; }
+
+ git init --bare "$repo_path"
+ echo "Repository '$repo' created"
+ '';
+ };
+ };
+
+ services.openssh.extraConfig = lib.mkAfter ''
+ Match User git
+ AllowTcpForwarding no
+ X11Forwarding no
+ PermitTunnel no
+ PubkeyAuthentication yes
+ AuthenticationMethods publickey
+ '';
+
+ environment.etc."cgitrc".text = ''
+ logo=/favicon.svg
+ favicon=/favicon.svg
+
+ repo.sort=age
+ enable-http-clone=1
+ enable-commit-graph=1
+ side-by-side-diffs=1
+
+ root-title=git@ganymede
+ root-desc=Git repos associated with Ganymede
+
+ readme=:README.md
+ about-filter=${pkgs.cgit}/lib/cgit/filters/html-converters/md2html
+ source-filter=${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+ footer=
+
+ virtual-root=/
+ scan-path=/var/lib/cgit
+ '';
+
+ services.fcgiwrap.instances."cgit" = {
+ process = {
+ user = "git";
+ group = "git";
+ };
+
+ socket = {
+ user = "caddy";
+ group = "caddy";
+ type = "unix";
+ address = "/run/fcgiwrap-cgit.sock";
+ };
+ };
+
+ networking.extraHosts = "127.0.0.1 git.ganymede";
+ services.caddy.virtualHosts."git.ganymede".extraConfig = let
+ custom_cgit = pkgs.stdenv.mkDerivation {
+ name = "custom-cgit-assets";
+ src = pkgs.cgit;
+ installPhase = ''
+ mkdir -p $out
+ cp -pPR ./cgit/* $out/
+
+ rm -f $out/cgit.png $out/favicon.ico $out/cgit.css
+ cp -f ${./favicon.svg} $out/favicon.svg
+ cp -f ${./cgit.css} $out/cgit.css
+ '';
+ };
+ in ''
+ tls internal
+
+ @assets path /cgit.css /cgit.js /favicon.svg /robots.txt
+ handle @assets {
+ root * ${custom_cgit}
+ file_server
+ }
+
+ reverse_proxy unix//run/fcgiwrap-cgit.sock {
+ transport fastcgi {
+ env SCRIPT_FILENAME ${custom_cgit}/cgit.cgi
+ }
+ }
+ '';
+ };
+}
diff --git a/modules/services/nixos/cgit/favicon.svg b/modules/services/nixos/cgit/favicon.svg
new file mode 100644
index 0000000..52c5f79
--- /dev/null
+++ b/modules/services/nixos/cgit/favicon.svg
@@ -0,0 +1 @@
+<svg version="1.1" viewBox="0.0 0.0 288.0 288.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="p.0"><path d="m0 0l288.0 0l0 288.0l-288.0 0l0 -288.0z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l288.0 0l0 288.0l-288.0 0z" fill-rule="evenodd"/><defs><linearGradient id="p.1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(18.149084525702985 0.0 0.0 18.149084525702985 0.0 0.0)" spreadMethod="pad" x1="7.934265855638697" y1="20.767769854446048" x2="20.767769854446048" y2="7.93459237442547"><stop offset="0.0" stop-color="#3d85c6"/><stop offset="1.0" stop-color="#073763"/></linearGradient></defs><path fill="url(#p.1)" d="m27.54593 66.36561l0 0c0 -21.439518 17.38016 -38.81968 38.81968 -38.81968l155.27405 0l0 0c10.2956085 0 20.169556 4.08992 27.449646 11.37002c7.2800903 7.280102 11.370026 17.154037 11.370026 27.449657l0 155.27405c0 21.439514 -17.380157 38.81967 -38.81967 38.81967l-155.27405 0c-21.439518 0 -38.81968 -17.380157 -38.81968 -38.81967z" fill-rule="evenodd"/><path stroke="#073763" stroke-width="8.0" stroke-linejoin="round" stroke-linecap="butt" d="m27.54593 66.36561l0 0c0 -21.439518 17.38016 -38.81968 38.81968 -38.81968l155.27405 0l0 0c10.2956085 0 20.169556 4.08992 27.449646 11.37002c7.2800903 7.280102 11.370026 17.154037 11.370026 27.449657l0 155.27405c0 21.439514 -17.380157 38.81967 -38.81967 38.81967l-155.27405 0c-21.439518 0 -38.81968 -17.380157 -38.81968 -38.81967z" fill-rule="evenodd"/><path fill="#073763" d="m106.705 143.98044l0 0c0 -20.586746 16.68885 -37.275597 37.27559 -37.275597l0 0c9.886108 0 19.367294 3.9272385 26.357819 10.91777c6.9905396 6.9905243 10.91777 16.471718 10.91777 26.357826l0 0c0 20.586731 -16.688843 37.27559 -37.27559 37.27559l0 0c-20.586739 0 -37.27559 -16.688858 -37.27559 -37.27559z" fill-rule="evenodd"/><path stroke="#073763" stroke-width="8.0" stroke-linejoin="round" stroke-linecap="butt" d="m106.705 143.98044l0 0c0 -20.586746 16.68885 -37.275597 37.27559 -37.275597l0 0c9.886108 0 19.367294 3.9272385 26.357819 10.91777c6.9905396 6.9905243 10.91777 16.471718 10.91777 26.357826l0 0c0 20.586731 -16.688843 37.27559 -37.27559 37.27559l0 0c-20.586739 0 -37.27559 -16.688858 -37.27559 -37.27559z" fill-rule="evenodd"/></g></svg>