Skip to content

Instantly share code, notes, and snippets.

@carlosvillu
Last active July 28, 2025 21:47
Show Gist options
  • Save carlosvillu/113ed71d88d0698c0d851db37fa92067 to your computer and use it in GitHub Desktop.
Save carlosvillu/113ed71d88d0698c0d851db37fa92067 to your computer and use it in GitHub Desktop.
#!/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