diff --git a/hosts/ildkule/services/monitoring/gatus/default.nix b/hosts/ildkule/services/monitoring/gatus/default.nix index 3704065..ff60c35 100644 --- a/hosts/ildkule/services/monitoring/gatus/default.nix +++ b/hosts/ildkule/services/monitoring/gatus/default.nix @@ -5,6 +5,8 @@ in { imports = [ ./minecraft-checker.nix + ./postgres-checker.nix + ./mariadb-checker.nix ]; services.gatus = { @@ -133,6 +135,18 @@ in "[BODY].ok == true" ]; }) + (mkService "PostgreSQL" "http://localhost:1338" // { + conditions = [ + "[STATUS] == 200" + "[BODY].ok == true" + ]; + }) + (mkService "MariaDB" "http://localhost:1339" // { + conditions = [ + "[STATUS] == 200" + "[BODY].ok == true" + ]; + }) (mkService "Email (SMTP)" "starttls://mail.pvv.ntnu.no:587") (mkService "Email (POP3)" "tls://mail.pvv.ntnu.no:995") (mkService "Email (IMAP)" "tls://mail.pvv.ntnu.no:993") diff --git a/hosts/ildkule/services/monitoring/gatus/mariadb-checker.nix b/hosts/ildkule/services/monitoring/gatus/mariadb-checker.nix new file mode 100644 index 0000000..d22402d --- /dev/null +++ b/hosts/ildkule/services/monitoring/gatus/mariadb-checker.nix @@ -0,0 +1,79 @@ +{ config, pkgs, ... }: +{ + sops.secrets."keys/gatus/mariadb" = { + restartUnits = [ "phh-gatus-mariadb-checker.service" ]; + }; + + sops.templates."gatus-mariadb-checker.env" = { + restartUnits = [ "phh-gatus-mariadb-checker.service" ]; + content = '' + MYSQL_HOST=mysql.pvv.ntnu.no + MYSQL_PORT=3306 + MYSQL_DATABASE=mysql + MYSQL_USER=gatus_healthcheck + MYSQL_PASSWORD=${config.sops.placeholder."keys/gatus/mariadb"} + ''; + }; + + services.python-http-handlers."gatus-mariadb-checker" = { + listenStreams = [ "127.0.0.1:1339" ]; + libraries = with pkgs.python3Packages; [ + pymysql + ]; + serviceConfig = { + EnvironmentFile = config.sops.templates."gatus-mariadb-checker.env".path; + }; + handler = '' + import os + import json + import pymysql + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + try: + conn = pymysql.connect( + host=os.environ["MYSQL_HOST"], + port=int(os.environ.get("MYSQL_PORT", "3306")), + user=os.environ["MYSQL_USER"], + password=os.environ["MYSQL_PASSWORD"], + database=os.environ.get("MYSQL_DATABASE", "mysql"), + connect_timeout=5, + ) + + try: + with conn.cursor() as cur: + cur.execute("SELECT VERSION();") + (version,) = cur.fetchone() + + cur.execute("SHOW STATUS LIKE 'Threads_connected';") + (_, connections) = cur.fetchone() + + cur.execute("SHOW DATABASES;") + databases = cur.fetchall() + finally: + conn.close() + + body = { + "ok": True, + "version": version, + "connections": int(connections), + "databases": len(databases), + } + data = json.dumps(body).encode() + + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + except Exception as e: + data = json.dumps({"ok": False, "error": str(e)}).encode() + self.send_response(500) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + ''; + }; +} diff --git a/hosts/ildkule/services/monitoring/gatus/postgres-checker.nix b/hosts/ildkule/services/monitoring/gatus/postgres-checker.nix new file mode 100644 index 0000000..b5b62df --- /dev/null +++ b/hosts/ildkule/services/monitoring/gatus/postgres-checker.nix @@ -0,0 +1,79 @@ +{ config, pkgs, ... }: +{ + sops.secrets."keys/gatus/postgres" = { + restartUnits = [ "phh-gatus-postgres-checker.service" ]; + }; + + sops.templates."gatus-postgres-checker.env" = { + restartUnits = [ "phh-gatus-postgres-checker.service" ]; + content = '' + PGHOST=postgres.pvv.ntnu.no + PGPORT=5432 + PGDATABASE=postgres + PGUSER=gatus_healthcheck + PGPASSWORD=${config.sops.placeholder."keys/gatus/postgres"} + ''; + }; + + services.python-http-handlers."gatus-postgres-checker" = { + listenStreams = [ "127.0.0.1:1338" ]; + libraries = with pkgs.python3Packages; [ + psycopg2 + ]; + serviceConfig = { + EnvironmentFile = config.sops.templates."gatus-postgres-checker.env".path; + }; + handler = '' + import os + import json + import psycopg2 + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + try: + conn = psycopg2.connect( + host=os.environ["PGHOST"], + port=os.environ.get("PGPORT", "5432"), + dbname=os.environ.get("PGDATABASE", "postgres"), + user=os.environ["PGUSER"], + password=os.environ["PGPASSWORD"], + connect_timeout=5, + ) + + try: + with conn.cursor() as cur: + cur.execute("SELECT version();") + (version,) = cur.fetchone() + + cur.execute("SELECT count(*) FROM pg_stat_activity;") + (connections,) = cur.fetchone() + + cur.execute("SELECT count(*) FROM pg_database WHERE NOT datistemplate;") + (databases,) = cur.fetchone() + finally: + conn.close() + + body = { + "ok": True, + "version": version, + "connections": connections, + "databases": databases, + } + data = json.dumps(body).encode() + + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + except Exception as e: + data = json.dumps({"ok": False, "error": str(e)}).encode() + self.send_response(500) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + ''; + }; +} diff --git a/secrets/ildkule/ildkule.yaml b/secrets/ildkule/ildkule.yaml index 3544145..7528465 100644 --- a/secrets/ildkule/ildkule.yaml +++ b/secrets/ildkule/ildkule.yaml @@ -1,6 +1,9 @@ config: mysqld_exporter_password: ENC[AES256_GCM,data:I9K+QMqaN3FOOVKzeOR9Q6UERStXX0P8WEHyN1jzzbM=,iv:UxvIdlfAyJvNuxPkU4+guKPa0fiD0vVLzHOTYktcmso=,tag:ltnIqEwESYx9HBu8UN0ZLw==,type:str] keys: + gatus: + mariadb: ENC[AES256_GCM,data:U7CGtjHacJlKcReEw3JO86okCVgLAkP92iX1bHGNw8qeImpvk5mNw77Ox7tbsChvozrJWa/Npu2pTBsNnVYYYA==,iv:hWa+KPiBc3AnH1tHCGJa8LhPXZ1o8sxufpGe7nUjV0E=,tag:mso5HEfmWAOGw350408X7g==,type:str] + postgres: ENC[AES256_GCM,data:7fI0JeZDK1MX5poXWaMo67PXbjdjvM+xPKioHSHmGdS8iNDGe5Ym6LlS/hOnPvvDuNFyHjKKizqDst0227U2gg==,iv:D3bz/xyFrnQsUCiMEFq8JOjSudyiw+idEMWQ1e6Uk3k=,tag:8XqadKVHK9NMWehK7V5J0Q==,type:str] grafana: secret_key: ENC[AES256_GCM,data:+WoAJbDBEgKs0RoHT+7oEELAVQ+/2Xt+5RTMSXg23moCqVRx+Gzll9P5Drw=,iv:AkRn/Y20iEe5i1T+84wAgLCTFtAox2G3giyawAkltAw=,tag:BZbt5Wb5lYLIJBm/pfP4GQ==,type:str] admin_password: ENC[AES256_GCM,data:ttKwfC4WuXeL/6x4,iv:x1X+e3z08CR992GzC62YnFIN7SGrE81/nDNrgcgVzx0=,tag:YajUoy61kYbpeGeC7yNrXQ==,type:str] @@ -9,8 +12,7 @@ keys: postgres_exporter_knakelibrak_env: ENC[AES256_GCM,data:xjC7DGXrW2GIJq8XioIZb+jSe/Hzcz0tv9cUHmX/n1nhI+D64lYt+EKnq1+RX/vJzU4sTaKjveKBh88Qqnv6RQm+MZC//dIxcvnnAdl50qnHZyBCaFFEzSNI8I8vGyArMk8Ja72clBq3kMpUz/pLBP0qDrjblKDoWkU=,iv:ZW98hJy8A5t4Oxtu17R3tM7gou183VLbgBsHA8LFuJY=,tag:VMOvQz3X/XDylV1YFg2Jsg==,type:str] sops: age: - - recipient: age102e6y8gah0ntr6fxqnkpepc8ar29p6ls7ks9ka7v8w87q8scm9yqmc2u8d - enc: | + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyTWRSM3IwMmxtTmZVcCsw OUhlakxHZzgrSEhEdUZFTXE1anNjQ2wvdkZFCnB6S1l3TXQ3ZGFYWmtYM1cwMFZT @@ -18,8 +20,8 @@ sops: YlltQ3FBU3RBYUx4TnNPRk1SUWNqZG8KAJjc09x553ncaWduGLsnIHdroaOmMasP /fq0GzW6UNfmE2rQ6qrQti21B37/sN0WMLCSPLUPG45kBgx20GG4hQ== -----END AGE ENCRYPTED FILE----- - - recipient: age1ug30gg4y7ftuya0wdv7q0vh4egn00wlv2th7mt7cgc2ze46wmvyq9lq6ge - enc: | + recipient: age102e6y8gah0ntr6fxqnkpepc8ar29p6ls7ks9ka7v8w87q8scm9yqmc2u8d + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTOWFvcFFzc1lmNVdmV3lX cVVLNHowcGdENzI5UUJLZTNSMHhjNlI2c0FnCmdwOS9oL1kwTnhwbXRodWxxWVE3 @@ -27,8 +29,8 @@ sops: eDVwd2dKMG9FRW1OY1pyUkhLeWw3b0EKtJpsQ/Ss39ZLiRNqUhn8sdB3hpQy7Syv ererqhMkqmDugGEHPk6KpZuj7DVSK1di7JgA2qZOUPzI7UpxjaC0Kg== -----END AGE ENCRYPTED FILE----- - - recipient: age1mrnldl334l2nszuta6ywvewng0fswv2dz9l5g4qcwe3nj4yxf92qjskdx6 - enc: | + recipient: age1ug30gg4y7ftuya0wdv7q0vh4egn00wlv2th7mt7cgc2ze46wmvyq9lq6ge + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOQU1iVXRkQmo4b3F1Vngz S0pkNkVFR1FUb1djdmI0eHh3V3BBTDJTSTJZClA0S0Z0cTdFRmRaOEZQdHQzdGZ3 @@ -36,8 +38,8 @@ sops: REdjRFZyY2pNdEd6cmgvQisyVDhLUEkKRItJ0CGbzlEB5RNAyem4feMVhTfcLef3 QIqltZ2l4LLexnkECi3FCJZHxrbUa+/RF6p1DsueUw7LLUnOcphB9A== -----END AGE ENCRYPTED FILE----- - - recipient: age1hmpdk4h69wxpwqk9tkud39f66hprhehxtzhgw97r6dvr7v0mx5jscsuhkn - enc: | + recipient: age1mrnldl334l2nszuta6ywvewng0fswv2dz9l5g4qcwe3nj4yxf92qjskdx6 + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvZTBpaWM0c1hmODBaK0Iw bmp1NzRacklXMHU0K2J2d0g5ajBiNlRNWGhRCm1DOWI0cm5BdTdlNmFzM2JVekNk @@ -45,8 +47,8 @@ sops: U0ZCT2toZ1ZMZ3E0bXRhSTQvNGFWNVkKhxfQDIDe2LQW7OMBJv0J267AW1wI32df ZQxd657TEqzm7i19azrCS0jyRbfj2MYzEJAtTGiGZaNC9uKDFzBhKw== -----END AGE ENCRYPTED FILE----- - - recipient: age1wrssr4z4g6vl3fd3qme5cewchmmhm0j2xe6wf2meu4r6ycn37anse98mfs - enc: | + recipient: age1hmpdk4h69wxpwqk9tkud39f66hprhehxtzhgw97r6dvr7v0mx5jscsuhkn + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmRlJROHRKb21YUnlicmc1 MlptQllEcXFhajNKS1krMDdUMWk5QWo4eVJBCndGSlhXS1Vaa2RSTllIcmF1ZVpl @@ -54,8 +56,8 @@ sops: TE41aFdjU0h0ekQ2Zjg4Z3VQVjFWcnMK6zjSalqeYjyc4NH6nOeghlhYJydrz4pM N5ZcXjRbrIVFdhbYnvQGKvGKZm0kK6vjzBjdT7BM6ctr8cq/qrz1xQ== -----END AGE ENCRYPTED FILE----- - - recipient: age1zhxul786an743u0fascv4wtc5xduu7qfy803lfs539yzhgmlq5ds2lznt5 - enc: | + recipient: age1wrssr4z4g6vl3fd3qme5cewchmmhm0j2xe6wf2meu4r6ycn37anse98mfs + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIRXpBa2tYc2xEeldub0VK bkFwOFdlUGRZM0FVT0tyUW1RWnl0TjRUTUh3CnZlMC92MU1hRW1yZU1NSUdoUEZh @@ -63,8 +65,8 @@ sops: aXFQWlNVQ2laVm1ETStRemNZRXc3TUEKlPYSU3gp67dsPfbEJkru4ieMvspC7+pu rfp315HLyj1FGhrA8f2qOxE/PYI2rn0yKm80KffWBV7ylX/uonm4Fg== -----END AGE ENCRYPTED FILE----- - - recipient: age1sqs7urnzsdy64efmd0zukzv3gs5pnjksuxd7nqmdwdy5l0nqnunq6hyune - enc: | + recipient: age1zhxul786an743u0fascv4wtc5xduu7qfy803lfs539yzhgmlq5ds2lznt5 + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsaTEzOFBEeG9LVThSVmQ5 VXBoaFpueFRCbFJ1akE4RWc1aE1HUmVGcXdZCjFnbU0wd2drazNsTmNBMHNuOFhO @@ -72,8 +74,9 @@ sops: dC9meDZlc3d3aUJEVjc4REF0Y1BLcGcK79LbJzc5KVgEgyJR11crGuX8YcVoJBbT Fin7Zoon06L7qx0Zw5u27wV7RKMnYT7hOMiWs6660ZTLcYJ5M1aEZQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-03-16T20:08:18Z" - mac: ENC[AES256_GCM,data:C2tpWppc13jKJq5d4nmAKQOaNWHm27TKwxAxm1fi2lejN1lqUaoz5bHfTBA7MfaWvuP5uZnfbtG32eeu48mnlWpo58XRUFFecAhb9JUpW9s5IR3/nbzLNkGU7H5C0oWPrxI4thd+bAVduIgBjjFyGj1pe6J9db3c0yUWRwNlwGU=,iv:YpoQ4psiFYOWLGipxv1QvRvr034XFsyn2Bhyy39HmOo=,tag:ByiCWygFC/VokVTbdLoLgg==,type:str] + recipient: age1sqs7urnzsdy64efmd0zukzv3gs5pnjksuxd7nqmdwdy5l0nqnunq6hyune + lastmodified: "2026-07-21T09:40:54Z" + mac: ENC[AES256_GCM,data:FLpmsW9I21qNMFs6Tn/1AzFo+RcmCwtVdAaaGIrGlUi2l3gzb//ZK6ruI/VYx1R5DoX1OhD3EIg6aLMSOT6jiHf+8GwfhdijpayG1nrgeZW3SuFbjVlCYNMPH3DOJJB82U3T+ozSbpnZEtdUwmNWcZJt5F2hOEnoaNa+jLEKDao=,iv:qyQuyWuu+w2ynRvhIx0QVtw+tieO3dbQsUxEB5Brzp0=,tag:qYAj4h6foWm16YqIdZ6xbQ==,type:str] pgp: - created_at: "2026-05-20T17:35:58Z" enc: |- @@ -96,4 +99,4 @@ sops: -----END PGP MESSAGE----- fp: F7D37890228A907440E1FD4846B9228E814A2AAC unencrypted_suffix: _unencrypted - version: 3.9.4 + version: 3.13.2