You were right — everything's containerized, the host ports are just
reverse-proxy targets (+ an optional psql peephole for the db). Hardcoding
them is why the local smoke test face-planted on 5492 (amazon-transcreation
was squatting it) and would have done the same any time anything else
bound :3002 or :5492 on the shared server.
docker-compose.yml:
- ports now reference `${APP_HOST_PORT:-3002}` and `${DB_HOST_PORT:-5492}`.
Defaults match the prior-committed values; override via env vars.
Container-internal ports (3000, 5432) never change.
apache/dow-prod-tracker.conf → .conf.tmpl:
- Moved to a committed template with `${APP_HOST_PORT}` placeholders in
both the WebSocket rewrite and the ProxyPass/ProxyPassReverse lines.
- deploy.sh renders the real .conf from the template on every run with
the chosen port substituted in. Rendered .conf is gitignored so it
can vary per server without drift.
deploy.sh:
- New is_port_free() and find_free_port() using bash's /dev/tcp — no
external tool dependency, works identically on Ubuntu and macOS.
- After `docker compose down` (which frees any of OUR ports), probe for
APP_HOST_PORT starting from 3002 and DB_HOST_PORT from 5492. Pick the
first free port (scan up to 50). Warn if the preferred port was busy.
Honors explicit override: `APP_HOST_PORT=3005 ./deploy.sh` works.
- Exports the chosen ports before `docker compose up` so compose
substitutes them into the `ports:` mappings.
- Renders apache/dow-prod-tracker.conf from the .tmpl with the same
APP_HOST_PORT, every deploy. If the Apache Include line is already in
the vhost, we reload Apache anyway (picks up the re-rendered snippet
in case the port changed).
- Health check URL uses APP_HOST_PORT.
- "Deploy complete" banner now prints the chosen ports.
.gitignore:
- Added docker-compose.override.yml (per-machine local overrides) and
apache/dow-prod-tracker.conf (rendered by deploy.sh, varies per server).
DEPLOY.md updated with the auto-detection behaviour and override recipe.
Sanity-checked locally:
- is_port_free correctly identifies 5492 busy (amazon-transcreation),
5493 busy (our smoke-test db), 3002 busy (Docker Desktop grabs 3000-3002
on this Mac), and picks 5494/3003 respectively.
- `APP_HOST_PORT=3999 DB_HOST_PORT=5999 docker compose config` produces
published ports 3999 and 5999.
- `bash -n deploy.sh` clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- basePath /dow-prod-tracker, DB name dow_prod_tracker
- docker-compose: name: dow-prod-tracker (volume isolation on shared server), ports 3002/5492
- OMG webhook env vars (secret + insecure toggle)
- NEXT_PUBLIC_AUTH_ENTRA_ENABLED feature flag (MVP uses local auth)
- Dow logo at public/navbar-logo.png
- apache/hp-prod-tracker.conf → apache/dow-prod-tracker.conf
- Text rebrand across README, SETUP, CLAUDE.md, docs, UI labels
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Token exchange now happens entirely in the browser via @azure/msal-browser
(PKCE, no client_secret — correct for Azure SPA registrations)
- Browser stays on /hp-prod-tracker/login throughout; the /api/auth/callback
URL never appears in the address bar
- New /api/auth/sso route validates the id_token (jose + Azure JWKS),
creates User/Account/Session in Prisma, and sets the authjs session cookie
- Auth.js retained only for session reading (auth()) and signOut()
- Fix dev bypass safety gate: use NODE_ENV !== production instead of
absence of AUTH_MICROSOFT_ENTRA_ID_SECRET
- Rename env vars: AUTH_MICROSOFT_ENTRA_ID_ID → AZURE_CLIENT_ID,
AUTH_MICROSOFT_ENTRA_ID_TENANT_ID → AZURE_TENANT_ID, remove AUTH_URL
- Remove /api/auth Apache proxy rule (no longer needed)
- Delete OAuthRelay.tsx, add MsalLogin.tsx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Override authorization redirect_uri to match Azure SPA portal registration
(login page URL instead of Auth.js callback URL)
- Custom token.request: public client PKCE exchange — no client_secret sent
- Add OAuthRelay client component: forwards ?code&state from login page to
/api/auth/callback/microsoft-entra-id via window.location.replace
- Add AZURE_REDIRECT_URI env var to docker-compose.yml and .env.example
- Remove AUTH_MICROSOFT_ENTRA_ID_SECRET (SPA registrations don't issue secrets)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auth.js needs AUTH_URL to build the correct redirect URI
including the /hp-prod-tracker basePath.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemma 4 loads successfully, supports tool calling with proper
structured output, and responds in ~100ms after initial load.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Larger models (mistral-large 122B, qwen3-coder 30B, gpt-oss 20B) all
fail to load due to resource limits. mistral:latest (7.2B) loads and
responds successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mistral-large:latest requires 420GB RAM, server only has 345GB.
qwen3-coder:30b is a 30.5B MoE model that fits in ~20GB with good
tool calling and reasoning capabilities.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Claude is primary, Ollama (internal GPU server) is automatic fallback
- Provider auto-selects: Claude if API key set, else Ollama if reachable
- Ollama uses mistral-large:latest for chat with full tool calling support
- Removed local Ollama Docker service — uses remote at 10.24.42.219
- Chat panel badge shows "Claude" (purple) or "Ollama" (orange)
- OLLAMA_CHAT_HOST and OLLAMA_CHAT_MODEL env vars for configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The env var was in .env but not listed in docker-compose environment
block, so the container never received it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Source code is now on Bitbucket — IT builds from source directly.
Docker Hub and Cloudflare Tunnel are no longer needed. Removed
profiles gate from app service so docker compose up -d works without
flags. Updated .env.example with organized sections and comments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FFmpeg in Docker for transcoding, thumbnail extraction, and metadata
parsing. Videos stored in /data/uploads (mounted volume), served via
streaming API route with Range headers and HLS segment caching. Upload
flow: stream-write MP4 → ffprobe metadata → thumbnail → async HLS
transcode → update revision status to ready.
New files:
- video-service.ts: FFmpeg/ffprobe wrapper (HLS, thumbnails, metadata)
- /api/uploads/[...path]: streaming file server with Range support
Modified:
- upload-service.ts: video handling, 500MB limit, async HLS pipeline
- upload route: accepts video/referenceVideo types
- Dockerfile: ffmpeg + /data/uploads directory
- docker-compose.yml: uploads_data volume
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add event bus for dispatching automation events with handlers.
- Create rule engine to evaluate events against defined triggers.
- Introduce chat provider to interface with Claude API and Ollama fallback.
- Define tool schemas for Claude-compatible operations.
- Implement tool executor to map tool calls to service layer functions.
- Develop automation service for CRUD operations on rules and event handling.
- Implemented Smart Search Panel component for enhanced project and deliverable search functionality.
- Introduced useSemanticSearch and useOllamaHealth hooks for managing search queries and AI availability.
- Developed embedding-service to generate and store vector embeddings for projects and deliverables.
- Created semantic-search-service to handle vector search, structural query detection, and LLM summarization.
- Added support for hybrid search combining structural filters and semantic queries.
- Integrated UI components for displaying search results and user interactions.