Skip to content

Nightly Build

Nightly Build #306

Workflow file for this run

name: Nightly Build
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
logseq_branch:
description: "Logseq branch to build"
required: false
default: master
publish_release:
description: "Publish release and commit manifest update"
required: true
type: boolean
default: true
# Least-privilege default for jobs without their own block; writing jobs
# elevate per-job. The reusable build call needs only contents: read.
permissions:
contents: read
jobs:
build:
uses: ./.github/workflows/build-desktop.yml
with:
logseq_branch: ${{ github.event.inputs.logseq_branch || 'master' }}
publish-release:
if: github.event_name == 'schedule' || inputs.publish_release == true
runs-on: ubuntu-24.04
needs: build
permissions:
contents: write # push the manifest bump and publish the release
statuses: write # attach test-build-required to the bump commit
concurrency:
group: repo-main-writer
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Download packaged builds
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh run download "${{ github.run_id }}" --repo "${{ github.repository }}" --dir builds
- name: Resolve build metadata
env:
REPO: ${{ github.repository }}
run: |
set -euo pipefail
meta="$(find builds -name meta.txt | head -1)"
x64_tar="$(find builds -name 'logseq-linux-x64-*.tar.gz' | head -1)"
arm64_tar="$(find builds -name 'logseq-linux-arm64-*.tar.gz' | head -1)"
darwin_arm64_tar="$(find builds -name 'logseq-darwin-arm64-*.tar.gz' | head -1)"
notes="$(find builds -name release-notes.md | head -1)"
x64_hash_file="$(find builds -name 'hash-x64.txt' | head -1)"
arm64_hash_file="$(find builds -name 'hash-arm64.txt' | head -1)"
darwin_arm64_hash_file="$(find builds -name 'hash-darwin-arm64.txt' | head -1)"
for f in "$meta" "$x64_tar" "$arm64_tar" "$darwin_arm64_tar" "$notes" "$x64_hash_file" "$arm64_hash_file" "$darwin_arm64_hash_file"; do
test -n "$f" || { echo "Required artifact file missing" >&2; exit 1; }
done
# meta.txt holds trusted build-step outputs (version/revision/datestring).
version="$(grep -E '^version=' "$meta" | cut -d= -f2-)"
revision="$(grep -E '^revision=' "$meta" | cut -d= -f2-)"
datestring="$(grep -E '^datestring=' "$meta" | cut -d= -f2-)"
tag="nightly-${datestring}"
darwin_arm64_asset_url="https://github.com/${REPO}/releases/download/${tag}/$(basename "$darwin_arm64_tar")"
darwin_arm64_asset_sha256="$(cat "$darwin_arm64_hash_file")"
{
echo "LOGSEQ_VERSION=${version}"
echo "LOGSEQ_REV=${revision}"
echo "NIGHTLY_TAG=${tag}"
echo "RELEASE_TAG=${tag}"
echo "BUILD_REVISION=${revision}"
echo "X64_TARBALL=${x64_tar}"
echo "ARM64_TARBALL=${arm64_tar}"
echo "DARWIN_ARM64_TARBALL=${darwin_arm64_tar}"
echo "RELEASE_NOTES=${notes}"
echo "ASSET_URL_X86_64=https://github.com/${REPO}/releases/download/${tag}/$(basename "$x64_tar")"
echo "ASSET_SHA256_X86_64=$(cat "$x64_hash_file")"
echo "ASSET_URL_AARCH64=https://github.com/${REPO}/releases/download/${tag}/$(basename "$arm64_tar")"
echo "ASSET_SHA256_AARCH64=$(cat "$arm64_hash_file")"
echo "ASSET_URL_AARCH64_DARWIN=${darwin_arm64_asset_url}"
echo "ASSET_SHA256_AARCH64_DARWIN=${darwin_arm64_asset_sha256}"
} >>"$GITHUB_ENV"
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
accept-flake-config = true
- name: Setup Cachix
uses: cachix/cachix-action@v17
with:
name: nix-logseq-git-flake
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Publish GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
rev_short="${BUILD_REVISION:0:7}"
release_assets=("$X64_TARBALL" "$ARM64_TARBALL" "$DARWIN_ARM64_TARBALL")
gh release delete "$RELEASE_TAG" -y || true
gh release create "$RELEASE_TAG" "${release_assets[@]}" \
--title "logseq-nightly-${rev_short}" \
--notes-file "$RELEASE_NOTES" \
--target main
- name: Update nightly manifest and CLI hashes
run: bash scripts/update-nightly.sh
# Realize the architecture-independent CLI FODs with their freshly
# resolved hashes so Cachix uploads them; aarch64 jobs substitute these
# paths instead of rebuilding them.
- name: Seed CLI fixed-output derivations to Cachix
run: |
nix build --accept-flake-config --print-build-logs \
.#logseq-cli.cliCljDeps \
.#logseq-cli.cliPnpmDeps
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Format repository
run: nix fmt
- name: Validate flake
run: |
nix flake check
- name: Commit nightly update
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if git diff --quiet -- data/logseq-nightly.json; then
echo "Manifest unchanged"
exit 0
fi
git add data/logseq-nightly.json
git commit -F - <<EOF
chore: bump nightly manifest
The nightly publish-release job rewrites data/logseq-nightly.json for ${NIGHTLY_TAG}.
It records upstream revision ${LOGSEQ_REV} and refreshed CLI fixed-output hashes.
Validation: nix fmt
Validation: nix flake check
EOF
repo_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git fetch "$repo_url" main
git rebase FETCH_HEAD
commit_sha="$(git rev-parse HEAD)"
staging_branch="automation/nightly-manifest-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
cleanup_staging_branch() {
# Best-effort: a leaked automation/nightly-manifest-* ref is
# harmless, but a failed delete should be visible in the log.
git push "$repo_url" ":refs/heads/${staging_branch}" \
|| echo "WARNING: could not delete staging ref ${staging_branch}" >&2
}
trap cleanup_staging_branch EXIT
# Branch protection requires test-build-required before the commit
# reaches main. Publish the commit object on a short-lived ref, then
# attach the status after the real nightly validation has passed.
git push "$repo_url" "HEAD:refs/heads/${staging_branch}"
gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${commit_sha}" \
-f state=success \
-f context="test-build-required" \
-f description="Nightly publish-release validated this manifest bump." \
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
git push "$repo_url" HEAD:main
report-failure:
if: ${{ always() && github.event_name == 'schedule' && (needs.build.result != 'success' || needs['publish-release'].result != 'success') }}
needs:
- build
- publish-release
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- name: Create or update failure issue
uses: actions/github-script@v9
env:
ISSUE_TITLE: Nightly build automation failing
ISSUE_MARKER: <!-- nightly-build-failure -->
FAILURE_LABELS: type(bug),area(ci),area(automation),area(nightly),status(needs-manual-review),priority(p2),origin(automated)
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
BUILD_RESULT: ${{ needs.build.result }}
PUBLISH_RESULT: ${{ needs['publish-release'].result }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const title = process.env.ISSUE_TITLE;
const marker = process.env.ISSUE_MARKER;
const failureLabels = process.env.FAILURE_LABELS.split(",").filter(Boolean);
const runUrl = process.env.RUN_URL;
const buildResult = process.env.BUILD_RESULT;
const publishResult = process.env.PUBLISH_RESULT;
const timestamp = new Date().toISOString();
const body = [
marker,
"Nightly build workflow failed on a scheduled run.",
"",
`Latest failed run: ${runUrl}`,
"",
"This is a rolling issue; new failures are added as comments."
].join("\n");
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100
});
const existing = openIssues.find(
(issue) =>
!issue.pull_request &&
issue.title === title &&
issue.body &&
issue.body.includes(marker)
);
const commentBody = [
`Failure detected at ${timestamp}.`,
`Run: ${runUrl}`,
`build=${buildResult}, publish-release=${publishResult}`
].join("\n");
if (existing) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: existing.number,
body: commentBody
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: existing.number,
labels: failureLabels
});
core.info(`Updated existing issue #${existing.number}`);
} else {
const created = await github.rest.issues.create({
owner,
repo,
title,
body,
labels: failureLabels
});
core.info(`Created issue #${created.data.number}`);
}
report-recovery:
if: ${{ always() && github.event_name == 'schedule' && needs.build.result == 'success' && needs['publish-release'].result == 'success' }}
needs:
- build
- publish-release
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- name: Close failure issue after recovery
uses: actions/github-script@v9
env:
ISSUE_TITLE: Nightly build automation failing
ISSUE_MARKER: <!-- nightly-build-failure -->
RECOVERED_LABEL: status(recovered)
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const title = process.env.ISSUE_TITLE;
const marker = process.env.ISSUE_MARKER;
const recoveredLabel = process.env.RECOVERED_LABEL;
const runUrl = process.env.RUN_URL;
const timestamp = new Date().toISOString();
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100
});
const existing = openIssues.find(
(issue) =>
!issue.pull_request &&
issue.title === title &&
issue.body &&
issue.body.includes(marker)
);
if (!existing) {
core.info("No open rolling nightly failure issue to close.");
return;
}
await github.rest.issues.createComment({
owner,
repo,
issue_number: existing.number,
body: [
`Recovery detected at ${timestamp}.`,
`Successful run: ${runUrl}`,
"Closing this rolling failure issue."
].join("\n")
});
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: existing.number,
name: "status(needs-manual-review)"
}).catch((error) => {
if (error.status !== 404) {
throw error;
}
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: existing.number,
labels: [recoveredLabel]
});
await github.rest.issues.update({
owner,
repo,
issue_number: existing.number,
state: "closed"
});
core.info(`Closed issue #${existing.number}`);