What is Hackers' Pub?

Hackers' Pub is a place for software engineers to share their knowledge and experience with each other. It's also an ActivityPub-enabled social network, so you can follow your favorite hackers in the fediverse and get their latest posts in your feed.

This.

Again, if the topic of discussion is the intersection of racism and ableism, in particular, anti-Black racism and Tourette syndrome with coprolalia, listen to people who have expertise and lived experience with both.โ™ฅ๏ธ๐Ÿ‘๐Ÿฟ

And they will say the same things that I and other Black folk have said.

m.youtube.com/watch?v=FgaqKIxS

0
8
0
0
0
0

do you need to work with SPI flash a lot? do you hate the unreliable clothespin clip style clips? do you want a clip that actually works and is cheap?

aliexpress has you covered! check this product out [not sponsored]

there is a bit of a learning curve (you need to push on it quite strongly to make a connection) but after that it just works. do note that the pin 1 marker on the 8-pin DIP connector isn't placed in the right spot, but it is marked correctly on the probe itself and the little adapter board it has on the other end.

I tested the WSON-8 and SOIC-8-W versions so I'm quite sure the other two work also

product image
0
0
1
0
0
0

It is with profound sadness that the MetaBrainz Board of Directors announces the unexpected passing of our Founder and Executive Director, Robert Kaye.

Robertโ€™s vision and leadership shaped MetaBrainz and left a lasting mark on the music industry and open source movement. His contributions were significant and his loss is deeply felt across our global community.

blog.metabrainz.org/2026/02/24

0
6
2

RE: mastodon.social/@metabrainz/11

Today the world has lost a light ๐Ÿ˜ข Rob was a good friend and amazing human being. His passion for open communities, open data and music was infectious and unmatched. If you have never had a chance to meet Rob, please take a moment to read these two pieces by him to understand the difference he made in the world:
* his essay in Open Advice about how not to start a community: open-advice.org
* the story about using a cake to get your project paid by Big Tech: blog.metabrainz.org/2013/12/05

๐Ÿซก

0
2
0
0

Also needed a way to efficiently give coding agents full context on libraries I use in my projects, so I made this.
It generates JSON & TOON "blueprints" of a library's class signatures. It can be integrated in CI/CD pipelines and I'd love it if it became a standard.

github.com/diversified-design/

0

RE: fosstodon.org/@adamchainz/1161

This got me thinking ๐Ÿ’ก

Django docs might be a perfect fit for this, since theyโ€™re built statically with Sphinx and the build sees the whole corpus, so you could train a shared dictionary from all the repeated HTML, templates, and structure ๐Ÿ—ƒ๏ธ

With thousands of pages and many languages sharing the same layout, a per-language dictionary could squeeze responses even more once dictionary compression becomes easier to deploy โœ…

Feels like a fun experiment for the Django ecosystem โš—๏ธ

0
1

ใŠใฃใ‹ใ‘ใง่ฆ‹ใฆใ‚‹ใ‘ใฉไปŠ้€ฑใฎใƒ‹ใƒฅใƒผใ‚นใ‚ใฃใŸๅˆบใ—ใ‚‚้ข็™ฝใ„ใชใ€‚ใƒ‡ใƒขใซๅ‚ๅŠ ใ™ใ‚‹ๅ ดๅˆใฎๅฏพ็ญ–ใŒ่ถ…ๅฎŸ็”จ็š„

้€ฑๅˆŠใƒ‹ใƒฅใƒผใ‚นใ‚ใฃใŸๅˆบใ— 15 ๅทฆ็ฟผๆ–ฐ่ฆใธ๏ผใ“ใ‚Œใ ใ‘ใฟใจใ‘๏ฝœ ๅฒก็”ฐ้บปๆฒ™ร—้ซ˜ๅณถ้ˆด๏ผˆ2/25๏ผ‰#ใƒใƒชใ‚ฟใ‚นTV - YouTube: youtube.com/watch?v=UDSDfZJA0Rc

0

In an open letter, we have joined other organizations in calling for Aura Sallaโ€™s appointment as Digital Omnibus rapporteur to be withdrawn.

Before becoming an MEP, Ms Salla was an executive lobbyist at Meta, serving as its Public Policy Director and Head of EU Affairs. And here stated goal is "to cut EU digital regulation".

Therefore, she is not suitable for the job as rapporteur.

Open letter: lobbycontrol.de/wp-content/upl

0
1
0
2

์ž์ž‘๊ณก(ํ…Œํ† , ์ผ๋ณธ์–ด ํฌํ•จ) ๋งํฌ

https://drive.google.com/file/d/10qO3xVD5N86u7zygH2TJES9zIBLX6uGS/view?usp=sharing


์ด๋Ÿฐ๊ฑฐ ์˜ฌ๋ ค๋ณด๋Š”๊ฑฐ ์ฒ˜์Œ?์ธ๊ฒƒ๊ฐ™์•„์„œ ๋‘๊ทผ๋‘๊ทผ

1

This photo is very representative of how much fun I had at @ARCticConference! This is from @hiddeHidde van der Ploeg & @flarupMichael Flarup's Design Roast. What a talented duo!

๐Ÿ“ท Vera Tuuli Maria

Just a year ago, I was sitting in exactly the same spot with @klemensstrasserKlemens ๐Ÿฅณ
and @RobWRob Whitaker is not an object recording a @swiftovercoffee episode with co-hosts @mikaelacaronMikaela Caron ๐Ÿฆ„ & @twostrawsPaul Hudson.

If you ask me, it's a pretty good one ๐Ÿ˜‰:
podcasts.apple.com/gb/podcast/

Me sitting relaxed on a chair on stage, holding a microphone and smiling while looking to the side. Wearing a dark shirt and waistcoat, and light jeans, with one leg crossed and resting on their knee. A presentation screen is visible in the background.
0
0
0
0
0
0

์‹ค์ œ๋กœ ๋ฏผ์ฃผ๋‹น ์ถฉ๋ถ๋„๋‹น์€ ์˜ˆ๋น„ํ›„๋ณด ์ž๊ฒฉ ์‹ฌ์‚ฌ์—์„œ ์œ  ์ „ ํ–‰์ •๊ด€์„ ์ •๋ฐ€์‹ฌ์‚ฌ ๋Œ€์ƒ์ž๋กœ ๋ถ„๋ฅ˜ํ–ˆ๋‹ค. ์œ  ์ „ ํ–‰์ •๊ด€์€ ์‹ฌ์‚ฌ ๊ฒฐ๊ณผ์— ๋ฐ˜๋ฐœํ•ด 23์ผ ๋‹จ์‹ํˆฌ์Ÿ์„ ์‹œ์ž‘ํ–ˆ๋‹ค. ๋˜ํ•œ ํ˜„์ˆ˜๋ง‰ ๋‚ด์šฉ์ด ํ—ˆ์œ„ ์‚ฌ์‹ค์— ๊ธฐ๋ฐ˜ํ•œ ์ •์น˜๊ณต์ž‘์ด๋ผ๋ฉฐ ๊ฒŒ์‹œ์ž๋ฅผ ๋ช…์˜ˆํ›ผ์† ๋“ฑ ํ˜์˜๋กœ ๊ณ ์†Œํ–ˆ๋‹ค. www.pressian.com/pages/articl...

40๋…„ ์ „ ์‚ฌ๊ฑด '๋ฏธํˆฌ'ํ•œ 60๋Œ€ ์—ฌ์„ฑ์˜ ํ˜ธ์†Œ "๋ฏผ์ฃผ๋‹น...

0
1
0

์ƒˆ์‚ผ ๋ฐ€๋ ค์˜ค๋Š” ๋‹ต๋‹ตํ•จ. ์‚ฌ๋ฒ•๋ถ€๊ฐ€ ๊ทธ๊ฐ„ ์‚ฌ๋ฒ•์ œ๋„ ๊ฐœํ˜์„ ์œ„ํ•ด ๋ณด์ธ ๊ฐ€์žฅ ์ •์น˜์ ์ธ ๋™์ž‘์ด ๋ฌด์—ˆ์ด์—ˆ๋Š”๊ฐ€? ๊ณ ์ž‘ ์„ ์ถœ์ง ๋Œ€ํ†ต๋ น๊ณผ ๋‹ดํ•ฉํ•ด "์–‘์Šนํƒœ"ํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ๊ตญ๋ฏผ์˜ ์—ฌ๋ก ์ˆ˜๋ ด์„ ํ•œ๋‹ค๋ฉฐ ํ•œ ๊ฒƒ์€ ๋ญ์˜€๋Š”๊ฐ€? ์ƒ๊ณ ๋ฒ•์›์ œ๋ฅผ ํ™๋ณดํ•˜๋Š” ๊ณต์ต๊ด‘๊ณ ๋ฅผ ๋งŒ๋“ค์–ด ์ผ๋ฐฉ์ ์œผ๋กœ ํ‹€์–ด๋Œ„ ๊ฒƒ์ด์—ˆ๋‹ค.

0
0

์‹ค์ œ๋กœ ๋ฏผ์ฃผ๋‹น ์ถฉ๋ถ๋„๋‹น์€ ์˜ˆ๋น„ํ›„๋ณด ์ž๊ฒฉ ์‹ฌ์‚ฌ์—์„œ ์œ  ์ „ ํ–‰์ •๊ด€์„ ์ •๋ฐ€์‹ฌ์‚ฌ ๋Œ€์ƒ์ž๋กœ ๋ถ„๋ฅ˜ํ–ˆ๋‹ค. ์œ  ์ „ ํ–‰์ •๊ด€์€ ์‹ฌ์‚ฌ ๊ฒฐ๊ณผ์— ๋ฐ˜๋ฐœํ•ด 23์ผ ๋‹จ์‹ํˆฌ์Ÿ์„ ์‹œ์ž‘ํ–ˆ๋‹ค. ๋˜ํ•œ ํ˜„์ˆ˜๋ง‰ ๋‚ด์šฉ์ด ํ—ˆ์œ„ ์‚ฌ์‹ค์— ๊ธฐ๋ฐ˜ํ•œ ์ •์น˜๊ณต์ž‘์ด๋ผ๋ฉฐ ๊ฒŒ์‹œ์ž๋ฅผ ๋ช…์˜ˆํ›ผ์† ๋“ฑ ํ˜์˜๋กœ ๊ณ ์†Œํ–ˆ๋‹ค. www.pressian.com/pages/articl...

40๋…„ ์ „ ์‚ฌ๊ฑด '๋ฏธํˆฌ'ํ•œ 60๋Œ€ ์—ฌ์„ฑ์˜ ํ˜ธ์†Œ "๋ฏผ์ฃผ๋‹น...

0
0
1
0
0
0

๋ฆฌ๋”ฉ๋ฐฉ ์‚ฌ๊ธฐ ํ”ผํ•ด์ž๊ณ , ๋ฆฌ๋”ฉ๋ฐฉ ๊ณ„์ขŒ์— ๋ฒ”์ฃ„์ˆ˜์ต์„ ์†ก๊ธˆํ•˜๋ฉด ์ ˆ๋„์ฃ„๋กœ ์ˆ˜์‚ฌ ๋ฐ›์„ ๋•Œ ๋ฆฌ๋””๋ฐฉ๋„ ๊ฐ™์ด ์กฐ์‚ฌ ํ•ด์ค„๊ฑฐ๋ผ๊ณ  ์ฑ—์ง€ํ”ผํ‹ฐ๊ฐ€ ๋Œ€๋‹ตํ•ด์„œ ์ €์งˆ๋ €๋‹ค๋Š”๋Œ€. ํ™ฉ๋‹นํ•˜์ง€๋งŒ...์š”์ฆ˜ ์‹œ๊ธฐ์—๋Š” ๋‹จ์ˆœํ•œ ๋ณ€๋ช…์ด ์•„๋‹ˆ๋ผ ์ถฉ๋ถ„ํžˆ ์žˆ์„ ๋ฒ•ํ•œ ๋ฒ”ํ–‰ ๋™๊ธฐ ๊ฐ™๊ธฐ๋„ํ•จ. naver.me/Fx2iyRb9

"์ฑ—GPT๊ฐ€ ์‹œ์ผฐ๋‹ค"โ€ฆ1700๋งŒ์› ๊ทธ๋ž˜ํ”ฝ์นด๋“œ ํ›”์นœ ์ด์œ ...

0

Also needed a way to efficiently give coding agents full context on libraries I use in my projects, so I made this.
It generates JSON & TOON "blueprints" of a library's class signatures. It can be integrated in CI/CD pipelines and I'd love it if it became a standard.

github.com/diversified-design/

0
0
0
0
0
0

๋‹ต: ์•ผํ•˜๊ฒŒ๋„ ์ œ๋Œ€๋กœ ๋ชป ๋งŒ๋“ฌ......์‚ฌ์‹ค ์ข‹๊ฒŒ ์•ผํ•œ ๊ฒƒ๋„ ์ง€๊ฒน๊ณ  ์ง€์น˜์ง€ ์•Š์œผ๋ ค๋ฉด ๊ณ ์ฐฐ์žˆ๊ฒŒ ๋ณ€์ฃผํ•ด์„œ ์ž˜ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ ๋ญ๋“  ์ •๋ง ์ง„์ •ํ•œ ์ฐฝ์ž‘์— ๋“ค์–ด๊ฐ€๋Š” ๊ฑด 1๋„ ๋ชป ํ•˜๊ธฐ ๋•Œ๋ฌธ์—......๊ทธ๋ฆฌ๊ณ  ์ฐ ์•ผ๊ฒœ์€ ๋‚จ์„ฑ ์†Œ๋น„์ž๋งŒ ๋‚จ๋Š”๋ฐ ์ด ์†Œ๋น„์ž ์ธก์ด ๋„๋ฐ•์ด ์•„๋‹ˆ๋ฉด ๋ˆ์„ ์•ˆ ๋‚ด๊ธฐ๋กœ ์œ ๋ช…ํ•œ ์†Œ๋น„์ž๊ตฐ์ด๊ธฐ ๋•Œ๋ฌธ์—......์Œ, ๋‚ด๊ฐ€ ๋„ˆ๋ฌด ๋ฐ•ํ–ˆ๊ตฐ์š”. ๋„๋ฐ•๊ณผ ์„ฑ๋งค๋งค์š”. ์•„. ๊ทผ๋ฐ ์„ฑ๋งค๋งค๋„ ๊ทนํ•œ์˜ ๊ฐ€์„ฑ๋น„ ์ถ”๊ตฌํ•˜๋Š” ์ธต์ด์ง€? ๊ฒฐ๊ตญ ๋ฌด๋ฃŒ์ธ ๋ถˆ๋ฒ•์ดฌ์˜ ์˜์ƒ๊ณผ ์‹ธ์›Œ์•ผ ํ•˜๋Š”๊ตฐ์š”. ์ฐธ ๊ฐ€์ง€๊ฐ€์ง€ํ•˜๋Š”๊ตฐ์š”......

RE: https://bsky.app/profile/did:plc:niy7tv6ar72jwdtvr5jt6323/post/3mfoq7fff7c2d

0
0
1

โ€œ๋•…์ด ์šฐ๋ฆฌ ๋ชจ๋‘๊ฐ€ ํ•จ๊ป˜ ๋ฐœ ๋”›๊ณ  ์‚ด์•„์•ผ ํ•  ๋ฌผ๋ฆฌ์  ํ„ฐ์ „์ด๋“ฏ, ๋Œ๋ด„์€ ์ธ๊ฐ„์˜ ์กด์—„ํ•จ์ด ์ƒ์„ฑยท๋ฐœ์ „ยท์œ ์ง€ยทํšŒ๋ณต๋˜๋Š” ๊ด€๊ณ„์  ํ„ฐ์ „์ด๋‹ค. ํ† ์ง€๊ณต๊ฐœ๋…์ด ๊ฒฝ์ œ์  ์ •์˜๋ฅผ ์„ธ์šฐ๋Š” ๊ทผ๊ฐ„์ด์—ˆ๋‹ค๋ฉด, ๋Œ๋ด„๊ณต๊ฐœ๋…์€ ์ธ๊ฐ„ ์กด์—„์„ ์ˆ˜ํ˜ธํ•˜๊ณ  ๊ณต๋™์ฒด์˜ ์ง€์†๊ฐ€๋Šฅ์„ฑ์„ ๋‹ด๋ณดํ•˜๋Š” ์‹ค์งˆ์ ์ธ ์•ˆ์ „๋ง์ด ๋  ๊ฒƒ์ด๋‹ค.โ€

ํ† ์ง€๊ณต๊ฐœ๋…, ๋Œ๋ด„๊ณต๊ฐœ๋… [์„ธ์ƒ์ฝ๊ธฐ]

0

Meet the CSS Custom Highlight API โœจ

Style arbitrary text ranges without modifying the DOM.
๐Ÿ“ Great for search results
๐Ÿ’ป Syntax highlighting
๐Ÿ“š Text annotations

Learn how it works ๐Ÿ‘‡
developer.mozilla.org/en-US/do

0

Inside Indiekit: How 30+ Plugins Turn a Node.js Server into a Federated Personal Web Platform

Ricardo Mendes @rick@rmendes.net

This post is a guided tour through the architecture of the system that powers this site.

Itโ€™s built on Indiekit, an open-source Node.js IndieWeb server created by Paul Robert Lloyd. I forked it because I wanted to change fundamental aspects of how it works โ€” a new page post type for slash pages, OpenGraph card embeds in the Bluesky and Mastodon syndicators, full ActivityPub federation, content aggregation from external platforms, a social reader, a homepage builder, and more. The result is 27 @rmdes/* plugins that extend the original system far beyond its initial scope.

This level of extension โ€” going from a handful of core IndieWeb features to a 30+ plugin personal web platform โ€” was made possible by developing with Claude Code (Anthropic). It served as a pair-programming assistant throughout, helping me move fast without sacrificing code quality.

If you want the interactive version with diagrams, check out the Architecture Explorer.

What follows is the story of how all these pieces fit together.


1. The Core: What Indiekit Is

At its heart, Indiekit is an Express server with a plugin orchestration layer. It doesnโ€™t do much on its own โ€” its power comes from the plugins you load. The core provides five extension points:

  • addEndpoint() โ€” registers HTTP routes. This is how plugins expose admin UIs, APIs, and protocol endpoints.
  • addPostType() โ€” defines content types (articles, notes, photos, pages) with their own properties and permalink patterns.
  • addStore() โ€” plugs in a storage backend. The default writes .md files to the filesystem, but you could write to GitHub, GitLab, or anywhere else.
  • addSyndicator() โ€” registers targets for cross-posting. Each syndicator knows how to format and deliver content to its platform.
  • addPreset() โ€” integrates with a static site generator. The preset converts Indiekitโ€™s internal JF2 data format into whatever your SSG expects.

Configuration lives in indiekit.config.js, loaded via cosmiconfig. You list your plugins, set your preferences, and Indiekit wires everything together at startup.

The key design principle is that every piece of functionality is a plugin. Authentication? A plugin. Content creation via Micropub? A plugin. The admin UI for managing posts? A plugin. This means you can run a minimal IndieWeb blog with just a handful of packages, or load 30+ plugins for a full-featured personal web platform.

2. The Plugin Taxonomy

With 30+ plugins, organization matters. They break down into six categories:

Core IndieWeb Endpoints (6 plugins)

These are the essential building blocks. endpoint-auth handles IndieAuth with JWT and PKCE. endpoint-micropub processes content creation requests โ€” itโ€™s the main entry point for posting. endpoint-posts provides the admin UI at /posts for managing content. endpoint-syndicate triggers cross-posting on a 2-minute polling schedule. Two webmention plugins handle outbound sending and inbound receiving via webmention.io.

Several of these are forks of the upstream Indiekit packages. The Micropub fork adds type-based post discovery. The syndicate fork adds batch mode with a 2-second delay between targets to avoid rate limiting. These are the kinds of practical modifications that emerge from running the system in production.

Social & Federation (3 plugins)

This is where things get interesting. The ActivityPub plugin turns the entire site into a fediverse actor โ€” more on this in section 4. Microsub is a social reader with adaptive feed polling that ranges from 1-minute checks for active feeds down to 17-hour intervals for dormant ones. Blogroll aggregates blogs from OPML files, Microsub subscriptions, and FeedLand, with webhooks connecting it to the Microsub reader.

Content Aggregation (6 plugins)

Six plugins pull activity from external platforms on background schedules: RSS feeds every 15 minutes, Podroll (podcasts via FreshRSS) every 15 minutes, Funkwhale listening history every 5 minutes, Last.fm scrobbles every 5 minutes, GitHub activity, and YouTube channel data. Each stores its data in MongoDB and exposes an API that the Eleventy theme fetches at build time.

Site Management (3 plugins)

Homepage Builder provides a drag-and-drop admin UI for arranging homepage sections. It discovers available sections from other plugins โ€” CV data, GitHub repos, Funkwhale listening stats, recent blog posts, and more. CV manages a structured resume with experience, education, skills, projects, and certifications. LinkedIn OAuth handles token management for the LinkedIn syndicator.

Syndicators (4 plugins)

Four cross-posting targets: Bluesky (AT Protocol with native rich text facets and OG card embeds), Mastodon (with native favorites and reblogs), LinkedIn (REST API for articles and notes), and IndieNews (webmention-based, no API key needed). Each understands interaction types โ€” when you like or repost something, the syndicator sends the appropriate native interaction rather than creating a new post.

Post Types & Presets (2 plugins)

post-type-page creates root-level slash pages (/about, /now, /uses). preset-eleventy is a fork that generates permalinks for all post types, not just the ones upstream Indiekit supports. It converts JF2 content to YAML frontmatter with Markdown bodies โ€” the format Eleventy expects.

3. The Complete Data Flow

The system has two main directions: outbound (publishing your content) and inbound (aggregating external activity).

Outbound: From Micropub to Published Page

When you write a post โ€” using a Micropub client like Quill, the Indigenous app, or the admin UI at /posts โ€” hereโ€™s what happens:

  1. Auth check. endpoint-auth validates your IndieAuth bearer token and checks scopes (create, update, delete).
  2. Content processing. endpoint-micropub receives the JF2 content, determines the post type, and preserves any mp-syndicate-to targets for later.
  3. Format conversion. preset-eleventy converts the JF2 data into a YAML frontmatter + Markdown file, generating the appropriate permalink (/articles/2026/02/25/my-post/).
  4. Storage. store-file-system writes the .md file to /app/data/content/{type}/. Post metadata goes into the MongoDB posts collection for admin queries.
  5. Build. Eleventyโ€™s file watcher detects the change and triggers a build. The entire site is rebuilt, a new timestamped release directory is created, and a symlink swap makes it live โ€” zero downtime.
  6. Serving. nginx serves the static site on port 3000, proxying admin and API routes to Indiekit on port 8080.

After publishing, two background processes kick in:

  • The syndication poller (every 2 minutes) finds posts with pending mp-syndicate-to targets and triggers each syndicator with a 2-second delay between them.
  • The webmention sender (every 5 minutes) scans post content for URLs and sends webmentions to any linked page that advertises a webmention endpoint.

Inbound: External Activity Flowing In

The reverse direction is simpler. Six aggregator plugins run on background schedules, fetch data from external APIs, and store it in MongoDB. When Eleventy rebuilds, _data/*.js files call plugin API endpoints to get the latest aggregated content, and the theme renders it.

The cycle looks like: External service โ†’ background sync โ†’ MongoDB โ†’ plugin API โ†’ Eleventy _data file โ†’ Nunjucks template โ†’ static HTML.

4. ActivityPub Federation

This is the most complex part of the system. The ActivityPub plugin, built on Fedify 2.0, turns the site into a full fediverse actor. People on Mastodon, Pleroma, Misskey, or any ActivityPub-compatible platform can follow the site and see new posts in their timeline.

Outbound: Publishing to the Fediverse

When a post is created, the plugin converts JF2 content to an ActivityStreams 2.0 activity using jf2ToAS2Activity(). Fedifyโ€™s ctx.sendActivity() then delivers it to every followerโ€™s inbox. The conversion handles all post types โ€” articles become Article objects, notes become Note, likes become Like activities, and so on.

Inbound: Receiving from the Fediverse

Remote servers POST activities to the inbox. Fedify routes them to handlers for each activity type: Follow, Undo, Like, Announce (boost), Create (new post from a followed account), and Delete. Each handler updates the appropriate MongoDB collection.

The Reader

Following accounts on the fediverse populates a timeline. Their posts arrive as Create activities, are stored in ap_timeline, and rendered in a reader UI that supports composing, liking, boosting, and following โ€” all from the admin interface.

The Express-Fedify Bridge

One technical challenge: Fedify has an official @fedify/express adapter, but it doesnโ€™t work correctly with mounted sub-apps due to path resolution issues. The plugin uses a custom bridge that reconstructs req.originalUrl and POST bodies to make Express and Fedify communicate properly.

What It Looks Like

The plugin manages 13 MongoDB collections (ap_followers, ap_following, ap_activities, ap_keys, ap_kv, ap_profile, ap_featured, ap_featured_tags, ap_timeline, ap_notifications, ap_muted, ap_blocked, ap_interactions) and exposes:

Public endpoints for federation:

  • /.well-known/webfinger โ€” actor discovery
  • /.well-known/nodeinfo โ€” server metadata
  • /activitypub/users/* โ€” actor profile, inbox, outbox, collections
  • Content negotiation at / โ€” ActivityPub clients see AS2 JSON instead of HTML

Admin UI for management:

  • Dashboard, reader with timeline and compose
  • Profile editor, follower/following lists
  • Mastodon account import (migration)
  • Moderation (mute/block)

5. Inter-Plugin Relationships

Plugins donโ€™t exist in isolation. Several have meaningful relationships with each other:

The Homepage Builder as Aggregator of Aggregators

endpoint-homepage discovers sections from other plugins at runtime. It knows about CV (5 section types), GitHub, Funkwhale, Last.fm, Blogroll, Podroll, YouTube, and Microsub. The admin UI lets you drag and drop these sections into a layout โ€” single column, two-column with sidebar, or custom arrangements. When you save, it writes homepage.json, which triggers an Eleventy rebuild.

Microsub โ†” Blogroll

These two talk to each other. The Blogroll plugin can import feeds from Microsub channels, and when a new blog is added to the Blogroll, it notifies Microsub via webhook so the reader picks up the feed automatically.

The Publishing Chain

Content flows through a chain: Micropub (create the post) โ†’ Syndicate (trigger cross-posting) โ†’ Syndicators (deliver to platforms). The Micropub plugin preserves mp-syndicate-to targets in the post metadata. The syndicate endpoint polls for pending targets and dispatches to the appropriate syndicator. Each syndicator handles its platformโ€™s API natively.

LinkedIn Token Sharing

The LinkedIn endpoint and syndicator are separate plugins that share state through environment variables. The endpoint handles the OAuth flow (authorization, token refresh) and stores tokens. The syndicator reads those tokens to authenticate API calls. This separation means the OAuth complexity doesnโ€™t pollute the syndication logic.

The Three Social Outputs

When a post is published, it can reach three different networks simultaneously:

  • ActivityPub โ†’ fediverse (Mastodon, Pleroma, etc.)
  • Syndicators โ†’ social platforms (Bluesky, Mastodon API, LinkedIn)
  • Webmention sender โ†’ IndieWeb sites

Yes, Mastodon appears twice โ€” once via ActivityPub (federation, native) and once via the syndicator (API-based, for cases where you want explicit POSSE control). They serve different purposes.

6. Deployment Architecture

Everything runs inside a single Cloudron container. Three long-running processes share the filesystem:

nginx (:3000) โ€” The Entry Point

nginx serves the static site from /app/data/site, which is a symlink to the current Eleventy build. Media files come from /app/data/content/media/. All admin routes, Micropub endpoints, and plugin APIs are proxied to Indiekit on port 8080. It also handles legacy URL redirects (/content/ โ†’ clean URLs) and security headers (CSP, X-Frame-Options).

Indiekit (:8080) โ€” The Application

The Express server with 30+ plugins loaded via indiekit.config.js. It handles all dynamic operations: content creation, authentication, syndication, ActivityPub federation, and all 11 background sync processes. This is the only process that talks to MongoDB.

Eleventy โ€” The Site Builder

A file watcher on /app/data/content/. When files change, it triggers a full build. The build creates a timestamped directory in /app/data/releases/, then atomically swaps the /app/data/site symlink โ€” zero downtime. After building, it runs Pagefind for search indexing and notifies WebSub subscribers.

The Filesystem

/app/data/                    # writable, backed up by Cloudron
โ”œโ”€โ”€ config/                   # indiekit.config.js, env.sh, .secret
โ”œโ”€โ”€ content/                  # user posts organized by type
โ”‚   โ”œโ”€โ”€ articles/
โ”‚   โ”œโ”€โ”€ notes/
โ”‚   โ”œโ”€โ”€ photos/
โ”‚   โ”œโ”€โ”€ likes/
โ”‚   โ”œโ”€โ”€ pages/
โ”‚   โ””โ”€โ”€ media/                # uploaded images
โ”œโ”€โ”€ releases/                 # timestamped Eleventy builds
โ”œโ”€โ”€ site โ†’                    # symlink to current release
โ””โ”€โ”€ cache/                    # Eleventy build cache

MongoDB

Cloudron manages MongoDB. The database stores all state โ€” 30+ collections covering posts, blogroll data, Microsub feeds, webmentions, RSS items, listening history, scrobbles, CV data, homepage configuration, LinkedIn tokens, podcast episodes, and all 13 ActivityPub collections.

7. The Eleventy Theme Layer

The theme is where data becomes a website. Itโ€™s a standalone repository used as a Git submodule in the deployment.

Data Sources

Eleventyโ€™s _data/*.js files fetch data from three places:

Plugin APIs โ€” endpoints like /blogrollapi/*, /funkwhale/api/*, /lastfm/api/*, /podrollapi/*, /github/api/*, /cv/data.json, and /homepage/api/*. These are the aggregator plugins exposing their MongoDB data via HTTP.

External APIs โ€” YouTube Data API, GitHub REST API, Bluesky AT Protocol, and Mastodon API. These provide sidebar widgets with recent social activity that doesnโ€™t go through Indiekit.

Static config โ€” site.js (environment variables for site name, URL, author info), enabledPostTypes.js (which post types to show in navigation), homepageConfig.js (homepage layout), and cv.js (resume data).

Each data file follows the same pattern: try the Indiekit plugin API first, fall back to direct external API, return { source: "indiekit" | "api" | "error" } so templates can conditionally display content.

Templates

The template hierarchy is:

  • base.njk โ€” the HTML shell with <head>, navigation, footer, and conditional sidebar logic
  • home.njk โ€” the homepage with plugin-driven layout or default hero + recent posts
  • post.njk โ€” individual posts with full microformat markup (h-entry), Bridgy syndication content, webmentions, reply context
  • page.njk โ€” static slash pages with sidebar
  • fullwidth.njk โ€” full-width pages for rich HTML content (like the Architecture Explorer)

Components include the homepage builder (section router, sidebar widgets), author h-card, reply context for interactions, and the webmentions display (likes, reposts, replies with avatars).

IndieWeb Compliance

Every post is marked up with Microformats2: h-entry for posts, h-card for the author, h-feed for lists, h-cite for reply context. The <head> includes rel="me" links for identity verification, authorization and token endpoints for IndieAuth, and Micropub/Microsub endpoint discovery.

For syndication via Bridgy, posts include hidden content with emoji prefixes and target URLs that Bridgy reads when cross-posting to Bluesky and Mastodon.

Frontend Stack

  • Tailwind CSS with dark mode (.dark class toggle)
  • Alpine.js for interactive components (dropdowns, tabs, compose forms)
  • Pagefind for client-side search
  • lite-youtube-embed for lazy-loaded YouTube embeds
  • is-land for island architecture โ€” lazy-hydrated interactive widgets

8. Key Conventions

A few conventions keep the system consistent across 30+ plugins:

Dates are always ISO 8601 strings. new Date().toISOString(), never new Date(). The Nunjucks | date filter uses date-fns parseISO() which only accepts strings โ€” passing a Date object crashes the template. Every template guards date filters with {% if value %} to handle nulls.

ESM everywhere. All plugins use "type": "module" in their package.json. No CommonJS, no build step.

Three auth layers. IndieAuth for admin routes (the user logs in), JWT for background processes (the syndication poller authenticates itself), HTTP Signatures for ActivityPub (remote servers verify identity).

Dual storage by design. MongoDB stores state and metadata โ€” post records, aggregated content, ActivityPub data, configuration. The filesystem stores content โ€” the actual .md files that Eleventy builds into the site. This separation means you can wipe the database and still have your content, or rebuild the database from the filesystem.

The publish lifecycle. Updating a plugin follows a strict sequence: bump version in package.json โ†’ commit and push โ†’ npm publish (manual, requires OTP) โ†’ update the Dockerfile version โ†’ cloudron build --no-cache && cloudron update. Skipping a step means the change doesnโ€™t reach production.


The Big Picture

This is a personal web platform built from composable parts. At its core, itโ€™s the IndieWeb stack โ€” Micropub for publishing, webmentions for interactions, microformats for structured data. Layered on top is ActivityPub for fediverse federation, syndicators for platform cross-posting, and aggregators for pulling in external activity.

The result is a site that you fully own and control, that federates with the fediverse, cross-posts to social networks, aggregates your digital life, and serves as a static site for performance. All running inside a single container.

For the interactive version with navigable diagrams of each section, visit the Architecture Explorer.

๐Ÿ”— https://rmendes.net/articles/2026/02/25/deep-dive-inside-indiekit

Read more โ†’
0
0

So I just realised that the "tech princess" link on my website was broken, after I made my instagram disappear.

Anyway, here's the back story: I was going to React Day Berlin in November 2019, and I was so bored of going to tech conferences in jeans and a t-shirt, and wanted to be *hyper* femme, so I bought a huge ballgown and wore that to the conference. I also tried to encourage some other femmes to join me, but didn't have much luck.

It was literally to poke fun at the fact that tech conferences are all jeans and t-shirts or like suits. I wanted to do something different.

Photo of Emelia at the conference from over the left shoulder. She is holding a coffee cup with both hands, and facing away from the camera. 

Emelia is wearing a black corset, with lace puff sleeves, her hair is black and up in a fancy updo with a mixture of dark red and magenta highlights. At the bottom of the photo you can see the tulle skirt of her ballgown peaking through with pops of reds and pinks through black.

Behind Emelia is a man standing in business casual, his face blurred.

This photo was taken by the React Day Berlin 2019 event photographers (unfortunately uncredited by name).Photo of the full ballgown Emelia wore, the description is the same as the previous photo, but shows the full ballgown skirt. The skirt is pink, orange, and black tulle in overlapping layers. It is about 1.5m across with the hoop skirt and petticoats underneath.

The outfit is shown on a mannequin.
0
28
0