release: public alpha 0.6.0 baseline
This commit is contained in:
Executable
+180
@@ -0,0 +1,180 @@
|
||||
#!/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 <<EOF
|
||||
Edit and activate:
|
||||
${target_home}/.config/jwebot/profiles/${profile_id}/
|
||||
|
||||
Then install and sign in to desktop WeChat on display :1 before running:
|
||||
$0 verify ${source_dir} ${target_user} ${profile_id}
|
||||
EOF
|
||||
}
|
||||
|
||||
verify() {
|
||||
progress 1 5 "runtime account"
|
||||
id "${target_user}" >/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
|
||||
Executable
+628
@@ -0,0 +1,628 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
source_dir=${1:?usage: deploy-jwebot-production.sh SOURCE_DIR [TARGET_USER]}
|
||||
target_user=${2:-jwebot}
|
||||
[[ "${target_user}" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || {
|
||||
echo "[JWeBot] error: invalid target user" >&2
|
||||
exit 1
|
||||
}
|
||||
target_uid=$(id -u "${target_user}")
|
||||
target_gid=$(id -g "${target_user}")
|
||||
target_home=$(getent passwd "${target_user}" | cut -d: -f6)
|
||||
user_runtime="/run/user/${target_uid}"
|
||||
runtime_python=${JWEBOT_RUNTIME_PYTHON:-/opt/jwebot/venv/bin/python}
|
||||
require_wechat_ready=${REQUIRE_WECHAT_READY:-1}
|
||||
wechat_ready_timeout_seconds=${WECHAT_READY_TIMEOUT_SECONDS:-30}
|
||||
health_settle_seconds=${HEALTH_SETTLE_SECONDS:-8}
|
||||
backup_root=${JWEBOT_PRODUCTION_BACKUP_ROOT:-/var/backups/jwebot-production}
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
backup_dir="${backup_root}/${timestamp}-$$"
|
||||
preflight_rendered_dir=""
|
||||
|
||||
target_runtime="${target_home}/.local/lib/wechat-health-watch"
|
||||
target_user_units="${target_home}/.config/systemd/user"
|
||||
target_user_config="${target_home}/.config/jwebot"
|
||||
target_edge_state="${target_home}/.local/state/jwebot-edge"
|
||||
target_adaptive_state="${target_home}/.local/state/wechat-adaptive-ocr"
|
||||
root_ocr=/usr/local/sbin/wechat-local-ocr
|
||||
root_bridge=/usr/local/sbin/wechat-gui-bridge
|
||||
root_recorder_unit=/etc/systemd/system/wechat-local-ocr-recorder.service
|
||||
root_responder_unit=/etc/systemd/system/wechat-local-ocr-responder.service
|
||||
root_defaults=/etc/default/wechat-local-ocr
|
||||
system_state_file="${backup_dir}/metadata/system-state.tsv"
|
||||
user_state_file="${backup_dir}/metadata/user-state.tsv"
|
||||
armed=0
|
||||
success=0
|
||||
|
||||
system_units=(
|
||||
wechat-local-ocr-recorder.service
|
||||
wechat-local-ocr-responder.service
|
||||
)
|
||||
user_units=(
|
||||
jwebot-edge.service
|
||||
wechat-ocr-adaptive-recorder.service
|
||||
wechat-health-watch.timer
|
||||
jwebot-edge-memory-sync.timer
|
||||
wechat-health-watch.service
|
||||
jwebot-edge-memory-sync.service
|
||||
)
|
||||
|
||||
jwebot_log() {
|
||||
printf '[JWeBot] %s\n' "$*"
|
||||
}
|
||||
|
||||
jwebot_progress() {
|
||||
local step=$1
|
||||
local total=$2
|
||||
local label=$3
|
||||
local width=20
|
||||
local filled=$((step * width / total))
|
||||
local empty=$((width - filled))
|
||||
local bar
|
||||
printf -v bar '%*s' "${filled}" ''
|
||||
bar=${bar// /#}
|
||||
printf -v padding '%*s' "${empty}" ''
|
||||
padding=${padding// /-}
|
||||
printf '[JWeBot] [%s%s] %3d%% %s\n' \
|
||||
"${bar}" "${padding}" "$((step * 100 / total))" "${label}"
|
||||
}
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "[JWeBot] error: production deploy must run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${target_home}" || ! -d "${target_home}" ]]; then
|
||||
echo "[JWeBot] error: target user home is unavailable" >&2
|
||||
exit 1
|
||||
fi
|
||||
jwebot_log "production deploy starting version=$(cat "${source_dir}/VERSION")"
|
||||
jwebot_progress 1 8 "preflight"
|
||||
|
||||
user_systemctl() {
|
||||
/usr/sbin/runuser -u "${target_user}" -- /usr/bin/env \
|
||||
HOME="${target_home}" \
|
||||
XDG_RUNTIME_DIR="${user_runtime}" \
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=${user_runtime}/bus" \
|
||||
systemctl --user "$@"
|
||||
}
|
||||
|
||||
save_system_state() {
|
||||
: >"${system_state_file}"
|
||||
local unit enabled active
|
||||
for unit in "${system_units[@]}"; do
|
||||
enabled=$(systemctl is-enabled "${unit}" 2>/dev/null || true)
|
||||
active=$(systemctl is-active "${unit}" 2>/dev/null || true)
|
||||
printf '%s\t%s\t%s\n' "${unit}" "${enabled:-unknown}" "${active:-unknown}" \
|
||||
>>"${system_state_file}"
|
||||
done
|
||||
}
|
||||
|
||||
save_user_state() {
|
||||
: >"${user_state_file}"
|
||||
local unit enabled active
|
||||
for unit in "${user_units[@]}"; do
|
||||
enabled=$(user_systemctl is-enabled "${unit}" 2>/dev/null || true)
|
||||
active=$(user_systemctl is-active "${unit}" 2>/dev/null || true)
|
||||
printf '%s\t%s\t%s\n' "${unit}" "${enabled:-unknown}" "${active:-unknown}" \
|
||||
>>"${user_state_file}"
|
||||
done
|
||||
}
|
||||
|
||||
state_field() {
|
||||
local file="$1"
|
||||
local unit="$2"
|
||||
local field="$3"
|
||||
awk -F '\t' -v unit="${unit}" -v field="${field}" \
|
||||
'$1 == unit { print $field; exit }' "${file}"
|
||||
}
|
||||
|
||||
restore_enable_state() {
|
||||
local scope="$1"
|
||||
local file="$2"
|
||||
shift 2
|
||||
local unit enabled
|
||||
for unit in "$@"; do
|
||||
enabled=$(state_field "${file}" "${unit}" 2 2>/dev/null || true)
|
||||
case "${enabled}" in
|
||||
enabled)
|
||||
if [[ "${scope}" == "user" ]]; then
|
||||
user_systemctl unmask "${unit}" >/dev/null 2>&1 || true
|
||||
user_systemctl enable "${unit}" >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl unmask "${unit}" >/dev/null 2>&1 || true
|
||||
systemctl enable "${unit}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
enabled-runtime)
|
||||
if [[ "${scope}" == "user" ]]; then
|
||||
user_systemctl unmask "${unit}" >/dev/null 2>&1 || true
|
||||
user_systemctl enable --runtime "${unit}" >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl unmask "${unit}" >/dev/null 2>&1 || true
|
||||
systemctl enable --runtime "${unit}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
disabled)
|
||||
if [[ "${scope}" == "user" ]]; then
|
||||
user_systemctl unmask "${unit}" >/dev/null 2>&1 || true
|
||||
user_systemctl disable "${unit}" >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl unmask "${unit}" >/dev/null 2>&1 || true
|
||||
systemctl disable "${unit}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
masked)
|
||||
if [[ "${scope}" == "user" ]]; then
|
||||
user_systemctl mask "${unit}" >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl mask "${unit}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
masked-runtime)
|
||||
if [[ "${scope}" == "user" ]]; then
|
||||
user_systemctl mask --runtime "${unit}" >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl mask --runtime "${unit}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
restore_active_state() {
|
||||
local scope="$1"
|
||||
local file="$2"
|
||||
shift 2
|
||||
local unit active
|
||||
for unit in "$@"; do
|
||||
active=$(state_field "${file}" "${unit}" 3 2>/dev/null || true)
|
||||
case "${active}" in
|
||||
active|activating|reloading)
|
||||
if [[ "${scope}" == "user" ]]; then
|
||||
user_systemctl start "${unit}" >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl start "${unit}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
backup_file() {
|
||||
local source="$1"
|
||||
local name="$2"
|
||||
if [[ -e "${source}" || -L "${source}" ]]; then
|
||||
cp -a "${source}" "${backup_dir}/root/${name}"
|
||||
: >"${backup_dir}/root/${name}.present"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_file() {
|
||||
local target="$1"
|
||||
local name="$2"
|
||||
if [[ -f "${backup_dir}/root/${name}.present" ]]; then
|
||||
rm -rf "${target}"
|
||||
cp -a "${backup_dir}/root/${name}" "${target}"
|
||||
else
|
||||
rm -rf "${target}"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_directory_backup() {
|
||||
local source="$1"
|
||||
local name="$2"
|
||||
if [[ -d "${source}" ]]; then
|
||||
cp -a "${source}" "${backup_dir}/user/${name}"
|
||||
: >"${backup_dir}/user/${name}.present"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_directory_backup() {
|
||||
local target="$1"
|
||||
local name="$2"
|
||||
rm -rf "${target}"
|
||||
if [[ -f "${backup_dir}/user/${name}.present" ]]; then
|
||||
cp -a "${backup_dir}/user/${name}" "${target}"
|
||||
fi
|
||||
}
|
||||
|
||||
queue_count() {
|
||||
local directory="$1"
|
||||
if [[ ! -d "${directory}" ]]; then
|
||||
printf '0\n'
|
||||
return
|
||||
fi
|
||||
find "${directory}" -maxdepth 1 -type f -print | wc -l
|
||||
}
|
||||
|
||||
assert_queues_empty() {
|
||||
local adaptive legacy
|
||||
adaptive=$(queue_count "${target_adaptive_state}/queue")
|
||||
legacy=$(queue_count /var/lib/wechat-local-ocr/queue)
|
||||
if [[ "${adaptive}" -ne 0 || "${legacy}" -ne 0 ]]; then
|
||||
echo "[JWeBot] error: OCR queues must be empty before deployment" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
wechat_geometry() {
|
||||
local identifiers identifier title geometry width height
|
||||
local class_identifiers name_identifiers
|
||||
local best_width=0
|
||||
local best_height=0
|
||||
local display_environment=(
|
||||
HOME="${target_home}"
|
||||
DISPLAY=:1
|
||||
XAUTHORITY="${target_home}/.Xauthority"
|
||||
XDG_RUNTIME_DIR="${user_runtime}"
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=${user_runtime}/bus"
|
||||
)
|
||||
class_identifiers=$(
|
||||
/usr/sbin/runuser -u "${target_user}" -- \
|
||||
/usr/bin/env "${display_environment[@]}" \
|
||||
/usr/bin/xdotool search --onlyvisible --class wechat \
|
||||
2>/dev/null || true
|
||||
)
|
||||
name_identifiers=$(
|
||||
/usr/sbin/runuser -u "${target_user}" -- \
|
||||
/usr/bin/env "${display_environment[@]}" \
|
||||
/usr/bin/xdotool search --onlyvisible --name '^Weixin$' \
|
||||
2>/dev/null || true
|
||||
)
|
||||
identifiers=$(
|
||||
printf '%s\n%s\n' "${class_identifiers}" "${name_identifiers}" \
|
||||
| awk 'NF && !seen[$0]++'
|
||||
)
|
||||
for identifier in ${identifiers}; do
|
||||
title=$(
|
||||
/usr/sbin/runuser -u "${target_user}" -- \
|
||||
/usr/bin/env "${display_environment[@]}" \
|
||||
/usr/bin/xdotool getwindowname "${identifier}" \
|
||||
2>/dev/null || true
|
||||
)
|
||||
[[ "${title}" == "Weixin" ]] || continue
|
||||
geometry=$(
|
||||
/usr/sbin/runuser -u "${target_user}" -- \
|
||||
/usr/bin/env "${display_environment[@]}" \
|
||||
/usr/bin/xdotool getwindowgeometry --shell "${identifier}" \
|
||||
2>/dev/null || true
|
||||
)
|
||||
width=$(sed -n 's/^WIDTH=//p' <<<"${geometry}" | head -n 1)
|
||||
height=$(sed -n 's/^HEIGHT=//p' <<<"${geometry}" | head -n 1)
|
||||
[[ "${width}" =~ ^[0-9]+$ && "${height}" =~ ^[0-9]+$ ]] || continue
|
||||
if (( width * height > best_width * best_height )); then
|
||||
best_width=${width}
|
||||
best_height=${height}
|
||||
fi
|
||||
done
|
||||
if (( best_width > 0 && best_height > 0 )); then
|
||||
printf 'WIDTH=%s\nHEIGHT=%s\n' "${best_width}" "${best_height}"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_wechat_ready() {
|
||||
local geometry="" width="" height=""
|
||||
local attempt=0
|
||||
jwebot_log "WeChat readiness starting timeout=${wechat_ready_timeout_seconds}s"
|
||||
while (( attempt < wechat_ready_timeout_seconds )); do
|
||||
geometry=$(wechat_geometry)
|
||||
width=$(sed -n 's/^WIDTH=//p' <<<"${geometry}" | head -n 1)
|
||||
height=$(sed -n 's/^HEIGHT=//p' <<<"${geometry}" | head -n 1)
|
||||
if [[ "${width}" =~ ^[0-9]+$ && "${height}" =~ ^[0-9]+$ ]] \
|
||||
&& (( width >= 700 && height >= 500 ))
|
||||
then
|
||||
jwebot_log "WeChat ready geometry=${width}x${height}"
|
||||
return 0
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
if (( attempt < wechat_ready_timeout_seconds )); then
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
if [[ "${width}" =~ ^[0-9]+$ && "${height}" =~ ^[0-9]+$ ]]; then
|
||||
echo "[JWeBot] error: WeChat is not ready (${width}x${height})" >&2
|
||||
else
|
||||
echo "[JWeBot] error: WeChat window geometry is unavailable" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_edge_health() {
|
||||
local response
|
||||
response=$(
|
||||
printf '%s\n' '{"op":"health"}' \
|
||||
| /usr/sbin/runuser -u "${target_user}" -- /usr/bin/env \
|
||||
HOME="${target_home}" \
|
||||
"${runtime_python}" "${target_runtime}/jwebot_edge.py" respond
|
||||
)
|
||||
printf '%s' "${response}" \
|
||||
| "${runtime_python}" -c \
|
||||
'import json,sys; value=json.load(sys.stdin); raise SystemExit(0 if value.get("status") == "ok" else 1)'
|
||||
}
|
||||
|
||||
render_root_configuration() {
|
||||
local output_dir="$1"
|
||||
sed \
|
||||
-e "s/^User=.*/User=${target_user}/" \
|
||||
-e "s/^Group=.*/Group=${target_user}/" \
|
||||
-e "s#/opt/jwebot/venv/bin/python#${runtime_python}#g" \
|
||||
"${source_dir}/deploy/systemd/system/wechat-local-ocr-recorder.service" \
|
||||
>"${output_dir}/wechat-local-ocr-recorder.service"
|
||||
sed \
|
||||
-e "s#/opt/jwebot/venv/bin/python#${runtime_python}#g" \
|
||||
"${source_dir}/deploy/systemd/system/wechat-local-ocr-responder.service" \
|
||||
>"${output_dir}/wechat-local-ocr-responder.service"
|
||||
sed \
|
||||
-e "s#^JWEBOT_RUNTIME_USER=.*#JWEBOT_RUNTIME_USER=${target_user}#" \
|
||||
-e "s#^JWEBOT_RUNTIME_HOME=.*#JWEBOT_RUNTIME_HOME=${target_home}#" \
|
||||
"${source_dir}/deploy/defaults/wechat-local-ocr.production.default" \
|
||||
>"${output_dir}/wechat-local-ocr.default"
|
||||
}
|
||||
|
||||
rollback() {
|
||||
local status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if [[ -n "${preflight_rendered_dir}" ]]; then
|
||||
rm -rf "${preflight_rendered_dir}"
|
||||
fi
|
||||
if [[ "${armed}" -eq 1 && "${success}" -eq 0 ]]; then
|
||||
jwebot_log "rollback starting backup=${backup_dir}"
|
||||
set +e
|
||||
user_systemctl stop \
|
||||
wechat-ocr-adaptive-recorder.service \
|
||||
jwebot-edge.service \
|
||||
wechat-health-watch.timer \
|
||||
jwebot-edge-memory-sync.timer \
|
||||
>/dev/null 2>&1
|
||||
systemctl disable --now \
|
||||
wechat-local-ocr-recorder.service \
|
||||
wechat-local-ocr-responder.service \
|
||||
>/dev/null 2>&1
|
||||
|
||||
restore_file "${root_ocr}" wechat-local-ocr
|
||||
restore_file "${root_bridge}" wechat-gui-bridge
|
||||
restore_file "${root_recorder_unit}" wechat-local-ocr-recorder.service
|
||||
restore_file "${root_responder_unit}" wechat-local-ocr-responder.service
|
||||
restore_file "${root_defaults}" wechat-local-ocr.default
|
||||
restore_directory_backup "${target_runtime}" runtime
|
||||
restore_directory_backup "${target_user_units}" systemd-user
|
||||
restore_directory_backup "${target_user_config}" jwebot-config
|
||||
|
||||
if [[ -f "${backup_dir}/state/edge.sqlite3" ]]; then
|
||||
install -d -o "${target_uid}" -g "${target_gid}" -m 0700 \
|
||||
"${target_edge_state}"
|
||||
rm -f \
|
||||
"${target_edge_state}/edge.sqlite3" \
|
||||
"${target_edge_state}/edge.sqlite3-wal" \
|
||||
"${target_edge_state}/edge.sqlite3-shm"
|
||||
install -o "${target_uid}" -g "${target_gid}" -m 0600 \
|
||||
"${backup_dir}/state/edge.sqlite3" \
|
||||
"${target_edge_state}/edge.sqlite3"
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
user_systemctl daemon-reload
|
||||
restore_enable_state system "${system_state_file}" "${system_units[@]}"
|
||||
restore_enable_state user "${user_state_file}" "${user_units[@]}"
|
||||
restore_active_state user "${user_state_file}" "${user_units[@]}"
|
||||
restore_active_state system "${system_state_file}" "${system_units[@]}"
|
||||
systemctl reset-failed
|
||||
user_systemctl reset-failed
|
||||
echo "[JWeBot] rollback ready backup=${backup_dir}" >&2
|
||||
fi
|
||||
exit "${status}"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
jwebot_log "preflight starting"
|
||||
test -x "${runtime_python}"
|
||||
for path in \
|
||||
"${source_dir}/wechat_local_ocr.py" \
|
||||
"${source_dir}/wechat-gui-bridge" \
|
||||
"${source_dir}/deploy/scripts/install-wechat-health-watch.sh" \
|
||||
"${source_dir}/deploy/systemd/system/wechat-local-ocr-recorder.service" \
|
||||
"${source_dir}/deploy/systemd/system/wechat-local-ocr-responder.service" \
|
||||
"${source_dir}/deploy/defaults/wechat-local-ocr.production.default"
|
||||
do
|
||||
test -f "${path}"
|
||||
done
|
||||
bash -n "${source_dir}/wechat-gui-bridge"
|
||||
sh -n "${source_dir}/deploy/scripts/install-wechat-health-watch.sh"
|
||||
"${runtime_python}" -m py_compile "${source_dir}/wechat_local_ocr.py"
|
||||
preflight_rendered_dir=$(mktemp -d)
|
||||
render_root_configuration "${preflight_rendered_dir}"
|
||||
systemd-analyze verify \
|
||||
"${preflight_rendered_dir}/wechat-local-ocr-recorder.service" \
|
||||
"${preflight_rendered_dir}/wechat-local-ocr-responder.service"
|
||||
rm -rf "${preflight_rendered_dir}"
|
||||
preflight_rendered_dir=""
|
||||
assert_queues_empty
|
||||
if [[ "${require_wechat_ready}" -eq 1 ]]; then
|
||||
assert_wechat_ready
|
||||
fi
|
||||
jwebot_log "preflight ready"
|
||||
|
||||
jwebot_progress 2 8 "backup"
|
||||
jwebot_log "backup starting path=${backup_dir}"
|
||||
install -d -o root -g root -m 0700 \
|
||||
"${backup_root}" \
|
||||
"${backup_dir}" \
|
||||
"${backup_dir}/root" \
|
||||
"${backup_dir}/user" \
|
||||
"${backup_dir}/state" \
|
||||
"${backup_dir}/metadata"
|
||||
save_system_state
|
||||
save_user_state
|
||||
backup_file "${root_ocr}" wechat-local-ocr
|
||||
backup_file "${root_bridge}" wechat-gui-bridge
|
||||
backup_file "${root_recorder_unit}" wechat-local-ocr-recorder.service
|
||||
backup_file "${root_responder_unit}" wechat-local-ocr-responder.service
|
||||
backup_file "${root_defaults}" wechat-local-ocr.default
|
||||
copy_directory_backup "${target_runtime}" runtime
|
||||
copy_directory_backup "${target_user_units}" systemd-user
|
||||
copy_directory_backup "${target_user_config}" jwebot-config
|
||||
jwebot_log "backup ready path=${backup_dir}"
|
||||
|
||||
armed=1
|
||||
jwebot_progress 3 8 "stop services"
|
||||
jwebot_log "cutover starting"
|
||||
user_systemctl stop \
|
||||
wechat-ocr-adaptive-recorder.service \
|
||||
jwebot-edge.service \
|
||||
wechat-health-watch.timer \
|
||||
jwebot-edge-memory-sync.timer \
|
||||
wechat-health-watch.service \
|
||||
jwebot-edge-memory-sync.service \
|
||||
>/dev/null 2>&1 || true
|
||||
systemctl stop \
|
||||
wechat-local-ocr-responder.service \
|
||||
wechat-local-ocr-recorder.service \
|
||||
>/dev/null 2>&1 || true
|
||||
assert_queues_empty
|
||||
jwebot_log "services stopped queues=empty"
|
||||
|
||||
if [[ -f "${target_edge_state}/edge.sqlite3" ]]; then
|
||||
"${runtime_python}" - \
|
||||
"${target_edge_state}/edge.sqlite3" \
|
||||
"${backup_dir}/state/edge.sqlite3" <<'PY'
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
source = sqlite3.connect(f"file:{sys.argv[1]}?mode=ro", uri=True)
|
||||
target = sqlite3.connect(sys.argv[2])
|
||||
try:
|
||||
if source.execute("PRAGMA quick_check").fetchone()[0] != "ok":
|
||||
raise SystemExit("source SQLite quick_check failed")
|
||||
source.backup(target)
|
||||
if target.execute("PRAGMA quick_check").fetchone()[0] != "ok":
|
||||
raise SystemExit("backup SQLite quick_check failed")
|
||||
target.commit()
|
||||
finally:
|
||||
target.close()
|
||||
source.close()
|
||||
PY
|
||||
fi
|
||||
jwebot_log "database backup ready"
|
||||
|
||||
jwebot_progress 4 8 "install root runtime"
|
||||
jwebot_log "runtime install starting"
|
||||
rendered_recorder_unit="${backup_dir}/metadata/wechat-local-ocr-recorder.service"
|
||||
rendered_responder_unit="${backup_dir}/metadata/wechat-local-ocr-responder.service"
|
||||
rendered_defaults="${backup_dir}/metadata/wechat-local-ocr.default"
|
||||
render_root_configuration "${backup_dir}/metadata"
|
||||
install -o root -g root -m 0755 \
|
||||
"${source_dir}/wechat_local_ocr.py" "${root_ocr}"
|
||||
install -o root -g root -m 0755 \
|
||||
"${source_dir}/wechat-gui-bridge" "${root_bridge}"
|
||||
install -o root -g root -m 0644 \
|
||||
"${rendered_recorder_unit}" \
|
||||
"${root_recorder_unit}"
|
||||
install -o root -g root -m 0644 \
|
||||
"${rendered_responder_unit}" \
|
||||
"${root_responder_unit}"
|
||||
install -o root -g root -m 0644 \
|
||||
"${rendered_defaults}" \
|
||||
"${root_defaults}"
|
||||
systemctl daemon-reload
|
||||
systemctl disable --now \
|
||||
wechat-local-ocr-recorder.service \
|
||||
wechat-local-ocr-responder.service
|
||||
jwebot_log "runtime install ready"
|
||||
|
||||
jwebot_progress 5 8 "install user runtime"
|
||||
jwebot_log "user runtime install starting"
|
||||
(
|
||||
cd "${target_home}"
|
||||
/usr/sbin/runuser -u "${target_user}" -- /usr/bin/env \
|
||||
HOME="${target_home}" \
|
||||
XDG_RUNTIME_DIR="${user_runtime}" \
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=${user_runtime}/bus" \
|
||||
START_TIMER=1 \
|
||||
HEALTH_SETTLE_SECONDS="${health_settle_seconds}" \
|
||||
JWEBOT_RUNTIME_PYTHON="${runtime_python}" \
|
||||
/bin/sh "${source_dir}/deploy/scripts/install-wechat-health-watch.sh" \
|
||||
"${source_dir}"
|
||||
)
|
||||
jwebot_log "user runtime install ready"
|
||||
|
||||
jwebot_progress 6 8 "verify services"
|
||||
systemctl disable --now \
|
||||
wechat-local-ocr-recorder.service \
|
||||
wechat-local-ocr-responder.service
|
||||
test "$(
|
||||
systemctl is-enabled wechat-local-ocr-recorder.service 2>/dev/null || true
|
||||
)" = "disabled"
|
||||
test "$(
|
||||
systemctl is-enabled wechat-local-ocr-responder.service 2>/dev/null || true
|
||||
)" = "disabled"
|
||||
if systemctl is-active --quiet wechat-local-ocr-recorder.service \
|
||||
|| systemctl is-active --quiet wechat-local-ocr-responder.service
|
||||
then
|
||||
echo "[JWeBot] error: legacy OCR unexpectedly remained active" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
user_systemctl is-enabled jwebot-edge.service \
|
||||
| grep -qx enabled
|
||||
user_systemctl is-enabled wechat-ocr-adaptive-recorder.service \
|
||||
| grep -qx enabled
|
||||
user_systemctl is-active --quiet jwebot-edge.service
|
||||
jwebot_log "edge active"
|
||||
user_systemctl is-active --quiet wechat-ocr-adaptive-recorder.service
|
||||
jwebot_log "adaptive active"
|
||||
verify_edge_health
|
||||
jwebot_log "edge health ready"
|
||||
restarts_before=$(
|
||||
user_systemctl show wechat-ocr-adaptive-recorder.service \
|
||||
-p NRestarts --value
|
||||
)
|
||||
sleep "${health_settle_seconds}"
|
||||
user_systemctl is-active --quiet wechat-ocr-adaptive-recorder.service
|
||||
restarts_after=$(
|
||||
user_systemctl show wechat-ocr-adaptive-recorder.service \
|
||||
-p NRestarts --value
|
||||
)
|
||||
test "${restarts_before}" = "${restarts_after}"
|
||||
jwebot_log "adaptive stability ready restarts=${restarts_after}"
|
||||
assert_queues_empty
|
||||
jwebot_log "queues ready depth=0"
|
||||
if [[ "${require_wechat_ready}" -eq 1 ]]; then
|
||||
assert_wechat_ready
|
||||
fi
|
||||
|
||||
cmp -s "${source_dir}/wechat_local_ocr.py" "${root_ocr}"
|
||||
cmp -s "${source_dir}/wechat_local_ocr.py" \
|
||||
"${target_runtime}/wechat_local_ocr.py"
|
||||
if [[ -f "${target_edge_state}/edge.sqlite3" ]]; then
|
||||
db_check=$(
|
||||
/usr/sbin/runuser -u "${target_user}" -- \
|
||||
/usr/bin/env HOME="${target_home}" \
|
||||
"${runtime_python}" -c \
|
||||
'import sqlite3; print(sqlite3.connect("file:'"${target_edge_state}"'/edge.sqlite3?mode=ro", uri=True).execute("pragma quick_check").fetchone()[0])'
|
||||
)
|
||||
test "${db_check}" = "ok"
|
||||
fi
|
||||
jwebot_log "database ready quick_check=${db_check:-absent}"
|
||||
|
||||
jwebot_progress 7 8 "verify backup"
|
||||
(
|
||||
cd "${backup_dir}"
|
||||
find . -type f ! -name SHA256SUMS -print0 \
|
||||
| LC_ALL=C sort -z \
|
||||
| xargs -0 sha256sum >SHA256SUMS
|
||||
sha256sum -c SHA256SUMS >metadata/sha256-verification.txt
|
||||
)
|
||||
jwebot_log "backup verification ready"
|
||||
success=1
|
||||
armed=0
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
jwebot_log "deployment ready version=$(cat "${source_dir}/VERSION")"
|
||||
jwebot_log "backup active path=${backup_dir}"
|
||||
jwebot_log "legacy OCR inactive"
|
||||
jwebot_log "edge active health=ok"
|
||||
jwebot_log "adaptive active restarts=${restarts_after}"
|
||||
jwebot_log "database ready quick_check=${db_check:-absent}"
|
||||
jwebot_progress 8 8 "deployment ready"
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
config=/www/dk_project/openclaw/config/openclaw.json
|
||||
workspace=/www/dk_project/openclaw/config/workspace-wechat-responder
|
||||
agent_state=/www/dk_project/openclaw/config/agents/wechat-responder
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
backup="${config}.bak.wechat-responder-${timestamp}"
|
||||
success=0
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
if [ "$success" -eq 0 ]; then
|
||||
cp -a "$backup" "$config"
|
||||
rm -rf "$workspace" "$agent_state"
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
test -f "$config"
|
||||
test ! -e "$workspace"
|
||||
test ! -e "$agent_state"
|
||||
test -f /tmp/wechat-responder-AGENTS.md
|
||||
test -f /tmp/wechat-responder-SOUL.md
|
||||
test -f /tmp/wechat-responder-IDENTITY.md
|
||||
test -f /tmp/wechat-responder-USER.md
|
||||
|
||||
cp -a "$config" "$backup"
|
||||
timeout 45 docker exec openclaw-gateway \
|
||||
node /app/dist/index.js agents add wechat-responder \
|
||||
--workspace /home/node/.openclaw/workspace-wechat-responder \
|
||||
--model deepseek/deepseek-flash \
|
||||
--non-interactive \
|
||||
--json
|
||||
|
||||
install -d -o 1000 -g 1000 -m 0700 "$workspace"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-AGENTS.md \
|
||||
"$workspace/AGENTS.md"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-SOUL.md \
|
||||
"$workspace/SOUL.md"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-IDENTITY.md \
|
||||
"$workspace/IDENTITY.md"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-USER.md \
|
||||
"$workspace/USER.md"
|
||||
rm -f "$workspace/BOOTSTRAP.md"
|
||||
|
||||
temporary="${config}.wechat-responder.tmp"
|
||||
jq '
|
||||
.agents.entries["wechat-responder"].name = "WeChat Tool-Free Chat"
|
||||
| .agents.entries["wechat-responder"].thinkingDefault = "off"
|
||||
| del(.agents.entries["wechat-responder"].timeoutSeconds)
|
||||
| .agents.entries["wechat-responder"].skills = []
|
||||
| .agents.entries["wechat-responder"].tools = {
|
||||
"profile": "minimal",
|
||||
"allow": ["session_status"],
|
||||
"codeMode": false
|
||||
}
|
||||
' "$config" > "$temporary"
|
||||
chown --reference="$config" "$temporary"
|
||||
chmod --reference="$config" "$temporary"
|
||||
mv "$temporary" "$config"
|
||||
|
||||
sleep 3
|
||||
test "$(jq -r '.agents.entries["wechat-responder"].tools.profile' "$config")" = minimal
|
||||
test "$(jq -r '.agents.entries["wechat-responder"].tools.allow | join(",")' "$config")" = session_status
|
||||
test "$(jq -r '.agents.entries["wechat-responder"].tools.codeMode' "$config")" = false
|
||||
test "$(jq -r '.agents.entries["wechat-responder"].skills | length' "$config")" -eq 0
|
||||
test "$(jq -r '.agents.entries["wechat-responder"] | has("timeoutSeconds")' "$config")" = false
|
||||
test ! -e "$workspace/BOOTSTRAP.md"
|
||||
|
||||
success=1
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
echo "backup=$backup"
|
||||
jq -c '.agents.entries["wechat-responder"]' "$config"
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
config=/www/dk_project/openclaw/config/openclaw.json
|
||||
workspace=/www/dk_project/openclaw/config/workspace-wechat-responder
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
backup="${config}.bak.wechat-responder-finalize-${timestamp}"
|
||||
success=0
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
if [ "$success" -eq 0 ]; then
|
||||
cp -a "$backup" "$config"
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
test "$(jq -r '.agents.entries | has("wechat-responder")' "$config")" = true
|
||||
test -d "$workspace"
|
||||
cp -a "$config" "$backup"
|
||||
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-AGENTS.md \
|
||||
"$workspace/AGENTS.md"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-SOUL.md \
|
||||
"$workspace/SOUL.md"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-IDENTITY.md \
|
||||
"$workspace/IDENTITY.md"
|
||||
install -o 1000 -g 1000 -m 0644 \
|
||||
/tmp/wechat-responder-USER.md \
|
||||
"$workspace/USER.md"
|
||||
rm -f "$workspace/BOOTSTRAP.md"
|
||||
|
||||
temporary="${config}.wechat-responder.tmp"
|
||||
jq '
|
||||
.agents.entries["wechat-responder"].name = "WeChat Tool-Free Chat"
|
||||
| .agents.entries["wechat-responder"].thinkingDefault = "off"
|
||||
| del(.agents.entries["wechat-responder"].timeoutSeconds)
|
||||
| .agents.entries["wechat-responder"].skills = []
|
||||
| .agents.entries["wechat-responder"].tools = {
|
||||
"profile": "minimal",
|
||||
"allow": ["session_status"],
|
||||
"codeMode": false
|
||||
}
|
||||
' "$config" > "$temporary"
|
||||
chown --reference="$config" "$temporary"
|
||||
chmod --reference="$config" "$temporary"
|
||||
mv "$temporary" "$config"
|
||||
|
||||
sleep 3
|
||||
test "$(jq -r '.agents.entries | ."wechat-responder" | .tools.profile' "$config")" = minimal
|
||||
test "$(jq -r '.agents.entries | ."wechat-responder" | .tools.allow | join(",")' "$config")" = session_status
|
||||
test "$(jq -r '.agents.entries | ."wechat-responder" | .tools.codeMode' "$config")" = false
|
||||
test "$(jq -r '.agents.entries | ."wechat-responder" | .skills | length' "$config")" -eq 0
|
||||
test "$(jq -r '.agents.entries | ."wechat-responder" | has("timeoutSeconds")' "$config")" = false
|
||||
test ! -e "$workspace/BOOTSTRAP.md"
|
||||
test "$(wc -c < "$workspace/AGENTS.md")" -lt 2000
|
||||
|
||||
success=1
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
echo "backup=$backup"
|
||||
jq -c '.agents.entries | ."wechat-responder"' "$config"
|
||||
find "$workspace" -maxdepth 1 -type f -printf "%f %s bytes\n" | sort
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
source_dir=${1:?usage: install-wechat-health-watch.sh SOURCE_DIR}
|
||||
runtime_source_dir="$source_dir"
|
||||
user_unit_source_dir="$source_dir/deploy/systemd/user"
|
||||
config_source_dir="$source_dir/packages/jwebot-config/src/jwebot_config"
|
||||
sdk_source_dir="$source_dir/packages/jwebot-sdk/src/jwebot_sdk"
|
||||
kernel_source_dir="$source_dir/packages/jwebot-kernel/src/jwebot_kernel"
|
||||
official_source_dir="$source_dir/packages/jwebot-official-extensions/src/jwebot_official_extensions"
|
||||
official_adapter_source_dir="$official_source_dir/adapters"
|
||||
target_dir="$HOME/.local/lib/wechat-health-watch"
|
||||
unit_dir="$HOME/.config/systemd/user"
|
||||
config_dir="$HOME/.config/jwebot"
|
||||
environment_file="$config_dir/current/generated/runtime.env"
|
||||
adaptive_state_file="$HOME/.local/state/wechat-adaptive-ocr/recorder-state.json"
|
||||
runtime_python=${JWEBOT_RUNTIME_PYTHON:-/opt/jwebot/venv/bin/python}
|
||||
health_settle_seconds=${HEALTH_SETTLE_SECONDS:-5}
|
||||
edge_health_timeout_seconds=${EDGE_HEALTH_TIMEOUT_SECONDS:-45}
|
||||
adaptive_ready_timeout_seconds=${ADAPTIVE_READY_TIMEOUT_SECONDS:-90}
|
||||
start_timer=${START_TIMER:-1}
|
||||
|
||||
ocr_script_name=wechat_local_ocr.py
|
||||
script_name=wechat_health_watch.py
|
||||
adaptive_script_name=wechat_adaptive_recorder.py
|
||||
adaptive_bridge_name=wechat_adaptive_gui_bridge.py
|
||||
edge_script_name=jwebot_edge.py
|
||||
edge_delivery_name=jwebot_edge_delivery.py
|
||||
service_name=wechat-health-watch.service
|
||||
timer_name=wechat-health-watch.timer
|
||||
adaptive_service_name=wechat-ocr-adaptive-recorder.service
|
||||
edge_service_name=jwebot-edge.service
|
||||
edge_memory_service_name=jwebot-edge-memory-sync.service
|
||||
edge_memory_timer_name=jwebot-edge-memory-sync.timer
|
||||
backup_dir="$target_dir/.backup-$(date +%Y%m%d-%H%M%S)-$$"
|
||||
unit_state_file="$backup_dir/systemd-user-state.tsv"
|
||||
adaptive_ready_marker="$backup_dir/adaptive-start.marker"
|
||||
installed=0
|
||||
|
||||
managed_units="
|
||||
$edge_service_name
|
||||
$adaptive_service_name
|
||||
$timer_name
|
||||
$edge_memory_timer_name
|
||||
$service_name
|
||||
$edge_memory_service_name
|
||||
"
|
||||
|
||||
jwebot_log() {
|
||||
printf '[JWeBot] %s\n' "$*"
|
||||
}
|
||||
|
||||
unit_enabled_state() {
|
||||
systemctl --user is-enabled "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
unit_active_state() {
|
||||
systemctl --user is-active "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
save_unit_states() {
|
||||
: >"$unit_state_file"
|
||||
for unit in $managed_units; do
|
||||
printf '%s\t%s\t%s\n' \
|
||||
"$unit" \
|
||||
"$(unit_enabled_state "$unit")" \
|
||||
"$(unit_active_state "$unit")" \
|
||||
>>"$unit_state_file"
|
||||
done
|
||||
}
|
||||
|
||||
recorded_unit_field() {
|
||||
unit=$1
|
||||
field=$2
|
||||
awk -F ' ' -v unit="$unit" -v field="$field" \
|
||||
'$1 == unit { print $field; exit }' "$unit_state_file"
|
||||
}
|
||||
|
||||
restore_unit_states() {
|
||||
systemctl --user daemon-reload >/dev/null 2>&1 || true
|
||||
for unit in $managed_units; do
|
||||
systemctl --user stop "$unit" >/dev/null 2>&1 || true
|
||||
done
|
||||
for unit in $managed_units; do
|
||||
enabled=$(recorded_unit_field "$unit" 2>/dev/null || true)
|
||||
case "$enabled" in
|
||||
enabled)
|
||||
systemctl --user unmask "$unit" >/dev/null 2>&1 || true
|
||||
systemctl --user enable "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
enabled-runtime)
|
||||
systemctl --user unmask "$unit" >/dev/null 2>&1 || true
|
||||
systemctl --user enable --runtime "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
disabled)
|
||||
systemctl --user unmask "$unit" >/dev/null 2>&1 || true
|
||||
systemctl --user disable "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
masked)
|
||||
systemctl --user mask "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
masked-runtime)
|
||||
systemctl --user mask --runtime "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
for unit in $managed_units; do
|
||||
active=$(recorded_unit_field "$unit" 3 2>/dev/null || true)
|
||||
case "$active" in
|
||||
active|activating|reloading)
|
||||
systemctl --user start "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
systemctl --user reset-failed >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
restore_file() {
|
||||
name=$1
|
||||
target=$2
|
||||
mode=$3
|
||||
if [ -f "$backup_dir/$name" ]; then
|
||||
install -m "$mode" "$backup_dir/$name" "$target"
|
||||
else
|
||||
rm -f "$target"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_edge_health() {
|
||||
attempt=0
|
||||
while [ "$attempt" -lt "$edge_health_timeout_seconds" ]; do
|
||||
if systemctl --user is-failed --quiet "$edge_service_name"; then
|
||||
echo "edge service failed before becoming healthy" >&2
|
||||
return 1
|
||||
fi
|
||||
if edge_health=$(
|
||||
printf '%s\n' '{"op":"health"}' \
|
||||
| "$runtime_python" "$target_dir/$edge_script_name" respond \
|
||||
2>/dev/null
|
||||
); then
|
||||
if printf '%s' "$edge_health" \
|
||||
| "$runtime_python" -c \
|
||||
'import json,sys; value=json.load(sys.stdin); raise SystemExit(0 if value.get("status") == "ok" else 1)'
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
echo "edge health did not become ready within ${edge_health_timeout_seconds}s" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_adaptive_ready() {
|
||||
attempt=0
|
||||
while [ "$attempt" -lt "$adaptive_ready_timeout_seconds" ]; do
|
||||
if systemctl --user is-failed --quiet "$adaptive_service_name"; then
|
||||
echo "[JWeBot] error: adaptive service failed before becoming ready" >&2
|
||||
return 1
|
||||
fi
|
||||
if [ -f "$adaptive_state_file" ] \
|
||||
&& [ "$adaptive_state_file" -nt "$adaptive_ready_marker" ] \
|
||||
&& "$runtime_python" - "$adaptive_state_file" "$adaptive_started_epoch" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
value = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
captured_at = datetime.fromisoformat(str(value["last_capture_at"]))
|
||||
ready = (
|
||||
value.get("initialized") is True
|
||||
and isinstance(value.get("message_signature_scheme"), str)
|
||||
and bool(value["message_signature_scheme"])
|
||||
and isinstance(value.get("visible_signatures"), list)
|
||||
and captured_at.timestamp() >= int(sys.argv[2])
|
||||
)
|
||||
except (KeyError, OSError, TypeError, ValueError, json.JSONDecodeError):
|
||||
ready = False
|
||||
raise SystemExit(0 if ready else 1)
|
||||
PY
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
echo "[JWeBot] error: adaptive OCR did not become ready within ${adaptive_ready_timeout_seconds}s" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if [ "$installed" -eq 1 ]; then
|
||||
jwebot_log "user runtime rollback starting backup=$backup_dir"
|
||||
systemctl --user stop \
|
||||
"$adaptive_service_name" \
|
||||
"$edge_service_name" \
|
||||
"$timer_name" \
|
||||
"$edge_memory_timer_name" \
|
||||
>/dev/null 2>&1 || true
|
||||
restore_file "$ocr_script_name" "$target_dir/$ocr_script_name" 0700
|
||||
restore_file "$script_name" "$target_dir/$script_name" 0700
|
||||
restore_file "$adaptive_script_name" "$target_dir/$adaptive_script_name" 0700
|
||||
restore_file "$adaptive_bridge_name" "$target_dir/$adaptive_bridge_name" 0700
|
||||
restore_file "$edge_script_name" "$target_dir/$edge_script_name" 0700
|
||||
restore_file "$edge_delivery_name" "$target_dir/$edge_delivery_name" 0700
|
||||
for package_name in \
|
||||
jwebot_config \
|
||||
jwebot_sdk \
|
||||
jwebot_kernel \
|
||||
jwebot_official_extensions
|
||||
do
|
||||
rm -rf "$target_dir/$package_name"
|
||||
if [ -d "$backup_dir/$package_name" ]; then
|
||||
cp -a "$backup_dir/$package_name" "$target_dir/$package_name"
|
||||
fi
|
||||
done
|
||||
restore_file "$service_name" "$unit_dir/$service_name" 0644
|
||||
restore_file "$timer_name" "$unit_dir/$timer_name" 0644
|
||||
restore_file "$adaptive_service_name" "$unit_dir/$adaptive_service_name" 0644
|
||||
restore_file "$edge_service_name" "$unit_dir/$edge_service_name" 0644
|
||||
restore_file "$edge_memory_service_name" "$unit_dir/$edge_memory_service_name" 0644
|
||||
restore_file "$edge_memory_timer_name" "$unit_dir/$edge_memory_timer_name" 0644
|
||||
restore_file environment "$environment_file" 0600
|
||||
restore_unit_states
|
||||
jwebot_log "user runtime rollback ready backup=$backup_dir"
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
jwebot_log "user runtime preflight starting"
|
||||
test -x "$runtime_python"
|
||||
for path in \
|
||||
"$runtime_source_dir/$ocr_script_name" \
|
||||
"$runtime_source_dir/$script_name" \
|
||||
"$runtime_source_dir/$adaptive_script_name" \
|
||||
"$runtime_source_dir/$adaptive_bridge_name" \
|
||||
"$runtime_source_dir/$edge_script_name" \
|
||||
"$runtime_source_dir/$edge_delivery_name" \
|
||||
"$user_unit_source_dir/$service_name" \
|
||||
"$user_unit_source_dir/$timer_name" \
|
||||
"$user_unit_source_dir/$adaptive_service_name" \
|
||||
"$user_unit_source_dir/$edge_service_name" \
|
||||
"$user_unit_source_dir/$edge_memory_service_name" \
|
||||
"$user_unit_source_dir/$edge_memory_timer_name"
|
||||
do
|
||||
test -f "$path"
|
||||
done
|
||||
for directory in \
|
||||
"$config_source_dir" \
|
||||
"$sdk_source_dir" \
|
||||
"$kernel_source_dir" \
|
||||
"$official_source_dir" \
|
||||
"$official_adapter_source_dir"
|
||||
do
|
||||
test -d "$directory"
|
||||
find "$directory" -maxdepth 1 -type f -name '*.py' | grep -q .
|
||||
done
|
||||
test -f "$environment_file"
|
||||
|
||||
if systemctl is-active --quiet wechat-local-ocr-recorder.service \
|
||||
|| systemctl is-active --quiet wechat-local-ocr-responder.service
|
||||
then
|
||||
echo "[JWeBot] error: legacy system OCR is active; use root deployer" >&2
|
||||
exit 1
|
||||
fi
|
||||
jwebot_log "user runtime preflight ready"
|
||||
|
||||
jwebot_log "user runtime backup starting path=$backup_dir"
|
||||
install -d -m 0700 "$target_dir" "$unit_dir" "$config_dir" "$backup_dir"
|
||||
save_unit_states
|
||||
for name in \
|
||||
"$ocr_script_name" \
|
||||
"$script_name" \
|
||||
"$adaptive_script_name" \
|
||||
"$adaptive_bridge_name" \
|
||||
"$edge_script_name" \
|
||||
"$edge_delivery_name"
|
||||
do
|
||||
if [ -f "$target_dir/$name" ]; then
|
||||
cp -a "$target_dir/$name" "$backup_dir/$name"
|
||||
fi
|
||||
done
|
||||
for package_name in \
|
||||
jwebot_config \
|
||||
jwebot_sdk \
|
||||
jwebot_kernel \
|
||||
jwebot_official_extensions
|
||||
do
|
||||
if [ -d "$target_dir/$package_name" ]; then
|
||||
cp -a "$target_dir/$package_name" "$backup_dir/$package_name"
|
||||
fi
|
||||
done
|
||||
for name in \
|
||||
"$service_name" \
|
||||
"$timer_name" \
|
||||
"$adaptive_service_name" \
|
||||
"$edge_service_name" \
|
||||
"$edge_memory_service_name" \
|
||||
"$edge_memory_timer_name"
|
||||
do
|
||||
if [ -f "$unit_dir/$name" ]; then
|
||||
cp -a "$unit_dir/$name" "$backup_dir/$name"
|
||||
fi
|
||||
done
|
||||
if [ -f "$environment_file" ]; then
|
||||
cp -a "$environment_file" "$backup_dir/environment"
|
||||
fi
|
||||
jwebot_log "user runtime backup ready path=$backup_dir"
|
||||
|
||||
installed=1
|
||||
jwebot_log "user services stopping"
|
||||
systemctl --user stop \
|
||||
"$adaptive_service_name" \
|
||||
"$edge_service_name" \
|
||||
"$timer_name" \
|
||||
"$edge_memory_timer_name" \
|
||||
"$service_name" \
|
||||
"$edge_memory_service_name" \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
jwebot_log "user runtime files install starting"
|
||||
install -m 0700 "$runtime_source_dir/$ocr_script_name" "$target_dir/$ocr_script_name"
|
||||
install -m 0700 "$runtime_source_dir/$script_name" "$target_dir/$script_name"
|
||||
install -m 0700 "$runtime_source_dir/$adaptive_script_name" "$target_dir/$adaptive_script_name"
|
||||
install -m 0700 "$runtime_source_dir/$adaptive_bridge_name" "$target_dir/$adaptive_bridge_name"
|
||||
install -m 0700 "$runtime_source_dir/$edge_script_name" "$target_dir/$edge_script_name"
|
||||
install -m 0700 "$runtime_source_dir/$edge_delivery_name" "$target_dir/$edge_delivery_name"
|
||||
rm -rf \
|
||||
"$target_dir/jwebot_config" \
|
||||
"$target_dir/jwebot_sdk" \
|
||||
"$target_dir/jwebot_kernel" \
|
||||
"$target_dir/jwebot_official_extensions"
|
||||
install -d -m 0700 \
|
||||
"$target_dir/jwebot_config" \
|
||||
"$target_dir/jwebot_sdk" \
|
||||
"$target_dir/jwebot_kernel" \
|
||||
"$target_dir/jwebot_official_extensions" \
|
||||
"$target_dir/jwebot_official_extensions/adapters"
|
||||
for path in "$config_source_dir"/*.py; do
|
||||
install -m 0600 "$path" "$target_dir/jwebot_config/$(basename "$path")"
|
||||
done
|
||||
for path in "$sdk_source_dir"/*.py; do
|
||||
install -m 0600 "$path" "$target_dir/jwebot_sdk/$(basename "$path")"
|
||||
done
|
||||
for path in "$kernel_source_dir"/*.py; do
|
||||
install -m 0600 "$path" "$target_dir/jwebot_kernel/$(basename "$path")"
|
||||
done
|
||||
for path in "$official_source_dir"/*.py; do
|
||||
install -m 0600 "$path" "$target_dir/jwebot_official_extensions/$(basename "$path")"
|
||||
done
|
||||
for path in "$official_adapter_source_dir"/*.py; do
|
||||
install -m 0600 "$path" \
|
||||
"$target_dir/jwebot_official_extensions/adapters/$(basename "$path")"
|
||||
done
|
||||
for name in \
|
||||
"$service_name" \
|
||||
"$timer_name" \
|
||||
"$adaptive_service_name" \
|
||||
"$edge_service_name" \
|
||||
"$edge_memory_service_name" \
|
||||
"$edge_memory_timer_name"
|
||||
do
|
||||
rendered_unit="$backup_dir/rendered-$name"
|
||||
sed "s#/opt/jwebot/venv/bin/python#$runtime_python#g" \
|
||||
"$user_unit_source_dir/$name" >"$rendered_unit"
|
||||
install -m 0644 "$rendered_unit" "$unit_dir/$name"
|
||||
done
|
||||
|
||||
for name in \
|
||||
"$ocr_script_name" \
|
||||
"$script_name" \
|
||||
"$adaptive_script_name" \
|
||||
"$adaptive_bridge_name" \
|
||||
"$edge_script_name" \
|
||||
"$edge_delivery_name"
|
||||
do
|
||||
"$runtime_python" -m py_compile "$target_dir/$name"
|
||||
done
|
||||
"$runtime_python" -m compileall -q \
|
||||
"$target_dir/jwebot_config" \
|
||||
"$target_dir/jwebot_sdk" \
|
||||
"$target_dir/jwebot_kernel" \
|
||||
"$target_dir/jwebot_official_extensions"
|
||||
systemd-analyze --user verify \
|
||||
"$unit_dir/$service_name" \
|
||||
"$unit_dir/$timer_name" \
|
||||
"$unit_dir/$adaptive_service_name" \
|
||||
"$unit_dir/$edge_service_name" \
|
||||
"$unit_dir/$edge_memory_service_name" \
|
||||
"$unit_dir/$edge_memory_timer_name"
|
||||
jwebot_log "user runtime files install ready"
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable \
|
||||
"$timer_name" \
|
||||
"$edge_service_name" \
|
||||
"$edge_memory_timer_name" \
|
||||
"$adaptive_service_name"
|
||||
jwebot_log "edge starting"
|
||||
systemctl --user start "$edge_service_name"
|
||||
systemctl --user is-active --quiet "$edge_service_name"
|
||||
wait_for_edge_health
|
||||
jwebot_log "edge active health=ok"
|
||||
|
||||
: >"$adaptive_ready_marker"
|
||||
adaptive_started_epoch=$(date +%s)
|
||||
jwebot_log "adaptive starting"
|
||||
systemctl --user start "$adaptive_service_name"
|
||||
systemctl --user is-active --quiet "$adaptive_service_name"
|
||||
jwebot_log "adaptive active"
|
||||
restarts_before=$(
|
||||
systemctl --user show "$adaptive_service_name" -p NRestarts --value
|
||||
)
|
||||
wait_for_adaptive_ready
|
||||
jwebot_log "adaptive ready baseline=captured"
|
||||
sleep "$health_settle_seconds"
|
||||
systemctl --user is-active --quiet "$adaptive_service_name"
|
||||
restarts_after=$(
|
||||
systemctl --user show "$adaptive_service_name" -p NRestarts --value
|
||||
)
|
||||
test "$restarts_before" = "$restarts_after"
|
||||
jwebot_log "adaptive stability ready restarts=$restarts_after"
|
||||
|
||||
if [ "$start_timer" -eq 1 ]; then
|
||||
systemctl --user start "$timer_name" "$edge_memory_timer_name"
|
||||
systemctl --user is-active --quiet "$timer_name"
|
||||
systemctl --user is-active --quiet "$edge_memory_timer_name"
|
||||
jwebot_log "timers active"
|
||||
else
|
||||
jwebot_log "timers inactive requested=false"
|
||||
fi
|
||||
|
||||
installed=0
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
jwebot_log "user runtime ready"
|
||||
jwebot_log "backup active path=$backup_dir"
|
||||
jwebot_log "edge active health=ok"
|
||||
jwebot_log "adaptive active restarts=$restarts_after"
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
source_script=/tmp/wechat-local-ocr.py
|
||||
source_bridge=/tmp/wechat-gui-bridge
|
||||
source_recorder_unit=/tmp/wechat-local-ocr-recorder.service
|
||||
source_responder_unit=/tmp/wechat-local-ocr-responder.service
|
||||
source_defaults=/tmp/wechat-local-ocr.default
|
||||
runtime_user=${JWEBOT_RUNTIME_USER:-jwebot}
|
||||
|
||||
target_script=/usr/local/sbin/wechat-local-ocr
|
||||
target_bridge=/usr/local/sbin/wechat-gui-bridge
|
||||
target_recorder_unit=/etc/systemd/system/wechat-local-ocr-recorder.service
|
||||
target_responder_unit=/etc/systemd/system/wechat-local-ocr-responder.service
|
||||
target_defaults=/etc/default/wechat-local-ocr
|
||||
venv_python=${JWEBOT_RUNTIME_PYTHON:-/opt/jwebot/venv/bin/python}
|
||||
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
backup_dir="/root/wechat-local-ocr-backup-${timestamp}"
|
||||
enable_legacy_ocr=${ENABLE_LEGACY_OCR:-0}
|
||||
success=0
|
||||
recorder_enabled=$(systemctl is-enabled wechat-local-ocr-recorder.service 2>/dev/null || true)
|
||||
recorder_active=$(systemctl is-active wechat-local-ocr-recorder.service 2>/dev/null || true)
|
||||
responder_enabled=$(systemctl is-enabled wechat-local-ocr-responder.service 2>/dev/null || true)
|
||||
responder_active=$(systemctl is-active wechat-local-ocr-responder.service 2>/dev/null || true)
|
||||
|
||||
restore_file() {
|
||||
name=$1
|
||||
target=$2
|
||||
if [ -f "${backup_dir}/${name}" ]; then
|
||||
cp -a "${backup_dir}/${name}" "$target"
|
||||
else
|
||||
rm -f "$target"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_service_state() {
|
||||
systemctl disable --now \
|
||||
wechat-local-ocr-recorder.service \
|
||||
wechat-local-ocr-responder.service \
|
||||
>/dev/null 2>&1 || true
|
||||
case "$recorder_enabled" in
|
||||
enabled) systemctl enable wechat-local-ocr-recorder.service >/dev/null 2>&1 || true ;;
|
||||
masked) systemctl mask wechat-local-ocr-recorder.service >/dev/null 2>&1 || true ;;
|
||||
esac
|
||||
case "$responder_enabled" in
|
||||
enabled) systemctl enable wechat-local-ocr-responder.service >/dev/null 2>&1 || true ;;
|
||||
masked) systemctl mask wechat-local-ocr-responder.service >/dev/null 2>&1 || true ;;
|
||||
esac
|
||||
case "$recorder_active" in
|
||||
active|activating|reloading)
|
||||
systemctl start wechat-local-ocr-recorder.service >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
case "$responder_active" in
|
||||
active|activating|reloading)
|
||||
systemctl start wechat-local-ocr-responder.service >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
if [ "$success" -eq 0 ]; then
|
||||
systemctl disable --now wechat-local-ocr-recorder.service >/dev/null 2>&1 || true
|
||||
systemctl disable --now wechat-local-ocr-responder.service >/dev/null 2>&1 || true
|
||||
restore_file wechat-local-ocr "$target_script"
|
||||
restore_file wechat-gui-bridge "$target_bridge"
|
||||
restore_file wechat-local-ocr-recorder.service "$target_recorder_unit"
|
||||
restore_file wechat-local-ocr-responder.service "$target_responder_unit"
|
||||
restore_file wechat-local-ocr.default "$target_defaults"
|
||||
systemctl daemon-reload || true
|
||||
restore_service_state
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
test -x "$venv_python"
|
||||
test -f "$source_script"
|
||||
test -f "$source_bridge"
|
||||
test -f "$source_recorder_unit"
|
||||
test -f "$source_responder_unit"
|
||||
test -f "$source_defaults"
|
||||
|
||||
install -d -o root -g root -m 0700 "$backup_dir"
|
||||
for pair in \
|
||||
"wechat-local-ocr:$target_script" \
|
||||
"wechat-gui-bridge:$target_bridge" \
|
||||
"wechat-local-ocr-recorder.service:$target_recorder_unit" \
|
||||
"wechat-local-ocr-responder.service:$target_responder_unit" \
|
||||
"wechat-local-ocr.default:$target_defaults"
|
||||
do
|
||||
name=${pair%%:*}
|
||||
target=${pair#*:}
|
||||
if [ -f "$target" ]; then
|
||||
cp -a "$target" "${backup_dir}/${name}"
|
||||
fi
|
||||
done
|
||||
|
||||
rendered_recorder_unit="${backup_dir}/wechat-local-ocr-recorder.rendered.service"
|
||||
rendered_responder_unit="${backup_dir}/wechat-local-ocr-responder.rendered.service"
|
||||
sed \
|
||||
-e "s/^User=.*/User=$runtime_user/" \
|
||||
-e "s/^Group=.*/Group=$runtime_user/" \
|
||||
-e "s#/opt/jwebot/venv/bin/python#$venv_python#g" \
|
||||
"$source_recorder_unit" >"$rendered_recorder_unit"
|
||||
sed \
|
||||
-e "s#/opt/jwebot/venv/bin/python#$venv_python#g" \
|
||||
"$source_responder_unit" >"$rendered_responder_unit"
|
||||
|
||||
install -o root -g root -m 0755 "$source_script" "$target_script"
|
||||
install -o root -g root -m 0755 "$source_bridge" "$target_bridge"
|
||||
install -o root -g root -m 0644 "$rendered_recorder_unit" "$target_recorder_unit"
|
||||
install -o root -g root -m 0644 "$rendered_responder_unit" "$target_responder_unit"
|
||||
install -o root -g root -m 0644 "$source_defaults" "$target_defaults"
|
||||
install -d -o "$runtime_user" -g "$runtime_user" -m 0700 \
|
||||
/var/lib/wechat-local-ocr \
|
||||
/var/lib/wechat-local-ocr/history \
|
||||
/var/lib/wechat-local-ocr/queue \
|
||||
/var/lib/wechat-local-ocr/failed
|
||||
chown -R "$runtime_user:$runtime_user" /var/lib/wechat-local-ocr
|
||||
touch /var/lib/wechat-local-ocr/gui-interaction.lock
|
||||
chown "$runtime_user:$runtime_user" /var/lib/wechat-local-ocr/gui-interaction.lock
|
||||
chmod 0600 /var/lib/wechat-local-ocr/gui-interaction.lock
|
||||
|
||||
"$venv_python" -m py_compile "$target_script"
|
||||
bash -n "$target_bridge"
|
||||
systemd-analyze verify "$target_recorder_unit" "$target_responder_unit"
|
||||
systemctl daemon-reload
|
||||
systemctl disable --now \
|
||||
wechat-local-ocr-recorder.service \
|
||||
wechat-local-ocr-responder.service \
|
||||
>/dev/null 2>&1 || true
|
||||
if [ "$enable_legacy_ocr" -eq 1 ]; then
|
||||
systemctl enable wechat-local-ocr-recorder.service
|
||||
systemctl start wechat-local-ocr-recorder.service
|
||||
systemctl is-active --quiet wechat-local-ocr-recorder.service
|
||||
fi
|
||||
|
||||
success=1
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
echo "backup_dir=$backup_dir"
|
||||
sha256sum "$target_script" "$target_bridge" "$target_recorder_unit" "$target_responder_unit" "$target_defaults"
|
||||
if [ "$enable_legacy_ocr" -eq 1 ]; then
|
||||
systemctl status wechat-local-ocr-recorder.service --no-pager -l | sed -n '1,28p'
|
||||
else
|
||||
echo "legacy_ocr=disabled"
|
||||
fi
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
defaults=/etc/default/wechat-local-ocr
|
||||
backup=$(mktemp /root/wechat-local-ocr-default.XXXXXX)
|
||||
old_service=wechat-openclaw-bridge.service
|
||||
recorder_service=wechat-local-ocr-recorder.service
|
||||
responder_service=wechat-local-ocr-responder.service
|
||||
success=0
|
||||
|
||||
cp -a "$defaults" "$backup"
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
if [ "$success" -eq 0 ]; then
|
||||
systemctl disable --now "$responder_service" >/dev/null 2>&1 || true
|
||||
cp -a "$backup" "$defaults"
|
||||
systemctl restart "$recorder_service" >/dev/null 2>&1 || true
|
||||
systemctl enable --now "$old_service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$backup"
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
test "$(find /var/lib/wechat-local-ocr/queue -maxdepth 1 -type f | wc -l)" -eq 0
|
||||
test "$(find /var/lib/wechat-local-ocr/failed -maxdepth 1 -type f | wc -l)" -eq 0
|
||||
systemctl is-active --quiet "$recorder_service"
|
||||
systemctl is-active --quiet "$old_service"
|
||||
|
||||
printf '%s\n' \
|
||||
'WECHAT_QUEUE_ENABLED=1' \
|
||||
'WECHAT_ROUTER_ENABLED=1' \
|
||||
> "$defaults"
|
||||
chmod 0644 "$defaults"
|
||||
|
||||
systemctl disable --now "$old_service"
|
||||
systemctl restart "$recorder_service"
|
||||
sleep 2
|
||||
systemctl is-active --quiet "$recorder_service"
|
||||
|
||||
systemctl enable --now "$responder_service"
|
||||
sleep 2
|
||||
systemctl is-active --quiet "$responder_service"
|
||||
test "$(systemctl is-active "$old_service")" = inactive
|
||||
test "$(find /var/lib/wechat-local-ocr/queue -maxdepth 1 -type f | wc -l)" -eq 0
|
||||
|
||||
success=1
|
||||
trap - EXIT HUP INT TERM
|
||||
rm -f "$backup"
|
||||
|
||||
systemctl is-active "$recorder_service"
|
||||
systemctl is-active "$responder_service"
|
||||
systemctl is-active "$old_service" || true
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
source_script=/tmp/wechat-local-ocr.py
|
||||
source_bridge=/tmp/wechat-gui-bridge
|
||||
runtime_user=${JWEBOT_RUNTIME_USER:-jwebot}
|
||||
runtime_python=${JWEBOT_RUNTIME_PYTHON:-/opt/jwebot/venv/bin/python}
|
||||
target_script=/usr/local/sbin/wechat-local-ocr
|
||||
target_bridge=/usr/local/sbin/wechat-gui-bridge
|
||||
recorder_service=wechat-local-ocr-recorder.service
|
||||
responder_service=wechat-local-ocr-responder.service
|
||||
backup_script=$(mktemp /root/wechat-local-ocr-script.XXXXXX)
|
||||
backup_bridge=$(mktemp /root/wechat-gui-bridge.XXXXXX)
|
||||
success=0
|
||||
recorder_active=$(systemctl is-active "$recorder_service" 2>/dev/null || true)
|
||||
responder_active=$(systemctl is-active "$responder_service" 2>/dev/null || true)
|
||||
|
||||
cp -a "$target_script" "$backup_script"
|
||||
cp -a "$target_bridge" "$backup_bridge"
|
||||
|
||||
restore_service_state() {
|
||||
systemctl stop "$responder_service" "$recorder_service" >/dev/null 2>&1 || true
|
||||
case "$recorder_active" in
|
||||
active|activating|reloading)
|
||||
systemctl start "$recorder_service" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
case "$responder_active" in
|
||||
active|activating|reloading)
|
||||
systemctl start "$responder_service" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
if [ "$success" -eq 0 ]; then
|
||||
cp -a "$backup_script" "$target_script"
|
||||
cp -a "$backup_bridge" "$target_bridge"
|
||||
restore_service_state
|
||||
fi
|
||||
rm -f "$backup_script" "$backup_bridge"
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
test -f "$source_script"
|
||||
test -f "$source_bridge"
|
||||
test "$(find /var/lib/wechat-local-ocr/queue -maxdepth 1 -type f | wc -l)" -eq 0
|
||||
touch /var/lib/wechat-local-ocr/gui-interaction.lock
|
||||
chown "$runtime_user:$runtime_user" /var/lib/wechat-local-ocr/gui-interaction.lock
|
||||
chmod 0600 /var/lib/wechat-local-ocr/gui-interaction.lock
|
||||
|
||||
systemctl stop "$responder_service" "$recorder_service" >/dev/null 2>&1 || true
|
||||
install -o root -g root -m 0755 "$source_script" "$target_script"
|
||||
install -o root -g root -m 0755 "$source_bridge" "$target_bridge"
|
||||
"$runtime_python" -m py_compile "$target_script"
|
||||
bash -n "$target_bridge"
|
||||
restore_service_state
|
||||
test "$(find /var/lib/wechat-local-ocr/queue -maxdepth 1 -type f | wc -l)" -eq 0
|
||||
|
||||
success=1
|
||||
trap - EXIT HUP INT TERM
|
||||
rm -f "$backup_script" "$backup_bridge"
|
||||
|
||||
sha256sum "$target_script" "$target_bridge"
|
||||
printf 'recorder_state=%s\n' "$(systemctl is-active "$recorder_service" 2>/dev/null || true)"
|
||||
printf 'responder_state=%s\n' "$(systemctl is-active "$responder_service" 2>/dev/null || true)"
|
||||
Reference in New Issue
Block a user