Sentinel #45
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: Sentinel | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 7 * * 6" | |
| - cron: "0 8 * * 6" | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: sentinel-scan | |
| cancel-in-progress: false | |
| jobs: | |
| scan: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Check Europe/Rome schedule | |
| id: schedule | |
| if: github.event_name == 'schedule' | |
| run: | | |
| # Non guardare l'ora a runtime: GitHub ritarda i job schedulati anche | |
| # di oltre un'ora, quindi l'ora di Roma è quasi sempre gia' scivolata | |
| # oltre le 09:00. Decidi in base a QUALE cron ha triggerato + fuso DST. | |
| # 09:00 Europe/Rome = 07:00 UTC in estate (+0200), 08:00 UTC in inverno (+0100). | |
| offset=$(TZ=Europe/Rome date +%z) | |
| sched="${{ github.event.schedule }}" | |
| should_run=false | |
| if [ "$offset" = "+0200" ] && [ "$sched" = "0 7 * * 6" ]; then should_run=true; fi | |
| if [ "$offset" = "+0100" ] && [ "$sched" = "0 8 * * 6" ]; then should_run=true; fi | |
| if [ "$should_run" != "true" ]; then | |
| echo "Cron $sched con offset $offset fuori dalla finestra 09:00 Europe/Rome, skip." | |
| fi | |
| echo "should_run=$should_run" >> "$GITHUB_OUTPUT" | |
| - name: Checkout | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| uses: actions/checkout@v7 | |
| - name: Restore application outputs | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: | | |
| output_branch="sentinel-outputs" | |
| output_remote_ref="refs/heads/$output_branch" | |
| output_ref="refs/remotes/origin/$output_branch" | |
| git fetch origin "$output_remote_ref:$output_ref" | |
| git restore --source="$output_ref" --worktree -- data snapshots reports | |
| - name: Setup Node | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Bootstrap npm 12 | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: npm install --global npm@12.0.2 | |
| - name: Install | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: npm ci --ignore-scripts | |
| - name: Enable esbuild | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: npm rebuild esbuild | |
| - name: Gate completo | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: npm run check | |
| - name: Apply GitHub email secret fallback | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| env: | |
| GITHUB_SENTINEL_EMAIL_TO: ${{ secrets.SENTINEL_EMAIL_TO }} | |
| GITHUB_SENTINEL_EMAIL_FROM: ${{ secrets.SENTINEL_EMAIL_FROM }} | |
| GITHUB_SENTINEL_GMAIL_USER: ${{ secrets.SENTINEL_GMAIL_USER }} | |
| GITHUB_SENTINEL_GMAIL_APP_PASSWORD: ${{ secrets.SENTINEL_GMAIL_APP_PASSWORD }} | |
| GITHUB_SENTINEL_ICLOUD_USER: ${{ secrets.SENTINEL_ICLOUD_USER }} | |
| GITHUB_SENTINEL_ICLOUD_APP_PASSWORD: ${{ secrets.SENTINEL_ICLOUD_APP_PASSWORD }} | |
| GITHUB_BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} | |
| run: | | |
| for name in \ | |
| SENTINEL_EMAIL_TO \ | |
| SENTINEL_EMAIL_FROM \ | |
| SENTINEL_GMAIL_USER \ | |
| SENTINEL_GMAIL_APP_PASSWORD \ | |
| SENTINEL_ICLOUD_USER \ | |
| SENTINEL_ICLOUD_APP_PASSWORD \ | |
| BLOB_READ_WRITE_TOKEN | |
| do | |
| fallback_name="GITHUB_${name}" | |
| if [ -z "${!name:-}" ] && [ -n "${!fallback_name:-}" ]; then | |
| echo "${name}=${!fallback_name}" >> "$GITHUB_ENV" | |
| fi | |
| done | |
| - name: Check email secrets | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: | | |
| missing=0 | |
| for name in \ | |
| SENTINEL_EMAIL_TO \ | |
| SENTINEL_EMAIL_FROM \ | |
| SENTINEL_GMAIL_USER \ | |
| SENTINEL_GMAIL_APP_PASSWORD | |
| do | |
| if [ -z "${!name}" ]; then | |
| echo "::error::Secret mancante: $name" | |
| missing=1 | |
| fi | |
| done | |
| if [ "$missing" -ne 0 ]; then | |
| echo "Configura i secret Gmail prima di avviare la scansione Sentinel." | |
| exit 1 | |
| fi | |
| - name: Scan | |
| id: scan | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: | | |
| set +e | |
| npm run sentinel -- scan | |
| status=$? | |
| echo "exit_code=$status" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| - name: Generate dashboard | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: npm run sentinel -- dashboard | |
| - name: Publish dashboard to Vercel Blob | |
| # La dashboard live legge da Vercel Blob: senza questo step resta ferma | |
| # anche se lo scan gira. Salta con notice se il token non e' disponibile. | |
| if: ${{ github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' }} | |
| run: | | |
| if [ -z "${BLOB_READ_WRITE_TOKEN:-}" ]; then | |
| echo "::warning::BLOB_READ_WRITE_TOKEN assente: dashboard live non aggiornata." | |
| exit 0 | |
| fi | |
| npm run sentinel -- publish-dashboard | |
| - name: Commit application outputs | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| mkdir -p data snapshots reports | |
| output_branch="sentinel-outputs" | |
| output_remote_ref="refs/heads/$output_branch" | |
| output_ref="refs/remotes/origin/$output_branch" | |
| git fetch origin "$output_remote_ref:$output_ref" | |
| output_dir="$(mktemp -d)" | |
| output_index="$output_dir/index" | |
| trap 'unlink "$output_index" 2>/dev/null || true; rmdir "$output_dir" 2>/dev/null || true' EXIT | |
| GIT_INDEX_FILE="$output_index" git read-tree --empty | |
| GIT_INDEX_FILE="$output_index" git add -- data snapshots reports | |
| output_tree="$(GIT_INDEX_FILE="$output_index" git write-tree)" | |
| previous_commit="$(git rev-parse "$output_ref")" | |
| previous_tree="$(git rev-parse "$output_ref^{tree}")" | |
| if [ "$output_tree" = "$previous_tree" ]; then | |
| echo "Nessun output da committare." | |
| exit 0 | |
| fi | |
| output_commit="$(printf '%s\n' 'chore: update sentinel outputs' | git commit-tree "$output_tree" -p "$previous_commit")" | |
| git push origin "${output_commit}:refs/heads/${output_branch}" | |
| - name: Fail on scan errors | |
| if: github.event_name != 'schedule' || steps.schedule.outputs.should_run == 'true' | |
| run: | | |
| if [ "${{ steps.scan.outputs.exit_code }}" != "0" ]; then | |
| echo "Scan conclusa con exit code ${{ steps.scan.outputs.exit_code }}." | |
| exit 1 | |
| fi |