This programming guide is only available in Vietnamese. Switch to Vietnamese to read the full article.

Bài cuối cùng trong series Bash sẽ đưa bạn vào thế giới DevOps — nơi Bash thực sự tỏa sáng. Từ Git hooks tự động hóa workflow, CI/CD scripting, Docker entrypoint, cron jobs, SSH automation cho đến linting với ShellCheck — đây là những kỹ năng mà mọi developer và DevOps engineer đều cần.

1. Git Hooks — Tự động hóa Git Workflow

Git hooks là các script Bash chạy tự động tại các thời điểm khác nhau trong Git workflow. Chúng nằm trong thư mục .git/hooks/.

1.1 Các hook phổ biến

hook_overview.sh
# Cấu trúc thư mục hooks
.git/hooks/
├── pre-commit       # Chạy TRƯỚC khi commit (lint, format, test)
├── commit-msg       # Kiểm tra commit message
├── pre-push         # Chạy TRƯỚC khi push (test, build check)
├── post-merge       # Chạy SAU khi merge (install deps)
└── prepare-commit-msg  # Tự động sửa commit message

# Mọi hook phải có quyền thực thi:
chmod +x .git/hooks/pre-commit

1.2 pre-commit hook — Lint & Format

.git/hooks/pre-commit
#!/usr/bin/env bash
set -euo pipefail

echo "🔍 Running pre-commit checks..."

# ── Check 1: Không commit file lớn ──
MAX_SIZE=5242880  # 5MB
large_files=$(git diff --cached --name-only | while read -r file; do
  if [[ -f "$file" ]]; then
    size=$(wc -c < "$file")
    if (( size > MAX_SIZE )); then
      echo "$file ($(( size / 1024 / 1024 ))MB)"
    fi
  fi
done)

if [[ -n "$large_files" ]]; then
  echo "❌ File quá lớn (>5MB):"
  echo "$large_files"
  exit 1
fi

# ── Check 2: Không commit secrets ──
PATTERNS=(
  'password\s*='
  'api_key\s*='
  'secret\s*='
  'AWS_SECRET'
  'PRIVATE_KEY'
)

for pattern in "${PATTERNS[@]}"; do
  if git diff --cached -i -G "$pattern" --name-only | grep -q .; then
    echo "❌ Có thể chứa secrets (pattern: $pattern):"
    git diff --cached -i -G "$pattern" --name-only
    echo "Dùng 'git commit --no-verify' nếu chắc chắn an toàn."
    exit 1
  fi
done

# ── Check 3: Lint staged files ──
STAGED_JS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$' || true)
if [[ -n "$STAGED_JS" ]]; then
  echo "📝 Linting JavaScript/TypeScript..."
  echo "$STAGED_JS" | xargs npx eslint --quiet || {
    echo "❌ ESLint failed. Fix errors trước khi commit."
    exit 1
  }
fi

# ── Check 4: Lint shell scripts ──
STAGED_SH=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.sh$' || true)
if [[ -n "$STAGED_SH" ]]; then
  echo "🐚 Linting shell scripts..."
  echo "$STAGED_SH" | xargs shellcheck || {
    echo "❌ ShellCheck failed."
    exit 1
  }
fi

echo "✅ All pre-commit checks passed!"

1.3 commit-msg hook — Validate Conventional Commits

.git/hooks/commit-msg
#!/usr/bin/env bash
set -euo pipefail

COMMIT_MSG_FILE="$1"
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Conventional Commits: type(scope): description
PATTERN='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\([a-z-]+\))?: .{1,72}$'

if ! echo "$COMMIT_MSG" | head -1 | grep -qE "$PATTERN"; then
  echo "❌ Commit message không đúng format!"
  echo ""
  echo "Format: type(scope): description"
  echo "Types: feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert"
  echo "Ví dụ: feat(auth): add Google OAuth login"
  echo ""
  echo "Message hiện tại: $COMMIT_MSG"
  exit 1
fi

echo "✅ Commit message hợp lệ"

1.4 Chia sẻ hooks qua .githooks/

.git/hooks/ không được track bởi Git, team thường dùng thư mục riêng:

setup_hooks.sh
# Cách 1: Cấu hình Git core.hooksPath
git config core.hooksPath .githooks

# Cách 2: Symlink (tương thích Git cũ)
#!/usr/bin/env bash
HOOKS_DIR=".githooks"
GIT_HOOKS_DIR=".git/hooks"

for hook in "$HOOKS_DIR"/*; do
  hook_name=$(basename "$hook")
  ln -sf "../../$HOOKS_DIR/$hook_name" "$GIT_HOOKS_DIR/$hook_name"
  echo "✅ Linked $hook_name"
done

# Cách 3: Thêm vào package.json (Node.js projects)
# "scripts": { "prepare": "git config core.hooksPath .githooks" }

2. CI/CD Scripting

Hầu hết CI/CD pipeline (GitHub Actions, GitLab CI, Jenkins) đều chạy Bash script. Hiểu cách viết script CI tốt sẽ giúp pipeline của bạn ổn định và dễ debug.

2.1 GitHub Actions — CI Script

scripts/ci-build.sh
#!/usr/bin/env bash
set -euo pipefail

# ══════════════════════════════════════════
# CI Build & Test Script
# Dùng trong GitHub Actions / GitLab CI
# ══════════════════════════════════════════

echo "::group::📦 Install Dependencies"
npm ci
echo "::endgroup::"

echo "::group::🔍 Lint"
npm run lint || {
  echo "::error::Lint failed"
  exit 1
}
echo "::endgroup::"

echo "::group::🧪 Test"
npm run test -- --coverage || {
  echo "::error::Tests failed"
  exit 1
}
echo "::endgroup::"

echo "::group::🏗️ Build"
npm run build || {
  echo "::error::Build failed"
  exit 1
}
echo "::endgroup::"

# ── Kiểm tra build output ──
BUILD_DIR="dist"
if [[ ! -d "$BUILD_DIR" ]] || [[ -z "$(ls -A "$BUILD_DIR")" ]]; then
  echo "::error::Build directory rỗng hoặc không tồn tại"
  exit 1
fi

BUILD_SIZE=$(du -sh "$BUILD_DIR" | cut -f1)
echo "✅ Build thành công! Size: $BUILD_SIZE"

# ── Export cho step tiếp theo (GitHub Actions) ──
echo "build_size=$BUILD_SIZE" >> "${GITHUB_OUTPUT:-/dev/null}"

2.2 GitLab CI — Deploy Script

scripts/ci-deploy.sh
#!/usr/bin/env bash
set -euo pipefail

# ── Kiểm tra env vars bắt buộc ──
: "${DEPLOY_HOST:?'DEPLOY_HOST chưa set'}"
: "${DEPLOY_USER:?'DEPLOY_USER chưa set'}"
: "${DEPLOY_PATH:?'DEPLOY_PATH chưa set'}"
: "${SSH_PRIVATE_KEY:?'SSH_PRIVATE_KEY chưa set'}"

# ── Setup SSH ──
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null

SSH_CMD="ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no"
SCP_CMD="scp -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no"

# ── Deploy ──
echo "🚀 Deploying to $DEPLOY_HOST..."

# Upload build artifacts
$SCP_CMD -r dist/* "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"

# Restart service trên remote
$SSH_CMD "${DEPLOY_USER}@${DEPLOY_HOST}" <<'REMOTE'
  cd /var/www/myapp
  npm ci --production
  pm2 restart myapp
  echo "Service restarted"
REMOTE

echo "✅ Deploy hoàn thành!"

# Cleanup
rm -f ~/.ssh/deploy_key

Lưu ý quan trọng:

  • : "${VAR:?'message'}" — Cú pháp ngắn gọn kiểm tra biến bắt buộc, thoát ngay nếu thiếu
  • ::group:: / ::error:: — GitHub Actions workflow commands để format output
  • Exit code khác 0 sẽ đánh dấu CI job là FAILED

3. Docker & Bash

Docker sử dụng Bash ở nhiều nơi: Dockerfile CMD/ENTRYPOINT, entrypoint scripts, và docker-compose commands.

3.1 Entrypoint Script

Entrypoint script là "cầu nối" giữa Docker container và ứng dụng. Nó thiết lập môi trường trước khi chạy app:

docker-entrypoint.sh
#!/usr/bin/env bash
set -euo pipefail

echo "🐳 Container starting..."
echo "Environment: ${NODE_ENV:-development}"

# ── Wait for dependencies ──
wait_for_service() {
  local host="$1" port="$2" timeout="${3:-30}"
  local elapsed=0

  echo "⏳ Waiting for $host:$port..."
  until nc -z "$host" "$port" 2>/dev/null; do
    if (( elapsed >= timeout )); then
      echo "❌ Timeout waiting for $host:$port after ${timeout}s"
      exit 1
    fi
    sleep 1
    elapsed=$((elapsed + 1))
  done
  echo "✅ $host:$port is ready (${elapsed}s)"
}

# Chờ database và Redis sẵn sàng
wait_for_service "${DB_HOST:-postgres}" "${DB_PORT:-5432}" 60
wait_for_service "${REDIS_HOST:-redis}" "${REDIS_PORT:-6379}" 30

# ── Run migrations ──
if [[ "${RUN_MIGRATIONS:-false}" == "true" ]]; then
  echo "📦 Running database migrations..."
  npm run migrate
fi

# ── Inject runtime env ──
if [[ -f /run/secrets/app_secrets ]]; then
  echo "🔐 Loading secrets..."
  set -a
  source /run/secrets/app_secrets
  set +a
fi

# ── Exec: thay thế shell process bằng app process ──
# Điều này quan trọng để app nhận được signals (SIGTERM, SIGINT)
echo "🚀 Starting application..."
exec "$@"

3.2 Docker utility commands

docker_utils.sh
#!/usr/bin/env bash
set -euo pipefail

# ── Dockerfile sử dụng entrypoint ──
# COPY docker-entrypoint.sh /usr/local/bin/
# RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# ENTRYPOINT ["docker-entrypoint.sh"]
# CMD ["node", "server.js"]

# ── Docker Compose helper ──
dc_up() {
  docker-compose up -d --build
  echo "✅ Services started"
  docker-compose ps
}

dc_logs() {
  docker-compose logs -f --tail=100 "${1:-}"
}

dc_restart() {
  local service="$1"
  docker-compose restart "$service"
  docker-compose logs -f --tail=20 "$service"
}

# ── Exec vào container ──
dc_shell() {
  local service="${1:-app}"
  docker-compose exec "$service" /bin/bash
}

# ── Cleanup Docker resources ──
docker_cleanup() {
  echo "🧹 Cleaning up Docker..."
  docker system prune -f
  docker volume prune -f
  echo "Disk recovered: $(docker system df | tail -1)"
}

4. Cron Jobs & SSH Automation

4.1 Crontab — Lập lịch tác vụ

Crontab là công cụ lập lịch mặc định trên Linux/macOS. Cú pháp gồm 5 trường:

crontab_examples.sh
# Cú pháp crontab:
# ┌───────────── phút (0-59)
# │ ┌─────────── giờ (0-23)
# │ │ ┌───────── ngày trong tháng (1-31)
# │ │ │ ┌─────── tháng (1-12)
# │ │ │ │ ┌───── ngày trong tuần (0-7, 0 và 7 = Chủ nhật)
# │ │ │ │ │
# * * * * * command

# Ví dụ:
# Mỗi ngày lúc 2:00 AM
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# Mỗi 5 phút
*/5 * * * * /opt/scripts/healthcheck.sh

# Thứ 2-6, lúc 9:00 AM
0 9 * * 1-5 /opt/scripts/report.sh

# Ngày 1 hàng tháng lúc 00:00
0 0 1 * * /opt/scripts/monthly_cleanup.sh

# ── Quản lý crontab ──
crontab -l          # Xem crontab hiện tại
crontab -e          # Sửa crontab
crontab -r          # Xóa toàn bộ crontab (CẨN THẬN!)

# ── Tip: Log output ──
# Luôn redirect output để debug:
* * * * * /path/script.sh >> /var/log/cron.log 2>&1

# ── Tip: Set PATH trong crontab ──
# Cron không có PATH giống shell bình thường
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * backup.sh

4.2 SSH Automation

ssh_automation.sh
#!/usr/bin/env bash
set -euo pipefail

# ══════════════════════════════════════════
# SSH Automation
# ══════════════════════════════════════════

REMOTE_USER="deploy"
REMOTE_HOST="server.example.com"
REMOTE_DIR="/var/www/myapp"

# ── SSH Key Authentication (setup) ──
# ssh-keygen -t ed25519 -C "deploy@myapp" -f ~/.ssh/deploy_key
# ssh-copy-id -i ~/.ssh/deploy_key.pub [email protected]

# ── Chạy lệnh trên remote server ──
ssh "$REMOTE_USER@$REMOTE_HOST" "df -h && free -m && uptime"

# ── Chạy nhiều lệnh (heredoc) ──
ssh "$REMOTE_USER@$REMOTE_HOST" <<'EOF'
  cd /var/www/myapp
  git pull origin main
  npm ci --production
  pm2 restart all
  echo "Deploy done at $(date)"
EOF

# ── SCP: Copy file ──
scp ./dist.tar.gz "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"

# ── Rsync: Sync thư mục (nhanh hơn scp) ──
rsync -avz --delete \
  --exclude='node_modules' \
  --exclude='.git' \
  ./dist/ "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
# -a: archive mode (giữ permissions, timestamps)
# -v: verbose
# -z: nén khi transfer
# --delete: xóa file ở remote không có ở local

# ── SSH Tunnel (port forwarding) ──
# Forward port 5432 từ remote về local:
# ssh -L 5432:localhost:5432 [email protected] -N
# Giờ có thể kết nối PostgreSQL local: psql -h localhost -p 5432

# ── SSH Config (~/.ssh/config) ──
# Host myserver
#   HostName server.example.com
#   User deploy
#   IdentityFile ~/.ssh/deploy_key
#   Port 22
#
# Sau đó chỉ cần: ssh myserver

5. Makefile & ShellCheck

5.1 Makefile — Task Runner cho mọi dự án

Makefile không chỉ dùng cho C/C++ — nó là task runner tuyệt vời cho bất kỳ dự án nào:

Makefile
# Makefile cho dự án web
.PHONY: help install dev build test deploy clean lint

# Target mặc định khi chạy `make` không có argument
.DEFAULT_GOAL := help

# Hiển thị help (parse comments ##)
help: ## Hiển thị danh sách commands
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
		awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-15s\033[0m %s\n", $$1, $$2}'

install: ## Cài đặt dependencies
	npm ci

dev: ## Chạy development server
	npm run dev

build: ## Build production
	npm run build

test: ## Chạy tests
	npm run test

lint: ## Lint code
	npm run lint
	shellcheck scripts/*.sh

deploy: build ## Deploy (chạy build trước)
	./scripts/deploy.sh

clean: ## Dọn dẹp build artifacts
	rm -rf dist node_modules .cache

# ── Ví dụ target với biến ──
ENV ?= staging
deploy-env: build ## Deploy tới môi trường cụ thể (ENV=prod make deploy-env)
	./scripts/deploy.sh $(ENV)

5.2 ShellCheck — Linting cho Shell Scripts

ShellCheck là công cụ static analysis phát hiện bugs, security issues và bad practices trong shell scripts:

shellcheck_demo.sh
# Cài đặt ShellCheck
# macOS:   brew install shellcheck
# Ubuntu:  apt install shellcheck
# npm:     npm install -g shellcheck

# ── Chạy ShellCheck ──
shellcheck myscript.sh
shellcheck -x myscript.sh       # -x: Follow source'd files
shellcheck -s bash myscript.sh  # Chỉ định shell type

# ════════════════════════════════════════
# Các lỗi phổ biến mà ShellCheck bắt:
# ════════════════════════════════════════

# SC2086: Double quote to prevent globbing and word splitting
# ❌ Sai:
rm $filename
# ✅ Đúng:
rm "$filename"

# SC2046: Quote this to prevent word splitting
# ❌ Sai:
files=$(find . -name "*.txt")
rm $files
# ✅ Đúng:
while IFS= read -r file; do
  rm "$file"
done < <(find . -name "*.txt")

# SC2034: Variable appears unused
# ❌ ShellCheck cảnh báo biến khai báo nhưng không dùng
UNUSED_VAR="hello"

# SC2155: Declare and assign separately
# ❌ Sai:
local output=$(command)
# ✅ Đúng:
local output
output=$(command)

# ── Disable cảnh báo cụ thể ──
# shellcheck disable=SC2086
echo $intentionally_unquoted

# ── Tích hợp CI ──
# GitHub Actions:
# - name: ShellCheck
#   uses: ludeeus/action-shellcheck@master
#   with:
#     scandir: './scripts'

5.3 Tổng kết Series — Best Practices

Sau 8 bài học, đây là checklist cho mọi shell script bạn viết:

  • Luôn bắt đầu với #!/usr/bin/env bashset -euo pipefail
  • Quote mọi biến: "$var" thay vì $var
  • Dùng [[ ]] thay vì [ ] cho conditional
  • Dùng $(command) thay vì backtick `command`
  • Trap signals để cleanup tài nguyên (temp files, lock files)
  • Log mọi thứ với timestamp và log levels
  • Chạy ShellCheck trước khi commit
  • Dùng functions để tổ chức code, tránh script dài 1 khối
  • Dry-run mode cho script nguy hiểm (delete, deploy)
  • Kiểm tra dependencies với command -v ở đầu script

6. Câu hỏi trắc nghiệm ôn tập

Trắc nghiệm: Crontab

Dòng crontab */15 9-17 * * 1-5 /opt/scripts/check.sh có nghĩa là gì?

Tải file code thực hành minh họa bài học

File chứa đầy đủ các ví dụ: Git hooks, CI/CD scripts, Docker entrypoint, cron, SSH automation, Makefile và ShellCheck patterns.

Tải về devops_automation.sh

Related Articles

Bài viết liên quan trong series

Lesson 9: Advanced I/O Redirection & File Descriptors Bài 9: Điều Hướng I/O Nâng Cao & File Descriptors Lesson 7: Real-World Bash: Backup, Monitor & Deploy Scripts Bài 7: Thực Chiến Bash: Backup, Monitor & Deploy Scripts Back to Bash Series Overview Quay lại Lộ trình Bash Series