Skip to content

Improve config file change handling#135

Open
tg73 wants to merge 10 commits into
Rat-OS:v2.1.x-developmentfrom
tg73:topic/devpub/setupwiz-2
Open

Improve config file change handling#135
tg73 wants to merge 10 commits into
Rat-OS:v2.1.x-developmentfrom
tg73:topic/devpub/setupwiz-2

Conversation

@tg73
Copy link
Copy Markdown
Collaborator

@tg73 tg73 commented May 11, 2026

Release Notes

  • New Features

    • Restart the crowsnest service if crowsnest.conf is changed.
  • Bug Fixes

    • Improved preservation of whitespace and inline comments during in-place modifications to ini-format config files.
    • Improved file state synchronization during config file overwrites.
    • Updated Rat Rig VAOC camera device path to /dev/RatOS/rr-vaoc-camera, improving configuration robustness.

Review Change Stack

tg73 added 10 commits March 13, 2026 12:59
…rowsnest.conf

- simplify the logic of writing files under ratos_generated: resetting these files is already handled by macros specifically created for that purpose, don't try to provide a reset story through the setup wizard.
…white space between neighbouring replaced sections
…1]` section

- don't set the `[crowsnest]` section as this too easily stomps on user changes, and the `[crowsnest]` section is not strictly a VAOC-specific concern anyhow
…amera

- use /dev/RatOS/rr-vaoc-camera instead of /dev/video0. This aims to pick the right camera even if the user has an additional camera installed.
…also restart crowsnest before restarting klipper
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

This PR enables scoped service restarts during configuration updates. It refactors klipperRestart to accept an options object with optional service-specific restart lists, adds a serviceRestart helper, and updates configuration generation to restart only the crowsnest service when its configuration changes. INI section replacement logic now preserves formatting and inline comments. The rat-rig template is updated to use unconditional overwrite flags and simplified boilerplate generation.

Changes

Scoped Service Restart and Configuration Updates

Layer / File(s) Summary
Klipper Restart API Refactor
src/server/helpers/klipper.ts
Refactors klipperRestart from (force = false) boolean to options-object API: { force?: boolean; servicesToRestart?: PermittedServices[]; abortOnServiceRestartFailure?: boolean }. Adds PermittedServices type and new exported serviceRestart(service) helper that POSTs to Moonraker /machine/services/restart endpoint.
INI Section Formatting Preservation
src/server/helpers/file-operations.ts
replaceOrAddIniSections now preserves per-key inline trailing comments and end-of-section decorations when section body changes, by mapping comment suffixes from original lines and reattaching them after normalization.
Configuration Router Scoped Restart
src/server/routers/printer.ts
regenerateConfiguration and saveConfiguration compute servicesToRestart to restart only crowsnest when its config is created/overwritten; otherwise pass no services to klipperRestart. Syncs last-saved tracking files and fixes backup timestamp parsing to use current file extension.
Instrumentation Service Restart Logic
src/instrumentation.ts
register now checks whether crowsnest configuration changed, sets servicesToRestart to ['crowsnest'] when applicable, and calls klipperRestart({ servicesToRestart }) instead of no-argument form.
Rat-Rig Template Configuration Updates
src/templates/toolhead-alignment-systems/rat-rig-vaoc.ts
Sets overwrite: true unconditionally for crowsnest and generated config files. Refactors boilerplate helpers to check file existence internally and drop forceDefault parameter, preserving existing files instead of regenerating them. Updates camera device path from /dev/video0 to /dev/RatOS/rr-vaoc-camera.
INI Formatting Test Coverage
src/__tests__/server.test.ts
Adds Vitest cases verifying blank-line and inline-comment preservation in replaceOrAddIniSections, including scenarios with trailing end-of-file comments.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 With whiskers twitched and hop so light,
We restart just the crowsnest right,
Comments preserved, not a trace is lost,
Scoped service restarts, precision on cost!
Hop, hop, hop—configuration's new flight! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve config file change handling' accurately summarizes the main changes across the pull request, which focus on refactoring how configuration files are processed, formatted, and restarted when changed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (6)
src/server/helpers/klipper.ts (1)

119-134: ⚡ Quick win

Consider validating the Moonraker service restart response.

The serviceRestart function currently treats any successful fetch as success, but doesn't verify that Moonraker accepted the restart request or that the service actually exists. If Moonraker returns an error response (e.g., HTTP 4xx/5xx or a JSON error payload), this function will still return true.

This could lead to silent failures where the configurator believes a service restarted successfully, but Moonraker rejected the request.

🔍 Proposed validation enhancement
 export const serviceRestart = async (service: PermittedServices) => {
 	getLogger().info(`Attempting to restart service ${service} via Moonraker...`);
 	try {
-		await fetch('http://localhost:7125/machine/services/restart', {
+		const response = await fetch('http://localhost:7125/machine/services/restart', {
 			method: 'POST',
 			headers: { 'Content-Type': 'application/json' },
 			body: JSON.stringify({ service }),
 		});
+		if (!response.ok) {
+			getLogger().error(`Moonraker returned error status ${response.status} for service ${service}`);
+			return false;
+		}
 		getLogger().info(`${service} restart command sent successfully.`);
 		return true;
 	} catch (e) {
 		getLogger().error(`Failed to send ${service} restart command: ${getErrorMessage(e)}`);
 	}
 
 	return false;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/helpers/klipper.ts` around lines 119 - 134, The serviceRestart
function currently treats any successful fetch as success; update serviceRestart
to validate the Moonraker HTTP response: await the fetch response, check
response.ok and if not ok log the status and response text (using
getLogger().error and getErrorMessage for exceptions) and return false; if
response.ok parse JSON and detect an error payload (e.g., presence of an "error"
field or missing success indicator) and log details and return false; only
return true when response.ok and the parsed JSON indicates the restart was
accepted. Use the existing symbols serviceRestart, PermittedServices, getLogger,
getErrorMessage and the same endpoint URL to locate the code to change.
src/templates/toolhead-alignment-systems/rat-rig-vaoc.ts (1)

63-80: ⚡ Quick win

Wrap variable declarations in blocks to prevent scope leakage.

Biome correctly flags that variable declarations in switch cases can be accessed by other cases. While this currently works because only one case is executed, wrapping the declarations in blocks is a defensive best practice that prevents future refactoring errors.

🛡️ Proposed fix to scope variables correctly
 	case 'config-helpers':
+		{
 		// If overwrite is requested (or overwrite details are not available), reset dc-endstop.cfg.
 		// Otherwise, use the existing content or the default content if the file does not exist.
 		const dcEndstopCfgFileName = 'ratos_generated/dc-endstop.cfg';
 		const dcEndstopContent = getDcEndstopConfigurationFileContent(dcEndstopCfgFileName);
 		ctx.extrasGenerator.addFileToRender({
 			fileName: dcEndstopCfgFileName,
 			content: dcEndstopContent,
 			overwrite: true,
 		});
 
 		// Ditto, for adjust-y-max.cfg.
 		const adjustYMaxCfgFileName = 'ratos_generated/adjust-y-max.cfg';
 		const adjustYMaxContent = getAdjustYMaxConfigurationFileContent(adjustYMaxCfgFileName);
 		ctx.extrasGenerator.addFileToRender({
 			fileName: adjustYMaxCfgFileName,
 			content: adjustYMaxContent,
 			overwrite: true,
 		});
 
 		return `
 ########################################
 # Configuration Helpers for Rat Rig VAOC
 ########################################
 #
 # These includes must come after any user-defined stepper sections
 # to ensure that overrides work correctly.
 #
 [include ratos_generated/dc-endstop.cfg]   # Managed by CONFIGURE_DC_ENDSTOP macro
 [include ratos_generated/adjust-y-max.cfg] # Managed by INCREASE_Y_MAX macro
 `;
+		}
 	default:
 		throw new Error(`Unknown purpose '${ctx.section}' for template rendering.`);

As per coding guidelines from static analysis tools.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/templates/toolhead-alignment-systems/rat-rig-vaoc.ts` around lines 63 -
80, The variables dcEndstopCfgFileName, dcEndstopContent and
adjustYMaxCfgFileName, adjustYMaxContent are declared directly in a switch case
and can leak scope; wrap each case's related declarations and the calls to
ctx.extrasGenerator.addFileToRender inside their own block { ... } so the names
are block-scoped and cannot be accessed from other cases (i.e., enclose the
dc-endstop.cfg declarations/call and the adjust-y-max.cfg declarations/call each
in their own braces).
src/server/routers/printer.ts (1)

1174-1179: Document the restart scoping design and plan for service config extensibility.

The code only restarts crowsnest when crowsnest.conf changes, which is correct for the current implementation where only crowsnest.conf is generated. However, the PermittedServices type includes other services (e.g., KlipperScreen, sonar) that may require similar handling if their config files are added in the future.

Consider adding a comment documenting why only crowsnest is currently restarted, and note that this logic (which appears in multiple places) will need to be extended if other service-specific config files are introduced.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/routers/printer.ts` around lines 1174 - 1179, Add an explanatory
comment above the servicesToRestart computation describing that only 'crowsnest'
is restarted because currently only 'crowsnest.conf' is generated, and that
PermittedServices includes other services (e.g., KlipperScreen, sonar) which
will require extending this conditional when their config files are introduced;
reference the servicesToRestart variable, the PermittedServices type, the
fileName check for 'crowsnest.conf', and the klipperRestart call so future
maintainers know to update all similar occurrences when adding service-specific
config handling.
src/server/helpers/file-operations.ts (3)

333-357: 💤 Low value

Hoist preserveTrailingInlineComments out of the section loop.

preserveTrailingInlineComments is redeclared on every section that needs replacement (it's inside the for (let i = 0; i < sections.length; i++) body via the else branch at line 332). Hoisting it to module/file scope (or just outside the loop) avoids the repeated closure allocation and makes the helper independently testable. Behavior would be unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/helpers/file-operations.ts` around lines 333 - 357, The helper
function preserveTrailingInlineComments is currently declared inside the section
replacement loop causing repeated allocations; move its declaration out of the
for (let i = 0; i < sections.length; i++) loop to module/file scope or at least
immediately above the loop so it is created once and can be unit-tested
independently; ensure you preserve its signature (originalBody: string,
updatedBody: string) and behavior, and if you need to test it export or attach
it so tests can import it without changing its internal logic.

360-368: 💤 Low value

Verify intentional loss of new-body trailing whitespace.

newBodyWithPreservedComments.replace(/\n+$/, '') strips all trailing newlines from the caller-supplied body before re-applying the original section's trailingDecoration. The effect is that any trailing blank-line/comment structure provided by the caller in update.body is discarded in favor of whatever already existed at the end of the original section.

This is fine for the documented goal (preserve formatting), but it means the caller cannot intentionally add or remove a trailing separator via body. If that's by design, a one-line comment explaining the priority (original decoration wins over new body's trailing whitespace) would prevent future confusion. The existing tests (idempotent CRLF / preserves blank-line-between-sections) cover the happy paths, but not the "caller adds extra trailing blank lines" case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/helpers/file-operations.ts` around lines 360 - 368, The code
intentionally strips trailing newlines from the caller-provided body
(newBodyWithPreservedComments.replace(/\n+$/, '')) and re-applies the original
section's trailingDecoration (derived from originalBodyWithTrailing), which
causes any caller-added trailing blank lines/comments in update.body to be
ignored; add a concise one-line comment above this block (near
newBodyWithPreservedComments, trailingDecoration and originalBodyWithTrailing)
stating that the original section's trailing decoration takes precedence over
caller-supplied trailing whitespace (i.e., intentional behavior) so future
readers/tests understand that caller cannot override the section trailing
separator.

334-343: 💤 Low value

First-occurrence-wins for duplicate keys in original body — confirm this is intentional.

commentByKey only records the first inline comment seen for any given key (line 340: if (!commentByKey.has(key))). If the original section legitimately contains the same key on multiple lines (e.g., repeated config entries) with different trailing comments, only the first comment is preserved when remapping to the new body. For typical Klipper/INI sections this is unlikely to matter, but worth a confirmation given the new behavior aims to preserve user-authored comments faithfully.

If duplicate keys are expected to be rare or invalid, a brief inline comment documenting this assumption would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/helpers/file-operations.ts` around lines 334 - 343, The code that
builds commentByKey from originalBody currently ignores later inline comments
for duplicate keys (see commentByKey, originalBody, and the if
(!commentByKey.has(key)) guard) — either make this behavior explicit or change
it: if you want first-occurrence-wins, add a concise inline comment above the
guard explaining the assumption that duplicate keys are invalid/rare and first
comment is authoritative; if you want last-occurrence-wins (to preserve the most
recent inline comment), remove the if-check so commentByKey.set(key,
commentSuffix) always overwrites previous entries; update tests if necessary to
reflect the chosen behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/server/helpers/file-operations.ts`:
- Around line 333-357: The helper function preserveTrailingInlineComments is
currently declared inside the section replacement loop causing repeated
allocations; move its declaration out of the for (let i = 0; i <
sections.length; i++) loop to module/file scope or at least immediately above
the loop so it is created once and can be unit-tested independently; ensure you
preserve its signature (originalBody: string, updatedBody: string) and behavior,
and if you need to test it export or attach it so tests can import it without
changing its internal logic.
- Around line 360-368: The code intentionally strips trailing newlines from the
caller-provided body (newBodyWithPreservedComments.replace(/\n+$/, '')) and
re-applies the original section's trailingDecoration (derived from
originalBodyWithTrailing), which causes any caller-added trailing blank
lines/comments in update.body to be ignored; add a concise one-line comment
above this block (near newBodyWithPreservedComments, trailingDecoration and
originalBodyWithTrailing) stating that the original section's trailing
decoration takes precedence over caller-supplied trailing whitespace (i.e.,
intentional behavior) so future readers/tests understand that caller cannot
override the section trailing separator.
- Around line 334-343: The code that builds commentByKey from originalBody
currently ignores later inline comments for duplicate keys (see commentByKey,
originalBody, and the if (!commentByKey.has(key)) guard) — either make this
behavior explicit or change it: if you want first-occurrence-wins, add a concise
inline comment above the guard explaining the assumption that duplicate keys are
invalid/rare and first comment is authoritative; if you want
last-occurrence-wins (to preserve the most recent inline comment), remove the
if-check so commentByKey.set(key, commentSuffix) always overwrites previous
entries; update tests if necessary to reflect the chosen behavior.

In `@src/server/helpers/klipper.ts`:
- Around line 119-134: The serviceRestart function currently treats any
successful fetch as success; update serviceRestart to validate the Moonraker
HTTP response: await the fetch response, check response.ok and if not ok log the
status and response text (using getLogger().error and getErrorMessage for
exceptions) and return false; if response.ok parse JSON and detect an error
payload (e.g., presence of an "error" field or missing success indicator) and
log details and return false; only return true when response.ok and the parsed
JSON indicates the restart was accepted. Use the existing symbols
serviceRestart, PermittedServices, getLogger, getErrorMessage and the same
endpoint URL to locate the code to change.

In `@src/server/routers/printer.ts`:
- Around line 1174-1179: Add an explanatory comment above the servicesToRestart
computation describing that only 'crowsnest' is restarted because currently only
'crowsnest.conf' is generated, and that PermittedServices includes other
services (e.g., KlipperScreen, sonar) which will require extending this
conditional when their config files are introduced; reference the
servicesToRestart variable, the PermittedServices type, the fileName check for
'crowsnest.conf', and the klipperRestart call so future maintainers know to
update all similar occurrences when adding service-specific config handling.

In `@src/templates/toolhead-alignment-systems/rat-rig-vaoc.ts`:
- Around line 63-80: The variables dcEndstopCfgFileName, dcEndstopContent and
adjustYMaxCfgFileName, adjustYMaxContent are declared directly in a switch case
and can leak scope; wrap each case's related declarations and the calls to
ctx.extrasGenerator.addFileToRender inside their own block { ... } so the names
are block-scoped and cannot be accessed from other cases (i.e., enclose the
dc-endstop.cfg declarations/call and the adjust-y-max.cfg declarations/call each
in their own braces).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e122e982-8a09-48f7-b8e2-25003f877912

📥 Commits

Reviewing files that changed from the base of the PR and between 1c77d17 and a882af7.

📒 Files selected for processing (6)
  • src/__tests__/server.test.ts
  • src/instrumentation.ts
  • src/server/helpers/file-operations.ts
  • src/server/helpers/klipper.ts
  • src/server/routers/printer.ts
  • src/templates/toolhead-alignment-systems/rat-rig-vaoc.ts

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