PieFed joins the pasture; the instance actor answers at the root

PieFed 1.7.17 (dockurr's image of the release) runs in the pasture with its
Celery worker on the shared Postgres and Redis, and scenarios/piefed.sh
checks it both ways: 29 checks, communities, titled threads, comments, votes
up and down, a community poll and a vote in it, private messages, a
moderator's lock, unlock and removal, the unfollow and statistics.

What it showed:
- PieFed sends a community's announces to the inbox of the Application at a
  peer's root (as Lemmy serves its site actor) and to /inbox otherwise.
  PrivaPub answered 404 at its root, so every announce went to an /inbox it
  does not have. The instance actor now answers at / for ActivityPub
  requests, unsigned under SecureMode as at its own address.
- PieFed keeps serving a thread its moderator removed, so the removal could
  never be checked against the post's origin. A community on the post's own
  server now speaks for it; one elsewhere still waits for the origin to say
  the post is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 15:44:13 +02:00
1 parent 0646de22bd
commit 47d6e22988
12 files changed
+304 -17

No files matched your search

@@ -74,6 +74,17 @@ namespace PrivaPub.Federation.Controllers
return Activity(ActivityPubRenderer.Actor(local));
}
// The instance actor at the server's root, for whoever asks for ActivityPub there: PieFed reads the inbox it sends a
// community's announces to from the Application at a peer's root, as Lemmy serves its own, and assumes /inbox otherwise
[HttpGet, Route("/")]
public async Task<IActionResult> Root(CancellationToken token)
{
var accept = Request.Headers.Accept.ToString();
if (!accept.Contains("activity+json", StringComparison.OrdinalIgnoreCase) && !accept.Contains("ld+json", StringComparison.OrdinalIgnoreCase))
return NotFound();
return Activity(ActivityPubRenderer.Actor(await _localActors.GetInstanceActor(token)));
}
[HttpGet, Route("{actor}/anus")]
public async Task<IActionResult> Outbox(string actor, [FromQuery] bool page, [FromQuery(Name = "max_id")] string maxId,
CancellationToken token)
@@ -448,7 +459,7 @@ namespace PrivaPub.Federation.Controllers
Response.Headers.Vary = "Accept";
// SecureMode asks every reader of ActivityPub documents for a signature, except for the instance actor, whose key
// peers need first, and except for browsers, which only get redirected to the public pages
if (_federation.CurrentValue.SecureMode && HttpMethods.IsGet(Request.Method) && !WantsHtml()
if (_federation.CurrentValue.SecureMode && HttpMethods.IsGet(Request.Method) && !WantsHtml() && Request.Path.Value != "/"
&& !string.Equals(context.RouteData.Values["actor"] as string, LocalActorService.InstanceUserName, StringComparison.OrdinalIgnoreCase)
&& await _fetches.Requester(Request, HttpContext.RequestAborted) == default)
{
@@ -198,12 +198,15 @@ namespace PrivaPub.Federation.Inbox.Handlers
return;
}
Arrival.About(visibility: deleted.Visibility, created: deleted.CreationDate);
using (var check = await _remoteActors.FetchObject(objectUri, token))
if (check != default && Value(System.Text.Json.Nodes.JsonNode.Parse(check.Root.GetRawText()), "type") != "Tombstone")
{
Arrival.Drop("not-deleted");
return;
}
// a community on the post's own server speaks for it: PieFed keeps serving a thread its moderator removed.
// One elsewhere is believed once the post's server says it is gone
if (!Origin.Same(objectUri, group.ActorURI))
using (var check = await _remoteActors.FetchObject(objectUri, token))
if (check != default && Value(System.Text.Json.Nodes.JsonNode.Parse(check.Root.GetRawText()), "type") != "Tombstone")
{
Arrival.Drop("not-deleted");
return;
}
Arrival.Accept("removed");
await RemoteDeletes.Remove(deleted, objectUri, token);
break;