This commit is contained in:
2026-09-11 14:05:09 +03:00
parent d4dd7be4ef
commit 3079c4edc2
3 changed files with 87 additions and 13 deletions

View File

@@ -15,6 +15,17 @@ COPY help_processor.py /app/help_processor.py
COPY help_codes.py /app/help_codes.py COPY help_codes.py /app/help_codes.py
COPY Bairaci.png /app/Bairaci.png COPY Bairaci.png /app/Bairaci.png
# Git SHA after COPY so pip layer stays cached.
# Coolify: Advanced → Include Source Commit in Build = ON
# (или Build ARG GIT_SHA). Иначе се ползва hash на копираните файлове.
ARG SOURCE_COMMIT=
ARG GIT_SHA=
RUN GIT_SHA="$GIT_SHA" SOURCE_COMMIT="$SOURCE_COMMIT" python -c 'import hashlib,os,pathlib,sys; sha=(os.environ.get("GIT_SHA") or os.environ.get("SOURCE_COMMIT") or "").strip(); h=hashlib.sha1(); \
[h.update(p.as_posix().encode()+p.read_bytes()) for p in sorted(pathlib.Path("/app").rglob("*")) if p.is_file() and "/__pycache__/" not in p.as_posix() and p.name!="BUILD_ID"]; \
pathlib.Path("/app/BUILD_ID").write_text(sha or h.hexdigest()[:12], encoding="utf-8"); \
print("BUILD_ID", pathlib.Path("/app/BUILD_ID").read_text(), file=sys.stderr)'
ENV APP_GIT_SHA=${GIT_SHA}
ENV OUTPUT_DIR=/mnt/mssql/share/RIP/RIP_Help_Source/Output ENV OUTPUT_DIR=/mnt/mssql/share/RIP/RIP_Help_Source/Output
ENV HOME_IMAGE=/app/Bairaci.png ENV HOME_IMAGE=/app/Bairaci.png
ENV HELP_REMOTE_ROOT=/mnt/mssql/share/RIP/RIP_Help_Source/Output ENV HELP_REMOTE_ROOT=/mnt/mssql/share/RIP/RIP_Help_Source/Output

View File

@@ -9,12 +9,13 @@ Endpoint-и:
GET /api/search — търсене (?q=&prefix=) GET /api/search — търсене (?q=&prefix=)
GET /api/section/{code} — една секция с пълен текст GET /api/section/{code} — една секция с пълен текст
POST /api/keywords/{code} — обновяване на keywords POST /api/keywords/{code} — обновяване на keywords
GET /healthz — health + OUTPUT_DIR статус GET /healthz — health + OUTPUT_DIR статус + version/git
Env: Env:
HELP_DB_CONN — libpq Postgres HELP_DB_CONN — libpq Postgres
OUTPUT_DIR — локален/mounted път към секциите (default: share) OUTPUT_DIR — локален/mounted път към секциите (default: share)
HOME_IMAGE — път към home картинка HOME_IMAGE — път към home картинка
GIT_SHA / SOURCE_COMMIT / COMMIT_SHA — git SHA (Coolify / Docker build-arg)
""" """
from __future__ import annotations from __future__ import annotations
@@ -22,6 +23,7 @@ from __future__ import annotations
import json import json
import os import os
import re import re
import subprocess
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@@ -56,9 +58,56 @@ HOME_IMAGE = os.getenv("HOME_IMAGE") # absolute path or None
_IMG_PLACEHOLDER_RE = re.compile(r"\[IMG:\s*([^\]]+?)\s*\]") _IMG_PLACEHOLDER_RE = re.compile(r"\[IMG:\s*([^\]]+?)\s*\]")
BASE_DIR = Path(__file__).parent BASE_DIR = Path(__file__).parent
APP_VERSION = "0.3.0"
_NO_CACHE = {"Cache-Control": "no-store, no-cache, max-age=0"}
def _short_id(value: str, n: int = 7) -> str:
value = value.strip()
if not value:
return ""
return value[:n] if len(value) > n else value
def _resolve_git() -> str:
"""Git SHA / build id that changes on each image rebuild.
Prefers Coolify/runtime env, then /app/BUILD_ID (baked in Docker),
then local ``git rev-parse``.
"""
for key in ("GIT_SHA", "APP_GIT_SHA", "SOURCE_COMMIT", "COMMIT_SHA"):
raw = (os.getenv(key) or "").strip()
if raw and raw.lower() not in {"unknown", "none", "null"}:
return _short_id(raw)
build_id = Path("/app/BUILD_ID")
if build_id.is_file():
try:
text = build_id.read_text(encoding="utf-8").strip()
if text:
return _short_id(text, 7 if len(text) >= 40 else 12)
except OSError:
pass
try:
repo = BASE_DIR.parent
r = subprocess.run(
["git", "rev-parse", "--short=7", "HEAD"],
cwd=str(repo),
capture_output=True,
text=True,
timeout=1,
)
if r.returncode == 0 and r.stdout.strip():
return r.stdout.strip()
except (OSError, subprocess.SubprocessError):
pass
return "dev"
GIT_SHA = _resolve_git()
from webapp import rescan as rescan_mod from webapp import rescan as rescan_mod
app = FastAPI(title="RIP Help System", version="0.3.0") app = FastAPI(title="RIP Help System", version=APP_VERSION)
templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
app.include_router(rescan_mod.router) app.include_router(rescan_mod.router)
@@ -502,7 +551,7 @@ def api_delete_file_extractions(
conn.close() conn.close()
@app.get("/healthz", summary="Статус на услугата и БД; output_dir_ok, txt_count.") @app.get("/healthz", summary="Статус на услугата и БД; version/git, output_dir_ok, txt_count.")
def healthz(): def healthz():
db_status = "ok" db_status = "ok"
try: try:
@@ -513,8 +562,16 @@ def healthz():
conn.close() conn.close()
except Exception as e: except Exception as e:
return JSONResponse( return JSONResponse(
{"status": "error", "db": str(e), "output_dir": str(OUTPUT_DIR)}, {
"ok": False,
"status": "error",
"version": f"{APP_VERSION}+{GIT_SHA}",
"git": GIT_SHA,
"db": str(e),
"output_dir": str(OUTPUT_DIR),
},
status_code=503, status_code=503,
headers=_NO_CACHE,
) )
out_ok = OUTPUT_DIR.is_dir() out_ok = OUTPUT_DIR.is_dir()
@@ -528,11 +585,17 @@ def healthz():
except Exception: except Exception:
pass pass
return { return JSONResponse(
{
"ok": True,
"status": "ok", "status": "ok",
"version": f"{APP_VERSION}+{GIT_SHA}",
"git": GIT_SHA,
"db": db_status, "db": db_status,
"output_dir": str(OUTPUT_DIR), "output_dir": str(OUTPUT_DIR),
"output_dir_ok": out_ok, "output_dir_ok": out_ok,
"txt_count": txt_count, "txt_count": txt_count,
"sample": sample, "sample": sample,
} },
headers=_NO_CACHE,
)

View File

@@ -476,7 +476,7 @@
<p>JSON API за четене на help секции, търсене и (опционално) rescan. Готово за връзка с външни системи.</p> <p>JSON API за четене на help секции, търсене и (опционално) rescan. Готово за връзка с външни системи.</p>
<div class="api-ep"><span class="method">GET</span><code>/healthz</code> <div class="api-ep"><span class="method">GET</span><code>/healthz</code>
<div class="desc">Статус на услугата и БД; <code>output_dir_ok</code>, <code>txt_count</code>.</div></div> <div class="desc">Статус на услугата и БД; <code>version</code>, <code>git</code>, <code>output_dir_ok</code>, <code>txt_count</code>.</div></div>
<div class="api-ep"><span class="method">GET</span><code>/api/sections?prefix=RIP</code> <div class="api-ep"><span class="method">GET</span><code>/api/sections?prefix=RIP</code>
<div class="desc">Списък секции (метаданни + preview текст). Филтър по <code>prefix</code>.</div></div> <div class="desc">Списък секции (метаданни + preview текст). Филтър по <code>prefix</code>.</div></div>
<div class="api-ep"><span class="method">GET</span><code>/api/search?q=…&amp;prefix=RIP</code> <div class="api-ep"><span class="method">GET</span><code>/api/search?q=…&amp;prefix=RIP</code>