Compare commits
3 Commits
c3f1d70e23
...
17c0dea4bf
| Author | SHA1 | Date | |
|---|---|---|---|
| 17c0dea4bf | |||
| fd077dad42 | |||
| ce38956e3e |
103
install.sh
103
install.sh
@@ -9,9 +9,17 @@
|
|||||||
#
|
#
|
||||||
# Uso:
|
# Uso:
|
||||||
# sudo ./install.sh --service # 24/7 headless vía systemd (recomendado en servidor)
|
# 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 --desktop # autostart de escritorio (requiere sesión gráfica)
|
# ./install.sh --desktop # autostart de escritorio (requiere sesión gráfica)
|
||||||
# ./install.sh # solo instala; sin arranque automático
|
# ./install.sh # solo instala; sin arranque automático
|
||||||
#
|
#
|
||||||
|
# --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:
|
# Opciones:
|
||||||
# --panel-env-file <ruta> # archivo KEY=valor con CLOUDRESTORE_PANEL_* que se
|
# --panel-env-file <ruta> # archivo KEY=valor con CLOUDRESTORE_PANEL_* que se
|
||||||
# # fusiona en config/.env y se borra al terminar.
|
# # fusiona en config/.env y se borra al terminar.
|
||||||
@@ -29,6 +37,7 @@ PANEL_ENV_FILE=""
|
|||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--service) MODE="service"; shift ;;
|
--service) MODE="service"; shift ;;
|
||||||
|
--user-service) MODE="user-service"; shift ;;
|
||||||
--desktop) MODE="desktop"; shift ;;
|
--desktop) MODE="desktop"; shift ;;
|
||||||
--panel-env-file)
|
--panel-env-file)
|
||||||
[[ $# -ge 2 ]] || { echo "ERROR: --panel-env-file requiere una ruta" >&2; exit 2; }
|
[[ $# -ge 2 ]] || { echo "ERROR: --panel-env-file requiere una ruta" >&2; exit 2; }
|
||||||
@@ -44,6 +53,10 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|||||||
PREFIX="${PREFIX:-/opt/cloudrestoreas}"
|
PREFIX="${PREFIX:-/opt/cloudrestoreas}"
|
||||||
BIN_NAME="CloudRestoreAS"
|
BIN_NAME="CloudRestoreAS"
|
||||||
UNIT_NAME="cloudrestoreas.service"
|
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"
|
||||||
|
|
||||||
# Localizar el binario: dist/CloudRestoreAS, dist/CloudRestoreAS-linux, o junto al script.
|
# Localizar el binario: dist/CloudRestoreAS, dist/CloudRestoreAS-linux, o junto al script.
|
||||||
SRC=""
|
SRC=""
|
||||||
@@ -71,8 +84,27 @@ echo "==============================================="
|
|||||||
# tiene que detenerlo antes de reemplazar el binario. Se recuerda si estaba activo
|
# 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.
|
# para volver a levantarlo al final incluso en los modos que no tocan systemd.
|
||||||
WAS_ACTIVE=0
|
WAS_ACTIVE=0
|
||||||
|
# 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)
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
if systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; 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
|
WAS_ACTIVE=1
|
||||||
echo "Deteniendo $UNIT_NAME para reemplazar el binario..."
|
echo "Deteniendo $UNIT_NAME para reemplazar el binario..."
|
||||||
systemctl stop "$UNIT_NAME" >/dev/null 2>&1 || \
|
systemctl stop "$UNIT_NAME" >/dev/null 2>&1 || \
|
||||||
@@ -94,7 +126,10 @@ echo "Binario instalado en $PREFIX/$BIN_NAME"
|
|||||||
# corta con timeout; así el usuario ya puede editar config/.env antes de habilitar
|
# corta con timeout; así el usuario ya puede editar config/.env antes de habilitar
|
||||||
# el servicio.
|
# el servicio.
|
||||||
echo "Inicializando config/ (bootstrap)..."
|
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 )
|
||||||
if [[ -f "$PREFIX/config/.env" ]]; then
|
if [[ -f "$PREFIX/config/.env" ]]; then
|
||||||
echo "config/.env creado."
|
echo "config/.env creado."
|
||||||
else
|
else
|
||||||
@@ -203,6 +238,67 @@ case "$MODE" in
|
|||||||
echo " Estado : systemctl status cloudrestoreas"
|
echo " Estado : systemctl status cloudrestoreas"
|
||||||
echo " Logs : journalctl -u cloudrestoreas -f"
|
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"
|
||||||
|
;;
|
||||||
desktop)
|
desktop)
|
||||||
echo "Modo escritorio: la app registra su autostart .desktop al iniciarse."
|
echo "Modo escritorio: la app registra su autostart .desktop al iniciarse."
|
||||||
echo "Ejecuta '$PREFIX/$BIN_NAME' en tu sesión gráfica."
|
echo "Ejecuta '$PREFIX/$BIN_NAME' en tu sesión gráfica."
|
||||||
@@ -215,7 +311,7 @@ esac
|
|||||||
|
|
||||||
# Si se detuvo un servicio que estaba activo y el modo elegido no lo relevanta, se
|
# 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.
|
# 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" ]]; then
|
||||||
echo "Reiniciando $UNIT_NAME (estaba activo antes de la actualización)..."
|
echo "Reiniciando $UNIT_NAME (estaba activo antes de la actualización)..."
|
||||||
systemctl start "$UNIT_NAME" >/dev/null 2>&1 || \
|
systemctl start "$UNIT_NAME" >/dev/null 2>&1 || \
|
||||||
echo "AVISO: no se pudo reiniciar $UNIT_NAME; hazlo a mano." >&2
|
echo "AVISO: no se pudo reiniciar $UNIT_NAME; hazlo a mano." >&2
|
||||||
@@ -224,4 +320,5 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Siguiente paso: edita $PREFIX/config/.env (CLOUDRESTORE_PANEL_*) y reinicia."
|
echo "Siguiente paso: edita $PREFIX/config/.env (CLOUDRESTORE_PANEL_*) y reinicia."
|
||||||
[[ "$MODE" == "service" ]] && echo " Tras editar: sudo systemctl restart cloudrestoreas"
|
[[ "$MODE" == "service" ]] && echo " Tras editar: sudo systemctl restart cloudrestoreas"
|
||||||
|
[[ "$MODE" == "user-service" ]] && echo " Tras editar: systemctl --user restart cloudrestoreas"
|
||||||
echo "Listo."
|
echo "Listo."
|
||||||
|
|||||||
22
packaging/linux/cloudrestoreas-user.service
Normal file
22
packaging/linux/cloudrestoreas-user.service
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=CloudRestoreAS - Restauración automática SQL Server (servicio de usuario)
|
||||||
|
# Sin After=mssql-server.service: un unit de usuario no puede ordenarse respecto de units del
|
||||||
|
# sistema. El agente reintenta la conexión a SQL Server, así que arrancar antes no lo rompe.
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
# Sin User=: en un unit de usuario lo prohíbe systemd (ya corre como el dueño de la sesión).
|
||||||
|
# Los marcadores de abajo los sustituye install.sh al instalar; no nombrarlos en los comentarios,
|
||||||
|
# porque el sed los reemplazaría aquí también y dejaría rutas absolutas en un texto sin sentido.
|
||||||
|
WorkingDirectory=__WORKDIR__
|
||||||
|
# Servidor headless: plataforma Qt offscreen (sin display) y motor auto-inicio.
|
||||||
|
Environment=QT_QPA_PLATFORM=offscreen
|
||||||
|
ExecStart=__EXEC__ --start-engine --headless
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
# El binario es autocontenido (Qt, ODBC, 7-Zip embebidos): no necesita libs del sistema.
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
# default.target, no multi-user.target: en la instancia de usuario ese es el objetivo de arranque.
|
||||||
|
WantedBy=default.target
|
||||||
Reference in New Issue
Block a user