#!/usr/bin/env bash # Leaga OpenCode de pe orice masina la cele 3 modele de pe PC2 (comutabile cu # /models), la uneltele homelab/toolsmith/ssh/cautare web si la comenzile /think si /copy. # curl -fsSL http://10.11.90.169:8088/setup.sh | bash # # Variabile optionale: QWEN_SRV, HOMELAB=0 (sare peste tool), HOMELAB_HOST, HOMELAB_USER set -euo pipefail SRV="${QWEN_SRV:-http://10.11.90.169:9090}" PROV="pc2" MODEL="qwen3.6-q4" HL_HOST="${HOMELAB_HOST:-10.11.90.169}" HL_USER="${HOMELAB_USER:-andrei}" CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode" CFG="$CFG_DIR/opencode.json" say() { printf " %s\n" "$*"; } echo "== Legare OpenCode la Qwen3.6-35B de pe PC2 ==" # 1. serverul raspunde? say "verific serverul $SRV ..." if ! curl -fsS -m 10 "$SRV/v1/models" >/dev/null 2>&1; then echo "EROARE: $SRV nu raspunde." >&2 echo "Verifica VPN-ul, sau pe PC2: sudo systemctl status llama-swap" >&2 exit 1 fi say "serverul raspunde." # 2. OpenCode instalat? if command -v opencode >/dev/null 2>&1; then say "OpenCode: deja instalat ($(command -v opencode))" else say "OpenCode lipseste, il instalez ..." curl -fsSL https://opencode.ai/install | bash export PATH="$HOME/.opencode/bin:$HOME/.local/bin:$PATH" command -v opencode >/dev/null 2>&1 || { echo "EROARE: instalarea a esuat." >&2; exit 1; } fi # 3. tool-ul homelab: serverul MCP ruleaza pe PC2, adus aici prin SSH. # NB: fiecare ssh de verificare are -n. Fara el consuma stdin, iar sub # "curl | bash" stdin e chiar scriptul: restul fisierului dispare. # Inventarul cu parole ramane astfel intr-un singur loc. MCP_CMD="" if [ "${HOMELAB:-1}" = "1" ]; then if [ -f "$HOME/bin/homelab_mcp.py" ]; then say "homelab: serverul e chiar pe masina asta, il pornesc local" MCP_CMD="[\"/usr/bin/python3\", \"$HOME/bin/homelab_mcp.py\"]" elif ssh -n -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new \ "$HL_USER@$HL_HOST" true >/dev/null 2>&1; then say "homelab: cheia SSH catre $HL_HOST merge deja" MCP_CMD="[\"ssh\", \"-o\", \"BatchMode=yes\", \"-o\", \"ConnectTimeout=10\", \"$HL_USER@$HL_HOST\", \"/usr/bin/python3\", \"/home/$HL_USER/bin/homelab_mcp.py\"]" else say "homelab: nu am acces cu cheie la $HL_HOST, il configurez acum" if [ ! -f "$HOME/.ssh/id_ed25519" ] && [ ! -f "$HOME/.ssh/id_rsa" ]; then say "generez o cheie SSH noua (fara parafraza)" ssh-keygen -t ed25519 -N "" -f "$HOME/.ssh/id_ed25519" -q fi PUB="$HOME/.ssh/id_ed25519.pub"; [ -f "$PUB" ] || PUB="$HOME/.ssh/id_rsa.pub" echo echo " >>> Urmeaza sa ti se ceara PAROLA contului $HL_USER de pe $HL_HOST." echo " >>> Se intampla o singura data; dupa aceea merge cu cheia." echo if command -v ssh-copy-id >/dev/null 2>&1; then ssh-copy-id -o StrictHostKeyChecking=accept-new "$HL_USER@$HL_HOST" > ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' \ < "$PUB" || true fi if ssh -n -o BatchMode=yes -o ConnectTimeout=8 "$HL_USER@$HL_HOST" true >/dev/null 2>&1; then say "homelab: cheia merge acum" MCP_CMD="[\"ssh\", \"-o\", \"BatchMode=yes\", \"-o\", \"ConnectTimeout=10\", \"$HL_USER@$HL_HOST\", \"/usr/bin/python3\", \"/home/$HL_USER/bin/homelab_mcp.py\"]" else echo " ATENTIE: cheia SSH tot nu merge, deci NU adaug tool-ul homelab." >&2 echo " Un tool care nu porneste ar da erori la fiecare pornire de OpenCode." >&2 echo " Repara cu: ssh-copy-id $HL_USER@$HL_HOST apoi ruleaza iar scriptul." >&2 fi fi fi # 4. plugin-ul cu /think si /copy mkdir -p "$CFG_DIR/plugin" if curl -fsS -m 20 "${SETUP_SRV:-http://10.11.90.169:8088}/extras.js" -o "$CFG_DIR/plugin/extras.js"; then say "plugin: /think si /copy instalate" else echo " ATENTIE: nu am putut lua plugin-ul; /think si /copy nu vor exista." >&2 fi # 5. configuratia -- se pastreaza ce exista deja, se adauga doar ce e al nostru mkdir -p "$CFG_DIR" [ -f "$CFG" ] && cp "$CFG" "$CFG.bak-$(date +%Y%m%d-%H%M%S)" && say "copie de rezerva a configuratiei vechi" PY=$(command -v python3 || command -v python) || { echo "EROARE: lipseste python3." >&2; exit 1; } SRV="$SRV" PROV="$PROV" MODEL="$MODEL" CFG="$CFG" MCP_CMD="$MCP_CMD" \ HL_USER="$HL_USER" HL_HOST="$HL_HOST" "$PY" <<"PYEOF" import json, os, pathlib cfg = pathlib.Path(os.environ["CFG"]) prov, model, srv = os.environ["PROV"], os.environ["MODEL"], os.environ["SRV"] user, gazda = os.environ["HL_USER"], os.environ["HL_HOST"] c = {} if cfg.exists(): try: c = json.loads(cfg.read_text()) except Exception: c = {} MODELE = { "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)", } c.setdefault("$schema", "https://opencode.ai/config.json") c.setdefault("provider", {})[prov] = { "npm": "@ai-sdk/openai-compatible", "name": "PC2 prin llama-swap (comutare la cerere)", "options": {"baseURL": srv + "/v1"}, # fara tool_call: true uneltele NU ajung deloc la model "models": {k: {"name": v, "tool_call": True, "limit": {"context": 262144, "output": 32768}} for k, v in MODELE.items()}, } # 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 "enabled_providers" in c: c["enabled_providers"] = sorted(set(c["enabled_providers"]) | {prov}) c["model"] = f"{prov}/{model}" t = c.setdefault("tools", {}) for x in ("bash", "read", "write", "edit", "grep", "glob", "list", "webfetch", "websearch"): t[x] = True mcp_cmd = json.loads(os.environ["MCP_CMD"]) if os.environ.get("MCP_CMD") else None if mcp_cmd is not None: prefix = mcp_cmd[:-2] # "ssh ... user@gazda", sau [] daca suntem chiar pe PC2 def cmd(*a): return prefix + list(a) m = c.setdefault("mcp", {}) m["homelab"] = {"type": "local", "enabled": True, "timeout": 300000, "command": cmd("/usr/bin/python3", f"/home/{user}/bin/homelab_mcp.py")} m["toolsmith"] = {"type": "local", "enabled": True, "timeout": 300000, "command": cmd("/usr/bin/python3", f"/home/{user}/bin/toolsmith_mcp.py")} m["ssh"] = {"type": "local", "enabled": True, "timeout": 120000, "command": cmd(f"/home/{user}/.local/bin/uvx", "mcp-ssh")} m["duckduckgo"] = {"type": "local", "enabled": True, "timeout": 60000, "command": cmd(f"/home/{user}/.local/bin/uvx", "duckduckgo-mcp-server", "--transport", "stdio")} c["command"] = { "think": {"description": "Porneste/opreste gandirea modelului: /think, /think on, /think off", "template": "(comutare gandire)"}, "copy": {"description": "Copiaza ultimul raspuns in clipboard", "template": "(copiere in clipboard)"}, } c.setdefault("permission", {"bash": "allow", "edit": "allow", "read": "allow", "webfetch": "allow", "external_directory": {"*": "allow"}}) cfg.write_text(json.dumps(c, indent=2) + "\n") print(" configuratie scrisa:", cfg) print(" modele disponibile :", ", ".join(sorted(MODELE))) print(" implicit :", c["model"]) print(" unelte MCP :", ", ".join(sorted(c.get("mcp", {}))) or "niciuna") print(" comenzi : /think, /copy") PYEOF echo echo "Gata. Porneste cu: opencode" echo "Schimbi modelul cu /models, pornesti gandirea cu /think, copiezi cu /copy." echo "Daca nu e gasit, adauga in PATH: export PATH=\"\$HOME/.opencode/bin:\$PATH\""