mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
Move all check- scripts to /entrypoint.d/ for better organization
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 0-storage-permission.sh: Fix permissions if running as root.
|
||||
#
|
||||
# This script checks if running as root and fixes ownership and permissions
|
||||
# for read-write paths to ensure proper operation.
|
||||
|
||||
# --- Color Codes ---
|
||||
MAGENTA='\033[1;35m'
|
||||
RESET='\033[0m'
|
||||
|
||||
# --- Main Logic ---
|
||||
|
||||
# Define paths that need read-write access
|
||||
READ_WRITE_PATHS="
|
||||
${NETALERTX_API}
|
||||
${NETALERTX_LOG}
|
||||
${SYSTEM_SERVICES_RUN}
|
||||
${NETALERTX_CONFIG}
|
||||
${NETALERTX_CONFIG_FILE}
|
||||
${NETALERTX_DB}
|
||||
${NETALERTX_DB_FILE}
|
||||
"
|
||||
|
||||
# If running as root, fix permissions first
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
>&2 printf "%s" "${MAGENTA}"
|
||||
>&2 cat <<'EOF'
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
🚨 CRITICAL SECURITY ALERT: NetAlertX is running as ROOT (UID 0)! 🚨
|
||||
|
||||
This configuration bypasses all built-in security hardening measures.
|
||||
You've granted a network monitoring application unrestricted access to
|
||||
your host system. A successful compromise here could jeopardize your
|
||||
entire infrastructure.
|
||||
|
||||
IMMEDIATE ACTION REQUIRED: Switch to the dedicated 'netalertx' user:
|
||||
* Remove any 'user:' directive specifying UID 0 from docker-compose.yml or
|
||||
* switch to the default USER in the image (20211:20211)
|
||||
|
||||
IMPORTANT: This corrective mode automatically adjusts ownership of
|
||||
/app/db and /app/config directories to the netalertx user, ensuring
|
||||
proper operation in subsequent runs.
|
||||
|
||||
Remember: Never operate security-critical tools as root unless you're
|
||||
actively trying to get pwned.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
|
||||
# Set ownership to netalertx user for all read-write paths
|
||||
chown -R netalertx ${READ_WRITE_PATHS}
|
||||
|
||||
# Set directory and file permissions for all read-write paths
|
||||
find ${READ_WRITE_PATHS} -type d -exec chmod u+rwx {} + 2>/dev/null
|
||||
find ${READ_WRITE_PATHS} -type f -exec chmod u+rw {} + 2>/dev/null
|
||||
echo Permissions fixed for read-write paths. Please restart the container as user 20211.
|
||||
sleep infinity & wait $!; exit 211
|
||||
fi
|
||||
|
||||
|
||||
|
||||
242
install/production-filesystem/entrypoint.d/10-mounts.py
Executable file
242
install/production-filesystem/entrypoint.d/10-mounts.py
Executable file
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class MountCheckResult:
|
||||
"""Object to track mount status and potential issues."""
|
||||
var_name: str
|
||||
path: str = ""
|
||||
is_writeable: bool = False
|
||||
is_mounted: bool = False
|
||||
is_ramdisk: bool = False
|
||||
underlying_fs_is_ramdisk: bool = False # Track this separately
|
||||
fstype: str = "N/A"
|
||||
error: bool = False
|
||||
write_error: bool = False
|
||||
performance_issue: bool = False
|
||||
dataloss_risk: bool = False
|
||||
|
||||
def get_mount_info():
|
||||
"""Parses /proc/mounts to get a dict of {mount_point: fstype}."""
|
||||
mounts = {}
|
||||
try:
|
||||
with open('/proc/mounts', 'r') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split()
|
||||
if len(parts) >= 3:
|
||||
mount_point = parts[1].replace('\\040', ' ')
|
||||
fstype = parts[2]
|
||||
mounts[mount_point] = fstype
|
||||
except FileNotFoundError:
|
||||
print("Error: /proc/mounts not found. Not a Linux system?", file=sys.stderr)
|
||||
return None
|
||||
return mounts
|
||||
|
||||
def analyze_path(var_name, is_persistent, mounted_filesystems, non_persistent_fstypes, read_only_vars):
|
||||
"""
|
||||
Analyzes a single path, checking for errors, performance, and dataloss.
|
||||
"""
|
||||
result = MountCheckResult(var_name=var_name)
|
||||
target_path = os.environ.get(var_name)
|
||||
|
||||
if target_path is None:
|
||||
result.path = f"({var_name} unset)"
|
||||
result.error = True
|
||||
return result
|
||||
|
||||
result.path = target_path
|
||||
|
||||
# --- 1. Check Write Permissions ---
|
||||
is_writeable = os.access(target_path, os.W_OK)
|
||||
|
||||
if not is_writeable and not os.path.exists(target_path):
|
||||
parent_dir = os.path.dirname(target_path)
|
||||
if os.access(parent_dir, os.W_OK):
|
||||
is_writeable = True
|
||||
|
||||
result.is_writeable = is_writeable
|
||||
|
||||
if var_name not in read_only_vars and not result.is_writeable:
|
||||
result.error = True
|
||||
result.write_error = True
|
||||
|
||||
# --- 2. Check Filesystem Type (Parent and Self) ---
|
||||
parent_mount_fstype = ""
|
||||
longest_mount = ""
|
||||
|
||||
for mount_point, fstype in mounted_filesystems.items():
|
||||
if target_path.startswith(mount_point):
|
||||
if len(mount_point) > len(longest_mount):
|
||||
longest_mount = mount_point
|
||||
parent_mount_fstype = fstype
|
||||
|
||||
result.underlying_fs_is_ramdisk = parent_mount_fstype in non_persistent_fstypes
|
||||
|
||||
if parent_mount_fstype:
|
||||
result.fstype = parent_mount_fstype
|
||||
|
||||
# --- 3. Check if path IS a mount point ---
|
||||
if target_path in mounted_filesystems:
|
||||
result.is_mounted = True
|
||||
result.fstype = mounted_filesystems[target_path]
|
||||
result.is_ramdisk = result.fstype in non_persistent_fstypes
|
||||
else:
|
||||
result.is_mounted = False
|
||||
result.is_ramdisk = False
|
||||
|
||||
# --- 4. Apply Risk Logic ---
|
||||
if is_persistent:
|
||||
if result.underlying_fs_is_ramdisk:
|
||||
result.dataloss_risk = True
|
||||
|
||||
if not result.is_mounted:
|
||||
result.dataloss_risk = True
|
||||
|
||||
else:
|
||||
# Performance issue if it's not a ramdisk mount
|
||||
if not result.is_mounted or not result.is_ramdisk:
|
||||
result.performance_issue = True
|
||||
|
||||
return result
|
||||
|
||||
def print_warning_message():
|
||||
"""Prints a formatted warning to stderr."""
|
||||
YELLOW = '\033[1;33m'
|
||||
RESET = '\033[0m'
|
||||
|
||||
message = (
|
||||
"══════════════════════════════════════════════════════════════════════════════\n"
|
||||
"⚠️ ATTENTION: Configuration issues detected (marked with ❌).\n\n"
|
||||
" Your configuration has write permission, dataloss, or performance issues\n"
|
||||
" as shown in the table above.\n\n"
|
||||
" We recommend starting with the default docker-compose.yml as the\n"
|
||||
" configuration can be quite complex.\n\n"
|
||||
" Review the documentation for a correct setup:\n"
|
||||
" https://github.com/jokob-sk/NetAlertX/blob/main/docs/DOCKER_COMPOSE.md\n"
|
||||
"══════════════════════════════════════════════════════════════════════════════\n"
|
||||
)
|
||||
|
||||
print(f"{YELLOW}{message}{RESET}", file=sys.stderr)
|
||||
|
||||
def main():
|
||||
NON_PERSISTENT_FSTYPES = {'tmpfs', 'ramfs'}
|
||||
PERSISTENT_VARS = {'NETALERTX_DB', 'NETALERTX_CONFIG'}
|
||||
# Define all possible read-only vars
|
||||
READ_ONLY_VARS = {'SYSTEM_NGINX_CONFIG', 'SYSTEM_SERVICES_ACTIVE_CONFIG'}
|
||||
|
||||
# Base paths to check
|
||||
PATHS_TO_CHECK = {
|
||||
'NETALERTX_DB': True,
|
||||
'NETALERTX_CONFIG': True,
|
||||
'NETALERTX_API': False,
|
||||
'NETALERTX_LOG': False,
|
||||
'SYSTEM_SERVICES_RUN': False,
|
||||
}
|
||||
|
||||
# *** KEY CHANGE: Conditionally add path based on PORT ***
|
||||
port_val = os.environ.get("PORT")
|
||||
if port_val is not None and port_val != "20211":
|
||||
PATHS_TO_CHECK['SYSTEM_SERVICES_ACTIVE_CONFIG'] = False
|
||||
# *** END KEY CHANGE ***
|
||||
|
||||
mounted_filesystems = get_mount_info()
|
||||
if mounted_filesystems is None:
|
||||
sys.exit(1)
|
||||
|
||||
results = []
|
||||
has_issues = False
|
||||
for var_name, is_persistent in PATHS_TO_CHECK.items():
|
||||
result = analyze_path(
|
||||
var_name, is_persistent,
|
||||
mounted_filesystems, NON_PERSISTENT_FSTYPES, READ_ONLY_VARS
|
||||
)
|
||||
if result.performance_issue or result.dataloss_risk or result.error:
|
||||
has_issues = True
|
||||
results.append(result)
|
||||
|
||||
if has_issues:
|
||||
# --- Print Table ---
|
||||
headers = ["Path", "Writeable", "Mount", "RAMDisk", "Performance", "DataLoss"]
|
||||
|
||||
CHECK_SYMBOL = "✅"
|
||||
CROSS_SYMBOL = "❌"
|
||||
BLANK_SYMBOL = "➖"
|
||||
|
||||
bool_to_check = lambda is_good: CHECK_SYMBOL if is_good else CROSS_SYMBOL
|
||||
|
||||
col_widths = [len(h) for h in headers]
|
||||
for r in results:
|
||||
col_widths[0] = max(col_widths[0], len(str(r.path)))
|
||||
|
||||
header_fmt = (
|
||||
f" {{:<{col_widths[0]}}} |"
|
||||
f" {{:^{col_widths[1]}}} |"
|
||||
f" {{:^{col_widths[2]}}} |"
|
||||
f" {{:^{col_widths[3]}}} |"
|
||||
f" {{:^{col_widths[4]}}} |"
|
||||
f" {{:^{col_widths[5]}}} "
|
||||
)
|
||||
|
||||
row_fmt = (
|
||||
f" {{:<{col_widths[0]}}} |"
|
||||
f" {{:^{col_widths[1]}}}|" # No space
|
||||
f" {{:^{col_widths[2]}}}|" # No space
|
||||
f" {{:^{col_widths[3]}}}|" # No space
|
||||
f" {{:^{col_widths[4]}}}|" # No space
|
||||
f" {{:^{col_widths[5]}}} " # DataLoss is last, needs space
|
||||
)
|
||||
|
||||
separator = (
|
||||
"-" * (col_widths[0] + 2) + "+" +
|
||||
"-" * (col_widths[1] + 2) + "+" +
|
||||
"-" * (col_widths[2] + 2) + "+" +
|
||||
"-" * (col_widths[3] + 2) + "+" +
|
||||
"-" * (col_widths[4] + 2) + "+" +
|
||||
"-" * (col_widths[5] + 2)
|
||||
)
|
||||
|
||||
print(header_fmt.format(*headers))
|
||||
print(separator)
|
||||
for r in results:
|
||||
is_persistent = r.var_name in PERSISTENT_VARS
|
||||
|
||||
# --- Symbol Logic ---
|
||||
write_symbol = bool_to_check(r.is_writeable)
|
||||
# Special case for read-only vars
|
||||
if r.var_name in READ_ONLY_VARS:
|
||||
write_symbol = CHECK_SYMBOL
|
||||
|
||||
mount_symbol = CHECK_SYMBOL if r.is_mounted else CROSS_SYMBOL
|
||||
|
||||
ramdisk_symbol = ""
|
||||
if is_persistent:
|
||||
ramdisk_symbol = CROSS_SYMBOL if r.underlying_fs_is_ramdisk else BLANK_SYMBOL
|
||||
else:
|
||||
ramdisk_symbol = CHECK_SYMBOL if r.is_ramdisk else CROSS_SYMBOL
|
||||
|
||||
if is_persistent:
|
||||
perf_symbol = BLANK_SYMBOL
|
||||
else:
|
||||
perf_symbol = bool_to_check(not r.performance_issue)
|
||||
|
||||
dataloss_symbol = bool_to_check(not r.dataloss_risk)
|
||||
|
||||
print(row_fmt.format(
|
||||
r.path,
|
||||
write_symbol,
|
||||
mount_symbol,
|
||||
ramdisk_symbol,
|
||||
perf_symbol,
|
||||
dataloss_symbol
|
||||
))
|
||||
|
||||
# --- Print Warning ---
|
||||
print("\n", file=sys.stderr)
|
||||
print_warning_message()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
26
install/production-filesystem/entrypoint.d/15-first-run-config.sh
Executable file
26
install/production-filesystem/entrypoint.d/15-first-run-config.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# first-run-check.sh - Checks and initializes configuration files on first run
|
||||
|
||||
# Check for app.conf and deploy if required
|
||||
if [ ! -f ${NETALERTX_CONFIG}/app.conf ]; then
|
||||
mkdir -p "${NETALERTX_CONFIG}" || {
|
||||
>&2 echo "ERROR: Failed to create config directory ${NETALERTX_CONFIG}"
|
||||
exit 1
|
||||
}
|
||||
cp /app/back/app.conf "${NETALERTX_CONFIG}/app.conf" || {
|
||||
>&2 echo "ERROR: Failed to copy default config to ${NETALERTX_CONFIG}/app.conf"
|
||||
exit 2
|
||||
}
|
||||
RESET='\033[0m'
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
🆕 First run detected. Default configuration written to ${NETALERTX_CONFIG}/app.conf.
|
||||
|
||||
Review your settings in the UI or edit the file directly before trusting
|
||||
this instance in production.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
|
||||
>&2 printf "%s" "${RESET}"
|
||||
fi
|
||||
|
||||
464
install/production-filesystem/entrypoint.d/20-first-run-db.sh
Executable file
464
install/production-filesystem/entrypoint.d/20-first-run-db.sh
Executable file
@@ -0,0 +1,464 @@
|
||||
#!/bin/sh
|
||||
# This script checks if the database file exists, and if not, creates it with the initial schema.
|
||||
# It is intended to be run at the first start of the application.
|
||||
|
||||
# If ALWAYS_FRESH_INSTALL is true, remove the database to force a rebuild.
|
||||
if [ "${ALWAYS_FRESH_INSTALL}" = "true" ]; then
|
||||
if [ -f "${NETALERTX_DB_FILE}" ]; then
|
||||
# Provide feedback to the user.
|
||||
>&2 echo "INFO: ALWAYS_FRESH_INSTALL is true. Removing existing database to force a fresh installation."
|
||||
rm -f "${NETALERTX_DB_FILE}" "${NETALERTX_DB_FILE}-shm" "${NETALERTX_DB_FILE}-wal"
|
||||
fi
|
||||
# Otherwise, if the db exists, exit.
|
||||
elif [ -f "${NETALERTX_DB_FILE}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CYAN='\033[1;36m'
|
||||
RESET='\033[0m'
|
||||
>&2 printf "%s" "${CYAN}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
🆕 First run detected. Building initial database schema in ${NETALERTX_DB_FILE}.
|
||||
|
||||
Do not interrupt this step. Once complete, consider backing up the fresh
|
||||
database before onboarding sensitive networks.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
|
||||
# Write all text to db file until we see "end-of-database-schema"
|
||||
sqlite3 "${NETALERTX_DB_FILE}" <<'end-of-database-schema'
|
||||
CREATE TABLE Events (eve_MAC STRING (50) NOT NULL COLLATE NOCASE, eve_IP STRING (50) NOT NULL COLLATE NOCASE, eve_DateTime DATETIME NOT NULL, eve_EventType STRING (30) NOT NULL COLLATE NOCASE, eve_AdditionalInfo STRING (250) DEFAULT (''), eve_PendingAlertEmail BOOLEAN NOT NULL CHECK (eve_PendingAlertEmail IN (0, 1)) DEFAULT (1), eve_PairEventRowid INTEGER);
|
||||
CREATE TABLE Sessions (ses_MAC STRING (50) COLLATE NOCASE, ses_IP STRING (50) COLLATE NOCASE, ses_EventTypeConnection STRING (30) COLLATE NOCASE, ses_DateTimeConnection DATETIME, ses_EventTypeDisconnection STRING (30) COLLATE NOCASE, ses_DateTimeDisconnection DATETIME, ses_StillConnected BOOLEAN, ses_AdditionalInfo STRING (250));
|
||||
CREATE TABLE IF NOT EXISTS "Online_History" (
|
||||
"Index" INTEGER,
|
||||
"Scan_Date" TEXT,
|
||||
"Online_Devices" INTEGER,
|
||||
"Down_Devices" INTEGER,
|
||||
"All_Devices" INTEGER,
|
||||
"Archived_Devices" INTEGER,
|
||||
"Offline_Devices" INTEGER,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE Devices (
|
||||
devMac STRING (50) PRIMARY KEY NOT NULL COLLATE NOCASE,
|
||||
devName STRING (50) NOT NULL DEFAULT "(unknown)",
|
||||
devOwner STRING (30) DEFAULT "(unknown)" NOT NULL,
|
||||
devType STRING (30),
|
||||
devVendor STRING (250),
|
||||
devFavorite BOOLEAN CHECK (devFavorite IN (0, 1)) DEFAULT (0) NOT NULL,
|
||||
devGroup STRING (10),
|
||||
devComments TEXT,
|
||||
devFirstConnection DATETIME NOT NULL,
|
||||
devLastConnection DATETIME NOT NULL,
|
||||
devLastIP STRING (50) NOT NULL COLLATE NOCASE,
|
||||
devStaticIP BOOLEAN DEFAULT (0) NOT NULL CHECK (devStaticIP IN (0, 1)),
|
||||
devScan INTEGER DEFAULT (1) NOT NULL,
|
||||
devLogEvents BOOLEAN NOT NULL DEFAULT (1) CHECK (devLogEvents IN (0, 1)),
|
||||
devAlertEvents BOOLEAN NOT NULL DEFAULT (1) CHECK (devAlertEvents IN (0, 1)),
|
||||
devAlertDown BOOLEAN NOT NULL DEFAULT (0) CHECK (devAlertDown IN (0, 1)),
|
||||
devSkipRepeated INTEGER DEFAULT 0 NOT NULL,
|
||||
devLastNotification DATETIME,
|
||||
devPresentLastScan BOOLEAN NOT NULL DEFAULT (0) CHECK (devPresentLastScan IN (0, 1)),
|
||||
devIsNew BOOLEAN NOT NULL DEFAULT (1) CHECK (devIsNew IN (0, 1)),
|
||||
devLocation STRING (250) COLLATE NOCASE,
|
||||
devIsArchived BOOLEAN NOT NULL DEFAULT (0) CHECK (devIsArchived IN (0, 1)),
|
||||
devParentMAC TEXT,
|
||||
devParentPort INTEGER,
|
||||
devIcon TEXT,
|
||||
devGUID TEXT,
|
||||
devSite TEXT,
|
||||
devSSID TEXT,
|
||||
devSyncHubNode TEXT,
|
||||
devSourcePlugin TEXT
|
||||
, "devCustomProps" TEXT);
|
||||
CREATE TABLE IF NOT EXISTS "Settings" (
|
||||
"setKey" TEXT,
|
||||
"setName" TEXT,
|
||||
"setDescription" TEXT,
|
||||
"setType" TEXT,
|
||||
"setOptions" TEXT,
|
||||
"setGroup" TEXT,
|
||||
"setValue" TEXT,
|
||||
"setEvents" TEXT,
|
||||
"setOverriddenByEnv" INTEGER
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "Parameters" (
|
||||
"par_ID" TEXT PRIMARY KEY,
|
||||
"par_Value" TEXT
|
||||
);
|
||||
CREATE TABLE Plugins_Objects(
|
||||
"Index" INTEGER,
|
||||
Plugin TEXT NOT NULL,
|
||||
Object_PrimaryID TEXT NOT NULL,
|
||||
Object_SecondaryID TEXT NOT NULL,
|
||||
DateTimeCreated TEXT NOT NULL,
|
||||
DateTimeChanged TEXT NOT NULL,
|
||||
Watched_Value1 TEXT NOT NULL,
|
||||
Watched_Value2 TEXT NOT NULL,
|
||||
Watched_Value3 TEXT NOT NULL,
|
||||
Watched_Value4 TEXT NOT NULL,
|
||||
Status TEXT NOT NULL,
|
||||
Extra TEXT NOT NULL,
|
||||
UserData TEXT NOT NULL,
|
||||
ForeignKey TEXT NOT NULL,
|
||||
SyncHubNodeName TEXT,
|
||||
"HelpVal1" TEXT,
|
||||
"HelpVal2" TEXT,
|
||||
"HelpVal3" TEXT,
|
||||
"HelpVal4" TEXT,
|
||||
ObjectGUID TEXT,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE Plugins_Events(
|
||||
"Index" INTEGER,
|
||||
Plugin TEXT NOT NULL,
|
||||
Object_PrimaryID TEXT NOT NULL,
|
||||
Object_SecondaryID TEXT NOT NULL,
|
||||
DateTimeCreated TEXT NOT NULL,
|
||||
DateTimeChanged TEXT NOT NULL,
|
||||
Watched_Value1 TEXT NOT NULL,
|
||||
Watched_Value2 TEXT NOT NULL,
|
||||
Watched_Value3 TEXT NOT NULL,
|
||||
Watched_Value4 TEXT NOT NULL,
|
||||
Status TEXT NOT NULL,
|
||||
Extra TEXT NOT NULL,
|
||||
UserData TEXT NOT NULL,
|
||||
ForeignKey TEXT NOT NULL,
|
||||
SyncHubNodeName TEXT,
|
||||
"HelpVal1" TEXT,
|
||||
"HelpVal2" TEXT,
|
||||
"HelpVal3" TEXT,
|
||||
"HelpVal4" TEXT, "ObjectGUID" TEXT,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE Plugins_History(
|
||||
"Index" INTEGER,
|
||||
Plugin TEXT NOT NULL,
|
||||
Object_PrimaryID TEXT NOT NULL,
|
||||
Object_SecondaryID TEXT NOT NULL,
|
||||
DateTimeCreated TEXT NOT NULL,
|
||||
DateTimeChanged TEXT NOT NULL,
|
||||
Watched_Value1 TEXT NOT NULL,
|
||||
Watched_Value2 TEXT NOT NULL,
|
||||
Watched_Value3 TEXT NOT NULL,
|
||||
Watched_Value4 TEXT NOT NULL,
|
||||
Status TEXT NOT NULL,
|
||||
Extra TEXT NOT NULL,
|
||||
UserData TEXT NOT NULL,
|
||||
ForeignKey TEXT NOT NULL,
|
||||
SyncHubNodeName TEXT,
|
||||
"HelpVal1" TEXT,
|
||||
"HelpVal2" TEXT,
|
||||
"HelpVal3" TEXT,
|
||||
"HelpVal4" TEXT, "ObjectGUID" TEXT,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE Plugins_Language_Strings(
|
||||
"Index" INTEGER,
|
||||
Language_Code TEXT NOT NULL,
|
||||
String_Key TEXT NOT NULL,
|
||||
String_Value TEXT NOT NULL,
|
||||
Extra TEXT NOT NULL,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE CurrentScan (
|
||||
cur_MAC STRING(50) NOT NULL COLLATE NOCASE,
|
||||
cur_IP STRING(50) NOT NULL COLLATE NOCASE,
|
||||
cur_Vendor STRING(250),
|
||||
cur_ScanMethod STRING(10),
|
||||
cur_Name STRING(250),
|
||||
cur_LastQuery STRING(250),
|
||||
cur_DateTime STRING(250),
|
||||
cur_SyncHubNodeName STRING(50),
|
||||
cur_NetworkSite STRING(250),
|
||||
cur_SSID STRING(250),
|
||||
cur_NetworkNodeMAC STRING(250),
|
||||
cur_PORT STRING(250),
|
||||
cur_Type STRING(250),
|
||||
UNIQUE(cur_MAC)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "AppEvents" (
|
||||
"Index" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"GUID" TEXT UNIQUE,
|
||||
"AppEventProcessed" BOOLEAN,
|
||||
"DateTimeCreated" TEXT,
|
||||
"ObjectType" TEXT,
|
||||
"ObjectGUID" TEXT,
|
||||
"ObjectPlugin" TEXT,
|
||||
"ObjectPrimaryID" TEXT,
|
||||
"ObjectSecondaryID" TEXT,
|
||||
"ObjectForeignKey" TEXT,
|
||||
"ObjectIndex" TEXT,
|
||||
"ObjectIsNew" BOOLEAN,
|
||||
"ObjectIsArchived" BOOLEAN,
|
||||
"ObjectStatusColumn" TEXT,
|
||||
"ObjectStatus" TEXT,
|
||||
"AppEventType" TEXT,
|
||||
"Helper1" TEXT,
|
||||
"Helper2" TEXT,
|
||||
"Helper3" TEXT,
|
||||
"Extra" TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "Notifications" (
|
||||
"Index" INTEGER,
|
||||
"GUID" TEXT UNIQUE,
|
||||
"DateTimeCreated" TEXT,
|
||||
"DateTimePushed" TEXT,
|
||||
"Status" TEXT,
|
||||
"JSON" TEXT,
|
||||
"Text" TEXT,
|
||||
"HTML" TEXT,
|
||||
"PublishedVia" TEXT,
|
||||
"Extra" TEXT,
|
||||
PRIMARY KEY("Index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE INDEX IDX_eve_DateTime ON Events (eve_DateTime);
|
||||
CREATE INDEX IDX_eve_EventType ON Events (eve_EventType COLLATE NOCASE);
|
||||
CREATE INDEX IDX_eve_MAC ON Events (eve_MAC COLLATE NOCASE);
|
||||
CREATE INDEX IDX_eve_PairEventRowid ON Events (eve_PairEventRowid);
|
||||
CREATE INDEX IDX_ses_EventTypeDisconnection ON Sessions (ses_EventTypeDisconnection COLLATE NOCASE);
|
||||
CREATE INDEX IDX_ses_EventTypeConnection ON Sessions (ses_EventTypeConnection COLLATE NOCASE);
|
||||
CREATE INDEX IDX_ses_DateTimeDisconnection ON Sessions (ses_DateTimeDisconnection);
|
||||
CREATE INDEX IDX_ses_MAC ON Sessions (ses_MAC COLLATE NOCASE);
|
||||
CREATE INDEX IDX_ses_DateTimeConnection ON Sessions (ses_DateTimeConnection);
|
||||
CREATE INDEX IDX_dev_PresentLastScan ON Devices (devPresentLastScan);
|
||||
CREATE INDEX IDX_dev_FirstConnection ON Devices (devFirstConnection);
|
||||
CREATE INDEX IDX_dev_AlertDeviceDown ON Devices (devAlertDown);
|
||||
CREATE INDEX IDX_dev_StaticIP ON Devices (devStaticIP);
|
||||
CREATE INDEX IDX_dev_ScanCycle ON Devices (devScan);
|
||||
CREATE INDEX IDX_dev_Favorite ON Devices (devFavorite);
|
||||
CREATE INDEX IDX_dev_LastIP ON Devices (devLastIP);
|
||||
CREATE INDEX IDX_dev_NewDevice ON Devices (devIsNew);
|
||||
CREATE INDEX IDX_dev_Archived ON Devices (devIsArchived);
|
||||
CREATE VIEW Events_Devices AS
|
||||
SELECT *
|
||||
FROM Events
|
||||
LEFT JOIN Devices ON eve_MAC = devMac
|
||||
/* Events_Devices(eve_MAC,eve_IP,eve_DateTime,eve_EventType,eve_AdditionalInfo,eve_PendingAlertEmail,eve_PairEventRowid,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps) */;
|
||||
CREATE VIEW LatestEventsPerMAC AS
|
||||
WITH RankedEvents AS (
|
||||
SELECT
|
||||
e.*,
|
||||
ROW_NUMBER() OVER (PARTITION BY e.eve_MAC ORDER BY e.eve_DateTime DESC) AS row_num
|
||||
FROM Events AS e
|
||||
)
|
||||
SELECT
|
||||
e.*,
|
||||
d.*,
|
||||
c.*
|
||||
FROM RankedEvents AS e
|
||||
LEFT JOIN Devices AS d ON e.eve_MAC = d.devMac
|
||||
INNER JOIN CurrentScan AS c ON e.eve_MAC = c.cur_MAC
|
||||
WHERE e.row_num = 1
|
||||
/* LatestEventsPerMAC(eve_MAC,eve_IP,eve_DateTime,eve_EventType,eve_AdditionalInfo,eve_PendingAlertEmail,eve_PairEventRowid,row_num,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps,cur_MAC,cur_IP,cur_Vendor,cur_ScanMethod,cur_Name,cur_LastQuery,cur_DateTime,cur_SyncHubNodeName,cur_NetworkSite,cur_SSID,cur_NetworkNodeMAC,cur_PORT,cur_Type) */;
|
||||
CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON ses_MAC = devMac
|
||||
/* Sessions_Devices(ses_MAC,ses_IP,ses_EventTypeConnection,ses_DateTimeConnection,ses_EventTypeDisconnection,ses_DateTimeDisconnection,ses_StillConnected,ses_AdditionalInfo,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps) */;
|
||||
CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eve_MAC,
|
||||
EVE1.eve_IP,
|
||||
EVE1.eve_EventType AS eve_EventTypeConnection,
|
||||
EVE1.eve_DateTime AS eve_DateTimeConnection,
|
||||
CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') OR
|
||||
EVE2.eve_EventType IS NULL THEN EVE2.eve_EventType ELSE '<missing event>' END AS eve_EventTypeDisconnection,
|
||||
CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') THEN EVE2.eve_DateTime ELSE NULL END AS eve_DateTimeDisconnection,
|
||||
CASE WHEN EVE2.eve_EventType IS NULL THEN 1 ELSE 0 END AS eve_StillConnected,
|
||||
EVE1.eve_AdditionalInfo
|
||||
FROM Events AS EVE1
|
||||
LEFT JOIN
|
||||
Events AS EVE2 ON EVE1.eve_PairEventRowID = EVE2.RowID
|
||||
WHERE EVE1.eve_EventType IN ('New Device', 'Connected','Down Reconnected')
|
||||
UNION
|
||||
SELECT eve_MAC,
|
||||
eve_IP,
|
||||
'<missing event>' AS eve_EventTypeConnection,
|
||||
NULL AS eve_DateTimeConnection,
|
||||
eve_EventType AS eve_EventTypeDisconnection,
|
||||
eve_DateTime AS eve_DateTimeDisconnection,
|
||||
0 AS eve_StillConnected,
|
||||
eve_AdditionalInfo
|
||||
FROM Events AS EVE1
|
||||
WHERE (eve_EventType = 'Device Down' OR
|
||||
eve_EventType = 'Disconnected') AND
|
||||
EVE1.eve_PairEventRowID IS NULL
|
||||
/* Convert_Events_to_Sessions(eve_MAC,eve_IP,eve_EventTypeConnection,eve_DateTimeConnection,eve_EventTypeDisconnection,eve_DateTimeDisconnection,eve_StillConnected,eve_AdditionalInfo) */;
|
||||
CREATE TRIGGER "trg_insert_devices"
|
||||
AFTER INSERT ON "Devices"
|
||||
WHEN NOT EXISTS (
|
||||
SELECT 1 FROM AppEvents
|
||||
WHERE AppEventProcessed = 0
|
||||
AND ObjectType = 'Devices'
|
||||
AND ObjectGUID = NEW.devGUID
|
||||
AND ObjectStatus = CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END
|
||||
AND AppEventType = 'insert'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO "AppEvents" (
|
||||
"GUID",
|
||||
"DateTimeCreated",
|
||||
"AppEventProcessed",
|
||||
"ObjectType",
|
||||
"ObjectGUID",
|
||||
"ObjectPrimaryID",
|
||||
"ObjectSecondaryID",
|
||||
"ObjectStatus",
|
||||
"ObjectStatusColumn",
|
||||
"ObjectIsNew",
|
||||
"ObjectIsArchived",
|
||||
"ObjectForeignKey",
|
||||
"ObjectPlugin",
|
||||
"AppEventType"
|
||||
)
|
||||
VALUES (
|
||||
|
||||
lower(
|
||||
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
|
||||
substr(hex( randomblob(2)), 2) || '-' ||
|
||||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
|
||||
substr(hex(randomblob(2)), 2) || '-' ||
|
||||
hex(randomblob(6))
|
||||
)
|
||||
,
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Devices',
|
||||
NEW.devGUID, -- ObjectGUID
|
||||
NEW.devMac, -- ObjectPrimaryID
|
||||
NEW.devLastIP, -- ObjectSecondaryID
|
||||
CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus
|
||||
'devPresentLastScan', -- ObjectStatusColumn
|
||||
NEW.devIsNew, -- ObjectIsNew
|
||||
NEW.devIsArchived, -- ObjectIsArchived
|
||||
NEW.devGUID, -- ObjectForeignKey
|
||||
'DEVICES', -- ObjectForeignKey
|
||||
'insert'
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER "trg_update_devices"
|
||||
AFTER UPDATE ON "Devices"
|
||||
WHEN NOT EXISTS (
|
||||
SELECT 1 FROM AppEvents
|
||||
WHERE AppEventProcessed = 0
|
||||
AND ObjectType = 'Devices'
|
||||
AND ObjectGUID = NEW.devGUID
|
||||
AND ObjectStatus = CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END
|
||||
AND AppEventType = 'update'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO "AppEvents" (
|
||||
"GUID",
|
||||
"DateTimeCreated",
|
||||
"AppEventProcessed",
|
||||
"ObjectType",
|
||||
"ObjectGUID",
|
||||
"ObjectPrimaryID",
|
||||
"ObjectSecondaryID",
|
||||
"ObjectStatus",
|
||||
"ObjectStatusColumn",
|
||||
"ObjectIsNew",
|
||||
"ObjectIsArchived",
|
||||
"ObjectForeignKey",
|
||||
"ObjectPlugin",
|
||||
"AppEventType"
|
||||
)
|
||||
VALUES (
|
||||
|
||||
lower(
|
||||
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
|
||||
substr(hex( randomblob(2)), 2) || '-' ||
|
||||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
|
||||
substr(hex(randomblob(2)), 2) || '-' ||
|
||||
hex(randomblob(6))
|
||||
)
|
||||
,
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Devices',
|
||||
NEW.devGUID, -- ObjectGUID
|
||||
NEW.devMac, -- ObjectPrimaryID
|
||||
NEW.devLastIP, -- ObjectSecondaryID
|
||||
CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus
|
||||
'devPresentLastScan', -- ObjectStatusColumn
|
||||
NEW.devIsNew, -- ObjectIsNew
|
||||
NEW.devIsArchived, -- ObjectIsArchived
|
||||
NEW.devGUID, -- ObjectForeignKey
|
||||
'DEVICES', -- ObjectForeignKey
|
||||
'update'
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER "trg_delete_devices"
|
||||
AFTER DELETE ON "Devices"
|
||||
WHEN NOT EXISTS (
|
||||
SELECT 1 FROM AppEvents
|
||||
WHERE AppEventProcessed = 0
|
||||
AND ObjectType = 'Devices'
|
||||
AND ObjectGUID = OLD.devGUID
|
||||
AND ObjectStatus = CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END
|
||||
AND AppEventType = 'delete'
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO "AppEvents" (
|
||||
"GUID",
|
||||
"DateTimeCreated",
|
||||
"AppEventProcessed",
|
||||
"ObjectType",
|
||||
"ObjectGUID",
|
||||
"ObjectPrimaryID",
|
||||
"ObjectSecondaryID",
|
||||
"ObjectStatus",
|
||||
"ObjectStatusColumn",
|
||||
"ObjectIsNew",
|
||||
"ObjectIsArchived",
|
||||
"ObjectForeignKey",
|
||||
"ObjectPlugin",
|
||||
"AppEventType"
|
||||
)
|
||||
VALUES (
|
||||
|
||||
lower(
|
||||
hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' ||
|
||||
substr(hex( randomblob(2)), 2) || '-' ||
|
||||
substr('AB89', 1 + (abs(random()) % 4) , 1) ||
|
||||
substr(hex(randomblob(2)), 2) || '-' ||
|
||||
hex(randomblob(6))
|
||||
)
|
||||
,
|
||||
DATETIME('now'),
|
||||
FALSE,
|
||||
'Devices',
|
||||
OLD.devGUID, -- ObjectGUID
|
||||
OLD.devMac, -- ObjectPrimaryID
|
||||
OLD.devLastIP, -- ObjectSecondaryID
|
||||
CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus
|
||||
'devPresentLastScan', -- ObjectStatusColumn
|
||||
OLD.devIsNew, -- ObjectIsNew
|
||||
OLD.devIsArchived, -- ObjectIsArchived
|
||||
OLD.devGUID, -- ObjectForeignKey
|
||||
'DEVICES', -- ObjectForeignKey
|
||||
'delete'
|
||||
);
|
||||
END;
|
||||
end-of-database-schema
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
RED='\033[1;31m'
|
||||
RESET='\033[0m'
|
||||
>&2 printf "%s" "${RED}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
❌ CRITICAL: Database schema creation failed for ${NETALERTX_DB_FILE}.
|
||||
|
||||
NetAlertX cannot start without a properly initialized database. This
|
||||
failure typically indicates:
|
||||
|
||||
* Insufficient disk space or write permissions in the database directory
|
||||
* Corrupted or inaccessible SQLite installation
|
||||
* File system issues preventing database file creation
|
||||
|
||||
Check the logs for detailed SQLite error messages. Ensure the container
|
||||
has write access to the database path and adequate storage space.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
53
install/production-filesystem/entrypoint.d/25-mandatory-folders.sh
Executable file
53
install/production-filesystem/entrypoint.d/25-mandatory-folders.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
# Initialize required directories and log files
|
||||
# These must exist before services start to avoid permission/write errors
|
||||
|
||||
check_mandatory_folders() {
|
||||
# Check and create plugins log directory
|
||||
if [ ! -d "${NETALERTX_PLUGINS_LOG}" ]; then
|
||||
echo " * Creating Plugins log."
|
||||
if ! mkdir -p "${NETALERTX_PLUGINS_LOG}"; then
|
||||
echo "Error: Failed to create plugins log directory: ${NETALERTX_PLUGINS_LOG}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check and create system services run log directory
|
||||
if [ ! -d "${SYSTEM_SERVICES_RUN_LOG}" ]; then
|
||||
echo " * Creating System services run log."
|
||||
if ! mkdir -p "${SYSTEM_SERVICES_RUN_LOG}"; then
|
||||
echo "Error: Failed to create system services run log directory: ${SYSTEM_SERVICES_RUN_LOG}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check and create system services run tmp directory
|
||||
if [ ! -d "${SYSTEM_SERVICES_RUN_TMP}" ]; then
|
||||
echo " * Creating System services run tmp."
|
||||
if ! mkdir -p "${SYSTEM_SERVICES_RUN_TMP}"; then
|
||||
echo "Error: Failed to create system services run tmp directory: ${SYSTEM_SERVICES_RUN_TMP}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check and create DB locked log file
|
||||
if [ ! -f "${LOG_DB_IS_LOCKED}" ]; then
|
||||
echo " * Creating DB locked log."
|
||||
if ! touch "${LOG_DB_IS_LOCKED}"; then
|
||||
echo "Error: Failed to create DB locked log file: ${LOG_DB_IS_LOCKED}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check and create execution queue log file
|
||||
if [ ! -f "${LOG_EXECUTION_QUEUE}" ]; then
|
||||
echo " * Creating Execution queue log."
|
||||
if ! touch "${LOG_EXECUTION_QUEUE}"; then
|
||||
echo "Error: Failed to create execution queue log file: ${LOG_EXECUTION_QUEUE}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Run the function
|
||||
check_mandatory_folders
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 30-writable-config.sh: Verify read/write permissions for config and database files.
|
||||
#
|
||||
# This script ensures that the application can read from and write to the
|
||||
# critical configuration and database files after startup.
|
||||
|
||||
# --- Color Codes ---
|
||||
RED='\033[1;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
RESET='\033[0m'
|
||||
|
||||
# --- Main Logic ---
|
||||
|
||||
# Define paths that need read-write access
|
||||
READ_WRITE_PATHS="
|
||||
${NETALERTX_CONFIG_FILE}
|
||||
${NETALERTX_DB_FILE}
|
||||
"
|
||||
|
||||
# --- Permission Validation ---
|
||||
|
||||
failures=0
|
||||
|
||||
# Check read-write paths for existence, read, and write access
|
||||
for path in $READ_WRITE_PATHS; do
|
||||
if [ ! -e "$path" ]; then
|
||||
failures=1
|
||||
>&2 printf "%s" "${RED}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
❌ CRITICAL: Path does not exist.
|
||||
|
||||
The required path "${path}" could not be found. The application
|
||||
cannot start without its complete directory structure.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
elif [ ! -r "$path" ]; then
|
||||
failures=1
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Read permission denied.
|
||||
|
||||
The application cannot read from "${path}". This will cause
|
||||
unpredictable errors. Please correct the file system permissions.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
elif [ ! -w "$path" ]; then
|
||||
failures=1
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Write permission denied.
|
||||
|
||||
The application cannot write to "${path}". This will prevent it from
|
||||
saving data, logs, or configuration.
|
||||
|
||||
To fix this automatically, restart the container with root privileges
|
||||
(e.g., remove the "user:" directive in your Docker Compose file).
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
fi
|
||||
done
|
||||
|
||||
# If there were any failures, exit
|
||||
if [ "$failures" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
50
install/production-filesystem/entrypoint.d/35-nginx-config.sh
Executable file
50
install/production-filesystem/entrypoint.d/35-nginx-config.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
# check-nginx-config.sh - verify nginx conf.active mount is writable when startup needs to render config.
|
||||
|
||||
CONF_ACTIVE_DIR="${SYSTEM_SERVICES_ACTIVE_CONFIG}"
|
||||
TARGET_FILE="${CONF_ACTIVE_DIR}/netalertx.conf"
|
||||
|
||||
# If the directory is missing entirely we warn and exit failure so the caller can see the message.
|
||||
if [ ! -d "${CONF_ACTIVE_DIR}" ]; then
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
RESET=$(printf '\033[0m')
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Nginx configuration mount ${CONF_ACTIVE_DIR} is missing.
|
||||
|
||||
Custom listen address or port changes require a writable nginx conf.active
|
||||
directory. Without it, the container falls back to defaults and ignores
|
||||
your overrides.
|
||||
|
||||
Create a bind mount:
|
||||
--mount type=bind,src=/path/on/host,dst=${CONF_ACTIVE_DIR}
|
||||
and ensure it is owned by the netalertx user (20211:20211) with 700 perms.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_FILE="${CONF_ACTIVE_DIR}/.netalertx-write-test"
|
||||
if ! ( : >"${TMP_FILE}" ) 2>/dev/null; then
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
RESET=$(printf '\033[0m')
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Unable to write to ${TARGET_FILE}.
|
||||
|
||||
Ensure the conf.active mount is writable by the netalertx user before
|
||||
changing LISTEN_ADDR or PORT. Fix permissions:
|
||||
chown -R 20211:20211 ${CONF_ACTIVE_DIR}
|
||||
find ${CONF_ACTIVE_DIR} -type d -exec chmod 700 {} +
|
||||
find ${CONF_ACTIVE_DIR} -type f -exec chmod 600 {} +
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "${TMP_FILE}"
|
||||
|
||||
exit 0
|
||||
41
install/production-filesystem/entrypoint.d/60-user-netalertx.sh
Executable file
41
install/production-filesystem/entrypoint.d/60-user-netalertx.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# check-user-netalertx.sh - ensure the container is running as the hardened service user.
|
||||
|
||||
EXPECTED_USER="${NETALERTX_USER:-netalertx}"
|
||||
EXPECTED_UID="$(getent passwd "${EXPECTED_USER}" 2>/dev/null | cut -d: -f3)"
|
||||
EXPECTED_GID="$(getent passwd "${EXPECTED_USER}" 2>/dev/null | cut -d: -f4)"
|
||||
CURRENT_UID="$(id -u)"
|
||||
CURRENT_GID="$(id -g)"
|
||||
|
||||
# Fallback to known defaults when lookups fail
|
||||
if [ -z "${EXPECTED_UID}" ]; then
|
||||
EXPECTED_UID="20211"
|
||||
fi
|
||||
if [ -z "${EXPECTED_GID}" ]; then
|
||||
EXPECTED_GID="20211"
|
||||
fi
|
||||
|
||||
if [ "${CURRENT_UID}" -eq "${EXPECTED_UID}" ] && [ "${CURRENT_GID}" -eq "${EXPECTED_GID}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
RESET=$(printf '\033[0m')
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: NetAlertX is running as UID ${CURRENT_UID}:${CURRENT_GID}.
|
||||
|
||||
Hardened permissions, file ownership, and runtime isolation expect the
|
||||
dedicated service account (${EXPECTED_USER} -> ${EXPECTED_UID}:${EXPECTED_GID}).
|
||||
When you override the container user (for example, docker run --user 1000:1000
|
||||
or a Compose "user:" directive), NetAlertX loses crucial safeguards and
|
||||
future upgrades may silently fail.
|
||||
|
||||
Restore the container to the default user:
|
||||
* Remove any custom --user flag
|
||||
* Delete "user:" overrides in compose files
|
||||
* Recreate the container so volume ownership is reset
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
64
install/production-filesystem/entrypoint.d/80-host-mode-network.sh
Executable file
64
install/production-filesystem/entrypoint.d/80-host-mode-network.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
# detect when the container is not using host networking.
|
||||
|
||||
# Exit if NETALERTX_DEBUG=1
|
||||
if [ "${NETALERTX_DEBUG}" = "1" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get the default network interface
|
||||
DEFAULT_IF="$(ip route show default 0.0.0.0/0 2>/dev/null | awk 'NR==1 {print $5}')"
|
||||
if [ -z "${DEFAULT_IF}" ]; then
|
||||
# No default route; nothing to validate.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
IF_LINK_INFO="$(ip link show "${DEFAULT_IF}" 2>/dev/null)"
|
||||
IF_IP="$(ip -4 addr show "${DEFAULT_IF}" 2>/dev/null | awk '/inet / {print $2}' | head -n1)"
|
||||
IF_MAC=""
|
||||
if [ -r "/sys/class/net/${DEFAULT_IF}/address" ]; then
|
||||
IF_MAC="$(cat "/sys/class/net/${DEFAULT_IF}/address")"
|
||||
fi
|
||||
|
||||
looks_like_bridge="0"
|
||||
|
||||
# Check for common bridge MAC and IP patterns
|
||||
case "${IF_MAC}" in
|
||||
02:42:*) looks_like_bridge="1" ;;
|
||||
00:00:00:00:00:00) looks_like_bridge="1" ;;
|
||||
"") ;; # leave as is
|
||||
esac
|
||||
|
||||
# Check for common bridge IP ranges
|
||||
case "${IF_IP}" in
|
||||
172.1[6-9].*|172.2[0-9].*|172.3[0-1].*) looks_like_bridge="1" ;;
|
||||
192.168.65.*) looks_like_bridge="1" ;;
|
||||
esac
|
||||
|
||||
if echo "${IF_LINK_INFO}" | grep -q "@if"; then
|
||||
looks_like_bridge="1"
|
||||
fi
|
||||
|
||||
if [ "${looks_like_bridge}" -ne 1 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
RESET=$(printf '\033[0m')
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: NetAlertX is not running with --network=host.
|
||||
|
||||
Bridge networking blocks passive discovery (ARP, NBNS, mDNS) and active
|
||||
scanning accuracy. Most plugins expect raw access to the LAN through host
|
||||
networking and CAP_NET_RAW capabilities.
|
||||
|
||||
Restart the container with:
|
||||
docker run --network=host --cap-add=NET_RAW --cap-add=NET_ADMIN --cap-add=NET_BIND_SERVICE
|
||||
or set "network_mode: host" in docker-compose.yml.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
exit 0
|
||||
31
install/production-filesystem/entrypoint.d/85-layer-2-capabilities.sh
Executable file
31
install/production-filesystem/entrypoint.d/85-layer-2-capabilities.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# layer-2-network.sh - Uses a real nmap command to detect missing container
|
||||
# privileges and warns the user. It is silent on success.
|
||||
|
||||
# Run a fast nmap command that requires raw sockets, capturing only stderr.
|
||||
ERROR_OUTPUT=$(nmap --privileged -sS -p 20211 127.0.0.1 2>&1)
|
||||
EXIT_CODE=$?
|
||||
|
||||
# Flag common capability errors regardless of exact exit code.
|
||||
if [ "$EXIT_CODE" -ne 0 ] && \
|
||||
echo "$ERROR_OUTPUT" | grep -q -e "Operation not permitted" -e "requires root privileges"
|
||||
then
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
RESET=$(printf '\033[0m')
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<'EOF'
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Raw network capabilities are missing.
|
||||
|
||||
Tools that rely on NET_RAW/NET_ADMIN/NET_BIND_SERVICE (e.g. nmap -sS,
|
||||
arp-scan, nbtscan) will not function. Restart the container with:
|
||||
|
||||
--cap-add=NET_RAW --cap-add=NET_ADMIN --cap-add=NET_BIND_SERVICE
|
||||
|
||||
Without those caps, NetAlertX cannot inspect your network. Fix it before
|
||||
trusting any results.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
fi
|
||||
exit 0 # Always exit success even after warnings
|
||||
Reference in New Issue
Block a user