Projeto final: script completo de provisionamento de servidor

1. Visão geral do projeto e objetivos

Este artigo apresenta um script Bash completo para provisionamento automatizado de um servidor web com banco de dados, seguindo o modelo LEMP (Linux, Nginx, MySQL/MariaDB, PHP) em distribuições baseadas em Debian/Ubuntu. O objetivo é criar um ambiente funcional e seguro para hospedar aplicações web com o mínimo de intervenção manual.

Escopo do projeto:
- Instalação e configuração do servidor web Nginx
- Instalação e configuração do MariaDB (banco de dados)
- Criação de usuário de serviço, virtual hosts e banco de dados dedicado
- Geração de certificado SSL autoassinado
- Configuração de backup automático e hardening básico

Pré-requisitos:
- Sistema Ubuntu 20.04+ ou Debian 11+
- Acesso root ou usuário com permissões sudo
- Conexão com internet para download de pacotes

Estrutura do script:

provisionamento.sh
config/
  └── nginx-default.conf
  └── mysql-secure.sql
logs/
  └── provisionamento.log
backups/
  └── config-originais/

2. Validação de ambiente e funções auxiliares

Antes de iniciar o provisionamento, validamos o ambiente e criamos funções essenciais para log, tratamento de erros e rollback.

#!/bin/bash

# Validação de ambiente
if [ "$(id -u)" -ne 0 ]; then
    echo "Este script deve ser executado como root!"
    exit 1
fi

OS=$(cat /etc/os-release | grep "^ID=" | cut -d= -f2 | tr -d '"')
if [ "$OS" != "ubuntu" ] && [ "$OS" != "debian" ]; then
    echo "Sistema operacional não suportado: $OS"
    exit 1
fi

# Função de log
LOG_FILE="/var/log/provisionamento.log"
log() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo "$msg" | tee -a "$LOG_FILE"
}

# Função de tratamento de erro
error_handler() {
    local exit_code=$?
    log "ERRO: Comando falhou com código $exit_code na linha $1"
    rollback
    exit $exit_code
}
trap 'error_handler $LINENO' ERR

# Função de rollback básico
rollback() {
    log "Iniciando rollback..."
    systemctl stop nginx 2>/dev/null || true
    systemctl stop mariadb 2>/dev/null || true
    apt-get remove --purge -y nginx mariadb-server 2>/dev/null || true
    log "Rollback concluído"
}

# Verificação de conectividade
check_internet() {
    if ! ping -c 1 8.8.8.8 &>/dev/null; then
        log "ERRO: Sem conexão com internet"
        exit 1
    fi
    log "Conexão com internet OK"
}

# Inicialização
log "Iniciando provisionamento do servidor"
check_internet

3. Configuração inicial do sistema

Realizamos as configurações básicas do sistema antes de instalar os serviços.

# Atualização de pacotes
log "Atualizando pacotes do sistema..."
apt-get update && apt-get upgrade -y

# Instalação de dependências básicas
log "Instalando dependências..."
apt-get install -y curl wget git unzip software-properties-common

# Configuração de hostname
NEW_HOSTNAME="webserver-prod"
hostnamectl set-hostname "$NEW_HOSTNAME"
echo "127.0.1.1 $NEW_HOSTNAME" >> /etc/hosts
log "Hostname configurado: $NEW_HOSTNAME"

# Configuração de fuso horário
timedatectl set-timezone America/Sao_Paulo
log "Fuso horário configurado: America/Sao_Paulo"

# Configuração de locale
locale-gen pt_BR.UTF-8
update-locale LANG=pt_BR.UTF-8
log "Locale configurado: pt_BR.UTF-8"

# Criação de usuário de serviço
SERVICE_USER="webapp"
useradd -m -s /bin/bash -d /home/$SERVICE_USER $SERVICE_USER
log "Usuário de serviço criado: $SERVICE_USER"

# Ajuste de limites do sistema
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf
echo "fs.file-max = 2097152" >> /etc/sysctl.conf
sysctl -p
log "Limites do sistema ajustados"

4. Instalação e configuração do servidor web (Nginx)

Instalamos o Nginx com módulos essenciais, geramos certificado SSL e criamos um virtual host seguro.

# Instalação do Nginx
log "Instalando Nginx..."
apt-get install -y nginx nginx-extras

# Geração de certificado SSL autoassinado
SSL_DIR="/etc/nginx/ssl"
mkdir -p $SSL_DIR
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout $SSL_DIR/server.key \
    -out $SSL_DIR/server.crt \
    -subj "/C=BR/ST=SP/L=SaoPaulo/O=Empresa/CN=$NEW_HOSTNAME"
log "Certificado SSL autoassinado gerado"

# Configuração de virtual host
cat > /etc/nginx/sites-available/app.conf << 'EOF'
server {
    listen 80;
    server_name _;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name _;
    root /var/www/html;
    index index.php index.html;

    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    access_log /var/log/nginx/app_access.log;
    error_log /var/log/nginx/app_error.log;
}
EOF

ln -sf /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default

# Hardening básico
sed -i 's/# server_tokens off;/server_tokens off;/' /etc/nginx/nginx.conf
echo "add_header X-Frame-Options DENY;" >> /etc/nginx/nginx.conf
echo "add_header X-Content-Type-Options nosniff;" >> /etc/nginx/nginx.conf

nginx -t && systemctl enable nginx && systemctl restart nginx
log "Nginx configurado e habilitado"

5. Instalação e configuração do banco de dados (MariaDB)

Instalamos o MariaDB, aplicamos segurança e criamos banco e usuário dedicados para a aplicação.

# Instalação do MariaDB
log "Instalando MariaDB..."
apt-get install -y mariadb-server mariadb-client

# Segurança pós-instalação
mysql_secure_installation << EOF
y
root_password123
root_password123
y
y
y
y
EOF
log "MariaDB seguro configurado"

# Criação de banco e usuário dedicado
DB_NAME="app_database"
DB_USER="app_user"
DB_PASS="AppSecurePassword2024!"

mysql -u root -proot_password123 << EOF
CREATE DATABASE IF NOT EXISTS $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';
GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOF
log "Banco de dados $DB_NAME e usuário $DB_USER criados"

# Configuração de backup automático com cron
BACKUP_DIR="/var/backups/mysql"
mkdir -p $BACKUP_DIR

cat > /usr/local/bin/backup-mysql.sh << 'BACKUPEOF'
#!/bin/bash
BACKUP_DIR="/var/backups/mysql"
DB_NAME="app_database"
DB_USER="app_user"
DB_PASS="AppSecurePassword2024!"
DATE=$(date +%Y%m%d_%H%M%S)
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/backup_$DATE.sql.gz
find $BACKUP_DIR -type f -mtime +7 -delete
BACKUPEOF

chmod +x /usr/local/bin/backup-mysql.sh
echo "0 3 * * * root /usr/local/bin/backup-mysql.sh" >> /etc/crontab
log "Backup automático configurado para 3h diárias"

6. Deploy da aplicação e ajustes finos

Realizamos o deploy de uma aplicação de exemplo e configuramos permissões e ambiente.

# Criação da estrutura da aplicação
APP_DIR="/var/www/html"
mkdir -p $APP_DIR

# Exemplo de página PHP de teste
cat > $APP_DIR/index.php << 'EOF'
<?php
echo "<h1>Servidor Provisionado com Sucesso!</h1>";
echo "<p>Servidor: " . gethostname() . "</p>";
echo "<p>Data: " . date('d/m/Y H:i:s') . "</p>";

// Teste de conexão com banco
try {
    $pdo = new PDO('mysql:host=localhost;dbname=app_database;charset=utf8mb4', 'app_user', 'AppSecurePassword2024!');
    echo "<p>Conexão com banco de dados: OK</p>";
} catch (PDOException $e) {
    echo "<p>Erro na conexão: " . $e->getMessage() . "</p>";
}
?>
EOF

# Configuração de permissões
chown -R $SERVICE_USER:www-data $APP_DIR
chmod -R 755 $APP_DIR
log "Aplicação deployada em $APP_DIR"

# Testes de sanidade
echo "--- Testes de Sanidade ---"
echo "Porta 80: $(ss -tlnp | grep -c ':80')"
echo "Porta 443: $(ss -tlnp | grep -c ':443')"
echo "Porta 3306: $(ss -tlnp | grep -c ':3306')"
echo "Nginx: $(systemctl is-active nginx)"
echo "MariaDB: $(systemctl is-active mariadb)"

# Teste de página inicial
curl -k -o /dev/null -s -w "%{http_code}" https://localhost/
log "Teste de página concluído"

7. Script de limpeza e rollback

Disponibilizamos uma função para desfazer todo o provisionamento, com backup de configurações originais.

# Backup de configurações originais
BACKUP_ORIG="/root/backup-provisionamento"
mkdir -p $BACKUP_ORIG
cp -r /etc/nginx /etc/mysql /var/www $BACKUP_ORIG/
log "Backup das configurações originais salvo em $BACKUP_ORIG"

# Função de desfazer provisionamento
desfazer_provisionamento() {
    log "Iniciando desfazimento do provisionamento..."

    # Parar serviços
    systemctl stop nginx mariadb

    # Desinstalar pacotes
    apt-get remove --purge -y nginx* mariadb-server* php*

    # Remover configurações
    rm -rf /etc/nginx /etc/mysql /var/www/html

    # Remover usuário e banco
    userdel -r $SERVICE_USER 2>/dev/null || true
    mysql -u root -proot_password123 -e "DROP DATABASE IF EXISTS $DB_NAME;"

    # Restaurar backups se existirem
    if [ -d "$BACKUP_ORIG" ]; then
        cp -r $BACKUP_ORIG/* /
        log "Configurações originais restauradas"
    fi

    log "Provisionamento desfeito com sucesso"
}

# Log final com resumo
echo "=========================================" >> $LOG_FILE
echo "RESUMO DO PROVISIONAMENTO" >> $LOG_FILE
echo "=========================================" >> $LOG_FILE
echo "Servidor: $NEW_HOSTNAME" >> $LOG_FILE
echo "Usuário serviço: $SERVICE_USER" >> $LOG_FILE
echo "Banco: $DB_NAME / Usuário: $DB_USER" >> $LOG_FILE
echo "Aplicação: https://localhost/" >> $LOG_FILE
echo "Backup: /var/backups/mysql/" >> $LOG_FILE
echo "Log: $LOG_FILE" >> $LOG_FILE
echo "=========================================" >> $LOG_FILE

log "Provisionamento concluído com sucesso!"

8. Considerações finais e próximos passos

Este script oferece uma base sólida para provisionamento automatizado de servidores web com banco de dados. Para ambientes de produção, considere as seguintes extensões:

Para múltiplos servidores:
- Adapte o script para usar variáveis de ambiente e templates
- Implemente loops para provisionar vários servidores via SSH
- Considere ferramentas como Ansible para orquestração mais robusta

Integração com notificações:

# Exemplo de notificação Slack
notify_slack() {
    local message="$1"
    curl -X POST -H 'Content-type: application/json' \
        --data "{\"text\":\"$message\"}" \
        https://hooks.slack.com/services/SEU/WEBHOOK/AQUI
}

Checklist de segurança pós-provisionamento:
- [ ] Alterar senhas padrão (root MySQL, usuário app)
- [ ] Configurar firewall (UFW/iptables)
- [ ] Implementar fail2ban para proteção contra ataques
- [ ] Configurar monitoramento com Prometheus + Grafana
- [ ] Revisar logs de auditoria regularmente
- [ ] Manter sistema atualizado com unattended-upgrades

O script completo pode ser estendido para incluir instalação de PHP-FPM, Redis, Node.js ou outros serviços conforme necessidade do projeto. A automação reduz erros humanos e garante consistência entre ambientes de desenvolvimento, homologação e produção.

Referências