487 lines
22 KiB
Python
487 lines
22 KiB
Python
"""Stdlib-only authentication boundary for supervised application children."""
|
|
|
|
import sys
|
|
|
|
sys.dont_write_bytecode = True
|
|
if not sys.dont_write_bytecode:
|
|
raise RuntimeError('supervised child bootstrap could not disable bytecode writes')
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import os
|
|
import runpy
|
|
import socket
|
|
import stat
|
|
|
|
|
|
MAX_METADATA_BYTES = 256 * 1024
|
|
MAX_HANDSHAKE_BYTES = 1024 * 1024
|
|
MAX_PYVENV_BYTES = 64 * 1024
|
|
MANIFEST_SCHEMA = 5
|
|
APPLICATION_IMPORT_SUFFIXES = ('.py', '.pyw', '.pyc', '.pyd')
|
|
CONTROL_SCHEMA = 1
|
|
REQUIRED_DEPENDENCIES = {
|
|
'supervisor': ('psycopg', 'yaml'),
|
|
'postgres-runtime': ('psycopg', 'yaml'),
|
|
'migrate-runtime-safety': ('psycopg', 'yaml'),
|
|
'scanner': ('psycopg', 'requests', 'urllib3', 'yaml', 'zstandard'),
|
|
'discovery-producer': ('psycopg', 'requests', 'urllib3', 'yaml', 'zstandard'),
|
|
'docker-shadow': ('psycopg', 'requests', 'urllib3', 'yaml', 'zstandard'),
|
|
'keycheck': ('psycopg', 'requests', 'yaml'),
|
|
'dashboard': ('pandas', 'plotly', 'psycopg', 'streamlit', 'yaml'),
|
|
'keycheck-provider': ('boto3', 'botocore', 'psycopg', 'requests', 'yaml'),
|
|
'janitor': ('yaml',),
|
|
'result-ingester': ('psycopg', 'yaml'),
|
|
'jsonl-projector': ('psycopg', 'yaml'),
|
|
'worker-api': ('psycopg', 'requests', 'starlette', 'urllib3', 'uvicorn', 'yaml', 'zstandard'),
|
|
}
|
|
|
|
|
|
class ChildRuntimeError(RuntimeError):
|
|
pass
|
|
|
|
|
|
ENV = {
|
|
'instance_file': 'TRUF_SUPERVISOR_INSTANCE_FILE',
|
|
'instance_id': 'TRUF_SUPERVISOR_INSTANCE_ID',
|
|
'token': 'TRUF_SUPERVISOR_TOKEN',
|
|
'config_sha256': 'TRUF_SUPERVISOR_CONFIG_SHA256',
|
|
'supervisor_sha256': 'TRUF_SUPERVISOR_SHA256',
|
|
'code_manifest_sha256': 'TRUF_SUPERVISOR_CODE_MANIFEST_SHA256',
|
|
'dsn_sha256': 'TRUF_SUPERVISOR_DSN_SHA256',
|
|
'kind': 'TRUF_SUPERVISOR_CHILD_KIND',
|
|
}
|
|
|
|
|
|
def _canonical(path):
|
|
return os.path.normcase(os.path.realpath(os.path.abspath(os.fspath(path))))
|
|
|
|
|
|
def _is_reparse_point(path):
|
|
details = os.lstat(path)
|
|
if stat.S_ISLNK(details.st_mode):
|
|
return True
|
|
attributes = getattr(details, 'st_file_attributes', 0)
|
|
reparse_attribute = getattr(stat, 'FILE_ATTRIBUTE_REPARSE_POINT', 0)
|
|
return bool(attributes & reparse_attribute) or getattr(os.path, 'isjunction', lambda _path: False)(path)
|
|
|
|
|
|
def _contained(path, roots):
|
|
for root in roots:
|
|
try:
|
|
if path != root and os.path.commonpath((root, path)) == root:
|
|
return True
|
|
except ValueError:
|
|
continue
|
|
return False
|
|
|
|
|
|
def _validated_site_directory(path, roots):
|
|
if not path or not os.path.isdir(path):
|
|
return ''
|
|
candidate = _canonical(path)
|
|
trusted_roots = tuple(_canonical(root) for root in roots if root)
|
|
if os.path.basename(candidate).lower() not in ('site-packages', 'dist-packages'):
|
|
raise RuntimeError(f'interpreter dependency path is not a site-packages directory: {candidate}')
|
|
if not _contained(candidate, trusted_roots):
|
|
raise RuntimeError(f'interpreter dependency path escapes its trusted root: {candidate}')
|
|
return candidate
|
|
|
|
|
|
def _append_site_directories(paths, roots):
|
|
existing = {_canonical(path) for path in sys.path if path}
|
|
for path in paths:
|
|
candidate = _validated_site_directory(path, roots)
|
|
if candidate and candidate not in existing:
|
|
# Direct insertion intentionally does not evaluate .pth hook lines.
|
|
sys.path.append(candidate)
|
|
existing.add(candidate)
|
|
|
|
|
|
def _venv_configuration():
|
|
# Preserve a venv's bin/python symlink location while locating pyvenv.cfg.
|
|
executable_dir = os.path.dirname(os.path.normcase(os.path.abspath(sys.executable)))
|
|
roots = [executable_dir]
|
|
if os.path.basename(executable_dir).lower() in ('bin', 'scripts'):
|
|
roots.insert(0, os.path.dirname(executable_dir))
|
|
for root in roots:
|
|
config_path = os.path.join(root, 'pyvenv.cfg')
|
|
if not os.path.isfile(config_path):
|
|
continue
|
|
details = os.stat(config_path, follow_symlinks=False)
|
|
if not stat.S_ISREG(details.st_mode) or details.st_size > MAX_PYVENV_BYTES:
|
|
raise RuntimeError('interpreter pyvenv.cfg is not a bounded regular file')
|
|
with open(config_path, 'rb') as handle:
|
|
payload = handle.read(MAX_PYVENV_BYTES + 1)
|
|
if len(payload) > MAX_PYVENV_BYTES:
|
|
raise RuntimeError('interpreter pyvenv.cfg exceeds its byte bound')
|
|
include_system = False
|
|
for raw_line in payload.decode('utf-8', errors='strict').splitlines():
|
|
key, separator, value = raw_line.partition('=')
|
|
if separator and key.strip().lower() == 'include-system-site-packages':
|
|
include_system = value.strip().lower() in ('1', 'true', 'yes')
|
|
return _canonical(root), include_system
|
|
return '', True
|
|
|
|
|
|
def _venv_site_directories(root):
|
|
if os.name == 'nt':
|
|
return [os.path.join(root, 'Lib', 'site-packages')]
|
|
version = f'python{sys.version_info.major}.{sys.version_info.minor}'
|
|
return [
|
|
os.path.join(root, library, version, name)
|
|
for library in ('lib', 'lib64')
|
|
for name in ('site-packages', 'dist-packages')
|
|
]
|
|
|
|
|
|
def _system_site_directories():
|
|
import sysconfig
|
|
|
|
roots = tuple(dict.fromkeys((_canonical(sys.base_prefix), _canonical(sys.base_exec_prefix))))
|
|
paths = sysconfig.get_paths(vars={
|
|
'base': sys.base_prefix,
|
|
'platbase': sys.base_exec_prefix,
|
|
})
|
|
return [paths.get('purelib'), paths.get('platlib')], roots
|
|
|
|
|
|
def _user_site_directories():
|
|
if os.name == 'nt':
|
|
try:
|
|
import ctypes
|
|
|
|
appdata = ctypes.create_unicode_buffer(32768)
|
|
if ctypes.windll.shell32.SHGetFolderPathW(None, 0x001A, None, 0, appdata) != 0:
|
|
return [], ()
|
|
root = _canonical(os.path.join(appdata.value, 'Python'))
|
|
version = f'Python{sys.version_info.major}{sys.version_info.minor}'
|
|
return [os.path.join(root, version, 'site-packages')], (root,)
|
|
except (AttributeError, OSError, ValueError):
|
|
return [], ()
|
|
try:
|
|
import pwd
|
|
|
|
home = _canonical(pwd.getpwuid(os.getuid()).pw_dir)
|
|
except (ImportError, KeyError, OSError):
|
|
return [], ()
|
|
version = f'python{sys.version_info.major}.{sys.version_info.minor}'
|
|
if sys.platform == 'darwin':
|
|
root = _canonical(os.path.join(home, 'Library', 'Python', f'{sys.version_info.major}.{sys.version_info.minor}'))
|
|
return [os.path.join(root, 'lib', 'python', 'site-packages')], (root,)
|
|
root = _canonical(os.path.join(home, '.local'))
|
|
return [
|
|
os.path.join(root, 'lib', version, 'site-packages'),
|
|
os.path.join(root, 'lib', version, 'dist-packages'),
|
|
], (root,)
|
|
|
|
|
|
def _missing_dependencies(kind):
|
|
import importlib.util
|
|
|
|
return [name for name in REQUIRED_DEPENDENCIES[kind] if importlib.util.find_spec(name) is None]
|
|
|
|
|
|
def _enable_dependency_paths(kind):
|
|
venv_root, include_system = _venv_configuration()
|
|
if venv_root:
|
|
_append_site_directories(_venv_site_directories(venv_root), (venv_root,))
|
|
if include_system:
|
|
system_paths, system_roots = _system_site_directories()
|
|
_append_site_directories(system_paths, system_roots)
|
|
if _missing_dependencies(kind):
|
|
user_paths, user_roots = _user_site_directories()
|
|
_append_site_directories(user_paths, user_roots)
|
|
missing = _missing_dependencies(kind)
|
|
if missing:
|
|
raise RuntimeError('required authenticated child dependencies are unavailable: ' + ', '.join(missing))
|
|
|
|
|
|
def _sha256_file(path):
|
|
digest = hashlib.sha256()
|
|
with open(path, 'rb') as handle:
|
|
while True:
|
|
block = handle.read(1024 * 1024)
|
|
if not block:
|
|
return digest.hexdigest()
|
|
digest.update(block)
|
|
|
|
|
|
def _read_object(path):
|
|
details = os.stat(path, follow_symlinks=False)
|
|
if not stat.S_ISREG(details.st_mode) or details.st_size <= 0 or details.st_size > MAX_METADATA_BYTES:
|
|
raise RuntimeError('supervisor metadata is not a bounded regular file')
|
|
with open(path, 'rb') as handle:
|
|
payload = handle.read(MAX_METADATA_BYTES + 1)
|
|
if len(payload) > MAX_METADATA_BYTES:
|
|
raise RuntimeError('supervisor metadata exceeds its byte bound')
|
|
value = json.loads(payload.decode('utf-8'))
|
|
if not isinstance(value, dict):
|
|
raise RuntimeError('supervisor metadata root is invalid')
|
|
return value
|
|
|
|
|
|
def _manifest_digest(manifest):
|
|
payload = json.dumps(manifest, ensure_ascii=True, sort_keys=True, separators=(',', ':')).encode('utf-8')
|
|
return hashlib.sha256(payload).hexdigest()
|
|
|
|
|
|
def _application_code_files(root):
|
|
names = set()
|
|
|
|
def raise_walk_error(exc):
|
|
raise RuntimeError(f'unable to inspect the application root: {exc}') from exc
|
|
|
|
for current, directories, files in os.walk(root, followlinks=False, onerror=raise_walk_error):
|
|
for name in directories:
|
|
candidate = os.path.join(current, name)
|
|
if _is_reparse_point(candidate):
|
|
relative = os.path.relpath(candidate, root).replace(os.sep, '/')
|
|
raise RuntimeError(f'application directory reparse point is forbidden: {relative}')
|
|
relative_current = os.path.relpath(current, root)
|
|
in_cache = any(part.lower() == '__pycache__' for part in relative_current.split(os.sep))
|
|
suffixes = ('.pyc',) if in_cache else APPLICATION_IMPORT_SUFFIXES
|
|
for name in files:
|
|
source_path = os.path.abspath(os.path.join(current, name))
|
|
if _is_reparse_point(source_path):
|
|
relative = os.path.relpath(source_path, root).replace(os.sep, '/')
|
|
raise RuntimeError(f'application file reparse point is forbidden: {relative}')
|
|
if not name.lower().endswith(suffixes):
|
|
continue
|
|
path = _canonical(source_path)
|
|
try:
|
|
contained = os.path.commonpath((root, path)) == root
|
|
except ValueError:
|
|
contained = False
|
|
if not contained:
|
|
raise RuntimeError('application Python authority escapes its root')
|
|
names.add(os.path.relpath(source_path, root).replace(os.sep, '/'))
|
|
return names
|
|
|
|
|
|
def _reject_cached_bytecode(root):
|
|
def raise_walk_error(exc):
|
|
raise RuntimeError(f'unable to inspect the application root: {exc}') from exc
|
|
|
|
try:
|
|
root_details = os.lstat(root)
|
|
except OSError as exc:
|
|
raise RuntimeError(f'application root is unavailable: {root}') from exc
|
|
if _is_reparse_point(root):
|
|
raise RuntimeError(f'application root reparse point is forbidden: {root}')
|
|
if not stat.S_ISDIR(root_details.st_mode):
|
|
raise RuntimeError(f'application root is not a directory: {root}')
|
|
for current, directories, files in os.walk(root, followlinks=False, onerror=raise_walk_error):
|
|
for name in directories:
|
|
candidate = os.path.join(current, name)
|
|
if _is_reparse_point(candidate):
|
|
relative = os.path.relpath(candidate, root).replace(os.sep, '/')
|
|
if name.lower() == '__pycache__':
|
|
raise RuntimeError(f'application __pycache__ link is forbidden: {relative}')
|
|
raise RuntimeError(f'application directory reparse point is forbidden: {relative}')
|
|
relative_current = os.path.relpath(current, root)
|
|
in_cache = any(part.lower() == '__pycache__' for part in relative_current.split(os.sep))
|
|
for name in files:
|
|
candidate = os.path.join(current, name)
|
|
relative = os.path.relpath(candidate, root).replace(os.sep, '/')
|
|
if _is_reparse_point(candidate):
|
|
raise RuntimeError(f'application file reparse point is forbidden: {relative}')
|
|
if in_cache and name.lower().endswith('.pyc'):
|
|
raise RuntimeError(f'application __pycache__ bytecode is forbidden: {relative}')
|
|
|
|
|
|
def _verify_manifest(metadata, inherited):
|
|
manifest = metadata.get('code_manifest')
|
|
if not isinstance(manifest, dict) or manifest.get('schema') != MANIFEST_SCHEMA:
|
|
raise RuntimeError('unsupported child code manifest')
|
|
expected_digest = str(metadata.get('code_manifest_sha256') or '')
|
|
if not hmac.compare_digest(_manifest_digest(manifest), expected_digest):
|
|
raise RuntimeError('child code manifest digest mismatch')
|
|
if not hmac.compare_digest(expected_digest, inherited['code_manifest_sha256']):
|
|
raise RuntimeError('inherited child code manifest mismatch')
|
|
root_value = manifest.get('root') or ''
|
|
files = manifest.get('files')
|
|
executables = manifest.get('executables')
|
|
assets = manifest.get('assets')
|
|
if not root_value or not isinstance(files, dict) or not isinstance(executables, dict) or not isinstance(assets, dict):
|
|
raise RuntimeError('child code manifest is incomplete')
|
|
raw_root = os.path.abspath(os.fspath(root_value))
|
|
_reject_cached_bytecode(raw_root)
|
|
root = _canonical(raw_root)
|
|
manifested_code = set()
|
|
for name, value in files.items():
|
|
if not isinstance(value, dict):
|
|
raise RuntimeError('child code manifest file entry is invalid')
|
|
expected_path = _canonical(os.path.join(root, *str(name).split('/')))
|
|
path = _canonical(value.get('path') or '')
|
|
if path != expected_path or not hmac.compare_digest(_sha256_file(path), str(value.get('sha256') or '')):
|
|
raise RuntimeError(f'child code authority drifted: {name}')
|
|
try:
|
|
contained = os.path.commonpath((root, path)) == root
|
|
except ValueError:
|
|
contained = False
|
|
if contained and str(name).lower().endswith(APPLICATION_IMPORT_SUFFIXES):
|
|
manifested_code.add(str(name).replace('\\', '/'))
|
|
current_code = _application_code_files(root)
|
|
if current_code != manifested_code:
|
|
added = sorted(current_code - manifested_code)
|
|
removed = sorted(manifested_code - current_code)
|
|
detail = added[0] if added else removed[0] if removed else 'unknown'
|
|
raise RuntimeError(f'application code authority file set drifted: {detail}')
|
|
for group_name, values in (('executable', executables), ('asset', assets)):
|
|
for name, value in values.items():
|
|
if not isinstance(value, dict):
|
|
raise RuntimeError(f'child {group_name} authority entry is invalid')
|
|
path = _canonical(value.get('path') or '')
|
|
if not os.path.isabs(path) or not hmac.compare_digest(_sha256_file(path), str(value.get('sha256') or '')):
|
|
raise RuntimeError(f'child {group_name} authority drifted: {name}')
|
|
return root
|
|
|
|
|
|
def _handshake(metadata):
|
|
control = metadata.get('control') or {}
|
|
request = {
|
|
'schema': CONTROL_SCHEMA,
|
|
'instance_id': metadata['instance_id'],
|
|
'token': metadata['token'],
|
|
'action': 'handshake',
|
|
}
|
|
encoded = json.dumps(request, ensure_ascii=True, separators=(',', ':')).encode('utf-8') + b'\n'
|
|
chunks = []
|
|
total = 0
|
|
with socket.create_connection((control.get('host'), int(control.get('port') or 0)), timeout=3) as client:
|
|
client.settimeout(3)
|
|
client.sendall(encoded)
|
|
client.shutdown(socket.SHUT_WR)
|
|
while True:
|
|
chunk = client.recv(65536)
|
|
if not chunk:
|
|
break
|
|
total += len(chunk)
|
|
if total > MAX_HANDSHAKE_BYTES:
|
|
raise RuntimeError('supervisor handshake exceeds its byte bound')
|
|
chunks.append(chunk)
|
|
response = json.loads(b''.join(chunks).decode('utf-8'))
|
|
if (
|
|
not isinstance(response, dict)
|
|
or response.get('schema') != CONTROL_SCHEMA
|
|
or response.get('instance_id') != metadata['instance_id']
|
|
or response.get('ok') is not True
|
|
or not isinstance(response.get('result'), dict)
|
|
):
|
|
raise RuntimeError('authenticated supervisor handshake failed')
|
|
return response['result']
|
|
|
|
|
|
def _authenticate(kind):
|
|
inherited = {name: str(os.getenv(variable) or '') for name, variable in ENV.items()}
|
|
if not all(inherited.values()):
|
|
raise RuntimeError('direct mutation is retired; use an authenticated active supervisor command')
|
|
if inherited['kind'] != kind:
|
|
raise RuntimeError('supervised child kind does not match the bootstrap entrypoint')
|
|
instance_file = _canonical(inherited['instance_file'])
|
|
metadata = _read_object(instance_file)
|
|
if metadata.get('schema') != 2 or _canonical(metadata.get('instance_file') or '') != instance_file:
|
|
raise RuntimeError('supervisor child instance metadata is invalid')
|
|
for key in ('instance_id', 'token', 'config_sha256', 'supervisor_sha256', 'code_manifest_sha256'):
|
|
if not hmac.compare_digest(str(metadata.get(key) or ''), inherited[key]):
|
|
raise RuntimeError(f'supervisor child {key} authority mismatch')
|
|
if str(metadata.get('activation_state') or '').upper() != 'ACTIVE':
|
|
raise RuntimeError('supervisor is not ACTIVE; child launch is refused')
|
|
if not hmac.compare_digest(_sha256_file(metadata['config_path']), inherited['config_sha256']):
|
|
raise RuntimeError('supervisor config authority drifted')
|
|
if not hmac.compare_digest(_sha256_file(metadata['supervisor_path']), inherited['supervisor_sha256']):
|
|
raise RuntimeError('supervisor script authority drifted')
|
|
root = _verify_manifest(metadata, inherited)
|
|
dsn = str(os.getenv('TRUF_MANAGED_POSTGRES_DSN') or '')
|
|
if kind == 'janitor':
|
|
if dsn or os.getenv('SCANNER_DB_URL') or os.getenv('DATABASE_URL'):
|
|
raise RuntimeError('janitor child must not receive database mutation capability')
|
|
else:
|
|
dsn_digest = hashlib.sha256(dsn.encode('utf-8')).hexdigest() if dsn else ''
|
|
if not dsn or not hmac.compare_digest(dsn_digest, inherited['dsn_sha256']):
|
|
raise RuntimeError('managed PostgreSQL DSN authority mismatch')
|
|
for variable in ('SCANNER_DB_URL', 'DATABASE_URL'):
|
|
if not hmac.compare_digest(str(os.getenv(variable) or ''), dsn):
|
|
raise RuntimeError(f'{variable} does not match managed PostgreSQL authority')
|
|
handshake = _handshake(metadata)
|
|
if handshake.get('activation_state') != 'ACTIVE' or handshake.get('instance_id') != metadata['instance_id']:
|
|
raise RuntimeError('supervisor handshake did not confirm ACTIVE authority')
|
|
for key in ('config_sha256', 'supervisor_sha256', 'code_manifest_sha256'):
|
|
if not hmac.compare_digest(str(handshake.get(key) or ''), inherited[key]):
|
|
raise RuntimeError(f'supervisor handshake {key} mismatch')
|
|
if not hmac.compare_digest(str(handshake.get('canonical_dsn_sha256') or ''), inherited['dsn_sha256']):
|
|
raise RuntimeError('supervisor handshake PostgreSQL authority mismatch')
|
|
return root, metadata
|
|
|
|
|
|
def main():
|
|
if not sys.flags.isolated or not sys.flags.no_site or not sys.flags.dont_write_bytecode:
|
|
raise RuntimeError('supervised child bootstrap requires isolated no-site bytecode-free startup (-I -S -B)')
|
|
if len(sys.argv) < 2:
|
|
raise SystemExit('supervised child bootstrap kind is required')
|
|
kind = str(sys.argv[1]).strip().lower()
|
|
root, metadata = _authenticate(kind)
|
|
_enable_dependency_paths(kind)
|
|
arguments = list(sys.argv[2:])
|
|
if kind != 'keycheck-provider' and arguments[:1] == ['--']:
|
|
arguments.pop(0)
|
|
if kind in ('scanner', 'discovery-producer'):
|
|
entrypoint = os.path.join(root, 'console_runner.py')
|
|
elif kind == 'docker-shadow':
|
|
entrypoint = os.path.join(root, 'docker_shadow.py')
|
|
elif kind == 'keycheck':
|
|
entrypoint = os.path.join(root, 'keycheck_runner.py')
|
|
elif kind == 'dashboard':
|
|
entrypoint = os.path.join(root, 'dashboard.py')
|
|
elif kind == 'janitor':
|
|
entrypoint = os.path.join(root, 'janitor.py')
|
|
elif kind == 'result-ingester':
|
|
entrypoint = os.path.join(root, 'result_ingester.py')
|
|
elif kind == 'jsonl-projector':
|
|
entrypoint = os.path.join(root, 'jsonl_projector.py')
|
|
elif kind == 'worker-api':
|
|
entrypoint = os.path.join(root, 'worker_api.py')
|
|
elif kind == 'keycheck-provider':
|
|
if not arguments:
|
|
raise RuntimeError('keycheck provider bootstrap entrypoint is required')
|
|
relative = arguments.pop(0).replace('\\', '/')
|
|
if not arguments or arguments.pop(0) != '--':
|
|
raise RuntimeError('keycheck provider bootstrap separator is required')
|
|
if '--' in arguments:
|
|
raise RuntimeError('duplicate keycheck provider bootstrap separator')
|
|
entrypoint = _canonical(os.path.join(root, *relative.split('/')))
|
|
provider_root = _canonical(os.path.join(root, 'keycheckers'))
|
|
try:
|
|
allowed = os.path.commonpath((provider_root, entrypoint)) == provider_root
|
|
except ValueError:
|
|
allowed = False
|
|
if not allowed or not relative.lower().endswith('.py'):
|
|
raise RuntimeError('keycheck provider bootstrap entrypoint is outside authority')
|
|
else:
|
|
raise RuntimeError('unsupported supervised child bootstrap kind')
|
|
entrypoint = _canonical(entrypoint)
|
|
files = (metadata.get('code_manifest') or {}).get('files') or {}
|
|
if not any(_canonical(value.get('path') or '') == entrypoint for value in files.values() if isinstance(value, dict)):
|
|
raise RuntimeError('child entrypoint is absent from immutable authority')
|
|
sys.path.insert(0, root)
|
|
try:
|
|
if kind == 'dashboard':
|
|
sys.argv = ['streamlit', 'run', entrypoint, *arguments]
|
|
runpy.run_module('streamlit', run_name='__main__', alter_sys=True)
|
|
else:
|
|
sys.argv = [entrypoint, *arguments]
|
|
runpy.run_path(entrypoint, run_name='__main__')
|
|
except Exception as exc:
|
|
raise ChildRuntimeError(str(exc)) from exc
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except ChildRuntimeError as exc:
|
|
raise SystemExit(f'supervised child runtime failed: {exc}') from exc
|
|
except Exception as exc:
|
|
raise SystemExit(f'supervised child bootstrap rejected launch: {exc}') from exc
|