mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-08-27 22:34:14 +00:00
Introduce a web configuration portal for easier node management and provisioning without serial CLI. Enhance MQTT functionality with improved IATA code validation, dynamic slot management, and background NTP synchronization. Update web UI elements for better user experience and security notes regarding open AP usage.
107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# Pre-build script: gzip webui/index.html into a PROGMEM C header so the
|
|
# webconfig portal can serve the page straight from flash with
|
|
# Content-Encoding: gzip. The generated header is .gitignored; this script
|
|
# regenerates it whenever the source page (or this script) content changes.
|
|
#
|
|
# Freshness is decided by a content hash of the build inputs (not timestamps):
|
|
# a generated header written with a skewed/future mtime would otherwise mask a
|
|
# later source edit and embed stale UI. gzip output is deterministic (mtime=0),
|
|
# so an unchanged source produces an unchanged header.
|
|
#
|
|
# The generator is idempotent and cheap: when the inputs are unchanged it only
|
|
# hashes two small files and returns, so running it from esp32_base on every
|
|
# ESP32 build is negligible even for targets that don't compile the portal.
|
|
#
|
|
# Output: src/helpers/esp32/WebConfigHtml.h
|
|
# WEBCONFIG_HTML_GZ[] - gzipped page (PROGMEM)
|
|
# WEBCONFIG_HTML_GZ_LEN - byte length
|
|
# WEBCONFIG_HTML_ETAG - quoted strong ETag (sha256 prefix of the gz body)
|
|
|
|
import gzip
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
|
|
Import("env") # noqa: F821
|
|
|
|
SOURCE = os.path.join("webui", "index.html")
|
|
OUTPUT = os.path.join("src", "helpers", "esp32", "WebConfigHtml.h")
|
|
# __file__ is not defined inside PIO/SCons-executed extra_scripts
|
|
SCRIPT = os.path.join("scripts", "generate_webconfig_html.py")
|
|
HASH_MARKER = "// build-inputs-sha256: "
|
|
|
|
|
|
def status(msg):
|
|
sys.stderr.write("WebConfig HTML: %s\n" % msg)
|
|
|
|
|
|
def content_hash():
|
|
# Hash the source page and this generator so any change to either forces a
|
|
# regenerate, independent of file timestamps.
|
|
h = hashlib.sha256()
|
|
with open(SOURCE, "rb") as f:
|
|
h.update(f.read())
|
|
if os.path.isfile(SCRIPT):
|
|
h.update(b"\0")
|
|
with open(SCRIPT, "rb") as f:
|
|
h.update(f.read())
|
|
return h.hexdigest()
|
|
|
|
|
|
def stored_hash():
|
|
try:
|
|
with open(OUTPUT, "r") as f:
|
|
for line in f:
|
|
if line.startswith(HASH_MARKER):
|
|
return line[len(HASH_MARKER):].strip()
|
|
if line.startswith("#"): # reached the C preprocessor lines
|
|
break
|
|
except OSError:
|
|
return None
|
|
return None
|
|
|
|
|
|
def main():
|
|
if not os.path.isfile(SOURCE):
|
|
status("ERROR: %s not found" % SOURCE)
|
|
sys.exit(2)
|
|
|
|
src_hash = content_hash()
|
|
if os.path.isfile(OUTPUT) and stored_hash() == src_hash:
|
|
return
|
|
|
|
with open(SOURCE, "rb") as f:
|
|
raw = f.read()
|
|
|
|
# mtime=0 keeps the gzip output (and therefore the ETag) deterministic
|
|
gz = gzip.compress(raw, compresslevel=9, mtime=0)
|
|
etag = hashlib.sha256(gz).hexdigest()[:16]
|
|
|
|
lines = []
|
|
lines.append("// Auto-generated by scripts/generate_webconfig_html.py from %s" % SOURCE.replace(os.sep, "/"))
|
|
lines.append("// DO NOT EDIT - edit webui/index.html instead.")
|
|
lines.append("%s%s" % (HASH_MARKER, src_hash))
|
|
lines.append("#pragma once")
|
|
lines.append("#include <stdint.h>")
|
|
lines.append("#include <pgmspace.h>")
|
|
lines.append("")
|
|
lines.append("const uint32_t WEBCONFIG_HTML_GZ_LEN = %d;" % len(gz))
|
|
lines.append('const char WEBCONFIG_HTML_ETAG[] = "\\"%s\\"";' % etag)
|
|
lines.append("const uint8_t WEBCONFIG_HTML_GZ[] PROGMEM = {")
|
|
for i in range(0, len(gz), 16):
|
|
chunk = gz[i:i + 16]
|
|
lines.append(" " + "".join("0x%02x," % b for b in chunk))
|
|
lines.append("};")
|
|
lines.append("")
|
|
|
|
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
|
|
with open(OUTPUT, "w") as f:
|
|
f.write("\n".join(lines))
|
|
|
|
status("%s -> %s (%d bytes raw, %d bytes gzipped)" % (SOURCE, OUTPUT, len(raw), len(gz)))
|
|
|
|
|
|
main()
|