From nobody Sun Jun 28 18:40:46 2026 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gpJBl56smz6jkgv for ; Sun, 28 Jun 2026 18:40:47 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gpJBk5j7jz3Y8t for ; Sun, 28 Jun 2026 18:40:46 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1782672046; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=EnxLKLYTuBFm3h9HJ+ATCbHsrsuXOZx/668gw0Typl4=; b=rCiLEcpMb62uoZAdcRGxAz2evMAmUOeQ0ReXDv/wuJOoMDpaNURarnJkEm2LXODN9osQNx N4gSCBRkFnE88nD1sJaSKLkYakUk9tDpbha+VTQGPATXUuRDjX0Zm4uHK8fbUqt8KdWG2o TX8LuxfYW0VODbrVddI9MVdWAIhidjxPYFU/KZQq9oi7sVdqmAdL6FP+USwocmxU3vWvwC buXst0D1BQfdcPFFAUGp4v3Ur9N22Oc2IlT2xeGgdAlC/8Z9vY94cu6LJtt1GjbFmCFIBW IPeXzit1sS9CJLNGW2I0slVtaGQto98s+JXfaQF8nphJgebGaEx3DTngFfTffQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1782672046; a=rsa-sha256; cv=none; b=Ow4p5n3U11WjrN277VTIQ/fRWSYT1zJICAZGXBDX/kYJlx8ixhqXQ/jK6v/YM3q1OwCdo7 XG8t6JlCz4awqJiAoQORz7XBw2y1OJKYl4FGxLw/upitSHKGRuoWtDwjIN+sHxc4uvVpl0 BLyYbvekvioHFcGth4TvFmP0ojCS8783Tcdn6YesWXJoCa9N9BWMBcYVAVgKbH5yqJprAa infi9+jqk9ecF+IT/EqlfZ2g+p2WiiKCNmiErRvMS2CT+87ZvFSDVQgcDfm8WdMbVLeMds Bkh7ApT9mz4z1Vt1Fcbe0NFg4WYSDcLMMYL0Lh9Oi9yHanE54IJrBmKe+eAuqQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1782672046; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=EnxLKLYTuBFm3h9HJ+ATCbHsrsuXOZx/668gw0Typl4=; b=lgYOb9ZwX7+3QndarmFp0bH1O4Mws8Xv4V6eLiYW4pRpFjUrSDFb3gCeS0KWmKfSSMvTYu 7b4z8/ulycu4wVzJj/IAhcg0XsilnqX3o5YYkSaNO0TJixOAObl71tPp2rsiJ/bKmihJVc X9EX/etllynDPuozDFcDMgo2K2C8EduuGC1fuPP7GIr89qeyZV3ilEqyQ9nPUj0xtiaxPH /ShqxMGbAz3PgUYDVu4jLozp9qoDJKr0zHn4ZVlk6UHq5zoyLt8GMxfzRtbZfVoYyE0ojJ owyE7Sc+ZAMthfouykzkHarI7oUwCoW1yR/12/lud1F/WUeQs05DteqgfjRVLg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gpJBk4m8Zz7GR for ; Sun, 28 Jun 2026 18:40:46 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 1cb9e by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Sun, 28 Jun 2026 18:40:46 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Baptiste Daroussin Subject: git: bf026697c6d8 - stable/15 - nuageinit: implement MIME multipart user-data support List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bapt X-Git-Repository: src X-Git-Refname: refs/heads/stable/15 X-Git-Reftype: branch X-Git-Commit: bf026697c6d84b027678c7a9019b2b890556a6ca Auto-Submitted: auto-generated Date: Sun, 28 Jun 2026 18:40:46 +0000 Message-Id: <6a416aae.1cb9e.52d7d9c3@gitrepo.freebsd.org> The branch stable/15 has been updated by bapt: URL: https://cgit.FreeBSD.org/src/commit/?id=bf026697c6d84b027678c7a9019b2b890556a6ca commit bf026697c6d84b027678c7a9019b2b890556a6ca Author: Baptiste Daroussin AuthorDate: 2026-06-05 20:45:54 +0000 Commit: Baptiste Daroussin CommitDate: 2026-06-28 18:32:28 +0000 nuageinit: implement MIME multipart user-data support Add support for MIME multipart/mixed user-data, allowing a single user-data blob to contain multiple parts with different content types. (cherry picked from commit be711ade6f66506fb2cae9fd33b142ce910f0346) --- libexec/nuageinit/nuage.lua | 45 ++++++++++++++++++++++++++++++++++++ libexec/nuageinit/nuageinit | 38 ++++++++++++++++++++++++++++++ libexec/nuageinit/nuageinit.7 | 14 +++++++++++ libexec/nuageinit/tests/nuageinit.sh | 35 ++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua index 7fde2d936f1b..6cef5d2dd904 100644 --- a/libexec/nuageinit/nuage.lua +++ b/libexec/nuageinit/nuage.lua @@ -896,6 +896,50 @@ local function remove_fstab_entry(root, mount_point) nf:close() end +local function parse_mime_multipart(data) + local boundary = data:match("boundary=\"([^\"]+)\"") + if not boundary then + boundary = data:match("boundary=([^%s;]+)") + end + if not boundary then + return nil + end + local parts = {} + local pos = data:find("\n") or 1 + local first = data:find("--" .. boundary, pos, true) + if not first then + return nil + end + pos = data:find("\n", first) + if not pos then return nil end + pos = pos + 1 + while true do + local nextb = data:find("--" .. boundary, pos, true) + if not nextb then break end + local part = data:sub(pos, nextb - 1) + part = part:gsub("^\r?\n", ""):gsub("\r?\n$", "") + local header_end = part:find("\r?\n\r?\n") + local headers_str, body + if header_end then + headers_str = part:sub(1, header_end - 1) + body = part:sub(header_end + 2):gsub("^\r?\n", ""):gsub("\r?\n$", "") + else + body = part + end + local ct = "text/plain" + if headers_str then + local m = headers_str:match("[Cc]ontent%-[Tt]ype:%s*([^%s;]+)") + if m then ct = m:lower() end + end + table.insert(parts, {content_type = ct, body = body}) + local after = data:sub(nextb + 2 + #boundary, nextb + 3 + #boundary) + if after == "--" then break end + pos = data:find("\n", nextb) or nextb + if pos then pos = pos + 1 end + end + return parts +end + local n = { shell_escape = shell_escape, warn = warnmsg, @@ -923,6 +967,7 @@ local n = { add_fstab_entry = add_fstab_entry, remove_fstab_entry = remove_fstab_entry, write_resolv_conf = write_resolv_conf, + parse_mime_multipart = parse_mime_multipart, } return n diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit index f5a018a00793..bd72f02d4503 100755 --- a/libexec/nuageinit/nuageinit +++ b/libexec/nuageinit/nuageinit @@ -915,6 +915,44 @@ local function load_userdata() f:close() return end + if line:match("^Content%-Type: multipart/") then + local rest = f:read("*a") + f:close() + local full = line .. "\n" .. rest + local parts = nuage.parse_mime_multipart(full) + if parts then + local cc_body = nil + for _, p in ipairs(parts) do + if p.content_type == "text/cloud-config" then + cc_body = p.body + elseif p.content_type:match("x%-shellscript") or p.content_type:match("x%-sh") then + if citype ~= "postnet" then + nuage.mkdir_p(root .. "/var/cache/nuageinit") + local spath = root .. "/var/cache/nuageinit/multipart_script" + local sf = io.open(spath, "w") + if sf then + sf:write(p.body .. "\n") + sf:close() + nuage.chmod(spath, "0755") + end + end + end + end + if cc_body then + local obj = yaml.load(cc_body) + if obj then + if citype ~= "postnet" then + nuage.mkdir_p(root .. "/var/cache/nuageinit") + local tof = assert(io.open(root .. "/var/cache/nuageinit/user_data", "w")) + tof:write("#cloud-config\n" .. cc_body) + tof:close() + end + return "#cloud-config", obj + end + end + end + return nil, nil + end if citype ~= "postnet" then local content = f:read("*a") if not content or #string.gsub(content, "^%s*(.-)%s*$", "%1") == 0 then diff --git a/libexec/nuageinit/nuageinit.7 b/libexec/nuageinit/nuageinit.7 index fcccf1bef4f0..b4be4e4b2d58 100644 --- a/libexec/nuageinit/nuageinit.7 +++ b/libexec/nuageinit/nuageinit.7 @@ -551,6 +551,20 @@ A boolean to specify that the files should be created after the packages are installed and the users are created. .El .El +.Pp +Additionally, user-data can be provided as a MIME multipart message +with content type +.Qq multipart/mixed . +Each part is handled according to its +.Qq Content-Type +header. +Supported part types: +.Bl -tag -width "text/x-shellscript" +.It text/cloud-config +Processed as a cloud-config YAML document. +.It text/x-shellscript +Saved as an executable script for later execution. +.El .Sh EXAMPLES Here is an example of a YAML configuration for .Nm : diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh index 8f746599f14f..4b751dd2ca43 100644 --- a/libexec/nuageinit/tests/nuageinit.sh +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -40,6 +40,7 @@ atf_test_case config2_userdata_keyboard atf_test_case config2_userdata_ssh_authkey_fingerprints atf_test_case config2_userdata_ntp atf_test_case config2_userdata_ca_certs +atf_test_case config2_userdata_multipart atf_test_case config2_userdata_fqdn_and_hostname atf_test_case config2_userdata_write_files @@ -1274,6 +1275,39 @@ EOF true } +config2_userdata_multipart_head() +{ + atf_set "require.user" root +} +config2_userdata_multipart_body() +{ + mkdir -p media/nuageinit + setup_test_adduser + printf "{}" > media/nuageinit/meta_data.json + cat > media/nuageinit/user_data <<'EOF' +Content-Type: multipart/mixed; boundary="==BOUNDARY==" + +--==BOUNDARY== +Content-Type: text/cloud-config; charset="us-ascii" + +#cloud-config +hostname: multipart-host + +--==BOUNDARY== +Content-Type: text/x-shellscript + +#!/bin/sh +echo "multipart script executed" + +--==BOUNDARY==-- +EOF + atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 + atf_check -o inline:"hostname=\"multipart-host\"\n" cat etc/rc.conf.d/hostname + atf_check -o inline:"#!/bin/sh\necho \"multipart script executed\"\n" cat var/cache/nuageinit/multipart_script + test -x var/cache/nuageinit/multipart_script || atf_fail "multipart_script not executable" + true +} + config2_userdata_fqdn_and_hostname_body() { mkdir -p media/nuageinit @@ -1329,6 +1363,7 @@ atf_init_test_cases() atf_add_test_case config2_userdata_ssh_authkey_fingerprints atf_add_test_case config2_userdata_ntp atf_add_test_case config2_userdata_ca_certs + atf_add_test_case config2_userdata_multipart atf_add_test_case config2_userdata_fqdn_and_hostname atf_add_test_case config2_userdata_write_files }