diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..aceec58 --- /dev/null +++ b/Jenkinsfile @@ -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() + } + } +}