Skip to content

Commit 7914eb3

Browse files
committed
initial commit
0 parents  commit 7914eb3

16 files changed

+3124
-0
lines changed

.github/workflows/docker-build.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Docker Builds
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
tags:
8+
- 'v*'
9+
10+
jobs:
11+
dev-build-and-push:
12+
if: github.ref == 'refs/heads/develop'
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: docker/setup-qemu-action@v3
17+
- uses: docker/setup-buildx-action@v3
18+
- uses: docker/login-action@v3
19+
with:
20+
username: ${{ secrets.DOCKERHUB_USERNAME }}
21+
password: ${{ secrets.DOCKERHUB_TOKEN }}
22+
- name: Build and push Docker image
23+
uses: docker/build-push-action@v5
24+
with:
25+
context: .
26+
file: ./Dockerfile
27+
push: true
28+
tags: magicpages/ghost-cache-invalidation-proxy:dev
29+
platforms: linux/amd64,linux/arm64
30+
31+
release-build-and-push:
32+
if: startsWith(github.ref, 'refs/tags/v')
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v4
36+
- uses: docker/setup-qemu-action@v3
37+
- uses: docker/setup-buildx-action@v3
38+
- uses: docker/login-action@v3
39+
with:
40+
username: ${{ secrets.DOCKERHUB_USERNAME }}
41+
password: ${{ secrets.DOCKERHUB_TOKEN }}
42+
- run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
43+
- name: Build and push Docker image
44+
uses: docker/build-push-action@v5
45+
with:
46+
context: .
47+
file: ./Dockerfile
48+
push: true
49+
tags: magicpages/ghost-cache-invalidation-proxy:${{ env.VERSION }},magicpages/ghost-cache-invalidation-proxy:latest
50+
platforms: linux/amd64,linux/arm64

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
dist/
3+
.env
4+
*.log
5+
.DS_Store

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Changelog
2+
3+
All notable changes to the Ghost Cache Invalidation Proxy will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.0.0] - 2025-03-11
9+
10+
### Added
11+
- Initial release of the Ghost Cache Invalidation Proxy
12+
- Evolved from ghost-bunnycdn-perma-cache-purger to be more versatile and CDN-agnostic
13+
- Proxy functionality to forward requests to Ghost CMS
14+
- Webhook configuration via environment variables
15+
- Template support for webhook request bodies
16+
- Retry mechanism for failed webhook calls
17+
- Integration examples for popular CDN providers
18+
- Abstracted cache invalidation to work with any webhook-capable CDN or cache system

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM node:18-alpine
2+
3+
WORKDIR /app
4+
5+
COPY package*.json ./
6+
RUN npm install
7+
8+
COPY . .
9+
RUN npm run build
10+
11+
EXPOSE 3000
12+
13+
CMD ["npm", "start"]

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Ghost Cache Invalidation Proxy
2+
3+
## Overview
4+
5+
This proxy sits between a Ghost CMS instance and clients. It monitors responses from Ghost for the `X-Cache-Invalidate` header and triggers configured webhooks when cache invalidation is needed.
6+
7+
When Ghost updates content, it includes an `X-Cache-Invalidate` header in its responses to indicate which content needs cache invalidation. This proxy captures that header and forwards the information to a configurable webhook endpoint, allowing integration with any cache service or CDN.
8+
9+
## Project History
10+
11+
This project evolved from [ghost-bunnycdn-perma-cache-purger](https://github.com/magicpages/ghost-bunnycdn-perma-cache-purger), which was specifically designed to work with BunnyCDN. While the original project served its purpose well, this version has been abstracted to work with any webhook-capable CDN or cache system, making it more versatile for different hosting setups. The core functionality of monitoring Ghost's X-Cache-Invalidate headers remains the same, but the cache purging mechanism has been generalized to support configurable webhooks.
12+
13+
## Usage
14+
15+
The `magicpages/ghost-cache-invalidation-proxy` Docker image is available on [Docker Hub](https://hub.docker.com/r/magicpages/ghost-cache-invalidation-proxy). It can be used to deploy the proxy as part of a Docker Compose stack alongside Ghost.
16+
17+
### Environment Variables
18+
19+
#### Required variables
20+
21+
- `GHOST_URL`: The URL of your Ghost CMS instance. Ideally, the hostname of your Ghost container and the port it listens on (e.g., `http://ghost:2368`).
22+
- `WEBHOOK_URL`: The URL of the webhook endpoint to call when cache invalidation is needed.
23+
24+
#### Optional variables
25+
26+
- `PORT`: The port on which the proxy listens for incoming requests. Defaults to `3000`.
27+
- `DEBUG`: Set to `true` to enable debug logging. Defaults to `false`.
28+
- `WEBHOOK_METHOD`: HTTP method to use for the webhook call. Defaults to `POST`.
29+
- `WEBHOOK_SECRET`: Secret key for webhook authentication. Will be used in the Authorization header if provided.
30+
- `WEBHOOK_HEADERS`: JSON string of additional headers to include in the webhook request.
31+
- `WEBHOOK_BODY_TEMPLATE`: JSON template for the webhook request body. Supports the following variables:
32+
- `${urls}`: Array of URLs/patterns from the `X-Cache-Invalidate` header.
33+
- `${purgeAll}`: Boolean indicating if all cache should be purged.
34+
- `${timestamp}`: Current timestamp.
35+
- `${pattern}`: Raw pattern from the `X-Cache-Invalidate` header.
36+
- `WEBHOOK_RETRY_COUNT`: Number of retry attempts for failed webhook calls. Defaults to `3`.
37+
- `WEBHOOK_RETRY_DELAY`: Delay in milliseconds between retry attempts. Defaults to `1000`.
38+
39+
### Example Docker Compose Configuration
40+
41+
```yaml
42+
version: '3.8'
43+
44+
services:
45+
ghost:
46+
image: ghost:5
47+
environment:
48+
url: http://localhost:4000
49+
database__client: sqlite3
50+
database__connection__filename: /var/lib/ghost/content/data/ghost.db
51+
volumes:
52+
- ghost_data:/var/lib/ghost/content
53+
54+
cache-invalidation:
55+
image: magicpages/ghost-cache-invalidation-proxy:latest
56+
environment:
57+
- GHOST_URL=http://ghost:2368
58+
- PORT=4000
59+
- DEBUG=true
60+
- WEBHOOK_URL=https://api.example.com/invalidate
61+
- WEBHOOK_METHOD=POST
62+
- WEBHOOK_SECRET=your_secret_key
63+
- WEBHOOK_HEADERS={"Custom-Header": "Value"}
64+
- WEBHOOK_BODY_TEMPLATE={"urls": ${urls}, "timestamp": "${timestamp}", "purgeAll": ${purgeAll}}
65+
ports:
66+
- "4000:4000"
67+
depends_on:
68+
- ghost
69+
70+
volumes:
71+
ghost_data:
72+
```
73+
74+
## Integration Examples
75+
76+
### BunnyCDN Integration
77+
78+
To use this with BunnyCDN, set up your webhook configuration like this:
79+
80+
```
81+
WEBHOOK_URL=https://api.bunny.net/purge
82+
WEBHOOK_METHOD=POST
83+
WEBHOOK_SECRET=your_bunnycdn_api_key
84+
WEBHOOK_HEADERS={"AccessKey": "${secret}", "Content-Type": "application/json"}
85+
WEBHOOK_BODY_TEMPLATE={"urls": ${urls}}
86+
```
87+
88+
### Cloudflare Integration
89+
90+
For Cloudflare:
91+
92+
```
93+
WEBHOOK_URL=https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/purge_cache
94+
WEBHOOK_METHOD=POST
95+
WEBHOOK_SECRET=your_cloudflare_api_token
96+
WEBHOOK_HEADERS={"Authorization": "Bearer ${secret}", "Content-Type": "application/json"}
97+
WEBHOOK_BODY_TEMPLATE={"files": ${urls}}
98+
```
99+
100+
## How It Works
101+
102+
1. The proxy forwards all client requests to the Ghost CMS instance.
103+
2. When Ghost responds, the proxy checks for the `X-Cache-Invalidate` header.
104+
3. If the header is present, the proxy extracts the invalidation patterns and constructs a webhook payload.
105+
4. The webhook is called with the configured parameters, headers, and body.
106+
5. The proxy supports retries for failed webhook calls.
107+
108+
## License
109+
110+
This project is licensed under the MIT License.
111+
112+
## Contributing
113+
114+
If you have any ideas for improvements or new features, feel free to open an issue or submit a pull request.

docker-compose.example.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
version: '3.8'
2+
3+
services:
4+
ghost:
5+
image: ghost:5
6+
environment:
7+
url: http://localhost:4000
8+
database__client: sqlite3
9+
database__connection__filename: /var/lib/ghost/content/data/ghost.db
10+
volumes:
11+
- ghost_data:/var/lib/ghost/content
12+
13+
cache-invalidation:
14+
build:
15+
context: .
16+
environment:
17+
- GHOST_URL=http://ghost:2368
18+
- PORT=4000
19+
- DEBUG=true
20+
- WEBHOOK_URL=https://api.example.com/invalidate
21+
- WEBHOOK_METHOD=POST
22+
- WEBHOOK_SECRET=your_secret_key
23+
- WEBHOOK_HEADERS={"Custom-Header": "Value", "Authorization": "Bearer ${secret}"}
24+
- WEBHOOK_BODY_TEMPLATE={"urls": ${urls}, "timestamp": "${timestamp}", "purgeAll": ${purgeAll}}
25+
- WEBHOOK_RETRY_COUNT=3
26+
- WEBHOOK_RETRY_DELAY=1000
27+
ports:
28+
- "4000:4000"
29+
depends_on:
30+
- ghost
31+
32+
volumes:
33+
ghost_data:

example.env

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Required Configuration
2+
# =====================
3+
4+
# The URL of your Ghost CMS instance (required)
5+
GHOST_URL=http://ghost:2368
6+
7+
# The URL for the webhook endpoint that will be called when cache invalidation is needed (required)
8+
WEBHOOK_URL=https://api.example.com/invalidate
9+
10+
11+
# Optional Configuration
12+
# =====================
13+
14+
# Port on which the proxy listens (default: 3000)
15+
PORT=4000
16+
17+
# Enable debug logging for detailed output (default: false)
18+
DEBUG=true
19+
20+
# HTTP method for webhook calls (default: POST)
21+
WEBHOOK_METHOD=POST
22+
23+
# Secret key for webhook authentication
24+
# This can be referenced in WEBHOOK_HEADERS using ${secret}
25+
WEBHOOK_SECRET=your_secret_key_here
26+
27+
# JSON string of headers to include in webhook request
28+
# Example for Authorization header using the configured secret
29+
WEBHOOK_HEADERS={"Authorization": "Bearer ${secret}", "Content-Type": "application/json"}
30+
31+
# JSON template for webhook request body
32+
# Supported variables:
33+
# - ${urls}: Array of URLs/patterns from the X-Cache-Invalidate header
34+
# - ${purgeAll}: Boolean indicating if all cache should be purged
35+
# - ${timestamp}: Current timestamp
36+
# - ${pattern}: Raw pattern from the X-Cache-Invalidate header
37+
# Note: All variables are properly JSON-encoded during substitution
38+
WEBHOOK_BODY_TEMPLATE={"urls": ${urls}, "timestamp": "${timestamp}", "purgeAll": ${purgeAll}}
39+
40+
# Number of retry attempts for failed webhook calls (default: 3)
41+
WEBHOOK_RETRY_COUNT=3
42+
43+
# Delay in milliseconds between retry attempts (default: 1000)
44+
WEBHOOK_RETRY_DELAY=1000

0 commit comments

Comments
 (0)