diff --git a/configure-searxng-safesearch.sh b/configure-searxng-safesearch.sh new file mode 100755 index 0000000..c7bcd52 --- /dev/null +++ b/configure-searxng-safesearch.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +# configure-searxng-safesearch.sh +# Set SearXNG safe-search level; optionally disable categories or engines. +# +# Usage: +# ./configure-searxng-safesearch.sh [strict|moderate|none] [OPTIONS] +# +# Options: +# --disable-categories cat1,cat2 videos images news science social +# --disable-engines eng1,eng2 e.g. duckduckgo,bing,yandex +# --enable-engines eng1,eng2 re-enable engines auto-disabled by level +# +# Examples: +# ./configure-searxng-safesearch.sh strict +# ./configure-searxng-safesearch.sh moderate --disable-categories videos,images +# ./configure-searxng-safesearch.sh none --disable-engines bing,duckduckgo +# ./configure-searxng-safesearch.sh strict --disable-categories videos \ +# --disable-engines bing --enable-engines yandex + +set -euo pipefail + +# ── Helpers ─────────────────────────────────────────────────────────────────── +red() { printf '\e[31m%s\e[0m\n' "$*"; } +grn() { printf '\e[32m%s\e[0m\n' "$*"; } +blu() { printf '\e[34m%s\e[0m\n' "$*"; } +yel() { printf '\e[33m%s\e[0m\n' "$*"; } +die() { red "ERROR: $*"; exit 1; } +ok() { grn " ✓ $*"; } +info() { blu " → $*"; } +warn() { yel " ! $*"; } + +# ── Defaults ────────────────────────────────────────────────────────────────── +LEVEL="moderate" +DISABLE_CATS="" +DISABLE_ENGINES_EXTRA="" +ENABLE_ENGINES_EXTRA="" +BASE="${BASE:-$HOME/docker/ai-stack}" + +# ── Parse arguments ─────────────────────────────────────────────────────────── +while [[ $# -gt 0 ]]; do + case "$1" in + strict|moderate|none) LEVEL="$1"; shift ;; + --disable-categories) DISABLE_CATS="$2"; shift 2 ;; + --disable-engines) DISABLE_ENGINES_EXTRA="$2"; shift 2 ;; + --enable-engines) ENABLE_ENGINES_EXTRA="$2"; shift 2 ;; + --base) BASE="$2"; shift 2 ;; + -h|--help) + sed -n '2,20p' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) die "Unknown argument: $1 (run with --help)" ;; + esac +done + +SETTINGS="$BASE/searxng/settings.yml" +COMPOSE="$BASE/docker-compose.yml" + +# Ensure settings directory exists +mkdir -p "$(dirname "$SETTINGS")" + +# ── Level → integer ─────────────────────────────────────────────────────────── +case "$LEVEL" in + none) SAFE_INT=0 ;; + moderate) SAFE_INT=1 ;; + strict) SAFE_INT=2 ;; +esac + +info "Safe search: $LEVEL (${SAFE_INT})" + +# ── Engines with no safe-search support ─────────────────────────────────────── +# Auto-disabled when level is moderate or strict. +NO_SAFESEARCH_ENGINES=( + # Torrent / P2P — no filtering possible + "1337x" "piratebay" "nyaa" "torrentz" "kickass torrents" + # Web engines without safe-search API + "mojeek" "naver" "baidu" + # Yandex: parameter exists but not reliably enforced for non-Russian queries + "yandex" + # Video frontends — no safe-search passthrough + "invidious" "piped" "peertube" "sepiasearch" +) + +# ── Category → engine lists ─────────────────────────────────────────────────── +VIDEOS_ENGINES=( + "youtube" "invidious" "piped" "peertube" "sepiasearch" + "dailymotion" "vimeo" + "bing videos" "duckduckgo videos" "google videos" +) +IMAGES_ENGINES=( + "google images" "bing images" "duckduckgo images" + "brave images" "qwant images" + "flickr" "unsplash" "imgur" "deviantart" "openverse" +) +NEWS_ENGINES=( + "google news" "bing news" "duckduckgo news" "brave news" "qwant news" +) +SCIENCE_ENGINES=( + "arxiv" "semantic scholar" "pubmed" "crossref" "base" +) +SOCIAL_ENGINES=( + "reddit" "lemmy" "mastodon" +) + +# ── Build disable/enable maps ───────────────────────────────────────────────── +declare -A DISABLE_MAP # engine → 1 +declare -A ENABLE_MAP # engine → 1 (overrides everything) + +# Parse --enable-engines +if [[ -n "$ENABLE_ENGINES_EXTRA" ]]; then + IFS=',' read -ra _engs <<< "$ENABLE_ENGINES_EXTRA" + for e in "${_engs[@]}"; do + e="${e#"${e%%[![:space:]]*}"}"; e="${e%"${e##*[![:space:]]}"}" # trim + [[ -n "$e" ]] && ENABLE_MAP["$e"]=1 + done +fi + +# Helper: add to DISABLE_MAP unless explicitly re-enabled +mark_disabled() { + local eng="$1" + [[ -n "${ENABLE_MAP[$eng]+x}" ]] && return # user said keep it + DISABLE_MAP["$eng"]=1 +} + +# Auto-disable no-safesearch engines for moderate/strict +if [[ "$LEVEL" != "none" ]]; then + for eng in "${NO_SAFESEARCH_ENGINES[@]}"; do + mark_disabled "$eng" + done +fi + +# Category disables +if [[ -n "$DISABLE_CATS" ]]; then + IFS=',' read -ra _cats <<< "$DISABLE_CATS" + for cat in "${_cats[@]}"; do + cat="${cat#"${cat%%[![:space:]]*}"}"; cat="${cat%"${cat##*[![:space:]]}"}" + cat="${cat,,}" + case "$cat" in + videos) for e in "${VIDEOS_ENGINES[@]}"; do mark_disabled "$e"; done ;; + images) for e in "${IMAGES_ENGINES[@]}"; do mark_disabled "$e"; done ;; + news) for e in "${NEWS_ENGINES[@]}"; do mark_disabled "$e"; done ;; + science) for e in "${SCIENCE_ENGINES[@]}"; do mark_disabled "$e"; done ;; + social) for e in "${SOCIAL_ENGINES[@]}"; do mark_disabled "$e"; done ;; + "") ;; + *) warn "Unknown category '$cat' — valid: videos images news science social" ;; + esac + done +fi + +# Extra engine disables +if [[ -n "$DISABLE_ENGINES_EXTRA" ]]; then + IFS=',' read -ra _engs <<< "$DISABLE_ENGINES_EXTRA" + for e in "${_engs[@]}"; do + e="${e#"${e%%[![:space:]]*}"}"; e="${e%"${e##*[![:space:]]}"}" + [[ -n "$e" ]] && mark_disabled "$e" + done +fi + +# ── Preserve existing secret key ───────────────────────────────────────────── +SECRET_KEY=$(grep -oP '(?<=secret_key: ")[^"]+' "$SETTINGS" 2>/dev/null || true) +[[ -z "$SECRET_KEY" ]] && SECRET_KEY=$(openssl rand -hex 32) + +# ── Build engine override block ─────────────────────────────────────────────── +ENGINE_BLOCK="" +for eng in "${!DISABLE_MAP[@]}"; do + ENGINE_BLOCK+=" - name: ${eng}\n disabled: true\n" +done +for eng in "${!ENABLE_MAP[@]}"; do + ENGINE_BLOCK+=" - name: ${eng}\n disabled: false\n" +done + +# ── Write settings.yml ──────────────────────────────────────────────────────── +{ + printf 'use_default_settings: true\n' + printf 'general:\n instance_name: "Local Search"\n' + printf 'server:\n secret_key: "%s"\n limiter: false\n' "$SECRET_KEY" + printf 'search:\n safe_search: %d\n default_lang: "en"\n formats: [html, json]\n' "$SAFE_INT" + if [[ -n "$ENGINE_BLOCK" ]]; then + printf 'engines:\n' + printf '%b' "$ENGINE_BLOCK" + fi +} > "$SETTINGS" + +ok "Updated settings.yml (safe_search: $SAFE_INT)" +if [[ ${#DISABLE_MAP[@]} -gt 0 ]]; then + info "Disabled (${#DISABLE_MAP[@]}): $(printf '%s, ' "${!DISABLE_MAP[@]}" | sed 's/, $//')" +fi +if [[ ${#ENABLE_MAP[@]} -gt 0 ]]; then + info "Re-enabled: $(printf '%s, ' "${!ENABLE_MAP[@]}" | sed 's/, $//')" +fi + +# ── Update &safesearch= in SEARXNG_QUERY_URL inside docker-compose.yml ──────── +if [[ -f "$COMPOSE" ]]; then + sed -i -E \ + "s|(SEARXNG_QUERY_URL=http://searxng:[0-9]+/search\?[^&[:space:]]*)(&safesearch=[0-9])?|\1\&safesearch=${SAFE_INT}|g" \ + "$COMPOSE" + ok "Updated SEARXNG_QUERY_URL (&safesearch=${SAFE_INT})" +fi + +# ── Restart SearXNG ─────────────────────────────────────────────────────────── +if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^searxng$'; then + info "Restarting SearXNG..." + docker restart searxng + ok "SearXNG restarted" +else + info "SearXNG not running — changes take effect on next start" +fi + +echo +grn "Done — safe search: $LEVEL" +[[ "$LEVEL" != "none" ]] && \ + info "Engines skipped (can't enforce '$LEVEL'): ${#DISABLE_MAP[@]} total" diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index 6046dd7..6840215 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -493,6 +493,156 @@ if $INSTALL_AI; then [[ "${DO_PULL,,}" != "n" ]] && PULL_MODELS=true fi +# ── Q6: SearXNG safe-search & engine selection ──────────────────────────────── +SEARXNG_SAFE_LEVEL="none" +SEARXNG_SAFE_INT=0 +SEARXNG_DISABLE_ENGINES="" +SEARXNG_ENABLE_ENGINES="" + +if $INSTALL_AI && $SVC_SEARXNG; then + echo "" + echo -e " ${BOLD}[6/6] SearXNG safe-search${NC}" + echo " 0) None — all results, no filtering (default)" + echo " 1) Moderate — filter explicit content" + echo " 2) Strict — block all explicit content" + echo "" + read -rp " Choice [0]: " _SAFE_PICK + case "${_SAFE_PICK:-0}" in + 1) SEARXNG_SAFE_LEVEL="moderate"; SEARXNG_SAFE_INT=1 ;; + 2) SEARXNG_SAFE_LEVEL="strict"; SEARXNG_SAFE_INT=2 ;; + *) SEARXNG_SAFE_LEVEL="none"; SEARXNG_SAFE_INT=0 ;; + esac + + # Default ON/OFF for an engine given the chosen safe-search level + _sxst() { + [[ "$SEARXNG_SAFE_LEVEL" == "none" ]] && echo ON && return + case "$1" in + mojeek|yandex|baidu|naver|invidious|piped|peertube|sepiasearch|\ + dailymotion|vimeo|imgur|deviantart|flickr) echo OFF ;; + *) echo ON ;; + esac + } + + # Whiptail checklist — prints selected engine names (one per line). + # ESC returns the engines that were pre-checked ON (keeps defaults unchanged). + # Args: "Title" name "Desc" ON|OFF [name "Desc" ON|OFF ...] + _sxmenu() { + local _t="$1"; shift + local -a _n=() _s=() _a=(); local _i=1 + while [[ $# -ge 3 ]]; do + _n+=("$1"); _s+=("$3") + _a+=("$_i" "$(printf '%-22s %s' "$1" "$2")" "$3") + _i=$(( _i + 1 )); shift 3 + done + local _h=$(( ${#_n[@]} + 9 > 24 ? 24 : ${#_n[@]} + 9 )) + local _lh=$(( ${#_n[@]} < 14 ? ${#_n[@]} : 14 )) + local _o _j + if ! _o=$(whiptail --backtitle "SearXNG — $SEARXNG_SAFE_LEVEL" \ + --title "$_t" --checklist \ + "SPACE = toggle ENTER = confirm ESC = use defaults" \ + "$_h" 72 "$_lh" "${_a[@]}" 3>&1 1>&2 2>&3 2>/dev/null); then + for _j in "${!_n[@]}"; do + [[ "${_s[$_j]}" == "ON" ]] && printf '%s\n' "${_n[$_j]}" + done + return + fi + for _id in $_o; do _id="${_id//\"/}"; printf '%s\n' "${_n[$(( _id-1 ))]}"; done + } + + # Engines auto-disabled by configure script for moderate/strict. + # If user re-enables one in the menu we pass --enable-engines to override. + _SX_AUTOFF=(mojeek yandex baidu naver invidious piped peertube sepiasearch) + + if command -v whiptail &>/dev/null && [[ -t 0 ]]; then + _SX_W=$(_sxmenu "Web Search Engines" \ + "google" "Google" ON \ + "bing" "Microsoft Bing" ON \ + "duckduckgo" "DuckDuckGo" ON \ + "brave" "Brave Search" ON \ + "startpage" "Startpage (Google proxy)" ON \ + "qwant" "Qwant" ON \ + "yahoo" "Yahoo" ON \ + "ecosia" "Ecosia" ON \ + "mojeek" "Mojeek [no safe-search]" "$(_sxst mojeek)" \ + "yandex" "Yandex [unreliable filter]" "$(_sxst yandex)" \ + "baidu" "Baidu [no safe-search]" "$(_sxst baidu)" \ + "naver" "Naver [no safe-search]" "$(_sxst naver)") + + _SX_I=$(_sxmenu "Image Search Engines" \ + "google images" "Google Images" ON \ + "bing images" "Bing Images" ON \ + "duckduckgo images" "DuckDuckGo Images" ON \ + "brave images" "Brave Images" ON \ + "qwant images" "Qwant Images" ON \ + "openverse" "Openverse (open license)" ON \ + "unsplash" "Unsplash (stock photos)" ON \ + "flickr" "Flickr [no safe-search]" "$(_sxst flickr)" \ + "imgur" "Imgur [no safe-search]" "$(_sxst imgur)" \ + "deviantart" "DeviantArt [no filter]" "$(_sxst deviantart)") + + _SX_V=$(_sxmenu "Video Search Engines" \ + "youtube" "YouTube (via Google)" ON \ + "bing videos" "Bing Videos" ON \ + "duckduckgo videos" "DuckDuckGo Videos" ON \ + "google videos" "Google Videos" ON \ + "invidious" "Invidious [no filter]" "$(_sxst invidious)" \ + "piped" "Piped [no filter]" "$(_sxst piped)" \ + "peertube" "PeerTube [no filter]" "$(_sxst peertube)" \ + "sepiasearch" "SepiaSearch [no filter]" "$(_sxst sepiasearch)" \ + "dailymotion" "Dailymotion [no filter]" "$(_sxst dailymotion)" \ + "vimeo" "Vimeo [no filter]" "$(_sxst vimeo)") + + _SX_N=$(_sxmenu "News Search Engines" \ + "google news" "Google News" ON \ + "bing news" "Bing News" ON \ + "duckduckgo news" "DuckDuckGo News" ON \ + "brave news" "Brave News" ON \ + "qwant news" "Qwant News" ON) + + _SX_S=$(_sxmenu "Science / Academic" \ + "arxiv" "arXiv (preprints)" ON \ + "semantic scholar" "Semantic Scholar" ON \ + "pubmed" "PubMed (medical)" ON \ + "crossref" "Crossref (DOI/papers)" ON \ + "base" "BASE (open access)" ON) + + _SX_ALL=( + "google" "bing" "duckduckgo" "brave" "startpage" "qwant" "yahoo" "ecosia" + "mojeek" "yandex" "baidu" "naver" + "google images" "bing images" "duckduckgo images" "brave images" "qwant images" + "openverse" "unsplash" "flickr" "imgur" "deviantart" + "youtube" "bing videos" "duckduckgo videos" "google videos" + "invidious" "piped" "peertube" "sepiasearch" "dailymotion" "vimeo" + "google news" "bing news" "duckduckgo news" "brave news" "qwant news" + "arxiv" "semantic scholar" "pubmed" "crossref" "base" + ) + _SX_SEL=$(printf '%s\n' "$_SX_W" "$_SX_I" "$_SX_V" "$_SX_N" "$_SX_S") + + for _e in "${_SX_ALL[@]}"; do + if printf '%s\n' "$_SX_SEL" | grep -qxF "$_e"; then + # Selected — if auto-disabled by level, pass --enable-engines to override + for _ao in "${_SX_AUTOFF[@]}"; do + if [[ "$_e" == "$_ao" ]]; then + SEARXNG_ENABLE_ENGINES+="${SEARXNG_ENABLE_ENGINES:+,}${_e}" + break + fi + done + else + SEARXNG_DISABLE_ENGINES+="${SEARXNG_DISABLE_ENGINES:+,}${_e}" + fi + done + else + # Fallback: no whiptail / non-interactive terminal + echo "" + echo " (whiptail not available — text entry)" + echo " Web: google bing duckduckgo brave startpage qwant yahoo" + echo " Imgs: google_images bing_images duckduckgo_images flickr imgur" + echo " Vids: youtube invidious piped peertube dailymotion vimeo" + read -rp " Engines to disable (space-separated, Enter for defaults): " _ENGS + SEARXNG_DISABLE_ENGINES="${_ENGS// /,}" + fi +fi + # ── Summary ─────────────────────────────────────────────────────────────────── echo "" echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" @@ -521,6 +671,14 @@ if $INSTALL_AI; then fi fi $SVC_KIWIX && echo " ✓ Kiwix ZIMs → $KIWIX_DIR" +if $INSTALL_AI && $SVC_SEARXNG; then + _sx="safe_search: $SEARXNG_SAFE_LEVEL" + if [[ -n "$SEARXNG_DISABLE_ENGINES" ]]; then + _ndis=$(tr ',' '\n' <<< "$SEARXNG_DISABLE_ENGINES" | grep -c .) + _sx+=" | ${_ndis} engines disabled" + fi + echo " ✓ SearXNG → $_sx" +fi $PULL_MODELS && echo " ✓ Pull models : $EMBED_MODEL + $FAST_MODEL + $CHAT_MODEL + $CODE_MODEL${REASON_MODEL:+ + $REASON_MODEL}" [[ "$ZIM_CHOICE" == "1" ]] && echo " ✓ Download all ZIMs in background (~130GB)" [[ "$ZIM_CHOICE" == "2" ]] && echo " ✓ Select ZIMs to download (prompted after stack starts)" @@ -674,23 +832,16 @@ ok "mcp_requirements.txt" fi # INSTALL_AI # ============================================================================= -if $INSTALL_AI; then +if $INSTALL_AI && $SVC_SEARXNG; then section "SearXNG Config" # ============================================================================= -write_if_new "$BASE/searxng/settings.yml" << SEARXNG -use_default_settings: true -general: - instance_name: "Local Search" -server: - secret_key: "$(openssl rand -hex 32)" - limiter: false -search: - safe_search: 0 - default_lang: "en" - formats: [html, json] -SEARXNG +mkdir -p "$BASE/searxng" +_SXARGS=("$SEARXNG_SAFE_LEVEL") +[[ -n "$SEARXNG_DISABLE_ENGINES" ]] && _SXARGS+=(--disable-engines "$SEARXNG_DISABLE_ENGINES") +[[ -n "$SEARXNG_ENABLE_ENGINES" ]] && _SXARGS+=(--enable-engines "$SEARXNG_ENABLE_ENGINES") +BASE="$BASE" bash "$SCRIPT_DIR/configure-searxng-safesearch.sh" "${_SXARGS[@]}" -fi # INSTALL_AI +fi # INSTALL_AI && SVC_SEARXNG # ============================================================================= section ".env File" @@ -780,8 +931,11 @@ ${OLLAMA_VOLUME_LINE} - ENABLE_OPENAI_API=true - ENABLE_RAG_WEB_SEARCH=true - RAG_WEB_SEARCH_ENGINE=searxng - - SEARXNG_QUERY_URL=http://searxng:8080/search?q=&format=json - - WEBUI_AUTH=false + - SEARXNG_QUERY_URL=http://searxng:8080/search?q=&format=json&safesearch=${SEARXNG_SAFE_INT} + - RAG_WEB_SEARCH_RESULT_COUNT=5 + - RAG_WEB_SEARCH_CONCURRENT_REQUESTS=10 + - ENABLE_TOOL_SERVERS=true + - WEBUI_AUTH=true - WEBUI_URL=${WEBUI_URL:-} depends_on: ollama: diff --git a/local-ai-setup.sh b/local-ai-setup.sh index 263be2c..f161ee9 100755 --- a/local-ai-setup.sh +++ b/local-ai-setup.sh @@ -14,6 +14,7 @@ FORCE=false; NO_PULL=false for a in "$@"; do [[ "$a" == "--force" ]] && FORCE=true; [[ "$a" == "--no-pull" ]] && NO_PULL=true; done BASE="$HOME/docker/ai-stack" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K\S+' || hostname -I | awk '{print $1}') [[ -z "$LOCAL_IP" ]] && read -rp "Enter LAN IP: " LOCAL_IP @@ -43,6 +44,147 @@ info "Base : $BASE" info "IP : $LOCAL_IP" info "GPU : ${VRAM_GB}GB VRAM → $TIER" +# ── SearXNG safe-search & engine selection ──────────────────────────────────── +SEARXNG_DISABLE_ENGINES="" +SEARXNG_ENABLE_ENGINES="" +echo "" +echo " SearXNG safe-search level:" +echo " 0) None — all results, no filtering (default)" +echo " 1) Moderate — filter explicit content" +echo " 2) Strict — block all explicit content" +echo "" +read -rp " Choice [0]: " _SAFE_PICK +case "${_SAFE_PICK:-0}" in + 1) SEARXNG_SAFE_LEVEL="moderate"; SEARXNG_SAFE_INT=1 ;; + 2) SEARXNG_SAFE_LEVEL="strict"; SEARXNG_SAFE_INT=2 ;; + *) SEARXNG_SAFE_LEVEL="none"; SEARXNG_SAFE_INT=0 ;; +esac + +# Default ON/OFF for an engine given the chosen safe-search level +_sxst() { + [[ "$SEARXNG_SAFE_LEVEL" == "none" ]] && echo ON && return + case "$1" in + mojeek|yandex|baidu|naver|invidious|piped|peertube|sepiasearch|\ + dailymotion|vimeo|imgur|deviantart|flickr) echo OFF ;; + *) echo ON ;; + esac +} + +# Whiptail checklist — prints selected engine names (one per line). +# ESC returns the engines that were pre-checked ON (keeps defaults unchanged). +_sxmenu() { + local _t="$1"; shift + local -a _n=() _s=() _a=(); local _i=1 + while [[ $# -ge 3 ]]; do + _n+=("$1"); _s+=("$3") + _a+=("$_i" "$(printf '%-22s %s' "$1" "$2")" "$3") + _i=$(( _i + 1 )); shift 3 + done + local _h=$(( ${#_n[@]} + 9 > 24 ? 24 : ${#_n[@]} + 9 )) + local _lh=$(( ${#_n[@]} < 14 ? ${#_n[@]} : 14 )) + local _o _j + if ! _o=$(whiptail --backtitle "SearXNG — $SEARXNG_SAFE_LEVEL" \ + --title "$_t" --checklist \ + "SPACE = toggle ENTER = confirm ESC = use defaults" \ + "$_h" 72 "$_lh" "${_a[@]}" 3>&1 1>&2 2>&3 2>/dev/null); then + for _j in "${!_n[@]}"; do + [[ "${_s[$_j]}" == "ON" ]] && printf '%s\n' "${_n[$_j]}" + done + return + fi + for _id in $_o; do _id="${_id//\"/}"; printf '%s\n' "${_n[$(( _id-1 ))]}"; done +} + +_SX_AUTOFF=(mojeek yandex baidu naver invidious piped peertube sepiasearch) + +if command -v whiptail &>/dev/null && [[ -t 0 ]]; then + _SX_W=$(_sxmenu "Web Search Engines" \ + "google" "Google" ON \ + "bing" "Microsoft Bing" ON \ + "duckduckgo" "DuckDuckGo" ON \ + "brave" "Brave Search" ON \ + "startpage" "Startpage (Google proxy)" ON \ + "qwant" "Qwant" ON \ + "yahoo" "Yahoo" ON \ + "ecosia" "Ecosia" ON \ + "mojeek" "Mojeek [no safe-search]" "$(_sxst mojeek)" \ + "yandex" "Yandex [unreliable filter]" "$(_sxst yandex)" \ + "baidu" "Baidu [no safe-search]" "$(_sxst baidu)" \ + "naver" "Naver [no safe-search]" "$(_sxst naver)") + + _SX_I=$(_sxmenu "Image Search Engines" \ + "google images" "Google Images" ON \ + "bing images" "Bing Images" ON \ + "duckduckgo images" "DuckDuckGo Images" ON \ + "brave images" "Brave Images" ON \ + "qwant images" "Qwant Images" ON \ + "openverse" "Openverse (open license)" ON \ + "unsplash" "Unsplash (stock photos)" ON \ + "flickr" "Flickr [no safe-search]" "$(_sxst flickr)" \ + "imgur" "Imgur [no safe-search]" "$(_sxst imgur)" \ + "deviantart" "DeviantArt [no filter]" "$(_sxst deviantart)") + + _SX_V=$(_sxmenu "Video Search Engines" \ + "youtube" "YouTube (via Google)" ON \ + "bing videos" "Bing Videos" ON \ + "duckduckgo videos" "DuckDuckGo Videos" ON \ + "google videos" "Google Videos" ON \ + "invidious" "Invidious [no filter]" "$(_sxst invidious)" \ + "piped" "Piped [no filter]" "$(_sxst piped)" \ + "peertube" "PeerTube [no filter]" "$(_sxst peertube)" \ + "sepiasearch" "SepiaSearch [no filter]" "$(_sxst sepiasearch)" \ + "dailymotion" "Dailymotion [no filter]" "$(_sxst dailymotion)" \ + "vimeo" "Vimeo [no filter]" "$(_sxst vimeo)") + + _SX_N=$(_sxmenu "News Search Engines" \ + "google news" "Google News" ON \ + "bing news" "Bing News" ON \ + "duckduckgo news" "DuckDuckGo News" ON \ + "brave news" "Brave News" ON \ + "qwant news" "Qwant News" ON) + + _SX_S=$(_sxmenu "Science / Academic" \ + "arxiv" "arXiv (preprints)" ON \ + "semantic scholar" "Semantic Scholar" ON \ + "pubmed" "PubMed (medical)" ON \ + "crossref" "Crossref (DOI/papers)" ON \ + "base" "BASE (open access)" ON) + + _SX_ALL=( + "google" "bing" "duckduckgo" "brave" "startpage" "qwant" "yahoo" "ecosia" + "mojeek" "yandex" "baidu" "naver" + "google images" "bing images" "duckduckgo images" "brave images" "qwant images" + "openverse" "unsplash" "flickr" "imgur" "deviantart" + "youtube" "bing videos" "duckduckgo videos" "google videos" + "invidious" "piped" "peertube" "sepiasearch" "dailymotion" "vimeo" + "google news" "bing news" "duckduckgo news" "brave news" "qwant news" + "arxiv" "semantic scholar" "pubmed" "crossref" "base" + ) + _SX_SEL=$(printf '%s\n' "$_SX_W" "$_SX_I" "$_SX_V" "$_SX_N" "$_SX_S") + + for _e in "${_SX_ALL[@]}"; do + if printf '%s\n' "$_SX_SEL" | grep -qxF "$_e"; then + for _ao in "${_SX_AUTOFF[@]}"; do + if [[ "$_e" == "$_ao" ]]; then + SEARXNG_ENABLE_ENGINES+="${SEARXNG_ENABLE_ENGINES:+,}${_e}" + break + fi + done + else + SEARXNG_DISABLE_ENGINES+="${SEARXNG_DISABLE_ENGINES:+,}${_e}" + fi + done +else + echo "" + echo " (whiptail not available — text entry)" + echo " Web: google bing duckduckgo brave startpage qwant yahoo" + echo " Imgs: google_images bing_images duckduckgo_images flickr imgur" + echo " Vids: youtube invidious piped peertube dailymotion vimeo" + read -rp " Engines to disable (space-separated, Enter for defaults): " _ENGS + SEARXNG_DISABLE_ENGINES="${_ENGS// /,}" +fi +echo "" + write_if_new() { local dest="$1"; local body; body=$(cat) if [[ ! -f "$dest" ]] || $FORCE; then @@ -481,18 +623,11 @@ ok "requirements.txt + mcp_requirements.txt" # ============================================================================= section "SearXNG Config" # ============================================================================= -write_if_new "$BASE/searxng/settings.yml" << SEARXNG -use_default_settings: true -general: - instance_name: "Local Search" -server: - secret_key: "$(openssl rand -hex 32)" - limiter: false -search: - safe_search: 0 - default_lang: "en" - formats: [html, json] -SEARXNG +mkdir -p "$BASE/searxng" +_SXARGS=("$SEARXNG_SAFE_LEVEL") +[[ -n "$SEARXNG_DISABLE_ENGINES" ]] && _SXARGS+=(--disable-engines "$SEARXNG_DISABLE_ENGINES") +[[ -n "$SEARXNG_ENABLE_ENGINES" ]] && _SXARGS+=(--enable-engines "$SEARXNG_ENABLE_ENGINES") +BASE="$BASE" bash "$SCRIPT_DIR/configure-searxng-safesearch.sh" "${_SXARGS[@]}" # ============================================================================= section ".env (tokens — never overwritten)" @@ -553,8 +688,11 @@ services: - ENABLE_OPENAI_API=true - ENABLE_RAG_WEB_SEARCH=true - RAG_WEB_SEARCH_ENGINE=searxng - - SEARXNG_QUERY_URL=http://searxng:8080/search?q=&format=json - - WEBUI_AUTH=false + - SEARXNG_QUERY_URL=http://searxng:8080/search?q=&format=json&safesearch=${SEARXNG_SAFE_INT} + - RAG_WEB_SEARCH_RESULT_COUNT=5 + - RAG_WEB_SEARCH_CONCURRENT_REQUESTS=10 + - ENABLE_TOOL_SERVERS=true + - WEBUI_AUTH=true depends_on: ollama: {condition: service_healthy}