Resilience — Limnoria plugin
Automatic IRC self-maintenance for Limnoria bots.
- Retries joining channels after ban, full, invite-only, or bad-key errors
- Sends a configurable command (e.g.
ChanServ UNBAN) before each retry - Removes own bans via
MODE -bwhen the bot retains ops - Rejoins after kicks, with a configurable attempt limit
- Recovers ops after being deopped (halfop self-op or a configurable command)
- Sends a command on every successful join (e.g.
ChanServ UPon Anope) - Runs a per-network perform list on connect (like ZNC
*perform) - Reclaims the configured nick when on a fallback nick, including a DALnet-style RECOVER/RELEASE sequence
Installation
cp -r Resilience/ /path/to/your/bot/plugins/
Then in your bot:
load Resilience
Configuration hierarchy
All channel-level settings support Limnoria's three-level inheritance:
global default
└─ per-network override
└─ per-channel override
Set a network-wide default:
config network libera supybot.plugins.Resilience.<setting> <value>
Override for a specific channel:
config channel #chan supybot.plugins.Resilience.<setting> <value>
Reset a per-channel override back to the network default:
config channel #chan supybot.plugins.Resilience.<setting>
Network-only settings (nick password, perform, nick recovery) use
config network only — they don't apply per-channel.
Command templates
Several settings accept a raw IRC command string with $-substitutions:
| Variable | Value |
|---|---|
$nick |
The bot's configured (desired) nick for this network |
$botnick |
Same as $nick |
$currentnick |
The nick the bot is actually using right now |
$network |
The network name |
$password |
The value of nickPassword for this network |
$channel |
The channel name (where applicable) |
The command must be a valid raw IRC string, including a colon before any trailing parameter:
PRIVMSG ChanServ :UNBAN $channel
PRIVMSG ChanServ :OP $channel $botnick
MODE $nick +ix
For the perform and nickRecoverCommands settings, multiple commands are
separated by commas. To include a literal comma inside a single command,
escape it as \,.
Settings reference
Join retry
These are channel-level settings.
retryJoin
Type: Boolean
Default: True
Scope: channel
Master switch for all join-retry behaviour. When False, the bot gives up
immediately on any join error and ignores all other retry settings for that
channel.
config network libera supybot.plugins.Resilience.retryJoin True
config channel #readonly supybot.plugins.Resilience.retryJoin False
retryJoinDelay
Type: positive integer (seconds)
Default: 60
Scope: channel
How long to wait between join retry attempts. Applies to all join error types (ban, full, invite-only, bad key).
config network libera supybot.plugins.Resilience.retryJoinDelay 60
retryJoinMax
Type: non-negative integer
Default: 10
Scope: channel
Maximum number of join attempts before giving up entirely. The counter resets to zero on a successful join, so a temporary ban that later gets lifted will start fresh next time.
Set to 0 for unlimited retries (the bot will keep trying forever — use with
care, as this can get the bot K-Lined on some networks).
config network libera supybot.plugins.Resilience.retryJoinMax 10
config channel #important supybot.plugins.Resilience.retryJoinMax 0
onBanCommand
Type: string (command template)
Default: (empty)
Scope: channel
Raw IRC command sent before each retry attempt when the bot is banned from a channel (numeric 474). Empty by default — set it to opt in.
The command is sent on every cycle, so if services successfully lift the ban the next retry join will succeed.
# Anope / Atheme ChanServ:
config network libera supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
# IRCop bot or custom service:
config network mynet supybot.plugins.Resilience.onBanCommand PRIVMSG OpBot :unban $channel $botnick
onFullCommand
Type: string (command template)
Default: (empty)
Scope: channel
Raw IRC command sent before each retry when the channel is full (numeric 471). Usually left empty — there is little you can do except wait for a slot to open. Could be used to ask an operator bot to raise the limit.
config network mynet supybot.plugins.Resilience.onFullCommand PRIVMSG OpBot :limit $channel
onInviteOnlyCommand
Type: string (command template)
Default: (empty)
Scope: channel
Raw IRC command sent before each retry when the channel is invite-only (numeric 473).
config network libera supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
onBadKeyCommand
Type: string (command template)
Default: (empty)
Scope: channel
Raw IRC command sent before each retry when the channel key is wrong (numeric 475). Usually left empty unless you have a way to retrieve the current key from a service.
selfUnban
Type: Boolean
Default: True
Scope: channel
When True, if the bot has +o in the channel at the time it is banned, it
will issue MODE #channel -b <mask> to remove its own matching ban entries
before retrying the join.
This is a fallback for the unusual case where someone sets a ban on the bot
without kicking it first, leaving it opped. It works alongside
onBanCommand — both can be active at the same time.
config channel #chan supybot.plugins.Resilience.selfUnban True
Kick rejoin
These are channel-level settings.
rejoinOnKick
Type: Boolean
Default: True
Scope: channel
Whether the bot automatically rejoins after being kicked.
config channel #staff supybot.plugins.Resilience.rejoinOnKick False
rejoinKickDelay
Type: positive integer (seconds)
Default: 5
Scope: channel
Seconds to wait before rejoining after a kick. A small delay looks less aggressive and is required by the rules of some channels.
config network libera supybot.plugins.Resilience.rejoinKickDelay 5
rejoinKickMax
Type: non-negative integer
Default: 3
Scope: channel
Maximum number of rejoin attempts after kicks before the bot stops trying. Repeated kicks from the same channel usually mean someone actively wants the bot out, not a mistake. The counter resets to zero on a successful join.
Set to 0 for unlimited.
config network libera supybot.plugins.Resilience.rejoinKickMax 3
Op recovery
These are channel-level settings.
autoReop
Type: Boolean
Default: True
Scope: channel
When True, the bot tries to recover ops after being deopped. It first checks
whether it has +h (halfop) and uses that to self-op with no external command.
If it doesn't have halfop, it falls back to onDeopCommand if that is set.
config channel #chan supybot.plugins.Resilience.autoReop True
autoReopDelay
Type: non-negative integer (seconds)
Default: 3
Scope: channel
Seconds to wait after being deopped before attempting to recover ops. A brief
pause avoids triggering a mode war if the deop was intentional. Set to 0 to
react immediately.
config network libera supybot.plugins.Resilience.autoReopDelay 3
onDeopCommand
Type: string (command template)
Default: (empty)
Scope: channel
Raw IRC command sent to recover ops after being deopped, if the bot does not have halfop. Empty by default — set to opt in.
# Anope:
config network libera supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel $botnick
# Atheme:
config network libera supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel
onJoinCommand
Type: string (command template)
Default: (empty)
Scope: channel
Raw IRC command sent every time the bot successfully joins a channel. Empty by default — set to opt in.
The Anope UP command requests all access flags (op, halfop, voice) the bot
has registered, making it a convenient single setting that covers both initial
join and rejoin after kick.
# Anope UP — requests whatever access the bot has registered:
config network libera supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :UP $channel
# Atheme OP:
config network libera supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel
Nick recovery
These are network-level settings.
recoverNick
Type: Boolean
Default: True
Scope: network
When True and the bot is not using its configured nick (e.g. it connected as
MyBot_ because MyBot was taken), the bot will:
- Watch for the desired nick to become free via
QUITorNICKevents and claim it immediately. - Run a periodic poll every
recoverNickDelayseconds and attemptNICK <desired>if the nick appears free.
config network libera supybot.plugins.Resilience.recoverNick True
recoverNickDelay
Type: non-negative integer (seconds)
Default: 30
Scope: network
Two roles:
- Polling interval — seconds between periodic attempts to reclaim the configured nick while the bot is on a fallback nick.
- Recovery gap — seconds the bot waits between sending
nickRecoverCommandsand issuing the actualNICK <desired>command. This gives services (e.g. DALnet RECOVER/RELEASE) time to process before the bot tries to take the nick.
Set to 0 to disable polling entirely (the bot will only react to QUIT/
NICK events) and to skip the gap before NICK.
config network libera supybot.plugins.Resilience.recoverNickDelay 30
config network dalnet supybot.plugins.Resilience.recoverNickDelay 5
nickRecoverCommands
Type: string (comma-separated command templates)
Default: (empty)
Scope: network
Comma-separated raw IRC commands sent when the bot is not using its configured
nick. Fired automatically on connect (after MOTD) and when claimnick is run
manually. After sending them, the bot waits recoverNickDelay seconds then
issues NICK <desired>.
Leave empty on networks where the bot can simply re-use the nick without any service interaction (Libera, OFTC, etc.). Set it for networks that require an explicit recovery sequence.
# DALnet — RECOVER frees the nick, RELEASE makes it immediately available:
config network dalnet supybot.plugins.Resilience.nickRecoverCommands PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password
# Anope GHOST (alternative to RECOVER on some networks):
config network mynet supybot.plugins.Resilience.nickRecoverCommands PRIVMSG NickServ :GHOST $nick $password
Available substitutions: $nick (desired nick), $botnick (same),
$currentnick (what the bot is actually using now), $network, $password.
nickPassword
Type: string
Default: (empty)
Scope: network
Private: yes (stored in the private config file, not logged)
The password for the bot's registered nick on this network. Used as
$password in perform, nickRecoverCommands, and all on*Command
templates.
Set this with the nickpassword command, not by editing the config file
directly, so that the value is written to the private config file:
nickpassword libera mySecretPassword
Perform on connect
These are network-level settings.
perform
Type: string (comma-separated command templates)
Default: (empty)
Scope: network
Comma-separated raw IRC commands sent after the bot receives the end of MOTD
(numerics 376 / 422). The equivalent of ZNC's *perform module.
Commands are separated by commas. To include a literal comma inside a single
command, escape it as \,.
Available substitutions: $nick, $botnick, $currentnick, $network,
$password.
# Identify to NickServ and set user modes:
config network libera supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix
# Multiple steps for a network that needs them:
config network dalnet supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $nick $password, PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password
# Operator auth:
config network mynet supybot.plugins.Resilience.perform OPER mylogin $password
Manage the perform list interactively with the perform subcommands
(see Commands below).
performDelay
Type: non-negative integer (seconds)
Default: 2
Scope: network
Seconds to wait after receiving MOTD before sending perform commands. A brief
delay lets server-side authentication (SASL, etc.) complete before NickServ
or other commands are sent on top. Set to 0 to send immediately.
config network libera supybot.plugins.Resilience.performDelay 2
Commands
All commands require the admin capability.
perform set <network> <cmd1>, <cmd2>, ...
Set the perform list for <network>. Replaces the entire existing list.
Separate commands with commas; use \, for a literal comma inside a command.
perform set libera PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix
perform show <network>
Display the current perform list for <network>, numbered.
perform run <network>
Immediately send the perform list for <network> as if the bot had just
connected, without actually reconnecting. Useful for testing.
perform clear <network>
Clear the entire perform list for <network>.
nickrecover set <network> <cmd1>, <cmd2>, ...
Set the nick-recovery command list for <network>. Same comma-separated
format as perform.
nickrecover set dalnet PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password
nickrecover show <network>
Display the current nick-recovery command list for <network>.
nickrecover run <network>
Immediately run the nick-recovery commands for <network> and schedule
a NICK <desired> after recoverNickDelay seconds.
nickrecover clear <network>
Clear the nick-recovery command list for <network>.
nickpassword <network> <password>
Set (or update) the nick password for <network>. The password is stored
in the private config file and never logged. Use this instead of setting
nickPassword via the config command.
nickpassword libera mySecretPassword
retrylist
Show all channels currently waiting for a join retry, across all networks.
retrycancel [<channel>]
Cancel the pending join retry for <channel> on the current network and
clear the attempt counter. <channel> defaults to the current channel.
retrynow [<channel>]
Cancel any scheduled retry and immediately attempt to join <channel> on
the current network. Clears the attempt counter so the full retryJoinMax
budget is available again. <channel> defaults to the current channel.
claimnick
Immediately run nickRecoverCommands for the current network and schedule
a NICK <desired> after recoverNickDelay seconds. Also starts the
periodic polling loop if recoverNick is enabled.
reop [<channel>]
Manually trigger op recovery in <channel>: tries halfop self-op first,
then falls back to onDeopCommand if configured.
<channel> defaults to the current channel.
selfunban [<channel>]
Manually attempt to remove the bot's own ban masks from <channel> via
MODE -b. Requires the bot to currently have +o in the channel.
<channel> defaults to the current channel.
Recipes
Libera.Chat (Atheme, SASL handled by Limnoria)
SASL is configured at the Limnoria level (supybot.networks.libera.sasl.*),
so perform only needs post-auth steps.
config network libera supybot.plugins.Resilience.perform MODE $nick +ix
config network libera supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network libera supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network libera supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel
config network libera supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel
OFTC (custom NickServ, no SASL)
OFTC runs its own services and does not support SASL. Identification is done
via PRIVMSG NickServ after connect.
nickpassword oftc mypassword
config network oftc supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix
config network oftc supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network oftc supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network oftc supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel
config network oftc supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel
Anope-based network (SASL handled by Limnoria)
Most Anope 2.x networks support SASL. Configure it at the Limnoria level;
perform only needs post-auth steps. The UP command requests all access flags
the bot has registered (op, halfop, voice) in one shot.
config network mynet supybot.plugins.Resilience.perform MODE $nick +ix
config network mynet supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network mynet supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network mynet supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :UP $channel
config network mynet supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel $botnick
Anope-based network (no SASL / password auth only)
nickpassword mynet mypassword
config network mynet supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix
config network mynet supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network mynet supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network mynet supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :UP $channel
config network mynet supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel $botnick
DALnet (Dreamforge / old Anope, RECOVER+RELEASE nick flow)
nickpassword dalnet mypassword
config network dalnet supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $nick $password
config network dalnet supybot.plugins.Resilience.nickRecoverCommands PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password
config network dalnet supybot.plugins.Resilience.recoverNickDelay 5
config network dalnet supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network dalnet supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel $botnick
Disable all retry on a specific channel
config channel #sensitive supybot.plugins.Resilience.retryJoin False
config channel #sensitive supybot.plugins.Resilience.rejoinOnKick False
config channel #sensitive supybot.plugins.Resilience.autoReop False
Aggressive retry on an important channel
config channel #important supybot.plugins.Resilience.retryJoinMax 0
config channel #important supybot.plugins.Resilience.retryJoinDelay 30
config channel #important supybot.plugins.Resilience.rejoinKickMax 0
Full settings summary
| Setting | Scope | Type | Default | Description |
|---|---|---|---|---|
retryJoin |
channel | Boolean | True |
Master switch for join retrying |
retryJoinDelay |
channel | integer (s) | 60 |
Seconds between retry attempts |
retryJoinMax |
channel | integer | 10 |
Max attempts; 0 = unlimited |
onBanCommand |
channel | template | (empty) | Command sent before each ban retry |
onFullCommand |
channel | template | (empty) | Command sent before each full-channel retry |
onInviteOnlyCommand |
channel | template | (empty) | Command sent before each invite-only retry |
onBadKeyCommand |
channel | template | (empty) | Command sent before each bad-key retry |
selfUnban |
channel | Boolean | True |
Remove own bans via MODE -b when opped |
rejoinOnKick |
channel | Boolean | True |
Auto-rejoin after kick |
rejoinKickDelay |
channel | integer (s) | 5 |
Seconds before rejoining after kick |
rejoinKickMax |
channel | integer | 3 |
Max kick-rejoin attempts; 0 = unlimited |
autoReop |
channel | Boolean | True |
Recover ops after deop |
autoReopDelay |
channel | integer (s) | 3 |
Seconds before attempting reop |
onDeopCommand |
channel | template | (empty) | Command sent to recover ops |
onJoinCommand |
channel | template | (empty) | Command sent on every successful join |
recoverNick |
network | Boolean | True |
Reclaim configured nick when free |
recoverNickDelay |
network | integer (s) | 30 |
Poll interval and RECOVER→NICK gap |
nickRecoverCommands |
network | templates | (empty) | Commands sent when on a fallback nick |
nickPassword |
network | string | (empty) | Nick password (private); use nickpassword cmd |
perform |
network | templates | (empty) | Commands sent after MOTD |
performDelay |
network | integer (s) | 2 |
Seconds before sending perform |