Automação e Projetos Práticos
Aula 6 de 6
Script de Backup
Backup com rsync
cat > backup.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
readonly BACKUP_SRC="${1:-/home/$USER}"
readonly BACKUP_DST="${2:-/mnt/backup}"
readonly BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
readonly RETENTION_DAYS=30
# Verificar diretórios
for dir in "$BACKUP_SRC" "$BACKUP_DST"; do
[ -d "$dir" ] || { echo "Diretório não existe: $dir"; exit 1; }
done
echo "Iniciando backup de $BACKUP_SRC para $BACKUP_DST/$BACKUP_NAME"
# Backup completo com rsync
rsync -avz --progress \
--delete \
--link-dest="$BACKUP_DST/latest" \
"$BACKUP_SRC/" \
"$BACKUP_DST/$BACKUP_NAME/"
# Atualizar link latest
rm -f "$BACKUP_DST/latest"
ln -s "$BACKUP_DST/$BACKUP_NAME" "$BACKUP_DST/latest"
echo "Backup concluído: $BACKUP_NAME"
# Rotação: deletar backups antigos
find "$BACKUP_DST" -maxdepth 1 -type d -name "backup-*" -mtime +$RETENTION_DAYS \
-exec rm -rf {} \; \
-exec echo "Removido backup antigo: {}" \;
echo "Rotação concluída (retenção: $RETENTION_DAYS dias)"
SCRIPT
chmod +x backup.sh
# Backup com compressão tar
cat > backup-tar.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
BACKUP_SRC="$1"
BACKUP_FILE="backup-$(date +%Y%m%d-%H%M%S).tar.gz"
BACKUP_DIR="/backup"
# Criar diretório se não existir
mkdir -p "$BACKUP_DIR"
# Compactar com tar + gzip
tar -czf "$BACKUP_DIR/$BACKUP_FILE" \
--exclude='*.log' \
--exclude='node_modules' \
--exclude='.cache' \
-C "$(dirname "$BACKUP_SRC")" "$(basename "$BACKUP_SRC")"
echo "Backup criado: $BACKUP_DIR/$BACKUP_FILE"
ls -lh "$BACKUP_DIR/$BACKUP_FILE"
SCRIPT
Script de Deploy
Deploy com health check e rollback
cat > deploy.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
readonly APP_DIR="/opt/app"
readonly BACKUP_DIR="/opt/app-backups"
readonly REPO_URL="https://github.com/org/app.git"
readonly BRANCH="main"
readonly HEALTH_URL="http://localhost:8080/health"
readonly MAX_RETRIES=12
deploy() {
local commit_hash
local timestamp
timestamp=$(date +%Y%m%d-%H%M%S)
echo "=== DEPLOY $timestamp ==="
# 1. Pull latest
echo "[1/5] Pull do código..."
cd "$APP_DIR"
git fetch origin
commit_hash=$(git rev-parse --short origin/"$BRANCH")
git checkout "$BRANCH"
git pull origin "$BRANCH"
# 2. Build
echo "[2/5] Build..."
npm ci --production
npm run build
# 3. Backup da versão atual
echo "[3/5] Backup..."
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/pre-deploy-$timestamp.tar.gz" \
--exclude=node_modules .
# 4. Restart
echo "[4/5] Restart do serviço..."
sudo systemctl restart app
# 5. Health check
echo "[5/5] Health check..."
local retry=0
while [[ $retry -lt $MAX_RETRIES ]]; do
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
echo "Deploy $commit_hash concluído com sucesso!"
return 0
fi
echo "Aguardando serviço... ($((retry+1))/$MAX_RETRIES)"
sleep 5
((retry++))
done
# Rollback
echo "ERRO: Health check falhou. Iniciando rollback..."
rollback
return 1
}
rollback() {
local latest_backup
latest_backup=$(ls -t "$BACKUP_DIR"/pre-deploy-*.tar.gz 2>/dev/null | head -1)
if [[ -z "$latest_backup" ]]; then
echo "Nenhum backup disponível para rollback"
exit 1
fi
echo "Restaurando backup: $latest_backup"
cd "$APP_DIR"
tar -xzf "$latest_backup"
sudo systemctl restart app
echo "Rollback concluído"
}
# Executar
if [[ "${1:-}" == "rollback" ]]; then
rollback
else
deploy
fi
SCRIPT
chmod +x deploy.sh
Script de Monitoramento
cat > monitor.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
readonly CPU_ALERT=80
readonly MEM_ALERT=90
readonly DISK_ALERT=85
readonly ALERT_EMAIL="[email protected]"
readonly LOG_FILE="/var/log/monitor.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
check_cpu() {
local usage
usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d. -f1)
if [[ $usage -gt $CPU_ALERT ]]; then
log "ALERTA CPU: ${usage}% (limite: ${CPU_ALERT}%)"
return 1
fi
log "CPU: ${usage}%"
}
check_memory() {
local usage
usage=$(free | awk '/Mem:/ {printf "%.0f", $3/$2 * 100}')
if [[ $usage -gt $MEM_ALERT ]]; then
log "ALERTA MEM: ${usage}% (limite: ${MEM_ALERT}%)"
return 1
fi
log "MEM: ${usage}%"
}
check_disk() {
df -h / | awk 'NR==2 {
gsub(/%/,"",$5);
if ($5+0 > '"$DISK_ALERT"') {
system("echo ALERTA DISK: " $5 "% >> " "'"$LOG_FILE"'")
}
}'
}
check_process() {
local proc="$1"
if pgrep -x "$proc" > /dev/null; then
log "Processo $proc: OK (PID $(pgrep -x "$proc" | head -1))"
else
log "ALERTA: Processo $proc não está rodando"
return 1
fi
}
send_alert() {
local subject="$1"
local body="$2"
echo "$body" | mail -s "$subject" "$ALERT_EMAIL"
}
echo "=== MONITORAMENTO $(date) ==="
check_cpu
check_memory
check_disk
check_process "nginx"
check_process "postgres"
echo "Monitoramento concluído."
SCRIPT
chmod +x monitor.sh
Cron Jobs
# Editar crontab
crontab -e
# Formato: minuto hora dia-mês mês dia-semana comando
# m h dom mon dow
# Executar todo dia às 3h
0 3 * * * /home/user/scripts/backup.sh
# A cada hora
0 * * * * /home/user/scripts/monitor.sh
# A cada 30 minutos
*/30 * * * * /home/user/scripts/health-check.sh
# A cada dia útil às 8h
0 8 * * 1-5 /home/user/scripts/daily-report.sh
# No reboot (@reboot)
@reboot /home/user/scripts/start-services.sh
# Logging em cron jobs
0 3 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1
# Exemplo completo: criar arquivo de crontab
cat > my-cron << 'EOF'
# Backup diário às 3h com log
0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Monitoramento a cada 5 minutos (apenas se falhar)
*/5 * * * * /opt/scripts/monitor.sh 2>&1 | mail -s "Alerta" [email protected]
# Limpeza semanal (domingo às 2h)
0 2 * * 0 find /tmp -type f -atime +7 -delete
# Rotação de logs (todo dia 1o do mês)
0 1 1 * * /opt/scripts/rotate-logs.sh
# Verificação de SSL (toda segunda às 9h)
0 9 * * 1 /opt/scripts/check-ssl.sh
EOF
crontab my-cron # Importar
crontab -l # Listar
crontab -r # Remover (cuidado!)
CI/CD — GitHub Actions com Bash
cat > .github/workflows/deploy.yml << 'YAML'
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test
run: |
set -euo pipefail
bash -n scripts/*.sh
shellcheck scripts/*.sh
echo "Todos os testes passaram!"
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
run: |
set -euo pipefail
echo "${{ secrets.SSH_KEY }}" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
ssh -i /tmp/deploy_key user@server '
cd /opt/app
git pull origin main
npm ci --production
npm run build
sudo systemctl restart app
'
YAML
Argument Parsing com getopts
cat > getopts-example.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
usage() {
cat <<EOF
Uso: $0 [-v] [-o arquivo] [-n nome] [-d] [-h]
Opções:
-o ARQUIVO Arquivo de saída (obrigatório)
-n NOME Nome do projeto (padrão: projeto)
-v Modo verboso
-d Modo debug
-h Ajuda
EOF
exit 0
}
# Valores padrão
OUTPUT_FILE=""
PROJECT_NAME="projeto"
VERBOSE=false
DEBUG=false
# Parse com getopts
while getopts "o:n:vdh" opt; do
case $opt in
o) OUTPUT_FILE="$OPTARG" ;;
n) PROJECT_NAME="$OPTARG" ;;
v) VERBOSE=true ;;
d) DEBUG=true ;;
h) usage ;;
\?) echo "Opção inválida: -$OPTARG" >&2; usage ;;
:) echo "Opção -$OPTARG requer argumento" >&2; exit 1 ;;
esac
done
# Validar argumentos obrigatórios
[[ -n "$OUTPUT_FILE" ]] || { echo "Erro: -o é obrigatório"; usage; }
$VERBOSE && echo "Verbose ativado"
$DEBUG && set -x
shift $((OPTIND-1))
echo "Argumentos restantes: $@"
echo "Configuração:"
echo " Output: $OUTPUT_FILE"
echo " Nome: $PROJECT_NAME"
echo " Debug: $DEBUG"
SCRIPT
chmod +x getopts-example.sh
./getopts-example.sh -o saida.txt -n MeuProjeto -v
Lab: Pipeline CI/CD Completo com Bash
cat > pipeline.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
# ─── Configuração ─────────────────────────────────────────────
readonly APP_NAME="myapp"
readonly APP_DIR="/opt/$APP_NAME"
readonly REPO="https://github.com/org/$APP_NAME.git"
readonly BRANCH="${CI_COMMIT_BRANCH:-main}"
readonly BUILD_DIR="/tmp/$APP_NAME-build"
readonly BACKUP_DIR="/opt/$APP_NAME-backups"
readonly HEALTH_URL="http://localhost:8080/health"
readonly SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
log() { echo "[$(date +%H:%M:%S)] $*"; }
error(){ echo "[ERRO] $*" >&2; exit 1; }
# ─── Etapas ───────────────────────────────────────────────────
stage_lint() {
log "Lint: verificando scripts..."
for script in scripts/*.sh; do
bash -n "$script" || error "Sintaxe inválida: $script"
if command -v shellcheck &>/dev/null; then
shellcheck --severity=style "$script"
fi
done
log "Lint: OK"
}
stage_test() {
log "Test: executando testes..."
# Assumindo framework de teste
if [[ -f "tests/run.sh" ]]; then
bash tests/run.sh
fi
log "Test: OK"
}
stage_build() {
log "Build: compilando..."
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
cp -r src/ package.json "$BUILD_DIR/"
(cd "$BUILD_DIR" && npm ci --production)
log "Build: OK"
}
stage_deploy() {
log "Deploy: publicando..."
# Backup da versão atual
if [[ -d "$APP_DIR" ]]; then
local backup_file="$BACKUP_DIR/pre-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
mkdir -p "$BACKUP_DIR"
tar -czf "$backup_file" -C "$APP_DIR" .
log "Backup criado: $backup_file"
fi
# Deploy
rsync -az --delete "$BUILD_DIR/" "$APP_DIR/"
sudo systemctl restart "$APP_NAME"
log "Deploy: OK"
}
stage_health() {
log "Health: verificando serviço..."
local retry=0
while [[ $retry -lt 12 ]]; do
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
log "Health: serviço saudável"
return 0
fi
sleep 5
((retry++))
done
error "Health check falhou após deploy - necessário rollback manual"
}
stage_notify() {
if [[ -n "$SLACK_WEBHOOK" ]]; then
curl -sf -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"✅ Deploy $APP_NAME ($BRANCH) concluído\"}" \
"$SLACK_WEBHOOK" || true
fi
log "Notificação enviada"
}
# ─── Main ────────────────────────────────────────────────────
main() {
log "=== PIPELINE $APP_NAME ($BRANCH) ==="
trap 'error "Pipeline falhou na linha $LINENO"' ERR
stage_lint
stage_test
stage_build
stage_deploy
stage_health
stage_notify
log "=== PIPELINE CONCLUÍDO COM SUCESSO ==="
}
main "$@"
SCRIPT
chmod +x pipeline.sh
Scripts de automação combinam rsync/tar para backup, health check com curl para deploy, cron para agendamento, getopts para argumentos, e traps para rollback. Idempotência é essencial em automação.