#!/bin/sh set -eu DOG_RESET="" DOG_BOLD="" DOG_CYAN="" DOG_MAGENTA="" DOG_AMBER="" DOG_GREEN="" DOG_RED="" DOG_GRAY="" if [ -t 1 ]; then DOG_RESET="$(printf '\033[0m')" DOG_BOLD="$(printf '\033[1m')" if [ "${COLORTERM:-}" = "truecolor" ] || [ "${COLORTERM:-}" = "24bit" ]; then DOG_CYAN="$(printf '\033[38;2;0;255;255m')" DOG_MAGENTA="$(printf '\033[38;2;255;0;255m')" DOG_AMBER="$(printf '\033[38;2;255;176;0m')" DOG_GREEN="$(printf '\033[38;2;51;255;51m')" DOG_RED="$(printf '\033[38;2;255;51;51m')" DOG_GRAY="$(printf '\033[38;2;106;106;122m')" else DOG_CYAN="$(printf '\033[0;96m')" DOG_MAGENTA="$(printf '\033[0;95m')" DOG_AMBER="$(printf '\033[0;33m')" DOG_GREEN="$(printf '\033[0;32m')" DOG_RED="$(printf '\033[0;31m')" DOG_GRAY="$(printf '\033[0;90m')" fi fi header() { printf "%s%sDOG_ EDGE INSTALLER%s\n" "$DOG_CYAN" "$DOG_BOLD" "$DOG_RESET" printf "%sNative Installer%s\n" "$DOG_BOLD" "$DOG_RESET" printf "%sLocal vision. Cloud-linked.%s\n" "$DOG_MAGENTA" "$DOG_RESET" } step() { printf "%s==>%s %s\n" "$DOG_AMBER" "$DOG_RESET" "$*" } note() { printf "%s•%s %s\n" "$DOG_CYAN" "$DOG_RESET" "$*" } subtle() { printf "%s%s%s\n" "$DOG_GRAY" "$*" "$DOG_RESET" } lore_quote() { case $(( $(date +%s) % 6 )) in 0) printf "The feed continues without you." ;; 1) printf "Every frame is evidence." ;; 2) printf "The eye does not blink." ;; 3) printf "The watch continues." ;; 4) printf "The calm is only the space between events." ;; 5) printf "Archive first. Forget later." ;; esac } flair() { subtle "\"$(lore_quote)\"" } ok() { printf "%s✓%s %s\n" "$DOG_GREEN" "$DOG_RESET" "$*" } die() { printf "%sERROR:%s %s\n" "$DOG_RED" "$DOG_RESET" "$*" >&2 exit 1 } sha256_file() { target="$1" if command -v sha256sum >/dev/null 2>&1; then sha256sum "$target" | awk '{print $1}' return fi if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$target" | awk '{print $1}' return fi die "Missing sha256 tool (sha256sum or shasum)." } release_public_key_pem() { cat <<'DOG_RELEASE_PUBLIC_KEY_EOF' -----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEApkngeFoPfdFGZH5Iw2XR WtyGi9Dk+f99facfp/iPvUHPj2bICHpDYYXc06M2QikUuiyRA2wkRkLnY5Lvseub BqceEnYpSAvSfAL1tXRC+Ne022B6rNYUOjwpy1sHDh3nYy7QWtv7XCyNxtOy6Qhi dzb9zHaZkLIaV1l5VUThvKk3N+NNKjaiJM9ca6u1rYChg1MJDMG/vWacP4OuAzmE 9fQFANkNkleTENrB+urBzcuOMLbOFahmDvzm6b94OJ4N3KZojwmbYyBO7YZDXFk0 Lze2stHVGnIGAavB8b/iJNBL5f53gwBVKFX+Pp00Htlal1dzkOHYl8m6wrVamUJE UrLPlH/bKkeW6AGirR8dIZZyOivUbhcbCrJdT9x/f/K4tGbr8Vl/xXuN8QX1n8CP 86DIo9GCpQ4ZXzEHc7P8DzKaJjw3kvGn0MjLQZUnH6nltFcycIrstKHlAGlR7+BO 612NN0+yjrgL2jvRxQXMshFe7c9Wn4zs5H5GSEAnLJ4pAgMBAAE= -----END PUBLIC KEY----- DOG_RELEASE_PUBLIC_KEY_EOF } require_openssl() { command -v openssl >/dev/null 2>&1 || die "Missing openssl. DOG_ Edge release manifests require OpenSSL signature verification." } verify_release_manifest() { manifest="$1" signature="$2" require_openssl pubkey="$(mktemp)" release_public_key_pem > "$pubkey" if ! openssl dgst -sha256 -verify "$pubkey" -signature "$signature" "$manifest" >/dev/null 2>&1; then rm -f "$pubkey" die "Release manifest signature verification failed." fi rm -f "$pubkey" } manifest_get() { manifest="$1" key="$2" awk -F= -v wanted="$key" ' $1 == wanted { print substr($0, length($1) + 2) found = 1 } END { exit found ? 0 : 1 } ' "$manifest" } validate_release_manifest() { manifest="$1" expected_channel="$2" awk -F= -v expected_channel="$expected_channel" ' function fail(message) { print message > "/dev/stderr" failed = 1 exit 1 } function require_key(key) { if (!(key in seen)) { fail("Release manifest missing required key: " key) } } function require_sha(key) { if (length(value[key]) != 64 || value[key] !~ /^[0-9A-Fa-f]+$/) { fail("Release manifest has invalid SHA256 for " key) } } function require_asset(key, expected) { if (value[key] != expected) { fail("Release manifest asset mismatch for " key ": expected " expected ", got " value[key]) } } BEGIN { required["format"] = 1 required["channel"] = 1 required["version"] = 1 required["linux_amd64_asset"] = 1 required["linux_amd64_sha256"] = 1 required["linux_arm64_asset"] = 1 required["linux_arm64_sha256"] = 1 required["darwin_arm64_asset"] = 1 required["darwin_arm64_sha256"] = 1 optional["asset_base_url"] = 1 } { if ($0 == "") { fail("Release manifest contains a blank line") } if ($0 !~ /^[a-z0-9_]+=[A-Za-z0-9._:\/?=&%+-]+$/) { fail("Release manifest contains invalid syntax: " $0) } key = $1 val = substr($0, length(key) + 2) if (!(key in required) && !(key in optional)) { fail("Release manifest contains unknown key: " key) } if (key in seen) { fail("Release manifest contains duplicate key: " key) } seen[key] = 1 value[key] = val } END { if (failed) { exit 1 } require_key("format") require_key("channel") require_key("version") require_key("linux_amd64_asset") require_key("linux_amd64_sha256") require_key("linux_arm64_asset") require_key("linux_arm64_sha256") require_key("darwin_arm64_asset") require_key("darwin_arm64_sha256") if (value["format"] != "dog-edge-release-manifest-v1") { fail("Release manifest format is unsupported: " value["format"]) } if (value["channel"] != expected_channel) { fail("Release manifest channel mismatch: expected " expected_channel ", got " value["channel"]) } if (value["version"] !~ /^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$/) { fail("Release manifest version is invalid: " value["version"]) } if (("asset_base_url" in seen) && value["asset_base_url"] !~ /^https:\/\/[A-Za-z0-9._:\/?=&%+-]+$/) { fail("Release manifest asset_base_url must be https") } version = value["version"] require_asset("linux_amd64_asset", "dog-edge-linux-amd64-" version ".tar.gz") require_asset("linux_arm64_asset", "dog-edge-linux-arm64-" version ".tar.gz") require_asset("darwin_arm64_asset", "dog-edge-darwin-arm64-" version ".tar.gz") require_sha("linux_amd64_sha256") require_sha("linux_arm64_sha256") require_sha("darwin_arm64_sha256") } ' "$manifest" || die "Release manifest validation failed." } resolve_release_asset() { manifest="$1" VERSION="$(manifest_get "$manifest" version)" ASSET_BASE_URL="$(manifest_get "$manifest" asset_base_url 2>/dev/null || printf "%s" "$RELEASE_BASE_URL")" case "$PLATFORM_KEY" in LINUX_AMD64) ASSET="$(manifest_get "$manifest" linux_amd64_asset)" EXPECTED_SHA="$(manifest_get "$manifest" linux_amd64_sha256)" ;; LINUX_ARM64) ASSET="$(manifest_get "$manifest" linux_arm64_asset)" EXPECTED_SHA="$(manifest_get "$manifest" linux_arm64_sha256)" ;; DARWIN_ARM64) ASSET="$(manifest_get "$manifest" darwin_arm64_asset)" EXPECTED_SHA="$(manifest_get "$manifest" darwin_arm64_sha256)" ;; *) die "Unsupported platform: ${PLATFORM_KEY}" ;; esac } tar_supports_option() { archive="$1" option="$2" tar --help 2>&1 | grep -q -- "$option" } extract_verified_bundle() { archive="$1" destination="$2" set -- -xzf "$archive" -C "$destination" for option in --no-same-owner --no-same-permissions --no-overwrite-dir; do if tar_supports_option "$archive" "$option"; then set -- "$@" "$option" fi done tar "$@" } locate_bundle_root() { destination="$1" matches="$(find "$destination" -mindepth 1 -maxdepth 1 -type d -name 'dog-edge-*')" count="$(printf "%s\n" "$matches" | awk 'NF { count++ } END { print count + 0 }')" [ "$count" = "1" ] || die "Expected exactly one extracted DOG_ Edge bundle, found ${count}." printf "%s\n" "$matches" } spinner_frame() { case $(( $1 % 10 )) in 0) printf "⠋" ;; 1) printf "⠙" ;; 2) printf "⠹" ;; 3) printf "⠸" ;; 4) printf "⠼" ;; 5) printf "⠴" ;; 6) printf "⠦" ;; 7) printf "⠧" ;; 8) printf "⠇" ;; 9) printf "⠏" ;; esac } format_bytes() { b="${1:-0}" if [ "$b" -lt 1024 ]; then printf "%d B" "$b" elif [ "$b" -lt 1048576 ]; then printf "%d KiB" "$((b / 1024))" elif [ "$b" -lt 1073741824 ]; then printf "%d.%d MiB" "$((b / 1048576))" "$(((b % 1048576) * 10 / 1048576))" else printf "%d.%d GiB" "$((b / 1073741824))" "$(((b % 1073741824) * 10 / 1073741824))" fi } # Downloads $1 to $2 with a Braille spinner + byte counter. Falls back to a # silent curl when stdout is not a TTY (logs, CI, piped installs). download_with_feedback() { dl_url="$1" dl_dest="$2" if [ ! -t 1 ]; then curl -fsSL "$dl_url" -o "$dl_dest" return fi dl_total_raw="$(curl -fsSLI "$dl_url" 2>/dev/null \ | awk 'BEGIN{IGNORECASE=1} /^content-length:/ {gsub("\r",""); v=$2} END{print v+0}')" case "$dl_total_raw" in ''|*[!0-9]*) dl_total=0 ;; *) dl_total="$dl_total_raw" ;; esac curl -fsSL "$dl_url" -o "$dl_dest" & dl_pid=$! dl_spin=0 dl_start="$(date +%s)" while kill -0 "$dl_pid" 2>/dev/null; do dl_bytes=0 if [ -f "$dl_dest" ]; then dl_bytes_raw="$(wc -c < "$dl_dest" 2>/dev/null | tr -d ' ')" case "$dl_bytes_raw" in ''|*[!0-9]*) dl_bytes=0 ;; *) dl_bytes="$dl_bytes_raw" ;; esac fi dl_elapsed=$(( $(date +%s) - dl_start )) dl_detail="$(format_bytes "$dl_bytes")" if [ "$dl_total" -gt 0 ]; then dl_pct=$((dl_bytes * 100 / dl_total)) dl_detail="${dl_detail} / $(format_bytes "$dl_total") (${dl_pct}%)" fi printf "\r\033[K%s%s%s %s %s· %ss elapsed%s" \ "$DOG_CYAN" "$(spinner_frame "$dl_spin")" "$DOG_RESET" \ "$dl_detail" "$DOG_GRAY" "$dl_elapsed" "$DOG_RESET" dl_spin=$((dl_spin + 1)) sleep 1 done set +e wait "$dl_pid" dl_rc=$? set -e printf "\r\033[K" return "$dl_rc" } detect_platform() { os="$(uname -s | tr '[:upper:]' '[:lower:]')" arch="$(uname -m)" case "$os/$arch" in linux/x86_64) PLATFORM_KEY="LINUX_AMD64" ;; linux/aarch64|linux/arm64) PLATFORM_KEY="LINUX_ARM64" ;; darwin/arm64) PLATFORM_KEY="DARWIN_ARM64" ;; *) die "Unsupported platform: ${os}/${arch}" ;; esac } MODE="install" CHANNEL="${DOG_EDGE_CHANNEL:-stable}" RELEASE_BASE_URL="${DOG_EDGE_RELEASE_BASE_URL:-https://install.dognet.cam/releases/latest/download}" BOOTSTRAP_URL="${DOG_EDGE_BOOTSTRAP_URL:-https://install.dognet.cam/edge}" INSTALL_ROOT="" CONFIG_DIR="" DATA_DIR="" LOG_DIR="" WEBUI_PORT="${DOG_EDGE_WEBUI_PORT:-8088}" CLOUD_ENDPOINT="${DOG_EDGE_CLOUD_ENDPOINT:-wss://dognet.cam/ws/edge}" EXPECTED_VERSION="" while [ "$#" -gt 0 ]; do case "$1" in --update) MODE="update" shift ;; --channel) CHANNEL="$2" shift 2 ;; --release-base-url) RELEASE_BASE_URL="$2" shift 2 ;; --bootstrap-url) BOOTSTRAP_URL="$2" shift 2 ;; --install-root) INSTALL_ROOT="$2" shift 2 ;; --config-dir) CONFIG_DIR="$2" shift 2 ;; --data-dir) DATA_DIR="$2" shift 2 ;; --log-dir) LOG_DIR="$2" shift 2 ;; --webui-port) WEBUI_PORT="$2" shift 2 ;; --cloud-endpoint) CLOUD_ENDPOINT="$2" shift 2 ;; --expected-version) EXPECTED_VERSION="$2" shift 2 ;; *) die "Unknown argument: $1" ;; esac done detect_platform tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT INT TERM manifest_path="${tmpdir}/stable.manifest" manifest_sig_path="${tmpdir}/stable.manifest.sig" step "Fetching signed release manifest" curl -fsSL "${RELEASE_BASE_URL}/${CHANNEL}.manifest" -o "$manifest_path" curl -fsSL "${RELEASE_BASE_URL}/${CHANNEL}.manifest.sig" -o "$manifest_sig_path" verify_release_manifest "$manifest_path" "$manifest_sig_path" validate_release_manifest "$manifest_path" "$CHANNEL" resolve_release_asset "$manifest_path" if [ -n "${EXPECTED_VERSION}" ] && [ "${VERSION}" != "${EXPECTED_VERSION}" ]; then die "Release manifest changed during update: expected ${EXPECTED_VERSION}, got ${VERSION}." fi # Tarball mirror. Manifest may redirect the bundle download to a public # CDN-backed host (GitHub Releases, Cloudflare, etc.) while keeping the # manifest itself on the origin. Signature + SHA256 verification below make # this safe. header flair step "Preview" note "Mode: ${MODE}" note "Channel: ${CHANNEL}" note "Release: ${VERSION}" note "Platform: ${PLATFORM_KEY}" note "Model: CERBERUS J-micro H264" note "Web UI: :${WEBUI_PORT}" if [ "$ASSET_BASE_URL" != "$RELEASE_BASE_URL" ]; then note "Mirror: ${ASSET_BASE_URL}" fi subtle "This installer will:" note "download a verified DOG_ Edge bundle" note "install ffmpeg with your system package manager if this machine does not have it yet" note "install the runtime and managed model support" note "register the system service" note "start the Web UI on :${WEBUI_PORT}" note "keep the device ready for pairing" bundle_path="${tmpdir}/${ASSET}" step "Downloading ${ASSET}" if ! download_with_feedback "${ASSET_BASE_URL}/${ASSET}" "$bundle_path"; then if [ "$ASSET_BASE_URL" != "$RELEASE_BASE_URL" ]; then note "Mirror download failed. Retrying from origin." rm -f "$bundle_path" download_with_feedback "${RELEASE_BASE_URL}/${ASSET}" "$bundle_path" || die "Failed to download ${ASSET} from mirror and origin." else die "Failed to download ${ASSET}." fi fi step "Verifying checksum" ACTUAL_SHA="$(sha256_file "$bundle_path")" [ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || die "Checksum mismatch for ${ASSET}." ok "Checksum verified" step "Extracting bundle" extract_verified_bundle "$bundle_path" "$tmpdir" bundle_root="$(locate_bundle_root "$tmpdir")" set -- \ --mode "$MODE" \ --release-base-url "$RELEASE_BASE_URL" \ --bootstrap-url "$BOOTSTRAP_URL" \ --channel "$CHANNEL" \ --webui-port "$WEBUI_PORT" \ --cloud-endpoint "$CLOUD_ENDPOINT" if [ -n "$INSTALL_ROOT" ]; then set -- "$@" --install-root "$INSTALL_ROOT" fi if [ -n "$CONFIG_DIR" ]; then set -- "$@" --config-dir "$CONFIG_DIR" fi if [ -n "$DATA_DIR" ]; then set -- "$@" --data-dir "$DATA_DIR" fi if [ -n "$LOG_DIR" ]; then set -- "$@" --log-dir "$LOG_DIR" fi exec "${bundle_root}/scripts/install-local.sh" "$@"