Add support for external sender #34
9 changed files with 212 additions and 28 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -16,7 +16,7 @@
|
|||
*.out
|
||||
|
||||
# build directory
|
||||
tracker/build
|
||||
**/build
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
|
|
@ -10,7 +10,7 @@ FROM --platform=$TARGETPLATFORM alpine:latest
|
|||
WORKDIR /root
|
||||
COPY --from=builder go/src/git.faercol.me/public-ip-tracker/build/tracker ./
|
||||
|
||||
VOLUME [ "/config" ]
|
||||
VOLUME [ "/config", "/output" ]
|
||||
|
||||
ENTRYPOINT [ "./tracker" ]
|
||||
CMD [ "-config", "/config/config.json" ]
|
||||
|
|
|
@ -44,6 +44,9 @@ For now, the program is configured through a JSON configuration file. Here is a
|
|||
"polling_frequency": 5,
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"export": {
|
||||
"mode": "native
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
17
examples/docker-compose-unix.yml
Normal file
17
examples/docker-compose-unix.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
services:
|
||||
telegram_exporter:
|
||||
container_name: telegram_exporter
|
||||
image: git.faercol.me/notification/telegram-notifier:latest
|
||||
environment:
|
||||
- SOCK_PATH=/input/telegram.sock
|
||||
volumes:
|
||||
- './build/config/exporter:/config'
|
||||
- './build/run:/input'
|
||||
|
||||
ip_tracker:
|
||||
container_name: ip_tracker
|
||||
image: git.faercol.me/faercol/public-ip-tracker:latest
|
||||
# Volumes store your data between container upgrades
|
||||
volumes:
|
||||
- './build/config/tracker:/config'
|
||||
- './build/run:/output'
|
|
@ -10,6 +10,8 @@ import (
|
|||
"git.faercol.me/faercol/public-ip-tracker/tracker/config"
|
||||
"git.faercol.me/faercol/public-ip-tracker/tracker/ip"
|
||||
"git.faercol.me/faercol/public-ip-tracker/tracker/logger"
|
||||
"git.faercol.me/faercol/public-ip-tracker/tracker/messager"
|
||||
"git.faercol.me/faercol/public-ip-tracker/tracker/unixsender"
|
||||
"github.com/ahugues/go-telegram-api/bot"
|
||||
"github.com/ahugues/go-telegram-api/notifier"
|
||||
"github.com/ahugues/go-telegram-api/structs"
|
||||
|
@ -31,20 +33,32 @@ Public IP has changed, new IP is %s`, formattedHostname, currentTime.Format(time
|
|||
}
|
||||
|
||||
type Notifier struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
tgBot bot.Bot
|
||||
tgChatID int64
|
||||
tgWatcher notifier.EventNotifier
|
||||
ipGetter ip.IPGetter
|
||||
timeGetter func() time.Time
|
||||
currentIP net.IP
|
||||
frequency time.Duration
|
||||
errChan chan error
|
||||
changesChan chan net.IP
|
||||
exitChan chan struct{}
|
||||
logger logrus.Logger
|
||||
hostname string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
tgBot bot.Bot
|
||||
tgChatID int64
|
||||
tgWatcher notifier.EventNotifier
|
||||
ipGetter ip.IPGetter
|
||||
timeGetter func() time.Time
|
||||
currentIP net.IP
|
||||
frequency time.Duration
|
||||
errChan chan error
|
||||
exitChan chan struct{}
|
||||
logger logrus.Logger
|
||||
hostname string
|
||||
sendMode config.ExportMode
|
||||
sender messager.Sender
|
||||
}
|
||||
|
||||
func (n *Notifier) sendMessage(msg string) error {
|
||||
switch n.sendMode {
|
||||
case config.ExportNative:
|
||||
return n.tgBot.SendMessage(n.ctx, n.tgChatID, msg, structs.FormattingMarkdownV2)
|
||||
case config.ExportUnix:
|
||||
return n.sender.SendMessage(msg)
|
||||
default:
|
||||
return fmt.Errorf("invalid sending mode %v", n.sendMode)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notifier) SendInitMessage() error {
|
||||
|
@ -59,7 +73,8 @@ func (n *Notifier) SendInitMessage() error {
|
|||
currentTime := n.timeGetter()
|
||||
|
||||
n.logger.Debug("Sending init message to Telegram")
|
||||
if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, formatInitMsg(currentTime, publicIP, n.hostname), structs.FormattingMarkdownV2); err != nil {
|
||||
initMsg := formatInitMsg(currentTime, publicIP, n.hostname)
|
||||
if err := n.sendMessage(initMsg); err != nil {
|
||||
return fmt.Errorf("failed to send initialization message: %w", err)
|
||||
}
|
||||
n.logger.Debug("Message sent")
|
||||
|
@ -67,7 +82,8 @@ func (n *Notifier) SendInitMessage() error {
|
|||
}
|
||||
|
||||
func (n *Notifier) sendUpdatedIPMsg() error {
|
||||
if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, formatUpdate(n.timeGetter(), n.currentIP, n.hostname), structs.FormattingMarkdownV2); err != nil {
|
||||
updateMsg := formatUpdate(n.timeGetter(), n.currentIP, n.hostname)
|
||||
if err := n.sendMessage(updateMsg); err != nil {
|
||||
return fmt.Errorf("failed to send update message: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
@ -75,7 +91,7 @@ func (n *Notifier) sendUpdatedIPMsg() error {
|
|||
|
||||
func (n *Notifier) sendCurrentIP() error {
|
||||
statusMsg := fmt.Sprintf("Current public IP is %s", n.currentIP)
|
||||
if err := n.tgBot.SendMessage(n.ctx, n.tgChatID, statusMsg, structs.FormattingMarkdownV2); err != nil {
|
||||
if err := n.sendMessage(statusMsg); err != nil {
|
||||
return fmt.Errorf("failed to send message: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
@ -140,10 +156,26 @@ func (n *Notifier) Exit() <-chan struct{} {
|
|||
return n.exitChan
|
||||
}
|
||||
|
||||
func New(ctx context.Context, config *config.Config) *Notifier {
|
||||
func buildSender(ctx context.Context, conf *config.Config) (messager.Sender, error) {
|
||||
switch conf.Export.Mode {
|
||||
case config.ExportUnix:
|
||||
logger.L.Infof("Building notifier in Unix mode")
|
||||
return unixsender.New(ctx, conf)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func New(ctx context.Context, config *config.Config) (*Notifier, error) {
|
||||
subCtx, cancel := context.WithCancel(ctx)
|
||||
tgBot := bot.New(config.Telegram.Token)
|
||||
|
||||
sender, err := buildSender(subCtx, config)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to build message sender: %w", err)
|
||||
}
|
||||
|
||||
return &Notifier{
|
||||
ctx: subCtx,
|
||||
cancel: cancel,
|
||||
|
@ -157,5 +189,7 @@ func New(ctx context.Context, config *config.Config) *Notifier {
|
|||
tgWatcher: notifier.New(config.Telegram.Token),
|
||||
logger: logger.L,
|
||||
hostname: config.Hostname,
|
||||
}
|
||||
sendMode: config.Export.Mode,
|
||||
sender: sender,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,24 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ExportMode int64
|
||||
|
||||
const (
|
||||
ExportNative = iota
|
||||
ExportUnix
|
||||
)
|
||||
|
||||
func exportModeFromStr(val string) ExportMode {
|
||||
switch val {
|
||||
case "native":
|
||||
return ExportNative
|
||||
case "unix":
|
||||
return ExportUnix
|
||||
default:
|
||||
return ExportNative
|
||||
}
|
||||
}
|
||||
|
||||
type TelegramConfig struct {
|
||||
ChannelID int64 `json:"channel_id"`
|
||||
Token string `json:"token"`
|
||||
|
@ -22,18 +40,30 @@ type jsonLogConfig struct {
|
|||
Level string `json:"level"`
|
||||
}
|
||||
|
||||
type ExportConfig struct {
|
||||
Mode ExportMode
|
||||
UnixSock string
|
||||
}
|
||||
|
||||
type jsonExportConfig struct {
|
||||
Mode string `json:"mode"`
|
||||
UnixSock string `json:"sock_path"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Telegram *TelegramConfig
|
||||
PollingFrequency time.Duration
|
||||
Hostname string
|
||||
Log *LogConfig
|
||||
Log LogConfig
|
||||
Export ExportConfig
|
||||
}
|
||||
|
||||
type jsonConfig struct {
|
||||
Telegram *TelegramConfig `json:"telegram"`
|
||||
PollingFrequency int64 `json:"polling_frequency"`
|
||||
Hostname string `json:"hostname"`
|
||||
Log *jsonLogConfig `json:"log"`
|
||||
Telegram *TelegramConfig `json:"telegram"`
|
||||
PollingFrequency int64 `json:"polling_frequency"`
|
||||
Hostname string `json:"hostname"`
|
||||
Log jsonLogConfig `json:"log"`
|
||||
Export jsonExportConfig `json:"export"`
|
||||
}
|
||||
|
||||
func parseLevel(lvlStr string) logrus.Level {
|
||||
|
@ -54,12 +84,17 @@ func New(filepath string) (*Config, error) {
|
|||
if err := json.Unmarshal(content, &jsonConf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Telegram: jsonConf.Telegram,
|
||||
PollingFrequency: time.Duration(jsonConf.PollingFrequency) * time.Second,
|
||||
Hostname: jsonConf.Hostname,
|
||||
Log: &LogConfig{
|
||||
Log: LogConfig{
|
||||
Level: parseLevel(jsonConf.Log.Level),
|
||||
},
|
||||
Export: ExportConfig{
|
||||
UnixSock: jsonConf.Export.UnixSock,
|
||||
Mode: exportModeFromStr(jsonConf.Export.Mode),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -48,7 +48,10 @@ func main() {
|
|||
}
|
||||
|
||||
logger.L.Debug("Initializing notification bot")
|
||||
notifBot := bot.New(mainCtx, conf)
|
||||
notifBot, err := bot.New(mainCtx, conf)
|
||||
if err != nil {
|
||||
logger.L.Fatalf("Failed to create notification bot: %s", err.Error())
|
||||
}
|
||||
|
||||
logger.L.Debug("Sending initialization message to Telegram")
|
||||
if err := notifBot.SendInitMessage(); err != nil {
|
||||
|
|
26
tracker/messager/messager.go
Normal file
26
tracker/messager/messager.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package messager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Sender interface {
|
||||
SendMessage(message string) error
|
||||
}
|
||||
|
||||
func FormatInitMsg(currentTime time.Time, publicIP net.IP, hostname string) string {
|
||||
formattedHostname := fmt.Sprintf("`%s`", hostname)
|
||||
formattedIP := strings.ReplaceAll(publicIP.String(), ".", "\\.")
|
||||
return fmt.Sprintf(`\[Host %s\] %s
|
||||
Public IP tracker initialized\. Current IP is %s`, formattedHostname, currentTime.Format(time.RFC1123), formattedIP)
|
||||
}
|
||||
|
||||
func FormatUpdate(currentTime time.Time, publicIP net.IP, hostname string) string {
|
||||
formattedHostname := fmt.Sprintf("`%s`", hostname)
|
||||
formattedIP := strings.ReplaceAll(publicIP.String(), ".", "\\.")
|
||||
return fmt.Sprintf(`\[Host %s\] %s
|
||||
Public IP has changed, new IP is %s`, formattedHostname, currentTime.Format(time.RFC1123), formattedIP)
|
||||
}
|
66
tracker/unixsender/unixsender.go
Normal file
66
tracker/unixsender/unixsender.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package unixsender
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"git.faercol.me/faercol/public-ip-tracker/tracker/config"
|
||||
"git.faercol.me/faercol/public-ip-tracker/tracker/logger"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const method = http.MethodPost
|
||||
const baseURL = "http://unix/"
|
||||
|
||||
type UnixSender struct {
|
||||
logger *logrus.Logger
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
httpClt *http.Client
|
||||
}
|
||||
|
||||
func (s *UnixSender) SendMessage(message string) error {
|
||||
s.logger.Debug("Sending message to unix sock")
|
||||
|
||||
body := bytes.NewBufferString(message)
|
||||
|
||||
req, err := http.NewRequestWithContext(s.ctx, method, baseURL, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := s.httpClt.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run query: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("invalid returncode %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(ctx context.Context, config *config.Config) (*UnixSender, error) {
|
||||
subCtx, cancel := context.WithCancel(ctx)
|
||||
|
||||
logger.L.Infof("Creating Unix exporter to sock %q", config.Export.UnixSock)
|
||||
|
||||
httpClt := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := net.Dialer{}
|
||||
return dialer.DialContext(ctx, "unix", config.Export.UnixSock)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &UnixSender{
|
||||
ctx: subCtx,
|
||||
cancel: cancel,
|
||||
httpClt: &httpClt,
|
||||
logger: &logger.L,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in a new issue