sortix-mirror/sysinstall/sysinstall.c

1693 lines
48 KiB
C
Raw Normal View History

/*
2023-02-26 13:16:08 +00:00
* Copyright (c) 2015, 2016, 2020, 2021, 2022, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* sysinstall.c
* Operating system installer.
*/
#include <sys/display.h>
#include <sys/ioctl.h>
#include <sys/kernelinfo.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <assert.h>
#include <brand.h>
2023-09-03 15:24:48 +00:00
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <limits.h>
#include <pwd.h>
2023-05-15 21:25:19 +00:00
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
// Sortix libc doesn't have its own proper <limits.h> at this time.
#if defined(__sortix__)
#include <sortix/limits.h>
#endif
#include <mount/blockdevice.h>
#include <mount/devices.h>
#include <mount/filesystem.h>
#include <mount/harddisk.h>
#include <mount/partition.h>
#include <mount/uuid.h>
2023-05-15 21:25:19 +00:00
#include "autoconf.h"
#include "conf.h"
#include "devices.h"
#include "execute.h"
#include "fileops.h"
#include "interactive.h"
#include "manifest.h"
#include "release.h"
const char* prompt_man_section = "7";
const char* prompt_man_page = "installation";
static struct partition_table* search_bios_boot_pt(struct filesystem* root_fs)
{
char firmware[64];
if ( kernelinfo("firmware", firmware, sizeof(firmware)) != 0 )
return NULL;
if ( strcmp(firmware, "bios") != 0 )
return NULL;
struct blockdevice* bdev = root_fs->bdev;
while ( bdev->p )
bdev = bdev->p->parent_bdev;
if ( !bdev->pt )
return NULL;
struct partition_table* pt = bdev->pt;
if ( pt->type != PARTITION_TABLE_TYPE_GPT )
return NULL;
return pt;
}
static struct partition* search_bios_boot_search(struct partition_table* pt)
{
for ( size_t i = 0; i < pt->partitions_count; i++ )
{
struct partition* p = pt->partitions[i];
if ( p->bdev.fs && !strcmp(p->bdev.fs->fstype_name, "biosboot") )
return p;
}
return NULL;
}
static struct partition* search_bios_boot_partition(struct filesystem* root_fs)
{
struct partition_table* pt = search_bios_boot_pt(root_fs);
if ( !pt )
return NULL;
return search_bios_boot_search(pt);
}
static bool missing_bios_boot_partition(struct filesystem* root_fs)
{
struct partition_table* pt = search_bios_boot_pt(root_fs);
if ( !pt )
return NULL;
return !search_bios_boot_search(pt);
}
static bool should_install_bootloader_path(const char* mnt,
struct blockdevice* bdev)
{
char* release_errpath;
if ( asprintf(&release_errpath, "%s: /etc/sortix-release",
path_of_blockdevice(bdev)) < 0 )
{
warn("malloc");
return false;
}
char* release_path;
if ( asprintf(&release_path, "%s/etc/sortix-release", mnt) < 0 )
{
warn("malloc");
free(release_errpath);
return false;
}
struct release release;
if ( !os_release_load(&release, release_path, release_errpath) )
{
free(release_path);
free(release_errpath);
return false;
}
free(release_path);
free(release_errpath);
release_free(&release);
char* conf_path;
if ( asprintf(&conf_path, "%s/etc/upgrade.conf", mnt) < 0 )
{
warn("malloc");
return false;
}
struct conf conf;
conf_init(&conf);
bool result = false;
if ( conf_load(&conf, conf_path) )
result = conf.grub;
else if ( errno != ENOENT )
warn("%s: /etc/upgrade.conf", path_of_blockdevice(bdev));
conf_free(&conf);
free(conf_path);
return result;
}
static bool should_install_bootloader_bdev(struct blockdevice* bdev)
{
if ( !bdev->fs )
return false;
if ( bdev->fs->flags & FILESYSTEM_FLAG_NOT_FILESYSTEM )
return false;
if ( !bdev->fs->driver )
return false;
char mnt[] = "/tmp/fs.XXXXXX";
if ( !mkdtemp(mnt) )
{
warn("mkdtemp: %s", "/tmp/fs.XXXXXX");
return false;
}
struct mountpoint mp = { 0 };
mp.absolute = mnt;
mp.fs = bdev->fs;
mp.entry.fs_file = mnt;
if ( !mountpoint_mount(&mp) )
{
rmdir(mnt);
return false;
}
bool should = should_install_bootloader_path(mnt, bdev);
mountpoint_unmount(&mp);
rmdir(mnt);
return should;
}
static bool should_install_bootloader(void)
{
bool any_systems = false;
for ( size_t i = 0; i < hds_count; i++ )
{
struct harddisk* hd = hds[i];
if ( hd->bdev.pt )
{
for ( size_t n = 0; n < hd->bdev.pt->partitions_count; n++ )
{
any_systems = true;
struct partition* p = hd->bdev.pt->partitions[n];
if ( should_install_bootloader_bdev(&p->bdev) )
return true;
}
}
else if ( hd->bdev.fs )
{
any_systems = true;
if ( should_install_bootloader_bdev(&hd->bdev) )
return true;
}
}
return !any_systems;
}
static bool passwd_check(const char* passwd_path,
bool (*check)(struct passwd*, void*),
void* check_ctx)
{
FILE* passwd = fopen(passwd_path, "r");
if ( !passwd )
{
if ( errno != ENOENT )
warn("%s", passwd_path);
return false;
}
char* line = NULL;
size_t size = 0;
ssize_t length;
while ( 0 < (length = getline(&line, &size, passwd) ) )
{
if ( line[size - 1] == '\n' )
line[--size] = '\0';
struct passwd pwd;
if ( scanpwent(line, &pwd) && check(&pwd, check_ctx) )
{
free(line);
fclose(passwd);
return true;
}
}
free(line);
if ( ferror(passwd) )
warn("%s", passwd_path);
fclose(passwd);
return false;
}
static bool passwd_has_uid_check(struct passwd* pwd, void* ctx)
{
return pwd->pw_uid == *(uid_t*) ctx;
}
static bool passwd_has_uid(const char* passwd_path, uid_t uid)
{
return passwd_check(passwd_path, passwd_has_uid_check, &uid);
}
static bool passwd_has_name_check(struct passwd* pwd, void* ctx)
{
return !strcmp(pwd->pw_name, (const char*) ctx);
}
static bool passwd_has_name(const char* passwd_path, const char* name)
{
return passwd_check(passwd_path, passwd_has_name_check, (void*) name);
}
static void install_skel(const char* home, uid_t uid, gid_t gid)
{
const char* argv[] = { "cp", "-RT", "--", "etc/skel", home, NULL };
execute(argv, "ug", uid, gid);
}
__attribute__((format(printf, 3, 4)))
static bool install_configurationf(const char* path,
const char* mode,
const char* format,
...)
{
FILE* fp = fopen(path, mode);
if ( !fp )
{
warn("%s", path);
return false;
}
va_list ap;
va_start(ap, format);
int status = vfprintf(fp, format, ap);
va_end(ap);
if ( status < 0 )
{
warn("%s", path);
fclose(fp);
return false;
}
if ( fclose(fp) == EOF )
{
warn("%s", path);
return false;
}
return true;
}
static void grub_hash_password(char* buffer, size_t buffer_size, const char* pw)
{
int pipe_fds[2];
if ( pipe(pipe_fds) < 0 )
err(2, "pipe");
pid_t pid = fork();
if ( pid < 0 )
err(2, "fork");
if ( pid == 0 )
{
close(pipe_fds[0]);
if ( dup2(pipe_fds[1], 1) < 0 )
_exit(2);
close(pipe_fds[1]);
const char* argv[] = { "grub-mkpasswd-pbkdf2", "-p", pw, NULL };
execvp(argv[0], (char* const*) argv);
_exit(127);
}
close(pipe_fds[1]);
size_t done = 0;
while ( done < buffer_size )
{
ssize_t amount = read(pipe_fds[0], buffer + done, buffer_size - done);
if ( amount < 0 )
err(2, "read");
if ( amount == 0 )
break;
done += amount;
}
if ( done && buffer[done-1] == '\n' )
done--;
if ( done == buffer_size )
done--;
buffer[done] = '\0';
close(pipe_fds[0]);
int exit_code;
waitpid(pid, &exit_code, 0);
if ( !WIFEXITED(exit_code) || WEXITSTATUS(exit_code) != 0 )
errx(2, "grub password hash failed");
}
2023-09-03 15:24:48 +00:00
static const char* const ignore_kernel_options[] =
{
"--no-random-seed",
"--random-seed",
NULL,
};
static char* normalize_kernel_options(void)
{
char* options = akernelinfo("options");
if ( !options )
{
warn("kernelinfo: options");
return NULL;
}
size_t i = 0, o = 0;
while ( options[i] )
{
if ( isspace((unsigned char) options[i]) )
{
i++;
continue;
}
if ( options[i] != '-' ) // Imperfect since quoting options is allowed.
break;
if ( !strncmp(options + i, "--", 2) &&
(!options[i + 2] || isspace((unsigned char) options[i + 2])) )
break;
bool ignored = false;
for ( size_t n = 0; ignore_kernel_options[n]; n++ )
{
const char* opt = ignore_kernel_options[n];
size_t len = strlen(opt);
if ( !strncmp(options + i, opt, len) &&
(!options[i + len] ||
isspace((unsigned char) options[i + len])) )
{
i += len;
ignored = true;
break;
}
}
if ( ignored )
continue;
bool singly = false;
bool doubly = false;
bool escaped = false;
for ( ; options[i]; i++ )
{
char c = options[i];
options[o++] = c;
if ( !escaped && !singly && !doubly && isspace((unsigned char) c) )
break;
if ( !escaped && !doubly && c == '\'' )
{
singly = !singly;
continue;
}
if ( !escaped && !singly && c == '"' )
{
doubly = !doubly;
continue;
}
if ( !singly && !escaped && c == '\\' )
{
escaped = true;
continue;
}
escaped = false;
}
}
while ( o && isspace((unsigned char) options[o - 1]) )
o--;
options[o] = '\0';
return options;
}
static pid_t main_pid;
static struct mountpoint* mountpoints;
static size_t mountpoints_used;
static bool etc_made = false;
static char etc[] = "/tmp/etc.XXXXXX";
static bool fs_made = false;
static char fs[] = "/tmp/fs.XXXXXX";
static int exit_gui_code = -1;
2016-02-28 23:53:59 +00:00
static void unmount_all_but_root(void)
{
for ( size_t n = mountpoints_used; n != 0; n-- )
{
size_t i = n - 1;
struct mountpoint* mountpoint = &mountpoints[i];
if ( !strcmp(mountpoint->entry.fs_file, "/") )
continue;
mountpoint_unmount(mountpoint);
}
}
void exit_handler(void)
{
if ( getpid() != main_pid )
return;
chdir("/");
for ( size_t n = mountpoints_used; n != 0; n-- )
{
size_t i = n - 1;
struct mountpoint* mountpoint = &mountpoints[i];
mountpoint_unmount(mountpoint);
}
if ( fs_made )
rmdir(fs);
if ( etc_made )
execute((const char*[]) { "rm", "-rf", etc, NULL }, "");
if ( 0 <= exit_gui_code )
gui_shutdown(exit_gui_code);
}
void exit_gui(int code)
{
exit_gui_code = code;
exit(code);
}
2023-05-15 21:25:19 +00:00
static void cancel_on_sigint(int signum)
{
(void) signum;
errx(2, "fatal: Installation canceled");
}
int main(void)
{
shlvl();
if ( !isatty(0) )
errx(2, "fatal: stdin is not a terminal");
if ( !isatty(1) )
errx(2, "fatal: stdout is not a terminal");
if ( !isatty(2) )
errx(2, "fatal: stderr is not a terminal");
if ( getuid() != 0 )
errx(2, "You need to be root to install %s", BRAND_DISTRIBUTION_NAME);
if ( getgid() != 0 )
errx(2, "You need to be group root to install %s", BRAND_DISTRIBUTION_NAME);
main_pid = getpid();
if ( atexit(exit_handler) != 0 )
err(2, "atexit");
if ( !mkdtemp(etc) )
err(2, "mkdtemp: %s", "/tmp/etc.XXXXXX");
etc_made = true;
// Export for the convenience of users escaping to a shell.
setenv("SYSINSTALL_ETC", fs, 1);
if ( chdir(etc) < 0 )
err(2, "chdir: %s", etc);
struct utsname uts;
uname(&uts);
struct conf conf;
conf_init(&conf);
if ( !conf_load(&conf, "/etc/upgrade.conf") && errno != ENOENT )
warn("/etc/upgrade.conf");
2023-05-15 21:25:19 +00:00
autoconf_load("/etc/autoinstall.conf");
char* accepts_defaults = autoconf_eval("accept_defaults");
bool non_interactive = accepts_defaults &&
!strcasecmp(accepts_defaults, "yes");
free(accepts_defaults);
static char input[256];
textf("Hello and welcome to the " BRAND_DISTRIBUTION_NAME " " VERSIONSTR ""
" installer for %s.\n\n", uts.machine);
2023-05-15 21:25:19 +00:00
if ( non_interactive ||
(autoconf_has("ready") &&
(autoconf_has("disked") || autoconf_has("confirm_install"))) )
{
int countdown = 10;
if ( autoconf_has("countdown") )
{
char* string = autoconf_eval("countdown");
countdown = atoi(string);
free(string);
}
sigset_t old_set;
sigset_t new_set;
sigemptyset(&new_set);
sigaddset(&new_set, SIGINT);
sigprocmask(SIG_BLOCK, &new_set, &old_set);
struct sigaction old_sa;
struct sigaction new_sa = { 0 };
new_sa.sa_handler = cancel_on_sigint;
sigaction(SIGINT, &new_sa, &old_sa);
for ( ; 0 < countdown; countdown-- )
{
textf("Automatically installing " BRAND_DISTRIBUTION_NAME " "
VERSIONSTR " in %i %s... (Control-C to cancel)\n", countdown,
countdown != 1 ? "seconds" : "second");
sigprocmask(SIG_SETMASK, &old_set, NULL);
sleep(1);
sigprocmask(SIG_BLOCK, &new_set, &old_set);
}
textf("Automatically installing " BRAND_DISTRIBUTION_NAME " "
VERSIONSTR "...\n");
text("\n");
sigaction(SIGINT, &old_sa, NULL);
sigprocmask(SIG_SETMASK, &old_set, NULL);
}
// '|' rather than '||' is to ensure side effects.
if ( missing_program("cut") |
missing_program("dash") |
missing_program("fsck.ext2") |
missing_program("grub-install") |
missing_program("man") |
missing_program("sed") |
missing_program("xargs") )
{
text("Warning: This system does not have the necessary third party "
"software installed to properly install this operating system.\n");
while ( true )
{
prompt(input, sizeof(input), "ignore_missing_programs",
"Sure you want to proceed?", "no");
if ( strcasecmp(input, "no") == 0 )
return 0;
if ( strcasecmp(input, "yes") == 0 )
break;
}
text("\n");
}
text("You are about to install a new operating system on this computer. "
"This is not something you should do on a whim or when you are "
2016-08-27 19:30:30 +00:00
"impatient. Take the time to read the documentation and be patient "
"while you learn the new system. This is a very good time to start an "
2017-10-22 15:52:05 +00:00
"external music player that plays soothing classical music on loop.\n\n");
2023-02-26 13:16:08 +00:00
if ( !access_or_die("/tix/tixinfo/ssh", F_OK) &&
access_or_die("/root/.ssh/authorized_keys", F_OK) < 0 )
text("If you wish to ssh into your new installation, it's recommended "
"to first add your public keys to the .iso and obtain "
"fingerprints per release-iso-modification(7) before installing."
"\n\n");
const char* readies[] =
{
"Ready",
"Yes",
"Yeah",
"Yep",
"Let's go",
"Let's do this",
"Betcha",
"Sure am",
"You bet",
"It's very good music",
};
size_t num_readies = sizeof(readies) / sizeof(readies[0]);
const char* ready = readies[arc4random_uniform(num_readies)];
2023-05-15 21:25:19 +00:00
if ( autoconf_has("disked") )
text("Warning: This installer will perform automatic harddisk "
"partitioning!\n");
if ( autoconf_has("confirm_install") )
text("Warning: This installer will automatically install an operating "
"system!\n");
prompt(input, sizeof(input), "ready", "Ready?", ready);
text("\n");
text("This is not yet a fully fledged operating system. You should adjust "
"your expectations accordingly. You should not consider the system safe "
"for multi-user use. Filesystem permissions are not enforced yet. There "
"are many security issues so setuid(2) blatantly allows every user to "
"become root if they want to.\n\n");
text("You can always escape to a shell by answering '!' to any regular "
"prompt. You can view the installation(7) manual page by "
"answering '!man'. Default answers are in []'s and can be selected by "
"pressing enter.\n\n");
// TODO: You can leave this program by pressing ^C but it can leave your
// system in an inconsistent state.
install_configurationf("upgrade.conf", "a", "src = yes\n");
bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0) ||
getenv("DISPLAY_SOCKET");
while ( kblayout_setable )
{
// TODO: Detect the name of the current keyboard layout.
prompt(input, sizeof(input), "kblayout",
"Choose your keyboard layout ('?' or 'L' for list)", "default");
if ( !strcmp(input, "?") ||
!strcmp(input, "l") ||
!strcmp(input, "L") )
{
DIR* dir = opendir("/share/kblayout");
if ( !dir )
{
warn("%s", "/share/kblayout");
continue;
}
bool space = false;
struct dirent* entry;
while ( (entry = readdir(dir)) )
{
if ( entry->d_name[0] == '.' )
continue;
if ( space )
putchar(' ');
fputs(entry->d_name, stdout);
space = true;
}
closedir(dir);
if ( !space )
fputs("(No keyboard layouts available)", stdout);
putchar('\n');
continue;
}
if ( !strcmp(input, "default") )
break;
const char* argv[] = { "chkblayout", "--", input, NULL };
if ( execute(argv, "f") == 0 )
break;
}
if ( kblayout_setable )
{
if ( !input[0] || !strcmp(input, "default") )
text("/etc/kblayout will not be created (default).\n");
else
{
textf("/etc/kblayout will be set to \"%s\".\n", input);
mode_t old_umask = getumask();
umask(022);
install_configurationf("kblayout", "w", "%s\n", input);
umask(old_umask);
}
text("\n");
}
struct dispmsg_crtc_mode mode;
if ( get_video_mode(&mode) )
{
bool good = (mode.control & DISPMSG_CONTROL_VALID) &&
(mode.control & DISPMSG_CONTROL_GOOD_DEFAULT);
if ( mode.control & DISPMSG_CONTROL_VM_AUTO_SCALE )
2016-09-03 21:46:10 +00:00
{
text("The display resolution will automatically change to "
"match the size of the virtual machine window.\n\n");
good = true;
2016-09-03 21:46:10 +00:00
}
2023-05-15 21:25:19 +00:00
const char* def = non_interactive || good ? "no" : "yes";
while ( true )
{
prompt(input, sizeof(input), "videomode",
2023-05-15 21:25:19 +00:00
"Select display resolution? "
"(yes/no/WIDTHxHEIGHTxBPP)", def);
unsigned int xres, yres, bpp;
bool set = sscanf(input, "%ux%ux%u", &xres, &yres, &bpp) == 3;
if ( !strcasecmp(input, "no") )
{
input[0] = '\0';
break;
2023-05-15 21:25:19 +00:00
}
const char* r = set ? input : NULL;
if ( execute((const char*[]) { "chvideomode", r, NULL }, "f") != 0 )
continue;
2023-05-15 21:25:19 +00:00
input[0] = '\0';
if ( !get_video_mode(&mode) ||
!(mode.control & DISPMSG_CONTROL_VALID) ||
mode.control & DISPMSG_CONTROL_VGA )
continue;
snprintf(input, sizeof(input), "%ux%ux%u",
mode.view_xres, mode.view_yres, mode.fb_format);
break;
}
2016-03-24 22:58:14 +00:00
if ( !input[0] )
text("/etc/videomode will not be created.\n");
else
{
textf("/etc/videomode will be set to \"%s\".\n", input);
mode_t old_umask = getumask();
umask(022);
install_configurationf("videomode", "w", "%s\n", input);
umask(old_umask);
}
text("\n");
}
text("Searching for existing installations...\n");
scan_devices();
bool bootloader_default = should_install_bootloader();
text("\n");
textf("You need a bootloader to start the operating system. GRUB is the "
"standard %s bootloader and this installer comes with a copy. "
"This copy is however unable to automatically detect other operating "
"systems.\n\n", BRAND_DISTRIBUTION_NAME);
text("Single-boot installations should accept this bootloader.\n");
text("Dual-boot systems should refuse it and manually arrange for "
"bootloading by configuring any existing multiboot compliant "
"bootloader.\n");
text("\n");
char accept_grub[10];
char accept_grub_password[10];
char grub_password[512];
while ( true )
{
const char* def = bootloader_default ? "yes" : "no";
prompt(accept_grub, sizeof(accept_grub), "grub",
"Install a new GRUB bootloader?", def);
if ( strcasecmp(accept_grub, "no") == 0 ||
strcasecmp(accept_grub, "yes") == 0 )
break;
}
text("\n");
if ( strcasecmp(accept_grub, "yes") == 0 )
{
install_configurationf("upgrade.conf", "a", "grub = yes\n");
text("If an unauthorized person has access to the bootloader command "
"line, then the whole system security can be compromised. You can "
"prevent this by password protecting interactive use of the "
"bootloader, but still allowing anyone to start the system "
"normally. Similarly you may wish to manually go into your "
"firmware and password protect it.\n");
text("\n");
while ( true )
{
2023-05-15 21:25:19 +00:00
const char* def =
non_interactive &&
!autoconf_has("grub_password_hash") ? "no" : "yes";
prompt(accept_grub_password, sizeof(accept_grub_password),
"grub_password",
2023-05-15 21:25:19 +00:00
"Password protect interactive bootloader? (yes/no)", def);
if ( strcasecmp(accept_grub_password, "no") == 0 ||
strcasecmp(accept_grub_password, "yes") == 0 )
break;
}
2023-05-15 21:25:19 +00:00
if ( autoconf_has("grub_password_hash") )
{
char* hash = autoconf_eval("grub_password_hash");
install_configurationf("grubpw", "w", "%s\n", hash);
free(hash);
}
else while ( !strcasecmp(accept_grub_password, "yes") )
{
char first[128];
char second[128];
password(first, sizeof(first),
"Bootloader root password? (will not echo)");
password(second, sizeof(second),
"Bootloader root password? (again)");
if ( strcmp(first, second) != 0 )
{
printf("Passwords do not match, try again.\n");
continue;
}
explicit_bzero(second, sizeof(second));
if ( !strcmp(first, "") )
{
char answer[32];
prompt(answer, sizeof(answer), "grub_password_empty",
"Empty password is stupid, are you sure? (yes/no)", "no");
if ( strcasecmp(answer, "yes") != 0 )
continue;
}
grub_hash_password(grub_password, sizeof(grub_password), first);
textf("/etc/grubpw will be made with grub-mkpasswd-pbkdf2.\n");
mode_t old_umask = getumask();
umask(077);
install_configurationf("grubpw", "w", "%s\n", grub_password);
umask(old_umask);
break;
}
text("\n");
}
2023-09-03 15:24:48 +00:00
char* kernel_options = normalize_kernel_options();
if ( (autoconf_has("kernel_options") ||
(kernel_options && kernel_options[0])) &&
!access_or_die("/tix/tixinfo/grub", F_OK) )
{
text("The operating system was booted with explicit kernel(7) options. "
"Would you like set them permanently in /etc/grub?\n\n");
while ( true )
{
char options[1024];
prompt(options, sizeof(options), "kernel_options",
"Kernel options? (OPTIONS/no)", kernel_options);
if ( !strcasecmp(options, "no") )
{
kernel_options = NULL;
break;
}
if ( options[0] )
{
install_configurationf("grub", "w",
"GRUB_CMDLINE_SORTIX='%s'\n", options);
textf("/etc/grub will be made with the kernel options.\n");
}
break;
}
text("\n");
}
free(kernel_options);
// TODO: Offer the user an automatic layout of partitions if the disk is
// empty.
// TODO: Perhaps let the user know the size of the system that will be
// installed?
text("You need to select a root filesystem and other mountpoints now. You "
"will now be dumped into a partition editor. Create and format a "
"root filesystem partition as needed.\n");
text("\n");
const char* mktable_tip = "";
if ( check_lacking_partition_table() )
mktable_tip = "Type mktable to make a new partition table. ";
const char* devices_tip = "";
if ( check_multiple_harddisks() )
devices_tip = "Type devices to list the devices. "
"Type device 1 to switch to device 1. ";
textf("Type ls to list partitions on the device. "
"%s"
"%s"
"Type mkpart to make a new partition. "
"Type mount 2 / to create a mountpoint for partition 2. "
"Type exit when done. "
2016-08-27 19:30:30 +00:00
"There is partitioning advice in installation(7). "
"Type man 8 disked to display the disked(8) man page.\n",
mktable_tip, devices_tip);
struct filesystem* root_filesystem = NULL;
struct filesystem* boot_filesystem = NULL;
struct filesystem* bootloader_filesystem = NULL;
bool not_first = false;
while ( true )
{
if ( not_first )
text("Type man to display the disked(8) man page.\n");
not_first = true;
const char* argv[] = { "disked", "--fstab=fstab", NULL };
2023-05-15 21:25:19 +00:00
char* disked_input = autoconf_eval("disked");
if ( execute(argv, "fi", disked_input) != 0 )
{
2023-05-15 21:25:19 +00:00
free(disked_input);
// TODO: We also end up here on SIGINT.
// TODO: Offer a shell here instead of failing?
warnx("partitioning failed");
sleep(1);
continue;
}
2023-05-15 21:25:19 +00:00
free(disked_input);
free_mountpoints(mountpoints, mountpoints_used);
mountpoints = NULL;
mountpoints_used = 0;
scan_devices();
if ( !load_mountpoints("fstab", &mountpoints, &mountpoints_used) )
{
if ( errno == ENOENT )
text("You have not created any mountpoints. Try again.\n");
else
warn("fstab");
continue;
}
bool found_rootfs = false;
for ( size_t i = 0; !found_rootfs && i < mountpoints_used; i++ )
if ( !strcmp(mountpoints[i].entry.fs_file, "/") )
found_rootfs = true;
if ( !found_rootfs )
{
text("You have no root filesystem mountpoint. Try again.\n");
continue;
}
root_filesystem = NULL;
boot_filesystem = NULL;
bool cant_mount = false;
for ( size_t i = 0; i < mountpoints_used; i++ )
{
struct mountpoint* mnt = &mountpoints[i];
const char* spec = mnt->entry.fs_spec;
if ( !(mnt->fs = search_for_filesystem_by_spec(spec)) )
{
warnx("fstab: %s: Found no mountable filesystem matching `%s'",
mnt->entry.fs_file, spec);
cant_mount = true;
continue;
}
if ( !mnt->fs->driver )
{
warnx("fstab: %s: %s: Don't know how to mount this %s filesystem",
mnt->entry.fs_file,
path_of_blockdevice(mnt->fs->bdev),
mnt->fs->fstype_name);
cant_mount = true;
continue;
}
if ( !strcmp(mnt->entry.fs_file, "/") )
root_filesystem = mnt->fs;
if ( !strcmp(mnt->entry.fs_file, "/boot") )
boot_filesystem = mnt->fs;
}
if ( cant_mount )
continue;
assert(root_filesystem);
bootloader_filesystem = boot_filesystem ? boot_filesystem : root_filesystem;
assert(bootloader_filesystem);
if ( !strcasecmp(accept_grub, "yes") &&
missing_bios_boot_partition(bootloader_filesystem) )
{
const char* where = boot_filesystem ? "/boot" : "root";
const char* dev = device_path_of_blockdevice(bootloader_filesystem->bdev);
assert(dev);
textf("You are a installing BIOS bootloader and the %s "
"filesystem is located on a GPT partition, but you haven't "
"made a BIOS boot partition on the %s GPT disk. Pick "
"biosboot during mkpart and make a 1 MiB partition.\n",
where, dev);
char return_to_disked[10];
while ( true )
{
prompt(return_to_disked, sizeof(return_to_disked),
"missing_bios_boot_partition",
2016-08-27 19:30:30 +00:00
"Return to disked to make a BIOS boot partition?", "yes");
if ( strcasecmp(accept_grub, "no") == 0 ||
strcasecmp(accept_grub, "yes") == 0 )
break;
}
if ( !strcasecmp(return_to_disked, "yes") )
continue;
text("Proceeding, but expect the installation to fail.\n");
}
break;
}
text("\n");
textf("We are now ready to install %s %s. Take a moment to verify "
"everything is in order.\n", BRAND_DISTRIBUTION_NAME, VERSIONSTR);
text("\n");
printf(" %-16s system architecture\n", uts.machine);
for ( size_t i = 0; i < mountpoints_used; i++ )
{
struct mountpoint* mnt = &mountpoints[i];
const char* devname = path_of_blockdevice(mnt->fs->bdev);
const char* where = mnt->entry.fs_file;
printf(" %-16s use as %s\n", devname, where);
}
if ( strcasecmp(accept_grub, "yes") == 0 )
{
struct partition* bbp = search_bios_boot_partition(bootloader_filesystem);
if ( bbp )
printf(" %-16s bios boot partition\n",
path_of_blockdevice(&bbp->bdev));
printf(" %-16s bootloader installation target\n",
device_path_of_blockdevice(bootloader_filesystem->bdev));
}
text("\n");
while ( true )
{
prompt(input, sizeof(input), "confirm_install",
"Install " BRAND_DISTRIBUTION_NAME "? "
"(yes/no/exit/poweroff/reboot/halt)", "yes");
if ( !strcasecmp(input, "yes") )
break;
else if ( !strcasecmp(input, "no") )
{
text("Answer '!' to get a shell. Type !man to view the "
"installation(7) manual page.\n");
text("Alternatively, you can answer 'poweroff', 'reboot', or "
"'halt' to cancel the installation.\n");
continue;
}
else if ( !strcasecmp(input, "exit") )
exit(0);
else if ( !strcasecmp(input, "poweroff") )
exit_gui(0);
else if ( !strcasecmp(input, "reboot") )
exit_gui(1);
else if ( !strcasecmp(input, "halt") )
exit_gui(2);
else
continue;
}
text("\n");
text("Installing " BRAND_DISTRIBUTION_NAME " " VERSIONSTR " now:\n");
printf(" - Mounting filesystems...\n");
if ( !mkdtemp(fs) )
err(2, "mkdtemp: %s", "/tmp/fs.XXXXXX");
fs_made = true;
// Export for the convenience of users escaping to a shell.
setenv("SYSINSTALL_TARGET", fs, 1);
for ( size_t i = 0; i < mountpoints_used; i++ )
{
struct mountpoint* mnt = &mountpoints[i];
char* absolute;
if ( asprintf(&absolute, "%s%s", fs, mnt->absolute) < 0 )
err(2, "asprintf");
free(mnt->absolute);
mnt->absolute = absolute;
if ( mkdir_p(mnt->absolute, 0755) < 0 )
err(2, "mkdir: %s", mnt->absolute);
if ( !mountpoint_mount(mnt) )
exit(2);
}
if ( chdir(fs) < 0 )
err(2, "chdir: %s", fs);
pid_t install_pid = fork();
if ( install_pid < 0 )
err(2, "fork");
if ( install_pid == 0 )
{
printf(" - Populating root filesystem...\n");
chmod(".", 0755);
if ( access("tix/collection.conf", F_OK) < 0 )
execute((const char*[]) { "tix-collection", ".", "create",
2016-08-27 19:30:30 +00:00
"--prefix=", NULL }, "_e");
2021-01-18 23:04:56 +00:00
install_manifests_detect("", ".", true, true, true);
// TODO: Preserve the existing /src if it exists like in sysupgrade.
if ( has_manifest("src") )
2021-01-18 23:04:56 +00:00
install_manifest("src", "", ".", (const char*[]){}, 0);
printf(" - Creating configuration files...\n");
// TODO: Preserve mode/ownership/timestamps?
execute((const char*[]) { "cp", "-RTP", etc, "etc", NULL }, "_e");
// TODO: Auto detect appropriate bcrypt rounds and set up etc/login.conf
// and use those below instead of blowfish,a.
Seed kernel entropy with randomness from the previous boot. The bootloader will now load the /boot/random.seed file if it exists, in which case the kernel will use it as the initial kernel entropy. The kernel warns if no random seed was loaded, unless the --no-random-seed option was given. This option is used for live environments that inherently have no prior secret state. The kernel initializes its entropy pool from the random seed as of the first things, so randomness is available very early on. init(8) will emit a fresh /boot/random.seed file on boot to avoid the same entropy being used twice. init(8) also writes out /boot/random.seed on system shutdown where the system has the most entropy. init(8) will warn if writing the file fails, except if /boot is a real-only filesystem, and keeping such state is impossible. The system administrator is then responsible for ensuring the bootloader somehow passes a fresh random seed on the next boot. /boot/random.seed must be owned by the root user and root group and must have file permissions 600 to avoid unprivileged users can read it. The file is passed to the kernel by the bootloader as a multiboot module with the command line --random-seed. If no random seed is loaded, the kernel attempts a poor quality fallback where it seeds the kernel arc4random(3) continuously with the current time. The timing variance may provide some effective entropy. There is no real kernel entropy gathering yet. The read of the CMOS real time clock is moved to an early point in the kernel boot, so the current time is available as fallback entropy. The kernel access of the random seed module is supposed to be infallible and happens before the kernel log is set up, but there is not yet a failsafe API for mapping single pages in the early kernel. sysupgrade(8) creates /boot/random.seed if it's absent as a temporary compatibility measure for people upgrading from the 1.0 release. The GRUB port will need to be upgraded with support for /boot/random.seed in the 10_sortix script. Installation with manual bootloader configuration will need to load the random seed with the --random-seed command line. With GRUB, this can be done with: module /boot/random.seed --random-seed
2016-08-20 00:27:33 +00:00
if ( access_or_die("boot/random.seed", F_OK) < 0 )
{
printf(" - Creating random seed...\n");
write_random_seed("boot/random.seed");
}
printf(" - Creating initrd...\n");
execute((const char*[]) { "update-initrd", "--sysroot", fs, NULL }, "_e");
if ( strcasecmp(accept_grub, "yes") == 0 )
{
printf(" - Installing bootloader...\n");
execute((const char*[]) { "chroot", "-d", ".", "grub-install",
device_path_of_blockdevice(bootloader_filesystem->bdev), NULL },
"_eqQ");
printf(" - Configuring bootloader...\n");
execute((const char*[]) { "chroot", "-d", ".", "update-grub", NULL },
"_eqQ");
}
else if ( access_or_die("/etc/default/grub.d/10_sortix", F_OK) == 0 )
{
// Help dual booters by making /etc/grub.d/default/10_sortix.cache.
printf(" - Creating bootloader fragment...\n");
execute((const char*[]) { "chroot", "-d", ".",
"/etc/grub.d/default/10_sortix",
NULL }, "_eq");
}
printf(" - Finishing installation...\n");
_exit(0);
}
int install_code;
waitpid(install_pid, &install_code, 0);
if ( WIFEXITED(install_code) && WEXITSTATUS(install_code) == 0 )
{
}
else if ( WIFEXITED(install_code) )
errx(2, "installation failed with exit status %i", WEXITSTATUS(install_code));
else if ( WIFSIGNALED(install_code) )
errx(2, "installation failed: %s", strsignal(WTERMSIG(install_code)));
else
errx(2, "installation failed: unknown waitpid code %i", install_code);
unsetenv("SYSINSTALL_ETC");
execute((const char*[]) { "rm", "-r", etc, NULL }, "");
etc_made = false;
text("\n");
text("System files are now installed. We'll now make the system functional "
"by configuring a few essential matters.\n\n");
umask(0022);
if ( access("etc/hostname", F_OK) == 0 )
textf("/etc/hostname already exists, skipping creating it.\n");
else while ( true )
{
char defhost[HOST_NAME_MAX + 1] = "";
2023-05-15 21:25:19 +00:00
if ( non_interactive )
gethostname(defhost, sizeof(defhost));
FILE* defhost_fp = fopen("etc/hostname", "r");
if ( defhost_fp )
{
fgets(defhost, sizeof(defhost), defhost_fp);
size_t defhostlen = strlen(defhost);
if ( defhostlen && defhost[defhostlen-1] == '\n' )
defhost[defhostlen-1] = '\0';
fclose(defhost_fp);
}
char hostname[HOST_NAME_MAX + 1] = "";
prompt(hostname, sizeof(hostname), "hostname", "System hostname?",
defhost[0] ? defhost : NULL);
if ( !install_configurationf("etc/hostname", "w", "%s\n", hostname) )
continue;
textf("/etc/hostname was set to \"%s\".\n", hostname);
break;
}
text("\n");
2021-01-18 23:04:56 +00:00
if ( mkdir("root", 0700) < 0 )
{
if ( errno == EEXIST )
{
if ( chmod("root", 0700) < 0 )
warn("chmod: root");
}
else
warn("mkdir: root");
}
if ( passwd_has_uid("etc/passwd", 0) ||
passwd_has_name("etc/passwd", "root") )
{
textf("Root account already exists, skipping creating it.\n");
}
2023-05-15 21:25:19 +00:00
else if ( non_interactive || autoconf_has("password_hash_root") )
{
char* hash = autoconf_eval("password_hash_root");
if ( !hash && !(hash = strdup("x")) )
err(2, "malloc");
if ( !install_configurationf("etc/passwd", "a",
"root:%s:0:0:root:/root:sh\n"
"include /etc/default/passwd.d/*\n", hash) )
err(2, "etc/passwd");
textf("User '%s' added to /etc/passwd\n", "root");
if ( !install_configurationf("etc/group", "a",
"root::0:root\n"
"include /etc/default/group.d/*\n") )
err(2, "etc/passwd");
install_skel("/root", 0, 0);
textf("Group '%s' added to /etc/group.\n", "root");
free(hash);
}
else while ( true )
{
char first[128];
char second[128];
password(first, sizeof(first), "Password for root account? (will not echo)");
password(second, sizeof(second), "Password for root account? (again)");
if ( strcmp(first, second) != 0 )
{
printf("Passwords do not match, try again.\n");
continue;
}
explicit_bzero(second, sizeof(second));
if ( !strcmp(first, "") )
{
char answer[32];
prompt(answer, sizeof(answer), "empty_password",
"Empty password is stupid, are you sure? (yes/no)", "no");
if ( strcasecmp(answer, "yes") != 0 )
continue;
}
char hash[128];
if ( crypt_newhash(first, "blowfish,a", hash, sizeof(hash)) < 0 )
{
explicit_bzero(first, sizeof(first));
warn("crypt_newhash");
continue;
}
explicit_bzero(first, sizeof(first));
if ( !install_configurationf("etc/passwd", "a",
"root:%s:0:0:root:/root:sh\n"
"include /etc/default/passwd.d/*\n", hash) )
continue;
textf("User '%s' added to /etc/passwd\n", "root");
if ( !install_configurationf("etc/group", "a",
"root::0:root\n"
"include /etc/default/group.d/*\n") )
continue;
install_skel("/root", 0, 0);
textf("Group '%s' added to /etc/group.\n", "root");
break;
}
2023-02-26 13:16:08 +00:00
struct ssh_file
{
const char* key;
const char* path;
const char* pub;
};
const struct ssh_file ssh_files[] =
{
{"copy_ssh_authorized_keys_root", "/root/.ssh/authorized_keys", NULL},
{"copy_ssh_config_root", "/root/.ssh/config", NULL},
{"copy_ssh_id_rsa_root", "/root/.ssh/id_rsa", "/root/.ssh/id_rsa.pub"},
{"copy_ssh_known_hosts_root", "/root/.ssh/known_hosts", NULL},
};
size_t ssh_files_count = sizeof(ssh_files) / sizeof(ssh_files[0]);
bool any_ssh_keys = false;
for ( size_t i = 0; i < ssh_files_count; i++ )
{
const struct ssh_file* file = &ssh_files[i];
if ( access_or_die(file->path, F_OK) < 0 )
continue;
text("\n");
textf("Found %s\n", file->path);
if ( file->pub && !access_or_die(file->pub, F_OK) )
textf("Found %s\n", file->pub);
while ( true )
{
char question[256];
snprintf(question, sizeof(question),
"Copy %s from installer environment? (yes/no)",
file->path);
prompt(input, sizeof(input), file->key, question, "yes");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
mkdir_or_chmod_or_die("root/.ssh", 0700);
textf("Copying %s -> %s\n", file->path, file->path + 1);
execute((const char*[])
{"cp", file->path, file->path+ 1, NULL }, "f");
if ( file->pub )
{
textf("Copying %s -> %s\n", file->pub, file->pub + 1);
execute((const char*[])
{"cp", file->pub, file->pub + 1, NULL }, "f");
}
any_ssh_keys = true;
break;
}
}
text("\n");
2021-01-18 23:04:56 +00:00
if ( mkdir("etc/init", 0755) < 0 )
{
if ( errno == EEXIST )
{
if ( chmod("etc/init", 0755) < 0 )
warn("chmod: etc/init");
}
else
warn("mkdir: etc/init");
}
install_configurationf("etc/init/default", "w",
"require multi-user exit-code\n");
text("Congratulations, the system is now functional! This is a good time "
"to do further customization of the system.\n\n");
2023-05-15 21:25:19 +00:00
// TODO: autoconf users support.
bool made_user = false;
2023-05-15 21:25:19 +00:00
for ( uid_t uid = 1000; !has_autoconf; )
{
while ( passwd_has_uid("etc/passwd", uid) )
uid++;
gid_t gid = (gid_t) uid;
static char userstr[256];
const char* question = "Setup a user? (enter username or 'no')";
if ( made_user )
question = "Setup another user? (enter username or 'no')";
prompt(userstr, sizeof(userstr), NULL, question, "no");
if ( !strcmp(userstr, "no") )
break;
if ( !strcmp(userstr, "yes") )
continue;
const char* user = userstr;
while ( user[0] == ' ')
user++;
if ( passwd_has_name("etc/passwd", user) )
{
textf("Account '%s' already exists.\n", user);
continue;
}
static char name[256];
prompt(name, sizeof(name), NULL, "Full name of user?", user);
char first[128];
char second[128];
while ( true )
{
password(first, sizeof(first), "Password for user? (will not echo)");
password(second, sizeof(second), "Password for user? (again)");
if ( strcmp(first, second) != 0 )
{
printf("Passwords do not match, try again.\n");
continue;
}
explicit_bzero(second, sizeof(second));
if ( !strcmp(first, "") )
{
char answer[32];
prompt(answer, sizeof(answer), "empty_password",
2016-08-27 19:30:30 +00:00
"Empty password is stupid, are you sure? (yes/no)", "no");
if ( strcasecmp(answer, "yes") != 0 )
continue;
}
break;
}
char hash[128];
if ( crypt_newhash(first, "blowfish,a", hash, sizeof(hash)) < 0 )
{
explicit_bzero(first, sizeof(first));
warn("crypt_newhash");
continue;
}
explicit_bzero(first, sizeof(first));
if ( !install_configurationf("etc/passwd", "a",
"%s:%s:%" PRIuUID ":%" PRIuGID ":%s:/home/%s:sh\n",
2016-08-27 19:30:30 +00:00
user, hash, uid, gid, name, user) )
continue;
if ( !install_configurationf("etc/group", "a",
"%s::%" PRIuGID ":%s\n", user, gid, user) )
continue;
char* home;
if ( asprintf(&home, "home/%s", user) < 0 )
{
warn("asprintf");
continue;
}
if ( mkdir(home, 0700) < 0 && errno != EEXIST )
{
warn("mkdir: %s", home);
free(home);
continue;
}
chown(home, uid, gid);
install_skel(home, uid, gid);
free(home);
textf("User '%s' added to /etc/passwd\n", user);
textf("Group '%s' added to /etc/group.\n", user);
text("\n");
uid++;
made_user = true;
}
2023-05-15 21:25:19 +00:00
// TODO: autoconf support.
if ( !has_autoconf )
text("\n");
Add networking stack. This change adds all the kernel parts of a network stack. The network stack is partial but implements many of the important parts. Add if(4) network interface abstraction. Network interfaces are registered in a global list that can be iterated and each assigned an unique integer identifier. Add reference counted packets with a cache that recycles recent packets. Add support for lo(4) loopback and ether(4) ethernet network interfaces. The /dev/lo0 loopback device is created automatically on boot. Add arp(4) address resolution protocol driver for translation of inet(4) network layer addresses into ether(4) link layer addresses. arp(4) entries are cached and evicted from the cache when needed or when the entry has not been used for a while. The cache is limited to 256 entries for now. Add ip(4) internet protocol version 4 support. IP fragmentation and options are not implemented yet. Add tcp(4) transmission control protocol sockets for a reliable transport layer protocol that provides a reliable byte stream connection between two hosts. The implementation is incomplete and does not yet implement out of band data, options, and high performance extensions. Add udp(4) user datagram protocol sockets for a connectionless transport layer that provides best-effort delivery of datagrams. Add ping(4) sockets for a best-effort delivery of echo datagrams. Change type of sa_family_t from unsigned short to uint16_t. Add --disable-network-drivers to the kernel(7) options and expose it with a bootloader menu. tix-iso-bootconfig can set this option by default. Import CRC32 code from libz for the Ethernet checksum. This is a compatible ABI change that adds features to socket(2) (AF_INET, IPPROTO_TCP, IPPROTO_UDP, IPPROTO_PING), the ioctls for if(4), socket options, and the lo0 loopback interface. This commit is based on work by Meisaka Yukara contributed as the commit bbf7f1e8a5238a2bd1fe8eb1d2cc5c9c2421e2c4. Almost no lines of this work remains in this final commit as it has been rewritten or refactored away over the years, see the individual file headers for which files contain remnants of this work. Co-authored-by: Meisaka Yukara <Meisaka.Yukara@gmail.com>
2022-12-04 23:35:21 +00:00
// TODO: Ask if networking should be disabled / enabled.
while ( true )
{
prompt(input, sizeof(input), "enable_gui",
"Enable graphical user interface?",
getenv("DISPLAY_SOCKET") ? "yes" : "no");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
if ( !install_configurationf("etc/session", "w",
"#!sh\nexec display\n") ||
chmod("etc/session", 0755) < 0 )
{
warn("etc/session");
continue;
}
text("Added 'exec display' to /etc/session\n");
break;
}
text("\n");
2022-03-06 14:34:05 +00:00
if ( !access_or_die("/tix/tixinfo/ntpd", F_OK) )
{
text("A Network Time Protocol client (ntpd) has been installed that "
"can automatically synchronize the current time with the internet."
"\n\n");
text("Privacy notice: If enabled, the default configuration will "
"obtain time from pool.ntp.org and time.cloudflare.com; and "
"compare with HTTPS timestamps from quad9 and www.google.com. "
"You are encouraged to edit /etc/ntpd.conf per the ntpd.conf(5) "
"manual with your preferences.\n\n");
bool copied = false;
while ( true )
{
prompt(input, sizeof(input), "enable_ntpd",
"Automatically get time from the network? (yes/no/edit/man)",
copied ? "yes" : "no");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "man") == 0 )
{
execute((const char*[]) {"man", "5", "ntpd.conf", NULL}, "fi");
continue;
}
if ( strcasecmp(input, "edit") == 0 )
{
if ( !copied )
{
execute((const char*[]) {"cp", "etc/default/ntpd.conf",
"etc/ntpd.conf", NULL}, "f");
copied = true;
}
const char* editor =
getenv("EDITOR") ? getenv("EDITOR") : "editor";
execute((const char*[]) {editor, "etc/ntpd.conf", NULL}, "f");
text("Created /etc/ntpd.conf from /etc/default/ntpd.conf\n");
continue;
}
if ( strcasecmp(input, "yes") != 0 )
continue;
if ( !install_configurationf("etc/init/local", "a",
"require ntpd optional\n") )
{
warn("etc/init/local");
continue;
}
if ( !install_configurationf("etc/init/time", "a",
"furthermore\n"
"require ntpd optional\n") )
{
warn("etc/init/time");
continue;
}
text("Added 'require ntpd optional' to /etc/init/local\n");
text("Added 'require ntpd optional' to /etc/init/time\n");
break;
}
text("\n");
}
2023-02-26 13:16:08 +00:00
struct sshd_key_file
{
const char* pri;
const char* pub;
};
const struct sshd_key_file sshd_key_files[] =
{
{"/etc/ssh_host_ecdsa_key", "/etc/ssh_host_ecdsa_key.pub"},
{"/etc/ssh_host_ed25519_key", "/etc/ssh_host_ed25519_key.pub"},
{"/etc/ssh_host_rsa_key", "/etc/ssh_host_rsa_key.pub"},
};
size_t sshd_key_files_count
= sizeof(sshd_key_files) / sizeof(sshd_key_files[0]);
bool any_sshd_keys = false;
for ( size_t i = 0; i < sshd_key_files_count; i++ )
{
if ( !access_or_die(sshd_key_files[i].pri, F_OK) )
{
textf("Found %s\n", sshd_key_files[i].pri);
any_sshd_keys = true;
}
}
bool enabled_sshd = false;
if ( !access_or_die("/tix/tixinfo/ssh", F_OK) )
{
text("A ssh server has been installed. You have the option of starting "
"it on boot to allow remote login over a cryptographically secure "
"channel. Answer no if you don't know what ssh is.\n\n");
if ( !any_sshd_keys )
text("Warning: " BRAND_DISTRIBUTION_NAME " does not yet collect "
"entropy for secure random numbers. Unless you type '!' and "
"escape to a shell and put 256 bytes of actual randomness "
"into boot/random.seed, the first boot will use the "
"randomness of this installer environment to generate ssh "
"keys. This initial randomness may be as weak as the wall "
"time when you booted the installer, which is easily guessed "
"by an attacker. The same warning applies to outgoing secure "
"connections as well.\n\n");
bool might_want_sshd =
any_ssh_keys ||
any_sshd_keys ||
2023-02-26 13:16:08 +00:00
!access_or_die("/etc/sshd_config", F_OK);
while ( true )
{
2023-05-15 21:25:19 +00:00
prompt(input, sizeof(input), "enable_sshd",
2023-02-26 13:16:08 +00:00
"Enable ssh server? (yes/no)",
might_want_sshd ? "yes" : "no");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
if ( !install_configurationf("etc/init/local", "a",
"require sshd optional\n") )
{
warn("etc/init/local");
continue;
}
enabled_sshd = true;
text("Added 'require sshd optional' to /etc/init/local\n");
text("The ssh server will be started when the system boots.\n");
break;
}
text("\n");
}
bool has_sshd_config = false;
if ( !access_or_die("/etc/sshd_config", F_OK) )
{
while ( true )
{
prompt(input, sizeof(input), "copy_sshd_config",
"Copy /etc/sshd_config from installer environment? (yes/no)",
"yes");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
const char* file = "/etc/sshd_config";
textf("Copying %s -> %s\n", file, file + 1);
execute((const char*[]) {"cp", file, file + 1}, "f");
has_sshd_config = true;
break;
}
text("\n");
}
if ( enabled_sshd && !has_sshd_config )
{
text("Password authentication has been disabled by default in sshd to "
"prevent remotely guessing insecure passwords. The recommended "
"approach is to put your public key in the installation .iso and "
"generate the sshd credentials ahead of time as documented in "
"release-iso-modification(7). However, you could enable password "
"authentication if you picked a very strong password.\n\n");
bool enable_sshd_password = false;
while ( true )
{
2023-05-15 21:25:19 +00:00
prompt(input, sizeof(input), "enable_sshd_password",
2023-02-26 13:16:08 +00:00
"Enable sshd password authentication? (yes/no)", "no");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
if ( !install_configurationf("etc/sshd_config", "a",
"PasswordAuthentication yes\n") )
{
warn("etc/sshd_config");
continue;
}
enable_sshd_password = true;
text("Added 'PasswordAuthentication yes' to /etc/sshd_config\n");
break;
}
while ( enable_sshd_password )
{
2023-05-15 21:25:19 +00:00
prompt(input, sizeof(input), "enable_sshd_root_password",
2023-02-26 13:16:08 +00:00
"Enable sshd password authentication for root? (yes/no)",
"no");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
if ( !install_configurationf("etc/sshd_config", "a",
"PermitRootLogin yes\n") )
{
warn("etc/sshd_config");
continue;
}
text("Added 'PermitRootLogin yes' to /etc/sshd_config\n");
break;
}
text("\n");
}
if ( any_sshd_keys )
{
while ( true )
{
const char* question =
"Copy sshd private keys from installer environment? (yes/no)";
prompt(input, sizeof(input), "copy_sshd_private_keys",
question,
"yes");
if ( strcasecmp(input, "no") == 0 )
break;
if ( strcasecmp(input, "yes") != 0 )
continue;
for ( size_t i = 0; i < sshd_key_files_count; i++ )
{
const struct sshd_key_file* file = &sshd_key_files[i];
if ( access_or_die(file->pri, F_OK) < 0 )
continue;
textf("Copying %s -> %s\n", file->pri, file->pri + 1);
execute((const char*[])
{"cp", file->pri, file->pri + 1, NULL }, "f");
textf("Copying %s -> %s\n", file->pub, file->pub + 1);
execute((const char*[])
{"cp", file->pub, file->pub + 1, NULL }, "f");
}
break;
}
text("\n");
}
text("It's time to boot into the newly installed system.\n\n");
if ( strcasecmp(accept_grub, "no") == 0 )
text("You did not accept a bootloader and need to set up bootloading "
"yourself. /etc/default/grub.d/10_sortix.cache is a GRUB "
"configuration fragment that boots the newly installed system."
"\n\n");
text("Upon boot, you'll be greeted with a login screen. Enter your "
"credentials to get a command line. Login as user 'poweroff' as "
"described in login(8) to power off the machine or run poweroff(8). "
"After logging in, type 'man user-guide' to view the introductory "
"documentation.\n");
text("\n");
while ( true )
{
prompt(input, sizeof(input), "finally",
2023-06-07 23:23:56 +00:00
"What now? (exit/poweroff/reboot/halt/boot/chroot)", "boot");
if ( !strcasecmp(input, "exit") )
exit(0);
2023-06-07 23:23:56 +00:00
else if ( !strcasecmp(input, "poweroff") )
exit_gui(0);
2023-06-07 23:23:56 +00:00
else if ( !strcasecmp(input, "reboot") )
exit_gui(1);
2023-06-07 23:23:56 +00:00
else if ( !strcasecmp(input, "halt") )
exit_gui(2);
2023-06-07 23:23:56 +00:00
else if ( !strcasecmp(input, "boot") )
{
if ( !access("/etc/fstab", F_OK) )
{
printf("Only a live environment can reinit installations.\n");
continue;
}
execute((const char*[]) {"mkdir", "-p", "/etc/init", NULL }, "ef");
execute((const char*[]) {"cp", "etc/fstab", "/etc/fstab", NULL },
"ef");
execute((const char*[]) {"sh", "-c",
"echo 'require chain exit-code' > "
"/etc/init/default", NULL },
"ef");
exit_gui(3);
2023-06-07 23:23:56 +00:00
}
else if ( !strcasecmp(input, "chroot") )
{
unmount_all_but_root();
unsetenv("SYSINSTALL_TARGET");
unsetenv("SHLVL");
unsetenv("INIT_PID");
exit(execute((const char*[]) { "chroot", "-d", fs,
"/sbin/init", NULL }, "f"));
}
}
}