diff --git a/build-aux/iso-grub-cfg.sh b/build-aux/iso-grub-cfg.sh index 054ae796..9d5becb6 100755 --- a/build-aux/iso-grub-cfg.sh +++ b/build-aux/iso-grub-cfg.sh @@ -185,6 +185,9 @@ set version="$version" set machine="$machine" set base_menu_title="Sortix \$version for \$machine" set menu_title="\$base_menu_title" +set title_single_user='live environment' +set title_sysinstall='new installation' +set title_sysupgrade='upgrade existing installation' set timeout=10 set default="0" if [ -e /boot/random.seed ]; then @@ -202,6 +205,9 @@ export version export machine export base_menu_title export menu_title +export title_single_user +export title_sysinstall +export title_sysupgrade export timeout export default export no_random_seed @@ -412,9 +418,9 @@ menu_title="\$base_menu_title" hook_menu_pre EOF -menuentry "live environment" '-- /sbin/init' -menuentry "new installation" '-- /sbin/init --target=sysinstall' -menuentry "upgrade existing installation" '-- /sbin/init --target=sysupgrade' +menuentry "\$title_single_user" '-- /sbin/init' +menuentry "\$title_sysinstall" '-- /sbin/init --target=sysinstall' +menuentry "\$title_sysupgrade" '-- /sbin/init --target=sysupgrade' cat << EOF diff --git a/share/man/man5/autoinstall.conf.5 b/share/man/man5/autoinstall.conf.5 new file mode 100644 index 00000000..1d175b10 --- /dev/null +++ b/share/man/man5/autoinstall.conf.5 @@ -0,0 +1,444 @@ +.Dd April 23, 2023 +.Dt AUTOINSTALL.CONF 5 +.Os +.Sh NAME +.Nm autoinstall.conf +.Nd automatic installation configuration +.Sh SYNOPSIS +.Nm /etc/autoinstall.conf +.Sh DESCRIPTION +.Nm +configures +.Xr sysinstall 8 +to do an automatic operating system +.Xr installation 7 . +.Pp +The system administrator can automate operating system installations by +following +.Xr release-iso-modification 7 +to embed the +.Pa /etc/autoinstall.conf +file into the release cdrom filesystem. +Existing installations can similarly be automatically upgraded using +.Xr autoupgrade.conf 5 . +.Pp +Each line is formatted as +.Ar question Ns = Ns Ar answer +which provides an +.Ar answer +to a +.Ar question +asked during installation. +Alternatively +.Ar question Ns += Ns Ar answer +appends to an existing answer (if any) separated by a space, and +.Ar question Ns ++= Ns Ar answer +appends another line to an existing answer (if any). +Empty lines and lines starting with +.Sq # +are comments and are ignored. +Otherwise whitespace is significant. +The empty answer accepts the default answer (if any) and is different from no +answer, which makes +.Xr sysinstall 8 +ask the question normally. +Installations can be made fully non-interactive using +.Sy accept_defaults . +.Pp +Each question has a counterpart question suffixed with +.Sq "!" +which contains a +.Xr sh 1 +script that is executed before the question is asked. +If the original question isn't answered, then the script's standard output +is used as the answer to the original question. +The installation aborts if the script exits unsuccessfully. +These scripts are useful to customize the installation with arbitrary code. +.Pp +The questions in chronological order are as follows: +.Bl -tag -width "12345678" +.It Sy accept_defaults Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Accept the default answer (if any) to questions that were not +configured in +.Nm ? +This feature makes installations entirely automated even if unexpected questions +are asked, although the essential questions must be answered. +.It Sy countdown Ns "=" Ns Ar seconds No ( default Li 10 ) +Count down for this many +.Ar seconds +with a warning that an automated installation is about to happen? +The countdown happens if +.Sy accept_defaults=yes +or if the +.Sy ready +question is answered and either the +.Sy disked +or +.Sy confirm_install +questions are answered. +.It Sy ignore_missing_programs Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Ignore if the installer environment does not have the needed ports installed? +This situation ordinarily does not happen. +.It Sy ready Ns "=" Ns Ar affirmation +Are you ready to begin the installation process? +This is a human readable positive affirmation of your choice that you're ready +to install, useful for fully automated installations. +Not answering this question is useful for semi-automated installations where one +would acknowledge the installation before it's begun. +It also provides the opportunity to escape to a shell before installing. +.Pp +The +.Sy "ready!" +question is convenient for running shell commands before the installation +begins. +.It Sy kblayout Ns "=" Ns Oo Sy default "|" Ar layout Oc ( default Sy default ) +Switch to this keyboard +.Ar layout +using +.Xr chkblayout 1 ? +The choice is remembered as the +.Xr kblayout 5 +system default. +.It Sy videomode Ns "=" Ns Oo Sy no "|" Sy yes "|" Ar WIDTH Ns x Ns Ar HEIGHT Ns x Ns Ar BPP Oc ( default Sy yes ) +Interactively select a graphics resolution using +.Xr chvideomode 1 +or non-interactively set it to the specified resolution? +The choice is remembered as the +.Xr videomode 5 +system default. +.Pp +If the installation is non-interactive with +.Sy accept_defaults=true , +then the default is instead +.Sy no . +.It Sy grub Ns "=" Ns Oo Sy no "|" Sy yes Oc +Install the GRUB bootloader onto the device containing the +.Pa /boot +filesystem (if it exists, otherwise the root filesystem)? +The default is +.Sy yes +if any existing installations are found with GRUB enabled or if no filesystems +were found, otherwise the default is +.Sy no . +.Pp +This is an essential question that must be answered for automatic installations. +.It Sy grub_password Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Protect the bootloader with a password to avoid unauthorized root access via the +advanced bootloader features? +The standard bootloader menu options don't require a password. +The password is set in the +.Sy grub_password_hash +question. +.Pp +If the installation is non-interactive with +.Sy accept_defaults=true , +then the default is instead +.Sy no +if the +.Sy grub_password_hash +question is not answered. +.It Sy grub_password_hash Ns "=" Ns Ar hash +The bootloader password as hashed by the +.Xr grub-mkpasswd-pbkdf2 1 +program? +The password is asked interactively if this question is not answered. +The choice is remembered in +.Xr grubpw 5 . +.Pp +Although it's discouraged to place unhashed passwords in +.Nm , +the +.Sy "grub_password!" +question could be answered with +.Li "grub-mkpasswd-pbkdf2 -p password" +to dynamically hash the bootloader password. +.It Sy grub_password_empty Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Allow an insecure empty bootloader password typed interactively? +.It Sy disked Ns "=" Ns Ar commands +Commands to create partitions and filesystems with +.Xr disked 8 ? +The +.Li ++= +syntax is useful to append multiple lines to the +.Sy disked +answer. +A root filesystem must be mounted in order to install the operating system. +The mountpoints are remembered in +.Xr fstab 5 . +.Pp +This is an essential question that must be answered for automatic installations. +.Pp +The +.Sy "disked!" +question is convenient for running shell commands before the partitioning step, +or to generate dynamic partitioning commands on the standard output if the +original +.Sy disked +question is unanswered. +.It Sy missing_bios_boot_partition Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Attempt +.Xr disked 8 +again if the GRUB bootloader is installed but the +.Pa /boot +directory's filesystem is inside a GPT partition table that doesn't have a +biosboot partition? +.It Sy confirm_install Ns "=" Ns Oo Sy no "|" Sy yes "|" Sy exit "|" Sy poweroff "|" Sy reboot "|" Sy halt Oc ( default Sy yes ) +Install the operating system or abort the installation? +This is the final confirmation before the operating system is installed, after +the partitioning has taken place. +.Pp +The +.Sy "confirm_install!" +question is convenient for running shell commands before the installation step, +after partitioning has happened, but before the filesystems are mounted. +.It Sy hostname Ns "=" Ns Ar hostname +Hostname for the installation? +The choice is remembered in +.Xr hostname 5 . +.Pp +This is an essential question that must be answered for automatic installations. +If it isn't answered and +.Sy accept_defaults=yes , +then the current (and likely default) hostname is used. +.It Sy password_hash_root Ns "=" Ns Ar hash +Password for the root user as hashed with +.Xr crypt_newhash 2 ? +The empty string sets the password to the empty string (insecure) and a literal +.Sy x +disables password login for root. +The choice is remembered in +.Xr passwd 5 . +.Pp +This is an essential question that must be answered for automatic installations. +If it isn't answered and +.Sy accept_defaults=yes , +then password login is disabled for root. +.Pp +Although it's discouraged to place unhashed passwords in +.Nm , +the +.Sy "password_hash_root!" +question could be answered with +.Li "echo password | passwd -H" +to dynamically hash the root password. +.It Sy password_hash_root Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Allow an insecure empty root password typed interactively? +.It Sy copy_ssh_authorized_keys_root Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Copy the +.Pa /root/.ssh/authorized_keys +file (if it exists) into the installation? +.It Sy copy_ssh_config_root Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Copy the +.Pa /root/.ssh/config +file (if it exists) into the installation? +.It Sy copy_ssh_id_rsa_root Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Copy the +.Pa /root/.ssh/id_rsa +file (if it exists) into the installation? +.It Sy copy_ssh_known_hosts_root Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Copy the +.Pa /root/.ssh/known_hosts +file (if it exists) into the installation? +.It Sy empty_password Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Allow insecure empty passwords for regular users? +.It Sy enable_ntpd Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Automatically get time from the network using +.Xr ntpd 8 ? +Note this choice has privacy implications as the servers in +.Xr ntpd.conf 5 +file will be contacted in the background. +The choice is remembered in +.Pa /etc/init/local +per +.Xr init 5 . +.It Sy enable_sshd Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Enable the +.Xr sshd 8 +secure shell server? +The choice is remembered in +.Pa /etc/init/local +per +.Xr init 5 . +.It Sy copy_sshd_config Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Copy the +.Pa /etc/sshd_config +file (if it exists) into the installation? +.It Sy enable_sshd_password Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Enable password authentication in the local +.Xr sshd 8 ? +The choice is remembered in +.Xr sshd_config 5 . +.Pp +It's strongly encouraged to answer +.Sy no +and instead use public key authentication. +The installation environment can be seeded with the ssh keys using the +.Xr release-iso-modification 7 +procedure and the default answers will copy the keys into the installation. +.It Sy enable_sshd_root_password Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) +Enable ssh to the root user with password authentication? +.It Sy copy_sshd_private_keys Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Copy the sshd private keys (if they exist) into the installation? +These are +.Pa /etc/ssh_host_ecdsa_key , +.Pa /etc/ssh_host_ed25519_key , +and +.Pa /etc/ssh_host_rsa_key . +.It Sy finally Ns "=" Ns Oo Sy exit "|" Sy poweroff "|" Sy reboot "|" Sy halt "|" Sy boot Oc ( default Sy boot ) +What action should be taken when the installation is finished? +.Pp +The +.Sy "finally!" +question is convenient for running shell commands once the installation is +complete to customize the installation. +The working directory is the root filesystem of the installation with all +filesystems mounted. +The +.Li "chroot -d ." +command is useful to +.Xr chroot 8 +into the new root filesystem to run commands with the +.Pa /dev +filesystem mounted. +.El +.Sh FILES +.Bl -tag -width "/etc/autoinstall.conf" -compact +.It Pa /etc/autoinstall.conf +Automatic installation configuration. +.El +.Sh EXAMPLES +.Ss Fully automated installation +To perform a fully automated installation with root login disabled, create +.Pa autoinstall.conf : +.Bd -literal -offset indentq +accept_defaults=yes +grub=yes +disked++=mktable mbr +disked++=mkpart 1 0% 100% ext2 / +hostname=dragon +password_hash_root=x +.Ed +.Pp +Then follow +.Xr release-iso-modification 7 +to configure the installation medium: +.Bd -literal -offset indent +tix-iso-liveconfig --autoinstall=autoinstall.conf liveconfig +tix-iso-bootconfig \\ + --liveconfig=liveconfig --default=1 --random-seed \\ + bootconfig +tix-iso-add sortix.iso bootconfig -o autosortix.iso +.Ed +.Pp +The resulting +.Pa autosortix.iso +image will then automatically format the harddisk and install the operating +system on whatever machine it is booted on. +Take care. +.Ss Setting passwords +The +.Xr grub-mkpasswd-pbkdf2 1 +command can be used to hash a GRUB password and +.Xr passwd 1 +can be used to hash a user password: +.Bd -literal -offset indent +grub-mkpasswd-pbkdf2 -p bootloader-password +echo root-password | passwd -H +.Ed +.Pp +The hashes can then be inserted in +.Nm : +.Bd -literal -offset indent +grub_password_hash=grub.pbkdf2.sha512.10000.68DA[...] +password_hash_root=$2b$10$d/9pP1[...] +.Ed +.Pp +Alternatively the passwords could be embedded in +.Nm , +however beware that your +.Nm +file and the installation medium could leak: +.Bd -literal -offset indent +grub_password_hash!=grub-mkpasswd-pbkdf2 -p bootloader-password +password_hash_root!=echo root-password | passwd -H +.Ed +.Ss Automatically install with sshd +To automatically install the operating system with sshd enabled with keys and +authorizations set up ahead of time, first create +.Pa autoinstall.conf : +.Bd -literal -offset indent +accept_defaults=yes +grub=yes +disked++=mktable mbr +disked++=mkpart 1 0% 100% ext2 / +hostname=example.com +password_hash_root=x +enable_sshd=yes +.Ed +.Pp +Then follow +.Xr release-iso-modification 7 +to configure the installation medium with automated installation and sshd: +.Bd -literal -offset indent +tix-iso-liveconfig \\ + --autoinstall=autoinstall.conf \\ + --root-ssh-authorized-keys="$HOME/.ssh/id_rsa.pub" \\ + --sshd-keygen \\ + --sshd-key-known-hosts-file="$HOME/.ssh/known_hosts" \\ + --sshd-key-known-hosts-hosts="example.com example.com,192.0.2.1 192.0.2.1" \\ + liveconfig +tix-iso-bootconfig \\ + --liveconfig=liveconfig --default=1 --random-seed \\ + bootconfig +tix-iso-add sortix.iso bootconfig -o autosortix.iso +.Ed +.Pp +To ssh into the installation after it has finished: +.Bd -literal -offset indent +ssh root@example.com +.Ed +.Pp +The connection will be immediately trusted because the just-generated server +keys were associated with the hostname in the +.Pa $HOME/.ssh/known_hosts +file. +This step assumes the hostname (example.com in this example) resolves to the +installation's IP address, otherwise the IP address can be used directly. +If the IP address is known ahead of time, it should be inserted into the +space delimited +.Fl \-sshd-key-known-hosts-hosts +list of host aliases. +.Ss Automatically install into a virtual machine with sshd +To automatically install into a qemu virtual machine, follow the above example +but instead associate the server keys with your localhost and optionally use a +.Pa known_hosts +file per virtual machine. +.Bd -literal -offset indent + ... + --sshd-key-known-hosts-file="sortix.hdd.known_hosts" \\ + --sshd-key-known-hosts-hosts=127.0.0.1 \\ + ... +.Ed +.Pp +Create the harddisk and spawn the virtual machine and forward the local port +2222 to the virtual machine's port 22. +.Bd -literal -offset indent +qemu-img create -f qcow2 sortix.hdd 1G +qemu-system-x86_64 \\ + -vga std -m 1024 \\ + -hda sortix.hdd -cdrom autosortix.iso \\ + -device e1000,netdev=net0 \\ + -netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22 +.Ed +.Pp +To ssh into the installation after it has finished: +.Bd -literal -offset indent +ssh -oUserKnownHostsFile=sortix.hdd.known_hosts -p 2222 root@127.0.0.1 +.Ed +.Sh SEE ALSO +.Xr autoupgrade.conf 5 , +.Xr upgrade.conf 5 , +.Xr release-iso-modification 7 , +.Xr upgrade 7 , +.Xr sysupgrade 8 , +.Xr tix 8 diff --git a/share/man/man5/autoupgrade.conf.5 b/share/man/man5/autoupgrade.conf.5 new file mode 100644 index 00000000..8dcf02ee --- /dev/null +++ b/share/man/man5/autoupgrade.conf.5 @@ -0,0 +1,203 @@ +.Dd April 23, 2023 +.Dt AUTOUPGRADE.CONF 5 +.Os +.Sh NAME +.Nm autoupgrade.conf +.Nd automatic upgrade configuration +.Sh SYNOPSIS +.Nm /etc/autoupgrade.conf +.Sh DESCRIPTION +.Nm +configures +.Xr sysupgrade 8 +to do an automatic operating system +.Xr upgrade 7 . +.Pp +The system administrator can automate operating system upgrades by +following +.Xr release-iso-modification 7 +to embed the +.Pa /etc/autoupgrade.conf +file into the release cdrom filesystem. +New installations can similarly be automatically installed using +.Xr autoinstall.conf 5 . +.Pp +Each line is formatted as +.Ar question Ns = Ns Ar answer +which provides an +.Ar answer +to a +.Ar question +asked during upgrading. +Alternatively +.Ar question Ns += Ns Ar answer +appends to an existing answer (if any) separated by a space, and +.Ar question Ns ++= Ns Ar answer +appends another line to an existing answer (if any). +Empty lines and lines starting with +.Sq # +are comments and are ignored. +Otherwise whitespace is significant. +The empty answer accepts the default answer (if any) and is different from no +answer, which makes +.Xr sysupgrade 8 +ask the question normally. +Upgrades can be made fully non-interactive using +.Sy accept_defaults . +.Pp +Each question has a counterpart question suffixed with +.Sq "!" +which contains a +.Xr sh 1 +script that is executed before the question is asked. +If the original question isn't answered, then the script's standard output +is used as the answer to the original question. +The upgrade aborts if the script exits unsuccessfully. +These scripts are useful to customize the upgrades with arbitrary code. +.Pp +The questions in chronological order are as follows: +.Bl -tag -width "12345678" +.It Sy accept_defaults Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Accept the default answer (if any) to questions that were not +configured in +.Nm ? +This feature makes upgrades entirely automated even if unexpected questions +are asked, although the essential questions must be answered. +.It Sy countdown Ns "=" Ns Ar seconds No ( default Li 10 ) +Count down for this many +.Ar seconds +with a warning that an automated upgrade is about to happen? +The countdown happens if +.Sy accept_defaults=yes +or if the +.Sy ready +and +.Sy confirm_upgrrade +questions are answered. +.It Sy ignore_missing_programs Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Ignore if the upgrader environment does not have the needed ports installed? +This situation ordinarily does not happen. +.It Sy ready Ns "=" Ns Ar affirmation +Are you ready to begin the upgrade process? +This is a human readable positive affirmation of your choice that you're ready +to upgrade, useful for fully automated upgrades. +Not answering this question is useful for semi-automated upgrades where one +would acknowledge the upgrades before it's begun. +It also provides the opportunity to escape to a shell before upgrading. +.Pp +The +.Sy "ready!" +question is convenient for running shell commands before the upgrade begins. +.It Sy kblayout Ns "=" Ns Oo Sy default "|" Ar layout Oc ( default Sy default ) +Switch to this keyboard +.Ar layout +using +.Xr chkblayout 1 ? +.It Sy videomode Ns "=" Ns Oo Sy no "|" Sy yes "|" Ar WIDTH Ns x Ns Ar HEIGHT Ns x Ns Ar BPP Oc ( default Sy yes ) +Interactively select a graphics resolution using +.Xr chvideomode 1 +or non-interactively set it to the specified resolution? +.Pp +If the upgrade is non-interactive with +.Sy accept_defaults=true , +then the default is instead +.Sy no . +.It Sy run_installer_instead Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy yes ) +Run the +.Xr sysinstall 8 +installer instead if no installations were found? +.Pp +This answer is useful combined with +.Xr autoinstall.conf 5 +to either install or upgrade the operating system, regardless of what is already +installed on the machine. +.It Sy which_installaton Ns "=" Ns Ar block-device +The name of the +.Ar block-device +containing the root filesystem to upgrade? +This question is only asked if multiple installations were found. +.It Sy switch_architecture Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Switch the installation to another architecture? +.Pp +Such upgrades are not supported and may corrupt the installation. +.It Sy downgrade_release Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Downgrade the installation to an earlier release? +.Pp +Such upgrades are not supported and may corrupt the installation. +.It Sy skip_release Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Skip upgrading to a release that ordinarily must be upgraded to first? +.Pp +Such upgrades are not supported and may corrupt the installation. +.It Sy downgrade_abi Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy no ) +Downgrade the installation to an earlier ABI? +.Pp +Such upgrades are not supported and may corrupt the installation. +.It Sy cancel_pending_sysmerge Ns "=" Ns Oo Sy no "|" yes Oc ( default Sy yes ) +Cancel an existing pending +.Xr sysmerge 8 +upgrade? +.It Sy confirm_upgrade Ns "=" Ns Oo Sy no "|" Sy yes "|" Sy exit "|" Sy poweroff "|" Sy reboot "|" Sy halt Oc ( default Sy yes ) +Upgrade the operating system or abort the upgrade? +This is the final confirmation before the operating system is upgraded. +.Pp +The +.Sy "confirm_upgrade!" +question is convenient for running shell commands before the upgrade step. +The working directory is the root filesystem of the installation with all +filesystems mounted. +Note how +.Xr chroot 8 +command may not work as intended at this point since the ABI may have +incompatibly changed. +.It Sy finally Ns "=" Ns Oo Sy exit "|" Sy poweroff "|" Sy reboot "|" Sy halt "|" Sy boot Oc ( default Sy boot ) +What action should be taken when the upgrade is finished? +.Pp +The +.Sy "finally!" +question is convenient for running shell commands once the upgrade is +complete to customize the upgraded installation. +The working directory is the root filesystem of the installation with all +filesystems mounted. +The +.Li "chroot -d ." +command is useful to +.Xr chroot 8 +into the root filesystem to run commands with the +.Pa /dev +filesystem mounted. +.El +.Sh FILES +.Bl -tag -width "/etc/autoupgrade.conf" -compact +.It Pa /etc/autoupgrade.conf +Automatic upgrade configuration. +.El +.Sh EXAMPLES +.Ss Fully automated upgrade +To perform a fully automated upgrade with, create +.Pa autoupgrade.conf : +.Bd -literal -offset indent +accept_defaults=yes +.Ed +.Pp +Then follow +.Xr release-iso-modification 7 +to configure the upgrade medium: +.Bd -literal -offset indent +tix-iso-liveconfig --autoupgrade=autoupgrade.conf liveconfig +tix-iso-bootconfig \\ + --liveconfig=liveconfig --default=1 --random-seed \\ + bootconfig +tix-iso-add sortix.iso bootconfig -o autosortix.iso +.Ed +.Pp +The resulting +.Pa autosortix.iso +image will then automatically upgrade the operating system on whatever machine +it is booted on. +.Sh SEE ALSO +.Xr autoinstall.conf 5 , +.Xr upgrade.conf 5 , +.Xr release-iso-modification 7 , +.Xr upgrade 7 , +.Xr sysupgrade 8 , +.Xr tix 8 diff --git a/share/man/man5/upgrade.conf.5 b/share/man/man5/upgrade.conf.5 index 1bf47e29..93956581 100644 --- a/share/man/man5/upgrade.conf.5 +++ b/share/man/man5/upgrade.conf.5 @@ -109,5 +109,7 @@ src = no grub = yes .Ed .Sh SEE ALSO +.Xr autoinstall.conf 5 , +.Xr autoupgrade.conf 5 , .Xr upgrade 7 , .Xr sysupgrade 8 diff --git a/share/man/man7/installation.7 b/share/man/man7/installation.7 index 4bbe6547..2ac95702 100644 --- a/share/man/man7/installation.7 +++ b/share/man/man7/installation.7 @@ -88,7 +88,8 @@ default bootloader menu option and timeout, the default hostname, the default keyboard layout, the default graphics resolution, adding files of your choice to the live environment, control which drivers are loaded by default, control which live environment daemons are started by default, deploy ssh keys so secure shell -connections are trusted on the first connection, and so on. +connections are trusted on the first connection, configure automatic +installation and upgrading, and so on. .Pp Warning: The live environment does not come with any random entropy and entropy gathering is not yet implemented. diff --git a/share/man/man7/release-iso-bootconfig.7 b/share/man/man7/release-iso-bootconfig.7 index c472fa10..2900236b 100644 --- a/share/man/man7/release-iso-bootconfig.7 +++ b/share/man/man7/release-iso-bootconfig.7 @@ -319,6 +319,15 @@ variable) is automatically selected. If set to 0, the default menu entry is loaded instantaneously. The timeout is disabled if set to -1. (Default: 10) +.It Sy title_single_user +The menu title for the single-user selection. +(Default: live environment) +.It Sy title_sysinstall +The menu title for the sysinstall selection. +(Default: new installation) +.It Sy title_sysupgrade +The menu title for the sysupgrade selection. +(Default: upgrade existing installation) .It Sy tix_ Ns Ar $port A copy of the binary package is stored in .Pa /repository diff --git a/share/man/man7/release-iso-modification.7 b/share/man/man7/release-iso-modification.7 index b6ac0d1b..ac6e0d8e 100644 --- a/share/man/man7/release-iso-modification.7 +++ b/share/man/man7/release-iso-modification.7 @@ -18,7 +18,8 @@ default bootloader menu option and timeout, the default hostname, the default keyboard layout, the default graphics resolution, adding files of your choice to the live environment, control which drivers are loaded by default, control which live environment daemons are started by default, deploy ssh keys so secure shell -connections are trusted on the first connection, and so on. +connections are trusted on the first connection, configure automatic +installation and upgrading, and so on. .Ss Prerequisites .Bl -bullet -compact .It @@ -513,6 +514,22 @@ ssh-keygen -t rsa -f liveconfig/root/.ssh/id_rsa -N "" -C "root@$hostname" Consider omitting the .Fl N option and password protect the private key to protect it in the case of a leak. +.Ss Automatic Installation +To customize a release so it automatically installs itself per the +.Xr autoinstall.conf 5 : +.Bd -literal +tix-iso-liveconfig --autoinstall=autoinstall.conf liveconfig +tix-iso-bootconfig --liveconfig=liveconfig --default=1 bootconfig +tix-iso-add sortix.iso bootconfig +.Ed +.Ss Automatic Upgrade +To customize a release so it automatically upgrades a local installation per the +.Xr autoupgrade.conf 5 : +.Bd -literal +tix-iso-liveconfig --autoinstall=autoupgrade.conf liveconfig +tix-iso-bootconfig --liveconfig=liveconfig --default=2 bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr xorriso 1 , .Xr development 7 , diff --git a/share/man/man7/upgrade.7 b/share/man/man7/upgrade.7 index 43781ab7..b32b6316 100644 --- a/share/man/man7/upgrade.7 +++ b/share/man/man7/upgrade.7 @@ -31,7 +31,8 @@ default bootloader menu option and timeout, the default hostname, the default keyboard layout, the default graphics resolution, adding files of your choice to the live environment, control which drivers are loaded by default, control which live environment daemons are started by default, deploy ssh keys so secure shell -connections are trusted on the first connection, and so on. +connections are trusted on the first connection, configure automatic +installation and upgrading, and so on. .Pp Warning: The live environment does not come with any random entropy and entropy gathering is not yet implemented. diff --git a/sysinstall/Makefile b/sysinstall/Makefile index 52b2ef57..ad11b1df 100644 --- a/sysinstall/Makefile +++ b/sysinstall/Makefile @@ -16,6 +16,7 @@ sysmerge.o \ sysupgrade.o \ UTIL_OBJS=\ +autoconf.o \ conf.o \ devices.o \ execute.o \ @@ -28,9 +29,9 @@ string_array.o \ OBJS=$(MAIN_OBJS) $(UTIL_OBJS) -SYSINSTALL_DEPS=conf devices execute fileops interactive manifest release string_array +SYSINSTALL_DEPS=autoconf conf devices execute fileops interactive manifest release string_array SYSMERGE_DEPS=conf fileops execute hooks manifest release string_array -SYSUPGRADE_DEPS=conf devices execute fileops hooks interactive manifest release string_array +SYSUPGRADE_DEPS=autoconf conf devices execute fileops hooks interactive manifest release string_array SYSINSTALL_OBJS:=sysinstall.o $(SYSINSTALL_DEPS:=.o) SYSMERGE_OBJS:=sysmerge.o $(SYSMERGE_DEPS:=.o) @@ -73,13 +74,14 @@ sysupgrade: $(SYSUPGRADE_OBJS) sysinstall.o: $(SYSINSTALL_DEPS:=.h) sysmerge.o: $(SYSMERGE_DEPS:=.h) sysupgrade.o: $(SYSUPGRADE_DEPS:=.h) +autoconf.o: autoconf.h execute.h conf.o: conf.h devices.o: devices.h execute.o: execute.h fileops.o: fileops.h string_array.h string_array.o: string_array.h hooks.o: fileops.h manifest.h release.h string_array.h -interactive.o: interactive.h execute.h +interactive.o: interactive.h autoconf.h execute.h manifest.o: manifest.h fileops.h string_array.h release.o: release.h diff --git a/sysinstall/autoconf.c b/sysinstall/autoconf.c new file mode 100644 index 00000000..97dd532d --- /dev/null +++ b/sysinstall/autoconf.c @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2017, 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. + * + * autoconf.c + * Parser for autoinstall.conf(5) and autoupgrade.conf(5). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "autoconf.h" +#include "execute.h" + +static char** keyvalues = NULL; +static size_t keyvalues_used = 0; +static size_t keyvalues_length = 0; + +bool has_autoconf = false; + +bool autoconf_has(const char* name) +{ + if ( !name ) + return false; + size_t len = strlen(name); + for ( size_t i = 0; i < keyvalues_used; i++ ) + { + if ( strncmp(keyvalues[i], name, len) ) + continue; + if ( keyvalues[i][len] == '=' ) + return true; + if ( keyvalues[i][len] == '!' && keyvalues[i][len + 1] == '=' ) + return true; + } + return false; +} + +const char* autoconf_get(const char* name) +{ + if ( !name ) + return NULL; + size_t len = strlen(name); + for ( size_t i = 0; i < keyvalues_used; i++ ) + if ( !strncmp(keyvalues[i], name, len) && keyvalues[i][len] == '=' ) + return keyvalues[i] + len + 1; + return NULL; +} + +char* autoconf_eval(const char* name) +{ + if ( !name ) + return NULL; + char* shname; + if ( asprintf(&shname, "%s!", name) < 0 ) + err(1, "malloc"); + const char* script; + char* output = NULL; + if ( (script = autoconf_get(shname)) ) + { + char** out_ptr = autoconf_get(name) ? NULL : &output; + execute((const char*[]) { "sh", "-c", script, NULL }, "efo", out_ptr); + if ( out_ptr ) + { + size_t len = strlen(output); + if ( len && output[len - 1] == '\n' ) + output[len - 1] = '\0'; + } + } + free(shname); + if ( autoconf_get(name) ) + { + free(output); + if ( !(output = strdup(autoconf_get(name))) ) + err(1, "malloc"); + } + return output; +} + +bool autoconf_set(const char* name, const char* value) +{ + char* keyvalue; + if ( asprintf(&keyvalue, "%s=%s", name, value) < 0 ) + return false; + size_t len = strlen(name); + for ( size_t i = 0; i < keyvalues_used; i++ ) + { + if ( !strncmp(keyvalues[i], name, len) && keyvalues[i][len] == '=' ) + { + free(keyvalues[i]); + keyvalues[i] = keyvalue; + return true; + } + } + if ( keyvalues_used == keyvalues_length ) + { + size_t new_length = keyvalues_length ? 2 * keyvalues_length : 4; + char** new_keyvalues = + reallocarray(keyvalues, new_length, sizeof(char*)); + if ( !new_keyvalues ) + return free(keyvalue), false; + keyvalues = new_keyvalues; + keyvalues_length = new_length; + } + keyvalues[keyvalues_used++] = keyvalue; + return true; +} + +void autoconf_load(const char* path) +{ + FILE* fp = fopen(path, "r"); + if ( !fp ) + { + if ( errno != ENOENT ) + warn("%s", path); + return; + } + char* line = NULL; + size_t line_size = 0; + ssize_t line_length; + off_t line_number = 0; + while ( 0 < (line_length = getline(&line, &line_size, fp)) ) + { + line_number++; + if ( line[line_length - 1] == '\n' ) + line[--line_length] = '\0'; + line_length = 0; + while ( line[line_length] && line[line_length] != '#' ) + line_length++; + line[line_length] = '\0'; + char* name = line; + if ( !*name || *name == '=' ) + continue; + size_t name_length = 1; + while ( name[name_length] && + name[name_length] != '=' && + name[name_length] != '+' ) + name_length++; + if ( name[name_length + 0] == '+' && + name[name_length + 1] == '+' && + name[name_length + 2] == '=' ) + { + name[name_length + 0] = '\0'; + char* value = name + name_length + 3; + const char* existing = autoconf_get(name); + if ( existing ) + { + char* full; + if ( asprintf(&full, "%s\n%s", existing, value) < 0 ) + err(2, "%s: malloc", path); + if ( !autoconf_set(name, full) ) + err(2, "%s: malloc", path); + free(full); + } + else if ( !autoconf_set(name, value) ) + err(2, "%s: malloc", path); + } + else if ( name[name_length + 0] == '+' && + name[name_length + 1] == '=' ) + { + name[name_length + 0] = '\0'; + char* value = name + name_length + 2; + const char* existing = autoconf_get(name); + if ( existing ) + { + char* full; + if ( asprintf(&full, "%s %s", existing, value) < 0 ) + err(2, "%s: malloc", path); + if ( !autoconf_set(name, full) ) + err(2, "%s: malloc", path); + free(full); + } + else if ( !autoconf_set(name, value) ) + err(2, "%s: malloc", path); + } + else if ( name[name_length + 0] == '=' ) + { + name[name_length + 0] = '\0'; + char* value = name + name_length + 1; + if ( !autoconf_set(name, value) ) + err(2, "%s: malloc", path); + } + else + errx(2, "%s:%ji: Invalid line: %s", + path, (intmax_t) line_number, line); + char* value = name + name_length; + while ( *value && isblank((unsigned char) *value) ) + value++; + if ( *value != '=' ) + continue; + value++; + while ( *value && isblank((unsigned char) *value) ) + value++; + name[name_length] = '\0'; + } + if ( ferror(fp) ) + err(2, "%s", path); + free(line); + fclose(fp); + has_autoconf = true; +} diff --git a/sysinstall/autoconf.h b/sysinstall/autoconf.h new file mode 100644 index 00000000..92ddcb8a --- /dev/null +++ b/sysinstall/autoconf.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017, 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. + * + * autoconf.h + * Parser for autoinstall.conf(5) and autoupgrade.conf(5). + */ + +#ifndef AUTOCONF_H +#define AUTOCONF_H + +extern bool has_autoconf; + +bool autoconf_has(const char* name); +const char* autoconf_get(const char* name); +char* autoconf_eval(const char* name); +bool autoconf_set(const char* name, const char* value); +void autoconf_load(const char* path); + +#endif diff --git a/sysinstall/execute.c b/sysinstall/execute.c index 50c119b2..62972b9b 100644 --- a/sysinstall/execute.c +++ b/sysinstall/execute.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016 Jonas 'Sortie' Termansen. + * Copyright (c) 2015, 2016, 2017, 2021, 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 @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include "execute.h" @@ -34,6 +36,8 @@ int execute(const char* const* argv, const char* flags, ...) bool exit_on_failure = false; bool foreground = false; bool gid_set = false; + const char* input = NULL; + char** output = NULL; bool raw_exit_code = false; bool uid_set = false; bool quiet = false; @@ -50,6 +54,8 @@ int execute(const char* const* argv, const char* flags, ...) case 'e': exit_on_failure = true; break; case 'f': foreground = true; break; case 'g': gid_set = true; gid = va_arg(ap, gid_t); break; + case 'i': input = va_arg(ap, const char*); break; + case 'o': output = va_arg(ap, char**); break; case 'r': raw_exit_code = true; break; case 'u': uid_set = true; uid = va_arg(ap, uid_t); break; case 'q': quiet = true; break; @@ -63,6 +69,15 @@ int execute(const char* const* argv, const char* flags, ...) sigemptyset(&sigttou); sigaddset(&sigttou, SIGTTOU); } + int output_pipes[2] = {-1, -1}; + if ( output && pipe(output_pipes) < 0 ) + { + if ( !quiet_stderr ) + warn("pipe: %s", argv[0]); + if ( exit_on_failure ) + (_exit_instead ? _exit : exit)(2); + return -1; + } pid_t child_pid = fork(); if ( child_pid < 0 ) { @@ -90,20 +105,111 @@ int execute(const char* const* argv, const char* flags, ...) tcsetpgrp(0, getpgid(0)); sigprocmask(SIG_SETMASK, &oldset, NULL); } + if ( input ) + { + int input_pipes[2]; + if ( pipe(input_pipes) < 0 ) + { + if ( !quiet_stderr ) + warn("pipe: %s", argv[0]); + _exit(2); + } + pid_t input_pid = fork(); + if ( input_pid < 0 ) + { + if ( !quiet_stderr ) + warn("fork: %s", argv[0]); + _exit(2); + } + else if ( input_pid == 0 ) + { + close(input_pipes[0]); + size_t left = strlen(input); + while ( *input ) + { + ssize_t written = write(input_pipes[1], input, left); + if ( written <= 0 ) + break; + input += written; + left -= written; + } + _exit(0); + } + close(input_pipes[1]); + close(0); + dup2(input_pipes[0], 0); + close(input_pipes[0]); + } + if ( output ) + { + close(output_pipes[0]); + close(1); + dup2(output_pipes[1], 1); + close(output_pipes[1]); + } if ( quiet ) { close(1); - open("/dev/null", O_WRONLY); + if ( open("/dev/null", O_WRONLY) < 0 ) + { + if ( !quiet_stderr ) + warn("/dev/null"); + _exit(2); + } } if ( quiet_stderr ) { close(2); - open("/dev/null", O_WRONLY); + if ( open("/dev/null", O_WRONLY) < 0 ) + _exit(2); } execvp(argv[0], (char* const*) argv); warn("%s", argv[0]); _exit(127); } + bool success = true; + if ( output ) + { + close(output_pipes[1]); + char* out; + size_t out_size; + FILE* out_fp = open_memstream(&out, &out_size); + if ( out_fp ) + { + char buf[4096]; + ssize_t amount = 0; + while ( 0 < (amount = read(output_pipes[0], buf, sizeof(buf))) ) + { + if ( fwrite(buf, 1, amount, out_fp) != (size_t) amount ) + { + if ( !quiet_stderr ) + warn("fwrite to memstream"); + success = false; + break; + } + } + if ( success && fclose(out_fp) == EOF ) + { + if ( !quiet_stderr ) + warn("fclose of memstream"); + success = false; + } + close(output_pipes[0]); + if ( !success ) + { + free(out); + *output = NULL; + } + else + *output = out; + } + else + { + success = false; + if ( !quiet_stderr ) + warn("open_memstream"); + } + } int code; waitpid(child_pid, &code, 0); if ( foreground ) @@ -114,11 +220,13 @@ int execute(const char* const* argv, const char* flags, ...) } if ( exit_on_failure ) { - if ( !WIFEXITED(code) || WEXITSTATUS(code) != 0 ) + if ( !success || !WIFEXITED(code) || WEXITSTATUS(code) != 0 ) (_exit_instead ? _exit : exit)(2); } int exit_status; - if ( WIFEXITED(code) ) + if ( !success ) + exit_status = 2; + else if ( WIFEXITED(code) ) exit_status = WEXITSTATUS(code); else exit_status = 128 + WTERMSIG(code); diff --git a/sysinstall/interactive.c b/sysinstall/interactive.c index 20703476..bb82ed4b 100644 --- a/sysinstall/interactive.c +++ b/sysinstall/interactive.c @@ -31,6 +31,7 @@ #include #include +#include "autoconf.h" #include "execute.h" #include "interactive.h" @@ -158,7 +159,7 @@ void promptx(char* buffer, const char* answer, bool catch_if_shell) { - (void) autoconf_name; + char* autoconf_answer = autoconf_eval(autoconf_name); while ( true ) { printf("\e[1m"); @@ -169,6 +170,26 @@ void promptx(char* buffer, else printf(" "); fflush(stdout); + const char* accept_defaults = autoconf_get("accept_defaults"); + const char* automatic_answer = NULL; + if ( autoconf_answer ) + { + automatic_answer = autoconf_answer; + if ( !automatic_answer[0] ) + automatic_answer = answer; + } + else if ( accept_defaults && !strcasecmp(accept_defaults, "yes") ) + automatic_answer = answer; + if ( automatic_answer ) + { + printf("\e[93m"); + printf("%s\n", automatic_answer); + printf("\e[m"); + fflush(stdout); + strlcpy(buffer, automatic_answer, buffer_size); + free(autoconf_answer); + return; + } fgets(buffer, buffer_size, stdin); printf("\e[22m"); fflush(stdout); @@ -200,6 +221,7 @@ void promptx(char* buffer, } break; } + free(autoconf_answer); } void password(char* buffer, diff --git a/sysinstall/sysinstall.8 b/sysinstall/sysinstall.8 index e40ada20..94378fb8 100644 --- a/sysinstall/sysinstall.8 +++ b/sysinstall/sysinstall.8 @@ -20,5 +20,6 @@ must be run as root with stdin, stdout and stderr all pointing to the terminal. It is intended to be run from a live environment and not from an actual installation. .Sh SEE ALSO +.Xr autoinstall.conf 5 , .Xr installation 7 , .Xr sysupgrade 8 diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index 0e7a8a77..3593aec1 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ #include #include +#include "autoconf.h" #include "conf.h" #include "devices.h" #include "execute.h" @@ -376,6 +378,12 @@ void exit_handler(void) execute((const char*[]) { "rm", "-rf", etc, NULL }, ""); } +static void cancel_on_sigint(int signum) +{ + (void) signum; + errx(2, "fatal: Installation canceled"); +} + int main(void) { shlvl(); @@ -413,11 +421,54 @@ int main(void) if ( !conf_load(&conf, "/etc/upgrade.conf") && errno != ENOENT ) warn("/etc/upgrade.conf"); + 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); + 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") | @@ -469,6 +520,12 @@ int main(void) }; size_t num_readies = sizeof(readies) / sizeof(readies[0]); const char* ready = readies[arc4random_uniform(num_readies)]; + 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"); @@ -574,24 +631,26 @@ int main(void) good = true; } } - const char* def = good ? "no" : "yes"; + const char* def = non_interactive || good ? "no" : "yes"; while ( true ) { prompt(input, sizeof(input), "videomode", - "Select a default display resolution? (yes/no)", def); - if ( strcasecmp(input, "no") && strcasecmp(input, "yes") ) + "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; + } + const char* r = set ? input : NULL; + if ( execute((const char*[]) { "chvideomode", r, NULL }, "f") != 0 ) continue; - bool was_no = strcasecmp(input, "no") == 0; input[0] = '\0'; - if ( was_no ) - break; - if ( execute((const char*[]) { "chvideomode", NULL }, "f") != 0 ) - continue; - if ( dispmsg_issue(&get_mode, sizeof(get_mode)) < 0 ) - break; - if ( !(get_mode.mode.control & DISPMSG_CONTROL_VALID) ) - break; - if ( get_mode.mode.control & DISPMSG_CONTROL_VGA ) + if ( dispmsg_issue(&get_mode, sizeof(get_mode)) < 0 || + !(get_mode.mode.control & DISPMSG_CONTROL_VALID) || + get_mode.mode.control & DISPMSG_CONTROL_VGA ) break; snprintf(input, sizeof(input), "%ux%ux%u", get_mode.mode.view_xres, @@ -654,14 +713,23 @@ int main(void) text("\n"); while ( true ) { + const char* def = + non_interactive && + !autoconf_has("grub_password_hash") ? "no" : "yes"; prompt(accept_grub_password, sizeof(accept_grub_password), "grub_password", - "Password protect interactive bootloader? (yes/no)", "yes"); + "Password protect interactive bootloader? (yes/no)", def); if ( strcasecmp(accept_grub_password, "no") == 0 || strcasecmp(accept_grub_password, "yes") == 0 ) break; } - while ( !strcasecmp(accept_grub_password, "yes") ) + 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]; @@ -730,14 +798,17 @@ int main(void) text("Type man to display the disked(8) man page.\n"); not_first = true; const char* argv[] = { "disked", "--fstab=fstab", NULL }; - if ( execute(argv, "f") != 0 ) + char* disked_input = autoconf_eval("disked"); + if ( execute(argv, "fi", disked_input) != 0 ) { + 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; } + free(disked_input); free_mountpoints(mountpoints, mountpoints_used); mountpoints = NULL; mountpoints_used = 0; @@ -969,6 +1040,8 @@ int main(void) else while ( true ) { char defhost[HOST_NAME_MAX + 1] = ""; + if ( non_interactive ) + gethostname(defhost, sizeof(defhost)); FILE* defhost_fp = fopen("etc/hostname", "r"); if ( defhost_fp ) { @@ -1003,6 +1076,24 @@ int main(void) { textf("Root account already exists, skipping creating it.\n"); } + 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]; @@ -1112,8 +1203,9 @@ int main(void) text("Congratulations, the system is now functional! This is a good time " "to do further customization of the system.\n\n"); + // TODO: autoconf users support. bool made_user = false; - for ( uid_t uid = 1000; true; ) + for ( uid_t uid = 1000; !has_autoconf; ) { while ( passwd_has_uid("etc/passwd", uid) ) uid++; @@ -1195,7 +1287,9 @@ int main(void) uid++; made_user = true; } - text("\n"); + // TODO: autoconf support. + if ( !has_autoconf ) + text("\n"); // TODO: Ask if networking should be disabled / enabled. @@ -1303,7 +1397,7 @@ int main(void) !access_or_die("/etc/sshd_config", F_OK); while ( true ) { - prompt(input, sizeof(input), "enable_ssh", + prompt(input, sizeof(input), "enable_sshd", "Enable ssh server? (yes/no)", might_want_sshd ? "yes" : "no"); if ( strcasecmp(input, "no") == 0 ) @@ -1356,7 +1450,7 @@ int main(void) bool enable_sshd_password = false; while ( true ) { - prompt(input, sizeof(input), "enable_ssh_password", + prompt(input, sizeof(input), "enable_sshd_password", "Enable sshd password authentication? (yes/no)", "no"); if ( strcasecmp(input, "no") == 0 ) break; @@ -1374,7 +1468,7 @@ int main(void) } while ( enable_sshd_password ) { - prompt(input, sizeof(input), "enable_ssh_root_password", + prompt(input, sizeof(input), "enable_sshd_root_password", "Enable sshd password authentication for root? (yes/no)", "no"); if ( strcasecmp(input, "no") == 0 ) diff --git a/sysinstall/sysupgrade.8 b/sysinstall/sysupgrade.8 index 5a15c2db..f860f07e 100644 --- a/sysinstall/sysupgrade.8 +++ b/sysinstall/sysupgrade.8 @@ -22,5 +22,6 @@ must be run as root with stdin, stdout and stderr all pointing to the terminal. It is intended to be run from a live environment and not from an actual installation. .Sh SEE ALSO +.Xr autoupgrade.conf 5 , .Xr upgrade 7 , .Xr sysinstall 8 diff --git a/sysinstall/sysupgrade.c b/sysinstall/sysupgrade.c index 180b1cbb..d55c2270 100644 --- a/sysinstall/sysupgrade.c +++ b/sysinstall/sysupgrade.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include #include +#include "autoconf.h" #include "conf.h" #include "devices.h" #include "execute.h" @@ -329,6 +331,12 @@ void exit_handler(void) rmdir(fs); } +static void cancel_on_sigint(int signum) +{ + (void) signum; + errx(2, "fatal: Upgrade canceled"); +} + int main(void) { shlvl(); @@ -349,6 +357,13 @@ int main(void) if ( atexit(exit_handler) != 0 ) err(2, "atexit"); + autoconf_load("/etc/autoupgrade.conf"); + + char* accepts_defaults = autoconf_eval("accept_defaults"); + bool non_interactive = accepts_defaults && + !strcasecmp(accepts_defaults, "yes"); + free(accepts_defaults); + struct utsname uts; uname(&uts); @@ -357,6 +372,41 @@ int main(void) textf("Hello and welcome to the " BRAND_DISTRIBUTION_NAME " " VERSIONSTR "" " upgrader for %s.\n\n", uts.machine); + if ( autoconf_has("ready") || + autoconf_has("confirm_upgrade") ) + { + 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 upgrading to " 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 upgrading " 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") | @@ -401,6 +451,9 @@ int main(void) }; size_t num_readies = sizeof(readies) / sizeof(readies[0]); const char* ready = readies[arc4random_uniform(num_readies)]; + if ( autoconf_has("confirm_upgrade") ) + text("Warning: This upgrader will automatically upgrade an operating " + "system!\n"); prompt(input, sizeof(input), "ready", "Ready?", ready); text("\n"); @@ -479,16 +532,20 @@ int main(void) good = true; } } - const char* def = good ? "no" : "yes"; + const char* def = non_interactive || good ? "no" : "yes"; while ( true ) { prompt(input, sizeof(input), "videomode", - "Select display resolution? (yes/no)", def); - if ( strcasecmp(input, "no") && strcasecmp(input, "yes") ) + "Select display resolution? " + "(yes/no/WIDTHxHEIGHTxBPP)", def); + unsigned int xres, yres, bpp; + bool set = sscanf(input, "%ux%ux%u", &xres, &yres, &bpp) == 3; + if ( !set && strcasecmp(input, "no") && strcasecmp(input, "yes") ) continue; if ( strcasecmp(input, "no") == 0 ) break; - if ( execute((const char*[]) { "chvideomode", NULL }, "f") != 0 ) + const char* r = set ? input : NULL; + if ( execute((const char*[]) { "chvideomode", r, NULL }, "f") != 0 ) continue; break; } @@ -535,7 +592,7 @@ int main(void) execlp("sysinstall", "sysinstall", (const char*) NULL); err(2, "sysinstall"); } - continue; + errx(2, "Upgrade aborted since no installations were found"); } while ( true ) diff --git a/tix/tix-iso-bootconfig b/tix/tix-iso-bootconfig index 5ee14d01..467a7e34 100755 --- a/tix/tix-iso-bootconfig +++ b/tix/tix-iso-bootconfig @@ -130,7 +130,15 @@ if $random_seed; then fi fi +has_autoinstall=false +has_autoupgrade=false if [ -n "$liveconfig" ]; then + if [ -e "$liveconfig/etc/autoinstall.conf" ]; then + has_autoinstall=true + fi + if [ -e "$liveconfig/etc/autoupgrade.conf" ]; then + has_autoupgrade=true + fi mkdir -p -- "$directory/boot" (cd "$liveconfig" && tar -c -f- -- *) > "$directory/boot/liveconfig.tar" rm -f -- "$directory/boot/liveconfig.tar.xz" @@ -147,6 +155,12 @@ mkdir -p -- "$directory/boot/grub" if [ -n "$timeout" ]; then printf 'timeout="%s"\n' "$timeout" fi + if $has_autoinstall; then + echo "title_sysinstall='***AUTOMATIC INSTALLATION***'" + fi + if $has_autoupgrade; then + echo "title_sysupgrade='***AUTOMATIC UPGRADE***'" + fi print_enable_default_bool "$enable_dhclient" dhclient dhclient print_enable_default "$enable_network_drivers" network_drivers network-drivers print_enable_default_bool "$enable_src" src src diff --git a/tix/tix-iso-bootconfig.8 b/tix/tix-iso-bootconfig.8 index 9ab7481d..bd6859cc 100644 --- a/tix/tix-iso-bootconfig.8 +++ b/tix/tix-iso-bootconfig.8 @@ -216,6 +216,13 @@ GRUB module is loaded and an hook is emitted that loads .Pa output-directory/boot/liveconfig.tar.xz as a multiboot module. +.Pp +If the liveconfig contains +.Xr autoinstall.conf 5 +or +.Xr autoupgrade.conf 5 , +the menu titles are modified to loudly warn they will automatically +install/upgrade the operating system. .It Fl \-random-seed Copy 256 bytes of randomness from .Pa /dev/urandom diff --git a/tix/tix-iso-liveconfig b/tix/tix-iso-liveconfig index 5b8d82a1..8773239b 100755 --- a/tix/tix-iso-liveconfig +++ b/tix/tix-iso-liveconfig @@ -18,6 +18,8 @@ set -e +autoinstall= +autoupgrade= daemons= directory= hostname= @@ -51,6 +53,10 @@ for argument do case $dashdash$argument in --) dashdash=yes ;; + --autoinstall=*) autoinstall=$parameter ;; + --autoinstall) previous_option=autoinstall ;; + --autoupgrade=*) autoupgrade=$parameter ;; + --autoupgrade) previous_option=autoupgrade ;; --daemons=*) daemons=$parameter ;; --daemons) previous_option=daemons ;; --hostname=*) hostname=$parameter ;; @@ -101,6 +107,15 @@ fi mkdir -p "$directory" +if [ -n "$autoinstall" ]; then + mkdir -p -- "$directory/etc" + cp -- "$autoinstall" "$directory/etc/autoinstall.conf" +fi + +if [ -n "$autoupgrade" ]; then + mkdir -p -- "$directory/etc" + cp -- "$autoupgrade" "$directory/etc/autoupgrade.conf" +fi if [ -n "$daemons" ]; then mkdir -p -- "$directory/etc/init" diff --git a/tix/tix-iso-liveconfig.8 b/tix/tix-iso-liveconfig.8 index 3ff73c89..81aeac88 100644 --- a/tix/tix-iso-liveconfig.8 +++ b/tix/tix-iso-liveconfig.8 @@ -6,6 +6,8 @@ .Nd generate additional live environment configuration for Sortix .iso releases .Sh SYNOPSIS .Nm +.Op Fl \-autoinstall Ns = Ns Ar file +.Op Fl \-autoupgrade Ns = Ns Ar file .Op Fl \-daemons Ns = Ns Ar daemons .Op Fl \-hostname Ns = Ns Ar hostname .Op Fl \-kblayout Ns = Ns Ar kblayout @@ -53,6 +55,20 @@ installations made from inside it. .Pp The options are as follows: .Bl -tag -width "12345678" +.It Fl \-autoinstall Ns = Ns Ar file +Copy +.Ar file +to +.Pa output-directory/etc/autoinstall.conf . +(See +.Xr autoinstall.conf 5 ) +.It Fl \-autoupgrade Ns = Ns Ar file +Copy +.Ar file +to +.Pa output-directory/etc/autoupgrade.conf . +(See +.Xr autoupgrade.conf 5 ) .It Fl \-daemons Ns = Ns Ar daemons Configures the .Sy local @@ -286,9 +302,32 @@ rm -f bootconfig/boot/liveconfig.xz # When no longer useful. rm -f sortix.iso # When no longer useful. # And erase any media made from sortix.iso when no longer useful. .Ed +.Ss Automatic Installation +To customize a release so it automatically installs itself according to +.Pa autoinstall.conf +(see +.Xr autoinstall.conf 5 ) : +.Bd -literal +tix-iso-liveconfig --autoinstall=autoinstall.conf liveconfig +tix-iso-bootconfig --liveconfig=liveconfig --default=1 bootconfig +tix-iso-add sortix.iso bootconfig +.Ed +.Ss Automatic Upgrade +To customize a release so it automatically upgrades a local installation +according to +.Pa autoupgrade.conf +(see +.Xr autoupgrade.conf 5 ) : +.Bd -literal +tix-iso-liveconfig --autoinstall=autoupgrade.conf liveconfig +tix-iso-bootconfig --liveconfig=liveconfig --default=2 bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr ssh-keygen 1 , .Xr xorriso 1 , +.Xr autoinstall.conf 5 , +.Xr autoupgrade.conf 5 , .Xr hostname 5 , .Xr kblayout 5 , .Xr ssh_config 5 ,