feat(opensearch): Add BASE_URL config to support reverse proxy deployments

This commit is contained in:
Willie Zutz
2025-05-08 20:58:04 -06:00
parent d839769d7e
commit 85605fe166
5 changed files with 81 additions and 7 deletions

View File

@ -140,6 +140,37 @@ For more details, check out the full documentation [here](https://github.com/Itz
Perplexica runs on Next.js and handles all API requests. It works right away on the same network and stays accessible even with port forwarding. Perplexica runs on Next.js and handles all API requests. It works right away on the same network and stays accessible even with port forwarding.
### Running Behind a Reverse Proxy
When running Perplexica behind a reverse proxy (like Nginx, Apache, or Traefik), follow these steps to ensure proper functionality:
1. **Configure the BASE_URL setting**:
- In `config.toml`, set the `BASE_URL` parameter under the `[GENERAL]` section to your public-facing URL (e.g., `https://perplexica.yourdomain.com`)
2. **Ensure proper headers forwarding**:
- Your reverse proxy should forward the following headers:
- `X-Forwarded-Host`
- `X-Forwarded-Proto`
- `X-Forwarded-Port` (if using non-standard ports)
3. **Example Nginx configuration**:
```nginx
server {
listen 80;
server_name perplexica.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
```
This ensures that OpenSearch descriptions, browser integrations, and all URLs work properly when accessing Perplexica through your reverse proxy.
## One-Click Deployment ## One-Click Deployment
[![Deploy to Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica) [![Deploy to Sealos](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)

View File

@ -1,6 +1,7 @@
[GENERAL] [GENERAL]
SIMILARITY_MEASURE = "cosine" # "cosine" or "dot" SIMILARITY_MEASURE = "cosine" # "cosine" or "dot"
KEEP_ALIVE = "5m" # How long to keep Ollama models loaded into memory. (Instead of using -1 use "-1m") KEEP_ALIVE = "5m" # How long to keep Ollama models loaded into memory. (Instead of using -1 use "-1m")
BASE_URL = "" # Optional. When set, overrides detected URL for OpenSearch and other public URLs
[MODELS.OPENAI] [MODELS.OPENAI]
API_KEY = "" API_KEY = ""

View File

@ -1,5 +1,6 @@
import { import {
getAnthropicApiKey, getAnthropicApiKey,
getBaseUrl,
getCustomOpenaiApiKey, getCustomOpenaiApiKey,
getCustomOpenaiApiUrl, getCustomOpenaiApiUrl,
getCustomOpenaiModelName, getCustomOpenaiModelName,
@ -60,6 +61,7 @@ export const GET = async (req: Request) => {
config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl(); config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl();
config['customOpenaiApiKey'] = getCustomOpenaiApiKey(); config['customOpenaiApiKey'] = getCustomOpenaiApiKey();
config['customOpenaiModelName'] = getCustomOpenaiModelName(); config['customOpenaiModelName'] = getCustomOpenaiModelName();
config['baseUrl'] = getBaseUrl();
return Response.json({ ...config }, { status: 200 }); return Response.json({ ...config }, { status: 200 });
} catch (err) { } catch (err) {

View File

@ -1,11 +1,10 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getBaseUrl } from '@/lib/config';
export async function GET(request: Request) { /**
// Get the host from the request * Creates an OpenSearch XML response with the given origin URL
const url = new URL(request.url); */
const origin = url.origin; function generateOpenSearchResponse(origin: string): NextResponse {
// Create the OpenSearch XML with the correct origin
const opensearchXml = `<?xml version="1.0" encoding="utf-8"?> const opensearchXml = `<?xml version="1.0" encoding="utf-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/"> <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
<ShortName>Perplexica</ShortName> <ShortName>Perplexica</ShortName>
@ -17,10 +16,48 @@ export async function GET(request: Request) {
<Url type="application/opensearchdescription+xml" rel="self" template="${origin}/api/opensearch"/> <Url type="application/opensearchdescription+xml" rel="self" template="${origin}/api/opensearch"/>
</OpenSearchDescription>`; </OpenSearchDescription>`;
// Return the XML with the correct content type
return new NextResponse(opensearchXml, { return new NextResponse(opensearchXml, {
headers: { headers: {
'Content-Type': 'application/opensearchdescription+xml', 'Content-Type': 'application/opensearchdescription+xml',
}, },
}); });
} }
export async function GET(request: Request) {
// Check if a BASE_URL is explicitly configured
const configBaseUrl = getBaseUrl();
// If BASE_URL is configured, use it, otherwise detect from request
if (configBaseUrl) {
// Remove any trailing slashes for consistency
let origin = configBaseUrl.replace(/\/+$/, '');
return generateOpenSearchResponse(origin);
}
// Detect the correct origin, taking into account reverse proxy headers
const url = new URL(request.url);
let origin = url.origin;
// Extract headers
const headers = Object.fromEntries(request.headers);
// Check for X-Forwarded-Host and related headers to handle reverse proxies
if (headers['x-forwarded-host']) {
// Determine protocol: prefer X-Forwarded-Proto, fall back to original or https
const protocol = headers['x-forwarded-proto'] || url.protocol.replace(':', '');
// Build the correct public-facing origin
origin = `${protocol}://${headers['x-forwarded-host']}`;
// Handle non-standard ports if specified in X-Forwarded-Port
if (headers['x-forwarded-port']) {
const port = headers['x-forwarded-port'];
// Don't append standard ports (80 for HTTP, 443 for HTTPS)
if (!((protocol === 'http' && port === '80') || (protocol === 'https' && port === '443'))) {
origin = `${origin}:${port}`;
}
}
}
// Generate and return the OpenSearch response
return generateOpenSearchResponse(origin);
}

View File

@ -15,6 +15,7 @@ interface Config {
GENERAL: { GENERAL: {
SIMILARITY_MEASURE: string; SIMILARITY_MEASURE: string;
KEEP_ALIVE: string; KEEP_ALIVE: string;
BASE_URL?: string;
}; };
MODELS: { MODELS: {
OPENAI: { OPENAI: {
@ -70,6 +71,8 @@ export const getSimilarityMeasure = () =>
export const getKeepAlive = () => loadConfig().GENERAL.KEEP_ALIVE; export const getKeepAlive = () => loadConfig().GENERAL.KEEP_ALIVE;
export const getBaseUrl = () => loadConfig().GENERAL.BASE_URL;
export const getOpenaiApiKey = () => loadConfig().MODELS.OPENAI.API_KEY; export const getOpenaiApiKey = () => loadConfig().MODELS.OPENAI.API_KEY;
export const getGroqApiKey = () => loadConfig().MODELS.GROQ.API_KEY; export const getGroqApiKey = () => loadConfig().MODELS.GROQ.API_KEY;