remote.DocumentaciónInicio

Dispositivos Linux, FreeBSD, NetBSD, Windows, macOS y Android

Los dispositivos Linux (glibc / musl), FreeBSD, NetBSD, Windows, macOS y Android comparten remote-agent, Exchange, remote-cli y el cliente móvil de operaciones. El agente y la CLI usan nombres genéricos; las variables de entorno se unifican como REMOTE_*.

Instalación con un comando (Windows / Linux / macOS / FreeBSD / NetBSD)

El script del sitio oficial descarga y verifica el paquete correspondiente al sistema, coloca los binarios en ~/.local/bin y después invoca remote-agent install para registrar el inicio automático del agente al iniciar sesión:

Elija el comando adecuado y copie cada comando por separado.

Instalar Agent en el dispositivo administrado

curl -fsSL https://remote.ivjn.us/install.sh | bash -s -- agent

Instalar CLI en el equipo del operador

curl -fsSL https://remote.ivjn.us/install.sh | bash -s -- cli

En Windows, ejecútelo en Git Bash. Para el agente, escriba las credenciales después de la tubería: bash -s -- agent --device-id NAME --token TOKEN (o use un config.json / REMOTE_CONFIG existente). Las variables de entorno situadas antes de curl no llegan a bash. Para systemd a nivel de sistema en Linux, añada --system. El script distingue automáticamente glibc y musl (Alpine, etc.) y también admite Linux arm64 (aarch64); REMOTE_LIBC=musl fuerza la selección del paquete musl. FreeBSD / NetBSD: la instalación de usuario permanece en ~/.local/bin; sudo remote-agent install --system --config CONFIG registra rc.d. El control del escritorio requiere una sesión X11 con python3 y xdotool. Android y OpenWrt usan sus paquetes propios.

Comandos y servicios

Los comandos del dispositivo y de CLI son remote-agent y remote-cli. remote-cli escribe por defecto en remote-cli/config.toml; un REMOTE_CLI_CONFIG explícito tiene prioridad.

El paquete OpenWrt, el archivo UCI y el servicio init se llaman remote-agent. El archivo de estado es /var/run/remote-agent.state y el de configuración es /etc/config/remote-agent. El complemento LuCI luci-app-remote depende de este paquete. REMOTE_AGENT_SRC establece la ruta del código fuente para compilar.

Dispositivo Ejecución de comandos Terminal interactiva Ejecución persistente ubus
Linux /bin/sh -c Unix PTY systemd o proceso en primer plano Solo OpenWrt
Linux musl (Alpine 3.x, etc.), x64 / arm64 /bin/sh -c Unix PTY Proceso en primer plano o servicio OpenRC propio (sin systemd) No compatible
FreeBSD 14+, amd64 / arm64 /bin/sh -c Unix PTY rc.d (root/sistema); primer plano (usuario) No compatible
NetBSD 10+, amd64 /bin/sh -c Unix PTY rc.d (root/sistema); primer plano (usuario) No compatible
Windows 10 1809+/11, Server 2019+ Windows PowerShell 5.1 ConPTY + PowerShell Tarea programada al iniciar sesión o proceso en primer plano No compatible
macOS 11+, Apple Silicon / Intel /bin/sh -c Unix PTY LaunchAgent de launchd o proceso en primer plano No compatible
Android 8.0+, arm64 / x86_64 /system/bin/sh -c Unix PTY Servicio en primer plano de un APK independiente No compatible

Configuración común

Primero registre el dispositivo con remote-cli device add <id> y obtenga su token. En Linux/Windows, cree un config.json que solo pueda leer la cuenta que ejecuta el agente:

{
  "server": "wss://mgmt.example.com",
  "device_id": "my-device",
  "token": "REPLACE_WITH_DEVICE_TOKEN"
}
remote-agent --config /path/to/config.json

El orden de prioridad es: línea de comandos / variables de entorno REMOTE_SERVER, REMOTE_DEVICE_ID, REMOTE_TOKEN; después JSON; y finalmente UCI como alternativa solo en OpenWrt. JSON también admite state_file, que se puede sustituir mediante --state-file / REMOTE_STATE_FILE. Sin archivo de estado explícito, OpenWrt sigue usando /var/run/remote-agent.state; las demás plataformas buscan sucesivamente LOCALAPPDATA, XDG_STATE_HOME y $HOME/.local/state, con la ruta relativa remote-agent/agent.state, y recurren al directorio temporal si no hay directorio de usuario. Cada instancia debe tener su propia ruta de estado.

Utilice wss:// para las conexiones de producción. El ws://127.0.0.1 de los ejemplos es solo para validación local.

Notificaciones del enlace del agente

Puede configurar "notify_urls": ["https://example.invalid/notify"] en JSON o usar remote-agent --notify-url https://example.invalid/notify (repetible); la variable de entorno REMOTE_NOTIFY_URL acepta direcciones separadas por comas. Una lista no vacía de la línea de comandos o del entorno sustituye la configuración JSON; de lo contrario se usa JSON, y finalmente, solo en OpenWrt, el UCI remote-agent.main.notify_url, separado por comas. Si todas las fuentes se omiten o están vacías, se deshabilitan los POST.

Tras registrarse correctamente se envía agent.connected; al terminar una sesión establecida se envía una sola vez agent.disconnected; si el registro se rechaza explícitamente, solo se envía agent.rejected. Los fallos de red anteriores al registro no generan notificación. El POST usa el mismo objeto JSON que Web Push de Exchange (title, body, url, event, device_id, occurred_at), pero describe el enlace del agente, no el estado de conexión del dispositivo: el prefijo de event es agent. en lugar de device., url es "" (sin ruta a la consola Web Push) y no se envía tag. El cuerpo de las desconexiones y rechazos incluye un breve resumen seguro del motivo, sin el token del dispositivo; la fecha usa RFC3339 UTC.

Las notificaciones se envían en segundo plano. Ante un fallo solo se registra el nombre del evento, no la dirección de destino, y la reconexión del agente continúa. Los reintentos siguen una política similar a Web Push de Exchange: hasta 5 intentos; se reintentan errores de red y HTTP 408/429/5xx, y se descartan 404/410 y otros 4xx no recuperables. Se prioriza Retry-After en segundos enteros; de lo contrario se usa espera exponencial desde 5 segundos, limitada a 1–900 segundos. Solo se admite HTTP/HTTPS y no se siguen redirecciones. No incluya nombres de usuario, contraseñas ni otras credenciales en las URL; se rechazan las direcciones con usuario/contraseña.

Proxy de salida y mantenimiento de la conexión

Tanto el modo sin interfaz (headless) como --ui conectan con Exchange mediante el mismo client::run. WebSocket lee las variables de proxy heredadas por el proceso del agente: utiliza el primer valor no vacío, en orden, de HTTPS_PROXY / https_proxy, ALL_PROXY / all_proxy, HTTP_PROXY / http_proxy; los hosts coincidentes con NO_PROXY / no_proxy omiten el proxy. El WebSocket de remote-cli aplica las mismas reglas (#36/#37, #39). Para el inicio automático o los servicios en segundo plano, configure estas variables en su entorno de ejecución; establecerlas en otro terminal no las transmite a un proceso ya iniciado.

Cuando el WSS del dispositivo usa un proxy, primero establece un túnel HTTP CONNECT y después conecta con el destino mediante rustls. El ALPN del TLS ClientHello solo anuncia http/1.1, igual que la CLI. Negociar h2 interrumpe la ruta actual de WebSocket Upgrade mediante HTTP/1.1. Si la conexión se queda bloqueada con un proxy como Clash, compruebe que admite CONNECT y use una compilación con las correcciones #39/#42. Ni el WebSocket del dispositivo ni el de la CLI admiten SOCKS; utilice un proxy HTTP CONNECT (por ejemplo, HTTPS_PROXY=http://127.0.0.1:7890).

Tras la conexión directa o el CONNECT, el agente habilita TCP_NODELAY y TCP keepalive después de 30 segundos de inactividad. Cada latido JSON también envía un Ping WebSocket; al recibir un Ping se vacía explícitamente el Pong encolado automáticamente. La conexión y la espera de respuesta al registro tienen un límite de 45 segundos cada una; los envíos y el vaciado de Pong después del registro, de 30 segundos. Consulte Mantenimiento de la conexión y tiempos de espera (#42).

Las compilaciones de Windows con desktop-ui usan un runtime Tokio multihilo para ejecutar axum, la bandeja local y el WebSocket del dispositivo en paralelo, incluso en modo headless. En otras plataformas con esta función, la interfaz local (--ui explícito o inicio sin argumentos) también usa varios hilos. Las compilaciones sin la función y las ejecuciones headless con configuración explícita en otras plataformas usan current_thread, adecuado para dispositivos pequeños.

Interfaz de configuración Linux / Windows / macOS

Las compilaciones activan desktop-ui de forma predeterminada. Las versiones de escritorio deben incluir la interfaz de configuración local. Use --no-default-features solo para servidores sin interfaz o sistemas integrados. Durante la conexión se ocultan los campos del servidor de gestión, el ID y el token. Vuelven a mostrarse al detener el acceso remoto. El estado, los ajustes LAN y el inicio de sesión siguen visibles.

cargo build --locked -p agent --release
# Linux / macOS
./target/release/remote-agent --ui
# Windows
.\target\release\remote-agent.exe --ui

En Linux / Windows / macOS, si se inicia sin parámetros de conexión (por ejemplo, haciendo doble clic en remote-agent.exe) y con desktop-ui habilitado: si no existe una configuración válida, se escribe un config.json predeterminado (solo con Exchange predeterminado; device_id/token vacíos) y se abre la página de configuración en el navegador. Si ya está configurado, Windows sigue usando la bandeja (sin forzar la apertura del navegador), mientras Linux/macOS imprimen la URL local (macOS también tiene un icono en la barra de menús). Un inicio interactivo sin argumentos y sin desktop-ui crea la configuración predeterminada, imprime su ruta y sale, evitando mostrar únicamente Missing device_id. OpenWrt / systemd / tareas programadas deben seguir usando --config, UCI o REMOTE_*.

El inicio sin argumentos y --ui explícito siguen la misma regla: el navegador predeterminado solo se abre si falta un device_id/token no vacío en la configuración. Los agentes configurados se conectan en segundo plano sin abrir de nuevo el navegador al iniciar sesión. Windows conserva la entrada de la bandeja para abrir la página; Linux/macOS usan la URL local del terminal. --ui --no-open siempre evita la apertura automática, incluso en la primera configuración. --ui-port 8765 fija el puerto; en caso contrario se elige un puerto libre al azar. La interfaz solo escucha en 127.0.0.1. Todos los recursos están integrados, sin fuentes ni CDN externos.

La bandeja de Windows y la barra de menús de macOS ofrecen acceso a la configuración, los ajustes del salvapantallas, la gestión de tareas y las acciones del portapapeles. La gestión de tareas abre directamente el panel local. Linux usa la página de configuración del navegador.

Obtenga primero el ID y token del dispositivo con remote-cli device add <id>, y después rellene el servidor, ID y token en Connection settings:

  • Save only: guarda la configuración sin conectarse al servidor.
  • Save and connect: guarda y conecta; modificar la configuración durante la ejecución desconecta la conexión anterior y conecta con los nuevos valores.
  • Stop remote access: desconecta y cancela las ejecuciones remotas y sesiones de terminal actuales; conserva la configuración.
  • La barra de estado muestra Connected tras el registro correcto, o los estados de conexión, reconexión o detenido, junto con el error de conexión más reciente.

Al iniciar una conexión, la interfaz primero escribe Connecting en su propio archivo de estado y después inicia la tarea client::run, evitando que la página conserve el valor predeterminado correspondiente a un archivo de estado todavía inexistente (#42).

El token guardado no se devuelve a la página; dejarlo vacío conserva el token anterior. La configuración se sustituye atómicamente mediante un archivo temporal; los permisos son 0600 en Linux y la ACL de Windows solo autoriza al usuario actual. Si el JSON está dañado o no se puede leer, la interfaz pide repararlo sin sobrescribirlo. Solo un proceso de interfaz puede abrir un mismo archivo de configuración.

La ruta predeterminada es $XDG_CONFIG_HOME/remote-agent/config.json en Linux/macOS (~/.config/remote-agent/config.json si la variable no está definida), y %LOCALAPPDATA%\remote-agent\config.json en Windows. --ui --config <path> o REMOTE_CONFIG permiten indicar un JSON existente. La interfaz no acepta sustituciones con --server, --device-id, --token, --state-file ni sus variables de entorno, para evitar discrepancias entre los valores guardados y la conexión real. El JSON state_file también se aplica a la UI; si falta, usa la misma ruta persistente que headless para conservar la identidad sellada y la confianza.

La página incluye el interruptor Start at login / Inicio automático al iniciar sesión (autostart: { supported, enabled, detail } en GET /api/status; POST /api/autostart recibe { "enabled": true|false }). No requiere elevación de privilegios:

  • macOS: instala/elimina ~/Library/LaunchAgents/us.ivjn.remote-agent.plist (misma plantilla que install/macos/install.sh), con launchctl bootstrap / bootout; prioriza copiar el binario a ~/.local/bin/remote-agent y usa ~/.config/remote-agent/config.json por defecto (conservándolo si existe).
  • Windows: registra/elimina la tarea programada del usuario actual RemoteAgent (AtLogOn), con la misma lógica que install/windows/install.ps1; directorio de instalación %LOCALAPPDATA%\remote-agent.
  • Linux: instala/elimina la unidad systemd de usuario (install/linux/remote-agent.service), con systemctl --user enable --now / disable.

Al habilitarlo, copia el ejecutable actual a la ruta de instalación prevista cuando sea necesario y conserva el config.json existente; las respuestas y registros no imprimen tokens. La interfaz sigue administrando el proceso actual. Tras habilitar el inicio automático y salir de la interfaz, LaunchAgent / la tarea programada / systemd iniciará el agente en segundo plano en el siguiente inicio de sesión.

Cerrar la pestaña no detiene el Agent; salir de su proceso corta la conexión. Un dispositivo configurado se conecta automáticamente al iniciar la interfaz; la primera configuración requiere Save and connect. La interfaz administra el proceso actual y permite activar o desactivar el inicio de sesión automático del Agent. Detenga el servicio existente antes de iniciar otra interfaz para el mismo dispositivo. Tras configurar y activar el arranque automático, puede cerrar la pestaña. Las operaciones usan los permisos de la cuenta que ejecuta el Agent.

La URL local contiene una clave de acceso aleatoria para esta instancia de la interfaz; no la comparta. La API también comprueba Host y Origin y rechaza llamadas desde páginas externas. El token del dispositivo se guarda sin cifrar en el archivo de configuración; el usuario local actual y los administradores pueden leerlo. Las compilaciones reducidas sin desktop-ui conservan el comportamiento de línea de comandos; OpenWrt y Android usan sus interfaces existentes.

FreeBSD

El sitio oficial ofrece remote-freebsd-x64.tar.gz y remote-freebsd-arm64.tar.gz (compilados de forma cruzada en Linux con cargo zigbuild, enlazando libc de FreeBSD 14). Tras descomprimir:

mkdir -p ~/.local/bin
cp remote-agent remote-cli ~/.local/bin/
sudo remote-agent install --system --device-id NAME --token TOKEN

El código actual admite instalación rc.d, métricas sysctl, contadores netstat -ibn, temperaturas/ventiladores de FreeBSD y envstat de NetBSD. Anuncia control X11 si existen DISPLAY, python3 y xdotool; las capturas también requieren Pillow. Multimedia y ajustes del sistema siguen sin estar disponibles. La actualización reconoce archivos BSD x64/arm64 cuando el manifiesto incluye un paquete verificado correspondiente. Se requieren paquetes nuevos y pruebas nativas en BSD.

NetBSD

El sitio oficial ofrece remote-netbsd-x64.tar.gz (amd64, compilado de forma cruzada en Linux con zig y libc de NetBSD 10; solo depende dinámicamente de libc / libpthread / libutil). Se usa igual que FreeBSD:

mkdir -p ~/.local/bin
cp remote-agent remote-cli ~/.local/bin/
sudo remote-agent install --system --device-id NAME --token TOKEN

Capacidades: conexión con Exchange, exec / PTY / transferencia de archivos; métricas mediante sysctl (kern.cp_time, hw.physmem64, vm.uvmexp2); el control gráfico de escritorio es una implementación provisional y no se anuncia. La plataforma se comunica como netbsd. La autoactualización y el reinicio rc.d funcionan como en FreeBSD (remote-netbsd-x64.tar.gz / remote-netbsd-arm64.tar.gz), siempre que el manifiesto incluya el archivo.

Paquetes oficiales para OpenWrt 25.12

El sitio oficial proporciona remote-agent empaquetado con el SDK oficial de OpenWrt 25.12.5 (apk-tools, versión de paquete 0.1.29-r1, con agent 0.1.29):

target Arquitectura del paquete Archivo
ipq40xx/generic arm_cortex-a7_neon-vfpv4 remote-agent-openwrt-ipq40xx-official.apk
x86/64 x86_64 remote-agent-openwrt-x86_64-official.apk
armsr/armv8 aarch64_generic remote-agent-openwrt-armsr-armv8-official.apk
mediatek/filogic aarch64_cortex-a53 remote-agent-openwrt-mediatek-filogic-official.apk
bcm27xx/bcm2711 aarch64_cortex-a72 remote-agent-openwrt-bcm27xx-bcm2711-official.apk
ramips/mt7621 mipsel_24kc remote-agent-openwrt-ramips-mt7621-official.apk
ath79/generic mips_24kc remote-agent-openwrt-ath79-official.apk

El paquete de interfaz LuCI es común: luci-app-remote-openwrt-official.apk. Compruebe la arquitectura con apk --print-arch y después ejecute apk add --allow-untrusted ./文件.apk. Los paquetes MIPS también dependen de libgcc1 (incluido por defecto en las imágenes oficiales).

Linux musl (Alpine) y arm64

  • remote-linux-arm64.tar.gz: aarch64, glibc 2.28+ (Debian 10+/Ubuntu 18.04+/RHEL 8+, sistemas Raspberry Pi de 64 bits, etc.); admite el registro en systemd mediante remote-agent install y la actualización automática.
  • remote-linux-musl-x64.tar.gz / remote-linux-musl-arm64.tar.gz: musl enlazado estáticamente para distribuciones sin glibc como Alpine. Alpine no trae systemd de forma predeterminada; use remote-agent install --no-start para escribir la configuración y después cree su propio servicio OpenRC (/etc/init.d/remote-agent) o ejecútelo en primer plano. Las compilaciones musl admiten update self y la actualización automática de escritorio, y solo instalan el archivo musl; tras una actualización puntual prueban systemd, luego OpenRC (rc-service remote-agent restart) y luego un reinicio del proceso. Un archivo musl en un host OpenWrt se sigue actualizando con opkg/apk.

Linux

Se recomienda el subcomando integrado (copia el ejecutable actual, escribe una configuración privada y registra systemd; no depende de los archivos install/ del repositorio):

cargo build --locked -p agent --no-default-features --profile release-small
# 用户级(默认,非 root)
./target/release-small/remote-agent install \
  --config /path/to/config.json
# 或直接传凭证(无需先有文件;安装过程不访问网络)
./target/release-small/remote-agent install \
  --device-id NAME --token TOKEN

# 系统级(root 或 --system):/usr/local/bin + /etc/remote-agent + multi-user.target
sudo ./target/release-small/remote-agent install --system \
  --device-id NAME --token TOKEN

remote-agent status                    # 安装布局、服务、进程、连接(不打印令牌)
systemctl --user status remote-agent   # 用户级
# systemctl status remote-agent        # 系统级
remote-agent uninstall                 # 卸服务;加 --force 同时删二进制与配置

Pasos manuales equivalentes (todavía disponibles):

install -Dm755 target/release-small/remote-agent ~/.local/bin/remote-agent
install -Dm600 /path/to/config.json ~/.config/remote-agent/config.json
install -Dm644 install/linux/remote-agent.service ~/.config/systemd/user/remote-agent.service
systemctl --user daemon-reload
systemctl --user enable --now remote-agent

Por defecto, el servicio de usuario se ejecuta durante la sesión iniciada. Para mantenerlo activo desde el arranque, un administrador debe ejecutar loginctl enable-linger <user>, o usar la unidad de sistema --system anterior (RuntimeDirectory=remote-agent, WantedBy=multi-user.target). Los comandos remotos usan los permisos de la cuenta del servicio. Detener el servicio termina todos sus procesos. install/linux/install.sh es un envoltorio mínimo de remote-agent install --force.

Windows

Después de instalar la cadena de herramientas Rust MSVC y Visual Studio C++ Build Tools:

cargo build --locked -p agent --release
.\target\release\remote-agent.exe --config C:\path\config.json

# 推荐:为当前登录用户安装常驻计划任务
.\target\release\remote-agent.exe install --config C:\path\config.json
# 已存在任务/文件时加 --force;仅注册不启动加 --no-start
Get-ScheduledTaskInfo -TaskName RemoteAgent
remote-agent status
remote-agent uninstall
remote-agent uninstall --force   # 同时删除 %LOCALAPPDATA%\remote-agent 下的文件

install copia el binario y la configuración a %LOCALAPPDATA%\remote-agent, restringe la ACL de la configuración y registra la tarea programada RemoteAgent para iniciarse al iniciar sesión. Por defecto falla si ya existe la tarea o el archivo de destino; --force detiene y elimina la tarea anterior antes de sobrescribir. Este modo requiere que el usuario inicie sesión; no es un servicio del sistema Windows SCM. install/windows/install.ps1 es un envoltorio mínimo de remote-agent install (admite -Force).

Si genera primero la configuración con la interfaz, --config puede apuntar a una ruta independiente (por ejemplo, el archivo generado con --ui --config .\device-config.json).

Los comandos de Windows usan sintaxis PowerShell, como Get-Process, y envían la salida en UTF-8. Cada sesión de ejecución y terminal tiene un Job Object independiente; al agotarse el tiempo, cancelar, desconectar o salir el agente, se elimina su árbol de procesos hijos.

macOS

Se necesita la cadena de herramientas Rust (rustup, con el target predeterminado aarch64-apple-darwin o x86_64-apple-darwin). Las métricas se recopilan mediante sysctl, getloadavg(3) y mach host_statistics; la información estática (modelo, kernel y versión del sistema) mediante sysctl y sw_vers. La plataforma se comunica como macos.

Los sensores bajo demanda (fuera del latido) se recopilan en la medida de lo posible sin sudo ni dependencias SMC adicionales: batería mediante pmset -g batt (portátiles), CPU_Speed_Limit / Scheduler / Available CPUs y texto de presión térmica mediante pmset -g therm, y AppleSmartBattery Temperature y RPM de ventiladores mediante ioreg (si el firmware los expone). Los equipos de escritorio, como Mac mini, normalmente no tienen batería; Apple Silicon también puede carecer de CPU_Speed_Limit numérico. La temperatura directa del chip CPU / proximity requiere IOKit/SMC privado y no se recopila actualmente. Si hay una lectura de presión térmica, limitación o ventilador, thermal no se marca como unavailable. Con desktop-ui, remote-agent --ui permite configurar y activar/desactivar el inicio automático mediante LaunchAgent.

cargo build --locked -p agent --release

# 推荐:安装为随登录会话运行的 LaunchAgent(无需 sudo)
./target/release/remote-agent install --config ~/.config/remote-agent/config.json
# 或:install/macos/install.sh ~/.config/remote-agent/config.json
remote-agent status
launchctl print gui/$(id -u)/us.ivjn.remote-agent | grep state

remote-agent install copia el ejecutable actual a ~/.local/bin/remote-agent, escribe ~/.config/remote-agent/config.json (permisos 600), genera ~/Library/LaunchAgents/us.ivjn.remote-agent.plist, ejecuta codesign -s - --force sobre el binario (macOS termina los binarios sustituidos in situ sin firma) y después launchctl bootstrap / kickstart. RunAtLoad + KeepAlive inicia el agente al iniciar sesión y permite que launchd lo reinicie tras fallos o desconexiones. Los registros se escriben en ~/Library/Logs/remote-agent.log. install/macos/install.sh es un envoltorio mínimo. Sin archivo de estado explícito se usa $HOME/.local/state/remote-agent/agent.state. Los comandos remotos se ejecutan con los permisos del usuario de esa sesión.

Detener/desinstalar:

remote-agent uninstall
# 或手动:
# launchctl bootout gui/$(id -u)/us.ivjn.remote-agent
# rm ~/Library/LaunchAgents/us.ivjn.remote-agent.plist

Para ejecutarlo desde el arranque sin que nadie haya iniciado sesión, use un LaunchDaemon en /Library/LaunchDaemons cargado como root. En ese modo los comandos remotos se ejecutan con privilegios root; evalúe el riesgo correspondiente.

APK independiente para Android

El proyecto del dispositivo está en android-agent/. mobile/ sigue siendo el cliente de operaciones; ambos pueden instalarse por separado y no requieren Expo Go, Termux ni root.

La dirección predeterminada del agente en todas las plataformas es wss://exchange.ivjn.us. En Android, Connect with a six-digit code obtiene automáticamente la identidad del dispositivo después de que un administrador autorice el código de seis dígitos desde la CLI. Los agentes nativos (página local Windows/macOS, «Añadir dispositivo» de OpenWrt LuCI, comandos remote-agent pair / remote-agent join <邀请>) pueden mostrar un código de seis dígitos y un QR para que Console lo escanee y apruebe, o aceptar una invitación de un solo uso. Consulte Inicialización y emparejamiento.

Primera conexión e inicio

  1. Abra Remote Agent en el dispositivo Android que desea administrar. Para la configuración inicial, elija Conectar con un código de seis dígitos o Escanear código QR de conexión y obtenga la autorización de un administrador. Consulte Inicialización y emparejamiento para las cuentas, códigos QR y códigos de seis dígitos.
  2. Si ya dispone de un ID y token de dispositivo, abra Ajustes → Ajustes de conexión, introduzca el servidor y las credenciales y elija Guardar y conectar. Los campos se ocultan mientras la conexión está habilitada. Desconecte desde el inicio antes de cambiar la identidad del dispositivo.
  3. El inicio muestra el estado de conexión, el uso compartido de pantalla y los administradores. Si la configuración está guardada pero la conexión deshabilitada, elija Conectar dispositivo. Al reabrir la aplicación se restaura una conexión habilitada.

Android 12 y posteriores utilizan la pantalla de inicio del sistema; Android 8–11 usan un fondo de lanzamiento con los mismos colores de marca. El inicio se abre cuando está listo el primer fotograma, sin esperar la conexión de red ni añadir una duración mínima. Se omiten las transiciones si las animaciones del sistema están desactivadas. Los ajustes se cargan cuando se necesitan; abrir el inicio no concede permisos de accesibilidad ni de pantalla compartida. Esto describe el código actual; compruebe la versión instalada en las descargas públicas.

Segundo plano, notificaciones y detención

Pasar a segundo plano o cerrar la interfaz no detiene el servicio en primer plano. El Agent reconecta tras perder la red. Con la conexión habilitada, el reinicio y desbloqueo del dispositivo, o una actualización de la aplicación, provocan un intento de restaurar el servicio. Tras forzar la detención, abra la aplicación manualmente. Las políticas de batería del fabricante también pueden exigir permiso para funcionar en segundo plano.

Si se permiten notificaciones, Administrar conexión vuelve a la aplicación. Durante una sesión compartida, Dejar de compartir en la notificación termina solo esa sesión. Para desconectar el acceso remoto y deshabilitar la restauración automática, elija Desconectar en el inicio y confirme. La configuración se conserva para volver a conectar mediante Conectar dispositivo. Dejar de compartir la pantalla en el inicio también termina solo la sesión compartida, sin desconectar el acceso remoto.

El token del dispositivo se cifra con Android Keystore AES-GCM. La configuración se excluye de las copias de seguridad y las Activities o Services exportadas no transmiten tokens. Los comandos remotos se ejecutan con el UID de la aplicación APK y están limitados por el entorno aislado de Android y SELinux. Esto no concede datos de otras aplicaciones ni privilegios root; los ajustes del sistema, la accesibilidad y la pantalla compartida requieren sus propias autorizaciones.

Mientras el acceso remoto está habilitado, el servicio mantiene bloqueos de activación de CPU y pantalla. La pantalla permanece encendida en segundo plano y durante las reconexiones, con mayor consumo. Al desconectar o destruir el servicio se liberan los bloqueos y se restaura el apagado automático normal. El botón de encendido sigue bloqueando el dispositivo sin omitir la autenticación. Las notificaciones muestran el estado de conexión o de pantalla compartida; la desconexión está disponible en el inicio.

Compilar desde el código fuente

Inicie cada ejemplo Debug o Release siguiente desde la raíz del repositorio. Revise la lista de dispositivos antes de instalar y sustituya DEVICE_SERIAL por el número de serie de destino. La creación del APK, las pruebas unitarias y Lint completo son comprobaciones separadas. Si Lint falla, revise el informe y corrija o documente la causa; omitir la tarea no equivale a superarla.

La compilación requiere JDK 17+, Android SDK Platform 36, NDK 28.2.13676358, Python 3 y Rust. Defina ANDROID_HOME (o ANDROID_SDK_ROOT); para una ruta NDK personalizada use ANDROID_NDK_HOME. La visualización a pantalla completa de .Aran depende del SDK Android de Metafile: coloque el repositorio Metafile junto a remote-control (Gradle referencia el módulo :aran mediante ../../Metafile/android-sdk/aran).

rustup target add aarch64-linux-android x86_64-linux-android
cd android-agent
./gradlew :app:assembleDebug :app:testDebugUnitTest
# Windows: .\gradlew.bat :app:assembleDebug :app:testDebugUnitTest
./gradlew :app:lintDebug
# Windows: .\gradlew.bat :app:lintDebug
adb devices -l
adb -s DEVICE_SERIAL install -r app/build/outputs/apk/debug/remote-agent-debug.apk

Gradle invoca automáticamente scripts/build-android-agent.py para incorporar los ejecutables Rust de ambas ABI al directorio de bibliotecas nativas del APK. Tras instalarse, se ejecutan directamente desde ese directorio de solo lectura, respetando la prohibición de Android de ejecutar archivos desde el directorio de datos de la aplicación. El APK debug sirve para validación de desarrollo; para distribuirlo, compile release con la configuración de firma de su organización y conserve la clave para futuras actualizaciones.

La compilación Release lee android-agent/keystore.properties (ignorado por Git) o una configuración externa indicada por REMOTE_AGENT_SIGNING_PROPERTIES. Los campos están en android-agent/keystore.properties.example; guarde las claves fuera del repositorio. Sin configuración de firma, Release falla y no recurre a la firma Debug.

cd android-agent
$env:REMOTE_AGENT_SIGNING_PROPERTIES = "$env:USERPROFILE/.remote-control/signing/remote-agent.properties"
.\gradlew.bat :app:assembleRelease :app:lintRelease :app:testReleaseUnitTest --no-daemon
# Output: app/build/outputs/apk/release/remote-agent-release.apk

Guarde copias seguras de la clave de publicación y su contraseña; las versiones posteriores deben usar la misma clave para actualizar la instalación. Debug y Release tienen firmas distintas y una instalación Debug no puede sobrescribirse directamente; desinstalar elimina la configuración local y las credenciales, por lo que debe preparar un nuevo emparejamiento. scripts/build-site-downloads.py usa el APK Release firmado como descarga del agente. La firma Release no garantiza evitar el bloqueo de Play Protect.

Conexión mediante QR en Android

Scan connection QR code en la página principal de Remote Agent usa la cámara para reconocer el QR sin conexión y sin Google Play Services. Permita la cámara la primera vez; cancelar no cambia la configuración. Tras escanear, se muestran el servidor y el ID, con el token oculto. Solo al pulsar Connect se guarda cifrada la configuración y se inicia la conexión; si ya hay una conexión activa, deténgala antes de importar otra configuración. Escanear no habilita automáticamente el control de pantalla ni la accesibilidad.

Registre primero un dispositivo independiente en Exchange y obtenga su token de dispositivo; después genere el QR localmente en el ordenador (no use tokens de cuentas de operaciones ni servicios QR externos):

python -m pip install qrcode==8.2
python scripts/connection-qr.py --server wss://exchange.example.com --device-id android-phone --output "$env:TEMP\android-phone-connection.svg"
# 按提示输入设备令牌,输入不会显示。也可用 --token-stdin 从标准输入读取。

Abra el SVG generado en el ordenador y escanéelo con Remote Agent. El archivo QR contiene el token: manténgalo privado, elimínelo tras usarlo y no lo añada a Git. La herramienta rechaza sobrescribir archivos existentes.

El QR contiene JSON UTF-8, con una longitud total máxima de 2048 bytes:

{"type":"remote-agent","version":1,"server":"wss://exchange.example.com","device_id":"android-phone","token":"DEVICE_TOKEN"}

El escaneo solo acepta direcciones wss://, opcionalmente con la ruta /ws/device, sin información de usuario, parámetros de consulta ni fragmentos. server, device_id y token deben ser cadenas no vacías de hasta 512, 128 y 512 caracteres respectivamente, sin caracteres de control ni espacios al principio o al final. La configuración manual sigue admitiendo entornos de prueba locales ws://.

Accesibilidad, capturas y entrada de texto en Android

Utilice remote-cli para los siguientes comandos. El dispositivo anuncia android_control mediante el puente interno del APK; consulte status para comprobar los permisos.

  1. Conecte el dispositivo, abra Ajustes → Pantalla e interacción y habilite Permitir controles remotos de pantalla (desactivado por defecto).
  2. Elija Ajustes de accesibilidad y habilite Remote Agent en los ajustes de Android. Permite leer árboles de interfaz accesibles, tocar, deslizar, navegar por el sistema y sustituir el texto del campo activo.
  3. Android 11+ puede capturar mediante la accesibilidad habilitada. En Android 8–10, o para captura de pantalla únicamente, elija Compartir pantalla en el inicio o Uso compartido de pantalla estándar en los ajustes y confirme el diálogo del sistema. La autorización debe renovarse al terminar la sesión compartida o reiniciar el proceso.
remote-cli ui my-device status
remote-cli ui my-device tree
remote-cli ui my-device xml > screen.xml
remote-cli ui my-device click --view-id com.example:id/search
remote-cli ui my-device click --text "Search" --description "Open search"
remote-cli ui my-device screenshot --output screen.jpg
remote-cli ui my-device tap 300 500
remote-cli ui my-device swipe 500 1500 500 500 --duration-ms 400
# 多段滑动(折线,2-64 个点)
remote-cli ui my-device swipe-path 300,1500 300,800 900,800 --duration-ms 800
remote-cli ui my-device key back
remote-cli ui my-device text "Hello"
remote-cli ui my-device text --stdin < message.txt
remote-cli ui my-device type --stdin < message.txt

text usa ACTION_SET_TEXT de Android para sustituir todo el texto del campo actualmente enfocado. Admite chino, emojis y varias líneas, hasta 8192 bytes UTF-8, sin NUL. Toque primero un campo editable; los controles sin foco, no editables o incompatibles devuelven un error. --stdin lee UTF-8 conservando saltos de línea y evita guardar el texto en el historial de comandos. key admite back, home, recents, notifications y quick-settings.

xml devuelve un árbol de interfaz anidado <hierarchy><node ...> con resource-id, text, content-desc, bounds y estado de los controles; hasta 200 nodos, 50 niveles y 256 caracteres por atributo. Tanto la respuesta como el elemento raíz indican si está truncado. click realiza coincidencia exacta del valor completo de estos atributos (no XPath): --view-id corresponde a resource-id y --description a content-desc; varias condiciones se combinan con AND. Solo coinciden controles visibles y habilitados; cero o varias coincidencias producen error, y tampoco se toca si se supera el límite de recorrido. Para etiquetas de texto se busca un padre pulsable; si no se puede pulsar, use tap X Y. Vuelva a obtener el XML cuando cambie la interfaz y use el ID de recurso para atributos largos.

Para usar el método de entrada, abra Ajustes → Remote Input. Elija Habilitar Remote Input para habilitar Remote Input en Android y después Seleccionar método de entrada para seleccionarlo. Tras enfocar el campo de la aplicación de destino, type inserta texto en el cursor/selección mediante InputConnection.commitText, con chino, emojis y varias líneas, y el mismo límite de 8192 bytes UTF-8 que text. No pulsa enviar ni envía formularios automáticamente y no puede modificar la configuración del agente. Switch keyboard restaura el teclado habitual. status devuelve ime_enabled, ime_selected e ime_connected. Para leer o pulsar la interfaz e introducir texto, la pantalla debe estar encendida y desbloqueada localmente.

La página Pantalla del dispositivo de Remote Console permite tocar por coordenadas, obtener XML, pulsar por atributos e introducir texto con Remote Input. Los botones se deshabilitan si faltan permisos o el estado de pantalla no lo permite. El texto se vacía al introducirlo correctamente; al pasar a segundo plano, la página borra capturas, XML y texto pendiente. Actualice Exchange, el APK del agente y el cliente conjuntamente; las nuevas operaciones no requieren migraciones de base de datos.

Capacidades del sistema

Los ajustes usan los proveedores anunciados por el dispositivo; los espacios de nombres son cadenas, no enumeraciones fijas. Android conserva android_settings, con proveedores system, secure y global. Linux (sin OpenWrt) y macOS usan system_settings: gsettings del usuario actual en Linux y /usr/bin/defaults del usuario actual en macOS. La extensión de navegador usa browser_settings, leyendo y escribiendo privacy.network, privacy.services y privacy.websites mediante una sesión cifrada. Windows y OpenWrt no ofrecen estos ajustes. Para añadir un tipo de ajustes clave-valor, registre el proveedor en el agente o extensión; la interfaz de operaciones se genera a partir de la lista.

Los tres espacios de nombres Android son independientes del interruptor de pantalla, la presencia de pantalla y el bloqueo; usan ContentResolver en lugar de concatenar shell. Para escribir en system desde un APK normal, pulse Permitir modificar ajustes del sistema en el agente y autorícelo; escribir en secure/global exige además el permiso del sistema WRITE_SECURE_SETTINGS. El estado devuelve los permisos actuales y providers; Android y las políticas del fabricante pueden rechazar lecturas o escrituras concretas.

settings list --prefix filtra nombres de ajustes en Android. En Linux / macOS, sin prefix solo devuelve nombres de schema o domain; con prefix devuelve las cadenas, enteros y booleanos de primer nivel de ese schema o domain. Actualmente solo los ajustes de privacidad del navegador aceptan settings clear; otros proveedores devuelven un error explícito. La auditoría registra el espacio de nombres, el nombre del ajuste y el número de caracteres, sin valores.

remote-cli ui realmev5 settings
remote-cli ui realmev5 settings list --namespace system --limit 100
remote-cli ui realmev5 settings get screen_off_timeout
remote-cli ui realmev5 settings set screen_off_timeout 600000
remote-cli ui realmev5 screen-timeout 600
remote-cli ui realmev5 settings get device_name --namespace global
remote-cli ui realmev5 settings set vendor.custom_setting --stdin

El valor de settings set es una cadena y --stdin conserva los saltos de línea; los valores no se guardan en la auditoría. Máximo 8192 bytes UTF-8 sin NUL; los nombres admiten hasta 256 caracteres ASCII (letras, números, guion bajo, punto, dos puntos y guion). La lista está limitada a 500 elementos y 512 KiB, y devuelve truncated si los supera; un valor individual admite hasta 64 KiB. Un ajuste inexistente devuelve value: null. La interfaz no fuerza silenciosamente ajustes restringidos: relee cada escritura para confirmarla y devuelve un error explícito si falla. Los cambios son persistentes; desconectar el agente no restaura los valores anteriores. El atajo screen-timeout usa segundos (15–86400), mientras screen_off_timeout usa milisegundos; el apagado automático y el retraso de bloqueo posterior son distintos.

Reproducción multimedia (media)

La voz, el audio, las imágenes (incluido texto a pantalla completa), el vídeo y las páginas web usan la capacidad media, anunciada por agentes Android y Windows/Linux/macOS, pero no OpenWrt. El protocolo tiene tres acciones: media_play (con un objeto media), media_stop y media_status; todas devuelven el mismo estado media.

media.type Campos Canal
speech text, language opcional (BCP-47, como zh-CN) Sonido
audio Uno de url o path, loop opcional Sonido
image Uno de url, path o text, p2p opcional (no con text) Pantalla
video Uno de url o path, p2p opcional Pantalla
web url (solo https) Pantalla

El dispositivo tiene dos canales independientes: sonido (voz, audio) y pantalla (imágenes, texto, vídeo y web). El contenido nuevo solo sustituye lo que se reproduce en su mismo canal, de modo que la música de fondo y una presentación de imágenes pueden coexistir. channel de media_stop admite sound, screen o all (predeterminado).

remote-cli ui realmev5 media speak "Remote 连接正常" --language zh-CN
remote-cli ui realmev5 media speak --stdin --language zh-CN
remote-cli ui realmev5 media audio --path /sdcard/Music/doorbell.mp3
remote-cli ui realmev5 media audio --url https://cdn.example.com/notice.mp3 --loop
remote-cli ui realmev5 media image --text "值班中"
remote-cli ui realmev5 media image --url https://example.com/poster.jpg
remote-cli ui realmev5 media video --path /sdcard/loop.mp4
remote-cli ui realmev5 media video --url https://files.example.org/library/clip.Aran --p2p-group library --p2p-discovery https://p2p.ivjn.us --p2p-token secret --p2p-peer 192.0.2.10:7300
remote-cli ui realmev5 media web https://status.example.com/board
remote-cli ui realmev5 media status
remote-cli ui realmev5 media stop sound      # 或 screen;不写则两个通道都停
remote-cli ui laptop media audio --path 'C:\Users\me\Music\alert.wav'
remote-cli ui laptop media image --text "Kiosk"

media status devuelve:

{"kind":"media",
 "sound":{"type":"audio","id":"…","state":"playing","language":null,"loop":true},
 "screen":{"type":"web","keep_awake":true},
 "speech_available":true,"media_volume":7,"media_volume_max":15}

sound contiene el último elemento del canal de sonido (se conserva al terminar para consultar el resultado), con estado queued / speaking / loading / playing / completed / stopped / error; es null si nunca se reprodujo nada. screen solo aparece mientras se muestra contenido; de lo contrario es null. speech_available=false indica que no hay motor de voz disponible. El volumen multimedia solo tiene valor en Android; en escritorio es 0.

Validación y auditoría: el texto (voz o texto de imagen) admite hasta 4000 bytes UTF-8 sin NUL; la URL debe ser https, de hasta 2048 bytes y sin espacios ni caracteres de control; las rutas admiten hasta 4096 bytes sin caracteres de control. La auditoría solo registra tipo, clase de origen (url / path / text), número de caracteres, bucle y uso de P2P; nunca guarda URL, rutas ni contenido (las URL pueden incluir tokens firmados). Las cuentas readonly no pueden ejecutar operaciones multimedia.

Sonido: voz y audio

  • Voz: Android usa el motor TTS del sistema; Windows, System.Speech; macOS, say; Linux, spd-say / espeak-ng / espeak (sin ninguno instalado, speech_available=false y la voz devuelve un error explícito). En escritorio se intenta asociar la etiqueta de idioma con una voz local. Android solo responde cuando el motor empieza a hablar; devuelve error si falla la inicialización o no hay datos de idioma; detener el agente libera TTS.
  • Audio: el origen es una ruta absoluta del dispositivo o una URL https. Transfiera primero los archivos ausentes con remote-cli files upload y reprodúzcalos por ruta. loop repite hasta detenerlo.
    • Android usa MediaPlayer con el volumen multimedia, reproduce las URL directamente en streaming y solo responde al comenzar el sonido; si no empieza en 10 segundos, devuelve un error.
    • En escritorio, primero descarga la URL al directorio temporal de estado del usuario actual (máximo 50 MB, redirecciones solo https) y la elimina al finalizar, detenerse o fallar. La interfaz responde al iniciar el proceso de reproducción; los fallos de descarga o decodificación se reflejan en sound.state=error de media status. Windows usa MediaPlayer (mp3, wav, m4a, aac, wma, flac); macOS usa afplay; Linux busca en orden mpv, ffplay, paplay, pw-play, aplay, y devuelve error si no encuentra ninguno (paplay / aplay no admiten mp3).
    • La ruta debe ser un archivo normal existente y legible; Windows rechaza rutas de red como \\server\share para no enviar credenciales a servidores SMB remotos.
  • Si el volumen del dispositivo es cero, puede que no se oiga nada.

Pantalla: imágenes, texto, vídeo y web

  • image: path local, URL https o text (texto UTF-8 grande sobre fondo oscuro, tratado como imagen estática). En Android, los orígenes con extensión .Aran se descifran mediante el SDK Android de Metafile antes de mostrarse.
  • video: path local o URL https, reproducido en bucle. En Android, .Aran no es un flujo HTTPS convencional: sin caché, se descifra por bloques durante la descarga y reproducción, y se guarda el texto claro en una caché local para la próxima reproducción. Los bloques HTTP se obtienen de /meta/<SHA1> en la raíz del mismo origen; los archivos locales usan el directorio meta/ junto al manifiesto. Se usa la clave integrada en el manifiesto o la predeterminada del SDK. P2P opcional: el orden de fuentes de bloques es meta/ local, UDP P2P (LOOKUP_ONLY, sin registrarse en metastore), y HTTP como alternativa. media.p2p proporciona descubrimiento y peers estáticos, por ejemplo --p2p-group library --p2p-discovery https://p2p.ivjn.us; si P2P no arranca, continúa con HTTP/local sin interrumpir la reproducción.
  • web: abre una página https a pantalla completa. Android usa un WebView en una Activity de pantalla completa (JavaScript y DOM storage habilitados; la navegación solo sigue https; si falla el proceso de renderizado, se cierra la visualización sin afectar al agente). En escritorio se inicia un navegador independiente en modo kiosk con perfil temporal, que termina al detener la visualización: Windows busca Edge, Chrome y Firefox; macOS, Chrome, Edge, Chromium y Firefox (Safari no tiene kiosk); Linux, Chromium, Chrome, Edge y Firefox. Si no hay navegador, devuelve un error explícito.

Para manifiestos y bloques .Aran de https://cdn.ivjn.cn y https://cdn.ivjn.us, Android usa automáticamente el otro dominio como origen alternativo, conservando la ruta del manifiesto y leyendo bloques de /meta/<SHA1> en cada origen. No se aplica a otros dominios. Ante errores temporales (incluido HTTP 554), tiempos de espera o fallos de verificación de bloques, intenta primero el origen alternativo y después espera 250/500 ms, hasta 3 rondas; tras un éxito, prioriza ese origen. Cada conexión tiene 5 segundos de tiempo límite y cada lectura 30 segundos. Errores permanentes como 403/404 no repiten el mismo origen en esa lectura. Se siguen verificando tamaño y SHA-1 de bloques, sin redirecciones ni relajación de HTTPS. Esto requiere el SDK Android de Metafile ab2b92f o posterior (con MirroredHttpSource).

El canal de pantalla muestra un único elemento; el nuevo sustituye al anterior. Durante la visualización mantiene la pantalla encendida (Android: FLAG_KEEP_SCREEN_ON + turnScreenOn / showWhenLocked; Windows: SetThreadExecutionState; macOS: caffeinate -d; Linux: systemd-inhibit o xdg-screensaver inhibit). Imágenes estáticas y texto se desplazan lentamente unos píxeles para evitar quemaduras; los vídeos solo se repiten y las webs no se desplazan. No hay un borde resaltado fijo. Android usa una Activity independiente a pantalla completa (sin accesibilidad). Linux requiere una sesión X11 desbloqueada y mpv o ffplay para vídeo; macOS prioriza mpv y, si no está, open.

Control: posición de reproducción y web

media_control controla el contenido por canal y devuelve el mismo estado media. channel debe ser sound o screen; op es uno de los siguientes:

op.type Parámetros Aplicación
pause / resume — Audio (sound), vídeo (screen)
seek position_ms (posición absoluta, hasta 7 días) Audio, vídeo
navigate url (https) Web
reload / back / forward — Web
scroll dx, dy (píxeles, no cero, hasta 100000 cada uno) Web
eval script (hasta 16 KiB) Web, devuelve script_result (resultado codificado en JSON, hasta 64 KiB)
remote-cli ui realmev5 media pause sound
remote-cli ui realmev5 media seek 1:30 screen      # 也接受 90、1:02:03.5
remote-cli ui realmev5 media resume screen
remote-cli ui realmev5 media web go https://status.example.com/next
remote-cli ui realmev5 media web back
remote-cli ui realmev5 media web scroll 0 800
remote-cli ui realmev5 media web eval "document.title"

El estado añade position_ms, duration_ms y paused a sound / screen (audio y vídeo, si el reproductor puede comunicarlos), y url y title a screen para web. Voz, imágenes y texto no se pueden controlar; una operación incompatible con el contenido del canal devuelve un error explícito. La auditoría solo registra canal, tipo de operación y número de caracteres del script, sin URL ni contenido del script.

  • Android: controla audio y vídeo directamente mediante MediaPlayer / VideoView, y web mediante el WebView de la Activity de pantalla completa.
  • Windows: el audio se controla con el worker MediaPlayer integrado; el vídeo con mpv mediante tubería IPC; también se controla el reproductor alternativo WPF, pero ffplay no es compatible.
  • Linux / macOS: audio y vídeo solo son controlables con mpv (JSON IPC); de lo contrario devuelve «Install mpv to pause or seek desktop media». Si macOS tiene mpv instalado, también se usa para audio.
  • Web de escritorio: el kiosk de Chrome / Edge / Chromium se inicia con --remote-debugging-port=0 (solo 127.0.0.1, puerto aleatorio y perfil temporal) y se controla por el protocolo DevTools; Firefox solo admite navigate (reabre kiosk con la nueva dirección).

En los detalles del dispositivo, la página Capacidades del sistema de Remote Console móvil tiene pestañas «Ajustes del sistema» y «Multimedia». Esta última lee el estado de ambos canales, detiene sonido o pantalla por separado, reproduce voz, URL de audio, texto, imágenes, vídeos y webs, y controla posición de audio/vídeo y navegación web (ir a URL, avanzar/retroceder, recargar, desplazar y ejecutar JS). Al pasar a segundo plano se borran resultados de ajustes, valores pendientes, texto de voz y entradas de visualización. La consola web ofrece el mismo formulario de ajustes; los ajustes del navegador reutilizan la sesión cifrada establecida. La web no tiene una página adicional de reproducción multimedia.

Búsqueda de texto y XML en Android

remote-cli ui realmev5 xml
remote-cli ui realmev5 find-text "设置"
remote-cli ui realmev5 find-text "Settings" --exact --ignore-case

La búsqueda coincide con texto y descripción de nodos visibles; por defecto busca contenido parcial, con opciones de coincidencia exacta e ignorar mayúsculas. Devuelve bounds y center de cada coincidencia (píxeles originales de pantalla, pulsables con tap X Y), ID de recurso, texto y estado habilitado. El centro se calcula sobre la intersección con la pantalla para evitar coordenadas exteriores. Se recorren hasta 200 nodos y se indica el truncamiento. Se excluyen nodos de contraseña y el subárbol de configuración del propio agente. La accesibilidad Android puede no leer texto dibujado por la aplicación; esta interfaz no realiza OCR. Repita la búsqueda tras cambios de interfaz.

Las capturas son JPEG, de hasta 1600 píxeles en el lado mayor; la CLI no sobrescribe archivos existentes. Muestra dimensiones de la imagen y de la pantalla original; toques, deslizamientos y bounds de nodos usan píxeles originales. Para localizar desde una miniatura, use 屏幕坐标 = 图像坐标 × screen_width / width (en vertical, la proporción de alturas). Vuelva a leer las dimensiones tras rotar. El árbol admite hasta 200 nodos y cada cadena hasta 256 unidades UTF-16; truncated indica el límite de nodos. Las interfaces dibujadas por la aplicación pueden carecer de nodos accesibles.

No se devuelven texto ni descripción de contraseñas ni el subárbol de configuración de Remote Agent; la entrada remota no puede modificar su propia configuración. Las ventanas protegidas por FLAG_SECURE aparecen negras en capturas o producen un error del sistema; compartir pantalla en Android 15 también puede ocultar automáticamente aplicaciones con contraseñas u otros datos sensibles. El control reutiliza la autenticación de operaciones existente; las cuentas readonly no pueden iniciarlo. La auditoría solo registra acción, coordenadas y número de caracteres introducidos, sin texto, árbol ni capturas.

Stop screen sharing termina la sesión MediaProjection; si la accesibilidad sigue habilitada en Android 11+, sus capturas siguen disponibles. Desactivar Allow remote screen controls impide todas las operaciones posteriores y termina la sesión compartida; Stop remote access también desconecta el agente. Los toques o entradas ya ejecutados no se pueden deshacer. Los permisos de accesibilidad se administran por separado en Android; desactivar el interruptor o detener el servicio conserva la autorización del sistema, pero no acepta operaciones remotas de pantalla.

El puente local usa un socket Unix de nombre aleatorio y verifica que el llamante tenga el mismo UID que el APK. Esto protege el acceso frente a otras aplicaciones normales; el shell remoto autorizado comparte UID con el APK, por lo que el interruptor de pantalla no constituye una barrera de permisos frente a ese shell o al administrador del dispositivo.

Información y métricas del sistema

DeviceInfo.platform es linux, freebsd, netbsd, windows, android, macos u openwrt; os_version es la versión genérica del sistema y capabilities describe exec, pty, ubus y android_control. OpenWrt comunica openwrt_version.

Linux/OpenWrt recopila métricas de /proc; si SELinux limita Android, se conservan las demás métricas legibles, y el tiempo de actividad puede obtenerse con CLOCK_BOOTTIME. Windows usa API nativas para memoria, CPU, tiempo de actividad y contadores de red. Las métricas no disponibles se marcan en unavailable, y la CLI y el cliente móvil muestran los elementos ausentes. Windows no tiene carga media Unix, por lo que load siempre se marca como no disponible.

Validación

cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo test -p agent --features desktop-ui
cargo clippy -p agent --features desktop-ui --all-targets -- -D warnings
cd mobile && npm run typecheck && npm run lint && npm test

scripts/agent-smoke.py es un relay local de pruebas que usa websockets de Python para validar registro, métricas, ejecución, tiempos de espera, cancelación, PTY, resize y ubus no compatible de un agente real. No sustituye las pruebas de autenticación y base de datos de Exchange. Use las credenciales fijas de prueba según el script y no conecte dispositivos ni servicios de producción. Windows y Linux deben ejecutar sus pruebas nativas; en Android, instale además el APK y valide primer plano, segundo plano, detención y reconexión en el entorno real.

scripts/agent-ui-smoke.py --agent <binary> --platform linux|windows valida por separado la primera configuración de la interfaz, autenticación y restricciones de origen, guardado privado de archivos, conservación del token, registro y ejecución reales, limpieza al detener, reconexión y reapertura. Requiere Python 3.11+ y websockets; compile antes el agente con desktop-ui. La validación de navegador también debe cubrir botones de guardado, errores de entrada, visibilidad del token, recuperación tras recargar y diseño en pantalla estrecha.

scripts/android-control-smoke.py usa Exchange, CLI y una aplicación Android de pruebas independiente para validar operaciones de pantalla reales. Ejecútelo solo en un emulador dedicado y desechable: borra los datos de Remote Agent y cambia ajustes de accesibilidad. Compile e instale primero los APK :app:assembleDebug y :control-fixture:assembleDebug, prepare Exchange/base de datos independientes, registre el dispositivo de prueba y guarde su token y configuración CLI. Requiere Python 3.11+, Pillow y adb.

adb -s emulator-5580 install -r android-agent/app/build/outputs/apk/debug/remote-agent-debug.apk
adb -s emulator-5580 install -r -t android-agent/control-fixture/build/outputs/apk/debug/control-fixture-debug.apk
python scripts/android-control-smoke.py --serial emulator-5580 \
  --cli target/debug/remote-cli --cli-config /private/qa-cli.toml \
  --device-token /private/qa-device-token.txt \
  --device-id android-control-qa --device-server ws://10.0.2.2:18820/ws/device

En Windows, cambie la ruta de la CLI a target/debug/remote-cli.exe. Los resultados se guardan por defecto en target/android-control-qa/screenshots/. La fixture solo se usa para pruebas y no se incluye en el APK del dispositivo.

Registros, estado de pantalla y activación en Android

Remote Console añade Registros del dispositivo y Pantalla del dispositivo a los detalles. La página de pantalla muestra presencia, alimentación, bloqueo, dimensiones y permisos, y ofrece una vista previa JPEG bajo demanda y Activar pantalla. Las acciones se deshabilitan si el dispositivo carece de pantalla o de la interfaz de control Android; la falta de permiso de captura se muestra por separado. Una pantalla apagada puede activarse cuando los controles locales están habilitados. La activación nunca desbloquea la pantalla.

El agente Android incluye localmente Estado de pantalla y registros del dispositivo. Mientras el acceso remoto está habilitado, se ocultan campos de conexión y botones de registro QR y código de conexión. Desconectar requiere confirmación y conserva las credenciales guardadas. La autorización de accesibilidad y el servicio enlazado activo se comunican por separado; si está autorizado pero espera el enlace, desactive y reactive únicamente Remote Agent en los ajustes de Accesibilidad del sistema.

remote-cli ui realmev5 status
remote-cli ui realmev5 logs --limit 100
remote-cli ui realmev5 wake
remote-cli ui realmev5 screenshot --output screen.jpg

La interfaz WebSocket existente open_android acepta {action:"logs",limit:100} (1-300 entradas) y {action:"wake"}. El estado añade los campos opcionales display_present, screen_on, locked, screen_width, screen_height y accessibility_authorized; accessibility_enabled sigue indicando que el servicio está conectado. Las API nativas de pantalla Android siguen requiriendo el APK y los agentes de otras plataformas no anuncian esta interfaz.

Los registros proceden ahora de logcat -b all, sin filtrar PID, paquete ni etiqueta: búferes de sistema, aplicaciones, eventos y fallos disponibles en logd de Android. Cada captura devuelve hasta 100 entradas en la interfaz o 1-300 con la CLI, conservando fechas, niveles, nombres de búfer, PID/TID, etiquetas y mensajes originales. Es el historial actual de logcat del dispositivo, no un archivo de registros arbitrarios ni de entradas ya rotadas por Android.

Conceda al APK permiso para registros del sistema mediante una conexión ADB autorizada:

adb shell pm grant com.remote.agent android.permission.READ_LOGS

En versiones Android que exigen autorizar cada uso de registros, abra los diagnósticos del agente, acepte la confirmación del sistema y repita la solicitud remota. El acceso en segundo plano puede denegarse o la autorización caducar según las políticas Android/OEM. Tras una denegación, Android puede devolver silenciosamente solo el UID del llamante; por eso el recopilador examina los metadatos UID de las últimas 301 entradas antes de seleccionar la cola solicitada. Si no aparece otro UID (también con registros vacíos), indica que no pudo confirmar acceso a todo el dispositivo, en lugar de mostrar una captura limitada a la aplicación. La falta de permiso y los fallos de logcat también devuelven un error explícito; no se recurre al búfer circular de eventos del agente. La recopilación se ejecuta fuera del hilo de interfaz, se detiene al cancelar y tiene un límite de seis segundos y salida acotada; las capturas demasiado grandes indican truncamiento o error. Se reutilizan sesiones autenticadas de operadores, enrutamiento por tenant y restricciones de roles. Los registros pueden contener contenido escrito por otras aplicaciones. Las vistas web y móvil se vacían al salir o pasar a segundo plano, sin guardar capturas en disco. Remote Console también borra capturas de pantalla al salir o pasar a segundo plano.

Compartir pantalla completa con transparencia: En el agente Android, seleccione Compartir pantalla completa con superposición transparente. Conceda el permiso para mostrarse sobre otras aplicaciones y el consentimiento de compartir pantalla. El agente pasa a segundo plano e instala un TYPE_APPLICATION_OVERLAY a pantalla completa con opacidad cero y FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE: todos los toques llegan a la aplicación subyacente. MediaProjection captura toda la pantalla (Android 14+ solicita explícitamente la pantalla predeterminada). Detenga la sesión desde la página del agente o la notificación; dejar de compartir no desconecta el agente. La superposición se elimina al terminar la sesión o detener el servicio.

Las páginas de vista previa de pantalla y estado del agente conectado permiten capturas. Las páginas de configuración de conexión, tickets de acceso y QR de token siguen protegidas. Android puede devolver una imagen negra para una ventana segura de otra aplicación; la superposición transparente no evita su protección.

Antes de capturar, el agente Android comprueba tanto el estado de la pantalla como el estado interactivo de PowerManager. Solo activa una pantalla apagada, verifica que sea interactiva y rechaza la captura si sigue bloqueada. El lector MediaProjection consume continuamente los fotogramas y conserva una imagen compuesta actual, en lugar de recrear su superficie en cada captura. Ante fotogramas totalmente negros, espera uno utilizable y, si vence el tiempo, devuelve un error con indicaciones en lugar de un JPEG negro marcado como correcto.

Comparación de modos Agent

El modo de inicio y la plataforma son dimensiones distintas. Los modos nativos headless, UI local y servicio del sistema comparten el núcleo de conexión; desktop-ui solo añade configuración local y no concede permisos remotos.

Modo/plataforma Capacidades Condiciones y límites
headless / UI local Mismo protocolo nativo, comandos, terminal, archivos, métricas, notificaciones, LAN opcional UI solo en Linux/Windows/macOS; bandeja en Windows; las acciones de escritorio requieren una sesión gráfica de usuario disponible
Servicio del sistema / inicio de sesión Mismo Agent nativo Hereda la cuenta y el entorno del servicio; los parámetros de inicio no se copian a otro servicio
OpenWrt Núcleo nativo, ubus Anuncia ubus cuando está disponible; supervisión procd; no anuncia control de escritorio
Linux / Windows / macOS Núcleo nativo, control de escritorio, registros del sistema, multimedia Requiere permisos/herramientas del sistema; ajustes del sistema anunciados solo en Linux/macOS
FreeBSD / NetBSD Núcleo nativo, terminal, archivos, red, métricas, LAN opcional Batería no disponible; dependencias X11; sin multimedia/ajustes del sistema
Android APK Núcleo Rust + puente Java, pantalla, ajustes, multimedia, OTA Límites del UID de la aplicación y los permisos; el binario Rust independiente no tiene el puente de control APK
Extensión Chromium URL, cookies, páginas, ajustes del navegador, CDP, métricas Sin comandos del sistema/PTY/archivos arbitrarios del equipo/escucha LAN; CDP completo requiere permiso local

UI y headless comparten la resolución de configuración: CLI/entorno prevalecen sobre JSON para LAN, watchdog, TLS y notificaciones; las actualizaciones conservan CLI > JSON > REMOTE_AUTO_UPDATE > activadas por defecto. Se aceptan REMOTE_WATCHDOG=1, 0, true/false, yes/no y on/off. La UI sigue rechazando sobrescrituras de inicio para credenciales y rutas de estado. Las sobrescrituras de ejecución no se guardan en JSON; el inicio automático necesita configuración aparte. El estado LAN muestra los valores efectivos; guardar la página no elimina los parámetros de inicio.

UI y headless usan state_file de JSON o la misma ruta persistente predeterminada del sistema; cada instancia de dispositivo necesita una ruta distinta. Al conectar se actualiza connecting y al detener se muestra stopped. Detener cancela el future de actualización de esa conexión y libera el estado watchdog; los supervisores terminan en la siguiente comprobación. Las operaciones ya completadas no se pueden deshacer. Cada reconexión también recarga la identidad y la política de confianza; si falla la carga, se borra la identidad anterior en lugar de conservar la autorización del dispositivo anterior.

Todas las operaciones requieren sesiones selladas y autorización de la raíz del espacio de trabajo. El consentimiento por llamada/permiso desatendido de CDP completo no queda aislado por los ámbitos de cookies tipados ni permite eludir las políticas de Chrome. Cerrar la extensión o desconectarse requiere una nueva negociación. Los archivos de actualización corresponden a Windows x64, Linux glibc x64/arm64 y macOS arm64; otras plataformas nativas usan instaladores/gestores de paquetes, Android usa APK OTA y los navegadores paquetes de extensión. Anunciar una capacidad no garantiza hardware, permisos ni un archivo publicado.

Salvapantallas automático

Los ajustes del Agent local y de la extensión permiten iniciar por inactividad (300 segundos de forma predeterminada), al comenzar/finalizar el control o inmediatamente. Muestran texto, imágenes/vídeos HTTPS o HTML estático aislado. Los vídeos se repiten sin sonido y el texto y las imágenes pueden desplazarse ampliamente. Desktop y Android ofrecen contraseña de salida; la ventana de pantalla completa del navegador no usa contraseña y se cierra con una tecla, movimiento del ratón o cierre de ventana. Solo cubre la pantalla activa; no bloquea el sistema. Linux requiere X11 y xprintidle. OpenWrt sin escritorio, Wayland y BSD no admiten esta visualización actualmente.

Modo de color sólido sin texto ni medios: fondo #RRGGBB y brillo del 0 al 100% (negro al 0%). Android atenúa también la luz de la ventana y restaura el brillo del sistema al interactuar. Otras plataformas solo oscurecen la imagen; el ahorro LCD depende de la retroiluminación. El diálogo de salida permanece legible.

Desde 0.1.34, solo el texto y las imágenes recorren una zona amplia del fondo. El contenido grande se reduce cuando hace falta hasta un máximo del 44% del ancho y alto disponibles, para que las posiciones en esquinas opuestas no se superpongan. HTML, vídeo y color sólido permanecen fijos; la preferencia de movimiento reducido detiene la animación. Todos los modos ocultan el puntero desde el primer fotograma, lo muestran para la contraseña y lo ocultan al cancelar. Windows actualiza el puntero dentro de la ventana cada 100 ms sin cambiar el cursor del sistema. Disponible primero en Windows y Browser Agent 0.1.34; consulta el manifiesto para las demás plataformas.