A Grumpy POSSE

One of the main reasons I have decided to embrace more of the IndieWeb ethos is out of a desire to have more control over where the various things I create (micro-blogging and longer-form content) ends up. Twitter ended up being something that replaced the blogging I used to do.

One of the concepts coming out of the Indieweb is the great-sounding acronym POSSE. It stands for Publish On your own Site, Syndicate Elsewhere. Which is another way of saying you should have one central location where most of your material lives (shitposting on social media still will happen) and then push that content out to other places.

So for me, what would this look like?

For a long time I had another blog, but once I really leaned into the Grumpy Programmer brand that blog served no purpose and I really should've just moved stuff over there. Although that old blog is no longer online I still have all the posts I made to it. Maybe I will go through one day and do "the best of when I was less grumpy" or something like that.

So, in a world where I embrace POSSE, here is how it should work:

  • I write content on this blog
  • I use the static site generator Sculpin to create the site
  • As part of updating my site, I then automatically publish a link to that blog elsewhere

There are other solutions to make this happen if you use other blogging engines (static or otherwise) but there was nothing out-of-the-box to make it work for Sculpin. So, I used my favourite search engine and started doing some research.

I also, like any good online influencer, leveraged my personal relationships with people...like the current maintainer of Sculpin...to ask them how I could accomplish a few tasks.

Writing on this blog

Sculpin supports me writing posts using Markdown. This means I get to keep using the new One True Editor to create new posts. It also uses Twig for the templates it uses to generate the static HTML for the site.

Again, I am not telling you one way or the other what to use for your blog. For a lot of folks using Wordpress and a plugin tailored to IndieWeb needs will work. I didn't want to setup anything new, so I was going to stick with Sculpin.

Creating the site

I am currently using GitHub pages for this site, so all I have to do is copy the generated HTML output into the correct location in the repo that holds my site, push those changes up to GitHub and in a minute or two I have a new version of my web site for all the world to see.

This is no big change for what I was doing previously -- I used to have an AWS Lightsail instance for hosting the blog but decided GitHub was a better option since I was already paying for an account there. Why pay twice?!?

Now, to prepare my site to be "IndieWeb friendly" is simply followed the instructions at IndeWebify.me. Followed by, of course, a lot of commits and pushes to get things to behave exactly the way I needed them to.

For example. this is what the template looks like for a blog post, with all the IndieWeb microformats embedded in them:

{% extends "default" %}

{% block head_meta %}
    <meta name="robots" content="index, follow">
{% endblock %}

{% block content_wrapper %}
    <article class="h-entry">
            <h2><div class="p-name">{{ page.title }}</div> <small>post</small></h2>
        <div class="e-content">
            {{ page.blocks.content|raw }}
        {% if page.categories %}
            <p class="categories">
            {% for category in page.categories %}
            <a class="p-category" href="{{ site.url }}/blog/categories/{{ category|url_encode(true) }}">{{ category }}</a>{% if not loop.last %}, {% endif %}
            {% endfor %}
        {% endif %}
        {% if page.tags %}
            <p class="tags">
            {% for tag in page.tags %}
            <a href="{{ site.url }}/blog/tags/{{ tag|url_encode(true) }}">{{ tag }}</a>{% if not loop.last %}, {% endif %}
            {% endfor %}
        {% endif %}
        <a href="https://brid.gy/publish/mastodon"</a>
        <a href="https://brid.gy/publish/twitter"</a>
        {% if page.previous_post or page.next_post %}
            <nav class="article">
                    {% if page.next_post %}
                        <li>Next: <a class="next" href="{{ site.url }}{{ page.next_post.url }}" title="{{ page.next_post.title }}"><span class="title">{{ page.next_post.title }}</span></a></li>
                    {% endif %}
                    {% if page.previous_post %}
                        <li>Previous: <a class="previous" href="{{ site.url }}{{ page.previous_post.url }}" title="{{ page.previous_post.title }}"><span class="title">{{ page.previous_post.title }}</span></a></li>
                    {% endif %}
        {% endif %}
{% endblock %}

The Grumpy POSSE

Figuring out how to syndicate my content without an existing plugin proved to be a bit of a challenge. Luckily, I found a blog post that explained how to make this work by embracing Webmentions and using an awesome (and free!) service called Bridgy to automate syndication.

The solution I found was to create a GitHub action that would be triggered each time I did a push to the repo. This action would take care of using webmentions and Brid.gy to do the magic. But first, I needed a feed of my website that was in JSON, not XML.

So I hit up Kevin Boyd and ask him how could I do this in Sculpin. He very gracious created a Twig template that would turn my list of blog posts into a JSON feed. Here it is in all it's glory:

permalink: feed.json
    - posts
     Example data structure for delivering a Webmentions feed:

     From: https://blog.geheimesite.nl/en/index.json

            "author": {},
            "categories": ,
            "content": "yadda yadda yadda",
            "date": "2022-05-03T16:27:18+02:0",
            "site": "https://whateverthing.com/",
            "tags": null,
            "title": "Article One",
            "uri": "https://whateverthing.com/2022/11/11/article-one/"
            "author": {},
            "categories": ,
            "content": "yadda yadda yadda",
            "date": "2022-06-03T16:27:18+02:0",
            "site": "https://whateverthing.com/",
            "tags": null,
            "title": "Article Two",
            "uri": "https://whateverthing.com/2022/11/11/article-two/"
{% set outputArray = [] %}

{% for post in data.posts[:10] %}
        set postOutput = {
            'author': site.author,
            'categories': post.meta.categories,
            'content': post.blocks.content|raw,
            'date': post.date|date("c"),
            'site': site.global_url,
            'tags': post.meta.tags,
            'title': post.title,
            'uri': [ site.global_url, post.url]|join
    {% set outputArray = outputArray|merge([postOutput]) %}
{% endfor %}

{{ outputArray|json_encode(constant('JSON_PRETTY_PRINT'))|raw }}

I dropped that into the root directory Sculpin uses for generating my site, named it feed.json.twig and now I had a JSON-based feed for the site.

Now, the GitHub action. This would go in .github/workflows/send-webmention.yaml for my repo that I am using for the page.

name: Send Webmentions

on: push

    runs-on: ubuntu-latest

      - name: Send Webmentions
          GITHUB_TOKEN: $
          URL: $
        run: |
          NEW=$(curl --silent $URL | jq -r first.uri)

          curl -X POST https://webmention.app/check?url="https://grumpy.learning.com$NEW"

          curl -H "Content-Type: application/x-www-form-urlencoded" --request POST \
          -d source="https://grumpy-learning.com$NEW" \
          -d target="https://brid.gy/publish/twitter" \

          curl -H "Content-Type: application/x-www-form-urlencoded" --request POST \
          -d source="https://grumpy-learning.com$NEW" \
          -d target="https://brid.gy/publish/mastodon" \

The original instructions recommended putting the JSON feed details into a secret and then referencing it inside the action. I am not sure it matters that much but stuck with it.

So, the next thing is that it grabs the feed using cURL and grabs what it thinks is the latest post (the first one in the feed) and then proceeds to use webmention.app and Brid.gy to syndicate my content by sharing the post title and linking to it).

So far it is working well and if you came across this post via my social media microblogging (I sound so pretentious when I say it out loud) then it clearly worked.

I think my takeway from this is that gluing things together so your existing blog can syndicate content to a variety of platforms. If you're looking to have more control over the things you share online, I highly recommend looking into the IndieWeb. I hope this post helps!

Categories: indieweb, technology