Skip to content

feat(state): add installation progress checklist #6

feat(state): add installation progress checklist

feat(state): add installation progress checklist #6

# Installer Notification Receiver Workflow
# Receives repository_dispatch events from /dp project repos when installers change
# Independently verifies checksums and creates PRs with security scan results
#
# Related: bd-19y9.2.1
name: Installer Notification Receiver
on:
repository_dispatch:
types: [installer-updated, installer-removed, installer-added]
workflow_dispatch:
inputs:
tool_name:
description: 'Tool name to manually verify'
required: true
type: string
dry_run:
description: 'Skip PR creation'
required: false
default: false
type: boolean
concurrency:
group: installer-updates
cancel-in-progress: false # Queue updates, don't cancel
env:
CHECKSUMS_FILE: checksums.yaml
AUDIT_LOG_FILE: .github/audit/installer-updates.jsonl
permissions:
contents: write
pull-requests: write
issues: write
jobs:
validate-dispatch:
runs-on: ubuntu-latest
outputs:
tool_name: ${{ steps.validate.outputs.tool_name }}
event_type: ${{ steps.validate.outputs.event_type }}
new_sha256: ${{ steps.validate.outputs.new_sha256 }}
old_sha256: ${{ steps.validate.outputs.old_sha256 }}
source_repo: ${{ steps.validate.outputs.source_repo }}
source_commit: ${{ steps.validate.outputs.source_commit }}
installer_url: ${{ steps.validate.outputs.installer_url }}
is_valid: ${{ steps.validate.outputs.is_valid }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
- name: Validate payload and extract fields
id: validate
run: |
set -euo pipefail
echo "::group::Payload Validation"
# Determine event source
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TOOL_NAME="${{ github.event.inputs.tool_name }}"
EVENT_TYPE="manual-verify"
NEW_SHA256=""
OLD_SHA256=""
SOURCE_REPO="manual"
SOURCE_COMMIT="manual"
else
# Extract from repository_dispatch payload
TOOL_NAME="${{ github.event.client_payload.tool }}"
EVENT_TYPE="${{ github.event.action }}"
NEW_SHA256="${{ github.event.client_payload.new_sha256 }}"
OLD_SHA256="${{ github.event.client_payload.old_sha256 }}"
SOURCE_REPO="${{ github.event.client_payload.repo }}"
SOURCE_COMMIT="${{ github.event.client_payload.commit }}"
fi
echo "Tool: $TOOL_NAME"
echo "Event: $EVENT_TYPE"
echo "Source: $SOURCE_REPO @ $SOURCE_COMMIT"
# Validate tool name format (alphanumeric, underscore, hyphen only)
if ! [[ "$TOOL_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "::error::Invalid tool name format: $TOOL_NAME"
echo "is_valid=false" >> $GITHUB_OUTPUT
exit 0
fi
# Check if tool exists in checksums.yaml (for update/remove)
if [[ "$EVENT_TYPE" != "installer-added" ]]; then
if ! yq e ".installers.$TOOL_NAME" $CHECKSUMS_FILE | grep -q "url:"; then
echo "::error::Unknown tool: $TOOL_NAME (not in checksums.yaml)"
echo "is_valid=false" >> $GITHUB_OUTPUT
exit 0
fi
fi
# Get URL from checksums.yaml (or from payload for new tools)
if [[ "$EVENT_TYPE" == "installer-added" ]]; then
INSTALLER_URL="${{ github.event.client_payload.url }}"
else
INSTALLER_URL=$(yq e ".installers.$TOOL_NAME.url" $CHECKSUMS_FILE)
fi
echo "URL: $INSTALLER_URL"
# Validate URL is from trusted domain
TRUSTED_DOMAINS="github.com githubusercontent.com raw.githubusercontent.com claude.ai astral.sh bun.sh setup.atuin.sh sh.rustup.rs jeffreysprompts.com"
URL_HOST=$(echo "$INSTALLER_URL" | sed -E 's|^https?://([^/]+)/.*|\1|')
IS_TRUSTED=false
for domain in $TRUSTED_DOMAINS; do
if [[ "$URL_HOST" == "$domain" ]] || [[ "$URL_HOST" == *".$domain" ]]; then
IS_TRUSTED=true
break
fi
done
if [[ "$IS_TRUSTED" != "true" ]]; then
echo "::error::Untrusted URL domain: $URL_HOST"
echo "is_valid=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "::endgroup::"
# Set outputs
echo "tool_name=$TOOL_NAME" >> $GITHUB_OUTPUT
echo "event_type=$EVENT_TYPE" >> $GITHUB_OUTPUT
echo "new_sha256=$NEW_SHA256" >> $GITHUB_OUTPUT
echo "old_sha256=$OLD_SHA256" >> $GITHUB_OUTPUT
echo "source_repo=$SOURCE_REPO" >> $GITHUB_OUTPUT
echo "source_commit=$SOURCE_COMMIT" >> $GITHUB_OUTPUT
echo "installer_url=$INSTALLER_URL" >> $GITHUB_OUTPUT
echo "is_valid=true" >> $GITHUB_OUTPUT
- name: Append to audit log
if: always()
run: |
mkdir -p .github/audit
echo '{
"timestamp": "'$(date -Iseconds)'",
"event_type": "${{ steps.validate.outputs.event_type }}",
"tool": "${{ steps.validate.outputs.tool_name }}",
"source_repo": "${{ steps.validate.outputs.source_repo }}",
"source_commit": "${{ steps.validate.outputs.source_commit }}",
"is_valid": ${{ steps.validate.outputs.is_valid }},
"workflow_run": "${{ github.run_id }}"
}' >> $AUDIT_LOG_FILE
verify-checksum:
needs: validate-dispatch
if: needs.validate-dispatch.outputs.is_valid == 'true' && needs.validate-dispatch.outputs.event_type != 'installer-removed'
runs-on: ubuntu-latest
outputs:
computed_sha256: ${{ steps.compute.outputs.sha256 }}
checksum_changed: ${{ steps.compare.outputs.changed }}
current_sha256: ${{ steps.compare.outputs.current }}
steps:
- uses: actions/checkout@v4
- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
- name: Download and compute SHA256
id: compute
run: |
set -euo pipefail
INSTALLER_URL="${{ needs.validate-dispatch.outputs.installer_url }}"
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
echo "::group::Downloading installer from $INSTALLER_URL"
# Download with timeout and size limit (10MB max)
curl -fsSL --max-time 60 --max-filesize 10485760 \
-o /tmp/installer.sh "$INSTALLER_URL"
# Compute SHA256
COMPUTED_SHA256=$(sha256sum /tmp/installer.sh | cut -d' ' -f1)
echo "Computed SHA256: $COMPUTED_SHA256"
echo "::endgroup::"
echo "sha256=$COMPUTED_SHA256" >> $GITHUB_OUTPUT
- name: Compare with current checksum
id: compare
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
COMPUTED="${{ steps.compute.outputs.sha256 }}"
# Get current checksum from file
CURRENT=$(yq e ".installers.$TOOL_NAME.sha256" $CHECKSUMS_FILE 2>/dev/null || echo "none")
echo "Current: $CURRENT"
echo "Computed: $COMPUTED"
if [[ "$CURRENT" == "$COMPUTED" ]]; then
echo "Checksums match - no update needed"
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "Checksums differ - update required"
echo "changed=true" >> $GITHUB_OUTPUT
fi
echo "current=$CURRENT" >> $GITHUB_OUTPUT
security-scan:
needs: [validate-dispatch, verify-checksum]
if: needs.verify-checksum.outputs.checksum_changed == 'true'
runs-on: ubuntu-latest
outputs:
scan_passed: ${{ steps.scan.outputs.passed }}
scan_warnings: ${{ steps.scan.outputs.warnings }}
steps:
- name: Download installer for scanning
run: |
curl -fsSL --max-time 60 \
-o /tmp/installer.sh "${{ needs.validate-dispatch.outputs.installer_url }}"
- name: Security scan
id: scan
run: |
set -euo pipefail
echo "::group::Security Scan Results"
WARNINGS=""
PASSED=true
# Pattern 1: Curl piped directly to bash from non-trusted URLs
if grep -E 'curl.*\|.*bash' /tmp/installer.sh | grep -vE '(github\.com|githubusercontent\.com|astral\.sh)'; then
WARNINGS="$WARNINGS\n- WARNING: curl piped to bash from untrusted URL"
fi
# Pattern 2: wget piped to shell
if grep -E 'wget.*\|.*(bash|sh)' /tmp/installer.sh; then
WARNINGS="$WARNINGS\n- WARNING: wget piped to shell"
fi
# Pattern 3: eval with user input
if grep -E 'eval.*\$' /tmp/installer.sh; then
WARNINGS="$WARNINGS\n- WARNING: eval with variable expansion detected"
fi
# Pattern 4: Downloading to /usr/bin or /usr/local/bin without verification
if grep -E '(curl|wget).*(/usr/bin|/usr/local/bin)' /tmp/installer.sh | grep -v 'sha256\|checksum'; then
WARNINGS="$WARNINGS\n- WARNING: Direct download to system bin without checksum"
fi
# Pattern 5: sudo with stdin redirect
if grep -E 'sudo.*<<<' /tmp/installer.sh; then
WARNINGS="$WARNINGS\n- WARNING: sudo with heredoc/stdin (potential injection)"
fi
# Pattern 6: Overly permissive chmod
if grep -E 'chmod.*777' /tmp/installer.sh; then
WARNINGS="$WARNINGS\n- WARNING: chmod 777 detected (overly permissive)"
fi
# Pattern 7: rm -rf with variables
if grep -E 'rm\s+-rf\s+.*\$' /tmp/installer.sh; then
WARNINGS="$WARNINGS\n- WARNING: rm -rf with variable (potential path traversal)"
fi
echo "::endgroup::"
if [[ -n "$WARNINGS" ]]; then
echo "Security warnings found:"
echo -e "$WARNINGS"
echo "warnings<<EOF" >> $GITHUB_OUTPUT
echo -e "$WARNINGS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "No security warnings"
echo "warnings=" >> $GITHUB_OUTPUT
fi
# Security scan is advisory, always pass
echo "passed=true" >> $GITHUB_OUTPUT
update-checksums:
needs: [validate-dispatch, verify-checksum, security-scan]
if: |
always() &&
needs.verify-checksum.outputs.checksum_changed == 'true' &&
(needs.security-scan.result == 'success' || needs.security-scan.result == 'skipped')
runs-on: ubuntu-latest
outputs:
branch_name: ${{ steps.branch.outputs.name }}
pr_body: ${{ steps.pr-body.outputs.body }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
- name: Create branch
id: branch
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
SHORT_SHA="${{ needs.verify-checksum.outputs.computed_sha256 }}"
SHORT_SHA=${SHORT_SHA:0:8}
BRANCH_NAME="auto/update-${TOOL_NAME}-checksum-${SHORT_SHA}"
echo "name=$BRANCH_NAME" >> $GITHUB_OUTPUT
git checkout -b "$BRANCH_NAME"
- name: Update checksums.yaml
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
NEW_SHA256="${{ needs.verify-checksum.outputs.computed_sha256 }}"
EVENT_TYPE="${{ needs.validate-dispatch.outputs.event_type }}"
if [[ "$EVENT_TYPE" == "installer-added" ]]; then
# Add new entry
INSTALLER_URL="${{ needs.validate-dispatch.outputs.installer_url }}"
yq e -i ".installers.$TOOL_NAME.url = \"$INSTALLER_URL\"" $CHECKSUMS_FILE
yq e -i ".installers.$TOOL_NAME.sha256 = \"$NEW_SHA256\"" $CHECKSUMS_FILE
else
# Update existing entry
yq e -i ".installers.$TOOL_NAME.sha256 = \"$NEW_SHA256\"" $CHECKSUMS_FILE
fi
# Update timestamp comment
sed -i "1s/.*/# checksums.yaml - Auto-updated $(date -Iseconds)/" $CHECKSUMS_FILE
- name: Run tests
run: |
# Verify YAML is valid
yq e '.' $CHECKSUMS_FILE > /dev/null
# Run project tests if available
if [[ -f "package.json" ]] && grep -q "test" package.json; then
npm test || echo "Tests failed but continuing"
fi
- name: Generate PR body
id: pr-body
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
OLD_SHA="${{ needs.verify-checksum.outputs.current_sha256 }}"
NEW_SHA="${{ needs.verify-checksum.outputs.computed_sha256 }}"
SOURCE_REPO="${{ needs.validate-dispatch.outputs.source_repo }}"
SOURCE_COMMIT="${{ needs.validate-dispatch.outputs.source_commit }}"
WARNINGS="${{ needs.security-scan.outputs.scan_warnings }}"
cat << EOF > /tmp/pr-body.md
## Automated Checksum Update: $TOOL_NAME
This PR was automatically generated by the installer notification system.
### Changes
- **Tool**: \`$TOOL_NAME\`
- **Old SHA256**: \`$OLD_SHA\`
- **New SHA256**: \`$NEW_SHA\`
### Source
- **Repository**: $SOURCE_REPO
- **Commit**: $SOURCE_COMMIT
- **Upstream Link**: https://github.com/$SOURCE_REPO/commit/$SOURCE_COMMIT
### Security Scan
EOF
if [[ -n "$WARNINGS" ]]; then
echo "**Warnings detected:**" >> /tmp/pr-body.md
echo "$WARNINGS" >> /tmp/pr-body.md
echo "" >> /tmp/pr-body.md
echo "Please review the installer changes carefully before merging." >> /tmp/pr-body.md
else
echo "No security warnings detected." >> /tmp/pr-body.md
fi
cat << 'EOF2' >> /tmp/pr-body.md
### Verification Steps
- [x] Downloaded installer independently
- [x] Computed SHA256 locally
- [x] Ran security pattern scan
- [x] Updated checksums.yaml
### Manual Review Checklist
- [ ] Verified upstream commit is legitimate
- [ ] Reviewed installer diff for suspicious changes
- [ ] Confirmed tool functionality still works
---
*Generated by ACFS Installer Notification Receiver*
EOF2
# Output as multiline
echo "body<<PREOF" >> $GITHUB_OUTPUT
cat /tmp/pr-body.md >> $GITHUB_OUTPUT
echo "PREOF" >> $GITHUB_OUTPUT
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add $CHECKSUMS_FILE
git commit -m "chore(checksums): Update ${{ needs.validate-dispatch.outputs.tool_name }} to ${{ needs.verify-checksum.outputs.computed_sha256 }}"
git push origin "${{ steps.branch.outputs.name }}"
create-pr:
needs: [validate-dispatch, verify-checksum, security-scan, update-checksums]
if: needs.update-checksums.result == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Pull Request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
BRANCH_NAME="${{ needs.update-checksums.outputs.branch_name }}"
SHORT_SHA="${{ needs.verify-checksum.outputs.computed_sha256 }}"
SHORT_SHA=${SHORT_SHA:0:8}
# Create PR
gh pr create \
--base main \
--head "$BRANCH_NAME" \
--title "chore(checksums): Update $TOOL_NAME to $SHORT_SHA" \
--body "${{ needs.update-checksums.outputs.pr_body }}" \
--label "automated,checksum-update,needs-review"
- name: Add comment to tracking issue (optional)
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Find tracking issue if exists
ISSUE=$(gh issue list --label "installer-tracking" --state open --limit 1 --json number -q '.[0].number')
if [[ -n "$ISSUE" ]]; then
gh issue comment "$ISSUE" --body "Checksum update received for **${{ needs.validate-dispatch.outputs.tool_name }}** from ${{ needs.validate-dispatch.outputs.source_repo }}"
fi
handle-removal:
needs: validate-dispatch
if: needs.validate-dispatch.outputs.is_valid == 'true' && needs.validate-dispatch.outputs.event_type == 'installer-removed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
- name: Remove tool from checksums.yaml
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
# Create branch
BRANCH_NAME="auto/remove-${TOOL_NAME}"
git checkout -b "$BRANCH_NAME"
# Remove entry
yq e -i "del(.installers.$TOOL_NAME)" $CHECKSUMS_FILE
# Commit and push
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add $CHECKSUMS_FILE
git commit -m "chore(checksums): Remove $TOOL_NAME"
git push origin "$BRANCH_NAME"
- name: Create removal PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TOOL_NAME="${{ needs.validate-dispatch.outputs.tool_name }}"
SOURCE_REPO="${{ needs.validate-dispatch.outputs.source_repo }}"
gh pr create \
--base main \
--head "auto/remove-${TOOL_NAME}" \
--title "chore(checksums): Remove $TOOL_NAME" \
--body "## Tool Removal: $TOOL_NAME
This PR removes **$TOOL_NAME** from checksums.yaml.

Check failure on line 500 in .github/workflows/installer-notification-receiver.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/installer-notification-receiver.yml

Invalid workflow file

You have an error in your yaml syntax on line 500
### Reason
The upstream repository ($SOURCE_REPO) has indicated the installer should be removed.
### Checklist
- [ ] Confirmed tool is no longer needed
- [ ] Verified this is intentional, not an error
---
*Generated by ACFS Installer Notification Receiver*" \
--label "automated,checksum-removal,needs-review"