Spaces:
Paused
Paused
| from __future__ import annotations | |
| import re | |
| from pathlib import Path | |
| from sysadmin_env.models import DiagnosticTrigger | |
| from sysadmin_env.models import DifficultyTier | |
| from sysadmin_env.models import TaskMetadata | |
| from sysadmin_env.models import TaskScenarioDefinition | |
| from sysadmin_env.models import TaskScenarioState | |
| TASK_ID = "nginx_crash" | |
| COMPLETION_HEALTH = 0.99 | |
| PID_PATH = Path("var/run/nginx.pid") | |
| CONFIG_PATH = Path("etc/nginx/nginx.conf") | |
| ERROR_LOG_PATH = Path("var/log/nginx/error.log") | |
| RUNNING_FLAG_PATH = Path("run/nginx.running") | |
| PORT_FLAG_PATH = Path("run/nginx.port") | |
| BROKEN_CONFIG = """worker_processes 1; | |
| events { | |
| worker_connections 128; | |
| } | |
| http { | |
| server { | |
| listen 8080 | |
| location / { | |
| return 200 ok; | |
| } | |
| } | |
| } | |
| """ | |
| FIXED_CONFIG = """worker_processes 1; | |
| events { | |
| worker_connections 128; | |
| } | |
| http { | |
| server { | |
| listen 8080; | |
| location / { | |
| return 200 ok; | |
| } | |
| } | |
| } | |
| """ | |
| ERROR_LOG = "nginx emerg unexpected end of statement in /etc/nginx/nginx.conf line 7\n" | |
| def build_definition(base_filesystem_path: str) -> TaskScenarioDefinition: | |
| metadata = TaskMetadata( | |
| task_id=TASK_ID, | |
| difficulty=DifficultyTier.easy, | |
| description="nginx crashed with stale pid and config syntax error", | |
| max_steps=40, | |
| time_limit=300.0, | |
| base_filesystem_path=base_filesystem_path, | |
| ) | |
| return TaskScenarioDefinition( | |
| metadata=metadata, | |
| requires_network_isolation=False, | |
| diagnostic_triggers=diagnostic_triggers(), | |
| ) | |
| def diagnostic_triggers() -> list[DiagnosticTrigger]: | |
| return [ | |
| DiagnosticTrigger( | |
| fact_id="nginx_error_log_checked", | |
| command_patterns=[r"cat\s+.+error\.log", r"tail\s+.+error\.log", r"grep\s+.+error\.log"], | |
| reward=0.05, | |
| ), | |
| DiagnosticTrigger( | |
| fact_id="nginx_config_tested", | |
| command_patterns=[r"nginx\s+-t", r"/usr/sbin/nginx\s+-t"], | |
| reward=0.08, | |
| ), | |
| DiagnosticTrigger( | |
| fact_id="nginx_pid_checked", | |
| command_patterns=[r"cat\s+.+nginx\.pid", r"sed\s+.+nginx\.pid"], | |
| reward=0.04, | |
| ), | |
| DiagnosticTrigger( | |
| fact_id="nginx_process_table_checked", | |
| command_patterns=[r"ps\b.*nginx", r"pgrep\b.*nginx"], | |
| reward=0.04, | |
| ), | |
| ] | |
| def prepare_filesystem(root: str | Path) -> None: | |
| root_path = Path(root) | |
| for relative in [ | |
| Path("etc/nginx"), | |
| Path("var/run"), | |
| Path("var/log/nginx"), | |
| Path("run"), | |
| Path("usr/local/bin"), | |
| Path("root"), | |
| Path("tmp"), | |
| Path("home"), | |
| ]: | |
| (root_path / relative).mkdir(parents=True, exist_ok=True) | |
| (root_path / CONFIG_PATH).write_text(BROKEN_CONFIG) | |
| (root_path / PID_PATH).write_text("424242\n") | |
| (root_path / ERROR_LOG_PATH).write_text(ERROR_LOG) | |
| (root_path / RUNNING_FLAG_PATH).write_text("stopped\n") | |
| (root_path / PORT_FLAG_PATH).write_text("8080\n") | |
| _write_executable(root_path / "usr/local/bin/nginx", _nginx_stub()) | |
| _write_executable(root_path / "usr/local/bin/curl", _curl_stub()) | |
| _write_executable(root_path / "usr/local/bin/ps", _ps_stub()) | |
| _write_executable(root_path / "usr/local/bin/pgrep", _pgrep_stub()) | |
| _write_executable(root_path / "usr/local/bin/service", _service_stub()) | |
| _write_executable(root_path / "usr/local/bin/systemctl", _systemctl_stub()) | |
| def inject_fault(root: str | Path) -> None: | |
| prepare_filesystem(root) | |
| def observe_command(root: str | Path, command: str, _result) -> None: | |
| root_path = Path(root) | |
| if re.search(r"\b(service|systemctl)\b.*\bstatus\b", command, flags=re.IGNORECASE): | |
| if _service_is_running(root_path): | |
| (root_path / RUNNING_FLAG_PATH).write_text("running\n") | |
| else: | |
| (root_path / RUNNING_FLAG_PATH).write_text("stopped\n") | |
| def synchronize(root: str | Path) -> None: | |
| root_path = Path(root) | |
| running_flag = root_path / RUNNING_FLAG_PATH | |
| if not running_flag.exists(): | |
| running_flag.write_text("stopped\n") | |
| def grade(root: str | Path) -> TaskScenarioState: | |
| root_path = Path(root) | |
| stale_pid_removed = _stale_pid_cleared(root_path / PID_PATH) | |
| config_fixed = _config_is_fixed(root_path / CONFIG_PATH) | |
| running = _service_is_running(root_path) | |
| health = 0.0 | |
| if stale_pid_removed: | |
| health += 0.25 | |
| if config_fixed: | |
| health += 0.35 | |
| if running: | |
| health += 0.39 | |
| if running: | |
| health = COMPLETION_HEALTH | |
| return TaskScenarioState( | |
| health=health, | |
| done=running, | |
| details={ | |
| "stale_pid_removed": stale_pid_removed, | |
| "config_fixed": config_fixed, | |
| "service_running": running, | |
| }, | |
| ) | |
| def command_reveals_fact(command: str, trigger: DiagnosticTrigger) -> bool: | |
| return any(re.search(pattern, command, flags=re.IGNORECASE) for pattern in trigger.command_patterns) | |
| def _config_is_fixed(config_path: Path) -> bool: | |
| if not config_path.exists(): | |
| return False | |
| config_text = config_path.read_text() | |
| return re.search(r"listen\s+8080\s*;", config_text) is not None | |
| def _stale_pid_cleared(pid_path: Path) -> bool: | |
| if not pid_path.exists(): | |
| return True | |
| return pid_path.read_text().strip() == "1234" | |
| def _service_is_running(root_path: Path) -> bool: | |
| if not _config_is_fixed(root_path / CONFIG_PATH): | |
| return False | |
| running_flag = root_path / RUNNING_FLAG_PATH | |
| return running_flag.exists() and running_flag.read_text().strip() == "running" | |
| def _write_executable(path: Path, content: str) -> None: | |
| path.write_text(content) | |
| path.chmod(0o755) | |
| def _nginx_stub() -> str: | |
| return """#!/bin/sh | |
| config="/etc/nginx/nginx.conf" | |
| pidfile="/var/run/nginx.pid" | |
| statefile="/run/nginx.running" | |
| if [ "$1" = "-t" ]; then | |
| if grep -Eq 'listen[[:space:]]+8080;' "$config"; then | |
| echo "nginx config ok" | |
| exit 0 | |
| fi | |
| printf '%s\n' "nginx emerg unexpected end of statement in /etc/nginx/nginx.conf line 7" >&2 | |
| exit 1 | |
| fi | |
| if [ "$1" = "-s" ] && [ "$2" = "stop" ]; then | |
| rm -f "$pidfile" | |
| printf '%s\n' stopped > "$statefile" | |
| exit 0 | |
| fi | |
| if [ -f "$pidfile" ] && [ "$(cat "$pidfile" 2>/dev/null)" != "1234" ]; then | |
| printf '%s\n' "nginx stale pid present" >&2 | |
| exit 1 | |
| fi | |
| if grep -Eq 'listen[[:space:]]+8080;' "$config"; then | |
| printf '%s\n' running > "$statefile" | |
| printf '%s\n' 1234 > "$pidfile" | |
| printf '%s\n' "nginx started" | |
| exit 0 | |
| fi | |
| printf '%s\n' "nginx failed to start" >&2 | |
| exit 1 | |
| """ | |
| def _curl_stub() -> str: | |
| return """#!/bin/sh | |
| state="$(cat /run/nginx.running 2>/dev/null || printf '%s' stopped)" | |
| if [ "$state" = "running" ]; then | |
| printf '%s\n' "HTTP/1.1 200 ok" | |
| printf '%s\n' "server: nginx" | |
| exit 0 | |
| fi | |
| printf '%s\n' "curl failed to connect" >&2 | |
| exit 7 | |
| """ | |
| def _ps_stub() -> str: | |
| return """#!/bin/sh | |
| printf '%s\n' "USER PID CMD" | |
| state="$(cat /run/nginx.running 2>/dev/null || printf '%s' stopped)" | |
| if [ "$state" = "running" ]; then | |
| printf '%s\n' "root 1234 nginx" | |
| fi | |
| exit 0 | |
| """ | |
| def _pgrep_stub() -> str: | |
| return """#!/bin/sh | |
| state="$(cat /run/nginx.running 2>/dev/null || printf '%s' stopped)" | |
| if [ "$state" = "running" ]; then | |
| printf '%s\n' 1234 | |
| exit 0 | |
| fi | |
| exit 1 | |
| """ | |
| def _service_stub() -> str: | |
| return """#!/bin/sh | |
| name="$1" | |
| action="$2" | |
| if [ "$name" = "nginx" ] && [ "$action" = "start" ]; then | |
| exec nginx | |
| fi | |
| if [ "$name" = "nginx" ] && [ "$action" = "stop" ]; then | |
| exec nginx -s stop | |
| fi | |
| if [ "$name" = "nginx" ] && [ "$action" = "status" ]; then | |
| state="$(cat /run/nginx.running 2>/dev/null || printf '%s' stopped)" | |
| if [ "$state" = "running" ]; then | |
| printf '%s\n' "nginx active" | |
| exit 0 | |
| fi | |
| printf '%s\n' "nginx inactive" >&2 | |
| exit 3 | |
| fi | |
| printf '%s\n' "unsupported service action" >&2 | |
| exit 1 | |
| """ | |
| def _systemctl_stub() -> str: | |
| return """#!/bin/sh | |
| action="$1" | |
| name="$2" | |
| if [ "$name" = "nginx" ] && [ "$action" = "start" ]; then | |
| exec nginx | |
| fi | |
| if [ "$name" = "nginx" ] && [ "$action" = "stop" ]; then | |
| exec nginx -s stop | |
| fi | |
| if [ "$name" = "nginx" ] && [ "$action" = "restart" ]; then | |
| nginx -s stop >/dev/null 2>&1 || true | |
| exec nginx | |
| fi | |
| if [ "$name" = "nginx" ] && [ "$action" = "status" ]; then | |
| exec service nginx status | |
| fi | |
| printf '%s\n' "unsupported systemctl action" >&2 | |
| exit 1 | |
| """ | |