|
|
@@ -0,0 +1,227 @@
|
|
|
+// note-manager Jenkins Pipeline
|
|
|
+// 项目:Go/Gin + SQLite(CGO),部署端口 9090
|
|
|
+
|
|
|
+pipeline {
|
|
|
+ agent any
|
|
|
+
|
|
|
+ environment {
|
|
|
+ // ─── 基础配置 ───
|
|
|
+ APP_NAME = 'note-manager'
|
|
|
+ GO_VERSION = '1.21'
|
|
|
+ GIT_REPO = 'https://git.cnbugs.com/AI-Agent/note-manager.git'
|
|
|
+ GIT_BRANCH = 'main'
|
|
|
+ // ─── 部署配置(敏感信息用 Jenkins Credentials)───
|
|
|
+ DEPLOY_HOST = credentials('deploy-host')
|
|
|
+ DEPLOY_USER = credentials('deploy-user')
|
|
|
+ DEPLOY_PATH = '/vol1/Project/note-manager'
|
|
|
+ SERVICE_PORT = '9090'
|
|
|
+ // ─── 构建产物 ───
|
|
|
+ BUILD_DIR = 'dist'
|
|
|
+ }
|
|
|
+
|
|
|
+ options {
|
|
|
+ timestamps()
|
|
|
+ timeout(time: 15, unit: 'MINUTES')
|
|
|
+ disableConcurrentBuilds()
|
|
|
+ buildDiscarder(logRotator(numToKeepStr: '10'))
|
|
|
+ }
|
|
|
+
|
|
|
+ stages {
|
|
|
+
|
|
|
+ // ━━━━━━━━ 拉取代码 ━━━━━━━━
|
|
|
+ stage('Checkout') {
|
|
|
+ steps {
|
|
|
+ git branch: "${GIT_BRANCH}",
|
|
|
+ url: "${GIT_REPO}",
|
|
|
+ credentialsId: 'git-cnbugs-credentials'
|
|
|
+ sh 'echo "分支: ${GIT_BRANCH} | 提交: $(git rev-parse --short HEAD)"'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ━━━━━━━━ 代码检查 ━━━━━━━━
|
|
|
+ stage('Lint') {
|
|
|
+ steps {
|
|
|
+ sh '''
|
|
|
+ docker run --rm \
|
|
|
+ -v "$PWD":/app \
|
|
|
+ -w /app \
|
|
|
+ golang:${GO_VERSION}-alpine \
|
|
|
+ sh -c "go fmt ./... && go vet ./..."
|
|
|
+ '''
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ━━━━━━━━ 单元测试 ━━━━━━━━
|
|
|
+ stage('Test') {
|
|
|
+ steps {
|
|
|
+ sh '''
|
|
|
+ docker run --rm \
|
|
|
+ -v "$PWD":/app \
|
|
|
+ -w /app \
|
|
|
+ -e CGO_ENABLED=1 \
|
|
|
+ golang:${GO_VERSION} \
|
|
|
+ go test -v -race -coverprofile=coverage.out ./...
|
|
|
+ '''
|
|
|
+ }
|
|
|
+ post {
|
|
|
+ always {
|
|
|
+ archiveArtifacts artifacts: 'coverage.out', allowEmptyArchive: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ━━━━━━━━ 编译构建 ━━━━━━━━
|
|
|
+ stage('Build') {
|
|
|
+ steps {
|
|
|
+ sh '''
|
|
|
+ rm -rf ${BUILD_DIR} && mkdir -p ${BUILD_DIR}
|
|
|
+
|
|
|
+ VERSION=$(git describe --tags --always --dirty 2>/dev/null || git rev-parse --short HEAD)
|
|
|
+ BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
|
+ COMMIT=$(git rev-parse --short HEAD)
|
|
|
+
|
|
|
+ echo "━━━ 构建信息 ━━━"
|
|
|
+ echo "版本: ${VERSION}"
|
|
|
+ echo "提交: ${COMMIT}"
|
|
|
+ echo "时间: ${BUILD_TIME}"
|
|
|
+
|
|
|
+ # go-sqlite3 需要 CGO_ENABLED=1,用完整 golang 镜像确保 gcc 可用
|
|
|
+ docker run --rm \
|
|
|
+ -v "$PWD":/app \
|
|
|
+ -w /app \
|
|
|
+ -e CGO_ENABLED=1 \
|
|
|
+ -e GOOS=linux \
|
|
|
+ -e GOARCH=amd64 \
|
|
|
+ golang:${GO_VERSION} \
|
|
|
+ go build \
|
|
|
+ -ldflags="-s -w \
|
|
|
+ -X main.Version=${VERSION} \
|
|
|
+ -X main.BuildTime=${BUILD_TIME} \
|
|
|
+ -X main.Commit=${COMMIT}" \
|
|
|
+ -o ${BUILD_DIR}/${APP_NAME} \
|
|
|
+ ./...
|
|
|
+
|
|
|
+ file ${BUILD_DIR}/${APP_NAME}
|
|
|
+ ls -lh ${BUILD_DIR}/${APP_NAME}
|
|
|
+ '''
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ━━━━━━━━ 打包发布 ━━━━━━━━
|
|
|
+ stage('Package') {
|
|
|
+ steps {
|
|
|
+ sh '''
|
|
|
+ VERSION=$(git describe --tags --always --dirty 2>/dev/null || git rev-parse --short HEAD)
|
|
|
+
|
|
|
+ mkdir -p ${BUILD_DIR}/package
|
|
|
+
|
|
|
+ # 复制运行所需文件
|
|
|
+ cp ${BUILD_DIR}/${APP_NAME} ${BUILD_DIR}/package/
|
|
|
+ cp -r config ${BUILD_DIR}/package/
|
|
|
+ cp -r web ${BUILD_DIR}/package/
|
|
|
+
|
|
|
+ # 生成启动脚本
|
|
|
+ cat > ${BUILD_DIR}/package/start.sh << 'STARTSCRIPT'
|
|
|
+#!/bin/bash
|
|
|
+APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
+cd "$APP_DIR"
|
|
|
+
|
|
|
+export PORT=${SERVICE_PORT}
|
|
|
+export DB_PATH=data/notes.db
|
|
|
+export UPLOAD_DIR=uploads
|
|
|
+export ADMIN_PASS=${ADMIN_PASS:-admin123}
|
|
|
+
|
|
|
+mkdir -p data uploads
|
|
|
+
|
|
|
+echo "启动 note-manager ..."
|
|
|
+./note-manager
|
|
|
+STARTSCRIPT
|
|
|
+ chmod +x ${BUILD_DIR}/package/start.sh
|
|
|
+
|
|
|
+ # 打 tar 包
|
|
|
+ cd ${BUILD_DIR}/package
|
|
|
+ tar czf ${WORKSPACE}/${APP_NAME}-${VERSION}-linux-amd64.tar.gz .
|
|
|
+ cd ${WORKSPACE}
|
|
|
+
|
|
|
+ echo "━━━ 发布包 ━━━"
|
|
|
+ ls -lh ${APP_NAME}-${VERSION}-linux-amd64.tar.gz
|
|
|
+ '''
|
|
|
+ }
|
|
|
+ post {
|
|
|
+ success {
|
|
|
+ archiveArtifacts artifacts: "${APP_NAME}-*-linux-amd64.tar.gz", fingerprint: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ━━━━━━━━ 部署(需手动确认)━━━━━━━━
|
|
|
+ stage('Deploy') {
|
|
|
+ when {
|
|
|
+ branch 'main'
|
|
|
+ }
|
|
|
+ input {
|
|
|
+ message '确认部署到生产环境?'
|
|
|
+ ok '部署'
|
|
|
+ }
|
|
|
+ steps {
|
|
|
+ sh '''
|
|
|
+ VERSION=$(git describe --tags --always --dirty 2>/dev/null || git rev-parse --short HEAD)
|
|
|
+ PACKAGE="${APP_NAME}-${VERSION}-linux-amd64.tar.gz"
|
|
|
+
|
|
|
+ echo "━━━ 部署到 ${DEPLOY_HOST} ━━━"
|
|
|
+
|
|
|
+ # 上传发布包
|
|
|
+ scp -o StrictHostKeyChecking=no \
|
|
|
+ ${PACKAGE} \
|
|
|
+ ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/${PACKAGE}
|
|
|
+
|
|
|
+ # 远程执行部署
|
|
|
+ ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} "bash -s" << REMOTE_DEPLOY
|
|
|
+ set -e
|
|
|
+ echo "停止旧服务..."
|
|
|
+ pkill -f 'note-manager' || true
|
|
|
+ sleep 1
|
|
|
+
|
|
|
+ echo "备份旧版本..."
|
|
|
+ if [ -f ${DEPLOY_PATH}/note-manager ]; then
|
|
|
+ cp ${DEPLOY_PATH}/note-manager ${DEPLOY_PATH}/note-manager.bak
|
|
|
+ fi
|
|
|
+
|
|
|
+ echo "解压新版本..."
|
|
|
+ mkdir -p ${DEPLOY_PATH}
|
|
|
+ cd ${DEPLOY_PATH}
|
|
|
+ tar xzf /tmp/${PACKAGE}
|
|
|
+ rm -f /tmp/${PACKAGE}
|
|
|
+
|
|
|
+ echo "启动服务..."
|
|
|
+ cd ${DEPLOY_PATH}
|
|
|
+ PORT=${SERVICE_PORT} nohup ./note-manager > ${DEPLOY_PATH}/app.log 2>&1 &
|
|
|
+
|
|
|
+ sleep 2
|
|
|
+ if curl -sf http://localhost:${SERVICE_PORT}/ > /dev/null 2>&1; then
|
|
|
+ echo "✅ 服务启动成功!端口: ${SERVICE_PORT}"
|
|
|
+ else
|
|
|
+ echo "❌ 服务启动失败,查看日志:"
|
|
|
+ tail -20 ${DEPLOY_PATH}/app.log
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+REMOTE_DEPLOY
|
|
|
+
|
|
|
+ echo "━━━ 部署完成 ━━━"
|
|
|
+ '''
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ post {
|
|
|
+ success {
|
|
|
+ echo '✅ Pipeline 执行成功'
|
|
|
+ }
|
|
|
+ failure {
|
|
|
+ echo '❌ Pipeline 执行失败'
|
|
|
+ }
|
|
|
+ always {
|
|
|
+ cleanWs()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|