chore(sync): cascade main -> dev #25

Merged
jmiller merged 1 commits from main into dev 2026-07-20 14:03:16 +00:00
3 changed files with 81 additions and 278 deletions
+26 -92
View File
@@ -1,24 +1,16 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Build and deploy to the Dev environment on push to the dev branch.
# Portable across Go server repos: ALL deployment config comes from repo
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
# The dev branch is the ongoing integration branch.
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
# BRIEF: Deploy to the Dev environment on push to the dev branch. THIN TRIGGER —
# the deploy LOGIC lives in mokocli (cli/deploy.php) and server-side in the
# restricted per-repo deploy pattern (.vault system/deploy, runbook 16).
# This workflow validates + invokes only; it carries NO registry token and
# NO build script. A leaked DEPLOY_SSH_KEY can only redeploy this one repo.
# OWNER: Template-Go (canonical; syncs to each repo's .mokogit/workflows).
#
# Required repo VARIABLES (all tier-scoped so each environment is independent —
# a repo may host its rc/dev/prod tiers on different machines):
# DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME - SSH deploy target for the dev tier
# DEV_REGISTRY, DEV_REGISTRY_USER, DEV_IMAGE - container registry + login user + image
# DEV_CONTAINER - compose service/container to recreate
# DEV_COMPOSE_PROJECT - docker compose -p project name
# DEV_COMPOSE_DIR - dir containing docker-compose.yml on host
# DEV_SOURCE_DIR - build source checkout on host
# DEV_TAG_ENV - compose env-var name that pins the image tag
# DEV_HEALTH_URL - external URL to verify after deploy
# Required SECRETS (already configured org-wide; reused, not re-set):
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
# MOKOGIT_TOKEN - registry/API token (org secret)
# Required repo VARIABLES (tier-scoped): DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME.
# Required SECRET: DEPLOY_SSH_KEY (the deploy-<repo> private key).
# ONBOARDING: a repo joins the restricted deploy pattern when DEV_SSH_USERNAME is
# set to deploy-<repo>. Un-onboarded go repos skip the job (guards below).
name: Deploy (Dev)
@@ -26,21 +18,18 @@ on:
push:
branches:
- dev
# Manual trigger for isolated end-to-end tests.
# Runs on the ref it is dispatched from.
workflow_dispatch:
# No `concurrency:` block: it triggers a MokoGIT Actions run-creation bug that
# silently drops deploys on rapid pushes to the branch. Do not re-add until the
# upstream bug is confirmed fixed.
# No `concurrency:` block: triggers a MokoGIT run-creation bug that drops deploys.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy-dev:
name: "Build & Deploy to Dev"
name: "Deploy to Dev"
runs-on: ubuntu-latest
if: ${{ vars.DEV_SSH_USERNAME != '' }}
steps:
- name: Checkout source
uses: actions/checkout@v4
@@ -62,75 +51,20 @@ jobs:
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
- name: Build and deploy to RC via SSH
- name: Set up mokocli (deploy logic)
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} \
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
set -e
echo 'SSH connected to Dev environment'
command -v php >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq php-cli; }
git clone --depth 1 "https://x-access-token:${MOKOGIT_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" "$RUNNER_TEMP/mokocli"
echo "MOKO_CLI=$RUNNER_TEMP/mokocli/cli" >> $GITHUB_ENV
if [ -z "$TAG" ]; then
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
exit 1
fi
HEALTH_FMT='{{.State.Health.Status}}'
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Pulling source...'
SOURCE_DIR='${{ vars.DEV_SOURCE_DIR }}'
if [ ! -d "$SOURCE_DIR/.git" ]; then
git clone -b dev https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR"
fi
cd "$SOURCE_DIR"
git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true
git fetch origin dev
git reset --hard origin/dev
echo "Building image: ${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG"
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" \
-f Dockerfile .
echo 'Pushing to registry...'
echo "$REGISTRY_TOKEN" | docker login ${{ vars.DEV_REGISTRY }} -u ${{ vars.DEV_REGISTRY_USER }} --password-stdin
docker push "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG"
echo 'Restarting Dev container...'
cd '${{ vars.DEV_COMPOSE_DIR }}'
# Drive the rc service image tag via its compose env-var (no sed on the shared
# file); remove any lingering fixed-name container first, then force-recreate.
docker rm -f '${{ vars.DEV_CONTAINER }}' 2>/dev/null || true
${{ vars.DEV_TAG_ENV }}="$TAG" docker compose -p '${{ vars.DEV_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.DEV_CONTAINER }}'
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format="$HEALTH_FMT" '${{ vars.DEV_CONTAINER }}' 2>/dev/null | grep -q healthy; then
echo 'Dev container healthy!'
exit 0
fi
echo "Waiting... (attempt $i/8)"
done
echo 'Health check failed'
docker logs '${{ vars.DEV_CONTAINER }}' --tail 20
exit 1
DEPLOY_EOF
- name: Verify Dev instance
continue-on-error: true
- name: Deploy (mokocli triggers the restricted deploy pattern)
run: |
sleep 5
ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} "curl -sf '${{ vars.DEV_HEALTH_URL }}'" && echo " Dev API healthy"
if [ -z "${{ vars.DEV_SSH_USERNAME }}" ]; then
echo "DEV_SSH_USERNAME unset — repo not onboarded to the restricted deploy pattern; skipping."
exit 0
fi
php "$MOKO_CLI/deploy.php" --tier dev --tag "${{ steps.config.outputs.tag }}" \
--ssh-host "${{ vars.DEV_SSH_HOST }}" --ssh-port "${{ vars.DEV_SSH_PORT }}" \
--ssh-user "${{ vars.DEV_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
+26 -92
View File
@@ -1,24 +1,16 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Build and deploy to the Prod environment on push to the main branch.
# Portable across Go server repos: ALL deployment config comes from repo
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
# Prod deploys on merge to main (dev -> rc -> main pipeline).
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
# BRIEF: Deploy to the Prod environment on push to the main branch. THIN TRIGGER —
# the deploy LOGIC lives in mokocli (cli/deploy.php) and server-side in the
# restricted per-repo deploy pattern (.vault system/deploy, runbook 16).
# This workflow validates + invokes only; it carries NO registry token and
# NO build script. A leaked DEPLOY_SSH_KEY can only redeploy this one repo.
# OWNER: Template-Go (canonical; syncs to each repo's .mokogit/workflows).
#
# Required repo VARIABLES (all tier-scoped so each environment is independent —
# a repo may host its rc/dev/prod tiers on different machines):
# PROD_SSH_HOST, PROD_SSH_PORT, PROD_SSH_USERNAME - SSH deploy target for the prod tier
# PROD_REGISTRY, PROD_REGISTRY_USER, PROD_IMAGE - container registry + login user + image
# PROD_CONTAINER - compose service/container to recreate
# PROD_COMPOSE_PROJECT - docker compose -p project name
# PROD_COMPOSE_DIR - dir containing docker-compose.yml on host
# PROD_SOURCE_DIR - build source checkout on host
# PROD_TAG_ENV - compose env-var name that pins the image tag
# PROD_HEALTH_URL - external URL to verify after deploy
# Required SECRETS (already configured org-wide; reused, not re-set):
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
# MOKOGIT_TOKEN - registry/API token (org secret)
# Required repo VARIABLES (tier-scoped): PROD_SSH_HOST, PROD_SSH_PORT, PROD_SSH_USERNAME.
# Required SECRET: DEPLOY_SSH_KEY (the deploy-<repo> private key).
# ONBOARDING: a repo joins the restricted deploy pattern when PROD_SSH_USERNAME is
# set to deploy-<repo>. Un-onboarded go repos skip the job (guards below).
name: Deploy (Prod)
@@ -26,21 +18,18 @@ on:
push:
branches:
- main
# Manual trigger for a prod re-deploy.
# Runs on the ref it is dispatched from (use main).
workflow_dispatch:
# No `concurrency:` block: it triggers a MokoGIT Actions run-creation bug that
# silently drops deploys on rapid pushes to the branch. Do not re-add until the
# upstream bug is confirmed fixed.
# No `concurrency:` block: triggers a MokoGIT run-creation bug that drops deploys.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy-prod:
name: "Build & Deploy to Prod"
name: "Deploy to Prod"
runs-on: ubuntu-latest
if: ${{ vars.PROD_SSH_USERNAME != '' }}
steps:
- name: Checkout source
uses: actions/checkout@v4
@@ -62,75 +51,20 @@ jobs:
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
- name: Build and deploy to RC via SSH
- name: Set up mokocli (deploy logic)
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
ssh -i ~/.ssh/deploy_key -p ${{ vars.PROD_SSH_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ vars.PROD_SSH_USERNAME }}@${{ vars.PROD_SSH_HOST }} \
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
set -e
echo 'SSH connected to Prod environment'
command -v php >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq php-cli; }
git clone --depth 1 "https://x-access-token:${MOKOGIT_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" "$RUNNER_TEMP/mokocli"
echo "MOKO_CLI=$RUNNER_TEMP/mokocli/cli" >> $GITHUB_ENV
if [ -z "$TAG" ]; then
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
exit 1
fi
HEALTH_FMT='{{.State.Health.Status}}'
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Pulling source...'
SOURCE_DIR='${{ vars.PROD_SOURCE_DIR }}'
if [ ! -d "$SOURCE_DIR/.git" ]; then
git clone -b main https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR"
fi
cd "$SOURCE_DIR"
git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true
git fetch origin main
git reset --hard origin/main
echo "Building image: ${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG"
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag "${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG" \
-f Dockerfile .
echo 'Pushing to registry...'
echo "$REGISTRY_TOKEN" | docker login ${{ vars.PROD_REGISTRY }} -u ${{ vars.PROD_REGISTRY_USER }} --password-stdin
docker push "${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG"
echo 'Restarting Prod container...'
cd '${{ vars.PROD_COMPOSE_DIR }}'
# Drive the rc service image tag via its compose env-var (no sed on the shared
# file); remove any lingering fixed-name container first, then force-recreate.
docker rm -f '${{ vars.PROD_CONTAINER }}' 2>/dev/null || true
${{ vars.PROD_TAG_ENV }}="$TAG" docker compose -p '${{ vars.PROD_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.PROD_CONTAINER }}'
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format="$HEALTH_FMT" '${{ vars.PROD_CONTAINER }}' 2>/dev/null | grep -q healthy; then
echo 'Prod container healthy!'
exit 0
fi
echo "Waiting... (attempt $i/8)"
done
echo 'Health check failed'
docker logs '${{ vars.PROD_CONTAINER }}' --tail 20
exit 1
DEPLOY_EOF
- name: Verify Prod instance
continue-on-error: true
- name: Deploy (mokocli triggers the restricted deploy pattern)
run: |
sleep 5
ssh -i ~/.ssh/deploy_key -p ${{ vars.PROD_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.PROD_SSH_USERNAME }}@${{ vars.PROD_SSH_HOST }} "curl -sf '${{ vars.PROD_HEALTH_URL }}'" && echo " Prod API healthy"
if [ -z "${{ vars.PROD_SSH_USERNAME }}" ]; then
echo "PROD_SSH_USERNAME unset — repo not onboarded to the restricted deploy pattern; skipping."
exit 0
fi
php "$MOKO_CLI/deploy.php" --tier prod --tag "${{ steps.config.outputs.tag }}" \
--ssh-host "${{ vars.PROD_SSH_HOST }}" --ssh-port "${{ vars.PROD_SSH_PORT }}" \
--ssh-user "${{ vars.PROD_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
+29 -94
View File
@@ -1,24 +1,17 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Build and deploy to the RC environment on push to the rc branch.
# Portable across Go server repos: ALL deployment config comes from repo
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
# The rc branch is created by promote-rc when a PR to main opens.
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
# BRIEF: Deploy to the RC environment on push to the rc branch. THIN TRIGGER —
# the deploy LOGIC lives in mokocli (cli/deploy.php) and server-side in the
# restricted per-repo deploy pattern (.vault system/deploy, runbook 16).
# This workflow validates + invokes only; it carries NO registry token and
# NO build script. A leaked DEPLOY_SSH_KEY can only redeploy this one repo.
# OWNER: Template-Go (canonical; syncs to each repo's .mokogit/workflows).
#
# Required repo VARIABLES (all tier-scoped so each environment is independent —
# a repo may host its rc/dev/prod tiers on different machines):
# RC_SSH_HOST, RC_SSH_PORT, RC_SSH_USERNAME - SSH deploy target for the rc tier
# RC_REGISTRY, RC_REGISTRY_USER, RC_IMAGE - container registry + login user + image
# RC_CONTAINER - compose service/container to recreate
# RC_COMPOSE_PROJECT - docker compose -p project name
# RC_COMPOSE_DIR - dir containing docker-compose.yml on host
# RC_SOURCE_DIR - build source checkout on host
# RC_TAG_ENV - compose env-var name that pins the image tag
# RC_HEALTH_URL - external URL to verify after deploy
# Required SECRETS (already configured org-wide; reused, not re-set):
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
# MOKOGIT_TOKEN - registry/API token (org secret)
# Required repo VARIABLES (tier-scoped): RC_SSH_HOST, RC_SSH_PORT, RC_SSH_USERNAME.
# Required SECRET: DEPLOY_SSH_KEY (the deploy-<repo> private key).
# ONBOARDING: a repo joins the restricted deploy pattern when RC_SSH_USERNAME is
# set to deploy-<repo>. Un-onboarded go repos skip the job (guards below), so this
# template is safe to cascade to every go repo.
name: Deploy (RC)
@@ -26,23 +19,19 @@ on:
push:
branches:
- rc
# Manual trigger for isolated end-to-end tests without a full RC promotion.
# Runs on the ref it is dispatched from; that ref must carry current source
# (>= the RC database migration version) or the rebuilt image will refuse the
# newer DB. Dispatch from `rc` once `rc` is current.
workflow_dispatch:
# No `concurrency:` block: it triggers a MokoGIT Actions run-creation bug that
# silently drops deploys on rapid pushes to the branch. Do not re-add until the
# upstream bug is confirmed fixed.
# No `concurrency:` block: triggers a MokoGIT run-creation bug that drops deploys.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy-rc:
name: "Build & Deploy to RC"
name: "Deploy to RC"
runs-on: ubuntu-latest
# Guard 1 (job-level): skip entirely unless onboarded.
if: ${{ vars.RC_SSH_USERNAME != '' }}
steps:
- name: Checkout source
uses: actions/checkout@v4
@@ -64,75 +53,21 @@ jobs:
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
- name: Build and deploy to RC via SSH
- name: Set up mokocli (deploy logic)
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
ssh -i ~/.ssh/deploy_key -p ${{ vars.RC_SSH_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ vars.RC_SSH_USERNAME }}@${{ vars.RC_SSH_HOST }} \
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
set -e
echo 'SSH connected to RC environment'
command -v php >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq php-cli; }
git clone --depth 1 "https://x-access-token:${MOKOGIT_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" "$RUNNER_TEMP/mokocli"
echo "MOKO_CLI=$RUNNER_TEMP/mokocli/cli" >> $GITHUB_ENV
if [ -z "$TAG" ]; then
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
exit 1
fi
HEALTH_FMT='{{.State.Health.Status}}'
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Pulling source...'
SOURCE_DIR='${{ vars.RC_SOURCE_DIR }}'
if [ ! -d "$SOURCE_DIR/.git" ]; then
git clone -b rc https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR"
fi
cd "$SOURCE_DIR"
git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true
git fetch origin rc
git reset --hard origin/rc
echo "Building image: ${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG"
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag "${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG" \
-f Dockerfile .
echo 'Pushing to registry...'
echo "$REGISTRY_TOKEN" | docker login ${{ vars.RC_REGISTRY }} -u ${{ vars.RC_REGISTRY_USER }} --password-stdin
docker push "${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG"
echo 'Restarting RC container...'
cd '${{ vars.RC_COMPOSE_DIR }}'
# Drive the rc service image tag via its compose env-var (no sed on the shared
# file); remove any lingering fixed-name container first, then force-recreate.
docker rm -f '${{ vars.RC_CONTAINER }}' 2>/dev/null || true
${{ vars.RC_TAG_ENV }}="$TAG" docker compose -p '${{ vars.RC_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.RC_CONTAINER }}'
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format="$HEALTH_FMT" '${{ vars.RC_CONTAINER }}' 2>/dev/null | grep -q healthy; then
echo 'RC container healthy!'
exit 0
fi
echo "Waiting... (attempt $i/8)"
done
echo 'Health check failed'
docker logs '${{ vars.RC_CONTAINER }}' --tail 20
exit 1
DEPLOY_EOF
- name: Verify RC instance
continue-on-error: true
- name: Deploy (mokocli triggers the restricted deploy pattern)
run: |
sleep 5
ssh -i ~/.ssh/deploy_key -p ${{ vars.RC_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.RC_SSH_USERNAME }}@${{ vars.RC_SSH_HOST }} "curl -sf '${{ vars.RC_HEALTH_URL }}'" && echo " RC API healthy"
# Guard 2 (step-level, reliable): no-op if not onboarded.
if [ -z "${{ vars.RC_SSH_USERNAME }}" ]; then
echo "RC_SSH_USERNAME unset — repo not onboarded to the restricted deploy pattern; skipping."
exit 0
fi
php "$MOKO_CLI/deploy.php" --tier rc --tag "${{ steps.config.outputs.tag }}" \
--ssh-host "${{ vars.RC_SSH_HOST }}" --ssh-port "${{ vars.RC_SSH_PORT }}" \
--ssh-user "${{ vars.RC_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key