772c06c205
Deploy staging to test server / deploy (push) Successful in 2m27s
Create staging mutation payloads in randomized deploy-owned temporary directories so migration and deployment do not require writes under the protected application root.
94 lines
6.1 KiB
YAML
94 lines
6.1 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); xs=d if isinstance(d,list) else [d]; xs=[x for x in xs if isinstance(x,dict) and isinstance(x.get("Descriptor",x),dict)]; x=next((x for x in xs if x.get("Descriptor",x).get("platform",{}).get("os")=="linux" and x.get("Descriptor",x).get("platform",{}).get("architecture")=="amd64"),None); print(x.get("Descriptor",x).get("digest","") if x else "")')"
|
|
[[ "$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"
|
|
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"
|
|
incoming="$(ssh "${ssh_options[@]}" "$remote" "mktemp -d /tmp/jyotisha-staging.XXXXXXXXXX")"
|
|
[[ "$incoming" == /tmp/jyotisha-staging.* ]]
|
|
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.'
|