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
225- `401` on `/mcp`: verify your bearer token issuer matches `MCP_AUTHORIZATION_SERVER`.225- `401` on `/mcp`: verify your bearer token issuer matches `MCP_AUTHORIZATION_SERVER`.
226- `401` on `/mcp/anonymous`: pass `API_KEY` from the setup card as `Authorization: Bearer <API_KEY>` (or `x-api-key`).226- `401` on `/mcp/anonymous`: pass `API_KEY` from the setup card as `Authorization: Bearer <API_KEY>` (or `x-api-key`).
227- Web UI cannot load data: verify `CONVEX_URL` / `CONVEX_SITE_URL` and that Convex dev is running.227- 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
6INSTALL_DIR="${EXECUTOR_INSTALL_DIR:-$HOME/.executor/bin}"6INSTALL_DIR="${EXECUTOR_INSTALL_DIR:-$HOME/.executor/bin}"
7WEB_INSTALL_DIR="${EXECUTOR_WEB_INSTALL_DIR:-$HOME/.executor/runtime/web}"7WEB_INSTALL_DIR="${EXECUTOR_WEB_INSTALL_DIR:-$HOME/.executor/runtime/web}"
8RUNTIME_DIR="${EXECUTOR_RUNTIME_DIR:-$HOME/.executor/runtime}"8RUNTIME_DIR="${EXECUTOR_RUNTIME_DIR:-$HOME/.executor/runtime}"
9BOOTSTRAP_ARCHIVE_FILENAME="executor-bootstrap-project.tar.gz"
9REQUESTED_VERSION="${VERSION:-}"10REQUESTED_VERSION="${VERSION:-}"
10RESOLVED_VERSION=""11RESOLVED_VERSION=""
11BINARY_PATH=""12BINARY_PATH=""
@@ -15,6 +16,9 @@ SCRIPT_FILE="${BASH_SOURCE[0]:-$0}" · 3 unmodified lines
15SCRIPT_DIR="$(cd -- "$(dirname -- "${SCRIPT_FILE}")" >/dev/null 2>&1 && pwd)"16SCRIPT_DIR="$(cd -- "$(dirname -- "${SCRIPT_FILE}")" >/dev/null 2>&1 && pwd)"
16LOCAL_BINARY_CANDIDATE="$SCRIPT_DIR/dist/executor"17LOCAL_BINARY_CANDIDATE="$SCRIPT_DIR/dist/executor"
17LOCAL_WEB_CANDIDATE="$SCRIPT_DIR/dist/release"18LOCAL_WEB_CANDIDATE="$SCRIPT_DIR/dist/release"
19LOCAL_BOOTSTRAP_CANDIDATE="$LOCAL_WEB_CANDIDATE/$BOOTSTRAP_ARCHIVE_FILENAME"
20CHECKSUMS_FILE=""
21CHECKSUMS_FETCHED="0"
18 22
19RED='\033[0;31m'23RED='\033[0;31m'
20MUTED='\033[0;2m'24MUTED='\033[0;2m'
@@ -38,6 +42,7 @@ Environment: · 17 unmodified lines
38 EXECUTOR_REPO GitHub repository to install from (default: RhysSullivan/executor)42 EXECUTOR_REPO GitHub repository to install from (default: RhysSullivan/executor)
39 EXECUTOR_INSTALL_DIR Install directory (default: ~/.executor/bin)43 EXECUTOR_INSTALL_DIR Install directory (default: ~/.executor/bin)
40 EXECUTOR_WEB_INSTALL_DIR Web bundle directory (default: ~/.executor/runtime/web)44 EXECUTOR_WEB_INSTALL_DIR Web bundle directory (default: ~/.executor/runtime/web)
45 EXECUTOR_BOOTSTRAP_PROJECT_ARCHIVE Optional local bootstrap archive override
41EOF46EOF
42}47}
43 48
@@ -112,9 +117,97 @@ SERVICE_DIR="$RUNTIME_DIR/services" · 68 unmodified lines
112LOG_DIR="$RUNTIME_DIR/logs"117LOG_DIR="$RUNTIME_DIR/logs"
113BACKEND_PID_FILE="$SERVICE_DIR/backend.pid"118BACKEND_PID_FILE="$SERVICE_DIR/backend.pid"
114WEB_PID_FILE="$SERVICE_DIR/web.pid"119WEB_PID_FILE="$SERVICE_DIR/web.pid"
120WORK_DIR="$(mktemp -d)"
121trap 'rm -rf "$WORK_DIR"' EXIT
115mkdir -p "$INSTALL_DIR"122mkdir -p "$INSTALL_DIR"
116mkdir -p "$WEB_INSTALL_DIR"123mkdir -p "$WEB_INSTALL_DIR"
117 124
125require_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
137sha256_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
153ensure_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
165download_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
184verify_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
209ensure_install_prerequisites
210
118install_local_binary() {211install_local_binary() {
119 local src="$1"212 local src="$1"
120 local tmp_target213 local tmp_target
@@ -151,8 +244,7 @@ install_web_bundle_from_archive() { · 30 unmodified lines
151 244
152install_web_bundle() {245install_web_bundle() {
153 local web_url246 local web_url
154 local temp_archive247 local temp_archive="$WORK_DIR/$web_filename"
155 temp_archive="$(mktemp)"
156 248
157 if [[ -z "$REQUESTED_VERSION" ]]; then249 if [[ -z "$REQUESTED_VERSION" ]]; then
158 web_url="https://github.com/${REPO}/releases/latest/download/${web_filename}"250 web_url="https://github.com/${REPO}/releases/latest/download/${web_filename}"
@@ -161,12 +253,11 @@ install_web_bundle() { · 2 unmodified lines
161 fi253 fi
162 254
163 if curl -fsSL "$web_url" -o "$temp_archive"; then255 if curl -fsSL "$web_url" -o "$temp_archive"; then
256 verify_release_checksum_if_available "$temp_archive" "$web_filename"
164 echo -e "${MUTED}Installing web bundle${NC} ${web_filename}"257 echo -e "${MUTED}Installing web bundle${NC} ${web_filename}"
165 install_web_bundle_from_archive "$temp_archive"258 install_web_bundle_from_archive "$temp_archive"
166 rm -f "$temp_archive"
167 return259 return
168 fi260 fi
169 rm -f "$temp_archive"
170 261
171 local local_web_archive="$LOCAL_WEB_CANDIDATE/${web_filename}"262 local local_web_archive="$LOCAL_WEB_CANDIDATE/${web_filename}"
172 if [[ -f "$local_web_archive" ]]; then263 if [[ -f "$local_web_archive" ]]; then
@@ -200,8 +291,7 @@ else · 27 unmodified lines
200 url="https://github.com/${REPO}/releases/download/v${version}/${filename}"291 url="https://github.com/${REPO}/releases/download/v${version}/${filename}"
201 fi292 fi
202 293
203 tmp_dir="$(mktemp -d)"294 tmp_dir="$WORK_DIR"
204 trap 'rm -rf "$tmp_dir"' EXIT
205 295
206 echo -e "${MUTED}Installing ${APP}${NC} ${version:-latest}"296 echo -e "${MUTED}Installing ${APP}${NC} ${version:-latest}"
207 if ! curl -fsSL "$url" -o "$tmp_dir/$filename"; then297 if ! curl -fsSL "$url" -o "$tmp_dir/$filename"; then
@@ -217,6 +307,7 @@ else · 9 unmodified lines
217 exit 1307 exit 1
218 fi308 fi
219 else309 else
310 verify_release_checksum_if_available "$tmp_dir/$filename" "$filename"
220 tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"311 tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
221 312
222 if [[ ! -f "$tmp_dir/$APP" ]]; then313 if [[ ! -f "$tmp_dir/$APP" ]]; then
@@ -347,19 +438,24 @@ download_bootstrap_project_archive() { · 124 unmodified lines
347 return 1438 return 1
348 fi439 fi
349 440
350 local urls=()441 if [[ -z "$REQUESTED_VERSION" ]]; then
351 if [[ -n "$RESOLVED_VERSION" ]]; then442 local latest_url="https://github.com/${REPO}/releases/latest/download/${BOOTSTRAP_ARCHIVE_FILENAME}"
352 urls+=("https://github.com/${REPO}/archive/refs/tags/v${RESOLVED_VERSION}.tar.gz")443 if curl -fsSL "$latest_url" -o "$destination"; then
353 urls+=("https://github.com/${REPO}/archive/refs/tags/${RESOLVED_VERSION}.tar.gz")444 verify_release_checksum_if_available "$destination" "$BOOTSTRAP_ARCHIVE_FILENAME"
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
360 return 0445 return 0
361 fi446 fi
362 done447 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
363 459
364 return 1460 return 1
365}461}
@@ -367,7 +463,7 @@ download_bootstrap_project_archive() { · 1 unmodified line
367prepare_release_bootstrap_project() {463prepare_release_bootstrap_project() {
368 local bootstrap_root="$RUNTIME_DIR/bootstrap-project"464 local bootstrap_root="$RUNTIME_DIR/bootstrap-project"
369 local version_file="$bootstrap_root/.executor-bootstrap-version"465 local version_file="$bootstrap_root/.executor-bootstrap-version"
370 local cache_key="${RESOLVED_VERSION:-main}"466 local cache_key="${RESOLVED_VERSION:-latest}"
371 467
372 if [[ -f "$version_file" ]] && [[ "$(cat "$version_file" 2>/dev/null || true)" == "$cache_key" ]]; then468 if [[ -f "$version_file" ]] && [[ "$(cat "$version_file" 2>/dev/null || true)" == "$cache_key" ]]; then
373 if is_convex_project_dir "$bootstrap_root"; then469 if is_convex_project_dir "$bootstrap_root"; then
@@ -380,10 +476,8 @@ prepare_release_bootstrap_project() { · 6 unmodified lines
380 fi476 fi
381 fi477 fi
382 478
383 local archive_file479 local archive_file="$WORK_DIR/$BOOTSTRAP_ARCHIVE_FILENAME"
384 archive_file="$(mktemp)"
385 if ! download_bootstrap_project_archive "$archive_file"; then480 if ! download_bootstrap_project_archive "$archive_file"; then
386 rm -f "$archive_file"
387 printf ""481 printf ""
388 return482 return
389 fi483 fi
@@ -391,12 +485,10 @@ prepare_release_bootstrap_project() { · 1 unmodified line
391 rm -rf "$bootstrap_root"485 rm -rf "$bootstrap_root"
392 mkdir -p "$bootstrap_root"486 mkdir -p "$bootstrap_root"
393 if ! tar -xzf "$archive_file" -C "$bootstrap_root" --strip-components=1; then487 if ! tar -xzf "$archive_file" -C "$bootstrap_root" --strip-components=1; then
394 rm -f "$archive_file"
395 rm -rf "$bootstrap_root"488 rm -rf "$bootstrap_root"
396 printf ""489 printf ""
397 return490 return
398 fi491 fi
399 rm -f "$archive_file"
400 492
401 local project_dir="$bootstrap_root"493 local project_dir="$bootstrap_root"
402 if is_convex_project_dir "$bootstrap_root"; then494 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
168 const seeded = await runManagedConvexCli(168 const seeded = await runManagedConvexCli(
169 info,169 info,
170 projectDir,170 projectDir,
171 ["env", "set", entry.name, entry.value],171 ["env", "set", `${entry.name}=${entry.value}`],
172 envFilePath,172 envFilePath,
173 {173 {
174 stdout: "pipe",174 stdout: "pipe",
@@ -319,7 +319,7 @@ async function hasBootstrapDependencies(projectDir: string): Promise<boolean> { · 144 unmodified lines
319 319
320async function ensureProjectDependencies(info: ManagedRuntimeInfo, projectDir: string): Promise<void> {320async function ensureProjectDependencies(info: ManagedRuntimeInfo, projectDir: string): Promise<void> {
321 const bootstrapRelative = path.relative(path.join(info.rootDir, "bootstrap-project"), projectDir);321 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);
323 323
324 if (!isRuntimeBootstrapProject) {324 if (!isRuntimeBootstrapProject) {
325 return;325 return;
executor/scripts/release/build-release.ts+26−0
@@ -35,6 +35,10 @@ function webArchiveName(platform: ReleaseTarget["platform"], arch: ReleaseTarget · 34 unmodified lines
35 return `executor-web-${platform}-${arch}.tar.gz`;35 return `executor-web-${platform}-${arch}.tar.gz`;
36}36}
37 37
38function bootstrapArchiveName(): string {
39 return "executor-bootstrap-project.tar.gz";
40}
41
38async function sha256(filePath: string): Promise<string> {42async function sha256(filePath: string): Promise<string> {
39 const bytes = await Bun.file(filePath).arrayBuffer();43 const bytes = await Bun.file(filePath).arrayBuffer();
40 return createHash("sha256").update(Buffer.from(bytes)).digest("hex");44 return createHash("sha256").update(Buffer.from(bytes)).digest("hex");
@@ -142,6 +146,27 @@ async function buildWebArtifact(rootDir: string, releaseDir: string, checksums: · 101 unmodified lines
142 }146 }
143}147}
144 148
149async 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
145async function main(): Promise<void> {170async function main(): Promise<void> {
146 const rootDir = path.resolve(import.meta.dir, "..", "..");171 const rootDir = path.resolve(import.meta.dir, "..", "..");
147 const releaseDir = path.join(rootDir, "dist", "release");172 const releaseDir = path.join(rootDir, "dist", "release");
@@ -182,6 +207,7 @@ async function main(): Promise<void> { · 34 unmodified lines
182 }207 }
183 208
184 await buildWebArtifact(rootDir, releaseDir, checksums);209 await buildWebArtifact(rootDir, releaseDir, checksums);
210 await buildBootstrapProjectArtifact(rootDir, releaseDir, checksums);
185 211
186 await Bun.write(path.join(releaseDir, "checksums.txt"), `${checksums.join("\n")}\n`);212 await Bun.write(path.join(releaseDir, "checksums.txt"), `${checksums.join("\n")}\n`);
187 console.log(`wrote ${path.join("dist", "release", "checksums.txt")}`);213 console.log(`wrote ${path.join("dist", "release", "checksums.txt")}`);