diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml
index db6f2569a8..252ec73fed 100644
--- a/man/systemd-firstboot.xml
+++ b/man/systemd-firstboot.xml
@@ -67,6 +67,8 @@
The system hostname
+ The machine tags
+
The kernel command line used when installing kernel imagesThe root user's password and shell
@@ -198,6 +200,27 @@
+
+
+
+ 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, - and .. This
+ controls the TAGS= field of
+ machine-info5. The
+ tags may later be queried and changed with the tags command of
+ hostnamectl1, and
+ matched against with the ConditionMachineTag=/AssertMachineTag=
+ unit settings, see
+ systemd.unit5.
+
+ Unlike most other settings, machine tags are not prompted for interactively.
+
+
+
+
@@ -458,6 +481,18 @@
+
+
+ firstboot.machine-tags
+
+ This credential specifies the machine tags to set during first boot, as a
+ colon-separated list, equivalent to the switch described above. The
+ tags are written into the TAGS= field of /etc/machine-info
+ (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.
+
+
+ Note that by default the systemd-firstboot.service unit file is set up to
diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml
index fb1377c560..f316961cd7 100644
--- a/man/systemd.system-credentials.xml
+++ b/man/systemd.system-credentials.xml
@@ -91,6 +91,21 @@
+
+ firstboot.machine-tags
+
+ The machine tags to set, as a colon-separated list (e.g.
+ webserver:frontend:berlin). Read by
+ systemd-firstboot1,
+ and only honoured if /etc/machine-info has not been configured before. Written
+ into the TAGS= field of that file, see
+ machine-info5 for
+ details.
+
+
+
+
+
login.issue
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index e328c85500..e922abdd63 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -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;
}