CraftDaemon Documentation
Getting Started
Welcome to CraftDaemon! This documentation covers everything you need to know to install, configure, and run CraftDaemon on your Linux server. If this is your first time, read through each section in order, skipping steps is the most common cause of setup issues.
What is CraftDaemon?
CraftDaemon is a Discord bot that lets you manage your Minecraft server directly from Discord using slash commands. It integrates with systemd (Linux's service manager) to start, stop, and restart your server process, and uses RCON (a remote console protocol built into Minecraft) to query live server data like player count and TPS. You don't need to SSH into your server just to restart it, CraftDaemon handles it all from a Discord channel.
System Requirements
- Operating System: Linux with systemd (Ubuntu, Debian, Arch, etc.) - Check Operating System Compatibility
- Node.js: v18+ (v22 recommended; used for development and testing). To check what you have, run: node --version
- Minecraft Server:: Any server with RCON support enabled (Although Paper is recommended)
- Network: Outbound HTTPS access for the Discord API, and a local connection to RCON (127.0.0.1 by default)
CraftDaemon works with any Minecraft server that supports RCON for start/stop/restart commands. However, the full /status command (Specifically for TPS) requires a Paper server. This is the sole reason Paper is recommended, apart from it being extremely light. Compatiblity is being worked on for future updates.
This bot does not run natively on Windows or macOS. Advanced users may still run it using environments like Windows Subsystem for Linux (WSL) on Windows, or other Unix-like setups on macOS, but this is not officially supported.
A future migration to TypeScript is planned, which will likely require Node.js v22+.
Installation
Step 1: Clone the Repository
SSH into your Linux server and run the following to download CraftDaemon into the current directory, then navigate into it:
cd CraftDaemon
Step 2: Install Node.js Dependencies
This installs all required packages listed in package.json. Run this from inside the CraftDaemon directory:
If you get a "command not found" error, Node.js is not installed or not in your PATH. Verify with node --version and which node.
Step 3: Create Discord Bot Application
You'll need to create a bot account in Discord's developer portal. This gives you a token (like a password) that CraftDaemon uses to log in as your bot.
- Go to Discord Developer Portal at discord.com/developers/applications
- Click "New Application" and give it a name (e.g., "CraftDaemon")
- Navigate to the "Bot" section in the left sidebar and click "Add Bot"
- Click "Reset Token" to reveal your bot token, then copy it — you'll paste this into your .env file later. You only see it once, so save it now.
- Scroll down to "Privileged Gateway Intents" and enable both:
- Server Members Intent
- Message Content Intent
- Go to "OAuth2" → "URL Generator" in the left sidebar. Under "Scopes", check bot and applications.commands
- Under "Bot Permissions", check: Send Messages, Embed Links, Read Message History
- Copy the generated URL at the bottom of the page and open it in your browser to invite the bot to your Discord server
Your bot token is like a password, never share it. If you are contributing or modifying the for code yourself, never commit it to Git. Keep it only in your config/.env file, and make sure .env is listed in your .gitignore. If it gets leaked, immediately regenerate it in the Developer Portal.
Step 4: Set Up Minecraft as a systemd Service (If you haven't already)
CraftDaemon controls your Minecraft server through systemd, Linux's service manager. If your server isn't already running as a systemd service, you'll need to set this up. Create the service file using a text editor with root privileges:
Paste the following content into the file, then save and exit (Ctrl+O, Enter, Ctrl+X in nano):
Description=Minecraft Server
After=network.target
[Service]
Type=simple
User=minecraft
WorkingDirectory=/home/minecraft/server
ExecStart=/usr/bin/java -Xmx8G -Xms8G -jar server.jar nogui
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Adjust the following to match your setup before saving:
- User: the Linux user that owns the Minecraft server files
- WorkingDirectory: the full path to your server folder
- ExecStart: make sure the java path is correct (which java will tell you) and the .jar filename matches your actual server jar
- -Xmx8G / -Xms8G: the maximum and starting RAM for your server — adjust to suit your machine
Then reload systemd and enable the service to start on boot:
sudo systemctl enable minecraft
sudo systemctl start minecraft
Step 5: Enable RCON on Your Server
RCON (Remote Console) is a protocol built into Minecraft that allows CraftDaemon to query live server data and send commands. You need to enable it in your server's server.properties file, which is located in your Minecraft server directory. Open it with:
Find and update (or add) these three lines:
rcon.port=25575
rcon.password=your_secure_password_here
Use a strong, unique password — this password must exactly match the RCON_PASSWORD value in your .env later. After saving, restart your Minecraft server:
Step 6: Configure Environment Variables
CraftDaemon uses a .env file to store all sensitive configuration like your bot token and RCON password. Start by copying the provided example file:
Then open it for editing:
Fill in your values. At minimum, set these required fields:
GUILD_ID=your_server_id
CLIENT_ID=your_client_id
STATUS_CHANNEL_ID=your_channel_id
MC_SERVICE=minecraft
RCON_HOST=127.0.0.1
RCON_PORT=25575
RCON_PASSWORD=your_rcon_password
AUTO_STOP_MINUTES=10
WARNING_MINUTES=8
MC_LOG_PATH=/home/minecraft/server/logs/latest.log
See the Configuration section for a full breakdown of every available variable.
Step 7: Register Slash Commands
Before users can see or use slash commands in Discord, they need to be registered with Discord's API. Run this once from the CraftDaemon directory:
You only need to run this once after initial setup, or again if you add or modify commands. Slash commands may take up to a minute to appear in Discord after registration.
Step 8: Set Up Sudoers Permissions
CraftDaemon needs to run systemctl commands (to start/stop/restart your Minecraft server) without being prompted for a password. This is done by adding a rule to the sudoers file. Always edit sudoers with visudo — it validates the file before saving and prevents you from locking yourself out:
Scroll to the bottom of the file and add this line, replacing botuser with the Linux username the bot runs as:
Save and exit (Ctrl+O, Enter, Ctrl+X). To verify which user the bot will run as, check your craftdaemon.service file's User= field.
Only grant the bot the specific systemctl permissions it needs (as shown above). Never add the bot user to ALL=(ALL) NOPASSWD: ALL — that would give it unrestricted root access to your entire system.
Step 9: Install as systemd Service (Optional but Recommended)
Running CraftDaemon as a systemd service means it automatically starts on boot and restarts itself if it crashes. Create the service file:
Paste the following, then update User and WorkingDirectory to match your setup:
Description=CraftDaemon Bot
After=network.target
[Service]
Type=simple
User=botuser
WorkingDirectory=/path/to/CraftDaemon
ExecStart=/usr/bin/node src/index.js
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
Environment="NODE_ENV=production"
[Install]
WantedBy=multi-user.target
Make sure the node path in ExecStart is correct — run which node to find yours. Then enable and start:
sudo systemctl enable craftdaemon
sudo systemctl start craftdaemon
CraftDaemon requires Node.js 18+ and is tested on Node.js 22. Check your version with node --version. If you need to install or update Node.js, use nvm or your distro's package manager.
Updating & Upgrading
CraftDaemon is actively developed and releases can introduce new environment variables, permission keys, command changes, or config file renames. Skipping these changes is the most common cause of issues after an upgrade. This section covers how to update safely and what to watch out for.
The Update Process
At a high level, updating CraftDaemon follows these steps:
- Read the patch notes for the version you're upgrading to (and any versions you're skipping — see below)
- Back up your config - at minimum, copy config/.env and config/permission-config.js somewhere safe
- Pull the new code - git pull or a clean install (see below)
- Install dependencies - run npm install in case package.json changed
- Apply config changes - add new .env variables, permission keys, or other changes noted in the patch notes
- Re-register slash commands - run node src/register-commands.js if new commands were added or existing ones changed
- Restart the bot - sudo systemctl restart craftdaemon
You only need to run node src/register-commands.js if the patch notes mention new or changed slash commands. If you're unsure, run it anyway — it's idempotent and won't cause issues if nothing changed.
Read the Patch Notes
Every release includes a changelog that lists exactly what changed. Pay special attention to these sections:
- Environment Variables - new .env keys that need to be added to your config/.env. Missing required variables will cause the bot to fail at startup
- Permission Changes - new permission strings in permission-config.js. Without these, new commands will be locked behind permissions that don't exist in your config, making them inaccessible
- Post-Update Steps - manual actions required after pulling (e.g. re-registering commands, clearing caches)
- Breaking Changes - anything that changes existing behavior or requires config migration
If you're upgrading from v1.2.0 to v1.3.3, don't just read the v1.3.3 notes — read all patch notes from v1.2.1 through v1.3.3. The latest changelog may assume you already applied changes from the versions in between. For example, v1.3.3's permission changes build on top of v1.3.0's RBAC additions — if you skip those, /player permissions won't work correctly.
git pull vs Clean Install
There are two ways to update. Which one you choose depends on how much has changed and whether you've modified the bot's source files.
Option A: git pull (Quick)
If you haven't modified any source files and your config is the only thing you've changed:
git pull
npm install
# Apply any config changes from the patch notes
node src/register-commands.js # if needed
sudo systemctl restart craftdaemon
This is the fastest method. However, git pull can fail with merge conflicts if you've edited source files (even accidentally). If that happens, a clean install is the safer route.
Option B: Clean Install (Recommended for major updates)
If you're jumping multiple versions, have merge conflicts, or want to be absolutely sure nothing is left over from an old install:
- Back up your config files:
cp config/.env ~/craftdaemon-env-backup
cp config/permission-config.js ~/craftdaemon-perms-backup - Rename or move the old directory:
mv CraftDaemon CraftDaemon-old
- Clone a fresh copy:
git clone https://github.com/d1vid3d/CraftDaemon
cd CraftDaemon - Install dependencies:
npm install
- Apply your config to the new copy. You have two options:
- Copy your backed-up config directly - fastest, but you may miss new keys that were added since your last version. Compare your old .env against the new .env.example to find what's missing
- Start from the new .env.example - copy it to .env, then fill in your values from the backup. This guarantees you have all current keys, but takes a few more minutes
- Re-register commands and restart:
node src/register-commands.js
sudo systemctl restart craftdaemon - Verify everything works, then delete the old directory:
rm -rf ~/CraftDaemon-old
Not sure what changed between your old config and the new one? Use diff to find out:
diff ~/craftdaemon-perms-backup config/permission-config.js
This shows exactly which lines differ, making it easy to add only what's new without overwriting your existing values.
What Typically Changes Between Versions
While every release is different, these are the most common things that need manual attention after an update:
| What Changes | Where | What Happens If You Skip It |
|---|---|---|
| New environment variable | config/.env | Feature may fail silently, bot may crash on startup, or a warning is logged |
| New permission string | config/permission-config.js | New commands are inaccessible — no role can use them |
| Changed default value | config/.env | Behavior differs from what the patch notes describe |
| New slash command or subcommand | Discord API | Command doesn't appear in Discord until you re-register |
| Renamed file or variable | Various | Bot may crash or feature may not load |
The bot's code is designed to work with its documented config. Running new code with an old config is the number one cause of post-update issues. Always check the patch notes, always diff your configs, and always test after upgrading.
Configuration
Environment Variables (.env)
| Variable | Required | Description | Options (Suggested Range) |
|---|---|---|---|
| TOKEN | ✅ | Your Discord bot token | Discord bot token string |
| GUILD_ID | ✅ | Your Discord server ID (for registration) | Discord guild/server ID |
| CLIENT_ID | ✅ | Your Discord application client ID | Discord application client ID |
| STATUS_CHANNEL_ID | ✅ | Channel for status/warning messages | Discord channel ID |
| MC_SERVICE | ✅ | Minecraft systemd service name | Systemd unit name (default: minecraft) |
| SERVER_TYPE | ☑️ | Server type for TPS reporting in /status | PAPER (default left blank), change to PAPER for Paper tps parsing |
| RCON_HOST | ✅ | RCON server address | Default: 127.0.0.1 |
| RCON_PORT | ✅ | RCON port (from server.properties) | Valid: 1-65535 (default: 25575) |
| RCON_PASSWORD | ✅ | RCON password (from server.properties) | Must exactly match server.properties |
| AUTO_STOP_MINUTES | ☑️ | Minutes before auto-stop (set 0 to disable) | 0 or 5-60 (default: 10) |
| WARNING_MINUTES | ☑️ | Minutes before warning is posted | 0 to disable warning, otherwise below AUTO_STOP_MINUTES (default: 8) |
| CHECK_INTERVAL_MS | ☑️ | Auto-stop check interval in milliseconds | 10000-60000 (default: 30000) |
| SAVEALL_DELAY_MS | ☑️ | Delay between save-all and stop/restart | 500-3000 (default: 1000) |
| COMMAND_COOLDOWN_MS | ☑️ | Cooldown between start/stop/restart commands | 0 or 2000-60000 (default: 10000) |
| LOGS_SOURCE | ☑️ | Log source for /logs command | journalctl (default) or file |
| LOG_FILE_PATH | ☑️ | Path to log file, only used if LOGS_SOURCE=file | Default: ./logs/latest.log |
| LOG_SESSION_TIMEOUT_MS | ☑️ | Auto-stop timeout for /logs live sessions in milliseconds | 0 or <0 (no timeout) or 30000-300000 (default: 60000) |
| MC_LOG_PATH | ☑️ | Absolute path to Minecraft server's latest.log. Required for /logs download. Leave blank to disable | Absolute file path (default: empty). e.g. /home/minecraft/server/logs/latest.log |
| EXEC_TELLRAW_ENABLED | ☑️ | Enable in-game announcements for /exec commands | true (default) or false |
| EXEC_TELLRAW_TARGET | ☑️ | Minecraft target selector for tellraw announcements | Default: @a |
| EXEC_TELLRAW_COLOR | ☑️ | Minecraft color name for the announcement prefix | Default: light_purple |
| EXEC_TELLRAW_PREFIX | ☑️ | Prefix shown in-game before the announcement text | Default: [DISCORD] |
| EXEC_SILENT_COMMANDS | ☑️ | Comma-separated base commands that never produce a tellraw | Default: login,register |
| EXEC_LOG_PATH | ☑️ | Path for the JSONL execution log, appended on each /exec | Default: ./logs/exec.jsonl |
| PRESENCE_SYSTEMD_FALLBACK_INTERVAL_MS | ☑️ | Presence fallback interval while RCON is disconnected | 10000-30000 (default: 15000) |
| RCON_KEEPALIVE_INTERVAL_MS | ☑️ | Persistent RCON keepalive interval | 30000-60000 (default: 45000) |
| RCON_RECONNECT_INTERVAL_MS | ☑️ | Delay between RCON reconnect attempts | 3000-10000 (default: 5000) |
| RCON_STARTING_GRACE_PERIOD_MS | ☑️ | How long to keep "Server Starting..." state after reconnect | 5000-20000 (default: 10000) |
| RCON_COMMAND_TIMEOUT_MS | ☑️ | Timeout for individual RCON commands | 5000-15000 (default: 8000) |
| RCON_MAX_KEEPALIVE_FAILURES | ☑️ | Keepalive failures before forced reconnect | 1-3 (default: 2) |
| RCON_REFUSED_LOG_INTERVAL_MS | ☑️ | Throttle interval for repeated ECONNREFUSED logs | 0 or 30000-120000 (default: 60000) |
| LOG_LEVEL | ☑️ | Global log level | DEBUG, INFO, WARN, ERROR |
| DEBUG_PERMS | ☑️ | Enable detailed permission check logs | true or false (default: false) |
| MAIN_ADDRESS | ☑️ | Public server address for /address command | Any reachable public address (optional) |
| LOCAL_ADDRESS | ☑️ | LAN address for /address command | LAN address + port (optional) |
| JAVA_EDITION_VERSION | ☑️ | Java version text shown in /address | Any Java version label text (optional) |
| UPDATE_NOTIFY_CHANNEL_ID | ☑️ | Preferred channel for update announcement embeds | Discord channel ID (optional; has fallback behavior) |
| UPDATE_SERVICE_DEBUG | ☑️ | Enable mock latest-version override for testing | true or false (default: false) |
| UPDATE_SERVICE_FORCE_LATEST | ☑️ | Forced semver used as latest when update debug is enabled | Semver string, used when UPDATE_SERVICE_DEBUG=true |
Legend: ✅ = Required, ☑️ = Optional (defaults are used when omitted).
Permission Configuration
CraftDaemon uses a config-driven RBAC (Role-Based Access Control) layer for slash commands. All permission rules live in a single file: config/permission-config.js. There is no hardcoded permission logic inside the bot's command handlers — everything is driven by this config, making it easy to audit and customize without touching code.
Configuration Structure Reference
| Key | Type | Description | Example |
|---|---|---|---|
| owner | Array of user IDs | Discord user IDs that bypass all permission checks, no role required | ["123456789012345678"] |
| roles | Key-value map | Maps descriptive role names (uppercase by convention) to Discord role IDs | { ADMIN: "111111111111111111" } |
| commands | Key-value map | Maps permission strings to arrays of allowed role names | { "server.start": ["ADMIN", "MOD"] } |
| users | Key-value map | Per-user overrides mapping Discord user IDs to permission strings — takes precedence over role-based permissions | { "444444444444444444": ["admin.logs"] } |
| rolePriority | Array of role keys | Order of role precedence for /exec permission resolution. Checked left to right; the first matching role is used | ["ADMIN", "MOD"] |
| exec.allowlist | Key-value map | Maps role keys to arrays of allowed Minecraft commands. "*" = unrestricted (except blocked commands) | { MOD: ["say", "kick"], ADMIN: ["*"] } |
| exec.dangerousCommands | Array of command names | Minecraft commands that require a confirmation button before executing | ["stop", "op", "ban"] |
| exec.blockedCommands | Array of command names | Minecraft commands completely blocked from execution through /exec — no override possible | ["stop", "reload"] |
Permission Hierarchy
The RBAC system checks permissions in this order, returning the first match:
- Owners - always pass regardless of the command. If a user's ID is in the owner array, they are granted access immediately with no further checks. This is intended for the server administrator.
- User overrides - checked next. If the user's ID has an entry in users, their permission strings are checked directly. This takes precedence over any role assignments.
- Role-based - checked last. The bot fetches the user's Discord roles, matches them against the role keys defined in roles, and checks if any of the matched roles are listed for the required permission string.
Strict fail: If a permission string is not registered in the commands object at all, it is denied by default — not granted.
Permission Strings & Defaults
Each command declares a permission string that is checked against the config.
| Command | Permission String | Default Roles |
|---|---|---|
| /start | server.start | ADMIN, MOD |
| /stop | server.stop | ADMIN, MOD |
| /restart | server.restart | ADMIN, MOD |
| /status | server.status | ADMIN, MOD |
| /address | server.address | ADMIN, MOD |
| /checkupdate | bot.checkUpdate | ADMIN, MOD |
| /logs | admin.logs | ADMIN, MOD |
| /exec | admin.exec | ADMIN, MOD |
| /player | player.list | ADMIN, MOD |
| /player lookup | player.lookup | ADMIN, MOD |
| /help | (none) | Everyone |
| /ping | (none) | Everyone |
Exec-Specific Configuration
The /exec command extends the RBAC system with Minecraft-command-level controls. Three separate lists determine granular permission:
| Config Key | Purpose | Precedence |
|---|---|---|
| exec.allowlist | Defines which Minecraft commands each role can run. "*" grants access to all non-blocked commands. Owner gets "*" automatically via the RBAC owner override — no entry needed. | Lowest (but must pass to proceed) |
| exec.dangerousCommands | Commands that trigger a confirmation prompt (Confirm / Cancel buttons). Anyone with exec permission can confirm and run them. Useful for preventing accidental op, ban, or give calls. | Middle (checked after allowlist) |
| exec.blockedCommands | Commands completely prevented from running through /exec. No role can override this — it is a hard safety net. If a command appears in both dangerousCommands and blockedCommands, block takes precedence. | Highest (checked first, immediate reject) |
Setting Up RBAC
- Enable Developer Mode in Discord: User Settings → Advanced → Developer Mode
- Get your Discord user ID: right-click your name in the server member list → Copy User ID. Add this to the owner array.
- Get role IDs: Server Settings → Roles → right-click each role → Copy Role ID. Add these to the roles map with descriptive keys.
- Adjust commands to match your desired permission layout — each permission string maps to an array of role keys from step 3.
- If using /exec, configure the exec block: set rolePriority, define which Minecraft commands each role can run in allowlist, and review the safety lists.
- Test your setup with DEBUG_PERMS="true" in config/.env — this logs every permission check (user, command, allowed) to help you verify your rules.
Requirements
For role-based permission checks to work reliably, the bot must have:
- Gateway Intent: GuildMembers enabled in the bot's code (enabled by default in src/index.js)
- Discord Developer Portal: "Server Members Intent" checkbox enabled in the Bot section of your application settings
Without these, the bot cannot see the roles of guild members and role-based permission checks will fail.
DM Restrictions
Permissions only work inside guilds, interaction.inGuild() must be true. Direct messages (DMs) are denied regardless of what the permission config says.
Full Config Example
Here is a complete example with all sections. (Already supplied in project, no need to copy this.)
owner: ["123456789012345678"],
roles: {
ADMIN: "111111111111111111",
MOD: "222222222222222222",
},
commands: {
"server.start": ["ADMIN", "MOD"],
"server.stop": ["ADMIN", "MOD"],
"server.restart": ["ADMIN", "MOD"],
"server.status": ["ADMIN", "MOD"],
"server.address": ["ADMIN", "MOD"],
"bot.checkUpdate": ["ADMIN", "MOD"],
"admin.logs": ["ADMIN", "MOD"],
"admin.exec": ["ADMIN", "MOD"],
"player.list": ["ADMIN", "MOD"],
"player.lookup": ["ADMIN", "MOD"], // requires player.list as prerequisite
},
users: {
"444444444444444444": ["admin.logs"]
},
rolePriority: ["ADMIN", "MOD"],
exec: {
allowlist: {
MOD: ["say", "kick", "time", "weather", "list", "tell", "msg", "w", "me"],
ADMIN: ["*"],
},
dangerousCommands: [
"stop", "op", "deop", "whitelist off", "ban", "pardon",
"reload", "ban-ip", "clear", "summon", "give", "tp", "kill"
],
blockedCommands: ["stop", "reload"],
}
};
Set DEBUG_PERMS="true" in your config/.env to see detailed permission check logs. Each time a user runs a command, the bot will log the user, the required permission string, which role keys they matched, and whether access was allowed or denied. This is the easiest way to verify your RBAC setup without guessing.
Commands Reference
/start
Starts the Minecraft server by running systemctl start on your configured service. The bot replies with a status embed confirming the start was triggered.
- Permission: server.start
- Response: Confirmation embed with server startup status
- Notes: The server itself may take 10–30 seconds to fully boot depending on world size — this is normal. The command only triggers the start; it does not wait for the server to be ready.
/stop
Gracefully stops the server. Before stopping, CraftDaemon sends a save-all command via RCON to ensure no world data is lost, then calls systemctl stop.
- Permission: server.stop
- Response: Confirmation embed with stop status
- Notes: This is safe to use at any time — the save-all happens automatically before shutdown
/restart
Restarts the server by saving world data first, then calling systemctl restart. Useful after config changes or to apply updates without manually stopping and starting.
- Permission: server.restart
- Response: Confirmation embed with restart status
- Notes: Like /stop, this always performs a save-all before restarting
/status
Shows a full live snapshot of your server: systemd service state, uptime, TPS (ticks per second), online player count, and current RCON round-trip latency.
- Permission: server.status
- Response: Rich embed with all server metrics
- Requires: A Paper server for full metrics (TPS, vanilla and other servers will not show TPS)
- Latency: Shows RCON round-trip time, which reflects the connection health between the bot and your server
/address
Displays your server's connection details: public IP/domain, LAN address, and the Java edition version label. Useful for sharing with players.
- Permission: server.address
- Response: Embed with connection details
- Requires: MAIN_ADDRESS and LOCAL_ADDRESS set in config/.env — the command still works without them but will show empty fields
/ping
Checks the bot's connection health by measuring Discord API round-trip latency. Does not interact with the Minecraft server.
- Permission: No restriction — all Discord members can use this
- Response: Simple message showing API latency in milliseconds
- Notes: Useful for confirming the bot is alive and responsive if other commands seem slow
/checkupdate
Fetches the latest release from GitHub and compares it to the version currently running. If a newer version is available, the full changelog is displayed in the embed.
- Permission: bot.checkUpdate
- Response: Embed showing current version vs latest GitHub release
- If newer: Displays the full changelog from the release notes
/help
A comprehensive help system with two modes: an interactive command reference and per-command deep dives with autocomplete.
/help (no options)
Shows an ephemeral embed with a StringSelectMenu to switch between two pages:
- Commands Reference - all commands grouped by category (Server Control, Admin & Tools, Players, Information) with permission lock indicators
- Quick Start Guide - step-by-step getting-started guide (start → status → address → logs → player → exec → stop/restart)
The select menu expires after 2 minutes of inactivity and auto-disables.
/help command:<name>
Shows detailed information for a specific command including description, exact usage syntax, in-depth behavior notes, examples, the required permission string, and important caveats. Supports autocomplete — start typing a command name and suggestions appear.
- Permission: No restriction - all Discord members can use this
- Response: Ephemeral embed with full command details
- Notes: Ideal for discovering commands or checking syntax without leaving Discord
/logs
Streams live Minecraft server logs directly in Discord, fetches recent lines as a static snapshot, or downloads the log file as an attachment. Logs are sourced from journalctl by default, with a file fallback for non-systemd setups.
Command Syntax
/logs tail [lines:number]
/logs download
/logs stop
Subcommands
| Subcommand | Description |
|---|---|
| live | Default. Opens a live-updating message that streams log lines in real time. A journalctl -f or tail -f child process is spawned and its output is collected via a rotating buffer. The message is edited every 2 seconds with new content. The session auto-stops after the configured timeout and the child process is killed. |
| tail | One-shot fetch of the last N lines (default 15, adjustable via the lines option). Returns a static code block embed immediately. No live session or timeout involved. |
| stop | Stops the active live log session in the current channel. Shows a warning if no session is running. On success, the child process is killed, the edit interval is cleared, and the Discord message is marked --- [Stopped by user.] ---. |
| download | Sends the Minecraft server's latest.log as a Discord file attachment. If the file is under 7MB it is sent as plain text (latest.log.txt); if over 7MB it is automatically gzip-compressed and sent as latest.log.gz. Requires MC_LOG_PATH to be set in config/.env. |
Code Examples
/logs live → explicitly live
/logs tail → last 15 lines as a static embed
/logs tail lines:50 → last 50 lines
/logs download → download latest.log as a file attachment
/logs stop → stops active live session
Live Session Behavior
- Rotating buffer: Only the 25 most recent lines are kept and displayed
- Edit cadence: The Discord message is updated every 2 seconds (debounced — skipped if no new lines since last edit), well under Discord's 5-edits-per-5-seconds rate limit
- One session per channel: If a user already has a live session running in a channel, other users in that channel see a warning instead of starting a second session
- Auto-timeout: Sessions expire after the configured LOG_SESSION_TIMEOUT_MS (default 60s). Set to 0 or a negative value for no timeout. On timeout, the child process is killed, the edit interval is cleared, the session is deleted, and the message is updated with a
[session ended]notice - Permission: admin.logs
- Log source: Controlled by LOGS_SOURCE in config/.env (journalctl or file)
- Stop command: Use /logs stop to end a live session early instead of waiting for the timeout
Under the Hood
The command uses child_process.spawn() to run journalctl -f -u <service> --output=short --no-pager (or tail -f <logfile> for file mode). Lines are collected from the stdout data event, pushed into a rotating buffer, and the session manager handles lifecycle (start, stop, timeout, cleanup). All child processes are guaranteed to be killed when a session ends to avoid orphans.
/exec
Sends Minecraft server commands through RCON directly from Discord. Built as a full administration tool with accountability, safety checks, and in-game visibility.
Command Syntax
Code Examples
/exec command:weather clear → clears the weather
/exec command:gamemode creative @p → sets the nearest player to creative
/exec command:kick Steve You are banned! → kicks a player with a reason
/exec silent:true command:gamerule doDaylightCycle false → silently changes a gamerule (Admin/Owner only)
Execution Flow
- User runs /exec with a Minecraft command
- Bot validates permissions against the exec allowlist in permission-config.js
- Bot checks if the base command is in blockedCommands — hard blocked, no override
- If in dangerousCommands, a confirmation button prompt is sent
- If confirmed (or not dangerous), the bot optionally injects a tellraw for in-game visibility
- The command is sent through RCON and the result is returned as a Discord embed
- Every execution is logged to EXEC_LOG_PATH as a JSONL line
Safety System
Two independent safety lists in permission-config.js protect your server:
| List | Behavior | Who Can Override |
|---|---|---|
| dangerousCommands | Prompts for a confirmation button before executing | Anyone with exec permission (after confirming) |
| blockedCommands | Completely prevented — no one can run these through /exec | No one (hard block) |
Confirmation Prompts
When a dangerous command is detected, the bot sends a message with two Discord buttons:
- ✅ Confirm — executes the command
- ❌ Cancel — aborts and removes the pending confirmation
Pending confirmations expire after 60 seconds. The interaction collector listens for button clicks from the original user only.
Tellraw Injection
When enabled (default), executed commands are announced in-game with a tellraw message showing who executed what. Configuration options in config/.env:
- EXEC_TELLRAW_ENABLED - master toggle
- EXEC_TELLRAW_TARGET - who sees the message (default: @a)
- EXEC_TELLRAW_COLOR - Minecraft color name (default: light_purple)
- EXEC_TELLRAW_PREFIX - prefix text (default: [DISCORD])
- EXEC_SILENT_COMMANDS - base commands that skip tellraw entirely (default: login,register)
Silent Mode
The silent:true option suppresses the tellraw announcement. This is restricted to Admin and Owner roles only, preventing lower roles from secretly executing commands without in-game visibility.
Command Autocomplete
As you type a Minecraft command in the command field, Discord shows real-time suggestions with argument completion. The autocomplete system uses a two-layer architecture:
- Data layer (commandTree.js) - a static tree describing Minecraft command argument structure, covering ~30 commands with support for argument types: literal, player (via RCON list with 8s cache), selector, item (~80 curated items), freetext, and number
- Smart walker (commandAutocomplete.js) - parses the user's input, traverses the tree, resolves suggestions, applies RBAC filtering, and formats output for Discord
RBAC integration: Blocked commands (stop, reload, save-all) never appear in suggestions for any user. Allowlist commands are filtered to only show what the user's role can execute.
Context-aware branching: Arguments can depend on previous values. For example, gamerule alone suggests rule names; gamerule keepInventory suggests true/false; gamerule randomTickSpeed suggests a numeric input.
Commands covered (Will add more data as we update.): time, gamemode, difficulty, weather, gamerule, kick, ban, ban-ip, pardon, op, deop, kill, tp, teleport, say, tell, msg, w, give, clear, enchant, xp, experience, whitelist, scoreboard, effect, summon, spawnpoint, setworldspawn, seed, list, save, save-all, save-on, save-off, reload, stop, playsound, stopsound, particle, tag, team, function, schedule, forceload, debug, help, publish, loot, item, damage, ride, random.
Exec Feedback Embed
The bot returns an embed with the following fields for every execution attempt:
| Field | Content |
|---|---|
| Executor | Discord username who ran the command |
| Command | The full Minecraft command executed |
| Result | ✅ Success or ❌ Failure with the RCON response |
| Timestamp | When the command was executed |
/player
Shows a leaderboard-style embed of every player the bot has ever seen. Online players are listed first, followed by offline players sorted by most recently seen. Footer shows X online • Y known. Automatically truncated if the description exceeds Discord's 3800-character limit. Data is collected passively — every RCON keepalive tick (every 45 seconds by default), the list response is parsed and the player store is updated. No server mods needed.
- Permission: player.list
- Response: Leaderboard embed with online/offline status and last-seen timestamps
- Notes: If no player data exists yet (bot just started, no one has joined), returns an embed explaining that data is collected as players join
/player lookup:<name>
Looks up a specific player by exact name. Autocomplete suggests known player names as you type, sorted online-first with emoji indicators (🟢 online, ⚫ offline).
For Online Players
Five sequential RCON queries fetch live data:
| Field | RCON Query | Example Response |
|---|---|---|
| Position | data get entity <name> Pos | [123.5d, 64.0d, -456.7d] |
| Health | data get entity <name> Health | 20.0f |
| Food | data get entity <name> foodLevel | 20b |
| XP Level | data get entity <name> XpLevel | 3 |
| Game Mode | data get entity <name> playerGameType | 0 (Survival) |
Commands are sent sequentially (not in parallel) to avoid destabilizing the persistent RCON connection. Each field degrades gracefully — if one query fails, the rest still display.
For Offline Players
The stored record is shown with a warning that live data is unavailable. If RCON is disconnected entirely, the same stored data is shown with an appropriate warning.
Name Validation
Lookup input is validated against ^[a-zA-Z0-9_]{1,16}$ (Minecraft username rules) to prevent injecting arbitrary RCON commands.
Edge Cases
| Case | Handling |
|---|---|
| Player not in store | "No record found for that player name. Names are case-sensitive." |
| Player online but RCON returns error | "Could not fetch live data. Server may be restarting." Shows stored data only. |
| RCON not connected | "RCON is unavailable. Cannot fetch live player data." |
| NBT field missing from response | Shows that field as "Unknown" rather than crashing |
- Permission: player.lookup (requires player.list as prerequisite)
- Response: Detail embed with live stats (online) or stored data (offline)
- Autocomplete: Suggests known player names sorted online-first
player.list is a prerequisite for player.lookup. A role that has player.lookup but not player.list will be blocked by the middleware before the command even executes. This is intentional: you may want users to see the leaderboard without giving them access to detailed live player data.
Player Store
The player store (logs/players.json) is a JSON file persisted automatically from every RCON keepalive tick. It tracks every player the bot has ever seen with their online state, first seen, and last seen timestamps:
"Steve": {
"firstSeen": "2026-05-01T10:00:00.000Z",
"lastSeen": "2026-05-24T18:30:00.000Z",
"online": false
},
"Alex": {
"firstSeen": "2026-05-10T14:00:00.000Z",
"lastSeen": "2026-05-24T20:00:00.000Z",
"online": true
}
}
- Created automatically on first write; directory is created if missing
- updatePlayers() is called on every keepalive tick (~45s); disk write is queued asynchronously and non-blocking
- Store file path: ./logs/players.json (alongside exec.jsonl, consistent with existing bot file conventions)
Permissions & RBAC
How Permissions Work
CraftDaemon uses a role-based access control (RBAC) system defined in config/permission-config.js. The idea is simple: each bot command has a permission key (e.g. server.start), and you decide which Discord roles can use it. You never configure permissions inside Discord itself — everything is in that one JS file on your server.
Available Permission Keys
| Permission Key | Controls |
|---|---|
| server.start | /start command |
| server.stop | /stop command |
| server.restart | /restart command |
| server.status | /status command |
| server.address | /address command |
| bot.checkUpdate | /checkupdate command |
| admin.logs | /logs command |
| admin.exec | /exec command |
| player.list | /player command (also a prerequisite for /player lookup) |
| player.lookup | /player lookup option (requires player.list) |
| — | /help, /ping (public — no permission required) |
Example Configuration
Here's a typical setup with Owner, Admin, and Moderator roles. Replace the placeholder IDs with your real ones (see "Finding Discord IDs" below):
owner: ["OWNER_USER_ID"],
roles: {
ADMIN: "ADMIN_ROLE_ID",
MOD: "MODERATOR_ROLE_ID"
},
commands: {
"server.start": ["ADMIN", "MOD"],
"server.stop": ["ADMIN", "MOD"],
"server.restart": ["ADMIN"],
"server.status": ["ADMIN", "MOD"],
"server.address": ["ADMIN", "MOD"],
"bot.checkUpdate":["ADMIN"],
"admin.logs": ["ADMIN", "MOD"],
"admin.exec": ["ADMIN", "MOD"],
"player.list": ["ADMIN", "MOD"],
"player.lookup": ["ADMIN", "MOD"]
},
users: {
"YOUR_USER_ID": ["server.start"]
}
};
Finding Discord IDs
Discord IDs are long numeric strings that identify users, roles, and servers. To copy them, you first need to enable Developer Mode in Discord:
- Open Discord → User Settings (gear icon) → Advanced → enable Developer Mode
- To get a Role ID: go to Server Settings → Roles, then right-click the role and select "Copy Role ID"
- To get a User ID: right-click any user in the server member list and select "Copy User ID"
- To get your Guild (Server) ID: right-click the server name/icon and select "Copy Server ID"
Users listed in the owner array can run any command regardless of what's in commands. This is intended for the server admin — typically just your own Discord user ID.
Features Guide
Auto-Shutdown
CraftDaemon can automatically shut down your Minecraft server when no players have been online for a specified duration. This is especially useful if you're paying for a VPS or cloud server — no one online means no point running it.
Configuration (in config/.env):
- AUTO_STOP_MINUTES - how many idle minutes before the server stops. Set to 0 to disable auto-shutdown entirely
- WARNING_MINUTES - how many minutes before shutdown to post a warning message in your status channel. Must be less than AUTO_STOP_MINUTES
- CHECK_INTERVAL_MS - how often (in milliseconds) the bot checks if the server is idle (default: 30000 = every 30 seconds)
How it works, step by step:
- The last player leaves the server - player count hits 0
- The idle countdown timer starts
- When WARNING_MINUTES remain, the bot posts a warning in the status channel
- When the full AUTO_STOP_MINUTES expires, CraftDaemon automatically runs /stop (saves world first)
Live Bot Presence
CraftDaemon updates your bot's Discord status in real time to reflect your server state. Anyone in your Discord server can glance at the bot in the member list and immediately know if the server is up without running a command.
Presence states:
- 🔴 Do Not Disturb - Server is offline
- 🟡 Idle - Server is starting up
- 🟢 Online - Shows live player count (e.g. "3 player(s) online")
RCON Manager
CraftDaemon maintains a persistent RCON connection to your Minecraft server rather than opening a new one for every command. This makes queries faster and allows the bot to detect connection drops and recover automatically.
What this handles for you:
- Automatic reconnection if the RCON connection drops (e.g. after a server restart)
- Command queueing so requests don't fail if sent during a brief reconnect window
- Live TPS and player count polling for presence updates
- RCON round-trip latency monitoring shown in /status
Update Notifications
Run /checkupdate at any time to see if a newer version of CraftDaemon is available. The bot fetches the latest release tag from GitHub and compares it to the version currently running. If an update is available, the full changelog from the release notes is shown in the response embed so you know exactly what changed before updating.
Live Logs (/logs)
The /logs command streams Minecraft server logs directly into Discord, giving authorized users real-time visibility into server activity without needing SSH access. It supports four subcommands: live (real-time streaming), tail (static snapshot), download (file attachment), and stop (end active session).
How live mode works:
- The bot spawns a journalctl -f (or tail -f) child process
- New log lines are collected via the stdout data event and pushed into a rotating 25-line buffer
- Every 2 seconds, the bot checks if the buffer changed since the last edit — if so, it edits the Discord message with the latest lines
- After the configured LOG_SESSION_TIMEOUT_MS (default 60s), the session expires: the child process is killed, the edit interval is cleared, and the message is marked
[session ended]. Use /logs stop to end a session early
Configuration: Set LOGS_SOURCE in config/.env to journalctl (default) or file. If using file mode, set LOG_FILE_PATH to the path of your latest.log. The default journalctl source automatically uses your configured MC_SERVICE.
Session guard: Only one active live session is allowed per channel at a time. This prevents conflicting edit loops from multiple users in the same channel.
Log Download (/logs download)
Sends the Minecraft server's latest.log as a Discord file attachment. Useful for sharing logs for debugging or review without SSH access.
- Files under 7MB: Sent as plain text (latest.log.txt)
- Files over 7MB: Automatically gzip-compressed and sent as latest.log.gz. Extract with any archive tool
- Requires: MC_LOG_PATH set in config/.env - must be an absolute path to your server's latest.log
- Permission: Reuses admin.logs - no new RBAC entry needed
- Temp file: Gzip compression uses os.tmpdir() (/tmp on Linux). Temp file is deleted immediately after Discord upload. No cleanup needed on crash - the OS manages /tmp
Add this to your config/.env:
If left blank, /logs download will return a configuration error embed. The bot prints the configured value (or "Not configured") at startup.
Player Visibility (/player)
Track every player who has ever joined your server. The player store is populated automatically from every RCON keepalive tick, no server mods or plugins needed.
/player (list view): Shows a leaderboard-style embed with all known players. Online players listed first with 🟢, offline players sorted by most recently seen with ⚫. Footer shows online count and total known players.
/player lookup:<name>: Detailed view for a specific player with autocomplete suggestions. For online players, five sequential RCON queries fetch position, health, food, XP level, and game mode. For offline players, stored data is shown with a warning. Name input is validated against Minecraft username rules.
Player store: Data is persisted to logs/players.json, tracking first seen, last seen, and online status for each player. The store is updated on every RCON keepalive tick (~45s by default) and disk writes are non-blocking.
Remote Command Execution (/exec)
The /exec command turns Discord into a remote administration console for your Minecraft server. Every command execution is logged, optionally announced in-game, and protected by configurable safety lists.
Architecture
↓
Permission check (allowlist)
↓
Blocked command check
↓
Dangerous command check → confirmation prompt
↓
Tellraw injector (unless silent or in silent list)
↓
Execute via RCON
↓
Log to exec.jsonl
↓
Return feedback embed
Exec Allowlist
The exec permission system extends the existing RBAC with Minecraft-command-level granularity. Defined in config/permission-config.js under exec.allowlist, it maps roles to arrays of allowed Minecraft commands. The rolePriority array determines role resolution order. A value of "*" grants access to all non-blocked commands.
Safety Lists
Two lists protect your server from accidental or malicious commands:
- dangerousCommands: Commands that need explicit confirmation before they run (e.g. ban, op, give). Anyone with exec permission can confirm and run them.
- blockedCommands: Commands that cannot be executed through /exec at all, regardless of role. Even owners cannot override this without editing the config.
Execution Logging
Every /exec command is logged as a JSONL line to ./logs/exec.jsonl (configurable via EXEC_LOG_PATH). Each entry captures who, what, and when — enabling audit trails and future analytics:
Troubleshooting
Bot Won't Start
The bot can't find your Discord token. Make sure config/.env exists (not just config/.env.example) and that TOKEN= is set. Create it with cp config/.env.example config/.env, then edit it with nano config/.env.
Check three things: (1) enable-rcon=true is set in server.properties, (2) the RCON_PASSWORD in your .env exactly matches rcon.password in server.properties, and (3) the Minecraft server is actually running (sudo systemctl status minecraft).
Commands Not Working
Run node src/register-commands.js again. Make sure GUILD_ID and CLIENT_ID in your .env are correct. Commands can take up to a minute to appear in Discord after registration.
Check config/permission-config.js. Verify that your Discord role ID or user ID is correctly entered — IDs are 18–19 digit numbers. Open it with nano config/permission-config.js and double-check the values. You can also set DEBUG_PERMS=true in your .env to see detailed permission check logs.
Server Control Issues
This is almost always a sudoers issue. Run sudo visudo and verify the bot user has the correct NOPASSWD rule for systemctl commands. Also confirm the service name in the sudoers rule matches MC_SERVICE in your .env (default is minecraft).
Full status data (TPS) requires a Paper server. Also verify RCON is connected by checking the bot logs.
Player & Log Download Issues
Player data is collected automatically from the RCON keepalive. If no one has joined the server since the bot started, the store will be empty. Make sure RCON is connected and the keepalive is running. Check the bot logs for RCON connection errors.
Add MC_LOG_PATH to your config/.env with the absolute path to your Minecraft server's latest.log. For example: MC_LOG_PATH=/home/minecraft/server/logs/latest.log. Restart the bot after adding it.
Verify the path in MC_LOG_PATH is correct and the file exists. Make sure it's an absolute path (starting with / on Linux). The Minecraft server must have run at least once to generate latest.log.
Checking Logs
If CraftDaemon is running as a systemd service, view its live logs with:
The -f flag follows the log in real time (like tail -f). Press Ctrl+C to exit. If you want to run the bot directly to see output in your terminal instead (useful for debugging), stop the service first and run:
node src/index.js
For Minecraft server logs:
Architecture
System Overview
CraftDaemon sits between Discord and your Minecraft server, translating slash commands into systemd and RCON calls:
↓
↓ /slash command
↓
CraftDaemon Bot ←→ systemctl start/stop/restart ←→ Minecraft (systemd)
↑ ↓
←→ RCON 127.0.0.1:25575 ←→ RCON Server
↓
←→ playerStore (logs/players.json) ← auto-updated on RCON keepalive
Directory Structure
Here's the full layout of the repository and what each file/folder of the bot does:
├── config/
│ ├── permission-config.js # RBAC rules (owners/roles/command permissions)
│ └── .env.example # Environment variable template
├── src/
│ ├── index.js # Bot entry: client, RconManager, presence, auto-stop, command loader
│ ├── register-commands.js # Guild slash registration (reads src/commands/*.js)
│ ├── commands/ # One file per slash command (data + permission + execute)
│ │ ├── ping.js
│ │ ├── start.js
│ │ ├── stop.js
│ │ ├── restart.js
│ │ ├── address.js
│ │ ├── status.js
│ │ ├── checkUpdate.js
│ │ ├── logs.js # /logs - live log streaming, tail snapshot, and file download
│ │ ├── exec.js # /exec - remote command execution
│ │ └── player.js # /player - player list and lookup with autocomplete
│ ├── events/
│ │ └── interactionCreate.js # Slash dispatch: RBAC middleware → command.execute()
│ ├── permissions/
│ │ ├── index.js
│ │ ├── middleware.js # permissionMiddleware (ephemeral deny)
│ │ └── resolver.js # hasPermission() against permission-config.js
│ ├── utils/
│ │ ├── env.js # Shared typed env parser (getEnvInt, getEnvString, etc.)
│ │ ├── playerStore.js # JSON persistence for player data (online/offline tracking)
│ │ └── storage.js # JSON persistence for per-guild update-notification state
│ └── services/
│ ├── autoStopService.js # Auto-Stop/Auto-Shutdown logic and handler
│ ├── updateService.js # GitHub release polling, ETag cache, update embed delivery
│ ├── rconManager.js # Persistent RCON connection lifecycle + command pipeline
│ ├── rconQuery.js # Command-facing RCON helpers (wired after clientReady)
│ ├── minecraftSystemd.js # systemctl + save-all before stop/restart
│ ├── commandLock.js # Cooldown lock for start/stop/restart
│ ├── logger.js # Structured logging utility used across bot modules
│ ├── logsServices/ # Live log streaming for /logs command
│ │ ├── logStream.js # Spawns journalctl/tail, manages the child process
│ │ ├── sessionManager.js # Active sessions Map, session lifecycle (start/stop/expire)
│ │ └── logBuffer.js # Rotating buffer logic, MAX_LINES trimming
│ └── execServices/ # Remote command execution for /exec command
│ ├── executeCommand.js # Centralized RCON execution with middleware pipeline
│ ├── commandLogger.js # JSONL logging for every executed command
│ ├── permissions.js # Exec-specific permission resolution against allowlist
│ ├── blacklist.js # Dangerous + blocked command safety checks
│ ├── confirmations.js # Confirmation button prompts with expiry
│ ├── tellrawInjector.js # In-game announcement via tellraw
│ ├── autocomplete/ # Minecraft command autocomplete system
│ │ ├── commandTree.js # Static command argument structure tree
│ │ └── commandAutocomplete.js # Input parser + suggestion resolver + RBAC filter
├── package.json
└── README.md
The src/ layout is the intended structure, but the bot isn't rigid about it. If you prefer running index.js from the project root, that works too, as long as the paths in your systemd ExecStart point to the right place AND your file imports are correct.
Key Services
- logger.js: Structured logging with category prefixes, timestamps, and color-coded log levels. Used across all modules — see the Logging section for full details
- rconManager.js: Persistent RCON connection lifecycle — handles connect, keepalive pinging, automatic reconnect on failure, and the command pipeline
- rconQuery.js: Command-facing RCON helpers (e.g. player list, TPS queries) that are wired up after the client is ready
- minecraftSystemd.js: Wraps systemctl calls with a save-all flow before stop/restart operations to prevent data loss
- updateService.js: Polls the GitHub Releases API with ETag caching to compare the running version against the latest tag; delivers update embeds to a configured channel
- commandLock.js: Cooldown guard that prevents rapid repeated start/stop/restart calls within the configured COMMAND_COOLDOWN_MS window
- middleware.js / resolver.js: RBAC enforcement layer — every slash command interaction passes through permissionMiddleware before command.execute() is called
- storage.js: Lightweight JSON persistence for per-guild state (e.g. update notification tracking)
- env.js: Shared typed environment variable parser providing getEnvInt, getEnvString, getEnvBool, and getEnvFloat with min/max clamping — used by 10+ services to replace duplicate process.env access
- playerStore.js: JSON-backed persistence for player data. Tracks every player the bot has ever seen via RCON keepalive ticks — stores first seen, last seen, and online status. Powers the /player command and /player lookup autocomplete
- logsServices/ (logStream.js, sessionManager.js, logBuffer.js): Powers the /logs command — spawns journalctl/tail child processes, manages live session lifecycle (start, stop, 60-second timeout), maintains a rotating line buffer for Discord-safe edits, and handles /logs download file attachment with automatic gzip compression for large logs
- execServices/ (executeCommand.js, commandLogger.js, permissions.js, blacklist.js, confirmations.js, tellrawInjector.js, autocomplete/): Powers the /exec command — centralized RCON execution with safety middleware pipeline, JSONL audit logging, role-based allowlist resolution, dangerous/blocked command checks, confirmation button prompts with expiry, in-game tellraw injection, and Minecraft command autocomplete with full argument completion via commandTree.js data layer and commandAutocomplete.js smart walker
Managing Services
Once both CraftDaemon and your Minecraft server are running, you'll mostly interact with them through Discord slash commands. But you'll occasionally need to manage things directly from the server, here's a reference for every useful command.
systemctl — Controlling Services
These commands let you start, stop, and inspect both the bot and the Minecraft server as systemd services. Replace craftdaemon with minecraft (or whatever you named your service) where applicable.
sudo systemctl status craftdaemon
sudo systemctl status minecraft
# Start a service
sudo systemctl start craftdaemon
# Stop a service
sudo systemctl stop craftdaemon
# Restart a service (stop then start)
sudo systemctl restart craftdaemon
# Enable a service to start automatically on boot
sudo systemctl enable craftdaemon
# Disable autostart on boot
sudo systemctl disable craftdaemon
# Reload systemd after creating or editing a .service file
sudo systemctl daemon-reload
journalctl — Reading Logs
journalctl reads logs written by systemd services. These are your main tool for debugging when something isn't working as expected.
journalctl -u craftdaemon -n 50 --no-pager
# Follow live logs in real time (like tail -f) — press Ctrl+C to exit
journalctl -u craftdaemon -f
# Follow live logs for the Minecraft server
journalctl -u minecraft -f
# View logs since the last boot only
journalctl -u craftdaemon -b
# View logs from a specific time window
journalctl -u craftdaemon --since "1 hour ago"
Open two terminals: run journalctl -u craftdaemon -f in one, and reproduce the issue in the other. You'll see exactly what the bot is doing in real time as it happens.
Logging
CraftDaemon uses a structured logging system with category-based prefixes, timestamps, and color-coded log levels. Every log line tells you when something happened, which component produced it, and how severe it is — making it much easier to trace issues across the bot.
Log Format
Every log entry follows this structure:
For example:
12:35:47 [RCON] [DEBUG] RCON response received: There are 0 of a max of 20 players online
12:36:02 [AutoStop] [WARN] Server has been empty for 8 minutes
12:36:12 [SystemD] [ERROR] Failed to start server: Permission denied
Log Categories
| Category | Color | What it covers |
|---|---|---|
| [Bot] | Cyan | General bot lifecycle and startup configuration |
| [Discord] | Blue | Discord.js client events and API interactions |
| [Minecraft] | Red | Minecraft server-specific events |
| [RCON] | Magenta | RCON connection lifecycle, commands sent, responses received |
| [AutoStop] | Yellow | Auto-shutdown timer events and idle detection |
| [SystemD] | White | systemctl calls — start, stop, restart, status |
| [Player] | Green | Player management — lookups, store updates, autocomplete |
Log Levels
| Level | Color | When you'll see it |
|---|---|---|
| [DEBUG] | Gray | Detailed internal info (RCON commands, reconnect attempts). Hidden by default — enable with LOG_LEVEL=DEBUG |
| [INFO] | Green | Normal operation - bot started, server started/stopped, commands executed |
| [WARN] | Yellow | Something worth noticing but not broken - RCON refused while server starts, idle warning sent |
| [ERROR] | Red | Something failed and needs attention - permission denied, unhandled exception |
Changing the Log Level
By default the log level is INFO, which hides DEBUG output. To change it, set LOG_LEVEL in your config/.env:
Valid values: DEBUG, INFO, WARN, ERROR. No code changes or restarts of systemd are needed beyond restarting the bot itself (sudo systemctl restart craftdaemon).
Real Log Examples
Startup configuration summary — printed every time the bot comes online:
03:15:30 [Bot] [INFO] RCON Host: 127.0.0.1
03:15:30 [Bot] [INFO] RCON Port: 25575
03:15:30 [Bot] [INFO] Minecraft Service: minecraft-service.service
03:15:30 [Bot] [INFO] Auto-stop enabled: Yes (10 min idle, warning at 8 min)
03:15:30 [Bot] [INFO] Status channel ID: 1404683867265489235
03:15:30 [Bot] [INFO] Main address: my-minecraft-server.joinmc.link
03:15:30 [Bot] [INFO] =============================================
03:15:31 [Discord] [INFO] ✅ CraftDaemon is online.
03:15:31 [Discord] [INFO] Logged in as CraftDaemon#2232
03:15:31 [SystemD] [INFO] Managing systemd service: minecraft-server.service
A slash command received from Discord:
RCON refused while the server is starting up — the bot throttles repeated refusal logs so they don't flood your output:
12:33:41 [RCON] [WARN] RCON connection refused (server may be offline/starting). Retrying every 5s. (+11 similar refusals suppressed) [connect ECONNREFUSED 127.0.0.1:25575]
Auto-stop engaging — the full idle → warning → shutdown sequence:
03:25:00 [AutoStop] [WARN] Server empty for 8.0 minutes. Warning sent (2 min until shutdown).
03:27:00 [AutoStop] [INFO] Server empty for 10.0 minutes (threshold: 10). Initiating shutdown.
Adding Logging to Custom Code
If you're extending CraftDaemon with your own modules, you can use the same logger. Import createLogger and give your component a category name:
const myLogger = createLogger('MyComponent');
myLogger.info("Something happened");
myLogger.warn("This might be a problem");
myLogger.error("An error occurred: " + err.message);
myLogger.debug("Detailed debug info — only visible with LOG_LEVEL=DEBUG");
Logging is lightweight and designed for production use. DEBUG-level calls are completely skipped (not just filtered) when the log level is set to INFO or higher, so there's no performance penalty for leaving debug calls in your code.
Contributing
Want to Help?
CraftDaemon is a solo project and contributions are very welcome! Whether it's bug fixes, new features, documentation improvements, or just suggestions — any input is appreciated.
How to Contribute
- Fork the repository on GitHub (click "Fork" on the repo page)
- Clone your fork locally: git clone https://github.com/YOUR_USERNAME/CraftDaemon
- Create a feature branch: git checkout -b feature/my-feature
- Make your changes
- Test thoroughly — make sure existing functionality still works
- Commit with a clear message: git commit -m "Add my feature"
- Push to your fork: git push origin feature/my-feature
- Open a Pull Request on GitHub against the main repo
You should open an issue first for substantial, larger changes, or breaking changes, so we can align before you put work in.
Reporting Bugs
Found a bug? Open an issue on GitHub Issues. A good bug report includes:
- A clear description of what went wrong
- Steps to reproduce the issue
- What you expected to happen vs what actually happened
- Your environment details: Linux distro, Node.js version (node --version), Minecraft server type
- Relevant log output from journalctl -u craftdaemon if applicable
Code Standards
- Use consistent indentation (2 spaces)
- Write descriptive variable and function names
- Add comments for any complex or non-obvious logic
- Test your changes before submitting
- Follow the existing code style throughout the project
- JSDoc comments
License
CraftDaemon is licensed under the MIT License. By contributing, you agree that your contributions will be licensed under the same license.
The CraftDaemon name and logo are not covered by the license.
Feel free to open a GitHub Discussion or Issue if you have questions about contributing. There's no such thing as a dumb question here, so don't hesitate to reach out.
If CraftDaemon has been useful to you, consider giving it a ⭐ on GitHub! It helps others discover the project and means a lot to the development. Thanks for using it.