linuxfabrik-lib 4.0.2__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/PKG-INFO +1 -1
  2. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/db_mysql.py +116 -1
  3. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/db_sqlite.py +117 -11
  4. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/disk.py +17 -10
  5. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/endoflifedate.py +105 -71
  6. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/linuxfabrik_lib.egg-info/PKG-INFO +1 -1
  7. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/pyproject.toml +1 -1
  8. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/LICENSE +0 -0
  9. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/README.md +0 -0
  10. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/__init__.py +0 -0
  11. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/args.py +0 -0
  12. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/base.py +0 -0
  13. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/cache.py +0 -0
  14. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/distro.py +0 -0
  15. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/dmidecode.py +0 -0
  16. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/feedparser.py +0 -0
  17. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/globals.py +0 -0
  18. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/grassfish.py +0 -0
  19. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/huawei.py +0 -0
  20. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/human.py +0 -0
  21. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/icinga.py +0 -0
  22. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/infomaniak.py +0 -0
  23. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/jitsi.py +0 -0
  24. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/keycloak.py +0 -0
  25. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/lftest.py +0 -0
  26. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/librenms.py +0 -0
  27. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/linuxfabrik_lib.egg-info/SOURCES.txt +0 -0
  28. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/linuxfabrik_lib.egg-info/dependency_links.txt +0 -0
  29. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/linuxfabrik_lib.egg-info/requires.txt +0 -0
  30. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/linuxfabrik_lib.egg-info/top_level.txt +0 -0
  31. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/net.py +0 -0
  32. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/nextcloud.py +0 -0
  33. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/nodebb.py +0 -0
  34. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/powershell.py +0 -0
  35. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/psutil.py +0 -0
  36. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/qts.py +0 -0
  37. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/redfish.py +0 -0
  38. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/rocket.py +0 -0
  39. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/setup.cfg +0 -0
  40. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/shell.py +0 -0
  41. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/smb.py +0 -0
  42. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/time.py +0 -0
  43. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/txt.py +0 -0
  44. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/uptimerobot.py +0 -0
  45. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/url.py +0 -0
  46. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/veeam.py +0 -0
  47. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/version.py +0 -0
  48. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/wildfly.py +0 -0
  49. {linuxfabrik_lib-4.0.2 → linuxfabrik_lib-4.2.0}/winrm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: linuxfabrik-lib
3
- Version: 4.0.2
3
+ Version: 4.2.0
4
4
  Summary: Python libraries used in various Linuxfabrik projects, including the 'Linuxfabrik Monitoring Plugins' project.
5
5
  Author-email: "Linuxfabrik GmbH, Zurich, Switzerland" <info@linuxfabrik.ch>
6
6
  License: This is free and unencumbered software released into the public domain.
@@ -15,7 +15,7 @@ import warnings
15
15
  warnings.filterwarnings('ignore', category=UserWarning, module='pymysql')
16
16
 
17
17
  __author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
18
- __version__ = '2026051801'
18
+ __version__ = '2026051803'
19
19
 
20
20
  import re
21
21
  import sys
@@ -416,6 +416,25 @@ def get_engines(conn):
416
416
  return engines
417
417
 
418
418
 
419
+ def get_flavor():
420
+ """
421
+ Return the installed MySQL/MariaDB flavor.
422
+
423
+ Thin wrapper around `get_server_info()` for callers that only care
424
+ about the flavor.
425
+
426
+ ### Returns
427
+ - **str | None**: `'mariadb'`, `'mysql'`, or `None` when no binary
428
+ responds.
429
+
430
+ ### Example
431
+ >>> get_flavor()
432
+ 'mariadb'
433
+ """
434
+ info = get_server_info()
435
+ return info['flavor'] if info else None
436
+
437
+
419
438
  def get_replica_status(conn):
420
439
  """
421
440
  Return the first row of `SHOW REPLICA STATUS` (or the legacy
@@ -442,6 +461,102 @@ def get_replica_status(conn):
442
461
  return rows[0]
443
462
 
444
463
 
464
+ # Output formats handled by the regex parser:
465
+ #
466
+ # mysqld Ver 8.0.45 for Linux on x86_64 (Source distribution)
467
+ # mysqld Ver 10.11.15-MariaDB for Linux on x86_64 (MariaDB Server)
468
+ # mysql Ver 14.14 Distrib 5.7.44, for Linux (x86_64) using EditLine wrapper
469
+ # mariadb Ver 15.1 Distrib 10.5.x-MariaDB, for Linux on x86_64
470
+ # mariadb from 11.4.10-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper
471
+ #
472
+ # MariaDB 11.4 dropped the `mysqld` symlink and renamed the client
473
+ # version banner from "Distrib X.Y.Z" to "from X.Y.Z", so the parser
474
+ # probes both server and client binaries and accepts both prefixes.
475
+ _VERSION_REGEXES = (
476
+ # `mysqld Ver X.Y.Z[-MariaDB] for ...` (any distro suffix like
477
+ # `-1~trusty` is stripped further down)
478
+ r'Ver (\d+\.\d+\.\d+(?:-MariaDB)?)',
479
+ # `mysql Ver 14.14 Distrib X.Y.Z[-MariaDB], for ...`
480
+ # `mariadb Ver 15.1 Distrib 10.5.x-MariaDB, for ...`
481
+ r'Distrib (\S+?),',
482
+ # `mariadb from X.Y.Z[-MariaDB], client ...` (MariaDB 11.4+)
483
+ r'from (\d+\.\d+\.\d+(?:-MariaDB)?),',
484
+ )
485
+
486
+
487
+ def _parse_version_banner(banner):
488
+ """Extract `(flavor, version)` from a `mysqld`/`mariadbd`/`mariadb`/
489
+ `mysql` --version banner. Returns `(None, None)` if no regex matches.
490
+ """
491
+ for regex in _VERSION_REGEXES:
492
+ m = re.search(regex, banner)
493
+ if not m:
494
+ continue
495
+ raw = m.group(1).strip()
496
+ flavor = 'mariadb' if '-MariaDB' in raw else 'mysql'
497
+ version = raw.replace('-MariaDB', '')
498
+ return flavor, version
499
+ return None, None
500
+
501
+
502
+ def get_server_info(banner=None):
503
+ """
504
+ Determine the installed MySQL/MariaDB flavor and version. Does not
505
+ require a database connection.
506
+
507
+ Returns a dict like `{'flavor': 'mariadb', 'version': '10.11.16'}`,
508
+ or `None` when nothing matches.
509
+
510
+ If `banner` is `None`, probe `mysqld`, `mariadbd`, `mariadb`, `mysql`
511
+ in order and parse the first responding --version banner. Server
512
+ binaries are tried first; the client fallback exists because MariaDB
513
+ 11.4 dropped the `mysqld` symlink and renamed the client.
514
+
515
+ Pass a banner string directly (for example a unit-test fixture) to
516
+ skip the shell probe entirely.
517
+
518
+ Useful where the systemd-based distinction is unreliable: on Fedora
519
+ and RHEL `mysql.service` is aliased to `mariadb.service`, so
520
+ `systemctl is-enabled mysql.service` reports `alias` rather than the
521
+ underlying flavor.
522
+
523
+ ### Parameters
524
+ - **banner** (`str | None`): Optional pre-collected --version banner
525
+ to parse instead of probing.
526
+
527
+ ### Returns
528
+ - **dict | None**: `{'flavor': 'mariadb'|'mysql', 'version': str}`
529
+ or `None`.
530
+
531
+ ### Example
532
+ >>> get_server_info()
533
+ {'flavor': 'mariadb', 'version': '10.11.16'}
534
+ """
535
+ if banner is not None:
536
+ flavor, version = _parse_version_banner(banner)
537
+ if version:
538
+ return {'flavor': flavor, 'version': version}
539
+ return None
540
+
541
+ from . import shell # local import to keep db_mysql usable without shell at module load
542
+ for command in (
543
+ 'mysqld --version',
544
+ 'mariadbd --version',
545
+ 'mariadb --version',
546
+ 'mysql --version',
547
+ ):
548
+ success, result = shell.shell_exec(command)
549
+ if not success:
550
+ continue
551
+ stdout, _, _ = result
552
+ if not stdout:
553
+ continue
554
+ flavor, version = _parse_version_banner(stdout.strip())
555
+ if version:
556
+ return {'flavor': flavor, 'version': version}
557
+ return None
558
+
559
+
445
560
  def has_is_role_column(conn):
446
561
  """
447
562
  Return `True` if `mysql.user.IS_ROLE` exists (MariaDB 10.0.5+ roles).
@@ -28,13 +28,14 @@ This is one typical use case of this library (taken from `disk-io`):
28
28
  """
29
29
 
30
30
  __author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
31
- __version__ = '2026051102'
31
+ __version__ = '2026060102'
32
32
 
33
33
  import csv
34
34
  import hashlib
35
35
  import os
36
36
  import re
37
37
  import sqlite3
38
+ import stat
38
39
 
39
40
  from . import disk, time, txt
40
41
 
@@ -299,6 +300,10 @@ def connect(path='', filename=''):
299
300
  - Error message string on failure.
300
301
 
301
302
  ### Notes
303
+ - On POSIX systems the database is stored in a per-user, `0700`-protected subdirectory of the
304
+ temporary directory (see `get_db_dir()`), not directly in the shared, world-writable `/tmp`.
305
+ This isolates each user's databases and prevents symlink attacks on the predictable paths
306
+ (CWE-377, GHSA-r35r-fpx2-jgr4).
302
307
  - The connection uses a `Row` factory, allowing rows to behave like dictionaries.
303
308
  - The connection registers a `REGEXP` SQL function for regular expression support.
304
309
  - Always check the returned success flag before using the connection.
@@ -311,16 +316,9 @@ def connect(path='', filename=''):
311
316
  >>> else:
312
317
  >>> print(conn)
313
318
  """
314
-
315
- def get_filename(path='', filename=''):
316
- """Helper to build the absolute path to the SQLite database file."""
317
- if not path:
318
- path = disk.get_tmpdir()
319
- if not filename:
320
- filename = 'linuxfabrik-monitoring-plugins-sqlite.db'
321
- return os.path.join(path, filename)
322
-
323
- db = get_filename(path, filename)
319
+ success, db = get_db_path(path=path, filename=filename)
320
+ if not success:
321
+ return False, db
324
322
 
325
323
  try:
326
324
  conn = sqlite3.connect(db)
@@ -640,6 +638,114 @@ def get_colnames(col_definition):
640
638
  return [col.strip().split()[0] for col in col_definition.split(',') if col.strip()]
641
639
 
642
640
 
641
+ def get_db_dir(path):
642
+ """
643
+ Return a per-user subdirectory of `path` that is safe for storing SQLite databases,
644
+ creating it if necessary.
645
+
646
+ SQLite databases are stored at predictable paths under the system temporary directory so the
647
+ data is found again on the next run. On a shared POSIX `/tmp`, that predictable path lets a
648
+ local attacker pre-create a symlink there and redirect writes to an arbitrary file. For a
649
+ process running as root (e.g. via sudo) this turns into an arbitrary-write primitive (CWE-377,
650
+ GHSA-r35r-fpx2-jgr4). To prevent this, all databases are kept inside a directory owned by the
651
+ current user with `0700` permissions, and the directory is rejected if anything about it looks
652
+ tampered with.
653
+
654
+ ### Parameters
655
+ - **path** (`str`):
656
+ The base directory (typically the system temporary directory) in which to create the
657
+ per-user subdirectory.
658
+
659
+ ### Returns
660
+ - **tuple** (`bool`, `str`):
661
+ - First element (`bool`): `True` on success, `False` on failure.
662
+ - Second element (`str`):
663
+ - The absolute path to the secure subdirectory on success.
664
+ - An error message describing the failure otherwise.
665
+
666
+ ### Notes
667
+ - `os.geteuid()` does not exist on Windows, where the temporary directory is already per-user
668
+ rather than a shared, world-writable location. There the base `path` is returned unchanged.
669
+ - The directory is validated with `os.lstat()` so a symlink planted at its path is detected
670
+ instead of being followed.
671
+
672
+ ### Example
673
+ >>> get_db_dir('/tmp')
674
+ (True, '/tmp/linuxfabrik-monitoring-plugins-uid1000')
675
+ """
676
+ # On Windows the temp dir is already per-user; the shared-/tmp hardening below does not apply.
677
+ if not hasattr(os, 'geteuid'):
678
+ return True, path
679
+
680
+ euid = os.geteuid()
681
+ db_dir = os.path.join(path, f'linuxfabrik-monitoring-plugins-uid{euid}')
682
+
683
+ # Reject a pre-existing symlink outright: makedirs(exist_ok=True) would either follow it
684
+ # (when it resolves to a directory) or fail with a confusing "File exists" (when it dangles).
685
+ # Either way it must not be used. os.path.islink() does not follow the link.
686
+ if os.path.islink(db_dir):
687
+ return False, f'DB directory {db_dir} is a symlink, refusing to use it'
688
+
689
+ try:
690
+ # 0o700: only the owner may access the directory. An existing directory is fine and gets
691
+ # validated below; any other error (e.g. an unwritable temp dir) aborts the connection.
692
+ os.makedirs(db_dir, mode=0o700, exist_ok=True)
693
+ except OSError as e:
694
+ return False, f'Creating DB directory {db_dir} failed, Error: {e}'
695
+
696
+ # lstat() does not follow symlinks, so a symlink planted at db_dir is caught here instead of
697
+ # silently redirecting every database to the attacker's target.
698
+ try:
699
+ st = os.lstat(db_dir)
700
+ except OSError as e:
701
+ return False, f'Inspecting DB directory {db_dir} failed, Error: {e}'
702
+
703
+ if not stat.S_ISDIR(st.st_mode):
704
+ return False, f'DB directory {db_dir} is not a directory, refusing to use it'
705
+ if st.st_uid != euid:
706
+ return False, f'DB directory {db_dir} has the wrong owner, refusing to use it'
707
+ if st.st_mode & 0o077:
708
+ return False, f'DB directory {db_dir} is too permissive, refusing to use it'
709
+
710
+ return True, db_dir
711
+
712
+
713
+ def get_db_path(path='', filename=''):
714
+ """
715
+ Return the absolute path of the SQLite database `filename`, resolving the same secured
716
+ per-user directory that `connect()` uses.
717
+
718
+ Use this whenever a caller needs the on-disk location of a database it opens through
719
+ `connect()` (for example to seed, migrate or remove the file), so the path is built in exactly
720
+ one place instead of being reconstructed by every caller.
721
+
722
+ ### Parameters
723
+ - **path** (`str`, optional):
724
+ Directory to resolve the database in. Defaults to the system temporary directory.
725
+ - **filename** (`str`, optional):
726
+ Name of the database file. Defaults to `'linuxfabrik-monitoring-plugins-sqlite.db'`.
727
+
728
+ ### Returns
729
+ - **tuple** (`bool`, `str`):
730
+ - First element (`bool`): `True` on success, `False` on failure.
731
+ - Second element (`str`):
732
+ - The absolute path to the database file on success.
733
+ - An error message describing the failure otherwise.
734
+
735
+ ### Example
736
+ >>> get_db_path(filename='example.db')
737
+ (True, '/tmp/linuxfabrik-monitoring-plugins-uid1000/example.db')
738
+ """
739
+ if not path:
740
+ path = disk.get_tmpdir()
741
+ if not filename:
742
+ filename = 'linuxfabrik-monitoring-plugins-sqlite.db'
743
+ success, db_dir = get_db_dir(path)
744
+ if not success:
745
+ return False, db_dir
746
+ return True, os.path.join(db_dir, filename)
747
+
748
+
643
749
  def get_tables(conn):
644
750
  """
645
751
  List all user-defined tables in the SQLite database.
@@ -13,7 +13,7 @@ partitions, grepping a file, etc.
13
13
  """
14
14
 
15
15
  __author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
16
- __version__ = '2025100201'
16
+ __version__ = '2026060101'
17
17
 
18
18
  import csv
19
19
  import os
@@ -226,25 +226,32 @@ def get_real_disks():
226
226
 
227
227
  def get_tmpdir():
228
228
  """
229
- Return the name of the directory used for temporary files, always without a trailing '/'.
229
+ Return the absolute path of the directory used for temporary files, without a trailing '/'.
230
230
 
231
- Searches a standard list of directories to find one in which the calling user can create files.
232
- The search order is:
231
+ Thin wrapper around `tempfile.gettempdir()`, which picks a usable temporary directory by
232
+ trying to create, write and delete a test file in each candidate and returning the first one
233
+ that works. The candidates are tried in this order:
233
234
 
234
- - The directory named by the TMPDIR environment variable.
235
- - The directory named by the TEMP environment variable.
236
- - The directory named by the TMP environment variable.
237
- - A platform-specific default:
238
- * On Windows: C:\\TEMP, C:\\TMP, \\TEMP, \\TMP (in that order).
239
- * On other systems: /tmp, /var/tmp, /usr/tmp (in that order).
235
+ - The directories named by the `TMPDIR`, `TEMP` and `TMP` environment variables.
236
+ - Platform-specific defaults: on Windows `~\\AppData\\Local\\Temp`, `%SYSTEMROOT%\\Temp`,
237
+ `c:\\temp`, `c:\\tmp`, `\\temp`, `\\tmp`; on other systems `/tmp`, `/var/tmp`, `/usr/tmp`.
240
238
  - As a last resort, the current working directory.
241
239
 
240
+ The literal `/tmp` is only returned as a fallback if `tempfile.gettempdir()` itself raises.
241
+
242
242
  ### Parameters
243
243
  - None
244
244
 
245
245
  ### Returns
246
246
  - **str**: The absolute path to the temporary directory.
247
247
 
248
+ ### Notes
249
+ - `tempfile.gettempdir()` computes the result once and caches it; changing `TMPDIR` and
250
+ friends afterwards has no effect for the rest of the process.
251
+ - The path is made absolute but not symlink-resolved (`os.path.abspath`, not
252
+ `os.path.realpath`), so a caller that needs a trusted location must validate the final path
253
+ itself.
254
+
248
255
  ### Example
249
256
  >>> get_tmpdir()
250
257
  '/tmp'
@@ -495,52 +495,59 @@ ENDOFLIFE_DATE = {
495
495
 
496
496
 
497
497
  'https://endoflife.date/api/gitlab.json': [
498
+ { 'cycle': '19.0',
499
+ 'eol': '2026-08-20',
500
+ 'latest': '19.0.1',
501
+ 'latestReleaseDate': '2026-05-26',
502
+ 'lts': False,
503
+ 'releaseDate': '2026-05-21',
504
+ 'support': '2026-06-18'},
498
505
  { 'cycle': '18.11',
499
506
  'eol': '2026-07-16',
500
- 'latest': '18.11.3',
501
- 'latestReleaseDate': '2026-05-13',
507
+ 'latest': '18.11.4',
508
+ 'latestReleaseDate': '2026-05-26',
502
509
  'lts': False,
503
510
  'releaseDate': '2026-04-16',
504
511
  'support': '2026-05-21'},
505
512
  { 'cycle': '18.10',
506
513
  'eol': '2026-06-18',
507
- 'latest': '18.10.6',
508
- 'latestReleaseDate': '2026-05-13',
514
+ 'latest': '18.10.7',
515
+ 'latestReleaseDate': '2026-05-26',
509
516
  'lts': False,
510
517
  'releaseDate': '2026-03-19',
511
518
  'support': '2026-04-16'},
512
519
  { 'cycle': '18.9',
513
520
  'eol': '2026-05-21',
514
- 'latest': '18.9.7',
515
- 'latestReleaseDate': '2026-05-13',
521
+ 'latest': '18.9.8',
522
+ 'latestReleaseDate': '2026-05-25',
516
523
  'lts': False,
517
524
  'releaseDate': '2026-02-19',
518
525
  'support': '2026-03-19'},
519
526
  { 'cycle': '18.8',
520
527
  'eol': '2026-04-16',
521
- 'latest': '18.8.9',
522
- 'latestReleaseDate': '2026-04-07',
528
+ 'latest': '18.8.10',
529
+ 'latestReleaseDate': '2026-05-25',
523
530
  'lts': False,
524
531
  'releaseDate': '2026-01-15',
525
532
  'support': '2026-02-19'},
526
533
  { 'cycle': '18.7',
527
534
  'eol': '2026-03-19',
528
- 'latest': '18.7.6',
529
- 'latestReleaseDate': '2026-03-10',
535
+ 'latest': '18.7.7',
536
+ 'latestReleaseDate': '2026-05-25',
530
537
  'lts': False,
531
538
  'releaseDate': '2025-12-18',
532
539
  'support': '2026-01-15'},
533
540
  { 'cycle': '18.6',
534
541
  'eol': '2026-02-19',
535
- 'latest': '18.6.6',
536
- 'latestReleaseDate': '2026-02-09',
542
+ 'latest': '18.6.8',
543
+ 'latestReleaseDate': '2026-05-26',
537
544
  'lts': False,
538
545
  'releaseDate': '2025-11-20',
539
546
  'support': '2025-12-18'},
540
547
  { 'cycle': '18.5',
541
548
  'eol': '2026-01-15',
542
- 'latest': '18.5.5',
543
- 'latestReleaseDate': '2026-01-07',
549
+ 'latest': '18.5.7',
550
+ 'latestReleaseDate': '2026-05-26',
544
551
  'lts': False,
545
552
  'releaseDate': '2025-10-16',
546
553
  'support': '2025-11-20'},
@@ -1126,16 +1133,22 @@ ENDOFLIFE_DATE = {
1126
1133
 
1127
1134
 
1128
1135
  'https://endoflife.date/api/graylog.json': [
1136
+ { 'cycle': '7.1',
1137
+ 'eol': False,
1138
+ 'latest': '7.1.2',
1139
+ 'latestReleaseDate': '2026-05-20',
1140
+ 'lts': False,
1141
+ 'releaseDate': '2026-05-04'},
1129
1142
  { 'cycle': '7.0',
1130
1143
  'eol': '2026-11-03',
1131
- 'latest': '7.0.6',
1132
- 'latestReleaseDate': '2026-04-01',
1144
+ 'latest': '7.0.7',
1145
+ 'latestReleaseDate': '2026-05-20',
1133
1146
  'lts': False,
1134
1147
  'releaseDate': '2025-11-03'},
1135
1148
  { 'cycle': '6.3',
1136
1149
  'eol': '2026-06-30',
1137
- 'latest': '6.3.11',
1138
- 'latestReleaseDate': '2026-04-01',
1150
+ 'latest': '6.3.12',
1151
+ 'latestReleaseDate': '2026-05-20',
1139
1152
  'lts': False,
1140
1153
  'releaseDate': '2025-06-30'},
1141
1154
  { 'cycle': '6.2',
@@ -1301,8 +1314,8 @@ ENDOFLIFE_DATE = {
1301
1314
  'https://endoflife.date/api/icinga.json': [
1302
1315
  { 'cycle': '2.16',
1303
1316
  'eol': False,
1304
- 'latest': '2.16.0',
1305
- 'latestReleaseDate': '2026-04-22',
1317
+ 'latest': '2.16.1',
1318
+ 'latestReleaseDate': '2026-05-21',
1306
1319
  'lts': False,
1307
1320
  'releaseDate': '2026-04-23',
1308
1321
  'support': True},
@@ -1432,8 +1445,8 @@ ENDOFLIFE_DATE = {
1432
1445
  'https://endoflife.date/api/keycloak.json': [
1433
1446
  { 'cycle': '26.6',
1434
1447
  'eol': False,
1435
- 'latest': '26.6.1',
1436
- 'latestReleaseDate': '2026-04-15',
1448
+ 'latest': '26.6.2',
1449
+ 'latestReleaseDate': '2026-05-19',
1437
1450
  'lts': False,
1438
1451
  'releaseDate': '2026-04-08'},
1439
1452
  { 'cycle': '26.5',
@@ -1614,8 +1627,8 @@ ENDOFLIFE_DATE = {
1614
1627
  { 'cycle': '11.8',
1615
1628
  'eol': '2028-06-04',
1616
1629
  'extendedSupport': '2033-10-22',
1617
- 'latest': '11.8.7',
1618
- 'latestReleaseDate': '2026-05-14',
1630
+ 'latest': '11.8.8',
1631
+ 'latestReleaseDate': '2026-05-27',
1619
1632
  'lts': True,
1620
1633
  'releaseDate': '2025-06-04'},
1621
1634
  { 'cycle': '11.7',
@@ -1645,8 +1658,8 @@ ENDOFLIFE_DATE = {
1645
1658
  { 'cycle': '11.4',
1646
1659
  'eol': '2029-05-29',
1647
1660
  'extendedSupport': '2033-01-16',
1648
- 'latest': '11.4.11',
1649
- 'latestReleaseDate': '2026-05-14',
1661
+ 'latest': '11.4.12',
1662
+ 'latestReleaseDate': '2026-05-27',
1650
1663
  'lts': True,
1651
1664
  'releaseDate': '2024-05-29'},
1652
1665
  { 'cycle': '11.3',
@@ -1684,8 +1697,8 @@ ENDOFLIFE_DATE = {
1684
1697
  { 'cycle': '10.11',
1685
1698
  'eol': '2028-02-16',
1686
1699
  'extendedSupport': '2028-02-16',
1687
- 'latest': '10.11.17',
1688
- 'latestReleaseDate': '2026-05-14',
1700
+ 'latest': '10.11.18',
1701
+ 'latestReleaseDate': '2026-05-27',
1689
1702
  'lts': True,
1690
1703
  'releaseDate': '2023-02-16'},
1691
1704
  { 'cycle': '10.10',
@@ -1723,8 +1736,8 @@ ENDOFLIFE_DATE = {
1723
1736
  { 'cycle': '10.6',
1724
1737
  'eol': '2026-07-06',
1725
1738
  'extendedSupport': '2029-08-23',
1726
- 'latest': '10.6.26',
1727
- 'latestReleaseDate': '2026-05-14',
1739
+ 'latest': '10.6.27',
1740
+ 'latestReleaseDate': '2026-05-27',
1728
1741
  'lts': True,
1729
1742
  'releaseDate': '2021-07-06'},
1730
1743
  { 'cycle': '10.5',
@@ -1813,20 +1826,20 @@ ENDOFLIFE_DATE = {
1813
1826
  'https://endoflife.date/api/mastodon.json': [
1814
1827
  { 'cycle': '4.5',
1815
1828
  'eol': False,
1816
- 'latest': '4.5.9',
1817
- 'latestReleaseDate': '2026-04-15',
1829
+ 'latest': '4.5.10',
1830
+ 'latestReleaseDate': '2026-05-20',
1818
1831
  'lts': False,
1819
1832
  'releaseDate': '2025-11-06'},
1820
1833
  { 'cycle': '4.4',
1821
1834
  'eol': False,
1822
- 'latest': '4.4.16',
1823
- 'latestReleaseDate': '2026-04-15',
1835
+ 'latest': '4.4.17',
1836
+ 'latestReleaseDate': '2026-05-20',
1824
1837
  'lts': False,
1825
1838
  'releaseDate': '2025-07-08'},
1826
1839
  { 'cycle': '4.3',
1827
1840
  'eol': '2026-05-06',
1828
- 'latest': '4.3.22',
1829
- 'latestReleaseDate': '2026-04-15',
1841
+ 'latest': '4.3.23',
1842
+ 'latestReleaseDate': '2026-05-20',
1830
1843
  'lts': False,
1831
1844
  'releaseDate': '2024-10-08'},
1832
1845
  { 'cycle': '4.2',
@@ -1901,8 +1914,8 @@ ENDOFLIFE_DATE = {
1901
1914
  'https://endoflife.date/api/matomo.json': [
1902
1915
  { 'cycle': '5',
1903
1916
  'eol': False,
1904
- 'latest': '5.10.0',
1905
- 'latestReleaseDate': '2026-05-04',
1917
+ 'latest': '5.10.1',
1918
+ 'latestReleaseDate': '2026-05-29',
1906
1919
  'lts': False,
1907
1920
  'releaseDate': '2023-12-18',
1908
1921
  'support': True},
@@ -2267,14 +2280,14 @@ ENDOFLIFE_DATE = {
2267
2280
  'https://endoflife.date/api/nextcloud.json': [
2268
2281
  { 'cycle': '33',
2269
2282
  'eol': '2027-02-28',
2270
- 'latest': '33.0.3',
2271
- 'latestReleaseDate': '2026-04-30',
2283
+ 'latest': '33.0.4',
2284
+ 'latestReleaseDate': '2026-05-28',
2272
2285
  'lts': False,
2273
2286
  'releaseDate': '2026-02-18'},
2274
2287
  { 'cycle': '32',
2275
2288
  'eol': '2026-09-30',
2276
- 'latest': '32.0.9',
2277
- 'latestReleaseDate': '2026-04-30',
2289
+ 'latest': '32.0.10',
2290
+ 'latestReleaseDate': '2026-05-28',
2278
2291
  'lts': False,
2279
2292
  'releaseDate': '2025-09-27'},
2280
2293
  { 'cycle': '31',
@@ -2604,29 +2617,29 @@ ENDOFLIFE_DATE = {
2604
2617
  'https://endoflife.date/api/postfix.json': [
2605
2618
  { 'cycle': '3.11',
2606
2619
  'eol': False,
2607
- 'latest': '3.11.2',
2608
- 'latestReleaseDate': '2026-05-02',
2620
+ 'latest': '3.11.3',
2621
+ 'latestReleaseDate': '2026-05-18',
2609
2622
  'link': 'https://www.postfix.org/announcements/postfix-3.11.0.html',
2610
2623
  'lts': False,
2611
2624
  'releaseDate': '2026-03-06'},
2612
2625
  { 'cycle': '3.10',
2613
2626
  'eol': False,
2614
- 'latest': '3.10.9',
2615
- 'latestReleaseDate': '2026-05-02',
2627
+ 'latest': '3.10.10',
2628
+ 'latestReleaseDate': '2026-05-18',
2616
2629
  'link': 'https://www.postfix.org/announcements/postfix-3.10.0.html',
2617
2630
  'lts': False,
2618
2631
  'releaseDate': '2025-02-16'},
2619
2632
  { 'cycle': '3.9',
2620
2633
  'eol': False,
2621
- 'latest': '3.9.10',
2622
- 'latestReleaseDate': '2026-05-02',
2634
+ 'latest': '3.9.11',
2635
+ 'latestReleaseDate': '2026-05-18',
2623
2636
  'link': 'https://www.postfix.org/announcements/postfix-3.9.2.html',
2624
2637
  'lts': False,
2625
2638
  'releaseDate': '2024-03-06'},
2626
2639
  { 'cycle': '3.8',
2627
2640
  'eol': False,
2628
- 'latest': '3.8.16',
2629
- 'latestReleaseDate': '2026-05-02',
2641
+ 'latest': '3.8.17',
2642
+ 'latestReleaseDate': '2026-05-18',
2630
2643
  'link': 'https://www.postfix.org/announcements/postfix-3.9.2.html',
2631
2644
  'lts': False,
2632
2645
  'releaseDate': '2023-04-17'},
@@ -3101,13 +3114,20 @@ ENDOFLIFE_DATE = {
3101
3114
 
3102
3115
 
3103
3116
  'https://endoflife.date/api/redis.json': [
3117
+ { 'cycle': '8.8',
3118
+ 'eol': False,
3119
+ 'latest': '8.8.0',
3120
+ 'latestReleaseDate': '2026-05-25',
3121
+ 'lts': False,
3122
+ 'releaseDate': '2026-05-25',
3123
+ 'support': True},
3104
3124
  { 'cycle': '8.6',
3105
3125
  'eol': False,
3106
3126
  'latest': '8.6.3',
3107
3127
  'latestReleaseDate': '2026-05-05',
3108
3128
  'lts': False,
3109
3129
  'releaseDate': '2026-02-11',
3110
- 'support': True},
3130
+ 'support': '2026-05-25'},
3111
3131
  { 'cycle': '8.4',
3112
3132
  'eol': False,
3113
3133
  'latest': '8.4.3',
@@ -3116,7 +3136,7 @@ ENDOFLIFE_DATE = {
3116
3136
  'releaseDate': '2025-11-18',
3117
3137
  'support': '2026-02-11'},
3118
3138
  { 'cycle': '8.2',
3119
- 'eol': False,
3139
+ 'eol': '2026-05-25',
3120
3140
  'latest': '8.2.6',
3121
3141
  'latestReleaseDate': '2026-05-05',
3122
3142
  'lts': False,
@@ -3178,16 +3198,16 @@ ENDOFLIFE_DATE = {
3178
3198
  { 'cycle': '10',
3179
3199
  'eol': '2035-05-31',
3180
3200
  'extendedSupport': '2038-05-31',
3181
- 'latest': '10.1',
3182
- 'latestReleaseDate': '2025-11-12',
3201
+ 'latest': '10.2',
3202
+ 'latestReleaseDate': '2026-05-20',
3183
3203
  'lts': '2035-05-31',
3184
3204
  'releaseDate': '2025-05-20',
3185
3205
  'support': '2030-05-31'},
3186
3206
  { 'cycle': '9',
3187
3207
  'eol': '2032-05-31',
3188
3208
  'extendedSupport': '2035-05-31',
3189
- 'latest': '9.7',
3190
- 'latestReleaseDate': '2025-11-12',
3209
+ 'latest': '9.8',
3210
+ 'latestReleaseDate': '2026-05-20',
3191
3211
  'lts': '2032-05-31',
3192
3212
  'releaseDate': '2022-05-18',
3193
3213
  'support': '2027-05-31'},
@@ -3237,43 +3257,43 @@ ENDOFLIFE_DATE = {
3237
3257
  'https://endoflife.date/api/rocketchat.json': [
3238
3258
  { 'cycle': '8.4',
3239
3259
  'eol': '2026-10-31',
3240
- 'latest': '8.4.1',
3241
- 'latestReleaseDate': '2026-05-08',
3260
+ 'latest': '8.4.2',
3261
+ 'latestReleaseDate': '2026-05-22',
3242
3262
  'lts': False,
3243
3263
  'releaseDate': '2026-04-30',
3244
3264
  'support': '2026-04-30'},
3245
3265
  { 'cycle': '8.3',
3246
3266
  'eol': '2026-10-31',
3247
- 'latest': '8.3.3',
3248
- 'latestReleaseDate': '2026-05-08',
3267
+ 'latest': '8.3.4',
3268
+ 'latestReleaseDate': '2026-05-22',
3249
3269
  'lts': False,
3250
3270
  'releaseDate': '2026-04-07',
3251
3271
  'support': '2026-04-07'},
3252
3272
  { 'cycle': '8.2',
3253
3273
  'eol': '2026-09-30',
3254
- 'latest': '8.2.3',
3255
- 'latestReleaseDate': '2026-05-08',
3274
+ 'latest': '8.2.4',
3275
+ 'latestReleaseDate': '2026-05-22',
3256
3276
  'lts': False,
3257
3277
  'releaseDate': '2026-03-02',
3258
3278
  'support': '2026-03-02'},
3259
3279
  { 'cycle': '8.1',
3260
3280
  'eol': '2026-08-31',
3261
- 'latest': '8.1.4',
3262
- 'latestReleaseDate': '2026-05-08',
3281
+ 'latest': '8.1.5',
3282
+ 'latestReleaseDate': '2026-05-22',
3263
3283
  'lts': False,
3264
3284
  'releaseDate': '2026-02-10',
3265
3285
  'support': '2026-02-10'},
3266
3286
  { 'cycle': '8.0',
3267
3287
  'eol': '2026-06-30',
3268
- 'latest': '8.0.5',
3269
- 'latestReleaseDate': '2026-05-08',
3288
+ 'latest': '8.0.6',
3289
+ 'latestReleaseDate': '2026-05-22',
3270
3290
  'lts': False,
3271
3291
  'releaseDate': '2026-01-12',
3272
3292
  'support': '2026-01-12'},
3273
3293
  { 'cycle': '7.13',
3274
3294
  'eol': '2026-05-31',
3275
- 'latest': '7.13.7',
3276
- 'latestReleaseDate': '2026-05-13',
3295
+ 'latest': '7.13.8',
3296
+ 'latestReleaseDate': '2026-05-22',
3277
3297
  'lts': False,
3278
3298
  'releaseDate': '2025-12-05',
3279
3299
  'support': '2025-12-05'},
@@ -3293,8 +3313,8 @@ ENDOFLIFE_DATE = {
3293
3313
  'support': '2025-10-17'},
3294
3314
  { 'cycle': '7.10',
3295
3315
  'eol': '2026-06-30',
3296
- 'latest': '7.10.11',
3297
- 'latestReleaseDate': '2026-05-13',
3316
+ 'latest': '7.10.12',
3317
+ 'latestReleaseDate': '2026-05-25',
3298
3318
  'lts': True,
3299
3319
  'releaseDate': '2025-09-04',
3300
3320
  'support': '2025-09-04'},
@@ -3824,6 +3844,13 @@ ENDOFLIFE_DATE = {
3824
3844
 
3825
3845
 
3826
3846
  'https://endoflife.date/api/valkey.json': [
3847
+ { 'cycle': '9.1',
3848
+ 'eol': False,
3849
+ 'latest': '9.1.0',
3850
+ 'latestReleaseDate': '2026-05-19',
3851
+ 'lts': False,
3852
+ 'releaseDate': '2026-05-19',
3853
+ 'support': True},
3827
3854
  { 'cycle': '9.0',
3828
3855
  'eol': '2028-10-21',
3829
3856
  'latest': '9.0.4',
@@ -3856,8 +3883,15 @@ ENDOFLIFE_DATE = {
3856
3883
 
3857
3884
 
3858
3885
  'https://endoflife.date/api/wordpress.json': [
3859
- { 'cycle': '6.9',
3886
+ { 'cycle': '7.0',
3860
3887
  'eol': False,
3888
+ 'latest': '7.0.0',
3889
+ 'latestReleaseDate': '2026-05-20',
3890
+ 'lts': False,
3891
+ 'releaseDate': '2026-05-20',
3892
+ 'supportedPHPVersions': '7.4 - 8.5'},
3893
+ { 'cycle': '6.9',
3894
+ 'eol': '2026-05-20',
3861
3895
  'latest': '6.9.4',
3862
3896
  'latestReleaseDate': '2026-03-11',
3863
3897
  'lts': False,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: linuxfabrik-lib
3
- Version: 4.0.2
3
+ Version: 4.2.0
4
4
  Summary: Python libraries used in various Linuxfabrik projects, including the 'Linuxfabrik Monitoring Plugins' project.
5
5
  Author-email: "Linuxfabrik GmbH, Zurich, Switzerland" <info@linuxfabrik.ch>
6
6
  License: This is free and unencumbered software released into the public domain.
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "linuxfabrik-lib"
9
- version = "4.0.2"
9
+ version = "4.2.0"
10
10
  description = "Python libraries used in various Linuxfabrik projects, including the 'Linuxfabrik Monitoring Plugins' project."
11
11
  readme = "README.md"
12
12
  authors = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes