pipeline { agent { node { label '构建机1' customWorkspace '/home/nanxunai/goujian/workspace/douyin-release' } } options { timestamps() disableConcurrentBuilds() skipDefaultCheckout(true) timeout(time: 180, unit: 'MINUTES') buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20')) } environment { PATH = '/home/nanxunai/.local/bin:/usr/local/bin:/usr/bin:/bin' DOTNET = '/home/nanxunai/.local/bin/dotnet' FNPACK_BIN = '/home/nanxunai/.local/bin/fnpack' REGISTRY_URL = 'reg.nxsir.cn' IMAGE_REPO = 'reg.nxsir.cn/douyin' HARBOR_CREDENTIALS = 'douyin_key' OPENLIST_CREDENTIALS = 'openlist_key' NODE_CREDENTIALS = 'bbb939ea-4f01-4b47-aecb-c5ee2a551ef4' OPENLIST_BASE_URL = 'https://openlist.nxsir.cn' OPENLIST_REMOTE_DIR = '/yidongpan/构建产物/douyin' 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,mcr.microsoft.com' BUILDER_NAME = "douyin-${BUILD_NUMBER}" DOCKER_CONFIG = "${WORKSPACE}/.ci/docker" NPM_CONFIG_CACHE = "${WORKSPACE}/.ci/npm-cache" NUGET_PACKAGES = "${WORKSPACE}/.ci/nuget-packages" DOTNET_CLI_HOME = "${WORKSPACE}/.ci/dotnet-home" DYSYNC_BUILD_TMPDIR = "${WORKSPACE}/.ci/fnos-tmp" DYSYNC_VERIFY_TMPDIR = "${WORKSPACE}/.ci/fnos-verify" DYSYNC_SMOKE_TMPDIR = "${WORKSPACE}/.ci/fnos-smoke" } stages { stage('Checkout') { steps { deleteDir() checkout scm } } stage('Metadata And Preflight') { steps { script { env.APP_VERSION = sh(script: "sed -n 's/^version[[:space:]]*=[[:space:]]*//p' fnos/manifest | head -n 1", returnStdout: true).trim() env.SHORT_SHA = sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim() env.FNOS_VERSION = sh(script: ''' set -euo pipefail base="$APP_VERSION" major=${base%%.*}; remainder=${base#*.} minor=${remainder%%.*}; patch=${remainder#*.} test "$base" = "$major.$minor.$patch" test "$BUILD_NUMBER" -lt 100000 printf '%s.%s.%s\n' "$major" "$minor" "$((patch * 100000 + BUILD_NUMBER))" ''', returnStdout: true).trim() env.IMMUTABLE_TAG = "${env.APP_VERSION}-b${env.BUILD_NUMBER}-${env.SHORT_SHA}" env.IMAGE_REF = "${env.IMAGE_REPO}:${env.IMMUTABLE_TAG}" env.FPK_BASENAME = "dy.net-${env.FNOS_VERSION}-x86_64.fpk" env.FPK_PATH = "${env.WORKSPACE}/artifacts/${env.FPK_BASENAME}" env.MANIFEST_PATH = "${env.WORKSPACE}/artifacts/douyin-${env.IMMUTABLE_TAG}-build-manifest.json" currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.IMMUTABLE_TAG}" currentBuild.description = "fnOS ${env.FNOS_VERSION}" } sh ''' set -euo pipefail test "$(uname -m)" = x86_64 for command_name in git sudo docker curl python3 npm node sha256sum tar; do command -v "$command_name" >/dev/null; done test -x "$DOTNET" && test -x "$FNPACK_BIN" mkdir -p "$DOCKER_CONFIG" "$NPM_CONFIG_CACHE" "$NUGET_PACKAGES" "$DOTNET_CLI_HOME" \ "$DYSYNC_BUILD_TMPDIR" "$DYSYNC_VERIFY_TMPDIR" "$DYSYNC_SMOKE_TMPDIR" artifacts chmod 700 "$DOCKER_CONFIG" available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }') test "$available_kb" -ge 3145728 ''' } } stage('Restore And Test') { steps { sh ''' set -euo pipefail npx --yes pnpm@10.15.0 --dir app install --frozen-lockfile npm run test:mobile-contract --prefix app npm run build --prefix app "$DOTNET" restore tests/dy.net.Tests/dy.net.Tests.csproj --disable-parallel "$DOTNET" test tests/dy.net.Tests/dy.net.Tests.csproj -c Release --no-restore \ --logger 'console;verbosity=normal' /maxcpucount:1 ''' } } stage('Build And Smoke fnOS x64') { steps { sh ''' set -euo pipefail PACKAGE_VERSION="$FNOS_VERSION" FNPACK="$FNPACK_BIN" DOTNET="$DOTNET" \ ./scripts/build-fnos-package.sh artifacts test -s "$FPK_PATH" && test -s "$FPK_PATH.sha256" ./scripts/verify-fnos-package.sh "$FPK_PATH" "$FNOS_VERSION" ./scripts/smoke-fnos-package.sh "$FPK_PATH" "$DYSYNC_SMOKE_TMPDIR" ''' } } stage('Build And Smoke Docker amd64') { steps { withCredentials([usernamePassword(credentialsId: "${NODE_CREDENTIALS}", usernameVariable: 'JENKINS_NODE_USERNAME', passwordVariable: 'JENKINS_NODE_PASSWORD')]) { sh ''' set -euo pipefail ./scripts/ci-docker.sh buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true ./scripts/ci-docker.sh buildx create --name "$BUILDER_NAME" --driver docker-container \ --driver-opt network=host \ --driver-opt "env.http_proxy=$HTTP_PROXY_URL" \ --driver-opt "env.https_proxy=$HTTP_PROXY_URL" \ --driver-opt "env.no_proxy=$NO_PROXY_HOSTS" --use ./scripts/ci-docker.sh buildx inspect "$BUILDER_NAME" --bootstrap ./scripts/ci-docker.sh buildx build --builder "$BUILDER_NAME" --platform linux/amd64 --network host \ --progress=plain --provenance=false --no-cache --load \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" --build-arg HTTPS_PROXY="$HTTP_PROXY_URL" \ --build-arg NO_PROXY="$NO_PROXY_HOSTS" -t "$IMAGE_REF" . smoke_name="douyin-smoke-$BUILD_NUMBER" mkdir -p .ci/docker-smoke ./scripts/ci-docker.sh rm -f "$smoke_name" >/dev/null 2>&1 || true ./scripts/ci-docker.sh run -d --name "$smoke_name" -p 127.0.0.1::10101 \ -v "$WORKSPACE/.ci/docker-smoke:/data" "$IMAGE_REF" host_port=$(./scripts/ci-docker.sh port "$smoke_name" 10101/tcp | sed -n 's/.*://p' | head -n 1) test -n "$host_port" ready=0 for attempt in $(seq 1 60); do if curl -fsS "http://127.0.0.1:$host_port/" >/dev/null; then ready=1; break; fi sleep 2 done test "$ready" = 1 || { ./scripts/ci-docker.sh logs "$smoke_name" || true; exit 1; } ./scripts/ci-docker.sh restart "$smoke_name" >/dev/null sleep 3 curl -fsS "http://127.0.0.1:$host_port/" >/dev/null ./scripts/ci-docker.sh rm -f "$smoke_name" ./scripts/ci-docker.sh buildx prune --builder "$BUILDER_NAME" --all --force ''' } } } stage('Push Immutable And Describe') { steps { withCredentials([ usernamePassword(credentialsId: "${NODE_CREDENTIALS}", usernameVariable: 'JENKINS_NODE_USERNAME', passwordVariable: 'JENKINS_NODE_PASSWORD'), 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" ./scripts/ci-docker.sh push "$IMAGE_REF" digest=$(./scripts/ci-docker.sh image inspect --format '{{index .RepoDigests 0}}' "$IMAGE_REF" | sed -n 's/.*@//p') test -n "$digest"; export digest fpk_sha=$(awk '{print $1}' "$FPK_PATH.sha256"); export fpk_sha python3 - <<'PY' import json, os, pathlib, subprocess path = pathlib.Path(os.environ['MANIFEST_PATH']) payload = { 'project': 'douyin', 'buildNumber': os.environ['BUILD_NUMBER'], 'commit': subprocess.check_output(['git','rev-parse','HEAD'], text=True).strip(), 'productVersion': os.environ['APP_VERSION'], 'fnosVersion': os.environ['FNOS_VERSION'], 'fpk': pathlib.Path(os.environ['FPK_PATH']).name, 'fpkSha256': os.environ['fpk_sha'], 'image': os.environ['IMAGE_REF'], 'digest': os.environ['digest'], 'platform': 'linux/amd64' } path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') PY sha256sum "$MANIFEST_PATH" >"$MANIFEST_PATH.sha256" ''' } } } stage('Upload Artifacts') { steps { withCredentials([usernamePassword(credentialsId: "${OPENLIST_CREDENTIALS}", usernameVariable: 'OPENLIST_USERNAME', passwordVariable: 'OPENLIST_PASSWORD')]) { sh ''' set -euo pipefail for artifact in "$FPK_PATH" "$FPK_PATH.sha256" "$MANIFEST_PATH" "$MANIFEST_PATH.sha256"; do ./scripts/upload-openlist-artifact.sh "$artifact" "$OPENLIST_REMOTE_DIR" done ''' } } } stage('Publish Stable Image Tags') { steps { withCredentials([usernamePassword(credentialsId: "${NODE_CREDENTIALS}", usernameVariable: 'JENKINS_NODE_USERNAME', passwordVariable: 'JENKINS_NODE_PASSWORD')]) { sh ''' set -euo pipefail for tag in "$APP_VERSION" latest; do ./scripts/ci-docker.sh tag "$IMAGE_REF" "$IMAGE_REPO:$tag" ./scripts/ci-docker.sh push "$IMAGE_REPO:$tag" done ''' } } } } post { success { archiveArtifacts artifacts: 'artifacts/*.fpk,artifacts/*.sha256,artifacts/*.json', fingerprint: true } always { withCredentials([usernamePassword(credentialsId: "${NODE_CREDENTIALS}", usernameVariable: 'JENKINS_NODE_USERNAME', passwordVariable: 'JENKINS_NODE_PASSWORD')]) { sh ''' set +e ./scripts/ci-docker.sh rm -f "douyin-smoke-$BUILD_NUMBER" >/dev/null 2>&1 || true ./scripts/ci-docker.sh buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true for tag in "$IMMUTABLE_TAG" "$APP_VERSION" latest; do ./scripts/ci-docker.sh rmi "$IMAGE_REPO:$tag" >/dev/null 2>&1 || true; done : >"$DOCKER_CONFIG/config.json" 2>/dev/null || true ''' } cleanWs(deleteDirs: true, notFailBuild: true) } } }