feat: establish Quant OS production-80 architecture
This commit is contained in:
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/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}"
|
||||
Reference in New Issue
Block a user