Skip to content

Commit 0bde145

Browse files
committed
fixes or something
1 parent 5b851df commit 0bde145

38 files changed

Lines changed: 2413 additions & 110 deletions

.buildkite/pipeline.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
agents:
2+
queue: kubernetes
3+
4+
steps:
5+
- label: ":dagger: CI"
6+
key: ci
7+
image: "oven/bun:latest"
8+
command: .buildkite/scripts/ci.sh
9+
timeout_in_minutes: 60
10+
retry:
11+
automatic:
12+
- exit_status: -1
13+
limit: 2
14+
- exit_status: 255
15+
limit: 2
16+
plugins:
17+
- kubernetes:
18+
podSpecPatch:
19+
serviceAccountName: buildkite-agent-stack-k8s
20+
containers:
21+
- name: container-0
22+
envFrom:
23+
- secretRef:
24+
name: buildkite-ci-secrets
25+
26+
- wait: ~
27+
if: build.branch == pipeline.default_branch
28+
29+
- label: ":books: Update READMEs"
30+
key: update-readmes
31+
if: build.branch == pipeline.default_branch
32+
image: "oven/bun:latest"
33+
command: .buildkite/scripts/update-readmes.sh
34+
timeout_in_minutes: 30
35+
plugins:
36+
- kubernetes:
37+
podSpecPatch:
38+
serviceAccountName: buildkite-agent-stack-k8s
39+
containers:
40+
- name: container-0
41+
envFrom:
42+
- secretRef:
43+
name: buildkite-ci-secrets

.buildkite/scripts/ci.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Install kubectl
5+
KUBECTL_VERSION="v1.34.1"
6+
echo "--- :kubectl: Installing kubectl ${KUBECTL_VERSION}"
7+
curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl
8+
chmod +x /usr/local/bin/kubectl
9+
10+
# Install Dagger CLI (version from dagger.json)
11+
DAGGER_VERSION=$(jq -r '.engineVersion' dagger.json | sed 's/^v//')
12+
echo "--- :dagger: Installing Dagger CLI ${DAGGER_VERSION}"
13+
if ! curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION="${DAGGER_VERSION}" BIN_DIR="/usr/local/bin" sh; then
14+
echo "Primary source failed, trying GitHub releases..."
15+
curl -fsSL "https://github.com/dagger/dagger/releases/download/v${DAGGER_VERSION}/dagger_v${DAGGER_VERSION}_linux_amd64.tar.gz" | tar xz -C /usr/local/bin
16+
chmod +x /usr/local/bin/dagger
17+
fi
18+
dagger version
19+
20+
# Connect to remote Dagger engine
21+
echo "--- :kubernetes: Connecting to Dagger engine"
22+
DAGGER_ENGINE_POD_NAME="$(kubectl get pod \
23+
--selector=name=dagger-dagger-helm-engine \
24+
--namespace=dagger \
25+
--output=jsonpath='{.items[0].metadata.name}')"
26+
export _EXPERIMENTAL_DAGGER_RUNNER_HOST="kube-pod://${DAGGER_ENGINE_POD_NAME}?namespace=dagger"
27+
echo "Using Dagger engine: ${DAGGER_ENGINE_POD_NAME}"
28+
29+
# Install Dagger module dependencies
30+
echo "--- :bun: Installing Dagger module dependencies"
31+
cd .dagger && bun install --frozen-lockfile && cd ..
32+
33+
# Build Dagger CI args
34+
echo "+++ :dagger: Running Dagger CI pipeline"
35+
ARGS=(
36+
--source=.
37+
--branch="${BUILDKITE_BRANCH}"
38+
--github-token=env:GH_TOKEN
39+
--npm-token=env:NPM_TOKEN
40+
--s3-access-key-id=env:S3_ACCESS_KEY_ID
41+
--s3-secret-access-key=env:S3_SECRET_ACCESS_KEY
42+
)
43+
44+
if [[ "${BUILDKITE_BRANCH}" == "main" ]]; then
45+
ARGS+=(
46+
--version="1.0.${BUILDKITE_BUILD_NUMBER}"
47+
--git-sha="${BUILDKITE_COMMIT}"
48+
--registry-username=shepherdjerred
49+
--registry-password=env:GH_TOKEN
50+
--commit-back-token=env:GH_TOKEN
51+
--argocd-token=env:ARGOCD_TOKEN
52+
--chart-museum-username=env:CHARTMUSEUM_USERNAME
53+
--chart-museum-password=env:CHARTMUSEUM_PASSWORD
54+
--cloudflare-api-token=env:CLOUDFLARE_API_TOKEN
55+
--cloudflare-account-id=env:CLOUDFLARE_ACCOUNT_ID
56+
--hass-base-url=env:HASS_BASE_URL
57+
--hass-token=env:HASS_TOKEN
58+
--tofu-github-token=env:TOFU_GITHUB_TOKEN
59+
)
60+
fi
61+
62+
MAX_RETRIES=5
63+
RETRY=0
64+
until dagger -v call ci "${ARGS[@]}"; do
65+
RETRY=$((RETRY + 1))
66+
if [ "${RETRY}" -ge "${MAX_RETRIES}" ]; then
67+
echo "Dagger CI failed after ${MAX_RETRIES} attempts"
68+
exit 1
69+
fi
70+
DELAY=$((10 + RETRY * 10))
71+
echo "Dagger CI failed (attempt ${RETRY}/${MAX_RETRIES}), retrying in ${DELAY}s..."
72+
sleep "${DELAY}"
73+
done
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Install kubectl
5+
KUBECTL_VERSION="v1.34.1"
6+
echo "--- :kubectl: Installing kubectl ${KUBECTL_VERSION}"
7+
curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl
8+
chmod +x /usr/local/bin/kubectl
9+
10+
# Install Dagger CLI (version from dagger.json)
11+
DAGGER_VERSION=$(jq -r '.engineVersion' dagger.json | sed 's/^v//')
12+
echo "--- :dagger: Installing Dagger CLI ${DAGGER_VERSION}"
13+
if ! curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION="${DAGGER_VERSION}" BIN_DIR="/usr/local/bin" sh; then
14+
echo "Primary source failed, trying GitHub releases..."
15+
curl -fsSL "https://github.com/dagger/dagger/releases/download/v${DAGGER_VERSION}/dagger_v${DAGGER_VERSION}_linux_amd64.tar.gz" | tar xz -C /usr/local/bin
16+
chmod +x /usr/local/bin/dagger
17+
fi
18+
19+
# Connect to remote Dagger engine
20+
echo "--- :kubernetes: Connecting to Dagger engine"
21+
DAGGER_ENGINE_POD_NAME="$(kubectl get pod \
22+
--selector=name=dagger-dagger-helm-engine \
23+
--namespace=dagger \
24+
--output=jsonpath='{.items[0].metadata.name}')"
25+
export _EXPERIMENTAL_DAGGER_RUNNER_HOST="kube-pod://${DAGGER_ENGINE_POD_NAME}?namespace=dagger"
26+
27+
# Install Dagger module dependencies
28+
echo "--- :bun: Installing Dagger module dependencies"
29+
cd .dagger && bun install --frozen-lockfile && cd ..
30+
31+
echo "+++ :books: Updating READMEs"
32+
dagger call update-readmes \
33+
--source=. \
34+
--github-token=env:GH_TOKEN \
35+
--openai-api-key=env:OPENAI_API_KEY \
36+
--base-branch="${BUILDKITE_BRANCH}"

.dagger/src/index-ci-helpers.ts

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -182,31 +182,6 @@ export async function buildClauderonWeb(
182182
return { container: c, frontendDist, outputs };
183183
}
184184

185-
/** Retry a check if it fails with a transient Dagger graphql error. */
186-
async function withGraphqlRetry<T>(
187-
name: string,
188-
fn: () => Promise<T>,
189-
maxRetries = 2,
190-
): Promise<T> {
191-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
192-
try {
193-
return await fn();
194-
} catch (error) {
195-
const msg = error instanceof Error ? error.message : String(error);
196-
const isGraphqlError = msg.includes(
197-
"unknown error while requesting data via graphql",
198-
);
199-
if (!isGraphqlError || attempt === maxRetries) {
200-
throw error;
201-
}
202-
console.log(
203-
`⟳ ${name}: graphql error on attempt ${String(attempt + 1)}, retrying...`,
204-
);
205-
}
206-
}
207-
throw new Error("unreachable");
208-
}
209-
210185
/** Run package-specific validation checks in parallel. */
211186
export async function runPackageValidation(
212187
source: Directory,
@@ -244,11 +219,9 @@ export async function runPackageValidation(
244219
}
245220
}
246221

247-
// Run scout-for-lol separately with retry to avoid graphql errors
222+
// Run scout-for-lol separately to avoid graphql errors from engine pressure
248223
try {
249-
outputs.push(
250-
await withGraphqlRetry("scout-for-lol", () => checkScoutForLol(source)),
251-
);
224+
outputs.push(await checkScoutForLol(source));
252225
} catch (error) {
253226
const msg = error instanceof Error ? error.message : String(error);
254227
outputs.push(`✗ scout-for-lol: ${msg}`);
@@ -452,32 +425,51 @@ export async function runReleasePhase(
452425
}
453426
}
454427

455-
// App deployments (parallel, returns appVersions from successful deploys)
456-
const deployResult = await runAppDeployments(options);
457-
outputs.push(...deployResult.outputs);
458-
errors.push(...deployResult.errors);
428+
// App deployments and Clauderon binary release in parallel
429+
// (independent of each other — clauderon needs releaseOutput, deploys need options)
430+
const [deploySettled, clauderonSettled] = await Promise.allSettled([
431+
runAppDeployments(options),
432+
runClauderonRelease(options, releaseResult.releaseOutput),
433+
]);
434+
435+
// Extract deploy result
436+
let deployAppVersions: Record<string, string> = {};
437+
if (deploySettled.status === "fulfilled") {
438+
outputs.push(...deploySettled.value.outputs);
439+
errors.push(...deploySettled.value.errors);
440+
deployAppVersions = deploySettled.value.appVersions;
441+
} else {
442+
const msg =
443+
deploySettled.reason instanceof Error
444+
? deploySettled.reason.message
445+
: String(deploySettled.reason);
446+
outputs.push(`✗ App deployments: ${msg}`);
447+
errors.push(`App deployments: ${msg}`);
448+
}
449+
450+
// Extract clauderon result
451+
if (clauderonSettled.status === "fulfilled") {
452+
outputs.push(...clauderonSettled.value.outputs);
453+
errors.push(...clauderonSettled.value.errors);
454+
} else {
455+
const msg =
456+
clauderonSettled.reason instanceof Error
457+
? clauderonSettled.reason.message
458+
: String(clauderonSettled.reason);
459+
outputs.push(`✗ Clauderon release: ${msg}`);
460+
errors.push(`Clauderon release: ${msg}`);
461+
}
459462

460463
// Homelab release (needs appVersions from deployments)
461-
const homelabResult = await runHomelabRelease(
462-
options,
463-
deployResult.appVersions,
464-
);
464+
const homelabResult = await runHomelabRelease(options, deployAppVersions);
465465
outputs.push(...homelabResult.outputs);
466466
errors.push(...homelabResult.errors);
467467

468-
// Clauderon binary release
469-
const clauderonResult = await runClauderonRelease(
470-
options,
471-
releaseResult.releaseOutput,
472-
);
473-
outputs.push(...clauderonResult.outputs);
474-
errors.push(...clauderonResult.errors);
475-
476468
// Version commit-back — AFTER all deployments, only if no errors
477469
if (errors.length === 0) {
478470
const commitResult = await runVersionCommitBack(
479471
options,
480-
deployResult.appVersions,
472+
deployAppVersions,
481473
homelabResult.infraVersions,
482474
);
483475
outputs.push(...commitResult);

.dagger/src/index-infra.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,10 @@ export function complianceCheck(source: Directory): Container {
280280
return dag
281281
.container()
282282
.from("alpine:latest")
283-
.withMountedDirectory("/workspace", source)
284283
.withWorkdir("/workspace")
284+
.withFile("/workspace/package.json", source.file("package.json"))
285+
.withMountedDirectory("/workspace/packages", source.directory("packages"))
286+
.withFile("/workspace/scripts/compliance-check.sh", source.file("scripts/compliance-check.sh"))
285287
.withExec(["sh", "scripts/compliance-check.sh"]);
286288
}
287289

@@ -356,8 +358,10 @@ echo "Quality ratchet passed"
356358
return dag
357359
.container()
358360
.from("alpine:latest")
359-
.withMountedDirectory("/workspace", source)
360361
.withWorkdir("/workspace")
362+
.withMountedDirectory("/workspace/packages", source.directory("packages"))
363+
.withMountedDirectory("/workspace/.dagger", source.directory(".dagger"))
364+
.withFile("/workspace/.quality-baseline.json", source.file(".quality-baseline.json"))
361365
.withNewFile("/tmp/ratchet.sh", script)
362366
.withExec(["sh", "/tmp/ratchet.sh"]);
363367
}
@@ -385,19 +389,24 @@ export function daggerLintCheck(source: Directory): Container {
385389
}
386390

387391
/**
388-
* Run shellcheck on all .sh files under packages/ and scripts/.
392+
* Run shellcheck on all .sh files under packages/, scripts/, and .buildkite/.
389393
*/
390394
export function shellcheckStep(source: Directory): Container {
391395
// Use actionlint image which includes sh, find, AND shellcheck
392396
return dag
393397
.container()
394398
.from("rhysd/actionlint:latest")
395-
.withMountedDirectory("/workspace", source)
396399
.withWorkdir("/workspace")
400+
.withMountedDirectory("/workspace/packages", source.directory("packages"))
401+
.withMountedDirectory("/workspace/scripts", source.directory("scripts"))
402+
.withMountedDirectory(
403+
"/workspace/.buildkite",
404+
source.directory(".buildkite"),
405+
)
397406
.withExec([
398407
"sh",
399408
"-c",
400-
"find /workspace/packages/ /workspace/scripts/ -name '*.sh' -not -path '*/node_modules/*' -print0 | xargs -0 -r shellcheck --severity=warning",
409+
"find /workspace/packages/ /workspace/scripts/ /workspace/.buildkite/ -name '*.sh' -not -path '*/node_modules/*' -print0 | xargs -0 -r shellcheck --severity=warning",
401410
]);
402411
}
403412

@@ -408,8 +417,8 @@ export function actionlintStep(source: Directory): Container {
408417
return dag
409418
.container()
410419
.from("rhysd/actionlint:latest")
411-
.withMountedDirectory("/workspace", source)
412420
.withWorkdir("/workspace")
421+
.withMountedDirectory("/workspace/.github", source.directory(".github"))
413422
.withExec(["actionlint", "-color"]);
414423
}
415424

.dagger/src/index-release-helpers.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -365,17 +365,12 @@ export async function runHomelabRelease(
365365
version,
366366
} = options;
367367

368-
if (
369-
argocdToken === undefined ||
370-
chartMuseumUsername === undefined ||
371-
chartMuseumPassword === undefined ||
372-
cloudflareApiToken === undefined ||
373-
cloudflareAccountId === undefined ||
374-
registryPassword === undefined ||
375-
s3AccessKeyId === undefined ||
376-
s3SecretAccessKey === undefined
377-
) {
378-
return { outputs, errors, infraVersions };
368+
const required = { argocdToken, chartMuseumUsername, chartMuseumPassword, cloudflareApiToken, cloudflareAccountId, registryPassword, s3AccessKeyId, s3SecretAccessKey };
369+
const missing = Object.entries(required).filter(([, v]) => v === undefined).map(([k]) => k);
370+
if (missing.length > 0) {
371+
const msg = `Homelab release skipped: missing required secrets: ${missing.join(", ")}`;
372+
errors.push(msg);
373+
return { outputs: [msg], errors, infraVersions };
379374
}
380375

381376
outputs.push("\n--- Homelab Release ---");

0 commit comments

Comments
 (0)