fix: bridge docker k3s adapter to WSL API

This commit is contained in:
Codex
2026-05-16 14:15:14 +00:00
parent fa5f0fa4d0
commit 5c5564aba5
6 changed files with 107 additions and 6 deletions
+1 -1
View File
@@ -62,7 +62,7 @@
- k3s Control Bridge Boundary
- `k3sctl-adapter` is part of the UniDesk control plane, not a workload controlled by k3s. It must remain `deployment.mode=unidesk-direct` or an equivalent UniDesk-managed host service, and must not be converted to `k3sctl-managed`.
- The adapter exists so UniDesk can inspect, deploy, and repair k3s-managed user services. Putting that bridge inside the k3s cluster would invert the dependency order: repairing or diagnosing k3s would first require the in-cluster adapter and service network to be healthy.
- On native k3s nodes, the adapter must read the host kubeconfig and connect to the host-local Kubernetes API endpoint, normally `/etc/rancher/k3s/k3s.yaml` and `https://127.0.0.1:6443`. If it is packaged as Docker, it must use a host-network or equivalent host-local topology; a future systemd service is also valid.
- On native k3s nodes, the adapter must read the host kubeconfig and connect to the host-local Kubernetes API endpoint, normally `/etc/rancher/k3s/k3s.yaml` and `https://127.0.0.1:6443`. If it is packaged as Docker on a Docker Desktop/WSL node, it must create an explicit host-local bridge such as an SSH local tunnel from the adapter container to the WSL host k3s API; a future systemd service is also valid.
- k3s-managed business services such as Code Queue and MDTODO still enter through the adapter's Kubernetes API service proxy. The adapter itself is deliberately outside that managed workload graph so CNI, Service, EndpointSlice, or kube-proxy failures do not remove UniDesk's control path.
- Connection Management
- When registering, a node carries an authentication token to verify its identity and declares resources such as GPU/CPU
+1 -1
View File
@@ -68,7 +68,7 @@ The reconciler selects the executor from `config.json`:
- `deployment.mode=unidesk-direct` on `main-server`: build the image on the main server, then use the fixed UniDesk Compose project and `up -d --no-build --no-deps --force-recreate <service>`.
- `deployment.mode=unidesk-direct` on a provider: dispatch `host.ssh` to that provider, build on the provider, then use the service's provider-local compose file and project. The executor resolves the actual Compose project, image name, build context, Dockerfile and target from the running container labels and `docker compose config`; it must not guess an image tag that the service will not actually run.
- Control bridges that UniDesk needs in order to inspect or repair an orchestrator must stay in this direct class. In particular, `k3sctl-adapter` is a UniDesk-managed bridge to native k3s and must remain outside k3s; Docker packaging must use host-network or an equivalent host-local topology to reach `/etc/rancher/k3s/k3s.yaml` and `127.0.0.1:6443`.
- Control bridges that UniDesk needs in order to inspect or repair an orchestrator must stay in this direct class. In particular, `k3sctl-adapter` is a UniDesk-managed bridge to native k3s and must remain outside k3s; Docker packaging on Docker Desktop/WSL must create an explicit host-local bridge, currently an adapter-container SSH local tunnel, to reach `/etc/rancher/k3s/k3s.yaml` and WSL `127.0.0.1:6443`.
- `deployment.mode=k3sctl-managed`: dispatch to the active control target, build on that target, verify or install native k3s on the host OS/WSL distro, import the image into native k3s/containerd, apply the existing Kubernetes manifest, stamp the Deployment and wait for rollout. The executor must use the native kubeconfig and containerd socket, for example `/etc/rancher/k3s/k3s.yaml` and `/run/k3s/containerd/containerd.sock`; running k3s itself in Docker is forbidden for both control-plane and worker nodes. A `rancher/k3s` image or legacy container may only be used as a temporary artifact source during migration, and any active containerized k3s control plane must be stopped before verification succeeds.
Existing service-specific commands such as Code Queue deploy should converge onto this reconciler path instead of keeping a parallel implementation.
+3 -3
View File
@@ -129,9 +129,9 @@ Baidu Netdisk 在 UniDesk 语境中按纯后端服务管理:不得暴露百度
- Provider`D601`,由 D601 provider-gateway 仅维护和访问 `k3sctl-adapter` 的本机私有端口 `127.0.0.1:4266`provider-gateway 不再作为 `code-queue` 业务请求的直接代理。
- 代码引用:`https://github.com/pikasTech/unidesk` 与配置中的 `repository.commitId`;服务源码位于 `src/components/microservices/k3sctl-adapter`,属于 UniDesk 自有控制面组件。
- 部署引用:UniDesk 仓库中的 `src/components/microservices/k3sctl-adapter/docker-compose.d601.yml`Dockerfile 为 `src/components/microservices/k3sctl-adapter/Dockerfile`,容器名为 `k3sctl-adapter`;当前 Docker 包装必须使用 host network,让 adapter 能用宿主 kubeconfig 访问原生 k3s API
- 部署引用:UniDesk 仓库中的 `src/components/microservices/k3sctl-adapter/docker-compose.d601.yml`Dockerfile 为 `src/components/microservices/k3sctl-adapter/Dockerfile`,容器名为 `k3sctl-adapter`;当前 D601 Docker Desktop 包装必须挂载宿主 kubeconfig 与 WSL 维护 SSH key,并由容器入口脚本建立到 WSL host 原生 k3s API 的 SSH local tunnel
- 控制桥边界:`k3sctl-adapter` 是 UniDesk 到 k3s 的控制桥,不是被 k3s 管理的业务 workload;它必须保持 `deployment.mode=unidesk-direct`,或迁移为等价的 UniDesk/systemd 直管宿主服务,不得改成 `k3sctl-managed` Deployment。原因是 UniDesk 依赖 adapter 做 k3s 服务代理、部署验证和故障诊断;如果 adapter 自身依赖 k3s Deployment、Service、CNI 或 kube-proxy 才能存活,k3s 网络故障时会失去修复 k3s 的入口,形成依赖顺序倒置。
- 原生 API 连接:D601 原生 k3s 的 kubeconfig 固定来自宿主 `/etc/rancher/k3s/k3s.yaml`adapter 内部挂载为 `/var/lib/unidesk/k3s/kubeconfig`;当 kubeconfig server 是 `127.0.0.1:6443` 时,adapter 必须在宿主网络或等价拓扑下连接 `127.0.0.1:6443`,不得依赖 Docker bridge 的 `host.docker.internal:6443`、旧 `rancher/k3s` 容器 IP、NodePort 或手工 service endpoint。
- 原生 API 连接:D601 原生 k3s 的 kubeconfig 固定来自宿主 `/etc/rancher/k3s/k3s.yaml`adapter 内部挂载为 `/var/lib/unidesk/k3s/kubeconfig`;当 kubeconfig server 是 `127.0.0.1:6443` 时,adapter 容器必须通过受控 SSH local tunnel 把容器内 `127.0.0.1:6443` 转发到 WSL host `127.0.0.1:6443`,并设置 `K3SCTL_KUBE_API_CONNECT_HOST=127.0.0.1`。不得依赖 Docker Desktop 的 `network_mode: host`,因为它进入的是 Docker Desktop VM 网络而不是 D601 WSL Ubuntu 网络;也不得依赖 `host.docker.internal:6443`、旧 `rancher/k3s` 容器 IP、NodePort 或手工 service endpoint。
- k3s 实现:D601 控制面、D518 或其他计算资源节点上的 k3s agent/worker 都必须原生安装在节点 host OS 或 WSL 发行版内,以 `/usr/local/bin/k3s` 和 systemd `k3s.service`/`k3s-agent.service` 运行;不得用 Docker、Compose、`rancher/k3s` 长驻容器、kind/k3d 或其他容器化方式承载 k3s 控制面或 kubelet。Docker 只允许用于 provider-gateway、业务容器镜像构建、运行用户 workload 或临时提取 k3s 二进制/镜像 artifact,不能成为 k3s runtime 边界。验收时必须证明 `systemctl is-active k3s` 或 agent 服务正常、`kubectl get nodes -o wide` 看到真实节点 OS/内核、k3s containerd socket 位于 `/run/k3s/containerd/containerd.sock`,且不存在 active `rancher/k3s` 控制面容器。
- k3s 路由对象:`k3sctl-managed` 可以落到 k3s、k8s 或等价标准 Kubernetes 控制面,但必须使用 Kubernetes 原生命名空间、Deployment、Service、readiness/liveness probe、Kubernetes API service proxy 等规范对象;不得把裸容器端口、NodePort、SSH curl、provider-gateway `microservice.http` 或 host 直连地址伪装成 k3s 服务路由。WSL 节点的 hostPath 和 local-path 语义必须解析到 WSL host 文件系统;例如 D601 Code Queue Pod 的 `/workspace` 必须映射 WSL `/home/ubuntu`,不能映射到容器化 k3s 内部的 `/home/ubuntu`
- k3s 系统组件:D601 原生 k3s server 必须禁用非必要的 `traefik``servicelb``metrics-server`,只保留业务必需的 API server、CoreDNS 与 local-path provisionerCoreDNS 和 local-path provisioner 固定运行在 D601 控制面节点,避免 D518 维护隧道限制导致系统 DNS/readiness 抖动。
@@ -325,7 +325,7 @@ ClaudeQQ 在 UniDesk 语境中按消息网关后端服务管理:不得直接
- 运行 `bun scripts/cli.ts microservice health met-nonlinear``bun scripts/cli.ts microservice proxy met-nonlinear /api/queue``bun scripts/cli.ts microservice proxy met-nonlinear '/api/projects?root=projects&limit=20'``bun scripts/cli.ts microservice proxy met-nonlinear /api/images`,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 MET Nonlinear TS 后端。
- 运行 `bun scripts/cli.ts microservice health claudeqq``bun scripts/cli.ts microservice proxy claudeqq /api/napcat/login``bun scripts/cli.ts microservice proxy claudeqq /api/events/recent``bun scripts/cli.ts microservice proxy claudeqq /api/events/subscriptions`,确认真实链路经过 backend-core、WebSocket、D601 provider-gateway 和 D601 本机 ClaudeQQ 后端;在 D601 上 `curl http://127.0.0.1:3290/health` 应显示 `service=claudeqq``pureBackend=true``napcat.containerized=true`、NapCat HTTP/WS 状态、二维码状态和订阅计数。
- 运行 `bun scripts/cli.ts microservice health todo-note``bun scripts/cli.ts microservice proxy todo-note /api/instances`,确认真实链路经过 backend-core、WebSocket、main-server provider-gateway 和主 server `todo-note-backend` 后端;输出中必须包含五个迁移清单和 PostgreSQL 存储健康状态。
- 运行 `bun scripts/cli.ts microservice health k3sctl-adapter``bun scripts/cli.ts microservice proxy k3sctl-adapter /api/control-plane --raw``bun scripts/cli.ts microservice health code-queue``bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview`,确认真实链路经过 backend-core -> k3sctl-adapter -> k3s active serviceadapter 验收还必须证明其作为 UniDesk 直管服务运行在 k3s 外部,Docker 形态下 `NetworkMode=host`kubeconfig 来自宿主 `/etc/rancher/k3s/k3s.yaml`,且没有 active `rancher/k3s` 控制面容器。Code Queue `/health` 必须仍返回业务后端自己的 `queue.storage.primary=postgres``queue.storage.postgresReady=true``queue.notifications.claudeqq.outbox.storage=postgres``egressProxy.connected=true`,不得被 adapter 聚合健康 JSON 替代。还必须在 active Code Queue Pod 内验证主 PostgreSQL 端口映射、主 OA Event Flow 端口映射、本机 ClaudeQQ `http://host.docker.internal:3290``d601-provider-egress-proxy` 均可访问,并在 adapter 控制页确认 D601 active serving healthy、D518 standby pod ready、`missingNodeIds=[]` 且整体不退化为 hidden fallback。再通过公网 frontend 提交一个 `gpt-5.5` 小任务,确认队列串行推进、输出实时更新、结束后有 judge 判定,且运行中可追加 prompt 或打断。Code Queue 的重启恢复必须作为验收项:运行中任务存在时重启或重建 active 实例后,任务必须从 PostgreSQL 恢复到可继续执行状态,不能丢失 active task、`promptHistory`、后续 queued 任务、readAt/未读状态或已入 outbox 的 ClaudeQQ 通知。Code Queue 服务名、表名前缀或持久化目录发生迁移后,还必须运行 `bun scripts/cli.ts e2e run --only microservice:catalog-code-queue,microservice:code-queue-status,microservice:code-queue-health,microservice:code-queue-tasks`,证明 backend-core catalog、k3s adapter 私有代理、PostgreSQL 队列和任务列表都指向 `code-queue`。批量验收必须通过公网 frontend 设置 `入队份数=5` 或使用多段 prompt 分隔,一次性入队 5 条任务,并确认 5 条任务按顺序进入 running/judging/succeeded,而不是只运行第一条。
- 运行 `bun scripts/cli.ts microservice health k3sctl-adapter``bun scripts/cli.ts microservice proxy k3sctl-adapter /api/control-plane --raw``bun scripts/cli.ts microservice health code-queue``bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview`,确认真实链路经过 backend-core -> k3sctl-adapter -> k3s active serviceadapter 验收还必须证明其作为 UniDesk 直管服务运行在 k3s 外部,Docker 形态下挂载宿主 `/etc/rancher/k3s/k3s.yaml``/run/host-ssh/id_ed25519`,通过容器内 SSH local tunnel 连接 WSL 原生 k3s API,且没有 active `rancher/k3s` 控制面容器。Code Queue `/health` 必须仍返回业务后端自己的 `queue.storage.primary=postgres``queue.storage.postgresReady=true``queue.notifications.claudeqq.outbox.storage=postgres``egressProxy.connected=true`,不得被 adapter 聚合健康 JSON 替代。还必须在 active Code Queue Pod 内验证主 PostgreSQL 端口映射、主 OA Event Flow 端口映射、本机 ClaudeQQ `http://host.docker.internal:3290``d601-provider-egress-proxy` 均可访问,并在 adapter 控制页确认 D601 active serving healthy、D518 standby pod ready、`missingNodeIds=[]` 且整体不退化为 hidden fallback。再通过公网 frontend 提交一个 `gpt-5.5` 小任务,确认队列串行推进、输出实时更新、结束后有 judge 判定,且运行中可追加 prompt 或打断。Code Queue 的重启恢复必须作为验收项:运行中任务存在时重启或重建 active 实例后,任务必须从 PostgreSQL 恢复到可继续执行状态,不能丢失 active task、`promptHistory`、后续 queued 任务、readAt/未读状态或已入 outbox 的 ClaudeQQ 通知。Code Queue 服务名、表名前缀或持久化目录发生迁移后,还必须运行 `bun scripts/cli.ts e2e run --only microservice:catalog-code-queue,microservice:code-queue-status,microservice:code-queue-health,microservice:code-queue-tasks`,证明 backend-core catalog、k3s adapter 私有代理、PostgreSQL 队列和任务列表都指向 `code-queue`。批量验收必须通过公网 frontend 设置 `入队份数=5` 或使用多段 prompt 分隔,一次性入队 5 条任务,并确认 5 条任务按顺序进入 running/judging/succeeded,而不是只运行第一条。
- Code Queue 内存防回归验收:凡是改动 Code Queue 的持久化、scheduler、输出/Trace、health、列表/详情查询、日志导出或容器运行参数,交付前必须在 D601 用 `kubectl -n unidesk get deploy,pod,svc,endpoints -o wide``kubectl -n unidesk describe deploy/code-queue` 或等价 Docker inspect 确认 memory/swap 硬上限符合预算,运行 `kubectl -n unidesk top pod` 或 Docker stats 确认常驻内存、`OOMKilled=false``RestartCount` 未异常增长,再运行 `bun scripts/cli.ts microservice health code-queue` 确认 `/health` 是轻量 readiness 且暴露 PostgreSQL/notification/outbox 状态。验收还必须覆盖有历史任务存在时的 `/api/tasks/overview`、单任务详情和 output/transcript 查询,证明热状态裁剪不会丢历史输出、也不会重新把全部历史 `task_json` 缓存在进程内;涉及 TypeScript/frontend 验证的任务应能在 D601 Code Queue memory/swap 预算中完成 `bun run --cwd src/components/frontend check` 这类短时高内存命令,而不是被 memory watchdog 反复 SIGTERM。
- Code Queue 延迟防回归验收:凡是改动 Code Queue 列表、overview、readAt、Trace/summary 懒加载、实时 output/SSE 事件发布、frontend 请求策略、backend-core 用户服务代理或 frontend Code Queue 请求路径,交付前必须在有历史任务数据且有 active output 流动的 live 环境验证 `GET /api/tasks/overview``POST /api/tasks/<id>/read`、选定 task 的 `trace-step` 和前端 `/app/code-queue/` 首屏均低于 1s 目标;可运行 `bun scripts/src/code-queue-perf.ts --json --target-ms 1000` 采集公网 frontend 下的首屏耗时、最慢 API 和 DOM 完成指标,并用 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview --raw`、D601 Pod `/health``/api/tasks/overview` curl、性能面板 `/api/performance``/api/frontend-performance` 失败/慢操作记录、`kubectl -n unidesk top pod` 或 Docker stats 补充后端耗时、代理 502 和内存/CPU 证据。验收结论必须同时说明是否使用了短 TTL cache、cache 如何被 mutation 或 archive append 失效、数据库索引/聚合是否命中、输出热路径是否只读增量指标,以及分页加载是否跳过 selected/active/stats;不能只展示 cache 命中后的单次快照。
- 运行 `bun scripts/cli.ts microservice health filebrowser``bun scripts/cli.ts microservice health filebrowser-d601``bun scripts/cli.ts microservice proxy filebrowser / --max-body-bytes 2000`,确认 File Browser health 返回 `status=OK`WebUI HTML 包含 `File Browser`D518/D601 通过 provider-gateway 访问节点本机 `4251`;随后在公网 frontend 的 `用户服务 / File Browser` 中确认 D518 为默认目标、可导出截图、iframe 紧凑布局不再有巨大 `folder` 标记遮挡文件名,并可浏览 `/mnt/c`
@@ -16,8 +16,11 @@ WORKDIR /app/src/components/microservices/k3sctl-adapter
COPY src/components/shared /app/src/components/shared
COPY src/components/microservices/k3sctl-adapter/package.json ./package.json
COPY src/components/microservices/k3sctl-adapter/tsconfig.json ./tsconfig.json
COPY src/components/microservices/k3sctl-adapter/entrypoint.sh ./entrypoint.sh
COPY src/components/microservices/k3sctl-adapter/src ./src
COPY src/components/microservices/k3sctl-adapter/k3s ./k3s
RUN chmod 755 ./entrypoint.sh
EXPOSE 4266
ENTRYPOINT ["./entrypoint.sh"]
CMD ["bun", "--smol", "run", "src/index.ts"]
@@ -8,10 +8,11 @@ services:
K3SCTL_ADAPTER_BASE_IMAGE: ${K3SCTL_ADAPTER_BASE_IMAGE:-oven/bun:1-debian}
container_name: k3sctl-adapter
restart: unless-stopped
network_mode: host
env_file:
- path: ${K3SCTL_ADAPTER_ENV_FILE:-../../../../.state/k3sctl-adapter-d601.env}
required: false
ports:
- "127.0.0.1:${K3SCTL_ADAPTER_HOST_PORT:-4266}:4266"
environment:
HOST: "0.0.0.0"
PORT: "4266"
@@ -22,14 +23,33 @@ services:
K3SCTL_KUBE_API_PROXY_ENABLED: "${K3SCTL_KUBE_API_PROXY_ENABLED:-true}"
K3SCTL_KUBECONFIG_PATH: "/var/lib/unidesk/k3s/kubeconfig"
K3SCTL_KUBE_API_CONNECT_HOST: "${K3SCTL_KUBE_API_CONNECT_HOST:-127.0.0.1}"
K3SCTL_KUBE_API_SSH_TUNNEL_ENABLED: "${K3SCTL_KUBE_API_SSH_TUNNEL_ENABLED:-true}"
K3SCTL_KUBE_API_SSH_HOST: "${K3SCTL_KUBE_API_SSH_HOST:-host.docker.internal}"
K3SCTL_KUBE_API_SSH_USER: "${K3SCTL_KUBE_API_SSH_USER:-ubuntu}"
K3SCTL_KUBE_API_SSH_KEY: "${K3SCTL_KUBE_API_SSH_KEY:-/run/host-ssh/id_ed25519}"
K3SCTL_KUBE_API_LOCAL_HOST: "${K3SCTL_KUBE_API_LOCAL_HOST:-127.0.0.1}"
K3SCTL_KUBE_API_LOCAL_PORT: "${K3SCTL_KUBE_API_LOCAL_PORT:-6443}"
K3SCTL_KUBE_API_REMOTE_HOST: "${K3SCTL_KUBE_API_REMOTE_HOST:-127.0.0.1}"
K3SCTL_KUBE_API_REMOTE_PORT: "${K3SCTL_KUBE_API_REMOTE_PORT:-6443}"
K3SCTL_MANIFEST_PATHS: "${K3SCTL_MANIFEST_PATHS:-k3s/code-queue.k3s.json,k3s/mdtodo.k3s.json}"
K3SCTL_SERVICES_JSON: "${K3SCTL_SERVICES_JSON:-[]}"
UNIDESK_LOG_RETENTION_BYTES: "${UNIDESK_LOG_RETENTION_BYTES:-512MiB}"
volumes:
- ${K3SCTL_ADAPTER_LOG_DIR:-../../../../.state/k3sctl-adapter/logs}:/var/log/unidesk
- ${K3SCTL_KUBECONFIG_HOST_PATH:-/etc/rancher/k3s/k3s.yaml}:/var/lib/unidesk/k3s/kubeconfig:ro
- ${K3SCTL_HOST_SSH_KEY_DIR:-/home/ubuntu/.web-terminal/wsl-ssh}:/run/host-ssh:ro
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- default
- provider-gateway
healthcheck:
test: ["CMD-SHELL", "curl -fsS --max-time 2 http://127.0.0.1:4266/health >/dev/null"]
interval: 5s
timeout: 3s
retries: 20
networks:
provider-gateway:
external: true
name: ${K3SCTL_PROVIDER_GATEWAY_NETWORK:-unidesk-provider-d601_default}
+78
View File
@@ -0,0 +1,78 @@
#!/bin/sh
set -eu
log() {
printf '%s\n' "$*" >&2
}
is_true() {
case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
1|true|yes|on) return 0 ;;
*) return 1 ;;
esac
}
wait_for_kube_api_tunnel() {
local_host="${K3SCTL_KUBE_API_LOCAL_HOST:-127.0.0.1}"
local_port="${K3SCTL_KUBE_API_LOCAL_PORT:-6443}"
attempts="${K3SCTL_KUBE_API_SSH_TUNNEL_WAIT_ATTEMPTS:-30}"
index=1
while [ "$index" -le "$attempts" ]; do
status="$(curl -ksS --connect-timeout 2 --max-time 4 -o /dev/null -w '%{http_code}' "https://${local_host}:${local_port}/version" 2>/dev/null || true)"
case "$status" in
200|401|403)
log "k3sctl-adapter: kube api tunnel reachable status=${status}"
return 0
;;
esac
sleep 1
index=$((index + 1))
done
log "k3sctl-adapter: kube api tunnel did not become reachable at ${local_host}:${local_port}"
return 1
}
start_kube_api_ssh_tunnel() {
if ! is_true "${K3SCTL_KUBE_API_SSH_TUNNEL_ENABLED:-false}"; then
return 0
fi
ssh_host="${K3SCTL_KUBE_API_SSH_HOST:-host.docker.internal}"
ssh_user="${K3SCTL_KUBE_API_SSH_USER:-ubuntu}"
ssh_key="${K3SCTL_KUBE_API_SSH_KEY:-/run/host-ssh/id_ed25519}"
local_host="${K3SCTL_KUBE_API_LOCAL_HOST:-127.0.0.1}"
local_port="${K3SCTL_KUBE_API_LOCAL_PORT:-6443}"
remote_host="${K3SCTL_KUBE_API_REMOTE_HOST:-127.0.0.1}"
remote_port="${K3SCTL_KUBE_API_REMOTE_PORT:-6443}"
restart_delay="${K3SCTL_KUBE_API_SSH_RESTART_DELAY_SECONDS:-2}"
if [ ! -r "$ssh_key" ]; then
log "k3sctl-adapter: SSH tunnel key is not readable: ${ssh_key}"
exit 1
fi
mkdir -p /tmp/k3sctl-ssh
(
while :; do
log "k3sctl-adapter: starting kube api SSH tunnel ${local_host}:${local_port}->${remote_host}:${remote_port} via ${ssh_user}@${ssh_host}"
ssh \
-i "$ssh_key" \
-o BatchMode=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/tmp/k3sctl-ssh/known_hosts \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=3 \
-N \
-L "${local_host}:${local_port}:${remote_host}:${remote_port}" \
"${ssh_user}@${ssh_host}" || true
log "k3sctl-adapter: kube api SSH tunnel exited; restarting in ${restart_delay}s"
sleep "$restart_delay"
done
) &
wait_for_kube_api_tunnel
}
start_kube_api_ssh_tunnel
exec "$@"