Author | Jakob Wakeling <[email protected]> |
Date | 2024-07-07 05:50:40 |
Commit | 32e8b4dcbfaa802afe57d15e3dae89c18b100342 |
Parent | a0ac27c1150c392bcc36911016fd12b2ddac0d1f |
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, "/")