Files
JWeBot/deploy/scripts/install-wechat-health-watch.sh
T
JWeBot Contributors afc71e63a7
check / local-gate (push) Failing after 1m35s
Release beta 1.0.0 baseline
2026-10-16 01:07:07 +08:00

463 lines
16 KiB
Bash
Executable File

#!/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"
official_autochess_data_source_dir="$official_source_dir/data/cs_autochess"
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 \
"$source_dir/VERSION" \
"$source_dir/CHANGELOG.md" \
"$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 -d "$official_autochess_data_source_dir"
find "$official_autochess_data_source_dir" -maxdepth 1 -type f -name '*.json' \
| grep -q .
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 \
VERSION \
CHANGELOG.md \
"$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 0600 "$source_dir/VERSION" "$target_dir/VERSION"
install -m 0600 "$source_dir/CHANGELOG.md" "$target_dir/CHANGELOG.md"
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" \
"$target_dir/jwebot_official_extensions/data" \
"$target_dir/jwebot_official_extensions/data/cs_autochess"
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 path in "$official_autochess_data_source_dir"/*.json; do
install -m 0600 "$path" \
"$target_dir/jwebot_official_extensions/data/cs_autochess/$(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"