]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
firstboot: allow configuring machine tags via firstboot
authorLennart Poettering <lennart@amutable.com>
Thu, 21 May 2026 06:39:23 +0000 (08:39 +0200)
committerLennart Poettering <lennart@amutable.com>
Thu, 21 May 2026 16:30:16 +0000 (18:30 +0200)
man/systemd-firstboot.xml
man/systemd.system-credentials.xml
src/firstboot/firstboot.c

index db6f2569a8d1f859ebc76cf62c3f8a6f6be0b3fe..252ec73feddc3ebee4d63b2f9d2b0f0dc5a98f9c 100644 (file)
@@ -67,6 +67,8 @@
 
       <listitem><para>The system hostname</para></listitem>
 
+      <listitem><para>The machine tags</para></listitem>
+
       <listitem><para>The kernel command line used when installing kernel images</para></listitem>
 
       <listitem><para>The root user's password and shell</para></listitem>
         <xi:include href="version-info.xml" xpointer="v216"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--machine-tags=<replaceable>TAG</replaceable><optional>:<replaceable>TAG</replaceable>…</optional></option></term>
+
+        <listitem><para>Set the machine tags to the specified colon-separated list. Machine tags are short
+        labels that may be used to classify and group machines for management purposes, for example to
+        identify the role a machine plays in a deployment, the fleet or organizational unit it belongs to, or
+        any other administrator-defined attribute. Each individual tag must be 1…255 characters long and
+        consist only of ASCII alphanumeric characters, <literal>-</literal> and <literal>.</literal>.  This
+        controls the <varname>TAGS=</varname> field of
+        <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
+        tags may later be queried and changed with the <command>tags</command> command of
+        <citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, and
+        matched against with the <varname>ConditionMachineTag=</varname>/<varname>AssertMachineTag=</varname>
+        unit settings, see
+        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+        <para>Unlike most other settings, machine tags are not prompted for interactively.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term>
         <term><option>--root-password-file=<replaceable>PATH</replaceable></option></term>
 
         <xi:include href="version-info.xml" xpointer="v261"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>firstboot.machine-tags</varname></term>
+
+        <listitem><para>This credential specifies the machine tags to set during first boot, as a
+        colon-separated list, equivalent to the <option>--machine-tags=</option> switch described above. The
+        tags are written into the <varname>TAGS=</varname> field of <filename>/etc/machine-info</filename>
+        (if that file is not already present), and only have an effect on first boot. If the list contains
+        invalid tags it is ignored in its entirety.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
     </variablelist>
 
     <para>Note that by default the <filename>systemd-firstboot.service</filename> unit file is set up to
index fb1377c560c7524b2e3bb6f81b628cad7a0951a4..f316961cd728ed7afc95cb63c4d0937f979af2fe 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>firstboot.machine-tags</varname></term>
+        <listitem>
+          <para>The machine tags to set, as a colon-separated list (e.g.
+          <literal>webserver:frontend:berlin</literal>). Read by
+          <citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+          and only honoured if <filename>/etc/machine-info</filename> has not been configured before. Written
+          into the <varname>TAGS=</varname> field of that file, see
+          <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+          details.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>login.issue</varname></term>
         <listitem>
index e328c8550015c5c143fa70cf7ad63b2803a381f6..e922abdd63cd3a1e4c16f64bfe0a878dc86761f8 100644 (file)
@@ -67,6 +67,7 @@ static char *arg_keymap = NULL;
 static char *arg_timezone = NULL;
 static char *arg_hostname = NULL;
 static sd_id128_t arg_machine_id = {};
+static char **arg_machine_tags = NULL;
 static char *arg_root_password = NULL;
 static char *arg_root_shell = NULL;
 static char *arg_kernel_cmdline = NULL;
@@ -98,6 +99,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_locale_messages, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_machine_tags, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep);
@@ -778,6 +780,71 @@ static int process_machine_id(int rfd) {
         return 0;
 }
 
+static int process_machine_tags(int rfd) {
+        int r;
+
+        assert(rfd >= 0);
+
+        _cleanup_free_ char *f = NULL;
+        _cleanup_close_ int pfd = chase_and_open_parent_at(
+                        /* root_fd= */ rfd,
+                        /* dir_fd= */ rfd,
+                        "/etc/machine-info",
+                        CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                        &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/machine-info parent: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/machine-info, assuming machine tags have been configured.");
+        if (r <= 0)
+                return r;
+
+        if (!arg_machine_tags) {
+                _cleanup_free_ char *tags = NULL;
+                r = read_credential("firstboot.machine-tags", (void**) &tags, /* ret_size= */ NULL);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read credential firstboot.machine-tags, ignoring: %m");
+                else {
+                        _cleanup_strv_free_ char **l = NULL;
+                        r = machine_tags_from_string(tags, /* graceful= */ false, &l);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to parse machine tags '%s', ignoring credential: %m", tags);
+                        else {
+                                strv_free_and_replace(arg_machine_tags, l);
+                                log_debug("Acquired machine tags list from credentials.");
+                        }
+                }
+        }
+
+        /* NB: We do not prompt for machine tags, at least not for now */
+
+        if (!arg_machine_tags) {
+                log_debug("Initialization of machine tags was not requested, skipping.");
+                return 0;
+        }
+
+        _cleanup_free_ char *j = strv_join(arg_machine_tags, ":");
+        if (!j)
+                return log_oom();
+
+        _cleanup_free_ char *c = strjoin("TAGS=\"", j, "\"\n");
+        if (!c)
+                return log_oom();
+
+        r = write_string_file_at(
+                        pfd,
+                        "machine-info",
+                        c,
+                        WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write /etc/machine-info: %m");
+
+        log_info("/etc/machine-info written.");
+        return 0;
+}
+
 static int prompt_root_password(int rfd, sd_varlink **mute_console_link) {
         const char *msg1, *msg2;
         int r;
@@ -1363,6 +1430,16 @@ static int parse_argv(int argc, char *argv[]) {
                                 return log_error_errno(r, "Failed to parse machine id %s.", opts.arg);
                         break;
 
+                OPTION_LONG("machine-tags", "TAG[:…]", "Set machine tags"): {
+                        _cleanup_strv_free_ char **tags = NULL;
+                        r = machine_tags_from_string(opts.arg, /* graceful= */ false, &tags);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse machine tags '%s': %m", opts.arg);
+
+                        strv_free_and_replace(arg_machine_tags, tags);
+                        break;
+                }
+
                 OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"):
                         r = free_and_strdup_warn(&arg_root_password, opts.arg);
                         if (r < 0)
@@ -1693,6 +1770,10 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
+        r = process_machine_tags(rfd);
+        if (r < 0)
+                return r;
+
         return 0;
 }