Add image name input to cloud-init build process and update handling

Enhance the dashboard UI by introducing an optional input field for the image name in the cloud-init build form. Update the API to process the image name, ensuring it is sanitized and included in the build request. Modify the build script to utilize the provided image name, allowing for customized output filenames during the image creation process. This improves user experience by offering more flexibility in naming cloud-init images.
This commit is contained in:
nearxos
2026-02-23 10:14:49 +02:00
parent 0bbd62213c
commit e13ad3d8f9
4 changed files with 28 additions and 1 deletions

View File

@@ -1636,12 +1636,15 @@ def api_build_cloudinit():
meta_data = body.get("meta_data") or DEFAULT_META_DATA meta_data = body.get("meta_data") or DEFAULT_META_DATA
network_config = body.get("network_config") or DEFAULT_NETWORK_CONFIG network_config = body.get("network_config") or DEFAULT_NETWORK_CONFIG
set_as_golden_after = bool(body.get("set_as_golden_after")) set_as_golden_after = bool(body.get("set_as_golden_after"))
image_name = (body.get("image_name") or "").strip()[:64]
image_name = re.sub(r"[^\w\-]", "", image_name) # only alphanumeric, underscore, dash
try: try:
BUILD_REQUEST_FILE.parent.mkdir(parents=True, exist_ok=True) BUILD_REQUEST_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(BUILD_REQUEST_FILE, "w") as f: with open(BUILD_REQUEST_FILE, "w") as f:
json.dump({ json.dump({
"url": url, "url": url,
"variant": variant, "variant": variant,
"image_name": image_name or None,
"user_data": user_data, "user_data": user_data,
"meta_data": meta_data, "meta_data": meta_data,
"network_config": network_config, "network_config": network_config,

View File

@@ -70,6 +70,11 @@
<select id="buildVariant"><option value="desktop" selected>Desktop (recommended)</option><option value="lite">Lite</option><option value="full">Full</option></select> <select id="buildVariant"><option value="desktop" selected>Desktop (recommended)</option><option value="lite">Lite</option><option value="full">Full</option></select>
<span id="buildRaspiosUrl" class="mono" style="margin-left:0.5rem;"></span> <span id="buildRaspiosUrl" class="mono" style="margin-left:0.5rem;"></span>
</div> </div>
<div style="margin-bottom:0.5rem;">
<label>Image name (optional):</label>
<input type="text" id="buildImageName" placeholder="e.g. reterminal-kiosk" style="width:12rem; margin-left:0.25rem;" />
<span class="mono" style="font-size:0.85rem; margin-left:0.25rem;">+ date suffix</span>
</div>
<div style="margin-bottom:0.5rem;"><label><input type="checkbox" id="buildSetGolden" /> Set as golden after build</label></div> <div style="margin-bottom:0.5rem;"><label><input type="checkbox" id="buildSetGolden" /> Set as golden after build</label></div>
<div style="margin-bottom:0.75rem;"><button type="button" id="buildCloudInitBtn" class="btn btn-primary">Download &amp; build</button></div> <div style="margin-bottom:0.75rem;"><button type="button" id="buildCloudInitBtn" class="btn btn-primary">Download &amp; build</button></div>
<div id="buildCloudInitStatus" class="mono" style="min-height:1.2em;"></div> <div id="buildCloudInitStatus" class="mono" style="min-height:1.2em;"></div>
@@ -143,7 +148,9 @@
function startBuild() { function startBuild() {
var btn = document.getElementById('buildCloudInitBtn'); var btn = document.getElementById('buildCloudInitBtn');
if (btn) btn.disabled = true; if (btn) btn.disabled = true;
var nameEl = document.getElementById('buildImageName');
var body = { variant: document.getElementById('buildVariant').value, set_as_golden_after: document.getElementById('buildSetGolden').checked }; var body = { variant: document.getElementById('buildVariant').value, set_as_golden_after: document.getElementById('buildSetGolden').checked };
if (nameEl && nameEl.value.trim()) body.image_name = nameEl.value.trim();
body.user_data = document.getElementById('buildUserData').value.trim(); body.user_data = document.getElementById('buildUserData').value.trim();
body.meta_data = document.getElementById('buildMetaData').value.trim(); body.meta_data = document.getElementById('buildMetaData').value.trim();
body.network_config = document.getElementById('buildNetworkConfig').value.trim(); body.network_config = document.getElementById('buildNetworkConfig').value.trim();

View File

@@ -437,6 +437,11 @@
</select> </select>
<span id="buildRaspiosUrl" class="backups-mono" style="font-size:0.8rem; margin-left:0.5rem;"></span> <span id="buildRaspiosUrl" class="backups-mono" style="font-size:0.8rem; margin-left:0.5rem;"></span>
</div> </div>
<div style="margin-bottom:0.5rem;">
<label>Image name (optional): </label>
<input type="text" id="buildImageName" placeholder="e.g. reterminal-kiosk" style="width:12rem;" />
<span class="backups-mono" style="font-size:0.8rem; margin-left:0.5rem;">+ date suffix (e.g. 20251204-143022)</span>
</div>
<div style="margin-bottom:0.5rem;"> <div style="margin-bottom:0.5rem;">
<label><input type="checkbox" id="buildSetGolden" /> Set as golden image after build</label> <label><input type="checkbox" id="buildSetGolden" /> Set as golden image after build</label>
<span class="backups-mono" style="font-size:0.8rem;"> (use for Deploy without clicking manually)</span> <span class="backups-mono" style="font-size:0.8rem;"> (use for Deploy without clicking manually)</span>
@@ -913,9 +918,11 @@
var md = document.getElementById('buildMetaData'); var md = document.getElementById('buildMetaData');
var nc = document.getElementById('buildNetworkConfig'); var nc = document.getElementById('buildNetworkConfig');
var setGolden = document.getElementById('buildSetGolden'); var setGolden = document.getElementById('buildSetGolden');
var nameEl = document.getElementById('buildImageName');
var body = { var body = {
variant: getBuildVariant(), variant: getBuildVariant(),
set_as_golden_after: setGolden && setGolden.checked, set_as_golden_after: setGolden && setGolden.checked,
image_name: (nameEl && nameEl.value.trim()) ? nameEl.value.trim() : undefined,
user_data: (ud && ud.value.trim()) ? ud.value.trim() : undefined, user_data: (ud && ud.value.trim()) ? ud.value.trim() : undefined,
meta_data: (md && md.value.trim()) ? md.value.trim() : undefined, meta_data: (md && md.value.trim()) ? md.value.trim() : undefined,
network_config: (nc && nc.value.trim()) ? nc.value.trim() : undefined network_config: (nc && nc.value.trim()) ? nc.value.trim() : undefined

View File

@@ -47,14 +47,24 @@ with open(f"{out}/url", "w") as f:
f.write(d.get("url", "")) f.write(d.get("url", ""))
with open(f"{out}/set_golden", "w") as f: with open(f"{out}/set_golden", "w") as f:
f.write("1" if d.get("set_as_golden_after") else "0") f.write("1" if d.get("set_as_golden_after") else "0")
name = (d.get("image_name") or "").strip()[:64]
name = "".join(c for c in name if c.isalnum() or c in "_-")
with open(f"{out}/image_name", "w") as f:
f.write(name)
PY PY
URL=$(cat "$TEMP_DIR/url") URL=$(cat "$TEMP_DIR/url")
VARIANT=$(cat "$TEMP_DIR/variant") VARIANT=$(cat "$TEMP_DIR/variant")
SET_AS_GOLDEN=$(cat "$TEMP_DIR/set_golden") SET_AS_GOLDEN=$(cat "$TEMP_DIR/set_golden")
IMAGE_NAME="$(cat "$TEMP_DIR/image_name" 2>/dev/null || true)"
[[ -n "$URL" ]] || { write_status "error" "" "" "Missing url in request"; exit 1; } [[ -n "$URL" ]] || { write_status "error" "" "" "Missing url in request"; exit 1; }
OUT_NAME="raspios-${VARIANT}-cloudinit-$(date +%Y%m%d-%H%M%S).img" DATE_SUFFIX="$(date +%Y%m%d-%H%M%S)"
if [[ -n "$IMAGE_NAME" ]]; then
OUT_NAME="${IMAGE_NAME}-${DATE_SUFFIX}.img"
else
OUT_NAME="raspios-${VARIANT}-cloudinit-${DATE_SUFFIX}.img"
fi
OUT_PATH="$OUTPUT_DIR/$OUT_NAME" OUT_PATH="$OUTPUT_DIR/$OUT_NAME"
BASENAME=$(basename "$URL") BASENAME=$(basename "$URL")