Installing and using SearXNG in Gentoo Linux

Introduction

Searching the internet has been both increasingly cumbersome and frightening due to privacy concerns with many of the major search engine providers (such as Google or Bing). Thankfully there are some other good options like Startpage, Qwant, and Mojeek. Some of them use results provided by the major providers like Google, and some of them are more independent. However, another option exists, and that’s to use an application like SearXNG that acts as a meta-search engine, “aggregating the results of other search engines” without “storing information about its users”.

I started using SearXNG quite some time ago by choosing a public instance, but have recently gone a bit further and installed my own self-hosted version of it on one of my Gentoo Linux servers. In this article, I’m going to outline the process of installing, configuring, and running a self-hosted SearXNG instance along with some troubleshooting tips and “gotchas” to avoid. Though the details are specific to Gentoo Linux, the concepts should be readily applicable to other Linux distributions.

For my setup, I chose to install SearXNG in the uWSGI application server and proxy it via the Apache web server using the mod_proxy_uwsgi module. The flow of data follows the following design:

For the remainder of this article, I will use the pretend domain name of my-secret-search-engine.com. Any place where that domain is listed, replace it with your respective domain name or IP address of your server.

Installation – Prerequisites

To start, SearXNG should run as a system user, so that user should be created accordingly:

useradd -s /bin/bash --system -m --home-dir /usr/local/searxng searxng

Now switch to that new user and create a Python virtual environment (venv) for installing and running the application and all of its dependencies. The Python venv is isolated from the overall system installation of Python so that there aren’t any conflicts with the system’s Python interpreter or module versions.

su - searxng
python -m venv /usr/local/searxng/searxng-pyvenv

The Python virtual environment needs to be activated in order to use it. It’s helpful to automatically activate it when switching to the ‘searxng’ user, which can be done by putting it in that user’s .bashrc:

echo "source /usr/local/searxng/searxng-pyvenv/bin/activate" >> /usr/local/searxng/.bashrc

To make sure that the automatic activation is working as intended, switch back to the root account and then back to the ‘searxng’ user:

exit
su - searxng

If the venv is activated, the name of the venv should be displayed before the standard BASH prompt:

# su - searxng
Last login: Thu Oct 24 14:52:00 UTC 2025 on pts/1
(searxng-pyvenv) searxng@my-secret-search ~ $

SearXNG has some prerequisite Python modules that need to be installed, so install them using Python’s own package manager, Pip:

pip install --upgrade pip setuptools wheel pyyaml msgspec

To see the modules and versions that are currently installed in the virtual environment, the freeze subcommand can be issued in Pip:

$ pip freeze
PyYAML==6.0.3
setuptools==80.9.0
wheel==0.45.1
msgspec==0.19.0

Installation – SearXNG application

SearXNG doesn’t currently offer packaged “releases”, so the way to install it is to clone their Git repository and build the application from that clone:

mkdir -p /usr/local/searxng/searxng-pyvenv/source/
git clone "https://github.com/searxng/searxng" "/usr/local/searxng/searxng-pyvenv/source"
cd /usr/local/searxng/searxng-pyvenv/source/
pip install --use-pep517 --no-build-isolation -e .

After the installation finishes, there will be many more modules listed in Pip (the exact modules and versions may change with subsequent SearXNG versions):

$ pip freeze
anyio==4.11.0
async-timeout==5.0.1
babel==2.17.0
blinker==1.9.0
Brotli==1.1.0
certifi==2025.10.5
click==8.3.0
fasttext-predict==0.9.2.4
Flask==3.1.2
flask-babel==4.0.0
h11==0.16.0
h2==4.3.0
hpack==4.1.0
httpcore==1.0.9
httpx==0.28.1
httpx-socks==0.10.0
hyperframe==6.1.0
idna==3.11
isodate==0.7.2
itsdangerous==2.2.0
Jinja2==3.1.6
lxml==6.0.2
markdown-it-py==3.0.0
MarkupSafe==3.0.3
mdurl==0.1.2
msgspec==0.19.0
Pygments==2.19.2
python-dateutil==2.9.0.post0
python-socks==2.7.2
pytz==2025.2
PyYAML==6.0.3
setproctitle==1.3.7
setuptools==80.9.0
six==1.17.0
sniffio==1.3.1
typer-slim==0.19.2
typing_extensions==4.14.1
valkey==6.1.1
Werkzeug==3.1.3
wheel==0.45.1
whitenoise==6.11.0

Installation – uWSGI application server

Now that the SearXNG application itself has been installed, it’s time to install the application server (before proceeding to configure both the application server and SearXNG itself). I chose to go with uWSGI. Since uWSGI supports many different application languages and various plugins, and seeing as they can be easily configured with Gentoo’s USE flags, they should be set before installing uWSGI:

# grep uwsgi /etc/portage/package.use 
www-servers/uwsgi -embedded -php python UWSGI_PLUGINS: -carbon -http -mongodblog -nagios -redislog -rrdtool pam

# emerge -av uwsgi

With the uWSGI server installed, it can be started automatically at boot time using Gentoo’s OpenRC init scripts:

cd /etc/conf.d
cp -v uwsgi uwsgi.searxng
ln -s /etc/init.d/uwsgi{,.searxng}
rc-update add uwsgi.searxng default

The above commands make a copy of the uWSGI configuration file specifically for SearXNG, and then create a new symlink of the uWSGI init script as /etc/init.d/uwsgi.searxng. Lastly, that new uwsgi.searxng init script is started at the default runlevel during the boot process.

Configuration – uWSGI application server

With both the SearXNG application and the uWSGI application server installed, it’s time to configure both of them accordingly. We’ll start with the uWSGI application server as it is more straightforward. First, we will copy the uWSGI template provided by SearXNG to the overall system location:

cp -v /usr/local/searxng/searxng-pyvenv/source/utils/templates/etc/uwsgi/apps-available/searxng.ini /etc/searxng/searxng.ini

Second, we will edit the uWSGI startup configuration to call this file:

echo 'UWSGI_EXTRA_OPTIONS="--ini /etc/searxng/searxng.ini"' >> /etc/conf.d/uwsgi.searxng

The full configuration file (excluding comments and blank lines) should look similar to the output below. The only other change that I made was to add a path for the log file. As such, the lines that I changed are in purple italics here, but feel free to make other changes for your particular setup or needs:

# grep -v "^#\|^$" /etc/conf.d/uwsgi.searxng 
UWSGI_SOCKET=
UWSGI_THREADS=0
UWSGI_PROGRAM=
UWSGI_XML_CONFIG=
UWSGI_PROCESSES=1
UWSGI_LOG_FILE=/var/log/uwsgi.log
UWSGI_CHROOT=
UWSGI_DIR=
UWSGI_PIDPATH_MODE=0750
UWSGI_USER=
UWSGI_GROUP=
UWSGI_EMPEROR_PATH=
UWSGI_EMPEROR_PIDPATH_MODE=0770
UWSGI_EMPEROR_GROUP=
UWSGI_EXTRA_OPTIONS="--ini /etc/searxng/searxng.ini"

Configuration – SearXNG application

With uWSGI being configured, it’s time to configure the SearXNG application itself. The configurations for it are handled by two separate files:

  • /etc/searxng/searxng.ini –> settings for running SearXNG as an application in uWSGI
  • /etc/searxng/settings.yml –> configuration options for the application itself

First, make the directory for storing these two settings files and copy them from the SearXNG sources:

mkdir -p /etc/searxng/
cp -v /usr/local/searxng/searxng-pyvenv/source/utils/templates/etc/uwsgi/apps-available/searxng.ini /etc/searxng/
cp -v /usr/local/searxng/searxng-pyvenv/source/utils/templates/etc/searxng/settings.yml /etc/searxng/
There are two versions of the settings.yml file that can be used:

/usr/local/searxng/searxng-pyvenv/source/utils/templates/etc/searxng/settings.yml
OR
/usr/local/searxng/searxng-pyvenv/source/searx/settings.yml

The first one (and the one used here) is much more compact and basically sets the defaults for most things, allowing for overrides where desired. I would strongly suggest using this first template unless you have a very specific reason to use the full one.

Starting with /etc/searxng/searxng.ini (which again is for configuring how the application will run inside the uWSGI server), here are the settings I suggest (pay close attention to the settings in purple italics as they are pertinent):

# grep -v "^#\|^$" /etc/searxng/searxng.ini 
[uwsgi]
uid = searxng
gid = searxng
env = LANG=C.UTF-8
env = LANGUAGE=C.UTF-8
env = LC_ALL=C.UTF-8
chdir = /usr/local/searxng/searxng-pyvenv/source/searx/ 
env = SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
disable-logging = true
chmod-socket = 666
single-interpreter = true
master = true
lazy-apps = true
plugin = python313,asyncio313
enable-threads = true
workers = ${UWSGI_WORKERS:-%k}
threads = ${UWSGI_THREADS:-4}
module = searx.webapp
virtualenv = /usr/local/searxng/searxng-pyvenv
pythonpath = /usr/local/searxng/searxng-pyvenv/source/searxng
socket = /usr/local/searxng/run/socket
buffer-size = 8192
offload-threads = %k

Now for the SearXNG settings themselves (in /etc/searxng/settings.yml), the template yields the following options by default:

# grep -v "^#\|^  #\|^$" /usr/local/searxng/searxng-pyvenv/source/utils/templates/etc/searxng/settings.yml
use_default_settings: true
general:
  debug: false
  instance_name: "SearXNG"
search:
  safe_search: 2
  autocomplete: 'duckduckgo'
  formats:
    - html
server:
  secret_key: "ultrasecretkey"
  limiter: true
  image_proxy: true
valkey:
  url: valkey://localhost:6379/0

The only option that MUST be changed is the ‘secret_key’, and it can be done easily with:

sed -i -e "s/ultrasecretkey/$(openssl rand -hex 16)/g" /etc/searxng/settings.yml

There are many other options that can be set or unset, and they are fairly well documented. Here’s an example settings.yml file with some of the settings that I personally prefer, followed by a brief explanation of them:

# grep -v "^#\|^  #\|^$" /etc/searxng/settings.yml 
use_default_settings: true
general:
  debug: false
  instance_name: "My Secret Search Engine"
  enable_metrics: false
search:
  safe_search: 0
  autocomplete: ""
  formats:
    - html
server:
  secret_key: "YOUR_RANDOM_KEY"
  limiter: false
  image_proxy: true
  http_protocol_version: "1.1"
  method: "GET"
ui:
  theme_args:
    simple_style: black
  url_formatting: full
OptionExplanation
instance_name:Setting it to your site's name will display it in the browser's title bar
enable_metrics: falseVarious anonymous metrics; disabled for even better anonymity
safe_search: 0Disables all "safe search" filtering
autocomplete: ""Which autocomplete backend to use; in this case none
image_proxy: trueUses your instance to proxy images for better anonymity
method: "GET"Favours some ease of use over the more private "POST"
url_formatting: fullShows the full URL of all links instead of a breadcrumb ("pretty")
Pay special attention to the ‘use_default_settings:’ declaration. I have included some additional syntax information about it in the “Troubleshooting and Gotchas” section at the bottom of the article.

Configuration – Apache web server

Now that both the uWSGI application server and the SearXNG application itself have been installed and configured, the last step is to proxy through the Apache web server.

Though uWSGI can be set up to handle HTTP requests directly—thus removing the need for Apache or a different web server—I prefer to keep it as a backend and let a dedicated web server manage HTTP connections.

Doing so requires two Apache modules: mod_proxy and mod_proxy_uwsgi. Linux distributions have different methods of enabling Apache modules, so consult your distribution’s documentation on doing so. In Gentoo, it is done by adding “proxy” and “proxy_uwsgi” to APACHE2_MODULES and re-emerging it.

It’s also important to load mod_proxy BEFORE mod_proxy_uwsgi, so make sure that the order is correct in the module-loading section of /etc/apache2/httpd.conf:

# grep proxy /etc/apache2/httpd.conf 
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so

As for the Apache vhost configuration, it is likely quite similar to any other site you have configured (and generic vhost configuration is outside the scope of this article). The parts that are specific to SearXNG are the proxy directives. Assuming that SearXNG is going to be accessed at the root of the domain, the proxy directives should look like:

<Location />
     ProxyPreserveHost On
     ProxyPass unix:/usr/local/searxng/run/socket|uwsgi://localhost/

     RequestHeader set X-Forwarded-Proto %{REQUEST_SCHEME}s
     RequestHeader set X-Script-Name /searxng
     RequestHeader set X-Real-IP %{REMOTE_ADDR}s
     RequestHeader append X-Forward-For %{REMOTE_ADDR}s
</Location>

The ‘ProxyPass’ line in purple is the one that is responsible for proxying the HTTP request via UNIX socket to the uWSGI application server and vice-versa. After reloading Apache, your SearXNG instance should now be accessible. If it isn’t, you may find some additional pointers in the “Troubleshooting and Gotchas” section at the bottom of this article.

Updating the SearXNG application

As previously mentioned, SearXNG doesn’t publish releases so updating involves pulling down the latest Git master branch, updating dependencies, and then rebuilding. There are many different methods for performing these updates in Git, but my approach is:

su - searxng
cd /usr/local/searxng/searxng-pyvenv/source
git pull origin master
pip install --upgrade pip setuptools wheel pyyaml msgspec
pip install --upgrade --use-pep517 --no-build-isolation -e .
exit
/etc/init.d/uwsgi.searxng restart

The steps outlined in the code block above will:

  • Switch to the ‘searxng’ user, which will activate the Python virtual environment (assuming you followed the instructions in the ‘Installation – Prerequisites’ section of this article)
  • Change to the SearXNG source directory (Git)
  • Pull the latest files from SearXNG’s Git master branch
  • Use pip to update the dependencies
  • Use pip to rebuild the application using the latest sources
  • Switch back from the ‘searxng’ user back to root (“exit”)
  • Restart the uWSGI application server containing the SearXNG application

After these steps, SearXNG should be the latest version. This can be validated by checking the following line in the footer of the page. For example:

Powered by SearXNG – 2025.10.24+2cdbbb249

That line should show the date of the pull and the first 9 characters of the latest commit that was pulled.

Updating – Rolling back

In case a particular update causes a problem, it can be readily rolled back to the previous version you had installed by performing a git reset:

su - searxng
cd /usr/local/searxng/searxng-pyvenv/source
git reset --keep HEAD@{1}
pip install --upgrade --use-pep517 --no-build-isolation -e .
exit
/etc/init.d/uwsgi.searxng restart

Troubleshooting and Gotchas

If you run into problems with the installation or configuration steps throughout this article, some of the more common errors and pitfalls are listed here, separated into sections based on where the problem lies.

SearXNG application

The SearXNG settings are documented quite well, but I did run into a particular syntax discrepancy that caused unexpected results. When using the use_default_settings directive, there is a syntax difference based on whether or not any of the search engines will be modified.

If no engine modifications will be made:

use_default_settings: true
use_default_settings:

If, however, any engine modifications are present, the true value must be dropped.

use_default_settings:
  engines:
    remove:
      - google
use_default_settings: true
  engines:
    remove:
      - google

In the ‘fails’ scenarios, the uWSGI log will show errors related to various Python scripts with these lines being at or near the end of the trace:

File "/usr/local/searxng/searxng-pyvenv/source/searx/settings_loader.py", line 218, in load_settings
user_cfg = load_yaml(cfg_file)
File "/usr/local/searxng/searxng-pyvenv/source/searx/settings_loader.py", line 48, in load_yaml
raise SearxSettingsException(e, str(file_name)) from e
searx.exceptions.SearxSettingsException: mapping values are not allowed here
in "/etc/searxng/settings.yml", line 5, column 10
unable to load app 0 (mountpoint='') (callable not found or import error)
*** no app loaded. going in full dynamic mode ***

uWSGI application server

If the following error message appears in the uWSGI log:

bind(): No such file or directory [core/socket.c line 230]

it is likely that the path to the UNIX socket is not present or that the permissions are incorrect. Check the ‘socket =’ line in /etc/searxng/searxng.ini and make sure the path is there and that the permissions are 755 owned by the ‘searxng’ user:

# ls -lhd /usr/local/searxng/run/
drwxr-xr-x 2 searxng searxng 4.0K Oct 24 17:38 /usr/local/searxng/run/

Apache web server

Though the Apache vhost configuration for SearXNG is essentially just the proxy section listed above, there are many syntax errors that can arise from it. The ProxyPass line—which passes HTTP requests to the UNIX socket—can be particularly finicky about syntax:

ProxyPass unix:/usr/local/searxng/run/socket|uwsgi://localhost/

I found that only specifying the ‘uwsgi://’ protocol resulted in the application failing. In my case, I needed it to specifically reference ‘localhost’. As such, the following ProxyPass directive did not work for me:

ProxyPass unix:/usr/local/searxng/run/socket|uwsgi://

Dovecot 2.3 to 2.4 update problems

Dovecot, which is a secure IMAP server, released version 2.4.0 on 24 January 2025 and a minor revision of 2.4.1 on 28 March 2025. As such, I recently decided to update my instances from the 2.3 branch (version 2.3.21.1 to be exact) to this new 2.4 branch. Though major revision updates can often be problematic, this one posed some additional challenges due to many backwards-incompatible changes. Though that linked upgrade documentation was helpful, I still ran into some problems with the update that I will explain in this post. Hopefully these tips will help others avoid some of the mistakes that I made.

My Dovecot IMAP configuration is rather simple, making some of the backwards-incompatible changes that applied to me easy enough to address:

  • Variable expansions (or so I thought; more on this one later)
  • Naming of userdb and passdb
    • These two directives now require a name, which can be the name of the lookup database or simply arbitrary
  • Arguments supplied to userdb and passdb separated into individual directives
    • Instead of having, say, passdb { args = username_format=%n $path_to_file }, the args are separated into:
      • passdb_driver = passwd-file
      • auth_username_format =
      • passwd_file_path = $path_to_file
  • Changes to the names of some directives (including SSL)
    • ssl_cert became ssl_server_cert_file
    • ssl_key became ssl_server_key_file
  • Separation of the mail_location directive into individual components
    • Instead of having mail_location = $mailbox_format:$mailbox_path (e.g. mail_location = maildir:/var/mail/), it is now:
      • mail_driver = $mailbox_format –> mail_driver = maildir
      • mail_path = $mailbox_path –> mail_path = /var/mail/
      • mail_home = $mailbox_home –> mail_home = /var/mail/home
  • Some new defaults for base settings
    • The base_dir now defaults to /var/run/dovecot, so it should be omitted and shouldn’t be changed
    • The listen directive now defaults to listen = *, ::, which will listen on all available IPv4 and IPv6 addresses
    • The disable_plaintext_auth directive was renamed to auth_allow_cleartext and now defaults to no for better security

I also took this upgrade window as an opportunity to shift all of my configuration from the included conf.d/ files to a single dovecot.conf file with all of my settings. Though I was able to make a copy of my Dovecot 2.3 configuration files that I could modify in preparation for the update to Dovecot 2.4, the new configuration didn’t work perfectly.

SSL errors when starting Dovecot

The first unexpected error I encountered was when trying to start Dovecot 2.4 with my new configuration. It refused to start, throwing an error message of ‘SSL certificates too long’. The problem here ended up being that certificates no longer need to be redirected into the directive. More simply put, the less-than sign (<) needed to be removed from the certificate lines:

In Dovecot 2.3:
ssl_cert = </etc/ssl/mail_server/fullchain.pem
ssl_key = </etc/ssl/mail_server/privkey.pem
In Dovecot 2.4
ssl_server_cert_file = /etc/ssl/mail_server/fullchain.pem
ssl_server_key_file = /etc/ssl/mail_server/privkey.pem
The important part here is that the ‘<‘ has been removed for each of the SSL server files (both cert and key).

Variable expansion not working as intended

With the documentation provided by the Dovecot team regarding changes to variables, I thought that the expansion would be a non-issue, but I was mistaken. After upgrading to Dovecot 2.4, I could no longer log in to any of my email addresses across multiple domains using any of my IMAP clients. Looking at the mail authentication logs, I found many error messages similar to this one:

Jul 03 00:57:06 [dovecot] auth(z@z-issue.com,$IP,sasl:login): passwd-file: missing passwd file: /var/mail/vhosts/%domain/shadow

This first error was a simple syntax mistake on my part where I accidentally omitted the needed curly braces around any variables. In particular, I referenced %domain instead of %{domain} in many directives, such as mail_home, mail_path, and for this exact error, the passwd_file_path line of passdb.

After correcting my mistake by adding the curly braces, I anticipated the problem would be resolved. However, I was still unable to log in using my IMAP clients. The error messages in the mail authentication logs did change, though, to ones like this:

Jul 03 01:14:52 [dovecot] auth(z@z-issue.com,$IP,sasl:cram-md5)<+ssPdf84tMAYz+f6>: Error: passwd-file: Failed to expand passwd-file path /var/mail/vhosts/%{domain}/shadow: domain: No value to get domain from

This error was more obtuse to me. The %{domain} variable should have been available based on the email address logging in. In this error message above, the email address (which is a fake example) of z@z-issue.com should have resulted in:

%{user} --> expanding to 'z'
%{domain} --> expanding to 'z-issue.com'

but it didn’t. Looking again at the Dovecot 2.4 upgrade documentation for variable expansion, I finally figured out what I needed to do. In the table under the ‘List of common short variables and their replacements‘, the row showing %d being replaced by %{user|domain} was confusing to me. I wrongly interpreted that syntax as the boolean “or” in Regular Expressions (RegEx), meaning that I can use %{user} as a variable containing the username portion of the email address, and %{domain} as a variable containing the domain portion of the email address.

My interpretation of that syntax was incorrect. The chart is showing the literal variable to use, and the pipe (or vertical bar) here is serving as a filter. That results in the variable expansion being:

Old variableNew syntaxExplanation
%d%{user|domain}Referencing the ‘user’ variable, extract only the ‘domain’ portion.
%n%{user|username}Referencing the ‘user’ variable, extract only the ‘username’ portion.

Now that I understood the proper syntax, I was able to update the relevant sections of my Dovecot auth directives:

mail_driver = maildir
mail_home = /var/mail/vhosts/%{user|domain}/%{user|username}/
mail_path = /var/mail/vhosts/%{user|domain}/%{user|username}/

userdb passwd-file {
        userdb_driver = passwd-file
        auth_username_format = %{user|username}
        passwd_file_path = /var/mail/vhosts/%{user|domain}/passwd
}

passdb passwd-file {
        passdb_driver = passwd-file
        auth_username_format = %{user|username}
        passwd_file_path = /var/mail/vhosts/%{user|domain}/shadow
}

After restarting Dovecot, I was able to connect via my IMAP clients once again.

Cheers,
Nathan Zachary

Gentoo Linux on the Framework 13 AMD 7840U with 2.8K display

Introduction

Until recently, I had been using a very old Dell laptop for my personal needs. However, when the motherboard started failing I decided it was time to look into options for replacing the laptop. I looked through many, many options from the “mainstream” manufacturers (e.g. Dell, Lenovo, Acer) and some of the lesser-known manufacturers who specialise in Linux (e.g. System76, Tuxedo Computers, Star Labs). Though I preferred the latter, neither they nor the mainstream manufacturers had options that met all of my goals. That’s when I remembered a relative new-comer to the laptop hardware industry: Framework.

Specifications

Checking their site, I found that the new Framework 13 met many of my needs:

  • AMD options (instead of Intel or NVIDIA)
  • Small form factor (13″)
  • Matte display with 2560×1440 resolution (or higher) and 90Hz (or higher)
  • UVC-supported webcam that wasn’t objectively awful
  • No components soldered onto the motherboard (meaning, all upgradeable)
    • Even better if it came bare-bones so that I could choose the components
  • A BIG bonus of the ports being expansion cards that are interchangeable
    • This allows the owner to install, say, 4 USB-C ports, or 2 of them with 2 HDMI ports for additional monitors, or any other combination needed at the time

So, I joined the pre-order list for the new Framework 13 with the following specs:

  • AMD Ryzen 7 7840U with AMD 780M integrated GPU
  • 13.5″ matte display at 2880×1920, 120Hz, and >500 nit brightness
  • Omnivision 9.2MP OVO8X sensor webcam (assembled by Chicony)

and “brought my own” additional components:

Configurations

Having gone through all that background information, I would like to document here some of the less-than-obvious configurations that I had to apply in order to get everything working nicely under Gentoo Linux. Though this guide focuses on Gentoo, much of the information is likely applicable to other Linux distributions.

This write-up isn’t meant to be an exhaustive list of Linux configurations for the Framework 13 laptop. There are some other great resources out there pertaining to getting this hardware working properly in Linux, including:

Now, onward to the configurations…

Firmware

AMD 780M iGPU

One of the more challenging parts to kernel configuration is the firmware blobs that are necessary for the AMD 780M iGPU (integrated graphics) that comes with the AMD Ryzen 7 7840U processor. Often with these chipsets, figuring out the firmware is a bit of trial-and-error by booting, checking dmesg to see which firmware blobs tried to load but couldn’t, adding them, and repeating until there are no further Failed to load firmware "$FIRMWARE_NAME.bin" error messages. Going through that process, I found the following list of firmware is needed:

dcn_3_1_4_dmcub.bin
gc_11_0_1_imu.bin
gc_11_0_1_me.bin
gc_11_0_1_mec.bin
gc_11_0_1_mes.bin
gc_11_0_1_mes1.bin
gc_11_0_1_mes_2.bin
gc_11_0_1_pfp.bin
gc_11_0_1_rlc.bin
psp_13_0_4_ta.bin
psp_13_0_4_toc.bin
sdma_6_0_1.bin
vcn_4_0_2.bin

I prefer to build the AMDGPU driver directly into the kernel instead of loading it as a module. As such, it’s necessary for me to list these firmware bin files directly via the kernel’s “Firmware loading facility”:

Device Drivers  --->
  Generic Driver Options --->
    Firmware loader  --->
      [*] Firmware loading facility 
      () 
      (/lib/firmware) Firmware blobs root directory

That results in this long single-line string being put into the “()” section of the kernel configuration:

amdgpu/dcn_3_1_4_dmcub.bin amdgpu/gc_11_0_1_imu.bin amdgpu/gc_11_0_1_me.bin amdgpu/gc_11_0_1_mec.bin amdgpu/gc_11_0_1_mes.bin amdgpu/gc_11_0_1_mes1.bin amdgpu/gc_11_0_1_mes_2.bin amdgpu/gc_11_0_1_pfp.bin amdgpu/gc_11_0_1_rlc.bin amdgpu/psp_13_0_4_ta.bin amdgpu/psp_13_0_4_toc.bin amdgpu/sdma_6_0_1.bin amdgpu/vcn_4_0_2.bin

If you don’t want to deal with that mess of building the firmware and driver directly into the kernel, you can always build the AMDGPU driver as a module and leave it out of your initramfs, so that it loads later in the boot process. It should then select the appropriate firmware blobs for you.

Intel AX210 WiFi

This generation of Framework 13 laptops (at least the AMD variants) ship with the AMD RZ616 WiFi chip, which is manufactured by MediaTek. This chipset has been known to have performance problems in Linux, and also requires at least a 6.5 kernel. Instead of dealing with those problems, I decided to replace it with an Intel AX210 WiFi chip.

As with the AMD iGPU mentioned above, the Intel AX210 WiFi chipset requires two firmware blobs be added to the kernel. In this case, the two needed ones are:

iwlwifi-ty-a0-gf-a0-89.ucode
iwlwifi-ty-a0-gf-a0.pnvm

and they can simply be appended to the “Firmware loading facility” line mentioned above.

Display

One of the reasons that I wanted the Framework 13 was this generation’s new display panel. I like that it’s a 13.5″ display with matte finish, 2880×1920 resolution, and 120Hz refresh rate. However, the colours were quite drab by default. By decoding the EDID for the panel, I found the actual model to be a BOE NE135A1M-NY1:

$ cat /sys/class/drm/card0-eDP-1/edid | edid-decode | grep -e 'Manufacturer' -e 'Display Product Name'
    Manufacturer: BOE
    Display Product Name: 'NE135A1M-NY1'

Knowing the panel manufacturer and model allowed me to get an Image Colour Management (ICM) file for the panel, which is essentially a colour profile for a more accurate calibration. I have uploaded the ICM file here for safekeeping (which you are free to download directly), but it was originally found on the NotebookCheck website.

In Linux, the method for applying a colour profile varies based on your Desktop Environment or Window Manager. For instance, KDE and GNOME both have colour management GUI applications available with easy ways of importing and managing profiles. I use the OpenBox Window Manager, so I would prefer to have a desktop-agnostic approach. The solution for me is a combination of colord and xiccd.

The colord application allows for importing the colour profile and assigning it to a particular device (i.e. a particular display panel), and then xiccd acts as a bridge between colord and Xorg. It is xiccd that will automatically apply the colour profile to the panel when starting Xorg, and subsequently, my OpenBox Window Manager session.

One “gotcha” is that xiccd must already be running before issuing these colormgr commands below. Start it beforehand in another terminal window.
## Get the Device ID of the display panel:
$ colormgr get-devices | grep 'Device ID'
Device ID:     xrandr-BOE-NE135A1M-NY1

## Copy the ICM file to the proper location and change the extension:
$ sudo mv $CURRENT_LOCATION/BOE0CB4.icm /usr/share/color/icc/colord/BOE0CB4.icc

## Import the colour profile into colormgr:
$ colormgr import-profile /usr/share/color/icc/colord/BOE0CB4.icc

## Find the Profile ID for the newly-imported colour profile:
$ colormgr get-profiles | grep -A1 'BOE0CB4.icc'
Filename:      /usr/share/color/icc/colord/BOE0CB4.icc
Profile ID:    icc-6cf5096a086792f1797bc4fa262bdfae

## Using the Device ID and the Profile ID, add the profile to the device:
## Syntax is:  colormgr device-add-profile $DEVICE_ID $PROFILE_ID$ colormgr device-add-profile xrandr-BOE-NE135A1M-NY1 icc-6cf5096a086792f1797bc4fa262bdfa
## Make that profile the default for the device:
## Syntax is:  colormgr device-make-profile-default $DEVICE_ID $PROFILE_ID
$ colormgr device-make-profile-default xrandr-BOE-NE135A1M-NY1 icc-6cf5096a086792f1797bc4fa262bdfae

## Confirm that the colour profile is applied to the device:
$ colormgr get-devices | grep -A2 'xrandr-BOE-NE135A1M-NY1'
Device ID:     xrandr-BOE-NE135A1M-NY1
Profile 1:     icc-6cf5096a086792f1797bc4fa262bdfae
               /usr/share/color/icc/colord/BOE0CB4.icc

As mentioned above, xiccd has to be running with the Window Manager session, so I choose to have it start automatically with OpenBox by adding xiccd & to ~/.config/openbox/autostart.

Keyboard bindings

As said, I use OpenBox as my Window Manager, so there isn’t a GUI application by default for adding or modifying the function keys. Instead, I mapped the keys to the actual functions that I wanted to perform. Here are my specific choices (with comments for the details of each binding):

    <!-- Keybinding for exiting Openbox with prompt with Alt+Esc -->
    <keybind key="A-Escape">
      <action name="Exit">
        <prompt>yes</prompt>
      </action>
    </keybind>
    <!-- Keybinding for showing the menu with left Windows key -->
    <keybind key="Super_L">
      <action name="ShowMenu">
        <menu>root-menu</menu>
      </action>
    </keybind>
    <!-- Keybinding for muting audio -->
    <keybind key="XF86AudioMute">
      <action name="Execute">
        <command>/usr/bin/amixer set Master toggle</command>
      </action>
    </keybind>
    <!-- Keybinding for lowering audio volume -->
    <keybind key="XF86AudioLowerVolume">
      <action name="Execute">
        <command>/usr/bin/amixer set Master 2%-</command>
      </action>
    </keybind>
    <!-- Keybinding for raising audio volume -->
    <keybind key="XF86AudioRaiseVolume">
      <action name="Execute">
        <command>/usr/bin/amixer set Master 2%+</command>
      </action>
    </keybind>
    <!-- Keybinding for audio previous track set in Audacious -->
    <!-- Keybinding for audio pause/play set in Audacious -->
    <!-- Keybinding for audio next track set in Audacious -->
    <!-- Keybinding for decreasing the brightness -->
    <keybind key="XF86MonBrightnessDown">
      <action name="Execute">
        <command>/usr/bin/xbacklight -dec 10</command>
      </action>
    </keybind>
    <!-- Keybinding for increasing the brightness -->
    <keybind key="XF86MonBrightnessUp">
      <action name="Execute">
        <command>/usr/bin/xbacklight -inc 10</command>
      </action>
    </keybind>

UGREEN USB-C ethernet adapter

Even though this particular adapter isn’t directly related to the Framework 13, I think it warrants a section here as I prefer it to the protrusive Framework Ethernet Expansion Card. The UGREEN USB-C gigabit ethernet adapter is based on the ASIX AX88179A chipset, which is supported directly in recent Linux kernels:

Device Drivers  --->
  Network Device Support --->
    USB Network Adapters --> 
      <*> Multipurpose USB Networking Framework
      <*>   ASIX AX88XXX Based USB 2.0 Ethernet Adapters
      <*>   ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet
      ...
      <*>   CDC NCM Support

The “gotcha” here, though, is that enabling the two ASIX AX88 drivers doesn’t automatically include the needed ‘CDC NCM Support’. For more information, see the following kernel bug 217204.

Conclusion

Hopefully this guide helped answer some questions about configuring the Framework 13’s hardware under Linux. As said, it wasn’t meant to be an exhaustive guide, but rather one focusing on some of the “gotchas” that I encountered when setting up mine with Gentoo Linux. If you run into any trouble, please feel free to comment and I will try to help.