From 5fee47964419849152140042aa62cad292ae326b Mon Sep 17 00:00:00 2001 From: Saifeddine ALOUI Date: Fri, 5 Sep 2025 23:29:08 +0200 Subject: [PATCH] =?UTF-8?q?Mise=20=C3=A0=20jour=20des=20fichiers=20de=20co?= =?UTF-8?q?nfiguration=20et=20am=C3=A9lioration=20du=20script=20d'installa?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- .gitignore | 5 +- app/api/v1/routes/admin.py | 40 +++-- app/main.py | 2 + run.sh | 300 ++++++++++++++++++++++--------------- run_windows.bat | 190 ++++++++++------------- setup_wizard.py | 76 ++++++++++ 6 files changed, 370 insertions(+), 243 deletions(-) create mode 100644 setup_wizard.py diff --git a/.gitignore b/.gitignore index 506e3ec..adde53a 100644 --- a/.gitignore +++ b/.gitignore @@ -95,4 +95,7 @@ docker-compose.yml # Database ollama_proxy.db -*.db-journal \ No newline at end of file +*.db-journal + +# setup state +.setup_state \ No newline at end of file diff --git a/app/api/v1/routes/admin.py b/app/api/v1/routes/admin.py index e73947f..83ca8a7 100644 --- a/app/api/v1/routes/admin.py +++ b/app/api/v1/routes/admin.py @@ -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, diff --git a/app/main.py b/app/main.py index 7656877..8c0f04d 100644 --- a/app/main.py +++ b/app/main.py @@ -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 # ---------------------------------------------------------------------- diff --git a/run.sh b/run.sh index d406b9d..c979193 100644 --- a/run.sh +++ b/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 \ No newline at end of file diff --git a/run_windows.bat b/run_windows.bat index 996a6b2..3a42894 100644 --- a/run_windows.bat +++ b/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 \ No newline at end of file +pause +exit /b \ No newline at end of file diff --git a/setup_wizard.py b/setup_wizard.py new file mode 100644 index 0000000..ec6d51d --- /dev/null +++ b/setup_wizard.py @@ -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) \ No newline at end of file