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 @@ -469,6 +469,7 @@ Add-on skills (activated when corresponding add-on is installed):
- `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()
- `peertube.md` — PeerTube federated video (YouTube-alt): status, list channels/videos, upload_video (multipart), search, subscribe/unsubscribe, rate_video, moderation (block_user inline; block_server/defederate queued admin), admin abuse reports with predefined_reasons taxonomy, remote reporting, media prune recipe; PeerTube v1 REST API; on-disk or S3 via storage-translators.peertube() (strongly recommend S3 — storage unbounded without it)
- `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
131 changes: 131 additions & 0 deletions bundles/peertube/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# PeerTube — federated video platform.
#
# Three-container bundle: peertube (app + transcoding) + postgres + redis.
# peertube on crow-federation; DB + redis isolated to default.
#
# Data:
# ~/.crow/peertube/postgres/ Postgres data dir
# ~/.crow/peertube/redis/ Redis persistence
# ~/.crow/peertube/data/ /data: video originals + transcoded
# variants + HLS playlists + thumbnails
# (can grow TB with on-disk storage)
# ~/.crow/peertube/config/ Generated production.yaml overrides
#
# Storage: on-disk by default but STRONGLY recommend S3. Set PEERTUBE_S3_*
# and scripts/configure-storage.mjs writes the PEERTUBE_OBJECT_STORAGE_*
# envelope to .env. Without S3 + aggressive pruning, a single active
# channel can fill a 500 GB disk within months.
#
# Image: chocobozzz/peertube:production-bookworm (stable line; pin tag to
# a specific release for production — we use the floating tag for the
# roll-out, will pin before merge).

networks:
crow-federation:
external: true
default:

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

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

peertube:
image: chocobozzz/peertube:production-bookworm
container_name: crow-peertube
networks:
- default
- crow-federation
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
PEERTUBE_DB_HOSTNAME: postgres
PEERTUBE_DB_USERNAME: peertube
PEERTUBE_DB_PASSWORD: ${PEERTUBE_DB_PASSWORD}
PEERTUBE_DB_SSL: "false"
PEERTUBE_REDIS_HOSTNAME: redis
PEERTUBE_WEBSERVER_HOSTNAME: ${PEERTUBE_WEBSERVER_HOSTNAME}
PEERTUBE_WEBSERVER_PORT: "443"
PEERTUBE_WEBSERVER_HTTPS: "true"
PEERTUBE_TRUST_PROXY: '["127.0.0.1","loopback","172.16.0.0/12"]'
PEERTUBE_SECRET: ${PEERTUBE_SECRET}
PEERTUBE_ADMIN_EMAIL: ${PEERTUBE_ADMIN_EMAIL:-admin@example.com}
# Transcoding
PEERTUBE_TRANSCODING_ENABLED: "true"
PEERTUBE_TRANSCODING_THREADS: ${PEERTUBE_TRANSCODING_THREADS:-0}
PEERTUBE_TRANSCODING_CONCURRENCY: ${PEERTUBE_TRANSCODING_CONCURRENCY:-1}
# Signup
PEERTUBE_SIGNUP_ENABLED: ${PEERTUBE_SIGNUP_ENABLED:-false}
PEERTUBE_SIGNUP_LIMIT: "100"
PEERTUBE_SIGNUP_MINIMUM_AGE: "16"
# Quotas
PEERTUBE_USER_VIDEO_QUOTA: "${PEERTUBE_VIDEO_QUOTA_MB:-10000}000000"
# Remote cache retention (scheduler reads this)
PEERTUBE_VIDEOS_CLEANUP_REMOTE_INTERVAL: "${PEERTUBE_MEDIA_RETENTION_DAYS:-14} days"
# S3 passthrough (empty unless configure-storage.mjs populated these)
PEERTUBE_OBJECT_STORAGE_ENABLED: ${PEERTUBE_OBJECT_STORAGE_ENABLED:-false}
PEERTUBE_OBJECT_STORAGE_ENDPOINT: ${PEERTUBE_OBJECT_STORAGE_ENDPOINT:-}
PEERTUBE_OBJECT_STORAGE_REGION: ${PEERTUBE_OBJECT_STORAGE_REGION:-us-east-1}
PEERTUBE_OBJECT_STORAGE_ACCESS_KEY_ID: ${PEERTUBE_OBJECT_STORAGE_ACCESS_KEY_ID:-}
PEERTUBE_OBJECT_STORAGE_SECRET_ACCESS_KEY: ${PEERTUBE_OBJECT_STORAGE_SECRET_ACCESS_KEY:-}
PEERTUBE_OBJECT_STORAGE_VIDEOS_BUCKET_NAME: ${PEERTUBE_OBJECT_STORAGE_VIDEOS_BUCKET_NAME:-}
PEERTUBE_OBJECT_STORAGE_STREAMING_PLAYLISTS_BUCKET_NAME: ${PEERTUBE_OBJECT_STORAGE_STREAMING_PLAYLISTS_BUCKET_NAME:-}
PEERTUBE_OBJECT_STORAGE_WEB_VIDEOS_BUCKET_NAME: ${PEERTUBE_OBJECT_STORAGE_WEB_VIDEOS_BUCKET_NAME:-}
PEERTUBE_OBJECT_STORAGE_ORIGINAL_VIDEO_FILES_BUCKET_NAME: ${PEERTUBE_OBJECT_STORAGE_ORIGINAL_VIDEO_FILES_BUCKET_NAME:-}
PEERTUBE_OBJECT_STORAGE_USER_EXPORTS_BUCKET_NAME: ${PEERTUBE_OBJECT_STORAGE_USER_EXPORTS_BUCKET_NAME:-}
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC: ${PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC:-public-read}
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE: ${PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE:-private}
# SMTP (optional)
PEERTUBE_SMTP_HOSTNAME: ${PEERTUBE_SMTP_HOSTNAME:-}
PEERTUBE_SMTP_PORT: ${PEERTUBE_SMTP_PORT:-587}
PEERTUBE_SMTP_USERNAME: ${PEERTUBE_SMTP_USERNAME:-}
PEERTUBE_SMTP_PASSWORD: ${PEERTUBE_SMTP_PASSWORD:-}
PEERTUBE_SMTP_FROM: ${PEERTUBE_SMTP_FROM:-}
volumes:
- ${PEERTUBE_DATA_DIR:-~/.crow/peertube}/data:/data
- ${PEERTUBE_DATA_DIR:-~/.crow/peertube}/config:/config
init: true
mem_limit: 6g
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9000/api/v1/ping >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 120s
59 changes: 59 additions & 0 deletions bundles/peertube/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"id": "peertube",
"name": "PeerTube",
"version": "1.0.0",
"description": "Federated video platform over ActivityPub — YouTube-alternative on the fediverse. Upload and transcode video, federate channels, stream via WebTorrent or HLS. Heaviest bundle in the federated line — S3 storage + aggressive transcoding policy are load-bearing, not optional.",
"type": "bundle",
"author": "Crow",
"category": "federated-media",
"tags": ["peertube", "activitypub", "fediverse", "video", "federated", "youtube-alt", "webtorrent"],
"icon": "phone-video",
"docker": { "composefile": "docker-compose.yml" },
"server": {
"command": "node",
"args": ["server/index.js"],
"envKeys": ["PEERTUBE_URL", "PEERTUBE_ACCESS_TOKEN", "PEERTUBE_WEBSERVER_HOSTNAME"]
},
"panel": "panel/peertube.js",
"panelRoutes": "panel/routes.js",
"skills": ["skills/peertube.md"],
"consent_required": true,
"install_consent_messages": {
"en": "PeerTube joins the public fediverse over ActivityPub — your instance becomes publicly addressable at the domain you configure, any video you publish can be replicated to federated servers and cached there; replicated content cannot be fully recalled. Video storage is unbounded without S3 + scheduled pruning: a single 1080p30 video is 500MB+ in transcoded variants, and federated caches of remote channels can add hundreds of GB within months. Video transcoding spikes RAM to 3-5 GB PER concurrent upload (ffmpeg on x264). PeerTube is hardware-gated: REFUSED on hosts with <16 GB total RAM and <500 GB disk; warned below 32 GB / 2 TB. If transcoding + S3 both disabled, webtorrent seeding is your only scaling lever — the instance becomes unfeasible past ~100 videos. Hosting copyrighted video (music, films, clips you don't have rights to) is a legal fast-track to defederation and takedown notices. DMCA + equivalent requests are your legal responsibility.",
"es": "PeerTube se une al fediverso público vía ActivityPub — tu instancia será direccionable en el dominio que configures, cualquier video publicado puede replicarse a servidores federados y cachearse allí; el contenido replicado no puede recuperarse completamente. El almacenamiento de video es ilimitado sin S3 + recorte programado: un solo video 1080p30 son 500MB+ en variantes transcodificadas, y los cachés federados de canales remotos pueden añadir cientos de GB en meses. La transcodificación de video eleva la RAM a 3-5 GB POR subida concurrente (ffmpeg x264). PeerTube está limitado por hardware: RECHAZADO en hosts con <16 GB de RAM total y <500 GB de disco; advierte bajo 32 GB / 2 TB. Si transcodificación + S3 están deshabilitados, el seeding webtorrent es tu única palanca de escala — la instancia se vuelve inviable pasados ~100 videos. Hospedar video con copyright (música, películas, clips sin derechos) es un camino directo a la defederación y avisos DMCA. Solicitudes DMCA + equivalentes son tu responsabilidad legal."
},
"requires": {
"env": ["PEERTUBE_WEBSERVER_HOSTNAME", "PEERTUBE_DB_PASSWORD", "PEERTUBE_SECRET"],
"bundles": ["caddy"],
"min_ram_mb": 4000,
"recommended_ram_mb": 8000,
"min_disk_mb": 100000,
"recommended_disk_mb": 2000000
},
"env_vars": [
{ "name": "PEERTUBE_WEBSERVER_HOSTNAME", "description": "Public domain (subdomain; IMMUTABLE after first federation). Appears in video URLs, channel URLs, every ActivityPub actor.", "required": true },
{ "name": "PEERTUBE_DB_PASSWORD", "description": "Password for the bundled Postgres role.", "required": true, "secret": true },
{ "name": "PEERTUBE_SECRET", "description": "Server secret (32+ random chars). Signs federation requests; IMMUTABLE — rotation requires re-announcing every federated actor.", "required": true, "secret": true },
{ "name": "PEERTUBE_ACCESS_TOKEN", "description": "OAuth bearer token (Administration → Users → your account → Personal Access Tokens, OR POST /api/v1/users/token with username+password + client_id+client_secret from /api/v1/oauth-clients/local).", "required": false, "secret": true },
{ "name": "PEERTUBE_URL", "description": "Internal URL the MCP server uses to reach PeerTube (default http://peertube:9000 over crow-federation).", "default": "http://peertube:9000", "required": false },
{ "name": "PEERTUBE_ADMIN_EMAIL", "description": "Admin email — where takedown notices, abuse reports, and federation alerts go.", "required": false },
{ "name": "PEERTUBE_TRANSCODING_THREADS", "description": "Worker threads per concurrent transcode (default 0 = all CPUs). Lower on multi-tenant hosts to keep latency under control.", "default": "0", "required": false },
{ "name": "PEERTUBE_TRANSCODING_CONCURRENCY", "description": "Max simultaneous transcodes. 1-2 on 16 GB hosts; 4+ on 32 GB+.", "default": "1", "required": false },
{ "name": "PEERTUBE_SIGNUP_ENABLED", "description": "Allow new user signups (true/false). Default false; opening signup without moderation tooling invites abuse.", "default": "false", "required": false },
{ "name": "PEERTUBE_VIDEO_QUOTA_MB", "description": "Default per-user video quota in MB. 10 GB default (10000).", "default": "10000", "required": false },
{ "name": "PEERTUBE_MEDIA_RETENTION_DAYS", "description": "Remote video cache retention in days (default 14; 7 on smaller hosts).", "default": "14", "required": false },
{ "name": "PEERTUBE_S3_ENDPOINT", "description": "Optional S3-compatible endpoint for video storage. STRONGLY RECOMMENDED on active instances. When set with bucket/access/secret, scripts/configure-storage.mjs routes via storage-translators.peertube() (PEERTUBE_OBJECT_STORAGE_* envelope).", "required": false },
{ "name": "PEERTUBE_S3_BUCKET", "description": "S3 bucket for video originals + transcoded variants + streaming playlists.", "required": false },
{ "name": "PEERTUBE_S3_ACCESS_KEY", "description": "S3 access key.", "required": false, "secret": true },
{ "name": "PEERTUBE_S3_SECRET_KEY", "description": "S3 secret key.", "required": false, "secret": true },
{ "name": "PEERTUBE_S3_REGION", "description": "S3 region.", "default": "us-east-1", "required": false },
{ "name": "PEERTUBE_SMTP_HOSTNAME", "description": "Outbound SMTP (registration confirmation, takedown receipts).", "required": false },
{ "name": "PEERTUBE_SMTP_PORT", "description": "SMTP port.", "default": "587", "required": false },
{ "name": "PEERTUBE_SMTP_USERNAME", "description": "SMTP username.", "required": false },
{ "name": "PEERTUBE_SMTP_PASSWORD", "description": "SMTP password.", "required": false, "secret": true },
{ "name": "PEERTUBE_SMTP_FROM", "description": "From address.", "required": false }
],
"ports": [],
"webUI": null,
"notes": "Three containers (peertube + postgres + redis). chocobozzz/peertube:production-bookworm image. Expose via caddy_add_federation_site { domain: PEERTUBE_WEBSERVER_HOSTNAME, upstream: 'peertube:9000', profile: 'activitypub-peertube' } — the peertube profile also wires WebSocket upgrade on /socket.io and large request-body limits for video upload chunking. Admin credentials: first-boot logs print a randomly-generated admin password (search `docker logs crow-peertube | grep -A1 \"Username\"`); rotate via `docker exec -it crow-peertube npm run reset-password -- -u root` immediately."
}
11 changes: 11 additions & 0 deletions bundles/peertube/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "crow-peertube",
"version": "1.0.0",
"description": "PeerTube (federated video) MCP server — upload, search, channels, subscriptions, moderation",
"type": "module",
"main": "server/index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.24.0"
}
}
Loading
Loading