# Leaga OpenCode de pe Windows la cele 3 modele de pe PC2 (comutabile cu /models), # la uneltele homelab/toolsmith/ssh/cautare web si la comenzile /think si /copy. # # irm http://10.11.90.169:8088/setup.ps1 | iex # # Variabile optionale, setate inainte de rulare: # $env:QWEN_SRV = "http://10.11.90.169:9090" alt server de modele # $env:HOMELAB = "0" sare peste uneltele MCP # $env:HOMELAB_HOST / $env:HOMELAB_USER daca SSH-ul catre PC2 e altfel # $env:OC_BASELINE= "1" binar pentru procesoare fara AVX2 # # Fara diacritice intentionat: serverul nu trimite charset, iar Windows PowerShell 5.1 # decodeaza raspunsul ca Latin-1 si le-ar transforma in mojibake. function Install-OpenCodePC2 { $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # altfel bara de progres incetineste descarcarea de ~10x # In PowerShell 7.4+ un cod de iesire non-zero al unui program extern devine # exceptie cand ErrorActionPreference e 'Stop'. Fara linia asta, simpla VERIFICARE # daca cheia SSH merge (care esueaza intentionat prima data) omoara tot scriptul. $PSNativeCommandUseErrorActionPreference = $false try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} $srv = if ($env:QWEN_SRV) { $env:QWEN_SRV } else { 'http://10.11.90.169:9090' } $setupSrv= if ($env:SETUP_SRV) { $env:SETUP_SRV } else { 'http://10.11.90.169:8088' } $hlHost = if ($env:HOMELAB_HOST) { $env:HOMELAB_HOST } else { '10.11.90.169' } $hlUser = if ($env:HOMELAB_USER) { $env:HOMELAB_USER } else { 'andrei' } $prov = 'pc2' $model = 'qwen3.6-q4' function say($m) { Write-Host " $m" } function warn($m) { Write-Host " ATENTIE: $m" -ForegroundColor Yellow } Write-Host "== Legare OpenCode la modelele de pe PC2 ==" -ForegroundColor Cyan # 1. serverul raspunde? say "verific serverul $srv ..." try { $modele = (Invoke-RestMethod -Uri "$srv/v1/models" -TimeoutSec 15).data.id } catch { Write-Host "EROARE: $srv nu raspunde." -ForegroundColor Red Write-Host "Verifica VPN-ul, sau pe PC2: systemctl status llama-swap" -ForegroundColor Red return } say "serverul raspunde. modele: $($modele -join ', ')" # 2. OpenCode instalat? $oc = (Get-Command opencode -ErrorAction SilentlyContinue).Source if (-not $oc) { $candidat = Join-Path $HOME '.opencode\bin\opencode.exe' if (Test-Path $candidat) { $oc = $candidat } } if ($oc) { say "OpenCode: deja instalat ($oc)" } else { say "OpenCode lipseste, il descarc ..." $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } elseif ($env:OC_BASELINE -eq '1') { 'x64-baseline' } else { 'x64' } $binDir = Join-Path $HOME '.opencode\bin' New-Item -ItemType Directory -Force -Path $binDir | Out-Null $zip = Join-Path $env:TEMP "opencode-windows-$arch.zip" $url = "https://github.com/anomalyco/opencode/releases/latest/download/opencode-windows-$arch.zip" try { Invoke-WebRequest -Uri $url -OutFile $zip -TimeoutSec 600 Expand-Archive -Path $zip -DestinationPath $binDir -Force Remove-Item $zip -ErrorAction SilentlyContinue } catch { Write-Host "EROARE: descarcarea a esuat: $_" -ForegroundColor Red Write-Host "Alternativ: npm install -g opencode-ai sau scoop install opencode" -ForegroundColor Red return } $oc = Join-Path $binDir 'opencode.exe' if (-not (Test-Path $oc)) { # unele arhive pun binarul intr-un subdirector $gasit = Get-ChildItem -Path $binDir -Filter 'opencode.exe' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($gasit) { $oc = $gasit.FullName } else { Write-Host "EROARE: nu gasesc opencode.exe in arhiva." -ForegroundColor Red; return } } # PATH-ul utilizatorului, ca sa mearga "opencode" din orice terminal nou $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($userPath -notlike "*$binDir*") { [Environment]::SetEnvironmentVariable('Path', "$userPath;$binDir", 'User') say "am adaugat $binDir in PATH (valabil in terminalele noi)" } $env:Path = "$env:Path;$binDir" say "OpenCode instalat: $oc" } $ver = & $oc --version 2>&1 | Select-Object -Last 1 say "versiune: $ver" # 3. unde isi tine OpenCode configuratia -- il intrebam pe el, nu ghicim calea $cfgDir = $null try { $paths = & $oc debug paths 2>&1 foreach ($l in $paths) { if ("$l" -match '^\s*config\s+(.+?)\s*$') { $cfgDir = $Matches[1]; break } } } catch {} if (-not $cfgDir) { $cfgDir = if ($env:XDG_CONFIG_HOME) { Join-Path $env:XDG_CONFIG_HOME 'opencode' } else { Join-Path $HOME '.config\opencode' } warn "nu am putut citi 'opencode debug paths', folosesc $cfgDir" } New-Item -ItemType Directory -Force -Path $cfgDir | Out-Null $cfg = Join-Path $cfgDir 'opencode.json' say "configuratie: $cfg" # 4. uneltele MCP: serverele ruleaza pe PC2, aduse aici prin SSH. # Inventarul cu parole ramane astfel intr-un singur loc. $prefix = $null if ($env:HOMELAB -ne '0') { # Tot blocul SSH e optional. Orice esec aici nu are voie sa opreasca scriptul # inainte de scrierea configuratiei -- altfel raman si fara modele, nu doar # fara unelte, si modelele sunt lucrul principal. try { $ssh = (Get-Command ssh -ErrorAction SilentlyContinue).Source if (-not $ssh) { warn "lipseste clientul OpenSSH, deci nu adaug uneltele MCP." warn "instaleaza-l cu: Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0" } else { $tinta = "$hlUser@$hlHost" & ssh -n -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new $tinta true 2>$null if ($LASTEXITCODE -eq 0) { say "homelab: cheia SSH catre $hlHost merge deja" $prefix = @('ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', $tinta) } else { say "homelab: nu am acces cu cheie la $hlHost, il configurez acum" $cheie = Join-Path $HOME '.ssh\id_ed25519' if (-not (Test-Path $cheie) -and -not (Test-Path (Join-Path $HOME '.ssh\id_rsa'))) { say "generez o cheie SSH noua (fara parafraza)" New-Item -ItemType Directory -Force -Path (Join-Path $HOME '.ssh') | Out-Null & ssh-keygen -t ed25519 -N '""' -f $cheie -q } $pub = if (Test-Path "$cheie.pub") { "$cheie.pub" } else { Join-Path $HOME '.ssh\id_rsa.pub' } Write-Host "" Write-Host " >>> Urmeaza sa ti se ceara PAROLA contului $hlUser de pe $hlHost." -ForegroundColor Cyan Write-Host " >>> Se intampla o singura data; dupa aceea merge cu cheia." -ForegroundColor Cyan Write-Host "" # ssh-copy-id nu exista pe Windows. Cheia NU se trimite pe stdin: cu stdin # redirectat, ssh.exe nu mai poate cere parola la terminal si cade direct in # "Permission denied (publickey,password)". Deci o punem in comanda de la # distanta si lasam stdin-ul terminalului liber pentru parola. $cheiePub = (Get-Content $pub -Raw).Trim() -replace "[`r`n]", '' if ($cheiePub -match "'") { warn "cheia publica are un apostrof in comentariu; il scot ca sa nu strice comanda" $cheiePub = $cheiePub -replace "'", '' } $comanda = "mkdir -p ~/.ssh && chmod 700 ~/.ssh && " + "grep -qxF '$cheiePub' ~/.ssh/authorized_keys 2>/dev/null || " + "printf '%s\n' '$cheiePub' >> ~/.ssh/authorized_keys; " + "chmod 600 ~/.ssh/authorized_keys" & ssh -o StrictHostKeyChecking=accept-new $tinta $comanda & ssh -n -o BatchMode=yes -o ConnectTimeout=8 $tinta true 2>$null if ($LASTEXITCODE -eq 0) { say "homelab: cheia merge acum" $prefix = @('ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', $tinta) } else { warn "cheia SSH tot nu merge, deci NU adaug uneltele MCP." warn "un tool care nu porneste ar da erori la fiecare pornire de OpenCode." warn "modelele vor merge oricum; uneltele le poti adauga mai tarziu." } } } } catch { $prefix = $null warn "pasul SSH a esuat ($($_.Exception.Message)); continui fara uneltele MCP." warn "modelele de pe PC2 vor merge oricum." } } # 5. plugin-ul cu /think si /copy $plugDir = Join-Path $cfgDir 'plugin' New-Item -ItemType Directory -Force -Path $plugDir | Out-Null try { Invoke-WebRequest -Uri "$setupSrv/extras.js" -OutFile (Join-Path $plugDir 'extras.js') -TimeoutSec 30 say "plugin: /think si /copy instalate" } catch { warn "nu am putut lua plugin-ul; /think si /copy nu vor exista." } # 6. configuratia -- se pastreaza ce exista deja, se adauga doar ce e al nostru if (Test-Path $cfg) { $bak = "$cfg.bak-" + (Get-Date -Format 'yyyyMMdd-HHmmss') Copy-Item $cfg $bak say "copie de rezerva: $bak" } # PowerShell 5.1 nu are ConvertFrom-Json -AsHashtable, iar pe PSCustomObject # nu poti adauga chei simplu. Convertim recursiv la hashtable ordonat. function ToHash($o) { if ($o -is [System.Management.Automation.PSCustomObject]) { $h = [ordered]@{} foreach ($p in $o.PSObject.Properties) { $h[$p.Name] = ToHash $p.Value } return $h } # Virgula din fata e obligatorie: fara ea, un vector cu un singur element se # despacheteaza la iesirea din functie si ["AGENTS.md"] ajunge "AGENTS.md" in fisier. if ($o -is [System.Collections.IList]) { return ,@($o | ForEach-Object { ToHash $_ }) } return $o } $c = [ordered]@{} if (Test-Path $cfg) { try { $c = ToHash (Get-Content $cfg -Raw | ConvertFrom-Json) } catch { $c = [ordered]@{} } } $MODELE = [ordered]@{ 'qwen3.6-q8' = 'Qwen3.6-35B Q8 uncensored + MTP, 256k (37.5 tok/s)' 'qwen3.6-q4' = 'Qwen3.6-35B Q4 + MTP, 256k (58.8 tok/s)' 'coder-next' = 'Qwen3-Coder-Next 80B Q4, 256k (24.3 tok/s)' } if (-not $c['$schema']) { $c['$schema'] = 'https://opencode.ai/config.json' } if (-not $c['provider']) { $c['provider'] = [ordered]@{} } $mm = [ordered]@{} foreach ($k in $MODELE.Keys) { $mm[$k] = [ordered]@{ name = $MODELE[$k] # fara tool_call: true uneltele NU ajung deloc la model tool_call = $true limit = [ordered]@{ context = 262144; output = 32768 } } } $c['provider'][$prov] = [ordered]@{ npm = '@ai-sdk/openai-compatible' name = 'PC2 prin llama-swap (comutare la cerere)' options = [ordered]@{ baseURL = "$srv/v1" } models = $mm } # Cheia lipsa inseamna "toti furnizorii activi". Daca o adaugam cu un singur # nume, dezactivam tacut ce avea utilizatorul deja. Deci o atingem doar daca exista. if ($c.Contains('enabled_providers')) { $c['enabled_providers'] = @(@($c['enabled_providers']) + $prov | Sort-Object -Unique) } $c['model'] = "$prov/$model" if (-not $c['tools']) { $c['tools'] = [ordered]@{} } foreach ($t in 'bash','read','write','edit','grep','glob','list','webfetch','websearch') { $c['tools'][$t] = $true } if ($prefix) { $acasa = "/home/$hlUser" if (-not $c['mcp']) { $c['mcp'] = [ordered]@{} } $c['mcp']['homelab'] = [ordered]@{ type='local'; enabled=$true; timeout=300000 command = @($prefix + @('/usr/bin/python3', "$acasa/bin/homelab_mcp.py")) } $c['mcp']['toolsmith'] = [ordered]@{ type='local'; enabled=$true; timeout=300000 command = @($prefix + @('/usr/bin/python3', "$acasa/bin/toolsmith_mcp.py")) } $c['mcp']['ssh'] = [ordered]@{ type='local'; enabled=$true; timeout=120000 command = @($prefix + @("$acasa/.local/bin/uvx", 'mcp-ssh')) } $c['mcp']['duckduckgo'] = [ordered]@{ type='local'; enabled=$true; timeout=60000 command = @($prefix + @("$acasa/.local/bin/uvx", 'duckduckgo-mcp-server', '--transport', 'stdio')) } } $c['command'] = [ordered]@{ think = [ordered]@{ description='Porneste/opreste gandirea modelului: /think, /think on, /think off' template='(comutare gandire)' } copy = [ordered]@{ description='Copiaza ultimul raspuns in clipboard' template='(copiere in clipboard)' } } if (-not $c['permission']) { $c['permission'] = [ordered]@{ bash='allow'; edit='allow'; read='allow'; webfetch='allow' external_directory=[ordered]@{ '*'='allow' } } } # -Depth mare: implicit e 2, iar configuratia are 5 niveluri -- restul ar deveni # sirul "System.Collections.Specialized.OrderedDictionary" in fisier. # Nu Set-Content -Encoding UTF8: in Windows PowerShell 5.1 acela scrie un BOM, # iar un BOM la inceputul lui opencode.json il face nevalid pentru parsere stricte. # WriteAllText cu UTF8Encoding($false) da acelasi fisier pe 5.1 si pe 7. [System.IO.File]::WriteAllText($cfg, ($c | ConvertTo-Json -Depth 32), (New-Object System.Text.UTF8Encoding($false))) Write-Host "" say "configuratie scrisa : $cfg" say "modele disponibile : $($MODELE.Keys -join ', ')" say "implicit : $($c['model'])" say "unelte MCP : $(if ($c['mcp']) { ($c['mcp'].Keys | Sort-Object) -join ', ' } else { 'niciuna' })" say "comenzi : /think, /copy" # 7. verificare reala, nu doar 'am scris fisierul' if ($prefix) { Write-Host "" say "verific uneltele MCP ..." & $oc mcp list 2>&1 | Select-Object -Last 20 } Write-Host "" Write-Host "Gata. Porneste cu: opencode" -ForegroundColor Green Write-Host "Schimbi modelul cu /models, pornesti gandirea cu /think, copiezi cu /copy." Write-Host "Daca 'opencode' nu e gasit, deschide un terminal nou (PATH-ul s-a schimbat)." } Install-OpenCodePC2