5 Commits

Author SHA1 Message Date
271b22fa1f
refactor(formmailer): ergonomic cleanups after prod deploy learnings
- Collapse SuccessBody/SuccessBodyFunc (and Error*) into a single required
  func() []byte. Callers build closures with baked-in fallbacks; one field,
  one code path, no priority ordering.
- Split EHLO local name from peer hostname. Add LocalName field + default
  to os.Hostname(). smtp.NewClient gets the peer name (for AUTH + TLS
  binding); c.Hello(localName) overrides the EHLO greeting separately.
  Stdlib conflates these but they're semantically distinct.
- Add TLSConfig override field (supports custom CAs, cert pinning, and
  test-time InsecureSkipVerify). Default is still ServerName: <SMTP host>.
- Add TrustedProxies []netip.Prefix. X-Forwarded-For is now only honored
  when r.RemoteAddr is in the trusted set — otherwise any client can forge
  their IP to bypass rate limits, blacklist, and geo gating.
- Integration tests drive sendMail against a fake SMTP server that does
  STARTTLS + AUTH PLAIN. Regression test for StartTLS(nil) panic (Go 1.22+
  requires ServerName or InsecureSkipVerify).

cmd/form2mail:
- Extract hotReload() helper; replaces the inline closures.
- silentDropRU looks up the email FormName from Fields by Kind instead of
  hardcoding "input_3" (survives field renames).
- Set TrustedProxies to loopback (proxy runs on same host).
- scripts/deploy.sh automates the rename-first / scp / restart / verify /
  cleanup cycle — leaves .old in place on verification failure for rollback.
2026-04-20 23:20:13 -06:00
6a85a93605
feat(form2mail): graceful SIGTERM shutdown via http.Server.Shutdown
log.Fatal(http.ListenAndServe) ignores ctx cancellation, so SIGTERM from
systemd hung the process until TimeoutStopSec (90s) triggered SIGKILL.
Replace with explicit *http.Server + Shutdown(ctx) driven by the existing
signal.NotifyContext ctx so restarts complete in under a second.
2026-04-20 22:30:30 -06:00
4ab9ffe364
feat(dataset): add View.Close and Set.Close for final-value cleanup
Previously only the *replaced* value was closed on swap — the
*currently-held* value leaked on process exit. Adds:

- View.Close() — closes the held value (if it implements io.Closer)
  and clears the pointer; idempotent.
- Set.Close() — joins Close errors across all views in the set.

form2mail defers geoSet.Close() to release the geoip2 file handles on
shutdown.
2026-04-20 22:23:32 -06:00
9e46d5ce90
fix(dataset): guard typed-nil previous value when closing replaced view
`any(typedNilPtr)` wraps the nil pointer in a non-nil interface, so the
prior `closer != nil` check didn't catch it — calling Close on the
underlying nil *geoip.Databases segfaulted during the first successful
reload after a failed one.

Also flip form2mail's default blocklist URL from git@ (SSH) to
https:// so fresh deployments work without a GitHub SSH key.
2026-04-20 22:20:08 -06:00
950aea9eeb
feat: add cmd/form2mail using formmailer/ipcohort/geoip/gitshallow
Drop-in replacement for the legacy standalone form2mail binary.
Preserves CLI flags, env vars, .env loading, password prompt, response
file hot-reload, field mapping (input_1/3/4/5/7), rate limit (5/min,
burst 3), North-America country gate, .ru silent-drop, routes, startup
banner, and [REDACTED] placeholder for bot rejections.

Changes vs. legacy:
- Replaces embedded iploc with net/geoip (requires GeoIP.conf)
- Reads bitwire-it repo's native layout (tables/inbound/*) instead of
  a hand-managed flat inbound.txt
- gitshallow.Repo with GCInterval=24 keeps .git from accumulating
  orphaned blobs after hourly upstream updates

formmailer additions to support form2mail's legacy behavior:
- SuccessBodyFunc / ErrorBodyFunc — per-request body providers for
  hot-reloadable templates
- HiddenSupportValue — string to render in place of {.SupportEmail}
  for blacklist/bot rejections (form2mail uses "[REDACTED]")
2026-04-20 22:09:11 -06:00