feat: establish Quant OS production-80 architecture
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# Quant OS public status operations
|
||||
|
||||
The repository is the source of truth for the public status page. A deployment
|
||||
contains exactly:
|
||||
|
||||
```text
|
||||
/srv/www/quant-os/
|
||||
releases/<git-sha>/
|
||||
index.html
|
||||
status-data.js
|
||||
status -> releases/<git-sha>
|
||||
```
|
||||
|
||||
Deployment invariants:
|
||||
|
||||
1. run `python3 tools/build_public_status.py --check` and the full local suite;
|
||||
2. deploy from a clean, pushed commit and name the release with that full SHA;
|
||||
3. acquire the web-root deployment lock and copy into a same-filesystem
|
||||
`.incoming-<sha>` directory;
|
||||
4. require exactly two regular, non-symlink payload files and compare local and
|
||||
staged SHA-256 for both;
|
||||
5. atomically rename the verified incoming directory to the immutable release;
|
||||
6. create a temporary relative symlink and activate it with one atomic rename;
|
||||
7. install [`nginx/quant-os-static.conf`](nginx/quant-os-static.conf), run
|
||||
`nginx -t`, then reload;
|
||||
8. verify the canonical page, generated JavaScript, cache header and all legacy
|
||||
redirects from a public client;
|
||||
9. keep the previous release for rollback.
|
||||
|
||||
The server-side deploy and activation are executable and fail closed:
|
||||
|
||||
```bash
|
||||
ops/deploy_static_status.sh <full-git-sha> <staged-status-dir>
|
||||
ops/activate_static_status.sh <previous-full-git-sha>
|
||||
```
|
||||
|
||||
The first command rejects a symlinked source root, symlink payloads,
|
||||
directories and any third source entry,
|
||||
holds `.status-deploy.lock` across staging and activation, verifies both
|
||||
SHA-256 values, atomically promotes `.incoming-<sha>`, and refuses to overwrite
|
||||
an existing release. Promoted payload files and their release directory are
|
||||
owner read-only. The second command is the rollback/activation primitive;
|
||||
when called directly it takes the same lock and switches only a relative
|
||||
symlink. A stale lock or incoming directory means a prior process may have
|
||||
crashed: inspect it and confirm no deploy is running before removing it.
|
||||
|
||||
Nginx is deliberately two-stage:
|
||||
|
||||
1. include `nginx/quant-os-static.conf` in the existing HTTPS server block,
|
||||
run `nginx -t`, reload, and verify `/quant-os/status/`;
|
||||
2. only then include `nginx/quant-os-legacy-redirects-302.conf`, preserving the
|
||||
existing broad `/quants-strategies/` route for other projects;
|
||||
3. after an observation period and a separate production change, replace the
|
||||
302 include with `quant-os-legacy-redirects-308.conf`.
|
||||
|
||||
Rollback only changes the `status` symlink to the previously verified release
|
||||
and reloads no application state. The page is a static, redacted build-time
|
||||
snapshot; it must never contain credentials, local paths, market data, account
|
||||
identifiers, positions or fills.
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then
|
||||
echo "usage: $0 <40-char-git-sha> [web-root]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
release_sha="$1"
|
||||
web_root="${2:-/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 [[ "${web_root}" != /* || "${web_root}" == "/" ]]; then
|
||||
echo "web root must be an explicit absolute non-root path" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -d "${web_root}" ]]; then
|
||||
echo "web root does not exist: ${web_root}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
lock_dir="${web_root}/.status-deploy.lock"
|
||||
lock_acquired=false
|
||||
next_link=""
|
||||
next_link_created=false
|
||||
|
||||
cleanup() {
|
||||
exit_status="$?"
|
||||
if [[ "${next_link_created}" == "true" && -L "${next_link}" ]]; then
|
||||
rm -f "${next_link}"
|
||||
fi
|
||||
if [[ "${lock_acquired}" == "true" ]]; then
|
||||
rmdir "${lock_dir}" 2>/dev/null || true
|
||||
fi
|
||||
exit "${exit_status}"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
if [[ "${QUANT_OS_STATUS_DEPLOY_LOCK_HELD:-0}" == "1" ]]; then
|
||||
if [[ ! -d "${lock_dir}" || -L "${lock_dir}" ]]; then
|
||||
echo "caller asserted a deployment lock that is not held" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! mkdir "${lock_dir}" 2>/dev/null; then
|
||||
echo "another status deployment or activation holds ${lock_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
lock_acquired=true
|
||||
fi
|
||||
|
||||
release_dir="${web_root}/releases/${release_sha}"
|
||||
if [[ ! -d "${release_dir}" || -L "${release_dir}" ]]; then
|
||||
echo "release must be a regular directory: ${release_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
release_entries=()
|
||||
while IFS= read -r -d '' entry; do
|
||||
release_entries+=("${entry}")
|
||||
done < <(find "${release_dir}" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
|
||||
if [[ "${#release_entries[@]}" != "2" ]]; then
|
||||
echo "release must contain exactly index.html and status-data.js" >&2
|
||||
exit 1
|
||||
fi
|
||||
for required in index.html status-data.js; do
|
||||
if [[ ! -f "${release_dir}/${required}" || -L "${release_dir}/${required}" ]]; then
|
||||
echo "release must contain a regular non-symlink ${required}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for entry in "${release_entries[@]}"; do
|
||||
case "$(basename "${entry}")" in
|
||||
index.html|status-data.js)
|
||||
;;
|
||||
*)
|
||||
echo "unexpected release entry: $(basename "${entry}")" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
next_link="${web_root}/.status-next-${release_sha}"
|
||||
if [[ -e "${next_link}" || -L "${next_link}" ]]; then
|
||||
echo "temporary activation link already exists: ${next_link}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ln -s "releases/${release_sha}" "${next_link}"
|
||||
next_link_created=true
|
||||
if [[ -e "${web_root}/status" && ! -L "${web_root}/status" ]]; then
|
||||
echo "active status path exists and is not a symlink" >&2
|
||||
exit 1
|
||||
fi
|
||||
if mv --version >/dev/null 2>&1; then
|
||||
mv -Tf "${next_link}" "${web_root}/status"
|
||||
else
|
||||
mv -fh "${next_link}" "${web_root}/status"
|
||||
fi
|
||||
next_link_created=false
|
||||
|
||||
active_target="$(readlink "${web_root}/status")"
|
||||
if [[ "${active_target}" != "releases/${release_sha}" ]]; then
|
||||
echo "active release verification failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "activated ${release_sha}"
|
||||
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}"
|
||||
@@ -0,0 +1,26 @@
|
||||
# Temporary compatibility redirects. Install only after the new canonical
|
||||
# route has passed public verification. Keep these as 302 during observation.
|
||||
|
||||
location = /quants-strategies/quant-os {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/ {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/ {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/index.html {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/status-data.js {
|
||||
return 302 https://$host/quant-os/status/status-data.js;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Stable compatibility redirects. Replace the temporary 302 include with this
|
||||
# file only after an observation period and an explicit production change.
|
||||
|
||||
location = /quants-strategies/quant-os {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/ {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/ {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/index.html {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/status-data.js {
|
||||
return 308 https://$host/quant-os/status/status-data.js;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Quant OS public status routes.
|
||||
#
|
||||
# This snippet belongs inside the gomars.fun HTTPS server block. The release
|
||||
# payload is published under /srv/www/quant-os/releases/<git-sha>/ and the
|
||||
# /srv/www/quant-os/status symlink is switched atomically after verification.
|
||||
|
||||
location = /quant-os {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quant-os/ {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quant-os/status {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quant-os/status/index.html {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location ^~ /quant-os/status/ {
|
||||
root /srv/www;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
Reference in New Issue
Block a user