|
|
@@ -0,0 +1,114 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "log"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
+ "path"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+func loadDotEnvIfExists(filename string) bool {
|
|
|
+ f, err := os.Open(filename)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ defer f.Close()
|
|
|
+
|
|
|
+ scanner := bufio.NewScanner(f)
|
|
|
+ for scanner.Scan() {
|
|
|
+ line := strings.TrimSpace(scanner.Text())
|
|
|
+ if line == "" || strings.HasPrefix(line, "#") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ k, v, ok := strings.Cut(line, "=")
|
|
|
+ if !ok {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ k = strings.TrimSpace(k)
|
|
|
+ v = strings.TrimSpace(v)
|
|
|
+ v = strings.Trim(v, "\"'")
|
|
|
+
|
|
|
+ if k == "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if _, exists := os.LookupEnv(k); exists {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ _ = os.Setenv(k, v)
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func envOr(key, def string) string {
|
|
|
+ if v, ok := os.LookupEnv(key); ok {
|
|
|
+ v = strings.TrimSpace(v)
|
|
|
+ if v != "" {
|
|
|
+ return v
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return def
|
|
|
+}
|
|
|
+
|
|
|
+func normalizeHookPath(p string) string {
|
|
|
+ p = strings.TrimSpace(p)
|
|
|
+ if p == "" {
|
|
|
+ return "/hook"
|
|
|
+ }
|
|
|
+ p = "/" + strings.TrimLeft(p, "/")
|
|
|
+ p = path.Clean(p)
|
|
|
+ if p == "." {
|
|
|
+ return "/hook"
|
|
|
+ }
|
|
|
+ return p
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ _ = loadDotEnvIfExists(".env") || loadDotEnvIfExists("../.env")
|
|
|
+
|
|
|
+ port := envOr("PORT", "8080")
|
|
|
+ hookPath := normalizeHookPath(envOr("HOOK_PATH", "/hook"))
|
|
|
+
|
|
|
+ mux := http.NewServeMux()
|
|
|
+ mux.HandleFunc(hookPath, func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if r.Method != http.MethodPost {
|
|
|
+ w.Header().Set("Allow", http.MethodPost)
|
|
|
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ body, err := io.ReadAll(r.Body)
|
|
|
+ if err != nil {
|
|
|
+ http.Error(w, "failed to read body", http.StatusBadRequest)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ ts := time.Now().Format(time.RFC3339Nano)
|
|
|
+ log.Printf("hook received ts=%s bytes=%d payload=%s", ts, len(body), string(body))
|
|
|
+
|
|
|
+ w.Header().Set("Content-Type", "application/json")
|
|
|
+ _, _ = w.Write([]byte("{\"ok\":true}"))
|
|
|
+ })
|
|
|
+
|
|
|
+ mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ w.Header().Set("Content-Type", "application/json")
|
|
|
+ _, _ = w.Write([]byte("{\"status\":\"up\"}"))
|
|
|
+ })
|
|
|
+
|
|
|
+ addr := ":" + port
|
|
|
+ fmt.Printf("Listening on http://localhost%s\n", addr)
|
|
|
+ fmt.Printf("Hook URL: http://localhost%s%s\n", addr, hookPath)
|
|
|
+
|
|
|
+ srv := &http.Server{
|
|
|
+ Addr: addr,
|
|
|
+ Handler: mux,
|
|
|
+ ReadHeaderTimeout: 5 * time.Second,
|
|
|
+ }
|
|
|
+
|
|
|
+ log.Fatal(srv.ListenAndServe())
|
|
|
+}
|