Skip to content

Microshift bootc image#314

Open
mangelajo wants to merge 13 commits intomainfrom
microshift-bootc-image
Open

Microshift bootc image#314
mangelajo wants to merge 13 commits intomainfrom
microshift-bootc-image

Conversation

@mangelajo
Copy link
Member

@mangelajo mangelajo commented Mar 12, 2026

Summary by CodeRabbit

  • New Features

    • MicroShift BootC delivery: image build/push, multi-arch support, and QCOW2/ISO artifact generation via provided tooling and run scripts
    • Web-based configuration UI with password/SSH setup, system dashboard, pod/route management, BootC upgrade/switch actions, and kubeconfig download
    • Systemd services and banner updater for the configuration UI; packaged Python project for the UI
  • Documentation

    • Detailed README and troubleshooting guides for deployment and usage
  • CI

    • CI matrix updated to optionally build installer artifacts during releases

@netlify
Copy link

netlify bot commented Mar 12, 2026

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit a1d167c
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/69b4229991cfa9000837a994
😎 Deploy Preview https://deploy-preview-314--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Adds a MicroShift BootC image build/runtime system and a new Flask-based configuration service (API, UI, auth, system utilities), container/build tooling, systemd units, templates, and documentation to enable local MicroShift bootstrap, image/ISO generation, and in-container Jumpstarter management.

Changes

Cohort / File(s) Summary
CI / Image Build
.github/workflows/build-images.yaml
Adds CI matrix entry for jumpstarter-dev/microshift/bootc and installer build steps gated by matrix flags.
BootC Container & Make tooling
controller/deploy/microshift-bootc/Containerfile, controller/deploy/microshift-bootc/Makefile, controller/deploy/microshift-bootc/run-microshift.sh
New Containerfile for bootc image; comprehensive Makefile for building/pushing single & multi-arch images and QCOW2/ISO generation; script to run the bootc container with LVM setup and kubeconfig wait logic.
Boot image artifacts
controller/deploy/microshift-bootc/config.toml, .../kickstart.ks, .../kustomization.yaml
Adds boot image customization (config.toml), kickstart for OS install, and a kustomize manifest for operator installation.
Config Service core code
controller/deploy/microshift-bootc/config-svc/__init__.py, .../app.py, .../api.py, .../routes.py, .../auth.py, .../system.py, .../pyproject.toml
New packaged Flask app: app entrypoint, extensive API routes (system, BootC lifecycle, pods/routes, kubeconfig, logs, password/SSH), UI route wiring, auth helpers (PAM fallback), and system utilities (LVM, banner, Jumpstarter CR apply). Exposes __version__, app, and main().
Config Service UI & assets
controller/deploy/microshift-bootc/config-svc/templates/index.html, .../password_required.html, .../styles.css
Adds single-page admin UI templates and CSS with client-side logic for configuration, BootC operations, system stats, pod/route views, password-required flow, and styling.
Runtime units & helper scripts
controller/deploy/microshift-bootc/config-svc/config-svc.service, .../update-banner.service, .../update-banner.sh
Systemd units for config-svc and banner updater plus a wrapper script to refresh the login banner via the system module.
Docs & READMEs
controller/deploy/microshift-bootc/README.md, controller/deploy/microshift-bootc/config-svc/README.md
Comprehensive documentation covering build/run steps, QCOW2 workflows, architecture, troubleshooting, and config-svc project details.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as User/Browser
    participant UI as Config UI
    participant API as Flask API
    participant Sys as System Utils
    participant K8s as Kubernetes/oc

    Browser->>UI: Load admin page
    UI->>API: GET /api/system-stats
    API->>Sys: collect disk/memory/cpu/network/LVM
    Sys-->>API: metrics JSON
    API-->>UI: Render system stats

    Browser->>UI: Submit configure-jumpstarter
    UI->>API: POST /api/configure-jumpstarter
    API->>Sys: validate_hostname -> apply_jumpstarter_cr
    Sys->>K8s: oc apply (using kubeconfig)
    K8s-->>Sys: apply result
    Sys-->>API: success/failure
    API-->>UI: Display result

    Browser->>UI: View pods/routes
    UI->>API: GET /api/pods or /api/routes
    API->>K8s: oc get pods/routes (kubeconfig)
    K8s-->>API: resource list
    API-->>UI: Render tables
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

python, go, enhancement

Suggested reviewers

  • bennyz
  • evakhoni
  • kirkbrauer

Poem

🐇 I hopped and found a BootC bright,

Flask pages gleam in morning light,
LVM drums and kubeconfigs spun,
Containers hum; the cluster's begun,
A tiny rabbit cheers "Well done!" 🚀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a new MicroShift bootc image with comprehensive supporting infrastructure including containerfile, makefile, documentation, and a configuration service web UI.
Docstring Coverage ✅ Passed Docstring coverage is 87.76% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch microshift-bootc-image
📝 Coding Plan
  • Generate coding plan for human review comments

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

Actionable comments posted: 18

🧹 Nitpick comments (11)
controller/deploy/microshift-bootc/config-svc/app.py (2)

16-16: Minor typo in comment.

"registrationfunctions" should be "registration functions".

-# Import route registrationfunctions
+# Import route registration functions
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/app.py` at line 16, Fix the
typo in the inline comment that reads "registrationfunctions" by updating it to
"registration functions" so the comment correctly reads something like "Import
route registration functions"; locate the comment near the import block in
app.py that currently contains "registrationfunctions" and replace the
single-word typo with the two-word phrase.

31-31: Consider changing the default port to match deployment configuration.

The default port is 8080, but the Containerfile exposes port 8880 and run-microshift.sh maps 8880:8880. While the systemd service (config-svc.service) overrides this with Environment="PORT=8880", changing the default to 8880 improves consistency for local development and reduces confusion:

Proposed change
-    port = int(os.environ.get('PORT', 8080))
+    port = int(os.environ.get('PORT', 8880))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/app.py` at line 31, The default
port for the app is set to 8080 (port = int(os.environ.get('PORT', 8080))) which
conflicts with the Containerfile and run scripts that use 8880; update the
default value to 8880 so that the PORT fallback matches deployment and local dev
(i.e., change the default in the PORT getenv call from 8080 to 8880 in the
app.py port assignment).
controller/deploy/microshift-bootc/config-svc/templates/password_required.html (1)

85-93: Add defensive checks for API response handling.

The response handling assumes result.messages is always an array. If the API returns an error response without a messages field, result.messages.forEach will throw. Consider adding a guard:

Proposed fix
                         .then(response => response.json())
                         .then(result => {
                             // Display messages
-                            result.messages.forEach(msg => {
+                            (result.messages || []).forEach(msg => {
                                 const messageDiv = document.createElement('div');
                                 messageDiv.className = `message ${msg.type}`;
                                 messageDiv.textContent = msg.text;
                                 messagesContainer.appendChild(messageDiv);
                             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@controller/deploy/microshift-bootc/config-svc/templates/password_required.html`
around lines 85 - 93, The code assumes result.messages is always an array and
calls result.messages.forEach which will throw if messages is missing or not an
array; update the response handling (the promise chain that processes
response.json and the block referencing result, result.messages,
messagesContainer, msg.type, msg.text) to defensively check that
Array.isArray(result.messages) (or that result.messages is truthy and iterable)
before iterating, and handle the else case by rendering a default error message
or skipping rendering so the UI doesn't crash.
controller/deploy/microshift-bootc/Containerfile (1)

19-20: Document security implications of default password.

The hardcoded default password "jumpstarter" is intentional and correctly triggers the password change flow via is_default_password() in auth.py. Consider adding a comment explaining this is by design and that users are forced to change it on first access.

Proposed documentation
-# Set root password
+# Set default root password - users are forced to change this on first login
+# via the password_required.html flow (see auth.py:is_default_password())
 RUN echo "root:jumpstarter" | chpasswd
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/Containerfile` around lines 19 - 20, Add
an explanatory comment above the RUN echo "root:jumpstarter" | chpasswd line
stating that the hardcoded default password "jumpstarter" is intentional for
initial boot, that the auth.py is_default_password() check forces users to
change it on first login, and note the security implications so reviewers/users
understand this is deliberate and must not be used in production without
changing; reference the is_default_password() function in auth.py in the comment
to tie behavior to implementation.
controller/deploy/microshift-bootc/run-microshift.sh (1)

86-86: DRY: Extract hostname to a single constant.

The hostname "jumpstarter.127-0-0-1.nip.io" is defined twice (line 86 and line 154). Consider defining it once at the top with the other constants to avoid inconsistency if one is updated without the other.

Proposed fix
 CONFIG_SVC_PORT=8880
+HOSTNAME="jumpstarter.127-0-0-1.nip.io"
 
 
 function pull_bootc_image() {

Then use ${HOSTNAME} in both locations.

Also applies to: 154-154

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/run-microshift.sh` at line 86, Extract the
duplicate literal "jumpstarter.127-0-0-1.nip.io" into a single read-only
constant (e.g., HOSTNAME) alongside the other top-of-file constants in
run-microshift.sh, then replace the two inline definitions/usages (the current
local -r hostname="..." and the other occurrence) with references to
${HOSTNAME}; ensure you remove the duplicated local -r hostname declaration so
both places use the single HOSTNAME constant.
controller/deploy/microshift-bootc/Makefile (2)

1-1: Add clean to .PHONY declaration.

The static analysis tool correctly identified that clean is missing from the .PHONY declaration. While this won't break functionality, it's a Makefile best practice.

♻️ Proposed fix
-.PHONY: help build bootc-build bootc-build-multi push bootc-push bootc-push-multi bootc-run bootc-stop bootc-sh bootc-rm build-image build-iso build-all build-all-multi push-all push-all-multi
+.PHONY: help build bootc-build bootc-build-multi push bootc-push bootc-push-multi bootc-run bootc-stop bootc-sh bootc-rm build-image build-iso build-all build-all-multi push-all push-all-multi clean bootc-reload-app
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/Makefile` at line 1, The .PHONY
declaration at the top of the Makefile is missing the clean target; update the
.PHONY line (the line containing ".PHONY: help build bootc-build ...") to
include "clean" so the clean target is treated as phony and not confused with a
file of the same name—add "clean" to that list.

76-92: LVM cleanup uses hardcoded volume group name.

The cleanup commands use hardcoded myvg1 for the volume group name. If the VG name changes or differs across environments, these cleanup steps will be ineffective.

♻️ Consider parameterizing the VG name
+# LVM configuration
+LVM_VG_NAME ?= myvg1
+
 build-image: bootc-build ## Build the bootc based qcow2 image
 	`@echo` "Building image: output/qcow2/disk.qcow2"
 	`@echo` "Cleaning up any existing LVM resources to avoid conflicts..."
-	-sudo vgs --noheadings -o vg_name,vg_uuid | grep myvg1 | while read vg uuid; do sudo vgremove -f --select vg_uuid=$$uuid 2>/dev/null || true; done
+	-sudo vgs --noheadings -o vg_name,vg_uuid | grep $(LVM_VG_NAME) | while read vg uuid; do sudo vgremove -f --select vg_uuid=$$uuid 2>/dev/null || true; done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/Makefile` around lines 76 - 92, The LVM
cleanup in the build-image and build-iso targets hardcodes the VG name "myvg1";
change it to use a Makefile variable (e.g., VG_NAME ?= myvg1) and reference that
variable in the vgs/grep/vgremove commands so different environments can
override it (update the commands in the build-image and build-iso targets that
currently call vgs ... | grep myvg1 to use $(VG_NAME)); ensure the variable has
a sensible default and that both targets are updated consistently.
controller/deploy/microshift-bootc/config-svc/api.py (3)

702-730: Log streaming process cleanup could leave zombie processes.

In the generate() function, if the client disconnects abruptly, the finally block may not execute immediately due to how Flask generators work. Consider adding a timeout to process.wait().

♻️ Add more robust process cleanup
             finally:
                 # Clean up process when connection closes
                 if process:
                     try:
                         process.terminate()
-                        process.wait(timeout=5)
+                        try:
+                            process.wait(timeout=5)
+                        except subprocess.TimeoutExpired:
+                            process.kill()
+                            process.wait(timeout=1)
                     except Exception:
                         process.kill()
+                        try:
+                            process.wait(timeout=1)
+                        except Exception:
+                            pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/api.py` around lines 702 - 730,
The generate() generator can leave zombie oc processes when a client
disconnects; update the subprocess.Popen call to start the process in its own
session (use start_new_session=True or preexec_fn=os.setsid) and in the finally
block call process.terminate() then process.wait(timeout=5) inside a try/except
that on TimeoutExpired uses process.kill() and, if you started a session,
os.killpg(process.pid, signal.SIGKILL) to ensure the whole group is killed;
ensure these changes reference the generate function, the subprocess.Popen
invocation, and the process variable so cleanup always forcibly reaps the child.

390-393: Image validation is too permissive.

The validation check ':' in image or '/' in image will accept many invalid formats. For example, foo: or /invalid would pass. Consider using a more robust validation pattern.

♻️ Proposed stricter validation
-            # Validate image format (basic check)
-            if not (image.startswith('quay.io/') or image.startswith('docker.io/') or 
-                    ':' in image or '/' in image):
-                return jsonify({'success': False, 'error': 'Invalid image reference format'}), 400
+            # Validate image format (basic check for registry/repo:tag pattern)
+            # Must contain at least one '/' for namespace/repo and optionally ':' for tag
+            image_pattern = re.compile(r'^[\w.-]+(/[\w.-]+)+(:[\w.-]+)?$')
+            if not image_pattern.match(image):
+                return jsonify({'success': False, 'error': 'Invalid image reference format. Expected format: registry/namespace/repo:tag'}), 400
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/api.py` around lines 390 - 393,
The current check around the image variable in the validation block is too
permissive (the clause "':' in image or '/' in image") and will allow invalid
values like "foo:" or "/invalid"; replace that ad-hoc check with a strict
regex-based validation for container image references (match optional registry
host[:port], one or more path components for namespace/repo, image name, and an
optional :tag or `@digest`) and use re.match on image before returning the jsonify
error; update the condition in the same validation block (the code that
currently returns jsonify({'success': False, 'error': 'Invalid image reference
format'}), 400) to use the compiled regex (or a helper function
is_valid_image(image)) so only syntactically correct image references are
accepted.

831-855: Kubeconfig regex manipulation is fragile and assumes specific YAML format.

The regex replacements for modifying the kubeconfig assume a specific YAML structure (specific indentation, field ordering, line endings). MicroShift's kubeconfig format is unlikely to change, but these replacements could fail silently if the format does change, resulting in an invalid or incomplete kubeconfig.

Consider parsing the kubeconfig as YAML, modifying the data structure, and serializing it back. This would be more robust and format-agnostic:

import yaml
config = yaml.safe_load(kubeconfig_content)
# Modify clusters[0].cluster.server, remove insecure-skip-tls-verify, add tls-server-name
kubeconfig_content = yaml.dump(config)

PyYAML is not currently a dependency, but adding it would make this code more maintainable and resilient to format variations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/api.py` around lines 831 - 855,
The current sequence of regex edits on kubeconfig_content (replacing server
host, removing insecure-skip-tls-verify, and inserting tls-server-name using
nip_hostname and original_hostname) is fragile; instead parse kubeconfig_content
as YAML (e.g., yaml.safe_load), update the cluster entries programmatically: set
each cluster.cluster.server to use nip_hostname, remove any
insecure-skip-tls-verify keys under cluster entries, and add tls-server-name:
original_hostname under the same cluster.cluster mapping, then serialize back
with yaml.safe_dump (yaml.dump) to kubeconfig_content; add PyYAML as a
dependency and ensure you operate on all clusters/contexts rather than relying
on regex/idempotent string replacements.
controller/deploy/microshift-bootc/config-svc/system.py (1)

348-368: Custom YAML serializer produces non-standard formatting for list items with nested objects.

The current json_to_yaml() implementation outputs list items with the dash (-) on its own line, followed by the nested content indented below:

endpoints:
  -
    address: test.example.com

Standard YAML formatting places the first key-value pair on the same line as the dash:

endpoints:
  - address: test.example.com

While both formats are valid YAML, the standard format is more conventional and readable.

♻️ Proposed fix

Modify the list handling to place the first key-value pair on the same line as the dash:

 elif isinstance(obj, list):
     for item in obj:
-        if isinstance(item, (dict, list)):
-            lines.append(f"{indent_str}-")
-            lines.append(json_to_yaml(item, indent + 1))
+        if isinstance(item, dict):
+            # First key-value on same line as dash
+            first = True
+            for key, value in item.items():
+                if first:
+                    if isinstance(value, (dict, list)):
+                        lines.append(f"{indent_str}- {key}:")
+                        lines.append(json_to_yaml(value, indent + 2))
+                    else:
+                        lines.append(f"{indent_str}- {key}: {yaml_value(value)}")
+                    first = False
+                else:
+                    if isinstance(value, (dict, list)):
+                        lines.append(f"{indent_str}  {key}:")
+                        lines.append(json_to_yaml(value, indent + 2))
+                    else:
+                        lines.append(f"{indent_str}  {key}: {yaml_value(value)}")
+        elif isinstance(item, list):
+            lines.append(f"{indent_str}-")
+            lines.append(json_to_yaml(item, indent + 1))
         else:
             lines.append(f"{indent_str}- {yaml_value(item)}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/system.py` around lines 348 -
368, The json_to_yaml function currently writes a lone dash line for list items
that are dicts/lists, producing non-standard output; update the list handling in
json_to_yaml so when an item is a dict you render the dash and the first
key/value on the same line (e.g., "- key: value") and then append remaining
nested lines indented one extra level; locate the branch in json_to_yaml where
it detects isinstance(item, (dict, list)) and change it to serialize the first
line of the nested dict inline after the dash (using yaml_value for simple
scalars) and then call json_to_yaml for the rest of the nested content with
indent+1, ensuring subsequent lines are indented correctly (reference
json_to_yaml and yaml_value to implement this).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build-images.yaml:
- Around line 46-48: The matrix entry uses a full registry+tag in image_name
which conflicts with the tag-generation logic that prefixes ${{ env.REGISTRY }}
and appends version tags; update the image_name value (in the matrix entry
currently set to "quay.io/jumpstarter-dev/microshift/bootc:latest") to the
org/repo format (e.g., "jumpstarter-dev/microshift/bootc") so generated tags
become correct, and add a matching label field (e.g., label: "bootc") so PR
label filtering (build-pr-images/<label>) works; adjust only the image_name and
add label in that matrix item to match the other entries and tagging logic.

In `@controller/deploy/microshift-bootc/config-svc/auth.py`:
- Around line 103-110: Remove the unreliable subprocess-based fallback that
calls subprocess.run(['su', username, '-c', 'true'], ...) for password checking;
since python-pam is guaranteed in the production image, delete that fallback
block from auth.py and make the module rely on the pam-based authentication path
(or raise a clear exception if pam import/initialization fails) so failures are
not silently ignored—ensure the authenticate function (the code path that
currently returns result.returncode == 0) either uses the pam API or surfaces an
explicit error when pam is unavailable.

In `@controller/deploy/microshift-bootc/config-svc/config-svc.service`:
- Around line 4-6: Remove the "Before=getty@.service
systemd-user-sessions.service" ordering from the config-svc unit so local
console login isn't gated on the web UI; update the unit file that contains the
After=network-online.target and Wants=network-online.target lines by deleting
the Before=... entry (leave the banner-updater unit to own the getty ordering
instead).

In `@controller/deploy/microshift-bootc/config-svc/pyproject.toml`:
- Around line 33-34: The console script entry jumpstarter-config-svc points to a
non-existent module app_new; update the entry point to reference the actual
module that defines main(). Either change the pyproject.toml [project.scripts]
value from "app_new:main" to "app:main" to match the existing app.py main()
function, or rename/create module app_new with a main() wrapper; ensure the
symbol main() in app.py (or the new module) is importable under the chosen
module name.
- Around line 46-53: The package name in pyproject.toml is invalid because it
uses a hyphen: update the [tool.setuptools] packages value from "config-svc" to
"config_svc" and adjust [tool.setuptools.package-data] key accordingly so
setuptools matches the actual package name; also fix the faulty entry point
"jumpstarter-config-svc = \"app_new:main\"" to reference the real module and
callable ("app:main") and rename the directory from config-svc to config_svc
(e.g., mv controller/deploy/microshift-bootc/config-svc
controller/deploy/microshift-bootc/config_svc) so package name, package-data and
the entry_point (app:main) are consistent.

In `@controller/deploy/microshift-bootc/config-svc/README.md`:
- Around line 16-32: The fenced project-structure block in the README is missing
a language identifier; update the opening fence for the tree diagram from ``` to
```text so the markdown linter recognizes it as a code block (i.e., change the
project-structure fence in README.md that contains the config-svc/ tree to use
```text).

In `@controller/deploy/microshift-bootc/config-svc/templates/index.html`:
- Around line 783-794: The routes table is vulnerable to XSS because values from
data.routes are interpolated directly into tbody.innerHTML; update the rendering
to escape/encode user-controlled fields (route.namespace, route.name,
route.host, route.service, route.port, route.tls, route.age, and the admitted
badge content used with getAdmittedClass(route.admitted)) before building the
HTML string or, better, create DOM elements programmatically and set
textContent/attributes (e.g., create tr/td nodes, set anchor href via
element.href and text via textContent) instead of using raw template literals
assigned to tbody.innerHTML so all values are safely escaped.
- Around line 740-754: The pod table is vulnerable to XSS because data.pods
values are injected directly into tbody.innerHTML; fix by escaping all dynamic
values before inserting into the DOM (or by setting textContent/attributes
instead of raw HTML). Add an escapeHtml utility and use it for pod.namespace,
pod.name, pod.ready, pod.status, pod.restarts and pod.age when building the row,
use getStatusClass(pod.status) only for the class name (ensure it returns safe
strings), and use encodeURIComponent for URL segments in the /logs link and pass
escaped or text-set values to restartPod(namespace, name). Update the code paths
that build the template (where tbody.innerHTML is assigned) to use the
escaped/encoded values.
- Line 329: The code assigns server error text directly into
messagesContainer.innerHTML using string concatenation (e.g.,
messagesContainer.innerHTML = '<div class="message error">Failed to apply
configuration: ' + error.message + '</div>'), which allows XSS; update these
spots to escape the error output by passing error.message through the existing
escapeHtml function (or create a DOM element and set its textContent) instead of
concatenating raw text, and apply the same change for the other identical
occurrences of messagesContainer.innerHTML assignments so all error.message
insertions use escapeHtml(error.message) or textContent.

In
`@controller/deploy/microshift-bootc/config-svc/templates/password_required.html`:
- Around line 40-45: Add the HTML required attribute to the password input
fields so client-side validation prevents empty submission: update the <input>
with id="newPassword" (name="newPassword") and the <input> with
id="confirmPassword" (name="confirmPassword") to include required (in addition
to existing minlength and type="password") so the form enforces that these
fields must be filled before submit.

In `@controller/deploy/microshift-bootc/config-svc/templates/styles.css`:
- Around line 104-106: The keyframes name fadeIn violates the
keyframes-name-pattern; rename the `@keyframes` identifier from "fadeIn" to
"fade-in" and update the animation declaration that references it (the
animation: fadeIn 0.3s ease-in; rule) to use "fade-in" so the `@keyframes` block
and the animation property match.

In `@controller/deploy/microshift-bootc/config-svc/update-banner.sh`:
- Line 1: The script update-banner.sh has a misleading .sh extension and a
non-portable shebang (#!/bin/env python3); either rename the file to
update-banner.py and update all references in the Containerfile and any callers
to use the new name, or keep the name but replace the shebang with the portable
form (#!/usr/bin/env python3) and ensure the file is executable; update any
Containerfile COPY/ENTRYPOINT lines that reference update-banner.sh accordingly.

In `@controller/deploy/microshift-bootc/kickstart.ks`:
- Around line 10-11: The kickstart currently runs unscoped disk wipes via
zerombr and clearpart --all --initlabel; update the kickstart to target the
intended install device by adding an explicit ignoredisk --only-use=<device> (or
equivalent) and change clearpart --all --initlabel to clearpart
--drives=<device> --initlabel so only the specified device is wiped; ensure the
chosen device name matches what the installer will see and keep zerombr if
needed for label initialization.
- Around line 21-25: The file currently sets an insecure plaintext root password
via the rootpw --plaintext jumpstarter line; replace that with a locked root
account or a secure injection method: remove the plaintext rootpw and enable
rootpw --lock (or provision a unique hashed password or SSH key during
provisioning and set it in %post), and if you must require rotation, expire the
password in the %post section (use rootpw with a hashed value or inject SSH keys
there and then run chage -d 0 or similar to force rotation). Ensure changes
reference the rootpw directive and any %post provisioning logic so no plaintext
root credential remains in the repository.
- Around line 59-60: Replace the mutable image reference used in the
ostreecontainer invocation (the line using
"quay.io/jumpstarter-dev/microshift/bootc:latest") with an immutable, versioned
tag or an image digest (sha256:...) to ensure reproducible Kickstart builds;
update the same pattern wherever the image is referenced (Makefile target,
run-microshift.sh, and the build workflow config) so all consumers use the same
pinned tag/digest and document the chosen version in the repo.

In `@controller/deploy/microshift-bootc/Makefile`:
- Around line 38-39: Remove the stray character in the Makefile echo for the
target output/qcow2/disk.qcow2: in the recipe that echoes the BOOTC image (uses
$(BOOTC_IMG)), delete the trailing 'a' so the message prints only the BOOTC
image path; update the echo line in the Makefile rule for
output/qcow2/disk.qcow2 accordingly.

In `@controller/deploy/microshift-bootc/README.md`:
- Around line 55-63: Update the markdown fenced code blocks under the "Output
example:" section (and the other example block noted) to include a language tag
so the linter stops warning; specifically change the fences from ``` to ```text
for the examples shown in README.md (the "Output example:" block and the later
example around lines mentioned in the review) so both are marked as text-coded
blocks.

In `@controller/deploy/microshift-bootc/run-microshift.sh`:
- Line 172: The echo that prints the Config Service URL uses ${HOSTNAME%%.*}
which yields just "jumpstarter" (non-resolvable); update the echo in
run-microshift.sh to output a usable address instead—either construct the full
nip.io hostname using ${HOSTNAME} and the cluster IP (or a known IP variable) or
simply print http://localhost:${CONFIG_SVC_PORT} (or both) so users see a
resolvable URL; locate the echo referencing HOSTNAME and CONFIG_SVC_PORT and
replace the ${HOSTNAME%%.*} usage with the chosen resolvable host string.

---

Nitpick comments:
In `@controller/deploy/microshift-bootc/config-svc/api.py`:
- Around line 702-730: The generate() generator can leave zombie oc processes
when a client disconnects; update the subprocess.Popen call to start the process
in its own session (use start_new_session=True or preexec_fn=os.setsid) and in
the finally block call process.terminate() then process.wait(timeout=5) inside a
try/except that on TimeoutExpired uses process.kill() and, if you started a
session, os.killpg(process.pid, signal.SIGKILL) to ensure the whole group is
killed; ensure these changes reference the generate function, the
subprocess.Popen invocation, and the process variable so cleanup always forcibly
reaps the child.
- Around line 390-393: The current check around the image variable in the
validation block is too permissive (the clause "':' in image or '/' in image")
and will allow invalid values like "foo:" or "/invalid"; replace that ad-hoc
check with a strict regex-based validation for container image references (match
optional registry host[:port], one or more path components for namespace/repo,
image name, and an optional :tag or `@digest`) and use re.match on image before
returning the jsonify error; update the condition in the same validation block
(the code that currently returns jsonify({'success': False, 'error': 'Invalid
image reference format'}), 400) to use the compiled regex (or a helper function
is_valid_image(image)) so only syntactically correct image references are
accepted.
- Around line 831-855: The current sequence of regex edits on kubeconfig_content
(replacing server host, removing insecure-skip-tls-verify, and inserting
tls-server-name using nip_hostname and original_hostname) is fragile; instead
parse kubeconfig_content as YAML (e.g., yaml.safe_load), update the cluster
entries programmatically: set each cluster.cluster.server to use nip_hostname,
remove any insecure-skip-tls-verify keys under cluster entries, and add
tls-server-name: original_hostname under the same cluster.cluster mapping, then
serialize back with yaml.safe_dump (yaml.dump) to kubeconfig_content; add PyYAML
as a dependency and ensure you operate on all clusters/contexts rather than
relying on regex/idempotent string replacements.

In `@controller/deploy/microshift-bootc/config-svc/app.py`:
- Line 16: Fix the typo in the inline comment that reads "registrationfunctions"
by updating it to "registration functions" so the comment correctly reads
something like "Import route registration functions"; locate the comment near
the import block in app.py that currently contains "registrationfunctions" and
replace the single-word typo with the two-word phrase.
- Line 31: The default port for the app is set to 8080 (port =
int(os.environ.get('PORT', 8080))) which conflicts with the Containerfile and
run scripts that use 8880; update the default value to 8880 so that the PORT
fallback matches deployment and local dev (i.e., change the default in the PORT
getenv call from 8080 to 8880 in the app.py port assignment).

In `@controller/deploy/microshift-bootc/config-svc/system.py`:
- Around line 348-368: The json_to_yaml function currently writes a lone dash
line for list items that are dicts/lists, producing non-standard output; update
the list handling in json_to_yaml so when an item is a dict you render the dash
and the first key/value on the same line (e.g., "- key: value") and then append
remaining nested lines indented one extra level; locate the branch in
json_to_yaml where it detects isinstance(item, (dict, list)) and change it to
serialize the first line of the nested dict inline after the dash (using
yaml_value for simple scalars) and then call json_to_yaml for the rest of the
nested content with indent+1, ensuring subsequent lines are indented correctly
(reference json_to_yaml and yaml_value to implement this).

In
`@controller/deploy/microshift-bootc/config-svc/templates/password_required.html`:
- Around line 85-93: The code assumes result.messages is always an array and
calls result.messages.forEach which will throw if messages is missing or not an
array; update the response handling (the promise chain that processes
response.json and the block referencing result, result.messages,
messagesContainer, msg.type, msg.text) to defensively check that
Array.isArray(result.messages) (or that result.messages is truthy and iterable)
before iterating, and handle the else case by rendering a default error message
or skipping rendering so the UI doesn't crash.

In `@controller/deploy/microshift-bootc/Containerfile`:
- Around line 19-20: Add an explanatory comment above the RUN echo
"root:jumpstarter" | chpasswd line stating that the hardcoded default password
"jumpstarter" is intentional for initial boot, that the auth.py
is_default_password() check forces users to change it on first login, and note
the security implications so reviewers/users understand this is deliberate and
must not be used in production without changing; reference the
is_default_password() function in auth.py in the comment to tie behavior to
implementation.

In `@controller/deploy/microshift-bootc/Makefile`:
- Line 1: The .PHONY declaration at the top of the Makefile is missing the clean
target; update the .PHONY line (the line containing ".PHONY: help build
bootc-build ...") to include "clean" so the clean target is treated as phony and
not confused with a file of the same name—add "clean" to that list.
- Around line 76-92: The LVM cleanup in the build-image and build-iso targets
hardcodes the VG name "myvg1"; change it to use a Makefile variable (e.g.,
VG_NAME ?= myvg1) and reference that variable in the vgs/grep/vgremove commands
so different environments can override it (update the commands in the
build-image and build-iso targets that currently call vgs ... | grep myvg1 to
use $(VG_NAME)); ensure the variable has a sensible default and that both
targets are updated consistently.

In `@controller/deploy/microshift-bootc/run-microshift.sh`:
- Line 86: Extract the duplicate literal "jumpstarter.127-0-0-1.nip.io" into a
single read-only constant (e.g., HOSTNAME) alongside the other top-of-file
constants in run-microshift.sh, then replace the two inline definitions/usages
(the current local -r hostname="..." and the other occurrence) with references
to ${HOSTNAME}; ensure you remove the duplicated local -r hostname declaration
so both places use the single HOSTNAME constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5ed38e2-24eb-448b-8ff5-4e7a5ec967eb

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa6fe2 and b190606.

📒 Files selected for processing (23)
  • .github/workflows/build-images.yaml
  • controller/deploy/microshift-bootc/Containerfile
  • controller/deploy/microshift-bootc/Makefile
  • controller/deploy/microshift-bootc/README.md
  • controller/deploy/microshift-bootc/config-svc/README.md
  • controller/deploy/microshift-bootc/config-svc/__init__.py
  • controller/deploy/microshift-bootc/config-svc/api.py
  • controller/deploy/microshift-bootc/config-svc/app.py
  • controller/deploy/microshift-bootc/config-svc/auth.py
  • controller/deploy/microshift-bootc/config-svc/config-svc.service
  • controller/deploy/microshift-bootc/config-svc/pyproject.toml
  • controller/deploy/microshift-bootc/config-svc/routes.py
  • controller/deploy/microshift-bootc/config-svc/system.py
  • controller/deploy/microshift-bootc/config-svc/templates/index.html
  • controller/deploy/microshift-bootc/config-svc/templates/password_required.html
  • controller/deploy/microshift-bootc/config-svc/templates/styles.css
  • controller/deploy/microshift-bootc/config-svc/update-banner.service
  • controller/deploy/microshift-bootc/config-svc/update-banner.sh
  • controller/deploy/microshift-bootc/config.toml
  • controller/deploy/microshift-bootc/kickstart.ks
  • controller/deploy/microshift-bootc/kustomization.yaml
  • controller/deploy/microshift-bootc/output/.gitkeep
  • controller/deploy/microshift-bootc/run-microshift.sh

Comment on lines +46 to +48
- image_name: quay.io/jumpstarter-dev/microshift/bootc:latest
dockerfile: deploy/microshift-bootc/Containerfile
context: controller
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect image_name format will produce malformed image tags.

The image_name includes the full registry URL and tag (quay.io/jumpstarter-dev/microshift/bootc:latest), but other matrix entries use just org/repo format. The tag generation at lines 127-130 prepends ${{ env.REGISTRY }}/ and appends version tags, which would produce malformed references like:

  • quay.io/quay.io/jumpstarter-dev/microshift/bootc:latest:0.7.0

Additionally, the missing label field means PR label filtering (build-pr-images/<label>) won't work for this image.

Proposed fix
-          - image_name: quay.io/jumpstarter-dev/microshift/bootc:latest
-            dockerfile: deploy/microshift-bootc/Containerfile
-            context: controller
+          - image_name: jumpstarter-dev/microshift-bootc
+            label: microshift-bootc
+            dockerfile: deploy/microshift-bootc/Containerfile
+            context: controller
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- image_name: quay.io/jumpstarter-dev/microshift/bootc:latest
dockerfile: deploy/microshift-bootc/Containerfile
context: controller
- image_name: jumpstarter-dev/microshift-bootc
label: microshift-bootc
dockerfile: deploy/microshift-bootc/Containerfile
context: controller
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-images.yaml around lines 46 - 48, The matrix entry
uses a full registry+tag in image_name which conflicts with the tag-generation
logic that prefixes ${{ env.REGISTRY }} and appends version tags; update the
image_name value (in the matrix entry currently set to
"quay.io/jumpstarter-dev/microshift/bootc:latest") to the org/repo format (e.g.,
"jumpstarter-dev/microshift/bootc") so generated tags become correct, and add a
matching label field (e.g., label: "bootc") so PR label filtering
(build-pr-images/<label>) works; adjust only the image_name and add label in
that matrix item to match the other entries and tagging logic.

Comment on lines +103 to +110
try:
result = subprocess.run(
['su', username, '-c', 'true'],
input=password.encode(),
capture_output=True,
timeout=5
)
return result.returncode == 0
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if python-pam is installed and available in the container
grep -r "python-pam\|pam" controller/deploy/microshift-bootc/Containerfile

Repository: jumpstarter-dev/jumpstarter

Length of output: 103


🏁 Script executed:

cat -n controller/deploy/microshift-bootc/config-svc/auth.py | head -120

Repository: jumpstarter-dev/jumpstarter

Length of output: 4680


🏁 Script executed:

# Check if there are other Containerfiles or build configurations
find controller/deploy/microshift-bootc -name "Containerfile*" -o -name "*.dockerfile" -o -name "*.docker" | head -20

Repository: jumpstarter-dev/jumpstarter

Length of output: 119


🏁 Script executed:

# Also check the deployment documentation or config for any conditional pam installation
grep -r "python-pam\|ImportError" controller/deploy/microshift-bootc --include="*.py" --include="*.md" --include="*.txt" --include="*.sh" -B 2 -A 2

Repository: jumpstarter-dev/jumpstarter

Length of output: 1895


🏁 Script executed:

cat -n controller/deploy/microshift-bootc/Containerfile | grep -A 5 -B 5 "python-pam"

Repository: jumpstarter-dev/jumpstarter

Length of output: 616


🏁 Script executed:

# Also check the full context around pip3 install to see if it's conditional
cat -n controller/deploy/microshift-bootc/Containerfile | grep -A 10 -B 10 "pip3 install"

Repository: jumpstarter-dev/jumpstarter

Length of output: 937


Subprocess fallback authentication is unreliable and will fail in most scenarios.

The su command requires a TTY for password input and does not read passwords from stdin in typical configurations. While python-pam is unconditionally installed in the production Containerfile (guaranteeing the primary authentication path works), the fallback code at lines 103-110 will silently fail if pam import ever fails. Either remove the fallback entirely since pam is guaranteed, or implement a reliable alternative authentication method.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/auth.py` around lines 103 -
110, Remove the unreliable subprocess-based fallback that calls
subprocess.run(['su', username, '-c', 'true'], ...) for password checking; since
python-pam is guaranteed in the production image, delete that fallback block
from auth.py and make the module rely on the pam-based authentication path (or
raise a clear exception if pam import/initialization fails) so failures are not
silently ignored—ensure the authenticate function (the code path that currently
returns result.returncode == 0) either uses the pam API or surfaces an explicit
error when pam is unavailable.

Comment on lines +4 to +6
After=network-online.target
Wants=network-online.target
Before=getty@.service systemd-user-sessions.service
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t gate console logins on the web UI service.

Before=getty@.service systemd-user-sessions.service makes local login availability depend on config-svc reaching started state. Since this unit also waits on network-online.target, a slow or failed UI startup can unnecessarily delay recovery access. The banner updater already owns the “before getty” behavior, so this ordering should be removed from the web service unit.

Suggested change
 After=network-online.target
 Wants=network-online.target
-Before=getty@.service systemd-user-sessions.service
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
After=network-online.target
Wants=network-online.target
Before=getty@.service systemd-user-sessions.service
After=network-online.target
Wants=network-online.target
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/config-svc.service` around
lines 4 - 6, Remove the "Before=getty@.service systemd-user-sessions.service"
ordering from the config-svc unit so local console login isn't gated on the web
UI; update the unit file that contains the After=network-online.target and
Wants=network-online.target lines by deleting the Before=... entry (leave the
banner-updater unit to own the getty ordering instead).

Copy link
Member Author

Choose a reason for hiding this comment

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

This is mostly for the banner.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mangelajo, understood — the intent is to ensure the banner is shown before the first login prompt. However, since there's already a dedicated update-banner.service (oneshot) for exactly that purpose, the Before=getty@.service systemd-user-sessions.service ordering belongs there rather than in config-svc.service.

Keeping it in the web UI service means a slow or failed Flask startup (e.g. due to a network delay or app crash) would hold back the getty login prompt unnecessarily — which is the opposite of what you'd want during troubleshooting. The update-banner.service is the lighter, purpose-built unit that should own the "before getty" dependency.

Comment on lines +33 to +34
[project.scripts]
jumpstarter-config-svc = "app_new:main"
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify which module contains the main() function

echo "Checking for app_new.py:"
fd -t f "app_new.py" controller/deploy/microshift-bootc/config-svc/

echo "Checking for app.py:"
fd -t f "app.py" controller/deploy/microshift-bootc/config-svc/

echo "Checking main() function definition:"
rg -n "^def main\(\)" controller/deploy/microshift-bootc/config-svc/

Repository: jumpstarter-dev/jumpstarter

Length of output: 274


Entry point references non-existent module app_new.

The console script entry point is configured as app_new:main, but app_new.py does not exist. The main() function is defined in app.py. This will cause an ImportError when running jumpstarter-config-svc.

🐛 Proposed fix
 [project.scripts]
-jumpstarter-config-svc = "app_new:main"
+jumpstarter-config-svc = "app:main"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[project.scripts]
jumpstarter-config-svc = "app_new:main"
[project.scripts]
jumpstarter-config-svc = "app:main"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/pyproject.toml` around lines 33
- 34, The console script entry jumpstarter-config-svc points to a non-existent
module app_new; update the entry point to reference the actual module that
defines main(). Either change the pyproject.toml [project.scripts] value from
"app_new:main" to "app:main" to match the existing app.py main() function, or
rename/create module app_new with a main() wrapper; ensure the symbol main() in
app.py (or the new module) is importable under the chosen module name.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok this is weird, because it really works, can you double check?

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

Let me do a thorough check of the directory structure and all Python files.


🏁 Script executed:

#!/bin/bash
echo "=== All files in config-svc ==="
find controller/deploy/microshift-bootc/config-svc/ -type f | sort

echo ""
echo "=== Searching for 'app_new' anywhere ==="
rg -rn "app_new" controller/deploy/microshift-bootc/config-svc/

echo ""
echo "=== Checking app.py for main() and any app_new alias ==="
cat controller/deploy/microshift-bootc/config-svc/app.py

echo ""
echo "=== Checking __init__.py ==="
cat controller/deploy/microshift-bootc/config-svc/__init__.py 2>/dev/null || echo "No __init__.py at top level"

echo ""
echo "=== pyproject.toml scripts section ==="
grep -A5 "project.scripts" controller/deploy/microshift-bootc/config-svc/pyproject.toml

Length of output: 2611


Let me verify how the service is actually launched in the container to understand why it works.


🏁 Script executed:

#!/bin/bash
echo "=== Containerfile ==="
cat controller/deploy/microshift-bootc/Containerfile

echo ""
echo "=== Searching for how app.py / config-svc is invoked ==="
rg -n "app\.py\|config-svc\|app_new" controller/deploy/microshift-bootc/Containerfile

Length of output: 2118


@mangelajo, confirmed — you're right that it works! Here's why:

The Containerfile bypasses the pyproject.toml entry point entirely. Instead of installing the package via pip, it:

  1. Copies all *.py files directly to /usr/local/lib/config-svc/
  2. Creates a bash wrapper at /usr/local/bin/config-svc that executes exec /usr/bin/python3 app.py "$@" from that directory
  3. The systemd service invokes that bash wrapper

So jumpstarter-config-svc = "app_new:main" in [project.scripts] is never called in this deployment. My original comment was technically correct that the pyproject.toml entry point is broken (since app_new.py doesn't exist), but it has no impact on the actual runtime behavior given the direct file-copy + bash wrapper deployment model.

You can either:

  • Fix it to "app:main" to keep the pyproject.toml accurate for anyone attempting a pip-based install, or
  • Leave it as-is if pyproject.toml is only used for metadata/tooling config and a pip-based install path is never intended.

Happy to close this comment if you'd like to leave it as-is!


🧠 Learnings used
Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/creating-new-drivers.mdc:0-0
Timestamp: 2026-02-04T18:12:55.555Z
Learning: Applies to python/packages/jumpstarter-driver-composite/pyproject.toml : The pyproject.toml for composite drivers should have a dependency on `jumpstarter-driver-composite`

Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2026-02-04T18:13:10.193Z
Learning: Applies to python/packages/jumpstarter-driver-*/pyproject.toml : Driver packages must register via `jumpstarter.drivers` entry point in their `pyproject.toml`

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@mangelajo, yes, exactly — that's the bash wrapper I identified:

exec /usr/bin/python3 app.py "$@"

Since the deployment always goes through that wrapper rather than the pip-installed entry point, the app_new:main reference in pyproject.toml is dead code in practice. The comment is valid from a correctness standpoint (a pip install would fail), but it has zero impact on the actual container runtime. Up to you whether you want to fix it to "app:main" for hygiene, or leave it as-is.


🧠 Learnings used
Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/creating-new-drivers.mdc:0-0
Timestamp: 2026-02-04T18:12:55.555Z
Learning: Applies to python/packages/jumpstarter-driver-composite/pyproject.toml : The pyproject.toml for composite drivers should have a dependency on `jumpstarter-driver-composite`

Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2026-02-04T18:13:10.193Z
Learning: Applies to python/packages/jumpstarter-driver-*/pyproject.toml : Driver packages must register via `jumpstarter.drivers` entry point in their `pyproject.toml`

Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 610
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:488-491
Timestamp: 2025-09-15T08:18:48.571Z
Learning: In the jumpstarter project, code review suggestions should stay focused on the specific scope of the PR. Suggestions about general improvements like timeout handling or error handling that are unrelated to the core changes being made should be avoided, even if they apply to modified code lines.

Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/creating-new-drivers.mdc:0-0
Timestamp: 2026-02-04T18:12:55.555Z
Learning: Applies to python/packages/jumpstarter-driver-*/**/{driver,driver_test}.py : Implement driver logic in the `driver.py` file and tests in `driver_test.py` file

Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2026-02-04T18:13:10.193Z
Learning: Applies to python/packages/jumpstarter-driver-*/jumpstarter_driver_*/driver.py : Each driver package must include a `driver.py` file with driver implementation in `<package>/jumpstarter_driver_<name>/driver.py`

Learnt from: CR
Repo: jumpstarter-dev/jumpstarter PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2026-02-04T18:13:10.193Z
Learning: Applies to python/packages/*/pyproject.toml : Package names use hyphens (e.g., `jumpstarter-driver-network`), while module names use underscores (e.g., `jumpstarter_driver_network`)

Comment on lines +46 to +53
[tool.setuptools]
packages = ["config-svc"]

[tool.setuptools.package-data]
config-svc = [
"templates/*.html",
"templates/*.css",
]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the actual directory/file structure
echo "Directory structure:"
tree controller/deploy/microshift-bootc/config-svc/ 2>/dev/null || \
  find controller/deploy/microshift-bootc/config-svc/ -type f -name "*.py" | head -20

Repository: jumpstarter-dev/jumpstarter

Length of output: 474


🏁 Script executed:

cat -n controller/deploy/microshift-bootc/config-svc/pyproject.toml

Repository: jumpstarter-dev/jumpstarter

Length of output: 2456


🏁 Script executed:

# Search for imports of config-svc or config_svc in the codebase
rg -r "import.*config.?svc|from.*config.?svc" --type python -i

Repository: jumpstarter-dev/jumpstarter

Length of output: 467


🏁 Script executed:

# Fix the rg pattern - search for imports of this package
rg "import.*config.*svc|from.*config.*svc" --type python

Repository: jumpstarter-dev/jumpstarter

Length of output: 101


🏁 Script executed:

# Search for app_new references
rg "app_new" --type python

Repository: jumpstarter-dev/jumpstarter

Length of output: 101


🏁 Script executed:

# Check if app_new.py exists
find controller/deploy/microshift-bootc/config-svc -name "app_new*"

Repository: jumpstarter-dev/jumpstarter

Length of output: 53


🏁 Script executed:

# Search for imports without file type filter
rg "import.*config|from.*config" | grep -i svc

Repository: jumpstarter-dev/jumpstarter

Length of output: 53


🏁 Script executed:

# Check Python files for imports
rg "^from|^import" controller/deploy/microshift-bootc/config-svc/*.py | head -30

Repository: jumpstarter-dev/jumpstarter

Length of output: 2399


🏁 Script executed:

# Look for where this package is used from
rg "config.*svc|config_svc" --type-list | grep -i text

Repository: jumpstarter-dev/jumpstarter

Length of output: 460


🏁 Script executed:

# Check if there's any build configuration that transforms names
cat controller/deploy/microshift-bootc/config-svc/__init__.py

Repository: jumpstarter-dev/jumpstarter

Length of output: 142


🏁 Script executed:

# Look for any setup.py or other config files
find controller/deploy/microshift-bootc/config-svc -name "setup.py" -o -name "setup.cfg"

Repository: jumpstarter-dev/jumpstarter

Length of output: 53


🏁 Script executed:

# Check how this package is actually deployed/used in the system
rg "config-svc|config_svc" controller/deploy/microshift-bootc/ | grep -v ".pyc" | head -20

Repository: jumpstarter-dev/jumpstarter

Length of output: 2106


Fix package configuration - Python package names cannot contain hyphens.

The [tool.setuptools] section specifies packages = ["config-svc"], but Python package names must use underscores instead of hyphens. This breaks the package for any standard Python import.

Additionally, line 34 references a non-existent entry point: jumpstarter-config-svc = "app_new:main" should be "app:main" (the file is app.py, not app_new.py).

Rename the directory from config-svc to config_svc and update the configuration:

Changes needed
 [tool.setuptools]
-packages = ["config-svc"]
+packages = ["config_svc"]
 
 [tool.setuptools.package-data]
-config-svc = [
+config_svc = [
     "templates/*.html",
     "templates/*.css",
 ]

Also fix the entry point on line 34:

 [project.scripts]
-jumpstarter-config-svc = "app_new:main"
+jumpstarter-config-svc = "app:main"

Then rename the directory:

mv controller/deploy/microshift-bootc/config-svc controller/deploy/microshift-bootc/config_svc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/config-svc/pyproject.toml` around lines 46
- 53, The package name in pyproject.toml is invalid because it uses a hyphen:
update the [tool.setuptools] packages value from "config-svc" to "config_svc"
and adjust [tool.setuptools.package-data] key accordingly so setuptools matches
the actual package name; also fix the faulty entry point "jumpstarter-config-svc
= \"app_new:main\"" to reference the real module and callable ("app:main") and
rename the directory from config-svc to config_svc (e.g., mv
controller/deploy/microshift-bootc/config-svc
controller/deploy/microshift-bootc/config_svc) so package name, package-data and
the entry_point (app:main) are consistent.

Comment on lines +38 to +39
output/qcow2/disk.qcow2: ## Build a bootable QCOW2 image from the bootc image
@echo "Building QCOW2 image from: $(BOOTC_IMG)"a
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: Extra 'a' character in echo statement.

Line 39 has $(BOOTC_IMG)"a which will print an extra 'a' at the end of the message.

🐛 Proposed fix
 output/qcow2/disk.qcow2: ## Build a bootable QCOW2 image from the bootc image
-	`@echo` "Building QCOW2 image from: $(BOOTC_IMG)"a
+	`@echo` "Building QCOW2 image from: $(BOOTC_IMG)"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output/qcow2/disk.qcow2: ## Build a bootable QCOW2 image from the bootc image
@echo "Building QCOW2 image from: $(BOOTC_IMG)"a
output/qcow2/disk.qcow2: ## Build a bootable QCOW2 image from the bootc image
`@echo` "Building QCOW2 image from: $(BOOTC_IMG)"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/Makefile` around lines 38 - 39, Remove the
stray character in the Makefile echo for the target output/qcow2/disk.qcow2: in
the recipe that echoes the BOOTC image (uses $(BOOTC_IMG)), delete the trailing
'a' so the message prints only the BOOTC image path; update the echo line in the
Makefile rule for output/qcow2/disk.qcow2 accordingly.

Comment on lines +55 to +63
**Output example:**
```
MicroShift is running in a bootc container
Hostname: jumpstarter.10.0.2.2.nip.io
Container: jumpstarter-microshift-okd
LVM disk: /var/lib/microshift-okd/lvmdisk.image
VG name: myvg1
Ports: HTTP:80, HTTPS:443, Config Service:8880
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Mark the output/diagram fences with a language.

Both fenced blocks are missing a language, so markdown lint will keep warning on them. Use text for these examples.

Also applies to: 184-195

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 56-56: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/README.md` around lines 55 - 63, Update
the markdown fenced code blocks under the "Output example:" section (and the
other example block noted) to include a language tag so the linter stops
warning; specifically change the fences from ``` to ```text for the examples
shown in README.md (the "Output example:" block and the later example around
lines mentioned in the review) so both are marked as text-coded blocks.

@mangelajo mangelajo force-pushed the microshift-bootc-image branch from ed676ed to 92c65dd Compare March 13, 2026 11:09
Add Make targets to download CentOS Stream 10 boot ISO, embed the
kickstart file using mkksiso, and upload the result to gofile.io.
Supports both x86_64 and aarch64 via the ARCH variable.

Made-with: Cursor
Copy link
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.

Actionable comments posted: 1

♻️ Duplicate comments (3)
controller/deploy/microshift-bootc/Makefile (1)

47-48: ⚠️ Potential issue | 🟡 Minor

Remove the stray a in the QCOW2 status message.

Line 48 currently prints an extra character.

Suggested patch
 output/qcow2/disk.qcow2: ## Build a bootable QCOW2 image from the bootc image
-	`@echo` "Building QCOW2 image from: $(BOOTC_IMG)"a
+	`@echo` "Building QCOW2 image from: $(BOOTC_IMG)"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/Makefile` around lines 47 - 48, The status
echo in the Makefile target output/qcow2/disk.qcow2 prints an extra stray
character; update the echo statement in that target (which references
$(BOOTC_IMG)) to remove the trailing "a" so the message reads correctly when
building the QCOW2 image.
.github/workflows/build-images.yaml (2)

46-49: ⚠️ Potential issue | 🟠 Major

Add a label for the new matrix entry to keep per-image PR builds working.

Line 78 reads matrix.label; without it, this entry can’t be selected via build-pr-images/<label> and gets skipped in label-targeted PR builds.

Suggested patch
           - image_name: jumpstarter-dev/microshift/bootc
+            label: microshift/bootc
             dockerfile: controller/deploy/microshift-bootc/Containerfile
             context: controller
             generate_installer: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-images.yaml around lines 46 - 49, The new matrix
entry with image_name "jumpstarter-dev/microshift/bootc" is missing a matrix
label, so PR builds targeted by label won't pick it up; add a `label:` key to
that matrix item (e.g., label: microshift-bootc or bootc) so it can be
referenced as `build-pr-images/<label>` in the workflow and selected via
`matrix.label`.

130-134: ⚠️ Potential issue | 🟠 Major

Installer manifest uses the wrong operator tag on pull requests.

On PRs, pushed operator images are tagged pr-<number>, but this step pins IMG to ${{ env.VERSION }}. That makes dist/install.yaml reference a tag that is typically not published for PR builds.

Suggested patch
       - name: Build operator installer manifest
         if: ${{ matrix.generate_installer && steps.check.outputs.skip != 'true' }}
         run: |
-          IMG="${{ env.QUAY_ORG }}/jumpstarter-operator:${{ env.VERSION }}"
+          if [[ "${{ github.event_name }}" == "pull_request" ]]; then
+            IMG_TAG="pr-${{ github.event.pull_request.number }}"
+          else
+            IMG_TAG="${{ env.VERSION }}"
+          fi
+          IMG="${{ env.QUAY_ORG }}/jumpstarter-operator:${IMG_TAG}"
           make -C controller/deploy/operator build-installer IMG="${IMG}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-images.yaml around lines 130 - 134, The installer
manifest step hardcodes IMG to env.VERSION which is wrong for PR builds; update
the step that defines IMG (used by make -C controller/deploy/operator
build-installer) to choose the PR image tag for pull requests (e.g. pr-<number>)
and fall back to env.VERSION otherwise—detect PRs via github.event_name ==
'pull_request' and build the tag using github.event.number (so IMG becomes
${QUAY_ORG}/jumpstarter-operator:pr-<number> for PRs), then pass that IMG into
the existing make invocation so dist/install.yaml references the actual pushed
PR image tag.
🧹 Nitpick comments (1)
controller/deploy/microshift-bootc/Makefile (1)

1-1: Expand .PHONY to include all non-file targets (clean, bootc-reload-app).

This prevents accidental file/target name collisions for operational targets.

Suggested patch
-.PHONY: help build bootc-build bootc-build-multi push bootc-push bootc-push-multi bootc-run bootc-stop bootc-sh bootc-rm build-image build-iso build-all build-all-multi push-all push-all-multi download-centos-iso build-ks-iso upload-ks-iso
+.PHONY: help build bootc-build bootc-build-multi push bootc-push bootc-push-multi bootc-run bootc-stop bootc-sh bootc-rm bootc-reload-app build-image build-iso build-all build-all-multi push-all push-all-multi download-centos-iso build-ks-iso upload-ks-iso clean
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/deploy/microshift-bootc/Makefile` at line 1, The .PHONY line is
missing non-file targets and should include the operational targets 'clean' and
'bootc-reload-app'; update the .PHONY declaration that currently lists targets
like build/bootc-build/bootc-push etc. to also list clean and bootc-reload-app
so Make treats them always as phony and avoids accidental file/target
collisions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/deploy/microshift-bootc/Makefile`:
- Around line 88-99: The Makefile currently uses a dangerous global cleanup
command (sudo losetup -D) in the default image target and build-iso target;
replace that with targeted loop-device detachment that only affects devices
backing our artifacts (e.g., query losetup -j output/qcow2/disk.qcow2 or losetup
-j output/iso/disk.iso, parse the returned /dev/loopN and call sudo losetup -d
/dev/loopN for each) and keep the existing vg removal logic (or narrow vg
selection with --select vg_name=myvg1) so build-iso and the rule that creates
output/qcow2/disk.qcow2 detach only the specific loop devices for those files
instead of detaching all loop devices on the host.

---

Duplicate comments:
In @.github/workflows/build-images.yaml:
- Around line 46-49: The new matrix entry with image_name
"jumpstarter-dev/microshift/bootc" is missing a matrix label, so PR builds
targeted by label won't pick it up; add a `label:` key to that matrix item
(e.g., label: microshift-bootc or bootc) so it can be referenced as
`build-pr-images/<label>` in the workflow and selected via `matrix.label`.
- Around line 130-134: The installer manifest step hardcodes IMG to env.VERSION
which is wrong for PR builds; update the step that defines IMG (used by make -C
controller/deploy/operator build-installer) to choose the PR image tag for pull
requests (e.g. pr-<number>) and fall back to env.VERSION otherwise—detect PRs
via github.event_name == 'pull_request' and build the tag using
github.event.number (so IMG becomes ${QUAY_ORG}/jumpstarter-operator:pr-<number>
for PRs), then pass that IMG into the existing make invocation so
dist/install.yaml references the actual pushed PR image tag.

In `@controller/deploy/microshift-bootc/Makefile`:
- Around line 47-48: The status echo in the Makefile target
output/qcow2/disk.qcow2 prints an extra stray character; update the echo
statement in that target (which references $(BOOTC_IMG)) to remove the trailing
"a" so the message reads correctly when building the QCOW2 image.

---

Nitpick comments:
In `@controller/deploy/microshift-bootc/Makefile`:
- Line 1: The .PHONY line is missing non-file targets and should include the
operational targets 'clean' and 'bootc-reload-app'; update the .PHONY
declaration that currently lists targets like build/bootc-build/bootc-push etc.
to also list clean and bootc-reload-app so Make treats them always as phony and
avoids accidental file/target collisions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d823af4-6992-4800-afae-76bcb78598af

📥 Commits

Reviewing files that changed from the base of the PR and between 92c65dd and a1d167c.

📒 Files selected for processing (2)
  • .github/workflows/build-images.yaml
  • controller/deploy/microshift-bootc/Makefile

$(BOOTC_IMG)
@echo "QCOW2 image built successfully in ./output/"

output/iso/disk.iso: ## Build a bootable ISO image from the bootc image
Copy link
Member Author

Choose a reason for hiding this comment

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

This iso version is bigger (2GB) because it contains the bootc layers embedded in the iso, the other one (Cs10+kickstart) is 1GB

upload-ks-iso: ## Upload kickstart ISO to gofile.io and print download URL
@test -f "$(KS_ISO)" || { echo "Error: $(KS_ISO) not found. Run 'make build-ks-iso' first."; exit 1; }
@echo "Uploading $(KS_ISO) to gofile.io..."
@SERVER=$$(curl -s https://api.gofile.io/servers | jq -r '.data.servers[0].name') && \
Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure how reputable gofile.io is, but I thought it's an easy way to share those isos for now, longer term we can stick them in github releases.

@github-actions
Copy link

Container Images

The following container images have been built for this PR:

Image URI
jumpstarter-controller quay.io/jumpstarter-dev/jumpstarter-controller:pr-314
jumpstarter-operator quay.io/jumpstarter-dev/jumpstarter-operator:pr-314
jumpstarter-operator-bundle quay.io/jumpstarter-dev/jumpstarter-operator-bundle:pr-314
jumpstarter quay.io/jumpstarter-dev/jumpstarter:pr-314
jumpstarter-utils quay.io/jumpstarter-dev/jumpstarter-utils:pr-314
jumpstarter-dev quay.io/jumpstarter-dev/jumpstarter-dev:pr-314
jumpstarter-devspace quay.io/jumpstarter-dev/jumpstarter-devspace:pr-314

Images expire after 7 days.

@mangelajo mangelajo requested a review from kirkbrauer March 13, 2026 15:37
@mangelajo
Copy link
Member Author

@kirkbrauer + all, it seems to work, even there are some / many improvements we can do, I suggest we just collaborate on main since I have very limited time for this now.

@mangelajo
Copy link
Member Author

This is an uploaded version of the iso image: https://gofile.io/d/wVVqfO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant