Make the app's port configurable via DEVPLACE_PORT
make dev/prod hardcoded uvicorn to port 10500 with no way to override it, so config.PORT (and everything derived from it - INTERNAL_BASE_URL, Devii's own base URL defaults) stayed 10500 regardless of what port the process actually bound to. DEVPLACE_PORT now drives all of it, defaulting to 10500. Docker's existing PORT var (the externally published port via nginx) is unrelated and untouched; its app container pins DEVPLACE_PORT to 10500 explicitly so a bare-metal .env value can never leak in and desync it from the Dockerfile's fixed internal bind port. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -29,9 +29,16 @@ SECRET_KEY=change-me
|
|||||||
# from the request.
|
# from the request.
|
||||||
DEVPLACE_SITE_URL=
|
DEVPLACE_SITE_URL=
|
||||||
|
|
||||||
# Host port the nginx front door binds.
|
# Host port the nginx front door binds (Docker only - the app container's own
|
||||||
|
# internal port stays fixed).
|
||||||
PORT=10500
|
PORT=10500
|
||||||
|
|
||||||
|
# Port the uvicorn process itself binds to for `make dev`/`make prod` (bare
|
||||||
|
# metal, no Docker/nginx in front). Also what the app calls itself on
|
||||||
|
# internally (DEVII_BASE_URL default, INTERNAL_GATEWAY_URL). Unrelated to
|
||||||
|
# PORT above - leave unset unless running bare metal on a non-default port.
|
||||||
|
# DEVPLACE_PORT=10500
|
||||||
|
|
||||||
# nginx upload ceiling. Must be >= the admin-configurable max_upload_size_mb.
|
# nginx upload ceiling. Must be >= the admin-configurable max_upload_size_mb.
|
||||||
NGINX_MAX_BODY_SIZE=50m
|
NGINX_MAX_BODY_SIZE=50m
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ Tests run on port 10501 with a tempfile SQLite DB and a dedicated `DEVPLACE_DATA
|
|||||||
| Var | Default | Purpose |
|
| Var | Default | Purpose |
|
||||||
|-----|---------|---------|
|
|-----|---------|---------|
|
||||||
| `DEVPLACE_DATABASE_URL` | `sqlite:///<repo>/data/devplace.db` | Override DB path (tests use this) |
|
| `DEVPLACE_DATABASE_URL` | `sqlite:///<repo>/data/devplace.db` | Override DB path (tests use this) |
|
||||||
|
| `DEVPLACE_PORT` | `10500` | Port the app itself binds to bare-metal (`make dev`/`make prod`, wired to uvicorn's `--port`; tests use their own `DEVPLACE_TEST_PORT`, default `10501`). Also `config.PORT`'s source, so `DEVPLACE_INTERNAL_BASE_URL`'s default and Devii's self-dial URL (`DEFAULT_BASE_URL`/`INSTANCE_ORIGIN_DEFAULT`) follow it automatically. The Docker app container pins it to `10500` in `docker-compose.yml` regardless of `.env` - Docker's externally reachable port is the unrelated `PORT` var (nginx's host mapping), never this one. |
|
||||||
| `SECRET_KEY` | hardcoded fallback | Session signing |
|
| `SECRET_KEY` | hardcoded fallback | Session signing |
|
||||||
| `DEVPLACE_DISABLE_SERVICES` | unset | When `1`, NewsService and other background services skip start (set by test conftest) |
|
| `DEVPLACE_DISABLE_SERVICES` | unset | When `1`, NewsService and other background services skip start (set by test conftest) |
|
||||||
| `PLAYWRIGHT_HEADLESS` | `1` in tests | Toggle headed mode |
|
| `PLAYWRIGHT_HEADLESS` | `1` in tests | Toggle headed mode |
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ LOCUST_RUN_TIME ?= 120s
|
|||||||
LOCUST_WEB_WORKERS ?= 4
|
LOCUST_WEB_WORKERS ?= 4
|
||||||
WEB_WORKERS ?= $(shell nproc 2>/dev/null || echo 2)
|
WEB_WORKERS ?= $(shell nproc 2>/dev/null || echo 2)
|
||||||
DEVPLACE_RATE_LIMIT ?= 1000000
|
DEVPLACE_RATE_LIMIT ?= 1000000
|
||||||
|
DEVPLACE_PORT ?= 10500
|
||||||
|
|
||||||
VENV ?= $(CURDIR)/.venv
|
VENV ?= $(CURDIR)/.venv
|
||||||
PYTHON := $(VENV)/bin/python
|
PYTHON := $(VENV)/bin/python
|
||||||
@@ -38,10 +39,10 @@ install: $(PYTHON)
|
|||||||
touch $(VENV_STAMP)
|
touch $(VENV_STAMP)
|
||||||
|
|
||||||
dev: $(VENV_STAMP)
|
dev: $(VENV_STAMP)
|
||||||
$(PYTHON) -m uvicorn devplacepy.main:app --reload --reload-dir devplacepy --host 0.0.0.0 --port 10500 --backlog 4096
|
DEVPLACE_PORT=$(DEVPLACE_PORT) $(PYTHON) -m uvicorn devplacepy.main:app --reload --reload-dir devplacepy --host 0.0.0.0 --port $(DEVPLACE_PORT) --backlog 4096
|
||||||
|
|
||||||
prod: $(VENV_STAMP)
|
prod: $(VENV_STAMP)
|
||||||
DEVPLACE_STATIC_VERSION=$$(date +%s) DEVPLACE_TEMPLATE_AUTO_RELOAD=0 DEVPLACE_WEB_WORKERS=$(WEB_WORKERS) $(PYTHON) -m uvicorn devplacepy.main:app --host 0.0.0.0 --port 10500 --workers $(WEB_WORKERS) --backlog 8192 --proxy-headers --forwarded-allow-ips '*'
|
DEVPLACE_STATIC_VERSION=$$(date +%s) DEVPLACE_TEMPLATE_AUTO_RELOAD=0 DEVPLACE_WEB_WORKERS=$(WEB_WORKERS) DEVPLACE_PORT=$(DEVPLACE_PORT) $(PYTHON) -m uvicorn devplacepy.main:app --host 0.0.0.0 --port $(DEVPLACE_PORT) --workers $(WEB_WORKERS) --backlog 8192 --proxy-headers --forwarded-allow-ips '*'
|
||||||
|
|
||||||
delete-pyc:
|
delete-pyc:
|
||||||
find . -name "__pycache__" -type d -prune -exec rm -rf {} + 2>/dev/null || true
|
find . -name "__pycache__" -type d -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|||||||
@@ -260,6 +260,8 @@ The log is **administrator-only**. `/admin/audit-log` is a paginated, filterable
|
|||||||
|---------|---------|---------|
|
|---------|---------|---------|
|
||||||
| `DEVPLACE_DATABASE_URL` | `sqlite:///<repo>/data/devplace.db` | Database connection string |
|
| `DEVPLACE_DATABASE_URL` | `sqlite:///<repo>/data/devplace.db` | Database connection string |
|
||||||
| `DEVPLACE_DATA_DIR` | `<repo>/data` | Single root for every runtime/user-generated artifact (DB, uploads, VAPID keys, locks, bot state, job staging, container workspaces), outside the package and not served via `/static`. Point at a volume in production. Defined once in `config.py` (`DATA_PATHS` registry, created by `ensure_data_dirs()`) |
|
| `DEVPLACE_DATA_DIR` | `<repo>/data` | Single root for every runtime/user-generated artifact (DB, uploads, VAPID keys, locks, bot state, job staging, container workspaces), outside the package and not served via `/static`. Point at a volume in production. Defined once in `config.py` (`DATA_PATHS` registry, created by `ensure_data_dirs()`) |
|
||||||
|
| `DEVPLACE_PORT` | `10500` | Port the app itself binds to (`make dev`/`make prod`, and what `Makefile`'s `dev`/`prod` targets pass to uvicorn's `--port`). Also feeds `config.PORT`, so `DEVPLACE_INTERNAL_BASE_URL`'s default and Devii's own self-dial URL follow it automatically. Bare metal only - the Docker app container always binds its fixed internal port regardless of this var; for Docker, use `PORT` (below) to change the externally reachable port |
|
||||||
|
| `PORT` | `10500` | Docker only: the host port `docker-compose.yml` publishes nginx on (`127.0.0.1:${PORT}:80`). Unrelated to `DEVPLACE_PORT` above - it never reaches the app container |
|
||||||
| `SECRET_KEY` | hardcoded fallback | Session signing key |
|
| `SECRET_KEY` | hardcoded fallback | Session signing key |
|
||||||
| `DEVPLACE_VAPID_SUB` | `mailto:retoor@molodetz.nl` | Contact address in the VAPID JWT `sub` claim |
|
| `DEVPLACE_VAPID_SUB` | `mailto:retoor@molodetz.nl` | Contact address in the VAPID JWT `sub` claim |
|
||||||
| `DEVPLACE_INTERNAL_BASE_URL` | `http://localhost:10500` | Base URL the platform's own services dial for the AI gateway |
|
| `DEVPLACE_INTERNAL_BASE_URL` | `http://localhost:10500` | Base URL the platform's own services dial for the AI gateway |
|
||||||
@@ -1206,7 +1208,7 @@ The version sits in the **path**, not a query string, because the frontend is un
|
|||||||
|
|
||||||
### Bare-metal alternative
|
### Bare-metal alternative
|
||||||
|
|
||||||
`make prod` runs the same app without containers (`uvicorn ... --workers $(WEB_WORKERS) --proxy-headers`, where `WEB_WORKERS` defaults to `nproc`) from the project root, sharing the identical database and files. Note it binds port 10500, so it conflicts with the Docker front door on the same port - run one, or set a different `PORT`.
|
`make prod` runs the same app without containers (`uvicorn ... --workers $(WEB_WORKERS) --proxy-headers`, where `WEB_WORKERS` defaults to `nproc`) from the project root, sharing the identical database and files. It binds port 10500 by default, so it conflicts with the Docker front door on the same port - run one, or set `DEVPLACE_PORT=<other-port> make prod` (also honoured by `make dev`).
|
||||||
|
|
||||||
### Multi-worker safety
|
### Multi-worker safety
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ SECRET_KEY = environ.get("SECRET_KEY", "devplace-secret-key-change-in-production
|
|||||||
SECONDS_PER_DAY = 86400
|
SECONDS_PER_DAY = 86400
|
||||||
SESSION_MAX_AGE = SECONDS_PER_DAY * 7
|
SESSION_MAX_AGE = SECONDS_PER_DAY * 7
|
||||||
SESSION_MAX_AGE_REMEMBER = SECONDS_PER_DAY * 30
|
SESSION_MAX_AGE_REMEMBER = SECONDS_PER_DAY * 30
|
||||||
PORT = 10500
|
PORT = int(environ.get("DEVPLACE_PORT", "10500"))
|
||||||
SITE_URL = environ.get("DEVPLACE_SITE_URL", "").rstrip("/")
|
SITE_URL = environ.get("DEVPLACE_SITE_URL", "").rstrip("/")
|
||||||
|
|
||||||
PRESENCE_TIMEOUT_SECONDS = int(environ.get("DEVPLACE_PRESENCE_TIMEOUT_SECONDS", "60"))
|
PRESENCE_TIMEOUT_SECONDS = int(environ.get("DEVPLACE_PRESENCE_TIMEOUT_SECONDS", "60"))
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ from devplacepy.config import (
|
|||||||
INTERNAL_MODEL,
|
INTERNAL_MODEL,
|
||||||
DEVII_TASKS_DB,
|
DEVII_TASKS_DB,
|
||||||
DEVII_LESSONS_DB,
|
DEVII_LESSONS_DB,
|
||||||
|
PORT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_AI_URL = INTERNAL_GATEWAY_URL
|
DEFAULT_AI_URL = INTERNAL_GATEWAY_URL
|
||||||
DEFAULT_AI_MODEL = INTERNAL_MODEL
|
DEFAULT_AI_MODEL = INTERNAL_MODEL
|
||||||
DEFAULT_BASE_URL = "http://127.0.0.1:10500"
|
DEFAULT_BASE_URL = f"http://127.0.0.1:{PORT}"
|
||||||
CONTEXT_WINDOW_TOKENS = 1_048_576
|
CONTEXT_WINDOW_TOKENS = 1_048_576
|
||||||
MAX_OUTPUT_TOKENS = 384_000
|
MAX_OUTPUT_TOKENS = 384_000
|
||||||
SYSTEM_RESERVE_TOKENS = 64_000
|
SYSTEM_RESERVE_TOKENS = 64_000
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from devplacepy.config import PORT
|
||||||
from devplacepy.services.base import BaseService, ConfigField
|
from devplacepy.services.base import BaseService, ConfigField
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
@@ -18,7 +19,7 @@ from .tasks.scheduler import GlobalScheduler, retire
|
|||||||
|
|
||||||
logger = logging.getLogger("devii.service")
|
logger = logging.getLogger("devii.service")
|
||||||
|
|
||||||
INSTANCE_ORIGIN_DEFAULT = "http://127.0.0.1:10500"
|
INSTANCE_ORIGIN_DEFAULT = f"http://127.0.0.1:{PORT}"
|
||||||
|
|
||||||
|
|
||||||
class DeviiService(BaseService):
|
class DeviiService(BaseService):
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ The gateway records one row per upstream call (chat, vision, passthrough) and su
|
|||||||
|
|
||||||
The gateway is the only place that holds real provider URLs/models/keys. Every other AI consumer (news, bots, Devii guests) points at it by default and never touches a provider key:
|
The gateway is the only place that holds real provider URLs/models/keys. Every other AI consumer (news, bots, Devii guests) points at it by default and never touches a provider key:
|
||||||
|
|
||||||
- Defaults live in `config.py`: `INTERNAL_GATEWAY_URL` (`http://localhost:10500/openai/v1/chat/completions`, override with `DEVPLACE_INTERNAL_BASE_URL`) and `INTERNAL_MODEL` (`molodetz`). `news_ai_url`, `bot_api_url`, `devii_ai_url` default to `INTERNAL_GATEWAY_URL`; their model defaults to `molodetz`.
|
- Defaults live in `config.py`: `INTERNAL_GATEWAY_URL` (`http://localhost:{DEVPLACE_PORT, default 10500}/openai/v1/chat/completions`; the whole base can also be overridden with `DEVPLACE_INTERNAL_BASE_URL`) and `INTERNAL_MODEL` (`molodetz`). `news_ai_url`, `bot_api_url`, `devii_ai_url` default to `INTERNAL_GATEWAY_URL`; their model defaults to `molodetz`.
|
||||||
- Each consumer's key falls back to `database.internal_gateway_key()` (reads the `gateway_internal_key` setting) when its own key field/env is unset - the provider-key fallbacks (`DEEPSEEK_API_KEY`/`OPENROUTER_API_KEY`) were removed from news and bots.
|
- Each consumer's key falls back to `database.internal_gateway_key()` (reads the `gateway_internal_key` setting) when its own key field/env is unset - the provider-key fallbacks (`DEEPSEEK_API_KEY`/`OPENROUTER_API_KEY`) were removed from news and bots.
|
||||||
- `gateway_force_model` (default on) and a `molodetz`/empty alias in `handle_chat` make the upstream always receive `gateway_model`, so `molodetz` is a stable generic alias.
|
- `gateway_force_model` (default on) and a `molodetz`/empty alias in `handle_chat` make the upstream always receive `gateway_model`, so `molodetz` is a stable generic alias.
|
||||||
- `database.migrate_ai_gateway_settings()` (called at the end of `init_db()`, under the startup `init_lock`): generates `gateway_internal_key` (uuid4) if missing; migrates `DEEPSEEK_API_KEY`/`OPENROUTER_API_KEY` env into `gateway_api_key`/`gateway_vision_key` when the db value is empty; and rewrites any consumer AI URL still equal to the old `openai.app.molodetz.nl` default to the gateway, plus `bot_model` `deepseek-chat` -> `molodetz` (only uncustomized values).
|
- `database.migrate_ai_gateway_settings()` (called at the end of `init_db()`, under the startup `init_lock`): generates `gateway_internal_key` (uuid4) if missing; migrates `DEEPSEEK_API_KEY`/`OPENROUTER_API_KEY` env into `gateway_api_key`/`gateway_vision_key` when the db value is empty; and rewrites any consumer AI URL still equal to the old `openai.app.molodetz.nl` default to the gateway, plus `bot_model` `deepseek-chat` -> `molodetz` (only uncustomized values).
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
HOME: /app
|
HOME: /app
|
||||||
PYTHONDONTWRITEBYTECODE: "1"
|
PYTHONDONTWRITEBYTECODE: "1"
|
||||||
|
# Pinned regardless of .env: the container always binds 10500 internally
|
||||||
|
# (Dockerfile CMD, nginx upstream). DEVPLACE_PORT is the bare-metal
|
||||||
|
# `make dev`/`make prod` knob; the externally reachable port is `PORT`.
|
||||||
|
DEVPLACE_PORT: "10500"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
expose:
|
expose:
|
||||||
|
|||||||
Reference in New Issue
Block a user