Source licenses, attribution, provenance, and font notices from the pinned game checkout while retaining installer compatibility with earlier manifest formats.
253 lines
7.4 KiB
Bash
Executable file
253 lines
7.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ $# -ne 2 ]]; then
|
|
echo "Usage: $0 ARCHIVE SHA256_OR_SHA256_FILE" >&2
|
|
exit 2
|
|
fi
|
|
if [[ ${EUID} -ne 0 ]]; then
|
|
echo "Run this installer as root." >&2
|
|
exit 2
|
|
fi
|
|
|
|
archive_path="$(readlink -f -- "$1")"
|
|
checksum_input="$2"
|
|
install_root="${NETFISHING_INSTALL_ROOT:-/opt/netfishing-server}"
|
|
service_name="${NETFISHING_SERVICE_NAME:-netfishing-server.service}"
|
|
|
|
if [[ ! -f "${archive_path}" ]]; then
|
|
echo "Archive not found: $1" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${install_root}" != /* || "${install_root}" == "/" ]]; then
|
|
echo "NETFISHING_INSTALL_ROOT must be a specific absolute directory." >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ "${checksum_input}" =~ ^[0-9A-Fa-f]{64}$ ]]; then
|
|
expected_sha256="${checksum_input,,}"
|
|
elif [[ -f "${checksum_input}" ]]; then
|
|
expected_sha256="$(awk 'NF {print tolower($1); exit}' "${checksum_input}")"
|
|
else
|
|
echo "Second argument must be a SHA-256 hash or checksum file." >&2
|
|
exit 2
|
|
fi
|
|
if [[ ! "${expected_sha256}" =~ ^[0-9a-f]{64}$ ]]; then
|
|
echo "Invalid expected SHA-256 value." >&2
|
|
exit 2
|
|
fi
|
|
actual_sha256="$(sha256sum "${archive_path}" | awk '{print $1}')"
|
|
if [[ "${actual_sha256}" != "${expected_sha256}" ]]; then
|
|
echo "Archive SHA-256 mismatch." >&2
|
|
exit 1
|
|
fi
|
|
|
|
work_dir="$(mktemp -d /tmp/netfishing-server-install.XXXXXX)"
|
|
release_stage=""
|
|
cleanup() {
|
|
if [[ -n "${release_stage}" && -d "${release_stage}" ]]; then
|
|
rm -rf -- "${release_stage}"
|
|
fi
|
|
rm -rf -- "${work_dir}"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
top_directory=""
|
|
while IFS= read -r member; do
|
|
[[ -n "${member}" ]] || continue
|
|
if [[ "${member}" == /* ]]; then
|
|
echo "Archive contains an absolute path." >&2
|
|
exit 1
|
|
fi
|
|
IFS='/' read -r -a path_parts <<< "${member}"
|
|
for part in "${path_parts[@]}"; do
|
|
if [[ "${part}" == ".." || "${part}" == "." ]]; then
|
|
echo "Archive contains an unsafe path." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
member_top="${member%%/*}"
|
|
if [[ -z "${top_directory}" ]]; then
|
|
top_directory="${member_top}"
|
|
elif [[ "${member_top}" != "${top_directory}" ]]; then
|
|
echo "Archive must contain exactly one top-level directory." >&2
|
|
exit 1
|
|
fi
|
|
done < <(tar -tzf "${archive_path}")
|
|
|
|
if [[ -z "${top_directory}" ]]; then
|
|
echo "Archive is empty." >&2
|
|
exit 1
|
|
fi
|
|
tar --no-same-owner --no-same-permissions -xzf "${archive_path}" -C "${work_dir}"
|
|
payload_dir="${work_dir}/${top_directory}"
|
|
if [[ -n "$(find "${payload_dir}" -type l -print -quit)" ]]; then
|
|
echo "Archive payload may not contain symbolic links." >&2
|
|
exit 1
|
|
fi
|
|
for filename in NETfishingServer.x86_64 NETfishingServer.pck BUILD_INFO SHA256SUMS; do
|
|
if [[ ! -f "${payload_dir}/${filename}" ]]; then
|
|
echo "Archive is missing ${filename}." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
manifest_value() {
|
|
local key="$1"
|
|
awk -F= -v key="${key}" '$1 == key {sub(/^[^=]*=/, ""); print; exit}' \
|
|
"${payload_dir}/BUILD_INFO"
|
|
}
|
|
manifest_version="$(manifest_value manifest_version)"
|
|
case "${manifest_version}" in
|
|
1|2)
|
|
required_notice_files=(
|
|
GPL-3.0-or-later.txt
|
|
ASSET-LICENSE.md
|
|
THIRD-PARTY-NOTICES.md
|
|
TRADEMARKS.md
|
|
CREDITS.md
|
|
)
|
|
;;
|
|
3)
|
|
required_notice_files=(
|
|
GPL-3.0-or-later.txt
|
|
ASSET-LICENSE.md
|
|
ATTRIBUTION.md
|
|
TRADEMARKS.md
|
|
Tuffy-LICENSE.txt
|
|
Seattle-Avenue-LICENSE.txt
|
|
)
|
|
;;
|
|
*)
|
|
echo "Unsupported BUILD_INFO format." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
for filename in "${required_notice_files[@]}"; do
|
|
if [[ ! -f "${payload_dir}/licenses/${filename}" ]]; then
|
|
echo "Archive is missing required notice licenses/${filename}." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
if [[ ! -f "${payload_dir}/SOURCE-CODE.md" ]]; then
|
|
echo "Archive is missing SOURCE-CODE.md." >&2
|
|
exit 1
|
|
fi
|
|
(cd -- "${payload_dir}" && sha256sum -c SHA256SUMS)
|
|
|
|
game_version="$(manifest_value game_version)"
|
|
game_commit="$(manifest_value game_commit)"
|
|
packaging_commit="$(manifest_value packaging_commit)"
|
|
platform="$(manifest_value platform)"
|
|
if [[ ! "${game_version}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
|
|
echo "Invalid game version in BUILD_INFO." >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! "${game_commit}" =~ ^[0-9a-f]{40}$ ]]; then
|
|
echo "Invalid game commit in BUILD_INFO." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${manifest_version}" != "1" \
|
|
&& ! "${packaging_commit}" =~ ^[0-9a-f]{40}$ ]]; then
|
|
echo "Invalid packaging commit in BUILD_INFO." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${platform}" != "linux-x86_64" ]]; then
|
|
echo "This installer only accepts linux-x86_64 packages." >&2
|
|
exit 1
|
|
fi
|
|
|
|
releases_dir="${install_root}/releases"
|
|
release_dir="${releases_dir}/${game_version}-${game_commit:0:12}"
|
|
install -d -m 0755 "${install_root}" "${releases_dir}"
|
|
if [[ -e "${release_dir}" ]]; then
|
|
if [[ ! -f "${release_dir}/BUILD_INFO" ]] \
|
|
|| ! cmp -s "${payload_dir}/BUILD_INFO" "${release_dir}/BUILD_INFO"; then
|
|
echo "A different release already occupies ${release_dir}." >&2
|
|
exit 1
|
|
fi
|
|
(cd -- "${release_dir}" && sha256sum -c SHA256SUMS)
|
|
else
|
|
release_stage="$(mktemp -d "${releases_dir}/.incoming.XXXXXX")"
|
|
install -m 0555 "${payload_dir}/NETfishingServer.x86_64" "${release_stage}/"
|
|
install -m 0444 "${payload_dir}/NETfishingServer.pck" "${release_stage}/"
|
|
install -m 0444 "${payload_dir}/BUILD_INFO" "${release_stage}/"
|
|
install -m 0444 "${payload_dir}/SHA256SUMS" "${release_stage}/"
|
|
install -m 0444 "${payload_dir}/SOURCE-CODE.md" "${release_stage}/"
|
|
install -d -m 0755 "${release_stage}/licenses"
|
|
for filename in "${required_notice_files[@]}"; do
|
|
install -m 0444 "${payload_dir}/licenses/${filename}" \
|
|
"${release_stage}/licenses/${filename}"
|
|
done
|
|
if [[ -f "${payload_dir}/README.md" ]]; then
|
|
install -m 0444 "${payload_dir}/README.md" "${release_stage}/"
|
|
fi
|
|
chmod 0755 "${release_stage}"
|
|
mv -- "${release_stage}" "${release_dir}"
|
|
release_stage=""
|
|
fi
|
|
|
|
replace_symlink() {
|
|
local target="$1"
|
|
local link_path="$2"
|
|
local temporary_link="${install_root}/.$(basename -- "${link_path}").new.$$"
|
|
ln -s -- "${target}" "${temporary_link}"
|
|
mv -Tf -- "${temporary_link}" "${link_path}"
|
|
}
|
|
|
|
old_current=""
|
|
if [[ -L "${install_root}/current" ]]; then
|
|
old_current="$(readlink -f -- "${install_root}/current")"
|
|
fi
|
|
|
|
service_exists=false
|
|
service_active=false
|
|
if command -v systemctl >/dev/null && systemctl cat "${service_name}" >/dev/null 2>&1; then
|
|
service_exists=true
|
|
if systemctl is-active --quiet "${service_name}"; then
|
|
service_active=true
|
|
unit_text="$(systemctl cat "${service_name}")"
|
|
if [[ "${unit_text}" != *"${install_root}/current/NETfishingServer.x86_64"* ]]; then
|
|
echo "Active ${service_name} does not use the current-release symlink." >&2
|
|
echo "Migrate the unit while stopped before using this updater." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ${service_active}; then
|
|
systemctl stop "${service_name}"
|
|
fi
|
|
replace_symlink "${release_dir}" "${install_root}/current"
|
|
|
|
if ${service_active}; then
|
|
if ! systemctl start "${service_name}"; then
|
|
start_succeeded=false
|
|
else
|
|
sleep 1
|
|
start_succeeded=true
|
|
systemctl is-active --quiet "${service_name}" || start_succeeded=false
|
|
fi
|
|
if ! ${start_succeeded}; then
|
|
echo "New release failed to start; restoring the previous release." >&2
|
|
if [[ -n "${old_current}" ]]; then
|
|
replace_symlink "${old_current}" "${install_root}/current"
|
|
else
|
|
unlink -- "${install_root}/current"
|
|
fi
|
|
systemctl start "${service_name}" || true
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${old_current}" && "${old_current}" != "${release_dir}" ]]; then
|
|
replace_symlink "${old_current}" "${install_root}/previous"
|
|
fi
|
|
|
|
echo "Installed NETfishing dedicated server ${game_version} (${game_commit})."
|
|
if [[ -n "${packaging_commit}" ]]; then
|
|
echo "Dedicated packaging commit: ${packaging_commit}."
|
|
fi
|
|
if ${service_exists} && ! ${service_active}; then
|
|
echo "${service_name} was inactive and was not started."
|
|
fi
|