From 724918a8b7c40b2715c6fb2df19efa3f5806823d Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 1 Oct 2025 01:10:14 +0000 Subject: [PATCH 1/6] format fixes and improve code that finds build user id --- libdeckdebuild/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libdeckdebuild/__init__.py b/libdeckdebuild/__init__.py index 64a5395..f7c05bb 100644 --- a/libdeckdebuild/__init__.py +++ b/libdeckdebuild/__init__.py @@ -86,7 +86,7 @@ def deckdebuild( if exists(chroot): print( f"warning: build chroot deck '{chroot}' exists; removing", - file=sys.stderr + file=sys.stderr, ) system(["deck", "-D", chroot], prefix="undeck") @@ -130,7 +130,7 @@ def deckdebuild( user, "-l", "-c", - 'cat /etc/passwd | grep build | cut -d":" -f3,4', + "grep '^build:' /etc/passwd | cut -d':' -f3,4", ], prefix="get uid", ) @@ -186,6 +186,7 @@ def deckdebuild( ) except Exception: import traceback + traceback.print_exc() error = True finally: @@ -229,9 +230,8 @@ def deckdebuild( else: print(f"built {source_name}_{source_version} successfully") preserve_reason = f"(preserve-build = {preserve_build}; error = {error})" - if ( - preserve_build == "never" - or (not error and preserve_build == "on-error") + if preserve_build == "never" or ( + not error and preserve_build == "on-error" ): print(f"deleting {chroot} {preserve_reason}") os.seteuid(0) From b8d3240c6c37fefdf891ccf577ce02217272987c Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 1 Oct 2025 01:14:15 +0000 Subject: [PATCH 2/6] useful error if not root --- libdeckdebuild/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libdeckdebuild/__init__.py b/libdeckdebuild/__init__.py index f7c05bb..a0910dd 100644 --- a/libdeckdebuild/__init__.py +++ b/libdeckdebuild/__init__.py @@ -80,7 +80,12 @@ def deckdebuild( chroot = join(path_chroots, source_dir) orig_uid = os.getuid() - os.setuid(0) + try: + os.setuid(0) + except PermissionError as e: + raise DeckDebuildError( + "deckdebuild requires root - please rerun with sudo" + ) from e # delete deck if it already exists if exists(chroot): From f1cb9cdc589f8bd4f1107ab86ee64476928c6d29 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 30 Sep 2025 18:00:43 +1000 Subject: [PATCH 3/6] Fix default preserve_build value and add preserve_build validation --- deckdebuild | 2 +- libdeckdebuild/__init__.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/deckdebuild b/deckdebuild index 23769d0..7ce22fa 100755 --- a/deckdebuild +++ b/deckdebuild @@ -115,7 +115,7 @@ def main() -> None: "-e", "--preserve-buildroot-on-error", action="store_const", - const="error", + const="on-error", dest="preserve_buildroot", help="leave build chroot intact after build if failure (default)", ) diff --git a/libdeckdebuild/__init__.py b/libdeckdebuild/__init__.py index a0910dd..8dc92b9 100644 --- a/libdeckdebuild/__init__.py +++ b/libdeckdebuild/__init__.py @@ -55,7 +55,7 @@ def deckdebuild( path: str, buildroot: str, output_dir: str, - preserve_build: str = "error", + preserve_build: str = "on-error", user: str = "build", root_cmd: str = "fakeroot", satisfydepends_cmd: str = "/usr/lib/pbuilder/pbuilder-satisfydepends", @@ -63,6 +63,12 @@ def deckdebuild( vardir: str = "/var/lib/deckdebuilds", build_source: bool = False, ) -> None: + preserve_build_opts = ("never", "always", "on-error") + if preserve_build not in preserve_build_opts: + raise DeckDebuildError( + "invalid preserve_build value, must be one of" + f" {'|'.join(preserve_build_opts)}" + ) vardir = os.fspath(vardir) path_chroots = join(vardir, "chroots") From 283220337a5ed5b95971261ecd6bd00794bf0cdf Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 1 Oct 2025 08:23:41 +1000 Subject: [PATCH 4/6] Fix up error conditions --- libdeckdebuild/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libdeckdebuild/__init__.py b/libdeckdebuild/__init__.py index 8dc92b9..7cf3d93 100644 --- a/libdeckdebuild/__init__.py +++ b/libdeckdebuild/__init__.py @@ -213,6 +213,7 @@ def deckdebuild( # copy packages packages = debsource.get_packages(path) + copied_deb = False for fname in os.listdir(build_dir): if ( not fname.endswith(".deb") @@ -222,7 +223,6 @@ def deckdebuild( and not fname.endswith(".tar.gz") and not fname.endswith(".tar.bz2") ): - error = True continue if fname.split("_")[0] in packages: @@ -231,8 +231,12 @@ def deckdebuild( print(f"# cp {shlex.quote(src)} {shlex.quote(dst)}") shutil.copyfile(src, dst) + # assume that at least one .deb/.udeb copied == success + if fname.endswith("deb"): + copied_deb = True - if error: + if error or not copied_deb: + error = True print( f"building {source_name}_{source_version} package failed" f"\n - see build log ({build_log}) &/or previous output for info", From 287caf83ef64a126fba9df1ea152fdee955913fa Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 30 Sep 2025 17:42:43 +1000 Subject: [PATCH 5/6] fix dpkg-buildpackage call when using faketime (not separate command) --- libdeckdebuild/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdeckdebuild/__init__.py b/libdeckdebuild/__init__.py index 7cf3d93..fdad1bd 100644 --- a/libdeckdebuild/__init__.py +++ b/libdeckdebuild/__init__.py @@ -169,7 +169,7 @@ def deckdebuild( if faketime: faketime_fmt = debsource.get_mtime(path).strftime("%Y-%m-%d %H:%M:%S") - build_cmd += f"faketime -f {shlex.quote(faketime_fmt)};" + build_cmd += f"faketime -f {shlex.quote(faketime_fmt)} " if build_source: build_cmd += ( From a421fb0f88e2d62a597a80795b7c031bf0bb1b74 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 1 Oct 2025 13:57:15 +1000 Subject: [PATCH 6/6] get reliable timestamp for faketime --- libdeckdebuild/__init__.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/libdeckdebuild/__init__.py b/libdeckdebuild/__init__.py index fdad1bd..2fa3c35 100644 --- a/libdeckdebuild/__init__.py +++ b/libdeckdebuild/__init__.py @@ -10,7 +10,9 @@ import os import shlex import shutil +import subprocess import sys +from datetime import datetime, timezone from io import StringIO from os.path import dirname, exists, isdir, join, lexists, relpath @@ -168,8 +170,40 @@ def deckdebuild( build_cmd = f"cd {shlex.quote(source_dir)};" if faketime: - faketime_fmt = debsource.get_mtime(path).strftime("%Y-%m-%d %H:%M:%S") - build_cmd += f"faketime -f {shlex.quote(faketime_fmt)} " + dt_format = "%Y-%m-%d %H:%M:%S" + print("getting timestamp to use with faketime") + # set a timestamp for use with faketime + git_dir = join(chr_source_dir, ".git") + if isdir(git_dir): + # if source is a git repo, use the real mtime of the + # 'debian/control' file via git log - the rationale is: + # - TKL pkg changelog is dynamically generated so last changelog + # date is not reproducable + # - filesystem mtime of git controlled files is also not + # reproducable + get_time_cmd = [ + "/usr/bin/git", + f"--git-dir={git_dir}", + "log", + "-1", + "--format=%ad", + "--date=iso", + "--", + "debian/control", + ] + iso_timestamp = subprocess.run( + get_time_cmd, capture_output=True, text=True + ).stdout.strip() + fake_dt = ( + datetime.fromisoformat(iso_timestamp) + .astimezone(timezone.utc) + .strftime(dt_format) + ) + else: + # fallback to using the last changelog entry date + fake_dt = debsource.get_mtime(path).strftime(dt_format) + print(f"using timestamp {fake_dt}") + build_cmd += f"faketime -f {shlex.quote(fake_dt)} " if build_source: build_cmd += (