feat(shell): auto-commit con 31 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: close_onlyoffice_instance
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: shell
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "close_onlyoffice_instance(instance: string = demo, [--purge]) -> json"
|
||||
description: "Termina el/los proceso(s) DesktopEditors de una INSTANCIA AISLADA (slot) de ONLYOFFICE Desktop Editors, identificados por su HOME=/tmp/oo_<instance> leido de /proc/<pid>/environ — asi NUNCA mata la instancia personal del usuario, solo la aislada. Envia SIGTERM, espera ~3s por evento (read -t, sin sleep foreground) y SIGKILL a los que sigan vivos. Con el flag --purge borra ademas los directorios del slot (/tmp/oo_<instance>*). Imprime JSON con instance, killed_pids (array), purged y status (closed|not_running)."
|
||||
tags: [onlyoffice, desktop, x11, shell]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: instance
|
||||
desc: "nombre del slot aislado a cerrar (default: demo). Solo se matan procesos DesktopEditors cuyo HOME sea /tmp/oo_<instance>"
|
||||
- name: --purge
|
||||
desc: "flag opcional: si se pasa, borra los directorios del slot (/tmp/oo_<instance>*) tras matar los procesos. Sin el flag, solo termina procesos y deja el estado del slot en disco"
|
||||
output: "una linea JSON a stdout: {\"instance\":\"<i>\",\"killed_pids\":[<pids>],\"purged\":true|false,\"status\":\"closed\"|\"not_running\"}. Exit 0 siempre que opere bien (closed si mato procesos, not_running si no habia ninguno del slot), exit 1 si falta dependencia, exit 2 si flag desconocido"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/shell/close_onlyoffice_instance.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Cerrar el slot demo (deja /tmp/oo_demo* en disco para reusar la config)
|
||||
bash bash/functions/shell/close_onlyoffice_instance.sh demo
|
||||
|
||||
# Cerrar y limpiar todo el estado del slot
|
||||
bash bash/functions/shell/close_onlyoffice_instance.sh demo --purge
|
||||
|
||||
# Slot por defecto (demo) sin argumentos
|
||||
bash bash/functions/shell/close_onlyoffice_instance.sh
|
||||
|
||||
# Via fn run
|
||||
./fn run close_onlyoffice_instance_bash_shell reporte --purge
|
||||
|
||||
# Sourceado
|
||||
source bash/functions/shell/close_onlyoffice_instance.sh
|
||||
out=$(close_onlyoffice_instance demo --purge)
|
||||
echo "$out"
|
||||
# {"instance":"demo","killed_pids":[12345,12350],"purged":true,"status":"closed"}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando terminas un flujo automatizado con ONLYOFFICE Desktop y quieres **cerrar la instancia aislada por completo** (cerrar la ventana con `wmctrl` deja el proceso vivo; esta funcion mata el proceso real).
|
||||
- Para **liberar recursos** de un slot que ya no usas, opcionalmente borrando su estado en /tmp con `--purge`.
|
||||
- Como ultimo paso del ciclo open -> reload -> close, garantizando que no quedan procesos huerfanos de la instancia aislada.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Solo mata la instancia aislada**: identifica procesos por `HOME=/tmp/oo_<instance>` en `/proc/<pid>/environ`. La instancia personal del usuario (HOME real) NUNCA se toca. Esto es por diseño y por seguridad.
|
||||
- **Cerrar la ventana NO mata el proceso**: por eso esta funcion existe. Tras `reload`/`wmctrl -ic` el proceso de la instancia aislada sigue vivo (deseable para reusar). Usa esta funcion para terminarlo de verdad.
|
||||
- **`--purge` borra /tmp/oo_<instance>***: pierdes la config del slot (perfil, recientes). El slot se recreara limpio en el siguiente `open`. Sin `--purge`, el estado persiste y el siguiente arranque reusa esa config.
|
||||
- **El slot vive en /tmp**: aunque no purgues, `/tmp/oo_<instance>*` se pierde al reiniciar el PC. Estado desechable.
|
||||
- **Requiere X11 + wmctrl + xdotool** instalados (coherencia con el grupo, aunque esta funcion solo usa /proc para matar). Comprueba `command -v` y falla claro si falta alguna; no funciona en Wayland puro sin XWayland para el resto del grupo.
|
||||
- **Carrera de /proc**: si un pid muere entre listarlo y leer su environ, se ignora silenciosamente (guardas `2>/dev/null || true`); no rompe la funcion (`set -uo pipefail` sin `-e`).
|
||||
- **SIGKILL como ultimo recurso**: tras ~3s de SIGTERM, los procesos vivos reciben SIGKILL. Cambios sin guardar en la app (si los hubiera) se pierden — pero el flujo previsto edita en disco, no en la app, asi que no deberia haber estado sin guardar.
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
# close_onlyoffice_instance — termina el/los proceso(s) DesktopEditors de una
|
||||
# INSTANCIA AISLADA (slot) de ONLYOFFICE Desktop Editors, identificados por su
|
||||
# HOME=/tmp/oo_<instance> en /proc/<pid>/environ. Opcionalmente limpia los
|
||||
# directorios del slot con --purge.
|
||||
#
|
||||
# Funcion impura: lee /proc, envia señales a procesos y (con --purge) borra
|
||||
# directorios bajo /tmp. NO toca la instancia personal del usuario: solo mata
|
||||
# procesos cuyo HOME apunta al slot aislado.
|
||||
#
|
||||
# Slot aislado: cada instance usa HOME=/tmp/oo_<instance>,
|
||||
# XDG_RUNTIME_DIR=/tmp/oo_<instance>_run, XDG_CONFIG_HOME=/tmp/oo_<instance>/.config.
|
||||
|
||||
# Sin -e: lecturas de /proc/<pid>/environ pueden fallar por carrera (el pid
|
||||
# muere entre listar y leer); no deben abortar la funcion.
|
||||
set -uo pipefail
|
||||
|
||||
close_onlyoffice_instance() {
|
||||
local instance="demo"
|
||||
local purge=false
|
||||
|
||||
# Parseo de args: [instance] y/o --purge en cualquier orden.
|
||||
local a
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--purge) purge=true ;;
|
||||
-*) echo "close_onlyoffice_instance: flag desconocido '$a'" >&2; return 2 ;;
|
||||
*) instance="$a" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 1. Dependencias del sistema (consistencia con el grupo, aunque aqui solo
|
||||
# se usa /proc; onlyoffice/wmctrl/xdotool deben existir para operar el slot).
|
||||
local dep
|
||||
for dep in onlyoffice-desktopeditors wmctrl xdotool; do
|
||||
if ! command -v "$dep" >/dev/null 2>&1; then
|
||||
echo "close_onlyoffice_instance: falta dependencia '$dep'" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
local oo_home="/tmp/oo_${instance}"
|
||||
|
||||
# 2. Encontrar pids de DesktopEditors con HOME=/tmp/oo_<instance>.
|
||||
local pids=() pid environ
|
||||
for pid in $(pgrep -f '/opt/onlyoffice/desktopeditors/DesktopEditors' 2>/dev/null || true); do
|
||||
# Leer el entorno del proceso; saltar si no se puede (carrera/permisos).
|
||||
environ=$(tr '\0' '\n' <"/proc/${pid}/environ" 2>/dev/null || true)
|
||||
[[ -z "$environ" ]] && continue
|
||||
if grep -qx "HOME=${oo_home}" <<<"$environ" 2>/dev/null; then
|
||||
pids+=("$pid")
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. Si no hay procesos del slot: not_running (purge opcional igualmente).
|
||||
if [[ ${#pids[@]} -eq 0 ]]; then
|
||||
local purged=false
|
||||
if [[ "$purge" == true ]]; then
|
||||
rm -rf -- /tmp/oo_"${instance}"* 2>/dev/null || true
|
||||
purged=true
|
||||
fi
|
||||
printf '{"instance":"%s","killed_pids":[],"purged":%s,"status":"not_running"}\n' \
|
||||
"$instance" "$purged"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 4. SIGTERM a todos los pids del slot.
|
||||
kill -TERM "${pids[@]}" 2>/dev/null || true
|
||||
|
||||
# 5. Esperar ~3s a que mueran (NUNCA sleep foreground): read -t 0.3 x10.
|
||||
local w=0 wmax=10
|
||||
while [[ $w -lt $wmax ]]; do
|
||||
local alive=false p
|
||||
for p in "${pids[@]}"; do
|
||||
if kill -0 "$p" 2>/dev/null; then alive=true; break; fi
|
||||
done
|
||||
[[ "$alive" == false ]] && break
|
||||
read -t 0.3 _ </dev/null 2>/dev/null || true
|
||||
w=$((w + 1))
|
||||
done
|
||||
|
||||
# 6. SIGKILL a los que sigan vivos.
|
||||
local p
|
||||
for p in "${pids[@]}"; do
|
||||
if kill -0 "$p" 2>/dev/null; then
|
||||
kill -KILL "$p" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# 7. Purge opcional de los dirs del slot.
|
||||
local purged=false
|
||||
if [[ "$purge" == true ]]; then
|
||||
rm -rf -- /tmp/oo_"${instance}"* 2>/dev/null || true
|
||||
purged=true
|
||||
fi
|
||||
|
||||
# 8. JSON con el array de pids terminados.
|
||||
local pids_json
|
||||
pids_json=$(printf '%s,' "${pids[@]}")
|
||||
pids_json="[${pids_json%,}]"
|
||||
printf '{"instance":"%s","killed_pids":%s,"purged":%s,"status":"closed"}\n' \
|
||||
"$instance" "$pids_json" "$purged"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Ejecutable directo o sourceado.
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
close_onlyoffice_instance "$@"
|
||||
fi
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: monitor_listening_ports
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: shell
|
||||
version: "0.3.0"
|
||||
purity: impure
|
||||
signature: "monitor_listening_ports([--interval N], [--once]) -> void"
|
||||
description: "TUI ligera de terminal que refresca cada N segundos una tabla de los sockets TCP en escucha (LISTEN) del equipo local: IP | PUERTO | PROCESO | PID | TIEMPO ACTIVO | CMD (cmdline real, util para distinguir python3/node genericos), ordenada por tiempo de vida del proceso dueño (descendente). Una fila por pid. Lanzada como root rellena tambien los sockets de otros usuarios. Modo --once imprime un solo frame y sale."
|
||||
tags: [recon, ports, monitor, tui]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: --interval N
|
||||
desc: "segundos entre refrescos en modo bucle (default: 1, acepta decimales)"
|
||||
- name: --once
|
||||
desc: "imprime un único frame de la tabla y termina con exit 0 (no interactivo; úsalo en tests y en `fn run` para no colgar)"
|
||||
output: "tabla a stdout con columnas IP, PUERTO, PROCESO, PID, TIEMPO ACTIVO ordenada por uptime del proceso descendente; sin --once refresca en bucle infinito hasta Ctrl-C"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/shell/monitor_listening_ports.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Un solo frame (no cuelga) — ideal para fn run o un pipe
|
||||
./fn run monitor_listening_ports_bash_shell --once
|
||||
|
||||
# Como script directo
|
||||
bash bash/functions/shell/monitor_listening_ports.sh --once
|
||||
|
||||
# Sourceada, en bucle interactivo refrescando cada segundo (Ctrl-C para salir)
|
||||
source bash/functions/shell/monitor_listening_ports.sh
|
||||
monitor_listening_ports --interval 1
|
||||
|
||||
# Refresco mas lento
|
||||
monitor_listening_ports --interval 5
|
||||
```
|
||||
|
||||
Salida (frame `--once`, recortado):
|
||||
|
||||
```
|
||||
IP PUERTO PROCESO PID TIEMPO ACTIVO
|
||||
* 8420 registry_api 1885 4d 23:40:46
|
||||
:: 8889 mitmweb 1892 4d 23:40:46
|
||||
127.0.0.1 8484 sqlite_api 1889 4d 23:40:42
|
||||
127.0.0.1 8899 jupyter-lab 155100 4d 19:33:55
|
||||
::1 631 - - ?
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando quieras vigilar **qué puertos abren tus dev-servers / procesos web locales y desde cuándo** llevan vivos, en una sola pantalla que se actualiza sola.
|
||||
- Para detectar de un vistazo un proceso recién levantado (aparece al fondo, con poco TIEMPO ACTIVO) o uno que lleva días escuchando (arriba del todo).
|
||||
- Como paso de reconocimiento local del grupo `recon`: inventario rápido de superficie de escucha TCP del propio equipo, con el dueño de cada socket.
|
||||
- En tests o automatizaciones que solo necesitan un snapshot: añade `--once` para obtener un frame y salir.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Impura**: depende de `ss` (paquete iproute2) y `ps` (procps). Si falta cualquiera, sale con exit 1 y un mensaje a stderr.
|
||||
- **Sin sudo no ves PROCESO/PID/CMD de sockets de otros usuarios** (típicamente procesos de root, ej. systemd-resolved en `127.0.0.54:53`, kernels Jupyter de otra sesión, o servidores en contenedores). Esas filas muestran `-`/`?`. La función **no usa sudo** a propósito; para **rellenarlos, lánzala como root**: `pass show claude/sudo | sudo -S bash bash/functions/shell/monitor_listening_ports.sh --interval 1` (el password se pipea, no queda en la cmdline). Como root, `ss` resuelve el dueño de todos los sockets.
|
||||
- **Columna CMD = cmdline real** (`ps -o args=`, recortada a 90 chars). Es lo que distingue un `python3`/`node` genérico (PROCESO) de lo que realmente ejecuta: `python3 -m ipykernel_launcher ...`, `registry_api -port 8420`, etc. Procesos en distinto namespace (docker) pueden seguir sin CMD aunque corras como root.
|
||||
- **Una fila por pid**: un mismo puerto con varios workers (ej. nginx, gunicorn) genera varias filas, una por cada pid dueño del socket.
|
||||
- **`--once` evita colgar**: sin `--once` corre en bucle infinito. No lo lances así en tests ni en `fn run` desatendido — usa `--once`.
|
||||
- **El orden es por uptime del PROCESO, no por el tiempo de la conexión**. `ps -o etimes=` mide cuánto lleva vivo el proceso completo, no cuándo abrió ese socket concreto.
|
||||
- **Carrera ps**: si un pid muere entre `ss` y `ps`, su TIEMPO ACTIVO sale como `?` y la fila se ordena al final (no rompe el bucle; el script usa `set -uo pipefail` sin `-e`).
|
||||
- En modo bucle oculta el cursor (`tput civis`) y lo restaura + limpia en un `trap` EXIT/INT/TERM, de modo que Ctrl-C deja la terminal limpia.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v0.3.0 (14/06/2026) — añade columna **CMD** con la cmdline real del proceso (mapa pid→args construido en la misma llamada `ps -eo pid=,etimes=,args=`), para distinguir un `python3`/`node` genérico de lo que realmente ejecuta. Documenta cómo rellenar los sockets de otros usuarios (`-`) lanzando la TUI como root. Anchos de columna reajustados para dar sitio a CMD.
|
||||
- v0.2.0 (14/06/2026) — corrige parpadeo y cuelgue del modo bucle. (1) Doble-buffer ANSI: cada frame se computa completo en una variable y se pinta con cursor-home `\033[H` + clear-to-end `\033[J` en vez de `tput clear` antes de recolectar, eliminando el instante en blanco. (2) Rendimiento: una sola llamada a `ps -eo pid=,etimes=` (mapa pid→uptime en memoria, antes era un fork de `ps` por pid) y construcción de filas con `printf -v` (builtin, antes un `$( )` por fila); frame de ~130 ms con cientos de sockets. (3) Bugfix de cuelgue: el avance del parser multi-pid usaba `BASH_REMATCH[0]`, que queda sobrescrito por el `[[ =~ ]]` interno de `_mlp_fmt_etime` → no recortaba el string y entraba en bucle infinito. Ahora el needle se captura justo tras el match, con guard anti-cuelgue si el recorte no progresa.
|
||||
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env bash
|
||||
# monitor_listening_ports — TUI ligera que refresca una tabla de sockets TCP en
|
||||
# escucha (LISTEN) del equipo local, ordenada por tiempo de vida del proceso
|
||||
# dueño (descendente). Columnas: IP | PUERTO | PROCESO | PID | TIEMPO ACTIVO.
|
||||
#
|
||||
# Funcion impura: lee estado del sistema (sockets via `ss`, uptime de procesos
|
||||
# via `ps`). Sin --once corre en bucle infinito refrescando cada N segundos.
|
||||
#
|
||||
# Rendimiento: cada frame hace UNA sola llamada a `ss` y UNA sola a `ps`
|
||||
# (mapa pid->etimes en memoria). El parseo de cada socket es bash puro y SIN
|
||||
# command substitution por fila: las cadenas se construyen con `printf -v`
|
||||
# (builtin, cero forks) y el formato de tiempo se devuelve en una variable
|
||||
# global. El modo bucle usa doble-buffer ANSI (cursor home + clear-to-end) en
|
||||
# lugar de limpiar la pantalla antes de computar, para que nunca se vea vacia
|
||||
# entre refrescos.
|
||||
|
||||
# No usamos -e a proposito: una carrera donde un pid muere entre `ss` y `ps`
|
||||
# no debe matar el bucle entero. -u y pipefail se mantienen para robustez.
|
||||
set -uo pipefail
|
||||
|
||||
# Formatea segundos a texto humano legible y lo deja en la global _mlp_human.
|
||||
# Se evita `$( )` (un fork por fila) usando una variable de retorno.
|
||||
# <1h -> MM:SS ej. 12:45
|
||||
# <1d -> HH:MM:SS ej. 03:12:45
|
||||
# >=1d -> Nd HH:MM:SS ej. 1d 03:12:45
|
||||
_mlp_human=""
|
||||
_mlp_fmt_etime() {
|
||||
local secs="$1"
|
||||
# Si no es un numero entero valido, devolver tal cual (ej. "?").
|
||||
if ! [[ "$secs" =~ ^[0-9]+$ ]]; then
|
||||
_mlp_human="$secs"
|
||||
return 0
|
||||
fi
|
||||
local days=$(( secs / 86400 ))
|
||||
local rem=$(( secs % 86400 ))
|
||||
local hours=$(( rem / 3600 ))
|
||||
local mins=$(( (rem % 3600) / 60 ))
|
||||
local s=$(( rem % 60 ))
|
||||
if (( days > 0 )); then
|
||||
printf -v _mlp_human '%dd %02d:%02d:%02d' "$days" "$hours" "$mins" "$s"
|
||||
elif (( hours > 0 )); then
|
||||
printf -v _mlp_human '%02d:%02d:%02d' "$hours" "$mins" "$s"
|
||||
else
|
||||
printf -v _mlp_human '%02d:%02d' "$mins" "$s"
|
||||
fi
|
||||
}
|
||||
|
||||
# Imprime un unico frame de la tabla a stdout.
|
||||
# Estrategia de rendimiento (cero forks por fila):
|
||||
# 1. Un solo `ps -eo pid=,etimes=` construye un mapa pid -> segundos vivo.
|
||||
# 2. Un solo `ss -H -tlnp` lista los sockets en escucha.
|
||||
# 3. Cada linea se parsea con bash puro: IP/puerto por parameter expansion,
|
||||
# (nombre,pid) del campo users:(...) iterando con BASH_REMATCH, y cada
|
||||
# fila se arma con `printf -v` (builtin). El uptime se resuelve por lookup
|
||||
# O(1) en el mapa.
|
||||
# 4. Se ordena por segundos vivo descendente con un unico `sort`.
|
||||
_mlp_render_frame() {
|
||||
# Mapas pid -> etimes (segundos vivo) y pid -> cmdline completa. Una sola
|
||||
# invocacion de ps por frame. `args=` va al ultimo porque lleva espacios,
|
||||
# asi `read` lo captura entero en la tercera variable.
|
||||
local -A etmap=() argmap=()
|
||||
local _pid _et _args
|
||||
while read -r _pid _et _args; do
|
||||
[[ -z "$_pid" ]] && continue
|
||||
etmap["$_pid"]="$_et"
|
||||
argmap["$_pid"]="$_args"
|
||||
done < <(ps -eo pid=,etimes=,args= 2>/dev/null)
|
||||
|
||||
# Cada fila intermedia: "<etimes>\t<ip>\t<puerto>\t<proceso>\t<pid>\t<humano>"
|
||||
local -a rows=()
|
||||
local line row
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
|
||||
# Campos de `ss -H -tlnp`: State Recv-Q Send-Q Local:Port Peer:Port users:(...)
|
||||
# Local:Port es el 4o token. Lo extraemos sin fork con read en array.
|
||||
local -a F=()
|
||||
read -ra F <<<"$line"
|
||||
local local_addr="${F[3]:-}"
|
||||
[[ -z "$local_addr" ]] && continue
|
||||
|
||||
# Separar IP y PUERTO partiendo por el ULTIMO ':'.
|
||||
local ip port
|
||||
port="${local_addr##*:}"
|
||||
ip="${local_addr%:*}"
|
||||
# Quitar corchetes de IPv6: [::] -> :: , [::1] -> ::1
|
||||
ip="${ip#[}"
|
||||
ip="${ip%]}"
|
||||
# Caso de bind sin direccion explicita (raro): dejar marcador.
|
||||
[[ -z "$ip" ]] && ip="*"
|
||||
|
||||
# Extraer el bloque users:(...) del final de la linea (si existe).
|
||||
local users=""
|
||||
[[ "$line" == *"users:("* ]] && users="${line#*users:(}"
|
||||
|
||||
if [[ -z "$users" ]]; then
|
||||
# Socket sin info de proceso (pertenece a otro usuario y no corremos
|
||||
# como root). Para verlo, lanzar la TUI como root (ver Gotchas).
|
||||
printf -v row '%s\t%s\t%s\t%s\t%s\t%s\t%s' "-1" "$ip" "$port" "-" "-" "?" "-"
|
||||
rows+=("$row")
|
||||
continue
|
||||
fi
|
||||
|
||||
# Dentro de users puede haber varios ("nombre",pid=N,fd=M). Una fila por
|
||||
# pid. Iteramos con BASH_REMATCH avanzando sobre el string (cero forks).
|
||||
local s="$users" pname pid etimes needle prev_s cmd found_any=0
|
||||
while [[ "$s" =~ \"([^\"]*)\",pid=([0-9]+) ]]; do
|
||||
# IMPORTANTE: capturar nombre/pid/needle ANTES de cualquier otra
|
||||
# comparacion `[[ =~ ]]` (p.ej. dentro de _mlp_fmt_etime), porque
|
||||
# cada `=~` SOBREESCRIBE BASH_REMATCH. Si se usara BASH_REMATCH[0]
|
||||
# despues, contendria el match del ultimo `=~` y el recorte de `s`
|
||||
# no avanzaria -> bucle infinito.
|
||||
pname="${BASH_REMATCH[1]}"
|
||||
pid="${BASH_REMATCH[2]}"
|
||||
needle="${BASH_REMATCH[0]}"
|
||||
found_any=1
|
||||
|
||||
# Lookup O(1) en el mapa. Si el pid ya no esta (carrera), marcar "?".
|
||||
etimes="${etmap[$pid]:-}"
|
||||
if [[ -z "$etimes" || ! "$etimes" =~ ^[0-9]+$ ]]; then
|
||||
etimes="-1"
|
||||
_mlp_human="?"
|
||||
else
|
||||
_mlp_fmt_etime "$etimes"
|
||||
fi
|
||||
|
||||
# Comando real (cmdline completa) del pid; dice QUE es realmente un
|
||||
# "python3"/"node" generico. Se recorta para no romper la tabla.
|
||||
cmd="${argmap[$pid]:-}"
|
||||
[[ -z "$cmd" ]] && cmd="-"
|
||||
cmd="${cmd:0:90}"
|
||||
|
||||
printf -v row '%s\t%s\t%s\t%s\t%s\t%s\t%s' "$etimes" "$ip" "$port" "$pname" "$pid" "$_mlp_human" "$cmd"
|
||||
rows+=("$row")
|
||||
|
||||
# Avanzar mas alla del match actual para no repetir el primer pid.
|
||||
# Guard: si el recorte no cambia `s`, cortar para no colgar nunca.
|
||||
prev_s="$s"
|
||||
s="${s#*"$needle"}"
|
||||
[[ "$s" == "$prev_s" ]] && break
|
||||
done
|
||||
|
||||
# Si el formato fue inesperado y no se parseo ningun par, fila placeholder.
|
||||
if (( found_any == 0 )); then
|
||||
printf -v row '%s\t%s\t%s\t%s\t%s\t%s\t%s' "-1" "$ip" "$port" "-" "-" "?" "-"
|
||||
rows+=("$row")
|
||||
fi
|
||||
done < <(ss -H -tlnp 2>/dev/null)
|
||||
|
||||
# Estilo de cabecera (negrita) si la terminal lo soporta.
|
||||
local bold="" reset=""
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
|
||||
bold=$(tput bold 2>/dev/null || true)
|
||||
reset=$(tput sgr0 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Anchos fijos para alineacion estable (no usamos column -t). La ultima
|
||||
# columna (CMD) es libre: muestra la cmdline real del proceso.
|
||||
local fmt='%-26s %-7s %-16s %-8s %-13s %s\n'
|
||||
# shellcheck disable=SC2059
|
||||
printf "${bold}${fmt}${reset}" "IP" "PUERTO" "PROCESO" "PID" "TIEMPO ACTIVO" "CMD"
|
||||
|
||||
if (( ${#rows[@]} == 0 )); then
|
||||
printf '(sin sockets TCP en escucha)\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Ordenar por la primera columna (etimes) numerica descendente y emitir las
|
||||
# 5 columnas visibles (descartando la columna de orden).
|
||||
printf '%s\n' "${rows[@]}" \
|
||||
| sort -t$'\t' -k1,1nr \
|
||||
| while IFS=$'\t' read -r _etimes ip port pname pid human cmd; do
|
||||
# shellcheck disable=SC2059
|
||||
printf "$fmt" "$ip" "$port" "$pname" "$pid" "$human" "$cmd"
|
||||
done
|
||||
}
|
||||
|
||||
monitor_listening_ports() {
|
||||
local interval=1
|
||||
local once=0
|
||||
|
||||
# Parseo de flags.
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--interval)
|
||||
interval="${2:-1}"
|
||||
shift 2
|
||||
;;
|
||||
--interval=*)
|
||||
interval="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--once)
|
||||
once=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
cat <<'USAGE'
|
||||
monitor_listening_ports [--interval N] [--once]
|
||||
|
||||
--interval N Segundos entre refrescos (default: 1, acepta decimales).
|
||||
--once Imprime un solo frame de la tabla y termina (exit 0).
|
||||
|
||||
Tabla de sockets TCP en escucha (LISTEN) ordenada por tiempo de vida del
|
||||
proceso dueño (descendente). Columnas: IP | PUERTO | PROCESO | PID | TIEMPO ACTIVO.
|
||||
USAGE
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
printf 'monitor_listening_ports: argumento desconocido: %s\n' "$1" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Dependencias minimas.
|
||||
if ! command -v ss >/dev/null 2>&1; then
|
||||
printf 'monitor_listening_ports: requiere `ss` (paquete iproute2)\n' >&2
|
||||
return 1
|
||||
fi
|
||||
if ! command -v ps >/dev/null 2>&1; then
|
||||
printf 'monitor_listening_ports: requiere `ps` (paquete procps)\n' >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Modo single-frame: util para tests y para `fn run` sin colgar.
|
||||
if (( once == 1 )); then
|
||||
_mlp_render_frame
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Modo bucle interactivo: oculta cursor y lo restaura + limpia al salir.
|
||||
local have_tput=0
|
||||
command -v tput >/dev/null 2>&1 && have_tput=1
|
||||
|
||||
_mlp_cleanup() {
|
||||
if (( have_tput == 1 )); then
|
||||
tput cnorm 2>/dev/null || true # restaurar cursor
|
||||
tput sgr0 2>/dev/null || true # resetear atributos
|
||||
fi
|
||||
printf '\n'
|
||||
}
|
||||
trap '_mlp_cleanup; trap - INT TERM EXIT; return 0 2>/dev/null || exit 0' INT TERM EXIT
|
||||
|
||||
(( have_tput == 1 )) && tput civis 2>/dev/null || true # ocultar cursor
|
||||
|
||||
# Limpiamos la pantalla UNA sola vez al entrar. A partir de aqui cada frame
|
||||
# se computa COMPLETO en una variable y luego se pinta con doble-buffer:
|
||||
# cursor a home (\033[H), volcado del frame, y clear-to-end (\033[J) para
|
||||
# borrar restos de un frame anterior mas largo. Asi nunca hay un instante
|
||||
# con la pantalla vacia mientras se recolectan los datos.
|
||||
printf '\033[2J'
|
||||
|
||||
local frame
|
||||
while true; do
|
||||
frame=$(
|
||||
printf 'monitor_listening_ports — %s — intervalo %ss — orden: TIEMPO ACTIVO desc (Ctrl-C para salir)\n\n' \
|
||||
"$(date '+%d/%m/%Y %H:%M:%S')" "$interval"
|
||||
_mlp_render_frame
|
||||
)
|
||||
printf '\033[H' # cursor al inicio (sin borrar todavia)
|
||||
printf '%s\n' "$frame" # volcar el frame ya calculado de golpe
|
||||
printf '\033[J' # borrar de aqui al final (restos del frame previo)
|
||||
sleep "$interval" || break
|
||||
done
|
||||
}
|
||||
|
||||
# Auto-invocacion cuando se ejecuta como script (no al hacer source).
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
monitor_listening_ports "$@"
|
||||
fi
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: open_onlyoffice_file
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: shell
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "open_onlyoffice_file(file_path: string, instance: string = demo) -> json"
|
||||
description: "Abre un archivo en una INSTANCIA AISLADA de ONLYOFFICE Desktop Editors (Linux/X11) sin perturbar la instancia personal del usuario. Cada 'instance' (slot, default demo) usa su propio HOME=/tmp/oo_<instance>, XDG_RUNTIME_DIR y XDG_CONFIG_HOME bajo /tmp, lo que rompe el single-instance lock de ONLYOFFICE y permite una ventana propia en vez de una pestaña en la instancia del usuario. Espera la ventana por evento (xdotool, basename del archivo, timeout ~25s) sin sleep en foreground. Idempotente: si ya hay ventana para ese basename, no relanza y devuelve el wid existente. NO crea archivos: si file_path no existe, falla. Imprime una linea JSON con instance, file (ruta absoluta), wid (hex), pid y status (open|timeout)."
|
||||
tags: [onlyoffice, desktop, x11, shell]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: file_path
|
||||
desc: "ruta (relativa o absoluta) al archivo a abrir; DEBE existir, esta funcion no crea archivos. Se normaliza con readlink -f y se busca la ventana por su basename"
|
||||
- name: instance
|
||||
desc: "nombre del slot aislado (default: demo). Determina el env: HOME=/tmp/oo_<instance>, XDG_RUNTIME_DIR=/tmp/oo_<instance>_run, XDG_CONFIG_HOME=/tmp/oo_<instance>/.config. Usa el MISMO instance en reload/close para operar la misma instancia"
|
||||
output: "una linea JSON a stdout: {\"instance\":\"<i>\",\"file\":\"<abs>\",\"wid\":\"<hex>|null\",\"pid\":<n>|null,\"status\":\"open\"|\"timeout\"}. Exit 0 si abrio (status open), exit 1 si la ventana no aparecio en el timeout (status timeout) o falta dependencia/archivo, exit 2 si falta el argumento file_path"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/shell/open_onlyoffice_file.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Como script directo (slot 'demo' por defecto)
|
||||
bash bash/functions/shell/open_onlyoffice_file.sh /tmp/demo_reload.xlsx
|
||||
|
||||
# Slot nombrado distinto (ventana propia, no perturba la instancia personal)
|
||||
bash bash/functions/shell/open_onlyoffice_file.sh /tmp/informe.docx reporte
|
||||
|
||||
# Via fn run
|
||||
./fn run open_onlyoffice_file_bash_shell /tmp/demo_reload.xlsx demo
|
||||
|
||||
# Sourceado, capturando el wid del JSON
|
||||
source bash/functions/shell/open_onlyoffice_file.sh
|
||||
out=$(open_onlyoffice_file /tmp/demo_reload.xlsx demo)
|
||||
echo "$out"
|
||||
# {"instance":"demo","file":"/tmp/demo_reload.xlsx","wid":"0x3c00007","pid":12345,"status":"open"}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando necesites **abrir un archivo en ONLYOFFICE Desktop desde terminal en su propia ventana aislada**, sin que se agregue como pestaña a la instancia personal del usuario.
|
||||
- Como primer paso de un flujo automatizado open -> (editas el archivo en disco) -> `reload_onlyoffice_file` -> `close_onlyoffice_instance`.
|
||||
- Cuando quieras un slot reproducible por nombre (`instance`) que reuse la misma instancia aislada entre llamadas (reabrir rapido en vez de arrancar el motor de cero).
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **ONLYOFFICE Desktop es single-instance por usuario**: sin el slot aislado (HOME/XDG_RUNTIME_DIR propios), un segundo lanzamiento se reenvia a la instancia viva y abre el archivo como PESTAÑA, no ventana nueva. El lock NO se rompe con XDG_CONFIG_HOME solo; SI con HOME + XDG_RUNTIME_DIR propios. Esta funcion ya aplica esa convencion.
|
||||
- **NO hay reload nativo de cambios externos** (GitHub Issue #2313 abierto, no implementado). Esta funcion solo abre; para reflejar ediciones hechas en disco hay que cerrar+reabrir con `reload_onlyoffice_file`.
|
||||
- **NO crea archivos**: si `file_path` no existe, falla con exit 1. Crea el archivo por tu cuenta antes de llamar.
|
||||
- **El slot vive en /tmp**: los dirs `/tmp/oo_<instance>*` se pierden al reiniciar el PC (tmpfs en muchos sistemas). No guardes nada importante ahi; es estado desechable de la instancia aislada.
|
||||
- **Requiere X11 + wmctrl + xdotool**: no funciona en Wayland puro sin XWayland (xdotool no encontrara la ventana). La funcion comprueba `command -v` de las 3 deps y falla claro si falta alguna.
|
||||
- **El pid reportado es el del launcher** (`onlyoffice-desktopeditors`), que puede reexec/fork al proceso real `DesktopEditors`; sirve como referencia best-effort, no para `kill` fiable (usa `close_onlyoffice_instance`, que localiza el proceso real por su HOME).
|
||||
- **Idempotencia por basename**: si ya existe una ventana cuyo titulo contiene el basename del archivo (lo abrio el usuario en su instancia personal, por ejemplo), la funcion la considera "ya abierta" y devuelve ese wid sin relanzar. Usa un basename unico para el slot de pruebas si quieres evitar colisiones.
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# open_onlyoffice_file — abre un archivo en una INSTANCIA AISLADA de ONLYOFFICE
|
||||
# Desktop Editors (Linux/X11), sin perturbar la instancia personal del usuario.
|
||||
#
|
||||
# Funcion impura: lanza un proceso GUI, lee estado de ventanas (xdotool) y
|
||||
# escribe directorios en /tmp. Imprime una linea JSON con el resultado.
|
||||
#
|
||||
# Por que "instancia aislada": ONLYOFFICE Desktop es single-instance por
|
||||
# usuario — un segundo `onlyoffice-desktopeditors <file>` se reenvia a la
|
||||
# instancia viva y abre el archivo como PESTAÑA en su ventana. El lock
|
||||
# single-instance NO se rompe con XDG_CONFIG_HOME, pero SI se rompe lanzando
|
||||
# con HOME y XDG_RUNTIME_DIR propios. Por eso cada "slot" nombrado (instance)
|
||||
# usa su propio HOME/XDG_RUNTIME_DIR/XDG_CONFIG_HOME bajo /tmp.
|
||||
|
||||
# Sin -e: las busquedas de ventana (xdotool search) pueden no matchear y
|
||||
# devolver exit !=0; no deben abortar la funcion. -u y pipefail se mantienen.
|
||||
set -uo pipefail
|
||||
|
||||
open_onlyoffice_file() {
|
||||
local file_path="${1:-}"
|
||||
local instance="${2:-demo}"
|
||||
|
||||
if [[ -z "$file_path" ]]; then
|
||||
echo "open_onlyoffice_file: falta <file_path>" >&2
|
||||
echo "uso: open_onlyoffice_file <file_path> [instance]" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
# 1. Dependencias del sistema.
|
||||
local dep
|
||||
for dep in onlyoffice-desktopeditors wmctrl xdotool; do
|
||||
if ! command -v "$dep" >/dev/null 2>&1; then
|
||||
echo "open_onlyoffice_file: falta dependencia '$dep' (instala el paquete correspondiente)" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. El archivo DEBE existir — esta funcion no crea archivos.
|
||||
if [[ ! -f "$file_path" ]]; then
|
||||
echo "open_onlyoffice_file: el archivo no existe: $file_path (esta funcion no crea archivos)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ruta absoluta y basename para titular/buscar la ventana.
|
||||
local abs_path base
|
||||
abs_path=$(readlink -f -- "$file_path")
|
||||
base=$(basename -- "$abs_path")
|
||||
|
||||
# 3. Slot aislado: HOME/XDG_RUNTIME_DIR/XDG_CONFIG_HOME propios bajo /tmp.
|
||||
local oo_home="/tmp/oo_${instance}"
|
||||
local oo_run="/tmp/oo_${instance}_run"
|
||||
local oo_cfg="${oo_home}/.config"
|
||||
mkdir -p "$oo_home" "$oo_cfg" "$oo_run"
|
||||
chmod 700 "$oo_run" 2>/dev/null || true
|
||||
|
||||
# 4. Idempotencia: si ya hay ventana para ese basename, no relanzar.
|
||||
local existing_wid
|
||||
existing_wid=$(xdotool search --name -- "$base" 2>/dev/null | head -1 || true)
|
||||
if [[ -n "$existing_wid" ]]; then
|
||||
local wid_hex
|
||||
wid_hex=$(printf '0x%x' "$existing_wid" 2>/dev/null || echo "$existing_wid")
|
||||
printf '{"instance":"%s","file":"%s","wid":"%s","pid":null,"status":"open"}\n' \
|
||||
"$instance" "$abs_path" "$wid_hex"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 5. Lanzar la instancia aislada con su env propio. setsid lo desacopla de
|
||||
# la terminal; redirige todo a un log del slot.
|
||||
env HOME="$oo_home" XDG_RUNTIME_DIR="$oo_run" XDG_CONFIG_HOME="$oo_cfg" \
|
||||
setsid onlyoffice-desktopeditors "$abs_path" \
|
||||
>"/tmp/oo_${instance}.log" 2>&1 </dev/null &
|
||||
local launch_pid=$!
|
||||
|
||||
# 6. Esperar la ventana por evento (NUNCA sleep en foreground).
|
||||
# ~25s con read -t 0.3 => ~83 iteraciones.
|
||||
local wid="" i=0 max=83
|
||||
while [[ $i -lt $max ]]; do
|
||||
wid=$(xdotool search --name -- "$base" 2>/dev/null | head -1 || true)
|
||||
[[ -n "$wid" ]] && break
|
||||
read -t 0.3 _ </dev/null 2>/dev/null || true
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [[ -z "$wid" ]]; then
|
||||
printf '{"instance":"%s","file":"%s","wid":null,"pid":%s,"status":"timeout"}\n' \
|
||||
"$instance" "$abs_path" "$launch_pid"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local wid_hex
|
||||
wid_hex=$(printf '0x%x' "$wid" 2>/dev/null || echo "$wid")
|
||||
# El pid del proceso real (DesktopEditors) puede diferir del launcher; el
|
||||
# launcher reexec/fork. Reportamos el pid del launcher (best-effort).
|
||||
printf '{"instance":"%s","file":"%s","wid":"%s","pid":%s,"status":"open"}\n' \
|
||||
"$instance" "$abs_path" "$wid_hex" "$launch_pid"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Ejecutable directo: `bash open_onlyoffice_file.sh <file> [instance]`.
|
||||
# Sourceado: define la funcion sin ejecutarla.
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
open_onlyoffice_file "$@"
|
||||
fi
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: reload_onlyoffice_file
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: shell
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "reload_onlyoffice_file(file_path: string, instance: string = demo) -> json"
|
||||
description: "Recarga en la ventana de ONLYOFFICE Desktop Editors los datos que el caller edito EN DISCO, cerrando y reabriendo el archivo en la INSTANCIA AISLADA (slot). Es la funcion estrella del grupo: ONLYOFFICE no recarga cambios externos del archivo (GitHub Issue #2313 abierto, no implementado), asi que la unica forma de mostrar datos editados fuera de la app es cerrar la ventana (wmctrl -ic) y reabrir (ONLYOFFICE lee fresco del disco al abrir). Localiza la ventana por basename, la cierra y espera a que desaparezca (timeout ~10s), relanza con el env del slot aislado y espera la ventana nueva (timeout ~25s), todo por evento sin sleep en foreground. Si no habia ventana previa, actua como open. NO edita el archivo: el caller lo edita antes de llamar. Imprime JSON con wid_old, wid_new, reopened, elapsed_s y status (reloaded|timeout)."
|
||||
tags: [onlyoffice, desktop, x11, shell]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: file_path
|
||||
desc: "ruta (relativa o absoluta) al archivo cuya ventana se recarga; DEBE existir. El caller ya lo edito en disco antes de llamar. Se busca la ventana por su basename"
|
||||
- name: instance
|
||||
desc: "nombre del slot aislado (default: demo); debe coincidir con el usado en open_onlyoffice_file para reusar la misma instancia. Determina HOME/XDG_RUNTIME_DIR/XDG_CONFIG_HOME bajo /tmp"
|
||||
output: "una linea JSON a stdout: {\"instance\":\"<i>\",\"file\":\"<abs>\",\"wid_old\":\"<hex>|null\",\"wid_new\":\"<hex>|null\",\"reopened\":true|false,\"elapsed_s\":<n>,\"status\":\"reloaded\"|\"timeout\"}. Exit 0 si reabrio (status reloaded), exit 1 si la ventana nueva no aparecio en el timeout (status timeout) o falta dependencia/archivo, exit 2 si falta file_path"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/shell/reload_onlyoffice_file.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Flujo tipico: editas el .xlsx en disco con tu herramienta y refrescas la vista
|
||||
# (este ejemplo asume que /tmp/demo_reload.xlsx ya esta abierto en el slot demo)
|
||||
bash bash/functions/shell/reload_onlyoffice_file.sh /tmp/demo_reload.xlsx demo
|
||||
|
||||
# Via fn run
|
||||
./fn run reload_onlyoffice_file_bash_shell /tmp/demo_reload.xlsx demo
|
||||
|
||||
# Sourceado, dentro de un bucle de "editar en disco -> ver en ONLYOFFICE"
|
||||
source bash/functions/shell/reload_onlyoffice_file.sh
|
||||
# ... el caller modifica /tmp/demo_reload.xlsx por su cuenta ...
|
||||
out=$(reload_onlyoffice_file /tmp/demo_reload.xlsx demo)
|
||||
echo "$out"
|
||||
# {"instance":"demo","file":"/tmp/demo_reload.xlsx","wid_old":"0x3c00007","wid_new":"0x3c0000b","reopened":true,"elapsed_s":4,"status":"reloaded"}
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando **editaste un archivo en disco fuera de ONLYOFFICE** (script, otra herramienta, generador) y necesitas que la ventana de ONLYOFFICE muestre los datos nuevos: esta funcion cierra y reabre para forzar la lectura fresca del disco.
|
||||
- En bucles de iteracion rapida "modificar el archivo -> ver el resultado en ONLYOFFICE" sin tocar la instancia personal del usuario.
|
||||
- Como reemplazo del reload nativo inexistente (Issue #2313): es la unica via fiable de refrescar la vista desde disco.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **No edita el archivo**: solo recarga la ventana desde disco. El caller es responsable de modificar el archivo ANTES de llamar; si no lo modifico, reabrira los mismos datos.
|
||||
- **ONLYOFFICE no tiene reload de cambios externos** (GitHub Issue #2313 abierto, no implementado): por eso esta funcion existe y hace cerrar+reabrir. No hay forma "in-place" de refrescar.
|
||||
- **`wmctrl -ic` puede disparar el dialogo "Guardar cambios"** si el usuario edito EN la app (no en disco) y hay cambios sin guardar en esa ventana. El flujo previsto es editar SOLO en disco con la ventana sin tocar; si editaste en la app, guarda o descarta antes, o el cierre se quedara esperando interaccion (la funcion saldra por timeout).
|
||||
- **Single-instance + slot aislado**: usa el mismo `instance` que en `open_onlyoffice_file`. Con HOME/XDG_RUNTIME_DIR propios el relaunch reenvia a la instancia aislada viva y reabre rapido; con env por defecto se reenviaria a la instancia personal del usuario (no deseado).
|
||||
- **El slot vive en /tmp**: `/tmp/oo_<instance>*` se pierde al reiniciar el PC. Estado desechable.
|
||||
- **Requiere X11 + wmctrl + xdotool**: no funciona en Wayland puro sin XWayland. Comprueba las 3 deps y falla claro si falta alguna.
|
||||
- **Carrera de cierre**: si la ventana tarda mas de ~10s en cerrarse (dialogo modal, app ocupada), la funcion continua igualmente al relaunch; el resultado puede acabar en `timeout` si la ventana nueva no aparece a tiempo.
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# reload_onlyoffice_file — cierra y reabre un archivo en la INSTANCIA AISLADA de
|
||||
# ONLYOFFICE Desktop Editors para que la ventana muestre los datos editados
|
||||
# EN DISCO por el caller (ONLYOFFICE no recarga cambios externos: GitHub Issue
|
||||
# #2313 abierto, no implementado — la unica forma es cerrar+reabrir).
|
||||
#
|
||||
# Funcion impura: cierra una ventana GUI (wmctrl), relanza un proceso y espera
|
||||
# la ventana nueva por evento. NO edita el archivo — solo recarga la ventana
|
||||
# desde el disco. El caller edita el archivo antes de llamar a esta funcion.
|
||||
#
|
||||
# Instancia aislada (slot): mismo HOME/XDG_RUNTIME_DIR/XDG_CONFIG_HOME que usa
|
||||
# open_onlyoffice_file, para que el relaunch reenvie a la instancia aislada
|
||||
# viva y reabra rapido en vez de arrancar el motor de cero.
|
||||
|
||||
# Sin -e: busquedas de ventana (xdotool/wmctrl) pueden no matchear; no deben
|
||||
# abortar la funcion. -u y pipefail se mantienen.
|
||||
set -uo pipefail
|
||||
|
||||
reload_onlyoffice_file() {
|
||||
local file_path="${1:-}"
|
||||
local instance="${2:-demo}"
|
||||
|
||||
if [[ -z "$file_path" ]]; then
|
||||
echo "reload_onlyoffice_file: falta <file_path>" >&2
|
||||
echo "uso: reload_onlyoffice_file <file_path> [instance]" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
# 1. Dependencias del sistema.
|
||||
local dep
|
||||
for dep in onlyoffice-desktopeditors wmctrl xdotool; do
|
||||
if ! command -v "$dep" >/dev/null 2>&1; then
|
||||
echo "reload_onlyoffice_file: falta dependencia '$dep' (instala el paquete correspondiente)" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. El archivo DEBE existir — no editamos ni creamos archivos.
|
||||
if [[ ! -f "$file_path" ]]; then
|
||||
echo "reload_onlyoffice_file: el archivo no existe: $file_path" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local abs_path base
|
||||
abs_path=$(readlink -f -- "$file_path")
|
||||
base=$(basename -- "$abs_path")
|
||||
|
||||
# 3. Slot aislado (identico a open_onlyoffice_file).
|
||||
local oo_home="/tmp/oo_${instance}"
|
||||
local oo_run="/tmp/oo_${instance}_run"
|
||||
local oo_cfg="${oo_home}/.config"
|
||||
mkdir -p "$oo_home" "$oo_cfg" "$oo_run"
|
||||
chmod 700 "$oo_run" 2>/dev/null || true
|
||||
|
||||
local start_ts
|
||||
start_ts=$(date +%s)
|
||||
|
||||
# 4. Localizar la ventana actual del archivo por basename.
|
||||
local wid_old=""
|
||||
wid_old=$(xdotool search --name -- "$base" 2>/dev/null | head -1 || true)
|
||||
|
||||
local wid_old_hex="null"
|
||||
if [[ -n "$wid_old" ]]; then
|
||||
wid_old_hex=$(printf '0x%x' "$wid_old" 2>/dev/null || echo "$wid_old")
|
||||
|
||||
# 5. Cerrar la ventana (sin teclear en la app) y esperar a que
|
||||
# desaparezca (~10s con read -t 0.3 => ~33 iteraciones).
|
||||
wmctrl -ic "$wid_old" 2>/dev/null || true
|
||||
local g=0 gmax=33
|
||||
while [[ $g -lt $gmax ]]; do
|
||||
if ! xdotool search --name -- "$base" 2>/dev/null | grep -q .; then
|
||||
break
|
||||
fi
|
||||
read -t 0.3 _ </dev/null 2>/dev/null || true
|
||||
g=$((g + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
# 6. Relanzar con el env del slot aislado. (Si no habia ventana previa,
|
||||
# esto actua simplemente como open.)
|
||||
env HOME="$oo_home" XDG_RUNTIME_DIR="$oo_run" XDG_CONFIG_HOME="$oo_cfg" \
|
||||
setsid onlyoffice-desktopeditors "$abs_path" \
|
||||
>"/tmp/oo_${instance}.log" 2>&1 </dev/null &
|
||||
|
||||
# 7. Esperar la ventana nueva por evento (~25s => ~83 iteraciones).
|
||||
local wid_new="" i=0 max=83
|
||||
while [[ $i -lt $max ]]; do
|
||||
wid_new=$(xdotool search --name -- "$base" 2>/dev/null | head -1 || true)
|
||||
# Si hubo ventana previa, aceptar cualquier wid que aparezca (el old
|
||||
# ya se cerro; el nuevo puede reutilizar id o no). Si no la hubo,
|
||||
# cualquier wid sirve.
|
||||
[[ -n "$wid_new" ]] && break
|
||||
read -t 0.3 _ </dev/null 2>/dev/null || true
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
local now_ts elapsed
|
||||
now_ts=$(date +%s)
|
||||
elapsed=$((now_ts - start_ts))
|
||||
|
||||
if [[ -z "$wid_new" ]]; then
|
||||
printf '{"instance":"%s","file":"%s","wid_old":"%s","wid_new":null,"reopened":false,"elapsed_s":%s,"status":"timeout"}\n' \
|
||||
"$instance" "$abs_path" "$wid_old_hex" "$elapsed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local wid_new_hex
|
||||
wid_new_hex=$(printf '0x%x' "$wid_new" 2>/dev/null || echo "$wid_new")
|
||||
printf '{"instance":"%s","file":"%s","wid_old":"%s","wid_new":"%s","reopened":true,"elapsed_s":%s,"status":"reloaded"}\n' \
|
||||
"$instance" "$abs_path" "$wid_old_hex" "$wid_new_hex" "$elapsed"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Ejecutable directo o sourceado.
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
reload_onlyoffice_file "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user