aboutsummaryrefslogtreecommitdiff
path: root/nix/per-host/git
diff options
context:
space:
mode:
authorKumar Damani <me@kumardamani.net>2026-08-02 01:43:25 +0000
committerKumar Damani <me@kumardamani.net>2026-08-02 01:43:25 +0000
commit43930cb8c22bc28ed64362845c5f4ebf54faabf2 (patch)
tree7351357bd38139b0267f1a4cdeff4b5143f5e89a /nix/per-host/git
parent3e139a0e702513f6e1145158b2695b43c8251b92 (diff)
added git with cgit
Diffstat (limited to 'nix/per-host/git')
-rw-r--r--nix/per-host/git/cgit-dark.css337
-rw-r--r--nix/per-host/git/configuration.nix141
-rwxr-xr-xnix/per-host/git/git-shell-commands/create49
-rwxr-xr-xnix/per-host/git/git-shell-commands/desc25
-rwxr-xr-xnix/per-host/git/git-shell-commands/hide19
-rwxr-xr-xnix/per-host/git/git-shell-commands/manage81
-rwxr-xr-xnix/per-host/git/git-shell-commands/publish19
7 files changed, 671 insertions, 0 deletions
diff --git a/nix/per-host/git/cgit-dark.css b/nix/per-host/git/cgit-dark.css
new file mode 100644
index 0000000..d3d5c8d
--- /dev/null
+++ b/nix/per-host/git/cgit-dark.css
@@ -0,0 +1,337 @@
+/*
+ Color overrides for browsers running in dark mode.
+ Appended to the stock cgit 1.2.3 stylesheet. Taken verbatim from the
+ upstream cgit stylesheet as served on https://git.zx2c4.com/cgit.css
+ (the dark section was added to cgit upstream after the 1.2.3 release).
+ "only all and ..." forces very old browsers to ignore the media query:
+ https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#improving_compatibility_with_older_browsers
+*/
+:root { color-scheme: light dark; }
+@media only all and (prefers-color-scheme: dark) {
+ html, div#cgit { color: #eee; background: #171717; }
+ div#cgit a { color: #3af; }
+ div#cgit .diffstat-header a { color: #28d; }
+ div#cgit table#header td.main a { color: #eee; }
+ div#cgit table#header td.sub { color: #999; }
+ div#cgit table.tabs { border-bottom-color: #444; }
+ div#cgit table.tabs td a { color: #888; }
+ div#cgit table.tabs td a.active { color: #fff; background-color: #444; }
+ div#cgit div.path { color: #eee; background-color: #333; }
+ div#cgit div.content { border-bottom-color: #bbb; }
+ div#cgit table.list tr { background: #171717; }
+ div#cgit table.list tr.logheader { background: #171717; }
+ div#cgit table.list tr:nth-child(even) { background: #171717; }
+ div#cgit table.list tr:nth-child(odd) { background: #1f1f1f; }
+ div#cgit table.list tr:hover { background: #333; }
+ div#cgit table.list tr.nohover { background: #171717; }
+ div#cgit table.list tr.nohover:hover { background: #171717; }
+ div#cgit table.list tr.nohover-highlight:hover:nth-child(even) { background: #171717; }
+ div#cgit table.list tr.nohover-highlight:hover:nth-child(odd) { background: #1f1f1f; }
+ div#cgit table.list td.commitgraph .column1 { color: #f55; }
+ div#cgit table.list td.commitgraph .column2 { color: #5f5; }
+ div#cgit table.list td.commitgraph .column3 { color: #ff5; }
+ div#cgit table.list td.commitgraph .column4 { color: #55f; }
+ div#cgit table.list td.commitgraph .column5 { color: #f5f; }
+ div#cgit table.list td.commitgraph .column6 { color: #5ff; }
+ div#cgit table.list td a { color: #eee; }
+ div#cgit table.list td a.ls-dir { color: #28d; }
+ div#cgit table.list td a:hover { color: #3af; }
+ div#cgit table#downloads { border-color: #888; }
+ div#cgit table#downloads th { background-color: #333; }
+ div#cgit div#blob { border-color: #eee; }
+ div#cgit table.blob { border-top-color: #eee; }
+ div#cgit table.blob td.hashes,
+ div#cgit table.blob td.lines { color: #eee; }
+ div#cgit table.blob td.linenumbers { border-right-color: gray; }
+ div#cgit table.blob td.linenumbers a,
+ div#cgit table.ssdiff td.lineno a { color: gray; }
+ div#cgit table.blob td.linenumbers a:hover,
+ div#cgit table.ssdiff td.lineno a:hover { color: #eee; }
+ div#cgit table.blame div.alt:nth-child(even) { background: #171717; }
+ div#cgit table.blame div.alt:nth-child(odd) { background: #1f1f1f; }
+ div#cgit table.bin-blob { border-color: #eee; }
+ div#cgit table.bin-blob th { border-color: #888; }
+ div#cgit table.bin-blob td { border-left-color: #888; }
+ div#cgit div.cgit-panel table { border-color: #555; background-color: #282828; }
+ div#cgit div.notes { border-color: #661; background-color: #220; }
+ div#cgit table.diffstat { border-color: #555; background-color: #282828; }
+ div#cgit table.diffstat td span.modechange { color: #c66; }
+ div#cgit table.diffstat td.add a { color: #6c6; }
+ div#cgit table.diffstat td.del a { color: #c66; }
+ div#cgit table.diffstat td.upd a { color: #3af; }
+ div#cgit table.diffstat td.graph td.add { background-color: #3a3; }
+ div#cgit table.diffstat td.graph td.rem { background-color: #a33; }
+ div#cgit div.diffstat-summary { color: #777; }
+ div#cgit table.diff td div.head { color: #eee; }
+ div#cgit table.diff td div.hunk { color: #28d; }
+ div#cgit table.diff td div.add { color: #6c6; }
+ div#cgit table.diff td div.del { color: #c66; }
+ div#cgit table.list td.reposection { color: #777; }
+ div#cgit ul.pager a { color: #888; }
+ div#cgit span.age-mins { color: #7f7; }
+ div#cgit span.age-hours { color: #7f7; }
+ div#cgit span.age-days { color: #9d9; }
+ div#cgit span.age-weeks { color: #bbb; }
+ div#cgit span.age-months { color: #888; }
+ div#cgit span.age-years { color: #666; }
+ div#cgit span.insertions { color: #7f7; }
+ div#cgit span.deletions { color: #e33; }
+ div#cgit div.footer { color: #555; }
+ div#cgit div.footer a { color: #555; }
+ div#cgit a.branch-deco { color: #fff; background-color: #361; border-color: #3b2; }
+ div#cgit a.tag-deco { color: #fff; background-color: #650; border-color: #ba3; }
+ div#cgit a.tag-annotated-deco { color: #fff; background-color: #840; border-color: #b96; }
+ div#cgit a.remote-deco { color: #fff; background-color: #348; border-color: #67c; }
+ div#cgit a.deco { color: #fff; background-color: #922; border-color: #d55; }
+ div#cgit a.branch-deco:hover, div#cgit table.list td a.branch-deco:hover,
+ div#cgit a.tag-deco:hover, div#cgit table.list td a.tag-deco:hover,
+ div#cgit a.tag-annotated-deco:hover, div#cgit table.list td a.tag-annotated-deco:hover,
+ div#cgit a.remote-deco:hover, div#cgit table.list td a.remote-deco:hover,
+ div#cgit a.deco:hover, div#cgit table.list td a.deco:hover { color: #fff; }
+ div#cgit table.stats { border-color: #555; }
+ div#cgit table.stats th { background-color: #333; border-color: #555; }
+ div#cgit table.stats td { border-color: #555; }
+ div#cgit table.stats td.sum { color: #5C7E9B; }
+ div#cgit table.vgraph { border-color: #eee; }
+ div#cgit table.vgraph th { background-color: #1f1f1f; border-color: black; }
+ div#cgit table.vgraph div.bar { background-color: #1f1f1f; }
+ div#cgit table.hgraph { border-color: #eee; }
+ div#cgit table.hgraph th { background-color: #1f1f1f; border-color: #eee; }
+ div#cgit table.hgraph div.bar { background-color: #1f1f1f; }
+ div#cgit table.ssdiff td { border-left-color: #555; border-right-color: #555; }
+ div#cgit table.ssdiff td.add { color: #eee; background: #353; }
+ div#cgit table.ssdiff td.add_dark { color: #eee; background: #030; }
+ div#cgit table.ssdiff span.add { background: #030; }
+ div#cgit table.ssdiff td.del { color: #eee; background: #533; }
+ div#cgit table.ssdiff td.del_dark { color: #eee; background: #411; }
+ div#cgit table.ssdiff span.del { background: #300; }
+ div#cgit table.ssdiff td.changed { color: #eee; background: #553; }
+ div#cgit table.ssdiff td.changed_dark { color: #eee; background: #330; }
+ div#cgit table.ssdiff td.lineno { color: #eee; background: #1f1f1f; }
+ div#cgit table.ssdiff td.hunk { color: #eee; background: #0D3D64; border-top-color: #555; border-bottom-color: #555; }
+ div#cgit table.ssdiff td.head { border-top-color: #555; border-bottom-color: #555; }
+ div#cgit table.ssdiff td.head div.head { color: #eee; }
+ div#cgit table.ssdiff td.foot { border-top-color: #555; }
+}
+
+/*
+ Rendered-markdown fixes (about-filter's md2html injects its own
+ light-themed <style> block into the page). The extra div#cgit
+ specificity beats the injected bare-.markdown-body rules.
+ Code blocks use the github-dark palette from the .codehilite
+ rules further below.
+*/
+@media only all and (prefers-color-scheme: dark) {
+ div#cgit .markdown-body { color: #eee; background: transparent; }
+ div#cgit .markdown-body a { color: #3af; }
+ /* the injected sheet colors h1/h2 #000 directly */
+ div#cgit .markdown-body h1,
+ div#cgit .markdown-body h2,
+ div#cgit .markdown-body h3,
+ div#cgit .markdown-body h4,
+ div#cgit .markdown-body h5,
+ div#cgit .markdown-body h6 { color: #eee; }
+ /* heading anchor links: injected rule is prefixed with div#cgit too,
+ so it needs the extra html element to win the tie */
+ html div#cgit .markdown-body h1 a.toclink,
+ html div#cgit .markdown-body h2 a.toclink,
+ html div#cgit .markdown-body h3 a.toclink,
+ html div#cgit .markdown-body h4 a.toclink,
+ html div#cgit .markdown-body h5 a.toclink,
+ html div#cgit .markdown-body h6 a.toclink { color: #3af; }
+ div#cgit .markdown-body h1,
+ div#cgit .markdown-body h2 { border-bottom-color: #444; }
+ div#cgit .markdown-body blockquote { color: #999; border-left-color: #444; }
+ div#cgit .markdown-body hr { background-color: #444; }
+ div#cgit .markdown-body table,
+ div#cgit .markdown-body table tr,
+ div#cgit .markdown-body table th,
+ div#cgit .markdown-body table td { background: transparent; border-color: #444; color: #eee; }
+ div#cgit .markdown-body code,
+ div#cgit .markdown-body pre { background-color: #0d1117; color: #e6edf3; border-color: #444; }
+ div#cgit .markdown-body pre code { background-color: transparent; }
+ div#cgit .markdown-body img { background: transparent; }
+}
+
+/*
+ Dark syntax highlighting for blob view (source-filter). The filter
+ injects a light-theme pygments palette scoped to .highlight; these
+ github-dark pygments rules use a div#cgit prefix for higher
+ specificity so they win in dark mode. Generated with:
+ pygmentize -S github-dark -f html -a "div#cgit .highlight"
+*/
+@media only all and (prefers-color-scheme: dark) {
+ /* The injected light palette also sets background colors on some
+ tokens (strings get #FFF0F0 etc.) - clear them all here; the
+ intentional dark backgrounds below (.hll/.gd/.gi) still win
+ via higher class specificity. */
+ div#cgit .highlight span,
+ div#cgit .codehilite span { background-color: transparent; }
+ div#cgit .highlight .hll { background-color: #6e7681 }
+ div#cgit .highlight { background: #0d1117; color: #E6EDF3 }
+ div#cgit .highlight .c { color: #8B949E; font-style: italic } /* Comment */
+ div#cgit .highlight .err { color: #F85149 } /* Error */
+ div#cgit .highlight .esc { color: #E6EDF3 } /* Escape */
+ div#cgit .highlight .g { color: #E6EDF3 } /* Generic */
+ div#cgit .highlight .k { color: #FF7B72 } /* Keyword */
+ div#cgit .highlight .l { color: #A5D6FF } /* Literal */
+ div#cgit .highlight .n { color: #E6EDF3 } /* Name */
+ div#cgit .highlight .o { color: #FF7B72; font-weight: bold } /* Operator */
+ div#cgit .highlight .x { color: #E6EDF3 } /* Other */
+ div#cgit .highlight .p { color: #E6EDF3 } /* Punctuation */
+ div#cgit .highlight .ch { color: #8B949E; font-style: italic } /* Comment.Hashbang */
+ div#cgit .highlight .cm { color: #8B949E; font-style: italic } /* Comment.Multiline */
+ div#cgit .highlight .cp { color: #8B949E; font-weight: bold; font-style: italic } /* Comment.Preproc */
+ div#cgit .highlight .cpf { color: #8B949E; font-style: italic } /* Comment.PreprocFile */
+ div#cgit .highlight .c1 { color: #8B949E; font-style: italic } /* Comment.Single */
+ div#cgit .highlight .cs { color: #8B949E; font-weight: bold; font-style: italic } /* Comment.Special */
+ div#cgit .highlight .gd { color: #FFA198; background-color: #490202 } /* Generic.Deleted */
+ div#cgit .highlight .ge { color: #E6EDF3; font-style: italic } /* Generic.Emph */
+ div#cgit .highlight .ges { color: #E6EDF3; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+ div#cgit .highlight .gr { color: #FFA198 } /* Generic.Error */
+ div#cgit .highlight .gh { color: #79C0FF; font-weight: bold } /* Generic.Heading */
+ div#cgit .highlight .gi { color: #56D364; background-color: #0F5323 } /* Generic.Inserted */
+ div#cgit .highlight .go { color: #8B949E } /* Generic.Output */
+ div#cgit .highlight .gp { color: #8B949E } /* Generic.Prompt */
+ div#cgit .highlight .gs { color: #E6EDF3; font-weight: bold } /* Generic.Strong */
+ div#cgit .highlight .gu { color: #79C0FF } /* Generic.Subheading */
+ div#cgit .highlight .gt { color: #FF7B72 } /* Generic.Traceback */
+ div#cgit .highlight .g-Underline { color: #E6EDF3; text-decoration: underline } /* Generic.Underline */
+ div#cgit .highlight .kc { color: #79C0FF } /* Keyword.Constant */
+ div#cgit .highlight .kd { color: #FF7B72 } /* Keyword.Declaration */
+ div#cgit .highlight .kn { color: #FF7B72 } /* Keyword.Namespace */
+ div#cgit .highlight .kp { color: #79C0FF } /* Keyword.Pseudo */
+ div#cgit .highlight .kr { color: #FF7B72 } /* Keyword.Reserved */
+ div#cgit .highlight .kt { color: #FF7B72 } /* Keyword.Type */
+ div#cgit .highlight .ld { color: #79C0FF } /* Literal.Date */
+ div#cgit .highlight .m { color: #A5D6FF } /* Literal.Number */
+ div#cgit .highlight .s { color: #A5D6FF } /* Literal.String */
+ div#cgit .highlight .na { color: #E6EDF3 } /* Name.Attribute */
+ div#cgit .highlight .nb { color: #E6EDF3 } /* Name.Builtin */
+ div#cgit .highlight .nc { color: #F0883E; font-weight: bold } /* Name.Class */
+ div#cgit .highlight .no { color: #79C0FF; font-weight: bold } /* Name.Constant */
+ div#cgit .highlight .nd { color: #D2A8FF; font-weight: bold } /* Name.Decorator */
+ div#cgit .highlight .ni { color: #FFA657 } /* Name.Entity */
+ div#cgit .highlight .ne { color: #F0883E; font-weight: bold } /* Name.Exception */
+ div#cgit .highlight .nf { color: #D2A8FF; font-weight: bold } /* Name.Function */
+ div#cgit .highlight .nl { color: #79C0FF } /* Name.Label */
+ div#cgit .highlight .nn { color: #FF7B72 } /* Name.Namespace */
+ div#cgit .highlight .nx { color: #E6EDF3 } /* Name.Other */
+ div#cgit .highlight .py { color: #79C0FF } /* Name.Property */
+ div#cgit .highlight .nt { color: #7EE787 } /* Name.Tag */
+ div#cgit .highlight .nv { color: #79C0FF } /* Name.Variable */
+ div#cgit .highlight .ow { color: #FF7B72; font-weight: bold } /* Operator.Word */
+ div#cgit .highlight .pm { color: #E6EDF3 } /* Punctuation.Marker */
+ div#cgit .highlight .w { color: #6E7681 } /* Text.Whitespace */
+ div#cgit .highlight .mb { color: #A5D6FF } /* Literal.Number.Bin */
+ div#cgit .highlight .mf { color: #A5D6FF } /* Literal.Number.Float */
+ div#cgit .highlight .mh { color: #A5D6FF } /* Literal.Number.Hex */
+ div#cgit .highlight .mi { color: #A5D6FF } /* Literal.Number.Integer */
+ div#cgit .highlight .mo { color: #A5D6FF } /* Literal.Number.Oct */
+ div#cgit .highlight .sa { color: #79C0FF } /* Literal.String.Affix */
+ div#cgit .highlight .sb { color: #79C0FF } /* Literal.String.Backtick */
+ div#cgit .highlight .sc { color: #79C0FF } /* Literal.String.Char */
+ div#cgit .highlight .dl { color: #79C0FF } /* Literal.String.Delimiter */
+ div#cgit .highlight .sd { color: #79C0FF } /* Literal.String.Doc */
+ div#cgit .highlight .s2 { color: #A5D6FF } /* Literal.String.Double */
+ div#cgit .highlight .se { color: #79C0FF } /* Literal.String.Escape */
+ div#cgit .highlight .sh { color: #79C0FF } /* Literal.String.Heredoc */
+ div#cgit .highlight .si { color: #79C0FF } /* Literal.String.Interpol */
+ div#cgit .highlight .sx { color: #A5D6FF } /* Literal.String.Other */
+ div#cgit .highlight .sr { color: #79C0FF } /* Literal.String.Regex */
+ div#cgit .highlight .s1 { color: #79C0FF } /* Literal.String.Single */
+ div#cgit .highlight .ss { color: #A5D6FF } /* Literal.String.Symbol */
+ div#cgit .highlight .bp { color: #E6EDF3 } /* Name.Builtin.Pseudo */
+ div#cgit .highlight .fm { color: #D2A8FF; font-weight: bold } /* Name.Function.Magic */
+ div#cgit .highlight .vc { color: #79C0FF } /* Name.Variable.Class */
+ div#cgit .highlight .vg { color: #79C0FF } /* Name.Variable.Global */
+ div#cgit .highlight .vi { color: #79C0FF } /* Name.Variable.Instance */
+ div#cgit .highlight .vm { color: #79C0FF } /* Name.Variable.Magic */
+ div#cgit .highlight .il { color: #A5D6FF } /* Literal.Number.Integer.Long */
+
+ /* Same palette for README code blocks - the about-filter's md2html
+ emits pygments markup under .codehilite instead of .highlight.
+ Generated with:
+ pygmentize -S github-dark -f html -a "div#cgit .codehilite" */
+ div#cgit .codehilite .hll { background-color: #6e7681 }
+ div#cgit .codehilite { background: #0d1117; color: #E6EDF3 }
+ div#cgit .codehilite .c { color: #8B949E; font-style: italic } /* Comment */
+ div#cgit .codehilite .err { color: #F85149 } /* Error */
+ div#cgit .codehilite .esc { color: #E6EDF3 } /* Escape */
+ div#cgit .codehilite .g { color: #E6EDF3 } /* Generic */
+ div#cgit .codehilite .k { color: #FF7B72 } /* Keyword */
+ div#cgit .codehilite .l { color: #A5D6FF } /* Literal */
+ div#cgit .codehilite .n { color: #E6EDF3 } /* Name */
+ div#cgit .codehilite .o { color: #FF7B72; font-weight: bold } /* Operator */
+ div#cgit .codehilite .x { color: #E6EDF3 } /* Other */
+ div#cgit .codehilite .p { color: #E6EDF3 } /* Punctuation */
+ div#cgit .codehilite .ch { color: #8B949E; font-style: italic } /* Comment.Hashbang */
+ div#cgit .codehilite .cm { color: #8B949E; font-style: italic } /* Comment.Multiline */
+ div#cgit .codehilite .cp { color: #8B949E; font-weight: bold; font-style: italic } /* Comment.Preproc */
+ div#cgit .codehilite .cpf { color: #8B949E; font-style: italic } /* Comment.PreprocFile */
+ div#cgit .codehilite .c1 { color: #8B949E; font-style: italic } /* Comment.Single */
+ div#cgit .codehilite .cs { color: #8B949E; font-weight: bold; font-style: italic } /* Comment.Special */
+ div#cgit .codehilite .gd { color: #FFA198; background-color: #490202 } /* Generic.Deleted */
+ div#cgit .codehilite .ge { color: #E6EDF3; font-style: italic } /* Generic.Emph */
+ div#cgit .codehilite .ges { color: #E6EDF3; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+ div#cgit .codehilite .gr { color: #FFA198 } /* Generic.Error */
+ div#cgit .codehilite .gh { color: #79C0FF; font-weight: bold } /* Generic.Heading */
+ div#cgit .codehilite .gi { color: #56D364; background-color: #0F5323 } /* Generic.Inserted */
+ div#cgit .codehilite .go { color: #8B949E } /* Generic.Output */
+ div#cgit .codehilite .gp { color: #8B949E } /* Generic.Prompt */
+ div#cgit .codehilite .gs { color: #E6EDF3; font-weight: bold } /* Generic.Strong */
+ div#cgit .codehilite .gu { color: #79C0FF } /* Generic.Subheading */
+ div#cgit .codehilite .gt { color: #FF7B72 } /* Generic.Traceback */
+ div#cgit .codehilite .g-Underline { color: #E6EDF3; text-decoration: underline } /* Generic.Underline */
+ div#cgit .codehilite .kc { color: #79C0FF } /* Keyword.Constant */
+ div#cgit .codehilite .kd { color: #FF7B72 } /* Keyword.Declaration */
+ div#cgit .codehilite .kn { color: #FF7B72 } /* Keyword.Namespace */
+ div#cgit .codehilite .kp { color: #79C0FF } /* Keyword.Pseudo */
+ div#cgit .codehilite .kr { color: #FF7B72 } /* Keyword.Reserved */
+ div#cgit .codehilite .kt { color: #FF7B72 } /* Keyword.Type */
+ div#cgit .codehilite .ld { color: #79C0FF } /* Literal.Date */
+ div#cgit .codehilite .m { color: #A5D6FF } /* Literal.Number */
+ div#cgit .codehilite .s { color: #A5D6FF } /* Literal.String */
+ div#cgit .codehilite .na { color: #E6EDF3 } /* Name.Attribute */
+ div#cgit .codehilite .nb { color: #E6EDF3 } /* Name.Builtin */
+ div#cgit .codehilite .nc { color: #F0883E; font-weight: bold } /* Name.Class */
+ div#cgit .codehilite .no { color: #79C0FF; font-weight: bold } /* Name.Constant */
+ div#cgit .codehilite .nd { color: #D2A8FF; font-weight: bold } /* Name.Decorator */
+ div#cgit .codehilite .ni { color: #FFA657 } /* Name.Entity */
+ div#cgit .codehilite .ne { color: #F0883E; font-weight: bold } /* Name.Exception */
+ div#cgit .codehilite .nf { color: #D2A8FF; font-weight: bold } /* Name.Function */
+ div#cgit .codehilite .nl { color: #79C0FF } /* Name.Label */
+ div#cgit .codehilite .nn { color: #FF7B72 } /* Name.Namespace */
+ div#cgit .codehilite .nx { color: #E6EDF3 } /* Name.Other */
+ div#cgit .codehilite .py { color: #79C0FF } /* Name.Property */
+ div#cgit .codehilite .nt { color: #7EE787 } /* Name.Tag */
+ div#cgit .codehilite .nv { color: #79C0FF } /* Name.Variable */
+ div#cgit .codehilite .ow { color: #FF7B72; font-weight: bold } /* Operator.Word */
+ div#cgit .codehilite .pm { color: #E6EDF3 } /* Punctuation.Marker */
+ div#cgit .codehilite .w { color: #6E7681 } /* Text.Whitespace */
+ div#cgit .codehilite .mb { color: #A5D6FF } /* Literal.Number.Bin */
+ div#cgit .codehilite .mf { color: #A5D6FF } /* Literal.Number.Float */
+ div#cgit .codehilite .mh { color: #A5D6FF } /* Literal.Number.Hex */
+ div#cgit .codehilite .mi { color: #A5D6FF } /* Literal.Number.Integer */
+ div#cgit .codehilite .mo { color: #A5D6FF } /* Literal.Number.Oct */
+ div#cgit .codehilite .sa { color: #79C0FF } /* Literal.String.Affix */
+ div#cgit .codehilite .sb { color: #79C0FF } /* Literal.String.Backtick */
+ div#cgit .codehilite .sc { color: #79C0FF } /* Literal.String.Char */
+ div#cgit .codehilite .dl { color: #79C0FF } /* Literal.String.Delimiter */
+ div#cgit .codehilite .sd { color: #79C0FF } /* Literal.String.Doc */
+ div#cgit .codehilite .s2 { color: #A5D6FF } /* Literal.String.Double */
+ div#cgit .codehilite .se { color: #79C0FF } /* Literal.String.Escape */
+ div#cgit .codehilite .sh { color: #79C0FF } /* Literal.String.Heredoc */
+ div#cgit .codehilite .si { color: #79C0FF } /* Literal.String.Interpol */
+ div#cgit .codehilite .sx { color: #A5D6FF } /* Literal.String.Other */
+ div#cgit .codehilite .sr { color: #79C0FF } /* Literal.String.Regex */
+ div#cgit .codehilite .s1 { color: #79C0FF } /* Literal.String.Single */
+ div#cgit .codehilite .ss { color: #A5D6FF } /* Literal.String.Symbol */
+ div#cgit .codehilite .bp { color: #E6EDF3 } /* Name.Builtin.Pseudo */
+ div#cgit .codehilite .fm { color: #D2A8FF; font-weight: bold } /* Name.Function.Magic */
+ div#cgit .codehilite .vc { color: #79C0FF } /* Name.Variable.Class */
+ div#cgit .codehilite .vg { color: #79C0FF } /* Name.Variable.Global */
+ div#cgit .codehilite .vi { color: #79C0FF } /* Name.Variable.Instance */
+ div#cgit .codehilite .vm { color: #79C0FF } /* Name.Variable.Magic */
+ div#cgit .codehilite .il { color: #A5D6FF } /* Literal.Number.Integer.Long */
+}
diff --git a/nix/per-host/git/configuration.nix b/nix/per-host/git/configuration.nix
new file mode 100644
index 0000000..2a2fa34
--- /dev/null
+++ b/nix/per-host/git/configuration.nix
@@ -0,0 +1,141 @@
+{ config, pkgs, modulesPath, lib, system, ... }:
+
+let
+ # Stock cgit.css from the package with upstream dark-mode overrides
+ # taken from https://git.zx2c4.com/cgit.css).
+ # Follows the browser's prefers-color-scheme, light or dark.
+ cgitCss = pkgs.writeText "cgit.css" (
+ builtins.readFile "${pkgs.cgit}/cgit/cgit.css"
+ + builtins.readFile ./cgit-dark.css
+ );
+
+ # The "manage" command is the default
+ gitShellCommands = ./git-shell-commands;
+in
+{
+ config = {
+ sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
+ sops.defaultSopsFile = ../../secrets/git.yaml;
+
+ networking.hostName = "git";
+ networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.cgit."git" = {
+ enable = true;
+ # Any bare repo dropped in /srv/git shows up in cgit automatically,
+ # but ONLY if it contains a git-daemon-export-ok marker file
+ # (private-by-default; see settings.strict-export and
+ # gitHttpBackend.checkExportOkFiles)
+ scanPath = "/srv/git";
+ gitHttpBackend.checkExportOkFiles = true;
+ settings = {
+ # repos without a git-daemon-export-ok file are neither browsable
+ # in cgit nor cloneable over HTTP (git-http-backend checks the same
+ # file via checkExportOkFiles above)
+ strict-export = "git-daemon-export-ok";
+ root-title = "My git browser";
+ root-desc = "git repositories";
+ repository-sort = "age";
+ enable-index-links = true;
+ remove-suffix = true;
+ # read gitweb.owner/description etc. from each repo's git config
+ enable-git-config = true;
+ clone-url = "https://git.kumardamani.net/$CGIT_REPO_URL git@git:$CGIT_REPO_URL";
+ robots = "noindex, nofollow";
+ # render the repo README on its About page (first match wins),
+ readme = [ ":README.md" ":readme.md" ":README.rst" ":README.txt" ":README" ];
+ about-filter = "${pkgs.cgit}/lib/cgit/filters/about-formatting.sh";
+ "mimetype.gif" = "image/gif";
+ "mimetype.png" = "image/png";
+ "mimetype.jpg" = "image/jpeg";
+ "mimetype.jpeg" = "image/jpeg";
+ "mimetype.webp" = "image/webp";
+ "mimetype.svg" = "image/svg+xml";
+ "mimetype.ico" = "image/x-icon";
+ "mimetype.pdf" = "application/pdf";
+ "mimetype.mp4" = "video/mp4";
+ "mimetype.webm" = "video/webm";
+ enable-commit-graph = true;
+ branch-sort = "age";
+ local-time = true;
+ source-filter = "${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
+ };
+ };
+
+ systemd.tmpfiles.rules = [
+ "d /srv/git 0755 git git -"
+ "Z /srv/git 0755 git git -"
+ # git-shell subcommands (see gitShellCommands above)
+ "d /srv/git/git-shell-commands 0755 git git -"
+ "L+ /srv/git/git-shell-commands/create - - - - ${gitShellCommands}/create"
+ "L+ /srv/git/git-shell-commands/publish - - - - ${gitShellCommands}/publish"
+ "L+ /srv/git/git-shell-commands/hide - - - - ${gitShellCommands}/hide"
+ "L+ /srv/git/git-shell-commands/desc - - - - ${gitShellCommands}/desc"
+ "L+ /srv/git/git-shell-commands/manage - - - - ${gitShellCommands}/manage"
+ # bare interactive ssh (ssh -t git@...) runs the manager instead of
+ # git-shell's "not enabled" error. git push/clone use -c, unaffected.
+ "L+ /srv/git/git-shell-commands/no-interactive-login - - - - ${gitShellCommands}/manage"
+ ];
+
+ users.users.git = {
+ isNormalUser = true;
+ group = "git";
+ home = "/srv/git"; # makes git@git:<repo>.git resolve to /srv/git/<repo>.git
+ createHome = false;
+ shell = "${pkgs.git}/bin/git-shell";
+ openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAYYjB+fK4JFlJSY6Ax48p38eSuDG12VQVqP+WSrWGuO"
+ ];
+ };
+ users.groups.git = { };
+
+ # Repos are owned by the git user; the cgit user (cgit.cgi /
+ # git-http-backend) only reads them. Exempt them from git's
+ # "dubious ownership" refusal. The templateDir gives every new repo
+ # the post-receive hook below.
+ environment.etc."gitconfig".text = ''
+ [safe]
+ directory = /srv/git/*
+ [init]
+ templateDir = /etc/git-template
+ '';
+
+ # Copy of the official hook:
+ # https://git.zx2c4.com/cgit/tree/contrib/hooks/post-receive.agefile
+ environment.etc."git-template/hooks/post-receive" = {
+ mode = "0755";
+ text = ''
+ #!/bin/sh
+ #
+ # An example hook to update the "agefile" for CGit's idle time calculation.
+ #
+ # This hook assumes that you are using the default agefile location of
+ # "info/web/last-modified". If you change the value in your cgitrc then you
+ # must also change it here.
+ #
+ # To install the hook, copy (or link) it to the file "hooks/post-receive" in
+ # each of your repositories.
+ #
+
+ agefile="$(git rev-parse --git-dir)"/info/web/last-modified
+
+ mkdir -p "$(dirname "$agefile")" &&
+ git for-each-ref \
+ --sort=-authordate --count=1 \
+ --format='%(authordate:iso8601)' \
+ >"$agefile"
+ '';
+ };
+
+ services.nginx.virtualHosts."git".locations."~ /.+/(git-receive-pack)" = {
+ return = "403";
+ };
+
+ # Serve the light+dark stylesheet instead of the package's light-only one
+ services.nginx.virtualHosts."git".locations."= /cgit.css".alias = cgitCss;
+
+ # Headroom for large git operations
+ swapDevices = [ { device = "/var/lib/swapfile"; size = 2048; } ];
+
+ };
+}
diff --git a/nix/per-host/git/git-shell-commands/create b/nix/per-host/git/git-shell-commands/create
new file mode 100755
index 0000000..459f428
--- /dev/null
+++ b/nix/per-host/git/git-shell-commands/create
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# git-shell-commands(5) subcommand: create a new bare repo.
+# Prompts for each configurable setting: name (if not given as an
+# argument), description, owner and visibility. Pipe </dev/null to accept
+# the defaults (empty description, owner kdam0, private).
+# Run: ssh git@git.kumardamani.net create [name]
+
+name="${1:-}"
+
+if [ -z "$name" ]; then
+ printf 'repo name: '
+ read -r name
+fi
+case "$name" in
+ ""|*[!a-zA-Z0-9._-]*|*..*)
+ echo "error: invalid name '$name' (allowed: a-z 0-9 . _ -)" >&2
+ exit 1
+ ;;
+esac
+repo="/srv/git/$name.git"
+if [ -e "$repo" ]; then
+ echo "error: repo '$name' already exists" >&2
+ exit 1
+fi
+
+printf 'description (optional): '
+read -r desc
+
+printf 'owner [kdam0]: '
+read -r owner
+owner="${owner:-kdam0}"
+
+printf 'publish now? [y/N]: '
+read -r pub
+
+git init --bare --quiet "$repo"
+git -C "$repo" config gitweb.owner "$owner"
+[ -n "$desc" ] && printf '%s\n' "$desc" > "$repo/description"
+
+case "$pub" in
+ y|Y|yes)
+ touch "$repo/git-daemon-export-ok"
+ echo "created public repo '$name': https://git.kumardamani.net/$name"
+ ;;
+ *)
+ echo "created private repo '$name'"
+ echo "publish it with: ssh git@git.kumardamani.net publish $name"
+ ;;
+esac
diff --git a/nix/per-host/git/git-shell-commands/desc b/nix/per-host/git/git-shell-commands/desc
new file mode 100755
index 0000000..cd0e569
--- /dev/null
+++ b/nix/per-host/git/git-shell-commands/desc
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# git-shell-commands(5) subcommand: set or show a repo's description as
+# displayed in cgit.
+# Run: ssh git@git.kumardamani.net desc <name> [description...]
+
+name="${1:-}"
+case "$name" in
+ ""|*[!a-zA-Z0-9._-]*|*..*)
+ echo "usage: desc <name> [description...]" >&2
+ exit 1
+ ;;
+esac
+repo="/srv/git/$name.git"
+if [ ! -d "$repo" ]; then
+ echo "error: no such repo '$name'" >&2
+ exit 1
+fi
+shift
+if [ $# -eq 0 ]; then
+ cat "$repo/description"
+ exit 0
+fi
+printf '%s\n' "$*" > "$repo/description"
+echo "description for '$name':"
+cat "$repo/description"
diff --git a/nix/per-host/git/git-shell-commands/hide b/nix/per-host/git/git-shell-commands/hide
new file mode 100755
index 0000000..4208e08
--- /dev/null
+++ b/nix/per-host/git/git-shell-commands/hide
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# git-shell-commands(5) subcommand: make a repo private again by removing
+# the git-daemon-export-ok marker. SSH access is unaffected.
+# Run: ssh git@git.kumardamani.net hide <name>
+
+name="${1:-}"
+case "$name" in
+ ""|*[!a-zA-Z0-9._-]*|*..*)
+ echo "usage: hide <name>" >&2
+ exit 1
+ ;;
+esac
+repo="/srv/git/$name.git"
+if [ ! -d "$repo" ]; then
+ echo "error: no such repo '$name'" >&2
+ exit 1
+fi
+rm -f "$repo/git-daemon-export-ok"
+echo "hidden (private): '$name'"
diff --git a/nix/per-host/git/git-shell-commands/manage b/nix/per-host/git/git-shell-commands/manage
new file mode 100755
index 0000000..01fd52e
--- /dev/null
+++ b/nix/per-host/git/git-shell-commands/manage
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+# git-shell-commands(5) subcommand: interactive repo manager.
+# Run: ssh -t git@git.kumardamani.net manage
+# Also symlinked as no-interactive-login, so a bare interactive ssh
+# (ssh -t git@git.kumardamani.net) lands here directly.
+
+cmds=/srv/git/git-shell-commands
+
+valid_name() {
+ case "$1" in
+ ""|*[!a-zA-Z0-9._-]*|*..*) return 1 ;;
+ *) return 0 ;;
+ esac
+}
+
+list_repos() {
+ printf '%-24s %-8s %s\n' REPO STATE DESCRIPTION
+ for repo in /srv/git/*.git; do
+ [ -d "$repo" ] || continue
+ name="${repo##*/}"
+ name="${name%.git}"
+ if [ -f "$repo/git-daemon-export-ok" ]; then
+ state=public
+ else
+ state=private
+ fi
+ desc="$(cat "$repo/description" 2>/dev/null)"
+ case "$desc" in Unnamed*) desc= ;; esac
+ printf '%-24s %-8s %s\n' "$name" "$state" "$desc"
+ done
+}
+
+ask_name() {
+ printf 'repo name: '
+ read -r name
+ if ! valid_name "$name"; then
+ echo "invalid name '$name' (allowed: a-z 0-9 . _ -)" >&2
+ return 1
+ fi
+}
+
+while true; do
+ echo
+ echo "== git repo manager =="
+ echo " l) list repos"
+ echo " c) create repo"
+ echo " p) publish repo"
+ echo " h) hide repo"
+ echo " d) set description"
+ echo " x) delete repo"
+ echo " q) quit"
+ printf '> '
+ read -r choice || break
+ case "$choice" in
+ l) list_repos ;;
+ c) "$cmds/create" ;; # create prompts for name + settings itself
+ p) ask_name && "$cmds/publish" "$name" ;;
+ h) ask_name && "$cmds/hide" "$name" ;;
+ d)
+ ask_name && {
+ printf 'description: '
+ read -r text
+ "$cmds/desc" "$name" "$text"
+ }
+ ;;
+ x)
+ ask_name && [ -d "/srv/git/$name.git" ] && {
+ printf 'type the repo name again to confirm DELETE: '
+ read -r confirm
+ if [ "$confirm" = "$name" ]; then
+ rm -rf "/srv/git/$name.git"
+ echo "deleted '$name'"
+ else
+ echo "aborted"
+ fi
+ }
+ ;;
+ q|quit|exit) break ;;
+ *) echo "unknown choice '$choice'" ;;
+ esac
+done
diff --git a/nix/per-host/git/git-shell-commands/publish b/nix/per-host/git/git-shell-commands/publish
new file mode 100755
index 0000000..54aff26
--- /dev/null
+++ b/nix/per-host/git/git-shell-commands/publish
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# git-shell-commands(5) subcommand: make a repo public (browsable in cgit
+# and cloneable over HTTP) by creating the git-daemon-export-ok marker.
+# Run: ssh git@git.kumardamani.net publish <name>
+
+name="${1:-}"
+case "$name" in
+ ""|*[!a-zA-Z0-9._-]*|*..*)
+ echo "usage: publish <name>" >&2
+ exit 1
+ ;;
+esac
+repo="/srv/git/$name.git"
+if [ ! -d "$repo" ]; then
+ echo "error: no such repo '$name'" >&2
+ exit 1
+fi
+touch "$repo/git-daemon-export-ok"
+echo "published: https://git.kumardamani.net/$name"