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/>.
27 /* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
28 * removed from glibc at some point. As part of the removal, defines for
29 * crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
31 * Newer versions of glibc (v2.0+) already ship crypt.h with a definition
32 * of crypt(3) as well, so we simply include it if it is present. MariaDB,
33 * MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
34 * same way since ages without any problems.
41 #include "alloc-util.h"
42 #include "ask-password-api.h"
47 #include "hostname-util.h"
48 #include "locale-util.h"
50 #include "parse-util.h"
51 #include "path-util.h"
52 #include "proc-cmdline.h"
53 #include "random-util.h"
54 #include "string-util.h"
56 #include "terminal-util.h"
57 #include "time-util.h"
58 #include "umask-util.h"
59 #include "user-util.h"
61 static char *arg_root
= NULL
;
62 static char *arg_locale
= NULL
; /* $LANG */
63 static char *arg_keymap
= NULL
;
64 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
65 static char *arg_timezone
= NULL
;
66 static char *arg_hostname
= NULL
;
67 static sd_id128_t arg_machine_id
= {};
68 static char *arg_root_password
= NULL
;
69 static bool arg_prompt_locale
= false;
70 static bool arg_prompt_keymap
= false;
71 static bool arg_prompt_timezone
= false;
72 static bool arg_prompt_hostname
= false;
73 static bool arg_prompt_root_password
= false;
74 static bool arg_copy_locale
= false;
75 static bool arg_copy_keymap
= false;
76 static bool arg_copy_timezone
= false;
77 static bool arg_copy_root_password
= false;
79 static bool press_any_key(void) {
83 printf("-- Press any key to proceed --");
86 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
94 static void print_welcome(void) {
95 _cleanup_free_
char *pretty_name
= NULL
;
96 const char *os_release
= NULL
;
97 static bool done
= false;
103 os_release
= prefix_roota(arg_root
, "/etc/os-release");
104 r
= parse_env_file(os_release
, NEWLINE
,
105 "PRETTY_NAME", &pretty_name
,
109 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
110 r
= parse_env_file(os_release
, NEWLINE
,
111 "PRETTY_NAME", &pretty_name
,
115 if (r
< 0 && r
!= -ENOENT
)
116 log_warning_errno(r
, "Failed to read os-release file: %m");
118 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
119 isempty(pretty_name
) ? "Linux" : pretty_name
);
126 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
127 unsigned n
, per_column
, i
, j
;
128 unsigned break_lines
, break_modulo
;
130 assert(n_columns
> 0);
133 per_column
= (n
+ n_columns
- 1) / n_columns
;
135 break_lines
= lines();
139 /* The first page gets two extra lines, since we want to show
141 break_modulo
= break_lines
;
142 if (break_modulo
> 3)
145 for (i
= 0; i
< per_column
; i
++) {
147 for (j
= 0; j
< n_columns
; j
++) {
148 _cleanup_free_
char *e
= NULL
;
150 if (j
* per_column
+ i
>= n
)
153 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
157 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
162 /* on the first screen we reserve 2 extra lines for the title */
163 if (i
% break_lines
== break_modulo
) {
164 if (!press_any_key())
172 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
180 _cleanup_free_
char *p
= NULL
;
183 r
= ask_string(&p
, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET
), text
);
185 return log_error_errno(r
, "Failed to query user: %m");
188 log_warning("No data entered, skipping.");
192 r
= safe_atou(p
, &u
);
196 if (u
<= 0 || u
> strv_length(l
)) {
197 log_error("Specified entry number out of range.");
201 log_info("Selected '%s'.", l
[u
-1]);
213 log_error("Entered data invalid.");
224 static int prompt_locale(void) {
225 _cleanup_strv_free_
char **locales
= NULL
;
228 if (arg_locale
|| arg_locale_messages
)
231 if (!arg_prompt_locale
)
234 r
= get_locales(&locales
);
236 return log_error_errno(r
, "Cannot query locales list: %m");
240 printf("\nAvailable Locales:\n\n");
241 r
= show_menu(locales
, 3, 22, 60);
247 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
251 if (isempty(arg_locale
))
254 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
261 static int process_locale(void) {
262 const char *etc_localeconf
;
267 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
268 if (laccess(etc_localeconf
, F_OK
) >= 0)
271 if (arg_copy_locale
&& arg_root
) {
273 mkdir_parents(etc_localeconf
, 0755);
274 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0, COPY_REFLINK
);
277 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
279 log_info("%s copied.", etc_localeconf
);
288 if (!isempty(arg_locale
))
289 locales
[i
++] = strjoina("LANG=", arg_locale
);
290 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
291 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
298 mkdir_parents(etc_localeconf
, 0755);
299 r
= write_env_file(etc_localeconf
, locales
);
301 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
303 log_info("%s written.", etc_localeconf
);
307 static int prompt_keymap(void) {
308 _cleanup_strv_free_
char **kmaps
= NULL
;
314 if (!arg_prompt_keymap
)
317 r
= get_keymaps(&kmaps
);
318 if (r
== -ENOENT
) /* no keymaps installed */
321 return log_error_errno(r
, "Failed to read keymaps: %m");
325 printf("\nAvailable keymaps:\n\n");
326 r
= show_menu(kmaps
, 3, 22, 60);
332 return prompt_loop("Please enter system keymap name or number",
333 kmaps
, keymap_is_valid
, &arg_keymap
);
336 static int process_keymap(void) {
337 const char *etc_vconsoleconf
;
341 etc_vconsoleconf
= prefix_roota(arg_root
, "/etc/vconsole.conf");
342 if (laccess(etc_vconsoleconf
, F_OK
) >= 0)
345 if (arg_copy_keymap
&& arg_root
) {
347 mkdir_parents(etc_vconsoleconf
, 0755);
348 r
= copy_file("/etc/vconsole.conf", etc_vconsoleconf
, 0, 0644, 0, COPY_REFLINK
);
351 return log_error_errno(r
, "Failed to copy %s: %m", etc_vconsoleconf
);
353 log_info("%s copied.", etc_vconsoleconf
);
360 return 0; /* don't fail if no keymaps are installed */
364 if (isempty(arg_keymap
))
367 keymap
= STRV_MAKE(strjoina("KEYMAP=", arg_keymap
));
369 r
= mkdir_parents(etc_vconsoleconf
, 0755);
371 return log_error_errno(r
, "Failed to create the parent directory of %s: %m", etc_vconsoleconf
);
373 r
= write_env_file(etc_vconsoleconf
, keymap
);
375 return log_error_errno(r
, "Failed to write %s: %m", etc_vconsoleconf
);
377 log_info("%s written.", etc_vconsoleconf
);
381 static int prompt_timezone(void) {
382 _cleanup_strv_free_
char **zones
= NULL
;
388 if (!arg_prompt_timezone
)
391 r
= get_timezones(&zones
);
393 return log_error_errno(r
, "Cannot query timezone list: %m");
397 printf("\nAvailable Time Zones:\n\n");
398 r
= show_menu(zones
, 3, 22, 30);
404 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
411 static int process_timezone(void) {
412 const char *etc_localtime
, *e
;
415 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
416 if (laccess(etc_localtime
, F_OK
) >= 0)
419 if (arg_copy_timezone
&& arg_root
) {
420 _cleanup_free_
char *p
= NULL
;
422 r
= readlink_malloc("/etc/localtime", &p
);
425 return log_error_errno(r
, "Failed to read host timezone: %m");
427 mkdir_parents(etc_localtime
, 0755);
428 if (symlink(p
, etc_localtime
) < 0)
429 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
431 log_info("%s copied.", etc_localtime
);
436 r
= prompt_timezone();
440 if (isempty(arg_timezone
))
443 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
445 mkdir_parents(etc_localtime
, 0755);
446 if (symlink(e
, etc_localtime
) < 0)
447 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
449 log_info("%s written", etc_localtime
);
453 static int prompt_hostname(void) {
459 if (!arg_prompt_hostname
)
466 _cleanup_free_
char *h
= NULL
;
468 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET
));
470 return log_error_errno(r
, "Failed to query hostname: %m");
473 log_warning("No hostname entered, skipping.");
477 if (!hostname_is_valid(h
, true)) {
478 log_error("Specified hostname invalid.");
482 /* Get rid of the trailing dot that we allow, but don't want to see */
483 arg_hostname
= hostname_cleanup(h
);
491 static int process_hostname(void) {
492 const char *etc_hostname
;
495 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
496 if (laccess(etc_hostname
, F_OK
) >= 0)
499 r
= prompt_hostname();
503 if (isempty(arg_hostname
))
506 mkdir_parents(etc_hostname
, 0755);
507 r
= write_string_file(etc_hostname
, arg_hostname
,
508 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
510 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
512 log_info("%s written.", etc_hostname
);
516 static int process_machine_id(void) {
517 const char *etc_machine_id
;
518 char id
[SD_ID128_STRING_MAX
];
521 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
522 if (laccess(etc_machine_id
, F_OK
) >= 0)
525 if (sd_id128_is_null(arg_machine_id
))
528 mkdir_parents(etc_machine_id
, 0755);
529 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
),
530 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
532 return log_error_errno(r
, "Failed to write machine id: %m");
534 log_info("%s written.", etc_machine_id
);
538 static int prompt_root_password(void) {
539 const char *msg1
, *msg2
, *etc_shadow
;
542 if (arg_root_password
)
545 if (!arg_prompt_root_password
)
548 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
549 if (laccess(etc_shadow
, F_OK
) >= 0)
555 msg1
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
556 msg2
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter new root password again: ");
559 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
561 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
563 return log_error_errno(r
, "Failed to query root password: %m");
566 log_warning("No password entered, skipping.");
570 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
572 return log_error_errno(r
, "Failed to query root password: %m");
575 log_error("Entered passwords did not match, please try again.");
579 arg_root_password
= a
;
587 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
588 _cleanup_fclose_
FILE *f
= NULL
;
593 f
= fopen(path
, "wex");
598 if (putspent(p
, f
) != 0)
599 return errno
> 0 ? -errno
: -EIO
;
601 return fflush_sync_and_check(f
);
604 static int process_root_password(void) {
606 static const char table
[] =
607 "abcdefghijklmnopqrstuvwxyz"
608 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
613 .sp_namp
= (char*) "root",
619 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
622 _cleanup_close_
int lock
= -1;
628 const char *etc_shadow
;
631 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
632 if (laccess(etc_shadow
, F_OK
) >= 0)
635 mkdir_parents(etc_shadow
, 0755);
637 lock
= take_etc_passwd_lock(arg_root
);
639 return log_error_errno(lock
, "Failed to take a lock: %m");
641 if (arg_copy_root_password
&& arg_root
) {
645 p
= getspnam("root");
646 if (p
|| errno
!= ENOENT
) {
651 return log_error_errno(errno
, "Failed to find shadow entry for root: %m");
654 r
= write_root_shadow(etc_shadow
, p
);
656 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
658 log_info("%s copied.", etc_shadow
);
663 r
= prompt_root_password();
667 if (!arg_root_password
)
670 r
= acquire_random_bytes(raw
, 16, true);
672 return log_error_errno(r
, "Failed to get salt: %m");
674 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
675 assert_cc(sizeof(table
) == 64 + 1);
676 j
= stpcpy(salt
, "$6$");
677 for (i
= 0; i
< 16; i
++)
678 j
[i
] = table
[raw
[i
] & 63];
683 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
688 return log_error_errno(errno
, "Failed to encrypt password: %m");
691 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
693 r
= write_root_shadow(etc_shadow
, &item
);
695 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
697 log_info("%s written.", etc_shadow
);
701 static void help(void) {
702 printf("%s [OPTIONS...]\n\n"
703 "Configures basic settings of the system.\n\n"
704 " -h --help Show this help\n"
705 " --version Show package version\n"
706 " --root=PATH Operate on an alternate filesystem root\n"
707 " --locale=LOCALE Set primary locale (LANG=)\n"
708 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
709 " --keymap=KEYMAP Set keymap\n"
710 " --timezone=TIMEZONE Set timezone\n"
711 " --hostname=NAME Set host name\n"
712 " --machine-ID=ID Set machine ID\n"
713 " --root-password=PASSWORD Set root password\n"
714 " --root-password-file=FILE Set root password from file\n"
715 " --prompt-locale Prompt the user for locale settings\n"
716 " --prompt-keymap Prompt the user for keymap settings\n"
717 " --prompt-timezone Prompt the user for timezone\n"
718 " --prompt-hostname Prompt the user for hostname\n"
719 " --prompt-root-password Prompt the user for root password\n"
720 " --prompt Prompt for all of the above\n"
721 " --copy-locale Copy locale from host\n"
722 " --copy-keymap Copy keymap from host\n"
723 " --copy-timezone Copy timezone from host\n"
724 " --copy-root-password Copy root password from host\n"
725 " --copy Copy locale, keymap, timezone, root password\n"
726 " --setup-machine-id Generate a new random machine ID\n"
727 , program_invocation_short_name
);
730 static int parse_argv(int argc
, char *argv
[]) {
742 ARG_ROOT_PASSWORD_FILE
,
748 ARG_PROMPT_ROOT_PASSWORD
,
753 ARG_COPY_ROOT_PASSWORD
,
754 ARG_SETUP_MACHINE_ID
,
757 static const struct option options
[] = {
758 { "help", no_argument
, NULL
, 'h' },
759 { "version", no_argument
, NULL
, ARG_VERSION
},
760 { "root", required_argument
, NULL
, ARG_ROOT
},
761 { "locale", required_argument
, NULL
, ARG_LOCALE
},
762 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
763 { "keymap", required_argument
, NULL
, ARG_KEYMAP
},
764 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
765 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
766 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
767 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
768 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
769 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
770 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
771 { "prompt-keymap", no_argument
, NULL
, ARG_PROMPT_KEYMAP
},
772 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
773 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
774 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
775 { "copy", no_argument
, NULL
, ARG_COPY
},
776 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
777 { "copy-keymap", no_argument
, NULL
, ARG_COPY_KEYMAP
},
778 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
779 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
780 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
789 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
801 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
807 if (!locale_is_valid(optarg
)) {
808 log_error("Locale %s is not valid.", optarg
);
812 r
= free_and_strdup(&arg_locale
, optarg
);
818 case ARG_LOCALE_MESSAGES
:
819 if (!locale_is_valid(optarg
)) {
820 log_error("Locale %s is not valid.", optarg
);
824 r
= free_and_strdup(&arg_locale_messages
, optarg
);
831 if (!keymap_is_valid(optarg
)) {
832 log_error("Keymap %s is not valid.", optarg
);
836 r
= free_and_strdup(&arg_keymap
, optarg
);
843 if (!timezone_is_valid(optarg
)) {
844 log_error("Timezone %s is not valid.", optarg
);
848 r
= free_and_strdup(&arg_timezone
, optarg
);
854 case ARG_ROOT_PASSWORD
:
855 r
= free_and_strdup(&arg_root_password
, optarg
);
860 case ARG_ROOT_PASSWORD_FILE
:
861 arg_root_password
= mfree(arg_root_password
);
863 r
= read_one_line_file(optarg
, &arg_root_password
);
865 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
870 if (!hostname_is_valid(optarg
, true)) {
871 log_error("Host name %s is not valid.", optarg
);
875 hostname_cleanup(optarg
);
876 r
= free_and_strdup(&arg_hostname
, optarg
);
883 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
884 log_error("Failed to parse machine id %s.", optarg
);
891 arg_prompt_locale
= arg_prompt_keymap
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
894 case ARG_PROMPT_LOCALE
:
895 arg_prompt_locale
= true;
898 case ARG_PROMPT_KEYMAP
:
899 arg_prompt_keymap
= true;
902 case ARG_PROMPT_TIMEZONE
:
903 arg_prompt_timezone
= true;
906 case ARG_PROMPT_HOSTNAME
:
907 arg_prompt_hostname
= true;
910 case ARG_PROMPT_ROOT_PASSWORD
:
911 arg_prompt_root_password
= true;
915 arg_copy_locale
= arg_copy_keymap
= arg_copy_timezone
= arg_copy_root_password
= true;
918 case ARG_COPY_LOCALE
:
919 arg_copy_locale
= true;
922 case ARG_COPY_KEYMAP
:
923 arg_copy_keymap
= true;
926 case ARG_COPY_TIMEZONE
:
927 arg_copy_timezone
= true;
930 case ARG_COPY_ROOT_PASSWORD
:
931 arg_copy_root_password
= true;
934 case ARG_SETUP_MACHINE_ID
:
936 r
= sd_id128_randomize(&arg_machine_id
);
938 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
946 assert_not_reached("Unhandled option");
952 int main(int argc
, char *argv
[]) {
956 r
= parse_argv(argc
, argv
);
960 log_set_target(LOG_TARGET_AUTO
);
961 log_parse_environment();
966 r
= proc_cmdline_get_bool("systemd.firstboot", &enabled
);
968 log_error_errno(r
, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
971 if (r
> 0 && !enabled
) {
972 r
= 0; /* disabled */
976 r
= process_locale();
980 r
= process_keymap();
984 r
= process_timezone();
988 r
= process_hostname();
992 r
= process_machine_id();
996 r
= process_root_password();
1003 free(arg_locale_messages
);
1007 string_erase(arg_root_password
);
1008 free(arg_root_password
);
1010 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;