class-ai-agent 1.2.0
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/.claude/CLAUDE.md +155 -0
- package/.claude/agents/backend.md +395 -0
- package/.claude/agents/code-reviewer.md +110 -0
- package/.claude/agents/copywriter-seo.md +236 -0
- package/.claude/agents/frontend.md +384 -0
- package/.claude/agents/project-manager.md +201 -0
- package/.claude/agents/qa.md +221 -0
- package/.claude/agents/security-auditor.md +143 -0
- package/.claude/agents/systems-architect.md +211 -0
- package/.claude/agents/test-engineer.md +123 -0
- package/.claude/agents/ui-ux-designer.md +210 -0
- package/.claude/commands/build.md +132 -0
- package/.claude/commands/debug.md +242 -0
- package/.claude/commands/deploy.md +40 -0
- package/.claude/commands/fix-issue.md +42 -0
- package/.claude/commands/plan.md +125 -0
- package/.claude/commands/review.md +50 -0
- package/.claude/commands/simplify.md +222 -0
- package/.claude/commands/spec.md +95 -0
- package/.claude/commands/test.md +214 -0
- package/.claude/references/accessibility-checklist.md +174 -0
- package/.claude/references/performance-checklist.md +150 -0
- package/.claude/references/security-checklist.md +94 -0
- package/.claude/references/testing-patterns.md +183 -0
- package/.claude/rules/api-conventions.md +79 -0
- package/.claude/rules/clean-code.md +205 -0
- package/.claude/rules/code-style.md +86 -0
- package/.claude/rules/database.md +60 -0
- package/.claude/rules/error-handling.md +92 -0
- package/.claude/rules/git-workflow.md +77 -0
- package/.claude/rules/monitoring.md +311 -0
- package/.claude/rules/naming-conventions.md +260 -0
- package/.claude/rules/project-structure.md +65 -0
- package/.claude/rules/security.md +90 -0
- package/.claude/rules/system-design.md +162 -0
- package/.claude/rules/tech-stack.md +456 -0
- package/.claude/rules/testing.md +104 -0
- package/.claude/settings.json +14 -0
- package/.claude/skills/code-review/SKILL.md +208 -0
- package/.claude/skills/deploy/SKILL.md +68 -0
- package/.claude/skills/deploy/deploy.md +735 -0
- package/.claude/skills/incremental-implementation/SKILL.md +210 -0
- package/.claude/skills/security-review/SKILL.md +71 -0
- package/.claude/skills/tdd/SKILL.md +217 -0
- package/.cursor/CURSOR.md +112 -0
- package/.cursor/agents/backend.md +395 -0
- package/.cursor/agents/code-reviewer.md +110 -0
- package/.cursor/agents/copywriter-seo.md +236 -0
- package/.cursor/agents/frontend.md +384 -0
- package/.cursor/agents/project-manager.md +201 -0
- package/.cursor/agents/qa.md +221 -0
- package/.cursor/agents/security-auditor.md +143 -0
- package/.cursor/agents/systems-architect.md +211 -0
- package/.cursor/agents/test-engineer.md +123 -0
- package/.cursor/agents/ui-ux-designer.md +210 -0
- package/.cursor/commands/build.md +132 -0
- package/.cursor/commands/debug.md +242 -0
- package/.cursor/commands/deploy.md +40 -0
- package/.cursor/commands/fix-issue.md +42 -0
- package/.cursor/commands/plan.md +125 -0
- package/.cursor/commands/review.md +50 -0
- package/.cursor/commands/simplify.md +222 -0
- package/.cursor/commands/spec.md +95 -0
- package/.cursor/commands/test.md +214 -0
- package/.cursor/references/accessibility-checklist.md +174 -0
- package/.cursor/references/performance-checklist.md +150 -0
- package/.cursor/references/security-checklist.md +94 -0
- package/.cursor/references/testing-patterns.md +183 -0
- package/.cursor/rules/api-conventions.mdc +85 -0
- package/.cursor/rules/clean-code.mdc +211 -0
- package/.cursor/rules/code-style.mdc +92 -0
- package/.cursor/rules/cursor-overview.mdc +35 -0
- package/.cursor/rules/database.mdc +66 -0
- package/.cursor/rules/error-handling.mdc +98 -0
- package/.cursor/rules/git-workflow.mdc +83 -0
- package/.cursor/rules/monitoring.mdc +317 -0
- package/.cursor/rules/naming-conventions.mdc +266 -0
- package/.cursor/rules/project-structure.mdc +71 -0
- package/.cursor/rules/security.mdc +95 -0
- package/.cursor/rules/system-design.mdc +168 -0
- package/.cursor/rules/tech-stack.mdc +462 -0
- package/.cursor/rules/testing.mdc +110 -0
- package/.cursor/settings.json +8 -0
- package/.cursor/skills/code-review/SKILL.md +208 -0
- package/.cursor/skills/deploy/SKILL.md +68 -0
- package/.cursor/skills/deploy/deploy.md +735 -0
- package/.cursor/skills/incremental-implementation/SKILL.md +210 -0
- package/.cursor/skills/security-review/SKILL.md +71 -0
- package/.cursor/skills/tdd/SKILL.md +217 -0
- package/AGENTS.md +11 -0
- package/README.md +405 -0
- package/bin/class-ai-agent.cjs +176 -0
- package/package.json +38 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
# Deployment Workflow
|
|
2
|
+
|
|
3
|
+
> Quy trình CI/CD hoàn chỉnh từ development đến production.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture Overview
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────┐ push ┌──────────────────────────────────────────────────────────┐
|
|
11
|
+
│ LOCAL │ ────────────► │ GITHUB │
|
|
12
|
+
│ │ │ │
|
|
13
|
+
│ Dev │ │ dev branch │
|
|
14
|
+
│ Claude Code │ │ │ │
|
|
15
|
+
│ + IDE │ │ ▼ PR (dev → main) │
|
|
16
|
+
│ │ │ ┌─────────────────┐ │
|
|
17
|
+
└─────────────┘ │ │ ci.yml — PR gate│ │
|
|
18
|
+
│ │ ✅ Test Backend │ │
|
|
19
|
+
│ │ ✅ Test Frontend │ │
|
|
20
|
+
│ └────────┬────────┘ │
|
|
21
|
+
│ │ must pass │
|
|
22
|
+
│ ▼ │
|
|
23
|
+
│ ┌─────────────────────┐ │
|
|
24
|
+
│ │ Branch Protection │ │
|
|
25
|
+
│ │ ✅ Require PR │ │
|
|
26
|
+
│ │ ✅ CI must pass │ │
|
|
27
|
+
│ │ ✅ No force push │ │
|
|
28
|
+
│ │ ✅ Enforce admins │ │
|
|
29
|
+
│ └────────┬────────────┘ │
|
|
30
|
+
│ │ merge │
|
|
31
|
+
│ ▼ │
|
|
32
|
+
│ main branch ─────────────────────────────────────► │
|
|
33
|
+
└──────────────────────────────────────────────────────────┘
|
|
34
|
+
│
|
|
35
|
+
┌────────────────────────────────────────┘
|
|
36
|
+
▼
|
|
37
|
+
┌─────────────────────────────────┐
|
|
38
|
+
│ deploy.yml — triggers on main │
|
|
39
|
+
│ │
|
|
40
|
+
│ ① Version: v1.21.6 (auto) │ ✅
|
|
41
|
+
│ ② Test Backend │ ✅
|
|
42
|
+
│ ③ Test Frontend │ ✅
|
|
43
|
+
│ ④ Build Backend → GHCR │ ✅
|
|
44
|
+
│ ⑤ Build Frontend → GHCR │ ✅
|
|
45
|
+
│ ⑥ Git Tag v1.21.6 │ ✅
|
|
46
|
+
│ ⑦ Deploy VPS (pull + up) │ ✅
|
|
47
|
+
│ ⑧ Telegram notify │ ✅
|
|
48
|
+
└─────────────────────────────────┘
|
|
49
|
+
│
|
|
50
|
+
┌─────────────────┼─────────────────┐
|
|
51
|
+
▼ push images │ ▼ git tag
|
|
52
|
+
┌───────────────────────────────────────────────────────────────────────────────┐
|
|
53
|
+
│ GHCR REGISTRY │
|
|
54
|
+
│ │
|
|
55
|
+
│ ┌─────────────────────────────────────┐ ┌─────────────────────────────────┐ │
|
|
56
|
+
│ │ runway-backend │ │ runway-frontend │ │
|
|
57
|
+
│ │ v1.21.6 ✅ v1.21.5 v1.21.4 latest │ │ v1.21.6 ✅ v1.21.5 v1.21.4 │ │
|
|
58
|
+
│ └─────────────────────────────────────┘ └─────────────────────────────────┘ │
|
|
59
|
+
│ │
|
|
60
|
+
│ Rollback (30s): TAG=v1.21.5 docker compose up -d │
|
|
61
|
+
└───────────────────────────────────────────────────────────────────────────────┘
|
|
62
|
+
│
|
|
63
|
+
▼ docker pull v1.21.6
|
|
64
|
+
┌───────────────────────────────────────────────────────────────────────────────┐
|
|
65
|
+
│ VPS │
|
|
66
|
+
│ │
|
|
67
|
+
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
68
|
+
│ │ Nginx (SSL) │ │
|
|
69
|
+
│ │ domain1 → :8000 domain2 → :3001 domain3 → :3000 domain4 → :5678│ │
|
|
70
|
+
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
71
|
+
│ │
|
|
72
|
+
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
73
|
+
│ │ Docker Containers │ │
|
|
74
|
+
│ │ runway-backend:v1.21.6 ✅ runway-worker:v1.21.6 ✅ n8n:latest ✅ │ │
|
|
75
|
+
│ │ runway-frontend:v1.21.6 ✅ postgres:15 ✅ │ │
|
|
76
|
+
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
77
|
+
│ │
|
|
78
|
+
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
79
|
+
│ │ Systemd │ │
|
|
80
|
+
│ │ Redis (127.0.0.1 + 172.17.0.1) ✅ Marketing Next.js :3000 ✅ │ │
|
|
81
|
+
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
82
|
+
└───────────────────────────────────────────────────────────────────────────────┘
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 1. Local Development
|
|
88
|
+
|
|
89
|
+
### Setup
|
|
90
|
+
```bash
|
|
91
|
+
# Clone repository
|
|
92
|
+
git clone git@github.com:org/project.git
|
|
93
|
+
cd project
|
|
94
|
+
|
|
95
|
+
# Install dependencies
|
|
96
|
+
npm install
|
|
97
|
+
|
|
98
|
+
# Setup environment
|
|
99
|
+
cp .env.example .env
|
|
100
|
+
|
|
101
|
+
# Start development
|
|
102
|
+
npm run dev
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Development Flow
|
|
106
|
+
1. Checkout từ `main` hoặc `dev` branch
|
|
107
|
+
2. Tạo feature branch: `feature/add-user-auth`
|
|
108
|
+
3. Code + test locally
|
|
109
|
+
4. Push lên GitHub
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git checkout -b feature/add-user-auth
|
|
113
|
+
# ... coding ...
|
|
114
|
+
git add .
|
|
115
|
+
git commit -m "feat(auth): add JWT authentication"
|
|
116
|
+
git push origin feature/add-user-auth
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 2. GitHub CI/CD
|
|
122
|
+
|
|
123
|
+
### 2.1 Branch Strategy
|
|
124
|
+
|
|
125
|
+
| Branch | Purpose | Protection |
|
|
126
|
+
|--------|---------|------------|
|
|
127
|
+
| `main` | Production code | Full protection |
|
|
128
|
+
| `dev` | Integration branch | PR required |
|
|
129
|
+
| `feature/*` | New features | None |
|
|
130
|
+
| `fix/*` | Bug fixes | None |
|
|
131
|
+
| `hotfix/*` | Urgent fixes | Direct to main via PR |
|
|
132
|
+
|
|
133
|
+
### 2.2 Branch Protection Rules (main)
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
# Settings → Branches → Branch protection rules
|
|
137
|
+
|
|
138
|
+
✅ Require a pull request before merging
|
|
139
|
+
✅ Require approvals (1+)
|
|
140
|
+
✅ Dismiss stale pull request approvals when new commits are pushed
|
|
141
|
+
|
|
142
|
+
✅ Require status checks to pass before merging
|
|
143
|
+
✅ Require branches to be up to date before merging
|
|
144
|
+
- test-backend (required)
|
|
145
|
+
- test-frontend (required)
|
|
146
|
+
|
|
147
|
+
✅ Do not allow force pushes
|
|
148
|
+
|
|
149
|
+
✅ Do not allow deletions
|
|
150
|
+
|
|
151
|
+
✅ Include administrators
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 2.3 CI Workflow (ci.yml)
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
# .github/workflows/ci.yml
|
|
158
|
+
name: CI
|
|
159
|
+
|
|
160
|
+
on:
|
|
161
|
+
pull_request:
|
|
162
|
+
branches: [main, dev]
|
|
163
|
+
|
|
164
|
+
jobs:
|
|
165
|
+
test-backend:
|
|
166
|
+
runs-on: ubuntu-latest
|
|
167
|
+
steps:
|
|
168
|
+
- uses: actions/checkout@v4
|
|
169
|
+
|
|
170
|
+
- name: Setup Node.js
|
|
171
|
+
uses: actions/setup-node@v4
|
|
172
|
+
with:
|
|
173
|
+
node-version: '20'
|
|
174
|
+
cache: 'npm'
|
|
175
|
+
cache-dependency-path: backend/package-lock.json
|
|
176
|
+
|
|
177
|
+
- name: Install dependencies
|
|
178
|
+
run: npm ci
|
|
179
|
+
working-directory: backend
|
|
180
|
+
|
|
181
|
+
- name: Run linter
|
|
182
|
+
run: npm run lint
|
|
183
|
+
working-directory: backend
|
|
184
|
+
|
|
185
|
+
- name: Run tests
|
|
186
|
+
run: npm test -- --coverage
|
|
187
|
+
working-directory: backend
|
|
188
|
+
|
|
189
|
+
- name: Upload coverage
|
|
190
|
+
uses: codecov/codecov-action@v4
|
|
191
|
+
with:
|
|
192
|
+
directory: backend/coverage
|
|
193
|
+
|
|
194
|
+
test-frontend:
|
|
195
|
+
runs-on: ubuntu-latest
|
|
196
|
+
steps:
|
|
197
|
+
- uses: actions/checkout@v4
|
|
198
|
+
|
|
199
|
+
- name: Setup Node.js
|
|
200
|
+
uses: actions/setup-node@v4
|
|
201
|
+
with:
|
|
202
|
+
node-version: '20'
|
|
203
|
+
cache: 'npm'
|
|
204
|
+
cache-dependency-path: frontend/package-lock.json
|
|
205
|
+
|
|
206
|
+
- name: Install dependencies
|
|
207
|
+
run: npm ci
|
|
208
|
+
working-directory: frontend
|
|
209
|
+
|
|
210
|
+
- name: Run linter
|
|
211
|
+
run: npm run lint
|
|
212
|
+
working-directory: frontend
|
|
213
|
+
|
|
214
|
+
- name: Run tests
|
|
215
|
+
run: npm test -- --coverage
|
|
216
|
+
working-directory: frontend
|
|
217
|
+
|
|
218
|
+
- name: Build check
|
|
219
|
+
run: npm run build
|
|
220
|
+
working-directory: frontend
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 2.4 Deploy Workflow (deploy.yml)
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
# .github/workflows/deploy.yml
|
|
227
|
+
name: Deploy
|
|
228
|
+
|
|
229
|
+
on:
|
|
230
|
+
push:
|
|
231
|
+
branches: [main]
|
|
232
|
+
|
|
233
|
+
env:
|
|
234
|
+
REGISTRY: ghcr.io
|
|
235
|
+
BACKEND_IMAGE: ghcr.io/${{ github.repository }}/runway-backend
|
|
236
|
+
FRONTEND_IMAGE: ghcr.io/${{ github.repository }}/runway-frontend
|
|
237
|
+
|
|
238
|
+
jobs:
|
|
239
|
+
# ① Auto Version
|
|
240
|
+
version:
|
|
241
|
+
runs-on: ubuntu-latest
|
|
242
|
+
outputs:
|
|
243
|
+
version: ${{ steps.version.outputs.version }}
|
|
244
|
+
steps:
|
|
245
|
+
- uses: actions/checkout@v4
|
|
246
|
+
with:
|
|
247
|
+
fetch-depth: 0
|
|
248
|
+
|
|
249
|
+
- name: Get next version
|
|
250
|
+
id: version
|
|
251
|
+
run: |
|
|
252
|
+
# Get latest tag
|
|
253
|
+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.0.0")
|
|
254
|
+
|
|
255
|
+
# Increment patch version
|
|
256
|
+
VERSION=$(echo $LATEST_TAG | awk -F. '{print $1"."$2"."$3+1}')
|
|
257
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
258
|
+
echo "📦 Version: $VERSION"
|
|
259
|
+
|
|
260
|
+
# ② Test Backend
|
|
261
|
+
test-backend:
|
|
262
|
+
runs-on: ubuntu-latest
|
|
263
|
+
steps:
|
|
264
|
+
- uses: actions/checkout@v4
|
|
265
|
+
- uses: actions/setup-node@v4
|
|
266
|
+
with:
|
|
267
|
+
node-version: '20'
|
|
268
|
+
cache: 'npm'
|
|
269
|
+
cache-dependency-path: backend/package-lock.json
|
|
270
|
+
- run: npm ci
|
|
271
|
+
working-directory: backend
|
|
272
|
+
- run: npm test
|
|
273
|
+
working-directory: backend
|
|
274
|
+
|
|
275
|
+
# ③ Test Frontend
|
|
276
|
+
test-frontend:
|
|
277
|
+
runs-on: ubuntu-latest
|
|
278
|
+
steps:
|
|
279
|
+
- uses: actions/checkout@v4
|
|
280
|
+
- uses: actions/setup-node@v4
|
|
281
|
+
with:
|
|
282
|
+
node-version: '20'
|
|
283
|
+
cache: 'npm'
|
|
284
|
+
cache-dependency-path: frontend/package-lock.json
|
|
285
|
+
- run: npm ci
|
|
286
|
+
working-directory: frontend
|
|
287
|
+
- run: npm test
|
|
288
|
+
working-directory: frontend
|
|
289
|
+
|
|
290
|
+
# ④ Build Backend → GHCR
|
|
291
|
+
build-backend:
|
|
292
|
+
needs: [version, test-backend]
|
|
293
|
+
runs-on: ubuntu-latest
|
|
294
|
+
permissions:
|
|
295
|
+
contents: read
|
|
296
|
+
packages: write
|
|
297
|
+
steps:
|
|
298
|
+
- uses: actions/checkout@v4
|
|
299
|
+
|
|
300
|
+
- name: Login to GHCR
|
|
301
|
+
uses: docker/login-action@v3
|
|
302
|
+
with:
|
|
303
|
+
registry: ${{ env.REGISTRY }}
|
|
304
|
+
username: ${{ github.actor }}
|
|
305
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
306
|
+
|
|
307
|
+
- name: Build and push
|
|
308
|
+
uses: docker/build-push-action@v5
|
|
309
|
+
with:
|
|
310
|
+
context: ./backend
|
|
311
|
+
push: true
|
|
312
|
+
tags: |
|
|
313
|
+
${{ env.BACKEND_IMAGE }}:${{ needs.version.outputs.version }}
|
|
314
|
+
${{ env.BACKEND_IMAGE }}:latest
|
|
315
|
+
cache-from: type=registry,ref=${{ env.BACKEND_IMAGE }}:buildcache
|
|
316
|
+
cache-to: type=registry,ref=${{ env.BACKEND_IMAGE }}:buildcache,mode=max
|
|
317
|
+
|
|
318
|
+
# ⑤ Build Frontend → GHCR
|
|
319
|
+
build-frontend:
|
|
320
|
+
needs: [version, test-frontend]
|
|
321
|
+
runs-on: ubuntu-latest
|
|
322
|
+
permissions:
|
|
323
|
+
contents: read
|
|
324
|
+
packages: write
|
|
325
|
+
steps:
|
|
326
|
+
- uses: actions/checkout@v4
|
|
327
|
+
|
|
328
|
+
- name: Login to GHCR
|
|
329
|
+
uses: docker/login-action@v3
|
|
330
|
+
with:
|
|
331
|
+
registry: ${{ env.REGISTRY }}
|
|
332
|
+
username: ${{ github.actor }}
|
|
333
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
334
|
+
|
|
335
|
+
- name: Build and push
|
|
336
|
+
uses: docker/build-push-action@v5
|
|
337
|
+
with:
|
|
338
|
+
context: ./frontend
|
|
339
|
+
push: true
|
|
340
|
+
tags: |
|
|
341
|
+
${{ env.FRONTEND_IMAGE }}:${{ needs.version.outputs.version }}
|
|
342
|
+
${{ env.FRONTEND_IMAGE }}:latest
|
|
343
|
+
build-args: |
|
|
344
|
+
NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }}
|
|
345
|
+
cache-from: type=registry,ref=${{ env.FRONTEND_IMAGE }}:buildcache
|
|
346
|
+
cache-to: type=registry,ref=${{ env.FRONTEND_IMAGE }}:buildcache,mode=max
|
|
347
|
+
|
|
348
|
+
# ⑥ Git Tag
|
|
349
|
+
tag:
|
|
350
|
+
needs: [version, build-backend, build-frontend]
|
|
351
|
+
runs-on: ubuntu-latest
|
|
352
|
+
permissions:
|
|
353
|
+
contents: write
|
|
354
|
+
steps:
|
|
355
|
+
- uses: actions/checkout@v4
|
|
356
|
+
|
|
357
|
+
- name: Create and push tag
|
|
358
|
+
run: |
|
|
359
|
+
git config user.name "github-actions[bot]"
|
|
360
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
361
|
+
git tag -a ${{ needs.version.outputs.version }} -m "Release ${{ needs.version.outputs.version }}"
|
|
362
|
+
git push origin ${{ needs.version.outputs.version }}
|
|
363
|
+
|
|
364
|
+
# ⑦ Deploy VPS
|
|
365
|
+
deploy:
|
|
366
|
+
needs: [version, build-backend, build-frontend, tag]
|
|
367
|
+
runs-on: ubuntu-latest
|
|
368
|
+
steps:
|
|
369
|
+
- name: Deploy to VPS via SSH
|
|
370
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
371
|
+
with:
|
|
372
|
+
host: ${{ secrets.VPS_HOST }}
|
|
373
|
+
username: ${{ secrets.VPS_USER }}
|
|
374
|
+
key: ${{ secrets.VPS_SSH_KEY }}
|
|
375
|
+
script: |
|
|
376
|
+
cd /opt/app
|
|
377
|
+
|
|
378
|
+
# Login to GHCR
|
|
379
|
+
echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
380
|
+
|
|
381
|
+
# Pull new images
|
|
382
|
+
export TAG=${{ needs.version.outputs.version }}
|
|
383
|
+
docker compose pull
|
|
384
|
+
|
|
385
|
+
# Deploy with zero-downtime
|
|
386
|
+
docker compose up -d --remove-orphans
|
|
387
|
+
|
|
388
|
+
# Cleanup old images
|
|
389
|
+
docker image prune -f
|
|
390
|
+
|
|
391
|
+
# Health check
|
|
392
|
+
sleep 10
|
|
393
|
+
curl -f http://localhost:8000/health || exit 1
|
|
394
|
+
|
|
395
|
+
# ⑧ Telegram Notify
|
|
396
|
+
notify:
|
|
397
|
+
needs: [version, deploy]
|
|
398
|
+
runs-on: ubuntu-latest
|
|
399
|
+
if: always()
|
|
400
|
+
steps:
|
|
401
|
+
- name: Send Telegram notification
|
|
402
|
+
uses: appleboy/telegram-action@master
|
|
403
|
+
with:
|
|
404
|
+
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
|
405
|
+
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
406
|
+
format: markdown
|
|
407
|
+
message: |
|
|
408
|
+
${{ needs.deploy.result == 'success' && '✅' || '❌' }} *Deploy ${{ needs.version.outputs.version }}*
|
|
409
|
+
|
|
410
|
+
Repository: `${{ github.repository }}`
|
|
411
|
+
Branch: `${{ github.ref_name }}`
|
|
412
|
+
Commit: `${{ github.sha }}`
|
|
413
|
+
|
|
414
|
+
Status: *${{ needs.deploy.result }}*
|
|
415
|
+
|
|
416
|
+
[View Action](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 3. GHCR Registry
|
|
422
|
+
|
|
423
|
+
### Image Naming Convention
|
|
424
|
+
```
|
|
425
|
+
ghcr.io/{owner}/{repo}/{service}:{version}
|
|
426
|
+
|
|
427
|
+
# Examples
|
|
428
|
+
ghcr.io/myorg/myproject/runway-backend:v1.21.6
|
|
429
|
+
ghcr.io/myorg/myproject/runway-frontend:v1.21.6
|
|
430
|
+
ghcr.io/myorg/myproject/runway-worker:v1.21.6
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Image Tags
|
|
434
|
+
| Tag | Description |
|
|
435
|
+
|-----|-------------|
|
|
436
|
+
| `v1.21.6` | Specific version (immutable) |
|
|
437
|
+
| `latest` | Latest stable release |
|
|
438
|
+
| `main` | Latest from main branch |
|
|
439
|
+
|
|
440
|
+
### Manual Rollback (30 seconds)
|
|
441
|
+
```bash
|
|
442
|
+
# SSH vào VPS
|
|
443
|
+
ssh user@vps
|
|
444
|
+
|
|
445
|
+
# Rollback to previous version
|
|
446
|
+
cd /opt/app
|
|
447
|
+
export TAG=v1.21.5
|
|
448
|
+
docker compose up -d
|
|
449
|
+
|
|
450
|
+
# Verify rollback
|
|
451
|
+
docker ps
|
|
452
|
+
curl http://localhost:8000/health
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## 4. VPS Infrastructure
|
|
458
|
+
|
|
459
|
+
### 4.1 Directory Structure
|
|
460
|
+
```
|
|
461
|
+
/opt/app/
|
|
462
|
+
├── docker-compose.yml
|
|
463
|
+
├── .env
|
|
464
|
+
├── nginx/
|
|
465
|
+
│ ├── nginx.conf
|
|
466
|
+
│ └── conf.d/
|
|
467
|
+
│ ├── backend.conf
|
|
468
|
+
│ ├── frontend.conf
|
|
469
|
+
│ └── n8n.conf
|
|
470
|
+
├── data/
|
|
471
|
+
│ ├── postgres/
|
|
472
|
+
│ └── redis/
|
|
473
|
+
└── logs/
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 4.2 Docker Compose
|
|
477
|
+
```yaml
|
|
478
|
+
# docker-compose.yml
|
|
479
|
+
version: '3.8'
|
|
480
|
+
|
|
481
|
+
services:
|
|
482
|
+
backend:
|
|
483
|
+
image: ghcr.io/myorg/myproject/runway-backend:${TAG:-latest}
|
|
484
|
+
container_name: runway-backend
|
|
485
|
+
restart: unless-stopped
|
|
486
|
+
ports:
|
|
487
|
+
- "8000:8000"
|
|
488
|
+
environment:
|
|
489
|
+
- NODE_ENV=production
|
|
490
|
+
- DATABASE_URL=${DATABASE_URL}
|
|
491
|
+
- REDIS_URL=${REDIS_URL}
|
|
492
|
+
depends_on:
|
|
493
|
+
- postgres
|
|
494
|
+
healthcheck:
|
|
495
|
+
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
|
496
|
+
interval: 30s
|
|
497
|
+
timeout: 10s
|
|
498
|
+
retries: 3
|
|
499
|
+
|
|
500
|
+
worker:
|
|
501
|
+
image: ghcr.io/myorg/myproject/runway-backend:${TAG:-latest}
|
|
502
|
+
container_name: runway-worker
|
|
503
|
+
restart: unless-stopped
|
|
504
|
+
command: ["npm", "run", "worker"]
|
|
505
|
+
environment:
|
|
506
|
+
- NODE_ENV=production
|
|
507
|
+
- DATABASE_URL=${DATABASE_URL}
|
|
508
|
+
- REDIS_URL=${REDIS_URL}
|
|
509
|
+
depends_on:
|
|
510
|
+
- backend
|
|
511
|
+
- redis
|
|
512
|
+
|
|
513
|
+
frontend:
|
|
514
|
+
image: ghcr.io/myorg/myproject/runway-frontend:${TAG:-latest}
|
|
515
|
+
container_name: runway-frontend
|
|
516
|
+
restart: unless-stopped
|
|
517
|
+
ports:
|
|
518
|
+
- "3000:3000"
|
|
519
|
+
environment:
|
|
520
|
+
- NODE_ENV=production
|
|
521
|
+
|
|
522
|
+
postgres:
|
|
523
|
+
image: postgres:15
|
|
524
|
+
container_name: postgres
|
|
525
|
+
restart: unless-stopped
|
|
526
|
+
volumes:
|
|
527
|
+
- ./data/postgres:/var/lib/postgresql/data
|
|
528
|
+
environment:
|
|
529
|
+
- POSTGRES_USER=${DB_USER}
|
|
530
|
+
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
531
|
+
- POSTGRES_DB=${DB_NAME}
|
|
532
|
+
healthcheck:
|
|
533
|
+
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
|
|
534
|
+
interval: 10s
|
|
535
|
+
timeout: 5s
|
|
536
|
+
retries: 5
|
|
537
|
+
|
|
538
|
+
n8n:
|
|
539
|
+
image: n8nio/n8n:latest
|
|
540
|
+
container_name: n8n
|
|
541
|
+
restart: unless-stopped
|
|
542
|
+
ports:
|
|
543
|
+
- "5678:5678"
|
|
544
|
+
volumes:
|
|
545
|
+
- ./data/n8n:/home/node/.n8n
|
|
546
|
+
environment:
|
|
547
|
+
- N8N_HOST=${N8N_HOST}
|
|
548
|
+
- N8N_PROTOCOL=https
|
|
549
|
+
- WEBHOOK_URL=${N8N_WEBHOOK_URL}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### 4.3 Nginx Configuration
|
|
553
|
+
```nginx
|
|
554
|
+
# /etc/nginx/conf.d/backend.conf
|
|
555
|
+
upstream backend {
|
|
556
|
+
server 127.0.0.1:8000;
|
|
557
|
+
keepalive 32;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
server {
|
|
561
|
+
listen 443 ssl http2;
|
|
562
|
+
server_name api.example.com;
|
|
563
|
+
|
|
564
|
+
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
|
|
565
|
+
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
|
|
566
|
+
|
|
567
|
+
location / {
|
|
568
|
+
proxy_pass http://backend;
|
|
569
|
+
proxy_http_version 1.1;
|
|
570
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
571
|
+
proxy_set_header Connection 'upgrade';
|
|
572
|
+
proxy_set_header Host $host;
|
|
573
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
574
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
575
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
576
|
+
proxy_cache_bypass $http_upgrade;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 4.4 Systemd Services
|
|
582
|
+
|
|
583
|
+
#### Redis Service
|
|
584
|
+
```ini
|
|
585
|
+
# /etc/systemd/system/redis.service
|
|
586
|
+
[Unit]
|
|
587
|
+
Description=Redis In-Memory Data Store
|
|
588
|
+
After=network.target
|
|
589
|
+
|
|
590
|
+
[Service]
|
|
591
|
+
User=redis
|
|
592
|
+
Group=redis
|
|
593
|
+
ExecStart=/usr/bin/redis-server /etc/redis/redis.conf
|
|
594
|
+
ExecStop=/usr/bin/redis-cli shutdown
|
|
595
|
+
Restart=always
|
|
596
|
+
RestartSec=3
|
|
597
|
+
|
|
598
|
+
[Install]
|
|
599
|
+
WantedBy=multi-user.target
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
#### Marketing Next.js Service
|
|
603
|
+
```ini
|
|
604
|
+
# /etc/systemd/system/marketing.service
|
|
605
|
+
[Unit]
|
|
606
|
+
Description=Marketing Next.js App
|
|
607
|
+
After=network.target
|
|
608
|
+
|
|
609
|
+
[Service]
|
|
610
|
+
Type=simple
|
|
611
|
+
User=www-data
|
|
612
|
+
WorkingDirectory=/opt/marketing
|
|
613
|
+
ExecStart=/usr/bin/npm start
|
|
614
|
+
Restart=on-failure
|
|
615
|
+
RestartSec=10
|
|
616
|
+
Environment=NODE_ENV=production
|
|
617
|
+
Environment=PORT=3000
|
|
618
|
+
|
|
619
|
+
[Install]
|
|
620
|
+
WantedBy=multi-user.target
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### 4.5 Port Mapping
|
|
624
|
+
|
|
625
|
+
| Domain | Internal Port | Service |
|
|
626
|
+
|--------|---------------|---------|
|
|
627
|
+
| api.example.com | :8000 | Backend API |
|
|
628
|
+
| app.example.com | :3000 | Frontend App |
|
|
629
|
+
| admin.example.com | :3001 | Admin Dashboard |
|
|
630
|
+
| n8n.example.com | :5678 | n8n Automation |
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## 5. Monitoring & Health Checks
|
|
635
|
+
|
|
636
|
+
### Health Endpoints
|
|
637
|
+
```bash
|
|
638
|
+
# Backend health
|
|
639
|
+
curl https://api.example.com/health
|
|
640
|
+
|
|
641
|
+
# Response
|
|
642
|
+
{
|
|
643
|
+
"status": "healthy",
|
|
644
|
+
"version": "v1.21.6",
|
|
645
|
+
"uptime": "2d 5h 30m",
|
|
646
|
+
"checks": {
|
|
647
|
+
"database": "ok",
|
|
648
|
+
"redis": "ok",
|
|
649
|
+
"queue": "ok"
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Docker Health Monitoring
|
|
655
|
+
```bash
|
|
656
|
+
# Check all containers
|
|
657
|
+
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
658
|
+
|
|
659
|
+
# Check logs
|
|
660
|
+
docker logs -f runway-backend --tail 100
|
|
661
|
+
|
|
662
|
+
# Resource usage
|
|
663
|
+
docker stats
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Useful Commands
|
|
667
|
+
```bash
|
|
668
|
+
# View deployment logs
|
|
669
|
+
docker compose logs -f
|
|
670
|
+
|
|
671
|
+
# Restart specific service
|
|
672
|
+
docker compose restart backend
|
|
673
|
+
|
|
674
|
+
# Scale workers
|
|
675
|
+
docker compose up -d --scale worker=3
|
|
676
|
+
|
|
677
|
+
# Enter container
|
|
678
|
+
docker exec -it runway-backend sh
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## 6. Troubleshooting
|
|
684
|
+
|
|
685
|
+
### Deploy Failed
|
|
686
|
+
```bash
|
|
687
|
+
# 1. Check GitHub Actions logs
|
|
688
|
+
# 2. SSH to VPS and check manually
|
|
689
|
+
ssh user@vps
|
|
690
|
+
cd /opt/app
|
|
691
|
+
docker compose logs backend --tail 50
|
|
692
|
+
|
|
693
|
+
# 3. Manual deploy
|
|
694
|
+
docker compose pull
|
|
695
|
+
docker compose up -d
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Rollback Steps
|
|
699
|
+
```bash
|
|
700
|
+
# 1. Find previous working version
|
|
701
|
+
git tag -l | tail -5
|
|
702
|
+
|
|
703
|
+
# 2. Deploy previous version
|
|
704
|
+
export TAG=v1.21.5
|
|
705
|
+
docker compose up -d
|
|
706
|
+
|
|
707
|
+
# 3. Verify
|
|
708
|
+
curl https://api.example.com/health
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Database Migration Failed
|
|
712
|
+
```bash
|
|
713
|
+
# 1. Check migration status
|
|
714
|
+
docker exec runway-backend npx prisma migrate status
|
|
715
|
+
|
|
716
|
+
# 2. Manual migration
|
|
717
|
+
docker exec runway-backend npx prisma migrate deploy
|
|
718
|
+
|
|
719
|
+
# 3. If needed, rollback migration
|
|
720
|
+
docker exec runway-backend npx prisma migrate resolve --rolled-back <migration_name>
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## 7. Security Checklist
|
|
726
|
+
|
|
727
|
+
- [ ] SSH key authentication only (no password)
|
|
728
|
+
- [ ] Firewall configured (ufw/iptables)
|
|
729
|
+
- [ ] SSL certificates auto-renewed (certbot)
|
|
730
|
+
- [ ] Docker socket not exposed
|
|
731
|
+
- [ ] Environment variables secured
|
|
732
|
+
- [ ] GHCR token rotated periodically
|
|
733
|
+
- [ ] VPS SSH key stored in GitHub Secrets
|
|
734
|
+
- [ ] Database backups automated
|
|
735
|
+
- [ ] Logs rotated and monitored
|