266 lines
6.2 KiB
Bash
Executable File
266 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
UPDATER="${SCRIPT_DIR}/update.sh"
|
|
|
|
ENABLED="false"
|
|
MANIFEST="${SCRIPT_DIR}/manifest.production.json"
|
|
SOURCE_POLICY=""
|
|
BIN_ROOT="/opt/selective-vpn/bin"
|
|
TARGET=""
|
|
COMPONENTS_RAW=""
|
|
ROLLOUT_STAGE="stable"
|
|
COHORT_ID=""
|
|
FORCE_ROLLOUT=0
|
|
SIGNATURE_MODE=""
|
|
MIN_INTERVAL_SEC=21600
|
|
JITTER_SEC=0
|
|
FORCE_NOW=0
|
|
DRY_RUN=0
|
|
STATE_DIR=""
|
|
LOCK_FILE=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
auto_update.sh [--enabled true|false] [--manifest PATH] [--source-policy PATH]
|
|
[--bin-root DIR] [--target OS-ARCH] [--component NAME[,NAME...]]
|
|
[--rollout-stage stable|canary|any] [--cohort-id 0..99]
|
|
[--signature-mode off|optional|required]
|
|
[--min-interval-sec N] [--jitter-sec N]
|
|
[--state-dir DIR] [--lock-file PATH]
|
|
[--force-rollout] [--force-now] [--dry-run]
|
|
|
|
Description:
|
|
Opt-in scheduler wrapper around update.sh.
|
|
Default behavior is disabled; when enabled, it enforces interval gating and lock.
|
|
|
|
Examples:
|
|
./scripts/transport-packaging/auto_update.sh --enabled true
|
|
./scripts/transport-packaging/auto_update.sh --enabled true --component singbox,phoenix --min-interval-sec 3600
|
|
EOF
|
|
}
|
|
|
|
require_cmd() {
|
|
local cmd="$1"
|
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
echo "[transport-auto-update] missing required command: ${cmd}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
bool_normalize() {
|
|
local raw
|
|
raw="$(echo "$1" | tr '[:upper:]' '[:lower:]' | xargs)"
|
|
case "$raw" in
|
|
1|true|yes|on) echo "true" ;;
|
|
0|false|no|off|"") echo "false" ;;
|
|
*)
|
|
echo "[transport-auto-update] invalid boolean value: ${1}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
int_validate_non_negative() {
|
|
local name="$1"
|
|
local value="$2"
|
|
if [[ ! "$value" =~ ^[0-9]+$ ]]; then
|
|
echo "[transport-auto-update] ${name} must be a non-negative integer" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--enabled)
|
|
ENABLED="${2:-}"
|
|
shift 2
|
|
;;
|
|
--manifest)
|
|
MANIFEST="${2:-}"
|
|
shift 2
|
|
;;
|
|
--source-policy)
|
|
SOURCE_POLICY="${2:-}"
|
|
shift 2
|
|
;;
|
|
--bin-root)
|
|
BIN_ROOT="${2:-}"
|
|
shift 2
|
|
;;
|
|
--target)
|
|
TARGET="${2:-}"
|
|
shift 2
|
|
;;
|
|
--component)
|
|
COMPONENTS_RAW="${2:-}"
|
|
shift 2
|
|
;;
|
|
--rollout-stage)
|
|
ROLLOUT_STAGE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--cohort-id)
|
|
COHORT_ID="${2:-}"
|
|
shift 2
|
|
;;
|
|
--signature-mode)
|
|
SIGNATURE_MODE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--min-interval-sec)
|
|
MIN_INTERVAL_SEC="${2:-}"
|
|
shift 2
|
|
;;
|
|
--jitter-sec)
|
|
JITTER_SEC="${2:-}"
|
|
shift 2
|
|
;;
|
|
--state-dir)
|
|
STATE_DIR="${2:-}"
|
|
shift 2
|
|
;;
|
|
--lock-file)
|
|
LOCK_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--force-rollout)
|
|
FORCE_ROLLOUT=1
|
|
shift
|
|
;;
|
|
--force-now)
|
|
FORCE_NOW=1
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "[transport-auto-update] unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
ENABLED="$(bool_normalize "$ENABLED")"
|
|
int_validate_non_negative "min-interval-sec" "$MIN_INTERVAL_SEC"
|
|
int_validate_non_negative "jitter-sec" "$JITTER_SEC"
|
|
|
|
if [[ "$ENABLED" != "true" ]]; then
|
|
echo "[transport-auto-update] disabled (opt-in mode)"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ ! -x "$UPDATER" ]]; then
|
|
echo "[transport-auto-update] updater not found or not executable: ${UPDATER}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
echo "[transport-auto-update] manifest not found: ${MANIFEST}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$SOURCE_POLICY" && ! -f "$SOURCE_POLICY" ]]; then
|
|
echo "[transport-auto-update] source policy not found: ${SOURCE_POLICY}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
require_cmd flock
|
|
require_cmd date
|
|
|
|
if [[ -z "$STATE_DIR" ]]; then
|
|
STATE_DIR="${BIN_ROOT}/.packaging/auto-update"
|
|
fi
|
|
mkdir -p "$STATE_DIR"
|
|
if [[ -z "$LOCK_FILE" ]]; then
|
|
LOCK_FILE="${STATE_DIR}/auto-update.lock"
|
|
fi
|
|
|
|
last_run_file="${STATE_DIR}/last_run_epoch"
|
|
last_success_file="${STATE_DIR}/last_success_epoch"
|
|
last_error_file="${STATE_DIR}/last_error"
|
|
|
|
echo "[transport-auto-update] enabled=true"
|
|
echo "[transport-auto-update] manifest=${MANIFEST}"
|
|
echo "[transport-auto-update] state_dir=${STATE_DIR}"
|
|
echo "[transport-auto-update] min_interval_sec=${MIN_INTERVAL_SEC}"
|
|
if [[ -n "$COMPONENTS_RAW" ]]; then
|
|
echo "[transport-auto-update] components=${COMPONENTS_RAW}"
|
|
fi
|
|
|
|
exec 9>"$LOCK_FILE"
|
|
if ! flock -n 9; then
|
|
echo "[transport-auto-update] skip: another auto-update process is running"
|
|
exit 0
|
|
fi
|
|
|
|
now_epoch="$(date +%s)"
|
|
last_run_epoch=0
|
|
if [[ -f "$last_run_file" ]]; then
|
|
last_run_epoch="$(cat "$last_run_file" 2>/dev/null || echo 0)"
|
|
[[ "$last_run_epoch" =~ ^[0-9]+$ ]] || last_run_epoch=0
|
|
fi
|
|
|
|
elapsed=$((now_epoch - last_run_epoch))
|
|
if [[ "$FORCE_NOW" -ne 1 && "$elapsed" -lt "$MIN_INTERVAL_SEC" ]]; then
|
|
echo "[transport-auto-update] skip: interval gate (elapsed=${elapsed}s < ${MIN_INTERVAL_SEC}s)"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$JITTER_SEC" -gt 0 && "$FORCE_NOW" -ne 1 ]]; then
|
|
jitter=$((RANDOM % (JITTER_SEC + 1)))
|
|
if [[ "$jitter" -gt 0 ]]; then
|
|
echo "[transport-auto-update] jitter sleep: ${jitter}s"
|
|
sleep "$jitter"
|
|
fi
|
|
fi
|
|
|
|
cmd=("$UPDATER" "--manifest" "$MANIFEST" "--bin-root" "$BIN_ROOT" "--rollout-stage" "$ROLLOUT_STAGE")
|
|
if [[ -n "$SOURCE_POLICY" ]]; then
|
|
cmd+=("--source-policy" "$SOURCE_POLICY")
|
|
fi
|
|
if [[ -n "$TARGET" ]]; then
|
|
cmd+=("--target" "$TARGET")
|
|
fi
|
|
if [[ -n "$COMPONENTS_RAW" ]]; then
|
|
cmd+=("--component" "$COMPONENTS_RAW")
|
|
fi
|
|
if [[ -n "$COHORT_ID" ]]; then
|
|
cmd+=("--cohort-id" "$COHORT_ID")
|
|
fi
|
|
if [[ -n "$SIGNATURE_MODE" ]]; then
|
|
cmd+=("--signature-mode" "$SIGNATURE_MODE")
|
|
fi
|
|
if [[ "$FORCE_ROLLOUT" -eq 1 ]]; then
|
|
cmd+=("--force-rollout")
|
|
fi
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
cmd+=("--dry-run")
|
|
fi
|
|
|
|
echo "[transport-auto-update] run: ${cmd[*]}"
|
|
if "${cmd[@]}"; then
|
|
date +%s >"$last_run_file"
|
|
date +%s >"$last_success_file"
|
|
: >"$last_error_file"
|
|
echo "[transport-auto-update] success"
|
|
exit 0
|
|
fi
|
|
|
|
rc=$?
|
|
date +%s >"$last_run_file"
|
|
{
|
|
echo "ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo "exit_code=${rc}"
|
|
echo "cmd=${cmd[*]}"
|
|
} >"$last_error_file"
|
|
echo "[transport-auto-update] failed rc=${rc}" >&2
|
|
exit "$rc"
|