Files
rustproxy-opnsense-repo/builder/debian/package-freebsd.sh
T
2026-09-29 15:57:07 +02:00

207 lines
6.9 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SOURCE_DIR="${RUSTPROXY_SOURCE_DIR:?RUSTPROXY_SOURCE_DIR is required}"
BINARY="${RUSTPROXY_BINARY:?RUSTPROXY_BINARY is required}"
OPNSENSE_ABI="${OPNSENSE_ABI:-26.7}"
FREEBSD_ABI="${FREEBSD_ABI:-FreeBSD:15:amd64}"
FREEBSD_ARCH="${FREEBSD_ARCH:-freebsd:15:x86:64}"
SOURCE_REVISION="${RUSTPROXY_SOURCE_REVISION:-0}"
SOURCE_COMMIT="${RUSTPROXY_SOURCE_COMMIT:-unknown}"
SOURCE_URL="${RUSTPROXY_SOURCE_URL:-https://git.linuxiarz.pl/gru/RustProxy}"
SOURCE_REF="${RUSTPROXY_SOURCE_REF:-HEAD}"
for tool in python3 sha256sum tar zstd file; do
command -v "$tool" >/dev/null 2>&1 || { echo "Missing tool: $tool" >&2; exit 1; }
done
[[ -f "$SOURCE_DIR/Cargo.toml" ]] || { echo "Missing Cargo.toml in $SOURCE_DIR" >&2; exit 1; }
[[ -f "$BINARY" ]] || { echo "Missing cross-built binary: $BINARY" >&2; exit 1; }
binary_desc="$(file -b "$BINARY")"
echo "$BINARY: $binary_desc"
if [[ "${ALLOW_NON_FREEBSD_BINARY:-0}" != "1" ]] && [[ "$binary_desc" != *FreeBSD* ]]; then
echo "Refusing to package a binary that is not identified as FreeBSD." >&2
exit 1
fi
app_version="$(awk -F '"' '/^version = "/ {print $2; exit}' "$SOURCE_DIR/Cargo.toml")"
[[ -n "$app_version" ]] || { echo "Cannot read app version" >&2; exit 1; }
if [[ "$SOURCE_REVISION" == "0" ]]; then
pkg_version="$app_version"
else
pkg_version="${app_version}_${SOURCE_REVISION}"
fi
work="$ROOT/dist/.package-work"
stage="$work/stage"
meta="$work/meta"
repo_dir="$ROOT/dist/packages/$OPNSENSE_ABI/amd64"
package_file="$repo_dir/os-rustproxy-${pkg_version}.pkg"
rm -rf "$work" "$ROOT/dist/packages"
mkdir -p "$stage/usr/local" "$meta" "$repo_dir"
# OPNsense plugin files map directly below /usr/local.
cp -a "$ROOT/opnsense-plugin/www/rustproxy/src/." "$stage/usr/local/"
mkdir -p "$stage/usr/local/sbin"
install -m 0555 "$BINARY" "$stage/usr/local/sbin/rust-proxy"
# Ensure executable hooks/scripts keep executable mode in the package.
chmod 0555 "$stage/usr/local/etc/rc.d/rustproxy"
chmod 0555 "$stage/usr/local/etc/rc.syshook.d/10-rustproxy.start"
chmod 0555 "$stage/usr/local/opnsense/scripts/rustproxy/service.sh"
POST_INSTALL_FILE="$ROOT/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre"
PRE_DEINSTALL_FILE="$ROOT/opnsense-plugin/www/rustproxy/+PRE_DEINSTALL.pre"
python3 - "$stage" "$meta" "$pkg_version" "$FREEBSD_ABI" "$FREEBSD_ARCH" \
"$POST_INSTALL_FILE" "$PRE_DEINSTALL_FILE" <<'PY'
import hashlib
import json
import os
from pathlib import Path
import sys
stage = Path(sys.argv[1])
meta_dir = Path(sys.argv[2])
version = sys.argv[3]
abi = sys.argv[4]
arch = sys.argv[5]
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 p in sorted(stage.rglob("*")):
if p.is_symlink():
rel = "/" + p.relative_to(stage).as_posix()
files[rel] = "-"
elif p.is_file():
rel = "/" + p.relative_to(stage).as_posix()
files[rel] = hashlib.sha256(p.read_bytes()).hexdigest()
flatsize += p.stat().st_size
common = {
"name": "os-rustproxy",
"version": version,
"origin": "opnsense/os-rustproxy",
"comment": "RustProxy reverse proxy service",
"desc": "RustProxy packaged as an OPNsense plugin with configurable HTTP, HTTPS/TLS and administration listeners.",
"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_dir / "+COMPACT_MANIFEST").write_text(
json.dumps(common, separators=(",", ":"), ensure_ascii=False) + "\n",
encoding="utf-8",
)
(meta_dir / "+MANIFEST").write_text(
json.dumps(manifest, separators=(",", ":"), ensure_ascii=False) + "\n",
encoding="utf-8",
)
PY
archive="$work/os-rustproxy.tar"
payload_list="$work/payload.list"
rm -f "$archive" "$package_file" "$payload_list"
# FreeBSD pkg expects payload paths to match +MANIFEST exactly. The manifest
# uses absolute paths (/usr/local/...), so the tar members must also be
# absolute. Do not archive directory entries: pkg creates parent directories
# while extracting files, and an undeclared directory entry makes pkg add fail.
(
cd "$stage"
find . \( -type f -o -type l \) -print \
| LC_ALL=C sort \
| sed 's#^\./##' > "$payload_list"
)
[[ -s "$payload_list" ]] || { echo "Package payload is empty" >&2; exit 1; }
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 "$payload_list"
# Validate the raw package archive before compression. This catches the exact
# class of errors that pkg(8) reports as "not specified in the manifest".
python3 - "$archive" "$meta/+MANIFEST" <<'PYVERIFY'
import json
from pathlib import Path
import sys
import tarfile
archive = Path(sys.argv[1])
manifest = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
expected = set(manifest.get("files", {}))
with tarfile.open(archive, "r:") as tf:
members = tf.getmembers()
if [m.name for m in members[:2]] != ["+COMPACT_MANIFEST", "+MANIFEST"]:
raise SystemExit("Package metadata must be the first two tar members")
payload = members[2:]
directories = [m.name for m in payload if m.isdir()]
if directories:
raise SystemExit(f"Directory entries are not allowed in payload: {directories[:5]}")
actual = {m.name for m in payload if m.isfile() or m.issym() or m.islnk()}
relative = sorted(name for name in actual if not name.startswith("/"))
if relative:
raise SystemExit(f"Payload contains non-absolute paths: {relative[:5]}")
missing = sorted(expected - actual)
extra = sorted(actual - expected)
if missing or extra:
raise SystemExit(
"Package payload does not match +MANIFEST; "
f"missing={missing[:5]} extra={extra[:5]}"
)
print(f"Verified package payload: {len(actual)} files, manifest paths match")
PYVERIFY
zstd -q -f -19 -T0 "$archive" -o "$package_file"
# Convenience stable path for direct deployment.
cp -f "$package_file" "$ROOT/dist/os-rustproxy-latest.pkg"
(cd "$repo_dir" && sha256sum "$(basename "$package_file")") > "$ROOT/dist/SHA256"
cat > "$ROOT/dist/BUILD-INFO.txt" <<INFO
app_version=$app_version
package_version=$pkg_version
source_url=$SOURCE_URL
source_ref=$SOURCE_REF
source_commit=$SOURCE_COMMIT
opnsense_series=$OPNSENSE_ABI
freebsd_abi=$FREEBSD_ABI
freebsd_arch=$FREEBSD_ARCH
package=$(basename "$package_file")
INFO
# Structural verification available on Linux.
zstd -dc "$package_file" | tar -tf - >/dev/null
rm -rf "$work"
echo "Package: $package_file"
echo "Latest: $ROOT/dist/os-rustproxy-latest.pkg"