|
|
|
|
@@ -9,9 +9,25 @@
|
|
|
|
|
#
|
|
|
|
|
# Uso:
|
|
|
|
|
# sudo ./install.sh --service # 24/7 headless vía systemd (recomendado en servidor)
|
|
|
|
|
# ./install.sh --user-service # 24/7 headless SIN root: unit de systemd de usuario
|
|
|
|
|
# ./install.sh --update-in-place # actualiza en su sitio, SIN privilegios
|
|
|
|
|
# ./install.sh --desktop # autostart de escritorio (requiere sesión gráfica)
|
|
|
|
|
# ./install.sh # solo instala; sin arranque automático
|
|
|
|
|
#
|
|
|
|
|
# --update-in-place actualiza una instalación EXISTENTE sin privilegios, dejando el unit de
|
|
|
|
|
# systemd como está. Se apoya en dos hechos: `install` desvincula el destino antes de crearlo (a
|
|
|
|
|
# diferencia de `cp`, que da ETXTBSY), así que reemplazar el binario solo exige escritura en el
|
|
|
|
|
# DIRECTORIO; y el unit trae Restart=always, así que basta señalizar al proceso —desde la cuenta
|
|
|
|
|
# que lo corre— para que systemd lo relevante con el binario nuevo. No toca /etc ni systemctl.
|
|
|
|
|
# Se niega a actuar si hay una restauración en curso.
|
|
|
|
|
#
|
|
|
|
|
# --user-service no necesita privilegios: instala donde apunte PREFIX (que debe ser escribible
|
|
|
|
|
# por el usuario, típicamente bajo su home), registra el unit en ~/.config/systemd/user/ y lo
|
|
|
|
|
# arranca con `systemctl --user`. Para que sobreviva al cierre de sesión intenta habilitar
|
|
|
|
|
# lingering; si el destino no lo permite, cae a una entrada @reboot en el crontab del usuario
|
|
|
|
|
# más un vigilante que lo rearranca. Es la vía cuando la cuenta SSH no es root ni tiene sudo
|
|
|
|
|
# sin contraseña: al panel le bastan entonces el usuario y la contraseña que ya tiene guardados.
|
|
|
|
|
#
|
|
|
|
|
# Opciones:
|
|
|
|
|
# --panel-env-file <ruta> # archivo KEY=valor con CLOUDRESTORE_PANEL_* que se
|
|
|
|
|
# # fusiona en config/.env y se borra al terminar.
|
|
|
|
|
@@ -29,6 +45,8 @@ PANEL_ENV_FILE=""
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
--service) MODE="service"; shift ;;
|
|
|
|
|
--user-service) MODE="user-service"; shift ;;
|
|
|
|
|
--update-in-place) MODE="update-in-place"; shift ;;
|
|
|
|
|
--desktop) MODE="desktop"; shift ;;
|
|
|
|
|
--panel-env-file)
|
|
|
|
|
[[ $# -ge 2 ]] || { echo "ERROR: --panel-env-file requiere una ruta" >&2; exit 2; }
|
|
|
|
|
@@ -44,6 +62,14 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
PREFIX="${PREFIX:-/opt/cloudrestoreas}"
|
|
|
|
|
BIN_NAME="CloudRestoreAS"
|
|
|
|
|
UNIT_NAME="cloudrestoreas.service"
|
|
|
|
|
# Patrón para localizar el agente vivo. El ancla ^ NO es opcional: sin ella, la propia
|
|
|
|
|
# línea de cron del vigilante contiene esta ruta, así que el `sh -c` que la ejecuta hace
|
|
|
|
|
# match consigo mismo, el vigilante cree que el agente corre y no lo rearranca nunca.
|
|
|
|
|
PGREP_PAT="^$PREFIX/$BIN_NAME"
|
|
|
|
|
# Usuario del servicio. Se resuelve AQUÍ y no dentro del case de --service porque el chown de
|
|
|
|
|
# propiedad tiene que correr antes, justo después del bootstrap. Precedencia: la variable
|
|
|
|
|
# explícita, luego quien invocó el sudo, luego el usuario efectivo.
|
|
|
|
|
SERVICE_USER="${SERVICE_USER:-${SUDO_USER:-$(id -un)}}"
|
|
|
|
|
|
|
|
|
|
# Localizar el binario: dist/CloudRestoreAS, dist/CloudRestoreAS-linux, o junto al script.
|
|
|
|
|
SRC=""
|
|
|
|
|
@@ -67,12 +93,38 @@ echo " Modo : $MODE"
|
|
|
|
|
echo "==============================================="
|
|
|
|
|
|
|
|
|
|
# --- Detener el servicio si está corriendo ------------------------------------
|
|
|
|
|
# Un ELF en ejecución no se puede sobrescribir (ETXTBSY), así que una actualización
|
|
|
|
|
# tiene que detenerlo antes de reemplazar el binario. Se recuerda si estaba activo
|
|
|
|
|
# para volver a levantarlo al final incluso en los modos que no tocan systemd.
|
|
|
|
|
# Ojo con la razón, que estuvo mal escrita mucho tiempo: `install` NO sufre ETXTBSY. A
|
|
|
|
|
# diferencia de `cp` —que abre con O_TRUNC—, `install` desvincula el destino antes de crearlo
|
|
|
|
|
# (coreutils fija unlink_dest_before_opening), que es justo por lo que `make install` funciona
|
|
|
|
|
# sobre binarios en ejecución. Comprobado: `cp` sobre un ELF corriendo da "Text file busy" y
|
|
|
|
|
# `install` no. Lo que hace falta para reemplazarlo es permiso de escritura en el DIRECTORIO.
|
|
|
|
|
#
|
|
|
|
|
# Se detiene igual en los modos de servicio porque conviene un apagado ordenado, no porque la
|
|
|
|
|
# copia lo exija. Se recuerda si estaba activo para volver a levantarlo al final.
|
|
|
|
|
WAS_ACTIVE=0
|
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
|
|
|
if systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then
|
|
|
|
|
# En modo usuario el unit vive en la instancia de systemd del propio usuario, que necesita
|
|
|
|
|
# XDG_RUNTIME_DIR: un `exec` de SSH no es una sesión de login y no siempre lo trae.
|
|
|
|
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
|
|
|
|
SYSTEMCTL_USER=(systemctl --user)
|
|
|
|
|
# En modo en-sitio NO se detiene nada: `install` reemplaza el binario con el proceso corriendo, y
|
|
|
|
|
# matarlo antes de tener el binario nuevo abriría una ventana en la que systemd relevanta el VIEJO.
|
|
|
|
|
if [[ "$MODE" != "update-in-place" ]] && command -v systemctl >/dev/null 2>&1; then
|
|
|
|
|
if [[ "$MODE" == "user-service" ]]; then
|
|
|
|
|
if "${SYSTEMCTL_USER[@]}" is-active --quiet "$UNIT_NAME" 2>/dev/null; then
|
|
|
|
|
WAS_ACTIVE=1
|
|
|
|
|
echo "Deteniendo $UNIT_NAME (usuario) para reemplazar el binario..."
|
|
|
|
|
"${SYSTEMCTL_USER[@]}" stop "$UNIT_NAME" >/dev/null 2>&1 || \
|
|
|
|
|
echo "AVISO: no se pudo detener $UNIT_NAME; la copia puede fallar." >&2
|
|
|
|
|
fi
|
|
|
|
|
# Sin systemd de usuario disponible el arranque pudo haber quedado por cron: el binario
|
|
|
|
|
# sigue vivo y sobrescribirlo daría ETXTBSY, así que se termina igual.
|
|
|
|
|
if [[ "$WAS_ACTIVE" -eq 0 ]] && pgrep -f "$PGREP_PAT" >/dev/null 2>&1; then
|
|
|
|
|
WAS_ACTIVE=1
|
|
|
|
|
echo "Terminando el proceso en curso para reemplazar el binario..."
|
|
|
|
|
pkill -f "$PGREP_PAT" >/dev/null 2>&1 || true
|
|
|
|
|
sleep 2
|
|
|
|
|
fi
|
|
|
|
|
elif systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then
|
|
|
|
|
WAS_ACTIVE=1
|
|
|
|
|
echo "Deteniendo $UNIT_NAME para reemplazar el binario..."
|
|
|
|
|
systemctl stop "$UNIT_NAME" >/dev/null 2>&1 || \
|
|
|
|
|
@@ -80,11 +132,40 @@ if command -v systemctl >/dev/null 2>&1; then
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Una restauración en curso no se interrumpe. El agente no atiende SIGTERM, así que matarlo a
|
|
|
|
|
# media restauración deja el job atascado —ese ZIP queda vetado en cada escaneo posterior— y puede
|
|
|
|
|
# dejar la base en SINGLE_USER. Cualquier subcarpeta de Temp/ es un job en vuelo.
|
|
|
|
|
# Código 75 (EX_TEMPFAIL) y no 1: le dice al panel "reintenta luego", no "falló la instalación".
|
|
|
|
|
if [[ "$MODE" == "update-in-place" ]] && [[ -d "$PREFIX/Temp" ]]; then
|
|
|
|
|
if [[ -n "$(ls -A "$PREFIX/Temp" 2>/dev/null)" ]]; then
|
|
|
|
|
echo "ERROR: hay una restauración en curso ($PREFIX/Temp no está vacío)." >&2
|
|
|
|
|
echo " No se actualiza para no dejarla a medias. Reintenta cuando termine." >&2
|
|
|
|
|
exit 75
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Respaldo para poder volver atrás. Una actualización en sitio reemplaza el binario de un
|
|
|
|
|
# servidor en producción sin red de seguridad: si el nuevo no arranca, sin esto el servidor queda
|
|
|
|
|
# sin agente y sin forma de recuperarlo salvo entrando a mano.
|
|
|
|
|
BACKUP=""
|
|
|
|
|
if [[ "$MODE" == "update-in-place" && -f "$PREFIX/$BIN_NAME" ]]; then
|
|
|
|
|
BACKUP="$PREFIX/.$BIN_NAME.prev"
|
|
|
|
|
if ! cp -p "$PREFIX/$BIN_NAME" "$BACKUP"; then
|
|
|
|
|
echo "ERROR: no se pudo respaldar el binario actual en $BACKUP" >&2
|
|
|
|
|
echo " Se aborta: actualizar sin poder revertir no es aceptable." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "Respaldo del binario actual en $BACKUP"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
mkdir -p "$PREFIX"
|
|
|
|
|
if ! install -m 0755 "$SRC" "$PREFIX/$BIN_NAME"; then
|
|
|
|
|
echo "ERROR: no se pudo instalar el binario en $PREFIX/$BIN_NAME" >&2
|
|
|
|
|
echo " Si el archivo está en uso, detén el proceso y reintenta:" >&2
|
|
|
|
|
echo " sudo systemctl stop $UNIT_NAME # o mata el proceso de escritorio" >&2
|
|
|
|
|
# `install` desvincula el destino antes de crearlo, así que un binario EN USO no es el
|
|
|
|
|
# problema (eso es cosa de `cp`). Lo que falta casi siempre es permiso en el DIRECTORIO.
|
|
|
|
|
echo " Se necesita permiso de escritura en el directorio $PREFIX." >&2
|
|
|
|
|
echo " Dueño actual: $(stat -c '%U:%G %a' "$PREFIX" 2>/dev/null || echo 'desconocido')" >&2
|
|
|
|
|
echo " Usuario actual: $(id -un)" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "Binario instalado en $PREFIX/$BIN_NAME"
|
|
|
|
|
@@ -93,8 +174,16 @@ echo "Binario instalado en $PREFIX/$BIN_NAME"
|
|
|
|
|
# al inicio de su arranque. Se corre una vez en modo offscreen (sin display) y se
|
|
|
|
|
# corta con timeout; así el usuario ya puede editar config/.env antes de habilitar
|
|
|
|
|
# el servicio.
|
|
|
|
|
# En una actualización en sitio se OMITE: el binario hace ensure_runtime_layout() en cada arranque,
|
|
|
|
|
# así que es redundante, y correr una segunda instancia junto a la viva es peligroso —al arrancar,
|
|
|
|
|
# el motor purga todas las subcarpetas de Temp, que son de la instancia en curso—.
|
|
|
|
|
if [[ "$MODE" != "update-in-place" ]]; then
|
|
|
|
|
echo "Inicializando config/ (bootstrap)..."
|
|
|
|
|
( cd "$PREFIX" && QT_QPA_PLATFORM=offscreen timeout 20 "$PREFIX/$BIN_NAME" --headless >/dev/null 2>&1 || true )
|
|
|
|
|
# `</dev/null` no es cosmético: es el único hijo que heredaría el stdin del canal SSH cuando el
|
|
|
|
|
# instalador corre en remoto. Cerrárselo hace estructural —y no accidental— que nada de lo que
|
|
|
|
|
# venga por ese canal pueda ser consumido aquí.
|
|
|
|
|
( cd "$PREFIX" && QT_QPA_PLATFORM=offscreen timeout 20 "$PREFIX/$BIN_NAME" --headless </dev/null >/dev/null 2>&1 || true )
|
|
|
|
|
fi
|
|
|
|
|
if [[ -f "$PREFIX/config/.env" ]]; then
|
|
|
|
|
echo "config/.env creado."
|
|
|
|
|
else
|
|
|
|
|
@@ -184,9 +273,51 @@ if [[ -n "$PANEL_ENV_FILE" ]]; then
|
|
|
|
|
rm -f "$PANEL_ENV_FILE"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# --- Propiedad del árbol -------------------------------------------------------
|
|
|
|
|
# Corriendo con sudo, el bootstrap crea config/, config/data/app.db, config/logs/, Entrada/,
|
|
|
|
|
# Procesados/, Fallados/ y Temp/ como ROOT, y la siembra escribe config/.env en 0600 de root.
|
|
|
|
|
# Pero el unit se registra con User=$SERVICE_USER, así que el agente arrancaría sin poder leer su
|
|
|
|
|
# propia configuración ni escribir su base local: falla en bucle, y la sonda del panel lo pinta
|
|
|
|
|
# verde porque comprueba existencia y no lectura. Devolver la propiedad es lo que cierra eso.
|
|
|
|
|
#
|
|
|
|
|
# Solo el usuario, no el grupo: basta para que el agente escriba y evita sorpresas con el grupo
|
|
|
|
|
# primario del destino. Y `chown` no toca los modos, así que el 0600 del .env sobrevive.
|
|
|
|
|
chown_tree_is_safe() {
|
|
|
|
|
local path="$1"
|
|
|
|
|
# Un chown -R sobre una ruta de sistema sería catastrófico. El panel ya valida la ruta, pero
|
|
|
|
|
# este script también se corre a mano.
|
|
|
|
|
case "$path" in
|
|
|
|
|
/|/opt|/usr|/etc|/var|/home|/srv|/root|/bin|/sbin|/lib|/lib64|/tmp) return 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
# Al menos dos componentes: /algo/otro.
|
|
|
|
|
case "${path#/}" in
|
|
|
|
|
*/*) return 0 ;;
|
|
|
|
|
*) return 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if [[ "$MODE" != "update-in-place" && "$(id -u)" -eq 0 && "$SERVICE_USER" != "root" ]]; then
|
|
|
|
|
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
|
|
|
|
echo "AVISO: el usuario '$SERVICE_USER' no existe en este servidor; no se cambia la" >&2
|
|
|
|
|
echo " propiedad de $PREFIX. Revisa SERVICE_USER." >&2
|
|
|
|
|
elif ! chown_tree_is_safe "$PREFIX"; then
|
|
|
|
|
echo "AVISO: $PREFIX es una ruta de sistema; no se hace chown -R sobre ella." >&2
|
|
|
|
|
echo " Ajústala a mano si el servicio corre como '$SERVICE_USER'." >&2
|
|
|
|
|
elif chown -R "$SERVICE_USER" "$PREFIX"; then
|
|
|
|
|
echo "Propiedad de $PREFIX asignada a $SERVICE_USER."
|
|
|
|
|
else
|
|
|
|
|
# No se asume que un comando de endurecimiento tuvo éxito: si falla, el agente no arranca
|
|
|
|
|
# y es mejor decirlo ahora que dejar un servicio reiniciándose en silencio.
|
|
|
|
|
echo "ERROR: no se pudo asignar la propiedad de $PREFIX a $SERVICE_USER." >&2
|
|
|
|
|
echo " El servicio corre como ese usuario y no podría leer config/.env ni escribir" >&2
|
|
|
|
|
echo " su base local. Corrígelo antes de arrancarlo:" >&2
|
|
|
|
|
echo " chown -R $SERVICE_USER $PREFIX" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
case "$MODE" in
|
|
|
|
|
service)
|
|
|
|
|
SERVICE_USER="${SERVICE_USER:-${SUDO_USER:-$(id -un)}}"
|
|
|
|
|
UNIT_SRC="$SCRIPT_DIR/packaging/linux/$UNIT_NAME"
|
|
|
|
|
UNIT_DST="/etc/systemd/system/$UNIT_NAME"
|
|
|
|
|
if [[ ! -w "$(dirname "$UNIT_DST")" ]]; then
|
|
|
|
|
@@ -203,6 +334,143 @@ case "$MODE" in
|
|
|
|
|
echo " Estado : systemctl status cloudrestoreas"
|
|
|
|
|
echo " Logs : journalctl -u cloudrestoreas -f"
|
|
|
|
|
;;
|
|
|
|
|
user-service)
|
|
|
|
|
# Sin root: unit en el home y arranque con la instancia de systemd del propio usuario.
|
|
|
|
|
# Requisito previo: PREFIX escribible por este usuario (el panel lo apunta a su home).
|
|
|
|
|
UNIT_SRC="$SCRIPT_DIR/packaging/linux/cloudrestoreas-user.service"
|
|
|
|
|
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
|
|
|
|
UNIT_DST="$UNIT_DIR/$UNIT_NAME"
|
|
|
|
|
if [[ ! -f "$UNIT_SRC" ]]; then
|
|
|
|
|
echo "ERROR: falta la plantilla del unit de usuario ($UNIT_SRC)" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
mkdir -p "$UNIT_DIR"
|
|
|
|
|
# Sin __USER__: un unit de usuario no admite User=, ya corre como su dueño.
|
|
|
|
|
sed -e "s|__WORKDIR__|$PREFIX|g" \
|
|
|
|
|
-e "s|__EXEC__|$PREFIX/$BIN_NAME|g" \
|
|
|
|
|
"$UNIT_SRC" > "$UNIT_DST"
|
|
|
|
|
|
|
|
|
|
STARTED=""
|
|
|
|
|
if "${SYSTEMCTL_USER[@]}" daemon-reload >/dev/null 2>&1 && \
|
|
|
|
|
"${SYSTEMCTL_USER[@]}" enable --now "$UNIT_NAME" >/dev/null 2>&1; then
|
|
|
|
|
STARTED="systemd-usuario"
|
|
|
|
|
# Sin lingering, la instancia de usuario muere al cerrar la última sesión y con ella el
|
|
|
|
|
# restaurador. Muchos polkit permiten habilitarlo para uno mismo; si no, no es fatal:
|
|
|
|
|
# se complementa con cron más abajo.
|
|
|
|
|
if loginctl enable-linger "$(id -un)" >/dev/null 2>&1; then
|
|
|
|
|
echo "Lingering habilitado: el servicio sobrevive al cierre de sesión."
|
|
|
|
|
else
|
|
|
|
|
echo "AVISO: no se pudo habilitar lingering; se agrega respaldo por cron." >&2
|
|
|
|
|
STARTED="systemd-usuario+cron"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
echo "AVISO: systemd de usuario no disponible; se usará cron." >&2
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Respaldo (o sustituto) por cron: @reboot para el arranque y un vigilante cada 5 min que
|
|
|
|
|
# cubre lo que en systemd hace Restart=always. El crontab del usuario no requiere root.
|
|
|
|
|
if [[ "$STARTED" != "systemd-usuario" ]]; then
|
|
|
|
|
if command -v crontab >/dev/null 2>&1; then
|
|
|
|
|
CRON_CMD="cd $PREFIX && QT_QPA_PLATFORM=offscreen $PREFIX/$BIN_NAME --start-engine --headless >/dev/null 2>&1 &"
|
|
|
|
|
CRON_TAG="# cloudrestoreas-autostart"
|
|
|
|
|
# Se reescribe el crontab completo filtrando las entradas propias anteriores, para que
|
|
|
|
|
# reinstalar no acumule duplicados.
|
|
|
|
|
{
|
|
|
|
|
crontab -l 2>/dev/null | grep -v "$CRON_TAG" || true
|
|
|
|
|
echo "@reboot $CRON_CMD $CRON_TAG"
|
|
|
|
|
echo "*/5 * * * * pgrep -f '$PGREP_PAT' >/dev/null || ($CRON_CMD) $CRON_TAG"
|
|
|
|
|
} | crontab -
|
|
|
|
|
STARTED="${STARTED:+$STARTED+}cron"
|
|
|
|
|
# El vigilante tarda hasta 5 min en actuar; se arranca ya para no dejar el hueco.
|
|
|
|
|
if ! pgrep -f "$PGREP_PAT" >/dev/null 2>&1; then
|
|
|
|
|
( cd "$PREFIX" && QT_QPA_PLATFORM=offscreen "$PREFIX/$BIN_NAME" --start-engine --headless </dev/null >/dev/null 2>&1 & )
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
echo "ERROR: sin systemd de usuario y sin crontab no hay forma de dejarlo arrancado." >&2
|
|
|
|
|
echo " Instala cron, habilita lingering, o usa --service con privilegios." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo "Servicio de usuario instalado y arrancado (mecanismo: $STARTED)."
|
|
|
|
|
echo " Estado : systemctl --user status cloudrestoreas"
|
|
|
|
|
echo " Logs : journalctl --user -u cloudrestoreas -f"
|
|
|
|
|
;;
|
|
|
|
|
update-in-place)
|
|
|
|
|
# El unit ya está registrado y no cambia: no se toca /etc, ni daemon-reload, ni enable.
|
|
|
|
|
# El binario nuevo ya está en su sitio; falta que el proceso lo tome.
|
|
|
|
|
if ! pgrep -f "$PGREP_PAT" >/dev/null 2>&1; then
|
|
|
|
|
echo "El agente no estaba corriendo; queda actualizado y systemd lo levantará."
|
|
|
|
|
else
|
|
|
|
|
# Segundo chequeo, inmediatamente antes de señalizar. El primero fue antes de copiar el
|
|
|
|
|
# binario y de sembrar el .env; en esos segundos pudo entrar una restauración, y matarla
|
|
|
|
|
# deja el respaldo vetado para siempre. Aquí ya no hay 270 MB de por medio: es barato.
|
|
|
|
|
if [[ -d "$PREFIX/Temp" ]] && [[ -n "$(ls -A "$PREFIX/Temp" 2>/dev/null)" ]]; then
|
|
|
|
|
echo "ERROR: entró una restauración mientras se actualizaba." >&2
|
|
|
|
|
echo " El binario nuevo YA está instalado y se activará en el próximo reinicio" >&2
|
|
|
|
|
echo " del agente; no se fuerza ahora para no interrumpirla." >&2
|
|
|
|
|
exit 75
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# `|| true` obligatorio: con pipefail, un pgrep sin resultados hace fallar la
|
|
|
|
|
# sustitución y `set -e` abortaría el script justo en el caso que hay que manejar.
|
|
|
|
|
OLD_PIDS="$(pgrep -f "$PGREP_PAT" | tr '\n' ' ' || true)"
|
|
|
|
|
echo "Señalizando al agente (PIDs: $OLD_PIDS) para que systemd lo relevante..."
|
|
|
|
|
# SIGTERM y no SIGKILL: si algún día el agente aprende a atender la señal, este camino ya
|
|
|
|
|
# le da la oportunidad de cerrar limpio.
|
|
|
|
|
pkill -TERM -f "$PGREP_PAT" >/dev/null 2>&1 || true
|
|
|
|
|
|
|
|
|
|
# Restart=always + RestartSec=5. Se espera con margen y se confirma que volvió: si el unit
|
|
|
|
|
# no tuviera Restart, matarlo lo dejaría muerto y eso NO puede pasar por bueno.
|
|
|
|
|
RESTARTED=0
|
|
|
|
|
for _ in $(seq 1 15); do
|
|
|
|
|
sleep 2
|
|
|
|
|
NEW_PID="$(pgrep -f "$PGREP_PAT" | head -1 || true)"
|
|
|
|
|
if [[ -n "$NEW_PID" ]] && [[ " $OLD_PIDS " != *" $NEW_PID "* ]]; then
|
|
|
|
|
RESTARTED=1
|
|
|
|
|
echo "Agente relevantado por systemd (PID $NEW_PID)."
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "$RESTARTED" -eq 0 ]]; then
|
|
|
|
|
# No volvió. Dejar el servidor sin agente no es una opción: se revierte al binario que
|
|
|
|
|
# sí funcionaba y se le da otra oportunidad a systemd.
|
|
|
|
|
echo "AVISO: el agente no volvió tras la señal; revirtiendo al binario anterior..." >&2
|
|
|
|
|
UNIT_STATE="$(systemctl is-active "$UNIT_NAME" 2>/dev/null || true)"
|
|
|
|
|
UNIT_RESTART="$(systemctl show -p Restart --value "$UNIT_NAME" 2>/dev/null || true)"
|
|
|
|
|
|
|
|
|
|
REVERTED=0
|
|
|
|
|
if [[ -n "$BACKUP" && -f "$BACKUP" ]]; then
|
|
|
|
|
if install -m 0755 "$BACKUP" "$PREFIX/$BIN_NAME"; then
|
|
|
|
|
for _ in $(seq 1 10); do
|
|
|
|
|
sleep 2
|
|
|
|
|
if pgrep -f "$PGREP_PAT" >/dev/null 2>&1; then REVERTED=1; break; fi
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "ERROR: la actualización no dejó al agente corriendo." >&2
|
|
|
|
|
echo " Estado del unit: ${UNIT_STATE:-desconocido} (Restart=${UNIT_RESTART:-desconocido})" >&2
|
|
|
|
|
if [[ "${UNIT_RESTART}" != "always" ]]; then
|
|
|
|
|
echo " El unit no tiene Restart=always: nadie lo relevanta al terminar." >&2
|
|
|
|
|
elif [[ "$UNIT_STATE" == "failed" ]]; then
|
|
|
|
|
echo " systemd lo marcó como failed; puede haber agotado StartLimitBurst." >&2
|
|
|
|
|
echo " Reintentar con: systemctl reset-failed $UNIT_NAME && systemctl start $UNIT_NAME" >&2
|
|
|
|
|
fi
|
|
|
|
|
if [[ "$REVERTED" -eq 1 ]]; then
|
|
|
|
|
echo " REVERTIDO: el binario anterior está corriendo de nuevo. El servidor" >&2
|
|
|
|
|
echo " quedó como estaba; la versión nueva NO se aplicó." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo " NO se pudo revertir. El binario nuevo está en $PREFIX/$BIN_NAME y el" >&2
|
|
|
|
|
echo " anterior en ${BACKUP:-(sin respaldo)}. Hay que revisar el servidor a mano." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Solo tras confirmar que el agente nuevo corre se descarta el respaldo.
|
|
|
|
|
[[ -n "$BACKUP" && -f "$BACKUP" ]] && rm -f "$BACKUP" || true
|
|
|
|
|
fi
|
|
|
|
|
;;
|
|
|
|
|
desktop)
|
|
|
|
|
echo "Modo escritorio: la app registra su autostart .desktop al iniciarse."
|
|
|
|
|
echo "Ejecuta '$PREFIX/$BIN_NAME' en tu sesión gráfica."
|
|
|
|
|
@@ -215,7 +483,8 @@ esac
|
|
|
|
|
|
|
|
|
|
# Si se detuvo un servicio que estaba activo y el modo elegido no lo relevanta, se
|
|
|
|
|
# restaura: una actualización no debe dejar el restaurador apagado en silencio.
|
|
|
|
|
if [[ "$WAS_ACTIVE" -eq 1 && "$MODE" != "service" ]]; then
|
|
|
|
|
if [[ "$WAS_ACTIVE" -eq 1 && "$MODE" != "service" && "$MODE" != "user-service" \
|
|
|
|
|
&& "$MODE" != "update-in-place" ]]; then
|
|
|
|
|
echo "Reiniciando $UNIT_NAME (estaba activo antes de la actualización)..."
|
|
|
|
|
systemctl start "$UNIT_NAME" >/dev/null 2>&1 || \
|
|
|
|
|
echo "AVISO: no se pudo reiniciar $UNIT_NAME; hazlo a mano." >&2
|
|
|
|
|
@@ -224,4 +493,5 @@ fi
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Siguiente paso: edita $PREFIX/config/.env (CLOUDRESTORE_PANEL_*) y reinicia."
|
|
|
|
|
[[ "$MODE" == "service" ]] && echo " Tras editar: sudo systemctl restart cloudrestoreas"
|
|
|
|
|
[[ "$MODE" == "user-service" ]] && echo " Tras editar: systemctl --user restart cloudrestoreas"
|
|
|
|
|
echo "Listo."
|
|
|
|
|
|