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
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ Uses `@libsql/client` for local SQLite files (default: `~/.crow/data/crow.db`, g
- **moderation_actions** — F.11 queued destructive moderation actions from federated bundles (bundle_id, action_type, payload_json, requested_at, expires_at, status, idempotency_key UNIQUE). 72h default TTL; operator confirms via Nest panel
- **identity_attestations** — F.11 signed bindings (crow_id, app, external_handle, app_pubkey?, sig, version, revoked_at). Published via gateway `/.well-known/crow-identity.json`. UNIQUE(crow_id, app, external_handle, version) — new version row per rotation
- **identity_attestation_revocations** — F.11 signed revocations (attestation_id FK CASCADE, revoked_at, reason, sig). Published via `/.well-known/crow-identity-revocations.json`
- **crosspost_rules** — F.12.2 opt-in crosspost config (source_app, source_trigger, target_app, transform, active). Triggers: `on_publish`, `on_tag:<tag>`, `manual`
- **crosspost_log** — F.12.2 audit + idempotency log (idempotency_key, source_app, source_post_id, target_app, status, target_post_id, scheduled_at, published_at, cancelled_at). UNIQUE(idempotency_key, source_app, target_app). 7-day idempotency window; >30 days GC'd daily
- **iptv_playlists** — IPTV M3U playlist sources (name, url, auto_refresh, channel_count)
- **iptv_channels** — IPTV channels from playlists (playlist_id FK, name, stream_url, tvg_id, group_title, is_favorite)
- **iptv_epg** — Electronic Program Guide entries (channel_tvg_id, title, start_time, end_time, indexed)
Expand Down Expand Up @@ -440,6 +442,7 @@ Consult `skills/superpowers.md` first — it routes user intent to the right ski
- `developer-kit.md` — Developer kit: scaffold, test, and submit Crow extensions to the registry
- `network-setup.md` — Tailscale remote access guidance
- `crow-identity.md` — F.11 identity attestations: sign per-app handles (Mastodon/Funkwhale/Matrix/etc.) with the Crow root Ed25519 key, publish via `/.well-known/crow-identity.json`, verify + revoke. Off by default; opt-in per handle — public linkage is effectively permanent
- `crow-crosspost.md` — F.12.2 cross-app publishing: mirror a post from one federated bundle to another via pure-function transforms (writefreely→mastodon, peertube→mastodon, pixelfed→mastodon, funkwhale→mastodon, gotosocial→mastodon, blog→gotosocial). Idempotency_key required; 60s publish-delay safety valve with operator cancel; no fake undo-after-publish
- `add-ons.md` — Add-on browsing, installation, removal
- `scheduling.md` — Scheduled and recurring task management
- `tutoring.md` — Socratic tutoring with progress tracking
Expand Down Expand Up @@ -469,6 +472,7 @@ Add-on skills (activated when corresponding add-on is installed):
- `gotosocial.md` — GoToSocial ActivityPub microblog: post, follow, search, moderate (block_user/mute inline; defederate/block_domain/import_blocklist queued for operator confirmation), media prune, federation health
- `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
- `matrix-bridges.md` — F.12.1 Matrix appservice bridges meta-bundle (mautrix-signal/telegram/whatsapp). Opt-in per bridge; each has distinct legal/privacy risks (Signal ToS prohibits bots; Meta may ban bridged WhatsApp numbers). post-install.sh writes appservice YAMLs into Dendrite + restarts it. Requires matrix-dendrite bundle
- `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
Expand Down
90 changes: 90 additions & 0 deletions bundles/matrix-bridges/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Matrix Bridges — Signal + Telegram + WhatsApp appservice sidecars.
#
# Each bridge is a `profiles`-gated service; the post-install.sh script
# enables the relevant profile based on BRIDGE_*_ENABLED env flags.
# All bridges join the crow-federation network so Dendrite can reach them
# over the internal docker DNS (appservice URL = http://<container>:<port>).
#
# Data:
# ~/.crow/matrix-bridges/signal/ mautrix-signal sqlite + config
# ~/.crow/matrix-bridges/telegram/ mautrix-telegram sqlite + config
# ~/.crow/matrix-bridges/whatsapp/ mautrix-whatsapp sqlite + config
# ~/.crow/matrix-bridges/signald/ signald state (mautrix-signal sidecar)
#
# Images: dock.mau.dev official mautrix tags. Pinned in each service.

networks:
crow-federation:
external: true
default:

services:
mautrix-signal:
image: dock.mau.dev/mautrix/signal:latest
container_name: crow-mautrix-signal
profiles: ["signal"]
networks:
- default
- crow-federation
environment:
MAUTRIX_HOMESERVER_ADDRESS: ${MATRIX_BRIDGE_HOMESERVER_URL}
MAUTRIX_HOMESERVER_DOMAIN: ${MATRIX_BRIDGE_DOMAIN}
volumes:
- ${MATRIX_BRIDGES_DATA_DIR:-~/.crow/matrix-bridges}/signal:/data
init: true
mem_limit: 512m
restart: unless-stopped
# Bridge generates /data/registration.yaml on first boot; post-install
# copies it into crow-dendrite:/etc/dendrite/appservices/signal.yaml
healthcheck:
test: ["CMD-SHELL", "test -f /data/registration.yaml"]
interval: 30s
timeout: 5s
retries: 20
start_period: 30s

mautrix-telegram:
image: dock.mau.dev/mautrix/telegram:latest
container_name: crow-mautrix-telegram
profiles: ["telegram"]
networks:
- default
- crow-federation
environment:
MAUTRIX_HOMESERVER_ADDRESS: ${MATRIX_BRIDGE_HOMESERVER_URL}
MAUTRIX_HOMESERVER_DOMAIN: ${MATRIX_BRIDGE_DOMAIN}
MAUTRIX_TELEGRAM_API_ID: ${BRIDGE_TELEGRAM_API_ID:-}
MAUTRIX_TELEGRAM_API_HASH: ${BRIDGE_TELEGRAM_API_HASH:-}
volumes:
- ${MATRIX_BRIDGES_DATA_DIR:-~/.crow/matrix-bridges}/telegram:/data
init: true
mem_limit: 768m
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "test -f /data/registration.yaml"]
interval: 30s
timeout: 5s
retries: 20
start_period: 30s

mautrix-whatsapp:
image: dock.mau.dev/mautrix/whatsapp:latest
container_name: crow-mautrix-whatsapp
profiles: ["whatsapp"]
networks:
- default
- crow-federation
environment:
MAUTRIX_HOMESERVER_ADDRESS: ${MATRIX_BRIDGE_HOMESERVER_URL}
MAUTRIX_HOMESERVER_DOMAIN: ${MATRIX_BRIDGE_DOMAIN}
volumes:
- ${MATRIX_BRIDGES_DATA_DIR:-~/.crow/matrix-bridges}/whatsapp:/data
init: true
mem_limit: 2g
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "test -f /data/registration.yaml"]
interval: 30s
timeout: 5s
retries: 20
start_period: 30s
39 changes: 39 additions & 0 deletions bundles/matrix-bridges/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"id": "matrix-bridges",
"name": "Matrix Bridges",
"version": "1.0.0",
"description": "Meta-bundle for Matrix appservice bridges (Signal, Telegram, WhatsApp, custom crow-matrix-bridge). Opt-in per bridge; each has its own consent text and hardware requirements. Installs appservice registrations into Dendrite and restarts the homeserver.",
"type": "bundle",
"author": "Crow",
"category": "federated-comms",
"tags": ["matrix", "bridges", "signal", "telegram", "whatsapp", "mautrix", "meta-bundle"],
"icon": "message-circle",
"docker": { "composefile": "docker-compose.yml" },
"server": null,
"skills": ["skills/matrix-bridges.md"],
"consent_required": true,
"install_consent_messages": {
"en": "This meta-bundle installs Matrix appservice bridges that relay messages between Matrix (your Dendrite homeserver from F.3) and closed platforms (Signal, Telegram, WhatsApp). EACH BRIDGE HAS ITS OWN LEGAL + PRIVACY PROFILE. Signal's ToS explicitly prohibits automated relays and bot accounts — running mautrix-signal against a personal Signal number risks Signal terminating that number with no appeal. Telegram tolerates bot bridges but throttles aggressively on new accounts. WhatsApp actively blocks accounts that register as a 'multi-device' client from a non-WhatsApp app; mautrix-whatsapp expects the target phone to be opted-in and the owner accepts that Meta may ban the number. All bridges require double-puppeting (bridge bot posts AS you, not as a proxy), which means your Matrix account can inadvertently leak every Signal/Telegram/WhatsApp message to Dendrite admins + your homeserver's federation peers. Each bridge enabled here adds 500 MB-2 GB of RAM + 5-20 GB of disk (mautrix-whatsapp alone can spike 2 GB during media-heavy chats). Only enable bridges you actively need; disable bridges you stop using (stale bridges keep consuming resources AND keep the bridge bot in every room it joined). Dendrite must be RESTARTED after bridges are enabled — appservice registrations are read only at startup; hot-reload silently no-ops.",
"es": "Este meta-paquete instala puentes appservice de Matrix que retransmiten mensajes entre Matrix (tu homeserver Dendrite de F.3) y plataformas cerradas (Signal, Telegram, WhatsApp). CADA PUENTE TIENE SU PROPIO PERFIL LEGAL + DE PRIVACIDAD. Los ToS de Signal prohíben explícitamente los relays automatizados y cuentas bot — ejecutar mautrix-signal contra un número Signal personal arriesga que Signal termine ese número sin apelación. Telegram tolera puentes bot pero limita agresivamente cuentas nuevas. WhatsApp bloquea activamente cuentas que se registran como cliente 'multi-dispositivo' desde una app que no es WhatsApp; mautrix-whatsapp espera que el teléfono destino esté opt-in y el dueño acepta que Meta puede banear el número. Todos los puentes requieren doble-puppeting (el bot del puente publica COMO tú, no como proxy), lo que significa que tu cuenta de Matrix puede filtrar inadvertidamente todos los mensajes de Signal/Telegram/WhatsApp a admins de Dendrite + pares federados de tu homeserver. Cada puente habilitado añade 500 MB-2 GB de RAM + 5-20 GB de disco (mautrix-whatsapp solo puede subir a 2 GB en chats con muchos medios). Solo habilita los puentes que activamente necesitas; deshabilita los que dejas de usar (puentes obsoletos siguen consumiendo recursos Y mantienen al bot del puente en cada sala a la que se unió). Dendrite debe REINICIARSE después de habilitar puentes — los registros appservice se leen solo al inicio; un hot-reload silenciosamente no hace nada."
},
"requires": {
"env": ["MATRIX_BRIDGE_HOMESERVER_URL", "MATRIX_BRIDGE_DOMAIN"],
"bundles": ["caddy", "matrix-dendrite"],
"min_ram_mb": 500,
"recommended_ram_mb": 2000,
"min_disk_mb": 5000,
"recommended_disk_mb": 30000
},
"env_vars": [
{ "name": "MATRIX_BRIDGE_HOMESERVER_URL", "description": "Internal URL of the Dendrite client-server API (http://dendrite:8008 over crow-federation).", "default": "http://dendrite:8008", "required": true },
{ "name": "MATRIX_BRIDGE_DOMAIN", "description": "MATRIX_SERVER_NAME from the matrix-dendrite bundle (appears in bridge bot MXIDs as @signalbot:<this>).", "required": true },
{ "name": "BRIDGE_SIGNAL_ENABLED", "description": "Enable mautrix-signal. RISK: Signal ToS prohibits automated clients — number may be terminated.", "default": "false", "required": false },
{ "name": "BRIDGE_TELEGRAM_ENABLED", "description": "Enable mautrix-telegram. Requires a Telegram API ID + hash.", "default": "false", "required": false },
{ "name": "BRIDGE_TELEGRAM_API_ID", "description": "Telegram API ID from my.telegram.org/apps.", "required": false, "secret": true },
{ "name": "BRIDGE_TELEGRAM_API_HASH", "description": "Telegram API hash.", "required": false, "secret": true },
{ "name": "BRIDGE_WHATSAPP_ENABLED", "description": "Enable mautrix-whatsapp. RISK: WhatsApp may ban the linked phone number.", "default": "false", "required": false }
],
"ports": [],
"webUI": null,
"notes": "Enabled bridges become sidecar containers. post-install.sh generates appservice registration YAMLs, calls matrix_register_appservice via docker exec, copies the YAMLs into /etc/dendrite/appservices/, and restarts the crow-dendrite container (registrations are read only at startup). To add/remove bridges: toggle BRIDGE_*_ENABLED in .env, run `crow bundle restart matrix-bridges`, then restart Dendrite manually."
}
7 changes: 7 additions & 0 deletions bundles/matrix-bridges/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "crow-matrix-bridges",
"version": "1.0.0",
"description": "Matrix appservice bridges meta-bundle (Signal/Telegram/WhatsApp). No MCP server — bridge state is managed via Dendrite's native tools and the bridge bots themselves.",
"type": "module",
"main": null
}
153 changes: 153 additions & 0 deletions bundles/matrix-bridges/scripts/post-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
# Matrix Bridges post-install.
#
# 1. For each BRIDGE_*_ENABLED=true flag, start the corresponding compose
# profile so the bridge container boots and generates its
# /data/registration.yaml.
# 2. Wait for registration.yaml to appear.
# 3. Copy the YAML into the crow-dendrite container's appservices dir.
# 4. Patch dendrite.yaml's app_service_api.config_files list to include
# the new YAML (idempotent — skip if already present).
# 5. Restart crow-dendrite (appservice registrations are read ONLY at
# startup; hot reload silently no-ops).
# 6. Print bridge-bot MXIDs and next steps (DM the bot to start pairing).

set -euo pipefail

BUNDLE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ENV_FILE="${BUNDLE_DIR}/.env"
if [ -f "$ENV_FILE" ]; then
set -a; . "$ENV_FILE"; set +a
fi

COMPOSE="docker compose -f ${BUNDLE_DIR}/docker-compose.yml"

# Map BRIDGE_*_ENABLED → (profile, bridge_id, registration_filename)
BRIDGE_RECORDS=()
if [ "${BRIDGE_SIGNAL_ENABLED:-false}" = "true" ]; then BRIDGE_RECORDS+=("signal:mautrix-signal:signal.yaml"); fi
if [ "${BRIDGE_TELEGRAM_ENABLED:-false}" = "true" ]; then BRIDGE_RECORDS+=("telegram:mautrix-telegram:telegram.yaml"); fi
if [ "${BRIDGE_WHATSAPP_ENABLED:-false}" = "true" ]; then BRIDGE_RECORDS+=("whatsapp:mautrix-whatsapp:whatsapp.yaml"); fi

if [ ${#BRIDGE_RECORDS[@]} -eq 0 ]; then
cat <<EOF
No bridges enabled. Set one or more of the following in .env, then re-run:
BRIDGE_SIGNAL_ENABLED=true # RISK: Signal ToS prohibits bots
BRIDGE_TELEGRAM_ENABLED=true # Requires BRIDGE_TELEGRAM_API_ID/HASH
BRIDGE_WHATSAPP_ENABLED=true # RISK: WhatsApp may ban the phone

After editing, run: crow bundle restart matrix-bridges
EOF
exit 0
fi

# Start the enabled profiles
PROFILES_ARG=""
for rec in "${BRIDGE_RECORDS[@]}"; do
profile="${rec%%:*}"
PROFILES_ARG+=" --profile $profile"
done
echo "Starting bridge containers${PROFILES_ARG}…"
eval "$COMPOSE$PROFILES_ARG up -d"

# Wait for each bridge's registration.yaml
for rec in "${BRIDGE_RECORDS[@]}"; do
IFS=':' read -r profile container yaml <<< "$rec"
full_container="crow-${container}"
echo "Waiting for ${full_container} to generate /data/registration.yaml (up to 120s)…"
for i in $(seq 1 24); do
if docker exec "$full_container" test -f /data/registration.yaml 2>/dev/null; then
echo " → ${full_container} registration.yaml ready"
break
fi
sleep 5
done
done

# Copy each registration.yaml into crow-dendrite
if ! docker ps --format '{{.Names}}' | grep -qw crow-dendrite; then
echo "ERROR: crow-dendrite is not running. Start matrix-dendrite bundle first." >&2
exit 1
fi

docker exec crow-dendrite mkdir -p /etc/dendrite/appservices
for rec in "${BRIDGE_RECORDS[@]}"; do
IFS=':' read -r profile container yaml <<< "$rec"
full_container="crow-${container}"
TMP="$(mktemp)"
docker cp "$full_container:/data/registration.yaml" "$TMP"
docker cp "$TMP" "crow-dendrite:/etc/dendrite/appservices/$yaml"
rm -f "$TMP"
echo " → copied $yaml into crow-dendrite:/etc/dendrite/appservices/"
done

# Patch dendrite.yaml to include the new appservice config_files (idempotent)
CFG=/etc/dendrite/dendrite.yaml
for rec in "${BRIDGE_RECORDS[@]}"; do
IFS=':' read -r profile container yaml <<< "$rec"
entry=" - appservices/$yaml"
if docker exec crow-dendrite grep -qF "appservices/$yaml" "$CFG" 2>/dev/null; then
echo " → dendrite.yaml already references appservices/$yaml"
continue
fi
# Add / update app_service_api.config_files block. Whole-file awk for safety.
docker exec crow-dendrite sh -c "
if ! grep -q 'app_service_api:' $CFG; then
printf '\napp_service_api:\n config_files:\n - appservices/$yaml\n' >> $CFG
elif ! grep -q 'config_files:' $CFG; then
awk '/app_service_api:/{print;print \" config_files:\";print \" - appservices/$yaml\";next}1' $CFG > $CFG.tmp && mv $CFG.tmp $CFG
else
awk -v ENT=\" - appservices/$yaml\" '
{print}
/^[[:space:]]*config_files:/{print ENT}
' $CFG > $CFG.tmp && mv $CFG.tmp $CFG
fi
"
echo " → patched dendrite.yaml with appservices/$yaml"
done

# Restart Dendrite (appservice registrations are only read at startup)
echo "Restarting crow-dendrite (appservice registrations read at startup only)…"
DENDRITE_COMPOSE="${BUNDLE_DIR}/../matrix-dendrite/docker-compose.yml"
if [ -f "$DENDRITE_COMPOSE" ]; then
docker compose -f "$DENDRITE_COMPOSE" restart dendrite
else
docker restart crow-dendrite >/dev/null
fi

# Wait for Dendrite to come back healthy
echo "Waiting for Dendrite to report healthy after restart (up to 60s)…"
for i in $(seq 1 12); do
if docker inspect crow-dendrite --format '{{.State.Health.Status}}' 2>/dev/null | grep -qw healthy; then
echo " → dendrite healthy"
break
fi
sleep 5
done

cat <<EOF

Matrix Bridges post-install complete. Enabled bridges:
EOF
for rec in "${BRIDGE_RECORDS[@]}"; do
IFS=':' read -r profile container yaml <<< "$rec"
echo " • $profile (container: crow-$container, registration: appservices/$yaml)"
done
cat <<EOF

Next steps per bridge:

Signal: DM @signalbot:${MATRIX_BRIDGE_DOMAIN:-example.com} → 'login'
(Scans QR from your phone's Signal → Linked Devices)

Telegram: DM @telegrambot:${MATRIX_BRIDGE_DOMAIN:-example.com} → 'login'
(Enter phone number + SMS code or password)

WhatsApp: DM @whatsappbot:${MATRIX_BRIDGE_DOMAIN:-example.com} → 'login qr'
(Scans QR from your phone's WhatsApp → Linked Devices)
Meta may ban the linked number — assume it's at risk.

If a bridge bot doesn't respond, check:
docker logs crow-mautrix-<bridge>
docker exec crow-dendrite cat /etc/dendrite/appservices/<yaml>

EOF
Loading
Loading