diff --git a/script/install/install_ntfy.sh b/script/install/install_ntfy.sh new file mode 100755 index 0000000..4cac17b --- /dev/null +++ b/script/install/install_ntfy.sh @@ -0,0 +1,303 @@ +#!/usr/bin/env bash +# Install NTFY self-hosted push notification server +# https://github.com/binwiederhier/ntfy +# Supports: Ubuntu 20.04+, Debian 11+, Arch Linux (and derivatives) +# +# Usage: +# sudo bash install_ntfy.sh +# sudo NTFY_PORT=8080 NTFY_BASE_URL=http://192.168.1.100:8080 bash install_ntfy.sh + +set -e + +NTFY_PORT="${NTFY_PORT:-8080}" +NTFY_BASE_URL="${NTFY_BASE_URL:-http://localhost:${NTFY_PORT}}" +INSTALL_DIR="/usr/local/bin" +CONFIG_DIR="/etc/ntfy" +CACHE_DIR="/var/cache/ntfy" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +log() { echo "[NTFY] $*"; } +die() { echo "[NTFY] ERROR: $*" >&2; exit 1; } + +check_root() { + if [ "$EUID" -ne 0 ]; then + die "This script must be run as root. Use: sudo bash $0" + fi +} + +detect_os() { + if [ -f /etc/os-release ]; then + # shellcheck disable=SC1091 + . /etc/os-release + OS="${ID}" + OS_LIKE="${ID_LIKE:-}" + else + die "Cannot detect OS (no /etc/os-release)" + fi + log "Detected OS: ${OS}" +} + +is_ubuntu_like() { + case "$OS" in + ubuntu|debian|linuxmint|pop|elementary|raspbian) return 0 ;; + *) echo "$OS_LIKE" | grep -qE "debian|ubuntu" && return 0 || return 1 ;; + esac +} + +is_arch_like() { + case "$OS" in + arch|manjaro|endeavouros|artix|garuda) return 0 ;; + *) echo "$OS_LIKE" | grep -q "arch" && return 0 || return 1 ;; + esac +} + +# --------------------------------------------------------------------------- +# Get latest ntfy version from GitHub +# --------------------------------------------------------------------------- + +get_latest_version() { + local ver + ver=$(curl -fsSL \ + "https://api.github.com/repos/binwiederhier/ntfy/releases/latest" \ + 2>/dev/null | grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/' | head -1) + if [ -z "$ver" ]; then + # Fallback to a known stable version if GitHub API is unreachable + ver="2.11.0" + log "Warning: could not reach GitHub API, falling back to v${ver}" + fi + echo "$ver" +} + +# --------------------------------------------------------------------------- +# Installation methods +# --------------------------------------------------------------------------- + +install_deb() { + local version="$1" + local arch + arch=$(dpkg --print-architecture) # amd64, arm64, armhf, ... + + local deb_file="ntfy_${version}_linux_${arch}.deb" + local url="https://github.com/binwiederhier/ntfy/releases/download/v${version}/${deb_file}" + local tmp + tmp=$(mktemp -d) + + log "Downloading ${deb_file}..." + if ! curl -fsSL "$url" -o "${tmp}/${deb_file}"; then + log "Warning: .deb not found for arch=${arch}, trying binary install..." + rm -rf "$tmp" + install_binary "$version" + return + fi + + log "Installing .deb package..." + dpkg -i "${tmp}/${deb_file}" || apt-get install -f -y + rm -rf "$tmp" +} + +install_binary() { + local version="$1" + local machine + machine=$(uname -m) + local arch_str="amd64" + case "$machine" in + aarch64|arm64) arch_str="arm64" ;; + armv7l|armhf) arch_str="armv7" ;; + esac + + local tarball="ntfy_${version}_linux_${arch_str}.tar.gz" + local url="https://github.com/binwiederhier/ntfy/releases/download/v${version}/${tarball}" + local tmp + tmp=$(mktemp -d) + + log "Downloading ${tarball}..." + curl -fsSL "$url" -o "${tmp}/${tarball}" || \ + die "Failed to download ntfy from GitHub. Check your internet connection." + tar -xzf "${tmp}/${tarball}" -C "$tmp" + + local binary + binary=$(find "$tmp" -name "ntfy" -type f | head -1) + [ -n "$binary" ] || die "Binary not found in archive" + + install -m 755 "$binary" "${INSTALL_DIR}/ntfy" + log "ntfy binary installed to ${INSTALL_DIR}/ntfy" + rm -rf "$tmp" + + # Install systemd service manually + create_systemd_service +} + +install_arch_aur() { + local aur_pkg="ntfy" + + if command -v yay &>/dev/null; then + log "Installing via yay (AUR)..." + sudo -u "${SUDO_USER:-$USER}" yay -S --noconfirm "$aur_pkg" + elif command -v paru &>/dev/null; then + log "Installing via paru (AUR)..." + sudo -u "${SUDO_USER:-$USER}" paru -S --noconfirm "$aur_pkg" + else + log "No AUR helper found (yay/paru). Falling back to binary install..." + local version + version=$(get_latest_version) + install_binary "$version" + fi +} + +create_systemd_service() { + cat > /etc/systemd/system/ntfy.service <<'SYSTEMD' +[Unit] +Description=ntfy push notification server +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/ntfy serve +Restart=on-failure +RestartSec=5 +User=ntfy +Group=ntfy +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true +ProtectSystem=strict +ReadWritePaths=/var/cache/ntfy /etc/ntfy + +[Install] +WantedBy=multi-user.target +SYSTEMD + + # Create dedicated system user if it doesn't exist + if ! id ntfy &>/dev/null; then + useradd --system --no-create-home --shell /usr/sbin/nologin ntfy + fi + + log "Systemd service created." +} + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +configure_ntfy() { + log "Configuring NTFY..." + mkdir -p "$CONFIG_DIR" "$CACHE_DIR" + + # Set ownership for cache dir if ntfy user exists + if id ntfy &>/dev/null; then + chown ntfy:ntfy "$CACHE_DIR" + fi + + if [ -f "${CONFIG_DIR}/server.yml" ]; then + log "Config already exists at ${CONFIG_DIR}/server.yml — skipping." + return + fi + + cat > "${CONFIG_DIR}/server.yml" <" to require login. +# auth-file: "${CONFIG_DIR}/user.db" +# auth-default-access: "deny-all" +YAML + log "Configuration written to ${CONFIG_DIR}/server.yml" +} + +# --------------------------------------------------------------------------- +# Enable & start service +# --------------------------------------------------------------------------- + +enable_service() { + log "Enabling and starting ntfy service..." + systemctl daemon-reload + systemctl enable ntfy + systemctl restart ntfy + sleep 1 + systemctl is-active ntfy --quiet && log "ntfy is running." \ + || log "Warning: ntfy may not have started. Run: systemctl status ntfy" +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +main() { + check_root + detect_os + + if command -v ntfy &>/dev/null && ntfy version &>/dev/null 2>&1; then + log "ntfy is already installed: $(ntfy version 2>/dev/null | head -1)" + log "Reconfiguring and restarting..." + configure_ntfy + enable_service + elif is_ubuntu_like; then + log "Using Ubuntu/Debian install path..." + apt-get update -qq + apt-get install -y --no-install-recommends curl ca-certificates + local version + version=$(get_latest_version) + log "Installing ntfy v${version}..." + install_deb "$version" + configure_ntfy + enable_service + elif is_arch_like; then + log "Using Arch Linux install path..." + install_arch_aur + configure_ntfy + enable_service + else + log "Unknown OS '${OS}', attempting generic binary install..." + apt-get install -y --no-install-recommends curl ca-certificates 2>/dev/null \ + || pacman -S --noconfirm curl ca-certificates 2>/dev/null \ + || true + local version + version=$(get_latest_version) + install_binary "$version" + configure_ntfy + enable_service + fi + + local ip + ip=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "127.0.0.1") + + echo "" + echo "=======================================================" + echo " NTFY push notification server installed successfully!" + echo "=======================================================" + echo " Local URL : http://localhost:${NTFY_PORT}" + echo " Network URL: http://${ip}:${NTFY_PORT}" + echo " Config : ${CONFIG_DIR}/server.yml" + echo "-------------------------------------------------------" + echo " Test (send a notification):" + echo " curl -d 'Hello World' http://localhost:${NTFY_PORT}/my-topic" + echo "" + echo " Subscribe on mobile (ntfy app):" + echo " 1. Install 'ntfy' from Google Play / F-Droid / App Store" + echo " 2. Add server: http://${ip}:${NTFY_PORT}" + echo " 3. Subscribe to topic: my-topic" + echo "=======================================================" +} + +main "$@" diff --git a/script/todo/todo.py b/script/todo/todo.py index 1866f13..ef2548e 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -662,6 +662,11 @@ class TODO: {"prompt_description": t("SSH - Run make target")}, {"prompt_description": t("SSH - Install systemd service")}, {"prompt_description": t("SSH - Configure nginx + SSL")}, + { + "prompt_description": t( + "Deploy - Install NTFY notification server" + ) + }, ] help_info = self.fill_help_info(choices) @@ -696,6 +701,8 @@ class TODO: self._deploy_ssh_install_systemd() elif status == "13": self._deploy_ssh_install_nginx() + elif status == "14": + self._deploy_ntfy_server() else: print(t("Command not found !")) @@ -722,6 +729,54 @@ class TODO: except Exception as e: print(f"{t('Error cloning ERPLibre: ')}{e}") + def _deploy_ntfy_server(self): + print( + f"\n{t('Deploy a local NTFY push notification server (Ubuntu/Arch)')}" + ) + port = ( + input(t("NTFY server port (default: 8080): ")).strip() or "8080" + ) + import socket + + hostname = socket.gethostname() + try: + local_ip = socket.gethostbyname(hostname) + except Exception: + local_ip = "127.0.0.1" + + default_url = f"http://{local_ip}:{port}" + base_url = ( + input( + f"{t('NTFY base URL')} (default: {default_url}): " + ).strip() + or default_url + ) + + script_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "install", + "install_ntfy.sh", + ) + script_path = os.path.realpath(script_path) + + if not os.path.isfile(script_path): + print(f"{t('NTFY install script not found: ')}{script_path}") + return + + print(f"\n{t('Installing NTFY server (requires sudo)...')}") + cmd = ( + f"sudo NTFY_PORT={port}" + f" NTFY_BASE_URL={base_url}" + f" bash {script_path}" + ) + print(f"{t('Will execute:')} {cmd}\n") + try: + self.execute.exec_command_live(cmd, source_erplibre=False) + print(f"\n{t('NTFY server installed and started successfully!')}") + except Exception as e: + print(f"{t('Error installing NTFY server: ')}{e}") + def _configure_sshfs(self): import getpass import re diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index a09e17c..8390470 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -905,6 +905,39 @@ TRANSLATIONS = { "fr": "Erreur lors de la création du fichier : ", "en": "Error creating file: ", }, + # NTFY section + "Deploy - Install NTFY notification server": { + "fr": "Déployer - Installer le serveur de notifications NTFY", + "en": "Deploy - Install NTFY notification server", + }, + "Deploy a local NTFY push notification server (Ubuntu/Arch)": { + "fr": "Déployer un serveur local de notifications NTFY (Ubuntu/Arch)", + "en": "Deploy a local NTFY push notification server (Ubuntu/Arch)", + }, + "NTFY server port (default: 8080): ": { + "fr": "Port du serveur NTFY (défaut : 8080) : ", + "en": "NTFY server port (default: 8080): ", + }, + "NTFY base URL": { + "fr": "URL de base NTFY", + "en": "NTFY base URL", + }, + "Installing NTFY server (requires sudo)...": { + "fr": "Installation du serveur NTFY (sudo requis)...", + "en": "Installing NTFY server (requires sudo)...", + }, + "NTFY server installed and started successfully!": { + "fr": "Serveur NTFY installé et démarré avec succès!", + "en": "NTFY server installed and started successfully!", + }, + "Error installing NTFY server: ": { + "fr": "Erreur lors de l'installation du serveur NTFY : ", + "en": "Error installing NTFY server: ", + }, + "NTFY install script not found: ": { + "fr": "Script d'installation NTFY introuvable : ", + "en": "NTFY install script not found: ", + }, }