diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e660fd93d3196215552065b1e63bf6a2f393ed86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+bin/
diff --git a/Makefile b/Makefile
index 7a0e125baadd8ba25b560106a0b1bd91ec30df17..ecf165dce0342209fc2879c732b2eb8d7b06eba0 100644
--- a/Makefile
+++ b/Makefile
@@ -3,3 +3,6 @@ go build -o bin/cerrado
run:
go run .
+
+test:
+ go test -v --tags=unit ./...
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5a723e55161cbc997e4cbeec226fd7bd9555e258
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Cerrado
+
+Read only single user mail based forge for git.
diff --git a/config.example.scfg b/config.example.scfg
new file mode 100644
index 0000000000000000000000000000000000000000..eda4f38d14d06608e6447089bbf13c5416092d65
--- /dev/null
+++ b/config.example.scfg
@@ -0,0 +1,22 @@
+scan /srv/git/ {
+ public true
+}
+
+# TBD
+#user admin:iKlvHe1g0UoXE
+#
+#list main {
+# server smtp.example.com
+# user admin@admin.com
+# password 1234567
+# security tls
+# authentication plain
+# default false
+#}
+#
+#repository cerrado {
+# title Cerrado
+# description "Self host single person readonly forge"
+# list main
+# public true
+#}
diff --git a/go.mod b/go.mod
index cca006e89444dbdcf6c81c9f4f0f9acf6ed0bb6b..bfbe03ad8f3b8502d8d8f9ba9d3967552b1171e1 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,8 @@ module git.gabrielgio.me/cerrado
go 1.22.2
-require golang.org/x/sync v0.7.0 // indirect
+require (
+ git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082
+ github.com/google/go-cmp v0.6.0
+ golang.org/x/sync v0.7.0
+)
diff --git a/go.sum b/go.sum
index e8ef4a360a1437536e8a3053e4ffc82fc69ce9c5..a98204454c0ef47546d171a1dada140655d0b45e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,8 @@
+git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082 h1:9Udx5fm4vRtmgDIBjy2ef5QioHbzpw5oHabbhpAUyEw=
+git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
diff --git a/main.go b/main.go
index 7c80564f57dfa106d4a4074f6c4586bc0a9c18ba..ba441fe71b8c6c0ea4aff7a8805d2e4729059e27 100644
--- a/main.go
+++ b/main.go
@@ -2,16 +2,20 @@ package main
import (
"context"
+ "encoding/json"
+ "flag"
"log/slog"
"net/http"
"os"
"os/signal"
"time"
+ "git.gabrielgio.me/cerrado/pkg/config"
"git.gabrielgio.me/cerrado/pkg/worker"
)
func main() {
+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()
if err := run(ctx); err != nil {
@@ -20,10 +24,36 @@ }
}
func run(ctx context.Context) error {
+ var (
+ configPath = flag.String("config", "config.example.scfg", "File path for the configuration file")
+ )
+
+ flag.Parse()
+
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
- if _, err := w.Write([]byte("Hello world!")); err != nil {
+
+ f, err := os.Open(*configPath)
+ if err != nil {
+ slog.Error("Error openning config file json", "error", err, "path", *configPath)
+ return
+ }
+
+ c, err := config.Parse(f)
+ if err != nil {
+ slog.Error("Error parsing config", "error", err, "path", *configPath)
+ return
+ }
+
+ b, err := json.MarshalIndent(c, "", " ")
+ if err != nil {
+ slog.Error("Error parsing json", "error", err)
+ return
+ }
+
+ if _, err := w.Write(b); err != nil {
slog.Error("Error handling index", "error", err)
+ return
}
})
serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..ba1614f6fa318316db0b44a319846bf4a275bba6
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,84 @@
+package config
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+
+ "git.sr.ht/~emersion/go-scfg"
+)
+
+type (
+ Scan struct {
+ Path string
+ Public bool
+ }
+
+ Configuration struct {
+ Scan *Scan
+ }
+)
+
+func Parse(r io.Reader) (*Configuration, error) {
+ block, err := scfg.Read(r)
+ if err != nil {
+ return nil, err
+ }
+
+ config := defaultConfiguration()
+
+ err = setScan(block, config.Scan)
+ if err != nil {
+ return nil, err
+ }
+
+ return config, nil
+}
+
+func defaultConfiguration() *Configuration {
+ return &Configuration{
+ Scan: &Scan{
+ Public: true,
+ Path: "",
+ },
+ }
+}
+
+func setScan(block scfg.Block, scan *Scan) error {
+ scanDir := block.Get("scan")
+ err := setString(scanDir, &scan.Path)
+ if err != nil {
+ return err
+ }
+
+ public := scanDir.Children.Get("public")
+ return setBool(public, &scan.Public)
+}
+
+func setBool(dir *scfg.Directive, field *bool) error {
+
+ if dir != nil {
+ p1 := first(dir.Params)
+ v, err := strconv.ParseBool(p1)
+ if err != nil {
+ return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
+ }
+ *field = v
+ }
+ return nil
+}
+
+func setString(dir *scfg.Directive, field *string) error {
+ if dir != nil {
+ *field = first(dir.Params)
+ }
+ return nil
+}
+
+func first[T any](v []T) T {
+ if len(v) == 0 {
+ var zero T
+ return zero
+ }
+ return v[0]
+}
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8cd8876055e2cabe66e1ee574b5c36df358efcb
--- /dev/null
+++ b/pkg/config/config_test.go
@@ -0,0 +1,56 @@
+// go:build unit
+package config
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestConfig(t *testing.T) {
+ testCases := []struct {
+ name string
+ config string
+ expectedConfig *Configuration
+ }{
+ {
+ name: "minimal scan",
+ config: `scan "/srv/git"`,
+ expectedConfig: &Configuration{
+ Scan: &Scan{
+ Public: true,
+ Path: "/srv/git",
+ },
+ },
+ },
+ {
+ name: "complete scan",
+ config: `scan "/srv/git" {
+ public false
+}`,
+ expectedConfig: &Configuration{
+ Scan: &Scan{
+ Public: false,
+ Path: "/srv/git",
+ },
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ r := strings.NewReader(tc.config)
+ config, err := Parse(r)
+ if err != nil {
+ t.Fatalf("Error parsing config %s", err.Error())
+ }
+
+ if diff := cmp.Diff(tc.expectedConfig, config); diff != "" {
+ t.Errorf("Wrong result given - wanted + got\n %s", diff)
+ }
+
+ })
+
+ }
+}