Skip to content

Commit cdcd2af

Browse files
authored
fix(release): derive DSH bootstrap version from installer bytes (#7107)
The production landing-page promotion pinned DSH_BOOTSTRAP_VERSION: v1 in the workflow, while the publisher refuses to overwrite an already-published immutable version with different bytes. PR #6983 renamed the brand inside install-dsh.ps1 and install-dsh.sh without bumping that pin, so the next production deploy failed closed on immutable bootstrap object already exists with different content: bootstrap/dsh/v1/install-dsh.ps1 and skipped every later step, including the Cloudflare Pages deploy. The version is a function of the installer bytes, so maintaining it as a hand-written constant guarantees this drift. The publisher now resolves the version itself: it probes published versions in order using SHA256SUMS as the fingerprint for the whole set, reuses the version that already holds these exact bytes, and mints the next one when the bytes changed. Every published version keeps if-none-match: * and the immutable cache header, so history stays permanent and unoverwritable -- only the manual bump is gone. A mutable bootstrap/dsh/latest.json pointer records the current version, per-file sha256, and the publishing run, and the verify step reads the resolved version instead of naming one. DSH_BOOTSTRAP_VERSION survives as an explicit escape hatch: setting it forces one version and still fails closed on a content mismatch.
1 parent 12de3ed commit cdcd2af

5 files changed

Lines changed: 164 additions & 17 deletions

File tree

.github/workflows/landing-page-production.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,32 +185,46 @@ jobs:
185185
186186
# The short open-design.ai/install-dsh.* entry points ship with the
187187
# landing page. Preserve the exact production bytes separately under an
188-
# immutable R2 version before allowing Pages to deploy them. Re-running a
189-
# production promotion is idempotent; changing an already-published v1
190-
# fails closed and requires an explicit v2 instead.
188+
# immutable R2 version before allowing Pages to deploy them. The version
189+
# is derived from the installer bytes rather than pinned here: unchanged
190+
# installers reuse the version already published, edited ones mint the
191+
# next one, and every published version stays byte-for-byte immutable.
192+
# `DSH_BOOTSTRAP_VERSION` remains an escape hatch that forces one specific
193+
# version and still fails closed on a content mismatch.
191194
- name: Publish immutable DeepSeek Harness bootstrap installers to R2
195+
id: dsh_bootstrap
192196
env:
193197
DSH_BOOTSTRAP_SOURCE_DIR: apps/landing-page/public
194-
DSH_BOOTSTRAP_VERSION: v1
198+
RELEASE_BRANCH: ${{ github.ref_name }}
199+
RELEASE_COMMIT: ${{ github.sha }}
195200
RELEASE_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
201+
RELEASE_REPOSITORY: ${{ github.repository }}
202+
RELEASE_RUN_ATTEMPT: ${{ github.run_attempt }}
203+
RELEASE_RUN_ID: ${{ github.run_id }}
196204
RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}
197205
RELEASE_STORAGE_BUCKET: ${{ secrets.CLOUDFLARE_R2_RELEASES_BUCKET }}
198206
RELEASE_STORAGE_ENDPOINT: ${{ secrets.CLOUDFLARE_R2_RELEASES_URL }}
199207
RELEASE_STORAGE_REGION: auto
200208
RELEASE_STORAGE_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_RELEASES_SK }}
209+
RELEASE_WORKFLOW: ${{ github.workflow }}
201210
run: pnpm exec tools-release publish-dsh-bootstrap
202211

203212
- name: Verify public DeepSeek Harness bootstrap downloads
204213
env:
214+
DSH_BOOTSTRAP_VERSION: ${{ steps.dsh_bootstrap.outputs.version }}
205215
RELEASE_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}
206216
run: |
207217
set -euo pipefail
218+
if [ -z "$DSH_BOOTSTRAP_VERSION" ]; then
219+
echo "::error::publish step did not report a bootstrap version"
220+
exit 1
221+
fi
208222
download_dir="$(mktemp -d)"
209223
trap 'rm -rf "$download_dir"' EXIT
210224
for name in install-dsh.sh install-dsh.ps1 install-dsh.cmd SHA256SUMS; do
211225
curl --fail --silent --show-error --location \
212226
--retry 5 --retry-delay 1 --retry-all-errors \
213-
"$RELEASE_PUBLIC_ORIGIN/bootstrap/dsh/v1/$name" \
227+
"$RELEASE_PUBLIC_ORIGIN/bootstrap/dsh/$DSH_BOOTSTRAP_VERSION/$name" \
214228
--output "$download_dir/$name"
215229
done
216230
(

docs/deepseek-harness-one-click-install.zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
55
正式发布不采用开发机手动上传。脚本合并到 `main` 后,由 `landing-page-production` 生产发布 workflow 自动完成两件事:
66

7-
1. 将脚本和 `SHA256SUMS` 以不可覆盖的 `v1` 版本保存到 `https://releases.open-design.ai/bootstrap/dsh/v1/`
7+
1. 将脚本和 `SHA256SUMS` 以不可覆盖的版本保存到 `https://releases.open-design.ai/bootstrap/dsh/<version>/`
88
2. 将相同脚本发布为下方 `open-design.ai/install-dsh.*` 用户短链接。
99

10-
如果 R2 中已有同名 `v1` 但内容不同,workflow 会停止,必须提升为 `v2`,不能静默覆盖已经对外分发的安装器
10+
版本号由脚本内容决定,不需要人工维护:脚本没变就复用已发布的那一版,脚本改了就自动开下一版(`v1` `v2` → …)。每个已发布版本都保持逐字节不可覆盖,历史版本永久留存,`https://releases.open-design.ai/bootstrap/dsh/latest.json` 指向当前版本并附带各文件的 sha256 与发布来源
1111

1212
## 对外宣发文案
1313

e2e/tests/packaged-smoke-workflow.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2585,7 +2585,14 @@ process.stdin.on("end", () => {
25852585
expect(productionWorkflow).toContain('wranglerVersion: "4.110.0"');
25862586
expect(productionWorkflow).toContain("d1 migrations apply open-design-landing-attribution --remote");
25872587
expect(productionWorkflow).toContain("Publish immutable DeepSeek Harness bootstrap installers to R2");
2588-
expect(productionWorkflow).toContain("DSH_BOOTSTRAP_VERSION: v1");
2588+
expect(productionWorkflow).toContain("id: dsh_bootstrap");
2589+
// The bootstrap version follows the installer bytes. Pinning it in the
2590+
// workflow is what turned a copy edit inside install-dsh.ps1 into a hard
2591+
// production deploy failure, so the promotion must read the version the
2592+
// publisher resolved instead of naming one.
2593+
expect(productionWorkflow).not.toMatch(/DSH_BOOTSTRAP_VERSION: v\d/);
2594+
expect(productionWorkflow).toContain("DSH_BOOTSTRAP_VERSION: ${{ steps.dsh_bootstrap.outputs.version }}");
2595+
expect(productionWorkflow).toContain('"$RELEASE_PUBLIC_ORIGIN/bootstrap/dsh/$DSH_BOOTSTRAP_VERSION/$name"');
25892596
expect(productionWorkflow).toContain("DSH_BOOTSTRAP_SOURCE_DIR: apps/landing-page/public");
25902597
expect(productionWorkflow).toContain("RELEASE_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_RELEASES_PUBLIC_ORIGIN }}");
25912598
expect(productionWorkflow).toContain("RELEASE_STORAGE_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_RELEASES_AK }}");

tools/release/src/storage/publish-dsh-bootstrap.ts

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { createHash } from "node:crypto";
2-
import { readFileSync } from "node:fs";
2+
import { appendFileSync, readFileSync } from "node:fs";
33
import { join } from "node:path";
44

5-
import { contentType, publicUrl, required, storageConfigFromEnv } from "./common.ts";
6-
import { getStorageObject, putStorageObjectWithStatus, type StorageConfig } from "./s3-upload.ts";
5+
import { contentType, githubInfo, optional, publicUrl, required, storageConfigFromEnv } from "./common.ts";
6+
import { getStorageObject, putStorageObject, putStorageObjectWithStatus, type StorageConfig } from "./s3-upload.ts";
77

88
const BOOTSTRAP_FILES = ["install-dsh.cmd", "install-dsh.ps1", "install-dsh.sh"] as const;
99
const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
10+
const POINTER_CACHE_CONTROL = "public, max-age=60";
11+
const POINTER_KEY = "bootstrap/dsh/latest.json";
12+
// Versions are minted one at a time by production promotions, so a run that has
13+
// to probe past this many of them is looping on a bug rather than catching up.
14+
const MAX_VERSION_PROBE = 100;
1015

1116
type BootstrapObject = {
1217
body: Buffer;
@@ -17,6 +22,10 @@ function sha256(body: Buffer): string {
1722
return createHash("sha256").update(body).digest("hex");
1823
}
1924

25+
function versionPrefix(version: string): string {
26+
return `bootstrap/dsh/${version}`;
27+
}
28+
2029
async function publishImmutableBootstrapObject(
2130
storage: StorageConfig,
2231
prefix: string,
@@ -46,15 +55,40 @@ async function publishImmutableBootstrapObject(
4655
console.log(`reused identical immutable bootstrap object ${objectKey}`);
4756
}
4857

49-
const version = required("DSH_BOOTSTRAP_VERSION");
50-
if (!/^v[1-9]\d*$/.test(version)) {
51-
throw new Error(`DSH_BOOTSTRAP_VERSION must look like v1 or v2; got ${version}`);
58+
/**
59+
* The bootstrap version is a function of the installer bytes, not a constant a
60+
* human is expected to bump. Probe published versions in ascending order and
61+
* settle on the first one that either does not exist yet (mint it) or already
62+
* holds exactly these bytes (reuse it). `SHA256SUMS` fingerprints all three
63+
* installers, so a single GET per version decides the whole set.
64+
*
65+
* This keeps every published version permanently immutable while making an
66+
* edited installer roll forward on its own instead of failing the deploy.
67+
*/
68+
async function resolveBootstrapVersion(storage: StorageConfig, checksums: Buffer): Promise<string> {
69+
for (let candidate = 1; candidate <= MAX_VERSION_PROBE; candidate += 1) {
70+
const version = `v${candidate}`;
71+
const published = await getStorageObject({ ...storage, objectKey: `${versionPrefix(version)}/SHA256SUMS` });
72+
if (published == null) {
73+
console.log(`minting new immutable bootstrap version ${version}`);
74+
return version;
75+
}
76+
if (published.bytes.equals(checksums)) {
77+
console.log(`reusing published immutable bootstrap version ${version}`);
78+
return version;
79+
}
80+
}
81+
throw new Error(`no free DeepSeek Harness bootstrap version below v${MAX_VERSION_PROBE + 1}`);
82+
}
83+
84+
const pinnedVersion = optional("DSH_BOOTSTRAP_VERSION");
85+
if (pinnedVersion.length > 0 && !/^v[1-9]\d*$/.test(pinnedVersion)) {
86+
throw new Error(`DSH_BOOTSTRAP_VERSION must look like v1 or v2; got ${pinnedVersion}`);
5287
}
5388

5489
const sourceDir = required("DSH_BOOTSTRAP_SOURCE_DIR");
5590
const publicOrigin = required("RELEASE_PUBLIC_ORIGIN");
5691
const storage = storageConfigFromEnv();
57-
const prefix = `bootstrap/dsh/${version}`;
5892
const installers = BOOTSTRAP_FILES.map((name) => ({
5993
body: readFileSync(join(sourceDir, name)),
6094
name,
@@ -65,6 +99,11 @@ const checksums = Buffer.from(
6599
);
66100
const objects: BootstrapObject[] = [...installers, { body: checksums, name: "SHA256SUMS" }];
67101

102+
// An explicit pin stays fail-closed: it is the escape hatch for forcing a
103+
// specific version, and it must never silently overwrite different bytes.
104+
const version = pinnedVersion.length > 0 ? pinnedVersion : await resolveBootstrapVersion(storage, checksums);
105+
const prefix = versionPrefix(version);
106+
68107
for (const object of objects) {
69108
await publishImmutableBootstrapObject(storage, prefix, object);
70109
}
@@ -77,3 +116,31 @@ for (const object of objects) {
77116
}
78117
console.log(publicUrl(publicOrigin, prefix, object.name));
79118
}
119+
120+
// Mutable pointer so consumers and the deploy workflow can find the current
121+
// version without hard-coding it.
122+
await putStorageObject({
123+
...storage,
124+
body: Buffer.from(
125+
`${JSON.stringify(
126+
{
127+
files: Object.fromEntries(installers.map(({ body, name }) => [name, sha256(body)])),
128+
github: githubInfo(),
129+
publishedAt: new Date().toISOString(),
130+
version,
131+
},
132+
null,
133+
2,
134+
)}\n`,
135+
"utf8",
136+
),
137+
cacheControl: POINTER_CACHE_CONTROL,
138+
contentType: contentType("latest.json"),
139+
objectKey: POINTER_KEY,
140+
});
141+
console.log(publicUrl(publicOrigin, "bootstrap/dsh", "latest.json"));
142+
143+
const githubOutput = optional("GITHUB_OUTPUT");
144+
if (githubOutput.length > 0) {
145+
appendFileSync(githubOutput, `version=${version}\n`, "utf8");
146+
}

tools/serve/tests/dsh-bootstrap-publish.test.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,24 @@ describe("DeepSeek Harness bootstrap publisher", () => {
4545
};
4646
await Promise.all(Object.entries(files).map(([name, body]) => writeFile(join(sourceDir, name), body, "utf8")));
4747

48-
const env = {
48+
const env: NodeJS.ProcessEnv = {
4949
...process.env,
5050
DSH_BOOTSTRAP_SOURCE_DIR: sourceDir,
51-
DSH_BOOTSTRAP_VERSION: "v1",
5251
RELEASE_PUBLIC_ORIGIN: "https://releases.example.test",
5352
RELEASE_STORAGE_ACCESS_KEY_ID: "ak",
5453
RELEASE_STORAGE_BUCKET: server.info.bucket,
5554
RELEASE_STORAGE_ENDPOINT: server.info.endpointUrl,
5655
RELEASE_STORAGE_REGION: "auto",
5756
RELEASE_STORAGE_SECRET_ACCESS_KEY: "sk",
5857
};
58+
delete env.DSH_BOOTSTRAP_VERSION;
59+
delete env.GITHUB_OUTPUT;
5960

6061
try {
6162
const output = await runPublisher(repoRoot, env);
6263
expect(output).toContain("https://releases.example.test/bootstrap/dsh/v1/install-dsh.sh");
6364
expect(server.listObjectKeys()).toEqual([
65+
"bootstrap/dsh/latest.json",
6466
"bootstrap/dsh/v1/SHA256SUMS",
6567
"bootstrap/dsh/v1/install-dsh.cmd",
6668
"bootstrap/dsh/v1/install-dsh.ps1",
@@ -72,9 +74,66 @@ describe("DeepSeek Harness bootstrap publisher", () => {
7274
expect(server.getObject("bootstrap/dsh/v1/SHA256SUMS")?.toString("utf8")).toMatch(
7375
/^[a-f0-9]{64} install-dsh\.cmd\n[a-f0-9]{64} install-dsh\.ps1\n[a-f0-9]{64} install-dsh\.sh\n$/,
7476
);
77+
expect(JSON.parse(server.getObject("bootstrap/dsh/latest.json")?.toString("utf8") ?? "{}")).toMatchObject({
78+
version: "v1",
79+
});
7580

81+
// Re-promoting the same bytes is a no-op that stays on the same version.
7682
await expect(runPublisher(repoRoot, env)).resolves.toContain("reused identical immutable bootstrap object");
83+
expect(server.listObjectKeys()).toContain("bootstrap/dsh/v1/SHA256SUMS");
84+
expect(server.listObjectKeys()).not.toContain("bootstrap/dsh/v2/SHA256SUMS");
85+
86+
// Changed bytes open the next version automatically instead of failing the
87+
// deploy; v1 keeps its original bytes as the immutable archive.
88+
const changedShell = "#!/usr/bin/env sh\necho changed\n";
89+
await writeFile(join(sourceDir, "install-dsh.sh"), changedShell, "utf8");
90+
const rolled = await runPublisher(repoRoot, env);
91+
expect(rolled).toContain("https://releases.example.test/bootstrap/dsh/v2/install-dsh.sh");
92+
expect(server.getObject("bootstrap/dsh/v2/install-dsh.sh")?.toString("utf8")).toBe(changedShell);
93+
expect(server.getObject("bootstrap/dsh/v1/install-dsh.sh")?.toString("utf8")).toBe(files["install-dsh.sh"]);
94+
expect(JSON.parse(server.getObject("bootstrap/dsh/latest.json")?.toString("utf8") ?? "{}")).toMatchObject({
95+
version: "v2",
96+
});
97+
98+
// Rolling back to the earlier bytes reuses the version that already holds
99+
// them rather than minting a third copy.
100+
await writeFile(join(sourceDir, "install-dsh.sh"), files["install-dsh.sh"], "utf8");
101+
const rolledBack = await runPublisher(repoRoot, env);
102+
expect(rolledBack).toContain("https://releases.example.test/bootstrap/dsh/v1/install-dsh.sh");
103+
expect(server.listObjectKeys()).not.toContain("bootstrap/dsh/v3/SHA256SUMS");
104+
} finally {
105+
await server.close();
106+
await rm(sourceDir, { force: true, recursive: true });
107+
}
108+
});
109+
110+
it("still fails closed when an explicit version pin would overwrite different bytes", async () => {
111+
const repoRoot = resolve(import.meta.dirname, "../../..");
112+
const sourceDir = await mkdtemp(join(tmpdir(), "od-dsh-bootstrap-pin-"));
113+
const server = await startReleaseStorageFixtureServer();
114+
await Promise.all(
115+
Object.entries({
116+
"install-dsh.cmd": "@echo off\r\necho cmd\r\n",
117+
"install-dsh.ps1": "Write-Host 'powershell'\n",
118+
"install-dsh.sh": "#!/usr/bin/env sh\necho posix\n",
119+
}).map(([name, body]) => writeFile(join(sourceDir, name), body, "utf8")),
120+
);
77121

122+
const env: NodeJS.ProcessEnv = {
123+
...process.env,
124+
DSH_BOOTSTRAP_SOURCE_DIR: sourceDir,
125+
DSH_BOOTSTRAP_VERSION: "v1",
126+
RELEASE_PUBLIC_ORIGIN: "https://releases.example.test",
127+
RELEASE_STORAGE_ACCESS_KEY_ID: "ak",
128+
RELEASE_STORAGE_BUCKET: server.info.bucket,
129+
RELEASE_STORAGE_ENDPOINT: server.info.endpointUrl,
130+
RELEASE_STORAGE_REGION: "auto",
131+
RELEASE_STORAGE_SECRET_ACCESS_KEY: "sk",
132+
};
133+
delete env.GITHUB_OUTPUT;
134+
135+
try {
136+
await runPublisher(repoRoot, env);
78137
await writeFile(join(sourceDir, "install-dsh.sh"), "#!/usr/bin/env sh\necho changed\n", "utf8");
79138
await expect(runPublisher(repoRoot, env)).rejects.toThrow(
80139
/immutable bootstrap object already exists with different content/,

0 commit comments

Comments
 (0)