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 #include "alloc-util.h"
27 #include "ask-password-api.h"
32 #include "hostname-util.h"
33 #include "locale-util.h"
35 #include "parse-util.h"
36 #include "path-util.h"
37 #include "proc-cmdline.h"
38 #include "random-util.h"
39 #include "string-util.h"
41 #include "terminal-util.h"
42 #include "time-util.h"
43 #include "umask-util.h"
44 #include "user-util.h"
46 static char *arg_root
= NULL
;
47 static char *arg_locale
= NULL
; /* $LANG */
48 static char *arg_keymap
= NULL
;
49 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
50 static char *arg_timezone
= NULL
;
51 static char *arg_hostname
= NULL
;
52 static sd_id128_t arg_machine_id
= {};
53 static char *arg_root_password
= NULL
;
54 static bool arg_prompt_locale
= false;
55 static bool arg_prompt_keymap
= false;
56 static bool arg_prompt_timezone
= false;
57 static bool arg_prompt_hostname
= false;
58 static bool arg_prompt_root_password
= false;
59 static bool arg_copy_locale
= false;
60 static bool arg_copy_keymap
= false;
61 static bool arg_copy_timezone
= false;
62 static bool arg_copy_root_password
= false;
64 static bool press_any_key(void) {
68 printf("-- Press any key to proceed --");
71 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
79 static void print_welcome(void) {
80 _cleanup_free_
char *pretty_name
= NULL
;
81 const char *os_release
= NULL
;
82 static bool done
= false;
88 os_release
= prefix_roota(arg_root
, "/etc/os-release");
89 r
= parse_env_file(os_release
, NEWLINE
,
90 "PRETTY_NAME", &pretty_name
,
94 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
95 r
= parse_env_file(os_release
, NEWLINE
,
96 "PRETTY_NAME", &pretty_name
,
100 if (r
< 0 && r
!= -ENOENT
)
101 log_warning_errno(r
, "Failed to read os-release file: %m");
103 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
104 isempty(pretty_name
) ? "Linux" : pretty_name
);
111 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
112 unsigned n
, per_column
, i
, j
;
113 unsigned break_lines
, break_modulo
;
115 assert(n_columns
> 0);
118 per_column
= (n
+ n_columns
- 1) / n_columns
;
120 break_lines
= lines();
124 /* The first page gets two extra lines, since we want to show
126 break_modulo
= break_lines
;
127 if (break_modulo
> 3)
130 for (i
= 0; i
< per_column
; i
++) {
132 for (j
= 0; j
< n_columns
; j
++) {
133 _cleanup_free_
char *e
= NULL
;
135 if (j
* per_column
+ i
>= n
)
138 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
142 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
147 /* on the first screen we reserve 2 extra lines for the title */
148 if (i
% break_lines
== break_modulo
) {
149 if (!press_any_key())
157 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
165 _cleanup_free_
char *p
= NULL
;
168 r
= ask_string(&p
, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET
), text
);
170 return log_error_errno(r
, "Failed to query user: %m");
173 log_warning("No data entered, skipping.");
177 r
= safe_atou(p
, &u
);
181 if (u
<= 0 || u
> strv_length(l
)) {
182 log_error("Specified entry number out of range.");
186 log_info("Selected '%s'.", l
[u
-1]);
198 log_error("Entered data invalid.");
209 static int prompt_locale(void) {
210 _cleanup_strv_free_
char **locales
= NULL
;
213 if (arg_locale
|| arg_locale_messages
)
216 if (!arg_prompt_locale
)
219 r
= get_locales(&locales
);
221 return log_error_errno(r
, "Cannot query locales list: %m");
225 printf("\nAvailable Locales:\n\n");
226 r
= show_menu(locales
, 3, 22, 60);
232 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
236 if (isempty(arg_locale
))
239 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
246 static int process_locale(void) {
247 const char *etc_localeconf
;
252 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
253 if (laccess(etc_localeconf
, F_OK
) >= 0)
256 if (arg_copy_locale
&& arg_root
) {
258 mkdir_parents(etc_localeconf
, 0755);
259 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0, COPY_REFLINK
);
262 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
264 log_info("%s copied.", etc_localeconf
);
273 if (!isempty(arg_locale
))
274 locales
[i
++] = strjoina("LANG=", arg_locale
);
275 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
276 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
283 mkdir_parents(etc_localeconf
, 0755);
284 r
= write_env_file(etc_localeconf
, locales
);
286 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
288 log_info("%s written.", etc_localeconf
);
292 static int prompt_keymap(void) {
293 _cleanup_strv_free_
char **kmaps
= NULL
;
299 if (!arg_prompt_keymap
)
302 r
= get_keymaps(&kmaps
);
303 if (r
== -ENOENT
) /* no keymaps installed */
306 return log_error_errno(r
, "Failed to read keymaps: %m");
310 printf("\nAvailable keymaps:\n\n");
311 r
= show_menu(kmaps
, 3, 22, 60);
317 r
= prompt_loop("Please enter system keymap name or number", kmaps
, keymap_is_valid
, &arg_keymap
);
321 if (isempty(arg_keymap
))
327 static int process_keymap(void) {
328 const char *etc_vconsoleconf
;
332 etc_vconsoleconf
= prefix_roota(arg_root
, "/etc/vconsole.conf");
333 if (laccess(etc_vconsoleconf
, F_OK
) >= 0)
336 if (arg_copy_keymap
&& arg_root
) {
338 mkdir_parents(etc_vconsoleconf
, 0755);
339 r
= copy_file("/etc/vconsole.conf", etc_vconsoleconf
, 0, 0644, 0, COPY_REFLINK
);
342 return log_error_errno(r
, "Failed to copy %s: %m", etc_vconsoleconf
);
344 log_info("%s copied.", etc_vconsoleconf
);
351 return 0; /* don't fail if no keymaps are installed */
355 if (!isempty(arg_keymap
))
356 keymap
= STRV_MAKE(strjoina("KEYMAP=", arg_keymap
));
361 mkdir_parents(etc_vconsoleconf
, 0755);
362 r
= write_env_file(etc_vconsoleconf
, keymap
);
364 return log_error_errno(r
, "Failed to write %s: %m", etc_vconsoleconf
);
366 log_info("%s written.", etc_vconsoleconf
);
370 static int prompt_timezone(void) {
371 _cleanup_strv_free_
char **zones
= NULL
;
377 if (!arg_prompt_timezone
)
380 r
= get_timezones(&zones
);
382 return log_error_errno(r
, "Cannot query timezone list: %m");
386 printf("\nAvailable Time Zones:\n\n");
387 r
= show_menu(zones
, 3, 22, 30);
393 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
400 static int process_timezone(void) {
401 const char *etc_localtime
, *e
;
404 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
405 if (laccess(etc_localtime
, F_OK
) >= 0)
408 if (arg_copy_timezone
&& arg_root
) {
409 _cleanup_free_
char *p
= NULL
;
411 r
= readlink_malloc("/etc/localtime", &p
);
414 return log_error_errno(r
, "Failed to read host timezone: %m");
416 mkdir_parents(etc_localtime
, 0755);
417 if (symlink(p
, etc_localtime
) < 0)
418 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
420 log_info("%s copied.", etc_localtime
);
425 r
= prompt_timezone();
429 if (isempty(arg_timezone
))
432 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
434 mkdir_parents(etc_localtime
, 0755);
435 if (symlink(e
, etc_localtime
) < 0)
436 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
438 log_info("%s written", etc_localtime
);
442 static int prompt_hostname(void) {
448 if (!arg_prompt_hostname
)
455 _cleanup_free_
char *h
= NULL
;
457 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET
));
459 return log_error_errno(r
, "Failed to query hostname: %m");
462 log_warning("No hostname entered, skipping.");
466 if (!hostname_is_valid(h
, true)) {
467 log_error("Specified hostname invalid.");
471 /* Get rid of the trailing dot that we allow, but don't want to see */
472 arg_hostname
= hostname_cleanup(h
);
480 static int process_hostname(void) {
481 const char *etc_hostname
;
484 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
485 if (laccess(etc_hostname
, F_OK
) >= 0)
488 r
= prompt_hostname();
492 if (isempty(arg_hostname
))
495 mkdir_parents(etc_hostname
, 0755);
496 r
= write_string_file(etc_hostname
, arg_hostname
,
497 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
499 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
501 log_info("%s written.", etc_hostname
);
505 static int process_machine_id(void) {
506 const char *etc_machine_id
;
507 char id
[SD_ID128_STRING_MAX
];
510 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
511 if (laccess(etc_machine_id
, F_OK
) >= 0)
514 if (sd_id128_is_null(arg_machine_id
))
517 mkdir_parents(etc_machine_id
, 0755);
518 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
),
519 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
521 return log_error_errno(r
, "Failed to write machine id: %m");
523 log_info("%s written.", etc_machine_id
);
527 static int prompt_root_password(void) {
528 const char *msg1
, *msg2
, *etc_shadow
;
531 if (arg_root_password
)
534 if (!arg_prompt_root_password
)
537 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
538 if (laccess(etc_shadow
, F_OK
) >= 0)
544 msg1
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
545 msg2
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter new root password again: ");
548 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
550 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
552 return log_error_errno(r
, "Failed to query root password: %m");
555 log_warning("No password entered, skipping.");
559 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
561 return log_error_errno(r
, "Failed to query root password: %m");
564 log_error("Entered passwords did not match, please try again.");
568 arg_root_password
= a
;
576 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
577 _cleanup_fclose_
FILE *f
= NULL
;
582 f
= fopen(path
, "wex");
587 if (putspent(p
, f
) != 0)
588 return errno
> 0 ? -errno
: -EIO
;
590 return fflush_sync_and_check(f
);
593 static int process_root_password(void) {
595 static const char table
[] =
596 "abcdefghijklmnopqrstuvwxyz"
597 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
602 .sp_namp
= (char*) "root",
608 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
611 _cleanup_close_
int lock
= -1;
617 const char *etc_shadow
;
620 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
621 if (laccess(etc_shadow
, F_OK
) >= 0)
624 mkdir_parents(etc_shadow
, 0755);
626 lock
= take_etc_passwd_lock(arg_root
);
628 return log_error_errno(lock
, "Failed to take a lock: %m");
630 if (arg_copy_root_password
&& arg_root
) {
634 p
= getspnam("root");
635 if (p
|| errno
!= ENOENT
) {
640 return log_error_errno(errno
, "Failed to find shadow entry for root: %m");
643 r
= write_root_shadow(etc_shadow
, p
);
645 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
647 log_info("%s copied.", etc_shadow
);
652 r
= prompt_root_password();
656 if (!arg_root_password
)
659 r
= acquire_random_bytes(raw
, 16, true);
661 return log_error_errno(r
, "Failed to get salt: %m");
663 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
664 assert_cc(sizeof(table
) == 64 + 1);
665 j
= stpcpy(salt
, "$6$");
666 for (i
= 0; i
< 16; i
++)
667 j
[i
] = table
[raw
[i
] & 63];
672 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
677 return log_error_errno(errno
, "Failed to encrypt password: %m");
680 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
682 r
= write_root_shadow(etc_shadow
, &item
);
684 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
686 log_info("%s written.", etc_shadow
);
690 static void help(void) {
691 printf("%s [OPTIONS...]\n\n"
692 "Configures basic settings of the system.\n\n"
693 " -h --help Show this help\n"
694 " --version Show package version\n"
695 " --root=PATH Operate on an alternate filesystem root\n"
696 " --locale=LOCALE Set primary locale (LANG=)\n"
697 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
698 " --keymap=KEYMAP Set keymap\n"
699 " --timezone=TIMEZONE Set timezone\n"
700 " --hostname=NAME Set host name\n"
701 " --machine-ID=ID Set machine ID\n"
702 " --root-password=PASSWORD Set root password\n"
703 " --root-password-file=FILE Set root password from file\n"
704 " --prompt-locale Prompt the user for locale settings\n"
705 " --prompt-keymap Prompt the user for keymap settings\n"
706 " --prompt-timezone Prompt the user for timezone\n"
707 " --prompt-hostname Prompt the user for hostname\n"
708 " --prompt-root-password Prompt the user for root password\n"
709 " --prompt Prompt for all of the above\n"
710 " --copy-locale Copy locale from host\n"
711 " --copy-keymap Copy keymap from host\n"
712 " --copy-timezone Copy timezone from host\n"
713 " --copy-root-password Copy root password from host\n"
714 " --copy Copy locale, keymap, timezone, root password\n"
715 " --setup-machine-id Generate a new random machine ID\n"
716 , program_invocation_short_name
);
719 static int parse_argv(int argc
, char *argv
[]) {
731 ARG_ROOT_PASSWORD_FILE
,
737 ARG_PROMPT_ROOT_PASSWORD
,
742 ARG_COPY_ROOT_PASSWORD
,
743 ARG_SETUP_MACHINE_ID
,
746 static const struct option options
[] = {
747 { "help", no_argument
, NULL
, 'h' },
748 { "version", no_argument
, NULL
, ARG_VERSION
},
749 { "root", required_argument
, NULL
, ARG_ROOT
},
750 { "locale", required_argument
, NULL
, ARG_LOCALE
},
751 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
752 { "keymap", required_argument
, NULL
, ARG_KEYMAP
},
753 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
754 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
755 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
756 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
757 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
758 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
759 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
760 { "prompt-keymap", no_argument
, NULL
, ARG_PROMPT_KEYMAP
},
761 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
762 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
763 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
764 { "copy", no_argument
, NULL
, ARG_COPY
},
765 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
766 { "copy-keymap", no_argument
, NULL
, ARG_COPY_KEYMAP
},
767 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
768 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
769 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
778 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
790 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
796 if (!locale_is_valid(optarg
)) {
797 log_error("Locale %s is not valid.", optarg
);
801 r
= free_and_strdup(&arg_locale
, optarg
);
807 case ARG_LOCALE_MESSAGES
:
808 if (!locale_is_valid(optarg
)) {
809 log_error("Locale %s is not valid.", optarg
);
813 r
= free_and_strdup(&arg_locale_messages
, optarg
);
820 if (!keymap_is_valid(optarg
)) {
821 log_error("Keymap %s is not valid.", optarg
);
825 r
= free_and_strdup(&arg_keymap
, optarg
);
832 if (!timezone_is_valid(optarg
)) {
833 log_error("Timezone %s is not valid.", optarg
);
837 r
= free_and_strdup(&arg_timezone
, optarg
);
843 case ARG_ROOT_PASSWORD
:
844 r
= free_and_strdup(&arg_root_password
, optarg
);
849 case ARG_ROOT_PASSWORD_FILE
:
850 arg_root_password
= mfree(arg_root_password
);
852 r
= read_one_line_file(optarg
, &arg_root_password
);
854 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
859 if (!hostname_is_valid(optarg
, true)) {
860 log_error("Host name %s is not valid.", optarg
);
864 hostname_cleanup(optarg
);
865 r
= free_and_strdup(&arg_hostname
, optarg
);
872 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
873 log_error("Failed to parse machine id %s.", optarg
);
880 arg_prompt_locale
= arg_prompt_keymap
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
883 case ARG_PROMPT_LOCALE
:
884 arg_prompt_locale
= true;
887 case ARG_PROMPT_KEYMAP
:
888 arg_prompt_keymap
= true;
891 case ARG_PROMPT_TIMEZONE
:
892 arg_prompt_timezone
= true;
895 case ARG_PROMPT_HOSTNAME
:
896 arg_prompt_hostname
= true;
899 case ARG_PROMPT_ROOT_PASSWORD
:
900 arg_prompt_root_password
= true;
904 arg_copy_locale
= arg_copy_keymap
= arg_copy_timezone
= arg_copy_root_password
= true;
907 case ARG_COPY_LOCALE
:
908 arg_copy_locale
= true;
911 case ARG_COPY_KEYMAP
:
912 arg_copy_keymap
= true;
915 case ARG_COPY_TIMEZONE
:
916 arg_copy_timezone
= true;
919 case ARG_COPY_ROOT_PASSWORD
:
920 arg_copy_root_password
= true;
923 case ARG_SETUP_MACHINE_ID
:
925 r
= sd_id128_randomize(&arg_machine_id
);
927 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
935 assert_not_reached("Unhandled option");
941 int main(int argc
, char *argv
[]) {
945 r
= parse_argv(argc
, argv
);
949 log_set_target(LOG_TARGET_AUTO
);
950 log_parse_environment();
955 r
= proc_cmdline_get_bool("systemd.firstboot", &enabled
);
957 log_error_errno(r
, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
960 if (r
> 0 && !enabled
) {
961 r
= 0; /* disabled */
965 r
= process_locale();
969 r
= process_keymap();
973 r
= process_timezone();
977 r
= process_hostname();
981 r
= process_machine_id();
985 r
= process_root_password();
992 free(arg_locale_messages
);
996 string_erase(arg_root_password
);
997 free(arg_root_password
);
999 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;