79 lines
3.0 KiB
Python
79 lines
3.0 KiB
Python
from flask import Flask, request, jsonify, abort
|
|
import requests
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
import time
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Optional token for access control (not production secure)
|
|
HEALTH_TOKEN = os.environ.get('HEALTH_TOKEN')
|
|
|
|
@app.route('/healthcheck')
|
|
def healthcheck():
|
|
token = request.args.get('token') or request.headers.get('X-Health-Token')
|
|
if HEALTH_TOKEN and token != HEALTH_TOKEN:
|
|
return jsonify({'error': 'Unauthorized'}), 401
|
|
|
|
# New behavior: prefer service id lookup
|
|
service_id = request.args.get('id')
|
|
host = request.args.get('host')
|
|
port = request.args.get('port')
|
|
proto = request.args.get('proto', 'http')
|
|
|
|
if service_id:
|
|
# Load services.xml and find service by id
|
|
try:
|
|
tree = ET.parse('/usr/share/nginx/html/services.xml')
|
|
root = tree.getroot()
|
|
except Exception as e:
|
|
return jsonify({'error': 'unable to parse services.xml', 'detail': str(e)}), 500
|
|
|
|
# Find service element by id
|
|
svc = None
|
|
for s in root.findall('.//service'):
|
|
sid = s.get('id') or s.get('name')
|
|
if sid and sid == service_id:
|
|
svc = s
|
|
break
|
|
if svc is None:
|
|
return jsonify({'error': 'service id not found'}), 404
|
|
|
|
# Determine host/port/proto preference: local-ip > tailscale-ip root > host attr > fallback to provided host
|
|
local_ip = svc.get('local-ip')
|
|
root_tailscale = root.get('tailscale-ip')
|
|
host_attr = svc.get('host')
|
|
proto_attr = svc.get('proto') or proto
|
|
port_attr = svc.get('port') or port
|
|
health_path = svc.get('health-path', '') # Custom health check path (e.g., /api/health, /ping)
|
|
|
|
# prefer local-ip if present
|
|
target_host = local_ip or root_tailscale or (host_attr.split(':')[0] if host_attr and ':' in host_attr else None) or host
|
|
target_port = port_attr or (host_attr.split(':')[1] if host_attr and ':' in host_attr else None)
|
|
target_proto = proto_attr
|
|
if not target_host or not target_port:
|
|
return jsonify({'error': 'service missing host or port mapping'}), 400
|
|
|
|
host = target_host
|
|
port = target_port
|
|
proto = target_proto
|
|
|
|
if not host or not port:
|
|
return jsonify({'error': 'missing parameters, expected host and port or id'}), 400
|
|
|
|
# Build URL with optional custom health path
|
|
health_path = health_path if 'health_path' in locals() else ''
|
|
url = f"{proto}://{host}:{port}{health_path}"
|
|
|
|
try:
|
|
# Use HEAD to do a lightweight check
|
|
r = requests.head(url, timeout=4, allow_redirects=True, verify=False)
|
|
# Any 2xx/3xx is considered OK
|
|
ok = 200 <= r.status_code < 400
|
|
return jsonify({'ok': ok, 'status_code': r.status_code}), (200 if ok else 502)
|
|
except requests.RequestException as e:
|
|
return jsonify({'ok': False, 'error': str(e)}), 502
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=8081)
|