<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Self-hosting on Searpa Docs</title><link>https://docs.searpa.eu/self-hosting/</link><description>Recent content in Self-hosting on Searpa Docs</description><generator>Hugo</generator><language>en</language><atom:link href="https://docs.searpa.eu/self-hosting/index.xml" rel="self" type="application/rss+xml"/><item><title>Requirements</title><link>https://docs.searpa.eu/self-hosting/requirements/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/requirements/</guid><description>&lt;h1 id="requirements"&gt;Requirements&lt;a class="anchor" href="#requirements"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="runtime"&gt;Runtime&lt;a class="anchor" href="#runtime"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Component&lt;/th&gt;
					&lt;th&gt;Notes&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;A container runtime&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Docker or Podman, to run the published image.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;The application database. Any reasonably recent PostgreSQL (16+) works.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;A Brave Search API key&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;The minimum for useful results, see &lt;a href="https://docs.searpa.eu/self-hosting/search-providers/"&gt;Search providers&lt;/a&gt;. The free tier is enough to start.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Outbound HTTPS&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;The server calls upstream providers; if you run behind an allowlist, see the &lt;a href="https://docs.searpa.eu/self-hosting/search-providers/#network-allowlist"&gt;provider hosts&lt;/a&gt;.&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything else, the web server, static-file serving and translation-catalog
compilation, is built into the image.&lt;/p&gt;</description></item><item><title>Quick start</title><link>https://docs.searpa.eu/self-hosting/quick-start/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/quick-start/</guid><description>&lt;h1 id="quick-start"&gt;Quick start&lt;a class="anchor" href="#quick-start"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The fastest way to a running instance is the Docker image. You need a
PostgreSQL database and, at minimum, a &lt;a href="https://docs.searpa.eu/self-hosting/search-providers/"&gt;Brave Search API
key&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="with-docker"&gt;With Docker&lt;a class="anchor" href="#with-docker"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -p 8000:8000 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-long-random-secret &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://user:password@db-host:5432/searpa &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;search.example.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;BRAVE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-brave-key &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;BRAVE_SUGGEST_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-brave-suggest-key &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="nv"&gt;MARGINALIA_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;public &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; searpa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On start the container:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Applies database &lt;strong&gt;migrations&lt;/strong&gt; (a no-op once the schema is current).&lt;/li&gt;
&lt;li&gt;Serves the app with &lt;strong&gt;gunicorn&lt;/strong&gt; (2 workers) on port &lt;strong&gt;8000&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Serves its own static files via
&lt;a href="https://whitenoise.readthedocs.io/"&gt;WhiteNoise&lt;/a&gt; (compressed and hashed), no
separate web server or CDN required.&lt;/li&gt;
&lt;li&gt;Answers a health check at &lt;strong&gt;&lt;code&gt;/up&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The translation catalogs and static files are compiled at build time, so there
is nothing else to run.&lt;/p&gt;</description></item><item><title>Configuration</title><link>https://docs.searpa.eu/self-hosting/configuration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/configuration/</guid><description>&lt;h1 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Searpa is configured entirely through &lt;strong&gt;environment variables&lt;/strong&gt; (in production)
or a &lt;code&gt;.env&lt;/code&gt; file (in development). The repository ships an &lt;code&gt;.env.example&lt;/code&gt;
documenting every variable; this page is the reference.&lt;/p&gt;
&lt;h2 id="core"&gt;Core&lt;a class="anchor" href="#core"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Variable&lt;/th&gt;
					&lt;th&gt;Purpose&lt;/th&gt;
					&lt;th&gt;Notes&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;SECRET_KEY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Django secret key&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;Required in production.&lt;/strong&gt; Long and random.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DEBUG&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Debug mode&lt;/td&gt;
					&lt;td&gt;Keep &lt;strong&gt;&lt;code&gt;False&lt;/code&gt;&lt;/strong&gt; (the default) in production.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ALLOWED_HOSTS&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Comma-separated hostnames the app will serve&lt;/td&gt;
					&lt;td&gt;e.g. &lt;code&gt;search.example.com&lt;/code&gt;.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DATABASE_URL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;PostgreSQL connection URL&lt;/td&gt;
					&lt;td&gt;e.g. &lt;code&gt;postgres://user:pass@host:5432/searpa&lt;/code&gt;.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;LOG_LEVEL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Logging verbosity&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DEBUG&lt;/code&gt; / &lt;code&gt;INFO&lt;/code&gt; / &lt;code&gt;WARNING&lt;/code&gt; / &lt;code&gt;ERROR&lt;/code&gt; (default &lt;code&gt;INFO&lt;/code&gt;).&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="search-providers"&gt;Search providers&lt;a class="anchor" href="#search-providers"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These keys decide which engines, media tabs and knowledge cards are available.
A provider with no key is simply hidden (not shown as broken). Full details on
&lt;a href="https://docs.searpa.eu/self-hosting/search-providers/"&gt;Search providers&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Search providers</title><link>https://docs.searpa.eu/self-hosting/search-providers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/search-providers/</guid><description>&lt;h1 id="search-providers"&gt;Search providers&lt;a class="anchor" href="#search-providers"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Searpa blends results from several independent providers. &lt;strong&gt;Which tabs and cards
appear depends on which keys you set.&lt;/strong&gt; A provider with no key is hidden, the
affected tab or card simply doesn&amp;rsquo;t show, rather than displaying an error.&lt;/p&gt;
&lt;p&gt;All upstream calls happen &lt;strong&gt;server-side&lt;/strong&gt;; keys are never exposed to the browser.&lt;/p&gt;
&lt;p&gt;This page is the &lt;strong&gt;overview&lt;/strong&gt; of which key enables what. For step-by-step signup
instructions for each provider, see
&lt;a href="https://docs.searpa.eu/self-hosting/provider-keys/"&gt;Getting API keys&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Getting API keys</title><link>https://docs.searpa.eu/self-hosting/provider-keys/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/provider-keys/</guid><description>&lt;h1 id="getting-api-keys"&gt;Getting API keys&lt;a class="anchor" href="#getting-api-keys"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Step-by-step guides for obtaining each provider&amp;rsquo;s key. None are required to boot
Searpa, but at least one &lt;strong&gt;web engine&lt;/strong&gt; (Brave is the easiest) is needed for
useful results. Each provider you skip simply hides its tab or card, see
&lt;a href="https://docs.searpa.eu/self-hosting/search-providers/"&gt;Search providers&lt;/a&gt; for what each one enables.&lt;/p&gt;
&lt;p&gt;Once you have a key, set the matching environment variable (see
&lt;a href="https://docs.searpa.eu/self-hosting/configuration/"&gt;Configuration&lt;/a&gt;) and restart the app.&lt;/p&gt;
&lt;blockquote class='book-hint note'&gt;
&lt;p&gt;All of these have a &lt;strong&gt;free tier&lt;/strong&gt; that is generous enough for a personal or
small-team instance, Searpa caches aggressively and only calls the paid card
APIs when a query actually matches. The shared keys (&lt;code&gt;public&lt;/code&gt; for Marginalia,
anonymous Stack Exchange) need no signup at all.&lt;/p&gt;</description></item><item><title>Translation</title><link>https://docs.searpa.eu/self-hosting/translation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/translation/</guid><description>&lt;h1 id="translation-libretranslate"&gt;Translation (LibreTranslate)&lt;a class="anchor" href="#translation-libretranslate"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The &lt;strong&gt;Translate&lt;/strong&gt; tab is powered by &lt;a href="https://libretranslate.com/"&gt;LibreTranslate&lt;/a&gt;,
an open-source, self-hostable machine-translation server. Searpa calls it
server-side, no third-party translation service is involved.&lt;/p&gt;
&lt;h2 id="enabling-the-tab"&gt;Enabling the tab&lt;a class="anchor" href="#enabling-the-tab"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Point Searpa at a LibreTranslate instance with &lt;code&gt;LIBRETRANSLATE_URL&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LIBRETRANSLATE_URL=http://libretranslate:5000&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt; it and the Translate tab (and its setting) appear, with whatever
languages that instance offers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leave it unset&lt;/strong&gt; and the Translate tab and its setting are disabled
entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="running-libretranslate"&gt;Running LibreTranslate&lt;a class="anchor" href="#running-libretranslate"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The development stack starts a local LibreTranslate automatically (&lt;code&gt;make up&lt;/code&gt;
brings one up at &lt;code&gt;http://localhost:5000&lt;/code&gt;). For production, run your own, for
example:&lt;/p&gt;</description></item><item><title>Users &amp; access</title><link>https://docs.searpa.eu/self-hosting/users/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/users/</guid><description>&lt;h1 id="users--access"&gt;Users &amp;amp; access&lt;a class="anchor" href="#users--access"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Searpa is &lt;strong&gt;login-only&lt;/strong&gt; with &lt;strong&gt;no public registration&lt;/strong&gt;, so as the
administrator you control who has an account. This is what keeps an instance
private to your users.&lt;/p&gt;
&lt;h2 id="creating-accounts"&gt;Creating accounts&lt;a class="anchor" href="#creating-accounts"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create users with Django&amp;rsquo;s standard management command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; -it &amp;lt;container&amp;gt; python manage.py createsuperuser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# From source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;make superuser&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;createsuperuser&lt;/code&gt; makes an admin account; for ordinary users you can create them
the same way (or from the Django admin once you have a superuser). Each account
needs a username and password; an email is optional (see below).&lt;/p&gt;</description></item><item><title>API keys</title><link>https://docs.searpa.eu/self-hosting/api-keys/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/api-keys/</guid><description>&lt;h1 id="api-keys"&gt;API keys&lt;a class="anchor" href="#api-keys"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Users access the &lt;a href="https://docs.searpa.eu/api/"&gt;public API&lt;/a&gt; with per-user API keys.
Users can manage their own keys in the web UI (&lt;strong&gt;Settings → API keys&lt;/strong&gt;), and you
can manage them from the command line.&lt;/p&gt;
&lt;h2 id="how-keys-are-stored"&gt;How keys are stored&lt;a class="anchor" href="#how-keys-are-stored"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A key looks like &lt;code&gt;searpa_sk_&amp;lt;prefix&amp;gt;.&amp;lt;secret&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;prefix&lt;/strong&gt; (8 characters) is stored in the clear for fast lookup.&lt;/li&gt;
&lt;li&gt;Only a &lt;strong&gt;SHA-256 hash&lt;/strong&gt; of the secret is stored, never the secret itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So a key&amp;rsquo;s full value is shown &lt;strong&gt;exactly once&lt;/strong&gt;, at creation, and cannot be
recovered afterwards, only revoked.&lt;/p&gt;</description></item><item><title>Maintenance</title><link>https://docs.searpa.eu/self-hosting/maintenance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/maintenance/</guid><description>&lt;h1 id="maintenance"&gt;Maintenance&lt;a class="anchor" href="#maintenance"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Searpa needs little ongoing care, but a few &lt;strong&gt;management commands&lt;/strong&gt; keep it tidy
and current. Run them on a schedule (a cron job, a systemd timer, or your
orchestrator&amp;rsquo;s scheduled-task feature). Run each inside the container
(&lt;code&gt;docker exec &amp;lt;container&amp;gt; python manage.py …&lt;/code&gt;) or via &lt;code&gt;make&lt;/code&gt; / &lt;code&gt;uv run&lt;/code&gt; from
source.&lt;/p&gt;
&lt;h2 id="scheduled-tasks"&gt;Scheduled tasks&lt;a class="anchor" href="#scheduled-tasks"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Command&lt;/th&gt;
					&lt;th&gt;What it does&lt;/th&gt;
					&lt;th&gt;Suggested schedule&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;clear_expired_cache&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Deletes expired search-result cache rows&lt;/td&gt;
					&lt;td&gt;Daily&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;check_provider_health&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Probes providers with a free health endpoint so &lt;code&gt;/status&lt;/code&gt; stays current, without spending paid quota&lt;/td&gt;
					&lt;td&gt;Every ~10 minutes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;refresh_currency&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Refreshes cached exchange rates and prunes the instant-answer cache&lt;/td&gt;
					&lt;td&gt;Daily (optional, also self-refreshes on demand)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A cron example:&lt;/p&gt;</description></item><item><title>Production notes</title><link>https://docs.searpa.eu/self-hosting/production/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.searpa.eu/self-hosting/production/</guid><description>&lt;h1 id="production-notes"&gt;Production notes&lt;a class="anchor" href="#production-notes"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A few things worth knowing when you run Searpa for real users.&lt;/p&gt;
&lt;h2 id="the-image"&gt;The image&lt;a class="anchor" href="#the-image"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Serves the app with &lt;strong&gt;gunicorn&lt;/strong&gt; (2 workers) on port &lt;strong&gt;8000&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Serves its own static files via
&lt;a href="https://whitenoise.readthedocs.io/"&gt;WhiteNoise&lt;/a&gt;, compressed and hashed, so
you don&amp;rsquo;t need a separate static-file server or CDN.&lt;/li&gt;
&lt;li&gt;Compiles translation catalogs and collects static files &lt;strong&gt;at build time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Runs database &lt;strong&gt;migrations on start&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="reverse-proxy-and-tls"&gt;Reverse proxy and TLS&lt;a class="anchor" href="#reverse-proxy-and-tls"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Put a TLS-terminating reverse proxy in front (Caddy, nginx, Traefik, …) and
forward to port 8000. Set:&lt;/p&gt;</description></item></channel></rss>