#!/bin/sh # Sourced by both activation paths after the deployment and synchronization locks. caiwu_verify_release_base() { case "$EXPECTED_PREVIOUS_SHA" in none) ;; *[!0-9a-f]*|'') echo 'Missing or invalid checked production revision; update the deployment scripts' >&2; return 1 ;; *) [ "${#EXPECTED_PREVIOUS_SHA}" -eq 40 ] || return 1 ;; esac PREVIOUS_SHA="$(cat "$REMOTE_DIR/.deployed-sha" 2>/dev/null || true)" [ -n "$PREVIOUS_SHA" ] || PREVIOUS_SHA=none if [ "$PREVIOUS_SHA" != none ] && ! printf '%s\n' "$PREVIOUS_SHA" | grep -Eq '^[0-9a-f]{40}$'; then echo 'Invalid production revision marker; refusing activation' >&2 return 1 fi if [ "$PREVIOUS_SHA" != "$EXPECTED_PREVIOUS_SHA" ]; then echo "Production changed while this release was being prepared: checked=$EXPECTED_PREVIOUS_SHA current=$PREVIOUS_SHA requested=$DEPLOY_SHA. Fetch latest main and retry; the running application was not changed." >&2 return 1 fi # A failed/interrupted older activation must not leave the marker ahead of the app. running_sha="$(docker inspect caiwu_app --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' 2>/dev/null || true)" if [ "$PREVIOUS_SHA" != none ] && [ "$running_sha" != "$PREVIOUS_SHA" ]; then echo "Running image and production marker disagree; refusing activation: marker=$PREVIOUS_SHA image=$running_sha" >&2 return 1 fi if [ "$PREVIOUS_SHA" = none ] && [ -n "$running_sha" ]; then echo 'An application is already running without a production marker; verify its revision before deploying' >&2 return 1 fi } caiwu_install_release_files() { # Each uploader has its own directory. Shared runtime files change only inside # the activation lock and after the production revision has been rechecked. [ "$RELEASE_DIR" != "$REMOTE_DIR" ] || return 0 release_files='compose.jiangling.yml activate-release.sh activate-incremental-release.sh release-guard.sh retired-activation.sh install-release-protection.sh config-db-init.sh run-finance-batch.sh run-finance-import.py finance-batch-imports.json clickhouse-resource-policy.xml update-runtime-url.sh configure-dingtalk-callback.sh host-nginx-8442-dingtalk.conf setup-incremental-registry.sh registry-config.yml' for release_file in $release_files; do [ -f "$RELEASE_DIR/$release_file" ] || { echo "Incomplete staged release: $release_file" >&2; return 1; } done sh "$RELEASE_DIR/install-release-protection.sh" "$REMOTE_DIR" || return 1 for release_file in $release_files; do case "$release_file" in activate-release.sh|activate-incremental-release.sh) continue ;; esac case "$release_file" in *.sh) release_mode=700 ;; *) release_mode=644 ;; esac [ "$release_file" != config-db-init.sh ] || release_mode=644 install -m "$release_mode" "$RELEASE_DIR/$release_file" "$REMOTE_DIR/.$release_file.next" mv "$REMOTE_DIR/.$release_file.next" "$REMOTE_DIR/$release_file" done if [ -n "$RELEASE_APP_URL" ]; then "$REMOTE_DIR/update-runtime-url.sh" "$RELEASE_APP_URL" "$REMOTE_DIR" fi printf '%s\n' "$DEPLOY_SHA" > "$REMOTE_DIR/.release-controls-sha.next" mv "$REMOTE_DIR/.release-controls-sha.next" "$REMOTE_DIR/.release-controls-sha" } caiwu_record_release_result() { release_exit="$1" [ "$RELEASE_RESULT" != running ] || RELEASE_RESULT=failed [ "$release_exit" -ne 0 ] || RELEASE_RESULT=success # Client identity is declared by the workstation; the SSH source is recorded # separately. Neither field is inferred from the commit author. release_actor="$(printf '%s' "$DEPLOY_ACTOR" | tr -cd 'A-Za-z0-9_.@:-' | cut -c1-160)" release_peer="$(printf '%s' "${SSH_CONNECTION:-local}" | tr -cd 'A-Za-z0-9 .:-' | cut -c1-160)" ( umask 077 mkdir -p "$REMOTE_DIR/runtime/private" printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ "$RELEASE_STARTED_AT" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$release_actor" "$release_peer" \ "${PREVIOUS_SHA:-unknown}" "$DEPLOY_SHA" "$RELEASE_RESULT" "$release_exit" "$EXPECTED_PREVIOUS_SHA" \ >> "$REMOTE_DIR/runtime/private/deployments.tsv" ) || echo 'Could not write the private deployment receipt' >&2 }