Merge pull request #1271 from jokob-sk/next_release

Next release
This commit is contained in:
Jokob @NetAlertX
2025-11-11 07:43:09 +11:00
committed by GitHub
271 changed files with 8642 additions and 5864 deletions

View File

@@ -46,14 +46,16 @@ ARG INSTALL_DIR=/app
# NetAlertX app directories
ENV NETALERTX_APP=${INSTALL_DIR}
ENV NETALERTX_CONFIG=${NETALERTX_APP}/config
ENV NETALERTX_DATA=/data
ENV NETALERTX_CONFIG=${NETALERTX_DATA}/config
ENV NETALERTX_FRONT=${NETALERTX_APP}/front
ENV NETALERTX_PLUGINS=${NETALERTX_FRONT}/plugins
ENV NETALERTX_SERVER=${NETALERTX_APP}/server
ENV NETALERTX_API=${NETALERTX_APP}/api
ENV NETALERTX_DB=${NETALERTX_APP}/db
ENV NETALERTX_API=/tmp/api
ENV NETALERTX_DB=${NETALERTX_DATA}/db
ENV NETALERTX_DB_FILE=${NETALERTX_DB}/app.db
ENV NETALERTX_BACK=${NETALERTX_APP}/back
ENV NETALERTX_LOG=${NETALERTX_APP}/log
ENV NETALERTX_LOG=/tmp/log
ENV NETALERTX_PLUGINS_LOG=${NETALERTX_LOG}/plugins
ENV NETALERTX_CONFIG_FILE=${NETALERTX_CONFIG}/app.conf
@@ -70,6 +72,7 @@ ENV LOG_EXECUTION_QUEUE=${NETALERTX_LOG}/execution_queue.log
ENV LOG_REPORT_OUTPUT_JSON=${NETALERTX_LOG}/report_output.json
ENV LOG_STDOUT=${NETALERTX_LOG}/stdout.log
ENV LOG_CROND=${NETALERTX_LOG}/crond.log
ENV LOG_NGINX_ERROR=${NETALERTX_LOG}/nginx-error.log
# System Services configuration files
ENV ENTRYPOINT_CHECKS=/entrypoint.d
@@ -78,25 +81,26 @@ ENV SYSTEM_SERVICES_SCRIPTS=${SYSTEM_SERVICES}/scripts
ENV SYSTEM_SERVICES_CONFIG=${SYSTEM_SERVICES}/config
ENV SYSTEM_NGINX_CONFIG=${SYSTEM_SERVICES_CONFIG}/nginx
ENV SYSTEM_NGINX_CONFIG_FILE=${SYSTEM_NGINX_CONFIG}/nginx.conf
ENV SYSTEM_SERVICES_ACTIVE_CONFIG=${SYSTEM_NGINX_CONFIG}/conf.active
ENV SYSTEM_SERVICES_ACTIVE_CONFIG=/tmp/nginx/active-config
ENV SYSTEM_SERVICES_PHP_FOLDER=${SYSTEM_SERVICES_CONFIG}/php
ENV SYSTEM_SERVICES_PHP_FPM_D=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.d
ENV SYSTEM_SERVICES_CROND=${SYSTEM_SERVICES_CONFIG}/crond
ENV SYSTEM_SERVICES_RUN=${SYSTEM_SERVICES}/run
ENV SYSTEM_SERVICES_RUN=/tmp/run
ENV SYSTEM_SERVICES_RUN_TMP=${SYSTEM_SERVICES_RUN}/tmp
ENV SYSTEM_SERVICES_RUN_LOG=${SYSTEM_SERVICES_RUN}/logs
ENV PHP_FPM_CONFIG_FILE=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.conf
ENV READ_ONLY_FOLDERS="${NETALERTX_BACK} ${NETALERTX_FRONT} ${NETALERTX_SERVER} ${SYSTEM_SERVICES} \
${SYSTEM_SERVICES_CONFIG} ${ENTRYPOINT_CHECKS}"
ENV READ_WRITE_FOLDERS="${NETALERTX_CONFIG} ${NETALERTX_DB} ${NETALERTX_API} ${NETALERTX_LOG} \
${NETALERTX_PLUGINS_LOG} ${SYSTEM_SERVICES_RUN} ${SYSTEM_SERVICES_RUN_TMP} \
${SYSTEM_SERVICES_RUN_LOG}"
ENV READ_WRITE_FOLDERS="${NETALERTX_DATA} ${NETALERTX_CONFIG} ${NETALERTX_DB} ${NETALERTX_API} \
${NETALERTX_LOG} ${NETALERTX_PLUGINS_LOG} ${SYSTEM_SERVICES_RUN} \
${SYSTEM_SERVICES_RUN_TMP} ${SYSTEM_SERVICES_RUN_LOG} \
${SYSTEM_SERVICES_ACTIVE_CONFIG}"
#Python environment
ENV PYTHONUNBUFFERED=1
ENV VIRTUAL_ENV=/opt/venv
ENV VIRTUAL_ENV_BIN=/opt/venv/bin
ENV PYTHONPATH=${NETALERTX_APP}:${NETALERTX_SERVER}:${VIRTUAL_ENV}/lib/python3.12/site-packages
ENV PYTHONPATH=${NETALERTX_APP}:${NETALERTX_SERVER}:${NETALERTX_PLUGINS}:${VIRTUAL_ENV}/lib/python3.12/site-packages
ENV PATH="${SYSTEM_SERVICES}:${VIRTUAL_ENV_BIN}:$PATH"
# App Environment
@@ -104,7 +108,7 @@ ENV LISTEN_ADDR=0.0.0.0
ENV PORT=20211
ENV NETALERTX_DEBUG=0
ENV VENDORSPATH=/app/back/ieee-oui.txt
ENV VENDORSPATH_NEWEST=/services/run/tmp/ieee-oui.txt
ENV VENDORSPATH_NEWEST=${SYSTEM_SERVICES_RUN_TMP}/ieee-oui.txt
ENV ENVIRONMENT=alpine
ENV READ_ONLY_USER=readonly READ_ONLY_GROUP=readonly
ENV NETALERTX_USER=netalertx NETALERTX_GROUP=netalertx
@@ -128,8 +132,9 @@ COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} install/production-filesystem/
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} --chmod=755 back ${NETALERTX_BACK}
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} --chmod=755 front ${NETALERTX_FRONT}
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} --chmod=755 server ${NETALERTX_SERVER}
RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 755 ${NETALERTX_API} \
${NETALERTX_LOG} ${SYSTEM_SERVICES_RUN_TMP} ${SYSTEM_SERVICES_RUN_LOG} && \
# Create required folders with correct ownership and permissions
RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 700 ${READ_WRITE_FOLDERS} && \
sh -c "find ${NETALERTX_APP} -type f \( -name '*.sh' -o -name 'speedtest-cli' \) \
-exec chmod 750 {} \;"
@@ -211,11 +216,14 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
# .devcontainer/scripts/generate-configs.sh
# The generator appends this stage to produce .devcontainer/Dockerfile.
# Prefer to place dev-only setup here; use setup.sh only for runtime fixes.
# Permissions in devcontainer should be of a brutalist nature. They will be
# Open and wide to avoid permission issues during development allowing max
# flexibility.
FROM runner AS netalertx-devcontainer
ENV INSTALL_DIR=/app
ENV PYTHONPATH=/workspaces/NetAlertX/test:/workspaces/NetAlertX/server:/app:/app/server:/opt/venv/lib/python3.12/site-packages:/usr/lib/python3.12/site-packages
ENV PYTHONPATH=${PYTHONPATH}:/workspaces/NetAlertX/test:/workspaces/NetAlertX/server:/usr/lib/python3.12/site-packages
ENV PATH=/services:${PATH}
ENV PHP_INI_SCAN_DIR=/services/config/php/conf.d:/etc/php83/conf.d
ENV LISTEN_ADDR=0.0.0.0
@@ -226,16 +234,28 @@ COPY .devcontainer/resources/devcontainer-overlay/ /
USER root
# Install common tools, create user, and set up sudo
RUN apk add --no-cache git nano vim jq php83-pecl-xdebug py3-pip nodejs sudo gpgconf pytest \
pytest-cov fish shfmt github-cli py3-yaml py3-docker-py docker-cli docker-cli-buildx \
pytest-cov zsh alpine-zsh-config shfmt github-cli py3-yaml py3-docker-py docker-cli docker-cli-buildx \
docker-cli-compose
RUN install -d -o netalertx -g netalertx -m 755 /services/php/modules && \
cp -a /usr/lib/php83/modules/. /services/php/modules/ && \
echo "${NETALERTX_USER} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN mkdir /workspaces && \
install -d -o netalertx -g netalertx -m 777 /services/run/logs && \
install -d -o netalertx -g netalertx -m 777 /app/run/tmp/client_body && \
sed -i -e 's|:/app:|:/workspaces:|' /etc/passwd && \
ENV SHELL=/bin/zsh
RUN mkdir -p /workspaces && \
install -d -m 777 /data /data/config /data/db && \
install -d -m 777 /tmp/log /tmp/log/plugins /tmp/api /tmp/run /tmp/nginx && \
install -d -m 777 /tmp/nginx/active-config /tmp/nginx/client_body /tmp/nginx/config && \
install -d -m 777 /tmp/nginx/fastcgi /tmp/nginx/proxy /tmp/nginx/scgi /tmp/nginx/uwsgi && \
install -d -m 777 /tmp/run/tmp /tmp/run/logs && \
chmod 777 /workspaces && \
chown -R netalertx:netalertx /data && \
chmod 666 /data/config/app.conf /data/db/app.db && \
chmod 1777 /tmp && \
install -d -o root -g root -m 1777 /tmp/.X11-unix && \
mkdir -p /home/netalertx && \
chown netalertx:netalertx /home/netalertx && \
sed -i -e 's#/app:#/workspaces:#' /etc/passwd && \
find /opt/venv -type d -exec chmod o+rwx {} \;
USER netalertx

View File

@@ -0,0 +1,37 @@
{
"folders": [
{
"name": "NetAlertX Source",
"path": "/workspaces/NetAlertX"
},
{
"name": "💾 NetAlertX Data",
"path": "/data"
},
{
"name": "🔍 Active NetAlertX log",
"path": "/tmp/log"
},
{
"name": "🌐 Active NetAlertX nginx",
"path": "/tmp/nginx"
},
{
"name": "📊 Active NetAlertX api",
"path": "/tmp/api"
},
{
"name": "⚙️ Active NetAlertX run",
"path": "/tmp/run"
}
],
"settings": {
"terminal.integrated.suggest.enabled": true,
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/usr/bin/fish"
}
}
}
}

View File

@@ -19,6 +19,17 @@ Common workflows (F1->Tasks: Run Task)
- Backend (GraphQL/Flask): `.devcontainer/scripts/restart-backend.sh` starts it under debugpy and logs to `/app/log/app.log`
- Frontend (nginx + PHP-FPM): Started via setup.sh; can be restarted by the task "Start Frontend (nginx and PHP-FPM)".
Production Container Evaulation
1. F1 → Tasks: Shutdown services ([Dev Container] Stop Frontend & Backend Services)
2. F1 → Tasks: Docker system and build prune ([Any] Docker system and build Prune)
3. F1 → Remote: Close Unused Forwarded Ports (VS Code command)
4. F1 → Tasks: Build & Launch Production (Build & Launch Prodcution Docker
5. visit http://localhost:20211
Unit tests
1. F1 → Tasks: Rebuild test container ([Any] Build Unit Test Docker image)
2. F1 → Test: Run all tests
Testing
- pytest is installed via Alpine packages (py3-pytest, py3-pytest-cov).
- PYTHONPATH includes workspace and venv site-packages so tests can import `server/*` modules and third-party libs.

View File

@@ -0,0 +1,26 @@
# NetAlertX Multi-Folder Workspace
This repository uses a multi-folder workspace configuration to provide easy access to runtime directories.
## Opening the Multi-Folder Workspace
After the devcontainer builds, open the workspace file to access all folders:
1. **File****Open Workspace from File**
2. Select `NetAlertX.code-workspace`
Or use Command Palette (Ctrl+Shift+P / Cmd+Shift+P):
- Type: `Workspaces: Open Workspace from File`
- Select `NetAlertX.code-workspace`
## Workspace Folders
The workspace includes:
- **NetAlertX** - Main source code
- **/tmp** - Runtime temporary files
- **/tmp/api** - API response cache (JSON files)
- **/tmp/log** - Application and plugin logs
## Testing Configuration
Pytest is configured to only discover tests in the main `test/` directory, not in `/tmp` folders.

View File

@@ -2,6 +2,8 @@
"name": "NetAlertX DevContainer",
"remoteUser": "netalertx",
"workspaceFolder": "/workspaces/NetAlertX",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/NetAlertX,type=bind,consistency=cached",
"onCreateCommand": "mkdir -p /tmp/api /tmp/log",
"build": {
"dockerfile": "./Dockerfile", // Dockerfile generated by script
"context": "../", // Context is the root of the repository
@@ -44,7 +46,8 @@
},
"postCreateCommand": {
"Install Pip Requirements": "/opt/venv/bin/pip3 install pytest docker debugpy"
"Install Pip Requirements": "/opt/venv/bin/pip3 install pytest docker debugpy",
"Workspace Instructions": "printf '\n\n<> DevContainer Ready!\n\n📁 To access /tmp folders in the workspace:\n File → Open Workspace from File → NetAlertX.code-workspace\n\n📖 See .devcontainer/WORKSPACE.md for details\n\n'"
},
"postStartCommand": {
"Start Environment":"${containerWorkspaceFolder}/.devcontainer/scripts/setup.sh",
@@ -70,15 +73,25 @@
"esbenp.prettier-vscode",
"eamodio.gitlens",
"alexcvzz.vscode-sqlite",
"yzhang.markdown-all-in-one",
"mkhl.shfmt"
"mkhl.shfmt",
"charliermarsh.ruff",
"ms-python.flake8"
],
"settings": {
"terminal.integrated.cwd": "${containerWorkspaceFolder}",
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/bin/zsh",
"args": ["-l"]
}
},
"terminal.integrated.defaultProfile.linux": "zsh",
// Python testing configuration
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestArgs": ["test"],
"python.testing.cwd": "${containerWorkspaceFolder}",
// Make sure we discover tests and import server correctly
"python.analysis.extraPaths": [
"/workspaces/NetAlertX",

View File

@@ -3,11 +3,14 @@
# .devcontainer/scripts/generate-configs.sh
# The generator appends this stage to produce .devcontainer/Dockerfile.
# Prefer to place dev-only setup here; use setup.sh only for runtime fixes.
# Permissions in devcontainer should be of a brutalist nature. They will be
# Open and wide to avoid permission issues during development allowing max
# flexibility.
FROM runner AS netalertx-devcontainer
ENV INSTALL_DIR=/app
ENV PYTHONPATH=/workspaces/NetAlertX/test:/workspaces/NetAlertX/server:/app:/app/server:/opt/venv/lib/python3.12/site-packages:/usr/lib/python3.12/site-packages
ENV PYTHONPATH=${PYTHONPATH}:/workspaces/NetAlertX/test:/workspaces/NetAlertX/server:/usr/lib/python3.12/site-packages
ENV PATH=/services:${PATH}
ENV PHP_INI_SCAN_DIR=/services/config/php/conf.d:/etc/php83/conf.d
ENV LISTEN_ADDR=0.0.0.0
@@ -18,16 +21,28 @@ COPY .devcontainer/resources/devcontainer-overlay/ /
USER root
# Install common tools, create user, and set up sudo
RUN apk add --no-cache git nano vim jq php83-pecl-xdebug py3-pip nodejs sudo gpgconf pytest \
pytest-cov fish shfmt github-cli py3-yaml py3-docker-py docker-cli docker-cli-buildx \
pytest-cov zsh alpine-zsh-config shfmt github-cli py3-yaml py3-docker-py docker-cli docker-cli-buildx \
docker-cli-compose
RUN install -d -o netalertx -g netalertx -m 755 /services/php/modules && \
cp -a /usr/lib/php83/modules/. /services/php/modules/ && \
echo "${NETALERTX_USER} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN mkdir /workspaces && \
install -d -o netalertx -g netalertx -m 777 /services/run/logs && \
install -d -o netalertx -g netalertx -m 777 /app/run/tmp/client_body && \
sed -i -e 's|:/app:|:/workspaces:|' /etc/passwd && \
ENV SHELL=/bin/zsh
RUN mkdir -p /workspaces && \
install -d -m 777 /data /data/config /data/db && \
install -d -m 777 /tmp/log /tmp/log/plugins /tmp/api /tmp/run /tmp/nginx && \
install -d -m 777 /tmp/nginx/active-config /tmp/nginx/client_body /tmp/nginx/config && \
install -d -m 777 /tmp/nginx/fastcgi /tmp/nginx/proxy /tmp/nginx/scgi /tmp/nginx/uwsgi && \
install -d -m 777 /tmp/run/tmp /tmp/run/logs && \
chmod 777 /workspaces && \
chown -R netalertx:netalertx /data && \
chmod 666 /data/config/app.conf /data/db/app.db && \
chmod 1777 /tmp && \
install -d -o root -g root -m 1777 /tmp/.X11-unix && \
mkdir -p /home/netalertx && \
chown netalertx:netalertx /home/netalertx && \
sed -i -e 's#/app:#/workspaces:#' /etc/passwd && \
find /opt/venv -type d -exec chmod o+rwx {} \;
USER netalertx

View File

@@ -8,7 +8,9 @@ worker_processes auto;
pcre_jit on;
# Configures default error logger.
error_log /app/log/nginx-error.log warn;
error_log /tmp/log/nginx-error.log warn;
pid /tmp/run/nginx.pid;
events {
# The maximum number of simultaneous connections that can be opened by
@@ -17,13 +19,12 @@ events {
}
http {
# Mapping of temp paths for various nginx modules.
client_body_temp_path /services/run/tmp/client_body;
proxy_temp_path /services/run/tmp/proxy;
fastcgi_temp_path /services/run/tmp/fastcgi;
uwsgi_temp_path /services/run/tmp/uwsgi;
scgi_temp_path /services/run/tmp/scgi;
client_body_temp_path /tmp/nginx/client_body;
proxy_temp_path /tmp/nginx/proxy;
fastcgi_temp_path /tmp/nginx/fastcgi;
uwsgi_temp_path /tmp/nginx/uwsgi;
scgi_temp_path /tmp/nginx/scgi;
# Includes mapping of file name extensions to MIME types of responses
# and defines the default type.
@@ -89,7 +90,7 @@ http {
'"$http_user_agent" "$http_x_forwarded_for"';
# Sets the path, format, and configuration for a buffered log write.
access_log /app/log/nginx-access.log main;
access_log /tmp/log/nginx-access.log main;
# Virtual host config
@@ -100,11 +101,10 @@ http {
index index.php;
add_header X-Forwarded-Prefix "/app" always;
location ~* \.php$ {
# Set Cache-Control header to prevent caching on the first load
add_header Cache-Control "no-store";
fastcgi_pass unix:/services/run/php.sock;
fastcgi_pass unix:/tmp/run/php.sock;
include /services/config/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;

View File

@@ -0,0 +1,47 @@
# NetAlertX devcontainer zsh configuration
# Keep this lightweight and deterministic so shells behave consistently.
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=vim
export SHELL=/bin/zsh
# Start inside the workspace if it exists
if [ -d "/workspaces/NetAlertX" ]; then
cd /workspaces/NetAlertX
fi
# Enable basic completion and prompt helpers
autoload -Uz compinit promptinit colors
colors
compinit -u
promptinit
# Friendly prompt with virtualenv awareness
setopt PROMPT_SUBST
_venv_segment() {
if [ -n "$VIRTUAL_ENV" ]; then
printf '(%s) ' "${VIRTUAL_ENV:t}"
fi
}
PROMPT='%F{green}$(_venv_segment)%f%F{cyan}%n@%m%f %F{yellow}%~%f %# '
RPROMPT='%F{magenta}$(git rev-parse --abbrev-ref HEAD 2>/dev/null)%f'
# Sensible defaults
setopt autocd
setopt correct
setopt extendedglob
HISTFILE="$HOME/.zsh_history"
HISTSIZE=5000
SAVEHIST=5000
alias ll='ls -alF'
alias la='ls -A'
alias gs='git status -sb'
alias gp='git pull --ff-only'
# Ensure pyenv/virtualenv activate hooks adjust the prompt cleanly
if [ -f "$HOME/.zshrc.local" ]; then
source "$HOME/.zshrc.local"
fi

View File

@@ -1,7 +1,11 @@
#!/bin/bash
set -euo pipefail
read -r -p "Are you sure you want to destroy your host docker containers and images? Type YES to continue: " reply
if [[ -n "${CONFIRM_PRUNE:-}" && "${CONFIRM_PRUNE}" == "YES" ]]; then
reply="YES"
else
read -r -p "Are you sure you want to destroy your host docker containers and images? Type YES to continue: " reply
fi
if [[ "${reply}" == "YES" ]]; then
docker system prune -af

View File

@@ -1,184 +1,110 @@
#!/bin/bash
# Runtime setup for devcontainer (executed after container starts).
# Prefer building setup into resources/devcontainer-Dockerfile when possible.
# Use this script for runtime-only adjustments (permissions, sockets, ownership,
# and services managed without init) that are difficult at build time.
id
# Define variables (paths, ports, environment)
export APP_DIR="/app"
export APP_COMMAND="/workspaces/NetAlertX/.devcontainer/scripts/restart-backend.sh"
export PHP_FPM_BIN="/usr/sbin/php-fpm83"
export CROND_BIN="/usr/sbin/crond -f"
# NetAlertX Devcontainer Setup Script
#
# This script forcefully resets all runtime state for a single-user devcontainer.
# It is intentionally idempotent: every run wipes and recreates all relevant folders,
# symlinks, and files, so the environment is always fresh and predictable.
#
# - No conditional logic: everything is (re)created, overwritten, or reset unconditionally.
# - No security hardening: this is for disposable, local dev use only.
# - No checks for existing files, mounts, or processes—just do the work.
#
# If you add new runtime files or folders, add them to the creation/reset section below.
#
# Do not add if-then logic or error handling for missing/existing files. Simplicity is the goal.
export ALWAYS_FRESH_INSTALL=false
export INSTALL_DIR=/app
export LOGS_LOCATION=/app/logs
export CONF_FILE="app.conf"
export DB_FILE="app.db"
export FULL_FILEDB_PATH="${INSTALL_DIR}/db/${DB_FILE}"
export OUI_FILE="/usr/share/arp-scan/ieee-oui.txt" # Define the path to ieee-oui.txt and ieee-iab.txt
export TZ=Europe/Paris
export PORT=20211
export SOURCE_DIR="/workspaces/NetAlertX"
SOURCE_DIR=${SOURCE_DIR:-/workspaces/NetAlertX}
PY_SITE_PACKAGES="${VIRTUAL_ENV:-/opt/venv}/lib/python3.12/site-packages"
SOURCE_SERVICES_DIR="${SOURCE_DIR}/install/production-filesystem/services"
LOG_FILES=(
LOG_APP
LOG_APP_FRONT
LOG_STDOUT
LOG_STDERR
LOG_EXECUTION_QUEUE
LOG_APP_PHP_ERRORS
LOG_IP_CHANGES
LOG_CROND
LOG_REPORT_OUTPUT_TXT
LOG_REPORT_OUTPUT_HTML
LOG_REPORT_OUTPUT_JSON
LOG_DB_IS_LOCKED
LOG_NGINX_ERROR
)
ensure_docker_socket_access() {
local socket="/var/run/docker.sock"
if [ ! -S "${socket}" ]; then
echo "docker socket not present; skipping docker group configuration"
return
fi
sudo chmod 666 /var/run/docker.sock 2>/dev/null || true
sudo chown "$(id -u)":"$(id -g)" /workspaces
sudo chmod 755 /workspaces
local sock_gid
sock_gid=$(stat -c '%g' "${socket}" 2>/dev/null || true)
if [ -z "${sock_gid}" ]; then
echo "unable to determine docker socket gid; skipping docker group configuration"
return
fi
killall php-fpm83 nginx crond python3 2>/dev/null || true
local group_entry=""
if command -v getent >/dev/null 2>&1; then
group_entry=$(getent group "${sock_gid}" 2>/dev/null || true)
else
group_entry=$(grep -E ":${sock_gid}:" /etc/group 2>/dev/null || true)
fi
# Mount ramdisks for volatile data
sudo mount -t tmpfs -o size=100m,mode=0777 tmpfs /tmp/log 2>/dev/null || true
sudo mount -t tmpfs -o size=50m,mode=0777 tmpfs /tmp/api 2>/dev/null || true
sudo mount -t tmpfs -o size=50m,mode=0777 tmpfs /tmp/run 2>/dev/null || true
sudo mount -t tmpfs -o size=50m,mode=0777 tmpfs /tmp/nginx 2>/dev/null || true
local group_name=""
if [ -n "${group_entry}" ]; then
group_name=$(echo "${group_entry}" | cut -d: -f1)
else
group_name="docker-host"
sudo addgroup -g "${sock_gid}" "${group_name}" 2>/dev/null || group_name=$(grep -E ":${sock_gid}:" /etc/group | head -n1 | cut -d: -f1)
fi
if [ -z "${group_name}" ]; then
echo "failed to resolve group for docker socket gid ${sock_gid}; skipping docker group configuration"
return
fi
if ! id -nG netalertx | tr ' ' '\n' | grep -qx "${group_name}"; then
sudo addgroup netalertx "${group_name}" 2>/dev/null || true
fi
}
main() {
echo "=== NetAlertX Development Container Setup ==="
killall php-fpm83 nginx crond python3 2>/dev/null
sleep 1
echo "Setting up ${SOURCE_DIR}..."
ensure_docker_socket_access
sudo chown $(id -u):$(id -g) /workspaces
sudo chmod 755 /workspaces
configure_source
echo "--- Starting Development Services ---"
configure_php
start_services
}
isRamDisk() {
if [ -z "$1" ] || [ ! -d "$1" ]; then
echo "Usage: isRamDisk <directory>" >&2
return 2
fi
local fstype
fstype=$(df -T "$1" | awk 'NR==2 {print $2}')
if [ "$fstype" = "tmpfs" ] || [ "$fstype" = "ramfs" ]; then
return 0 # Success (is a ramdisk)
else
return 1 # Failure (is not a ramdisk)
fi
}
# Setup source directory
configure_source() {
echo "[1/4] Configuring System..."
echo " -> Setting up /services permissions"
sudo chown -R netalertx /services
echo "[2/4] Configuring Source..."
echo " -> Cleaning up previous instances"
test -e ${NETALERTX_LOG} && sudo umount "${NETALERTX_LOG}" 2>/dev/null || true
test -e ${NETALERTX_API} && sudo umount "${NETALERTX_API}" 2>/dev/null || true
test -e ${NETALERTX_APP} && sudo rm -Rf ${NETALERTX_APP}/
echo " -> Linking source to ${NETALERTX_APP}"
sudo ln -s ${SOURCE_DIR}/ ${NETALERTX_APP}
echo " -> Mounting ramdisks for /log and /api"
mkdir -p ${NETALERTX_LOG} ${NETALERTX_API}
sudo mount -o uid=$(id -u netalertx),gid=$(id -g netalertx),mode=775 -t tmpfs -o size=256M tmpfs "${NETALERTX_LOG}"
sudo mount -o uid=$(id -u netalertx),gid=$(id -g netalertx),mode=775 -t tmpfs -o size=256M tmpfs "${NETALERTX_API}"
mkdir -p ${NETALERTX_PLUGINS_LOG}
touch ${NETALERTX_PLUGINS_LOG}/.dockerignore ${NETALERTX_API}/.dockerignore
# tmpfs mounts configured with netalertx ownership and 775 permissions above
touch /app/log/nginx_error.log
echo " -> Empty log"|tee ${INSTALL_DIR}/log/app.log \
${INSTALL_DIR}/log/app_front.log \
${INSTALL_DIR}/log/stdout.log
touch ${INSTALL_DIR}/log/stderr.log \
${INSTALL_DIR}/log/execution_queue.log
echo 0 > ${INSTALL_DIR}/log/db_is_locked.log
for f in ${INSTALL_DIR}/log/*.log; do
sudo chown netalertx:www-data $f
sudo chmod 664 $f
echo "" > $f
done
mkdir -p /app/log/plugins
sudo chown -R netalertx:www-data ${INSTALL_DIR}
while ps ax | grep -v grep | grep python3 > /dev/null; do
killall python3 &>/dev/null
sleep 0.2
done
sudo chmod 777 /opt/venv/lib/python3.12/site-packages/ && \
sudo chmod 005 /opt/venv/lib/python3.12/site-packages/
sudo chmod 666 /var/run/docker.sock
echo " -> Updating build timestamp"
date +%s > ${NETALERTX_FRONT}/buildtimestamp.txt
}
# configure_php: configure PHP-FPM and enable dev debug options
configure_php() {
echo "[3/4] Configuring PHP-FPM..."
sudo chown -R netalertx:netalertx ${SYSTEM_SERVICES_RUN} 2>/dev/null || true
}
# start_services: start crond, PHP-FPM, nginx and the application
start_services() {
echo "[4/4] Starting services"
sudo chmod +x /entrypoint.sh
setsid bash /entrypoint.sh&
sleep 1
}
sudo chmod 755 /app/
echo "Development $(git rev-parse --short=8 HEAD)"| sudo tee /app/.VERSION
# Run the main function
main
# create a services readme file
echo "This folder is auto-generated by the container and devcontainer setup.sh script." > /services/README.md
echo "Any changes here will be lost on rebuild. To make permanent changes, edit files in .devcontainer or production filesystem and rebuild the container." >> /services/README.md
echo "Only make temporary/test changes in this folder, then perform a rebuild to reset." >> /services/README.md
sudo chmod 777 /tmp/log /tmp/api /tmp/run /tmp/nginx
sudo rm -rf "${SYSTEM_NGINX_CONFIG}/conf.active"
sudo ln -s "${SYSTEM_SERVICES_ACTIVE_CONFIG}" "${SYSTEM_NGINX_CONFIG}/conf.active"
sudo rm -rf /entrypoint.d
sudo ln -s "${SOURCE_DIR}/install/production-filesystem/entrypoint.d" /entrypoint.d
sudo rm -rf "${NETALERTX_APP}"
sudo ln -s "${SOURCE_DIR}/" "${NETALERTX_APP}"
for dir in "${NETALERTX_DATA}" "${NETALERTX_CONFIG}" "${NETALERTX_DB}"; do
sudo install -d -m 777 "${dir}"
done
for dir in \
"${SYSTEM_SERVICES_RUN_LOG}" \
"${SYSTEM_SERVICES_ACTIVE_CONFIG}" \
"${NETALERTX_PLUGINS_LOG}" \
"/tmp/nginx/client_body" \
"/tmp/nginx/proxy" \
"/tmp/nginx/fastcgi" \
"/tmp/nginx/uwsgi" \
"/tmp/nginx/scgi"; do
sudo install -d -m 777 "${dir}"
done
# Create nginx temp subdirs with permissions
sudo mkdir -p "${SYSTEM_SERVICES_RUN_TMP}/client_body" "${SYSTEM_SERVICES_RUN_TMP}/proxy" "${SYSTEM_SERVICES_RUN_TMP}/fastcgi" "${SYSTEM_SERVICES_RUN_TMP}/uwsgi" "${SYSTEM_SERVICES_RUN_TMP}/scgi"
sudo chmod -R 777 "${SYSTEM_SERVICES_RUN_TMP}"
for var in "${LOG_FILES[@]}"; do
path=${!var}
dir=$(dirname "${path}")
sudo install -d -m 777 "${dir}"
touch "${path}"
done
printf '0\n' | sudo tee "${LOG_DB_IS_LOCKED}" >/dev/null
sudo chmod 777 "${LOG_DB_IS_LOCKED}"
sudo pkill -f python3 2>/dev/null || true
sudo chmod 777 "${PY_SITE_PACKAGES}" "${NETALERTX_DATA}" "${NETALERTX_DATA}"/* 2>/dev/null || true
sudo chmod 005 "${PY_SITE_PACKAGES}" 2>/dev/null || true
sudo chown -R "${NETALERTX_USER}:${NETALERTX_GROUP}" "${NETALERTX_APP}"
date +%s | sudo tee "${NETALERTX_FRONT}/buildtimestamp.txt" >/dev/null
sudo chmod 755 "${NETALERTX_APP}"
sudo chmod +x /entrypoint.sh
setsid bash /entrypoint.sh &
sleep 1
echo "Development $(git rev-parse --short=8 HEAD)" | sudo tee "${NETALERTX_APP}/.VERSION" >/dev/null

View File

@@ -1,4 +1,5 @@
.dockerignore
**/.dockerignore
.env
.git
.github

3
.flake8 Normal file
View File

@@ -0,0 +1,3 @@
[flake8]
max-line-length = 180
ignore = E221,E222,E251,E203

View File

@@ -46,7 +46,7 @@ body:
attributes:
label: app.conf
description: |
Paste your `app.conf` (remove personal info)
Paste relevant `app.conf`settings (remove sensitive info)
render: python
validations:
required: false
@@ -70,6 +70,13 @@ body:
- Bare-metal (community only support - Check Discord)
validations:
required: true
- type: checkboxes
attributes:
label: Debug or Trace enabled
description: I confirm I set `LOG_LEVEL` to `debug` or `trace`
options:
- label: I have read and followed the steps in the wiki link above and provided the required debug logs and the log section covers the time when the issue occurs.
required: true
- type: textarea
attributes:
label: app.log
@@ -78,13 +85,14 @@ body:
***Generally speaking, all bug reports should have logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
You can use `tail -100 /app/log/app.log` in the container if you have trouble getting to the log files.
You can use `tail -100 /app/log/app.log` in the container if you have trouble getting to the log files or send them to netalertx@gmail.com with the issue number.
validations:
required: false
- type: checkboxes
- type: textarea
attributes:
label: Debug enabled
description: I confirm I enabled `debug`
options:
- label: I have read and followed the steps in the wiki link above and provided the required debug logs and the log section covers the time when the issue occurs.
required: true
label: Docker Logs
description: |
You can retrieve the logs from Portainer -> Containers -> your NetAlertX container -> Logs or by running `sudo docker logs netalertx`.
validations:
required: true

View File

@@ -18,7 +18,7 @@ Backend loop phases (see `server/__main__.py` and `server/plugin.py`): `once`, `
## Plugin patterns that matter
- Manifest lives at `front/plugins/<code_name>/config.json`; `code_name` == folder, `unique_prefix` drives settings and filenames (e.g., `ARPSCAN`).
- Control via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).
- Data contract: scripts write `/app/log/plugins/last_result.<PREF>.log` (pipedelimited: 9 required cols + optional 4). Use `front/plugins/plugin_helper.py`s `Plugin_Objects` to sanitize text and normalize MACs, then `write_result_file()`.
- Data contract: scripts write `/tmp/log/plugins/last_result.<PREF>.log` (pipedelimited: 9 required cols + optional 4). Use `front/plugins/plugin_helper.py`s `Plugin_Objects` to sanitize text and normalize MACs, then `write_result_file()`.
- Device import: define `database_column_definitions` when creating/updating devices; watched fields trigger notifications.
### Standard Plugin Formats
@@ -30,6 +30,7 @@ Backend loop phases (see `server/__main__.py` and `server/plugin.py`): `once`, `
* other: Miscellaneous plugins. Runs at various times. Data source: self / Template.
### Plugin logging & outputs
- Always check relevant logs first.
- Use logging as shown in other plugins.
- Collect results with `Plugin_Objects.add_object(...)` during processing and call `plugin_objects.write_result_file()` exactly once at the end of the script.
- Prefer to log a brief summary before writing (e.g., total objects added) to aid troubleshooting; keep logs concise at `info` level and use `verbose` or `debug` for extra context.
@@ -42,22 +43,32 @@ Backend loop phases (see `server/__main__.py` and `server/plugin.py`): `once`, `
## Conventions & helpers to reuse
- Settings: add/modify via `ccd()` in `server/initialise.py` or perplugin manifest. Never hardcode ports or secrets; use `get_setting_value()`.
- Logging: use `logger.mylog(level, [message])`; levels: none/minimal/verbose/debug/trace.
- Time/MAC/strings: `helper.py` (`timeNowTZ`, `normalize_mac`, sanitizers). Validate MACs before DB writes.
- Time/MAC/strings: `helper.py` (`timeNowDB`, `normalize_mac`, sanitizers). Validate MACs before DB writes.
- DB helpers: prefer `server/db/db_helper.py` functions (e.g., `get_table_json`, device condition helpers) over raw SQL in new paths.
## Dev workflow (devcontainer)
- **Devcontainer philosophy: brutal simplicity.** One user, everything writable, completely idempotent. No permission checks, no conditional logic, no sudo needed. If something doesn't work, tear down the wall and rebuild - don't patch. We unit test permissions in the hardened build.
- **Permissions:** Never `chmod` or `chown` during operations. Everything is already writable. If you need permissions, the devcontainer setup is broken - fix `.devcontainer/scripts/setup.sh` or `.devcontainer/resources/devcontainer-Dockerfile` instead.
- **Files & Paths:** Use environment variables (`NETALERTX_DB`, `NETALERTX_LOG`, etc.) everywhere. `/data` for persistent config/db, `/tmp` for runtime logs/api/nginx state. Never hardcode `/data/db` or relative paths.
- **Database reset:** Use the `[Dev Container] Wipe and Regenerate Database` task. Kills backend, deletes `/data/{db,config}/*`, runs first-time setup scripts. Clean slate, no questions.
- Services: use tasks to (re)start backend and nginx/PHP-FPM. Backend runs with debugpy on 5678; attach a Python debugger if needed.
- Run a plugin manually: `python3 front/plugins/<code_name>/script.py` (ensure `sys.path` includes `/app/front/plugins` and `/app/server` like the template).
- Testing: pytest available via Alpine packages. Tests live in `test/`; app code is under `server/`. PYTHONPATH is preconfigured to include workspace and `/opt/venv` sitepackages.
- **Subprocess calls:** ALWAYS set explicit timeouts. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls (e.g., plugins calling external tools) need their own timeout - outer plugin timeout won't save you.
## What “done right” looks like
- When adding a plugin, start from `front/plugins/__template`, implement with `plugin_helper`, define manifest settings, and wire phase via `<PREF>_RUN`. Verify logs in `/app/log/plugins/` and data in `api/*.json`.
- When adding a plugin, start from `front/plugins/__template`, implement with `plugin_helper`, define manifest settings, and wire phase via `<PREF>_RUN`. Verify logs in `/tmp/log/plugins/` and data in `api/*.json`.
- When introducing new config, define it once (core `ccd()` or plugin manifest) and read it via helpers everywhere.
- When exposing new server functionality, add endpoints in `server/api_server/*` and keep authorization consistent; update UI by reading/writing JSON cache rather than bypassing the pipeline.
## Useful references
- Docs: `docs/PLUGINS_DEV.md`, `docs/SETTINGS_SYSTEM.md`, `docs/API_*.md`, `docs/DEBUG_*.md`
- Logs: backend `/app/log/app.log`, plugin logs under `/app/log/plugins/`, nginx/php logs under `/var/log/*`
- Logs: All logs are under `/tmp/log/`. Plugin logs are very shortly under `/tmp/log/plugins/` until picked up by the server.
- plugin logs: `/tmp/log/app.log`
- backend logs: `/tmp/log/stdout.log` and `/tmp/log/stderr.log`
- frontend commands logs: `/tmp/log/app_front.log`
- php errors: `/tmp/log/app.php_errors.log`
- nginx logs: `/tmp/log/nginx-access.log` and `/tmp/log/nginx-error.log`
## Assistant expectations:
- Be concise, opinionated, and biased toward security and simplicity.

View File

@@ -10,7 +10,7 @@ on:
branches:
- next_release
jobs:
jobs:
docker_dev:
runs-on: ubuntu-latest
timeout-minutes: 30
@@ -19,7 +19,8 @@ jobs:
packages: write
if: >
contains(github.event.head_commit.message, 'PUSHPROD') != 'True' &&
github.repository == 'jokob-sk/NetAlertX'
github.repository == 'jokob-sk/NetAlertX'
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -30,26 +31,36 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# --- Generate timestamped dev version
- name: Generate timestamp version
id: timestamp
run: |
ts=$(date -u +'%Y%m%d-%H%M%S')
echo "version=dev-${ts}" >> $GITHUB_OUTPUT
echo "Generated version: dev-${ts}"
- name: Set up dynamic build ARGs
id: getargs
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
- name: Get release version
id: get_version
run: echo "version=Dev" >> $GITHUB_OUTPUT
# --- Write the timestamped version to .VERSION file
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
run: echo "${{ steps.timestamp.outputs.version }}" > .VERSION
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/jokob-sk/netalertx-dev
jokobsk/netalertx-dev
tags: |
type=raw,value=latest
type=raw,value=${{ steps.timestamp.outputs.version }}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}

View File

@@ -6,7 +6,6 @@
# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.
name: Publish Docker image
on:
@@ -14,6 +13,7 @@ on:
types: [published]
tags:
- '*.[1-9]+[0-9]?.[1-9]+*'
jobs:
docker:
runs-on: ubuntu-latest
@@ -21,6 +21,7 @@ jobs:
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -31,42 +32,39 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up dynamic build ARGs
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
# --- Get release version from tag
- name: Get release version
id: get_version
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
# --- Write version to .VERSION file
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
run: echo "${{ steps.get_version.outputs.version }}" > .VERSION
# --- Generate Docker metadata and tags
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
ghcr.io/jokob-sk/netalertx
jokobsk/netalertx
# generate Docker tags based on the following events/attributes
jokobsk/netalertx
tags: |
type=semver,pattern={{version}},value=${{ inputs.version }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
type=semver,pattern={{major}},value=${{ inputs.version }}
type=semver,pattern={{version}},value=${{ steps.get_version.outputs.version }}
type=semver,pattern={{major}}.{{minor}},value=${{ steps.get_version.outputs.version }}
type=semver,pattern={{major}},value=${{ steps.get_version.outputs.version }}
type=ref,event=branch,suffix=-{{ sha }}
type=ref,event=pr
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
- name: Log in to Github Container registry
- name: Log in to Github Container Registry (GHCR)
uses: docker/login-action@v3
with:
registry: ghcr.io
username: jokob-sk
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
- name: Log in to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
@@ -81,6 +79,5 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# # ⚠ disable cache if build is failing to download debian packages
# cache-from: type=registry,ref=ghcr.io/jokob-sk/netalertx:buildcache
# cache-to: type=registry,ref=ghcr.io/jokob-sk/netalertx:buildcache,mode=max

View File

@@ -43,7 +43,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/jokob-sk/netalertx-dev-rewrite

View File

@@ -1,23 +0,0 @@
import sys, importlib
mods = [
'json', 'simplejson',
'httplib', 'http.client',
'urllib2', 'urllib.request',
'Queue', 'queue',
'cStringIO', 'StringIO', 'io',
'md5', 'hashlib',
'ssl'
]
print('PYTHON_EXE:' + sys.executable)
print('PYTHON_VER:' + sys.version.replace('\n', ' '))
for m in mods:
try:
mod = importlib.import_module(m)
ver = getattr(mod, '__version__', None)
if ver is None:
# try common attributes
ver = getattr(mod, 'version', None)
info = (' version=' + str(ver)) if ver is not None else ''
print('OK %s%s' % (m, info))
except Exception as e:
print('MISSING %s %s: %s' % (m, e.__class__.__name__, e))

8
.vscode/launch.json vendored
View File

@@ -29,6 +29,14 @@
"pathMappings": {
"/app": "${workspaceFolder}"
}
},
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
}
]
}

18
.vscode/settings.json vendored
View File

@@ -11,13 +11,23 @@
// Let the Python extension invoke pytest via the interpreter; avoid hardcoded paths
// Removed python.testing.pytestPath and legacy pytest.command overrides
"terminal.integrated.defaultProfile.linux": "fish",
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.profiles.linux": {
"fish": {
"path": "/usr/bin/fish"
"zsh": {
"path": "/bin/zsh"
}
}
,
// Fallback for older VS Code versions or schema validators that don't accept custom profiles
"terminal.integrated.shell.linux": "/usr/bin/fish"
"terminal.integrated.shell.linux": "/usr/bin/zsh"
,
"python.linting.flake8Enabled": true,
"python.linting.enabled": true,
"python.linting.flake8Args": [
"--config=.flake8"
],
"python.formatting.provider": "black",
"python.formatting.blackArgs": [
"--line-length=180"
]
}

93
.vscode/tasks.json vendored
View File

@@ -1,16 +1,27 @@
{
"version": "2.0.0",
"inputs": [
{
"id": "confirmPrune",
"type": "promptString",
"description": "DANGER! Type YES to confirm pruning all unused Docker resources. This will destroy containers, images, volumes, and networks!",
"default": ""
}
],
"tasks": [
{
"label": "[Any POSIX] Generate Devcontainer Configs",
"type": "shell",
"command": ".devcontainer/scripts/generate-configs.sh",
"detail": "Generates devcontainer configs from the template. This must be run after changes to devcontainer to combine/merge them into the final config used by VS Code. Note- this has no bearing on the production or test image.",
"presentation": {
"echo": true,
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
"showReuseMessage": false,
"group": "POSIX Tasks"
},
"problemMatcher": [],
"group": {
"kind": "build",
@@ -24,12 +35,19 @@
{
"label": "[Any] Docker system and build Prune",
"type": "shell",
"command": ".devcontainer/scripts/confirm-docker-prune.sh",
"command": ".devcontainer/scripts/confirm-docker-prune.sh",
"detail": "DANGER! Prunes all unused Docker resources (images, containers, volumes, networks). Any stopped container will be wiped and data will be lost. Use with caution.",
"options": {
"env": {
"CONFIRM_PRUNE": "${input:confirmPrune}"
}
},
"presentation": {
"echo": true,
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
"showReuseMessage": false,
"group": "Any"
},
"problemMatcher": [],
"group": {
@@ -45,6 +63,7 @@
"label": "[Dev Container] Re-Run Startup Script",
"type": "shell",
"command": "./isDevContainer.sh || exit 1;/workspaces/NetAlertX/.devcontainer/scripts/setup.sh",
"detail": "The startup script runs directly after the container is started. It reprovisions permissions, links folders, and performs other setup tasks. Run this if you have made changes to the setup script or need to reprovision the container.",
"options": {
"cwd": "/workspaces/NetAlertX/.devcontainer/scripts"
},
@@ -65,6 +84,7 @@
"label": "[Dev Container] Start Backend (Python)",
"type": "shell",
"command": "./isDevContainer.sh || exit 1; /services/start-backend.sh",
"detail": "Restarts the NetAlertX backend (Python) service in the dev container. This may take 5 seconds to be completely ready.",
"options": {
"cwd": "/workspaces/NetAlertX/.devcontainer/scripts"
},
@@ -73,7 +93,8 @@
"reveal": "always",
"panel": "shared",
"showReuseMessage": false,
"clear": false
"clear": false,
"group": "Devcontainer"
},
"problemMatcher": [],
"icon": {
@@ -85,6 +106,7 @@
"label": "[Dev Container] Start CronD (Scheduler)",
"type": "shell",
"command": "./isDevContainer.sh || exit 1; /services/start-crond.sh",
"detail": "Stops and restarts the crond service.",
"options": {
"cwd": "/workspaces/NetAlertX/.devcontainer/scripts"
},
@@ -93,7 +115,8 @@
"reveal": "always",
"panel": "shared",
"showReuseMessage": false,
"clear": false
"clear": false,
"group": "Devcontainer"
},
"problemMatcher": [],
"icon": {
@@ -105,6 +128,7 @@
"label": "[Dev Container] Start Frontend (nginx and PHP-FPM)",
"type": "shell",
"command": "./isDevContainer.sh || exit 1; /services/start-php-fpm.sh & /services/start-nginx.sh &",
"detail": "Stops and restarts the NetAlertX frontend services (nginx and PHP-FPM) in the dev container. This launches almost instantly.",
"options": {
"cwd": "/workspaces/NetAlertX/.devcontainer/scripts"
@@ -114,7 +138,8 @@
"reveal": "always",
"panel": "shared",
"showReuseMessage": false,
"clear": false
"clear": false,
"group": "Devcontainer"
},
"problemMatcher": [],
"icon": {
@@ -126,6 +151,7 @@
"label": "[Dev Container] Stop Frontend & Backend Services",
"type": "shell",
"command": "./isDevContainer.sh || exit 1; pkill -f 'php-fpm83|nginx|crond|python3' || true",
"detail": "Stops all NetAlertX services running in the dev container.",
"options": {
"cwd": "/workspaces/NetAlertX/.devcontainer/scripts"
},
@@ -133,7 +159,8 @@
"echo": true,
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
"showReuseMessage": false,
"group": "Devcontainer"
},
"problemMatcher": [],
"icon": {
@@ -142,29 +169,55 @@
}
},
{
"label": "[Dev Container] List NetAlertX Ports",
"label": "[Any] Build Unit Test Docker image",
"type": "shell",
"command": "list-ports.sh",
"options": {
"cwd": "/workspaces/NetAlertX/.devcontainer/scripts"
},
"command": "docker buildx build -t netalertx-test . && echo '🧪 Unit Test Docker image built: netalertx-test'",
"detail": "This must be run after changes to the container. Unit testing will not register changes until after this image is rebuilt. It takes about 30 seconds to build unless changes to the venv stage are made. venv takes 90s alone.",
"presentation": {
"echo": true,
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
"showReuseMessage": false,
"group": "Any"
},
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": false
},
"icon": {
"id": "beaker",
"color": "terminal.ansiBlue"
}
},
{
"label": "[Dev Container] Wipe and Regenerate Database",
"type": "shell",
"command": "killall 'python3' || true && sleep 1 && rm -rf /data/db/* /data/config/* && bash /entrypoint.d/15-first-run-config.sh && bash /entrypoint.d/20-first-run-db.sh && echo '✅ Database and config wiped and regenerated'",
"detail": "Wipes devcontainer db and config. Provides a fresh start in devcontainer, run this task, then run the Rerun Startup Task",
"options": {},
"presentation": {
"echo": true,
"reveal": "always",
"panel": "shared",
"showReuseMessage": false,
"group": "Devcontainer"
},
"problemMatcher": [],
"icon": {
"id": "output",
"color": "terminal.ansiBlue"
"id": "database",
"color": "terminal.ansiRed"
}
}
,
},
{
"label": "[Any] Build Unit Test Docker image",
"label": "Build & Launch Prodcution Docker Container",
"type": "shell",
"command": "docker buildx build -t netalertx-test . && echo '🧪 Unit Test Docker image built: netalertx-test'",
"command": "docker compose up -d --build --force-recreate",
"detail": "Before launching, ensure VSCode Ports are closed and services are stopped. Tasks: Stop Frontend & Backend Services & Remote: Close Unused Forwarded Ports to ensure proper operation of the new container.",
"options": {
"cwd": "/workspaces/NetAlertX"
},
"presentation": {
"echo": true,
"reveal": "always",
@@ -177,7 +230,7 @@
"isDefault": false
},
"icon": {
"id": "beaker",
"id": "package",
"color": "terminal.ansiBlue"
}
}

View File

@@ -43,14 +43,16 @@ ARG INSTALL_DIR=/app
# NetAlertX app directories
ENV NETALERTX_APP=${INSTALL_DIR}
ENV NETALERTX_CONFIG=${NETALERTX_APP}/config
ENV NETALERTX_DATA=/data
ENV NETALERTX_CONFIG=${NETALERTX_DATA}/config
ENV NETALERTX_FRONT=${NETALERTX_APP}/front
ENV NETALERTX_PLUGINS=${NETALERTX_FRONT}/plugins
ENV NETALERTX_SERVER=${NETALERTX_APP}/server
ENV NETALERTX_API=${NETALERTX_APP}/api
ENV NETALERTX_DB=${NETALERTX_APP}/db
ENV NETALERTX_API=/tmp/api
ENV NETALERTX_DB=${NETALERTX_DATA}/db
ENV NETALERTX_DB_FILE=${NETALERTX_DB}/app.db
ENV NETALERTX_BACK=${NETALERTX_APP}/back
ENV NETALERTX_LOG=${NETALERTX_APP}/log
ENV NETALERTX_LOG=/tmp/log
ENV NETALERTX_PLUGINS_LOG=${NETALERTX_LOG}/plugins
ENV NETALERTX_CONFIG_FILE=${NETALERTX_CONFIG}/app.conf
@@ -67,6 +69,7 @@ ENV LOG_EXECUTION_QUEUE=${NETALERTX_LOG}/execution_queue.log
ENV LOG_REPORT_OUTPUT_JSON=${NETALERTX_LOG}/report_output.json
ENV LOG_STDOUT=${NETALERTX_LOG}/stdout.log
ENV LOG_CROND=${NETALERTX_LOG}/crond.log
ENV LOG_NGINX_ERROR=${NETALERTX_LOG}/nginx-error.log
# System Services configuration files
ENV ENTRYPOINT_CHECKS=/entrypoint.d
@@ -75,25 +78,26 @@ ENV SYSTEM_SERVICES_SCRIPTS=${SYSTEM_SERVICES}/scripts
ENV SYSTEM_SERVICES_CONFIG=${SYSTEM_SERVICES}/config
ENV SYSTEM_NGINX_CONFIG=${SYSTEM_SERVICES_CONFIG}/nginx
ENV SYSTEM_NGINX_CONFIG_FILE=${SYSTEM_NGINX_CONFIG}/nginx.conf
ENV SYSTEM_SERVICES_ACTIVE_CONFIG=${SYSTEM_NGINX_CONFIG}/conf.active
ENV SYSTEM_SERVICES_ACTIVE_CONFIG=/tmp/nginx/active-config
ENV SYSTEM_SERVICES_PHP_FOLDER=${SYSTEM_SERVICES_CONFIG}/php
ENV SYSTEM_SERVICES_PHP_FPM_D=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.d
ENV SYSTEM_SERVICES_CROND=${SYSTEM_SERVICES_CONFIG}/crond
ENV SYSTEM_SERVICES_RUN=${SYSTEM_SERVICES}/run
ENV SYSTEM_SERVICES_RUN=/tmp/run
ENV SYSTEM_SERVICES_RUN_TMP=${SYSTEM_SERVICES_RUN}/tmp
ENV SYSTEM_SERVICES_RUN_LOG=${SYSTEM_SERVICES_RUN}/logs
ENV PHP_FPM_CONFIG_FILE=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.conf
ENV READ_ONLY_FOLDERS="${NETALERTX_BACK} ${NETALERTX_FRONT} ${NETALERTX_SERVER} ${SYSTEM_SERVICES} \
${SYSTEM_SERVICES_CONFIG} ${ENTRYPOINT_CHECKS}"
ENV READ_WRITE_FOLDERS="${NETALERTX_CONFIG} ${NETALERTX_DB} ${NETALERTX_API} ${NETALERTX_LOG} \
${NETALERTX_PLUGINS_LOG} ${SYSTEM_SERVICES_RUN} ${SYSTEM_SERVICES_RUN_TMP} \
${SYSTEM_SERVICES_RUN_LOG} ${SYSTEM_NGINX_CONFIG}"
ENV READ_WRITE_FOLDERS="${NETALERTX_DATA} ${NETALERTX_CONFIG} ${NETALERTX_DB} ${NETALERTX_API} \
${NETALERTX_LOG} ${NETALERTX_PLUGINS_LOG} ${SYSTEM_SERVICES_RUN} \
${SYSTEM_SERVICES_RUN_TMP} ${SYSTEM_SERVICES_RUN_LOG} \
${SYSTEM_SERVICES_ACTIVE_CONFIG}"
#Python environment
ENV PYTHONUNBUFFERED=1
ENV VIRTUAL_ENV=/opt/venv
ENV VIRTUAL_ENV_BIN=/opt/venv/bin
ENV PYTHONPATH=${NETALERTX_APP}:${NETALERTX_SERVER}:${VIRTUAL_ENV}/lib/python3.12/site-packages
ENV PYTHONPATH=${NETALERTX_APP}:${NETALERTX_SERVER}:${NETALERTX_PLUGINS}:${VIRTUAL_ENV}/lib/python3.12/site-packages
ENV PATH="${SYSTEM_SERVICES}:${VIRTUAL_ENV_BIN}:$PATH"
# App Environment
@@ -101,7 +105,7 @@ ENV LISTEN_ADDR=0.0.0.0
ENV PORT=20211
ENV NETALERTX_DEBUG=0
ENV VENDORSPATH=/app/back/ieee-oui.txt
ENV VENDORSPATH_NEWEST=/services/run/tmp/ieee-oui.txt
ENV VENDORSPATH_NEWEST=${SYSTEM_SERVICES_RUN_TMP}/ieee-oui.txt
ENV ENVIRONMENT=alpine
ENV READ_ONLY_USER=readonly READ_ONLY_GROUP=readonly
ENV NETALERTX_USER=netalertx NETALERTX_GROUP=netalertx
@@ -125,11 +129,15 @@ COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} install/production-filesystem/
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} --chmod=755 back ${NETALERTX_BACK}
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} --chmod=755 front ${NETALERTX_FRONT}
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} --chmod=755 server ${NETALERTX_SERVER}
RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 755 ${NETALERTX_API} \
${NETALERTX_LOG} ${SYSTEM_SERVICES_RUN_TMP} ${SYSTEM_SERVICES_RUN_LOG} && \
# Create required folders with correct ownership and permissions
RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 700 ${READ_WRITE_FOLDERS} && \
sh -c "find ${NETALERTX_APP} -type f \( -name '*.sh' -o -name 'speedtest-cli' \) \
-exec chmod 750 {} \;"
# Copy version information into the image
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .VERSION ${NETALERTX_APP}/.VERSION
# Copy the virtualenv from the builder stage
COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV}

View File

@@ -49,14 +49,15 @@ FROM debian:bookworm-slim
# NetAlertX app directories
ENV INSTALL_DIR=/app
ENV NETALERTX_APP=${INSTALL_DIR}
ENV NETALERTX_CONFIG=${NETALERTX_APP}/config
ENV NETALERTX_DATA=/data
ENV NETALERTX_CONFIG=${NETALERTX_DATA}/config
ENV NETALERTX_FRONT=${NETALERTX_APP}/front
ENV NETALERTX_SERVER=${NETALERTX_APP}/server
ENV NETALERTX_API=${NETALERTX_APP}/api
ENV NETALERTX_DB=${NETALERTX_APP}/db
ENV NETALERTX_API=/tmp/api
ENV NETALERTX_DB=${NETALERTX_DATA}/db
ENV NETALERTX_DB_FILE=${NETALERTX_DB}/app.db
ENV NETALERTX_BACK=${NETALERTX_APP}/back
ENV NETALERTX_LOG=${NETALERTX_APP}/log
ENV NETALERTX_LOG=/tmp/log
ENV NETALERTX_PLUGINS_LOG=${NETALERTX_LOG}/plugins
# NetAlertX log files
@@ -72,17 +73,19 @@ ENV LOG_EXECUTION_QUEUE=${NETALERTX_LOG}/execution_queue.log
ENV LOG_REPORT_OUTPUT_JSON=${NETALERTX_LOG}/report_output.json
ENV LOG_STDOUT=${NETALERTX_LOG}/stdout.log
ENV LOG_CROND=${NETALERTX_LOG}/crond.log
ENV LOG_NGINX_ERROR=${NETALERTX_LOG}/nginx-error.log
# System Services configuration files
ENV SYSTEM_SERVICES=/services
ENV SYSTEM_SERVICES_CONFIG=${SYSTEM_SERVICES}/config
ENV SYSTEM_NGINIX_CONFIG=${SYSTEM_SERVICES_CONFIG}/nginx
ENV SYSTEM_NGINX_CONFIG_FILE=${SYSTEM_NGINIX_CONFIG}/nginx.conf
ENV SYSTEM_SERVICES_ACTIVE_CONFIG=/tmp/nginx/active-config
ENV NETALERTX_CONFIG_FILE=${NETALERTX_CONFIG}/app.conf
ENV SYSTEM_SERVICES_PHP_FOLDER=${SYSTEM_SERVICES_CONFIG}/php
ENV SYSTEM_SERVICES_PHP_FPM_D=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.d
ENV SYSTEM_SERVICES_CROND=${SYSTEM_SERVICES_CONFIG}/crond
ENV SYSTEM_SERVICES_RUN=${SYSTEM_SERVICES}/run
ENV SYSTEM_SERVICES_RUN=/tmp/run
ENV SYSTEM_SERVICES_RUN_TMP=${SYSTEM_SERVICES_RUN}/tmp
ENV SYSTEM_SERVICES_RUN_LOG=${SYSTEM_SERVICES_RUN}/logs
ENV PHP_FPM_CONFIG_FILE=${SYSTEM_SERVICES_PHP_FOLDER}/php-fpm.conf
@@ -94,7 +97,7 @@ ENV VIRTUAL_ENV=/opt/venv
ENV VIRTUAL_ENV_BIN=/opt/venv/bin
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}:/services"
ENV VENDORSPATH=/app/back/ieee-oui.txt
ENV VENDORSPATH_NEWEST=/services/run/tmp/ieee-oui.txt
ENV VENDORSPATH_NEWEST=${SYSTEM_SERVICES_RUN_TMP}/ieee-oui.txt
# App Environment

View File

@@ -6,7 +6,7 @@
# NetAlertX - Network, presence scanner and alert framework
Get visibility of what's going on on your WIFI/LAN network and enable presence detection of important devices. Schedule scans for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md#readme) with auto-generated UI and in-build notification system. Build out and easily maintain your network source of truth (NSoT).
Get visibility of what's going on on your WIFI/LAN network and enable presence detection of important devices. Schedule scans for devices, port changes and get alerts if unknown devices or changes are found. Write your own [Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md#readme) with auto-generated UI and in-build notification system. Build out and easily maintain your network source of truth (NSoT) and device inventory.
## 📋 Table of Contents
@@ -33,13 +33,18 @@ Get visibility of what's going on on your WIFI/LAN network and enable presence d
## 🚀 Quick Start
> [!WARNING]
> ⚠️ **Important:** The documentation has been recently updated and some instructions may have changed.
> If you are using the currently live production image, please follow the instructions on [Docker Hub](https://hub.docker.com/r/jokobsk/netalertx) for building and running the container.
> These docs reflect the latest development version and may differ from the production image.
Start NetAlertX in seconds with Docker:
```bash
docker run -d --rm --network=host \
-v local_path/config:/app/config \
-v local_path/db:/app/db \
--mount type=tmpfs,target=/app/api \
-v local_path/config:/data/config \
-v local_path/db:/data/db \
--mount type=tmpfs,target=/tmp/api \
-e PUID=200 -e PGID=300 \
-e TZ=Europe/Berlin \
-e PORT=20211 \
@@ -61,7 +66,7 @@ For Home Assistant users: [Click here to add NetAlertX](https://my.home-assistan
For other install methods, check the [installation docs](#-documentation)
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://jokob-sk.github.io/NetAlertX/) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
| [📑 Docker guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_INSTALLATION.md) | [🚀 Releases](https://github.com/jokob-sk/NetAlertX/releases) | [📚 Docs](https://jokob-sk.github.io/NetAlertX/) | [🔌 Plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md) | [🤖 Ask AI](https://gurubase.io/g/netalertx)
|----------------------| ----------------------| ----------------------| ----------------------| ----------------------|
![showcase][showcase]
@@ -103,7 +108,7 @@ The [workflows module](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORK
Supported browsers: Chrome, Firefox
- [[Installation] Docker](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
- [[Installation] Docker](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_INSTALLATION.md)
- [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx)
- [[Installation] Bare metal](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md)
- [[Installation] Unraid App](https://unraid.net/community/apps)
@@ -140,7 +145,7 @@ A: No. All scans and data remain local, unless you set up cloud-based notificati
A: Yes! You can install it bare-metal. See the [bare metal installation guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md).
**Q: Where is the data stored?**
A: In the `/config` and `/db` folders, mapped in Docker. Back up these folders regularly.
A: In the `/data/config` and `/data/db` folders. Back up these folders regularly.
## 🐞 Known Issues

1
api Symbolic link
View File

@@ -0,0 +1 @@
/tmp/api

2
db/.gitignore vendored
View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

@@ -17,53 +17,38 @@ services:
volumes:
- type: volume # Persistent Docker-managed Named Volume for storage of config files
source: netalertx_config # the default name of the volume is netalertx_config
target: /app/config # inside the container mounted to /app/config
- type: volume # Persistent Docker-managed Named Volume for storage
source: netalertx_data # the default name of the volume is netalertx_data
target: /data # consolidated configuration and database storage
read_only: false # writable volume
# Example custom local folder called /home/user/netalertx_config
# Example custom local folder called /home/user/netalertx_data
# - type: bind
# source: /home/user/netalertx_config
# target: /app/config
# source: /home/user/netalertx_data
# target: /data
# read_only: false
# ... or use the alternative format
# - /home/user/netalertx_config:/app/config:rw
- type: volume
source: netalertx_db
target: /app/db
read_only: false
# - /home/user/netalertx_data:/data:rw
- type: bind # Bind mount for timezone consistency
source: /etc/localtime
target: /etc/localtime
read_only: true
# Use a custom Enterprise-configured nginx config for ldap or other settings
# - /custom-enterprise.conf:/services/config/nginx/conf.active/netalertx.conf:ro
# Use a custom Enterprise-configured nginx config for ldap or other settings
# - /custom-enterprise.conf:/tmp/nginx/active-config/netalertx.conf:ro
# Test your plugin on the production container
# - /path/on/host:/app/front/plugins/custom
# Retain logs - comment out tmpfs /app/log if you want to retain logs between container restarts
# - /path/on/host/log:/app/log
# Retain logs - comment out tmpfs /tmp/log if you want to retain logs between container restarts
# - /path/on/host/log:/tmp/log
# Tempfs mounts for writable directories in a read-only container and improve system performance
# All mounts have noexec,nosuid,nodev for security purposes no devices, no suid/sgid and no execution of binaries
# async where possible for performance, sync where required for correctness
# tmpfs mounts for writable directories in a read-only container and improve system performance
# All writes now live under /tmp/* subdirectories which are created dynamically by entrypoint.d scripts
# uid=20211 and gid=20211 is the netalertx user inside the container
# mode=1700 gives rwx------ permissions to the netalertx user only
tmpfs:
# Speed up logging. This can be commented out to retain logs between container restarts
- "/app/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# Speed up API access as frontend/backend API is very chatty
- "/app/api:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,sync,noatime,nodiratime"
# Required for customization of the nginx listen addr/port without rebuilding the container
- "/services/config/nginx/conf.active:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# /services/config/nginx/conf.d is required for nginx and php to start
- "/services/run:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# /tmp is required by php for session save this should be reworked to /services/run/tmp
- "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
environment:
LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} # Listen for connections on all interfaces
@@ -86,6 +71,5 @@ services:
# Always restart the container unless explicitly stopped
restart: unless-stopped
volumes: # Persistent volumes for configuration and database storage
netalertx_config: # Configuration files
netalertx_db: # Database files
volumes: # Persistent volume for configuration and database storage
netalertx_data:

View File

@@ -64,8 +64,9 @@ http://<server>:<GRAPHQL_PORT>/
* [Metrics](API_METRICS.md) Prometheus metrics and per-device status
* [Network Tools](API_NETTOOLS.md) Utilities like Wake-on-LAN, traceroute, nslookup, nmap, and internet info
* [Online History](API_ONLINEHISTORY.md) Online/offline device records
* [GraphQL](API_GRAPHQL.md) Advanced queries and filtering
* [GraphQL](API_GRAPHQL.md) Advanced queries and filtering for Devices, Settings and Language Strings
* [Sync](API_SYNC.md) Synchronization between multiple NetAlertX instances
* [Logs](API_LOGS.md) Purging of logs and adding to the event execution queue for user triggered events
* [DB query](API_DBQUERY.md) (⚠ Internal) - Low level database access - use other endpoints if possible
See [Testing](API_TESTS.md) for example requests and usage.

View File

@@ -1,9 +1,10 @@
# GraphQL API Endpoint
GraphQL queries are **read-optimized for speed**. Data may be slightly out of date until the file system cache refreshes. The GraphQL endpoints allows you to access the following objects:
GraphQL queries are **read-optimized for speed**. Data may be slightly out of date until the file system cache refreshes. The GraphQL endpoints allow you to access the following objects:
- Devices
- Settings
* Devices
* Settings
* Language Strings (LangStrings)
## Endpoints
@@ -190,11 +191,74 @@ curl 'http://host:GRAPHQL_PORT/graphql' \
}
```
---
## LangStrings Query
The **LangStrings query** provides access to localized strings. Supports filtering by `langCode` and `langStringKey`. If the requested string is missing or empty, you can optionally fallback to `en_us`.
### Sample Query
```graphql
query GetLangStrings {
langStrings(langCode: "de_de", langStringKey: "settings_other_scanners") {
langStrings {
langCode
langStringKey
langStringText
}
count
}
}
```
### Query Parameters
| Parameter | Type | Description |
| ---------------- | ------- | ---------------------------------------------------------------------------------------- |
| `langCode` | String | Optional language code (e.g., `en_us`, `de_de`). If omitted, all languages are returned. |
| `langStringKey` | String | Optional string key to retrieve a specific entry. |
| `fallback_to_en` | Boolean | Optional (default `true`). If `true`, empty or missing strings fallback to `en_us`. |
### `curl` Example
```sh
curl 'http://host:GRAPHQL_PORT/graphql' \
-X POST \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"query": "query GetLangStrings { langStrings(langCode: \"de_de\", langStringKey: \"settings_other_scanners\") { langStrings { langCode langStringKey langStringText } count } }"
}'
```
### Sample Response
```json
{
"data": {
"langStrings": {
"count": 1,
"langStrings": [
{
"langCode": "de_de",
"langStringKey": "settings_other_scanners",
"langStringText": "Other, non-device scanner plugins that are currently enabled." // falls back to en_us if empty
}
]
}
}
}
```
---
## Notes
* Device and settings queries can be combined in one request since GraphQL supports batching.
* Device, settings, and LangStrings queries can be combined in **one request** since GraphQL supports batching.
* The `fallback_to_en` feature ensures UI always has a value even if a translation is missing.
* Data is **cached in memory** per JSON file; changes to language or plugin files will only refresh after the cache detects a file modification.
* The `setOverriddenByEnv` flag helps identify setting values that are locked at container runtime.
* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details.

179
docs/API_LOGS.md Normal file
View File

@@ -0,0 +1,179 @@
# Logs API Endpoints
Manage or purge application log files stored under `/app/log` and manage the execution queue. These endpoints are primarily used for maintenance tasks such as clearing accumulated logs or adding system actions without restarting the container.
Only specific, pre-approved log files can be purged for security and stability reasons.
---
## Delete (Purge) a Log File
* **DELETE** `/logs?file=<log_file>` → Purge the contents of an allowed log file.
**Query Parameter:**
* `file` → The name of the log file to purge (e.g., `app.log`, `stdout.log`)
**Allowed Files:**
```
app.log
app_front.log
IP_changes.log
stdout.log
stderr.log
app.php_errors.log
execution_queue.log
db_is_locked.log
```
**Authorization:**
Requires a valid API token in the `Authorization` header.
---
### `curl` Example (Success)
```sh
curl -X DELETE 'http://<server_ip>:<GRAPHQL_PORT>/logs?file=app.log' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Accept: application/json'
```
**Response:**
```json
{
"success": true,
"message": "[clean_log] File app.log purged successfully"
}
```
---
### `curl` Example (Not Allowed)
```sh
curl -X DELETE 'http://<server_ip>:<GRAPHQL_PORT>/logs?file=not_allowed.log' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Accept: application/json'
```
**Response:**
```json
{
"success": false,
"message": "[clean_log] File not_allowed.log is not allowed to be purged"
}
```
---
### `curl` Example (Unauthorized)
```sh
curl -X DELETE 'http://<server_ip>:<GRAPHQL_PORT>/logs?file=app.log' \
-H 'Accept: application/json'
```
**Response:**
```json
{
"error": "Forbidden"
}
```
---
## Add an Action to the Execution Queue
* **POST** `/logs/add-to-execution-queue` → Add a system action to the execution queue.
**Request Body (JSON):**
```json
{
"action": "update_api|devices"
}
```
**Authorization:**
Requires a valid API token in the `Authorization` header.
---
### `curl` Example (Success)
The below will update the API cache for Devices
```sh
curl -X POST 'http://<server_ip>:<GRAPHQL_PORT>/logs/add-to-execution-queue' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data '{"action": "update_api|devices"}'
```
**Response:**
```json
{
"success": true,
"message": "[UserEventsQueueInstance] Action \"update_api|devices\" added to the execution queue."
}
```
---
### `curl` Example (Missing Parameter)
```sh
curl -X POST 'http://<server_ip>:<GRAPHQL_PORT>/logs/add-to-execution-queue' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data '{}'
```
**Response:**
```json
{
"success": false,
"message": "Missing parameters",
"error": "Missing required 'action' field in JSON body"
}
```
---
### `curl` Example (Unauthorized)
```sh
curl -X POST 'http://<server_ip>:<GRAPHQL_PORT>/logs/add-to-execution-queue' \
-H 'Content-Type: application/json' \
--data '{"action": "update_api|devices"}'
```
**Response:**
```json
{
"error": "Forbidden"
}
```
---
## Notes
* Only predefined files in `/app/log` can be purged — arbitrary paths are **not permitted**.
* When a log file is purged:
* Its content is replaced with a short marker text: `"File manually purged"`.
* A backend log entry is created via `mylog()`.
* A frontend notification is generated via `write_notification()`.
* Execution queue actions are appended to `execution_queue.log` and can be processed asynchronously by background tasks or workflows.
* Unauthorized or invalid attempts are safely logged and rejected.
* For advanced log retrieval, analysis, or structured querying, use the frontend log viewer.
* Always ensure that sensitive or production logs are handled carefully — purging cannot be undone.

View File

@@ -141,7 +141,7 @@ The endpoints are updated when objects in the API endpoints are changed.
### Location of the endpoints
In the container, these files are located under the `/app/api/` folder. You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
In the container, these files are located under the API directory (default: `/tmp/api/`, configurable via `NETALERTX_API` environment variable). You can access them via the `/php/server/query_json.php?file=user_notifications.json` endpoint.
### Available endpoints
@@ -332,7 +332,7 @@ Grafana template sample: [Download json](./samples/API/Grafana_Dashboard.json)
## API Endpoint: /log files
This API endpoint retrieves files from the `/app/log` folder.
This API endpoint retrieves files from the `/tmp/log` folder.
- Endpoint URL: `php/server/query_logs.php?file=<file name>`
- Host: `same as front end (web ui)`
@@ -357,7 +357,7 @@ This API endpoint retrieves files from the `/app/log` folder.
## API Endpoint: /config files
To retrieve files from the `/app/config` folder.
To retrieve files from the `/data/config` folder.
- Endpoint URL: `php/server/query_config.php?file=<file name>`
- Host: `same as front end (web ui)`

View File

@@ -1,7 +1,7 @@
# Backing Things Up
> [!NOTE]
> To back up 99% of your configuration, back up at least the `/app/config` folder.
> To back up 99% of your configuration, back up at least the `/data/config` folder.
> Database definitions can change between releases, so the safest method is to restore backups using the **same app version** they were taken from, then upgrade incrementally.
---
@@ -25,7 +25,7 @@ Understanding where your data is stored helps you plan your backup strategy.
### Core Configuration
Stored in `/app/config/app.conf`.
Stored in `/data/config/app.conf`.
This includes settings for:
* Notifications
@@ -37,7 +37,7 @@ This includes settings for:
### Device Data
Stored in `/app/config/devices_<timestamp>.csv` or `/app/config/devices.csv`, created by the [CSV Backup `CSVBCKP` Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup).
Stored in `/data/config/devices_<timestamp>.csv` or `/data/config/devices.csv`, created by the [CSV Backup `CSVBCKP` Plugin](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/csv_backup).
Contains:
* Device names, icons, and categories
@@ -46,7 +46,7 @@ Contains:
### Historical Data
Stored in `/app/db/app.db` (see [Database Overview](./DATABASE.md)).
Stored in `/data/db/app.db` (see [Database Overview](./DATABASE.md)).
Contains:
* Plugin data and historical entries
@@ -77,13 +77,13 @@ You can also download the `app.conf` and `devices.csv` files from the **Maintena
### 💾 What to Back Up
* `/app/db/app.db` (uncorrupted)
* `/app/config/app.conf`
* `/app/config/workflows.json`
* `/data/db/app.db` (uncorrupted)
* `/data/config/app.conf`
* `/data/config/workflows.json`
### 📥 How to Restore
Map these files into your container as described in the [Setup documentation](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#docker-paths).
Map these files into your container as described in the [Setup documentation](./DOCKER_INSTALLATION.md).
---
@@ -93,14 +93,14 @@ Map these files into your container as described in the [Setup documentation](ht
### 💾 What to Back Up
* `/app/config/app.conf`
* `/app/config/workflows.json`
* `/app/config/devices_<timestamp>.csv` (rename to `devices.csv` during restore)
* `/data/config/app.conf`
* `/data/config/workflows.json`
* `/data/config/devices_<timestamp>.csv` (rename to `devices.csv` during restore)
### 📥 How to Restore
1. Copy `app.conf` and `workflows.json` into `/app/config/`
2. Rename and place `devices_<timestamp>.csv``/app/config/devices.csv`
1. Copy `app.conf` and `workflows.json` into `/data/config/`
2. Rename and place `devices_<timestamp>.csv``/data/config/devices.csv`
3. Restore via the **Maintenance** section under *Devices → Bulk Editing*
This recovers nearly all configuration, workflows, and device metadata.
@@ -157,6 +157,6 @@ For users running NetAlertX via Docker, you can back up or restore directly from
## Summary
* Back up `/app/config` for configuration and devices; `/app/db` for history
* Back up `/data/config` for configuration and devices; `/data/db` for history
* Keep regular backups, especially before upgrades
* For Docker setups, use the lightweight `alpine`-based backup method for consistency and portability

View File

@@ -14,9 +14,9 @@ The app uses the MAC address as an unique identifier for devices. If a new MAC i
Make sure you [File permissions](./FILE_PERMISSIONS.md) are set correctly.
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/app/log`.
* To solve permission issues you can try setting the owner and group of the `app.db` by executing the following on the host system: `docker exec netalertx chown -R www-data:www-data /app/db/app.db`.
* If still facing issues, try to map the app.db file (⚠ not folder) to `:/app/db/app.db` (see [docker-compose Examples](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#-docker-composeyml-examples) for details)
* If facing issues (AJAX errors, can't write to DB, empty screen, etc,) make sure permissions are set correctly, and check the logs under `/tmp/log`.
* To solve permission issues you can try setting the owner and group of the `app.db` by executing the following on the host system: `docker exec netalertx chown -R www-data:www-data /data/db/app.db`.
* If still facing issues, try to map the app.db file (⚠ not folder) to `:/data/db/app.db` (see [docker-compose Examples](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md#-docker-composeyml-examples) for details)
### Container restarts / crashes
@@ -49,7 +49,7 @@ Make sure that the subnet and interface in `SCAN_SUBNETS` are correct. If your d
### Losing my settings and devices after an update
If you lose your devices and/or settings after an update that means you don't have the `/app/db` and `/app/config` folders mapped to a permanent storage. That means every time you update these folders are re-created. Make sure you have the [volumes specified correctly](./DOCKER_COMPOSE.md) in your `docker-compose.yml` or run command.
If you lose your devices and/or settings after an update that means you don't have the `/data/db` and `/data/config` folders mapped to a permanent storage. That means every time you update these folders are re-created. Make sure you have the [volumes specified correctly](./DOCKER_COMPOSE.md) in your `docker-compose.yml` or run command.
### The application is slow

View File

@@ -27,7 +27,7 @@ Sometimes, the UI might not be accessible. In that case, you can access the logs
3. **Check the PHP application error log:**
```bash
cat /app/log/app.php_errors.log
cat /tmp/log/app.php_errors.log
```
These logs will help identify syntax issues, fatal errors, or startup problems when the UI fails to load properly.

View File

@@ -14,8 +14,8 @@ Start the container via the **terminal** with a command similar to this one:
```bash
docker run --rm --network=host \
-v local/path/netalertx/config:/app/config \
-v local/path/netalertx/db:/app/db \
-v local/path/netalertx/config:/data/config \
-v local/path/netalertx/db:/data/db \
-e TZ=Europe/Berlin \
-e PORT=20211 \
ghcr.io/jokob-sk/netalertx:latest

View File

@@ -31,61 +31,46 @@ services:
- NET_BIND_SERVICE # Required to bind to privileged ports (nbtscan)
volumes:
- type: volume # Persistent Docker-managed Named Volume for storage of config files
source: netalertx_config # the default name of the volume is netalertx_config
target: /app/config # inside the container mounted to /app/config
read_only: false # writable volume
# Example custom local folder called /home/user/netalertx_config
# - type: bind
# source: /home/user/netalertx_config
# target: /app/config
# read_only: false
# ... or use the alternative format
# - /home/user/netalertx_config:/app/config:rw
- type: volume # NetAlertX Database partiton
source: netalertx_db
target: /app/db
- type: volume # Persistent Docker-managed named volume for config + database
source: netalertx_data
target: /data # `/data/config` and `/data/db` live inside this mount
read_only: false
- type: volume # Future proof mount. During the migration to a
source: netalertx_data # future version, app and db will be migrated to
target: /data # the /data partition. This will reduce the
read_only: false # overhead and pain in the upcoming migration.
# Example custom local folder called /home/user/netalertx_data
# - type: bind
# source: /home/user/netalertx_data
# target: /data
# read_only: false
# ... or use the alternative format
# - /home/user/netalertx_data:/data:rw
- type: bind # Bind mount for timezone consistency
source: /etc/localtime
source: /etc/localtime # Alternatively add environment TZ: America/New York
target: /etc/localtime
read_only: true
# Mount your DHCP server file into NetAlertX for a plugin to access
# - path/on/host/to/dhcp.file:/resources/dhcp.file
# Retain logs - comment out tmpfs /app/log if you want to retain logs between container restarts
# - /path/on/host/log:/app/log
# Tempfs mounts for writable directories in a read-only container and improve system performance
# All mounts have noexec,nosuid,nodev for security purposes no devices, no suid/sgid and no execution of binaries
# async where possible for performance, sync where required for correctness
# tmpfs mount consolidates writable state for a read-only container and improves performance
# uid=20211 and gid=20211 is the netalertx user inside the container
# mode=1700 gives rwx------ permissions to the netalertx user only
# mode=1700 grants rwx------ permissions to the netalertx user only
tmpfs:
# Speed up logging. This can be commented out to retain logs between container restarts
- "/app/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# Speed up API access as frontend/backend API is very chatty
- "/app/api:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,sync,noatime,nodiratime"
# Required for customization of the nginx listen addr/port without rebuilding the container
- "/services/config/nginx/conf.active:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# /services/config/nginx/conf.d is required for nginx and php to start
- "/services/run:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# /tmp is required by php for session save this should be reworked to /services/run/tmp
# Comment out to retain logs between container restarts - this has a server performance impact.
- "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# Retain logs - comment out tmpfs /tmp if you want to retain logs between container restarts
# Please note if you remove the /tmp mount, you must create and maintain sub-folder mounts.
# - /path/on/host/log:/tmp/log
# - "/tmp/api:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# - "/tmp/nginx:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# - "/tmp/run:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
environment:
LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} # Listen for connections on all interfaces
PORT: ${PORT:-20211} # Application port
GRAPHQL_PORT: ${GRAPHQL_PORT:-20212} # GraphQL API port (passed into APP_CONF_OVERRIDE at runtime)
NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0} # 0=kill all services and restart if any dies. 1 keeps running dead services.
# NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0} # 0=kill all services and restart if any dies. 1 keeps running dead services.
# Resource limits to prevent resource exhaustion
mem_limit: 2048m # Maximum memory usage
@@ -101,10 +86,8 @@ services:
# Always restart the container unless explicitly stopped
restart: unless-stopped
volumes: # Persistent volumes for configuration and database storage
netalertx_config: # Configuration files
netalertx_db: # Database files
netalertx_data: # For future config/db upgrade
volumes: # Persistent volume for configuration and database storage
netalertx_data:
```
Run or re-run it:
@@ -163,8 +146,8 @@ However, if you prefer to have direct, file-level access to your configuration f
```yaml
...
volumes:
- netalertx_config:/app/config:rw #short-form volume (no /path is a short volume)
- netalertx_db:/app/db:rw
- netalertx_config:/data/config:rw #short-form volume (no /path is a short volume)
- netalertx_db:/data/db:rw
...
```
@@ -174,14 +157,14 @@ Make sure to replace `/home/adam/netalertx-files` with your actual path. The for
```yaml
...
volumes:
# - netalertx_config:/app/config:rw
# - netalertx_db:/app/db:rw
- /home/adam/netalertx-files/config:/app/config:rw
- /home/adam/netalertx-files/db:/app/db:rw
# - netalertx_config:/data/config:rw
# - netalertx_db:/data/db:rw
- /home/adam/netalertx-files/config:/data/config:rw
- /home/adam/netalertx-files/db:/data/db:rw
...
```
Now, any files created by NetAlertX in `/app/config` will appear in your `/home/adam/netalertx-files/config` folder.
Now, any files created by NetAlertX in `/data/config` will appear in your `/home/adam/netalertx-files/config` folder.
This same method works for mounting other things, like custom plugins or enterprise NGINX files, as shown in the commented-out examples in the baseline file.

View File

@@ -25,9 +25,9 @@ Head to [https://netalertx.com/](https://netalertx.com/) for more gifs and scree
```yaml
docker run -d --rm --network=host \
-v local_path/config:/app/config \
-v local_path/db:/app/db \
--mount type=tmpfs,target=/app/api \
-v local_path/config:/data/config \
-v local_path/db:/data/db \
--mount type=tmpfs,target=/tmp/api \
-e PUID=200 -e PGID=300 \
-e TZ=Europe/Berlin \
-e PORT=20211 \
@@ -58,10 +58,10 @@ See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/
| Required | Path | Description |
| :------------- | :------------- | :-------------|
| ✅ | `:/app/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
| ✅ | `:/app/db` | Folder which will contain the `app.db` database file |
| | `:/app/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/app/api` | A simple [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. |
| ✅ | `:/data/config` | Folder which will contain the `app.conf` & `devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
| ✅ | `:/data/db` | Folder which will contain the `app.db` database file |
| | `:/tmp/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/tmp/api` | A simple [API endpoint](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md) containing static (but regularly updated) json and other files. Path configurable via `NETALERTX_API` environment variable. |
| | `:/app/front/plugins/<plugin>/ignore_plugin` | Map a file `ignore_plugin` to ignore a plugin. Plugins can be soft-disabled via settings. More in the [Plugin docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md). |
| | `:/etc/resolv.conf` | Use a custom `resolv.conf` file for [better name resolution](https://github.com/jokob-sk/NetAlertX/blob/main/docs/REVERSE_DNS.md). |
@@ -70,7 +70,7 @@ See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/
### Initial setup
- If unavailable, the app generates a default `app.conf` and `app.db` file on the first run.
- The preferred way is to manage the configuration via the Settings section in the UI, if UI is inaccessible you can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/back) in the `/app/config/` folder directly
- The preferred way is to manage the configuration via the Settings section in the UI, if UI is inaccessible you can modify [app.conf](https://github.com/jokob-sk/NetAlertX/tree/main/back) in the `/data/config/` folder directly
#### Setting up scanners

View File

@@ -51,13 +51,13 @@ You want to edit your `app.conf` and other configuration files directly from you
volumes:
# - type: volume
# source: netalertx_config
# target: /app/config
# target: /data/config
# read_only: false
...
# Example custom local folder called /data/netalertx_config
- type: bind
source: /data/netalertx_config
target: /app/config
target: /data/config
read_only: false
...
```
@@ -70,7 +70,7 @@ You want to edit your `app.conf` and other configuration files directly from you
### About This Method
This replaces the Docker-managed volume with a "bind mount." This is a direct mapping between a folder on your host computer (`/data/netalertx_config`) and a folder inside the container (`/app/config`), allowing you to edit the files directly.
This replaces the Docker-managed volume with a "bind mount." This is a direct mapping between a folder on your host computer (`/data/netalertx_config`) and a folder inside the container (`/data/config`), allowing you to edit the files directly.
---
@@ -97,13 +97,13 @@ You are currently using a local folder (bind mount) for your configuration (e.g.
volumes:
- type: volume
source: netalertx_config
target: /app/config
target: /data/config
read_only: false
...
# Example custom local folder called /data/netalertx_config
# - type: bind
# source: /data/netalertx_config
# target: /app/config
# target: /data/config
# read_only: false
...
```
@@ -149,7 +149,7 @@ You need to override the default Nginx configuration to add features like LDAP,
```yaml
...
# Use a custom Enterprise-configured nginx config for ldap or other settings
- /data/my-netalertx.conf:/services/config/nginx/conf.active/netalertx.conf:ro
- /data/my-netalertx.conf:/tmp/nginx/active-config/netalertx.conf:ro
...
```
4. Restart the container:

View File

@@ -45,18 +45,18 @@ services:
restart: unless-stopped
volumes:
- ${APP_FOLDER}/netalertx/config:/app/config
- ${APP_FOLDER}/netalertx/db:/app/db
- ${APP_FOLDER}/netalertx/config:/data/config
- ${APP_FOLDER}/netalertx/db:/data/db
# Optional: logs (useful for debugging setup issues, comment out for performance)
- ${APP_FOLDER}/netalertx/log:/app/log
- ${APP_FOLDER}/netalertx/log:/tmp/log
# API storage options:
# (Option 1) tmpfs (default, best performance)
- type: tmpfs
target: /app/api
target: /tmp/api
# (Option 2) bind mount (useful for debugging)
# - ${APP_FOLDER}/netalertx/api:/app/api
# - ${APP_FOLDER}/netalertx/api:/tmp/api
environment:
- TZ=${TZ}

View File

@@ -44,9 +44,9 @@ services:
ports:
- 20211:20211
volumes:
- /mnt/YOUR_SERVER/netalertx/config:/app/config:rw
- /mnt/YOUR_SERVER/netalertx/db:/netalertx/app/db:rw
- /mnt/YOUR_SERVER/netalertx/logs:/netalertx/app/log:rw
- /mnt/YOUR_SERVER/netalertx/config:/data/config:rw
- /mnt/YOUR_SERVER/netalertx/db:/netalertx/data/db:rw
- /mnt/YOUR_SERVER/netalertx/logs:/netalertx/tmp/log:rw
environment:
- TZ=Europe/London
- PORT=20211

View File

@@ -11,13 +11,15 @@ NetAlertX requires certain paths to be writable at runtime. These paths should b
| Path | Purpose | Notes |
| ------------------------------------ | ----------------------------------- | ------------------------------------------------------ |
| `/app/config` | Application configuration | Persistent volume recommended |
| `/app/db` | Database files | Persistent volume recommended |
| `/app/log` | Logs | Can be `tmpfs` for speed or host volume to retain logs |
| `/app/api` | API cache | Use `tmpfs` for faster access |
| `/services/config/nginx/conf.active` | Active nginx configuration override | `tmpfs` recommended or customized file mounted |
| `/services/run` | Runtime directories for nginx & PHP | `tmpfs` required |
| `/tmp` | PHP session save directory | `tmpfs` required |
| `/data/config` | Application configuration | Persistent volume recommended |
| `/data/db` | Database files | Persistent volume recommended |
| `/tmp/log` | Logs | Lives under `/tmp`; optional host bind to retain logs |
| `/tmp/api` | API cache | Subdirectory of `/tmp` |
| `/tmp/nginx/active-config` | Active nginx configuration override | Mount `/tmp` (or override specific file) |
| `/tmp/run` | Runtime directories for nginx & PHP | Subdirectory of `/tmp` |
| `/tmp` | PHP session save directory | Backed by `tmpfs` for runtime writes |
> Mounting `/tmp` as `tmpfs` automatically covers all of its subdirectories (`log`, `api`, `run`, `nginx/active-config`, etc.).
> All these paths will have **UID 20211 / GID 20211** inside the container. Files on the host will appear owned by `20211:20211`.
@@ -33,8 +35,8 @@ Sometimes, permission issues arise if your existing host directories were create
```bash
docker run -it --rm --name netalertx --user "0" \
-v local/path/config:/app/config \
-v local/path/db:/app/db \
-v local/path/config:/data/config \
-v local/path/db:/data/db \
ghcr.io/jokob-sk/netalertx:latest
```
@@ -60,16 +62,12 @@ services:
- NET_BIND_SERVICE
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- local/path/config:/data/config
- local/path/db:/data/db
environment:
- TZ=Europe/Berlin
- PORT=20211
tmpfs:
- "/app/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
- "/app/api:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,sync,noatime,nodiratime"
- "/services/config/nginx/conf.active:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
- "/services/run:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
tmpfs:
- "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
```

View File

@@ -4,7 +4,7 @@
NetAlertX can be installed several ways. The best supported option is Docker, followed by a supervised Home Assistant instance, as an Unraid app, and lastly, on bare metal.
- [[Installation] Docker (recommended)](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
- [[Installation] Docker (recommended)](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_INSTALLATION.md)
- [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx)
- [[Installation] Unraid App](https://unraid.net/community/apps)
- [[Installation] Bare metal (experimental - looking for maintainers)](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HW_INSTALL.md)

View File

@@ -9,7 +9,7 @@ NetAlertX comes with several logs that help to identify application issues. Thes
You can find most of the logs exposed in the UI under _Maintenance -> Logs_.
If the UI is inaccessible, you can access them under `/app/log`.
If the UI is inaccessible, you can access them under `/tmp/log`.
![Logs](./img/LOGGING/maintenance_logs.png)
@@ -52,18 +52,18 @@ The default logs are erased every time the container restarts because they are s
2. Edit your `docker-compose.yml` file:
* **Comment out** the `/app/log` line under the `tmpfs:` section.
* **Comment out** the `/tmp/log` line under the `tmpfs:` section.
* **Uncomment** the "Retain logs" line under the `volumes:` section and set your desired host path.
```yaml
...
tmpfs:
# - "/app/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# - "/tmp/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
...
volumes:
...
# Retain logs - comment out tmpfs /app/log if you want to retain logs between container restarts
- /home/adam/netalertx_logs:/app/log
# Retain logs - comment out tmpfs /tmp/log if you want to retain logs between container restarts
- /home/adam/netalertx_logs:/tmp/log
...
```
3. Restart the container:
@@ -72,4 +72,4 @@ The default logs are erased every time the container restarts because they are s
docker-compose up -d
```
This change stops Docker from mounting a temporary in-memory volume at `/app/log`. Instead, it "bind mounts" a persistent folder from your host computer (e.g., `/data/netalertx_logs`) to that *same location* inside the container.
This change stops Docker from mounting a temporary in-memory volume at `/tmp/log`. Instead, it "bind mounts" a persistent folder from your host computer (e.g., `/data/netalertx_logs`) to that *same location* inside the container.

View File

@@ -43,7 +43,7 @@ A banner message will appear at the top of the web UI reminding you to update yo
> [!TIP]
> If you have trouble accessing past backups, config or database files you can copy them into the newly mapped directories, for example by running this command in the container: `cp -r /app/config /home/pi/pialert/config/old_backup_files`. This should create a folder in the `config` directory called `old_backup_files` containing all the files in that location. Another approach is to map the old location and the new one at the same time to copy things over.
> If you have trouble accessing past backups, config or database files you can copy them into the newly mapped directories, for example by running this command in the container: `cp -r /data/config /home/pi/pialert/config/old_backup_files`. This should create a folder in the `config` directory called `old_backup_files` containing all the files in that location. Another approach is to map the old location and the new one at the same time to copy things over.
#### New Docker mount locations
@@ -51,8 +51,8 @@ The internal application path in the container has changed from `/home/pi/pialer
| Old mount point | New mount point |
|----------------------|---------------|
| `/home/pi/pialert/config` | `/app/config` |
| `/home/pi/pialert/db` | `/app/db` |
| `/home/pi/pialert/config` | `/data/config` |
| `/home/pi/pialert/db` | `/data/db` |
If you were mounting files directly, please note the file names have changed:
@@ -85,10 +85,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/home/pi/pialert/config
- local/path/db:/home/pi/pialert/db
- /local/path/config:/home/pi/pialert/config
- /local/path/db:/home/pi/pialert/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/home/pi/pialert/front/log
- /local/path/logs:/home/pi/pialert/front/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -104,10 +104,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config # 🆕 This has changed
- local/path/db:/app/db # 🆕 This has changed
- /local/path/config:/data/config # 🆕 This has changed
- /local/path/db:/data/db # 🆕 This has changed
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log # 🆕 This has changed
- /local/path/logs:/tmp/log # 🆕 This has changed
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -131,10 +131,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config/pialert.conf:/home/pi/pialert/config/pialert.conf
- local/path/db/pialert.db:/home/pi/pialert/db/pialert.db
- /local/path/config/pialert.conf:/home/pi/pialert/config/pialert.conf
- /local/path/db/pialert.db:/home/pi/pialert/db/pialert.db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/home/pi/pialert/front/log
- /local/path/logs:/home/pi/pialert/front/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -150,10 +150,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config/app.conf:/app/config/app.conf # 🆕 This has changed
- local/path/db/app.db:/app/db/app.db # 🆕 This has changed
- /local/path/config/app.conf:/data/config/app.conf # 🆕 This has changed
- /local/path/db/app.db:/data/db/app.db # 🆕 This has changed
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log # 🆕 This has changed
- /local/path/logs:/tmp/log # 🆕 This has changed
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -190,10 +190,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- /local/path/config:/data/config
- /local/path/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log
- /local/path/logs:/tmp/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -207,10 +207,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- /local/path/config:/data/config
- /local/path/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log
- /local/path/logs:/tmp/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -234,10 +234,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- /local/path/config:/data/config
- /local/path/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log
- /local/path/logs:/tmp/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -253,11 +253,19 @@ services:
```sh
docker run -it --rm --name netalertx --user "0" \
-v local/path/config:/app/config \
-v local/path/db:/app/db \
-v /local/path/config:/data/config \
-v /local/path/db:/data/db \
ghcr.io/jokob-sk/netalertx:latest
```
..or alternatively execute:
```bash
sudo chown -R 20211:20211 /local/path/config
sudo chown -R 20211:20211 /local/path/db
sudo chmod -R a+rwx /local/path/
```
7. Stop the container
8. Update the `docker-compose.yml` as per example below.
@@ -273,24 +281,16 @@ services:
- NET_BIND_SERVICE # 🆕 New line
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- /local/path/config:/data/config
- /local/path/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
#- local/path/logs:/app/log
#- /local/path/logs:/tmp/log
environment:
- TZ=Europe/Berlin
- PORT=20211
# 🆕 New "tmpfs" section START 🔽
tmpfs:
# Speed up logging. This can be commented out to retain logs between container restarts
- "/app/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# Speed up API access as frontend/backend API is very chatty
- "/app/api:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,sync,noatime,nodiratime"
# Required for customization of the nginx listen addr/port without rebuilding the container
- "/services/config/nginx/conf.active:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# /services/config/nginx/conf.d is required for nginx and php to start
- "/services/run:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# /tmp is required by php for session save this should be reworked to /services/run/tmp
tmpfs:
# All writable runtime state resides under /tmp; comment out to persist logs between restarts
- "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# 🆕 New "tmpfs" section END 🔼
```

View File

@@ -44,14 +44,19 @@ In Notification Processing settings, you can specify blanket rules. These allow
1. Notify on (`NTFPRCS_INCLUDED_SECTIONS`) allows you to specify which events trigger notifications. Usual setups will have `new_devices`, `down_devices`, and possibly `down_reconnected` set. Including `plugin` (dependenton the Plugin `<plugin>_WATCH` and `<plugin>_REPORT_ON` settings) and `events` (dependent on the on-device **Alert Events** setting) might be too noisy for most setups. More info in the [NTFPRCS plugin](https://github.com/jokob-sk/NetAlertX/blob/main/front/plugins/notification_processing/README.md) on what events these selections include.
2. Alert down after (`NTFPRCS_alert_down_time`) is useful if you want to wait for some time before the system sends out a down notification for a device. This is related to the on-device **Alert down** setting and only devices with this checked will trigger a down notification.
3. A filter to allow you to set device-specific exceptions to New devices being added to the app.
4. A filter to allow you to set device-specific exceptions to generated Events.
## Ignoring devices 🔕
You can filter out unwanted notifications globally. This could be because of a misbehaving device (GoogleNest/GoogleHub (See also [ARPSAN docs and the `--exclude-broadcast` flag](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/arp_scan#ip-flipping-on-google-nest-devices))) which flips between IP addresses, or because you want to ignore new device notifications of a certain pattern.
1. Events Filter (`NTFPRCS_event_condition`) - Filter out Events from notifications.
2. New Devices Filter (`NTFPRCS_new_dev_condition`) - Filter out New Devices from notifications, but log and keep a new device in the system.
## Ignoring devices 💻
![Ignoring new devices](./img/NOTIFICATIONS/NEWDEV_ignores.png)
You can completely ignore detected devices globally. This could be because your instance detects docker containers, you want to ignore devices from a specific manufacturer via MAC rules or you want to ignore devices on a specific IP range.
1. Ignored MACs (`NEWDEV_ignored_MACs`) - List of MACs to ignore.
2. Ignored IPs (`NEWDEV_ignored_IPs`) - List of IPs to ignore.
2. Ignored IPs (`NEWDEV_ignored_IPs`) - List of IPs to ignore.

View File

@@ -62,7 +62,7 @@ For example, the **ICMP plugin** allows you to specify a regular expression to s
## Storing Temporary Files in Memory
On systems with slower I/O speeds, you can optimize performance by storing temporary files in memory. This primarily applies to the `/app/api` and `/app/log` folders.
On systems with slower I/O speeds, you can optimize performance by storing temporary files in memory. This primarily applies to the API directory (default: `/tmp/api`, configurable via `NETALERTX_API`) and `/tmp/log` folders.
Using `tmpfs` reduces disk writes and improves performance. However, it should be **disabled** if persistent logs or API data storage are required.
@@ -80,15 +80,15 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- local/path/config:/data/config
- local/path/db:/data/db
# (Optional) Useful for debugging setup issues
- local/path/logs:/app/log
- local/path/logs:/tmp/log
# (API: OPTION 1) Store temporary files in memory (recommended for performance)
- type: tmpfs # ◀ 🔺
target: /app/api # ◀ 🔺
target: /tmp/api # ◀ 🔺
# (API: OPTION 2) Store API data on disk (useful for debugging)
# - local/path/api:/app/api
# - local/path/api:/tmp/api
environment:
- TZ=Europe/Berlin
- PORT=20211

View File

@@ -1,146 +1,192 @@
## config.json Lifecycle in NetAlertX
# Plugins Implementation Details
This document describes on a high level how `config.json` is read, processed, and used by the NetAlertX core and plugins. It also outlines the plugin output contract and the main plugin types.
Plugins provide data to the NetAlertX core, which processes it to detect changes, discover new devices, raise alerts, and apply heuristics.
> [!NOTE]
> For a deep-dive on the specific configuration options and sections of the `config.json` plugin manifest, consult the [Plugins Development Guide](PLUGINS_DEV.md).
---
## Overview: Plugin Data Flow
1. Each plugin runs on a defined schedule.
2. Aligning all plugin schedules is recommended so they execute in the same loop.
3. During execution, all plugins write their collected data into the **`CurrentScan`** table.
4. After all plugins complete, the `CurrentScan` table is evaluated to detect **new devices**, **changes**, and **triggers**.
Although plugins run independently, they contribute to the shared `CurrentScan` table.
To inspect its contents, set `LOG_LEVEL=trace` and check for the log section:
```
================ CurrentScan table content ================
```
---
## `config.json` Lifecycle
This section outlines how each plugins `config.json` manifest is read, validated, and used by the core and plugins.
It also describes plugin output expectations and the main plugin categories.
> [!TIP]
> For detailed schema and examples, see the [Plugin Development Guide](PLUGINS_DEV.md).
---
### 1. Loading
* On startup, the app core loads `config.json` for each plugin.
* The `config.json` represents a plugin manifest, that contains metadata and runtime settings.
* On startup, the core loads `config.json` for each plugin.
* The file acts as a **plugin manifest**, defining metadata, runtime configuration, and database mappings.
---
### 2. Validation
* The core checks that each required settings key (such as `RUN`) for a plugin exists.
* Invalid or missing values may be replaced with defaults, or the plugin may be disabled.
* The core validates required keys (for example, `RUN`).
* Missing or invalid entries may be replaced with defaults or cause the plugin to be disabled.
---
### 3. Preparation
* The plugins settings (paths, commands, parameters) are prepared.
* Database mappings (`mapped_to_table`, `database_column_definitions`) for data ingestion into the core app are parsed.
* Plugin parameters (paths, commands, and options) are prepared for execution.
* Database mappings (`mapped_to_table`, `database_column_definitions`) are parsed to define how data integrates with the main app.
---
### 4. Execution
* Plugins can be run at different core app execution points, such as on schedule, once on start, after a notification, etc.
* At runtime, the scheduler triggers plugins according to their `interval`.
* The plugin executes its command or script.
* Plugins may run:
* On a fixed schedule.
* Once at startup.
* After a notification or other trigger.
* The scheduler executes plugins according to their `interval`.
---
### 5. Parsing
* Plugin output is expected in **pipe (`|`)-delimited format**.
* The core parses lines into fields, matching the **plugin interface contract**.
* Plugin output must be **pipe-delimited (`|`)**.
* The core parses each output line following the **Plugin Interface Contract**, splitting and mapping fields accordingly.
---
### 6. Mapping
* Each parsed field is moved into the `Plugins_` database tables and can be mapped into a configured database table.
* Controlled by `database_column_definitions` and `mapped_to_table`.
* Example: `Object_PrimaryID → Devices.MAC`.
* Parsed fields are inserted into the plugins `Plugins_*` table.
* Data can be mapped into other tables (e.g., `Devices`, `CurrentScan`) as defined by:
* `database_column_definitions`
* `mapped_to_table`
**Example:** `Object_PrimaryID → devMAC`
---
### 6a. Plugin Output Contract
Each plugin must output results in the **plugin interface contract format**, pipe (`|`)-delimited values, in the column order described under [Plugin Interface Contract](PLUGINS_DEV.md)
All plugins must follow the **Plugin Interface Contract** defined in `PLUGINS_DEV.md`.
Output values are pipe-delimited in a fixed order.
#### IDs
#### Identifiers
* `Object_PrimaryID` and `Object_SecondaryID` identify the record (e.g. `MAC|IP`).
* `Object_PrimaryID` and `Object_SecondaryID` uniquely identify records (for example, `MAC|IP`).
#### **Watched values (`Watched_Value14`)**
#### Watched Values (`Watched_Value14`)
* Used by the core to detect changes between runs.
* Changes here can trigger **notifications**.
* Used by the core to detect changes between runs.
* Changes in these fields can trigger notifications.
#### **Extra value (`Extra`)**
#### Extra Field (`Extra`)
* Optional, extra field.
* Stored in the database but **not used for alerts**.
* Optional additional value.
* Stored in the database but not used for alerts.
#### **Helper values (`Helper_Value13`)**
#### Helper Values (`Helper_Value13`)
* Added for cases where more than IDs + watched + extra are needed.
* Can be made visible in the UI.
* Stored in the database but **not used for alerts**.
* Optional auxiliary data (for display or plugin logic).
* Stored but not alert-triggering.
#### **Mapping matters**
#### Mapping
* While the plugin output is free-form, the `database_column_definitions` and `mapped_to_table` settings in `config.json` determine the **target columns and data types** in NetAlertX.
* While the output format is flexible, the plugins manifest determines the destination and type of each field.
---
### 7. Persistence
* Data is upserted into the database.
* Conflicts are resolved using `Object_PrimaryID` + `Object_SecondaryID`.
* Parsed data is **upserted** into the database.
* Conflicts are resolved using the combined key: `Object_PrimaryID + Object_SecondaryID`.
---
### 8. Plugin Types and Expected Outputs
## Plugin Categories
Beyond the `data_source` setting, plugins fall into functional categories. Each has its own input requirements and output expectations:
Plugins fall into several functional categories depending on their purpose and expected outputs.
#### **Device discovery plugins**
### 1. Device Discovery Plugins
* **Inputs:** `N/A`, subnet, or API for discovery service, or similar.
* **Outputs:** At minimum `MAC` and `IP` that results in a new or updated device records in the `Devices` table.
* **Mapping:** Must be mapped to the `CurrentScan` table via `database_column_definitions` and `data_filters`.
* **Examples:** ARP-scan, NMAP device discovery (e.g., `ARPSCAN`, `NMAPDEV`).
#### **Device-data enrichment plugins**
* **Inputs:** Device identifier (usually `MAC`, `IP`).
* **Outputs:** Additional data for that device (e.g. open ports).
* **Mapping:** Controlled via `database_column_definitions` and `data_filters`.
* **Examples:** Ports, MQTT messages (e.g., `NMAP`, `MQTT`)
#### **Name resolver plugins**
* **Inputs:** Device identifiers (MAC, IP, or hostname).
* **Outputs:** Updated `devName` and `devFQDN` fields.
* **Mapping:** Not expected.
* **Note:** Currently requires **core app modification** to add new plugins, not fully driven by the plugins `config.json`.
* **Examples:** Avahiscan (e.g., `NBTSCAN`, `NSLOOKUP`).
#### **Generic plugins**
* **Inputs:** Whatever the script or query provides.
* **Outputs:** Data shown only in **Integrations → Plugins**, not tied to devices.
* **Mapping:** Not expected.
* **Examples:** External monitoring data (e.g., `INTRSPD`)
#### **Configuration-only plugins**
* **Inputs/Outputs:** None at runtime.
* **Mapping:** Not expected.
* **Examples:** Used to provide additional settings or execute scripts (e.g., `MAINT`, `CSVBCKP`).
* **Inputs:** None, subnet, or discovery API.
* **Outputs:** `MAC` and `IP` for new or updated device records in `Devices`.
* **Mapping:** Required usually into `CurrentScan`.
* **Examples:** `ARPSCAN`, `NMAPDEV`.
---
### 9. Post-Processing
### 2. Device Data Enrichment Plugins
* Notifications are generated if watched values change.
* UI is updated with new or updated records.
* All values that are configured to be shown in teh UI appear in the Plugins section.
* **Inputs:** Device identifiers (`MAC`, `IP`).
* **Outputs:** Additional metadata (for example, open ports or sensors).
* **Mapping:** Controlled via manifest definitions.
* **Examples:** `NMAP`, `MQTT`.
---
### 10. Summary
### 3. Name Resolver Plugins
The lifecycle of `config.json` entries is:
* **Inputs:** Device identifiers (`MAC`, `IP`, hostname`).
* **Outputs:** Updated `devName` and `devFQDN`.
* **Mapping:** Typically none.
* **Note:** Adding new resolvers currently requires a core change.
* **Examples:** `NBTSCAN`, `NSLOOKUP`.
---
### 4. Generic Plugins
* **Inputs:** Custom, based on the plugin logic or script.
* **Outputs:** Data displayed under **Integrations → Plugins** only.
* **Mapping:** Not required.
* **Examples:** `INTRSPD`, custom monitoring scripts.
---
### 5. Configuration-Only Plugins
* **Inputs/Outputs:** None at runtime.
* **Purpose:** Used for configuration or maintenance tasks.
* **Examples:** `MAINT`, `CSVBCKP`.
---
## Post-Processing
After persistence:
* The core generates notifications for any watched value changes.
* The UI updates with new or modified data.
* Plugins with UI-enabled data display under **Integrations → Plugins**.
---
## Summary
The lifecycle of a plugin configuration is:
**Load → Validate → Prepare → Execute → Parse → Map → Persist → Post-process**
Plugins must follow the **output contract**, and their category (discovery, specific, resolver, generic, config-only) defines what inputs they require and what outputs are expected.
Each plugin must:
* Follow the **output contract**.
* Declare its type and expected output structure.
* Define mappings and watched values clearly in `config.json`.

View File

@@ -13,7 +13,7 @@ There is also an in-app Help / FAQ section that should be answering frequently a
#### 🐳 Docker (Fully supported)
- The main installation method is as a [docker container - follow these instructions here](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md).
- The main installation method is as a [docker container - follow these instructions here](./DOCKER_INSTALLATION.md).
#### 💻 Bare-metal / On-server (Experimental/community supported 🧪)

View File

@@ -42,9 +42,9 @@ services:
image: "ghcr.io/jokob-sk/netalertx:latest"
restart: unless-stopped
volumes:
- /home/netalertx/config:/app/config
- /home/netalertx/db:/app/db
- /home/netalertx/log:/app/log
- /home/netalertx/config:/data/config
- /home/netalertx/db:/data/db
- /home/netalertx/log:/tmp/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -68,9 +68,9 @@ services:
image: "ghcr.io/jokob-sk/netalertx:latest"
restart: unless-stopped
volumes:
- ./config/app.conf:/app/config/app.conf
- ./db:/app/db
- ./log:/app/log
- ./config/app.conf:/data/config/app.conf
- ./db:/data/db
- ./log:/tmp/log
- ./config/resolv.conf:/etc/resolv.conf # Mapping the /resolv.conf file for better name resolution
environment:
- TZ=Europe/Berlin

View File

@@ -499,8 +499,8 @@ Mapping the updated file (on the local filesystem at `/appl/docker/netalertx/def
```bash
docker run -d --rm --network=host \
--name=netalertx \
-v /appl/docker/netalertx/config:/app/config \
-v /appl/docker/netalertx/db:/app/db \
-v /appl/docker/netalertx/config:/data/config \
-v /appl/docker/netalertx/db:/data/db \
-v /appl/docker/netalertx/default:/etc/nginx/sites-available/default \
-e TZ=Europe/Amsterdam \
-e PORT=20211 \

View File

@@ -48,7 +48,7 @@ Heres a breakdown of the defensive layers you get, right out of the box using
**Methodology:** All writable locations are treated as untrusted, temporary, and non-executable by default.
* **In-Memory Volatile Storage:** The `docker-compose.yml` configuration maps all temporary directories (e.g., `/app/log`, `/app/api`, `/tmp`) to in-memory `tmpfs` filesystems. They do not exist on the host's disk.
* **In-Memory Volatile Storage:** The `docker-compose.yml` configuration maps all temporary directories (e.g., `/tmp/log`, `/tmp/api`, `/tmp`) to in-memory `tmpfs` filesystems. They do not exist on the host's disk.
* **Volatile Data:** Because these locations exist only in RAM, their contents are **instantly and irrevocably erased** when the container is stopped. This provides a "self-cleaning" mechanism that purges any attacker-dropped files or payloads on every single restart.

View File

@@ -40,10 +40,10 @@ services:
network_mode: "host"
restart: unless-stopped
volumes:
- local/path/config:/app/config
- local/path/db:/app/db
- local/path/config:/data/config
- local/path/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
- local/path/logs:/app/log
- local/path/logs:/tmp/log
environment:
- TZ=Europe/Berlin
- PORT=20211
@@ -57,10 +57,10 @@ services:
```yaml
volumes:
- /volume1/app_storage/netalertx/config:/app/config
- /volume1/app_storage/netalertx/db:/app/db
- /volume1/app_storage/netalertx/config:/data/config
- /volume1/app_storage/netalertx/db:/data/db
# (optional) useful for debugging if you have issues setting up the container
# - local/path/logs:/app/log <- commented out with # ⚠
# - local/path/logs:/tmp/log <- commented out with # ⚠
```
![Adjusting docker-compose](./img/SYNOLOGY/08_Adjust_docker_compose_volumes.png)

View File

@@ -15,7 +15,7 @@ The **Web UI** is served by an **nginx** server, while the **API backend** runs
APP_CONF_OVERRIDE={"GRAPHQL_PORT":"20212"}
```
For more information, check the [Docker installation guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md).
For more information, check the [Docker installation guide](./DOCKER_INSTALLATION.md).
## Possible issues and troubleshooting
@@ -62,11 +62,11 @@ In the container execute and investigate:
`cat /var/log/nginx/error.log`
`cat /app/log/app.php_errors.log`
`cat /tmp/log/app.php_errors.log`
### 8. Make sure permissions are correct
> [!TIP]
> You can try to start the container without mapping the `/app/config` and `/app/db` dirs and if the UI shows up then the issue is most likely related to your file system permissions or file ownership.
> You can try to start the container without mapping the `/data/config` and `/data/db` dirs and if the UI shows up then the issue is most likely related to your file system permissions or file ownership.
Please read the [Permissions troubleshooting guide](./FILE_PERMISSIONS.md) and provide a screesnhot of the permissions and ownership in the `/app/db` and `app/config` directories.
Please read the [Permissions troubleshooting guide](./FILE_PERMISSIONS.md) and provide a screesnhot of the permissions and ownership in the `/data/db` and `app/config` directories.

View File

@@ -23,7 +23,7 @@ Limit capabilities to only those required:
- NET_ADMIN
- NET_BIND_SERVICE
```
- Remove any unnecessary `--cap-add` flags from docker run commands
- Remove any unnecessary `--cap-add` or `--privileged` flags from docker run commands
## Additional Resources

View File

@@ -24,9 +24,9 @@ Review and correct your volume mounts in docker-compose.yml:
Example volume configuration:
```yaml
volumes:
- ./data/db:/app/db
- ./data/config:/app/config
- ./data/log:/app/log
- ./data/db:/data/db
- ./data/config:/data/config
- ./data/log:/tmp/log
```
## Additional Resources

View File

@@ -20,7 +20,7 @@ If you want to use a custom port, create a bind mount for the nginx configuratio
- Add to your docker-compose.yml:
```yaml
volumes:
- /path/to/nginx-config:/app/system/services/active/config
- /path/to/nginx-config:/tmp/nginx/active-config
environment:
- PORT=your_custom_port
```

View File

@@ -21,7 +21,7 @@ The app can be installed different ways, with the best support of the docker-bas
NetAlertX is fully supported in Docker environments, allowing for easy setup and configuration. Follow the official guide to get started:
- [Docker Installation Guide](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md)
- [Docker Installation Guide](./DOCKER_INSTALLATION.md)
This guide will take you through the process of setting up NetAlertX using Docker Compose or standalone Docker commands.

View File

@@ -15,9 +15,22 @@
<?php
require 'php/templates/header.php';
// check permissions
$dbPath = "../db/app.db";
$confPath = "../config/app.conf";
// Use environment-aware paths with fallback to legacy locations
$dbFolderPath = rtrim(getenv('NETALERTX_DB') ?: '/data/db', '/');
$configFolderPath = rtrim(getenv('NETALERTX_CONFIG') ?: '/data/config', '/');
$dbPath = $dbFolderPath . '/app.db';
$confPath = $configFolderPath . '/app.conf';
// Fallback to legacy paths if new locations don't exist
if (!file_exists($dbPath) && file_exists('../db/app.db')) {
$dbPath = '../db/app.db';
}
if (!file_exists($confPath) && file_exists('../config/app.conf')) {
$confPath = '../config/app.conf';
}
checkPermissions([$dbPath, $confPath]);
?>

View File

@@ -17,11 +17,12 @@
// Size and last mod of DB ------------------------------------------------------
$nax_db = str_replace('front', 'db', getcwd()).'/app.db';
$nax_wal = str_replace('front', 'db', getcwd()).'/app.db-wal';
$nax_db_size = number_format((filesize($nax_db) / 1000000),2,",",".") . ' MB';
$nax_wal_size = number_format((filesize($nax_wal) / 1000000),2,",",".") . ' MB';
$nax_db_mod = date ("F d Y H:i:s", filemtime($nax_db));
$dbBasePath = rtrim(getenv('NETALERTX_DB') ?: '/data/db', '/');
$nax_db = $dbBasePath . '/app.db';
$nax_wal = $dbBasePath . '/app.db-wal';
$nax_db_size = file_exists($nax_db) ? number_format((filesize($nax_db) / 1000000),2,",",".") . ' MB' : '0 MB';
$nax_wal_size = file_exists($nax_wal) ? number_format((filesize($nax_wal) / 1000000),2,",",".") . ' MB' : '0 MB';
$nax_db_mod = file_exists($nax_db) ? date ("F d Y H:i:s", filemtime($nax_db)) : 'N/A';
// Table sizes -----------------------------------------------------------------
@@ -334,7 +335,7 @@ $db->close();
var emptyArr = ['undefined', "", undefined, null];
var selectedTab = 'tab_DBTools_id';
initializeTabs();
// initializeTabs() is called in window.onload
// -----------------------------------------------------------
// delete devices with emty macs
@@ -704,7 +705,7 @@ function renderLogs(customData) {
window.onload = function asyncFooter() {
renderLogs();
// initializeTabs();
initializeTabs();
try {
$("#lastCommit").append('<a href="https://github.com/jokob-sk/NetAlertX/commits" target="_blank"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/jokob-sk/netalertx/main?logo=github"></a>');

View File

@@ -2,30 +2,60 @@
require '../server/init.php';
$logBasePath = rtrim(getenv('NETALERTX_LOG') ?: '/tmp/log', '/');
function resolveLogPath($path)
{
global $logBasePath;
if ($path === null || $path === '') {
return $path;
}
$placeholder = '__NETALERTX_LOG__';
if (strpos($path, $placeholder) === 0) {
return $logBasePath . substr($path, strlen($placeholder));
}
return $path;
}
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
// Function to render the log area component
function renderLogArea($params) {
global $logBasePath;
$fileName = isset($params['fileName']) ? $params['fileName'] : '';
$filePath = isset($params['filePath']) ? $params['filePath'] : '';
$textAreaCssClass = isset($params['textAreaCssClass']) ? $params['textAreaCssClass'] : '';
$buttons = isset($params['buttons']) ? $params['buttons'] : [];
$content = "";
$fileSize = 0;
if (filesize($filePath) > 2000000) {
$filePath = resolveLogPath($filePath);
if (!is_file($filePath)) {
$content = "";
$fileSizeMb = 0.0;
} elseif (filesize($filePath) > 2000000) {
$content = file_get_contents($filePath, false, null, -2000000);
$fileSizeMb = filesize($filePath) / 1000000;
} else {
$content = file_get_contents($filePath);
$fileSizeMb = filesize($filePath) / 1000000;
}
// Prepare the download button HTML if filePath starts with /app
// Prepare the download button HTML if filePath resides under the active log base path
$downloadButtonHtml = '';
if (strpos($filePath, '/app') === 0) {
$logPrefix = $logBasePath . '/';
if ($logPrefix !== '/' && strpos($filePath, $logPrefix) === 0) {
$downloadName = basename($filePath);
$downloadButtonHtml = '
<span class="span-padding">
<a href="' . htmlspecialchars(str_replace('/app/log/', '/php/server/query_logs.php?file=', $filePath)) . '" target="_blank">
<a href="' . htmlspecialchars('/php/server/query_logs.php?file=' . rawurlencode($downloadName)) . '" target="_blank">
<i class="fa fa-download"></i>
</a>
</span>';
@@ -34,13 +64,7 @@ function renderLogArea($params) {
// Prepare buttons HTML
$buttonsHtml = '';
$totalButtons = count($buttons);
if ($totalButtons > 0) {
$colClass = 12 / $totalButtons;
// Use $colClass in your HTML generation or further logic
} else {
// Handle case where $buttons array is empty
$colClass = 12;
}
$colClass = $totalButtons > 0 ? (12 / $totalButtons) : 12;
foreach ($buttons as $button) {
$labelStringCode = isset($button['labelStringCode']) ? $button['labelStringCode'] : '';
@@ -52,8 +76,7 @@ function renderLogArea($params) {
</div>';
}
// Render the log area HTML
// Render HTML
$html = '
<div class="log-area box box-solid box-primary">
<div class="row logs-row col-sm-12 col-xs-12">
@@ -63,7 +86,7 @@ function renderLogArea($params) {
</div>
<div class="row logs-row">
<div class="log-file col-sm-6 col-xs-12">' . htmlspecialchars($filePath) . '
<div class="logs-size">' . number_format((filesize($filePath) / 1000000), 2, ",", ".") . ' MB'
<div class="logs-size">' . number_format($fileSizeMb, 2, ",", ".") . ' MB'
. $downloadButtonHtml .
'</div>
</div>

View File

@@ -10,8 +10,8 @@
"event": "askRestartBackend()"
}
],
"fileName": "app.log",
"filePath": "/app/log/app.log",
"fileName": "app.log",
"filePath": "__NETALERTX_LOG__/app.log",
"textAreaCssClass": "logs"
},
@@ -22,8 +22,8 @@
"event": "logManage('app_front.log', 'cleanLog')"
}
],
"fileName": "app_front.log",
"filePath": "/app/log/app_front.log",
"fileName": "app_front.log",
"filePath": "__NETALERTX_LOG__/app_front.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -33,8 +33,8 @@
"event": "logManage('app.php_errors.log', 'cleanLog')"
}
],
"fileName": "app.php_errors.log",
"filePath": "/app/log/app.php_errors.log",
"fileName": "app.php_errors.log",
"filePath": "__NETALERTX_LOG__/app.php_errors.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -44,15 +44,19 @@
"event": "logManage('execution_queue.log', 'cleanLog')"
}
],
"fileName": "execution_queue.log",
"filePath": "/app/log/execution_queue.log",
"fileName": "execution_queue.log",
"filePath": "__NETALERTX_LOG__/execution_queue.log",
"textAreaCssClass": "logs logs-small"
},
{
"buttons": [
{
"labelStringCode": "Maint_PurgeLog",
"event": "logManage('nginx-error.log', 'cleanLog')"
}
],
"fileName": "nginx/error.log",
"filePath": "/var/log/nginx/error.log",
"fileName": "nginx-error.log",
"filePath": "__NETALERTX_LOG__/nginx-error.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -62,8 +66,8 @@
"event": "logManage('db_is_locked.log', 'cleanLog')"
}
],
"fileName": "db_is_locked.log",
"filePath": "/app/log/db_is_locked.log",
"fileName": "db_is_locked.log",
"filePath": "__NETALERTX_LOG__/db_is_locked.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -73,8 +77,8 @@
"event": "logManage('stdout.log', 'cleanLog')"
}
],
"fileName": "stdout.log",
"filePath": "/app/log/stdout.log",
"fileName": "stdout.log",
"filePath": "__NETALERTX_LOG__/stdout.log",
"textAreaCssClass": "logs logs-small"
},
{
@@ -84,8 +88,30 @@
"event": "logManage('stderr.log', 'cleanLog')"
}
],
"fileName": "stderr.log",
"filePath": "/app/log/stderr.log",
"fileName": "stderr.log",
"filePath": "__NETALERTX_LOG__/stderr.log",
"textAreaCssClass": "logs logs-small"
},
{
"buttons": [
{
"labelStringCode": "Maint_PurgeLog",
"event": "logManage('IP_changes.log', 'cleanLog')"
}
],
"fileName": "IP_changes.log",
"filePath": "__NETALERTX_LOG__/IP_changes.log",
"textAreaCssClass": "logs logs-small"
},
{
"buttons": [
{
"labelStringCode": "Maint_PurgeLog",
"event": "logManage('crond.log', 'cleanLog')"
}
],
"fileName": "crond.log",
"filePath": "__NETALERTX_LOG__/crond.log",
"textAreaCssClass": "logs logs-small"
}
]

View File

@@ -13,8 +13,35 @@
// $DBFILE = dirname(__FILE__).'/../../../db/app.db';
// $DBFILE_LOCKED_FILE = dirname(__FILE__).'/../../../log/db_is_locked.log';
$scriptDir = realpath(dirname(__FILE__)); // Resolves symlinks to the actual physical path
$DBFILE = $scriptDir . '/../../../db/app.db';
$DBFILE_LOCKED_FILE = $scriptDir . '/../../../log/db_is_locked.log';
$legacyDbPath = $scriptDir . '/../../../db/app.db';
$legacyLogDir = $scriptDir . '/../../../log';
$dbFolderPath = rtrim(getenv('NETALERTX_DB') ?: '/data/db', '/');
$logFolderPath = rtrim(getenv('NETALERTX_LOG') ?: '/tmp/log', '/');
// Fallback to legacy layout if the new location is missing but the legacy file still exists
if (!is_dir($dbFolderPath) && file_exists($legacyDbPath)) {
$dbFolderPath = dirname($legacyDbPath);
}
if (!is_dir($dbFolderPath)) {
@mkdir($dbFolderPath, 0775, true);
}
$DBFILE = rtrim($dbFolderPath, '/') . '/app.db';
if (!file_exists($DBFILE) && file_exists($legacyDbPath)) {
$DBFILE = $legacyDbPath;
}
if (!is_dir($logFolderPath) && is_dir($legacyLogDir)) {
$logFolderPath = $legacyLogDir;
}
if (!is_dir($logFolderPath)) {
@mkdir($logFolderPath, 0775, true);
}
$DBFILE_LOCKED_FILE = rtrim($logFolderPath, '/') . '/db_is_locked.log';
//------------------------------------------------------------------------------
@@ -39,8 +66,10 @@ function SQLite3_connect($trytoreconnect = true, $retryCount = 0) {
if (!file_exists($DBFILE)) {
die("Database file not found: $DBFILE");
}
if (!file_exists(dirname($DBFILE_LOCKED_FILE))) {
die("Log directory not found: " . dirname($DBFILE_LOCKED_FILE));
$lockDir = dirname($DBFILE_LOCKED_FILE);
if (!is_dir($lockDir) && !@mkdir($lockDir, 0775, true)) {
die("Log directory not found and could not be created: $lockDir");
}
@@ -130,6 +159,7 @@ class CustomDatabaseWrapper {
$message = 'Error executing query (attempts: ' . $attempts . '), query: ' . $query;
// write_notification($message);
error_log("Query failed after {$this->maxRetries} attempts: " . $this->sqlite->lastErrorMsg());
return false;
}
public function query_log_add($query)
@@ -187,7 +217,7 @@ function OpenDB($DBPath = null) {
if (strlen($DBFILE) == 0) {
$message = 'Database not available';
echo '<script>alert('.$message.')</script>';
echo '<script>alert("'.$message.'")</script>';
write_notification($message);
die('<div style="padding-left:150px">'.$message.'</div>');
@@ -197,7 +227,7 @@ function OpenDB($DBPath = null) {
$db = new CustomDatabaseWrapper($DBFILE);
} catch (Exception $e) {
$message = "Error connecting to the database";
echo '<script>alert('.$message.'": ' . $e->getMessage() . '")</script>';
echo '<script>alert("'.$message.': '.$e->getMessage().'")</script>';
write_notification($message);
die('<div style="padding-left:150px">'.$message.'</div>');
}

View File

@@ -1,5 +1,6 @@
<?php
ini_set('error_log', '../../log/app.php_errors.log'); // initializing the app.php_errors.log file for the maintenance section
$logPath = rtrim(getenv('NETALERTX_LOG') ?: '/tmp/log', '/') . '/app.php_errors.log';
ini_set('error_log', $logPath); // initializing the app.php_errors.log file for the maintenance section
require dirname(__FILE__).'/../templates/globals.php';
require dirname(__FILE__).'/db.php';
require dirname(__FILE__).'/util.php';

View File

@@ -18,7 +18,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// Check if file parameter is provided
if ($file) {
// Define the folder where files are located
$filePath = "/app/config/" . basename($file);
$configRoot = getenv('NETALERTX_CONFIG') ?: '/data/config';
$filePath = rtrim($configRoot, '/') . "/" . basename($file);
// Check if the file exists and is readable
if (file_exists($filePath) && is_readable($filePath)) {

View File

@@ -11,6 +11,8 @@ require dirname(__FILE__).'/../server/init.php';
//------------------------------------------------------------------------------
// Handle incoming requests
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$configRoot = getenv('NETALERTX_CONFIG') ?: '/data/config';
$apiRoot = getenv('NETALERTX_API') ?: '/tmp/api';
// Get query string parameter ?file=settings_table.json
$file = isset($_GET['file']) ? $_GET['file'] : null;
@@ -19,10 +21,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// Define the folder where files are located
if ($file == "workflows.json")
{
$filePath = "/app/config/" . basename($file);
$filePath = rtrim($configRoot, '/') . "/" . basename($file);
} else
{
$filePath = "/app/api/" . basename($file);
$filePath = rtrim($apiRoot, '/') . "/" . basename($file);
}
// Check if the file exists
@@ -59,7 +61,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
$file = $_GET['file'];
$filePath = "/app/config/" . basename($file);
$configRoot = getenv('NETALERTX_CONFIG') ?: '/data/config';
$filePath = rtrim($configRoot, '/') . "/" . basename($file);
// Save new workflows.json (replace existing content)
if (file_put_contents($filePath, json_encode($decodedData, JSON_PRETTY_PRINT))) {

View File

@@ -16,8 +16,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// Check if file parameter is provided
if ($file) {
// Define the folder where files are located
$filePath = "/app/log/" . basename($file);
// Define the folder where files are located
$logBasePath = rtrim(getenv('NETALERTX_LOG') ?: '/tmp/log', '/');
$filePath = $logBasePath . '/' . basename($file);
// Check if the file exists
if (file_exists($filePath)) {

View File

@@ -11,6 +11,7 @@
require dirname(__FILE__).'/../templates/globals.php';
require dirname(__FILE__).'/../templates/skinUI.php';
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
@@ -176,7 +177,10 @@ function checkPermissions($files)
}
// ----------------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /messaging/in-app/write
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
function displayMessage($message, $logAlert = FALSE, $logConsole = TRUE, $logFile = TRUE, $logEcho = FALSE)
{
global $logFolderPath, $log_file, $timestamp;
@@ -234,7 +238,10 @@ function displayMessage($message, $logAlert = FALSE, $logConsole = TRUE, $logFil
}
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /logs/add-to-execution-queue
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// ----------------------------------------------------------------------------------------
// Adds an action to perform into the execution_queue.log file
function addToExecutionQueue($action)
@@ -257,13 +264,17 @@ function addToExecutionQueue($action)
// ----------------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /logs DELETE
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
function cleanLog($logFile)
{
global $logFolderPath, $timestamp;
$path = "";
$allowedFiles = ['app.log', 'app_front.log', 'IP_changes.log', 'stdout.log', 'stderr.log', 'app.php_errors.log', 'execution_queue.log', 'db_is_locked.log'];
$allowedFiles = ['app.log', 'app_front.log', 'IP_changes.log', 'stdout.log', 'stderr.log', 'app.php_errors.log', 'execution_queue.log', 'db_is_locked.log', 'nginx-error.log', 'crond.log'];
if(in_array($logFile, $allowedFiles))
{
@@ -312,7 +323,7 @@ function saveSettings()
$txt = $txt."#-----------------AUTOGENERATED FILE-----------------#\n";
$txt = $txt."# #\n";
$txt = $txt."# Generated: ".$timestamp." #\n";
$txt = $txt."# Generated: ".$timestamp." #\n";
$txt = $txt."# #\n";
$txt = $txt."# Config file for the LAN intruder detection app: #\n";
$txt = $txt."# https://github.com/jokob-sk/NetAlertX #\n";
@@ -321,7 +332,12 @@ function saveSettings()
// collect all groups
$decodedSettings = json_decode($SETTINGS, true);
$decodedSettings = json_decode((string)$SETTINGS, true);
if ($decodedSettings === null) {
echo "Error: Invalid JSON in settings data.";
return;
}
foreach ($decodedSettings as $setting) {
if( in_array($setting[0] , $groups) == false) {
@@ -418,6 +434,10 @@ function saveSettings()
}
// -------------------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /graphql LangStrings endpoint
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
function getString ($setKey, $default) {
$result = lang($setKey);
@@ -430,9 +450,14 @@ function getString ($setKey, $default) {
return $default;
}
// -------------------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
// equivalent: /settings/<key>
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
function getSettingValue($setKey) {
// Define the JSON endpoint URL
$url = dirname(__FILE__).'/../../../api/table_settings.json';
// Define the JSON endpoint URL
$apiRoot = rtrim(getenv('NETALERTX_API') ?: '/tmp/api', '/');
$url = $apiRoot . '/table_settings.json';
// Fetch the JSON data
$json = file_get_contents($url);

View File

@@ -7,6 +7,11 @@
require dirname(__FILE__).'/../templates/globals.php';
function get_notification_store_path(): string {
$apiRoot = getenv('NETALERTX_API') ?: '/tmp/api';
return rtrim($apiRoot, '/') . '/user_notifications.json';
}
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
@@ -69,7 +74,7 @@ function generate_guid() {
// ----------------------------------------------------------------------------------------
// Logs a notification in in-app notification system
function write_notification($content, $level = "interrupt") {
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
$NOTIFICATION_API_FILE = get_notification_store_path();
// Generate GUID
$guid = generate_guid();
@@ -102,7 +107,7 @@ function write_notification($content, $level = "interrupt") {
// ----------------------------------------------------------------------------------------
// Removes a notification based on GUID
function remove_notification($guid) {
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
$NOTIFICATION_API_FILE = get_notification_store_path();
// Read existing notifications
$notifications = json_decode(file_get_contents($NOTIFICATION_API_FILE), true);
@@ -119,7 +124,7 @@ function remove_notification($guid) {
// ----------------------------------------------------------------------------------------
// Deletes all notifications
function notifications_clear() {
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
$NOTIFICATION_API_FILE = get_notification_store_path();
// Clear notifications by writing an empty array to the file
file_put_contents($NOTIFICATION_API_FILE, json_encode(array()));
@@ -128,7 +133,7 @@ function notifications_clear() {
// ----------------------------------------------------------------------------------------
// Mark a notification read based on GUID
function mark_notification_as_read($guid) {
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
$NOTIFICATION_API_FILE = get_notification_store_path();
$max_attempts = 3;
$attempts = 0;
@@ -177,7 +182,7 @@ function notifications_mark_all_read() {
// ----------------------------------------------------------------------------------------
function get_unread_notifications() {
$NOTIFICATION_API_FILE = '/app/api/user_notifications.json';
$NOTIFICATION_API_FILE = get_notification_store_path();
// Read existing notifications
if (file_exists($NOTIFICATION_API_FILE) && is_readable($NOTIFICATION_API_FILE)) {

View File

@@ -15,7 +15,19 @@ if (isset($_SESSION["login"]) && $_SESSION["login"] == 1) {
// Check if a valid cookie is present
$CookieSaveLoginName = "NetAlertX_SaveLogin";
$config_file = "../../../config/app.conf"; // depends on where this file is called from
// Use environment-aware config path
$configFolderPath = rtrim(getenv('NETALERTX_CONFIG') ?: '/data/config', '/');
$config_file = $configFolderPath . '/app.conf';
// Fallback to legacy path if new location doesn't exist
if (!file_exists($config_file)) {
$legacyPath = "../../../config/app.conf";
if (file_exists($legacyPath)) {
$config_file = $legacyPath;
}
}
$config_file_lines = file($config_file);
$config_file_lines = array_values(preg_grep('/^SETPWD_password.*=/', $config_file_lines));
$password_line = explode("'", $config_file_lines[0]);

View File

@@ -4,8 +4,8 @@
// ## Global constants and TimeZone processing
// ######################################################################
$configFolderPath = "/app/config/";
$logFolderPath = "/app/log/";
$configFolderPath = rtrim(getenv('NETALERTX_CONFIG') ?: '/data/config', '/') . '/';
$logFolderPath = rtrim(getenv('NETALERTX_LOG') ?: '/tmp/log', '/') . '/';
$config_file = "app.conf";
$workflows_file = "workflows.json";

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Uptime:",
"Systeminfo_This_Client": "Aquest Client",
"Systeminfo_USB_Devices": "Dispositius USB",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ S'han detectat punts muntatge antics. Ves a <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">aquesta guia</a> per migrar les noves <code>/app/config</code> i <code>/app/db</code> carpetes i al <code>netalertx</code> contenidor.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ S'han detectat punts muntatge antics. Ves a <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">aquesta guia</a> per migrar les noves <code>/data/config</code> i <code>/data/db</code> carpetes i al <code>netalertx</code> contenidor.",
"TIMEZONE_description": "Fus horari per mostrar les estadístiques correctament. <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">aquí</a>.",
"TIMEZONE_name": "Fus horari",
"UI_DEV_SECTIONS_description": "Seleccioneu quins elements de la interfície d'usuari per ocultar a les pàgines de dispositius.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Uptime:",
"Systeminfo_This_Client": "This Client",
"Systeminfo_USB_Devices": "USB devices",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Old mount locations detected. Follow <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">this guide</a> to migrate to the new <code>/app/config</code> and <code>/app/db</code> folders and the <code>netalertx</code> container.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Old mount locations detected. Follow <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">this guide</a> to migrate to the new <code>/data/config</code> and <code>/data/db</code> folders and the <code>netalertx</code> container.",
"TIMEZONE_description": "Time zone to display stats correctly. Find your time zone <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">here</a>.",
"TIMEZONE_name": "Time zone",
"UI_DEV_SECTIONS_description": "Select which UI elements to hide in the devices pages.",

View File

@@ -734,7 +734,7 @@
"Systeminfo_System_Uptime": "Tiempo de actividad:",
"Systeminfo_This_Client": "Este cliente",
"Systeminfo_USB_Devices": "Dispositivos USB",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Ubicaciones de montaje antiguas detectadas. Siga <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">esta guía</a> para migrar a las nuevas carpetas <code>/app/config</code> y <code>/app/db</code> y el contenedor <code>netalertx</code>.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Ubicaciones de montaje antiguas detectadas. Siga <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">esta guía</a> para migrar a las nuevas carpetas <code>/data/config</code> y <code>/data/db</code> y el contenedor <code>netalertx</code>.",
"TIMEZONE_description": "La zona horaria para mostrar las estadísticas correctamente. Encuentra tu zona horaria <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">aquí</a>.",
"TIMEZONE_name": "Zona horaria",
"UI_DEV_SECTIONS_description": "Seleccione los elementos de la interfaz de usuario que desea ocultar en las páginas de dispositivos.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Durée d'activité:",
"Systeminfo_This_Client": "Ce client",
"Systeminfo_USB_Devices": "Appareils USB",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Emplacement de point de montage obsolète détecté. Suivez <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">ce guide</a> pour migrer vers les nouveaux dossiers <code>/app/config</code> and <code>/app/db</code> et le container <code>netalertx</code>.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Emplacement de point de montage obsolète détecté. Suivez <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">ce guide</a> pour migrer vers les nouveaux dossiers <code>/data/config</code> and <code>/data/db</code> et le container <code>netalertx</code>.",
"TIMEZONE_description": "Fuseau horaire pour afficher correctement les statistiques. Trouvez votre fuseau horaire <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">ici</a>.",
"TIMEZONE_name": "Fuseau horaire",
"UI_DEV_SECTIONS_description": "Slecetionnez quels éléments de l'interface graphique masquer dans les pages des appareils.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Tempo di attività:",
"Systeminfo_This_Client": "Questo client",
"Systeminfo_USB_Devices": "Dispositivi USB",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Rilevate vecchie posizioni di montaggio. Segui <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">questa guida</a> per migrare alle nuove cartelle <code> /app/config</code> e <code>/app/db</code> e al contenitore <code>netalertx</code>.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Rilevate vecchie posizioni di montaggio. Segui <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">questa guida</a> per migrare alle nuove cartelle <code> /data/config</code> e <code>/data/db</code> e al contenitore <code>netalertx</code>.",
"TIMEZONE_description": "Fuso orario per visualizzare correttamente le statistiche. Trova il tuo fuso orario <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">qui</a>.",
"TIMEZONE_name": "Fuso orario",
"UI_DEV_SECTIONS_description": "Seleziona quali elementi della UI nascondere nella pagina dei dispositivi.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Oppetid:",
"Systeminfo_This_Client": "Denne klienten",
"Systeminfo_USB_Devices": "USB-enheter",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Eldre Mount-lokasjoner oppdaget. Følg <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">denne guiden</a> for å migrere til den nye <code>/app/config</code> og <code>/app/db </code> mappene og <code>netalertx</code> containeren.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Eldre Mount-lokasjoner oppdaget. Følg <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">denne guiden</a> for å migrere til den nye <code>/data/config</code> og <code>/data/db </code> mappene og <code>netalertx</code> containeren.",
"TIMEZONE_description": "Tidssone for å vise statistikk riktig. Finn din tidssone <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">her</a>.",
"TIMEZONE_name": "Tidssone",
"UI_DEV_SECTIONS_description": "Velg hvilke UI -elementer du vil skjule på enhetssiden.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Czas pracy:",
"Systeminfo_This_Client": "Ten klient",
"Systeminfo_USB_Devices": "Urządzenia USB",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Wykryto stare lokalizacje montowania. Skorzystaj z <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">tego przewodnika</a>, aby przeprowadzić migrację do nowych folderów <code>/app/config</code> i <code>/app/db</code> oraz kontenera <code>netalertx</code>.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Wykryto stare lokalizacje montowania. Skorzystaj z <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">tego przewodnika</a>, aby przeprowadzić migrację do nowych folderów <code>/data/config</code> i <code>/data/db</code> oraz kontenera <code>netalertx</code>.",
"TIMEZONE_description": "Ustaw strefę czasową, aby statystyki były wyświetlane poprawnie. Znajdź swoją strefę czasową <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">tutaj</a>.",
"TIMEZONE_name": "Strefa czasowa",
"UI_DEV_SECTIONS_description": "Wybierz elementy interfejsu użytkownika (UI), które chcesz ukryć na stronach Urządzeń.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Время работы:",
"Systeminfo_This_Client": "Этот клиент",
"Systeminfo_USB_Devices": "USB-устройства",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Обнаружены устаревшие местоположения. Следуйте этому руководству <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\"></a>, чтобы перейти на новые <code>/app/config</code> и <code>/app/db</code> папки и контейнер <code>netalertx</code>.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Обнаружены устаревшие местоположения. Следуйте этому руководству <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\"></a>, чтобы перейти на новые <code>/data/config</code> и <code>/data/db</code> папки и контейнер <code>netalertx</code>.",
"TIMEZONE_description": "Часовой пояс для корректного отображения статистики. Найдите свой часовой пояс <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">здесь</a>.",
"TIMEZONE_name": "Часовой пояс",
"UI_DEV_SECTIONS_description": "Выберите, какие элементы интерфейса нужно скрыть на страницах «Устройства».",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "Час роботи:",
"Systeminfo_This_Client": "Цей клієнт",
"Systeminfo_USB_Devices": "USB-пристрої",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Виявлено старі місця монтування. Дотримуйтеся <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">цього посібника</a>, щоб перейти на новий <code> папки /app/config</code> і <code>/app/db</code> і контейнер <code>netalertx</code>.",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ Виявлено старі місця монтування. Дотримуйтеся <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">цього посібника</a>, щоб перейти на нові папки <code>/data/config</code> і <code>/data/db</code> та контейнер <code>netalertx</code>.",
"TIMEZONE_description": "Часовий пояс для правильного відображення статистики. Знайдіть свій часовий пояс <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">тут</a>.",
"TIMEZONE_name": "Часовий пояс",
"UI_DEV_SECTIONS_description": "Виберіть, які елементи інтерфейсу користувача приховати на сторінках пристроїв.",

View File

@@ -674,7 +674,7 @@
"Systeminfo_System_Uptime": "正常运行时间:",
"Systeminfo_This_Client": "此客户",
"Systeminfo_USB_Devices": "USB 设备",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ 检测到旧的挂载位置。请按照<a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">本指南</a>迁移到新的 <code>/app/config</code> 和 <code>/app/db</code> 文件夹以及 <code>netalertx</code> 容器。",
"TICKER_MIGRATE_TO_NETALERTX": "⚠ 检测到旧的挂载位置。请按照<a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/MIGRATION.md\" target=\"_blank\">本指南</a>迁移到新的 <code>/data/config</code> 和 <code>/data/db</code> 文件夹以及 <code>netalertx</code> 容器。",
"TIMEZONE_description": "时区可正确显示统计数据。在<a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\" rel=\"nofollow\">此处</a>查找您的时区。",
"TIMEZONE_name": "时区",
"UI_DEV_SECTIONS_description": "选择在设备页面中隐藏哪些 UI 元素。",

View File

@@ -1,7 +1,18 @@
<?php
// Constants
define('CONFIG_PATH', $_SERVER['DOCUMENT_ROOT'] . "/../config/app.conf");
$configFolderPath = rtrim(getenv('NETALERTX_CONFIG') ?: '/data/config', '/');
$legacyConfigPath = $_SERVER['DOCUMENT_ROOT'] . "/../config/app.conf";
// Use environment variable path, fallback to legacy
if (file_exists($configFolderPath . '/app.conf')) {
define('CONFIG_PATH', $configFolderPath . '/app.conf');
} else if (file_exists($legacyConfigPath)) {
define('CONFIG_PATH', $legacyConfigPath);
} else {
define('CONFIG_PATH', $configFolderPath . '/app.conf'); // default to new location
}
define('COOKIE_SAVE_LOGIN_NAME', "NetAlertX_SaveLogin");
// Utility Functions
@@ -48,7 +59,7 @@ if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'logout') {
// Load configuration
if (!file_exists(CONFIG_PATH)) {
die("Configuration file not found in " . $_SERVER['DOCUMENT_ROOT'] . "/../config/app.conf");
die("Configuration file not found in " . CONFIG_PATH);
}
$configLines = file(CONFIG_PATH);

View File

@@ -1,23 +1,18 @@
#!/usr/bin/env python
import os
import pathlib
import sys
import json
import sqlite3
from pytz import timezone
# Define the installation path and extend the system path for plugin imports
INSTALL_PATH = "/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from plugin_utils import get_plugins_configs
from const import logPath
from plugin_helper import Plugin_Objects
from logger import mylog, Logger
from const import pluginsPath, fullDbPath, logPath
from helper import timeNowTZ, get_setting_value
from helper import get_setting_value
from messaging.in_app import write_notification
import conf
# Make sure the TIMEZONE for logging is correct
@@ -54,26 +49,27 @@ def main():
# insert devices into the lats_result.log
# make sure the below mapping is mapped in config.json, for example:
#"database_column_definitions": [
# "database_column_definitions": [
# {
# "column": "Object_PrimaryID", <--------- the value I save into primaryId
# "mapped_to_column": "cur_MAC", <--------- gets inserted into the CurrentScan DB table column cur_MAC
# "column": "Object_PrimaryID", <--------- the value I save into primaryId
# "mapped_to_column": "cur_MAC", <--------- gets inserted into the CurrentScan DB
# table column cur_MAC
#
for device in device_data:
plugin_objects.add_object(
primaryId = device['mac_address'],
secondaryId = device['ip_address'],
watched1 = device['hostname'],
watched2 = device['vendor'],
watched3 = device['device_type'],
watched4 = device['last_seen'],
extra = '',
foreignKey = device['mac_address']
# helpVal1 = "Something1", # Optional Helper values to be passed for mapping into the app
# helpVal2 = "Something1", # If you need to use even only 1, add the remaining ones too
# helpVal3 = "Something1", # and set them to 'null'. Check the the docs for details:
# helpVal4 = "Something1", # https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md
)
plugin_objects.add_object(
primaryId = device['mac_address'],
secondaryId = device['ip_address'],
watched1 = device['hostname'],
watched2 = device['vendor'],
watched3 = device['device_type'],
watched4 = device['last_seen'],
extra = '',
foreignKey = device['mac_address']
# helpVal1 = "Something1", # Optional Helper values to be passed for mapping into the app
# helpVal2 = "Something1", # If you need to use even only 1, add the remaining ones too
# helpVal3 = "Something1", # and set them to 'null'. Check the the docs for details:
# helpVal4 = "Something1", # https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md
)
mylog('verbose', [f'[{pluginName}] New entries: "{len(device_data)}"'])

View File

@@ -1,31 +1,20 @@
#!/usr/bin/env python
# Just a testing library plugin for development purposes
import json
import subprocess
import argparse
import os
import pathlib
import sys
from datetime import datetime
import time
import re
import hashlib
import sqlite3
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
# NetAlertX modules
import conf
from const import apiPath, confFileName, logPath
from plugin_utils import getPluginObject
from const import logPath
from plugin_helper import Plugin_Objects
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value, bytes_to_string, sanitize_string, cleanDeviceName
from models.notification_instance import NotificationInstance
from database import DB, get_device_stats
from logger import mylog
from helper import get_setting_value
pluginName = 'TESTONLY'

View File

@@ -2,45 +2,46 @@
import json
import subprocess
import argparse
import os
import pathlib
import sys
from datetime import datetime
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
import conf
from const import confFileName, logPath
from utils.datetime_utils import timeNowDB
from plugin_helper import Plugin_Objects
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value
from logger import mylog, Logger
from helper import get_setting_value
from models.notification_instance import NotificationInstance
from database import DB
from pytz import timezone
# Make sure the TIMEZONE for logging is correct
conf.tz = timezone(get_setting_value('TIMEZONE'))
conf.tz = timezone(get_setting_value("TIMEZONE"))
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
Logger(get_setting_value("LOG_LEVEL"))
pluginName = 'APPRISE'
LOG_PATH = logPath + '/plugins'
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
pluginName = "APPRISE"
LOG_PATH = logPath + "/plugins"
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
mylog("verbose", [f"[{pluginName}](publisher) In script"])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your {confFileName} {pluginName}_* variables.'])
mylog(
"none",
[
f"[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your {confFileName} {pluginName}_* variables."
],
)
return
# Create a database connection
@@ -58,14 +59,13 @@ def main():
# Process the new notifications (see the Notifications DB table for structure or check the /php/server/query_json.php?file=table_notifications.json endpoint)
for notification in new_notifications:
# Send notification
result = send(notification["HTML"], notification["Text"])
result = send(notification["HTML"], notification["Text"])
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
secondaryId = timeNowDB(),
watched1 = notification["GUID"],
watched2 = result,
watched3 = 'null',
@@ -76,29 +76,33 @@ def main():
plugin_objects.write_result_file()
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
def check_config():
if get_setting_value('APPRISE_HOST') == '' or (get_setting_value('APPRISE_URL') == '' and get_setting_value('APPRISE_TAG') == ''):
return False
else:
return True
if get_setting_value("APPRISE_HOST") == "" or (
get_setting_value("APPRISE_URL") == ""
and get_setting_value("APPRISE_TAG") == ""
):
return False
else:
return True
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
def send(html, text):
payloadData = ''
result = ''
payloadData = ""
result = ""
# limit = 1024 * 1024 # 1MB limit (1024 bytes * 1024 bytes = 1MB)
limit = get_setting_value('APPRISE_SIZE')
limit = get_setting_value("APPRISE_SIZE")
# truncate size
if get_setting_value('APPRISE_PAYLOAD') == 'html':
if get_setting_value("APPRISE_PAYLOAD") == "html":
if len(html) > limit:
payloadData = html[:limit] + "<h1>(text was truncated)</h1>"
else:
payloadData = html
if get_setting_value('APPRISE_PAYLOAD') == 'text':
if get_setting_value("APPRISE_PAYLOAD") == "text":
if len(text) > limit:
payloadData = text[:limit] + " (text was truncated)"
else:
@@ -106,36 +110,55 @@ def send(html, text):
# Define Apprise compatible payload (https://github.com/caronc/apprise-api#stateless-solution)
target_key = "tag" if get_setting_value('APPRISE_TARGETTYPE') == 'tag' else "urls"
target_value = get_setting_value('APPRISE_TAG') if target_key == 'tag' else get_setting_value('APPRISE_URL')
target_key = "tag" if get_setting_value("APPRISE_TARGETTYPE") == "tag" else "urls"
target_value = (
get_setting_value("APPRISE_TAG")
if target_key == "tag"
else get_setting_value("APPRISE_URL")
)
_json_payload = {
target_key: target_value,
"title": "NetAlertX Notifications",
"format": get_setting_value('APPRISE_PAYLOAD'),
"body": payloadData
"format": get_setting_value("APPRISE_PAYLOAD"),
"body": payloadData,
}
try:
# try runnning a subprocess
p = subprocess.Popen(["curl","-i","-X", "POST" ,"-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), get_setting_value('APPRISE_HOST')], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p = subprocess.Popen(
[
"curl",
"-i",
"-X",
"POST",
"-H",
"Content-Type:application/json",
"-d",
json.dumps(_json_payload),
get_setting_value("APPRISE_HOST"),
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
stdout, stderr = p.communicate()
# write stdout and stderr into .log files for debugging if needed
# Log the stdout and stderr
mylog('debug', [stdout, stderr])
mylog("debug", [stdout, stderr])
# log result
result = stdout
except subprocess.CalledProcessError as e:
# An error occurred, handle it
mylog('none', [e.output])
mylog("none", [e.output])
# log result
result = e.output
return result
if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,12 +1,7 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import re
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
@@ -17,15 +12,16 @@ import socket
import ssl
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
# NetAlertX modules
import conf
from const import confFileName, logPath
from plugin_helper import Plugin_Objects
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value, hide_email
from utils.datetime_utils import timeNowDB
from logger import mylog, Logger
from helper import get_setting_value, hide_email
from models.notification_instance import NotificationInstance
from database import DB
from pytz import timezone
@@ -66,15 +62,15 @@ def main():
new_notifications = notifications.getNew()
# mylog('verbose', [f'[{pluginName}] new_notifications: ', new_notifications])
mylog('verbose', [f'[{pluginName}] SMTP_SERVER: ', get_setting_value("SMTP_SERVER")])
mylog('verbose', [f'[{pluginName}] SMTP_PORT: ', get_setting_value("SMTP_PORT")])
mylog('verbose', [f'[{pluginName}] SMTP_SKIP_LOGIN: ', get_setting_value("SMTP_SKIP_LOGIN")])
# mylog('verbose', [f'[{pluginName}] SMTP_USER: ', get_setting_value("SMTP_USER")])
mylog('verbose', [f'[{pluginName}] SMTP_SERVER: ', get_setting_value("SMTP_SERVER")])
mylog('verbose', [f'[{pluginName}] SMTP_PORT: ', get_setting_value("SMTP_PORT")])
mylog('verbose', [f'[{pluginName}] SMTP_SKIP_LOGIN: ', get_setting_value("SMTP_SKIP_LOGIN")])
# mylog('verbose', [f'[{pluginName}] SMTP_USER: ', get_setting_value("SMTP_USER")])
# mylog('verbose', [f'[{pluginName}] SMTP_PASS: ', get_setting_value("SMTP_PASS")])
mylog('verbose', [f'[{pluginName}] SMTP_SKIP_TLS: ', get_setting_value("SMTP_SKIP_TLS")])
mylog('verbose', [f'[{pluginName}] SMTP_FORCE_SSL: ', get_setting_value("SMTP_FORCE_SSL")])
# mylog('verbose', [f'[{pluginName}] SMTP_REPORT_TO: ', get_setting_value("SMTP_REPORT_TO")])
# mylog('verbose', [f'[{pluginName}] SMTP_REPORT_FROM: ', get_setting_value("SMTP_REPORT_FROM")])
mylog('verbose', [f'[{pluginName}] SMTP_SKIP_TLS: ', get_setting_value("SMTP_SKIP_TLS")])
mylog('verbose', [f'[{pluginName}] SMTP_FORCE_SSL: ', get_setting_value("SMTP_FORCE_SSL")])
# mylog('verbose', [f'[{pluginName}] SMTP_REPORT_TO: ', get_setting_value("SMTP_REPORT_TO")])
# mylog('verbose', [f'[{pluginName}] SMTP_REPORT_FROM: ', get_setting_value("SMTP_REPORT_FROM")])
# Process the new notifications (see the Notifications DB table for structure or check the /php/server/query_json.php?file=table_notifications.json endpoint)
@@ -86,7 +82,7 @@ def main():
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
secondaryId = timeNowDB(),
watched1 = notification["GUID"],
watched2 = result,
watched3 = 'null',

View File

@@ -14,17 +14,18 @@ from pytz import timezone
# Register NetAlertX directories
INSTALL_PATH = "/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
# NetAlertX modules
import conf
from const import confFileName, logPath
from plugin_utils import getPluginObject
from utils.plugin_utils import getPluginObject
from plugin_helper import Plugin_Objects
from logger import mylog, Logger
from helper import timeNowTZ, get_setting_value, bytes_to_string, \
from helper import get_setting_value, bytes_to_string, \
sanitize_string, normalize_string
from utils.datetime_utils import timeNowDB
from database import DB, get_device_stats
@@ -364,7 +365,6 @@ def mqtt_create_client():
return
except Exception as err:
mylog('verbose', [f"[{pluginName}] {err} Reconnect failed. Retrying..."])
pass
reconnect_delay *= RECONNECT_RATE
reconnect_delay = min(reconnect_delay, MAX_RECONNECT_DELAY)
@@ -567,7 +567,7 @@ def prepTimeStamp(datetime_str):
except ValueError:
mylog('verbose', [f"[{pluginName}] Timestamp conversion failed of string '{datetime_str}'"])
# Use the current time if the input format is invalid
parsed_datetime = timeNowTZ() # Assuming this function returns the current time with timezone
parsed_datetime = datetime.now(conf.tz)
# Convert to the required format with 'T' between date and time and ensure the timezone is included
return parsed_datetime.isoformat() # This will include the timezone offset

View File

@@ -2,24 +2,21 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
import conf
from const import confFileName, logPath
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value
from utils.datetime_utils import timeNowDB
from logger import mylog, Logger
from helper import get_setting_value
from models.notification_instance import NotificationInstance
from database import DB
from pytz import timezone
@@ -63,12 +60,12 @@ def main():
for notification in new_notifications:
# Send notification
response_text, response_status_code = send(notification["HTML"], notification["Text"])
response_text, response_status_code = send(notification["HTML"], notification["Text"])
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
secondaryId = timeNowDB(),
watched1 = notification["GUID"],
watched2 = handleEmpty(response_text),
watched3 = response_status_code,

View File

@@ -1,33 +1,34 @@
#!/usr/bin/env python3
import conf
from const import confFileName, logPath
from pytz import timezone
import os
import pathlib
import sys
import json
import requests
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Objects, handleEmpty # noqa: E402
from logger import mylog, Logger # noqa: E402
from helper import timeNowTZ, get_setting_value, hide_string # noqa: E402
from helper import get_setting_value, hide_string # noqa: E402
from utils.datetime_utils import timeNowDB
from models.notification_instance import NotificationInstance # noqa: E402
from database import DB # noqa: E402
import conf
from const import confFileName, logPath
from pytz import timezone
# Make sure the TIMEZONE for logging is correct
conf.tz = timezone(get_setting_value('TIMEZONE'))
conf.tz = timezone(get_setting_value("TIMEZONE"))
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
Logger(get_setting_value("LOG_LEVEL"))
pluginName = "PUSHOVER"
LOG_PATH = logPath + '/plugins'
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
LOG_PATH = logPath + "/plugins"
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
def main():
@@ -63,7 +64,7 @@ def main():
# Log result
plugin_objects.add_object(
primaryId=pluginName,
secondaryId=timeNowTZ(),
secondaryId=timeNowDB(),
watched1=notification["GUID"],
watched2=handleEmpty(response_text),
watched3=response_status_code,

View File

@@ -2,24 +2,20 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
import conf
from const import confFileName, logPath
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value, hide_string
from logger import mylog, Logger
from helper import get_setting_value, hide_string
from utils.datetime_utils import timeNowDB
from models.notification_instance import NotificationInstance
from database import DB
from pytz import timezone
@@ -68,7 +64,7 @@ def main():
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
secondaryId = timeNowDB(),
watched1 = notification["GUID"],
watched2 = handleEmpty(response_text),
watched3 = response_status_code,

View File

@@ -1,22 +1,19 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
from datetime import datetime
# Register NetAlertX directories
INSTALL_PATH = "/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
import conf
from const import confFileName, logPath
from plugin_helper import Plugin_Objects
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value
from utils.datetime_utils import timeNowDB
from logger import mylog, Logger
from helper import get_setting_value
from models.notification_instance import NotificationInstance
from database import DB
from pytz import timezone
@@ -65,7 +62,7 @@ def main():
# Log result
plugin_objects.add_object(
primaryId=pluginName,
secondaryId=timeNowTZ(),
secondaryId=timeNowDB(),
watched1=notification["GUID"],
watched2=result,
watched3='null',

View File

@@ -3,26 +3,22 @@
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
import hashlib
import hmac
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
import conf
from const import logPath, confFileName
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value, hide_string, write_file
from utils.datetime_utils import timeNowDB
from logger import mylog, Logger
from helper import get_setting_value, write_file
from models.notification_instance import NotificationInstance
from database import DB
from pytz import timezone
@@ -71,7 +67,7 @@ def main():
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
secondaryId = timeNowDB(),
watched1 = notification["GUID"],
watched2 = handleEmpty(response_stdout),
watched3 = handleEmpty(response_stderr),

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python
import os
import time
import pathlib
@@ -8,93 +7,89 @@ import sys
import re
import base64
import subprocess
from time import strftime
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from database import DB
from plugin_helper import Plugin_Object, Plugin_Objects, handleEmpty
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value
from helper import get_setting_value
from const import logPath, applicationPath
import conf
from pytz import timezone
# Make sure the TIMEZONE for logging is correct
conf.tz = timezone(get_setting_value('TIMEZONE'))
conf.tz = timezone(get_setting_value("TIMEZONE"))
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
Logger(get_setting_value("LOG_LEVEL"))
pluginName = 'ARPSCAN'
LOG_PATH = logPath + '/plugins'
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
pluginName = "ARPSCAN"
LOG_PATH = logPath + "/plugins"
LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log")
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
def main():
parser = argparse.ArgumentParser(description='Import devices from settings')
parser.add_argument('userSubnets', nargs='+', help="list of subnets with options")
values = parser.parse_args()
parser = argparse.ArgumentParser(description="Import devices from settings")
parser.add_argument("userSubnets", nargs="+", help="list of subnets with options")
values = parser.parse_args()
# Assuming Plugin_Objects is a class or function that reads data from the RESULT_FILE
# and returns a list of objects called 'devices'.
plugin_objects = Plugin_Objects(RESULT_FILE)
# Print a message to indicate that the script is starting.
mylog('verbose', [f'[{pluginName}] In script '])
mylog("verbose", [f"[{pluginName}] In script "])
# holds a list of user-submitted subnets.
# mylog('verbose', ['[ARP Scan] values.userSubnets: ', values.userSubnets])
# holds a list of user-submitted subnets.
# mylog('verbose', ['[ARP Scan] values.userSubnets: ', values.userSubnets])
# Extract the base64-encoded subnet information from the first element of the userSubnets list.
# The format of the element is assumed to be like 'userSubnets=b<base64-encoded-data>'.
userSubnetsParamBase64 = values.userSubnets[0].split('userSubnets=b')[1]
# The format of the element is assumed to be like 'userSubnets=<base64-encoded-data>'.
userSubnetsParamBase64 = values.userSubnets[0].split("userSubnets=")[1]
# Printing the extracted base64-encoded subnet information.
# mylog('verbose', ['[ARP Scan] userSubnetsParamBase64: ', userSubnetsParamBase64])
# mylog('verbose', ['[ARP Scan] userSubnetsParamBase64: ', userSubnetsParamBase64])
# Decode the base64-encoded subnet information to get the actual subnet information in ASCII format.
userSubnetsParam = base64.b64decode(userSubnetsParamBase64).decode('ascii')
userSubnetsParam = base64.b64decode(userSubnetsParamBase64).decode("ascii")
# Print the decoded subnet information.
mylog('verbose', [f'[{pluginName}] userSubnetsParam: ', userSubnetsParam])
mylog("verbose", [f"[{pluginName}] userSubnetsParam: ", userSubnetsParam])
# Check if the decoded subnet information contains multiple subnets separated by commas.
# If it does, split the string into a list of individual subnets.
# Otherwise, create a list with a single element containing the subnet information.
if ',' in userSubnetsParam:
subnets_list = userSubnetsParam.split(',')
if "," in userSubnetsParam:
subnets_list = userSubnetsParam.split(",")
else:
subnets_list = [userSubnetsParam]
# Create a database connection
db = DB() # instance of class DB
db.open()
# Execute the ARP scanning process on the list of subnets (whether it's one or multiple subnets).
# The function 'execute_arpscan' is assumed to be defined elsewhere in the code.
unique_devices = execute_arpscan(subnets_list)
for device in unique_devices:
plugin_objects.add_object(
primaryId = handleEmpty(device['mac']), # MAC (Device Name)
secondaryId = handleEmpty(device['ip']), # IP Address
watched1 = handleEmpty(device['ip']), # Device Name
watched2 = handleEmpty(device.get('hw', '')), # Vendor (assuming it's in the 'hw' field)
watched3 = handleEmpty(device.get('interface', '')), # Add the interface
watched4 = '',
extra = pluginName,
foreignKey = "")
primaryId=handleEmpty(device["mac"]), # MAC (Device Name)
secondaryId=handleEmpty(device["ip"]), # IP Address
watched1=handleEmpty(device["ip"]), # Device Name
watched2=handleEmpty(
device.get("hw", "")
), # Vendor (assuming it's in the 'hw' field)
watched3=handleEmpty(device.get("interface", "")), # Add the interface
watched4="",
extra=pluginName,
foreignKey="",
)
plugin_objects.write_result_file()
@@ -107,17 +102,19 @@ def execute_arpscan(userSubnets):
devices_list = []
# scan each interface
for interface in userSubnets :
arpscan_output = execute_arpscan_on_interface (interface)
for interface in userSubnets:
arpscan_output = execute_arpscan_on_interface(interface)
mylog("verbose", [f"[{pluginName}] arpscan_output: ", arpscan_output])
mylog('verbose', [f'[{pluginName}] arpscan_output: ', arpscan_output])
# Search IP + MAC + Vendor as regular expresion
re_ip = r'(?P<ip>((2[0-5]|1[0-9]|[0-9])?[0-9]\.){3}((2[0-5]|1[0-9]|[0-9])?[0-9]))'
re_mac = r'(?P<mac>([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2}))'
re_hw = r'(?P<hw>.*)'
re_ip = (
r"(?P<ip>((2[0-5]|1[0-9]|[0-9])?[0-9]\.){3}((2[0-5]|1[0-9]|[0-9])?[0-9]))"
)
re_mac = r"(?P<mac>([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2}))"
re_hw = r"(?P<hw>.*)"
re_pattern = re.compile(rf"{re_ip}\s+{re_mac}\s{re_hw}")
devices_list_tmp = [
@@ -127,44 +124,61 @@ def execute_arpscan(userSubnets):
devices_list += devices_list_tmp
# mylog('debug', ['[ARP Scan] Found: Devices including duplicates ', len(devices_list) ])
# Delete duplicate MAC
unique_mac = []
unique_devices = []
# mylog('debug', ['[ARP Scan] Found: Devices including duplicates ', len(devices_list) ])
for device in devices_list :
if device['mac'] not in unique_mac:
unique_mac.append(device['mac'])
unique_devices.append(device)
# Delete duplicate MAC
unique_mac = []
unique_devices = []
for device in devices_list:
if device["mac"] not in unique_mac:
unique_mac.append(device["mac"])
unique_devices.append(device)
# return list
mylog('verbose', [f'[{pluginName}] All devices List len:', len(devices_list)])
mylog('verbose', [f'[{pluginName}] Devices List:', devices_list])
mylog("verbose", [f"[{pluginName}] All devices List len:", len(devices_list)])
mylog("verbose", [f"[{pluginName}] Devices List:", devices_list])
mylog('verbose', [f'[{pluginName}] Found: Devices without duplicates ', len(unique_devices) ])
mylog(
"verbose",
[f"[{pluginName}] Found: Devices without duplicates ", len(unique_devices)],
)
return unique_devices
def execute_arpscan_on_interface(interface):
# Prepare command arguments
arpscan_args = get_setting_value('ARPSCAN_ARGS').split() + interface.split()
arpscan_args = get_setting_value("ARPSCAN_ARGS").split() + interface.split()
# Optional duration in seconds (0 = run once)
try:
scan_duration = int(get_setting_value('ARPSCAN_DURATION'))
scan_duration = int(get_setting_value("ARPSCAN_DURATION"))
except Exception:
scan_duration = 0 # default: single run
# Get timeout from plugin settings (default 30 seconds if not set)
try:
timeout_seconds = int(get_setting_value("ARPSCAN_RUN_TIMEOUT"))
except Exception:
timeout_seconds = 30
results = []
start_time = time.time()
while True:
try:
result = subprocess.check_output(arpscan_args, universal_newlines=True)
result = subprocess.check_output(
arpscan_args, universal_newlines=True, timeout=timeout_seconds
)
results.append(result)
except subprocess.CalledProcessError as e:
except subprocess.CalledProcessError:
result = ""
except subprocess.TimeoutExpired:
mylog(
"warning",
[f"[{pluginName}] arp-scan timed out after {timeout_seconds}s"],
)
result = ""
# stop looping if duration not set or expired
if scan_duration == 0 or (time.time() - start_time) > scan_duration:
@@ -175,10 +189,10 @@ def execute_arpscan_on_interface(interface):
return "\n".join(results)
# ===============================================================================
# BEGIN
# ===============================================================================
#===============================================================================
# BEGIN
#===============================================================================
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -2,7 +2,7 @@
import os
import sys
INSTALL_PATH = "/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
pluginName = "ASUSWRT"
@@ -15,8 +15,7 @@ from asusrouter.modules.connection import ConnectionState
from const import logPath
from helper import get_setting_value
from logger import Logger, mylog
from plugin_helper import (Plugin_Object, Plugin_Objects, decodeBase64,
handleEmpty)
from plugin_helper import (Plugin_Objects, handleEmpty)
from pytz import timezone
conf.tz = timezone(get_setting_value("TIMEZONE"))

View File

@@ -1,13 +1,11 @@
#!/usr/bin/env python3
import os
import sys
import json
import socket
import ipaddress
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo, InterfaceChoice, IPVersion
from zeroconf.asyncio import AsyncZeroconf
from zeroconf import Zeroconf
INSTALL_PATH = "/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Objects

View File

@@ -344,7 +344,7 @@
}
]
},
"default_value": "/app/config",
"default_value": "/data/config",
"options": [],
"localized": [
"name",
@@ -367,15 +367,15 @@
"description": [
{
"language_code": "en_us",
"string": "Where the <code>devices.csv</code> file should be saved. For example <code>/app/config</code>."
"string": "Where the <code>devices.csv</code> file should be saved. For example <code>/data/config</code>."
},
{
"language_code": "es_es",
"string": "Donde se debe guardar el archivo <code>devices.csv</code>. Por ejemplo <code>/app/config</code>."
"string": "Donde se debe guardar el archivo <code>devices.csv</code>. Por ejemplo <code>/data/config</code>."
},
{
"language_code": "de_de",
"string": "Wo die Datei <code>devices.csv</code> gespeichert werden soll. Zum Beispiel <code>/app/config</code>."
"string": "Wo die Datei <code>devices.csv</code> gespeichert werden soll. Zum Beispiel <code>/data/config</code>."
}
]
}

View File

@@ -1,23 +1,19 @@
#!/usr/bin/env python
import os
import pathlib
import argparse
import sys
import hashlib
import csv
import sqlite3
from io import StringIO
from datetime import datetime
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value
from const import logPath, applicationPath, fullDbPath
from logger import mylog, Logger
from helper import get_setting_value
from const import logPath, fullDbPath
import conf
from pytz import timezone

View File

@@ -1,88 +1,110 @@
#!/usr/bin/env python
import os
import pathlib
import argparse
import sys
import hashlib
import csv
import sqlite3
from io import StringIO
from datetime import datetime
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value
from const import logPath, applicationPath, fullDbPath
from logger import mylog, Logger
from helper import get_setting_value
from const import logPath, fullDbPath
import conf
from pytz import timezone
# Make sure the TIMEZONE for logging is correct
conf.tz = timezone(get_setting_value('TIMEZONE'))
conf.tz = timezone(get_setting_value("TIMEZONE"))
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
Logger(get_setting_value("LOG_LEVEL"))
pluginName = 'DBCLNP'
LOG_PATH = logPath + '/plugins'
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
pluginName = "DBCLNP"
LOG_PATH = logPath + "/plugins"
LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log")
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
def main():
PLUGINS_KEEP_HIST = int(get_setting_value("PLUGINS_KEEP_HIST"))
HRS_TO_KEEP_NEWDEV = int(get_setting_value("HRS_TO_KEEP_NEWDEV"))
HRS_TO_KEEP_OFFDEV = int(get_setting_value("HRS_TO_KEEP_OFFDEV"))
DAYS_TO_KEEP_EVENTS = int(get_setting_value("DAYS_TO_KEEP_EVENTS"))
CLEAR_NEW_FLAG = get_setting_value("CLEAR_NEW_FLAG")
mylog('verbose', [f'[{pluginName}] In script'])
PLUGINS_KEEP_HIST = int(get_setting_value("PLUGINS_KEEP_HIST"))
HRS_TO_KEEP_NEWDEV = int(get_setting_value("HRS_TO_KEEP_NEWDEV"))
HRS_TO_KEEP_OFFDEV = int(get_setting_value("HRS_TO_KEEP_OFFDEV"))
DAYS_TO_KEEP_EVENTS = int(get_setting_value("DAYS_TO_KEEP_EVENTS"))
CLEAR_NEW_FLAG = get_setting_value("CLEAR_NEW_FLAG")
mylog("verbose", [f"[{pluginName}] In script"])
# Execute cleanup/upkeep
cleanup_database(
fullDbPath,
DAYS_TO_KEEP_EVENTS,
HRS_TO_KEEP_NEWDEV,
HRS_TO_KEEP_OFFDEV,
PLUGINS_KEEP_HIST,
CLEAR_NEW_FLAG,
)
mylog("verbose", [f"[{pluginName}] Cleanup complete"])
# Execute cleanup/upkeep
cleanup_database(fullDbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KEEP_OFFDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG)
mylog('verbose', [f'[{pluginName}] Cleanup complete'])
return 0
#===============================================================================
# ===============================================================================
# Cleanup / upkeep database
#===============================================================================
def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KEEP_OFFDEV, PLUGINS_KEEP_HIST, CLEAR_NEW_FLAG):
# ===============================================================================
def cleanup_database(
dbPath,
DAYS_TO_KEEP_EVENTS,
HRS_TO_KEEP_NEWDEV,
HRS_TO_KEEP_OFFDEV,
PLUGINS_KEEP_HIST,
CLEAR_NEW_FLAG,
):
"""
Cleaning out old records from the tables that don't need to keep all data.
"""
mylog('verbose', [f'[{pluginName}] Upkeep Database:' ])
mylog("verbose", [f"[{pluginName}] Upkeep Database:"])
# Connect to the App database
conn = sqlite3.connect(dbPath, timeout=30)
cursor = conn.cursor()
conn = sqlite3.connect(dbPath, timeout=30)
cursor = conn.cursor()
# -----------------------------------------------------
# Cleanup Online History
mylog('verbose', [f'[{pluginName}] Online_History: Delete all but keep latest 150 entries'])
cursor.execute ("""DELETE from Online_History where "Index" not in (
mylog(
"verbose",
[f"[{pluginName}] Online_History: Delete all but keep latest 150 entries"],
)
cursor.execute(
"""DELETE from Online_History where "Index" not in (
SELECT "Index" from Online_History
order by Scan_Date desc limit 150)""")
order by Scan_Date desc limit 150)"""
)
# -----------------------------------------------------
# Cleanup Events
mylog('verbose', [f'[{pluginName}] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)'])
cursor.execute (f"""DELETE FROM Events
WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""")
# -----------------------------------------------------
mylog(
"verbose",
[
f"[{pluginName}] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)"
],
)
cursor.execute(
f"""DELETE FROM Events
WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')"""
)
# -----------------------------------------------------
# Trim Plugins_History entries to less than PLUGINS_KEEP_HIST setting per unique "Plugin" column entry
mylog('verbose', [f'[{pluginName}] Plugins_History: Trim Plugins_History entries to less than {str(PLUGINS_KEEP_HIST)} per Plugin (PLUGINS_KEEP_HIST setting)'])
mylog(
"verbose",
[
f"[{pluginName}] Plugins_History: Trim Plugins_History entries to less than {str(PLUGINS_KEEP_HIST)} per Plugin (PLUGINS_KEEP_HIST setting)"
],
)
# Build the SQL query to delete entries that exceed the limit per unique "Plugin" column entry
delete_query = f"""DELETE FROM Plugins_History
@@ -101,11 +123,16 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KE
# -----------------------------------------------------
# Trim Notifications entries to less than DBCLNP_NOTIFI_HIST setting
histCount = get_setting_value('DBCLNP_NOTIFI_HIST')
histCount = get_setting_value("DBCLNP_NOTIFI_HIST")
mylog('verbose', [f'[{pluginName}] Plugins_History: Trim Notifications entries to less than {histCount}'])
mylog(
"verbose",
[
f"[{pluginName}] Plugins_History: Trim Notifications entries to less than {histCount}"
],
)
# Build the SQL query to delete entries
# Build the SQL query to delete entries
delete_query = f"""DELETE FROM Notifications
WHERE "Index" NOT IN (
SELECT "Index"
@@ -119,14 +146,13 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KE
cursor.execute(delete_query)
# -----------------------------------------------------
# Trim Workflow entries to less than WORKFLOWS_AppEvents_hist setting
histCount = get_setting_value('WORKFLOWS_AppEvents_hist')
histCount = get_setting_value("WORKFLOWS_AppEvents_hist")
mylog('verbose', [f'[{pluginName}] Trim AppEvents to less than {histCount}'])
mylog("verbose", [f"[{pluginName}] Trim AppEvents to less than {histCount}"])
# Build the SQL query to delete entries
# Build the SQL query to delete entries
delete_query = f"""DELETE FROM AppEvents
WHERE "Index" NOT IN (
SELECT "Index"
@@ -141,38 +167,52 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KE
cursor.execute(delete_query)
conn.commit()
# -----------------------------------------------------
# Cleanup New Devices
if HRS_TO_KEEP_NEWDEV != 0:
mylog('verbose', [f'[{pluginName}] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_NEWDEV)} hours (HRS_TO_KEEP_NEWDEV setting)'])
mylog(
"verbose",
[
f"[{pluginName}] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_NEWDEV)} hours (HRS_TO_KEEP_NEWDEV setting)"
],
)
query = f"""DELETE FROM Devices WHERE devIsNew = 1 AND devFirstConnection < date('now', '-{str(HRS_TO_KEEP_NEWDEV)} hour')"""
mylog('verbose', [f'[{pluginName}] Query: {query} '])
cursor.execute (query)
mylog("verbose", [f"[{pluginName}] Query: {query} "])
cursor.execute(query)
# -----------------------------------------------------
# Cleanup Offline Devices
if HRS_TO_KEEP_OFFDEV != 0:
mylog('verbose', [f'[{pluginName}] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_OFFDEV)} hours (HRS_TO_KEEP_OFFDEV setting)'])
mylog(
"verbose",
[
f"[{pluginName}] Devices: Delete all New Devices older than {str(HRS_TO_KEEP_OFFDEV)} hours (HRS_TO_KEEP_OFFDEV setting)"
],
)
query = f"""DELETE FROM Devices WHERE devPresentLastScan = 0 AND devLastConnection < date('now', '-{str(HRS_TO_KEEP_OFFDEV)} hour')"""
mylog('verbose', [f'[{pluginName}] Query: {query} '])
cursor.execute (query)
mylog("verbose", [f"[{pluginName}] Query: {query} "])
cursor.execute(query)
# -----------------------------------------------------
# Clear New Flag
if CLEAR_NEW_FLAG != 0:
mylog('verbose', [f'[{pluginName}] Devices: Clear "New Device" flag for all devices older than {str(CLEAR_NEW_FLAG)} hours (CLEAR_NEW_FLAG setting)'])
mylog(
"verbose",
[
f'[{pluginName}] Devices: Clear "New Device" flag for all devices older than {str(CLEAR_NEW_FLAG)} hours (CLEAR_NEW_FLAG setting)'
],
)
query = f"""UPDATE Devices SET devIsNew = 0 WHERE devIsNew = 1 AND date(devFirstConnection, '+{str(CLEAR_NEW_FLAG)} hour') < date('now')"""
# select * from Devices where devIsNew = 1 AND date(devFirstConnection, '+3 hour' ) < date('now')
mylog('verbose', [f'[{pluginName}] Query: {query} '])
# select * from Devices where devIsNew = 1 AND date(devFirstConnection, '+3 hour' ) < date('now')
mylog("verbose", [f"[{pluginName}] Query: {query} "])
cursor.execute(query)
# -----------------------------------------------------
# De-dupe (de-duplicate) from the Plugins_Objects table
# TODO This shouldn't be necessary - probably a concurrency bug somewhere in the code :(
mylog('verbose', [f'[{pluginName}] Plugins_Objects: Delete all duplicates'])
cursor.execute("""
# De-dupe (de-duplicate) from the Plugins_Objects table
# TODO This shouldn't be necessary - probably a concurrency bug somewhere in the code :(
mylog("verbose", [f"[{pluginName}] Plugins_Objects: Delete all duplicates"])
cursor.execute(
"""
DELETE FROM Plugins_Objects
WHERE rowid > (
SELECT MIN(rowid) FROM Plugins_Objects p2
@@ -181,8 +221,8 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KE
AND Plugins_Objects.Object_SecondaryID = p2.Object_SecondaryID
AND Plugins_Objects.UserData = p2.UserData
)
""")
"""
)
conn.commit()
@@ -190,18 +230,18 @@ def cleanup_database (dbPath, DAYS_TO_KEEP_EVENTS, HRS_TO_KEEP_NEWDEV, HRS_TO_KE
cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
cursor.execute("PRAGMA wal_checkpoint(FULL);")
mylog('verbose', [f'[{pluginName}] WAL checkpoint executed to truncate file.'])
mylog("verbose", [f"[{pluginName}] WAL checkpoint executed to truncate file."])
# Shrink DB
mylog('verbose', [f'[{pluginName}] Shrink Database'])
cursor.execute ("VACUUM;")
mylog("verbose", [f"[{pluginName}] Shrink Database"])
cursor.execute("VACUUM;")
# Close the database connection
conn.close()
conn.close()
#===============================================================================
# ===============================================================================
# BEGIN
#===============================================================================
if __name__ == '__main__':
main()
# ===============================================================================
if __name__ == "__main__":
main()

View File

@@ -1,26 +1,17 @@
#!/usr/bin/env python
import os
import pathlib
import argparse
import sys
import hashlib
import csv
import subprocess
import re
import base64
import sqlite3
from io import StringIO
from datetime import datetime
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, Logger, append_line_to_file
from helper import timeNowTZ, get_setting_value, check_IP_format
from const import logPath, applicationPath, fullDbPath
from logger import mylog, Logger
from helper import get_setting_value, check_IP_format
from const import logPath
import conf
from pytz import timezone

View File

@@ -1,21 +1,19 @@
#!/usr/bin/env python
from __future__ import unicode_literals
import pathlib
import subprocess
import argparse
import os
import sys
import chardet
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, handleEmpty, is_mac
from plugin_helper import Plugin_Objects, handleEmpty, is_mac
from logger import mylog, Logger
from dhcp_leases import DhcpLeases
from helper import timeNowTZ, get_setting_value
from helper import get_setting_value
import conf
from const import logPath
from pytz import timezone

View File

@@ -8,12 +8,12 @@ from datetime import datetime
import sys
# Register NetAlertX directories
INSTALL_PATH="/app"
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Objects, Plugin_Object
from logger import mylog, Logger
from helper import timeNowTZ, get_setting_value
from helper import get_setting_value
import conf
from pytz import timezone
from const import logPath
@@ -44,8 +44,11 @@ def main():
nmapArgs = ['sudo', 'nmap', '--privileged', '--script', 'broadcast-dhcp-discover']
try:
# Number of DHCP discovery probes to send
dhcp_probes = 1
newLines = [datetime.now().strftime("%Y-%m-%d %H:%M:%S")]
# Initialize a list to store output lines from the scan
newLines = []
for _ in range(dhcp_probes):
output = subprocess.check_output(nmapArgs, universal_newlines=True, stderr=subprocess.STDOUT, timeout=timeoutSec)

Some files were not shown because too many files have changed in this diff Show More