Skip to content

Native HTTPS on Linux frames: TLS in the runtime, Caddy gone - #481

Merged
mariusandra merged 3 commits into
mainfrom
worktree-native-https
Sep 12, 2026
Merged

Native HTTPS on Linux frames: TLS in the runtime, Caddy gone#481
mariusandra merged 3 commits into
mainfrom
worktree-native-https

Conversation

@mariusandra

Copy link
Copy Markdown
Collaborator

Every Linux frame — Raspberry Pi OS and the three Buildroot images alike — now serves HTTPS from inside the runtime's own HTTP server, from the same per-frame certificate the backend mints. No Caddy process any more, neither for the API (tls_proxy.nim, deleted) nor for the hotspot portal (setup_proxy.nim, deleted). ESP32 firmware already did this natively; Linux frames match. Plan, decisions and removal list: docs/native-https.md.

Depends on the FrameOS/mummy fork: FrameOS/mummy#1 (frameos.nimble pins its commit like pixie; nimble.lock updated by hand for that one entry).

Runtime

  • mummy fork: TLS listeners with OpenSSL inside the epoll loop, newTlsConfig() loading the PEM material from memory (the private key never touches the file system), addListener/removeListener from any thread while serving, Request.secure. OpenSSL is already linked on every image (hub_client wss, http_client, logger), so no base image rebuild.
  • server/listeners.nim plans the sockets from the config alone: plain framePort (on loopback with exposeOnlyPort, on bindHost when set) plus TLS on httpsProxy.port when enabled and certificate material is present; otherwise tls:default_cert as before. A certificate the runtime cannot load or a port it cannot bind logs tls:config_error / tls:start_error and the frame stays reachable over plain HTTP; the plain listener failing is fatal, as it always was.
  • server/hotspot_listener.nim: while the hotspot is up and the plain listener is not on every interface, a plain listener on 10.42.0.1:<first free port from 8000> (every interface when that address is not up yet, which is what the Caddy setup proxy always bound), removed with the AP.
  • Request.secure feeds the same-origin guard, the Secure cookie flag and the cloud link's recorded origin alongside X-Forwarded-Proto, so an owner's own reverse proxy keeps working.
  • mummy's own log lines reach the frame logger (http:error, http:info; http:debug with per-handshake timing only when debug is on).
  • frameos.service.unprivileged carries CAP_NET_BIND_SERVICE next to CAP_SYS_TTY_CONFIG.

Backend, frontend, deploy

  • The three Buildroot gates from bde54b2 are gone. A Buildroot frame keeps its HTTPS setting; a new one gets HTTPS on by default like every other frame.
  • The caddy apt package is no longer planned or installed.
  • Upgrades from a Caddy-era release: the caddy.service probe in the full deploy plan is version-gated on the deploy baseline's device-reported frameos_version. frame_may_still_run_caddy() is true only for an unknown version or one at or below LAST_CADDY_FRAMEOS_VERSION = "2026.9.13" (the last release that shipped Caddy, cut this morning). The first full deploy past it disables a leftover caddy.service; a frame already reporting something newer is never asked again. The setup scripts keep their systemctl disable --now caddy.service line for one release, then both go.
  • Copy: "HTTPS proxy via Caddy" → "HTTPS API"; the section is "HTTPS" on every platform. docs/api-triality.md is unchanged (the https_proxy shape stays).

Tests

  • Fork: tests/test_tls.nim — GET/POST, plain and TLS side by side, a 4 MB response, keep-alive, WebSocket over TLS, a client stalled mid-handshake, plain text on the TLS port, listeners added/removed while serving. Upstream suite green with and without -d:ssl.
  • Runtime: test_listeners (planning), test_hotspot_listener, test_tls_listener (a TLS listener next to the harness's plain one through the real router: Secure cookie without a proxy header, the origin guard's 443 default, a 200 KB POST, removal while serving). The harness's stopServer now runs GC_fullCollect() first: the router handler closure is built on the test thread and freed on mummy's serving thread, and ORC's cycle roots are per thread — it only passed before by allocation-churn luck.
  • Backend: test_frame_may_still_run_caddy, test_post_deploy_plan_probes_caddy_only_for_caddy_era_frames, test_api_frame_update_buildroot_keeps_https_proxy; app/tasks, test_frames.py, frame sync and app/utils suites green (802 passed).
  • Full runtime binary compiles; frontend tsc and prettier clean.

Still to do

Bench on uus2w (uid 990), Cloud-5, Cloud-W (ARMv6, handshake ms with debug on) and one Raspbian Pi upgraded from 2026.9.13 (expect the plan to disable caddy.service once, then never probe again) — see the doc's Bench section.

🤖 Generated with Claude Code

https://claude.ai/code/session_018eY8yWLDyHKssZ31wnkB54

mariusandra and others added 3 commits September 12, 2026 14:48
Every Linux frame — Raspberry Pi OS and the three Buildroot images alike —
now serves HTTPS from inside the runtime's own HTTP server, from the same
per-frame certificate the backend mints. No Caddy process any more, neither
for the API (tls_proxy.nim, deleted) nor for the hotspot portal
(setup_proxy.nim, deleted). ESP32 firmware already did this natively; Linux
frames match. Plan and removal list: docs/native-https.md.

Runtime
- mummy is now the FrameOS/mummy fork (pinned by commit like pixie): TLS
  listeners with OpenSSL inside the epoll loop, newTlsConfig() loading the
  PEM material from memory (the private key never touches the file
  system), addListener/removeListener from any thread while serving,
  Request.secure. OpenSSL is already linked on every image (hub_client wss,
  http_client, logger), so no base image rebuild.
- server/listeners.nim plans the sockets from the config alone: plain
  framePort (on loopback with exposeOnlyPort, on bindHost when set) plus
  TLS on httpsProxy.port when enabled and certificate material is present;
  otherwise tls:default_cert as before. A certificate the runtime cannot
  load or a port it cannot bind logs tls:config_error / tls:start_error and
  the frame stays reachable over plain HTTP; the plain listener failing is
  fatal, as it always was.
- server/hotspot_listener.nim: while the hotspot is up and the plain
  listener is not on every interface, a plain listener on
  10.42.0.1:<first free port from 8000> (every interface when that address
  is not up yet, which is what the Caddy setup proxy always bound), removed
  with the AP. hotspotSetupPort reads it for the QR code and the
  captive-portal redirect.
- Request.secure feeds the same-origin guard, the Secure cookie flag and
  the cloud link's recorded origin alongside X-Forwarded-Proto, so an
  owner's own reverse proxy keeps working.
- mummy's own log lines reach the frame logger (http:error, http:info;
  http:debug with the per-handshake timing only when debug is on).
- frameos.service.unprivileged carries CAP_NET_BIND_SERVICE next to
  CAP_SYS_TTY_CONFIG for frames configured on a port below 1024.

Backend, frontend, deploy
- The three Buildroot gates from bde54b2 are gone (force-off in
  ensure_buildroot_frame_defaults, the 400, the disabled switch). A
  Buildroot frame keeps its HTTPS setting; a new one gets HTTPS on by
  default like every other frame.
- The caddy apt package is no longer planned or installed.
- Upgrades from a Caddy-era release: the caddy.service probe in the full
  deploy plan is version-gated on the deploy baseline's device-reported
  frameos_version — frame_may_still_run_caddy() is true only for an unknown
  version or one at or below LAST_CADDY_FRAMEOS_VERSION (2026.9.13, the
  last release that shipped Caddy). The first full deploy past it disables a
  leftover caddy.service; a frame already reporting something newer is
  never asked again. The setup scripts keep their disable line for one
  release.
- Copy: "HTTPS proxy via Caddy" -> "HTTPS API", the section is "HTTPS" on
  every platform. docs/api-triality.md is unchanged (the https_proxy shape
  stays).

Tests
- Fork: tests/test_tls.nim (GET/POST, plain and TLS side by side, a 4 MB
  response, keep-alive, WebSocket over TLS, a client stalled mid-handshake,
  plain text on the TLS port, listeners added/removed while serving).
- Runtime: test_listeners (planning), test_hotspot_listener and
  test_tls_listener (a TLS listener next to the harness's plain one through
  the real router: Secure cookie without a proxy header, the origin guard's
  443 default, a 200 KB POST, removal while serving). The harness's
  stopServer now runs GC_fullCollect() first: the router handler closure is
  built on the test thread and freed on mummy's serving thread, and ORC's
  cycle roots are per thread — it only passed before by allocation-churn
  luck.
- Backend: test_frame_may_still_run_caddy,
  test_post_deploy_plan_probes_caddy_only_for_caddy_era_frames,
  test_api_frame_update_buildroot_keeps_https_proxy.

Bench on uus2w, Cloud-5, Cloud-W and one upgraded Raspbian Pi is still to
do; see the doc's Bench section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018eY8yWLDyHKssZ31wnkB54
- mummy fork at 522e28f9: the extra OpenSSL symbols are bound through the
  wrapper's dynlib patterns (stock Nim loads libssl at run time, so a plain
  importc left SSL_get_version undefined at link time on every Ubuntu job),
  and listener changes ride the responseQueued event instead of a fourth
  SelectEvent.
- frame_may_still_run_caddy: the last-Caddy version tuple is asserted
  non-None before the comparison; mypy baseline refreshed (five entries
  went away with the removed Buildroot guard, the test fakes gained the
  usual arg-type entries).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018eY8yWLDyHKssZ31wnkB54
destroy() skips events newServer never created (the Windows fuzz job's
ephemeral-port exhaustion used to segfault there instead of raising).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018eY8yWLDyHKssZ31wnkB54
@github-actions

Copy link
Copy Markdown
Contributor

ESP32 firmware size

Built from 4cff633 by this run; compared with the latest release v2026.9.13.

Image This PR v2026.9.13 Δ
esp32-s3 app (OTA image) 3,160,224
87.7% of 3520K slot, 434 KB free
3,160,224 ±0
esp32-s3 merged flash image 3,225,760 3,225,760 ±0
esp32-s3-32mb app (OTA image) 3,160,208
76.5% of 4032K slot, 946 KB free
3,160,208 ±0
esp32-s3-32mb merged flash image 3,291,280 3,291,280 ±0

Breakdown by subsystem — esp32-s3

Subsystem This PR v2026.9.13 Δ
FrameOS core (Nim) 404,620 404,620 ±0
QuickJS 356,328 356,328 ±0
FrameOS apps (Nim) 316,786 316,786 ±0
pixie 300,949 300,949 ±0
Wi-Fi stack 295,300 295,300 ±0
Nim stdlib 249,026 249,026 ±0
ESP-IDF misc 178,065 178,065 ±0
Embedded font 146,358 146,358 ±0
String pool (attributed to efuse) 132,857 132,857 ±0
mbedTLS + certificates 132,539 132,539 ±0
lwIP / HTTP / WebSocket 126,644 126,644 ±0
fos_* firmware shell (C) 116,897 116,897 ±0
libc / libm / newlib 116,259 116,259 ±0
Storage (SPIFFS / FatFS / SD / NVS) 94,132 94,132 ±0
Nim packages (chrono, zippy, chroma, qrgen, ...) 72,043 72,043 ±0
Display drivers (C) 64,934 64,934 ±0
Crypto (monocypher) 35,149 35,149 ±0
Total mapped flash 3,138,886 3,138,886 ±0
Inside each subsystem

FrameOS core (Nim) — 404,620

Part This PR v2026.9.13 Δ
frameos/js_runtime 102,097 102,097 ±0
frameos/interpreter.nim 97,276 97,276 ±0
frameos/utils 89,928 89,928 ±0
embedded_main.nim 18,319 18,319 ±0
lib/tz.nim 16,751 16,751 ±0
embedded_runtime.nim 15,056 15,056 ±0
frameos_nim_glue.c.obj 9,814 9,814 ±0
frameos/planner.nim 9,605 9,605 ±0
frameos/types.nim 7,913 7,913 ±0
frameos/values.nim 6,873 6,873 ±0
frameos/apps.nim 6,054 6,054 ±0
frameos/spool.nim 5,678 5,678 ±0
embedded_scene.nim 4,945 4,945 ±0
frameos/app_config.nim 2,871 2,871 ±0
frameos/reboot_reason.nim 2,628 2,628 ±0
frameos/node_config.nim 2,590 2,590 ±0
frameos/app_capabilities.nim 2,489 2,489 ±0
fos_netguard.c.obj 2,092 2,092 ±0
frameos/runtime_diagnostics.nim 763 763 ±0
fos_version.c.obj 324 324 ±0
frameos/cloud 255 255 ±0
frameos/ids.nim 137 137 ±0
frameos/channels.nim 122 122 ±0
frameos/hal 40 40 ±0

QuickJS — 356,328

Part This PR v2026.9.13 Δ
quickjs.c.obj 283,813 283,813 ±0
libunicode.c.obj 50,174 50,174 ±0
libregexp.c.obj 13,995 13,995 ±0
dtoa.c.obj 5,681 5,681 ±0
cutils.c.obj 2,141 2,141 ±0
fos_qjs_glue.c.obj 449 449 ±0
fos_quickjs_tz.c.obj 75 75 ±0

FrameOS apps (Nim) — 316,786

Part This PR v2026.9.13 Δ
apps/data/icalJson 50,265 50,265 ±0
apps/render/calendar 30,707 30,707 ±0
apps/render/chart 21,541 21,541 ±0
apps/data/immich 17,695 17,695 ±0
apps/data/wikicommons 17,110 17,110 ±0
apps/data/openaiImage 13,970 13,970 ±0
apps/data/unsplash 11,084 11,084 ±0
apps/data/weather 10,193 10,193 ±0
apps/data/beRecycle 9,918 9,918 ±0
apps/render/split 9,648 9,648 ±0
apps/data/localImage 9,202 9,202 ±0
apps/data/eventsToAgenda 9,060 9,060 ±0
apps/data/googlePhotos 9,044 9,044 ±0
apps/apps.nim 8,636 8,636 ±0
apps/render/zoomPan 8,544 8,544 ±0
apps/data/haSensor 7,287 7,287 ±0
apps/data/openaiText 6,762 6,762 ±0
apps/data/xmlToJson 5,420 5,420 ±0
apps/render/text 5,419 5,419 ±0
apps/render/svg 5,176 5,176 ±0
apps/render/image 4,510 4,510 ±0
apps/data/rotateImage 3,457 3,457 ±0
apps/logic/setAsState 3,441 3,441 ±0
apps/data/downloadImage 3,345 3,345 ±0
apps/render/gradient 3,173 3,173 ±0
apps/data/qr 3,118 3,118 ±0
apps/data/frameOSGallery 3,046 3,046 ±0
apps/data/downloadUrl 3,045 3,045 ±0
apps/render/color 2,804 2,804 ±0
apps/data/newImage 2,676 2,676 ±0
apps/data/resizeImage 2,550 2,550 ±0
apps/data/log 2,283 2,283 ±0
apps/render/opacity 2,272 2,272 ±0
apps/data/prettyJson 2,213 2,213 ±0
apps/data/parseJson 1,844 1,844 ±0
apps/data/clock 1,835 1,835 ±0
apps/logic/nextSleepDuration 1,639 1,639 ±0
apps/logic/ifElse 1,476 1,476 ±0
apps/logic/breakIfRendering 1,378 1,378 ±0

pixie — 300,949

Part This PR v2026.9.13 Δ
pixie/fontformats/opentype.nim 55,507 55,507 ±0
pixie/fileformats/webp.nim 37,525 37,525 ±0
pixie/fileformats/svg.nim 37,143 37,143 ±0
pixie/paths.nim 32,570 32,570 ±0
pixie/fileformats/jpeg.nim 25,338 25,338 ±0
pixie/fileformats/png.nim 22,849 22,849 ±0
pixie/images.nim 15,783 15,783 ±0
pixie/fonts.nim 11,019 11,019 ±0
pixie/paints.nim 9,889 9,889 ±0
pixie/fileformats/bmp.nim 8,234 8,234 ±0
pixie/common.nim 7,416 7,416 ±0
pixie/blends.nim 6,520 6,520 ±0
pixie/fileformats/gif.nim 5,925 5,925 ±0
pixie/inflatestream.nim 5,325 5,325 ±0
pixie.nim 5,224 5,224 ±0
pixie/fileformats/webp_vp8_tables.nim 3,881 3,881 ±0
pixie/fileformats/ppm.nim 3,681 3,681 ±0
pixie/contexts.nim 3,038 3,038 ±0
pixie/fileformats/qoi.nim 2,682 2,682 ±0
pixie/internal.nim 754 754 ±0
pixie/rgb565.nim 381 381 ±0
pixie/fontformats/svgfont.nim 215 215 ±0
pixie/decodebudget.nim 50 50 ±0

Wi-Fi stack — 295,300

Part This PR v2026.9.13 Δ
libnet80211.a 136,168 136,168 ±0
libpp.a 62,154 62,154 ±0
libwpa_supplicant.a 54,900 54,900 ±0
libphy.a 34,176 34,176 ±0
libesp_wifi.a 4,780 4,780 ±0
libesp_phy.a 2,541 2,541 ±0
libesp_coex.a 291 291 ±0
libcore.a 287 287 ±0
libespnow.a 3 3 ±0

Nim stdlib — 249,026

Part This PR v2026.9.13 Δ
pure/collections/tables.nim 70,556 70,556 ±0
system.nim 46,659 46,659 ±0
pure/unicode.nim 17,405 17,405 ±0
pure/times.nim 16,984 16,984 ±0
pure/json.nim 13,414 13,414 ±0
std/private/dragonbox.nim 10,351 10,351 ±0
pure/strutils.nim 8,520 8,520 ±0
pure/parsexml.nim 5,999 5,999 ±0
pure/algorithm.nim 5,368 5,368 ±0
pure/base64.nim 5,302 5,302 ±0
pure/hashes.nim 4,114 4,114 ±0
pure/xmlparser.nim 3,567 3,567 ±0
pure/parsejson.nim 3,553 3,553 ±0
std/formatfloat.nim 3,508 3,508 ±0
pure/uri.nim 3,225 3,225 ±0
pure/collections/sets.nim 3,211 3,211 ±0
pure/streams.nim 2,820 2,820 ±0
pure/strformat.nim 2,641 2,641 ±0
std/syncio.nim 2,355 2,355 ±0
pure/xmltree.nim 2,051 2,051 ±0
pure/lexbase.nim 1,805 1,805 ±0
pure/parseutils.nim 1,757 1,757 ±0
system/exceptions.nim 1,598 1,598 ±0
pure/strtabs.nim 1,568 1,568 ±0
pure/pathnorm.nim 1,356 1,356 ±0
pure/options.nim 1,118 1,118 ±0
std/private/ospaths2.nim 1,032 1,032 ±0
pure/collections/lists.nim 983 983 ±0
system/dollars.nim 891 891 ±0
pure/random.nim 835 835 ±0
std/private/digitsutils.nim 651 651 ±0
pure/math.nim 650 650 ±0
std/oserrors.nim 637 637 ±0
std/private/osdirs.nim 580 580 ±0
std/monotimes.nim 401 401 ±0
std/private/oscommon.nim 329 329 ±0
std/envvars.nim 250 250 ±0
std/private/decode_helpers.nim 240 240 ±0
std/private/ossymlinks.nim 187 187 ±0
pure/bitops.nim 162 162 ±0
pure/os.nim 99 99 ±0
std/private/osfiles.nim 96 96 ±0
std/assertions.nim 87 87 ±0
core/macros.nim 69 69 ±0
system/iterators.nim 33 33 ±0
system/ctypes.nim 9 9 ±0

ESP-IDF misc — 178,065

Part This PR v2026.9.13 Δ
libesp_hw_support.a 37,650 37,650 ±0
libhal.a 21,464 21,464 ±0
libfreertos.a 17,194 17,194 ±0
libesp_system.a 13,780 13,780 ±0
libesp_driver_uart.a 11,627 11,627 ±0
libheap.a 8,660 8,660 ±0
libesp_driver_i2c.a 8,062 8,062 ±0
libesp_driver_spi.a 7,756 7,756 ±0
libconsole.a 5,733 5,733 ±0
libjson.a 4,940 4,940 ±0
libesp_driver_gpio.a 3,890 3,890 ±0
libbootloader_support.a 3,813 3,813 ±0
libxtensa.a 3,691 3,691 ±0
libesp_driver_usb_serial_jtag.a 3,408 3,408 ±0
libesp_ringbuf.a 3,322 3,322 ±0
libesp_event.a 3,257 3,257 ±0
libesp_mm.a 2,451 2,451 ±0
libapp_update.a 2,422 2,422 ±0
libesp_psram.a 2,329 2,329 ±0
libesp_timer.a 2,326 2,326 ±0
libsoc.a 2,315 2,315 ±0
libesp_adc.a 2,289 2,289 ±0
liblog.a 1,375 1,375 ±0
libpthread.a 1,365 1,365 ±0
libesp_security.a 1,050 1,050 ±0
libesp_rom.a 902 902 ±0
libesp_app_format.a 507 507 ±0
libxt_hal.a 437 437 ±0
libesp_common.a 26 26 ±0
libesp_pm.a 24 24 ±0

Embedded font — 146,358

Part This PR v2026.9.13 Δ
assets/fonts.nim 146,358 146,358 ±0

String pool (attributed to efuse) — 132,857

Part This PR v2026.9.13 Δ
esp_efuse_utility.c.obj 131,811 131,811 ±0
esp_efuse_rtc_calib.c.obj 566 566 ±0
esp_efuse_table.c.obj 268 268 ±0
esp_efuse_api.c.obj 151 151 ±0
esp_efuse_startup.c.obj 61 61 ±0

mbedTLS + certificates — 132,539

Part This PR v2026.9.13 Δ
libmbedcrypto.a 71,391 71,391 ±0
libmbedtls.a 45,803 45,803 ±0
libmbedx509.a 7,084 7,084 ±0
libesp-tls.a 6,709 6,709 ±0
libesp_https_server.a 1,552 1,552 ±0

lwIP / HTTP / WebSocket — 126,644

Part This PR v2026.9.13 Δ
liblwip.a 78,305 78,305 ±0
libhttp_parser.a 10,350 10,350 ±0
libesp_http_server.a 10,016 10,016 ±0
libtcp_transport.a 8,237 8,237 ±0
libespressif__esp_websocket_client.a 6,826 6,826 ±0
libesp_http_client.a 6,666 6,666 ±0
libesp_netif.a 6,244 6,244 ±0

fos_ firmware shell (C)* — 116,897

Part This PR v2026.9.13 Δ
fos_http.c.obj 22,202 22,202 ±0
fos_cloud.c.obj 16,975 16,975 ±0
fos_console.c.obj 13,601 13,601 ±0
fos_client.c.obj 10,499 10,499 ±0
fos_scenes.c.obj 7,551 7,551 ±0
fos_settings.c.obj 6,511 6,511 ±0
fos_ota.c.obj 5,039 5,039 ±0
fos_config.c.obj 4,298 4,298 ±0
fos_wifi.c.obj 3,201 3,201 ±0
fos_assets.c.obj 3,124 3,124 ±0
fos_cloud_contract.c.obj 2,896 2,896 ±0
fos_sd_probe.c.obj 2,753 2,753 ±0
fos_assets_sd.c.obj 2,480 2,480 ±0
fos_schedule.c.obj 2,351 2,351 ±0
fos_buttons.c.obj 2,139 2,139 ±0
fos_status_screen.c.obj 2,049 2,049 ±0
main.c.obj 1,667 1,667 ±0
fos_config_parse.c.obj 1,467 1,467 ±0
fos_url_guard.c.obj 1,397 1,397 ±0
fos_tz.c.obj 1,381 1,381 ±0
fos_battery.c.obj 1,140 1,140 ±0
fos_minisig.c.obj 497 497 ±0
fos_framebuffer.c.obj 483 483 ±0
fos_assets_path.c.obj 259 259 ±0
fos_power.c.obj 241 241 ±0
fos_battery_filter.c.obj 214 214 ±0
fos_wake.c.obj 184 184 ±0
fos_board.c.obj 179 179 ±0
fos_json_guard.c.obj 119 119 ±0

libc / libm / newlib — 116,259

Part This PR v2026.9.13 Δ
libc.a 76,467 76,467 ±0
libm.a 31,563 31,563 ±0
libnewlib.a 6,609 6,609 ±0
libstdc++.a 1,476 1,476 ±0
libgcc.a 98 98 ±0
libcxx.a 46 46 ±0

Storage (SPIFFS / FatFS / SD / NVS) — 94,132

Part This PR v2026.9.13 Δ
libspiffs.a 20,380 20,380 ±0
libfatfs.a 20,133 20,133 ±0
libspi_flash.a 14,168 14,168 ±0
libnvs_flash.a 13,741 13,741 ±0
libsdmmc.a 13,013 13,013 ±0
libvfs.a 6,196 6,196 ±0
libesp_driver_sdspi.a 3,858 3,858 ±0
libesp_partition.a 1,960 1,960 ±0
libesp_vfs_console.a 678 678 ±0
libnvs_sec_provider.a 5 5 ±0

Nim packages (chrono, zippy, chroma, qrgen, ...) — 72,043

Part This PR v2026.9.13 Δ
QRgen 3.1.0 22,036 22,036 ±0
chrono 0.3.1 15,510 15,510 ±0
zippy 0.10.19 15,280 15,280 ±0
chroma 1.0.0 14,924 14,924 ±0
checksums 0.2.1 3,371 3,371 ±0
flatty 0.3.4 533 533 ±0
jsony 1.1.5 272 272 ±0
bumpy 1.1.3 117 117 ±0

Display drivers (C) — 64,934

Part This PR v2026.9.13 Δ
(everything else) 21,290 21,290 ±0
frameos_panel_table.c.obj 6,685 6,685 ±0
EPD_13in3e.c.obj 1,792 1,792 ±0
EPD_3in7.c.obj 1,564 1,564 ±0
EPD_5in79.c.obj 1,481 1,481 ±0
DEV_Debug.c.obj 1,309 1,309 ±0
EPD_2in7.c.obj 1,292 1,292 ±0
EPD_4in2.c.obj 1,262 1,262 ±0
EPD_7in3e.c.obj 1,208 1,208 ±0
EPD_4in2_V2.c.obj 1,184 1,184 ±0
DEV_Config_esp.c.obj 1,180 1,180 ±0
EPD_4in26.c.obj 1,123 1,123 ±0
29 smaller rows 23,564 23,564 ±0

Crypto (monocypher) — 35,149

Part This PR v2026.9.13 Δ
monocypher.c.obj 31,916 31,916 ±0
monocypher-ed25519.c.obj 3,233 3,233 ±0
Largest 30 objects
Object Subsystem This PR v2026.9.13 Δ
quickjs.c.obj QuickJS 283,813 283,813 ±0
assets/fonts.nim Embedded font 146,358 146,358 ±0
esp_efuse_utility.c.obj String pool (attributed to efuse) 131,811 131,811 ±0
frameos/interpreter.nim FrameOS core (Nim) 97,276 97,276 ±0
nim/lib/pure/collections/tables.nim Nim stdlib 70,556 70,556 ±0
frameos/js_runtime/app_runtime.nim FrameOS core (Nim) 61,493 61,493 ±0
pkgs/pixie-6.1.0/pixie/fontformats/opentype.nim pixie 55,507 55,507 ±0
libunicode.c.obj QuickJS 50,174 50,174 ±0
nim/lib/system.nim Nim stdlib 46,659 46,659 ±0
apps/data/icalJson/ical.nim FrameOS apps (Nim) 44,568 44,568 ±0
pkgs/pixie-6.1.0/pixie/fileformats/webp.nim pixie 37,525 37,525 ±0
pkgs/pixie-6.1.0/pixie/fileformats/svg.nim pixie 37,143 37,143 ±0
frameos/utils/image.nim FrameOS core (Nim) 34,490 34,490 ±0
pkgs/pixie-6.1.0/pixie/paths.nim pixie 32,570 32,570 ±0
monocypher.c.obj Crypto (monocypher) 31,916 31,916 ±0
frameos/js_runtime/runtime.nim FrameOS core (Nim) 30,086 30,086 ±0
pkgs/pixie-6.1.0/pixie/fileformats/jpeg.nim pixie 25,338 25,338 ±0
apps/render/calendar/app.nim FrameOS apps (Nim) 23,659 23,659 ±0
pkgs/pixie-6.1.0/pixie/fileformats/png.nim pixie 22,849 22,849 ±0
fos_http.c.obj fos_* firmware shell (C) 22,202 22,202 ±0
apps/render/chart/app.nim FrameOS apps (Nim) 18,940 18,940 ±0
embedded_main.nim FrameOS core (Nim) 18,319 18,319 ±0
frameos/utils/status_screen.nim FrameOS core (Nim) 18,111 18,111 ±0
x509_crt_bundle.S.obj mbedTLS + certificates 17,928 17,928 ±0
nim/lib/pure/unicode.nim Nim stdlib 17,405 17,405 ±0
nim/lib/pure/times.nim Nim stdlib 16,984 16,984 ±0
fos_cloud.c.obj fos_* firmware shell (C) 16,975 16,975 ±0
lib/tz.nim FrameOS core (Nim) 16,751 16,751 ±0
apps/data/immich/app.nim FrameOS apps (Nim) 16,148 16,148 ±0
pkgs/pixie-6.1.0/pixie/images.nim pixie 15,783 15,783 ±0

Flash = .text + .rodata from the linker map via esp_idf_size; "Inside each subsystem" splits every bucket one level down (the Nim core by directory, apps by app, packages by package, ESP-IDF by archive) over all objects, not just the largest. "String pool (attributed to efuse)" is the linker's merged string-literal pool for the whole image, not efuse code — see docs/esp32-image-size.md.

@mariusandra
mariusandra merged commit 6ba625d into main Sep 12, 2026
33 checks passed
@mariusandra
mariusandra deleted the worktree-native-https branch September 12, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant