refactor: reorganizar dev-scripts en subdirectorios server/ y agent/

Se separan los scripts de gestión en dos categorías claras:
- dev-scripts/server/ — operaciones del launcher (start, stop, restart, ps, logs, dashboard)
- dev-scripts/agent/ — operaciones de agentes (new, register, verify, avatar, remove, list)

Se añade create-full.sh como script unificado que ejecuta scaffold + build + register + verify.
Se incluyen READMEs en cada subdirectorio documentando los scripts disponibles.
Los scripts originales en la raíz de dev-scripts/ se eliminan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 21:53:19 +00:00
parent c1889ab0c7
commit 6858a5f13e
19 changed files with 379 additions and 76 deletions
+89
View File
@@ -0,0 +1,89 @@
# dev-scripts/agent
Scripts para crear, registrar, verificar y gestionar agentes individuales en el sistema Matrix.
## Scripts
### new-agent.sh
Genera el scaffold completo para un nuevo agente: `config.yaml`, `agent.go` (reglas puras), directorio de prompts y data. También registra automáticamente el import y la entrada en `rulesRegistry` de `cmd/launcher/main.go`.
```bash
./dev-scripts/agent/new-agent.sh <agent-id> "Display Name"
./dev-scripts/agent/new-agent.sh monitor-bot "Monitor Agent"
```
### register.sh
Registra un nuevo bot en el servidor Matrix via Synapse admin API. Genera y guarda en `.env`: access token (`MATRIX_TOKEN_*`), password (`MATRIX_PASSWORD_*`) y pickle key (`PICKLE_KEY_*`).
Requiere `MATRIX_ADMIN_TOKEN` y `MATRIX_HOMESERVER` en `.env`.
```bash
./dev-scripts/agent/register.sh <agent-id> "Display Name"
./dev-scripts/agent/register.sh assistant-bot "Assistant"
```
### verify.sh
Verifica o regenera los dispositivos E2EE de los agentes. Genera cross-signing keys, firma el device y guarda el recovery key en `.env`. Sin este paso, los mensajes del bot aparecen como "not verified by its owner".
```bash
./dev-scripts/agent/verify.sh # verifica todos los habilitados con E2EE
./dev-scripts/agent/verify.sh assistant-bot # verifica uno específico
```
### avatar.sh
Sube una imagen como avatar del bot en Matrix y sincroniza el displayname desde el `config.yaml`.
```bash
./dev-scripts/agent/avatar.sh <agent-id> <image-path>
./dev-scripts/agent/avatar.sh assistant-bot static/assistant.jpg
```
### reset-password.sh
Resetea la contraseña de un bot existente via Synapse admin API sin crear nueva sesión ni cambiar el device ID. El access token actual sigue siendo válido.
```bash
./dev-scripts/agent/reset-password.sh <agent-id>
./dev-scripts/agent/reset-password.sh assistant-bot
```
### remove.sh
Deshabilita un agente marcándolo como `enabled: false` en su `config.yaml`. No borra datos — los preserva en `agents/<id>/data/`.
```bash
./dev-scripts/agent/remove.sh <agent-id>
```
### list.sh
Muestra todos los agentes registrados con su estado (running/stopped/disabled), versión y descripción en una tabla formateada.
```bash
./dev-scripts/agent/list.sh
```
## Flujo típico para un nuevo agente
```bash
# 1. Crear scaffold
./dev-scripts/agent/new-agent.sh mi-bot "Mi Bot"
# 2. Editar config, reglas y prompt
# agents/mi-bot/config.yaml
# agents/mi-bot/agent.go
# agents/mi-bot/prompts/system.md
# 3. Registrar en Matrix
./dev-scripts/agent/register.sh mi-bot "Mi Bot"
# 4. Verificar E2EE
./dev-scripts/agent/verify.sh mi-bot
# 5. Arrancar
./dev-scripts/server/start.sh
```
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# avatar.sh — sube una imagen y la establece como avatar de un bot.
# También sincroniza el displayname desde el config (agent.name).
#
# Uso:
# ./dev-scripts/agent/avatar.sh <agent-id> <image-path>
#
# Ejemplos:
# ./dev-scripts/agent/avatar.sh assistant-bot assets/assistant.png
# ./dev-scripts/agent/avatar.sh devops-bot assets/devops.jpg
source "$(dirname "$0")/../_common.sh"
load_env
AGENT_ID="${1:-}"
IMAGE_PATH="${2:-}"
[[ -n "$AGENT_ID" ]] || fail "Uso: $0 <agent-id> <image-path>"
[[ -n "$IMAGE_PATH" ]] || fail "Uso: $0 <agent-id> <image-path>"
[[ -f "$IMAGE_PATH" ]] || fail "Imagen no encontrada: $IMAGE_PATH"
# Resuelve el binario de agentctl: compiled > go run
if [[ -f "$REPO_ROOT/bin/agentctl" ]]; then
CTL="$REPO_ROOT/bin/agentctl"
else
info "bin/agentctl no encontrado, usando go run ./cmd/agentctl"
CTL="$GO run ./cmd/agentctl"
fi
info "Subiendo avatar para $AGENT_ID desde $IMAGE_PATH..."
$CTL avatar "$AGENT_ID" "$IMAGE_PATH"
info "Sincronizando displayname desde config..."
$CTL displayname "$AGENT_ID"
ok "Perfil de $AGENT_ID actualizado."
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# create-full.sh — pipeline completo para crear un agente funcional
#
# Ejecuta en orden: scaffold → build → register → verify E2EE
# NO arranca el agente — primero personalizar agent.go, config.yaml y prompts/system.md
#
# Uso:
# ./dev-scripts/agent/create-full.sh <agent-id> "Display Name"
#
# Ejemplo:
# ./dev-scripts/agent/create-full.sh monitor-bot "Monitor Agent"
#
# Requisitos en .env:
# MATRIX_ADMIN_TOKEN, MATRIX_HOMESERVER, MATRIX_SERVER_NAME
source "$(dirname "$0")/../_common.sh"
load_env
need_arg "${1:-}"
ID="$1"
DISPLAYNAME="${2:-$ID}"
NORM="$(normalize_id "$ID")"
SCRIPT_DIR="$(dirname "$0")"
echo ""
echo -e "${BLU}═══════════════════════════════════════════════════════${RST}"
echo -e "${BLU} Creando agente: ${GRN}$ID${BLU} ($DISPLAYNAME)${RST}"
echo -e "${BLU}═══════════════════════════════════════════════════════${RST}"
echo ""
# ── Paso 1: Scaffold ─────────────────────────────────────────────────────
info "Paso 1/4 — Scaffold (agent.go, config.yaml, prompts, launcher)"
echo ""
"$SCRIPT_DIR/new-agent.sh" "$ID" "$DISPLAYNAME"
echo ""
# ── Paso 2: Verificar compilación ─────────────────────────────────────────
info "Paso 2/4 — Verificando compilación..."
if "$GO" build -tags goolm ./... 2>&1; then
ok "Compilación exitosa"
else
fail "Error de compilación — revisa agents/$ID/agent.go y cmd/launcher/main.go"
fi
echo ""
# ── Paso 3: Registrar en Matrix ──────────────────────────────────────────
info "Paso 3/4 — Registrando en Matrix..."
echo ""
# Reload .env in case new-agent.sh or previous steps changed it
load_env
"$SCRIPT_DIR/register.sh" "$ID" "$DISPLAYNAME"
echo ""
# ── Paso 4: Verificar E2EE ───────────────────────────────────────────────
info "Paso 4/4 — Verificación E2EE (cross-signing + recovery key)..."
echo ""
# Reload .env to pick up token, password, pickle key from register.sh
load_env
"$SCRIPT_DIR/verify.sh" "$ID"
echo ""
# ── Resumen ──────────────────────────────────────────────────────────────
echo -e "${GRN}═══════════════════════════════════════════════════════${RST}"
echo -e "${GRN} ✓ Agente $ID creado exitosamente${RST}"
echo -e "${GRN}═══════════════════════════════════════════════════════${RST}"
echo ""
echo -e " ${BLU}Archivos creados:${RST}"
echo -e " agents/$ID/agent.go"
echo -e " agents/$ID/config.yaml"
echo -e " agents/$ID/prompts/system.md"
echo ""
echo -e " ${BLU}Variables en .env:${RST}"
echo -e " MATRIX_TOKEN_${NORM}"
echo -e " MATRIX_PASSWORD_${NORM}"
echo -e " PICKLE_KEY_${NORM}"
echo -e " SSSS_RECOVERY_KEY_${NORM}"
echo ""
echo -e " ${BLU}Launcher actualizado:${RST}"
echo -e " cmd/launcher/main.go (import + rulesRegistry)"
echo ""
echo -e "${YLW}Siguiente paso:${RST}"
echo ""
echo -e " 1. Personalizar los archivos del agente:"
echo -e " ${DIM}agents/$ID/agent.go${RST} — reglas de decisión"
echo -e " ${DIM}agents/$ID/config.yaml${RST} — LLM, tools, personalidad"
echo -e " ${DIM}agents/$ID/prompts/system.md${RST} — system prompt"
echo ""
echo -e " 2. Arrancar:"
echo -e " ${DIM}./dev-scripts/server/start.sh${RST}"
echo ""
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# list.sh — muestra todos los agentes y su estado actual
# Uso: ./dev-scripts/agent/list.sh
source "$(dirname "$0")/../_common.sh"
printf "%-22s %-12s %-8s %s\n" "ID" "STATUS" "VERSION" "DESCRIPTION"
printf '%s\n' "$(printf '─%.0s' {1..70})"
while IFS='|' read -r id version enabled desc _cfg; do
status=$(agent_status "$id" "$enabled")
case "$status" in
running) label="${GRN}● running${RST}" ;;
stopped) label="${DIM}○ stopped${RST}" ;;
disabled) label="${YLW} disabled${RST}" ;;
*) label="$status" ;;
esac
# Truncate description
[[ ${#desc} -gt 38 ]] && desc="${desc:0:37}"
printf "%-22s " "$id"
printf "${label}"
printf " %-8s %s\n" "$version" "$desc"
done < <(list_agents_raw)
+396
View File
@@ -0,0 +1,396 @@
#!/usr/bin/env bash
# new-agent.sh — genera el scaffold de un nuevo agente
#
# Uso:
# ./dev-scripts/agent/new-agent.sh <agent-id> [displayname]
#
# Ejemplo:
# ./dev-scripts/agent/new-agent.sh monitor-bot "Monitor Agent"
#
# Crea:
# agents/<agent-id>/config.yaml (basado en el assistant como plantilla)
# agents/<agent-id>/agent.go (reglas puras vacías, listo para extender)
# agents/<agent-id>/prompts/ (directorio para system prompt)
# agents/<agent-id>/data/ (directorio de datos, en .gitignore)
#
# También te recuerda los dos pasos manuales que quedan.
source "$(dirname "$0")/../_common.sh"
load_env
need_arg "${1:-}"
ID="$1"
DISPLAYNAME="${2:-$ID}"
PACKAGE="$(echo "$ID" | tr '-' '_' | sed 's/_bot//')" # "monitor-bot" → "monitor"
NORM="$(normalize_id "$ID")" # "monitor-bot" → "MONITOR_BOT"
DIR="agents/$ID"
[[ -d "$DIR" ]] && fail "Ya existe agents/$ID — ¿ya fue creado?"
info "Creando scaffold para $ID..."
mkdir -p "$DIR/prompts" "$DIR/data"
# ── config.yaml ────────────────────────────────────────────────────────────
cat > "$DIR/config.yaml" <<YAML
# ============================================
# IDENTIDAD
# ============================================
agent:
id: $ID
name: "$DISPLAYNAME"
version: "1.0.0"
enabled: true
description: "Descripción del agente $DISPLAYNAME"
tags: [$(echo "$ID" | tr '-' ',')]
# ============================================
# PERSONALIDAD Y COMPORTAMIENTO
# ============================================
personality:
tone: friendly
verbosity: concise
language: es
languages_supported: [es, en]
emoji_style: minimal
prefix: "🤖"
error_style: helpful
templates:
greeting: "Hola, soy $DISPLAYNAME. ¿En qué puedo ayudarte?"
unknown_command: "No reconozco ese comando. Escríbeme directamente."
permission_denied: "No tengo permiso para hacer eso."
error: "Algo salió mal: {{.Error}}"
success: "{{.Summary}}"
busy: "Procesando, dame un momento..."
behavior:
proactive: false
ask_confirmation: false
show_reasoning: false
thread_replies: true
typing_indicator: true
acknowledge_receipt: false
# ============================================
# LLM
# ============================================
llm:
primary:
provider: openai
model: gpt-4o
api_key_env: OPENAI_API_KEY
base_url: ""
max_tokens: 4096
temperature: 0.7
fallback:
provider: ""
model: ""
api_key_env: ""
base_url: ""
max_tokens: 0
temperature: 0
reasoning:
system_prompt_file: "prompts/system.md"
context_window: 16384
memory_messages: 20
tool_use:
enabled: false
max_iterations: 3
parallel_calls: false
rate_limit:
requests_per_minute: 30
tokens_per_minute: 100000
concurrent_requests: 3
# ============================================
# TOOLS — ajustar según necesidades del agente
# ============================================
tools:
ssh:
enabled: false
allowed_targets: []
forbidden_commands: []
timeout: 0s
max_concurrent: 0
require_confirmation: []
http:
enabled: false
allowed_domains: []
timeout: 0s
max_retries: 0
scripts:
enabled: false
scripts_dir: ""
allowed: []
timeout: 0s
sandbox: false
file_ops:
enabled: false
allowed_paths: []
read_only: true
mcp:
enabled: false
servers: []
expose:
port: 0
tools: []
# ============================================
# MATRIX
# ============================================
matrix:
homeserver: "${MATRIX_HOMESERVER}"
user_id: "@$ID:${MATRIX_SERVER_NAME}"
access_token_env: MATRIX_TOKEN_${NORM}
device_id: ""
encryption:
enabled: true
store_path: "./agents/${ID}/data/crypto/"
pickle_key_env: PICKLE_KEY_${NORM}
trust_mode: tofu
recovery_key_env: SSSS_RECOVERY_KEY_${NORM}
rooms:
listen: []
respond: []
admin: []
filters:
command_prefix: "!"
mention_respond: true
dm_respond: true
ignore_bots: true
ignore_users: []
min_power_level: 0
# ============================================
# INTER-AGENTES
# ============================================
agents:
peers: []
delegation:
enabled: false
can_delegate_to: []
can_receive_from: []
max_delegation_depth: 1
timeout: 30s
protocol:
format: json
channel: matrix
heartbeat_interval: 60s
# ============================================
# SSH
# ============================================
ssh:
defaults:
user: ""
port: 22
key_file_env: ""
known_hosts: ""
keepalive_interval: 0s
timeout: 0s
targets: {}
# ============================================
# SEGURIDAD
# ============================================
security:
roles:
admin:
users: ["@admin:\${MATRIX_SERVER_NAME}"]
actions: ["*"]
user:
users: ["*"]
actions: ["help"]
audit:
enabled: false
log_file: "./data/audit.log"
log_to_room: ""
include: []
secrets:
provider: env
# ============================================
# SCHEDULING
# ============================================
schedules: []
# ============================================
# OBSERVABILIDAD
# ============================================
observability:
logging:
level: info
format: json
output: stdout
file: "./data/$ID.log"
metrics:
enabled: false
port: 0
path: /metrics
export: prometheus
health:
enabled: true
port: 0
path: /healthz
tracing:
enabled: false
provider: ""
endpoint: ""
# ============================================
# RESILIENCIA
# ============================================
resilience:
circuit_breaker:
failure_threshold: 5
timeout: 30s
half_open_max: 2
retry:
max_attempts: 2
backoff: exponential
initial_delay: 1s
max_delay: 10s
shutdown:
timeout: 10s
drain_messages: true
save_state: false
state_file: ""
queue:
enabled: true
max_size: 50
priority_users: ["@admin:\${MATRIX_SERVER_NAME}"]
# ============================================
# ALMACENAMIENTO
# ============================================
storage:
state:
backend: sqlite
path: "./data/$ID.db"
cache:
enabled: true
backend: memory
ttl: 5m
max_entries: 200
history:
backend: sqlite
path: "./data/history.db"
retention: 168h
YAML
# ── agent.go ───────────────────────────────────────────────────────────────
cat > "$DIR/agent.go" <<GO
// Package $PACKAGE defines the pure rules for the $DISPLAYNAME.
package $PACKAGE
import "github.com/enmanuel/agents/pkg/decision"
// Rules returns the decision rules for the $ID.
func Rules() []decision.Rule {
return []decision.Rule{
{
Name: "help",
Match: decision.MatchCommand("help"),
Actions: []decision.Action{{
Kind: decision.ActionKindReply,
Reply: &decision.ReplyAction{
Content: "Soy $DISPLAYNAME. Escríbeme lo que necesitas.",
},
}},
},
// Catch-all: DMs y menciones van al LLM
{
Name: "llm-fallback",
Match: func(ctx decision.MessageContext) bool {
return ctx.IsDirectMsg || ctx.IsMention
},
Actions: []decision.Action{{
Kind: decision.ActionKindLLM,
LLM: &decision.LLMAction{},
}},
},
}
}
GO
# ── system prompt ──────────────────────────────────────────────────────────
cat > "$DIR/prompts/system.md" <<MD
# $DISPLAYNAME — System Prompt
Eres $DISPLAYNAME. Describe aquí el rol, capacidades y restricciones del agente.
## Rol
...
## Capacidades
...
## Restricciones
...
MD
ok "Scaffold creado en $DIR/"
echo ""
# ── Actualizar cmd/launcher/main.go ───────────────────────────────────────
LAUNCHER="cmd/launcher/main.go"
if grep -q "\"$ID\":" "$LAUNCHER" 2>/dev/null; then
warn "$ID ya está en rulesRegistry de $LAUNCHER — saltando"
else
TAB=$'\t'
IMPORT_LINE="${TAB}${PACKAGE}agent \"github.com/enmanuel/agents/agents/$ID\""
REGISTRY_LINE="${TAB}\"$ID\": ${PACKAGE}agent.Rules,"
# Insertar import después del último import agents/agents/*
if awk -v new_import="$IMPORT_LINE" '
{
lines[NR] = $0
if ($0 ~ /[a-z_]+agent "github\.com\/enmanuel\/agents\/agents\/[^"]+"/)
last_import = NR
}
END {
if (!last_import) { for (i=1;i<=NR;i++) print lines[i]; exit 1 }
for (i = 1; i <= NR; i++) {
print lines[i]
if (i == last_import) print new_import
}
}
' "$LAUNCHER" > /tmp/_launcher_tmp; then
mv /tmp/_launcher_tmp "$LAUNCHER"
ok "Import añadido en $LAUNCHER"
else
warn "No se pudo insertar el import automáticamente — añádelo manualmente:"
echo -e " ${GRN}${IMPORT_LINE}${RST}"
fi
# Insertar entry en rulesRegistry antes del cierre }
if awk -v new_entry="$REGISTRY_LINE" '
/^var rulesRegistry/ { in_reg = 1 }
in_reg && /^\}/ { print new_entry; in_reg = 0 }
{ print }
' "$LAUNCHER" > /tmp/_launcher_tmp; then
mv /tmp/_launcher_tmp "$LAUNCHER"
ok "Registry entry añadida en $LAUNCHER"
else
warn "No se pudo insertar el registry entry — añádelo manualmente:"
echo -e " ${GRN}${REGISTRY_LINE}${RST}"
fi
fi
echo ""
echo -e "${YLW}Quedan 3 pasos:${RST}"
echo ""
echo -e " ${DIM}1. ./dev-scripts/agent/register.sh $ID \"$DISPLAYNAME\"${RST} # registra en Matrix + genera token, password, pickle key"
echo -e " ${DIM}2. ./dev-scripts/agent/verify.sh $ID${RST} # genera cross-signing keys + verifica device"
echo -e " ${DIM}3. ./dev-scripts/server/start.sh $ID${RST} # arranca el agente"
echo ""
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# register.sh — registra un nuevo bot en el servidor Matrix via Synapse admin API
#
# Uso:
# ./dev-scripts/agent/register.sh <username> [displayname]
#
# Ejemplos:
# ./dev-scripts/agent/register.sh assistant-bot "Assistant"
# ./dev-scripts/agent/register.sh devops-bot "DevOps Agent"
#
# Genera y guarda en .env:
# MATRIX_TOKEN_<NORM>=... (access token)
# MATRIX_PASSWORD_<NORM>=... (password para UIA)
# PICKLE_KEY_<NORM>=... (E2EE crypto store key)
#
# Requiere en .env:
# MATRIX_ADMIN_TOKEN=syt_...
# MATRIX_HOMESERVER=https://...
source "$(dirname "$0")/../_common.sh"
load_env
need_arg "${1:-}"
USERNAME="$1"
DISPLAYNAME="${2:-$USERNAME}"
NORM="$(normalize_id "$USERNAME")"
ENV_VAR="MATRIX_TOKEN_${NORM}"
[[ -n "${MATRIX_ADMIN_TOKEN:-}" ]] || fail "MATRIX_ADMIN_TOKEN no está en .env"
[[ -n "${MATRIX_HOMESERVER:-}" ]] || fail "MATRIX_HOMESERVER no está en .env"
info "Registrando @${USERNAME}:${MATRIX_SERVER_NAME:-$MATRIX_HOMESERVER}..."
dim " Env var prefix: ${NORM}"
echo ""
# Ejecutar cmd/register y capturar su output completo
OUTPUT=$("$GO" run ./cmd/register \
--homeserver "$MATRIX_HOMESERVER" \
--username "$USERNAME" \
--displayname "$DISPLAYNAME" \
--env-var "$ENV_VAR" 2>&1) || fail "cmd/register falló:\n$OUTPUT"
echo "$OUTPUT"
echo ""
# ── Parsear y guardar cada variable en .env ──────────────────────────────
save_env_var() {
local key="$1" value="$2"
[[ -n "$value" ]] || return
# Quote values with spaces
if [[ "$value" == *" "* ]]; then
value="\"${value}\""
fi
if grep -q "^${key}=" .env; then
awk -v key="$key" -v val="$value" \
'index($0, key "=") == 1 { print key "=" val; next } { print }' \
.env > /tmp/_env_tmp && mv /tmp/_env_tmp .env
ok "$key actualizado en .env"
else
printf '%s=%s\n' "$key" "$value" >> .env
ok "$key añadido a .env"
fi
}
# Extract parseable lines from output
TOKEN=$(echo "$OUTPUT" | grep "^${ENV_VAR}=" | cut -d= -f2-)
PASSWORD=$(echo "$OUTPUT" | grep "^MATRIX_PASSWORD_${NORM}=" | cut -d= -f2-)
PICKLE_KEY=$(echo "$OUTPUT" | grep "^PICKLE_KEY_${NORM}=" | cut -d= -f2-)
[[ -n "$TOKEN" ]] || fail "No se encontró '${ENV_VAR}=' en el output"
save_env_var "$ENV_VAR" "$TOKEN"
save_env_var "MATRIX_PASSWORD_${NORM}" "$PASSWORD"
save_env_var "PICKLE_KEY_${NORM}" "$PICKLE_KEY"
echo ""
echo -e "${YLW}Siguientes pasos:${RST}"
echo ""
echo -e " ${DIM}1. ./dev-scripts/agent/verify.sh $USERNAME${RST} # genera cross-signing keys E2EE"
echo -e " ${DIM}2. ./dev-scripts/server/start.sh $USERNAME${RST} # arranca el agente"
echo ""
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# remove.sh — deshabilita un agente (enabled: false). No borra datos.
#
# Uso:
# ./dev-scripts/agent/remove.sh assistant-bot
source "$(dirname "$0")/../_common.sh"
need_arg "${1:-}"
TARGET="$1"
found=false
while IFS='|' read -r id _version _enabled _desc cfg; do
[[ "$id" != "$TARGET" ]] && continue
found=true
# Marcar como disabled en el config
if grep -q 'enabled: true' "$cfg"; then
sed -i 's/enabled: true/enabled: false/' "$cfg"
ok "$id marcado como disabled en $cfg"
info "Reinicia el launcher para aplicar: ./dev-scripts/server/server.sh restart"
else
warn "$id ya estaba marcado como disabled"
fi
dim " Datos preservados en agents/$id/data/"
done < <(list_agents_raw)
"$found" || fail "Agente '$TARGET' no encontrado"
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# reset-password.sh — resetea la contraseña de un bot existente y la escribe en .env
#
# A diferencia de register.sh, este script NO crea una nueva sesión ni cambia el device ID.
# El token de acceso actual sigue siendo válido.
# Útil para añadir MATRIX_PASSWORD_X a .env en bots ya registrados.
#
# Uso:
# ./dev-scripts/agent/reset-password.sh <agent-id>
#
# Ejemplo:
# ./dev-scripts/agent/reset-password.sh assistant-bot
#
# Requiere en .env:
# MATRIX_ADMIN_TOKEN=syt_...
# MATRIX_HOMESERVER=https://...
# MATRIX_SERVER_NAME=...
source "$(dirname "$0")/../_common.sh"
load_env
need_arg "${1:-}"
ID="$1"
USERNAME="$ID"
PASSWORD_ENV_VAR="MATRIX_PASSWORD_$(echo "$ID" | tr '[:lower:]-' '[:upper:]_' | sed 's/_BOT$//')"
[[ -n "${MATRIX_ADMIN_TOKEN:-}" ]] || fail "MATRIX_ADMIN_TOKEN no está en .env"
[[ -n "${MATRIX_HOMESERVER:-}" ]] || fail "MATRIX_HOMESERVER no está en .env"
[[ -n "${MATRIX_SERVER_NAME:-}" ]] || fail "MATRIX_SERVER_NAME no está en .env"
USER_ID="@${USERNAME}:${MATRIX_SERVER_NAME}"
# Generar nueva contraseña aleatoria
NEW_PASSWORD=$(head -c 24 /dev/urandom | od -A n -t x1 | tr -d ' \n')
info "Reseteando contraseña de ${USER_ID}..."
info "(El token de acceso actual NO cambia — solo la contraseña)"
# Synapse admin API: reset password sin cerrar sesiones existentes
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
"${MATRIX_HOMESERVER}/_synapse/admin/v1/reset_password/${USER_ID}" \
-H "Authorization: Bearer ${MATRIX_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"new_password\": \"${NEW_PASSWORD}\", \"logout_devices\": false}")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | head -1)
if [[ "$HTTP_CODE" != "200" ]]; then
fail "Admin API devolvió HTTP $HTTP_CODE: $BODY"
fi
# Escribir en .env
if grep -q "^${PASSWORD_ENV_VAR}=" .env; then
awk -v key="$PASSWORD_ENV_VAR" -v val="$NEW_PASSWORD" \
'index($0, key "=") == 1 { print key "=" val; next } { print }' \
.env > /tmp/_env_tmp && mv /tmp/_env_tmp .env
ok "$PASSWORD_ENV_VAR actualizado en .env"
else
printf '\n%s=%s\n' "$PASSWORD_ENV_VAR" "$NEW_PASSWORD" >> .env
ok "$PASSWORD_ENV_VAR añadido a .env"
fi
echo ""
ok "Contraseña reseteada para ${USER_ID}"
dim " El bot usará esta contraseña para cross-signing bootstrap en el próximo arranque."
dim " Reinícialo con: ./dev-scripts/server/stop.sh $ID && ./dev-scripts/server/start.sh $ID"
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env bash
# verify.sh — (re)verifica dispositivos E2EE de agentes Matrix
#
# Genera/sube cross-signing keys y firma el device de cada agente.
# Usa el MISMO crypto store que el agente para que las keys queden disponibles.
#
# Uso:
# ./dev-scripts/agent/verify.sh # verifica todos los habilitados con E2EE
# ./dev-scripts/agent/verify.sh assistant-bot # verifica uno específico
source "$(dirname "$0")/../_common.sh"
load_env
TARGET="${1:-}"
# ── YAML helpers (simple grep-based, no deps) ────────────────────────────
yaml_val() {
# Extract a simple YAML value: yaml_val file "key"
# Handles both quoted and unquoted values.
local file="$1" key="$2"
grep -m1 "^\s*${key}:" "$file" 2>/dev/null \
| sed 's/^[^:]*:\s*//' \
| tr -d '"' \
| tr -d "'" \
| xargs
}
# ── Verify a single agent ────────────────────────────────────────────────
verify_agent() {
local cfg="$1"
local agent_id; agent_id="$(yaml_val "$cfg" "id")"
local agent_dir; agent_dir="$(dirname "$cfg")"
# Check E2EE is enabled
local enc_enabled; enc_enabled="$(yaml_val "$cfg" "enabled")"
# The first "enabled" is agent.enabled; we need encryption.enabled specifically
enc_enabled="$(grep -A5 'encryption:' "$cfg" | grep -m1 'enabled:' | awk '{print $2}')"
if [[ "$enc_enabled" != "true" ]]; then
dim " $agent_id — E2EE deshabilitado, saltando"
return 0
fi
# Extract config values
local user_id; user_id="$(yaml_val "$cfg" "user_id")"
local username; username="$(echo "$user_id" | sed 's/@\([^:]*\):.*/\1/')"
local token_env; token_env="$(yaml_val "$cfg" "access_token_env")"
local pickle_env; pickle_env="$(yaml_val "$cfg" "pickle_key_env")"
local recovery_env; recovery_env="$(yaml_val "$cfg" "recovery_key_env")"
local store_path; store_path="$(grep -A5 'encryption:' "$cfg" | grep -m1 'store_path:' | sed 's/^[^:]*:\s*//' | tr -d '"' | xargs)"
local token="${!token_env:-}"
local pickle_key="${!pickle_env:-}"
# Find password — convention: MATRIX_PASSWORD_<NORMALIZED>
local norm; norm="$(echo "$username" | tr '-' '_' | tr '[:lower:]' '[:upper:]')"
local pass_env="MATRIX_PASSWORD_${norm}"
local password="${!pass_env:-}"
# Validate required values
if [[ -z "$token" ]]; then
fail " $agent_id$token_env no está en .env"
return 1
fi
if [[ -z "$password" ]]; then
warn " $agent_id$pass_env no está en .env, intentando sin password..."
fi
info "$agent_id — verificando device..."
dim " user: $username"
dim " store: $store_path"
dim " pickle_env: $pickle_env"
dim " token_env: $token_env"
# Stop agent if running (crypto store can't be shared)
local was_running=false
if is_running "$agent_id"; then
was_running=true
info " Deteniendo $agent_id antes de verificar..."
"$REPO_ROOT/dev-scripts/server/stop.sh" "$agent_id"
sleep 1
fi
# Build verify command
local verify_bin="$REPO_ROOT/bin/verify"
if [[ ! -x "$verify_bin" ]] || [[ "$(find ./cmd/verify -newer "$verify_bin" 2>/dev/null | head -1)" ]]; then
info " Compilando cmd/verify..."
mkdir -p "$(dirname "$verify_bin")"
"$GO" build -tags goolm -o "$verify_bin" ./cmd/verify || {
fail " No se pudo compilar cmd/verify"
return 1
}
fi
# Run verification
local verify_args=(
--homeserver "$MATRIX_HOMESERVER"
--username "$username"
--token "$token"
--store "$store_path"
)
if [[ -n "$password" ]]; then
verify_args+=(--password "$password")
fi
if [[ -n "$pickle_key" ]]; then
verify_args+=(--pickle-key "$pickle_key")
fi
local output
if output=$("$verify_bin" "${verify_args[@]}" 2>&1); then
ok "$agent_id — verificación exitosa"
# Extract recovery key from output if present
local new_rk
new_rk="$(echo "$output" | grep "^SSSS_RECOVERY_KEY_" | cut -d= -f2-)"
if [[ -n "$new_rk" && -n "$recovery_env" ]]; then
# Update .env with new recovery key (quoted — keys contain spaces)
local quoted_rk="\"${new_rk}\""
if grep -q "^${recovery_env}=" "$REPO_ROOT/.env"; then
sed -i "s|^${recovery_env}=.*|${recovery_env}=${quoted_rk}|" "$REPO_ROOT/.env"
ok " Recovery key actualizada en .env ($recovery_env)"
else
echo "${recovery_env}=${quoted_rk}" >> "$REPO_ROOT/.env"
ok " Recovery key añadida a .env ($recovery_env)"
fi
fi
else
warn "$agent_id — verify output:"
echo "$output"
# If it says keys already exist, that's usually fine
if echo "$output" | grep -q "signed with cross-signing key"; then
ok "$agent_id — device firmado con keys existentes"
else
warn "$agent_id — puede necesitar atención manual"
fi
fi
echo "$output" | sed 's/^/ /'
# Restart agent if it was running
if [[ "$was_running" == "true" ]]; then
info " Reiniciando $agent_id..."
"$REPO_ROOT/dev-scripts/server/start.sh" "$agent_id"
fi
echo
}
# ── Main ──────────────────────────────────────────────────────────────────
echo
info "Verificación E2EE de agentes Matrix"
echo
if [[ -n "$TARGET" ]]; then
cfg="$(config_path_for "$TARGET")"
[[ -n "$cfg" ]] || fail "Agente '$TARGET' no encontrado"
verify_agent "$cfg"
else
while IFS='|' read -r id version enabled desc cfg; do
[[ "$enabled" == "true" ]] || continue
verify_agent "$cfg"
done < <(list_agents_raw)
fi
ok "Verificación completada"