PHPackages                             lucamauri/activitywiki - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. lucamauri/activitywiki

ActiveMediawiki-extension[API Development](/categories/api)

lucamauri/activitywiki
======================

ActivityPub support for MediaWiki — integrate your wiki with the Fediverse via ActivityPub.

v1.1.0(1mo ago)30GPL-2.0-or-laterPHPPHP &gt;=8.0.0

Since Jul 12Pushed 1mo agoCompare

[ Source](https://github.com/lucamauri/ActivityWiki)[ Packagist](https://packagist.org/packages/lucamauri/activitywiki)[ Docs](https://github.com/lucamauri/ActivityWiki)[ RSS](/packages/lucamauri-activitywiki/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (4)Versions (3)Used By (0)

ActivityWiki
============

[](#activitywiki)

Integrate your MediaWiki instance with the Fediverse via ActivityPub.

Overview
--------

[](#overview)

ActivityWiki is a MediaWiki extension that enables wiki instances to participate in the Fediverse by implementing the [ActivityPub](https://www.w3.org/TR/activitypub/) W3C protocol. It allows Mastodon users and other Fediverse participants to follow your wiki and receive notifications when pages are created, edited, or deleted — directly in their Fediverse timeline.

> ℹ️ **Current status: v1.1.0 — first fully working release.** Identity (actor + WebFinger), Security (HTTP Signatures, including a documented key rotation path), Publishing (outbound delivery to followers, with a real database-backed outbox), and Receiving (inbox, `Follow`/`Undo` handling, a real database-backed followers collection) are all complete and verified live against real Fediverse traffic (Mastodon). All five ActivityPub-spec-required endpoints — actor, WebFinger, outbox, followers, inbox — are implemented and working end-to-end. See the Roadmap section below for what's deferred as a post-release fast-follow.

Requirements
------------

[](#requirements)

- MediaWiki 1.41 or later
- PHP 8.0 or later
- MariaDB / MySQL
- Apache or Nginx with the ability to add a rewrite rule (see [WebFinger routing](#webfinger-routing) below — this is required, not optional)

Installation
------------

[](#installation)

Choose whichever of the two methods below fits how you manage your MediaWiki installation. Both end up in the same place — an `ActivityWiki`directory inside your `extensions/` folder — so the remaining steps (2–4) are identical either way.

### Option A — Clone from GitHub

[](#option-a--clone-from-github)

```
cd /path/to/your/mediawiki/extensions
git clone https://github.com/lucamauri/ActivityWiki.git ActivityWiki
```

### Option B — Install from Packagist via Composer

[](#option-b--install-from-packagist-via-composer)

ActivityWiki is published on Packagist as [`lucamauri/activitywiki`](https://packagist.org/packages/lucamauri/activitywiki). This method only works if your MediaWiki installation is itself Composer-managed (i.e. you ran `composer install` at some point to set up core, rather than installing purely from a tarball/git clone with no `composer.json` at the wiki root — most modern installations qualify).

From your MediaWiki installation root (the directory containing the main `composer.json`, **not** the `extensions/` directory):

```
composer require lucamauri/activitywiki
```

This package declares `"type": "mediawiki-extension"` in its own `composer.json`, which the `composer/installers` plugin (bundled with MediaWiki core's own `composer.json` by default) recognises and uses to place the extension's files directly into `extensions/ActivityWiki/`automatically — no manual cloning or moving required.

> If `composer require` fails with something like "could not find a matching version" or complains about missing `composer/installers`, your MediaWiki installation likely isn't Composer-managed at the root level. In that case, use Option A instead — there is no functional difference between the two once the files are in place.

To upgrade later, either method works the same way going forward: `git pull` for Option A, or `composer update lucamauri/activitywiki` for Option B.

### 2. Add to LocalSettings.php

[](#2-add-to-localsettingsphp)

```
wfLoadExtension( 'ActivityWiki' );
```

### 3. Run database setup

[](#3-run-database-setup)

```
php maintenance/run.php update.php
```

### 4. Set up WebFinger routing

[](#4-set-up-webfinger-routing)

**This step is required.** Without it, the wiki is completely invisible to Fediverse search, even though every other endpoint works correctly. See [WebFinger routing](#webfinger-routing) below for full instructions.

Configuration
-------------

[](#configuration)

All configuration variables are optional. The defaults are shown below.

```
// Master switch — disable to pause all federation without uninstalling
$wgActivityWikiEnabled = true;

// Display name shown on Mastodon and other Fediverse clients
// Defaults to $wgSitename if not set
$wgActivityWikiActorName = null;

// The handle for the wiki actor (the part before @domain)
// Defaults to a slugified $wgSitename if not set
$wgActivityWikiActorUsername = null;

// Short bio shown on Mastodon profile
$wgActivityWikiActorSummary = '';

// Absolute URL to the wiki logo used as the actor avatar
// Defaults to $wgFavicon if not set
$wgActivityWikiActorIcon = null;

// RSA key size in bits for HTTP Signature key pair generation
// Minimum 2048, recommended 4096
$wgActivityWikiKeySize = 2048;

// Namespaces to federate — default is main namespace only
$wgActivityWikiPublishNamespaces = [ NS_MAIN ];

// Which event types to federate
$wgActivityWikiPublishCreations   = true;
$wgActivityWikiPublishEdits       = true;
$wgActivityWikiPublishDeletions   = true;
$wgActivityWikiPublishMoves       = true;
$wgActivityWikiPublishMinorEdits  = false; // Minor edits suppressed by default
$wgActivityWikiPublishProtections = false;

// Maximum plain-text excerpt length in characters included in activities
$wgActivityWikiExcerptLength = 500;

// Number of retry attempts for failed HTTP deliveries (outbound activities
// and Accept replies alike)
$wgActivityWikiDeliveryRetries = 3;

// Maximum number of activities returned by the outbox endpoint
// (most recent first). Does not affect the reported "totalItems" count,
// which always reflects the true total number of activities.
$wgActivityWikiOutboxLimit = 20;

// Enable per-user ActivityPub actors. Post-MVP feature — do not enable
// in production; not yet implemented.
$wgActivityWikiEnableUserActors = false;

// Debug logging (0 = off, 1 = verbose)
$wgActivityWikiDebugLevel = 0;
```

To see ActivityWiki's debug log output, add a dedicated log channel in `LocalSettings.php`:

```
$wgDebugLogGroups['ActivityWiki'] = '/path/to/your/logs/ActivityWiki.log';
```

WebFinger routing
-----------------

[](#webfinger-routing)

The WebFinger protocol (RFC 7033) requires that requests be served from a fixed, well-known path at the **root of your domain**:

```
https://yourdomain.org/.well-known/webfinger?resource=acct:user@yourdomain.org

```

This path is outside MediaWiki's normal URL space, and MediaWiki cannot serve it on its own. Setting this up correctly requires two things: a small entry-point script, and a web-server rewrite rule pointing to it. Both are described below.

> Throughout this section, replace `/path/to/your/wiki/` with wherever your MediaWiki installation actually lives (the directory containing `rest.php`), and replace `/your-script-path/` with your own `$wgScriptPath` (e.g. `/w`, `/wiki`, or empty if MediaWiki is installed at your domain root). These vary per installation — there is no universal default.

### Why a separate entry-point file is needed

[](#why-a-separate-entry-point-file-is-needed)

MediaWiki's REST router validates that every incoming `REQUEST_URI` starts with the REST base path (e.g. `/your-script-path/rest.php`). A plain web-server-level rewrite from `/.well-known/webfinger` straight to the REST path is **not enough on its own** — the rewrite happens after PHP has already read `$_SERVER['REQUEST_URI']`, so MediaWiki's router still sees the original, unrewritten URI and rejects the request with a `rest-prefix-mismatch` error, even though the route and handler (`WebFingerHandler`) are correctly registered.

The fix is `entry-points/webfinger.php`, included with this extension. It corrects `$_SERVER['REQUEST_URI']` itself *before* MediaWiki boots, then hands off entirely to `rest.php`. It contains no business logic of its own — all actual WebFinger handling still happens in `WebFingerHandler.php`.

> **Future improvement:** A MediaWiki core patch to accept configurable REST base path validation would make this entry-point file unnecessary. Worth contributing upstream separately.

### Step 1 — Copy the entry-point file and set your script path

[](#step-1--copy-the-entry-point-file-and-set-your-script-path)

Copy `entry-points/webfinger.php` from the extension to the same directory as `rest.php`:

```
cp extensions/ActivityWiki/entry-points/webfinger.php /path/to/your/wiki/webfinger.php
```

Open the copied file and confirm the rewritten path matches your `$wgScriptPath`:

```
$_SERVER['REQUEST_URI'] = '/your-script-path/rest.php/activitywiki/webfinger'
    . ( /* ...query string handling... */ );
```

Replace `/your-script-path/` with your actual `$wgScriptPath` (for example, if `$wgScriptPath = '/w'`, this line should read `'/w/rest.php/activitywiki/webfinger'`).

### Step 2 — Add the web-server rewrite rule

[](#step-2--add-the-web-server-rewrite-rule)

#### Apache

[](#apache)

Add the following rule inside the `` block that serves your wiki, **before** any existing MediaWiki rewrite rules:

```
# ActivityWiki — WebFinger discovery endpoint
RewriteRule ^\.well-known/webfinger$ %{DOCUMENT_ROOT}/webfinger.php [QSA,L]
```

> ⚠️ **Do not anchor the pattern with a leading `/`.** Inside a `` block (as opposed to an `.htaccess` file), Apache's `RewriteRule` matches the URL path **without** a leading slash. A pattern written as `^/\.well-known/webfinger$` will never match — Apache will silently fall through to its normal file-serving logic and return a generic Apache 404 page, with nothing logged anywhere that points to the actual cause. If `%{DOCUMENT_ROOT}/webfinger.php` is not where you copied the file in Step 1 (for example, if your `DocumentRoot` is a parent directory of your wiki installation rather than the installation directory itself), adjust the target path accordingly.

The `QSA` flag (Query String Append) passes the `?resource=...` parameter through. The `L` flag stops further rule processing for this request.

#### Nginx

[](#nginx)

Add the following to your `server` block:

```
# ActivityWiki — WebFinger discovery endpoint
location = /.well-known/webfinger {
    fastcgi_param REQUEST_URI /your-script-path/rest.php/activitywiki/webfinger;
    fastcgi_param QUERY_STRING $query_string;
    # ... your existing fastcgi_pass and other fastcgi_param settings
}
```

Replace `/your-script-path/` with your `$wgScriptPath`, matching Step 1. This approach rewrites `REQUEST_URI` directly via FastCGI parameters, so the `entry-points/webfinger.php` file is not needed for the Nginx path.

#### Behind a reverse proxy (Anubis, Varnish, Cloudflare, etc.)

[](#behind-a-reverse-proxy-anubis-varnish-cloudflare-etc)

If your setup uses a reverse proxy in front of your web server, make sure `/.well-known/` paths are passed through without being blocked or challenged. Most proxy configurations already allow this for Let's Encrypt compatibility, but bot-detection layers (e.g. Anubis) can sometimes intercept paths that look unusual. If WebFinger works when tested directly against your web server but not from the public internet, check your proxy's passthrough or allow-list rules for `/.well-known/`.

### Step 3 — Reload your web server

[](#step-3--reload-your-web-server)

```
sudo systemctl reload apache2   # or: sudo systemctl reload nginx
```

### Step 4 — Verify

[](#step-4--verify)

Request the well-known URL directly:

```
curl -v "https://yourdomain.org/.well-known/webfinger?resource=acct:yourusername@yourdomain.org"
```

A **correct** response is `200 OK`, with `Content-Type: application/jrd+json`, and a JSON body shaped like:

```
{
    "subject": "acct:yourusername@yourdomain.org",
    "aliases": [
        "https://yourdomain.org/your-script-path/rest.php/activitywiki/actor"
    ],
    "links": [
        {
            "rel": "self",
            "type": "application/activity+json",
            "href": "https://yourdomain.org/your-script-path/rest.php/activitywiki/actor"
        },
        {
            "rel": "http://webfinger.net/rel/profile-page",
            "type": "text/html",
            "href": "https://yourdomain.org/your-script-path/index.php"
        }
    ]
}
```

An unrecognized resource correctly returns a 404 with a small JSON error body (e.g. `{"error":"Resource not found on this server."}`) — that's expected and fine.

#### Troubleshooting

[](#troubleshooting)

SymptomLikely causeFix`404 Not Found`, `Content-Type: text/html`, Apache/Nginx server signature in the bodyRewrite rule isn't matching — most commonly the leading-slash mistake described aboveCheck the `RewriteRule`/`location` pattern; confirm it's in the `` block that actually serves your public HTTPS traffic`500`, JSON body mentioning `rest-prefix-mismatch``entry-points/webfinger.php` missing, in the wrong location, or its hardcoded script path doesn't match `$wgScriptPath`Re-check Step 1`500`, JSON body mentioning `ArgumentCountError` or similar`routes.json`'s `webfinger` route is missing a required service, or extension files are out of dateConfirm you're running the current version of the extension; check `routes.json` lists all services `WebFingerHandler`'s constructor requiresWorks when curled directly against the server but not from the public internetA reverse proxy is intercepting or challenging the requestCheck your proxy's passthrough rules for `/.well-known/``404` with a small JSON body (not HTML)This is MediaWiki's own "resource not found" responseCheck that the `resource` parameter's username and domain match your actual `$wgActivityWikiActorUsername` and `$wgServer`Key rotation
------------

[](#key-rotation)

ActivityWiki's HTTP Signature key pair (used to sign every outbound activity, and served publicly as part of the actor object) is generated automatically on first install — you don't need to do anything for normal operation. A dedicated maintenance script, `maintenance/GenerateKeys.php`, exists for two situations:

- **First-time setup didn't generate a key for some reason** (e.g. it was skipped, or OpenSSL failed at that point and you've since fixed the underlying cause). Running the script with no key present is safe and non-destructive.
- **Deliberate rotation** — e.g. you suspect the private key has been compromised, or you've increased `$wgActivityWikiKeySize` and want to regenerate. This is destructive: the existing key pair is replaced, and is gated behind an explicit `--force` flag so it can never happen by accident.

```
# First-time generation, or check current state (safe, no key yet):
php maintenance/run.php extensions/ActivityWiki/maintenance/GenerateKeys.php

# Deliberate rotation of an existing key:
php maintenance/run.php extensions/ActivityWiki/maintenance/GenerateKeys.php --force

# Rotation, printing the new public key PEM afterward for verification:
php maintenance/run.php extensions/ActivityWiki/maintenance/GenerateKeys.php --force --show
```

> ⚠️ **Rotation is immediate and has real consequences.** The moment the new key is stored, every outbound HTTP Signature is signed with it. Any Fediverse server that has cached your *old* public key (some do cache aggressively) may continue rejecting your signed requests until that cache expires on their end — there is no way to force this from your side. This is expected and self-resolves; it is not a bug in the rotation itself.

Restart PHP-FPM after running the script, as a precaution (the script will remind you).

Repository Structure
--------------------

[](#repository-structure)

```
ActivityWiki/
├── extension.json              # Extension metadata, config, and service/job wiring
├── composer.json                # PHP dependencies
├── README.md                    # This file
├── entry-points/
│   └── webfinger.php            # Required manual install step — see WebFinger routing
├── i18n/                         # Localisation files
├── src/
│   ├── ServiceWiring.php        # Registers all ActivityWiki services
│   ├── Hooks.php                 # Page save/edit/delete event hooks
│   ├── ActivityBuilder.php       # Builds outbound ActivityPub activity arrays
│   ├── WikiActorUrls.php         # Shared wiki base-URL / actor-URL building logic
│   │                              #   (consolidated from four classes)
│   ├── ActorFetcher.php          # Shared remote actor document fetching
│   │                              #   (consolidated from SignatureVerifier/FollowManager)
│   ├── DeliveryQueue.php         # Persists outbound activities, enqueues delivery jobs
│   ├── KeyManager.php            # RSA key pair generation and storage
│   ├── HttpSigner.php            # Signs outbound HTTP requests
│   ├── SignatureVerifier.php     # Verifies incoming HTTP Signatures
│   ├── FollowManager.php         # Follow/Undo business logic for the inbox
│   ├── Api/
│   │   └── ActivityPubModule.php # Shared actor/URL-building helpers
│   ├── Jobs/
│   │   ├── DeliveryJob.php       # Async fan-out delivery to all followers
│   │   └── AcceptJob.php         # Async single-recipient delivery (Accept replies)
│   └── Rest/
│       ├── ActorHandler.php      # GET  /activitywiki/actor
│       ├── WebFingerHandler.php  # GET  /activitywiki/webfinger
│       ├── OutboxHandler.php     # GET  /activitywiki/outbox (real data, see $wgActivityWikiOutboxLimit)
│       ├── FollowersHandler.php  # GET  /activitywiki/followers (real data)
│       ├── InboxHandler.php      # POST /activitywiki/inbox
│       └── routes.json           # REST route definitions
├── db/                            # Database schema files
└── maintenance/
    └── GenerateKeys.php           # Manual key (re)generation — see Key rotation

```

Roadmap
-------

[](#roadmap)

### Phase 0 — Audit fixes ✅ Complete

[](#phase-0--audit-fixes--complete)

- Fixed all blockers and compatibility issues found in the initial audit
- Modernised deprecated MW API calls
- Renamed all config keys to the `ActivityWiki*` prefix

### Phase 1 — Identity ✅ Complete

[](#phase-1--identity--complete)

- Actor object endpoint
- WebFinger endpoint for Fediverse discoverability

### Phase 2 — Security ✅ Complete

[](#phase-2--security--complete)

- RSA key pair generation and storage
- HTTP Signature signing on all outbound requests

### Phase 3 — Publishing ✅ Complete

[](#phase-3--publishing--complete)

- Full event hook coverage (create, edit, delete, move)
- Async HTTP delivery to follower inboxes, with per-follower retry
- Outbox endpoint completion

### Phase 4 — Receiving ✅ Complete

[](#phase-4--receiving--complete)

- Inbox endpoint
- `Follow` / `Undo{Follow}` handling
- Followers collection serving real, database-backed data

### Phase 5 — Administration &amp; consolidation ✅ Mostly complete

[](#phase-5--administration--consolidation--mostly-complete)

- Shared URL-building logic consolidated into `WikiActorUrls`
- Shared remote actor fetching consolidated into `ActorFetcher`
- Real database-backed outbox (`$wgActivityWikiOutboxLimit`)
- `maintenance/GenerateKeys.php` — manual key generation and `--force`-gated rotation (see [Key rotation](#key-rotation))
- Still planned: `Special:ActivityWiki` status page and MediaWiki log integration (see Post-release fast-follows below)

### Post-release fast-follows (not blockers)

[](#post-release-fast-follows-not-blockers)

- Actor caching (`activitywiki_actor_cache`)
- Full outbox pagination (`OrderedCollectionPage` with `first`/`next`/`prev` links) — the outbox currently returns the most recent activities up to `$wgActivityWikiOutboxLimit`, with an accurate `totalItems`, but no cursor pagination
- `Special:ActivityWiki` status page and related MediaWiki log integration

### Post-MVP (deferred, not scoped)

[](#post-mvp-deferred-not-scoped)

- Per-user ActivityPub actors (`$wgActivityWikiEnableUserActors`)
- Extended inbox activity handling (`Like`, `Announce`, reply mentions)

Troubleshooting
---------------

[](#troubleshooting-1)

### Check the actor endpoint is reachable

[](#check-the-actor-endpoint-is-reachable)

```
curl -H "Accept: application/activity+json" "https://yourdomain.org/your-script-path/rest.php/activitywiki/actor"
```

Should return actor JSON, not a 404.

### Check WebFinger is reachable at the public well-known path

[](#check-webfinger-is-reachable-at-the-public-well-known-path)

```
curl -v "https://yourdomain.org/.well-known/webfinger?resource=acct:yourusername@yourdomain.org"
```

See the [WebFinger routing](#webfinger-routing) troubleshooting table above if this doesn't return a correct JSON response.

### Check debug logs

[](#check-debug-logs)

Set `$wgActivityWikiDebugLevel = 1;` and configure a log channel (see Configuration above), then:

```
tail -f /path/to/your/logs/ActivityWiki.log
```

License
-------

[](#license)

GPL-2.0-or-later. See LICENSE file for details.

References
----------

[](#references)

- [ActivityPub Specification](https://www.w3.org/TR/activitypub/)
- [WebFinger (RFC 7033)](https://www.rfc-editor.org/rfc/rfc7033)
- [MediaWiki Extension Development](https://www.mediawiki.org/wiki/Manual:Developing_extensions)

---

In the spirit of IDIC 🖖🏻 forged at [WikiTrek](https://wikitrek.org), shared freely with the Galaxy

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance92

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

37d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/dd8777d6a2cb6716bb24cef15643edf8f463a9ff9de1c3c7efeda0f1111cf342?d=identicon)[lucamauri](/maintainers/lucamauri)

---

Top Contributors

[![lucamauri](https://avatars.githubusercontent.com/u/433582?v=4)](https://github.com/lucamauri "lucamauri (46 commits)")

---

Tags

activitypubfediversemediawiki-extensionsocial-webwebfingermediawikiFederationmastodonactivitypubfediverse

###  Code Quality

TestsPHPUnit

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/lucamauri-activitywiki/health.svg)

```
[![Health](https://phpackages.com/badges/lucamauri-activitywiki/health.svg)](https://phpackages.com/packages/lucamauri-activitywiki)
```

###  Alternatives

[landrok/activitypub

A PHP implementation of ActivityPub protocol based upon the ActivityStreams 2.0 data format.

29381.9k4](/packages/landrok-activitypub)[revolution/laravel-mastodon-api

Mastodon API for Laravel

2785.0k2](/packages/revolution-laravel-mastodon-api)[surface/laravel-webfinger

A Laravel package to create an ActivityPub webfinger.

114.1k](/packages/surface-laravel-webfinger)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
