-- Leo's gemini proxy

-- Connecting to gemini.dimakrasner.com:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Self-Hosting on ESP32


This capsule is now self-hosted on an ESP32 development board (ESP32-DevKitC V4) connected to a phone charger and Wi-Fi. When the ESP32 application gets notified that Wi-Fi connection is established, it starts a Gemini server and uses the Duck DNS API to set the address of dimkr-esp32.duckdns.org to the response from ifconfig.me. gemini.dimakrasner.com is now a CNAME record that points to dimkr-esp32.duckdns.org, and I copied my certificate to the ESP32.


https://www.duckdns.org


(I might regret this decision and return to my VPS, if the ESP32 setup turns out to be unstable.)


It didn't take long to implement a Gemini server, but this board definitely has some disadvantages compared to a Pi:

You must choose an ESP-IDF version, because they're API-incompatible with each other. The ESP-IDF also dictates the versions of libraries, because some "open-source" libraries included in ESP-IDF are provided as binary blobs: you can't update these libraries.

mbedtls, the default TLS library, supports TLS 1.3 in ESP-IDF 5.0, which is great. However, the sentence "Mbed TLS supports SSL 3.0 up to TLS 1.3" is misleading, because TLS 1.3 support is limited to the client side.

There's more than one way to do many of the things you'd probably want to do in your application. The examples provide at least 3 different ways to set up a Wi-Fi connection and get notifications about the connection status.

ESP-TLS is a super simple wrapper around mbedtls (or wolfssl), which reduces the amount of boilerplate code you need to write if you want to implement a simple TLS client or server. This wrapper is very high-level and doesn't allow you to tweak internal configuration easily.

The HTTPS Server component is implemented as a bunch of callbacks (new session, TX/RX) passed to the HTTP Server component. I haven't found a good, minimal example of a "raw TLS" server.

The TCP/IP stack has an artificial (?) limitation of CONFIG_LWIP_MAX_SOCKETS (<= 16) sockets. That's enough for many Gemini capsules, but not much.

It looks like O_NONBLOCK and FIONBIO don't work: esp_tls_server_session_create() blocks. To overcome this, I had to create a "task" for every incoming connection, and that's OK becaue I don't have to care about scale: the number of sockets is limited anyway, and I know I have enough RAM for 16 stacks.

SO_RCVTIMEO doesn't work as it should: esp_tls_server_session_create() doesn't fail on timeout. The main "task" of my server, which accepts incoming connections, is responsible for killing slow clients and killing the oldest client to make room for a new one. It unblocks esp_tls_server_session_create() by closing the socket.

idf.py and CMake can be painfully slow, even if ccache is enabled. I have a Windows laptop, which I used for a C# course I had to take when I started my current job: I'm transforming the backend of a product that normally runs on Windows servers into a scalable SaaS offering that runs on Linux with PostgreSql. It seems to me that every file system operation that involves many small files is extremely slow on Windows. Later, I moved my workflow to a much older Linux laptop: thankfully, ESP-IDF has a Linux version, and now it takes <5s to build my application.

I looked at some power consumption figures and I'm not sure if the ESP32 is as power efficient as some people think. Maybe I misunderstood what I read, but it looks like power consumption is very close to that of a Raspberry Pi Zero 2 W, which has a quad-core AArch64 CPU and 512 MB of RAM. It will be much harder to recommend the ESP32 when Pi prices drop.

I have concerns about longevity: unlike a Pi, the ESP32 uses on-board flash. I don't like the idea of flashing firmware every time I want to add a post.

4 MB of flash is not much.

RAM is slow: the first version of my Gemini server used a giant blob of concatenated .gmi files and memmem() to search for a magic marker that marks the beginning of a file. It was extremely slow, and now this blob starts with a small array that specifies the offset of a .gmi file, given the CRC32 of its name. This is much faster than the previous solution, even after I zlib-compressed the individual .gmi files and added a decompression step (using miniz) before the response is sent.


https://github.com/richgel999/miniz


More to come later: I'm still cleaning up my code.

-- Response ended

-- Page fetched on Thu May 9 22:23:46 2024