History

stabilize managed install artifacts and anonymous auth bootstrap

Rhys Sullivan committed 8 months ago

Commit 52a27111 parent e34ac2e4 files · +144 −26Browse files

Unified · Split

4 files
  1. executor/README.md +1 −1
  2. executor/install +115 −23
  3. executor/packages/core/src/managed/runtime-bootstrap.ts +2 −2
  4. executor/scripts/release/build-release.ts +26 −0
executor/README.md+1−1
@@ -225,4 +225,4 @@ executor/ · 224 unmodified lines
225225 - `401` on `/mcp`: verify your bearer token issuer matches `MCP_AUTHORIZATION_SERVER`.
226226 - `401` on `/mcp/anonymous`: pass `API_KEY` from the setup card as `Authorization: Bearer <API_KEY>` (or `x-api-key`).
227227 - Web UI cannot load data: verify `CONVEX_URL` / `CONVEX_SITE_URL` and that Convex dev is running.
228−- Release build missing web archive files: run `bun run build:release` and verify `executor/dist/release/` contains all expected `executor-web-*.tar.gz` assets.
228+- Release build missing managed artifacts: run `bun run build:release` and verify `executor/dist/release/` contains `executor-*.tar.gz`, `executor-web-*.tar.gz`, and `executor-bootstrap-project.tar.gz`.
executor/install+115−23
@@ -6,6 +6,7 @@ REPO="${EXECUTOR_REPO:-RhysSullivan/executor}" · 5 unmodified lines
66 INSTALL_DIR="${EXECUTOR_INSTALL_DIR:-$HOME/.executor/bin}"
77 WEB_INSTALL_DIR="${EXECUTOR_WEB_INSTALL_DIR:-$HOME/.executor/runtime/web}"
88 RUNTIME_DIR="${EXECUTOR_RUNTIME_DIR:-$HOME/.executor/runtime}"
9+BOOTSTRAP_ARCHIVE_FILENAME="executor-bootstrap-project.tar.gz"
910 REQUESTED_VERSION="${VERSION:-}"
1011 RESOLVED_VERSION=""
1112 BINARY_PATH=""
@@ -15,6 +16,9 @@ SCRIPT_FILE="${BASH_SOURCE[0]:-$0}" · 3 unmodified lines
1516 SCRIPT_DIR="$(cd -- "$(dirname -- "${SCRIPT_FILE}")" >/dev/null 2>&1 && pwd)"
1617 LOCAL_BINARY_CANDIDATE="$SCRIPT_DIR/dist/executor"
1718 LOCAL_WEB_CANDIDATE="$SCRIPT_DIR/dist/release"
19+LOCAL_BOOTSTRAP_CANDIDATE="$LOCAL_WEB_CANDIDATE/$BOOTSTRAP_ARCHIVE_FILENAME"
20+CHECKSUMS_FILE=""
21+CHECKSUMS_FETCHED="0"
1822
1923 RED='\033[0;31m'
2024 MUTED='\033[0;2m'
@@ -38,6 +42,7 @@ Environment: · 17 unmodified lines
3842 EXECUTOR_REPO GitHub repository to install from (default: RhysSullivan/executor)
3943 EXECUTOR_INSTALL_DIR Install directory (default: ~/.executor/bin)
4044 EXECUTOR_WEB_INSTALL_DIR Web bundle directory (default: ~/.executor/runtime/web)
45+ EXECUTOR_BOOTSTRAP_PROJECT_ARCHIVE Optional local bootstrap archive override
4146 EOF
4247 }
4348
@@ -112,9 +117,97 @@ SERVICE_DIR="$RUNTIME_DIR/services" · 68 unmodified lines
112117 LOG_DIR="$RUNTIME_DIR/logs"
113118 BACKEND_PID_FILE="$SERVICE_DIR/backend.pid"
114119 WEB_PID_FILE="$SERVICE_DIR/web.pid"
120+WORK_DIR="$(mktemp -d)"
121+trap 'rm -rf "$WORK_DIR"' EXIT
115122 mkdir -p "$INSTALL_DIR"
116123 mkdir -p "$WEB_INSTALL_DIR"
117124
125+require_command() {
126+ local command_name="$1"
127+ local install_hint="$2"
128+ if command -v "$command_name" >/dev/null 2>&1; then
129+ return
130+ fi
131+
132+ echo -e "${RED}Missing required dependency: ${command_name}${NC}"
133+ echo "Install hint: ${install_hint}"
134+ exit 1
135+}
136+
137+sha256_of_file() {
138+ local target="$1"
139+ if command -v sha256sum >/dev/null 2>&1; then
140+ sha256sum "$target" | awk '{print $1}'
141+ return
142+ fi
143+
144+ if command -v shasum >/dev/null 2>&1; then
145+ shasum -a 256 "$target" | awk '{print $1}'
146+ return
147+ fi
148+
149+ echo -e "${RED}Missing checksum utility (sha256sum or shasum)${NC}"
150+ exit 1
151+}
152+
153+ensure_install_prerequisites() {
154+ require_command "curl" "Install curl from your OS package manager."
155+ require_command "tar" "Install tar from your OS package manager."
156+ if command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1; then
157+ return
158+ fi
159+
160+ echo -e "${RED}Missing required dependency: sha256sum or shasum${NC}"
161+ echo "Install hint: coreutils (sha256sum) or perl-Digest-SHA tools (shasum)."
162+ exit 1
163+}
164+
165+download_release_checksums() {
166+ if [[ "$CHECKSUMS_FETCHED" == "1" ]]; then
167+ return
168+ fi
169+
170+ CHECKSUMS_FETCHED="1"
171+ local checksums_url
172+ if [[ -z "$REQUESTED_VERSION" ]]; then
173+ checksums_url="https://github.com/${REPO}/releases/latest/download/checksums.txt"
174+ else
175+ checksums_url="https://github.com/${REPO}/releases/download/v${RESOLVED_VERSION}/checksums.txt"
176+ fi
177+
178+ local destination="$WORK_DIR/checksums.txt"
179+ if curl -fsSL "$checksums_url" -o "$destination"; then
180+ CHECKSUMS_FILE="$destination"
181+ fi
182+}
183+
184+verify_release_checksum_if_available() {
185+ local downloaded_file="$1"
186+ local artifact_name="$2"
187+ download_release_checksums
188+
189+ if [[ -z "$CHECKSUMS_FILE" || ! -f "$CHECKSUMS_FILE" ]]; then
190+ return
191+ fi
192+
193+ local expected
194+ expected="$(grep -E "[[:xdigit:]]+[[:space:]]+${artifact_name}$" "$CHECKSUMS_FILE" | awk '{print $1}' | head -n1)"
195+ if [[ -z "$expected" ]]; then
196+ return
197+ fi
198+
199+ local actual
200+ actual="$(sha256_of_file "$downloaded_file")"
201+ if [[ "$actual" != "$expected" ]]; then
202+ echo -e "${RED}Checksum mismatch for ${artifact_name}${NC}"
203+ echo " expected: ${expected}"
204+ echo " actual: ${actual}"
205+ exit 1
206+ fi
207+}
208+
209+ensure_install_prerequisites
210+
118211 install_local_binary() {
119212 local src="$1"
120213 local tmp_target
@@ -151,8 +244,7 @@ install_web_bundle_from_archive() { · 30 unmodified lines
151244
152245 install_web_bundle() {
153246 local web_url
154− local temp_archive
155− temp_archive="$(mktemp)"
247+ local temp_archive="$WORK_DIR/$web_filename"
156248
157249 if [[ -z "$REQUESTED_VERSION" ]]; then
158250 web_url="https://github.com/${REPO}/releases/latest/download/${web_filename}"
@@ -161,12 +253,11 @@ install_web_bundle() { · 2 unmodified lines
161253 fi
162254
163255 if curl -fsSL "$web_url" -o "$temp_archive"; then
256+ verify_release_checksum_if_available "$temp_archive" "$web_filename"
164257 echo -e "${MUTED}Installing web bundle${NC} ${web_filename}"
165258 install_web_bundle_from_archive "$temp_archive"
166− rm -f "$temp_archive"
167259 return
168260 fi
169− rm -f "$temp_archive"
170261
171262 local local_web_archive="$LOCAL_WEB_CANDIDATE/${web_filename}"
172263 if [[ -f "$local_web_archive" ]]; then
@@ -200,8 +291,7 @@ else · 27 unmodified lines
200291 url="https://github.com/${REPO}/releases/download/v${version}/${filename}"
201292 fi
202293
203− tmp_dir="$(mktemp -d)"
204− trap 'rm -rf "$tmp_dir"' EXIT
294+ tmp_dir="$WORK_DIR"
205295
206296 echo -e "${MUTED}Installing ${APP}${NC} ${version:-latest}"
207297 if ! curl -fsSL "$url" -o "$tmp_dir/$filename"; then
@@ -217,6 +307,7 @@ else · 9 unmodified lines
217307 exit 1
218308 fi
219309 else
310+ verify_release_checksum_if_available "$tmp_dir/$filename" "$filename"
220311 tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
221312
222313 if [[ ! -f "$tmp_dir/$APP" ]]; then
@@ -347,19 +438,24 @@ download_bootstrap_project_archive() { · 124 unmodified lines
347438 return 1
348439 fi
349440
350− local urls=()
351− if [[ -n "$RESOLVED_VERSION" ]]; then
352− urls+=("https://github.com/${REPO}/archive/refs/tags/v${RESOLVED_VERSION}.tar.gz")
353− urls+=("https://github.com/${REPO}/archive/refs/tags/${RESOLVED_VERSION}.tar.gz")
354− fi
355− urls+=("https://github.com/${REPO}/archive/refs/heads/main.tar.gz")
356−
357− local url
358− for url in "${urls[@]}"; do
359− if curl -fsSL "$url" -o "$destination"; then
441+ if [[ -z "$REQUESTED_VERSION" ]]; then
442+ local latest_url="https://github.com/${REPO}/releases/latest/download/${BOOTSTRAP_ARCHIVE_FILENAME}"
443+ if curl -fsSL "$latest_url" -o "$destination"; then
444+ verify_release_checksum_if_available "$destination" "$BOOTSTRAP_ARCHIVE_FILENAME"
360445 return 0
361446 fi
362− done
447+ else
448+ local versioned_url="https://github.com/${REPO}/releases/download/v${RESOLVED_VERSION}/${BOOTSTRAP_ARCHIVE_FILENAME}"
449+ if curl -fsSL "$versioned_url" -o "$destination"; then
450+ verify_release_checksum_if_available "$destination" "$BOOTSTRAP_ARCHIVE_FILENAME"
451+ return 0
452+ fi
453+ fi
454+
455+ if [[ -f "$LOCAL_BOOTSTRAP_CANDIDATE" ]]; then
456+ cp "$LOCAL_BOOTSTRAP_CANDIDATE" "$destination"
457+ return 0
458+ fi
363459
364460 return 1
365461 }
@@ -367,7 +463,7 @@ download_bootstrap_project_archive() { · 1 unmodified line
367463 prepare_release_bootstrap_project() {
368464 local bootstrap_root="$RUNTIME_DIR/bootstrap-project"
369465 local version_file="$bootstrap_root/.executor-bootstrap-version"
370− local cache_key="${RESOLVED_VERSION:-main}"
466+ local cache_key="${RESOLVED_VERSION:-latest}"
371467
372468 if [[ -f "$version_file" ]] && [[ "$(cat "$version_file" 2>/dev/null || true)" == "$cache_key" ]]; then
373469 if is_convex_project_dir "$bootstrap_root"; then
@@ -380,10 +476,8 @@ prepare_release_bootstrap_project() { · 6 unmodified lines
380476 fi
381477 fi
382478
383− local archive_file
384− archive_file="$(mktemp)"
479+ local archive_file="$WORK_DIR/$BOOTSTRAP_ARCHIVE_FILENAME"
385480 if ! download_bootstrap_project_archive "$archive_file"; then
386− rm -f "$archive_file"
387481 printf ""
388482 return
389483 fi
@@ -391,12 +485,10 @@ prepare_release_bootstrap_project() { · 1 unmodified line
391485 rm -rf "$bootstrap_root"
392486 mkdir -p "$bootstrap_root"
393487 if ! tar -xzf "$archive_file" -C "$bootstrap_root" --strip-components=1; then
394− rm -f "$archive_file"
395488 rm -rf "$bootstrap_root"
396489 printf ""
397490 return
398491 fi
399− rm -f "$archive_file"
400492
401493 local project_dir="$bootstrap_root"
402494 if is_convex_project_dir "$bootstrap_root"; then
executor/packages/core/src/managed/runtime-bootstrap.ts+2−2
@@ -168,7 +168,7 @@ async function seedManagedRuntimeEnvVars( · 167 unmodified lines
168168 const seeded = await runManagedConvexCli(
169169 info,
170170 projectDir,
171− ["env", "set", entry.name, entry.value],
171+ ["env", "set", `${entry.name}=${entry.value}`],
172172 envFilePath,
173173 {
174174 stdout: "pipe",
@@ -319,7 +319,7 @@ async function hasBootstrapDependencies(projectDir: string): Promise<boolean> { · 144 unmodified lines
319319
320320 async function ensureProjectDependencies(info: ManagedRuntimeInfo, projectDir: string): Promise<void> {
321321 const bootstrapRelative = path.relative(path.join(info.rootDir, "bootstrap-project"), projectDir);
322− const isRuntimeBootstrapProject = bootstrapRelative.length > 0 && !bootstrapRelative.startsWith("..") && !path.isAbsolute(bootstrapRelative);
322+ const isRuntimeBootstrapProject = !bootstrapRelative.startsWith("..") && !path.isAbsolute(bootstrapRelative);
323323
324324 if (!isRuntimeBootstrapProject) {
325325 return;
executor/scripts/release/build-release.ts+26−0
@@ -35,6 +35,10 @@ function webArchiveName(platform: ReleaseTarget["platform"], arch: ReleaseTarget · 34 unmodified lines
3535 return `executor-web-${platform}-${arch}.tar.gz`;
3636 }
3737
38+function bootstrapArchiveName(): string {
39+ return "executor-bootstrap-project.tar.gz";
40+}
41+
3842 async function sha256(filePath: string): Promise<string> {
3943 const bytes = await Bun.file(filePath).arrayBuffer();
4044 return createHash("sha256").update(Buffer.from(bytes)).digest("hex");
@@ -142,6 +146,27 @@ async function buildWebArtifact(rootDir: string, releaseDir: string, checksums: · 101 unmodified lines
142146 }
143147 }
144148
149+async function buildBootstrapProjectArtifact(rootDir: string, releaseDir: string, checksums: string[]): Promise<void> {
150+ const monorepoRoot = path.resolve(rootDir, "..");
151+ const archivePath = path.join(releaseDir, bootstrapArchiveName());
152+
153+ await runCommand(
154+ [
155+ "git",
156+ "archive",
157+ "--format=tar.gz",
158+ "--prefix=executor/",
159+ `--output=${archivePath}`,
160+ "HEAD:executor",
161+ ],
162+ { cwd: monorepoRoot },
163+ );
164+
165+ const digest = await sha256(archivePath);
166+ checksums.push(`${digest} ${path.basename(archivePath)}`);
167+ console.log(`built ${path.basename(archivePath)}`);
168+}
169+
145170 async function main(): Promise<void> {
146171 const rootDir = path.resolve(import.meta.dir, "..", "..");
147172 const releaseDir = path.join(rootDir, "dist", "release");
@@ -182,6 +207,7 @@ async function main(): Promise<void> { · 34 unmodified lines
182207 }
183208
184209 await buildWebArtifact(rootDir, releaseDir, checksums);
210+ await buildBootstrapProjectArtifact(rootDir, releaseDir, checksums);
185211
186212 await Bun.write(path.join(releaseDir, "checksums.txt"), `${checksums.join("\n")}\n`);
187213 console.log(`wrote ${path.join("dist", "release", "checksums.txt")}`);