updated to latest commits
This commit is contained in:
commit
f4e616cd9e
|
|
@ -0,0 +1,31 @@
|
||||||
|
# This workflow will upload a Python Package using Twine when a release is created
|
||||||
|
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||||
|
|
||||||
|
name: Upload archinstall to PyPi
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install setuptools wheel twine
|
||||||
|
- name: Build and publish
|
||||||
|
env:
|
||||||
|
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||||
|
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
python setup.py sdist bdist_wheel
|
||||||
|
twine upload dist/*
|
||||||
|
|
@ -40,7 +40,12 @@ Also, do not squash your commits after you have submitted a pull request, as thi
|
||||||
At present the current contributors are (alphabetically):
|
At present the current contributors are (alphabetically):
|
||||||
|
|
||||||
* Anton Hvornum ([@Torxed](https://github.com/Torxed))
|
* Anton Hvornum ([@Torxed](https://github.com/Torxed))
|
||||||
|
* Borislav Kosharov ([@nikibobi](https://github.com/nikibobi))
|
||||||
* demostanis ([@demostanis](https://github.com/demostanis))
|
* demostanis ([@demostanis](https://github.com/demostanis))
|
||||||
|
* Giancarlo Razzolini (@[grazzolini](https://github.com/grazzolini))
|
||||||
|
* j-james ([@j-james](https://github.com/j-james))
|
||||||
* Jerker Bengtsson ([@jaybent](https://github.com/jaybent))
|
* Jerker Bengtsson ([@jaybent](https://github.com/jaybent))
|
||||||
|
* Ninchester ([@ninchester](https://github.com/ninchester))
|
||||||
|
* Philipp Schaffrath ([@phisch](https://github.com/phisch))
|
||||||
* Varun Madiath ([@vamega](https://github.com/vamega))
|
* Varun Madiath ([@vamega](https://github.com/vamega))
|
||||||
* nullrequest ([@advaithm](https://github.com/advaithm))
|
* nullrequest ([@advaithm](https://github.com/advaithm))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Contributor: Anton Hvornum anton@hvornum.se
|
# Contributor: Anton Hvornum anton@hvornum.se
|
||||||
|
|
||||||
pkgname="archinstall-bin"
|
pkgname="archinstall-bin"
|
||||||
pkgver="2.1.1"
|
pkgver="2.1.3"
|
||||||
pkgdesc="Installs a pre-built binary of ${pkgname}"
|
pkgdesc="Installs a pre-built binary of ${pkgname}"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
url="https://github.com/Torxed/archinstall"
|
url="https://github.com/Torxed/archinstall"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
||||||
|
|
||||||
pkgname="archinstall"
|
pkgname="archinstall"
|
||||||
pkgver="2.1.1"
|
pkgver="2.1.3"
|
||||||
pkgdesc="Installs launcher scripts for archinstall"
|
pkgdesc="Installs launcher scripts for archinstall"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
url="https://github.com/Torxed/archinstall"
|
url="https://github.com/Torxed/archinstall"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
||||||
|
|
||||||
pkgname="python-archinstall"
|
pkgname="python-archinstall"
|
||||||
pkgver="2.1.1"
|
pkgver="2.1.3"
|
||||||
pkgdesc="Installs ${pkgname} as a python library."
|
pkgdesc="Installs ${pkgname} as a python library."
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
url="https://github.com/Torxed/archinstall"
|
url="https://github.com/Torxed/archinstall"
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -6,7 +6,6 @@ The installer also doubles as a python library to install Arch Linux and manage
|
||||||
* archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel
|
* archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel
|
||||||
* archinstall [#archinstall@freenode (IRC)](irc://#archinstall@FreeNode)
|
* archinstall [#archinstall@freenode (IRC)](irc://#archinstall@FreeNode)
|
||||||
* archinstall [documentation](https://python-archinstall.readthedocs.io/en/latest/index.html)
|
* archinstall [documentation](https://python-archinstall.readthedocs.io/en/latest/index.html)
|
||||||
* archinstall ISO's: https://archlinux.life/
|
|
||||||
|
|
||||||
|
|
||||||
# Installation & Usage
|
# Installation & Usage
|
||||||
|
|
@ -14,7 +13,7 @@ The installer also doubles as a python library to install Arch Linux and manage
|
||||||
$ sudo pacman -S archinstall
|
$ sudo pacman -S archinstall
|
||||||
|
|
||||||
Or simply `git clone` the repo as it has no external dependencies *(but there are optional ones)*.<br>
|
Or simply `git clone` the repo as it has no external dependencies *(but there are optional ones)*.<br>
|
||||||
Or run the pre-compiled binary attached in every release as `archinstall-v[ver].tar.gz`.
|
Or use `pip install --upgrade archinstall` to use as a library.
|
||||||
|
|
||||||
## Running the [guided](examples/guided.py) installer
|
## Running the [guided](examples/guided.py) installer
|
||||||
|
|
||||||
|
|
@ -88,10 +87,5 @@ This will create a *5GB* `testimage.img` and create a loop device which we can u
|
||||||
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete,<br>
|
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete,<br>
|
||||||
~~you can use qemu/kvm to boot the test media.~~ *(You'd actually need to do some EFI magic in order to point the EFI vars to the partition 0 in the test medium so this won't work entirely out of the box, but gives you a general idea of what we're going for here)*
|
~~you can use qemu/kvm to boot the test media.~~ *(You'd actually need to do some EFI magic in order to point the EFI vars to the partition 0 in the test medium so this won't work entirely out of the box, but gives you a general idea of what we're going for here)*
|
||||||
|
|
||||||
You can also run a pre-built ISO with pip and python
|
There's also a [Building and Testing](https://github.com/Torxed/archinstall/wiki/Building-and-Testing) guide.<br>
|
||||||
|
It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch.
|
||||||
# qemu-system-x86_64 -enable-kvm -cdrom /home/user/Downloads/archinstall-2020.07.08-x86_64.iso -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd
|
|
||||||
|
|
||||||
and once inside, just do
|
|
||||||
|
|
||||||
# python -m archlinux guided
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
|
# This __init__ file is just here to support the
|
||||||
|
# use of archinstall as a git submodule.
|
||||||
from .archinstall import *
|
from .archinstall import *
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import glob, re, os, json, time, hashlib
|
import glob, re, os, json, time, hashlib
|
||||||
import pathlib
|
import pathlib, traceback
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from .exceptions import DiskError
|
from .exceptions import DiskError
|
||||||
from .general import *
|
from .general import *
|
||||||
|
|
@ -108,7 +108,7 @@ class BlockDevice():
|
||||||
if part_id not in self.part_cache:
|
if part_id not in self.part_cache:
|
||||||
## TODO: Force over-write even if in cache?
|
## TODO: Force over-write even if in cache?
|
||||||
if part_id not in self.part_cache or self.part_cache[part_id].size != part['size']:
|
if part_id not in self.part_cache or self.part_cache[part_id].size != part['size']:
|
||||||
self.part_cache[part_id] = Partition(root_path + part_id, part_id=part_id, size=part['size'])
|
self.part_cache[part_id] = Partition(root_path + part_id, self, part_id=part_id, size=part['size'])
|
||||||
|
|
||||||
return {k: self.part_cache[k] for k in sorted(self.part_cache)}
|
return {k: self.part_cache[k] for k in sorted(self.part_cache)}
|
||||||
|
|
||||||
|
|
@ -130,16 +130,22 @@ class BlockDevice():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def flush_cache(self):
|
||||||
|
self.part_cache = OrderedDict()
|
||||||
|
|
||||||
class Partition():
|
class Partition():
|
||||||
def __init__(self, path, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True):
|
def __init__(self, path :str, block_device :BlockDevice, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True):
|
||||||
if not part_id:
|
if not part_id:
|
||||||
part_id = os.path.basename(path)
|
part_id = os.path.basename(path)
|
||||||
|
|
||||||
|
self.block_device = block_device
|
||||||
self.path = path
|
self.path = path
|
||||||
self.part_id = part_id
|
self.part_id = part_id
|
||||||
self.mountpoint = mountpoint
|
self.mountpoint = mountpoint
|
||||||
self.target_mountpoint = mountpoint
|
self.target_mountpoint = mountpoint
|
||||||
self.filesystem = filesystem
|
self.filesystem = filesystem
|
||||||
self.size = size # TODO: Refresh?
|
self.size = size # TODO: Refresh?
|
||||||
|
self._encrypted = None
|
||||||
self.encrypted = encrypted
|
self.encrypted = encrypted
|
||||||
self.allow_formatting = False # A fail-safe for unconfigured partitions, such as windows NTFS partitions.
|
self.allow_formatting = False # A fail-safe for unconfigured partitions, such as windows NTFS partitions.
|
||||||
|
|
||||||
|
|
@ -175,28 +181,48 @@ class Partition():
|
||||||
elif self.target_mountpoint:
|
elif self.target_mountpoint:
|
||||||
mount_repr = f", rel_mountpoint={self.target_mountpoint}"
|
mount_repr = f", rel_mountpoint={self.target_mountpoint}"
|
||||||
|
|
||||||
if self.encrypted:
|
if self._encrypted:
|
||||||
return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
|
return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})'
|
||||||
else:
|
else:
|
||||||
return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})'
|
return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def encrypted(self):
|
||||||
|
return self._encrypted
|
||||||
|
|
||||||
|
@encrypted.setter
|
||||||
|
def encrypted(self, value :bool):
|
||||||
|
if value:
|
||||||
|
log(f'Marking {self} as encrypted: {value}', level=LOG_LEVELS.Debug)
|
||||||
|
log(f"Callstrack when marking the partition: {''.join(traceback.format_stack())}", level=LOG_LEVELS.Debug)
|
||||||
|
|
||||||
|
self._encrypted = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def real_device(self):
|
def real_device(self):
|
||||||
if not self.encrypted:
|
if not self._encrypted:
|
||||||
return self.path
|
return self.path
|
||||||
else:
|
else:
|
||||||
for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']:
|
for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']:
|
||||||
if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))):
|
if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))):
|
||||||
return f"/dev/{parent}"
|
return f"/dev/{parent}"
|
||||||
raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
|
# raise DiskError(f'Could not find appropriate parent for encrypted partition {self}')
|
||||||
|
return self.path
|
||||||
|
|
||||||
def detect_inner_filesystem(self, password):
|
def detect_inner_filesystem(self, password):
|
||||||
log(f'Trying to detect inner filesystem format on {self} (This might take a while)', level=LOG_LEVELS.Info)
|
log(f'Trying to detect inner filesystem format on {self} (This might take a while)', level=LOG_LEVELS.Info)
|
||||||
from .luks import luks2
|
from .luks import luks2
|
||||||
with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device:
|
|
||||||
return unlocked_device.filesystem
|
try:
|
||||||
|
with luks2(self, 'luksloop', password, auto_unmount=True) as unlocked_device:
|
||||||
|
return unlocked_device.filesystem
|
||||||
|
except SysCallError:
|
||||||
|
return None
|
||||||
|
|
||||||
def has_content(self):
|
def has_content(self):
|
||||||
|
if not get_filesystem_type(self.path):
|
||||||
|
return False
|
||||||
|
|
||||||
temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest()
|
temporary_mountpoint = '/tmp/'+hashlib.md5(bytes(f"{time.time()}", 'UTF-8')+os.urandom(12)).hexdigest()
|
||||||
temporary_path = pathlib.Path(temporary_mountpoint)
|
temporary_path = pathlib.Path(temporary_mountpoint)
|
||||||
|
|
||||||
|
|
@ -213,8 +239,10 @@ class Partition():
|
||||||
|
|
||||||
def safe_to_format(self):
|
def safe_to_format(self):
|
||||||
if self.allow_formatting is False:
|
if self.allow_formatting is False:
|
||||||
|
log(f"Partition {self} is not marked for formatting.", level=LOG_LEVELS.Debug)
|
||||||
return False
|
return False
|
||||||
elif self.target_mountpoint == '/boot' and self.has_content():
|
elif self.target_mountpoint == '/boot' and self.has_content():
|
||||||
|
log(f"Partition {self} is a boot partition and has content inside.", level=LOG_LEVELS.Debug)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
@ -225,10 +253,11 @@ class Partition():
|
||||||
"""
|
"""
|
||||||
from .luks import luks2
|
from .luks import luks2
|
||||||
|
|
||||||
if not self.encrypted:
|
if not self._encrypted:
|
||||||
raise DiskError(f"Attempting to encrypt a partition that was not marked for encryption: {self}")
|
raise DiskError(f"Attempting to encrypt a partition that was not marked for encryption: {self}")
|
||||||
|
|
||||||
if not self.safe_to_format():
|
if not self.safe_to_format():
|
||||||
|
log(f"Partition {self} was marked as protected but encrypt() was called on it!", level=LOG_LEVELS.Error, fg="red")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
handle = luks2(self, None, None)
|
handle = luks2(self, None, None)
|
||||||
|
|
@ -247,6 +276,11 @@ class Partition():
|
||||||
if allow_formatting is None:
|
if allow_formatting is None:
|
||||||
allow_formatting = self.allow_formatting
|
allow_formatting = self.allow_formatting
|
||||||
|
|
||||||
|
# To avoid "unable to open /dev/x: No such file or directory"
|
||||||
|
start_wait = time.time()
|
||||||
|
while pathlib.Path(path).exists() is False and time.time() - start_wait < 10:
|
||||||
|
time.sleep(0.025)
|
||||||
|
|
||||||
if not allow_formatting:
|
if not allow_formatting:
|
||||||
raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})")
|
raise PermissionError(f"{self} is not formatable either because instance is locked ({self.allow_formatting}) or a blocking flag was given ({allow_formatting})")
|
||||||
|
|
||||||
|
|
@ -288,6 +322,12 @@ class Partition():
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.")
|
raise UnknownFilesystemFormat(f"Fileformat '{filesystem}' is not yet implemented.")
|
||||||
|
|
||||||
|
if get_filesystem_type(path) == 'crypto_LUKS' or get_filesystem_type(self.real_device) == 'crypto_LUKS':
|
||||||
|
self.encrypted = True
|
||||||
|
else:
|
||||||
|
self.encrypted = False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def find_parent_of(self, data, name, parent=None):
|
def find_parent_of(self, data, name, parent=None):
|
||||||
|
|
@ -313,6 +353,24 @@ class Partition():
|
||||||
self.mountpoint = target
|
self.mountpoint = target
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def unmount(self):
|
||||||
|
try:
|
||||||
|
exit_code = sys_command(f'/usr/bin/umount {self.path}').exit_code
|
||||||
|
except SysCallError as err:
|
||||||
|
exit_code = err.exit_code
|
||||||
|
|
||||||
|
# Without to much research, it seams that low error codes are errors.
|
||||||
|
# And above 8k is indicators such as "/dev/x not mounted.".
|
||||||
|
# So anything in between 0 and 8k are errors (?).
|
||||||
|
if exit_code > 0 and exit_code < 8000:
|
||||||
|
raise err
|
||||||
|
|
||||||
|
self.mountpoint = None
|
||||||
|
return True
|
||||||
|
|
||||||
|
def umount(self):
|
||||||
|
return self.unmount()
|
||||||
|
|
||||||
def filesystem_supported(self):
|
def filesystem_supported(self):
|
||||||
"""
|
"""
|
||||||
The support for a filesystem (this partition) is tested by calling
|
The support for a filesystem (this partition) is tested by calling
|
||||||
|
|
@ -343,7 +401,8 @@ class Filesystem():
|
||||||
if self.blockdevice.keep_partitions is False:
|
if self.blockdevice.keep_partitions is False:
|
||||||
log(f'Wiping {self.blockdevice} by using partition format {self.mode}', level=LOG_LEVELS.Debug)
|
log(f'Wiping {self.blockdevice} by using partition format {self.mode}', level=LOG_LEVELS.Debug)
|
||||||
if self.mode == GPT:
|
if self.mode == GPT:
|
||||||
if sys_command(f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt',).exit_code == 0:
|
if self.raw_parted(f'{self.blockdevice.device} mklabel gpt').exit_code == 0:
|
||||||
|
self.blockdevice.flush_cache()
|
||||||
return self
|
return self
|
||||||
else:
|
else:
|
||||||
raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt')
|
raise DiskError(f'Problem setting the partition format to GPT:', f'/usr/bin/parted -s {self.blockdevice.device} mklabel gpt')
|
||||||
|
|
@ -380,7 +439,7 @@ class Filesystem():
|
||||||
|
|
||||||
def raw_parted(self, string:str):
|
def raw_parted(self, string:str):
|
||||||
x = sys_command(f'/usr/bin/parted -s {string}')
|
x = sys_command(f'/usr/bin/parted -s {string}')
|
||||||
o = b''.join(x)
|
log(f"'parted -s {string}' returned: {b''.join(x)}", level=LOG_LEVELS.Debug)
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def parted(self, string:str):
|
def parted(self, string:str):
|
||||||
|
|
@ -392,25 +451,33 @@ class Filesystem():
|
||||||
"""
|
"""
|
||||||
return self.raw_parted(string).exit_code
|
return self.raw_parted(string).exit_code
|
||||||
|
|
||||||
def use_entire_disk(self, root_filesystem_type='ext4', encrypt_root_partition=True):
|
def use_entire_disk(self, root_filesystem_type='ext4'):
|
||||||
self.add_partition('primary', start='1MiB', end='513MiB', format='vfat')
|
log(f"Using and formatting the entire {self.blockdevice}.", level=LOG_LEVELS.Debug)
|
||||||
#TODO: figure out what do for bios, we don't need a seprate partion for the bootloader
|
|
||||||
if hasUEFI():
|
if hasUEFI():
|
||||||
|
self.add_partition('primary', start='1MiB', end='513MiB', format='fat32')
|
||||||
self.set_name(0, 'EFI')
|
self.set_name(0, 'EFI')
|
||||||
self.set(0, 'boot on')
|
self.set(0, 'boot on')
|
||||||
# TODO: Probably redundant because in GPT mode 'esp on' is an alias for "boot on"?
|
# TODO: Probably redundant because in GPT mode 'esp on' is an alias for "boot on"?
|
||||||
# https://www.gnu.org/software/parted/manual/html_node/set.html
|
# https://www.gnu.org/software/parted/manual/html_node/set.html
|
||||||
self.set(0, 'esp on')
|
self.set(0, 'esp on')
|
||||||
self.add_partition('primary', start='513MiB', end='100%')
|
self.add_partition('primary', start='513MiB', end='100%')
|
||||||
|
|
||||||
self.blockdevice.partition[0].filesystem = 'vfat'
|
self.blockdevice.partition[0].filesystem = 'vfat'
|
||||||
self.blockdevice.partition[1].filesystem = root_filesystem_type
|
self.blockdevice.partition[1].filesystem = root_filesystem_type
|
||||||
|
log(f"Set the root partition {self.blockdevice.partition[1]} to use filesystem {root_filesystem_type}.", level=LOG_LEVELS.Debug)
|
||||||
|
|
||||||
self.blockdevice.partition[0].target_mountpoint = '/boot'
|
self.blockdevice.partition[0].target_mountpoint = '/boot'
|
||||||
self.blockdevice.partition[1].target_mountpoint = '/'
|
self.blockdevice.partition[1].target_mountpoint = '/'
|
||||||
|
|
||||||
if encrypt_root_partition:
|
self.blockdevice.partition[0].allow_formatting = True
|
||||||
self.blockdevice.partition[1].encrypted = True
|
self.blockdevice.partition[1].allow_formatting = True
|
||||||
|
else:
|
||||||
|
#we don't need a seprate boot partition it would be a waste of space
|
||||||
|
self.add_partition('primary', start='1MB', end='100%')
|
||||||
|
self.blockdevice.partition[0].filesystem=root_filesystem_type
|
||||||
|
log(f"Set the root partition {self.blockdevice.partition[0]} to use filesystem {root_filesystem_type}.", level=LOG_LEVELS.Debug)
|
||||||
|
self.blockdevice.partition[0].target_mountpoint = '/'
|
||||||
|
self.blockdevice.partition[0].allow_formatting = True
|
||||||
|
|
||||||
def add_partition(self, type, start, end, format=None):
|
def add_partition(self, type, start, end, format=None):
|
||||||
log(f'Adding partition to {self.blockdevice}', level=LOG_LEVELS.Info)
|
log(f'Adding partition to {self.blockdevice}', level=LOG_LEVELS.Info)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,10 @@ class UnknownFilesystemFormat(BaseException):
|
||||||
class ProfileError(BaseException):
|
class ProfileError(BaseException):
|
||||||
pass
|
pass
|
||||||
class SysCallError(BaseException):
|
class SysCallError(BaseException):
|
||||||
pass
|
def __init__(self, message, exit_code):
|
||||||
|
super(SysCallError, self).__init__(message)
|
||||||
|
self.message = message
|
||||||
|
self.exit_code = exit_code
|
||||||
class ProfileNotFound(BaseException):
|
class ProfileNotFound(BaseException):
|
||||||
pass
|
pass
|
||||||
class HardwareIncompatibilityError(BaseException):
|
class HardwareIncompatibilityError(BaseException):
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,13 @@ class sys_command():#Thread):
|
||||||
self.status = 'starting'
|
self.status = 'starting'
|
||||||
|
|
||||||
user_catalogue = os.path.expanduser('~')
|
user_catalogue = os.path.expanduser('~')
|
||||||
self.cwd = f"{user_catalogue}/.cache/archinstall/workers/{kwargs['worker_id']}/"
|
|
||||||
self.exec_dir = f'{self.cwd}/{os.path.basename(self.cmd[0])}_workingdir'
|
if (workdir := kwargs.get('workdir', None)):
|
||||||
|
self.cwd = workdir
|
||||||
|
self.exec_dir = workdir
|
||||||
|
else:
|
||||||
|
self.cwd = f"{user_catalogue}/.cache/archinstall/workers/{kwargs['worker_id']}/"
|
||||||
|
self.exec_dir = f'{self.cwd}/{os.path.basename(self.cmd[0])}_workingdir'
|
||||||
|
|
||||||
if not self.cmd[0][0] == '/':
|
if not self.cmd[0][0] == '/':
|
||||||
# "which" doesn't work as it's a builtin to bash.
|
# "which" doesn't work as it's a builtin to bash.
|
||||||
|
|
@ -251,7 +256,7 @@ class sys_command():#Thread):
|
||||||
if self.exit_code != 0 and not self.kwargs['suppress_errors']:
|
if self.exit_code != 0 and not self.kwargs['suppress_errors']:
|
||||||
#self.log(self.trace_log.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
#self.log(self.trace_log.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
||||||
#self.log(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.", level=LOG_LEVELS.Error)
|
#self.log(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.", level=LOG_LEVELS.Error)
|
||||||
raise SysCallError(f"{self.trace_log.decode('UTF-8')}\n'{self.raw_cmd}' did not exit gracefully (trace log above), exit code: {self.exit_code}")
|
raise SysCallError(message=f"{self.trace_log.decode('UTF-8')}\n'{self.raw_cmd}' did not exit gracefully (trace log above), exit code: {self.exit_code}", exit_code=self.exit_code)
|
||||||
|
|
||||||
self.ended = time.time()
|
self.ended = time.time()
|
||||||
with open(f'{self.cwd}/trace.log', 'wb') as fh:
|
with open(f'{self.cwd}/trace.log', 'wb') as fh:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import os, stat, time, shutil, subprocess
|
import os, stat, time, shutil, pathlib
|
||||||
|
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .disk import *
|
from .disk import *
|
||||||
|
|
@ -9,7 +9,6 @@ from .mirrors import *
|
||||||
from .systemd import Networkd
|
from .systemd import Networkd
|
||||||
from .output import log, LOG_LEVELS
|
from .output import log, LOG_LEVELS
|
||||||
from .storage import storage
|
from .storage import storage
|
||||||
from .hardware import *
|
|
||||||
|
|
||||||
class Installer():
|
class Installer():
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,8 +53,6 @@ class Installer():
|
||||||
}
|
}
|
||||||
|
|
||||||
self.base_packages = base_packages.split(' ')
|
self.base_packages = base_packages.split(' ')
|
||||||
if not hasUEFI():
|
|
||||||
base_packages.append('grub') # if it isn't uefi is must be bios therefore we need grub as systemd-boot is uefi only
|
|
||||||
self.post_base_install = []
|
self.post_base_install = []
|
||||||
storage['session'] = self
|
storage['session'] = self
|
||||||
|
|
||||||
|
|
@ -172,10 +169,19 @@ class Installer():
|
||||||
return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False
|
return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False
|
||||||
|
|
||||||
def set_timezone(self, zone, *args, **kwargs):
|
def set_timezone(self, zone, *args, **kwargs):
|
||||||
if not len(zone): return True
|
if not zone: return True
|
||||||
|
if not len(zone): return True # Redundant
|
||||||
|
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime'))
|
if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
|
||||||
return True
|
(pathlib.Path(self.mountpoint)/"etc"/"localtime").unlink(missing_ok=True)
|
||||||
|
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.log(
|
||||||
|
f"Time zone {zone} does not exist, continuing with system default.",
|
||||||
|
level=LOG_LEVELS.Warning,
|
||||||
|
fg='red'
|
||||||
|
)
|
||||||
|
|
||||||
def activate_ntp(self):
|
def activate_ntp(self):
|
||||||
self.log(f'Installing and activating NTP.', level=LOG_LEVELS.Info)
|
self.log(f'Installing and activating NTP.', level=LOG_LEVELS.Info)
|
||||||
|
|
@ -292,13 +298,22 @@ class Installer():
|
||||||
# TODO: Use python functions for this
|
# TODO: Use python functions for this
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root')
|
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root')
|
||||||
|
|
||||||
|
# Configure mkinitcpio to handle some specific use cases.
|
||||||
|
# TODO: Yes, we should not overwrite the entire thing, but for now this should be fine
|
||||||
|
# since we just installed the base system.
|
||||||
if self.partition.filesystem == 'btrfs':
|
if self.partition.filesystem == 'btrfs':
|
||||||
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||||
## TODO: Don't replace it, in case some update in the future actually adds something.
|
|
||||||
mkinit.write('MODULES=(btrfs)\n')
|
mkinit.write('MODULES=(btrfs)\n')
|
||||||
mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
|
mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
|
||||||
mkinit.write('FILES=()\n')
|
mkinit.write('FILES=()\n')
|
||||||
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)\n')
|
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n')
|
||||||
|
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
||||||
|
elif self.partition.encrypted:
|
||||||
|
with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||||
|
mkinit.write('MODULES=()\n')
|
||||||
|
mkinit.write('BINARIES=()\n')
|
||||||
|
mkinit.write('FILES=()\n')
|
||||||
|
mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keymap keyboard fsck)\n')
|
||||||
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux')
|
||||||
|
|
||||||
self.helper_flags['base'] = True
|
self.helper_flags['base'] = True
|
||||||
|
|
@ -314,62 +329,75 @@ class Installer():
|
||||||
self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info)
|
self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info)
|
||||||
|
|
||||||
if bootloader == 'systemd-bootctl':
|
if bootloader == 'systemd-bootctl':
|
||||||
if hasUEFI():
|
# TODO: Ideally we would want to check if another config
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install'))
|
# points towards the same disk and/or partition.
|
||||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
|
# And in which case we should do some clean up.
|
||||||
loader.write('default arch\n')
|
|
||||||
loader.write('timeout 5\n')
|
|
||||||
|
|
||||||
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
# Install the boot loader
|
||||||
## And blkid is wrong in terms of LUKS.
|
sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install')
|
||||||
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
|
||||||
with open(f'{self.mountpoint}/boot/loader/entries/arch.conf', 'w') as entry:
|
|
||||||
entry.write('title Arch Linux\n')
|
|
||||||
entry.write('linux /vmlinuz-linux\n')
|
|
||||||
entry.write('initrd /initramfs-linux.img\n')
|
|
||||||
## blkid doesn't trigger on loopback devices really well,
|
|
||||||
## so we'll use the old manual method until we get that sorted out.
|
|
||||||
|
|
||||||
|
# Modify or create a loader.conf
|
||||||
|
if os.path.isfile(f'{self.mountpoint}/boot/loader/loader.conf'):
|
||||||
|
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'r') as loader:
|
||||||
|
loader_data = loader.read().split('\n')
|
||||||
|
else:
|
||||||
|
loader_data = [
|
||||||
|
f"default {self.init_time}",
|
||||||
|
f"timeout 5"
|
||||||
|
]
|
||||||
|
|
||||||
if self.partition.encrypted:
|
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
|
||||||
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
for line in loader_data:
|
||||||
for uid in uids:
|
if line[:8] == 'default ':
|
||||||
real_path = os.path.realpath(os.path.join(root, uid))
|
loader.write(f'default {self.init_time}\n')
|
||||||
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
|
|
||||||
if hasAMDCPU(): # intel_paste is intel only, it's redudant on AMD systens
|
|
||||||
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw\n')
|
|
||||||
else:
|
|
||||||
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
|
||||||
|
|
||||||
self.helper_flags['bootloader'] = bootloader
|
|
||||||
return True
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
for root, folders, uids in os.walk('/dev/disk/by-partuuid'):
|
loader.write(f"{line}")
|
||||||
for uid in uids:
|
|
||||||
real_path = os.path.realpath(os.path.join(root, uid))
|
|
||||||
if not os.path.basename(real_path) == os.path.basename(self.partition.path): continue
|
|
||||||
if hasAMDCPU():
|
|
||||||
entry.write(f'options root=PARTUUID={uid} rw\n')
|
|
||||||
else:
|
|
||||||
entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
|
|
||||||
|
|
||||||
self.helper_flags['bootloader'] = bootloader
|
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
||||||
return True
|
## And blkid is wrong in terms of LUKS.
|
||||||
break
|
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
||||||
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
|
|
||||||
else:
|
# Setup the loader entry
|
||||||
raise RequirementError("Systemd-boot is UEFI only it can not be installed or used on bios")
|
with open(f'{self.mountpoint}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
|
||||||
elif bootloader == 'grub-install':
|
entry.write(f'# Created by: archinstall\n')
|
||||||
if hasUEFI():
|
entry.write(f'# Created on: {self.init_time}\n')
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB'))
|
entry.write(f'title Arch Linux\n')
|
||||||
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
entry.write(f'linux /vmlinuz-linux\n')
|
||||||
else:
|
entry.write(f'initrd /initramfs-linux.img\n')
|
||||||
root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{self.partition.path.strip("/dev/")}/..")',shell=True).decode().strip()
|
## blkid doesn't trigger on loopback devices really well,
|
||||||
if root_device == "block":
|
## so we'll use the old manual method until we get that sorted out.
|
||||||
root_device = f"{self.partition.path}"
|
|
||||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=--target=i386-pc /dev/{root_device}'))
|
|
||||||
sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg')
|
if self.partition.encrypted:
|
||||||
|
log(f"Identifying root partition by DISK-UUID on {self.partition}, looking for '{os.path.basename(self.partition.real_device)}'.", level=LOG_LEVELS.Debug)
|
||||||
|
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
||||||
|
for uid in uids:
|
||||||
|
real_path = os.path.realpath(os.path.join(root, uid))
|
||||||
|
|
||||||
|
log(f"Checking root partition match {os.path.basename(real_path)} against {os.path.basename(self.partition.real_device)}: {os.path.basename(real_path) == os.path.basename(self.partition.real_device)}", level=LOG_LEVELS.Debug)
|
||||||
|
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
|
||||||
|
|
||||||
|
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||||
|
|
||||||
|
self.helper_flags['bootloader'] = bootloader
|
||||||
|
return True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log(f"Identifying root partition by PART-UUID on {self.partition}, looking for '{os.path.basename(self.partition.path)}'.", level=LOG_LEVELS.Debug)
|
||||||
|
for root, folders, uids in os.walk('/dev/disk/by-partuuid'):
|
||||||
|
for uid in uids:
|
||||||
|
real_path = os.path.realpath(os.path.join(root, uid))
|
||||||
|
|
||||||
|
log(f"Checking root partition match {os.path.basename(real_path)} against {os.path.basename(self.partition.path)}: {os.path.basename(real_path) == os.path.basename(self.partition.path)}", level=LOG_LEVELS.Debug)
|
||||||
|
if not os.path.basename(real_path) == os.path.basename(self.partition.path): continue
|
||||||
|
|
||||||
|
entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
|
||||||
|
|
||||||
|
self.helper_flags['bootloader'] = bootloader
|
||||||
|
return True
|
||||||
|
break
|
||||||
|
|
||||||
|
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
|
||||||
else:
|
else:
|
||||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
||||||
|
|
||||||
|
|
@ -377,7 +405,17 @@ class Installer():
|
||||||
return self.pacstrap(*packages)
|
return self.pacstrap(*packages)
|
||||||
|
|
||||||
def install_profile(self, profile):
|
def install_profile(self, profile):
|
||||||
profile = Profile(self, profile)
|
# TODO: Replace this with a import archinstall.session instead in the profiles.
|
||||||
|
# The tricky thing with doing the import archinstall.session instead is that
|
||||||
|
# profiles might be run from a different chroot, and there's no way we can
|
||||||
|
# guarantee file-path safety when accessing the installer object that way.
|
||||||
|
# Doing the __builtins__ replacement, ensures that the global vriable "installation"
|
||||||
|
# is always kept up to date. It's considered a nasty hack - but it's a safe way
|
||||||
|
# of ensuring 100% accuracy of archinstall session variables.
|
||||||
|
__builtins__['installation'] = self
|
||||||
|
|
||||||
|
if type(profile) == str:
|
||||||
|
profile = Profile(self, profile)
|
||||||
|
|
||||||
self.log(f'Installing network profile {profile}', level=LOG_LEVELS.Info)
|
self.log(f'Installing network profile {profile}', level=LOG_LEVELS.Info)
|
||||||
return profile.install()
|
return profile.install()
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,37 @@ class luks2():
|
||||||
with open(key_file, 'wb') as fh:
|
with open(key_file, 'wb') as fh:
|
||||||
fh.write(password)
|
fh.write(password)
|
||||||
|
|
||||||
o = b''.join(sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash {hash_type} --key-size {key_size} --iter-time {iter_time} --key-file {os.path.abspath(key_file)} --use-urandom luksFormat {partition.path}'))
|
try:
|
||||||
if b'Command successful.' not in o:
|
# Try to setup the crypt-device
|
||||||
|
cmd_handle = sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash {hash_type} --key-size {key_size} --iter-time {iter_time} --key-file {os.path.abspath(key_file)} --use-urandom luksFormat {partition.path}')
|
||||||
|
except SysCallError as err:
|
||||||
|
if err.exit_code == 256:
|
||||||
|
log(f'{partition} is being used, trying to unmount and crypt-close the device and running one more attempt at encrypting the device.', level=LOG_LEVELS.Debug)
|
||||||
|
# Partition was in use, unmount it and try again
|
||||||
|
partition.unmount()
|
||||||
|
|
||||||
|
# Get crypt-information about the device by doing a reverse lookup starting with the partition path
|
||||||
|
# For instance: /dev/sda
|
||||||
|
devinfo = json.loads(b''.join(sys_command(f"lsblk --fs -J {partition.path}")).decode('UTF-8'))['blockdevices'][0]
|
||||||
|
|
||||||
|
# For each child (sub-partition/sub-device)
|
||||||
|
if len(children := devinfo.get('children', [])):
|
||||||
|
for child in children:
|
||||||
|
# Unmount the child location
|
||||||
|
if child_mountpoint := child.get('mountpoint', None):
|
||||||
|
log(f'Unmounting {child_mountpoint}', level=LOG_LEVELS.Debug)
|
||||||
|
sys_command(f"umount -R {child_mountpoint}")
|
||||||
|
|
||||||
|
# And close it if possible.
|
||||||
|
log(f"Closing crypt device {child['name']}", level=LOG_LEVELS.Debug)
|
||||||
|
sys_command(f"cryptsetup close {child['name']}")
|
||||||
|
|
||||||
|
# Then try again to set up the crypt-device
|
||||||
|
cmd_handle = sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash {hash_type} --key-size {key_size} --iter-time {iter_time} --key-file {os.path.abspath(key_file)} --use-urandom luksFormat {partition.path}')
|
||||||
|
else:
|
||||||
|
raise err
|
||||||
|
|
||||||
|
if b'Command successful.' not in b''.join(cmd_handle):
|
||||||
raise DiskError(f'Could not encrypt volume "{partition.path}": {o}')
|
raise DiskError(f'Could not encrypt volume "{partition.path}": {o}')
|
||||||
|
|
||||||
return key_file
|
return key_file
|
||||||
|
|
@ -84,7 +113,7 @@ class luks2():
|
||||||
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
|
sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2')
|
||||||
if os.path.islink(f'/dev/mapper/{mountpoint}'):
|
if os.path.islink(f'/dev/mapper/{mountpoint}'):
|
||||||
self.mapdev = f'/dev/mapper/{mountpoint}'
|
self.mapdev = f'/dev/mapper/{mountpoint}'
|
||||||
unlocked_partition = Partition(self.mapdev, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False)
|
unlocked_partition = Partition(self.mapdev, None, encrypted=True, filesystem=get_filesystem_type(self.mapdev), autodetect_filesystem=False)
|
||||||
unlocked_partition.allow_formatting = self.partition.allow_formatting
|
unlocked_partition.allow_formatting = self.partition.allow_formatting
|
||||||
return unlocked_partition
|
return unlocked_partition
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,17 @@ def log(*args, **kwargs):
|
||||||
if (filename := storage.get('LOG_FILE', None)):
|
if (filename := storage.get('LOG_FILE', None)):
|
||||||
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
|
||||||
if not os.path.isfile(absolute_logfile):
|
if not os.path.isfile(absolute_logfile):
|
||||||
os.makedirs(os.path.dirname(absolute_logfile))
|
try:
|
||||||
|
Path(absolute_logfile).parents[0].mkdir(exist_ok=True, parents=True)
|
||||||
|
except PermissionError:
|
||||||
|
# Fallback to creating the log file in the current folder
|
||||||
|
err_string = f"Not enough permission to place log file at {absolute_logfile}, creating it in {Path('./').absolute()/filename} instead."
|
||||||
|
absolute_logfile = Path('./').absolute()/filename
|
||||||
|
absolute_logfile.parents[0].mkdir(exist_ok=True)
|
||||||
|
absolute_logfile = str(absolute_logfile)
|
||||||
|
storage['LOG_PATH'] = './'
|
||||||
|
log(err_string, fg="red")
|
||||||
|
|
||||||
Path(absolute_logfile).touch() # Overkill?
|
Path(absolute_logfile).touch() # Overkill?
|
||||||
|
|
||||||
with open(absolute_logfile, 'a') as log_file:
|
with open(absolute_logfile, 'a') as log_file:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os, urllib.request, urllib.parse, ssl, json, re
|
import os, urllib.request, urllib.parse, ssl, json, re
|
||||||
import importlib.util, sys, glob, hashlib
|
import importlib.util, sys, glob, hashlib
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from .general import multisplit, sys_command, log
|
from .general import multisplit, sys_command
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .networking import *
|
from .networking import *
|
||||||
from .output import log, LOG_LEVELS
|
from .output import log, LOG_LEVELS
|
||||||
|
|
@ -76,6 +76,8 @@ class Script():
|
||||||
self.spec = None
|
self.spec = None
|
||||||
self.examples = None
|
self.examples = None
|
||||||
self.namespace = os.path.splitext(os.path.basename(self.path))[0]
|
self.namespace = os.path.splitext(os.path.basename(self.path))[0]
|
||||||
|
self.original_namespace = self.namespace
|
||||||
|
log(f"Script {self} has been loaded with namespace '{self.namespace}'", level=LOG_LEVELS.Debug)
|
||||||
|
|
||||||
def __enter__(self, *args, **kwargs):
|
def __enter__(self, *args, **kwargs):
|
||||||
self.execute()
|
self.execute()
|
||||||
|
|
@ -138,7 +140,6 @@ class Script():
|
||||||
if not self.namespace in sys.modules or self.spec is None:
|
if not self.namespace in sys.modules or self.spec is None:
|
||||||
self.load_instructions()
|
self.load_instructions()
|
||||||
|
|
||||||
__builtins__['installation'] = self.installer # TODO: Replace this with a import archinstall.session instead
|
|
||||||
self.spec.loader.exec_module(sys.modules[self.namespace])
|
self.spec.loader.exec_module(sys.modules[self.namespace])
|
||||||
|
|
||||||
return sys.modules[self.namespace]
|
return sys.modules[self.namespace]
|
||||||
|
|
@ -146,7 +147,6 @@ class Script():
|
||||||
class Profile(Script):
|
class Profile(Script):
|
||||||
def __init__(self, installer, path, args={}):
|
def __init__(self, installer, path, args={}):
|
||||||
super(Profile, self).__init__(path, installer)
|
super(Profile, self).__init__(path, installer)
|
||||||
self._cache = None
|
|
||||||
|
|
||||||
def __dump__(self, *args, **kwargs):
|
def __dump__(self, *args, **kwargs):
|
||||||
return {'path' : self.path}
|
return {'path' : self.path}
|
||||||
|
|
@ -155,6 +155,10 @@ class Profile(Script):
|
||||||
return f'Profile({os.path.basename(self.profile)})'
|
return f'Profile({os.path.basename(self.profile)})'
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
|
# Before installing, revert any temporary changes to the namespace.
|
||||||
|
# This ensures that the namespace during installation is the original initation namespace.
|
||||||
|
# (For instance awesome instead of aweosme.py or app-awesome.py)
|
||||||
|
self.namespace = self.original_namespace
|
||||||
return self.execute()
|
return self.execute()
|
||||||
|
|
||||||
def has_prep_function(self):
|
def has_prep_function(self):
|
||||||
|
|
@ -203,3 +207,10 @@ class Application(Profile):
|
||||||
return self.localize_path(self.profile)
|
return self.localize_path(self.profile)
|
||||||
else:
|
else:
|
||||||
raise ProfileNotFound(f"Application cannot handle scheme {parsed_url.scheme}")
|
raise ProfileNotFound(f"Application cannot handle scheme {parsed_url.scheme}")
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
# Before installing, revert any temporary changes to the namespace.
|
||||||
|
# This ensures that the namespace during installation is the original initation namespace.
|
||||||
|
# (For instance awesome instead of aweosme.py or app-awesome.py)
|
||||||
|
self.namespace = self.original_namespace
|
||||||
|
return self.execute()
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import getpass
|
import getpass, pathlib, os, shutil
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .profiles import Profile
|
from .profiles import Profile
|
||||||
from .locale_helpers import search_keyboard_layout
|
from .locale_helpers import search_keyboard_layout
|
||||||
|
|
@ -9,15 +9,44 @@ from .networking import list_interfaces
|
||||||
## TODO: Some inconsistencies between the selection processes.
|
## TODO: Some inconsistencies between the selection processes.
|
||||||
## Some return the keys from the options, some the values?
|
## Some return the keys from the options, some the values?
|
||||||
|
|
||||||
|
def get_terminal_height():
|
||||||
|
return shutil.get_terminal_size().lines
|
||||||
|
|
||||||
|
def get_terminal_width():
|
||||||
|
return shutil.get_terminal_size().columns
|
||||||
|
|
||||||
|
def get_longest_option(options):
|
||||||
|
return max([len(x) for x in options])
|
||||||
|
|
||||||
def get_password(prompt="Enter a password: "):
|
def get_password(prompt="Enter a password: "):
|
||||||
while (passwd := getpass.getpass(prompt)):
|
while (passwd := getpass.getpass(prompt)):
|
||||||
passwd_verification = getpass.getpass(prompt='And one more time for verification: ')
|
passwd_verification = getpass.getpass(prompt='And one more time for verification: ')
|
||||||
if passwd != passwd_verification:
|
if passwd != passwd_verification:
|
||||||
log(' * Passwords did not match * ', bg='black', fg='red')
|
log(' * Passwords did not match * ', bg='black', fg='red')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if len(passwd.strip()) <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
return passwd
|
return passwd
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def print_large_list(options, padding=5, margin_bottom=0, separator=': '):
|
||||||
|
highest_index_number_length = len(str(len(options)))
|
||||||
|
longest_line = highest_index_number_length + len(separator) + get_longest_option(options) + padding
|
||||||
|
max_num_of_columns = get_terminal_width() // longest_line
|
||||||
|
max_options_in_cells = max_num_of_columns * (get_terminal_height()-margin_bottom)
|
||||||
|
|
||||||
|
if (len(options) > max_options_in_cells):
|
||||||
|
for index, option in enumerate(options):
|
||||||
|
print(f"{index}: {option}")
|
||||||
|
else:
|
||||||
|
for row in range(0, (get_terminal_height()-margin_bottom)):
|
||||||
|
for column in range(row, len(options), (get_terminal_height()-margin_bottom)):
|
||||||
|
spaces = " "*(longest_line - len(options[column]))
|
||||||
|
print(f"{str(column): >{highest_index_number_length}}{separator}{options[column]}", end = spaces)
|
||||||
|
print()
|
||||||
|
|
||||||
def ask_for_superuser_account(prompt='Create a required super-user with sudo privileges: ', forced=False):
|
def ask_for_superuser_account(prompt='Create a required super-user with sudo privileges: ', forced=False):
|
||||||
while 1:
|
while 1:
|
||||||
new_user = input(prompt).strip(' ')
|
new_user = input(prompt).strip(' ')
|
||||||
|
|
@ -31,7 +60,7 @@ def ask_for_superuser_account(prompt='Create a required super-user with sudo pri
|
||||||
raise UserError("No superuser was created.")
|
raise UserError("No superuser was created.")
|
||||||
|
|
||||||
password = get_password(prompt=f'Password for user {new_user}: ')
|
password = get_password(prompt=f'Password for user {new_user}: ')
|
||||||
return {new_user: password}
|
return {new_user: {"!password" : password}}
|
||||||
|
|
||||||
def ask_for_additional_users(prompt='Any additional users to install (leave blank for no users): '):
|
def ask_for_additional_users(prompt='Any additional users to install (leave blank for no users): '):
|
||||||
users = {}
|
users = {}
|
||||||
|
|
@ -44,12 +73,23 @@ def ask_for_additional_users(prompt='Any additional users to install (leave blan
|
||||||
password = get_password(prompt=f'Password for user {new_user}: ')
|
password = get_password(prompt=f'Password for user {new_user}: ')
|
||||||
|
|
||||||
if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'):
|
if input("Should this user be a sudo (super) user (y/N): ").strip(' ').lower() in ('y', 'yes'):
|
||||||
super_users[new_user] = password
|
super_users[new_user] = {"!password" : password}
|
||||||
else:
|
else:
|
||||||
users[new_user] = password
|
users[new_user] = {"!password" : password}
|
||||||
|
|
||||||
return users, super_users
|
return users, super_users
|
||||||
|
|
||||||
|
def ask_for_a_timezone():
|
||||||
|
timezone = input('Enter a valid timezone (Example: Europe/Stockholm): ').strip()
|
||||||
|
if (pathlib.Path("/usr")/"share"/"zoneinfo"/timezone).exists():
|
||||||
|
return timezone
|
||||||
|
else:
|
||||||
|
log(
|
||||||
|
f"Time zone {timezone} does not exist, continuing with system default.",
|
||||||
|
level=LOG_LEVELS.Warning,
|
||||||
|
fg='red'
|
||||||
|
)
|
||||||
|
|
||||||
def ask_to_configure_network():
|
def ask_to_configure_network():
|
||||||
# Optionally configure one network interface.
|
# Optionally configure one network interface.
|
||||||
#while 1:
|
#while 1:
|
||||||
|
|
@ -102,11 +142,10 @@ def ask_for_main_filesystem_format():
|
||||||
'btrfs' : 'btrfs',
|
'btrfs' : 'btrfs',
|
||||||
'ext4' : 'ext4',
|
'ext4' : 'ext4',
|
||||||
'xfs' : 'xfs',
|
'xfs' : 'xfs',
|
||||||
'f2fs' : 'f2fs',
|
'f2fs' : 'f2fs'
|
||||||
'vfat' : 'vfat'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value = generic_select(options.values(), "Select your main partitions filesystem by number or free-text: ")
|
value = generic_select(options.values(), "Select which filesystem your main partition should use (by number or name): ")
|
||||||
return next((key for key, val in options.items() if val == value), None)
|
return next((key for key, val in options.items() if val == value), None)
|
||||||
|
|
||||||
def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True):
|
def generic_select(options, input_text="Select one of the above by index or absolute value: ", sort=True):
|
||||||
|
|
@ -131,7 +170,10 @@ def generic_select(options, input_text="Select one of the above by index or abso
|
||||||
if len(selected_option.strip()) <= 0:
|
if len(selected_option.strip()) <= 0:
|
||||||
return None
|
return None
|
||||||
elif selected_option.isdigit():
|
elif selected_option.isdigit():
|
||||||
selected_option = options[int(selected_option)]
|
selected_option = int(selected_option)
|
||||||
|
if selected_option >= len(options):
|
||||||
|
raise RequirementError(f'Selected option "{selected_option}" is out of range')
|
||||||
|
selected_option = options[selected_option]
|
||||||
elif selected_option in options:
|
elif selected_option in options:
|
||||||
pass # We gave a correct absolute value
|
pass # We gave a correct absolute value
|
||||||
else:
|
else:
|
||||||
|
|
@ -156,7 +198,10 @@ def select_disk(dict_o_disks):
|
||||||
print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})")
|
print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})")
|
||||||
drive = input('Select one of the above disks (by number or full path): ')
|
drive = input('Select one of the above disks (by number or full path): ')
|
||||||
if drive.isdigit():
|
if drive.isdigit():
|
||||||
drive = dict_o_disks[drives[int(drive)]]
|
drive = int(drive)
|
||||||
|
if drive >= len(drives):
|
||||||
|
raise DiskError(f'Selected option "{drive}" is out of range')
|
||||||
|
drive = dict_o_disks[drives[drive]]
|
||||||
elif drive in dict_o_disks:
|
elif drive in dict_o_disks:
|
||||||
drive = dict_o_disks[drive]
|
drive = dict_o_disks[drive]
|
||||||
else:
|
else:
|
||||||
|
|
@ -182,10 +227,10 @@ def select_profile(options):
|
||||||
for index, profile in enumerate(profiles):
|
for index, profile in enumerate(profiles):
|
||||||
print(f"{index}: {profile}")
|
print(f"{index}: {profile}")
|
||||||
|
|
||||||
print(' -- The above list is pre-programmed profiles. --')
|
print(' -- The above list is a set of pre-programmed profiles. --')
|
||||||
print(' -- They might make it easier to install things like desktop environments. --')
|
print(' -- They might make it easier to install things like desktop environments. --')
|
||||||
print(' -- (Leave blank to skip this next optional step) --')
|
print(' -- (Leave blank and hit enter to skip this step and continue) --')
|
||||||
selected_profile = input('Any particular pre-programmed profile you want to install: ')
|
selected_profile = input('Enter a pre-programmed profile name if you want to install one: ')
|
||||||
|
|
||||||
if len(selected_profile.strip()) <= 0:
|
if len(selected_profile.strip()) <= 0:
|
||||||
return None
|
return None
|
||||||
|
|
@ -265,24 +310,13 @@ def select_mirror_regions(mirrors, show_top_mirrors=True):
|
||||||
selected_mirrors = {}
|
selected_mirrors = {}
|
||||||
|
|
||||||
if len(regions) >= 1:
|
if len(regions) >= 1:
|
||||||
for index, region in enumerate(regions):
|
print_large_list(regions, margin_bottom=2)
|
||||||
print(f"{index}: {region}")
|
|
||||||
|
|
||||||
print(' -- You can enter ? or help to search for more regions --')
|
|
||||||
print(' -- You can skip this step by leaving the option blank --')
|
print(' -- You can skip this step by leaving the option blank --')
|
||||||
print(' -- (You can use Shift + PageUp to scroll in the list --')
|
|
||||||
selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ')
|
selected_mirror = input('Select one of the above regions to download packages from (by number or full name): ')
|
||||||
if len(selected_mirror.strip()) == 0:
|
if len(selected_mirror.strip()) == 0:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
elif selected_mirror.lower() in ('?', 'help'):
|
|
||||||
filter_string = input('Search for a region containing (example: "united"): ').strip().lower()
|
|
||||||
for region in mirrors:
|
|
||||||
if filter_string in region.lower():
|
|
||||||
selected_mirrors[region] = mirrors[region]
|
|
||||||
|
|
||||||
return selected_mirrors
|
|
||||||
|
|
||||||
elif selected_mirror.isdigit() and (pos := int(selected_mirror)) <= len(regions)-1:
|
elif selected_mirror.isdigit() and (pos := int(selected_mirror)) <= len(regions)-1:
|
||||||
region = regions[int(selected_mirror)]
|
region = regions[int(selected_mirror)]
|
||||||
selected_mirrors[region] = mirrors[region]
|
selected_mirrors[region] = mirrors[region]
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ def ask_user_questions():
|
||||||
# 3. Check that we support the current partitions
|
# 3. Check that we support the current partitions
|
||||||
# 2. If so, ask if we should keep them or wipe everything
|
# 2. If so, ask if we should keep them or wipe everything
|
||||||
if archinstall.arguments['harddrive'].has_partitions():
|
if archinstall.arguments['harddrive'].has_partitions():
|
||||||
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='red')
|
archinstall.log(f"{archinstall.arguments['harddrive']} contains the following partitions:", fg='yellow')
|
||||||
|
|
||||||
# We curate a list pf supported paritions
|
# We curate a list pf supported paritions
|
||||||
# and print those that we don't support.
|
# and print those that we don't support.
|
||||||
|
|
@ -91,7 +91,11 @@ def ask_user_questions():
|
||||||
new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ')
|
new_filesystem = input(f"Enter a valid filesystem for {partition} (leave blank for {partition.filesystem}): ").strip(' ')
|
||||||
if len(new_filesystem) <= 0:
|
if len(new_filesystem) <= 0:
|
||||||
if partition.encrypted and partition.filesystem == 'crypto_LUKS':
|
if partition.encrypted and partition.filesystem == 'crypto_LUKS':
|
||||||
if (autodetected_filesystem := partition.detect_inner_filesystem(archinstall.arguments.get('!encryption-password', None))):
|
old_password = archinstall.arguments.get('!encryption-password', None)
|
||||||
|
if not old_password:
|
||||||
|
old_password = input(f'Enter the old encryption password for {partition}: ')
|
||||||
|
|
||||||
|
if (autodetected_filesystem := partition.detect_inner_filesystem(old_password)):
|
||||||
new_filesystem = autodetected_filesystem
|
new_filesystem = autodetected_filesystem
|
||||||
else:
|
else:
|
||||||
archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red')
|
archinstall.log(f"Could not auto-detect the filesystem inside the encrypted volume.", fg='red')
|
||||||
|
|
@ -127,11 +131,17 @@ def ask_user_questions():
|
||||||
elif option == 'format-all':
|
elif option == 'format-all':
|
||||||
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||||
archinstall.arguments['harddrive'].keep_partitions = False
|
archinstall.arguments['harddrive'].keep_partitions = False
|
||||||
|
else:
|
||||||
|
# If the drive doesn't have any partitions, safely mark the disk with keep_partitions = False
|
||||||
|
# and ask the user for a root filesystem.
|
||||||
|
archinstall.arguments['filesystem'] = archinstall.ask_for_main_filesystem_format()
|
||||||
|
archinstall.arguments['harddrive'].keep_partitions = False
|
||||||
|
|
||||||
# Get disk encryption password (or skip if blank)
|
# Get disk encryption password (or skip if blank)
|
||||||
if not archinstall.arguments.get('!encryption-password', None):
|
if not archinstall.arguments.get('!encryption-password', None):
|
||||||
archinstall.arguments['!encryption-password'] = archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')
|
if (passwd := archinstall.get_password(prompt='Enter disk encryption password (leave blank for no encryption): ')):
|
||||||
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
archinstall.arguments['!encryption-password'] = passwd
|
||||||
|
archinstall.arguments['harddrive'].encryption_password = archinstall.arguments['!encryption-password']
|
||||||
|
|
||||||
# Get the hostname for the machine
|
# Get the hostname for the machine
|
||||||
if not archinstall.arguments.get('hostname', None):
|
if not archinstall.arguments.get('hostname', None):
|
||||||
|
|
@ -139,7 +149,7 @@ def ask_user_questions():
|
||||||
|
|
||||||
# Ask for a root password (optional, but triggers requirement for super-user if skipped)
|
# Ask for a root password (optional, but triggers requirement for super-user if skipped)
|
||||||
if not archinstall.arguments.get('!root-password', None):
|
if not archinstall.arguments.get('!root-password', None):
|
||||||
archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommended: leave blank to leave root disabled): ')
|
archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommendation: leave blank to leave root disabled): ')
|
||||||
|
|
||||||
# Ask for additional users (super-user if root pw was not set)
|
# Ask for additional users (super-user if root pw was not set)
|
||||||
archinstall.arguments['users'] = {}
|
archinstall.arguments['users'] = {}
|
||||||
|
|
@ -147,7 +157,7 @@ def ask_user_questions():
|
||||||
if not archinstall.arguments.get('!root-password', None):
|
if not archinstall.arguments.get('!root-password', None):
|
||||||
archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True)
|
archinstall.arguments['superusers'] = archinstall.ask_for_superuser_account('Create a required super-user with sudo privileges: ', forced=True)
|
||||||
|
|
||||||
users, superusers = archinstall.ask_for_additional_users('Any additional users to install (leave blank for no users): ')
|
users, superusers = archinstall.ask_for_additional_users('Enter a username to create a additional user (leave blank to skip & continue): ')
|
||||||
archinstall.arguments['users'] = users
|
archinstall.arguments['users'] = users
|
||||||
archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers}
|
archinstall.arguments['superusers'] = {**archinstall.arguments['superusers'], **superusers}
|
||||||
|
|
||||||
|
|
@ -170,7 +180,7 @@ def ask_user_questions():
|
||||||
|
|
||||||
# Additional packages (with some light weight error handling for invalid package names)
|
# Additional packages (with some light weight error handling for invalid package names)
|
||||||
if not archinstall.arguments.get('packages', None):
|
if not archinstall.arguments.get('packages', None):
|
||||||
archinstall.arguments['packages'] = [package for package in input('Additional packages aside from base (space separated): ').split(' ') if len(package)]
|
archinstall.arguments['packages'] = [package for package in input('Write additional packages to install (space separated, leave blank to skip): ').split(' ') if len(package)]
|
||||||
|
|
||||||
# Verify packages that were given
|
# Verify packages that were given
|
||||||
try:
|
try:
|
||||||
|
|
@ -182,6 +192,11 @@ def ask_user_questions():
|
||||||
# Ask or Call the helper function that asks the user to optionally configure a network.
|
# Ask or Call the helper function that asks the user to optionally configure a network.
|
||||||
if not archinstall.arguments.get('nic', None):
|
if not archinstall.arguments.get('nic', None):
|
||||||
archinstall.arguments['nic'] = archinstall.ask_to_configure_network()
|
archinstall.arguments['nic'] = archinstall.ask_to_configure_network()
|
||||||
|
if not archinstall.arguments['nic']:
|
||||||
|
archinstall.log(f"No network configuration was selected. Network is going to be unavailable until configured manually!", fg="yellow")
|
||||||
|
|
||||||
|
if not archinstall.arguments.get('timezone', None):
|
||||||
|
archinstall.arguments['timezone'] = archinstall.ask_for_a_timezone()
|
||||||
|
|
||||||
|
|
||||||
def perform_installation_steps():
|
def perform_installation_steps():
|
||||||
|
|
@ -232,10 +247,10 @@ def perform_installation_steps():
|
||||||
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
with archinstall.Filesystem(archinstall.arguments['harddrive'], archinstall.GPT) as fs:
|
||||||
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
# Wipe the entire drive if the disk flag `keep_partitions`is False.
|
||||||
if archinstall.arguments['harddrive'].keep_partitions is False:
|
if archinstall.arguments['harddrive'].keep_partitions is False:
|
||||||
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'),
|
fs.use_entire_disk(root_filesystem_type=archinstall.arguments.get('filesystem', 'btrfs'))
|
||||||
encrypt_root_partition=archinstall.arguments.get('!encryption-password', False))
|
|
||||||
# Otherwise, check if encryption is desired and mark the root partition as encrypted.
|
# Check if encryption is desired and mark the root partition as encrypted.
|
||||||
elif archinstall.arguments.get('!encryption-password', None):
|
if archinstall.arguments.get('!encryption-password', None):
|
||||||
root_partition = fs.find_partition('/')
|
root_partition = fs.find_partition('/')
|
||||||
root_partition.encrypted = True
|
root_partition.encrypted = True
|
||||||
|
|
||||||
|
|
@ -243,8 +258,11 @@ def perform_installation_steps():
|
||||||
# which ones are safe to format, and format those.
|
# which ones are safe to format, and format those.
|
||||||
for partition in archinstall.arguments['harddrive']:
|
for partition in archinstall.arguments['harddrive']:
|
||||||
if partition.safe_to_format():
|
if partition.safe_to_format():
|
||||||
if partition.encrypted:
|
# Partition might be marked as encrypted due to the filesystem type crypt_LUKS
|
||||||
partition.encrypt(password=archinstall.arguments.get('!encryption-password', None))
|
# But we might have omitted the encryption password question to skip encryption.
|
||||||
|
# In which case partition.encrypted will be true, but passwd will be false.
|
||||||
|
if partition.encrypted and (passwd := archinstall.arguments.get('!encryption-password', None)):
|
||||||
|
partition.encrypt(password=passwd)
|
||||||
else:
|
else:
|
||||||
partition.format()
|
partition.format()
|
||||||
else:
|
else:
|
||||||
|
|
@ -262,7 +280,6 @@ def perform_installation_steps():
|
||||||
language=archinstall.arguments['keyboard-language'],
|
language=archinstall.arguments['keyboard-language'],
|
||||||
mirrors=archinstall.arguments['mirror-region'])
|
mirrors=archinstall.arguments['mirror-region'])
|
||||||
else:
|
else:
|
||||||
archinstall.arguments['harddrive'].partition[1].format('ext4')
|
|
||||||
perform_installation(device=fs.find_partition('/'),
|
perform_installation(device=fs.find_partition('/'),
|
||||||
boot_partition=fs.find_partition('/boot'),
|
boot_partition=fs.find_partition('/boot'),
|
||||||
language=archinstall.arguments['keyboard-language'],
|
language=archinstall.arguments['keyboard-language'],
|
||||||
|
|
@ -305,18 +322,17 @@ def perform_installation(device, boot_partition, language, mirrors):
|
||||||
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
||||||
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
||||||
|
|
||||||
if archinstall.arguments.get('profile', None) and len(profile := archinstall.arguments.get('profile').strip()):
|
if archinstall.arguments.get('profile', None):
|
||||||
installation.install_profile(profile)
|
installation.install_profile(archinstall.arguments.get('profile', None))
|
||||||
|
|
||||||
if archinstall.arguments.get('users', None):
|
for user, user_info in archinstall.arguments.get('users', {}).items():
|
||||||
for user in archinstall.arguments.get('users'):
|
installation.user_create(user, user_info["!password"], sudo=False)
|
||||||
password = users[user]
|
|
||||||
installation.user_create(user, password, sudo=False)
|
|
||||||
if archinstall.arguments.get('superusers', None):
|
|
||||||
for user in archinstall.arguments.get('users'):
|
|
||||||
password = users[user]
|
|
||||||
installation.user_create(user, password, sudo=Tru)
|
|
||||||
|
|
||||||
|
for superuser, user_info in archinstall.arguments.get('superusers', {}).items():
|
||||||
|
installation.user_create(superuser, user_info["!password"], sudo=True)
|
||||||
|
|
||||||
|
if (timezone := archinstall.arguments.get('timezone', None)):
|
||||||
|
installation.set_timezone(timezone)
|
||||||
|
|
||||||
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
||||||
installation.user_set_pw('root', root_pw)
|
installation.user_set_pw('root', root_pw)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ if __name__ == 'awesome':
|
||||||
editor = "nano"
|
editor = "nano"
|
||||||
filebrowser = "nemo gpicview-gtk3"
|
filebrowser = "nemo gpicview-gtk3"
|
||||||
webbrowser = "chromium" # TODO: Ask the user to select one instead
|
webbrowser = "chromium" # TODO: Ask the user to select one instead
|
||||||
utils = "openssh sshfs git htop pkgfile scrot dhclient wget libu2f-host"
|
utils = "openssh sshfs htop scrot wget"
|
||||||
|
|
||||||
|
|
||||||
installation.add_additional_packages(f"{webbrowser} {utils} {filebrowser} {editor}")
|
installation.add_additional_packages(f"{webbrowser} {utils} {filebrowser} {editor}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue