From f900f3f0d5874c00df123d439905d44017d13085 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Tue, 30 Sep 2025 13:38:31 +0000 Subject: [PATCH 01/25] Resolve merge: keep proxmox installer and add README for Proxmox installer --- install/proxmox/README.md | 215 ++++++++ install/proxmox/proxmox-install-netalertx.sh | 505 +++++++++++++++++++ 2 files changed, 720 insertions(+) create mode 100644 install/proxmox/README.md create mode 100755 install/proxmox/proxmox-install-netalertx.sh diff --git a/install/proxmox/README.md b/install/proxmox/README.md new file mode 100644 index 00000000..2eb3a3f5 --- /dev/null +++ b/install/proxmox/README.md @@ -0,0 +1,215 @@ +# NetAlertX Proxmox Installer + +A comprehensive installer script for deploying NetAlertX on Proxmox VE (Debian-based) systems. This installer automates the complete setup including dependencies, NGINX configuration, systemd service, and security hardening. + +## 🚀 Quick Start + +### Prerequisites +- Proxmox VE (Debian-based) +- Root access +- Internet connection + +### Installation + +```bash +# Download and run the installer +curl -fsSL https://raw.githubusercontent.com/JVKeller/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh | bash +``` + +### Non-Interactive Installation +```bash +# Skip all prompts and use defaults +NETALERTX_ASSUME_YES=1 curl -fsSL https://raw.githubusercontent.com/JVKeller/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh | bash + +# Custom port +PORT=8080 NETALERTX_ASSUME_YES=1 curl -fsSL https://raw.githubusercontent.com/JVKeller/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh | bash +``` + +## 📋 What This Installer Does + +### System Dependencies +- **PHP 8.4** with FPM, SQLite3, cURL extensions +- **NGINX** with custom configuration +- **Python 3** with virtual environment +- **Network tools**: nmap, arp-scan, traceroute, mtr, speedtest-cli +- **Additional tools**: git, build-essential, avahi-daemon + +### Security Features +- **Hardened permissions**: Proper user/group ownership +- **TMPFS mounts**: Log and API directories mounted as tmpfs for security +- **NGINX user**: Configured to run as www-data +- **Strict bash options**: Error handling and security + +### Service Management +- **Systemd service**: Auto-start on boot with restart policies +- **Service monitoring**: Built-in health checks and logging +- **Dependency management**: Waits for network and NGINX + +## 🔧 Configuration + +### Port Configuration +The installer prompts for a custom port (default: 20211) with a 10-second countdown: + +``` +Enter HTTP port for NetAlertX [20211] (auto-continue in 10s): +``` + +### Environment Variables +| Variable | Description | Default | +|----------|-------------|---------| +| `NETALERTX_ASSUME_YES` | Skip all prompts | `false` | +| `NETALERTX_FORCE` | Force installation | `false` | +| `PORT` | Custom HTTP port | `20211` | +| `LISTEN_ADDR` | Bind address | `0.0.0.0` | +| `ALWAYS_FRESH_INSTALL` | Clear existing data | `false` | + +### Service Management +```bash +# Check service status +systemctl status netalertx + +# View logs +journalctl -u netalertx -f + +# Restart service +systemctl restart netalertx + +# Stop service +systemctl stop netalertx +``` + +## 🌐 Access + +After installation, access NetAlertX at: +``` +http://[SERVER_IP]:[PORT] +``` + +## 🔒 Security Considerations + +### TMPFS Mounts +- `/app/log` - Mounted as tmpfs (no persistent logs) +- `/app/api` - Mounted as tmpfs (temporary API data) + +### File Permissions +- Application files: `www-data:www-data` with appropriate permissions +- NGINX runs as `www-data` user +- Log directories: Secure permissions with tmpfs + +### Network Security +- NGINX configured for internal network access +- No external firewall rules added (configure manually if needed) + +## 🛠️ Troubleshooting + +### Common Issues + +#### 403 Forbidden Error +```bash +# Check file permissions +ls -la /var/www/html/netalertx +ls -la /app/front + +# Fix permissions +chown -R www-data:www-data /app/front +chmod -R 755 /app/front +``` + +#### Service Won't Start +```bash +# Check service status +systemctl status netalertx + +# View detailed logs +journalctl -u netalertx --no-pager -l + +# Check if port is in use +ss -tlnp | grep :20211 +``` + +#### GraphQL Connection Issues +```bash +# Check API token in config +grep API_TOKEN /app/config/app.conf + +# Verify GraphQL port +grep GRAPHQL_PORT /app/config/app.conf + +# Check backend logs +tail -f /app/log/app.log +``` + +### Log Locations +- **Service logs**: `journalctl -u netalertx` +- **Application logs**: `/app/log/` (tmpfs) +- **NGINX logs**: `/var/log/nginx/` +- **PHP logs**: `/app/log/app.php_errors.log` + +### Manual Service Start +If systemd service fails: +```bash +# Activate Python environment +source /opt/myenv/bin/activate + +# Start manually +cd /app +python server/ +``` +or +``` +./start.netalertx.sh +``` +## 🔄 Updates + +### Updating NetAlertX +```bash +# Stop service +systemctl stop netalertx + +# Update from repository +cd /app +git pull origin main + +# Restart service +systemctl start netalertx +``` + + +## 📁 File Structure + +``` +/app/ # Main application directory +├── front/ # Web interface (symlinked to /var/www/html/netalertx) +├── server/ # Python backend +├── config/ # Configuration files +├── db/ # Database files +├── log/ # Log files (tmpfs) +├── api/ # API files (tmpfs) +└── start.netalertx.sh # Service startup script + +/etc/systemd/system/ +└── netalertx.service # Systemd service definition + +/etc/nginx/conf.d/ +└── netalertx.conf # NGINX configuration +``` + +## 🤝 Contributing +This installer will need a maintainer + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## 🙏 Acknowledgments + +- NetAlertX development team +- Proxmox VE community +- Debian/Ubuntu maintainers +- Open source contributors + +--- + +**Note**: This installer was designed for a Proxmox LXC Debian 13 container. For other systems, please use the appropriate installer or manual installation instructions. diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh new file mode 100755 index 00000000..4bca2ca5 --- /dev/null +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -0,0 +1,505 @@ +#!/usr/bin/env bash + +# Exit immediately if a command exits with a non-zero status. +set -e +# Treat unset variables as an error when substituting +set -u +# Consider failures in a pipeline +set -o pipefail +# Safe IFS +IFS=$' \t\n' + +# 🛑 Important: This is only used for the bare-metal install 🛑 +# Colors (guarded) +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + RESET='\e[0m' + GREEN='\e[38;5;2m' + BOLD='\e[1m' + WHITE='\e[97m' + RED='\e[31m' +else + RESET=''; GREEN=''; BOLD=''; WHITE=''; RED='' +fi + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Running proxmox-install-netalertx.sh" + printf "%b\n" "--------------------------------------------------------------------------" + +# Set environment variables +INSTALL_DIR=/app # default installation directory +# DO NOT CHANGE ANYTHING BELOW THIS LINE! +INSTALLER_DIR="$INSTALL_DIR/install/proxmox" +CONF_FILE=app.conf +DB_FILE=app.db +NGINX_CONF_NAME=netalertx.conf +WEB_UI_DIR=/var/www/html/netalertx +NGINX_CONFIG_FILE="/etc/nginx/conf.d/${NGINX_CONF_NAME}" +OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" +FILEDB=$INSTALL_DIR/db/$DB_FILE +# DO NOT CHANGE ANYTHING ABOVE THIS LINE! + +# Check if script is run as root +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root." + exit 1 +fi + +# Interactive confirmation: warn about overwriting/removing existing installation and NGINX config +if [ -z "${NETALERTX_ASSUME_YES:-}" ] && [ -z "${ASSUME_YES:-}" ] && [ -z "${NETALERTX_FORCE:-}" ]; then + printf "%b\n" "------------------------------------------------------------------------" + printf "%b\n" "${RED}[WARNING] ${RESET}This script should be run on a fresh server" + printf "%b\n" "${RED}[WARNING] ${RESET}This script will install NetAlertX and will: " + printf "%b\n" "${RED}[WARNING] ${RESET}• Overwrite existing files under ${INSTALL_DIR} " + printf "%b\n" "${RED}[WARNING] ${RESET}• Wipe any existing database " + printf "%b\n" "${RED}[WARNING] ${RESET}• Wipe/Set up NGINX configuration under /etc/nginx" + printf "%b\n" "${RED}[WARNING] ${RESET}• Set up systemd services. " + read -r -p "Proceed with installation? [y/N]: " _reply + case "${_reply}" in + y|Y|yes|YES) ;; + *) echo "Aborting by user choice."; exit 1;; + esac +else + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Non-interactive mode detected; proceeding without confirmation." + printf "%b\n" "--------------------------------------------------------------------------" +fi + +# Prompt for HTTP port (default 20211) with countdown fallback +DEFAULT_PORT=20211 +if [ -z "${NETALERTX_ASSUME_YES:-}" ] && [ -z "${ASSUME_YES:-}" ] && [ -z "${NETALERTX_FORCE:-}" ]; then + printf "%b\n" "--------------------------------------------------------------------------" + # Countdown-based prompt + _entered_port="" + for _sec in 10 9 8 7 6 5 4 3 2 1; do + printf "\rEnter HTTP port for NetAlertX [${DEFAULT_PORT}] (auto-continue in %2ds): " "${_sec}" + if read -t 1 -r _entered_port; then + break + fi + done + printf "\n" + if [ -z "${_entered_port}" ]; then + PORT="${DEFAULT_PORT}" + elif printf '%s' "${_entered_port}" | grep -Eq '^[0-9]+$' && [ "${_entered_port}" -ge 1 ] && [ "${_entered_port}" -le 65535 ]; then + PORT="${_entered_port}" + else + printf "%b\n" "${RED}[WARNING] ${RESET}Invalid port. Falling back to ${DEFAULT_PORT}" + PORT="${DEFAULT_PORT}" + fi +else + PORT="${PORT-}"; PORT="${PORT:-${DEFAULT_PORT}}" +fi +export PORT + +# Detect primary server IP for final prompt/use +SERVER_IP="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')" +if [ -z "${SERVER_IP}" ]; then + SERVER_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" +fi +if [ -z "${SERVER_IP}" ]; then + SERVER_IP="127.0.0.1" +fi +export SERVER_IP + +# Remove existing installation directory immediately (no prompt), with safety guard +if [ -d "$INSTALL_DIR" ]; then + printf "%b\n" "Removing existing directory: $INSTALL_DIR" + rm -rf "$INSTALL_DIR" +fi + +# 1. INSTALL SYSTEM DEPENDENCIES & ADD PHP REPOSITORY +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Installing system dependencies" +printf "%b\n" "--------------------------------------------------------------------------" +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +# software-properties-common is not available and not needed +apt-get install -y --no-install-recommends \ + ca-certificates apt-transport-https lsb-release curl gnupg + +# Detect OS +. /etc/os-release +OS_ID="${ID:-}" +OS_VER="${VERSION_ID:-}" + +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Detected OS: ${OS_ID} ${OS_VER}" +printf "%b\n" "--------------------------------------------------------------------------" + +if [ "${OS_ID}" = "debian" ] && printf '%s' "${OS_VER}" | grep -q '^13'; then + # Debian 13 (trixie) ships PHP 8.4 in main repos; no extra repo needed + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Debian 13 detected - using built-in PHP 8.4" + apt-get install -y --no-install-recommends \ + tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ + php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ + python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ + avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ + debian-archive-keyring python3-venv +elif [ "${OS_ID}" = "ubuntu" ] && printf '%s' "${OS_VER}" | grep -q '^24'; then + # Ubuntu 24.x typically ships PHP 8.3; add ondrej/php PPA and set 8.4 + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Ubuntu 24 detected - enabling ondrej/php PPA for PHP 8.4" + apt-get install -y --no-install-recommends software-properties-common || true + if command -v add-apt-repository >/dev/null 2>&1; then + add-apt-repository -y ppa:ondrej/php || true + else + # Fallback: manually add ondrej/php PPA for the current codename + CODENAME=$(lsb_release -sc 2>/dev/null || echo noble) + curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x4F4EA0AAE5267A6C | gpg --dearmor | tee /usr/share/keyrings/ondrej-php.gpg >/dev/null + echo "deb [signed-by=/usr/share/keyrings/ondrej-php.gpg] http://ppa.launchpad.net/ondrej/php/ubuntu ${CODENAME} main" > /etc/apt/sources.list.d/ondrej-php.list + fi + apt-get update -y + apt-get install -y --no-install-recommends \ + tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ + php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ + python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ + avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ + python3-venv + # Set PHP 8.4 as the default alternatives where applicable + update-alternatives --set php /usr/bin/php8.4 || true + systemctl enable php8.4-fpm || true + systemctl restart php8.4-fpm || true +else + # Generic fallback: try installing PHP 8.4, may require external repo on older OSes + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Generic install path - attempting PHP 8.4 from current repos" + apt-get install -y --no-install-recommends \ + tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ + php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ + python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ + avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ + python3-venv || true +fi + +# 2. SET UP NGINX REPOSITORY AND INSTALL + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up NGINX" + printf "%b\n" "--------------------------------------------------------------------------" +curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ + | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null + + +echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian $(lsb_release -cs) nginx" \ + | tee /etc/apt/sources.list.d/nginx.list + +printf "Package: *\\nPin: origin nginx.org\\nPin: release o=nginx\\nPin-Priority: 900\\n" \ + | tee /etc/apt/preferences.d/99nginx + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up NGINX - Might take a minute!" + printf "%b\n" "--------------------------------------------------------------------------" + +apt-get update -y +apt-get install -y nginx + +# Enable and start nginx +if command -v systemctl >/dev/null 2>&1; then + systemctl enable nginx || true + systemctl restart nginx || true +fi + +# 3. SET UP PYTHON VIRTUAL ENVIRONMENT & DEPENDENCIES + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up Python environment" + printf "%b\n" "--------------------------------------------------------------------------" +python3 -m venv /opt/myenv +source /opt/myenv/bin/activate + +update-alternatives --install /usr/bin/python python /usr/bin/python3 10 + +# Create requirements.txt on-the-fly +cat > /tmp/requirements.txt << EOF +openwrt-luci-rpc +asusrouter +asyncio +aiohttp +graphene +flask +flask-cors +unifi-sm-api +tplink-omada-client +wakeonlan +pycryptodome +requests +paho-mqtt +scapy +cron-converter +pytz +json2table +dhcp-leases +pyunifi +speedtest-cli +chardet +python-nmap +dnspython +librouteros +yattag +git+https://github.com/foreign-sub/aiofreepybox.git +EOF + +python -m pip install --upgrade pip +python -m pip install -r /tmp/requirements.txt +rm /tmp/requirements.txt + +# 4. CLONE OR UPDATE APPLICATION REPOSITORY +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning application repository" +printf "%b\n" "--------------------------------------------------------------------------" + +mkdir -p "$INSTALL_DIR" +git clone https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing + +# 5. FINAL SETUP + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Starting NetAlertX" + printf "%b\n" "--------------------------------------------------------------------------" +if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then + date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" +fi + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}NetAlertX Installation complete" + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Installing NGINX and setting up the web server" + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Stopping any NGINX web server" + printf "%b\n" "--------------------------------------------------------------------------" + +service nginx stop 2>/dev/null || true +pkill -f "python ${INSTALL_DIR}/server" 2>/dev/null || true + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Updating the existing installation..." + printf "%b\n" "--------------------------------------------------------------------------" + +# Remove default NGINX site if it is symlinked, or backup it otherwise +if [ -L /etc/nginx/sites-enabled/default ] ; then + echo "Disabling default NGINX site, removing symlink in /etc/nginx/sites-enabled" + rm /etc/nginx/sites-enabled/default +elif [ -f /etc/nginx/sites-enabled/default ]; then + echo "Disabling default NGINX site, moving config to /etc/nginx/sites-available" + mv /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default.bkp_netalertx +fi + +# Clear existing directories and files +if [ -d "$WEB_UI_DIR" ]; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Removing existing NetAlertX web-UI" + printf "%b\n" "--------------------------------------------------------------------------" + rm -R "$WEB_UI_DIR" +fi + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Removing existing NetAlertX NGINX config" + printf "%b\n" "--------------------------------------------------------------------------" +rm "$NGINX_CONFIG_FILE" 2>/dev/null || true + +# Create web directory if it doesn't exist +mkdir -p /var/www/html + +# create symbolic link to the installer directory +ln -sfn "${INSTALL_DIR}/front" "$WEB_UI_DIR" +# create symbolic link to NGINX configuration coming with NetAlertX +ln -sfn "${INSTALLER_DIR}/${NGINX_CONF_NAME}" "${NGINX_CONFIG_FILE}" + +# Use selected port (may be default 20211) +if [ -n "${PORT-}" ]; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "Setting webserver to port ($PORT)" + printf "%b\n" "--------------------------------------------------------------------------" + sed -i "s/listen 20211;/listen ${PORT};/g" "${NGINX_CONFIG_FILE}" + # Also update the template file so it reflects the chosen port + sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" +fi + +# Change web interface address if set +if [ -n "${LISTEN_ADDR-}" ]; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "Setting webserver to user-supplied address (${LISTEN_ADDR})" + printf "%b\n" "--------------------------------------------------------------------------" + sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG_FILE}" + sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" +fi + +# Run the hardware vendors update at least once + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Run the hardware vendors update" + printf "%b\n" "--------------------------------------------------------------------------" + +# Check if ieee-oui.txt or ieee-iab.txt exist +if [ -f "$OUI_FILE" ]; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "The file ieee-oui.txt exists. Skipping update_vendors.sh..." + printf "%b\n" "--------------------------------------------------------------------------" +else + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "The file ieee-oui.txt does not exist. Running update_vendors..." + printf "%b\n" "--------------------------------------------------------------------------" + + # Run the update_vendors.sh script + if [ -f "${INSTALL_DIR}/back/update_vendors.sh" ]; then + "${INSTALL_DIR}/back/update_vendors.sh" + else + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" " update_vendors.sh script not found in $INSTALL_DIR." + printf "%b\n" "--------------------------------------------------------------------------" + fi +fi + +# Create empty log files +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Creating log and api mounts" +printf "%b\n" "--------------------------------------------------------------------------" + +printf "%b\n" "Cleaning up old mounts if any" +umount "${INSTALL_DIR}/log" 2>/dev/null || true +umount "${INSTALL_DIR}/api" 2>/dev/null || true + +printf "%b\n" "Creating log and api folders if they don't exist" +mkdir -p "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" + +printf "%b\n" "Mounting log and api folders as tmpfs" +mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/log" +mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/api" + +# Create the execution_queue.log file if it doesn't exist +touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} +touch "${INSTALL_DIR}"/api/user_notifications.json +# Create plugins sub-directory if it doesn't exist in case a custom log folder is used +mkdir -p "${INSTALL_DIR}"/log/plugins + +# Fixing file permissions + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing file permissions" + printf "%b\n" "--------------------------------------------------------------------------" +chown root:www-data "${INSTALL_DIR}"/api/user_notifications.json + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing WEB_UI_DIR: ${WEB_UI_DIR}" + printf "%b\n" "--------------------------------------------------------------------------" +chmod -R a+rwx "$WEB_UI_DIR" + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing INSTALL_DIR: ${INSTALL_DIR}" + printf "%b\n" "--------------------------------------------------------------------------" + +chmod -R a+rw "$INSTALL_DIR/log" +chmod -R a+rwx "$INSTALL_DIR" + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Copy starter $DB_FILE and $CONF_FILE if they don't exist" + printf "%b\n" "--------------------------------------------------------------------------" + +# DANGER ZONE: ALWAYS_FRESH_INSTALL +if [ "${ALWAYS_FRESH_INSTALL:-false}" = true ]; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}❗ ALERT /db and /config folders are cleared because the" + printf "%b\n" " ALWAYS_FRESH_INSTALL is set to: ${ALWAYS_FRESH_INSTALL}❗" + printf "%b\n" "--------------------------------------------------------------------------" + # Delete content of "/config/" + rm -rf "${INSTALL_DIR}/config/"* + + # Delete content of "/db/" + rm -rf "${INSTALL_DIR}/db/"* +fi + + +# Copy starter $DB_FILE and $CONF_FILE if they don't exist +mkdir -p "${INSTALL_DIR}/config" "${INSTALL_DIR}/db" +cp -u "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}" +cp -u "${INSTALL_DIR}/back/${DB_FILE}" "${FILEDB}" + + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing permissions after copied starter config & DB" + printf "%b\n" "--------------------------------------------------------------------------" + +if [ -f "$FILEDB" ]; then + chown -R www-data:www-data "$FILEDB" +fi +# Change Nginx User +sed -i '2s/.*/user www-data;/' /etc/nginx/nginx.conf + +# Change Nginx User +sed -i '2s/.*/user www-data;/' /etc/nginx/nginx.conf + +chmod -R a+rwx "$INSTALL_DIR" # second time after we copied the files +chmod -R a+rw "$INSTALL_DIR/config" +chgrp -R www-data "$INSTALL_DIR" + +# Check if buildtimestamp.txt doesn't exist +if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then + # Create buildtimestamp.txt + date +%s > "${INSTALL_DIR}/front/buildtimestamp.txt" +fi + +# start PHP +/etc/init.d/php8.4-fpm start +nginx -t || { printf "%b\n" "${RED}[INSTALLING] ${RESET}NGINX config test failed"; exit 1; } +/etc/init.d/nginx start + +# Add nginx to www-data +usermod -aG www-data nginx || true + +# Make a start script +cat > "$INSTALL_DIR/start.netalertx.sh" << 'EOF' +#!/usr/bin/env bash + +# Activate the virtual python environment +source /opt/myenv/bin/activate + +echo -e "--------------------------------------------------------------------------" +echo -e "Starting NetAlertX - navigate to http://${SERVER_IP}:${PORT}" +echo -e "--------------------------------------------------------------------------" + +# Start the NetAlertX python script +python server/ +EOF + +chmod +x "$INSTALL_DIR/start.netalertx.sh" + +# Install and manage systemd service if available, otherwise fallback to direct start +if command -v systemctl >/dev/null 2>&1; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up systemd service" + printf "%b\n" "--------------------------------------------------------------------------" + +cat > /etc/systemd/system/netalertx.service << 'EOF' +[Unit] +Description=NetAlertX Service +After=network-online.target nginx.service +Wants=network-online.target + +[Service] +Type=simple +User=www-data +Group=www-data +ExecStart=/bin/bash -lc '/app/start.netalertx.sh' +WorkingDirectory=/app +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + + # Reload systemd and enable/start service + systemctl daemon-reload + systemctl enable netalertx.service + systemctl start netalertx.service + + # Verify service is running + if systemctl is-active --quiet netalertx.service; then + printf "%b\n" "${GREEN}[SUCCESS] ${RESET}NetAlertX service started successfully" + else + printf "%b\n" "${RED}[WARNING] ${RESET}NetAlertX service may not have started properly" + systemctl status netalertx.service --no-pager -l + fi +else + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Starting NetAlertX (no systemd)" + printf "%b\n" "--------------------------------------------------------------------------" + "$INSTALL_DIR/start.netalertx.sh" & +fi + +echo -e "--------------------------------------------------------------------------" +echo -e "${GREEN}[Service] 🚀 Starting app - navigate to http://${SERVER_IP}:${PORT}" +echo -e "--------------------------------------------------------------------------" From c086ac3cf8e13a22728c7751b9f8717ae20e3870 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 15:22:21 +0000 Subject: [PATCH 02/25] Merge Deb/Ubuntu --- install/proxmox/proxmox-install-netalertx.sh | 284 +++++++------------ 1 file changed, 109 insertions(+), 175 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 4bca2ca5..802bb957 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -13,20 +13,27 @@ IFS=$' \t\n' # Colors (guarded) if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then RESET='\e[0m' - GREEN='\e[38;5;2m' - BOLD='\e[1m' - WHITE='\e[97m' + GREEN='\e[1;38;5;2m' RED='\e[31m' else - RESET=''; GREEN=''; BOLD=''; WHITE=''; RED='' + RESET=''; GREEN=''; RED='' fi - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Running proxmox-install-netalertx.sh" - printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[UPDATING] ${RESET}Making sure the system is up to date" +printf "%b\n" "--------------------------------------------------------------------------" + +# Getting up to date +apt-get update -y +apt-get upgrade -y + +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Running proxmox-install-netalertx.sh" +printf "%b\n" "--------------------------------------------------------------------------" # Set environment variables INSTALL_DIR=/app # default installation directory + # DO NOT CHANGE ANYTHING BELOW THIS LINE! INSTALLER_DIR="$INSTALL_DIR/install/proxmox" CONF_FILE=app.conf @@ -90,7 +97,7 @@ else fi export PORT -# Detect primary server IP for final prompt/use +# Detect primary server IP SERVER_IP="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')" if [ -z "${SERVER_IP}" ]; then SERVER_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" @@ -100,7 +107,7 @@ if [ -z "${SERVER_IP}" ]; then fi export SERVER_IP -# Remove existing installation directory immediately (no prompt), with safety guard +# Making sure the system is clean if [ -d "$INSTALL_DIR" ]; then printf "%b\n" "Removing existing directory: $INSTALL_DIR" rm -rf "$INSTALL_DIR" @@ -125,68 +132,40 @@ printf "%b\n" "----------------------------------------------------------------- printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Detected OS: ${OS_ID} ${OS_VER}" printf "%b\n" "--------------------------------------------------------------------------" -if [ "${OS_ID}" = "debian" ] && printf '%s' "${OS_VER}" | grep -q '^13'; then - # Debian 13 (trixie) ships PHP 8.4 in main repos; no extra repo needed - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Debian 13 detected - using built-in PHP 8.4" - apt-get install -y --no-install-recommends \ - tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ - php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ - python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ - avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ - debian-archive-keyring python3-venv -elif [ "${OS_ID}" = "ubuntu" ] && printf '%s' "${OS_VER}" | grep -q '^24'; then +if + [ "${OS_ID}" = "ubuntu" ] && printf '%s' "${OS_VER}" | grep -q '^24'; then # Ubuntu 24.x typically ships PHP 8.3; add ondrej/php PPA and set 8.4 + printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Ubuntu 24 detected - enabling ondrej/php PPA for PHP 8.4" + printf "%b\n" "--------------------------------------------------------------------------" apt-get install -y --no-install-recommends software-properties-common || true - if command -v add-apt-repository >/dev/null 2>&1; then - add-apt-repository -y ppa:ondrej/php || true - else - # Fallback: manually add ondrej/php PPA for the current codename - CODENAME=$(lsb_release -sc 2>/dev/null || echo noble) - curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x4F4EA0AAE5267A6C | gpg --dearmor | tee /usr/share/keyrings/ondrej-php.gpg >/dev/null - echo "deb [signed-by=/usr/share/keyrings/ondrej-php.gpg] http://ppa.launchpad.net/ondrej/php/ubuntu ${CODENAME} main" > /etc/apt/sources.list.d/ondrej-php.list - fi - apt-get update -y - apt-get install -y --no-install-recommends \ - tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ - php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ - python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ - avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ - python3-venv - # Set PHP 8.4 as the default alternatives where applicable + add-apt-repository ppa:ondrej/php -y + apt update -y +elif + [ "${OS_ID}" = "debian" ] && printf '%s' "${OS_VER}" | grep -q '^13'; then + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Debian 13 detected - using built-in PHP 8.4" + printf "%b\n" "--------------------------------------------------------------------------" +fi + +apt-get install -y --no-install-recommends \ + tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ + php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ + python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ + avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ + debian-archive-keyring python3-venv + +if + [ "${OS_ID}" = "ubuntu" ] && printf '%s' "${OS_VER}" | grep -q '^24'; then # Set PHP 8.4 as the default alternatives where applicable update-alternatives --set php /usr/bin/php8.4 || true systemctl enable php8.4-fpm || true systemctl restart php8.4-fpm || true -else - # Generic fallback: try installing PHP 8.4, may require external repo on older OSes - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Generic install path - attempting PHP 8.4 from current repos" - apt-get install -y --no-install-recommends \ - tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \ - php8.4 php8.4-cgi php8.4-fpm php8.4-sqlite3 php8.4-curl sqlite3 dnsutils net-tools mtr \ - python3 python3-dev iproute2 nmap python3-pip zip usbutils traceroute nbtscan \ - avahi-daemon avahi-utils build-essential git gnupg2 lsb-release \ - python3-venv || true fi -# 2. SET UP NGINX REPOSITORY AND INSTALL - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up NGINX" - printf "%b\n" "--------------------------------------------------------------------------" -curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ - | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up NGINX - Might take a minute!" +printf "%b\n" "--------------------------------------------------------------------------" - -echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian $(lsb_release -cs) nginx" \ - | tee /etc/apt/sources.list.d/nginx.list - -printf "Package: *\\nPin: origin nginx.org\\nPin: release o=nginx\\nPin-Priority: 900\\n" \ - | tee /etc/apt/preferences.d/99nginx - - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up NGINX - Might take a minute!" - printf "%b\n" "--------------------------------------------------------------------------" - -apt-get update -y apt-get install -y nginx # Enable and start nginx @@ -196,9 +175,9 @@ if command -v systemctl >/dev/null 2>&1; then fi # 3. SET UP PYTHON VIRTUAL ENVIRONMENT & DEPENDENCIES - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up Python environment" - printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up Python environment" +printf "%b\n" "--------------------------------------------------------------------------" python3 -m venv /opt/myenv source /opt/myenv/bin/activate @@ -240,55 +219,43 @@ rm /tmp/requirements.txt # 4. CLONE OR UPDATE APPLICATION REPOSITORY printf "%b\n" "--------------------------------------------------------------------------" -printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning application repository" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning application repository and setup" printf "%b\n" "--------------------------------------------------------------------------" mkdir -p "$INSTALL_DIR" -git clone https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing +git clone -b proxmox-baremetal-installer https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing -# 5. FINAL SETUP - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Starting NetAlertX" - printf "%b\n" "--------------------------------------------------------------------------" if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" fi - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}NetAlertX Installation complete" - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Installing NGINX and setting up the web server" - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Stopping any NGINX web server" - printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[FINISHED] ${RESET}NetAlertX Installation complete" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[CONFIGURATION] ${RESET}Configuring the web server" +printf "%b\n" "--------------------------------------------------------------------------" service nginx stop 2>/dev/null || true pkill -f "python ${INSTALL_DIR}/server" 2>/dev/null || true - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Updating the existing installation..." - printf "%b\n" "--------------------------------------------------------------------------" - -# Remove default NGINX site if it is symlinked, or backup it otherwise +# Backup default NGINX site just in case if [ -L /etc/nginx/sites-enabled/default ] ; then - echo "Disabling default NGINX site, removing symlink in /etc/nginx/sites-enabled" rm /etc/nginx/sites-enabled/default elif [ -f /etc/nginx/sites-enabled/default ]; then - echo "Disabling default NGINX site, moving config to /etc/nginx/sites-available" mv /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default.bkp_netalertx fi # Clear existing directories and files if [ -d "$WEB_UI_DIR" ]; then printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Removing existing NetAlertX web-UI" + printf "%b\n" "${GREEN}[CHECKING] ${RESET}Removing existing NetAlertX web-UI" printf "%b\n" "--------------------------------------------------------------------------" - rm -R "$WEB_UI_DIR" + rm -R "$WEB_UI_DIR" fi - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Removing existing NetAlertX NGINX config" - printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[CHECKING] ${RESET}Removing existing NetAlertX NGINX config" +printf "%b\n" "--------------------------------------------------------------------------" rm "$NGINX_CONFIG_FILE" 2>/dev/null || true # Create web directory if it doesn't exist @@ -298,30 +265,31 @@ mkdir -p /var/www/html ln -sfn "${INSTALL_DIR}/front" "$WEB_UI_DIR" # create symbolic link to NGINX configuration coming with NetAlertX ln -sfn "${INSTALLER_DIR}/${NGINX_CONF_NAME}" "${NGINX_CONFIG_FILE}" +ln -sfn "${INSTALLER_DIR}/$NGINX_CONF_FILE" $NGINX_CONFIG_FILE # Use selected port (may be default 20211) if [ -n "${PORT-}" ]; then printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "Setting webserver to port ($PORT)" printf "%b\n" "--------------------------------------------------------------------------" - sed -i "s/listen 20211;/listen ${PORT};/g" "${NGINX_CONFIG_FILE}" - # Also update the template file so it reflects the chosen port - sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" + sed -i "s/listen 20211;/listen ${PORT};/g" "${NGINX_CONFIG_FILE}" + # Update the template to reflect the right port + sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" fi # Change web interface address if set -if [ -n "${LISTEN_ADDR-}" ]; then - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "Setting webserver to user-supplied address (${LISTEN_ADDR})" - printf "%b\n" "--------------------------------------------------------------------------" - sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG_FILE}" - sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" -fi +# if [ -n "${LISTEN_ADDR-}" ]; then +# printf "%b\n" "--------------------------------------------------------------------------" +# printf "%b\n" "Setting webserver to user-supplied address (${LISTEN_ADDR})" +# printf "%b\n" "--------------------------------------------------------------------------" +# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG_FILE}" +# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" +# fi # Run the hardware vendors update at least once - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Run the hardware vendors update" - printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[VENDORS UPDATE] ${RESET}Run the hardware vendors update" +printf "%b\n" "--------------------------------------------------------------------------" # Check if ieee-oui.txt or ieee-iab.txt exist if [ -f "$OUI_FILE" ]; then @@ -343,100 +311,62 @@ else fi fi -# Create empty log files +# Create empty log files and plugin folders printf "%b\n" "--------------------------------------------------------------------------" -printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Creating log and api mounts" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Creating mounts and file structure" printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "Cleaning up old mounts if any" umount "${INSTALL_DIR}/log" 2>/dev/null || true umount "${INSTALL_DIR}/api" 2>/dev/null || true -printf "%b\n" "Creating log and api folders if they don't exist" +printf "%b\n" "Creating log api folders if they don't exist" mkdir -p "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" +mkdir -p "${INSTALL_DIR}"/log/plugins -printf "%b\n" "Mounting log and api folders as tmpfs" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Mounting log and api folders as tmpfs" +printf "%b\n" "--------------------------------------------------------------------------" mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/log" mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/api" # Create the execution_queue.log file if it doesn't exist touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} touch "${INSTALL_DIR}"/api/user_notifications.json -# Create plugins sub-directory if it doesn't exist in case a custom log folder is used -mkdir -p "${INSTALL_DIR}"/log/plugins - -# Fixing file permissions - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing file permissions" - printf "%b\n" "--------------------------------------------------------------------------" chown root:www-data "${INSTALL_DIR}"/api/user_notifications.json - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing WEB_UI_DIR: ${WEB_UI_DIR}" - printf "%b\n" "--------------------------------------------------------------------------" -chmod -R a+rwx "$WEB_UI_DIR" +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up DB and CONF files" +printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing INSTALL_DIR: ${INSTALL_DIR}" - printf "%b\n" "--------------------------------------------------------------------------" - -chmod -R a+rw "$INSTALL_DIR/log" -chmod -R a+rwx "$INSTALL_DIR" - - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Copy starter $DB_FILE and $CONF_FILE if they don't exist" - printf "%b\n" "--------------------------------------------------------------------------" - -# DANGER ZONE: ALWAYS_FRESH_INSTALL -if [ "${ALWAYS_FRESH_INSTALL:-false}" = true ]; then - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}❗ ALERT /db and /config folders are cleared because the" - printf "%b\n" " ALWAYS_FRESH_INSTALL is set to: ${ALWAYS_FRESH_INSTALL}❗" - printf "%b\n" "--------------------------------------------------------------------------" - # Delete content of "/config/" - rm -rf "${INSTALL_DIR}/config/"* - - # Delete content of "/db/" - rm -rf "${INSTALL_DIR}/db/"* -fi - - -# Copy starter $DB_FILE and $CONF_FILE if they don't exist +# Copy starter $DB_FILE and $CONF_FILE mkdir -p "${INSTALL_DIR}/config" "${INSTALL_DIR}/db" cp -u "${INSTALL_DIR}/back/${CONF_FILE}" "${INSTALL_DIR}/config/${CONF_FILE}" -cp -u "${INSTALL_DIR}/back/${DB_FILE}" "${FILEDB}" +cp -u "${INSTALL_DIR}/back/${DB_FILE}" "${FILEDB}" - printf "%b\n" "--------------------------------------------------------------------------" - printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Fixing permissions after copied starter config & DB" - printf "%b\n" "--------------------------------------------------------------------------" - -if [ -f "$FILEDB" ]; then - chown -R www-data:www-data "$FILEDB" -fi -# Change Nginx User -sed -i '2s/.*/user www-data;/' /etc/nginx/nginx.conf - -# Change Nginx User -sed -i '2s/.*/user www-data;/' /etc/nginx/nginx.conf - -chmod -R a+rwx "$INSTALL_DIR" # second time after we copied the files +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[CONFIGURING] ${RESET}Setting File Permissions" +printf "%b\n" "--------------------------------------------------------------------------" +chmod -R a+rwx "$INSTALL_DIR" +chmod -R a+rw "$INSTALL_DIR/log" chmod -R a+rw "$INSTALL_DIR/config" chgrp -R www-data "$INSTALL_DIR" - -# Check if buildtimestamp.txt doesn't exist -if [ ! -f "${INSTALL_DIR}/front/buildtimestamp.txt" ]; then - # Create buildtimestamp.txt - date +%s > "${INSTALL_DIR}/front/buildtimestamp.txt" -fi - -# start PHP -/etc/init.d/php8.4-fpm start -nginx -t || { printf "%b\n" "${RED}[INSTALLING] ${RESET}NGINX config test failed"; exit 1; } -/etc/init.d/nginx start - +chmod -R a+rwx "$WEB_UI_DIR" +chown -R www-data:www-data "$FILEDB" # Add nginx to www-data usermod -aG www-data nginx || true +# start PHP +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[STARTING] ${RESET}Starting PHP and NGINX" +printf "%b\n" "--------------------------------------------------------------------------" +/etc/init.d/php8.4-fpm start +nginx -t || { + printf "%b\n" "--------------------------------------------------------------------------" + printf "%b\n" "${RED}[ERROR] ${RESET}NGINX config test failed!" + printf "%b\n" "--------------------------------------------------------------------------"; exit 1; } +/etc/init.d/nginx start + # Make a start script cat > "$INSTALL_DIR/start.netalertx.sh" << 'EOF' #!/usr/bin/env bash @@ -481,16 +411,20 @@ StandardError=journal WantedBy=multi-user.target EOF - # Reload systemd and enable/start service - systemctl daemon-reload - systemctl enable netalertx.service - systemctl start netalertx.service +# Reload systemd and enable/start service +systemctl daemon-reload +systemctl enable netalertx.service +systemctl start netalertx.service # Verify service is running if systemctl is-active --quiet netalertx.service; then + printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[SUCCESS] ${RESET}NetAlertX service started successfully" + printf "%b\n" "--------------------------------------------------------------------------" else + printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${RED}[WARNING] ${RESET}NetAlertX service may not have started properly" + printf "%b\n" "--------------------------------------------------------------------------" systemctl status netalertx.service --no-pager -l fi else From 21e770a4bd3a8223e713453b847513888410430d Mon Sep 17 00:00:00 2001 From: rell3k Date: Wed, 1 Oct 2025 11:25:15 -0400 Subject: [PATCH 03/25] Create netalertx.conf --- install/proxmox/netalertx.conf | 1 + 1 file changed, 1 insertion(+) create mode 100644 install/proxmox/netalertx.conf diff --git a/install/proxmox/netalertx.conf b/install/proxmox/netalertx.conf new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/install/proxmox/netalertx.conf @@ -0,0 +1 @@ + From 223aa29d4ddc5752f9a3fa87ed1f78f82b9dbe71 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 17:40:02 +0000 Subject: [PATCH 04/25] tweaks --- install/proxmox/proxmox-install-netalertx.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 802bb957..84530628 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -23,10 +23,6 @@ printf "%b\n" "----------------------------------------------------------------- printf "%b\n" "${GREEN}[UPDATING] ${RESET}Making sure the system is up to date" printf "%b\n" "--------------------------------------------------------------------------" -# Getting up to date -apt-get update -y -apt-get upgrade -y - printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Running proxmox-install-netalertx.sh" printf "%b\n" "--------------------------------------------------------------------------" @@ -55,11 +51,12 @@ fi if [ -z "${NETALERTX_ASSUME_YES:-}" ] && [ -z "${ASSUME_YES:-}" ] && [ -z "${NETALERTX_FORCE:-}" ]; then printf "%b\n" "------------------------------------------------------------------------" printf "%b\n" "${RED}[WARNING] ${RESET}This script should be run on a fresh server" - printf "%b\n" "${RED}[WARNING] ${RESET}This script will install NetAlertX and will: " + printf "%b\n" "${RED}[WARNING] ${RESET}This script will install NetAlertX and will:" + printf "%b\n" "${RED}[WARNING] ${RESET}• Update OS with apt-get update/upgrade" printf "%b\n" "${RED}[WARNING] ${RESET}• Overwrite existing files under ${INSTALL_DIR} " - printf "%b\n" "${RED}[WARNING] ${RESET}• Wipe any existing database " + printf "%b\n" "${RED}[WARNING] ${RESET}• Wipe any existing database" printf "%b\n" "${RED}[WARNING] ${RESET}• Wipe/Set up NGINX configuration under /etc/nginx" - printf "%b\n" "${RED}[WARNING] ${RESET}• Set up systemd services. " + printf "%b\n" "${RED}[WARNING] ${RESET}• Set up systemd services." read -r -p "Proceed with installation? [y/N]: " _reply case "${_reply}" in y|Y|yes|YES) ;; @@ -71,6 +68,10 @@ else printf "%b\n" "--------------------------------------------------------------------------" fi +# Getting up to date +apt-get update -y +apt-get upgrade -y + # Prompt for HTTP port (default 20211) with countdown fallback DEFAULT_PORT=20211 if [ -z "${NETALERTX_ASSUME_YES:-}" ] && [ -z "${ASSUME_YES:-}" ] && [ -z "${NETALERTX_FORCE:-}" ]; then From e7d067dd3846229045b4b0a5d4f8a788a014d8af Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 18:15:28 +0000 Subject: [PATCH 05/25] tweaks --- install/proxmox/proxmox-install-netalertx.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 84530628..044c7fc6 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -266,7 +266,6 @@ mkdir -p /var/www/html ln -sfn "${INSTALL_DIR}/front" "$WEB_UI_DIR" # create symbolic link to NGINX configuration coming with NetAlertX ln -sfn "${INSTALLER_DIR}/${NGINX_CONF_NAME}" "${NGINX_CONFIG_FILE}" -ln -sfn "${INSTALLER_DIR}/$NGINX_CONF_FILE" $NGINX_CONFIG_FILE # Use selected port (may be default 20211) if [ -n "${PORT-}" ]; then From 09c345796f76ae9d4aed124bc6d50703ce9600f3 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 18:33:44 +0000 Subject: [PATCH 06/25] fix typo --- install/proxmox/proxmox-install-netalertx.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 044c7fc6..0abc18d1 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -34,9 +34,9 @@ INSTALL_DIR=/app # default installation directory INSTALLER_DIR="$INSTALL_DIR/install/proxmox" CONF_FILE=app.conf DB_FILE=app.db -NGINX_CONF_NAME=netalertx.conf +NGINX_CONF_FILE=netalertx.conf WEB_UI_DIR=/var/www/html/netalertx -NGINX_CONFIG_FILE="/etc/nginx/conf.d/${NGINX_CONF_NAME}" +NGINX_CONFIG_FILE=/etc/nginx/conf.d/$NGINX_CONF_FILE OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" FILEDB=$INSTALL_DIR/db/$DB_FILE # DO NOT CHANGE ANYTHING ABOVE THIS LINE! From 5f0a482556c83359f9bb3d50671325758a984fea Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 18:58:05 +0000 Subject: [PATCH 07/25] bug fix --- install/proxmox/proxmox-install-netalertx.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 0abc18d1..b919c1a8 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -36,7 +36,7 @@ CONF_FILE=app.conf DB_FILE=app.db NGINX_CONF_FILE=netalertx.conf WEB_UI_DIR=/var/www/html/netalertx -NGINX_CONFIG_FILE=/etc/nginx/conf.d/$NGINX_CONF_FILE +NGINX_CONFIG=/etc/nginx/conf.d/$NGINX_CONF_FILE OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" FILEDB=$INSTALL_DIR/db/$DB_FILE # DO NOT CHANGE ANYTHING ABOVE THIS LINE! @@ -257,24 +257,24 @@ fi printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[CHECKING] ${RESET}Removing existing NetAlertX NGINX config" printf "%b\n" "--------------------------------------------------------------------------" -rm "$NGINX_CONFIG_FILE" 2>/dev/null || true +rm "$NGINX_CONFIG" 2>/dev/null || true # Create web directory if it doesn't exist mkdir -p /var/www/html # create symbolic link to the installer directory ln -sfn "${INSTALL_DIR}/front" "$WEB_UI_DIR" + # create symbolic link to NGINX configuration coming with NetAlertX -ln -sfn "${INSTALLER_DIR}/${NGINX_CONF_NAME}" "${NGINX_CONFIG_FILE}" +ln -sfn "${INSTALLER_DIR}/${NGINX_CONF_FILE}" "${NGINX_CONFIG}" # Use selected port (may be default 20211) if [ -n "${PORT-}" ]; then printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "Setting webserver to port ($PORT)" printf "%b\n" "--------------------------------------------------------------------------" - sed -i "s/listen 20211;/listen ${PORT};/g" "${NGINX_CONFIG_FILE}" # Update the template to reflect the right port - sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" + sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_FILE}" fi # Change web interface address if set @@ -282,7 +282,7 @@ fi # printf "%b\n" "--------------------------------------------------------------------------" # printf "%b\n" "Setting webserver to user-supplied address (${LISTEN_ADDR})" # printf "%b\n" "--------------------------------------------------------------------------" -# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG_FILE}" +# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG}" # sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" # fi From d9ecffdd22ec5fbd83637e2dc406f49f23ce737e Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 19:09:49 +0000 Subject: [PATCH 08/25] Cleanup --- install/proxmox/proxmox-install-netalertx.sh | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index b919c1a8..93842098 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -107,6 +107,8 @@ if [ -z "${SERVER_IP}" ]; then SERVER_IP="127.0.0.1" fi export SERVER_IP +# Ensure tmpfs mounts are cleaned up on exit/failure +trap 'umount "${INSTALL_DIR}/log" 2>/dev/null || true; umount "${INSTALL_DIR}/api" 2>/dev/null || true' EXIT # Making sure the system is clean if [ -d "$INSTALL_DIR" ]; then @@ -122,7 +124,7 @@ export DEBIAN_FRONTEND=noninteractive apt-get update -y # software-properties-common is not available and not needed apt-get install -y --no-install-recommends \ - ca-certificates apt-transport-https lsb-release curl gnupg + ca-certificates lsb-release curl gnupg # Detect OS . /etc/os-release @@ -182,7 +184,7 @@ printf "%b\n" "----------------------------------------------------------------- python3 -m venv /opt/myenv source /opt/myenv/bin/activate -update-alternatives --install /usr/bin/python python /usr/bin/python3 10 +# Use python3 explicitly; avoid changing global python alternative # Create requirements.txt on-the-fly cat > /tmp/requirements.txt << EOF @@ -236,8 +238,8 @@ printf "%b\n" "----------------------------------------------------------------- printf "%b\n" "${GREEN}[CONFIGURATION] ${RESET}Configuring the web server" printf "%b\n" "--------------------------------------------------------------------------" -service nginx stop 2>/dev/null || true -pkill -f "python ${INSTALL_DIR}/server" 2>/dev/null || true +# Stop any existing NetAlertX python server process (narrow pattern) +pkill -f "^python(3)?\s+.*${INSTALL_DIR}/server/?$" 2>/dev/null || true # Backup default NGINX site just in case if [ -L /etc/nginx/sites-enabled/default ] ; then @@ -275,6 +277,10 @@ if [ -n "${PORT-}" ]; then printf "%b\n" "--------------------------------------------------------------------------" # Update the template to reflect the right port sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_FILE}" + # Warn if port is already in use + if ss -ltn | awk '{print $4}' | grep -q ":${PORT}$"; then + printf "%b\n" "${RED}[WARNING] ${RESET}Port ${PORT} appears in use. NGINX may fail to bind." + fi fi # Change web interface address if set @@ -283,7 +289,7 @@ fi # printf "%b\n" "Setting webserver to user-supplied address (${LISTEN_ADDR})" # printf "%b\n" "--------------------------------------------------------------------------" # sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG}" -# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_NAME}" +# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_FILE}" # fi # Run the hardware vendors update at least once @@ -327,8 +333,8 @@ mkdir -p "${INSTALL_DIR}"/log/plugins printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Mounting log and api folders as tmpfs" printf "%b\n" "--------------------------------------------------------------------------" -mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/log" -mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/api" +mountpoint -q "${INSTALL_DIR}/log" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/log" +mountpoint -q "${INSTALL_DIR}/api" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/api" # Create the execution_queue.log file if it doesn't exist touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} @@ -347,12 +353,12 @@ cp -u "${INSTALL_DIR}/back/${DB_FILE}" "${FILEDB}" printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[CONFIGURING] ${RESET}Setting File Permissions" printf "%b\n" "--------------------------------------------------------------------------" -chmod -R a+rwx "$INSTALL_DIR" -chmod -R a+rw "$INSTALL_DIR/log" -chmod -R a+rw "$INSTALL_DIR/config" -chgrp -R www-data "$INSTALL_DIR" -chmod -R a+rwx "$WEB_UI_DIR" -chown -R www-data:www-data "$FILEDB" +# Restrict wide permissions; allow owner/group access +chgrp -R www-data "$INSTALL_DIR" +chmod -R ug+rwX,o-rwx "$INSTALL_DIR" +chmod -R ug+rwX,o-rwx "$WEB_UI_DIR" +chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/config" +chown -R www-data:www-data "$FILEDB" 2>/dev/null || true # Add nginx to www-data usermod -aG www-data nginx || true From af6394a3345e9aeb3211e853bacf444bff556ab4 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 19:34:47 +0000 Subject: [PATCH 09/25] Tweak permissions Tighten security --- install/proxmox/proxmox-install-netalertx.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 93842098..18337183 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -328,18 +328,23 @@ umount "${INSTALL_DIR}/api" 2>/dev/null || true printf "%b\n" "Creating log api folders if they don't exist" mkdir -p "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" -mkdir -p "${INSTALL_DIR}"/log/plugins printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Mounting log and api folders as tmpfs" printf "%b\n" "--------------------------------------------------------------------------" mountpoint -q "${INSTALL_DIR}/log" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/log" mountpoint -q "${INSTALL_DIR}/api" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "${INSTALL_DIR}/api" +chown -R www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" + +# Ensure plugins directory exists within the tmpfs mount +mkdir -p "${INSTALL_DIR}"/log/plugins +chown -R www-data:www-data "${INSTALL_DIR}"/log/plugins # Create the execution_queue.log file if it doesn't exist touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} touch "${INSTALL_DIR}"/api/user_notifications.json -chown root:www-data "${INSTALL_DIR}"/api/user_notifications.json +chown -R www-data:www-data "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api +chmod -R ug+rwX "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up DB and CONF files" From 55cfced3f6939d7e37c972574c0c824cf3b92e76 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 19:41:51 +0000 Subject: [PATCH 10/25] Comment out line --- install/proxmox/proxmox-install-netalertx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 18337183..f405e629 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -365,7 +365,7 @@ chmod -R ug+rwX,o-rwx "$WEB_UI_DIR" chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/config" chown -R www-data:www-data "$FILEDB" 2>/dev/null || true # Add nginx to www-data -usermod -aG www-data nginx || true +# usermod -aG www-data nginx || true # start PHP printf "%b\n" "--------------------------------------------------------------------------" From cdee9b3b0dd05e3d89165405b7b0339da9687fb0 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Wed, 1 Oct 2025 20:33:12 +0000 Subject: [PATCH 11/25] Permissions --- install/proxmox/proxmox-install-netalertx.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index f405e629..a2237663 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -364,8 +364,15 @@ chmod -R ug+rwX,o-rwx "$INSTALL_DIR" chmod -R ug+rwX,o-rwx "$WEB_UI_DIR" chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/config" chown -R www-data:www-data "$FILEDB" 2>/dev/null || true -# Add nginx to www-data -# usermod -aG www-data nginx || true + +mkdir -p "$INSTALL_DIR/log" "$INSTALL_DIR/api" +mountpoint -q "$INSTALL_DIR/log" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "$INSTALL_DIR/log" +mountpoint -q "$INSTALL_DIR/api" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "$INSTALL_DIR/api" +mkdir -p "$INSTALL_DIR/log/plugins" +touch "$INSTALL_DIR"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} +touch "$INSTALL_DIR"/api/user_notifications.json +chown -R www-data:www-data "$INSTALL_DIR/log" "$INSTALL_DIR/api" +chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/api" # start PHP printf "%b\n" "--------------------------------------------------------------------------" From 1c2721549beb72ea18b115e969209c9f24ceb328 Mon Sep 17 00:00:00 2001 From: priestlypython Date: Wed, 1 Oct 2025 18:31:49 -0700 Subject: [PATCH 12/25] fix: Support compound conditions in SafeConditionBuilder (Issue #1210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem PR #1182 introduced SafeConditionBuilder to prevent SQL injection, but it only supported single-clause conditions. This broke notification filters using multiple AND/OR clauses, causing user filters like: `AND devLastIP NOT LIKE '192.168.50.%' AND devLastIP NOT LIKE '192.168.60.%'...` to be rejected with "Unsupported condition pattern" errors. ## Root Cause The `_parse_condition()` method used regex patterns that only matched single conditions. When multiple clauses were chained, the entire string failed to match any pattern and was rejected for security. ## Solution Enhanced SafeConditionBuilder with compound condition support: 1. **Added `_is_compound_condition()`** - Detects multiple logical operators while respecting quoted strings 2. **Added `_parse_compound_condition()`** - Splits compound conditions into individual clauses and parses each one 3. **Added `_split_by_logical_operators()`** - Intelligently splits on AND/OR while preserving operators in quoted strings 4. **Refactored `_parse_condition()`** - Routes to compound or single parser 5. **Created `_parse_single_condition()`** - Handles individual clauses (from original `_parse_condition` logic) ## Testing - Added comprehensive test suite (19 tests, 100% passing) - Tested user's exact failing filter (6 AND clauses with NOT LIKE) - Verified backward compatibility with single conditions - Validated security (SQL injection attempts still blocked) - Tested edge cases (mixed AND/OR, whitespace, empty conditions) ## Impact - ✅ Fixes reported issue #1210 - ✅ Maintains all security protections from PR #1182 - ✅ Backward compatible with existing single-clause filters - ✅ No breaking changes to API Fixes #1210 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- server/db/sql_safe_builder.py | 252 ++++++++++++++++++++++-- test/test_compound_conditions.py | 326 +++++++++++++++++++++++++++++++ 2 files changed, 558 insertions(+), 20 deletions(-) create mode 100644 test/test_compound_conditions.py diff --git a/server/db/sql_safe_builder.py b/server/db/sql_safe_builder.py index 5548561f..ce8c5360 100755 --- a/server/db/sql_safe_builder.py +++ b/server/db/sql_safe_builder.py @@ -153,47 +153,259 @@ class SafeConditionBuilder: def _parse_condition(self, condition: str) -> Tuple[str, Dict[str, Any]]: """ Parse a condition string into safe SQL with parameters. - - This method handles basic patterns like: - - AND devName = 'value' - - AND devComments LIKE '%value%' - - AND eve_EventType IN ('type1', 'type2') - + + This method handles both single and compound conditions: + - Single: AND devName = 'value' + - Compound: AND devName = 'value' AND devVendor = 'Apple' + - Multiple clauses with AND/OR operators + Args: condition: Condition string to parse - + Returns: Tuple of (safe_sql_snippet, parameters_dict) """ condition = condition.strip() - + + # Handle empty conditions + if not condition: + return "", {} + + # Check if this is a compound condition (multiple clauses) + if self._is_compound_condition(condition): + return self._parse_compound_condition(condition) + + # Single condition: extract leading logical operator if present + logical_op = None + clause_text = condition + + # Check for leading AND + if condition.upper().startswith('AND ') or condition.upper().startswith('AND\t'): + logical_op = 'AND' + clause_text = condition[3:].strip() + # Check for leading OR + elif condition.upper().startswith('OR ') or condition.upper().startswith('OR\t'): + logical_op = 'OR' + clause_text = condition[2:].strip() + + # Parse the single condition + return self._parse_single_condition(clause_text, logical_op) + + def _is_compound_condition(self, condition: str) -> bool: + """ + Determine if a condition contains multiple clauses (compound condition). + + A compound condition has multiple logical operators (AND/OR) connecting + separate comparison clauses. + + Args: + condition: Condition string to check + + Returns: + True if compound (multiple clauses), False if single clause + """ + # Track if we're inside quotes to avoid counting operators in quoted strings + in_quotes = False + logical_op_count = 0 + i = 0 + + while i < len(condition): + char = condition[i] + + # Toggle quote state + if char == "'": + in_quotes = not in_quotes + i += 1 + continue + + # Only count logical operators outside of quotes + if not in_quotes: + # Look for AND or OR as whole words + remaining = condition[i:].upper() + + # Check for AND (must be word boundary) + if remaining.startswith('AND ') or remaining.startswith('AND\t'): + logical_op_count += 1 + i += 3 + continue + + # Check for OR (must be word boundary) + if remaining.startswith('OR ') or remaining.startswith('OR\t'): + logical_op_count += 1 + i += 2 + continue + + i += 1 + + # A compound condition has more than one logical operator + # (first AND/OR starts the condition, subsequent ones connect clauses) + return logical_op_count > 1 + + def _parse_compound_condition(self, condition: str) -> Tuple[str, Dict[str, Any]]: + """ + Parse a compound condition with multiple clauses. + + Splits the condition into individual clauses, parses each one, + and reconstructs the full condition with all parameters. + + Args: + condition: Compound condition string + + Returns: + Tuple of (safe_sql_snippet, parameters_dict) + """ + # Split the condition into individual clauses while preserving logical operators + clauses = self._split_by_logical_operators(condition) + + # Parse each clause individually + parsed_parts = [] + all_params = {} + + for clause_text, logical_op in clauses: + # Parse this single clause + sql_part, params = self._parse_single_condition(clause_text, logical_op) + + if sql_part: + parsed_parts.append(sql_part) + all_params.update(params) + + if not parsed_parts: + raise ValueError("No valid clauses found in compound condition") + + # Join all parsed parts + final_sql = " ".join(parsed_parts) + + return final_sql, all_params + + def _split_by_logical_operators(self, condition: str) -> List[Tuple[str, Optional[str]]]: + """ + Split a compound condition into individual clauses. + + Returns a list of tuples: (clause_text, logical_operator) + The logical operator is the AND/OR that precedes the clause. + + Args: + condition: Compound condition string + + Returns: + List of (clause_text, logical_op) tuples + """ + clauses = [] + current_clause = [] + current_logical_op = None + in_quotes = False + i = 0 + + while i < len(condition): + char = condition[i] + + # Toggle quote state + if char == "'": + in_quotes = not in_quotes + current_clause.append(char) + i += 1 + continue + + # Only look for logical operators outside of quotes + if not in_quotes: + remaining = condition[i:].upper() + + # Check if we're at a word boundary (start of string or after whitespace) + at_word_boundary = (i == 0 or condition[i-1] in ' \t') + + # Check for AND (must be at word boundary) + if at_word_boundary and (remaining.startswith('AND ') or remaining.startswith('AND\t')): + # Save current clause if we have one + if current_clause: + clause_text = ''.join(current_clause).strip() + if clause_text: + clauses.append((clause_text, current_logical_op)) + current_clause = [] + + # Set the logical operator for the next clause + current_logical_op = 'AND' + i += 3 # Skip 'AND' + + # Skip whitespace after AND + while i < len(condition) and condition[i] in ' \t': + i += 1 + continue + + # Check for OR (must be at word boundary) + if at_word_boundary and (remaining.startswith('OR ') or remaining.startswith('OR\t')): + # Save current clause if we have one + if current_clause: + clause_text = ''.join(current_clause).strip() + if clause_text: + clauses.append((clause_text, current_logical_op)) + current_clause = [] + + # Set the logical operator for the next clause + current_logical_op = 'OR' + i += 2 # Skip 'OR' + + # Skip whitespace after OR + while i < len(condition) and condition[i] in ' \t': + i += 1 + continue + + # Add character to current clause + current_clause.append(char) + i += 1 + + # Don't forget the last clause + if current_clause: + clause_text = ''.join(current_clause).strip() + if clause_text: + clauses.append((clause_text, current_logical_op)) + + return clauses + + def _parse_single_condition(self, condition: str, logical_op: Optional[str] = None) -> Tuple[str, Dict[str, Any]]: + """ + Parse a single condition clause into safe SQL with parameters. + + This method handles basic patterns like: + - devName = 'value' (with optional AND/OR prefix) + - devComments LIKE '%value%' + - eve_EventType IN ('type1', 'type2') + + Args: + condition: Single condition string to parse + logical_op: Optional logical operator (AND/OR) to prepend + + Returns: + Tuple of (safe_sql_snippet, parameters_dict) + """ + condition = condition.strip() + # Handle empty conditions if not condition: return "", {} # Simple pattern matching for common conditions - # Pattern 1: AND/OR column operator value (supporting Unicode in quoted strings) - pattern1 = r'^\s*(AND|OR)?\s+(\w+)\s+(=|!=|<>|<|>|<=|>=|LIKE|NOT\s+LIKE)\s+\'([^\']*)\'\s*$' + # Pattern 1: [AND/OR] column operator value (supporting Unicode in quoted strings) + pattern1 = r'^\s*(\w+)\s+(=|!=|<>|<|>|<=|>=|LIKE|NOT\s+LIKE)\s+\'([^\']*)\'\s*$' match1 = re.match(pattern1, condition, re.IGNORECASE | re.UNICODE) - + if match1: - logical_op, column, operator, value = match1.groups() + column, operator, value = match1.groups() return self._build_simple_condition(logical_op, column, operator, value) - # Pattern 2: AND/OR column IN ('val1', 'val2', ...) - pattern2 = r'^\s*(AND|OR)?\s+(\w+)\s+(IN|NOT\s+IN)\s+\(([^)]+)\)\s*$' + # Pattern 2: [AND/OR] column IN ('val1', 'val2', ...) + pattern2 = r'^\s*(\w+)\s+(IN|NOT\s+IN)\s+\(([^)]+)\)\s*$' match2 = re.match(pattern2, condition, re.IGNORECASE) - + if match2: - logical_op, column, operator, values_str = match2.groups() + column, operator, values_str = match2.groups() return self._build_in_condition(logical_op, column, operator, values_str) - # Pattern 3: AND/OR column IS NULL/IS NOT NULL - pattern3 = r'^\s*(AND|OR)?\s+(\w+)\s+(IS\s+NULL|IS\s+NOT\s+NULL)\s*$' + # Pattern 3: [AND/OR] column IS NULL/IS NOT NULL + pattern3 = r'^\s*(\w+)\s+(IS\s+NULL|IS\s+NOT\s+NULL)\s*$' match3 = re.match(pattern3, condition, re.IGNORECASE) - + if match3: - logical_op, column, operator = match3.groups() + column, operator = match3.groups() return self._build_null_condition(logical_op, column, operator) # If no patterns match, reject the condition for security diff --git a/test/test_compound_conditions.py b/test/test_compound_conditions.py new file mode 100644 index 00000000..e7d15557 --- /dev/null +++ b/test/test_compound_conditions.py @@ -0,0 +1,326 @@ +""" +Unit tests for SafeConditionBuilder compound condition parsing. + +Tests the fix for Issue #1210 - compound conditions with multiple AND/OR clauses. +""" + +import sys +import unittest +from unittest.mock import MagicMock + +# Mock the logger module before importing SafeConditionBuilder +sys.modules['logger'] = MagicMock() + +# Add parent directory to path for imports +sys.path.insert(0, '/tmp/netalertx_hotfix/server/db') + +from sql_safe_builder import SafeConditionBuilder + + +class TestCompoundConditions(unittest.TestCase): + """Test compound condition parsing functionality.""" + + def setUp(self): + """Create a fresh builder instance for each test.""" + self.builder = SafeConditionBuilder() + + def test_user_failing_filter_six_and_clauses(self): + """Test the exact user-reported failing filter from Issue #1210.""" + condition = ( + "AND devLastIP NOT LIKE '192.168.50.%' " + "AND devLastIP NOT LIKE '192.168.60.%' " + "AND devLastIP NOT LIKE '192.168.70.2' " + "AND devLastIP NOT LIKE '192.168.70.5' " + "AND devLastIP NOT LIKE '192.168.70.3' " + "AND devLastIP NOT LIKE '192.168.70.4'" + ) + + sql, params = self.builder.build_safe_condition(condition) + + # Should successfully parse + self.assertIsNotNone(sql) + self.assertIsNotNone(params) + + # Should have 6 parameters (one per clause) + self.assertEqual(len(params), 6) + + # Should contain all 6 AND operators + self.assertEqual(sql.count('AND'), 6) + + # Should contain all 6 NOT LIKE operators + self.assertEqual(sql.count('NOT LIKE'), 6) + + # Should have 6 parameter placeholders + self.assertEqual(sql.count(':param_'), 6) + + # Verify all IP patterns are in parameters + param_values = list(params.values()) + self.assertIn('192.168.50.%', param_values) + self.assertIn('192.168.60.%', param_values) + self.assertIn('192.168.70.2', param_values) + self.assertIn('192.168.70.5', param_values) + self.assertIn('192.168.70.3', param_values) + self.assertIn('192.168.70.4', param_values) + + def test_multiple_and_clauses_simple(self): + """Test multiple AND clauses with simple equality operators.""" + condition = "AND devName = 'Device1' AND devVendor = 'Apple' AND devFavorite = '1'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 3 parameters + self.assertEqual(len(params), 3) + + # Should have 3 AND operators + self.assertEqual(sql.count('AND'), 3) + + # Verify all values are parameterized + param_values = list(params.values()) + self.assertIn('Device1', param_values) + self.assertIn('Apple', param_values) + self.assertIn('1', param_values) + + def test_multiple_or_clauses(self): + """Test multiple OR clauses.""" + condition = "OR devName = 'Device1' OR devName = 'Device2' OR devName = 'Device3'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 3 parameters + self.assertEqual(len(params), 3) + + # Should have 3 OR operators + self.assertEqual(sql.count('OR'), 3) + + # Verify all device names are parameterized + param_values = list(params.values()) + self.assertIn('Device1', param_values) + self.assertIn('Device2', param_values) + self.assertIn('Device3', param_values) + + def test_mixed_and_or_clauses(self): + """Test mixed AND/OR logical operators.""" + condition = "AND devName = 'Device1' OR devName = 'Device2' AND devFavorite = '1'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 3 parameters + self.assertEqual(len(params), 3) + + # Should preserve the logical operator order + self.assertIn('AND', sql) + self.assertIn('OR', sql) + + # Verify all values are parameterized + param_values = list(params.values()) + self.assertIn('Device1', param_values) + self.assertIn('Device2', param_values) + self.assertIn('1', param_values) + + def test_single_condition_backward_compatibility(self): + """Test that single conditions still work (backward compatibility).""" + condition = "AND devName = 'TestDevice'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 1 parameter + self.assertEqual(len(params), 1) + + # Should match expected format + self.assertIn('AND devName = :param_', sql) + + # Parameter should contain the value + self.assertIn('TestDevice', params.values()) + + def test_single_condition_like_operator(self): + """Test single LIKE condition for backward compatibility.""" + condition = "AND devComments LIKE '%important%'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 1 parameter + self.assertEqual(len(params), 1) + + # Should contain LIKE operator + self.assertIn('LIKE', sql) + + # Parameter should contain the pattern + self.assertIn('%important%', params.values()) + + def test_compound_with_like_patterns(self): + """Test compound conditions with LIKE patterns.""" + condition = "AND devLastIP LIKE '192.168.%' AND devVendor LIKE '%Apple%'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 2 parameters + self.assertEqual(len(params), 2) + + # Should have 2 LIKE operators + self.assertEqual(sql.count('LIKE'), 2) + + # Verify patterns are parameterized + param_values = list(params.values()) + self.assertIn('192.168.%', param_values) + self.assertIn('%Apple%', param_values) + + def test_compound_with_inequality_operators(self): + """Test compound conditions with various inequality operators.""" + condition = "AND eve_DateTime > '2024-01-01' AND eve_DateTime < '2024-12-31'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 2 parameters + self.assertEqual(len(params), 2) + + # Should have both operators + self.assertIn('>', sql) + self.assertIn('<', sql) + + # Verify dates are parameterized + param_values = list(params.values()) + self.assertIn('2024-01-01', param_values) + self.assertIn('2024-12-31', param_values) + + def test_empty_condition(self): + """Test empty condition string.""" + condition = "" + + sql, params = self.builder.build_safe_condition(condition) + + # Should return empty results + self.assertEqual(sql, "") + self.assertEqual(params, {}) + + def test_whitespace_only_condition(self): + """Test condition with only whitespace.""" + condition = " \t\n " + + sql, params = self.builder.build_safe_condition(condition) + + # Should return empty results + self.assertEqual(sql, "") + self.assertEqual(params, {}) + + def test_invalid_column_name_rejected(self): + """Test that invalid column names are rejected.""" + condition = "AND malicious_column = 'value'" + + with self.assertRaises(ValueError): + self.builder.build_safe_condition(condition) + + def test_invalid_operator_rejected(self): + """Test that invalid operators are rejected.""" + condition = "AND devName EXECUTE 'DROP TABLE'" + + with self.assertRaises(ValueError): + self.builder.build_safe_condition(condition) + + def test_sql_injection_attempt_blocked(self): + """Test that SQL injection attempts are blocked.""" + condition = "AND devName = 'value'; DROP TABLE devices; --" + + # Should either reject or sanitize the dangerous input + # The semicolon and comment should not appear in the final SQL + try: + sql, params = self.builder.build_safe_condition(condition) + # If it doesn't raise an error, it should sanitize the input + self.assertNotIn('DROP', sql.upper()) + self.assertNotIn(';', sql) + except ValueError: + # Rejection is also acceptable + pass + + def test_quoted_string_with_spaces(self): + """Test that quoted strings with spaces are handled correctly.""" + condition = "AND devName = 'My Device Name' AND devComments = 'Has spaces here'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 2 parameters + self.assertEqual(len(params), 2) + + # Verify values with spaces are preserved + param_values = list(params.values()) + self.assertIn('My Device Name', param_values) + self.assertIn('Has spaces here', param_values) + + def test_compound_condition_with_not_equal(self): + """Test compound conditions with != operator.""" + condition = "AND devName != 'Device1' AND devVendor != 'Unknown'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have 2 parameters + self.assertEqual(len(params), 2) + + # Should have != operators (or converted to <>) + self.assertTrue('!=' in sql or '<>' in sql) + + # Verify values are parameterized + param_values = list(params.values()) + self.assertIn('Device1', param_values) + self.assertIn('Unknown', param_values) + + def test_very_long_compound_condition(self): + """Test handling of very long compound conditions (10+ clauses).""" + clauses = [] + for i in range(10): + clauses.append(f"AND devName != 'Device{i}'") + + condition = " ".join(clauses) + sql, params = self.builder.build_safe_condition(condition) + + # Should have 10 parameters + self.assertEqual(len(params), 10) + + # Should have 10 AND operators + self.assertEqual(sql.count('AND'), 10) + + # Verify all device names are parameterized + param_values = list(params.values()) + for i in range(10): + self.assertIn(f'Device{i}', param_values) + + +class TestParameterGeneration(unittest.TestCase): + """Test parameter generation and naming.""" + + def setUp(self): + """Create a fresh builder instance for each test.""" + self.builder = SafeConditionBuilder() + + def test_parameters_have_unique_names(self): + """Test that all parameters get unique names.""" + condition = "AND devName = 'A' AND devName = 'B' AND devName = 'C'" + + sql, params = self.builder.build_safe_condition(condition) + + # All parameter names should be unique + param_names = list(params.keys()) + self.assertEqual(len(param_names), len(set(param_names))) + + def test_parameter_values_match_condition(self): + """Test that parameter values correctly match the condition values.""" + condition = "AND devLastIP NOT LIKE '192.168.1.%' AND devLastIP NOT LIKE '10.0.0.%'" + + sql, params = self.builder.build_safe_condition(condition) + + # Should have exactly the values from the condition + param_values = sorted(params.values()) + expected_values = sorted(['192.168.1.%', '10.0.0.%']) + self.assertEqual(param_values, expected_values) + + def test_parameters_referenced_in_sql(self): + """Test that all parameters are actually referenced in the SQL.""" + condition = "AND devName = 'Device1' AND devVendor = 'Apple'" + + sql, params = self.builder.build_safe_condition(condition) + + # Every parameter should appear in the SQL + for param_name in params.keys(): + self.assertIn(f':{param_name}', sql) + + +if __name__ == '__main__': + unittest.main() From aad5bec7e21343f3c6acba0932386c1497616c51 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 16:00:19 +0000 Subject: [PATCH 13/25] Single Debian/Ubuntu Installer --- install/proxmox/README.md | 28 +++---------------- install/proxmox/proxmox-install-netalertx.sh | 29 +++++--------------- install/proxmox/requirements.txt | 26 ++++++++++++++++++ 3 files changed, 37 insertions(+), 46 deletions(-) create mode 100644 install/proxmox/requirements.txt diff --git a/install/proxmox/README.md b/install/proxmox/README.md index 2eb3a3f5..aa53218d 100644 --- a/install/proxmox/README.md +++ b/install/proxmox/README.md @@ -1,11 +1,11 @@ # NetAlertX Proxmox Installer -A comprehensive installer script for deploying NetAlertX on Proxmox VE (Debian-based) systems. This installer automates the complete setup including dependencies, NGINX configuration, systemd service, and security hardening. +An installer script for deploying NetAlertX on Proxmox VE (Debian-based) systems. This installer automates the complete setup including dependencies, NGINX configuration, systemd service, and security hardening. ## 🚀 Quick Start ### Prerequisites -- Proxmox VE (Debian-based) +- Fresh LXC or VM of Debian 13 or Ubuntu 24 - Root access - Internet connection @@ -13,16 +13,7 @@ A comprehensive installer script for deploying NetAlertX on Proxmox VE (Debian-b ```bash # Download and run the installer -curl -fsSL https://raw.githubusercontent.com/JVKeller/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh | bash -``` - -### Non-Interactive Installation -```bash -# Skip all prompts and use defaults -NETALERTX_ASSUME_YES=1 curl -fsSL https://raw.githubusercontent.com/JVKeller/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh | bash - -# Custom port -PORT=8080 NETALERTX_ASSUME_YES=1 curl -fsSL https://raw.githubusercontent.com/JVKeller/NetAlertX/main/install/proxmox/proxmox-install-netalertx.sh | bash +wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/refs/heads/main/install/proxmox/proxmox-install-netalertx.sh -O proxmox-install-netalertx.sh && chmod +x proxmox-install-netalertx.sh && ./proxmox-install-netalertx.sh ``` ## 📋 What This Installer Does @@ -37,8 +28,6 @@ PORT=8080 NETALERTX_ASSUME_YES=1 curl -fsSL https://raw.githubusercontent.com/JV ### Security Features - **Hardened permissions**: Proper user/group ownership - **TMPFS mounts**: Log and API directories mounted as tmpfs for security -- **NGINX user**: Configured to run as www-data -- **Strict bash options**: Error handling and security ### Service Management - **Systemd service**: Auto-start on boot with restart policies @@ -48,21 +37,12 @@ PORT=8080 NETALERTX_ASSUME_YES=1 curl -fsSL https://raw.githubusercontent.com/JV ## 🔧 Configuration ### Port Configuration -The installer prompts for a custom port (default: 20211) with a 10-second countdown: +The installer will prompt for a custom port, or defaultto 20211 after 10-seconds: ``` Enter HTTP port for NetAlertX [20211] (auto-continue in 10s): ``` -### Environment Variables -| Variable | Description | Default | -|----------|-------------|---------| -| `NETALERTX_ASSUME_YES` | Skip all prompts | `false` | -| `NETALERTX_FORCE` | Force installation | `false` | -| `PORT` | Custom HTTP port | `20211` | -| `LISTEN_ADDR` | Bind address | `0.0.0.0` | -| `ALWAYS_FRESH_INSTALL` | Clear existing data | `false` | - ### Service Management ```bash # Check service status diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index a2237663..3d6265a5 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -267,8 +267,8 @@ mkdir -p /var/www/html # create symbolic link to the installer directory ln -sfn "${INSTALL_DIR}/front" "$WEB_UI_DIR" -# create symbolic link to NGINX configuration coming with NetAlertX -ln -sfn "${INSTALLER_DIR}/${NGINX_CONF_FILE}" "${NGINX_CONFIG}" +# Copy NGINX configuration to NetAlertX config directory +cp "${INSTALLER_DIR}/${NGINX_CONF_FILE}" "${INSTALL_DIR}/config/${NGINX_CONF_FILE}" # Use selected port (may be default 20211) if [ -n "${PORT-}" ]; then @@ -276,22 +276,16 @@ if [ -n "${PORT-}" ]; then printf "%b\n" "Setting webserver to port ($PORT)" printf "%b\n" "--------------------------------------------------------------------------" # Update the template to reflect the right port - sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALLER_DIR}/${NGINX_CONF_FILE}" + sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALL_DIR}/config/${NGINX_CONF_FILE}" + sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALL_DIR}/config/${NGINX_CONF_FILE}" # Warn if port is already in use if ss -ltn | awk '{print $4}' | grep -q ":${PORT}$"; then + printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${RED}[WARNING] ${RESET}Port ${PORT} appears in use. NGINX may fail to bind." + printf "%b\n" "--------------------------------------------------------------------------" fi fi -# Change web interface address if set -# if [ -n "${LISTEN_ADDR-}" ]; then -# printf "%b\n" "--------------------------------------------------------------------------" -# printf "%b\n" "Setting webserver to user-supplied address (${LISTEN_ADDR})" -# printf "%b\n" "--------------------------------------------------------------------------" -# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${NGINX_CONFIG}" -# sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALLER_DIR}/${NGINX_CONF_FILE}" -# fi - # Run the hardware vendors update at least once printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[VENDORS UPDATE] ${RESET}Run the hardware vendors update" @@ -362,18 +356,9 @@ printf "%b\n" "----------------------------------------------------------------- chgrp -R www-data "$INSTALL_DIR" chmod -R ug+rwX,o-rwx "$INSTALL_DIR" chmod -R ug+rwX,o-rwx "$WEB_UI_DIR" -chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/config" +# chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/config" chown -R www-data:www-data "$FILEDB" 2>/dev/null || true -mkdir -p "$INSTALL_DIR/log" "$INSTALL_DIR/api" -mountpoint -q "$INSTALL_DIR/log" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "$INSTALL_DIR/log" -mountpoint -q "$INSTALL_DIR/api" || mount -t tmpfs -o noexec,nosuid,nodev tmpfs "$INSTALL_DIR/api" -mkdir -p "$INSTALL_DIR/log/plugins" -touch "$INSTALL_DIR"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} -touch "$INSTALL_DIR"/api/user_notifications.json -chown -R www-data:www-data "$INSTALL_DIR/log" "$INSTALL_DIR/api" -chmod -R ug+rwX "$INSTALL_DIR/log" "$INSTALL_DIR/api" - # start PHP printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[STARTING] ${RESET}Starting PHP and NGINX" diff --git a/install/proxmox/requirements.txt b/install/proxmox/requirements.txt new file mode 100644 index 00000000..d1127387 --- /dev/null +++ b/install/proxmox/requirements.txt @@ -0,0 +1,26 @@ +openwrt-luci-rpc +asusrouter +asyncio +aiohttp +graphene +flask +flask-cors +unifi-sm-api +tplink-omada-client +wakeonlan +pycryptodome +requests +paho-mqtt +scapy +cron-converter +pytz +json2table +dhcp-leases +pyunifi +speedtest-cli +chardet +python-nmap +dnspython +librouteros +yattag +git+https://github.com/foreign-sub/aiofreepybox.git From 79cec583d96d332f1f40bddde6bfd683ed619be8 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 16:03:23 +0000 Subject: [PATCH 14/25] NGINX configuration --- install/proxmox/netalertx.conf | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/install/proxmox/netalertx.conf b/install/proxmox/netalertx.conf index 8b137891..01f94157 100644 --- a/install/proxmox/netalertx.conf +++ b/install/proxmox/netalertx.conf @@ -1 +1,33 @@ +server { + listen 20211; + server_name _; #change this to your custom domain if you have one + # Web-interface files location + root /var/www/html/netalertx; + + # Main page + index index.php; + + #rewrite /app/(.*) / permanent; + add_header X-Forwarded-Prefix "/netalertx" always; + proxy_set_header X-Forwarded-Prefix "/netalertx"; + + # Specify a character set + charset utf-8; + + location / { + # Try to serve files directly, fallback to index.php + try_files $uri $uri/ /index.php?$query_string; + } + + # FastCGI configuration for PHP + location ~ \.php$ { + # Use a Unix socket for better performance + fastcgi_pass unix:/var/run/php/php-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param QUERY_STRING $query_string; + include fastcgi_params; + } +} From 7790530d085d94318276ef6309ba17931b952c2c Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 16:05:31 +0000 Subject: [PATCH 15/25] Revert source repo --- install/proxmox/proxmox-install-netalertx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 3d6265a5..8aef0c9b 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -226,7 +226,7 @@ printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning app printf "%b\n" "--------------------------------------------------------------------------" mkdir -p "$INSTALL_DIR" -git clone -b proxmox-baremetal-installer https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing +git clone -b proxmox-baremetal-installer https://github.com/jokob-sk/NetAlertX.git "$INSTALL_DIR/" #change after testing if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" From 0aceb097baf4fcff5abfe3d14a220aad84c0c4a0 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 16:41:30 +0000 Subject: [PATCH 16/25] Testing --- install/proxmox/README.md | 2 +- install/proxmox/proxmox-install-netalertx.sh | 42 +++----------------- 2 files changed, 6 insertions(+), 38 deletions(-) diff --git a/install/proxmox/README.md b/install/proxmox/README.md index aa53218d..b179479c 100644 --- a/install/proxmox/README.md +++ b/install/proxmox/README.md @@ -192,4 +192,4 @@ This installer will need a maintainer --- -**Note**: This installer was designed for a Proxmox LXC Debian 13 container. For other systems, please use the appropriate installer or manual installation instructions. +**Note**: This installer was designed for a Proxmox LXC Debian 13 or Ubuntu 24 containers. For other systems, please use the appropriate installer or manual installation instructions. diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 8aef0c9b..ad5fcb9c 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -183,42 +183,8 @@ printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up printf "%b\n" "--------------------------------------------------------------------------" python3 -m venv /opt/myenv source /opt/myenv/bin/activate - -# Use python3 explicitly; avoid changing global python alternative - -# Create requirements.txt on-the-fly -cat > /tmp/requirements.txt << EOF -openwrt-luci-rpc -asusrouter -asyncio -aiohttp -graphene -flask -flask-cors -unifi-sm-api -tplink-omada-client -wakeonlan -pycryptodome -requests -paho-mqtt -scapy -cron-converter -pytz -json2table -dhcp-leases -pyunifi -speedtest-cli -chardet -python-nmap -dnspython -librouteros -yattag -git+https://github.com/foreign-sub/aiofreepybox.git -EOF - python -m pip install --upgrade pip -python -m pip install -r /tmp/requirements.txt -rm /tmp/requirements.txt +python -m pip install -r "${INSTALLER_DIR}/requirements.txt" # 4. CLONE OR UPDATE APPLICATION REPOSITORY printf "%b\n" "--------------------------------------------------------------------------" @@ -226,7 +192,7 @@ printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning app printf "%b\n" "--------------------------------------------------------------------------" mkdir -p "$INSTALL_DIR" -git clone -b proxmox-baremetal-installer https://github.com/jokob-sk/NetAlertX.git "$INSTALL_DIR/" #change after testing +git clone -b proxmox-baremetal-installer https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" @@ -277,7 +243,6 @@ if [ -n "${PORT-}" ]; then printf "%b\n" "--------------------------------------------------------------------------" # Update the template to reflect the right port sed -i "s/listen 20211;/listen ${PORT};/g" "${INSTALL_DIR}/config/${NGINX_CONF_FILE}" - sed -i "s/listen /listen ${LISTEN_ADDR}:/g" "${INSTALL_DIR}/config/${NGINX_CONF_FILE}" # Warn if port is already in use if ss -ltn | awk '{print $4}' | grep -q ":${PORT}$"; then printf "%b\n" "--------------------------------------------------------------------------" @@ -286,6 +251,9 @@ if [ -n "${PORT-}" ]; then fi fi +# Create symbolic link to NGINX configuration coming with NetAlertX +ln -sfn "${INSTALL_DIR}/config/${NGINX_CONF_FILE}" "${NGINX_CONFIG}" + # Run the hardware vendors update at least once printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[VENDORS UPDATE] ${RESET}Run the hardware vendors update" From dd113f7940634516003452d0d2f0d5d487619585 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 16:45:59 +0000 Subject: [PATCH 17/25] testing --- install/proxmox/proxmox-install-netalertx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index ad5fcb9c..5df5e6be 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -192,7 +192,7 @@ printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning app printf "%b\n" "--------------------------------------------------------------------------" mkdir -p "$INSTALL_DIR" -git clone -b proxmox-baremetal-installer https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing +git clone -b baremetal-installer https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" From 70d63febda09b28a8efd84a9d0a24329158c5fac Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 18:14:51 +0000 Subject: [PATCH 18/25] Tweak log file paths --- install/proxmox/proxmox-install-netalertx.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 5df5e6be..809d051c 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -302,11 +302,12 @@ chown -R www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" mkdir -p "${INSTALL_DIR}"/log/plugins chown -R www-data:www-data "${INSTALL_DIR}"/log/plugins -# Create the execution_queue.log file if it doesn't exist -touch "${INSTALL_DIR}"/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} -touch "${INSTALL_DIR}"/api/user_notifications.json -chown -R www-data:www-data "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api -chmod -R ug+rwX "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api +# Ensure plugins directory and log files exist right after mounting tmpfs +mkdir -p "${INSTALL_DIR}/log/plugins" +touch "${INSTALL_DIR}/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}" +touch "${INSTALL_DIR}/api/user_notifications.json" +chown -R www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" +chmod -R ug+rwX "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up DB and CONF files" From 98d69e1ce82e619545a780039320f2b74701828a Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 18:17:43 +0000 Subject: [PATCH 19/25] Restart nginx --- install/proxmox/proxmox-install-netalertx.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 809d051c..097888a3 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -387,6 +387,7 @@ EOF systemctl daemon-reload systemctl enable netalertx.service systemctl start netalertx.service +systemctl restart nginx # Verify service is running if systemctl is-active --quiet netalertx.service; then From 35cd8003b87d195df03fe5441dfefc797554c33b Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 18:38:00 +0000 Subject: [PATCH 20/25] Fix logs --- install/proxmox/proxmox-install-netalertx.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 097888a3..289e33ad 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -302,12 +302,11 @@ chown -R www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" mkdir -p "${INSTALL_DIR}"/log/plugins chown -R www-data:www-data "${INSTALL_DIR}"/log/plugins -# Ensure plugins directory and log files exist right after mounting tmpfs -mkdir -p "${INSTALL_DIR}/log/plugins" -touch "${INSTALL_DIR}/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log}" -touch "${INSTALL_DIR}/api/user_notifications.json" -chown -R www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" -chmod -R ug+rwX "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" +# Create the execution_queue.log file if it doesn't exist +touch ${INSTALL_DIR}/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} +touch ${INSTALL_DIR}/api/user_notifications.json +chown -R www-data:www-data "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api +chmod -R ug+rwX "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up DB and CONF files" From 683f4e6c2d7b6d003ee7f1e8e95f1ff2e9b89bdf Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 18:53:37 +0000 Subject: [PATCH 21/25] Move clone before setting up python env --- install/proxmox/proxmox-install-netalertx.sh | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 289e33ad..87c7c456 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -177,22 +177,13 @@ if command -v systemctl >/dev/null 2>&1; then systemctl restart nginx || true fi -# 3. SET UP PYTHON VIRTUAL ENVIRONMENT & DEPENDENCIES -printf "%b\n" "--------------------------------------------------------------------------" -printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up Python environment" -printf "%b\n" "--------------------------------------------------------------------------" -python3 -m venv /opt/myenv -source /opt/myenv/bin/activate -python -m pip install --upgrade pip -python -m pip install -r "${INSTALLER_DIR}/requirements.txt" - -# 4. CLONE OR UPDATE APPLICATION REPOSITORY +# 3. CLONE OR UPDATE APPLICATION REPOSITORY printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Cloning application repository and setup" printf "%b\n" "--------------------------------------------------------------------------" mkdir -p "$INSTALL_DIR" -git clone -b baremetal-installer https://github.com/JVKeller/NetAlertX.git "$INSTALL_DIR/" #change after testing +git clone -b baremetal-installer https://github.com/jokob-sk/NetAlertX.git "$INSTALL_DIR/" #change after testing if [ ! -f "$INSTALL_DIR/front/buildtimestamp.txt" ]; then date +%s > "$INSTALL_DIR/front/buildtimestamp.txt" @@ -207,6 +198,15 @@ printf "%b\n" "----------------------------------------------------------------- # Stop any existing NetAlertX python server process (narrow pattern) pkill -f "^python(3)?\s+.*${INSTALL_DIR}/server/?$" 2>/dev/null || true +# 4. SET UP PYTHON VIRTUAL ENVIRONMENT & DEPENDENCIES +printf "%b\n" "--------------------------------------------------------------------------" +printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up Python environment" +printf "%b\n" "--------------------------------------------------------------------------" +python3 -m venv /opt/myenv +source /opt/myenv/bin/activate +python -m pip install --upgrade pip +python -m pip install -r "${INSTALLER_DIR}/requirements.txt" + # Backup default NGINX site just in case if [ -L /etc/nginx/sites-enabled/default ] ; then rm /etc/nginx/sites-enabled/default From b34269d043fd3d396ad23377b61f3fc5d8718655 Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 19:04:46 +0000 Subject: [PATCH 22/25] Misc tweaks --- install/proxmox/proxmox-install-netalertx.sh | 4 ++-- install/proxmox/requirements.txt | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 87c7c456..1a71f245 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -339,7 +339,7 @@ nginx -t || { /etc/init.d/nginx start # Make a start script -cat > "$INSTALL_DIR/start.netalertx.sh" << 'EOF' +cat > "$INSTALL_DIR/start.netalertx.sh" << EOF #!/usr/bin/env bash # Activate the virtual python environment @@ -371,7 +371,7 @@ Wants=network-online.target Type=simple User=www-data Group=www-data -ExecStart=/bin/bash -lc '/app/start.netalertx.sh' +ExecStart=/app/start.netalertx.sh WorkingDirectory=/app Restart=on-failure RestartSec=5 diff --git a/install/proxmox/requirements.txt b/install/proxmox/requirements.txt index d1127387..2525b62e 100644 --- a/install/proxmox/requirements.txt +++ b/install/proxmox/requirements.txt @@ -1,6 +1,5 @@ openwrt-luci-rpc asusrouter -asyncio aiohttp graphene flask From d7e6ff268837042c1d371ed1ad7266acf0aad1cc Mon Sep 17 00:00:00 2001 From: Jeff Keller Date: Thu, 2 Oct 2025 19:41:06 +0000 Subject: [PATCH 23/25] Fix log permissions --- install/proxmox/proxmox-install-netalertx.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/install/proxmox/proxmox-install-netalertx.sh b/install/proxmox/proxmox-install-netalertx.sh index 1a71f245..f78740b6 100755 --- a/install/proxmox/proxmox-install-netalertx.sh +++ b/install/proxmox/proxmox-install-netalertx.sh @@ -308,6 +308,20 @@ touch ${INSTALL_DIR}/api/user_notifications.json chown -R www-data:www-data "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api chmod -R ug+rwX "${INSTALL_DIR}"/log "${INSTALL_DIR}"/api +# Set ownership of the tmpfs mountpoints first. +chown www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" + +# Ensure plugins directory exists within the tmpfs mount +mkdir -p "${INSTALL_DIR}/log/plugins" + +# Create log and api files directly as the www-data user to ensure correct ownership from the start. +sudo -u www-data touch ${INSTALL_DIR}/log/{app.log,execution_queue.log,app_front.log,app.php_errors.log,stderr.log,stdout.log,db_is_locked.log} +sudo -u www-data touch ${INSTALL_DIR}/api/user_notifications.json + +# Set final permissions for all created files and directories. +chown -R www-data:www-data "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" +chmod -R ug+rwX "${INSTALL_DIR}/log" "${INSTALL_DIR}/api" + printf "%b\n" "--------------------------------------------------------------------------" printf "%b\n" "${GREEN}[INSTALLING] ${RESET}Setting up DB and CONF files" printf "%b\n" "--------------------------------------------------------------------------" From 521bf541231bad188df1581184d0f608c478d011 Mon Sep 17 00:00:00 2001 From: Gonzague Dambricourt Date: Fri, 3 Oct 2025 10:40:03 +0200 Subject: [PATCH 24/25] Update HW_INSTALL.md Fixing references to the Ubuntu install script --- docs/HW_INSTALL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/HW_INSTALL.md b/docs/HW_INSTALL.md index 9fa7e60b..e96248de 100755 --- a/docs/HW_INSTALL.md +++ b/docs/HW_INSTALL.md @@ -65,11 +65,11 @@ wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/debian12/ ### Installation via curl ```bash -curl -o install.ubuntu24.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.ubuntu24.sh && sudo chmod +x install.ubuntu24.sh && sudo ./install.ubuntu24.sh +curl -o install.sh https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.sh && sudo chmod +x install.sh && sudo ./install.sh ``` ### Installation via wget ```bash -wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.ubuntu24.sh -O install.ubuntu24.sh && sudo chmod +x install.ubuntu24.sh && sudo ./install.ubuntu24.sh +wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/install.sh -O install.sh && sudo chmod +x install.sh && sudo ./install.sh ``` From ab3f9046d27b8297a3feddbdf69e59adec81bbbf Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Fri, 3 Oct 2025 17:27:27 -0400 Subject: [PATCH 25/25] Update timestamp format to use UTC timezone Remove deprecated API utilization. --- back/speedtest-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/speedtest-cli b/back/speedtest-cli index 186b5292..1fc0b46b 100755 --- a/back/speedtest-cli +++ b/back/speedtest-cli @@ -957,7 +957,7 @@ class SpeedtestResults(object): self.client = client or {} self._share = None - self.timestamp = '%sZ' % datetime.datetime.utcnow().isoformat() + self.timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z') self.bytes_received = 0 self.bytes_sent = 0