feat: phase 3 redesign, game images, auth system, vm guides, service isolation
Some checks failed
Deploy Multi-VM / Deploy VM Web (push) Has been cancelled
Deploy Multi-VM / Deploy VM Auth (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.fusionfall.yml, VM_FUSIONFALL_HOST, VM_FUSIONFALL_SSH_KEY, VM_FUSIONFALL_USER, fusionfall) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.maple2.yml, VM_MAPLE2_HOST, VM_MAPLE2_SSH_KEY, VM_MAPLE2_USER, maple2) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.minecraft.yml, VM_MINECRAFT_HOST, VM_MINECRAFT_SSH_KEY, VM_MINECRAFT_USER, minecraft) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.retro.yml, VM_RETRO_HOST, VM_RETRO_SSH_KEY, VM_RETRO_USER, retro) (push) Has been cancelled

- Redesign all internal pages to warm/gold aesthetic (catalog, game detail,
  documentary, about, donate, community, guides, contact, server-status,
  login, profile, admin, not-found)
- Add real cover images for all 4 games via Strapi CMS with getImageUrl helper
- Integrate NextAuth v5 with Authentik OIDC authentication
- Add new public pages: community, guides, contact, server-status
- Add new protected pages: login, profile, admin dashboard
- Remove legacy AFC/MercadoPago system entirely
- Add Docker Compose split files for service isolation (main, auth, fusionfall, nier)
- Add OpenFusion VM deployment configs (config.vm.ini, systemd service, README-VM)
- Add NieR Reincarnation server guide and desktop client guide
- Add architecture docs for multi-VM deployment
- Add healthcheck, SSE, contact, newsletter, admin API routes
- Add reusable UI components, skeleton loaders, activity feed, bookmark system
- Update deployment and game server documentation
This commit is contained in:
consultoria-as
2026-04-28 05:15:38 +00:00
parent ea142501fa
commit 449c02eadc
151 changed files with 10053 additions and 2312 deletions

105
scripts/deploy-vm.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/bin/bash
# =============================================================================
# deploy-vm.sh - Helper script for deploying Project Afterlife on a specific VM
# =============================================================================
# Usage: ./deploy-vm.sh <vm-name>
# Example: ./deploy-vm.sh web
#
# Available VMs: web, auth, nier, dbo, maple2, fusionfall
# =============================================================================
set -e
VM_NAME="$1"
PROJECT_DIR="/opt/project-afterlife"
COMPOSE_DIR="$PROJECT_DIR/docker"
if [ -z "$VM_NAME" ]; then
echo "Usage: $0 <vm-name>"
echo "Available VMs: web, auth, nier, dbo, maple2, fusionfall"
exit 1
fi
case "$VM_NAME" in
web)
COMPOSE_FILE="docker-compose.web.yml"
SERVICES="web cms postgres minio nginx"
;;
auth)
COMPOSE_FILE="docker-compose.auth.yml"
SERVICES="authentik-server authentik-worker authentik-postgres authentik-redis"
;;
nier)
COMPOSE_FILE="docker-compose.nier.yml"
SERVICES="nier-server"
;;
dbo)
COMPOSE_FILE="docker-compose.dbo.yml"
SERVICES="dbo-mariadb"
;;
maple2)
COMPOSE_FILE="docker-compose.maple2.yml"
SERVICES="maple2-world maple2-login maple2-game-ch0 maple2-web maple2-mysql"
;;
fusionfall)
COMPOSE_FILE="docker-compose.fusionfall.yml"
SERVICES="openfusion"
;;
*)
echo "Unknown VM: $VM_NAME"
echo "Available VMs: web, auth, nier, dbo, maple2, fusionfall"
exit 1
;;
esac
echo "=========================================="
echo "Deploying VM: $VM_NAME"
echo "Compose file: $COMPOSE_FILE"
echo "Services: $SERVICES"
echo "=========================================="
cd "$PROJECT_DIR"
echo "[1/4] Pulling latest code..."
git pull origin main
echo "[2/4] Building images..."
cd "$COMPOSE_DIR"
docker compose -f "$COMPOSE_FILE" build
echo "[3/4] Starting services..."
docker compose -f "$COMPOSE_FILE" up -d
echo "[4/4] Checking health..."
sleep 5
docker compose -f "$COMPOSE_FILE" ps
echo "=========================================="
echo "Deployment of $VM_NAME complete!"
echo "=========================================="
# Special handling for web VM: rebuild Next.js
case "$VM_NAME" in
web)
echo "[Extra] Rebuilding Next.js app..."
docker compose -f "$COMPOSE_FILE" exec web npm run build
docker compose -f "$COMPOSE_FILE" restart web
echo "Next.js rebuilt and restarted."
;;
maple2)
echo "[Extra] MapleStory 2 deployed."
echo "Remember to set GAME_IP and LOGIN_IP in servers/maple2/.env to the VM's public IP."
;;
nier)
echo "[Extra] NieR Reincarnation deployed."
echo "Remember to:"
echo " 1. Place AssetDatabase and MasterDatabase in the data volume"
echo " 2. Update RESOURCES_BASE_URL in .env (must be 43 chars after host)"
echo " 3. Patch the APK with scripts/patcher.ipynb (Google Colab)"
;;
dbo)
echo "[Extra] DBO Global deployed."
echo "WARNING: DBO server is currently a placeholder. The C++ server requires"
echo "Windows or Wine. Consider running it on a dedicated Windows VM."
;;
esac

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
/**
* Fix Spanish chapter links to Spanish documentaries
*/
const STRAPI_URL = "http://localhost:1337";
async function apiRequest(endpoint, method = "GET", body = null) {
const url = `${STRAPI_URL}/api${endpoint}`;
const options = {
method,
headers: { "Content-Type": "application/json" },
...(body ? { body: JSON.stringify(body) } : {}),
};
const response = await fetch(url, options);
const data = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`${response.status}: ${JSON.stringify(data)}`);
}
return data;
}
// Map chapter titles to documentary IDs (Spanish)
const docMap = {
"nier": 19,
"dbo": 21,
"fusionfall": 3,
"maple": 6,
};
function getDocId(title) {
const t = title.toLowerCase();
if (t.includes("jaula") || t.includes("melancolia") || t.includes("recuerdos") || t.includes("pasado") || t.includes("grietas") || t.includes("suspiro")) return docMap.nier;
if (t.includes("mundo abierto") || t.includes("toriyama") || t.includes("año 1000") || t.includes("guerreros") || t.includes("frontera") || t.includes("ultimo deseo")) return docMap.dbo;
if (t.includes("cubos") || t.includes("juguete") || t.includes("musica del maple") || t.includes("espada") || t.includes("tormentas") || t.includes("hoja")) return docMap.maple;
if (t.includes("alianza") || t.includes("planet fusion") || t.includes("creadores") || t.includes("fusions") || t.includes("red que se rompio") || t.includes("juicio")) return docMap.fusionfall;
return null;
}
async function main() {
console.log("🔧 Fixing Spanish chapter links...\n");
// Get all Spanish chapters
const res = await apiRequest("/chapters?pagination[pageSize]=200&locale=es");
const chapters = res.data || [];
console.log(`Found ${chapters.length} Spanish chapters`);
for (const ch of chapters) {
const docId = getDocId(ch.title);
if (!docId) {
console.log(` ⚠️ Could not map: ${ch.title}`);
continue;
}
try {
await apiRequest(`/chapters/${ch.documentId}?locale=es`, "PUT", {
data: { documentary: docId },
});
console.log(`${ch.title} → doc ${docId}`);
} catch (err) {
console.log(`${ch.title}: ${err.message}`);
}
}
console.log("\n✨ Done!");
}
main().catch((err) => {
console.error("💥 Fatal error:", err.message);
process.exit(1);
});

View File

@@ -0,0 +1,220 @@
#!/usr/bin/env node
/**
* Import Documentaries into Strapi CMS (with upsert support)
*
* Usage:
* node scripts/import-documentaries.js [path-to-json]
*
* Environment variables:
* STRAPI_URL - Strapi API URL (default: http://localhost:1337)
*
* The JSON file should match the structure in docs/seeds/documentaries-seed.json
*/
const fs = require("fs");
const path = require("path");
const STRAPI_URL = process.env.STRAPI_URL || "http://localhost:1337";
const HEADERS = {
"Content-Type": "application/json",
};
async function apiRequest(endpoint, method = "GET", body = null) {
const url = `${STRAPI_URL}/api${endpoint}`;
const options = {
method,
headers: HEADERS,
...(body ? { body: JSON.stringify(body) } : {}),
};
const response = await fetch(url, options);
const data = await response.json().catch(() => null);
if (!response.ok) {
const errMsg = data?.error?.message || JSON.stringify(data);
throw new Error(`${response.status}: ${errMsg}`);
}
return data;
}
async function findGameBySlug(slug) {
try {
const res = await apiRequest(`/games?filters[slug][$eq]=${slug}`);
return res.data?.[0] || null;
} catch {
return null;
}
}
async function findDocumentaryBySlug(slug) {
try {
// Documentary doesn't have slug, find via game relation
const game = await findGameBySlug(slug);
if (!game) return null;
const res = await apiRequest(`/documentaries?filters[game][id][$eq]=${game.id}`);
return res.data?.[0] || null;
} catch {
return null;
}
}
async function createGame(gameData) {
const payload = {
data: {
...gameData,
publishedAt: new Date().toISOString(),
},
};
const created = await apiRequest("/games", "POST", payload);
console.log(` ✅ Created game '${gameData.title}' (id: ${created.data.id})`);
return created.data;
}
async function updateGame(documentId, gameData) {
const payload = {
data: {
...gameData,
},
};
const updated = await apiRequest(`/games/${documentId}`, "PUT", payload);
console.log(` 🔄 Updated game '${gameData.title}' (id: ${updated.data.id})`);
return updated.data;
}
async function createDocumentary(docData, gameId) {
const payload = {
data: {
title: docData.title,
description: docData.description,
game: gameId,
publishedAt: new Date().toISOString(),
},
};
const created = await apiRequest("/documentaries", "POST", payload);
console.log(` ✅ Created documentary '${docData.title}' (id: ${created.data.id})`);
return created.data;
}
async function updateDocumentary(documentId, docData, gameId) {
const payload = {
data: {
title: docData.title,
description: docData.description,
game: gameId,
},
};
const updated = await apiRequest(`/documentaries/${documentId}`, "PUT", payload);
console.log(` 🔄 Updated documentary '${docData.title}' (id: ${updated.data.id})`);
return updated.data;
}
async function deleteChaptersByDocumentary(documentaryId) {
try {
// Find all chapters linked to this documentary
const res = await apiRequest(`/chapters?filters[documentary][id][$eq]=${documentaryId}&pagination[pageSize]=100`);
const chapters = res.data || [];
for (const ch of chapters) {
await apiRequest(`/chapters/${ch.documentId}`, "DELETE");
console.log(` 🗑️ Deleted old chapter '${ch.title}'`);
}
} catch (err) {
console.log(` ⚠️ Could not delete old chapters: ${err.message}`);
}
}
async function createChapters(chapters, documentaryId) {
for (const chapter of chapters) {
const payload = {
data: {
title: chapter.title,
content: chapter.content,
order: chapter.order,
documentary: documentaryId,
publishedAt: new Date().toISOString(),
},
};
const created = await apiRequest("/chapters", "POST", payload);
console.log(` ✅ Chapter ${chapter.order}: '${chapter.title}' (id: ${created.data.id})`);
}
}
async function importDocumentaries(jsonPath) {
console.log(`\n📖 Importing documentaries from: ${jsonPath}`);
console.log(`🔗 Strapi URL: ${STRAPI_URL}\n`);
const raw = fs.readFileSync(jsonPath, "utf-8");
const seedData = JSON.parse(raw);
if (!seedData.documentaries || !Array.isArray(seedData.documentaries)) {
throw new Error("Invalid seed file: 'documentaries' array not found");
}
let imported = 0;
let updated = 0;
let errors = 0;
for (const doc of seedData.documentaries) {
try {
console.log(`\n🎬 Processing: ${doc.title}`);
const existingGame = await findGameBySlug(doc.game.slug);
let game;
if (existingGame) {
game = await updateGame(existingGame.documentId, doc.game);
updated++;
} else {
game = await createGame(doc.game);
imported++;
}
const existingDoc = await findDocumentaryBySlug(doc.game.slug);
let documentary;
if (existingDoc) {
documentary = await updateDocumentary(existingDoc.documentId, doc, game.id);
// Delete old chapters and recreate
console.log(` 📝 Recreating chapters...`);
await deleteChaptersByDocumentary(existingDoc.documentId);
await createChapters(doc.chapters, existingDoc.documentId);
} else {
documentary = await createDocumentary(doc, game.id);
console.log(` 📝 Creating ${doc.chapters.length} chapters...`);
await createChapters(doc.chapters, documentary.documentId);
}
console.log(` ✨ Completed: ${doc.title}`);
} catch (err) {
errors++;
console.error(` ❌ Failed to import '${doc.title}':`, err.message);
}
}
console.log(`\n📊 Import Summary:`);
console.log(` Total documentaries: ${seedData.documentaries.length}`);
console.log(` Games created: ${imported}`);
console.log(` Games updated: ${updated}`);
console.log(` Failed: ${errors}`);
console.log(`\n✨ Done!\n`);
}
// Main
const jsonPath = process.argv[2] || path.join(__dirname, "..", "docs", "seeds", "documentaries-seed.json");
if (!fs.existsSync(jsonPath)) {
console.error(`❌ Seed file not found: ${jsonPath}`);
console.error(`Usage: node scripts/import-documentaries.js [path-to-json]`);
process.exit(1);
}
importDocumentaries(jsonPath).catch((err) => {
console.error("\n💥 Fatal error:", err.message);
process.exit(1);
});

204
scripts/install.sh Executable file
View File

@@ -0,0 +1,204 @@
#!/bin/bash
# =============================================================================
# install.sh — Instalador maestro de Project Afterlife
# =============================================================================
# Uso: ./install.sh [main|nier|dbo|maple2|fusionfall]
#
# Si no se especifica argumento, detecta automaticamente la VM por hostname.
# =============================================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
COMPOSE_DIR="$PROJECT_DIR/docker"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# -----------------------------------------------------------------------------
# Detectar VM objetivo
# -----------------------------------------------------------------------------
detect_vm() {
local hostname
hostname=$(hostname -s 2>/dev/null || hostname)
case "$hostname" in
*main*|*web*|*auth*|*portal*)
echo "main"
;;
*nier*|*reincarnation*)
echo "nier"
;;
*dbo*|*dragonball*)
echo "dbo"
;;
*maple*|*ms2*)
echo "maple2"
;;
*fusion*|*fusionfall*)
echo "fusionfall"
;;
*)
echo ""
;;
esac
}
# -----------------------------------------------------------------------------
# Verificar prerequisitos
# -----------------------------------------------------------------------------
check_prerequisites() {
log_info "Verificando prerequisitos..."
if ! command -v docker &> /dev/null; then
log_error "Docker no esta instalado. Ejecuta primero el script de setup correspondiente."
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
log_error "Docker Compose no esta instalado."
exit 1
fi
if ! command -v git &> /dev/null; then
log_error "Git no esta instalado."
exit 1
fi
log_ok "Prerequisitos verificados."
}
# -----------------------------------------------------------------------------
# Verificar archivo .env
# -----------------------------------------------------------------------------
check_env() {
if [ ! -f "$COMPOSE_DIR/.env" ]; then
log_warn "No se encontro docker/.env"
log_info "Copiando desde .env.example..."
cp "$COMPOSE_DIR/.env.example" "$COMPOSE_DIR/.env"
log_warn "EDITA el archivo $COMPOSE_DIR/.env antes de continuar."
log_warn "Al menos debes configurar:"
log_warn " - DATABASE_PASSWORD"
log_warn " - APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, etc. (Strapi)"
log_warn " - AUTHENTIK_SECRET_KEY"
log_warn " - MINIO_ROOT_PASSWORD"
exit 1
fi
}
# -----------------------------------------------------------------------------
# Instalar VM Principal
# -----------------------------------------------------------------------------
install_main() {
log_info "Instalando VM PRINCIPAL (Web + Auth + CMS)..."
cd "$COMPOSE_DIR"
log_info "Descargando imagenes y construyendo..."
docker compose -f docker-compose.main.yml pull
docker compose -f docker-compose.main.yml build
log_info "Iniciando servicios..."
docker compose -f docker-compose.main.yml up -d
log_info "Esperando a que los servicios esten listos..."
sleep 10
log_info "Verificando estado..."
docker compose -f docker-compose.main.yml ps
echo ""
log_ok "=== VM PRINCIPAL INSTALADA ==="
echo ""
echo "Accesos:"
echo " Web: https://TU_DOMINIO"
echo " CMS Admin: https://TU_DOMINIO/admin"
echo " Authentik: https://TU_DOMINIO/auth (si configuras proxy)"
echo " MinIO: https://TU_DOMINIO:9001 (o via proxy)"
echo ""
echo "Proximos pasos:"
echo " 1. Configura Authentik: docker exec -it main-authentik-server ak bootstrap"
echo " 2. Crea el admin de Strapi: docker exec -it main-cms npm run strapi admin:create-user"
echo " 3. Genera el API Token en Strapi Admin > Settings > API Tokens"
echo " 4. Reconstruye Next.js: docker compose -f docker-compose.main.yml exec web npm run build"
echo ""
}
# -----------------------------------------------------------------------------
# Instalar VM de Juego
# -----------------------------------------------------------------------------
install_game() {
local game="$1"
local compose_file="docker-compose.${game}.yml"
log_info "Instalando VM de juego: $game..."
cd "$COMPOSE_DIR"
if [ ! -f "$compose_file" ]; then
log_error "No se encontro $compose_file"
exit 1
fi
log_info "Construyendo e iniciando..."
docker compose -f "$compose_file" build
docker compose -f "$compose_file" up -d
log_info "Verificando estado..."
docker compose -f "$compose_file" ps
echo ""
log_ok "=== VM $game INSTALADA ==="
echo ""
}
# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
main() {
echo ""
echo "=========================================="
echo " Project Afterlife — Instalador"
echo "=========================================="
echo ""
local vm_target="${1:-}"
if [ -z "$vm_target" ]; then
vm_target=$(detect_vm)
if [ -n "$vm_target" ]; then
log_info "VM detectada automaticamente: $vm_target"
else
log_error "No se pudo detectar la VM automaticamente."
echo "Uso: $0 [main|nier|dbo|maple2|fusionfall]"
exit 1
fi
fi
check_prerequisites
check_env
case "$vm_target" in
main)
install_main
;;
nier|dbo|maple2|fusionfall)
install_game "$vm_target"
;;
*)
log_error "VM desconocida: $vm_target"
echo "Opciones validas: main, nier, dbo, maple2, fusionfall"
exit 1
;;
esac
log_ok "Instalacion completada!"
}
main "$@"

87
scripts/localize-to-es.js Normal file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env node
/**
* Create Spanish (es) localizations for all English (en) content in Strapi
*/
const STRAPI_URL = process.env.STRAPI_URL || "http://localhost:1337";
async function apiRequest(endpoint, method = "GET", body = null) {
const url = `${STRAPI_URL}/api${endpoint}`;
const options = {
method,
headers: { "Content-Type": "application/json" },
...(body ? { body: JSON.stringify(body) } : {}),
};
const response = await fetch(url, options);
const data = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`${response.status}: ${JSON.stringify(data)}`);
}
return data;
}
async function getAllEnDocuments(path) {
const res = await apiRequest(`${path}?pagination[pageSize]=200&locale=en`);
const items = res.data || [];
// Deduplicate by documentId
const seen = new Set();
return items.filter((item) => {
if (seen.has(item.documentId)) return false;
seen.add(item.documentId);
return true;
});
}
async function createEsLocalization(path, documentId, data) {
return apiRequest(`${path}/${documentId}?locale=es`, "PUT", { data });
}
async function main() {
console.log("🌐 Creating Spanish localizations...\n");
// 1. Documentaries
console.log("📽️ Documentaries:");
const docs = await getAllEnDocuments("/documentaries");
for (const doc of docs) {
try {
await createEsLocalization("/documentaries", doc.documentId, {
title: doc.title,
description: doc.description,
});
console.log(`${doc.title}`);
} catch (err) {
if (err.message.includes("already exists")) {
console.log(` ⚠️ Already has es: ${doc.title}`);
} else {
console.log(`${doc.title}: ${err.message}`);
}
}
}
// 2. Chapters
console.log("\n📖 Chapters:");
const chapters = await getAllEnDocuments("/chapters");
for (const ch of chapters) {
try {
await createEsLocalization("/chapters", ch.documentId, {
title: ch.title,
content: ch.content,
order: ch.order,
});
console.log(`${ch.title}`);
} catch (err) {
if (err.message.includes("already exists")) {
console.log(` ⚠️ Already has es: ${ch.title}`);
} else {
console.log(`${ch.title}: ${err.message}`);
}
}
}
console.log("\n✨ Done!");
}
main().catch((err) => {
console.error("💥 Fatal error:", err.message);
process.exit(1);
});

138
scripts/setup-game-vm.sh Executable file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
# =============================================================================
# setup-game-vm.sh — Preparar una VM limpia para un servidor de juegos
# =============================================================================
# Uso: ./setup-game-vm.sh <nombre-del-juego>
# Ejemplo: ./setup-game-vm.sh nier
#
# Instala Docker, Docker Compose, Git, configura firewall y deja todo listo
# para ejecutar install.sh
# =============================================================================
set -e
GAME="$1"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
if [ -z "$GAME" ]; then
log_error "Debes especificar el nombre del juego."
echo "Uso: $0 <nombre-del-juego>"
echo "Ejemplos: nier, dbo, maple2, fusionfall"
exit 1
fi
# -----------------------------------------------------------------------------
# Puertos por juego
# -----------------------------------------------------------------------------
declare -A GAME_PORTS
declare -A GAME_RAM
declare -A GAME_DESC
GAME_PORTS[nier]="80/tcp 443/tcp"
GAME_PORTS[dbo]="22000/tcp 22001/tcp 22002/tcp 22003/tcp 22004/tcp 22005/tcp 22006/tcp 22007/tcp 22008/tcp 22009/tcp 22010/tcp"
GAME_PORTS[maple2]="20001/tcp 21001/tcp 20003/tcp 21003/tcp 4000/tcp"
GAME_PORTS[fusionfall]="23000/tcp 23001/tcp"
GAME_RAM[nier]="1"
GAME_RAM[dbo]="2"
GAME_RAM[maple2]="2"
GAME_RAM[fusionfall]="512"
GAME_DESC[nier]="NieR Reincarnation (MariesWonderland)"
GAME_DESC[dbo]="Dragon Ball Online (DBO Global)"
GAME_DESC[maple2]="MapleStory 2 (Maple2)"
GAME_DESC[fusionfall]="FusionFall (OpenFusion)"
PORTS="${GAME_PORTS[$GAME]}"
RAM="${GAME_RAM[$GAME]}"
DESC="${GAME_DESC[$GAME]}"
if [ -z "$PORTS" ]; then
log_error "Juego desconocido: $GAME"
exit 1
fi
echo ""
echo "=========================================="
echo " Setup VM: $DESC"
echo "=========================================="
echo ""
# -----------------------------------------------------------------------------
# 1. Actualizar sistema
# -----------------------------------------------------------------------------
log_info "Actualizando sistema..."
apt-get update && apt-get upgrade -y
# -----------------------------------------------------------------------------
# 2. Instalar dependencias
# -----------------------------------------------------------------------------
log_info "Instalando dependencias..."
apt-get install -y curl wget git ufw ca-certificates gnupg lsb-release
# -----------------------------------------------------------------------------
# 3. Instalar Docker
# -----------------------------------------------------------------------------
if ! command -v docker &> /dev/null; then
log_info "Instalando Docker..."
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker
systemctl start docker
log_ok "Docker instalado."
else
log_ok "Docker ya estaba instalado."
fi
# -----------------------------------------------------------------------------
# 4. Firewall
# -----------------------------------------------------------------------------
log_info "Configurando firewall..."
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
for port in $PORTS; do
ufw allow "$port"
log_info " Puerto abierto: $port"
done
ufw --force enable
log_ok "Firewall configurado."
# -----------------------------------------------------------------------------
# 5. Directorio del proyecto
# -----------------------------------------------------------------------------
PROJECT_DIR="/opt/project-afterlife"
mkdir -p "$PROJECT_DIR"
# -----------------------------------------------------------------------------
# 6. Resumen
# -----------------------------------------------------------------------------
echo ""
echo "=========================================="
echo " VM $GAME — Setup Completado"
echo "=========================================="
echo ""
echo "Juego: $DESC"
echo "RAM estimada: ${RAM} GB"
echo "Puertos: $PORTS"
echo "Docker: $(docker --version)"
echo "UFW: activo"
echo ""
echo "Proximos pasos:"
echo " 1. git clone https://git.consultoria-as.com/consultoria-as/project-afterlife.git $PROJECT_DIR"
echo " 2. cd $PROJECT_DIR"
echo " 3. cp docker/.env.example docker/.env"
echo " 4. Edita las variables especificas de $GAME en docker/.env"
echo " 5. ./scripts/install.sh $GAME"
echo ""

138
scripts/setup-main.sh Executable file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
# =============================================================================
# setup-main.sh — Preparar una VM limpia Ubuntu/Debian para la VM Principal
# =============================================================================
# Este script instala Docker, Docker Compose, Git, configura firewall basico
# y genera secrets automaticos para la VM Principal.
# =============================================================================
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
# -----------------------------------------------------------------------------
# 1. Actualizar sistema
# -----------------------------------------------------------------------------
log_info "Actualizando paquetes del sistema..."
apt-get update && apt-get upgrade -y
# -----------------------------------------------------------------------------
# 2. Instalar dependencias basicas
# -----------------------------------------------------------------------------
log_info "Instalando dependencias..."
apt-get install -y \
curl \
wget \
git \
ufw \
software-properties-common \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
jq \
openssl
# -----------------------------------------------------------------------------
# 3. Instalar Docker
# -----------------------------------------------------------------------------
if ! command -v docker &> /dev/null; then
log_info "Instalando Docker..."
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker
systemctl start docker
log_ok "Docker instalado."
else
log_ok "Docker ya estaba instalado."
fi
# -----------------------------------------------------------------------------
# 4. Verificar Docker Compose v2
# -----------------------------------------------------------------------------
if ! docker compose version &> /dev/null; then
log_error "Docker Compose v2 no disponible. Instalando..."
apt-get install -y docker-compose-plugin
fi
log_ok "Docker Compose v2: $(docker compose version --short)"
# -----------------------------------------------------------------------------
# 5. Configurar firewall UFW
# -----------------------------------------------------------------------------
log_info "Configurando firewall..."
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
log_ok "Firewall configurado. Puertos permitidos: SSH, 80, 443"
# -----------------------------------------------------------------------------
# 6. Crear directorio del proyecto
# -----------------------------------------------------------------------------
PROJECT_DIR="/opt/project-afterlife"
mkdir -p "$PROJECT_DIR"
log_info "Directorio del proyecto: $PROJECT_DIR"
# -----------------------------------------------------------------------------
# 7. Generar secrets automaticos
# -----------------------------------------------------------------------------
log_info "Generando secrets para .env..."
GEN_KEY() { openssl rand -base64 32; }
SECRETS_FILE="/tmp/afterlife-secrets.txt"
{
echo "# Secrets generados automaticamente el $(date)"
echo "DATABASE_NAME=afterlife"
echo "DATABASE_USERNAME=afterlife"
echo "DATABASE_PASSWORD=$(GEN_KEY)"
echo "APP_KEYS=$(GEN_KEY),$(GEN_KEY),$(GEN_KEY),$(GEN_KEY)"
echo "API_TOKEN_SALT=$(GEN_KEY)"
echo "ADMIN_JWT_SECRET=$(GEN_KEY)"
echo "TRANSFER_TOKEN_SALT=$(GEN_KEY)"
echo "JWT_SECRET=$(GEN_KEY)"
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60)"
echo "AUTHENTIK_POSTGRES_PASSWORD=$(GEN_KEY)"
echo "MINIO_ROOT_USER=afterlife"
echo "MINIO_ROOT_PASSWORD=$(GEN_KEY)"
} > "$SECRETS_FILE"
log_ok "Secrets generados en: $SECRETS_FILE"
log_warn "COPIA estos valores a docker/.env antes de ejecutar install.sh"
# -----------------------------------------------------------------------------
# 8. Mostrar resumen
# -----------------------------------------------------------------------------
echo ""
echo "=========================================="
echo " VM Principal — Setup Completado"
echo "=========================================="
echo ""
echo "Docker: $(docker --version)"
echo "Docker Compose: $(docker compose version --short)"
echo "Git: $(git --version)"
echo "UFW: $(ufw status | head -1)"
echo ""
echo "Directorio: $PROJECT_DIR"
echo "Secrets: $SECRETS_FILE"
echo ""
echo "Proximos pasos:"
echo " 1. git clone https://git.consultoria-as.com/consultoria-as/project-afterlife.git $PROJECT_DIR"
echo " 2. cd $PROJECT_DIR"
echo " 3. cp docker/.env.example docker/.env"
echo " 4. Copia los secrets de $SECRETS_FILE a docker/.env"
echo " 5. Edita PUBLIC_STRAPI_URL, NEXT_PUBLIC_SITE_URL, etc."
echo " 6. ./scripts/install.sh main"
echo ""