Portal live on Dokploy: ODBC Kerberos library and India time in the image

Dockerfile installs libgssapi-krb5-2 (ODBC Driver 18 could not load
without it) and runs on TZ=Asia/Kolkata with tzdata (the server is UTC).
Context.md: how the Portal was deployed (Drop zip, SQL login, DB host).
71 test files, 1224 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-27 16:03:44 +05:30
1 parent c049cece23
commit 83a0cf65ae
3 files changed
+64 -8

No files matched your search

+35 -5
View File
@@ -187,7 +187,8 @@ Get-ChildItem tests\test_*.py | ForEach-Object { "$($_.Name): $((python $_.FullN
- Tests run offline against fake HTML pages in `tests/fixtures/` and fake DB - Tests run offline against fake HTML pages in `tests/fixtures/` and fake DB
connections; they never touch eCW or SQL Server (`test_cdp_browser.py` connections; they never touch eCW or SQL Server (`test_cdp_browser.py`
starts a local Chromium on a debug port). As of 2026-10-27: 71 test starts a local Chromium on a debug port). As of 2026-10-27: 71 test
files, **1222 tests** (full run 2026-10-27: 1222/1222) (earlier flaky, each files, **1224 tests** (full run 2026-10-27: 1222/1222; the 2 Dockerfile
tests since, with test_portal_signin.py 24/24) (earlier flaky, each
failed once and passed on rerun: test_verify_no_live_writes, failed once and passed on rerun: test_verify_no_live_writes,
depression_dx_pointer, progress_note_wait — watch them) (about 5 minutes for all of them). depression_dx_pointer, progress_note_wait — watch them) (about 5 minutes for all of them).
@@ -623,8 +624,9 @@ the Portal (restarted 2026-10-26, [portal] terminal tab).
History: `git log --oneline`. History: `git log --oneline`.
Last code commits: `70ba709` (2026-10-27, Portal server mode for Dokploy — Last code commits: `cc3a01a` (2026-10-27, Portal container on India time —
§10), `f99dc17` (2026-10-27, Portal roles Super Admin / §10), `69c51d8` (2026-10-27, Dockerfile: libgssapi-krb5-2 for ODBC Driver
18 — §10), `70ba709` (2026-10-27, Portal server mode for Dokploy — §10), `f99dc17` (2026-10-27, Portal roles Super Admin /
Admin / Viewer — §2), `61338b4` (2026-10-26, Sign-in page redesign + tab Admin / Viewer — §2), `61338b4` (2026-10-26, Sign-in page redesign + tab
icon — §5), `a436489` (2026-10-26, Portal look: aligned header / icon — §5), `a436489` (2026-10-26, Portal look: aligned header /
footer, 7-day chart, today's reasons, pass rate — §5), `2d4d4fc` footer, 7-day chart, today's reasons, pass rate — §5), `2d4d4fc`
@@ -762,8 +764,36 @@ test claim ids — new posts must not (§7).
- **Gitea:** one overall snapshot update at the end of a day, when the - **Gitea:** one overall snapshot update at the end of a day, when the
operator asks (from the clone `D:\ecw-charge-validation`; operator operator asks (from the clone `D:\ecw-charge-validation`; operator
pushes; 2026-10-26: `ba15a57` → `120fd68`). pushes; 2026-10-26: `ba15a57` → `120fd68`).
- **Portal for the client — Gitea + Dokploy (asked 2026-10-23; server - **Portal for the client — Gitea + Dokploy (asked 2026-10-23; LIVE on
mode built 2026-10-27 `70ba709`, not deployed yet).** `Dockerfile` Dokploy 2026-10-27, `cc3a01a`, Ecare network only).** URL
`http://10.200.0.15:8085` (Dokploy project Ecw_ChargeValidation →
production → app **Portal**; port 8085 → 8000). The operator signed in
there as Super Admin; Dashboard, Users, Sign-ins work. How it got there:
- **Source = Drop (zip upload), not Git:** Dokploy cannot clone from
Gitea (`server certificate verification failed`, Traefik's self-signed
cert); Gitea has no internal http port and no SSH; the operator cannot
open Dokploy Settings → Git Providers; the OTS-Team Gitea account does
not see `automation-tools`. So: `git archive --format=zip HEAD
Dockerfile requirements-portal.txt src web` → `D:\ecw-portal-deploy\
portal-<hash>.zip` → app → General → Provider → **Drop** → select →
the **Deploy under the drop box** (the top Deploy / Reload rebuilds the
last upload). Long term: ask the Dokploy admin for a Gitea provider.
- **DB:** the SQL Server is on the operator's PC (`MECARE331` =
10.50.2.131), instance TCP port 58468 (dynamic — a SQL restart may move
it; set a static port later). SQL login **`cv_portal`** (made in SSMS
2026-10-27: db_datareader + INSERT/UPDATE `CV_PortalUser`, INSERT
`CV_PortalSignIn`); its password only in Dokploy's env
(`RESULTS_DB_CONN`), never here or in chat. The Portal works only while
that PC is on.
- **Fixes found live:** `69c51d8` the image needs `libgssapi-krb5-2` by
name (autoremove took it; ODBC Driver 18 "Can't open lib ... file not
found"); `cc3a01a` `TZ=Asia/Kolkata` + `tzdata` (the server is UTC: a
15:45 IST Sign-in read 10:15; three such rows stay in CV_PortalSignIn
from 2026-10-27 ~15:45). Errors seen: `SQLSTATE 01000` = driver not
loading; `28000` = login missing / wrong password.
- Sessions are in memory: every Deploy / Reload signs everyone out.
- For the client: needs a public HTTPS URL (or VPN) from IT, and the DB
off a desktop PC. `Dockerfile`
(python 3.13-slim + ODBC Driver 18, copies only `src/` + `web/`, (python 3.13-slim + ODBC Driver 18, copies only `src/` + `web/`,
`requirements-portal.txt`); env in Dokploy, never in Git: `requirements-portal.txt`); env in Dokploy, never in Git:
`RESULTS_DB_CONN` (a SQL login; a Linux container cannot use `RESULTS_DB_CONN` (a SQL login; a Linux container cannot use
+5 -3
View File
@@ -4,7 +4,8 @@
# and PORTAL_URLS in Dokploy's environment — never in Git (.env.example). # and PORTAL_URLS in Dokploy's environment — never in Git (.env.example).
FROM python:3.13-slim-bookworm FROM python:3.13-slim-bookworm
# Microsoft ODBC Driver 18 for SQL Server, for pyodbc. # Microsoft ODBC Driver 18 for SQL Server, for pyodbc. libgssapi-krb5-2 by
# name: the driver needs it, and autoremove took it with curl otherwise.
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends curl gnupg ca-certificates \ && apt-get install -y --no-install-recommends curl gnupg ca-certificates \
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \ && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
@@ -12,7 +13,7 @@ RUN apt-get update \
&& curl -fsSL https://packages.microsoft.com/config/debian/12/prod.list \ && curl -fsSL https://packages.microsoft.com/config/debian/12/prod.list \
-o /etc/apt/sources.list.d/mssql-release.list \ -o /etc/apt/sources.list.d/mssql-release.list \
&& apt-get update \ && apt-get update \
&& ACCEPT_EULA=Y apt-get install -y --no-install-recommends msodbcsql18 \ && ACCEPT_EULA=Y apt-get install -y --no-install-recommends msodbcsql18 libgssapi-krb5-2 tzdata \
&& apt-get purge -y curl gnupg && apt-get autoremove -y \ && apt-get purge -y curl gnupg && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@@ -24,7 +25,8 @@ COPY web ./web
RUN useradd --create-home portal RUN useradd --create-home portal
USER portal USER portal
ENV PORTAL_SERVER=1 PYTHONUNBUFFERED=1 # India time, like the bot's rows: Sign-ins and "today" in IST, not UTC.
ENV PORTAL_SERVER=1 PYTHONUNBUFFERED=1 TZ=Asia/Kolkata
EXPOSE 8000 EXPOSE 8000
CMD ["python", "-m", "uvicorn", "web.app:app", "--host", "0.0.0.0", \ CMD ["python", "-m", "uvicorn", "web.app:app", "--host", "0.0.0.0", \
"--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"] "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"]
+24
View File
@@ -378,6 +378,30 @@ def the_portal_url_gives_the_hosts_origins_and_a_secure_cookie():
assert "; secure" in r.headers["set-cookie"].lower(), r.headers assert "; secure" in r.headers["set-cookie"].lower(), r.headers
@case
def the_container_keeps_the_odbc_drivers_kerberos_library():
"""Dokploy, 2026-10-27: autoremove took libgssapi-krb5-2 with curl, so
ODBC Driver 18 could not load ("Can't open lib ... file not found")."""
docker = (pathlib.Path(__file__).resolve().parent.parent
/ "Dockerfile").read_text(encoding="utf-8")
line = next(l for l in docker.splitlines() if "msodbcsql18" in l
and "install" in l)
assert "libgssapi-krb5-2" in line, line
@case
def the_container_keeps_india_time():
"""Dokploy, 2026-10-27: the server clock is UTC, so a Sign-in at 15:45
IST was recorded 10:15 and "today" would turn at 18:30 IST. The bot's
rows are IST; the Portal must be too."""
docker = (pathlib.Path(__file__).resolve().parent.parent
/ "Dockerfile").read_text(encoding="utf-8")
assert "TZ=Asia/Kolkata" in docker
line = next(l for l in docker.splitlines() if "msodbcsql18" in l
and "install" in l)
assert "tzdata" in line, line
@case @case
def no_signed_in_page_carries_an_inline_style(): def no_signed_in_page_carries_an_inline_style():
app, _, _ = _portal() app, _, _ = _portal()