pirogue-images/raspberrypi/misc/raspi-build
Cyril Brulebois 15bed78f6a Add raspi-build script for reference.
That script is deployed in a VM maintained by DEBAMAX, initially building
and publishing raspi_4_bookworm.img.xz(.sha256) every Monday below:

  https://pirogue.apt.debamax.com/raspi-images/
2024-07-22 09:10:44 +02:00

123 lines
4.1 KiB
Python
Executable File

#!/usr/bin/python3
"""
Helper to build Raspberry Pi images.
The PiRogue image generation tool would ideally use daily auto-built images
provided by Debian (https://raspi.debian.net/) but the build process might be
stalled for several weeks or months, so we're building our own image(s) in
a weekly fashion. This makes sure we don't end up shipping PiRogue images
containing severely outdated packages (meaning a possibly significant amount of
missing security/stability fixes).
This script is a wrapper around image-specs's Makefile, and it also helps
maintain the required associated sudo rules.
Generate sudo configuration for the specified image(s):
./raspi-build --sudo raspi_4_bookworm
Build, compress, checksum, and rsync the specified images(s) to a web server
(see RSYNC_DEST):
./raspi-build --build raspi_4_bookworm
"""
import argparse
import hashlib
import logging
import os
import subprocess
import sys
from pathlib import Path
# Settings:
WORK_DIR = Path('~/image-specs').expanduser()
RSYNC_DEST = 'website:pirogue.apt.debamax.com/raspi-images/'
def sudo(images):
"""
Generate the sudo snippet for all specified images.
"""
print(f'# Generated by {Path(__file__).resolve()}')
for image in images:
# Keep in sync with the Makefile in image-specs:
print(f'{login} ALL=NOPASSWD: /usr/bin/vmdb2 --verbose --rootfs-tarball={image}.tar.gz '
f'--output={image}.img {image}.yaml --log {image}.log')
# Keep in sync with root-owned products/byproducts (also noting the
# escaped ':' character):
print(f'{login} ALL=NOPASSWD: /usr/bin/chown {uid}\\:{gid} {WORK_DIR}/{image}.img')
print(f'{login} ALL=NOPASSWD: /usr/bin/chown {uid}\\:{gid} {WORK_DIR}/{image}.tar.gz')
def run(title, *cmd):
"""
Log and exec.
"""
logging.info(title)
subprocess.check_call(*cmd)
def build(images):
"""
Build all specified images.
"""
os.chdir(WORK_DIR)
for image in images:
logging.info('=== BEGIN: WORK WITH %s ===', image)
# Generate the uncompressed images, then fix permissions on our own:
run('Generate image',
['make', f'{image}.img'])
run('Fix image owner',
['sudo', 'chown', f'{uid}:{gid}', f'{WORK_DIR}/{image}.img'])
run('Fix tarball owner',
['sudo', 'chown', f'{uid}:{gid}', f'{WORK_DIR}/{image}.tar.gz'])
# Generate compressed image ourselves to avoid timestamp-related fun if
# we were using the Makefile:
run('Compress image',
['xz', '-9', f'{image}.img'])
# Compute checksum ourselves (same reason as above), without using
# run(): sha256sum doesn't take an output argument so we'd need
# shell=True.
logging.info('Generate checksum')
sha256 = hashlib.file_digest(Path(f'{image}.img.xz').open('rb'), 'sha256')
Path(f'{image}.img.xz.sha256').write_text(f'{sha256.hexdigest()} {image}.img.xz\n')
# Sync only what we need:
run('Sync compressed image and checksum',
['rsync', '-av', *Path('.').glob(f'{image}.img.xz*'), RSYNC_DEST])
logging.info('=== END: WORK WITH %s ===', image)
logging.info('=== BEGIN: CLEAN-UP ===')
run('Cleaning git',
['git', 'clean', '-xdf'])
logging.info('=== END: CLEAN-UP ===')
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO,
datefmt='%H:%M:%S',
format='%(asctime)s %(message)s')
parser = argparse.ArgumentParser()
parser.add_argument('images', metavar='IMAGE', type=str, nargs='+',
help='an image to build (e.g. raspi_4_bookworm)')
parser.add_argument('--sudo', action='store_true',
help='generate the sudo snippet required to build specified images')
parser.add_argument('--build', action='store_true',
help='build the specified images')
args = parser.parse_args()
login = os.getlogin()
uid = os.getuid()
gid = os.getgid()
if args.sudo:
sudo(args.images)
sys.exit(0)
if args.build:
build(args.images)
sys.exit(0)