Skip to content

Commit 13895fa

Browse files
feat: add healthcheck
1 parent 61e3a36 commit 13895fa

File tree

4 files changed

+94
-13
lines changed

4 files changed

+94
-13
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ data_directory: smtpbridge_data
9494
# Python executable
9595
python_executable: python3
9696
97+
# Healthcheck enables verification that the program has not crashed or lost network access
98+
# You can use a third party service such as healthchecks.io
99+
healthcheck:
100+
# URL to fetch, empty means health checking is disabled
101+
url: "" # (https://hc-ping.com/cb8bcf81-d3c4-4c98-85a6-734c3b7ddb2b, ...)
102+
103+
# Interval between each fetch
104+
interval: 5m # (5m, 5h45m, ...)
105+
106+
# Run on startup
107+
startup: false
108+
97109
# Retention policy for envelopes and attachment files
98110
retention:
99111
# Retention policy for envelopes in database

cmd/smtpbridge/main.go

+12
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,18 @@ func run(flags *flag.FlagSet) lieut.Executor {
132132
job.Execute(ctx)
133133
}
134134

135+
if cfg.HealthcheckURL != "" {
136+
job := cron.NewHealthcheck(cfg.HealthcheckURL)
137+
trigger := quartz.NewSimpleTrigger(cfg.HealthcheckInterval)
138+
if err := scheduler.ScheduleJob(ctx, job, trigger); err != nil {
139+
return err
140+
}
141+
142+
if cfg.HealthcheckStartup {
143+
job.Execute(ctx)
144+
}
145+
}
146+
135147
return nil
136148
}))
137149

config/config.go

+33-13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ type Config struct {
3737
CSRFSecretPath string
3838
SessionSecretPath string
3939
SessionsDirectory string
40+
HealthcheckURL string
41+
HealthcheckInterval time.Duration
42+
HealthcheckStartup bool
4043
HTTPDisable bool
4144
HTTPAddress string
4245
HTTPPort uint16
@@ -61,6 +64,9 @@ type Raw struct {
6164
RetentionEnvelopeCount string `koanf:"retention.envelope_count"`
6265
RetentionEnvelopeAge string `koanf:"retention.envelope_age"`
6366
RetentionAttachmentSize string `koanf:"retention.attachment_size"`
67+
HealthcheckURL string `koanf:"healthcheck.url"`
68+
HealthcheckInterval string `koanf:"healthcheck.interval"`
69+
HealthcheckStartup bool `koanf:"healthcheck.startup"`
6470
SMTPDisable bool `koanf:"smtp.disable"`
6571
SMTPHost string `koanf:"smtp.host"`
6672
SMTPPort uint16 `koanf:"smtp.port"`
@@ -97,21 +103,23 @@ type RawRule struct {
97103
}
98104

99105
var RawDefault = struct {
100-
TimeFormat string `koanf:"time_format"`
101-
MaxPayloadSize string `koanf:"max_payload_size"`
102-
DataDirectory string `koanf:"data_directory"`
103-
PythonExecutable string `koanf:"python_executable"`
104-
SMTPPort uint16 `koanf:"smtp.port"`
105-
SMTPMaxPayloadSize string `koanf:"smtp.max_payload_size"`
106-
HTTPPort uint16 `koanf:"http.port"`
106+
HealthcheckInterval string `koanf:"healthcheck.interval"`
107+
TimeFormat string `koanf:"time_format"`
108+
MaxPayloadSize string `koanf:"max_payload_size"`
109+
DataDirectory string `koanf:"data_directory"`
110+
PythonExecutable string `koanf:"python_executable"`
111+
SMTPPort uint16 `koanf:"smtp.port"`
112+
SMTPMaxPayloadSize string `koanf:"smtp.max_payload_size"`
113+
HTTPPort uint16 `koanf:"http.port"`
107114
// IMAPPort uint16 `koanf:"imap.port"`
108115
}{
109-
TimeFormat: timeFormat12H,
110-
SMTPMaxPayloadSize: "25 MB",
111-
DataDirectory: "smtpbridge_data",
112-
PythonExecutable: "python3",
113-
SMTPPort: 1025,
114-
HTTPPort: 8080,
116+
HealthcheckInterval: "5m",
117+
TimeFormat: timeFormat12H,
118+
SMTPMaxPayloadSize: "25 MB",
119+
DataDirectory: "smtpbridge_data",
120+
PythonExecutable: "python3",
121+
SMTPPort: 1025,
122+
HTTPPort: 8080,
115123
// IMAPPort: 10143,
116124
}
117125

@@ -130,6 +138,10 @@ func WithFlagSet(flags *flag.FlagSet) *flag.FlagSet {
130138
flags.String("python-executable", "", flagUsageString(RawDefault.PythonExecutable, "Python executable."))
131139
flags.Bool("debug", false, flagUsageBool(false, "Run in debug mode."))
132140

141+
flags.String("healthcheck-url", "", flagUsageString("", "Healthcheck URL to fetch."))
142+
flags.String("healthcheck-interval", "", flagUsageString(RawDefault.HealthcheckInterval, "Healthcheck interval between each fetch."))
143+
flags.Bool("healthcheck-startup", false, flagUsageBool(false, "Healthcheck fetch on startup."))
144+
133145
flags.Bool("smtp-disable", false, flagUsageBool(false, "Disable SMTP server."))
134146
flags.String("smtp-host", "", flagUsageString("", "SMTP host address to listen on."))
135147
flags.Int("smtp-port", 0, flagUsageInt(int(RawDefault.SMTPPort), "SMTP port to listen on."))
@@ -306,7 +318,15 @@ func (p Parser) Parse(raw Raw) (Config, error) {
306318

307319
// imapAddress := raw.IMAPHost + ":" + strconv.Itoa(int(raw.IMAPPort))
308320

321+
healthcheckInterval, err := time.ParseDuration(raw.HealthcheckInterval)
322+
if err != nil {
323+
return Config{}, err
324+
}
325+
309326
return Config{
327+
HealthcheckURL: raw.HealthcheckURL,
328+
HealthcheckInterval: healthcheckInterval,
329+
HealthcheckStartup: raw.HealthcheckStartup,
310330
Debug: raw.Debug,
311331
TimeHourFormat: timeHourFormat,
312332
DatabasePath: databasePath,

cron/cron.go

+37
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package cron
22

33
import (
44
"context"
5+
"net/http"
56

67
"github.com/ItsNotGoodName/smtpbridge/internal/core"
78
"github.com/ItsNotGoodName/smtpbridge/internal/trace"
89
"github.com/reugn/go-quartz/quartz"
910
"github.com/rs/zerolog/log"
1011
)
1112

13+
// RetentionPolicy
1214
type RetentionPolicy struct {
1315
app core.App
1416
}
@@ -34,6 +36,7 @@ func (r RetentionPolicy) Key() int {
3436
return quartz.HashCode(r.Description())
3537
}
3638

39+
// AttachmentOrphan
3740
type AttachmentOrphan struct {
3841
app core.App
3942
}
@@ -58,3 +61,37 @@ func (r AttachmentOrphan) Execute(ctx context.Context) {
5861
func (r AttachmentOrphan) Key() int {
5962
return quartz.HashCode(r.Description())
6063
}
64+
65+
// Healthcheck
66+
type Healthcheck struct {
67+
URL string
68+
}
69+
70+
func NewHealthcheck(url string) Healthcheck {
71+
return Healthcheck{
72+
URL: url,
73+
}
74+
}
75+
76+
func (Healthcheck) Description() string {
77+
return "healthcheck"
78+
}
79+
80+
func (r Healthcheck) Execute(ctx context.Context) {
81+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, r.URL, nil)
82+
if err != nil {
83+
log.Err(err).Msg("Failed create HTTP request")
84+
return
85+
}
86+
87+
res, err := http.DefaultClient.Do(req)
88+
if err != nil {
89+
log.Err(err).Msg("Failed send HTTP request")
90+
return
91+
}
92+
res.Body.Close()
93+
}
94+
95+
func (r Healthcheck) Key() int {
96+
return quartz.HashCode(r.Description())
97+
}

0 commit comments

Comments
 (0)