Repository Backup #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Repository Backup | |
| on: | |
| schedule: | |
| # Run weekly on Sunday at 2 AM UTC | |
| - cron: '0 2 * * 0' | |
| workflow_dispatch: | |
| inputs: | |
| organization: | |
| description: 'GitHub organization to backup' | |
| required: false | |
| default: 'quantecon' | |
| force: | |
| description: 'Force backup even if already exists today' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| backup: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write # Required for OIDC authentication | |
| contents: read | |
| issues: write # Required for creating/updating issues | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| # Recommended: Use OIDC authentication (no long-lived credentials) | |
| # Requires AWS IAM Identity Provider and Role configured for GitHub Actions | |
| # See README.md for setup instructions | |
| - name: Configure AWS credentials (OIDC) | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ secrets.AWS_ROLE_ARN }} | |
| aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} | |
| - name: Run backup | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.REPO_BACKUP_TOKEN || secrets.GITHUB_TOKEN }} | |
| run: | | |
| if [ "${{ github.event.inputs.force }}" = "true" ]; then | |
| python -m src.main --config config.yml --task backup --force | |
| else | |
| python -m src.main --config config.yml --task backup | |
| fi | |
| - name: Generate backup report | |
| id: report | |
| if: always() | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.REPO_BACKUP_TOKEN || secrets.GITHUB_TOKEN }} | |
| run: | | |
| python -m src.main --config config.yml --task report 2>&1 | tee /tmp/report.txt | |
| echo "report<<EOF" >> $GITHUB_OUTPUT | |
| cat /tmp/report.txt >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Create or update monthly backup report issue | |
| if: always() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; | |
| const now = new Date(); | |
| const date = now.toISOString().split('T')[0]; | |
| const dayOfMonth = now.getUTCDate(); | |
| const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', | |
| 'July', 'August', 'September', 'October', 'November', 'December']; | |
| const monthName = monthNames[now.getUTCMonth()]; | |
| const year = now.getUTCFullYear(); | |
| const issueTitle = `Backup Reports - ${monthName} ${year}`; | |
| const jobStatus = '${{ job.status }}'; | |
| const isFailure = jobStatus === 'failure'; | |
| // Check if this is the last week of the month (day >= 25) | |
| const isEndOfMonth = dayOfMonth >= 25; | |
| // Build comment with collapsed report details | |
| let comment = `### ${date} - ${jobStatus === 'success' ? '✅ Success' : '❌ Failed'}\n\n`; | |
| comment += `**Workflow Run:** [View Details](${runUrl})\n\n`; | |
| comment += `<details>\n<summary>📋 Report Output</summary>\n\n`; | |
| comment += '```\n'; | |
| comment += `${{ steps.report.outputs.report }}`; | |
| comment += '\n```\n\n'; | |
| comment += `</details>\n`; | |
| if (isFailure) { | |
| comment += `\n⚠️ @mmcky - backup failed, please investigate.\n`; | |
| } | |
| // Add end-of-month review reminder | |
| if (isEndOfMonth && !isFailure) { | |
| comment += `\n---\n📅 **End of month reminder:** @mmcky please review this month's backups and close this issue when done.\n`; | |
| } | |
| // Find existing issue for this month | |
| const issues = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'backup-report' | |
| }); | |
| const existingIssue = issues.data.find(i => i.title === issueTitle); | |
| if (existingIssue) { | |
| // Add comment to existing monthly issue | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: existingIssue.number, | |
| body: comment | |
| }); | |
| console.log(`Added comment to issue #${existingIssue.number}: ${issueTitle}`); | |
| } else { | |
| // Create new monthly issue | |
| let issueBody = `# Backup Reports - ${monthName} ${year}\n\n`; | |
| issueBody += `This issue tracks all backup runs for **${monthName} ${year}**.\n\n`; | |
| issueBody += `## Summary\n\n`; | |
| issueBody += `- **Schedule:** Weekly on Sunday at 2 AM UTC\n`; | |
| issueBody += `- **Organization:** QuantEcon\n`; | |
| issueBody += `- **Storage:** AWS S3\n\n`; | |
| issueBody += `Each backup run will be posted as a comment below with a collapsed report.\n`; | |
| const newIssue = await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: issueTitle, | |
| body: issueBody, | |
| labels: ['backup-report'] | |
| }); | |
| // Add first run as comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: newIssue.data.number, | |
| body: comment | |
| }); | |
| console.log(`Created issue #${newIssue.data.number}: ${issueTitle}`); | |
| } |