Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ventilator

Ventilator is a small, reusable GitHub Actions heartbeat for low-traffic APIs and websites. It can make a bounded HTTPS request or load a JavaScript-rendered page and wait for expected visible text—without placing credentials in the workflow or URL.

It works with services that expose an HTTP health or read endpoint, including Supabase REST APIs, and with public web pages whose content must be checked in a real browser. Ventilator is a heartbeat, not a full uptime monitor: it fails the workflow when the endpoint or expected page content is unavailable, while GitHub handles run history and notifications.

Quick start

API endpoint

Create .github/workflows/keep-alive.yml in the repository that will own the schedule:

name: Keep services active

on:
  schedule:
    - cron: "17 */5 * * *"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  heartbeat:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: Example API
      url: https://api.example.com/health

The example runs at minute 17 of every fifth UTC hour. workflow_dispatch also adds a Run workflow button for manual checks.

Rendered website content

Use website_url instead of url when the content is added by JavaScript. Ventilator loads the page in Chrome and waits up to 30 seconds for the text to appear in the page's rendered visible text. Matching is literal apart from capitalization:

jobs:
  website:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: World Cup schedule
      website_url: https://worldcup2023.varshithkumar.com/
      keyword_string_to_wait: "Sun, Nov 19, 2 PM GMT+5:30"
      timezone_id: Asia/Kolkata

Choose one mode per job: either url for an API request, or both website_url and keyword_string_to_wait for a browser check.

Warning

Use a private repository for unattended cron schedules. GitHub supports scheduled workflows in public repositories, but automatically disables them after 60 days without repository activity. See GitHub's official schedule event documentation.

Using a private caller repository also keeps endpoint names and operational configuration out of the public repository.

Interface

uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1

Inputs

Name Required Default Description
endpoint_name Yes Friendly check name shown in logs. Do not include secrets.
url API mode Empty HTTPS API endpoint to request. Credentials must not be included in the URL. Cannot be combined with website_url.
website_url Website mode Empty Public HTTPS page on port 443 to load in Chrome. It must remain on the configured origin, be paired with keyword_string_to_wait, and cannot be combined with url.
keyword_string_to_wait Website mode Empty Text of up to 500 characters to find in the rendered visible body text. Matching ignores capitalization.
timezone_id No UTC Website mode IANA timezone, such as Asia/Kolkata. Use this when page text depends on the visitor's timezone.
method No GET API mode request method: GET or HEAD.
expected_status No 2xx API mode expectation: 2xx or a specific non-redirect HTTP status such as 204. Exact 3xx values are not supported.

Secrets

Name Required Description
request_headers_json No API mode only. JSON object of up to 20 HTTP header names and string values. Names may contain letters, numbers, and hyphens. Values are masked before the request runs.

Store the complete JSON object as one GitHub Actions repository secret. Never commit it to the caller workflow.

Authentication examples

No authentication

No secret mapping is needed for a public health endpoint:

jobs:
  heartbeat:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: Public health check
      url: https://api.example.com/health
      method: HEAD
      expected_status: "204"

Bearer token

Create a repository secret named SERVICE_HEADERS_JSON:

{"Authorization":"Bearer replace-with-token"}

Pass it to Ventilator:

jobs:
  heartbeat:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: Protected API
      url: https://api.example.com/health
    secrets:
      request_headers_json: ${{ secrets.SERVICE_HEADERS_JSON }}

API key header

Create SERVICE_HEADERS_JSON with the header required by your provider:

{"x-api-key":"replace-with-api-key"}

Use the same secrets.request_headers_json mapping shown above.

Supabase

Choose a cheap, read-only REST query, such as selecting one public row. Keep the query bounded with limit=1:

jobs:
  supabase:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: Supabase database
      url: https://YOUR_PROJECT_REF.supabase.co/rest/v1/YOUR_PUBLIC_TABLE?select=id&limit=1
    secrets:
      request_headers_json: ${{ secrets.SUPABASE_HEADERS_JSON }}

For a current sb_publishable_... key, set SUPABASE_HEADERS_JSON with the apikey header:

{
  "apikey": "YOUR_SUPABASE_PUBLISHABLE_KEY"
}

If the project still uses a legacy JWT-based anon key, Supabase's traditional REST pattern sends that key in both headers:

{
  "apikey": "YOUR_LEGACY_ANON_KEY",
  "Authorization": "Bearer YOUR_LEGACY_ANON_KEY"
}

Do not put a current publishable key in Authorization; it is not a JWT. Never use a Supabase service_role or secret key for a heartbeat. Those keys bypass Row Level Security and are unnecessary for a minimal public read. Review Supabase API key guidance and ensure the chosen query is permitted by your Row Level Security policies.

Multiple checks

Add one job per check. Jobs run independently, so one failure does not prevent another check from running:

jobs:
  api:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: API
      url: https://api.example.com/health

  database:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: Database API
      url: https://database.example.com/rest/v1/health?select=id&limit=1
    secrets:
      request_headers_json: ${{ secrets.DATABASE_HEADERS_JSON }}

  website:
    uses: codefromrvk/ventilator/.github/workflows/heartbeat.yml@v1
    with:
      endpoint_name: Public website
      website_url: https://example.com/
      keyword_string_to_wait: Example Domain

What the workflow does

  • Accepts exactly one mode per job: an API request or a rendered website check.
  • In API mode, validates the method, expected status, and optional header JSON before sending a bounded curl request.
  • In website mode, runs a checksum-pinned browser client against the runner's sandboxed Chrome and waits for a substring in rendered innerText, ignoring capitalization.
  • Uses UTC for website rendering unless timezone_id specifies another IANA timezone.
  • Masks API header values and does not print response bodies or expected website text.
  • Gives the browser job no GitHub token permissions and never passes API header secrets to it.
  • Fails the job when the API status is unexpected or the website text does not appear within 30 seconds.

Limitations

  • GitHub scheduled workflows are best-effort and can be delayed during periods of high load. Schedule critical work on infrastructure with delivery guarantees instead. See GitHub's schedule event documentation.
  • GitHub automatically disables scheduled workflows in public repositories after 60 days without repository activity. A private caller repository is recommended for unattended heartbeats.
  • Website mode checks the initial page load and rendered body text only. It does not click, sign in, or validate a complete user journey.
  • Website mode does not support authentication, non-standard HTTPS ports, WebSockets, or pages that require POST requests to render the expected text.
  • Ventilator does not provide latency history, geographic probes, escalation policies, or service-level monitoring.
  • A heartbeat may not satisfy every provider's activity policy. Check the provider's current terms and pausing rules.
  • GitHub Actions usage and retention are subject to the caller repository's plan and settings.

Troubleshooting

The workflow cannot parse the headers secret

Confirm the secret is a valid JSON object of no more than 20 headers whose keys and values are strings. Header names may contain letters, numbers, and hyphens. JSON requires double quotes. Do not paste shell flags or a YAML map into the secret.

The endpoint returns 401 or 403

Check the credential type, required header names, and the endpoint's authorization policy. For Supabase, verify Row Level Security allows the intended read for the publishable or anon role.

The workflow returns 3xx

Ventilator intentionally does not follow redirects, and expected_status rejects exact 3xx values. Change url to the final HTTPS endpoint so authorization headers never cross hosts.

The website loads but the check fails

Confirm the text is visible in the rendered page and appears within 30 seconds. keyword_string_to_wait is a substring match that ignores capitalization; hidden DOM text does not count. If the page formats dates or times for the visitor, set timezone_id to the timezone represented by the expected text. Browser mode does not accept authentication headers or perform clicks.

A scheduled run did not start exactly on time

GitHub schedules can be delayed. Use Actions → Keep services active → Run workflow to verify the configuration manually.

The schedule stopped

Check whether GitHub disabled the workflow after repository inactivity, then re-enable it from the Actions tab. Keeping the caller private avoids the public-repository inactivity rule.

Versioning

Use @v1 for compatible updates within major version 1. Pin to @v1.1.1 when you need the immutable release with website checks and configurable browser timezones. Breaking interface or behavior changes are released under a new major tag.

Maintainers can follow the manual release checklist in RELEASE.md.

Removing Ventilator

Delete or disable the caller workflow to stop scheduled requests, then remove its header secrets from Repository settings → Secrets and variables → Actions. If you created a credential only for Ventilator, revoke it with the service provider as well.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request.

Do not include credentials, private endpoints, or response data in an issue. Report security concerns privately using the instructions in SECURITY.md.

License

Ventilator is available under the MIT License.

About

A secure, reusable GitHub Actions heartbeat for low-traffic HTTP services

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors