intitial docker impl

This commit is contained in:
Game_Time
2025-08-20 15:26:14 +05:00
parent fc9727cb73
commit c8c6540d23
7 changed files with 167 additions and 4 deletions

19
.env.example Normal file
View File

@@ -0,0 +1,19 @@
# Port
PORT=8000
# Auth dir
CHATGPT_LOCAL_HOME=/data
# show request/stream logs
VERBOSE=false
# OAuth client id (modify only if you know what you're doing)
# CHATGPT_LOCAL_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann
# Reasoning controls
CHATGPT_LOCAL_REASONING_EFFORT=medium # minimal|low|medium|high
CHATGPT_LOCAL_REASONING_SUMMARY=auto # auto|concise|detailed|none
CHATGPT_LOCAL_REASONING_COMPAT=think-tags # legacy|o3|think-tags|current
# Force a specific model name
# CHATGPT_LOCAL_DEBUG_MODEL=gpt-5

39
DOCKER.md Normal file
View File

@@ -0,0 +1,39 @@
# Docker Deployment
## Quick Start
1) Setup env:
cp .env.example .env
2) Build the image:
docker compose build
3) Login:
docker compose run --rm --service-ports chatmock-login login
- The command prints an auth URL, copy paste it into your browser.
- Server should stop automatically once it recieves the tokens and they are saved.
4) Start the server:
docker compose up -d chatmock
5) Free to use it in whichever chat app you like!
## Configuration
Set options in `.env` or pass environment variables:
- `PORT`: Container listening port (default 8000)
- `VERBOSE`: `true|false` to enable request/stream logs
- `CHATGPT_LOCAL_REASONING_EFFORT`: minimal|low|medium|high
- `CHATGPT_LOCAL_REASONING_SUMMARY`: auto|concise|detailed|none
- `CHATGPT_LOCAL_REASONING_COMPAT`: legacy|o3|think-tags|current
- `CHATGPT_LOCAL_DEBUG_MODEL`: force model override (e.g., `gpt-5`)
- `CHATGPT_LOCAL_CLIENT_ID`: OAuth client id override (rarely needed)
## Logs
Set `VERBOSE=true` to include extra logging for debugging issues in upstream or chat app requests. Please include and use these logs when submitting bug reports.
## Test
```
curl -s http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gpt-5","messages":[{"role":"user","content":"Hello world!"}]}' | jq .
```

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
RUN mkdir -p /data
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8000 1455
ENTRYPOINT ["/entrypoint.sh"]
CMD ["serve"]

View File

@@ -11,6 +11,7 @@ from .app import create_app
from .config import CLIENT_ID_DEFAULT
from .oauth import OAuthHTTPServer, OAuthHandler, REQUIRED_PORT, URL_BASE
from .utils import eprint, get_home_dir, load_chatgpt_tokens, parse_jwt_claims, read_auth_file
import os
def cmd_login(no_browser: bool, verbose: bool) -> int:
@@ -21,7 +22,8 @@ def cmd_login(no_browser: bool, verbose: bool) -> int:
return 1
try:
httpd = OAuthHTTPServer(("127.0.0.1", REQUIRED_PORT), OAuthHandler, home_dir=home_dir, client_id=client_id, verbose=verbose)
bind_host = os.getenv("CHATGPT_LOCAL_LOGIN_BIND", "127.0.0.1")
httpd = OAuthHTTPServer((bind_host, REQUIRED_PORT), OAuthHandler, home_dir=home_dir, client_id=client_id, verbose=verbose)
except OSError as e:
eprint(f"ERROR: {e}")
if e.errno == errno.EADDRINUSE:

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import datetime
import ssl
import http.server
import json
import secrets
@@ -10,6 +11,8 @@ import urllib.parse
import urllib.request
from typing import Any, Dict, Tuple
import certifi
from .models import AuthBundle, PkceCodes, TokenData
from .utils import eprint, generate_pkce, parse_jwt_claims, write_auth_file
@@ -34,6 +37,7 @@ LOGIN_SUCCESS_HTML = """<!DOCTYPE html>
</html>
"""
_SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
class OAuthHTTPServer(http.server.HTTPServer):
def __init__(
@@ -174,7 +178,8 @@ class OAuthHandler(http.server.BaseHTTPRequestHandler):
data=data,
method="POST",
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
),
context=_SSL_CONTEXT,
) as resp:
payload = json.loads(resp.read().decode())
@@ -242,7 +247,8 @@ class OAuthHandler(http.server.BaseHTTPRequestHandler):
data=exchange_data,
method="POST",
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
),
context=_SSL_CONTEXT,
) as resp:
exchange_payload = json.loads(resp.read().decode())
exchanged_access_token = exchange_payload.get("access_token")
@@ -258,4 +264,3 @@ class OAuthHandler(http.server.BaseHTTPRequestHandler):
}
success_url = f"{URL_BASE}/success?{urllib.parse.urlencode(success_url_query)}"
return exchanged_access_token, success_url

37
docker-compose.yml Normal file
View File

@@ -0,0 +1,37 @@
version: "3.9"
services:
chatmock:
build: .
image: chatmock:latest
container_name: chatmock
command: ["serve"]
env_file: .env
environment:
- CHATGPT_LOCAL_HOME=/data
ports:
- "8000:8000"
volumes:
- chatmock_data:/data
- ./prompt.md:/app/prompt.md:ro
healthcheck:
test: ["CMD-SHELL", "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health').status==200 else 1)\" "]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
chatmock-login:
image: chatmock:latest
profiles: ["login"]
command: ["login"]
environment:
- CHATGPT_LOCAL_HOME=/data
- CHATGPT_LOCAL_LOGIN_BIND=0.0.0.0
volumes:
- chatmock_data:/data
ports:
- "1455:1455"
volumes:
chatmock_data:

39
docker/entrypoint.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
export CHATGPT_LOCAL_HOME="${CHATGPT_LOCAL_HOME:-/data}"
cmd="${1:-serve}"
shift || true
bool() {
case "${1:-}" in
1|true|TRUE|yes|YES|on|ON) return 0;;
*) return 1;;
esac
}
if [[ "$cmd" == "serve" ]]; then
PORT="${PORT:-8000}"
ARGS=(serve --host 0.0.0.0 --port "${PORT}")
if bool "${VERBOSE:-}" || bool "${CHATGPT_LOCAL_VERBOSE:-}"; then
ARGS+=(--verbose)
fi
if [[ "$#" -gt 0 ]]; then
ARGS+=("$@")
fi
exec python chatmock.py "${ARGS[@]}"
elif [[ "$cmd" == "login" ]]; then
ARGS=(login --no-browser)
if bool "${VERBOSE:-}" || bool "${CHATGPT_LOCAL_VERBOSE:-}"; then
ARGS+=(--verbose)
fi
exec python chatmock.py "${ARGS[@]}"
else
exec "$cmd" "$@"
fi