Skip to content

Add update-modules scripts to maintain the embedded JS modules - #400

Open
PTR-inc wants to merge 3 commits into
Ylianst:masterfrom
PTR-inc:polymodules
Open

PTR-inc wants to merge 3 commits into
Ylianst:masterfrom
PTR-inc:polymodules

Conversation

@PTR-inc

@PTR-inc PTR-inc commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

I was tired of the annoying process of generating the embedded modules in polyfills.c, so made this little script to do it automatically.

Sadly the limit on string literals is still a thing in MSVC, so the memcpy way is still needed in the current version.

There is an option to split the embedded modules into a seperate ILibDuktape_EmbeddedModules.c. This has a few advantages:

  • the polyfills.c file isn't (over)loaded/cluttered with giant b64 strings anymore. It made the file really annoying to edit.
  • By using a deflated const unsigned char array, the whole base64/memcpy part is gone. This makes the startup faster (no more string rebuilt in a buffer), smaller binary (no more base64, so 30% less), and no more string literal limit (the memcpy split is no longer needed).
  • It is more manageable to auto-generate the embedded modules, through a git workflow for example.

The scripts (linux and windows wrappers for the core update-modules.js) can manage both the current style, embed in polyfills, and the proposed split style.

By default it uses the list of embedded modules in the source, but there is a -sync option to add/remove/update the embedded modules depending on the contents of the modules/ dir. At this moment the modules/ dir contains more than needed, but a way could be to (re)move the extra modules, as these are served from meshcentral. A point of discussion how to manage those and keep them in sync between meshcentral/meshagent repo's (github actions maybe?). There should be even less embedded modules then there are now I think.

There are the -add & -remove options to do that to specific modules.

The option -current updates the embedded modules with the modules in modules/. Why I didn't call it -update is a mystery to me now that I'm typing this. But not going to change that now. It is late.

One of the nice things I think is that this script uses the meshagent as a javascript engine! But it can also use node if it's installed.

FIxed: !! Oh and a important issue to be aware of (which I may fix tomorrow, maybe) is that if you use the meshagent for the engine, you need a meshagent with #376 applied, as the js script is larger the 4096 characters. Simple fix is to use node.

14-09-2026: Proper (annoying) CRLF handling between windows/POSIX, agent timebug & readdirsync workaround, added pako to node to keep the zipstreams byte-equal (node uses a chrome fork with different match finder), and a few usage improvements, like naming the parameter '-update' instead of '-current'.

Details
  • 🧠 I used LLMs/AI in this contribution and reviewed all generated content.
    I understand that I am responsible for and able to explain every line of code I submit.
  • 🛠️ I have self-reviewed my code and self-tested it against a MeshCentral server to ensure it works as expected.
  • 🖥️ My change compiles on every platform it affects (Windows / Linux / macOS / FreeBSD), and I have considered
    the impact on platforms and architectures I could not test.
  • 📦 If I changed JavaScript modules under modules/, I re-embedded them so the compiled-in copies in
    microscript/ILibDuktape_Polyfills.c match (the agent runs the embedded copies, not the files on disk).
  • 🤖 I ran the agent self-test where appropriate (see "Self Test" in readme.md).
  • 📄 Documentation updates are included (if applicable), e.g. the .msh options table in readme.md.
  • 🧰 Updates to vendored dependencies (OpenSSL, zlib, ...) are listed and explained.
  • ⚠️ CI passes and is green (Windows / Linux / macOS / FreeBSD builds and CodeQL).

Testing

Tested on linux and windows x86/x64 with the testscripts and a connectivity test to a meshcentral server (desktop/files/terminal/console work)

tools/update-modules(.sh/.ps1) keeps the embedded modules in sync with modules/*.js.

- -current updates every entry whose modules/<name>.js changed, -add and
  -remove work on named entries, -sync follows modules/ exactly, -dryrun
  only reports, -list and -export show or save what is embedded.

- While ILibDuktape_Polyfills.c still carries its addCompressedModule()
  statements, the scripts read and write those in place in the file's own
  format: an inline duk_peval_string_noresult(), or the chunked
  ILibMemory_Allocate/memcpy_s block for entries over 16000 base64
  characters. Every run re-renders the whole table, so an unchanged entry
  produces no diff.

- -striplegacy migrates: the modules move into a generated
  ILibDuktape_EmbeddedModules.c as deflated const unsigned char[] arrays
  plus a name/timestamp/data/length table, #included from the polyfills
  file, and the addCompressedModule() statements are replaced by one call
  to ILibDuktape_Polyfills_EmbeddedModules(ctx). That drops the base64
  layer and the startup duk_peval of one giant string. The migration
  itself is a separate commit.
And while we're at it, update -current to -update
… settings

Under node the tool wrote stamps like 'WedTSepT02T2026T...' (node's
Date.toString() joined with T), which the agent turns into a NaN
ModuleFileDate. Stamps are now rendered as 2026-09-02T15:36:17.000+02:00
on both engines, and taken from the commit that last touched the module
(one git log over modules/), falling back to mtime for files with
uncommitted changes, untracked files, or when git is missing.

That mtime fallback read an hour or two late under a POSIX agent: its
fs.statSync() formats file times with localtime() but appends 'Z', so
a January file edited at 03:04:05 CET was stamped 04:04:05+01:00 while
node wrote 03:04:05+01:00. The tool now writes and stats a probe file
once per run, and when the agent's time is ahead by the timezone offset
it reads mtimes back as local time. A fixed agent, node, or a machine
on UTC gets no correction.

Node's zlib is Chromium's fork and never reproduces the agent's stock
zlib stream (0 of 100 entries), so an entry's bytes depended on which
engine regenerated it. pako, a port of stock zlib, matches all 100; it is
used under node when installed and the launchers offer to npm install it.
The generated file's hex is lower-cased, since the agent emits upper case.

Workarounds for the agent: fs._readdirSync() is called directly, because
the Windows readdirSync wrapper loads 'os' and crashes an agent without
the alloca fix; git is located on PATH by hand and argv[0] repeated, as
the agent's execFile does neither; the -Check exit code travels through a
variable, since the agent's own process.exit() logs itself through the
launcher's catch.

Settings now travel as environment variables instead of JS source, which
removes all quoting: a Windows -Export path pasted into a '...' literal
had its backslashes read as escapes and landed on drive C:. The bootstrap
chdir covers a Windows agent run from Git Bash, which moves to its own
directory at startup.

Also:
- Update is passed through so -Update -Remove x updates the rest.
- Sync implies update; -Check for CI exits 1 when the target would change.
- Add runs count untouched entries; a readdir failure throws rather than
  reading as nothing new.
- Export creates nested directories
- A bare -BinaryPath file name resolves
- Node is used when no agent binary is found.
- The script and its package files move to tools/update-modules/ to keep
  tools/ clean.
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