diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 02647859..102f5cba 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -30,6 +30,8 @@ sidebar: false ### 修复 +- 修复 Docker 部署模式下 Agent 无法自更新:修改 Docker 启动脚本 `agent-entrypoint.sh`,在降权前将二进制文件所在目录 `/usr/local/bin` 及二进制文件自身的属主赋予 `openflare`,解决容器内自更新写入时报 `permission denied` 的问题。 + - 修复 Agent 升级版本比对逻辑:使用统一的 `pkg/utils.CompareVersions` 对比版本,正确处理预览/预发布版本(如 `v3.0.0-beta` 升级到 `v3.0.0-beta.1`),避免升级按钮非预期禁用的问题。 - 修复 Agent 以 `openflare` 非 root 运行时 OpenResty `-t`/reload 失败:nginx `pid` 与 `client_body_temp`/`proxy_temp` 等临时目录改写入 `data_dir/var/run` 与 `data_dir/var/cache/nginx`(`__OPENFLARE_PID_PATH__` / `__OPENFLARE_NGINX_CACHE_DIR__` 占位符),不再使用 OpenResty 安装目录下不可写路径。 diff --git a/frontend/app/(main)/nodes/components/install-command.tsx b/frontend/app/(main)/nodes/components/install-command.tsx index 7588fb2d..2d27e0ec 100644 --- a/frontend/app/(main)/nodes/components/install-command.tsx +++ b/frontend/app/(main)/nodes/components/install-command.tsx @@ -1,6 +1,7 @@ 'use client'; import {useEffect, useMemo, useState} from 'react'; +import {useQuery} from '@tanstack/react-query'; import {Copy} from 'lucide-react'; import {toast} from 'sonner'; @@ -8,7 +9,7 @@ import {Button} from '@/components/ui/button'; import {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@/components/ui/card'; import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; -import type {NodeItem} from '@/lib/services/openflare'; +import {type NodeItem, StatusService} from '@/lib/services/openflare'; import { buildRelayDockerInstallCommand, @@ -56,6 +57,14 @@ export function InstallCommand({ const [serverUrl, setServerUrl] = useState(''); const meta = variantMeta[variant]; + const statusQuery = useQuery({ + queryKey: ['openflare', 'public-status'], + queryFn: () => StatusService.getPublicStatus(), + staleTime: 5 * 60 * 1000, + }); + + const serverVersion = statusQuery.data?.version; + useEffect(() => { if (typeof window !== 'undefined' && !serverUrl) { setServerUrl(window.location.origin); @@ -77,9 +86,9 @@ export function InstallCommand({ return ''; } return variant === 'relay' - ? buildRelayDockerInstallCommand(normalizedServerUrl, node.access_token) - : buildTunnelDockerInstallCommand(normalizedServerUrl, node.access_token); - }, [normalizedServerUrl, node.access_token, variant]); + ? buildRelayDockerInstallCommand(normalizedServerUrl, node.access_token, serverVersion) + : buildTunnelDockerInstallCommand(normalizedServerUrl, node.access_token, serverVersion); + }, [normalizedServerUrl, node.access_token, variant, serverVersion]); const handleCopy = async (value: string, message: string) => { try { diff --git a/frontend/app/(main)/nodes/components/node-utils.ts b/frontend/app/(main)/nodes/components/node-utils.ts index ae7ac17a..e4ecdb1d 100644 --- a/frontend/app/(main)/nodes/components/node-utils.ts +++ b/frontend/app/(main)/nodes/components/node-utils.ts @@ -107,6 +107,20 @@ const relayInstallerScriptUrl = const flaredInstallerScriptUrl = 'https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-flared.sh'; +export function getImageTag(version?: string): string { + if (!version) { + return 'latest'; + } + const v = version.toLowerCase(); + if (v === 'dev' || v.includes('alpha') || v.includes('beta') || v.includes('rc')) { + return 'beta'; + } + if (v.startsWith('v')) { + return version; + } + return 'latest'; +} + export function buildRelayInstallCommand(serverUrl: string, discoveryToken: string) { return [ `curl -fsSL ${relayInstallerScriptUrl} | bash -s -- \\`, @@ -115,8 +129,9 @@ export function buildRelayInstallCommand(serverUrl: string, discoveryToken: stri ].join('\n'); } -export function buildRelayDockerInstallCommand(serverUrl: string, discoveryToken: string) { - const image = 'ghcr.io/rain-kl/openflare-relay:latest'; +export function buildRelayDockerInstallCommand(serverUrl: string, discoveryToken: string, version?: string) { + const tag = getImageTag(version); + const image = `ghcr.io/rain-kl/openflare-relay:${tag}`; return [ `docker pull ${image}`, @@ -136,8 +151,9 @@ export function buildTunnelInstallCommand(serverUrl: string, tunnelToken: string ].join('\n'); } -export function buildTunnelDockerInstallCommand(serverUrl: string, tunnelToken: string) { - const image = 'ghcr.io/rain-kl/openflared:latest'; +export function buildTunnelDockerInstallCommand(serverUrl: string, tunnelToken: string, version?: string) { + const tag = getImageTag(version); + const image = `ghcr.io/rain-kl/openflared:${tag}`; return [ `docker pull ${image}`, @@ -149,6 +165,7 @@ export function buildTunnelDockerInstallCommand(serverUrl: string, tunnelToken: ].join('\n'); } + export function formatBytes(bytes?: number | null, decimals = 1) { if (bytes === undefined || bytes === null || !Number.isFinite(bytes)) { return '—'; diff --git a/scripts/agent-entrypoint.sh b/scripts/agent-entrypoint.sh index 233e3c0a..8eca1168 100755 --- a/scripts/agent-entrypoint.sh +++ b/scripts/agent-entrypoint.sh @@ -11,6 +11,8 @@ fix_runtime_ownership() { chmod -R u+rwX,g+rX "$target" 2>/dev/null || true fi done + # Allow the runtime user to write to /usr/local/bin to support container self-updating + chown "${RUNTIME_USER}:${RUNTIME_USER}" /usr/local/bin "${AGENT_BIN}" 2>/dev/null || true } if [ "$(id -u)" -eq 0 ]; then