|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 3 | +# SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +# Reproduce the Release Canary Snap lifecycle: install the OpenShell Snap, |
| 6 | +# connect its interfaces after the daemon is started, then immediately use the |
| 7 | +# local gateway. Run this as root inside an Ubuntu guest prepared with --with snapd. |
| 8 | + |
| 9 | +set -uo pipefail |
| 10 | + |
| 11 | +usage() { |
| 12 | + cat <<'EOF' |
| 13 | +Usage: snap-gateway-repro.sh SNAP_FILE [ATTEMPTS] [READY_TIMEOUT_SECONDS] |
| 14 | +
|
| 15 | +Install SNAP_FILE repeatedly using the Release Canary interface ordering. |
| 16 | +ATTEMPTS defaults to 1. READY_TIMEOUT_SECONDS defaults to 0, preserving the |
| 17 | +canary's immediate readiness check. Set it to a positive value to wait for |
| 18 | +automatic gateway recovery after the immediate check fails. Every failed |
| 19 | +attempt prints service, connection, snap-change, journal, gateway-log, and |
| 20 | +listener diagnostics. |
| 21 | +EOF |
| 22 | +} |
| 23 | + |
| 24 | +if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then |
| 25 | + usage >&2 |
| 26 | + exit 2 |
| 27 | +fi |
| 28 | + |
| 29 | +snap_file=$1 |
| 30 | +attempts=${2:-1} |
| 31 | +ready_timeout=${3:-0} |
| 32 | +if [ ! -f "${snap_file}" ]; then |
| 33 | + echo "Snap file does not exist: ${snap_file}" >&2 |
| 34 | + exit 2 |
| 35 | +fi |
| 36 | +if [[ ! ${attempts} =~ ^[1-9][0-9]*$ ]]; then |
| 37 | + echo "ATTEMPTS must be a positive integer: ${attempts}" >&2 |
| 38 | + exit 2 |
| 39 | +fi |
| 40 | +if [[ ! ${ready_timeout} =~ ^[0-9]+$ ]]; then |
| 41 | + echo "READY_TIMEOUT_SECONDS must be a non-negative integer: ${ready_timeout}" >&2 |
| 42 | + exit 2 |
| 43 | +fi |
| 44 | + |
| 45 | +diagnostics() { |
| 46 | + local attempt=$1 |
| 47 | + echo "========== Snap diagnostics (attempt ${attempt}) ==========" >&2 |
| 48 | + snap services openshell >&2 || true |
| 49 | + snap connections openshell >&2 || true |
| 50 | + snap changes >&2 || true |
| 51 | + systemctl status snap.openshell.gateway.service --no-pager >&2 || true |
| 52 | + journalctl -b -u snap.openshell.gateway.service --no-pager -n 300 >&2 || true |
| 53 | + journalctl -b -u snapd.service --no-pager -n 300 >&2 || true |
| 54 | + snap logs openshell.gateway -n=300 >&2 || true |
| 55 | + ss -ltnp '( sport = :17670 )' >&2 || true |
| 56 | +} |
| 57 | + |
| 58 | +gateway_is_ready() { |
| 59 | + runuser -u openshell -- /snap/bin/openshell status >/dev/null 2>&1 |
| 60 | +} |
| 61 | + |
| 62 | +wait_for_gateway() { |
| 63 | + local deadline=$((SECONDS + ready_timeout)) |
| 64 | + while [ "${SECONDS}" -lt "${deadline}" ]; do |
| 65 | + if gateway_is_ready; then |
| 66 | + return 0 |
| 67 | + fi |
| 68 | + sleep 1 |
| 69 | + done |
| 70 | + gateway_is_ready |
| 71 | +} |
| 72 | + |
| 73 | +if ! snap list docker >/dev/null 2>&1; then |
| 74 | + echo "==> Installing Docker Snap" |
| 75 | + snap install docker |
| 76 | +fi |
| 77 | + |
| 78 | +failures=0 |
| 79 | +for attempt in $(seq 1 "${attempts}"); do |
| 80 | + echo "==> Snap gateway reproduction attempt ${attempt}/${attempts}" |
| 81 | + snap remove --purge openshell >/dev/null 2>&1 || true |
| 82 | + rm -rf /home/openshell/snap/openshell |
| 83 | + |
| 84 | + if ! snap install "${snap_file}" --dangerous || |
| 85 | + ! snap connect openshell:docker docker:docker-daemon || |
| 86 | + ! snap connect openshell:log-observe || |
| 87 | + ! snap connect openshell:system-observe; then |
| 88 | + echo "OpenShell installation or interface connection failed" >&2 |
| 89 | + diagnostics "${attempt}" |
| 90 | + failures=$((failures + 1)) |
| 91 | + continue |
| 92 | + fi |
| 93 | + |
| 94 | + # This deliberately does not wait for the listener. It mirrors the canary |
| 95 | + # and exposes a daemon that fails or races after late interface connections. |
| 96 | + if ! runuser -u openshell -- /snap/bin/openshell gateway add \ |
| 97 | + http://127.0.0.1:17670 --local --name snap-docker || |
| 98 | + ! runuser -u openshell -- /snap/bin/openshell gateway select snap-docker || |
| 99 | + ! gateway_is_ready; then |
| 100 | + if [ "${ready_timeout}" -gt 0 ] && wait_for_gateway; then |
| 101 | + echo "Gateway recovered automatically within ${ready_timeout}s" |
| 102 | + continue |
| 103 | + fi |
| 104 | + echo "Gateway was not usable immediately after interface connection" >&2 |
| 105 | + diagnostics "${attempt}" |
| 106 | + failures=$((failures + 1)) |
| 107 | + fi |
| 108 | +done |
| 109 | + |
| 110 | +if [ "${failures}" -gt 0 ]; then |
| 111 | + echo "${failures}/${attempts} attempt(s) failed" >&2 |
| 112 | + exit 1 |
| 113 | +fi |
| 114 | + |
| 115 | +echo "All ${attempts} attempt(s) passed" |
0 commit comments