Installer engine consist of two pieces (stages):
1) installer.configure(install_config, ui_config)
- install_config is a KS input config as well as 'installer'
portion of image builder config
if 'install_config' is None then configure will trigger
interactive ncurses configurator
- ui_config is a config for interactive ncurses configurator.
Can be used to control what screens to show.
No actions allowed toward target system (disk or image) during
configure stage.
2) installer.execute(install_config)
- takes install_config and "execute" it. All actions on target
system are performed here.
Installer can be run as an app, like ./isoInstaller
In this case control flow looks like:
isoInstaller
<detect KS from kernel cmdline> - no luck
installer.configure(None, ui_config)
ui_config - run ncurses interractive installer to
construct 'install_config'
<perform sanity checks of install_config>
installer.execute(install_config)
When isoInstaller detects KS file - control flow is like:
isoInstaller
<detect KS from kernel cmdline> - success - create 'install_config'
installer.configure(install_config, None)
<perform sanity checks of install_config>
installer.execute(install_config)
When installer called from image builder it is similar to KS case:
image builder
<parses its config file> - creates install_config out of
'installer' section
installer.configure(install_config, None)
<perform sanity checks of install_config>
installer.execute(install_config)
<performs post actions>
<convert raw disk to some output format>
Changed images config file to move installer specific
changes to 'installer' subsection
Deprecate install_config fields: iso_installer, ui_install,
additional-files.
Unified password related logic in installer and removed it
from image builder.
Added ks_config.txt file - documentation for ks file format.
NOTE: this commit can break ostree, as it was not tested.
Change-Id: I3f718b6793c7f0befeb63df4d7e99072cf4d7693
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/8127
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: Alexey Makhalov <amakhalov@vmware.com>
| ... | ... |
@@ -163,6 +163,7 @@ packages: check-docker-py check-tools photon-stage $(PHOTON_PUBLISH_XRPMS) $(PHO |
| 163 | 163 |
$(PUBLISH_BUILD_DEPENDENCIES) \ |
| 164 | 164 |
$(PACKAGE_WEIGHTS) \ |
| 165 | 165 |
--threads ${THREADS}
|
| 166 |
+ $(PHOTON_REPO_TOOL) $(PHOTON_RPMS_DIR) |
|
| 166 | 167 |
|
| 167 | 168 |
packages-docker: check-docker-py check-docker-service check-tools photon-stage $(PHOTON_PUBLISH_XRPMS) $(PHOTON_PUBLISH_RPMS) $(PHOTON_SOURCES) $(CONTAIN) generate-dep-lists |
| 168 | 169 |
@echo "Building all RPMS..." |
| ... | ... |
@@ -438,6 +439,7 @@ packages-cached: |
| 438 | 438 |
$(RM) -f $(PHOTON_RPMS_DIR_ARCH)/* && \ |
| 439 | 439 |
$(CP) -f $(PHOTON_CACHE_PATH)/RPMS/noarch/* $(PHOTON_RPMS_DIR_NOARCH)/ && \ |
| 440 | 440 |
$(CP) -f $(PHOTON_CACHE_PATH)/RPMS/$(ARCH)/* $(PHOTON_RPMS_DIR_ARCH)/ |
| 441 |
+ $(PHOTON_REPO_TOOL) $(PHOTON_RPMS_DIR) |
|
| 441 | 442 |
|
| 442 | 443 |
sources: |
| 443 | 444 |
@$(MKDIR) -p $(PHOTON_SRCS_DIR) |
| ... | ... |
@@ -1,6 +1,9 @@ |
| 1 | 1 |
# pylint: disable=invalid-name,missing-docstring |
| 2 | 2 |
import subprocess |
| 3 | 3 |
import os |
| 4 |
+import crypt |
|
| 5 |
+import string |
|
| 6 |
+import random |
|
| 4 | 7 |
|
| 5 | 8 |
class CommandUtils(object): |
| 6 | 9 |
def __init__(self, logger): |
| ... | ... |
@@ -16,3 +19,21 @@ class CommandUtils(object): |
| 16 | 16 |
self.logger.debug(err.decode()) |
| 17 | 17 |
return retval |
| 18 | 18 |
|
| 19 |
+ @staticmethod |
|
| 20 |
+ def is_vmware_virtualization(): |
|
| 21 |
+ """Detect vmware vm""" |
|
| 22 |
+ process = subprocess.Popen(['systemd-detect-virt'], stdout=subprocess.PIPE) |
|
| 23 |
+ out, err = process.communicate() |
|
| 24 |
+ if err is not None and err != 0: |
|
| 25 |
+ return False |
|
| 26 |
+ return out.decode() == 'vmware\n' |
|
| 27 |
+ |
|
| 28 |
+ @staticmethod |
|
| 29 |
+ def generate_password_hash(password): |
|
| 30 |
+ """Generate hash for the password""" |
|
| 31 |
+ shadow_password = crypt.crypt( |
|
| 32 |
+ password, "$6$" + "".join( |
|
| 33 |
+ [random.choice( |
|
| 34 |
+ string.ascii_letters + string.digits) for _ in range(16)])) |
|
| 35 |
+ return shadow_password |
|
| 36 |
+ |
| ... | ... |
@@ -12,6 +12,7 @@ import sys |
| 12 | 12 |
import glob |
| 13 | 13 |
import re |
| 14 | 14 |
import modules.commons |
| 15 |
+import random |
|
| 15 | 16 |
from logger import Logger |
| 16 | 17 |
from commandutils import CommandUtils |
| 17 | 18 |
from jsonwrapper import JsonWrapper |
| ... | ... |
@@ -23,34 +24,51 @@ class Installer(object): |
| 23 | 23 |
""" |
| 24 | 24 |
Photon installer |
| 25 | 25 |
""" |
| 26 |
+ |
|
| 27 |
+ # List of allowed keys in kickstart config file. |
|
| 28 |
+ # Please keep ks_config.txt file updated. |
|
| 29 |
+ known_keys = {
|
|
| 30 |
+ 'additional_packages', |
|
| 31 |
+ 'autopartition', |
|
| 32 |
+ 'bootmode', |
|
| 33 |
+ 'disk', |
|
| 34 |
+ 'eject_cdrom', |
|
| 35 |
+ 'hostname', |
|
| 36 |
+ 'install_linux_esx', |
|
| 37 |
+ 'packages', |
|
| 38 |
+ 'packagelist_file', |
|
| 39 |
+ 'partition_type', |
|
| 40 |
+ 'partitions', |
|
| 41 |
+ 'password', |
|
| 42 |
+ 'postinstall', |
|
| 43 |
+ 'public_key', |
|
| 44 |
+ 'setup_grub_script', |
|
| 45 |
+ 'shadow_password', |
|
| 46 |
+ 'type' |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 26 | 49 |
mount_command = os.path.dirname(__file__)+"/mk-mount-disk.sh" |
| 50 |
+ prepare_command = os.path.dirname(__file__)+"/mk-prepare-system.sh" |
|
| 27 | 51 |
finalize_command = "./mk-finalize-system.sh" |
| 28 | 52 |
chroot_command = os.path.dirname(__file__)+"/mk-run-chroot.sh" |
| 29 | 53 |
unmount_disk_command = os.path.dirname(__file__)+"/mk-unmount-disk.sh" |
| 30 | 54 |
default_partitions = [{"mountpoint": "/", "size": 0, "filesystem": "ext4"}]
|
| 31 | 55 |
|
| 32 |
- def __init__(self, install_config, maxy=0, maxx=0, iso_installer=False, |
|
| 56 |
+ def __init__(self, working_directory="/mnt/photon-root", maxy=0, maxx=0, iso_installer=False, |
|
| 33 | 57 |
rpm_path=os.path.dirname(__file__)+"/../stage/RPMS", log_path=os.path.dirname(__file__)+"/../stage/LOGS", log_level="info"): |
| 34 | 58 |
self.exiting = False |
| 35 |
- self.install_config = install_config |
|
| 36 |
- self.install_config['iso_installer'] = iso_installer |
|
| 59 |
+ self.interactive = False |
|
| 60 |
+ self.install_config = None |
|
| 61 |
+ self.iso_installer = iso_installer |
|
| 37 | 62 |
self.rpm_path = rpm_path |
| 38 | 63 |
self.logger = Logger.get_logger(log_path, log_level, not iso_installer) |
| 39 | 64 |
self.cmd = CommandUtils(self.logger) |
| 65 |
+ self.working_directory = working_directory |
|
| 40 | 66 |
|
| 41 |
- if 'working_directory' in self.install_config: |
|
| 42 |
- self.working_directory = self.install_config['working_directory'] |
|
| 43 |
- else: |
|
| 44 |
- self.working_directory = "/mnt/photon-root" |
|
| 45 |
- |
|
| 46 |
- if os.path.exists(self.working_directory) and os.path.isdir(self.working_directory): |
|
| 67 |
+ if os.path.exists(self.working_directory) and os.path.isdir(self.working_directory) and working_directory == '/mnt/photon-root': |
|
| 47 | 68 |
shutil.rmtree(self.working_directory) |
| 48 |
- os.mkdir(self.working_directory) |
|
| 49 |
- |
|
| 50 |
- if 'prepare_script' in self.install_config: |
|
| 51 |
- self.prepare_command = self.install_config['prepare_script'] |
|
| 52 |
- else: |
|
| 53 |
- self.prepare_command = os.path.dirname(__file__)+"/mk-prepare-system.sh" |
|
| 69 |
+ if not os.path.exists(self.working_directory): |
|
| 70 |
+ os.mkdir(self.working_directory) |
|
| 54 | 71 |
|
| 55 | 72 |
self.photon_root = self.working_directory + "/photon-chroot" |
| 56 | 73 |
self.installer_path = os.path.dirname(os.path.abspath(__file__)) |
| ... | ... |
@@ -60,13 +78,13 @@ class Installer(object): |
| 60 | 60 |
# used by tdnf.conf as cachedir=, tdnf will append the rest |
| 61 | 61 |
self.rpm_cache_dir_short = self.photon_root + '/cache/tdnf' |
| 62 | 62 |
|
| 63 |
- if 'setup_grub_script' in self.install_config: |
|
| 64 |
- self.setup_grub_command = self.install_config['setup_grub_script'] |
|
| 65 |
- else: |
|
| 66 |
- self.setup_grub_command = os.path.dirname(__file__)+"/mk-setup-grub.sh" |
|
| 63 |
+ self.setup_grub_command = os.path.dirname(__file__)+"/mk-setup-grub.sh" |
|
| 64 |
+ |
|
| 67 | 65 |
self.rpms_tobeinstalled = None |
| 68 | 66 |
|
| 69 |
- if self.install_config['iso_installer']: |
|
| 67 |
+ if iso_installer: |
|
| 68 |
+ self.maxy = maxy |
|
| 69 |
+ self.maxx = maxx |
|
| 70 | 70 |
#initializing windows |
| 71 | 71 |
height = 10 |
| 72 | 72 |
width = 75 |
| ... | ... |
@@ -83,11 +101,102 @@ class Installer(object): |
| 83 | 83 |
|
| 84 | 84 |
signal.signal(signal.SIGINT, self.exit_gracefully) |
| 85 | 85 |
|
| 86 |
- def install(self): |
|
| 86 |
+ """ |
|
| 87 |
+ create, append and validate configuration date - install_config |
|
| 88 |
+ """ |
|
| 89 |
+ def configure(self, install_config, ui_config = None): |
|
| 90 |
+ # run UI configurator iff install_config param is None |
|
| 91 |
+ if not install_config and ui_config: |
|
| 92 |
+ from iso_config import IsoConfig |
|
| 93 |
+ self.interactive = True |
|
| 94 |
+ config = IsoConfig(self.maxy, self.maxx) |
|
| 95 |
+ install_config = config.configure(ui_config) |
|
| 96 |
+ |
|
| 97 |
+ self._add_defaults(install_config) |
|
| 98 |
+ |
|
| 99 |
+ issue = self._check_install_config(install_config) |
|
| 100 |
+ if issue: |
|
| 101 |
+ self.logger.error(issue) |
|
| 102 |
+ raise Exception(issue) |
|
| 103 |
+ |
|
| 104 |
+ self.install_config = install_config |
|
| 105 |
+ |
|
| 106 |
+ |
|
| 107 |
+ def execute(self): |
|
| 108 |
+ if 'setup_grub_script' in self.install_config: |
|
| 109 |
+ self.setup_grub_command = self.install_config['setup_grub_script'] |
|
| 110 |
+ |
|
| 111 |
+ if self.install_config.get('type', '') == "ostree_host":
|
|
| 112 |
+ ostree = OstreeInstaller(self.install_config, self.maxy, self.maxx, self.interactive, |
|
| 113 |
+ self.iso_installer, self.rpm_path, self.log_path, self.log_level) |
|
| 114 |
+ return ostree.install() |
|
| 115 |
+ |
|
| 116 |
+ return self._install() |
|
| 117 |
+ |
|
| 118 |
+ def _add_defaults(self, install_config): |
|
| 119 |
+ """ |
|
| 120 |
+ Add default install_config settings if not specified |
|
| 121 |
+ """ |
|
| 122 |
+ # extend 'packages' by 'packagelist_file' and 'additional_packages' |
|
| 123 |
+ packages = [] |
|
| 124 |
+ if 'packagelist_file' in install_config: |
|
| 125 |
+ plf = install_config['packagelist_file'] |
|
| 126 |
+ if not plf.startswith('/'):
|
|
| 127 |
+ plf = os.path.join(os.path.dirname(__file__), plf) |
|
| 128 |
+ json_wrapper_package_list = JsonWrapper(plf) |
|
| 129 |
+ package_list_json = json_wrapper_package_list.read() |
|
| 130 |
+ packages.extend(package_list_json["packages"]) |
|
| 131 |
+ |
|
| 132 |
+ if 'additional_packages' in install_config: |
|
| 133 |
+ packages.extend(install_config['additional_packages']) |
|
| 134 |
+ |
|
| 135 |
+ if 'packages' in install_config: |
|
| 136 |
+ install_config['packages'] = list(set(packages + install_config['packages'])) |
|
| 137 |
+ else: |
|
| 138 |
+ install_config['packages'] = packages |
|
| 139 |
+ |
|
| 140 |
+ # 'bootmode' mode |
|
| 141 |
+ if 'bootmode' not in install_config: |
|
| 142 |
+ arch = subprocess.check_output(['uname', '-m'], universal_newlines=True) |
|
| 143 |
+ if "x86_64" in arch: |
|
| 144 |
+ install_config['bootmode'] = 'dualboot' |
|
| 145 |
+ else: |
|
| 146 |
+ install_config['bootmode'] = 'efi' |
|
| 147 |
+ |
|
| 148 |
+ # define 'hostname' as 'photon-<RANDOM STRING>' |
|
| 149 |
+ if "hostname" not in install_config or install_config['hostname'] == "": |
|
| 150 |
+ install_config['hostname'] = 'photon-%12x' % random.randrange(16**12) |
|
| 151 |
+ |
|
| 152 |
+ # Set crypted password if needed |
|
| 153 |
+ if 'shadow_password' not in install_config: |
|
| 154 |
+ if 'password' in install_config: |
|
| 155 |
+ if install_config['password']['crypted']: |
|
| 156 |
+ install_config['shadow_password'] = install_config['password']['text'] |
|
| 157 |
+ else: |
|
| 158 |
+ install_config['shadow_password'] = CommandUtils.generate_password_hash(install_config['password']['text']) |
|
| 159 |
+ else: |
|
| 160 |
+ install_config['shadow_password'] = '*' |
|
| 161 |
+ |
|
| 162 |
+ def _check_install_config(self, install_config): |
|
| 163 |
+ """ |
|
| 164 |
+ Sanity check of install_config before its execution. |
|
| 165 |
+ Return error string or None |
|
| 166 |
+ """ |
|
| 167 |
+ |
|
| 168 |
+ unknown_keys = install_config.keys() - Installer.known_keys |
|
| 169 |
+ if len(unknown_keys) > 0: |
|
| 170 |
+ return "Unknown install_config keys: " + ", ".join(unknown_keys) |
|
| 171 |
+ |
|
| 172 |
+ if not 'disk' in install_config: |
|
| 173 |
+ return "No disk configured" |
|
| 174 |
+ |
|
| 175 |
+ return None |
|
| 176 |
+ |
|
| 177 |
+ def _install(self): |
|
| 87 | 178 |
""" |
| 88 | 179 |
Install photon system and handle exception |
| 89 | 180 |
""" |
| 90 |
- if self.install_config['iso_installer']: |
|
| 181 |
+ if self.iso_installer: |
|
| 91 | 182 |
self.window.show_window() |
| 92 | 183 |
self.progress_bar.initialize('Initializing installation...')
|
| 93 | 184 |
self.progress_bar.show() |
| ... | ... |
@@ -97,7 +206,7 @@ class Installer(object): |
| 97 | 97 |
except Exception as inst: |
| 98 | 98 |
self.logger.exception(repr(inst)) |
| 99 | 99 |
self.exit_gracefully() |
| 100 |
- if not self.install_config['iso_installer']: |
|
| 100 |
+ if not self.iso_installer: |
|
| 101 | 101 |
raise |
| 102 | 102 |
|
| 103 | 103 |
def _unsafe_install(self): |
| ... | ... |
@@ -127,7 +236,7 @@ class Installer(object): |
| 127 | 127 |
del frame1 |
| 128 | 128 |
if not self.exiting: |
| 129 | 129 |
self.exiting = True |
| 130 |
- if self.install_config['iso_installer']: |
|
| 130 |
+ if self.iso_installer: |
|
| 131 | 131 |
self.progress_bar.hide() |
| 132 | 132 |
self.window.addstr(0, 0, 'Oops, Installer got interrupted.\n\n' + |
| 133 | 133 |
'Press any key to get to the bash...') |
| ... | ... |
@@ -147,6 +256,8 @@ class Installer(object): |
| 147 | 147 |
if retval != 0: |
| 148 | 148 |
self.logger.error("Failed to unmount disks")
|
| 149 | 149 |
|
| 150 |
+ shutil.rmtree(self.photon_root) |
|
| 151 |
+ |
|
| 150 | 152 |
# Uninitialize device paritions mapping |
| 151 | 153 |
disk = self.install_config['disk'] |
| 152 | 154 |
if 'loop' in disk: |
| ... | ... |
@@ -156,12 +267,12 @@ class Installer(object): |
| 156 | 156 |
return None |
| 157 | 157 |
|
| 158 | 158 |
# Congratulation screen |
| 159 |
- if self.install_config['iso_installer'] and not self.exiting: |
|
| 159 |
+ if self.iso_installer and not self.exiting: |
|
| 160 | 160 |
self.progress_bar.hide() |
| 161 | 161 |
self.window.addstr(0, 0, 'Congratulations, Photon has been installed in {0} secs.\n\n'
|
| 162 | 162 |
'Press any key to continue to boot...' |
| 163 | 163 |
.format(self.progress_bar.time_elapsed)) |
| 164 |
- if 'ui_install' in self.install_config: |
|
| 164 |
+ if self.interactive: |
|
| 165 | 165 |
self.window.content_window().getch() |
| 166 | 166 |
self._eject_cdrom() |
| 167 | 167 |
|
| ... | ... |
@@ -275,11 +386,11 @@ class Installer(object): |
| 275 | 275 |
self.logger.info("Failed to setup the disk for installation")
|
| 276 | 276 |
self.exit_gracefully() |
| 277 | 277 |
|
| 278 |
- if self.install_config['iso_installer']: |
|
| 278 |
+ if self.iso_installer: |
|
| 279 | 279 |
self.progress_bar.update_message('Initializing system...')
|
| 280 | 280 |
self._bind_installer() |
| 281 | 281 |
self._bind_repo_dir() |
| 282 |
- retval = self.cmd.run([self.prepare_command, '-w', self.photon_root, |
|
| 282 |
+ retval = self.cmd.run([Installer.prepare_command, '-w', self.photon_root, |
|
| 283 | 283 |
self.working_directory, self.rpm_cache_dir]) |
| 284 | 284 |
if retval != 0: |
| 285 | 285 |
self.logger.info("Failed to bind the installer and repo needed by tdnf")
|
| ... | ... |
@@ -289,7 +400,7 @@ class Installer(object): |
| 289 | 289 |
""" |
| 290 | 290 |
Finalize the system after the installation |
| 291 | 291 |
""" |
| 292 |
- if self.install_config['iso_installer']: |
|
| 292 |
+ if self.iso_installer: |
|
| 293 | 293 |
self.progress_bar.show_loading('Finalizing installation')
|
| 294 | 294 |
#Setup the disk |
| 295 | 295 |
retval = self.cmd.run([Installer.chroot_command, '-w', self.photon_root, |
| ... | ... |
@@ -304,6 +415,8 @@ class Installer(object): |
| 304 | 304 |
retval = self.cmd.run(['rm', '-rf', os.path.join(self.photon_root, "cache")]) |
| 305 | 305 |
if retval != 0: |
| 306 | 306 |
self.logger.error("Fail to remove the cache")
|
| 307 |
+ os.remove(self.tdnf_conf_path) |
|
| 308 |
+ os.remove(self.tdnf_repo_path) |
|
| 307 | 309 |
|
| 308 | 310 |
def _post_install(self): |
| 309 | 311 |
# install grub |
| ... | ... |
@@ -413,7 +526,7 @@ class Installer(object): |
| 413 | 413 |
# run in shell to do not throw exception if tdnf not found |
| 414 | 414 |
process = subprocess.Popen(tdnf_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 415 | 415 |
|
| 416 |
- if self.install_config['iso_installer']: |
|
| 416 |
+ if self.iso_installer: |
|
| 417 | 417 |
while True: |
| 418 | 418 |
output = process.stdout.readline().decode() |
| 419 | 419 |
if output == '': |
| ... | ... |
@@ -475,10 +588,7 @@ class Installer(object): |
| 475 | 475 |
""" |
| 476 | 476 |
Eject the cdrom on request |
| 477 | 477 |
""" |
| 478 |
- eject_cdrom = True |
|
| 479 |
- if 'eject_cdrom' in self.install_config and not self.install_config['eject_cdrom']: |
|
| 480 |
- eject_cdrom = False |
|
| 481 |
- if eject_cdrom: |
|
| 478 |
+ if self.install_config.get('eject_cdrom', True):
|
|
| 482 | 479 |
self.cmd.run(['eject', '-r']) |
| 483 | 480 |
|
| 484 | 481 |
def _enable_network_in_chroot(self): |
| ... | ... |
@@ -519,7 +629,7 @@ class Installer(object): |
| 519 | 519 |
Partition the disk |
| 520 | 520 |
""" |
| 521 | 521 |
|
| 522 |
- if self.install_config['iso_installer']: |
|
| 522 |
+ if self.iso_installer: |
|
| 523 | 523 |
self.progress_bar.update_message('Partitioning...')
|
| 524 | 524 |
|
| 525 | 525 |
if 'partitions' not in self.install_config: |
| 526 | 526 |
deleted file mode 100755 |
| ... | ... |
@@ -1,31 +0,0 @@ |
| 1 |
-# |
|
| 2 |
-# |
|
| 3 |
-# Author: Touseef Liaqat <tliaqat@vmware.com> |
|
| 4 |
- |
|
| 5 |
-from installer import Installer |
|
| 6 |
-from ostreeinstaller import OstreeInstaller |
|
| 7 |
- |
|
| 8 |
-class InstallerContainer(object): |
|
| 9 |
- def __init__(self, install_config, maxy=0, maxx=0, |
|
| 10 |
- iso_installer=False, rpm_path="../stage/RPMS", |
|
| 11 |
- log_path="../stage/LOGS", log_level="info"): |
|
| 12 |
- |
|
| 13 |
- self.install_config = install_config |
|
| 14 |
- self.maxy = maxy |
|
| 15 |
- self.maxx = maxx |
|
| 16 |
- self.iso_installer = iso_installer |
|
| 17 |
- self.rpm_path = rpm_path |
|
| 18 |
- self.log_path = log_path |
|
| 19 |
- self.log_level = log_level |
|
| 20 |
- |
|
| 21 |
- def install(self): |
|
| 22 |
- installer = None |
|
| 23 |
- |
|
| 24 |
- if self.install_config.get('type', '') == "ostree_host":
|
|
| 25 |
- installer = OstreeInstaller(self.install_config, self.maxy, self.maxx, |
|
| 26 |
- self.iso_installer, self.rpm_path, self.log_path, self.log_level) |
|
| 27 |
- else: |
|
| 28 |
- installer = Installer(self.install_config, self.maxy, self.maxx, |
|
| 29 |
- self.iso_installer, self.rpm_path, self.log_path, self.log_level) |
|
| 30 |
- |
|
| 31 |
- return installer.install() |
| ... | ... |
@@ -3,10 +3,11 @@ |
| 3 | 3 |
# |
| 4 | 4 |
# Author: Mahmoud Bassiouny <mbassiouny@vmware.com> |
| 5 | 5 |
|
| 6 |
-from argparse import ArgumentParser |
|
| 6 |
+import shlex |
|
| 7 | 7 |
import curses |
| 8 |
-from installercontainer import InstallerContainer |
|
| 9 |
-from iso_config import IsoConfig |
|
| 8 |
+from argparse import ArgumentParser |
|
| 9 |
+from installer import Installer |
|
| 10 |
+from commandutils import CommandUtils |
|
| 10 | 11 |
|
| 11 | 12 |
class IsoInstaller(object): |
| 12 | 13 |
def __init__(self, stdscreen, options_file, rpms_path): |
| ... | ... |
@@ -23,19 +24,124 @@ class IsoInstaller(object): |
| 23 | 23 |
self.maxy, self.maxx = self.screen.getmaxyx() |
| 24 | 24 |
self.screen.addstr(self.maxy - 1, 0, ' Arrow keys make selections; <Enter> activates.') |
| 25 | 25 |
curses.curs_set(0) |
| 26 |
- config = IsoConfig() |
|
| 27 |
- rpm_path, install_config = config.configure(options_file, rpms_path, self.maxy, self.maxx, log_path="/var/log") |
|
| 28 | 26 |
|
| 29 |
- self.screen.erase() |
|
| 30 |
- installer = InstallerContainer( |
|
| 31 |
- install_config, |
|
| 32 |
- self.maxy, self.maxx, |
|
| 33 |
- True, |
|
| 34 |
- rpm_path=rpm_path, |
|
| 27 |
+ install_config=None |
|
| 28 |
+ self.cd_mount_path = None |
|
| 29 |
+ ks_path = None |
|
| 30 |
+ cd_search = None |
|
| 31 |
+ |
|
| 32 |
+ with open('/proc/cmdline', 'r') as f:
|
|
| 33 |
+ kernel_params = shlex.split(f.read().replace('\n', ''))
|
|
| 34 |
+ |
|
| 35 |
+ for arg in kernel_params: |
|
| 36 |
+ if arg.startswith("ks="):
|
|
| 37 |
+ ks_path = arg[len("ks="):]
|
|
| 38 |
+ elif arg.startswith("repo="):
|
|
| 39 |
+ rpms_path = arg[len("repo="):]
|
|
| 40 |
+ elif arg.startswith("photon.media="):
|
|
| 41 |
+ cd_search = arg[len("photon.media="):]
|
|
| 42 |
+ |
|
| 43 |
+ if cd_search is not None: |
|
| 44 |
+ self.mount_cd(cd_search) |
|
| 45 |
+ |
|
| 46 |
+ if ks_path is not None: |
|
| 47 |
+ install_config=self.get_ks_config(ks_path) |
|
| 48 |
+ |
|
| 49 |
+ # Run installer |
|
| 50 |
+ installer = Installer( |
|
| 51 |
+ maxy=self.maxy, |
|
| 52 |
+ maxx=self.maxx, |
|
| 53 |
+ iso_installer=True, |
|
| 54 |
+ rpm_path=rpms_path, |
|
| 35 | 55 |
log_path="/var/log", |
| 36 | 56 |
log_level="debug") |
| 37 | 57 |
|
| 38 |
- installer.install() |
|
| 58 |
+ ui_config={}
|
|
| 59 |
+ ui_config['options_file'] = options_file |
|
| 60 |
+ |
|
| 61 |
+ installer.configure(install_config, ui_config) |
|
| 62 |
+ self.screen.erase() |
|
| 63 |
+ installer.execute() |
|
| 64 |
+ |
|
| 65 |
+ def _load_ks_config(self, path): |
|
| 66 |
+ """kick start configuration""" |
|
| 67 |
+ if path.startswith("http://"):
|
|
| 68 |
+ # Do 5 trials to get the kick start |
|
| 69 |
+ # TODO: make sure the installer run after network is up |
|
| 70 |
+ ks_file_error = "Failed to get the kickstart file at {0}".format(path)
|
|
| 71 |
+ wait = 1 |
|
| 72 |
+ for _ in range(0, 5): |
|
| 73 |
+ err_msg = "" |
|
| 74 |
+ try: |
|
| 75 |
+ response = requests.get(path, timeout=3) |
|
| 76 |
+ if response.ok: |
|
| 77 |
+ return json.loads(response.text) |
|
| 78 |
+ err_msg = response.text |
|
| 79 |
+ except Exception as e: |
|
| 80 |
+ err_msg = e |
|
| 81 |
+ |
|
| 82 |
+ self.logger.warning(ks_file_error) |
|
| 83 |
+ self.logger.warning("error msg: {0}".format(err_msg))
|
|
| 84 |
+ self.logger.warning("retry in a second")
|
|
| 85 |
+ time.sleep(wait) |
|
| 86 |
+ wait = wait * 2 |
|
| 87 |
+ |
|
| 88 |
+ # Something went wrong |
|
| 89 |
+ self.logger.error(ks_file_error) |
|
| 90 |
+ raise Exception(err_msg) |
|
| 91 |
+ else: |
|
| 92 |
+ if path.startswith("cdrom:/"):
|
|
| 93 |
+ if self.cd_mount_path is None: |
|
| 94 |
+ raise Exception("cannot read ks config from cdrom, no cdrom specified")
|
|
| 95 |
+ path = os.path.join(self.cd_mount_path, path.replace("cdrom:/", "", 1))
|
|
| 96 |
+ return (JsonWrapper(path)).read() |
|
| 97 |
+ |
|
| 98 |
+ def get_ks_config(self, ks_path): |
|
| 99 |
+ """Load configuration from kick start file""" |
|
| 100 |
+ install_config = self._load_ks_config(ks_path) |
|
| 101 |
+ |
|
| 102 |
+ if 'install_linux_esx' not in install_config and CommandUtils.is_vmware_virtualization(): |
|
| 103 |
+ install_config['install_linux_esx'] = True |
|
| 104 |
+ |
|
| 105 |
+ return install_config |
|
| 106 |
+ |
|
| 107 |
+ def mount_cd(self, cd_search): |
|
| 108 |
+ """Mount the cd with RPMS""" |
|
| 109 |
+ # check if the cd is already mounted |
|
| 110 |
+ if self.cd_mount_path: |
|
| 111 |
+ return |
|
| 112 |
+ mount_path = "/mnt/cdrom" |
|
| 113 |
+ |
|
| 114 |
+ # Mount the cd to get the RPMS |
|
| 115 |
+ os.makedirs(mount_path, exist_ok=True) |
|
| 116 |
+ |
|
| 117 |
+ # Construct mount cmdline |
|
| 118 |
+ cmdline = ['mount'] |
|
| 119 |
+ if cd_search.startswith("UUID="):
|
|
| 120 |
+ cmdline.extend(['-U', cd_search[len("UUID="):] ]);
|
|
| 121 |
+ elif cd_search.startswith("LABEL="):
|
|
| 122 |
+ cmdline.extend(['-L', cd_search[len("LABEL="):] ]);
|
|
| 123 |
+ elif cd_search == "cdrom": |
|
| 124 |
+ cmdline.append('/dev/cdrom')
|
|
| 125 |
+ else: |
|
| 126 |
+ self.logger.error("Unsupported installer media, check photon.media in kernel cmdline")
|
|
| 127 |
+ raise Exception("Can not mount the cd")
|
|
| 128 |
+ |
|
| 129 |
+ cmdline.extend(['-o', 'ro', mount_path]) |
|
| 130 |
+ |
|
| 131 |
+ # Retry mount the CD |
|
| 132 |
+ for _ in range(0, 3): |
|
| 133 |
+ process = subprocess.Popen(cmdline) |
|
| 134 |
+ retval = process.wait() |
|
| 135 |
+ if retval == 0: |
|
| 136 |
+ self.cd_mount_path = mount_path |
|
| 137 |
+ return |
|
| 138 |
+ self.logger.error("Failed to mount the cd, retry in a second")
|
|
| 139 |
+ time.sleep(1) |
|
| 140 |
+ self.logger.error("Failed to mount the cd, exiting the installer")
|
|
| 141 |
+ self.logger.error("check the logs for more details")
|
|
| 142 |
+ raise Exception("Can not mount the cd")
|
|
| 143 |
+ |
|
| 39 | 144 |
|
| 40 | 145 |
if __name__ == '__main__': |
| 41 | 146 |
usage = "Usage: %prog [options]" |
| ... | ... |
@@ -1,13 +1,6 @@ |
| 1 | 1 |
import os |
| 2 | 2 |
import sys |
| 3 |
-import subprocess |
|
| 4 |
-import shlex |
|
| 5 | 3 |
import re |
| 6 |
-import json |
|
| 7 |
-import time |
|
| 8 |
-import crypt |
|
| 9 |
-import string |
|
| 10 |
-from urllib.request import urlopen |
|
| 11 | 4 |
import random |
| 12 | 5 |
import requests |
| 13 | 6 |
import cracklib |
| ... | ... |
@@ -16,19 +9,18 @@ from custompartition import CustomPartition |
| 16 | 16 |
from packageselector import PackageSelector |
| 17 | 17 |
from windowstringreader import WindowStringReader |
| 18 | 18 |
from confirmwindow import ConfirmWindow |
| 19 |
-from jsonwrapper import JsonWrapper |
|
| 20 | 19 |
from selectdisk import SelectDisk |
| 21 | 20 |
from license import License |
| 22 | 21 |
from linuxselector import LinuxSelector |
| 23 | 22 |
from ostreeserverselector import OSTreeServerSelector |
| 24 | 23 |
from ostreewindowstringreader import OSTreeWindowStringReader |
| 24 |
+from commandutils import CommandUtils |
|
| 25 | 25 |
|
| 26 | 26 |
class IsoConfig(object): |
| 27 | 27 |
g_ostree_repo_url = None |
| 28 | 28 |
"""This class handles iso installer configuration.""" |
| 29 |
- def __init__(self): |
|
| 29 |
+ def __init__(self, maxy, maxx): |
|
| 30 | 30 |
self.logger = None |
| 31 |
- self.cd_mount_path = None |
|
| 32 | 31 |
self.alpha_chars = list(range(65, 91)) |
| 33 | 32 |
self.alpha_chars.extend(range(97, 123)) |
| 34 | 33 |
self.hostname_accepted_chars = self.alpha_chars |
| ... | ... |
@@ -39,160 +31,10 @@ class IsoConfig(object): |
| 39 | 39 |
self.random_id = '%12x' % random.randrange(16**12) |
| 40 | 40 |
self.random_hostname = "photon-" + self.random_id.strip() |
| 41 | 41 |
|
| 42 |
- def configure(self, options_file, rpms_path, maxy, maxx, log_path="../stage/LOGS"): |
|
| 43 |
- self.logger = Logger.get_logger(log_path) |
|
| 44 |
- ks_path = None |
|
| 45 |
- rpm_path = rpms_path |
|
| 46 |
- ks_config = None |
|
| 47 |
- cd_search = None |
|
| 42 |
+ self.maxy = maxy |
|
| 43 |
+ self.maxx = maxx |
|
| 44 |
+ self.logger = Logger.get_logger() |
|
| 48 | 45 |
|
| 49 |
- with open('/proc/cmdline', 'r') as f:
|
|
| 50 |
- kernel_params = shlex.split(f.read().replace('\n', ''))
|
|
| 51 |
- |
|
| 52 |
- for arg in kernel_params: |
|
| 53 |
- if arg.startswith("ks="):
|
|
| 54 |
- ks_path = arg[len("ks="):]
|
|
| 55 |
- elif arg.startswith("repo="):
|
|
| 56 |
- rpm_path = arg[len("repo="):]
|
|
| 57 |
- elif arg.startswith("photon.media="):
|
|
| 58 |
- cd_search = arg[len("photon.media="):]
|
|
| 59 |
- |
|
| 60 |
- if cd_search is not None: |
|
| 61 |
- self.mount_cd(cd_search) |
|
| 62 |
- |
|
| 63 |
- if ks_path is not None: |
|
| 64 |
- ks_config = self.get_config(ks_path) |
|
| 65 |
- |
|
| 66 |
- if rpm_path is None: |
|
| 67 |
- # the rpms should be in the cd |
|
| 68 |
- if self.cd_mount_path is None: |
|
| 69 |
- self.logger.error("Please specify RPM repo location, as no cdrom is specified. (PXE?)")
|
|
| 70 |
- raise Exception("RPM repo not found")
|
|
| 71 |
- rpm_path = os.path.join(self.cd_mount_path, "RPMS") |
|
| 72 |
- |
|
| 73 |
- if ks_config: |
|
| 74 |
- install_config = self.ks_config(options_file, ks_config) |
|
| 75 |
- else: |
|
| 76 |
- install_config = self.ui_config(options_file, maxy, maxx) |
|
| 77 |
- |
|
| 78 |
- self._add_default(install_config) |
|
| 79 |
- |
|
| 80 |
- issue = self._check_install_config(install_config) |
|
| 81 |
- if issue: |
|
| 82 |
- self.logger.error(issue) |
|
| 83 |
- raise Exception(issue) |
|
| 84 |
- |
|
| 85 |
- return rpm_path, install_config |
|
| 86 |
- |
|
| 87 |
- @staticmethod |
|
| 88 |
- def is_vmware_virtualization(): |
|
| 89 |
- """Detect vmware vm""" |
|
| 90 |
- process = subprocess.Popen(['systemd-detect-virt'], stdout=subprocess.PIPE) |
|
| 91 |
- out, err = process.communicate() |
|
| 92 |
- if err is not None and err != 0: |
|
| 93 |
- return False |
|
| 94 |
- return out.decode() == 'vmware\n' |
|
| 95 |
- |
|
| 96 |
- def get_config(self, path): |
|
| 97 |
- """kick start configuration""" |
|
| 98 |
- if path.startswith("http://"):
|
|
| 99 |
- # Do 5 trials to get the kick start |
|
| 100 |
- # TODO: make sure the installer run after network is up |
|
| 101 |
- ks_file_error = "Failed to get the kickstart file at {0}".format(path)
|
|
| 102 |
- wait = 1 |
|
| 103 |
- for _ in range(0, 5): |
|
| 104 |
- err_msg = "" |
|
| 105 |
- try: |
|
| 106 |
- response = requests.get(path, timeout=3) |
|
| 107 |
- if response.ok: |
|
| 108 |
- return json.loads(response.text) |
|
| 109 |
- err_msg = response.text |
|
| 110 |
- except Exception as e: |
|
| 111 |
- err_msg = e |
|
| 112 |
- |
|
| 113 |
- self.logger.warning(ks_file_error) |
|
| 114 |
- self.logger.warning("error msg: {0}".format(err_msg))
|
|
| 115 |
- self.logger.warning("retry in a second")
|
|
| 116 |
- time.sleep(wait) |
|
| 117 |
- wait = wait * 2 |
|
| 118 |
- |
|
| 119 |
- # Something went wrong |
|
| 120 |
- self.logger.error(ks_file_error) |
|
| 121 |
- raise Exception(err_msg) |
|
| 122 |
- else: |
|
| 123 |
- if path.startswith("cdrom:/"):
|
|
| 124 |
- if self.cd_mount_path is None: |
|
| 125 |
- raise Exception("cannot read ks config from cdrom, no cdrom specified")
|
|
| 126 |
- path = os.path.join(self.cd_mount_path, path.replace("cdrom:/", "", 1))
|
|
| 127 |
- return (JsonWrapper(path)).read() |
|
| 128 |
- |
|
| 129 |
- def mount_cd(self, cd_search): |
|
| 130 |
- """Mount the cd with RPMS""" |
|
| 131 |
- # check if the cd is already mounted |
|
| 132 |
- if self.cd_mount_path: |
|
| 133 |
- return |
|
| 134 |
- mount_path = "/mnt/cdrom" |
|
| 135 |
- |
|
| 136 |
- # Mount the cd to get the RPMS |
|
| 137 |
- os.makedirs(mount_path, exist_ok=True) |
|
| 138 |
- |
|
| 139 |
- # Construct mount cmdline |
|
| 140 |
- cmdline = ['mount'] |
|
| 141 |
- if cd_search.startswith("UUID="):
|
|
| 142 |
- cmdline.extend(['-U', cd_search[len("UUID="):] ]);
|
|
| 143 |
- elif cd_search.startswith("LABEL="):
|
|
| 144 |
- cmdline.extend(['-L', cd_search[len("LABEL="):] ]);
|
|
| 145 |
- elif cd_search == "cdrom": |
|
| 146 |
- cmdline.append('/dev/cdrom')
|
|
| 147 |
- else: |
|
| 148 |
- self.logger.error("Unsupported installer media, check photon.media in kernel cmdline")
|
|
| 149 |
- raise Exception("Can not mount the cd")
|
|
| 150 |
- |
|
| 151 |
- cmdline.extend(['-o', 'ro', mount_path]) |
|
| 152 |
- |
|
| 153 |
- # Retry mount the CD |
|
| 154 |
- for _ in range(0, 3): |
|
| 155 |
- process = subprocess.Popen(cmdline) |
|
| 156 |
- retval = process.wait() |
|
| 157 |
- if retval == 0: |
|
| 158 |
- self.cd_mount_path = mount_path |
|
| 159 |
- return |
|
| 160 |
- self.logger.error("Failed to mount the cd, retry in a second")
|
|
| 161 |
- time.sleep(1) |
|
| 162 |
- self.logger.error("Failed to mount the cd, exiting the installer")
|
|
| 163 |
- self.logger.error("check the logs for more details")
|
|
| 164 |
- raise Exception("Can not mount the cd")
|
|
| 165 |
- |
|
| 166 |
- def ks_config(self, options_file, ks_config): |
|
| 167 |
- """Load configuration from file""" |
|
| 168 |
- del options_file |
|
| 169 |
- install_config = ks_config |
|
| 170 |
- if self.is_vmware_virtualization() and 'install_linux_esx' not in install_config: |
|
| 171 |
- install_config['install_linux_esx'] = True |
|
| 172 |
- |
|
| 173 |
- base_path = os.path.dirname("build_install_options_all.json")
|
|
| 174 |
- package_list = [] |
|
| 175 |
- if 'packagelist_file' in install_config: |
|
| 176 |
- package_list = PackageSelector.get_packages_to_install(install_config['packagelist_file'], |
|
| 177 |
- base_path) |
|
| 178 |
- |
|
| 179 |
- if 'additional_packages' in install_config: |
|
| 180 |
- package_list.extend(install_config['additional_packages']) |
|
| 181 |
- install_config['packages'] = package_list |
|
| 182 |
- |
|
| 183 |
- if "hostname" in install_config: |
|
| 184 |
- evalhostname = os.popen('printf ' + install_config["hostname"].strip(" ")).readlines()
|
|
| 185 |
- install_config['hostname'] = evalhostname[0] |
|
| 186 |
- |
|
| 187 |
- # crypt the password if needed |
|
| 188 |
- if install_config['password']['crypted']: |
|
| 189 |
- install_config['password'] = install_config['password']['text'] |
|
| 190 |
- else: |
|
| 191 |
- install_config['password'] = crypt.crypt( |
|
| 192 |
- install_config['password']['text'], |
|
| 193 |
- "$6$" + "".join([random.choice( |
|
| 194 |
- string.ascii_letters + string.digits) for _ in range(16)])) |
|
| 195 |
- return install_config |
|
| 196 | 46 |
|
| 197 | 47 |
@staticmethod |
| 198 | 48 |
def validate_hostname(hostname): |
| ... | ... |
@@ -290,23 +132,10 @@ class IsoConfig(object): |
| 290 | 290 |
password = str(message) |
| 291 | 291 |
return password == text, "Error: " + password |
| 292 | 292 |
|
| 293 |
- @staticmethod |
|
| 294 |
- def generate_password_hash(password): |
|
| 295 |
- """Generate hash for the password""" |
|
| 296 |
- shadow_password = crypt.crypt( |
|
| 297 |
- password, "$6$" + "".join( |
|
| 298 |
- [random.choice( |
|
| 299 |
- string.ascii_letters + string.digits) for _ in range(16)])) |
|
| 300 |
- return shadow_password |
|
| 301 |
- |
|
| 302 |
- def ui_config(self, options_file, maxy, maxx): |
|
| 293 |
+ def configure(self, ui_config): |
|
| 303 | 294 |
"""Configuration through UI""" |
| 304 |
- # This represents the installer screen, the bool indicated if |
|
| 305 |
- # I can go back to this window or not |
|
| 306 | 295 |
install_config = {}
|
| 307 |
- install_config['ui_install'] = True |
|
| 308 |
- items = self.add_ui_pages(options_file, maxy, maxx, |
|
| 309 |
- install_config) |
|
| 296 |
+ items = self.add_ui_pages(install_config, ui_config, self.maxy, self.maxx) |
|
| 310 | 297 |
index = 0 |
| 311 | 298 |
while True: |
| 312 | 299 |
result = items[index][0]() |
| ... | ... |
@@ -325,12 +154,13 @@ class IsoConfig(object): |
| 325 | 325 |
if index < 0: |
| 326 | 326 |
index = 0 |
| 327 | 327 |
return install_config |
| 328 |
- def add_ui_pages(self, options_file, maxy, maxx, install_config): |
|
| 328 |
+ |
|
| 329 |
+ def add_ui_pages(self, install_config, ui_config, maxy, maxx): |
|
| 329 | 330 |
items = [] |
| 330 | 331 |
license_agreement = License(maxy, maxx) |
| 331 | 332 |
select_disk = SelectDisk(maxy, maxx, install_config) |
| 332 | 333 |
custom_partition = CustomPartition(maxy, maxx, install_config) |
| 333 |
- package_selector = PackageSelector(maxy, maxx, install_config, options_file) |
|
| 334 |
+ package_selector = PackageSelector(maxy, maxx, install_config, ui_config['options_file']) |
|
| 334 | 335 |
hostname_reader = WindowStringReader( |
| 335 | 336 |
maxy, maxx, 10, 70, |
| 336 | 337 |
'hostname', |
| ... | ... |
@@ -344,7 +174,7 @@ class IsoConfig(object): |
| 344 | 344 |
True) |
| 345 | 345 |
root_password_reader = WindowStringReader( |
| 346 | 346 |
maxy, maxx, 10, 70, |
| 347 |
- 'password', |
|
| 347 |
+ 'shadow_password', |
|
| 348 | 348 |
None, # confirmation error msg if it's a confirmation text |
| 349 | 349 |
'*', # echo char |
| 350 | 350 |
None, # set of accepted chars |
| ... | ... |
@@ -353,13 +183,13 @@ class IsoConfig(object): |
| 353 | 353 |
'Set up root password', 'Root password:', 2, install_config) |
| 354 | 354 |
confirm_password_reader = WindowStringReader( |
| 355 | 355 |
maxy, maxx, 10, 70, |
| 356 |
- 'password', |
|
| 356 |
+ 'shadow_password', |
|
| 357 | 357 |
# confirmation error msg if it's a confirmation text |
| 358 | 358 |
"Passwords don't match, please try again.", |
| 359 | 359 |
'*', # echo char |
| 360 | 360 |
None, # set of accepted chars |
| 361 | 361 |
None, # validation function of the input |
| 362 |
- IsoConfig.generate_password_hash, # post processing of the input field |
|
| 362 |
+ CommandUtils.generate_password_hash, # post processing of the input field |
|
| 363 | 363 |
'Confirm root password', 'Confirm Root password:', 2, install_config) |
| 364 | 364 |
|
| 365 | 365 |
ostree_server_selector = OSTreeServerSelector(maxy, maxx, install_config) |
| ... | ... |
@@ -388,11 +218,13 @@ class IsoConfig(object): |
| 388 | 388 |
'Start installation? All data on the selected disk will be lost.\n\n' |
| 389 | 389 |
'Press <Yes> to confirm, or <No> to quit') |
| 390 | 390 |
|
| 391 |
+ # This represents the installer screens, the bool indicated if |
|
| 392 |
+ # I can go back to this window or not |
|
| 391 | 393 |
items.append((license_agreement.display, False)) |
| 392 | 394 |
items.append((select_disk.display, True)) |
| 393 | 395 |
items.append((custom_partition.display, False)) |
| 394 | 396 |
items.append((package_selector.display, True)) |
| 395 |
- if self.is_vmware_virtualization(): |
|
| 397 |
+ if CommandUtils.is_vmware_virtualization(): |
|
| 396 | 398 |
linux_selector = LinuxSelector(maxy, maxx, install_config) |
| 397 | 399 |
items.append((linux_selector.display, True)) |
| 398 | 400 |
items.append((hostname_reader.get_user_string, True)) |
| ... | ... |
@@ -405,29 +237,3 @@ class IsoConfig(object): |
| 405 | 405 |
|
| 406 | 406 |
return items |
| 407 | 407 |
|
| 408 |
- def _add_default(self, install_config): |
|
| 409 |
- """ |
|
| 410 |
- Add default install_config settings if not specified |
|
| 411 |
- """ |
|
| 412 |
- # 'bootmode' mode |
|
| 413 |
- if 'bootmode' not in install_config: |
|
| 414 |
- arch = subprocess.check_output(['uname', '-m'], universal_newlines=True) |
|
| 415 |
- if "x86_64" in arch: |
|
| 416 |
- install_config['bootmode'] = 'dualboot' |
|
| 417 |
- else: |
|
| 418 |
- install_config['bootmode'] = 'efi' |
|
| 419 |
- |
|
| 420 |
- # define 'hostname' as 'photon-<RANDOM STRING>' |
|
| 421 |
- if "hostname" not in install_config or install_config['hostname'] == "": |
|
| 422 |
- install_config['hostname'] = "photon-" + self.random_id.strip() |
|
| 423 |
- |
|
| 424 |
- def _check_install_config(self, install_config): |
|
| 425 |
- """ |
|
| 426 |
- Sanity check of install_config before its execution. |
|
| 427 |
- Return error string or None |
|
| 428 |
- """ |
|
| 429 |
- if not 'disk' in install_config: |
|
| 430 |
- return "No disk configured" |
|
| 431 |
- |
|
| 432 |
- return None |
|
| 433 |
- |
| 434 | 408 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,97 @@ |
| 0 |
+Kickstart config file is a json format with following possible parameters: |
|
| 1 |
+ |
|
| 2 |
+'additional_packages' same as 'packages' |
|
| 3 |
+ |
|
| 4 |
+'bootmode' (optional) |
|
| 5 |
+ Sets the boot type to suppot: EFI, BIOS or both. |
|
| 6 |
+ Acceptible values are: "bios", "efi", "dualboot" |
|
| 7 |
+ "bios" will add special partition (very first) for first stage grub. |
|
| 8 |
+ "efi" will add ESP (Efi Special Partition), format is as FAT and copy |
|
| 9 |
+ there EFI binaries including grub.efi |
|
| 10 |
+ "dualboot" will add two extra partitions for "bios" and "efi" modes. |
|
| 11 |
+ This target will support both modes that can be switched in bios |
|
| 12 |
+ settings without extra actions in the OS. |
|
| 13 |
+ Default value: 'dualboot' for x86_64 and 'efi' for aarch64 |
|
| 14 |
+ Example: { 'bootmode': 'bios' }
|
|
| 15 |
+ |
|
| 16 |
+'disk' (required) |
|
| 17 |
+ Target's disk device file path to install into, such as '/dev/sda'. |
|
| 18 |
+ Loop device is also supported. |
|
| 19 |
+ Example: { 'disk': '/dev/sdb' }
|
|
| 20 |
+ |
|
| 21 |
+'eject_cdrom' (optional) |
|
| 22 |
+ Eject or not cdrom after installation completed. |
|
| 23 |
+ Boolean: True or False |
|
| 24 |
+ Default value: True |
|
| 25 |
+ Example: { 'eject_cdrom': False }
|
|
| 26 |
+ |
|
| 27 |
+'hostname' (optional) |
|
| 28 |
+ Set target host name. |
|
| 29 |
+ Default value: 'photon-<randomized string>' |
|
| 30 |
+ Example: { 'hostname': 'photon-machine' }
|
|
| 31 |
+ |
|
| 32 |
+'packagelist_file' (optional if 'packages' set) |
|
| 33 |
+ Contains file name which has list of packages to install. |
|
| 34 |
+ Example: { 'packagelist_file': 'packages_minimal.json' }
|
|
| 35 |
+ |
|
| 36 |
+'packages' (optional if 'packagelist_file' set) |
|
| 37 |
+ Contains list of packages to install. |
|
| 38 |
+ Example: { 'packages': ['minimal', 'linux', 'initramfs'] }
|
|
| 39 |
+ |
|
| 40 |
+'partition_type' (optional) |
|
| 41 |
+ Set partition table type. Supported values are: "gpt", "msdos". |
|
| 42 |
+ Default value: 'gpt' |
|
| 43 |
+ Example: { 'partition_type': 'msdos' }
|
|
| 44 |
+ |
|
| 45 |
+'partitions' (optional) |
|
| 46 |
+ Contains list of partitions to create. |
|
| 47 |
+ Each partition is a dictionary of the following items: |
|
| 48 |
+ 'filesystem' (required) |
|
| 49 |
+ Filesystem type. Supported values are: 'swap', 'ext4', 'fat'. |
|
| 50 |
+ 'mountpoint' (required for non 'swap' partitions) |
|
| 51 |
+ Mount point for the partition. |
|
| 52 |
+ 'size' (required) |
|
| 53 |
+ Size of the partition in MB. If 0 then partition is considered |
|
| 54 |
+ as expansible to fill rest of the disk. Only one expansible |
|
| 55 |
+ partition is allowed. |
|
| 56 |
+ 'fs_options' (optional) |
|
| 57 |
+ Additional parameters for the mkfs command as a string |
|
| 58 |
+ Default value: [{"mountpoint": "/", "size": 0, "filesystem": "ext4"}]
|
|
| 59 |
+ Example: { 'partitions' : [
|
|
| 60 |
+ {"mountpoint": "/", "size": 0, "filesystem": "ext4"},
|
|
| 61 |
+ {
|
|
| 62 |
+ "mountpoint": "/boot/esp", |
|
| 63 |
+ "size": 12, |
|
| 64 |
+ "filesystem": "fat", |
|
| 65 |
+ "fs_options": "-n EFI" |
|
| 66 |
+ }, |
|
| 67 |
+ {"size": 128, "filesystem": "swap"} ] }
|
|
| 68 |
+ |
|
| 69 |
+'password' (optional) |
|
| 70 |
+ Set root password. It is dictionary of two items: |
|
| 71 |
+ 'text' (required) password plain text ('crypted' : false)
|
|
| 72 |
+ of encrypted ('crypted': true)
|
|
| 73 |
+ 'crypted' (required) how to interpret 'text' content |
|
| 74 |
+ Default value: { "crypted": true, "text": "*"} }
|
|
| 75 |
+ which means root is not allowed to login. |
|
| 76 |
+ Example: { "password": {
|
|
| 77 |
+ "crypted": false, |
|
| 78 |
+ "text": "changeme"} } |
|
| 79 |
+ |
|
| 80 |
+'postinstall' (optional) |
|
| 81 |
+ Contains list of lines to be executed as a single script on |
|
| 82 |
+ the target after installation. |
|
| 83 |
+ Example: { "postinstall": [
|
|
| 84 |
+ "#!/bin/sh", |
|
| 85 |
+ "echo \"Hello World\" > /etc/postinstall" ] } |
|
| 86 |
+ |
|
| 87 |
+'public_key' (optional) |
|
| 88 |
+ To inject entry to authorized_keys as a string |
|
| 89 |
+ |
|
| 90 |
+'shadow_password' (optional) |
|
| 91 |
+ Contains encrypted root password <encrypted password here>. |
|
| 92 |
+ Short form of: { "password": {
|
|
| 93 |
+ "crypted": true, |
|
| 94 |
+ "text": "<encrypted password here>"} } |
|
| 95 |
+ |
|
| 96 |
+For reference, look at 'sample_ks.cfg' file |
| ... | ... |
@@ -8,7 +8,7 @@ install_phase = commons.POST_INSTALL |
| 8 | 8 |
enabled = True |
| 9 | 9 |
|
| 10 | 10 |
def execute(installer): |
| 11 |
- shadow_password = installer.install_config['password'] |
|
| 11 |
+ shadow_password = installer.install_config['shadow_password'] |
|
| 12 | 12 |
installer.logger.info("Set root password")
|
| 13 | 13 |
|
| 14 | 14 |
passwd_filename = os.path.join(installer.photon_root, 'etc/passwd') |
| ... | ... |
@@ -14,8 +14,9 @@ from actionresult import ActionResult |
| 14 | 14 |
|
| 15 | 15 |
class OstreeInstaller(Installer): |
| 16 | 16 |
|
| 17 |
- def __init__(self, install_config, maxy = 0, maxx = 0, iso_installer = False, rpm_path = "../stage/RPMS", log_path = "../stage/LOGS", log_level = "info"): |
|
| 17 |
+ def __init__(self, install_config, maxy = 0, maxx = 0, iso_installer = False, interactive = False, rpm_path = "../stage/RPMS", log_path = "../stage/LOGS", log_level = "info"): |
|
| 18 | 18 |
Installer.__init__(self, install_config, maxy, maxx, iso_installer, rpm_path, log_path, log_level) |
| 19 |
+ self.interactive = interactive |
|
| 19 | 20 |
self.repo_config = {}
|
| 20 | 21 |
self.repo_read_conf() |
| 21 | 22 |
|
| ... | ... |
@@ -192,7 +193,7 @@ class OstreeInstaller(Installer): |
| 192 | 192 |
self.progress_bar.update_loading_message("Ready to restart")
|
| 193 | 193 |
self.progress_bar.hide() |
| 194 | 194 |
self.window.addstr(0, 0, 'Congratulations, Photon RPM-OSTree Host has been installed in {0} secs.\n\nPress any key to continue to boot...'.format(self.progress_bar.time_elapsed))
|
| 195 |
- if 'ui_install' in self.install_config: |
|
| 195 |
+ if self.interactive: |
|
| 196 | 196 |
self.window.content_window().getch() |
| 197 | 197 |
return ActionResult(True, None) |
| 198 | 198 |
|
| ... | ... |
@@ -39,13 +39,6 @@ class PackageSelector(object): |
| 39 | 39 |
else: |
| 40 | 40 |
raise Exception("Install option '" + option['title'] + "' must have 'packagelist_file' or 'packages' property")
|
| 41 | 41 |
|
| 42 |
- @staticmethod |
|
| 43 |
- def get_additional_files_to_copy_in_iso(install_option, base_path): |
|
| 44 |
- additional_files = [] |
|
| 45 |
- if "additional-files" in install_option[1]: |
|
| 46 |
- additional_files = install_option[1]["additional-files"] |
|
| 47 |
- return additional_files |
|
| 48 |
- |
|
| 49 | 42 |
def load_package_list(self, options_file): |
| 50 | 43 |
json_wrapper_option_list = JsonWrapper(options_file) |
| 51 | 44 |
option_list_json = json_wrapper_option_list.read() |
| ... | ... |
@@ -61,12 +54,9 @@ class PackageSelector(object): |
| 61 | 61 |
if install_option[1]["visible"] == True: |
| 62 | 62 |
package_list = PackageSelector.get_packages_to_install(install_option[1], |
| 63 | 63 |
base_path) |
| 64 |
- additional_files = PackageSelector.get_additional_files_to_copy_in_iso( |
|
| 65 |
- install_option, base_path) |
|
| 66 | 64 |
self.package_menu_items.append((install_option[1]["title"], |
| 67 | 65 |
self.exit_function, |
| 68 |
- [install_option[0], |
|
| 69 |
- package_list, additional_files])) |
|
| 66 |
+ [install_option[0], package_list])) |
|
| 70 | 67 |
if install_option[0] == 'minimal': |
| 71 | 68 |
default_selected = visible_options_cnt |
| 72 | 69 |
visible_options_cnt = visible_options_cnt + 1 |
| ... | ... |
@@ -78,7 +68,6 @@ class PackageSelector(object): |
| 78 | 78 |
def exit_function(self, selected_item_params): |
| 79 | 79 |
self.install_config['type'] = selected_item_params[0] |
| 80 | 80 |
self.install_config['packages'] = selected_item_params[1] |
| 81 |
- self.install_config['additional-files'] = selected_item_params[2] |
|
| 82 | 81 |
return ActionResult(True, {'custom': False})
|
| 83 | 82 |
|
| 84 | 83 |
def custom_packages(self): |
| ... | ... |
@@ -1,14 +1,10 @@ |
| 1 | 1 |
{
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "hostname": "photon-machine", |
|
| 4 |
+ "packagelist_file": "packages_ami.json" |
|
| 5 |
+ }, |
|
| 2 | 6 |
"image_type": "ami", |
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": false, |
|
| 7 |
- "text": "PASSWORD" |
|
| 8 |
- }, |
|
| 9 |
- "packagelist_file": "packages_ami.json", |
|
| 10 | 7 |
"size": 8192, |
| 11 |
- "public_key":"<ssh-key-here>", |
|
| 12 | 8 |
"postinstallscripts": [ "ami-patch.sh", "../password-expiry.sh" ], |
| 13 | 9 |
"additionalfiles": [ |
| 14 | 10 |
{"cloud-photon.cfg": "/etc/cloud/cloud.cfg"}
|
| ... | ... |
@@ -1,14 +1,10 @@ |
| 1 | 1 |
{
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "hostname": "photon-machine", |
|
| 4 |
+ "packagelist_file": "packages_azure.json" |
|
| 5 |
+ }, |
|
| 2 | 6 |
"image_type": "azure", |
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": false, |
|
| 7 |
- "text": "PASSWORD" |
|
| 8 |
- }, |
|
| 9 |
- "packagelist_file": "packages_azure.json", |
|
| 10 | 7 |
"size": 16384, |
| 11 |
- "public_key":"<ssh-key-here>", |
|
| 12 | 8 |
"postinstallscripts": [ "azure-patch.sh", "../password-expiry.sh" ], |
| 13 | 9 |
"additionalfiles": [ |
| 14 | 10 |
{"cloud-photon.cfg": "/etc/cloud/cloud.cfg"}
|
| ... | ... |
@@ -1,15 +1,11 @@ |
| 1 | 1 |
{
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "bootmode":"bios", |
|
| 4 |
+ "hostname": "photon-machine", |
|
| 5 |
+ "packagelist_file": "packages_gce.json" |
|
| 6 |
+ }, |
|
| 2 | 7 |
"image_type": "gce", |
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": false, |
|
| 7 |
- "text": "PASSWORD" |
|
| 8 |
- }, |
|
| 9 |
- "packagelist_file": "packages_gce.json", |
|
| 10 | 8 |
"size": 16284, |
| 11 |
- "bootmode":"bios", |
|
| 12 |
- "public_key":"<ssh-key-here>", |
|
| 13 | 9 |
"postinstallscripts": [ "gce-patch.sh", "../password-expiry.sh" ], |
| 14 | 10 |
"additionalfiles": [ |
| 15 | 11 |
{"cloud-photon.cfg": "/etc/cloud/cloud.cfg"},
|
| ... | ... |
@@ -12,30 +12,18 @@ import subprocess |
| 12 | 12 |
from argparse import ArgumentParser |
| 13 | 13 |
import imagegenerator |
| 14 | 14 |
|
| 15 |
-def runInstaller(options, config): |
|
| 15 |
+def runInstaller(options, install_config, working_directory): |
|
| 16 | 16 |
try: |
| 17 | 17 |
sys.path.insert(0, options.installer_path) |
| 18 | 18 |
from installer import Installer |
| 19 |
- from packageselector import PackageSelector |
|
| 20 | 19 |
except: |
| 21 | 20 |
raise ImportError('Installer path incorrect!')
|
| 22 | 21 |
|
| 23 |
- # Check the installation type |
|
| 24 |
- option_list_json = Utils.jsonread(options.package_list_file) |
|
| 25 |
- options_sorted = option_list_json.items() |
|
| 26 |
- |
|
| 27 |
- packages = [] |
|
| 28 |
- if 'packagelist_file' in config: |
|
| 29 |
- packages = PackageSelector.get_packages_to_install(config, |
|
| 30 |
- options.generated_data_path) |
|
| 31 |
- if 'additional_packages' in config: |
|
| 32 |
- packages = packages.extend(config['additional_packages']) |
|
| 33 |
- |
|
| 34 |
- config['packages'] = packages |
|
| 35 | 22 |
# Run the installer |
| 36 |
- package_installer = Installer(config, rpm_path=options.rpm_path, |
|
| 37 |
- log_path=options.log_path, log_level=options.log_level) |
|
| 38 |
- return package_installer.install() |
|
| 23 |
+ installer = Installer(working_directory = working_directory, rpm_path=options.rpm_path, |
|
| 24 |
+ log_path=options.log_path, log_level=options.log_level) |
|
| 25 |
+ installer.configure(install_config) |
|
| 26 |
+ return installer.execute() |
|
| 39 | 27 |
|
| 40 | 28 |
def get_file_name_with_last_folder(filename): |
| 41 | 29 |
basename = os.path.basename(filename) |
| ... | ... |
@@ -159,20 +147,6 @@ def createIso(options): |
| 159 | 159 |
if os.path.exists(working_directory) and os.path.isdir(working_directory): |
| 160 | 160 |
shutil.rmtree(working_directory) |
| 161 | 161 |
|
| 162 |
-def cryptPassword(config, passwordtext): |
|
| 163 |
- config['passwordtext'] = passwordtext |
|
| 164 |
- crypted = config['password']['crypted'] |
|
| 165 |
- if config['password']['text'] == 'PASSWORD': |
|
| 166 |
- config['password'] = "".join([random.SystemRandom().choice( |
|
| 167 |
- string.ascii_letters + string.digits) for _ in range(16)]) |
|
| 168 |
- if crypted: |
|
| 169 |
- config['password'] = crypt.crypt( |
|
| 170 |
- config['password'], |
|
| 171 |
- "$6$" + "".join([random.SystemRandom().choice( |
|
| 172 |
- string.ascii_letters + string.digits) for _ in range(16)])) |
|
| 173 |
- else: |
|
| 174 |
- config['password'] = crypt.crypt(passwordtext, '$6$saltsalt$') |
|
| 175 |
- |
|
| 176 | 162 |
def replaceScript(script_dir, img, script_name, parent_script_dir=None): |
| 177 | 163 |
if not parent_script_dir: |
| 178 | 164 |
parent_script_dir = script_dir |
| ... | ... |
@@ -222,17 +196,17 @@ def createImage(options): |
| 222 | 222 |
if 'ova' in config['artifacttype'] and shutil.which("ovftool") is None:
|
| 223 | 223 |
raise Exception("ovftool is not available")
|
| 224 | 224 |
|
| 225 |
+ install_config = config['installer'] |
|
| 226 |
+ |
|
| 225 | 227 |
image_type = config['image_type'] |
| 226 | 228 |
workingDir = os.path.abspath(options.stage_path + "/" + image_type) |
| 227 | 229 |
if os.path.exists(workingDir) and os.path.isdir(workingDir): |
| 228 | 230 |
shutil.rmtree(workingDir) |
| 229 | 231 |
os.mkdir(workingDir) |
| 230 |
- if 'password' in config: |
|
| 231 |
- cryptPassword(config, config['password']['text']) |
|
| 232 | 232 |
script_dir = os.path.dirname(os.path.realpath(__file__)) |
| 233 | 233 |
|
| 234 | 234 |
grub_script = replaceScript(script_dir, image_type, "mk-setup-grub.sh", options.installer_path) |
| 235 |
- config['setup_grub_script'] = grub_script |
|
| 235 |
+ install_config['setup_grub_script'] = grub_script |
|
| 236 | 236 |
|
| 237 | 237 |
if options.additional_rpms_path: |
| 238 | 238 |
os.mkdir(options.rpm_path + '/additional') |
| ... | ... |
@@ -241,6 +215,13 @@ def createImage(options): |
| 241 | 241 |
d = os.path.join(options.rpm_path + '/additional', item) |
| 242 | 242 |
shutil.copy2(s, d) |
| 243 | 243 |
|
| 244 |
+ # Set absolute path for 'packagelist_file' |
|
| 245 |
+ if 'packagelist_file' in install_config: |
|
| 246 |
+ plf = install_config['packagelist_file'] |
|
| 247 |
+ if not plf.startswith('/'):
|
|
| 248 |
+ plf = os.path.join(options.generated_data_path, plf) |
|
| 249 |
+ install_config['packagelist_file'] = plf |
|
| 250 |
+ |
|
| 244 | 251 |
os.chdir(workingDir) |
| 245 | 252 |
image_file = workingDir + "/photon-" + image_type + ".raw" |
| 246 | 253 |
|
| ... | ... |
@@ -251,15 +232,15 @@ def createImage(options): |
| 251 | 251 |
"chmod 755 {}".format(image_file))
|
| 252 | 252 |
|
| 253 | 253 |
# Associating loopdevice to raw disk and save the name as a target's 'disk' |
| 254 |
- config['disk'] = (Utils.runshellcommand( |
|
| 254 |
+ install_config['disk'] = (Utils.runshellcommand( |
|
| 255 | 255 |
"losetup --show -f {}".format(image_file))).rstrip('\n')
|
| 256 | 256 |
|
| 257 |
- result = runInstaller(options, config) |
|
| 257 |
+ result = runInstaller(options, install_config, workingDir) |
|
| 258 | 258 |
if not result: |
| 259 | 259 |
raise Exception("Installation process failed")
|
| 260 | 260 |
|
| 261 | 261 |
# Detaching loop device from vmdk |
| 262 |
- Utils.runshellcommand("losetup -d {}".format(config['disk']))
|
|
| 262 |
+ Utils.runshellcommand("losetup -d {}".format(install_config['disk']))
|
|
| 263 | 263 |
|
| 264 | 264 |
os.chdir(script_dir) |
| 265 | 265 |
imagegenerator.generateImage( |
| ... | ... |
@@ -176,8 +176,9 @@ def generateImage(raw_image_path, additional_rpms_path, tools_bin_path, src_root |
| 176 | 176 |
working_directory = os.path.dirname(raw_image_path) |
| 177 | 177 |
mount_path = os.path.splitext(raw_image_path)[0] |
| 178 | 178 |
build_scripts_path = os.path.dirname(os.path.abspath(__file__)) |
| 179 |
+ # TODO: remove 'bootmode' -> partition_no hack |
|
| 179 | 180 |
root_partition_no = 2 |
| 180 |
- if 'bootmode' in config and config['bootmode'] == 'dualboot': |
|
| 181 |
+ if config['installer'].get('bootmode', '') == 'dualboot':
|
|
| 181 | 182 |
root_partition_no = 3 |
| 182 | 183 |
|
| 183 | 184 |
if os.path.exists(mount_path) and os.path.isdir(mount_path): |
| ... | ... |
@@ -196,10 +197,6 @@ def generateImage(raw_image_path, additional_rpms_path, tools_bin_path, src_root |
| 196 | 196 |
(uuidval, partuuidval) = generateUuid(loop_device_path) |
| 197 | 197 |
# Prep the loop device |
| 198 | 198 |
prepLoopDevice(loop_device_path, mount_path) |
| 199 |
- # Clear the root password if not set explicitly from the config file |
|
| 200 |
- if config['passwordtext'] == 'PASSWORD': |
|
| 201 |
- Utils.replaceinfile(mount_path + "/etc/shadow", |
|
| 202 |
- 'root:.*?:', 'root:*:') |
|
| 203 | 199 |
# Clear machine-id so it gets regenerated on boot |
| 204 | 200 |
open(mount_path + "/etc/machine-id", "w").close() |
| 205 | 201 |
# Write fstab |
| ... | ... |
@@ -1,20 +1,16 @@ |
| 1 | 1 |
{
|
| 2 |
- "image_type": "ls1012afrwy", |
|
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": false, |
|
| 7 |
- "text": "PASSWORD" |
|
| 8 |
- }, |
|
| 9 |
- "partition_type": "msdos", |
|
| 10 |
- "partitions": [ |
|
| 11 |
- {"mountpoint": "/boot/esp", "size": 12, "filesystem": "fat", "fs_options": "-n EFI"},
|
|
| 12 |
- {"mountpoint": "/", "size": 0, "filesystem": "ext4", "fs_options": "-F -O ^huge_file -b 4096 -L rootfs"}
|
|
| 13 |
- ], |
|
| 14 |
- "packagelist_file": "packages_ls1012afrwy.json", |
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "bootmode":"efi", |
|
| 4 |
+ "hostname": "photon-machine", |
|
| 5 |
+ "packagelist_file": "packages_ls1012afrwy.json", |
|
| 6 |
+ "partition_type": "msdos", |
|
| 7 |
+ "partitions": [ |
|
| 8 |
+ {"mountpoint": "/boot/esp", "size": 12, "filesystem": "fat", "fs_options": "-n EFI"},
|
|
| 9 |
+ {"mountpoint": "/", "size": 0, "filesystem": "ext4", "fs_options": "-F -O ^huge_file -b 4096 -L rootfs"}
|
|
| 10 |
+ ] |
|
| 11 |
+ }, |
|
| 12 |
+ "image_type": "ls1012afrwy", |
|
| 15 | 13 |
"size": 512, |
| 16 |
- "bootmode":"efi", |
|
| 17 |
- "public_key":"<ssh-key-here>", |
|
| 18 | 14 |
"postinstallscripts": ["ls1012afrwy-custom-patch.sh"], |
| 19 | 15 |
"additionalfiles": [ |
| 20 | 16 |
{"resizefs.sh": "/usr/local/bin/resizefs.sh"},
|
| ... | ... |
@@ -1,15 +1,15 @@ |
| 1 | 1 |
{
|
| 2 |
- "image_type": "ova", |
|
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": true, |
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "bootmode":"dualboot", |
|
| 4 |
+ "hostname": "photon-machine", |
|
| 5 |
+ "packagelist_file": "packages_ova.json", |
|
| 6 |
+ "password": {
|
|
| 7 |
+ "crypted": false, |
|
| 7 | 8 |
"text": "changeme" |
| 8 |
- }, |
|
| 9 |
- "packagelist_file": "packages_ova.json", |
|
| 9 |
+ } |
|
| 10 |
+ }, |
|
| 11 |
+ "image_type": "ova", |
|
| 10 | 12 |
"size": 16384, |
| 11 |
- "bootmode":"dualboot", |
|
| 12 |
- "public_key":"<ssh-key-here>", |
|
| 13 | 13 |
"expirepassword": true, |
| 14 | 14 |
"artifacttype": "ova", |
| 15 | 15 |
"keeprawdisk": false |
| ... | ... |
@@ -1,15 +1,15 @@ |
| 1 | 1 |
{
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "bootmode":"efi", |
|
| 4 |
+ "hostname": "photon-machine", |
|
| 5 |
+ "password": {
|
|
| 6 |
+ "crypted": false, |
|
| 7 |
+ "text": "changeme" |
|
| 8 |
+ }, |
|
| 9 |
+ "packagelist_file": "packages_ova.json" |
|
| 10 |
+ }, |
|
| 2 | 11 |
"image_type": "ova_uefi", |
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": true, |
|
| 7 |
- "text": "changeme" |
|
| 8 |
- }, |
|
| 9 |
- "packagelist_file": "packages_ova.json", |
|
| 10 | 12 |
"size": 16384, |
| 11 |
- "bootmode":"efi", |
|
| 12 |
- "public_key":"<ssh-key-here>", |
|
| 13 | 13 |
"expirepassword": true, |
| 14 | 14 |
"artifacttype": "ova", |
| 15 | 15 |
"keeprawdisk": false |
| ... | ... |
@@ -1,20 +1,16 @@ |
| 1 | 1 |
{
|
| 2 |
- "image_type": "rpi3", |
|
| 3 |
- "hostname": "photon-machine", |
|
| 4 |
- "password": |
|
| 5 |
- {
|
|
| 6 |
- "crypted": false, |
|
| 7 |
- "text": "PASSWORD" |
|
| 8 |
- }, |
|
| 9 |
- "packagelist_file": "packages_rpi3.json", |
|
| 10 |
- "partition_type": "msdos", |
|
| 11 |
- "partitions": [ |
|
| 12 |
- {"mountpoint": "/boot/esp", "size": 30, "filesystem": "fat"},
|
|
| 13 |
- {"mountpoint": "/", "size": 0, "filesystem": "ext4"}
|
|
| 14 |
- ], |
|
| 2 |
+ "installer": {
|
|
| 3 |
+ "bootmode":"efi", |
|
| 4 |
+ "hostname": "photon-machine", |
|
| 5 |
+ "packagelist_file": "packages_rpi3.json", |
|
| 6 |
+ "partition_type": "msdos", |
|
| 7 |
+ "partitions": [ |
|
| 8 |
+ {"mountpoint": "/boot/esp", "size": 30, "filesystem": "fat"},
|
|
| 9 |
+ {"mountpoint": "/", "size": 0, "filesystem": "ext4"}
|
|
| 10 |
+ ] |
|
| 11 |
+ }, |
|
| 12 |
+ "image_type": "rpi3", |
|
| 15 | 13 |
"size": 512, |
| 16 |
- "bootmode":"efi", |
|
| 17 |
- "public_key":"<ssh-key-here>", |
|
| 18 | 14 |
"postinstallscripts": ["rpi3-custom-patch.sh"], |
| 19 | 15 |
"additionalfiles": [ |
| 20 | 16 |
{"resizefs.sh": "/usr/local/bin/resizefs.sh"},
|