feat: add init-channels
This commit is contained in:
parent
5356e0ac64
commit
4be9e56821
1 changed files with 59 additions and 33 deletions
|
|
@ -1,15 +1,18 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Configures new-api channels and token via the admin API.
|
# Configures new-api channels and API token via the admin API.
|
||||||
# Run once after first boot: ./new-api/init-channels.sh
|
# Run once after first boot: ./new-api/init-channels.sh
|
||||||
#
|
#
|
||||||
# Requires these env vars (or .env file in project root):
|
# Requires these env vars (or .env file in project root):
|
||||||
# NEW_API_USERNAME - admin username (default: root)
|
|
||||||
# NEW_API_PASSWORD - admin password
|
# NEW_API_PASSWORD - admin password
|
||||||
# DEEPINFRA_API_KEY
|
# DEEPINFRA_API_KEY
|
||||||
# SILICONFLOW_API_KEY
|
# SILICONFLOW_API_KEY
|
||||||
# OPENROUTER_API_KEY
|
# OPENROUTER_API_KEY
|
||||||
# GROQ_API_KEY
|
# GROQ_API_KEY
|
||||||
# CEREBRAS_API_KEY
|
# CEREBRAS_API_KEY
|
||||||
|
#
|
||||||
|
# Optional:
|
||||||
|
# NEW_API_USERNAME - admin username (default: root)
|
||||||
|
# NEW_API_BASE - API base URL (default: http://localhost:4000)
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -28,9 +31,10 @@ API_BASE="${NEW_API_BASE:-http://localhost:4000}"
|
||||||
USERNAME="${NEW_API_USERNAME:-root}"
|
USERNAME="${NEW_API_USERNAME:-root}"
|
||||||
PASSWORD="${NEW_API_PASSWORD:?Set NEW_API_PASSWORD to the admin password}"
|
PASSWORD="${NEW_API_PASSWORD:?Set NEW_API_PASSWORD to the admin password}"
|
||||||
COOKIE_JAR=$(mktemp)
|
COOKIE_JAR=$(mktemp)
|
||||||
|
USER_ID=""
|
||||||
trap 'rm -f "$COOKIE_JAR"' EXIT
|
trap 'rm -f "$COOKIE_JAR"' EXIT
|
||||||
|
|
||||||
# ── Login to get session cookie ─────────────────────────
|
# ── Login and get user ID ───────────────────────────────
|
||||||
login() {
|
login() {
|
||||||
echo "Logging in as ${USERNAME}..."
|
echo "Logging in as ${USERNAME}..."
|
||||||
local resp
|
local resp
|
||||||
|
|
@ -47,10 +51,22 @@ print(json.dumps({'username': sys.argv[1], 'password': sys.argv[2]}))
|
||||||
echo "ERROR: Login failed: ${resp}"
|
echo "ERROR: Login failed: ${resp}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo " Logged in."
|
|
||||||
|
USER_ID=$(echo "$resp" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")
|
||||||
|
echo " Logged in (user ID: ${USER_ID})."
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Helper ──────────────────────────────────────────────
|
# ── API call helper (cookie + New-Api-User header) ─────
|
||||||
|
api_call() {
|
||||||
|
local endpoint="$1"
|
||||||
|
shift
|
||||||
|
curl -s -b "$COOKIE_JAR" \
|
||||||
|
-H "New-Api-User: ${USER_ID}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}${endpoint}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Create channel ──────────────────────────────────────
|
||||||
create_channel() {
|
create_channel() {
|
||||||
local name="$1" type="$2" key="$3" base_url="$4" priority="$5" models="$6" model_mapping="$7"
|
local name="$1" type="$2" key="$3" base_url="$4" priority="$5" models="$6" model_mapping="$7"
|
||||||
|
|
||||||
|
|
@ -75,10 +91,7 @@ print(json.dumps({
|
||||||
" "$type" "$name" "$key" "$base_url" "$models" "$model_mapping" "$priority")
|
" "$type" "$name" "$key" "$base_url" "$models" "$model_mapping" "$priority")
|
||||||
|
|
||||||
local resp success
|
local resp success
|
||||||
resp=$(curl -s "${API_BASE}/api/channel/" \
|
resp=$(api_call "/api/channel/" -d "$payload")
|
||||||
-b "$COOKIE_JAR" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$payload")
|
|
||||||
|
|
||||||
success=$(echo "$resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('success', False))")
|
success=$(echo "$resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('success', False))")
|
||||||
if [[ "$success" == "True" ]]; then
|
if [[ "$success" == "True" ]]; then
|
||||||
|
|
@ -89,7 +102,7 @@ print(json.dumps({
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wait for new-api to be ready
|
# ── Wait for new-api ────────────────────────────────────
|
||||||
echo "Waiting for new-api at ${API_BASE}..."
|
echo "Waiting for new-api at ${API_BASE}..."
|
||||||
for i in $(seq 1 30); do
|
for i in $(seq 1 30); do
|
||||||
if curl -sf "${API_BASE}/" > /dev/null 2>&1; then
|
if curl -sf "${API_BASE}/" > /dev/null 2>&1; then
|
||||||
|
|
@ -103,10 +116,32 @@ for i in $(seq 1 30); do
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Login first
|
# ── Login ───────────────────────────────────────────────
|
||||||
login
|
login
|
||||||
|
|
||||||
# ── Channel: DeepInfra (priority 1) ────────────────────
|
# ── Generate system access token for future use ─────────
|
||||||
|
echo ""
|
||||||
|
echo "Generating system access token..."
|
||||||
|
ACCESS_TOKEN_RESP=$(api_call "/api/user/token")
|
||||||
|
ACCESS_TOKEN=$(echo "$ACCESS_TOKEN_RESP" | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
if data.get('success'):
|
||||||
|
print(data.get('data', ''))
|
||||||
|
else:
|
||||||
|
print('')
|
||||||
|
" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$ACCESS_TOKEN" ]]; then
|
||||||
|
echo " Access token: ${ACCESS_TOKEN}"
|
||||||
|
echo " Save as NEW_API_ACCESS_TOKEN in .env for future API use."
|
||||||
|
echo " Usage: -H 'Authorization: Bearer ${ACCESS_TOKEN}' -H 'New-Api-User: ${USER_ID}'"
|
||||||
|
else
|
||||||
|
echo " Could not generate access token (non-critical, using session)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Channels ────────────────────────────────────────────
|
||||||
|
|
||||||
create_channel "DeepInfra" 1 \
|
create_channel "DeepInfra" 1 \
|
||||||
"${DEEPINFRA_API_KEY:?}" \
|
"${DEEPINFRA_API_KEY:?}" \
|
||||||
"https://api.deepinfra.com/v1/openai" \
|
"https://api.deepinfra.com/v1/openai" \
|
||||||
|
|
@ -114,7 +149,6 @@ create_channel "DeepInfra" 1 \
|
||||||
"deepseek-v3.2,deepseek-r1,gpt-oss,gpt-oss-20b,nemotron-super,nemotron-nano,devstral,glm-4.6,glm-4.7,glm-5,kimi-k2,kimi-k2.5" \
|
"deepseek-v3.2,deepseek-r1,gpt-oss,gpt-oss-20b,nemotron-super,nemotron-nano,devstral,glm-4.6,glm-4.7,glm-5,kimi-k2,kimi-k2.5" \
|
||||||
'{"deepseek-v3.2":"deepseek-ai/DeepSeek-V3.2","deepseek-r1":"deepseek-ai/DeepSeek-R1","gpt-oss":"openai/gpt-oss-120b","gpt-oss-20b":"openai/gpt-oss-20b","nemotron-super":"nvidia/Llama-3.3-Nemotron-Super-49B-v1.5","nemotron-nano":"nvidia/NVIDIA-Nemotron-Nano-9B-v2","devstral":"mistralai/Devstral-Small-2505","glm-4.6":"zai-org/GLM-4.6","glm-4.7":"zai-org/GLM-4.7","glm-5":"zai-org/GLM-5","kimi-k2":"moonshotai/Kimi-K2-Instruct-0905","kimi-k2.5":"moonshotai/Kimi-K2.5"}'
|
'{"deepseek-v3.2":"deepseek-ai/DeepSeek-V3.2","deepseek-r1":"deepseek-ai/DeepSeek-R1","gpt-oss":"openai/gpt-oss-120b","gpt-oss-20b":"openai/gpt-oss-20b","nemotron-super":"nvidia/Llama-3.3-Nemotron-Super-49B-v1.5","nemotron-nano":"nvidia/NVIDIA-Nemotron-Nano-9B-v2","devstral":"mistralai/Devstral-Small-2505","glm-4.6":"zai-org/GLM-4.6","glm-4.7":"zai-org/GLM-4.7","glm-5":"zai-org/GLM-5","kimi-k2":"moonshotai/Kimi-K2-Instruct-0905","kimi-k2.5":"moonshotai/Kimi-K2.5"}'
|
||||||
|
|
||||||
# ── Channel: SiliconFlow (priority 2) ──────────────────
|
|
||||||
create_channel "SiliconFlow" 1 \
|
create_channel "SiliconFlow" 1 \
|
||||||
"${SILICONFLOW_API_KEY:?}" \
|
"${SILICONFLOW_API_KEY:?}" \
|
||||||
"https://api.siliconflow.com/v1" \
|
"https://api.siliconflow.com/v1" \
|
||||||
|
|
@ -122,7 +156,6 @@ create_channel "SiliconFlow" 1 \
|
||||||
"deepseek-v3.2,glm-4.7,kimi-k2,qwen3-coder,qwen3-coder-30b" \
|
"deepseek-v3.2,glm-4.7,kimi-k2,qwen3-coder,qwen3-coder-30b" \
|
||||||
'{"deepseek-v3.2":"deepseek-ai/DeepSeek-V3.2","glm-4.7":"THUDM/GLM-4-32B-0414","kimi-k2":"moonshotai/Kimi-K2-Instruct-0905","qwen3-coder":"Qwen/Qwen3-Coder-480B-A35B-Instruct","qwen3-coder-30b":"Qwen/Qwen3-Coder-30B-A3B-Instruct"}'
|
'{"deepseek-v3.2":"deepseek-ai/DeepSeek-V3.2","glm-4.7":"THUDM/GLM-4-32B-0414","kimi-k2":"moonshotai/Kimi-K2-Instruct-0905","qwen3-coder":"Qwen/Qwen3-Coder-480B-A35B-Instruct","qwen3-coder-30b":"Qwen/Qwen3-Coder-30B-A3B-Instruct"}'
|
||||||
|
|
||||||
# ── Channel: OpenRouter (priority 3) ───────────────────
|
|
||||||
create_channel "OpenRouter" 1 \
|
create_channel "OpenRouter" 1 \
|
||||||
"${OPENROUTER_API_KEY:?}" \
|
"${OPENROUTER_API_KEY:?}" \
|
||||||
"https://openrouter.ai/api/v1" \
|
"https://openrouter.ai/api/v1" \
|
||||||
|
|
@ -130,7 +163,6 @@ create_channel "OpenRouter" 1 \
|
||||||
"deepseek-v3.2,deepseek-v3-free,kimi-k2.5,minimax-m2.5,gpt-4.1-mini,gpt-4.1,gemini-3-flash-preview,gemini-2.5-pro,claude-sonnet,trinity-large-preview" \
|
"deepseek-v3.2,deepseek-v3-free,kimi-k2.5,minimax-m2.5,gpt-4.1-mini,gpt-4.1,gemini-3-flash-preview,gemini-2.5-pro,claude-sonnet,trinity-large-preview" \
|
||||||
'{"deepseek-v3.2":"deepseek/deepseek-chat-v3-0324","deepseek-v3-free":"deepseek/deepseek-chat-v3-0324:free","kimi-k2.5":"moonshotai/kimi-k2.5","minimax-m2.5":"minimax/minimax-m2.5","gpt-4.1-mini":"openai/gpt-4.1-mini","gpt-4.1":"openai/gpt-4.1","gemini-3-flash-preview":"google/gemini-3-flash-preview","gemini-2.5-pro":"google/gemini-2.5-pro-preview","claude-sonnet":"anthropic/claude-sonnet-4","trinity-large-preview":"arcee-ai/trinity-large-preview"}'
|
'{"deepseek-v3.2":"deepseek/deepseek-chat-v3-0324","deepseek-v3-free":"deepseek/deepseek-chat-v3-0324:free","kimi-k2.5":"moonshotai/kimi-k2.5","minimax-m2.5":"minimax/minimax-m2.5","gpt-4.1-mini":"openai/gpt-4.1-mini","gpt-4.1":"openai/gpt-4.1","gemini-3-flash-preview":"google/gemini-3-flash-preview","gemini-2.5-pro":"google/gemini-2.5-pro-preview","claude-sonnet":"anthropic/claude-sonnet-4","trinity-large-preview":"arcee-ai/trinity-large-preview"}'
|
||||||
|
|
||||||
# ── Channel: Groq (priority 1) ─────────────────────────
|
|
||||||
create_channel "Groq" 1 \
|
create_channel "Groq" 1 \
|
||||||
"${GROQ_API_KEY:?}" \
|
"${GROQ_API_KEY:?}" \
|
||||||
"https://api.groq.com/openai/v1" \
|
"https://api.groq.com/openai/v1" \
|
||||||
|
|
@ -138,7 +170,6 @@ create_channel "Groq" 1 \
|
||||||
"llama-3.3-70b" \
|
"llama-3.3-70b" \
|
||||||
'{"llama-3.3-70b":"llama-3.3-70b-versatile"}'
|
'{"llama-3.3-70b":"llama-3.3-70b-versatile"}'
|
||||||
|
|
||||||
# ── Channel: Cerebras (priority 1) ─────────────────────
|
|
||||||
create_channel "Cerebras" 1 \
|
create_channel "Cerebras" 1 \
|
||||||
"${CEREBRAS_API_KEY:?}" \
|
"${CEREBRAS_API_KEY:?}" \
|
||||||
"https://api.cerebras.ai/v1" \
|
"https://api.cerebras.ai/v1" \
|
||||||
|
|
@ -146,13 +177,10 @@ create_channel "Cerebras" 1 \
|
||||||
"llama-3.3-70b-cerebras" \
|
"llama-3.3-70b-cerebras" \
|
||||||
'{"llama-3.3-70b-cerebras":"llama-3.3-70b"}'
|
'{"llama-3.3-70b-cerebras":"llama-3.3-70b"}'
|
||||||
|
|
||||||
# ── Create API token for Open WebUI ────────────────────
|
# ── Create API token for Open WebUI ─────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "Creating API token for Open WebUI..."
|
echo "Creating API token for Open WebUI..."
|
||||||
TOKEN_RESP=$(curl -s "${API_BASE}/api/token/" \
|
TOKEN_RESP=$(api_call "/api/token/" -d "$(python3 -c "
|
||||||
-b "$COOKIE_JAR" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$(python3 -c "
|
|
||||||
import json
|
import json
|
||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
'name': 'open-webui',
|
'name': 'open-webui',
|
||||||
|
|
@ -170,23 +198,21 @@ else:
|
||||||
print('FAILED: ' + data.get('message', 'unknown error'))
|
print('FAILED: ' + data.get('message', 'unknown error'))
|
||||||
" 2>/dev/null || echo "FAILED: could not parse response")
|
" 2>/dev/null || echo "FAILED: could not parse response")
|
||||||
|
|
||||||
echo " Token: ${TOKEN_KEY}"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "══════════════════════════════════════"
|
echo "══════════════════════════════════════"
|
||||||
echo "Channel setup complete!"
|
echo "Setup complete!"
|
||||||
echo ""
|
echo ""
|
||||||
if [[ "$TOKEN_KEY" != FAILED* ]]; then
|
if [[ "$TOKEN_KEY" != FAILED* ]]; then
|
||||||
echo "Open WebUI API key: ${TOKEN_KEY}"
|
echo "Open WebUI API key: ${TOKEN_KEY}"
|
||||||
echo "Set OPENWEBUI_API_KEY=${TOKEN_KEY} in your .env"
|
echo "Set OPENWEBUI_API_KEY=${TOKEN_KEY} in your .env"
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Test:"
|
||||||
echo " 1. Verify channels at ${API_BASE}"
|
|
||||||
echo " 2. Test a model:"
|
|
||||||
echo " curl ${API_BASE}/v1/chat/completions \\"
|
echo " curl ${API_BASE}/v1/chat/completions \\"
|
||||||
echo " -H 'Authorization: Bearer ${TOKEN_KEY}' \\"
|
echo " -H 'Authorization: Bearer ${TOKEN_KEY}' \\"
|
||||||
echo " -H 'Content-Type: application/json' \\"
|
echo " -H 'Content-Type: application/json' \\"
|
||||||
echo " -d '{\"model\":\"deepseek-v3.2\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'"
|
echo " -d '{\"model\":\"deepseek-v3.2\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'"
|
||||||
echo " 3. Restart Open WebUI with the new API key"
|
else
|
||||||
|
echo "Token creation: ${TOKEN_KEY}"
|
||||||
|
echo "Create a token manually in the new-api UI."
|
||||||
|
fi
|
||||||
echo "══════════════════════════════════════"
|
echo "══════════════════════════════════════"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue