Files
InstanceBrick--nightcap/scripts/payload_store.sh
T
2026-08-12 01:58:18 +03:00

979 lines
38 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copyright 2026 Mocktail Project Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
set -Eeuo pipefail
umask 077
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
source "${SCRIPT_DIR}/payload_integrity.sh"
STORE_ROOT="${MOCKTAIL_PAYLOAD_STORE_ROOT:-${XDG_DATA_HOME:-${HOME}/.local/share}/mocktail}"
COMPATIBILITY_PATH=""
EXPECTED_CURRENT=""
EXPECTED_PAYLOAD_FINGERPRINT=""
EXPECTED_ROLLBACK_TARGET=""
CANDIDATE_PROFILE_PATH=""
CANDIDATE_COMPATIBILITY_PATH=""
RUNTIME_FINGERPRINT=""
RUNTIME_BUILD_ID=""
CANARY_ATTESTATION_PATHS=()
PROMOTION_APPROVAL_GENERATION=""
Log() {
printf '[payload-store] %s\n' "$*" >&2
}
Die() {
Log "error: $*"
exit 1
}
Usage() {
cat <<'EOF'
Usage: scripts/payload_store.sh [GLOBAL OPTIONS] COMMAND [ARGUMENTS]
Immutable Roblox payload staging and atomic activation.
Global options:
--root DIR Override the XDG payload store root.
--compatibility FILE Build-ID compatibility profile used by promotion.
--expected-current ID Require current payload ID to remain unchanged. Use
"none" when no payload may already be active.
--expected-payload-fingerprint SHA256
Require the candidate bytes observed before canary.
--expected-rollback-target ID
Require previous-good to match a known baseline.
--candidate-profile FILE
Derived exact host-ABI profile for probation promotion.
--candidate-compatibility FILE
One-payload compatibility manifest used by probation.
--runtime-fingerprint SHA256
Bind approval to the canary runtime executable bytes.
--runtime-build-id HEX
Bind approval to the installed runtime ELF Build ID.
--canary-attestation FILE
Passed Tier C attestation. Supply exactly twice for
probation promotion.
Commands:
stage PAYLOAD_DIR Validate and immutably stage a prepared payload.
import PAYLOAD_DIR Stage, then promote only an exact supported Build ID.
promote PAYLOAD_ID Atomically activate an exact supported payload.
promote-probation PAYLOAD_ID
Approve and activate a double-canary candidate.
fingerprint PAYLOAD_ID
Verify and fingerprint immutable runtime bytes.
verify-current Verify current manifest, payload bytes, and any
probation approval generation; print its payload ID.
rollback Restore current.json from previous_good.json.
status Print current and previous-good manifests.
PAYLOAD_DIR must contain libroblox.so, assets/, sober_apk/base.apk,
sober_apk/split_config.x86_64.apk, and roblox_payload.json. APK/ELF validation
belongs to update_roblox_payload.sh; this command verifies the resulting hashes
and metadata before crossing the immutable store boundary.
EOF
}
RequireCommands() {
local command_name
for command_name in flock jq sha256sum sort; do
command -v "${command_name}" >/dev/null 2>&1 ||
Die "missing required command: ${command_name}"
done
}
ParseGlobalOptions() {
while (( $# > 0 )); do
case "$1" in
--root)
(( $# >= 2 )) || Die "--root requires a directory"
STORE_ROOT="$2"
shift 2
;;
--compatibility)
(( $# >= 2 )) || Die "--compatibility requires a file"
COMPATIBILITY_PATH="$2"
shift 2
;;
--expected-current)
(( $# >= 2 )) || Die "--expected-current requires a payload ID or none"
EXPECTED_CURRENT="$2"
shift 2
;;
--expected-payload-fingerprint)
(( $# >= 2 )) ||
Die "--expected-payload-fingerprint requires a SHA-256 digest"
EXPECTED_PAYLOAD_FINGERPRINT="$2"
shift 2
;;
--expected-rollback-target)
(( $# >= 2 )) ||
Die "--expected-rollback-target requires a payload ID"
EXPECTED_ROLLBACK_TARGET="$2"
shift 2
;;
--candidate-profile)
(( $# >= 2 )) || Die "--candidate-profile requires a file"
CANDIDATE_PROFILE_PATH="$2"
shift 2
;;
--candidate-compatibility)
(( $# >= 2 )) || Die "--candidate-compatibility requires a file"
CANDIDATE_COMPATIBILITY_PATH="$2"
shift 2
;;
--runtime-fingerprint)
(( $# >= 2 )) || Die "--runtime-fingerprint requires a SHA-256 digest"
RUNTIME_FINGERPRINT="$2"
shift 2
;;
--runtime-build-id)
(( $# >= 2 )) || Die "--runtime-build-id requires a hex Build ID"
RUNTIME_BUILD_ID="${2,,}"
shift 2
;;
--canary-attestation)
(( $# >= 2 )) || Die "--canary-attestation requires a file"
CANARY_ATTESTATION_PATHS+=("$2")
shift 2
;;
-h|--help)
Usage
exit 0
;;
--)
shift
break
;;
-*)
Die "unknown global option: $1"
;;
*)
break
;;
esac
done
REMAINING_ARGUMENTS=("$@")
}
MetadataPath() {
printf '%s/roblox_payload.json\n' "$1"
}
PayloadFingerprint() {
jq -Sc '{schema_version, package, version_name, version_code, abi,
elf_build_id: (.elf_build_id | ascii_downcase), sha256, assets}' \
"$(MetadataPath "$1")"
}
ReadPayloadId() {
local -r payload_dir="$1"
local -r metadata="$(MetadataPath "${payload_dir}")"
[[ -f "${metadata}" ]] || Die "payload metadata is missing: ${metadata}"
local version_code build_id
version_code="$(jq -er '.version_code | select(type == "number" and floor == . and . >= 0)' "${metadata}")" ||
Die "payload metadata has an invalid version_code"
build_id="$(jq -er '.elf_build_id | ascii_downcase | select(test("^[0-9a-f]{40}$"))' "${metadata}")" ||
Die "payload metadata has an invalid ELF Build ID"
printf '%s-%s\n' "${version_code}" "${build_id}"
}
ReadManifestPayloadId() {
local -r manifest="$1"
[[ -f "${manifest}" && ! -L "${manifest}" ]] ||
Die "payload manifest is not a regular file: ${manifest}"
jq -er '
select(.schema_version == 1) |
select((.payload_id | type) == "string" and
(.payload_id | test("^[0-9]+-[0-9a-f]{40}$"))) |
select(.payload_path == ("payloads/" + .payload_id)) |
select((.version_code | type) == "number" and
(.version_code | floor == . and . >= 0)) |
select((.elf_build_id | type) == "string" and
(.elf_build_id | test("^[0-9a-f]{40}$"))) |
select(.payload_id == ((.version_code | tostring) + "-" +
(.elf_build_id | ascii_downcase))) |
.payload_id
' "${manifest}" || Die "payload manifest is invalid: ${manifest}"
}
VerifyPayload() {
local -r payload_dir="$1"
[[ -d "${payload_dir}" ]] || Die "payload directory is not readable: ${payload_dir}"
local required_path
for required_path in \
libroblox.so \
sober_apk/base.apk \
sober_apk/split_config.x86_64.apk \
roblox_payload.json; do
[[ -f "${payload_dir}/${required_path}" ]] ||
Die "prepared payload is missing ${required_path}"
done
[[ -d "${payload_dir}/assets" ]] || Die "prepared payload is missing assets/"
if find -P "${payload_dir}" -type l -print -quit | grep -q .; then
Die "prepared payload must not contain symbolic links"
fi
local -r metadata="$(MetadataPath "${payload_dir}")"
jq -e '
.schema_version == 1 and
.package == "com.roblox.client" and
.abi == "x86_64" and
(.version_name | type == "string" and length > 0) and
(.sha256.libroblox | test("^[0-9a-f]{64}$")) and
(.sha256.base_apk | test("^[0-9a-f]{64}$")) and
(.sha256.x86_64_split_apk | test("^[0-9a-f]{64}$"))
' "${metadata}" >/dev/null || Die "prepared payload metadata is incomplete"
local expected actual relative_path
while IFS='|' read -r relative_path expected; do
actual="$(sha256sum "${payload_dir}/${relative_path}" | awk '{print $1}')"
[[ "${actual}" == "${expected}" ]] || Die "hash mismatch for ${relative_path}"
done < <(jq -r '
["libroblox.so", .sha256.libroblox],
["sober_apk/base.apk", .sha256.base_apk],
["sober_apk/split_config.x86_64.apk", .sha256.x86_64_split_apk] |
@tsv
' "${metadata}" | tr '\t' '|')
local expected_assets actual_assets
expected_assets="$(jq -er '.assets.file_count | select(type == "number" and floor == . and . >= 0)' "${metadata}")" ||
Die "payload metadata has an invalid asset count"
actual_assets="$(find -P "${payload_dir}/assets" -type f -printf . | wc -c)"
[[ "${actual_assets}" == "${expected_assets}" ]] || Die "asset count mismatch"
local expected_asset_tree actual_asset_tree
expected_asset_tree="$(jq -r '.assets.sha256_tree // empty' "${metadata}")"
if [[ -n "${expected_asset_tree}" ]]; then
[[ "${expected_asset_tree}" =~ ^[0-9a-f]{64}$ ]] ||
Die "payload metadata has an invalid asset tree hash"
actual_asset_tree="$(PayloadAssetTreeSha256 "${payload_dir}/assets")"
[[ "${actual_asset_tree}" == "${expected_asset_tree}" ]] ||
Die "asset tree hash mismatch"
fi
}
RequirePayloadIdentity() {
local -r payload_id="$1"
local derived_payload_id
derived_payload_id="$(ReadPayloadId "${STORE_ROOT}/payloads/${payload_id}")"
[[ "${derived_payload_id}" == "${payload_id}" ]] ||
Die "payload directory identity mismatch: expected ${payload_id}, found ${derived_payload_id}"
}
PayloadRuntimeFingerprint() {
local -r payload_id="$1"
local -r payload_dir="${STORE_ROOT}/payloads/${payload_id}"
RequirePayloadIdentity "${payload_id}"
VerifyPayload "${payload_dir}"
local metadata_hash lib_hash base_hash split_hash asset_tree_hash
metadata_hash="$(sha256sum "${payload_dir}/roblox_payload.json" | awk '{print $1}')"
lib_hash="$(sha256sum "${payload_dir}/libroblox.so" | awk '{print $1}')"
base_hash="$(sha256sum "${payload_dir}/sober_apk/base.apk" | awk '{print $1}')"
split_hash="$(sha256sum "${payload_dir}/sober_apk/split_config.x86_64.apk" | awk '{print $1}')"
asset_tree_hash="$(PayloadAssetTreeSha256 "${payload_dir}/assets")"
printf 'payload_id=%s\nmetadata=%s\nlibroblox=%s\nbase_apk=%s\nx86_64_split=%s\nassets=%s\n' \
"${payload_id}" "${metadata_hash}" "${lib_hash}" "${base_hash}" \
"${split_hash}" "${asset_tree_hash}" |
sha256sum | awk '{print $1}'
}
AcquireStoreLock() {
mkdir -p "${STORE_ROOT}/payloads"
exec 9>"${STORE_ROOT}/.payload-store.lock"
flock 9
}
StagePayload() {
local payload_dir
payload_dir="$(cd -- "$1" && pwd -P)"
VerifyPayload "${payload_dir}"
local payload_id
payload_id="$(ReadPayloadId "${payload_dir}")"
local -r destination="${STORE_ROOT}/payloads/${payload_id}"
if [[ -e "${destination}" || -L "${destination}" ]]; then
if [[ -d "${destination}" && ! -L "${destination}" ]] &&
(VerifyPayload "${destination}") 2>/dev/null; then
[[ "$(PayloadFingerprint "${payload_dir}")" == "$(PayloadFingerprint "${destination}")" ]] ||
Die "immutable payload ID collision: ${payload_id}"
Log "payload already staged: ${payload_id}"
printf '%s\n' "${payload_id}"
return 0
fi
local -r quarantine_root="${STORE_ROOT}/quarantine"
local -r quarantine_path="${quarantine_root}/${payload_id}-$(date -u +%Y%m%dT%H%M%SZ)-$$"
mkdir -p "${quarantine_root}"
mv -- "${destination}" "${quarantine_path}"
Log "quarantined corrupt immutable payload: ${quarantine_path}"
fi
local staging_dir
staging_dir="$(mktemp -d "${STORE_ROOT}/payloads/.stage-${payload_id}.XXXXXX")"
trap 'rm -rf -- "${staging_dir}"' RETURN
cp -a -- "${payload_dir}/." "${staging_dir}/"
VerifyPayload "${staging_dir}"
chmod -R a-w "${staging_dir}"
mv -- "${staging_dir}" "${destination}"
trap - RETURN
Log "staged immutable payload: ${payload_id}"
printf '%s\n' "${payload_id}"
}
IsSupportedProfile() {
local -r payload_id="$1"
local -r metadata="${STORE_ROOT}/payloads/${payload_id}/roblox_payload.json"
[[ -f "${metadata}" ]] || return 1
[[ -n "${COMPATIBILITY_PATH}" && -r "${COMPATIBILITY_PATH}" ]] || return 1
local build_id version_code
build_id="$(jq -r '.elf_build_id | ascii_downcase' "${metadata}")"
version_code="$(jq -r '.version_code' "${metadata}")"
jq -e --arg build_id "${build_id}" --argjson version_code "${version_code}" '
any(.profiles[]?;
(.elf_build_id | ascii_downcase) == $build_id and
.version_code == $version_code and
.status == "supported" and
.default_allowed == true and
.allow_legacy_binary_patches == false)
' "${COMPATIBILITY_PATH}" >/dev/null
}
ApprovalReceiptPath() {
printf '%s/approvals/%s-%s.json\n' "${STORE_ROOT}" "$1" "$2"
}
ApprovedProfilePath() {
printf '%s/host_abi_profiles/%s-%s.json\n' "${STORE_ROOT}" "$1" "$2"
}
ApprovedCompatibilityPath() {
printf '%s/compatibility_profiles/%s-%s.json\n' "${STORE_ROOT}" "$1" "$2"
}
ApprovedCanaryPath() {
printf '%s/approvals/%s-%s.canary-%s.json\n' \
"${STORE_ROOT}" "$1" "$2" "$3"
}
ManifestApprovalGeneration() {
local -r manifest="$1"
local -r payload_id="$2"
local approval_path profile_path compatibility_path generation
approval_path="$(jq -er '.approval_path | select(type == "string")' \
"${manifest}" 2>/dev/null || true)"
profile_path="$(jq -er '.host_abi_profile_path |
select(type == "string")' "${manifest}" 2>/dev/null || true)"
compatibility_path="$(jq -er '.compatibility_manifest_path |
select(type == "string")' "${manifest}" 2>/dev/null || true)"
if [[ "${approval_path}" =~ ^approvals/${payload_id}-([0-9a-f]{40})\.json$ ]]; then
generation="${BASH_REMATCH[1]}"
else
return 1
fi
[[ "${profile_path}" == \
"host_abi_profiles/${payload_id}-${generation}.json" &&
"${compatibility_path}" == \
"compatibility_profiles/${payload_id}-${generation}.json" ]] ||
return 1
printf '%s\n' "${generation}"
}
IsExactCandidateProfile() {
local -r payload_id="$1"
local -r profile_path="$2"
local -r payload_dir="${STORE_ROOT}/payloads/${payload_id}"
local -r metadata="${payload_dir}/roblox_payload.json"
[[ -f "${profile_path}" && ! -L "${profile_path}" ]] || return 1
local build_id version_code libroblox_sha256
build_id="$(jq -er '.elf_build_id | ascii_downcase |
select(test("^[0-9a-f]{40}$"))' "${metadata}" 2>/dev/null)" || return 1
version_code="$(jq -er '.version_code |
select(type == "number" and floor == . and . >= 0)' \
"${metadata}" 2>/dev/null)" || return 1
libroblox_sha256="$(jq -er '.sha256.libroblox |
select(test("^[0-9a-f]{64}$"))' "${metadata}" 2>/dev/null)" || return 1
jq -e --arg payload_id "${payload_id}" \
--arg payload_path "payloads/${payload_id}" \
--arg build_id "${build_id}" \
--arg payload_sha256 "${libroblox_sha256}" '
.schema_version == 1 and
.payload_id == $payload_id and
.payload_path == $payload_path and
(.elf_build_id | ascii_downcase) == $build_id and
.payload_sha256 == $payload_sha256 and
(.reference | type) == "object" and
(.profile | type) == "object" and
(.profile.bridge_entries | type) == "array" and
(.profile.data_seeds | type) == "object" and
(.profile.native_allocator | type) == "object" and
(.profile.constructor_run_ranges | type) == "array" and
(.profile.native_mimalloc_constructor_run_ranges | type) == "array" and
(.profile.native_pre_jni_bootstrap | type) == "object" and
.profile.default_allocator_strategy == "native_mimalloc"
' "${profile_path}" >/dev/null
}
IsExactCandidateCompatibility() {
local -r payload_id="$1"
local -r manifest_path="$2"
local -r metadata="${STORE_ROOT}/payloads/${payload_id}/roblox_payload.json"
[[ -f "${manifest_path}" && ! -L "${manifest_path}" ]] || return 1
local build_id version_code
build_id="$(jq -er '.elf_build_id | ascii_downcase' "${metadata}" 2>/dev/null)" || return 1
version_code="$(jq -er '.version_code' "${metadata}" 2>/dev/null)" || return 1
jq -e --arg build_id "${build_id}" --argjson version_code "${version_code}" '
.schema_version == 1 and
(.profiles | type) == "array" and
(.profiles | length) == 1 and
.profiles[0].version_code == $version_code and
(.profiles[0].elf_build_id | ascii_downcase) == $build_id and
.profiles[0].status == "experimental" and
.profiles[0].default_allowed == true and
.profiles[0].allow_legacy_binary_patches == false and
.profiles[0].allow_host_abi_bridges == true and
.profiles[0].allow_host_constructor_replay == true
' "${manifest_path}" >/dev/null
}
IsApprovedProfile() {
local -r payload_id="$1"
local -r generation="$2"
[[ "${generation}" =~ ^[0-9a-f]{40}$ ]] || return 1
local -r receipt="$(ApprovalReceiptPath "${payload_id}" "${generation}")"
local -r profile="$(ApprovedProfilePath "${payload_id}" "${generation}")"
local -r compatibility="$(ApprovedCompatibilityPath \
"${payload_id}" "${generation}")"
local -r canary_one="$(ApprovedCanaryPath \
"${payload_id}" "${generation}" 1)"
local -r canary_two="$(ApprovedCanaryPath \
"${payload_id}" "${generation}" 2)"
local regular_path
for regular_path in "${receipt}" "${profile}" "${compatibility}" \
"${canary_one}" "${canary_two}"; do
[[ -f "${regular_path}" && ! -L "${regular_path}" ]] || return 1
done
IsExactCandidateProfile "${payload_id}" "${profile}" || return 1
IsExactCandidateCompatibility "${payload_id}" "${compatibility}" || return 1
local payload_fingerprint libroblox_sha256 profile_sha256
local compatibility_sha256 canary_one_sha256 canary_two_sha256
payload_fingerprint="$(PayloadRuntimeFingerprint "${payload_id}" 2>/dev/null)" || return 1
libroblox_sha256="$(sha256sum \
"${STORE_ROOT}/payloads/${payload_id}/libroblox.so" | awk '{print $1}')"
profile_sha256="$(sha256sum "${profile}" | awk '{print $1}')"
compatibility_sha256="$(sha256sum "${compatibility}" | awk '{print $1}')"
canary_one_sha256="$(sha256sum "${canary_one}" | awk '{print $1}')"
canary_two_sha256="$(sha256sum "${canary_two}" | awk '{print $1}')"
local canonical_library
canonical_library="$(cd -- "${STORE_ROOT}/payloads/${payload_id}" && pwd -P)/libroblox.so"
local receipt_runtime_sha256 receipt_runtime_build_id expected_generation
receipt_runtime_sha256="$(jq -er '.canary_runtime_sha256 |
select(test("^[0-9a-f]{64}$"))' "${receipt}")" || return 1
receipt_runtime_build_id="$(jq -er '.runtime_build_id |
select(test("^[0-9a-f]{40}$"))' "${receipt}")" || return 1
expected_generation="$(printf '%s\n' \
"runtime_build_id=${receipt_runtime_build_id}" \
"payload=${payload_fingerprint}" \
"profile=${profile_sha256}" \
"compatibility=${compatibility_sha256}" \
"canary_1=${canary_one_sha256}" \
"canary_2=${canary_two_sha256}" |
sha256sum | awk '{print substr($1, 1, 40)}')"
[[ "${generation}" == "${expected_generation}" ]] || return 1
jq -e --arg payload_id "${payload_id}" \
--arg generation "${generation}" \
--arg payload_path "${canonical_library}" \
--arg build_id "${payload_id#*-}" \
--arg payload_sha256 "${libroblox_sha256}" \
--arg payload_fingerprint "${payload_fingerprint}" \
--arg profile_sha256 "${profile_sha256}" \
--arg compatibility_sha256 "${compatibility_sha256}" \
--arg canary_one_sha256 "${canary_one_sha256}" \
--arg canary_two_sha256 "${canary_two_sha256}" \
--arg runtime_sha256 "${receipt_runtime_sha256}" \
--arg runtime_build_id "${receipt_runtime_build_id}" '
.schema_version == 1 and .status == "approved" and
.generation == $generation and
.payload_id == $payload_id and .payload_path == $payload_path and
(.elf_build_id | ascii_downcase) == $build_id and
.payload_sha256 == $payload_sha256 and
.payload_fingerprint == $payload_fingerprint and
.profile_sha256 == $profile_sha256 and
.compatibility_manifest_sha256 == $compatibility_sha256 and
.canary_runtime_sha256 == $runtime_sha256 and
.runtime_sha256 == $runtime_sha256 and
.runtime_build_id == $runtime_build_id and
.canary_tier == "C" and .successful_runs == 2 and
.canary_attestation_sha256 ==
[$canary_one_sha256, $canary_two_sha256]
' "${receipt}" >/dev/null || return 1
VerifyCanaryAttestation "${canary_one}" 1 "${payload_id}" \
"${payload_fingerprint}" "${profile_sha256}" \
"${compatibility_sha256}" "${receipt_runtime_sha256}" \
"${receipt_runtime_build_id}" || return 1
VerifyCanaryAttestation "${canary_two}" 2 "${payload_id}" \
"${payload_fingerprint}" "${profile_sha256}" \
"${compatibility_sha256}" "${receipt_runtime_sha256}" \
"${receipt_runtime_build_id}" || return 1
[[ "$(jq -er '.run_id' "${canary_one}")" != \
"$(jq -er '.run_id' "${canary_two}")" ]]
}
ValidateManifestPayload() {
local -r manifest="$1"
local payload_id approval_generation=""
payload_id="$(ReadManifestPayloadId "${manifest}")"
if IsSupportedProfile "${payload_id}"; then
RequireSupportedProfile "${payload_id}"
else
approval_generation="$(ManifestApprovalGeneration \
"${manifest}" "${payload_id}")" ||
Die "approved payload manifest has invalid approval references"
RequireSupportedProfile "${payload_id}" "${approval_generation}"
fi
printf '%s|%s\n' "${payload_id}" "${approval_generation}"
}
RequireSupportedProfile() {
local -r payload_id="$1"
local -r approval_generation="${2:-}"
[[ -d "${STORE_ROOT}/payloads/${payload_id}" ]] || Die "payload is not staged: ${payload_id}"
RequirePayloadIdentity "${payload_id}"
[[ -n "${COMPATIBILITY_PATH}" && -r "${COMPATIBILITY_PATH}" ]] ||
Die "promotion requires --compatibility FILE"
if ! IsSupportedProfile "${payload_id}" &&
! IsApprovedProfile "${payload_id}" "${approval_generation}"; then
Die "payload ${payload_id} is neither exact-supported nor probation-approved"
fi
VerifyPayload "${STORE_ROOT}/payloads/${payload_id}"
}
CurrentPayloadId() {
local -r current="${STORE_ROOT}/current.json"
if [[ ! -e "${current}" && ! -L "${current}" ]]; then
printf 'none\n'
return 0
fi
local manifest_identity payload_id
manifest_identity="$(ValidateManifestPayload "${current}")"
payload_id="${manifest_identity%%|*}"
printf '%s\n' "${payload_id}"
}
RequireExpectedCurrent() {
[[ -n "${EXPECTED_CURRENT}" ]] || return 0
[[ "${EXPECTED_CURRENT}" == none ||
"${EXPECTED_CURRENT}" =~ ^[0-9]+-[0-9a-f]{40}$ ]] ||
Die "--expected-current has an invalid payload ID"
local actual
actual="$(CurrentPayloadId)"
[[ "${actual}" == "${EXPECTED_CURRENT}" ]] ||
Die "current payload changed during update: expected ${EXPECTED_CURRENT}, found ${actual}"
}
WriteManifest() {
local -r payload_id="$1"
local -r destination="$2"
local -r approval_generation="${3:-}"
local -r metadata="${STORE_ROOT}/payloads/${payload_id}/roblox_payload.json"
local temporary
temporary="$(mktemp "${STORE_ROOT}/.manifest.XXXXXX")"
local approved=false
if ! IsSupportedProfile "${payload_id}" &&
IsApprovedProfile "${payload_id}" "${approval_generation}"; then
approved=true
fi
if ! jq -n \
--arg payload_id "${payload_id}" \
--arg payload_path "payloads/${payload_id}" \
--arg version_name "$(jq -r '.version_name' "${metadata}")" \
--argjson version_code "$(jq -r '.version_code' "${metadata}")" \
--arg build_id "$(jq -r '.elf_build_id | ascii_downcase' "${metadata}")" \
--arg activated_at "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--argjson approved "${approved}" \
--arg approval_path \
"approvals/${payload_id}-${approval_generation}.json" \
--arg profile_path \
"host_abi_profiles/${payload_id}-${approval_generation}.json" \
--arg compatibility_path \
"compatibility_profiles/${payload_id}-${approval_generation}.json" \
'{schema_version: 1, payload_id: $payload_id, payload_path: $payload_path,
version_name: $version_name, version_code: $version_code,
elf_build_id: $build_id, activated_at: $activated_at} +
(if $approved then {
approval_path: $approval_path,
host_abi_profile_path: $profile_path,
compatibility_manifest_path: $compatibility_path
} else {} end)' > "${temporary}"; then
rm -f -- "${temporary}"
return 1
fi
if ! chmod 0644 "${temporary}"; then
rm -f -- "${temporary}"
return 1
fi
if ! mv -f -- "${temporary}" "${destination}"; then
rm -f -- "${temporary}"
return 1
fi
}
CopyApprovalArtifact() {
local -r source_path="$1"
local -r destination_path="$2"
local -r expected_sha256="$3"
[[ -f "${source_path}" && ! -L "${source_path}" ]] ||
Die "approval input is not a regular file: ${source_path}"
[[ "${expected_sha256}" =~ ^[0-9a-f]{64}$ ]] ||
Die "approval artifact has an invalid expected hash"
mkdir -p -- "${destination_path%/*}"
if [[ -e "${destination_path}" || -L "${destination_path}" ]]; then
[[ -f "${destination_path}" && ! -L "${destination_path}" &&
"$(sha256sum "${destination_path}" | awk '{print $1}')" == \
"${expected_sha256}" ]] ||
Die "immutable approval generation collision: ${destination_path}"
return 0
fi
local temporary_path
temporary_path="$(mktemp "${destination_path%/*}/.approval-artifact.XXXXXX")"
if ! cp -- "${source_path}" "${temporary_path}" ||
[[ "$(sha256sum "${temporary_path}" | awk '{print $1}')" != \
"${expected_sha256}" ]] ||
! chmod 0444 -- "${temporary_path}" ||
! mv -- "${temporary_path}" "${destination_path}"; then
rm -f -- "${temporary_path}"
Die "failed to persist an approval artifact"
fi
}
VerifyCanaryAttestation() {
local -r attestation_path="$1"
local -r expected_run="$2"
local -r payload_id="$3"
local -r payload_fingerprint="$4"
local -r profile_sha256="$5"
local -r compatibility_sha256="$6"
local -r runtime_sha256="$7"
local -r runtime_build_id="$8"
[[ -f "${attestation_path}" && ! -L "${attestation_path}" ]] || return 1
jq -e --argjson run "${expected_run}" \
--arg payload_id "${payload_id}" \
--arg payload_fingerprint "${payload_fingerprint}" \
--arg profile_sha256 "${profile_sha256}" \
--arg compatibility_sha256 "${compatibility_sha256}" \
--arg runtime_sha256 "${runtime_sha256}" \
--arg runtime_build_id "${runtime_build_id}" '
.schema_version == 1 and .status == "passed" and
.canary_tier == "C" and .run == $run and
.payload_id == $payload_id and
.payload_fingerprint == $payload_fingerprint and
.profile_sha256 == $profile_sha256 and
.compatibility_manifest_sha256 == $compatibility_sha256 and
.runtime_sha256 == $runtime_sha256 and
.runtime_build_id == $runtime_build_id and
(.run_id | type == "string" and
test("^[0-9]+-[0-9a-f]{40}-[12]-[0-9]+-[0-9]+$")) and
(.readiness_log_sha256 | test("^[0-9a-f]{64}$"))
' "${attestation_path}" >/dev/null
}
PromoteProbationPayload() {
local -r payload_id="$1"
[[ "${payload_id}" =~ ^[0-9]+-[0-9a-f]{40}$ ]] ||
Die "invalid payload ID: ${payload_id}"
[[ -d "${STORE_ROOT}/payloads/${payload_id}" ]] ||
Die "payload is not staged: ${payload_id}"
RequirePayloadIdentity "${payload_id}"
VerifyPayload "${STORE_ROOT}/payloads/${payload_id}"
IsSupportedProfile "${payload_id}" &&
Die "an exact-supported payload must use regular promotion"
[[ -n "${EXPECTED_PAYLOAD_FINGERPRINT}" &&
"${EXPECTED_PAYLOAD_FINGERPRINT}" =~ ^[0-9a-f]{64}$ ]] ||
Die "probation promotion requires --expected-payload-fingerprint"
[[ "${RUNTIME_FINGERPRINT}" =~ ^[0-9a-f]{64}$ ]] ||
Die "probation promotion requires a valid --runtime-fingerprint"
[[ "${RUNTIME_BUILD_ID}" =~ ^[0-9a-f]{40}$ ]] ||
Die "probation promotion requires a valid --runtime-build-id"
(( ${#CANARY_ATTESTATION_PATHS[@]} == 2 )) ||
Die "probation promotion requires exactly two canary attestations"
IsExactCandidateProfile "${payload_id}" "${CANDIDATE_PROFILE_PATH}" ||
Die "derived candidate profile is not bound to ${payload_id}"
IsExactCandidateCompatibility "${payload_id}" \
"${CANDIDATE_COMPATIBILITY_PATH}" ||
Die "candidate compatibility manifest is not exact probation metadata"
local actual_fingerprint profile_sha256 compatibility_sha256
actual_fingerprint="$(PayloadRuntimeFingerprint "${payload_id}")"
[[ "${actual_fingerprint}" == "${EXPECTED_PAYLOAD_FINGERPRINT}" ]] ||
Die "payload ${payload_id} changed after canary"
profile_sha256="$(sha256sum "${CANDIDATE_PROFILE_PATH}" | awk '{print $1}')"
compatibility_sha256="$(sha256sum "${CANDIDATE_COMPATIBILITY_PATH}" | awk '{print $1}')"
VerifyCanaryAttestation "${CANARY_ATTESTATION_PATHS[0]}" 1 \
"${payload_id}" "${actual_fingerprint}" "${profile_sha256}" \
"${compatibility_sha256}" "${RUNTIME_FINGERPRINT}" \
"${RUNTIME_BUILD_ID}" ||
Die "first canary attestation is invalid"
VerifyCanaryAttestation "${CANARY_ATTESTATION_PATHS[1]}" 2 \
"${payload_id}" "${actual_fingerprint}" "${profile_sha256}" \
"${compatibility_sha256}" "${RUNTIME_FINGERPRINT}" \
"${RUNTIME_BUILD_ID}" ||
Die "second canary attestation is invalid"
[[ "$(jq -er '.run_id' "${CANARY_ATTESTATION_PATHS[0]}")" != \
"$(jq -er '.run_id' "${CANARY_ATTESTATION_PATHS[1]}")" ]] ||
Die "canary attestations must have distinct run IDs"
RequireExpectedCurrent
local canary_one_sha256 canary_two_sha256
canary_one_sha256="$(sha256sum "${CANARY_ATTESTATION_PATHS[0]}" | awk '{print $1}')"
canary_two_sha256="$(sha256sum "${CANARY_ATTESTATION_PATHS[1]}" | awk '{print $1}')"
local approval_generation
approval_generation="$(printf '%s\n' \
"runtime_build_id=${RUNTIME_BUILD_ID}" \
"payload=${actual_fingerprint}" \
"profile=${profile_sha256}" \
"compatibility=${compatibility_sha256}" \
"canary_1=${canary_one_sha256}" \
"canary_2=${canary_two_sha256}" |
sha256sum | awk '{print substr($1, 1, 40)}')"
CopyApprovalArtifact "${CANDIDATE_PROFILE_PATH}" \
"$(ApprovedProfilePath "${payload_id}" "${approval_generation}")" \
"${profile_sha256}"
CopyApprovalArtifact "${CANDIDATE_COMPATIBILITY_PATH}" \
"$(ApprovedCompatibilityPath \
"${payload_id}" "${approval_generation}")" "${compatibility_sha256}"
CopyApprovalArtifact "${CANARY_ATTESTATION_PATHS[0]}" \
"$(ApprovedCanaryPath "${payload_id}" "${approval_generation}" 1)" \
"${canary_one_sha256}"
CopyApprovalArtifact "${CANARY_ATTESTATION_PATHS[1]}" \
"$(ApprovedCanaryPath "${payload_id}" "${approval_generation}" 2)" \
"${canary_two_sha256}"
local canonical_library libroblox_sha256 receipt_temporary receipt_path
canonical_library="$(cd -- "${STORE_ROOT}/payloads/${payload_id}" && pwd -P)/libroblox.so"
libroblox_sha256="$(sha256sum "${canonical_library}" | awk '{print $1}')"
receipt_path="$(ApprovalReceiptPath \
"${payload_id}" "${approval_generation}")"
receipt_temporary="$(mktemp "${STORE_ROOT}/approvals/.receipt.XXXXXX")"
jq -n \
--arg payload_id "${payload_id}" \
--arg generation "${approval_generation}" \
--arg payload_path "${canonical_library}" \
--arg build_id "${payload_id#*-}" \
--arg payload_sha256 "${libroblox_sha256}" \
--arg payload_fingerprint "${actual_fingerprint}" \
--arg profile_sha256 "${profile_sha256}" \
--arg compatibility_sha256 "${compatibility_sha256}" \
--arg runtime_sha256 "${RUNTIME_FINGERPRINT}" \
--arg runtime_build_id "${RUNTIME_BUILD_ID}" \
--arg canary_one_sha256 "${canary_one_sha256}" \
--arg canary_two_sha256 "${canary_two_sha256}" \
--arg approved_at "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" '
{schema_version: 1, status: "approved", payload_id: $payload_id,
generation: $generation,
payload_path: $payload_path, elf_build_id: $build_id,
payload_sha256: $payload_sha256,
payload_fingerprint: $payload_fingerprint,
profile_sha256: $profile_sha256,
compatibility_manifest_sha256: $compatibility_sha256,
canary_runtime_sha256: $runtime_sha256,
runtime_sha256: $runtime_sha256,
runtime_build_id: $runtime_build_id, canary_tier: "C",
successful_runs: 2,
canary_attestation_sha256:
[$canary_one_sha256, $canary_two_sha256],
approved_at: $approved_at}' > "${receipt_temporary}" || {
rm -f -- "${receipt_temporary}"
Die "failed to create probation approval receipt"
}
chmod 0444 -- "${receipt_temporary}"
local -r receipt_sha256="$(sha256sum \
"${receipt_temporary}" | awk '{print $1}')"
CopyApprovalArtifact "${receipt_temporary}" "${receipt_path}" \
"${receipt_sha256}"
rm -f -- "${receipt_temporary}"
IsApprovedProfile "${payload_id}" "${approval_generation}" ||
Die "persisted probation approval failed verification"
PROMOTION_APPROVAL_GENERATION="${approval_generation}"
PromotePayload "${payload_id}"
}
PromotePayload() {
local -r payload_id="$1"
[[ "${payload_id}" =~ ^[0-9]+-[0-9a-f]{40}$ ]] || Die "invalid payload ID: ${payload_id}"
RequireSupportedProfile "${payload_id}" \
"${PROMOTION_APPROVAL_GENERATION}"
if [[ -n "${EXPECTED_PAYLOAD_FINGERPRINT}" ]]; then
[[ "${EXPECTED_PAYLOAD_FINGERPRINT}" =~ ^[0-9a-f]{64}$ ]] ||
Die "--expected-payload-fingerprint has an invalid SHA-256 digest"
local actual_fingerprint
actual_fingerprint="$(PayloadRuntimeFingerprint "${payload_id}")"
[[ "${actual_fingerprint}" == "${EXPECTED_PAYLOAD_FINGERPRINT}" ]] ||
Die "payload ${payload_id} changed after canary"
fi
RequireExpectedCurrent
local current_payload_id
current_payload_id="$(CurrentPayloadId)"
if [[ "${current_payload_id}" == "${payload_id}" ]]; then
WriteManifest "${payload_id}" "${STORE_ROOT}/current.json" \
"${PROMOTION_APPROVAL_GENERATION}" ||
Die "failed to refresh the current payload manifest"
Log "payload is already current: ${payload_id}"
return 0
fi
local previous_backup=""
local previous_existed=false
if [[ "${current_payload_id}" != none ]]; then
if [[ -e "${STORE_ROOT}/previous_good.json" ||
-L "${STORE_ROOT}/previous_good.json" ]]; then
ValidateManifestPayload "${STORE_ROOT}/previous_good.json" >/dev/null
previous_existed=true
previous_backup="$(mktemp "${STORE_ROOT}/.previous-backup.XXXXXX")"
cp -- "${STORE_ROOT}/previous_good.json" "${previous_backup}"
fi
local previous_temporary
previous_temporary="$(mktemp "${STORE_ROOT}/.previous-good.XXXXXX")"
cp -- "${STORE_ROOT}/current.json" "${previous_temporary}"
if ! mv -f -- "${previous_temporary}" "${STORE_ROOT}/previous_good.json"; then
rm -f -- "${previous_temporary}"
[[ -z "${previous_backup}" ]] || rm -f -- "${previous_backup}"
return 1
fi
fi
if ! WriteManifest "${payload_id}" "${STORE_ROOT}/current.json" \
"${PROMOTION_APPROVAL_GENERATION}"; then
if [[ "${previous_existed}" == true ]]; then
mv -f -- "${previous_backup}" "${STORE_ROOT}/previous_good.json"
elif [[ "${current_payload_id}" != none ]]; then
rm -f -- "${STORE_ROOT}/previous_good.json"
fi
return 1
fi
if [[ -n "${previous_backup}" ]] &&
! rm -f -- "${previous_backup}"; then
Log "warning: failed to remove temporary previous-good backup" || true
fi
if IsSupportedProfile "${payload_id}"; then
Log "promoted supported payload: ${payload_id}" || true
else
Log "promoted probation-approved payload: ${payload_id}" || true
fi
return 0
}
ImportPayload() {
local payload_id
payload_id="$(StagePayload "$1")"
if IsSupportedProfile "${payload_id}"; then
PromotePayload "${payload_id}"
else
Log "staged ${payload_id} without promotion: Build ID is not default-supported"
fi
printf '%s\n' "${payload_id}"
}
RollbackPayload() {
local -r previous="${STORE_ROOT}/previous_good.json"
[[ -f "${previous}" ]] || Die "no previous-good payload is available"
local manifest_identity payload_id approval_generation
manifest_identity="$(ValidateManifestPayload "${previous}")"
payload_id="${manifest_identity%%|*}"
approval_generation="${manifest_identity#*|}"
if [[ -n "${EXPECTED_ROLLBACK_TARGET}" ]]; then
[[ "${EXPECTED_ROLLBACK_TARGET}" =~ ^[0-9]+-[0-9a-f]{40}$ ]] ||
Die "--expected-rollback-target has an invalid payload ID"
[[ "${payload_id}" == "${EXPECTED_ROLLBACK_TARGET}" ]] ||
Die "previous-good payload ${payload_id} does not match rollback target ${EXPECTED_ROLLBACK_TARGET}"
fi
RequireExpectedCurrent
if [[ -z "${approval_generation}" ]]; then
WriteManifest "${payload_id}" "${STORE_ROOT}/current.json" ||
Die "failed to atomically restore previous-good payload"
Log "rolled back to previous-good payload: ${payload_id}" || true
return 0
fi
local rollback_temporary
rollback_temporary="$(mktemp "${STORE_ROOT}/.rollback.XXXXXX")"
if ! cp -- "${previous}" "${rollback_temporary}" ||
! chmod 0644 -- "${rollback_temporary}" ||
! ValidateManifestPayload "${rollback_temporary}" >/dev/null ||
! mv -f -- "${rollback_temporary}" "${STORE_ROOT}/current.json"; then
rm -f -- "${rollback_temporary}"
Die "failed to atomically restore previous-good payload"
fi
Log "rolled back to previous-good payload: ${payload_id}" || true
return 0
}
PrintStatus() {
jq -n \
--slurpfile current <(if [[ -f "${STORE_ROOT}/current.json" ]]; then cat "${STORE_ROOT}/current.json"; else printf 'null\n'; fi) \
--slurpfile previous <(if [[ -f "${STORE_ROOT}/previous_good.json" ]]; then cat "${STORE_ROOT}/previous_good.json"; else printf 'null\n'; fi) \
'{current: $current[0], previous_good: $previous[0]}'
}
Main() {
RequireCommands
ParseGlobalOptions "$@"
set -- "${REMAINING_ARGUMENTS[@]}"
(( $# > 0 )) || { Usage >&2; exit 2; }
local -r command="$1"
shift
AcquireStoreLock
case "${command}" in
stage)
(( $# == 1 )) || Die "stage requires PAYLOAD_DIR"
StagePayload "$1"
;;
import)
(( $# == 1 )) || Die "import requires PAYLOAD_DIR"
ImportPayload "$1"
;;
promote)
(( $# == 1 )) || Die "promote requires PAYLOAD_ID"
PromotePayload "$1"
;;
promote-probation)
(( $# == 1 )) || Die "promote-probation requires PAYLOAD_ID"
PromoteProbationPayload "$1"
;;
fingerprint)
(( $# == 1 )) || Die "fingerprint requires PAYLOAD_ID"
[[ "$1" =~ ^[0-9]+-[0-9a-f]{40}$ ]] || Die "invalid payload ID: $1"
PayloadRuntimeFingerprint "$1"
;;
verify-current)
(( $# == 0 )) || Die "verify-current accepts no arguments"
CurrentPayloadId
;;
rollback)
(( $# == 0 )) || Die "rollback accepts no arguments"
RollbackPayload
;;
status)
(( $# == 0 )) || Die "status accepts no arguments"
PrintStatus
;;
*)
Die "unknown command: ${command}"
;;
esac
}
Main "$@"