searxng: add Nord theme and deploy script

Override SearXNG's native CSS variables with Nord palette (cream white
light mode, true black dark mode). Replace SearXNG logo with Bocken
logo. Custom base.html template injects the CSS. Deploy script supports
reset to restore original state.
This commit is contained in:
2026-03-08 20:33:25 +01:00
parent df50c6737a
commit df34ed0628
3 changed files with 421 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# Deploy SearXNG custom theme to searx.bocken.org
# CSS is hosted on bocken.org, template override on the SearXNG server
#
# Usage:
# ./deploy-searxng.sh Deploy custom theme
# ./deploy-searxng.sh reset Restore original SearXNG base.html and remove custom CSS
CSS_SRC=static/other/searxng.css
CSS_DEST=/var/www/static/css/searxng.css
TMPL_SRC=static/other/searxng_base.html
TMPL_DEST=/var/lib/searxng/venv/lib/python3.14/site-packages/searx/templates/simple/base.html
TMPL_BACKUP="${TMPL_DEST}.orig"
if [ "$1" = "reset" ]; then
echo "Resetting SearXNG to original theme..."
ssh root@bocken.org "
if [ -f '$TMPL_BACKUP' ]; then
mv '$TMPL_BACKUP' '$TMPL_DEST'
chown searxng:searxng '$TMPL_DEST'
else
echo 'No backup found at $TMPL_BACKUP — nothing to restore'
exit 1
fi
rm -f '$CSS_DEST'
systemctl restart uwsgi@emperor
"
echo "Done. Original theme restored."
exit 0
fi
# Back up original base.html if no backup exists yet
ssh root@bocken.org "
if [ ! -f '$TMPL_BACKUP' ]; then
cp '$TMPL_DEST' '$TMPL_BACKUP'
echo 'Backed up original base.html'
fi
"
# Deploy CSS to bocken.org static hosting
ssh root@bocken.org "mkdir -p /var/www/static/css"
rsync -av "$CSS_SRC" "root@bocken.org:$CSS_DEST"
# Deploy custom base.html template to SearXNG server
rsync -av "$TMPL_SRC" "root@bocken.org:$TMPL_DEST"
ssh root@bocken.org "chown searxng:searxng '$TMPL_DEST'"
# Restart SearXNG to pick up template changes
ssh root@bocken.org "systemctl restart uwsgi@emperor"
echo "Done. Check https://searx.bocken.org"
echo "To restore original: ./deploy-searxng.sh reset"
+276
View File
@@ -0,0 +1,276 @@
/*
SearXNG custom theme for searx.bocken.org
Overrides SearXNG's native CSS variables with Nord palette
Deployed via deploy-searxng.sh
*/
/* ============================================
LIGHT MODE — cream white base
============================================ */
:root {
--color-base-font: #2a2a2a;
--color-base-font-rgb: 42, 42, 42;
--color-base-background: #f8f6f1;
--color-base-background-mobile: #efecea;
--color-url-font: #5E81AC;
--color-url-visited-font: #B48EAD;
--color-header-background: #f8f6f1;
--color-header-border: #dfdcd8;
--color-footer-background: #f8f6f1;
--color-footer-border: #dfdcd8;
--color-sidebar-border: #dfdcd8;
--color-sidebar-font: #2a2a2a;
--color-sidebar-background: #efecea;
--color-backtotop-font: #555;
--color-backtotop-border: #dfdcd8;
--color-backtotop-background: #efecea;
--color-btn-background: #5E81AC;
--color-btn-font: #fff;
--color-show-btn-background: #dfdcd8;
--color-show-btn-font: #2a2a2a;
--color-search-border: #dfdcd8;
--color-search-shadow: 0 2px 8px rgba(0,0,0,0.08);
--color-search-background: #efecea;
--color-search-font: #2a2a2a;
--color-search-background-hover: #5E81AC;
--color-error: #BF616A;
--color-error-background: #f5e1e3;
--color-warning: #EBCB8B;
--color-warning-background: #faf5e1;
--color-success: #A3BE8C;
--color-success-background: #e8f2e1;
--color-categories-item-selected-font: #5E81AC;
--color-categories-item-border-selected: #5E81AC;
--color-autocomplete-font: #2a2a2a;
--color-autocomplete-border: #dfdcd8;
--color-autocomplete-shadow: 0 2px 8px rgba(0,0,0,0.08);
--color-autocomplete-background: #efecea;
--color-autocomplete-background-hover: #e8e5e1;
--color-answer-font: #2a2a2a;
--color-answer-background: #efecea;
--color-result-keyvalue-col-table: #f8f6f1;
--color-result-keyvalue-odd: #f8f6f1;
--color-result-keyvalue-even: #efecea;
--color-result-background: #efecea;
--color-result-border: #dfdcd8;
--color-result-url-font: #555;
--color-result-vim-selected: #e8e5e1cc;
--color-result-vim-arrow: #5E81AC;
--color-result-description-highlight-font: #2a2a2a;
--color-result-link-font: #5E81AC;
--color-result-link-font-highlight: #5E81AC;
--color-result-link-visited-font: #B48EAD;
--color-result-publishdate-font: #777;
--color-result-engines-font: #777;
--color-result-search-url-border: #dfdcd8;
--color-result-search-url-font: #555;
--color-result-image-span-font: #555;
--color-result-image-span-font-selected: #fff;
--color-result-image-background: #efecea;
--color-settings-tr-hover: #e8e5e1;
--color-settings-engine-description-font: #777;
--color-settings-table-group-background: rgba(0,0,0,0.03);
--color-result-detail-font: #fff;
--color-result-detail-label-font: #D8DEE9;
--color-result-detail-background: #2E3440;
--color-result-detail-hr: #4C566A;
--color-result-detail-link: #88C0D0;
--color-result-detail-loader-border: rgba(255,255,255,0.2);
--color-result-detail-loader-borderleft: transparent;
--color-toolkit-badge-font: #fff;
--color-toolkit-badge-background: #4C566A;
--color-toolkit-kbd-font: #fff;
--color-toolkit-kbd-background: #2E3440;
--color-toolkit-dialog-border: #dfdcd8;
--color-toolkit-dialog-background: #f8f6f1;
--color-toolkit-tabs-label-border: #f8f6f1;
--color-toolkit-tabs-section-border: #dfdcd8;
--color-toolkit-select-background: #e8e5e1;
--color-toolkit-select-border: #dfdcd8;
--color-toolkit-select-background-hover: #dfdcd8;
--color-toolkit-input-text-font: #2a2a2a;
--color-toolkit-checkbox-onoff-off-background: #dfdcd8;
--color-toolkit-checkbox-onoff-on-background: #dfdcd8;
--color-toolkit-checkbox-onoff-on-mark-background: #5E81AC;
--color-toolkit-checkbox-onoff-on-mark-color: #fff;
--color-toolkit-checkbox-onoff-off-mark-background: #aaa;
--color-toolkit-checkbox-onoff-off-mark-color: #fff;
--color-toolkit-checkbox-label-background: #dfdcd8;
--color-toolkit-checkbox-label-border: #dfdcd8;
--color-toolkit-checkbox-input-border: #5E81AC;
--color-toolkit-engine-tooltip-border: #dfdcd8;
--color-toolkit-engine-tooltip-background: #f8f6f1;
--color-toolkit-loader-border: rgba(0,0,0,0.1);
--color-toolkit-loader-borderleft: transparent;
--color-doc-code: #2E3440;
--color-doc-code-background: #e8e5e1;
--color-bar-chart-primary: #5E81AC;
--color-bar-chart-secondary: #D08770;
--color-image-resolution-background: rgba(0,0,0,0.5);
--color-image-resolution-font: #fff;
--color-loading-indicator: rgba(255,255,255,0.2);
--color-loading-indicator-gap: #f8f6f1;
--color-line-number: #4C566A;
--color-favicon-background-color: #e8e5e1;
--color-favicon-border-color: #dfdcd8;
}
/* ============================================
DARK MODE — true black base
============================================ */
@media (prefers-color-scheme: dark) {
:root.theme-auto {
--color-base-font: #e5e5e5;
--color-base-font-rgb: 229, 229, 229;
--color-base-background: #000;
--color-base-background-mobile: #000;
--color-url-font: #88C0D0;
--color-url-visited-font: #c89fb6;
--color-header-background: #000;
--color-header-border: #222;
--color-footer-background: #000;
--color-footer-border: #222;
--color-sidebar-border: #333;
--color-sidebar-font: #e5e5e5;
--color-sidebar-background: #1a1a1a;
--color-backtotop-font: #aaa;
--color-backtotop-border: #333;
--color-backtotop-background: #1a1a1a;
--color-btn-background: #88C0D0;
--color-btn-font: #000;
--color-show-btn-background: #333;
--color-show-btn-font: #e5e5e5;
--color-search-border: #333;
--color-search-shadow: 0 2px 8px rgba(0,0,0,0.5);
--color-search-background: #1a1a1a;
--color-search-font: #e5e5e5;
--color-search-background-hover: #88C0D0;
--color-error: #BF616A;
--color-error-background: #2a0f0f;
--color-warning: #EBCB8B;
--color-warning-background: #2a2008;
--color-success: #A3BE8C;
--color-success-background: #0e2a0a;
--color-categories-item-selected-font: #88C0D0;
--color-categories-item-border-selected: #88C0D0;
--color-autocomplete-font: #e5e5e5;
--color-autocomplete-border: #333;
--color-autocomplete-shadow: 0 2px 8px rgba(0,0,0,0.5);
--color-autocomplete-background: #1a1a1a;
--color-autocomplete-background-hover: #111;
--color-answer-font: #e5e5e5;
--color-answer-background: #1a1a1a;
--color-result-keyvalue-col-table: #111;
--color-result-keyvalue-odd: #111;
--color-result-keyvalue-even: #1a1a1a;
--color-result-background: #1a1a1a;
--color-result-border: #222;
--color-result-url-font: #888;
--color-result-vim-selected: #111c;
--color-result-vim-arrow: #88C0D0;
--color-result-description-highlight-font: #fff;
--color-result-link-font: #88C0D0;
--color-result-link-font-highlight: #88C0D0;
--color-result-link-visited-font: #c89fb6;
--color-result-publishdate-font: #888;
--color-result-engines-font: #888;
--color-result-search-url-border: #333;
--color-result-search-url-font: #aaa;
--color-result-image-span-font: #aaa;
--color-result-image-span-font-selected: #fff;
--color-result-image-background: #1a1a1a;
--color-settings-tr-hover: #222;
--color-settings-engine-description-font: #888;
--color-settings-table-group-background: rgba(255,255,255,0.03);
--color-result-detail-font: #e5e5e5;
--color-result-detail-label-font: #aaa;
--color-result-detail-background: #111;
--color-result-detail-hr: #333;
--color-result-detail-link: #88C0D0;
--color-result-detail-loader-border: rgba(255,255,255,0.2);
--color-result-detail-loader-borderleft: transparent;
--color-toolkit-badge-font: #e5e5e5;
--color-toolkit-badge-background: #4C566A;
--color-toolkit-kbd-font: #e5e5e5;
--color-toolkit-kbd-background: #000;
--color-toolkit-dialog-border: #333;
--color-toolkit-dialog-background: #111;
--color-toolkit-tabs-label-border: #111;
--color-toolkit-tabs-section-border: #333;
--color-toolkit-select-background: #222;
--color-toolkit-select-border: #333;
--color-toolkit-select-background-hover: #333;
--color-toolkit-input-text-font: #e5e5e5;
--color-toolkit-checkbox-onoff-off-background: #333;
--color-toolkit-checkbox-onoff-on-background: #333;
--color-toolkit-checkbox-onoff-on-mark-background: #88C0D0;
--color-toolkit-checkbox-onoff-on-mark-color: #000;
--color-toolkit-checkbox-onoff-off-mark-background: #555;
--color-toolkit-checkbox-onoff-off-mark-color: #e5e5e5;
--color-toolkit-checkbox-label-background: #333;
--color-toolkit-checkbox-label-border: #333;
--color-toolkit-checkbox-input-border: #88C0D0;
--color-toolkit-engine-tooltip-border: #333;
--color-toolkit-engine-tooltip-background: #111;
--color-toolkit-loader-border: rgba(255,255,255,0.1);
--color-toolkit-loader-borderleft: transparent;
--color-doc-code: #e5e5e5;
--color-doc-code-background: #1a1a1a;
--color-bar-chart-primary: #88C0D0;
--color-bar-chart-secondary: #D08770;
--color-image-resolution-background: rgba(0,0,0,0.7);
--color-image-resolution-font: #e5e5e5;
--color-loading-indicator: rgba(255,255,255,0.2);
--color-loading-indicator-gap: #000;
--color-line-number: #4C566A;
--color-favicon-background-color: #222;
--color-favicon-border-color: #333;
}
}
/* ============================================
FONT
============================================ */
* {
font-family: Helvetica, Arial, "Noto Sans", sans-serif !important;
}
/* ============================================
INDEX PAGE — LOGO
Replace SearXNG logo with Bocken logo
============================================ */
.index .title {
background-image: url("https://bocken.org/static/css/logos/logo_text_smart.svg") !important;
}
/* Results page logo */
#search_logo svg,
#search_logo img {
display: none !important;
}
#search_logo span {
display: none !important;
}
#search_logo::after {
content: "";
display: block;
width: 100px;
height: 30px;
background: url("https://bocken.org/static/css/logos/logo_full_light.svg") no-repeat center / contain;
}
@media (prefers-color-scheme: light) {
#search_logo::after {
background-image: url("https://bocken.org/static/css/logos/logo_full_dark.svg");
}
}
/* ============================================
SCROLLBAR
============================================ */
* {
scrollbar-width: thin;
scrollbar-color: var(--color-header-border) var(--color-base-background);
}
+92
View File
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html class="no-js theme-{{ preferences.get_value('simple_style') or 'auto' }} center-alignment-{{ preferences.get_value('center_alignment') and 'yes' or 'no' }}" lang="{{ locale_rfc5646 }}" {% if rtl %} dir="rtl"{% endif %}>
<head>
<meta charset="UTF-8">
<meta name="endpoint" content="{{ endpoint }}">
<meta name="description" content="SearXNG — a privacy-respecting, open metasearch engine">
<meta name="keywords" content="SearXNG, search, search engine, metasearch, meta search">
<meta name="generator" content="searxng/{{ searx_version }}">
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noarchive">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}{{ instance_name }}</title>
<script type="module" src="{{ url_for('static', filename='sxng-core.min.js') }}" client_settings="{{ client_settings
}}"></script>
{% block meta %}{% endblock %}
{% if rtl %}
<link rel="stylesheet" href="{{ url_for('static', filename='sxng-rtl.min.css') }}" type="text/css" media="screen">
{% else %}
<link rel="stylesheet" href="{{ url_for('static', filename='sxng-ltr.min.css') }}" type="text/css" media="screen">
{% endif %}
{% if get_setting('server.limiter') or get_setting('server.public_instance') %}
<link rel="stylesheet" href="{{ url_for('client_token', token=link_token) }}" type="text/css">
{% endif %}
<!-- bocken.org custom theme -->
<link rel="stylesheet" href="https://bocken.org/static/css/searxng.css" type="text/css" media="screen">
{% block head %}
<link title="{{ instance_name }}" type="application/opensearchdescription+xml" rel="search" href="{{ opensearch_url }}">
{% endblock %}
<link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}" sizes="any">
<link rel="icon" href="{{ url_for('static', filename='img/favicon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='img/favicon.png') }}">
</head>
<body class="{{ endpoint }}_endpoint" >
<main id="main_{{ self._TemplateReference__context.name|replace("simple/", "")|replace(".html", "") }}" class="{{body_class}}">
{% if errors %}
<div class="dialog-error" role="alert">
<a href="#" class="close" aria-label="close" title="close">×</a>
<ul>
{% for message in errors %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<nav id="links_on_top">
{%- from 'simple/icons.html' import icon_big -%}
{%- block linkto_about -%}
<a href="{{ url_for('info', pagename='about') }}" class="link_on_top_about">{{ icon_big('information-circle') }}<span>{{ _('About') }}</span></a>
{%- endblock -%}
{%- block linkto_donate -%}
{%- if donation_url -%}
<a href="{{ donation_url }}" class="link_on_top_donate">{{ icon_big('heart') }}<span>{{ _('Donate') }}</span></a>
{%- endif -%}
{%- endblock -%}
{%- block linkto_preferences -%}
{%- if request.args.get('preferences') -%}
<a href="{{ url_for('preferences') }}?preferences={{ request.args.get('preferences') }}&preferences_preview_only=true" class="link_on_top_preferences">{{ icon_big('settings') }}<span>{{ _('Preferences') }}</span></a>
{%- else -%}
<a href="{{ url_for('preferences') }}" class="link_on_top_preferences">{{ icon_big('settings') }}<span>{{ _('Preferences') }}</span></a>
{%- endif -%}
{%- endblock -%}
</nav>
{% block header %}
{% endblock %}
{% block content %}
{% endblock %}
</main>
<footer>
<p>
{{ _('Powered by') }} <a href="{{ url_for('info', pagename='about') }}">SearXNG</a> - {{ searx_version }} — {{ _('a privacy-respecting, open metasearch engine') }}<br>
<a href="{{ searx_git_url }}">{{ _('Source code') }}</a>
| <a href="{{ get_setting('brand.issue_url') }}">{{ _('Issue tracker') }}</a>
{% if enable_metrics %}| <a href="{{ url_for('stats') }}">{{ _('Engine stats') }}</a>{% endif %}
{% if get_setting('brand.public_instances') %}
| <a href="{{ get_setting('brand.public_instances') }}">{{ _('Public instances') }}</a>
{% endif %}
{% if get_setting('general.privacypolicy_url') %}
| <a href="{{ get_setting('general.privacypolicy_url') }}">{{ _('Privacy policy') }}</a>
{% endif %}
{% if get_setting('general.contact_url') %}
| <a href="{{ get_setting('general.contact_url') }}">{{ _('Contact instance maintainer') }}</a>
{% endif %}
{% for title, link in get_setting('brand.custom.links').items() %}
| <a href="{{ link }}">{{ _(title) }}</a>
{% endfor %}
</p>
</footer>
</body>
</html>