Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/new-episode-check.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Check New Episodes

# The checker runs hourly on the VPS (netcup-panel stack/notifier) with its own state file. This
# workflow is a manual fallback only: a live run here uses the repo's older state and would resend
# releases, so it rehearses by default. Turn dry_run off only after copying the VPS state back.
on:
schedule:
# Run every 30 minutes
- cron: '*/30 * * * *'
workflow_dispatch: # Allow manual triggering
workflow_dispatch:
inputs:
dry_run:
description: 'Rehearsal: decide and log, send nothing, save nothing'
type: boolean
default: false
default: true

concurrency:
group: tracked-podcast-rtdb-maintenance
Expand Down
4 changes: 2 additions & 2 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ The local updater fixture accepts `--notes-file` for long UTF-8 sample release n

### Check New Episodes

[`.github/workflows/new-episode-check.yml`](../.github/workflows/new-episode-check.yml) runs approximately every 30 minutes on GitHub Actions. It polls notification registrations independently of catalog sync.
The checker runs **hourly on the VPS** (netcup-panel `stack/notifier`: a systemd timer runs this script from the sync runner's copy, with its own Firebase service account that can only read `tracked_podcasts` and send pushes). GitHub's scheduled runs came every 2–6 hours instead of every 30 minutes. [`.github/workflows/new-episode-check.yml`](../.github/workflows/new-episode-check.yml) is now a manual fallback that rehearses (`dry_run`) by default; a live run there would use the repo's older state file. It polls notification registrations independently of catalog sync.

| What | Where |
| :--- | :--- |
| Script | [`scripts/check-new-episodes.js`](check-new-episodes.js) + [`check-new-episodes-lib.js`](check-new-episodes-lib.js) |
| Who to poll | Firebase RTDB `tracked_podcasts/{podcastIndexId}` (client writes this when **show notifications** are on). Rules allow `title`, `imageUrl`, and optional HTTPS `feedUrl` only — extra children are rejected. |
| Last-notified state | [`scripts/data/episode-tracker.json`](data/episode-tracker.json) (the Action commits this) |
| Last-notified state | `NOTIFIER_STATE_FILE` on the VPS (`/var/lib/boxlore-notifier/episode-tracker.json`, in the daily backup). [`scripts/data/episode-tracker.json`](data/episode-tracker.json) is the last state from GitHub Actions |
| Tests | `npm ci` then `npm run test:check-new-episodes` from `scripts/` (the Check New Episodes workflow runs the same script after `npm ci`) |

Release selection and recovery:
Expand Down
4 changes: 3 additions & 1 deletion scripts/check-new-episodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ async function run() {
}

// 3. Read state file (local json)
const statePath = path.join(__dirname, 'data/episode-tracker.json');
// On the VPS the state lives outside the code checkout (NOTIFIER_STATE_FILE); the repo copy is
// the last state from when the job ran on GitHub Actions.
const statePath = process.env.NOTIFIER_STATE_FILE || path.join(__dirname, 'data/episode-tracker.json');

// Ensure the data directory exists
const dataDir = path.dirname(statePath);
Expand Down
Loading