fix: pin staging compose selectors

This commit is contained in:
Jesse_Chen
2026-07-20 16:43:08 +08:00
parent 26373d257b
commit 801666a6c7
5 changed files with 39 additions and 6 deletions
+3 -3
View File
@@ -101,7 +101,7 @@ jobs:
PREVIOUS_SHA="$(curl --fail --silent --show-error --max-time 10 "$STAGING_URL/api/health" 2>/dev/null | jq -r '.deployment.gitCommit // empty' || true)"
test -n "$PREVIOUS_SHA" || PREVIOUS_SHA="not-deployed"
PREVIOUS_IMAGES="$(ssh $SSH_OPTIONS "$DEPLOY_USER@$DEPLOY_HOST" \
"if [ -f '$DEPLOY_PATH/.env.staging' ] && [ -f '$DEPLOY_PATH/deploy/docker-compose.server.yml' ]; then cd '$DEPLOY_PATH' && docker compose --env-file .env.staging -f deploy/docker-compose.server.yml images --quiet; else echo not-deployed; fi")"
"if [ -f '$DEPLOY_PATH/.env.staging' ] && [ -f '$DEPLOY_PATH/deploy/docker-compose.server.yml' ]; then cd '$DEPLOY_PATH' && APP_ENV_FILE='../.env.staging' CADDYFILE_PATH='./Caddyfile.staging' SITE_ADDRESS='https://staging.jyotisha.chat' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml images --quiet; else echo not-deployed; fi")"
test -n "$PREVIOUS_IMAGES" || PREVIOUS_IMAGES="not-deployed"
{
echo "### Staging deployment state"
@@ -128,7 +128,7 @@ jobs:
-e "$RSYNC_SSH" \
./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"
ssh $SSH_OPTIONS "$DEPLOY_USER@$DEPLOY_HOST" \
"cd '$DEPLOY_PATH' && bash deploy/validate-staging-env.sh .env.staging && docker compose --env-file .env.staging -f deploy/docker-compose.server.yml config --quiet && GITHUB_SHA='$DEPLOY_GIT_SHA' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml up -d --build --remove-orphans"
"cd '$DEPLOY_PATH' && bash deploy/validate-staging-env.sh .env.staging && APP_ENV_FILE='../.env.staging' CADDYFILE_PATH='./Caddyfile.staging' SITE_ADDRESS='https://staging.jyotisha.chat' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml config --quiet && APP_ENV_FILE='../.env.staging' CADDYFILE_PATH='./Caddyfile.staging' SITE_ADDRESS='https://staging.jyotisha.chat' GITHUB_SHA='$DEPLOY_GIT_SHA' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml up -d --build --remove-orphans"
- name: Verify staging
env:
@@ -140,5 +140,5 @@ jobs:
ssh -i ~/.ssh/jyotisha-staging -p "$DEPLOY_PORT" \
-o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes \
"$DEPLOY_USER@$DEPLOY_HOST" \
"cd '$DEPLOY_PATH' && docker compose --env-file .env.staging -f deploy/docker-compose.server.yml exec -T web node -e 'fetch(\"http://api:5200/api/health\").then(async r => { const body = await r.json(); if (!r.ok || body.status !== \"ok\" || body.swisseph_available !== true) process.exit(1); console.log(JSON.stringify(body)); })'"
"cd '$DEPLOY_PATH' && APP_ENV_FILE='../.env.staging' CADDYFILE_PATH='./Caddyfile.staging' SITE_ADDRESS='https://staging.jyotisha.chat' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml exec -T web node -e 'fetch(\"http://api:5200/api/health\").then(async r => { const body = await r.json(); if (!r.ok || body.status !== \"ok\" || body.swisseph_available !== true) process.exit(1); console.log(JSON.stringify(body)); })'"
echo "- Verified deployed SHA: \`$DEPLOY_GIT_SHA\`" >> "$GITHUB_STEP_SUMMARY"
+1 -1
View File
@@ -155,7 +155,7 @@ CADDYFILE_PATH=./Caddyfile.staging
SITE_ADDRESS=https://staging.jyotisha.chat
```
After source sync and before `up`, the workflow validates `.env.staging` mode/selectors and runs `docker compose --env-file .env.staging -f deploy/docker-compose.server.yml config --quiet`. For later manual inspections, run the same checks only after the tracked deployment files exist on the server. The first deployment should be manual:
After source sync and before `up`, the workflow validates `.env.staging` mode/selectors, explicitly pins the three staging selectors against ambient shell overrides, and runs `docker compose --env-file .env.staging -f deploy/docker-compose.server.yml config --quiet`. For later manual inspections, run the same checks only after the tracked deployment files exist on the server. The first deployment should be manual:
1. Confirm `/opt/jyotisha-staging/.env.staging` exists, has mode `0600`, and contains the three selectors above.
2. Open GitHub Actions -> Jyotish Skill CI -> Run workflow, using workflow from `main`.
@@ -347,7 +347,7 @@ jobs:
-e "$RSYNC_SSH" \
./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/"
ssh $SSH_OPTIONS "$DEPLOY_USER@$DEPLOY_HOST" \
"cd '$DEPLOY_PATH' && bash deploy/validate-staging-env.sh .env.staging && docker compose --env-file .env.staging -f deploy/docker-compose.server.yml config --quiet && GITHUB_SHA='$DEPLOY_GIT_SHA' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml up -d --build --remove-orphans"
"cd '$DEPLOY_PATH' && bash deploy/validate-staging-env.sh .env.staging && APP_ENV_FILE='../.env.staging' CADDYFILE_PATH='./Caddyfile.staging' SITE_ADDRESS='https://staging.jyotisha.chat' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml config --quiet && APP_ENV_FILE='../.env.staging' CADDYFILE_PATH='./Caddyfile.staging' SITE_ADDRESS='https://staging.jyotisha.chat' GITHUB_SHA='$DEPLOY_GIT_SHA' docker compose --env-file .env.staging -f deploy/docker-compose.server.yml up -d --build --remove-orphans"
- name: Verify staging
env:
@@ -71,7 +71,7 @@ push staging
-> checkout 已测试 SHA
-> 记录旧 SHA 和镜像 ID
-> rsync 到 /opt/jyotisha-staging(排除所有 .env*
-> 校验 .env.staging 权限、固定选择器和 Compose 配置
-> 校验 .env.staging 权限、固定选择器和 Compose 配置,并在 Compose 进程上显式钉死 staging 选择器
-> docker compose build/up
-> login、401 account、Python health smoke tests
-> 记录部署 SHA
+33
View File
@@ -120,6 +120,17 @@ test("staging deploy consumes only the isolated staging environment and tested r
assert.match(workflow, /deployment\.gitCommit/);
assert.doesNotMatch(workflow, /PRODUCTION_SSH_PRIVATE_KEY/);
assert.doesNotMatch(workflow, /103\.117\.123\.53/);
const composeLines = workflow
.split("\n")
.filter((line) => line.includes("docker compose"));
assert.equal(workflow.match(/docker compose/g)?.length, 4);
assert.equal(composeLines.length, 3);
for (const line of composeLines) {
assert.match(line, /APP_ENV_FILE='\.\.\/\.env\.staging'/);
assert.match(line, /CADDYFILE_PATH='\.\/Caddyfile\.staging'/);
assert.match(line, /SITE_ADDRESS='https:\/\/staging\.jyotisha\.chat'/);
}
});
test("staging rsync preserves every destination env variant during delete", () => {
@@ -209,6 +220,28 @@ test("staging env validator rejects selector drift, duplicates, and unsafe permi
);
writeEnv(validSelectors);
assert.equal(run().status, 0);
const shellOverride = spawnSync(
"docker",
[
"compose",
"--env-file",
envFile,
"-f",
composeFile,
"config",
"--format",
"json",
],
{
encoding: "utf8",
env: { ...process.env, APP_ENV_FILE: "../.env.production" },
},
);
assert.equal(shellOverride.status, 0, shellOverride.stderr);
assert.equal(
JSON.parse(shellOverride.stdout).services.probe.environment.SELECTED,
"../.env.production",
);
writeEnv(["APP_ENV_FILE=../.env.production", ...validSelectors.slice(1)]);
assert.notEqual(run().status, 0);