mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
feat(qa): publish the Telegram Desktop variant AMI
Bakes the catalog-only Crabbox variant image from the same pinned Telegram Desktop the local Docker image uses, then proves selection: an --image-sdk lease gets the new AMI and a plain desktop lease keeps the generic default. --prep-only validates the recipe on one lease without creating an image or needing coordinator admin, which is how the AMI contract is testable by anyone. Three defects only a real AWS lease could show: - the version marker lived in Crabbox's state dir, which the lease user cannot traverse on AWS desktop images, so the recorder preflight could not read it - liveness matched the process name, missing the setsid fork-to-exec window on a cold 234MB binary - remote payloads ran through a login shell, whose ~/.bash_logout clear_console failure replaced the script's exit status
This commit is contained in:
@@ -241,10 +241,12 @@ bash scripts/mantis/build-telegram-desktop-image.sh
|
||||
|
||||
`--provider aws` targets a Crabbox catalog-only Telegram variant image
|
||||
(`--image-sdk telegram-desktop=7.0.9`) so the generic desktop image never
|
||||
carries the client. Publishing that variant needs Crabbox coordinator admin
|
||||
(`crabbox image create` / `image promote`) and is not part of this repository
|
||||
yet; until it is published, use the local Docker image above. Either image must
|
||||
provide an
|
||||
carries the client. `scripts/mantis/bake-telegram-desktop-image.sh` bakes and
|
||||
publishes it; `crabbox image create` and `image promote` are admin-only, so a
|
||||
Crabbox coordinator admin runs it with `--run`. Anyone can validate the recipe
|
||||
first with `--prep-only --run`, which warms one lease, installs the pinned
|
||||
client, runs the recorder's own contract check, and stops without creating an
|
||||
image. Either image must provide an
|
||||
executable Telegram Desktop at `/opt/Telegram/Telegram`, a readable desktop
|
||||
version marker, `wmctrl`, `xdotool`, `scrot`, `ffmpeg`, `zbarimg`, and
|
||||
`xdpyinfo`, plus a reachable `DISPLAY=:99`. Crabbox refuses the lease when no
|
||||
|
||||
@@ -93,7 +93,9 @@ export function renderGoldenImagePreflight(): string {
|
||||
contract="Telegram Desktop recorder golden image contract"
|
||||
fail() { echo "$contract failed: $1" >&2; exit 1; }
|
||||
test -x ${TELEGRAM_BINARY} || fail "${TELEGRAM_BINARY} is not executable"
|
||||
test "$(cat /var/lib/crabbox/telegram-desktop-version 2>/dev/null)" = "${TELEGRAM_DESKTOP_VERSION}" || fail "/var/lib/crabbox/telegram-desktop-version is not ${TELEGRAM_DESKTOP_VERSION}"
|
||||
# The marker sits beside the app, not in Crabbox's state dir: that directory is
|
||||
# not traversable by the lease user on AWS desktop images.
|
||||
test "$(cat /opt/Telegram/openclaw-image-version 2>/dev/null)" = "${TELEGRAM_DESKTOP_VERSION}" || fail "/opt/Telegram/openclaw-image-version is not ${TELEGRAM_DESKTOP_VERSION}"
|
||||
for command in wmctrl xdotool scrot ffmpeg zbarimg xdpyinfo; do
|
||||
command -v "$command" >/dev/null 2>&1 || fail "$command is not on PATH"
|
||||
done
|
||||
@@ -112,8 +114,11 @@ rm -rf ${shellQuote(TELEGRAM_WORKDIR)}
|
||||
# setsid plus closed stdin detaches Telegram from this SSH session: container sshd
|
||||
# tears down the session process group on exit, which kills a plain background child.
|
||||
setsid ${TELEGRAM_BINARY} -noupdate -workdir ${shellQuote(TELEGRAM_WORKDIR)} </dev/null >${shellQuote(remotePaths.desktopLog)} 2>&1 &
|
||||
# setsid execs in place, so this pid becomes Telegram; matching by name would miss
|
||||
# the window between fork and exec on a cold, large binary.
|
||||
telegram_pid=$!
|
||||
for _ in $(seq 1 30); do
|
||||
pgrep -x Telegram >/dev/null 2>&1 || { tail -c 262144 ${shellQuote(remotePaths.desktopLog)} >&2 || true; echo "Telegram Desktop exited before opening a window." >&2; exit 1; }
|
||||
kill -0 "$telegram_pid" 2>/dev/null || { tail -c 262144 ${shellQuote(remotePaths.desktopLog)} >&2 || true; echo "Telegram Desktop exited before opening a window." >&2; exit 1; }
|
||||
wmctrl -lx | awk 'tolower($0) ~ /telegramdesktop/ {found=1} END {exit !found}' && exit 0
|
||||
sleep 1
|
||||
done
|
||||
|
||||
+507
@@ -0,0 +1,507 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
telegram_version="7.0.9"
|
||||
telegram_url="https://github.com/telegramdesktop/tdesktop/releases/download/v7.0.9/tsetup.7.0.9.tar.xz"
|
||||
telegram_sha256="d3c05df0259ab116d11d8c1cdc1403019d2a3be303ad3b46d16a84e19df6615f"
|
||||
variant_sdk="telegram-desktop=${telegram_version}"
|
||||
|
||||
region="eu-west-1"
|
||||
server_class="standard"
|
||||
server_type=""
|
||||
image_name=""
|
||||
output_file=""
|
||||
crabbox_bin="crabbox"
|
||||
run=0
|
||||
promote=1
|
||||
keep_lease=0
|
||||
prep_only=0
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/mantis/bake-telegram-desktop-image.sh [flags]
|
||||
|
||||
Bake and optionally publish the catalog-only Telegram Desktop Crabbox image.
|
||||
By default this prints the plan and exits. Add --run to create paid AWS
|
||||
leases and an AMI.
|
||||
|
||||
Flags:
|
||||
--region REGION AWS region. Default: eu-west-1.
|
||||
--class CLASS Crabbox class. Default: standard.
|
||||
--type TYPE Optional AWS instance type.
|
||||
--name NAME Image name. Default: openclaw-telegram-desktop-<UTC timestamp>.
|
||||
--output FILE JSON summary path. Default: .artifacts/mantis/telegram-desktop-image/<name>.json.
|
||||
--run Create leases and the AMI.
|
||||
--no-promote Smoke the candidate without publishing it.
|
||||
--prep-only Prove the recipe on one lease (prep + smoke), then stop.
|
||||
Creates no image and needs no coordinator admin.
|
||||
--keep-lease Keep every lease created by this run.
|
||||
--crabbox-bin PATH Crabbox executable. Default: crabbox from PATH.
|
||||
-h, --help Show this help.
|
||||
|
||||
Required with --run:
|
||||
Crabbox coordinator admin: run from a crabbox login listed in the
|
||||
coordinator's CRABBOX_GITHUB_ADMIN_OWNERS, or set CRABBOX_COORDINATOR plus
|
||||
CRABBOX_COORDINATOR_ADMIN_TOKEN for headless use. Image create/promote are
|
||||
admin-only; the script probes for admin before paying for any lease.
|
||||
|
||||
Optional coordinator access:
|
||||
CRABBOX_ACCESS_CLIENT_ID
|
||||
CRABBOX_ACCESS_CLIENT_SECRET
|
||||
USAGE
|
||||
}
|
||||
|
||||
require_value() {
|
||||
local value="${2:-}"
|
||||
if [[ -z "$value" || "$value" == --* ]]; then
|
||||
printf '%s requires a value\n' "$1" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--region)
|
||||
require_value "$@"
|
||||
region="$2"
|
||||
shift 2
|
||||
;;
|
||||
--class)
|
||||
require_value "$@"
|
||||
server_class="$2"
|
||||
shift 2
|
||||
;;
|
||||
--type)
|
||||
require_value "$@"
|
||||
server_type="$2"
|
||||
shift 2
|
||||
;;
|
||||
--name)
|
||||
require_value "$@"
|
||||
image_name="$2"
|
||||
shift 2
|
||||
;;
|
||||
--output)
|
||||
require_value "$@"
|
||||
output_file="$2"
|
||||
shift 2
|
||||
;;
|
||||
--run)
|
||||
run=1
|
||||
shift
|
||||
;;
|
||||
--prep-only)
|
||||
prep_only=1
|
||||
shift
|
||||
;;
|
||||
--no-promote)
|
||||
promote=0
|
||||
shift
|
||||
;;
|
||||
--keep-lease)
|
||||
keep_lease=1
|
||||
shift
|
||||
;;
|
||||
--crabbox-bin)
|
||||
require_value "$@"
|
||||
crabbox_bin="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf 'unknown argument: %s\n' "$1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$image_name" ]]; then
|
||||
image_name="openclaw-telegram-desktop-$(date -u +%Y%m%d-%H%M)"
|
||||
fi
|
||||
if [[ -z "$output_file" ]]; then
|
||||
output_file=".artifacts/mantis/telegram-desktop-image/${image_name}.json"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
Telegram Desktop image bake
|
||||
image: $image_name
|
||||
region: $region
|
||||
class: $server_class
|
||||
type: ${server_type:-auto}
|
||||
version: $telegram_version
|
||||
promote: $promote
|
||||
prep only: $prep_only
|
||||
keep leases: $keep_lease
|
||||
output: $output_file
|
||||
paid run: $run
|
||||
EOF
|
||||
|
||||
if [[ "$run" != "1" ]]; then
|
||||
printf 'dry plan only; add --run to create leases and an AMI.\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! crabbox_resolved="$(command -v "$crabbox_bin")"; then
|
||||
printf 'Crabbox executable not found: %s\n' "$crabbox_bin" >&2
|
||||
exit 2
|
||||
fi
|
||||
# Admin probe before any paid lease. image create/promote are admin-only, so a
|
||||
# prep-only run — which creates no image — skips it. The
|
||||
# probe asks the coordinator about a non-existent AMI: only an admin login gets
|
||||
# through to the coordinator, which answers "not found"; a non-admin login is
|
||||
# refused by the CLI before any request. Anything else (outage, auth, contract
|
||||
# drift) fails closed rather than paying for leases first.
|
||||
if [[ "$prep_only" == "1" ]]; then
|
||||
admin_probe="not found"
|
||||
else
|
||||
admin_probe="$("$crabbox_resolved" image fsr-status ami-00000000000000000 --provider aws 2>&1 || true)"
|
||||
fi
|
||||
if [[ "$admin_probe" == *"not found"* ]]; then
|
||||
:
|
||||
elif [[ "$admin_probe" == *"admin command requires"* ]]; then
|
||||
printf 'Crabbox coordinator admin is required with --run (CRABBOX_GITHUB_ADMIN_OWNERS or CRABBOX_COORDINATOR_ADMIN_TOKEN).\n' >&2
|
||||
exit 2
|
||||
else
|
||||
printf 'Could not verify Crabbox coordinator admin access; refusing to start paid leases. Probe output:\n%s\n' "$admin_probe" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
printf 'jq is required with --run.\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
artifact_dir="$(dirname "$output_file")"
|
||||
log_dir="$artifact_dir/logs"
|
||||
mkdir -p "$log_dir"
|
||||
prep_script="$(mktemp "${TMPDIR:-/tmp}/openclaw-telegram-image-prep.XXXXXX.sh")"
|
||||
source_lease=""
|
||||
candidate_lease=""
|
||||
variant_lease=""
|
||||
default_lease=""
|
||||
|
||||
cleanup() {
|
||||
local status=$?
|
||||
trap - EXIT
|
||||
rm -f "$prep_script"
|
||||
if [[ "$keep_lease" != "1" ]]; then
|
||||
local lease
|
||||
for lease in "$default_lease" "$variant_lease" "$candidate_lease" "$source_lease"; do
|
||||
[[ -n "$lease" ]] || continue
|
||||
"$crabbox_resolved" stop --provider aws --target linux "$lease" || true
|
||||
done
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cat >"$prep_script" <<PREP
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
telegram_version="$telegram_version"
|
||||
telegram_url="$telegram_url"
|
||||
telegram_sha256="$telegram_sha256"
|
||||
|
||||
if [[ "\$(id -u)" -ne 0 ]]; then
|
||||
if ! command -v sudo >/dev/null 2>&1; then
|
||||
printf 'sudo is required to prepare the Telegram Desktop image.\\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
exec sudo -H env DEBIAN_FRONTEND=noninteractive bash "\$0"
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
retry() {
|
||||
local attempt=1
|
||||
until "\$@"; do
|
||||
if [[ "\$attempt" -ge 5 ]]; then
|
||||
return 1
|
||||
fi
|
||||
sleep "\$((attempt * 3))"
|
||||
attempt="\$((attempt + 1))"
|
||||
done
|
||||
}
|
||||
|
||||
if [[ "\$(dpkg --print-architecture)" != "amd64" ]]; then
|
||||
printf 'Telegram Desktop image requires amd64.\\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
retry apt-get update
|
||||
retry apt-get install -y --no-install-recommends \
|
||||
x11-utils \
|
||||
zbar-tools \
|
||||
libopengl0 \
|
||||
libxcb-cursor0 \
|
||||
libxcb-icccm4 \
|
||||
libxcb-image0 \
|
||||
libxcb-keysyms1 \
|
||||
libxcb-randr0 \
|
||||
libxcb-render-util0 \
|
||||
libxcb-shape0 \
|
||||
libxcb-xfixes0 \
|
||||
libxcb-xinerama0 \
|
||||
libxkbcommon-x11-0
|
||||
|
||||
marker=/opt/Telegram/openclaw-image-version
|
||||
if [[ -x /opt/Telegram/Telegram && -f "\$marker" && "\$(<"\$marker")" == "\$telegram_version" ]]; then
|
||||
rm -f /opt/Telegram/Updater
|
||||
test -f /var/lib/crabbox/image-ready
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp_dir="\$(mktemp -d /tmp/telegram-desktop-install.XXXXXX)"
|
||||
trap 'rm -rf "\$tmp_dir"' EXIT
|
||||
archive="\$tmp_dir/telegram.tar.xz"
|
||||
retry curl -fL --retry 5 --retry-all-errors -o "\$archive" "\$telegram_url"
|
||||
printf '%s %s\\n' "\$telegram_sha256" "\$archive" | sha256sum --check --status
|
||||
tar -xJf "\$archive" -C "\$tmp_dir"
|
||||
test -x "\$tmp_dir/Telegram/Telegram"
|
||||
|
||||
rm -rf /opt/Telegram
|
||||
install -d -o root -g root -m 0755 /opt/Telegram
|
||||
cp -a "\$tmp_dir/Telegram/." /opt/Telegram/
|
||||
chown -R root:root /opt/Telegram
|
||||
chmod 0755 /opt/Telegram /opt/Telegram/Telegram
|
||||
rm -f /opt/Telegram/Updater
|
||||
printf '%s\\n' "\$telegram_version" >"\$marker"
|
||||
chmod 0644 "\$marker"
|
||||
test -f /var/lib/crabbox/image-ready
|
||||
PREP
|
||||
chmod 0755 "$prep_script"
|
||||
|
||||
run_cmd() {
|
||||
printf '+'
|
||||
printf ' %q' "$@"
|
||||
printf '\n'
|
||||
"$@"
|
||||
}
|
||||
|
||||
warmup() {
|
||||
local label="$1"
|
||||
local image_override="${2:-}"
|
||||
local image_selector="${3:-}"
|
||||
local log="$log_dir/${label}.log"
|
||||
local -a args=(
|
||||
warmup
|
||||
--provider aws
|
||||
--target linux
|
||||
--desktop
|
||||
--class "$server_class"
|
||||
--market on-demand
|
||||
--ttl 1h
|
||||
--idle-timeout 20m
|
||||
--timing-json
|
||||
)
|
||||
if [[ -n "$server_type" ]]; then
|
||||
args+=(--type "$server_type")
|
||||
fi
|
||||
if [[ -n "$image_selector" ]]; then
|
||||
args+=(--image-sdk "$image_selector")
|
||||
fi
|
||||
|
||||
printf 'warming %s lease; log=%s\n' "$label" "$log" >&2
|
||||
local status=0
|
||||
if [[ -n "$image_override" ]]; then
|
||||
run_cmd env CRABBOX_AWS_REGION="$region" CRABBOX_AWS_AMI="$image_override" \
|
||||
"$crabbox_resolved" "${args[@]}" 2>&1 | tee "$log" >&2 || status=$?
|
||||
else
|
||||
run_cmd env CRABBOX_AWS_REGION="$region" \
|
||||
"$crabbox_resolved" "${args[@]}" 2>&1 | tee "$log" >&2 || status=$?
|
||||
fi
|
||||
local lease
|
||||
lease="$(grep -oE 'cbx_[A-Za-z0-9_-]+' "$log" | tail -n 1 || true)"
|
||||
if [[ "$status" -ne 0 ]]; then
|
||||
if [[ -n "$lease" && "$keep_lease" != "1" ]]; then
|
||||
"$crabbox_resolved" stop --provider aws --target linux "$lease" || true
|
||||
fi
|
||||
return "$status"
|
||||
fi
|
||||
if [[ -z "$lease" ]]; then
|
||||
printf 'warmup did not return a cbx lease id; log=%s\n' "$log" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$lease"
|
||||
}
|
||||
|
||||
selected_image_id() {
|
||||
local log="$1"
|
||||
sed -nE 's/.*image selected id=([^[:space:]]+) source=[^[:space:]]+.*/\1/p' "$log" | tail -n 1
|
||||
}
|
||||
|
||||
assert_selected_image() {
|
||||
local log="$1"
|
||||
local image_id="$2"
|
||||
local source="$3"
|
||||
if ! grep -Fq "image selected id=${image_id} source=${source}" "$log"; then
|
||||
printf 'warmup did not prove image selected id=%s source=%s; log=%s\n' \
|
||||
"$image_id" "$source" "$log" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
smoke() {
|
||||
local lease="$1"
|
||||
local smoke_script
|
||||
read -r -d '' smoke_script <<'SMOKE' || true
|
||||
set -euo pipefail
|
||||
test -x /opt/Telegram/Telegram
|
||||
test ! -e /opt/Telegram/Updater
|
||||
test "$(</opt/Telegram/openclaw-image-version)" = "7.0.9"
|
||||
for tool in wmctrl xdotool scrot ffmpeg zbarimg xdpyinfo; do
|
||||
command -v "$tool" >/dev/null
|
||||
done
|
||||
DISPLAY=:99 xdpyinfo >/dev/null
|
||||
rm -rf /tmp/tg-smoke
|
||||
# Same launch contract the recorder uses: setsid with closed stdin survives the
|
||||
# SSH session, and process-name matching never matches this script's own shell.
|
||||
pkill -x Telegram >/dev/null 2>&1 || true
|
||||
DISPLAY=:99 setsid /opt/Telegram/Telegram -noupdate -workdir /tmp/tg-smoke </dev/null >/tmp/tg-smoke.log 2>&1 &
|
||||
# setsid execs in place, so this pid becomes Telegram; name matching would miss
|
||||
# the window between fork and exec on a cold, large binary.
|
||||
telegram_pid=$!
|
||||
for _ in $(seq 1 30); do
|
||||
kill -0 "$telegram_pid" 2>/dev/null || { tail -c 4096 /tmp/tg-smoke.log >&2 || true; echo "Telegram Desktop exited before opening a window." >&2; exit 1; }
|
||||
DISPLAY=:99 wmctrl -lx | awk 'tolower($0) ~ /telegramdesktop/ {found=1} END {exit !found}' && { pkill -x Telegram >/dev/null 2>&1 || true; exit 0; }
|
||||
sleep 1
|
||||
done
|
||||
tail -c 4096 /tmp/tg-smoke.log >&2 || true
|
||||
echo "Telegram Desktop window did not open." >&2
|
||||
exit 1
|
||||
SMOKE
|
||||
# --script, not --shell: a login shell runs ~/.bash_logout, whose clear_console
|
||||
# fails without a console and replaces our exit status with 1.
|
||||
local smoke_file
|
||||
smoke_file="$(mktemp "${TMPDIR:-/tmp}/openclaw-telegram-image-smoke.XXXXXX.sh")"
|
||||
printf '%s\n' "$smoke_script" >"$smoke_file"
|
||||
chmod 0755 "$smoke_file"
|
||||
run_cmd "$crabbox_resolved" run --provider aws --target linux --id "$lease" \
|
||||
--no-sync --script "$smoke_file"
|
||||
rm -f "$smoke_file"
|
||||
}
|
||||
|
||||
stop_lease() {
|
||||
local lease="$1"
|
||||
if [[ "$keep_lease" != "1" ]]; then
|
||||
run_cmd "$crabbox_resolved" stop --provider aws --target linux "$lease"
|
||||
fi
|
||||
}
|
||||
|
||||
source_lease="$(warmup source)"
|
||||
run_cmd "$crabbox_resolved" run --provider aws --target linux --id "$source_lease" \
|
||||
--no-sync --script "$prep_script"
|
||||
smoke "$source_lease"
|
||||
|
||||
if [[ "$prep_only" == "1" ]]; then
|
||||
printf 'prep-only run passed: the recipe boots Telegram Desktop %s on a real lease.\n' "$telegram_version"
|
||||
jq -n \
|
||||
--arg name "$image_name" \
|
||||
--arg region "$region" \
|
||||
--arg version "$telegram_version" \
|
||||
--arg lease "$source_lease" \
|
||||
'{name: $name, region: $region, telegramDesktopVersion: $version, lease: $lease, mode: "prep-only", proofs: {prep: "passed", smoke: "passed", imageCreated: false, promoted: false}}' \
|
||||
| tee "$output_file"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
image_json="$log_dir/image-create.json"
|
||||
env CRABBOX_AWS_REGION="$region" "$crabbox_resolved" image create \
|
||||
--id "$source_lease" --name "$image_name" --wait --json | tee "$image_json"
|
||||
ami_id="$(jq -er '.id | select(type == "string" and startswith("ami-"))' "$image_json")"
|
||||
|
||||
if [[ "$keep_lease" != "1" ]]; then
|
||||
stop_lease "$source_lease"
|
||||
source_lease=""
|
||||
fi
|
||||
|
||||
candidate_lease="$(warmup candidate "$ami_id")"
|
||||
assert_selected_image "$log_dir/candidate.log" "$ami_id" explicit
|
||||
smoke "$candidate_lease"
|
||||
if [[ "$keep_lease" != "1" ]]; then
|
||||
stop_lease "$candidate_lease"
|
||||
candidate_lease=""
|
||||
fi
|
||||
|
||||
promotion_status="skipped"
|
||||
variant_selection="skipped"
|
||||
variant_smoke="skipped"
|
||||
default_unchanged="skipped"
|
||||
if [[ "$promote" == "1" ]]; then
|
||||
promote_json="$log_dir/image-promote.json"
|
||||
env CRABBOX_AWS_REGION="$region" "$crabbox_resolved" image promote "$ami_id" \
|
||||
--provider aws --target linux --region "$region" --desktop \
|
||||
--catalog-only --variant-sdk "$variant_sdk" --json | tee "$promote_json"
|
||||
jq -e --arg ami "$ami_id" '.id == $ami and .catalogOnly == true' "$promote_json" >/dev/null
|
||||
promotion_status="catalog-only"
|
||||
|
||||
variant_lease="$(warmup variant "" "$variant_sdk")"
|
||||
assert_selected_image "$log_dir/variant.log" "$ami_id" promoted
|
||||
variant_selection="promoted"
|
||||
smoke "$variant_lease"
|
||||
variant_smoke="passed"
|
||||
if [[ "$keep_lease" != "1" ]]; then
|
||||
stop_lease "$variant_lease"
|
||||
variant_lease=""
|
||||
fi
|
||||
|
||||
default_lease="$(warmup default)"
|
||||
default_image="$(selected_image_id "$log_dir/default.log")"
|
||||
if [[ -z "$default_image" ]]; then
|
||||
printf 'default warmup did not report its selected image; log=%s\n' "$log_dir/default.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$default_image" == "$ami_id" ]]; then
|
||||
printf 'generic desktop selection changed to catalog-only AMI %s.\n' "$ami_id" >&2
|
||||
exit 1
|
||||
fi
|
||||
default_unchanged="passed:${default_image}"
|
||||
if [[ "$keep_lease" != "1" ]]; then
|
||||
stop_lease "$default_lease"
|
||||
default_lease=""
|
||||
fi
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg amiId "$ami_id" \
|
||||
--arg name "$image_name" \
|
||||
--arg region "$region" \
|
||||
--arg version "$telegram_version" \
|
||||
--arg promotion "$promotion_status" \
|
||||
--arg variantSelection "$variant_selection" \
|
||||
--arg variantSmoke "$variant_smoke" \
|
||||
--arg defaultUnchanged "$default_unchanged" \
|
||||
'{
|
||||
amiId: $amiId,
|
||||
name: $name,
|
||||
region: $region,
|
||||
telegramDesktopVersion: $version,
|
||||
proofs: {
|
||||
sourceSmoke: "passed",
|
||||
candidateSelection: "explicit",
|
||||
candidateSmoke: "passed",
|
||||
promotion: $promotion,
|
||||
variantSelection: $variantSelection,
|
||||
variantSmoke: $variantSmoke,
|
||||
defaultUnchanged: $defaultUnchanged
|
||||
}
|
||||
}' >"$output_file"
|
||||
|
||||
cat <<EOF
|
||||
Telegram Desktop image bake complete
|
||||
AMI: $ami_id
|
||||
name: $image_name
|
||||
region: $region
|
||||
source smoke: passed
|
||||
candidate selection: explicit
|
||||
candidate smoke: passed
|
||||
promotion: $promotion_status
|
||||
variant selection: $variant_selection
|
||||
variant smoke: $variant_smoke
|
||||
default unchanged: $default_unchanged
|
||||
summary: $output_file
|
||||
EOF
|
||||
@@ -65,13 +65,13 @@ RUN set -eux; \
|
||||
printf '%s %s\n' "$telegram_desktop_sha256" "$install_root/telegram.tar.xz" | sha256sum --check --status; \
|
||||
tar -xJf "$install_root/telegram.tar.xz" -C "$install_root"; \
|
||||
test -x "$install_root/Telegram/Telegram"; \
|
||||
install -d -o root -g root -m 0755 /opt/Telegram /var/lib/crabbox; \
|
||||
install -d -o root -g root -m 0755 /opt/Telegram; \
|
||||
cp -a "$install_root/Telegram/." /opt/Telegram/; \
|
||||
chown -R root:root /opt/Telegram; \
|
||||
chmod 0755 /opt/Telegram /opt/Telegram/Telegram; \
|
||||
rm -f /opt/Telegram/Updater; \
|
||||
printf '%s\n' "$telegram_desktop_version" >/var/lib/crabbox/telegram-desktop-version; \
|
||||
chmod 0644 /var/lib/crabbox/telegram-desktop-version; \
|
||||
printf '%s\n' "$telegram_desktop_version" >/opt/Telegram/openclaw-image-version; \
|
||||
chmod 0644 /opt/Telegram/openclaw-image-version; \
|
||||
rm -rf "$install_root" /var/lib/apt/lists/*
|
||||
|
||||
# Fail the build if the base ever drops the account this image defaults to.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const SCRIPT = "scripts/mantis/bake-telegram-desktop-image.sh";
|
||||
|
||||
describe("Mantis Telegram Desktop image bake", () => {
|
||||
it("pins Telegram and proves explicit, promoted, and unchanged-default selection", () => {
|
||||
const script = readFileSync(SCRIPT, "utf8");
|
||||
|
||||
expect(script).toContain('telegram_version="7.0.9"');
|
||||
expect(script).toContain(
|
||||
'telegram_url="https://github.com/telegramdesktop/tdesktop/releases/download/v7.0.9/tsetup.7.0.9.tar.xz"',
|
||||
);
|
||||
expect(script).toContain(
|
||||
'telegram_sha256="d3c05df0259ab116d11d8c1cdc1403019d2a3be303ad3b46d16a84e19df6615f"',
|
||||
);
|
||||
expect(script).toContain('--catalog-only --variant-sdk "$variant_sdk"');
|
||||
expect(script).toContain('args+=(--image-sdk "$image_selector")');
|
||||
expect(script).toContain('assert_selected_image "$log_dir/variant.log" "$ami_id" promoted');
|
||||
expect(script).toContain('if [[ "$default_image" == "$ami_id" ]]');
|
||||
expect(script).toContain("Crabbox coordinator admin is required with --run");
|
||||
expect(script).toContain(
|
||||
"Could not verify Crabbox coordinator admin access; refusing to start paid leases",
|
||||
);
|
||||
expect(script).not.toContain("ADMIN_TOKEN is required");
|
||||
});
|
||||
|
||||
it("proves the image contract the way the recorder asserts it", () => {
|
||||
const script = readFileSync(SCRIPT, "utf8");
|
||||
|
||||
// The marker lives with the app: Crabbox's state dir is not traversable by
|
||||
// the lease user on AWS desktop images.
|
||||
expect(script).toContain("marker=/opt/Telegram/openclaw-image-version");
|
||||
expect(script).not.toContain("/var/lib/crabbox/telegram-desktop-version");
|
||||
// setsid execs in place, so the launcher tracks its pid instead of matching
|
||||
// by process name, which misses the fork-to-exec window on a cold binary.
|
||||
expect(script).toContain("setsid /opt/Telegram/Telegram");
|
||||
expect(script).toContain('kill -0 "$telegram_pid"');
|
||||
// A login shell runs ~/.bash_logout, whose clear_console failure replaces the
|
||||
// script's exit status, so remote payloads are uploaded, never inlined.
|
||||
expect(script).toContain("--no-sync --script");
|
||||
expect(script).not.toContain("--no-sync --shell");
|
||||
});
|
||||
|
||||
it("runs prep-only without an image, a promotion, or coordinator admin", () => {
|
||||
const script = readFileSync(SCRIPT, "utf8");
|
||||
|
||||
expect(script).toContain("--prep-only");
|
||||
expect(script).toContain('if [[ "$prep_only" == "1" ]]; then\n admin_probe="not found"');
|
||||
expect(script).toContain("prep-only run passed");
|
||||
expect(script).toContain("imageCreated: false");
|
||||
});
|
||||
|
||||
it("parses as Bash and performs no Crabbox work without --run", () => {
|
||||
const syntax = spawnSync("bash", ["-n", SCRIPT], { encoding: "utf8" });
|
||||
expect(syntax.stderr).toBe("");
|
||||
expect(syntax.status).toBe(0);
|
||||
|
||||
const tempRoot = mkdtempSync(path.join(tmpdir(), "telegram-image-dry-run-"));
|
||||
try {
|
||||
const crabbox = path.join(tempRoot, "crabbox");
|
||||
const marker = path.join(tempRoot, "called");
|
||||
writeFileSync(crabbox, `#!/usr/bin/env bash\nprintf called >${JSON.stringify(marker)}\n`);
|
||||
chmodSync(crabbox, 0o755);
|
||||
const dryRun = spawnSync("bash", [SCRIPT, "--crabbox-bin", crabbox], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
expect(dryRun.status).toBe(0);
|
||||
expect(dryRun.stdout).toContain("dry plan only");
|
||||
expect(existsSync(marker)).toBe(false);
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -323,7 +323,7 @@ describe("Telegram Desktop recorder remote contract", () => {
|
||||
expect(scripts).toContain("Telegram Desktop recorder golden image contract");
|
||||
expect(scripts).toContain("/opt/Telegram/Telegram");
|
||||
expect(scripts).toContain(
|
||||
'test "$(cat /var/lib/crabbox/telegram-desktop-version 2>/dev/null)" = "7.0.9"',
|
||||
'test "$(cat /opt/Telegram/openclaw-image-version 2>/dev/null)" = "7.0.9"',
|
||||
);
|
||||
expect(scripts).toContain("DISPLAY=:99 xdpyinfo");
|
||||
expect(scripts).toContain("wmctrl xdotool scrot ffmpeg zbarimg xdpyinfo");
|
||||
@@ -331,7 +331,9 @@ describe("Telegram Desktop recorder remote contract", () => {
|
||||
// -f patterns also match this script's own shell (its command line contains the
|
||||
// binary path), so a -f pkill kills the launcher instead of a stale Telegram.
|
||||
expect(scripts).toContain("pkill -x Telegram");
|
||||
expect(scripts).toContain("pgrep -x Telegram");
|
||||
// setsid execs in place, so liveness follows the pid; name matching would miss
|
||||
// the fork-to-exec window on a cold binary.
|
||||
expect(scripts).toContain('kill -0 "$telegram_pid"');
|
||||
expect(scripts).not.toMatch(/p(kill|grep) -f [^\n]*Telegram/u);
|
||||
// Container sshd tears down the session process group; the client must detach.
|
||||
expect(scripts).toContain("setsid /opt/Telegram/Telegram");
|
||||
|
||||
Reference in New Issue
Block a user