intitial docker impl
This commit is contained in:
19
.env.example
Normal file
19
.env.example
Normal 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
39
DOCKER.md
Normal 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
22
Dockerfile
Normal 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"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
37
docker-compose.yml
Normal 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
39
docker/entrypoint.sh
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user