This repo turns an article RSS feed into a podcast feed:
- Read source RSS entries.
- Generate MP3 audio with Google Cloud Text-to-Speech.
- Upload episode audio to Internet Archive.
- Write podcast RSS XML into
public/feeds/<slug>.xml. - Deploy
public/to Cloudflare Pages.
The runtime entrypoint is still python run_feed.py <feed_slug>.
The public-safe GitHub Actions model is:
- one repo
- one workflow file per pipeline under
.github/workflows/ - one GitHub environment per pipeline
- scheduled workflows run from the default branch
- no reusable workflows
- no per-pipeline branch
- no per-pipeline repo
Shared Google values live once at the repository level as GitHub repository variables:
GCP_PROJECT_IDGCP_PROJECT_NUMBERGCP_WIF_POOL_IDGCP_WIF_PROVIDER_ID
Each pipeline gets its own GitHub environment, for example geektime-he or eu-startups-en.
Each environment contains variables:
GCP_SERVICE_ACCOUNT_EMAILCLOUDFLARE_ACCOUNT_IDCF_PAGES_PROJECTCF_KV_NAMESPACE_IDIA_ID_PREFIXPODCAST_MAX_RETRY_ATTEMPTS
Each environment contains secrets:
CLOUDFLARE_API_TOKENIA_ACCESS_KEYIA_SECRET_KEY
If failure email is enabled later, its SMTP or API credentials should also be environment secrets.
Generated workflow files are safe to commit because they reference only GitHub vars and secrets:
${{ vars.GCP_PROJECT_ID }}${{ vars.GCP_PROJECT_NUMBER }}${{ vars.GCP_WIF_POOL_ID }}${{ vars.GCP_WIF_PROVIDER_ID }}${{ vars.GCP_SERVICE_ACCOUNT_EMAIL }}${{ vars.CF_PAGES_PROJECT }}${{ vars.CF_KV_NAMESPACE_ID }}${{ vars.PODCAST_MAX_RETRY_ATTEMPTS }}${{ secrets.CLOUDFLARE_API_TOKEN }}${{ secrets.IA_ACCESS_KEY }}${{ secrets.IA_SECRET_KEY }}
Local mode is still supported and unchanged in spirit:
- root
.envfor local Cloudflare and Internet Archive values - local Google credentials file referenced by
GOOGLE_APPLICATION_CREDENTIALS - per-feed env file under
configs/<feed>.env - optional local-only pipeline config overlays under
pipelines/*.local.yaml - optional local-only shared Google setup config at
pipelines/shared.yaml
pipelines/shared.yaml is local-only and ignored. It is for setup and preflight tooling that talks to GCP directly through gcloud. It is not needed by committed GitHub workflow files.
See docs/github-actions-oidc.md for the detailed GitHub setup flow.
Committed pipeline configs are intentionally public-safe and contain only:
- the pipeline id
- the feed slug passed to
run_feed.py - the per-feed env file
- the workflow file path
- the branch ref
- the GitHub environment name
- the schedule
- optional failure email routing metadata
Examples:
The loader also supports optional local-only overlays:
pipelines/<pipeline-id>.local.yamlpipelines/shared.local.yaml
Those overlays are merged on top of the tracked file when present.
The intended multi-pipeline model is:
- one shared GCP project
- one shared Workload Identity Pool
- one shared Workload Identity Provider
- one dedicated service account per pipeline
- one GitHub environment per pipeline
- one workflow file per pipeline
- one schedule per pipeline
Example workflow layout:
.github/workflows/
geektime-he.yml
eu-startups-en.yml
whatever-next.yml
Adding a second pipeline does not require a new branch or a new repository.
- Create a Python environment and install dependencies.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt- Install the pinned Node dependency for Wrangler.
npm ci- Create local
.env.
cp .env.example .env-
Create or edit
configs/<feed>.env. -
If you want to use the GCP setup tooling locally, create local-only
pipelines/shared.yamlfrom pipelines/shared.example.yaml. -
Run local preflight.
python -m tools.preflight local --pipeline <pipeline-id>- Run the feed.
python run_feed.py <feed_slug>- Authenticate the operator CLIs.
gh auth login
gcloud auth login-
If you want to run the local GCP setup scripts or the GitHub variable setup helper, create local-only
pipelines/shared.yaml. -
Run GitHub-mode preflight.
python -m tools.preflight github --pipeline <pipeline-id>- Create or reconcile the shared GCP OIDC resources.
scripts/setup-gcp-oidc-shared.sh --pipeline <pipeline-id>- Create or reconcile the dedicated pipeline service account.
scripts/setup-gcp-pipeline-sa.sh --pipeline <pipeline-id>- Create or reconcile the shared GitHub repository variables and the pipeline environment variables.
scripts/setup-gh-environment.sh --pipeline <pipeline-id>
scripts/setup-gh-environment.sh --pipeline <pipeline-id> --dry-runThis helper uses local shared Google config plus local .env values to ensure:
- repository variables:
GCP_PROJECT_ID,GCP_PROJECT_NUMBER,GCP_WIF_POOL_ID,GCP_WIF_PROVIDER_ID - environment creation:
github.environment - environment variables:
GCP_SERVICE_ACCOUNT_EMAIL,CF_PAGES_PROJECT,CF_KV_NAMESPACE_ID,PODCAST_MAX_RETRY_ATTEMPTS
- Push the pipeline environment secrets from local
.env.
scripts/push-gh-secrets.sh --pipeline <pipeline-id>- Generate exactly one workflow file.
python -m tools.generate_workflow --pipeline <pipeline-id>- Commit the pipeline config and generated workflow.
TTS usage reporting reads Google Cloud Billing export data from BigQuery. Audio generation does not require this, but billing usage output needs a configured export table.
For local runs, set these in .env:
GCP_PROJECT_ID=rss-hebrew-podcast-omer
BILLING_EXPORT_TABLE=rss-hebrew-podcast-omer.billing_export.gcp_billing_export_v1_010406_CE1277_E64516
For GitHub Actions, set BILLING_EXPORT_TABLE as a repository variable. GCP_PROJECT_ID is already a shared repository variable.
gh variable set BILLING_EXPORT_TABLE --body "rss-hebrew-podcast-omer.billing_export.gcp_billing_export_v1_010406_CE1277_E64516"
BILLING_EXPORT_TABLE is global for the shared GCP project, not per RSS feed.
python -m tools.preflight local --pipeline geektime-he
python -m tools.preflight github --pipeline geektime-he
python -m tools.preflight github --pipeline geektime-he --jsonExit codes:
0: all checks passed10: at least one dependency or variable or secret is missing11: at least one item is misconfigured64: invalid invocation or invalid pipeline config
scripts/push-gh-secrets.sh --pipeline geektime-he
scripts/push-gh-secrets.sh --pipeline geektime-he --dry-runBehavior:
- reads local
.env - reads the selected pipeline config
- uploads environment secrets to the selected GitHub environment
- never uploads Google key material
- prints secret names only, never secret values
scripts/setup-gh-environment.sh --pipeline geektime-he
scripts/setup-gh-environment.sh --pipeline geektime-he --dry-runBehavior:
- reads local shared Google config from
pipelines/shared.yamlor local overlays - reads local
.env - creates or reuses the selected GitHub environment
- sets the shared repository variables used by workflow OIDC auth
- sets the selected pipeline environment variables
- intentionally does not upload secrets
scripts/setup-gcp-oidc-shared.sh --pipeline geektime-heBehavior:
- uses local-only shared Google config from
pipelines/shared.yaml - creates or reuses one Workload Identity Pool
- creates or updates one shared GitHub OIDC provider
- restricts provider admission to the exact GitHub repository
scripts/setup-gcp-pipeline-sa.sh --pipeline geektime-heBehavior:
- uses local-only shared Google config from
pipelines/shared.yaml - creates or reuses the pipeline service account
- grants only
roles/serviceusage.serviceUsageConsumer - grants
roles/iam.workloadIdentityUserto the exact workflow file and branch
python -m tools.generate_workflow --pipeline geektime-he
scripts/generate-workflow.py --pipeline geektime-heBehavior:
- loads exactly one selected pipeline config
- sets the GitHub environment on the job
- authenticates to Google with OIDC through
google-github-actions/auth - reads shared Google values from repository variables
- reads pipeline-specific values from environment variables and secrets
- generates one public-safe workflow file
- Local mode does not require GitHub Actions setup.
- GitHub mode does not store a long-lived Google JSON key in GitHub.
- The committed workflows are safe to keep public because they contain only GitHub expressions, not real values.
pipelines/shared.yamlis intentionally ignored because it can hold real local GCP metadata for setup tooling.