The branch main has been updated by bapt:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=328a76d17f85ff6aa6228035c4c4b989eb7534f8

commit 328a76d17f85ff6aa6228035c4c4b989eb7534f8
Author:     Baptiste Daroussin <[email protected]>
AuthorDate: 2026-06-05 20:48:18 +0000
Commit:     Baptiste Daroussin <[email protected]>
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
 }

Reply via email to