ci: make release artifacts selectable

This commit is contained in:
2026-08-14 13:31:15 +08:00
parent 51e4c41ee2
commit 506dca898e
Vendored
+211 -78
View File
@@ -14,6 +14,34 @@ pipeline {
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20')) buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20'))
} }
parameters {
booleanParam(
name: 'RUN_TESTS',
defaultValue: true,
description: '运行前端构建检查和 .NET 全量测试'
)
booleanParam(
name: 'BUILD_FNOS',
defaultValue: true,
description: '构建 fnOS x86_64 FPK 安装包'
)
booleanParam(
name: 'UPLOAD_FNOS',
defaultValue: true,
description: '将本次构建的 fnOS 包上传到 OpenList(仅 BUILD_FNOS 生效)'
)
choice(
name: 'API_IMAGE_PLATFORMS',
choices: ['amd64+arm64', 'amd64', 'arm64', 'none'],
description: '选择需要构建并推送的 API Docker 镜像架构'
)
choice(
name: 'WEB_IMAGE_PLATFORMS',
choices: ['amd64+arm64', 'amd64', 'arm64', 'none'],
description: '选择需要构建并推送的 Web Docker 镜像架构'
)
}
environment { environment {
PATH = '/home/nanxunai/.local/bin:/usr/local/bin:/usr/bin:/bin' PATH = '/home/nanxunai/.local/bin:/usr/local/bin:/usr/bin:/bin'
REGISTRY_URL = 'reg.nxsir.cn' REGISTRY_URL = 'reg.nxsir.cn'
@@ -79,6 +107,13 @@ pipeline {
stage('Metadata And Preflight') { stage('Metadata And Preflight') {
steps { steps {
script { script {
if (!params.RUN_TESTS &&
!params.BUILD_FNOS &&
params.API_IMAGE_PLATFORMS == 'none' &&
params.WEB_IMAGE_PLATFORMS == 'none') {
error('Select at least one test or build output.')
}
env.APP_VERSION = sh( env.APP_VERSION = sh(
script: "sed -n 's/^version=//p' fnos/manifest | head -n 1", script: "sed -n 's/^version=//p' fnos/manifest | head -n 1",
returnStdout: true returnStdout: true
@@ -160,6 +195,9 @@ pipeline {
} }
stage('Restore And Test') { stage('Restore And Test') {
when {
expression { params.RUN_TESTS }
}
steps { steps {
sh ''' sh '''
set -euo pipefail set -euo pipefail
@@ -171,7 +209,22 @@ pipeline {
} }
} }
stage('Prepare fnOS Dependencies') {
when {
expression { params.BUILD_FNOS && !params.RUN_TESTS }
}
steps {
sh '''
set -euo pipefail
npm ci --prefix frontend
'''
}
}
stage('Build fnOS x64') { stage('Build fnOS x64') {
when {
expression { params.BUILD_FNOS }
}
steps { steps {
sh ''' sh '''
set -euo pipefail set -euo pipefail
@@ -184,6 +237,12 @@ pipeline {
} }
stage('Prepare Docker Builder') { stage('Prepare Docker Builder') {
when {
expression {
params.API_IMAGE_PLATFORMS != 'none' ||
params.WEB_IMAGE_PLATFORMS != 'none'
}
}
steps { steps {
withCredentials([usernamePassword( withCredentials([usernamePassword(
credentialsId: "${NODE_CREDENTIALS}", credentialsId: "${NODE_CREDENTIALS}",
@@ -192,10 +251,15 @@ pipeline {
)]) { )]) {
sh ''' sh '''
set -euo pipefail set -euo pipefail
if ! ./scripts/ci-docker.sh run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m 2>/dev/null | grep -q aarch64; then if [ "$API_IMAGE_PLATFORMS" = arm64 ] ||
./scripts/ci-docker.sh run --privileged --rm "$BINFMT_IMAGE" --install arm64 [ "$API_IMAGE_PLATFORMS" = amd64+arm64 ] ||
[ "$WEB_IMAGE_PLATFORMS" = arm64 ] ||
[ "$WEB_IMAGE_PLATFORMS" = amd64+arm64 ]; then
if ! ./scripts/ci-docker.sh run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m 2>/dev/null | grep -q aarch64; then
./scripts/ci-docker.sh run --privileged --rm "$BINFMT_IMAGE" --install arm64
fi
./scripts/ci-docker.sh run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m | grep -q aarch64
fi fi
./scripts/ci-docker.sh run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m | grep -q aarch64
if ./scripts/ci-docker.sh buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then if ./scripts/ci-docker.sh buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then
./scripts/ci-docker.sh buildx use "$BUILDER_NAME" ./scripts/ci-docker.sh buildx use "$BUILDER_NAME"
@@ -218,6 +282,12 @@ pipeline {
} }
stage('Prepare Harbor Authentication') { stage('Prepare Harbor Authentication') {
when {
expression {
params.API_IMAGE_PLATFORMS != 'none' ||
params.WEB_IMAGE_PLATFORMS != 'none'
}
}
steps { steps {
withCredentials([usernamePassword( withCredentials([usernamePassword(
credentialsId: "${HARBOR_CREDENTIALS}", credentialsId: "${HARBOR_CREDENTIALS}",
@@ -238,6 +308,12 @@ pipeline {
} }
stage('Build And Push amd64') { stage('Build And Push amd64') {
when {
expression {
params.API_IMAGE_PLATFORMS.contains('amd64') ||
params.WEB_IMAGE_PLATFORMS.contains('amd64')
}
}
steps { steps {
withCredentials([usernamePassword( withCredentials([usernamePassword(
credentialsId: "${NODE_CREDENTIALS}", credentialsId: "${NODE_CREDENTIALS}",
@@ -251,37 +327,47 @@ pipeline {
api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch" api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch" web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
./scripts/ci-docker.sh buildx build \ if [ "$API_IMAGE_PLATFORMS" = amd64 ] || [ "$API_IMAGE_PLATFORMS" = amd64+arm64 ]; then
--builder "$BUILDER_NAME" --platform "$platform" --network host \ ./scripts/ci-docker.sh buildx build \
--progress=plain --provenance=false \ --builder "$BUILDER_NAME" --platform "$platform" --network host \
--build-arg DOTNET_SDK_IMAGE="$DOTNET_SDK_IMAGE" \ --progress=plain --provenance=false \
--build-arg DOTNET_RUNTIME_IMAGE="$DOTNET_RUNTIME_IMAGE" \ --build-arg DOTNET_SDK_IMAGE="$DOTNET_SDK_IMAGE" \
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \ --build-arg DOTNET_RUNTIME_IMAGE="$DOTNET_RUNTIME_IMAGE" \
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \ --build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
--build-arg http_proxy="$HTTP_PROXY_URL" \ --build-arg NO_PROXY="$NO_PROXY_HOSTS" \
--build-arg https_proxy="$HTTP_PROXY_URL" \ --build-arg http_proxy="$HTTP_PROXY_URL" \
--build-arg no_proxy="$NO_PROXY_HOSTS" \ --build-arg https_proxy="$HTTP_PROXY_URL" \
-f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --push . --build-arg no_proxy="$NO_PROXY_HOSTS" \
./scripts/ci-docker.sh buildx build \ -f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --push .
--builder "$BUILDER_NAME" --platform "$platform" --network host \ fi
--progress=plain --provenance=false \ if [ "$WEB_IMAGE_PLATFORMS" = amd64 ] || [ "$WEB_IMAGE_PLATFORMS" = amd64+arm64 ]; then
--build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \ ./scripts/ci-docker.sh buildx build \
--build-arg NGINX_IMAGE="$NGINX_IMAGE" \ --builder "$BUILDER_NAME" --platform "$platform" --network host \
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \ --progress=plain --provenance=false \
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \ --build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \ --build-arg NGINX_IMAGE="$NGINX_IMAGE" \
--build-arg http_proxy="$HTTP_PROXY_URL" \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
--build-arg https_proxy="$HTTP_PROXY_URL" \ --build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
--build-arg no_proxy="$NO_PROXY_HOSTS" \ --build-arg NO_PROXY="$NO_PROXY_HOSTS" \
--build-arg VITE_API_BASE_URL=/api \ --build-arg http_proxy="$HTTP_PROXY_URL" \
-f frontend/Dockerfile -t "$web_ref" --push frontend --build-arg https_proxy="$HTTP_PROXY_URL" \
--build-arg no_proxy="$NO_PROXY_HOSTS" \
--build-arg VITE_API_BASE_URL=/api \
-f frontend/Dockerfile -t "$web_ref" --push frontend
fi
''' '''
} }
} }
} }
stage('Build And Push arm64') { stage('Build And Push arm64') {
when {
expression {
params.API_IMAGE_PLATFORMS.contains('arm64') ||
params.WEB_IMAGE_PLATFORMS.contains('arm64')
}
}
steps { steps {
withCredentials([usernamePassword( withCredentials([usernamePassword(
credentialsId: "${NODE_CREDENTIALS}", credentialsId: "${NODE_CREDENTIALS}",
@@ -295,37 +381,47 @@ pipeline {
api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch" api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch" web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
./scripts/ci-docker.sh buildx build \ if [ "$API_IMAGE_PLATFORMS" = arm64 ] || [ "$API_IMAGE_PLATFORMS" = amd64+arm64 ]; then
--builder "$BUILDER_NAME" --platform "$platform" --network host \ ./scripts/ci-docker.sh buildx build \
--progress=plain --provenance=false \ --builder "$BUILDER_NAME" --platform "$platform" --network host \
--build-arg DOTNET_SDK_IMAGE="$DOTNET_SDK_IMAGE" \ --progress=plain --provenance=false \
--build-arg DOTNET_RUNTIME_IMAGE="$DOTNET_RUNTIME_IMAGE" \ --build-arg DOTNET_SDK_IMAGE="$DOTNET_SDK_IMAGE" \
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \ --build-arg DOTNET_RUNTIME_IMAGE="$DOTNET_RUNTIME_IMAGE" \
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \ --build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
--build-arg http_proxy="$HTTP_PROXY_URL" \ --build-arg NO_PROXY="$NO_PROXY_HOSTS" \
--build-arg https_proxy="$HTTP_PROXY_URL" \ --build-arg http_proxy="$HTTP_PROXY_URL" \
--build-arg no_proxy="$NO_PROXY_HOSTS" \ --build-arg https_proxy="$HTTP_PROXY_URL" \
-f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --push . --build-arg no_proxy="$NO_PROXY_HOSTS" \
./scripts/ci-docker.sh buildx build \ -f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --push .
--builder "$BUILDER_NAME" --platform "$platform" --network host \ fi
--progress=plain --provenance=false \ if [ "$WEB_IMAGE_PLATFORMS" = arm64 ] || [ "$WEB_IMAGE_PLATFORMS" = amd64+arm64 ]; then
--build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \ ./scripts/ci-docker.sh buildx build \
--build-arg NGINX_IMAGE="$NGINX_IMAGE" \ --builder "$BUILDER_NAME" --platform "$platform" --network host \
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \ --progress=plain --provenance=false \
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \ --build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \ --build-arg NGINX_IMAGE="$NGINX_IMAGE" \
--build-arg http_proxy="$HTTP_PROXY_URL" \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
--build-arg https_proxy="$HTTP_PROXY_URL" \ --build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
--build-arg no_proxy="$NO_PROXY_HOSTS" \ --build-arg NO_PROXY="$NO_PROXY_HOSTS" \
--build-arg VITE_API_BASE_URL=/api \ --build-arg http_proxy="$HTTP_PROXY_URL" \
-f frontend/Dockerfile -t "$web_ref" --push frontend --build-arg https_proxy="$HTTP_PROXY_URL" \
--build-arg no_proxy="$NO_PROXY_HOSTS" \
--build-arg VITE_API_BASE_URL=/api \
-f frontend/Dockerfile -t "$web_ref" --push frontend
fi
''' '''
} }
} }
} }
stage('Publish Multi-arch Manifests') { stage('Publish Multi-arch Manifests') {
when {
expression {
params.API_IMAGE_PLATFORMS == 'amd64+arm64' ||
params.WEB_IMAGE_PLATFORMS == 'amd64+arm64'
}
}
steps { steps {
withCredentials([usernamePassword( withCredentials([usernamePassword(
credentialsId: "${NODE_CREDENTIALS}", credentialsId: "${NODE_CREDENTIALS}",
@@ -334,7 +430,8 @@ pipeline {
)]) { )]) {
sh ''' sh '''
set -euo pipefail set -euo pipefail
for repository in "$API_IMAGE_REPO" "$WEB_IMAGE_REPO"; do publish_manifest() {
repository=$1
immutable_ref="$repository:$IMMUTABLE_TAG" immutable_ref="$repository:$IMMUTABLE_TAG"
./scripts/ci-docker.sh buildx imagetools create \ ./scripts/ci-docker.sh buildx imagetools create \
--tag "$immutable_ref" \ --tag "$immutable_ref" \
@@ -343,20 +440,26 @@ pipeline {
manifest=$(./scripts/ci-docker.sh buildx imagetools inspect --raw "$immutable_ref") manifest=$(./scripts/ci-docker.sh buildx imagetools inspect --raw "$immutable_ref")
printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"amd64"' printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"amd64"'
printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"arm64"' printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"arm64"'
done ./scripts/ci-docker.sh buildx imagetools create \
--tag "$repository:$APP_VERSION" --tag "$repository:latest" \
"$immutable_ref"
}
./scripts/ci-docker.sh buildx imagetools create \ if [ "$API_IMAGE_PLATFORMS" = amd64+arm64 ]; then
--tag "$API_IMAGE_REPO:$APP_VERSION" --tag "$API_IMAGE_REPO:latest" \ publish_manifest "$API_IMAGE_REPO"
"$API_IMAGE_REPO:$IMMUTABLE_TAG" fi
./scripts/ci-docker.sh buildx imagetools create \ if [ "$WEB_IMAGE_PLATFORMS" = amd64+arm64 ]; then
--tag "$WEB_IMAGE_REPO:$APP_VERSION" --tag "$WEB_IMAGE_REPO:latest" \ publish_manifest "$WEB_IMAGE_REPO"
"$WEB_IMAGE_REPO:$IMMUTABLE_TAG" fi
''' '''
} }
} }
} }
stage('Upload fnOS Artifact') { stage('Upload fnOS Artifact') {
when {
expression { params.BUILD_FNOS && params.UPLOAD_FNOS }
}
steps { steps {
withCredentials([usernamePassword( withCredentials([usernamePassword(
credentialsId: "${OPENLIST_CREDENTIALS}", credentialsId: "${OPENLIST_CREDENTIALS}",
@@ -374,29 +477,59 @@ pipeline {
stage('Build Summary') { stage('Build Summary') {
steps { steps {
sh ''' script {
set -euo pipefail def summary = [
printf '%s\n' \ 'Commit: ' + sh(script: 'git rev-parse HEAD', returnStdout: true).trim(),
"Commit: $(git rev-parse HEAD)" \ 'Product version: ' + env.APP_VERSION,
"Product version: $APP_VERSION" \ 'Tests: ' + (params.RUN_TESTS ? 'executed' : 'skipped')
"fnOS package version: $FNOS_VERSION" \ ]
"fnOS: $FPK_BASENAME" \ if (params.BUILD_FNOS) {
"Checksum: $(awk '{print $1}' "$FPK_PATH.sha256")" \ def checksum = sh(
"API amd64: $API_IMAGE_REPO:$IMMUTABLE_TAG-amd64" \ script: "cut -d ' ' -f 1 '" + env.FPK_PATH + ".sha256'",
"API arm64: $API_IMAGE_REPO:$IMMUTABLE_TAG-arm64" \ returnStdout: true
"API multi-arch: $API_IMAGE_REPO:$IMMUTABLE_TAG" \ ).trim()
"Web amd64: $WEB_IMAGE_REPO:$IMMUTABLE_TAG-amd64" \ summary.addAll([
"Web arm64: $WEB_IMAGE_REPO:$IMMUTABLE_TAG-arm64" \ 'fnOS package version: ' + env.FNOS_VERSION,
"Web multi-arch: $WEB_IMAGE_REPO:$IMMUTABLE_TAG" \ 'fnOS: ' + env.FPK_BASENAME,
"OpenList: $OPENLIST_BASE_URL$OPENLIST_REMOTE_DIR/$FPK_BASENAME" 'Checksum: ' + checksum
''' ])
if (params.UPLOAD_FNOS) {
summary << 'OpenList: ' + env.OPENLIST_BASE_URL + env.OPENLIST_REMOTE_DIR + '/' + env.FPK_BASENAME
}
}
if (params.API_IMAGE_PLATFORMS != 'none') {
summary << 'API platforms: ' + params.API_IMAGE_PLATFORMS
if (params.API_IMAGE_PLATFORMS.contains('amd64')) {
summary << 'API amd64: ' + env.API_IMAGE_REPO + ':' + env.IMMUTABLE_TAG + '-amd64'
}
if (params.API_IMAGE_PLATFORMS.contains('arm64')) {
summary << 'API arm64: ' + env.API_IMAGE_REPO + ':' + env.IMMUTABLE_TAG + '-arm64'
}
if (params.API_IMAGE_PLATFORMS == 'amd64+arm64') {
summary << 'API multi-arch: ' + env.API_IMAGE_REPO + ':' + env.IMMUTABLE_TAG
}
}
if (params.WEB_IMAGE_PLATFORMS != 'none') {
summary << 'Web platforms: ' + params.WEB_IMAGE_PLATFORMS
if (params.WEB_IMAGE_PLATFORMS.contains('amd64')) {
summary << 'Web amd64: ' + env.WEB_IMAGE_REPO + ':' + env.IMMUTABLE_TAG + '-amd64'
}
if (params.WEB_IMAGE_PLATFORMS.contains('arm64')) {
summary << 'Web arm64: ' + env.WEB_IMAGE_REPO + ':' + env.IMMUTABLE_TAG + '-arm64'
}
if (params.WEB_IMAGE_PLATFORMS == 'amd64+arm64') {
summary << 'Web multi-arch: ' + env.WEB_IMAGE_REPO + ':' + env.IMMUTABLE_TAG
}
}
echo summary.join('\n')
}
} }
} }
} }
post { post {
success { success {
echo "LiveRecorder ${env.IMMUTABLE_TAG} published successfully." echo "LiveRecorder ${env.IMMUTABLE_TAG} selected pipeline completed successfully."
} }
failure { failure {
echo 'LiveRecorder pipeline failed; stable Docker tags were only updated if all architecture builds succeeded.' echo 'LiveRecorder pipeline failed; stable Docker tags were only updated if all architecture builds succeeded.'