284 lines
8.5 KiB
Shell
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
|