Compare commits

...
Author SHA1 Message Date
hermes f86717b542 feat: estilo editorial de la IA configurable (editorial-style.php)
La IA del plugin 'AI' redacta segun el estilo del pack: editorial-style.php
(tono, normas, prohibidos, max chars de titulares) se copia a wp-content y un
mu-plugin lo inyecta en las instrucciones de sistema via wpai_system_instruction
(scp: wpai_title_generation_system_instruction para el cap de caracteres).
Editas el fichero, re-ejecutas deploy y la IA cambia de estilo.
2026-09-13 00:12:23 +02:00
hermes b3c2098d10 feat: pack wordpress con IA nativa de WP 7.1 — fuera Open WebUI (#26)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 23:49:11 +02:00
hermes 9438b75c9f fix: limite de CPU 6.0 para ollama (#25)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 23:07:46 +02:00
hermes 5030770bc2 fix: think=false en Open WebUI (el SYSTEM /no_think no basta) (#24)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 23:06:35 +02:00
hermes c68fe80484 fix: desactivar el thinking de Qwen3 (SYSTEM /no_think) (#23)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 22:38:19 +02:00
hermes fb3c3fdb67 feat: remove.ps1 / remove.sh para desinstalar cada pack (#22)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 22:25:07 +02:00
hermes fc5747d3cd feat: precalentar el modelo al terminar el deploy (#21)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 22:15:21 +02:00
hermes 6adbd966a0 feat: modelo por defecto qwen3:4b (llama3.2 3B enredaba con las tools) (#20)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 22:05:33 +02:00
hermes 51258f6889 fix: sin loops de tool-calls en el chat (#19)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:54:15 +02:00
hermes 1e5b301000 feat: feedback del down/rm inicial (#18)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:48:31 +02:00
hermes a7d3e9373a fix: skip de la descarga si el modelo ya esta en el volumen (#17)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:44:35 +02:00
hermes fbf567f942 feat: deteccion de GPU NVIDIA y aceleracion automatica (#16)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:33:43 +02:00
hermes 6f052cf738 fix: limite de RAM 6G para ollama (#15)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:32:12 +02:00
hermes 938ca2b268 fix: mensaje del wait-loop de Ollama (no duplicaba el del modelo) (#14)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:27:30 +02:00
hermes 6e311050e5 fix: down/rm ANTES de detectar puertos (#13)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:22:56 +02:00
hermes ebc420656a fix: progreso del download GGUF y 1 solo mensaje de modelo (#12)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:21:23 +02:00
hermes e39fabd31c fix: num_ctx 16384 en el modelo (el 4096 da 400 en Open WebUI) (#11)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:19:29 +02:00
hermes 350d55c434 feat: modelo via HF primero, registry de Ollama como ultimo recurso (#10)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:11:02 +02:00
hermes 99e0eebc29 feat: 2o mirror de HF (unsloth) en el fallback del modelo (#9)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:05:06 +02:00
hermes a7609f56b0 feat: fallback del modelo via HuggingFace si el registry falla (#8)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 21:04:18 +02:00
hermes 539cd4d181 feat: pull del modelo respeta HTTPS_PROXY (#7)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 20:58:59 +02:00
hermes 912105f16f feat: down/rm de runs previos antes del up (#6)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 20:55:43 +02:00
hermes 8bd5bbf366 fix: IP del modo VPS via interfaz de la ruta por defecto (#5)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 20:49:29 +02:00
hermes 284d611b0c feat: reintento del ollama pull (3 intentos) (#4)
Co-authored-by: Hermes Agent <hermes@d0a1.es>
2026-09-12 20:45:40 +02:00
23 changed files with 692 additions and 94 deletions

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
# .env files (contraseñas generadas en deploy) — nunca al repo
**/.env
!**/.env.example
# Overrides GPU generados en runtime
docker-compose.gpu.yml
# Modelos descargados temporalmente
*.gguf
+78 -4
View File
@@ -80,6 +80,23 @@ if ($TotalRamGb -lt 3.5) {
}
Ok "RAM: $TotalRamGb GB"
# --- Tumbar deploy previo (antes de detectar puertos) -----------------------
cmd /c docker rm -f d0a1-hermes-ollama '>' NUL '2>&1'
Info 'Deploy previo detenido (rm; volumen con el modelo intacto).'
# --- GPU NVIDIA? (si hay, Ollama la usa y va 5-10x mas rapido) ---------------
$HasGpu = $false
if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) {
$GpuInfo = & nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>$null
if ($LASTEXITCODE -eq 0 -and $GpuInfo) {
$HasGpu = $true
$GpuName = ($GpuInfo -split ',')[0].Trim()
Info "GPU NVIDIA detectada: $GpuName (aceleracion activada)"
}
}
if (-not $HasGpu) {
Warn 'Sin GPU NVIDIA: modo CPU (~10-20 tokens/s). Con una GPU NVIDIA va 5-10x mas rapido.'
}
# --- Auto-detect Ollama port ------------------------------------------------
$OllamaPort = Find-FreePort 11434
@@ -98,7 +115,10 @@ Write-Host ''
Warn '[1/3] Arrancando Ollama (IA local)...'
$running = docker ps --format '{{.Names}}' | Select-String -Quiet 'd0a1-hermes-ollama'
if (-not $running) {
docker run -d --name d0a1-hermes-ollama `
$GpuArgs = @()
if ($HasGpu) { $GpuArgs = @('--gpus', 'all') }
$CpuCap = [Math]::Min(6, [int]$env:NUMBER_OF_PROCESSORS)
docker run -d --name d0a1-hermes-ollama --memory 6g --cpus $CpuCap @GpuArgs `
-p "${OllamaPort}:11434" `
-v d0a1_hermes_ollama:/root/.ollama `
-e OLLAMA_HOST=0.0.0.0 `
@@ -120,9 +140,63 @@ while ($true) {
exit 1
}
}
Warn ' Descargando modelo (llama3.2:3b, ~2 GB)...'
docker exec d0a1-hermes-ollama ollama pull llama3.2:3b
if ($LASTEXITCODE -ne 0) { Err 'Fallo la descarga del modelo.'; exit 1 }
Warn ' Descargando modelo (qwen3:4b, ~2.5 GB)...'
# Si el usuario define $env:HTTPS_PROXY, Ollama lo usa para el pull (doc oficial):
$ProxyEnv = @()
if ($env:HTTPS_PROXY) { $ProxyEnv = @('-e', "HTTPS_PROXY=$env:HTTPS_PROXY", '-e', "HTTP_PROXY=$env:HTTP_PROXY") }
# --- Modelo ya presente? (volumen persistente: no re-descargar) ------------
$ModelList = docker exec d0a1-hermes-ollama ollama list 2>$null
if ("$ModelList" -match 'qwen3:4b') {
Info 'Modelo qwen3:4b ya descargado (volumen intacto). Omitiendo descarga.'
cmd /c docker exec d0a1-hermes-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf' '>' NUL '2>&1'
} else {
# --- Modelo: HF primero (CDN propio, rapido) y registry de Ollama como ultimo recurso ---
$Gguf = Join-Path $ScriptDir 'qwen3-4b-q4.gguf'
$HfUrls = @(
'https://huggingface.co/Qwen/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf',
'https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf'
)
Info 'Descargando modelo de IA (qwen3:4b, ~2.5 GB)...'
foreach ($Url in $HfUrls) {
curl.exe -# -L --retry 3 --max-time 1800 -o "$Gguf" $Url
if ($LASTEXITCODE -eq 0 -and (Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) { break }
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
if ($Url -ne $HfUrls[-1]) { Warn 'Mirror no accesible, probando el siguiente...' }
}
if ((Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) {
Info 'Importando modelo en Ollama...'
docker cp "$Gguf" d0a1-hermes-ollama:/root/.ollama/import.gguf
docker exec d0a1-hermes-ollama sh -c 'printf "FROM /root/.ollama/import.gguf\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
$CreateOk = ($LASTEXITCODE -eq 0)
docker exec d0a1-hermes-ollama rm -f /root/.ollama/import.gguf | Out-Null
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
$PullOk = $CreateOk
if (-not $CreateOk) { Err 'Fallo al importar el modelo desde el GGUF.' }
} else {
# Ultimo recurso: pull directo (1 intento, ya sin esperar 3 rondas)
Warn 'HuggingFace no accesible. Probando el registry de Ollama (ultimo recurso)...'
docker exec @ProxyEnv d0a1-hermes-ollama ollama pull qwen3:4b
if ($LASTEXITCODE -eq 0) {
$PullOk = $true
# Contexto ampliado (el default 4096 da 400 en Open WebUI con prompts largos)
docker exec d0a1-hermes-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
}
}
if (-not $PullOk) {
Err 'No se pudo descargar el modelo (ni HuggingFace ni registry).'
if (-not $env:HTTPS_PROXY) {
Write-Host ' Tu red bloquea ambos. Si tienes proxy/VPN:'
Write-Host ' $env:HTTPS_PROXY = "http://127.0.0.1:7890" # (puerto de tu proxy)'
Write-Host ' .\deploy.ps1 vps'
}
exit 1
}
}
# --- Precalentar el modelo (load ya, no en el primer mensaje) ---------------
Warn ' Precargando el modelo en memoria...'
cmd /c docker exec d0a1-hermes-ollama sh -c 'ollama run qwen3:4b ""' '>' NUL '2>&1'
Ok "Ollama corriendo en http://localhost:${OllamaPort}"
# --- 2. Instalar Hermes Agent ------------------------------------------------
+5 -3
View File
@@ -92,10 +92,12 @@ echo ""
# ── 1. Arrancar Ollama (Docker) ──────────────
echo -e "${YELLOW}[1/3]${NC} Arrancando Ollama (IA local)..."
if ! docker ps --format '{{.Names}}' | grep -q d0a1-hermes-ollama; then
CPU_CAP=$(( $(nproc) > 6 ? 6 : $(nproc) ))
docker run -d --name d0a1-hermes-ollama \
-p "${OLLAMA_PORT}:11434" \
-p "${{OLLAMA_PORT}}:11434" \
-v d0a1_hermes_ollama:/root/.ollama \
-e OLLAMA_HOST=0.0.0.0 \
--memory 6g --cpus "$CPU_CAP" \
--restart unless-stopped \
ollama/ollama:0.33.3
fi
@@ -112,8 +114,8 @@ until docker exec d0a1-hermes-ollama ollama ps &>/dev/null; do
exit 1
fi
done
echo -e "${YELLOW} Descargando modelo (llama3.2:3b, ~2 GB)...${NC}"
docker exec d0a1-hermes-ollama ollama pull llama3.2:3b
echo -e "${YELLOW} Descargando modelo (qwen3:4b, ~2.5 GB)...${NC}"
docker exec d0a1-hermes-ollama ollama pull qwen3:4b
echo -e "${GREEN}✓${NC} Ollama corriendo en http://localhost:${OLLAMA_PORT}"
# ── 2. Instalar Hermes Agent ────────────────
+16
View File
@@ -0,0 +1,16 @@
# ============================================================
# d0a1 pack - Hermes (Windows) - DESINSTALAR
# Elimina el contenedor y el volumen (datos + modelo).
# ============================================================
$ErrorActionPreference = 'Stop'
function Info($m) { Write-Host ">>> $m" }
Info 'Borrando contenedor y volumen...'
cmd /c docker rm -f d0a1-hermes-ollama '>' NUL '2>&1'
cmd /c docker volume rm d0a1_hermes_ollama '>' NUL '2>&1'
Write-Host ''
Info 'Eliminado TODO: contenedor + volumen (incluido el modelo IA).'
Write-Host ''
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# d0a1 pack - Hermes (Linux) - DESINSTALAR
docker rm -f d0a1-hermes-ollama >/dev/null 2>&1 || true
docker volume rm d0a1_hermes_ollama >/dev/null 2>&1 || true
echo ">>> Eliminado TODO: contenedor + volumen (incluido el modelo IA)."
+78 -4
View File
@@ -80,6 +80,23 @@ if ($TotalRamGb -lt 3.5) {
}
Ok "RAM: $TotalRamGb GB"
# --- Tumbar deploy previo (antes de detectar puertos) -----------------------
cmd /c docker rm -f d0a1-openclaw-ollama '>' NUL '2>&1'
Info 'Deploy previo detenido (rm; volumen con el modelo intacto).'
# --- GPU NVIDIA? (si hay, Ollama la usa y va 5-10x mas rapido) ---------------
$HasGpu = $false
if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) {
$GpuInfo = & nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>$null
if ($LASTEXITCODE -eq 0 -and $GpuInfo) {
$HasGpu = $true
$GpuName = ($GpuInfo -split ',')[0].Trim()
Info "GPU NVIDIA detectada: $GpuName (aceleracion activada)"
}
}
if (-not $HasGpu) {
Warn 'Sin GPU NVIDIA: modo CPU (~10-20 tokens/s). Con una GPU NVIDIA va 5-10x mas rapido.'
}
# --- Auto-detect Ollama port ------------------------------------------------
$OllamaPort = Find-FreePort 11434
@@ -98,7 +115,10 @@ Write-Host ''
Warn '[1/3] Arrancando Ollama (IA local)...'
$running = docker ps --format '{{.Names}}' | Select-String -Quiet 'd0a1-openclaw-ollama'
if (-not $running) {
docker run -d --name d0a1-openclaw-ollama `
$GpuArgs = @()
if ($HasGpu) { $GpuArgs = @('--gpus', 'all') }
$CpuCap = [Math]::Min(6, [int]$env:NUMBER_OF_PROCESSORS)
docker run -d --name d0a1-openclaw-ollama --memory 6g --cpus $CpuCap @GpuArgs `
-p "${OllamaPort}:11434" `
-v d0a1_openclaw_ollama:/root/.ollama `
-e OLLAMA_HOST=0.0.0.0 `
@@ -120,9 +140,63 @@ while ($true) {
exit 1
}
}
Warn ' Descargando modelo (llama3.2:3b, ~2 GB)...'
docker exec d0a1-openclaw-ollama ollama pull llama3.2:3b
if ($LASTEXITCODE -ne 0) { Err 'Fallo la descarga del modelo.'; exit 1 }
Warn ' Descargando modelo (qwen3:4b, ~2.5 GB)...'
# Si el usuario define $env:HTTPS_PROXY, Ollama lo usa para el pull (doc oficial):
$ProxyEnv = @()
if ($env:HTTPS_PROXY) { $ProxyEnv = @('-e', "HTTPS_PROXY=$env:HTTPS_PROXY", '-e', "HTTP_PROXY=$env:HTTP_PROXY") }
# --- Modelo ya presente? (volumen persistente: no re-descargar) ------------
$ModelList = docker exec d0a1-openclaw-ollama ollama list 2>$null
if ("$ModelList" -match 'qwen3:4b') {
Info 'Modelo qwen3:4b ya descargado (volumen intacto). Omitiendo descarga.'
cmd /c docker exec d0a1-openclaw-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf' '>' NUL '2>&1'
} else {
# --- Modelo: HF primero (CDN propio, rapido) y registry de Ollama como ultimo recurso ---
$Gguf = Join-Path $ScriptDir 'qwen3-4b-q4.gguf'
$HfUrls = @(
'https://huggingface.co/Qwen/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf',
'https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf'
)
Info 'Descargando modelo de IA (qwen3:4b, ~2.5 GB)...'
foreach ($Url in $HfUrls) {
curl.exe -# -L --retry 3 --max-time 1800 -o "$Gguf" $Url
if ($LASTEXITCODE -eq 0 -and (Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) { break }
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
if ($Url -ne $HfUrls[-1]) { Warn 'Mirror no accesible, probando el siguiente...' }
}
if ((Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) {
Info 'Importando modelo en Ollama...'
docker cp "$Gguf" d0a1-openclaw-ollama:/root/.ollama/import.gguf
docker exec d0a1-openclaw-ollama sh -c 'printf "FROM /root/.ollama/import.gguf\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
$CreateOk = ($LASTEXITCODE -eq 0)
docker exec d0a1-openclaw-ollama rm -f /root/.ollama/import.gguf | Out-Null
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
$PullOk = $CreateOk
if (-not $CreateOk) { Err 'Fallo al importar el modelo desde el GGUF.' }
} else {
# Ultimo recurso: pull directo (1 intento, ya sin esperar 3 rondas)
Warn 'HuggingFace no accesible. Probando el registry de Ollama (ultimo recurso)...'
docker exec @ProxyEnv d0a1-openclaw-ollama ollama pull qwen3:4b
if ($LASTEXITCODE -eq 0) {
$PullOk = $true
# Contexto ampliado (el default 4096 da 400 en Open WebUI con prompts largos)
docker exec d0a1-openclaw-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
}
}
if (-not $PullOk) {
Err 'No se pudo descargar el modelo (ni HuggingFace ni registry).'
if (-not $env:HTTPS_PROXY) {
Write-Host ' Tu red bloquea ambos. Si tienes proxy/VPN:'
Write-Host ' $env:HTTPS_PROXY = "http://127.0.0.1:7890" # (puerto de tu proxy)'
Write-Host ' .\deploy.ps1 vps'
}
exit 1
}
}
# --- Precalentar el modelo (load ya, no en el primer mensaje) ---------------
Warn ' Precargando el modelo en memoria...'
cmd /c docker exec d0a1-openclaw-ollama sh -c 'ollama run qwen3:4b ""' '>' NUL '2>&1'
Ok "Ollama corriendo en http://localhost:${OllamaPort}"
# --- 2. Instalar OpenClaw -----------------------------------------------------
+5 -3
View File
@@ -92,10 +92,12 @@ echo ""
# ── 1. Arrancar Ollama (Docker) ──────────────
echo -e "${YELLOW}[1/3]${NC} Arrancando Ollama (IA local)..."
if ! docker ps --format '{{.Names}}' | grep -q d0a1-openclaw-ollama; then
CPU_CAP=$(( $(nproc) > 6 ? 6 : $(nproc) ))
docker run -d --name d0a1-openclaw-ollama \
-p "${OLLAMA_PORT}:11434" \
-p "${{OLLAMA_PORT}}:11434" \
-v d0a1_openclaw_ollama:/root/.ollama \
-e OLLAMA_HOST=0.0.0.0 \
--memory 6g --cpus "$CPU_CAP" \
--restart unless-stopped \
ollama/ollama:0.33.3
fi
@@ -112,8 +114,8 @@ until docker exec d0a1-openclaw-ollama ollama ps &>/dev/null; do
exit 1
fi
done
echo -e "${YELLOW} Descargando modelo (llama3.2:3b, ~2 GB)...${NC}"
docker exec d0a1-openclaw-ollama ollama pull llama3.2:3b
echo -e "${YELLOW} Descargando modelo (qwen3:4b, ~2.5 GB)...${NC}"
docker exec d0a1-openclaw-ollama ollama pull qwen3:4b
echo -e "${GREEN}✓${NC} Ollama corriendo en http://localhost:${OLLAMA_PORT}"
# ── 2. Instalar OpenClaw ─────────────────────
+16
View File
@@ -0,0 +1,16 @@
# ============================================================
# d0a1 pack - OpenClaw (Windows) - DESINSTALAR
# Elimina el contenedor y el volumen (datos + modelo).
# ============================================================
$ErrorActionPreference = 'Stop'
function Info($m) { Write-Host ">>> $m" }
Info 'Borrando contenedor y volumen...'
cmd /c docker rm -f d0a1-openclaw-ollama '>' NUL '2>&1'
cmd /c docker volume rm d0a1_openclaw_ollama '>' NUL '2>&1'
Write-Host ''
Info 'Eliminado TODO: contenedor + volumen (incluido el modelo IA).'
Write-Host ''
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# d0a1 pack - OpenClaw (Linux) - DESINSTALAR
docker rm -f d0a1-openclaw-ollama >/dev/null 2>&1 || true
docker volume rm d0a1_openclaw_ollama >/dev/null 2>&1 || true
echo ">>> Eliminado TODO: contenedor + volumen (incluido el modelo IA)."
+1 -1
View File
@@ -33,7 +33,7 @@ Solo pagas el servidor. La IA es tuya.
## Modelos
El modelo por defecto es `llama3.2:3b` (2 GB, corre en 4 GB RAM).
El modelo por defecto es `qwen3:4b` (2 GB, corre en 4 GB RAM).
```bash
docker exec d0a1-ollama ollama pull qwen3:4b # Mejor en español + razonamiento (4 GB RAM)
+89 -6
View File
@@ -86,6 +86,25 @@ if ($TotalRamGb -lt 3.5) {
}
Ok "RAM: $TotalRamGb GB"
# --- Tumbar deploy previo (antes de detectar puertos) ----------------------
# Mantiene los volumenes (el modelo ya descargado no se re-descarga) y libera
# los puertos para que Find-FreePort vea el estado REAL tras el down.
cmd /c docker compose -f $ComposeFile down '>' NUL '2>&1'
Info 'Deploy previo detenido (down; volumenes con el modelo intactos).'
# --- GPU NVIDIA? (si hay, Ollama la usa y va 5-10x mas rapido) ---------------
$HasGpu = $false
if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) {
$GpuInfo = & nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>$null
if ($LASTEXITCODE -eq 0 -and $GpuInfo) {
$HasGpu = $true
$GpuName = ($GpuInfo -split ',')[0].Trim()
Info "GPU NVIDIA detectada: $GpuName (aceleracion activada)"
}
}
if (-not $HasGpu) {
Warn 'Sin GPU NVIDIA: modo CPU (~10-20 tokens/s). Con una GPU NVIDIA va 5-10x mas rapido.'
}
# --- Auto-detect ports ----------------------------------------------------
$OllamaPort = Find-FreePort 11434
$WebUiPort = Find-FreePort 3000
@@ -96,8 +115,12 @@ if ($Mode -eq 'local') {
Write-Host " URL: ${Cyan}http://localhost:${WebUiPort}${NC}"
} else {
Warn 'Modo: VPS (accesible desde fuera)'
# IP real de LAN: la de la interfaz con la ruta por defecto (evita vEthernet
# WSL/Hyper-V, Docker, VPN — que son 172.16-31.*, 10.x internos, etc.)
$GwIf = (Get-NetRoute -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
Sort-Object RouteMetric | Select-Object -First 1).ifIndex
$Ip = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
Where-Object { $_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*' -and $_.IPAddress -notmatch '^172\.(1[6-9]|2[0-9]|3[01])\.' } |
Where-Object { $_.ifIndex -eq $GwIf -and $_.IPAddress -notlike '127.*' } |
Select-Object -First 1).IPAddress
if (-not $Ip) { $Ip = 'TU_IP' }
Write-Host " URL: ${Cyan}http://${Ip}:${WebUiPort}${NC}"
@@ -115,11 +138,17 @@ Write-Host ''
Warn 'Descargando contenedores (2-3 min la primera vez)...'
$env:OLLAMA_PORT = "$OllamaPort"
$env:WEBUI_PORT = "$WebUiPort"
docker compose -f $ComposeFile up -d
$ComposeArgs = @('-f', $ComposeFile)
if ($HasGpu) {
$OverrideFile = Join-Path $ScriptDir 'docker-compose.gpu.yml'
Set-Content -Path $OverrideFile -Value "services:`r`n ollama:`r`n gpus: all`r`n"
$ComposeArgs += @('-f', $OverrideFile)
}
docker compose @ComposeArgs up -d
if ($LASTEXITCODE -ne 0) { Err "docker compose up fallo (exit $LASTEXITCODE)."; exit 1 }
# --- Pull model -------------------------------------------------------------
Warn 'Descargando modelo de IA (llama3.2:3b, ~2 GB)...'
Info 'Esperando a que Ollama arranque...'
$MaxWait = 60; $Waited = 0
while ($true) {
# Descarte de stderr via cmd /c (ver nota de PS 5.1 / PS #4002 arriba)
@@ -132,8 +161,62 @@ while ($true) {
exit 1
}
}
docker exec d0a1-ollama ollama pull llama3.2:3b
if ($LASTEXITCODE -ne 0) { Err 'Fallo la descarga del modelo.'; exit 1 }
# Si el usuario define $env:HTTPS_PROXY, Ollama lo usa para el pull (doc oficial):
$ProxyEnv = @()
if ($env:HTTPS_PROXY) { $ProxyEnv = @('-e', "HTTPS_PROXY=$env:HTTPS_PROXY", '-e', "HTTP_PROXY=$env:HTTP_PROXY") }
# --- Modelo ya presente? (volumen persistente: no re-descargar) ------------
$ModelList = docker exec d0a1-ollama ollama list 2>$null
if ("$ModelList" -match 'qwen3:4b') {
Info 'Modelo qwen3:4b ya descargado (volumen intacto). Omitiendo descarga.'
cmd /c docker exec d0a1-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf' '>' NUL '2>&1'
} else {
# --- Modelo: HF primero (CDN propio, rapido) y registry de Ollama como ultimo recurso ---
$Gguf = Join-Path $ScriptDir 'qwen3-4b-q4.gguf'
$HfUrls = @(
'https://huggingface.co/Qwen/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf',
'https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf'
)
Info 'Descargando modelo de IA (qwen3:4b, ~2.5 GB)...'
foreach ($Url in $HfUrls) {
curl.exe -# -L --retry 3 --max-time 1800 -o "$Gguf" $Url
if ($LASTEXITCODE -eq 0 -and (Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) { break }
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
if ($Url -ne $HfUrls[-1]) { Warn 'Mirror no accesible, probando el siguiente...' }
}
if ((Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) {
Info 'Importando modelo en Ollama...'
docker cp "$Gguf" d0a1-ollama:/root/.ollama/import.gguf
docker exec d0a1-ollama sh -c 'printf "FROM /root/.ollama/import.gguf\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
$CreateOk = ($LASTEXITCODE -eq 0)
docker exec d0a1-ollama rm -f /root/.ollama/import.gguf | Out-Null
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
$PullOk = $CreateOk
if (-not $CreateOk) { Err 'Fallo al importar el modelo desde el GGUF.' }
} else {
# Ultimo recurso: pull directo (1 intento, ya sin esperar 3 rondas)
Warn 'HuggingFace no accesible. Probando el registry de Ollama (ultimo recurso)...'
docker exec @ProxyEnv d0a1-ollama ollama pull qwen3:4b
if ($LASTEXITCODE -eq 0) {
$PullOk = $true
# Contexto ampliado (el default 4096 da 400 en Open WebUI con prompts largos)
docker exec d0a1-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
}
}
if (-not $PullOk) {
Err 'No se pudo descargar el modelo (ni HuggingFace ni registry).'
if (-not $env:HTTPS_PROXY) {
Write-Host ' Tu red bloquea ambos. Si tienes proxy/VPN:'
Write-Host ' $env:HTTPS_PROXY = "http://127.0.0.1:7890" # (puerto de tu proxy)'
Write-Host ' .\deploy.ps1 vps'
}
exit 1
}
}
# --- Precalentar el modelo (load a GPU/RAM ya, no en el primer mensaje) -----
Info 'Precargando el modelo en memoria...'
cmd /c docker exec d0a1-ollama sh -c 'ollama run qwen3:4b ""' '>' NUL '2>&1'
# --- Done -------------------------------------------------------------------
Write-Host ''
@@ -146,7 +229,7 @@ if ($Mode -eq 'local') {
}
Write-Host ''
Write-Host " ${Bold}Modelos alternativos:${NC}"
Write-Host ' docker exec d0a1-ollama ollama pull qwen3:4b # Mejor en espanol + razonamiento (4 GB RAM)'
Write-Host ' docker exec d0a1-ollama ollama pull llama3.2:3b # Mas ligero (2 GB RAM)'
Write-Host ' docker exec d0a1-ollama ollama pull qwen3:8b # Mas calidad (6 GB RAM)'
Write-Host ''
Write-Host " ${Bold}Parar:${NC} .\deploy.ps1 stop"
+3 -3
View File
@@ -105,7 +105,7 @@ echo -e "${YELLOW}Descargando contenedores (2-3 min la primera vez)...${NC}"
docker compose -f "$COMPOSE_FILE" up -d
# ── Pull model ───────────────────────────────
echo -e "${YELLOW}Descargando modelo de IA (llama3.2:3b, ~2 GB)...${NC}"
echo -e "${YELLOW}Descargando modelo de IA (qwen3:4b, ~2.5 GB)...${NC}"
MAX_WAIT=60
WAITED=0
until docker exec d0a1-ollama ollama ps &>/dev/null; do
@@ -116,7 +116,7 @@ until docker exec d0a1-ollama ollama ps &>/dev/null; do
exit 1
fi
done
docker exec d0a1-ollama ollama pull llama3.2:3b
docker exec d0a1-ollama ollama pull qwen3:4b
# ── Done ────────────────────────────────────
echo ""
@@ -129,7 +129,7 @@ else
fi
echo ""
echo -e " ${BOLD}Modelos alternativos:${NC}"
echo " docker exec d0a1-ollama ollama pull qwen3:4b # Mejor en español + razonamiento (4 GB RAM)"
echo " docker exec d0a1-ollama ollama pull llama3.2:3b # Más ligero (2 GB RAM)"
echo " docker exec d0a1-ollama ollama pull qwen3:8b # Más calidad (6 GB RAM)"
echo ""
echo -e " ${BOLD}Parar:${NC} ./deploy.sh stop"
+9 -2
View File
@@ -6,7 +6,7 @@
#
# Requisitos: Docker + 4 GB RAM mínimo
# Incluye: Ollama (IA local) + Open WebUI (interfaz web)
# Modelo por defecto: llama3.2:3b (~2 GB, corre en 4 GB RAM)
# Modelo por defecto: qwen3:4b (~2.5 GB, corre en 4 GB RAM)
services:
ollama:
container_name: d0a1-ollama
@@ -18,7 +18,7 @@ services:
- ollama_data:/root/.ollama
environment:
OLLAMA_HOST: "0.0.0.0"
OLLAMA_KEEP_ALIVE: "5m"
OLLAMA_KEEP_ALIVE: "-1"
OLLAMA_NUM_PARALLEL: "1"
healthcheck:
test: ["CMD", "ollama", "ps"]
@@ -26,6 +26,11 @@ services:
timeout: 5s
retries: 10
start_period: 30s
deploy:
resources:
limits:
memory: 6G
cpus: "${OLLAMA_CPUS:-6.0}"
openwebui:
container_name: d0a1-openwebui
@@ -43,6 +48,8 @@ services:
ENABLE_SIGNUP: "true"
ENABLE_WEB_SEARCH: "false"
ENABLE_DIRECT_CONNECTIONS: "false"
CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS: "1"
DEFAULT_MODEL_PARAMS: '{"function_calling": "legacy", "think": false}'
depends_on:
ollama:
condition: service_healthy
+24
View File
@@ -0,0 +1,24 @@
# ============================================================
# d0a1 pack - Open WebUI (Windows) - DESINSTALAR
# Elimina contenedores, red y volumenes (datos + modelo).
# ============================================================
$ErrorActionPreference = 'Stop'
$ScriptDir = $PSScriptRoot
if (-not $ScriptDir) { $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
$ComposeFile = Join-Path $ScriptDir 'docker-compose.yml'
function Info($m) { Write-Host ">>> $m" }
function Err($m) { Write-Host "!!! $m"; exit 1 }
# Sin confirmacion: este pack no guarda datos sensibles (solo chat + modelo).
Info 'Deteniendo y borrando contenedores...'
cmd /c docker compose -f "$ComposeFile" down -v --remove-orphans '>' NUL '2>&1'
if ($LASTEXITCODE -ne 0) { Err 'Fallo el compose down (docker no arrancado?)' }
Write-Host ''
Info 'Eliminado TODO:'
Write-Host ' - Contenedores y red'
Write-Host ' - Volumenes (incluido el modelo IA)'
Write-Host ''
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# d0a1 pack - Open WebUI (Linux) - DESINSTALAR
set -e
cd "$(cd "$(dirname "$0")" && pwd)"
echo ">>> Deteniendo y borrando contenedores..."
docker compose -f docker-compose.yml down -v --remove-orphans >/dev/null 2>&1 || { echo "!!! Fallo el compose down"; exit 1; }
echo ""
echo ">>> Eliminado TODO: contenedores, red y volumenes (incluido el modelo IA)."
+8 -7
View File
@@ -1,10 +1,10 @@
# WordPress + IA — WP con IA local
Tu WordPress con IA local conectada. Genera borradores, modera comentarios y chatea con tu sitio.
Tu WordPress con IA local conectada. Titulares, extractos y moderación de comentarios con IA, dentro del propio editor.
## Qué es
WordPress corriendo en tu servidor con Ollama (IA local) y Open WebUI conectados. Instala el plugin AI Agent Bridge para generar contenido y moderar comentarios con IA local, sin API de terceros.
WordPress corriendo en tu servidor con Ollama (IA local) conectado de serie: los plugins **AI** y **AI Provider for Ollama** se instalan y configuran solos durante el deploy. Genera títulos, extractos y alt text desde el editor de WordPress con tu propio modelo (qwen3:4b), sin API de terceros ni cuotas.
## Prueba local (gratis, 1 comando)
@@ -13,9 +13,8 @@ WordPress corriendo en tu servidor con Ollama (IA local) y Open WebUI conectados
```
- WordPress: http://localhost:8080
- IA (chat): http://localhost:3000
Sigue el asistente de instalación de WordPress (2 minutos), luego instala el plugin AI Agent Bridge desde el panel.
Sigue el asistente de instalación de WordPress (2 minutos). Al terminar, ejecuta `./deploy.sh` otra vez y los plugins de IA quedan activados y conectados.
## Despliega en VPS
@@ -33,13 +32,14 @@ Solo pagas el servidor. WordPress y la IA son tuyos.
- Docker instalado
- 6 GB RAM mínimo
- GPU NVIDIA recomendada (detección automática)
## Qué incluye
- WordPress 7.0 + PHP 8.4
- WordPress 7.1 + PHP 8.4
- MariaDB 11.8
- Ollama (IA local, modelo llama3.2:3b por defecto)
- Open WebUI (interfaz de chat)
- Ollama (IA local, modelo qwen3:4b por defecto)
- Plugins IA preconfigurados: AI (funciones en el editor) + AI Provider for Ollama
## Comandos
@@ -47,4 +47,5 @@ Solo pagas el servidor. WordPress y la IA son tuyos.
./deploy.sh local # Arrancar en local
./deploy.sh vps # Arrancar en VPS
./deploy.sh stop # Parar todo
./remove.sh # Desinstalar todo (contenedores + datos)
```
+163 -19
View File
@@ -119,45 +119,66 @@ if (-not (Test-Path $EnvFile)) {
Info "Contrasenas generadas en $EnvFile"
}
# --- GPU NVIDIA? (si hay, Ollama la usa y va 5-10x mas rapido) ---------------
$HasGpu = $false
if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) {
$GpuInfo = & nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>$null
if ($LASTEXITCODE -eq 0 -and $GpuInfo) {
$HasGpu = $true
$GpuName = ($GpuInfo -split ',')[0].Trim()
Info "GPU NVIDIA detectada: $GpuName (aceleracion activada)"
}
}
if (-not $HasGpu) {
Warn 'Sin GPU NVIDIA: modo CPU (~10-20 tokens/s). Con una GPU NVIDIA va 5-10x mas rapido.'
}
# --- Auto-detect ports ----------------------------------------------------
$OllamaPort = Find-FreePort 11434
# --- Tumbar deploy previo (antes de detectar puertos) ----------------------
cmd /c docker compose -f $ComposeFile down '>' NUL '2>&1'
Info 'Deploy previo detenido (down; volumenes con el modelo intactos).'
$OllamaPort = $null # Ollama ya no publica puerto: solo lo usa WP por la red interna
$WpPort = Find-FreePort 8080
$WebUiPort = Find-FreePort 3000
# --- Mode info ------------------------------------------------------------
if ($Mode -eq 'local') {
Warn 'Modo: local (tu ordenador)'
Write-Host " WordPress: ${Cyan}http://localhost:${WpPort}${NC}"
Write-Host " IA (chat): ${Cyan}http://localhost:${WebUiPort}${NC}"
} else {
Warn 'Modo: VPS (accesible desde fuera)'
# IP real de LAN: la de la interfaz con la ruta por defecto (evita
# vEthernet WSL/Hyper-V, Docker, VPN)
$GwIf = (Get-NetRoute -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
Sort-Object RouteMetric | Select-Object -First 1).ifIndex
$Ip = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
Where-Object { $_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*' -and $_.IPAddress -notmatch '^172\.(1[6-9]|2[0-9]|3[01])\.' } |
Where-Object { $_.ifIndex -eq $GwIf -and $_.IPAddress -notlike '127.*' } |
Select-Object -First 1).IPAddress
if (-not $Ip) { $Ip = 'TU_IP' }
Write-Host " WordPress: ${Cyan}http://${Ip}:${WpPort}${NC}"
Write-Host " IA (chat): ${Cyan}http://${Ip}:${WebUiPort}${NC}"
Write-Host ''
Write-Host " ${Bold}¿No tienes VPS?${NC} Consigue uno con descuento:"
Write-Host " ${Cyan}https://d0a1.es/go/vultr/${NC} - Vultr"
Write-Host " ${Cyan}https://d0a1.es/go/digitalocean/${NC} - DigitalOcean (`$200 credit)"
}
if ($OllamaPort -ne 11434) { Warn "Puerto 11434 ocupado, usando $OllamaPort" }
if ($WpPort -ne 8080) { Warn "Puerto 8080 ocupado, usando $WpPort" }
if ($WebUiPort -ne 3000) { Warn "Puerto 3000 ocupado, usando $WebUiPort" }
Write-Host ''
# --- Deploy ----------------------------------------------------------------
Warn 'Descargando contenedores (3-5 min la primera vez)...'
$env:OLLAMA_PORT = "$OllamaPort"
cmd /c docker compose -f $ComposeFile down '>' NUL '2>&1'
$env:WP_PORT = "$WpPort"
$env:WEBUI_PORT = "$WebUiPort"
docker compose -f $ComposeFile up -d
$ComposeArgs = @('-f', $ComposeFile)
if ($HasGpu) {
$OverrideFile = Join-Path $ScriptDir 'docker-compose.gpu.yml'
Set-Content -Path $OverrideFile -Value "services:`r`n ollama:`r`n gpus: all`r`n"
$ComposeArgs += @('-f', $OverrideFile)
}
docker compose @ComposeArgs up -d
if ($LASTEXITCODE -ne 0) { Err "docker compose up fallo (exit $LASTEXITCODE)."; exit 1 }
# --- Pull model -------------------------------------------------------------
Warn 'Descargando modelo de IA (llama3.2:3b, ~2 GB)...'
Info 'Esperando a que Ollama arranque...'
$MaxWait = 60; $Waited = 0
while ($true) {
# Descarte de stderr via cmd /c (ver nota de PS 5.1 / PS #4002 arriba)
@@ -170,25 +191,148 @@ while ($true) {
exit 1
}
}
docker exec d0a1-wp-ollama ollama pull llama3.2:3b
if ($LASTEXITCODE -ne 0) { Err 'Fallo la descarga del modelo.'; exit 1 }
# Si el usuario define $env:HTTPS_PROXY, Ollama lo usa para el pull (doc oficial):
$ProxyEnv = @()
if ($env:HTTPS_PROXY) { $ProxyEnv = @('-e', "HTTPS_PROXY=$env:HTTPS_PROXY", '-e', "HTTP_PROXY=$env:HTTP_PROXY") }
# --- Modelo ya presente? (volumen persistente: no re-descargar) ------------
$ModelList = docker exec d0a1-wp-ollama ollama list 2>$null
if ("$ModelList" -match 'qwen3:4b') {
Info 'Modelo qwen3:4b ya descargado (volumen intacto). Omitiendo descarga.'
cmd /c docker exec d0a1-wp-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf' '>' NUL '2>&1'
} else {
# --- Modelo: HF primero (CDN propio, rapido) y registry de Ollama como ultimo recurso ---
$Gguf = Join-Path $ScriptDir 'qwen3-4b-q4.gguf'
$HfUrls = @(
'https://huggingface.co/Qwen/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf',
'https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf'
)
Info 'Descargando modelo de IA (qwen3:4b, ~2.5 GB)...'
foreach ($Url in $HfUrls) {
curl.exe -# -L --retry 3 --max-time 1800 -o "$Gguf" $Url
if ($LASTEXITCODE -eq 0 -and (Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) { break }
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
if ($Url -ne $HfUrls[-1]) { Warn 'Mirror no accesible, probando el siguiente...' }
}
if ((Test-Path $Gguf) -and (Get-Item $Gguf).Length -gt 2400000000) {
Info 'Importando modelo en Ollama...'
docker cp "$Gguf" d0a1-wp-ollama:/root/.ollama/import.gguf
docker exec d0a1-wp-ollama sh -c 'printf "FROM /root/.ollama/import.gguf\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
$CreateOk = ($LASTEXITCODE -eq 0)
docker exec d0a1-wp-ollama rm -f /root/.ollama/import.gguf | Out-Null
Remove-Item "$Gguf" -Force -ErrorAction SilentlyContinue
$PullOk = $CreateOk
if (-not $CreateOk) { Err 'Fallo al importar el modelo desde el GGUF.' }
} else {
# Ultimo recurso: pull directo (1 intento, ya sin esperar 3 rondas)
Warn 'HuggingFace no accesible. Probando el registry de Ollama (ultimo recurso)...'
docker exec @ProxyEnv d0a1-wp-ollama ollama pull qwen3:4b
if ($LASTEXITCODE -eq 0) {
$PullOk = $true
# Contexto ampliado (el default 4096 da 400 en Open WebUI con prompts largos)
docker exec d0a1-wp-ollama sh -c 'printf "FROM qwen3:4b\\nPARAMETER num_ctx 16384\\nSYSTEM /no_think\\n" > /tmp/mf && ollama create qwen3:4b -f /tmp/mf'
}
}
if (-not $PullOk) {
Err 'No se pudo descargar el modelo (ni HuggingFace ni registry).'
if (-not $env:HTTPS_PROXY) {
Write-Host ' Tu red bloquea ambos. Si tienes proxy/VPN:'
Write-Host ' $env:HTTPS_PROXY = "http://127.0.0.1:7890" # (puerto de tu proxy)'
Write-Host ' .\deploy.ps1 vps'
}
exit 1
}
}
# --- Precalentar el modelo (load a GPU/RAM ya, no en el primer mensaje) -----
Info 'Precargando el modelo en memoria...'
cmd /c docker exec d0a1-wp-ollama sh -c 'ollama run qwen3:4b ""' '>' NUL '2>&1'
# --- Done -------------------------------------------------------------------
Write-Host ''
Info 'Listo! Tu WordPress con IA esta corriendo.'
Write-Host ''
# --- Auto-config WordPress: plugins IA + conexion a Ollama -------------------
# EAP=Continue: los 2>$null sobre nativos con EAP=Stop provocan NativeCommandError
# terminante (PS #4002, ver nota arriba). El bloque nuevo maneja errores por rc.
$PrevEap = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
# El contenedor wordpress:7.1 no trae WP-CLI; usar el contenedor wordpress:cli.
Info 'Configurando los plugins de IA en WordPress (1-2 min la primera vez)...'
cmd /c "docker pull wordpress:cli >NUL 2>&1"
# Red real del compose (el nombre depende del directorio, no es fijo):
$WpNet = docker inspect d0a1-wp --format '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{end}}' 2>$null
if (-not $WpNet) {
Warn 'No se detecto la red del compose (d0a1-wp no corre). Saltando autoconfig.'
$ErrorActionPreference = $PrevEap
exit 1
}
$EnvFileContent = Get-Content $EnvFile -ErrorAction SilentlyContinue
$WpDbPass = ($EnvFileContent | Where-Object { $_ -like 'MYSQL_PASSWORD=*' }) -replace '^MYSQL_PASSWORD=', '' | Select-Object -First 1
if (-not $WpDbPass) { $WpDbPass = 'change-me-to-a-strong-password' }
function Invoke-WpCli {
# docker run con --volumes-from (mismo patron env que usa el compose).
$ArgsList = @('run', '--rm', '--network', $WpNet, '--volumes-from', 'd0a1-wp',
'--env', "WORDPRESS_DB_HOST=db:3306",
'--env', 'WORDPRESS_DB_NAME=wordpress',
'--env', 'WORDPRESS_DB_USER=wordpress',
'--env', "WORDPRESS_DB_PASSWORD=$WpDbPass",
'wordpress:cli') + $Args + @('--allow-root')
& docker @ArgsList 2>$null
return $LASTEXITCODE
}
# Esperar a que WP este instalado (asistente del navegador opcional: si el
# usuario aun no instalo WP, la autoconfig se salta y puede repetir deploy).
$WpReady = $false
for ($i = 0; $i -lt 15; $i++) {
if ((Invoke-WpCli 'core' 'is-installed') -eq 0) { $WpReady = $true; break }
Start-Sleep -Seconds 4
}
if ($WpReady) {
$Rc = Invoke-WpCli 'plugin' 'install' 'ai' 'ai-provider-for-ollama' '--activate'
if ($Rc -eq 0) {
$Rc = Invoke-WpCli 'option' 'update' 'ai_provider_for_ollama_settings' '{"host":"http://ollama:11434"}' '--format=json'
}
if ($Rc -eq 0) {
Ok 'Plugins de IA activados y conectados a Ollama (IA local)'
} else {
Warn 'No se pudo auto-configurar. Re-ejecuta .\deploy.ps1 tras el asistente de WordPress.'
}
# Estilo editorial: copiar al wp-content (editable: editorial-style.php del pack)
# e instalar el mu-plugin que lo inyecta en las instrucciones de la IA.
$StyleRc = 1
cmd /c "docker exec d0a1-wp mkdir -p /var/www/html/wp-content/mu-plugins >NUL 2>&1"
if ($LASTEXITCODE -eq 0) {
docker cp (Join-Path $ScriptDir 'editorial-style.php') d0a1-wp:/var/www/html/wp-content/editorial-style.php 2>$null
if ($LASTEXITCODE -eq 0) {
docker cp (Join-Path $ScriptDir 'mu-plugin-d0a1-editorial.php') d0a1-wp:/var/www/html/wp-content/mu-plugins/d0a1-editorial.php 2>$null
$StyleRc = $LASTEXITCODE
}
}
if ($StyleRc -eq 0) {
Ok 'Estilo editorial activo (edita editorial-style.php y re-ejecuta para cambiarlo)'
}
} else {
Warn 'WordPress aun no esta instalado. Tras el asistente del navegador,'
Warn 'vuelve a ejecutar .\deploy.ps1 y los plugins de IA se configuran solos.'
}
$ErrorActionPreference = $PrevEap
Write-Host ''
if ($Mode -eq 'local') {
Write-Host " ${Bold}WordPress:${NC} ${Cyan}http://localhost:${WpPort}${NC}"
Write-Host " ${Bold}IA (chat):${NC} ${Cyan}http://localhost:${WebUiPort}${NC}"
} else {
Write-Host " ${Bold}WordPress:${NC} ${Cyan}http://${Ip}:${WpPort}${NC}"
Write-Host " ${Bold}IA (chat):${NC} ${Cyan}http://${Ip}:${WebUiPort}${NC}"
}
Write-Host ''
Write-Host " ${Bold}Configurar WordPress:${NC}"
Write-Host ' 1. Abre la URL de WordPress en tu navegador'
Write-Host ' 2. Sigue el asistente de instalacion (2 minutos)'
Write-Host " 3. Instala el plugin 'AI Agent Bridge' desde el panel"
Write-Host " ${Bold}IA integrada en el editor:${NC} titulos sugeridos, extractos, alt text"
Write-Host ' y moderacion de comentarios con tu modelo local (qwen3:4b).'
Write-Host " Configuracion en ${Bold}Ajustes - Connectors${NC} dentro de WordPress."
Write-Host ''
Write-Host " ${Bold}Parar:${NC} .\deploy.ps1 stop"
Write-Host " ${Bold}¿Quieres mas?${NC} ${Cyan}https://d0a1.es/packs/${NC}"
+59 -18
View File
@@ -84,20 +84,16 @@ find_free_port() {
return 1
}
export OLLAMA_PORT=$(find_free_port 11434)
export WP_PORT=$(find_free_port 8080)
export WEBUI_PORT=$(find_free_port 3000)
# ── Mode info ───────────────────────────────
if [ "$MODE" = "local" ]; then
echo -e "${YELLOW}Modo:${NC} local (tu ordenador)"
echo -e " WordPress: ${CYAN}http://localhost:${WP_PORT}${NC}"
echo -e " IA (chat): ${CYAN}http://localhost:${WEBUI_PORT}${NC}"
elif [ "$MODE" = "vps" ]; then
IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'TU_IP')
echo -e "${YELLOW}Modo:${NC} VPS (accesible desde fuera)"
echo -e " WordPress: ${CYAN}http://${IP}:${WP_PORT}${NC}"
echo -e " IA (chat): ${CYAN}http://${IP}:${WEBUI_PORT}${NC}"
echo ""
echo -e " ${BOLD}¿No tienes VPS?${NC} Consigue uno con descuento:"
echo -e " ${CYAN}https://d0a1.es/go/vultr/${NC} — Vultr"
@@ -108,15 +104,9 @@ else
exit 1
fi
if [ "$OLLAMA_PORT" != "11434" ]; then
echo -e "${YELLOW}⚠ Puerto 11434 ocupado, usando ${OLLAMA_PORT}${NC}"
fi
if [ "$WP_PORT" != "8080" ]; then
echo -e "${YELLOW}⚠ Puerto 8080 ocupado, usando ${WP_PORT}${NC}"
fi
if [ "$WEBUI_PORT" != "3000" ]; then
echo -e "${YELLOW}⚠ Puerto 3000 ocupado, usando ${WEBUI_PORT}${NC}"
fi
echo ""
# ── Deploy ──────────────────────────────────
@@ -124,7 +114,7 @@ echo -e "${YELLOW}Descargando contenedores (3-5 min la primera vez)...${NC}"
docker compose -f "$COMPOSE_FILE" up -d
# ── Pull model ───────────────────────────────
echo -e "${YELLOW}Descargando modelo de IA (llama3.2:3b, ~2 GB)...${NC}"
echo -e "${YELLOW}Descargando modelo de IA (qwen3:4b, ~2.5 GB)...${NC}"
MAX_WAIT=60
WAITED=0
until docker exec d0a1-wp-ollama ollama ps &>/dev/null; do
@@ -135,24 +125,75 @@ until docker exec d0a1-wp-ollama ollama ps &>/dev/null; do
exit 1
fi
done
docker exec d0a1-wp-ollama ollama pull llama3.2:3b
docker exec d0a1-wp-ollama ollama pull qwen3:4b
# ── Done ────────────────────────────────────
echo ""
echo -e "${BOLD}${GREEN}¡Listo! Tu WordPress con IA está corriendo.${NC}"
echo ""
# ── Auto-config WordPress (plugins IA + conexión a Ollama) ──
# El contenedor wordpress:7.1 no trae WP-CLI; usar el contenedor wordpress:cli.
echo -e "${YELLOW}Configurando los plugins de IA en WordPress (1-2 min la primera vez)...${NC}"
docker pull wordpress:cli >/dev/null 2>&1
# Red real del compose (el nombre depende del directorio, no es fijo):
WP_NET=$(docker inspect d0a1-wp --format '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{end}}' 2>/dev/null)
if [ -z "$WP_NET" ]; then
echo -e "${YELLOW}⚠ No se detecto la red del compose (d0a1-wp no corre). Saltando autoconfig.${NC}"
exit 1
fi
WP_DB_PASS=$(grep '^MYSQL_PASSWORD=' "$SCRIPT_DIR/.env" | cut -d= -f2)
wpcli() {
docker run --rm --network "$WP_NET" --volumes-from d0a1-wp \
--env WORDPRESS_DB_HOST=db:3306 \
--env WORDPRESS_DB_NAME=wordpress \
--env WORDPRESS_DB_USER=wordpress \
--env WORDPRESS_DB_PASSWORD="$WP_DB_PASS" \
wordpress:cli "$@" --allow-root
}
# Esperar a que WP esté instalado (el asistente del navegador es opcional:
# si el usuario aun no instalo WP, la autoconfig se salta y se puede repetir
# re-ejecutando deploy.sh).
WP_READY=0
for i in $(seq 1 15); do
if wpcli core is-installed >/dev/null 2>&1; then
WP_READY=1
break
fi
sleep 4
done
if [ "$WP_READY" = "1" ]; then
wpcli plugin install ai ai-provider-for-ollama --activate >/dev/null 2>&1 \
&& wpcli option update ai_provider_for_ollama_settings \
'{"host":"http://ollama:11434"}' --format=json >/dev/null 2>&1 \
&& echo -e "${GREEN}✓${NC} Plugins de IA activados y conectados a Ollama (IA local)" \
|| echo -e "${YELLOW}⚠ No se pudo auto-configurar. Re-ejecuta ./deploy.sh tras el asistente de WordPress.${NC}"
# Estilo editorial: copiar al wp-content (editable: editorial-style.php del pack)
# e instalar el mu-plugin que lo inyecta en las instrucciones de la IA.
if docker cp "$SCRIPT_DIR/editorial-style.php" d0a1-wp:/var/www/html/wp-content/editorial-style.php >/dev/null 2>&1 \
&& docker exec d0a1-wp mkdir -p /var/www/html/wp-content/mu-plugins \
&& docker cp "$SCRIPT_DIR/mu-plugin-d0a1-editorial.php" \
d0a1-wp:/var/www/html/wp-content/mu-plugins/d0a1-editorial.php >/dev/null 2>&1; then
echo -e "${GREEN}✓${NC} Estilo editorial activo (edita editorial-style.php y re-ejecuta para cambiarlo)"
fi
else
echo -e "${YELLOW}⚠ WordPress aun no está instalado. Tras el asistente del navegador,"
echo -e " vuelve a ejecutar ./deploy.sh y los plugins de IA se configuran solos.${NC}"
fi
echo ""
if [ "$MODE" = "local" ]; then
echo -e " ${BOLD}WordPress:${NC} ${CYAN}http://localhost:${WP_PORT}${NC}"
echo -e " ${BOLD}IA (chat):${NC} ${CYAN}http://localhost:${WEBUI_PORT}${NC}"
else
echo -e " ${BOLD}WordPress:${NC} ${CYAN}http://${IP}:${WP_PORT}${NC}"
echo -e " ${BOLD}IA (chat):${NC} ${CYAN}http://${IP}:${WEBUI_PORT}${NC}"
fi
echo ""
echo -e " ${BOLD}Configurar WordPress:${NC}"
echo " 1. Abre la URL de WordPress en tu navegador"
echo " 2. Sigue el asistente de instalación (2 minutos)"
echo " 3. Instala el plugin 'AI Agent Bridge' desde el panel"
echo -e " ${BOLD}IA integrada en el editor:${NC} títulos sugeridos, extractos, alt text"
echo -e " y moderación de comentarios con tu modelo local (qwen3:4b)."
echo -e " Configuración en ${BOLD}Ajustes → Connectors${NC} dentro de WordPress."
echo ""
echo -e " ${BOLD}Parar:${NC} ./deploy.sh stop"
echo -e " ${BOLD}¿Quieres más?${NC} ${CYAN}https://d0a1.es/packs/${NC}"
+7 -24
View File
@@ -5,7 +5,7 @@
# VPS: ./deploy.sh vps → http://TU_IP:8080
#
# Requisitos: Docker + 6 GB RAM mínimo
# Incluye: WordPress + MariaDB + Ollama + Open WebUI
# Incluye: WordPress + MariaDB + Ollama (IA local) + plugins IA (AI + AI Provider for Ollama)
services:
db:
container_name: d0a1-wp-db
@@ -46,40 +46,25 @@ services:
container_name: d0a1-wp-ollama
image: ollama/ollama:0.33.3
restart: unless-stopped
ports:
- "127.0.0.1:${OLLAMA_PORT:-11434}:11434"
# Sin publicar puerto: solo lo usa WordPress (red interna del compose).
expose:
- "11434"
volumes:
- ollama_data:/root/.ollama
environment:
OLLAMA_HOST: "0.0.0.0"
OLLAMA_KEEP_ALIVE: "5m"
OLLAMA_KEEP_ALIVE: "-1"
healthcheck:
test: ["CMD", "ollama", "ps"]
interval: 15s
timeout: 5s
retries: 10
start_period: 30s
openwebui:
container_name: d0a1-wp-openwebui
image: ghcr.io/open-webui/open-webui:0.11.3
restart: unless-stopped
ports:
- "${WEBUI_PORT:-3000}:8080"
volumes:
- openwebui_data:/app/backend/data
environment:
OLLAMA_BASE_URL: "http://ollama:11434"
WEBUI_NAME: "d0a1"
WEBUI_AUTH: "false"
DEFAULT_LOCALE: "es"
depends_on:
ollama:
condition: service_healthy
deploy:
resources:
limits:
memory: 2G
memory: 6G
cpus: "${OLLAMA_CPUS:-6.0}"
volumes:
wp_db:
@@ -88,5 +73,3 @@ volumes:
driver: local
ollama_data:
driver: local
openwebui_data:
driver: local
+20
View File
@@ -0,0 +1,20 @@
<?php
/**
* Estilo editorial de d0a1 para la IA local (WordPress "AI" plugin).
* EDITA AQUI y re-ejecuta deploy para aplicarlo. La IA redacta segun esto.
*/
return array(
'estilo' => 'Directo, claro y cercano. Sin humo. Espanol de Espana (tu, no usted). Frases cortas.',
'reglas' => array(
'Spanish (Spain) siempre, salvo que el contenido fuente este en otro idioma.',
'Nada de relleno: cada frase aporta un dato, un beneficio o un paso.',
'Precios en euros (EUR).',
'Voz activa. Verbo al principio cuando sea natural.',
),
'prohibido' => array(
'Emojis, hashtags ni llamadas a la accion genericas.',
'Superlativos vacios ("increible", "revolucionario").',
'Markdown ni comillas envolviendo el resultado.',
),
'titulares_max_chars' => 60,
);
+43
View File
@@ -0,0 +1,43 @@
<?php
/**
* Plugin Name: d0a1 — Instrucciones editoriales IA (mu-plugin)
* Description: Inyecta el estilo editorial (editorial-style.php del pack) en las instrucciones de sistema del plugin "AI".
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
function d0a1_editorial_style() {
$file = WP_CONTENT_DIR . '/editorial-style.php';
if ( ! file_exists( $file ) ) { return null; }
$s = include $file;
return is_array( $s ) ? $s : null;
}
function d0a1_editorial_block() {
$s = d0a1_editorial_style();
if ( ! $s ) { return ''; }
$out = "\n\nESTILO EDITORIAL DEL SITIO (prioridad sobre instrucciones anteriores):\n";
if ( ! empty( $s['estilo'] ) ) { $out .= 'Tono: ' . $s['estilo'] . "\n"; }
if ( ! empty( $s['reglas'] ) ) {
$out .= "Normas:\n";
foreach ( $s['reglas'] as $r ) { $out .= '- ' . $r . "\n"; }
}
if ( ! empty( $s['prohibido'] ) ) {
$out .= "Prohibido:\n";
foreach ( $s['prohibido'] as $p ) { $out .= '- ' . $p . "\n"; }
}
return $out;
}
// Global: todas las funciones IA del plugin (titulos, extractos, alt text, moderacion...).
add_filter( 'wpai_system_instruction', function ( $instruction ) {
return $instruction . d0a1_editorial_block();
}, 10, 1 );
// Afinar titulares: max chars configurable en editorial-style.php.
add_filter( 'wpai_title_generation_system_instruction', function ( $instruction ) {
$s = d0a1_editorial_style();
if ( $s && ! empty( $s['titulares_max_chars'] ) && $s['titulares_max_chars'] !== 80 ) {
$instruction = preg_replace( '/Be no more than \d+ characters/', 'Be no more than ' . (int) $s['titulares_max_chars'] . ' characters', $instruction );
}
return $instruction;
}, 10, 1 );
+28
View File
@@ -0,0 +1,28 @@
# ============================================================
# d0a1 pack - WordPress + IA (Windows) - DESINSTALAR
# Elimina contenedores, red y volumenes (datos + modelo).
# ============================================================
$ErrorActionPreference = 'Stop'
$ScriptDir = $PSScriptRoot
if (-not $ScriptDir) { $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
$ComposeFile = Join-Path $ScriptDir 'docker-compose.yml'
function Info($m) { Write-Host ">>> $m" }
function Err($m) { Write-Host "!!! $m"; exit 1 }
Write-Host ''
Write-Host '>>> ATENCION: esto borra TAMBIEN la base de datos y el contenido de WordPress.'
$Resp = Read-Host 'Continuar? [y/N]'
if ($Resp -notmatch '^[Yy]') { Write-Host 'Cancelado.'; exit 0 }
Write-Host ''
Info 'Deteniendo y borrando contenedores...'
cmd /c docker compose -f "$ComposeFile" down -v --remove-orphans '>' NUL '2>&1'
if ($LASTEXITCODE -ne 0) { Err 'Fallo el compose down (docker no arrancado?)' }
Write-Host ''
Info 'Eliminado TODO:'
Write-Host ' - Contenedores y red'
Write-Host ' - Volumenes (incluido el modelo IA)'
Write-Host ''
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# d0a1 pack - WordPress + IA (Linux) - DESINSTALAR
set -e
cd "$(cd "$(dirname "$0")" && pwd)"
read -r -p ">>> ATENCION: borra TAMBIEN la BD de WordPress. Continuar? [y/N] " r
case "$r" in [yY]*) ;; *) echo Cancelado.; exit 0;; esac
echo ""
echo ">>> Deteniendo y borrando contenedores..."
docker compose -f docker-compose.yml down -v --remove-orphans >/dev/null 2>&1 || { echo "!!! Fallo el compose down"; exit 1; }
echo ""
echo ">>> Eliminado TODO: contenedores, red y volumenes (incluido el modelo IA)."