471 lines
16 KiB
Bash
Executable File
471 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
|
|
release_update_name=jwebot_release_update.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
|
|
restore_file "$release_update_name" "$target_dir/$release_update_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" \
|
|
"$runtime_source_dir/$release_update_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" \
|
|
"$release_update_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"
|
|
install -m 0700 \
|
|
"$runtime_source_dir/$release_update_name" \
|
|
"$target_dir/$release_update_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" \
|
|
"$release_update_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"
|