58 строки
1.4 KiB
Python
58 строки
1.4 KiB
Python
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
DATA_DIR = Path(os.getenv('DEVCONSOLE_DATA_DIR', '/var/lib/devconsole'))
|
|
PROJECTS_DIR = DATA_DIR / 'projects'
|
|
|
|
BLOCKED_TOKENS = [
|
|
'rm -rf /',
|
|
'mkfs',
|
|
':(){',
|
|
'dd if=',
|
|
'shutdown',
|
|
'reboot',
|
|
'poweroff',
|
|
]
|
|
|
|
|
|
def _is_inside_projects(path: Path) -> bool:
|
|
try:
|
|
path.resolve().relative_to(PROJECTS_DIR.resolve())
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def run_command(command: str, cwd: str | None = None, timeout: int = 900) -> dict:
|
|
if not command.strip():
|
|
raise ValueError('Command is empty')
|
|
|
|
lowered = command.lower()
|
|
for token in BLOCKED_TOKENS:
|
|
if token in lowered:
|
|
raise ValueError(f'Blocked unsafe command: {token}')
|
|
|
|
workdir = Path(cwd or PROJECTS_DIR).resolve()
|
|
PROJECTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
if not _is_inside_projects(workdir):
|
|
raise ValueError('Command cwd must be inside DevConsole projects directory')
|
|
|
|
process = subprocess.run(
|
|
command,
|
|
cwd=workdir,
|
|
shell=True,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=timeout,
|
|
)
|
|
|
|
return {
|
|
'command': command,
|
|
'cwd': workdir.as_posix(),
|
|
'returncode': process.returncode,
|
|
'stdout': process.stdout[-20000:],
|
|
'stderr': process.stderr[-20000:],
|
|
}
|