Compare commits
3 Commits
c3f1d70e23
...
17c0dea4bf
| Author | SHA1 | Date | |
|---|---|---|---|
| 17c0dea4bf | |||
| fd077dad42 | |||
| ce38956e3e |
103
install.sh
103
install.sh
@@ -9,9 +9,17 @@
|
||||
#
|
||||
# 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 --desktop # autostart de escritorio (requiere sesión gráfica)
|
||||
# ./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:
|
||||
# --panel-env-file <ruta> # archivo KEY=valor con CLOUDRESTORE_PANEL_* que se
|
||||
# # fusiona en config/.env y se borra al terminar.
|
||||
@@ -29,6 +37,7 @@ PANEL_ENV_FILE=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--service) MODE="service"; shift ;;
|
||||
--user-service) MODE="user-service"; shift ;;
|
||||
--desktop) MODE="desktop"; shift ;;
|
||||
--panel-env-file)
|
||||
[[ $# -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}"
|
||||
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"
|
||||
|
||||
# Localizar el binario: dist/CloudRestoreAS, dist/CloudRestoreAS-linux, o junto al script.
|
||||
SRC=""
|
||||
@@ -71,8 +84,27 @@ echo "==============================================="
|
||||
# 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.
|
||||
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 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
|
||||
echo "Deteniendo $UNIT_NAME para reemplazar el binario..."
|
||||
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
|
||||
# el servicio.
|
||||
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
|
||||
echo "config/.env creado."
|
||||
else
|
||||
@@ -203,6 +238,67 @@ 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"
|
||||
;;
|
||||
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 +311,7 @@ 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" ]]; 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 +320,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."
|
||||
|
||||
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