Class: Tebako::Stitcher
- Inherits:
-
Object
- Object
- Tebako::Stitcher
- Defined in:
- lib/tebako/stitcher.rb
Overview
Stitches tebako images onto a prebuilt runtime binary, producing a single-file package with a tpkg manifest trailer (spec §4.3).
Wire layout (all integers little-endian), appended after the runtime:
[runtime bytes][pad to 8][image 0][pad to 8]...[image n-1]
[slot table: slot_count x 280 bytes][trailer header: 166 bytes at EOF]
This is a pure-Ruby reimplementation of the tpkg.h writer (libtfs include/tebako/tpkg.h); the byte stream is identical to tpkg_write_fd() for the same manifest. CRC32 is the zlib polynomial (0xEDB88320, init/xorout 0xFFFFFFFF) — exactly what Zlib.crc32 computes.
Codesigning: appending bytes invalidates any embedded code signature.
On macOS a signed runtime (ad-hoc included, detected via codesign -dv)
is re-signed ad-hoc after stitching (codesign --remove-signature, then
codesign --sign - --force). Note that codesign(1) refuses to re-sign thin
Mach-O binaries carrying trailing payload ("main executable failed strict
validation"), so the re-sign is best-effort: on failure a warning is
printed and the package is kept — the ad-hoc linker signature is
invalidated by construction, but the binary still executes on macOS
(verified on macOS 14/arm64). Re-signing with a real identity remains a
user post-press step. An unsigned runtime is left alone.
On Windows signing is a no-op — re-applying an Authenticode signature
(signtool) is left to the user as a post-press step.
Constant Summary collapse
- MAGIC =
rubocop:disable Metrics/ClassLength
"TEBAKOTFS\0".b
- VERSION =
10 bytes, NUL-terminated
1- MAX_SLOTS =
8- HEADER_SIZE =
166- SLOT_SIZE =
280- MOUNT_POINT_LEN =
256- RUNTIME_REF_LEN =
128- ALIGNMENT =
8- FLAG_LEAN =
0x1- FORMAT_AUTO =
0- FORMAT_DWARFS =
1- FORMAT_SQUASHFS =
2- FORMAT_ZIP =
3- FORMAT_RUNTIME =
Runtime payload slot of a fat package: installed into the shared cache by the bootstrap at first run, never mounted (tpkg.h TPKG_FORMAT_RUNTIME)
4- FORMAT_IDS =
(FORMAT_AUTO..FORMAT_RUNTIME).freeze
- OFF_PACKAGE_FLAGS =
Header field offsets
14- OFF_SLOT_COUNT =
18- OFF_TABLE =
22- OFF_RUNTIME_REF =
30- OFF_CRC32 =
162
Class Method Summary collapse
-
.adhoc_resign(path) ⇒ Object
Re-apply an ad-hoc signature after the binary was mutated; true on success.
- .macos? ⇒ Boolean
-
.signed?(path) ⇒ Boolean
True when
pathcarries any code signature (ad-hoc included). -
.stitch(runtime_path, images:, output:, lean: false, ruby_version: nil, tebako_version: Tebako::VERSION, launcher_abi: 0, runtime_sha256: nil) ⇒ Object
Stitch
images(Array of mount_point:, format_id:) onto a copy of the runtime atruntime_path, writingoutput.
Class Method Details
.adhoc_resign(path) ⇒ Object
Re-apply an ad-hoc signature after the binary was mutated; true on success. codesign(1) refuses to re-sign thin Mach-O binaries with trailing payload (strict validation) — callers treat failure as non-fatal (see the class comment).
130 131 132 133 |
# File 'lib/tebako/stitcher.rb', line 130 def adhoc_resign(path) system("codesign", "--remove-signature", path, out: File::NULL, err: File::NULL) && system("codesign", "--sign", "-", "--force", path, out: File::NULL, err: File::NULL) end |
.macos? ⇒ Boolean
114 115 116 |
# File 'lib/tebako/stitcher.rb', line 114 def macos? RbConfig::CONFIG["host_os"] =~ /darwin/i ? true : false end |
.signed?(path) ⇒ Boolean
True when path carries any code signature (ad-hoc included)
119 120 121 122 123 124 |
# File 'lib/tebako/stitcher.rb', line 119 def signed?(path) _out, _err, status = Open3.capture3("codesign", "-dv", path) status.success? rescue Errno::ENOENT false end |
.stitch(runtime_path, images:, output:, lean: false, ruby_version: nil, tebako_version: Tebako::VERSION, launcher_abi: 0, runtime_sha256: nil) ⇒ Object
Stitch images (Array of mount_point:, format_id:) onto a
copy of the runtime at runtime_path, writing output.
lean: true marks the package LEAN and records runtime_ref
("ruby@<ruby_version>;tebako=<tebako_version>"); classic packages
carry an empty runtime_ref. runtime_sha256 appends ";sha256=
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/tebako/stitcher.rb', line 98 def stitch(runtime_path, images:, output:, lean: false, ruby_version: nil, # rubocop:disable Metrics/ParameterLists tebako_version: Tebako::VERSION, launcher_abi: 0, runtime_sha256: nil) images = normalize_images(images) validate_inputs!(runtime_path, images, lean, ruby_version) validate_runtime_sha256!(runtime_sha256) FileUtils.mkdir_p(File.dirname(output)) FileUtils.cp(runtime_path, output) FileUtils.chmod(0o755, output) append(images, output, package_flags(lean), runtime_ref(lean, ruby_version, tebako_version, runtime_sha256), launcher_abi) resign_if_needed(output) output end |