git: 328a76d17f85 - main - nuageinit: implement power_state_change and locale support
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 06 Jun 2026 06:14:18 UTC
The branch main has been updated by bapt:
URL: https://cgit.FreeBSD.org/src/commit/?id=328a76d17f85ff6aa6228035c4c4b989eb7534f8
commit 328a76d17f85ff6aa6228035c4c4b989eb7534f8
Author: Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-06-05 20:48:18 +0000
Commit: Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-06-05 20:48:18 +0000
nuageinit: implement power_state_change and locale support
---
libexec/nuageinit/nuageinit | 61 ++++++++++++++++++++++++++++++++++++
libexec/nuageinit/nuageinit.7 | 49 +++++++++++++++++++++++++++++
libexec/nuageinit/tests/nuageinit.sh | 56 +++++++++++++++++++++++++++++++++
3 files changed, 166 insertions(+)
diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit
index bd72f02d4503..8a9c5c022862 100755
--- a/libexec/nuageinit/nuageinit
+++ b/libexec/nuageinit/nuageinit
@@ -637,6 +637,28 @@ local function keyboard(obj)
f:close()
end
+local function locale(obj)
+ if obj.locale == nil then return end
+ local root = os.getenv("NUAGE_FAKE_ROOTDIR")
+ if not root then root = "" end
+ local profile_path = root .. "/etc/profile"
+ local f = io.open(profile_path, "a")
+ if not f then
+ nuage.warn("unable to open " .. profile_path .. " for writing")
+ return
+ end
+ if type(obj.locale) == "string" then
+ f:write("export LANG=" .. obj.locale .. "\n")
+ elseif type(obj.locale) == "table" then
+ for k, v in pairs(obj.locale) do
+ f:write("export " .. k .. "=" .. v .. "\n")
+ end
+ else
+ nuage.warn("locale: invalid type " .. type(obj.locale) .. ", expecting string or object")
+ end
+ f:close()
+end
+
local function mounts(obj)
if obj.mounts == nil then return end
for _, m in ipairs(obj.mounts) do
@@ -725,6 +747,43 @@ local function packages(obj)
end
end
+local function power_state_change(obj)
+ if obj.power_state == nil then return end
+ local ps = obj.power_state
+ local delay = ps.delay or "now"
+ local mode = ps.mode or "poweroff"
+ local message = ps.message
+ local condition = ps.condition
+ if condition == nil then condition = true end
+
+ -- Evaluate condition
+ if condition == false then return end
+ if type(condition) == "string" then
+ local ret = os.execute(condition)
+ if not ret then return end
+ end
+
+ -- Map mode to shutdown flag
+ local mode_map = {poweroff = "p", reboot = "r", halt = "h"}
+ local flag = mode_map[mode]
+ if not flag then
+ nuage.warn("power_state: invalid mode '" .. mode .. "', using poweroff")
+ flag = "p"
+ end
+
+ -- Build shutdown command
+ local cmd = "shutdown -" .. flag .. " " .. delay
+ if message then
+ cmd = cmd .. " " .. nuage.shell_escape(message)
+ end
+
+ if os.getenv("NUAGE_RUN_TESTS") then
+ print(cmd)
+ return
+ end
+ os.execute(cmd)
+end
+
local function chpasswd(obj)
if obj.chpasswd == nil then return end
nuage.chpasswd(obj.chpasswd)
@@ -1018,6 +1077,7 @@ elseif line == "#cloud-config" then
network_config,
resolv_conf,
keyboard,
+ locale,
disable_root,
ssh_pwauth,
runcmd,
@@ -1030,6 +1090,7 @@ elseif line == "#cloud-config" then
users,
chpasswd,
write_files_deferred,
+ power_state_change,
}
local calls_table = pre_network_calls
diff --git a/libexec/nuageinit/nuageinit.7 b/libexec/nuageinit/nuageinit.7
index b4be4e4b2d58..6d0f8ae2f41c 100644
--- a/libexec/nuageinit/nuageinit.7
+++ b/libexec/nuageinit/nuageinit.7
@@ -212,6 +212,16 @@ A list of IP/netmask sortlist entries.
.It options
A dictionary of resolver options.
.El
+.It Ic locale
+Set the system locale by appending
+.Qq Cm export
+statements to
+.Pa /etc/profile .
+.Pp
+If the value is a string, it is used as the
+.Dq Cm LANG
+value.
+If the value is an object mapping, each key-value pair is exported.
.It Ic keyboard
An object configuring the keyboard layout.
.Pp
@@ -442,6 +452,45 @@ List of packages to be installed.
Update the remote package metadata.
.It Ic package_upgrade
Upgrade the packages installed to their latest version.
+.It Ic power_state
+An object controlling the power state of the instance after configuration.
+The following keys are recognized:
+.Bl -tag -width "condition"
+.It Ic delay
+Time to wait before the action.
+Can be
+.Qq now
+or a time accepted by
+.Xr shutdown 8
+(e.g.,
+.Qq +5
+for five minutes).
+Defaults to
+.Qq now .
+.It Ic mode
+The action to take:
+.Qq poweroff ,
+.Qq reboot ,
+or
+.Qq halt .
+Defaults to
+.Qq poweroff .
+.It Ic message
+Optional message to display to users.
+.It Ic timeout
+Not supported on
+.Fx ,
+silently ignored.
+.It Ic condition
+Boolean or command string.
+If
+.Qq false ,
+the action is skipped.
+If a string, it is run as a command; the action is only taken if the command
+succeeds.
+Defaults to
+.Qq true .
+.El
.It Ic users
Specify a list of users to be created:
.Bl -tag -width "ssh_authorized_keys"
diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh
index 4b751dd2ca43..21cd2e8f17c5 100644
--- a/libexec/nuageinit/tests/nuageinit.sh
+++ b/libexec/nuageinit/tests/nuageinit.sh
@@ -41,6 +41,8 @@ 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_power_state
+atf_test_case config2_userdata_locale
atf_test_case config2_userdata_fqdn_and_hostname
atf_test_case config2_userdata_write_files
@@ -1308,6 +1310,58 @@ EOF
true
}
+config2_userdata_power_state_head()
+{
+ atf_set "require.user" root
+}
+config2_userdata_power_state_body()
+{
+ mkdir -p media/nuageinit
+ setup_test_adduser
+ export NUAGE_RUN_TESTS=1
+ printf "{}" > media/nuageinit/meta_data.json
+ cat > media/nuageinit/user_data <<EOF
+#cloud-config
+power_state:
+ delay: "+5"
+ mode: reboot
+ message: "Rebooting after configuration is complete"
+ timeout: 30
+ condition: true
+EOF
+ atf_check -o inline:"shutdown -r +5 'Rebooting after configuration is complete'\n" \
+ /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet
+ true
+}
+
+config2_userdata_locale_head()
+{
+ atf_set "require.user" root
+}
+config2_userdata_locale_body()
+{
+ mkdir -p media/nuageinit
+ setup_test_adduser
+ printf "{}" > media/nuageinit/meta_data.json
+ cat > media/nuageinit/user_data <<EOF
+#cloud-config
+locale: fr_FR.UTF-8
+EOF
+ atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2
+ atf_check -o inline:"export LANG=fr_FR.UTF-8\n" cat etc/profile
+
+ cat > media/nuageinit/user_data <<EOF
+#cloud-config
+locale:
+ LANG: de_DE.UTF-8
+ LC_ALL: de_DE.UTF-8
+EOF
+ atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2
+ atf_check -o match:"export LANG=de_DE.UTF-8" cat etc/profile
+ atf_check -o match:"export LC_ALL=de_DE.UTF-8" cat etc/profile
+ true
+}
+
config2_userdata_fqdn_and_hostname_body()
{
mkdir -p media/nuageinit
@@ -1364,6 +1418,8 @@ atf_init_test_cases()
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_power_state
+ atf_add_test_case config2_userdata_locale
atf_add_test_case config2_userdata_fqdn_and_hostname
atf_add_test_case config2_userdata_write_files
}