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 <agent@slaid098.dev>
This commit is contained in:
parent
954b5b71af
commit
efc0827226
7 changed files with 110 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
41
.opencode/scripts/tunnel.sh
Executable file
41
.opencode/scripts/tunnel.sh
Executable file
|
|
@ -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
|
||||
19
.opencode/tools/tunnel.ts
Normal file
19
.opencode/tools/tunnel.ts
Normal file
|
|
@ -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()
|
||||
},
|
||||
})
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
15
docs/decisions/013-pr-34-tunnel-tool.md
Normal file
15
docs/decisions/013-pr-34-tunnel-tool.md
Normal file
|
|
@ -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)
|
||||
26
docs/handoff/pr-34-tunnel-tool.md
Normal file
26
docs/handoff/pr-34-tunnel-tool.md
Normal file
|
|
@ -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:<PORT>` (не `localhost:<PORT>`), иначе 502 Bad Gateway
|
||||
- Tools auto-discovered через @opencode-ai/plugin — НЕ нужно регистрировать в opencode.json
|
||||
- `cloudflared` в Dockerfile устанавливается из latest release (не pinned version)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue