Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2024-01-29 09:10:46
Commit2ac8bbb62765cdf913e0516e728406c8c5e37f01
Parent813b687f0799ead13f2d7eb6982c2754659f826b

Replace XDG dependency with a custom solution

Also use /etc/goit, /var/lib/goit, /var/log/goit, and /run instead of XDG directories when running as root.

Diffstat

M go.mod | 1 -
M go.sum | 3 ---
A src/goit/config.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M src/goit/goit.go | 68 ++++++++++++++++----------------------------------------------------
M src/main.go | 7 +++----

5 files changed, 142 insertions, 60 deletions

diff --git a/go.mod b/go.mod
index 89c75f6..334edbc 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/Jamozed/Goit
 go 1.21.0
 
 require (
-	github.com/adrg/xdg v0.4.0
 	github.com/alecthomas/chroma v0.10.0
 	github.com/buildkite/terminal-to-html/v3 v3.10.1
 	github.com/dustin/go-humanize v1.0.1
diff --git a/go.sum b/go.sum
index 3c9e76f..ed00554 100644
--- a/go.sum
+++ b/go.sum
@@ -5,8 +5,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
 github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
 github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
 github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
-github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
-github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
 github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
 github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -123,7 +121,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/src/goit/config.go b/src/goit/config.go
new file mode 100644
index 0000000..be54bff
--- /dev/null
+++ b/src/goit/config.go
@@ -0,0 +1,123 @@
+// Copyright (C) 2024, Jakob Wakeling
+// All rights reserved.
+
+package goit
+
+import (
+	"encoding/json"
+	"errors"
+	"os"
+	"path/filepath"
+)
+
+type config struct {
+	DataPath    string `json:"data_path"`
+	LogsPath    string `json:"logs_path"`
+	RuntimePath string `json:"runtime_path"`
+	HttpAddr    string `json:"http_addr"`
+	HttpPort    string `json:"http_port"`
+	GitPath     string `json:"git_path"`
+	IpSessions  bool   `json:"ip_sessions"`
+	UsesHttps   bool   `json:"uses_https"`
+	IpForwarded bool   `json:"ip_forwarded"`
+	CsrfSecret  string `json:"csrf_secret"`
+}
+
+func loadConfig() (config, error) {
+	conf := config{
+		DataPath:    dataPath(),
+		LogsPath:    logsPath(),
+		RuntimePath: runtimePath(),
+		HttpAddr:    "",
+		HttpPort:    "8080",
+		GitPath:     "git",
+		IpSessions:  true,
+		UsesHttps:   false,
+		IpForwarded: false,
+		CsrfSecret:  "1234567890abcdef1234567890abcdef",
+	}
+
+	/* Load config file(s) */
+	configs := []string{
+		filepath.Join("/etc", "goit", "goit.conf"),
+	}
+
+	if os.Getuid() != 0 {
+		configs = append(configs, filepath.Join(userConfigBase(), "goit", "goit.conf"))
+	}
+
+	for _, file := range configs {
+		if data, err := os.ReadFile(file); err != nil {
+			if !errors.Is(err, os.ErrNotExist) {
+				return config{}, err
+			}
+		} else if data != nil {
+			if json.Unmarshal(data, &conf); err != nil {
+				return config{}, err
+			}
+		}
+	}
+
+	/* Check required config values */
+	if conf.DataPath == "" {
+		return config{}, errors.New("data path unset")
+	}
+
+	return conf, nil
+}
+
+func userConfigBase() string {
+	if path := os.Getenv("XDG_CONFIG_HOME"); path != "" {
+		return path
+	}
+
+	if path := os.Getenv("HOME"); path != "" {
+		return filepath.Join(path, ".config")
+	}
+
+	return ""
+}
+
+func dataPath() string {
+	if os.Getuid() == 0 {
+		return "/var/lib/goit"
+	}
+
+	if path := os.Getenv("XDG_DATA_HOME"); path != "" {
+		return filepath.Join(path, "goit")
+	}
+
+	if path := os.Getenv("HOME"); path != "" {
+		return filepath.Join(path, ".local", "share", "goit")
+	}
+
+	return ""
+}
+
+func logsPath() string {
+	if os.Getuid() == 0 {
+		return "/var/log/goit"
+	}
+
+	if path := os.Getenv("XDG_STATE_HOME"); path != "" {
+		return filepath.Join(path, "goit")
+	}
+
+	if path := os.Getenv("HOME"); path != "" {
+		return filepath.Join(path, ".local", "state", "goit")
+	}
+
+	return ""
+}
+
+func runtimePath() string {
+	if os.Getuid() == 0 {
+		return "/run"
+	}
+
+	if path := os.Getenv("XDG_RUNTIME_DIR"); path != "" {
+		return filepath.Join(path)
+	}
+
+	return ""
+}
diff --git a/src/goit/goit.go b/src/goit/goit.go
index f2ed0c8..0a799b2 100644
--- a/src/goit/goit.go
+++ b/src/goit/goit.go
@@ -21,34 +21,12 @@ import (
 	"github.com/Jamozed/Goit/res"
 	"github.com/Jamozed/Goit/src/cron"
 	"github.com/Jamozed/Goit/src/util"
-	"github.com/adrg/xdg"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	_ "github.com/mattn/go-sqlite3"
 )
 
-type Config struct {
-	DataPath    string `json:"data_path"`
-	HttpAddr    string `json:"http_addr"`
-	HttpPort    string `json:"http_port"`
-	GitPath     string `json:"git_path"`
-	IpSessions  bool   `json:"ip_sessions"`
-	UsesHttps   bool   `json:"uses_https"`
-	IpForwarded bool   `json:"ip_forwarded"`
-	CsrfSecret  string `json:"csrf_secret"`
-}
-
-var Conf = Config{
-	DataPath:    filepath.Join(xdg.DataHome, "goit"),
-	HttpAddr:    "",
-	HttpPort:    "8080",
-	GitPath:     "git",
-	IpSessions:  true,
-	UsesHttps:   false,
-	IpForwarded: false,
-	CsrfSecret:  "1234567890abcdef1234567890abcdef",
-}
-
+var Conf config
 var db *sql.DB
 var Favicon []byte
 var Cron *cron.Cron
@@ -57,23 +35,18 @@ var Reserved []string = []string{"admin", "repo", "static", "user"}
 
 var StartTime = time.Now()
 
-func Goit(conf string) (err error) {
-	if dat, err := os.ReadFile(conf); err != nil {
-		if !errors.Is(err, os.ErrNotExist) {
-			return fmt.Errorf("[Config] %w", err)
-		}
-	} else if dat != nil {
-		if json.Unmarshal(dat, &Conf); err != nil {
-			return fmt.Errorf("[Config] %w", err)
-		}
+func Goit() error {
+	if conf, err := loadConfig(); err != nil {
+		return err
+	} else {
+		Conf = conf
 	}
 
-	logPath, err := xdg.StateFile(filepath.Join("goit", fmt.Sprint("goit_", time.Now().Unix(), ".log")))
-	if err != nil {
-		log.Fatalln("[log]", err.Error())
+	if err := os.MkdirAll(Conf.LogsPath, 0o777); err != nil {
+		return fmt.Errorf("[config] %w", err)
 	}
 
-	logFile, err := os.Create(logPath)
+	logFile, err := os.Create(filepath.Join(Conf.LogsPath, fmt.Sprint("goit_", time.Now().Unix(), ".log")))
 	if err != nil {
 		log.Fatalln("[log]", err.Error())
 	}
@@ -83,37 +56,37 @@ func Goit(conf string) (err error) {
 
 	log.Println("[Config] using data path:", Conf.DataPath)
 	if err := os.MkdirAll(Conf.DataPath, 0o777); err != nil {
-		return fmt.Errorf("[Config] %w", err)
+		return fmt.Errorf("[config] %w", err)
 	}
 
 	if dat, err := os.ReadFile(filepath.Join(Conf.DataPath, "favicon.png")); err != nil {
-		log.Println("[Favicon]", err.Error())
+		log.Println("[favicon]", err.Error())
 	} else {
 		Favicon = dat
 	}
 
 	if db, err = sql.Open("sqlite3", filepath.Join(Conf.DataPath, "goit.db")); err != nil {
-		return fmt.Errorf("[Database] %w", err)
+		return fmt.Errorf("[database] %w", err)
 	}
 
 	/* Update the database if necessary */
 	if err := dbUpdate(db); err != nil {
-		return fmt.Errorf("[Database] %w", err)
+		return fmt.Errorf("[database] %w", err)
 	}
 
 	/* Create an admin user if one does not exist */
 	if exists, err := UserExists("admin"); err != nil {
-		log.Println("[admin Exists]", err.Error())
+		log.Println("[admin:exists]", err.Error())
 		err = nil /* ignored */
 	} else if !exists {
 		if salt, err := Salt(); err != nil {
-			log.Println("[admin Salt]", err.Error())
+			log.Println("[admin:salt]", err.Error())
 			err = nil /* ignored */
 		} else if _, err = db.Exec(
 			"INSERT INTO users (id, name, name_full, pass, pass_algo, salt, is_admin) VALUES (?, ?, ?, ?, ?, ?, ?)",
 			0, "admin", "Administrator", Hash("admin", salt), "argon2", salt, true,
 		); err != nil {
-			log.Println("[admin INSERT]", err.Error())
+			log.Println("[admin:INSERT]", err.Error())
 			err = nil /* ignored */
 		}
 	}
@@ -148,15 +121,6 @@ func Goit(conf string) (err error) {
 	return nil
 }
 
-func ConfPath() string {
-	if p, err := xdg.SearchConfigFile(filepath.Join("goit", "goit.json")); err != nil {
-		log.Println("[config]", err.Error())
-		return ""
-	} else {
-		return p
-	}
-}
-
 func RepoPath(name string, abs bool) string {
 	return util.If(abs, filepath.Join(Conf.DataPath, "repos", name+".git"), filepath.Join(name+".git"))
 }
diff --git a/src/main.go b/src/main.go
index 8ac6ce1..590a187 100644
--- a/src/main.go
+++ b/src/main.go
@@ -24,7 +24,6 @@ import (
 	"github.com/Jamozed/Goit/src/repo"
 	"github.com/Jamozed/Goit/src/user"
 	"github.com/Jamozed/Goit/src/util"
-	"github.com/adrg/xdg"
 	"github.com/go-chi/chi/v5"
 	"github.com/go-chi/chi/v5/middleware"
 	"github.com/gorilla/csrf"
@@ -40,7 +39,7 @@ func main() {
 	flag.Parse()
 
 	if backup /* IPC client */ {
-		c, err := net.Dial("unix", filepath.Join(xdg.RuntimeDir, "goit-"+goit.Conf.HttpPort+".sock"))
+		c, err := net.Dial("unix", filepath.Join(goit.Conf.RuntimePath, "goit-"+goit.Conf.HttpPort+".sock"))
 		if err != nil {
 			log.Fatalln(err.Error())
 		}
@@ -77,7 +76,7 @@ func main() {
 	}()
 
 	/* Initialise Goit */
-	if err := goit.Goit(goit.ConfPath()); err != nil {
+	if err := goit.Goit(); err != nil {
 		log.Fatalln(err.Error())
 	}
 
@@ -156,7 +155,7 @@ func main() {
 	// h.Post("/{repo}/git-receive-pack", goit.HandleReceivePack)
 
 	/* Listen for IPC */
-	ipc, err := net.Listen("unix", filepath.Join(xdg.RuntimeDir, "goit-"+goit.Conf.HttpPort+".sock"))
+	ipc, err := net.Listen("unix", filepath.Join(goit.Conf.RuntimePath, "goit-"+goit.Conf.HttpPort+".sock"))
 	if err != nil {
 		log.Fatalln("[sock]", err.Error())
 	}