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
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.
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 (no timeout) or 30000-300000 (default: 60000) |
| 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 |
| /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. Replace all placeholder IDs with your real Discord IDs:
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"],
},
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, Information) with permission lock indicators
- Quick Start Guide — step-by-step getting-started guide (start → status → address → logs → 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, or fetches recent lines as a static snapshot. Logs are sourced from journalctl by default, with a file fallback for non-systemd setups.
Command Syntax
/logs tail [lines:number]
/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.] ---. |
Code Examples
/logs live → explicitly live
/logs tail → last 15 lines as a static embed
/logs tail lines:50 → last 50 lines
/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 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 autocomplete suggestions from a static list of base commands (e.g. time, weather, gamemode, tp, give, etc.). This list is version-pinned to the Minecraft version the bot was built against.
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 |
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 |
| — | /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"]
},
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 three subcommands: live (real-time streaming), tail (static snapshot), 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.
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.
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
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 and tail mode
│ │ └── exec.js # /exec - remote command execution
│ ├── 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/
│ │ └── 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
│ └── commandAutocomplete.js # Minecraft command autocomplete for Discord
├── 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)
- 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), and maintains a rotating line buffer for Discord-safe edits
- execServices/ (executeCommand.js, commandLogger.js, permissions.js, blacklist.js, confirmations.js, tellrawInjector.js, commandAutocomplete.js): 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
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 |
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.