Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions packs/common-telemetron.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
# packs/common-telemetron.sh — Shared telemetron sidecar installer
#
# Sourced by pack install scripts. Provides `install_telemetron <mode>`.
# Requires: nothing (self-contained). Source after common.sh in pack scripts.
#
# Contract:
# - Never prints to stdout/stderr (all output to log file)
# - Never fails the caller (always returns 0)
# - Idempotent (safe to re-run)

# ── Constants ─────────────────────────────────────────────────────────────────
_TELEMETRON_ENDPOINT="https://telemetry.loki.run/v1/metrics"
_TELEMETRON_ENROLL_ENDPOINT="https://telemetry.loki.run/v1/enroll"
_TELEMETRON_INSTALL_URL="https://raw.githubusercontent.com/inceptionstack/telemetron/main/install.sh"

# ── Gate: should telemetron run? ──────────────────────────────────────────────
_telemetron_should_run() {
if [[ "${PACK_ARG_SKIP_TELEMETRON:-false}" = "true" ]]; then
echo "skip: --skip-telemetron"; return
fi
if [[ "$(uname -s)" != "Linux" ]]; then
echo "skip: non-Linux"; return
fi
if [[ "${LOWKEY_TELEMETRY:-1}" = "0" ]] \
|| [[ "${DO_NOT_TRACK:-0}" = "1" ]] \
|| [[ -f "${HOME:-}/.lowkey/telemetry-off" ]]; then
echo "skip: telemetry opt-out"; return
fi
if ! command -v systemctl >/dev/null 2>&1; then
echo "skip: no systemctl"; return
fi
echo "yes"
}

# ── Tier detection ────────────────────────────────────────────────────────────
# Detects whether the AWS account is internal (@amazon.com) or external.
# Tries 3 methods with 5s timeouts each. Email never leaves the machine.
_telemetron_detect_tier() {
local acct_email=""

# Method 1: account contact info — check all fields for @amazon.com
local contact_info
contact_info=$(timeout 5 aws account get-contact-information --output json 2>/dev/null) || true
if echo "$contact_info" | grep -q '@amazon\.com"'; then
echo "internal"; return
fi

# Method 2: organization master account email (works from member accounts)
acct_email=$(timeout 5 aws organizations describe-organization \
--query 'Organization.MasterAccountEmail' --output text 2>/dev/null) || true
if [[ "$acct_email" == *@amazon.com ]]; then
echo "internal"; return
fi

# Method 3: describe-account (management account only)
local acct_id
acct_id=$(timeout 5 aws sts get-caller-identity --query Account --output text 2>/dev/null) || true
if [[ -n "$acct_id" ]]; then
acct_email=$(timeout 5 aws organizations describe-account \
--account-id "$acct_id" --query 'Account.Email' --output text 2>/dev/null) || true
if [[ "$acct_email" == *@amazon.com ]]; then
echo "internal"; return
fi
fi

echo "external"
}

# ── Write tier files ──────────────────────────────────────────────────────────
_telemetron_write_tier() {
local tier="$1"
local home="${HOME:-/home/ec2-user}"
for dir in "$home/.lowkey" "$home/.loki"; do
mkdir -p "$dir" 2>/dev/null || true
printf '%s\n' "$tier" > "$dir/tier" 2>/dev/null || true
done
}

# ── Install binary ────────────────────────────────────────────────────────────
# Downloads and installs telemetron to /usr/local/bin via sudo.
# Returns 0 on success or if already installed, 1 on failure.
_telemetron_ensure_binary() {
local log="$1"

if command -v telemetron >/dev/null 2>&1 \
|| [[ -x /var/lib/telemetron/bin/telemetron ]]; then
return 0
fi

if ! sudo -n true 2>/dev/null; then
printf '[telemetron] sudo not available (non-interactive) — skipping\n' >>"$log"
return 1
fi

timeout 60 bash -c "
set -euo pipefail
curl --retry 3 --retry-delay 2 --connect-timeout 5 --max-time 55 \
-fsSL '${_TELEMETRON_INSTALL_URL}' | sudo -n TELEMETRON_PREFIX=/usr/local bash
" >>"$log" 2>&1 || {
printf '[telemetron] binary install failed (exit %d)\n' "$?" >>"$log"
return 1
}
}

# ── Resolve binary path ───────────────────────────────────────────────────────
_telemetron_resolve_bin() {
if [[ -x /var/lib/telemetron/bin/telemetron ]]; then
echo "/var/lib/telemetron/bin/telemetron"
elif command -v telemetron >/dev/null 2>&1; then
command -v telemetron
fi
}

# ══════════════════════════════════════════════════════════════════════════════
# Public API
# ══════════════════════════════════════════════════════════════════════════════

# install_telemetron <mode>
#
# Installs and configures telemetron for the given pack mode.
# Uses `telemetron detect` which auto-discovers sessions by install directory.
#
# When called via `sudo`, SUDO_USER is preserved so telemetron resolves
# the correct user home (not /root).
#
# Usage:
# install_telemetron openclaw
# install_telemetron roundhouse
install_telemetron() {
local mode="${1:?usage: install_telemetron <mode>}"
(
set +e
local _raw_log="${INSTALL_LOG:-/tmp/loki-install.log}"
local log
{ >> "$_raw_log"; } 2>/dev/null && log="$_raw_log" || log=/dev/null

printf '\n[telemetron] begin %s mode=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$mode" >>"$log"

# Gate check
local decision
decision="$(_telemetron_should_run)"
if [[ "$decision" != "yes" ]]; then
printf '[telemetron] %s\n' "$decision" >>"$log"
printf '[telemetron] end %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$log"
exit 0
fi

# Tier detection + write
local tier
tier="$(_telemetron_detect_tier)"
_telemetron_write_tier "$tier"
printf '[telemetron] tier=%s\n' "$tier" >>"$log"

# Ensure binary installed
if ! _telemetron_ensure_binary "$log"; then
printf '[telemetron] end %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$log"
exit 0
fi

# Resolve binary path
local bin
bin="$(_telemetron_resolve_bin)"
if [[ -z "$bin" ]]; then
printf '[telemetron] binary not found after install\n' >>"$log"
printf '[telemetron] end %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$log"
exit 0
fi

# Run detect — auto-discovers sessions, enrolls, starts service
timeout 30 sudo -n "$bin" detect \
--endpoint "$_TELEMETRON_ENDPOINT" \
--enroll-endpoint "$_TELEMETRON_ENROLL_ENDPOINT" \
--mode "$mode" \
--force >>"$log" 2>&1 || {
printf '[telemetron] detect failed (exit %d)\n' "$?" >>"$log"
printf '[telemetron] end %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$log"
exit 0
}

printf '[telemetron] detect completed successfully\n' >>"$log"
printf '[telemetron] end %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$log"
exit 0
) || true
}
4 changes: 4 additions & 0 deletions packs/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ pack_banner() {
# companion (metrics agent, diagnostics daemon, etc.) with the following
# hard invariants:
#
# DEPRECATED: No pack currently calls this function. Telemetron installs now
# use `install_telemetron` from common-telemetron.sh instead. Kept for
# potential future sidecars that use a curl|bash pipeline pattern.
#
# - Silent: zero bytes on caller's stdout/stderr. Every line (begin,
# outcome, transcript of the inner installer, end) goes to
# LOG_FILE only.
Expand Down
107 changes: 4 additions & 103 deletions packs/openclaw/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -249,107 +249,8 @@ fi
write_done_marker "openclaw"
printf "\n[PACK:openclaw] INSTALLED — gateway on :%s (systemd: openclaw-gateway)\n" "${GW_PORT}"

# ── Optional sidecar: telemetron ──────────────────────────────────────────────
# Anonymous enrollment against the Loki telemetry backend. The pack decides
# "should this run?" here; common.sh:run_optional_sidecar owns the silent,
# bounded, pipefail-safe install machinery. Never blocks, never warns, never
# prints anything on the user's terminal — all output goes to $INSTALL_LOG.

# should_run_telemetron — pure decision. Echoes "yes" or "skip: <reason>".
should_run_telemetron() {
if [[ "${PACK_ARG_SKIP_TELEMETRON:-false}" = "true" ]]; then
echo "skip: --skip-telemetron"; return
fi
if [[ "$(uname -s)" != "Linux" ]]; then
echo "skip: non-Linux"; return
fi
if [[ "${LOWKEY_TELEMETRY:-1}" = "0" ]] \
|| [[ "${DO_NOT_TRACK:-0}" = "1" ]] \
|| [[ -f "${HOME:-}/.lowkey/telemetry-off" ]]; then
echo "skip: lowkey telemetry opt-out"; return
fi
if ! command -v systemctl >/dev/null 2>&1; then
echo "skip: systemctl not found"; return
fi
echo "yes"
}

_telemetron_sidecar() {
# Resolve the log path once. Fall back to /dev/null if not writable
# so the "never prints on the user's terminal" contract holds even when
# INSTALL_LOG points at an unwritable or nonexistent path.
local _raw_log="${INSTALL_LOG:-/tmp/loki-install.log}"
local log; { >> "$_raw_log"; } 2>/dev/null && log="$_raw_log" || log=/dev/null
local decision
decision="$(should_run_telemetron)"
if [[ "$decision" != "yes" ]]; then
{
printf '\n[telemetron] begin %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf '[telemetron] %s\n' "$decision"
printf '[telemetron] end %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
} >>"$log" || true
return 0
fi
local endpoint="https://telemetry.loki.run/v1/metrics"
local enroll_endpoint="https://telemetry.loki.run/v1/enroll"
local url="https://raw.githubusercontent.com/inceptionstack/telemetron/main/install.sh"
# session_dir: telemetron auto-detects from $HOME, but under sudo $HOME
# becomes /root. Point it at the real ec2-user openclaw session tree.
local session_dir="${HOME:-/home/ec2-user}/.openclaw/agents/main/sessions"

# Detect tier from AWS account owner email. Internal = @amazon.com.
# The email stays local — only the tier string is written to the tier file.
# Bounded to 5s per method to avoid hanging the installer.
# Tries three methods:
# 1. aws account get-contact-information — checks all fields for @amazon.com
# 2. aws organizations describe-organization — master account email (works from member accounts)
# 3. aws organizations describe-account — account email (only from management account)
local tier="external"
local acct_email=""

# Method 1: account contact info — check ALL fields for @amazon.com
# FullName sometimes contains the account email on Amazon accounts, but other
# accounts may have a person's name there. Check the full JSON output.
local contact_info
contact_info=$(timeout 5 aws account get-contact-information --output json 2>/dev/null) || true
if echo "$contact_info" | grep -q '@amazon\.com"'; then
acct_email="internal@amazon.com"
fi

# Method 2: organization master account email (works from member accounts)
if [[ "$acct_email" != *@amazon.com ]]; then
acct_email=$(timeout 5 aws organizations describe-organization \
--query 'Organization.MasterAccountEmail' --output text 2>/dev/null) || true
fi

# Method 3: organizations describe-account (only works from management account)
if [[ "$acct_email" != *@amazon.com ]]; then
acct_email=$(timeout 5 aws organizations describe-account \
--account-id "${ACCOUNT_ID:-$(timeout 3 aws sts get-caller-identity --query Account --output text 2>/dev/null)}" \
--query 'Account.Email' --output text 2>/dev/null) || true
fi

if [[ "$acct_email" == *@amazon.com ]]; then
tier="internal"
fi

# Write tier file for telemetron (and future tools) to read.
# Both .lowkey and .loki paths in case we rename.
local home="${HOME:-/home/ec2-user}"
for dir in "$home/.lowkey" "$home/.loki"; do
mkdir -p "$dir" 2>/dev/null || true
printf '%s\n' "$tier" > "$dir/tier" 2>/dev/null || true
done

# TELEMETRON_VERSION intentionally unset — install.sh resolves latest via GitHub API.
# Auto-update handles subsequent upgrades after initial install.
run_optional_sidecar telemetron "$url" 60 "$log" \
"TELEMETRON_ENDPOINT=$endpoint" \
"TELEMETRON_ENROLL_ENDPOINT=$enroll_endpoint" \
"TELEMETRON_PREFIX=/usr/local" \
"TELEMETRON_SESSION_DIR=$session_dir" \
"TELEMETRON_RUN_AS=${USER:-ec2-user}" \
"SIDECAR_USE_SUDO=1"
}

_telemetron_sidecar
# ── Optional sidecar: telemetron ──────────────────────────────────────────────
# shellcheck source=../common-telemetron.sh
source "${SCRIPT_DIR}/../common-telemetron.sh"
install_telemetron openclaw
49 changes: 17 additions & 32 deletions packs/openclaw/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,48 +70,33 @@ bash "${INSTALL}" --skip-telemetron --help >/dev/null 2>&1 \
&& pass "--skip-telemetron accepted as an arg (not rejected by parser)" \
|| fail "--skip-telemetron rejected by parser"

# ── Test: should_run_telemetron() decisions ───────────────────────────────────
# The pack's *policy* layer. Pure function — given env/PATH, returns one of:
# ── Test: _telemetron_should_run() decisions ──────────────────────────────────
# The shared policy gate from common-telemetron.sh. Pure function — given
# env/PATH, returns one of:
# yes | skip: --skip-telemetron | skip: non-Linux
# skip: lowkey telemetry opt-out | skip: systemctl not found
# We source install.sh with the `_telemetron_sidecar` call short-circuited so
# sourcing doesn't actually run any network or install work.
header "Test: should_run_telemetron() — pack policy decisions"
# skip: telemetry opt-out | skip: no systemctl
header "Test: _telemetron_should_run() — shared policy decisions"

DEC_TMP="$(mktemp -d)"
# Patched install.sh that defines functions but skips the final invocation
# and the rest of the pack install. We extract only up to the `_telemetron_sidecar`
# invocation, and stop there.
sed '/^_telemetron_sidecar$/,$d' "${INSTALL}" > "${DEC_TMP}/install-patched.sh"
# The patched file sources common.sh and then runs the entire pack install.
# That's fine for function definitions, but we also need to neutralize the
# install logic so tests don't try to `npm install` etc. Strategy: source only
# the common.sh + the last two function definitions (should_run_telemetron,
# _telemetron_sidecar) by extracting them explicitly.

# Extract should_run_telemetron from install.sh.
awk '
/^should_run_telemetron\(\) \{/ { p=1 }
p
p && /^\}/ { p=0 }
' "${INSTALL}" > "${DEC_TMP}/should_run.sh"
if grep -q '^should_run_telemetron() {' "${DEC_TMP}/should_run.sh" \
&& grep -q '^}$' "${DEC_TMP}/should_run.sh"; then
pass "extracted should_run_telemetron()"

# The function lives in common-telemetron.sh — source it directly.
COMMON_TELEMETRON="${SCRIPT_DIR}/../common-telemetron.sh"
if [[ -f "$COMMON_TELEMETRON" ]]; then
pass "common-telemetron.sh exists"
else
fail "could not extract should_run_telemetron"
fail "common-telemetron.sh not found at ${COMMON_TELEMETRON}"
exit 1
fi

# Helper: run should_run_telemetron under an isolated PATH + env, compare output.
# Helper: run _telemetron_should_run under an isolated PATH + env, compare output.
dec_run() { # args: ENV_KV... -- PATH_DIR — prints decision
local envs=() path_dir=""
while [[ $# -gt 0 ]]; do
if [[ "$1" = "--" ]]; then shift; path_dir="$1"; shift; break
else envs+=("$1"); shift; fi
done
env -i HOME="${DEC_TMP}/home" PATH="${path_dir}" BASH_ENV= ENV= "${envs[@]}" \
bash --noprofile --norc -c "set -euo pipefail; source '${DEC_TMP}/should_run.sh'; should_run_telemetron"
bash --noprofile --norc -c "set -euo pipefail; source '${COMMON_TELEMETRON}'; _telemetron_should_run"
}

mk_path() { # args: dir — pre-populate a PATH dir with common coreutils
Expand Down Expand Up @@ -140,19 +125,19 @@ mkdir -p "${DEC_TMP}/home"

assert_decision "no flags → yes" "yes" -- "$P"
assert_decision "--skip-telemetron → skip" "skip: --skip-telemetron" PACK_ARG_SKIP_TELEMETRON=true -- "$P"
assert_decision "LOWKEY_TELEMETRY=0 → opt-out" "skip: lowkey telemetry opt-out" LOWKEY_TELEMETRY=0 -- "$P"
assert_decision "DO_NOT_TRACK=1 → opt-out" "skip: lowkey telemetry opt-out" DO_NOT_TRACK=1 -- "$P"
assert_decision "LOWKEY_TELEMETRY=0 → opt-out" "skip: telemetry opt-out" LOWKEY_TELEMETRY=0 -- "$P"
assert_decision "DO_NOT_TRACK=1 → opt-out" "skip: telemetry opt-out" DO_NOT_TRACK=1 -- "$P"

mkdir -p "${DEC_TMP}/home/.lowkey"; touch "${DEC_TMP}/home/.lowkey/telemetry-off"
assert_decision "telemetry-off file → opt-out" "skip: lowkey telemetry opt-out" -- "$P"
assert_decision "telemetry-off file → opt-out" "skip: telemetry opt-out" -- "$P"
rm -f "${DEC_TMP}/home/.lowkey/telemetry-off"

P_DARWIN="${DEC_TMP}/path.darwin"; mk_path "$P_DARWIN"
rm -f "${P_DARWIN}/uname"; printf '#!/bin/sh\necho Darwin\n' > "${P_DARWIN}/uname"; chmod +x "${P_DARWIN}/uname"
assert_decision "Darwin → non-Linux" "skip: non-Linux" -- "$P_DARWIN"

P_NOSYSD="${DEC_TMP}/path.nosysd"; mk_path "$P_NOSYSD"; rm -f "${P_NOSYSD}/systemctl"
assert_decision "no systemctl → skip" "skip: systemctl not found" -- "$P_NOSYSD"
assert_decision "no systemctl → skip" "skip: no systemctl" -- "$P_NOSYSD"

# Decision precedence: explicit --skip wins over everything.
assert_decision "--skip beats opt-out env" "skip: --skip-telemetron" \
Expand Down
Loading