Skip to content

Commit 302ce58

Browse files
authored
harden UI builder artifact bootstrap with verified pinned metadata (#9189)
* feat: harden UI builder artifact bootstrap with verified pinned metadata * fix: emit tab-indented artifact json to match prettier config * refactor: rewrite artifact json with node instead of python * refactor: simplify bootstrap to flat script, drop test scaffolding
1 parent 4e25954 commit 302ce58

3 files changed

Lines changed: 59 additions & 49 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"baseUrl": "https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev",
3+
"version": "6715153",
4+
"sha256": "1485930ea5f5309e4bdc09a55aae72eae8230eb74f0928715a0e6fe610703d9b"
5+
}
Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,50 @@
1-
import path from 'path'
2-
import fs from 'fs'
1+
import { createHash } from 'node:crypto'
2+
import fs from 'node:fs'
3+
import path from 'node:path'
34

5+
import artifact from './ui_builder_artifact.json' with { type: 'json' }
46

5-
// Check if we're in node_modules (installed as dependency)
7+
// Skip when installed as a dependency or outside the root project
68
if (process.cwd().includes('node_modules')) {
7-
console.log('Skipping postinstall - running as dependency');
8-
process.exit(0);
9+
console.log('Skipping postinstall - running as dependency')
10+
process.exit(0)
911
}
10-
11-
// Check if we're in the root project
1212
if (process.env.INIT_CWD && process.env.INIT_CWD !== process.cwd()) {
13-
console.log('Skipping postinstall - not root project');
14-
process.exit(0);
13+
console.log('Skipping postinstall - not root project')
14+
process.exit(0)
1515
}
1616

17-
// Your actual postinstall logic here
18-
console.log('Running postinstall for root project');
17+
console.log('Running postinstall for root project')
1918

19+
const tarUrl = `${artifact.baseUrl}/ui_builder-${artifact.version}.tar.gz`
20+
const response = await fetch(tarUrl)
21+
if (!response.ok) {
22+
throw new Error(`Failed to download ${tarUrl}: ${response.status} ${response.statusText}`)
23+
}
2024

21-
import { x } from 'tar'
25+
const buffer = Buffer.from(await response.arrayBuffer())
26+
const sha256 = createHash('sha256').update(buffer).digest('hex')
27+
if (sha256 !== artifact.sha256) {
28+
throw new Error(
29+
`UI builder artifact checksum mismatch: expected ${artifact.sha256}, got ${sha256}`
30+
)
31+
}
2232

23-
const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-6715153.tar.gz'
2433
const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz')
2534
const extractTo = path.join(process.cwd(), 'static/ui_builder/')
2635

27-
import { fileURLToPath } from 'url'
28-
import { dirname } from 'path'
29-
30-
const __filename = fileURLToPath(import.meta.url)
31-
const __dirname = dirname(__filename)
36+
await fs.promises.mkdir(extractTo, { recursive: true })
37+
await fs.promises.writeFile(outputTarPath, buffer)
3238

33-
// Download the tar file
34-
const response = await fetch(tarUrl)
35-
const buffer = await response.arrayBuffer()
36-
await fs.promises.writeFile(outputTarPath, Buffer.from(buffer))
37-
38-
39-
// Create extract directory if it doesn't exist
39+
const { x } = await import('tar')
4040
try {
41-
await fs.promises.mkdir(extractTo, { recursive: true })
42-
} catch (err) {
43-
if (err.code !== 'EEXIST') {
44-
throw err
45-
}
41+
await x({
42+
file: outputTarPath,
43+
cwd: extractTo,
44+
sync: false,
45+
gzip: true,
46+
preservePaths: false
47+
})
48+
} finally {
49+
await fs.promises.rm(outputTarPath, { force: true })
4650
}
47-
48-
await x({
49-
file: outputTarPath,
50-
cwd: extractTo,
51-
sync: false,
52-
gzip: true
53-
})
54-
55-
await fs.promises.unlink(outputTarPath)

frontend/use_latest_ui_builder.sh

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
#!/bin/bash
2-
3-
# Auto-detect operating system
4-
if [[ "$OSTYPE" == "darwin"* ]]; then
5-
IS_MAC=true
6-
else
7-
IS_MAC=false
8-
fi
2+
set -euo pipefail
93

104
cd ~/windmill-code-ui-builder
115
HASH=$(git rev-parse --short HEAD)
126
HASH=${HASH::-1}
7+
ARTIFACT_URL="https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-${HASH}.tar.gz"
8+
9+
TMP_FILE=$(mktemp)
10+
trap 'rm -f "$TMP_FILE"' EXIT
1311

1412
echo "Using UI Builder hash: ${HASH}"
13+
curl -fsSL "$ARTIFACT_URL" -o "$TMP_FILE"
1514

16-
if [ "$IS_MAC" = true ]; then
17-
sed -i '' "s/ui_builder-[^.]*\.tar\.gz/ui_builder-${HASH}.tar.gz/" ../windmill/frontend/scripts/untar_ui_builder.js
15+
if command -v sha256sum >/dev/null 2>&1; then
16+
SHA256=$(sha256sum "$TMP_FILE" | awk '{print $1}')
1817
else
19-
sed -i "s/ui_builder-[^.]*\.tar\.gz/ui_builder-${HASH}.tar.gz/" ../windmill/frontend/scripts/untar_ui_builder.js
18+
SHA256=$(shasum -a 256 "$TMP_FILE" | awk '{print $1}')
2019
fi
20+
echo "Using UI Builder sha256: ${SHA256}"
21+
22+
node -e '
23+
const fs = require("fs")
24+
const [version, sha256] = process.argv.slice(1)
25+
const artifactPath = "../windmill/frontend/scripts/ui_builder_artifact.json"
26+
const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8"))
27+
artifact.version = version
28+
artifact.sha256 = sha256
29+
fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, "\t") + "\n")
30+
' "$HASH" "$SHA256"

0 commit comments

Comments
 (0)