165 lines
4.7 KiB
Bash
Executable File
165 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ "$#" -lt 2 || "$#" -gt 3 ]]; then
|
|
echo "usage: $0 <40-char-git-sha> <source-dir> [web-root]" >&2
|
|
exit 2
|
|
fi
|
|
|
|
release_sha="$1"
|
|
source_dir="$2"
|
|
web_root="${3:-/srv/www/quant-os}"
|
|
|
|
if [[ ! "${release_sha}" =~ ^[0-9a-f]{40}$ ]]; then
|
|
echo "release SHA must be a 40-character lowercase Git SHA" >&2
|
|
exit 2
|
|
fi
|
|
if [[ ! -d "${source_dir}" ]]; then
|
|
echo "source directory does not exist" >&2
|
|
exit 2
|
|
fi
|
|
if [[ -L "${source_dir}" ]]; then
|
|
echo "source directory must not be a symlink" >&2
|
|
exit 2
|
|
fi
|
|
if [[ "${web_root}" != /* || "${web_root}" == "/" ]]; then
|
|
echo "web root must be an explicit absolute non-root path" >&2
|
|
exit 2
|
|
fi
|
|
|
|
source_entries=()
|
|
while IFS= read -r -d '' entry; do
|
|
source_entries+=("${entry}")
|
|
done < <(find "${source_dir}" -mindepth 1 -maxdepth 1 -print0)
|
|
if [[ "${#source_entries[@]}" != "2" ]]; then
|
|
echo "source must contain exactly index.html and status-data.js" >&2
|
|
exit 1
|
|
fi
|
|
for required in index.html status-data.js; do
|
|
if [[ ! -f "${source_dir}/${required}" || -L "${source_dir}/${required}" ]]; then
|
|
echo "source must contain a regular non-symlink ${required}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
for entry in "${source_entries[@]}"; do
|
|
case "$(basename "${entry}")" in
|
|
index.html|status-data.js)
|
|
;;
|
|
*)
|
|
echo "unexpected source entry: $(basename "${entry}")" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if [[ ! -f "${entry}" || -L "${entry}" ]]; then
|
|
echo "source entries must be regular non-symlink files" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
release_dir="${web_root}/releases/${release_sha}"
|
|
incoming_dir="${web_root}/.incoming-${release_sha}"
|
|
lock_dir="${web_root}/.status-deploy.lock"
|
|
incoming_created=false
|
|
lock_acquired=false
|
|
|
|
cleanup() {
|
|
exit_status="$?"
|
|
if [[ "${incoming_created}" == "true" ]]; then
|
|
chmod 0755 "${incoming_dir}" 2>/dev/null || true
|
|
rm -f \
|
|
"${incoming_dir}/index.html" \
|
|
"${incoming_dir}/status-data.js"
|
|
rmdir "${incoming_dir}" 2>/dev/null || true
|
|
fi
|
|
if [[ "${lock_acquired}" == "true" ]]; then
|
|
rmdir "${lock_dir}" 2>/dev/null || true
|
|
fi
|
|
exit "${exit_status}"
|
|
}
|
|
trap cleanup EXIT HUP INT TERM
|
|
|
|
install -d -m 0755 "${web_root}"
|
|
if ! mkdir "${lock_dir}" 2>/dev/null; then
|
|
echo "another status deployment or activation holds ${lock_dir}" >&2
|
|
exit 1
|
|
fi
|
|
lock_acquired=true
|
|
|
|
if [[ -L "${web_root}/releases" ]]; then
|
|
echo "releases directory must not be a symlink" >&2
|
|
exit 1
|
|
fi
|
|
install -d -m 0755 "${web_root}/releases"
|
|
if [[ -L "${web_root}/releases" ]]; then
|
|
echo "releases directory must not be a symlink" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -e "${release_dir}" || -L "${release_dir}" ]]; then
|
|
echo "refusing to overwrite existing release: ${release_dir}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -e "${incoming_dir}" || -L "${incoming_dir}" ]]; then
|
|
echo "stale incoming path must be inspected and removed: ${incoming_dir}" >&2
|
|
exit 1
|
|
fi
|
|
install -d -m 0755 "${incoming_dir}"
|
|
incoming_created=true
|
|
install -m 0644 "${source_dir}/index.html" "${incoming_dir}/index.html"
|
|
install -m 0644 \
|
|
"${source_dir}/status-data.js" \
|
|
"${incoming_dir}/status-data.js"
|
|
|
|
hash_command=()
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
hash_command=(sha256sum)
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
hash_command=(shasum -a 256)
|
|
else
|
|
echo "sha256sum or shasum is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for required in index.html status-data.js; do
|
|
source_hash="$("${hash_command[@]}" "${source_dir}/${required}" | awk '{print $1}')"
|
|
incoming_hash="$("${hash_command[@]}" "${incoming_dir}/${required}" | awk '{print $1}')"
|
|
if [[ "${source_hash}" != "${incoming_hash}" ]]; then
|
|
echo "release hash mismatch: ${required}" >&2
|
|
exit 1
|
|
fi
|
|
echo "${required} ${incoming_hash}"
|
|
done
|
|
|
|
incoming_entries=()
|
|
while IFS= read -r -d '' entry; do
|
|
incoming_entries+=("${entry}")
|
|
done < <(find "${incoming_dir}" -mindepth 1 -maxdepth 1 -print0)
|
|
if [[ "${#incoming_entries[@]}" != "2" ]]; then
|
|
echo "incoming release must contain exactly two files" >&2
|
|
exit 1
|
|
fi
|
|
for required in index.html status-data.js; do
|
|
if [[ ! -f "${incoming_dir}/${required}" || -L "${incoming_dir}/${required}" ]]; then
|
|
echo "incoming release is not a regular immutable payload: ${required}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
chmod 0444 \
|
|
"${incoming_dir}/index.html" \
|
|
"${incoming_dir}/status-data.js"
|
|
|
|
if mv --version >/dev/null 2>&1; then
|
|
mv -T "${incoming_dir}" "${release_dir}"
|
|
else
|
|
if [[ -e "${release_dir}" || -L "${release_dir}" ]]; then
|
|
echo "refusing to overwrite concurrently created release" >&2
|
|
exit 1
|
|
fi
|
|
mv "${incoming_dir}" "${release_dir}"
|
|
fi
|
|
incoming_created=false
|
|
chmod 0555 "${release_dir}"
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
export QUANT_OS_STATUS_DEPLOY_LOCK_HELD=1
|
|
"${script_dir}/activate_static_status.sh" "${release_sha}" "${web_root}"
|