252 lines
8.7 KiB
Shell
Executable file
252 lines
8.7 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
# Hesabix anonymous/semi-anonymous install telemetry (opt-out: HESABIX_TELEMETRY=0).
|
|
# Sourced by deploy.sh (after clone) and update.sh. Never aborts the caller on failure.
|
|
#
|
|
# Sends JSON POST to https://hesabix.ir/wp-json/hesabix-stats/v1/event
|
|
# Override: HESABIX_STATS_URL, HESABIX_STATS_TOKEN
|
|
#
|
|
# shellcheck shell=bash
|
|
|
|
# Endpoint and soft ingest token (must match WP option his_ingest_token on hesabix.ir).
|
|
HESABIX_STATS_URL_DEFAULT="https://hesabix.ir/wp-json/hesabix-stats/v1/event"
|
|
HESABIX_STATS_TOKEN_DEFAULT="hesabix-stats-ingest-v1"
|
|
|
|
hesabix_telemetry_enabled() {
|
|
case "${HESABIX_TELEMETRY:-1}" in
|
|
0|false|FALSE|no|NO|n|N|off|OFF) return 1 ;;
|
|
*) return 0 ;;
|
|
esac
|
|
}
|
|
|
|
# Ensure INSTALL_ID exists (UUID). Prefer existing value from env / .deploy_env.
|
|
hesabix_ensure_install_id() {
|
|
local env_file="${APP_ROOT:-/opt/hesabix}/.deploy_env"
|
|
local candidate="${INSTALL_ID:-}"
|
|
|
|
if [[ -z "${candidate}" ]] && [[ -f "${env_file}" ]]; then
|
|
candidate="$(grep -E '^INSTALL_ID=' "${env_file}" 2>/dev/null | head -n1 | cut -d= -f2- | tr -d '\r' || true)"
|
|
fi
|
|
|
|
if [[ -n "${candidate}" ]] && [[ "${candidate}" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ ]]; then
|
|
INSTALL_ID="$(printf '%s' "${candidate}" | tr '[:upper:]' '[:lower:]')"
|
|
export INSTALL_ID
|
|
return 0
|
|
fi
|
|
|
|
if command -v uuidgen >/dev/null 2>&1; then
|
|
INSTALL_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
|
|
elif [[ -r /proc/sys/kernel/random/uuid ]]; then
|
|
INSTALL_ID="$(tr '[:upper:]' '[:lower:]' < /proc/sys/kernel/random/uuid)"
|
|
elif command -v python3 >/dev/null 2>&1; then
|
|
INSTALL_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
|
|
else
|
|
# Last resort: not a real UUID v4 but unique enough; WP will reject — skip send later.
|
|
INSTALL_ID=""
|
|
return 1
|
|
fi
|
|
export INSTALL_ID
|
|
return 0
|
|
}
|
|
|
|
# Persist INSTALL_ID into .deploy_env without wiping other keys (append or replace line).
|
|
hesabix_persist_install_id() {
|
|
local env_file="${APP_ROOT:-/opt/hesabix}/.deploy_env"
|
|
hesabix_ensure_install_id || return 0
|
|
mkdir -p "$(dirname "${env_file}")"
|
|
if [[ -f "${env_file}" ]]; then
|
|
if grep -qE '^INSTALL_ID=' "${env_file}" 2>/dev/null; then
|
|
# portable in-place replace
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
awk -v id="${INSTALL_ID}" 'BEGIN{done=0} /^INSTALL_ID=/{print "INSTALL_ID=" id; done=1; next} {print} END{if(!done) print "INSTALL_ID=" id}' "${env_file}" > "${tmp}" \
|
|
&& mv -f "${tmp}" "${env_file}"
|
|
else
|
|
printf '\nINSTALL_ID=%s\n' "${INSTALL_ID}" >> "${env_file}"
|
|
fi
|
|
chmod 600 "${env_file}" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
hesabix_telemetry_json_escape() {
|
|
# Escape a string for JSON (no surrounding quotes).
|
|
local s="${1-}"
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 -c 'import json,sys; print(json.dumps(sys.argv[1])[1:-1])' "${s}" 2>/dev/null && return 0
|
|
fi
|
|
s="${s//\\/\\\\}"
|
|
s="${s//\"/\\\"}"
|
|
s="${s//$'\n'/\\n}"
|
|
s="${s//$'\r'/\\r}"
|
|
s="${s//$'\t'/\\t}"
|
|
printf '%s' "${s}"
|
|
}
|
|
|
|
hesabix_telemetry_collect_ints() {
|
|
# Sets: _HIS_RAM_MB _HIS_CPU _HIS_DISK_GB
|
|
_HIS_RAM_MB=0
|
|
_HIS_CPU=0
|
|
_HIS_DISK_GB=0
|
|
if [[ -r /proc/meminfo ]]; then
|
|
_HIS_RAM_MB="$(awk '/^MemTotal:/ {print int($2/1024); exit}' /proc/meminfo 2>/dev/null || echo 0)"
|
|
fi
|
|
if command -v nproc >/dev/null 2>&1; then
|
|
_HIS_CPU="$(nproc 2>/dev/null || echo 0)"
|
|
elif [[ -r /proc/cpuinfo ]]; then
|
|
_HIS_CPU="$(grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo 0)"
|
|
fi
|
|
if command -v df >/dev/null 2>&1; then
|
|
_HIS_DISK_GB="$(df -BG / 2>/dev/null | awk 'NR==2 {gsub(/G/,"",$4); print int($4); exit}' || echo 0)"
|
|
fi
|
|
}
|
|
|
|
hesabix_telemetry_os_fields() {
|
|
_HIS_OS_ID=""
|
|
_HIS_OS_VERSION=""
|
|
if [[ -r /etc/os-release ]]; then
|
|
# shellcheck disable=SC1091
|
|
. /etc/os-release
|
|
_HIS_OS_ID="${ID:-}"
|
|
_HIS_OS_VERSION="${VERSION_ID:-}"
|
|
fi
|
|
}
|
|
|
|
hesabix_telemetry_git_commit() {
|
|
local app_dir="${APP_ROOT:-/opt/hesabix}/app"
|
|
_HIS_GIT_COMMIT=""
|
|
if [[ -d "${app_dir}/.git" ]] && command -v git >/dev/null 2>&1; then
|
|
_HIS_GIT_COMMIT="$(git -C "${app_dir}" rev-parse --short=12 HEAD 2>/dev/null || true)"
|
|
fi
|
|
}
|
|
|
|
hesabix_telemetry_app_version() {
|
|
_HIS_APP_VERSION=""
|
|
local candidates=(
|
|
"${APP_ROOT:-/opt/hesabix}/app/hesabixAPI/VERSION"
|
|
"${APP_ROOT:-/opt/hesabix}/app/VERSION"
|
|
)
|
|
local f
|
|
for f in "${candidates[@]}"; do
|
|
if [[ -f "${f}" ]]; then
|
|
_HIS_APP_VERSION="$(head -n1 "${f}" | tr -d '\r' | head -c 64)"
|
|
break
|
|
fi
|
|
done
|
|
}
|
|
|
|
hesabix_telemetry_ssl_flags() {
|
|
_HIS_SSL_API=0
|
|
_HIS_SSL_UI=0
|
|
local api="${API_DOMAIN:-}"
|
|
local ui="${UI_DOMAIN:-}"
|
|
if [[ -n "${api}" ]] && [[ -d "/etc/letsencrypt/live/${api}" ]]; then
|
|
_HIS_SSL_API=1
|
|
fi
|
|
if [[ -n "${ui}" ]] && [[ -d "/etc/letsencrypt/live/${ui}" ]]; then
|
|
_HIS_SSL_UI=1
|
|
fi
|
|
}
|
|
|
|
hesabix_telemetry_bool_yn() {
|
|
case "${1:-}" in
|
|
[Yy]|[Yy][Ee][Ss]|1|true|TRUE|on|ON) echo 1 ;;
|
|
*) echo 0 ;;
|
|
esac
|
|
}
|
|
|
|
# Public: send one event. Args: event_name (install|update|heartbeat)
|
|
# Optional second arg: script label (deploy|update)
|
|
hesabix_telemetry_send() {
|
|
local event="${1:-}"
|
|
local script_label="${2:-unknown}"
|
|
|
|
if ! hesabix_telemetry_enabled; then
|
|
return 0
|
|
fi
|
|
if [[ "${event}" != "install" && "${event}" != "update" && "${event}" != "heartbeat" ]]; then
|
|
return 0
|
|
fi
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
hesabix_ensure_install_id || return 0
|
|
hesabix_persist_install_id
|
|
|
|
local api_domain="${API_DOMAIN:-}"
|
|
local ui_domain="${UI_DOMAIN:-}"
|
|
if [[ -z "${api_domain}" && -z "${ui_domain}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
hesabix_telemetry_collect_ints
|
|
hesabix_telemetry_os_fields
|
|
hesabix_telemetry_git_commit
|
|
hesabix_telemetry_app_version
|
|
hesabix_telemetry_ssl_flags
|
|
|
|
local arch hostname reported_ip branch
|
|
arch="$(uname -m 2>/dev/null || true)"
|
|
hostname="$(hostname -f 2>/dev/null || hostname 2>/dev/null || true)"
|
|
branch="${BRANCH:-}"
|
|
reported_ip=""
|
|
# Best-effort public IP (non-fatal, short timeout).
|
|
reported_ip="$(curl -fsS --connect-timeout 2 --max-time 3 https://api.ipify.org 2>/dev/null || true)"
|
|
if [[ -n "${reported_ip}" ]] && ! [[ "${reported_ip}" =~ ^[0-9a-fA-F:.]+$ ]]; then
|
|
reported_ip=""
|
|
fi
|
|
|
|
local workers="${UVICORN_WORKERS:-0}"
|
|
if ! [[ "${workers}" =~ ^[0-9]+$ ]]; then
|
|
workers=0
|
|
fi
|
|
# Force collected metrics to integers (empty/non-numeric → 0).
|
|
[[ "${_HIS_RAM_MB}" =~ ^[0-9]+$ ]] || _HIS_RAM_MB=0
|
|
[[ "${_HIS_CPU}" =~ ^[0-9]+$ ]] || _HIS_CPU=0
|
|
[[ "${_HIS_DISK_GB}" =~ ^[0-9]+$ ]] || _HIS_DISK_GB=0
|
|
|
|
local pgadmin voice pip_m flutter_m
|
|
pgadmin="$(hesabix_telemetry_bool_yn "${INSTALL_PGADMIN4:-N}")"
|
|
voice="$(hesabix_telemetry_bool_yn "${INSTALL_VOICE:-N}")"
|
|
pip_m="${PIP_MIRROR:-}"
|
|
flutter_m="${FLUTTER_MIRROR:-}"
|
|
|
|
local url token
|
|
url="${HESABIX_STATS_URL:-${HESABIX_STATS_URL_DEFAULT}}"
|
|
token="${HESABIX_STATS_TOKEN:-${HESABIX_STATS_TOKEN_DEFAULT}}"
|
|
|
|
local eid eapi eui earch eosid eosver ebranch ecommit eapp epip eflutter ehost eip escript
|
|
eid="$(hesabix_telemetry_json_escape "${INSTALL_ID}")"
|
|
eapi="$(hesabix_telemetry_json_escape "${api_domain}")"
|
|
eui="$(hesabix_telemetry_json_escape "${ui_domain}")"
|
|
earch="$(hesabix_telemetry_json_escape "${arch}")"
|
|
eosid="$(hesabix_telemetry_json_escape "${_HIS_OS_ID}")"
|
|
eosver="$(hesabix_telemetry_json_escape "${_HIS_OS_VERSION}")"
|
|
ebranch="$(hesabix_telemetry_json_escape "${branch}")"
|
|
ecommit="$(hesabix_telemetry_json_escape "${_HIS_GIT_COMMIT}")"
|
|
eapp="$(hesabix_telemetry_json_escape "${_HIS_APP_VERSION}")"
|
|
epip="$(hesabix_telemetry_json_escape "${pip_m}")"
|
|
eflutter="$(hesabix_telemetry_json_escape "${flutter_m}")"
|
|
ehost="$(hesabix_telemetry_json_escape "${hostname}")"
|
|
eip="$(hesabix_telemetry_json_escape "${reported_ip}")"
|
|
escript="$(hesabix_telemetry_json_escape "${script_label}")"
|
|
|
|
local payload
|
|
payload=$(cat <<EOF
|
|
{"schema_version":1,"install_id":"${eid}","event":"${event}","api_domain":"${eapi}","ui_domain":"${eui}","reported_ip":"${eip}","ram_mb":${_HIS_RAM_MB:-0},"cpu_cores":${_HIS_CPU:-0},"disk_free_gb":${_HIS_DISK_GB:-0},"arch":"${earch}","os_id":"${eosid}","os_version":"${eosver}","branch":"${ebranch}","git_commit":"${ecommit}","app_version":"${eapp}","uvicorn_workers":${workers:-0},"ssl_api":${_HIS_SSL_API},"ssl_ui":${_HIS_SSL_UI},"install_pgadmin":${pgadmin},"install_voice":${voice},"pip_mirror":"${epip}","flutter_mirror":"${eflutter}","script":"${escript}","hostname":"${ehost}"}
|
|
EOF
|
|
)
|
|
|
|
# Never fail the installer: swallow curl errors.
|
|
curl -fsS --http1.1 \
|
|
--connect-timeout 5 \
|
|
--max-time 12 \
|
|
-X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept: application/json" \
|
|
-H "X-Hesabix-Stats-Token: ${token}" \
|
|
-H "User-Agent: HesabixTelemetry/1.0 (${script_label})" \
|
|
--data-binary "${payload}" \
|
|
"${url}" >/dev/null 2>&1 || true
|
|
|
|
return 0
|
|
}
|