diff --git a/configs/pxe/fstab.11 b/configs/pxe/fstab.11 index e51246c..fba995d 100644 --- a/configs/pxe/fstab.11 +++ b/configs/pxe/fstab.11 @@ -1,3 +1,3 @@ # Device Mountpoint FStype Options Dump Pass# -192.168.0.10:/mnt/ship/pxe/FreeBSD11 / nfs rw 0 0 +192.168.10.10:/mnt/ship/pxe/FreeBSD11 / nfs rw 0 0 fdescfs /dev/fd fdescfs rw 0 0 diff --git a/configs/pxe/fstab.12 b/configs/pxe/fstab.12 index b9cdc6b..7f76caf 100644 --- a/configs/pxe/fstab.12 +++ b/configs/pxe/fstab.12 @@ -1,3 +1,3 @@ # Device Mountpoint FStype Options Dump Pass# -192.168.0.10:/mnt/ship/pxe/FreeBSD12 / nfs rw 0 0 +192.168.10.10:/mnt/ship/pxe/FreeBSD12 / nfs rw 0 0 fdescfs /dev/fd fdescfs rw 0 0 diff --git a/configs/pxe/fstab.12p b/configs/pxe/fstab.12p index 11c44dd..a2c3e0e 100644 --- a/configs/pxe/fstab.12p +++ b/configs/pxe/fstab.12p @@ -1,3 +1,3 @@ # Device Mountpoint FStype Options Dump Pass# -192.168.0.10:/mnt/ship/pxe/FreeBSD12p / nfs rw 0 0 +192.168.10.10:/mnt/ship/pxe/FreeBSD12p / nfs rw 0 0 fdescfs /dev/fd fdescfs rw 0 0 diff --git a/configs/pxe/fstab.13 b/configs/pxe/fstab.13 index 6ffff01..aa89fde 100644 --- a/configs/pxe/fstab.13 +++ b/configs/pxe/fstab.13 @@ -1,3 +1,3 @@ # Device Mountpoint FStype Options Dump Pass# -192.168.0.10:/mnt/ship/pxe/FreeBSD13 / nfs rw 0 0 +192.168.10.10:/mnt/ship/pxe/FreeBSD13 / nfs rw 0 0 fdescfs /dev/fd fdescfs rw 0 0 diff --git a/configs/pxe/fstab.13p b/configs/pxe/fstab.13p index 41fef92..780a4e6 100644 --- a/configs/pxe/fstab.13p +++ b/configs/pxe/fstab.13p @@ -1,3 +1,3 @@ # Device Mountpoint FStype Options Dump Pass# -192.168.0.10:/mnt/ship/pxe/FreeBSD13p / nfs rw 0 0 +192.168.10.10:/mnt/ship/pxe/FreeBSD13p / nfs rw 0 0 fdescfs /dev/fd fdescfs rw 0 0 diff --git a/configs/pxe/iscsi.conf.11 b/configs/pxe/iscsi.conf.11 index a2b9102..fc3b65c 100644 --- a/configs/pxe/iscsi.conf.11 +++ b/configs/pxe/iscsi.conf.11 @@ -1,5 +1,5 @@ t0 { - TargetAddress = 192.168.0.10 + TargetAddress = 192.168.10.10 TargetName = iqn.nas.ahlawat.com:f11 AuthMethod = CHAP chapIName = user diff --git a/configs/pxe/iscsi.conf.12 b/configs/pxe/iscsi.conf.12 index 1afdda5..56bca89 100644 --- a/configs/pxe/iscsi.conf.12 +++ b/configs/pxe/iscsi.conf.12 @@ -1,5 +1,5 @@ t0 { - TargetAddress = 192.168.0.10 + TargetAddress = 192.168.10.10 TargetName = iqn.nas.ahlawat.com:f12 AuthMethod = CHAP chapIName = user diff --git a/configs/pxe/iscsi.conf.12p b/configs/pxe/iscsi.conf.12p index 2e666d1..3c10213 100644 --- a/configs/pxe/iscsi.conf.12p +++ b/configs/pxe/iscsi.conf.12p @@ -1,5 +1,5 @@ t0 { - TargetAddress = 192.168.0.10 + TargetAddress = 192.168.10.10 TargetName = iqn.nas.ahlawat.com:f12p AuthMethod = CHAP chapIName = user diff --git a/configs/pxe/iscsi.conf.13 b/configs/pxe/iscsi.conf.13 index f4daee9..0ba4dc5 100644 --- a/configs/pxe/iscsi.conf.13 +++ b/configs/pxe/iscsi.conf.13 @@ -1,5 +1,5 @@ t0 { - TargetAddress = 192.168.0.10 + TargetAddress = 192.168.10.10 TargetName = iqn.nas.ahlawat.com:f13 AuthMethod = CHAP chapIName = user diff --git a/configs/pxe/iscsi.conf.13p b/configs/pxe/iscsi.conf.13p index f39b03f..bb7e42f 100644 --- a/configs/pxe/iscsi.conf.13p +++ b/configs/pxe/iscsi.conf.13p @@ -1,5 +1,5 @@ t0 { - TargetAddress = 192.168.0.10 + TargetAddress = 192.168.10.10 TargetName = iqn.nas.ahlawat.com:f13p AuthMethod = CHAP chapIName = user diff --git a/configs/pxe/resolv.conf b/configs/pxe/resolv.conf index 2bc218d..d90441c 100644 --- a/configs/pxe/resolv.conf +++ b/configs/pxe/resolv.conf @@ -1,6 +1,6 @@ # Generated by resolvconf search diyit.org -nameserver 192.168.0.5 +nameserver 192.168.10.5 nameserver fd01::5 nameserver fd02::5 nameserver fd05::5 diff --git a/iocage/Makefile b/iocage/Makefile new file mode 100644 index 0000000..0dc9d94 --- /dev/null +++ b/iocage/Makefile @@ -0,0 +1,27 @@ +ZPOOL="" +SERVER="" +PYTHON?=/usr/local/bin/python3.7 +# pkg install python37 git-lite +# pip19 required for iocage files install + +depends: + @(pkg -vv | grep -e "url.*/latest") > /dev/null 2>&1 || (echo "It is advised pkg url is using \"latest\" instead of \"quarterly\" in /etc/pkg/FreeBSD.conf.";) + @test -s ${PYTHON} || (echo "Python binary ${PYTHON} not found, iocage will install python37"; pkg install -q -y python37) + pkg install -q -y py37-cython py37-pip py37-libzfs py37-six + ${PYTHON} -m pip install pip==19.3.1 + ${PYTHON} -m ensurepip + ${PYTHON} -m pip install -Ur requirements.txt + +install: depends + ${PYTHON} -m pip install -U . +uninstall: + ${PYTHON} -m pip uninstall -y iocage-lib iocage-cli +test: + pytest --zpool $(ZPOOL) --server $(SERVER) +help: + @echo " install" + @echo " Installs iocage" + @echo " uninstall" + @echo " Removes iocage" + @echo " test" + @echo " Run unit tests with pytest" diff --git a/iocage/iocage_lib/ioc_fetch.py b/iocage/iocage_lib/ioc_fetch.py new file mode 100644 index 0000000..e05a763 --- /dev/null +++ b/iocage/iocage_lib/ioc_fetch.py @@ -0,0 +1,1021 @@ +# Copyright (c) 2014-2019, iocage +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted providing that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +"""iocage fetch module.""" +import hashlib +import logging +import os +import shutil +import subprocess as su +import tarfile +import tempfile +import time +import urllib.request + +import requests +import requests.auth +import requests.packages.urllib3.exceptions + +import iocage_lib.ioc_common +import iocage_lib.ioc_destroy +import iocage_lib.ioc_exceptions +import iocage_lib.ioc_exec +import iocage_lib.ioc_json +import iocage_lib.ioc_start + + +from iocage_lib.pools import Pool +from iocage_lib.dataset import Dataset + + +class IOCFetch: + + """Fetch a RELEASE for use as a jail base.""" + + def __init__(self, + release, + server="download.freebsd.org", + user="anonymous", + password="anonymous@", + auth=None, + root_dir=None, + http=True, + _file=False, + verify=True, + hardened=False, + update=True, + eol=True, + files=('MANIFEST', 'base.txz', 'lib32.txz'), + silent=False, + callback=None): + self.pool = iocage_lib.ioc_json.IOCJson().json_get_value("pool") + self.iocroot = iocage_lib.ioc_json.IOCJson( + self.pool).json_get_value("iocroot") + self.server = server + self.user = user + self.password = password + self.auth = auth + + if release and (not _file and server == 'download.freebsd.org'): + self.release = release.upper() + else: + self.release = release + + self.root_dir = root_dir + self.arch = os.uname()[4] + self.http = http + self._file = _file + self.verify = verify + self.hardened = hardened + self.files = files + self.files_left = list(files) + self.update = update + self.eol = eol + self.silent = silent + self.callback = callback + self.zpool = Pool(self.pool) + + if hardened: + if release: + self.release = f"{self.release[:2]}-stable".upper() + else: + self.release = release + + if not verify: + # The user likely knows this already. + requests.packages.urllib3.disable_warnings( + requests.packages.urllib3.exceptions.InsecureRequestWarning) + + @staticmethod + def __fetch_eol_check__(): + """Scrapes the FreeBSD website and returns a list of EOL RELEASES""" + logging.getLogger("requests").setLevel(logging.WARNING) + _eol = "https://www.freebsd.org/security/unsupported.html" + req = requests.get(_eol) + status = req.status_code == requests.codes.ok + eol_releases = [] + + if not status: + req.raise_for_status() + + for eol in req.content.decode("iso-8859-1").split(): + eol = eol.strip("href=").strip("/").split(">") + # We want a dynamic EOL + try: + if "-RELEASE" in eol[1]: + eol = eol[1].strip(' 2: + # Quick list validation + try: + releases.index(self.release) + except ValueError: + # Time to print the list again + self.release = self.__fetch_validate_release__(releases) + else: + return self.release + + try: + self.release = releases[int(self.release)] + iocage_lib.ioc_common.check_release_newer( + self.release, self.callback, self.silent, major_only=True) + except IndexError: + # Time to print the list again + self.release = self.__fetch_validate_release__(releases) + except ValueError: + # We want to use their host as RELEASE, but it may + # not be on the mirrors anymore. + try: + if self.release == "": + self.release = iocage_lib.ioc_common.get_host_release() + + if "-STABLE" in self.release: + # Custom HardenedBSD server + self.hardened = True + + return self.release + + releases.index(self.release) + except ValueError: + # Time to print the list again + self.release = self.__fetch_validate_release__(releases) + + return self.release + + def fetch_release(self, _list=False): + """Small wrapper to choose the right fetch.""" + + if self.http and not self._file: + if self.eol and self.verify: + eol = self.__fetch_eol_check__() + else: + eol = [] + + if self.release: + iocage_lib.ioc_common.check_release_newer( + self.release, callback=self.callback, silent=self.silent, + major_only=True, + ) + rel = self.fetch_http_release(eol, _list=_list) + + if _list: + return rel + elif self._file: + # Format for file directory should be: root-dir/RELEASE/*.txz + + if not self.root_dir: + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": "Please supply --root-dir or -d." + }, + _callback=self.callback, + silent=self.silent) + + if self.release is None: + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": "Please supply a RELEASE!" + }, + _callback=self.callback, + silent=self.silent) + + dataset = f"{self.iocroot}/download/{self.release}" + pool_dataset = f"{self.pool}/iocage/download/{self.release}" + + if os.path.isdir(dataset): + pass + else: + self.zpool.create_dataset({ + 'name': pool_dataset, 'properties': {'compression': 'lz4'} + }) + + for f in self.files: + file_path = os.path.join(self.root_dir, self.release, f) + if not os.path.isfile(file_path): + + ds = Dataset(pool_dataset) + ds.destroy(recursive=True, force=True) + + if f == "MANIFEST": + error = f"{f} is a required file!" \ + f"\nPlease place it in {self.root_dir}/" \ + f"{self.release}" + else: + error = f"{f}.txz is a required file!" \ + f"\nPlease place it in {self.root_dir}/" \ + f"{self.release}" + + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": error + }, + _callback=self.callback, + silent=self.silent) + + iocage_lib.ioc_common.logit( + { + "level": "INFO", + "message": f"Copying: {f}... " + }, + _callback=self.callback, + silent=self.silent) + shutil.copy(file_path, dataset) + + if f != "MANIFEST": + iocage_lib.ioc_common.logit( + { + "level": "INFO", + "message": f"Extracting: {f}... " + }, + _callback=self.callback, + silent=self.silent) + self.fetch_extract(f) + + def fetch_http_release(self, eol, _list=False): + """ + Fetch a user specified RELEASE from FreeBSD's http server or a user + supplied one. The user can also specify the user, password and + root-directory containing the release tree that looks like so: + - XX.X-RELEASE + - XX.X-RELEASE + - XX.X-RELEASE + """ + + if self.hardened: + if self.server == "download.freebsd.org": + self.server = "http://jenkins.hardenedbsd.org" + rdir = "builds" + + if self.root_dir is None: + self.root_dir = f"ftp/releases/{self.arch}" + + if self.auth and "https" not in self.server: + self.server = "https://" + self.server + elif "http" not in self.server: + self.server = "http://" + self.server + + logging.getLogger("requests").setLevel(logging.WARNING) + + if self.hardened: + if self.auth == "basic": + req = requests.get( + f"{self.server}/{rdir}", + auth=(self.user, self.password), + verify=self.verify) + elif self.auth == "digest": + req = requests.get( + f"{self.server}/{rdir}", + auth=requests.auth.HTTPDigestAuth(self.user, + self.password), + verify=self.verify) + else: + req = requests.get(f"{self.server}/{rdir}") + + releases = [] + status = req.status_code == requests.codes.ok + + if not status: + req.raise_for_status() + + if not self.release: + for rel in req.content.split(): + rel = rel.decode() + rel = rel.strip("href=").strip("/").split(">") + + if "-STABLE" in rel[0]: + rel = rel[0].strip('"').strip("/").strip( + "HardenedBSD-").rsplit("-") + rel = f"{rel[0]}-{rel[1]}" + + if rel not in releases: + releases.append(rel) + + if len(releases) == 0: + iocage_lib.ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + f"""\ + No RELEASEs were found at {self.server}/{self.root_dir}! + Please ensure the server is correct and the root-dir is + pointing to a top-level directory with the format: + - XX.X-RELEASE + - XX.X-RELEASE + - XX.X-RELEASE + """ + }, + _callback=self.callback, + silent=self.silent) + + releases = iocage_lib.ioc_common.sort_release( + releases, fetch_releases=True) + + self.release = self.__fetch_validate_release__(releases) + else: + if self.auth == "basic": + req = requests.get( + f"{self.server}/{self.root_dir}", + auth=(self.user, self.password), + verify=self.verify) + elif self.auth == "digest": + req = requests.get( + f"{self.server}/{self.root_dir}", + auth=requests.auth.HTTPDigestAuth(self.user, + self.password), + verify=self.verify) + else: + req = requests.get(f"{self.server}/{self.root_dir}") + + releases = [] + status = req.status_code == requests.codes.ok + + if not status: + req.raise_for_status() + + if not self.release: + for rel in req.content.split(): + rel = rel.decode() + rel = rel.strip("href=").strip("/").split(">") + + if "-RELEASE" in rel[0]: + rel = rel[0].strip('"').strip("/").strip("/ 0: + sha256.update(buf) + buf = txz.read(hash_block) + + if hashes[f] != sha256.hexdigest(): + if not _missing: + iocage_lib.ioc_common.logit( + { + "level": + "WARNING", + "message": + f"{f} failed verification," + " will redownload!" + }, + _callback=self.callback, + silent=self.silent) + missing.append(f) + except FileNotFoundError: + if not _missing: + iocage_lib.ioc_common.logit( + { + "level": + "WARNING", + "message": + f"{f} missing, will try to redownload!" + }, + _callback=self.callback, + silent=self.silent) + missing.append(f) + else: + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": "Too many failed verifications!" + }, + _callback=self.callback, + silent=self.silent) + except KeyError: + iocage_lib.ioc_common.logit( + { + 'level': 'WARNING', + 'message': f'{f} missing from MANIFEST,' + ' refusing to extract!' + }, + _callback=self.callback, + silent=self.silent) + if f == 'doc.txz': + # some releases might not have it, + # it is safe to skip + self.files_left.remove(f) + continue + + if not missing and f in _list: + iocage_lib.ioc_common.logit( + { + "level": "INFO", + "message": f"Extracting: {f}... " + }, + _callback=self.callback, + silent=self.silent) + + try: + self.fetch_extract(f) + except Exception: + raise + + if f in self.files_left: + self.files_left.remove(f) + + return missing + + def fetch_download(self, _list, missing=False): + """Creates the download dataset and then downloads the RELEASE.""" + dataset = f"{self.iocroot}/download/{self.release}" + fresh = False + + if not os.path.isdir(dataset): + fresh = True + dataset = f"{self.pool}/iocage/download/{self.release}" + + ds = Dataset(dataset) + if not ds.exists: + ds.create({'properties': {'compression': 'lz4'}}) + if not ds.mounted: + ds.mount() + + if missing or fresh: + release_download_path = os.path.join( + self.iocroot, 'download', self.release + ) + + for f in _list: + if self.hardened: + _file = f"{self.server}/{self.root_dir}/{f}" + + if f == "lib32.txz": + continue + else: + _file = f"{self.server}/{self.root_dir}/" \ + f"{self.release}/{f}" + + if self.auth == "basic": + r = requests.get( + _file, + auth=(self.user, self.password), + verify=self.verify, + stream=True) + elif self.auth == "digest": + r = requests.get( + _file, + auth=requests.auth.HTTPDigestAuth( + self.user, self.password), + verify=self.verify, + stream=True) + else: + r = requests.get( + _file, verify=self.verify, stream=True) + + status = r.status_code == requests.codes.ok + + if not status: + r.raise_for_status() + + with open(os.path.join(release_download_path, f), 'wb') as txz: + file_size = int(r.headers['Content-Length']) + chunk_size = 1024 * 1024 + total = file_size / chunk_size + start = time.time() + dl_progress = 0 + last_progress = 0 + + for i, chunk in enumerate( + r.iter_content(chunk_size=chunk_size), 1 + ): + if chunk: + elapsed = time.time() - start + dl_progress += len(chunk) + txz.write(chunk) + + progress = float(i) / float(total) + if progress >= 1.: + progress = 1 + progress = round(progress * 100, 0) + + if progress != last_progress: + text = self.update_progress( + progress, + f'Downloading: {f}', + elapsed, + chunk_size + ) + + if progress % 10 == 0: + # Not for user output, but for callback + # heartbeats + iocage_lib.ioc_common.logit( + { + 'level': 'INFO', + 'message': text.rstrip() + }, + _callback=self.callback, + silent=True) + + last_progress = progress + start = time.time() + + def update_progress(self, progress, display_text, elapsed, chunk_size): + """ + Displays or updates a console progress bar. + Original source: https://stackoverflow.com/a/15860757/1391441 + """ + barLength, status = 20, "" + + current_time = chunk_size / elapsed + current_time = round(current_time / 1000000, 1) + + block = int(round(barLength * (progress / 100))) + + if progress == 100: + status = "\r\n" + + if self.silent: + return + + text = "\r{} [{}] {:.0f}% {} {}MB/s".format( + display_text, + "#" * block + "-" * (barLength - block), + progress, status, current_time) + + erase = '\x1b[2K' + + print(erase, text, end="\r") + + return text + + def __fetch_check_members__(self, members): + """Checks if the members are relative, if not, log a warning.""" + _members = [] + + for m in members: + if m.name == ".": + continue + + if ".." in m.name: + iocage_lib.ioc_common.logit( + { + "level": "WARNING", + "message": + f"{m.name} is not a relative file, skipping " + }, + _callback=self.callback, + silent=self.silent) + + continue + + _members.append(m) + + return _members + + def fetch_extract(self, f): + """ + Takes a src and dest then creates the RELEASE dataset for the data. + """ + src = f"{self.iocroot}/download/{self.release}/{f}" + dest = f"{self.iocroot}/releases/{self.release}/root" + + dataset = f"{self.pool}/iocage/releases/{self.release}/root" + + if not os.path.isdir(dest): + self.zpool.create_dataset({ + 'name': dataset, 'create_ancestors': True, + 'properties': {'compression': 'lz4'}, + }) + + with tarfile.open(src) as f: + # Extracting over the same files is much slower then + # removing them first. + member = self.__fetch_extract_remove__(f) + member = self.__fetch_check_members__(member) + f.extractall(dest, members=member) + + def fetch_update(self, cli=False, uuid=None): + """This calls 'freebsd-update' to update the fetched RELEASE.""" + iocage_lib.ioc_common.tmp_dataset_checks(self.callback, self.silent) + + if cli: + cmd = [ + "mount", "-t", "devfs", "devfs", + f"{self.iocroot}/jails/{uuid}/root/dev" + ] + mount = f'{self.iocroot}/jails/{uuid}' + mount_root = f'{mount}/root' + + iocage_lib.ioc_common.logit( + { + "level": + "INFO", + "message": + f"\n* Updating {uuid} to the latest patch" + " level... " + }, + _callback=self.callback, + silent=self.silent) + else: + cmd = [ + "mount", "-t", "devfs", "devfs", + f"{self.iocroot}/releases/{self.release}/root/dev" + ] + mount = f'{self.iocroot}/releases/{self.release}' + mount_root = f'{mount}/root' + + iocage_lib.ioc_common.logit( + { + "level": + "INFO", + "message": + f"\n* Updating {self.release} to the latest patch" + " level... " + }, + _callback=self.callback, + silent=self.silent) + + shutil.copy("/etc/resolv.conf", f"{mount_root}/etc/resolv.conf") + + path = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:'\ + '/usr/local/bin:/root/bin' + fetch_env = { + 'UNAME_r': self.release, + 'PAGER': '/bin/cat', + 'PATH': path, + 'PWD': '/', + 'HOME': '/', + 'TERM': 'xterm-256color' + } + + update_path = f'{mount_root}/etc/freebsd-update.conf' + exception_msg = None + if not os.path.exists(update_path) or not os.path.isfile(update_path): + exception_msg = f'{update_path} not found or is not a file.' + else: + with open(update_path, 'r') as f: + contents = f.read() + if 'ServerName' not in contents: + exception_msg = f'ServerName not configured in {update_path}' + + if exception_msg: + iocage_lib.ioc_common.logit( + {'level': 'EXCEPTION', 'message': f'Failed to update: {exception_msg}'} + ) + + su.Popen(cmd).communicate() + if self.verify: + f = "https://raw.githubusercontent.com/freebsd/freebsd-src" \ + "/master/usr.sbin/freebsd-update/freebsd-update.sh" + + tmp = tempfile.NamedTemporaryFile(delete=False) + with urllib.request.urlopen(f) as fbsd_update: + tmp.write(fbsd_update.read()) + tmp.close() + os.chmod(tmp.name, 0o755) + fetch_name = tmp.name + else: + fetch_name = f"{mount_root}/usr/sbin/freebsd-update" + + fetch_cmd = [ + fetch_name, "-b", mount_root, "-d", + f"{mount_root}/var/db/freebsd-update/", "-f", + f"{mount_root}/etc/freebsd-update.conf", + "--not-running-from-cron", "fetch" + ] + with iocage_lib.ioc_exec.IOCExec( + fetch_cmd, + f"{self.iocroot}/jails/{uuid}", + uuid=uuid, + unjailed=True, + callback=self.callback, + su_env=fetch_env + ) as _exec: + try: + iocage_lib.ioc_common.consume_and_log( + _exec, callback=self.callback) + except iocage_lib.ioc_exceptions.CommandFailed as e: + su.Popen(['umount', f'{mount_root}/dev']).communicate() + iocage_lib.ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': b''.join(e.message) + }, + _callback=self.callback, + silent=self.silent + ) + + try: + fetch_install_cmd = [ + fetch_name, "-b", mount_root, "-d", + f"{mount_root}/var/db/freebsd-update/", "-f", + f"{mount_root}/etc/freebsd-update.conf", "install" + ] + with iocage_lib.ioc_exec.IOCExec( + fetch_install_cmd, + f"{self.iocroot}/jails/{uuid}", + uuid=uuid, + unjailed=True, + callback=self.callback, + su_env=fetch_env + ) as _exec: + try: + iocage_lib.ioc_common.consume_and_log( + _exec, callback=self.callback) + except iocage_lib.ioc_exceptions.CommandFailed as e: + iocage_lib.ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': b''.join(e.message) + }, + _callback=self.callback, + silent=self.silent + ) + + finally: + su.Popen(['umount', f'{mount_root}/dev']).communicate() + new_release = iocage_lib.ioc_common.get_jail_freebsd_version( + mount_root, self.release + ) + + if self.release != new_release: + jails = iocage_lib.ioc_list.IOCList( + 'uuid', hdr=False).list_datasets() + + if not cli: + for jail, path in jails.items(): + _json = iocage_lib.ioc_json.IOCJson( + path, cli=False + ) + props = _json.json_get_value('all') + + if props['basejail'] and self.release.rsplit( + '-', 1 + )[0] in props['release']: + _json.json_set_value(f'release={new_release}') + else: + _json = iocage_lib.ioc_json.IOCJson( + jails[uuid], cli=False + ) + _json.json_set_value(f'release={new_release}') + + if self.verify: + # tmp only exists if they verify SSL certs + + if not tmp.closed: + tmp.close() + + os.remove(tmp.name) + + try: + if not cli: + # Why this sometimes doesn't exist, we may never know. + os.remove(f"{mount_root}/etc/resolv.conf") + except OSError: + pass + + def __fetch_extract_remove__(self, tar): + """ + Tries to remove any file that exists from the archive as overwriting + is very slow in tar. + """ + members = [] + + for f in tar.getmembers(): + rel_path = f"{self.iocroot}/releases/{self.release}/root/" \ + f"{f.name}" + try: + # . and so forth won't like this. + os.remove(rel_path) + except (IOError, OSError): + pass + + members.append(f) + + return members diff --git a/iocage/iocage_lib/ioc_upgrade.py b/iocage/iocage_lib/ioc_upgrade.py new file mode 100644 index 0000000..e1187d2 --- /dev/null +++ b/iocage/iocage_lib/ioc_upgrade.py @@ -0,0 +1,410 @@ +# Copyright (c) 2014-2019, iocage +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted providing that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +"""iocage upgrade module""" +import datetime +import fileinput +import hashlib +import os +import pathlib +import subprocess as su +import tempfile +import urllib.request + +import iocage_lib.ioc_common +import iocage_lib.ioc_json +import iocage_lib.ioc_list + + +class IOCUpgrade: + + """Will upgrade a jail to the specified RELEASE.""" + + def __init__(self, + new_release, + path, + interactive=True, + silent=False, + callback=None, + ): + super().__init__() + self.pool = iocage_lib.ioc_json.IOCJson().json_get_value("pool") + self.iocroot = iocage_lib.ioc_json.IOCJson( + self.pool).json_get_value("iocroot") + self.freebsd_version = iocage_lib.ioc_common.checkoutput( + ["freebsd-version"]) + self.conf = iocage_lib.ioc_json.IOCJson(path.rsplit( + '/root', 1)[0]).json_get_value('all') + self.uuid = self.conf["host_hostuuid"] + self.host_release = os.uname()[2] + _release = self.conf["release"].rsplit("-", 1)[0] + self.jail_release = _release if "-RELEASE" in _release else \ + self.conf["release"] + self.new_release = new_release + self.path = path + self.status, self.jid = iocage_lib.ioc_list.IOCList.list_get_jid( + self.uuid) + self._freebsd_version = f"{self.iocroot}/jails/" \ + f"{self.uuid}/root/bin/freebsd-version" + self.date = datetime.datetime.utcnow().strftime("%F") + self.interactive = interactive + self.silent = silent + + path = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:'\ + '/usr/local/bin:/root/bin' + self.upgrade_env = { + 'PAGER': '/bin/cat', + 'PATH': path, + 'PWD': '/', + 'HOME': '/', + 'TERM': 'xterm-256color' + } + + self.callback = callback + + # symbolic link created on fetch by freebsd-update + bd_hash = hashlib.sha256((self.path + '\n').encode('utf-8')).hexdigest() + self.freebsd_install_link = os.path.join(self.path, + 'var/db/freebsd-update', bd_hash + '-install') + + def upgrade_jail(self): + iocage_lib.ioc_common.tmp_dataset_checks(self.callback, self.silent) + + if "HBSD" in self.freebsd_version: + su.Popen(["hbsd-upgrade", "-j", self.jid]).communicate() + + return + + if not os.path.isfile(f"{self.path}/etc/freebsd-update.conf"): + return + + self.__upgrade_check_conf__() + + f_rel = f'{self.new_release.rsplit("-RELEASE")[0]}' + f = 'https://raw.githubusercontent.com/freebsd/freebsd-src' \ + f'/releng/{f_rel}/usr.sbin/freebsd-update/freebsd-update.sh' + + tmp = None + try: + tmp = tempfile.NamedTemporaryFile(delete=False) + with urllib.request.urlopen(f) as fbsd_update: + tmp.write(fbsd_update.read()) + tmp.close() + os.chmod(tmp.name, 0o755) + + fetch_cmd = [ + tmp.name, "-b", self.path, "-d", + f"{self.path}/var/db/freebsd-update/", "-f", + f"{self.path}/etc/freebsd-update.conf", + "--not-running-from-cron", "--currently-running " + f"{self.jail_release}", "-r", self.new_release, "upgrade" + ] + + # FreeNAS MW/Others, this is a best effort as things may require + # stdin input, in which case dropping to a tty is the best solution + if not self.interactive: + with iocage_lib.ioc_exec.IOCExec( + fetch_cmd, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True, + stdin_bytestring=b'y\n', + callback=self.callback, + ) as _exec: + iocage_lib.ioc_common.consume_and_log( + _exec, + callback=self.callback + ) + else: + iocage_lib.ioc_exec.InteractiveExec( + fetch_cmd, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True + ) + + if not os.path.islink(self.freebsd_install_link): + msg = 'Upgrade failed, nothing to install after fetch!' + iocage_lib.ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': msg + }, + _callback=self.callback, + silent=self.silent + ) + + for _ in range(50): # up to 50 invocations to prevent runaway + if os.path.islink(self.freebsd_install_link): + self.__upgrade_install__(tmp.name) + else: + break + + if os.path.islink(self.freebsd_install_link): + msg = f'Upgrade failed, freebsd-update won\'t finish!' + iocage_lib.ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': msg + }, + _callback=self.callback, + silent=self.silent + ) + + new_release = iocage_lib.ioc_common.get_jail_freebsd_version( + self.path, + self.new_release + ) + + if f_rel.startswith('12'): + # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=239498 + cp = su.Popen( + ['pkg-static', '-j', self.jid, 'install', '-q', '-f', '-y', 'pkg'], + stdout=su.PIPE, stderr=su.PIPE + ) + _, stderr = cp.communicate() + if cp.returncode: + # Let's make this non-fatal as this is only being done as a convenience to user + iocage_lib.ioc_common.logit( + { + 'level': 'ERROR', + 'message': 'Unable to install pkg after upgrade' + }, + _callback=self.callback, + silent=self.silent, + ) + + finally: + if tmp: + if not tmp.closed: + tmp.close() + os.remove(tmp.name) + + iocage_lib.ioc_json.IOCJson( + self.path.replace('/root', ''), + silent=True).json_set_value(f"release={new_release}") + + return new_release + + def upgrade_basejail(self, snapshot=True, snap_name=None): + if "HBSD" in self.freebsd_version: + # TODO: Not supported yet + msg = "Upgrading basejails on HardenedBSD is not supported yet." + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + + release_p = pathlib.Path(f"{self.iocroot}/releases/{self.new_release}") + self._freebsd_version = f"{self.iocroot}/releases/"\ + f"{self.new_release}/root/bin/freebsd-version" + + if not release_p.exists(): + msg = f"{self.new_release} is missing, please fetch it!" + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + + if snapshot: + self.__snapshot_jail__() + + p = pathlib.Path( + f"{self.iocroot}/releases/{self.new_release}/root/usr/src") + p_files = [] + + if p.exists(): + for f in p.iterdir(): + # We want to make sure files actually exist as well + p_files.append(f) + + if not p_files: + msg = f"{self.new_release} is missing 'src.txz', please refetch!" + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + + self.__upgrade_replace_basejail_paths__() + ioc_up_dir = pathlib.Path(f"{self.path}/iocage_upgrade") + + if not ioc_up_dir.exists(): + ioc_up_dir.mkdir(exist_ok=True, parents=True) + + mount_cmd = [ + "mount_nullfs", "-o", "ro", + f"{self.iocroot}/releases/{self.new_release}/root/usr/src", + f"{self.path}/iocage_upgrade" + ] + try: + iocage_lib.ioc_exec.SilentExec( + mount_cmd, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True + ) + except iocage_lib.ioc_exceptions.CommandFailed: + msg = "Mounting src into jail failed! Rolling back snapshot." + self.__rollback_jail__(name=snap_name) + + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + + etcupdate_cmd = [ + "/usr/sbin/jexec", f"ioc-{self.uuid.replace('.', '_')}", + "/usr/sbin/etcupdate", "-F", "-s", "/iocage_upgrade" + ] + try: + iocage_lib.ioc_exec.SilentExec( + etcupdate_cmd, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True + ) + except iocage_lib.ioc_exceptions.CommandFailed: + # These are now the result of a failed merge, nuking and putting + # the backup back + msg = "etcupdate failed! Rolling back snapshot." + self.__rollback_jail__(name=snap_name) + + su.Popen([ + "umount", "-f", f"{self.path}/iocage_upgrade" + ]).communicate() + + iocage_lib.ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + + new_release = iocage_lib.ioc_common.get_jail_freebsd_version( + f'{self.iocroot}/releases/{self.new_release}/root', + self.new_release + ) + + iocage_lib.ioc_json.IOCJson( + f"{self.path.replace('/root', '')}", + silent=True).json_set_value(f"release={new_release}") + + mq = pathlib.Path(f"{self.path}/var/spool/mqueue") + + if not mq.exists(): + mq.mkdir(exist_ok=True, parents=True) + + iocage_lib.ioc_exec.SilentExec( + ['newaliases'], + self.path.replace('/root', ''), + uuid=self.uuid + ) + + umount_command = [ + "umount", "-f", f"{self.path}/iocage_upgrade" + ] + iocage_lib.ioc_exec.SilentExec( + umount_command, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True + ) + + return new_release + + def __upgrade_install__(self, name): + """Installs the upgrade.""" + install_cmd = [ + name, "-b", self.path, "-d", + f"{self.path}/var/db/freebsd-update/", "-f", + f"{self.path}/etc/freebsd-update.conf", "-r", self.new_release, + "install" + ] + + if not self.interactive: + with iocage_lib.ioc_exec.IOCExec( + install_cmd, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True, + callback=self.callback, + ) as _exec: + iocage_lib.ioc_common.consume_and_log( + _exec, + callback=self.callback + ) + else: + iocage_lib.ioc_exec.InteractiveExec( + install_cmd, + self.path.replace('/root', ''), + uuid=self.uuid, + unjailed=True + ) + + def __upgrade_check_conf__(self): + """ + Replaces freebsd-update.conf's default Components configuration to not + update kernel + """ + f = f"{self.path}/etc/freebsd-update.conf" + text = "Components src world kernel" + replace = "Components src world" + + self.__upgrade_replace_text__(f, text, replace) + + def __upgrade_replace_basejail_paths__(self): + f = f"{self.iocroot}/jails/{self.uuid}/fstab" + self.__upgrade_replace_text__(f, self.jail_release, self.new_release) + + @staticmethod + def __upgrade_replace_text__(path, text, replace): + with fileinput.FileInput(path, inplace=True, backup=".bak") as _file: + for line in _file: + print(line.replace(text, replace), end='') + + os.remove(f"{path}.bak") + + def __snapshot_jail__(self): + import iocage_lib.iocage as ioc # Avoids dep issues + name = f"ioc_upgrade_{self.date}" + ioc.IOCage(jail=self.uuid, skip_jails=True, silent=True).snapshot(name) + + def __rollback_jail__(self, name=None): + import iocage_lib.iocage as ioc # Avoids dep issues + name = name if name else f'ioc_upgrade_{self.date}' + iocage = ioc.IOCage(jail=self.uuid, skip_jails=True, silent=True) + iocage.stop() + iocage.rollback(name) diff --git a/iocage/iocage_lib/iocage.py b/iocage/iocage_lib/iocage.py new file mode 100644 index 0000000..34d7da7 --- /dev/null +++ b/iocage/iocage_lib/iocage.py @@ -0,0 +1,2177 @@ +# Copyright (c) 2014-2019, iocage +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted providing that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import collections +import datetime +import json +import operator +import os +import subprocess as su + +import iocage_lib.ioc_clean as ioc_clean +import iocage_lib.ioc_common as ioc_common +import iocage_lib.ioc_create as ioc_create +import iocage_lib.ioc_destroy as ioc_destroy +import iocage_lib.ioc_exec as ioc_exec +import iocage_lib.ioc_fetch as ioc_fetch +import iocage_lib.ioc_fstab as ioc_fstab +import iocage_lib.ioc_image as ioc_image +import iocage_lib.ioc_json as ioc_json +import iocage_lib.ioc_list as ioc_list +import iocage_lib.ioc_plugin as ioc_plugin +import iocage_lib.ioc_start as ioc_start +import iocage_lib.ioc_stop as ioc_stop +import iocage_lib.ioc_upgrade as ioc_upgrade +import iocage_lib.ioc_debug as ioc_debug +import iocage_lib.ioc_exceptions as ioc_exceptions + +from iocage_lib.cache import cache +from iocage_lib.dataset import Dataset +from iocage_lib.pools import Pool, PoolListableResource +from iocage_lib.release import Release +from iocage_lib.snapshot import SnapshotListableResource, Snapshot + + +class PoolAndDataset: + + def get_pool(self): + """ + Helper to get the current pool. + + Return: + string: with the pool name. + """ + + return ioc_json.IOCJson().json_get_value("pool") + + def get_iocroot(self): + """ + Helper to get the iocroot. + + Return: + string: with the iocroot name. + """ + return ioc_json.IOCJson().json_get_value("iocroot") + + +class IOCage: + + def __init__( + self, jail=None, rc=False, callback=None, silent=False, + activate=False, skip_jails=False, reset_cache=False, + ): + self.rc = rc + self.silent = silent + + # FreeNAS won't be entering through the CLI, so we set sane defaults + os.environ.get("IOCAGE_SKIP", "FALSE") + os.environ.get("IOCAGE_FORCE", "TRUE") + + if reset_cache: + self.reset_cache() + + if not activate: + self.generic_iocjson = ioc_json.IOCJson() + self.pool = self.generic_iocjson.pool + self.iocroot = self.generic_iocjson.iocroot + + if not skip_jails: + # When they need to destroy a jail with a missing or bad + # configuration, this gets in our way otherwise. + self.jails = self.list("uuid") + + self.skip_jails = skip_jails + self.jail = jail + self._all = True if self.jail and 'ALL' in self.jail else False + self.callback = callback + self.is_depend = False + + @staticmethod + def reset_cache(): + cache.reset() + + def __all__(self, jail_order, action, ignore_exception=False): + # So we can properly start these. + self._all = False + + for j in jail_order: + # We want this to be the real jail now. + self.jail = j + uuid, path = self.__check_jail_existence__() + status, jid = self.list("jid", uuid=uuid) + + if action == 'stop': + self.stop(j, ignore_exception=ignore_exception) + elif action == 'start': + if not status: + err, msg = self.start(j, ignore_exception=ignore_exception) + + if err: + ioc_common.logit( + { + 'level': 'ERROR', + 'message': msg + }, + _callback=self.callback, silent=self.silent + ) + else: + message = f"{uuid} ({j}) is already running!" + ioc_common.logit( + { + 'level': 'WARNING', + 'message': message + }, + _callback=self.callback, silent=self.silent + ) + + def __jail_order__(self, action, ignore_exception=False): + """Helper to gather lists of all the jails by order and boot order.""" + jail_order = {} + boot_order = {} + + _reverse = True if action == 'stop' else False + + for jail in self.jails: + self.jail = jail + uuid, path = self.__check_jail_existence__() + conf = ioc_json.IOCJson(path).json_get_value('all') + boot = conf['boot'] + priority = conf['priority'] + jail_order[jail] = int(priority) + + # This removes having to grab all the JSON again later. + + if boot: + boot_order[jail] = int(priority) + + jail_order = collections.OrderedDict( + sorted( + jail_order.items(), + key=operator.itemgetter(1), + reverse=_reverse)) + boot_order = collections.OrderedDict( + sorted( + boot_order.items(), + key=operator.itemgetter(1), + reverse=_reverse)) + + if self.rc: + self.__rc__(boot_order, action, ignore_exception) + elif self._all: + self.__all__(jail_order, action, ignore_exception) + + def __rc__(self, boot_order, action, ignore_exception=False): + """Helper to start all jails with boot=on""" + # So we can properly start these. + self.rc = False + + for j in boot_order.keys(): + # We want this to be the real jail now. + self.jail = j + + uuid, path = self.__check_jail_existence__() + status, _ = self.list("jid", uuid=uuid) + + if action == 'stop': + if status: + message = f" Stopping {uuid}" + ioc_common.logit( + { + 'level': 'INFO', + 'message': message + }, + _callback=self.callback, silent=self.silent + ) + + self.stop(j, ignore_exception=ignore_exception) + else: + message = f"{uuid} is not running!" + ioc_common.logit( + { + 'level': 'INFO', + 'message': message + }, + _callback=self.callback, silent=self.silent + ) + elif action == 'start': + if not status: + message = f" Starting {uuid}" + ioc_common.logit( + { + 'level': 'INFO', + 'message': message + }, + _callback=self.callback, silent=self.silent + ) + + err, msg = self.start(j, ignore_exception=ignore_exception) + + if err: + ioc_common.logit( + { + 'level': 'ERROR', + 'message': msg + }, + _callback=self.callback, silent=self.silent + ) + else: + message = f"{uuid} is already running!" + ioc_common.logit( + { + 'level': 'WARNING', + 'message': message + }, + _callback=self.callback, silent=self.silent + ) + + def __check_jail_existence__(self): + """ + Helper to check if jail dataset exists + Return: + tuple: The jails uuid, path + """ + + if os.path.isdir(f"{self.iocroot}/jails/{self.jail}"): + path = f"{self.iocroot}/jails/{self.jail}" + + return self.jail, path + elif os.path.isdir(f"{self.iocroot}/templates/{self.jail}"): + path = f"{self.iocroot}/templates/{self.jail}" + + return self.jail, path + else: + if self.skip_jails: + # We skip jails for performance, but if they didn't match be + # now need to gather the list and iterate. + self.jails = self.list("uuid") + + # We got a partial, time to search. + _jail = { + uuid: path + + for (uuid, path) in self.jails.items() + + if uuid.startswith(self.jail) + } + + if len(_jail) == 1: + uuid, path = next(iter(_jail.items())) + + return uuid, path + elif len(_jail) > 1: + msg = f"Multiple jails found for {self.jail}:" + + for u, p in sorted(_jail.items()): + msg += f"\n {u} ({p})" + + ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + else: + msg = f"jail '{self.jail}' not found!" + + ioc_common.logit( + { + "level": "EXCEPTION", + "message": msg + }, + _callback=self.callback, + silent=self.silent) + + @staticmethod + def __check_jail_type__(_type, uuid): + """ + Return: + tuple: True if error with a message, or False/None + """ + + if _type in ("jail", "plugin", "clonejail", "pluginv2"): + return False, None + elif _type == 'basejail': + return (True, "Please run \"iocage migrate\" before trying to" + f" start {uuid}") + elif _type == 'template': + return (True, "Please convert back to a jail before trying to" + f" start {uuid}") + else: + return True, f"{_type} is not a supported jail type." + + @staticmethod + def __mount__(path, _type): + if _type == "devfs": + cmd = ["mount", "-t", "devfs", "devfs", path] + else: + cmd = ["mount", "-a", "-F", path] + + _, stderr = su.Popen(cmd, stdout=su.PIPE, stderr=su.PIPE).communicate() + + return stderr + + @staticmethod + def __umount__(path, _type): + if _type == "devfs": + cmd = ["umount", path] + else: + cmd = ["umount", "-a", "-F", path] + + _, stderr = su.Popen(cmd, stdout=su.PIPE, stderr=su.PIPE).communicate() + + return stderr + + def activate(self, zpool): + """Activates the zpool for iocage usage""" + zpool = Pool(zpool, cache=False) + if not zpool.exists: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"ZFS pool '{zpool}' not found!" + }, + _callback=self.callback, + silent=self.silent) + + for pool in PoolListableResource(): + if pool == zpool: + locked_error = None + if pool.root_dataset.locked: + locked_error = f'ZFS pool "{zpool}" root dataset is locked' + + iocage_ds = Dataset(os.path.join(zpool.name, 'iocage')) + if iocage_ds.exists and iocage_ds.locked: + locked_error = f'ZFS dataset "{iocage_ds.name}" is locked' + if locked_error: + ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': locked_error, + }, + _callback=self.callback, + silent=self.silent, + ) + else: + pool.activate_pool() + else: + pool.deactivate_pool() + + def deactivate(self, zpool): + zpool = Pool(zpool, cache=False) + if not zpool.exists: + ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': f'ZFS pool "{zpool}" not found!' + }, + _callback=self.callback, + silent=self.silent) + zpool.deactivate_pool() + + def chroot(self, command): + """Deprecated: Chroots into a jail and runs a command, or the shell.""" + ioc_common.logit( + { + "level": "INFO", + "message": + ( + "iocage chroot is deprecated. " + "If you need to execute a {} inside the jail use: {}" + ).format(*[ + ["shell", "iocage console"], + ["command", "iocage exec"] + ][int(len(command) != 0)]) + }, + _callback=self.callback, + silent=self.silent) + + def clean(self, d_type): + """Destroys all of a specified dataset types.""" + if d_type == 'jails': + ioc_clean.IOCClean(silent=self.silent).clean_jails() + ioc_common.logit( + { + 'level': 'INFO', + 'message': 'All iocage jail datasets have been destroyed.' + }, + _callback=self.callback, + silent=self.silent) + elif d_type == 'all': + ioc_clean.IOCClean(silent=self.silent).clean_all() + ioc_common.logit( + { + 'level': 'INFO', + 'message': 'All iocage datasets have been destroyed.' + }, + _callback=self.callback, + silent=self.silent) + elif d_type == 'release': + ioc_clean.IOCClean(silent=self.silent).clean_releases() + ioc_common.logit( + { + 'level': 'INFO', + 'message': 'All iocage RELEASE and jail datasets have been' + ' destroyed.' + }, + _callback=self.callback, + silent=self.silent) + elif d_type == 'template': + ioc_clean.IOCClean(silent=self.silent).clean_templates() + ioc_common.logit( + { + 'level': 'INFO', + 'message': + 'All iocage template datasets have been destroyed.' + }, + _callback=self.callback, + silent=self.silent) + elif d_type == 'images': + ioc_clean.IOCClean(silent=self.silent).clean_images() + ioc_common.logit( + { + 'level': 'INFO', + 'message': 'The iocage images dataset has been destroyed.' + }, + _callback=self.callback, + silent=self.silent) + elif d_type == 'debug': + ioc_clean.IOCClean(silent=self.silent).clean_debug() + ioc_common.logit( + { + 'level': 'INFO', + 'message': 'All iocage debugs have been destroyed.' + }, + _callback=self.callback, + silent=self.silent) + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": "Please specify a dataset type to clean!" + }, + _callback=self.callback, + silent=self.silent) + + def create(self, + release, + props, + count=0, + pkglist=None, + template=False, + short=False, + _uuid=None, + basejail=False, + thickjail=False, + empty=False, + clone=None, + skip_batch=False, + thickconfig=False, + clone_basejail=False): + """Creates the jail dataset""" + count = 0 if count == 1 and not skip_batch else count + + if short and _uuid: + _uuid = _uuid[:8] + + if len(_uuid) != 8: + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "Need a minimum of 8 characters to use --short" + " (-s) and --uuid (-u) together!" + }, + _callback=self.callback, + silent=self.silent) + + if not template and not release and not empty and not clone: + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "Must supply either --template (-t) or" + " --release (-r)!" + }, + _callback=self.callback, + silent=self.silent) + + if release is not None: + if os.path.isdir( + f'{self.iocroot}/releases/{release.upper()}' + ) and not template and not empty and not clone: + release = release.upper() + + if not os.path.isdir( + f'{self.iocroot}/releases/{release}' + ) and not template and not empty and not clone: + freebsd_version = ioc_common.checkoutput(["freebsd-version"]) + + if "HBSD" in freebsd_version: + hardened = True + else: + hardened = False + + arch = os.uname()[4] + + if arch in {'i386', 'arm64'}: + files = ['MANIFEST', 'base.txz', 'src.txz'] + else: + files = ['MANIFEST', 'base.txz', 'lib32.txz', 'src.txz'] + + try: + if int(release.rsplit('-')[0].rsplit('.')[0]) < 12: + # doc.txz has relevance here still + files.append('doc.txz') + except (AttributeError, ValueError): + # Non-standard naming scheme, assuming it's current + pass + + ioc_fetch.IOCFetch( + release, + hardened=hardened, + files=files, + silent=self.silent + ).fetch_release() + + if clone: + clone_uuid, path = self.__check_jail_existence__() + + if 'templates' in path: + template = True + + status, _ = self.list("jid", uuid=clone_uuid) + + if status: + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + f"Jail: {self.jail} must not be running to be" + " cloned!" + }, + _callback=self.callback, + silent=self.silent) + + release = clone_uuid + clone = self.jail + + try: + if count > 1 and not skip_batch: + for j in range(1, count + 1): + + self.create( + release, + props, + j, + pkglist=pkglist, + template=template, + short=short, + _uuid=f"{_uuid}_{j}" if _uuid else None, + basejail=basejail, + thickjail=thickjail, + empty=empty, + clone=clone, + skip_batch=True, + thickconfig=thickconfig, + clone_basejail=clone_basejail) + else: + ioc_create.IOCCreate( + release, + props, + count, + pkglist, + silent=self.silent, + template=template, + short=short, + basejail=basejail, + thickjail=thickjail, + empty=empty, + uuid=_uuid, + clone=clone, + thickconfig=thickconfig, + clone_basejail=clone_basejail + ).create_jail() + except BaseException: + if clone: + su.run( + [ + 'zfs', 'destroy', '-r', + f'{self.pool}/iocage/jails/{clone}@{_uuid}' + ] + ) + raise + + return False, None + + def destroy_release(self, download=False): + """Destroy supplied RELEASE and the download dataset if asked""" + path = f"{self.pool}/iocage/releases/{self.jail}" + + release = Release(self.jail) + # Let's make sure the release exists before we try to destroy it + if not release: + ioc_common.logit({ + 'level': 'EXCEPTION', + 'message': f'Release: {self.jail} not found!' + }) + + ioc_common.logit( + { + "level": "INFO", + "message": f"Destroying RELEASE dataset: {self.jail}" + }, + _callback=self.callback, + silent=self.silent) + + ioc_destroy.IOCDestroy().__destroy_parse_datasets__(path, stop=False) + + if download: + path = f"{self.pool}/iocage/download/{self.jail}" + ioc_common.logit( + { + "level": "INFO", + "message": + f"Destroying RELEASE download dataset: {self.jail}" + }, + _callback=self.callback, + silent=self.silent) + + ioc_destroy.IOCDestroy().__destroy_parse_datasets__(path, + stop=False) + + def destroy_jail(self, force=False): + """ + Destroys the supplied jail, to reduce perfomance hit, + call IOCage with skip_jails=True + """ + try: + self.jails = self.list("uuid") + except (RuntimeError, SystemExit) as err: + err = str(err) + + if "Configuration is missing" in err: + uuid = err.split()[5] + path = f"{self.pool}/iocage/jails/{uuid}" + + if uuid == self.jail: + ioc_destroy.IOCDestroy().__destroy_parse_datasets__( + path, stop=False) + + ioc_common.logit( + { + "level": "INFO", + "message": f"{uuid} destroyed" + }, + _callback=self.callback, + silent=self.silent) + + return + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": err + }, + _callback=self.callback, + silent=self.silent) + except FileNotFoundError as err: + # Jail is lacking a configuration, time to nuke it from orbit. + uuid = str(err).rsplit("/")[-2] + path = f"{self.pool}/iocage/jails/{uuid}" + + if uuid == self.jail: + ioc_destroy.IOCDestroy().__destroy_parse_datasets__(path) + + return + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": err + }, + _callback=self.callback, + silent=self.silent) + + uuid, path = self.__check_jail_existence__() + status, _ = self.list("jid", uuid=uuid) + + if status: + if not force: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": (f"Jail {uuid} is still running, " + f"please stop the jail first " + f"or destroy it with -f") + }, + _callback=self.callback, + silent=self.silent) + else: + ioc_common.logit( + { + "level": "INFO", + "message": f"Stopping {uuid}" + }, + _callback=self.callback, + silent=self.silent) + + ioc_common.logit( + { + "level": "INFO", + "message": f"Destroying {uuid}" + }, + _callback=self.callback, + silent=self.silent) + + ioc_destroy.IOCDestroy().destroy_jail(path) + + def df(self): + """Returns a list containing the resource usage of all jails""" + jail_list = [] + + for jail, path in self.jails.items(): + conf = ioc_json.IOCJson(path).json_get_value('all') + mountpoint = f"{self.pool}/iocage/jails/{jail}" + + template = conf["type"] + + if template == "template": + mountpoint = f"{self.pool}/iocage/templates/{jail}" + + ds = Dataset(mountpoint) + zconf = ds.properties + + compressratio = zconf["compressratio"] + reservation = zconf["reservation"] + quota = zconf["quota"] + used = zconf["used"] + available = zconf["available"] + + jail_list.append( + [jail, compressratio, reservation, quota, used, available]) + + return jail_list + + def exec_all( + self, command, host_user='root', jail_user=None, console=False, + start_jail=False, interactive=False, unjailed=False, msg_return=False + ): + """Runs exec for all jails""" + self._all = False + for jail in self.jails: + self.jail = jail + self.exec( + command, host_user, jail_user, console, start_jail, + interactive, unjailed, msg_return + ) + + def exec( + self, command, host_user='root', jail_user=None, console=False, + start_jail=False, interactive=False, unjailed=False, msg_return=False + ): + """Executes a command in the jail as the supplied users.""" + if self._all: + self.exec_all( + command, host_user, jail_user, console, start_jail, + interactive, unjailed, msg_return + ) + return + + pkg = unjailed + + if host_user and jail_user is not None: + ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': 'Please only specify either host_user or' + ' jail_user, not both!' + }, + _callback=self.callback, + silent=self.silent) + + uuid, path = self.__check_jail_existence__() + exec_clean = self.get('exec_clean') + + if exec_clean: + env_path = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:' \ + '/usr/local/bin:/root/bin' + env_lang = os.environ.get('LANG', 'en_US.UTF-8') + su_env = { + 'PATH': env_path, + 'PWD': '/', + 'HOME': '/', + 'TERM': 'xterm-256color', + 'LANG': env_lang, + 'LC_ALL': env_lang + } + else: + su_env = os.environ.copy() + + status, jid = self.list("jid", uuid=uuid) + + if not status and not start_jail: + if not ioc_common.INTERACTIVE: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f'{self.jail} is not running! Please supply' + ' start_jail=True or start the jail' + }, + _callback=self.callback, + silent=self.silent) + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f'{self.jail} is not running! Please supply' + ' --force (-f) or start the jail' + }, + _callback=self.callback, + silent=self.silent) + elif not status: + self.start() + status, jid = self.list("jid", uuid=uuid) + + if pkg: + ip4_addr = self.get("ip4_addr") + ip6_addr = self.get("ip6_addr") + dhcp = self.get("dhcp") + nat = self.get('nat') + + if ( + ip4_addr == ip6_addr == "none" and not dhcp and not nat + ): + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "The jail requires an IP address before you " + "can use pkg. Set one and restart the jail." + }, + _callback=self.callback, + silent=self.silent) + + command = ["pkg", "-j", jid] + list(command) + + if console: + login_flags = self.get('login_flags').split() + console_cmd = ['login', '-p'] + login_flags + + try: + ioc_exec.InteractiveExec(console_cmd, path, uuid=uuid) + except BaseException as e: + ioc_common.logit( + { + 'level': 'ERROR', + 'message': 'Console failed!\nThe cause could be bad ' + f'permissions for {path}/root/usr/lib.' + }, + _callback=self.callback, + silent=False + ) + raise e + return + + if interactive: + ioc_exec.InteractiveExec( + command, + path, + uuid=uuid, + host_user=host_user, + jail_user=jail_user, + skip=True + ) + return + + try: + with ioc_exec.IOCExec( + command, + path, + uuid=uuid, + host_user=host_user, + jail_user=jail_user, + unjailed=pkg, + su_env=su_env + ) as _exec: + output = ioc_common.consume_and_log( + _exec + ) + + if msg_return: + return output['stdout'] + + for line in output['stdout']: + ioc_common.logit( + { + "level": "INFO", + "message": line + }, + _callback=self.callback, + silent=self.silent) + except ioc_exceptions.CommandFailed as e: + msgs = [_msg.decode().rstrip() for _msg in e.message] + if msgs: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": '\n'.join(msgs) + }, + _callback=self.callback, + silent=self.silent) + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f'Command: {command} failed!' + }, + _callback=self.callback, + silent=self.silent) + + def export(self, compression_algo='zip'): + """Will export a jail""" + uuid, path = self.__check_jail_existence__() + status, _ = self.list("jid", uuid=uuid) + + if status: + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + f"{uuid} is running, stop the jail before" + " exporting!" + }, + _callback=self.callback, + silent=self.silent) + + ioc_image.IOCImage().export_jail( + uuid, path, compression_algo=compression_algo + ) + + def fetch(self, **kwargs): + """Fetches a release or plugin.""" + release = kwargs.pop("release", None) + name = kwargs.pop("name", None) + props = kwargs.pop("props", ()) + plugins = kwargs.pop("plugins", False) + plugin_name = kwargs.pop("plugin_name", None) + count = kwargs.pop("count", 1) + accept = kwargs.pop("accept", False) + _list = kwargs.pop("list", False) + remote = kwargs.pop("remote", False) + http = kwargs.get("http", True) + hardened = kwargs.get("hardened", False) + header = kwargs.pop("header", True) + _long = kwargs.pop("_long", False) + official = kwargs.pop("official", False) + branch = kwargs.pop("branch", None) + keep_jail_on_failure = kwargs.pop("keep_jail_on_failure", False) + thick_config = kwargs.pop("thickconfig", False) + + freebsd_version = ioc_common.checkoutput(["freebsd-version"]) + arch = os.uname()[4] + + if not _list: + if not kwargs.get('files', None): + if arch in {'i386', 'arm64'}: + kwargs['files'] = ['MANIFEST', 'base.txz', 'src.txz'] + else: + kwargs['files'] = ['MANIFEST', 'base.txz', 'lib32.txz', + 'src.txz'] + + try: + if int(release.rsplit('-')[0].rsplit('.')[0]) < 12: + # doc.txz has relevance here still + kwargs['files'].append('doc.txz') + except (AttributeError, ValueError): + # Non-standard naming scheme, assuming it's current + pass + + if "HBSD" in freebsd_version: + if kwargs["server"] == "download.freebsd.org": + kwargs["hardened"] = True + else: + kwargs["hardened"] = False + else: + kwargs["hardened"] = False + + if plugins or plugin_name: + if _list: + rel_list = ioc_plugin.IOCPlugin( + branch=branch, + thickconfig=thick_config, + **kwargs + ).fetch_plugin_index( + "", _list=True, list_header=header, list_long=_long, + icon=True, official=official + ) + + return rel_list + + if plugins: + ioc_plugin.IOCPlugin( + release=release, + plugin=plugin_name, + branch=branch, + thickconfig=thick_config, + **kwargs).fetch_plugin_index( + props, accept_license=accept, official=official) + + return + + plugin_obj = ioc_plugin.IOCPlugin( + release=release, plugin=plugin_name, + branch=branch, silent=self.silent, + keep_jail_on_failure=keep_jail_on_failure, + callback=self.callback, **kwargs, + thickconfig=thick_config, + ) + + i = 1 + check_jail_name = name or plugin_obj.retrieve_plugin_json().get( + 'name', plugin_name + ) + while True: + if check_jail_name not in self.jails: + jail_name = check_jail_name + break + elif f'{check_jail_name}_{i}' not in self.jails: + jail_name = f'{check_jail_name}_{i}' + break + i += 1 + + self.jails[jail_name] = jail_name # Not a valid value + if count == 1: + plugin_obj.jail = jail_name + plugin_obj.fetch_plugin(props, 0, accept) + else: + for j in range(1, count + 1): + # Repeating this block in case they have gaps in their + # plugins + # Allows plugin_1, plugin_2, and such to happen instead of + # plugin_1_1, plugin_1_2 + while True: + if jail_name not in self.jails: + break + elif f'{check_jail_name}_{i}' not in self.jails: + jail_name = f'{check_jail_name}_{i}' + break + + i += 1 + + self.jails[jail_name] = jail_name # Not a valid value + plugin_obj.jail = jail_name + plugin_obj.fetch_plugin(props, j, accept) + else: + kwargs.pop('git_repository', None) + kwargs.pop('git_destination', None) + + if _list: + if remote: + rel_list = ioc_fetch.IOCFetch( + "", http=http, hardened=hardened).fetch_release( + _list=True) + else: + rel_list = self.list("base") + + return rel_list + + ioc_fetch.IOCFetch( + release, + silent=self.silent, callback=self.callback, + **kwargs).fetch_release() + + def fstab(self, + action, + source, + destination, + fstype, + options, + dump, + _pass, + index=None, + add_path=False, + header=False): + """Adds an fstab entry for a jail""" + uuid, path = self.__check_jail_existence__() + + if action != "list": + if add_path: + destination = f"{self.iocroot}/jails/{uuid}/root{destination}" + + if destination and len(destination) > 88: + ioc_common.logit( + { + "level": + "WARNING", + "message": + "The destination's mountpoint exceeds 88 " + "characters, this may cause failure!" + }, + _callback=self.callback, + silent=self.silent) + + if action == "list": + fstab = ioc_fstab.IOCFstab( + uuid, + action, + source, + destination, + fstype, + options, + dump, + _pass, + index=index, + header=header, + ).fstab_list() + + return fstab + else: + ioc_fstab.IOCFstab( + uuid, + action, + source, + destination, + fstype, + options, + dump, + _pass, + index=index + ) + + def get( + self, prop, recursive=False, plugin=False, pool=False, start_jail=False + ): + """Get a jail property""" + if start_jail and not plugin: + ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': + '--force (-f) is only applicable with --plugin (-P)!' + }, + _callback=self.callback, + silent=self.silent) + + if pool: + return self.pool + + if not recursive: + if self.jail == "default": + try: + return ioc_json.IOCJson().json_get_value(prop, + default=True) + except KeyError: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{prop} is not a valid property!" + }, + _callback=self.callback, + silent=self.silent) + + uuid, path = self.__check_jail_existence__() + status, jid = self.list("jid", uuid=uuid) + + state = "up" if status else "down" + + if prop == "state": + return state + elif plugin: + if not status and not start_jail: + if not ioc_common.INTERACTIVE: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f'{self.jail} is not running!' + ' Please supply start_jail=True or' + ' start the jail' + }, + _callback=self.callback, + silent=self.silent) + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f'{self.jail} is not running!' + ' Please supply --force (-f) or' + ' start the jail' + }, + _callback=self.callback, + silent=self.silent) + + try: + _prop = prop.split(".") + props = ioc_json.IOCJson(path).json_plugin_get_value( + _prop + ) + except ioc_exceptions.CommandNeedsRoot as err: + ioc_common.logit( + { + 'level': 'EXCEPTION', + 'message': err.message + }, + _callback=self.callback, + silent=False) + + if isinstance(props, dict): + return json.dumps(props, indent=4) + else: + return props + elif prop == "all": + _props = {} + + props = ioc_json.IOCJson(path).json_get_value(prop) + + # We want this sorted below, so we add it to the old dict + props["state"] = state + + for key in sorted(props.keys()): + _props[key] = props[key] + + return _props + else: + try: + return ioc_json.IOCJson(path).json_get_value(prop) + except KeyError: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{prop} is not a valid property!" + }, + _callback=self.callback, + silent=self.silent) + else: + jail_list = [] + active_jails = ioc_common.get_active_jails() + + for uuid, path in self.jails.items(): + try: + jid = active_jails.get(f'ioc-{uuid.replace(".", "_")}', {}).get('jid') + state = "up" if jid else "down" + + if prop == "state": + jail_list.append({uuid: state}) + elif prop == "all": + _props = {} + try: + props = ioc_json.IOCJson(path).json_get_value(prop) + except (Exception, SystemExit): + # Jail is corrupt, we want all the keys to exist. + # So we will take the defaults and let the user + # know that they are not correct. + def_props = ioc_json.IOCJson().json_get_value( + 'all', + default=True + ) + jail_list.append({ + uuid: { + **{x: 'N/A' for x in def_props}, + 'host_hostuuid': uuid, + 'state': 'CORRUPT', + 'release': 'N/A', + 'jid': None, + } + }) + + continue + + # We want this sorted below, so we add it to the old + # dict + props.update({ + 'state': state, + 'jid': jid, + }) + + for key in sorted(props.keys()): + _props[key] = props[key] + + jail_list.append({uuid: _props}) + else: + jail_list.append({ + uuid: + ioc_json.IOCJson(path).json_get_value(prop) + }) + except KeyError: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{prop} is not a valid property!" + }, + _callback=self.callback, + silent=self.silent) + + sort = ioc_common.ioc_sort("get", "key") + jail_list.sort(key=sort) + + return jail_list + + def import_(self, compression_algo='zip', path=None): + """Imports a jail""" + ioc_image.IOCImage().import_jail( + self.jail, compression_algo=compression_algo, path=path + ) + + def list( + self, lst_type, header=False, long=False, sort='name', uuid=None, + plugin=False, quick=False, **kwargs + ): + """Returns a list of lst_type""" + + if lst_type == "jid": + return ioc_list.IOCList(**kwargs).list_get_jid(uuid) + + return ioc_list.IOCList( + lst_type, + header, + long, + sort, + plugin=plugin, + quick=quick, + silent=self.silent, + **kwargs + ).list_datasets() + + def rename(self, new_name): + uuid, old_mountpoint = self.__check_jail_existence__() + + _template = False + _folders = ["jails", "templates"] + + if old_mountpoint.startswith(f"{self.iocroot}/templates/"): + _template = True + _folders = _folders[::-1] + + new_mountpoint = f"{self.iocroot}/{_folders[0]}/{new_name}" + + if ioc_common.match_to_dir(self.iocroot, new_name, + old_uuid=old_mountpoint): + + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"Jail: {new_name} already exists!" + }, + _callback=self.callback, + silent=self.silent) + + path = f"{self.pool}/iocage/{_folders[0]}/{uuid}" + new_path = f"{self.pool}/iocage/{_folders[0]}/{new_name}" + + _silent = self.silent + self.silent = True + + self.stop() + + self.silent = _silent + + # Can't rename when the child is in a non-global zone + for str_dataset in self.get("jail_zfs_dataset").split(): + data_dataset = Dataset(f'{self.pool}/{str_dataset.strip()}') + if data_dataset.exists: + # We only do this when it exists ( keeping old behavior ) + data_dataset.set_property('jailed', 'off') + + for release_snap in SnapshotListableResource().release_snapshots: + if uuid == release_snap.name: + rel_ds = release_snap.dataset.name + su.check_call([ + 'zfs', 'rename', '-r', f'{rel_ds}@{uuid}', f'@{new_name}' + ]) + + dataset = Dataset(path) + dataset.rename(new_path, {'force_unmount': True}) + + self.jail = new_name + + self.silent = True + self.set(f"host_hostuuid={new_name}", rename=True) + + if self.get("host_hostname") == uuid: + self.set(f"host_hostname={new_name}") + + zfs_dataset = self.get("jail_zfs_dataset") + if f"iocage/jails/{uuid}" in zfs_dataset: + zfs_dataset = zfs_dataset.replace(f"iocage/jails/{uuid}", + f"iocage/jails/{new_name}") + self.set(f"jail_zfs_dataset={zfs_dataset}") + + self.silent = _silent + + # Templates are readonly + if _template: + # All self.set's set this back to on, this must be last + dataset.set_property('readonly', 'off') + + # Adjust mountpoints in fstab + jail_fstab = f"{new_mountpoint}/fstab" + + try: + with open(jail_fstab, "r") as fstab: + with ioc_common.open_atomic(jail_fstab, "w") as _fstab: + for line in fstab.readlines(): + _fstab.write(line.replace( + f"{self.iocroot}/jails/{uuid}/", + f"{self.iocroot}/jails/{new_name}/")) + except OSError: + pass + + if _template: + for jail, path in self.jails.items(): + # Stale list and isn't relevant for our loop anyways + if jail == uuid: + continue + + _json = ioc_json.IOCJson(path, silent=True) + + try: + source_template = _json.json_get_value('source_template') + except KeyError: + continue + + if source_template == uuid: + _json.json_set_value(f'source_template={new_name}') + + dataset.set_property('readonly', 'on') + + ioc_common.logit( + { + "level": "INFO", + "message": f"Jail: {uuid} renamed to {new_name}" + }, + _callback=self.callback, + silent=self.silent) + + def restart(self, soft=False): + if self._all: + if not soft: + self.__jail_order__("stop") + # This gets unset each time. + self._all = True + + self.__jail_order__("start") + else: + for j in self.jails: + self.jail = j + self.__soft_restart__() + else: + if not soft: + # __rc__ will set this to false for each, we want to preserve + # it + _rc = self.rc + self.stop() + + self.rc = _rc + self.start() + else: + self.__soft_restart__() + + def rollback(self, name): + """Rolls back a jail and all datasets to the supplied snapshot""" + uuid, path = self.__check_jail_existence__() + conf = ioc_json.IOCJson(path, silent=self.silent).json_get_value('all') + status, _ = self.list("jid", uuid=uuid) + + if status: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"Please stop {uuid} before trying to" + " rollback!" + }, + _callback=self.callback, + silent=self.silent) + + if ioc_common.check_truthy(conf['template']): + target = f"{self.pool}/iocage/templates/{uuid}" + else: + target = f"{self.pool}/iocage/jails/{uuid}" + + dataset = Dataset(target) + if not dataset.exists: + ioc_common.logit( + {'level': 'EXCEPTION', 'message': f'{target} does not exist'}, + _callback=self.callback, silent=self.silent + ) + snap = Snapshot(f'{dataset.name}@{name}') + if not snap.exists: + ioc_common.logit( + {'level': 'EXCEPTION', 'message': f'{snap} does not exist'}, + _callback=self.callback, silent=self.silent + ) + + for ds in dataset.get_dependents(depth=None): + if ds.properties['type'] == 'filesystem': + Snapshot(f'{ds.name}@{name}').rollback( + {'destroy_latest': True} + ) + + # datasets is actually the parent. + snap.rollback({'destroy_latest': True}) + + ioc_common.logit( + { + "level": "INFO", + "message": f"Rolled back to: {target}" + }, + _callback=self.callback, + silent=self.silent) + + def set(self, prop, plugin=False, rename=False): + """Sets a property for a jail or plugin""" + # The cli check prevents users changing unwanted properties. We do + # want to change a protected property with rename, so we disable that. + cli = False if rename else True + + try: + key, value = prop.split("=", 1) + except ValueError: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{prop} is missing a value!" + }, + _callback=self.callback, + silent=self.silent) + + if key == "ip4_addr" or key == "ip6_addr": + # We don't want spaces here + value = value.replace(" ", "") + + if self.jail == "default": + ioc_json.IOCJson().check_default_config() + default = True + else: + default = False + + if default: + ioc_json.IOCJson(self.iocroot).json_set_value(prop, default=True) + return + + uuid, path = self.__check_jail_existence__() + iocjson = ioc_json.IOCJson( + path, + cli=cli, + callback=self.callback, + silent=self.silent) + + if plugin: + _prop = prop.split(".") + iocjson.json_plugin_set_value(_prop) + + return + + if "template" in key: + if prop in ioc_common.construct_truthy( + 'template' + ) and path.startswith( + f'{self.iocroot}/templates/' + ): + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{uuid} is already a template!" + }, + _callback=self.callback, + silent=self.silent) + + elif prop in ioc_common.construct_truthy( + 'template', inverse=True + ) and path.startswith( + f'{self.iocroot}/jails/' + ): + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{uuid} is already a jail!" + }, + _callback=self.callback, + silent=self.silent) + + try: + # We use this to test if it's a valid property at all. + _prop = prop.partition("=")[0] + self.get(_prop) + + # The actual setting of the property. + iocjson.json_set_value(prop) + except KeyError: + _prop = prop.partition("=")[0] + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{_prop} is not a valid property!" + }, + _callback=self.callback, + silent=self.silent) + + if key == "ip6_addr": + rtsold_enable = "YES" if "accept_rtadv" in value else "NO" + ioc_common.set_rcconf(path, "rtsold_enable", rtsold_enable) + + def snap_list(self, long=True, _sort="created"): + """Gathers a list of snapshots and returns it""" + uuid, path = self.__check_jail_existence__() + conf = ioc_json.IOCJson(path, silent=self.silent).json_get_value('all') + snap_list = [] + snap_list_temp = [] + snap_list_root = [] + + if ioc_common.check_truthy(conf['template']): + full_path = f"{self.pool}/iocage/templates/{uuid}" + else: + full_path = f"{self.pool}/iocage/jails/{uuid}" + + dataset = Dataset(full_path) + + for snap in dataset.snapshots_recursive(): + snap_name = snap.name if not long else snap.resource_name + root_snap_name = snap.resource_name.rsplit("@")[0].split("/")[-1] + root = False + + if root_snap_name == "root": + if not long: + snap_name += "/root" + + root = True + elif root_snap_name != uuid: + # basejail datasets. + + continue + + creation = snap.properties["creation"] + used = snap.properties["used"] + referenced = snap.properties["referenced"] + + snap_list_temp.append([snap_name, creation, referenced, used]) \ + if not root else snap_list_root.append([snap_name, creation, + referenced, used]) + + for parent in snap_list_temp: + # We want the /root snapshots immediately after the parent ones + name = parent[0] + + if long: + name, snap_name = parent[0].split("@") + name = f"{name}/root@{snap_name}" + + for root in snap_list_root: + _name = root[0] + + # Long has this already, the short comparison will fail. + root_comparison = name if long else f"{name}/root" + + if root_comparison == _name: + snap_list.append(parent) + snap_list.append(root) + + sort = ioc_common.ioc_sort("snaplist", _sort, data=snap_list) + snap_list.sort(key=sort) + + return snap_list + + def snapshot(self, name): + """Will create a snapshot for the given jail""" + date = datetime.datetime.utcnow().strftime("%F_%T") + uuid, path = self.__check_jail_existence__() + + # If they don't supply a snapshot name, we will use the date. + + if not name: + name = date + + # Looks like foo/iocage/jails/df0ef69a-57b6-4480-b1f8-88f7b6febbdf@BAR + conf = ioc_json.IOCJson(path, silent=self.silent).json_get_value('all') + + if ioc_common.check_truthy(conf['template']): + target = f"{self.pool}/iocage/templates/{uuid}" + else: + target = f"{self.pool}/iocage/jails/{uuid}" + + snap = Snapshot(f'{target}@{name}') + if snap.exists: + ioc_common.logit( + { + 'level': 'EXCEPTION', 'force_raise': True, + 'message': 'Snapshot already exists' + }, + _callback=self.callback, silent=self.silent, + exception=ioc_exceptions.Exists + ) + + snap.create_snapshot({'recursive': True}) + + if not self.silent: + ioc_common.logit({ + "level": "INFO", + "message": f"Snapshot: {target}@{name} created." + }) + + def __soft_restart__(self): + """ + Executes a soft reboot by keeping the jail network stack intact, + but executing the rc scripts. + """ + uuid, path = self.__check_jail_existence__() + status, jid = self.list("jid", uuid=uuid) + conf = ioc_json.IOCJson(path, silent=self.silent).json_get_value('all') + + # These need to be a list. + exec_start = conf["exec_start"].split() + exec_stop = conf["exec_stop"].split() + exec_fib = conf["exec_fib"] + + if status: + ioc_common.logit( + { + "level": "INFO", + "message": f"Soft restarting {uuid} ({self.jail})" + }, + _callback=self.callback, + silent=self.silent) + + stop_cmd = [ + "setfib", exec_fib, "jexec", f"ioc-{uuid.replace('.', '_')}" + ] + exec_stop + su.Popen(stop_cmd, stdout=su.PIPE, stderr=su.PIPE).communicate() + + su.Popen(["pkill", "-j", jid]).communicate() + start_cmd = [ + "setfib", exec_fib, "jexec", f"ioc-{uuid.replace('.', '_')}" + ] + exec_start + su.Popen(start_cmd, stdout=su.PIPE, stderr=su.PIPE).communicate() + ioc_json.IOCJson(path, silent=True).json_set_value( + f"last_started={datetime.datetime.utcnow().strftime('%F %T')}") + else: + ioc_common.logit( + { + "level": "ERROR", + "message": f"{self.jail} is not running!" + }, + _callback=self.callback, + silent=self.silent) + + def start(self, jail=None, ignore_exception=False, used_ports=None): + """Checks jails type and existence, then starts the jail""" + if self.rc or self._all: + if not jail: + self.__jail_order__("start", ignore_exception=ignore_exception) + else: + uuid, path = self.__check_jail_existence__() + conf = ioc_json.IOCJson(path, silent=self.silent).json_get_value( + 'all') + release = conf["release"] + + if release != "EMPTY": + release = float(release.rsplit("-", 1)[0].rsplit("-", 1)[0]) + ioc_common.check_release_newer(release, major_only=True) + + err, msg = self.__check_jail_type__(conf["type"], uuid) + depends = conf["depends"].split() + + if not err: + for depend in depends: + if depend != "none": + try: + self.jail = depend + _is_depend = self.is_depend + self.is_depend = True + self.start(depend) + except ioc_exceptions.JailRunning: + pass + finally: + self.is_depend = _is_depend + + ioc_start.IOCStart( + uuid, + path, + silent=self.silent, + callback=self.callback, + is_depend=self.is_depend, + suppress_exception=ignore_exception, + used_ports=used_ports, + ) + + return False, None + else: + if jail: + return err, msg + else: + ioc_common.logit( + { + 'level': 'ERROR', + 'message': msg + }, + _callback=self.callback, silent=self.silent + ) + exit(1) + + def stop(self, jail=None, force=False, ignore_exception=False): + """Stops the jail.""" + + if self.rc or self._all: + if not jail: + self.__jail_order__("stop", ignore_exception=ignore_exception) + else: + uuid, path = self.__check_jail_existence__() + ioc_stop.IOCStop( + uuid, path, silent=self.silent, + force=force, suppress_exception=ignore_exception + ) + + def update_all(self, pkgs=False): + """Runs update for all jails""" + self._all = False + for jail in self.jails: + self.jail = jail + self.update(pkgs) + + def update(self, pkgs=False): + """Updates a jail to the latest patchset.""" + if self._all: + self.update_all(pkgs) + return + + uuid, path = self.__check_jail_existence__() + conf = ioc_json.IOCJson( + path, silent=self.silent, stop=True).json_get_value('all') + freebsd_version = ioc_common.checkoutput(["freebsd-version"]) + status, jid = self.list("jid", uuid=uuid) + started = False + _release = conf["release"].rsplit("-", 1)[0] + release = _release if "-RELEASE" in _release else conf["release"] + _silent = self.silent + jail_type = conf["type"] + updateable = True if jail_type in ( + "jail", "clonejail", "pluginv2") else False + + if updateable: + date = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + self.snapshot( + f'ioc_update_{conf["release"]}_{date}' + ) + + if not status: + self.silent = True + self.start() + status, jid = self.list("jid", uuid=uuid) + started = True + self.silent = _silent + elif conf["type"] == "basejail": + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "Please run \"iocage migrate\" before trying" + f" to update {uuid}" + }) + elif conf["type"] == "template": + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "Please convert back to a jail before trying" + f" to update {uuid}" + }) + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{conf['type']} is not a supported jail type." + }) + + if "HBSD" in freebsd_version: + su.Popen(["hbsd-update", "-j", jid]).communicate() + + if started: + self.silent = True + self.stop() + self.silent = _silent + else: + if pkgs and not (jail_type in ('plugin', 'pluginv2')): + # Let's update pkg repos first + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Updating pkgs...' + }) + pkg_update = su.run( + ['pkg-static', '-j', jid, 'update', '-q', '-f'], + stdout=su.PIPE, stderr=su.STDOUT + ) + if pkg_update.returncode: + ioc_common.logit({ + 'level': 'EXCEPTION', + 'message': 'Failed to update pkg repositories.' + }) + else: + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Updated pkg repositories successfully.' + }) + # This will run pkg upgrade now + ioc_create.IOCCreate( + self.jail, '', 0, pkglist=[], + silent=True, callback=self.callback + ).create_install_packages(self.jail, path, repo='') + + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Upgraded pkgs successfully.' + }) + + if jail_type == "pluginv2" or jail_type == "plugin": + # TODO: Warn about erasing all pkgs + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Updating plugin...' + }) + ioc_plugin.IOCPlugin( + jail=uuid, + plugin=conf['plugin_name'], + git_repository=conf['plugin_repository'], + callback=self.callback + ).update(jid) + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Updated plugin successfully.' + }) + + # Jail updates should always happen + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Updating jail...' + }) + + is_basejail = ioc_common.check_truthy(conf['basejail']) + params = [] if is_basejail else [True, uuid] + try: + ioc_fetch.IOCFetch( + release, + callback=self.callback + ).fetch_update(*params) + finally: + if not started and jail_type == 'pluginv2': + silent = self.silent + self.silent = True + self.restart() + self.silent = silent + + ioc_common.logit({ + 'level': 'INFO', + 'message': 'Updated jail successfully.' + }) + + if started: + self.silent = True + self.stop() + self.silent = _silent + + message = f"\n{uuid} updates have been applied successfully." + ioc_common.logit( + { + "level": "INFO", + "message": message + }, + _callback=self.callback, + silent=self.silent) + + def upgrade_all(self, release): + """Runs upgrade for all jails""" + self._all = False + for jail in self.jails: + self.jail = jail + self.upgrade(release) + + def upgrade(self, release): + if self._all: + self.upgrade_all(release) + return + + if release is not None: + _release = release.rsplit("-", 1)[0].rsplit("-", 1)[0] + ioc_common.check_release_newer(_release, major_only=True) + + uuid, path = self.__check_jail_existence__() + root_path = f"{path}/root" + status, jid = self.list("jid", uuid=uuid) + conf = ioc_json.IOCJson(path).json_get_value('all') + + if release is None and conf["type"] != "pluginv2": + ioc_common.logit({ + "level": "EXCEPTION", + "message": "Target RELEASE is required to upgrade." + }, + _callback=self.callback) + + jail_release = conf["release"] + + if conf["type"] != "pluginv2": + if release in jail_release: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": + f"Jail: {uuid} is already at version {release}!" + }, + _callback=self.callback) + + started = False + basejail = False + plugin = False + + if conf["release"] == "EMPTY": + ioc_common.logit( + { + "level": "EXCEPTION", + "message": "Upgrading is not supported for empty jails." + }, + _callback=self.callback) + + if conf["type"] == "jail": + if not status: + ioc_start.IOCStart(uuid, path, silent=True) + started = True + + if ioc_common.check_truthy(conf['basejail']): + new_release = ioc_upgrade.IOCUpgrade( + release, + root_path, + callback=self.callback + ).upgrade_basejail() + basejail = True + else: + new_release = ioc_upgrade.IOCUpgrade( + release, + root_path, + callback=self.callback + ).upgrade_jail() + elif conf["type"] == "basejail": + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "Please run \"iocage migrate\" before trying" + f" to upgrade {uuid}" + }, + _callback=self.callback) + elif conf["type"] == "template": + ioc_common.logit( + { + "level": + "EXCEPTION", + "message": + "Please convert back to a jail before trying" + f" to upgrade {uuid}" + }, + _callback=self.callback) + elif conf["type"] == "pluginv2": + if not status: + ioc_start.IOCStart(uuid, path, silent=True) + started = True + + status, jid = self.list('jid', uuid=uuid) + new_release = ioc_plugin.IOCPlugin( + jail=uuid, + plugin=conf['plugin_name'], + git_repository=conf['plugin_repository'], + callback=self.callback + ).upgrade(jid) + plugin = True + else: + ioc_common.logit( + { + "level": "EXCEPTION", + "message": f"{conf['type']} is not a supported jail type." + }, + _callback=self.callback) + + if started: + _silent = self.silent + self.silent = True + self.stop() + self.silent = _silent + + if basejail: + _date = datetime.datetime.utcnow().strftime("%F") + msg = f"""\ +\n{uuid} successfully upgraded from {jail_release} to {new_release}! +Please reboot the jail and inspect. +Remove the snapshot: ioc_upgrade_{_date} if everything is OK +""" + elif plugin: + msg = f"\n{uuid} successfully upgraded!" + else: + msg = f"\n{uuid} successfully upgraded from" \ + f" {jail_release} to {new_release}!" + + ioc_common.logit( + { + 'level': 'INFO', + 'message': msg + }, + _callback=self.callback + ) + + def debug(self, directory): + if directory is None: + directory = f'{self.iocroot}/debug' + + ioc_debug.IOCDebug(directory).run_debug() + + def snap_remove(self, snapshot): + """Removes user supplied snapshot from jail""" + uuid, path = self.__check_jail_existence__() + conf = ioc_json.IOCJson(path, silent=self.silent).json_get_value('all') + + if ioc_common.check_truthy(conf['template']): + target = f'{self.pool}/iocage/templates/{uuid}@{snapshot}' + else: + target = f'{self.pool}/iocage/jails/{uuid}@{snapshot}' + + # Let's verify target exists and then destroy it, else log it + snapshot = Snapshot(target) + + if not snapshot: + ioc_common.logit({ + 'level': 'EXCEPTION', + 'message': f'Snapshot: {target} not found!' + }) + else: + snapshot.destroy(recursive=True) + + ioc_common.logit( + { + 'level': 'INFO', + 'message': f'Snapshot: {target} destroyed' + }, + _callback=self.callback, silent=self.silent + ) diff --git a/jails/config/common/current-src.bzip2 b/jails/config/common/current-src.bzip2 new file mode 100644 index 0000000..f88f268 Binary files /dev/null and b/jails/config/common/current-src.bzip2 differ diff --git a/jails/config/vm/cvm-b.sh b/jails/config/vm/cvm-b.sh index 3804e0b..c19edec 100755 --- a/jails/config/vm/cvm-b.sh +++ b/jails/config/vm/cvm-b.sh @@ -16,7 +16,7 @@ bhyvectl --destroy --vm=cvm-b while true do -bhyve -c 4 -m 16G -A -H -P \ +bhyve -c 8 -m 32G -A -H -P \ -s 0,hostbridge \ -s 3,ahci-cd \ -s 4,virtio-blk,/dev/zvol/ship/raw/cvm-b \ diff --git a/jails/config/vm/freebsd.sh b/jails/config/vm/freebsd.sh index 35e7287..54eefdf 100755 --- a/jails/config/vm/freebsd.sh +++ b/jails/config/vm/freebsd.sh @@ -19,8 +19,13 @@ do bhyve -c 2 -m 4G -A -H -P \ -s 0,hostbridge \ -s 3,ahci-cd \ --s 4,virtio-blk,/dev/zvol/ship/raw/freebsd \ --s 5,virtio-net,tap83,mac=00:0A:0B:0C:0D:83 \ +-s 4,virtio-blk,/dev/zvol/ship/raw/freebsd_1 \ +-s 5,virtio-blk,/dev/zvol/ship/raw/freebsd_2 \ +-s 6,virtio-blk,/dev/zvol/ship/raw/freebsd_z1 \ +-s 7,virtio-blk,/dev/zvol/ship/raw/freebsd_z2 \ +-s 8,virtio-blk,/dev/zvol/ship/raw/freebsd_z3 \ +-s 9,virtio-blk,/dev/zvol/ship/raw/freebsd \ +-s 10,virtio-net,tap83,mac=00:0A:0B:0C:0D:83 \ -s 29,fbuf,tcp=0.0.0.0:5983,w=1600,h=900 \ -s 30,xhci,tablet \ -s 31,lpc -l com1,/dev/nmdm83A \ @@ -47,7 +52,7 @@ done exit $? #-s 3,ahci-cd \ -#-s 3,ahci-cd,/mnt/freebsd/FreeBSD-12.1-RELEASE-amd64-disc1.iso \ +#-s 3,ahci-cd,/mnt/freebsd/FreeBSD-12.2-RELEASE-amd64-disc1.iso \ # set boot_serial=NO # first in boot menu option 3 and then /boot/loader.conf after install @@ -58,8 +63,17 @@ exit $? #on base system: #zfs create -V 32G -o refreservation=none ship/raw/freebsd +#zfs create -V 16G -o refreservation=none ship/raw/freebsd_1 +#zfs create -V 16G -o refreservation=none ship/raw/freebsd_2 +#zfs create -V 16G -o refreservation=none ship/raw/freebsd_z1 +#zfs create -V 16G -o refreservation=none ship/raw/freebsd_z2 +#zfs create -V 16G -o refreservation=none ship/raw/freebsd_z3 # on boot #ifconfig tap83 create #ifconfig bridge1 addm tap83 up #ifconfig tap83 up #ifconfig tap83 inet6 auto_linklocal +# +#zroot mirror /dev/vtbd1 /dev/vtbd2 - created during zroot install +#zpool create -f ship /dev/vtbd2 /dev/vtbd3 /dev/vtbd4 +#zpool create -f data /dev/vtbd5 diff --git a/jails/update.sh b/jails/update.sh index 3a9ad90..081400e 100755 --- a/jails/update.sh +++ b/jails/update.sh @@ -37,8 +37,9 @@ update_jail () # iocage exec $JAIL "pkg upgrade -f -y" iocage exec $JAIL "pkg clean -ay" - iocage exec $JAIL "rm -rf /var/tmp/temproot*" - iocage exec $JAIL "mergemaster -a" +# iocage exec $JAIL "rm -rf /var/tmp/temproot*" +# iocage exec $JAIL "mergemaster -a" + iocage exec $JAIL "etcupdate -t /mnt/common/current-src.bzip2" iocage exec $JAIL "freebsd-version" @@ -48,10 +49,8 @@ update_jail () iocage fetch -U -r 12.2-RELEASE -read -p "update pkgp jail (y/N)? " RESP +read -p "update pkgp packages first (y/N)? " RESP if [ ! -z $RESP ] && [ $RESP == "y" ]; then - JAIL="pkgp" - update_jail /root/FreeBSD/jails/jails-update-pkgs.sh pkgp-only fi @@ -60,11 +59,11 @@ if [ ! -z $RESP ] && [ $RESP == "y" ]; then # for JAIL in `iocage list -h | cut -f2`; for JAIL in `jls -N | cut -d " " -f 2 | cut -d "-" -f 2- | grep -v JID | sort`; do - if [ $JAIL == "pkgp" ] || [ $JAIL == "debian" ]; then + if [ $JAIL == "test" ] || [ $JAIL == "debian" ]; then continue fi if [[ $1 == "upgrade" ]]; then - read -p "update jail $JAIL (Y/n)? " RESP + read -p "upgrade jail $JAIL (Y/n)? " RESP if [ ! -z $RESP ] && [ $RESP == "n" ]; then continue fi @@ -75,10 +74,15 @@ fi echo "update base system by running:" echo "freebsd-update fetch" +# echo "freebsd-update upgrade -r 13.0-RELEASE" echo "freebsd-update install" +# echo "pkg bootstrap -f ; pkg update ; pkg upgrade" echo "cd /usr/src; svn update; make -j8 buildkernel KERNCONF=diyIT && make -j8 installkernel KERNCONF=diyIT" +echo "etcupdate build /root/FreeBSD/jails/configs/common/current-src.bzip2" echo "reboot" echo "pkg-static upgrade -f" -echo "mergemaster" +#echo "rm -rf /var/tmp/temproot*" +#echo "mergemaster -a" +echo "etcupdate -t /mnt/common/current-src.bzip2" echo "/root/FreeBSD/scripts/zfs-prune-snapshots -vn -p 'ioc_update' 1d | grep removing" diff --git a/scripts/fan.py b/scripts/fan.py new file mode 100644 index 0000000..46ed243 --- /dev/null +++ b/scripts/fan.py @@ -0,0 +1,14 @@ +# pkgk install py37-pysnmp + +from pysnmp import hlapi + + +def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()): + handler = hlapi.getCmd( + engine, + credentials, + hlapi.UdpTransportTarget((target, port)), + context, + *construct_object_types(oids) + ) + return fetch(handler, 1)[0]