RKNnoVPN/module/service.sh
2026-05-07 19:44:47 +03:00

284 lines
8.5 KiB
Shell

#!/system/bin/sh
# RKNnoVPN — service.sh
# Runs at boot after data is decrypted (non-blocking).
# Prepares module layout at boot. Runtime control is APK-driven through the
# rknnovpn-runtime root CLI; service.sh only prepares module layout.
# POSIX sh compatible (busybox ash).
# ============================================================================
# Constants
# ============================================================================
MODDIR="${0%/*}"
RKNNOVPN_DIR="${RKNNOVPN_DIR:-${MODDIR:-/data/adb/modules/rknnovpn}}"
RKNNOVPN_GID=23333
RUNTIME_CLI_BIN="${RKNNOVPN_DIR}/bin/rknnovpn-runtime"
MANUAL_FLAG="${RKNNOVPN_DIR}/config/manual"
LOG_FILE="${RKNNOVPN_DIR}/logs/service.log"
RUNTIME_CONFIG_FILE="${RKNNOVPN_RUNTIME_CONFIG_FILE:-${RKNNOVPN_DIR}/config/config.json}"
MODULE_PROP="${MODDIR}/module.prop"
LOG_VERSION_FILE="${RKNNOVPN_DIR}/logs/.version"
LOG_ARCHIVE_DIR="${RKNNOVPN_DIR}/logs/archive"
TAG="rknnovpn:service"
BOOT_TIMEOUT=120
SETTLE_DELAY=5
SERVICE_LOCK_DIR="${RKNNOVPN_DIR}/config/service.lock"
SERVICE_LOCK_WAIT="${RKNNOVPN_SERVICE_LOCK_WAIT:-60}"
SERVICE_LOCK_HELD=0
if [ -f "${RKNNOVPN_DIR}/scripts/lib/rknnovpn_env.sh" ]; then
. "${RKNNOVPN_DIR}/scripts/lib/rknnovpn_env.sh"
fi
# ============================================================================
# Logging
# ============================================================================
prepare_runtime_dirs() {
mkdir -p "${RKNNOVPN_DIR}/logs" "${RKNNOVPN_DIR}/run" "${RKNNOVPN_DIR}/config" 2>/dev/null
chown 0:0 "${RKNNOVPN_DIR}/logs" "${RKNNOVPN_DIR}/run" "${RKNNOVPN_DIR}/config" 2>/dev/null
chmod 0700 "${RKNNOVPN_DIR}/logs" "${RKNNOVPN_DIR}/run" "${RKNNOVPN_DIR}/config" 2>/dev/null
touch "$LOG_FILE" 2>/dev/null
chown 0:0 "$LOG_FILE" 2>/dev/null
chmod 0600 "$LOG_FILE" 2>/dev/null
}
ts() {
date "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "----"
}
log_info() {
msg="$(ts) [INFO] $1"
echo "$msg" >> "$LOG_FILE" 2>/dev/null
/system/bin/log -t "$TAG" -p i "$1" 2>/dev/null
}
log_warn() {
msg="$(ts) [WARN] $1"
echo "$msg" >> "$LOG_FILE" 2>/dev/null
/system/bin/log -t "$TAG" -p w "$1" 2>/dev/null
}
log_error() {
msg="$(ts) [ERROR] $1"
echo "$msg" >> "$LOG_FILE" 2>/dev/null
/system/bin/log -t "$TAG" -p e "$1" 2>/dev/null
}
module_version() {
if [ -f "$MODULE_PROP" ]; then
sed -n 's/^version=//p' "$MODULE_PROP" 2>/dev/null | head -n 1
fi
}
rotate_logs_if_version_changed() {
mkdir -p "${RKNNOVPN_DIR}/logs" 2>/dev/null || return 0
chown 0:0 "${RKNNOVPN_DIR}/logs" 2>/dev/null
chmod 0700 "${RKNNOVPN_DIR}/logs" 2>/dev/null
CURRENT_VERSION="$(module_version)"
[ -n "$CURRENT_VERSION" ] || return 0
PREVIOUS_VERSION=""
if [ -f "$LOG_VERSION_FILE" ]; then
PREVIOUS_VERSION="$(sed -n '1p' "$LOG_VERSION_FILE" 2>/dev/null)"
fi
if [ "$PREVIOUS_VERSION" = "$CURRENT_VERSION" ]; then
return 0
fi
STAMP="$(date "+%Y%m%d-%H%M%S" 2>/dev/null || echo "now")"
FROM_VERSION="${PREVIOUS_VERSION:-unknown}"
ARCHIVE_DIR="${LOG_ARCHIVE_DIR}/${FROM_VERSION}_to_${CURRENT_VERSION}_${STAMP}"
MOVED=0
for name in sing-box.log xray.log service.log rescue_reset.log net_change.log; do
src="${RKNNOVPN_DIR}/logs/${name}"
if [ -s "$src" ]; then
mkdir -p "$ARCHIVE_DIR" 2>/dev/null || continue
if mv "$src" "${ARCHIVE_DIR}/${name}" 2>/dev/null; then
MOVED=1
fi
fi
done
if [ "$MOVED" = "1" ]; then
chown -R 0:0 "$ARCHIVE_DIR" 2>/dev/null
chmod 0700 "$ARCHIVE_DIR" 2>/dev/null
for f in "$ARCHIVE_DIR"/*; do
[ -f "$f" ] && chmod 0600 "$f" 2>/dev/null
done
echo "$(ts) [INFO] Rotated logs for module ${FROM_VERSION} -> ${CURRENT_VERSION}: ${ARCHIVE_DIR}" >> "$LOG_FILE" 2>/dev/null
fi
echo "$CURRENT_VERSION" > "$LOG_VERSION_FILE" 2>/dev/null
chown 0:0 "$LOG_VERSION_FILE" 2>/dev/null
chmod 0600 "$LOG_VERSION_FILE" 2>/dev/null
}
# ============================================================================
# 1. Detect root manager and set busybox path
# ============================================================================
detect_busybox() {
# KernelSU
if [ -n "$KSU" ] && [ "$KSU" = "true" ]; then
if [ -x "/data/adb/ksu/bin/busybox" ]; then
BUSYBOX="/data/adb/ksu/bin/busybox"
return
fi
fi
# APatch
if [ -n "$APATCH" ] && [ "$APATCH" = "true" ]; then
if [ -x "/data/adb/ap/bin/busybox" ]; then
BUSYBOX="/data/adb/ap/bin/busybox"
return
fi
fi
# Magisk
if [ -x "/data/adb/magisk/busybox" ]; then
BUSYBOX="/data/adb/magisk/busybox"
return
fi
# System busybox
if command -v busybox >/dev/null 2>&1; then
BUSYBOX="busybox"
return
fi
# Fallback — no busybox, use shell builtins only
BUSYBOX=""
}
prepare_runtime_dirs
service_lock_owner_alive() {
_pid="$(cat "${SERVICE_LOCK_DIR}/pid" 2>/dev/null)"
[ -n "$_pid" ] || return 1
kill -0 "$_pid" 2>/dev/null || return 1
[ -r "/proc/${_pid}/cmdline" ] || return 1
_cmd="$(cat "/proc/${_pid}/cmdline" 2>/dev/null | tr '\000' ' ')"
case "$_cmd" in
*"${RKNNOVPN_DIR}/service.sh"*)
return 0
;;
esac
return 1
}
release_service_lock() {
if [ "$SERVICE_LOCK_HELD" = "1" ]; then
rm -rf "$SERVICE_LOCK_DIR" 2>/dev/null
SERVICE_LOCK_HELD=0
fi
}
acquire_service_lock() {
_waited=0
while ! mkdir "$SERVICE_LOCK_DIR" 2>/dev/null; do
if ! service_lock_owner_alive; then
rm -rf "$SERVICE_LOCK_DIR" 2>/dev/null
continue
fi
if [ "$_waited" -ge "$SERVICE_LOCK_WAIT" ]; then
log_warn "Another service launch is still running; skipping this invocation"
return 1
fi
if [ "$_waited" = "0" ]; then
log_info "Another service launch is running; waiting for lock"
fi
sleep 1
_waited=$((_waited + 1))
done
SERVICE_LOCK_HELD=1
echo "$$" > "${SERVICE_LOCK_DIR}/pid" 2>/dev/null
trap release_service_lock EXIT HUP INT TERM
return 0
}
if ! acquire_service_lock; then
exit 0
fi
rotate_logs_if_version_changed
prepare_runtime_dirs
detect_busybox
log_info "Busybox: ${BUSYBOX:-not found}"
# ============================================================================
# 2. Wait for boot completion
# ============================================================================
wait_boot_completed() {
log_info "Waiting for sys.boot_completed (timeout: ${BOOT_TIMEOUT}s)..."
ELAPSED=0
while [ "$ELAPSED" -lt "$BOOT_TIMEOUT" ]; do
BOOT_DONE="$(getprop sys.boot_completed 2>/dev/null)"
if [ "$BOOT_DONE" = "1" ]; then
log_info "Boot completed after ${ELAPSED}s"
return 0
fi
sleep 1
ELAPSED=$((ELAPSED + 1))
done
log_warn "Boot completion timeout after ${BOOT_TIMEOUT}s — proceeding anyway"
return 1
}
wait_boot_completed
# Post-boot settle delay — let other services finish starting
log_info "Settle delay: ${SETTLE_DELAY}s"
sleep "$SETTLE_DELAY"
# ============================================================================
# 3. Runtime stays idle until an APK command
# ============================================================================
log_info "Boot networking cleanup skipped; runtime cleanup waits for explicit APK command"
if [ -f "$MANUAL_FLAG" ]; then
log_info "Manual flag detected at ${MANUAL_FLAG} — proxy autostart stays disabled"
fi
# ============================================================================
# 4. Pre-launch validation
# ============================================================================
has_runtime_profile() {
if command -v rknnovpn_has_runtime_profile >/dev/null 2>&1; then
rknnovpn_has_runtime_profile "$RUNTIME_CONFIG_FILE"
return $?
fi
if [ -f "$RUNTIME_CONFIG_FILE" ]; then
compact="$(tr -d '\n\r\t ' < "$RUNTIME_CONFIG_FILE" 2>/dev/null)"
case "$compact" in
*'"profile":{'*'"nodes":[{'*) return 0 ;;
esac
fi
return 1
}
if ! has_runtime_profile; then
log_info "No rendered runtime profile yet; APK can still edit local settings"
fi
if [ ! -x "$RUNTIME_CLI_BIN" ]; then
log_warn "Runtime CLI not found or not executable: ${RUNTIME_CLI_BIN}; APK local settings remain available"
release_service_lock
exit 0
fi
log_info "Runtime CLI mode is ready; runtime autostart waits for APK command"
release_service_lock
exit 0