Last active
July 28, 2025 21:47
-
-
Save carlosvillu/113ed71d88d0698c0d851db37fa92067 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# ============================================================================= | |
# HETZNER VPS SETUP SCRIPT FOR PONDER DEVELOPMENT | |
# ============================================================================= | |
# Este script automatiza la configuraciΓ³n completa de un VPS nuevo de Hetzner | |
# para desarrollo con Ponder, siguiendo las mejores prΓ‘cticas de seguridad. | |
# | |
# Uso: curl -sSL https://raw.githubusercontent.com/tu-repo/setup.sh | bash | |
# O: wget -O - https://raw.githubusercontent.com/tu-repo/setup.sh | bash | |
# O: chmod +x setup.sh && ./setup.sh | |
# | |
# Requisitos: VPS Ubuntu 22.04/24.04 LTS con acceso root SSH | |
# ============================================================================= | |
# ConfiguraciΓ³n bash segura (pero flexible) | |
set -euo pipefail 2>/dev/null || set -e # Fallback si pipefail no estΓ‘ disponible | |
# Colores para output | |
RED='\033[0;31m' | |
GREEN='\033[0;32m' | |
YELLOW='\033[1;33m' | |
BLUE='\033[0;34m' | |
PURPLE='\033[0;35m' | |
CYAN='\033[0;36m' | |
NC='\033[0m' # No Color | |
# Variables globales | |
SCRIPT_VERSION="1.0.0" | |
LOG_FILE="/var/log/vps-setup.log" | |
START_TIME=$(date +%s) | |
# ============================================================================= | |
# FUNCIONES AUXILIARES | |
# ============================================================================= | |
# FunciΓ³n de debug para depurar problemas | |
debug_env() { | |
if [[ "${DEBUG:-}" == "1" ]]; then | |
echo "DEBUG: Bash version: $BASH_VERSION" | |
echo "DEBUG: Script path: ${0}" | |
echo "DEBUG: Arguments: $*" | |
echo "DEBUG: PWD: $PWD" | |
echo "DEBUG: USER: ${USER:-unknown}" | |
echo "DEBUG: HOME: ${HOME:-unknown}" | |
fi | |
} | |
log() { | |
echo -e "${CYAN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" | |
} | |
success() { | |
echo -e "${GREEN}β${NC} $1" | tee -a "$LOG_FILE" | |
} | |
warning() { | |
echo -e "${YELLOW}β ${NC} $1" | tee -a "$LOG_FILE" | |
} | |
error() { | |
echo -e "${RED}β${NC} $1" | tee -a "$LOG_FILE" | |
exit 1 | |
} | |
info() { | |
echo -e "${BLUE}βΉ${NC} $1" | tee -a "$LOG_FILE" | |
} | |
prompt() { | |
echo -e "${PURPLE}?${NC} $1" | |
} | |
check_root() { | |
if [[ $EUID -ne 0 ]]; then | |
error "Este script debe ejecutarse como root. Usa: sudo $0" | |
fi | |
} | |
check_ubuntu() { | |
if ! grep -q "Ubuntu" /etc/os-release; then | |
error "Este script estΓ‘ diseΓ±ado para Ubuntu. Sistema detectado: $(lsb_release -d | cut -f2)" | |
fi | |
local version=$(lsb_release -rs) | |
if [[ $(echo "$version 22.04" | awk '{print ($1 >= $2)}') -eq 0 ]]; then | |
warning "VersiΓ³n de Ubuntu $version detectada. Recomendado: 22.04+" | |
read -p "ΒΏContinuar de todas formas? (y/N): " -n 1 -r | |
echo | |
if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
exit 1 | |
fi | |
fi | |
} | |
spinner() { | |
local pid=$1 | |
local delay=0.1 | |
local spinstr='|/-\' | |
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do | |
local temp=${spinstr#?} | |
printf " [%c] " "$spinstr" | |
local spinstr=$temp${spinstr%"$temp"} | |
sleep $delay | |
printf "\b\b\b\b\b\b" | |
done | |
printf " \b\b\b\b" | |
} | |
# ============================================================================= | |
# CONFIGURACIΓN INTERACTIVA | |
# ============================================================================= | |
gather_config() { | |
clear | |
echo -e "${CYAN}" | |
cat << 'EOF' | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
β HETZNER VPS SETUP FOR PONDER β | |
β Development Environment β | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
EOF | |
echo -e "${NC}" | |
info "ConfiguraciΓ³n del VPS para desarrollo con Ponder" | |
info "Este script configurarΓ‘: Sistema, Seguridad, Docker, Node.js, y Ponder" | |
echo | |
# Redirigir entrada para asegurar input interactivo | |
exec < /dev/tty | |
# ConfiguraciΓ³n del usuario | |
while [[ -z "${NEW_USER:-}" ]]; do | |
prompt "Nombre del usuario no-root a crear:" | |
read -r NEW_USER < /dev/tty | |
if [[ -z "$NEW_USER" ]]; then | |
warning "El nombre de usuario no puede estar vacΓo" | |
fi | |
done | |
# ConfiguraciΓ³n SSH | |
echo | |
prompt "ΒΏQuieres cambiar el puerto SSH del 22 por defecto? (recomendado para seguridad)" | |
echo "Puerto SSH (22 para mantener por defecto, o introduce otro puerto):" | |
read -r SSH_PORT < /dev/tty | |
if [[ -z "$SSH_PORT" ]]; then | |
SSH_PORT=22 | |
fi | |
# Validar puerto SSH | |
if [[ ! "$SSH_PORT" =~ ^[0-9]+$ ]] || [[ "$SSH_PORT" -lt 22 ]] || [[ "$SSH_PORT" -gt 65535 ]]; then | |
warning "Puerto invΓ‘lido. Usando puerto 2222 por defecto" | |
SSH_PORT=2222 | |
fi | |
# Zona horaria | |
echo | |
prompt "Zona horaria (UTC recomendado para desarrollo, Europe/Madrid para local):" | |
echo " 1) UTC (recomendado)" | |
echo " 2) Europe/Madrid" | |
echo " 3) Personalizada" | |
read -r tz_choice < /dev/tty | |
case $tz_choice in | |
1) TIMEZONE="UTC" ;; | |
2) TIMEZONE="Europe/Madrid" ;; | |
3) | |
prompt "Introduce zona horaria (ej: America/New_York):" | |
read -r TIMEZONE < /dev/tty | |
if [[ -z "$TIMEZONE" ]]; then | |
TIMEZONE="UTC" | |
fi | |
;; | |
*) TIMEZONE="UTC" ;; | |
esac | |
# ConfiguraciΓ³n de swap | |
echo | |
local ram_gb=$(free -g | awk 'NR==2{print $2}') | |
prompt "Tu VPS tiene ${ram_gb}GB de RAM." | |
if [[ $ram_gb -lt 4 ]]; then | |
info "Se recomienda crear swap para sistemas con menos de 4GB RAM" | |
SETUP_SWAP="yes" | |
else | |
prompt "ΒΏCrear archivo swap de 2GB? (y/N):" | |
read -r swap_response < /dev/tty | |
if [[ "$swap_response" =~ ^[Yy]$ ]]; then | |
SETUP_SWAP="yes" | |
else | |
SETUP_SWAP="no" | |
fi | |
fi | |
# ConfiguraciΓ³n de Ponder | |
echo | |
prompt "ΒΏCrear proyecto Ponder de ejemplo al final del setup? (Y/n):" | |
read -r ponder_response < /dev/tty | |
if [[ "$ponder_response" =~ ^[Nn]$ ]]; then | |
CREATE_PONDER_PROJECT="no" | |
else | |
CREATE_PONDER_PROJECT="yes" | |
fi | |
# Mostrar resumen | |
echo | |
echo -e "${YELLOW}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${NC}" | |
echo -e "${CYAN}RESUMEN DE CONFIGURACIΓN:${NC}" | |
echo -e "${YELLOW}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${NC}" | |
echo "β’ Usuario no-root: $NEW_USER" | |
echo "β’ Puerto SSH: $SSH_PORT" | |
echo "β’ Zona horaria: $TIMEZONE" | |
echo "β’ Swap: $SETUP_SWAP" | |
echo "β’ Proyecto Ponder de ejemplo: $CREATE_PONDER_PROJECT" | |
echo "β’ IP del servidor: $(curl -s https://ipinfo.io/ip 2>/dev/null || echo "No detectada")" | |
echo | |
prompt "ΒΏContinuar con esta configuraciΓ³n? (Y/n):" | |
read -r continue_response < /dev/tty | |
if [[ "$continue_response" =~ ^[Nn]$ ]]; then | |
error "Setup cancelado por el usuario" | |
fi | |
echo | |
info "Iniciando configuraciΓ³n automΓ‘tica..." | |
sleep 2 | |
} | |
# ============================================================================= | |
# FUNCIONES DE INSTALACIΓN | |
# ============================================================================= | |
update_system() { | |
log "Actualizando el sistema..." | |
# Crear archivo de log | |
touch "$LOG_FILE" | |
apt update -y >> "$LOG_FILE" 2>&1 & | |
spinner $! | |
apt upgrade -y >> "$LOG_FILE" 2>&1 & | |
spinner $! | |
apt autoremove -y >> "$LOG_FILE" 2>&1 & | |
spinner $! | |
success "Sistema actualizado" | |
} | |
install_basic_tools() { | |
log "Instalando herramientas bΓ‘sicas..." | |
local packages=( | |
"software-properties-common" | |
"apt-transport-https" | |
"ca-certificates" | |
"gnupg" | |
"lsb-release" | |
"curl" | |
"wget" | |
"unzip" | |
"build-essential" | |
"htop" | |
"tree" | |
"vim" | |
"nano" | |
"screen" | |
"tmux" | |
"net-tools" | |
"dnsutils" | |
"git" | |
"ufw" | |
"fail2ban" | |
"openssh-server" | |
"openssh-client" | |
) | |
apt install -y "${packages[@]}" >> "$LOG_FILE" 2>&1 & | |
spinner $! | |
# Asegurar que SSH estΓ‘ habilitado e iniciado | |
systemctl enable ssh >> "$LOG_FILE" 2>&1 || true | |
systemctl start ssh >> "$LOG_FILE" 2>&1 || true | |
success "Herramientas bΓ‘sicas instaladas" | |
} | |
configure_timezone_locale() { | |
log "Configurando timezone y locale..." | |
# Configurar timezone | |
timedatectl set-timezone "$TIMEZONE" >> "$LOG_FILE" 2>&1 | |
# Configurar locale | |
locale-gen en_US.UTF-8 >> "$LOG_FILE" 2>&1 | |
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 >> "$LOG_FILE" 2>&1 | |
success "Timezone configurado a $TIMEZONE y locale en UTF-8" | |
} | |
create_user() { | |
log "Creando usuario $NEW_USER..." | |
# Crear usuario | |
if id "$NEW_USER" &>/dev/null; then | |
warning "Usuario $NEW_USER ya existe, saltando creaciΓ³n" | |
else | |
adduser --disabled-password --gecos "" "$NEW_USER" >> "$LOG_FILE" 2>&1 | |
# Generar contraseΓ±a temporal segura | |
local temp_password=$(openssl rand -base64 16) | |
echo "$NEW_USER:$temp_password" | chpasswd | |
info "Usuario $NEW_USER creado con contraseΓ±a temporal: $temp_password" | |
info "Se recomiendan claves SSH en lugar de contraseΓ±as" | |
fi | |
# AΓ±adir a grupo sudo | |
usermod -aG sudo "$NEW_USER" >> "$LOG_FILE" 2>&1 | |
# Crear estructura de directorios | |
sudo -u "$NEW_USER" mkdir -p "/home/$NEW_USER"/{projects/ponder,scripts,.ssh,backups} >> "$LOG_FILE" 2>&1 | |
# Configurar permisos SSH | |
chmod 700 "/home/$NEW_USER/.ssh" | |
chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.ssh" | |
success "Usuario $NEW_USER configurado" | |
} | |
configure_ssh() { | |
log "Configurando SSH seguro..." | |
# Verificar si SSH estΓ‘ instalado, si no, instalarlo | |
if ! command -v sshd &> /dev/null || [[ ! -f /etc/ssh/sshd_config ]]; then | |
warning "SSH no encontrado, instalando openssh-server..." | |
apt update >> "$LOG_FILE" 2>&1 | |
apt install -y openssh-server >> "$LOG_FILE" 2>&1 | |
systemctl enable ssh >> "$LOG_FILE" 2>&1 | |
systemctl start ssh >> "$LOG_FILE" 2>&1 | |
success "SSH instalado y iniciado" | |
fi | |
# Verificar nuevamente que el archivo existe | |
if [[ ! -f /etc/ssh/sshd_config ]]; then | |
error "No se pudo crear el archivo de configuraciΓ³n SSH" | |
fi | |
# Backup de configuraciΓ³n original | |
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup | |
# Configurar SSH | |
cat > /etc/ssh/sshd_config << EOF | |
# SSH Configuration - Generated by VPS Setup Script | |
Port $SSH_PORT | |
Protocol 2 | |
# Authentication | |
PermitRootLogin no | |
PubkeyAuthentication yes | |
AuthorizedKeysFile .ssh/authorized_keys | |
PasswordAuthentication yes | |
# Cambiar a 'no' despuΓ©s de configurar claves SSH | |
PermitEmptyPasswords no | |
ChallengeResponseAuthentication no | |
UsePAM yes | |
# Security settings | |
MaxAuthTries 6 | |
ClientAliveInterval 300 | |
ClientAliveCountMax 2 | |
MaxStartups 10:30:60 | |
LoginGraceTime 60 | |
# Logging | |
SyslogFacility AUTH | |
LogLevel INFO | |
# Allow specific users | |
AllowUsers $NEW_USER | |
# Subsystem | |
Subsystem sftp /usr/lib/openssh/sftp-server | |
# Banner | |
PrintMotd no | |
AcceptEnv LANG LC_* | |
EOF | |
# Crear mensaje de bienvenida | |
cat > /etc/motd << EOF | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
β PONDER DEVELOPMENT SERVER β | |
β β | |
β π Docker: $(docker --version 2>/dev/null | cut -d' ' -f3 | cut -d',' -f1 || echo "Instalando...") β | |
β π¦ Node.js: $(su - $NEW_USER -c 'node --version 2>/dev/null' || echo "Instalando...") β | |
β π§ Usuario: $NEW_USER β | |
β π IP: $(curl -s https://ipinfo.io/ip 2>/dev/null || echo "No detectada") β | |
β π Proyectos: /home/$NEW_USER/projects/ponder β | |
β β | |
β Para empezar: cd ~/projects/ponder && pnpm create ponder mi-proyecto β | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
EOF | |
# Verificar configuraciΓ³n SSH | |
if ! sshd -t >> "$LOG_FILE" 2>&1; then | |
error "ConfiguraciΓ³n SSH invΓ‘lida. Revisa el log: $LOG_FILE" | |
fi | |
# Reiniciar SSH | |
systemctl restart ssh >> "$LOG_FILE" 2>&1 | |
# Verificar que estΓ‘ corriendo | |
if ! systemctl is-active --quiet ssh; then | |
error "SSH no se pudo iniciar correctamente" | |
fi | |
success "SSH configurado en puerto $SSH_PORT" | |
warning "Β‘IMPORTANTE! Configura claves SSH antes de deshabilitar password authentication" | |
info "Comando para copiar clave SSH: ssh-copy-id -p $SSH_PORT $NEW_USER@$(curl -s https://ipinfo.io/ip)" | |
} | |
configure_firewall() { | |
log "Configurando firewall UFW..." | |
# Configurar UFW | |
ufw --force reset >> "$LOG_FILE" 2>&1 | |
ufw default deny incoming >> "$LOG_FILE" 2>&1 | |
ufw default allow outgoing >> "$LOG_FILE" 2>&1 | |
# Permitir puertos necesarios | |
ufw allow "$SSH_PORT"/tcp >> "$LOG_FILE" 2>&1 | |
ufw allow 80/tcp >> "$LOG_FILE" 2>&1 | |
ufw allow 443/tcp >> "$LOG_FILE" 2>&1 | |
ufw allow 3000:3010/tcp >> "$LOG_FILE" 2>&1 # Puertos de desarrollo | |
ufw allow 42069/tcp >> "$LOG_FILE" 2>&1 # Puerto por defecto de Ponder | |
# Habilitar firewall | |
ufw --force enable >> "$LOG_FILE" 2>&1 | |
success "Firewall configurado y habilitado" | |
} | |
configure_fail2ban() { | |
log "Configurando fail2ban..." | |
# Configurar fail2ban | |
cat > /etc/fail2ban/jail.local << EOF | |
[DEFAULT] | |
# IPs que nunca serΓ‘n bloqueadas | |
ignoreip = 127.0.0.1/8 ::1 | |
# ConfiguraciΓ³n para desarrollo (menos agresiva) | |
bantime = 600 # 10 minutos | |
findtime = 900 # 15 minutos | |
maxretry = 8 # Intentos antes del bloqueo | |
# Usar UFW como backend | |
banaction = ufw | |
[sshd] | |
enabled = true | |
port = $SSH_PORT | |
filter = sshd | |
logpath = /var/log/auth.log | |
maxretry = 8 | |
EOF | |
# Iniciar y habilitar fail2ban | |
systemctl restart fail2ban >> "$LOG_FILE" 2>&1 | |
systemctl enable fail2ban >> "$LOG_FILE" 2>&1 | |
success "fail2ban configurado" | |
} | |
install_docker() { | |
log "Instalando Docker..." | |
# Desinstalar versiones conflictivas | |
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do | |
apt-get remove $pkg -y >> "$LOG_FILE" 2>&1 || true | |
done | |
# Instalar prerequisitos | |
apt-get update >> "$LOG_FILE" 2>&1 | |
apt-get install -y ca-certificates curl >> "$LOG_FILE" 2>&1 | |
# AΓ±adir clave GPG de Docker | |
install -m 0755 -d /etc/apt/keyrings | |
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc | |
chmod a+r /etc/apt/keyrings/docker.asc | |
# AΓ±adir repositorio | |
echo \ | |
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ | |
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ | |
tee /etc/apt/sources.list.d/docker.list > /dev/null | |
# Instalar Docker | |
apt-get update >> "$LOG_FILE" 2>&1 & | |
spinner $! | |
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >> "$LOG_FILE" 2>&1 & | |
spinner $! | |
# Configurar Docker para usuario no-root | |
groupadd docker 2>/dev/null || true | |
usermod -aG docker "$NEW_USER" >> "$LOG_FILE" 2>&1 | |
# Configurar Docker daemon | |
cat > /etc/docker/daemon.json << EOF | |
{ | |
"log-driver": "json-file", | |
"log-opts": { | |
"max-size": "10m", | |
"max-file": "3" | |
}, | |
"storage-driver": "overlay2", | |
"live-restore": true | |
} | |
EOF | |
# Habilitar Docker | |
systemctl enable docker.service >> "$LOG_FILE" 2>&1 | |
systemctl enable containerd.service >> "$LOG_FILE" 2>&1 | |
systemctl restart docker >> "$LOG_FILE" 2>&1 | |
success "Docker instalado y configurado" | |
} | |
install_nodejs() { | |
log "Instalando Node.js vΓa NVM..." | |
# Instalar NVM como usuario no-root | |
sudo -u "$NEW_USER" bash << 'EOF' | |
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash | |
source ~/.bashrc | |
export NVM_DIR="$HOME/.nvm" | |
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" | |
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" | |
# Instalar Node.js LTS | |
nvm install --lts | |
nvm use --lts | |
nvm alias default lts/* | |
# Instalar herramientas globales | |
npm install -g pnpm typescript tsx | |
EOF | |
# Configurar PATH y variables de entorno | |
sudo -u "$NEW_USER" bash << EOF | |
cat >> ~/.bashrc << 'EOFBASH' | |
# NVM configuration | |
export NVM_DIR="\$HOME/.nvm" | |
[ -s "\$NVM_DIR/nvm.sh" ] && \. "\$NVM_DIR/nvm.sh" | |
[ -s "\$NVM_DIR/bash_completion" ] && \. "\$NVM_DIR/bash_completion" | |
# Docker aliases | |
alias d="docker" | |
alias dc="docker compose" | |
alias dps="docker ps" | |
alias di="docker images" | |
alias dclean="docker system prune -f" | |
alias dcleanall="docker system prune -a -f --volumes" | |
# Ponder development environment | |
if [ -f ~/.env.development ]; then | |
export \$(cat ~/.env.development | grep -v "^#" | xargs) | |
fi | |
# Useful aliases | |
alias ll="ls -la" | |
alias la="ls -A" | |
alias l="ls -CF" | |
alias monitor="~/monitor.sh" | |
alias ponder-start="cd ~/projects/ponder && docker compose up -d" | |
alias ponder-logs="cd ~/projects/ponder && docker compose logs -f" | |
alias ponder-stop="cd ~/projects/ponder && docker compose down" | |
EOFBASH | |
EOF | |
success "Node.js y herramientas instaladas" | |
} | |
setup_swap() { | |
if [[ "$SETUP_SWAP" == "yes" ]]; then | |
log "Configurando archivo swap..." | |
# Crear swap de 2GB | |
fallocate -l 2G /swapfile | |
chmod 600 /swapfile | |
mkswap /swapfile >> "$LOG_FILE" 2>&1 | |
swapon /swapfile | |
# Hacer permanente | |
echo '/swapfile none swap sw 0 0' >> /etc/fstab | |
# Configurar swappiness | |
echo 'vm.swappiness=10' >> /etc/sysctl.conf | |
success "Swap de 2GB configurado" | |
fi | |
} | |
create_monitoring_tools() { | |
log "Creando herramientas de monitoreo..." | |
# Script de monitoreo | |
sudo -u "$NEW_USER" cat > "/home/$NEW_USER/monitor.sh" << 'EOF' | |
#!/bin/bash | |
echo "=== Estado del Sistema $(date) ===" | |
echo | |
echo "π₯οΈ CPU y Memoria:" | |
free -h | |
echo | |
echo "πΎ Espacio en disco:" | |
df -h / /tmp 2>/dev/null | grep -v tmpfs | |
echo | |
echo "π³ Contenedores Docker:" | |
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "Docker no disponible" | |
echo | |
echo "π Estado de servicios:" | |
echo "SSH: $(systemctl is-active ssh)" | |
echo "Docker: $(systemctl is-active docker)" | |
echo "fail2ban: $(systemctl is-active fail2ban)" | |
echo "UFW: $(ufw status | head -1)" | |
echo | |
echo "π Conexiones de red activas:" | |
ss -tuln | grep LISTEN | head -10 | |
echo | |
echo "π Carga del sistema:" | |
uptime | |
EOF | |
chmod +x "/home/$NEW_USER/monitor.sh" | |
chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/monitor.sh" | |
# Script de backup bΓ‘sico | |
sudo -u "$NEW_USER" cat > "/home/$NEW_USER/scripts/backup-dev.sh" << 'EOF' | |
#!/bin/bash | |
BACKUP_DIR="$HOME/backups" | |
DATE=$(date +%Y%m%d_%H%M%S) | |
mkdir -p "$BACKUP_DIR" | |
echo "ποΈ Creando backup de desarrollo..." | |
# Backup de proyectos | |
if [ -d "$HOME/projects" ]; then | |
echo "Respaldando proyectos..." | |
tar -czf "$BACKUP_DIR/projects_$DATE.tar.gz" -C "$HOME" projects/ 2>/dev/null | |
fi | |
# Backup de configuraciones | |
echo "Respaldando configuraciones..." | |
tar -czf "$BACKUP_DIR/configs_$DATE.tar.gz" -C "$HOME" \ | |
.bashrc .env.development .ssh/ scripts/ 2>/dev/null | |
# Limpiar backups antiguos (mantener solo los ΓΊltimos 5) | |
ls -t "$BACKUP_DIR"/projects_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm | |
ls -t "$BACKUP_DIR"/configs_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm | |
echo "β Backup completado en: $BACKUP_DIR" | |
ls -la "$BACKUP_DIR" | |
EOF | |
chmod +x "/home/$NEW_USER/scripts/backup-dev.sh" | |
chown -R "$NEW_USER:$NEW_USER" "/home/$NEW_USER/scripts" | |
success "Herramientas de monitoreo creadas" | |
} | |
setup_ponder_environment() { | |
log "Configurando entorno para Ponder..." | |
# Variables de entorno para desarrollo | |
sudo -u "$NEW_USER" cat > "/home/$NEW_USER/.env.development" << 'EOF' | |
# Variables de entorno para desarrollo Ponder | |
NODE_ENV=development | |
# ConfiguraciΓ³n de base de datos (PGlite es el default) | |
# DATABASE_URL=postgresql://user:password@localhost:5432/dbname | |
# URLs RPC para diferentes redes (configura las que necesites) | |
# PONDER_RPC_URL_1=https://eth-mainnet.g.alchemy.com/v2/your-api-key | |
# PONDER_RPC_URL_8453=https://base-mainnet.g.alchemy.com/v2/your-api-key | |
# PONDER_RPC_URL_137=https://polygon-mainnet.g.alchemy.com/v2/your-api-key | |
# Puerto de desarrollo (por defecto 42069) | |
# PORT=42069 | |
# Debug (descomentar para logs detallados) | |
# DEBUG=ponder:* | |
EOF | |
chmod 600 "/home/$NEW_USER/.env.development" | |
chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.env.development" | |
success "Entorno Ponder configurado" | |
} | |
create_ponder_project() { | |
if [[ "$CREATE_PONDER_PROJECT" == "yes" ]]; then | |
log "Creando proyecto Ponder de ejemplo..." | |
sudo -u "$NEW_USER" bash << 'EOF' | |
cd ~/projects/ponder | |
source ~/.bashrc | |
export NVM_DIR="$HOME/.nvm" | |
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" | |
# Crear proyecto Ponder | |
echo "Creando proyecto Ponder..." | |
pnpm create ponder example-ponder | |
cd example-ponder | |
# Instalar dependencias | |
pnpm install | |
# Crear configuraciΓ³n bΓ‘sica | |
cat > ponder.config.ts << 'EOFCONFIG' | |
import { createConfig } from "ponder"; | |
import { http } from "viem"; | |
export default createConfig({ | |
networks: { | |
mainnet: { | |
chainId: 1, | |
transport: http(process.env.PONDER_RPC_URL_1 || "https://eth.llamarpc.com"), | |
pollingInterval: 1_000, | |
}, | |
}, | |
contracts: { | |
// Ejemplo con contrato USDC | |
USDC: { | |
abi: [ | |
{ | |
"anonymous": false, | |
"inputs": [ | |
{"indexed": true, "name": "from", "type": "address"}, | |
{"indexed": true, "name": "to", "type": "address"}, | |
{"indexed": false, "name": "value", "type": "uint256"} | |
], | |
"name": "Transfer", | |
"type": "event" | |
} | |
], | |
network: "mainnet", | |
address: "0xA0b86a33E6e1b6e43c81d1C5D04b6b7e30b17e4f", | |
startBlock: 18000000, | |
endBlock: 18001000, // Solo 1000 bloques para el ejemplo | |
}, | |
}, | |
database: { | |
kind: "pglite", | |
}, | |
}); | |
EOFCONFIG | |
# Crear Dockerfile para desarrollo | |
cat > Dockerfile << 'EOFDOCKER' | |
FROM node:lts-alpine | |
WORKDIR /app | |
# Instalar pnpm | |
RUN npm install -g pnpm | |
# Copiar archivos de dependencias | |
COPY package*.json pnpm-lock.yaml ./ | |
# Instalar dependencias | |
RUN pnpm install | |
# Copiar cΓ³digo fuente | |
COPY . . | |
# Crear directorio de datos | |
RUN mkdir -p .ponder | |
# Exponer puerto | |
EXPOSE 42069 | |
# Comando de desarrollo | |
CMD ["pnpm", "dev"] | |
EOFDOCKER | |
# Crear docker-compose.yml | |
cat > docker-compose.yml << 'EOFDCOMPOSE' | |
version: '3.8' | |
services: | |
ponder: | |
build: . | |
ports: | |
- "42069:42069" | |
volumes: | |
- ponder_data:/app/.ponder | |
- ./src:/app/src | |
- ./ponder.config.ts:/app/ponder.config.ts | |
- ./ponder.schema.ts:/app/ponder.schema.ts | |
environment: | |
- NODE_ENV=development | |
restart: unless-stopped | |
volumes: | |
ponder_data: | |
EOFDCOMPOSE | |
# Crear .env.local | |
cat > .env.local << 'EOFENV' | |
# Configura tu RPC provider aquΓ | |
# PONDER_RPC_URL_1=https://eth-mainnet.g.alchemy.com/v2/your-api-key | |
# Para desarrollo puedes usar un RPC pΓΊblico (con limitaciones) | |
PONDER_RPC_URL_1=https://eth.llamarpc.com | |
# Debug opcional | |
# DEBUG=ponder:* | |
EOFENV | |
echo "π¦ Proyecto Ponder creado en ~/projects/ponder/example-ponder" | |
EOF | |
success "Proyecto Ponder de ejemplo creado" | |
fi | |
} | |
create_verification_script() { | |
log "Creando script de verificaciΓ³n..." | |
sudo -u "$NEW_USER" cat > "/home/$NEW_USER/verify-setup.sh" << 'EOF' | |
#!/bin/bash | |
# Colores | |
RED='\033[0;31m' | |
GREEN='\033[0;32m' | |
YELLOW='\033[1;33m' | |
BLUE='\033[0;34m' | |
NC='\033[0m' | |
check() { | |
if $1 &>/dev/null; then | |
echo -e "${GREEN}β${NC} $2" | |
return 0 | |
else | |
echo -e "${RED}β${NC} $2" | |
return 1 | |
fi | |
} | |
echo -e "${BLUE}" | |
cat << 'EOFBANNER' | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
β VERIFICACIΓN DEL SETUP PONDER β | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
EOFBANNER | |
echo -e "${NC}" | |
# Verificaciones del sistema | |
echo -e "${YELLOW}Sistema:${NC}" | |
check "command -v docker" "Docker instalado" | |
check "docker compose version" "Docker Compose instalado" | |
check "command -v node" "Node.js instalado" | |
check "command -v pnpm" "pnpm instalado" | |
check "command -v git" "Git instalado" | |
# Verificaciones de servicios | |
echo -e "\n${YELLOW}Servicios:${NC}" | |
check "systemctl is-active --quiet docker" "Docker ejecutΓ‘ndose" | |
check "systemctl is-active --quiet ssh" "SSH ejecutΓ‘ndose" | |
check "systemctl is-active --quiet fail2ban" "fail2ban ejecutΓ‘ndose" | |
# Verificar permisos Docker | |
echo -e "\n${YELLOW}Permisos:${NC}" | |
check "docker ps" "Docker funciona sin sudo" | |
# Verificar puertos | |
echo -e "\n${YELLOW}Red y Seguridad:${NC}" | |
check "sudo ufw status | grep -q 42069" "Puerto 42069 (Ponder) permitido" | |
check "sudo ufw status | grep -q 'Status: active'" "Firewall UFW activo" | |
# InformaciΓ³n del sistema | |
echo -e "\n${YELLOW}InformaciΓ³n del sistema:${NC}" | |
echo "π₯οΈ Usuario: $(whoami)" | |
echo "π³ Docker: $(docker --version 2>/dev/null | cut -d' ' -f3 | cut -d',' -f1 || echo 'No disponible')" | |
echo "π¦ Node.js: $(node --version 2>/dev/null || echo 'No disponible')" | |
echo "β‘ pnpm: $(pnpm --version 2>/dev/null || echo 'No disponible')" | |
echo "πΎ Memoria libre: $(free -h | awk 'NR==2{print $7}')" | |
echo "ποΈ Espacio libre: $(df -h / | awk 'NR==2{print $4}')" | |
echo "π IP pΓΊblica: $(curl -s https://ipinfo.io/ip 2>/dev/null || echo 'No detectada')" | |
# Verificar proyectos | |
echo -e "\n${YELLOW}Proyectos:${NC}" | |
if [ -d "~/projects/ponder/example-ponder" ]; then | |
echo -e "${GREEN}β${NC} Proyecto Ponder de ejemplo creado" | |
else | |
echo -e "${YELLOW}βΉ${NC} No hay proyecto Ponder de ejemplo" | |
fi | |
echo -e "\n${GREEN}π Para empezar con Ponder:${NC}" | |
echo "1. cd ~/projects/ponder" | |
echo "2. pnpm create ponder mi-proyecto" | |
echo "3. cd mi-proyecto && pnpm install" | |
echo "4. Configura tu RPC en .env.local" | |
echo "5. pnpm dev" | |
echo "" | |
echo -e "${GREEN}π³ Para usar Docker:${NC}" | |
echo "1. docker compose up -d" | |
echo "2. docker compose logs -f" | |
echo "" | |
echo -e "${BLUE}π Monitor del sistema: ${NC}./monitor.sh" | |
echo -e "${BLUE}πΎ Backup: ${NC}./scripts/backup-dev.sh" | |
EOF | |
chmod +x "/home/$NEW_USER/verify-setup.sh" | |
chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/verify-setup.sh" | |
success "Script de verificaciΓ³n creado" | |
} | |
# ============================================================================= | |
# FUNCIΓN PRINCIPAL | |
# ============================================================================= | |
show_completion_summary() { | |
local end_time=$(date +%s) | |
local duration=$((end_time - START_TIME)) | |
local minutes=$((duration / 60)) | |
local seconds=$((duration % 60)) | |
clear | |
echo -e "${GREEN}" | |
cat << 'EOF' | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
β Β‘SETUP COMPLETADO! β | |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
EOF | |
echo -e "${NC}" | |
success "VPS configurado exitosamente en ${minutes}m ${seconds}s" | |
echo | |
echo -e "${CYAN}π RESUMEN DE CONFIGURACIΓN:${NC}" | |
echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
echo "π₯οΈ Sistema: Ubuntu $(lsb_release -rs) actualizado" | |
echo "π€ Usuario: $NEW_USER (con privilegios sudo)" | |
echo "π SSH: Puerto $SSH_PORT, autenticaciΓ³n por clave recomendada" | |
echo "π‘οΈ Firewall: UFW activo con puertos necesarios" | |
echo "π fail2ban: ProtecciΓ³n contra ataques de fuerza bruta" | |
echo "π³ Docker: $(docker --version | cut -d' ' -f3 | cut -d',' -f1) con Docker Compose" | |
echo "π¦ Node.js: $(sudo -u "$NEW_USER" bash -c 'source ~/.bashrc && node --version')" | |
echo "β‘ pnpm: Gestor de paquetes instalado" | |
echo "π Timezone: $TIMEZONE" | |
if [[ "$SETUP_SWAP" == "yes" ]]; then | |
echo "πΎ Swap: 2GB configurado" | |
fi | |
echo "π IP pΓΊblica: $(curl -s https://ipinfo.io/ip)" | |
echo | |
echo -e "${YELLOW}π CONFIGURACIΓN SSH IMPORTANTE:${NC}" | |
echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
warning "1. Configura claves SSH ANTES de cerrar esta sesiΓ³n:" | |
echo " # En tu mΓ‘quina local:" | |
echo " ssh-copy-id -p $SSH_PORT $NEW_USER@$(curl -s https://ipinfo.io/ip)" | |
echo | |
warning "2. Crea un alias SSH en tu mΓ‘quina local:" | |
echo " # Editar ~/.ssh/config y aΓ±adir:" | |
echo " Host ponder-dev" | |
echo " HostName $(curl -s https://ipinfo.io/ip)" | |
echo " User $NEW_USER" | |
echo " Port $SSH_PORT" | |
echo | |
echo " # DespuΓ©s usar: ssh ponder-dev" | |
echo | |
echo -e "${GREEN}π PRΓXIMOS PASOS:${NC}" | |
echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
echo "1. Configura claves SSH (Β‘CRΓTICO!)" | |
echo "2. Conecta como $NEW_USER: ssh -p $SSH_PORT $NEW_USER@$(curl -s https://ipinfo.io/ip)" | |
echo "3. Verifica el setup: ./verify-setup.sh" | |
echo "4. Crea tu primer proyecto: cd ~/projects/ponder && pnpm create ponder mi-proyecto" | |
echo "5. Configura tu RPC provider en .env.local" | |
echo "6. Ejecuta tu proyecto: pnpm dev o docker compose up -d" | |
echo | |
echo -e "${BLUE}π οΈ HERRAMIENTAS ΓTILES:${NC}" | |
echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
echo "β’ Monitor del sistema: ./monitor.sh" | |
echo "β’ Backup de desarrollo: ./scripts/backup-dev.sh" | |
echo "β’ Verificar setup: ./verify-setup.sh" | |
echo "β’ Logs del sistema: sudo journalctl -f" | |
echo "β’ Estado de Docker: docker ps" | |
echo "β’ Estado de servicios: systemctl status docker ssh fail2ban" | |
echo | |
if [[ "$CREATE_PONDER_PROJECT" == "yes" ]]; then | |
echo -e "${PURPLE}π PROYECTO DE EJEMPLO:${NC}" | |
echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
echo "β’ UbicaciΓ³n: /home/$NEW_USER/projects/ponder/example-ponder" | |
echo "β’ Ejecutar: cd ~/projects/ponder/example-ponder && pnpm dev" | |
echo "β’ Con Docker: cd ~/projects/ponder/example-ponder && docker compose up -d" | |
echo "β’ GraphQL UI: http://$(curl -s https://ipinfo.io/ip):42069/graphql" | |
echo | |
fi | |
echo -e "${RED}β οΈ RECORDATORIOS IMPORTANTES:${NC}" | |
echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
echo "β’ Haz snapshot/backup regular desde el panel de Hetzner" | |
echo "β’ Configura tu RPC provider (Alchemy, Infura, etc.) en las variables de entorno" | |
echo "β’ Considera cambiar PasswordAuthentication a 'no' en SSH despuΓ©s de configurar claves" | |
echo "β’ MantΓ©n el sistema actualizado: apt update && apt upgrade" | |
echo | |
echo -e "${GREEN}β¨ Β‘Tu entorno de desarrollo Ponder estΓ‘ listo! β¨${NC}" | |
echo | |
info "Log completo guardado en: $LOG_FILE" | |
info "Tiempo total de instalaciΓ³n: ${minutes}m ${seconds}s" | |
} | |
main() { | |
# Debug opcional | |
debug_env | |
# Verificaciones iniciales | |
check_root | |
check_ubuntu | |
# ConfiguraciΓ³n interactiva | |
gather_config | |
# Proceso de instalaciΓ³n | |
echo | |
log "Iniciando configuraciΓ³n automΓ‘tica del VPS..." | |
update_system | |
install_basic_tools | |
configure_timezone_locale | |
create_user | |
configure_ssh | |
configure_firewall | |
configure_fail2ban | |
install_docker | |
install_nodejs | |
setup_swap | |
create_monitoring_tools | |
setup_ponder_environment | |
create_ponder_project | |
create_verification_script | |
# FinalizaciΓ³n | |
show_completion_summary | |
} | |
# ============================================================================= | |
# EJECUCIΓN DEL SCRIPT | |
# ============================================================================= | |
# Manejo de seΓ±ales | |
trap 'error "Script interrumpido por el usuario"' INT TERM | |
# Verificar si se estΓ‘ ejecutando directamente | |
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then | |
main "$@" | |
fi | |
# ============================================================================= | |
# INSTRUCCIONES DE USO | |
# ============================================================================= | |
# Para usar este script: | |
# | |
# 1. MΓ©todo directo (recomendado): | |
# curl -sSL https://raw.githubusercontent.com/tu-usuario/vps-setup/main/setup.sh | sudo bash | |
# | |
# 2. Descargar y ejecutar: | |
# wget https://raw.githubusercontent.com/tu-usuario/vps-setup/main/setup.sh | |
# chmod +x setup.sh | |
# sudo ./setup.sh | |
# | |
# 3. Copiar y pegar: | |
# Copia todo el contenido del script en un archivo setup.sh | |
# chmod +x setup.sh | |
# sudo ./setup.sh | |
# | |
# El script es completamente interactivo y te guiarΓ‘ a travΓ©s de la configuraciΓ³n. | |
# Todas las acciones se registran en /var/log/vps-setup.log para referencia. | |
# | |
# IMPORTANTE: | |
# - Ejecutar como root: sudo ./setup.sh | |
# - Tener acceso a internet desde el VPS | |
# - Ubuntu 22.04+ LTS recomendado | |
# - Configurar claves SSH inmediatamente despuΓ©s del setup |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment