Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# Immutable source/runtime contract for the schema-compatible ContextForge build.
|
||||
# CONTEXTFORGE_LOCAL_IMAGE is a verified local staging tag only; production uses
|
||||
# CONTEXTFORGE_IMAGE as an immutable registry digest supplied by promotion.
|
||||
CONTEXTFORGE_UPSTREAM_COMMIT=1e58a0434a78bbdbe1e975abeb0bed1043ff14c1
|
||||
CONTEXTFORGE_ALEMBIC_HEAD=12d4a0c7789c
|
||||
CONTEXTFORGE_MCP_SDK=1.30.0
|
||||
CONTEXTFORGE_LOCAL_IMAGE=itworx-mcp-hub/contextforge-schema:12d4a0c7789c
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
fail_secret() {
|
||||
echo "ContextForge secret-file validation failed" >&2
|
||||
exit 78
|
||||
}
|
||||
|
||||
fail_session_topology() {
|
||||
echo "ContextForge multi-worker session topology is unsafe: Redis-backed stateful session affinity is required" >&2
|
||||
exit 78
|
||||
}
|
||||
|
||||
fail_runtime_contract() {
|
||||
echo "ContextForge strict runtime contract verification failed" >&2
|
||||
exit 78
|
||||
}
|
||||
|
||||
read_secret() {
|
||||
[ -f "$1" ] && [ -r "$1" ] && [ -s "$1" ] || fail_secret
|
||||
mode="$(stat -c '%a' "$1" 2>/dev/null || true)"
|
||||
case "$mode" in 400|600) ;; *) fail_secret ;; esac
|
||||
[ "$(stat -c '%u' "$1" 2>/dev/null || true)" = "${SECRET_EXPECTED_UID:?}" ] || fail_secret
|
||||
[ "$(stat -c '%g' "$1" 2>/dev/null || true)" = "${SECRET_EXPECTED_GID:?}" ] || fail_secret
|
||||
value="$(tr -d '\r\n' < "$1")"
|
||||
[ -n "$value" ] || fail_secret
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
cf_password="$(read_secret /run/secrets/contextforge_database_password)"
|
||||
export DATABASE_URL="postgresql+psycopg://contextforge:${cf_password}@postgres:5432/contextforge"
|
||||
export JWT_SECRET_KEY="$(read_secret /run/secrets/contextforge_jwt_secret)"
|
||||
export AUTH_ENCRYPTION_SECRET="$(read_secret /run/secrets/contextforge_auth_encryption_secret)"
|
||||
export BASIC_AUTH_USER="itworx-contextforge-admin"
|
||||
export BASIC_AUTH_PASSWORD="$(read_secret /run/secrets/contextforge_admin_credential)"
|
||||
export PLATFORM_ADMIN_EMAIL="${PLATFORM_ADMIN_EMAIL:-contextforge-admin@example.com}"
|
||||
export PLATFORM_ADMIN_PASSWORD="$BASIC_AUTH_PASSWORD"
|
||||
|
||||
# Base/dev defaults retain the historical safe multi-worker topology. Production
|
||||
# overrides these values to the separately tested stateless single-worker
|
||||
# contract. Multi-worker mode is never allowed to weaken its affinity controls.
|
||||
export CACHE_TYPE="${CACHE_TYPE:-redis}"
|
||||
export USE_STATEFUL_SESSIONS="${USE_STATEFUL_SESSIONS:-true}"
|
||||
export MCPGATEWAY_SESSION_AFFINITY_ENABLED="${MCPGATEWAY_SESSION_AFFINITY_ENABLED:-true}"
|
||||
export MCPGATEWAY_SESSION_AFFINITY_TTL="${MCPGATEWAY_SESSION_AFFINITY_TTL:-3600}"
|
||||
|
||||
case "${GUNICORN_WORKERS:-auto}" in
|
||||
1) ;;
|
||||
*)
|
||||
[ "$CACHE_TYPE" = "redis" ] || fail_session_topology
|
||||
[ "$USE_STATEFUL_SESSIONS" = "true" ] || fail_session_topology
|
||||
[ "$MCPGATEWAY_SESSION_AFFINITY_ENABLED" = "true" ] || fail_session_topology
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${REDIS_AUTH_ENABLED:-false}" = "true" ]; then
|
||||
redis_password="$(read_secret /run/secrets/redis_password)"
|
||||
export REDIS_URL="redis://:${redis_password}@redis:6379/0"
|
||||
unset redis_password
|
||||
else
|
||||
export REDIS_URL="${REDIS_URL:-redis://redis:6379/0}"
|
||||
fi
|
||||
|
||||
# Production strict mode is a hard startup gate. The verifier is read-only: it
|
||||
# proves the embedded compatibility tuple, installed MCP SDK/API, image Alembic
|
||||
# head, database revision and transport settings. It never installs packages or
|
||||
# runs migration mutations. Any failure stops before ContextForge/Gunicorn.
|
||||
if [ "${ITWORX_CONTEXTFORGE_CONTRACT_MODE:-relaxed}" = "strict" ]; then
|
||||
[ -r /opt/itworx/contextforge-runtime-contract.py ] || fail_runtime_contract
|
||||
/app/.venv/bin/python /opt/itworx/contextforge-runtime-contract.py --startup || fail_runtime_contract
|
||||
fi
|
||||
|
||||
exec /app/docker-entrypoint.sh
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"schema": "itworx.contextforge-runtime-contract/v1",
|
||||
"sourceCommit": "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1",
|
||||
"alembicHead": "12d4a0c7789c",
|
||||
"mcpSdk": "1.30.0",
|
||||
"requiredSymbol": "McpError",
|
||||
"productionEnvironment": {
|
||||
"GUNICORN_WORKERS": "1",
|
||||
"USE_STATEFUL_SESSIONS": "false",
|
||||
"MCPGATEWAY_SESSION_AFFINITY_ENABLED": "false",
|
||||
"JSON_RESPONSE_ENABLED": "true",
|
||||
"EMAIL_AUTH_ENABLED": "false",
|
||||
"MCP_SESSION_POOL_ENABLED": "false",
|
||||
"MCPGATEWAY_SKIP_MIGRATIONS": "true",
|
||||
"ITWORX_CONTEXTFORGE_CONTRACT_MODE": "strict"
|
||||
},
|
||||
"migrationPolicy": {
|
||||
"startupMigrations": "forbidden",
|
||||
"skipEnv": "MCPGATEWAY_SKIP_MIGRATIONS",
|
||||
"skipValue": "true"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
FROM python:3.13.14-slim@sha256:6771159cd4fa5d9bba1258caf0b82e6b73458c694d178ad97c5e925c2d0e1a91
|
||||
|
||||
COPY infrastructure/images/install-base-security-updates.sh /tmp/install-base-security-updates.sh
|
||||
RUN sh /tmp/install-base-security-updates.sh \
|
||||
&& rm /tmp/install-base-security-updates.sh
|
||||
|
||||
ARG GIT_REVISION
|
||||
ARG SOURCE_DATE_EPOCH
|
||||
ARG BUILD_DATE
|
||||
|
||||
RUN groupadd --gid 10002 broker \
|
||||
&& useradd --uid 10002 --gid 10002 --create-home --shell /usr/sbin/nologin broker
|
||||
|
||||
COPY services/docker_read_broker /opt/broker/docker_read_broker
|
||||
COPY packages/__init__.py /opt/broker/packages/__init__.py
|
||||
COPY packages/hub_runtime /opt/broker/packages/hub_runtime
|
||||
RUN chown -R 10002:10002 /opt/broker
|
||||
|
||||
LABEL org.opencontainers.image.title="itworx-mcp-hub-docker-read-broker" \
|
||||
org.opencontainers.image.revision="${GIT_REVISION}" \
|
||||
org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \
|
||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.vendor="ITWorx"
|
||||
|
||||
WORKDIR /opt/broker
|
||||
USER 10002:10002
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["python", "-m", "docker_read_broker.server"]
|
||||
@@ -0,0 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
|
||||
class BrokerHealthHandler(BaseHTTPRequestHandler):
|
||||
server_version = "ITWorxDockerReadBroker/0.1"
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
if self.path != "/healthz":
|
||||
self.send_error(HTTPStatus.NOT_FOUND)
|
||||
return
|
||||
body = json.dumps({"status": "ok", "capability": "health-only-bootstrap"}).encode()
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, _format: str, *_args: object) -> None:
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ThreadingHTTPServer(("0.0.0.0", 8080), BrokerHealthHandler).serve_forever() # noqa: S104
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Exact Debian security versions from the staging image scan. Keep these pins
|
||||
# explicit: a release build must fail rather than silently consume newer OS
|
||||
# packages that were not scanned.
|
||||
gzip_version='1.13-1+deb13u1'
|
||||
pcre2_version='10.46-1~deb13u2'
|
||||
sqlite3_version='3.46.1-7+deb13u2'
|
||||
openssl_version='3.5.7-1~deb13u2'
|
||||
perl_base_version='5.40.1-6+deb13u1'
|
||||
util_linux_version='2.41.5-0+deb13u1'
|
||||
bsdutils_version='1:2.41.5-0+deb13u1'
|
||||
login_version='1:4.16.0-2+really2.41.5-0+deb13u1'
|
||||
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
|
||||
"bsdutils=${bsdutils_version}" \
|
||||
"gzip=${gzip_version}" \
|
||||
"libblkid1=${util_linux_version}" \
|
||||
"liblastlog2-2=${util_linux_version}" \
|
||||
"libmount1=${util_linux_version}" \
|
||||
"libpcre2-8-0=${pcre2_version}" \
|
||||
"libsqlite3-0=${sqlite3_version}" \
|
||||
"libssl3t64=${openssl_version}" \
|
||||
"libsmartcols1=${util_linux_version}" \
|
||||
"libuuid1=${util_linux_version}" \
|
||||
"login=${login_version}" \
|
||||
"mount=${util_linux_version}" \
|
||||
"openssl=${openssl_version}" \
|
||||
"openssl-provider-legacy=${openssl_version}" \
|
||||
"perl-base=${perl_base_version}" \
|
||||
"util-linux=${util_linux_version}"
|
||||
|
||||
assert_package_version() {
|
||||
actual="$(dpkg-query -W -f='${Version}' "$1")"
|
||||
if [ "${actual}" != "$2" ]; then
|
||||
printf 'Expected %s=%s but found %s\n' "$1" "$2" "${actual}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_package_version "bsdutils" "${bsdutils_version}"
|
||||
assert_package_version "login" "${login_version}"
|
||||
for package in libblkid1 liblastlog2-2 libmount1 libsmartcols1 libuuid1 mount util-linux; do
|
||||
assert_package_version "${package}" "${util_linux_version}"
|
||||
done
|
||||
assert_package_version "gzip" "${gzip_version}"
|
||||
assert_package_version "libpcre2-8-0" "${pcre2_version}"
|
||||
assert_package_version "libsqlite3-0" "${sqlite3_version}"
|
||||
assert_package_version "libssl3t64" "${openssl_version}"
|
||||
assert_package_version "openssl" "${openssl_version}"
|
||||
assert_package_version "openssl-provider-legacy" "${openssl_version}"
|
||||
assert_package_version "perl-base" "${perl_base_version}"
|
||||
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
FROM python:3.13.14-slim@sha256:6771159cd4fa5d9bba1258caf0b82e6b73458c694d178ad97c5e925c2d0e1a91
|
||||
|
||||
COPY infrastructure/images/install-base-security-updates.sh /tmp/install-base-security-updates.sh
|
||||
RUN sh /tmp/install-base-security-updates.sh \
|
||||
&& rm /tmp/install-base-security-updates.sh
|
||||
|
||||
ARG GIT_REVISION
|
||||
ARG SOURCE_DATE_EPOCH
|
||||
ARG BUILD_DATE
|
||||
|
||||
RUN groupadd --gid 10001 hub \
|
||||
&& useradd --uid 10001 --gid 10001 --create-home --shell /usr/sbin/nologin hub
|
||||
COPY infrastructure/images/observability/collector.py /opt/hub/collector.py
|
||||
RUN chown -R 10001:10001 /opt/hub
|
||||
|
||||
LABEL org.opencontainers.image.title="itworx-mcp-hub-observability-collector" \
|
||||
org.opencontainers.image.revision="${GIT_REVISION}" \
|
||||
org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \
|
||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.vendor="ITWorx"
|
||||
|
||||
USER 10001:10001
|
||||
EXPOSE 4318
|
||||
ENTRYPOINT ["python", "/opt/hub/collector.py"]
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Minimal internal OTLP/HTTP collector for the optional observer profile.
|
||||
|
||||
It accepts bounded trace batches, keeps only counters in memory, and exposes
|
||||
Prometheus text metrics. It never logs request bodies or trace attributes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from threading import Lock
|
||||
|
||||
MAX_BODY = 1024 * 1024
|
||||
_lock = Lock()
|
||||
_spans = 0
|
||||
_batches = 0
|
||||
|
||||
|
||||
class CollectorHandler(BaseHTTPRequestHandler):
|
||||
server_version = "ITWorxOTLPCollector/1.0"
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
if self.path == "/healthz":
|
||||
self._write(
|
||||
HTTPStatus.OK,
|
||||
b'{"status":"ok","component":"observability-collector"}',
|
||||
"application/json",
|
||||
)
|
||||
return
|
||||
if self.path == "/metrics":
|
||||
with _lock:
|
||||
body = (
|
||||
f"itworx_otel_batches_total {_batches}\\nitworx_otel_spans_total {_spans}\\n"
|
||||
).encode()
|
||||
self._write(HTTPStatus.OK, body, "text/plain; version=0.0.4")
|
||||
return
|
||||
self.send_error(HTTPStatus.NOT_FOUND)
|
||||
|
||||
def do_POST(self) -> None: # noqa: N802
|
||||
global _batches, _spans
|
||||
if self.path != "/v1/traces":
|
||||
self.send_error(HTTPStatus.NOT_FOUND)
|
||||
return
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
if length <= 0 or length > MAX_BODY:
|
||||
self.send_error(HTTPStatus.REQUEST_ENTITY_TOO_LARGE)
|
||||
return
|
||||
try:
|
||||
document = json.loads(self.rfile.read(length))
|
||||
batches = document.get("resourceSpans", [])
|
||||
spans = sum(
|
||||
len(scope.get("spans", []))
|
||||
for batch in batches
|
||||
for scope in batch.get("scopeSpans", [])
|
||||
)
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
self.send_error(HTTPStatus.BAD_REQUEST)
|
||||
return
|
||||
with _lock:
|
||||
_batches += 1
|
||||
_spans += spans
|
||||
self._write(HTTPStatus.ACCEPTED, b"", "text/plain")
|
||||
|
||||
def _write(self, status: HTTPStatus, body: bytes, content_type: str) -> None:
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", content_type)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
if body:
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, _format: str, *_args: object) -> None:
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ThreadingHTTPServer(("0.0.0.0", 4318), CollectorHandler).serve_forever() # noqa: S104
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
fail_secret() {
|
||||
echo "PostgreSQL secret-file validation failed" >&2
|
||||
exit 78
|
||||
}
|
||||
|
||||
source_root=/run/secrets
|
||||
runtime_root=/run/itworx-secrets
|
||||
runtime_uid="$(id -u postgres)"
|
||||
runtime_gid="$(id -g postgres)"
|
||||
|
||||
install -d -o root -g "$runtime_gid" -m 0750 "$runtime_root"
|
||||
|
||||
for name in postgres_superuser_password hub_database_password contextforge_database_password; do
|
||||
source="$source_root/$name"
|
||||
target="$runtime_root/$name"
|
||||
[ -f "$source" ] && [ ! -L "$source" ] && [ -r "$source" ] && [ -s "$source" ] || fail_secret
|
||||
mode="$(stat -c '%a' "$source" 2>/dev/null || true)"
|
||||
case "$mode" in 400|600) ;; *) fail_secret ;; esac
|
||||
[ "$(stat -c '%u' "$source" 2>/dev/null || true)" = "${SECRET_EXPECTED_UID:?}" ] || fail_secret
|
||||
[ "$(stat -c '%g' "$source" 2>/dev/null || true)" = "${SECRET_EXPECTED_GID:?}" ] || fail_secret
|
||||
[ "$(wc -c < "$source")" -le 8192 ] || fail_secret
|
||||
value="$(tr -d '\r\n' < "$source")"
|
||||
[ -n "$value" ] || fail_secret
|
||||
unset value
|
||||
install -o "$runtime_uid" -g "$runtime_gid" -m 0400 "$source" "$target"
|
||||
done
|
||||
|
||||
export POSTGRES_PASSWORD_FILE="$runtime_root/postgres_superuser_password"
|
||||
export POSTGRES_SECRET_ROOT="$runtime_root"
|
||||
export POSTGRES_SECRET_EXPECTED_UID="$runtime_uid"
|
||||
export POSTGRES_SECRET_EXPECTED_GID="$runtime_gid"
|
||||
|
||||
exec /usr/local/bin/docker-entrypoint.sh "$@"
|
||||
@@ -0,0 +1,58 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
FROM node:24.18.1-bookworm-slim@sha256:235600a8101ab264e117b1768e925532262668dc9b581ef1dd7d96ced463b8e7 AS web-build
|
||||
|
||||
WORKDIR /workspace
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
COPY packages/ui/package.json packages/ui/package.json
|
||||
RUN npm install --global pnpm@10.33.0 \
|
||||
&& pnpm install --frozen-lockfile --filter @itworx-mcp-hub/web...
|
||||
COPY apps/web apps/web
|
||||
RUN pnpm --filter @itworx-mcp-hub/web build
|
||||
|
||||
FROM python:3.13.14-slim@sha256:6771159cd4fa5d9bba1258caf0b82e6b73458c694d178ad97c5e925c2d0e1a91
|
||||
|
||||
COPY infrastructure/images/install-base-security-updates.sh /tmp/install-base-security-updates.sh
|
||||
RUN sh /tmp/install-base-security-updates.sh \
|
||||
&& rm /tmp/install-base-security-updates.sh
|
||||
|
||||
ARG SERVICE_NAME=itworx-mcp-hub-service
|
||||
ARG GIT_REVISION
|
||||
ARG SOURCE_DATE_EPOCH
|
||||
ARG BUILD_DATE
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH=/opt/venv/bin:$PATH \
|
||||
SERVICE_NAME=${SERVICE_NAME}
|
||||
|
||||
RUN groupadd --gid 10001 hub \
|
||||
&& useradd --uid 10001 --gid 10001 --create-home --shell /usr/sbin/nologin hub
|
||||
|
||||
COPY pyproject.toml uv.lock /opt/hub/
|
||||
RUN python -m pip install --no-cache-dir uv==0.10.0 \
|
||||
&& cd /opt/hub \
|
||||
&& UV_PROJECT_ENVIRONMENT=/opt/venv uv sync --locked --no-dev --no-install-project \
|
||||
&& /usr/local/bin/python -m pip uninstall --yes uv
|
||||
|
||||
COPY packages /opt/hub/packages
|
||||
COPY services /opt/hub/services
|
||||
COPY scripts /opt/hub/scripts
|
||||
COPY connectors /opt/hub/connectors
|
||||
COPY catalog /opt/hub/catalog
|
||||
COPY contracts /opt/hub/contracts
|
||||
COPY plugins/itworx-gitea /opt/hub/plugins/itworx-gitea
|
||||
COPY infrastructure/database /opt/hub/infrastructure/database
|
||||
COPY --from=web-build /workspace/apps/web/dist /opt/hub/web-dist
|
||||
RUN chown -R 10001:10001 /opt/hub
|
||||
|
||||
LABEL org.opencontainers.image.title="itworx-mcp-hub-${SERVICE_NAME}" \
|
||||
org.opencontainers.image.revision="${GIT_REVISION}" \
|
||||
org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \
|
||||
org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
org.opencontainers.image.vendor="ITWorx"
|
||||
|
||||
WORKDIR /opt/hub
|
||||
USER 10001:10001
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["python", "-m", "services.runtime"]
|
||||
@@ -0,0 +1,129 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from packages.hub_observability import build_telemetry, trace_from_headers, valid_trace_id
|
||||
from packages.hub_runtime.secret_files import SecretFileError, validate_required_secret_paths
|
||||
from services.observability.health import DependencyHealth, aggregate_health
|
||||
|
||||
|
||||
def _validate_startup() -> None:
|
||||
try:
|
||||
validate_required_secret_paths(os.environ.get("REQUIRED_SECRET_FILES", ""))
|
||||
except (SecretFileError, ValueError):
|
||||
sys.stderr.write("required runtime secret validation failed\n")
|
||||
raise SystemExit(78) from None
|
||||
|
||||
|
||||
_validate_startup()
|
||||
|
||||
_telemetry = build_telemetry(os.environ.get("SERVICE_NAME", "unknown"))
|
||||
|
||||
|
||||
def _trace_id(handler: BaseHTTPRequestHandler) -> str:
|
||||
candidate = handler.headers.get("X-Trace-Id", "")
|
||||
if valid_trace_id(candidate):
|
||||
return candidate.lower()
|
||||
return trace_from_headers({"traceparent": handler.headers.get("traceparent", "")}).trace_id
|
||||
|
||||
|
||||
def _dependencies() -> list[DependencyHealth]:
|
||||
raw = os.environ.get("DEPENDENCY_HEALTH_JSON", "")
|
||||
if not raw:
|
||||
return [DependencyHealth(os.environ.get("SERVICE_NAME", "unknown"), "healthy")]
|
||||
try:
|
||||
document = json.loads(raw)
|
||||
except ValueError:
|
||||
return [
|
||||
DependencyHealth(
|
||||
"configuration", "unhealthy", detail="invalid dependency health configuration"
|
||||
)
|
||||
]
|
||||
if not isinstance(document, dict):
|
||||
return [
|
||||
DependencyHealth(
|
||||
"configuration", "unhealthy", detail="dependency health must be an object"
|
||||
)
|
||||
]
|
||||
values = []
|
||||
for name, value in document.items():
|
||||
if not isinstance(value, dict):
|
||||
continue
|
||||
values.append(
|
||||
DependencyHealth(
|
||||
str(name)[:64],
|
||||
str(value.get("status", "unknown")),
|
||||
bool(value.get("required", True)),
|
||||
bool(value.get("authoritative", True)),
|
||||
str(value.get("detail", "")),
|
||||
)
|
||||
)
|
||||
return values or [
|
||||
DependencyHealth("configuration", "unhealthy", detail="no dependencies configured")
|
||||
]
|
||||
|
||||
|
||||
class HealthHandler(BaseHTTPRequestHandler):
|
||||
server_version = "ITWorxHealth/0.2"
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
trace_id = _trace_id(self)
|
||||
if self.path == "/metrics":
|
||||
body = _telemetry.metrics.render_prometheus().encode("utf-8")
|
||||
self._write(HTTPStatus.OK, body, "text/plain; version=0.0.4", trace_id)
|
||||
return
|
||||
if self.path == "/health/aggregate":
|
||||
aggregate = aggregate_health(_dependencies())
|
||||
status = (
|
||||
HTTPStatus.OK if aggregate.status != "unhealthy" else HTTPStatus.SERVICE_UNAVAILABLE
|
||||
)
|
||||
self._write_json(
|
||||
status,
|
||||
aggregate.payload(
|
||||
service=os.environ.get("SERVICE_NAME", "unknown"), trace_id=trace_id
|
||||
),
|
||||
trace_id,
|
||||
)
|
||||
return
|
||||
if self.path not in {"/health", "/healthz", "/ready"}:
|
||||
self.send_error(HTTPStatus.NOT_FOUND)
|
||||
return
|
||||
_telemetry.metrics.inc("itworx_health_requests_total", labels={"path": self.path})
|
||||
_telemetry.logger.emit("HEALTH_REQUEST", trace_id, fields={"path": self.path})
|
||||
self._write_json(
|
||||
HTTPStatus.OK,
|
||||
{
|
||||
"status": "ok",
|
||||
"service": os.environ.get("SERVICE_NAME", "unknown"),
|
||||
"traceId": trace_id,
|
||||
},
|
||||
trace_id,
|
||||
)
|
||||
|
||||
def _write_json(self, status: HTTPStatus, payload: dict[str, object], trace_id: str) -> None:
|
||||
self._write(
|
||||
status,
|
||||
json.dumps(payload, separators=(",", ":")).encode("utf-8"),
|
||||
"application/json",
|
||||
trace_id,
|
||||
)
|
||||
|
||||
def _write(self, status: HTTPStatus, body: bytes, content_type: str, trace_id: str) -> None:
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", content_type)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("X-Trace-Id", trace_id)
|
||||
self.end_headers()
|
||||
if body:
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, _format: str, *_args: object) -> None:
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ThreadingHTTPServer(("0.0.0.0", 8080), HealthHandler).serve_forever() # noqa: S104
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
fail() {
|
||||
echo "Redis secret-file validation failed" >&2
|
||||
exit 78
|
||||
}
|
||||
|
||||
if [ "${REDIS_AUTH_ENABLED:-false}" = "true" ]; then
|
||||
secret=/run/secrets/redis_password
|
||||
[ -f "$secret" ] && [ -r "$secret" ] || fail
|
||||
[ -s "$secret" ] || fail
|
||||
mode="$(stat -c '%a' "$secret" 2>/dev/null || true)"
|
||||
case "$mode" in 400|600) ;; *) fail ;; esac
|
||||
[ "$(stat -c '%u' "$secret" 2>/dev/null || true)" = "${SECRET_EXPECTED_UID:?}" ] || fail
|
||||
[ "$(stat -c '%g' "$secret" 2>/dev/null || true)" = "${SECRET_EXPECTED_GID:?}" ] || fail
|
||||
password="$(tr -d '\r\n' < "$secret")"
|
||||
[ -n "$password" ] || fail
|
||||
umask 077
|
||||
{
|
||||
printf 'save ""\nappendonly no\nmaxmemory-policy allkeys-lru\nrequirepass '
|
||||
printf '%s' "$password" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||||
printf '\n'
|
||||
} > /run/itworx/redis.conf
|
||||
unset password
|
||||
exec redis-server /run/itworx/redis.conf
|
||||
fi
|
||||
|
||||
exec redis-server --save "" --appendonly no --maxmemory-policy allkeys-lru
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "${REDIS_AUTH_ENABLED:-false}" = "true" ]; then
|
||||
secret=/run/secrets/redis_password
|
||||
[ -f "$secret" ] && [ -r "$secret" ] && [ -s "$secret" ] || exit 1
|
||||
REDISCLI_AUTH="$(tr -d '\r\n' < "$secret")"
|
||||
export REDISCLI_AUTH
|
||||
fi
|
||||
|
||||
response="$(redis-cli ping 2>/dev/null)"
|
||||
[ "$response" = "PONG" ]
|
||||
Reference in new issue
Block a user