From 2dc6ed055d4db6aeecb73755a9ed70faee4d89ce Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 26 May 2026 15:01:56 +0200 Subject: [PATCH] Add refresh-external-dependencies.sh script (#2082) --- scripts/refresh-external-dependencies.sh | 224 +++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100755 scripts/refresh-external-dependencies.sh diff --git a/scripts/refresh-external-dependencies.sh b/scripts/refresh-external-dependencies.sh new file mode 100755 index 000000000..1a172c11d --- /dev/null +++ b/scripts/refresh-external-dependencies.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +# +# Refresh local copies of external dependencies that we cache in the repo +# (allowlists, public key bundles, etc.) so the mobile/browser/server apps +# do not need to fetch them at runtime. +# +# Run as part of the manual release pipeline. +# +# Usage: +# ./refresh-external-dependencies.sh # run every task +# ./refresh-external-dependencies.sh ... # run only the named task(s) +# ./refresh-external-dependencies.sh --list # show available tasks +# +# Adding a new task: +# 1. Define a function named `task_` that fetches the upstream payload +# and writes the result into the relevant source file. Use the +# `replace_generated_block` helper to swap content between +# `// BEGIN_GENERATED: ` and `// END_GENERATED: ` markers (any +# comment syntax works — the helper matches both line- and block-style). +# 2. Register the task in TASK_ORDER + TASK_DESCRIPTIONS below. + +set -euo pipefail + +if [ -z "${BASH_VERSION:-}" ]; then + echo "Error: this script must be run with bash" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +TODAY="$(date +%Y-%m-%d)" + +# Colors +BLUE='\033[0;34m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +RESET='\033[0m' + +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "$WORK_DIR"' EXIT + +# ---------------------------------------------------------------------- +# Helpers shared by all tasks +# ---------------------------------------------------------------------- + +# Replace everything between `BEGIN_GENERATED: ` and `END_GENERATED: ` +# markers in $1 with the contents of the file at $3. The marker lines +# themselves are preserved; only the lines between them change. +# +# Arguments: +# $1 target file +# $2 block id (must match the marker) +# $3 path to file whose contents become the new block body +replace_generated_block() { + local target_file="$1" + local block_id="$2" + local payload_file="$3" + + python3 - "$target_file" "$block_id" "$payload_file" <<'PY' +import re +import sys + +target, block_id, payload_path = sys.argv[1:4] + +with open(target, "r") as f: + src = f.read() +with open(payload_path, "r") as f: + payload = f.read().rstrip("\n") + +pattern = re.compile( + r"(BEGIN_GENERATED:\s*" + re.escape(block_id) + r".*?\n)" + r"(.*?)" + r"(^[ \t]*\S+\s*END_GENERATED:\s*" + re.escape(block_id) + r")", + re.DOTALL | re.MULTILINE, +) + +match = pattern.search(src) +if not match: + sys.stderr.write( + f"ERROR: BEGIN_GENERATED/END_GENERATED markers for '{block_id}' " + f"not found in {target}\n" + ) + sys.exit(1) + +new_src = src[:match.start()] + match.group(1) + payload + "\n" + match.group(3) + src[match.end():] + +with open(target, "w") as f: + f.write(new_src) +PY +} + +# Download a URL into a file, failing loudly on HTTP errors. +download() { + local url="$1" + local out="$2" + if ! curl --fail --silent --show-error --location "$url" -o "$out"; then + echo -e " ${RED}✗${RESET} download failed: $url" >&2 + return 1 + fi +} + +# Validate that a file contains parseable JSON. +ensure_valid_json() { + local file="$1" + if ! python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$file" 2>/dev/null; then + echo -e " ${RED}✗${RESET} downloaded content is not valid JSON" >&2 + return 1 + fi +} + +# ---------------------------------------------------------------------- +# Tasks — one function per external dependency +# ---------------------------------------------------------------------- + +task_passkeys_allowlist() { + local url="https://www.gstatic.com/gpm-passkeys-privileged-apps/apps.json" + local target="$REPO_ROOT/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/credentialprovider/OriginVerifier.kt" + local block_id="passkeys-allowlist" + + echo -e " ${BLUE}↓${RESET} fetching $url" + local raw="$WORK_DIR/passkeys-allowlist.json" + download "$url" "$raw" + ensure_valid_json "$raw" + + # Build the replacement block. The JSON lines are embedded inside a + # Kotlin triple-quoted string with `.trimIndent()`, so we prefix every + # line with 8 spaces of indent — trimIndent strips that back out at + # runtime, leaving the upstream bytes intact. + local block="$WORK_DIR/passkeys-allowlist.block" + { + echo " // Source: $url" + echo " // Last refreshed: $TODAY" + echo ' private val PRIVILEGED_ALLOWLIST_JSON = """' + awk '{ if (length($0) > 0) print " " $0; else print "" }' "$raw" + echo ' """.trimIndent()' + } > "$block" + + replace_generated_block "$target" "$block_id" "$block" + echo -e " ${GREEN}✓${RESET} updated ${target#$REPO_ROOT/}" +} + +# ---------------------------------------------------------------------- +# Registry +# ---------------------------------------------------------------------- + +# Each entry is "task-name:human description". The order here is the order +# tasks run when no specific task argument is given. +TASKS=( + "passkeys-allowlist:Android privileged-apps allowlist for WebAuthn (gstatic.com/gpm-passkeys-privileged-apps/apps.json)" +) + +# ---------------------------------------------------------------------- +# Dispatch +# ---------------------------------------------------------------------- + +task_description() { + local needle="$1" + local entry + for entry in "${TASKS[@]}"; do + if [ "${entry%%:*}" = "$needle" ]; then + echo "${entry#*:}" + return 0 + fi + done + return 1 +} + +list_tasks() { + echo "Available tasks:" + local entry name desc + for entry in "${TASKS[@]}"; do + name="${entry%%:*}" + desc="${entry#*:}" + printf " %-24s %s\n" "$name" "$desc" + done +} + +run_task() { + local name="$1" + local fn="task_${name//-/_}" + if ! declare -F "$fn" > /dev/null; then + echo -e "${RED}Unknown task: $name${RESET}" >&2 + echo >&2 + list_tasks >&2 + return 1 + fi + echo -e "${YELLOW}→${RESET} $name" + "$fn" +} + +usage() { + cat <