database setup for outsidethebox.top webhosting infrastructure project
https://data.outsidethebox.top
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.9 KiB
114 lines
3.9 KiB
\ |
|
#!/usr/bin/env python3 |
|
import os, json, socket, ssl, datetime |
|
import pymysql |
|
import requests |
|
|
|
APP_ROOT = os.path.dirname(os.path.abspath(__file__)) |
|
CFG = json.load(open(os.path.join(APP_ROOT, "config.json"))) |
|
|
|
def db_conn(): |
|
return pymysql.connect( |
|
host=CFG["db"]["host"], |
|
port=int(CFG["db"]["port"]), |
|
user=CFG["db"]["user"], |
|
password=CFG["db"]["password"], |
|
database=CFG["db"]["name"], |
|
charset="utf8mb4", |
|
autocommit=True, |
|
cursorclass=pymysql.cursors.DictCursor |
|
) |
|
|
|
def get_cert_not_after(hostname: str, port: int = 443, timeout: int = 8): |
|
ctx = ssl.create_default_context() |
|
ctx.check_hostname = False |
|
ctx.verify_mode = ssl.CERT_NONE |
|
with socket.create_connection((hostname, port), timeout=timeout) as sock: |
|
with ctx.wrap_socket(sock, server_hostname=hostname) as ssock: |
|
cert = ssock.getpeercert() |
|
# notAfter format like: 'Jun 5 12:00:00 2026 GMT' |
|
na = cert.get("notAfter") |
|
if not na: |
|
return None |
|
return datetime.datetime.strptime(na, "%b %d %H:%M:%S %Y %Z") |
|
|
|
def http_status(url: str, timeout: int = 8): |
|
# Don't download big content; HEAD sometimes blocked, so use GET with small read |
|
r = requests.get(url, timeout=timeout, allow_redirects=True, verify=False, stream=True, headers={"User-Agent":"outsidethebox-host-registry/1.0"}) |
|
try: |
|
r.close() |
|
except Exception: |
|
pass |
|
return int(r.status_code) |
|
|
|
def compute_status(code_http, code_https, err_http, err_https): |
|
# Green if 2xx on 80 OR 443 |
|
def is_green(code): |
|
return code is not None and 200 <= code <= 299 |
|
if is_green(code_http) or is_green(code_https): |
|
return "up" |
|
# Red if got a response but not 2xx, OR any connection-type error |
|
if code_http is not None or code_https is not None or err_http or err_https: |
|
return "down" |
|
return "unknown" |
|
|
|
def main(): |
|
now = datetime.datetime.utcnow() |
|
rows=[] |
|
with db_conn() as con: |
|
with con.cursor() as cur: |
|
cur.execute("SELECT id, fqdn, monitor_enabled FROM hosts WHERE monitor_enabled=1 ORDER BY zone ASC, fqdn ASC") |
|
rows = cur.fetchall() |
|
|
|
for r in rows: |
|
hid = r["id"] |
|
fqdn = (r["fqdn"] or "").strip().lower().strip(".") |
|
code_http = None |
|
code_https = None |
|
ssl_exp = None |
|
err_http = "" |
|
err_https = "" |
|
last_error = "" |
|
|
|
# HTTP 80 |
|
try: |
|
code_http = http_status(f"http://{fqdn}/") |
|
except Exception as e: |
|
err_http = f"http: {type(e).__name__}: {e}" |
|
|
|
# HTTPS 443 + cert |
|
try: |
|
code_https = http_status(f"https://{fqdn}/") |
|
except Exception as e: |
|
err_https = f"https: {type(e).__name__}: {e}" |
|
|
|
try: |
|
ssl_exp = get_cert_not_after(fqdn, 443) |
|
except Exception as e: |
|
# only store as error if https is enabled; still useful for debugging |
|
last_error = (last_error + " ; " if last_error else "") + f"cert: {type(e).__name__}: {e}" |
|
|
|
if err_http: |
|
last_error = (last_error + " ; " if last_error else "") + err_http |
|
if err_https: |
|
last_error = (last_error + " ; " if last_error else "") + err_https |
|
|
|
status = compute_status(code_http, code_https, err_http, err_https) |
|
|
|
with db_conn() as con: |
|
with con.cursor() as cur: |
|
cur.execute(""" |
|
UPDATE hosts SET |
|
status_code_http=%s, |
|
status_code_https=%s, |
|
status=%s, |
|
last_check_at=%s, |
|
last_error=%s, |
|
ssl_expires_at=%s |
|
WHERE id=%s |
|
""", (code_http, code_https, status, now, last_error[:1000] if last_error else None, ssl_exp, hid)) |
|
|
|
print(f"Checked {len(rows)} host(s) at {now.isoformat()}Z") |
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|