#!/usr/bin/env bash set -Eeuo pipefail action=${1:-} source_dir=${2:-} target_user=${3:-jwebot} profile_id=${4:-default} runtime_root=${JWEBOT_RUNTIME_ROOT:-/opt/jwebot} runtime_python="${runtime_root}/venv/bin/python" usage() { cat >&2 <<'EOF' usage: bootstrap-jwebot.sh prepare SOURCE_DIR [TARGET_USER] [PROFILE] bootstrap-jwebot.sh verify SOURCE_DIR [TARGET_USER] [PROFILE] bootstrap-jwebot.sh deploy SOURCE_DIR [TARGET_USER] [PROFILE] EOF exit 64 } [[ -n "${action}" && -n "${source_dir}" ]] || usage [[ "${target_user}" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || { echo "[JWeBot] error: invalid target user" >&2 exit 1 } [[ "${profile_id}" =~ ^[a-z][a-z0-9-]{0,47}$ ]] || { echo "[JWeBot] error: invalid profile id" >&2 exit 1 } [[ "${EUID}" -eq 0 ]] || { echo "[JWeBot] error: bootstrap must run as root" >&2 exit 1 } [[ -d "${source_dir}" ]] || { echo "[JWeBot] error: source directory is unavailable" >&2 exit 1 } progress() { local step=$1 local total=$2 local label=$3 local width=20 local filled=$((step * width / total)) local empty=$((width - filled)) local bar padding printf -v bar '%*s' "${filled}" '' printf -v padding '%*s' "${empty}" '' printf '[JWeBot] [%s%s] %3d%% %s\n' \ "${bar// /#}" "${padding// /-}" "$((step * 100 / total))" "${label}" } account_home() { getent passwd "${target_user}" | cut -d: -f6 } as_runtime_user() { local home home=$(account_home) /usr/sbin/runuser -u "${target_user}" -- /usr/bin/env \ HOME="${home}" \ JWEBOT_CONFIG_ROOT="${home}/.config/jwebot" \ JWEBOT_CONFIG_STATE_ROOT="${home}/.local/state/jwebot" \ "$@" } prepare() { progress 1 6 "operating-system packages" command -v apt-get >/dev/null 2>&1 || { echo "[JWeBot] error: automatic prepare currently supports apt-based systems" >&2 exit 1 } apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y \ python3 \ python3-pip \ python3-venv \ libx11-6 \ libxdamage1 \ scrot \ tesseract-ocr \ tesseract-ocr-chi-sim \ wmctrl \ xclip \ xdotool progress 2 6 "runtime account" if ! id "${target_user}" >/dev/null 2>&1; then useradd --create-home --shell /bin/bash "${target_user}" fi target_home=$(account_home) [[ -n "${target_home}" && -d "${target_home}" ]] progress 3 6 "python environment" install -d -o root -g root -m 0755 "${runtime_root}" if [[ ! -x "${runtime_python}" ]]; then python3 -m venv "${runtime_root}/venv" fi "${runtime_python}" -m pip install --upgrade pip "${runtime_python}" -m pip install -r "${source_dir}/requirements-runtime.txt" "${runtime_python}" -m pip install \ "${source_dir}/packages/jwebot-config" \ "${source_dir}/packages/jwebot-sdk" \ "${source_dir}/packages/jwebot-official-extensions" \ "${source_dir}/packages/jwebot-kernel" \ "${source_dir}/packages/jwebot-extension-host" progress 4 6 "configuration profile" if [[ ! -L "${target_home}/.config/jwebot/current" ]]; then as_runtime_user "${runtime_python}" -m jwebot_config \ init "${profile_id}" --activate else as_runtime_user "${runtime_python}" -m jwebot_config \ validate "${profile_id}" fi progress 5 6 "private state permissions" install -d -o "${target_user}" -g "${target_user}" -m 0700 \ "${target_home}/.config/jwebot" \ "${target_home}/.local/state/jwebot" \ "${target_home}/.local/state/jwebot-edge" \ "${target_home}/.local/state/wechat-adaptive-ocr" progress 6 6 "prepare ready" cat </dev/null target_home=$(account_home) [[ -d "${target_home}" ]] progress 2 5 "runtime dependencies" for command in xdotool scrot xclip wmctrl tesseract; do command -v "${command}" >/dev/null done "${runtime_python}" -c \ 'import cv2,numpy,PIL,rapidocr,jwebot_config,jwebot_kernel' progress 3 5 "configuration profile" as_runtime_user "${runtime_python}" -m jwebot_config validate "${profile_id}" as_runtime_user "${runtime_python}" -m jwebot_config activate "${profile_id}" progress 4 5 "repository gate" PATH="${runtime_root}/venv/bin:${PATH}" \ "${source_dir}/tools/check-config.sh" PATH="${runtime_root}/venv/bin:${PATH}" \ "${source_dir}/tools/check-secrets.py" progress 5 5 "desktop prerequisites" /usr/sbin/runuser -u "${target_user}" -- /usr/bin/env \ HOME="${target_home}" \ DISPLAY=:1 \ XAUTHORITY="${target_home}/.Xauthority" \ xdotool search --onlyvisible --class wechat >/dev/null echo "[JWeBot] verify ready" } deploy() { verify progress 1 2 "coordinated deployment" JWEBOT_RUNTIME_PYTHON="${runtime_python}" \ "${source_dir}/deploy/scripts/deploy-jwebot-production.sh" \ "${source_dir}" "${target_user}" progress 2 2 "running" } case "${action}" in prepare) prepare ;; verify) verify ;; deploy) deploy ;; *) usage ;; esac