Skip to content

Add Retire Nexusp Backend for K1C 2025 - #22

Open
arlophoenix wants to merge 4 commits into
C0DEbrained:mainfrom
arlophoenix:feat/k1c-2025-retire-nexusp
Open

Add Retire Nexusp Backend for K1C 2025#22
arlophoenix wants to merge 4 commits into
C0DEbrained:mainfrom
arlophoenix:feat/k1c-2025-retire-nexusp

Conversation

@arlophoenix

Copy link
Copy Markdown

Stacks on #17.

The 2025 runs two Moonrakers against one Klipper: Creality's forked nexusp on :7125 for the touchscreen, and this script's real Moonraker on :7126. Querying the wrong one does not fail, it answers — curl :7125/server/spoolman/status returns Method 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 :7125 command from a Klipper forum hits it.

This adds an opt-in, default-off option that retires nexusp and puts the real Moonraker on :7125, the port the rest of the Klipper ecosystem assumes. The touchscreen is never patched and could not be — vectorp hardcodes http://127.0.0.1:7125 and 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, and server.history.count, which the screen calls the moment it connects. A small Moonraker component implements both. Its behaviour was measured against the real nexusp before 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_ex is websocket-only and 404s over HTTP, order arrives as a comma-separated triple, since/before are accepted and ignored, and only .gcode is listed even though Moonraker itself treats .g and .gco as 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_time order: Moonraker pages history with ORDER 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 ran nginx -s reload with 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 against history.history_table. All three are checked when the component loads and fail by name, because install_moonraker_nginx runs git checkout master; git pull and 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/info rather 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 -n clean on every changed shell file
  • Config round-trips verified against the real moonraker.conf and nginx.conf this repo ships
  • Manual test pass (see checklist below)

There is no CI in this repository, so nothing runs the above automatically.

Manual test pass

  • Retire on an idle printer — the screen reconnects on its own, and the file browser pages, sorts, searches, deletes and starts a print
  • Upload a fresh gcode after retiring — a thumbnail appears on the screen. This path has never run: every file in the reference corpus already had thumbnails written by nexusp beforehand
  • Run "Install Moonraker and Nginx" on a retired printer — the port stays 7125, [creality_compat] survives, and the screen keeps working
  • Run "Remove Moonraker and Nginx" on a retired printer — it refuses and points at Restore
  • Run a long print end to end on the new arrangement
  • Cold-boot — Moonraker comes up on 7125 and the screen reconnects without help
  • Restore — the screen returns to nexusp and its history includes prints made while retired

Not addressed

  • Thumbnail generation needs Pillow, which is not in the Moonraker this repo ships. moonraker.tar.gz is a Python 3.8 venv with no PIL, and install_moonraker_nginx never 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.info and printer.objects.list answer -32601 for 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_endpoint returns 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 answering printer.info forever is worse than a four-second gap.
  • server.history.debug.job and server.debug.status are not shimmed. Neither is screen-facing; each was logged exactly once, at connect, with nothing visibly broken. The second emits malformed JSON.
  • The "not applicable" marker in the Information menu sits three columns off the tick rows. info_line pads 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.

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.
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