Operations and runtime

Self-hosting guide

Everything after first launch: config keys, data paths, logs, Docker, and bot-owner maintenance commands.

Python path

Direct local run

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python run.py

Docker path

Container option in the repository

docker compose up --build -d

Docker Compose mounts config.ini, data/, and logs/ from the host.

Persistence

Files and directories the repository expects

SQLite and logs stay local. No cloud database required.

data/

SQLite data store and other runtime state live here.

logs/

Rotating logs write to logs/nanobot.log.

config.ini

Primary config file. Owner commands can reload and edit values.

run.py

Preflight checker validates Python version, deps, structure, config, and token format before launch.

Dependency base

Runtime packages

discord.py >= 2.7.1, aiosqlite >= 0.22.1, aiohttp >= 3.14.1, psutil >= 7.2.2, Pillow >= 12.2.0, PyNaCl >= 1.6.2, yt-dlp >= 2026.6.9

Config reference

example_config.ini broken into sections

The descriptions below come from comments in the repository's sample config.

[bot]

  • token YOUR_BOT_TOKEN_HERE Bot token from the Discord Developer Portal.
  • db_encryption_key (blank) Passphrase that encrypts both SQLite databases at rest via SQLCipher. Blank = no encryption. Requires pip install sqlcipher3-binary; the NANOBOT_DB_KEY env var overrides it. An existing plaintext database is encrypted on the next start. No key recovery — losing the key loses the data.
  • default_prefix n! Default command prefix (max 5 chars, no spaces).
  • owner_id (blank) Discord user ID of the bot owner. Leave blank to use the application owner.
  • health_check_port 0 Port for a plain-HTTP health endpoint (GET /health) for containers and orchestrators. Returns 200 once ready, 503 while starting. 0 disables it. May share a port with vote_webhook_port — the health route and vote webhook then use one listener.
  • health_check_host 0.0.0.0 Bind address for the health endpoint. 0.0.0.0 = all interfaces (needed for orchestrators / panels); 127.0.0.1 = host-local only. The payload is unauthenticated, so lock it down if the port is reachable.

[logging]

  • log_level INFO DEBUG / INFO / WARNING / ERROR / CRITICAL
  • log_http false true = log every raw HTTP request (very noisy).
  • log_events_jsonl true Also write structured command-lifecycle events (start/ok/error with timings and a correlation id) to logs/events.jsonl, one JSON object per line. The human-readable log is unaffected.
  • db_slow_query_ms 0 Log any SQLite query slower than this many milliseconds, so a slow/contended database surfaces in the logs. 0 disables it (zero overhead). A starting point on a busy bot: 200.

[votes]

  • topgg_token (blank) top.gg AUTH token.
  • topgg_v1_token (blank) top.gg v1 API token used for commands sync.
  • dbl_token (blank) discordbotlist.com bot token.
  • discordbotsgg_token (blank) discord.bots.gg bot token.
  • vote_webhook_port 5000 Local port the vote webhook listens on.
  • vote_webhook_secret (blank) Shared secret used by bot lists to authenticate webhooks.

[groq]

  • groq_api_key (blank) Groq API key (free at console.groq.com). Powers /eli5 and WYR generation. The GROQ_API_KEY environment variable takes priority if set.

[scraper]

  • fml_pages_per_scrape 500 FML pages fetched per daily scrape (~5-10 stories each).
  • wyr_requests_per_scrape 500 Would-You-Rather API requests per rating per daily scrape.
  • nekos_per_endpoint 400 nekos.best images fetched per endpoint per daily scrape.
  • nekosia_per_tag 400 Nekosia images fetched per tag per daily scrape.
  • revalidate_age 604800 Age (seconds) before a cached URL is rechecked with HEAD. 604800 = 7 days.
  • revalidate_batch 1000 Max URLs to HEAD-check per 6-hour revalidation cycle.
  • groq_wyr_system You generate Would You Rather questions for a Discord bot. Return ONLY... System prompt used when Groq generates fresh WYR questions daily.

[music]

  • music_cookie_file (blank) Path to a Netscape cookies file exported from a logged-in browser. Needed for age-restricted or login-only content.
  • music_default_volume 50 Default playback volume (0–200).
  • music_idle_timeout 180 Seconds before leaving after the channel empties or the queue runs out. When everyone leaves, playback pauses immediately and resumes automatically if someone returns within this window. Values below 30 are clamped to 30.
  • music_skip_ratio 50 Percent of voice-channel listeners who must vote to skip (0–100). Requesters and Manage Server can always force-skip.
  • music_max_queue 500 Maximum tracks allowed in a single server's queue.
  • music_js_runtime_path (blank) Path to a deno, node, or bun binary for yt-dlp's JavaScript interpreter. Leave blank to auto-detect.
  • music_use_opus true Send audio as Opus (default, cheaper) or PCM (true = instant volume/speed changes).
  • music_persist_queue true Save queue to disk so it survives a restart. Bot reconnects and resumes on startup.
  • music_predownload true Download the next queued track while the current one plays for instant track switching. Live streams are never pre-downloaded.
  • music_self_deafen true Self-deafen when joining a voice channel.
  • music_default_speed 1.0 Default playback speed (0.5–3.0).
  • music_search_service ytsearch Search service for non-URL queries: ytsearch, ytmsearch, or scsearch.
  • music_proxy (blank) HTTP/HTTPS proxy URL for yt-dlp. Example: http://user:pass@host:port.
  • music_save_videos false Keep downloaded audio in data/music_cache/ for instant replays. Pair with cache limits below.
  • music_cache_max_mb 0 Max cache size in MB (0 = unlimited). Only used when music_save_videos is on.
  • music_cache_max_age_days 0 Delete cached audio older than this many days (0 = never).
  • music_save_history true Save a per-server played-track history (for the n!history command).
  • music_metadata_lookup true Fill missing artist info via Apple's free iTunes Search API.
  • music_sponsorblock false Skip sponsor/non-music segments using the crowd-sourced SponsorBlock database. Forces a download before playback; live streams are unaffected.
  • music_sponsorblock_categories music_offtopic Comma-separated SponsorBlock categories to remove. Valid: sponsor, intro, outro, selfpromo, preview, filler, interaction, music_offtopic, poi_highlight.

Owner only

Maintenance commands

Commands in cogs/admin/ and cogs/debug.py require bot ownership.

cachestats

Owner Prefix

Show cache DB statistics for FML, WYR, and image pools.

Usage n!cachestats

Aliases cs

config

Owner Prefix

DM-only: show, get, or set config values without restarting.

Usage n!config <action> [key] [value]

Example n!config set default_prefix !

Aliases cfg

fmlpurge

Owner Prefix

Wipe all cached FML stories and force a fresh scrape on next run.

Usage n!fmlpurge

health

Owner Prefix

Probe the bot's HTTP /health endpoint and show the response, verifying it is reachable from outside the bot process. Requires health_check_port in config.ini.

Usage n!health

logs

Owner Prefix

Print the last N lines of the log file directly in Discord. Defaults to 20 lines.

Usage n!logs [lines]

Example n!logs 50

Aliases log

reload

Owner Prefix

Hot-reload one cog by name, or reload every cog if no argument given.

Usage n!reload [cog]

Example n!reload moderation

Aliases rl

reloadconfig

Owner Prefix

Re-read config.ini at runtime without restarting the bot.

Usage n!reloadconfig

Aliases rlc, rlconfig

restart

Owner Prefix

Graceful shutdown then re-exec the process — equivalent to stop + start.

Usage n!restart

Aliases reboot, rs

scrape

Owner Prefix

Manually trigger the daily content cache scrape without waiting for the scheduler.

Usage n!scrape

servers

Owner Prefix

List all servers the bot is in, with member counts and IDs. Paginated.

Usage n!servers [page]

Example n!servers 2

Aliases guilds, serverlist

setloglevel

Owner Prefix

Change the active log level immediately and persist the new value to config.ini.

Usage n!setloglevel <level>

Example n!setloglevel DEBUG

Aliases loglevel, loglvl

shutdown

Owner Prefix

Flush logs and close the Discord connection before exiting the process.

Usage n!shutdown

Aliases die, stop

sync

Owner Prefix

Push slash command definitions to Discord — globally or scoped to a single guild.

Usage n!sync [target] [guild_id]

Example n!sync guild 123456789012345678

unload

Owner Prefix

Unload a single cog by name without restarting the bot.

Usage n!unload <cog>

Example n!unload fun

Aliases ul

update

Owner Prefix

Pull latest code from git and reload all cogs. Does not re-sync slash commands.

Usage n!update

Aliases pull

upgrade

Owner Prefix

Full upgrade: git pull, pip install, spawn a new process, then shut down the old one.

Usage n!upgrade

Aliases deploy, ud

sh

Owner Prefix

Run a shell command and see stdout + stderr in Discord. 60-second timeout by default — pass --disable-timeout for long jobs like downloads. Output truncated at 900 chars per stream.

Usage n!sh [--disable-timeout] <command>

Example n!sh df -h

Aliases shell, exec

py

Owner Prefix

Evaluate Python code inside the running bot process. Top-level await works. 60-second timeout by default — pass --disable-timeout to remove it. Output truncated at 900 chars.

Usage n!py [--disable-timeout] <code>

Example n!py len(bot.guilds)

Aliases eval, python