Repository: archlinux/archinstall

Issue:
[Feature Request] Btrfs compression option
Fedora ships with zstd level 1 (their study shows level 1 is the [most efficient](https://fedoraproject.org/wiki/Changes/BtrfsTransparentCompression#Simple_Analysis_of_btrfs_zstd_compression_level)) compression enabled by default and this would be great for users with btrfs.
File system can be mounted with `compress=zstd:1` argument and later than add `compress=zstd:1` to fstab.

Of course this can be enabled by user after installation but as arch wiki says;

> Only files created or modified after the mount option is added will be compressed. 

So stuff loaded by archinstall will not be compressed until files modified which is bad (need to re-write all files).
https://wiki.archlinux.org/title/Btrfs#Compression
https://fedoraproject.org/wiki/Changes/BtrfsTransparentCompression
https://btrfs.wiki.kernel.org/index.php/Compression

My own test on fresh install (Arch, Gnome, zstd 1)
```
    [kaan@arch ~]$ sudo compsize -x /
    Processed 138323 files, 71908 regular extents (74483 refs), 85668 inline.
    Type       Perc     Disk Usage   Uncompressed Referenced  
    TOTAL       54%      2.2G         4.1G         4.4G       
    none       100%      1.2G         1.2G         1.2G       
    zstd        35%      1.0G         2.9G         3.1G  
```


Discussion:
We've discussed this back and forth a couple of times, what to enable and what we should leave up to the users.
Most recently I had this discussion in #380 where we landed in a compromise for advanced users: https://github.com/archlinux/archinstall/issues/380#issuecomment-955578905

The option *should* have been placed under the `filesystem` key, but it will do for now.
I think compression might be a sane option unless we can find arguments against it *(I don't know how taxing it is for older CPU's or limited RAM)*.
I fully support this request. :)   Btrfs Compression is a good option that can be provided to the user by Archinstall.  

Arch wiki mentions that Btrfs compression significantly extends the life of Flash based boot media by reducing write amplification.  I am sure Solid State Hard Drives (SSDs) will also get life extension benefits from compression based on the wikipedia article that I linked below. Based on wikipedia, it seems like compression also improves SSD speed.
https://en.wikipedia.org/wiki/Write_amplification  

Archinstall script asks the user about encryption and swap on zram.  Asking about compression (if Btrfs is selected) is a logical step.  "compress=zstd:1" and "compress-force=zstd:1" are good choices that can be provided to the user.  Without the "force" flag some files don't get compressed.  
What im juggling is keeping menu entries simple and minimizing user questions, vs giving users options about every customizable aspect of all the choices.

Discussions like these are tricky because isolated to this context alone, i agree that it makes sense and "whats the harm? its just one option!" comes to mind.

Maybe with a `--compression` flag to easily enable it, but avoid adding one more meny entry?
Yep you're right. There's so much mount options and we can't ask every single one of them but what if we ask single question after user selected disk format type "write down mount options you would like to add, leave blank to use default options", by this way users can write down all the mount options they would like to add like compress, noatime, ssd etc and users who don't know/care about mount options can directly press enter. This would combine a lot of things in a single question.

Edit: we can hide this question with a flag like "--advanced" and basic users won't even see this
Actually questions like this could stay behind "--advanced" flag, this way users could see much more less question and power users can easily see all of them
That seems like a good compromise. I would offer a set of default to standard options. 

Another way is you could do a select screen like you do with the kernel selection
@Torxed, thanks for the quick reply.  You are the visionary and the driving force behind this project.  Do it the way you think is the best.  You can use -compression flag or do what hkaancaliskan recommended.  If possible, chose something that can be implemented before the new Arch Installation CD is ready.  We only have  8 days left before the March 1st deadline. :))


The comment you linked https://github.com/archlinux/archinstall/issues/380#issuecomment-955578905 , seems to talk about format options.  Am I right about this?  My understanding is that inserting "options": "compress-force=zstd:15" into user_disk_layout.json file will not work.

The pull request by wllacer https://github.com/archlinux/archinstall/pull/838 has a mount option in it.  I am using the February Arch install CD.  Should I insert  "mount_options": "compress-force=zstd:15" into the Json file?

**There might be one issue/bug regarding this pull request https://github.com/archlinux/archinstall/pull/838**.  When I run archinstall with --dry-run command, the generated user_disk_layout.json file does not have any format options or mount options fields.  Shouldn't the file contain empty fields "format_options": "" and  "mount_options": "" ? Or do we need a special dry-run command for those.  For example --dry-run-showallfields.  People use --dry-run to generate these json files and they edit them.  They won't even know that new options were implemented if they are not in the json file.  Editing existing fields is also easier/less error prone than adding them from scratch.

More I think about this, more complicated it gets. :))
 
@J1Man: I will experiment and investigate some logical moments of archinstall before I can give a concrete answer :) Just wanted to ping and say that I'm looking in to it and will get back to you :)
> @J1Man: I will experiment and investigate some logical moments of archinstall before I can give a concrete answer :) Just wanted to ping and say that I'm looking in to it and will get back to you :)

How about a generic filesystem mount options menu/prompt but only if --advanced is used?
> > @J1Man: I will experiment and investigate some logical moments of archinstall before I can give a concrete answer :) Just wanted to ping and say that I'm looking in to it and will get back to you :)
> 
> How about a generic filesystem mount options menu/prompt but only if --advanced is used?

Could work too! That would probably be what most users assume `--advanced` to be even :)

Resolving patch:
diff --git a/archinstall/lib/disk/user_guides.py b/archinstall/lib/disk/user_guides.py
--- a/archinstall/lib/disk/user_guides.py
+++ b/archinstall/lib/disk/user_guides.py
@@ -23,12 +23,17 @@ def suggest_single_disk_layout(block_device :BlockDevice,
 	MIN_SIZE_TO_ALLOW_HOME_PART = 40 # GiB
 	using_subvolumes = False
 	using_home_partition = False
+	compression = False
 
 	if default_filesystem == 'btrfs':
 		prompt = 'Would you like to use BTRFS subvolumes with a default structure?'
 		choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run()
 		using_subvolumes = choice == 'yes'
 
+		prompt = 'Would you like to use BTRFS compression?'
+		choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run()
+		compression = choice == 'yes'
+
 	layout = {
 		block_device.path : {
 			"wipe" : True,
@@ -73,7 +78,8 @@ def suggest_single_disk_layout(block_device :BlockDevice,
 		"wipe" : True,
 		"mountpoint" : "/" if not using_subvolumes else None,
 		"filesystem" : {
-			"format" : default_filesystem
+			"format" : default_filesystem,
+			"mount_options" : ["compress=zstd"] if compression else []
 		}
 	})
 
@@ -124,7 +130,8 @@ def suggest_single_disk_layout(block_device :BlockDevice,
 			"wipe" : True,
 			"mountpoint" : "/home",
 			"filesystem" : {
-				"format" : default_filesystem
+				"format" : default_filesystem,
+				"mount_options" : ["compress=zstd"] if compression else []
 			}
 		})
 
@@ -151,6 +158,17 @@ def suggest_multi_disk_layout(block_devices :List[BlockDevice],
 	home_device = select_largest_device(block_devices, gigabytes=MIN_SIZE_TO_ALLOW_HOME_PART)
 	root_device = select_disk_larger_than_or_close_to(block_devices, gigabytes=ARCH_LINUX_INSTALLED_SIZE, filter_out=[home_device])
 
+	compression = False
+
+	if default_filesystem == 'btrfs':
+		# prompt = 'Would you like to use BTRFS subvolumes with a default structure?'
+		# choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run()
+		# using_subvolumes = choice == 'yes'
+
+		prompt = 'Would you like to use BTRFS compression?'
+		choice = Menu(prompt, ['yes', 'no'], skip=False, default_option='yes').run()
+		compression = choice == 'yes'
+
 	log(f"Suggesting multi-disk-layout using {len(block_devices)} disks, where {root_device} will be /root and {home_device} will be /home", level=logging.DEBUG)
 
 	layout = {
@@ -193,7 +211,8 @@ def suggest_multi_disk_layout(block_devices :List[BlockDevice],
 		"wipe" : True,
 		"mountpoint" : "/",
 		"filesystem" : {
-			"format" : default_filesystem
+			"format" : default_filesystem,
+			"mount_options" : ["compress=zstd"] if compression else []
 		}
 	})
 	if has_uefi():
@@ -208,7 +227,8 @@ def suggest_multi_disk_layout(block_devices :List[BlockDevice],
 		"wipe" : True,
 		"mountpoint" : "/home",
 		"filesystem" : {
-			"format" : default_filesystem
+			"format" : default_filesystem,
+			"mount_options" : ["compress=zstd"] if compression else []
 		}
 	})
 
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -235,7 +235,7 @@ def mount_ordered_layout(self, layouts: Dict[str, Any]) -> None:
 			list_part.extend(layouts[blockdevice]['partitions'])
 
 		# we manage the encrypted partititons
-		for partition in [entry for entry in list_part if entry.get('encrypted',False)]:
+		for partition in [entry for entry in list_part if entry.get('encrypted', False)]:
 			# open the luks device and all associate stuff
 			if not (password := partition.get('!password', None)):
 				raise RequirementError(f"Missing partition {partition['device_instance'].path} encryption password in layout: {partition}")
@@ -252,7 +252,11 @@ def mount_ordered_layout(self, layouts: Dict[str, Any]) -> None:
 
 		# we manage the btrfs partitions
 		for partition in [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', {})]:
-			self.mount(partition['device_instance'],"/")
+			if partition.get('filesystem',{}).get('mount_options',[]):
+				mount_options = ','.join(partition['filesystem']['mount_options'])
+				self.mount(partition['device_instance'], "/", options=mount_options)
+			else:
+				self.mount(partition['device_instance'], "/")
 			try:
 				new_mountpoints = manage_btrfs_subvolumes(self,partition)
 			except Exception as e:
@@ -270,7 +274,7 @@ def mount_ordered_layout(self, layouts: Dict[str, Any]) -> None:
 
 			if partition.get('filesystem',{}).get('mount_options',[]):
 				mount_options = ','.join(partition['filesystem']['mount_options'])
-				partition['device_instance'].mount(f"{self.target}{mountpoint}",options=mount_options)
+				partition['device_instance'].mount(f"{self.target}{mountpoint}", options=mount_options)
 			else:
 				partition['device_instance'].mount(f"{self.target}{mountpoint}")
 
@@ -287,11 +291,11 @@ def mount_ordered_layout(self, layouts: Dict[str, Any]) -> None:
 			log(f"creating key-file for {ppath}",level=logging.INFO)
 			self._create_keyfile(handle[0],handle[1],handle[2])
 
-	def mount(self, partition :Partition, mountpoint :str, create_mountpoint :bool = True) -> None:
+	def mount(self, partition :Partition, mountpoint :str, create_mountpoint :bool = True, options='') -> None:
 		if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'):
 			os.makedirs(f'{self.target}{mountpoint}')
 
-		partition.mount(f'{self.target}{mountpoint}')
+		partition.mount(f'{self.target}{mountpoint}', options=options)
 
 	def post_install_check(self, *args :str, **kwargs :str) -> List[str]:
 		return [step for step, flag in self.helper_flags.items() if flag is False]
diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py
--- a/archinstall/lib/user_interaction/partitioning_conf.py
+++ b/archinstall/lib/user_interaction/partitioning_conf.py
@@ -113,7 +113,7 @@ def select_individual_blockdevice_usage(block_devices: list) -> Dict[str, Any]:
 	return result
 
 
-def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, Any]:
+def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, Any]: # noqa: max-complexity: 50
 	block_device_struct = {"partitions": [partition.__dump__() for partition in block_device.partitions.values()]}
 	# Test code: [part.__dump__() for part in block_device.partitions.values()]
 	# TODO: Squeeze in BTRFS subvolumes here
@@ -125,6 +125,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
 	assign_mount_point = str(_('Assign mount-point for a partition'))
 	mark_formatted = str(_('Mark/Unmark a partition to be formatted (wipes data)'))
 	mark_encrypted = str(_('Mark/Unmark a partition as encrypted'))
+	mark_compressed = str(_('Mark/Unmark a partition as compressed (btrfs only)'))
 	mark_bootable = str(_('Mark/Unmark a partition as bootable (automatic for /boot)'))
 	set_filesystem_partition = str(_('Set desired filesystem for a partition'))
 	set_btrfs_subvolumes = str(_('Set desired subvolumes on a btrfs partition'))
@@ -140,6 +141,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
 				mark_formatted,
 				mark_encrypted,
 				mark_bootable,
+				mark_compressed,
 				set_filesystem_partition,
 				set_btrfs_subvolumes,
 			]
@@ -213,6 +215,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
 					continue
 
 			block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path])
+						
 		elif task is None:
 			return block_device_struct
 		else:
@@ -226,6 +229,18 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
 					block_device_struct['partitions'] = [
 						p for idx, p in enumerate(block_device_struct['partitions']) if idx not in to_delete
 					]
+			elif task == mark_compressed:
+				title = _('{}\n\nSelect which partition to mark as bootable').format(current_layout)
+				partition = select_partition(title, block_device_struct["partitions"])
+
+				if partition is not None:
+					if "filesystem" not in block_device_struct["partitions"][partition]:
+						block_device_struct["partitions"][partition]["filesystem"] = {}
+					if "mount_options" not in block_device_struct["partitions"][partition]["filesystem"]:
+						block_device_struct["partitions"][partition]["filesystem"]["mount_options"] = []
+
+					if "compress=zstd" not in block_device_struct["partitions"][partition]["filesystem"]["mount_options"]:
+						block_device_struct["partitions"][partition]["filesystem"]["mount_options"].append("compress=zstd")
 			elif task == delete_all_partitions:
 				block_device_struct["partitions"] = []
 			elif task == assign_mount_point:


Test patch: