fix: Add Dendrite to docker-compose stack (#619)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-25 00:28:04 +00:00
parent df640af7c1
commit b86edd7e5d
6 changed files with 195 additions and 9 deletions

View file

@ -155,7 +155,7 @@ generate_compose() {
cat > "$compose_file" <<'COMPOSEEOF'
# docker-compose.yml — generated by disinto init
# Brings up Forgejo, Woodpecker, and the agent runtime.
# Brings up Forgejo, Woodpecker, Dendrite (Matrix), and the agent runtime.
services:
forgejo:
@ -194,6 +194,16 @@ services:
networks:
- disinto-net
dendrite:
image: matrixdotorg/dendrite-monolith:latest
restart: unless-stopped
volumes:
- dendrite-data:/etc/dendrite
environment:
DENDRITE_DOMAIN: disinto.local
networks:
- disinto-net
agents:
build: ./docker/agents
restart: unless-stopped
@ -208,18 +218,21 @@ services:
environment:
FORGE_URL: http://forgejo:3000
WOODPECKER_SERVER: http://woodpecker:8000
MATRIX_HOMESERVER: http://dendrite:8008
DISINTO_CONTAINER: "1"
env_file:
- .env
depends_on:
- forgejo
- woodpecker
- dendrite
networks:
- disinto-net
volumes:
forgejo-data:
woodpecker-data:
dendrite-data:
agent-data:
project-repos:
claude-auth:
@ -899,6 +912,144 @@ setup_woodpecker() {
fi
}
# Provision Dendrite Matrix homeserver: create bot user, room, and access token.
# Stores MATRIX_TOKEN, MATRIX_ROOM_ID, MATRIX_BOT_USER in .env.
setup_matrix() {
local use_bare="${DISINTO_BARE:-false}"
local env_file="${FACTORY_ROOT}/.env"
echo ""
echo "── Matrix setup ───────────────────────────────────────"
# In compose mode, Dendrite runs inside the network at http://dendrite:8008.
# For provisioning from the host during init, we exec into the container.
local matrix_host="http://dendrite:8008"
# Skip if MATRIX_TOKEN is already configured
if [ -n "${MATRIX_TOKEN:-}" ]; then
echo "Matrix: already configured (MATRIX_TOKEN set)"
return
fi
if [ "$use_bare" = true ]; then
echo "Matrix: skipped in bare mode (configure manually or install Dendrite)"
echo " See: https://matrix-org.github.io/dendrite/"
return
fi
# Wait for Dendrite to become healthy
echo -n "Waiting for Dendrite to start"
local retries=0
while true; do
# Probe Dendrite via docker compose exec since it's not exposed on the host
local version_resp
version_resp=$(docker compose -f "${FACTORY_ROOT}/docker-compose.yml" exec -T dendrite \
curl -sf --max-time 3 "http://localhost:8008/_matrix/client/versions" 2>/dev/null) || version_resp=""
if [ -n "$version_resp" ]; then
break
fi
retries=$((retries + 1))
if [ "$retries" -gt 60 ]; then
echo ""
echo "Warning: Dendrite did not become ready within 60s — skipping Matrix setup" >&2
echo " Run 'disinto init' again after Dendrite is healthy" >&2
return
fi
echo -n "."
sleep 1
done
echo " ready"
# Create bot user via Dendrite's create-account tool
local bot_localpart="factory-bot"
local bot_user="@${bot_localpart}:disinto.local"
local bot_pass
bot_pass="matrix-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)"
echo "Creating Matrix bot user: ${bot_user}"
docker compose -f "${FACTORY_ROOT}/docker-compose.yml" exec -T dendrite \
/usr/bin/create-account \
-config /etc/dendrite/dendrite.yaml \
-username "${bot_localpart}" \
-password "${bot_pass}" 2>/dev/null || true
# Log in to get an access token
local login_resp
login_resp=$(docker compose -f "${FACTORY_ROOT}/docker-compose.yml" exec -T dendrite \
curl -sf -X POST "http://localhost:8008/_matrix/client/v3/login" \
-H "Content-Type: application/json" \
-d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"${bot_localpart}\"},\"password\":\"${bot_pass}\"}" \
2>/dev/null) || login_resp=""
local access_token
access_token=$(printf '%s' "$login_resp" | jq -r '.access_token // empty' 2>/dev/null) || access_token=""
if [ -z "$access_token" ]; then
echo "Warning: failed to obtain Matrix access token — skipping Matrix setup" >&2
echo " Create the bot user manually via Dendrite admin API" >&2
return
fi
echo " Bot login successful"
# Create coordination room
local room_resp
room_resp=$(docker compose -f "${FACTORY_ROOT}/docker-compose.yml" exec -T dendrite \
curl -sf -X POST "http://localhost:8008/_matrix/client/v3/createRoom" \
-H "Authorization: Bearer ${access_token}" \
-H "Content-Type: application/json" \
-d '{"room_alias_name":"factory","name":"disinto factory","topic":"Autonomous code factory coordination room","preset":"private_chat"}' \
2>/dev/null) || room_resp=""
local room_id
room_id=$(printf '%s' "$room_resp" | jq -r '.room_id // empty' 2>/dev/null) || room_id=""
if [ -z "$room_id" ]; then
# Room might already exist — try resolving the alias
local alias_resp
alias_resp=$(docker compose -f "${FACTORY_ROOT}/docker-compose.yml" exec -T dendrite \
curl -sf "http://localhost:8008/_matrix/client/v3/directory/room/%23factory%3Adisinto.local" \
-H "Authorization: Bearer ${access_token}" \
2>/dev/null) || alias_resp=""
room_id=$(printf '%s' "$alias_resp" | jq -r '.room_id // empty' 2>/dev/null) || room_id=""
fi
if [ -z "$room_id" ]; then
echo "Warning: failed to create or find coordination room — skipping Matrix setup" >&2
return
fi
echo " Room: ${room_id} (alias: #factory:disinto.local)"
# Store Matrix credentials in .env
local matrix_vars=(
"MATRIX_HOMESERVER=${matrix_host}"
"MATRIX_BOT_USER=${bot_user}"
"MATRIX_TOKEN=${access_token}"
"MATRIX_ROOM_ID=${room_id}"
)
for var_line in "${matrix_vars[@]}"; do
local var_name="${var_line%%=*}"
if grep -q "^${var_name}=" "$env_file" 2>/dev/null; then
sed -i "s|^${var_name}=.*|${var_line}|" "$env_file"
else
printf '%s\n' "$var_line" >> "$env_file"
fi
done
export MATRIX_TOKEN="$access_token"
export MATRIX_BOT_USER="$bot_user"
export MATRIX_ROOM_ID="$room_id"
export MATRIX_HOMESERVER="$matrix_host"
echo " Credentials saved to .env"
echo ""
echo " To receive notifications in your Matrix client:"
echo " 1. Add 'ports: [\"8008:8008\"]' to the dendrite service in docker-compose.yml"
echo " 2. Join #factory:disinto.local from Element or another Matrix client"
}
# ── init command ─────────────────────────────────────────────────────────────
disinto_init() {
@ -1065,6 +1216,9 @@ p.write_text(text)
fi
fi
# Provision Matrix homeserver (compose mode only)
setup_matrix
# Create labels on remote
create_labels "$forge_repo" "$forge_url"
@ -1100,7 +1254,7 @@ p.write_text(text)
echo ""
echo "── Starting full stack ────────────────────────────────"
docker compose -f "${FACTORY_ROOT}/docker-compose.yml" up -d
echo "Stack: running (forgejo + woodpecker + agents)"
echo "Stack: running (forgejo + woodpecker + dendrite + agents)"
fi
echo ""