Added support for getting configuration from a config file (#364)
* added support for ingesting config * fixed condition to check key in dictionary * Removed redundant code, profile and desktop keys are now optional * Added base-config.json and support for pulling credentials from .env * added base config file and env file for users credentials * added silent install switch * added python-dotenv as a dependency * Updated Readme to include argparse changes as well as config ingestion * Updated Readme to include argparse changes as well as config ingestion * fixed typo in pyproject.toml * Replaced the magic __builtin__ global variable. This should fix mypy complaints while still retaining the same functionality, kinda. It's less automatic but it's also less of dark magic, which makes sense for anyone but me. * Fixes string index error. * Quotation error. * fixed initializing --script argument * added python-dotenv as a dependency * Installation can't be silent if config is not passed * fixed silent install help * fixed condition for ask_user_questions * reverted to creating profile object properly * Cleaned up and incorporated suggestions * added Profile import * added condition if Profile is null * fixed condition * updated parsing vars from argparse * removed loading users from .env * Reworking SysCommand & Moving to localectl for locale related activities (#4) * Moving to `localectl` rather than local file manipulation *(both for listing locales and setting them)*. * Swapped `loadkeys` for localectl. * Renamed `main` to `maim` in awesome profile. * Created `archinstall.Boot(<installation>)` which spawns a `systemd-nspawn` container against the installation target. * Exposing systemd.py's internals to archinstall global scope. * Re-worked `SysCommand` completely, it's now a wrapper for `SysCommandWorker` which supports interacting with the process in a different way. `SysCommand` should behave just like the old one, for backwards compatibility reasons. This fixes #68 and #69. * `SysCommand()` now has a `.decode()` function that defaults to `UTF-8`. * Adding back peak_output=True to pacstrap. Co-authored-by: Anton Hvornum <anton.feeds@gmail.com> Co-authored-by: Dylan Taylor <dylan@dylanmtaylor.com> Co-authored-by: Anton Hvornum <anton@hvornum.se> Co-authored-by: Anton Hvornum <anton.feeds@gmail.com> * fixed indent * removed redundant import * removed duplicate import * removed duplicate import Co-authored-by: Anton Hvornum <anton.feeds@gmail.com> Co-authored-by: Anton Hvornum <anton@hvornum.se> Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
This commit is contained in:
parent
49e6cbdc54
commit
bbb4599165
14
README.md
14
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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
return SysCommandWorker(["machinectl", "shell", self.container_name, *cmd], *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -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>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -27,4 +27,4 @@ include = ["docs/","profiles"]
|
|||
exclude = ["docs/*.html", "docs/_static","docs/*.png","docs/*.psd"]
|
||||
|
||||
[tool.flit.metadata.requires-extra]
|
||||
doc = ["sphinx"]
|
||||
doc = ["sphinx"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue