mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 16:29:12 +00:00
feat(irc): improve reconnect and add notifications (#315)
* refactor(irc): nickserv and methods * feat(notifications): add new events and refactor send * feat(irc): handle disconnect reconnect and connect * feat(irc): update config for ergo local irc server * feat(irc): retry initial connect * feat(irc): show nickserv errors
This commit is contained in:
parent
41eef4e8be
commit
f63ace662a
16 changed files with 1343 additions and 133 deletions
|
@ -97,8 +97,8 @@ func main() {
|
|||
indexerService = indexer.NewService(log, cfg.Config, indexerRepo, apiService, schedulingService)
|
||||
filterService = filter.NewService(log, filterRepo, actionRepo, apiService, indexerService)
|
||||
releaseService = release.NewService(log, releaseRepo, actionService, filterService)
|
||||
ircService = irc.NewService(log, ircRepo, releaseService, indexerService)
|
||||
notificationService = notification.NewService(log, notificationRepo)
|
||||
ircService = irc.NewService(log, ircRepo, releaseService, indexerService, notificationService)
|
||||
feedService = feed.NewService(log, feedRepo, feedCacheRepo, releaseService, schedulingService)
|
||||
)
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/Masterminds/squirrel v1.5.1
|
||||
github.com/anacrolix/torrent v1.11.0
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/dcarbone/zadapters/zstdlog v0.3.1
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/ergochat/irc-go v0.1.1-0.20220619021632-5b9a0365c50a
|
||||
|
|
2
go.sum
2
go.sum
|
@ -118,6 +118,8 @@ github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572/go.mod h1:MDwc+vsGEq
|
|||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM=
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
|
|
|
@ -35,18 +35,21 @@ type IrcNetwork struct {
|
|||
}
|
||||
|
||||
type IrcNetworkWithHealth struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"tls"`
|
||||
Pass string `json:"pass"`
|
||||
InviteCommand string `json:"invite_command"`
|
||||
NickServ NickServ `json:"nickserv,omitempty"`
|
||||
Channels []ChannelWithHealth `json:"channels"`
|
||||
Connected bool `json:"connected"`
|
||||
ConnectedSince time.Time `json:"connected_since"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Server string `json:"server"`
|
||||
Port int `json:"port"`
|
||||
TLS bool `json:"tls"`
|
||||
Pass string `json:"pass"`
|
||||
InviteCommand string `json:"invite_command"`
|
||||
NickServ NickServ `json:"nickserv,omitempty"`
|
||||
CurrentNick string `json:"current_nick"`
|
||||
PreferredNick string `json:"preferred_nick"`
|
||||
Channels []ChannelWithHealth `json:"channels"`
|
||||
Connected bool `json:"connected"`
|
||||
ConnectedSince time.Time `json:"connected_since"`
|
||||
ConnectionErrors []string `json:"connection_errors"`
|
||||
}
|
||||
|
||||
type ChannelWithHealth struct {
|
||||
|
|
|
@ -78,11 +78,13 @@ const (
|
|||
type NotificationEvent string
|
||||
|
||||
const (
|
||||
//NotificationEventAppUpdateAvailable NotificationEvent = "APP_UPDATE_AVAILABLE"
|
||||
|
||||
NotificationEventPushApproved NotificationEvent = "PUSH_APPROVED"
|
||||
NotificationEventPushRejected NotificationEvent = "PUSH_REJECTED"
|
||||
NotificationEventPushError NotificationEvent = "PUSH_ERROR"
|
||||
NotificationEventUpdateAvailable NotificationEvent = "UPDATE_AVAILABLE"
|
||||
NotificationEventIRCHealth NotificationEvent = "IRC_HEALTH"
|
||||
NotificationEventIRCDisconnected NotificationEvent = "IRC_DISCONNECTED"
|
||||
NotificationEventIRCReconnected NotificationEvent = "IRC_RECONNECTED"
|
||||
NotificationEventTest NotificationEvent = "TEST"
|
||||
)
|
||||
|
||||
|
|
|
@ -57,7 +57,5 @@ func (s Subscriber) releasePushStatus(actionStatus *domain.ReleaseActionStatus)
|
|||
func (s Subscriber) sendNotification(event *domain.NotificationEvent, payload *domain.NotificationPayload) {
|
||||
s.log.Trace().Msgf("events: '%v' '%+v'", event, payload)
|
||||
|
||||
if err := s.notificationSvc.Send(*event, *payload); err != nil {
|
||||
s.log.Error().Err(err).Msgf("events: '%v' error sending notification", event)
|
||||
}
|
||||
s.notificationSvc.Send(*event, *payload)
|
||||
}
|
||||
|
|
|
@ -11,18 +11,16 @@ import (
|
|||
"github.com/autobrr/autobrr/internal/announce"
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/logger"
|
||||
"github.com/autobrr/autobrr/internal/notification"
|
||||
"github.com/autobrr/autobrr/internal/release"
|
||||
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/dcarbone/zadapters/zstdlog"
|
||||
"github.com/ergochat/irc-go/ircevent"
|
||||
"github.com/ergochat/irc-go/ircmsg"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var (
|
||||
connectTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
type channelHealth struct {
|
||||
m sync.RWMutex
|
||||
|
||||
|
@ -52,40 +50,46 @@ func (h *channelHealth) resetMonitoring() {
|
|||
h.m.Lock()
|
||||
h.monitoring = false
|
||||
h.monitoringSince = time.Time{}
|
||||
h.lastAnnounce = time.Time{}
|
||||
h.m.Unlock()
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
log zerolog.Logger
|
||||
network *domain.IrcNetwork
|
||||
releaseSvc release.Service
|
||||
announceProcessors map[string]announce.Processor
|
||||
definitions map[string]*domain.IndexerDefinition
|
||||
log zerolog.Logger
|
||||
network *domain.IrcNetwork
|
||||
releaseSvc release.Service
|
||||
notificationService notification.Service
|
||||
announceProcessors map[string]announce.Processor
|
||||
definitions map[string]*domain.IndexerDefinition
|
||||
|
||||
client *ircevent.Connection
|
||||
m sync.RWMutex
|
||||
|
||||
lastPing time.Time
|
||||
connected bool
|
||||
connectedSince time.Time
|
||||
// tODO disconnectedTime
|
||||
connected bool
|
||||
connectedSince time.Time
|
||||
haveDisconnected bool
|
||||
manuallyDisconnected bool
|
||||
|
||||
validAnnouncers map[string]struct{}
|
||||
validChannels map[string]struct{}
|
||||
channelHealth map[string]*channelHealth
|
||||
|
||||
connectionErrors []string
|
||||
failedNickServAttempts int
|
||||
}
|
||||
|
||||
func NewHandler(log logger.Logger, network domain.IrcNetwork, definitions []*domain.IndexerDefinition, releaseSvc release.Service) *Handler {
|
||||
func NewHandler(log logger.Logger, network domain.IrcNetwork, definitions []*domain.IndexerDefinition, releaseSvc release.Service, notificationSvc notification.Service) *Handler {
|
||||
h := &Handler{
|
||||
log: log.With().Str("network", network.Server).Logger(),
|
||||
client: nil,
|
||||
network: &network,
|
||||
releaseSvc: releaseSvc,
|
||||
definitions: map[string]*domain.IndexerDefinition{},
|
||||
announceProcessors: map[string]announce.Processor{},
|
||||
validAnnouncers: map[string]struct{}{},
|
||||
validChannels: map[string]struct{}{},
|
||||
channelHealth: map[string]*channelHealth{},
|
||||
log: log.With().Str("network", network.Server).Logger(),
|
||||
client: nil,
|
||||
network: &network,
|
||||
releaseSvc: releaseSvc,
|
||||
notificationService: notificationSvc,
|
||||
definitions: map[string]*domain.IndexerDefinition{},
|
||||
announceProcessors: map[string]announce.Processor{},
|
||||
validAnnouncers: map[string]struct{}{},
|
||||
validChannels: map[string]struct{}{},
|
||||
channelHealth: map[string]*channelHealth{},
|
||||
}
|
||||
|
||||
// init indexer, announceProcessor
|
||||
|
@ -135,6 +139,10 @@ func (h *Handler) removeIndexer() {
|
|||
}
|
||||
|
||||
func (h *Handler) Run() error {
|
||||
// TODO validate
|
||||
// check if network requires nickserv
|
||||
// chech if network or channels requires invite command
|
||||
|
||||
addr := fmt.Sprintf("%v:%d", h.network.Server, h.network.Port)
|
||||
|
||||
subLogger := zstdlog.NewStdLoggerWithLevel(h.log.With().Logger(), zerolog.TraceLevel)
|
||||
|
@ -160,11 +168,14 @@ func (h *Handler) Run() error {
|
|||
}
|
||||
|
||||
h.client.AddConnectCallback(h.onConnect)
|
||||
h.client.AddDisconnectCallback(h.onDisconnect)
|
||||
h.client.AddCallback("MODE", h.handleMode)
|
||||
h.client.AddCallback("INVITE", h.handleInvite)
|
||||
h.client.AddCallback("366", h.handleJoined)
|
||||
h.client.AddCallback("PART", h.handlePart)
|
||||
h.client.AddCallback("PRIVMSG", h.onMessage)
|
||||
h.client.AddCallback("NOTICE", h.onNotice)
|
||||
h.client.AddCallback("NICK", h.onNick)
|
||||
|
||||
if err := h.client.Connect(); err != nil {
|
||||
h.log.Error().Stack().Err(err).Msg("connect error")
|
||||
|
@ -172,11 +183,32 @@ func (h *Handler) Run() error {
|
|||
// reset connection status on handler and channels
|
||||
h.resetConnectionStatus()
|
||||
|
||||
//return err
|
||||
}
|
||||
// count connect attempts
|
||||
connectAttempts := 1
|
||||
|
||||
// set connected since now
|
||||
h.setConnectionStatus()
|
||||
// retry initial connect if network is down
|
||||
// using exponential backoff of 15 seconds
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
h.log.Debug().Msgf("connect attempt %d", connectAttempts)
|
||||
|
||||
err := h.client.Connect()
|
||||
if err != nil {
|
||||
connectAttempts++
|
||||
return err
|
||||
}
|
||||
h.log.Debug().Msgf("connected at attempt %d", connectAttempts)
|
||||
return nil
|
||||
},
|
||||
retry.Delay(time.Second*15),
|
||||
retry.Attempts(25),
|
||||
retry.DelayType(func(n uint, err error, config *retry.Config) time.Duration {
|
||||
return retry.BackOffDelay(n, err, config)
|
||||
}),
|
||||
)
|
||||
|
||||
h.log.Error().Stack().Err(err).Msgf("connect error: attempt %d", connectAttempts)
|
||||
}
|
||||
|
||||
h.client.Loop()
|
||||
|
||||
|
@ -239,12 +271,20 @@ func (h *Handler) AddChannelHealth(channel string) {
|
|||
|
||||
func (h *Handler) Stop() {
|
||||
h.log.Debug().Msg("Disconnecting...")
|
||||
|
||||
h.m.Lock()
|
||||
h.manuallyDisconnected = true
|
||||
h.m.Unlock()
|
||||
h.client.Quit()
|
||||
}
|
||||
|
||||
func (h *Handler) Restart() error {
|
||||
h.log.Debug().Msg("Restarting network...")
|
||||
|
||||
h.m.Lock()
|
||||
h.manuallyDisconnected = true
|
||||
h.m.Unlock()
|
||||
|
||||
h.client.Quit()
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
|
@ -253,41 +293,182 @@ func (h *Handler) Restart() error {
|
|||
}
|
||||
|
||||
func (h *Handler) onConnect(m ircmsg.Message) {
|
||||
identified := false
|
||||
// 1. No nickserv, no invite command - join
|
||||
// 2. Nickserv - join after auth
|
||||
// 3. nickserv and invite command - join after nickserv
|
||||
// 4. invite command - join
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
h.resetConnectErrors()
|
||||
h.setConnectionStatus()
|
||||
|
||||
if h.haveDisconnected {
|
||||
h.notificationService.Send(domain.NotificationEventIRCReconnected, domain.NotificationPayload{
|
||||
Subject: "IRC Reconnected",
|
||||
Message: fmt.Sprintf("Network: %v", h.network.Name),
|
||||
})
|
||||
|
||||
// reset haveDisconnected
|
||||
h.haveDisconnected = false
|
||||
}
|
||||
|
||||
h.log.Debug().Msgf("connected to: %v", h.network.Name)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if h.network.NickServ.Password != "" {
|
||||
err := h.HandleNickServIdentify(h.network.NickServ.Password)
|
||||
if err != nil {
|
||||
if err := h.NickServIdentify(h.network.NickServ.Password); err != nil {
|
||||
h.log.Error().Stack().Err(err).Msg("error nickserv")
|
||||
return
|
||||
}
|
||||
identified = true
|
||||
|
||||
// return and wait for NOTICE of nickserv auth
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
|
||||
if h.network.InviteCommand != "" {
|
||||
err := h.handleConnectCommands(h.network.InviteCommand)
|
||||
if err != nil {
|
||||
if h.network.InviteCommand != "" && h.network.NickServ.Password == "" {
|
||||
if err := h.sendConnectCommands(h.network.InviteCommand); err != nil {
|
||||
h.log.Error().Stack().Err(err).Msgf("error sending connect command %v", h.network.InviteCommand)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
return
|
||||
}
|
||||
|
||||
if !identified {
|
||||
for _, channel := range h.network.Channels {
|
||||
err := h.HandleJoinChannel(channel.Name, channel.Password)
|
||||
if err != nil {
|
||||
h.log.Error().Stack().Err(err).Msgf("error joining channels %v", err)
|
||||
// join channels if no password or no invite command
|
||||
h.JoinChannels()
|
||||
|
||||
}
|
||||
|
||||
func (h *Handler) onDisconnect(m ircmsg.Message) {
|
||||
h.log.Debug().Msgf("DISCONNECT")
|
||||
|
||||
h.haveDisconnected = true
|
||||
|
||||
h.resetConnectionStatus()
|
||||
|
||||
// check if we are responsible for disconnect
|
||||
if !h.manuallyDisconnected {
|
||||
// only send notification if we did not initiate disconnect/restart/stop
|
||||
h.notificationService.Send(domain.NotificationEventIRCDisconnected, domain.NotificationPayload{
|
||||
Subject: "IRC Disconnected unexpectedly",
|
||||
Message: fmt.Sprintf("Network: %v", h.network.Name),
|
||||
})
|
||||
|
||||
// reset
|
||||
h.manuallyDisconnected = false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) onNotice(msg ircmsg.Message) {
|
||||
if msg.Nick() == "NickServ" {
|
||||
h.log.Debug().Msgf("NOTICE from nickserv: %v", msg.Params)
|
||||
|
||||
if contains(msg.Params[1],
|
||||
"Invalid account credentials",
|
||||
"Authentication failed: Invalid account credentials",
|
||||
"password incorrect",
|
||||
) {
|
||||
h.addConnectError("authentication failed: Bad account credentials")
|
||||
h.log.Warn().Msg("NickServ: authentication failed - bad account credentials")
|
||||
|
||||
if h.failedNickServAttempts >= 1 {
|
||||
h.log.Warn().Msgf("NickServ %d failed login attempts", h.failedNickServAttempts)
|
||||
|
||||
// stop network and notify user
|
||||
h.Stop()
|
||||
}
|
||||
|
||||
h.failedNickServAttempts++
|
||||
}
|
||||
|
||||
if contains(msg.Params[1],
|
||||
"Account does not exist",
|
||||
"Authentication failed: Account does not exist", // Nick ANICK isn't registered
|
||||
) {
|
||||
h.addConnectError("authentication failed: account does not exist")
|
||||
|
||||
if h.failedNickServAttempts >= 2 {
|
||||
h.log.Warn().Msgf("NickServ %d failed login attempts", h.failedNickServAttempts)
|
||||
|
||||
// stop network and notify user
|
||||
h.Stop()
|
||||
}
|
||||
|
||||
h.failedNickServAttempts++
|
||||
}
|
||||
|
||||
if contains(msg.Params[1],
|
||||
"This nickname is registered and protected",
|
||||
"please choose a different nick",
|
||||
"choose a different nick",
|
||||
) {
|
||||
|
||||
if h.failedNickServAttempts >= 3 {
|
||||
h.log.Warn().Msgf("NickServ %d failed login attempts", h.failedNickServAttempts)
|
||||
|
||||
h.addConnectError("authentication failed: nick in use and not authenticated")
|
||||
|
||||
// stop network and notify user
|
||||
h.Stop()
|
||||
}
|
||||
|
||||
h.failedNickServAttempts++
|
||||
}
|
||||
|
||||
// params: [test-bot You're now logged in as test-bot]
|
||||
// Password accepted - you are now recognized.
|
||||
if contains(msg.Params[1], "you're now logged in as", "password accepted", "you are now recognized") {
|
||||
h.log.Debug().Msgf("NOTICE nickserv logged in: %v", msg.Params)
|
||||
|
||||
h.resetConnectErrors()
|
||||
h.failedNickServAttempts = 0
|
||||
|
||||
// if no invite command, join
|
||||
if h.network.InviteCommand == "" {
|
||||
h.JoinChannels()
|
||||
return
|
||||
}
|
||||
|
||||
// else send connect commands
|
||||
if err := h.sendConnectCommands(h.network.InviteCommand); err != nil {
|
||||
h.log.Error().Stack().Err(err).Msgf("error sending connect command %v", h.network.InviteCommand)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//[test-bot Invalid parameters. For usage, do /msg NickServ HELP IDENTIFY]
|
||||
// fallback for networks that require both password and nick to NickServ IDENTIFY
|
||||
if contains(msg.Params[1], "invalid parameters", "help identify") {
|
||||
h.log.Debug().Msgf("NOTICE nickserv invalid: %v", msg.Params)
|
||||
|
||||
if err := h.client.Send("PRIVMSG", "NickServ", fmt.Sprintf("IDENTIFY %v %v", h.network.NickServ.Account, h.network.NickServ.Password)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Your nickname is not registered
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s string, substr ...string) bool {
|
||||
s = strings.ToLower(s)
|
||||
for _, c := range substr {
|
||||
c = strings.ToLower(c)
|
||||
if strings.Contains(s, c) {
|
||||
return true
|
||||
} else if c == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *Handler) onNick(msg ircmsg.Message) {
|
||||
h.log.Debug().Msgf("NICK event: %v params: %v", msg.Nick(), msg.Params)
|
||||
|
||||
if h.client.CurrentNick() != h.client.PreferredNick() {
|
||||
h.log.Debug().Msgf("nick miss-match: got %v want %v", h.client.CurrentNick(), h.client.PreferredNick())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) onMessage(msg ircmsg.Message) {
|
||||
|
@ -349,7 +530,18 @@ func (h *Handler) sendToAnnounceProcessor(channel string, msg string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) HandleJoinChannel(channel string, password string) error {
|
||||
func (h *Handler) JoinChannels() {
|
||||
for _, channel := range h.network.Channels {
|
||||
if err := h.JoinChannel(channel.Name, channel.Password); err != nil {
|
||||
h.log.Error().Stack().Err(err).Msgf("error joining channel %v", channel.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) JoinChannel(channel string, password string) error {
|
||||
m := ircmsg.Message{
|
||||
Command: "JOIN",
|
||||
Params: []string{channel},
|
||||
|
@ -397,12 +589,12 @@ func (h *Handler) handlePart(msg ircmsg.Message) {
|
|||
|
||||
// TODO remove announceProcessor
|
||||
|
||||
h.log.Info().Msgf("Left channel '%v'", channel)
|
||||
h.log.Debug().Msgf("Left channel '%v'", channel)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Handler) HandlePartChannel(channel string) error {
|
||||
func (h *Handler) PartChannel(channel string) error {
|
||||
h.log.Debug().Msgf("PART channel %v", channel)
|
||||
|
||||
err := h.client.Part(channel)
|
||||
|
@ -452,7 +644,7 @@ func (h *Handler) handleJoined(msg ircmsg.Message) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleConnectCommands(msg string) error {
|
||||
func (h *Handler) sendConnectCommands(msg string) error {
|
||||
connectCommand := strings.ReplaceAll(msg, "/msg", "")
|
||||
connectCommands := strings.Split(connectCommand, ",")
|
||||
|
||||
|
@ -495,7 +687,7 @@ func (h *Handler) handleInvite(msg ircmsg.Message) {
|
|||
return
|
||||
}
|
||||
|
||||
func (h *Handler) HandleNickServIdentify(password string) error {
|
||||
func (h *Handler) NickServIdentify(password string) error {
|
||||
m := ircmsg.Message{
|
||||
Command: "PRIVMSG",
|
||||
Params: []string{"NickServ", "IDENTIFY", password},
|
||||
|
@ -512,7 +704,7 @@ func (h *Handler) HandleNickServIdentify(password string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) HandleNickChange(nick string) error {
|
||||
func (h *Handler) NickChange(nick string) error {
|
||||
h.log.Debug().Msgf("Nick change: %v", nick)
|
||||
|
||||
h.client.SetNick(nick)
|
||||
|
@ -520,6 +712,14 @@ func (h *Handler) HandleNickChange(nick string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) CurrentNick() string {
|
||||
return h.client.CurrentNick()
|
||||
}
|
||||
|
||||
func (h *Handler) PreferredNick() string {
|
||||
return h.client.PreferredNick()
|
||||
}
|
||||
|
||||
func (h *Handler) handleMode(msg ircmsg.Message) {
|
||||
h.log.Debug().Msgf("MODE: %+v", msg)
|
||||
|
||||
|
@ -528,22 +728,15 @@ func (h *Handler) handleMode(msg ircmsg.Message) {
|
|||
return
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if h.network.NickServ.Password != "" && !strings.Contains(msg.Params[0], h.client.Nick) || !strings.Contains(msg.Params[1], "+r") {
|
||||
h.log.Trace().Msgf("MODE: Not correct permission yet: %v", msg.Params)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ch := range h.network.Channels {
|
||||
err := h.HandleJoinChannel(ch.Name, ch.Password)
|
||||
if err != nil {
|
||||
h.log.Error().Err(err).Msgf("error joining channel: %v", ch.Name)
|
||||
continue
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
// join channels
|
||||
h.JoinChannels()
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -568,14 +761,6 @@ func (h *Handler) isValidChannel(channel string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (h *Handler) setLastPing() {
|
||||
h.lastPing = time.Now()
|
||||
}
|
||||
|
||||
func (h *Handler) GetLastPing() time.Time {
|
||||
return h.lastPing
|
||||
}
|
||||
|
||||
// irc line can contain lots of extra stuff like color so lets clean that
|
||||
func (h *Handler) cleanMessage(message string) string {
|
||||
var regexMessageClean = `\x0f|\x1f|\x02|\x03(?:[\d]{1,2}(?:,[\d]{1,2})?)?`
|
||||
|
@ -588,3 +773,17 @@ func (h *Handler) cleanMessage(message string) string {
|
|||
|
||||
return rxp.ReplaceAllString(message, "")
|
||||
}
|
||||
|
||||
func (h *Handler) addConnectError(message string) {
|
||||
h.m.Lock()
|
||||
defer h.m.Unlock()
|
||||
|
||||
h.connectionErrors = append(h.connectionErrors, message)
|
||||
}
|
||||
|
||||
func (h *Handler) resetConnectErrors() {
|
||||
h.m.Lock()
|
||||
defer h.m.Unlock()
|
||||
|
||||
h.connectionErrors = []string{}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/internal/indexer"
|
||||
"github.com/autobrr/autobrr/internal/logger"
|
||||
"github.com/autobrr/autobrr/internal/notification"
|
||||
"github.com/autobrr/autobrr/internal/release"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -28,24 +29,26 @@ type Service interface {
|
|||
}
|
||||
|
||||
type service struct {
|
||||
log logger.Logger
|
||||
repo domain.IrcRepo
|
||||
releaseService release.Service
|
||||
indexerService indexer.Service
|
||||
indexerMap map[string]string
|
||||
handlers map[handlerKey]*Handler
|
||||
log logger.Logger
|
||||
repo domain.IrcRepo
|
||||
releaseService release.Service
|
||||
indexerService indexer.Service
|
||||
notificationService notification.Service
|
||||
indexerMap map[string]string
|
||||
handlers map[handlerKey]*Handler
|
||||
|
||||
stopWG sync.WaitGroup
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewService(log logger.Logger, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service) Service {
|
||||
func NewService(log logger.Logger, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service, notificationSvc notification.Service) Service {
|
||||
return &service{
|
||||
log: log,
|
||||
repo: repo,
|
||||
releaseService: releaseSvc,
|
||||
indexerService: indexerSvc,
|
||||
handlers: make(map[handlerKey]*Handler),
|
||||
log: log,
|
||||
repo: repo,
|
||||
releaseService: releaseSvc,
|
||||
indexerService: indexerSvc,
|
||||
notificationService: notificationSvc,
|
||||
handlers: make(map[handlerKey]*Handler),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +82,7 @@ func (s *service) StartHandlers() {
|
|||
definitions := s.indexerService.GetIndexersByIRCNetwork(network.Server)
|
||||
|
||||
// init new irc handler
|
||||
handler := NewHandler(s.log, network, definitions, s.releaseService)
|
||||
handler := NewHandler(s.log, network, definitions, s.releaseService, s.notificationService)
|
||||
|
||||
// use network.Server + nick to use multiple indexers with different nick per network
|
||||
// this allows for multiple handlers to one network
|
||||
|
@ -135,7 +138,7 @@ func (s *service) startNetwork(network domain.IrcNetwork) error {
|
|||
definitions := s.indexerService.GetIndexersByIRCNetwork(network.Server)
|
||||
|
||||
// init new irc handler
|
||||
handler := NewHandler(s.log, network, definitions, s.releaseService)
|
||||
handler := NewHandler(s.log, network, definitions, s.releaseService, s.notificationService)
|
||||
|
||||
s.handlers[handlerKey{network.Server, network.NickServ.Account}] = handler
|
||||
s.lock.Unlock()
|
||||
|
@ -198,15 +201,13 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error
|
|||
if handler.NickServ.Account != network.NickServ.Account {
|
||||
s.log.Debug().Msg("changing nick")
|
||||
|
||||
err := existingHandler.HandleNickChange(network.NickServ.Account)
|
||||
if err != nil {
|
||||
if err := existingHandler.NickChange(network.NickServ.Account); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("failed to change nick %q", network.NickServ.Account)
|
||||
}
|
||||
} else if handler.NickServ.Password != network.NickServ.Password {
|
||||
s.log.Debug().Msg("nickserv: changing password")
|
||||
|
||||
err := existingHandler.HandleNickServIdentify(network.NickServ.Password)
|
||||
if err != nil {
|
||||
if err := existingHandler.NickServIdentify(network.NickServ.Password); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("failed to identify with nickserv %q", network.NickServ.Account)
|
||||
}
|
||||
}
|
||||
|
@ -252,8 +253,8 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error
|
|||
// leave channels
|
||||
for _, leaveChannel := range channelsToLeave {
|
||||
s.log.Debug().Msgf("%v: part channel %v", network.Server, leaveChannel)
|
||||
err := existingHandler.HandlePartChannel(leaveChannel)
|
||||
if err != nil {
|
||||
|
||||
if err := existingHandler.PartChannel(leaveChannel); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("failed to leave channel: %q", leaveChannel)
|
||||
}
|
||||
}
|
||||
|
@ -261,8 +262,8 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error
|
|||
// join channels
|
||||
for _, joinChannel := range channelsToJoin {
|
||||
s.log.Debug().Msgf("%v: join new channel %v", network.Server, joinChannel)
|
||||
err := existingHandler.HandleJoinChannel(joinChannel.Name, joinChannel.Password)
|
||||
if err != nil {
|
||||
|
||||
if err := existingHandler.JoinChannel(joinChannel.Name, joinChannel.Password); err != nil {
|
||||
s.log.Error().Stack().Err(err).Msgf("failed to join channel: %q", joinChannel.Name)
|
||||
}
|
||||
}
|
||||
|
@ -386,17 +387,18 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor
|
|||
|
||||
for _, n := range networks {
|
||||
netw := domain.IrcNetworkWithHealth{
|
||||
ID: n.ID,
|
||||
Name: n.Name,
|
||||
Enabled: n.Enabled,
|
||||
Server: n.Server,
|
||||
Port: n.Port,
|
||||
TLS: n.TLS,
|
||||
Pass: n.Pass,
|
||||
InviteCommand: n.InviteCommand,
|
||||
NickServ: n.NickServ,
|
||||
Connected: false,
|
||||
Channels: []domain.ChannelWithHealth{},
|
||||
ID: n.ID,
|
||||
Name: n.Name,
|
||||
Enabled: n.Enabled,
|
||||
Server: n.Server,
|
||||
Port: n.Port,
|
||||
TLS: n.TLS,
|
||||
Pass: n.Pass,
|
||||
InviteCommand: n.InviteCommand,
|
||||
NickServ: n.NickServ,
|
||||
Connected: false,
|
||||
Channels: []domain.ChannelWithHealth{},
|
||||
ConnectionErrors: []string{},
|
||||
}
|
||||
|
||||
handler, ok := s.handlers[handlerKey{n.Server, n.NickServ.Account}]
|
||||
|
@ -404,10 +406,21 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor
|
|||
// only set connected and connected since if we have an active handler and connection
|
||||
if handler.client.Connected() {
|
||||
handler.m.RLock()
|
||||
|
||||
netw.Connected = handler.connected
|
||||
netw.ConnectedSince = handler.connectedSince
|
||||
|
||||
// current and preferred nick is only available if the network is connected
|
||||
netw.CurrentNick = handler.CurrentNick()
|
||||
netw.PreferredNick = handler.PreferredNick()
|
||||
|
||||
handler.m.RUnlock()
|
||||
}
|
||||
|
||||
// if we have any connection errors like bad nickserv auth add them here
|
||||
if len(handler.connectionErrors) > 0 {
|
||||
netw.ConnectionErrors = handler.connectionErrors
|
||||
}
|
||||
}
|
||||
|
||||
channels, err := s.repo.ListChannels(n.ID)
|
||||
|
|
|
@ -140,6 +140,10 @@ func (a *discordSender) buildEmbed(event domain.NotificationEvent, payload domai
|
|||
color = GRAY
|
||||
case domain.NotificationEventPushError:
|
||||
color = RED
|
||||
case domain.NotificationEventIRCDisconnected:
|
||||
color = RED
|
||||
case domain.NotificationEventIRCReconnected:
|
||||
color = GREEN
|
||||
case domain.NotificationEventTest:
|
||||
color = LIGHT_BLUE
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ type Service interface {
|
|||
Store(ctx context.Context, n domain.Notification) (*domain.Notification, error)
|
||||
Update(ctx context.Context, n domain.Notification) (*domain.Notification, error)
|
||||
Delete(ctx context.Context, id int) error
|
||||
Send(event domain.NotificationEvent, payload domain.NotificationPayload) error
|
||||
Send(event domain.NotificationEvent, payload domain.NotificationPayload)
|
||||
Test(ctx context.Context, notification domain.Notification) error
|
||||
}
|
||||
|
||||
|
@ -109,17 +109,19 @@ func (s *service) registerSenders() {
|
|||
}
|
||||
|
||||
// Send notifications
|
||||
func (s *service) Send(event domain.NotificationEvent, payload domain.NotificationPayload) error {
|
||||
func (s *service) Send(event domain.NotificationEvent, payload domain.NotificationPayload) {
|
||||
s.log.Debug().Msgf("sending notification for %v", string(event))
|
||||
|
||||
for _, sender := range s.senders {
|
||||
// check if sender is active and have notification types
|
||||
if sender.CanSend(event) {
|
||||
sender.Send(event, payload)
|
||||
go func() {
|
||||
for _, sender := range s.senders {
|
||||
// check if sender is active and have notification types
|
||||
if sender.CanSend(event) {
|
||||
sender.Send(event, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) Test(ctx context.Context, notification domain.Notification) error {
|
||||
|
|
|
@ -2,12 +2,13 @@ version: "3.8"
|
|||
|
||||
services:
|
||||
ergo:
|
||||
image: ghcr.io/ergochat/ergo:stable
|
||||
image: ghcr.io/ergochat/ergo:latest
|
||||
ports:
|
||||
- "6667:6667/tcp"
|
||||
- "6697:6697/tcp"
|
||||
volumes:
|
||||
- data:/ircd
|
||||
- ergo-data:/ircd
|
||||
- ./ircd.yaml:/ircd/ircd.yaml
|
||||
|
||||
volumes:
|
||||
data:
|
||||
ergo-data:
|
||||
|
|
964
test/integration/ircd.yaml
Normal file
964
test/integration/ircd.yaml
Normal file
|
@ -0,0 +1,964 @@
|
|||
# This is the "traditional" or "mainstream" config file for Ergo.
|
||||
# It tries to replicate the behavior of other ircds, at the cost of not
|
||||
# taking full advantage of Ergo's features. This config is suitable for use
|
||||
# in IRCv3 conformance testing.
|
||||
|
||||
# network configuration
|
||||
network:
|
||||
# name of the network
|
||||
name: ErgoTest
|
||||
|
||||
# server configuration
|
||||
server:
|
||||
# server name
|
||||
name: ergo.test
|
||||
|
||||
# addresses to listen on
|
||||
listeners:
|
||||
# This version of the config provides a public plaintext listener on
|
||||
# port 6667 for testing and compatibility with legacy applications.
|
||||
# We recommend disabling this listener in a production setting
|
||||
# and replacing it with loopback-only listeners (see default.yaml):
|
||||
":6667":
|
||||
|
||||
# The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
|
||||
":6697":
|
||||
# this is a standard TLS configuration with a single certificate;
|
||||
# see the manual for instructions on how to configure SNI
|
||||
tls:
|
||||
cert: fullchain.pem
|
||||
key: privkey.pem
|
||||
# 'proxy' should typically be false. It's for cloud load balancers that
|
||||
# always send a PROXY protocol header ahead of the connection. See the
|
||||
# manual ("Reverse proxies") for more details.
|
||||
proxy: false
|
||||
# optionally set the minimum TLS version (defaults to 1.0):
|
||||
# min-tls-version: 1.2
|
||||
|
||||
# Example of a Unix domain socket for proxying:
|
||||
# "/tmp/ergo_sock":
|
||||
|
||||
# Example of a Tor listener: any connection that comes in on this listener will
|
||||
# be considered a Tor connection. It is strongly recommended that this listener
|
||||
# *not* be on a public interface --- it should be on 127.0.0.0/8 or unix domain:
|
||||
# "/hidden_service_sockets/ergo_tor_sock":
|
||||
# tor: true
|
||||
|
||||
# Example of a WebSocket listener:
|
||||
# ":8097":
|
||||
# websocket: true
|
||||
# tls:
|
||||
# cert: fullchain.pem
|
||||
# key: privkey.pem
|
||||
|
||||
# sets the permissions for Unix listen sockets. on a typical Linux system,
|
||||
# the default is 0775 or 0755, which prevents other users/groups from connecting
|
||||
# to the socket. With 0777, it behaves like a normal TCP socket
|
||||
# where anyone can connect.
|
||||
unix-bind-mode: 0777
|
||||
|
||||
# configure the behavior of Tor listeners (ignored if you didn't enable any):
|
||||
tor-listeners:
|
||||
# if this is true, connections from Tor must authenticate with SASL
|
||||
require-sasl: false
|
||||
|
||||
# what hostname should be displayed for Tor connections?
|
||||
vhost: "tor-network.onion"
|
||||
|
||||
# allow at most this many connections at once (0 for no limit):
|
||||
max-connections: 64
|
||||
|
||||
# connection throttling (limit how many connection attempts are allowed at once):
|
||||
throttle-duration: 10m
|
||||
# set to 0 to disable throttling:
|
||||
max-connections-per-duration: 64
|
||||
|
||||
# strict transport security, to get clients to automagically use TLS
|
||||
sts:
|
||||
# whether to advertise STS
|
||||
#
|
||||
# to stop advertising STS, leave this enabled and set 'duration' below to "0". this will
|
||||
# advertise to connecting users that the STS policy they have saved is no longer valid
|
||||
enabled: false
|
||||
|
||||
# how long clients should be forced to use TLS for.
|
||||
# setting this to a too-long time will mean bad things if you later remove your TLS.
|
||||
# the default duration below is 1 month, 2 days and 5 minutes.
|
||||
duration: 1mo2d5m
|
||||
|
||||
# tls port - you should be listening on this port above
|
||||
port: 6697
|
||||
|
||||
# should clients include this STS policy when they ship their inbuilt preload lists?
|
||||
preload: false
|
||||
|
||||
websockets:
|
||||
# Restrict the origin of WebSocket connections by matching the "Origin" HTTP
|
||||
# header. This setting causes ergo to reject websocket connections unless
|
||||
# they originate from a page on one of the whitelisted websites in this list.
|
||||
# This prevents malicious websites from making their visitors connect to your
|
||||
# ergo instance without their knowledge. An empty list means there are no
|
||||
# restrictions.
|
||||
allowed-origins:
|
||||
# - "https://ergo.chat"
|
||||
# - "https://*.ergo.chat"
|
||||
|
||||
# casemapping controls what kinds of strings are permitted as identifiers (nicknames,
|
||||
# channel names, account names, etc.), and how they are normalized for case.
|
||||
# with the recommended default of 'precis', UTF8 identifiers that are "sane"
|
||||
# (according to RFC 8265) are allowed, and the server additionally tries to protect
|
||||
# against confusable characters ("homoglyph attacks").
|
||||
# the other options are 'ascii' (traditional ASCII-only identifiers), and 'permissive',
|
||||
# which allows identifiers to contain unusual characters like emoji, but makes users
|
||||
# vulnerable to homoglyph attacks. unless you're really confident in your decision,
|
||||
# we recommend leaving this value at its default (changing it once the network is
|
||||
# already up and running is problematic).
|
||||
casemapping: "precis"
|
||||
|
||||
# enforce-utf8 controls whether the server will preemptively discard non-UTF8
|
||||
# messages (since they cannot be relayed to websocket clients), or will allow
|
||||
# them and relay them to non-websocket clients (as in traditional IRC).
|
||||
enforce-utf8: true
|
||||
|
||||
# whether to look up user hostnames with reverse DNS. there are 3 possibilities:
|
||||
# 1. [enabled here] lookup-hostnames enabled, IP cloaking disabled; users will see each other's hostnames
|
||||
# 2. lookup-hostnames disabled, IP cloaking disabled; users will see each other's numeric IPs
|
||||
# 3. IP cloaking enabled; users will see cloaked hostnames
|
||||
lookup-hostnames: true
|
||||
# whether to confirm hostname lookups using "forward-confirmed reverse DNS", i.e., for
|
||||
# any hostname returned from reverse DNS, resolve it back to an IP address and reject it
|
||||
# unless it matches the connecting IP
|
||||
forward-confirm-hostnames: true
|
||||
|
||||
# use ident protocol to get usernames
|
||||
check-ident: true
|
||||
|
||||
# ignore the supplied user/ident string from the USER command, always setting user/ident
|
||||
# to the following literal value; this can potentially reduce confusion and simplify bans.
|
||||
# the value must begin with a '~' character. comment out / omit to disable:
|
||||
#coerce-ident: '~u'
|
||||
|
||||
# password to login to the server, generated using `ergo genpasswd`:
|
||||
#password: "$2a$04$0123456789abcdef0123456789abcdef0123456789abcdef01234"
|
||||
|
||||
# motd filename
|
||||
# if you change the motd, you should move it to ircd.motd
|
||||
motd: ergo.motd
|
||||
|
||||
# motd formatting codes
|
||||
# if this is true, the motd is escaped using formatting codes like $c, $b, and $i
|
||||
motd-formatting: true
|
||||
|
||||
# relaying using the RELAYMSG command
|
||||
relaymsg:
|
||||
# is relaymsg enabled at all?
|
||||
enabled: true
|
||||
|
||||
# which character(s) are reserved for relayed nicks?
|
||||
separators: "/"
|
||||
|
||||
# can channel operators use RELAYMSG in their channels?
|
||||
# our implementation of RELAYMSG makes it safe for chanops to use without the
|
||||
# possibility of real users being silently spoofed
|
||||
available-to-chanops: true
|
||||
|
||||
# IPs/CIDRs the PROXY command can be used from
|
||||
# This should be restricted to localhost (127.0.0.1/8, ::1/128, and unix sockets).
|
||||
# Unless you have a good reason. you should also add these addresses to the
|
||||
# connection limits and throttling exemption lists.
|
||||
proxy-allowed-from:
|
||||
- localhost
|
||||
# - "192.168.1.1"
|
||||
# - "192.168.10.1/24"
|
||||
|
||||
# controls the use of the WEBIRC command (by IRC<->web interfaces, bouncers and similar)
|
||||
webirc:
|
||||
# one webirc block -- should correspond to one set of gateways
|
||||
-
|
||||
# SHA-256 fingerprint of the TLS certificate the gateway must use to connect
|
||||
# (comment this out to use passwords only)
|
||||
certfp: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
|
||||
# password the gateway uses to connect, made with `ergo genpasswd`
|
||||
password: "$2a$04$abcdef0123456789abcdef0123456789abcdef0123456789abcde" # test
|
||||
|
||||
# IPs/CIDRs that can use this webirc command
|
||||
# you should also add these addresses to the connection limits and throttling exemption lists
|
||||
hosts:
|
||||
- localhost
|
||||
# - "192.168.1.1"
|
||||
# - "192.168.10.1/24"
|
||||
|
||||
# maximum length of clients' sendQ in bytes
|
||||
# this should be big enough to hold bursts of channel/direct messages
|
||||
max-sendq: 96k
|
||||
|
||||
# compatibility with legacy clients
|
||||
compatibility:
|
||||
# many clients require that the final parameter of certain messages be an
|
||||
# RFC1459 trailing parameter, i.e., prefixed with :, whether or not this is
|
||||
# actually required. this forces Ergo to send those parameters
|
||||
# as trailings. this is recommended unless you're testing clients for conformance;
|
||||
# defaults to true when unset for that reason.
|
||||
force-trailing: true
|
||||
|
||||
# some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower) do not
|
||||
# respond correctly to SASL messages with the server name as a prefix:
|
||||
# https://github.com/znc/znc/issues/1212
|
||||
# this works around that bug, allowing them to use SASL.
|
||||
send-unprefixed-sasl: true
|
||||
|
||||
# traditionally, IRC servers will truncate and send messages that are
|
||||
# too long to be relayed intact. this behavior can be disabled by setting
|
||||
# allow-truncation to false, in which case Ergo will reject the message
|
||||
# and return an error to the client. (note that this option defaults to true
|
||||
# when unset.)
|
||||
allow-truncation: true
|
||||
|
||||
# IP-based DoS protection
|
||||
ip-limits:
|
||||
# whether to limit the total number of concurrent connections per IP/CIDR
|
||||
count: true
|
||||
# maximum concurrent connections per IP/CIDR
|
||||
max-concurrent-connections: 16
|
||||
|
||||
# whether to restrict the rate of new connections per IP/CIDR
|
||||
throttle: true
|
||||
# how long to keep track of connections for
|
||||
window: 10m
|
||||
# maximum number of new connections per IP/CIDR within the given duration
|
||||
max-connections-per-window: 32
|
||||
|
||||
# how wide the CIDR should be for IPv4 (a /32 is a fully specified IPv4 address)
|
||||
cidr-len-ipv4: 32
|
||||
# how wide the CIDR should be for IPv6 (a /64 is the typical prefix assigned
|
||||
# by an ISP to an individual customer for their LAN)
|
||||
cidr-len-ipv6: 64
|
||||
|
||||
# IPs/networks which are exempted from connection limits
|
||||
exempted:
|
||||
- "localhost"
|
||||
# - "192.168.1.1"
|
||||
# - "2001:0db8::/32"
|
||||
|
||||
# custom connection limits for certain IPs/networks.
|
||||
custom-limits:
|
||||
#"irccloud":
|
||||
# nets:
|
||||
# - "192.184.9.108" # highgate.irccloud.com
|
||||
# - "192.184.9.110" # ealing.irccloud.com
|
||||
# - "192.184.9.112" # charlton.irccloud.com
|
||||
# - "192.184.10.118" # brockwell.irccloud.com
|
||||
# - "192.184.10.9" # tooting.irccloud.com
|
||||
# - "192.184.8.73" # hathersage.irccloud.com
|
||||
# - "192.184.8.103" # stonehaven.irccloud.com
|
||||
# - "5.254.36.57" # tinside.irccloud.com
|
||||
# - "5.254.36.56/29" # additional ipv4 net
|
||||
# - "2001:67c:2f08::/48"
|
||||
# - "2a03:5180:f::/64"
|
||||
# max-concurrent-connections: 2048
|
||||
# max-connections-per-window: 2048
|
||||
|
||||
# pluggable IP ban mechanism, via subprocess invocation
|
||||
# this can be used to check new connections against a DNSBL, for example
|
||||
# see the manual for details on how to write an IP ban checking script
|
||||
ip-check-script:
|
||||
enabled: false
|
||||
command: "/usr/local/bin/check-ip-ban"
|
||||
# constant list of args to pass to the command; the actual query
|
||||
# and result are transmitted over stdin/stdout:
|
||||
args: []
|
||||
# timeout for process execution, after which we send a SIGTERM:
|
||||
timeout: 9s
|
||||
# how long after the SIGTERM before we follow up with a SIGKILL:
|
||||
kill-timeout: 1s
|
||||
# how many scripts are allowed to run at once? 0 for no limit:
|
||||
max-concurrency: 64
|
||||
# if true, only check anonymous connections (not logged into an account)
|
||||
# at the very end of the handshake:
|
||||
exempt-sasl: false
|
||||
|
||||
# IP cloaking hides users' IP addresses from other users and from channel admins
|
||||
# (but not from server admins), while still allowing channel admins to ban
|
||||
# offending IP addresses or networks. In place of hostnames derived from reverse
|
||||
# DNS, users see fake domain names like pwbs2ui4377257x8.irc. These names are
|
||||
# generated deterministically from the underlying IP address, but if the underlying
|
||||
# IP is not already known, it is infeasible to recover it from the cloaked name.
|
||||
ip-cloaking:
|
||||
# whether to enable IP cloaking
|
||||
enabled: false
|
||||
|
||||
# whether to use these cloak settings (specifically, `netname` and `num-bits`)
|
||||
# to produce unique hostnames for always-on clients. you can enable this even if
|
||||
# you disabled IP cloaking for normal clients above. if this is disabled,
|
||||
# always-on clients will all have an identical hostname (the server name).
|
||||
enabled-for-always-on: true
|
||||
|
||||
# fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.irc
|
||||
# you may want to use your network name here
|
||||
netname: "irc"
|
||||
|
||||
# the cloaked hostname is derived only from the CIDR (most significant bits
|
||||
# of the IP address), up to a configurable number of bits. this is the
|
||||
# granularity at which bans will take effect for IPv4. Note that changing
|
||||
# this value will invalidate any stored bans.
|
||||
cidr-len-ipv4: 32
|
||||
|
||||
# analogous granularity for IPv6
|
||||
cidr-len-ipv6: 64
|
||||
|
||||
# number of bits of hash output to include in the cloaked hostname.
|
||||
# more bits means less likelihood of distinct IPs colliding,
|
||||
# at the cost of a longer cloaked hostname. if this value is set to 0,
|
||||
# all users will receive simply `netname` as their cloaked hostname.
|
||||
num-bits: 64
|
||||
|
||||
# secure-nets identifies IPs and CIDRs which are secure at layer 3,
|
||||
# for example, because they are on a trusted internal LAN or a VPN.
|
||||
# plaintext connections from these IPs and CIDRs will be considered
|
||||
# secure (clients will receive the +Z mode and be allowed to resume
|
||||
# or reattach to secure connections). note that loopback IPs are always
|
||||
# considered secure:
|
||||
secure-nets:
|
||||
# - "10.0.0.0/8"
|
||||
|
||||
# Ergo will write files to disk under certain circumstances, e.g.,
|
||||
# CPU profiling or data export. by default, these files will be written
|
||||
# to the working directory. set this to customize:
|
||||
#output-path: "/home/ergo/out"
|
||||
|
||||
# the hostname used by "services", e.g., NickServ, defaults to "localhost",
|
||||
# e.g., `NickServ!NickServ@localhost`. uncomment this to override:
|
||||
#override-services-hostname: "example.network"
|
||||
|
||||
# in a "closed-loop" system where you control the server and all the clients,
|
||||
# you may want to increase the maximum (non-tag) length of an IRC line from
|
||||
# the default value of 512. DO NOT change this on a public server:
|
||||
# max-line-len: 512
|
||||
|
||||
# send all 0's as the LUSERS (user counts) output to non-operators; potentially useful
|
||||
# if you don't want to publicize how popular the server is
|
||||
suppress-lusers: false
|
||||
|
||||
# account options
|
||||
accounts:
|
||||
# is account authentication enabled, i.e., can users log into existing accounts?
|
||||
authentication-enabled: true
|
||||
|
||||
# account registration
|
||||
registration:
|
||||
# can users register new accounts for themselves? if this is false, operators with
|
||||
# the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER`
|
||||
enabled: true
|
||||
|
||||
# can users use the REGISTER command to register before fully connecting?
|
||||
allow-before-connect: true
|
||||
|
||||
# global throttle on new account creation
|
||||
throttling:
|
||||
enabled: true
|
||||
# window
|
||||
duration: 10m
|
||||
# number of attempts allowed within the window
|
||||
max-attempts: 30
|
||||
|
||||
# this is the bcrypt cost we'll use for account passwords
|
||||
# (note that 4 is the lowest value allowed by the bcrypt library)
|
||||
bcrypt-cost: 4
|
||||
|
||||
# length of time a user has to verify their account before it can be re-registered
|
||||
verify-timeout: "32h"
|
||||
|
||||
# options for email verification of account registrations
|
||||
email-verification:
|
||||
enabled: false
|
||||
sender: "admin@my.network"
|
||||
require-tls: true
|
||||
helo-domain: "my.network" # defaults to server name if unset
|
||||
# options to enable DKIM signing of outgoing emails (recommended, but
|
||||
# requires creating a DNS entry for the public key):
|
||||
# dkim:
|
||||
# domain: "my.network"
|
||||
# selector: "20200229"
|
||||
# key-file: "dkim.pem"
|
||||
# to use an MTA/smarthost instead of sending email directly:
|
||||
# mta:
|
||||
# server: localhost
|
||||
# port: 25
|
||||
# username: "admin"
|
||||
# password: "hunter2"
|
||||
blacklist-regexes:
|
||||
# - ".*@mailinator.com"
|
||||
timeout: 60s
|
||||
# email-based password reset:
|
||||
password-reset:
|
||||
enabled: false
|
||||
# time before we allow resending the email
|
||||
cooldown: 1h
|
||||
# time for which a password reset code is valid
|
||||
timeout: 1d
|
||||
|
||||
# throttle account login attempts (to prevent either password guessing, or DoS
|
||||
# attacks on the server aimed at forcing repeated expensive bcrypt computations)
|
||||
login-throttling:
|
||||
enabled: true
|
||||
|
||||
# window
|
||||
duration: 1m
|
||||
|
||||
# number of attempts allowed within the window
|
||||
max-attempts: 3
|
||||
|
||||
# some clients (notably Pidgin and Hexchat) offer only a single password field,
|
||||
# which makes it impossible to specify a separate server password (for the PASS
|
||||
# command) and SASL password. if this option is set to true, a client that
|
||||
# successfully authenticates with SASL will not be required to send
|
||||
# PASS as well, so it can be configured to authenticate with SASL only.
|
||||
skip-server-password: false
|
||||
|
||||
# enable login to accounts via the PASS command, e.g., PASS account:password
|
||||
# this is useful for compatibility with old clients that don't support SASL
|
||||
login-via-pass-command: false
|
||||
|
||||
# require-sasl controls whether clients are required to have accounts
|
||||
# (and sign into them using SASL) to connect to the server
|
||||
require-sasl:
|
||||
# if this is enabled, all clients must authenticate with SASL while connecting.
|
||||
# WARNING: for a private server, you MUST set accounts.registration.enabled
|
||||
# to false as well, in order to prevent non-administrators from registering
|
||||
# accounts.
|
||||
enabled: false
|
||||
|
||||
# IPs/CIDRs which are exempted from the account requirement
|
||||
exempted:
|
||||
- "localhost"
|
||||
# - '10.10.0.0/16'
|
||||
|
||||
# nick-reservation controls how, and whether, nicknames are linked to accounts
|
||||
nick-reservation:
|
||||
# is there any enforcement of reserved nicknames?
|
||||
enabled: true
|
||||
|
||||
# how many nicknames, in addition to the account name, can be reserved?
|
||||
# (note that additional nicks are unusable under force-nick-equals-account
|
||||
# or if the client is always-on)
|
||||
additional-nick-limit: 2
|
||||
|
||||
# method describes how nickname reservation is handled
|
||||
# strict: users must already be logged in to their account (via
|
||||
# SASL, PASS account:password, or /NickServ IDENTIFY)
|
||||
# in order to use their reserved nickname(s)
|
||||
# optional: no enforcement by default, but allow users to opt in to
|
||||
# the enforcement level of their choice
|
||||
method: optional
|
||||
|
||||
# allow users to set their own nickname enforcement status, e.g.,
|
||||
# to opt out of strict enforcement
|
||||
allow-custom-enforcement: true
|
||||
|
||||
# format for guest nicknames:
|
||||
# 1. these nicknames cannot be registered or reserved
|
||||
# 2. if a client is automatically renamed by the server,
|
||||
# this is the template that will be used (e.g., Guest-nccj6rgmt97cg)
|
||||
# 3. if enforce-guest-format (see below) is enabled, clients without
|
||||
# a registered account will have this template applied to their
|
||||
# nicknames (e.g., 'katie' will become 'Guest-katie')
|
||||
guest-nickname-format: "Guest-*"
|
||||
|
||||
# when enabled, forces users not logged into an account to use
|
||||
# a nickname matching the guest template. a caveat: this may prevent
|
||||
# users from choosing nicknames in scripts different from the guest
|
||||
# nickname format.
|
||||
force-guest-format: false
|
||||
|
||||
# when enabled, forces users logged into an account to use the
|
||||
# account name as their nickname. when combined with strict nickname
|
||||
# enforcement, this lets users treat nicknames and account names
|
||||
# as equivalent for the purpose of ban/invite/exception lists.
|
||||
force-nick-equals-account: false
|
||||
|
||||
# parallel setting to force-nick-equals-account: if true, this forbids
|
||||
# anonymous users (i.e., users not logged into an account) to change their
|
||||
# nickname after the initial connection is complete
|
||||
forbid-anonymous-nick-changes: false
|
||||
|
||||
# multiclient controls whether Ergo allows multiple connections to
|
||||
# attach to the same client/nickname identity; this is part of the
|
||||
# functionality traditionally provided by a bouncer like ZNC
|
||||
multiclient:
|
||||
# when disabled, each connection must use a separate nickname (as is the
|
||||
# typical behavior of IRC servers). when enabled, a new connection that
|
||||
# has authenticated with SASL can associate itself with an existing
|
||||
# client
|
||||
enabled: true
|
||||
|
||||
# if this is disabled, clients have to opt in to bouncer functionality
|
||||
# using nickserv or the cap system. if it's enabled, they can opt out
|
||||
# via nickserv
|
||||
allowed-by-default: false
|
||||
|
||||
# whether to allow clients that remain on the server even
|
||||
# when they have no active connections. The possible values are:
|
||||
# "disabled", "opt-in", "opt-out", or "mandatory".
|
||||
always-on: "disabled"
|
||||
|
||||
# whether to mark always-on clients away when they have no active connections:
|
||||
auto-away: "opt-in"
|
||||
|
||||
# QUIT always-on clients from the server if they go this long without connecting
|
||||
# (use 0 or omit for no expiration):
|
||||
#always-on-expiration: 90d
|
||||
|
||||
# vhosts controls the assignment of vhosts (strings displayed in place of the user's
|
||||
# hostname/IP) by the HostServ service
|
||||
vhosts:
|
||||
# are vhosts enabled at all?
|
||||
enabled: true
|
||||
|
||||
# maximum length of a vhost
|
||||
max-length: 64
|
||||
|
||||
# regexp for testing the validity of a vhost
|
||||
# (make sure any changes you make here are RFC-compliant)
|
||||
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
|
||||
|
||||
# modes that are set by default when a user connects
|
||||
# if unset, no user modes will be set by default
|
||||
# +i is invisible (a user's channels are hidden from whois replies)
|
||||
# see /QUOTE HELP umodes for more user modes
|
||||
# default-user-modes: +i
|
||||
|
||||
# pluggable authentication mechanism, via subprocess invocation
|
||||
# see the manual for details on how to write an authentication plugin script
|
||||
auth-script:
|
||||
enabled: false
|
||||
command: "/usr/local/bin/authenticate-irc-user"
|
||||
# constant list of args to pass to the command; the actual authentication
|
||||
# data is transmitted over stdin/stdout:
|
||||
args: []
|
||||
# should we automatically create users if the plugin returns success?
|
||||
autocreate: true
|
||||
# timeout for process execution, after which we send a SIGTERM:
|
||||
timeout: 9s
|
||||
# how long after the SIGTERM before we follow up with a SIGKILL:
|
||||
kill-timeout: 1s
|
||||
# how many scripts are allowed to run at once? 0 for no limit:
|
||||
max-concurrency: 64
|
||||
|
||||
# channel options
|
||||
channels:
|
||||
# modes that are set when new channels are created
|
||||
# +n is no-external-messages and +t is op-only-topic
|
||||
# see /QUOTE HELP cmodes for more channel modes
|
||||
default-modes: +nt
|
||||
|
||||
# how many channels can a client be in at once?
|
||||
max-channels-per-client: 100
|
||||
|
||||
# if this is true, new channels can only be created by operators with the
|
||||
# `chanreg` operator capability
|
||||
operator-only-creation: false
|
||||
|
||||
# channel registration - requires an account
|
||||
registration:
|
||||
# can users register new channels?
|
||||
enabled: true
|
||||
|
||||
# restrict new channel registrations to operators only?
|
||||
# (operators can then transfer channels to regular users using /CS TRANSFER)
|
||||
operator-only: false
|
||||
|
||||
# how many channels can each account register?
|
||||
max-channels-per-account: 15
|
||||
|
||||
# as a crude countermeasure against spambots, anonymous connections younger
|
||||
# than this value will get an empty response to /LIST (a time period of 0 disables)
|
||||
list-delay: 0s
|
||||
|
||||
# INVITE to an invite-only channel expires after this amount of time
|
||||
# (0 or omit for no expiration):
|
||||
invite-expiration: 24h
|
||||
|
||||
# operator classes:
|
||||
# an operator has a single "class" (defining a privilege level), which can include
|
||||
# multiple "capabilities" (defining privileged actions they can take). all
|
||||
# currently available operator capabilities are associated with either the
|
||||
# 'chat-moderator' class (less privileged) or the 'server-admin' class (full
|
||||
# privileges) below: you can mix and match to create new classes.
|
||||
oper-classes:
|
||||
# chat moderator: can ban/unban users from the server, join channels,
|
||||
# fix mode issues and sort out vhosts.
|
||||
"chat-moderator":
|
||||
# title shown in WHOIS
|
||||
title: Chat Moderator
|
||||
|
||||
# capability names
|
||||
capabilities:
|
||||
- "kill" # disconnect user sessions
|
||||
- "ban" # ban IPs, CIDRs, NUH masks, and suspend accounts (UBAN / DLINE / KLINE)
|
||||
- "nofakelag" # exempted from "fakelag" restrictions on rate of message sending
|
||||
- "relaymsg" # use RELAYMSG in any channel (see the `relaymsg` config block)
|
||||
- "vhosts" # add and remove vhosts from users
|
||||
- "sajoin" # join arbitrary channels, including private channels
|
||||
- "samode" # modify arbitrary channel and user modes
|
||||
- "snomasks" # subscribe to arbitrary server notice masks
|
||||
- "roleplay" # use the (deprecated) roleplay commands in any channel
|
||||
|
||||
# server admin: has full control of the ircd, including nickname and
|
||||
# channel registrations
|
||||
"server-admin":
|
||||
# title shown in WHOIS
|
||||
title: Server Admin
|
||||
|
||||
# oper class this extends from
|
||||
extends: "chat-moderator"
|
||||
|
||||
# capability names
|
||||
capabilities:
|
||||
- "rehash" # rehash the server, i.e. reload the config at runtime
|
||||
- "accreg" # modify arbitrary account registrations
|
||||
- "chanreg" # modify arbitrary channel registrations
|
||||
- "history" # modify or delete history messages
|
||||
- "defcon" # use the DEFCON command (restrict server capabilities)
|
||||
- "massmessage" # message all users on the server
|
||||
|
||||
# ircd operators
|
||||
opers:
|
||||
# default operator named 'admin'; log in with /OPER admin <password>
|
||||
admin:
|
||||
# which capabilities this oper has access to
|
||||
class: "server-admin"
|
||||
|
||||
# traditionally, operator status is visible to unprivileged users in
|
||||
# WHO and WHOIS responses. this can be disabled with 'hidden'.
|
||||
hidden: false
|
||||
|
||||
# custom whois line (if `hidden` is enabled, visible only to other operators)
|
||||
whois-line: is the server administrator
|
||||
|
||||
# custom hostname (ignored if `hidden` is enabled)
|
||||
vhost: "staff"
|
||||
|
||||
# modes are modes to auto-set upon opering-up. uncomment this to automatically
|
||||
# enable snomasks ("server notification masks" that alert you to server events;
|
||||
# see `/quote help snomasks` while opered-up for more information):
|
||||
#modes: +is acdjknoqtuxv
|
||||
|
||||
# operators can be authenticated either by password (with the /OPER command),
|
||||
# or by certificate fingerprint, or both. if a password hash is set, then a
|
||||
# password is required to oper up (e.g., /OPER dan mypassword). to generate
|
||||
# the hash, use `ergo genpasswd`.
|
||||
password: "$2a$04$am7Whn1NKJfKHnBXefLlpu0kiX7a.smAyO5Irai/Pf4NcO0ddbUfa"
|
||||
|
||||
# if a SHA-256 certificate fingerprint is configured here, then it will be
|
||||
# required to /OPER. if you comment out the password hash above, then you can
|
||||
# /OPER without a password.
|
||||
#certfp: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
# if 'auto' is set (and no password hash is set), operator permissions will be
|
||||
# granted automatically as soon as you connect with the right fingerprint.
|
||||
#auto: true
|
||||
|
||||
# example of a moderator named 'alice'
|
||||
# (log in with /OPER alice <password>):
|
||||
#alice:
|
||||
# class: "chat-moderator"
|
||||
# whois-line: "can help with moderation issues!"
|
||||
# password: "$2a$04$0123456789abcdef0123456789abcdef0123456789abcdef01234"
|
||||
|
||||
# logging, takes inspiration from Insp
|
||||
logging:
|
||||
-
|
||||
# how to log these messages
|
||||
#
|
||||
# file log to a file
|
||||
# stdout log to stdout
|
||||
# stderr log to stderr
|
||||
# (you can specify multiple methods, e.g., to log to both stderr and a file)
|
||||
method: stderr
|
||||
|
||||
# filename to log to, if file method is selected
|
||||
# filename: ircd.log
|
||||
|
||||
# type(s) of logs to keep here. you can use - to exclude those types
|
||||
#
|
||||
# exclusions take precedent over inclusions, so if you exclude a type it will NEVER
|
||||
# be logged, even if you explicitly include it
|
||||
#
|
||||
# useful types include:
|
||||
# * everything (usually used with exclusing some types below)
|
||||
# server server startup, rehash, and shutdown events
|
||||
# accounts account registration and authentication
|
||||
# channels channel creation and operations
|
||||
# opers oper actions, authentication, etc
|
||||
# services actions related to NickServ, ChanServ, etc.
|
||||
# internal unexpected runtime behavior, including potential bugs
|
||||
# userinput raw lines sent by users
|
||||
# useroutput raw lines sent to users
|
||||
type: "* -userinput -useroutput"
|
||||
|
||||
# one of: debug info warn error
|
||||
level: info
|
||||
#-
|
||||
# # example of a file log that avoids logging IP addresses
|
||||
# method: file
|
||||
# filename: ircd.log
|
||||
# type: "* -userinput -useroutput -connect-ip"
|
||||
# level: debug
|
||||
|
||||
# debug options
|
||||
debug:
|
||||
# when enabled, Ergo will attempt to recover from certain kinds of
|
||||
# client-triggered runtime errors that would normally crash the server.
|
||||
# this makes the server more resilient to DoS, but could result in incorrect
|
||||
# behavior. deployments that would prefer to "start from scratch", e.g., by
|
||||
# letting the process crash and auto-restarting it with systemd, can set
|
||||
# this to false.
|
||||
recover-from-errors: true
|
||||
|
||||
# optionally expose a pprof http endpoint: https://golang.org/pkg/net/http/pprof/
|
||||
# it is strongly recommended that you don't expose this on a public interface;
|
||||
# if you need to access it remotely, you can use an SSH tunnel.
|
||||
# set to `null`, "", leave blank, or omit to disable
|
||||
# pprof-listener: "localhost:6060"
|
||||
|
||||
# lock file preventing multiple instances of Ergo from accidentally being
|
||||
# started at once. comment out or set to the empty string ("") to disable.
|
||||
# this path is relative to the working directory; if your datastore.path
|
||||
# is absolute, you should use an absolute path here as well.
|
||||
lock-file: "ircd.lock"
|
||||
|
||||
# datastore configuration
|
||||
datastore:
|
||||
# path to the datastore
|
||||
path: ircd.db
|
||||
|
||||
# if the database schema requires an upgrade, `autoupgrade` will attempt to
|
||||
# perform it automatically on startup. the database will be backed
|
||||
# up, and if the upgrade fails, the original database will be restored.
|
||||
autoupgrade: true
|
||||
|
||||
# connection information for MySQL (currently only used for persistent history):
|
||||
mysql:
|
||||
enabled: false
|
||||
host: "localhost"
|
||||
port: 3306
|
||||
# if socket-path is set, it will be used instead of host:port
|
||||
#socket-path: "/var/run/mysqld/mysqld.sock"
|
||||
user: "ergo"
|
||||
password: "hunter2"
|
||||
history-database: "ergo_history"
|
||||
timeout: 3s
|
||||
max-conns: 4
|
||||
# this may be necessary to prevent middleware from closing your connections:
|
||||
#conn-max-lifetime: 180s
|
||||
|
||||
# languages config
|
||||
languages:
|
||||
# whether to load languages
|
||||
enabled: false
|
||||
|
||||
# default language to use for new clients
|
||||
# 'en' is the default English language in the code
|
||||
default: en
|
||||
|
||||
# which directory contains our language files
|
||||
path: languages
|
||||
|
||||
# limits - these need to be the same across the network
|
||||
limits:
|
||||
# nicklen is the max nick length allowed
|
||||
nicklen: 32
|
||||
|
||||
# identlen is the max ident length allowed
|
||||
identlen: 20
|
||||
|
||||
# channellen is the max channel length allowed
|
||||
channellen: 64
|
||||
|
||||
# awaylen is the maximum length of an away message
|
||||
awaylen: 390
|
||||
|
||||
# kicklen is the maximum length of a kick message
|
||||
kicklen: 390
|
||||
|
||||
# topiclen is the maximum length of a channel topic
|
||||
topiclen: 390
|
||||
|
||||
# maximum number of monitor entries a client can have
|
||||
monitor-entries: 100
|
||||
|
||||
# whowas entries to store
|
||||
whowas-entries: 100
|
||||
|
||||
# maximum length of channel lists (beI modes)
|
||||
chan-list-modes: 60
|
||||
|
||||
# maximum number of messages to accept during registration (prevents
|
||||
# DoS / resource exhaustion attacks):
|
||||
registration-messages: 1024
|
||||
|
||||
# message length limits for the new multiline cap
|
||||
multiline:
|
||||
max-bytes: 4096 # 0 means disabled
|
||||
max-lines: 100 # 0 means no limit
|
||||
|
||||
# fakelag: prevents clients from spamming commands too rapidly
|
||||
fakelag:
|
||||
# whether to enforce fakelag
|
||||
enabled: true
|
||||
|
||||
# time unit for counting command rates
|
||||
window: 1s
|
||||
|
||||
# clients can send this many commands without fakelag being imposed
|
||||
burst-limit: 5
|
||||
|
||||
# once clients have exceeded their burst allowance, they can send only
|
||||
# this many commands per `window`:
|
||||
messages-per-window: 2
|
||||
|
||||
# client status resets to the default state if they go this long without
|
||||
# sending any commands:
|
||||
cooldown: 2s
|
||||
|
||||
# the roleplay commands are semi-standardized extensions to IRC that allow
|
||||
# sending and receiving messages from pseudo-nicknames. this can be used either
|
||||
# for actual roleplaying, or for bridging IRC with other protocols.
|
||||
roleplay:
|
||||
# are roleplay commands enabled at all? (channels and clients still have to
|
||||
# opt in individually with the +E mode)
|
||||
enabled: false
|
||||
|
||||
# require the "roleplay" oper capability to send roleplay messages?
|
||||
require-oper: false
|
||||
|
||||
# require channel operator permissions to send roleplay messages?
|
||||
require-chanops: false
|
||||
|
||||
# add the real nickname, in parentheses, to the end of every roleplay message?
|
||||
add-suffix: true
|
||||
|
||||
# external services can integrate with the ircd using JSON Web Tokens (https://jwt.io).
|
||||
# in effect, the server can sign a token attesting that the client is present on
|
||||
# the server, is a member of a particular channel, etc.
|
||||
extjwt:
|
||||
# # default service config (for `EXTJWT #channel`).
|
||||
# # expiration time for the token:
|
||||
# expiration: 45s
|
||||
# # you can configure tokens to be signed either with HMAC and a symmetric secret:
|
||||
# secret: "65PHvk0K1_sM-raTsCEhatVkER_QD8a0zVV8gG2EWcI"
|
||||
# # or with an RSA private key:
|
||||
# #rsa-private-key-file: "extjwt.pem"
|
||||
|
||||
# # named services (for `EXTJWT #channel service_name`):
|
||||
# services:
|
||||
# "jitsi":
|
||||
# expiration: 30s
|
||||
# secret: "qmamLKDuOzIzlO8XqsGGewei_At11lewh6jtKfSTbkg"
|
||||
|
||||
# history message storage: this is used by CHATHISTORY, HISTORY, znc.in/playback,
|
||||
# various autoreplay features, and the resume extension
|
||||
history:
|
||||
# should we store messages for later playback?
|
||||
# by default, messages are stored in RAM only; they do not persist
|
||||
# across server restarts. however, you may want to understand how message
|
||||
# history interacts with the GDPR and/or any data privacy laws that apply
|
||||
# in your country and the countries of your users.
|
||||
enabled: true
|
||||
|
||||
# how many channel-specific events (messages, joins, parts) should be tracked per channel?
|
||||
channel-length: 2048
|
||||
|
||||
# how many direct messages and notices should be tracked per user?
|
||||
client-length: 256
|
||||
|
||||
# how long should we try to preserve messages?
|
||||
# if `autoresize-window` is 0, the in-memory message buffers are preallocated to
|
||||
# their maximum length. if it is nonzero, the buffers are initially small and
|
||||
# are dynamically expanded up to the maximum length. if the buffer is full
|
||||
# and the oldest message is older than `autoresize-window`, then it will overwrite
|
||||
# the oldest message rather than resize; otherwise, it will expand if possible.
|
||||
autoresize-window: 3d
|
||||
|
||||
# number of messages to automatically play back on channel join (0 to disable):
|
||||
autoreplay-on-join: 0
|
||||
|
||||
# maximum number of CHATHISTORY messages that can be
|
||||
# requested at once (0 disables support for CHATHISTORY)
|
||||
chathistory-maxmessages: 1000
|
||||
|
||||
# maximum number of messages that can be replayed at once during znc emulation
|
||||
# (znc.in/playback, or automatic replay on initial reattach to a persistent client):
|
||||
znc-maxmessages: 2048
|
||||
|
||||
# options to delete old messages, or prevent them from being retrieved
|
||||
restrictions:
|
||||
# if this is set, messages older than this cannot be retrieved by anyone
|
||||
# (and will eventually be deleted from persistent storage, if that's enabled)
|
||||
expire-time: 1w
|
||||
|
||||
# this restricts access to channel history (it can be overridden by channel
|
||||
# owners). options are: 'none' (no restrictions), 'registration-time'
|
||||
# (logged-in users cannot retrieve messages older than their account
|
||||
# registration date, and anonymous users cannot retrieve messages older than
|
||||
# their sign-on time, modulo the grace-period described below), and
|
||||
# 'join-time' (users cannot retrieve messages older than the time they
|
||||
# joined the channel, so only always-on clients can view history).
|
||||
query-cutoff: 'none'
|
||||
|
||||
# if query-cutoff is set to 'registration-time', this allows retrieval
|
||||
# of messages that are up to 'grace-period' older than the above cutoff.
|
||||
# if you use 'registration-time', this is recommended to allow logged-out
|
||||
# users to query history after disconnections.
|
||||
grace-period: 1h
|
||||
|
||||
# options to store history messages in a persistent database (currently only MySQL).
|
||||
# in order to enable any of this functionality, you must configure a MySQL server
|
||||
# in the `datastore.mysql` section.
|
||||
persistent:
|
||||
enabled: false
|
||||
|
||||
# store unregistered channel messages in the persistent database?
|
||||
unregistered-channels: false
|
||||
|
||||
# for a registered channel, the channel owner can potentially customize
|
||||
# the history storage setting. as the server operator, your options are
|
||||
# 'disabled' (no persistent storage, regardless of per-channel setting),
|
||||
# 'opt-in', 'opt-out', and 'mandatory' (force persistent storage, ignoring
|
||||
# per-channel setting):
|
||||
registered-channels: "opt-out"
|
||||
|
||||
# direct messages are only stored in the database for logged-in clients;
|
||||
# you can control how they are stored here (same options as above).
|
||||
# if you enable this, strict nickname reservation is strongly recommended
|
||||
# as well.
|
||||
direct-messages: "opt-out"
|
||||
|
||||
# options to control how messages are stored and deleted:
|
||||
retention:
|
||||
# allow users to delete their own messages from history?
|
||||
allow-individual-delete: false
|
||||
|
||||
# if persistent history is enabled, create additional index tables,
|
||||
# allowing deletion of JSON export of an account's messages. this
|
||||
# may be needed for compliance with data privacy regulations.
|
||||
enable-account-indexing: false
|
||||
|
||||
# options to control storage of TAGMSG
|
||||
tagmsg-storage:
|
||||
# by default, should TAGMSG be stored?
|
||||
default: false
|
||||
|
||||
# if `default` is false, store TAGMSG containing any of these tags:
|
||||
whitelist:
|
||||
- "+draft/react"
|
||||
- "+react"
|
||||
|
||||
# if `default` is true, don't store TAGMSG containing any of these tags:
|
||||
#blacklist:
|
||||
# - "+draft/typing"
|
||||
# - "typing"
|
||||
|
||||
# whether to allow customization of the config at runtime using environment variables,
|
||||
# e.g., ERGO__SERVER__MAX_SENDQ=128k. see the manual for more details.
|
||||
allow-environment-overrides: true
|
|
@ -315,5 +315,15 @@ export const EventOptions: SelectOption[] = [
|
|||
label: "Push Error",
|
||||
value: "PUSH_ERROR",
|
||||
description: "On push error for the arrs or download client"
|
||||
},
|
||||
{
|
||||
label: "IRC Disconnected",
|
||||
value: "IRC_DISCONNECTED",
|
||||
description: "Unexpectedly disconnected from irc network"
|
||||
},
|
||||
{
|
||||
label: "IRC Reconnected",
|
||||
value: "IRC_RECONNECTED",
|
||||
description: "Reconnected to irc network after error"
|
||||
}
|
||||
];
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { useToggle } from "../../hooks/hooks";
|
||||
import { APIClient } from "../../api/APIClient";
|
||||
import { EmptySimple } from "../../components/emptystates";
|
||||
import {ExclamationCircleIcon} from "@heroicons/react/outline";
|
||||
|
||||
export const IrcSettings = () => {
|
||||
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
|
||||
|
@ -88,12 +89,12 @@ const ListItem = ({ idx, network }: ListItemProps) => {
|
|||
<span className="relative inline-flex items-center">
|
||||
{
|
||||
network.enabled ? (
|
||||
network.connected ? (
|
||||
networkHealthy(network) ? (
|
||||
<span className="mr-3 flex h-3 w-3 relative" title={`Connected since: ${simplifyDate(network.connected_since)}`}>
|
||||
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75"/>
|
||||
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500"/>
|
||||
</span>
|
||||
) : <span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-red-400" />
|
||||
) : <span className="mr-3 flex items-center" title={network.connection_errors.toString()}><ExclamationCircleIcon className="h-4 w-4 text-red-400 hover:text-red-600" /></span>
|
||||
) : <span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-gray-500" />
|
||||
}
|
||||
{network.name}
|
||||
|
@ -155,3 +156,11 @@ const ListItem = ({ idx, network }: ListItemProps) => {
|
|||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
function networkHealthy(network: IrcNetworkWithHealth): boolean {
|
||||
if (network.connection_errors.length > 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
1
web/src/types/Irc.d.ts
vendored
1
web/src/types/Irc.d.ts
vendored
|
@ -53,6 +53,7 @@ interface IrcNetworkWithHealth {
|
|||
channels: IrcChannelWithHealth[];
|
||||
connected: boolean;
|
||||
connected_since: string;
|
||||
connection_errors: string[];
|
||||
}
|
||||
|
||||
interface NickServ {
|
||||
|
|
3
web/src/types/Notification.d.ts
vendored
3
web/src/types/Notification.d.ts
vendored
|
@ -1,11 +1,12 @@
|
|||
type NotificationType = "DISCORD" | "TELEGRAM";
|
||||
type NotificationEvent = "PUSH_APPROVED" | "PUSH_REJECTED" | "PUSH_ERROR" | "IRC_DISCONNECTED" | "IRC_RECONNECTED";
|
||||
|
||||
interface Notification {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
type: NotificationType;
|
||||
events: string[];
|
||||
events: NotificationEvent[];
|
||||
webhook?: string;
|
||||
token?: string;
|
||||
channel?: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue