Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/build-release-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
# without quotes the 3.10 will be interpreted as 3.1 which leads to fail
python-version: ['3.10', 3.11, 3.12, 3.13, 3.14]
python-version: [3.9, '3.10', 3.11, 3.12, 3.13, 3.14]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -296,7 +296,7 @@ jobs:
- run: echo "${{ secrets.NAGSTAMON_REPO_KEY_WEB }}" > ~/.ssh/id_ed25519
- run: chmod -R go-rwx ~/.ssh
# get and prepare nagstamon-jekyll
- run: git clone git@github.com:HenriWahl/nagstamon-jekyll.git
- run: git clone --depth 1 git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.dist }}/${{ env.release }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.dist }}/${{ env.release }}
# somehow weird way to get the hash over the Dockerfile to be aware if it changed
Expand Down Expand Up @@ -351,7 +351,7 @@ jobs:
- run: echo "${{ secrets.NAGSTAMON_REPO_KEY_WEB }}" > ~/.ssh/id_ed25519
- run: chmod -R go-rwx ~/.ssh
# get and prepare nagstamon-jekyll
- run: git clone git@github.com:HenriWahl/nagstamon-jekyll.git
- run: git clone --depth 1 git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
# somehow weird way to get the hash over the Dockerfile to be aware if it changed
Expand Down Expand Up @@ -397,7 +397,7 @@ jobs:
- run: echo "${{ secrets.NAGSTAMON_REPO_KEY_WEB }}" > ~/.ssh/id_ed25519
- run: chmod -R go-rwx ~/.ssh
# get and prepare nagstamon-jekyll
- run: git clone git@github.com:HenriWahl/nagstamon-jekyll.git
- run: git clone --depth 1 git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
# somehow weird way to get the hash over the Dockerfile to be aware if it changed
Expand Down
46 changes: 26 additions & 20 deletions .github/workflows/build-release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
# without quotes the 3.10 will be interpreted as 3.1 which leads to fail
python-version: ['3.10', 3.11, 3.12, 3.13, 3.14]
python-version: [3.9, '3.10', 3.11, 3.12, 3.13, 3.14]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -292,7 +292,7 @@ jobs:
- run: echo "${{ secrets.NAGSTAMON_REPO_KEY_WEB }}" > ~/.ssh/id_ed25519
- run: chmod -R go-rwx ~/.ssh
# get and prepare nagstamon-jekyll
- run: git clone git@github.com:HenriWahl/nagstamon-jekyll.git
- run: git clone --depth 1 git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.dist }}/${{ env.release }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.dist }}/${{ env.release }}
# somehow weird way to get the hash over the Dockerfile to be aware if it changed
Expand Down Expand Up @@ -347,7 +347,7 @@ jobs:
- run: echo "${{ secrets.NAGSTAMON_REPO_KEY_WEB }}" > ~/.ssh/id_ed25519
- run: chmod -R go-rwx ~/.ssh
# get and prepare nagstamon-jekyll
- run: git clone git@github.com:HenriWahl/nagstamon-jekyll.git
- run: git clone --depth 1 git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
# somehow weird way to get the hash over the Dockerfile to be aware if it changed
Expand All @@ -359,13 +359,16 @@ jobs:
- run: docker push ${{ env.cr_image }}-${{ env.family }}-${{ env.version_latest }}:${{ steps.dockerfile_hash.outputs.HASH }}
# copy *.rpm files into nagstamon-jekyll and create repodata
- run: |
version=${{ env.release }} && \
mkdir -p mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${version} && \
cp -r artifact/*.${{ env.family }}* ${{ env.repo_dir }}/${{ env.family }}/${version} && \
docker run --rm -v ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}:/repo \
${{ env.cr_image }}-${{ env.family }}-${{ env.version_latest }}:${{ steps.dockerfile_hash.outputs.HASH }} \
/bin/bash -c "createrepo --verbose --workers 1 /repo" && \
ls -laR ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}
for noarch_rpm in artifact/*${{ env.family }}*.noarch.rpm; \
do \
version=$(echo ${noarch_rpm} | python3 -c "file=input(); print(file.split('${{ env.family }}')[1].split('.')[0])") && \
mkdir -p mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${version} && \
cp -r artifact/*.${{ env.family }}${version}* ${{ env.repo_dir }}/${{ env.family }}/${version} && \
docker run --rm -v ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}:/repo \
${{ env.cr_image }}-${{ env.family }}-${{ env.version_latest }}:${{ steps.dockerfile_hash.outputs.HASH }} \
/bin/bash -c "createrepo --verbose --workers 1 /repo" && \
ls -laR ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}; \
done
# commit and push new binaries to nagstamon-repo
- run: git config --global user.email "repo@nagstamon.de" && git config --global user.name "Nagstamon Repository"
- run: cd ${{ env.repo_dir }} && git pull && git add . && git commit -am "new latest repo ${{ env.family }}" && git push
Expand Down Expand Up @@ -393,21 +396,24 @@ jobs:
- run: echo "${{ secrets.NAGSTAMON_REPO_KEY_WEB }}" > ~/.ssh/id_ed25519
- run: chmod -R go-rwx ~/.ssh
# get and prepare nagstamon-jekyll
- run: git clone git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.release }}
- run: git clone --depth 1 git@github.com:HenriWahl/nagstamon-jekyll.git
- run: rm -rf ${{ env.repo_dir }}/${{ env.family }}/${{ env.version }}
- run: mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${{ env.version }}
# somehow weird way to get the hash over the Dockerfile to be aware if it changed
- id: dockerfile_hash
run: echo "HASH=$(md5sum build/docker/Dockerfile-${{ env.family }}-${{ env.version }} | cut -d\ -f1)" >> $GITHUB_OUTPUT
# copy *.rpm files into nagstamon-jekyll and create repodata
- run: |
version=${{ env.release }} && \
mkdir -p mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${version} && \
cp -r artifact/*.${{ env.family }}* ${{ env.repo_dir }}/${{ env.family }}/${version} && \
docker run --rm -v ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}:/repo \
${{ env.cr_image }}-${{ env.family }}-${{ env.version }}:${{ steps.dockerfile_hash.outputs.HASH }} \
/bin/bash -c "createrepo --verbose --workers 1 /repo" && \
ls -laR ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}
for noarch_rpm in artifact/*${{ env.family }}*.noarch.rpm; \
do \
version=$(echo ${noarch_rpm} | python3 -c "file=input(); print(file.split('${{ env.family }}')[1].split('.')[0])") && \
mkdir -p mkdir -p ${{ env.repo_dir }}/${{ env.family }}/${version} && \
cp -r artifact/*.${{ env.family }}${version}* ${{ env.repo_dir }}/${{ env.family }}/${version} && \
docker run --rm -v ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}:/repo \
${{ env.cr_image }}-${{ env.family }}-${{ env.version }}:${{ steps.dockerfile_hash.outputs.HASH }} \
/bin/bash -c "createrepo --verbose --workers 1 /repo" && \
ls -laR ${PWD}/${{ env.repo_dir }}/${{ env.family }}/${version}; \
done
# commit and push new binaries to nagstamon-repo
- run: git config --global user.email "repo@nagstamon.de" && git config --global user.name "Nagstamon Repository"
- run: cd ${{ env.repo_dir }} && git pull && git add . && git commit -am "new latest repo ${{ env.family }}" && git push
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand Down
5 changes: 3 additions & 2 deletions Nagstamon/cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import requests
import sqlite3
from time import time
from typing import Optional, Union

from cryptography.fernet import (Fernet,
InvalidToken)
Expand Down Expand Up @@ -220,7 +221,7 @@ def has_any_cookie(server_name: str, cookie_name: str) -> bool:
return False


def has_valid_cookie(server_name: str, cookie_name: str, now: int | None = None, skew_seconds: int = 30) -> bool:
def has_valid_cookie(server_name: str, cookie_name: str, now: Union[int, None] = None, skew_seconds: int = 30) -> bool:
"""
Return True if at least one cookie with given name has an expiration in the future.
skew_seconds: small safety window to avoid edge cases at the boundary.
Expand All @@ -242,7 +243,7 @@ def has_valid_cookie(server_name: str, cookie_name: str, now: int | None = None,
return False


def delete_cookie(server_name: str, cookie_name: str | None = None, domain: str | None = None, path: str | None = None) -> int:
def delete_cookie(server_name: str, cookie_name: Optional[str] = None, domain: Optional[str] = None, path: Optional[str] = None) -> int:
"""
Delete cookie rows from SQLite DB. Returns number of deleted rows.
Optional name/domain/path narrow down the deletion.
Expand Down
2 changes: 1 addition & 1 deletion Nagstamon/resources/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Benoît Soenen,<br>
Carl Chenet <a href=https://github.com/chaica>@chaica</a>,<br>
Carl Helmertz <a href=https://github.com/chelmertz>@chelmertz</a>,<br>
Christian <a href=https://github.com/idl0r>@idl0r</a>,<br>
Christian Gierschner <a href=https://github.com/christiangierschner></a>,<br>
Christian Gierschner <a href=https://github.com/christiangierschner>christiangierschner</a>,<br>
Christoph Handel <a href=https://github.com/fragfutter>@fragfutter</a>,<br>
<a href=https://github.com/Coolgeek789>@Coolgeek789</a>,<br>
<a href=https://github.com/cventastic>@cventastic</a>,<br>
Expand Down
Binary file added Nagstamon/resources/nagstamon.1.gz
Binary file not shown.
84 changes: 51 additions & 33 deletions Nagstamon/servers/Alertmanager/alertmanagerserver.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import sys
import json
import re
import time

from datetime import datetime, timedelta

import requests
from datetime import datetime, timedelta, timezone

from Nagstamon.config import conf
from Nagstamon.objects import (GenericHost, Result)
Expand Down Expand Up @@ -61,8 +58,9 @@ def init_http(self):
GenericServer.init_http(self)

# prepare for JSON
self.session.headers.update({'Accept': 'application/json',
'Content-Type': 'application/json'})
if self.session is not None:
self.session.headers.update({'Accept': 'application/json',
'Content-Type': 'application/json'})


def init_config(self):
Expand All @@ -87,7 +85,7 @@ def map_severity(self, the_severity):

Args:
the_severity (str): The severity that should be mapped

Returns:
str: The matched Nagstamon severity
"""
Expand Down Expand Up @@ -266,7 +264,7 @@ def open_monitor_webpage(self, host, service):
"""
webbrowser_open('%s' % (self.monitor_url))

def open_monitor(self, host, service=''):
def open_monitor(self, host, service):
"""
open monitor for alert
"""
Expand All @@ -277,7 +275,13 @@ def open_monitor(self, host, service=''):
def _set_downtime(self, host, service, author, comment, fixed, start_time,
end_time, hours, minutes):

alert = self.hosts[host].services[service]
# Services are keyed by fingerprint internally, but the UI passes display_name.
# Look up the alert by display_name.
alert = next((s for s in self.hosts[host].services.values()
if s.display_name == service), None)
if alert is None:
log.error(f'_set_downtime: service "{service}" not found on host "{host}"')
return

# Convert local dates to UTC
start_time_dt = convert_timestring_to_utc(start_time)
Expand All @@ -298,10 +302,8 @@ def _set_downtime(self, host, service, author, comment, fixed, start_time,
silence_data["comment"] = comment or "Nagstamon downtime"
silence_data["createdBy"] = author or "Nagstamon"


post = requests.post(self.monitor_url + self.API_PATH_SILENCES, json=silence_data)

#silence_id = post.json()["silenceID"]
self.fetch_url(self.monitor_url + self.API_PATH_SILENCES, giveback="raw",
cgi_data=json.dumps(silence_data))


# Overwrite function from generic server to add expire_time value
Expand Down Expand Up @@ -329,25 +331,41 @@ def set_acknowledge(self, info_dict):
info_dict['expire_time'])


def _post_silence(self, alert, author, comment, starts_at, ends_at):
"""Build and POST a silence payload for the given alert object."""
silence_data = {
"matchers": [
{"name": name, "value": value, "isRegex": False}
for name, value in alert.labels.items()
],
"startsAt": starts_at,
"endsAt": ends_at,
"comment": comment or "Nagstamon silence",
"createdBy": author or "Nagstamon",
}
return self.fetch_url(self.monitor_url + self.API_PATH_SILENCES, giveback="raw",
cgi_data=json.dumps(silence_data))

def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent,
all_services=None, expire_time=None):
alert = self.hosts[host].services[service]
ends_at = convert_timestring_to_utc(expire_time)

cgi_data = {}
cgi_data["matchers"] = []
for name, value in alert.labels.items():
cgi_data["matchers"].append({
"name": name,
"value": value,
"isRegex": False
})
cgi_data["startsAt"] = datetime.utcfromtimestamp(time.time()).isoformat()
cgi_data["endsAt"] = ends_at or cgi_data["startAt"]
cgi_data["comment"] = comment or "Nagstamon silence"
cgi_data["createdBy"] = author or "Nagstamon"
cgi_data = json.dumps(cgi_data)

result = self.fetch_url(self.monitor_url + self.API_PATH_SILENCES, giveback="raw",
cgi_data=cgi_data)
return result
# Services are keyed by fingerprint internally, but the UI passes display_name.
# Look up the alert by display_name.
alert = next((s for s in self.hosts[host].services.values()
if s.display_name == service), None)
if alert is None:
log.error(f'_set_acknowledge: service "{service}" not found on host "{host}"')
return

starts_at = datetime.now(timezone.utc).isoformat()
ends_at = convert_timestring_to_utc(expire_time) if expire_time else starts_at

self._post_silence(alert, author, comment, starts_at, ends_at)

if all_services:
for svc_name in all_services:
svc_alert = next((s for s in self.hosts[host].services.values()
if s.display_name == svc_name), None)
if svc_alert is None:
log.error(f'_set_acknowledge: service "{svc_name}" not found on host "{host}"')
continue
self._post_silence(svc_alert, author, comment, starts_at, ends_at)
5 changes: 4 additions & 1 deletion Nagstamon/servers/Alertmanager/alertmanagerservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ class AlertmanagerService(GenericService):
add alertmanager specific service property to generic service class
"""
fingerprint = ""
labels = {}

def __init__(self):
super().__init__()
self.labels = {}

def get_service_name(self):
return self.display_name
Expand Down
19 changes: 13 additions & 6 deletions Nagstamon/servers/Prometheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ class PrometheusService(GenericService):
add Prometheus specific service property to generic service class
"""
service_object_id = ""
labels = {}

def __init__(self):
super().__init__()
self.labels = {}


class PrometheusServer(GenericServer):
Expand All @@ -95,8 +98,9 @@ def init_http(self):
GenericServer.init_http(self)

# prepare for JSON
self.session.headers.update({'Accept': 'application/json',
'Content-Type': 'application/json'})
if self.session is not None:
self.session.headers.update({'Accept': 'application/json',
'Content-Type': 'application/json'})

def init_config(self):
"""
Expand Down Expand Up @@ -149,7 +153,10 @@ def _get_status(self):
try:
result = self.fetch_url(self.monitor_url + self.API_PATH_ALERTS,
giveback="raw")
data = json.loads(result.result)
try:
data = json.loads(result.result)
except json.decoder.JSONDecodeError:
data = {}
error = result.error
status_code = result.status_code

Expand Down Expand Up @@ -194,7 +201,7 @@ def _get_status(self):
service.server = self.name
service.status = severity
service.last_check = "n/a"
service.attempt = alert.get("state", "firirng")
service.attempt = alert.get("state", "firing")
service.duration = str(self._get_duration(alert["activeAt"]))

annotations = alert.get("annotations", {})
Expand All @@ -211,7 +218,7 @@ def _get_status(self):
self.new_hosts[hostname].server = self.name
self.new_hosts[hostname].services[servicename] = service

except:
except Exception:
# set checking flag back to False
self.isChecking = False
result, error = self.error(sys.exc_info())
Expand Down
Loading
Loading