#!/usr/bin/env bash
# =============================================================================
# fwh-pyrun —— 在 bwrap 沙箱中运行 Python3，供 fwh-toolbox-agent 安全调用
# =============================================================================
# 用法：
#   fwh-pyrun /tmp/fwh-toolbox-agent/<脚本>.py [args...]
#
# 安全模型（三层）：
#   B（强制隔离，bwrap）：只读挂载系统目录以支撑 Python 运行；仅沙箱目录
#     /tmp/fwh-toolbox-agent 可写；--unshare-net 断网；--unshare-all 降权。
#     脚本在笼子里“看不见” /home、项目目录、凭据等，越界访问会直接失败。
#   A（准入校验）：脚本必须位于沙箱目录内、以 .py 结尾、真实存在；解析真实
#     路径以防 ../ 与软链逃逸。（agent.json 的 allowedCommands 已在更外层拦截）
#   C（资源限额，ulimit + timeout）：限制 CPU / 内存 / 单文件大小 / 进程数，
#     外层 timeout 兜底防死循环。
#
# 退出码：
#   2  入参非法 / 路径越界 / 脚本不存在
#   124 运行超时（timeout 触发）
#   其它 透传 Python 退出码
# =============================================================================
set -euo pipefail

SANDBOX_ROOT="/tmp/fwh-toolbox-agent"
PY="/usr/bin/python3"

# --- 依赖自检（部署脚本应已确保安装；此处兜底给出清晰报错） ---
command -v bwrap >/dev/null 2>&1 || { echo "fwh-pyrun: 未找到 bwrap（bubblewrap），无法建立沙箱" >&2; exit 2; }
[ -x "$PY" ] || { echo "fwh-pyrun: 未找到 python3（$PY）" >&2; exit 2; }

# --- A：入参与路径校验 ---
script="${1:-}"
[ -n "$script" ] || { echo "fwh-pyrun: 缺少脚本路径。用法: fwh-pyrun $SANDBOX_ROOT/<脚本>.py [args...]" >&2; exit 2; }
shift || true

# 解析真实绝对路径，防止 ../ 或软链逃逸
real_script="$(readlink -f -- "$script" 2>/dev/null)" || { echo "fwh-pyrun: 无法解析路径：$script" >&2; exit 2; }
case "$real_script" in
  "$SANDBOX_ROOT"/*.py) : ;;   # 合法：沙箱内 .py
  *) echo "fwh-pyrun: 拒绝——脚本必须位于 $SANDBOX_ROOT 内且以 .py 结尾（实际：$real_script）" >&2; exit 2 ;;
esac
[ -f "$real_script" ] || { echo "fwh-pyrun: 脚本不存在：$real_script" >&2; exit 2; }

# 确保沙箱目录存在（幂等）
mkdir -p "$SANDBOX_ROOT"

# --- C：资源限额 ---
ulimit -t 30          # CPU 时间上限 30 秒
ulimit -v 1048576     # 虚拟内存上限 1GB（KB）
ulimit -f 262144      # 单文件写入上限 ~128MB（blocks，512B/block）
ulimit -c 0           # 不产生 core dump
# 注：不在此处收紧 ulimit -u（进程数按整个用户计，过紧会导致 timeout/bwrap
# 自身 fork 失败）；fork 炸弹由 bwrap 的 PID namespace 隔离 + CPU/内存限额抑制。

# --- B：bwrap 强制隔离 + 外层 timeout 兜底（总运行 ≤ 60s） ---
# 只读挂载 Python 运行所需系统目录；仅沙箱目录可写；无网络；降权。
# 用数组构造参数，避免路径含特殊字符时被拆分。
bwrap_args=(
  --ro-bind /usr /usr
  --ro-bind /etc /etc
  --bind "$SANDBOX_ROOT" "$SANDBOX_ROOT"
  --proc /proc
  --dev /dev
  --unshare-all
  --unshare-net
  --die-with-parent
  --new-session
  --setenv HOME "$SANDBOX_ROOT"
  --setenv TMPDIR "$SANDBOX_ROOT"
  --setenv PYTHONDONTWRITEBYTECODE 1
  --setenv PYTHONPATH ""
  --chdir "$SANDBOX_ROOT"
)
# /lib /lib64 /bin /sbin 在多数发行版是指向 /usr 的符号链接；存在时才只读挂载
for d in /lib /lib64 /bin /sbin; do
  [ -e "$d" ] && bwrap_args+=( --ro-bind "$d" "$d" )
done

exec timeout --kill-after=5s 60s \
  bwrap "${bwrap_args[@]}" -- "$PY" -I -B "$real_script" "$@"
