ScuttleBot

feat: config architecture — Ergo hot-reload, expose missing settings (#128, #127) #128 — Ergo config flow-through: add UpdateConfig() to ergo Manager. cfgStore.OnChange now regenerates ircd.yaml and rehashes Ergo when config changes via API. Settings like require_sasl, default_channel_modes, and history now take effect without restart. #127 — Config completeness: expose require_sasl, default_channel_modes, and history.enabled in the web UI Ergo config card. All three hot-reload via the new flow-through pipeline.

lmata 2026-04-05 15:35 trunk
Commit 4ab8ed69eaac9639280e3dc6bd14ee3f7b1350ed96a1a024aff5b1211c31e7b3
--- cmd/scuttlebot/main.go
+++ cmd/scuttlebot/main.go
@@ -337,10 +337,18 @@
337337
}
338338
}
339339
// Hot-reload bridge web TTL.
340340
if bridgeBot != nil {
341341
bridgeBot.SetWebUserTTL(time.Duration(updated.Bridge.WebUserTTLMinutes) * time.Minute)
342
+ }
343
+ // Regenerate ircd.yaml and rehash Ergo on config changes.
344
+ if ergoMgr != nil {
345
+ if err := ergoMgr.UpdateConfig(updated.Ergo); err != nil {
346
+ log.Error("ergo config hot-reload failed", "err", err)
347
+ } else {
348
+ log.Info("ergo config reloaded")
349
+ }
342350
}
343351
})
344352
345353
// Start HTTP REST API server.
346354
var llmCfg *config.LLMConfig
347355
--- cmd/scuttlebot/main.go
+++ cmd/scuttlebot/main.go
@@ -337,10 +337,18 @@
337 }
338 }
339 // Hot-reload bridge web TTL.
340 if bridgeBot != nil {
341 bridgeBot.SetWebUserTTL(time.Duration(updated.Bridge.WebUserTTLMinutes) * time.Minute)
 
 
 
 
 
 
 
 
342 }
343 })
344
345 // Start HTTP REST API server.
346 var llmCfg *config.LLMConfig
347
--- cmd/scuttlebot/main.go
+++ cmd/scuttlebot/main.go
@@ -337,10 +337,18 @@
337 }
338 }
339 // Hot-reload bridge web TTL.
340 if bridgeBot != nil {
341 bridgeBot.SetWebUserTTL(time.Duration(updated.Bridge.WebUserTTLMinutes) * time.Minute)
342 }
343 // Regenerate ircd.yaml and rehash Ergo on config changes.
344 if ergoMgr != nil {
345 if err := ergoMgr.UpdateConfig(updated.Ergo); err != nil {
346 log.Error("ergo config hot-reload failed", "err", err)
347 } else {
348 log.Info("ergo config reloaded")
349 }
350 }
351 })
352
353 // Start HTTP REST API server.
354 var llmCfg *config.LLMConfig
355
--- internal/api/ui/index.html
+++ internal/api/ui/index.html
@@ -798,10 +798,31 @@
798798
</div>
799799
<div class="setting-row">
800800
<div class="setting-label">IRC address</div>
801801
<div class="setting-desc">Address Ergo listens on for IRC connections. Requires restart.</div>
802802
<input type="text" id="ergo-irc-addr" placeholder="127.0.0.1:6667" style="width:180px;padding:4px 8px;font-size:12px">
803
+ </div>
804
+ <div class="setting-row">
805
+ <div class="setting-label">require SASL</div>
806
+ <div class="setting-desc">Enforce SASL authentication for all IRC connections. Only registered accounts can connect. Hot-reloads.</div>
807
+ <label style="display:flex;align-items:center;gap:6px;cursor:pointer">
808
+ <input type="checkbox" id="ergo-require-sasl">
809
+ <span style="font-size:12px">enforce SASL</span>
810
+ </label>
811
+ </div>
812
+ <div class="setting-row">
813
+ <div class="setting-label">default channel modes</div>
814
+ <div class="setting-desc">Modes applied to new channels (e.g. "+n", "+Rn"). Hot-reloads.</div>
815
+ <input type="text" id="ergo-default-modes" placeholder="+n" style="width:120px;padding:4px 8px;font-size:12px">
816
+ </div>
817
+ <div class="setting-row">
818
+ <div class="setting-label">message history</div>
819
+ <div class="setting-desc">Enable persistent message history (CHATHISTORY). Hot-reloads.</div>
820
+ <label style="display:flex;align-items:center;gap:6px;cursor:pointer">
821
+ <input type="checkbox" id="ergo-history-enabled">
822
+ <span style="font-size:12px">enabled</span>
823
+ </label>
803824
</div>
804825
<div class="setting-row">
805826
<div class="setting-label">external mode</div>
806827
<div class="setting-desc">Disable subprocess management — scuttlebot expects Ergo to already be running. Requires restart.</div>
807828
<label style="display:flex;align-items:center;gap:6px;cursor:pointer">
@@ -3222,14 +3243,17 @@
32223243
// general
32233244
document.getElementById('general-api-addr').value = cfg.api_addr || '';
32243245
document.getElementById('general-mcp-addr').value = cfg.mcp_addr || '';
32253246
// ergo
32263247
const e = cfg.ergo || {};
3227
- document.getElementById('ergo-network-name').value = e.network_name || '';
3228
- document.getElementById('ergo-server-name').value = e.server_name || '';
3229
- document.getElementById('ergo-irc-addr').value = e.irc_addr || '';
3230
- document.getElementById('ergo-external').checked = !!e.external;
3248
+ document.getElementById('ergo-network-name').value = e.network_name || '';
3249
+ document.getElementById('ergo-server-name').value = e.server_name || '';
3250
+ document.getElementById('ergo-irc-addr').value = e.irc_addr || '';
3251
+ document.getElementById('ergo-require-sasl').checked = !!e.require_sasl;
3252
+ document.getElementById('ergo-default-modes').value = e.default_channel_modes || '';
3253
+ document.getElementById('ergo-history-enabled').checked = !!(e.history && e.history.enabled);
3254
+ document.getElementById('ergo-external').checked = !!e.external;
32313255
// tls
32323256
const t = cfg.tls || {};
32333257
document.getElementById('tls-domain').value = t.domain || '';
32343258
document.getElementById('tls-email').value = t.email || '';
32353259
document.getElementById('tls-allow-insecure').checked = !!t.allow_insecure;
@@ -3302,14 +3326,17 @@
33023326
}
33033327
33043328
function saveErgoConfig() {
33053329
saveConfigPatch({
33063330
ergo: {
3307
- network_name: document.getElementById('ergo-network-name').value.trim() || undefined,
3308
- server_name: document.getElementById('ergo-server-name').value.trim() || undefined,
3309
- irc_addr: document.getElementById('ergo-irc-addr').value.trim() || undefined,
3310
- external: document.getElementById('ergo-external').checked,
3331
+ network_name: document.getElementById('ergo-network-name').value.trim() || undefined,
3332
+ server_name: document.getElementById('ergo-server-name').value.trim() || undefined,
3333
+ irc_addr: document.getElementById('ergo-irc-addr').value.trim() || undefined,
3334
+ require_sasl: document.getElementById('ergo-require-sasl').checked,
3335
+ default_channel_modes: document.getElementById('ergo-default-modes').value.trim() || undefined,
3336
+ history: { enabled: document.getElementById('ergo-history-enabled').checked },
3337
+ external: document.getElementById('ergo-external').checked,
33113338
}
33123339
}, 'ergo-save-result');
33133340
}
33143341
33153342
function saveTLSConfig() {
33163343
--- internal/api/ui/index.html
+++ internal/api/ui/index.html
@@ -798,10 +798,31 @@
798 </div>
799 <div class="setting-row">
800 <div class="setting-label">IRC address</div>
801 <div class="setting-desc">Address Ergo listens on for IRC connections. Requires restart.</div>
802 <input type="text" id="ergo-irc-addr" placeholder="127.0.0.1:6667" style="width:180px;padding:4px 8px;font-size:12px">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
803 </div>
804 <div class="setting-row">
805 <div class="setting-label">external mode</div>
806 <div class="setting-desc">Disable subprocess management — scuttlebot expects Ergo to already be running. Requires restart.</div>
807 <label style="display:flex;align-items:center;gap:6px;cursor:pointer">
@@ -3222,14 +3243,17 @@
3222 // general
3223 document.getElementById('general-api-addr').value = cfg.api_addr || '';
3224 document.getElementById('general-mcp-addr').value = cfg.mcp_addr || '';
3225 // ergo
3226 const e = cfg.ergo || {};
3227 document.getElementById('ergo-network-name').value = e.network_name || '';
3228 document.getElementById('ergo-server-name').value = e.server_name || '';
3229 document.getElementById('ergo-irc-addr').value = e.irc_addr || '';
3230 document.getElementById('ergo-external').checked = !!e.external;
 
 
 
3231 // tls
3232 const t = cfg.tls || {};
3233 document.getElementById('tls-domain').value = t.domain || '';
3234 document.getElementById('tls-email').value = t.email || '';
3235 document.getElementById('tls-allow-insecure').checked = !!t.allow_insecure;
@@ -3302,14 +3326,17 @@
3302 }
3303
3304 function saveErgoConfig() {
3305 saveConfigPatch({
3306 ergo: {
3307 network_name: document.getElementById('ergo-network-name').value.trim() || undefined,
3308 server_name: document.getElementById('ergo-server-name').value.trim() || undefined,
3309 irc_addr: document.getElementById('ergo-irc-addr').value.trim() || undefined,
3310 external: document.getElementById('ergo-external').checked,
 
 
 
3311 }
3312 }, 'ergo-save-result');
3313 }
3314
3315 function saveTLSConfig() {
3316
--- internal/api/ui/index.html
+++ internal/api/ui/index.html
@@ -798,10 +798,31 @@
798 </div>
799 <div class="setting-row">
800 <div class="setting-label">IRC address</div>
801 <div class="setting-desc">Address Ergo listens on for IRC connections. Requires restart.</div>
802 <input type="text" id="ergo-irc-addr" placeholder="127.0.0.1:6667" style="width:180px;padding:4px 8px;font-size:12px">
803 </div>
804 <div class="setting-row">
805 <div class="setting-label">require SASL</div>
806 <div class="setting-desc">Enforce SASL authentication for all IRC connections. Only registered accounts can connect. Hot-reloads.</div>
807 <label style="display:flex;align-items:center;gap:6px;cursor:pointer">
808 <input type="checkbox" id="ergo-require-sasl">
809 <span style="font-size:12px">enforce SASL</span>
810 </label>
811 </div>
812 <div class="setting-row">
813 <div class="setting-label">default channel modes</div>
814 <div class="setting-desc">Modes applied to new channels (e.g. "+n", "+Rn"). Hot-reloads.</div>
815 <input type="text" id="ergo-default-modes" placeholder="+n" style="width:120px;padding:4px 8px;font-size:12px">
816 </div>
817 <div class="setting-row">
818 <div class="setting-label">message history</div>
819 <div class="setting-desc">Enable persistent message history (CHATHISTORY). Hot-reloads.</div>
820 <label style="display:flex;align-items:center;gap:6px;cursor:pointer">
821 <input type="checkbox" id="ergo-history-enabled">
822 <span style="font-size:12px">enabled</span>
823 </label>
824 </div>
825 <div class="setting-row">
826 <div class="setting-label">external mode</div>
827 <div class="setting-desc">Disable subprocess management — scuttlebot expects Ergo to already be running. Requires restart.</div>
828 <label style="display:flex;align-items:center;gap:6px;cursor:pointer">
@@ -3222,14 +3243,17 @@
3243 // general
3244 document.getElementById('general-api-addr').value = cfg.api_addr || '';
3245 document.getElementById('general-mcp-addr').value = cfg.mcp_addr || '';
3246 // ergo
3247 const e = cfg.ergo || {};
3248 document.getElementById('ergo-network-name').value = e.network_name || '';
3249 document.getElementById('ergo-server-name').value = e.server_name || '';
3250 document.getElementById('ergo-irc-addr').value = e.irc_addr || '';
3251 document.getElementById('ergo-require-sasl').checked = !!e.require_sasl;
3252 document.getElementById('ergo-default-modes').value = e.default_channel_modes || '';
3253 document.getElementById('ergo-history-enabled').checked = !!(e.history && e.history.enabled);
3254 document.getElementById('ergo-external').checked = !!e.external;
3255 // tls
3256 const t = cfg.tls || {};
3257 document.getElementById('tls-domain').value = t.domain || '';
3258 document.getElementById('tls-email').value = t.email || '';
3259 document.getElementById('tls-allow-insecure').checked = !!t.allow_insecure;
@@ -3302,14 +3326,17 @@
3326 }
3327
3328 function saveErgoConfig() {
3329 saveConfigPatch({
3330 ergo: {
3331 network_name: document.getElementById('ergo-network-name').value.trim() || undefined,
3332 server_name: document.getElementById('ergo-server-name').value.trim() || undefined,
3333 irc_addr: document.getElementById('ergo-irc-addr').value.trim() || undefined,
3334 require_sasl: document.getElementById('ergo-require-sasl').checked,
3335 default_channel_modes: document.getElementById('ergo-default-modes').value.trim() || undefined,
3336 history: { enabled: document.getElementById('ergo-history-enabled').checked },
3337 external: document.getElementById('ergo-external').checked,
3338 }
3339 }, 'ergo-save-result');
3340 }
3341
3342 function saveTLSConfig() {
3343
--- internal/ergo/manager.go
+++ internal/ergo/manager.go
@@ -115,10 +115,17 @@
115115
}
116116
wait = min(wait*2, restartMaxWait) //nolint:ineffassign,staticcheck
117117
}
118118
}
119119
}
120
+
121
+// UpdateConfig replaces the Ergo config, regenerates ircd.yaml, and rehashes.
122
+// Use when scuttlebot.yaml Ergo settings change at runtime.
123
+func (m *Manager) UpdateConfig(cfg config.ErgoConfig) error {
124
+ m.cfg = cfg
125
+ return m.Rehash()
126
+}
120127
121128
// Rehash reloads the Ergo config. Call after writing a new ircd.yaml.
122129
func (m *Manager) Rehash() error {
123130
if err := m.writeConfig(); err != nil {
124131
return fmt.Errorf("ergo: write config: %w", err)
125132
--- internal/ergo/manager.go
+++ internal/ergo/manager.go
@@ -115,10 +115,17 @@
115 }
116 wait = min(wait*2, restartMaxWait) //nolint:ineffassign,staticcheck
117 }
118 }
119 }
 
 
 
 
 
 
 
120
121 // Rehash reloads the Ergo config. Call after writing a new ircd.yaml.
122 func (m *Manager) Rehash() error {
123 if err := m.writeConfig(); err != nil {
124 return fmt.Errorf("ergo: write config: %w", err)
125
--- internal/ergo/manager.go
+++ internal/ergo/manager.go
@@ -115,10 +115,17 @@
115 }
116 wait = min(wait*2, restartMaxWait) //nolint:ineffassign,staticcheck
117 }
118 }
119 }
120
121 // UpdateConfig replaces the Ergo config, regenerates ircd.yaml, and rehashes.
122 // Use when scuttlebot.yaml Ergo settings change at runtime.
123 func (m *Manager) UpdateConfig(cfg config.ErgoConfig) error {
124 m.cfg = cfg
125 return m.Rehash()
126 }
127
128 // Rehash reloads the Ergo config. Call after writing a new ircd.yaml.
129 func (m *Manager) Rehash() error {
130 if err := m.writeConfig(); err != nil {
131 return fmt.Errorf("ergo: write config: %w", err)
132

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button