[ADD] install: add NTFY self-hosted push notification server
Add a one-command installer for the ntfy push notification server (Ubuntu/Debian and Arch Linux), wired into the todo.py Deploy menu. Users can now deploy a local ntfy server from the CLI and subscribe to topics from their mobile device (ntfy app) to receive push notifications from ERPLibre. Generated by Claude Code 2.1.101 model claude-sonnet-4-6 Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
This commit is contained in:
parent
4766fcb3b0
commit
3136bb694f
3 changed files with 391 additions and 0 deletions
303
script/install/install_ntfy.sh
Executable file
303
script/install/install_ntfy.sh
Executable file
|
|
@ -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" <<YAML
|
||||
# NTFY Server configuration
|
||||
# Documentation: https://ntfy.sh/docs/config/
|
||||
|
||||
# Public URL of this server (used in notification links)
|
||||
base-url: "${NTFY_BASE_URL}"
|
||||
|
||||
# Listening address — change to ":443" + TLS for production
|
||||
listen-http: ":${NTFY_PORT}"
|
||||
|
||||
# Message cache (enables message history for reconnecting subscribers)
|
||||
cache-file: "${CACHE_DIR}/cache.db"
|
||||
cache-duration: "12h"
|
||||
|
||||
log-level: info
|
||||
|
||||
# Attachment support
|
||||
attachment-cache-dir: "${CACHE_DIR}/attachments"
|
||||
attachment-total-size-limit: "5G"
|
||||
attachment-file-size-limit: "15M"
|
||||
attachment-expiry-duration: "3h"
|
||||
|
||||
# ─── Authentication (optional) ───────────────────────────────────────────────
|
||||
# Uncomment and run "ntfy user add <user>" 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 "$@"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: ",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue