@@ -9,10 +9,11 @@
9 9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
import (
10 10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"context"
11 11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"fmt"
12 12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"log/slog"
13 13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"strings"
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "sync"
14 15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"time"
15 16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
16 17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
"github.com/lrstanley/girc"
17 18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
)
18 19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
@@ -32,19 +33,28 @@
32 33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
Voice []string
33 34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
34 35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// Autojoin is a list of bot nicks to invite after provisioning.
35 36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
Autojoin []string
36 37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // channelRecord tracks a provisioned channel for TTL-based reaping.
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ type channelRecord struct {
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ name string
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ provisionedAt time.Time
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
37 44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
38 45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// Manager provisions and maintains IRC channel topology.
39 46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
type Manager struct {
40 47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
ircAddr string
41 48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
nick string
42 49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
password string
43 50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
log *slog.Logger
44 51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
policy *Policy
45 52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
client *girc.Client
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ mu sync.Mutex
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ channels map[string]channelRecord // channel name → record
46 56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
47 57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
48 58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// NewManager creates a topology Manager. nick and password are the Ergo
49 59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// credentials of the scuttlebot oper account used to manage channels.
50 60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// policy may be nil if the caller only uses the manager for ad-hoc provisioning.
@@ -53,10 +63,11 @@
53 63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
ircAddr: ircAddr,
54 64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
nick: nick,
55 65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
password: password,
56 66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
policy: policy,
57 67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
log: log,
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ channels: make(map[string]channelRecord),
58 69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
59 70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
60 71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
61 72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// Policy returns the policy attached to this manager, or nil.
62 73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func (m *Manager) Policy() *Policy { return m.policy }
@@ -207,13 +218,65 @@
207 218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
208 219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if len(ch.Autojoin) > 0 {
209 220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
m.Invite(ch.Name, ch.Autojoin)
210 221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
211 222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.mu.Lock()
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.channels[ch.Name] = channelRecord{name: ch.Name, provisionedAt: time.Now()}
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.mu.Unlock()
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
212 227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
m.log.Info("provisioned channel", "channel", ch.Name)
213 228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return nil
214 229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // DropChannel drops an IRC channel via ChanServ DROP and removes it from the
232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // channel registry. Use for ephemeral channels that have expired or been closed.
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (m *Manager) DropChannel(channel string) {
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.chanserv("DROP %s", channel)
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.mu.Lock()
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ delete(m.channels, channel)
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.mu.Unlock()
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.log.Info("dropped channel", "channel", channel)
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // StartReaper starts a background goroutine that drops ephemeral channels once
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // their TTL has elapsed. The reaper runs until ctx is cancelled.
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Policy must be set on the Manager for TTL rules to be evaluated.
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (m *Manager) StartReaper(ctx context.Context) {
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if m.policy == nil {
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ go func() {
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ticker := time.NewTicker(5 * time.Minute)
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ defer ticker.Stop()
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for {
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ select {
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case <-ctx.Done():
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case <-ticker.C:
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.reap()
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }()
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (m *Manager) reap() {
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ now := time.Now()
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.mu.Lock()
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ expired := make([]channelRecord, 0)
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for _, rec := range m.channels {
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ttl := m.policy.TTLFor(rec.name)
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if ttl > 0 && m.policy.IsEphemeral(rec.name) && now.Sub(rec.provisionedAt) > ttl {
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ expired = append(expired, rec)
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.mu.Unlock()
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for _, rec := range expired {
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.log.Info("reaping expired ephemeral channel", "channel", rec.name, "age", now.Sub(rec.provisionedAt).Round(time.Minute))
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m.DropChannel(rec.name)
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
215 278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
216 279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func (m *Manager) chanserv(format string, args ...any) {
217 280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
msg := fmt.Sprintf(format, args...)
218 281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
m.client.Cmd.Message("ChanServ", msg)
219 282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
220 283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!