platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
265
scripts/transport-packaging/auto_update.sh
Executable file
265
scripts/transport-packaging/auto_update.sh
Executable file
@@ -0,0 +1,265 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user