main.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "os"
  9. "path"
  10. "strings"
  11. "time"
  12. )
  13. func loadDotEnvIfExists(filename string) bool {
  14. f, err := os.Open(filename)
  15. if err != nil {
  16. return false
  17. }
  18. defer f.Close()
  19. scanner := bufio.NewScanner(f)
  20. for scanner.Scan() {
  21. line := strings.TrimSpace(scanner.Text())
  22. if line == "" || strings.HasPrefix(line, "#") {
  23. continue
  24. }
  25. k, v, ok := strings.Cut(line, "=")
  26. if !ok {
  27. continue
  28. }
  29. k = strings.TrimSpace(k)
  30. v = strings.TrimSpace(v)
  31. v = strings.Trim(v, "\"'")
  32. if k == "" {
  33. continue
  34. }
  35. if _, exists := os.LookupEnv(k); exists {
  36. continue
  37. }
  38. _ = os.Setenv(k, v)
  39. }
  40. return true
  41. }
  42. func envOr(key, def string) string {
  43. if v, ok := os.LookupEnv(key); ok {
  44. v = strings.TrimSpace(v)
  45. if v != "" {
  46. return v
  47. }
  48. }
  49. return def
  50. }
  51. func normalizeHookPath(p string) string {
  52. p = strings.TrimSpace(p)
  53. if p == "" {
  54. return "/hook"
  55. }
  56. p = "/" + strings.TrimLeft(p, "/")
  57. p = path.Clean(p)
  58. if p == "." {
  59. return "/hook"
  60. }
  61. return p
  62. }
  63. func main() {
  64. _ = loadDotEnvIfExists(".env") || loadDotEnvIfExists("../.env")
  65. port := envOr("PORT", "8080")
  66. hookPath := normalizeHookPath(envOr("HOOK_PATH", "/hook"))
  67. mux := http.NewServeMux()
  68. mux.HandleFunc(hookPath, func(w http.ResponseWriter, r *http.Request) {
  69. if r.Method != http.MethodPost {
  70. w.Header().Set("Allow", http.MethodPost)
  71. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  72. return
  73. }
  74. body, err := io.ReadAll(r.Body)
  75. if err != nil {
  76. http.Error(w, "failed to read body", http.StatusBadRequest)
  77. return
  78. }
  79. ts := time.Now().Format(time.RFC3339Nano)
  80. log.Printf("hook received ts=%s bytes=%d payload=%s", ts, len(body), string(body))
  81. w.Header().Set("Content-Type", "application/json")
  82. _, _ = w.Write([]byte("{\"ok\":true}"))
  83. })
  84. mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
  85. w.Header().Set("Content-Type", "application/json")
  86. _, _ = w.Write([]byte("{\"status\":\"up\"}"))
  87. })
  88. addr := ":" + port
  89. fmt.Printf("Listening on http://localhost%s\n", addr)
  90. fmt.Printf("Hook URL: http://localhost%s%s\n", addr, hookPath)
  91. srv := &http.Server{
  92. Addr: addr,
  93. Handler: mux,
  94. ReadHeaderTimeout: 5 * time.Second,
  95. }
  96. log.Fatal(srv.ListenAndServe())
  97. }