Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) Mkkbeydoun <mohamed@beyd.ca> and contributors
Copyright (c) Mkkbeydoun <mohamed@rustix.xyz> and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ The server console also controls the bots. Type `help` for the list:
| Bot Panel Password (`PANEL_PASSWORD`) | | Web panel password; blank generates one |
| Bots Folder (`BOTS_DIR`) | | Folder holding the bot folders; blank is the server's main folder |
| Auto Restart Bots (`AUTO_RESTART`) | `1` | Restart crashed bots with backoff |
| Bot Logs In Main Console (`CONSOLE_MIRROR`) | `0` | Also show every bot's output in the main console, not only in its own log in the panel |
| Update Check (`UPDATE_CHECK`) | `1` | Say in the console when the egg or the Docker image has an update |

### Git
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Reporting a vulnerability

Please do not open a public issue for security problems. Report them privately through [GitHub's private vulnerability reporting](https://github.com/Rustix-Development/pterodactyl-python-egg/security/advisories/new), or by email to mohamed@beyd.ca.
Please do not open a public issue for security problems. Report them privately through [GitHub's private vulnerability reporting](https://github.com/Rustix-Development/pterodactyl-python-egg/security/advisories/new), or by email to mohamed@rustix.xyz.

Include the egg version, the Python version (Docker image) of the server, whether Multi-Bot Mode is on, your panel version and the steps to reproduce. You'll get a reply as soon as possible, and a fix will be released before the issue is made public.

Expand Down
2 changes: 1 addition & 1 deletion docker/rustix/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def env_on(name, default="1"):
LOG_DIR = HOME / "logs"
ENTRIES = os.environ.get("BOT_ENTRIES", "bot.py main.py app.py").split()
AUTO_RESTART = env_on("AUTO_RESTART")
MIRROR = env_on("CONSOLE_MIRROR")
MIRROR = env_on("CONSOLE_MIRROR", "0") # bot output in the main console too, not just the panel
PORT = int(os.environ.get("SERVER_PORT") or os.environ.get("PORT") or 8080)
EXCLUDED = {"logs", "__pycache__", "node_modules", "venv", "env", "backup-dont-touch"}
# Never hand these to bot processes: panel/git secrets and egg plumbing.
Expand Down
14 changes: 12 additions & 2 deletions egg-python.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"exported_at": "2026-10-16T22:00:00+00:00",
"name": "Python",
"author": "mohamed@beyd.ca",
"author": "mohamed@rustix.xyz",
"description": "Run Python apps and Discord bots, on Python 3.11 to 3.15.\n\n- Dependencies: requirements.txt is installed with uv into a venv, and skipped on restarts when nothing changed\n- Git: clone your bot from a Git repo, optionally pulling updates on every start\n- Multi-Bot Mode (set by an admin): host many bots on one server, each in its own folder with its own packages and .env, managed from a web panel on the server's port\n\nBy Rustix (rustix.xyz).",
"features": [],
"docker_images": {
Expand All @@ -26,7 +26,7 @@
},
"scripts": {
"installation": {
"script": "#!/bin/ash\n# Rustix Python installer: sets up the server's files (Git clone, or a starter bot folder).\n\n# Bump this whenever the egg changes. Servers compare it with the egg on GitHub at\n# startup and show an update notice in the console.\nEGG_VERSION=\"1.0.0\"\n\nis_true() { [ \"$1\" = \"1\" ] || [ \"$1\" = \"true\" ]; }\n\n# Drop user:token@ from a git URL so a changed token still counts as the same repo\nstrip_auth() { printf '%s' \"$1\" | sed -E 's#^([a-z]+://)[^/@]*@#\\1#'; }\n\n# The install container has a terminal nobody types into: never wait for a git password\nexport GIT_TERMINAL_PROMPT=0\n\ncd /mnt/server || exit 1\napk add --no-cache git || { echo \"Failed to install git\"; exit 1; }\n# Server files belong to the server user, not root; let git work on them anyway\ngit config --global --add safe.directory '*'\n\n# Anything here besides what the egg itself creates?\nhas_files() {\n [ -n \"$(find . -mindepth 1 -maxdepth 1 ! -name .egg-version ! -name logs ! -name .cache ! -name .venv ! -name .rustix | head -n 1)\" ]\n}\n\nfinish() {\n mkdir -p /mnt/server/logs\n echo \"${EGG_VERSION}\" > /mnt/server/.egg-version\n echo \"$1\"\n exit 0\n}\n\n# Starter bot for multi-bot mode, so the panel has something to show. Auto-start is off.\nseed_example_bot() {\n has_files && return\n mkdir -p example-bot\n cat > example-bot/bot.py <<'PY'\n# Example bot: copy this folder for each bot you host. The panel runs every folder\n# that has a bot.py (or main.py / app.py) in it, each with its own packages and .env.\nimport os\nimport time\n\nprint(f\"example-bot running, DISCORD_TOKEN set: {bool(os.getenv('DISCORD_TOKEN'))}\")\nwhile True:\n time.sleep(60)\nPY\n printf '# discord.py>=2.6\\n' > example-bot/requirements.txt\n printf '# Each bot reads its own .env\\nDISCORD_TOKEN=\\n' > example-bot/.env\n touch example-bot/.disabled\n echo \"Added example-bot/ (auto-start off). Copy it for each bot you want to host.\"\n}\n\nif is_true \"${USER_UPLOAD}\"; then\n is_true \"${MULTI_BOT}\" && seed_example_bot\n finish \"User Uploaded Files is on, skipping Git setup. Upload your files.\"\nfi\n\nif [ -z \"${GIT_ADDRESS}\" ]; then\n is_true \"${MULTI_BOT}\" && seed_example_bot\n if is_true \"${MULTI_BOT}\"; then\n finish \"Install complete. Put each bot in its own folder, then open the web panel on this server's address.\"\n fi\n finish \"Install complete. Upload your bot (${PY_FILE:-bot.py}) and its requirements.txt.\"\nfi\n\n# Git\ncase \"${GIT_ADDRESS}\" in\n *.git) ;;\n *) GIT_ADDRESS=\"${GIT_ADDRESS}.git\" ;;\nesac\n\nif [ -n \"${USERNAME}\" ] && [ -n \"${ACCESS_TOKEN}\" ]; then\n GIT_ADDRESS=\"https://${USERNAME}:${ACCESS_TOKEN}@${GIT_ADDRESS#*://}\"\nfi\n\nif has_files; then\n if [ -d .git ]; then\n if [ \"$(strip_auth \"$(git config --get remote.origin.url)\")\" = \"$(strip_auth \"${GIT_ADDRESS}\")\" ]; then\n echo \"Pulling latest files from git\"\n # Picks up a changed Git Username/Access Token\n git remote set-url origin \"${GIT_ADDRESS}\"\n git pull --ff-only || echo \"git pull failed, keeping the current files.\"\n else\n echo \"This server is a clone of a different git repo, leaving it alone.\"\n fi\n else\n echo \"This server already has files that aren't from git, leaving them alone.\"\n fi\nelse\n echo \"Cloning files from git\"\n rm -rf /tmp/clone\n if [ -n \"${BRANCH}\" ]; then\n git clone --single-branch --branch \"${BRANCH}\" \"${GIT_ADDRESS}\" /tmp/clone || { echo \"git clone failed, check Git Repo Address, Git Branch and credentials\"; exit 1; }\n else\n git clone \"${GIT_ADDRESS}\" /tmp/clone || { echo \"git clone failed, check Git Repo Address and credentials\"; exit 1; }\n fi\n cp -a /tmp/clone/. /mnt/server/ && rm -rf /tmp/clone\nfi\n\nfinish \"Install complete.\"\n",
"script": "#!/bin/ash\n# Rustix Python installer: sets up the server's files (Git clone, or a starter bot folder).\n\n# Bump this whenever the egg changes. Servers compare it with the egg on GitHub at\n# startup and show an update notice in the console.\nEGG_VERSION=\"1.0.1\"\n\nis_true() { [ \"$1\" = \"1\" ] || [ \"$1\" = \"true\" ]; }\n\n# Drop user:token@ from a git URL so a changed token still counts as the same repo\nstrip_auth() { printf '%s' \"$1\" | sed -E 's#^([a-z]+://)[^/@]*@#\\1#'; }\n\n# The install container has a terminal nobody types into: never wait for a git password\nexport GIT_TERMINAL_PROMPT=0\n\ncd /mnt/server || exit 1\napk add --no-cache git || { echo \"Failed to install git\"; exit 1; }\n# Server files belong to the server user, not root; let git work on them anyway\ngit config --global --add safe.directory '*'\n\n# Anything here besides what the egg itself creates?\nhas_files() {\n [ -n \"$(find . -mindepth 1 -maxdepth 1 ! -name .egg-version ! -name logs ! -name .cache ! -name .venv ! -name .rustix | head -n 1)\" ]\n}\n\nfinish() {\n mkdir -p /mnt/server/logs\n echo \"${EGG_VERSION}\" > /mnt/server/.egg-version\n echo \"$1\"\n exit 0\n}\n\n# Starter bot for multi-bot mode, so the panel has something to show. Auto-start is off.\nseed_example_bot() {\n has_files && return\n mkdir -p example-bot\n cat > example-bot/bot.py <<'PY'\n# Example bot: copy this folder for each bot you host. The panel runs every folder\n# that has a bot.py (or main.py / app.py) in it, each with its own packages and .env.\nimport os\nimport time\n\nprint(f\"example-bot running, DISCORD_TOKEN set: {bool(os.getenv('DISCORD_TOKEN'))}\")\nwhile True:\n time.sleep(60)\nPY\n printf '# discord.py>=2.6\\n' > example-bot/requirements.txt\n printf '# Each bot reads its own .env\\nDISCORD_TOKEN=\\n' > example-bot/.env\n touch example-bot/.disabled\n echo \"Added example-bot/ (auto-start off). Copy it for each bot you want to host.\"\n}\n\nif is_true \"${USER_UPLOAD}\"; then\n is_true \"${MULTI_BOT}\" && seed_example_bot\n finish \"User Uploaded Files is on, skipping Git setup. Upload your files.\"\nfi\n\nif [ -z \"${GIT_ADDRESS}\" ]; then\n is_true \"${MULTI_BOT}\" && seed_example_bot\n if is_true \"${MULTI_BOT}\"; then\n finish \"Install complete. Put each bot in its own folder, then open the web panel on this server's address.\"\n fi\n finish \"Install complete. Upload your bot (${PY_FILE:-bot.py}) and its requirements.txt.\"\nfi\n\n# Git\ncase \"${GIT_ADDRESS}\" in\n *.git) ;;\n *) GIT_ADDRESS=\"${GIT_ADDRESS}.git\" ;;\nesac\n\nif [ -n \"${USERNAME}\" ] && [ -n \"${ACCESS_TOKEN}\" ]; then\n GIT_ADDRESS=\"https://${USERNAME}:${ACCESS_TOKEN}@${GIT_ADDRESS#*://}\"\nfi\n\nif has_files; then\n if [ -d .git ]; then\n if [ \"$(strip_auth \"$(git config --get remote.origin.url)\")\" = \"$(strip_auth \"${GIT_ADDRESS}\")\" ]; then\n echo \"Pulling latest files from git\"\n # Picks up a changed Git Username/Access Token\n git remote set-url origin \"${GIT_ADDRESS}\"\n git pull --ff-only || echo \"git pull failed, keeping the current files.\"\n else\n echo \"This server is a clone of a different git repo, leaving it alone.\"\n fi\n else\n echo \"This server already has files that aren't from git, leaving them alone.\"\n fi\nelse\n echo \"Cloning files from git\"\n rm -rf /tmp/clone\n if [ -n \"${BRANCH}\" ]; then\n git clone --single-branch --branch \"${BRANCH}\" \"${GIT_ADDRESS}\" /tmp/clone || { echo \"git clone failed, check Git Repo Address, Git Branch and credentials\"; exit 1; }\n else\n git clone \"${GIT_ADDRESS}\" /tmp/clone || { echo \"git clone failed, check Git Repo Address and credentials\"; exit 1; }\n fi\n cp -a /tmp/clone/. /mnt/server/ && rm -rf /tmp/clone\nfi\n\nfinish \"Install complete.\"\n",
"container": "alpine:3.24",
"entrypoint": "ash"
}
Expand Down Expand Up @@ -122,6 +122,16 @@
"rules": "required|boolean",
"field_type": "text"
},
{
"name": "Bot Logs In Main Console",
"description": "Multi-Bot Mode: also show every bot's output in the server's main console. Off shows each bot's output only in its own log in the web panel (and logs/<bot>.log).\n\n0 = false (default)\n1 = true",
"env_variable": "CONSOLE_MIRROR",
"default_value": "0",
"user_viewable": true,
"user_editable": true,
"rules": "required|boolean",
"field_type": "text"
},
{
"name": "Git Repo Address",
"description": "Git repo to clone on install, e.g. https://github.com/you/your-bot",
Expand Down
Loading