Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b4fae98
feat: add Cloud command for managing iii Cloud deployments and update…
ytallo Mar 28, 2026
a18db3f
Merge remote-tracking branch 'origin/main' into feat/iii-cloud-cli
ytallo Mar 28, 2026
2483d37
feat(telemetry): migrate engine identity to telemetry.yaml v2
anthonyiscoding Apr 1, 2026
0b390f6
refactor(telemetry): deduplicate heartbeat event construction in star…
anthonyiscoding Apr 1, 2026
3c8a154
fix(telemetry): normalize framework identity and worker counting acro…
anthonyiscoding Apr 1, 2026
f11845f
fix(sdk/python): normalize framework fallback and apply formatter
anthonyiscoding Apr 1, 2026
ccc5fb8
chore(install): drop curl telemetry fallback, binary-only install events
anthonyiscoding Apr 1, 2026
1533cac
fix(telemetry): use real delta metrics for boot heartbeat instead of …
anthonyiscoding Apr 1, 2026
a247fe2
chore(telemetry): comment out delta metrics reporting for now
anthonyiscoding Apr 1, 2026
ad6770e
Merge origin/main into fix/amplitude-telemetry-ids
anthonyiscoding Apr 1, 2026
24a313f
fix(sdk): guard framework fallback against empty/falsy values
anthonyiscoding Apr 1, 2026
2f87517
fix(telemetry): harden install script JSON encoding, error handling, …
anthonyiscoding Apr 1, 2026
15963c7
style: apply cargo fmt formatting
anthonyiscoding Apr 1, 2026
6dd6481
fix(telemetry): prioritize CI detection over III_EXECUTION_CONTEXT en…
anthonyiscoding Apr 2, 2026
e684ba8
fix(telemetry): generate stable device IDs in container environments
anthonyiscoding Apr 2, 2026
6210605
fix(telemetry): report host_user_id in container environment info
anthonyiscoding Apr 2, 2026
3234263
fix(telemetry): isolate tests that write to HOME and add missing host…
anthonyiscoding Apr 2, 2026
502253f
fix(telemetry): fall back to project.ini device_id for host_user_id
anthonyiscoding Apr 2, 2026
22e3ae0
fix: report user_host_id
anthonyiscoding Apr 2, 2026
676db61
Merge remote-tracking branch 'origin/main' into fix/amplitude-telemet…
anthonyiscoding Apr 2, 2026
eeeabdc
fix: id reporting
anthonyiscoding Apr 2, 2026
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
351 changes: 285 additions & 66 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ nix = { version = "0.30.1", features = ["signal", "process"] }
winapi = { version = "0.3.9", features = ["minwindef", "wincon", "winbase", "consoleapi"] }
dirs = "6"
hostname = "0.4"
machineid-rs = "1.2.4"
sha2 = "0.10"
iana-time-zone = "0.1"
ring = "0.17.14"
Expand Down
2 changes: 1 addition & 1 deletion engine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM gcr.io/distroless/cc-debian12:nonroot
ARG TARGETARCH
COPY iii-${TARGETARCH} /app/iii

ENV III_CONTAINER=docker
ENV III_EXECUTION_CONTEXT=docker
ENV III_ENV=development

EXPOSE 49134 3111 3112 9464
Expand Down
2 changes: 1 addition & 1 deletion engine/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ services:
volumes:
- ./config.prod.yaml:/app/config.yaml:ro
environment:
- III_HOST_USER_ID=${III_HOST_USER_ID:-}
- III_EXECUTION_CONTEXT=docker
healthcheck:
test: ["CMD-SHELL", "nc -z 127.0.0.1 3111 || exit 1"]
interval: 10s
Expand Down
2 changes: 1 addition & 1 deletion engine/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ services:
- ./config.yaml:/app/config.yaml:ro
environment:
- RUST_LOG=info
- III_HOST_USER_ID=${III_HOST_USER_ID:-}
- III_EXECUTION_CONTEXT=docker
depends_on:
redis:
condition: service_healthy
Expand Down
238 changes: 45 additions & 193 deletions engine/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@ set -eu
REPO="${REPO:-iii-hq/iii}"
BIN_NAME="${BIN_NAME:-iii}"

AMPLITUDE_ENDPOINT="https://api2.amplitude.com/2/httpapi"
AMPLITUDE_API_KEY="${III_INSTALL_AMPLITUDE_API_KEY:-a7182ac460dde671c8f2e1318b517228}"
telemetry_emitter=""
install_event_prefix=""
from_version=""
release_version=""

err() {
_stage="$1"; shift
echo "error: $*" >&2
if [ -n "${install_event_prefix:-}" ] && [ -n "${install_id:-}" ] && [ -n "${telemetry_id:-}" ]; then
if [ -n "${install_event_prefix:-}" ]; then
_err_msg=$(echo "$*" | tr '"' "'")
if [ "$install_event_prefix" = "upgrade" ]; then
iii_send_event "upgrade_failed" \
"\"install_id\":\"${install_id}\",\"from_version\":\"${from_version:-}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\",\"error_stage\":\"${_stage}\",\"error_message\":\"${_err_msg}\"" \
"$telemetry_id" "$install_id"
iii_emit_event "upgrade_failed" \
"{\"from_version\":\"${from_version:-}\",\"to_version\":\"${release_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\",\"error_stage\":\"${_stage}\",\"error_message\":\"${_err_msg}\"}"
else
iii_send_event "install_failed" \
"\"install_id\":\"${install_id}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\",\"error_stage\":\"${_stage}\",\"error_message\":\"${_err_msg}\"" \
"$telemetry_id" "$install_id"
iii_emit_event "install_failed" \
"{\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\",\"error_stage\":\"${_stage}\",\"error_message\":\"${_err_msg}\"}"
fi
wait
fi
exit 1
}
Expand All @@ -30,163 +29,17 @@ err() {
# Telemetry helpers
# ---------------------------------------------------------------------------

iii_telemetry_enabled() {
case "${III_TELEMETRY_ENABLED:-}" in
false|0) return 1 ;;
esac
for ci_var in CI GITHUB_ACTIONS GITLAB_CI CIRCLECI JENKINS_URL TRAVIS BUILDKITE TF_BUILD CODEBUILD_BUILD_ID BITBUCKET_BUILD_NUMBER DRONE TEAMCITY_VERSION; do
if [ -n "$(eval "echo \${${ci_var}:-}")" ]; then
return 1
fi
done
return 0
}

iii_gen_uuid() {
if command -v uuidgen >/dev/null 2>&1; then
uuidgen | tr '[:upper:]' '[:lower:]'
elif [ -r /proc/sys/kernel/random/uuid ]; then
cat /proc/sys/kernel/random/uuid
else
od -x /dev/urandom 2>/dev/null | head -1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' | head -c 36 || echo "00000000-0000-0000-0000-000000000000"
fi
}

iii_toml_path() {
echo "${HOME}/.iii/telemetry.toml"
}

iii_read_toml_key() {
_toml_section="$1"
_toml_key="$2"
_toml_file=$(iii_toml_path)
if [ ! -f "$_toml_file" ]; then
echo ""
return
fi
_in_section=0
while IFS= read -r _line || [ -n "$_line" ]; do
_line=$(printf '%s' "$_line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
case "$_line" in
"[$_toml_section]") _in_section=1 ;;
"["*"]") _in_section=0 ;;
*)
if [ "$_in_section" = "1" ]; then
case "$_line" in
"$_toml_key ="*|"$_toml_key= "*|"$_toml_key=")
_val=$(printf '%s' "$_line" | cut -d'=' -f2- | sed 's/^[[:space:]]*//;s/^"//;s/"$//')
echo "$_val"
return
;;
esac
fi
;;
esac
done < "$_toml_file"
echo ""
}

iii_set_toml_key() {
_toml_section="$1"
_toml_key="$2"
_toml_value="$3"
_toml_file=$(iii_toml_path)
mkdir -p "$(dirname "$_toml_file")"
_tmp_file="${_toml_file}.tmp"
_written=0
_in_target=0
_key_written=0
: > "$_tmp_file"
if [ -f "$_toml_file" ]; then
while IFS= read -r _line || [ -n "$_line" ]; do
_trimmed=$(printf '%s' "$_line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
case "$_trimmed" in
"[$_toml_section]")
printf '%s\n' "$_trimmed" >> "$_tmp_file"
_in_target=1
;;
"["*"]")
if [ "$_in_target" = "1" ] && [ "$_key_written" = "0" ]; then
printf '%s = "%s"\n' "$_toml_key" "$_toml_value" >> "$_tmp_file"
_key_written=1
fi
_in_target=0
printf '%s\n' "$_trimmed" >> "$_tmp_file"
;;
"$_toml_key ="*|"$_toml_key= "*|"$_toml_key=")
if [ "$_in_target" = "1" ]; then
printf '%s = "%s"\n' "$_toml_key" "$_toml_value" >> "$_tmp_file"
_key_written=1
else
printf '%s\n' "$_line" >> "$_tmp_file"
fi
;;
"")
printf '\n' >> "$_tmp_file"
;;
*)
printf '%s\n' "$_line" >> "$_tmp_file"
;;
esac
done < "$_toml_file"
fi
if [ "$_key_written" = "0" ]; then
if [ "$_in_target" = "1" ]; then
printf '%s = "%s"\n' "$_toml_key" "$_toml_value" >> "$_tmp_file"
else
printf '\n[%s]\n%s = "%s"\n' "$_toml_section" "$_toml_key" "$_toml_value" >> "$_tmp_file"
fi
fi
mv "$_tmp_file" "$_toml_file"
}

iii_get_or_create_telemetry_id() {
_existing_id=$(iii_read_toml_key "identity" "id")
if [ -n "$_existing_id" ]; then
echo "$_existing_id"
return
fi

_legacy_path="${HOME}/.iii/telemetry_id"
if [ -f "$_legacy_path" ]; then
_legacy_id=$(cat "$_legacy_path" 2>/dev/null | tr -d '[:space:]')
if [ -n "$_legacy_id" ]; then
iii_set_toml_key "identity" "id" "$_legacy_id"
echo "$_legacy_id"
return
fi
fi

mkdir -p "${HOME}/.iii"
_new_id="auto-$(iii_gen_uuid)"
iii_set_toml_key "identity" "id" "$_new_id"
echo "$_new_id"
}

iii_send_event() {
iii_emit_event() {
_event_type="$1"
_event_props="$2"
_telemetry_id="$3"
_install_id="$4"

if [ -z "$AMPLITUDE_API_KEY" ]; then
if [ -z "${telemetry_emitter:-}" ] || [ ! -x "$telemetry_emitter" ]; then
return 0
fi

if ! iii_telemetry_enabled; then
return 0
fi

_os=$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
_arch=$(uname -m 2>/dev/null || echo "unknown")
_ts=$(date +%s 2>/dev/null || echo "0")
_ts_ms=$(( _ts * 1000 ))

_payload="{\"api_key\":\"${AMPLITUDE_API_KEY}\",\"events\":[{\"device_id\":\"${_telemetry_id}\",\"user_id\":\"${_telemetry_id}\",\"event_type\":\"${_event_type}\",\"event_properties\":{${_event_props}},\"platform\":\"install-script\",\"os_name\":\"${_os}\",\"app_version\":\"script\",\"time\":${_ts_ms},\"insert_id\":\"$(iii_gen_uuid)\",\"ip\":\"\$remote\"}]}"

curl -s -o /dev/null -X POST "$AMPLITUDE_ENDPOINT" \
-H "Content-Type: application/json" \
--data-raw "$_payload" &
"$telemetry_emitter" \
--install-only-generate-ids \
--install-event-type "$_event_type" \
--install-event-properties "$_event_props" \
>/dev/null 2>&1 || true
}

iii_detect_from_version() {
Expand All @@ -200,20 +53,6 @@ iii_detect_from_version() {
fi
}

iii_export_host_user_id() {
_huid=$(iii_read_toml_key "identity" "id")
if [ -z "$_huid" ]; then
return 0
fi
_export_line="export III_HOST_USER_ID=\"${_huid}\""
for _profile in "${HOME}/.bashrc" "${HOME}/.zshrc" "${HOME}/.profile"; do
if [ -f "$_profile" ] && ! grep -qF "III_HOST_USER_ID" "$_profile" 2>/dev/null; then
printf '\n# iii host correlation\n%s\n' "$_export_line" >> "$_profile"
break
fi
done
}

# --- Argument parsing ---
engine_version="${VERSION:-}"

Expand Down Expand Up @@ -264,9 +103,6 @@ if ! command -v curl >/dev/null 2>&1; then
err "dependency" "curl is required"
fi

install_id=$(iii_gen_uuid)
telemetry_id=$(iii_get_or_create_telemetry_id)

if [ -n "${TARGET:-}" ]; then
target="$TARGET"
else
Expand Down Expand Up @@ -335,6 +171,7 @@ if [ -n "$VERSION" ]; then
echo "installing version: $VERSION"
_ver="${VERSION#iii/}"
_ver="${_ver#v}"
release_version="$_ver"
_tag="iii/v${_ver}"
api_url="https://api.github.com/repos/$REPO/releases/tags/${_tag}"
json=$(github_api "$api_url" 2>/dev/null) || {
Expand Down Expand Up @@ -365,6 +202,18 @@ else
fi
fi

if [ -z "$release_version" ]; then
if command -v jq >/dev/null 2>&1; then
release_version=$(printf '%s' "$json" | jq -r '.tag_name' | sed -E 's#^(iii/)?v##')
else
release_version=$(printf '%s' "$json" \
| grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \
| head -n 1 \
| sed -E 's/.*"([^"]+)".*/\1/' \
| sed -E 's#^(iii/)?v##')
fi
fi

if command -v jq >/dev/null 2>&1; then
asset_url=$(printf '%s' "$json" \
| jq -r --arg bn "$BIN_NAME" --arg target "$target" \
Expand Down Expand Up @@ -402,14 +251,8 @@ fi
from_version=$(iii_detect_from_version "$bin_dir/$BIN_NAME")
if [ -n "$from_version" ]; then
install_event_prefix="upgrade"
iii_send_event "upgrade_started" \
"\"install_id\":\"${install_id}\",\"from_version\":\"${from_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\"" \
"$telemetry_id" "$install_id"
else
install_event_prefix="install"
iii_send_event "install_started" \
"\"install_id\":\"${install_id}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\",\"os\":\"$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo unknown)\",\"arch\":\"$(uname -m 2>/dev/null || echo unknown)\"" \
"$telemetry_id" "$install_id"
fi

mkdir -p "$bin_dir"
Expand Down Expand Up @@ -446,6 +289,15 @@ if [ -z "${bin_file:-}" ] || [ ! -f "$bin_file" ]; then
err "binary_lookup" "binary not found in downloaded asset"
fi

telemetry_emitter="$bin_file"
if [ "$install_event_prefix" = "upgrade" ]; then
iii_emit_event "upgrade_started" \
"{\"from_version\":\"${from_version}\",\"to_version\":\"${release_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\"}"
else
iii_emit_event "install_started" \
"{\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\",\"os\":\"$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo unknown)\",\"arch\":\"$(uname -m 2>/dev/null || echo unknown)\"}"
fi

installed_version=""
if command -v install >/dev/null 2>&1; then
install -m 755 "$bin_file" "$bin_dir/$BIN_NAME"
Expand All @@ -459,16 +311,16 @@ installed_version=$("$bin_dir/$BIN_NAME" --version 2>/dev/null | awk '{print $NF
printf 'installed %s to %s\n' "$BIN_NAME" "$bin_dir/$BIN_NAME"

if [ "$install_event_prefix" = "upgrade" ]; then
iii_send_event "upgrade_succeeded" \
"\"install_id\":\"${install_id}\",\"from_version\":\"${from_version}\",\"to_version\":\"${installed_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\"" \
"$telemetry_id" "$install_id"
iii_emit_event "upgrade_succeeded" \
"{\"from_version\":\"${from_version}\",\"to_version\":\"${installed_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\"}"
else
iii_send_event "install_succeeded" \
"\"install_id\":\"${install_id}\",\"installed_version\":\"${installed_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\"" \
"$telemetry_id" "$install_id"
iii_emit_event "install_succeeded" \
"{\"installed_version\":\"${installed_version}\",\"install_method\":\"sh\",\"target_binary\":\"${BIN_NAME}\"}"
fi

iii_export_host_user_id
# Best-effort: have the binary initialize its telemetry IDs.
# Older binaries won't have this flag — silently skip.
"$bin_dir/$BIN_NAME" --install-only-generate-ids >/dev/null 2>&1 || true

case ":$PATH:" in
*":$bin_dir:"*)
Expand Down
27 changes: 27 additions & 0 deletions engine/src/cli/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,25 @@ pub static REGISTRY: &[BinarySpec] = &[
}],
tag_prefix: None,
},
BinarySpec {
name: "iii-cloud",
repo: "iii-hq/iii-cloud-cli",
has_checksum: true,
supported_targets: &[
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-gnu",
],
commands: &[CommandMapping {
cli_command: "cloud",
binary_subcommand: None,
}],
tag_prefix: Some("cli"),
},
];

/// Resolve a CLI command name to its BinarySpec and optional binary subcommand.
Expand Down Expand Up @@ -176,6 +195,14 @@ mod tests {
assert!(sub.is_none());
}

#[test]
fn test_resolve_cloud() {
let (spec, sub) = resolve_command("cloud").unwrap();
assert_eq!(spec.name, "iii-cloud");
assert_eq!(spec.repo, "iii-hq/iii-cloud-cli");
assert!(sub.is_none());
}

#[test]
fn test_motia_no_checksum() {
let (spec, _) = resolve_command("motia").unwrap();
Expand Down
Loading