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/>.
28 #include "alloc-util.h"
29 #include "ask-password-api.h"
34 #include "hostname-util.h"
35 #include "locale-util.h"
37 #include "parse-util.h"
38 #include "path-util.h"
39 #include "proc-cmdline.h"
40 #include "random-util.h"
41 #include "string-util.h"
43 #include "terminal-util.h"
44 #include "time-util.h"
45 #include "umask-util.h"
46 #include "user-util.h"
48 static char *arg_root
= NULL
;
49 static char *arg_locale
= NULL
; /* $LANG */
50 static char *arg_keymap
= NULL
;
51 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
52 static char *arg_timezone
= NULL
;
53 static char *arg_hostname
= NULL
;
54 static sd_id128_t arg_machine_id
= {};
55 static char *arg_root_password
= NULL
;
56 static bool arg_prompt_locale
= false;
57 static bool arg_prompt_keymap
= false;
58 static bool arg_prompt_timezone
= false;
59 static bool arg_prompt_hostname
= false;
60 static bool arg_prompt_root_password
= false;
61 static bool arg_copy_locale
= false;
62 static bool arg_copy_keymap
= false;
63 static bool arg_copy_timezone
= false;
64 static bool arg_copy_root_password
= false;
66 static bool press_any_key(void) {
70 printf("-- Press any key to proceed --");
73 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
81 static void print_welcome(void) {
82 _cleanup_free_
char *pretty_name
= NULL
;
83 const char *os_release
= NULL
;
84 static bool done
= false;
90 os_release
= prefix_roota(arg_root
, "/etc/os-release");
91 r
= parse_env_file(os_release
, NEWLINE
,
92 "PRETTY_NAME", &pretty_name
,
96 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
97 r
= parse_env_file(os_release
, NEWLINE
,
98 "PRETTY_NAME", &pretty_name
,
102 if (r
< 0 && r
!= -ENOENT
)
103 log_warning_errno(r
, "Failed to read os-release file: %m");
105 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
106 isempty(pretty_name
) ? "Linux" : pretty_name
);
113 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
114 unsigned n
, per_column
, i
, j
;
115 unsigned break_lines
, break_modulo
;
117 assert(n_columns
> 0);
120 per_column
= (n
+ n_columns
- 1) / n_columns
;
122 break_lines
= lines();
126 /* The first page gets two extra lines, since we want to show
128 break_modulo
= break_lines
;
129 if (break_modulo
> 3)
132 for (i
= 0; i
< per_column
; i
++) {
134 for (j
= 0; j
< n_columns
; j
++) {
135 _cleanup_free_
char *e
= NULL
;
137 if (j
* per_column
+ i
>= n
)
140 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
144 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
149 /* on the first screen we reserve 2 extra lines for the title */
150 if (i
% break_lines
== break_modulo
) {
151 if (!press_any_key())
159 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
167 _cleanup_free_
char *p
= NULL
;
170 r
= ask_string(&p
, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET
), text
);
172 return log_error_errno(r
, "Failed to query user: %m");
175 log_warning("No data entered, skipping.");
179 r
= safe_atou(p
, &u
);
183 if (u
<= 0 || u
> strv_length(l
)) {
184 log_error("Specified entry number out of range.");
188 log_info("Selected '%s'.", l
[u
-1]);
200 log_error("Entered data invalid.");
211 static int prompt_locale(void) {
212 _cleanup_strv_free_
char **locales
= NULL
;
215 if (arg_locale
|| arg_locale_messages
)
218 if (!arg_prompt_locale
)
221 r
= get_locales(&locales
);
223 return log_error_errno(r
, "Cannot query locales list: %m");
227 printf("\nAvailable Locales:\n\n");
228 r
= show_menu(locales
, 3, 22, 60);
234 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
238 if (isempty(arg_locale
))
241 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
248 static int process_locale(void) {
249 const char *etc_localeconf
;
254 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
255 if (laccess(etc_localeconf
, F_OK
) >= 0)
258 if (arg_copy_locale
&& arg_root
) {
260 mkdir_parents(etc_localeconf
, 0755);
261 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0, COPY_REFLINK
);
264 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
266 log_info("%s copied.", etc_localeconf
);
275 if (!isempty(arg_locale
))
276 locales
[i
++] = strjoina("LANG=", arg_locale
);
277 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
278 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
285 mkdir_parents(etc_localeconf
, 0755);
286 r
= write_env_file(etc_localeconf
, locales
);
288 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
290 log_info("%s written.", etc_localeconf
);
294 static int prompt_keymap(void) {
295 _cleanup_strv_free_
char **kmaps
= NULL
;
301 if (!arg_prompt_keymap
)
304 r
= get_keymaps(&kmaps
);
305 if (r
== -ENOENT
) /* no keymaps installed */
308 return log_error_errno(r
, "Failed to read keymaps: %m");
312 printf("\nAvailable keymaps:\n\n");
313 r
= show_menu(kmaps
, 3, 22, 60);
319 return prompt_loop("Please enter system keymap name or number",
320 kmaps
, keymap_is_valid
, &arg_keymap
);
323 static int process_keymap(void) {
324 const char *etc_vconsoleconf
;
328 etc_vconsoleconf
= prefix_roota(arg_root
, "/etc/vconsole.conf");
329 if (laccess(etc_vconsoleconf
, F_OK
) >= 0)
332 if (arg_copy_keymap
&& arg_root
) {
334 mkdir_parents(etc_vconsoleconf
, 0755);
335 r
= copy_file("/etc/vconsole.conf", etc_vconsoleconf
, 0, 0644, 0, COPY_REFLINK
);
338 return log_error_errno(r
, "Failed to copy %s: %m", etc_vconsoleconf
);
340 log_info("%s copied.", etc_vconsoleconf
);
347 return 0; /* don't fail if no keymaps are installed */
351 if (isempty(arg_keymap
))
354 keymap
= STRV_MAKE(strjoina("KEYMAP=", arg_keymap
));
356 r
= mkdir_parents(etc_vconsoleconf
, 0755);
358 return log_error_errno(r
, "Failed to create the parent directory of %s: %m", etc_vconsoleconf
);
360 r
= write_env_file(etc_vconsoleconf
, keymap
);
362 return log_error_errno(r
, "Failed to write %s: %m", etc_vconsoleconf
);
364 log_info("%s written.", etc_vconsoleconf
);
368 static int prompt_timezone(void) {
369 _cleanup_strv_free_
char **zones
= NULL
;
375 if (!arg_prompt_timezone
)
378 r
= get_timezones(&zones
);
380 return log_error_errno(r
, "Cannot query timezone list: %m");
384 printf("\nAvailable Time Zones:\n\n");
385 r
= show_menu(zones
, 3, 22, 30);
391 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
398 static int process_timezone(void) {
399 const char *etc_localtime
, *e
;
402 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
403 if (laccess(etc_localtime
, F_OK
) >= 0)
406 if (arg_copy_timezone
&& arg_root
) {
407 _cleanup_free_
char *p
= NULL
;
409 r
= readlink_malloc("/etc/localtime", &p
);
412 return log_error_errno(r
, "Failed to read host timezone: %m");
414 mkdir_parents(etc_localtime
, 0755);
415 if (symlink(p
, etc_localtime
) < 0)
416 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
418 log_info("%s copied.", etc_localtime
);
423 r
= prompt_timezone();
427 if (isempty(arg_timezone
))
430 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
432 mkdir_parents(etc_localtime
, 0755);
433 if (symlink(e
, etc_localtime
) < 0)
434 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
436 log_info("%s written", etc_localtime
);
440 static int prompt_hostname(void) {
446 if (!arg_prompt_hostname
)
453 _cleanup_free_
char *h
= NULL
;
455 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET
));
457 return log_error_errno(r
, "Failed to query hostname: %m");
460 log_warning("No hostname entered, skipping.");
464 if (!hostname_is_valid(h
, true)) {
465 log_error("Specified hostname invalid.");
469 /* Get rid of the trailing dot that we allow, but don't want to see */
470 arg_hostname
= hostname_cleanup(h
);
478 static int process_hostname(void) {
479 const char *etc_hostname
;
482 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
483 if (laccess(etc_hostname
, F_OK
) >= 0)
486 r
= prompt_hostname();
490 if (isempty(arg_hostname
))
493 mkdir_parents(etc_hostname
, 0755);
494 r
= write_string_file(etc_hostname
, arg_hostname
,
495 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
497 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
499 log_info("%s written.", etc_hostname
);
503 static int process_machine_id(void) {
504 const char *etc_machine_id
;
505 char id
[SD_ID128_STRING_MAX
];
508 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
509 if (laccess(etc_machine_id
, F_OK
) >= 0)
512 if (sd_id128_is_null(arg_machine_id
))
515 mkdir_parents(etc_machine_id
, 0755);
516 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
),
517 WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_SYNC
);
519 return log_error_errno(r
, "Failed to write machine id: %m");
521 log_info("%s written.", etc_machine_id
);
525 static int prompt_root_password(void) {
526 const char *msg1
, *msg2
, *etc_shadow
;
529 if (arg_root_password
)
532 if (!arg_prompt_root_password
)
535 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
536 if (laccess(etc_shadow
, F_OK
) >= 0)
542 msg1
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
543 msg2
= strjoina(special_glyph(TRIANGULAR_BULLET
), " Please enter new root password again: ");
546 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
548 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
550 return log_error_errno(r
, "Failed to query root password: %m");
553 log_warning("No password entered, skipping.");
557 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
559 return log_error_errno(r
, "Failed to query root password: %m");
562 log_error("Entered passwords did not match, please try again.");
566 arg_root_password
= a
;
574 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
575 _cleanup_fclose_
FILE *f
= NULL
;
580 f
= fopen(path
, "wex");
585 if (putspent(p
, f
) != 0)
586 return errno
> 0 ? -errno
: -EIO
;
588 return fflush_sync_and_check(f
);
591 static int process_root_password(void) {
593 static const char table
[] =
594 "abcdefghijklmnopqrstuvwxyz"
595 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
600 .sp_namp
= (char*) "root",
606 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
609 _cleanup_close_
int lock
= -1;
615 const char *etc_shadow
;
618 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
619 if (laccess(etc_shadow
, F_OK
) >= 0)
622 mkdir_parents(etc_shadow
, 0755);
624 lock
= take_etc_passwd_lock(arg_root
);
626 return log_error_errno(lock
, "Failed to take a lock: %m");
628 if (arg_copy_root_password
&& arg_root
) {
632 p
= getspnam("root");
633 if (p
|| errno
!= ENOENT
) {
638 return log_error_errno(errno
, "Failed to find shadow entry for root: %m");
641 r
= write_root_shadow(etc_shadow
, p
);
643 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
645 log_info("%s copied.", etc_shadow
);
650 r
= prompt_root_password();
654 if (!arg_root_password
)
657 r
= acquire_random_bytes(raw
, 16, true);
659 return log_error_errno(r
, "Failed to get salt: %m");
661 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
662 assert_cc(sizeof(table
) == 64 + 1);
663 j
= stpcpy(salt
, "$6$");
664 for (i
= 0; i
< 16; i
++)
665 j
[i
] = table
[raw
[i
] & 63];
670 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
675 return log_error_errno(errno
, "Failed to encrypt password: %m");
678 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
680 r
= write_root_shadow(etc_shadow
, &item
);
682 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
684 log_info("%s written.", etc_shadow
);
688 static void help(void) {
689 printf("%s [OPTIONS...]\n\n"
690 "Configures basic settings of the system.\n\n"
691 " -h --help Show this help\n"
692 " --version Show package version\n"
693 " --root=PATH Operate on an alternate filesystem root\n"
694 " --locale=LOCALE Set primary locale (LANG=)\n"
695 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
696 " --keymap=KEYMAP Set keymap\n"
697 " --timezone=TIMEZONE Set timezone\n"
698 " --hostname=NAME Set host name\n"
699 " --machine-ID=ID Set machine ID\n"
700 " --root-password=PASSWORD Set root password\n"
701 " --root-password-file=FILE Set root password from file\n"
702 " --prompt-locale Prompt the user for locale settings\n"
703 " --prompt-keymap Prompt the user for keymap settings\n"
704 " --prompt-timezone Prompt the user for timezone\n"
705 " --prompt-hostname Prompt the user for hostname\n"
706 " --prompt-root-password Prompt the user for root password\n"
707 " --prompt Prompt for all of the above\n"
708 " --copy-locale Copy locale from host\n"
709 " --copy-keymap Copy keymap from host\n"
710 " --copy-timezone Copy timezone from host\n"
711 " --copy-root-password Copy root password from host\n"
712 " --copy Copy locale, keymap, timezone, root password\n"
713 " --setup-machine-id Generate a new random machine ID\n"
714 , program_invocation_short_name
);
717 static int parse_argv(int argc
, char *argv
[]) {
729 ARG_ROOT_PASSWORD_FILE
,
735 ARG_PROMPT_ROOT_PASSWORD
,
740 ARG_COPY_ROOT_PASSWORD
,
741 ARG_SETUP_MACHINE_ID
,
744 static const struct option options
[] = {
745 { "help", no_argument
, NULL
, 'h' },
746 { "version", no_argument
, NULL
, ARG_VERSION
},
747 { "root", required_argument
, NULL
, ARG_ROOT
},
748 { "locale", required_argument
, NULL
, ARG_LOCALE
},
749 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
750 { "keymap", required_argument
, NULL
, ARG_KEYMAP
},
751 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
752 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
753 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
754 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
755 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
756 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
757 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
758 { "prompt-keymap", no_argument
, NULL
, ARG_PROMPT_KEYMAP
},
759 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
760 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
761 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
762 { "copy", no_argument
, NULL
, ARG_COPY
},
763 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
764 { "copy-keymap", no_argument
, NULL
, ARG_COPY_KEYMAP
},
765 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
766 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
767 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
776 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
788 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
794 if (!locale_is_valid(optarg
)) {
795 log_error("Locale %s is not valid.", optarg
);
799 r
= free_and_strdup(&arg_locale
, optarg
);
805 case ARG_LOCALE_MESSAGES
:
806 if (!locale_is_valid(optarg
)) {
807 log_error("Locale %s is not valid.", optarg
);
811 r
= free_and_strdup(&arg_locale_messages
, optarg
);
818 if (!keymap_is_valid(optarg
)) {
819 log_error("Keymap %s is not valid.", optarg
);
823 r
= free_and_strdup(&arg_keymap
, optarg
);
830 if (!timezone_is_valid(optarg
)) {
831 log_error("Timezone %s is not valid.", optarg
);
835 r
= free_and_strdup(&arg_timezone
, optarg
);
841 case ARG_ROOT_PASSWORD
:
842 r
= free_and_strdup(&arg_root_password
, optarg
);
847 case ARG_ROOT_PASSWORD_FILE
:
848 arg_root_password
= mfree(arg_root_password
);
850 r
= read_one_line_file(optarg
, &arg_root_password
);
852 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
857 if (!hostname_is_valid(optarg
, true)) {
858 log_error("Host name %s is not valid.", optarg
);
862 hostname_cleanup(optarg
);
863 r
= free_and_strdup(&arg_hostname
, optarg
);
870 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
871 log_error("Failed to parse machine id %s.", optarg
);
878 arg_prompt_locale
= arg_prompt_keymap
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
881 case ARG_PROMPT_LOCALE
:
882 arg_prompt_locale
= true;
885 case ARG_PROMPT_KEYMAP
:
886 arg_prompt_keymap
= true;
889 case ARG_PROMPT_TIMEZONE
:
890 arg_prompt_timezone
= true;
893 case ARG_PROMPT_HOSTNAME
:
894 arg_prompt_hostname
= true;
897 case ARG_PROMPT_ROOT_PASSWORD
:
898 arg_prompt_root_password
= true;
902 arg_copy_locale
= arg_copy_keymap
= arg_copy_timezone
= arg_copy_root_password
= true;
905 case ARG_COPY_LOCALE
:
906 arg_copy_locale
= true;
909 case ARG_COPY_KEYMAP
:
910 arg_copy_keymap
= true;
913 case ARG_COPY_TIMEZONE
:
914 arg_copy_timezone
= true;
917 case ARG_COPY_ROOT_PASSWORD
:
918 arg_copy_root_password
= true;
921 case ARG_SETUP_MACHINE_ID
:
923 r
= sd_id128_randomize(&arg_machine_id
);
925 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
933 assert_not_reached("Unhandled option");
939 int main(int argc
, char *argv
[]) {
943 r
= parse_argv(argc
, argv
);
947 log_set_target(LOG_TARGET_AUTO
);
948 log_parse_environment();
953 r
= proc_cmdline_get_bool("systemd.firstboot", &enabled
);
955 log_error_errno(r
, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
958 if (r
> 0 && !enabled
) {
959 r
= 0; /* disabled */
963 r
= process_locale();
967 r
= process_keymap();
971 r
= process_timezone();
975 r
= process_hostname();
979 r
= process_machine_id();
983 r
= process_root_password();
990 free(arg_locale_messages
);
994 string_erase(arg_root_password
);
995 free(arg_root_password
);
997 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;