宿主 08-30 治理后为非 root gitea-runner,rm 容器 root 属主缓存文件必 EACCES (Seabed run 917 两 attempt 测试全绿、清理步必死)。复用 job 已拉的 node-image find -mindepth 1 -delete + 宿主 rmdir 自建目录。v3 不动;合并后打 v4, per-run 调用方(Seabed)升 @v4,fixed 调用方(Reef)可留 @v3。
162 lines
7.0 KiB
YAML
162 lines
7.0 KiB
YAML
# Reusable quality gate for est CI (node toolchain in digest-pinned helper image).
|
|
#
|
|
# P1-7 verification status (2026-08-27, Gitea 1.27.2):
|
|
# - reusable workflow calls verified working (1.27.2 probe + this workflow's
|
|
# selftest caller: .gitea/workflows/selftest.yml)
|
|
# - checkout via mu-ref/actions-checkout@v4 (JS action) + explicit
|
|
# HEAD==SHA postcondition: tn has a host node runtime (v22, installed
|
|
# 2026-08-27). v3 restores the P1-10 JS path — the 2026-08-27 12:07
|
|
# consolidation had based this repo on the P1-7 host-clone fallback
|
|
# lineage and dropped the completed migration (est/Est-Infra@57cc9f7);
|
|
# the action repo must stay PUBLIC (act clones it anonymously)
|
|
# - no actions/cache layer; the persistent /data/cache/ci directory is
|
|
# the primary (and only) cache layer
|
|
#
|
|
# Conventions proven on the business-repo pipelines:
|
|
# - runs-on: build-docker (host job, NON-ROOT user gitea-runner) + docker run for the toolchain
|
|
# - CA: GIT_SSL_CAINFO (host git) + SSL_CERT_FILE/NODE_EXTRA_CA_CERTS (container;
|
|
# helper images have their apt/apk sources baked in, so replacing the public
|
|
# trust store is safe here — see playbook pitfall 13)
|
|
# - workspace mounted at ${inputs.workspace} (legacy CNB scripts expect /workspace)
|
|
# - cache dirs: 0777 on the DIRECTORY ONLY; never chmod -R (selection files stay 0600)
|
|
|
|
name: reusable-node-quality
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
node-image:
|
|
required: true
|
|
type: string
|
|
description: "Digest-pinned est helper image, e.g. est/ci-node-bookworm@sha256:78c01b0be47ebc60c0fa940110d66c60ca4e57f0fa57178bb88788e4ea131b61"
|
|
workspace:
|
|
required: false
|
|
type: string
|
|
default: /workspace
|
|
description: "Mount point for the workspace inside the helper container"
|
|
install-command:
|
|
required: true
|
|
type: string
|
|
description: "Shell command executed inside the helper container (frozen lockfile install)"
|
|
quality-command:
|
|
required: true
|
|
type: string
|
|
description: "Shell command executed inside the helper container (lint/typecheck/test/build)"
|
|
cache-dir:
|
|
required: false
|
|
type: string
|
|
default: /data/cache/ci/quality
|
|
description: "Host-side cache dir (pnpm store, tools, browsers)"
|
|
cache-mode:
|
|
required: false
|
|
type: string
|
|
default: fixed
|
|
description: "fixed: one shared persistent dir. per-run: fresh mktemp child per run + always() cleanup (for scripts with from-zero guards on commit-keyed tool caches)"
|
|
mount-docker-socket:
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
description: "Non-empty (e.g. 'yes') mounts /var/run/docker.sock into the toolchain containers (boolean inputs are NOT passed reliably through workflow_call with:)"
|
|
commit-env-name:
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
description: "Env var name that receives the triggering commit SHA inside the containers (e.g. EST_GIT_COMMIT); empty disables"
|
|
extra-env:
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
description: "Multiline KEY=VALUE extra env for the toolchain containers (values must not contain spaces; caller-side expressions are NOT evaluated)"
|
|
secrets:
|
|
REGISTRY_PASSWORD:
|
|
required: false
|
|
|
|
jobs:
|
|
quality:
|
|
runs-on: build-docker
|
|
steps:
|
|
- name: checkout (mu-ref/actions-checkout; tn host node runs JS actions)
|
|
uses: https://git.moneywood.site/mu-ref/actions-checkout@v4
|
|
env:
|
|
GIT_SSL_CAINFO: /usr/local/share/ca-certificates/caddy-root-ca.crt
|
|
|
|
- name: verify HEAD equals triggering commit
|
|
run: |
|
|
set -eu
|
|
test "$(git rev-parse HEAD)" = "${GITHUB_SHA}"
|
|
|
|
- name: authenticate to gitea registry (private helper image pulls)
|
|
env:
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: |
|
|
set -eu
|
|
if [ -n "${REGISTRY_PASSWORD:-}" ]; then
|
|
printf '%s' "${REGISTRY_PASSWORD}" \
|
|
| docker login git.moneywood.site -u "${{ github.actor }}" --password-stdin
|
|
fi
|
|
|
|
- name: prepare cache dir (dirs 0777, files untouched)
|
|
run: |
|
|
set -eu
|
|
cache_root="${{ inputs.cache-dir }}"
|
|
mkdir -p "$cache_root"
|
|
chmod 0777 "$cache_root"
|
|
if [ "${{ inputs.cache-mode }}" = "per-run" ]; then
|
|
cache_dir="$(mktemp -d "$cache_root/run-XXXXXXXX")"
|
|
else
|
|
cache_dir="$cache_root"
|
|
fi
|
|
chmod 0777 "$cache_dir"
|
|
find "$cache_dir" -mindepth 1 -maxdepth 1 -type d -exec chmod 0777 {} + 2>/dev/null || true
|
|
printf 'TOOL_CACHE_DIR=%s\n' "$cache_dir" >>"$GITHUB_ENV"
|
|
|
|
# install and quality run in ONE container: toolchains installed by the
|
|
# install command (global pnpm, local bins) must still be on PATH for the
|
|
# quality command — two `docker run --rm` invocations would not share
|
|
# anything but the workspace and /cache mounts.
|
|
- name: install and quality (containerized toolchain)
|
|
env:
|
|
COMMIT_ENV_NAME: ${{ inputs.commit-env-name }}
|
|
run: |
|
|
set -eu
|
|
mount_args=""
|
|
if [ -n "${{ inputs.mount-docker-socket }}" ]; then
|
|
mount_args="-v /var/run/docker.sock:/var/run/docker.sock"
|
|
fi
|
|
env_args=""
|
|
if [ -n "${COMMIT_ENV_NAME}" ]; then
|
|
env_args="${env_args} -e ${COMMIT_ENV_NAME}=${GITHUB_SHA}"
|
|
fi
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] && env_args="${env_args} -e $line"
|
|
done <<EOF
|
|
${{ inputs.extra-env }}
|
|
EOF
|
|
# shellcheck disable=SC2086
|
|
docker run --rm --add-host npm.cache.est:172.17.0.1 \
|
|
$mount_args \
|
|
-v "$PWD:${{ inputs.workspace }}" -w "${{ inputs.workspace }}" \
|
|
-v "${TOOL_CACHE_DIR}:/cache" -e TMPDIR=/cache \
|
|
-v /usr/local/share/ca-certificates/est-bundle.crt:/ca/est-bundle.crt:ro \
|
|
-e SSL_CERT_FILE=/ca/est-bundle.crt \
|
|
-e NODE_EXTRA_CA_CERTS=/ca/est-bundle.crt \
|
|
$env_args \
|
|
"${{ inputs.node-image }}" sh -euxc '${{ inputs.install-command }} && ${{ inputs.quality-command }}'
|
|
|
|
# PLAT-014: the helper container writes root-owned files (tsx workers,
|
|
# playwright, compile caches) into the per-run cache mount; the host job
|
|
# runs as non-root gitea-runner since the 2026-08-30 runner governance,
|
|
# so a host-side rm -rf fails with EACCES and reds the job (playbook #18
|
|
# family). Clean as container root using the image this job already
|
|
# pulled, then rmdir the host-created directory itself.
|
|
- name: cleanup per-run cache dir (container root)
|
|
if: always() && inputs.cache-mode == 'per-run'
|
|
run: |
|
|
set -eu
|
|
[ -n "${TOOL_CACHE_DIR:-}" ] || exit 0
|
|
docker run --rm \
|
|
-v "${TOOL_CACHE_DIR}":/cleanup-root \
|
|
"${{ inputs.node-image }}" \
|
|
find /cleanup-root -mindepth 1 -delete
|
|
rmdir "${TOOL_CACHE_DIR}"
|