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()) }