Created
June 16, 2021 11:55
-
-
Save jupiterbjy/dcf4dd27784c80369b76c65d2077b643 to your computer and use it in GitHub Desktop.
Server-side script to support running python script as [discord command](https://github.com/jupiterbjy/Meow_py/tree/main/Meowpy/BotComponents/PythonExecution). Designed for separate machine using OverlayFS.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/python3.9 | |
""" | |
Only tested to run on raspberry pi, but shouldn't be limited to it only. | |
Listens for incoming connections and execute received python codes. | |
Obviously this is very dangerous. This is to be run on overlayFS with Raspbian in mind. | |
""" | |
import subprocess | |
import itertools | |
import trio | |
from loguru import logger | |
from sys import executable | |
end_signature = "\u200a\u200a\u200a" | |
EOF_ = end_signature.encode("utf8") | |
TIMEOUT = 10 | |
COUNTER = itertools.count() | |
logger.add( | |
"/home/pyexec/py_{time}.log", | |
rotation="5 MB", | |
retention="7 days", | |
compression="zip", | |
) | |
def encode(string: str): | |
string += end_signature | |
return string.encode("utf8") | |
def decode(byte_string: bytes): | |
decoded = byte_string.decode("utf8") | |
return decoded.removeprefix(end_signature) | |
async def try_to_send(stream: trio.SocketStream, bytes_, ident): | |
logger.debug("[{}] Sending back {} bytes", ident, len(bytes_)) | |
try: | |
await stream.send_all(bytes_) | |
except Exception as err_: | |
logger.critical("[{}] Could not send, reason: {}", ident, err_) | |
else: | |
logger.info("[{}] Sent {}", ident, len(bytes_)) | |
async def receive(stream: trio.SocketStream, ident): | |
data_ = b"" | |
logger.debug("[{}] Receiving", ident) | |
while not data_.endswith(EOF_): | |
data_ += await stream.receive_some() | |
logger.info("[{}] Received {}", ident, len(data_)) | |
return data_ | |
async def handler(stream: trio.SocketStream): | |
ident = next(COUNTER) | |
logger.debug("[{}] Receiving connection.", ident) | |
# trio will fail when handler raises exception. | |
# need to prevent it to keep server working, in case for unexpected | |
try: | |
try: | |
data = await receive(stream, ident) | |
except Exception as err_: | |
logger.critical(err_) | |
await try_to_send(stream, encode(str(err_)), ident) | |
else: | |
decoded = decode(data).strip() | |
logger.debug("[{}] Executing code: \n{}", ident, decoded) | |
try: | |
with trio.fail_after(TIMEOUT): | |
proc = await trio.run_process( | |
[executable, "-c", decoded], | |
capture_stdout=True, | |
stderr=subprocess.STDOUT, | |
check=False, | |
) | |
except trio.TooSlowError: | |
logger.critical("[{}] Got timeout executing script.", ident) | |
await try_to_send( | |
stream, | |
encode(f"Reached {TIMEOUT} seconds timeout limit while executing."), | |
ident | |
) | |
except Exception as err_: | |
logger.critical("[{}] Got an error.\nDetails: {}", ident, err_) | |
await try_to_send(stream, encode(str(err_)), ident) | |
else: | |
stdout = proc.stdout | |
return_code = f"Return code was {proc.returncode}" | |
output = ( | |
f"```\n{stdout.decode('utf8')}```\n{return_code}" | |
if stdout | |
else return_code | |
) | |
await try_to_send(stream, encode(output), ident) | |
finally: | |
await stream.send_eof() | |
logger.debug("[{}] Connection closed.", ident) | |
except Exception as err_: | |
logger.critical("[{}] Handler failed!\nDetail: {}", ident, err_) | |
async def main_routine(): | |
logger.info(f"Server starting.") | |
await trio.serve_tcp(handler, 8123, task_status=trio.TASK_STATUS_IGNORED) | |
trio.run(main_routine) |
Author
jupiterbjy
commented
May 9, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment