Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md14
-rw-r--r--archinstall/__init__.py60
-rw-r--r--archinstall/lib/disk.py2
-rw-r--r--archinstall/lib/general.py3
-rw-r--r--archinstall/lib/networking.py4
-rw-r--r--archinstall/lib/systemd.py9
-rw-r--r--examples/config-sample.json35
-rw-r--r--examples/guided.py38
-rw-r--r--profiles/desktop.py3
-rw-r--r--pyproject.toml2
-rw-r--r--setup.cfg2
11 files changed, 129 insertions, 43 deletions
diff --git a/README.md b/README.md
index c03b2e0f..100288f3 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,17 @@ Or use `pip install --upgrade archinstall` to use as a library.
Assuming you are on an Arch Linux live-ISO and booted into EFI mode.
- # python -m archinstall guided
+ # python -m archinstall --script guided
+
+
+## Running from a declarative [config](examples/base-config.json)
+
+Prequisites:
+ 1. Edit the [config](examples/base-config.json) according to your requirements.
+
+Assuming you are on a Arch Linux live-ISO and booted into EFI mode.
+
+ # python -m archinstall --config <path to config file> --vars '<space_seperated KEY=VALUE pairs>'
# Help?
@@ -143,7 +153,7 @@ This can be done by installing `pacman -S arch-install-scripts util-linux` local
# losetup -fP ./testimage.img
# losetup -a | grep "testimage.img" | awk -F ":" '{print $1}'
# pip install --upgrade archinstall
- # python -m archinstall guided
+ # python -m archinstall --script guided
# qemu-system-x86_64 -enable-kvm -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
This will create a *5 GB* `testimage.img` and create a loop device which we can use to format and install to.<br>
diff --git a/archinstall/__init__.py b/archinstall/__init__.py
index 075b6f50..276d122f 100644
--- a/archinstall/__init__.py
+++ b/archinstall/__init__.py
@@ -1,4 +1,6 @@
"""Arch Linux installer - guided, templates etc."""
+from argparse import ArgumentParser, FileType
+
from .lib.disk import *
from .lib.exceptions import *
from .lib.general import *
@@ -16,22 +18,46 @@ from .lib.storage import *
from .lib.systemd import *
from .lib.user_interaction import *
+parser = ArgumentParser()
+
__version__ = "2.2.0.dev1"
-# Basic version of arg.parse() supporting:
-# --key=value
-# --boolean
-arguments = {}
-positionals = []
-for arg in sys.argv[1:]:
- if '--' == arg[:2]:
- if '=' in arg:
- key, val = [x.strip() for x in arg[2:].split('=', 1)]
- else:
- key, val = arg[2:], True
- arguments[key] = val
- else:
- positionals.append(arg)
+
+def initialize_arguments():
+ config = {}
+ parser.add_argument("--config", nargs="?", help="json config file", type=FileType("r", encoding="UTF-8"))
+ parser.add_argument("--silent", action="store_true",
+ help="Warning!!! No prompts, ignored if config is not passed")
+ parser.add_argument("--script", default="guided", nargs="?", help="Script to run for installation", type=str)
+ parser.add_argument("--vars",
+ metavar="KEY=VALUE",
+ nargs='?',
+ help="Set a number of key-value pairs "
+ "(do not put spaces before or after the = sign). "
+ "If a value contains spaces, you should define "
+ "it with double quotes: "
+ 'foo="this is a sentence". Note that '
+ "values are always treated as strings.")
+ args = parser.parse_args()
+ if args.config is not None:
+ try:
+ config = json.load(args.config)
+ except Exception as e:
+ print(e)
+ # Installation can't be silent if config is not passed
+ config["silent"] = args.silent
+ if args.vars is not None:
+ try:
+ for var in args.vars.split(' '):
+ key, val = var.split("=")
+ config[key] = val
+ except Exception as e:
+ print(e)
+ config["script"] = args.script
+ return config
+
+
+arguments = initialize_arguments()
# TODO: Learn the dark arts of argparse... (I summon thee dark spawn of cPython)
@@ -46,12 +72,8 @@ def run_as_a_module():
# Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc.
storage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples'))
-
- if len(sys.argv) == 1:
- sys.argv.append('guided')
-
try:
- script = Script(sys.argv[1])
+ script = Script(arguments.get('script', None))
except ProfileNotFound as err:
print(f"Couldn't find file: {err}")
sys.exit(1)
diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py
index 44f2742b..8f67111a 100644
--- a/archinstall/lib/disk.py
+++ b/archinstall/lib/disk.py
@@ -266,7 +266,7 @@ class Partition:
files = len(glob.glob(f"{temporary_mountpoint}/*"))
iterations = 0
- while SysCommand(f"/usr/bin/umount -R {temporary_mountpoint}").exit_code != 0 and (iterations := iterations+1) < 10:
+ while SysCommand(f"/usr/bin/umount -R {temporary_mountpoint}").exit_code != 0 and (iterations := iterations + 1) < 10:
time.sleep(1)
temporary_path.rmdir()
diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py
index 65c83484..cec3891a 100644
--- a/archinstall/lib/general.py
+++ b/archinstall/lib/general.py
@@ -360,7 +360,8 @@ def prerequisite_check():
def reboot():
o = b''.join(SysCommand("/usr/bin/reboot"))
-def pid_exists(pid :int):
+
+def pid_exists(pid: int):
try:
return any(subprocess.check_output(['/usr/bin/ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
except subprocess.CalledProcessError:
diff --git a/archinstall/lib/networking.py b/archinstall/lib/networking.py
index fdeefb84..eb11a47e 100644
--- a/archinstall/lib/networking.py
+++ b/archinstall/lib/networking.py
@@ -1,14 +1,14 @@
import fcntl
-import os
import logging
+import os
import socket
import struct
from collections import OrderedDict
from .exceptions import *
from .general import SysCommand
-from .storage import storage
from .output import log
+from .storage import storage
def get_hw_addr(ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
diff --git a/archinstall/lib/systemd.py b/archinstall/lib/systemd.py
index e64ff7e0..383f1f17 100644
--- a/archinstall/lib/systemd.py
+++ b/archinstall/lib/systemd.py
@@ -5,6 +5,7 @@ from .installer import Installer
from .output import log
from .storage import storage
+
class Ini:
def __init__(self, *args, **kwargs):
"""
@@ -103,7 +104,7 @@ class Boot:
return self.session.is_alive()
- def SysCommand(self, cmd :list, *args, **kwargs):
+ def SysCommand(self, cmd: list, *args, **kwargs):
if cmd[0][0] != '/' and cmd[0][:2] != './':
# This check is also done in SysCommand & SysCommandWorker.
# However, that check is done for `machinectl` and not for our chroot command.
@@ -113,8 +114,8 @@ class Boot:
return SysCommand(["machinectl", "shell", self.container_name, *cmd], *args, **kwargs)
- def SysCommandWorker(self, cmd :list, *args, **kwargs):
+ def SysCommandWorker(self, cmd: list, *args, **kwargs):
if cmd[0][0] != '/' and cmd[0][:2] != './':
cmd[0] = locate_binary(cmd[0])
-
- return SysCommandWorker(["machinectl", "shell", self.container_name, *cmd], *args, **kwargs) \ No newline at end of file
+
+ return SysCommandWorker(["machinectl", "shell", self.container_name, *cmd], *args, **kwargs)
diff --git a/examples/config-sample.json b/examples/config-sample.json
new file mode 100644
index 00000000..55bdf04b
--- /dev/null
+++ b/examples/config-sample.json
@@ -0,0 +1,35 @@
+{
+ "!root-password": "<root_password>",
+ "audio": null,
+ "bootloader": "systemd-bootctl",
+ "filesystem": "btrfs",
+ "harddrive": {
+ "path": "/dev/sda"
+ },
+ "hostname": "box",
+ "kernels": [
+ "linux"
+ ],
+ "keyboard-language": "us",
+ "mirror-region": {
+ "Worldwide": {
+ "https://mirror.rackspace.com/archlinux/$repo/os/$arch": true
+ }
+ },
+ "nic": {
+ "NetworkManager": true
+ },
+ "packages": [],
+ "profile": null,
+ "superusers": {
+ "<username>": {
+ "!password": "<password>"
+ }
+ },
+ "timezone": "UTC",
+ "users": {
+ "<username>": {
+ "!password": "<password>"
+ }
+ }
+} \ No newline at end of file
diff --git a/examples/guided.py b/examples/guided.py
index b9b06a64..f0d0db7a 100644
--- a/examples/guided.py
+++ b/examples/guided.py
@@ -1,11 +1,12 @@
import json
import logging
-import time
import os
+import time
import archinstall
from archinstall.lib.hardware import has_uefi
from archinstall.lib.networking import check_mirror_reachable
+from archinstall.lib.profiles import Profile
if archinstall.arguments.get('help'):
print("See `man archinstall` for help.")
@@ -243,7 +244,8 @@ def perform_installation_steps():
archinstall.log(json.dumps(archinstall.arguments, indent=4, sort_keys=True, cls=archinstall.JSON), level=logging.INFO)
print()
- input('Press Enter to continue.')
+ if not archinstall.arguments.get('silent'):
+ input('Press Enter to continue.')
"""
Issue a final warning before we continue with something un-revertable.
@@ -261,7 +263,6 @@ def perform_installation_steps():
mode = archinstall.GPT
if has_uefi() is False:
mode = archinstall.MBR
-
with archinstall.Filesystem(archinstall.arguments['harddrive'], mode) as fs:
# Wipe the entire drive if the disk flag `keep_partitions`is False.
if archinstall.arguments['harddrive'].keep_partitions is False:
@@ -297,7 +298,7 @@ def perform_installation_steps():
fs.find_partition('/').mount(archinstall.storage.get('MOUNT_POINT', '/mnt'))
if has_uefi():
- fs.find_partition('/boot').mount(archinstall.storage.get('MOUNT_POINT', '/mnt')+'/boot')
+ fs.find_partition('/boot').mount(archinstall.storage.get('MOUNT_POINT', '/mnt') + '/boot')
perform_installation(archinstall.storage.get('MOUNT_POINT', '/mnt'))
@@ -381,12 +382,13 @@ def perform_installation(mountpoint):
exit(1)
installation.log("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation", fg="yellow")
- choice = input("Would you like to chroot into the newly created installation and perform post-installation configuration? [Y/n] ")
- if choice.lower() in ("y", ""):
- try:
- installation.drop_to_shell()
- except:
- pass
+ if not archinstall.arguments.get('silent'):
+ choice = input("Would you like to chroot into the newly created installation and perform post-installation configuration? [Y/n] ")
+ if choice.lower() in ("y", ""):
+ try:
+ installation.drop_to_shell()
+ except:
+ pass
# For support reasons, we'll log the disk layout post installation (crash or no crash)
archinstall.log(f"Disk states after installing: {archinstall.disk_layouts()}", level=logging.DEBUG)
@@ -397,5 +399,19 @@ if not check_mirror_reachable():
archinstall.log(f"Arch Linux mirrors are not reachable. Please check your internet connection and the log file '{log_file}'.", level=logging.INFO, fg="red")
exit(1)
-ask_user_questions()
+if archinstall.arguments.get('silent', None) is None:
+ ask_user_questions()
+else:
+ # Workarounds if config is loaded from a file
+ # The harddrive section should be moved to perform_installation_steps, where it's actually being performed
+ # Blockdevice object should be created in perform_installation_steps
+ # This needs to be done until then
+ archinstall.arguments['harddrive'] = archinstall.BlockDevice(path=archinstall.arguments['harddrive']['path'])
+ # Temporarily disabling keep_partitions if config file is loaded
+ archinstall.arguments['harddrive'].keep_partitions = False
+ # Temporary workaround to make Desktop Environments work
+ archinstall.storage['_desktop_profile'] = archinstall.arguments.get('desktop', None)
+ if archinstall.arguments.get('profile', None):
+ archinstall.arguments['profile'] = Profile(installer=None, path=archinstall.arguments['profile']['path'])
+
perform_installation_steps()
diff --git a/profiles/desktop.py b/profiles/desktop.py
index 30bb9a6a..631c7f76 100644
--- a/profiles/desktop.py
+++ b/profiles/desktop.py
@@ -48,7 +48,8 @@ def _prep_function(*args, **kwargs):
# Temporarily store the selected desktop profile
# in a session-safe location, since this module will get reloaded
# the next time it gets executed.
- archinstall.storage['_desktop_profile'] = desktop
+ if '_desktop_profile' not in archinstall.storage.keys():
+ archinstall.storage['_desktop_profile'] = desktop
profile = archinstall.Profile(None, desktop)
# Loading the instructions with a custom namespace, ensures that a __name__ comparison is never triggered.
diff --git a/pyproject.toml b/pyproject.toml
index 73c7a876..7afde7c7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,4 +27,4 @@ include = ["docs/","profiles"]
exclude = ["docs/*.html", "docs/_static","docs/*.png","docs/*.psd"]
[tool.flit.metadata.requires-extra]
-doc = ["sphinx"] \ No newline at end of file
+doc = ["sphinx"]
diff --git a/setup.cfg b/setup.cfg
index e5d79ef3..79dff732 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -22,7 +22,7 @@ classifers =
[options]
packages = find:
python_requires = >= 3.8
-
+
[options.packages.find]
include =
archinstall