Repository navigation
Expand file tree
/
Copy pathmain.go
More file actions
104 lines (92 loc) · 3.2 KB
/
Copy pathmain.go
File metadata and controls
104 lines (92 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Command smsforwarder-api is the backend for the SMS Forwarder app: it receives
// SMS uploaded by the phone client, authenticates the user, and fans each message
// out to that user's configured channels (Telegram). An optional
// operator admin console runs on a separate (localhost-by-default) address.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/shuttlelab/smsforwarder-api/internal/admin"
"github.com/shuttlelab/smsforwarder-api/internal/api"
"github.com/shuttlelab/smsforwarder-api/internal/channels"
"github.com/shuttlelab/smsforwarder-api/internal/config"
"github.com/shuttlelab/smsforwarder-api/internal/forward"
"github.com/shuttlelab/smsforwarder-api/internal/ratelimit"
"github.com/shuttlelab/smsforwarder-api/internal/store"
"github.com/shuttlelab/smsforwarder-api/internal/tg"
)
func main() {
cfg := config.Load()
st, err := store.Open(cfg.DBPath)
if err != nil {
log.Fatalf("open store: %v", err)
}
defer st.Close()
// Bot config is DB-first (admin console), env fallback.
botToken, botUsername := cfg.BotToken, cfg.BotUsername
if v, _ := st.GetSetting(context.Background(), "bot_token"); v != "" {
botToken = v
}
if v, _ := st.GetSetting(context.Background(), "bot_username"); v != "" {
botUsername = v
}
reg := channels.NewRegistry()
limiter := ratelimit.New(cfg.RateLimitPerMin)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
// Shared Telegram bot (optional): handles /bind via long polling + provides the sender.
if botToken != "" {
tgBot, err := tg.New(botToken, st)
if err != nil {
log.Fatalf("telegram bot: %v", err)
}
reg.Register(channels.TypeTelegram, channels.NewTelegramSender(tgBot.Send))
go tgBot.Start(ctx)
log.Printf("telegram bot enabled (@%s)", botUsername)
} else {
log.Printf("telegram bot disabled (no bot token)")
}
// Delivery engine (polls the outbox, retries with backoff).
go forward.New(st, reg).Run(ctx)
apiSrv := &http.Server{
Addr: cfg.HTTPAddr,
Handler: api.NewServer(st, reg, limiter, botUsername).Routes(),
ReadHeaderTimeout: 10 * time.Second,
}
go func() {
log.Printf("api listening on %s (db=%s)", cfg.HTTPAddr, cfg.DBPath)
if err := apiSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("api server: %v", err)
}
}()
// Admin console (optional): only when creds are configured.
var adminSrv *http.Server
if cfg.AdminUser != "" && cfg.AdminPassword != "" {
adminSrv = &http.Server{
Addr: cfg.AdminAddr,
Handler: admin.New(st, cfg.AdminUser, cfg.AdminPassword).Routes(),
ReadHeaderTimeout: 10 * time.Second,
}
go func() {
log.Printf("admin console on %s", cfg.AdminAddr)
if err := adminSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("admin server: %v", err)
}
}()
} else {
log.Printf("admin console disabled (ADMIN_USER/ADMIN_PASSWORD not set)")
}
<-ctx.Done()
log.Println("shutting down…")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = apiSrv.Shutdown(shutdownCtx)
if adminSrv != nil {
_ = adminSrv.Shutdown(shutdownCtx)
}
}