Skip to content

Auto-Update Upstream Checksums #657

Auto-Update Upstream Checksums

Auto-Update Upstream Checksums #657

name: Auto-Update Upstream Checksums
on:
schedule:
- cron: "0 */2 * * *" # Every 2 hours (more frequent for faster updates)
workflow_dispatch: # Manual trigger for testing
push:
paths:
- 'scripts/lib/security.sh' # Re-run if security.sh changes
repository_dispatch:
types: [upstream-changed] # Triggered by our other repos via webhook
concurrency:
group: checksum-monitor
cancel-in-progress: false # Let running job complete, queue new ones
jobs:
auto-update-checksums:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write # For creating issues on failure
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Log repository dispatch payload
if: github.event_name == 'repository_dispatch'
run: |
echo "Repository dispatch payload:"
echo '${{ toJson(github.event.client_payload) }}'
- name: Verify current checksums
id: verify
run: |
chmod +x ./scripts/lib/security.sh
echo "🔍 Verifying checksums against upstream..."
# Note: --verify returns exit 1 when mismatches found, which is expected
# We capture the JSON output regardless of exit code
# stderr is separate to avoid corrupting JSON with any warning messages
./scripts/lib/security.sh --verify --json > current.json 2>verify-errors.log || true
# Check if JSON is valid
if ! jq empty current.json 2>/dev/null; then
echo "error=invalid_json" >> $GITHUB_OUTPUT
echo "❌ Failed to parse verification output"
cat current.json
exit 1
fi
# Extract counts from JSON
mismatches=$(jq '.mismatches | length' current.json)
errors=$(jq '.errors | length' current.json)
total_issues=$((mismatches + errors))
echo "mismatches=$mismatches" >> $GITHUB_OUTPUT
echo "errors=$errors" >> $GITHUB_OUTPUT
echo "total_issues=$total_issues" >> $GITHUB_OUTPUT
# Categorize changed tools
TRUSTED_CHANGED=""
EXTERNAL_CHANGED=""
if [[ "$mismatches" -gt 0 ]]; then
while IFS= read -r name; do
url=$(jq -r --arg n "$name" '.mismatches[] | select(.name==$n) | .url // empty' current.json)
if [[ "$url" == *"Dicklesworthstone"* ]]; then
TRUSTED_CHANGED="${TRUSTED_CHANGED}${name},"
else
EXTERNAL_CHANGED="${EXTERNAL_CHANGED}${name},"
fi
done < <(jq -r '.mismatches[].name' current.json)
fi
# Remove trailing commas
TRUSTED_CHANGED="${TRUSTED_CHANGED%,}"
EXTERNAL_CHANGED="${EXTERNAL_CHANGED%,}"
echo "trusted_changed=$TRUSTED_CHANGED" >> $GITHUB_OUTPUT
echo "external_changed=$EXTERNAL_CHANGED" >> $GITHUB_OUTPUT
if [[ "$total_issues" -gt 0 ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo ""
echo "📋 Changed tools:"
jq -r '.mismatches[] | " - \(.name)"' current.json 2>/dev/null || true
if [[ -n "$TRUSTED_CHANGED" ]]; then
echo ""
echo " 🏠 Trusted (Dicklesworthstone): $TRUSTED_CHANGED"
fi
if [[ -n "$EXTERNAL_CHANGED" ]]; then
echo ""
echo " 🌐 External: $EXTERNAL_CHANGED"
fi
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "✅ All checksums match - no update needed"
fi
- name: Generate updated checksums
if: steps.verify.outputs.changed == 'true'
run: |
./scripts/lib/security.sh --update-checksums > checksums.yaml.new
mv checksums.yaml.new checksums.yaml
- name: Commit and push updates
if: steps.verify.outputs.changed == 'true'
id: commit
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Check if checksums.yaml actually changed (handles race condition)
if git diff --quiet checksums.yaml; then
echo "checksums.yaml unchanged (possibly already updated by another run)"
echo "committed=false" >> $GITHUB_OUTPUT
exit 0
fi
# Generate commit message with details
CHANGED_TOOLS=$(jq -r '.mismatches[].name' current.json 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
git add checksums.yaml
git commit -m "chore(security): auto-update checksums for ${CHANGED_TOOLS}" \
-m "Updated checksums for upstream installer scripts that have changed." \
-m "" \
-m "Changed tools: ${CHANGED_TOOLS}" \
-m "Trusted: ${{ steps.verify.outputs.trusted_changed || 'none' }}" \
-m "External: ${{ steps.verify.outputs.external_changed || 'none' }}" \
-m "" \
-m "🤖 Generated by checksum-monitor workflow"
# Pull any changes that happened while we were running (rebase our commit on top)
git pull --rebase origin main || {
echo "Rebase failed - likely a conflict. Will retry on next scheduled run."
echo "committed=false" >> $GITHUB_OUTPUT
exit 0
}
git push
echo "committed=true" >> $GITHUB_OUTPUT
echo "✅ Successfully pushed checksum updates"
- name: Create issue for external changes (security visibility)
if: steps.verify.outputs.external_changed != '' && steps.commit.outputs.committed == 'true'
uses: actions/github-script@v7
with:
script: |
const external = '${{ steps.verify.outputs.external_changed }}';
const tools = external.split(',').filter(t => t);
if (tools.length === 0) return;
// Check for existing open issue
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'security,checksum-update'
});
const existingIssue = issues.find(i => i.title.includes('External installer checksums'));
// Build body with proper indentation for YAML literal block
const toolsList = tools.map(t => '- `' + t + '`').join('\n');
const reviewList = tools.map(t => '- [ ] Review ' + t + ' changes').join('\n');
const body = [
'## External Installer Checksums Updated',
'',
'The following **external** (non-Dicklesworthstone) installer scripts have changed:',
'',
toolsList,
'',
'### Action Required',
'These checksums were automatically updated. Please verify the upstream changes are legitimate:',
'',
reviewList,
'',
'### Why this matters',
'External installers (ohmyzsh, rustup, bun, etc.) could be compromised. While auto-updating keeps users unblocked, a quick review ensures we\'re not distributing malicious code.',
'',
'---',
'🤖 Auto-generated by checksum-monitor workflow'
].join('\n');
if (existingIssue) {
// Update existing issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
body: '### Additional changes detected\n\n' + body
});
} else {
// Create new issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '🔐 External installer checksums updated - review recommended',
body: body,
labels: ['security', 'checksum-update']
});
}
- name: Summary
if: always()
run: |
echo "## Checksum Auto-Update Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Total Checked | $(jq '.total // 0' current.json 2>/dev/null || echo 0) |" >> $GITHUB_STEP_SUMMARY
echo "| Mismatches | ${{ steps.verify.outputs.mismatches || 0 }} |" >> $GITHUB_STEP_SUMMARY
echo "| Errors | ${{ steps.verify.outputs.errors || 0 }} |" >> $GITHUB_STEP_SUMMARY
echo "| Trusted Changed | ${{ steps.verify.outputs.trusted_changed || 'none' }} |" >> $GITHUB_STEP_SUMMARY
echo "| External Changed | ${{ steps.verify.outputs.external_changed || 'none' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ steps.verify.outputs.changed }}" == "true" ]]; then
if [[ "${{ steps.commit.outputs.committed }}" == "true" ]]; then
echo "✅ **Checksums automatically updated and committed to main**" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ **Changes detected but commit skipped (race condition or conflict)**" >> $GITHUB_STEP_SUMMARY
fi
else
echo "✅ **All checksums match upstream - no action needed**" >> $GITHUB_STEP_SUMMARY
fi