mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
/data and /tmp standarization
This commit is contained in:
@@ -13,12 +13,13 @@ RESET=$(printf '\033[0m')
|
||||
|
||||
# Define paths that need read-write access
|
||||
READ_WRITE_PATHS="
|
||||
${NETALERTX_DATA}
|
||||
${NETALERTX_DB}
|
||||
${NETALERTX_API}
|
||||
${NETALERTX_LOG}
|
||||
${SYSTEM_SERVICES_RUN}
|
||||
${NETALERTX_CONFIG}
|
||||
${NETALERTX_CONFIG_FILE}
|
||||
${NETALERTX_DB}
|
||||
${NETALERTX_DB_FILE}
|
||||
"
|
||||
|
||||
@@ -39,7 +40,7 @@ if [ "$(id -u)" -eq 0 ]; then
|
||||
* 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
|
||||
/data/db and /data/config directories to the netalertx user, ensuring
|
||||
proper operation in subsequent runs.
|
||||
|
||||
Remember: Never operate security-critical tools as root unless you're
|
||||
@@ -54,8 +55,8 @@ EOF
|
||||
chown -R netalertx ${READ_WRITE_PATHS} 2>/dev/null || true
|
||||
|
||||
# Set directory and file permissions for all read-write paths
|
||||
find ${READ_WRITE_PATHS} -type d -exec chmod u+rwx {}
|
||||
find ${READ_WRITE_PATHS} -type f -exec chmod u+rw {}
|
||||
find ${READ_WRITE_PATHS} -type d -exec chmod u+rwx {} \;
|
||||
find ${READ_WRITE_PATHS} -type f -exec chmod u+rw {} \;
|
||||
echo Permissions fixed for read-write paths. Please restart the container as user 20211.
|
||||
sleep infinity & wait $!
|
||||
fi
|
||||
|
||||
145
install/production-filesystem/entrypoint.d/01-data-migration.sh
Executable file
145
install/production-filesystem/entrypoint.d/01-data-migration.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/bin/sh
|
||||
# 01-data-migration.sh - consolidate legacy /app mounts into /data
|
||||
|
||||
set -eu
|
||||
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
CYAN=$(printf '\033[1;36m')
|
||||
RED=$(printf '\033[1;31m')
|
||||
RESET=$(printf '\033[0m')
|
||||
|
||||
DATA_DIR=${NETALERTX_DATA:-/data}
|
||||
TARGET_CONFIG=${NETALERTX_CONFIG:-${DATA_DIR}/config}
|
||||
TARGET_DB=${NETALERTX_DB:-${DATA_DIR}/db}
|
||||
LEGACY_CONFIG=/app/config
|
||||
LEGACY_DB=/app/db
|
||||
MARKER_NAME=.migration
|
||||
|
||||
is_mounted() {
|
||||
local path="$1"
|
||||
if [ ! -d "${path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
mountpoint -q "${path}" 2>/dev/null
|
||||
}
|
||||
|
||||
warn_unmount_legacy() {
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Legacy mounts detected at ${LEGACY_CONFIG} or ${LEGACY_DB}.
|
||||
|
||||
Migration markers are present. Your data now lives under ${DATA_DIR}.
|
||||
Unmount the legacy /app/config and /app/db paths from your docker-compose
|
||||
file to avoid stale mounts on future starts.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
}
|
||||
|
||||
fatal_missing_data_mount() {
|
||||
>&2 printf "%s" "${RED}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
❌ CRITICAL: /data is not mounted but legacy mounts are still present.
|
||||
|
||||
Mount the new consolidated volume at ${DATA_DIR} so data can be migrated.
|
||||
Once mounted, restart the container to complete migration automatically.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
}
|
||||
|
||||
migrate_legacy_mounts() {
|
||||
>&2 printf "%s" "${CYAN}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
🛠️ Migrating legacy /app mounts into ${DATA_DIR}.
|
||||
|
||||
Existing configuration and database files will be copied into the new
|
||||
consolidated volume. This runs once per environment.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
|
||||
mkdir -p "${TARGET_CONFIG}" "${TARGET_DB}" || return 1
|
||||
chmod 700 "${TARGET_CONFIG}" "${TARGET_DB}" 2>/dev/null || true
|
||||
|
||||
if ! cp -a "${LEGACY_CONFIG}/." "${TARGET_CONFIG}/"; then
|
||||
>&2 printf "%s" "${RED}"
|
||||
>&2 echo "Migration failed while copying configuration files."
|
||||
>&2 printf "%s" "${RESET}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! cp -a "${LEGACY_DB}/." "${TARGET_DB}/"; then
|
||||
>&2 printf "%s" "${RED}"
|
||||
>&2 echo "Migration failed while copying database files."
|
||||
>&2 printf "%s" "${RESET}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
touch "${LEGACY_CONFIG}/${MARKER_NAME}" "${LEGACY_DB}/${MARKER_NAME}" 2>/dev/null || true
|
||||
|
||||
warn_unmount_legacy
|
||||
return 0
|
||||
}
|
||||
|
||||
CONFIG_MARKED=false
|
||||
DB_MARKED=false
|
||||
[ -f "${LEGACY_CONFIG}/${MARKER_NAME}" ] && CONFIG_MARKED=true
|
||||
[ -f "${LEGACY_DB}/${MARKER_NAME}" ] && DB_MARKED=true
|
||||
|
||||
if ${CONFIG_MARKED} || ${DB_MARKED}; then
|
||||
warn_unmount_legacy
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CONFIG_MOUNTED=false
|
||||
DB_MOUNTED=false
|
||||
DATA_MOUNTED=false
|
||||
is_mounted "${LEGACY_CONFIG}" && CONFIG_MOUNTED=true
|
||||
is_mounted "${LEGACY_DB}" && DB_MOUNTED=true
|
||||
is_mounted "${DATA_DIR}" && DATA_MOUNTED=true
|
||||
|
||||
# Nothing to migrate if legacy mounts are absent
|
||||
if ! ${CONFIG_MOUNTED} && ! ${DB_MOUNTED}; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Partial legacy mount state, notify and exit
|
||||
if ${CONFIG_MOUNTED} && ! ${DB_MOUNTED}; then
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: /app/config is still mounted but /app/db is not.
|
||||
|
||||
Mount both legacy paths alongside ${DATA_DIR} to migrate automatically,
|
||||
or unmount /app/config entirely.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ${DB_MOUNTED} && ! ${CONFIG_MOUNTED}; then
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: /app/db is still mounted but /app/config is not.
|
||||
|
||||
Mount both legacy paths alongside ${DATA_DIR} to migrate automatically,
|
||||
or unmount /app/db entirely.
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! ${DATA_MOUNTED}; then
|
||||
fatal_missing_data_mount
|
||||
exit 1
|
||||
fi
|
||||
|
||||
migrate_legacy_mounts || exit 1
|
||||
exit 0
|
||||
@@ -7,30 +7,60 @@ from dataclasses import dataclass
|
||||
# if NETALERTX_DEBUG is 1 then exit
|
||||
if os.environ.get("NETALERTX_DEBUG") == "1":
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@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_mount_point: bool = False
|
||||
is_ramdisk: bool = False
|
||||
underlying_fs_is_ramdisk: bool = False # Track this separately
|
||||
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
|
||||
category: str = ""
|
||||
role: str = ""
|
||||
group: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PathSpec:
|
||||
"""Describes how a filesystem path should behave."""
|
||||
|
||||
var_name: str
|
||||
category: str # e.g. persist, ramdisk
|
||||
role: str # primary, sub, secondary
|
||||
group: str # logical grouping for primary/sub relationships
|
||||
|
||||
|
||||
PATH_SPECS = (
|
||||
PathSpec("NETALERTX_DATA", "persist", "primary", "data"),
|
||||
PathSpec("NETALERTX_DB", "persist", "sub", "data"),
|
||||
PathSpec("NETALERTX_CONFIG", "persist", "sub", "data"),
|
||||
PathSpec("SYSTEM_SERVICES_RUN_TMP", "ramdisk", "primary", "tmp"),
|
||||
PathSpec("NETALERTX_API", "ramdisk", "sub", "tmp"),
|
||||
PathSpec("NETALERTX_LOG", "ramdisk", "sub", "tmp"),
|
||||
PathSpec("SYSTEM_SERVICES_RUN", "ramdisk", "sub", "tmp"),
|
||||
PathSpec("SYSTEM_SERVICES_ACTIVE_CONFIG", "ramdisk", "secondary", "tmp"),
|
||||
)
|
||||
|
||||
|
||||
def get_mount_info():
|
||||
"""Parses /proc/mounts to get a dict of {mount_point: fstype}."""
|
||||
mounts = {}
|
||||
try:
|
||||
with open('/proc/mounts', 'r') as f:
|
||||
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', ' ')
|
||||
mount_point = parts[1].replace("\\040", " ")
|
||||
fstype = parts[2]
|
||||
mounts[mount_point] = fstype
|
||||
except FileNotFoundError:
|
||||
@@ -38,78 +68,112 @@ def get_mount_info():
|
||||
return None
|
||||
return mounts
|
||||
|
||||
def analyze_path(var_name, is_persistent, mounted_filesystems, non_persistent_fstypes, read_only_vars):
|
||||
|
||||
def _resolve_writeable_state(target_path: str) -> bool:
|
||||
"""Determine if a path is writeable, ascending to the first existing parent."""
|
||||
|
||||
current = target_path
|
||||
seen: set[str] = set()
|
||||
while True:
|
||||
if current in seen:
|
||||
break
|
||||
seen.add(current)
|
||||
|
||||
if os.path.exists(current):
|
||||
return os.access(current, os.W_OK)
|
||||
|
||||
parent_dir = os.path.dirname(current)
|
||||
if not parent_dir or parent_dir == current:
|
||||
break
|
||||
current = parent_dir
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def analyze_path(
|
||||
spec: PathSpec,
|
||||
mounted_filesystems,
|
||||
non_persistent_fstypes,
|
||||
):
|
||||
"""
|
||||
Analyzes a single path, checking for errors, performance, and dataloss.
|
||||
"""
|
||||
result = MountCheckResult(var_name=var_name)
|
||||
target_path = os.environ.get(var_name)
|
||||
result = MountCheckResult(
|
||||
var_name=spec.var_name,
|
||||
category=spec.category,
|
||||
role=spec.role,
|
||||
group=spec.group,
|
||||
)
|
||||
target_path = os.environ.get(spec.var_name)
|
||||
|
||||
if target_path is None:
|
||||
result.path = f"({var_name} unset)"
|
||||
result.path = f"({spec.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.is_writeable = _resolve_writeable_state(target_path)
|
||||
|
||||
if not result.is_writeable:
|
||||
result.error = True
|
||||
result.write_error = True
|
||||
if spec.role != "secondary":
|
||||
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
|
||||
normalized = mount_point.rstrip("/") if mount_point != "/" else "/"
|
||||
if target_path == normalized or target_path.startswith(f"{normalized}/"):
|
||||
if len(normalized) > len(longest_mount):
|
||||
longest_mount = normalized
|
||||
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.is_mount_point = 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
|
||||
if longest_mount and longest_mount != "/":
|
||||
if target_path == longest_mount or target_path.startswith(
|
||||
f"{longest_mount}/"
|
||||
):
|
||||
result.is_mounted = True
|
||||
result.fstype = parent_mount_fstype
|
||||
result.is_ramdisk = parent_mount_fstype in non_persistent_fstypes
|
||||
|
||||
# --- 4. Apply Risk Logic ---
|
||||
if is_persistent:
|
||||
if result.underlying_fs_is_ramdisk:
|
||||
if spec.category == "persist":
|
||||
if result.underlying_fs_is_ramdisk or result.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
|
||||
|
||||
elif spec.category == "ramdisk":
|
||||
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'
|
||||
|
||||
YELLOW = "\033[1;33m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
message = (
|
||||
"══════════════════════════════════════════════════════════════════════════════\n"
|
||||
"⚠️ ATTENTION: Configuration issues detected (marked with ❌).\n\n"
|
||||
@@ -122,61 +186,139 @@ def print_warning_message():
|
||||
" https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/mount-configuration-issues.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 ***
|
||||
def _get_active_specs() -> list[PathSpec]:
|
||||
"""Return the path specifications that should be evaluated for this run."""
|
||||
|
||||
return list(PATH_SPECS)
|
||||
|
||||
|
||||
def _sub_result_is_healthy(result: MountCheckResult) -> bool:
|
||||
"""Determine if a sub-path result satisfies its expected constraints."""
|
||||
|
||||
if result.category == "persist":
|
||||
if not result.is_mounted:
|
||||
return False
|
||||
if result.dataloss_risk or result.write_error or result.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
if result.category == "ramdisk":
|
||||
if not result.is_mounted or not result.is_ramdisk:
|
||||
return False
|
||||
if result.performance_issue or result.write_error or result.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _apply_primary_rules(specs: list[PathSpec], results_map: dict[str, MountCheckResult]) -> list[MountCheckResult]:
|
||||
"""Suppress or flag primary rows based on the state of their sub-paths."""
|
||||
|
||||
final_results: list[MountCheckResult] = []
|
||||
specs_by_group: dict[str, list[PathSpec]] = {}
|
||||
for spec in specs:
|
||||
specs_by_group.setdefault(spec.group, []).append(spec)
|
||||
|
||||
for spec in specs:
|
||||
result = results_map.get(spec.var_name)
|
||||
if result is None:
|
||||
continue
|
||||
|
||||
if spec.role == "primary":
|
||||
group_specs = specs_by_group.get(spec.group, [])
|
||||
sub_results_all = [
|
||||
results_map[s.var_name]
|
||||
for s in group_specs
|
||||
if s.var_name in results_map and s.var_name != spec.var_name
|
||||
]
|
||||
core_sub_results = [
|
||||
results_map[s.var_name]
|
||||
for s in group_specs
|
||||
if s.var_name in results_map and s.role == "sub"
|
||||
]
|
||||
|
||||
sub_mount_points = [sub for sub in sub_results_all if sub.is_mount_point]
|
||||
core_mount_points = [sub for sub in core_sub_results if sub.is_mount_point]
|
||||
all_core_subs_healthy = bool(core_sub_results) and all(
|
||||
_sub_result_is_healthy(sub) for sub in core_sub_results
|
||||
)
|
||||
all_core_subs_are_mounts = bool(core_sub_results) and len(core_mount_points) == len(core_sub_results)
|
||||
|
||||
if all_core_subs_healthy:
|
||||
if result.write_error:
|
||||
result.write_error = False
|
||||
if not result.is_writeable:
|
||||
result.is_writeable = True
|
||||
if spec.category == "persist" and result.dataloss_risk:
|
||||
result.dataloss_risk = False
|
||||
if result.error and not (result.performance_issue or result.dataloss_risk or result.write_error):
|
||||
result.error = False
|
||||
|
||||
suppress_primary = False
|
||||
if all_core_subs_healthy and all_core_subs_are_mounts:
|
||||
if not result.is_mount_point and not result.error and not result.write_error:
|
||||
suppress_primary = True
|
||||
|
||||
if suppress_primary:
|
||||
# All sub-paths are healthy and mounted; suppress the aggregate row.
|
||||
continue
|
||||
|
||||
if sub_mount_points and result.is_mount_point:
|
||||
result.error = True
|
||||
if result.category == "persist":
|
||||
result.dataloss_risk = True
|
||||
elif result.category == "ramdisk":
|
||||
result.performance_issue = True
|
||||
|
||||
final_results.append(result)
|
||||
|
||||
return final_results
|
||||
|
||||
|
||||
def main():
|
||||
NON_PERSISTENT_FSTYPES = {"tmpfs", "ramfs"}
|
||||
|
||||
active_specs = _get_active_specs()
|
||||
|
||||
mounted_filesystems = get_mount_info()
|
||||
if mounted_filesystems is None:
|
||||
sys.exit(1)
|
||||
|
||||
results = []
|
||||
has_issues = False
|
||||
has_write_errors = 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
|
||||
results_map: dict[str, MountCheckResult] = {}
|
||||
for spec in active_specs:
|
||||
results_map[spec.var_name] = analyze_path(
|
||||
spec,
|
||||
mounted_filesystems,
|
||||
NON_PERSISTENT_FSTYPES,
|
||||
)
|
||||
if result.dataloss_risk or result.error or result.write_error or result.performance_issue:
|
||||
has_issues = True
|
||||
if result.write_error:
|
||||
has_write_errors = True
|
||||
results.append(result)
|
||||
|
||||
results = _apply_primary_rules(active_specs, results_map)
|
||||
|
||||
has_issues = any(
|
||||
r.dataloss_risk or r.error or r.write_error or r.performance_issue
|
||||
for r in results
|
||||
)
|
||||
has_write_errors = any(r.write_error for r in results)
|
||||
|
||||
if has_issues or True: # Always print table for diagnostic purposes
|
||||
# --- 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
|
||||
BLANK_SYMBOL = "➖"
|
||||
|
||||
def bool_to_check(is_good):
|
||||
return 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)))
|
||||
col_widths[0] = max(col_widths[0], len(str(r.path)))
|
||||
|
||||
header_fmt = (
|
||||
f" {{:<{col_widths[0]}}} |"
|
||||
@@ -186,7 +328,7 @@ def main():
|
||||
f" {{:^{col_widths[4]}}} |"
|
||||
f" {{:^{col_widths[5]}}} "
|
||||
)
|
||||
|
||||
|
||||
row_fmt = (
|
||||
f" {{:<{col_widths[0]}}} |"
|
||||
f" {{:^{col_widths[1]}}}|" # No space
|
||||
@@ -195,59 +337,64 @@ def main():
|
||||
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) + "+" +
|
||||
|
||||
separator = "".join([
|
||||
"-" * (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 ---
|
||||
# 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:
|
||||
mount_symbol = CHECK_SYMBOL if r.is_mounted else CROSS_SYMBOL
|
||||
|
||||
if r.category == "persist":
|
||||
if r.underlying_fs_is_ramdisk or r.is_ramdisk:
|
||||
ramdisk_symbol = CROSS_SYMBOL
|
||||
else:
|
||||
ramdisk_symbol = BLANK_SYMBOL
|
||||
perf_symbol = BLANK_SYMBOL
|
||||
else:
|
||||
elif r.category == "ramdisk":
|
||||
ramdisk_symbol = CHECK_SYMBOL if r.is_ramdisk else CROSS_SYMBOL
|
||||
perf_symbol = bool_to_check(not r.performance_issue)
|
||||
|
||||
else:
|
||||
ramdisk_symbol = BLANK_SYMBOL
|
||||
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(
|
||||
row_fmt.format(
|
||||
r.path,
|
||||
write_symbol,
|
||||
mount_symbol,
|
||||
ramdisk_symbol,
|
||||
perf_symbol,
|
||||
dataloss_symbol,
|
||||
)
|
||||
)
|
||||
|
||||
# --- Print Warning ---
|
||||
if has_issues:
|
||||
print("\n", file=sys.stderr)
|
||||
print_warning_message()
|
||||
|
||||
|
||||
# Exit with error only if there are write permission issues
|
||||
if has_write_errors and os.environ.get("NETALERTX_DEBUG") != "1":
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -3,6 +3,43 @@
|
||||
# These must exist before services start to avoid permission/write errors
|
||||
|
||||
check_mandatory_folders() {
|
||||
# Base volatile directories live on /tmp mounts and must always exist
|
||||
if [ ! -d "${NETALERTX_LOG}" ]; then
|
||||
echo " * Creating NetAlertX log directory."
|
||||
if ! mkdir -p "${NETALERTX_LOG}"; then
|
||||
echo "Error: Failed to create log directory: ${NETALERTX_LOG}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${NETALERTX_LOG}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ ! -d "${NETALERTX_API}" ]; then
|
||||
echo " * Creating NetAlertX API cache."
|
||||
if ! mkdir -p "${NETALERTX_API}"; then
|
||||
echo "Error: Failed to create API cache directory: ${NETALERTX_API}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${NETALERTX_API}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ ! -d "${SYSTEM_SERVICES_RUN}" ]; then
|
||||
echo " * Creating System services runtime directory."
|
||||
if ! mkdir -p "${SYSTEM_SERVICES_RUN}"; then
|
||||
echo "Error: Failed to create System services runtime directory: ${SYSTEM_SERVICES_RUN}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${SYSTEM_SERVICES_RUN}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ ! -d "${SYSTEM_SERVICES_ACTIVE_CONFIG}" ]; then
|
||||
echo " * Creating nginx active configuration directory."
|
||||
if ! mkdir -p "${SYSTEM_SERVICES_ACTIVE_CONFIG}"; then
|
||||
echo "Error: Failed to create nginx active configuration directory: ${SYSTEM_SERVICES_ACTIVE_CONFIG}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${SYSTEM_SERVICES_ACTIVE_CONFIG}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check and create plugins log directory
|
||||
if [ ! -d "${NETALERTX_PLUGINS_LOG}" ]; then
|
||||
echo " * Creating Plugins log."
|
||||
@@ -10,6 +47,7 @@ check_mandatory_folders() {
|
||||
echo "Error: Failed to create plugins log directory: ${NETALERTX_PLUGINS_LOG}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${NETALERTX_PLUGINS_LOG}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check and create system services run log directory
|
||||
@@ -19,6 +57,7 @@ check_mandatory_folders() {
|
||||
echo "Error: Failed to create system services run log directory: ${SYSTEM_SERVICES_RUN_LOG}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${SYSTEM_SERVICES_RUN_LOG}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check and create system services run tmp directory
|
||||
@@ -28,6 +67,7 @@ check_mandatory_folders() {
|
||||
echo "Error: Failed to create system services run tmp directory: ${SYSTEM_SERVICES_RUN_TMP}"
|
||||
return 1
|
||||
fi
|
||||
chmod 700 "${SYSTEM_SERVICES_RUN_TMP}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check and create DB locked log file
|
||||
|
||||
@@ -57,7 +57,7 @@ EOF
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: Write permission denied.
|
||||
⚠️ ATTENTION: Read permission denied (write permission denied).
|
||||
|
||||
The application cannot write to "${path}". This will prevent it from
|
||||
saving data, logs, or configuration.
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
# excessive-capabilities.sh checks that no more than the necessary
|
||||
# NET_ADMIN NET_BIND_SERVICE and NET_RAW capabilities are present.
|
||||
|
||||
|
||||
# if we are running in devcontainer then we should exit imemditely without checking
|
||||
# The devcontainer is set up to have additional permissions which are not granted
|
||||
# in production so this check would always fail there.
|
||||
if [ "${NETALERTX_DEBUG}" == "1" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get bounding capabilities from /proc/self/status (what can be acquired)
|
||||
BND_HEX=$(grep '^CapBnd:' /proc/self/status 2>/dev/null | awk '{print $2}' | tr -d '\t')
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#!/bin/bash
|
||||
# read-only-mode.sh detects and warns if running read-write on the root filesystem.
|
||||
|
||||
# This check is skipped in devcontainer mode as the devcontainer is not set up to run
|
||||
# read-only and this would always trigger a warning. RW is required for development
|
||||
# in the devcontainer.
|
||||
if [ "${NETALERTX_DEBUG}" == "1" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if the root filesystem is mounted as read-only
|
||||
if ! awk '$2 == "/" && $4 ~ /ro/ {found=1} END {exit !found}' /proc/mounts; then
|
||||
cat <<EOF
|
||||
|
||||
Reference in New Issue
Block a user