// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. package lib import ( "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "os" "os/exec" "path" "path/filepath" ) type WebhookCFG struct { Forgejo PageDir string GitDir string GitUser string GitPass string } func (c *WebhookCFG) pageDir(user, repo string) string { return filepath.Join(c.PageDir, user, repo) } func (c *WebhookCFG) gitDir(user, repo string) string { return filepath.Join(c.GitDir, user, repo) } func (c *WebhookCFG) checkout(user, repo string) (err error) { pageDir := c.pageDir(user, repo) gitDir := c.gitDir(user, repo) git := exec.Command( "git", "--git-dir", gitDir, "--work-tree", pageDir, "checkout", "origin/"+c.Branch, ) output, err := git.CombinedOutput() if err != nil { fmt.Println("git checkout failed: ", err) fmt.Println("=========== Dump output: ============") fmt.Println(string(output)) fmt.Println("=====================================") } return } func (c *WebhookCFG) fetch(user, repo string) (err error) { gitDir := c.gitDir(user, repo) git := exec.Command( "git", "--git-dir", gitDir, "fetch", "origin", c.Branch, ) output, err := git.CombinedOutput() if err != nil { fmt.Println("git fetch failed: ", err) fmt.Println("=========== Dump output: ============") fmt.Println(string(output)) fmt.Println("=====================================") } return } func (c *WebhookCFG) clone(user, repo string) (err error) { pageDir := c.pageDir(user, repo) err = os.MkdirAll(filepath.Dir(pageDir), 0755) if err != nil { return } gitDir := c.gitDir(user, repo) err = os.MkdirAll(filepath.Dir(gitDir), 0700) if err != nil { return } uri := c.Server uri.Path = "/" + path.Join(user, repo) + ".git" uri.User = url.UserPassword(c.GitUser, c.GitPass) git := exec.Command( "git", "clone", "-b", c.Branch, "--single-branch", "--no-tags", "--separate-git-dir", gitDir, uri.String(), pageDir, ) output, err := git.CombinedOutput() if err != nil { fmt.Println("git clone failed: ", err) fmt.Println("=========== Dump output: ============") fmt.Println(string(output)) fmt.Println("=====================================") } os.Remove(filepath.Join(pageDir, ".git")) return } func (c *WebhookCFG) download(user, repo string) (err error) { gitDir := c.gitDir(user, repo) if _, err = os.Stat(gitDir); os.IsNotExist(err) { err = c.clone(user, repo) } else { err = c.fetch(user, repo) } if err != nil { return } err = c.checkout(user, repo) return } type webhookPayload struct { Ref string `json:"ref"` Repository struct { FullName string `json:"full_name"` } `json:"repository"` } func (c *WebhookCFG) handle(w http.ResponseWriter, r *http.Request) { fmt.Println("Received webhook event") ev := r.Header.Get("X-GitHub-Event") if ev != "push" { // skip non-push events fmt.Println("Skip non-push event: ", ev) return } defer r.Body.Close() data, err := io.ReadAll(r.Body) if err != nil { fmt.Println("Failed to read request body: ", err) return } var payload webhookPayload err = json.Unmarshal(data, &payload) if err != nil { fmt.Println("Failed to parse request body: ", err) fmt.Println("=========== Dump body: ============") fmt.Println(string(data)) fmt.Println("===================================") return } if payload.Ref != "refs/heads/"+c.Branch { // skip non-branch events fmt.Println("Skip different branch: ", payload.Ref) return } user, repo := path.Split(payload.Repository.FullName) user = user[:len(user)-1] fmt.Println("Pulling ", user, repo) err = c.download(user, repo) if err != nil { fmt.Println("Failed to download repository: ", err) return } } func UseWebhook(bind string, cfg *WebhookCFG) (*http.Server, error) { if cfg == nil { return nil, errors.New("webhook config is nil") } s := &http.Server{ Addr: bind, Handler: http.HandlerFunc(cfg.handle), } return s, nil }