1 Commits
edge ... main

Author SHA1 Message Date
7c1ee43b0a Update README
Goes for all branches.
2024-11-23 13:12:28 +01:00
8 changed files with 106 additions and 360 deletions

View File

@@ -5,8 +5,8 @@ It allows you to jail Python programs in a closed off filesystem
## How to install ## How to install
Installing PyJail is really simple! Just run `python3 ./install.py` Installing PyJail is really simple! Just run `python3 ./install.py` in the directory
(for Windows `py .\install.py`) in the directory where the files are stored! where the files are stored!
## Compatibility ## Compatibility
@@ -14,7 +14,6 @@ As of now we're still working on a custom Python interpreter to make all program
fully jailing compatible, sadly enough it's quite hard work. fully jailing compatible, sadly enough it's quite hard work.
So as of now it is compatible with all Python programs, **but** only some will be So as of now it is compatible with all Python programs, **but** only some will be
properly confined. properly confined.
There is a converter to automatically convert tools, but some still aren't compatible yet.
## POSIX compatibility ## POSIX compatibility
@@ -23,46 +22,17 @@ made for Linux to also run natively on Windows. This is more meant for Windows
versions that don't feature WSL (Windows 7/8/8.1) but still need to run some versions that don't feature WSL (Windows 7/8/8.1) but still need to run some
Linux only scripts. Linux only scripts.
## Bundled programs ### The 4 branches
To keep the installation extremely small in size and footprint, the bundled programs are also
extremely small. Currently we bundle 3 programs:
### `sh.py` (While installed: `/bin/sh` or `/usr/bin/sh` or `/usr/bin/shell.py`)
A very simple shell, just does directory navigation and installs packages.
### `ledit.py` (While installed: `/usr/bin/ledit.py`)
A simple line text editor. Meant for extreme simplicity.
### `autoconvert.py` (While installed: `/usr/bin/autoconvert.py`)
A converter to convert Python programs to be compatible with the jailed filesystem.
We recommend getting essential packages like a proper shell and the UwUGet package manager.
## The 4 branches
Which branch works best for you? Which branch works best for you?
Well, that's pretty simple. We have 4 branches (`main`, `next`, `edge` and `no-posix`). Well, that's pretty simple. We have 4 branches (`main`, `next`, `edge` and `no-posix`).
If you want the most stable experience, then the `main` branch is for you. If you want the most stable experience, then the `main` branch is for you.
If you want the lastest features, but also a more stable experience (compared to `edge`), If you want the lastest features, but also a more stable experience (compared to `edge`),
then the `next` branch is for you then the `next` branch is for you
If you want the bleeding-edge and don't care about stability, then `edge` is for you. If you want the bleeding-edge and don't care about stability, then `edge` is for you.
If you only need simple jailing and no POSIX compatibility, then `no-posix` is for you. If you only need simple jailing and no POSIX compatibility, then `no-posix` is for you.
## Issues ### Issues
Please report issues [over here](https://git.novacow.ch/Nova/PyJail/issues/) Please report issues [over here](https://git.novacow.ch/Nova/PyJail/issues/)
And please check if your issue isn't a duplicate before reporting. And please check if your issue isn't a duplicate before reporting.

View File

@@ -1,82 +0,0 @@
"""
Converts files to be jail-compatible
"""
import importlib
class FileConverter:
def __init__(self):
self.jail_mgr = importlib.import_module(".jail_mgr", "vfs.sys")
self.jailmgr = self.jail_mgr.PyJail()
self._file_openers = ["os.path", "open", "shutil.copy", "shutil.rm"]
self._unsupported_file_openers = ["QFile"]
def convert_file(self, fn):
unsupported_openers_found = 0
lines = []
with open(self.jailmgr.fs(fn), "a") as f:
for line in f:
line = line.strip()
lines.append(line)
blank_line_found = False
init_found = False
i_total = 0
for i, line in enumerate(lines):
if line == "" and blank_line_found is False:
line = "import importlib\n"
lines[i] = line
blank_line_found = True
if "def __init__" in line and init_found is False:
line_to_export = (
"\nself.jail_mgr = importlib.import_module('.jail_mgr', 'vfs.sys')\n"
"self.jailmgr = self.jail_mgr.PyJail()"
)
lines[i + 1] = line_to_export
init_found = True
if self._file_openers in line:
idx = line.index("(")
idx2 = line.index(")")
# if idx2 - idx != 1:
# self.jailmgr.msg(f"{self}", "Unsupported type!", False, "WARN")
# unsupported_openers_found += 1
# else:
expression = line[idx : idx2 + 1]
line_to_edit = f"self.jailmgr.fs({expression})"
idx -= 1
idx2 += 2
full_line = f"{line[:idx]}{line_to_edit}{line[idx2:]}"
lines[i] = full_line
elif self._unsupported_file_openers in line:
self.jailmgr.msg(f"{self}", "Unsupported opener!", False, "WARN")
unsupported_openers_found += 1
else:
pass
i_total = i
if unsupported_openers_found > 0:
self.jailmgr.msg(
f"{self}",
f"Some/all of the openers in this file aren't compatible with the converter, amount: {unsupported_openers_found} of the {i_total}",
True,
"WARN",
)
return lines
def file_writer(self, fn, content):
for i, line in enumerate(content):
line.rstrip("\n")
with open(self.jailmgr.fs(fn), "a+") as f:
f.write(f"{line}\n")
f.close()
self.jailmgr.msg(f"{self}", "Conversion OK! Please check results.", True)
return 0
if __name__ == "__main__":
fc = FileConverter()
fn = input("Enter filepath to convert (fullpath): ")
try:
lines = fc.convert_file(fn)
fc.file_writer(lines)
except Exception:
print("Failure to convert!")

View File

@@ -1,11 +1,9 @@
""" """
Install script for the Python jailer. Install script for the Python jailer.
Version: 0.3.0-main1 Version: 1.0.0-main1
""" """
import os import os
import shutil import shutil
with open("./kverinfo.txt", "r") as f: with open("./kverinfo.txt", "r") as f:
kver = f.read() kver = f.read()
f.close() f.close()
@@ -25,50 +23,65 @@ shutil.move("./main.py", "./vfs/main.py")
shutil.move("./runner.py", "./main.py") shutil.move("./runner.py", "./main.py")
shutil.move("./sh.py", "./vfs/sh.py") shutil.move("./sh.py", "./vfs/sh.py")
shutil.move("./ledit.py", "./vfs/ledit.py") shutil.move("./ledit.py", "./vfs/ledit.py")
shutil.move("./autoconvert.py" "./vfs/autoconvert.py")
os.chdir(os.getcwd() + "/vfs") os.chdir(os.getcwd() + "/vfs")
print("Gathering info...") print("Gathering info...")
usrname = input("Please enter your username: [usr1] ") usrname = input("Please enter your username: [usr1] ")
if usrname == "": if usrname == "":
usrname = "usr1" usrname = "usr1"
print("Creating directories...") setup_posix = input("Do you wish to setup a mostly-POSIX compliant environment? [y/N] ")
with open("./bin", "a+") as f: if setup_posix.lower() == "y":
print("Creating directories...")
with open("./bin", "a+") as f:
f.write("symlnk /usr/bin/") f.write("symlnk /usr/bin/")
f.close() f.close()
with open("./sbin", "a+") as f: with open("./sbin", "a+") as f:
f.write("symlnk /usr/sbin/") f.write("symlnk /usr/sbin/")
f.close() f.close()
with open("./lib", "a+") as f: with open("./lib", "a+") as f:
f.write("symlnk /usr/lib/") f.write("symlnk /usr/lib/")
f.close() f.close()
with open("./lib64", "a+") as f: with open("./lib64", "a+") as f:
f.write("symlnk /usr/lib64/") f.write("symlnk /usr/lib64/")
f.close() f.close()
with open("./usr/bin/sh", "a+") as f: with open("./usr/bin/sh", "a+") as f:
f.write("symlnk /usr/bin/shell.py") f.write("symlnk /usr/bin/shell.py")
f.close() f.close()
os.mkdir("./sys") os.mkdir("./sys")
os.mkdir("./usr") os.mkdir("./usr")
os.mkdir("./proc") os.mkdir("./proc")
os.mkdir(f"./home/{usrname}") os.mkdir(f"./home/{usrname}")
os.mkdir("./usr/bin/") os.mkdir("./usr/bin/")
os.mkdir("./usr/sbin/") os.mkdir("./usr/sbin/")
os.mkdir("./usr/lib/") os.mkdir("./usr/lib/")
os.mkdir("./usr/lib64/") os.mkdir("./usr/lib64/")
print("Copying files...") print("Copying files...")
shutil.move("./main.py", "./sys/jail_mgr.py") shutil.move("./main.py", "./sys/jail_mgr.py")
shutil.move("./sh.py", "./usr/bin/shell.py") shutil.move("./sh.py", "./bin/shell.py")
shutil.move("./ledit.py", "./usr/bin/ledit.py") shutil.move("./ledit.py", "./bin/ledit.py")
shutil.move("./autoconvert.py", "./usr/bin/autoconvert.py") print("Creating system configuration files...")
print("Creating system configuration files...") with open("./sys/usr.conf", "a+") as f:
with open("./sys/usr.conf", "a+") as f:
f.write(usrname) f.write(usrname)
f.close() f.close()
with open("./sys/procinfo", "a+") as f: with open("./sys/procinfo", "a+") as f:
f.write("proc: vfs(/proc/)\nkernel: vfs(/proc/kcore)") f.write("proc: vfs(/proc/)\nmgr: vfs(/proc/kcore)")
f.close() f.close()
with open("./proc/kcore", "a+") as f: with open("./proc/kcore", "a+") as f:
f.write("/sys/jail_mgr.py") f.write("/sys/jail_mgr.py")
f.close() f.close()
print("Install completed! Run ./main.py to start the process!") else:
print("Creating directories...")
os.mkdir("./bin")
os.mkdir("./sys")
os.mkdir("./usr")
os.mkdir("./proc")
os.mkdir(f"./home/{usrname}")
print("Copying files...")
shutil.move("./main.py", "./sys/jail_mgr.py")
shutil.move("./sh.py", "./bin/shell.py")
shutil.move("./ledit.py", "./bin/ledit.py")
print("Creating system configuration files...")
with open("./sys/usr.conf", "a+") as f:
f.write(usrname)
f.close()
print("Install completed! Run ./main.py to start the kernel!")
input("Press <Enter> to exit! ") input("Press <Enter> to exit! ")

View File

@@ -1 +1 @@
edge0005-base0.2.1 0.2.0-main1

View File

@@ -3,12 +3,9 @@ LEdit, a single line text editor made in Python
Version: 0.0.2-alpha1 Version: 0.0.2-alpha1
Made as an example program for PyNVOS Made as an example program for PyNVOS
""" """
class ledit: class ledit:
def __init__(self, instance): def __init__(self, instance):
self._kernel = instance self._kernel = instance
def prgm(self): def prgm(self):
path_to_file = input("Enter location of file to edit/create: ") path_to_file = input("Enter location of file to edit/create: ")
path_to_file = self._kernel.fs(path_to_file) path_to_file = self._kernel.fs(path_to_file)
@@ -17,7 +14,4 @@ class ledit:
f.write(text_to_write + "\n") f.write(text_to_write + "\n")
f.close() f.close()
exit() exit()
if __name__ == "<run_path>": if __name__ == "<run_path>":
raise NotImplementedError("no")

163
main.py
View File

@@ -1,8 +1,7 @@
""" """
This is the PyJail, a jailing tool for running Python apps in a sandboxed environment. This is the PyJail, a jailing tool for running Python apps in a sandboxed environment.
Version: edge0007-base0.2.1 Version: 0.2.0-main1
""" """
import os import os
import time import time
import runpy import runpy
@@ -10,9 +9,8 @@ import runpy
class PyJail: class PyJail:
""" """
The jail manager, handles all system calls and such. The
""" """
def __init__(self, debug=False): def __init__(self, debug=False):
self.rootpath = "" self.rootpath = ""
self.rootpath = self.fs() self.rootpath = self.fs()
@@ -32,22 +30,18 @@ class PyJail:
Runs a specified program. Runs a specified program.
""" """
path_to_bin = self.fs(path_to_bin) path_to_bin = self.fs(path_to_bin)
# print(path_to_bin)
# print(str(self.rootpath) + str(path_to_bin))
if path_to_bin == 3 or path_to_bin == 2: if path_to_bin == 3 or path_to_bin == 2:
self.msg( self.msg("jailmgr.run_program()", "An error has occurred launching the program.", True,
"jailmgr.run_program()", "WARNING")
"An error has occurred launching the program.",
True,
"WARNING",
)
else: else:
with open(self.fs("/proc/kproc"), "a+") as f: with open(self.fs("/proc/kproc"), "a+") as f:
f.write(f"proc: {path_to_bin}({self._program_counter})") f.write(f"proc: {path_to_bin}({self._program_counter})")
self._program_counter += 1 self._program_counter += 1
runpy.run_path(path_to_bin) runpy.run_path(path_to_bin)
def msg( def msg(self, caller: str, message:str, emit: bool = False, log_level: str = "INFO"):
self, caller: str, message: str, emit: bool = False, log_level: str = "INFO"
):
""" """
The custom message parser, can parse messages and alert apps of said messages. The custom message parser, can parse messages and alert apps of said messages.
Replaces print statements. Replaces print statements.
@@ -57,27 +51,15 @@ class PyJail:
emit: If the message needs to be passed to apps. emit: If the message needs to be passed to apps.
log_level: The loglevel, either DEBUG, INFO, WARNING, ERROR, CRITICAL log_level: The loglevel, either DEBUG, INFO, WARNING, ERROR, CRITICAL
""" """
emit_full = False
if self._debug is True: if self._debug is True:
emit_full = True emit = True
accepted_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] accepted_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
if log_level.upper() not in accepted_log_levels: if log_level.upper() not in accepted_log_levels:
self.msg( self.msg(f"jailmgr.msg()",f"Not accepted loglevel!! {log_level}", False, "ERROR")
"jailmgr.msg()", f"Not accepted loglevel!! {log_level}", False, "ERROR"
)
return 1
if log_level == "DEBUG" and self._debug is False:
emit = False
emit_full = False
msg = f"[{time.time}] [{caller}] [{log_level}] {message}"
with open(self.fs("/proc/klog"), "a+") as f: with open(self.fs("/proc/klog"), "a+") as f:
f.write(msg) f.write(f"[{time.time}] [{caller}] [{log_level}] {message}")
if emit is True and log_level.upper() == "CRITICAL": if emit is True:
print(msg)
elif emit is True:
print(message) print(message)
elif emit_full is True:
print(msg)
return 0 return 0
def fs(self, check_path=None, resolve_symlinks=True): def fs(self, check_path=None, resolve_symlinks=True):
@@ -99,11 +81,7 @@ class PyJail:
check_path_split = check_path.split("/", -1) check_path_split = check_path.split("/", -1)
prepend_path = "" prepend_path = ""
for i, path in enumerate(check_path_split): for i, path in enumerate(check_path_split):
prepend_path = ( prepend_path = f"{prepend_path}/{path}" if not path.endswith("/") else f"{prepend_path}/{path}/"
f"{prepend_path}/{path}"
if not path.endswith("/")
else f"{prepend_path}/{path}/"
)
check_path_split[i] = f"{prepend_path}" check_path_split[i] = f"{prepend_path}"
check_path = "" check_path = ""
for i, path in enumerate(check_path_split): for i, path in enumerate(check_path_split):
@@ -113,9 +91,7 @@ class PyJail:
elif os.path.isdir(path) and i == (len(check_path_split) - 1): elif os.path.isdir(path) and i == (len(check_path_split) - 1):
# The last thing to access was a directory. We can safely return the full path now. # The last thing to access was a directory. We can safely return the full path now.
return path return path
elif not os.path.isdir(path) and i != ( elif not os.path.isdir(path) and i != (len(check_path_split) - 1):
len(check_path_split) - 1
):
# This was not the last thing we needed to access, so we assume it's a symlink. # This was not the last thing we needed to access, so we assume it's a symlink.
# One problem is that we can't be sure, so we make sure it is a symlink. # One problem is that we can't be sure, so we make sure it is a symlink.
with open(self.fs(path, False)) as f: with open(self.fs(path, False)) as f:
@@ -125,27 +101,19 @@ class PyJail:
# This is a symlink! # This is a symlink!
# Symlinks always contain the full literal path that they need to access, so we can # Symlinks always contain the full literal path that they need to access, so we can
# take that and do the same trick to split it and add the next things to it. # take that and do the same trick to split it and add the next things to it.
# raise NotImplementedError()
is_symlink_split = is_symlink.split(" ", 1) is_symlink_split = is_symlink.split(" ", 1)
symlink_dest = is_symlink_split[1] symlink_dest = is_symlink_split[1]
symlink_dest = f"{symlink_dest}/{path}" symlink_dest = f"{symlink_dest}/{path}"
return symlink_dest return symlink_dest
else: else:
# This is either not a symlink or an improperly configured one. # This is either not a symlink or an improperly configured one.
self.msg( self.msg("jailmgr.fs()", "reached non-symlink file not at end of list",
"jailmgr.fs()", False, "ERROR")
"reached non-symlink file not at end of list", self.msg("jailmgr.fs()", "What was assumed to be a directory isn't a"
False,
"ERROR",
)
self.msg(
"jailmgr.fs()",
"What was assumed to be a directory isn't a"
" directory nor a symlink! This might be because of a " " directory nor a symlink! This might be because of a "
"typo or misconfigured symlink. The directory in question: " "typo or misconfigured symlink. The directory in question: "
f"{path}", f"{path}", True, "WARNING")
True,
"WARNING",
)
return 2 return 2
return self.rootpath + check_path return self.rootpath + check_path
@@ -153,103 +121,20 @@ class PyJail:
check_path = check_path.lstrip(".") check_path = check_path.lstrip(".")
return os.getcwd() + check_path if "vfs" in os.getcwd() else 2 return os.getcwd() + check_path if "vfs" in os.getcwd() else 2
elif self.rootpath in check_path: elif self.rootpath in check_path:
self.msg( self.msg(f"jailmgr.fs()", "Cannot parse rootpath, expected vfspath", log_level="ERROR")
"jailmgr.fs()",
"Cannot parse rootpath, expected vfspath",
log_level="ERROR",
)
return 3 return 3
else: else:
# Path is not in the jailed fs, so we say it doesn't exist. # Path is not in the jailed fs, so we say it doesn't exist.
self.msg( self.msg(f"jailmgr.fs()", "File/directory doesn't exist in vfspath", log_level="ERROR")
"jailmgr.fs()",
"File/directory doesn't exist in vfspath",
log_level="ERROR",
)
return 2 return 2
else: else:
rootpath = os.getcwd() + "/vfs" rootpath = os.getcwd() + "/vfs"
self.msg("jailmgr.fs()", message=rootpath, log_level="INFO") self.msg("jailmgr.fs()", message=rootpath, log_level="INFO")
return rootpath return rootpath
def kver(self): @staticmethod
def kver():
""" """
Returns the kernel version Returns the jail manager version
""" """
return "edge0007-base0.2.1" return "0.2.0-main1"
def netsock(self, ip, port, mode, msg):
"""
An easy interface to network sockets, built right into the jailmanager
Args:
ip: The IP of the server to access.
port: The port to access the server on
mode: Either UDP, TCP or PKG (HTTP)
msg: The message to send the server
Returns:
Whatever the server returns.
"""
if mode == "PKG":
import requests
else:
import socket
if mode == "TCP":
try:
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except Exception:
self.msg(
"jailmgr.netsock()", "Socket import failed!", False, "CRITICAL"
)
self.msg(
"jailmgr.netsock()", "An unexpected error occurred!", True, "ERROR"
)
return None
# Connect to the server
client_socket.connect((ip, port))
self.msg(
"jailmgr.netsock()",
f"Connected to server at {ip}:{port}",
False,
"INFO",
)
# Send the message to the server
client_socket.send(msg.encode())
# Receive the response from the server
response = client_socket.recv(1024).decode()
client_socket.close() # Close the connection
self.msg("jailmgr.netsock", f"Received from server: {response}")
return response
elif mode == "PKG":
# raise NotImplementedError("TODO: PKG will be implemented later!")
file_io = requests.get(ip)
if file_io.startswith("PYPAK PMD"):
with open(self.fs(f"/usr/netsock/cache/{msg}.pmd"), "a+") as f:
f.write(file_io)
f.close()
else:
with open(self.fs(f"/usr/netsock/cache/{msg}.py"), "a+") as f:
f.write(file_io)
f.close()
else:
# Create a UDP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Send the message to the server
client_socket.sendto(msg.encode(), (ip, port))
# Receive the response from the server
response, _ = client_socket.recvfrom(1024)
self.msg(f"{self}", f"Received from server: {response.decode()}")
# Close the socket
client_socket.close()
# raise NotImplementedError("TODO: Netsock will be implemented once 0.3.0 comes around!")

64
sh.py
View File

@@ -1,85 +1,51 @@
""" """
The shell for PyNVOS The shell for PyNVOS
Version: 0.2.0.0402 Version: 0.1.0-main1
""" """
import importlib import importlib
import os import os
import cmd import cmd
import shutil
# from ..sys.krnl import Kernel
print(__name__)
class shell(cmd.Cmd): class shell(cmd.Cmd):
jail_mgr = importlib.import_module(".jail_mgr", "vfs.sys") intro = "Shell started, PyNVOS 0.1.1-main1"
jailmgr = jail_mgr.PyJail() prompt = "shell-0.1.0$ "
kver = jailmgr.kver()
intro = f"Shell started, PyJail {kver}"
prompt = "shell-0.2$ "
file = None file = None
krnl = importlib.import_module(".jail_mgr", "vfs.sys")
kernel = krnl.Kernel()
print(str(kernel) + " " + str(type(kernel)))
def do_cd(self, args): def do_cd(self, args):
"""Changes directory""" """Changes directory"""
args = shell.jailmgr.fs(args) args = shell.kernel.fs(args)
os.chdir(args) os.chdir(args)
def do_exec(self, args): def do_exec(self, args):
"""Allows you to execute a file""" """Allows you to execute a file"""
# Apps in /bin should be allowed to launch without first adding /bin/ or ./, just the name of the executable # Apps in /bin should be allowed to launch without first adding /bin/ or ./, just the name of the executable
# So for ledit it should be just 'ledit' and not /bin/ledit.py or ./ledit.py # So for ledit it should be just 'ledit' and not /bin/ledit.py or ./ledit.py
bins_in_bin = os.listdir(self.jailmgr.fs("/bin")) bins_in_bin = os.listdir(shell.kernel.fs("/bin"))
apps_strip = [] apps_strip = []
for apps in bins_in_bin: for apps in bins_in_bin:
if apps.endswith(".py"): if apps.endswith(".py"):
apps_strip.append(apps.strip(".py")) apps_strip.append(apps.strip(".py"))
if args in apps_strip: if args in apps_strip:
shell.jailmgr.run_program(f"/bin/{args}.py") shell.kernel.run_program(f"/bin/{args}.py")
else: else:
shell.jailmgr.run_program(args) shell.kernel.run_program(args)
def do_ls(self, none): def do_ls(self, none):
"""Lists the content of a directory""" """Lists the content of a directory"""
os.listdir(os.getcwd()) os.listdir(os.getcwd())
def do_pkg(self, pkg):
"""Downloads packages over the internet."""
self.jailmgr.netsock(
f"https://pkg.novacow.ch/repo/{self.kver}/meta/{pkg}.pmd",
None,
"PKG",
f"{pkg}",
)
shutil.copy(
self.jailmgr.fs(f"/usr/netsock/cache/{pkg}.pmd"),
self.jailmgr.fs("/usr/pkg/metacache/"),
)
with open(self.jailmgr.fs(f"/usr/pkg/metacache/{pkg}.pmd"), "r") as f:
package_meta = f.read()
f.close()
self.jailmgr.msg("shell.do_pkg", package_meta, True, "INFO")
y_n_confirmation = input("Do you want to install this package? [y/N] ")
if y_n_confirmation.lower() != "y":
print("Aborted.")
return
self.jailmgr.netsock(
f"https://pkg.novacow.ch/repo/{self.kver}/main/static/binary/{pkg}.py",
None,
"PKG",
f"{pkg}",
)
shutil.copy(
self.jailmgr.fs(f"/usr/netsock/cache/{pkg}.py"),
self.jailmgr.fs("/usr/bin/"),
)
def postloop(self): def postloop(self):
pass pass
if __name__ == "<run_path>": if __name__ == '<run_path>':
shell().cmdloop() shell().cmdloop()
if __name__ == "__main__": if __name__ == '__main__':
print( print("The shell can't be ran as a standalone program and must be ran in conjunction with the kernel.")
"The shell can't be ran as a standalone program and must be ran in conjunction with the jail manager."
)
input("Press Enter to continue...") input("Press Enter to continue...")
exit(-1) exit(-1)

View File

@@ -1 +1 @@
edge 0.2 build 0036