mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
refactor(cmd/form2mail): rebase release onto main
This commit is contained in:
parent
93b360ecc2
commit
bf052b4904
2
cmd/form2mail/.gitignore
vendored
Normal file
2
cmd/form2mail/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/form2mail
|
||||||
|
/form2mail-*
|
||||||
1
cmd/form2mail/README.md
Normal file
1
cmd/form2mail/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://grok.com/c/3d268fcf-e236-49f3-8f74-c007bdc795b5?rid=c834f57c-ff18-4dd6-8be8-115223ae3801
|
||||||
95
cmd/form2mail/error-file.html
Normal file
95
cmd/form2mail/error-file.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'/>
|
||||||
|
</head>
|
||||||
|
<body class='GF_AJAX_POSTBACK'>
|
||||||
|
<div class='gf_browser_chrome gform_wrapper gform-theme gform-theme--foundation gform-theme--framework gform-theme--orbital gform_validation_error' data-form-theme='orbital' data-form-index='0' id='gform_wrapper_1'>
|
||||||
|
<style>
|
||||||
|
#gform_wrapper_1[data-form-index="0"].gform-theme,[data-parent-form="1_0"] {
|
||||||
|
--gf-color-primary: #204ce5;
|
||||||
|
--gf-color-primary-rgb: 32, 76, 229;
|
||||||
|
--gf-color-primary-contrast: #fff;
|
||||||
|
--gf-color-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--gf-color-primary-darker: #001AB3;
|
||||||
|
--gf-color-primary-lighter: #527EFF;
|
||||||
|
--gf-color-secondary: #fff;
|
||||||
|
--gf-color-secondary-rgb: 255, 255, 255;
|
||||||
|
--gf-color-secondary-contrast: #112337;
|
||||||
|
--gf-color-secondary-contrast-rgb: 17, 35, 55;
|
||||||
|
--gf-color-secondary-darker: #F5F5F5;
|
||||||
|
--gf-color-secondary-lighter: #FFFFFF;
|
||||||
|
--gf-color-out-ctrl-light: rgba(17, 35, 55, 0.1);
|
||||||
|
--gf-color-out-ctrl-light-rgb: 17, 35, 55;
|
||||||
|
--gf-color-out-ctrl-light-darker: rgba(104, 110, 119, 0.35);
|
||||||
|
--gf-color-out-ctrl-light-lighter: #F5F5F5;
|
||||||
|
--gf-color-out-ctrl-dark: #585e6a;
|
||||||
|
--gf-color-out-ctrl-dark-rgb: 88, 94, 106;
|
||||||
|
--gf-color-out-ctrl-dark-darker: #112337;
|
||||||
|
--gf-color-out-ctrl-dark-lighter: rgba(17, 35, 55, 0.65);
|
||||||
|
--gf-color-in-ctrl: #fff;
|
||||||
|
--gf-color-in-ctrl-rgb: 255, 255, 255;
|
||||||
|
--gf-color-in-ctrl-contrast: #112337;
|
||||||
|
--gf-color-in-ctrl-contrast-rgb: 17, 35, 55;
|
||||||
|
--gf-color-in-ctrl-darker: #F5F5F5;
|
||||||
|
--gf-color-in-ctrl-lighter: #FFFFFF;
|
||||||
|
--gf-color-in-ctrl-primary: #204ce5;
|
||||||
|
--gf-color-in-ctrl-primary-rgb: 32, 76, 229;
|
||||||
|
--gf-color-in-ctrl-primary-contrast: #fff;
|
||||||
|
--gf-color-in-ctrl-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--gf-color-in-ctrl-primary-darker: #001AB3;
|
||||||
|
--gf-color-in-ctrl-primary-lighter: #527EFF;
|
||||||
|
--gf-color-in-ctrl-light: rgba(17, 35, 55, 0.1);
|
||||||
|
--gf-color-in-ctrl-light-rgb: 17, 35, 55;
|
||||||
|
--gf-color-in-ctrl-light-darker: rgba(104, 110, 119, 0.35);
|
||||||
|
--gf-color-in-ctrl-light-lighter: #F5F5F5;
|
||||||
|
--gf-color-in-ctrl-dark: #585e6a;
|
||||||
|
--gf-color-in-ctrl-dark-rgb: 88, 94, 106;
|
||||||
|
--gf-color-in-ctrl-dark-darker: #112337;
|
||||||
|
--gf-color-in-ctrl-dark-lighter: rgba(17, 35, 55, 0.65);
|
||||||
|
--gf-radius: 3px;
|
||||||
|
--gf-font-size-secondary: 14px;
|
||||||
|
--gf-font-size-tertiary: 13px;
|
||||||
|
--gf-icon-ctrl-number: url("data:image/svg+xml,%3Csvg width='8' height='14' viewBox='0 0 8 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4 0C4.26522 5.96046e-08 4.51957 0.105357 4.70711 0.292893L7.70711 3.29289C8.09763 3.68342 8.09763 4.31658 7.70711 4.70711C7.31658 5.09763 6.68342 5.09763 6.29289 4.70711L4 2.41421L1.70711 4.70711C1.31658 5.09763 0.683417 5.09763 0.292893 4.70711C-0.0976311 4.31658 -0.097631 3.68342 0.292893 3.29289L3.29289 0.292893C3.48043 0.105357 3.73478 0 4 0ZM0.292893 9.29289C0.683417 8.90237 1.31658 8.90237 1.70711 9.29289L4 11.5858L6.29289 9.29289C6.68342 8.90237 7.31658 8.90237 7.70711 9.29289C8.09763 9.68342 8.09763 10.3166 7.70711 10.7071L4.70711 13.7071C4.31658 14.0976 3.68342 14.0976 3.29289 13.7071L0.292893 10.7071C-0.0976311 10.3166 -0.0976311 9.68342 0.292893 9.29289Z' fill='rgba(17, 35, 55, 0.65)'/%3E%3C/svg%3E");
|
||||||
|
--gf-icon-ctrl-select: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0.292893 0.292893C0.683417 -0.097631 1.31658 -0.097631 1.70711 0.292893L5 3.58579L8.29289 0.292893C8.68342 -0.0976311 9.31658 -0.0976311 9.70711 0.292893C10.0976 0.683417 10.0976 1.31658 9.70711 1.70711L5.70711 5.70711C5.31658 6.09763 4.68342 6.09763 4.29289 5.70711L0.292893 1.70711C-0.0976311 1.31658 -0.0976311 0.683418 0.292893 0.292893Z' fill='rgba(17, 35, 55, 0.65)'/%3E%3C/svg%3E");
|
||||||
|
--gf-icon-ctrl-search: url("data:image/svg+xml,%3Csvg width='640' height='640' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M256 128c-70.692 0-128 57.308-128 128 0 70.691 57.308 128 128 128 70.691 0 128-57.309 128-128 0-70.692-57.309-128-128-128zM64 256c0-106.039 85.961-192 192-192s192 85.961 192 192c0 41.466-13.146 79.863-35.498 111.248l154.125 154.125c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0L367.248 412.502C335.862 434.854 297.467 448 256 448c-106.039 0-192-85.962-192-192z' fill='rgba(17, 35, 55, 0.65)'/%3E%3C/svg%3E");
|
||||||
|
--gf-label-space-y-secondary: var(--gf-label-space-y-md-secondary);
|
||||||
|
--gf-ctrl-border-color: #686e77;
|
||||||
|
--gf-ctrl-size: var(--gf-ctrl-size-md);
|
||||||
|
--gf-ctrl-label-color-primary: #112337;
|
||||||
|
--gf-ctrl-label-color-secondary: #112337;
|
||||||
|
--gf-ctrl-choice-size: var(--gf-ctrl-choice-size-md);
|
||||||
|
--gf-ctrl-checkbox-check-size: var(--gf-ctrl-checkbox-check-size-md);
|
||||||
|
--gf-ctrl-radio-check-size: var(--gf-ctrl-radio-check-size-md);
|
||||||
|
--gf-ctrl-btn-font-size: var(--gf-ctrl-btn-font-size-md);
|
||||||
|
--gf-ctrl-btn-padding-x: var(--gf-ctrl-btn-padding-x-md);
|
||||||
|
--gf-ctrl-btn-size: var(--gf-ctrl-btn-size-md);
|
||||||
|
--gf-ctrl-btn-border-color-secondary: #686e77;
|
||||||
|
--gf-ctrl-file-btn-bg-color-hover: #EBEBEB;
|
||||||
|
--gf-field-img-choice-size: var(--gf-field-img-choice-size-md);
|
||||||
|
--gf-field-img-choice-card-space: var(--gf-field-img-choice-card-space-md);
|
||||||
|
--gf-field-img-choice-check-ind-size: var(--gf-field-img-choice-check-ind-size-md);
|
||||||
|
--gf-field-img-choice-check-ind-icon-size: var(--gf-field-img-choice-check-ind-icon-size-md);
|
||||||
|
--gf-field-pg-steps-number-color: rgba(17, 35, 55, 0.8);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id='gf_1' class='gform_anchor' tabindex='-1'></div>
|
||||||
|
<div class="gform_validation_errors" id="gform_1_validation_container" data-js="gform-focus-validation-error" autofocus>
|
||||||
|
<h2 class='gform_submission_error hide_summary'>
|
||||||
|
<span class='gform-icon gform-icon--circle-error'></span>
|
||||||
|
<!-- NOTE: this is response managed by a mailer on the static server, not the WP instance -->
|
||||||
|
There was a problem with your submission.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
Please email {.SupportEmail} directly.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
For reference, this was the error:
|
||||||
|
<br/>
|
||||||
|
{.Error}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
227
cmd/form2mail/form2mail.go
Normal file
227
cmd/form2mail/form2mail.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
|
"github.com/therootcompany/golib/net/formmailer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
name = "form2email"
|
||||||
|
licenseYear = "2026"
|
||||||
|
licenseOwner = "AJ ONeal"
|
||||||
|
licenseType = "CC0-1.0"
|
||||||
|
|
||||||
|
defaultBlocklistRepo = "https://github.com/bitwire-it/ipblocklist.git"
|
||||||
|
|
||||||
|
requestsPerMinute = 5
|
||||||
|
burstSize = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version = "0.0.0-dev"
|
||||||
|
commit = "0000000"
|
||||||
|
date = "0001-01-01T00:00:00Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printVersion(out io.Writer) {
|
||||||
|
if len(commit) > 7 {
|
||||||
|
commit = commit[:7]
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(out, "%s v%s %s (%s)\n", name, version, commit, date)
|
||||||
|
_, _ = fmt.Fprintf(out, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
|
||||||
|
_, _ = fmt.Fprintf(out, "Licensed under the %s license\n", licenseType)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MainConfig struct {
|
||||||
|
showVersion bool
|
||||||
|
listenAddr string
|
||||||
|
smtpHost string
|
||||||
|
smtpFrom string
|
||||||
|
smtpToList string
|
||||||
|
smtpUser string
|
||||||
|
smtpPass string
|
||||||
|
smtpSubject string
|
||||||
|
successFile string
|
||||||
|
errorFile string
|
||||||
|
blocklistRepo string
|
||||||
|
cacheDir string
|
||||||
|
geoipDir string
|
||||||
|
geoipBaseURL string
|
||||||
|
geoipConfPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
_ = godotenv.Load()
|
||||||
|
_ = godotenv.Load(filepath.Join(home, ".config/form2mail/env"))
|
||||||
|
|
||||||
|
cfg := MainConfig{
|
||||||
|
listenAddr: "localhost:3081",
|
||||||
|
smtpHost: os.Getenv("SMTP_HOST"),
|
||||||
|
smtpFrom: os.Getenv("SMTP_FROM"),
|
||||||
|
smtpToList: os.Getenv("SMTP_TO"),
|
||||||
|
smtpUser: os.Getenv("SMTP_USER"),
|
||||||
|
smtpSubject: "Website contact request from {.Email}",
|
||||||
|
successFile: "success-file.html",
|
||||||
|
errorFile: "error-file.html",
|
||||||
|
blocklistRepo: defaultBlocklistRepo,
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
|
fs.BoolVar(&cfg.showVersion, "version", false, "Print version and exit")
|
||||||
|
fs.StringVar(&cfg.listenAddr, "listen", cfg.listenAddr, "Address to listen on")
|
||||||
|
fs.StringVar(&cfg.smtpHost, "smtp-host", cfg.smtpHost, "SMTP server:port e.g. smtp.gmail.com:587 (required)")
|
||||||
|
fs.StringVar(&cfg.smtpFrom, "smtp-from", cfg.smtpFrom, "Sender email e.g. you@gmail.com (required)")
|
||||||
|
fs.StringVar(&cfg.smtpToList, "smtp-to", cfg.smtpToList, "Recipient email e.g. alerts@yourdomain.com (required)")
|
||||||
|
fs.StringVar(&cfg.smtpUser, "smtp-user", cfg.smtpUser, "SMTP username (defaults to smtp-from if not set)")
|
||||||
|
fs.StringVar(&cfg.successFile, "success-file", cfg.successFile, "HTML or JSON file to reply with on success.")
|
||||||
|
fs.StringVar(&cfg.errorFile, "error-file", cfg.errorFile, "HTML or JSON file to reply with on failure.")
|
||||||
|
fs.StringVar(&cfg.blocklistRepo, "blocklist-repo", cfg.blocklistRepo, "git URL of the bitwire-it-compatible blocklist repo")
|
||||||
|
fs.StringVar(&cfg.cacheDir, "cache-dir", "", "cache parent dir (default: ~/.cache)")
|
||||||
|
fs.StringVar(&cfg.geoipDir, "geoip-dir", "", "dir holding GeoLite2 tarballs (default: <$cache-dir>/maxmind)")
|
||||||
|
fs.StringVar(&cfg.geoipBaseURL, "geoip-base-url", "", "MaxMind download base URL (default: https://download.maxmind.com/geoip/databases)")
|
||||||
|
fs.StringVar(&cfg.geoipConfPath, "geoip-conf", "", "path to GeoIP.conf (default: ./GeoIP.conf or ~/.config/maxmind/GeoIP.conf)")
|
||||||
|
|
||||||
|
fs.Usage = func() {
|
||||||
|
printVersion(os.Stderr)
|
||||||
|
fmt.Fprintln(os.Stderr, "\nUSAGE")
|
||||||
|
fmt.Fprintln(os.Stderr, " form2email [options]")
|
||||||
|
fs.PrintDefaults()
|
||||||
|
fmt.Fprintln(os.Stderr, "\nEnv vars (overrides flags): SMTP_HOST, SMTP_FROM, SMTP_TO, SMTP_USER, SMTP_PASS")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||||
|
if errors.Is(err, flag.ErrHelp) {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
fs.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.showVersion {
|
||||||
|
printVersion(os.Stdout)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.smtpHost == "" || cfg.smtpFrom == "" || cfg.smtpToList == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nError: missing required SMTP settings\n\n")
|
||||||
|
fs.Usage()
|
||||||
|
fmt.Fprintf(os.Stderr, "\nError: missing required SMTP settings\n\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
printVersion(os.Stderr)
|
||||||
|
|
||||||
|
// Verify templates are readable at startup; re-read on each request so
|
||||||
|
// operators can edit HTML without restarting (matches legacy behavior).
|
||||||
|
successFallback, err := os.ReadFile(cfg.successFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nError: couldn't read success response file %q: %v\n\n", cfg.successFile, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
errorFallback, err := os.ReadFile(cfg.errorFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nError: couldn't read error response file %q: %v\n\n", cfg.errorFile, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
successBody := hotReload(cfg.successFile, successFallback)
|
||||||
|
errorBody := hotReload(cfg.errorFile, errorFallback)
|
||||||
|
|
||||||
|
if cfg.smtpUser == "" {
|
||||||
|
cfg.smtpUser = cfg.smtpFrom
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass, hasPass := os.LookupEnv("SMTP_PASS"); !hasPass {
|
||||||
|
fmt.Fprintf(os.Stderr, "SMTP_PASS not set → ")
|
||||||
|
pwBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read password: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr)
|
||||||
|
cfg.smtpPass = strings.TrimSpace(string(pwBytes))
|
||||||
|
} else {
|
||||||
|
cfg.smtpPass = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build FormMailer — all blocklist/GeoIP setup is handled by Run().
|
||||||
|
fields := []formmailer.Field{
|
||||||
|
{Label: "Name", FormName: "input_1", Kind: formmailer.KindText},
|
||||||
|
{Label: "Email", FormName: "input_3", Kind: formmailer.KindEmail},
|
||||||
|
{Label: "Phone", FormName: "input_4", Kind: formmailer.KindPhone},
|
||||||
|
{Label: "Company", FormName: "input_5", Kind: formmailer.KindText},
|
||||||
|
{Label: "Message", FormName: "input_7", Kind: formmailer.KindMessage},
|
||||||
|
}
|
||||||
|
|
||||||
|
fm := &formmailer.FormMailer{
|
||||||
|
ListenAddr: cfg.listenAddr,
|
||||||
|
SMTPHost: cfg.smtpHost,
|
||||||
|
SMTPFrom: cfg.smtpFrom,
|
||||||
|
SMTPTo: strings.Split(cfg.smtpToList, ","),
|
||||||
|
SMTPUser: cfg.smtpUser,
|
||||||
|
SMTPPass: cfg.smtpPass,
|
||||||
|
Subject: cfg.smtpSubject,
|
||||||
|
SuccessBody: successBody,
|
||||||
|
ErrorBody: errorBody,
|
||||||
|
ContentType: inferContentType(cfg.successFile),
|
||||||
|
HiddenSupportValue: "[REDACTED]",
|
||||||
|
TrustedProxies: []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("127.0.0.0/8"),
|
||||||
|
netip.MustParsePrefix("::1/128"),
|
||||||
|
netip.MustParsePrefix("10.0.0.0/8"),
|
||||||
|
netip.MustParsePrefix("172.16.0.0/12"),
|
||||||
|
netip.MustParsePrefix("192.168.0.0/16"),
|
||||||
|
},
|
||||||
|
BlocklistRepo: cfg.blocklistRepo,
|
||||||
|
CacheDir: cfg.cacheDir,
|
||||||
|
GeoIPDir: cfg.geoipDir,
|
||||||
|
GeoIPBaseURL: cfg.geoipBaseURL,
|
||||||
|
GeoIPConfPath: cfg.geoipConfPath,
|
||||||
|
AllowedCountries: []string{"US", "CA", "MX", "CR", "VI"},
|
||||||
|
Fields: fields,
|
||||||
|
RPM: requestsPerMinute,
|
||||||
|
Burst: burstSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fm.Run(context.Background()); err != nil {
|
||||||
|
log.Fatalf("server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hotReload returns a function that re-reads path on each call, falling back
|
||||||
|
// to the provided bytes on read error (logged). Used for templates that
|
||||||
|
// operators may edit out-of-band.
|
||||||
|
func hotReload(path string, fallback []byte) func() []byte {
|
||||||
|
return func() []byte {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s read: %v", path, err)
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferContentType(path string) string {
|
||||||
|
switch strings.ToLower(filepath.Ext(path)) {
|
||||||
|
case ".html", ".htm":
|
||||||
|
return "text/html; charset=utf-8"
|
||||||
|
case ".json":
|
||||||
|
return "application/json"
|
||||||
|
default:
|
||||||
|
return "text/plain; charset=utf-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
cmd/form2mail/go.mod
Normal file
31
cmd/form2mail/go.mod
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
module github.com/therootcompany/golib/cmd/form2mail
|
||||||
|
|
||||||
|
go 1.26.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/therootcompany/golib/net/formmailer v0.3.0
|
||||||
|
golang.org/x/term v0.40.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/miekg/dns v1.1.69 // indirect
|
||||||
|
github.com/oschwald/geoip2-golang v1.13.0 // indirect
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||||
|
github.com/therootcompany/golib/https v0.9.0 // indirect
|
||||||
|
github.com/therootcompany/golib/io/transform/gsheet2csv v1.1.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/dnsresolver v0.6.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/geoip v0.6.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/gitshallow v0.10.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/httpcache v0.6.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/ipcohort v0.9.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/iplist v0.9.0 // indirect
|
||||||
|
github.com/therootcompany/golib/net/ippolicy v0.9.0 // indirect
|
||||||
|
github.com/therootcompany/golib/sync/cachable v0.1.0 // indirect
|
||||||
|
golang.org/x/mod v0.30.0 // indirect
|
||||||
|
golang.org/x/net v0.47.0 // indirect
|
||||||
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
|
golang.org/x/time v0.15.0 // indirect
|
||||||
|
golang.org/x/tools v0.39.0 // indirect
|
||||||
|
)
|
||||||
54
cmd/form2mail/go.sum
Normal file
54
cmd/form2mail/go.sum
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||||
|
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||||
|
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
|
||||||
|
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/therootcompany/golib/https v0.9.0 h1:D9gh0K6ljkvF805JUbylITn8SHPMo08q5PglQGXsK8c=
|
||||||
|
github.com/therootcompany/golib/https v0.9.0/go.mod h1:ilExqR/Uh7oB36oy9mwuNhANcFh+zdCfD3uJiZ11YAY=
|
||||||
|
github.com/therootcompany/golib/io/transform/gsheet2csv v1.1.0 h1:R5zAPun5p3JPlNfcMD9es4LD9sqh6nWDNTflZBeeUJo=
|
||||||
|
github.com/therootcompany/golib/io/transform/gsheet2csv v1.1.0/go.mod h1:CsntOAhhIMdzSAYuwuRZtfkdoiER9+TfSgYOIDWN71o=
|
||||||
|
github.com/therootcompany/golib/net/dnsresolver v0.6.0 h1:sy08rzX/2QowofAKnCw1QtTDoatPL6/FXZcb+Wz4GlM=
|
||||||
|
github.com/therootcompany/golib/net/dnsresolver v0.6.0/go.mod h1:tOV99lX6ghpWPksxSFAt4CDe5XLefFzIGf2qXsZJap4=
|
||||||
|
github.com/therootcompany/golib/net/formmailer v0.3.0 h1:Mg4ST8n8Z0Rmq4QMiwRnkm+vOJfMqc/5PWkjJ7PtqMM=
|
||||||
|
github.com/therootcompany/golib/net/formmailer v0.3.0/go.mod h1:aGsxkgRvzWV0EgpyTPKw+o+LyWsID5kpmJ+h70nL9XQ=
|
||||||
|
github.com/therootcompany/golib/net/geoip v0.6.0 h1:+LyOKt47Mbxoi8ssaX9RaN0loW6wyPDA5XvExSiRzB0=
|
||||||
|
github.com/therootcompany/golib/net/geoip v0.6.0/go.mod h1:yrk7CuIet0BntICfRwRSX44fwu/1YAuHHjTQYoWHSOE=
|
||||||
|
github.com/therootcompany/golib/net/gitshallow v0.10.0 h1:cLN9ElTmNWk5HSbaRD3sSFuGdpgy4YXc+LVzjzT0nM4=
|
||||||
|
github.com/therootcompany/golib/net/gitshallow v0.10.0/go.mod h1:S/3OK7wMOqJJjP79U9Qw/l3jjEyrb/zsjJ8HAPAX/Fc=
|
||||||
|
github.com/therootcompany/golib/net/httpcache v0.6.0 h1:qFBnSs6rZrtUQ+fweP4bHF2FQd/Syvs7mRN5nO5YtkY=
|
||||||
|
github.com/therootcompany/golib/net/httpcache v0.6.0/go.mod h1:mKSQJWpGSCDBzWkTCwT7dO1SCVCsFmdkRgKr39fNS1U=
|
||||||
|
github.com/therootcompany/golib/net/ipcohort v0.9.0 h1:j3AaoW2u4XQJz1ya6fixv3pb3KgxIpZV8t0082/kS3E=
|
||||||
|
github.com/therootcompany/golib/net/ipcohort v0.9.0/go.mod h1:Ljcnt2xOryVDGIVV5Kzt5hj2LhW4lBAWo5OeeVYU9ys=
|
||||||
|
github.com/therootcompany/golib/net/iplist v0.9.0 h1:rEDrflVvc5pQWt6sptrZVFWPZZ3M7ATUKjfIuFchKKI=
|
||||||
|
github.com/therootcompany/golib/net/iplist v0.9.0/go.mod h1:8ifRSgp4ptijldClyh688NLom8EZre6E16JZ2AHCEMw=
|
||||||
|
github.com/therootcompany/golib/net/ippolicy v0.9.0 h1:fhNEqtshRLiY1Ycuykf7yj+VkaP+tnnqCvxHXsIhfrE=
|
||||||
|
github.com/therootcompany/golib/net/ippolicy v0.9.0/go.mod h1:VMnaRjH6yTJ6bdnWFpji1xGCboOxEDsao9GDwA6g/ss=
|
||||||
|
github.com/therootcompany/golib/sync/cachable v0.1.0 h1:swCXtXB116jhSmla81HaXi/9cPsqFI+Oq/6OcSfdU5Q=
|
||||||
|
github.com/therootcompany/golib/sync/cachable v0.1.0/go.mod h1:l/NZdGiSbW5CkLlkkRa0yObkqak2sj/jQTXXqXwvCl0=
|
||||||
|
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||||
|
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||||
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
|
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
42
cmd/form2mail/scripts/deploy.sh
Executable file
42
cmd/form2mail/scripts/deploy.sh
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Cross-compile form2mail for linux-amd64 and deploy to <user>@<host>.
|
||||||
|
# Usage: scripts/deploy.sh <user>@<host> [service-name]
|
||||||
|
#
|
||||||
|
# Uses the rename-old-first pattern (avoids scp ETXTBSY on running ELFs),
|
||||||
|
# restarts via systemctl, verifies --version runs from the new inode, then
|
||||||
|
# removes the .old backup. Leaves .old in place if verification fails so
|
||||||
|
# rollback is `mv ~/bin/<bin>.old ~/bin/<bin>`.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
g_remote="${1:?usage: $0 <user>@<host> [service-name]}"
|
||||||
|
g_service="${2:-form2mail}"
|
||||||
|
g_bin="form2mail"
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "Building ${g_bin} for linux-amd64..."
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o "${g_bin}-linux" .
|
||||||
|
|
||||||
|
echo "Renaming old binary on ${g_remote}..."
|
||||||
|
ssh "${g_remote}" "mv ~/bin/${g_bin} ~/bin/${g_bin}.old"
|
||||||
|
|
||||||
|
echo "Copying new binary..."
|
||||||
|
scp "${g_bin}-linux" "${g_remote}:~/bin/${g_bin}"
|
||||||
|
|
||||||
|
echo "Restarting ${g_service}..."
|
||||||
|
ssh "${g_remote}" "sudo systemctl restart ${g_service}"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo "Verifying..."
|
||||||
|
if ! ssh "${g_remote}" "systemctl is-active ${g_service}" > /dev/null; then
|
||||||
|
echo " FAILED: ${g_service} not active. Logs:"
|
||||||
|
ssh "${g_remote}" "sudo journalctl -u ${g_service} -n 20 --no-pager"
|
||||||
|
echo " Backup still at ~/bin/${g_bin}.old — roll back with:"
|
||||||
|
echo " ssh ${g_remote} 'mv ~/bin/${g_bin}.old ~/bin/${g_bin} && sudo systemctl restart ${g_service}'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing backup..."
|
||||||
|
ssh "${g_remote}" "rm ~/bin/${g_bin}.old"
|
||||||
|
|
||||||
|
echo "OK: ${g_service} deployed and active."
|
||||||
83
cmd/form2mail/success-file.html
Normal file
83
cmd/form2mail/success-file.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'/>
|
||||||
|
</head>
|
||||||
|
<body class='GF_AJAX_POSTBACK'>
|
||||||
|
<div id='gf_1' class='gform_anchor' tabindex='-1'></div>
|
||||||
|
<div id='gform_confirmation_wrapper_1' data-form-theme='orbital' class='gform_confirmation_wrapper gform_wrapper gform-theme gform-theme--foundation gform-theme--framework gform-theme--orbital '>
|
||||||
|
<style>
|
||||||
|
#gform_confirmation_wrapper_1.gform-theme {
|
||||||
|
--gf-color-primary: #204ce5;
|
||||||
|
--gf-color-primary-rgb: 32, 76, 229;
|
||||||
|
--gf-color-primary-contrast: #fff;
|
||||||
|
--gf-color-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--gf-color-primary-darker: #001AB3;
|
||||||
|
--gf-color-primary-lighter: #527EFF;
|
||||||
|
--gf-color-secondary: #fff;
|
||||||
|
--gf-color-secondary-rgb: 255, 255, 255;
|
||||||
|
--gf-color-secondary-contrast: #112337;
|
||||||
|
--gf-color-secondary-contrast-rgb: 17, 35, 55;
|
||||||
|
--gf-color-secondary-darker: #F5F5F5;
|
||||||
|
--gf-color-secondary-lighter: #FFFFFF;
|
||||||
|
--gf-color-out-ctrl-light: rgba(17, 35, 55, 0.1);
|
||||||
|
--gf-color-out-ctrl-light-rgb: 17, 35, 55;
|
||||||
|
--gf-color-out-ctrl-light-darker: rgba(104, 110, 119, 0.35);
|
||||||
|
--gf-color-out-ctrl-light-lighter: #F5F5F5;
|
||||||
|
--gf-color-out-ctrl-dark: #585e6a;
|
||||||
|
--gf-color-out-ctrl-dark-rgb: 88, 94, 106;
|
||||||
|
--gf-color-out-ctrl-dark-darker: #112337;
|
||||||
|
--gf-color-out-ctrl-dark-lighter: rgba(17, 35, 55, 0.65);
|
||||||
|
--gf-color-in-ctrl: #fff;
|
||||||
|
--gf-color-in-ctrl-rgb: 255, 255, 255;
|
||||||
|
--gf-color-in-ctrl-contrast: #112337;
|
||||||
|
--gf-color-in-ctrl-contrast-rgb: 17, 35, 55;
|
||||||
|
--gf-color-in-ctrl-darker: #F5F5F5;
|
||||||
|
--gf-color-in-ctrl-lighter: #FFFFFF;
|
||||||
|
--gf-color-in-ctrl-primary: #204ce5;
|
||||||
|
--gf-color-in-ctrl-primary-rgb: 32, 76, 229;
|
||||||
|
--gf-color-in-ctrl-primary-contrast: #fff;
|
||||||
|
--gf-color-in-ctrl-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--gf-color-in-ctrl-primary-darker: #001AB3;
|
||||||
|
--gf-color-in-ctrl-primary-lighter: #527EFF;
|
||||||
|
--gf-color-in-ctrl-light: rgba(17, 35, 55, 0.1);
|
||||||
|
--gf-color-in-ctrl-light-rgb: 17, 35, 55;
|
||||||
|
--gf-color-in-ctrl-light-darker: rgba(104, 110, 119, 0.35);
|
||||||
|
--gf-color-in-ctrl-light-lighter: #F5F5F5;
|
||||||
|
--gf-color-in-ctrl-dark: #585e6a;
|
||||||
|
--gf-color-in-ctrl-dark-rgb: 88, 94, 106;
|
||||||
|
--gf-color-in-ctrl-dark-darker: #112337;
|
||||||
|
--gf-color-in-ctrl-dark-lighter: rgba(17, 35, 55, 0.65);
|
||||||
|
--gf-radius: 3px;
|
||||||
|
--gf-font-size-secondary: 14px;
|
||||||
|
--gf-font-size-tertiary: 13px;
|
||||||
|
--gf-icon-ctrl-number: url("data:image/svg+xml,%3Csvg width='8' height='14' viewBox='0 0 8 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4 0C4.26522 5.96046e-08 4.51957 0.105357 4.70711 0.292893L7.70711 3.29289C8.09763 3.68342 8.09763 4.31658 7.70711 4.70711C7.31658 5.09763 6.68342 5.09763 6.29289 4.70711L4 2.41421L1.70711 4.70711C1.31658 5.09763 0.683417 5.09763 0.292893 4.70711C-0.0976311 4.31658 -0.097631 3.68342 0.292893 3.29289L3.29289 0.292893C3.48043 0.105357 3.73478 0 4 0ZM0.292893 9.29289C0.683417 8.90237 1.31658 8.90237 1.70711 9.29289L4 11.5858L6.29289 9.29289C6.68342 8.90237 7.31658 8.90237 7.70711 9.29289C8.09763 9.68342 8.09763 10.3166 7.70711 10.7071L4.70711 13.7071C4.31658 14.0976 3.68342 14.0976 3.29289 13.7071L0.292893 10.7071C-0.0976311 10.3166 -0.0976311 9.68342 0.292893 9.29289Z' fill='rgba(17, 35, 55, 0.65)'/%3E%3C/svg%3E");
|
||||||
|
--gf-icon-ctrl-select: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0.292893 0.292893C0.683417 -0.097631 1.31658 -0.097631 1.70711 0.292893L5 3.58579L8.29289 0.292893C8.68342 -0.0976311 9.31658 -0.0976311 9.70711 0.292893C10.0976 0.683417 10.0976 1.31658 9.70711 1.70711L5.70711 5.70711C5.31658 6.09763 4.68342 6.09763 4.29289 5.70711L0.292893 1.70711C-0.0976311 1.31658 -0.0976311 0.683418 0.292893 0.292893Z' fill='rgba(17, 35, 55, 0.65)'/%3E%3C/svg%3E");
|
||||||
|
--gf-icon-ctrl-search: url("data:image/svg+xml,%3Csvg width='640' height='640' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M256 128c-70.692 0-128 57.308-128 128 0 70.691 57.308 128 128 128 70.691 0 128-57.309 128-128 0-70.692-57.309-128-128-128zM64 256c0-106.039 85.961-192 192-192s192 85.961 192 192c0 41.466-13.146 79.863-35.498 111.248l154.125 154.125c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0L367.248 412.502C335.862 434.854 297.467 448 256 448c-106.039 0-192-85.962-192-192z' fill='rgba(17, 35, 55, 0.65)'/%3E%3C/svg%3E");
|
||||||
|
--gf-label-space-y-secondary: var(--gf-label-space-y-md-secondary);
|
||||||
|
--gf-ctrl-border-color: #686e77;
|
||||||
|
--gf-ctrl-size: var(--gf-ctrl-size-md);
|
||||||
|
--gf-ctrl-label-color-primary: #112337;
|
||||||
|
--gf-ctrl-label-color-secondary: #112337;
|
||||||
|
--gf-ctrl-choice-size: var(--gf-ctrl-choice-size-md);
|
||||||
|
--gf-ctrl-checkbox-check-size: var(--gf-ctrl-checkbox-check-size-md);
|
||||||
|
--gf-ctrl-radio-check-size: var(--gf-ctrl-radio-check-size-md);
|
||||||
|
--gf-ctrl-btn-font-size: var(--gf-ctrl-btn-font-size-md);
|
||||||
|
--gf-ctrl-btn-padding-x: var(--gf-ctrl-btn-padding-x-md);
|
||||||
|
--gf-ctrl-btn-size: var(--gf-ctrl-btn-size-md);
|
||||||
|
--gf-ctrl-btn-border-color-secondary: #686e77;
|
||||||
|
--gf-ctrl-file-btn-bg-color-hover: #EBEBEB;
|
||||||
|
--gf-field-img-choice-size: var(--gf-field-img-choice-size-md);
|
||||||
|
--gf-field-img-choice-card-space: var(--gf-field-img-choice-card-space-md);
|
||||||
|
--gf-field-img-choice-check-ind-size: var(--gf-field-img-choice-check-ind-size-md);
|
||||||
|
--gf-field-img-choice-check-ind-icon-size: var(--gf-field-img-choice-check-ind-icon-size-md);
|
||||||
|
--gf-field-pg-steps-number-color: rgba(17, 35, 55, 0.8);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id='gform_confirmation_message_1' class='gform_confirmation_message_1 gform_confirmation_message'>
|
||||||
|
<!-- NOTE: this is response managed by a mailer on the static server, not the WP instance -->
|
||||||
|
Thanks for contacting us! We will get in touch with you shortly.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user