1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 /* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
27 * removed from glibc at some point. As part of the removal, defines for
28 * crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
30 * Newer versions of glibc (v2.0+) already ship crypt.h with a definition
31 * of crypt(3) as well, so we simply include it if it is present. MariaDB,
32 * MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
33 * same way since ages without any problems.
40 #include "alloc-util.h"
41 #include "ask-password-api.h"
46 #include "hostname-util.h"
47 #include "locale-util.h"
49 #include "parse-util.h"
50 #include "path-util.h"
51 #include "proc-cmdline.h"
52 #include "random-util.h"
53 #include "string-util.h"
55 #include "terminal-util.h"
56 #include "time-util.h"
57 #include "umask-util.h"
58 #include "user-util.h"
60 static char *arg_root
= NULL
;
61 static char *arg_locale
= NULL
; /* $LANG */
62 static char *arg_keymap
= NULL
;
63 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
64 static char *arg_timezone
= NULL
;
65 static char *arg_hostname
= NULL
;
66 static sd_id128_t arg_machine_id
= {};
67 static char *arg_root_password
= NULL
;
68 static bool arg_prompt_locale
= false;
69 static bool arg_prompt_keymap
= false;
70 static bool arg_prompt_timezone
= false;
71 static bool arg_prompt_hostname
= false;
72 static bool arg_prompt_root_password
= false;
73 static bool arg_copy_locale
= false;
74 static bool arg_copy_keymap
= false;
75 static bool arg_copy_timezone
= false;
76 static bool arg_copy_root_password
= false;
78 static bool press_any_key(void) {
82 printf("-- Press any key to proceed --");
85 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
93 static void print_welcome(void) {
94 _cleanup_free_
char *pretty_name
= NULL
;
95 const char *os_release
= NULL
;
96 static bool done
= false;
102 os_release
= prefix_roota(arg_root
, "/etc/os-release");
103 r
= parse_env_file(os_release
, NEWLINE
,
104 "PRETTY_NAME", &pretty_name
,
108 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
109 r
= parse_env_file(os_release
, NEWLINE
,
110 "PRETTY_NAME", &pretty_name
,
114 if (r
< 0 && r
!= -ENOENT
)
115 log_warning_errno(r
, "Failed to read os-release file: %m");
117 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
118 isempty(pretty_name
) ? "Linux" : pretty_name
);
125 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
126 unsigned n
, per_column
, i
, j
;
127 unsigned break_lines
, break_modulo
;
129 assert(n_columns
> 0);
132 per_column
= DIV_ROUND_UP(n
, n_columns
);
134 break_lines
= lines();
138 /* The first page gets two extra lines, since we want to show
140 break_modulo
= break_lines
;
141 if (break_modulo
> 3)
144 for (i
= 0; i
< per_column
; i
++) {
146 for (j
= 0; j
< n_columns
; j
++) {
147 _cleanup_free_
char *e
= NULL
;
149 if (j
* per_column
+ i
>= n
)
152 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
156 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
161 /* on the first screen we reserve 2 extra lines for the title */
162 if (i
% break_lines
== break_modulo
) {
163 if (!press_any_key())
171 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
179 _cleanup_free_
char *p
= NULL
;
182 r
= ask_string(&p
, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET
), text
);
184 return log_error_errno(r
, "Failed to query user: %m");
187 log_warning("No data entered, skipping.");
191 r
= safe_atou(p
, &u
);
195 if (u
<= 0 || u
> strv_length(l
)) {
196 log_error("Specified entry number out of range.");
200 log_info("Selected '%s'.", l
[u
-1]);
212 log_error("Entered data invalid.");
223 static int prompt_locale(void) {
224 _cleanup_strv_free_
char **locales
= NULL
;
227 if (arg_locale
|| arg_locale_messages
)
230 if (!arg_prompt_locale
)
233 r
= get_locales(&locales
);
235 return log_error_errno(r
, "Cannot query locales list: %m");
239 printf("\nAvailable Locales:\n\n");
240 r
= show_menu(locales
, 3, 22, 60);
246 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
250 if (isempty(arg_locale
))
253 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
260 static int process_locale(void) {
261 const char *etc_localeconf
;
266 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
267 if (laccess(etc_localeconf
, F_OK
) >= 0)
270 if (arg_copy_locale
&& arg_root
) {
272 mkdir_parents(etc_localeconf
, 0755);
273 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0, COPY_REFLINK
);
276 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
278 log_info("%s copied.", etc_localeconf
);
287 if (!isempty(arg_locale
))
288 locales
[i
++] = strjoina("LANG=", arg_locale
);
289 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
290 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
297 mkdir_parents(etc_localeconf
, 0755);
298 r
= write_env_file(etc_localeconf
, locales
);
300 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
302 log_info("%s written.", etc_localeconf
);
306 static int prompt_keymap(void) {
307 _cleanup_strv_free_
char **kmaps
= NULL
;
313 if (!arg_prompt_keymap
)
316 r
= get_keymaps(&kmaps
);
317 if (r
== -ENOENT
) /* no keymaps installed */
320 return log_error_errno(r
, "Failed to read keymaps: %m");
324 printf("\nAvailable keymaps:\n\n");
325 r
= show_menu(kmaps
, 3, 22, 60);
331 return prompt_loop("Please enter system keymap name or number",
332 kmaps
, keymap_is_valid
, &arg_keymap
);
335 static int process_keymap(void) {
336 const char *etc_vconsoleconf
;
340 etc_vconsoleconf
= prefix_roota(arg_root
, "/etc/vconsole.conf");
341 if (laccess(etc_vconsoleconf
, F_OK
) >= 0)
344 if (arg_copy_keymap
&& arg_root
) {
346 mkdir_parents(etc_vconsoleconf
, 0755);
347 r
= copy_file("/etc/vconsole.conf", etc_vconsoleconf
, 0, 0644, 0, COPY_REFLINK
);
350 return log_error_errno(r
, "Failed to copy %s: %m", etc_vconsoleconf
);
352 log_info("%s copied.", etc_vconsoleconf
);
359 return 0; /* don't fail if no keymaps are installed */
363 if (isempty(arg_keymap
))
366 keymap
= STRV_MAKE(strjoina("KEYMAP=", arg_keymap
));
368 r
= mkdir_parents(etc_vconsoleconf
, 0755);
370 return log_error_errno(r
, "Failed to create the parent directory of %s: %m", etc_vconsoleconf
);
372 r
= write_env_file(etc_vconsoleconf
, keymap
);
374 return log_error_errno(r
, "Failed to write %s: %m", etc_vconsoleconf
);
376 log_info("%s written.", etc_vconsoleconf
);
380 static int prompt_timezone(void) {
381 _cleanup_strv_free_
char **zones
= NULL
;
387 if (!arg_prompt_timezone
)
390 r
= get_timezones(&zones
);
392 return log_error_errno(r
, "Cannot query timezone list: %m");
396 printf("\nAvailable Time Zones:\n\n");
397 r
= show_menu(zones
, 3, 22, 30);
403 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
410 static int process_timezone(void) {
411 const char *etc_localtime
, *e
;
414 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
415 if (laccess(etc_localtime
, F_OK
) >= 0)
418 if (arg_copy_timezone
&& arg_root
) {
419 _cleanup_free_
char *p
= NULL
;
421 r
= readlink_malloc("/etc/localtime", &p
);
424 return log_error_errno(r
, "Failed to read host timezone: %m");
426 mkdir_parents(etc_localtime
, 0755);
427 if (symlink(p
, etc_localtime
) < 0)
428 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
430 log_info("%s copied.", etc_localtime
);
435 r
= prompt_timezone();
439 if (isempty(arg_timezone
))
442 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
444 mkdir_parents(etc_localtime
, 0755);
445 if (symlink(e
, etc_localtime
) < 0)
446 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
448 log_info("%s written", etc_localtime
);
452 static int prompt_hostname(void) {
458 if (!arg_prompt_hostname
)
465 _cleanup_free_
char *h
= NULL
;
467 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET
));
469 return log_error_errno(r
, "Failed to query hostname: %m");
472 log_warning("No hostname entered, skipping.");
476 if (!hostname_is_valid(h
, true)) {
477 log_error("Specified hostname invalid.");
481 /* Get rid of the trailing dot that we allow, but don't want to see */
482 arg_hostname
= hostname_cleanup(h
);
490 static int process_hostname(void) {
491 const char *etc_hostname
;
494 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
495 if (laccess(etc_hostname
, F_OK
) >= 0)
498 r
= prompt_hostname();
502 if (isempty(arg_hostname
))
505 mkdir_parents(etc_hostname
, 0755);
506 r
= write_string_file(etc_hostname
, arg_hostname
,
507 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
509 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
511 log_info("%s written.", etc_hostname
);
515 static int process_machine_id(void) {
516 const char *etc_machine_id
;
517 char id
[SD_ID128_STRING_MAX
];
520 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
521 if (laccess(etc_machine_id
, F_OK
) >= 0)
524 if (sd_id128_is_null(arg_machine_id
))
527 mkdir_parents(etc_machine_id
, 0755);
528 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
),
529 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
531 return log_error_errno(r
, "Failed to write machine id: %m");
533 log_info("%s written.", etc_machine_id
);
537 static int prompt_root_password(void) {
538 const char *msg1
, *msg2
, *etc_shadow
;
541 if (arg_root_password
)
544 if (!arg_prompt_root_password
)
547 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
548 if (laccess(etc_shadow
, F_OK
) >= 0)
554 msg1
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
555 msg2
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter new root password again: ");
558 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
560 r
= ask_password_tty(-1, msg1
, NULL
, 0, 0, NULL
, &a
);
562 return log_error_errno(r
, "Failed to query root password: %m");
565 log_warning("No password entered, skipping.");
569 r
= ask_password_tty(-1, msg2
, NULL
, 0, 0, NULL
, &b
);
571 return log_error_errno(r
, "Failed to query root password: %m");
574 log_error("Entered passwords did not match, please try again.");
578 arg_root_password
= a
;
586 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
587 _cleanup_fclose_
FILE *f
= NULL
;
594 f
= fopen(path
, "wex");
598 r
= putspent_sane(p
, f
);
602 return fflush_sync_and_check(f
);
605 static int process_root_password(void) {
607 static const char table
[] =
608 "abcdefghijklmnopqrstuvwxyz"
609 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
614 .sp_namp
= (char*) "root",
620 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
623 _cleanup_close_
int lock
= -1;
629 const char *etc_shadow
;
632 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
633 if (laccess(etc_shadow
, F_OK
) >= 0)
636 mkdir_parents(etc_shadow
, 0755);
638 lock
= take_etc_passwd_lock(arg_root
);
640 return log_error_errno(lock
, "Failed to take a lock: %m");
642 if (arg_copy_root_password
&& arg_root
) {
646 p
= getspnam("root");
647 if (p
|| errno
!= ENOENT
) {
652 return log_error_errno(errno
, "Failed to find shadow entry for root: %m");
655 r
= write_root_shadow(etc_shadow
, p
);
657 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
659 log_info("%s copied.", etc_shadow
);
664 r
= prompt_root_password();
668 if (!arg_root_password
)
671 r
= acquire_random_bytes(raw
, 16, true);
673 return log_error_errno(r
, "Failed to get salt: %m");
675 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
676 assert_cc(sizeof(table
) == 64 + 1);
677 j
= stpcpy(salt
, "$6$");
678 for (i
= 0; i
< 16; i
++)
679 j
[i
] = table
[raw
[i
] & 63];
684 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
689 return log_error_errno(errno
, "Failed to encrypt password: %m");
692 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
694 r
= write_root_shadow(etc_shadow
, &item
);
696 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
698 log_info("%s written.", etc_shadow
);
702 static void help(void) {
703 printf("%s [OPTIONS...]\n\n"
704 "Configures basic settings of the system.\n\n"
705 " -h --help Show this help\n"
706 " --version Show package version\n"
707 " --root=PATH Operate on an alternate filesystem root\n"
708 " --locale=LOCALE Set primary locale (LANG=)\n"
709 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
710 " --keymap=KEYMAP Set keymap\n"
711 " --timezone=TIMEZONE Set timezone\n"
712 " --hostname=NAME Set host name\n"
713 " --machine-ID=ID Set machine ID\n"
714 " --root-password=PASSWORD Set root password\n"
715 " --root-password-file=FILE Set root password from file\n"
716 " --prompt-locale Prompt the user for locale settings\n"
717 " --prompt-keymap Prompt the user for keymap settings\n"
718 " --prompt-timezone Prompt the user for timezone\n"
719 " --prompt-hostname Prompt the user for hostname\n"
720 " --prompt-root-password Prompt the user for root password\n"
721 " --prompt Prompt for all of the above\n"
722 " --copy-locale Copy locale from host\n"
723 " --copy-keymap Copy keymap from host\n"
724 " --copy-timezone Copy timezone from host\n"
725 " --copy-root-password Copy root password from host\n"
726 " --copy Copy locale, keymap, timezone, root password\n"
727 " --setup-machine-id Generate a new random machine ID\n"
728 , program_invocation_short_name
);
731 static int parse_argv(int argc
, char *argv
[]) {
743 ARG_ROOT_PASSWORD_FILE
,
749 ARG_PROMPT_ROOT_PASSWORD
,
754 ARG_COPY_ROOT_PASSWORD
,
755 ARG_SETUP_MACHINE_ID
,
758 static const struct option options
[] = {
759 { "help", no_argument
, NULL
, 'h' },
760 { "version", no_argument
, NULL
, ARG_VERSION
},
761 { "root", required_argument
, NULL
, ARG_ROOT
},
762 { "locale", required_argument
, NULL
, ARG_LOCALE
},
763 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
764 { "keymap", required_argument
, NULL
, ARG_KEYMAP
},
765 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
766 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
767 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
768 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
769 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
770 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
771 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
772 { "prompt-keymap", no_argument
, NULL
, ARG_PROMPT_KEYMAP
},
773 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
774 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
775 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
776 { "copy", no_argument
, NULL
, ARG_COPY
},
777 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
778 { "copy-keymap", no_argument
, NULL
, ARG_COPY_KEYMAP
},
779 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
780 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
781 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
790 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
802 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
808 if (!locale_is_valid(optarg
)) {
809 log_error("Locale %s is not valid.", optarg
);
813 r
= free_and_strdup(&arg_locale
, optarg
);
819 case ARG_LOCALE_MESSAGES
:
820 if (!locale_is_valid(optarg
)) {
821 log_error("Locale %s is not valid.", optarg
);
825 r
= free_and_strdup(&arg_locale_messages
, optarg
);
832 if (!keymap_is_valid(optarg
)) {
833 log_error("Keymap %s is not valid.", optarg
);
837 r
= free_and_strdup(&arg_keymap
, optarg
);
844 if (!timezone_is_valid(optarg
)) {
845 log_error("Timezone %s is not valid.", optarg
);
849 r
= free_and_strdup(&arg_timezone
, optarg
);
855 case ARG_ROOT_PASSWORD
:
856 r
= free_and_strdup(&arg_root_password
, optarg
);
861 case ARG_ROOT_PASSWORD_FILE
:
862 arg_root_password
= mfree(arg_root_password
);
864 r
= read_one_line_file(optarg
, &arg_root_password
);
866 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
871 if (!hostname_is_valid(optarg
, true)) {
872 log_error("Host name %s is not valid.", optarg
);
876 hostname_cleanup(optarg
);
877 r
= free_and_strdup(&arg_hostname
, optarg
);
884 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
885 log_error("Failed to parse machine id %s.", optarg
);
892 arg_prompt_locale
= arg_prompt_keymap
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
895 case ARG_PROMPT_LOCALE
:
896 arg_prompt_locale
= true;
899 case ARG_PROMPT_KEYMAP
:
900 arg_prompt_keymap
= true;
903 case ARG_PROMPT_TIMEZONE
:
904 arg_prompt_timezone
= true;
907 case ARG_PROMPT_HOSTNAME
:
908 arg_prompt_hostname
= true;
911 case ARG_PROMPT_ROOT_PASSWORD
:
912 arg_prompt_root_password
= true;
916 arg_copy_locale
= arg_copy_keymap
= arg_copy_timezone
= arg_copy_root_password
= true;
919 case ARG_COPY_LOCALE
:
920 arg_copy_locale
= true;
923 case ARG_COPY_KEYMAP
:
924 arg_copy_keymap
= true;
927 case ARG_COPY_TIMEZONE
:
928 arg_copy_timezone
= true;
931 case ARG_COPY_ROOT_PASSWORD
:
932 arg_copy_root_password
= true;
935 case ARG_SETUP_MACHINE_ID
:
937 r
= sd_id128_randomize(&arg_machine_id
);
939 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
947 assert_not_reached("Unhandled option");
953 int main(int argc
, char *argv
[]) {
957 r
= parse_argv(argc
, argv
);
961 log_set_target(LOG_TARGET_AUTO
);
962 log_parse_environment();
967 r
= proc_cmdline_get_bool("systemd.firstboot", &enabled
);
969 log_error_errno(r
, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
972 if (r
> 0 && !enabled
) {
973 r
= 0; /* disabled */
977 r
= process_locale();
981 r
= process_keymap();
985 r
= process_timezone();
989 r
= process_hostname();
993 r
= process_machine_id();
997 r
= process_root_password();
1004 free(arg_locale_messages
);
1008 string_erase(arg_root_password
);
1009 free(arg_root_password
);
1011 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;