A small Discord bot for a Pelican panel. It keeps one message in a channel, always up to date, listing your servers with their addresses — and lets the people you choose start and stop them from buttons on that message.
┌─────────────────────────────────────────────┐
│ ## 🎮 Game Servers │
│ ─────────────────────────────────────────── │
│ 🟢 Satisfactory · play.example.com:25501 │
│ CPU 24% · RAM 3.1/16 GB [ ⏹ Stop ] │
│ │
│ ⚫ Palworld · play.example.com:25500 │
│ [ ▶ Start ] │
│ ─────────────────────────────────────────── │
│ 1/5 online · updated 4 seconds ago │
│ [ Manage permissions ] [ Permission list ] │
└─────────────────────────────────────────────┘
- One message, not a feed. The panel is edited in place and re-used across restarts, so the channel never fills with status spam.
- Says only what matters. A status dot, the address you paste into the game, and CPU/RAM for the servers that are actually running. Online servers sort to the top; when there are more servers than fit, the panel pages rather than growing without end.
- Start/stop that tells you the truth. Actions that cannot work are refused before anything is sent, requests are serialised per server, and the bot watches the server afterwards and reports what actually happened — including the case where it starts, crashes and drops back to offline.
- Permissions you can edit from Discord. Grants attach to users or roles, can be limited to particular servers, and are stored outside the config file. The admin ids in the config are permanent, so you cannot lock yourself out.
-
In your panel: Admin → Eggs → Import Egg, and upload
egg-pelican-beacon.json. -
Create a server from the Pelican Beacon egg. It needs no allocation and very little memory (256 MB is plenty).
-
Fill in the variables:
Variable What it is Discord Bot Token From the Developer Portal → your app → Bot → Reset Token. Admin-only field. Panel Client API Key Panel → your account menu → API Credentials → create one. Admin-only field. Panel URL e.g. https://panel.example.comStatus Channel ID Right-click the channel → Copy Channel ID (needs Developer Mode). Discord Server ID Optional; makes slash commands appear immediately. Admin User IDs Your Discord user id. Comma-separated for several. The rest — title, layout, page size, refresh rate, hidden servers — have sensible defaults and can be changed later, either as variables or in
config.jsonon the server. -
Start it. The panel appears within a few seconds.
The bot hides the server it is itself running on, so it never lists itself.
The bot needs no privileged intents. In the Developer Portal → OAuth2 →
URL Generator, tick bot and applications.commands, and give it
View Channel, Send Messages and Read Message History in the channel
the panel lives in.
git clone https://github.com/valentinps/pelican-beacon.git
cd pelican-beacon
python -m venv .venv && .venv/bin/pip install -r requirements.txt
cp config.example.json config.json && chmod 600 config.json # then edit it
.venv/bin/python -m beacon config.jsonPython 3.11+.
Everything lives in config.json. Any value can also be supplied as an
environment variable, which always wins — that is how the egg passes secrets
without writing them to disk.
| Section | Key | Env | Meaning |
|---|---|---|---|
discord |
token |
DISCORD_TOKEN |
Bot token. Treat as a password. |
status_channel_id |
BEACON_STATUS_CHANNEL_ID |
Channel the panel lives in | |
guild_id |
BEACON_GUILD_ID |
Your server id (fast command sync) | |
panel |
url |
PANEL_URL |
Panel base URL |
client_api_key |
PANEL_CLIENT_API_KEY |
Client API key. Treat as a password. | |
display |
title |
BEACON_TITLE |
Heading of the panel |
title_emoji |
BEACON_TITLE_EMOJI |
Emoji before the heading; empty for none | |
manage_buttons |
BEACON_MANAGE_BUTTONS |
Admin buttons under the panel | |
layout |
BEACON_LAYOUT |
sections or compact |
|
public_host |
BEACON_PUBLIC_HOST |
Show a domain instead of the IP | |
show_resources |
BEACON_SHOW_RESOURCES |
CPU/RAM line for running servers | |
shorten_shared_host |
— | Print a shared host once, ports per row | |
hide_servers |
BEACON_HIDE_SERVERS |
Names/ids to leave out | |
hide_servers_without_allocation |
BEACON_HIDE_UNALLOCATED |
Skip serverless-looking entries | |
page_size |
BEACON_PAGE_SIZE |
Servers per page (1–8) | |
refresh_seconds |
BEACON_REFRESH_SECONDS |
Poll interval (min 5) | |
accent_colour |
BEACON_ACCENT_COLOUR |
Hex colour of the card's edge | |
footer_note |
BEACON_FOOTER_NOTE |
Extra small line at the bottom | |
power |
stop_signal |
BEACON_STOP_SIGNAL |
stop (clean) or kill |
transition_timeout |
— | Seconds to wait for a server to settle | |
locked_servers |
BEACON_LOCKED_SERVERS |
Shown, but never controllable | |
permissions |
admin_user_ids |
BEACON_ADMIN_USER_IDS |
Permanent admins |
operators |
BEACON_OPERATORS |
May control every server | |
server_operators |
BEACON_SERVER_OPERATORS |
Per-server access | |
everyone_can_start |
BEACON_EVERYONE_CAN_START |
Anyone may start | |
everyone_can_stop |
BEACON_EVERYONE_CAN_STOP |
Anyone may stop |
- Under the egg, the token and API key are panel variables marked admin-only, so
they are never written into
config.jsonand are not visible to users who can see the server. - If you do put them in
config.json, the bot forces the file to mode600on startup and warns you if it was readable by anyone else. - Both values are stripped from anything the bot logs.
Three layers, and a person gets whatever the most generous one gives them.
1. Admins — ids in permissions.admin_user_ids (or the Admin User IDs egg
variable). They can do anything and cannot be revoked from Discord, so you can
never lock yourself out.
2. Config lists — the quick way to set everything up without touching Discord. Every id here may be a user id or a role id; both are checked, so there is nothing to get wrong.
As egg variables, the same thing:
| Variable | Example |
|---|---|
BEACON_OPERATORS |
123456789012345678, 234567890123456789 |
BEACON_SERVER_OPERATORS |
Satisfactory=234567890123456789; Palworld=345678901234567890 |
BEACON_EVERYONE_CAN_START |
1 |
BEACON_EVERYONE_CAN_STOP |
0 |
Layers 1 and 2 are read once at startup, so changing an egg variable or the config file needs a restart. Layer 3 never does.
3. Grants made from Discord, stored in permissions.json. The easiest way is
the Manage permissions button under the panel — same screen as:
/permissions setup
Pick a server from the dropdown, pick the members and roles, then press Start + stop, Start only or Remove access. It shows who can already control that server, and adding a second server widens someone's access instead of replacing it. For the typed equivalent:
/permissions grant target:@Friends level:operator servers:Satisfactory, Palworld
/permissions grant target:@Alice level:operator can:"start only"
/permissions revoke target:@Alice
/permissions list
permissions.json is re-read whenever it changes, so editing it by hand in the
panel's file manager also takes effect on the next click without a restart.
servers accepts names, short ids or UUIDs. A start-only restriction applies to
a person's whole grant rather than per server, so pressing Start + stop for
one server lifts it everywhere for them.
| Level | Can |
|---|---|
viewer |
Nothing beyond seeing the panel, which everyone in the channel can do anyway |
operator |
Start and stop the servers in their scope — optionally only one of the two |
admin |
Everything, plus handing out permissions |
Servers listed in power.locked_servers are shown but can never be controlled
from Discord, whatever anyone's permissions say.
| Command | Who | What |
|---|---|---|
/panel refresh |
admin | Redraw the panel now |
/panel move |
admin | Move the panel into the current channel |
/panel status |
anyone | What the bot can see, and its latency |
/permissions setup |
admin | Pick a server, pick who may control it |
/permissions list |
admin | Every grant, from all three layers |
/permissions grant |
admin | Give a user or role access, by name |
/permissions revoke |
admin | Take it away |
Day to day nobody needs a command: the buttons do the work.
The panel never appears. The bot needs View Channel and Send Messages in that channel; check the console for a permission error, and that the channel id is the channel's, not the server's.
Buttons say "you do not have permission". Only the config admins can act
until someone is granted operator with /permissions grant.
A server says "state unknown". The panel answered but wings did not — usually the node is down. The panel keeps trying.
"started but shut itself down again". The process exited on its own; the bot's job ends at reporting it. Open that server's console in the panel.
beacon/
panel.py Pelican client API wrapper
power.py start/stop: validation, locking, watching, outcomes
permissions.py grants for users and roles, persisted as JSON
permissions_ui.py the /permissions setup screen
render.py builds the Components V2 card
bot.py the bot: update loop, button dispatch, slash commands
config.py config file + environment loading
MIT