GoBot is a Go IRC bot built for long-running use on one or more IRC networks. It connects over TLS, rejoins after disconnects, rate-limits outbound messages, stores plugin data in BoltDB, and exposes runtime stats over HTTP.
The repository includes sample IRC connection details to help you get started, but the bot is structured to support multiple networks and multiple channels per network.
- What GoBot does
- Requirements
- Build and run
- GitHub Actions and security updates
- Systemd deployment
- Project layout
- Configuration overview
- Plugins
- NickServ registration and SASL authentication
- Docker
- First-run workflow
- Optional banter plugin
- News plugin
- Stats and monitoring
- Security notes
- License
GoBot starts one IRC connection per configured network. Each connection:
- authenticates with the configured nickname and optional SASL credentials
- joins the configured channels
- listens for channel and private messages
- hands each message to enabled plugins
- reconnects with backoff if the network drops
Persistent plugin state such as seen/karma data is stored in BoltDB. Runtime counters are exposed on /stats and /metrics and their cumulative totals survive restarts.
- Go
1.25.12+(or a newer supported release) - A writable filesystem for the BoltDB database
- IRC network access
- Optional: Docker / Docker Compose
- Optional: fortune packages if you want a curated set of extra banter quote sources
Build the bot binary:
make buildThis writes:
bin/irc-bot
You can also use the helper script:
./scripts/build.shRun directly from the repository root so config.yaml is found:
./bin/irc-botFor development:
make run
make testGitHub Actions runs verification on pushes and pull requests targeting main or dev. It checks Go formatting, go vet, race-enabled tests, the production build, and known vulnerabilities with govulncheck. A separate CodeQL workflow performs source-level security analysis and publishes findings to GitHub code scanning.
The workflow also runs weekly on Monday at 03:17 UTC. Dependabot checks Go modules and GitHub Actions weekly and opens update pull requests labeled dependencies and security. If you fork GoBot, review these updates before merging or deploying them.
For a manual binary deployment on a Linux host using systemd, run the installer from the repository directory:
sudo ./scripts/install-systemd.shThe installer detects the repository directory and the invoking Linux user, installs /etc/systemd/system/gobot.service, reloads systemd, and enables the service at boot. It does not start GoBot.
Before starting the service:
- Review and customize
config.yaml. - Copy
.env.exampleto.env, add any secrets, and runchmod 600 .env. - Build the binary with
./scripts/build.sh. - Start it when ready:
sudo systemctl start gobot.service
sudo systemctl status gobot.service
journalctl -u gobot.service -fThe installer supports a different deployment directory or service account when needed:
sudo ./scripts/install-systemd.sh --install-dir /opt/gobot --user gobot --group gobotIf the repository is moved, rerun the installer so the unit's paths are updated. Configuration changes require a service restart; code changes require rebuilding before restarting.
cmd/irc-bot/: main program entrypointbot/: IRC connection, config loading, queueing, stats, and plugin interfacesplugins/: built-in plugins, one plugin per file where practicalstorage/: BoltDB wrapper used by stateful pluginsquotes/: local banter quote filesgrafana/: importable Prometheus dashboard for GoBotscripts/: helper scripts for building, publishing, and installing systemd
The main configuration file is config.yaml.
Important sections:
networks: one entry per IRC networknetworks[].server: IRC host, port, TLS, certificate verificationnetworks[].identity: nick, user, real name, optional SASL settingsnetworks[].channels: channels to join on that networkcommand_prefix: command prefix, default!owner_accounts: authenticated IRC account names reserved for future owner-only featuresinvites: whether the bot accepts IRC invitations and the invite cooldownrate_limit: outbound message pacing, per-user command cooldowns, and cooldown-warning pacingplugins: plugin enable/disable and plugin-specific settingsstats: HTTP stats listenerstorage: BoltDB pathlog: log level and format
GoBot ignores channel messages for 10 seconds after joining by default. This prevents IRC history replay or relay backlog from triggering URL previews, banter, and other plugins as a flood. Adjust the window with rate_limit.join_warmup_seconds; set it to 0 to use the 10-second default.
Example multi-network layout:
networks:
- name: primary
server:
host: irc.example.net
port: 6697
tls: true
verify_cert: true
identity:
nick: GoBot
user: gobot
realname: Go IRC Bot
sasl_user: ""
sasl_pass: ""
channels:
- "#example"
- "#bots"
- name: secondary
server:
host: irc.example.org
port: 6697
tls: true
verify_cert: true
identity:
nick: GoBot2
user: gobot2
realname: GoBot on secondary network
sasl_user: ""
sasl_pass: ""
channels:
- "#example"If networks is set, it takes precedence over the older single-network top-level fields.
Owners are configured out-of-band using authenticated NickServ/IRC accounts. Nicknames alone are never treated as proof of ownership:
owner_accounts:
- "your-account"
- "another-owner"GoBot records the IRCv3 account tag when the network provides it and exposes owner status to future owner-only features. There is intentionally no ownership-claim command, so nobody can claim ownership from IRC.
Anyone may invite the bot to a valid channel when invitations are enabled:
invites:
enabled: true
cooldown_seconds: 30From an IRC client:
/invite GoBot #new-channel
GoBot joins the invited channel after validating the channel name. Invitations are rate-limited globally per bot/network and per channel. Invited channels are temporary and are not written to config.yaml; add a channel to the network's channels list if it should be rejoined after a restart.
All command responses are rate-limited per authenticated account or sender identity. This includes !help, !weather, !roll, !poll, and every other command. Configure it with:
rate_limit:
command_cooldown_seconds: 2
command_warning_cooldown_seconds: 10When a user sends commands too quickly, GoBot replies once with command cooldown—please wait a moment. Warning messages have their own cooldown, so repeated violations cannot flood the channel. The normal outbound queue rate limit still applies to all bot messages.
Selected command responses use standard mIRC IRC color and bold control codes for headings, success, warning, and error states. These are not terminal-specific ANSI escapes: clients such as WeeChat, Irssi, and Relay can render them, while clients without formatting support show the same underlying text. Blackjack also uses UTF-8 suit symbols; clients with limited Unicode fonts may display those symbols as replacement characters.
GoBot ships with these plugins:
correction: watches recent messages and supports IRC-style fixes likes/wiiifee/wifebanter: optional conversational replies when the bot is directly addressedurltitle: fetches and posts page titles for shared URLs; YouTube links include the channel and video durationweather: current weather using Open-Meteo, no API key required; aliases:!wx,!forecast, and!tempnews: headlines and search using NewsAPIwikipedia: article summaries from Wikipediaseen: records when a nick last spoketell: immediately relays a message to the current channel or conversationkarma: tracksthing++andthing--, and answers!karma <thing>dice: dice rolling commandsblackjack: play a separate per-user game of blackjack/21poll: create and vote in channel pollsremind: schedule a reminder messagequote: request a random quote from the configured quote sourceschoose: randomly choose between several optionstime: show the current time in an IANA timezonechannelstats: persistent per-channel message and user statisticsduckhunt: optional activity-triggered duck hunt with shooting, befriending, and persistent per-channel scoreshelp: lists commands and usagealias: lists command aliases and supports per-command alias details
Plugin toggles live under plugins.<name>.enabled.
!help shows a compact one-line plugin index. Use !help <plugin> for detailed usage, such as !help poll, !help blackjack, or !help urltitle.
Use !alias for a compact grouped alias list, or !alias wiki to inspect one command's aliases.
With the optional banter plugin enabled, address the bot in a channel:
GoBot hello, how are you?
@GoBot hello
Banter replies are intentionally random and may not respond to every message. Commands can be sent in a channel or by direct message where the command supports it:
!help
!weather Seattle
!wx Seattle
!quote
!tell username hello
!tell immediately relays a message to the current channel or conversation:
!tell GoBot you are awesome
GoBot posts GoBot: you are awesome immediately. owner_accounts is reserved for future owner-only features.
Use the familiar IRC correction syntax immediately after your message:
I need a wiiifee
s/wiiifee/wife
GoBot corrects the most recent matching message from that nickname. Add /g for every matching occurrence:
s/wiiifee/wife/g
The correction history is kept in memory and controlled by history_size.
When someone posts an HTTP or HTTPS URL in a channel, urltitle fetches a page title and posts it. It prefers Open Graph and Twitter metadata, then falls back to the HTML <title> element. YouTube links are formatted with the title, channel, and duration:
[YouTube] Video title | Channel: Example Channel | 1m 37s
For the most reliable YouTube duration data, configure an optional YouTube Data API v3 key. Without it, GoBot uses public oEmbed, player metadata, and HTML fallbacks; some restricted or dynamically rendered videos may not expose their duration. Sites that block automated requests may not provide a title; GoBot suppresses access-denied/error titles rather than posting them.
Keep the key out of Git and set it in .env:
BOT_YOUTUBE_API_KEY=your-youtube-data-api-key
The API key must have YouTube Data API v3 enabled. GoBot requests only snippet and contentDetails for the linked video; the API response supplies the canonical title, channel, and ISO-8601 duration.
Weather uses Open-Meteo and does not require an API key:
!weather Seattle
Set default_units to metric or imperial in config.yaml.
Use either command alias to search English Wikipedia and receive a summary:
!wiki Linux
!wikipedia FreeBSD
GoBot first tries the article title, then searches Wikipedia if the query is not an exact title. max_summary_length controls the response length.
!seen nickname
!tell nickname message for them
!karma project
project++
project--
!roll d20
!roll 2d6
seenreports the channel and message from the last time a nickname spoke. Its records are stored in BoltDB.tellimmediately relays the requested message in the current channel or conversation.karmatracks case-insensitivething++andthing--changes and reports totals with!karma <thing>.diceacceptsNdNnotation or a single number such as!roll 20, which means1d20. It allows up to 100 dice and 10,000 sides per die and uses secure random values.
!help returns a compact plugin index. Ask for details without flooding the channel:
!help
!help weather
!help urltitle
!help blackjack
List aliases without scanning the full help output:
!alias
!alias blackjack
Start a game with:
!21
Then use:
!21 hitto draw another card!21 standto let the dealer finish!21 doubleto draw one final card and stand; this is available only on the initial two-card hand
View your persistent personal record or the top five players:
!bj stats
!bj leaderboard
Shortcut aliases are also available during a game: !hit, !stand, and !double. !bj is a short alias for !21.
Games are tracked separately for each nickname in each channel and are held in memory, so active games disappear if the bot restarts. Abandoned games expire after 30 minutes. Replies are posted to the channel. The dealer stands on 17. !blackjack is also accepted as an alias for !21.
Blackjack records are stored in BoltDB and survive restarts. GoBot tracks hands, wins, losses, pushes, blackjacks, busts, win rate, and streaks. Authenticated IRC account names are preferred for identity; nicknames are used when no account tag is available. No real-money or virtual-currency betting is involved.
Blackjack replies use standard IRC color formatting where supported, plus UTF-8 suit symbols such as ♠, ♥, ♦, and ♣. Clients without color or Unicode support still receive readable card ranks and game results, although the suit symbols may display differently.
Create a poll with two or more options:
!poll create Pizza or tacos? | Pizza | Tacos
!poll vote 1
!poll results
!poll close
Each nickname gets one vote, and voting again changes that nickname's vote. Active polls are stored in BoltDB and restored after a restart. Polls automatically expire after 15 days.
!remind 30m check the logs
!remind 45s check the connection
!remind 2h restart the service
!remind 1h30m check the deployment
!remind 72h check in three days
Reminders accept Go-style durations from 1s through 360h and are limited to 20 pending reminders per user/channel. Supported units are s (seconds), m (minutes), and h (hours). Days must be expressed as hours because d is not a valid Go duration; use 72h for three days. Pending reminders are stored in BoltDB and rescheduled after a restart; reminders older than 15 days are discarded.
!quote
The quote plugin uses the same built-in, configured, and fortune sources as banter. It reads files directly and never executes the fortune command.
Choose between two to twenty options using pipes or commas:
!choose pizza | tacos | burgers
!choose red, blue, green
Use IANA timezone names, with a few common aliases:
!time America/Los_Angeles
!tz UTC
!time EST
Use any of these aliases:
!stats
!chanstats
!channelstats
The response reports message totals, distinct users, and the top five users for the current channel. HTTP /stats, /metrics, and !stats channel details persist in BoltDB and survive restarts.
Duck Hunt is a lightweight channel activity event rather than a round-based game. It is disabled by default so enabling it is an intentional choice for channels that want occasional fun interruptions. It does not kick users, use virtual currency, or involve wagers.
Enable it in config.yaml:
plugins:
duckhunt:
enabled: true
minimum_messages: 25
minimum_users: 2
min_delay_seconds: 60
max_delay_seconds: 300
timeout_seconds: 30
befriend_enabled: true
min_reaction_seconds: 1
retry_cooldown_seconds: 7After the channel reaches the configured activity threshold, GoBot waits a random amount of time and announces a duck. The first person to send !bang wins:
A wild duck appeared: \_o< quack! Type !bang to shoot it!
username shot a duck in 2.137 seconds! You have killed 1 duck in #example.
Use !ducks to see your score or !ducks nickname to check another participant's score. Scores include both ducks shot and ducks befriended, are stored in BoltDB, and survive restarts. Normal command rate limiting applies to Duck Hunt commands.
Commands:
!bang shoot an active duck
!befriend befriend an active duck, if enabled
!ducks [nickname] show persistent scores
!dh show Duck Hunt status
!dh status show Duck Hunt status
!dh start enable automatic activity for this channel (owner only)
!dh stop stop Duck Hunt in this channel (owner only)
!dh is a short alias for !duckhunt. Start/stop controls require an authenticated IRC account listed in owner_accounts; nicknames alone are not accepted as proof of ownership. Anyone can shoot or befriend an active duck.
Shots taken instantly are treated as likely scripted input and miss. Shots taken during the early reaction window have a probability of success, while slower shots succeed. A user gets a short retry cooldown after an attempt. Invalid shots when no duck is active are ignored, which keeps the feature quiet and prevents command flooding.
Duck Hunt announcements and results use standard mIRC IRC colors for the Duck Hunt label, duck, quack, misses, and successful interactions. Clients that do not render IRC colors still receive the same readable text and ASCII duck.
The activity settings control how often the event can occur:
minimum_messages: messages required before a spawn can be scheduledminimum_users: distinct nicknames required before a spawn can be scheduledmin_delay_secondsandmax_delay_seconds: random wait after the activity thresholdtimeout_seconds: how long the duck remains availablebefriend_enabled: whether!befriendis availablemin_reaction_seconds: minimum reaction time before a shot can succeedretry_cooldown_seconds: per-user cooldown after a shot attempt
NickServ registration and SASL authentication are separate steps:
- register the nickname/account on the IRC network
- configure GoBot to log into that account automatically
First, connect with a normal IRC client and check the network’s NickServ help:
/msg NickServ HELP REGISTER
On many networks the registration flow looks like:
/msg NickServ REGISTER <strong-password> <email-address>
Then verify the account manually:
/msg NickServ IDENTIFY <account-name> <strong-password>
After that, configure the bot with the registered nick and SASL account name.
Recommended split for a one-network deployment:
- keep
nickandsasl_userinconfig.yaml - keep the password in
.env
Example .env:
BOT_SASL_USER=GoBot
BOT_SASL_PASS=replace-with-your-nickserv-password
BOT_NEWS_API_KEY=
BOT_STORAGE_DB_PATH=./data/bot.db
BOT_STATS_LISTEN_ADDRESS=127.0.0.1:8082Protect it:
chmod 600 .envNotes:
BOT_SASL_USERandBOT_SASL_PASSoverride config valuesBOT_SASL_PASSauthenticates the bot, it does not register the nickname- do not commit NickServ passwords
- keep
verify_cert: true
If a network sometimes renames the bot to Guest..., review these optional identity settings:
nickserv_fallbacknickserv_ghost
The repository includes a multi-stage Dockerfile and a Compose file.
Build and run manually:
docker build -t gobot .
docker run --rm --env-file .env gobotRun with Compose:
cp .env.example .env
docker compose build
docker compose up -d
docker compose logs -f gobotCompose behavior:
- mounts
config.yamlread-only - persists data in a named volume
- publishes the stats listener
- restarts the bot automatically unless stopped
Useful commands:
docker compose ps
curl http://localhost:8082/stats
docker compose downdocker compose down -v also removes the named volume and deletes stored bot data.
There is no setup wizard yet, so first run is configuration-first.
- Build the binary with
make buildor./scripts/build.sh. - Review
config.yaml. - Set at least one network, nick, and channel.
- If using NickServ, create
.envand setBOT_SASL_USER/BOT_SASL_PASS. - If using NewsAPI, set
BOT_NEWS_API_KEY. - Start the bot.
- Watch logs and confirm it joins the expected channels.
- Test a few commands such as
!help,!weather,!wiki, and!karma. - To add a temporary channel, invite the bot with
/invite <bot-nick> #channel; add permanent channels toconfig.yaml.
For direct binary use:
./bin/irc-botFor Docker:
docker compose up -d
docker compose logs -f gobotFor config-only changes, restart the bot. You do not need to rebuild the Go binary unless the code changed.
The banter plugin adds occasional personality without turning the bot into constant channel noise. You can enable or disable it depending on how chatty you want the bot to be.
Key settings:
plugins:
banter:
enabled: true
probability: 0.25
quotes_file: "quotes/banter.txt"
fortune_dir: "/usr/share/games/fortunes"Behavior:
- replies only when directly messaged or when its nick is mentioned in-channel
- ignores
!commands - picks a quote at random
- splits long quotes into safe IRC-sized chunks
- loads plain text quote files from
quotes/ - optionally loads classic
fortunequote files fromfortune_dir
GoBot does not shell out to fortune. It reads quote files directly, which keeps behavior predictable and avoids command-execution surprises.
The recommended setup is to install the fortune data packages, then copy only the collections you want into a separate curated directory. This avoids loading every system fortune into the bot and makes the bot's quote sources explicit.
On Debian or Ubuntu, the following installs the data and creates a curated set containing computers, linux, science, tao, and wisdom in one step:
sudo apt update && sudo apt install -y fortune-mod fortunes-min fortunes && \
mkdir -p "$HOME/irc-bot/fortune-curated" && \
for quote in computers linux science tao wisdom; do \
source_file="$(find /usr/share/games/fortunes /usr/share/fortune -maxdepth 1 -type f -name "$quote" -print -quit 2>/dev/null)"; \
if [ -n "$source_file" ]; then \
cp "$source_file" "$HOME/irc-bot/fortune-curated/$quote"; \
else \
printf 'Warning: fortune collection not found: %s\n' "$quote" >&2; \
fi; \
doneThe package names and source directories can vary by distribution. If a collection is not found, locate it with:
find /usr/share/games/fortunes /usr/share/fortune -maxdepth 1 -type f 2>/dev/null | sortAdd or remove names in the loop to choose a different curated set. The files are classic %-delimited fortune files; GoBot parses those delimiters directly.
Point fortune_dir at the resulting directory using an absolute path:
plugins:
banter:
enabled: true
probability: 0.25
quotes_file: "quotes/banter.txt"
fortune_dir: "/path/to/your/fortune-curated"
quote:
enabled: true
fortune_dir: "/path/to/your/fortune-curated"Replace /path/to/your/fortune-curated with the real path on the host, for example the $HOME/irc-bot/fortune-curated directory created above. Configuration values are read by GoBot as written, so use the expanded absolute path rather than relying on $HOME inside YAML.
Both banter and !quote use the configured fortune directory. Every regular file in that directory is loaded, so the curated directory can contain exactly the collections you selected. Files ending in .dat or .u8 are ignored. The repository's built-in file is also loaded automatically:
quotes/banter.txt
You do not need OS fortune packages if you only want quotes/banter.txt. You may also point fortune_dir at another local directory containing classic %-delimited fortune text files, but avoid pointing it at a large system-wide collection unless you intentionally want all of it loaded.
The news plugin requires a NewsAPI key.
Enable it in config:
plugins:
news:
enabled: true
api_key: ""
max_results: 3Recommended secret handling:
- leave
api_keyempty inconfig.yaml - set
BOT_NEWS_API_KEYin.env
Usage:
!newsshows top US headlines!news linuxsearches recent English-language articles forlinux
If no key is configured, the plugin responds with news is not configured.
GoBot exposes:
/stats: human-readable JSON runtime stats for troubleshooting/metrics: Prometheus-compatible metrics for monitoring
Typical checks:
curl http://127.0.0.1:8082/stats
curl http://127.0.0.1:8082/metricsThe cumulative counters persist in BoltDB and survive restarts. /stats shows uptime as a human-readable value; /metrics exposes numeric bot_uptime_seconds for Prometheus. Connection status and uptime naturally reset or change when the process restarts.
The Prometheus metrics are:
bot_connected:1while at least one IRC connection is active, otherwise0bot_reconnects: cumulative reconnect countbot_messages_received: cumulative IRC messages receivedbot_messages_sent: cumulative messages sent by GoBotbot_commands_handled: cumulative commands handledbot_uptime_seconds: current process uptime in secondsbot_messages_dropped: messages discarded because the outbound queue was full
The repository includes an importable Grafana dashboard at
grafana/gobot-dashboard.json. A populated example
is shown below; values and time ranges will vary with your GoBot instance.
If Prometheus runs on another machine, GoBot must listen on a VPS address reachable by that machine. For example, on the GoBot VPS set .env to the VPS's LAN address and port:
BOT_STATS_LISTEN_ADDRESS=<BOT_VPS_LAN_IP>:8082Find the correct VPS address with:
ip -br addrAllow only the monitoring host through the VPS firewall. For UFW, where 192.0.2.50 is the Prometheus host:
sudo ufw allow from 192.0.2.50 to any port 8082 proto tcp
sudo systemctl restart gobotPrometheus should scrape /metrics, not /stats:
scrape_configs:
- job_name: gobot
metrics_path: /metrics
static_configs:
- targets:
- <BOT_VPS_LAN_IP>:8082Test connectivity from the Prometheus host:
curl http://<BOT_VPS_LAN_IP>:8082/metricsBoth endpoints have no built-in authentication. Restrict port 8082 to the Prometheus host and do not expose it to the public Internet.
- Keep
server.verify_cert: trueunless you have a controlled reason not to. - GoBot refuses to send SASL or NickServ credentials over a non-TLS IRC connection.
- Keep secrets in
.envor your deployment secret store, not in Git. - Configure owner accounts by authenticated account name; do not rely on nicknames for authorization.
/statsand/metricshave no built-in authentication.- Bind stats to localhost unless you intentionally expose them.
- The URL title plugin only fetches public HTTP/HTTPS targets and rejects loopback, private, link-local, multicast, and local hostnames to reduce SSRF risk.
- External HTTP lookups use timeouts.
- IRC invitations, command handling, and cooldown warnings are rate-limited to reduce channel and join flooding.
- The Docker image runs as a non-root user.
- BoltDB data persists locally; protect filesystem access on the host.
MIT
