#!/bin/sh # RPCD backend for Ollama LuCI integration # Copyright (C) 2025 CyberMind.fr . /lib/functions.sh CONFIG="ollama" OLLAMA_CTL="/usr/sbin/ollamactl" # Load UCI config load_config() { config_load "$CONFIG" config_get API_PORT main api_port "11434" config_get DATA_PATH main data_path "/srv/ollama" config_get MEMORY_LIMIT main memory_limit "2g" } # Detect container runtime detect_runtime() { if command -v podman >/dev/null 2>&1; then echo "podman" elif command -v docker >/dev/null 2>&1; then echo "docker" else echo "" fi } # Check if Ollama is running is_running() { local rt=$(detect_runtime) [ -z "$rt" ] && return 1 $rt ps --format '{{.Names}}' 2>/dev/null | grep -q "^ollama$" } # Get service status get_status() { load_config local running="false" local uptime=0 local rt=$(detect_runtime) if is_running; then running="true" # Get container uptime if [ -n "$rt" ]; then local status=$($rt ps --filter "name=ollama" --format '{{.Status}}' 2>/dev/null | head -1) if [ -n "$status" ]; then case "$status" in *minute*) uptime=$(($(echo "$status" | grep -oE '[0-9]+' | head -1) * 60)) ;; *hour*) uptime=$(($(echo "$status" | grep -oE '[0-9]+' | head -1) * 3600)) ;; *second*) uptime=$(echo "$status" | grep -oE '[0-9]+' | head -1) ;; *) uptime=0 ;; esac fi fi fi local enabled="false" [ "$(uci -q get ${CONFIG}.main.enabled)" = "1" ] && enabled="true" cat </dev/null if [ -f "$tmpfile" ] && [ -s "$tmpfile" ]; then local i=0 while [ $i -lt 50 ]; do local model_name=$(jsonfilter -i "$tmpfile" -e "@.models[$i].name" 2>/dev/null) [ -z "$model_name" ] && break local model_size=$(jsonfilter -i "$tmpfile" -e "@.models[$i].size" 2>/dev/null) [ -z "$model_size" ] && model_size=0 local modified=$(jsonfilter -i "$tmpfile" -e "@.models[$i].modified_at" 2>/dev/null) [ $first -eq 0 ] && echo "," first=0 cat </dev/null; then healthy="true" api_status="ok" else api_status="unhealthy" fi else api_status="stopped" fi cat </dev/null 2>&1 sleep 3 if is_running; then echo '{"success":true}' else echo '{"success":false,"error":"Failed to start"}' fi } # Stop service do_stop() { /etc/init.d/ollama stop >/dev/null 2>&1 sleep 1 if ! is_running; then echo '{"success":true}' else echo '{"success":false,"error":"Failed to stop"}' fi } # Restart service do_restart() { /etc/init.d/ollama restart >/dev/null 2>&1 sleep 3 if is_running; then echo '{"success":true}' else echo '{"success":false,"error":"Failed to restart"}' fi } # Pull model do_model_pull() { local name="$1" [ -z "$name" ] && { echo '{"success":false,"error":"Model name required"}'; return; } if ! is_running; then echo '{"success":false,"error":"Ollama not running"}' return fi local rt=$(detect_runtime) local output=$($rt exec ollama ollama pull "$name" 2>&1) local ret=$? if [ $ret -eq 0 ]; then echo '{"success":true}' else local error=$(echo "$output" | tail -1 | sed 's/"/\\"/g') echo "{\"success\":false,\"error\":\"$error\"}" fi } # Remove model do_model_remove() { local name="$1" [ -z "$name" ] && { echo '{"success":false,"error":"Model name required"}'; return; } if ! is_running; then echo '{"success":false,"error":"Ollama not running"}' return fi local rt=$(detect_runtime) local output=$($rt exec ollama ollama rm "$name" 2>&1) local ret=$? if [ $ret -eq 0 ]; then echo '{"success":true}' else local error=$(echo "$output" | tail -1 | sed 's/"/\\"/g') echo "{\"success\":false,\"error\":\"$error\"}" fi } # Chat completion (proxy to Ollama API) do_chat() { load_config local model="$1" local message="$2" if ! is_running; then echo '{"response":"","error":"Ollama is not running. Start with: /etc/init.d/ollama start"}' return fi [ -z "$model" ] && { echo '{"response":"","error":"Model not specified"}'; return; } [ -z "$message" ] && { echo '{"response":"","error":"Message not provided"}'; return; } # Build request body for Ollama /api/chat endpoint local request_body="{\"model\":\"$model\",\"messages\":[{\"role\":\"user\",\"content\":\"$message\"}],\"stream\":false}" logger -t ollama-chat "Request to model: $model" local tmpfile="/tmp/ollama_chat_$$" if command -v curl >/dev/null 2>&1; then curl -s -X POST "http://127.0.0.1:$API_PORT/api/chat" \ -H "Content-Type: application/json" \ -d "$request_body" \ -o "$tmpfile" 2>/dev/null else wget -q -O "$tmpfile" --post-data "$request_body" \ --header="Content-Type: application/json" \ "http://127.0.0.1:$API_PORT/api/chat" 2>/dev/null fi if [ -f "$tmpfile" ] && [ -s "$tmpfile" ]; then local content=$(jsonfilter -i "$tmpfile" -e '@.message.content' 2>/dev/null) local error=$(jsonfilter -i "$tmpfile" -e '@.error' 2>/dev/null) if [ -n "$error" ]; then error=$(echo "$error" | sed 's/"/\\"/g' | tr '\n' ' ') echo "{\"response\":\"\",\"error\":\"$error\"}" elif [ -n "$content" ]; then content=$(printf '%s' "$content" | sed 's/\\/\\\\/g; s/"/\\"/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//') echo "{\"response\":\"$content\"}" else echo '{"response":"","error":"Empty response from Ollama API"}' fi rm -f "$tmpfile" else rm -f "$tmpfile" echo '{"response":"","error":"API request failed"}' fi } # Generate completion do_generate() { load_config local model="$1" local prompt="$2" if ! is_running; then echo '{"text":"","error":"Ollama not running"}' return fi local response=$(wget -q -O - --post-data "{\"model\":\"$model\",\"prompt\":\"$prompt\",\"stream\":false}" \ --header="Content-Type: application/json" \ "http://127.0.0.1:$API_PORT/api/generate" 2>/dev/null) if [ -n "$response" ]; then local text=$(echo "$response" | jsonfilter -e '@.response' 2>/dev/null) text=$(printf '%s' "$text" | sed 's/\\/\\\\/g; s/"/\\"/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//') echo "{\"text\":\"$text\"}" else echo '{"text":"","error":"API request failed"}' fi } # UBUS method list case "$1" in list) cat <<'EOF' { "status": {}, "models": {}, "config": {}, "health": {}, "start": {}, "stop": {}, "restart": {}, "model_pull": {"name": "string"}, "model_remove": {"name": "string"}, "chat": {"model": "string", "message": "string"}, "generate": {"model": "string", "prompt": "string"} } EOF ;; call) case "$2" in status) get_status ;; models) get_models ;; config) get_config ;; health) get_health ;; start) do_start ;; stop) do_stop ;; restart) do_restart ;; model_pull) read -r input name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null) do_model_pull "$name" ;; model_remove) read -r input name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null) do_model_remove "$name" ;; chat) read -r input model=$(echo "$input" | jsonfilter -e '@.model' 2>/dev/null) message=$(echo "$input" | jsonfilter -e '@.message' 2>/dev/null) do_chat "$model" "$message" ;; generate) read -r input model=$(echo "$input" | jsonfilter -e '@.model' 2>/dev/null) prompt=$(echo "$input" | jsonfilter -e '@.prompt' 2>/dev/null) do_generate "$model" "$prompt" ;; *) echo '{"error":"Unknown method"}' ;; esac ;; esac