Add Retire Nexusp Backend for K1C 2025 - #22
Open
arlophoenix wants to merge 4 commits into
Open
Conversation
Creality's stock 2025 firmware runs a telemetry agent, alchemistp, that uploads printer configuration and logs to Creality, plus WebRTC and AI daemons that go inert once the Built-in Camera Fix takes /dev/video0. Adds a Customize-menu option to disable them and a matching restore, in the shape of the existing Creality Web Interface pair. The rename uses a "disabled." prefix rather than a ".disabled" suffix: rcK iterates over CS??* on this model, which still matches a suffixed name, so a suffix would be a silent no-op. onyxp, thirteenthp and solusp are gated on the Built-in Camera Fix specifically, not on any camera fix - USB Camera Support filters out /dev/video0, so a USB-only install leaves those daemons serving a working built-in camera. klipper, nexusp, quintusp and vectorp are in an enforced refuse-list carrying both the S and CS name forms. Renames are guarded so a failure cannot abort the helper mid-sequence, neither direction overwrites an existing file, the print-state check fails safe to a confirmation prompt on anything it cannot parse, and the completion message reports what actually changed.
The 2025 runs two Moonrakers against one Klipper: Creality's forked nexusp on :7125 for the touchscreen and the helper's real one on :7126. Querying the wrong port does not fail, it answers - plausibly and wrongly, which has cost real debugging time for Spoolman and for timelapse, and which every user hits the first time they paste a :7125 command from a Klipper forum. This adds an opt-in option that retires nexusp and puts the real Moonraker on :7125, the port the rest of the Klipper ecosystem assumes. The screen is never patched - vectorp hardcodes http://127.0.0.1:7125 and cannot be patched anyway, so what answers there becomes ours. What is load-bearing is not the daemon but two JSON-RPC methods the screen calls that stock Moonraker lacks: server.files.get_directory_ex and server.history.count. creality_compat.py implements them, with 72 offline tests recording behaviour measured against the real nexusp before it was switched off - measurements nobody can re-derive once it is disabled. The print histories are merged first, in both directions: forward at retirement so the screen does not lose everything printed before the helper was installed, and backward at restore so it does not lose everything printed while nexusp was retired. Also fixes S50nginx's reload path, which ran nginx -s reload with no -c and so silently left the old config live.
Fixes found by a seven-specialist review of the previous commit, three of them verified by execution rather than inspection. The helper died mid-retirement. nexusp_merge_history re-enabled errexit before returning non-zero, and because helper.sh sources every script into one shell under a global set -e, that killed the whole helper at the call site - past the guard meant to catch it, with both daemons stopped and no menu to return to. Verified failing in bash, sh, dash and zsh. The history merge lost prints and mis-ordered the rest. Duplicate matching collapsed several source rows onto one target row, so a cancelled print and its retry became two duplicates and one real record vanished; matching is now one-to-one, nearest first. And Moonraker pages history with ORDER BY job_id, not start_time, so appended older prints came back presented as the newest - rows are now renumbered into start_time order. The screen could be left with nothing answering it. Step 10 checked only that port 7125 replied, but Moonraker swallows optional-component load failures and keeps serving, so a broken shim reported success with a dead file browser. restore_nexusp moved the port before the rename that could fail, and remove_moonraker_nginx had no retired-box guard at all. get_directory_ex dropped disk_usage, which is present in all ten golden nexusp captures and is where the screen reads its free-space figure. The .thumbs reserved-path trick is gone. It suppressed phantom notifications by making thumbnails permanently undeletable through Fluidd and taxing every Moonraker listing; renders are capped per request instead. Adds a shell test suite for the option (20 cases) and takes the Python suites to 119. Merge now confirms before applying, validates the schema it writes into, and refuses on an instance_id mismatch.
A cross-model pass over the previous commit found two regressions it had introduced and several gaps neither the specialists nor the red team saw. Renumbering could not handle non-positive job ids. SQLite's INTEGER PRIMARY KEY is a signed rowid alias, so ids like [-2, -1] staged into [0, 1] and the second pass collided with itself: UNIQUE constraint failed, the transaction rolled back, and a valid database became unmergeable with the retirement aborting at the point both daemons are stopped. Staging now offsets by max(current_max, N), which is disjoint from the final range by construction. Making two helpers report write failures turned every unguarded caller into an abort under the global errexit - including two inside nexusp_rollback_retire itself, so a failed rollback could exit halfway and leave precisely the dead-port state it exists to prevent. Rollback is now unconditionally best-effort and reports what the printer actually looks like afterwards. Restore announced success without checking that nexusp started. The init script's status is discarded by design, so a failed start left nothing on port 7125 - Moonraker having already moved off it - under a success message. It now polls, and says how to recover. Prompts no longer kill the helper on EOF: read returns non-zero on a closed stdin and errexit did the rest, mid-retirement in the worst case. A signal handler now rolls back on SIGINT/SIGTERM/SIGHUP, so a dropped SSH session during the merge prompt no longer leaves nexusp disabled and the port unmoved. The merge also verifies its own backup (integrity check plus row count, then fsync) and refuses to apply a plan the databases have moved out from under. 147 tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacks on #17.
The 2025 runs two Moonrakers against one Klipper: Creality's forked
nexuspon:7125for the touchscreen, and this script's real Moonraker on:7126. Querying the wrong one does not fail, it answers —curl :7125/server/spoolman/statusreturnsMethod not found, which reads as "Spoolman was never connected on this printer" and is wrong. That has cost real debugging time twice on the reference unit, and every user who pastes a:7125command from a Klipper forum hits it.This adds an opt-in, default-off option that retires
nexuspand puts the real Moonraker on:7125, the port the rest of the Klipper ecosystem assumes. The touchscreen is never patched and could not be —vectorphardcodeshttp://127.0.0.1:7125and is a symlink into tmpfs regenerated at boot from an encrypted blob. What we change is which process answers there.The daemon is not what is load-bearing. Two JSON-RPC methods are:
server.files.get_directory_ex, which drives the file browser's paging, sorting and search, andserver.history.count, which the screen calls the moment it connects. A small Moonraker component implements both. Its behaviour was measured against the realnexuspbefore it was switched off, and once a user retires theirs those measurements cannot be re-derived without reviving it — so the tests ship beside the component as the executable record. Several of the rules are unguessable:get_directory_exis websocket-only and 404s over HTTP,orderarrives as a comma-separated triple,since/beforeare accepted and ignored, and only.gcodeis listed even though Moonraker itself treats.gand.gcoas gcode.The print histories are merged in both directions, because the two daemons keep separate databases. Forward at retirement, so the screen does not lose everything printed before this script was installed — on the reference unit that was 22 of 42 records existing in exactly one place. Backward at restore, so it does not lose everything printed while retired. Merged rows are renumbered into
start_timeorder: Moonraker pages history withORDER BY job_id, so appending older prints without renumbering presents them as the newest.This also corrects two comments in #17's file whose premise this PR overturns, and fixes
files/services/S50nginx, whose reload path rannginx -s reloadwith no-c— so it opened a config that does not exist on this board, failed, and left the old config live while reporting nothing.Reviewers should know the component reaches into three Moonraker internals:
file_manager._list_directory,file_manager._convert_request_path, and raw SQL againsthistory.history_table. All three are checked when the component loads and fail by name, becauseinstall_moonraker_nginxrunsgit checkout master; git pulland every user's Moonraker is therefore a moving target. Note also that Moonraker swallows optional-component load failures and keeps serving, so the option verifies the component appears in/server/inforather than trusting that the port answers.Test plan
python3 -m pytest -q— 147 tests, no printer or Moonraker needed (122 component and merge, 25 shell option)bash -nclean on every changed shell filemoonraker.confandnginx.confthis repo shipsThere is no CI in this repository, so nothing runs the above automatically.
Manual test pass
nexuspbeforehand[creality_compat]survives, and the screen keeps workingnexuspand its history includes prints made while retiredNot addressed
moonraker.tar.gzis a Python 3.8 venv with no PIL, andinstall_moonraker_nginxnever pip-installs. The option offers to install it and explains that doing so also repairs Moonraker's own embedded-thumbnail parsing for every user, retired or not. Without it, the disk-union listing still works — that is the part that fixed 66 blank thumbnails on the reference unit.printer.infoandprinter.objects.listanswer-32601for about four seconds after a cold boot, while the screen polls. They are genuine Moonraker methods registered dynamically when Klipper connects. Deliberately not shimmed:register_endpointreturns early for an already-registered path when the incoming registration is remote, and Klipper's are remote, so a static stub would not collide with Klipper's later registration — it would silently win it for the life of the process. A stub answeringprinter.infoforever is worse than a four-second gap.server.history.debug.jobandserver.debug.statusare not shimmed. Neither is screen-facing; each was logged exactly once, at connect, with nothing visibly broken. The second emits malformed JSON.info_linepads on${#status}, which counts ANSI escape bytes; Add Disable Creality Stock Services for K1C 2025 #17's existing tri-state marker has the same property. Fixing it means changing shared padding used by every model's menu, which does not belong here.