Rewrite the service in Go and ship it as a static container

The PHP app, Apache, and .htaccess are replaced by a single Go binary
(standard library only). Routing, the JSON response, the landing page,
the shared email design, and all four parsers are ported with the same
output; JSON object keys are kept in sender order via an ordered decoder.

- mail.go: SMTP delivery with quoted-printable body and RFC 2047 subject
- main.go: routing, client IP from the proxy, /healthz, -check flag
- render.go: email shell, escaping, table helpers, generic renderer
- parser_*.go: github, grafana, json, wxinteract
- main_test.go: handler, response shape, IP forwarding, every parser
- Dockerfile: multi-stage build to distroless static, non-root, HEALTHCHECK
- deploy/webhooks.dixon.cx.service: systemd unit for the container
- README and PARSER_PROMPT updated for Go; IMPLEMENTATION_SUMMARY removed
This commit is contained in:
Andy Dixon
2026-09-12 19:59:54 +01:00
parent a61da1f6e3
commit 2d0f98e041
26 changed files with 1739 additions and 2301 deletions
+67
View File
@@ -0,0 +1,67 @@
package main
import (
"fmt"
"mime"
"mime/quotedprintable"
"net/smtp"
"os"
"time"
)
var (
smtpAddr = envOr("SMTP_ADDR", "127.0.0.1:25")
mailFrom = envOr("MAIL_FROM", "no-reply@dixon.cx")
)
// sendMail is a variable so tests can capture messages instead of sending them.
var sendMail = smtpSend
func smtpSend(to, subject, htmlBody string) error {
c, err := smtp.Dial(smtpAddr)
if err != nil {
return err
}
defer c.Close()
if err := c.Mail(mailFrom); err != nil {
return err
}
if err := c.Rcpt(to); err != nil {
return err
}
w, err := c.Data()
if err != nil {
return err
}
if _, err := w.Write(buildMessage(to, subject, htmlBody)); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
return c.Quit()
}
func buildMessage(to, subject, htmlBody string) []byte {
host, _ := os.Hostname()
var b []byte
b = fmt.Appendf(b, "From: Webhook Call <%s>\r\n", mailFrom)
b = fmt.Appendf(b, "To: <%s>\r\n", to)
b = fmt.Appendf(b, "Subject: %s\r\n", mime.QEncoding.Encode("utf-8", subject))
b = fmt.Appendf(b, "Date: %s\r\n", time.Now().Format(time.RFC1123Z))
b = fmt.Appendf(b, "Message-ID: <%d.%d@%s>\r\n", time.Now().UnixNano(), os.Getpid(), host)
b = append(b, "MIME-Version: 1.0\r\n"...)
b = append(b, "Content-Type: text/html; charset=UTF-8\r\n"...)
b = append(b, "Content-Transfer-Encoding: quoted-printable\r\n\r\n"...)
qp := quotedprintable.NewWriter(sliceWriter{&b})
_, _ = qp.Write([]byte(htmlBody))
_ = qp.Close()
return b
}
type sliceWriter struct{ b *[]byte }
func (s sliceWriter) Write(p []byte) (int, error) {
*s.b = append(*s.b, p...)
return len(p), nil
}