85ae00f391
Deploy staging to test server / deploy (push) Successful in 2m27s
Make staging migration, deployment, and maintenance workflows use the staging branch exclusively so test delivery no longer depends on or modifies production main.
93 lines
5.8 KiB
YAML
93 lines
5.8 KiB
YAML
name: Migrate Staging Database (manual only)
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
deploy_sha:
|
|
description: Full current staging SHA to migrate
|
|
required: true
|
|
type: string
|
|
|
|
concurrency:
|
|
group: staging-mutation
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
migrate:
|
|
runs-on: xiaoxin
|
|
timeout-minutes: 20
|
|
env:
|
|
GITEA_SHA: ${{ gitea.sha }}
|
|
REGISTRY_HOST: crpi-d1feco6itet73spp.cn-hongkong.personal.cr.aliyuncs.com
|
|
IMAGE_REPOSITORY: crpi-d1feco6itet73spp.cn-hongkong.personal.cr.aliyuncs.com/copse/jyotisha
|
|
DEPLOY_HOST: ${{ vars.STAGING_HOST }}
|
|
DEPLOY_PORT: ${{ vars.STAGING_PORT }}
|
|
DEPLOY_USER: ${{ vars.STAGING_USER }}
|
|
DEPLOY_PATH: ${{ vars.STAGING_PATH }}
|
|
STAGING_KNOWN_HOSTS: ${{ vars.STAGING_KNOWN_HOSTS }}
|
|
steps:
|
|
- name: Checkout staging controller
|
|
run: |
|
|
set -euo pipefail
|
|
git init .
|
|
git remote remove origin 2>/dev/null || true
|
|
git remote add origin https://git.copse.top/root/Jyotisha.git
|
|
git fetch --no-tags origin staging
|
|
git checkout --detach --force origin/staging
|
|
- name: Verify runner toolchain
|
|
run: |
|
|
set -euo pipefail
|
|
python3 --version
|
|
node --version
|
|
npm --version
|
|
docker version
|
|
- name: Validate revision and resolve migration image
|
|
id: image
|
|
env:
|
|
DEPLOY_SHA: ${{ inputs.deploy_sha }}
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$DEPLOY_SHA" =~ ^[0-9a-f]{40}$ ]] || exit 1
|
|
[[ "$(git ls-remote origin refs/heads/staging | awk '{print $1}')" == "$DEPLOY_SHA" ]] || { echo "migration requires current staging head" >&2; exit 1; }
|
|
git cat-file -e "${DEPLOY_SHA}^{commit}"
|
|
printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin
|
|
web_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:web-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("Descriptor", d).get("digest", ""))')"
|
|
[[ "$web_digest" =~ ^sha256:[0-9a-f]{64}$ ]] || exit 1
|
|
echo "web_image=${IMAGE_REPOSITORY}@${web_digest}" >> "$GITHUB_OUTPUT"
|
|
echo "deploy_sha=$DEPLOY_SHA" >> "$GITHUB_OUTPUT"
|
|
- name: Apply digest-pinned migration under host lock
|
|
env:
|
|
SSH_PRIVATE_KEY: ${{ secrets.STAGING_SSH_PRIVATE_KEY }}
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
DEPLOY_SHA: ${{ steps.image.outputs.deploy_sha }}
|
|
WEB_IMAGE: ${{ steps.image.outputs.web_image }}
|
|
run: |
|
|
set -euo pipefail
|
|
ssh_root="${RUNNER_TEMP}/staging-migration-ssh"
|
|
key_path="$ssh_root/id_ed25519"
|
|
known_hosts_path="$ssh_root/known_hosts"
|
|
incoming="$DEPLOY_PATH/.incoming/$GITEA_RUN_NUMBER-$GITEA_RUN_ATTEMPT"
|
|
install -m 700 -d "$ssh_root"
|
|
printf '%s\n' "$SSH_PRIVATE_KEY" | tr -d '\r' > "$key_path"
|
|
printf '%s\n' "$STAGING_KNOWN_HOSTS" | tr -d '\r' > "$known_hosts_path"
|
|
chmod 600 "$key_path" "$known_hosts_path"
|
|
ssh_options=(-i "$key_path" -p "$DEPLOY_PORT" -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_path")
|
|
remote="$DEPLOY_USER@$DEPLOY_HOST"
|
|
cleanup() { ssh "${ssh_options[@]}" "$remote" "DOCKER_CONFIG='$incoming/.docker' docker logout '$REGISTRY_HOST' >/dev/null 2>&1 || true; rm -rf -- '$incoming'" >/dev/null 2>&1 || true; docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true; rm -rf -- "$ssh_root"; }
|
|
trap cleanup EXIT
|
|
ssh "${ssh_options[@]}" "$remote" "install -d -m 700 '$incoming/.docker'"
|
|
tar -cf "${RUNNER_TEMP}/deploy.tar" deploy
|
|
scp -i "$key_path" -P "$DEPLOY_PORT" -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_path" "${RUNNER_TEMP}/deploy.tar" "$remote:$incoming/deploy.tar"
|
|
ssh "${ssh_options[@]}" "$remote" "tar -xf '$incoming/deploy.tar' -C '$incoming' && rm -f -- '$incoming/deploy.tar'"
|
|
previous_sha="$(ssh "${ssh_options[@]}" "$remote" "state='$DEPLOY_PATH/.state/deployed-revision'; if [ -f \"\$state\" ]; then cat \"\$state\"; else id=\$(docker ps -aq --filter 'label=com.docker.compose.project=jyotisha-staging' --filter 'label=com.docker.compose.service=web' | head -n 1); if [ -n \"\$id\" ]; then docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' \"\$id\" | sed -n 's/^GITHUB_SHA=//p' | head -n 1; else printf not-deployed; fi; fi")"
|
|
[[ "$previous_sha" == not-deployed || "$previous_sha" =~ ^[0-9a-f]{40}$ ]] || exit 1
|
|
forward_verified=false
|
|
if [[ "$previous_sha" != not-deployed && "$previous_sha" != "$DEPLOY_SHA" ]]; then git cat-file -e "${previous_sha}^{commit}" 2>/dev/null || git fetch origin "$previous_sha"; git merge-base --is-ancestor "$previous_sha" "$DEPLOY_SHA" || { echo "migration rollback or divergence refused" >&2; exit 1; }; forward_verified=true; fi
|
|
printf '%s' "$REGISTRY_PASSWORD" | ssh "${ssh_options[@]}" "$remote" "DOCKER_CONFIG='$incoming/.docker' docker login '$REGISTRY_HOST' --username '$REGISTRY_USERNAME' --password-stdin"
|
|
ssh "${ssh_options[@]}" "$remote" "INCOMING_PATH='$incoming' DEPLOY_PATH='$DEPLOY_PATH' WEB_IMAGE='$WEB_IMAGE' DEPLOY_SHA='$DEPLOY_SHA' EXPECTED_PREVIOUS_SHA='$previous_sha' FORWARD_REVISION_VERIFIED='$forward_verified' DOCKER_CONFIG='$incoming/.docker' bash '$incoming/deploy/run-staging-migration.sh'"
|
|
- name: Operator action
|
|
run: echo 'Migration complete. Start Deploy staging manually with this exact SHA.'
|