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 @@ -466,6 +466,7 @@ Add-on skills (activated when corresponding add-on is installed):
- `writefreely.md` — WriteFreely federated blog: create/update/publish/unpublish posts, list collections, fetch public posts, export; minimalist publisher (no comments, no moderation queue — WF is publish-oriented only)
- `matrix-dendrite.md` — Matrix homeserver on Dendrite: create/join/leave rooms, send messages, sync, invite users, federation health; appservice registration prep for F.12 bridges; :8448-vs-well-known either/or federation story
- `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()
- `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
186 changes: 186 additions & 0 deletions bundles/pixelfed/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Pixelfed — federated photo-sharing on ActivityPub.
#
# Four-container bundle: pixelfed (nginx+PHP-FPM via supervisord in the
# zknt image) + horizon (laravel queue worker) + postgres + redis. app +
# horizon on crow-federation; DB/redis isolated to default. Caddy
# reverse-proxies :443 → pixelfed:80.
#
# Data:
# ~/.crow/pixelfed/postgres/ Postgres data dir
# ~/.crow/pixelfed/redis/ Redis persistence
# ~/.crow/pixelfed/storage/ Laravel storage/ (uploads, cache, logs)
# ~/.crow/pixelfed/uploads/ Public uploads dir (symlinked from storage)
#
# Media storage: on-disk by default. Set PIXELFED_S3_* to route to MinIO /
# external S3 — storage-translators.pixelfed() maps to the AWS_* +
# FILESYSTEM_CLOUD=s3 envelope Pixelfed expects. configure-storage.mjs in
# scripts/ does the translation at install time.
#
# Image: zknt/pixelfed:0.12 (floats within 0.12.x — verify the current
# tag + CVE feed at implementation time per the plan's image-tag policy).

networks:
crow-federation:
external: true
default:

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

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

pixelfed:
image: zknt/pixelfed:0.12
container_name: crow-pixelfed
networks:
- default
- crow-federation
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
APP_NAME: Pixelfed
APP_ENV: production
APP_DEBUG: "false"
APP_KEY: ${PIXELFED_APP_KEY}
APP_URL: https://${PIXELFED_HOSTNAME}
APP_DOMAIN: ${PIXELFED_HOSTNAME}
ADMIN_DOMAIN: ${PIXELFED_HOSTNAME}
SESSION_DOMAIN: ${PIXELFED_HOSTNAME}
TRUST_PROXIES: "*"
# DB
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: "5432"
DB_DATABASE: pixelfed
DB_USERNAME: pixelfed
DB_PASSWORD: ${PIXELFED_DB_PASSWORD}
# Cache / queue
BROADCAST_DRIVER: log
CACHE_DRIVER: redis
QUEUE_DRIVER: redis
SESSION_DRIVER: redis
REDIS_CLIENT: predis
REDIS_HOST: redis
REDIS_PASSWORD: "null"
REDIS_PORT: "6379"
# Federation
ACTIVITY_PUB: "true"
AP_REMOTE_FOLLOW: "true"
AP_SHAREDINBOX: "true"
AP_INBOX: "true"
AP_OUTBOX: "true"
# Registration / limits
OPEN_REGISTRATION: ${PIXELFED_OPEN_REGISTRATION:-false}
ENFORCE_EMAIL_VERIFICATION: "true"
PF_MAX_USERS: ${PIXELFED_MAX_USERS:-1000}
OAUTH_ENABLED: "true"
# Media
PF_OPTIMIZE_IMAGES: "true"
IMAGE_DRIVER: imagick
MAX_PHOTO_SIZE: ${PIXELFED_MAX_PHOTO_SIZE:-15000}
MAX_ALBUM_LENGTH: ${PIXELFED_MAX_ALBUM_LENGTH:-4}
MEDIA_EXIF_DATABASE: "false"
MEDIA_DELETE_LOCAL_AFTER_CLOUD: "true"
# Remote media cache retention (days)
PF_REMOTE_MEDIA_DAYS: ${PIXELFED_MEDIA_RETENTION_DAYS:-14}
# S3 (empty unless configure-storage.mjs populated them)
FILESYSTEM_CLOUD: ${FILESYSTEM_CLOUD:-local}
PF_ENABLE_CLOUD: ${PF_ENABLE_CLOUD:-false}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-east-1}
AWS_BUCKET: ${AWS_BUCKET:-}
AWS_URL: ${AWS_URL:-}
AWS_ENDPOINT: ${AWS_ENDPOINT:-}
AWS_USE_PATH_STYLE_ENDPOINT: ${AWS_USE_PATH_STYLE_ENDPOINT:-true}
volumes:
- ${PIXELFED_DATA_DIR:-~/.crow/pixelfed}/storage:/var/www/storage
- ${PIXELFED_DATA_DIR:-~/.crow/pixelfed}/uploads:/var/www/public/storage
init: true
mem_limit: 1500m
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/api/v1/instance >/dev/null || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 120s

horizon:
image: zknt/pixelfed:0.12
container_name: crow-pixelfed-horizon
networks:
- default
depends_on:
pixelfed:
condition: service_healthy
environment:
APP_NAME: Pixelfed
APP_ENV: production
APP_KEY: ${PIXELFED_APP_KEY}
APP_URL: https://${PIXELFED_HOSTNAME}
APP_DOMAIN: ${PIXELFED_HOSTNAME}
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_DATABASE: pixelfed
DB_USERNAME: pixelfed
DB_PASSWORD: ${PIXELFED_DB_PASSWORD}
BROADCAST_DRIVER: log
CACHE_DRIVER: redis
QUEUE_DRIVER: redis
REDIS_CLIENT: predis
REDIS_HOST: redis
REDIS_PASSWORD: "null"
REDIS_PORT: "6379"
FILESYSTEM_CLOUD: ${FILESYSTEM_CLOUD:-local}
PF_ENABLE_CLOUD: ${PF_ENABLE_CLOUD:-false}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-east-1}
AWS_BUCKET: ${AWS_BUCKET:-}
AWS_URL: ${AWS_URL:-}
AWS_ENDPOINT: ${AWS_ENDPOINT:-}
AWS_USE_PATH_STYLE_ENDPOINT: ${AWS_USE_PATH_STYLE_ENDPOINT:-true}
volumes:
- ${PIXELFED_DATA_DIR:-~/.crow/pixelfed}/storage:/var/www/storage
- ${PIXELFED_DATA_DIR:-~/.crow/pixelfed}/uploads:/var/www/public/storage
command: ["php", "/var/www/artisan", "horizon"]
init: true
mem_limit: 768m
restart: unless-stopped
51 changes: 51 additions & 0 deletions bundles/pixelfed/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"id": "pixelfed",
"name": "Pixelfed",
"version": "1.0.0",
"description": "Federated photo-sharing server over ActivityPub — Instagram-alternative on the fediverse. Publish photos/stories/collections; remote Mastodon/GoToSocial/Funkwhale followers see your posts in their timelines.",
"type": "bundle",
"author": "Crow",
"category": "federated-media",
"tags": ["pixelfed", "activitypub", "fediverse", "photos", "federated", "instagram-alt"],
"icon": "image",
"docker": { "composefile": "docker-compose.yml" },
"server": {
"command": "node",
"args": ["server/index.js"],
"envKeys": ["PIXELFED_URL", "PIXELFED_ACCESS_TOKEN", "PIXELFED_HOSTNAME"]
},
"panel": "panel/pixelfed.js",
"panelRoutes": "panel/routes.js",
"skills": ["skills/pixelfed.md"],
"consent_required": true,
"install_consent_messages": {
"en": "Pixelfed joins the public fediverse over ActivityPub — your instance becomes publicly addressable at the domain you configure, any post you publish can be replicated to federated servers (Mastodon, GoToSocial, Funkwhale, other Pixelfed instances) and cached there; replicated content cannot be fully recalled — deletions may not reach every server that cached the media. Pixelfed's remote-media cache grows fast under an active follow graph: 10-50 GB within weeks is typical, and scheduled pruning (default 14 days) is load-bearing. Pixelfed is hardware-gated to refuse install on hosts with <1.5 GB effective RAM after committed bundles; warns below 8 GB total host RAM. Uploading copyrighted or illegal imagery is your legal responsibility — major hubs (mastodon.social) may defederate instances reported for abuse. CSAM hosting is a criminal offense; media moderation is not optional — configure IFTAS or Bad Space blocklists before opening registration.",
"es": "Pixelfed se une al fediverso público vía ActivityPub — tu instancia será direccionable en el dominio que configures, cualquier publicación puede replicarse a servidores federados (Mastodon, GoToSocial, Funkwhale, otras instancias de Pixelfed) y cachearse allí; el contenido replicado no puede recuperarse completamente — las eliminaciones pueden no llegar a todos los servidores que cachearon el medio. El caché de medios remotos de Pixelfed crece rápido con un grafo de seguimiento activo: 10-50 GB en semanas es típico, y el recorte programado (14 días por defecto) es crítico. Pixelfed está limitado por hardware: rechazado en hosts con <1.5 GB de RAM efectiva tras paquetes comprometidos; advierte bajo 8 GB de RAM total. Subir imágenes con copyright o ilegales es tu responsabilidad legal — los hubs principales (mastodon.social) pueden dejar de federarse con instancias reportadas por abuso. Hospedar CSAM es un delito; la moderación de medios no es opcional — configura listas de bloqueo IFTAS o Bad Space antes de abrir el registro."
},
"requires": {
"env": ["PIXELFED_HOSTNAME", "PIXELFED_DB_PASSWORD", "PIXELFED_APP_KEY"],
"bundles": ["caddy"],
"min_ram_mb": 1500,
"recommended_ram_mb": 3000,
"min_disk_mb": 10000,
"recommended_disk_mb": 100000
},
"env_vars": [
{ "name": "PIXELFED_HOSTNAME", "description": "Public domain (subdomain; path-mounts break ActivityPub).", "required": true },
{ "name": "PIXELFED_DB_PASSWORD", "description": "Password for the bundled Postgres role.", "required": true, "secret": true },
{ "name": "PIXELFED_APP_KEY", "description": "Laravel application key (32+ random bytes). Generate: `php artisan key:generate --show` in a test container, or `openssl rand -base64 32 | head -c 32`.", "required": true, "secret": true },
{ "name": "PIXELFED_ACCESS_TOKEN", "description": "OAuth2 Personal Access Token (Settings → Development → New Application).", "required": false, "secret": true },
{ "name": "PIXELFED_URL", "description": "Internal URL the MCP server uses to reach Pixelfed (default http://pixelfed:80 over crow-federation).", "default": "http://pixelfed:80", "required": false },
{ "name": "PIXELFED_OPEN_REGISTRATION", "description": "Allow new user signups (true/false). Default false — opening registration without moderation tooling invites abuse.", "default": "false", "required": false },
{ "name": "PIXELFED_MAX_USERS", "description": "Cap on registered users (0 = unlimited, honors OPEN_REGISTRATION).", "default": "1000", "required": false },
{ "name": "PIXELFED_MEDIA_RETENTION_DAYS", "description": "Remote media cache retention (default 14; 7 on Pi-class hosts).", "default": "14", "required": false },
{ "name": "PIXELFED_S3_ENDPOINT", "description": "Optional S3-compatible endpoint for media storage (defaults to on-disk). If set with bucket/access/secret, media goes to S3 via storage-translators.pixelfed().", "required": false },
{ "name": "PIXELFED_S3_BUCKET", "description": "S3 bucket for media.", "required": false },
{ "name": "PIXELFED_S3_ACCESS_KEY", "description": "S3 access key.", "required": false, "secret": true },
{ "name": "PIXELFED_S3_SECRET_KEY", "description": "S3 secret key.", "required": false, "secret": true },
{ "name": "PIXELFED_S3_REGION", "description": "S3 region.", "default": "us-east-1", "required": false }
],
"ports": [],
"webUI": null,
"notes": "Four containers (app + horizon + postgres + redis). app ships nginx+PHP-FPM via supervisord. Expose via caddy_add_federation_site { domain: PIXELFED_HOSTNAME, upstream: 'pixelfed:80', profile: 'activitypub' }. Admin account via `docker exec -it crow-pixelfed php artisan user:create`. Media retention enforced by scheduled horizon job; tune via PIXELFED_MEDIA_RETENTION_DAYS."
}
11 changes: 11 additions & 0 deletions bundles/pixelfed/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "crow-pixelfed",
"version": "1.0.0",
"description": "Pixelfed (federated photo-sharing) MCP server — posts, photos, feeds, follows, moderation",
"type": "module",
"main": "server/index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.24.0"
}
}
Loading
Loading