clouddreamai-cicd-setup 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +421 -92
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/gitlab-client.d.ts.map +1 -1
- package/dist/core/gitlab-client.js +0 -4
- package/dist/core/gitlab-client.js.map +1 -1
- package/dist/core/types.d.ts +26 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/variables.d.ts.map +1 -1
- package/dist/core/variables.js +54 -47
- package/dist/core/variables.js.map +1 -1
- package/dist/utils/prompts.d.ts +8 -8
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +192 -147
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/template.d.ts +13 -5
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +77 -12
- package/dist/utils/template.js.map +1 -1
- package/package.json +1 -1
- package/templates/docker-compose/nestjs.yml +17 -0
- package/templates/docker-compose/vue.yml +15 -0
- package/templates/gitlab-ci/nestjs-gitlab.yml +170 -0
- package/templates/gitlab-ci/nestjs.yml +15 -24
- package/templates/gitlab-ci/vue-gitlab.yml +168 -0
- package/templates/gitlab-ci/vue.yml +15 -24
- package/templates/scripts/deploy.sh +121 -15
package/dist/utils/template.js
CHANGED
|
@@ -52,16 +52,16 @@ class TemplateGenerator {
|
|
|
52
52
|
/**
|
|
53
53
|
* 生成 .gitlab-ci.yml 文件内容
|
|
54
54
|
*/
|
|
55
|
-
generateGitLabCI(projectType, config, gitlabHost) {
|
|
56
|
-
//
|
|
57
|
-
const templateName = this.getTemplateNameByType(projectType);
|
|
55
|
+
generateGitLabCI(projectType, config, gitlabHost, registryType) {
|
|
56
|
+
// 根据项目类型和 Registry 类型选择模板
|
|
57
|
+
const templateName = this.getTemplateNameByType(projectType, registryType);
|
|
58
58
|
const templatePath = path.join(this.templatesDir, 'gitlab-ci', `${templateName}.yml`);
|
|
59
59
|
// 读取模板
|
|
60
60
|
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
61
61
|
// 准备替换变量
|
|
62
62
|
const variables = {
|
|
63
63
|
APP_NAME: config.name,
|
|
64
|
-
DOCKER_IMAGE: config.dockerImage,
|
|
64
|
+
DOCKER_IMAGE: config.dockerImage || '',
|
|
65
65
|
DEPLOY_DIR: config.deployDir,
|
|
66
66
|
DEV_PORT: config.devPort.toString(),
|
|
67
67
|
PROD_PORT: config.prodPort.toString(),
|
|
@@ -69,6 +69,7 @@ class TemplateGenerator {
|
|
|
69
69
|
PROD_URL: config.prodUrl || `http://localhost:${config.prodPort}`,
|
|
70
70
|
GITLAB_HOST: gitlabHost.replace(/^https?:\/\//, ''),
|
|
71
71
|
LINT_COMMAND: this.getLintCommand(projectType),
|
|
72
|
+
REGISTRY_TYPE: registryType,
|
|
72
73
|
};
|
|
73
74
|
// 替换占位符
|
|
74
75
|
return this.replacePlaceholders(template, variables);
|
|
@@ -84,6 +85,8 @@ class TemplateGenerator {
|
|
|
84
85
|
DEPLOY_DIR: config.deployDir,
|
|
85
86
|
DEV_PORT: config.devPort.toString(),
|
|
86
87
|
PROD_PORT: config.prodPort.toString(),
|
|
88
|
+
DEV_DOMAIN: config.devDomain || '',
|
|
89
|
+
PROD_DOMAIN: config.prodDomain || '',
|
|
87
90
|
};
|
|
88
91
|
return this.replacePlaceholders(template, variables);
|
|
89
92
|
}
|
|
@@ -98,10 +101,26 @@ class TemplateGenerator {
|
|
|
98
101
|
};
|
|
99
102
|
return this.replacePlaceholders(template, variables);
|
|
100
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* 生成 docker-compose.yml 文件内容
|
|
106
|
+
*/
|
|
107
|
+
generateDockerCompose(projectType, config) {
|
|
108
|
+
// 根据项目类型选择模板
|
|
109
|
+
const templateName = this.getDockerComposeTemplate(projectType);
|
|
110
|
+
const templatePath = path.join(this.templatesDir, 'docker-compose', `${templateName}.yml`);
|
|
111
|
+
// 读取模板
|
|
112
|
+
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
113
|
+
// 准备替换变量
|
|
114
|
+
const variables = {
|
|
115
|
+
APP_NAME: config.name,
|
|
116
|
+
DOCKER_IMAGE: config.dockerImage || config.name,
|
|
117
|
+
};
|
|
118
|
+
return this.replacePlaceholders(template, variables);
|
|
119
|
+
}
|
|
101
120
|
/**
|
|
102
121
|
* 生成所有文件到指定目录
|
|
103
122
|
*/
|
|
104
|
-
generateAll(outputDir, projectType, config, gitlabHost) {
|
|
123
|
+
async generateAll(outputDir, projectType, config, gitlabHost, registryType) {
|
|
105
124
|
try {
|
|
106
125
|
const files = [];
|
|
107
126
|
// 确保输出目录存在
|
|
@@ -114,21 +133,48 @@ class TemplateGenerator {
|
|
|
114
133
|
fs.mkdirSync(ciDir, { recursive: true });
|
|
115
134
|
}
|
|
116
135
|
// 生成 .gitlab-ci.yml
|
|
117
|
-
const gitlabCIContent = this.generateGitLabCI(projectType, config, gitlabHost);
|
|
136
|
+
const gitlabCIContent = this.generateGitLabCI(projectType, config, gitlabHost, registryType);
|
|
118
137
|
const gitlabCIPath = path.join(outputDir, '.gitlab-ci.yml');
|
|
119
138
|
fs.writeFileSync(gitlabCIPath, gitlabCIContent, 'utf-8');
|
|
120
139
|
files.push(gitlabCIPath);
|
|
140
|
+
// 生成 docker-compose.yml
|
|
141
|
+
const dockerComposePath = path.join(outputDir, 'docker-compose.yml');
|
|
142
|
+
if (fs.existsSync(dockerComposePath)) {
|
|
143
|
+
// 文件已存在,询问用户是否覆盖
|
|
144
|
+
const inquirer = (await Promise.resolve().then(() => __importStar(require('inquirer')))).default;
|
|
145
|
+
const { overwrite } = await inquirer.prompt([
|
|
146
|
+
{
|
|
147
|
+
type: 'confirm',
|
|
148
|
+
name: 'overwrite',
|
|
149
|
+
message: '检测到现有 docker-compose.yml,是否覆盖?(覆盖后请检查配置一致性)',
|
|
150
|
+
default: false,
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
if (overwrite) {
|
|
154
|
+
const dockerComposeContent = this.generateDockerCompose(projectType, config);
|
|
155
|
+
fs.writeFileSync(dockerComposePath, dockerComposeContent, 'utf-8');
|
|
156
|
+
files.push(dockerComposePath);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.log('⚠️ 跳过生成 docker-compose.yml,保留现有文件');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const dockerComposeContent = this.generateDockerCompose(projectType, config);
|
|
164
|
+
fs.writeFileSync(dockerComposePath, dockerComposeContent, 'utf-8');
|
|
165
|
+
files.push(dockerComposePath);
|
|
166
|
+
}
|
|
121
167
|
// 生成 ci/deploy.sh
|
|
122
168
|
const deployScriptContent = this.generateDeployScript(config);
|
|
123
169
|
const deployScriptPath = path.join(ciDir, 'deploy.sh');
|
|
124
|
-
fs.writeFileSync(
|
|
170
|
+
fs.writeFileSync(deployScriptPath, deployScriptContent, 'utf-8');
|
|
125
171
|
// 添加执行权限
|
|
126
172
|
fs.chmodSync(deployScriptPath, '755');
|
|
127
173
|
files.push(deployScriptPath);
|
|
128
174
|
// 生成 ci/generate-env.sh
|
|
129
175
|
const envScriptContent = this.generateEnvScript(config);
|
|
130
176
|
const envScriptPath = path.join(ciDir, 'generate-env.sh');
|
|
131
|
-
fs.writeFileSync(
|
|
177
|
+
fs.writeFileSync(envScriptPath, envScriptContent, 'utf-8');
|
|
132
178
|
// 添加执行权限
|
|
133
179
|
fs.chmodSync(envScriptPath, '755');
|
|
134
180
|
files.push(envScriptPath);
|
|
@@ -155,16 +201,35 @@ class TemplateGenerator {
|
|
|
155
201
|
return result;
|
|
156
202
|
}
|
|
157
203
|
/**
|
|
158
|
-
*
|
|
204
|
+
* 根据项目类型和 Registry 类型获取模板名称
|
|
205
|
+
*/
|
|
206
|
+
getTemplateNameByType(projectType, registryType) {
|
|
207
|
+
// 基础模板映射
|
|
208
|
+
const baseTemplateMapping = {
|
|
209
|
+
nestjs: 'nestjs',
|
|
210
|
+
vue: 'vue',
|
|
211
|
+
react: 'vue', // React 使用和 Vue 相同的模板
|
|
212
|
+
node: 'nestjs', // 通用 Node.js 项目使用 NestJS 模板
|
|
213
|
+
};
|
|
214
|
+
const baseTemplate = baseTemplateMapping[projectType] || 'nestjs';
|
|
215
|
+
// 如果使用 GitLab Registry,使用专用模板
|
|
216
|
+
if (registryType === 'gitlab') {
|
|
217
|
+
return `${baseTemplate}-gitlab`;
|
|
218
|
+
}
|
|
219
|
+
// 其他类型(dockerhub, custom, none)使用标准模板
|
|
220
|
+
return baseTemplate;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 根据项目类型获取 docker-compose 模板名称
|
|
159
224
|
*/
|
|
160
|
-
|
|
161
|
-
const
|
|
225
|
+
getDockerComposeTemplate(projectType) {
|
|
226
|
+
const templateMapping = {
|
|
162
227
|
nestjs: 'nestjs',
|
|
163
228
|
vue: 'vue',
|
|
164
229
|
react: 'vue', // React 使用和 Vue 相同的模板
|
|
165
230
|
node: 'nestjs', // 通用 Node.js 项目使用 NestJS 模板
|
|
166
231
|
};
|
|
167
|
-
return
|
|
232
|
+
return templateMapping[projectType] || 'nestjs';
|
|
168
233
|
}
|
|
169
234
|
/**
|
|
170
235
|
* 根据项目类型获取 lint 命令
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
{{APP_NAME}}:
|
|
5
|
+
image: {{DOCKER_IMAGE}}:latest
|
|
6
|
+
container_name: {{APP_NAME}}
|
|
7
|
+
ports:
|
|
8
|
+
- "${APP_PORT}:${APP_PORT}"
|
|
9
|
+
env_file:
|
|
10
|
+
- .env
|
|
11
|
+
restart: unless-stopped
|
|
12
|
+
networks:
|
|
13
|
+
- app-network
|
|
14
|
+
|
|
15
|
+
networks:
|
|
16
|
+
app-network:
|
|
17
|
+
driver: bridge
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
{{APP_NAME}}:
|
|
5
|
+
image: {{DOCKER_IMAGE}}:latest
|
|
6
|
+
container_name: {{APP_NAME}}
|
|
7
|
+
ports:
|
|
8
|
+
- "${APP_PORT}:${APP_PORT}"
|
|
9
|
+
restart: unless-stopped
|
|
10
|
+
networks:
|
|
11
|
+
- app-network
|
|
12
|
+
|
|
13
|
+
networks:
|
|
14
|
+
app-network:
|
|
15
|
+
driver: bridge
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# GitLab CI/CD 配置文件 - NestJS 项目(GitLab Container Registry)
|
|
2
|
+
stages:
|
|
3
|
+
- lint
|
|
4
|
+
- build
|
|
5
|
+
- deploy
|
|
6
|
+
|
|
7
|
+
# 全局变量
|
|
8
|
+
variables:
|
|
9
|
+
DOCKER_DRIVER: overlay2
|
|
10
|
+
NODE_VERSION: "20"
|
|
11
|
+
APP_NAME: "{{APP_NAME}}"
|
|
12
|
+
GIT_STRATEGY: fetch
|
|
13
|
+
GIT_SSL_NO_VERIFY: "true"
|
|
14
|
+
DOCKER_PULL_POLICY: if-not-present
|
|
15
|
+
# 使用 GitLab Container Registry
|
|
16
|
+
DOCKER_IMAGE: $CI_REGISTRY_IMAGE
|
|
17
|
+
|
|
18
|
+
# 全局缓存配置
|
|
19
|
+
cache:
|
|
20
|
+
key: ${CI_COMMIT_REF_SLUG}-${CI_JOB_NAME}
|
|
21
|
+
paths:
|
|
22
|
+
- node_modules/
|
|
23
|
+
- .npm/
|
|
24
|
+
|
|
25
|
+
# 代码质量检查阶段
|
|
26
|
+
lint:
|
|
27
|
+
stage: lint
|
|
28
|
+
image:
|
|
29
|
+
name: node:20-alpine
|
|
30
|
+
pull_policy: if-not-present
|
|
31
|
+
cache:
|
|
32
|
+
key: ${CI_COMMIT_REF_SLUG}-node-modules
|
|
33
|
+
paths:
|
|
34
|
+
- node_modules/
|
|
35
|
+
- .npm/
|
|
36
|
+
policy: pull-push
|
|
37
|
+
before_script:
|
|
38
|
+
- apk add --no-cache git curl
|
|
39
|
+
- git --version
|
|
40
|
+
- echo "Setting up Git authentication..."
|
|
41
|
+
- git config --global http.sslVerify false
|
|
42
|
+
- git config --global url."https://oauth2:${GITLAB_ACCESS_TOKEN}@{{GITLAB_HOST}}".insteadOf "http://{{GITLAB_HOST}}"
|
|
43
|
+
- git config --global url."https://oauth2:${GITLAB_ACCESS_TOKEN}@{{GITLAB_HOST}}".insteadOf "https://{{GITLAB_HOST}}"
|
|
44
|
+
- npm ci --cache .npm --prefer-offline
|
|
45
|
+
script:
|
|
46
|
+
- {{LINT_COMMAND}}
|
|
47
|
+
only:
|
|
48
|
+
- merge_requests
|
|
49
|
+
- main
|
|
50
|
+
- develop
|
|
51
|
+
|
|
52
|
+
# 构建阶段(使用 GitLab Container Registry)
|
|
53
|
+
build:
|
|
54
|
+
stage: build
|
|
55
|
+
cache: {}
|
|
56
|
+
image:
|
|
57
|
+
name: docker:24.0.5
|
|
58
|
+
pull_policy: if-not-present
|
|
59
|
+
before_script:
|
|
60
|
+
- docker info
|
|
61
|
+
- echo "登录到 GitLab Container Registry..."
|
|
62
|
+
# 使用 GitLab CI 预定义变量登录
|
|
63
|
+
- echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
64
|
+
script:
|
|
65
|
+
- echo "尝试拉取最新镜像作为缓存..."
|
|
66
|
+
- docker pull $DOCKER_IMAGE:latest || true
|
|
67
|
+
- echo "构建Docker镜像..."
|
|
68
|
+
- docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_IMAGE:latest -t $DOCKER_IMAGE:$CI_COMMIT_SHA -t $DOCKER_IMAGE:latest .
|
|
69
|
+
- echo "推送镜像到 GitLab Container Registry..."
|
|
70
|
+
- docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
|
|
71
|
+
- docker push $DOCKER_IMAGE:latest
|
|
72
|
+
only:
|
|
73
|
+
- main
|
|
74
|
+
- develop
|
|
75
|
+
|
|
76
|
+
# 开发环境部署(develop 分支自动触发)
|
|
77
|
+
deploy_dev:
|
|
78
|
+
stage: deploy
|
|
79
|
+
image:
|
|
80
|
+
name: alpine:latest
|
|
81
|
+
pull_policy: if-not-present
|
|
82
|
+
variables:
|
|
83
|
+
ENV_TYPE: "development"
|
|
84
|
+
ENV_FILE_CONTENT: $DEV_ENV_FILE
|
|
85
|
+
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
86
|
+
APP_PORT: "{{DEV_PORT}}"
|
|
87
|
+
cache: {}
|
|
88
|
+
before_script:
|
|
89
|
+
- apk add --no-cache sshpass bash openssl rsync
|
|
90
|
+
- mkdir -p ~/.ssh
|
|
91
|
+
- chmod 700 ~/.ssh
|
|
92
|
+
# 使用密码认证,跳过 SSH 主机密钥检查
|
|
93
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
94
|
+
- chmod 600 ~/.ssh/config
|
|
95
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
96
|
+
- chmod 600 ~/.ssh/config
|
|
97
|
+
script:
|
|
98
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
99
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
100
|
+
- sshpass -p "$SSH_PASSWORD" rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$TEST_SERVER_HOST:$DEPLOY_DIR/
|
|
101
|
+
# 传递 GitLab Registry 凭据到部署服务器
|
|
102
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "export REGISTRY_URL='$CI_REGISTRY' && export REGISTRY_USERNAME='$CI_REGISTRY_USER' && export REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE gitlab"
|
|
103
|
+
environment:
|
|
104
|
+
name: development
|
|
105
|
+
url: {{DEV_URL}}
|
|
106
|
+
only:
|
|
107
|
+
- develop
|
|
108
|
+
|
|
109
|
+
# 生产环境部署(main 分支手动触发)
|
|
110
|
+
deploy_prod:
|
|
111
|
+
stage: deploy
|
|
112
|
+
image:
|
|
113
|
+
name: alpine:latest
|
|
114
|
+
pull_policy: if-not-present
|
|
115
|
+
variables:
|
|
116
|
+
ENV_TYPE: "production"
|
|
117
|
+
ENV_FILE_CONTENT: $PROD_ENV_FILE
|
|
118
|
+
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
119
|
+
APP_PORT: "{{PROD_PORT}}"
|
|
120
|
+
cache: {}
|
|
121
|
+
before_script:
|
|
122
|
+
- apk add --no-cache sshpass bash openssl rsync
|
|
123
|
+
- mkdir -p ~/.ssh
|
|
124
|
+
- chmod 700 ~/.ssh
|
|
125
|
+
# 使用密码认证,跳过 SSH 主机密钥检查
|
|
126
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
127
|
+
- chmod 600 ~/.ssh/config
|
|
128
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
129
|
+
- chmod 600 ~/.ssh/config
|
|
130
|
+
script:
|
|
131
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
132
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
133
|
+
- sshpass -p "$SSH_PASSWORD" rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$PROD_SERVER_HOST:$DEPLOY_DIR/
|
|
134
|
+
# 传递 GitLab Registry 凭据到部署服务器
|
|
135
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "export REGISTRY_URL='$CI_REGISTRY' && export REGISTRY_USERNAME='$CI_REGISTRY_USER' && export REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE gitlab"
|
|
136
|
+
environment:
|
|
137
|
+
name: production
|
|
138
|
+
url: {{PROD_URL}}
|
|
139
|
+
only:
|
|
140
|
+
- main
|
|
141
|
+
when: manual
|
|
142
|
+
|
|
143
|
+
# 回滚部署(可选)
|
|
144
|
+
rollback:
|
|
145
|
+
stage: deploy
|
|
146
|
+
image:
|
|
147
|
+
name: alpine:latest
|
|
148
|
+
pull_policy: if-not-present
|
|
149
|
+
variables:
|
|
150
|
+
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
151
|
+
APP_PORT: "{{PROD_PORT}}"
|
|
152
|
+
cache: {}
|
|
153
|
+
before_script:
|
|
154
|
+
- apk add --no-cache openssh-client openssl
|
|
155
|
+
- mkdir -p ~/.ssh
|
|
156
|
+
- chmod 700 ~/.ssh
|
|
157
|
+
# 使用密码认证,跳过 SSH 主机密钥检查
|
|
158
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
159
|
+
- chmod 600 ~/.ssh/config
|
|
160
|
+
- chmod 644 ~/.ssh/known_hosts
|
|
161
|
+
script:
|
|
162
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose down"
|
|
163
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose pull"
|
|
164
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose up -d"
|
|
165
|
+
environment:
|
|
166
|
+
name: production
|
|
167
|
+
url: {{PROD_URL}}
|
|
168
|
+
when: manual
|
|
169
|
+
only:
|
|
170
|
+
- main
|
|
@@ -83,21 +83,18 @@ deploy_dev:
|
|
|
83
83
|
ENV_FILE_CONTENT: $DEV_ENV_FILE
|
|
84
84
|
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
85
85
|
APP_PORT: "{{DEV_PORT}}"
|
|
86
|
+
REGISTRY_TYPE: "{{REGISTRY_TYPE}}"
|
|
86
87
|
cache: {}
|
|
87
88
|
before_script:
|
|
88
|
-
- apk add --no-cache
|
|
89
|
-
- eval $(ssh-agent -s)
|
|
89
|
+
- apk add --no-cache sshpass bash openssl rsync
|
|
90
90
|
- mkdir -p ~/.ssh
|
|
91
91
|
- chmod 700 ~/.ssh
|
|
92
|
-
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - 2>/dev/null || (echo "无法加载SSH密钥,尝试不同格式..." && echo "$SSH_PRIVATE_KEY" | base64 -d | ssh-add - 2>/dev/null || echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa && ssh-add ~/.ssh/id_rsa)
|
|
93
|
-
- ssh-keyscan $TEST_SERVER_HOST >> ~/.ssh/known_hosts
|
|
94
92
|
- chmod 644 ~/.ssh/known_hosts
|
|
95
93
|
script:
|
|
96
|
-
- ssh root@$TEST_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
97
|
-
- ssh root@$TEST_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
98
|
-
- rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$TEST_SERVER_HOST:$DEPLOY_DIR/
|
|
99
|
-
- ssh root@$TEST_SERVER_HOST "export DOCKER_HUB_USERNAME='$DOCKER_HUB_USERNAME' && export DOCKER_HUB_PASSWORD='$DOCKER_HUB_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE"
|
|
100
|
-
- ssh root@$TEST_SERVER_HOST "sleep 30 && curl -f http://localhost:$APP_PORT/api || exit 1"
|
|
94
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
95
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
96
|
+
- sshpass -p "$SSH_PASSWORD" rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$TEST_SERVER_HOST:$DEPLOY_DIR/
|
|
97
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "export DOCKER_HUB_USERNAME='$DOCKER_HUB_USERNAME' && export DOCKER_HUB_PASSWORD='$DOCKER_HUB_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE $REGISTRY_TYPE"
|
|
101
98
|
environment:
|
|
102
99
|
name: development
|
|
103
100
|
url: {{DEV_URL}}
|
|
@@ -115,21 +112,18 @@ deploy_prod:
|
|
|
115
112
|
ENV_FILE_CONTENT: $PROD_ENV_FILE
|
|
116
113
|
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
117
114
|
APP_PORT: "{{PROD_PORT}}"
|
|
115
|
+
REGISTRY_TYPE: "{{REGISTRY_TYPE}}"
|
|
118
116
|
cache: {}
|
|
119
117
|
before_script:
|
|
120
|
-
- apk add --no-cache
|
|
121
|
-
- eval $(ssh-agent -s)
|
|
118
|
+
- apk add --no-cache sshpass bash openssl rsync
|
|
122
119
|
- mkdir -p ~/.ssh
|
|
123
120
|
- chmod 700 ~/.ssh
|
|
124
|
-
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - 2>/dev/null || (echo "无法加载SSH密钥,尝试不同格式..." && echo "$SSH_PRIVATE_KEY" | base64 -d | ssh-add - 2>/dev/null || echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa && ssh-add ~/.ssh/id_rsa)
|
|
125
|
-
- ssh-keyscan $PROD_SERVER_HOST >> ~/.ssh/known_hosts
|
|
126
121
|
- chmod 644 ~/.ssh/known_hosts
|
|
127
122
|
script:
|
|
128
|
-
- ssh root@$PROD_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
129
|
-
- ssh root@$PROD_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
130
|
-
- rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$PROD_SERVER_HOST:$DEPLOY_DIR/
|
|
131
|
-
- ssh root@$PROD_SERVER_HOST "export DOCKER_HUB_USERNAME='$DOCKER_HUB_USERNAME' && export DOCKER_HUB_PASSWORD='$DOCKER_HUB_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE"
|
|
132
|
-
- ssh root@$PROD_SERVER_HOST "sleep 30 && curl -f http://localhost:$APP_PORT/api || exit 1"
|
|
123
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
124
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
125
|
+
- sshpass -p "$SSH_PASSWORD" rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$PROD_SERVER_HOST:$DEPLOY_DIR/
|
|
126
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "export DOCKER_HUB_USERNAME='$DOCKER_HUB_USERNAME' && export DOCKER_HUB_PASSWORD='$DOCKER_HUB_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE $REGISTRY_TYPE"
|
|
133
127
|
environment:
|
|
134
128
|
name: production
|
|
135
129
|
url: {{PROD_URL}}
|
|
@@ -149,16 +143,13 @@ rollback:
|
|
|
149
143
|
cache: {}
|
|
150
144
|
before_script:
|
|
151
145
|
- apk add --no-cache openssh-client openssl
|
|
152
|
-
- eval $(ssh-agent -s)
|
|
153
146
|
- mkdir -p ~/.ssh
|
|
154
147
|
- chmod 700 ~/.ssh
|
|
155
|
-
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - 2>/dev/null || (echo "无法加载SSH密钥,尝试不同格式..." && echo "$SSH_PRIVATE_KEY" | base64 -d | ssh-add - 2>/dev/null || echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa && ssh-add ~/.ssh/id_rsa)
|
|
156
|
-
- ssh-keyscan $PROD_SERVER_HOST >> ~/.ssh/known_hosts
|
|
157
148
|
- chmod 644 ~/.ssh/known_hosts
|
|
158
149
|
script:
|
|
159
|
-
- ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose down"
|
|
160
|
-
- ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose pull"
|
|
161
|
-
- ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose up -d"
|
|
150
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose down"
|
|
151
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose pull"
|
|
152
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose up -d"
|
|
162
153
|
environment:
|
|
163
154
|
name: production
|
|
164
155
|
url: {{PROD_URL}}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# GitLab CI/CD 配置文件 - Vue/React 项目(GitLab Container Registry)
|
|
2
|
+
stages:
|
|
3
|
+
- lint
|
|
4
|
+
- build
|
|
5
|
+
- deploy
|
|
6
|
+
|
|
7
|
+
# 全局变量
|
|
8
|
+
variables:
|
|
9
|
+
DOCKER_DRIVER: overlay2
|
|
10
|
+
NODE_VERSION: "20"
|
|
11
|
+
APP_NAME: "{{APP_NAME}}"
|
|
12
|
+
GIT_STRATEGY: fetch
|
|
13
|
+
GIT_SSL_NO_VERIFY: "true"
|
|
14
|
+
DOCKER_PULL_POLICY: if-not-present
|
|
15
|
+
# 使用 GitLab Container Registry
|
|
16
|
+
DOCKER_IMAGE: $CI_REGISTRY_IMAGE
|
|
17
|
+
|
|
18
|
+
# 全局缓存配置
|
|
19
|
+
cache:
|
|
20
|
+
key: ${CI_COMMIT_REF_SLUG}-${CI_JOB_NAME}
|
|
21
|
+
paths:
|
|
22
|
+
- node_modules/
|
|
23
|
+
- .npm/
|
|
24
|
+
|
|
25
|
+
# 代码质量检查阶段
|
|
26
|
+
lint:
|
|
27
|
+
stage: lint
|
|
28
|
+
image:
|
|
29
|
+
name: node:20-alpine
|
|
30
|
+
pull_policy: if-not-present
|
|
31
|
+
cache:
|
|
32
|
+
key: ${CI_COMMIT_REF_SLUG}-node-modules
|
|
33
|
+
paths:
|
|
34
|
+
- node_modules/
|
|
35
|
+
- .npm/
|
|
36
|
+
policy: pull-push
|
|
37
|
+
before_script:
|
|
38
|
+
- apk add --no-cache git curl
|
|
39
|
+
- git --version
|
|
40
|
+
- echo "Setting up Git authentication..."
|
|
41
|
+
- git config --global http.sslVerify false
|
|
42
|
+
- git config --global url."https://oauth2:${GITLAB_ACCESS_TOKEN}@{{GITLAB_HOST}}".insteadOf "http://{{GITLAB_HOST}}"
|
|
43
|
+
- git config --global url."https://oauth2:${GITLAB_ACCESS_TOKEN}@{{GITLAB_HOST}}".insteadOf "https://{{GITLAB_HOST}}"
|
|
44
|
+
- npm ci --cache .npm --prefer-offline
|
|
45
|
+
script:
|
|
46
|
+
- {{LINT_COMMAND}}
|
|
47
|
+
only:
|
|
48
|
+
- merge_requests
|
|
49
|
+
- main
|
|
50
|
+
- develop
|
|
51
|
+
|
|
52
|
+
# 构建阶段(使用 GitLab Container Registry)
|
|
53
|
+
build:
|
|
54
|
+
stage: build
|
|
55
|
+
cache: {}
|
|
56
|
+
image:
|
|
57
|
+
name: docker:24.0.5
|
|
58
|
+
pull_policy: if-not-present
|
|
59
|
+
before_script:
|
|
60
|
+
- docker info
|
|
61
|
+
- echo "登录到 GitLab Container Registry..."
|
|
62
|
+
# 使用 GitLab CI 预定义变量登录
|
|
63
|
+
- echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
64
|
+
script:
|
|
65
|
+
- echo "尝试拉取最新镜像作为缓存..."
|
|
66
|
+
- docker pull $DOCKER_IMAGE:latest || true
|
|
67
|
+
- echo "构建Docker镜像..."
|
|
68
|
+
- docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_IMAGE:latest -t $DOCKER_IMAGE:$CI_COMMIT_SHA -t $DOCKER_IMAGE:latest .
|
|
69
|
+
- echo "推送镜像到 GitLab Container Registry..."
|
|
70
|
+
- docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
|
|
71
|
+
- docker push $DOCKER_IMAGE:latest
|
|
72
|
+
only:
|
|
73
|
+
- main
|
|
74
|
+
- develop
|
|
75
|
+
|
|
76
|
+
# 开发环境部署(develop 分支自动触发)
|
|
77
|
+
deploy_dev:
|
|
78
|
+
stage: deploy
|
|
79
|
+
image:
|
|
80
|
+
name: alpine:latest
|
|
81
|
+
pull_policy: if-not-present
|
|
82
|
+
variables:
|
|
83
|
+
ENV_TYPE: "development"
|
|
84
|
+
ENV_FILE_CONTENT: $DEV_ENV_FILE
|
|
85
|
+
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
86
|
+
APP_PORT: "{{DEV_PORT}}"
|
|
87
|
+
cache: {}
|
|
88
|
+
before_script:
|
|
89
|
+
- apk add --no-cache sshpass bash openssl rsync
|
|
90
|
+
- mkdir -p ~/.ssh
|
|
91
|
+
- chmod 700 ~/.ssh
|
|
92
|
+
- # 使用密码认证,跳过 SSH 主机密钥检查
|
|
93
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
94
|
+
- chmod 600 ~/.ssh/config
|
|
95
|
+
- chmod 644 ~/.ssh/known_hosts
|
|
96
|
+
script:
|
|
97
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
98
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
99
|
+
- sshpass -p "$SSH_PASSWORD" rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$TEST_SERVER_HOST:$DEPLOY_DIR/
|
|
100
|
+
# 传递 GitLab Registry 凭据到部署服务器
|
|
101
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$TEST_SERVER_HOST "export REGISTRY_URL='$CI_REGISTRY' && export REGISTRY_USERNAME='$CI_REGISTRY_USER' && export REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE gitlab"
|
|
102
|
+
environment:
|
|
103
|
+
name: development
|
|
104
|
+
url: {{DEV_URL}}
|
|
105
|
+
only:
|
|
106
|
+
- develop
|
|
107
|
+
|
|
108
|
+
# 生产环境部署(main 分支手动触发)
|
|
109
|
+
deploy_prod:
|
|
110
|
+
stage: deploy
|
|
111
|
+
image:
|
|
112
|
+
name: alpine:latest
|
|
113
|
+
pull_policy: if-not-present
|
|
114
|
+
variables:
|
|
115
|
+
ENV_TYPE: "production"
|
|
116
|
+
ENV_FILE_CONTENT: $PROD_ENV_FILE
|
|
117
|
+
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
118
|
+
APP_PORT: "{{PROD_PORT}}"
|
|
119
|
+
cache: {}
|
|
120
|
+
before_script:
|
|
121
|
+
- apk add --no-cache sshpass bash openssl rsync
|
|
122
|
+
- mkdir -p ~/.ssh
|
|
123
|
+
- chmod 700 ~/.ssh
|
|
124
|
+
- # 使用密码认证,跳过 SSH 主机密钥检查
|
|
125
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
126
|
+
- chmod 600 ~/.ssh/config
|
|
127
|
+
- chmod 644 ~/.ssh/known_hosts
|
|
128
|
+
script:
|
|
129
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "mkdir -p $DEPLOY_DIR"
|
|
130
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "which rsync || apk add --no-cache rsync"
|
|
131
|
+
- sshpass -p "$SSH_PASSWORD" rsync -avz --delete --exclude='.git/' --exclude='node_modules/' . root@$PROD_SERVER_HOST:$DEPLOY_DIR/
|
|
132
|
+
# 传递 GitLab Registry 凭据到部署服务器
|
|
133
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "export REGISTRY_URL='$CI_REGISTRY' && export REGISTRY_USERNAME='$CI_REGISTRY_USER' && export REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD' && export APP_PORT='$APP_PORT' && cd $DEPLOY_DIR && chmod +x ci/deploy.sh && bash ci/deploy.sh $ENV_TYPE gitlab"
|
|
134
|
+
environment:
|
|
135
|
+
name: production
|
|
136
|
+
url: {{PROD_URL}}
|
|
137
|
+
only:
|
|
138
|
+
- main
|
|
139
|
+
when: manual
|
|
140
|
+
|
|
141
|
+
# 回滚部署(可选)
|
|
142
|
+
rollback:
|
|
143
|
+
stage: deploy
|
|
144
|
+
image:
|
|
145
|
+
name: alpine:latest
|
|
146
|
+
pull_policy: if-not-present
|
|
147
|
+
variables:
|
|
148
|
+
DEPLOY_DIR: "{{DEPLOY_DIR}}"
|
|
149
|
+
APP_PORT: "{{PROD_PORT}}"
|
|
150
|
+
cache: {}
|
|
151
|
+
before_script:
|
|
152
|
+
- apk add --no-cache openssh-client openssl
|
|
153
|
+
- mkdir -p ~/.ssh
|
|
154
|
+
- chmod 700 ~/.ssh
|
|
155
|
+
- # 使用密码认证,跳过 SSH 主机密钥检查
|
|
156
|
+
- echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
|
157
|
+
- chmod 600 ~/.ssh/config
|
|
158
|
+
- chmod 644 ~/.ssh/known_hosts
|
|
159
|
+
script:
|
|
160
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose down"
|
|
161
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose pull"
|
|
162
|
+
- sshpass -p "$SSH_PASSWORD" ssh root@$PROD_SERVER_HOST "cd $DEPLOY_DIR && export APP_PORT=$APP_PORT && docker-compose up -d"
|
|
163
|
+
environment:
|
|
164
|
+
name: production
|
|
165
|
+
url: {{PROD_URL}}
|
|
166
|
+
when: manual
|
|
167
|
+
only:
|
|
168
|
+
- main
|