package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "io" "log" "net/http" "os" "bytes" "fmt" "time" "github.com/go-git/go-git/v5" githttp "github.com/go-git/go-git/v5/plumbing/transport/http" ) var repo *git.Repository func cloneOrOpen(cfg *Config) { auth := &githttp.BasicAuth{ Username: "x-token", Password: cfg.RepoToken, } if _, err := os.Stat(cfg.ContentDir + "/.git"); os.IsNotExist(err) { log.Println("Cloning repo...") r, err := git.PlainClone(cfg.ContentDir, false, &git.CloneOptions{ URL: cfg.RepoURL, Auth: auth, }) if err != nil { notifyFailure(cfg, "clone failed", err) // NEU log.Fatalf("clone failed: %v", err) } repo = r } else { log.Println("Opening existing repo...") r, err := git.PlainOpen(cfg.ContentDir) if err != nil { notifyFailure(cfg, "open failed", err) // NEU log.Fatalf("open failed: %v", err) } repo = r } } func pull(cfg *Config) error { w, err := repo.Worktree() if err != nil { return err } err = w.Pull(&git.PullOptions{ Auth: &githttp.BasicAuth{ Username: "x-token", Password: cfg.RepoToken, }, }) if err == git.NoErrAlreadyUpToDate { return nil } return err } func validateSignature(secret, signature string, body []byte) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(body) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } func webhookHandler(cfg *Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) return } sig := r.Header.Get("X-Gitea-Signature") if !validateSignature(cfg.WebhookSecret, sig, body) { log.Println("invalid webhook signature") w.WriteHeader(http.StatusUnauthorized) return } if err := pull(cfg); err != nil { notifyFailure(cfg, "pull failed", err) // NEU log.Printf("pull failed: %v", err) w.WriteHeader(http.StatusInternalServerError) return } log.Println("pulled new content") w.WriteHeader(http.StatusOK) } } func notifyFailure(cfg *Config, context string, err error) { if cfg.NtfyURL == "" || cfg.NtfyTopic == "" { return } msg := fmt.Sprintf("%s: %v", context, err) url := fmt.Sprintf("%s/%s", cfg.NtfyURL, cfg.NtfyTopic) req, reqErr := http.NewRequest("POST", url, bytes.NewBufferString(msg)) if reqErr != nil { log.Printf("ntfy request build failed: %v", reqErr) return } req.Header.Set("Title", "Varde Webhook Failure") req.Header.Set("Priority", "high") client := &http.Client{Timeout: 5 * time.Second} resp, sendErr := client.Do(req) if sendErr != nil { log.Printf("ntfy notification failed: %v", sendErr) return } defer resp.Body.Close() if resp.StatusCode >= 300 { log.Printf("ntfy notification returned status %d", resp.StatusCode) } }