123 lines
4.1 KiB
Plaintext
123 lines
4.1 KiB
Plaintext
|
#!/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)
|