From 65f26e495a1ddbb9d2f3dbedf266d9c3e41fd726 Mon Sep 17 00:00:00 2001 From: "Max P." Date: Sun, 15 Jun 2025 16:30:03 +0200 Subject: [PATCH] feat: add script for creating self-extracting archives - Introduces a script to build self-extracting archives with validation - Includes payload compression, placeholder replacement, and output creation - Adds a header script to extract and execute the embedded entrypoint --- build_sfx.sh | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ sfx_header.sh | 19 +++++++++++++ 2 files changed, 97 insertions(+) create mode 100755 build_sfx.sh create mode 100644 sfx_header.sh diff --git a/build_sfx.sh b/build_sfx.sh new file mode 100755 index 0000000..3813122 --- /dev/null +++ b/build_sfx.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "Usage: $0 --payload-dir DIR --entrypoint FILE [--output FILE]" + echo " $0 -p DIR -e FILE [-o FILE]" + exit 1 +} + +# 📥 Default values +HEADER="sfx_header.sh" +PAYLOAD_DIR="" +ENTRYPOINT="" +OUTPUT="sfx.run" + +# 📋 Process arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --payload-dir|-p) + PAYLOAD_DIR="$2" + shift 2 + ;; + --entrypoint|-e) + ENTRYPOINT="$2" + shift 2 + ;; + --output|-o) + OUTPUT="$2" + shift 2 + ;; + *) + echo "❌ Unknown option: $1" >&2 + usage + ;; + esac +done + +# ✅ Validation +[[ -z "$PAYLOAD_DIR" || -z "$ENTRYPOINT" ]] && usage +[[ ! -d "$PAYLOAD_DIR" ]] && { echo "❌ Payload directory not found: $PAYLOAD_DIR" >&2; exit 1; } +[[ ! -f "$PAYLOAD_DIR/$ENTRYPOINT" ]] && { echo "❌ Entrypoint not found in payload: $ENTRYPOINT" >&2; exit 1; } + +# 🧊 Temporary directory +BUILD_TMP="$(mktemp -d "${TMPDIR:-/tmp}/sfxbuild.XXXXXX")" +PAYLOAD_ARCHIVE="$BUILD_TMP/payload.tar.zst" +HEADER_TMP="$BUILD_TMP/sfx_header.final" + +cleanup() { + rm -rf "$BUILD_TMP" +} +trap cleanup EXIT + +echo "📦 Creating payload (→ $PAYLOAD_ARCHIVE)..." +tar -cf - -C "$PAYLOAD_DIR" . | tee >(wc -c > "$BUILD_TMP/raw_size") | zstd -19 -T0 -o "$PAYLOAD_ARCHIVE" +RAW_SIZE=$(numfmt --to=iec < "$BUILD_TMP/raw_size") + +# 🧭 Find marker +ARCHIVE_LINE=$(grep -n '^__ARCHIVE_BEGIN__$' "$HEADER" | cut -d: -f1) +[[ ! "$ARCHIVE_LINE" =~ ^[0-9]+$ ]] && { + echo "❌ Error: Marker __ARCHIVE_BEGIN__ not found!" >&2 + exit 1 +} +echo "🧮 Archive starts from line $ARCHIVE_LINE" + +# 🛠️ Replace placeholders +sed "s|__ARCHIVE_LINE__|$ARCHIVE_LINE|" "$HEADER" | \ +sed "s|__ENTRYPOINT__|$ENTRYPOINT|" | \ +sed '/^__ARCHIVE_BEGIN__$/d' > "$HEADER_TMP" + +# 🔗 Combine +cat "$HEADER_TMP" "$PAYLOAD_ARCHIVE" > "$OUTPUT" +chmod +x "$OUTPUT" + +# 📊 Output +FINAL_SIZE=$(du -h "$OUTPUT" | cut -f1) +echo "✅ Done: $OUTPUT" +echo " 📁 Original size: $RAW_SIZE" +echo " 📦 Output file: $FINAL_SIZE" diff --git a/sfx_header.sh b/sfx_header.sh new file mode 100644 index 0000000..3a733d4 --- /dev/null +++ b/sfx_header.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/sfx.XXXXXX")" +ARCHIVE_LINE=__ARCHIVE_LINE__ + +cleanup() { + rm -rf "$TMPDIR" +} +trap cleanup EXIT + +tail -n +$ARCHIVE_LINE "$0" | unzstd | tar -xf - -C "$TMPDIR" + +ENTRYPOINT="$TMPDIR/__ENTRYPOINT__" +chmod +x "$ENTRYPOINT" +"$ENTRYPOINT" "$@" +exit_code=$? +exit $exit_code +__ARCHIVE_BEGIN__