Skip to content

Hybrid RTEMS5 IOC support (ibek+msi runtime generation, NFS layout, autosave)#58

Merged
gilesknap merged 41 commits into
mainfrom
shutdown-May-2026
Jun 15, 2026
Merged

Hybrid RTEMS5 IOC support (ibek+msi runtime generation, NFS layout, autosave)#58
gilesknap merged 41 commits into
mainfrom
shutdown-May-2026

Conversation

@gilesknap

@gilesknap gilesknap commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Adds first-class support for hybrid RTEMS5 IOCs driven by rtems-proxy: legacy cross-compiled IOC binaries booted with an ibek/msi-generated runtime, laid out on NFS, and deployable from a Linux devcontainer.

Highlights

  • --hybrid flag automates ibek + msi runtime generation; --instance flag makes the start command devcontainer-friendly.
  • New src/rtems_proxy/hybrid.py; IOC-name resolution fixes for legacy-built RTEMS IOCs; generic ioc/ioc.boot binary names.
  • NFS root laid out as runtime/ + ioc/ subfolders; RUNTIME_DIR set so dbLoadRecords resolves paths; filesystem-copy step for stale-export safety.
  • Autosave generation added to rtems-proxy with /epics/autosave access fixes.
  • Permissions: destination folders manage their own umask; dbd files readable to the crate; ibek-defs writeable in-cluster.

Docs & skills

  • New worked example (docs/example-bl19i-va-ioc-01.md), docs/hybrid.md, docs/overview.md.
  • Expanded rtems-hybrid-debug skill (Makefile foot-guns, msi/regen recipe, ibek shared-template column-misalignment foot-gun).

Note for reviewers

Two scratch artifacts are included and may warrant removal before merge: vacuumSpace-fix.patch and vacuumSpace.ibek.support.yaml.fixed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added RTEMS “hybrid” IOC workflow with --hybrid and --instance to generate and stage runtime artifacts from instance values.yaml
    • Instance environment variables are auto-loaded from values.yaml
  • Documentation
    • Added hybrid workflow documentation, worked conversion example, and an extended end-to-end debugging guide; updated README to point to the overview
  • Bug Fixes
    • Improved runtime version tracking on NFS and more resilient RTEMS startup-script handling
  • Chores
    • Updated container/CI configuration and added editor ignore settings
  • Tests
    • Added regression coverage for build vs deployment naming differences

gilesknap and others added 30 commits February 13, 2026 12:39
New hybrid.py module handles the full sequence: linking ibek-support
YAMLs, running ibek generate2, expanding ioc.subst with msi, and
placing all files on NFS/TFTP. The existing in-container path is
unchanged.

Fixes #56

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… testing

Extracts env vars from services repo values.yaml so manual exports are
unnecessary. Also symlinks instance config, creates missing directories,
sources full msi.vars for macro resolution, and refactors dev command to
share the values.yaml parsing logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Legacy-built RTEMS IOCs carry two genuinely different names:
  * the build folder basename (IOC_ORIGINAL_LOCATION.name), which the
    source .boot binary is named after, e.g. bl-va-ioc-01 ->
    BL-VA-IOC-01.boot
  * the deployment/instance name (IOC_NAME), used for the TFTP/NFS/boot
    paths, e.g. bl19i-va-ioc-01

Previously IOC_NAME was derived from the build folder basename, so the
TFTP boot path, NFS mount and rtems-client-name all resolved to the
wrong name (/iocs/bl-va-ioc-01/rtems.ioc.bin).

  * __main__.py: set IOC_NAME from the services instance folder name.
  * globals.py: add IOC_BUILD_NAME (build folder basename) for locating
    source binaries.
  * copy.py, hybrid.py: name the source .boot from IOC_BUILD_NAME.
  * configure.py: correct the NFS mount point (/epics_rtems_root ->
    /epics).
  * tests/test_globals.py: regression test pinning IOC_BUILD_NAME apart
    from IOC_NAME for the bl-va-ioc-01 / bl19i-va-ioc-01 case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The RTEMS crate NFS-mounts the export root at /epics, and the generated
st.cmd references fixed paths within it:

  cd "/epics/ioc"; dbLoadDatabase dbd/ioc.dbd
  STREAM_PROTOCOL_PATH /epics/runtime/protocol/
  set_requestfile_path("/epics", "runtime")          # autosave .req files
  epics-script = /epics/runtime/st.cmd               # from RTEMS_EPICS_SCRIPT

_copy_to_nfs previously placed everything flat in the NFS root, so st.cmd,
ioc.db, dbd/ and protocol/ landed at the wrong paths. Split the copy into:

  runtime/  st.cmd, ioc.db, protocol/ (data/*.proto*), autosave *.req
  ioc/      dbd/

The bulk build-time data/ folder is no longer copied (only its protocol
files are needed at runtime). save_current_version/check_new_version now
read and write rtems_proxy_version.txt under runtime/ to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ibek's st.cmd template renders the database load line as
'dbLoadRecords {{ get_env("RUNTIME_DIR") }}/ioc.db'. get_env has no
default, so with RUNTIME_DIR unset it emitted 'dbLoadRecords /ioc.db' --
the wrong absolute path on the crate.

Pass RUNTIME_DIR=GLOBALS.RUNTIME (/epics/runtime) to the ibek generate2
subprocess so the line renders as 'dbLoadRecords /epics/runtime/ioc.db',
matching where _copy_to_nfs now places ioc.db.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 4 of the worked example now covers pushing the generated runtime
files onto the real NFS export, mirroring the one-off TFTP push: empty
/dls_sw/i19/epics/rtems/bl19i-va-ioc-01 then copy /ioc_nfs in. Emptying
first is called out because the new runtime/+ioc/ layout would otherwise
leave stale flat files (e.g. an st.cmd pointing at the retired
/epics_rtems_root). The split-access caveat (devcontainer has the source
but read-only /dls_sw; workstation has write but no /ioc_nfs) is noted as
a known rough edge.

Also brings the docs in line with the recent code changes:
  * IOC_NAME now comes from the instance folder (bl19i-va-ioc-01); the
    .boot binary is located via IOC_BUILD_NAME (bl-va-ioc-01).
  * NFS artefacts now live under /ioc_nfs/runtime and /ioc_nfs/ioc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generic-IOC project now builds with a fixed PROD name `ioc` (src/Makefile
`PROD_IOC = ioc`, main source renamed to iocMain.cpp), so it emits
bin/RTEMS-beatnik/ioc and ioc.boot regardless of the IOC instance name.

rtems-proxy previously derived the boot image name from the (uppercased) build
folder, e.g. BL-VA-IOC-01.boot, which no longer exists. Look for the generic
ioc / ioc.boot names instead in both the TFTP copy paths (hybrid.py, copy.py),
update the IOC_BUILD_NAME docstring, drop the stale .boot-name test assertion,
and document the generic-IOC Makefile/iocMain.cpp setup in the docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the rtems-hybrid-debug skill with: a "Regenerating & deploying the
runtime" section noting that `msi` must be on PATH (lives at
/epics/epics-base/bin/linux-x86_64/msi, symlinked into .venv/bin) or the
generate step fails before the NFS copy; and the mapped-enum vs template
mismatch that caused the DLS8515 parity asynSetOption to be dropped, so 7E2
gauges silently ran 7N2 and timed out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a complete RTEMS hybrid IOC preparation pipeline (hybrid_prepare()) that generates runtime artifacts from ibek+msi and stages them to NFS/TFTP. Extends globals with new path/identity fields, adds --hybrid/--instance CLI options, updates copy/configure/rsync behavior, upgrades container base images, and adds workflow documentation plus a debug skill document.

Changes

Hybrid Mode Pipeline

Layer / File(s) Summary
Globals extension and project metadata
src/rtems_proxy/globals.py, pyproject.toml, .github/workflows/ci.yml, .github/workflows/_dist.yml
Adds IBEK_DEFS_PATH, IOC_CONFIG_PATH, IOC_BUILD_NAME, IOC_DOMAIN, reload_globals(), and updated RTEMS_EPICS_SCRIPT default; raises requires-python to 3.13, adds Python 3.14 classifier, updates CI matrix to 3.13/3.14.
hybrid.py preparation pipeline
src/rtems_proxy/hybrid.py
Adds hybrid_prepare() orchestrator and all helpers: _link_instance_config, _link_ibek_support_yamls, _run_ibek_generate, _run_msi, _run_ibek_autosave, _run_checked, _copy_to_nfs, _copy_binary_to_tftp; each validates inputs and raises typer.Exit(1) on failure.
copy.py, configure.py, rsync.sh.jinja updates
src/rtems_proxy/copy.py, src/rtems_proxy/configure.py, src/rtems_proxy/rsync.sh.jinja
Moves version file to runtime/ subdir; defaults startup script glob to st.cmd; uses generic ioc/ioc.boot binary names; switches NVM config to enet1; builds nfs_mount from IOC_DOMAIN/IOC_NAME unconditionally; replaces IOC_GROUP with IOC_DOMAIN and adds RTEMS_TFTP_PATH/RTEMS_NFS_PATH exports in the rsync template.
CLI wiring, update-schema, and test
src/rtems_proxy/__main__.py, update-schema, tests/test_globals.py
Adds _load_instance_env() to read values.yaml and inject OS env vars; extends start with --hybrid/--no-hybrid and --instance; updates dev to use the new helper; adds update-schema bash helper for schema generation; adds regression test verifying IOC_BUILD_NAME differs from IOC_NAME.
Dockerfile, devcontainer, submodule, and gitignore
Dockerfile, .devcontainer/devcontainer.json, .gitmodules, .gitignore
Switches developer and runtime stage base images to epics-base-*:7.0.10ec1; creates /epics/ioc, /epics/runtime, /ioc_tftp, /ioc_nfs in developer build and /epics/ibek-defs, /epics/autosave with chmod a+rwx in runtime stage; adds DLS-specific devcontainer bind mounts; adds ibek-runtime-support-dls submodule; extends .gitignore.
Hybrid workflow documentation
README.md, docs/overview.md, docs/hybrid.md, docs/example-bl19i-va-ioc-01.md
Adds proxy concept overview with three deployment flavours, Generic IOC prerequisites and builder2ibek conversion reference, --hybrid step internals and troubleshooting, and a full BL19I worked walkthrough from VxWorks XML through cluster deployment.

rtems-hybrid-debug Skill Document

Layer / File(s) Summary
rtems-hybrid-debug skill documentation
.claude/skills/rtems-hybrid-debug/SKILL.md
Adds debugging recipes: ELF symbol inspection, iocsh registrar checks, NFS layout contract, ibek positional misalignment, group collision hazards, Makefile traps, serial parity mismatch (June 2026), FINS port failure, msi regen flow, zero-PVs triage, and dbd NFS permission fix.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer / CLI
    participant Main as start --hybrid --instance
    participant Env as _load_instance_env()
    participant HP as hybrid_prepare()
    participant ibek as ibek runtime generate2
    participant msi as msi macro expansion
    participant NFS as NFS /epics
    participant TFTP as TFTP /ioc_tftp

    Dev->>Main: rtems-proxy start --hybrid --instance <ioc-path>
    Main->>Env: _load_instance_env(ioc_path)
    Env->>Env: read global/instance values.yaml
    Env->>Env: set IOC_DOMAIN, IOC_NAME, os.environ
    Env-->>Main: (env_vars, domain)
    Main->>Main: reload_globals()
    Main->>HP: hybrid_prepare(instance_path)
    HP->>HP: _link_instance_config() → config/
    HP->>HP: _link_ibek_support_yamls() → *.ibek.support.yaml
    HP->>ibek: _run_ibek_generate() → st.cmd, ioc.subst
    HP->>msi: _run_msi() → ioc.db from ioc.subst
    HP->>ibek: _run_ibek_autosave() → *.req autosave files
    HP->>NFS: _copy_to_nfs() → st.cmd, ioc.db, proto, dbd/, *.req
    HP->>TFTP: _copy_binary_to_tftp() → ioc.boot → RTEMS_BINARY_DEFAULT_NAME
    HP-->>Main: artifacts staged
    Main->>Main: connect + reboot (unless --no-connect)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Automate hybrid RTEMS IOC startup sequence #56: This PR directly implements the automation requested in issue #56 by adding --hybrid/--no-hybrid and --instance CLI options to the start command and introducing hybrid_prepare() to fully automate ibek runtime generation, msi expansion, and NFS/TFTP asset staging.

Poem

🐇 Hippity-hop, the hybrid's alive!
With ibek and msi, the IOC will thrive.
NFS gets st.cmd, TFTP gets the boot,
--instance auto-loads values to suit.
No more manual steps — just one command to run,
This rabbit says: "Hybrid debugging? Done!" 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: adding hybrid RTEMS5 IOC support with ibek runtime generation, NFS layout, and autosave functionality, which matches the PR's core objective.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch shutdown-May-2026

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
Dockerfile (1)

41-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop root runtime by declaring a non-root USER.

The image currently runs as root (Line 41 onward), and static analysis correctly flags this. Add a dedicated unprivileged user and run rtems-proxy under it.

Suggested patch
 FROM ghcr.io/epics-containers/epics-base-runtime:7.0.10ec1 AS runtime
@@
 RUN mkdir -p /epics/ibek-defs /epics/runtime /epics/autosave \
     && chmod a+rwx /epics/ibek-defs /epics/runtime /epics/autosave
+
+RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin rtemsproxy
+USER 10001
🤖 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 `@Dockerfile` around lines 41 - 63, The runtime stage in the Dockerfile
currently runs as root, which is a security risk. After the directory permission
setup (the chmod commands in the RUN instruction that creates /epics/ibek-defs,
/epics/runtime, and /epics/autosave directories), add a new RUN instruction to
create a dedicated non-root user, then add a USER instruction before the
ENTRYPOINT declaration to ensure the rtems-proxy application runs under that
unprivileged user instead of root.

Source: Linters/SAST tools

src/rtems_proxy/configure.py (1)

44-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate IOC_DOMAIN and IOC_NAME before writing epics-nfsmount.

Line 70 now unconditionally builds mount from IOC_DOMAIN/IOC_NAME. If IOC_DOMAIN is unset (or IOC_NAME is default placeholder), the crate gets an invalid NFS path.

Proposed fix
         if not self.dry_run:
             for v in [
                 GLOBALS.RTEMS_IOC_NETMASK,
                 GLOBALS.RTEMS_IOC_GATEWAY,
                 GLOBALS.RTEMS_IOC_IP,
                 GLOBALS.RTEMS_NFS_IP,
                 GLOBALS.RTEMS_TFTP_IP,
             ]:
                 if v is None or v == "":
                     raise ValueError(
                         "RTEMS_IOC_NETMASK, RTEMS_IOC_GATEWAY, RTEMS_IOC_IP, "
                         "RTEMS_NFS_IP, and RTEMS_TFTP_IP must be set"
                     )
+            if not GLOBALS.IOC_DOMAIN:
+                raise ValueError("IOC_DOMAIN must be set")
+            if not GLOBALS.IOC_NAME or GLOBALS.IOC_NAME == "NO_IOC_NAME":
+                raise ValueError("IOC_NAME must be set to a real instance name")

Also applies to: 70-84

🤖 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/rtems_proxy/configure.py` around lines 44 - 56, Add validation for
GLOBALS.IOC_DOMAIN and GLOBALS.IOC_NAME to the existing validation block (in the
if not self.dry_run section where RTEMS_IOC_NETMASK, RTEMS_IOC_GATEWAY,
RTEMS_IOC_IP, RTEMS_NFS_IP, and RTEMS_TFTP_IP are checked). Include these two
variables in the list being validated to ensure they are not None and not empty
strings before they are used to build the NFS mount path in the code that
follows (around line 70-84). Update the ValueError message to include IOC_DOMAIN
and IOC_NAME in the list of required variables.
src/rtems_proxy/copy.py (1)

58-85: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid creating a self-referential st.cmd symlink.

If no startup script is found (Line 59), ioc_script_name is st.cmd; Line 84 then links st.cmd to itself, producing a broken startup script path.

Proposed fix
     if len(sts) == 0:
         ioc_script_name = "st.cmd"
@@
-    ioc_script_path = Path(local_nfs_root) / GLOBALS.RTEMS_SCRIPT_DEFAULT_NAME
-    ioc_script_path.unlink(missing_ok=True)
-    ioc_script_path.symlink_to(Path(local_nfs_root) / ioc_script_name)
+    if ioc_script_name != GLOBALS.RTEMS_SCRIPT_DEFAULT_NAME:
+        ioc_script_path = Path(local_nfs_root) / GLOBALS.RTEMS_SCRIPT_DEFAULT_NAME
+        ioc_script_path.unlink(missing_ok=True)
+        ioc_script_path.symlink_to(Path(local_nfs_root) / ioc_script_name)
🤖 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/rtems_proxy/copy.py` around lines 58 - 85, The code creates a
self-referential symlink when no startup scripts are found. When len(sts) == 0,
ioc_script_name is set to "st.cmd", and then line 85 creates a symlink from
st.cmd to itself via ioc_script_path.symlink_to(). Fix this by adding a
conditional check before the symlink creation to only create the symlink if
ioc_script_name differs from GLOBALS.RTEMS_SCRIPT_DEFAULT_NAME, preventing the
self-referential link.
🤖 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.

Inline comments:
In @.claude/hooks/sandbox-check.sh:
- Around line 22-23: The sandbox-check.sh file at lines 22-23 is currently
testing for file existence and size, but the stated policy is that gitconfig
must NOT be readable. Replace the existence check with existence (`-e`) AND
readability check (`-r`) to properly enforce that the file is not readable.
Change the test to fail when the file exists AND is readable, explicitly using
the `-r` flag to test readability permission instead of checking file size.
- Line 16: The sandbox-check.sh script at line 16 currently bypasses all sandbox
checks using only the CLAUDE_CODE_REMOTE environment variable. Replace this
single-factor bypass with a multi-factor verification strategy by combining the
CLAUDE_CODE_REMOTE check with additional runtime signals such as validating the
presence of CLAUDE_CODE_REMOTE_SESSION_ID. Implement a layered verification
approach in the conditional check that requires multiple signals to be satisfied
before skipping sandbox validation, rather than allowing a single environment
variable to unconditionally bypass security-critical checks.

In @.claude/skills/rtems-hybrid-debug/SKILL.md:
- Around line 73-79: Add language identifiers to the fenced code blocks in the
file .claude/skills/rtems-hybrid-debug/SKILL.md. At lines 73-79 (anchor site)
and lines 241-244 (sibling site), the markdown code fences are unlabeled. Add an
appropriate language identifier (such as "shell" or "bash") immediately after
the opening triple backticks on each code block to enable proper syntax
highlighting and satisfy markdown linting requirements.
- Around line 167-171: The documentation in
.claude/skills/rtems-hybrid-debug/SKILL.md at lines 167-171 incorrectly
describes the permission mode created by install -m 644 as
"world-readable+writable". The file permission mode 644 actually creates
owner-writable and world-readable files, not world-writable. Correct the wording
in that section to accurately reflect that install -m 644 results in
owner-writable, world-readable (but not world-writable) files, which is what the
NFS root-squash export requires.

In @.devcontainer/devcontainer.json:
- Around line 73-76: The mount configuration is using object-form syntax
incorrectly by embedding mount options (readonly and bind-propagation=slave)
within the target field value. Convert this mount entry to string-form syntax
where all mount parameters (type, source, target, and options) are combined into
a single comma-separated string value. Replace the current object with three
properties (type, source, target) with a single string property containing all
mount specifications in the format:
type=bind,source=/dls_sw,target=/dls_sw,readonly,bind-propagation=slave.

In @.github/workflows/_dist.yml:
- Line 35: The `actions/setup-python` action on line 35 is referenced using the
mutable tag `@v5` instead of a pinned commit SHA. Replace `@v5` with a full
40-character commit SHA to pin the action to a specific immutable version. This
ensures the workflow always uses the exact version intended and closes the
supply-chain security gap.

In `@Dockerfile`:
- Line 30: The mkdir command creates a directory named /ioc_nfsv2, but the code
in globals.py resolves the NFS root path as /ioc_nfs, causing a mismatch. Update
the RUN mkdir command to create /ioc_nfs instead of /ioc_nfsv2 to align with the
runtime contract expected by the application.

In `@docs/hybrid.md`:
- Around line 228-283: The documentation for the `hybrid_prepare()` function is
missing the `_run_ibek_autosave()` step which generates autosave request files.
Insert a new step 6 between the current "Run msi" step and the "Copy to NFS"
step with the title "Run ibek runtime generate-autosave" that describes how this
step parses ioc.subst, matches DB templates to req files, and expands them with
instance macros into autosave_positions.req and autosave_settings.req in
/epics/runtime/. Renumber the existing "Copy to NFS" step to step 7 and "Copy
binary to TFTP" to step 8. In the manual debugging section, add corresponding
ibek runtime generate-autosave command after the msi expansion step and before
the verification step.

In `@src/rtems_proxy/hybrid.py`:
- Around line 211-255: The subprocess.run calls with check=True in the rsync
operations allow CalledProcessError and FileNotFoundError exceptions to
propagate as unhandled tracebacks instead of being normalized to a controlled
exit. Wrap all subprocess.run calls that perform rsync operations (the ones at
lines 211-255 copying st.cmd, ioc.db, protocol files, req files, and dbd
directory) and line 274 in try-except blocks that catch CalledProcessError and
FileNotFoundError, then convert these exceptions to typer.Exit(1) to maintain
consistent CLI behavior across all deployment steps.
- Around line 45-60: The symlink created by config_dst.symlink_to(config_src)
uses a potentially relative target path. When instance_path is a relative path
(from the --instance flag), config_src becomes relative and the symlink target
won't resolve correctly at runtime. Convert config_src to an absolute path using
the .resolve() method before passing it to symlink_to to ensure the symlink
always points to a valid absolute target regardless of how the instance path was
provided.

In `@src/rtems_proxy/rsync.sh.jinja`:
- Around line 15-23: The rsync.sh.jinja file has swapped variable assignments
where RTEMS_TFTP_IP is incorrectly set to a filesystem path value (/ioc_tftp)
instead of an IP address, while RTEMS_TFTP_PATH is never defined but used later
in mkdir and rsync operations. Fix this by assigning the filesystem path to
RTEMS_TFTP_PATH and assigning the proper IP address to RTEMS_TFTP_IP (which may
need to be templated or sourced from an environment variable). This ensures the
validation check correctly validates the IP address variable and the mkdir and
rsync operations use the correct path variable, preventing broken host
resolution and empty path issues.

In `@update-schema`:
- Around line 6-16: The script needs to be hardened to prevent continuation
after errors and to use proper output handling. Enable strict mode (set -e) at
the beginning of the script, quote all variable references including
IOC_ORIGINAL_LOCATION and EPICS_ROOT throughout, add validation to ensure the
glob pattern in the ln command matches at least one file before attempting the
symlink operation, and replace the shell output redirection (>) with the
documented --output flag in the ibek ioc generate-schema command to properly
handle the output and prevent noisy stdout from being written to
ioc.schema.json.

In `@vacuumSpace-fix.patch`:
- Around line 1-2: Remove the patch file vacuumSpace-fix.patch before merging,
as it is a temporary scratch artifact containing /tmp paths that duplicate
changes already present in the fixed YAML configuration. If documentation
explaining the fix is needed, add that rationale to the project documentation
directory instead and retain only the final production-ready YAML files in the
repository.

In `@vacuumSpace.ibek.support.yaml.fixed`:
- Around line 1-3: The file vacuumSpace.ibek.support.yaml.fixed does not match
the expected naming pattern for support YAML files. The
_link_ibek_support_yamls() function only symlinks files that match the pattern
*.ibek.support.yaml (without the .fixed suffix). Rename the file from
vacuumSpace.ibek.support.yaml.fixed to vacuumSpace.ibek.support.yaml to ensure
it is properly discovered and loaded during hybrid mode, or delete the file if
it is only a temporary artifact and not needed.
- Around line 242-390: The spaceGroup entity model declares parameters (device,
name, space0 through space7) and describes itself as a helper that creates a
group of spaces to represent a hutch, but all databases entries are commented
out. This means instantiating spaceGroup will appear to succeed during
configuration but will silently produce no actual IOC records. Either uncomment
and properly implement the databases entries in the spaceGroup entity model to
actually create the promised space group records, or remove the spaceGroup
entity entirely if it should not be a public entity. Choose the approach based
on whether this entity is intended to be functional.
- Around line 427-429: The template file `$(VACUUMSPACE)/db/space.template` is
instantiated with inconsistent argument orders in two different places (one with
`device, ionp, gauge, img, pirg, valve` and another with `device, name, ionp,
gauge, img, pirg, valve`), and `$(VACUUMSPACE)/db/space_b.template` is
instantiated with significantly different and incompatible argument sequences
across two locations. Since ibek merges rows by template path and appends values
positionally, these mismatches cause data to shift into wrong columns.
Standardize each template path to use a single consistent argument sequence
across all its instantiations: decide on the canonical arg order for
`space.template` and update all its usages to match, then do the same for
`space_b.template` (and determine whether `SELM` and `SELN` are required
parameters for `space_b.template` to include or exclude them consistently).

---

Outside diff comments:
In `@Dockerfile`:
- Around line 41-63: The runtime stage in the Dockerfile currently runs as root,
which is a security risk. After the directory permission setup (the chmod
commands in the RUN instruction that creates /epics/ibek-defs, /epics/runtime,
and /epics/autosave directories), add a new RUN instruction to create a
dedicated non-root user, then add a USER instruction before the ENTRYPOINT
declaration to ensure the rtems-proxy application runs under that unprivileged
user instead of root.

In `@src/rtems_proxy/configure.py`:
- Around line 44-56: Add validation for GLOBALS.IOC_DOMAIN and GLOBALS.IOC_NAME
to the existing validation block (in the if not self.dry_run section where
RTEMS_IOC_NETMASK, RTEMS_IOC_GATEWAY, RTEMS_IOC_IP, RTEMS_NFS_IP, and
RTEMS_TFTP_IP are checked). Include these two variables in the list being
validated to ensure they are not None and not empty strings before they are used
to build the NFS mount path in the code that follows (around line 70-84). Update
the ValueError message to include IOC_DOMAIN and IOC_NAME in the list of
required variables.

In `@src/rtems_proxy/copy.py`:
- Around line 58-85: The code creates a self-referential symlink when no startup
scripts are found. When len(sts) == 0, ioc_script_name is set to "st.cmd", and
then line 85 creates a symlink from st.cmd to itself via
ioc_script_path.symlink_to(). Fix this by adding a conditional check before the
symlink creation to only create the symlink if ioc_script_name differs from
GLOBALS.RTEMS_SCRIPT_DEFAULT_NAME, preventing the self-referential link.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3bf5d9f-3f26-4311-a7be-0af37ea1e925

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd20af and 2775ee1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .claude/hooks/sandbox-check.sh
  • .claude/settings.json
  • .claude/skills/rtems-hybrid-debug/SKILL.md
  • .claude/statusline-command.sh
  • .devcontainer/devcontainer.json
  • .github/workflows/_dist.yml
  • .github/workflows/ci.yml
  • .gitignore
  • .gitmodules
  • .python-version
  • Dockerfile
  • README.md
  • docs/example-bl19i-va-ioc-01.md
  • docs/hybrid.md
  • docs/overview.md
  • pyproject.toml
  • rtems-proxy.code-workspace
  • src/rtems_proxy/__main__.py
  • src/rtems_proxy/configure.py
  • src/rtems_proxy/copy.py
  • src/rtems_proxy/globals.py
  • src/rtems_proxy/hybrid.py
  • src/rtems_proxy/rsync.sh.jinja
  • tests/test_globals.py
  • update-schema
  • vacuumSpace-fix.patch
  • vacuumSpace.ibek.support.yaml.fixed
💤 Files with no reviewable changes (2)
  • rtems-proxy.code-workspace
  • .python-version

Comment thread .claude/hooks/sandbox-check.sh Outdated
Comment thread .claude/hooks/sandbox-check.sh Outdated
Comment thread .claude/skills/rtems-hybrid-debug/SKILL.md Outdated
Comment thread .claude/skills/rtems-hybrid-debug/SKILL.md Outdated
Comment thread .devcontainer/devcontainer.json
Comment thread update-schema Outdated
Comment thread vacuumSpace-fix.patch Outdated
Comment thread vacuumSpace.ibek.support.yaml.fixed Outdated
Comment thread vacuumSpace.ibek.support.yaml.fixed Outdated
Comment thread vacuumSpace.ibek.support.yaml.fixed Outdated
gilesknap and others added 9 commits June 15, 2026 12:37
These were promoted claude-sandbox artifacts (sandbox-check.sh hook,
statusline-command.sh, and the settings.json wiring them up). The
sandbox approach is being replaced with a guest sandbox, so these no
longer belong in the repo. Only the repo-specific rtems-hybrid-debug
skill remains under .claude.

Resolves CodeRabbit review comments on sandbox-check.sh by removal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Annotate the two unlabeled fenced code blocks (st.cmd<->NFS contract
  and the FINS init error output) with the `text` language so they pass
  markdownlint MD040.
- Correct the protocols-target wording: `install -m 644` pins
  owner-writable, world-readable (0644) files, not world-writable.

Addresses CodeRabbit review comments on SKILL.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mkdir created /ioc_nfsv2 but globals.py resolves the NFS root as
/ioc_nfs (RTEMS_NFS_ROOT_PATH), used consistently across docs and the
hybrid code. Align the directory with the runtime contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The TFTP filesystem path was assigned to RTEMS_TFTP_IP, clobbering the
real IP exported by the env_vars loop, while RTEMS_TFTP_PATH (used by
mkdir/rsync/inotifywait) was never set. Assign the path to
RTEMS_TFTP_PATH, leave RTEMS_TFTP_IP as the host, and add it to the
validation guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A relative --instance path produced a relative symlink target at
/epics/ioc/config that ibek could not follow. Resolve config_src to an
absolute path and mark the symlink as a directory target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The six rsync calls used check=True, letting CalledProcessError or a
missing rsync escape as a traceback instead of the controlled exit used
by every other step. Add a _run_checked helper following the existing
check=False + return-code pattern and route all rsync calls through it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enable strict mode, quote IOC_ORIGINAL_LOCATION/$0, guard the support
YAML glob so an empty match fails loudly instead of creating a broken
symlink, and write the schema via ibek's --output flag rather than
stdout redirection (which could capture stray output).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The step-by-step list and manual-debug block omitted _run_ibek_autosave,
which runs between msi expansion and the NFS copy. Document it as step 6
(renumbering copy-to-NFS and copy-binary) and add the equivalent manual
commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vacuumSpace-fix.patch and vacuumSpace.ibek.support.yaml.fixed were
scratch files from debugging (they reference /tmp paths and are used
nowhere in the repo). The real fix lives in the ibek-support repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@src/rtems_proxy/hybrid.py`:
- Around line 188-197: Replace the direct subprocess.run() calls in the
functions _run_ibek_generate, _run_msi, and _run_ibek_autosave with calls to
_run_checked to ensure consistent error handling. Each function should use
_run_checked instead of calling subprocess.run directly, passing the appropriate
command list and error message to maintain consistent error normalization across
all subprocess operations and prevent raw tracebacks from missing executables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df7df878-50f7-4506-8379-124708c32552

📥 Commits

Reviewing files that changed from the base of the PR and between 83ecb85 and 2de358d.

📒 Files selected for processing (5)
  • Dockerfile
  • docs/hybrid.md
  • src/rtems_proxy/hybrid.py
  • src/rtems_proxy/rsync.sh.jinja
  • update-schema
✅ Files skipped from review due to trivial changes (1)
  • docs/hybrid.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • update-schema
  • Dockerfile

Comment thread src/rtems_proxy/hybrid.py Outdated
gilesknap and others added 2 commits June 15, 2026 13:52
Extend _run_checked with **kwargs (for env=) and use it in
_run_ibek_generate, _run_msi and _run_ibek_autosave so a missing
executable exits via typer.Exit(1) like the rsync calls, instead of
leaking a traceback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/rtems_proxy/hybrid.py (1)

83-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast on duplicate basenames when staging support/autosave links.

Both link steps flatten files by .name; if two modules ship the same filename, the later symlink silently replaces the earlier one. That can drop required ibek defs or autosave req inputs and produce incomplete runtime artifacts.

Suggested minimal guard
 def _link_ibek_support_yamls():
@@
-    for yaml_file in yaml_files:
+    seen_yaml: dict[str, Path] = {}
+    for yaml_file in yaml_files:
+        prev = seen_yaml.get(yaml_file.name)
+        if prev and prev != yaml_file:
+            typer.echo(
+                f"Duplicate ibek support filename '{yaml_file.name}': {prev} and {yaml_file}"
+            )
+            raise typer.Exit(1)
+        seen_yaml[yaml_file.name] = yaml_file
         link = ibek_defs / yaml_file.name
         if link.exists():
             link.unlink()
         link.symlink_to(yaml_file)
@@
 def _run_ibek_autosave():
@@
-    for req in req_files:
+    seen_req: dict[str, Path] = {}
+    for req in req_files:
+        prev = seen_req.get(req.name)
+        if prev and prev != req:
+            typer.echo(f"Duplicate autosave req filename '{req.name}': {prev} and {req}")
+            raise typer.Exit(1)
+        seen_req[req.name] = req
         link = autosave_dir / req.name
         if link.exists() or link.is_symlink():
             link.unlink()
         link.symlink_to(req)

Also applies to: 165-168

🤖 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/rtems_proxy/hybrid.py` around lines 83 - 86, When staging
support/autosave links, both symlink creation steps flatten files by basename
without detecting duplicates. If two modules ship the same filename, the later
symlink silently overwrites the earlier one, potentially dropping required ibek
definitions or autosave inputs. Add a guard check before each link.symlink_to()
call to detect if a symlink with the same basename already exists from a
different source file, and raise an error immediately with a clear message
identifying the conflicting files rather than allowing silent replacement. This
guard should be applied at both symlink creation locations where files are being
flattened by basename.
🤖 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.

Outside diff comments:
In `@src/rtems_proxy/hybrid.py`:
- Around line 83-86: When staging support/autosave links, both symlink creation
steps flatten files by basename without detecting duplicates. If two modules
ship the same filename, the later symlink silently overwrites the earlier one,
potentially dropping required ibek definitions or autosave inputs. Add a guard
check before each link.symlink_to() call to detect if a symlink with the same
basename already exists from a different source file, and raise an error
immediately with a clear message identifying the conflicting files rather than
allowing silent replacement. This guard should be applied at both symlink
creation locations where files are being flattened by basename.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e1f94b6-0441-4fac-bd41-54ab5384cc4d

📥 Commits

Reviewing files that changed from the base of the PR and between 2de358d and 7a77490.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • src/rtems_proxy/hybrid.py

@gilesknap gilesknap merged commit 3c3fd3f into main Jun 15, 2026
8 checks passed
@Fleinsopp

Copy link
Copy Markdown

File change

6 similar comments
@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

Change file

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

Change file

@Fleinsopp

Copy link
Copy Markdown

File change

@Fleinsopp

Copy link
Copy Markdown

Files change

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.

2 participants