From ca63ea923a091037a97cd8247078738c3852ec00 Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Thu, 9 Apr 2026 15:19:46 +1000 Subject: [PATCH] fix: generate web panel certs per build and clean up mqtt env flags --- .gitignore | 1 + .../extra_scripts/generate_web_panel_cert.py | 77 +++++++++++++++++++ platformio.ini | 4 +- src/helpers/mqtt/WebPanelCert.h | 27 +++---- 4 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 arch/esp32/extra_scripts/generate_web_panel_cert.py diff --git a/.gitignore b/.gitignore index a0ad5f6e..af22aa5d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ compile_commands.json .venv/ venv/ platformio.local.ini +src/helpers/mqtt/WebPanelCert.h diff --git a/arch/esp32/extra_scripts/generate_web_panel_cert.py b/arch/esp32/extra_scripts/generate_web_panel_cert.py new file mode 100644 index 00000000..a1268f8b --- /dev/null +++ b/arch/esp32/extra_scripts/generate_web_panel_cert.py @@ -0,0 +1,77 @@ +Import("env") + +from pathlib import Path +import subprocess +import tempfile + + +def render_pem_array(name, pem_text): + lines = [] + for chunk in pem_text.splitlines(keepends=True): + escaped = chunk.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + lines.append(f' "{escaped}"') + joined = "\n".join(lines) if lines else ' ""' + return f"static const char {name}[] =\n{joined};\n" + + +project_dir = Path(env["PROJECT_DIR"]) +header_path = project_dir / "src/helpers/mqtt/WebPanelCert.h" + +if header_path.exists(): + existing = header_path.read_text() + if "AUTO-GENERATED BY generate_web_panel_cert.py" in existing and "kServerKeyPem" in existing: + Return() + +with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + key_path = tmp_path / "web-panel-key.pem" + cert_path = tmp_path / "web-panel-cert.pem" + + try: + subprocess.run( + ["openssl", "ecparam", "-name", "prime256v1", "-genkey", "-noout", "-out", str(key_path)], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + subprocess.run( + [ + "openssl", + "req", + "-new", + "-x509", + "-key", + str(key_path), + "-out", + str(cert_path), + "-days", + "3650", + "-subj", + "/CN=meshcore-local", + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + except FileNotFoundError as exc: + raise RuntimeError("openssl is required to generate the web panel certificate header") from exc + except subprocess.CalledProcessError as exc: + raise RuntimeError( + f"openssl failed while generating the web panel certificate header: {exc.stderr.strip()}" + ) from exc + + cert_pem = cert_path.read_text() + key_pem = key_path.read_text() + +header_path.parent.mkdir(parents=True, exist_ok=True) +header_path.write_text( + "#pragma once\n\n" + "// AUTO-GENERATED BY generate_web_panel_cert.py. DO NOT COMMIT.\n\n" + "namespace mqtt_web_panel_cert {\n\n" + f"{render_pem_array('kServerCertPem', cert_pem)}\n" + f"{render_pem_array('kServerKeyPem', key_pem)}" + "}\n", + encoding="utf-8", +) diff --git a/platformio.ini b/platformio.ini index 5f722e89..fcad6303 100644 --- a/platformio.ini +++ b/platformio.ini @@ -58,7 +58,9 @@ build_src_filter = extends = arduino_base platform = platformio/espressif32@6.11.0 monitor_filters = esp32_exception_decoder -extra_scripts = merge-bin.py +extra_scripts = + pre:arch/esp32/extra_scripts/generate_web_panel_cert.py + merge-bin.py build_flags = ${arduino_base.build_flags} -D ESP32_PLATFORM ; -D ESP32_CPU_FREQ=80 ; change it to your need diff --git a/src/helpers/mqtt/WebPanelCert.h b/src/helpers/mqtt/WebPanelCert.h index 991244e5..82478d25 100644 --- a/src/helpers/mqtt/WebPanelCert.h +++ b/src/helpers/mqtt/WebPanelCert.h @@ -1,25 +1,26 @@ #pragma once +// AUTO-GENERATED BY generate_web_panel_cert.py. DO NOT COMMIT. + namespace mqtt_web_panel_cert { static const char kServerCertPem[] = "-----BEGIN CERTIFICATE-----\n" - "MIIBiDCCAS2gAwIBAgIUYPagAbDjmNMHx+sqAC5qvOnlywIwCgYIKoZIzj0EAwIw\n" - "GTEXMBUGA1UEAwwObWVzaGNvcmUtbG9jYWwwHhcNMjYwNDA5MDE0MjAzWhcNMzYw\n" - "NDA2MDE0MjAzWjAZMRcwFQYDVQQDDA5tZXNoY29yZS1sb2NhbDBZMBMGByqGSM49\n" - "AgEGCCqGSM49AwEHA0IABJWLgQ/4sDJKykqR9a5brDLR9RikwoZ/6n1Od38rbfzV\n" - "ykP2LJIdrxU6zS1gzLk33klycB7AWFL1W7klStdiSpmjUzBRMB0GA1UdDgQWBBSk\n" - "dpP2r+UPriHT+hXLiyFdjkFgmTAfBgNVHSMEGDAWgBSkdpP2r+UPriHT+hXLiyFd\n" - "jkFgmTAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCWS5ZRbON4\n" - "71RrwcogEzGuQTNdkbtkRc+517nZ63ZOawIhAPwSy/0yMyhqAoOMpVnhIb+w995f\n" - "zH4nApL2Q2U0KI2l\n" + "MIIBiDCCAS2gAwIBAgIUFysjM9xosI98WhPpK1doyLOAkgYwCgYIKoZIzj0EAwIw\n" + "GTEXMBUGA1UEAwwObWVzaGNvcmUtbG9jYWwwHhcNMjYwNDA5MDUxODQ4WhcNMzYw\n" + "NDA2MDUxODQ4WjAZMRcwFQYDVQQDDA5tZXNoY29yZS1sb2NhbDBZMBMGByqGSM49\n" + "AgEGCCqGSM49AwEHA0IABBpVegwc/kZDjVtHXnl0iYbD2vdes7nLztlyRDylcfOD\n" + "GoR9hopoc9NernvGJLUFP/+PsjIsfEGmVrmPESnShsWjUzBRMB0GA1UdDgQWBBTi\n" + "oWH5V8BvTj9Kla8ki8SHb2vdSTAfBgNVHSMEGDAWgBTioWH5V8BvTj9Kla8ki8SH\n" + "b2vdSTAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDjNde/RApq\n" + "jx/FYar9+qMMxHeBMrSiOBozTFpcmFr7RgIhAMdVESr6P2urGsxzbYirwURj5H1F\n" + "8eej1APpWRRZvvP0\n" "-----END CERTIFICATE-----\n"; static const char kServerKeyPem[] = "-----BEGIN EC PRIVATE KEY-----\n" - "MHcCAQEEIGX71nLSiA8qUJXuiZRNzaGizNbMqgOxALSPd/HzfzWAoAoGCCqGSM49\n" - "AwEHoUQDQgAElYuBD/iwMkrKSpH1rlusMtH1GKTChn/qfU53fytt/NXKQ/Yskh2v\n" - "FTrNLWDMuTfeSXJwHsBYUvVbuSVK12JKmQ==\n" + "MHcCAQEEIPQxD+WYeE2tQja5AVrOgDUHAKJ7TYfELqU55Npb7mJ1oAoGCCqGSM49\n" + "AwEHoUQDQgAEGlV6DBz+RkONW0deeXSJhsPa916zucvO2XJEPKVx84MahH2Gimhz\n" + "016ue8YktQU//4+yMix8QaZWuY8RKdKGxQ==\n" "-----END EC PRIVATE KEY-----\n"; - }