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)
ℹ️ Paper vs Other servers

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.

ℹ️ Operating System Compatibility

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.

⚠️ Node Version Note

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:

git clone https://github.com/d1vid3d/CraftDaemon
cd CraftDaemon

Step 2: Install Node.js Dependencies

This installs all required packages listed in package.json. Run this from inside the CraftDaemon directory:

npm install

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.

  1. Go to Discord Developer Portal at discord.com/developers/applications
  2. Click "New Application" and give it a name (e.g., "CraftDaemon")
  3. Navigate to the "Bot" section in the left sidebar and click "Add Bot"
  4. 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.
  5. Scroll down to "Privileged Gateway Intents" and enable both:
    • Server Members Intent
    • Message Content Intent
  6. Go to "OAuth2""URL Generator" in the left sidebar. Under "Scopes", check bot and applications.commands
  7. Under "Bot Permissions", check: Send Messages, Embed Links, Read Message History
  8. Copy the generated URL at the bottom of the page and open it in your browser to invite the bot to your Discord server
⚠️ Token Security

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:

sudo nano /etc/systemd/system/minecraft.service

Paste the following content into the file, then save and exit (Ctrl+O, Enter, Ctrl+X in nano):

[Unit]
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 daemon-reload
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:

sudo nano /home/minecraft/server/server.properties

Find and update (or add) these three lines:

enable-rcon=true
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:

sudo systemctl restart minecraft

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:

cp config/.env.example config/.env

Then open it for editing:

nano config/.env

Fill in your values. At minimum, set these required fields:

TOKEN=your_bot_token_here
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:

node src/register-commands.js
ℹ️ When to re-run command registration

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:

sudo visudo

Scroll to the bottom of the file and add this line, replacing botuser with the Linux username the bot runs as:

botuser ALL=(ALL) NOPASSWD: /bin/systemctl start minecraft, /bin/systemctl stop minecraft, /bin/systemctl restart minecraft, /bin/systemctl show minecraft

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.

🔒 Least Privilege

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:

sudo nano /etc/systemd/system/craftdaemon.service

Paste the following, then update User and WorkingDirectory to match your setup:

[Unit]
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 daemon-reload
sudo systemctl enable craftdaemon
sudo systemctl start craftdaemon
ℹ️ Node version note

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:

  1. 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.
  2. 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.
  3. 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

  1. Enable Developer Mode in Discord: User Settings → Advanced → Developer Mode
  2. Get your Discord user ID: right-click your name in the server member list → Copy User ID. Add this to the owner array.
  3. Get role IDs: Server Settings → Roles → right-click each role → Copy Role ID. Add these to the roles map with descriptive keys.
  4. Adjust commands to match your desired permission layout — each permission string maps to an array of role keys from step 3.
  5. If using /exec, configure the exec block: set rolePriority, define which Minecraft commands each role can run in allowlist, and review the safety lists.
  6. 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:

module.exports = {
  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"],
  }
};
🔍 Testing Your Permissions

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 live
/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 subcommand (default)
/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

/exec command:<minecraft_command> [silent:true|false]

Code Examples

/exec command:time set day → sets the time to day
/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

  1. User runs /exec with a Minecraft command
  2. Bot validates permissions against the exec allowlist in permission-config.js
  3. Bot checks if the base command is in blockedCommands — hard blocked, no override
  4. If in dangerousCommands, a confirmation button prompt is sent
  5. If confirmed (or not dangerous), the bot optionally injects a tellraw for in-game visibility
  6. The command is sent through RCON and the result is returned as a Discord embed
  7. 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):

module.exports = {
  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"
ℹ️ Owner Override

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:

  1. The last player leaves the server — player count hits 0
  2. The idle countdown timer starts
  3. When WARNING_MINUTES remain, the bot posts a warning in the status channel
  4. 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:

  1. The bot spawns a journalctl -f (or tail -f) child process
  2. New log lines are collected via the stdout data event and pushed into a rotating 25-line buffer
  3. 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
  4. 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

/exec

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:

{ "timestamp": 1714920211, "userId": "123456789", "username": "David", "command": "time set day", "success": true, "requiresConfirmation": false, "silent": false }

Troubleshooting

Bot Won't Start

Problem: "TOKEN not found in config/.env"

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.

Problem: "Failed to connect to RCON"

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

Problem: Slash commands don't appear in Discord

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.

Problem: "You do not have permission to run this command"

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

Problem: "/start doesn't work"

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).

Problem: "/status shows no data"

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:

journalctl -u craftdaemon -f

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:

sudo systemctl stop craftdaemon
node src/index.js

For Minecraft server logs:

journalctl -u minecraft -f

Architecture

System Overview

CraftDaemon sits between Discord and your Minecraft server, translating slash commands into systemd and RCON calls:

Discord User

↓ /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:

CraftDaemon/
├── 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
ℹ️ Layout flexibility (Advanced)

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.

# Check the current status of a service (active state, recent logs, PID)
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.

# View the last 50 lines of logs for the bot
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"
ℹ️ Best debugging workflow

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:

HH:MM:SS [Category] [Level] Message

For example:

12:35:47 [RCON] [DEBUG] Sending command: list
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:

LOG_LEVEL=DEBUG

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] ========== BOT STARTUP CONFIGURATION ==========
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:

03:16:29 [SystemD] [INFO] Start command from steve

RCON refused while the server is starting up — the bot throttles repeated refusal logs so they don't flood your output:

12:32:41 [RCON] [WARN] RCON connection refused (server may be offline/starting). Retrying every 5s. [connect ECONNREFUSED 127.0.0.1:25575]
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:17:00 [AutoStop] [INFO] Server is now empty. Auto-stop timer started (10 minutes until shutdown).
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 { createLogger } = require("./services/logger");
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");
ℹ️ Performance note

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

  1. Fork the repository on GitHub (click "Fork" on the repo page)
  2. Clone your fork locally: git clone https://github.com/YOUR_USERNAME/CraftDaemon
  3. Create a feature branch: git checkout -b feature/my-feature
  4. Make your changes
  5. Test thoroughly — make sure existing functionality still works
  6. Commit with a clear message: git commit -m "Add my feature"
  7. Push to your fork: git push origin feature/my-feature
  8. 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.

💬 Questions?

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.

🌟 Like this project?

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.