diff options
| author | Kumar Damani <me@kumardamani.net> | 2026-08-02 01:43:25 +0000 |
|---|---|---|
| committer | Kumar Damani <me@kumardamani.net> | 2026-08-02 01:43:25 +0000 |
| commit | 43930cb8c22bc28ed64362845c5f4ebf54faabf2 (patch) | |
| tree | 7351357bd38139b0267f1a4cdeff4b5143f5e89a /nix/per-host | |
| parent | 3e139a0e702513f6e1145158b2695b43c8251b92 (diff) | |
added git with cgit
Diffstat (limited to 'nix/per-host')
| -rw-r--r-- | nix/per-host/git/cgit-dark.css | 337 | ||||
| -rw-r--r-- | nix/per-host/git/configuration.nix | 141 | ||||
| -rwxr-xr-x | nix/per-host/git/git-shell-commands/create | 49 | ||||
| -rwxr-xr-x | nix/per-host/git/git-shell-commands/desc | 25 | ||||
| -rwxr-xr-x | nix/per-host/git/git-shell-commands/hide | 19 | ||||
| -rwxr-xr-x | nix/per-host/git/git-shell-commands/manage | 81 | ||||
| -rwxr-xr-x | nix/per-host/git/git-shell-commands/publish | 19 |
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" |
