From efc0827226a1121c03208cf56b25aa214a0855af Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Fri, 24 Jul 2026 02:11:29 +0300 Subject: [PATCH] feat(tunnel): add cloudflare tunnel toggle tool (#34) * feat(tunnel): add toggle tool and bash script * feat(tunnel): add cloudflared to Dockerfile * docs(tunnel): add ADR and handoff * docs(handoff): set PR number * docs: update project map + handoff + ADR --------- Co-authored-by: opencode-agent --- .env.example | 1 + .opencode/scripts/tunnel.sh | 41 +++++++++++++++++++++++++ .opencode/tools/tunnel.ts | 19 ++++++++++++ Dockerfile | 2 ++ docs/decisions/013-pr-34-tunnel-tool.md | 15 +++++++++ docs/handoff/pr-34-tunnel-tool.md | 26 ++++++++++++++++ docs/project-map/README.md | 10 +++--- 7 files changed, 110 insertions(+), 4 deletions(-) create mode 100755 .opencode/scripts/tunnel.sh create mode 100644 .opencode/tools/tunnel.ts create mode 100644 docs/decisions/013-pr-34-tunnel-tool.md create mode 100644 docs/handoff/pr-34-tunnel-tool.md diff --git a/.env.example b/.env.example index d49fc9d..00b52a9 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,7 @@ REDIS_PASSWORD=your-redis-password # Cloudflare Tunnel (optional — see separate tunnel repo) CLOUDFLARE_TUNNEL_TOKEN= +TUNNEL_DOMAIN= # Antidetect Browser MCP # Local: http://localhost:8765/mcp diff --git a/.opencode/scripts/tunnel.sh b/.opencode/scripts/tunnel.sh new file mode 100755 index 0000000..76d9179 --- /dev/null +++ b/.opencode/scripts/tunnel.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +PID_FILE="/tmp/tunnel.pid" +LOG_FILE="/tmp/tunnel.log" + +if [[ -z "${CLOUDFLARE_TUNNEL_TOKEN:-}" ]]; then + echo "❌ CLOUDFLARE_TUNNEL_TOKEN is not set" + exit 1 +fi + +# Toggle: if PID file exists and process is alive → stop +if [[ -f "$PID_FILE" ]]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + kill "$PID" + rm -f "$PID_FILE" + echo "stopped" + exit 0 + else + rm -f "$PID_FILE" + fi +fi + +# Start +nohup cloudflared tunnel run --token "$CLOUDFLARE_TUNNEL_TOKEN" > "$LOG_FILE" 2>&1 & +PID=$! +echo "$PID" > "$PID_FILE" +sleep 2 + +if ! kill -0 "$PID" 2>/dev/null; then + rm -f "$PID_FILE" + echo "❌ tunnel failed to start — check $LOG_FILE" + exit 1 +fi + +if [[ -n "${TUNNEL_DOMAIN:-}" ]]; then + echo "started (PID: $PID, domain: $TUNNEL_DOMAIN)" +else + echo "started (PID: $PID)" +fi diff --git a/.opencode/tools/tunnel.ts b/.opencode/tools/tunnel.ts new file mode 100644 index 0000000..b4e88dd --- /dev/null +++ b/.opencode/tools/tunnel.ts @@ -0,0 +1,19 @@ +import { spawnSync } from "child_process" +import path from "path" +import { tool } from "@opencode-ai/plugin" + +export default tool({ + description: "Toggle cloudflare tunnel. First call starts, second call stops. No arguments needed.", + args: {}, + async execute(_args, context) { + const script = path.join(import.meta.dir, "..", "scripts", "tunnel.sh") + const r = spawnSync("bash", [script], { + encoding: "utf-8", + cwd: context.worktree, + }) + if (r.status !== 0) { + return `⚠️ tunnel failed (exit ${r.status}): ${r.stderr}` + } + return r.stdout.trim() + }, +}) diff --git a/Dockerfile b/Dockerfile index 3a65583..3ad5106 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,8 @@ RUN mkdir -p -m 0755 /etc/apt/keyrings \ && apt-get install -y gh \ && rm -rf /var/lib/apt/lists/* +RUN curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared + ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true diff --git a/docs/decisions/013-pr-34-tunnel-tool.md b/docs/decisions/013-pr-34-tunnel-tool.md new file mode 100644 index 0000000..a439b32 --- /dev/null +++ b/docs/decisions/013-pr-34-tunnel-tool.md @@ -0,0 +1,15 @@ +# ADR-013: Cloudflare tunnel toggle tool + +## Статус +Accepted + +## Контекст +Tunnel skill в старом приватном репо (slaid098/opencode, PR#52/54) содержал приватные данные — IPs, домены, UUIDs. Для публичного репо opencode-config нужен tool без хардкода. Toggle mode (без аргументов) максимально прост для агента: первый вызов = start, второй = stop. + +## Решение +TS tool `tunnel.ts` (wrapper) + bash script `tunnel.sh` (toggle logic). Named tunnel через env var `CLOUDFLARE_TUNNEL_TOKEN` (dashboard-managed, без локального config.yml). PID-файл `/tmp/tunnel.pid` отслеживает состояние. `TUNNEL_DOMAIN` — опциональный, только для display в output. + +## Альтернативы +- Quick mode (random `*.trycloudflare.com`) — отклонено (random URL меняется при рестарте, не подходит для стабильного доступа) +- localtunnel (npx) — отклонено (лишняя Node.js зависимость, less reliable чем cloudflared) +- SSH localhost.run — отклонено (ограниченный bandwidth, TCP-only) diff --git a/docs/handoff/pr-34-tunnel-tool.md b/docs/handoff/pr-34-tunnel-tool.md new file mode 100644 index 0000000..5242cc5 --- /dev/null +++ b/docs/handoff/pr-34-tunnel-tool.md @@ -0,0 +1,26 @@ +--- +pr_number: 34 +title: Tunnel tool with toggle mode +--- + +# PR: Tunnel tool with toggle mode + +## Что сделано +- `.opencode/tools/tunnel.ts` — TS tool wrapper (toggle без args, вызывает `bash .opencode/scripts/tunnel.sh`) +- `.opencode/scripts/tunnel.sh` — bash toggle: PID файл → stop, нет → start named tunnel (`cloudflared tunnel run --token $CLOUDFLARE_TUNNEL_TOKEN`) +- `Dockerfile` — cloudflared install (`curl -L ... -o /usr/local/bin/cloudflared`) +- `.env.example` — добавлен `TUNNEL_DOMAIN=` (опциональный, для display) +- ADR-013 + этот handoff + +## Почему +Tunnel skill в старом приватном репо содержал приватные данные (IPs, домены, UUID). Для публичного репо нужен tool без хардкода. Toggle mode (без аргументов) максимально прост для агента — первый вызов = start, второй = stop. + +## Pending +- Нет + +## Watch out +- `CLOUDFLARE_TUNNEL_TOKEN` должен быть в `.env` (compose env_file) — без него скрипт падает exit 1 +- PID-файл `/tmp/tunnel.pid` теряется при рестарте контейнера — `status` скажет not running, нужно перезапустить +- DinD: origin host в Cloudflare dashboard должен быть `docker-dind:` (не `localhost:`), иначе 502 Bad Gateway +- Tools auto-discovered через @opencode-ai/plugin — НЕ нужно регистрировать в opencode.json +- `cloudflared` в Dockerfile устанавливается из latest release (не pinned version) diff --git a/docs/project-map/README.md b/docs/project-map/README.md index 2eb5b09..50cbdb9 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -41,7 +41,8 @@ opencode-config/ │ ├── tools/ │ │ ├── merge-pr.ts # merge_pr tool wrapper (orchestrator-safe gh pr merge) — PR#30 │ │ ├── pipeline-status.ts # pipeline_status tool wrapper -│ │ └── spec-status.ts # spec_status tool wrapper +│ │ ├── spec-status.ts # spec_status tool wrapper +│ │ └── tunnel.ts # Cloudflare tunnel toggle tool (start/stop без args) — PR#34 │ ├── scripts/ │ │ ├── check-adr-refs.py # ADR cross-reference validator (adr-check.yml) │ │ ├── check-permissions.py # Permissions validator (permissions-check.yml) @@ -49,7 +50,8 @@ opencode-config/ │ │ ├── pipeline-status.py # 7-phase oracle (gh PR + CI polling) │ │ ├── scaffold-handoff.sh # Scaffold handoff + ADR stubs │ │ ├── setup-memory.sh # opencode-memory bootstrap -│ │ └── spec-status.py # 9-phase spec oracle +│ │ ├── spec-status.py # 9-phase spec oracle +│ │ └── tunnel.sh # Cloudflare tunnel toggle bash (named mode via CLOUDFLARE_TUNNEL_TOKEN) — PR#34 │ ├── opencode.json # MCP servers, providers, permissions, agents, plugins │ ├── package.json # npm deps for tools/*.ts │ └── .gitignore # Ignores node_modules, etc. @@ -85,8 +87,8 @@ opencode-config/ ├── uv.lock # Locked deps for Python project ├── .pre-commit-config.yaml # ruff + UV hooks ├── docker-compose.yml # 2 services (dind + opencode), opencode_network, 4 bind mounts — PR#24 -├── Dockerfile # node:20-slim + uv + gh + chromium + docker.io + opencode-ai + repomix — PR#24 -├── .env.example # Placeholder-only env template (user copies to .env) — PR#24 +├── Dockerfile # node:20-slim + uv + gh + chromium + docker.io + opencode-ai + repomix + cloudflared — PR#24, PR#34 +├── .env.example # Placeholder-only env template (user copies to .env) — PR#24, PR#34 (TUNNEL_DOMAIN) ├── app_data/ │ ├── workspaces/ # Agent working directory (.gitkeep) │ └── ssh/ # SSH keys, not in git (.gitkeep)