#!/usr/bin/env bash # Nubix — assemble the GPLv3 section 6 "Corresponding Source" archive for a binary release. # # Usage: # scripts/make-source-bundle.sh [--nubix-ref | --nubix-dir ] [--offline] # # Output (all under dist/): # nubix-v-corresponding-source.tar.xz the archive to upload next to the binary # nubix-v-corresponding-source.MANIFEST copy of the MANIFEST inside the archive # nubix-v-corresponding-source.tar.xz.sha256 checksum of the archive # # is taken from `project(nubix VERSION x.y.z ...)` in CMakeLists.txt. # # Nubix's own source is taken from (in this order of precedence): # --nubix-dir every file below (e.g. a clean export of the release tree), # except .git/, build*/, deps/, dist/, .cache/ and docs/ # --nubix-ref `git archive ` (only committed content; use for tagged releases) # default the CURRENT WORKING TREE: `git ls-files -co --exclude-standard`, i.e. # tracked files (incl. uncommitted modifications) plus untracked files that # are not gitignored. docs/ and files deleted from the working tree are left # out; build trees, deps/, dist/ and .cache/ are gitignored. Make sure the # tree is exactly what the binary was built from. # # Third-party sources are downloaded once into .cache/sources/ (tarballs are verified against # pinned sha256 sums; git commits are fetched by full hash and exported with `git archive`), # then copied unmodified into the archive under upstream/. Every included file is listed in # MANIFEST and SHA256SUMS. Re-running the script reuses the cache and rebuilds the archive. # # --offline never touch the network; fail if something is missing from the cache. # # Environment: # LDC_LOCAL_SRC existing libdatachannel checkout whose git objects may be used instead of # fetching (default: .cache/libdatachannel-v0.24.6; it is only read, never # modified — exports come from the pinned commits, not from its working tree). # XZ_OPT read by xz itself (e.g. XZ_OPT=-9e for a slightly smaller outer archive). # # Requirements: bash, git, curl, tar (GNU tar or bsdtar), xz, sha256sum or shasum. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" NUBIX_REF="" NUBIX_DIR="" OFFLINE=0 while [[ $# -gt 0 ]]; do case "$1" in --nubix-ref) NUBIX_REF="${2:?--nubix-ref needs a value}"; shift 2 ;; --nubix-dir) NUBIX_DIR="${2:?--nubix-dir needs a value}"; shift 2 ;; --offline) OFFLINE=1; shift ;; -h|--help) sed -n '2,38p' "$0"; exit 0 ;; *) echo "unknown argument: $1" >&2; exit 2 ;; esac done if [[ -n "${NUBIX_REF}" && -n "${NUBIX_DIR}" ]]; then echo "--nubix-ref and --nubix-dir are mutually exclusive" >&2; exit 2 fi VERSION="$(sed -nE 's/^[[:space:]]*project\(nubix[[:space:]]+VERSION[[:space:]]+([0-9][0-9.]*).*/\1/p' "${ROOT}/CMakeLists.txt" | head -n1)" [[ -n "${VERSION}" ]] || { echo "cannot read project(nubix VERSION ...) from CMakeLists.txt" >&2; exit 1; } NAME="nubix-v${VERSION}-corresponding-source" CACHE="${ROOT}/.cache/sources" GITCACHE="${CACHE}/git" WORK="${ROOT}/.cache/source-bundle" STAGE="${WORK}/${NAME}" DIST="${ROOT}/dist" OUT="${DIST}/${NAME}.tar.xz" LDC_LOCAL_SRC="${LDC_LOCAL_SRC:-${ROOT}/.cache/libdatachannel-v0.24.6}" export COPYFILE_DISABLE=1 # macOS bsdtar: no AppleDouble ._* files export LC_ALL=C log() { printf '>> %s\n' "$*" >&2; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } for t in git tar xz; do command -v "$t" >/dev/null 2>&1 || die "missing tool: $t"; done [[ ${OFFLINE} -eq 1 ]] || command -v curl >/dev/null 2>&1 || die "missing tool: curl" sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' else shasum -a 256 "$1" | awk '{print $1}'; fi } fsize() { wc -c <"$1" | tr -d ' '; } if tar --version 2>/dev/null | grep -q 'GNU tar'; then TAR_NORM=(--owner=0 --group=0 --numeric-owner --format=pax --pax-option=delete=atime,delete=ctime) else TAR_NORM=(--uid 0 --gid 0 --uname root --gname root --format=pax) fi # ------------------------------------------------------------- components --- # Release tarballs: id|version|file|sha256|url [mirror-url ...] # sha256: from the pacbrew-repo v0.40.2 PKGBUILD (which pacbrew verified when building the # SDK libraries) unless noted. FFmpeg's PKGBUILD has sha256sums=SKIP and ffmpeg.org publishes # only PGP signatures, so that sum was computed on download (2026-10-07) and pinned here. TARBALLS=' ffmpeg|7.0.1|ffmpeg-7.0.1.tar.xz|bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff|https://ffmpeg.org/releases/ffmpeg-7.0.1.tar.xz libiconv|1.17|libiconv-1.17.tar.gz|8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313|https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz https://ftpmirror.gnu.org/libiconv/libiconv-1.17.tar.gz openssl|3.5.2|openssl-3.5.2.tar.gz|c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec|https://github.com/openssl/openssl/releases/download/openssl-3.5.2/openssl-3.5.2.tar.gz curl|8.18.0|curl-8.18.0.tar.xz|40df79166e74aa20149365e11ee4c798a46ad57c34e4f68fd13100e2c9a91946|https://curl.se/download/curl-8.18.0.tar.xz https://curl.haxx.se/download/curl-8.18.0.tar.xz libpsl|0.21.5|libpsl-0.21.5.tar.gz|1dcc9ceae8b128f3c0b3f654decd0e1e891afc6ff81098f227ef260449dae208|https://github.com/rockdaboot/libpsl/releases/download/0.21.5/libpsl-0.21.5.tar.gz SDL2_ttf|2.22.0|SDL2_ttf-2.22.0.tar.gz|d48cbd1ce475b9e178206bf3b72d56b66d84d44f64ac05803328396234d67723|https://libsdl.org/projects/SDL_ttf/release/SDL2_ttf-2.22.0.tar.gz https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz freetype|2.13.2|freetype-2.13.2.tar.gz|1ac27e16c134a7f2ccea177faba19801131116fd682efc1f5737037c5db224b5|https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz https://downloads.sourceforge.net/project/freetype/freetype2/2.13.2/freetype-2.13.2.tar.gz libpng|1.6.43|libpng-1.6.43.tar.xz|6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c|https://download.sourceforge.net/libpng/libpng-1.6.43.tar.xz https://downloads.sourceforge.net/project/libpng/libpng16/1.6.43/libpng-1.6.43.tar.xz zlib|1.3.2|zlib-1.3.2.tar.gz|bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16|https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz zstd|1.5.6|zstd-1.5.6.tar.gz|8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1|https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz xz|5.4.6|xz-5.4.6.tar.xz|b92d4e3a438affcf13362a1305cd9d94ed47ddda22e456a42791e630a5644f5c|https://github.com/tukaani-project/xz/releases/download/v5.4.6/xz-5.4.6.tar.xz bzip2|1.0.8|bzip2-1.0.8.tar.gz|ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269|https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz openlibm|0.8.6|openlibm-0.8.6.tar.gz|347998968cfeb2f9b91de6a8e85d2ba92dec0915d53500a4bc483e056f85b94c|https://github.com/JuliaMath/openlibm/archive/refs/tags/v0.8.6.tar.gz opus|1.5.2|opus-1.5.2.tar.gz|65c1d2f78b9f2fb20082c38cbe47c951ad5839345876e46941612ee87f9a7ce1|https://downloads.xiph.org/releases/opus/opus-1.5.2.tar.gz https://github.com/xiph/opus/releases/download/v1.5.2/opus-1.5.2.tar.gz libsamplerate|0.2.2|libsamplerate-0.2.2.tar.xz|3258da280511d24b49d6b08615bbe824d0cacc9842b0e4caf11c52cf2b043893|https://github.com/libsndfile/libsamplerate/releases/download/0.2.2/libsamplerate-0.2.2.tar.xz ' # Git commits: id|version-label|repo-url|full-commit|archive-prefix # Exported with `git archive --prefix=/ | xz -T1 -6`. # The libdatachannel submodule archives use prefixes inside libdatachannel-v0.24.6/ so that # extracting all six into one directory reproduces the recursive checkout. GITSRCS=' libdatachannel|v0.24.6|https://github.com/paullouisageneau/libdatachannel.git|6b1e2e620f1e37f0eafeee702eaea0043cb305fd|libdatachannel-v0.24.6 libjuice|v1.7.4|https://github.com/paullouisageneau/libjuice.git|b89c792e3612faf2f12cf35bcc56857313a06be3|libdatachannel-v0.24.6/deps/libjuice libsrtp|24b3bf8|https://github.com/cisco/libsrtp.git|24b3bf8f19b6f5ab4cd2bcceb4f4064efca86fd5|libdatachannel-v0.24.6/deps/libsrtp usrsctp|fec583d|https://github.com/paullouisageneau/usrsctp.git|fec583d54493f879d2ae44a743423bf8a04371ab|libdatachannel-v0.24.6/deps/usrsctp plog|94899e0|https://github.com/SergiusTheBest/plog.git|94899e0b926ac1b0f4750bfbd495167b4a6ae9ef|libdatachannel-v0.24.6/deps/plog json|55f9368|https://github.com/nlohmann/json.git|55f93686c01528224f448c19128836e7df245f72|libdatachannel-v0.24.6/deps/json x264|r3222-b35605a|https://code.videolan.org/videolan/x264.git|b35605ace3ddf7c1a5d67a2eb553f034aef41d55|x264-b35605a SDL-ps5|2.30.12-a47182a20|https://github.com/ps5-payload-dev/SDL.git|a47182a20e741e4735d1fcd41e8656bf338b0c72|SDL-ps5-a47182a20 ps5-payload-sdk|4eb7012|https://github.com/ps5-payload-dev/sdk.git|4eb701204fc3f8d31e84cf8ca272974e2be9c867|ps5-payload-sdk-4eb7012 pacbrew-repo|v0.40.2|https://github.com/ps5-payload-dev/pacbrew-repo.git|1687e827675c66e1b040751c7f0811222bd3aea0|pacbrew-repo-v0.40.2 ' # ---------------------------------------------------------------- helpers --- fetch_tarball() { # file sha256 urls... local file="$1" want="$2"; shift 2 local dst="${CACHE}/${file}" if [[ -f "${dst}" ]]; then [[ "$(sha256 "${dst}")" == "${want}" ]] && return 0 log "cached ${file} has a wrong checksum, re-downloading" rm -f "${dst}" fi [[ ${OFFLINE} -eq 0 ]] || die "${file} not in cache and --offline given" local url got for url in "$@"; do log "downloading ${url}" if curl -fsSL --retry 3 --connect-timeout 30 -o "${dst}.part" "${url}"; then got="$(sha256 "${dst}.part")" if [[ "${got}" == "${want}" ]]; then mv "${dst}.part" "${dst}"; return 0; fi log "checksum mismatch for ${url}: got ${got}, want ${want}" fi rm -f "${dst}.part" done die "could not download ${file} with sha256 ${want}" } have_commit() { git -C "$1" cat-file -e "$2^{commit}" 2>/dev/null; } # Find a git dir that already has : our bare cache, or the read-only libdatachannel checkout. local_git_for() { # id commit -> prints dir or nothing local id="$1" commit="$2" d local cands=("${GITCACHE}/${id}.git") if [[ -d "${LDC_LOCAL_SRC}" ]]; then case "${id}" in libdatachannel) cands+=("${LDC_LOCAL_SRC}") ;; libjuice|libsrtp|usrsctp|plog|json) cands+=("${LDC_LOCAL_SRC}/deps/${id}") ;; esac fi for d in "${cands[@]}"; do [[ -e "${d}" ]] && have_commit "${d}" "${commit}" && { echo "${d}"; return 0; } done return 0 } fetch_commit() { # id repo commit -> prints git dir containing commit local id="$1" repo="$2" commit="$3" d d="$(local_git_for "${id}" "${commit}")" if [[ -n "${d}" ]]; then echo "${d}"; return 0; fi [[ ${OFFLINE} -eq 0 ]] || die "commit ${commit} of ${id} not in cache and --offline given" d="${GITCACHE}/${id}.git" [[ -d "${d}" ]] || git init -q --bare "${d}" log "fetching ${repo} ${commit}" if ! git -C "${d}" fetch -q --depth 1 "${repo}" "${commit}" >&2; then log "fetch by hash refused, fetching all branches and tags of ${repo}" git -C "${d}" fetch -q "${repo}" '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' >&2 fi have_commit "${d}" "${commit}" || die "commit ${commit} not found in ${repo}" echo "${d}" } export_commit() { # id label repo commit prefix -> prints archive file name (in CACHE) local id="$1" label="$2" repo="$3" commit="$4" prefix="$5" local file="${id}-${label}-${commit:0:12}.tar.xz" dst d dst="${CACHE}/${file}" if [[ ! -f "${dst}" ]]; then d="$(fetch_commit "${id}" "${repo}" "${commit}")" log "exporting ${id} ${commit} from ${d}" git -C "${d}" archive --format=tar --prefix="${prefix}/" "${commit}" | xz -T1 -6 -c >"${dst}.part" mv "${dst}.part" "${dst}" fi echo "${file}" } # ------------------------------------------------------------ nubix source --- nubix_source() { # -> writes ${STAGE}/nubix-v${VERSION}-src.tar.xz, prints description local top="nubix-v${VERSION}" tmp="${WORK}/nubix-tree" local out="${STAGE}/${top}-src.tar.xz" rm -rf "${tmp}"; mkdir -p "${tmp}/${top}" if [[ -n "${NUBIX_REF}" ]]; then git -C "${ROOT}" archive --format=tar "${NUBIX_REF}" | tar -xf - -C "${tmp}/${top}" rm -rf "${tmp}/${top}/docs" echo "git archive $(git -C "${ROOT}" rev-parse "${NUBIX_REF}^{commit}") (docs/ excluded)" elif [[ -n "${NUBIX_DIR}" ]]; then local src; src="$(cd "${NUBIX_DIR}" && pwd)" (cd "${src}" && find . -type f \ ! -path './.git/*' ! -path './.git' ! -path './build*' ! -path './deps/*' \ ! -path './dist/*' ! -path './.cache/*' ! -path './docs/*' ! -name '.DS_Store' -print0 \ | tar -cf - --null -T -) | tar -xf - -C "${tmp}/${top}" echo "directory ${src##*/}" else (cd "${ROOT}" && git ls-files -z -co --exclude-standard \ | while IFS= read -r -d '' f; do case "$f" in docs/*|dist/*|deps/*|.cache/*|build*/*) continue ;; esac [[ -f "$f" || -L "$f" ]] && printf '%s\0' "$f" done \ | tar -cf - --null -T -) | tar -xf - -C "${tmp}/${top}" local head dirty head="$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo none)" dirty="$(git -C "${ROOT}" status --porcelain 2>/dev/null | grep -vc '^?? docs/' || true)" echo "working tree of ${ROOT##*/} at HEAD ${head} (${dirty} uncommitted/untracked paths; docs/ excluded)" fi (cd "${tmp}" && find "${top}" -print0 | LC_ALL=C sort -z \ | tar -cf - "${TAR_NORM[@]}" --no-recursion --null -T -) | xz -T1 -6 -c >"${out}" rm -rf "${tmp}" } # ------------------------------------------------------------------ main ---- mkdir -p "${CACHE}" "${GITCACHE}" "${DIST}" rm -rf "${WORK}"; mkdir -p "${STAGE}/upstream" MANIFEST_ROWS=() # path|component|version|origin log "Nubix ${VERSION}: packaging own source" NUBIX_DESC="$(nubix_source)" MANIFEST_ROWS+=("nubix-v${VERSION}-src.tar.xz|nubix|${VERSION}|${NUBIX_DESC}") while IFS='|' read -r id ver file sum urls; do [[ -n "${id}" ]] || continue # shellcheck disable=SC2086 fetch_tarball "${file}" "${sum}" ${urls} cp "${CACHE}/${file}" "${STAGE}/upstream/${file}" MANIFEST_ROWS+=("upstream/${file}|${id}|${ver}|${urls%% *}") done <<<"${TARBALLS}" while IFS='|' read -r id label repo commit prefix; do [[ -n "${id}" ]] || continue file="$(export_commit "${id}" "${label}" "${repo}" "${commit}" "${prefix}")" cp "${CACHE}/${file}" "${STAGE}/upstream/${file}" MANIFEST_ROWS+=("upstream/${file}|${id}|${label}|${repo} @ ${commit}") done <<<"${GITSRCS}" [[ -f "${ROOT}/SOURCE.txt" ]] && cp "${ROOT}/SOURCE.txt" "${STAGE}/SOURCE.txt" [[ -f "${ROOT}/LICENSE" ]] && cp "${ROOT}/LICENSE" "${STAGE}/LICENSE" cat >"${STAGE}/README.txt" <"${STAGE}/MANIFEST" (cd "${STAGE}" && find . -type f ! -name SHA256SUMS ! -name MANIFEST | sed 's|^\./||' | LC_ALL=C sort \ | while IFS= read -r p; do printf '%s %s\n' "$(sha256 "$p")" "$p"; done) >"${STAGE}/SHA256SUMS" log "writing ${OUT}" rm -f "${OUT}.part" (cd "${WORK}" && find "${NAME}" -print0 | LC_ALL=C sort -z \ | tar -cf - "${TAR_NORM[@]}" --no-recursion --null -T -) | xz -6 -T0 -c >"${OUT}.part" mv "${OUT}.part" "${OUT}" cp "${STAGE}/MANIFEST" "${DIST}/${NAME}.MANIFEST" (cd "${DIST}" && printf '%s %s\n' "$(sha256 "${NAME}.tar.xz")" "${NAME}.tar.xz" >"${NAME}.tar.xz.sha256") rm -rf "${WORK}" log "done: ${OUT} ($(fsize "${OUT}") bytes)" cat "${DIST}/${NAME}.MANIFEST"