Repository: archlinux/archinstall

Issue:
Allow retrying package download to improve install process reliability
I suggest allowing some way to retry failed package downloads, instead of the current behavior of continuing with the installation process even when `pacman` fails to download a package (leading to a failed installation). This could be caused by intermittent network failures, overloaded package mirrors, etc.

Currently, it is possible to re-launch `archinstall` again and retry the installation process, but the error code it prints before bailing out of the installation process is not that clear (especially if you leave the installation unattended and/or don't scroll up to read the previous log messages :), potentially confusing newbies.

This tends to happen more often when installing bigger DEs and other similarly "huge" packages. The `base` package is less than 200 MB IIRC, and is spread across multiple smaller packages, which makes it much more resilient to failures.

Torxed (via Discord) suggested that instead we only `pacstrap` the core packages (`base` and maybe `base-devel`) and then `arch-chroot` and install the rest of the packages inside that. Maybe with a script that first runs `pacman` with the `--downloadonly` flag, prompts/automatically retry when it fails, and then proceeds to install the packages "for real" if `pacman` exits cleanly.


Discussion:
So I worked on this for a bit because currently I have very unstable connection.

First patch I created follows the OP suggestion by moving everything except base and base-devel to `arch-chroot pacman ...` which came out to be a lot of changes. Which is fine but then I realized that I can just handle network error in pacstrap which is just a few lines change. If process `exit_code` and just restart it as is it seems to run just fine. Because pacstrap always first downloads everything that it hasn't downloaded yet and then runs install process.

I want to PR this, which patch would be better? If neither is good why?

If maintainers don't want this then here are both patches for anyone having similar problem (both are based on c6b499cf47fac7a02714e590cf662a85db3095b1)
[chroot_install.diff.tar.gz](https://github.com/archlinux/archinstall/files/7784473/chroot_install.diff.tar.gz)
[~~chroot_install2.diff.tar.gz~~](https://github.com/archlinux/archinstall/files/7784474/chroot_install2.diff.tar.gz)
[chroot_install2.diff.tar.gz](https://github.com/archlinux/archinstall/files/7785358/chroot_install2.diff.tar.gz) (also retry repo update on failrue)


So, we've addressed the unclear error code.
Archinstall will not silently continue any longer since v2.3.1. Instead it will bail out completely.

The diff-files are helpful.
I'll open a PR with them unless someone already has. However, it will take me a bit of time because I'm trying to get v2.3.1 out there asap : )
> I'll open a PR with them unless someone already has.

Thanks, then I guess I will leave this to you as I'm not really sure which of those to pick and if my changes would break something, I'm pretty new to arch.

I would just like to point out that I haven't forgotten about this yet! I'm merging in a bunch of PR's to push forward towards v2.4.0. After all those are in I'll work on some stability improvements and come back to this :)

Suggestions:
 * Install one package at a time (and detect error codes)
 * Download all packages first, and then install them
 * Install them all, and if we detect a bad error code, just re-try again

I strongly suggest writing a wrapper for pacman/pacstra. Instead of `Installer().pacman()` I think we need a `PacMan(installation_session, packages)` instead. So we handle the logic elsewhere.

Resolving patch:
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -365,15 +365,28 @@ def pacstrap(self, *packages :str, **kwargs :str) -> bool:
 
 		self.log(f'Installing packages: {packages}', level=logging.INFO)
 
-		if (sync_mirrors := run_pacman('-Syy', default_cmd='/usr/bin/pacman')).exit_code == 0:
-			if (pacstrap := SysCommand(f'/usr/bin/pacstrap -C /etc/pacman.conf {self.target} {" ".join(packages)} --noconfirm', peak_output=True)).exit_code == 0:
-				return True
-			else:
-				self.log(f'Could not strap in packages: {pacstrap}', level=logging.ERROR, fg="red")
-				self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=logging.ERROR, fg="red")
-				raise RequirementError("Pacstrap failed. See /var/log/archinstall/install.log or above message for error details.")
-		else:
-			self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=logging.INFO)
+		# TODO: We technically only need to run the -Syy once.
+		try:
+			run_pacman('-Syy', default_cmd='/usr/bin/pacman')
+		except SysCallError as error:
+			self.log(f'Could not sync a new package databse: {error}', level=logging.ERROR, fg="red")
+
+			if storage['arguments'].get('silent', False) is False:
+				if input('Would you like to re-try this download? (Y/n): ').lower().strip() in ('', 'y'):
+					return self.pacstrap(*packages, **kwargs)
+
+			raise RequirementError(f'Could not sync mirrors: {error}', level=logging.ERROR, fg="red")
+
+		try:
+			return SysCommand(f'/usr/bin/pacstrap -C /etc/pacman.conf {self.target} {" ".join(packages)} --noconfirm', peak_output=True).exit_code == 0
+		except SysCallError as error:
+			self.log(f'Could not strap in packages: {error}', level=logging.ERROR, fg="red")
+
+			if storage['arguments'].get('silent', False) is False:
+				if input('Would you like to re-try this download? (Y/n): ').lower().strip() in ('', 'y'):
+					return self.pacstrap(*packages, **kwargs)
+			
+			raise RequirementError("Pacstrap failed. See /var/log/archinstall/install.log or above message for error details.")
 
 	def set_mirrors(self, mirrors :Mapping[str, Iterator[str]]) -> None:
 		for plugin in plugins.values():


Test patch: