177 lines
6.6 KiB
YAML
177 lines
6.6 KiB
YAML
name: Polaris CI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- ts/web/nextjs/polaris/**
|
|
- .gitea/workflows/polaris-ci.yml
|
|
|
|
jobs:
|
|
verify:
|
|
runs-on: polaris-ci
|
|
container: node:22.23.2-bookworm
|
|
timeout-minutes: 30
|
|
defaults:
|
|
run:
|
|
working-directory: ts/web/nextjs/polaris
|
|
steps:
|
|
- name: Check out repository
|
|
uses: https://gitea.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
|
|
- name: Install dependencies
|
|
run: corepack pnpm install --frozen-lockfile
|
|
|
|
- name: Lint
|
|
run: corepack pnpm lint
|
|
|
|
- name: Typecheck
|
|
run: corepack pnpm typecheck
|
|
|
|
- name: Test
|
|
run: corepack pnpm test
|
|
|
|
- name: Build standalone server
|
|
run: corepack pnpm build
|
|
|
|
- name: Prepare standalone output
|
|
run: |
|
|
test -f .next/standalone/server.js
|
|
cp -r public .next/standalone/
|
|
cp -r .next/static .next/standalone/.next/
|
|
|
|
- name: Smoke test standalone server
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
base_url="http://127.0.0.1:3100"
|
|
PORT=3100 HOSTNAME=127.0.0.1 node .next/standalone/server.js &
|
|
server_pid=$!
|
|
trap 'kill "$server_pid" 2>/dev/null || true' EXIT
|
|
|
|
ready=0
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS -o /dev/null "$base_url/"; then
|
|
ready=1
|
|
break
|
|
fi
|
|
kill -0 "$server_pid" 2>/dev/null || { echo "server process exited early" >&2; exit 1; }
|
|
sleep 1
|
|
done
|
|
test "$ready" -eq 1 || { echo "server not ready within 30s" >&2; exit 1; }
|
|
|
|
curl -fsS -o /dev/null "$base_url/categories/dev/"
|
|
curl -fsS -o /dev/null "$base_url/brand/logo.svg"
|
|
|
|
static_asset=$(curl -fsS "$base_url/" | grep -o '/_next/static/[^"]*' | sed -n '1p')
|
|
test -n "$static_asset" || { echo "homepage references no /_next/static asset" >&2; exit 1; }
|
|
curl -fsS -o /dev/null "$base_url$static_asset"
|
|
|
|
publish:
|
|
needs: verify
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
runs-on: polaris-ci
|
|
container: node:22.23.2-bookworm
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Check out repository
|
|
uses: https://gitea.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
|
|
# Daemon comes from the runner (gitea/runner:3-dind-rootless) via the
|
|
# job container's /var/run/docker.sock. Only the CLI is installed here;
|
|
# DOCKER_HOST stays unset and no host socket is mounted.
|
|
- name: Install Docker CLI
|
|
run: |
|
|
set -euo pipefail
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update
|
|
apt-get install -y --no-install-recommends docker.io
|
|
docker version
|
|
|
|
- name: Resolve image reference
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
repository="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is not set}"
|
|
owner="${repository%%/*}"
|
|
owner="${owner,,}"
|
|
if ! [[ "$owner" =~ ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$ ]]; then
|
|
echo "::error::owner segment '$owner' is not a valid registry path component" >&2
|
|
exit 1
|
|
fi
|
|
image="git.labmem.icu/${owner}/polaris:${GITHUB_SHA}"
|
|
echo "image reference: $image"
|
|
echo "IMAGE=$image" >> "$GITHUB_ENV"
|
|
|
|
- name: Build image
|
|
run: docker build -t "$IMAGE" ts/web/nextjs/polaris
|
|
|
|
- name: Smoke test image
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# Use the unique container ID instead of a fixed name so
|
|
# concurrent runs on a shared runner daemon cannot collide.
|
|
container="$(docker run -d "$IMAGE")"
|
|
cleanup() { docker rm -f "$container" >/dev/null 2>&1 || true; }
|
|
trap cleanup EXIT
|
|
|
|
probe() {
|
|
docker exec "$container" node -e "fetch('http://127.0.0.1:3000$1').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" >/dev/null 2>&1
|
|
}
|
|
|
|
ready=0
|
|
for _ in $(seq 1 30); do
|
|
if probe /; then
|
|
ready=1
|
|
break
|
|
fi
|
|
if [[ "$(docker inspect -f '{{.State.Running}}' "$container")" != "true" ]]; then
|
|
echo "container exited early, logs:" >&2
|
|
docker logs "$container" >&2 || true
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
test "$ready" -eq 1 || { echo "server not ready within 30s" >&2; docker logs "$container" >&2 || true; exit 1; }
|
|
|
|
probe /categories/dev/ || { echo "smoke test failed: /categories/dev/" >&2; exit 1; }
|
|
probe /brand/logo.svg || { echo "smoke test failed: /brand/logo.svg" >&2; exit 1; }
|
|
echo "smoke test passed for $IMAGE"
|
|
|
|
- name: Log in and push image
|
|
shell: bash
|
|
env:
|
|
POLARIS_REGISTRY_USER: ${{ secrets.POLARIS_REGISTRY_USER }}
|
|
POLARIS_REGISTRY_TOKEN: ${{ secrets.POLARIS_REGISTRY_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "${POLARIS_REGISTRY_USER}" || -z "${POLARIS_REGISTRY_TOKEN}" ]]; then
|
|
echo "::error::缺少 secrets.POLARIS_REGISTRY_USER 或 secrets.POLARIS_REGISTRY_TOKEN,无法登录 git.labmem.icu;请在仓库 Settings -> Actions -> Secrets 配置后重试" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Ephemeral docker config: credentials live only inside this step
|
|
# and are deleted when the step (and with it the job) exits.
|
|
docker_config_dir="$(mktemp -d)"
|
|
push_log_file="$(mktemp)"
|
|
trap 'rm -rf "$docker_config_dir" "$push_log_file"' EXIT
|
|
|
|
printf '%s' "$POLARIS_REGISTRY_TOKEN" | docker --config "$docker_config_dir" login git.labmem.icu --username "$POLARIS_REGISTRY_USER" --password-stdin >/dev/null
|
|
|
|
echo "pushing $IMAGE"
|
|
# tee keeps push output visible in the log; pipefail (already set
|
|
# via `set -euo pipefail`) fails the step when push itself fails.
|
|
docker --config "$docker_config_dir" push "$IMAGE" 2>&1 | tee "$push_log_file"
|
|
|
|
digest="$(sed -n 's/.*digest: \(sha256:[0-9a-f]\{64\}\).*/\1/p' "$push_log_file" | tail -n 1)"
|
|
if [[ ! "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
|
echo "::error::push 日志中未解析到合法 digest(期望 sha256:<64 位十六进制>),不输出部署引用;请在 registry 界面确认该镜像的实际 digest 后再手动部署" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "published image: $IMAGE"
|
|
echo "digest: $digest"
|
|
echo "manual deploy reference: docker pull ${IMAGE%%:*}@$digest"
|