mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
141 lines
5.0 KiB
Bash
Executable File
141 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
if [[ $# -ne 3 ]]; then
|
|
echo "Usage: $0 <release-dir> <version> <output-dir>" >&2
|
|
exit 64
|
|
fi
|
|
|
|
release_dir="$(cd "$1" && pwd -P)"
|
|
version="$2"
|
|
output_dir="$3"
|
|
|
|
if [[ -z "${output_dir}" || "${output_dir}" != /* ]]; then
|
|
echo "Error: refusing unsafe release container context output: ${output_dir:-<empty>}" >&2
|
|
exit 1
|
|
fi
|
|
output_dir="${output_dir%/}"
|
|
output_basename="$(basename "${output_dir}")"
|
|
output_parent="$(dirname "${output_dir}")"
|
|
if [[ -z "${output_dir}" || "${output_dir}" == "/" || "${output_basename}" == "." || "${output_basename}" == ".." ]]; then
|
|
echo "Error: refusing unsafe release container context output: ${output_dir:-<empty>}" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p "${output_parent}"
|
|
output_parent="$(cd "${output_parent}" && pwd -P)"
|
|
output_dir="${output_parent}/${output_basename}"
|
|
if [[ -L "${output_dir}" ]]; then
|
|
echo "Error: refusing symlink release container context output: ${output_dir}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
validate_archive_entries() {
|
|
local archive="$1"
|
|
python3 - "${archive}" <<'PY'
|
|
import pathlib
|
|
import posixpath
|
|
import sys
|
|
import tarfile
|
|
|
|
archive = sys.argv[1]
|
|
seen: set[str] = set()
|
|
with tarfile.open(archive, "r:gz") as candidate:
|
|
for member in candidate.getmembers():
|
|
raw_name = member.name
|
|
name = raw_name[2:] if raw_name.startswith("./") else raw_name
|
|
if name in {"", "."} and member.isdir():
|
|
continue
|
|
parts = pathlib.PurePosixPath(name).parts
|
|
unsafe_name = (
|
|
not name
|
|
or name.startswith("/")
|
|
or "\\" in name
|
|
or "//" in name
|
|
or any(part in {"", ".", ".."} for part in parts)
|
|
)
|
|
unsafe_link = False
|
|
if member.issym():
|
|
target = member.linkname
|
|
resolved = posixpath.normpath(posixpath.join(posixpath.dirname(name), target))
|
|
unsafe_link = (
|
|
not target
|
|
or target.startswith("/")
|
|
or "\\" in target
|
|
or resolved == ".."
|
|
or resolved.startswith("../")
|
|
)
|
|
if unsafe_name or unsafe_link or not (member.isfile() or member.isdir() or member.issym()):
|
|
raise SystemExit(f"Error: release archive contains an unsafe member: {raw_name}")
|
|
canonical = pathlib.PurePosixPath(*parts).as_posix()
|
|
if canonical in seen:
|
|
raise SystemExit(f"Error: release archive contains a duplicate member: {raw_name}")
|
|
seen.add(canonical)
|
|
PY
|
|
}
|
|
|
|
rm -rf "${output_dir}"
|
|
mkdir -p "${output_dir}/amd64" "${output_dir}/arm64"
|
|
|
|
for arch in amd64 arm64; do
|
|
archive="${release_dir}/pulse-v${version}-linux-${arch}.tar.gz"
|
|
if [[ ! -f "${archive}" ]]; then
|
|
echo "Error: immutable candidate is missing ${archive}." >&2
|
|
exit 1
|
|
fi
|
|
validate_archive_entries "${archive}"
|
|
tar --no-same-owner --no-same-permissions -xzf "${archive}" -C "${output_dir}/${arch}"
|
|
for windows_arch in amd64 arm64 386; do
|
|
alias_path="${output_dir}/${arch}/bin/pulse-agent-windows-${windows_arch}"
|
|
expected_target="pulse-agent-windows-${windows_arch}.exe"
|
|
if [[ ! -L "${alias_path}" ]] || \
|
|
[[ "$(readlink "${alias_path}")" != "${expected_target}" ]] || \
|
|
[[ ! -f "${output_dir}/${arch}/bin/${expected_target}" ]]; then
|
|
echo "Error: ${archive} has an invalid Windows agent alias ${alias_path}." >&2
|
|
exit 1
|
|
fi
|
|
# runtime_prebuilt recreates these aliases after copying the immutable
|
|
# .exe payloads. Keep the manifest-covered context symlink-free rather
|
|
# than weakening its canonical-file rule.
|
|
rm -f "${alias_path}"
|
|
done
|
|
for required in \
|
|
bin/pulse \
|
|
bin/pulse.sig \
|
|
bin/pulse.sshsig \
|
|
bin/pulse-agent-linux-amd64 \
|
|
bin/pulse-agent-linux-amd64.sig \
|
|
bin/pulse-agent-linux-amd64.sshsig \
|
|
scripts/install-container-agent.sh \
|
|
scripts/install-docker.sh \
|
|
scripts/install.sh \
|
|
scripts/install.sh.sig \
|
|
scripts/install.sh.sshsig \
|
|
VERSION; do
|
|
if [[ ! -f "${output_dir}/${arch}/${required}" ]]; then
|
|
echo "Error: ${archive} is missing candidate container input ${required}." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
done
|
|
|
|
if ! diff -qr \
|
|
--exclude=pulse \
|
|
--exclude=pulse.sig \
|
|
--exclude=pulse.sshsig \
|
|
"${output_dir}/amd64" "${output_dir}/arm64"; then
|
|
echo "Error: candidate container inputs differ outside the target server binary and its signatures." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The universal agent/script payload is identical in both server archives.
|
|
# Keep one copy plus the arm64 server and VERSION needed by the multi-arch
|
|
# runtime target so Actions transfers do not carry the same payload twice.
|
|
find "${output_dir}/arm64" -depth -mindepth 1 \
|
|
! -path "${output_dir}/arm64/bin" \
|
|
! -path "${output_dir}/arm64/bin/pulse" \
|
|
! -path "${output_dir}/arm64/VERSION" \
|
|
-delete
|
|
|
|
echo "Prepared exact-candidate container context at ${output_dir}."
|