Goit

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

AuthorJakob Wakeling <[email protected]>
Date2024-07-07 05:50:40
Commit32e8b4dcbfaa802afe57d15e3dae89c18b100342
Parenta0ac27c1150c392bcc36911016fd12b2ddac0d1f

Replace backup IPC mechanism with DB locking

Diffstat

M .vscode/launch.json | 1 -
D .vscode/tasks.json | 11 -----------
M src/cron/cron.go | 4 ++--
M src/goit/db.go | 57 ++++++++++++++++++++++++++++++++++++++++++---------------
M src/goit/goit.go | 44 +++++++++++++++++++++++++++++++++++---------
M src/goit/repo.go | 15 ++-------------
M src/main.go | 87 ++-----------------------------------------------------------------------------

7 files changed, 84 insertions, 135 deletions

diff --git a/.vscode/launch.json b/.vscode/launch.json
index ecbe435..5275abd 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -9,7 +9,6 @@
 			"cwd": "${workspaceFolder}",
 			"mode": "debug",
 			"args": ["--debug"],
-			"postDebugTask": "clean",
 		},
 	],
 }
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index 99110fd..0000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-	"version": "2.0.0",
-	"tasks": [
-		{
-			"label": "clean",
-			"type": "shell",
-			"command": "rm /run/user/$(id -u)/goit-8080.sock",
-			"presentation": { "reveal": "silent" }
-		},
-	],
-}
diff --git a/src/cron/cron.go b/src/cron/cron.go
index 6357732..a44e8ed 100644
--- a/src/cron/cron.go
+++ b/src/cron/cron.go
@@ -51,8 +51,8 @@ func (c *Cron) Start() {
 		return
 	}
 
-	for _, job := range c.jobs {
-		job.Next = job.Schedule.Next(time.Now().UTC())
+	for i, job := range c.jobs {
+		c.jobs[i].Next = job.Schedule.Next(time.Now().UTC())
 	}
 
 	go func() {
diff --git a/src/goit/db.go b/src/goit/db.go
index 734f8c2..cdc24e4 100644
--- a/src/goit/db.go
+++ b/src/goit/db.go
@@ -8,11 +8,40 @@ import (
 	"github.com/Jamozed/Goit/src/util"
 )
 
+/*
+Current database schema:
+users:
+	id INTEGER PRIMARY KEY AUTOINCREMENT
+	name TEXT UNIQUE NOT NULL
+	name_full TEXT NOT NULL
+	pass BLOB NOT NULL
+	pass_algo TEXT NOT NULL
+	salt BLOB NOT NULL
+	is_admin BOOLEAN NOT NULL
+
+repos:
+	id INTEGER PRIMARY KEY AUTOINCREMENT
+	owner_id INTEGER NOT NULL
+	name TEXT UNIQUE NOT NULL
+	name_lower TEXT UNIQUE NOT NULL
+	description TEXT NOT NULL
+	default_branch TEXT NOT NULL DEFAULT 'master'
+	upstream TEXT NOT NULL
+	visibility INTEGER NOT NULL DEFAULT 0
+	is_mirror BOOLEAN NOT NULL
+*/
+
 func updateDatabase(db *sql.DB) error {
 	const LATEST_VERSION = 3
 
+	tx, err := db.Begin()
+	if err != nil {
+		return err
+	}
+	defer tx.Rollback()
+
 	var version int
-	if err := db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
+	if err := tx.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
 		return err
 	}
 
@@ -27,7 +56,7 @@ func updateDatabase(db *sql.DB) error {
 		log.Println("Initialising database at version", LATEST_VERSION)
 		logMigration = false
 
-		if _, err := db.Exec(
+		if _, err := tx.Exec(
 			`CREATE TABLE IF NOT EXISTS users (
 				id INTEGER PRIMARY KEY AUTOINCREMENT,
 				name TEXT UNIQUE NOT NULL,
@@ -41,7 +70,7 @@ func updateDatabase(db *sql.DB) error {
 			return err
 		}
 
-		if _, err := db.Exec(
+		if _, err := tx.Exec(
 			`CREATE TABLE IF NOT EXISTS repos (
 				id INTEGER PRIMARY KEY AUTOINCREMENT,
 				owner_id INTEGER NOT NULL,
@@ -63,9 +92,7 @@ func updateDatabase(db *sql.DB) error {
 			log.Println("Migrating database from version 1 to 2")
 		}
 
-		if _, err := db.Exec(
-			"ALTER TABLE repos ADD COLUMN default_branch TEXT NOT NULL DEFAULT 'master'",
-		); err != nil {
+		if _, err := tx.Exec("ALTER TABLE repos ADD COLUMN default_branch TEXT NOT NULL DEFAULT 'master'"); err != nil {
 			return err
 		}
 
@@ -76,16 +103,14 @@ func updateDatabase(db *sql.DB) error {
 			log.Println("Migrating database from version 2 to 3")
 		}
 
-		if _, err := db.Exec(
-			"ALTER TABLE repos ADD COLUMN visibility INTEGER NOT NULL DEFAULT 0",
-		); err != nil {
+		if _, err := tx.Exec("ALTER TABLE repos ADD COLUMN visibility INTEGER NOT NULL DEFAULT 0"); err != nil {
 			return err
 		}
 
 		/* Set values for each repo according to is_private */
 		var visibilities = map[int64]Visibility{}
 
-		if rows, err := db.Query("SELECT id, is_private FROM repos"); err != nil {
+		if rows, err := tx.Query("SELECT id, is_private FROM repos"); err != nil {
 			return err
 		} else {
 			for rows.Next() {
@@ -103,22 +128,24 @@ func updateDatabase(db *sql.DB) error {
 		}
 
 		for id, visibility := range visibilities {
-			if _, err := db.Exec(
-				"UPDATE repos SET visibility = ? WHERE id = ?", visibility, id,
-			); err != nil {
+			if _, err := tx.Exec("UPDATE repos SET visibility = ? WHERE id = ?", visibility, id); err != nil {
 				return err
 			}
 		}
 
 		/* Remove is_private column */
-		if _, err := db.Exec("ALTER TABLE repos DROP COLUMN is_private"); err != nil {
+		if _, err := tx.Exec("ALTER TABLE repos DROP COLUMN is_private"); err != nil {
 			return err
 		}
 
 		version = 3
 	}
 
-	if _, err := db.Exec(fmt.Sprint("PRAGMA user_version = ", version)); err != nil {
+	if _, err := tx.Exec(fmt.Sprint("PRAGMA user_version = ", version)); err != nil {
+		return err
+	}
+
+	if err := tx.Commit(); err != nil {
 		return err
 	}
 
diff --git a/src/goit/goit.go b/src/goit/goit.go
index 121ba4f..050bb54 100644
--- a/src/goit/goit.go
+++ b/src/goit/goit.go
@@ -65,7 +65,7 @@ func Goit() error {
 		Favicon = dat
 	}
 
-	if db, err = sql.Open("sqlite3", filepath.Join(Conf.DataPath, "goit.db")); err != nil {
+	if db, err = sql.Open("sqlite3", filepath.Join(Conf.DataPath, "goit.db?_timeout=5000")); err != nil {
 		return fmt.Errorf("[database] %w", err)
 	}
 
@@ -138,6 +138,12 @@ func IsLegal(s string) bool {
 }
 
 func Backup() error {
+	if conf, err := loadConfig(); err != nil {
+		return err
+	} else {
+		Conf = conf
+	}
+
 	data := struct {
 		Users []User `json:"users"`
 		Repos []Repo `json:"repos"`
@@ -148,11 +154,23 @@ func Backup() error {
 		return err
 	}
 
+	db, err := sql.Open("sqlite3", filepath.Join(Conf.DataPath, "goit.db?_timeout=5000&_txlock=immediate"))
+	if err != nil {
+		return err
+	}
+
+	tx, err := db.Begin()
+	if err != nil {
+		return err
+	}
+	defer tx.Rollback()
+
 	/* Dump users */
-	rows, err := db.Query("SELECT id, name, name_full, pass, pass_algo, salt, is_admin FROM users")
+	rows, err := tx.Query("SELECT id, name, name_full, pass, pass_algo, salt, is_admin FROM users")
 	if err != nil {
 		return err
 	}
+	defer rows.Close()
 
 	for rows.Next() {
 		u := User{}
@@ -162,15 +180,15 @@ func Backup() error {
 
 		data.Users = append(data.Users, u)
 	}
-	rows.Close()
 
 	/* Dump repositories */
-	rows, err = db.Query(
+	rows, err = tx.Query(
 		"SELECT id, owner_id, name, description, default_branch, upstream, visibility, is_mirror FROM repos",
 	)
 	if err != nil {
 		return err
 	}
+	defer rows.Close()
 
 	for rows.Next() {
 		r := Repo{}
@@ -182,11 +200,11 @@ func Backup() error {
 
 		data.Repos = append(data.Repos, r)
 	}
-	rows.Close()
 
 	/* Open an output ZIP file */
 	ts := "goit_" + time.Now().UTC().Format("20060102T150405Z")
 
+	log.Println("Backing up to", filepath.Join(bdir, ts+".zip"))
 	zf, err := os.Create(filepath.Join(bdir, ts+".zip"))
 	if err != nil {
 		return err
@@ -197,24 +215,27 @@ func Backup() error {
 	defer zw.Close()
 
 	/* Copy repositories to ZIP */
-	td, err := os.MkdirTemp(os.TempDir(), "goit-")
+	tempdir, err := os.MkdirTemp(os.TempDir(), "goit-")
 	if err != nil {
 		return err
 	}
-	defer os.RemoveAll(td)
+	defer os.RemoveAll(tempdir)
 
 	for _, r := range data.Repos {
-		cd := filepath.Join(td, RepoPath(r.Name, false))
+		t0 := time.Now()
+		cd := filepath.Join(tempdir, RepoPath(r.Name, false))
 
 		gr, err := git.PlainClone(cd, true, &git.CloneOptions{
 			URL: RepoPath(r.Name, true), Mirror: true,
 		})
 		if err != nil {
 			if errors.Is(err, transport.ErrRepositoryNotFound) {
+				log.Println("Skipping", r.Name, "as it does not exist")
 				continue
 			}
 
 			if errors.Is(err, transport.ErrEmptyRemoteRepository) {
+				log.Println("Skipping", r.Name, "as it is empty")
 				continue
 			}
 
@@ -241,7 +262,7 @@ func Backup() error {
 				return err
 			}
 
-			head.Name = filepath.Join(ts, strings.TrimPrefix(path, Conf.DataPath))
+			head.Name = filepath.Join(ts, "repos", strings.TrimPrefix(path, tempdir))
 
 			if d.IsDir() {
 				head.Name += "/"
@@ -271,6 +292,7 @@ func Backup() error {
 		}
 
 		os.RemoveAll(cd)
+		log.Println("Backed up", r.Name, "in", time.Since(t0))
 	}
 
 	/* Write database as JSON to ZIP */
@@ -282,5 +304,9 @@ func Backup() error {
 		return err
 	}
 
+	if err := tx.Commit(); err != nil {
+		return err
+	}
+
 	return nil
 }
diff --git a/src/goit/repo.go b/src/goit/repo.go
index 8a94d31..8d44dab 100644
--- a/src/goit/repo.go
+++ b/src/goit/repo.go
@@ -126,6 +126,7 @@ func CreateRepo(repo Repo) (int64, error) {
 	if err != nil {
 		return -1, err
 	}
+	defer tx.Rollback()
 
 	res, err := tx.Exec(
 		`INSERT INTO repos (owner_id, name, name_lower, description, default_branch, upstream, visibility, is_mirror)
@@ -133,19 +134,16 @@ func CreateRepo(repo Repo) (int64, error) {
 		repo.DefaultBranch, repo.Upstream, repo.Visibility, repo.IsMirror,
 	)
 	if err != nil {
-		tx.Rollback()
 		return -1, err
 	}
 
 	r, err := git.PlainInit(RepoPath(repo.Name, true), true)
 	if err != nil {
-		tx.Rollback()
 		return -1, err
 	}
 
 	ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.NewBranchReferenceName(repo.DefaultBranch))
 	if err := r.Storer.SetReference(ref); err != nil {
-		tx.Rollback()
 		os.RemoveAll(RepoPath(repo.Name, true))
 		return -1, err
 	}
@@ -157,7 +155,6 @@ func CreateRepo(repo Repo) (int64, error) {
 			Mirror: util.If(repo.IsMirror, true, false),
 			Fetch:  []gitconfig.RefSpec{gitconfig.RefSpec("+refs/heads/*:refs/heads/*")},
 		}); err != nil {
-			tx.Rollback()
 			os.RemoveAll(RepoPath(repo.Name, true))
 			return -1, err
 		}
@@ -216,24 +213,22 @@ func UpdateRepo(rid int64, repo Repo) error {
 	if err != nil {
 		return err
 	}
+	defer tx.Rollback()
 
 	if _, err := tx.Exec(
 		`UPDATE repos SET name = ?, name_lower = ?, description = ?, default_branch = ?, upstream = ?, visibility = ?,
 		is_mirror = ? WHERE id = ?`, repo.Name, strings.ToLower(repo.Name), repo.Description, repo.DefaultBranch,
 		repo.Upstream, repo.Visibility, repo.IsMirror, rid,
 	); err != nil {
-		tx.Rollback()
 		return err
 	}
 
 	if repo.Name != old.Name {
 		if err := os.MkdirAll(filepath.Dir(RepoPath(repo.Name, true)), 0o777); err != nil {
-			tx.Rollback()
 			return err
 		}
 
 		if err := os.Rename(RepoPath(old.Name, true), RepoPath(repo.Name, true)); err != nil {
-			tx.Rollback()
 			return err
 		}
 	}
@@ -243,14 +238,12 @@ func UpdateRepo(rid int64, repo Repo) error {
 		// if r == nil {
 		r, err = git.PlainOpen(RepoPath(repo.Name, true))
 		if err != nil {
-			tx.Rollback()
 			return err
 		}
 		// }
 
 		ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.NewBranchReferenceName(repo.DefaultBranch))
 		if err := r.Storer.SetReference(ref); err != nil {
-			tx.Rollback()
 			return err
 		}
 	}
@@ -260,13 +253,11 @@ func UpdateRepo(rid int64, repo Repo) error {
 		if r == nil {
 			r, err = git.PlainOpen(RepoPath(repo.Name, true))
 			if err != nil {
-				tx.Rollback()
 				return err
 			}
 		}
 
 		if err := r.DeleteRemote("origin"); err != nil {
-			tx.Rollback()
 			return err
 		}
 	}
@@ -276,13 +267,11 @@ func UpdateRepo(rid int64, repo Repo) error {
 		if r == nil {
 			r, err = git.PlainOpen(RepoPath(repo.Name, true))
 			if err != nil {
-				tx.Rollback()
 				return err
 			}
 		}
 
 		if err := r.DeleteRemote("origin"); err != nil && !errors.Is(err, git.ErrRemoteNotFound) {
-			tx.Rollback()
 			return err
 		}
 
diff --git a/src/main.go b/src/main.go
index 879c6e5..6af8707 100644
--- a/src/main.go
+++ b/src/main.go
@@ -4,18 +4,13 @@
 package main
 
 import (
-	"errors"
 	"flag"
-	"fmt"
 	"log"
-	"net"
 	"net/http"
 	"os"
 	"os/signal"
 	"path"
-	"path/filepath"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/Jamozed/Goit/res"
@@ -38,40 +33,21 @@ func main() {
 	flag.BoolVar(&util.Debug, "debug", false, "Enable debug logging")
 	flag.Parse()
 
-	if backup /* IPC client */ {
-		c, err := net.Dial("unix", filepath.Join(goit.Conf.RuntimePath, "goit-"+goit.Conf.HttpPort+".sock"))
-		if err != nil {
+	if backup {
+		if err := goit.Backup(); err != nil {
 			log.Fatalln(err.Error())
 		}
 
-		_, err = c.Write([]byte{0xBA})
-		if err != nil {
-			log.Fatalln(err.Error())
-		}
-
-		buf := make([]byte, 512)
-		n, err := c.Read(buf)
-		if err != nil {
-			log.Fatalln(err.Error())
-		}
-
-		fmt.Println(string(buf[1:n]))
-		c.Close()
-
-		os.Exit(util.If(buf[0] == 0x01, -1, 0))
+		os.Exit(0)
 	}
 
 	/* Listen for and handle SIGINT */
-	stop := make(chan struct{})
-	wait := &sync.WaitGroup{}
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, os.Interrupt)
 
 	go func() {
 		<-c
-		close(stop)
 		goit.Cron.Stop()
-		wait.Wait()
 		os.Exit(0)
 	}()
 
@@ -154,20 +130,6 @@ func main() {
 	// h.Get("/{repo}/git-receive-pack", goit.HandleReceivePack)
 	// h.Post("/{repo}/git-receive-pack", goit.HandleReceivePack)
 
-	/* Listen for IPC */
-	ipc, err := net.Listen("unix", filepath.Join(goit.Conf.RuntimePath, "goit-"+goit.Conf.HttpPort+".sock"))
-	if err != nil {
-		log.Fatalln("[sock]", err.Error())
-	}
-
-	go func() {
-		defer ipc.Close()
-		<-stop
-	}()
-
-	wait.Add(1)
-	go handleIpc(stop, wait, ipc)
-
 	/* Listen for HTTP on the specified port */
 	if err := http.ListenAndServe(goit.Conf.HttpAddr+":"+goit.Conf.HttpPort, h); err != nil {
 		log.Fatalln("[http]", err.Error())
@@ -206,49 +168,6 @@ func handleFavicon(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-/* Handle IPC messages. */
-func handleIpc(stop chan struct{}, wait *sync.WaitGroup, ipc net.Listener) {
-	defer wait.Done()
-
-	for {
-		select {
-		case <-stop:
-			return
-		default:
-			c, err := ipc.Accept()
-			if err != nil {
-				if !errors.Is(err, net.ErrClosed) {
-					log.Println("[ipc]", err.Error())
-				}
-				continue
-			}
-
-			c.SetReadDeadline(time.Now().Add(1 * time.Second))
-
-			buf := make([]byte, 1)
-			if _, err := c.Read(buf); err != nil {
-				log.Println("[ipc]", err.Error())
-				continue
-			}
-
-			if buf[0] == 0xBA {
-				log.Println("[backup] Starting")
-				if err := goit.Backup(); err != nil {
-					c.Write(append([]byte{0x01}, []byte(err.Error())...))
-					log.Println("[backup]", err.Error())
-				} else {
-					c.Write(append([]byte{0x00}, []byte("SUCCESS")...))
-					log.Println("[backup] Success")
-				}
-			} else {
-				c.Write(append([]byte{0x01}, []byte("ILLEGAL")...))
-			}
-
-			c.Close()
-		}
-	}
-}
-
 func HandleRepo(w http.ResponseWriter, r *http.Request) {
 	parts := strings.Split(r.URL.Path, "/")