From 56979ac4bda369927ad201d95df09f64670aa615 Mon Sep 17 00:00:00 2001 From: dtoro Date: Sun, 12 Jul 2026 12:12:01 +0200 Subject: [PATCH] feat(deploy): add webhook receiver and launchd service for push-to-deploy - cmd/webhook/main.go: HMAC-validated webhook receiver on :9797 - launchd plist: keeps webhook running, PATH includes docker - Makefile: 'make webhook' target - Registered as Gitea webhook id 15 on dtoro/oikos Fixes: auto-deploy was not wired on mac-mini after the consolidation --- Makefile | 5 +- cmd/webhook/main.go | 96 +++++++++++++++++++ .../network.hubris.oikos-deploy-webhook.plist | 35 +++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 cmd/webhook/main.go create mode 100644 scripts/deploy/network.hubris.oikos-deploy-webhook.plist diff --git a/Makefile b/Makefile index b0cc196..b0d81ea 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test test-db lint generate generate-check dev migrate seed export clean tidy +.PHONY: build webhook test test-db lint generate generate-check dev migrate seed export clean tidy BINARY := oikos GO ?= go @@ -6,6 +6,9 @@ GO ?= go build: $(GO) build -o $(BINARY) -tags timetzdata ./cmd/oikos +webhook: + $(GO) build -o webhook -tags timetzdata ./cmd/webhook + test: $(GO) test -race -cover ./... diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go new file mode 100644 index 0000000..916099a --- /dev/null +++ b/cmd/webhook/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "os/exec" + "time" + + "github.com/dtoro/oikos/internal/safego" +) + +func main() { + port := os.Getenv("WEBHOOK_LISTEN") + if port == "" { + port = ":9797" + } + + secret := os.Getenv("WEBHOOK_HMAC_SECRET") + if secret == "" { + fmt.Fprintln(os.Stderr, "WEBHOOK_HMAC_SECRET must be set") + os.Exit(1) + } + + repoDir := os.Getenv("WEBHOOK_REPO_DIR") + if repoDir == "" { + repoDir = os.Getenv("HOME") + "/Projects/oikos" + } + + mux := http.NewServeMux() + mux.HandleFunc("/deploy", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", 405) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "read body failed", 400) + return + } + + sigHex := r.Header.Get("X-Hub-Signature-256") + if sigHex == "" { + http.Error(w, "missing signature", 401) + return + } + + mac := hmac.New(sha256.New, []byte(secret)) + mac.Write(body) + expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) + + if !hmac.Equal([]byte(sigHex), []byte(expected)) { + slog.Warn("webhook: invalid signature") + http.Error(w, "invalid signature", 401) + return + } + + slog.Info("webhook: deploy triggered") + w.WriteHeader(http.StatusAccepted) + w.Write([]byte(`{"status":"deploy started"}`)) + + safego.Go("webhook:deploy", func() { + cmd := exec.Command(repoDir + "/scripts/deploy.sh") + cmd.Dir = repoDir + cmd.Env = append(os.Environ(), + "REPO_DIR="+repoDir, + "PROFILE=full", + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + start := time.Now() + if err := cmd.Run(); err != nil { + slog.Error("webhook: deploy failed", "error", err, "duration", time.Since(start)) + return + } + slog.Info("webhook: deploy succeeded", "duration", time.Since(start)) + }) + }) + + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte("ok")) + }) + + slog.Info("webhook: listening", "port", port) + if err := http.ListenAndServe(port, mux); err != nil { + slog.Error("webhook: serve failed", "error", err) + os.Exit(1) + } +} diff --git a/scripts/deploy/network.hubris.oikos-deploy-webhook.plist b/scripts/deploy/network.hubris.oikos-deploy-webhook.plist new file mode 100644 index 0000000..2717c38 --- /dev/null +++ b/scripts/deploy/network.hubris.oikos-deploy-webhook.plist @@ -0,0 +1,35 @@ + + + + + Label + network.hubris.oikos-deploy-webhook + ProgramArguments + + /Users/dtoro/Projects/oikos/webhook + + EnvironmentVariables + + WEBHOOK_HMAC_SECRET + 6502524162d6dbc3f6d137000395d401f1837d74ef9bb0a876f8e6bbd65d1ff2 + WEBHOOK_REPO_DIR + /Users/dtoro/Projects/oikos + WEBHOOK_LISTEN + :9797 + HOME + /Users/dtoro + PATH + /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin + + WorkingDirectory + /Users/dtoro/Projects/oikos + RunAtLoad + + KeepAlive + + StandardOutPath + /Users/dtoro/Library/Logs/oikos-webhook.log + StandardErrorPath + /Users/dtoro/Library/Logs/oikos-webhook.log + +