Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ Add-on skills (activated when corresponding add-on is installed):
- `funkwhale.md` — Funkwhale federated music pod: library listing, search, upload, follow remote channels/libraries, playlists, listening history, moderation (block_user/mute inline; block_domain/defederate queued), media prune; on-disk or S3 audio storage via storage-translators.funkwhale()
- `pixelfed.md` — Pixelfed federated photo-sharing: post photos (upload+status), feed, search, follow, moderation (block_user/mute inline; block_domain/defederate/import_blocklist queued), admin reports, remote reporting, media prune; Mastodon-compatible REST API; on-disk or S3 media via storage-translators.pixelfed()
- `lemmy.md` — Lemmy federated link aggregator: status, list/follow/unfollow communities, post (link + body), comment, feed (Subscribed/Local/All), search, moderation (block_user/block_community inline; block_instance/defederate queued), admin reports, pict-rs media prune; Lemmy v3 REST API; community-scoped federation
- `mastodon.md` — Mastodon federated microblog (flagship ActivityPub): status, post, post_with_media (async media upload), feed (home/public/local/notifications), search, follow/unfollow, moderation (block_user/mute inline; defederate/import_blocklist queued admin), admin reports, remote reporting, media prune (tootctl); Mastodon v1/v2 reference API; on-disk or S3 media via storage-translators.mastodon()
- `calibre-server.md` — Calibre content server: search, browse, download ebooks via OPDS
- `calibre-web.md` — Calibre-Web reader: search, shelves, reading status, download
- `miniflux.md` — Miniflux RSS reader: subscribe feeds, read articles, star, mark read
Expand Down
207 changes: 207 additions & 0 deletions bundles/mastodon/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Mastodon — flagship ActivityPub microblogging server.
#
# Five-container bundle: web (Rails/Puma) + streaming (Node) + sidekiq
# (background jobs) + postgres + redis. web, streaming, and sidekiq on
# crow-federation (Caddy reverse-proxies to web + streaming); DB/redis
# on default.
#
# Data:
# ~/.crow/mastodon/postgres/ Postgres data dir
# ~/.crow/mastodon/redis/ Redis persistence
# ~/.crow/mastodon/system/ Active-storage media uploads (if on-disk)
#
# Media: on-disk by default. Set MASTODON_S3_* to route to MinIO /
# external S3 — storage-translators.mastodon() maps to S3_* envelope
# (documented at https://docs.joinmastodon.org/admin/optional/object-storage/).
# configure-storage.mjs in scripts/ handles this at install time.
#
# Image: ghcr.io/mastodon/mastodon:v4.3.0 (pin semver at impl time;
# check release feed + CVE advisories before merge). Mastodon 4.3+
# splits web and streaming into separate images
# (ghcr.io/mastodon/mastodon-streaming).

networks:
crow-federation:
external: true
default:

services:
postgres:
image: postgres:15-alpine
container_name: crow-mastodon-postgres
networks:
- default
environment:
POSTGRES_USER: mastodon
POSTGRES_PASSWORD: ${MASTODON_DB_PASSWORD}
POSTGRES_DB: mastodon_production
volumes:
- ${MASTODON_DATA_DIR:-~/.crow/mastodon}/postgres:/var/lib/postgresql/data
init: true
mem_limit: 512m
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mastodon -d mastodon_production"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s

redis:
image: redis:7-alpine
container_name: crow-mastodon-redis
networks:
- default
volumes:
- ${MASTODON_DATA_DIR:-~/.crow/mastodon}/redis:/data
init: true
mem_limit: 256m
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 10

web:
image: ghcr.io/mastodon/mastodon:v4.3.0
container_name: crow-mastodon-web
networks:
- default
- crow-federation
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
RAILS_ENV: production
NODE_ENV: production
LOCAL_DOMAIN: ${MASTODON_LOCAL_DOMAIN}
WEB_DOMAIN: ${MASTODON_WEB_DOMAIN:-}
ALTERNATE_DOMAINS: ""
SINGLE_USER_MODE: ${MASTODON_SINGLE_USER_MODE:-false}
SECRET_KEY_BASE: ${MASTODON_SECRET_KEY_BASE}
OTP_SECRET: ${MASTODON_OTP_SECRET}
VAPID_PRIVATE_KEY: ${MASTODON_VAPID_PRIVATE_KEY}
VAPID_PUBLIC_KEY: ${MASTODON_VAPID_PUBLIC_KEY}
DB_HOST: postgres
DB_USER: mastodon
DB_NAME: mastodon_production
DB_PASS: ${MASTODON_DB_PASSWORD}
DB_PORT: "5432"
REDIS_HOST: redis
REDIS_PORT: "6379"
BIND: 0.0.0.0
PORT: "3000"
TRUSTED_PROXY_IP: "0.0.0.0/0"
# Media retention (sidekiq scheduled job reads this)
MEDIA_CACHE_RETENTION_PERIOD: ${MASTODON_MEDIA_RETENTION_DAYS:-14}
# S3 (empty unless configure-storage.mjs populated them)
S3_ENABLED: ${S3_ENABLED:-false}
S3_BUCKET: ${S3_BUCKET:-}
S3_REGION: ${S3_REGION:-us-east-1}
S3_PROTOCOL: ${S3_PROTOCOL:-}
S3_HOSTNAME: ${S3_HOSTNAME:-}
S3_ENDPOINT: ${S3_ENDPOINT:-}
S3_FORCE_SINGLE_REQUEST: ${S3_FORCE_SINGLE_REQUEST:-true}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
# SMTP (optional)
SMTP_SERVER: ${MASTODON_SMTP_SERVER:-}
SMTP_PORT: ${MASTODON_SMTP_PORT:-587}
SMTP_LOGIN: ${MASTODON_SMTP_LOGIN:-}
SMTP_PASSWORD: ${MASTODON_SMTP_PASSWORD:-}
SMTP_FROM_ADDRESS: ${MASTODON_SMTP_FROM_ADDRESS:-}
volumes:
- ${MASTODON_DATA_DIR:-~/.crow/mastodon}/system:/mastodon/public/system
command: >
bash -c "bin/rails db:migrate && bin/rails assets:precompile &&
bundle exec puma -C config/puma.rb"
init: true
mem_limit: 2g
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/health >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 180s

streaming:
image: ghcr.io/mastodon/mastodon-streaming:v4.3.0
container_name: crow-mastodon-streaming
networks:
- default
- crow-federation
depends_on:
web:
condition: service_healthy
environment:
NODE_ENV: production
LOCAL_DOMAIN: ${MASTODON_LOCAL_DOMAIN}
WEB_DOMAIN: ${MASTODON_WEB_DOMAIN:-}
DB_HOST: postgres
DB_USER: mastodon
DB_NAME: mastodon_production
DB_PASS: ${MASTODON_DB_PASSWORD}
DB_PORT: "5432"
REDIS_HOST: redis
REDIS_PORT: "6379"
BIND: 0.0.0.0
PORT: "4000"
STREAMING_CLUSTER_NUM: "1"
init: true
mem_limit: 512m
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:4000/api/v1/streaming/health >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s

sidekiq:
image: ghcr.io/mastodon/mastodon:v4.3.0
container_name: crow-mastodon-sidekiq
networks:
- default
- crow-federation
depends_on:
web:
condition: service_healthy
environment:
RAILS_ENV: production
LOCAL_DOMAIN: ${MASTODON_LOCAL_DOMAIN}
WEB_DOMAIN: ${MASTODON_WEB_DOMAIN:-}
SECRET_KEY_BASE: ${MASTODON_SECRET_KEY_BASE}
OTP_SECRET: ${MASTODON_OTP_SECRET}
VAPID_PRIVATE_KEY: ${MASTODON_VAPID_PRIVATE_KEY}
VAPID_PUBLIC_KEY: ${MASTODON_VAPID_PUBLIC_KEY}
DB_HOST: postgres
DB_USER: mastodon
DB_NAME: mastodon_production
DB_PASS: ${MASTODON_DB_PASSWORD}
DB_PORT: "5432"
REDIS_HOST: redis
REDIS_PORT: "6379"
MEDIA_CACHE_RETENTION_PERIOD: ${MASTODON_MEDIA_RETENTION_DAYS:-14}
S3_ENABLED: ${S3_ENABLED:-false}
S3_BUCKET: ${S3_BUCKET:-}
S3_REGION: ${S3_REGION:-us-east-1}
S3_PROTOCOL: ${S3_PROTOCOL:-}
S3_HOSTNAME: ${S3_HOSTNAME:-}
S3_ENDPOINT: ${S3_ENDPOINT:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
SMTP_SERVER: ${MASTODON_SMTP_SERVER:-}
SMTP_PORT: ${MASTODON_SMTP_PORT:-587}
SMTP_LOGIN: ${MASTODON_SMTP_LOGIN:-}
SMTP_PASSWORD: ${MASTODON_SMTP_PASSWORD:-}
SMTP_FROM_ADDRESS: ${MASTODON_SMTP_FROM_ADDRESS:-}
volumes:
- ${MASTODON_DATA_DIR:-~/.crow/mastodon}/system:/mastodon/public/system
command: ["bundle", "exec", "sidekiq"]
init: true
mem_limit: 1500m
restart: unless-stopped
59 changes: 59 additions & 0 deletions bundles/mastodon/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"id": "mastodon",
"name": "Mastodon",
"version": "1.0.0",
"description": "The flagship ActivityPub server — federated microblogging at scale. Heaviest of the small-AP bundles. Hosts a public (or invite-only) Mastodon instance with the full v1/v2 API surface, web UI, and Sidekiq background job processing.",
"type": "bundle",
"author": "Crow",
"category": "federated-social",
"tags": ["mastodon", "activitypub", "fediverse", "microblog", "federated", "flagship"],
"icon": "globe",
"docker": { "composefile": "docker-compose.yml" },
"server": {
"command": "node",
"args": ["server/index.js"],
"envKeys": ["MASTODON_URL", "MASTODON_ACCESS_TOKEN", "MASTODON_LOCAL_DOMAIN"]
},
"panel": "panel/mastodon.js",
"panelRoutes": "panel/routes.js",
"skills": ["skills/mastodon.md"],
"consent_required": true,
"install_consent_messages": {
"en": "Mastodon joins the public fediverse over ActivityPub — your instance becomes publicly addressable at the domain you configure (LOCAL_DOMAIN appears in user handles @user@example.com and is IMMUTABLE after first boot — changing it effectively abandons all existing federation identities). Any content you publish can be replicated to federated servers and cannot be fully recalled. Mastodon's remote-media cache can reach 10-100 GB within weeks under an active follow graph — automatic pruning (14-day default) is enabled but on busy instances you may need S3 storage to stay ahead of local disk. Mastodon is hardware-gated: refused on hosts with <8 GB total RAM — the 3 GB container-minimum is only realistic when the instance isn't federated with many busy actors. Runs 4 containers (web, streaming, sidekiq, plus postgres/redis); typical idle ~3 GB, under load 6 GB+. Hosting illegal content or enabling harassment is your legal responsibility — major hubs may defederate your instance quickly.",
"es": "Mastodon se une al fediverso público vía ActivityPub — tu instancia será direccionable en el dominio que configures (LOCAL_DOMAIN aparece en handles como @usuario@ejemplo.com y es INMUTABLE tras el primer arranque — cambiarlo abandona efectivamente todas las identidades de federación existentes). Cualquier contenido publicado puede replicarse a servidores federados y no puede recuperarse completamente. El caché de medios remotos de Mastodon puede alcanzar 10-100 GB en semanas con un grafo de seguimiento activo — el recorte automático (14 días por defecto) está habilitado pero en instancias activas puede ser necesario almacenamiento S3 para mantener el disco local. Mastodon está limitado por hardware: rechazado en hosts con <8 GB de RAM total — el mínimo de 3 GB por contenedor solo es realista cuando la instancia no está federada con muchos actores activos. Ejecuta 4 contenedores (web, streaming, sidekiq, más postgres/redis); típico ~3 GB en reposo, 6 GB+ bajo carga. Hospedar contenido ilegal o permitir acoso es tu responsabilidad legal — los hubs principales pueden dejar de federarse rápidamente."
},
"requires": {
"env": ["MASTODON_LOCAL_DOMAIN", "MASTODON_DB_PASSWORD", "MASTODON_SECRET_KEY_BASE", "MASTODON_OTP_SECRET", "MASTODON_VAPID_PRIVATE_KEY", "MASTODON_VAPID_PUBLIC_KEY"],
"bundles": ["caddy"],
"min_ram_mb": 3000,
"recommended_ram_mb": 6000,
"min_disk_mb": 20000,
"recommended_disk_mb": 500000
},
"env_vars": [
{ "name": "MASTODON_LOCAL_DOMAIN", "description": "Public domain that appears in handles (@user@<this>). IMMUTABLE after first boot — changing breaks all federation identities.", "required": true },
{ "name": "MASTODON_WEB_DOMAIN", "description": "Domain where the web UI lives (if using apex-delegation this differs from LOCAL_DOMAIN; otherwise same).", "required": false },
{ "name": "MASTODON_DB_PASSWORD", "description": "Password for the bundled Postgres role.", "required": true, "secret": true },
{ "name": "MASTODON_SECRET_KEY_BASE", "description": "Rails secret_key_base (128 hex chars). Generate: `docker run --rm ghcr.io/mastodon/mastodon:v4.3 bundle exec rake secret`.", "required": true, "secret": true },
{ "name": "MASTODON_OTP_SECRET", "description": "2FA OTP secret (128 hex chars). Generate same way as SECRET_KEY_BASE.", "required": true, "secret": true },
{ "name": "MASTODON_VAPID_PRIVATE_KEY", "description": "Web Push VAPID private key. Generate: `docker run --rm ghcr.io/mastodon/mastodon:v4.3 bundle exec rake mastodon:webpush:generate_vapid_key` → gives both keys.", "required": true, "secret": true },
{ "name": "MASTODON_VAPID_PUBLIC_KEY", "description": "Web Push VAPID public key (pair of above).", "required": true },
{ "name": "MASTODON_ACCESS_TOKEN", "description": "Admin access token (Settings → Development → New Application, grant read/write/admin:read/admin:write).", "required": false, "secret": true },
{ "name": "MASTODON_URL", "description": "Internal URL the MCP server uses to reach Mastodon's web container (default http://mastodon-web:3000 over crow-federation).", "default": "http://mastodon-web:3000", "required": false },
{ "name": "MASTODON_MEDIA_RETENTION_DAYS", "description": "Remote media cache retention in days (default 14; 7 on Pi-class hosts).", "default": "14", "required": false },
{ "name": "MASTODON_SINGLE_USER_MODE", "description": "Skip the landing page and redirect to the admin profile.", "default": "false", "required": false },
{ "name": "MASTODON_S3_ENDPOINT", "description": "Optional S3-compatible endpoint for media storage. If set with bucket/access/secret, media goes to S3 via storage-translators.mastodon().", "required": false },
{ "name": "MASTODON_S3_BUCKET", "description": "S3 bucket for media.", "required": false },
{ "name": "MASTODON_S3_ACCESS_KEY", "description": "S3 access key.", "required": false, "secret": true },
{ "name": "MASTODON_S3_SECRET_KEY", "description": "S3 secret key.", "required": false, "secret": true },
{ "name": "MASTODON_S3_REGION", "description": "S3 region.", "default": "us-east-1", "required": false },
{ "name": "MASTODON_SMTP_SERVER", "description": "Outbound SMTP server (required for registration confirmations + password reset).", "required": false },
{ "name": "MASTODON_SMTP_PORT", "description": "SMTP port (default 587).", "default": "587", "required": false },
{ "name": "MASTODON_SMTP_LOGIN", "description": "SMTP username.", "required": false },
{ "name": "MASTODON_SMTP_PASSWORD", "description": "SMTP password.", "required": false, "secret": true },
{ "name": "MASTODON_SMTP_FROM_ADDRESS", "description": "From address for outbound mail (e.g. Mastodon <notifications@example.com>).", "required": false }
],
"ports": [],
"webUI": null,
"notes": "Five containers (web + streaming + sidekiq + postgres + redis). Expose via caddy_add_federation_site { domain: MASTODON_LOCAL_DOMAIN, upstream: 'mastodon-web:3000', profile: 'activitypub-mastodon' } — the mastodon profile also wires the streaming path (/api/v1/streaming → mastodon-streaming:4000) and static asset caching. Admin via `docker exec -it crow-mastodon-web bin/tootctl accounts create admin --email <you@example.com> --confirmed --role Admin`."
}
11 changes: 11 additions & 0 deletions bundles/mastodon/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "crow-mastodon",
"version": "1.0.0",
"description": "Mastodon MCP server — statuses, timelines, search, follows, admin moderation",
"type": "module",
"main": "server/index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.24.0"
}
}
Loading
Loading