Home

ugit @main - refs - log -
-
https://git.jolheiser.com/ugit.git
The code powering this h*ckin' site
tree log patch
config: convert to jsonnet Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQIoz+V/EavhrUXnlGcgJNlsGjUicAKYH2bhnhmlc5vy6flhRBf87/9Bdby1jOGsJK0 wG4GkVNZv8TUo6WGRtIAY= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
9 hours ago
8 changed files, 297 additions(+), 52 deletions(-)
README.mdcmd/ugitd/args.gocmd/ugitd/schema.cuego.modgo.mod.srigo.sumnix/module.nixnix/vm.nix
M README.md -> README.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
diff --git a/README.md b/README.md
index ad7ae4fa9e494db1086da5e7c9ce66f6c5404ac1..324fd3b19301b5046c088372b18bc3ae867a86aa 100644
--- a/README.md
+++ b/README.md
@@ -68,10 +68,8 @@         {
           services.ugit.public = {
             enable = true;
             authorizedKeysFile = "/etc/ugit/authorized_keys";
-            config = {
-              show-private = false;
-              http.port = 8449;
-            };
+            showPrivate = false;
+            http.port = 8449;
           };
         }
       ];
@@ -88,21 +86,23 @@ µgit needs write access to its repo directory, SSH host key path, and (if generated) authorized_keys path.
 
 ## Configuration
 
-µgit is configured via CLI flags, environment variables, and/or a YAML config file, in that order of precedence.
+µgit is configured via CLI flags, environment variables, and/or a JSONnet config file, in that order of precedence.
 
 - CLI flags use the names below, e.g. `--ssh.port=2222`
 - Environment variables are the flag name upper-cased with `.` and `-` replaced by `_`, prefixed with `UGIT_`,
   e.g. `ssh.port` becomes `UGIT_SSH_PORT`
-- The config file is YAML, with dotted flag names expanded into
-  nested keys, e.g.
-  ```yaml
-  ssh:
-    port: 2222
+The config file is JSONnet, with dotted flag names expanded into nested keys, e.g.
+  ```jsonnet
+  {
+    ssh: {
+      port: 2222
+    }
+  }
   ```
 
 | Flag                     | Default                     | Description                                                  |
 |--------------------------|-----------------------------|--------------------------------------------------------------|
-| `--config`               | `ugit.yaml`                 | Path to config file                                          |
+| `--config`               | `ugit.jsonnet`              | Path to config file                                          |
 | `--repo-dir`             | `.ugit`                     | Path to directory containing repositories                    |
 | `--show-private`         | `false`                     | Show private repos in the web interface                      |
 | `--log.level`            | `error`                     | Logging level: `debug`, `info`, `warn`, `error`              |
M cmd/ugitd/args.go -> cmd/ugitd/args.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
diff --git a/cmd/ugitd/args.go b/cmd/ugitd/args.go
index 5dd586ad4dc96aaabe420f74e02da5e4f4b09777..e7b42a6037ee8c1e80de0459bac6f126e76814d7 100644
--- a/cmd/ugitd/args.go
+++ b/cmd/ugitd/args.go
@@ -1,14 +1,18 @@
 package main
 
 import (
+	_ "embed"
 	"flag"
 	"fmt"
 	"log/slog"
 	"strings"
 
 	"github.com/peterbourgon/ff/v3"
-	"github.com/peterbourgon/ff/v3/ffyaml"
+	"go.jolheiser.com/ffjsonnet"
 )
+
+//go:embed schema.cue
+var schema string
 
 type cliArgs struct {
 	RepoDir     string
@@ -57,7 +61,7 @@ }
 
 func parseArgs(args []string) (c cliArgs, e error) {
 	fs := flag.NewFlagSet("ugitd", flag.ContinueOnError)
-	fs.String("config", "ugit.yaml", "Path to config file")
+	fs.String("config", "ugit.jsonnet", "Path to config file")
 
 	c = cliArgs{
 		RepoDir: ".ugit",
@@ -126,10 +130,13 @@ 		})
 		return nil
 	})
 
+	parser := ffjsonnet.ParseConfig{
+		Schema: schema,
+	}
 	return c, ff.Parse(fs, args,
 		ff.WithEnvVarPrefix("UGIT"),
 		ff.WithConfigFileFlag("config"),
 		ff.WithAllowMissingConfigFile(true),
-		ff.WithConfigFileParser(ffyaml.Parser),
+		ff.WithConfigFileParser(parser.Parse),
 	)
 }
I cmd/ugitd/schema.cue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
diff --git a/cmd/ugitd/schema.cue b/cmd/ugitd/schema.cue
new file mode 100644
index 0000000000000000000000000000000000000000..f6549374d238d9f2d57470c4cf27c2cfb7a62e7f
--- /dev/null
+++ b/cmd/ugitd/schema.cue
@@ -0,0 +1,45 @@
+#Port: int & >0 & <65536
+
+#LogLevel: "debug" | "info" | "warn" | "warning" | "error"
+
+#SSHArgs: {
+	enable?:            bool
+	"authorized-keys"?: string
+	"clone-url"?:       string
+	port?:              #Port
+	"host-key"?:        string
+}
+
+#HTTPArgs: {
+	enable?:      bool
+	"clone-url"?: string
+	port?:        #Port
+}
+
+#MetaArgs: {
+	title?:       string
+	description?: string
+}
+
+#ProfileArgs: {
+	username?: string
+	email?:    string
+	links?: [...string]
+}
+
+#LogArgs: {
+	level?: #LogLevel
+	json?:  bool
+}
+
+#Schema: {
+	"repo-dir"?:     string
+	"show-private"?: bool
+	ssh?:            #SSHArgs
+	http?:           #HTTPArgs
+	meta?:           #MetaArgs
+	profile?:        #ProfileArgs
+	log?:            #LogArgs
+}
+
+#Schema
M go.mod -> go.mod
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
diff --git a/go.mod b/go.mod
index 7c5bd7c0449ee66f9972b3f2e64b04a4a32a8990..d4ae8d486cd97d79411a0f62fd1ee70434af5edc 100644
--- a/go.mod
+++ b/go.mod
@@ -17,10 +17,12 @@ 	github.com/peterbourgon/ff/v3 v3.4.0
 	github.com/yuin/goldmark v1.8.4
 	github.com/yuin/goldmark-emoji v1.0.6
 	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
+	go.jolheiser.com/ffjsonnet v0.0.0-20260610143310-849918e000d4
 	golang.org/x/net v0.57.0
 )
 
 require (
+	cuelang.org/go v0.16.1 // indirect
 	dario.cat/mergo v1.0.2 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/ProtonMail/go-crypto v1.4.1 // indirect
@@ -44,17 +46,21 @@ 	github.com/cli/browser v1.3.0 // indirect
 	github.com/clipperhouse/displaywidth v0.11.0 // indirect
 	github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
 	github.com/cloudflare/circl v1.6.4 // indirect
+	github.com/cockroachdb/apd/v3 v3.2.3 // indirect
 	github.com/creack/pty v1.1.24 // indirect
 	github.com/cyphar/filepath-securejoin v0.7.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/dlclark/regexp2/v2 v2.5.1 // indirect
+	github.com/emicklei/proto v1.14.3 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
-	github.com/fatih/color v1.16.0 // indirect
+	github.com/fatih/color v1.18.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 	github.com/go-logfmt/logfmt v0.6.1 // indirect
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+	github.com/google/go-jsonnet v0.22.0 // indirect
+	github.com/google/uuid v1.6.0 // indirect
 	github.com/hexops/gotextdiff v1.0.3 // indirect
 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/kevinburke/ssh_config v1.6.0 // indirect
@@ -64,17 +70,23 @@ 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.23 // indirect
 	github.com/mattn/go-localereader v0.0.1 // indirect
 	github.com/mattn/go-runewidth v0.0.24 // indirect
+	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
 	github.com/muesli/cancelreader v0.2.2 // indirect
 	github.com/muesli/termenv v0.16.0 // indirect
 	github.com/natefinch/atomic v1.0.1 // indirect
+	github.com/pelletier/go-toml/v2 v2.3.1 // indirect
 	github.com/pjbgf/sha1cd v0.6.0 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/protocolbuffers/txtpbfmt v0.0.0-20260420112717-c39628bde8b5 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/sergi/go-diff v1.4.0 // indirect
 	github.com/skeema/knownhosts v1.3.2 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
+	go.jolheiser.com/cuesonnet v0.0.0-20260608154847-ad92adf766cc // indirect
+	go.yaml.in/yaml/v2 v2.4.4 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
 	golang.org/x/crypto v0.54.0 // indirect
 	golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect
 	golang.org/x/mod v0.38.0 // indirect
@@ -82,8 +94,9 @@ 	golang.org/x/sync v0.22.0 // indirect
 	golang.org/x/sys v0.47.0 // indirect
 	golang.org/x/text v0.40.0 // indirect
 	golang.org/x/tools v0.48.0 // indirect
+	google.golang.org/protobuf v1.36.11 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
 )
 
 tool (
M go.mod.sri -> go.mod.sri
1
2
3
4
5
6
7
8
9
diff --git a/go.mod.sri b/go.mod.sri
index cfd71df118388a2b6ab97c4880ca97130268ca77..956bc1c316a7de9eb104f31ba96288c7ed9a8553 100644
--- a/go.mod.sri
+++ b/go.mod.sri
@@ -1 +1 @@
-sha256-q6aKqeGyd90Cg2o3LDAJ2vLIWVxwIICEDwd5/RN1STI=
\ No newline at end of file
+sha256-Aar2RLjcRjD3dvdtzQBmB2JEulbl5v8Te6JTgnO+xZA=
\ No newline at end of file
M go.sum -> go.sum
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
diff --git a/go.sum b/go.sum
index c58e65bc63e74384f928dfe1aa24d3b212f1f8b7..964ca6a8cdf5e945d94d90888258d2a765717ab2 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 h1:Zh+Ur3OsoWpvALHPLT45nOekHkgOt+IOfutBbPqM17I=
+cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819/go.mod h1:WjmQxb+W6nVNCgj8nXrF24lIz95AHwnSl36tpjDZSU8=
+cuelang.org/go v0.16.1 h1:iPN1lHZd2J0hjcr8hfq9PnIGk7VfPkKFfxH4de+m9sE=
+cuelang.org/go v0.16.1/go.mod h1:/aW3967FeWC5Hc1cDrN4Z4ICVApdMi83wO5L3uF/1hM=
 dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
 dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
@@ -59,6 +63,8 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
 github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
 github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
 github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
+github.com/cockroachdb/apd/v3 v3.2.3 h1:4Zx+I3R35bFXMnltzmjP79i2cravE4jTRL6ps9Aux80=
+github.com/cockroachdb/apd/v3 v3.2.3/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
 github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
 github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
 github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE=
@@ -75,12 +81,14 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
 github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
+github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q=
+github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
 github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
-github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
-github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -99,10 +107,16 @@ github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
 github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
 github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
 github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
+github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
+github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/go-jsonnet v0.22.0 h1:o0bOAIE+9SIfRZ7FXQPuta0mHLLE0AwbY/L5GTH5CH8=
+github.com/google/go-jsonnet v0.22.0/go.mod h1:pLhKpu0/ODjL2Zev4y+CmCoHKAgONT1gSLQyriuYh9w=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -118,6 +132,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
 github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -129,6 +147,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
 github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
 github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
 github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
+github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -139,6 +159,12 @@ github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
 github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
 github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
 github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
+github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
+github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
 github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
 github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
@@ -148,6 +174,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20260420112717-c39628bde8b5 h1:Mckui8l+Wqz2Ve7XQvsE8SbHNmDWu8NA7Xce5NFJ/kM=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20260420112717-c39628bde8b5/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -174,6 +202,14 @@ github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
 github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
+go.jolheiser.com/cuesonnet v0.0.0-20260608154847-ad92adf766cc h1:pYFHXKY2BOjdK6ag9yr3XTNMTvfovHeldtndZmuo4tg=
+go.jolheiser.com/cuesonnet v0.0.0-20260608154847-ad92adf766cc/go.mod h1:Vbhj9tI/M7a/p32d/PD3v4FgsL5lQWrcxq8ckYZfYCc=
+go.jolheiser.com/ffjsonnet v0.0.0-20260610143310-849918e000d4 h1:kYm616mK6ki+lPMxpW4ggr9T5BASjeWwfMLPBMK3l2M=
+go.jolheiser.com/ffjsonnet v0.0.0-20260610143310-849918e000d4/go.mod h1:NjqnvA6DhensjkW+6BDMBZuUJc11EX9zqS4GPI0OcUo=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
 golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
@@ -184,6 +220,8 @@ golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
 golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
 golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
 golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -205,6 +243,8 @@ golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
 golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -212,8 +252,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
M nix/module.nix -> nix/module.nix
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
diff --git a/nix/module.nix b/nix/module.nix
index a6855c09edab68c3cced342f3a2d3e565ae1eca1..a40412eae67946fc8ab774a472deee7b803321c2 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -7,7 +7,33 @@ }:
 let
   cfg = config.services.ugit;
   pkg = pkgs.callPackage ./pkg.nix { inherit pkgs; };
-  yamlFormat = pkgs.formats.yaml { };
+  jsonFormat = pkgs.formats.json { };
+
+  profileLinkOption = {
+    options = {
+      name = lib.mkOption {
+        type = lib.types.str;
+        description = "Display name for the link";
+      };
+      url = lib.mkOption {
+        type = lib.types.str;
+        description = "URL for the link";
+      };
+    };
+  };
+  hookOption = {
+    options = {
+      name = lib.mkOption {
+        type = lib.types.str;
+        description = "Hook name";
+      };
+      content = lib.mkOption {
+        type = lib.types.str;
+        description = "Hook contents";
+      };
+    };
+
+  };
   instanceOptions =
     { name, config, ... }:
     let
@@ -54,12 +80,6 @@           description = "path to host key file (will be created if it doesn't exist)";
           default = "${baseDir}/ugit_ed25519";
         };
 
-        config = mkOption {
-          type = types.attrs;
-          default = { };
-          description = "config.yaml contents";
-        };
-
         user = mkOption {
           type = types.str;
           default = "ugit-${name}";
@@ -73,25 +93,144 @@           description = "Group account under which ugit runs";
         };
 
         hooks = mkOption {
-          type = types.listOf (
-            types.submodule {
-              options = {
-                name = mkOption {
-                  type = types.str;
-                  description = "Hook name";
-                };
-                content = mkOption {
-                  type = types.str;
-                  description = "Hook contents";
-                };
-              };
-            }
-          );
+          type = types.listOf (types.submodule hookOption);
           description = "A list of pre-receive hooks to run";
           default = [ ];
         };
+
+        showPrivate = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Show private repos in web interface";
+        };
+
+        ssh = {
+          enable = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Enable SSH server";
+          };
+
+          cloneUrl = mkOption {
+            type = types.str;
+            default = "ssh://localhost:8448";
+            description = "SSH clone URL base, e.g. ssh://host:port";
+          };
+
+          port = mkOption {
+            type = types.port;
+            default = 8448;
+            description = "SSH port";
+          };
+        };
+
+        http = {
+          enable = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Enable HTTP server";
+          };
+
+          cloneUrl = mkOption {
+            type = types.str;
+            default = "http://localhost:8449";
+            description = "HTTP clone URL base, e.g. http://host:port";
+          };
+
+          port = mkOption {
+            type = types.port;
+            default = 8449;
+            description = "HTTP port";
+          };
+        };
+
+        meta = {
+          title = mkOption {
+            type = types.str;
+            default = "ugit";
+            description = "App title";
+          };
+
+          description = mkOption {
+            type = types.str;
+            default = "Minimal git server";
+            description = "App description";
+          };
+        };
+
+        profile = {
+          username = mkOption {
+            type = types.str;
+            default = "";
+            description = "Username for index page";
+          };
+
+          email = mkOption {
+            type = types.str;
+            default = "";
+            description = "Email for index page";
+          };
+
+          links = mkOption {
+            type = types.listOf (types.submodule profileLinkOption);
+            default = [ ];
+            description = "Link(s) for index page";
+          };
+        };
+
+        log = {
+          level = mkOption {
+            type = types.enum [
+              "debug"
+              "info"
+              "warn"
+              "warning"
+              "error"
+            ];
+            default = "error";
+            description = "Logging level";
+          };
+
+          json = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Print logs in JSON(L) format";
+          };
+        };
+
+        extraConfig = mkOption {
+          type = types.attrs;
+          default = { };
+          description = "Extra config.jsonnet fields, merged over the generated config";
+        };
       };
     };
+  mkConfig =
+    cfg: authorizedKeysPath:
+    lib.recursiveUpdate {
+      "repo-dir" = cfg.repoDir;
+      "show-private" = cfg.showPrivate;
+      ssh = {
+        inherit (cfg.ssh) enable port;
+        "clone-url" = cfg.ssh.cloneUrl;
+        "authorized-keys" = authorizedKeysPath;
+        "host-key" = cfg.hostKeyFile;
+      };
+      http = {
+        inherit (cfg.http) enable port;
+        "clone-url" = cfg.http.cloneUrl;
+      };
+      meta = {
+        inherit (cfg.meta) title description;
+      };
+      profile = {
+        inherit (cfg.profile) username email;
+        links = map (l: "${l.name},${l.url}") cfg.profile.links;
+      };
+      log = {
+        inherit (cfg.log) level json;
+      };
+    } cfg.extraConfig;
 in
 {
   options = {
@@ -182,26 +321,21 @@               PrivateMounts = true;
               SystemCallArchitectures = "native";
               ExecStart =
                 let
-                  configFile = pkgs.writeText "ugit-${name}.yaml" (
-                    builtins.readFile (yamlFormat.generate "ugit-${name}-yaml" instanceCfg.config)
-                  );
                   authorizedKeysFile = pkgs.writeText "ugit_${name}_keys" (
                     builtins.concatStringsSep "\n" instanceCfg.authorizedKeys
                   );
-
                   authorizedKeysPath =
                     if (builtins.length instanceCfg.authorizedKeys) > 0 then
                       authorizedKeysFile
                     else
                       instanceCfg.authorizedKeysFile;
-                  args = [
-                    "--config=${configFile}"
-                    "--repo-dir=${instanceCfg.repoDir}"
-                    "--ssh.authorized-keys=${authorizedKeysPath}"
-                    "--ssh.host-key=${instanceCfg.hostKeyFile}"
-                  ];
+                  configFile = pkgs.writeText "ugit-${name}.jsonnet" (
+                    builtins.readFile (
+                      jsonFormat.generate "ugit-${name}-jsonnet" (mkConfig instanceCfg authorizedKeysPath)
+                    )
+                  );
                 in
-                "${instanceCfg.package}/bin/ugitd ${builtins.concatStringsSep " " args}";
+                "${instanceCfg.package}/bin/ugitd --config=${configFile}";
             };
           };
 
M nix/vm.nix -> nix/vm.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
diff --git a/nix/vm.nix b/nix/vm.nix
index 087fa6b88148c81845ec1a779f0b9498727e6e05..5c9d4d5f3be1ed485c9fb1398490244436e6e90f 100644
--- a/nix/vm.nix
+++ b/nix/vm.nix
@@ -27,6 +27,11 @@   services.openssh.enable = true;
   services.ugit.vm = {
     enable = true;
     authorizedKeys = [ pubKey ];
+    showPrivate = true;
+    log = {
+      level = "debug";
+      json = true;
+    };
     hooks = [
       {
         name = "pre-receive";