Merge pull request #24 from coleam00/searxng-caddy-additions

Searxng caddy additions
This commit is contained in:
Cole Medin
2025-03-11 15:20:57 -05:00
committed by GitHub
7 changed files with 423 additions and 13 deletions

View File

@@ -1,6 +1,7 @@
# Change the name of this file to .env after updating it!
############
# [required]
# n8n credentials - you set this to whatever you want, just make it a long and secure string for both!
############
@@ -9,8 +10,14 @@ N8N_USER_MANAGEMENT_JWT_SECRET=even-more-secret
############
# [required]
# Supabase Secrets
# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION
# Read these docs for any help: https://supabase.com/docs/guides/self-hosting/docker
# For the JWT Secret and keys, see: https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys
# For the other secrets, see: https://supabase.com/docs/guides/self-hosting/docker#update-secrets
# You can really decide any value for POOLER_TENANT_ID like 1000.
############
POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password
@@ -19,9 +26,47 @@ ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKIC
SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
DASHBOARD_USERNAME=supabase
DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated
SECRET_KEY_BASE=UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
VAULT_ENC_KEY=your-encryption-key-32-chars-exactly
POOLER_TENANT_ID=your-tenant-id
############
# [required for prod]
# Caddy Config
# By default listen on https://localhost:[service port] and don't use an email for SSL
# To change this for production:
# Uncomment all of these environment variables for the services you want exposed
# Note that you might not want to expose Ollama or SearXNG since they aren't secured by default
# Replace the placeholder value with the host for each service (like n8n.yourdomain.com)
# Replace internal by your email (require to create a Let's Encrypt certificate)
############
# N8N_HOSTNAME=n8n.yourdomain.com
# WEBUI_HOSTNAME=openwebui.yourdomain.com
# FLOWISE_HOSTNAME=flowise.yourdomain.com
# SUPABASE_HOSTNAME=supabase.yourdomain.com
# OLLAMA_HOSTNAME=ollama.yourdomain.com
# SEARXNG_HOSTNAME=searxng.yourdomain.com
# LETSENCRYPT_EMAIL=internal
# Everything below this point is optional.
# Default values will suffice unless you need more features/customization.
#
#
#######
#####
#
############
# Optional SearXNG Config
# If you run a very small or a very large instance, you might want to change the amount of used uwsgi workers and threads per worker
# More workers (= processes) means that more search requests can be handled at the same time, but it also causes more resource usage
############
# SEARXNG_UWSGI_WORKERS=4
# SEARXNG_UWSGI_THREADS=4
############
# Database - You can change these to any PostgreSQL database that has logical replication enabled.
@@ -34,12 +79,13 @@ POSTGRES_PORT=5432
############
# Supavisor -- Database pooler
# Supavisor -- Database pooler and others that can be left as default values
############
POOLER_PROXY_PORT_TRANSACTION=6543
POOLER_DEFAULT_POOL_SIZE=20
POOLER_MAX_CLIENT_CONN=100
POOLER_TENANT_ID=your-tenant-id
SECRET_KEY_BASE=UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
VAULT_ENC_KEY=your-encryption-key-32-chars-exactly
############

4
.gitignore vendored
View File

@@ -2,4 +2,6 @@
.env.test
volumes/
shared/
supabase/
supabase/
searxng/uwsgi.ini
searxng/settings.yml

90
Caddyfile Normal file
View File

@@ -0,0 +1,90 @@
{
# Global options - works for both environments
email {$LETSENCRYPT_EMAIL}
}
# N8N
{$N8N_HOSTNAME} {
# For domains, Caddy will automatically use Let's Encrypt
# For localhost/port addresses, HTTPS won't be enabled
reverse_proxy localhost:5678
}
# Open WebUI
{$WEBUI_HOSTNAME} {
reverse_proxy localhost:3000
}
# Flowise
{$FLOWISE_HOSTNAME} {
reverse_proxy localhost:3001
}
# Ollama API
{$OLLAMA_HOSTNAME} {
reverse_proxy localhost:11434
}
# Supabase
{$SUPABASE_HOSTNAME} {
reverse_proxy localhost:8000
}
# SearXNG
{$SEARXNG_HOSTNAME} {
encode zstd gzip
@api {
path /config
path /healthz
path /stats/errors
path /stats/checker
}
@search {
path /search
}
@imageproxy {
path /image_proxy
}
@static {
path /static/*
}
header {
# CSP (https://content-security-policy.com)
Content-Security-Policy "upgrade-insecure-requests; default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self' https://github.com/searxng/searxng/issues/new; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' https://overpass-api.de; img-src * data:; frame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.dailymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundcloud.com https://embed.spotify.com;"
# Disable some browser features
Permissions-Policy "accelerometer=(),camera=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),payment=(),usb=()"
# Set referrer policy
Referrer-Policy "no-referrer"
# Force clients to use HTTPS
Strict-Transport-Security "max-age=31536000"
# Prevent MIME type sniffing from the declared Content-Type
X-Content-Type-Options "nosniff"
# X-Robots-Tag (comment to allow site indexing)
X-Robots-Tag "noindex, noarchive, nofollow"
# Remove "Server" header
-Server
}
header @api {
Access-Control-Allow-Methods "GET, OPTIONS"
Access-Control-Allow-Origin "*"
}
route {
# Cache policy
header Cache-Control "max-age=0, no-store"
header @search Cache-Control "max-age=5, private"
header @imageproxy Cache-Control "max-age=604800, public"
header @static Cache-Control "max-age=31536000, public, immutable"
}
# SearXNG (uWSGI)
reverse_proxy localhost:8080 {
header_up X-Forwarded-Port {http.request.port}
header_up X-Real-IP {http.request.remote.host}
# https://github.com/searx/searx-docker/issues/24
header_up Connection "close"
}
}

View File

@@ -4,7 +4,7 @@
quickly bootstraps a fully featured Local AI and Low Code development
environment including Ollama for your local LLMs, Open WebUI for an interface to chat with your N8N agents, and Supabase for your database, vector store, and authentication.
This is Cole's version with a couple of improvements and the addition of Supabase, Open WebUI, and Flowise!
This is Cole's version with a couple of improvements and the addition of Supabase, Open WebUI, Flowise, SearXNG, and Caddy!
Postgres was also removed since Supabase runs Postgres under the hood.
Also, the local RAG AI Agent workflow from the video will be automatically in your
n8n instance if you use this setup instead of the base one provided by n8n!
@@ -46,6 +46,11 @@ builder that pairs very well with n8n
store with an comprehensive API. Even though you can use Supabase for RAG, this was
kept unlike Postgres since it's faster than Supabase so sometimes is the better option.
✅ [**SearXNG**](https://searxng.org/) - Open-source, free internet metasearch engine which aggregates
results from up to 229 search services. Users are neither tracked nor profiled, hence the fit with the local AI package.
✅ [**Caddy**](https://caddyserver.com/) - Managed HTTPS/TLS for custom domains
## Prerequisites
Before you begin, make sure you have the following software installed:
@@ -82,16 +87,26 @@ Before running the services, you need to set up your environment variables for S
SERVICE_ROLE_KEY=
DASHBOARD_USERNAME=
DASHBOARD_PASSWORD=
############
# Supavisor -- Database pooler
############
POOLER_TENANT_ID=
```
> [!IMPORTANT]
> Make sure to generate secure random values for all secrets. Never use the example values in production.
> [!IMPORTANT]
> Make sure to generate secure random values for all secrets. Never use the example values in production.
3. Set the following environment variables if deploying to production, otherwise leave commented:
```bash
############
# Caddy Config
############
N8N_HOSTNAME=n8n.yourdomain.com
WEBUI_HOSTNAME=:openwebui.yourdomain.com
FLOWISE_HOSTNAME=:flowise.yourdomain.com
SUPABASE_HOSTNAME=:supabase.yourdomain.com
OLLAMA_HOSTNAME=:ollama.yourdomain.com
SEARXNG_HOSTNAME=searxng.yourdomain.com
LETSENCRYPT_EMAIL=your-email-address
```
---
@@ -153,6 +168,28 @@ Additionally, after you see "Editor is now accessible via: http://localhost:5678
python start_services.py --profile cpu
```
## Deploying to the Cloud
### Prerequisites for the below steps
- Linux machine (preferably Unbuntu) with Nano, Git, and Docker installed
### Extra steps
Before running the above commands to pull the repo and install everything:
1. Run the commands as root to open up the necessary ports:
- ufw enable
- ufw allow 8000 && ufw allow 3001 && ufw allow 3000 && ufw allow 5678 && ufw allow 80 && ufw allow 443
- ufw allow 8080 (if you want to expose SearXNG)
- ufw allow 11434 (if you want to expose Ollama)
- ufw reload
2. Set up A records for your DNS provider to point your subdomains you'll set up in the .env file for Caddy
to the IP address of your cloud instance.
For example, A record to point n8n to [cloud instance IP] for n8n.yourdomain.com
## ⚡️ Quick start and usage
The main component of the self-hosted AI starter kit is a docker compose file
@@ -266,7 +303,7 @@ and nodes. If you run into an issue, go to [support](#support).
## 🎥 Video walkthrough
- [Cole's Guide to the Local AI Starter Kit](https://youtu.be/V_0dNE-H2gw)
- [Cole's Guide to the Local AI Starter Kit](https://youtu.be/pOsO40HSbOo)
## 🛍️ More AI templates

View File

@@ -4,6 +4,9 @@ volumes:
qdrant_storage:
open-webui:
flowise:
caddy-data:
caddy-config:
valkey-data:
x-n8n: &service-n8n
image: n8nio/n8n:latest
@@ -100,6 +103,76 @@ services:
volumes:
- qdrant_storage:/qdrant/storage
caddy:
container_name: caddy
image: docker.io/library/caddy:2-alpine
network_mode: host
restart: unless-stopped
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data:rw
- caddy-config:/config:rw
environment:
- N8N_HOSTNAME=${N8N_HOSTNAME:-":8001"}
- WEBUI_HOSTNAME=${WEBUI_HOSTNAME:-":8002"}
- FLOWISE_HOSTNAME=${FLOWISE_HOSTNAME:-":8003"}
- OLLAMA_HOSTNAME=${OLLAMA_HOSTNAME:-":8004"}
- SUPABASE_HOSTNAME=${SUPABASE_HOSTNAME:-":8005"}
- SEARXNG_HOSTNAME=${SEARXNG_HOSTNAME:-":8006"}
- LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL:-internal}
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "1"
redis:
container_name: redis
image: docker.io/valkey/valkey:8-alpine
command: valkey-server --save 30 1 --loglevel warning
restart: unless-stopped
volumes:
- valkey-data:/data
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
- DAC_OVERRIDE
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "1"
searxng:
container_name: searxng
image: docker.io/searxng/searxng:latest
restart: unless-stopped
ports:
- 8080:8080
volumes:
- ./searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=https://${SEARXNG_HOSTNAME:-localhost}/
- UWSGI_WORKERS=${SEARXNG_UWSGI_WORKERS:-4}
- UWSGI_THREADS=${SEARXNG_UWSGI_THREADS:-4}
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "1"
ollama-cpu:
profiles: ["cpu"]
<<: *service-ollama

15
searxng/settings-base.yml Normal file
View File

@@ -0,0 +1,15 @@
# see https://docs.searxng.org/admin/settings/settings.html#settings-use-default-settings
use_default_settings: true
server:
# base_url is defined in the SEARXNG_BASE_URL environment variable, see .env and docker-compose.yml
secret_key: "ultrasecretkey" # change this!
limiter: false
image_proxy: true
ui:
static_use_hash: true
redis:
url: redis://redis:6379/0
search:
formats:
- html
- json

View File

@@ -12,6 +12,8 @@ import subprocess
import shutil
import time
import argparse
import platform
import sys
def run_command(cmd, cwd=None):
"""Run a shell command and print it."""
@@ -71,6 +73,146 @@ def start_local_ai(profile=None):
cmd.extend(["-f", "docker-compose.yml", "up", "-d"])
run_command(cmd)
def generate_searxng_secret_key():
"""Generate a secret key for SearXNG based on the current platform."""
print("Checking SearXNG settings...")
# Define paths for SearXNG settings files
settings_path = os.path.join("searxng", "settings.yml")
settings_base_path = os.path.join("searxng", "settings-base.yml")
# Check if settings-base.yml exists
if not os.path.exists(settings_base_path):
print(f"Warning: SearXNG base settings file not found at {settings_base_path}")
return
# Check if settings.yml exists, if not create it from settings-base.yml
if not os.path.exists(settings_path):
print(f"SearXNG settings.yml not found. Creating from {settings_base_path}...")
try:
shutil.copyfile(settings_base_path, settings_path)
print(f"Created {settings_path} from {settings_base_path}")
except Exception as e:
print(f"Error creating settings.yml: {e}")
return
else:
print(f"SearXNG settings.yml already exists at {settings_path}")
print("Generating SearXNG secret key...")
# Detect the platform and run the appropriate command
system = platform.system()
try:
if system == "Windows":
print("Detected Windows platform, using PowerShell to generate secret key...")
# PowerShell command to generate a random key and replace in the settings file
ps_command = [
"powershell", "-Command",
"$randomBytes = New-Object byte[] 32; " +
"(New-Object Security.Cryptography.RNGCryptoServiceProvider).GetBytes($randomBytes); " +
"$secretKey = -join ($randomBytes | ForEach-Object { \"{0:x2}\" -f $_ }); " +
"(Get-Content searxng/settings.yml) -replace 'ultrasecretkey', $secretKey | Set-Content searxng/settings.yml"
]
subprocess.run(ps_command, check=True)
elif system == "Darwin": # macOS
print("Detected macOS platform, using sed command with empty string parameter...")
# macOS sed command requires an empty string for the -i parameter
openssl_cmd = ["openssl", "rand", "-hex", "32"]
random_key = subprocess.check_output(openssl_cmd).decode('utf-8').strip()
sed_cmd = ["sed", "-i", "", f"s|ultrasecretkey|{random_key}|g", settings_path]
subprocess.run(sed_cmd, check=True)
else: # Linux and other Unix-like systems
print("Detected Linux/Unix platform, using standard sed command...")
# Standard sed command for Linux
openssl_cmd = ["openssl", "rand", "-hex", "32"]
random_key = subprocess.check_output(openssl_cmd).decode('utf-8').strip()
sed_cmd = ["sed", "-i", f"s|ultrasecretkey|{random_key}|g", settings_path]
subprocess.run(sed_cmd, check=True)
print("SearXNG secret key generated successfully.")
except Exception as e:
print(f"Error generating SearXNG secret key: {e}")
print("You may need to manually generate the secret key using the commands:")
print(" - Linux: sed -i \"s|ultrasecretkey|$(openssl rand -hex 32)|g\" searxng/settings.yml")
print(" - macOS: sed -i '' \"s|ultrasecretkey|$(openssl rand -hex 32)|g\" searxng/settings.yml")
print(" - Windows (PowerShell):")
print(" $randomBytes = New-Object byte[] 32")
print(" (New-Object Security.Cryptography.RNGCryptoServiceProvider).GetBytes($randomBytes)")
print(" $secretKey = -join ($randomBytes | ForEach-Object { \"{0:x2}\" -f $_ })")
print(" (Get-Content searxng/settings.yml) -replace 'ultrasecretkey', $secretKey | Set-Content searxng/settings.yml")
def check_and_fix_docker_compose_for_searxng():
"""Check and modify docker-compose.yml for SearXNG first run."""
docker_compose_path = "docker-compose.yml"
if not os.path.exists(docker_compose_path):
print(f"Warning: Docker Compose file not found at {docker_compose_path}")
return
try:
# Read the docker-compose.yml file
with open(docker_compose_path, 'r') as file:
content = file.read()
# Default to first run
is_first_run = True
# Check if Docker is running and if the SearXNG container exists
try:
# Check if the SearXNG container is running
container_check = subprocess.run(
["docker", "ps", "--filter", "name=searxng", "--format", "{{.Names}}"],
capture_output=True, text=True, check=True
)
searxng_containers = container_check.stdout.strip().split('\n')
# If SearXNG container is running, check inside for uwsgi.ini
if any(container for container in searxng_containers if container):
container_name = next(container for container in searxng_containers if container)
print(f"Found running SearXNG container: {container_name}")
# Check if uwsgi.ini exists inside the container
container_check = subprocess.run(
["docker", "exec", container_name, "sh", "-c", "[ -f /etc/searxng/uwsgi.ini ] && echo 'found' || echo 'not_found'"],
capture_output=True, text=True, check=True
)
if "found" in container_check.stdout:
print("Found uwsgi.ini inside the SearXNG container - not first run")
is_first_run = False
else:
print("uwsgi.ini not found inside the SearXNG container - first run")
is_first_run = True
else:
print("No running SearXNG container found - assuming first run")
except Exception as e:
print(f"Error checking Docker container: {e} - assuming first run")
if is_first_run and "cap_drop: - ALL" in content:
print("First run detected for SearXNG. Temporarily removing 'cap_drop: - ALL' directive...")
# Temporarily comment out the cap_drop line
modified_content = content.replace("cap_drop: - ALL", "# cap_drop: - ALL # Temporarily commented out for first run")
# Write the modified content back
with open(docker_compose_path, 'w') as file:
file.write(modified_content)
print("Note: After the first run completes successfully, you should re-add 'cap_drop: - ALL' to docker-compose.yml for security reasons.")
elif not is_first_run and "# cap_drop: - ALL # Temporarily commented out for first run" in content:
print("SearXNG has been initialized. Re-enabling 'cap_drop: - ALL' directive for security...")
# Uncomment the cap_drop line
modified_content = content.replace("# cap_drop: - ALL # Temporarily commented out for first run", "cap_drop: - ALL")
# Write the modified content back
with open(docker_compose_path, 'w') as file:
file.write(modified_content)
except Exception as e:
print(f"Error checking/modifying docker-compose.yml for SearXNG: {e}")
def main():
parser = argparse.ArgumentParser(description='Start the local AI and Supabase services.')
parser.add_argument('--profile', choices=['cpu', 'gpu-nvidia', 'gpu-amd', 'none'], default='cpu',
@@ -79,6 +221,11 @@ def main():
clone_supabase_repo()
prepare_supabase_env()
# Generate SearXNG secret key and check docker-compose.yml
generate_searxng_secret_key()
check_and_fix_docker_compose_for_searxng()
stop_existing_containers()
# Start Supabase first