Compare commits

...

23 Commits

Author SHA1 Message Date
jokob-sk
3d17dc47b5 BE: ensure /db - better error #1327
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-04 10:22:34 +11:00
jokob-sk
ef2e7886c4 BE: ensure /db - reorder scripts #1327
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-04 09:57:46 +11:00
jokob-sk
c8f3a84b92 BE: ensure /db and /config dirs - reorder scripts #1327
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 20:56:42 +11:00
jokob-sk
9688fee2d2 BE: ensure /db and /config dirs #1327
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 20:18:39 +11:00
jokob-sk
2dcd9eda19 BE: re-implement APP_CONF_OVERRIDE support
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 19:35:45 +11:00
jokob-sk
24187495e1 BE: debug - removal of GRAPHQL PORT conflict check
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 18:46:42 +11:00
jokob-sk
c27d25d4ab DOCS: ip flipping docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 18:06:59 +11:00
jokob-sk
93a2dad2eb DOCS: pihole guide docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 17:59:30 +11:00
jokob-sk
b235863644 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-12-03 13:03:05 +11:00
jokob-sk
f387f8c5b6 DOCS: installation docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-03 13:02:36 +11:00
mid
5af760f5ee Translated using Weblate (Japanese)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ja/
2025-12-01 10:00:26 +01:00
jokob-sk
d93a3981fa DOCS: migration docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 19:32:55 +11:00
jokob-sk
fbb4a2f8b4 BE: added /auth endpoint
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 09:24:44 +11:00
jokob-sk
54bce6505b PLG: SNMPDSC Fortinet support #1324
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 09:11:23 +11:00
jokob-sk
6da47cc830 DOCS: migration docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 08:32:22 +11:00
jokob-sk
9cabbf3622 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-12-01 08:03:28 +11:00
jokob-sk
6c28a08bee FE: YYYY-DD-MM timestamp handling #1312
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-12-01 08:03:13 +11:00
Sylvain Pichon
86e3decd4e Translated using Weblate (French)
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2025-11-30 08:01:30 +00:00
Safeguard
e14e0bb9e8 Translated using Weblate (Russian)
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/
2025-11-30 08:01:28 +00:00
mid
b6023d1373 Translated using Weblate (Japanese)
Currently translated at 88.8% (678 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/ja/
2025-11-30 08:01:24 +00:00
Максим Горпиніч
1812cc8ef8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (763 of 763 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/uk/
2025-11-30 08:00:21 +00:00
jokob-sk
5df39f984a BE: docker version github action work #1320
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
Code checks / lint (push) Has been cancelled
Code checks / docker-tests (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 12:00:18 +11:00
jokob-sk
d007ed711a BE: docker version github action work #1320
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:58:11 +11:00
20 changed files with 1161 additions and 901 deletions

View File

@@ -47,6 +47,12 @@ jobs:
id: get_version
run: echo "version=Dev" >> $GITHUB_OUTPUT
# --- debug output
- name: Debug version
run: |
echo "GITHUB_REF: $GITHUB_REF"
echo "Version: '${{ steps.get_version.outputs.version }}'"
# --- Write the timestamped version to .VERSION file
- name: Create .VERSION file
run: echo "${{ steps.timestamp.outputs.version }}" > .VERSION

View File

@@ -49,9 +49,17 @@ jobs:
id: get_version
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
# --- debug output
- name: Debug version
run: |
echo "GITHUB_REF: $GITHUB_REF"
echo "Version: '${{ steps.get_version.outputs.version }}'"
echo "Version prev: '${{ steps.get_version_prev.outputs.version }}'"
# --- Write version to .VERSION file
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" > .VERSION
run: echo -n "${{ steps.get_version.outputs.version }}" > .VERSION
# --- Generate Docker metadata and tags
- name: Docker meta

View File

@@ -112,3 +112,11 @@ Slowness can be caused by:
> See [Performance Tips](./PERFORMANCE.md) for detailed optimization steps.
#### IP flipping
With `ARPSCAN` scans some devices might flip IP addresses after each scan triggering false notifications. This is because some devices respond to broadcast calls and thus different IPs after scans are logged.
See how to prevent IP flipping in the [ARPSCAN plugin guide](/front/plugins/arp_scan/README.md).
Alternatively adjust your [notification settings](./NOTIFICATIONS.md) to prevent false positives by filtering out events or devices.

View File

@@ -61,20 +61,38 @@ See alternative [docked-compose examples](https://github.com/jokob-sk/NetAlertX/
| Required | Path | Description |
| :------------- | :------------- | :-------------|
| ✅ | `:/data` | Folder which will contain the `/db/app.db`, `/config/app.conf` & `/config/devices.csv` ([read about devices.csv](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEVICES_BULK_EDITING.md)) files |
| ✅ | `/etc/localtime:/etc/localtime:ro` | Ensuring the timezone is teh same as on teh server. |
| ✅ | `:/data` | Folder which needs to contain a `/db` and `/config` sub-folders. |
| ✅ | `/etc/localtime:/etc/localtime:ro` | Ensuring the timezone is the same as on the server. |
| | `:/tmp/log` | Logs folder useful for debugging if you have issues setting up the container |
| | `:/tmp/api` | The [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). |
> Use separate `db` and `config` directories, do not nest them.
### Folder structure
Use separate `db` and `config` directories, do not nest them:
```
data
├── config
└── db
```
### Permissions
If you are facing permissions issues run the following commands on your server. This will change the owner and assure sufficient access to the database and config files that are stored in the `/local_data_dir/db` and `/local_data_dir/config` folders (replace `local_data_dir` with the location where your `/db` and `/config` folders are located).
```bash
sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir
```
### 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 `/data/config/` folder directly
#### Setting up scanners
You have to specify which network(s) should be scanned. This is done by entering subnets that are accessible from the host. If you use the default `ARPSCAN` plugin, you have to specify at least one valid subnet and interface in the `SCAN_SUBNETS` setting. See the documentation on [How to set up multiple SUBNETS, VLANs and what are limitations](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for troubleshooting and more advanced scenarios.

View File

@@ -239,29 +239,7 @@ services:
4. Start the container and verify everything works as expected.
5. Stop the container.
6. Perform a one-off migration to the latest `netalertx` image and `20211` user:
> [!NOTE]
> The example below assumes your `/config` and `/db` folders are stored in `local_data_dir`.
> Replace this path with your actual configuration directory. `netalertx` is the container name, which might differ from your setup.
```sh
docker run -it --rm --name netalertx --user "0" \
-v /local_data_dir/config:/data/config \
-v /local_data_dir/db:/data/db \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
ghcr.io/jokob-sk/netalertx:latest
```
...or alternatively execute:
```bash
sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir
```
7. Stop the container
8. Update the `docker-compose.yml` as per example below.
6. Update the `docker-compose.yml` as per example below.
```yaml
services:
@@ -288,5 +266,34 @@ services:
- "/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime"
# 🆕 New "tmpfs" section END 🔼
```
7. Perform a one-off migration to the latest `netalertx` image and `20211` user.
9. Start the container and verify everything works as expected.
> [!NOTE]
> The examples below assumes your `/config` and `/db` folders are stored in `local_data_dir`.
> Replace this path with your actual configuration directory. `netalertx` is the container name, which might differ from your setup.
**Automated approach**:
Run the container with the `--user "0"` parameter. Please note, some systems will require the manual approach below.
```sh
docker run -it --rm --name netalertx --user "0" \
-v /local_data_dir/config:/app/config \
-v /local_data_dir/db:/app/db \
-v /local_data_dir:/data \
--tmpfs /tmp:uid=20211,gid=20211,mode=1700 \
ghcr.io/jokob-sk/netalertx:latest
```
Stop the container and run it as you would normally.
**Manual approach**:
Use the manual approach if the Automated approach fails. Execute the below commands:
```bash
sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir
```
8. Start the container and verify everything works as expected.

View File

@@ -1,8 +1,29 @@
# Integration with PiHole
NetAlertX comes with 2 plugins suitable for integrating with your existing PiHole instance. One plugin is using a direct SQLite DB connection, the other leverages the DHCP.leases file generated by PiHole. You can combine both approaches and also supplement it with other [plugins](/docs/PLUGINS.md).
NetAlertX comes with 3 plugins suitable for integrating with your existing PiHole instance. The first plugin uses the v6 API, the second plugin is using a direct SQLite DB connection, the other leverages the `DHCP.leases` file generated by PiHole. You can combine multiple approaches and also supplement scans with other [plugins](/docs/PLUGINS.md).
## Approach 1: `DHCPLSS` Plugin - Import devices from the PiHole DHCP leases file
## Approach 1: `PIHOLEAPI` Plugin - Import devices directly from PiHole v6 API
![PIHOLEAPI sample settings](./img/PIHOLE_GUIDE/PIHOLEAPI_settings.png)
To use this approach make sure the Web UI password in **Pi-hole** is set.
| Setting | Description | Recommended value |
| :------------- | :------------- | :-------------|
| `PIHOLEAPI_URL` | Your Pi-hole base URL including port. | `http://192.168.1.82:9880/` |
| `PIHOLEAPI_RUN_SCHD` | If you run multiple device scanner plugins, align the schedules of all plugins to the same value. | `*/5 * * * *` |
| `PIHOLEAPI_PASSWORD` | The Web UI base64 encoded (en-/decoding handled by the app) admin password. | `passw0rd` |
| `PIHOLEAPI_SSL_VERIFY` | Whether to verify HTTPS certificates. Disable only for self-signed certificates. | `False` |
| `PIHOLEAPI_API_MAXCLIENTS` | Maximum number of devices to request from Pi-hole. Defaults are usually fine. | `500` |
| `PIHOLEAPI_FAKE_MAC` | Generate FAKE MAC from IP. | `False` |
Check the [PiHole API plugin readme](https://github.com/jokob-sk/NetAlertX/tree/main/front/plugins/pihole_api_scan/) for details and troubleshooting.
### docker-compose changes
No changes needed
## Approach 2: `DHCPLSS` Plugin - Import devices from the PiHole DHCP leases file
![DHCPLSS sample settings](./img/PIHOLE_GUIDE/DHCPLSS_pihole_settings.png)
@@ -23,7 +44,7 @@ Check the [DHCPLSS plugin readme](https://github.com/jokob-sk/NetAlertX/tree/mai
| `:/etc/pihole/dhcp.leases` | PiHole's `dhcp.leases` file. Required if you want to use PiHole `dhcp.leases` file. This has to be matched with a corresponding `DHCPLSS_paths_to_check` setting entry (the path in the container must contain `pihole`) |
## Approach 2: `PIHOLE` Plugin - Import devices directly from the PiHole database
## Approach 3: `PIHOLE` Plugin - Import devices directly from the PiHole database
![DHCPLSS sample settings](./img/PIHOLE_GUIDE/PIHOLE_settings.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -390,12 +390,18 @@ function localizeTimestamp(input) {
}
// 2. European DD/MM/YYYY
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?$/);
if (match) {
let [, d, m, y, t = "00:00:00", tzPart = ""] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
const dNum = parseInt(d, 10);
const mNum = parseInt(m, 10);
if (dNum <= 12 && mNum > 12) {
} else {
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5 ? t + ":00" : t}${tzPart}`;
return formatSafe(iso, tz);
}
}
// 3. US MM/DD/YYYY
match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);

2
front/php/templates/language/fr_fr.json Executable file → Normal file
View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Filtrer",
"Gen_Generate": "Générer",
"Gen_InvalidMac": "Adresse MAC invalide.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "Une valeur invalide a été renseignée",
"Gen_LockedDB": "Erreur - La base de données est peut-être verrouillée - Vérifier avec les outils de dév via F12 -> Console ou essayer plus tard.",
"Gen_NetworkMask": "Masque réseau",
"Gen_Offline": "Hors ligne",

File diff suppressed because it is too large Load Diff

View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Фильтр",
"Gen_Generate": "Генерировать",
"Gen_InvalidMac": "Неверный Mac-адрес.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "Введено некорректное значение",
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
"Gen_NetworkMask": "Маска сети",
"Gen_Offline": "Оффлайн",

2
front/php/templates/language/uk_ua.json Executable file → Normal file
View File

@@ -311,7 +311,7 @@
"Gen_Filter": "Фільтр",
"Gen_Generate": "Генерувати",
"Gen_InvalidMac": "Недійсна Mac-адреса.",
"Gen_Invalid_Value": "",
"Gen_Invalid_Value": "Введено недійсне значення",
"Gen_LockedDB": "ПОМИЛКА БД може бути заблоковано перевірте F12 Інструменти розробника -> Консоль або спробуйте пізніше.",
"Gen_NetworkMask": "Маска мережі",
"Gen_Offline": "Офлайн",

View File

@@ -14,6 +14,14 @@ Specify the following settings in the Settings section of NetAlertX:
If unsure, please check [snmpwalk examples](https://www.comparitech.com/net-admin/snmpwalk-examples-windows-linux/).
Supported output formats:
```
ipNetToMediaPhysAddress[3][192.168.1.9] 6C:6C:6C:6C:6C:b6C1
IP-MIB::ipNetToMediaPhysAddress.17.10.10.3.202 = STRING: f8:81:1a:ef:ef:ef
mib-2.3.1.1.2.15.1.192.168.1.14 "2C F4 32 18 61 43 "
```
### Setup Cisco IOS
Enable IOS SNMP service and restrict to selected (internal) IP/Subnet.

View File

@@ -30,7 +30,7 @@ RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
def main():
mylog('verbose', ['[SNMPDSC] In script '])
mylog('verbose', f"[{pluginName}] In script ")
# init global variables
global snmpWalkCmds
@@ -57,7 +57,7 @@ def main():
commands = [snmpWalkCmds]
for cmd in commands:
mylog('verbose', ['[SNMPDSC] Router snmpwalk command: ', cmd])
mylog('verbose', [f"[{pluginName}] Router snmpwalk command: ", cmd])
# split the string, remove white spaces around each item, and exclude any empty strings
snmpwalkArgs = [arg.strip() for arg in cmd.split(' ') if arg.strip()]
@@ -72,7 +72,7 @@ def main():
timeout=(timeoutSetting)
)
mylog('verbose', ['[SNMPDSC] output: ', output])
mylog('verbose', [f"[{pluginName}] output: ", output])
lines = output.split('\n')
@@ -80,6 +80,8 @@ def main():
tmpSplt = line.split('"')
# Expected Format:
# mib-2.3.1.1.2.15.1.192.168.1.14 "2C F4 32 18 61 43 "
if len(tmpSplt) == 3:
ipStr = tmpSplt[0].split('.')[-4:] # Get the last 4 elements to extract the IP
@@ -89,7 +91,7 @@ def main():
macAddress = ':'.join(macStr)
ipAddress = '.'.join(ipStr)
mylog('verbose', [f'[SNMPDSC] IP: {ipAddress} MAC: {macAddress}'])
mylog('verbose', [f"[{pluginName}] IP: {ipAddress} MAC: {macAddress}"])
plugin_objects.add_object(
primaryId = handleEmpty(macAddress),
@@ -100,8 +102,40 @@ def main():
foreignKey = handleEmpty(macAddress) # Use the primary ID as the foreign key
)
else:
mylog('verbose', ['[SNMPDSC] ipStr does not seem to contain a valid IP:', ipStr])
mylog('verbose', [f"[{pluginName}] ipStr does not seem to contain a valid IP:", ipStr])
# Expected Format:
# IP-MIB::ipNetToMediaPhysAddress.17.10.10.3.202 = STRING: f8:81:1a:ef:ef:ef
elif "ipNetToMediaPhysAddress" in line and "=" in line and "STRING:" in line:
# Split on "=" → ["IP-MIB::ipNetToMediaPhysAddress.xxx.xxx.xxx.xxx ", " STRING: aa:bb:cc:dd:ee:ff"]
left, right = line.split("=", 1)
# Extract the MAC (right side)
macAddress = right.split("STRING:")[-1].strip()
macAddress = normalize_mac(macAddress)
# Extract IP address from the left side
# tail of the OID: last 4 integers = IPv4 address
oid_parts = left.strip().split('.')
ip_parts = oid_parts[-4:]
ipAddress = ".".join(ip_parts)
mylog('verbose', [f"[{pluginName}] (fallback) IP: {ipAddress} MAC: {macAddress}"])
plugin_objects.add_object(
primaryId = handleEmpty(macAddress),
secondaryId = handleEmpty(ipAddress),
watched1 = '(unknown)',
watched2 = handleEmpty(snmpwalkArgs[6]),
extra = handleEmpty(line),
foreignKey = handleEmpty(macAddress)
)
continue
# Expected Format:
# ipNetToMediaPhysAddress[3][192.168.1.9] 6C:6C:6C:6C:6C:b6C1
elif line.startswith('ipNetToMediaPhysAddress'):
# Format: snmpwalk -OXsq output
parts = line.split()
@@ -110,7 +144,7 @@ def main():
ipAddress = parts[0].split('[')[-1][:-1]
macAddress = normalize_mac(parts[1])
mylog('verbose', [f'[SNMPDSC] IP: {ipAddress} MAC: {macAddress}'])
mylog('verbose', [f"[{pluginName}] IP: {ipAddress} MAC: {macAddress}"])
plugin_objects.add_object(
primaryId = handleEmpty(macAddress),
@@ -121,7 +155,7 @@ def main():
foreignKey = handleEmpty(macAddress)
)
mylog('verbose', ['[SNMPDSC] Entries found: ', len(plugin_objects)])
mylog('verbose', [f"[{pluginName}] Entries found: ", len(plugin_objects)])
plugin_objects.write_result_file()

View File

View File

@@ -1,32 +1,57 @@
#!/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.
# Ensures the database exists, or creates a new one on first run.
# Intended to run only at initial startup.
# 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"
set -eu
YELLOW=$(printf '\033[1;33m')
CYAN=$(printf '\033[1;36m')
RED=$(printf '\033[1;31m')
RESET=$(printf '\033[0m')
# Ensure DB folder exists
if [ ! -d "${NETALERTX_DB}" ]; then
if ! mkdir -p "${NETALERTX_DB}"; then
>&2 printf "%s" "${RED}"
>&2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
❌ Error creating DB folder in: ${NETALERTX_DB}
A database directory is required for proper operation, however there appear to be
insufficient permissions on this mount or it is otherwise inaccessible.
More info: https://github.com/jokob-sk/NetAlertX/blob/main/docs/FILE_PERMISSIONS.md
══════════════════════════════════════════════════════════════════════════════
EOF
>&2 printf "%s" "${RESET}"
exit 1
fi
# Otherwise, if the db exists, exit.
elif [ -f "${NETALERTX_DB_FILE}" ]; then
chmod 700 "${NETALERTX_DB}" 2>/dev/null || true
fi
# Fresh rebuild requested
if [ "${ALWAYS_FRESH_INSTALL:-false}" = "true" ] && [ -f "${NETALERTX_DB_FILE}" ]; then
>&2 echo "INFO: ALWAYS_FRESH_INSTALL enabled — removing existing database."
rm -f "${NETALERTX_DB_FILE}" "${NETALERTX_DB_FILE}-shm" "${NETALERTX_DB_FILE}-wal"
fi
# If file exists now, nothing to do
if [ -f "${NETALERTX_DB_FILE}" ]; then
exit 0
fi
CYAN=$(printf '\033[1;36m')
RESET=$(printf '\033[0m')
>&2 printf "%s" "${CYAN}"
>&2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
🆕 First run detected. Building initial database schema in ${NETALERTX_DB_FILE}.
🆕 First run detected — building initial database at: ${NETALERTX_DB_FILE}
Do not interrupt this step. Once complete, consider backing up the fresh
database before onboarding sensitive networks.
Do not interrupt this step. When complete, consider backing up the fresh
DB before onboarding sensitive or critical 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);

View File

@@ -0,0 +1,35 @@
#!/bin/sh
# override-config.sh - Handles APP_CONF_OVERRIDE environment variable
OVERRIDE_FILE="${NETALERTX_CONFIG}/app_conf_override.json"
# Ensure config directory exists
mkdir -p "$(dirname "$NETALERTX_CONFIG")" || {
>&2 echo "ERROR: Failed to create config directory $(dirname "$NETALERTX_CONFIG")"
exit 1
}
# Remove old override file if it exists
rm -f "$OVERRIDE_FILE"
# Check if APP_CONF_OVERRIDE is set
if [ -z "$APP_CONF_OVERRIDE" ]; then
>&2 echo "APP_CONF_OVERRIDE is not set. Skipping override config file creation."
else
# Save the APP_CONF_OVERRIDE env variable as a JSON file
echo "$APP_CONF_OVERRIDE" > "$OVERRIDE_FILE" || {
>&2 echo "ERROR: Failed to write override config to $OVERRIDE_FILE"
exit 2
}
RESET=$(printf '\033[0m')
>&2 cat <<EOF
══════════════════════════════════════════════════════════════════════════════
📝 APP_CONF_OVERRIDE detected. Configuration written to $OVERRIDE_FILE.
Make sure the JSON content is correct before starting the application.
══════════════════════════════════════════════════════════════════════════════
EOF
>&2 printf "%s" "${RESET}"
fi

View File

@@ -5,22 +5,22 @@
# Define ports from ENV variables, applying defaults
PORT_APP=${PORT:-20211}
PORT_GQL=${APP_CONF_OVERRIDE:-${GRAPHQL_PORT:-20212}}
# PORT_GQL=${APP_CONF_OVERRIDE:-${GRAPHQL_PORT:-20212}}
# Check if ports are configured to be the same
if [ "$PORT_APP" -eq "$PORT_GQL" ]; then
cat <<EOF
══════════════════════════════════════════════════════════════════════════════
⚠️ Configuration Warning: Both ports are set to ${PORT_APP}.
# # Check if ports are configured to be the same
# if [ "$PORT_APP" -eq "$PORT_GQL" ]; then
# cat <<EOF
# ══════════════════════════════════════════════════════════════════════════════
# ⚠️ Configuration Warning: Both ports are set to ${PORT_APP}.
The Application port (\$PORT) and the GraphQL API port
(\$APP_CONF_OVERRIDE or \$GRAPHQL_PORT) are configured to use the
same port. This will cause a conflict.
# The Application port (\$PORT) and the GraphQL API port
# (\$APP_CONF_OVERRIDE or \$GRAPHQL_PORT) are configured to use the
# same port. This will cause a conflict.
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
══════════════════════════════════════════════════════════════════════════════
EOF
fi
# https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
# ══════════════════════════════════════════════════════════════════════════════
# EOF
# fi
# Check for netstat (usually provided by busybox)
if ! command -v netstat >/dev/null 2>&1; then
@@ -53,17 +53,17 @@ if echo "$LISTENING_PORTS" | grep -q ":${PORT_APP}$"; then
EOF
fi
# Check GraphQL Port
# We add a check to avoid double-warning if ports are identical AND in use
if [ "$PORT_APP" -ne "$PORT_GQL" ] && echo "$LISTENING_PORTS" | grep -q ":${PORT_GQL}$"; then
cat <<EOF
══════════════════════════════════════════════════════════════════════════════
⚠️ Port Warning: GraphQL API port ${PORT_GQL} is already in use.
# # Check GraphQL Port
# # We add a check to avoid double-warning if ports are identical AND in use
# if [ "$PORT_APP" -ne "$PORT_GQL" ] && echo "$LISTENING_PORTS" | grep -q ":${PORT_GQL}$"; then
# cat <<EOF
# ══════════════════════════════════════════════════════════════════════════════
# ⚠️ Port Warning: GraphQL API port ${PORT_GQL} is already in use.
The GraphQL API (defined by \$APP_CONF_OVERRIDE or \$GRAPHQL_PORT)
may fail to start.
# The GraphQL API (defined by \$APP_CONF_OVERRIDE or \$GRAPHQL_PORT)
# may fail to start.
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
══════════════════════════════════════════════════════════════════════════════
EOF
fi
# https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/port-conflicts.md
# ══════════════════════════════════════════════════════════════════════════════
# EOF
# fi

View File

@@ -87,7 +87,8 @@ CORS(
r"/dbquery/*": {"origins": "*"},
r"/messaging/*": {"origins": "*"},
r"/events/*": {"origins": "*"},
r"/logs/*": {"origins": "*"}
r"/logs/*": {"origins": "*"},
r"/auth/*": {"origins": "*"}
},
supports_credentials=True,
allow_headers=["Authorization", "Content-Type"],
@@ -744,6 +745,23 @@ def sync_endpoint():
return jsonify({"success": False, "message": "ERROR: No allowed", "error": "Method Not Allowed"}), 405
# --------------------------
# Auth endpoint
# --------------------------
@app.route("/auth", methods=["GET"])
def check_auth():
if not is_authorized():
return jsonify({"success": False, "message": "ERROR: Not authorized", "error": "Forbidden"}), 403
elif request.method == "GET":
return jsonify({"success": True, "message": "Authentication check successful"}), 200
else:
msg = "[sync endpoint] Method Not Allowed"
write_notification(msg, "alert")
mylog("verbose", [msg])
return jsonify({"success": False, "message": "ERROR: No allowed", "error": "Method Not Allowed"}), 405
# --------------------------
# Background Server Start
# --------------------------

View File

@@ -0,0 +1,66 @@
# tests/test_auth.py
import sys
import os
import pytest
# Register NetAlertX directories
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from helper import get_setting_value # noqa: E402
from api_server.api_server_start import app # noqa: E402
@pytest.fixture(scope="session")
def api_token():
"""Load API token from system settings (same as other tests)."""
return get_setting_value("API_TOKEN")
@pytest.fixture
def client():
"""Flask test client."""
with app.test_client() as client:
yield client
def auth_headers(token):
return {"Authorization": f"Bearer {token}"}
# -------------------------
# AUTH ENDPOINT TESTS
# -------------------------
def test_auth_ok(client, api_token):
"""Valid token should allow access."""
resp = client.get("/auth", headers=auth_headers(api_token))
assert resp.status_code == 200
data = resp.get_json()
assert data is not None
assert data.get("success") is True
assert "successful" in data.get("message", "").lower()
def test_auth_missing_token(client):
"""Missing token should be forbidden."""
resp = client.get("/auth")
assert resp.status_code == 403
data = resp.get_json()
assert data is not None
assert data.get("success") is False
assert "not authorized" in data.get("message", "").lower()
def test_auth_invalid_token(client):
"""Invalid bearer token should be forbidden."""
resp = client.get("/auth", headers=auth_headers("INVALID-TOKEN"))
assert resp.status_code == 403
data = resp.get_json()
assert data is not None
assert data.get("success") is False
assert "not authorized" in data.get("message", "").lower()