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
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:
105
scripts/deploy-vm.sh
Executable file
105
scripts/deploy-vm.sh
Executable 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
|
||||
71
scripts/fix-es-chapters.js
Normal file
71
scripts/fix-es-chapters.js
Normal 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);
|
||||
});
|
||||
220
scripts/import-documentaries.js
Normal file
220
scripts/import-documentaries.js
Normal 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
204
scripts/install.sh
Executable 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
87
scripts/localize-to-es.js
Normal 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
138
scripts/setup-game-vm.sh
Executable 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
138
scripts/setup-main.sh
Executable 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 ""
|
||||
Reference in New Issue
Block a user