#!/usr/bin/env bash set -Eeuo pipefail BUILDER_ROOT=${BUILDER_ROOT:-/opt/rust-package-builder} source "$BUILDER_ROOT/scripts/lib.sh" SOURCE_DIR=${1:?source path required} BINARY=${2:?FreeBSD binary required} PACKAGE_FILE=${3:?output package file required} PACKAGE_VERSION=${4:?FreeBSD package version required} FREEBSD_ABI=${FREEBSD_ABI:-FreeBSD:15:amd64} FREEBSD_ARCH=${FREEBSD_ARCH:-freebsd:15:x86:64} PACKAGING="$BUILDER_ROOT/packaging/freebsd" for tool in python3 sha256sum tar zstd file; do require_cmd "$tool" done [[ -f "$SOURCE_DIR/Cargo.toml" ]] || fatal "missing Cargo.toml: $SOURCE_DIR" [[ -f "$BINARY" ]] || fatal "missing FreeBSD binary: $BINARY" [[ "$(file -b "$BINARY")" == *FreeBSD* ]] || fatal "refusing non-FreeBSD native package: $BINARY" WORK=$(mktemp -d "${PACKAGE_FILE}.work.XXXXXX") trap 'rm -rf "$WORK"' EXIT STAGE="$WORK/stage" META="$WORK/meta" ARCHIVE="$WORK/rustproxy.tar" mkdir -p "$META" "$(dirname "$PACKAGE_FILE")" install -D -m 0555 "$BINARY" "$STAGE/usr/local/sbin/rust-proxy" install -D -m 0555 "$PACKAGING/rustproxy.rc" "$STAGE/usr/local/etc/rc.d/rustproxy" install -D -m 0644 "$PACKAGING/rustproxy.newsyslog" "$STAGE/usr/local/etc/newsyslog.conf.d/rustproxy.conf" python3 - "$STAGE" "$META" "$PACKAGE_VERSION" "$FREEBSD_ABI" "$FREEBSD_ARCH" \ "$PACKAGING/+POST_INSTALL" "$PACKAGING/+PRE_DEINSTALL" <<'PY' import hashlib import json from pathlib import Path import sys stage, meta = map(Path, sys.argv[1:3]) version, abi, arch = sys.argv[3:6] post_install = Path(sys.argv[6]).read_text(encoding='utf-8') pre_deinstall = Path(sys.argv[7]).read_text(encoding='utf-8') files = {} flatsize = 0 for path in sorted(stage.rglob('*')): if path.is_symlink(): files['/' + path.relative_to(stage).as_posix()] = '-' elif path.is_file(): files['/' + path.relative_to(stage).as_posix()] = hashlib.sha256(path.read_bytes()).hexdigest() flatsize += path.stat().st_size common = { 'name': 'rustproxy', 'version': version, 'origin': 'www/rustproxy', 'comment': 'RustProxy reverse proxy for native FreeBSD', 'desc': 'RustProxy daemon and rc.d service for FreeBSD (no OPNsense plugin or dependency).', 'maintainer': 'local@localhost', 'www': 'https://git.linuxiarz.pl/gru/RustProxy', 'prefix': '/usr/local', 'abi': abi, 'arch': arch, 'flatsize': flatsize, 'licenselogic': 'single', 'licenses': ['UNKNOWN'], 'categories': ['www'], 'deps': {}, 'options': {}, } manifest = dict(common) manifest['files'] = files manifest['scripts'] = {'post-install': post_install, 'pre-deinstall': pre_deinstall} (meta / '+COMPACT_MANIFEST').write_text(json.dumps(common, separators=(',', ':')) + '\n') (meta / '+MANIFEST').write_text(json.dumps(manifest, separators=(',', ':')) + '\n') PY ( cd "$STAGE" find . \( -type f -o -type l \) -print | LC_ALL=C sort | sed 's#^\./##' > "$WORK/payload.list" ) [[ -s "$WORK/payload.list" ]] || fatal 'FreeBSD package payload is empty' tar --format=ustar --owner=0 --group=0 --numeric-owner \ -cf "$ARCHIVE" -C "$META" +COMPACT_MANIFEST +MANIFEST tar -P --format=ustar --owner=0 --group=0 --numeric-owner --no-recursion \ --transform='s,^,/,S' \ -rf "$ARCHIVE" -C "$STAGE" -T "$WORK/payload.list" python3 - "$ARCHIVE" "$META/+MANIFEST" <<'PY' from pathlib import Path import json import sys import tarfile pkg = Path(sys.argv[1]) manifest = json.loads(Path(sys.argv[2]).read_text()) with tarfile.open(pkg) as tf: members = tf.getmembers() assert [m.name for m in members[:2]] == ['+COMPACT_MANIFEST', '+MANIFEST'] assert all(not m.isdir() for m in members[2:]), 'package contains directory records' actual = {m.name for m in members[2:] if m.isfile() or m.issym() or m.islnk()} assert actual == set(manifest['files']), 'payload differs from +MANIFEST' assert all(name.startswith('/') for name in actual), 'non-absolute native FreeBSD payload path' PY zstd -q -f -19 -T0 "$ARCHIVE" -o "$PACKAGE_FILE" zstd -dc "$PACKAGE_FILE" | tar -tf - >/dev/null log "created native FreeBSD package: $PACKAGE_FILE"