Today I show a way how to redirect GhostCMS posts directly to Mastodon.

Creating new posts on the website by hand to social networks is possible, but has no charm.

While I'd love to use pre-built solutions, Zapier, for example, doesn't allow webhooks with a free account (the paid accounts are a cheek for what's offered). Also the direct forwarding to Mastodon via GhostCMS fails, because you can't change the payload with GhostCMS.

General design

Ghost CMS triggers new posts and sends a webhook with the new post to a local Python Flask instance.
The Flask script fetches the relevant data from the payload and creates a Mastodon post. We use Mastodon.py for this.
The Flask script is secured by UWSGI.
As a bonus, GhostCMS tags are converted to Mastodon hashtags.

Remote use / security

By default, the webhook endpoint should not be accessible from outside.
Access to the webhook is unencrypted, because GhostCMS does not allow access (without major intervention) to self-signed certificates.
If you are not running the instance on the same server as Ghost CMS, you need to encrypt the connection via https (e.g. using a reverse proxy).
Your GhostCMS must then be added to the trusted proxies list in .secrets/TRUSTED_PROXIES (see entries for Docker below).

1. Create Mastodon Application

Login to your Mastodon instance (i.e. mastodon.social).

After registration you can find the item development under preferences. Here you create an application with the following input:

Create Mastodon application

The only check you need to set is at "write:statuses". Leave all others unchecked!

After you click on "submit", you will find the required access token under "Development":

Mastodon access token

You don't need the remaining tokens.

2. Enable webhooks in Ghost CMS

Log in to your Ghost CMS instance and open the admin settings. Create a new custom integration with any name.

Ghost CMS: Creat custome integrations

At the bottom you will find the item "Add webhook". This is what we need.

Create a webhook endpoint with your local accessible IP and port 5000 (you can change the port later).

If you are using the docker compose setting with fixed ip, that would be the IP 10.9.9.99, thus http://10.9.9.99/webhook
On bare metal installations it would be localhost/127.0.0.1, thus 127.0.0.1/webhook

It is important that you select "posts published" as trigger and not "posts created". Otherwise you will send unfinished posts to Mastodon.

Don't forget to click on save in the upper right corner.

3. Docker magic

Create a folder  .secrets in your Docker folder and docker-compose.yml file.
For passwords, tokens, etc we need four additional files in the .secrets folder.:

.
├── docker-compose.yml
├── .secrets
│   ├── MASTODON_ACCESS_TOKEN
│   ├── MASTODON_BASE_URL
│   ├── TRUSTED_PROXIES
│   └── WEBHOOK_SECRET
file tree

Populate the files in the .secrets directory as follows:
Copy your Mastodon token to MASTODON_ACCESS_TOKEN.
Copy your Webhook token from Ghost CMS to WEBHOOK_SECRET.
Enter in MASTODON_BASE_URL the address of your Mastodon instance, e.g. https://mastodon.social .
At TRUSTED_PROXIES you enter a list of IPs that should have access to your webhook instance. By default, only localhost and access via Docker subnet from the Docker compose example is entered with a fixed IP, i.e. 10.9.9.0/24.

You can set up your docker-compose.yml as follows:

version: '3.1'

secrets:
  WEBHOOK_SECRET:
    file: ${PWD}/.secrets/WEBHOOK_SECRET
  MASTODON_ACCESS_TOKEN:
    file: ${PWD}/.secrets/MASTODON_ACCESS_TOKEN
  MASTODON_BASE_URL:
    file: ${PWD}/.secrets/MASTODON_BASE_URL
  TRUSTED_PROXIES:
    file: ${PWD}/.secrets/TRUSTED_PROXIES
    
services:
  ghostcms2mastodon:
    image: okxo/ghostcms2mastodon:latest
    restart: always
    ports:
      - 127.0.0.1:5000:5000
    secrets: [WEBHOOK_SECRET,MASTODON_ACCESS_TOKEN,MASTODON_BASE_URL,TRUSTED_PROXIES]
    environment:
       WEBHOOK_SECRET: /run/secrets/WEBHOOK_SECRET
       MASTODON_ACCESS_TOKEN: /run/secrets/MASTODON_ACCESS_TOKEN
       MASTODON_BASE_URL: /run/secrets/MASTODON_BASE_URL
       TRUSTED_PROXIES: /run/secrets/TRUSTED_PROXIES
    networks:
      vpcbr:
        ipv4_address: 10.9.9.99
    tty: true

networks:
  vpcbr:
     driver: bridge
     ipam:
       config:
         - subnet: 10.9.9.0/24
           gateway: 10.9.9.1

Start your docker stack with:

docker compose up -d

Alternatively with docker run:

Create network:

docker network create --subnet=10.9.9.0/24 webhookSubnet

Run docker instance with:


docker run -d -p 5000:5000/tcp -v "${PWD}/.secrets/WEBHOOK_SECRET:/run/secrets/WEBHOOK_SECRET:ro" -v "${PWD}/.secrets/MASTODON_ACCESS_TOKEN:/run/secrets/MASTODON_ACCESS_TOKEN:ro" -v "${PWD}/.secrets/MASTODON_BASE_URL:/run/secrets/MASTODON_BASE_URL:ro" -v "${PWD}/.secrets/TRUSTED_PROXIES:/run/secrets/TRUSTED_PROXIES:ro" --net webhookSubnet --ip 10.9.9.99 okxo/ghostcms2mastodon

NGINX reverse proxy

Create an upstream in /etc/nginx/nginx.conf

  upstream mastodonwebhook {
  server 127.0.0.1:5000;
  keepalive 64;
  }

Add webhook location to your GhostCMS NGINX conf (in sites-availabe or in conf.d folder):

location /webhook {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_redirect  http://  $scheme://;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_cache_bypass $cookie_session;
        proxy_no_cache $cookie_session;
        proxy_buffers 64 256k;

        # If behind reverse proxy, forwards the correct IP                                                                                                                                                                                                                                                                                                                                                                                                                     \

        set_real_ip_from 10.0.0.0/8;
        set_real_ip_from 172.0.0.0/8;
        set_real_ip_from 10.9.0.0/16;
        set_real_ip_from 192.168.0.0/16;
        set_real_ip_from fc00::/7;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;

        proxy_pass http://mastodonwebhook;
}

Restart NGINX.