ci: add Jenkins multi-architecture release pipeline
This commit is contained in:
Vendored
+282
-266
@@ -1,325 +1,341 @@
|
||||
pipeline {
|
||||
agent { label '\u6784\u5efa\u673a1' }
|
||||
agent { label '构建机1' }
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
skipDefaultCheckout(true)
|
||||
timeout(time: 180, unit: 'MINUTES')
|
||||
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20'))
|
||||
}
|
||||
|
||||
environment {
|
||||
REGISTRY_URL = 'reg.nxsir.cn'
|
||||
API_IMAGE_NAME = 'live_recorder/app-api'
|
||||
WEB_IMAGE_NAME = 'live_recorder/app-web'
|
||||
IMAGE_TAG = "${env.BUILD_ID}"
|
||||
DOCKER_CREDS = 'harbor_key'
|
||||
TARGET_PLATFORMS = 'linux/amd64,linux/arm64'
|
||||
BUILDER_NAME = 'liverecorder-buildx'
|
||||
WEB_NODE_IMAGE = 'docker.m.daocloud.io/library/node:22-alpine'
|
||||
WEB_NGINX_IMAGE = 'docker.m.daocloud.io/library/nginx:1.27-alpine'
|
||||
HTTP_PROXY_URL = 'http://192.168.5.200:7890'
|
||||
// Tsinghua/daocloud/mcr hosts are reachable directly from the builder — routing them
|
||||
// through the 7890 proxy caused intermittent '502 Bad Gateway' during apt-get install.
|
||||
NO_PROXY_HOSTS = '127.0.0.1,localhost,reg.nxsir.cn,gitea.nxsir.cn,mirrors.tuna.tsinghua.edu.cn,.tsinghua.edu.cn,mcr.microsoft.com,docker.m.daocloud.io'
|
||||
REGISTRY_URL = 'reg.nxsir.cn'
|
||||
API_IMAGE_REPO = 'reg.nxsir.cn/live_recorder/app-api'
|
||||
WEB_IMAGE_REPO = 'reg.nxsir.cn/live_recorder/app-web'
|
||||
HARBOR_CREDENTIALS = 'harbor_key'
|
||||
OPENLIST_CREDENTIALS = 'openlist_key'
|
||||
OPENLIST_BASE_URL = 'https://openlist.nxsir.cn'
|
||||
OPENLIST_REMOTE_DIR = '/yidongpan/构建产物/liverecorder'
|
||||
|
||||
GIT_REPO_URL = 'https://gitea.nxsir.cn/nanxun/live_recorder.git'
|
||||
GIT_BRANCH = 'main'
|
||||
GIT_CREDS = ''
|
||||
FNPACK_BIN = '/home/nanxunai/.local/bin/fnpack'
|
||||
FNPACK_SHA256 = '54b97fa7b70968c4d05c79840f5daeff508957d0bb2062fdb0376d00d9615c93'
|
||||
DOTNET = '/home/nanxunai/.local/bin/dotnet'
|
||||
|
||||
API_IMAGE_TAGGED = "${REGISTRY_URL}/${API_IMAGE_NAME}:${IMAGE_TAG}"
|
||||
API_IMAGE_LATEST = "${REGISTRY_URL}/${API_IMAGE_NAME}:latest"
|
||||
WEB_IMAGE_TAGGED = "${REGISTRY_URL}/${WEB_IMAGE_NAME}:${IMAGE_TAG}"
|
||||
WEB_IMAGE_LATEST = "${REGISTRY_URL}/${WEB_IMAGE_NAME}:latest"
|
||||
API_CACHE_IMAGE = "${REGISTRY_URL}/${API_IMAGE_NAME}:buildcache"
|
||||
WEB_CACHE_IMAGE = "${REGISTRY_URL}/${WEB_IMAGE_NAME}:buildcache"
|
||||
BUILDER_NAME = "liverecorder-${BUILD_NUMBER}"
|
||||
BUILDKIT_IMAGE = 'docker.m.daocloud.io/moby/buildkit:buildx-stable-1'
|
||||
BINFMT_IMAGE = 'docker.m.daocloud.io/tonistiigi/binfmt:latest'
|
||||
ARM_TEST_IMAGE = 'docker.m.daocloud.io/library/alpine:latest'
|
||||
NODE_BUILD_IMAGE = 'docker.m.daocloud.io/library/node:22-alpine'
|
||||
NGINX_IMAGE = 'docker.m.daocloud.io/library/nginx:1.27-alpine'
|
||||
|
||||
HTTP_PROXY_URL = 'http://192.168.5.200:7890'
|
||||
NO_PROXY_HOSTS = '127.0.0.1,localhost,reg.nxsir.cn,gitea.nxsir.cn,openlist.nxsir.cn,mirrors.tuna.tsinghua.edu.cn,.tsinghua.edu.cn,mcr.microsoft.com,docker.m.daocloud.io'
|
||||
|
||||
CI_ROOT = "${WORKSPACE}/.ci"
|
||||
DOCKER_CONFIG = "${WORKSPACE}/.ci/docker"
|
||||
NUGET_PACKAGES = "${WORKSPACE}/.ci/nuget-packages"
|
||||
NUGET_HTTP_CACHE_PATH = "${WORKSPACE}/.ci/nuget-http"
|
||||
NUGET_PLUGINS_CACHE_PATH = "${WORKSPACE}/.ci/nuget-plugins"
|
||||
DOTNET_CLI_HOME = "${WORKSPACE}/.ci/dotnet-home"
|
||||
NPM_CONFIG_CACHE = "${WORKSPACE}/.ci/npm-cache"
|
||||
LIVERECORDER_NUGET_FEED = "${WORKSPACE}/.ci/nuget-feed"
|
||||
LIVERECORDER_BUILD_TMPDIR = "${WORKSPACE}/.ci/fnos-tmp"
|
||||
LIVERECORDER_VERIFY_TMPDIR = "${WORKSPACE}/.ci/fnos-verify"
|
||||
LIVERECORDER_SKIP_NPM_CI = '1'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
script {
|
||||
def userRemoteConfig = [url: env.GIT_REPO_URL]
|
||||
if (env.GIT_CREDS?.trim()) {
|
||||
userRemoteConfig.credentialsId = env.GIT_CREDS.trim()
|
||||
}
|
||||
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: "*/${env.GIT_BRANCH}"]],
|
||||
userRemoteConfigs: [userRemoteConfig]
|
||||
])
|
||||
}
|
||||
deleteDir()
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Prepare Buildx') {
|
||||
stage('Metadata And Preflight') {
|
||||
steps {
|
||||
sh """
|
||||
set -e
|
||||
script {
|
||||
env.APP_VERSION = sh(
|
||||
script: "sed -n 's/^version=//p' fnos/manifest | head -n 1",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
env.SHORT_SHA = sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim()
|
||||
if (!env.APP_VERSION || !env.SHORT_SHA) {
|
||||
error('Unable to resolve fnOS version or Git commit.')
|
||||
}
|
||||
env.IMMUTABLE_TAG = "${env.APP_VERSION}-b${env.BUILD_NUMBER}-${env.SHORT_SHA}"
|
||||
env.FPK_BASENAME = "liverecorder-${env.IMMUTABLE_TAG}-x86_64.fpk"
|
||||
env.FPK_PATH = "${env.WORKSPACE}/artifacts/ci/${env.FPK_BASENAME}"
|
||||
currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.APP_VERSION} ${env.SHORT_SHA}"
|
||||
currentBuild.description = "${env.IMMUTABLE_TAG}"
|
||||
}
|
||||
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
test "$(uname -m)" = x86_64
|
||||
for command_name in git sudo docker curl python3 npm node sha256sum stat tar; do
|
||||
command -v "$command_name" >/dev/null
|
||||
done
|
||||
test -x "$DOTNET"
|
||||
test -x "$FNPACK_BIN"
|
||||
printf '%s %s\n' "$FNPACK_SHA256" "$FNPACK_BIN" | sha256sum --check --status
|
||||
sudo -n true
|
||||
sudo docker version >/dev/null
|
||||
sudo docker buildx version
|
||||
|
||||
# Register ARM64 binfmt. Docker Hub is unreachable from this builder,
|
||||
# so try daocloud mirror first; fall back to apt-installed qemu-user-static.
|
||||
if [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ] && [ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64-static ]; then
|
||||
echo 'Registering ARM64 emulation (binfmt not present)...'
|
||||
if ! sudo docker run --privileged --rm docker.m.daocloud.io/tonistiigi/binfmt --install arm64 2>/dev/null; then
|
||||
echo 'daocloud mirror failed, falling back to qemu-user-static via apt...'
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq qemu-user-static
|
||||
fi
|
||||
else
|
||||
echo 'ARM64 binfmt already registered, skipping.'
|
||||
available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }')
|
||||
if [ "$available_kb" -lt 2097152 ]; then
|
||||
echo "At least 2 GiB free workspace disk is required; available KiB: $available_kb" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify emulation works before proceeding
|
||||
sudo docker run --rm --platform linux/arm64 docker.m.daocloud.io/library/alpine:latest uname -m
|
||||
|
||||
# Use the default docker builder (docker driver) — not a separate
|
||||
# docker-container instance — so we don't need to pull moby/buildkit
|
||||
# from unreachable Docker Hub.
|
||||
sudo docker buildx use default 2>/dev/null || true
|
||||
sudo docker buildx inspect --bootstrap >/dev/null
|
||||
"""
|
||||
mkdir -p \
|
||||
"$DOCKER_CONFIG" \
|
||||
"$NUGET_PACKAGES" \
|
||||
"$NUGET_HTTP_CACHE_PATH" \
|
||||
"$NUGET_PLUGINS_CACHE_PATH" \
|
||||
"$DOTNET_CLI_HOME" \
|
||||
"$NPM_CONFIG_CACHE" \
|
||||
"$LIVERECORDER_NUGET_FEED" \
|
||||
"$LIVERECORDER_BUILD_TMPDIR" \
|
||||
"$LIVERECORDER_VERIFY_TMPDIR" \
|
||||
"$(dirname -- "$FPK_PATH")"
|
||||
chmod 700 "$DOCKER_CONFIG"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build And Push API Image') {
|
||||
stage('Restore And Test') {
|
||||
steps {
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: "${DOCKER_CREDS}",
|
||||
usernameVariable: 'DOCKER_USERNAME',
|
||||
passwordVariable: 'DOCKER_PASSWORD'
|
||||
)
|
||||
]) {
|
||||
sh """
|
||||
set -e
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
npm ci --prefix frontend
|
||||
npm run build --prefix frontend
|
||||
"$DOTNET" restore LiveRecorder.sln --disable-parallel
|
||||
"$DOTNET" test LiveRecorder.sln -c Release --no-restore --logger 'console;verbosity=normal' /maxcpucount:1
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
run_with_heartbeat() {
|
||||
log_file="\$1"
|
||||
build_label="\$2"
|
||||
shift 2
|
||||
rm -f "\$log_file"
|
||||
: > "\$log_file"
|
||||
"\$@" >"\$log_file" 2>&1 &
|
||||
cmd_pid=\$!
|
||||
cmd_status=0
|
||||
last_size=0
|
||||
while kill -0 "\$cmd_pid" >/dev/null 2>&1; do
|
||||
echo "[heartbeat] \${build_label} still running at \$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
if [ -f "\$log_file" ]; then
|
||||
current_size=\$(stat -c%s "\$log_file" 2>/dev/null || echo 0)
|
||||
if [ "\$current_size" -gt "\$last_size" ]; then
|
||||
start_byte=\$((last_size + 1))
|
||||
tail -c +"\$start_byte" "\$log_file" || true
|
||||
last_size=\$current_size
|
||||
fi
|
||||
fi
|
||||
sleep 20
|
||||
done
|
||||
wait "\$cmd_pid" || cmd_status=\$?
|
||||
if [ -f "\$log_file" ]; then
|
||||
current_size=\$(stat -c%s "\$log_file" 2>/dev/null || echo 0)
|
||||
if [ "\$current_size" -gt "\$last_size" ]; then
|
||||
start_byte=\$((last_size + 1))
|
||||
tail -c +"\$start_byte" "\$log_file" || true
|
||||
fi
|
||||
fi
|
||||
if [ "\$cmd_status" -ne 0 ]; then
|
||||
echo "[heartbeat] \${build_label} failed with exit code \$cmd_status"
|
||||
fi
|
||||
return "\$cmd_status"
|
||||
}
|
||||
stage('Build fnOS x64') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
FNPACK="$FNPACK_BIN" ./scripts/build-fnos-package.sh "$FPK_PATH"
|
||||
./scripts/verify-fnos-package.sh "$FPK_PATH"
|
||||
test -s "$FPK_PATH.sha256"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
# ── Build each platform separately, push with docker CLI,
|
||||
# then assemble a multi-arch manifest.
|
||||
# buildx --push does not forward auth reliably on this
|
||||
# builder; per-platform --load + docker push + manifest
|
||||
# bypasses that entirely.
|
||||
for PLATFORM in linux/amd64 linux/arm64; do
|
||||
ARCH_TAG="\${PLATFORM##*/}" # amd64 / arm64
|
||||
PLAT_REF="${API_IMAGE_TAGGED}-\${ARCH_TAG}"
|
||||
PLAT_LATEST="${API_IMAGE_LATEST}-\${ARCH_TAG}"
|
||||
stage('Prepare Docker Builder') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
if ! sudo docker run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m 2>/dev/null | grep -q aarch64; then
|
||||
sudo docker run --privileged --rm "$BINFMT_IMAGE" --install arm64
|
||||
fi
|
||||
sudo docker run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m | grep -q aarch64
|
||||
|
||||
echo ''
|
||||
echo "=== Building API for \$PLATFORM -> \$PLAT_REF ==="
|
||||
run_with_heartbeat /tmp/live-recorder-api-\${ARCH_TAG}.log "API \${ARCH_TAG} build" \
|
||||
sudo docker buildx build \
|
||||
--platform "\$PLATFORM" \
|
||||
--network host \
|
||||
--progress=plain \
|
||||
--provenance=false \
|
||||
--no-cache \
|
||||
--build-arg HTTP_PROXY=${HTTP_PROXY_URL} \
|
||||
--build-arg HTTPS_PROXY=${HTTP_PROXY_URL} \
|
||||
--build-arg NO_PROXY=${NO_PROXY_HOSTS} \
|
||||
--build-arg http_proxy=${HTTP_PROXY_URL} \
|
||||
--build-arg https_proxy=${HTTP_PROXY_URL} \
|
||||
--build-arg no_proxy=${NO_PROXY_HOSTS} \
|
||||
-f src/LiveRecorder.WebApi/Dockerfile \
|
||||
-t "\$PLAT_REF" \
|
||||
-t "\$PLAT_LATEST" \
|
||||
--load \
|
||||
.
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx create \
|
||||
--name "$BUILDER_NAME" \
|
||||
--driver docker-container \
|
||||
--driver-opt "image=$BUILDKIT_IMAGE" \
|
||||
--driver-opt network=host \
|
||||
--use
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx inspect "$BUILDER_NAME" --bootstrap
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Pushing \$PLAT_REF ==="
|
||||
# Write Harbor auth directly to config.json (bypass docker
|
||||
# login entirely) — then push with fresh baked-in credentials.
|
||||
AUTH_B64=\$(printf '%s:%s' "\$DOCKER_USERNAME" "\$DOCKER_PASSWORD" | base64 -w0)
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}' "${REGISTRY_URL}" "\$AUTH_B64" | sudo tee /root/.docker/config.json >/dev/null
|
||||
sudo docker push "\$PLAT_REF" 2>&1 | tail -5
|
||||
sudo docker push "\$PLAT_LATEST" 2>&1 | tail -5
|
||||
|
||||
# Free disk space before the next platform build
|
||||
sudo docker rmi "\$PLAT_REF" "\$PLAT_LATEST" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo ''
|
||||
echo "=== Creating multi-arch manifest: ${API_IMAGE_TAGGED} ==="
|
||||
AUTH_B64=\$(printf '%s:%s' "\$DOCKER_USERNAME" "\$DOCKER_PASSWORD" | base64 -w0) && printf '{"auths":{"%s":{"auth":"%s"}}}' "${REGISTRY_URL}" "\$AUTH_B64" | sudo tee /root/.docker/config.json >/dev/null
|
||||
sudo docker manifest create "${API_IMAGE_TAGGED}" \
|
||||
"${API_IMAGE_TAGGED}-amd64" \
|
||||
"${API_IMAGE_TAGGED}-arm64"
|
||||
sudo docker manifest create --amend "${API_IMAGE_LATEST}" \
|
||||
"${API_IMAGE_LATEST}-amd64" \
|
||||
"${API_IMAGE_LATEST}-arm64"
|
||||
|
||||
echo "=== Pushing manifests ==="
|
||||
sudo docker manifest push "${API_IMAGE_TAGGED}" 2>&1 | tail -3
|
||||
sudo docker manifest push "${API_IMAGE_LATEST}" 2>&1 | tail -3
|
||||
|
||||
# (Optionally clean up per-platform tags from registry, but harmless to leave them)
|
||||
echo ''
|
||||
echo "API multi-arch images pushed successfully:"
|
||||
echo " ${API_IMAGE_TAGGED}"
|
||||
echo " ${API_IMAGE_LATEST}"
|
||||
"""
|
||||
stage('Prepare Harbor Authentication') {
|
||||
steps {
|
||||
withCredentials([usernamePassword(
|
||||
credentialsId: "${HARBOR_CREDENTIALS}",
|
||||
usernameVariable: 'HARBOR_USERNAME',
|
||||
passwordVariable: 'HARBOR_PASSWORD'
|
||||
)]) {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
auth=$(printf '%s:%s' "$HARBOR_USERNAME" "$HARBOR_PASSWORD" | base64 -w0)
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}\n' "$REGISTRY_URL" "$auth" >"$DOCKER_CONFIG/config.json"
|
||||
chmod 600 "$DOCKER_CONFIG/config.json"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build And Push Web Image') {
|
||||
stage('Build And Push amd64') {
|
||||
steps {
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: "${DOCKER_CREDS}",
|
||||
usernameVariable: 'DOCKER_USERNAME',
|
||||
passwordVariable: 'DOCKER_PASSWORD'
|
||||
)
|
||||
]) {
|
||||
sh """
|
||||
set -e
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
arch=amd64
|
||||
platform=linux/amd64
|
||||
api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
|
||||
web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
|
||||
|
||||
run_with_heartbeat() {
|
||||
log_file="\$1"
|
||||
build_label="\$2"
|
||||
shift 2
|
||||
rm -f "\$log_file"
|
||||
: > "\$log_file"
|
||||
"\$@" >"\$log_file" 2>&1 &
|
||||
cmd_pid=\$!
|
||||
cmd_status=0
|
||||
last_size=0
|
||||
while kill -0 "\$cmd_pid" >/dev/null 2>&1; do
|
||||
echo "[heartbeat] \${build_label} still running at \$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
if [ -f "\$log_file" ]; then
|
||||
current_size=\$(stat -c%s "\$log_file" 2>/dev/null || echo 0)
|
||||
if [ "\$current_size" -gt "\$last_size" ]; then
|
||||
start_byte=\$((last_size + 1))
|
||||
tail -c +"\$start_byte" "\$log_file" || true
|
||||
last_size=\$current_size
|
||||
fi
|
||||
fi
|
||||
sleep 20
|
||||
done
|
||||
wait "\$cmd_pid" || cmd_status=\$?
|
||||
if [ -f "\$log_file" ]; then
|
||||
current_size=\$(stat -c%s "\$log_file" 2>/dev/null || echo 0)
|
||||
if [ "\$current_size" -gt "\$last_size" ]; then
|
||||
start_byte=\$((last_size + 1))
|
||||
tail -c +"\$start_byte" "\$log_file" || true
|
||||
fi
|
||||
fi
|
||||
if [ "\$cmd_status" -ne 0 ]; then
|
||||
echo "[heartbeat] \${build_label} failed with exit code \$cmd_status"
|
||||
fi
|
||||
return "\$cmd_status"
|
||||
}
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \
|
||||
--builder "$BUILDER_NAME" --platform "$platform" --network host \
|
||||
--progress=plain --provenance=false --no-cache \
|
||||
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \
|
||||
--build-arg http_proxy="$HTTP_PROXY_URL" \
|
||||
--build-arg https_proxy="$HTTP_PROXY_URL" \
|
||||
--build-arg no_proxy="$NO_PROXY_HOSTS" \
|
||||
-f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --load .
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$api_ref"
|
||||
sudo docker rmi "$api_ref" >/dev/null
|
||||
|
||||
for PLATFORM in linux/amd64 linux/arm64; do
|
||||
ARCH_TAG="\${PLATFORM##*/}"
|
||||
PLAT_REF="${WEB_IMAGE_TAGGED}-\${ARCH_TAG}"
|
||||
PLAT_LATEST="${WEB_IMAGE_LATEST}-\${ARCH_TAG}"
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \
|
||||
--builder "$BUILDER_NAME" --platform "$platform" --network host \
|
||||
--progress=plain --provenance=false --no-cache \
|
||||
--build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \
|
||||
--build-arg NGINX_IMAGE="$NGINX_IMAGE" \
|
||||
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \
|
||||
--build-arg http_proxy="$HTTP_PROXY_URL" \
|
||||
--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" --load frontend
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$web_ref"
|
||||
sudo docker rmi "$web_ref" >/dev/null
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
echo ''
|
||||
echo "=== Building Web for \$PLATFORM -> \$PLAT_REF ==="
|
||||
run_with_heartbeat /tmp/live-recorder-web-\${ARCH_TAG}.log "Web \${ARCH_TAG} build" \
|
||||
sudo docker buildx build \
|
||||
--platform "\$PLATFORM" \
|
||||
--network host \
|
||||
--progress=plain \
|
||||
--provenance=false \
|
||||
--no-cache \
|
||||
--build-arg NODE_IMAGE=${WEB_NODE_IMAGE} \
|
||||
--build-arg NGINX_IMAGE=${WEB_NGINX_IMAGE} \
|
||||
--build-arg HTTP_PROXY=${HTTP_PROXY_URL} \
|
||||
--build-arg HTTPS_PROXY=${HTTP_PROXY_URL} \
|
||||
--build-arg NO_PROXY=${NO_PROXY_HOSTS} \
|
||||
--build-arg http_proxy=${HTTP_PROXY_URL} \
|
||||
--build-arg https_proxy=${HTTP_PROXY_URL} \
|
||||
--build-arg no_proxy=${NO_PROXY_HOSTS} \
|
||||
-f frontend/Dockerfile \
|
||||
--build-arg VITE_API_BASE_URL=/api \
|
||||
-t "\$PLAT_REF" \
|
||||
-t "\$PLAT_LATEST" \
|
||||
--load \
|
||||
frontend
|
||||
stage('Build And Push arm64') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
arch=arm64
|
||||
platform=linux/arm64
|
||||
api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
|
||||
web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch"
|
||||
|
||||
echo "=== Pushing \$PLAT_REF ==="
|
||||
AUTH_B64=\$(printf '%s:%s' "\$DOCKER_USERNAME" "\$DOCKER_PASSWORD" | base64 -w0) && printf '{"auths":{"%s":{"auth":"%s"}}}' "${REGISTRY_URL}" "\$AUTH_B64" | sudo tee /root/.docker/config.json >/dev/null
|
||||
sudo docker push "\$PLAT_REF" 2>&1 | tail -3
|
||||
sudo docker push "\$PLAT_LATEST" 2>&1 | tail -3
|
||||
sudo docker rmi "\$PLAT_REF" "\$PLAT_LATEST" 2>/dev/null || true
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \
|
||||
--builder "$BUILDER_NAME" --platform "$platform" --network host \
|
||||
--progress=plain --provenance=false --no-cache \
|
||||
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \
|
||||
--build-arg http_proxy="$HTTP_PROXY_URL" \
|
||||
--build-arg https_proxy="$HTTP_PROXY_URL" \
|
||||
--build-arg no_proxy="$NO_PROXY_HOSTS" \
|
||||
-f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --load .
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$api_ref"
|
||||
sudo docker rmi "$api_ref" >/dev/null
|
||||
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \
|
||||
--builder "$BUILDER_NAME" --platform "$platform" --network host \
|
||||
--progress=plain --provenance=false --no-cache \
|
||||
--build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \
|
||||
--build-arg NGINX_IMAGE="$NGINX_IMAGE" \
|
||||
--build-arg HTTP_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \
|
||||
--build-arg NO_PROXY="$NO_PROXY_HOSTS" \
|
||||
--build-arg http_proxy="$HTTP_PROXY_URL" \
|
||||
--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" --load frontend
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$web_ref"
|
||||
sudo docker rmi "$web_ref" >/dev/null
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Publish Multi-arch Manifests') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
for repository in "$API_IMAGE_REPO" "$WEB_IMAGE_REPO"; do
|
||||
immutable_ref="$repository:$IMMUTABLE_TAG"
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools create \
|
||||
--tag "$immutable_ref" \
|
||||
"$immutable_ref-amd64" "$immutable_ref-arm64"
|
||||
|
||||
manifest=$(sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools inspect --raw "$immutable_ref")
|
||||
printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"amd64"'
|
||||
printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"arm64"'
|
||||
done
|
||||
|
||||
echo ''
|
||||
echo "=== Creating multi-arch manifest: ${WEB_IMAGE_TAGGED} ==="
|
||||
AUTH_B64=\$(printf '%s:%s' "\$DOCKER_USERNAME" "\$DOCKER_PASSWORD" | base64 -w0) && printf '{"auths":{"%s":{"auth":"%s"}}}' "${REGISTRY_URL}" "\$AUTH_B64" | sudo tee /root/.docker/config.json >/dev/null
|
||||
sudo docker manifest create "${WEB_IMAGE_TAGGED}" \
|
||||
"${WEB_IMAGE_TAGGED}-amd64" \
|
||||
"${WEB_IMAGE_TAGGED}-arm64"
|
||||
sudo docker manifest create --amend "${WEB_IMAGE_LATEST}" \
|
||||
"${WEB_IMAGE_LATEST}-amd64" \
|
||||
"${WEB_IMAGE_LATEST}-arm64"
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools create \
|
||||
--tag "$API_IMAGE_REPO:$APP_VERSION" --tag "$API_IMAGE_REPO:latest" \
|
||||
"$API_IMAGE_REPO:$IMMUTABLE_TAG"
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools create \
|
||||
--tag "$WEB_IMAGE_REPO:$APP_VERSION" --tag "$WEB_IMAGE_REPO:latest" \
|
||||
"$WEB_IMAGE_REPO:$IMMUTABLE_TAG"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Pushing manifests ==="
|
||||
sudo docker manifest push "${WEB_IMAGE_TAGGED}" 2>&1 | tail -3
|
||||
sudo docker manifest push "${WEB_IMAGE_LATEST}" 2>&1 | tail -3
|
||||
|
||||
echo ''
|
||||
echo "Web multi-arch images pushed successfully:"
|
||||
echo " ${WEB_IMAGE_TAGGED}"
|
||||
echo " ${WEB_IMAGE_LATEST}"
|
||||
"""
|
||||
stage('Upload fnOS Artifact') {
|
||||
steps {
|
||||
withCredentials([usernamePassword(
|
||||
credentialsId: "${OPENLIST_CREDENTIALS}",
|
||||
usernameVariable: 'OPENLIST_USERNAME',
|
||||
passwordVariable: 'OPENLIST_PASSWORD'
|
||||
)]) {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
./scripts/upload-openlist-artifact.sh "$FPK_PATH" "$OPENLIST_REMOTE_DIR"
|
||||
./scripts/upload-openlist-artifact.sh "$FPK_PATH.sha256" "$OPENLIST_REMOTE_DIR"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build Summary') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
printf '%s\n' \
|
||||
"Commit: $(git rev-parse HEAD)" \
|
||||
"fnOS: $FPK_BASENAME" \
|
||||
"Checksum: $(awk '{print $1}' "$FPK_PATH.sha256")" \
|
||||
"API amd64: $API_IMAGE_REPO:$IMMUTABLE_TAG-amd64" \
|
||||
"API arm64: $API_IMAGE_REPO:$IMMUTABLE_TAG-arm64" \
|
||||
"API multi-arch: $API_IMAGE_REPO:$IMMUTABLE_TAG" \
|
||||
"Web amd64: $WEB_IMAGE_REPO:$IMMUTABLE_TAG-amd64" \
|
||||
"Web arm64: $WEB_IMAGE_REPO:$IMMUTABLE_TAG-arm64" \
|
||||
"Web multi-arch: $WEB_IMAGE_REPO:$IMMUTABLE_TAG" \
|
||||
"OpenList: $OPENLIST_BASE_URL$OPENLIST_REMOTE_DIR/$FPK_BASENAME"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
echo "Pipeline completed successfully."
|
||||
echo "LiveRecorder ${env.IMMUTABLE_TAG} published successfully."
|
||||
}
|
||||
failure {
|
||||
echo "Pipeline failed. Please check the build log."
|
||||
echo 'LiveRecorder pipeline failed; stable Docker tags were only updated if all architecture builds succeeded.'
|
||||
}
|
||||
always {
|
||||
sh """
|
||||
sh '''
|
||||
set +e
|
||||
sudo docker logout ${REGISTRY_URL} >/dev/null 2>&1 || true
|
||||
true
|
||||
"""
|
||||
deleteDir()
|
||||
if [ -n "${BUILDER_NAME:-}" ]; then
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "${IMMUTABLE_TAG:-}" ]; then
|
||||
for repository in "$API_IMAGE_REPO" "$WEB_IMAGE_REPO"; do
|
||||
for suffix in amd64 arm64; do
|
||||
sudo docker rmi "$repository:$IMMUTABLE_TAG-$suffix" >/dev/null 2>&1 || true
|
||||
done
|
||||
sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker manifest rm "$repository:$IMMUTABLE_TAG" >/dev/null 2>&1 || true
|
||||
done
|
||||
fi
|
||||
if [ -f "$DOCKER_CONFIG/config.json" ]; then
|
||||
: >"$DOCKER_CONFIG/config.json"
|
||||
fi
|
||||
'''
|
||||
cleanWs(deleteDirs: true, notFailBuild: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user