20-sep: infra global - OmniRoute watchdog + plugin OpenCode + changelog sobremesa/portatil
This commit is contained in:
36
CHANGELOG_INFRA.md
Normal file
36
CHANGELOG_INFRA.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Changelog de infraestructura — global (sobremesa <-> portatil <-> VPS)
|
||||
|
||||
> Fichero generado por `scripts/infra_changelog.py`. NO editar a mano.
|
||||
> Fuente de verdad: `changelog_infra.json`.
|
||||
|
||||
## Pendientes de replicar
|
||||
|
||||
- **2026-09-20-omniroute-arranque-fiable-tarea-programada-vigilante** [omniroute] OmniRoute arranque fiable (tarea programada + vigilante) (aplicado en: sobremesa)
|
||||
- **2026-09-20-omniroute-505-modelos-expuestos-en-opencode** [omniroute] OmniRoute: 505 modelos expuestos en OpenCode (aplicado en: sobremesa)
|
||||
- **2026-09-20-plugin-global-opencode-relanza-omniroute** [opencode] Plugin global OpenCode relanza OmniRoute (aplicado en: sobremesa)
|
||||
|
||||
## Historial
|
||||
|
||||
### 2026-09-20 · OmniRoute arranque fiable (tarea programada + vigilante)
|
||||
- **id**: `2026-09-20-omniroute-arranque-fiable-tarea-programada-vigilante`
|
||||
- **Equipo**: sobremesa
|
||||
- **Categoria**: omniroute
|
||||
- **Transferible**: si
|
||||
- **Aplicado en**: sobremesa
|
||||
- **Detalle**: Tarea 'OmniRoute Watchdog' al iniciar sesion + omniroute_watchdog.ps1 (puerto 20128 cada 120s, autocuracion). Sustituye el .vbs fragil.
|
||||
|
||||
### 2026-09-20 · OmniRoute: 505 modelos expuestos en OpenCode
|
||||
- **id**: `2026-09-20-omniroute-505-modelos-expuestos-en-opencode`
|
||||
- **Equipo**: sobremesa
|
||||
- **Categoria**: omniroute
|
||||
- **Transferible**: si
|
||||
- **Aplicado en**: sobremesa
|
||||
- **Detalle**: setup-opencode --only de 5 conexiones probadas (antigravity, claude, opencode-go, opencode, aihorde) + auto/*. Quitar BOM de opencode.jsonc.
|
||||
|
||||
### 2026-09-20 · Plugin global OpenCode relanza OmniRoute
|
||||
- **id**: `2026-09-20-plugin-global-opencode-relanza-omniroute`
|
||||
- **Equipo**: sobremesa
|
||||
- **Categoria**: opencode
|
||||
- **Transferible**: si
|
||||
- **Aplicado en**: sobremesa
|
||||
- **Detalle**: omniroute-bootstrap.ts en ~/.config/opencode/plugins/ lanza el vigilante -Once al arrancar OpenCode en cualquier repo.
|
||||
46
README.md
Normal file
46
README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# INFRAESTRUCTURA
|
||||
|
||||
Repo de infraestructura compartida entre equipos (sobremesa, portátil, VPS).
|
||||
|
||||
## Contenido
|
||||
|
||||
| Ruta | Qué es |
|
||||
|------|--------|
|
||||
| `changelog_infra.json` | Fuente de verdad del **changelog global de infraestructura** (por equipo) |
|
||||
| `CHANGELOG_INFRA.md` | Versión legible (generada; no editar a mano) |
|
||||
| `scripts/infra_changelog.py` | Añadir / consultar / marcar cambios |
|
||||
| `omniroute/omniroute_watchdog.ps1` | Vigilante de OmniRoute (arranque + autocuración) |
|
||||
| `omniroute/install_omniroute_autostart.ps1` | Registra la tarea programada `OmniRoute Watchdog` |
|
||||
| `opencode/omniroute-bootstrap.ts` | Plugin GLOBAL de OpenCode: relanza OmniRoute al arrancar OpenCode |
|
||||
| `scripts/instalar_global.ps1` | Instala el plugin global y la tarea de arranque |
|
||||
|
||||
## Changelog global (sobremesa ↔ portátil)
|
||||
|
||||
Registra cada cambio de infraestructura y si es transferible al otro equipo:
|
||||
|
||||
```powershell
|
||||
# Añadir un cambio (detecta el equipo automáticamente)
|
||||
python scripts/infra_changelog.py add --titulo "Instalado Docker" --categoria servicios --transferible si --detalle "..."
|
||||
|
||||
# Ver qué falta replicar en ESTE equipo
|
||||
python scripts/infra_changelog.py pendientes
|
||||
|
||||
# Marcar como aplicado aquí
|
||||
python scripts/infra_changelog.py aplicado <id>
|
||||
|
||||
# Resumen
|
||||
python scripts/infra_changelog.py estado
|
||||
```
|
||||
|
||||
El protocolo `re` de cada equipo lee este fichero y avisa de lo pendiente.
|
||||
|
||||
## Instalación en un equipo nuevo (p. ej. el portátil)
|
||||
|
||||
```powershell
|
||||
git clone https://git.v-encore-lab.com/jjminguez/INFRAESTRUCTURA.git
|
||||
cd INFRAESTRUCTURA
|
||||
powershell -ExecutionPolicy Bypass -File scripts/instalar_global.ps1
|
||||
```
|
||||
|
||||
Prerrequisitos de OmniRoute: Node.js + `npm i -g omniroute`, y copiar `~/.omniroute`
|
||||
(credenciales) y `~/.config/opencode/opencode.jsonc`.
|
||||
41
changelog_infra.json
Normal file
41
changelog_infra.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": 1,
|
||||
"entries": [
|
||||
{
|
||||
"id": "2026-09-20-omniroute-arranque-fiable-tarea-programada-vigilante",
|
||||
"fecha": "2026-09-20",
|
||||
"equipo": "sobremesa",
|
||||
"categoria": "omniroute",
|
||||
"titulo": "OmniRoute arranque fiable (tarea programada + vigilante)",
|
||||
"transferible": true,
|
||||
"aplicado_en": [
|
||||
"sobremesa"
|
||||
],
|
||||
"detalle": "Tarea 'OmniRoute Watchdog' al iniciar sesion + omniroute_watchdog.ps1 (puerto 20128 cada 120s, autocuracion). Sustituye el .vbs fragil."
|
||||
},
|
||||
{
|
||||
"id": "2026-09-20-omniroute-505-modelos-expuestos-en-opencode",
|
||||
"fecha": "2026-09-20",
|
||||
"equipo": "sobremesa",
|
||||
"categoria": "omniroute",
|
||||
"titulo": "OmniRoute: 505 modelos expuestos en OpenCode",
|
||||
"transferible": true,
|
||||
"aplicado_en": [
|
||||
"sobremesa"
|
||||
],
|
||||
"detalle": "setup-opencode --only de 5 conexiones probadas (antigravity, claude, opencode-go, opencode, aihorde) + auto/*. Quitar BOM de opencode.jsonc."
|
||||
},
|
||||
{
|
||||
"id": "2026-09-20-plugin-global-opencode-relanza-omniroute",
|
||||
"fecha": "2026-09-20",
|
||||
"equipo": "sobremesa",
|
||||
"categoria": "opencode",
|
||||
"titulo": "Plugin global OpenCode relanza OmniRoute",
|
||||
"transferible": true,
|
||||
"aplicado_en": [
|
||||
"sobremesa"
|
||||
],
|
||||
"detalle": "omniroute-bootstrap.ts en ~/.config/opencode/plugins/ lanza el vigilante -Once al arrancar OpenCode en cualquier repo."
|
||||
}
|
||||
]
|
||||
}
|
||||
98
omniroute/install_omniroute_autostart.ps1
Normal file
98
omniroute/install_omniroute_autostart.ps1
Normal file
@@ -0,0 +1,98 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Instala el arranque fiable de OmniRoute al iniciar sesion de Windows.
|
||||
|
||||
.DESCRIPTION
|
||||
Sustituye el autostart fragil de OmniRoute (un .vbs en la carpeta Inicio, ya
|
||||
afectado por la deprecacion de VBScript en Windows) por una tarea programada
|
||||
que ejecuta el vigilante omniroute_watchdog.ps1:
|
||||
- Se lanza al iniciar sesion del usuario.
|
||||
- Arranca OmniRoute si no esta corriendo.
|
||||
- Lo relanza si se cae durante la sesion (autocuracion).
|
||||
|
||||
Pasos:
|
||||
1. Desactiva el autostart propio de OmniRoute (OmniRoute.vbs) para evitar dobles arranques.
|
||||
2. Registra la tarea programada "OmniRoute Watchdog".
|
||||
3. La arranca ahora mismo.
|
||||
|
||||
.NOTES
|
||||
No requiere privilegios de administrador (tarea del propio usuario).
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$NoStart
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$TaskName = 'OmniRoute Watchdog'
|
||||
$ScriptPath = Join-Path $PSScriptRoot 'omniroute_watchdog.ps1'
|
||||
$PwshExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||
$NodeExe = 'C:\Program Files\nodejs\node.exe'
|
||||
$OmniRouteMjs = Join-Path $env:APPDATA 'npm\node_modules\omniroute\bin\omniroute.mjs'
|
||||
$UserId = "$env:USERDOMAIN\$env:USERNAME"
|
||||
|
||||
Write-Host '== Instalando arranque fiable de OmniRoute ==' -ForegroundColor Cyan
|
||||
|
||||
# 1) Desactivar el autostart VBS propio de OmniRoute -----------------------
|
||||
$vbs = Join-Path ([Environment]::GetFolderPath('Startup')) 'OmniRoute.vbs'
|
||||
if ((Test-Path $NodeExe) -and (Test-Path $OmniRouteMjs)) {
|
||||
Write-Host ' - Desactivando autostart VBS de OmniRoute...' -ForegroundColor Yellow
|
||||
$prevEap = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try { & $NodeExe $OmniRouteMjs autostart disable 2>&1 | Out-Null } catch { }
|
||||
$ErrorActionPreference = $prevEap
|
||||
}
|
||||
if (Test-Path $vbs) {
|
||||
Remove-Item -LiteralPath $vbs -Force
|
||||
Write-Host ' - Eliminado OmniRoute.vbs de la carpeta Inicio.' -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 2) Registrar la tarea programada -----------------------------------------
|
||||
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
|
||||
Write-Host ' - Eliminando tarea anterior...' -ForegroundColor Yellow
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
$Action = New-ScheduledTaskAction `
|
||||
-Execute $PwshExe `
|
||||
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$ScriptPath`"" `
|
||||
-WorkingDirectory $env:USERPROFILE
|
||||
|
||||
$Trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
||||
|
||||
$Settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-RestartCount 999 `
|
||||
-RestartInterval (New-TimeSpan -Minutes 1) `
|
||||
-MultipleInstances IgnoreNew
|
||||
$Settings.ExecutionTimeLimit = 'PT0S' # sin limite de tiempo (vigilante permanente)
|
||||
$Settings.Hidden = $true
|
||||
|
||||
$Principal = New-ScheduledTaskPrincipal -UserId $UserId -LogonType Interactive -RunLevel Limited
|
||||
|
||||
Register-ScheduledTask `
|
||||
-TaskName $TaskName `
|
||||
-Action $Action `
|
||||
-Trigger $Trigger `
|
||||
-Settings $Settings `
|
||||
-Principal $Principal `
|
||||
-Description 'Arranca y vigila OmniRoute (localhost:20128) al iniciar sesion. Autocuracion si se cae.' `
|
||||
-Force | Out-Null
|
||||
|
||||
Write-Host " - Tarea '$TaskName' registrada." -ForegroundColor Green
|
||||
|
||||
# 3) Arrancar ahora ---------------------------------------------------------
|
||||
if (-not $NoStart) {
|
||||
Write-Host ' - Arrancando el vigilante ahora...' -ForegroundColor Yellow
|
||||
Start-ScheduledTask -TaskName $TaskName
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Listo. Comandos utiles:' -ForegroundColor Cyan
|
||||
Write-Host " Get-ScheduledTask -TaskName '$TaskName' | Get-ScheduledTaskInfo"
|
||||
Write-Host " Start-ScheduledTask -TaskName '$TaskName'"
|
||||
Write-Host " Stop-ScheduledTask -TaskName '$TaskName'"
|
||||
Write-Host " Get-Content `"$env:USERPROFILE\.omniroute\watchdog.log`" -Tail 20"
|
||||
174
omniroute/omniroute_watchdog.ps1
Normal file
174
omniroute/omniroute_watchdog.ps1
Normal file
@@ -0,0 +1,174 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Vigilante (watchdog) de OmniRoute: garantiza que el servidor local este SIEMPRE arriba.
|
||||
|
||||
.DESCRIPTION
|
||||
OmniRoute escucha en http://127.0.0.1:20128 y tarda ~60 s en quedar listo.
|
||||
Este vigilante:
|
||||
- Comprueba el puerto 20128 periodicamente.
|
||||
- Si no hay proceso, arranca el servidor en segundo plano (oculto, desde un
|
||||
directorio neutro para no contaminarse con .env de repositorios).
|
||||
- Si hay proceso pero no responde tras el tiempo de arranque, lo mata y lo relanza.
|
||||
Se lanza al iniciar sesion mediante la tarea programada "OmniRoute Watchdog".
|
||||
|
||||
.PARAMETER Once
|
||||
Comprueba/arranca una sola vez, espera a que responda y sale (para pruebas).
|
||||
.PARAMETER IntervalSeconds
|
||||
Segundos entre comprobaciones del bucle de vigilancia (por defecto 120).
|
||||
.PARAMETER StartupTimeoutSeconds
|
||||
Segundos maximos de espera a que OmniRoute responda antes de considerarlo colgado.
|
||||
.PARAMETER UseTray
|
||||
Arranca OmniRoute con icono en la bandeja del sistema (por defecto NO, para maxima
|
||||
fiabilidad en el arranque desatendido).
|
||||
|
||||
.NOTES
|
||||
Repo: biblioteca_negocio_prolongo. Regla 24 no aplica (no toca NET/VB/4GL).
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Once,
|
||||
[int]$IntervalSeconds = 120,
|
||||
[int]$StartupTimeoutSeconds = 180,
|
||||
[switch]$UseTray
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
|
||||
# --- Configuracion ---------------------------------------------------------
|
||||
$OmniHost = '127.0.0.1'
|
||||
$Port = 20128
|
||||
$NodeExe = 'C:\Program Files\nodejs\node.exe'
|
||||
$OmniRouteMjs = Join-Path $env:APPDATA 'npm\node_modules\omniroute\bin\omniroute.mjs'
|
||||
$WorkDir = $env:USERPROFILE # directorio neutro (sin .env de repos)
|
||||
$LogDir = Join-Path $env:USERPROFILE '.omniroute'
|
||||
$LogFile = Join-Path $LogDir 'watchdog.log'
|
||||
$MaxLogBytes = 1MB
|
||||
|
||||
# --- Utilidades ------------------------------------------------------------
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = 'INFO')
|
||||
$line = '{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
|
||||
if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null }
|
||||
if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length -gt $MaxLogBytes)) {
|
||||
Move-Item -LiteralPath $LogFile -Destination "$LogFile.1" -Force
|
||||
}
|
||||
Add-Content -LiteralPath $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
function Resolve-NodeExe {
|
||||
if (Test-Path $NodeExe) { return $NodeExe }
|
||||
$cmd = Get-Command node -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
return $NodeExe
|
||||
}
|
||||
|
||||
function Test-OmniRoutePort {
|
||||
$client = New-Object System.Net.Sockets.TcpClient
|
||||
try {
|
||||
$iar = $client.BeginConnect($OmniHost, $Port, $null, $null)
|
||||
if ($iar.AsyncWaitHandle.WaitOne(2000)) {
|
||||
$client.EndConnect($iar)
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
} catch {
|
||||
return $false
|
||||
} finally {
|
||||
try { $client.Close() } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-OmniRouteProcesses {
|
||||
Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.CommandLine -and
|
||||
($_.CommandLine -like '*omniroute*') -and
|
||||
($_.CommandLine -like '*serve*')
|
||||
}
|
||||
}
|
||||
|
||||
function Start-OmniRouteServer {
|
||||
$node = Resolve-NodeExe
|
||||
if (-not (Test-Path $OmniRouteMjs)) {
|
||||
Write-Log "No se encuentra omniroute.mjs en $OmniRouteMjs" 'ERROR'
|
||||
return
|
||||
}
|
||||
$argList = @("`"$OmniRouteMjs`"", 'serve', '--no-open')
|
||||
if ($UseTray) { $argList += '--tray' } else { $argList += '--no-tray' }
|
||||
|
||||
Start-Process -FilePath $node `
|
||||
-ArgumentList $argList `
|
||||
-WorkingDirectory $WorkDir `
|
||||
-WindowStyle Hidden
|
||||
Write-Log "Servidor OmniRoute lanzado (tray=$([bool]$UseTray))."
|
||||
}
|
||||
|
||||
function Wait-OmniRouteUp {
|
||||
param([int]$TimeoutSeconds)
|
||||
for ($i = 0; $i -lt $TimeoutSeconds; $i += 3) {
|
||||
Start-Sleep -Seconds 3
|
||||
if (Test-OmniRoutePort) { return $true }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
# --- Bloqueo de instancia unica -------------------------------------------
|
||||
$mutex = New-Object System.Threading.Mutex($false, 'Local\OmniRouteWatchdog')
|
||||
if (-not $mutex.WaitOne(0)) {
|
||||
Write-Log 'Ya hay otro vigilante en ejecucion. Saliendo.' 'WARN'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Modo puntual (-Once) --------------------------------------------------
|
||||
if ($Once) {
|
||||
if (Test-OmniRoutePort) {
|
||||
Write-Log "Comprobacion puntual: OmniRoute ya responde en $Port." 'OK'
|
||||
exit 0
|
||||
}
|
||||
$procs = @(Get-OmniRouteProcesses)
|
||||
if ($procs.Count -eq 0) {
|
||||
Write-Log 'Comprobacion puntual: OmniRoute caido. Arrancando...' 'WARN'
|
||||
Start-OmniRouteServer
|
||||
} else {
|
||||
Write-Log "Comprobacion puntual: proceso presente (PID $($procs[0].ProcessId)), esperando puerto..." 'WARN'
|
||||
}
|
||||
if (Wait-OmniRouteUp -TimeoutSeconds $StartupTimeoutSeconds) {
|
||||
Write-Log "Comprobacion puntual: OmniRoute arriba en $Port." 'OK'
|
||||
exit 0
|
||||
}
|
||||
Write-Log "Comprobacion puntual: OmniRoute NO respondio en $StartupTimeoutSeconds s." 'ERROR'
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Bucle de vigilancia ---------------------------------------------------
|
||||
Write-Log 'Vigilante de OmniRoute iniciado.'
|
||||
$downSince = $null
|
||||
|
||||
while ($true) {
|
||||
if (Test-OmniRoutePort) {
|
||||
if ($downSince) { Write-Log "OmniRoute vuelve a responder en $Port." 'OK' }
|
||||
$downSince = $null
|
||||
}
|
||||
else {
|
||||
$procs = @(Get-OmniRouteProcesses)
|
||||
if ($procs.Count -gt 0) {
|
||||
if (-not $downSince) {
|
||||
$downSince = Get-Date
|
||||
Write-Log "Proceso OmniRoute presente (PID $($procs[0].ProcessId)) pero $Port aun no responde; esperando arranque (~60 s)..."
|
||||
}
|
||||
elseif (((Get-Date) - $downSince).TotalSeconds -gt $StartupTimeoutSeconds) {
|
||||
Write-Log "OmniRoute lleva mas de $StartupTimeoutSeconds s sin responder. Matando procesos colgados y relanzando." 'WARN'
|
||||
$procs | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||
Start-Sleep -Seconds 3
|
||||
Start-OmniRouteServer
|
||||
$downSince = Get-Date
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log "OmniRoute CAIDO (puerto $Port cerrado). Arrancando servidor..." 'WARN'
|
||||
Start-OmniRouteServer
|
||||
$downSince = Get-Date
|
||||
}
|
||||
}
|
||||
Start-Sleep -Seconds $IntervalSeconds
|
||||
}
|
||||
67
opencode/omniroute-bootstrap.ts
Normal file
67
opencode/omniroute-bootstrap.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* OmniRoute Bootstrap — plugin GLOBAL de OpenCode
|
||||
*
|
||||
* Objetivo: que al arrancar OpenCode (en CUALQUIER repositorio) se asegure de que
|
||||
* OmniRoute esta levantado en localhost:20128, lanzando el vigilante en modo `-Once`.
|
||||
*
|
||||
* No depende de ningun repositorio: vive en la config global de OpenCode
|
||||
* (~/.config/opencode/plugins/) y el vigilante canonico esta en el repo INFRAESTRUCTURA.
|
||||
*
|
||||
* Instalacion: `scripts/instalar_global.ps1` (copia este fichero a la carpeta global
|
||||
* de plugins de OpenCode y registra la tarea de arranque).
|
||||
*
|
||||
* Nota: el vigilante ya corre como tarea programada al iniciar sesion; este plugin es
|
||||
* la red de seguridad para cuando OpenCode arranca sin que el vigilante este vivo.
|
||||
*/
|
||||
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
import { spawn } from "node:child_process"
|
||||
import { existsSync } from "node:fs"
|
||||
|
||||
// --- Configuracion ---------------------------------------------------------
|
||||
|
||||
// Vigilante canonico (repo INFRAESTRUCTURA). Se puede sobreescribir con OMNIROUTE_WATCHDOG.
|
||||
const WATCHDOG =
|
||||
process.env.OMNIROUTE_WATCHDOG ??
|
||||
"C:\\Users\\juanm\\Documents\\Github\\INFRAESTRUCTURA\\omniroute\\omniroute_watchdog.ps1"
|
||||
|
||||
const POWERSHELL =
|
||||
process.env.SystemRoot
|
||||
? `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
|
||||
: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
|
||||
|
||||
// Evita lanzar el vigilante mas de una vez por proceso de OpenCode.
|
||||
let launched = false
|
||||
|
||||
function ensureOmniRoute(): void {
|
||||
if (launched) return
|
||||
launched = true
|
||||
try {
|
||||
if (!existsSync(WATCHDOG)) return
|
||||
const child = spawn(
|
||||
POWERSHELL,
|
||||
[
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-WindowStyle",
|
||||
"Hidden",
|
||||
"-File",
|
||||
WATCHDOG,
|
||||
"-Once",
|
||||
],
|
||||
{ detached: true, stdio: "ignore", windowsHide: true },
|
||||
)
|
||||
child.unref()
|
||||
} catch {
|
||||
// Silencioso: el arranque de OpenCode no debe romperse por OmniRoute.
|
||||
}
|
||||
}
|
||||
|
||||
// --- Plugin ----------------------------------------------------------------
|
||||
|
||||
export const OmniRouteBootstrap: Plugin = async () => {
|
||||
ensureOmniRoute()
|
||||
return {}
|
||||
}
|
||||
256
scripts/infra_changelog.py
Normal file
256
scripts/infra_changelog.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Changelog global de infraestructura (sobremesa <-> portatil <-> VPS).
|
||||
|
||||
Fichero unico para saber que cambios de infraestructura se han hecho en cada equipo
|
||||
y cuales hay que replicar en el otro. Fuente de verdad: `changelog_infra.json`
|
||||
(en este repo). Se genera `CHANGELOG_INFRA.md` para lectura humana.
|
||||
|
||||
Uso:
|
||||
python scripts/infra_changelog.py add --titulo "..." --categoria omniroute \
|
||||
--transferible si --detalle "..." [--equipo sobremesa] [--id slug]
|
||||
python scripts/infra_changelog.py pendientes [--json]
|
||||
python scripts/infra_changelog.py aplicado <id> [--equipo sobremesa]
|
||||
python scripts/infra_changelog.py listar [--equipo X]
|
||||
python scripts/infra_changelog.py estado
|
||||
python scripts/infra_changelog.py render
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
JSON_FILE = REPO / "changelog_infra.json"
|
||||
MD_FILE = REPO / "CHANGELOG_INFRA.md"
|
||||
|
||||
CATEGORIAS = ["omniroute", "opencode", "red", "servicios", "drivers", "memoria", "otros"]
|
||||
|
||||
|
||||
# --- Deteccion de equipo ---------------------------------------------------
|
||||
|
||||
def _powershell(query: str) -> str:
|
||||
"""Ejecuta una consulta CIM via PowerShell y devuelve la salida."""
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["powershell", "-NoProfile", "-NonInteractive", "-Command", query],
|
||||
capture_output=True, text=True, timeout=15,
|
||||
).stdout
|
||||
return out.strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _huella_hardware() -> str:
|
||||
"""CPU + modelo del equipo (Windows). wmic ya no existe en Windows 11 nuevo."""
|
||||
cpu = _powershell("(Get-CimInstance Win32_Processor).Name")
|
||||
modelo = _powershell("(Get-CimInstance Win32_ComputerSystem).Model")
|
||||
if not cpu and not modelo:
|
||||
cpu = platform.processor() or ""
|
||||
return f"{cpu} | {modelo}".lower()
|
||||
|
||||
|
||||
def detectar_equipo() -> str:
|
||||
"""Devuelve 'sobremesa', 'portatil' o el hostname si no se reconoce."""
|
||||
huella = _huella_hardware()
|
||||
if "7900x" in huella or "b650" in huella:
|
||||
return "sobremesa"
|
||||
if "255hx" in huella or "vector" in huella or "ultra 7" in huella:
|
||||
return "portatil"
|
||||
return socket.gethostname().lower()
|
||||
|
||||
|
||||
# --- Persistencia ----------------------------------------------------------
|
||||
|
||||
def cargar() -> dict:
|
||||
if JSON_FILE.exists():
|
||||
return json.loads(JSON_FILE.read_text(encoding="utf-8"))
|
||||
return {"version": 1, "entries": []}
|
||||
|
||||
|
||||
def guardar(data: dict) -> None:
|
||||
JSON_FILE.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
render(data)
|
||||
|
||||
|
||||
def _slug(texto: str) -> str:
|
||||
s = re.sub(r"[^a-z0-9]+", "-", texto.lower()).strip("-")
|
||||
return s[:60] or "cambio"
|
||||
|
||||
|
||||
# --- Render markdown -------------------------------------------------------
|
||||
|
||||
def render(data: dict | None = None) -> None:
|
||||
data = data or cargar()
|
||||
entries = data.get("entries", [])
|
||||
lineas: list[str] = []
|
||||
lineas.append("# Changelog de infraestructura — global (sobremesa <-> portatil <-> VPS)")
|
||||
lineas.append("")
|
||||
lineas.append("> Fichero generado por `scripts/infra_changelog.py`. NO editar a mano.")
|
||||
lineas.append("> Fuente de verdad: `changelog_infra.json`.")
|
||||
lineas.append("")
|
||||
lineas.append("## Pendientes de replicar")
|
||||
lineas.append("")
|
||||
pend = [e for e in entries if e.get("transferible") and len(e.get("aplicado_en", [])) < 2]
|
||||
if pend:
|
||||
for e in pend:
|
||||
equipos = "|".join(e.get("aplicado_en", [])) or "-"
|
||||
lineas.append(f"- **{e['id']}** [{e.get('categoria','otros')}] {e['titulo']} "
|
||||
f"(aplicado en: {equipos})")
|
||||
else:
|
||||
lineas.append("_(nada pendiente)_")
|
||||
lineas.append("")
|
||||
lineas.append("## Historial")
|
||||
lineas.append("")
|
||||
for e in sorted(entries, key=lambda x: x.get("fecha", ""), reverse=True):
|
||||
lineas.append(f"### {e.get('fecha','?')} · {e.get('titulo','')}")
|
||||
lineas.append(f"- **id**: `{e['id']}`")
|
||||
lineas.append(f"- **Equipo**: {e.get('equipo','?')}")
|
||||
lineas.append(f"- **Categoria**: {e.get('categoria','otros')}")
|
||||
lineas.append(f"- **Transferible**: {'si' if e.get('transferible') else 'no'}")
|
||||
lineas.append(f"- **Aplicado en**: {', '.join(e.get('aplicado_en', [])) or '-'}")
|
||||
if e.get("detalle"):
|
||||
lineas.append(f"- **Detalle**: {e['detalle']}")
|
||||
lineas.append("")
|
||||
MD_FILE.write_text("\n".join(lineas), encoding="utf-8")
|
||||
|
||||
|
||||
# --- Comandos --------------------------------------------------------------
|
||||
|
||||
def cmd_add(args: argparse.Namespace) -> int:
|
||||
data = cargar()
|
||||
equipo = args.equipo or detectar_equipo()
|
||||
entrada_id = args.id or f"{date.today().isoformat()}-{_slug(args.titulo)}"
|
||||
if any(e["id"] == entrada_id for e in data["entries"]):
|
||||
print(f"Ya existe una entrada con id '{entrada_id}'.", file=sys.stderr)
|
||||
return 1
|
||||
data["entries"].append({
|
||||
"id": entrada_id,
|
||||
"fecha": args.fecha or date.today().isoformat(),
|
||||
"equipo": equipo,
|
||||
"categoria": args.categoria,
|
||||
"titulo": args.titulo,
|
||||
"transferible": args.transferible == "si",
|
||||
"aplicado_en": [equipo],
|
||||
"detalle": args.detalle or "",
|
||||
})
|
||||
guardar(data)
|
||||
print(f"Anadido '{entrada_id}' (equipo={equipo}, transferible={args.transferible}).")
|
||||
return 0
|
||||
|
||||
|
||||
def _cargar_para_lectura() -> dict:
|
||||
return cargar()
|
||||
|
||||
|
||||
def cmd_pendientes(args: argparse.Namespace) -> int:
|
||||
data = _cargar_para_lectura()
|
||||
equipo = args.equipo or detectar_equipo()
|
||||
pend = [
|
||||
e for e in data.get("entries", [])
|
||||
if e.get("transferible") and equipo not in e.get("aplicado_en", [])
|
||||
]
|
||||
if args.json:
|
||||
print(json.dumps(pend, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if not pend:
|
||||
print(f"Sin pendientes de replicar en '{equipo}'.")
|
||||
return 0
|
||||
print(f"Pendientes de replicar en '{equipo}':")
|
||||
for e in pend:
|
||||
print(f" - {e['id']} [{e.get('categoria')}] {e['titulo']} "
|
||||
f"(origen: {e.get('equipo')}, aplicado en: {', '.join(e.get('aplicado_en', []))})")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_aplicado(args: argparse.Namespace) -> int:
|
||||
data = cargar()
|
||||
equipo = args.equipo or detectar_equipo()
|
||||
for e in data.get("entries", []):
|
||||
if e["id"] == args.id:
|
||||
if equipo not in e.get("aplicado_en", []):
|
||||
e.setdefault("aplicado_en", []).append(equipo)
|
||||
guardar(data)
|
||||
print(f"Marcado '{args.id}' como aplicado en '{equipo}'.")
|
||||
else:
|
||||
print(f"'{args.id}' ya estaba aplicado en '{equipo}'.")
|
||||
return 0
|
||||
print(f"No existe la entrada '{args.id}'.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def cmd_listar(args: argparse.Namespace) -> int:
|
||||
data = cargar()
|
||||
entries = data.get("entries", [])
|
||||
if args.equipo:
|
||||
entries = [e for e in entries if e.get("equipo") == args.equipo]
|
||||
for e in sorted(entries, key=lambda x: x.get("fecha", ""), reverse=True):
|
||||
print(f"{e.get('fecha','?')} [{e.get('categoria','otros')}] {e['titulo']} "
|
||||
f"(equipo={e.get('equipo')}, transf={'si' if e.get('transferible') else 'no'}, "
|
||||
f"aplicado en: {', '.join(e.get('aplicado_en', [])) or '-'})")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_estado(args: argparse.Namespace) -> int:
|
||||
equipo = detectar_equipo()
|
||||
data = cargar()
|
||||
total = len(data.get("entries", []))
|
||||
pend = [
|
||||
e for e in data.get("entries", [])
|
||||
if e.get("transferible") and equipo not in e.get("aplicado_en", [])
|
||||
]
|
||||
print(f"Equipo actual: {equipo}")
|
||||
print(f"Entradas totales: {total}")
|
||||
print(f"Pendientes de replicar aqui: {len(pend)}")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(description="Changelog global de infraestructura.")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
a = sub.add_parser("add", help="Anadir una entrada")
|
||||
a.add_argument("--titulo", required=True)
|
||||
a.add_argument("--categoria", default="otros", choices=CATEGORIAS)
|
||||
a.add_argument("--transferible", default="si", choices=["si", "no"])
|
||||
a.add_argument("--detalle", default="")
|
||||
a.add_argument("--equipo", default=None)
|
||||
a.add_argument("--fecha", default=None)
|
||||
a.add_argument("--id", default=None)
|
||||
a.set_defaults(func=cmd_add)
|
||||
|
||||
pe = sub.add_parser("pendientes", help="Ver pendientes de replicar en este equipo")
|
||||
pe.add_argument("--equipo", default=None)
|
||||
pe.add_argument("--json", action="store_true")
|
||||
pe.set_defaults(func=cmd_pendientes)
|
||||
|
||||
ap = sub.add_parser("aplicado", help="Marcar una entrada como aplicada en este equipo")
|
||||
ap.add_argument("id")
|
||||
ap.add_argument("--equipo", default=None)
|
||||
ap.set_defaults(func=cmd_aplicado)
|
||||
|
||||
li = sub.add_parser("listar", help="Listar entradas")
|
||||
li.add_argument("--equipo", default=None)
|
||||
li.set_defaults(func=cmd_listar)
|
||||
|
||||
es = sub.add_parser("estado", help="Resumen")
|
||||
es.set_defaults(func=cmd_estado)
|
||||
|
||||
re_ = sub.add_parser("render", help="Regenerar CHANGELOG_INFRA.md")
|
||||
re_.set_defaults(func=lambda a: (render(), 0)[1])
|
||||
|
||||
args = p.parse_args(argv)
|
||||
return args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
88
scripts/instalar_global.ps1
Normal file
88
scripts/instalar_global.ps1
Normal file
@@ -0,0 +1,88 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Instala en el equipo actual la infraestructura global de OmniRoute + OpenCode.
|
||||
|
||||
.DESCRIPTION
|
||||
1. Copia el plugin global `omniroute-bootstrap.ts` a la carpeta de plugins de OpenCode
|
||||
(~/.config/opencode/plugins/). Asi OpenCode relanza OmniRoute al arrancar, en
|
||||
cualquier repositorio.
|
||||
2. Registra/actualiza la tarea programada `OmniRoute Watchdog` apuntando al vigilante
|
||||
canonico de este repo (INFRAESTRUCTURA).
|
||||
3. Arranca la tarea.
|
||||
|
||||
No requiere administrador. Idempotente: se puede re-ejecutar tras un git pull.
|
||||
|
||||
.NOTES
|
||||
Repo: INFRAESTRUCTURA.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$NoStart
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$TaskName = 'OmniRoute Watchdog'
|
||||
$PwshExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||
$Watchdog = Join-Path $RepoRoot 'omniroute\omniroute_watchdog.ps1'
|
||||
$PluginSrc = Join-Path $RepoRoot 'opencode\omniroute-bootstrap.ts'
|
||||
$PluginDir = Join-Path $env:USERPROFILE '.config\opencode\plugins'
|
||||
$PluginDst = Join-Path $PluginDir 'omniroute-bootstrap.ts'
|
||||
$UserId = "$env:USERDOMAIN\$env:USERNAME"
|
||||
|
||||
Write-Host '== Instalando infraestructura global (OmniRoute + OpenCode) ==' -ForegroundColor Cyan
|
||||
|
||||
# 1) Plugin global de OpenCode ---------------------------------------------
|
||||
if (-not (Test-Path $PluginDir)) { New-Item -ItemType Directory -Force -Path $PluginDir | Out-Null }
|
||||
Copy-Item -LiteralPath $PluginSrc -Destination $PluginDst -Force
|
||||
Write-Host " - Plugin global instalado en $PluginDst" -ForegroundColor Green
|
||||
|
||||
# 2) Tarea programada ------------------------------------------------------
|
||||
if (-not (Test-Path $Watchdog)) { throw "No se encuentra el vigilante: $Watchdog" }
|
||||
|
||||
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
|
||||
Write-Host ' - Eliminando tarea anterior...' -ForegroundColor Yellow
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
$Action = New-ScheduledTaskAction `
|
||||
-Execute $PwshExe `
|
||||
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$Watchdog`"" `
|
||||
-WorkingDirectory $env:USERPROFILE
|
||||
|
||||
$Trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
||||
|
||||
$Settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-RestartCount 999 `
|
||||
-RestartInterval (New-TimeSpan -Minutes 1) `
|
||||
-MultipleInstances IgnoreNew
|
||||
$Settings.ExecutionTimeLimit = 'PT0S'
|
||||
$Settings.Hidden = $true
|
||||
|
||||
$Principal = New-ScheduledTaskPrincipal -UserId $UserId -LogonType Interactive -RunLevel Limited
|
||||
|
||||
Register-ScheduledTask `
|
||||
-TaskName $TaskName `
|
||||
-Action $Action `
|
||||
-Trigger $Trigger `
|
||||
-Settings $Settings `
|
||||
-Principal $Principal `
|
||||
-Description 'Arranca y vigila OmniRoute (localhost:20128) al iniciar sesion. Autocuracion.' `
|
||||
-Force | Out-Null
|
||||
|
||||
Write-Host " - Tarea '$TaskName' registrada -> $Watchdog" -ForegroundColor Green
|
||||
|
||||
# 3) Arrancar --------------------------------------------------------------
|
||||
if (-not $NoStart) {
|
||||
Start-ScheduledTask -TaskName $TaskName
|
||||
Write-Host ' - Vigilante arrancado.' -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Listo. Comprobar:' -ForegroundColor Cyan
|
||||
Write-Host " Get-ScheduledTask -TaskName '$TaskName' | Get-ScheduledTaskInfo"
|
||||
Write-Host " Get-Content `"$env:USERPROFILE\.omniroute\watchdog.log`" -Tail 20"
|
||||
Reference in New Issue
Block a user