Hollo 0.9.0: Redesigned UI, passkey authentication, FEP-044f quote authorization, and major performance improvements #496
dahlia
announced in
Announcements
Replies: 1 comment
-
|
Wow this was a huge release! Thank you for your hard work. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Hollo is a single-user, headless ActivityPub server. It exposes a Mastodon-compatible API with no built-in frontend, so you can connect any Mastodon client of your choice. It's built on Fedify and designed for people who want to run their own fediverse presence without operating a full multi-user instance.
Version 0.9.0 is the largest release since 0.7.0. The most visible change is a complete redesign of every server-rendered page. Beyond that: passkeys, full FEP-044f quote authorization, a configurable media proxy, optional split-domain WebFinger, and a collection of serious database performance fixes that were affecting larger instances.
Redesigned UI (#458)
Every server-rendered page has been rebuilt from scratch. Pico CSS and its 22 generated theme stylesheets are gone. The new design system, documented in DESIGN.md, is styled through UnoCSS (Wind4, Icons, Typography, and Web Fonts presets) and generates a single uno.css file instead.
The visual language is achromatic by default. Each account owner's chosen theme color—selected from the 20-swatch picker on the account editor—injects
--theme-50through--theme-950CSS custom properties on<html>, which tint that account's profile, post, hashtag, and dashboard pages. The dashboard chrome itself stays neutral.Web Fonts load from bunny.net: Inter for Latin, Noto Sans KR/JP/SC for CJK, JetBrains Mono for code. Lucide icons replace the previous ad-hoc iconography. Dark mode follows
prefers-color-scheme: darkautomatically; primary buttons usebrand-600(stepping tobrand-700on dark surfaces) so they don't dominate dark backgrounds.Posts render with attached media in a two-column grid, polls as brand-colored progress bars, quoted posts as an inset card, and link previews as a thumbnail/title/description card. The account editor gained avatar and header image upload with drag-and-drop and in-page preview. Every public and dashboard page was rebuilt: login, setup, OTP, the public home, account profiles, single-post permalinks, the hashtag stream, the account list and editor, custom emojis, federation, thumbnail cleanup, the OAuth consent screen, and the 2FA panel.
Public profile pages now also include dedicated Followers and Following pages (#491), and each local post links to reaction list pages for likes, boosts, emoji reactions, and quotes (#490). See “Other changes” below for details.
Passkey (WebAuthn) authentication (#487)
The admin Auth page has a new Passkeys section for enrolling and managing passkeys. The login page shows a “Sign in with passkey” button—with the email/password form tucked behind a toggle—whenever at least one passkey is enrolled. Both device-bound and synced (multi-device) passkeys are accepted.
A successful passkey sign-in counts as multi-factor authentication, so the TOTP step is skipped in the same session. Registration uses
residentKey: requiredanduserVerification: required, meaning every enrolled passkey is discoverable and tied to a biometric or PIN gesture. Registration challenges are bound to the current login session with a 5-minute TTL. Login challenges are stored as single-use records so a captured cookie and assertion pair can only be redeemed once.FEP-044f quote authorization (#457, #459, #460)
Quote posts now implement FEP-044f end-to-end.
On the federation side, Hollo publishes
quote,quoteAuthorization, andinteractionPolicy.canQuoteon ActivityPub objects and handles incomingQuoteRequest,Accept(QuoteRequest),Reject(QuoteRequest), andDelete(QuoteAuthorization)activities. Quote states (pending,accepted,rejected,revoked,unauthorized) are persisted, and dereferenceable localQuoteAuthorizationobjects are published for accepted quotes. The legacyquoteUrlproperty is kept for compatibility with software that doesn't yet implement FEP-044f.On the API side,
POST /api/v1/statusesnow enforces quote policy, target visibility, block relationships, and follower-only permissions when creating a quote. A newPUT /api/v1/statuses/:id/interaction_policyendpoint lets you update a post's quote policy after publication. Remote posts without an advertised FEP-044f policy are treated as legacy quote targets and don't require authorization.quotes_countnow reflects only accepted quotes and updates when quotes are accepted, rejected, revoked, created, deleted, or received through federation.Outbound quote posts include a
quote-inlinefallback paragraph in the HTMLcontentso software without quote support can still show the quoted URL. Incomingquote-inlineparagraphs are hidden from API responses and profile pages when Hollo can render the structured quote, but remain visible if the quoted post is unavailable.Media proxy (#481, #483, #493)
A new
MEDIA_PROXYenvironment variable controls how remote media URLs are delivered to clients. Three levels:off(default): the API and web UI pass original remote URLs to clients, matching historical behavior.proxy: every remote media URL is rewritten to a signed/proxy/<sig>/<b64url>path served by Hollo itself. The proxy runs SSRF checks on the upstream URL and every redirect target, allows only image/video/audio content types (SVG is blocked to prevent same-origin XSS), caps responses at 32 MiB, and serves withCache-Control: public, max-age=2592000, immutableandX-Content-Type-Options: nosniff. No on-disk cache.cache: same URL rewriting, but the streamed body is persisted to the configured storage backend atproxy/<sha256>.bin. Subsequent requests skip the upstream fetch. Remote actor avatars for accounts with an approved follow relationship are also prefetched into this cache when the actor is stored or refreshed.true/on/1alias toproxy;false/off/0tooff. Disk caching is opt-in only via the explicitcachevalue. Outbound federation is unaffected: Hollo still publishes original remote URLs in ActivityPub objects.A companion variable,
REMOTE_MEDIA_THUMBNAILS, controls whether Hollo downloads incoming remote attachments to generate local WebP thumbnails. Set tooffto use the remote URL directly as the thumbnail instead, which pairs naturally withMEDIA_PROXY=proxyorcacheto avoid redundant storage.Split-domain WebFinger (#161, #484)
Two new environment variables,
HANDLE_HOSTandWEB_ORIGIN, enable split-domain WebFinger. When set, fediverse handles (e.g.@alice@example.com) and ActivityPub actor URIs (e.g.https://ap.example.com/@alice) can live on separate domains. The/api/v1/instanceand/api/v2/instanceendpoints exposeHANDLE_HOSTas the instance domain so clients display the correct handle.Both variables must be set together and configured before the first account is created. Changing the handle domain after federation has begun breaks existing remote follow relationships; Hollo logs a warning at startup if the configured
HANDLE_HOSTdoesn't match the stored account's handle. The reverse proxy on the handle domain must redirect/.well-known/webfingertoWEB_ORIGIN. The split-domain WebFinger guide covers the full setup.Performance
Several database performance problems affecting larger instances are fixed in this release.
Profile page queries (#489):
posts.actor_idhad no composite index withpublished, so timeline queries ordering bypublished DESCperformed a full sort of all matching rows. A B-tree index on(actor_id, published)is now added by migration. Separately, every post list query used a lateral join to eagerly load all direct replies per post, even though only the denormalizedreplies_countcounter is used in API serialization. The lateral join is removed from all list-context queries. On a cold PostgreSQL buffer cache, profile page load times were measured in the hundreds of seconds before these fixes.NodeInfo endpoint (#488): The endpoint was performing a sequential scan of the entire
poststable on every request, taking 15–20 seconds on large instances (e.g., 4M rows / 13 GB). A B-tree index onposts.updatedis now added by migration, theactiveMonth/activeHalfyearqueries are rewritten from a full-scanRIGHT JOINto anEXISTSsemi-join with a composite index on(actor_id, updated), and a 5-minute in-process TTL cache absorbs repeated polls from external directories and health checkers.Hashtag timelines: A GIN index on
posts.tagsis now added, cutting what were full sequential scans down to index lookups for all hashtag-related queries (hashtag timelines, featured tag counts, profile hashtag filters, and tag pages).Timeline loading and pagination: Home, list, public, and hashtag timelines now fetch matching post IDs first, then hydrate only those posts, so filtered and empty-cursor checks avoid the heavier relation-join query.
min_idnow returns posts immediately newer than the cursor rather than the most recent posts above it, matching Mastodon's semantics for gap-loading clients like SubwayTooter.since_idis now honored on all timeline endpoints.rel="prev"is included inLinkheaders alongsiderel="next", andrel="next"is now kept even when a bounded gap page returns fewer statuses than the requested limit. Thelimitparameter is capped at 1000. (#479, #482, #492)Query cancellation: PostgreSQL queries are now cancelled when the corresponding
GETorHEADrequest is aborted, such as when a client disconnects mid-load. This reduces wasted database work and helps keep the connection pool available under repeated client-side aborts.Trigram search indexes: Search queries on handles, display names, and post content now use PostgreSQL trigram indexes via the
pg_trgmextension, improvingILIKEsearch performance. The extension is enabled automatically when the migration runs.TIMELINE_INBOXESnow defaults totrue. Set it tofalseexplicitly to opt out. The flag will be removed entirely in Hollo 1.0.0.Other changes
followingcollection is hidden andGET /api/v1/accounts/:id/followingreturns an empty array. The toggle also round-trips throughPATCH update_credentialsashide_collections./@:handle/:id/likes), boosts (/@:handle/:id/shares), emoji reactions (/@:handle/:id/reactions/:emoji), and quotes (/@:handle/:id/quotes). The per-post indicators on the profile feed and post permalink page now link into these pages for local posts.PATCH /api/v1/accounts/update_credentialsalso now accepts up to 10 fields viafields_attributes[0]throughfields_attributes[9].LOG_FILE_FORMAT: A new environment variable controlling whetherLOG_FILEoutput is written as JSON Lines (default,jsonl) or logfmt (logfmt).must be owner of table __drizzle_migrationsif your Hollo database user doesn't own Drizzle's migration log table. SeeCHANGES.mdfor the SQL commands to transfer ownership before retrying.tokenRequiredmiddleware now uses a lightweight single-table lookup instead of a complex multi-table JOIN. Account owner data is fetched on demand only by routes that actually need it, and requests that fail scope validation no longer touch the accounts table. (Improve performance oftokenRequiredmiddleware #127, OptimizetokenRequiredmiddleware performance #467)preview_card. Posts with attached media or shown as quoted posts hide the preview to avoid visual clutter.PATCH /api/v1/accounts/update_credentialssilently wiping all custom fields whenfields_attributeswas absent. Fixed preview card generation for remote posts whose content links only to mentioned accounts (e.g., NodeBB posts that emit plain profile links without amentionclass). Fixed a crash when persisting ActivityPub posts with apublisheddate before the Unix epoch. Fixed a performance bug where saving the account editor always triggered a network lookup of @hollo@hollo.social regardless of whether the news-following setting had actually changed.zh-TWdocumentation.Upgrading
Docker:
Railway: Go to your Railway dashboard, select your Hollo service, and click Redeploy from the deployments menu.
Manual installation:
Note
The first run of
pnpm run migrateafter upgrading may fail withmust be owner of table __drizzle_migrationsif your Hollo database user doesn't own Drizzle's migration log table. SeeCHANGES.mdfor the SQL commands to transfer ownership before retrying with the normalDATABASE_URL.Thank you
This release is primarily maintainer work. Thanks to everyone who filed issues, tested pre-release builds, and reported bugs.
Beta Was this translation helpful? Give feedback.
All reactions