summaryrefslogtreecommitdiff
path: root/scripts/image-build/build-vyos-image
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/image-build/build-vyos-image')
-rwxr-xr-xscripts/image-build/build-vyos-image210
1 files changed, 143 insertions, 67 deletions
diff --git a/scripts/image-build/build-vyos-image b/scripts/image-build/build-vyos-image
index 80b4d61d..3275c5de 100755
--- a/scripts/image-build/build-vyos-image
+++ b/scripts/image-build/build-vyos-image
@@ -25,6 +25,7 @@ import copy
import uuid
import glob
import json
+import base64
import shutil
import argparse
import datetime
@@ -62,7 +63,7 @@ except Exception as e:
# Checkout vyos-1x under build directory
try:
branch_name = build_defaults['vyos_branch']
- url_vyos_1x = 'https://github.com/vyos/vyos-1x'
+ url_vyos_1x = os.getenv('VYOS1X_REPO_URL', default='https://github.com/vyos/vyos-1x')
path_vyos_1x = os.path.join(defaults.BUILD_DIR, 'vyos-1x')
try:
repo_vyos_1x = git.Repo.clone_from(url_vyos_1x, path_vyos_1x, no_checkout=True)
@@ -191,9 +192,10 @@ if __name__ == "__main__":
'pbuilder-debian-mirror': ('Debian repository mirror for pbuilder env bootstrap', None),
'vyos-mirror': ('VyOS package mirror', None),
'build-type': ('Build type, release or development', lambda x: x in ['release', 'development']),
- 'version': ('Version number (release builds only)', None),
+ 'version': ('Version string', None),
'build-comment': ('Optional build comment', None),
- 'build-hook-opts': ('Custom options for the post-build hook', None)
+ 'build-hook-opts': ('Custom options for the post-build hook', None),
+ 'bootloaders': ('Bootloaders to include in the image', None)
}
# Create the option parser
@@ -259,28 +261,24 @@ if __name__ == "__main__":
pre_build_config = merge_defaults(args, defaults=pre_build_config, skip_none=True)
# Some fixup for mirror settings.
- # The idea is: if --debian-mirror is specified but --pbuilder-debian-mirror is not,
- # use the --debian-mirror value for both lb and pbuilder bootstrap
- if pre_build_config['debian_mirror'] is None or pre_build_config['debian_security_mirror'] is None:
- print("E: debian_mirror and debian_security_mirror cannot be empty")
+ # The idea is: if --debian-mirror is specified
+ # but --pbuilder-debian-mirror or --debian-security-mirror are not,
+ # use the --debian-mirror value for those
+ if pre_build_config['debian_mirror'] is None:
+ print("E: debian_mirror must be specified")
sys.exit(1)
if pre_build_config['pbuilder_debian_mirror'] is None:
- args['pbuilder_debian_mirror'] = pre_build_config['pbuilder_debian_mirror'] = pre_build_config['debian_mirror']
-
- # Version can only be set for release builds,
- # for dev builds it hardly makes any sense
- if pre_build_config['build_type'] == 'development':
- if args['version'] is not None:
- print("E: Version can only be set for release builds")
- print("Use --build-type=release option if you want to set version number")
- sys.exit(1)
+ pre_build_config['pbuilder_debian_mirror'] = pre_build_config['debian_mirror']
+
+ if pre_build_config['debian_security_mirror'] is None:
+ pre_build_config['debian_security_mirror'] = pre_build_config['debian_mirror']
# Validate characters in version name
- if 'version' in args and args['version'] != None:
+ if args.get('version'):
allowed = string.ascii_letters + string.digits + '.' + '-' + '+'
if not set(args['version']) <= set(allowed):
- print(f'Version contained illegal character(s), allowed: {allowed}')
+ print(f'Version string contains illegal character(s), allowed: {allowed}')
sys.exit(1)
## Inject some useful hardcoded options
@@ -306,6 +304,11 @@ if __name__ == "__main__":
build_config = merge_defaults(flavor_config, defaults=build_config)
build_config = merge_defaults(args, defaults=build_config, skip_none=True)
+ # If Debian mirror is specified explicitly but Debian security mirror is not,
+ # assume that the user wants to use that mirror for security updates as well.
+ if (args['debian_mirror'] is not None) and (args['debian_security_mirror'] is None):
+ build_config['debian_security_mirror'] = args['debian_mirror']
+
## Rename and merge some fields for simplicity
## E.g. --custom-packages is for the user, but internally
## it's added to the same package list as everything else
@@ -326,6 +329,10 @@ if __name__ == "__main__":
print("E: image format is not specified in the build flavor file")
sys.exit(1)
+ ## Override bootloaders if specified
+ if args['bootloaders'] is not None:
+ build_config['bootloaders'] = args['bootloaders']
+
## Add default boot settings if needed
if "boot_settings" not in build_config:
build_config["boot_settings"] = defaults.boot_settings
@@ -337,6 +344,17 @@ if __name__ == "__main__":
if type(build_config["image_format"]) != list:
build_config["image_format"] = [ build_config["image_format"] ]
+ ## If the user didn't explicitly specify what extensions build artifact should have,
+ ## assume that the list is the same as image formats.
+ ## One case when it's not the same is when a custom build hook is used
+ ## to build a format that our build script doesn't support natively.
+ if not has_nonempty_key(build_config, "artifact_format"):
+ build_config["artifact_format"] = build_config["image_format"]
+ else:
+ # If the option is there, also make it list if it's a scalar
+ if type(build_config["artifact_format"]) != list:
+ build_config["artifact_format"] = [ build_config["artifact_format"] ]
+
## Dump the complete config if the user enabled debug mode
if debug:
import json
@@ -349,6 +367,11 @@ if __name__ == "__main__":
shutil.copytree("data/live-build-config/", lb_config_dir)
os.makedirs(lb_config_dir, exist_ok=True)
+ ## Secure Boot - Copy public Keys to image
+ sb_certs = 'data/certificates'
+ if os.path.isdir(sb_certs):
+ shutil.copytree(sb_certs, f'{lb_config_dir}/includes.chroot/var/lib/shim-signed/mok')
+
# Switch to the build directory, this is crucial for the live-build work
# because the efective build config files etc. are there.
#
@@ -401,8 +424,10 @@ if __name__ == "__main__":
build_git = ""
git_branch = ""
- # Create the build version string
- if build_config['build_type'] == 'development':
+ # Create the build version string, if it's not explicitly given
+ if build_config.get('version'):
+ version = build_config['version']
+ else:
try:
if not git_branch:
raise ValueError("git branch could not be determined")
@@ -417,14 +442,8 @@ if __name__ == "__main__":
except Exception as e:
print("W: Could not build a version string specific to git branch, falling back to default: {0}".format(str(e)))
version = "999.{0}".format(build_timestamp)
- else:
- # Release build, use the version from ./configure arguments
- version = build_config['version']
- if build_config['build_type'] == 'development':
- lts_build = False
- else:
- lts_build = True
+ build_config['version'] = version
version_data = {
'version': version,
@@ -436,7 +455,7 @@ if __name__ == "__main__":
'build_branch': git_branch,
'release_train': build_config['release_train'],
'architecture': build_config['architecture'],
- 'lts_build': lts_build,
+ 'build_type': build_config['build_type'],
'build_comment': build_config['build_comment'],
'bugtracker_url': build_config['bugtracker_url'],
'documentation_url': build_config['documentation_url'],
@@ -446,19 +465,18 @@ if __name__ == "__main__":
# Multi line strings needs to be un-indented to not have leading
# whitespaces in the resulting file
- os_release = f"""
- PRETTY_NAME="VyOS {version} ({build_config['release_train']})"
- NAME="VyOS"
- VERSION_ID="{version}"
- VERSION="{version} ({build_config['release_train']})"
- VERSION_CODENAME={build_defaults['debian_distribution']}
- ID=vyos
- BUILD_ID="{build_git}"
- HOME_URL="{build_defaults['website_url']}"
- SUPPORT_URL="{build_defaults['support_url']}"
- BUG_REPORT_URL="{build_defaults['bugtracker_url']}"
- DOCUMENTATION_URL="{build_config['documentation_url']}"
- """
+ os_release = f"""PRETTY_NAME="VyOS {version} ({build_config['release_train']})"
+NAME="VyOS"
+VERSION_ID="{version}"
+VERSION="{version} ({build_config['release_train']})"
+VERSION_CODENAME={build_defaults['release_train']}
+ID=vyos
+BUILD_ID="{build_git}"
+HOME_URL="{build_defaults['website_url']}"
+SUPPORT_URL="{build_defaults['support_url']}"
+BUG_REPORT_URL="{build_defaults['bugtracker_url']}"
+DOCUMENTATION_URL="{build_config['documentation_url']}"
+"""
# Reminder: all paths relative to the build dir, not to the repository root
chroot_includes_dir = defaults.CHROOT_INCLUDES_DIR
@@ -478,8 +496,8 @@ if __name__ == "__main__":
print("Version: {0}".format(version), file=f)
# Define variables that influence to welcome message on boot
- os.makedirs(os.path.join(chroot_includes_dir, 'usr/lib/'), exist_ok=True)
- with open(os.path.join(chroot_includes_dir, 'usr/lib/os-release'), 'w') as f:
+ os.makedirs(os.path.join(chroot_includes_dir, 'etc/'), exist_ok=True)
+ with open(os.path.join(chroot_includes_dir, 'etc/os-release'), 'w') as f:
print(os_release, file=f)
## Clean up earlier build state and artifacts
@@ -498,8 +516,9 @@ if __name__ == "__main__":
## Create live-build configuration files
# Add the additional repositories to package lists
- print("I: Setting up additional APT entries")
+ print("I: Setting up VyOS repository APT entries")
vyos_repo_entry = "deb {vyos_mirror} {vyos_branch} main\n".format(**build_config)
+ vyos_repo_entry += "deb-src {vyos_mirror} {vyos_branch} main\n".format(**build_config)
apt_file = defaults.VYOS_REPO_FILE
@@ -511,10 +530,36 @@ if __name__ == "__main__":
f.write(vyos_repo_entry)
# Add custom APT entries
+ print("I: Setting up additional APT entries")
if build_config.get('additional_repositories', False):
- build_config['custom_apt_entry'] += build_config['additional_repositories']
+ for r in build_config['additional_repositories']:
+ repo_data = build_config['additional_repositories'][r]
+
+ url = repo_data.get('url', None)
+ arch = repo_data.get('architecture', None)
+ distro = repo_data.get('distribution', build_config['debian_distribution'])
+ components = repo_data.get('components', 'main')
- if build_config.get('custom_apt_entry', False):
+ if not url:
+ print(f'E: repository {r} does not specify URL')
+ sys.exit(1)
+
+ if arch:
+ arch_string = f'[arch={arch}]'
+ else:
+ arch_string = ''
+
+ entry = f'deb {arch_string} {url} {distro} {components}'
+ build_config['custom_apt_entry'].append(entry)
+
+ if not repo_data.get('no_source', False):
+ src_entry = f'deb-src {url} {distro} {components}'
+ build_config['custom_apt_entry'].append(src_entry)
+
+ if repo_data.get('key', None):
+ build_config['custom_apt_keys'].append({'name': r, 'key': repo_data['key']})
+
+ if build_config.get('custom_apt_entry', []):
custom_apt_file = defaults.CUSTOM_REPO_FILE
entries = "\n".join(build_config['custom_apt_entry'])
if debug:
@@ -525,11 +570,13 @@ if __name__ == "__main__":
f.write("\n")
# Add custom APT keys
- if has_nonempty_key(build_config, 'custom_apt_key'):
+ if has_nonempty_key(build_config, 'custom_apt_keys'):
key_dir = defaults.ARCHIVES_DIR
- for k in build_config['custom_apt_key']:
- dst_name = '{0}.key.chroot'.format(os.path.basename(k))
- shutil.copy(k, os.path.join(key_dir, dst_name))
+ for k in build_config['custom_apt_keys']:
+ dst_name = '{0}.key.chroot'.format(k['name'])
+ with open(os.path.join(key_dir, dst_name), 'bw') as f:
+ key_data = base64.b64decode(k['key'])
+ f.write(key_data)
# Add custom packages
if has_nonempty_key(build_config, 'packages'):
@@ -548,6 +595,15 @@ if __name__ == "__main__":
with open(file_path, 'w') as f:
f.write(i["data"])
+ if has_nonempty_key(build_config, "includes_binary"):
+ for i in build_config["includes_binary"]:
+ file_path = os.path.join(binary_includes_dir, i["path"])
+ if debug:
+ print(f"D: Creating binary image include file: {file_path}")
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
+ with open(file_path, 'w') as f:
+ f.write(i["data"])
+
## Create the default config
## Technically it's just another includes.chroot entry,
## but it's special enough to warrant making it easier for flavor writers
@@ -560,35 +616,37 @@ if __name__ == "__main__":
## Configure live-build
lb_config_tmpl = jinja2.Template("""
lb config noauto \
+ --no-color \
--apt-indices false \
- --apt-options "--yes -oAPT::Get::allow-downgrades=true" \
+ --apt-options "--yes" \
--apt-recommends false \
- --architecture {{architecture}} \
- --archive-areas {{debian_archive_areas}} \
+ --architecture "{{architecture}}" \
+ --archive-areas "{{debian_archive_areas}}" \
--backports true \
--binary-image iso-hybrid \
--bootappend-live "boot=live components hostname=vyos username=live nopersistence noautologin nonetworking union=overlay console=ttyS0,115200 console=tty0 net.ifnames=0 biosdevname=0" \
--bootappend-live-failsafe "live components memtest noapic noapm nodma nomce nolapic nomodeset nosmp nosplash vga=normal console=ttyS0,115200 console=tty0 net.ifnames=0 biosdevname=0" \
- --bootloaders {{bootloaders}} \
- --checksums 'sha256 md5' \
+ --bootloaders "{{bootloaders}}" \
+ --checksums "sha256" \
--chroot-squashfs-compression-type "{{squashfs_compression_type}}" \
--debian-installer none \
--debootstrap-options "--variant=minbase --exclude=isc-dhcp-client,isc-dhcp-common,ifupdown --include=apt-utils,ca-certificates,gnupg2,linux-kbuild-6.1" \
- --distribution {{debian_distribution}} \
+ --distribution "{{debian_distribution}}" \
--firmware-binary false \
--firmware-chroot false \
--iso-application "VyOS" \
--iso-publisher "{{build_by}}" \
--iso-volume "VyOS" \
- --linux-flavours {{kernel_flavor}} \
- --linux-packages linux-image-{{kernel_version}} \
- --mirror-binary {{debian_mirror}} \
- --mirror-binary-security {{debian_security_mirror}} \
- --mirror-bootstrap {{debian_mirror}} \
- --mirror-chroot {{debian_mirror}} \
- --mirror-chroot-security {{debian_security_mirror}} \
+ --linux-flavours "{{kernel_flavor}}" \
+ --linux-packages "linux-image-{{kernel_version}}" \
+ --mirror-binary "{{debian_mirror}}" \
+ --mirror-binary-security "{{debian_security_mirror}}" \
+ --mirror-bootstrap "{{debian_mirror}}" \
+ --mirror-chroot "{{debian_mirror}}" \
+ --mirror-chroot-security "{{debian_security_mirror}}" \
--security true \
- --updates true
+ --updates true \
+ --utc-time true
"${@}"
""")
@@ -631,11 +689,14 @@ Pin-Priority: 600
# Copy the image
shutil.copy("live-image-{0}.hybrid.iso".format(build_config["architecture"]), iso_file)
+ # Add the image to the manifest
+ manifest['artifacts'].append(iso_file)
+
# If the flavor has `image_format = "iso"`, then the work is done.
# If not, build additional flavors from the ISO.
if build_config["image_format"] != ["iso"]:
# For all non-iso formats, we always build a raw image first
- raw_image = raw_image.create_raw_image(build_config, iso_file, "tmp/")
+ version_data, raw_image = raw_image.create_raw_image(build_config, iso_file, "tmp/")
manifest['artifacts'].append(raw_image)
# If there are other formats in the flavor, the assumptions is that
@@ -665,9 +726,24 @@ Pin-Priority: 600
hook_opts = build_config["build_hook_opts"]
else:
hook_opts = ""
- custom_image = rc_cmd(f"./build_hook {raw_image} {build_config['version']} \
- {build_config['architecture']} {hook_opts}")
+ build_hook_command = f"./build_hook {raw_image} {version_data['version']} \
+ {build_config['architecture']} {hook_opts}"
+ print(f'I: executing build hook command: {build_hook_command}')
+ custom_image = rc_cmd(build_hook_command)
manifest['artifacts'].append(custom_image)
+ # Filter out unwanted files from the artifact list
+ # and leave only those the user specified
+ # in either `artifact_format` or `image_format`.
+ #
+ # For example, with `image_format = "raw"`,
+ # the ISO image is just an intermediate object, not an target artifact.
+
+ # os.path.splitext returns extensions with dots,
+ # so we need to remove the dots, hence [1:]
+ is_artifact = lambda f: os.path.splitext(f)[-1][1:] in build_config['artifact_format']
+
+ manifest['artifacts'] = list(filter(is_artifact, manifest['artifacts']))
+
with open('manifest.json', 'w') as f:
f.write(json.dumps(manifest))