mirror of
https://github.com/ParisNeo/ollama_proxy_server.git
synced 2025-09-06 22:25:54 +00:00
Mise à jour des fichiers de configuration et amélioration du script d'installation
- Ajout de la gestion des fichiers .env dans le script d'installation pour garantir une configuration correcte. - Révision de la logique de création de l'environnement virtuel et d'installation des dépendances dans run.sh et run_windows.bat. - Modification de la route API pour la création de clés utilisateur dans admin.py. - Ajout de la gestion des fichiers .setup_state pour suivre l'état de l'installation.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -95,4 +95,7 @@ docker-compose.yml
|
||||
|
||||
# Database
|
||||
ollama_proxy.db
|
||||
*.db-journal
|
||||
*.db-journal
|
||||
|
||||
# setup state
|
||||
.setup_state
|
@@ -206,21 +206,41 @@ async def create_user_api_key(
|
||||
},
|
||||
)
|
||||
|
||||
@router.post("/keys/{key_id}/revoke", name="revoke_user_api_key")
|
||||
async def revoke_user_api_key(
|
||||
@router.post("/users/{user_id}/keys", name="create_user_api_key")
|
||||
async def create_user_api_key(
|
||||
request: Request,
|
||||
key_id: int,
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin_user: User = Depends(require_admin_user),
|
||||
key_name: str = Form(...),
|
||||
):
|
||||
key = await apikey_crud.get_api_key_by_id(db, key_id=key_id)
|
||||
if not key:
|
||||
raise HTTPException(status_code=404, detail="API Key not found")
|
||||
|
||||
await apikey_crud.revoke_api_key(db, key_id=key_id)
|
||||
flash(request, f"API Key '{key.key_name}' has been revoked.", "success")
|
||||
return RedirectResponse(url=request.url_for("get_user_details", user_id=key.user_id), status_code=status.HTTP_303_SEE_OTHER)
|
||||
"""
|
||||
Creates a new API key for the given user.
|
||||
Instead of flashing the key (which would be lost on a redirect),
|
||||
we render a dedicated page that displays the **full plain key**
|
||||
exactly once and offers a “Copy to clipboard” button.
|
||||
"""
|
||||
plain_key, _ = await apikey_crud.create_api_key(db, user_id=user_id, key_name=key_name)
|
||||
|
||||
# --- FIX ---
|
||||
# The user object in request.state (admin_user) is from a different,
|
||||
# now-closed session. Accessing its properties in the template would
|
||||
# cause a lazy-load, which fails in a synchronous context.
|
||||
# We fetch a fresh user object using the current session ('db') and
|
||||
# update the request state to ensure the template renders correctly.
|
||||
fresh_admin_user = await user_crud.get_user_by_id(db, user_id=admin_user.id)
|
||||
request.state.user = fresh_admin_user
|
||||
# --- END FIX ---
|
||||
|
||||
# Render a page that shows the key and a copy button.
|
||||
return templates.TemplateResponse(
|
||||
"admin/key_created.html",
|
||||
{
|
||||
"request": request,
|
||||
"plain_key": plain_key,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
@router.post("/users/{user_id}/delete", name="delete_user_account")
|
||||
async def delete_user_account(
|
||||
request: Request,
|
||||
|
@@ -28,7 +28,9 @@ from app.database.session import AsyncSessionLocal
|
||||
from app.crud import user_crud, server_crud
|
||||
from app.schema.user import UserCreate
|
||||
from app.schema.server import ServerCreate
|
||||
import os
|
||||
|
||||
os.environ.setdefault("PASSLIB_DISABLE_WARNINGS", "1")
|
||||
# ----------------------------------------------------------------------
|
||||
# Logging
|
||||
# ----------------------------------------------------------------------
|
||||
|
300
run.sh
300
run.sh
@@ -1,147 +1,199 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail # fail on error, undefined vars, and pipe failures
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Ollama Proxy Server installer & runner (macOS / Linux)
|
||||
# ------------------------------------------------------------
|
||||
# ====================================================================
|
||||
#
|
||||
# Ollama Proxy Fortress - Professional Installer & Runner
|
||||
# Version: 3.0 (with Linux Service Installer)
|
||||
# For: macOS & Linux
|
||||
#
|
||||
# ====================================================================
|
||||
|
||||
# --- Configuration ---
|
||||
VENV_DIR="venv"
|
||||
REQUIREMENTS_FILE="requirements.txt"
|
||||
GUNICORN_CONF="gunicorn_conf.py"
|
||||
APP_MODULE="app.main:app"
|
||||
STATE_FILE=".setup_state"
|
||||
SERVICE_NAME="ollama_proxy"
|
||||
PROJECT_DIR=$(pwd) # Get the absolute path to the project directory
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Helper Functions
|
||||
# ------------------------------------------------------------
|
||||
print_info() { echo -e "\e[34m[INFO]\e[0m $*"; }
|
||||
print_success() { echo -e "\e[32m[SUCCESS]\e[0m $*"; }
|
||||
print_error() { echo -e "\e[31m[ERROR]\e[0m $*" >&2; }
|
||||
# --- Colors and Styling ---
|
||||
COLOR_RESET='\e[0m'
|
||||
COLOR_INFO='\e[1;34m' # Bold Blue
|
||||
COLOR_SUCCESS='\e[1;32m' # Bold Green
|
||||
COLOR_ERROR='\e[1;31m' # Bold Red
|
||||
COLOR_WARN='\e[1;33m' # Bold Yellow
|
||||
COLOR_HEADER='\e[1;35m' # Bold Magenta
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 1️⃣ Check for Python 3
|
||||
# ------------------------------------------------------------
|
||||
print_info "Checking for Python 3 installation..."
|
||||
if ! command -v python3 &>/dev/null; then
|
||||
print_error "Python 3 not found. Please install it to continue."
|
||||
# --- Helper Functions ---
|
||||
print_header() { echo -e "\n${COLOR_HEADER}=====================================================${COLOR_RESET}"; \
|
||||
echo -e "${COLOR_HEADER}$1${COLOR_RESET}"; \
|
||||
echo -e "${COLOR_HEADER}=====================================================${COLOR_RESET}"; }
|
||||
print_info() { echo -e "${COLOR_INFO}[INFO]${COLOR_RESET} $*"; }
|
||||
print_success() { echo -e "${COLOR_SUCCESS}[SUCCESS]${COLOR_RESET} $*"; }
|
||||
print_error() { echo -e "${COLOR_ERROR}[ERROR]${COLOR_RESET} $*" >&2; }
|
||||
print_warn() { echo -e "${COLOR_WARN}[WARNING]${COLOR_RESET} $*"; }
|
||||
|
||||
clear
|
||||
print_header " Ollama Proxy Fortress Installer & Runner"
|
||||
|
||||
# ====================================================================
|
||||
# 1. PRE-CHECKS
|
||||
# ====================================================================
|
||||
print_info "Performing initial system checks..."
|
||||
if ! command -v python3 &>/dev/null || ! python3 -m pip --version &>/dev/null || ! python3 -m venv -h &>/dev/null; then
|
||||
print_error "Python 3, pip, or venv is missing. Please ensure a complete Python 3 installation."
|
||||
exit 1
|
||||
fi
|
||||
print_success "Python 3 is available."
|
||||
print_success "Python 3, pip, and venv are available."
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 2️⃣ First‑time setup (create venv, install deps, generate .env)
|
||||
# ------------------------------------------------------------
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
print_info "First‑time setup detected – configuring the server..."
|
||||
CURRENT_STATE=0
|
||||
if [[ -f "$STATE_FILE" ]]; then CURRENT_STATE=$(cat "$STATE_FILE"); fi
|
||||
|
||||
# ---- 2.1 Create virtual environment
|
||||
print_info "Creating Python virtual environment in ./$VENV_DIR ..."
|
||||
python3 -m venv "$VENV_DIR"
|
||||
|
||||
# ---- 2.2 Activate and install dependencies
|
||||
print_info "Activating environment and installing dependencies..."
|
||||
source "$VENV_DIR/bin/activate"
|
||||
if [[ -f "$REQUIREMENTS_FILE" ]]; then
|
||||
pip install --no-cache-dir -r "$REQUIREMENTS_FILE"
|
||||
if [[ "$CURRENT_STATE" -ge 4 ]] && [[ ! -f ".env" ]]; then
|
||||
print_warn "Setup complete, but '.env' file is missing! The server cannot start."
|
||||
read -p "Do you want to run the setup wizard again to create a new .env file? (y/n): " REBUILD_CHOICE
|
||||
if [[ "$REBUILD_CHOICE" =~ ^[Yy]$ ]]; then
|
||||
print_info "Resetting setup state..."
|
||||
rm -f "$STATE_FILE"
|
||||
CURRENT_STATE=0
|
||||
else
|
||||
print_error "Missing $REQUIREMENTS_FILE – aborting."
|
||||
exit 1
|
||||
print_info "Aborting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- 2.3 Gather configuration from the user
|
||||
print_info "Please provide the following configuration (press Enter for defaults):"
|
||||
|
||||
read -p "Port for the proxy server to listen on [8080]: " PROXY_PORT
|
||||
PROXY_PORT=${PROXY_PORT:-8080}
|
||||
|
||||
read -p "Backend Ollama server URL(s), comma‑separated [http://127.0.0.1:11434]: " OLLAMA_SERVERS
|
||||
OLLAMA_SERVERS=${OLLAMA_SERVERS:-http://127.0.0.1:11434}
|
||||
|
||||
read -p "Redis URL for rate limiting [redis://localhost:6379/0]: " REDIS_URL
|
||||
REDIS_URL=${REDIS_URL:-redis://localhost:6379/0}
|
||||
|
||||
read -p "Username for the admin dashboard [admin]: " ADMIN_USER
|
||||
ADMIN_USER=${ADMIN_USER:-admin}
|
||||
|
||||
# hide password input
|
||||
read -s -p "Password for the admin user (will be hidden): " ADMIN_PASSWORD
|
||||
echo
|
||||
if [[ -z "$ADMIN_PASSWORD" ]]; then
|
||||
print_error "Admin password cannot be empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read -p "Allowed IPs, comma‑separated (leave empty for all): " ALLOWED_IPS
|
||||
read -p "Denied IPs, comma‑separated (leave empty for none): " DENIED_IPS
|
||||
|
||||
# ---- 2.4 Generate .env file
|
||||
print_info "Generating .env configuration file..."
|
||||
|
||||
# Create a random secret key (32‑byte hex)
|
||||
SECRET_KEY=$(openssl rand -hex 32)
|
||||
|
||||
# Escape any double quotes that might be present in the password
|
||||
ESCAPED_ADMIN_PASSWORD=${ADMIN_PASSWORD//\"/\\\"}
|
||||
|
||||
{
|
||||
echo "# --------------------------------------------------"
|
||||
echo "# Application Settings"
|
||||
echo "# --------------------------------------------------"
|
||||
echo "APP_NAME=\"Ollama Proxy Server\""
|
||||
echo "APP_VERSION=\"8.0.0\""
|
||||
echo "LOG_LEVEL=\"info\""
|
||||
echo "PROXY_PORT=${PROXY_PORT}"
|
||||
echo "OLLAMA_SERVERS=\"${OLLAMA_SERVERS}\""
|
||||
echo "DATABASE_URL=\"sqlite+aiosqlite:///./ollama_proxy.db\""
|
||||
echo "ADMIN_USER=${ADMIN_USER}"
|
||||
echo "ADMIN_PASSWORD=\"${ESCAPED_ADMIN_PASSWORD}\""
|
||||
echo "SECRET_KEY=${SECRET_KEY}"
|
||||
echo ""
|
||||
echo "# --------------------------------------------------"
|
||||
echo "# Advanced Security"
|
||||
echo "# --------------------------------------------------"
|
||||
echo "REDIS_URL=\"${REDIS_URL}\""
|
||||
echo "RATE_LIMIT_REQUESTS=100"
|
||||
echo "RATE_LIMIT_WINDOW_MINUTES=1"
|
||||
# Only write the list‑type variables when they are non‑empty.
|
||||
if [[ -n "$ALLOWED_IPS" ]]; then
|
||||
echo "ALLOWED_IPS=${ALLOWED_IPS}"
|
||||
fi
|
||||
if [[ -n "$DENIED_IPS" ]]; then
|
||||
echo "DENIED_IPS=${DENIED_IPS}"
|
||||
fi
|
||||
} > .env
|
||||
|
||||
print_success ".env file created."
|
||||
|
||||
# ---- 2.5 Initialise the database with Alembic
|
||||
print_info "Running database migrations (Alembic)..."
|
||||
alembic upgrade head
|
||||
print_success "Database is up‑to‑date."
|
||||
|
||||
print_success "First‑time setup complete!"
|
||||
echo
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 3️⃣ Start the server
|
||||
# ------------------------------------------------------------
|
||||
print_info "Activating virtual environment..."
|
||||
source "$VENV_DIR/bin/activate"
|
||||
# ====================================================================
|
||||
# 2. SETUP WIZARD (Resumable)
|
||||
# ====================================================================
|
||||
if [[ "$CURRENT_STATE" -lt 4 ]]; then
|
||||
print_info "Setup state is ${CURRENT_STATE}/4. Starting or resuming installation..."
|
||||
|
||||
print_info "Setting PYTHONPATH to project root..."
|
||||
export PYTHONPATH=.
|
||||
if [[ "$CURRENT_STATE" -lt 1 ]]; then
|
||||
print_header "--- [Step 1/4] Creating Python Virtual Environment ---"
|
||||
python3 -m venv "$VENV_DIR"
|
||||
echo "1" > "$STATE_FILE"
|
||||
print_success "Virtual environment created in './${VENV_DIR}'."
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
if [[ "$CURRENT_STATE" -lt 2 ]]; then
|
||||
print_header "--- [Step 2/4] Installing Python Dependencies ---"
|
||||
pip install --no-cache-dir -r "$REQUIREMENTS_FILE"
|
||||
echo "2" > "$STATE_FILE"
|
||||
print_success "All dependencies installed."
|
||||
fi
|
||||
if [[ "$CURRENT_STATE" -lt 3 ]]; then
|
||||
print_header "--- [Step 3/4] Server Configuration ---"
|
||||
read -p " -> Port for the proxy server [8080]: " PROXY_PORT
|
||||
read -p " -> Backend Ollama server(s) [http://127.0.0.1:11434]: " OLLAMA_SERVERS
|
||||
read -p " -> Redis URL [redis://localhost:6379/0]: " REDIS_URL
|
||||
read -p " -> Admin username [admin]: " ADMIN_USER
|
||||
ADMIN_PASSWORD=""
|
||||
while [[ -z "$ADMIN_PASSWORD" ]]; do
|
||||
read -s -p " -> Admin password (cannot be empty): " ADMIN_PASSWORD; echo
|
||||
if [[ -z "$ADMIN_PASSWORD" ]]; then print_error " Password cannot be empty."; fi
|
||||
done
|
||||
read -p " -> Allowed IPs (comma-separated, leave empty for all): " ALLOWED_IPS
|
||||
read -p " -> Denied IPs (comma-separated, leave empty for none): " DENIED_IPS
|
||||
print_info "Generating .env configuration file..."
|
||||
SECRET_KEY=$(openssl rand -hex 32)
|
||||
(
|
||||
echo "APP_NAME=\"Ollama Proxy Fortress\""; echo "APP_VERSION=\"8.0.0\""; echo "LOG_LEVEL=\"info\""
|
||||
echo "PROXY_PORT=\"${PROXY_PORT:-8080}\""
|
||||
echo "OLLAMA_SERVERS=\"${OLLAMA_SERVERS:-http://127.0.0.1:11434}\""
|
||||
echo "DATABASE_URL=\"sqlite+aiosqlite:///./ollama_proxy.db\""
|
||||
echo "ADMIN_USER=\"${ADMIN_USER:-admin}\""; echo "ADMIN_PASSWORD=\"${ADMIN_PASSWORD}\""
|
||||
echo "SECRET_KEY=\"${SECRET_KEY}\""
|
||||
echo "REDIS_URL=\"${REDIS_URL:-redis://localhost:6379/0}\""
|
||||
echo "RATE_LIMIT_REQUESTS=\"100\""; echo "RATE_LIMIT_WINDOW_MINUTES=\"1\""
|
||||
echo "ALLOWED_IPS=\"${ALLOWED_IPS}\""; echo "DENIED_IPS=\"${DENIED_IPS}\""
|
||||
) > .env
|
||||
echo "3" > "$STATE_FILE"
|
||||
print_success ".env file created."
|
||||
fi
|
||||
if [[ "$CURRENT_STATE" -lt 4 ]]; then
|
||||
print_header "--- [Step 4/4] Initializing Database ---"
|
||||
alembic upgrade head
|
||||
echo "4" > "$STATE_FILE"
|
||||
print_success "Database migrated to the latest version."
|
||||
fi
|
||||
|
||||
# Determine the port from .env (fallback to 8080)
|
||||
DEFAULT_PORT=8080
|
||||
if [[ -f .env && $(grep -E '^PROXY_PORT=' .env) ]]; then
|
||||
# Strip quotes if present
|
||||
PORT_TO_USE=$(grep -E '^PROXY_PORT=' .env | cut -d '=' -f2 | tr -d '"' | tr -d "'")
|
||||
else
|
||||
PORT_TO_USE=$DEFAULT_PORT
|
||||
print_header "--- Setup Complete! ---"
|
||||
ADMIN_USER_FINAL=$(grep -E '^ADMIN_USER=' .env | cut -d '=' -f2 | tr -d '"')
|
||||
PORT_FINAL=$(grep -E '^PROXY_PORT=' .env | cut -d '=' -f2 | tr -d '"')
|
||||
print_success "Your Ollama Proxy Fortress is ready."
|
||||
print_info "Admin Dashboard: http://127.0.0.1:${PORT_FINAL}/admin"
|
||||
print_info "Admin Username: ${ADMIN_USER_FINAL}"
|
||||
fi
|
||||
|
||||
print_info "Starting Ollama Proxy Server on port ${PORT_TO_USE}..."
|
||||
print_info "Press Ctrl+C to stop the server."
|
||||
# ====================================================================
|
||||
# 3. OPTIONAL: CREATE LINUX SYSTEMD SERVICE
|
||||
# ====================================================================
|
||||
SERVICE_CREATED=false
|
||||
if [[ "$(uname)" == "Linux" ]] && command -v systemctl &>/dev/null; then
|
||||
print_header "--- Optional: Create a Systemd Service ---"
|
||||
print_info "A service will automatically start the proxy on boot and restart it if it fails."
|
||||
read -p "Do you want to create and enable a systemd service for this application? (y/n): " CREATE_SERVICE
|
||||
if [[ "$CREATE_SERVICE" =~ ^[Yy]$ ]]; then
|
||||
SERVICE_FILE_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
print_info "Creating systemd service file..."
|
||||
|
||||
# Run via Gunicorn – the config file is expected to exist in the repo.
|
||||
exec gunicorn -c "$GUNICORN_CONF" "$APP_MODULE" --bind "0.0.0.0:${PORT_TO_USE}"
|
||||
# Using a 'here document' to create the service file content
|
||||
SERVICE_FILE_CONTENT=$(cat << EOF
|
||||
[Unit]
|
||||
Description=Ollama Proxy Fortress Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=${USER}
|
||||
Group=$(id -gn ${USER})
|
||||
WorkingDirectory=${PROJECT_DIR}
|
||||
Environment="PYTHONPATH=${PROJECT_DIR}"
|
||||
ExecStart=${PROJECT_DIR}/${VENV_DIR}/bin/gunicorn -c ${PROJECT_DIR}/${GUNICORN_CONF} ${APP_MODULE} --bind 0.0.0.0:$(grep -E '^PROXY_PORT=' .env | cut -d '=' -f2 | tr -d '"')
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
)
|
||||
print_warn "Root privileges are required to install the service."
|
||||
echo "$SERVICE_FILE_CONTENT" | sudo tee "$SERVICE_FILE_PATH" > /dev/null
|
||||
|
||||
print_info "Reloading systemd daemon..."
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
print_info "Enabling the service to start on boot..."
|
||||
sudo systemctl enable "${SERVICE_NAME}.service"
|
||||
|
||||
print_info "Starting the service now..."
|
||||
sudo systemctl start "${SERVICE_NAME}.service"
|
||||
|
||||
print_header "--- Service Management ---"
|
||||
print_success "Service '${SERVICE_NAME}' is now running."
|
||||
print_info "Check status: sudo systemctl status ${SERVICE_NAME}"
|
||||
print_info "View logs: sudo journalctl -u ${SERVICE_NAME} -f"
|
||||
print_info "Stop service: sudo systemctl stop ${SERVICE_NAME}"
|
||||
SERVICE_CREATED=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# ====================================================================
|
||||
# 4. START THE SERVER (if service was not created)
|
||||
# ====================================================================
|
||||
if [ "$SERVICE_CREATED" = false ]; then
|
||||
print_header "--- Starting Ollama Proxy Fortress (Foreground Mode) ---"
|
||||
print_info "Activating virtual environment..."
|
||||
source "$VENV_DIR/bin/activate"
|
||||
|
||||
print_info "Setting PYTHONPATH to project root..."
|
||||
export PYTHONPATH=.
|
||||
PORT_TO_USE=$(grep -E '^PROXY_PORT=' .env | cut -d '=' -f2 | tr -d '"' | tr -d "'" || echo "8080")
|
||||
|
||||
print_info "Starting Gunicorn server on http://0.0.0.0:${PORT_TO_USE}"
|
||||
print_info "Press Ctrl+C to stop the server."
|
||||
echo
|
||||
exec gunicorn -c "$GUNICORN_CONF" "$APP_MODULE" --bind "0.0.0.0:${PORT_TO_USE}"
|
||||
fi
|
190
run_windows.bat
190
run_windows.bat
@@ -1,155 +1,129 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: This script sets up and runs the Ollama Proxy Server on Windows.
|
||||
:: It has been rewritten to be more robust.
|
||||
:: On first run, it will guide you through setup.
|
||||
:: Afterwards, it will just start the server.
|
||||
:: ==================================================================
|
||||
:: Ollama Proxy Server - Python-Powered Installer for Windows
|
||||
:: ==================================================================
|
||||
:: This script now delegates the fragile .env creation to a Python
|
||||
:: script (`setup_wizard.py`) to guarantee a correct setup.
|
||||
|
||||
set VENV_DIR=venv
|
||||
set REQUIREMENTS_FILE=requirements.txt
|
||||
set STATE_FILE=.setup_state
|
||||
set SETUP_WIZARD_SCRIPT=setup_wizard.py
|
||||
|
||||
:: --- Check for Python ---
|
||||
:: ------------------------------------------------------------------
|
||||
:: 1. PRE-CHECKS
|
||||
:: ------------------------------------------------------------------
|
||||
echo [INFO] Checking for Python installation...
|
||||
where python >nul 2>nul
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Python not found in your PATH.
|
||||
echo Please install Python 3.11+ from https://python.org or the Microsoft Store.
|
||||
echo Make sure to check "Add Python to PATH" during installation.
|
||||
echo [ERROR] Python not found in your PATH. Please install Python 3.11+.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [SUCCESS] Python found.
|
||||
|
||||
:: --- Check if setup is needed ---
|
||||
if exist "%VENV_DIR%\Scripts\activate.bat" goto :start_server
|
||||
set "CURRENT_STATE=0"
|
||||
if exist "%STATE_FILE%" (
|
||||
set /p CURRENT_STATE=<%STATE_FILE%
|
||||
)
|
||||
|
||||
if %CURRENT_STATE% GEQ 4 (
|
||||
if not exist ".env" (
|
||||
echo.
|
||||
echo *****************************************************************
|
||||
echo * [WARNING] The setup is complete, but the '.env' file is missing!
|
||||
echo *****************************************************************
|
||||
echo.
|
||||
set /p REBUILD_CHOICE="Do you want to run the setup wizard again? (y/n): "
|
||||
if /i "!REBUILD_CHOICE!"=="y" (
|
||||
echo [INFO] Resetting setup state...
|
||||
del /f "%STATE_FILE%" >nul 2>nul
|
||||
set "CURRENT_STATE=0"
|
||||
echo.
|
||||
) else (
|
||||
echo [INFO] Aborting.
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
:: ##################################################################
|
||||
:: # FIRST-TIME SETUP SCRIPT #
|
||||
:: ##################################################################
|
||||
echo.
|
||||
echo [INFO] First-time setup detected. Configuring the server...
|
||||
if %CURRENT_STATE% GEQ 4 goto start_server
|
||||
|
||||
:: 1. Create Virtual Environment
|
||||
echo [INFO] Creating Python virtual environment...
|
||||
:: ==================================================================
|
||||
:: SETUP WIZARD (RESUMABLE)
|
||||
:: ==================================================================
|
||||
echo [INFO] Setup state is !CURRENT_STATE!/4. Starting or resuming installation...
|
||||
|
||||
if %CURRENT_STATE% GEQ 1 goto setup_step_2
|
||||
echo [INFO] [1/4] Creating Python virtual environment...
|
||||
python -m venv %VENV_DIR%
|
||||
if %errorlevel% neq 0 ( echo [ERROR] Failed to create virtual environment. & pause & exit /b 1 )
|
||||
(echo 1) > %STATE_FILE%
|
||||
echo [SUCCESS] Virtual environment created.
|
||||
|
||||
:setup_step_2
|
||||
call .\%VENV_DIR%\Scripts\activate.bat
|
||||
|
||||
if %CURRENT_STATE% GEQ 2 goto setup_step_3
|
||||
echo [INFO] [2/4] Installing dependencies...
|
||||
pip install --no-cache-dir -r %REQUIREMENTS_FILE%
|
||||
if %errorlevel% neq 0 ( echo [ERROR] Failed to install Python packages. & pause & exit /b 1 )
|
||||
(echo 2) > %STATE_FILE%
|
||||
echo [SUCCESS] Dependencies installed.
|
||||
|
||||
:setup_step_3
|
||||
if %CURRENT_STATE% GEQ 4 goto setup_step_4
|
||||
echo [INFO] [3/4] Launching Python setup wizard for configuration...
|
||||
python %SETUP_WIZARD_SCRIPT%
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Failed to create virtual environment.
|
||||
echo [ERROR] The Python setup wizard failed to create the .env file.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
(echo 3) > %STATE_FILE%
|
||||
echo [SUCCESS] .env file created successfully by Python wizard.
|
||||
|
||||
:: 2. Install Dependencies
|
||||
echo [INFO] Activating environment and installing dependencies (this may take a moment)...
|
||||
call %VENV_DIR%\Scripts\activate.bat
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Failed to install Python packages.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 3. Gather Configuration
|
||||
echo [INFO] Please provide the following configuration:
|
||||
set /p PROXY_PORT="Enter the port for the proxy server to listen on [8080]: "
|
||||
if not defined PROXY_PORT set PROXY_PORT=8080
|
||||
|
||||
set /p OLLAMA_SERVERS="Enter the backend Ollama server URL(s), comma-separated [http://127.0.0.1:11434]: "
|
||||
if not defined OLLAMA_SERVERS set OLLAMA_SERVERS=http://127.0.0.1:11434
|
||||
|
||||
set /p REDIS_URL="Enter the Redis URL for rate limiting [redis://localhost:6379/0]: "
|
||||
if not defined REDIS_URL set REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
set /p ADMIN_USER="Enter a username for the admin dashboard [admin]: "
|
||||
if not defined ADMIN_USER set ADMIN_USER=admin
|
||||
|
||||
set /p ADMIN_PASSWORD="Enter a password for the admin user: "
|
||||
if not defined ADMIN_PASSWORD (
|
||||
echo [ERROR] Admin password cannot be empty.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set /p ALLOWED_IPS_INPUT="Enter allowed IPs, comma-separated (e.g., 127.0.0.1,localhost) [*]: "
|
||||
if not defined ALLOWED_IPS_INPUT set ALLOWED_IPS_INPUT=*
|
||||
|
||||
set /p DENIED_IPS_INPUT="Enter denied IPs, comma-separated (leave empty for none): "
|
||||
|
||||
|
||||
:: 4. Generate .env file
|
||||
echo [INFO] Generating .env configuration file...
|
||||
(
|
||||
echo # Application Settings
|
||||
REM --- FIX START ---
|
||||
REM Removed quotes from simple key-value pairs
|
||||
echo APP_NAME=Ollama Proxy Server
|
||||
echo APP_VERSION=8.0.0
|
||||
echo LOG_LEVEL=info
|
||||
echo PROXY_PORT=!PROXY_PORT!
|
||||
echo OLLAMA_SERVERS=!OLLAMA_SERVERS!
|
||||
echo DATABASE_URL=sqlite+aiosqlite:///./ollama_proxy.db
|
||||
echo ADMIN_USER=!ADMIN_USER!
|
||||
echo ADMIN_PASSWORD=!ADMIN_PASSWORD!
|
||||
echo SECRET_KEY=!RANDOM!!RANDOM!!RANDOM!!RANDOM!
|
||||
echo.
|
||||
echo # --- Advanced Security ---
|
||||
echo REDIS_URL=!REDIS_URL!
|
||||
echo RATE_LIMIT_REQUESTS=100
|
||||
echo RATE_LIMIT_WINDOW_MINUTES=1
|
||||
REM --- FIX END ---
|
||||
|
||||
REM Correctly formats the ALLOWED_IPS value. These need special formatting.
|
||||
if "!ALLOWED_IPS_INPUT!"=="*" (
|
||||
echo ALLOWED_IPS='["*"]'
|
||||
) else (
|
||||
set formatted_allowed_ips=!ALLOWED_IPS_INPUT:,=","!
|
||||
echo ALLOWED_IPS='["!formatted_allowed_ips!"]'
|
||||
)
|
||||
|
||||
REM Correctly formats the DENIED_IPS value. These need special formatting.
|
||||
if not defined DENIED_IPS_INPUT (
|
||||
echo DENIED_IPS='[]'
|
||||
) else (
|
||||
set formatted_denied_ips=!DENIED_IPS_INPUT:,=","!
|
||||
echo DENIED_IPS='["!formatted_denied_ips!"]'
|
||||
)
|
||||
|
||||
) > .env
|
||||
|
||||
:: 5. Initialize Database
|
||||
echo [INFO] Initializing the database...
|
||||
:setup_step_4
|
||||
if %CURRENT_STATE% GEQ 4 goto all_setup_done
|
||||
echo [INFO] [4/4] Initializing the database with Alembic...
|
||||
alembic upgrade head
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Failed to initialize the database.
|
||||
echo [ERROR] Failed to initialize the database. The app configuration might be invalid.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
(echo 4) > %STATE_FILE%
|
||||
echo [SUCCESS] Database is up-to-date.
|
||||
|
||||
echo [SUCCESS] First-time setup complete!
|
||||
:all_setup_done
|
||||
echo.
|
||||
echo [SUCCESS] Setup complete!
|
||||
echo.
|
||||
goto :start_server
|
||||
|
||||
:: ##################################################################
|
||||
:: # SERVER START SCRIPT #
|
||||
:: ##################################################################
|
||||
:: ==================================================================
|
||||
:: START THE SERVER
|
||||
:: ==================================================================
|
||||
:start_server
|
||||
echo [INFO] Activating virtual environment...
|
||||
call %VENV_DIR%\Scripts\activate.bat
|
||||
call .\%VENV_DIR%\Scripts\activate.bat
|
||||
|
||||
echo [INFO] Setting Python Path...
|
||||
set PYTHONPATH=.
|
||||
|
||||
:: Read the port from the .env file for the status message
|
||||
set PORT_TO_USE=8080
|
||||
for /f "usebackq tokens=1,* delims==" %%a in (".env") do (
|
||||
if "%%a"=="PROXY_PORT" set PORT_TO_USE=%%b
|
||||
if /i "%%a"=="PROXY_PORT" set "PORT_TO_USE=%%~b"
|
||||
)
|
||||
|
||||
echo [INFO] Starting Ollama Proxy Server on port !PORT_TO_USE!...
|
||||
echo To stop the server, simply close this window or press Ctrl+C.
|
||||
echo.
|
||||
|
||||
:: Uvicorn is used directly as Gunicorn is not available on Windows.
|
||||
uvicorn app.main:app --host 0.0.0.0 --port !PORT_TO_USE!
|
||||
|
||||
echo [INFO] Server has been stopped.
|
||||
pause
|
||||
pause
|
||||
exit /b
|
76
setup_wizard.py
Normal file
76
setup_wizard.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import sys
|
||||
import secrets
|
||||
|
||||
def print_info(message):
|
||||
print(f"[INFO] {message}")
|
||||
|
||||
def print_error(message):
|
||||
print(f"[ERROR] {message}", file=sys.stderr)
|
||||
|
||||
def get_user_input(prompt, default=None):
|
||||
"""Gets user input with a default value."""
|
||||
default_text = f"[{default}]" if default is not None else ""
|
||||
prompt_text = f" -> {prompt} {default_text}: "
|
||||
user_value = input(prompt_text)
|
||||
return user_value.strip() if user_value.strip() else default
|
||||
|
||||
def create_env_file():
|
||||
"""
|
||||
Guides the user through creating a .env file and writes it.
|
||||
This version omits empty optional fields to prevent parsing errors.
|
||||
"""
|
||||
print_info("Please provide the following configuration (press Enter for defaults):")
|
||||
|
||||
config = {
|
||||
"PROXY_PORT": get_user_input("Port for the proxy server", "8080"),
|
||||
"OLLAMA_SERVERS": get_user_input("Backend Ollama server(s)", "http://127.0.0.1:11434"),
|
||||
"REDIS_URL": get_user_input("Redis URL for rate limiting", "redis://localhost:6379/0"),
|
||||
"ADMIN_USER": get_user_input("Username for the admin dashboard", "admin"),
|
||||
"ALLOWED_IPS": get_user_input("Allowed IPs (comma-separated, leave empty for all)", ""),
|
||||
"DENIED_IPS": get_user_input("Denied IPs (comma-separated, leave empty for none)", ""),
|
||||
}
|
||||
|
||||
# --- Password Loop ---
|
||||
admin_password = ""
|
||||
while not admin_password:
|
||||
admin_password = input(" -> Password for the admin user (cannot be empty): ").strip()
|
||||
if not admin_password:
|
||||
print_error(" Password cannot be empty. Please try again.")
|
||||
config["ADMIN_PASSWORD"] = admin_password
|
||||
|
||||
# --- Generate Secret Key ---
|
||||
config["SECRET_KEY"] = secrets.token_hex(32)
|
||||
|
||||
# --- Write the .env file ---
|
||||
print_info("Generating .env configuration file...")
|
||||
try:
|
||||
with open(".env", "w", encoding="utf-8") as f:
|
||||
f.write('APP_NAME="Ollama Proxy Server"\n')
|
||||
f.write('APP_VERSION="8.0.0"\n')
|
||||
f.write('LOG_LEVEL="info"\n')
|
||||
f.write(f'PROXY_PORT="{config["PROXY_PORT"]}"\n')
|
||||
f.write(f'OLLAMA_SERVERS="{config["OLLAMA_SERVERS"]}"\n')
|
||||
f.write('DATABASE_URL="sqlite+aiosqlite:///./ollama_proxy.db"\n')
|
||||
f.write(f'ADMIN_USER="{config["ADMIN_USER"]}"\n')
|
||||
f.write(f'ADMIN_PASSWORD="{config["ADMIN_PASSWORD"]}"\n')
|
||||
f.write(f'SECRET_KEY="{config["SECRET_KEY"]}"\n')
|
||||
f.write(f'REDIS_URL="{config["REDIS_URL"]}"\n')
|
||||
f.write('RATE_LIMIT_REQUESTS="100"\n')
|
||||
f.write('RATE_LIMIT_WINDOW_MINUTES="1"\n')
|
||||
|
||||
# --- CRITICAL FIX: Only write these lines if they have a value ---
|
||||
if config["ALLOWED_IPS"]:
|
||||
f.write(f'ALLOWED_IPS="{config["ALLOWED_IPS"]}"\n')
|
||||
if config["DENIED_IPS"]:
|
||||
f.write(f'DENIED_IPS="{config["DENIED_IPS"]}"\n')
|
||||
|
||||
print_info(".env file created successfully.")
|
||||
return True
|
||||
except IOError as e:
|
||||
print_error(f"Failed to write to .env file: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not create_env_file():
|
||||
# Exit with a non-zero code to signal failure to the batch script
|
||||
sys.exit(1)
|
Reference in New Issue
Block a user