feat: one-line install for agents and probes from one shared script (curl | bash, zsh, PowerShell) [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

This commit is contained in:
justme committed 2026-10-07 20:02:38 +08:00
1 parent 5528753d45
commit af2962fc58
29 files changed
+558 -124

No files matched your search

+3 -38
View File
@@ -6,7 +6,8 @@ import type { AgentDto, CreatedJoinTokenDto, JoinTokenDto, Page } from '@shared/
import { fmtAgo, fmtTime } from '@shared/format'
import { useQuery } from '@shared/hooks'
import { NodeTag, useNodeInfos } from '@shared/nodes'
import { Chip, CommandTabs, Confirm, CopyButton, Dialog, Dot, Empty, ErrorNotice, Field, guessPlatform, Segmented, Spinner, useAction } from '@shared/ui'
import { Chip, Confirm, Dialog, Dot, Empty, ErrorNotice, Field, Segmented, Spinner, useAction } from '@shared/ui'
import { JoinTokenCreated } from './JoinTokenCreated'
export const STATUS: Record<string, { label: string; tone: 'ok' | 'warn' | 'bad' }> = {
active: { label: '已启用', tone: 'ok' },
@@ -33,43 +34,7 @@ function AddAgentDialog({ onClose, onCreated }: { onClose: () => void; onCreated
const [hours, setHours] = useState(24)
const [created, setCreated] = useState<CreatedJoinTokenDto>()
if (created) {
return (
<Dialog
title="加入令牌已创建"
onClose={onClose}
actions={
<button type="button" className="btn primary" onClick={onClose}>
完成
</button>
}
>
<div className="stack">
<div className="notice warn">令牌明文只显示这一次,请现在就复制。</div>
<Field label="在要加入的机器上运行">
<CommandTabs
storageKey="moshiro.install_tab"
initial={guessPlatform()}
tabs={[
{ id: 'linux', label: 'Linux', command: created.install_commands.linux },
{ id: 'macos', label: 'macOS', command: created.install_commands.macos },
{ id: 'windows', label: 'Windows', command: created.install_commands.windows },
]}
/>
</Field>
<Field label="令牌">
<pre className="code">{created.plaintext}</pre>
<div>
<CopyButton text={created.plaintext} label="复制令牌" />
</div>
</Field>
<p className="muted">
{created.token.auto_approve ? '使用这个令牌注册的 Agent 会自动启用。' : '使用这个令牌注册的 Agent 需要你在列表里审批之后才会启用。'} 可用 {created.token.max_uses} 次,{fmtTime(created.token.expires_at)} 过期。
</p>
</div>
</Dialog>
)
}
if (created) return <JoinTokenCreated created={created} kind="Agent" onClose={onClose} />
return (
<Dialog
+50
View File
@@ -0,0 +1,50 @@
import type { CreatedJoinTokenDto } from '@shared/api/types'
import { fmtTime } from '@shared/format'
import { CommandTabs, CopyButton, Dialog, Field, guessPlatform } from '@shared/ui'
/**
* 加入令牌创建之后的对话框:Agent 和 Probe 共用同一份,一行命令装好。
* 命令里只有一次性代码,角色由代码决定;节点名字默认取主机名。
*/
export function JoinTokenCreated({ created, kind, onClose }: { created: CreatedJoinTokenDto; kind: 'Agent' | 'Probe'; onClose: () => void }) {
const commands = created.install_commands
return (
<Dialog
title="加入令牌已创建"
onClose={onClose}
actions={
<button type="button" className="btn primary" onClick={onClose}>
完成
</button>
}
>
<div className="stack">
<div className="notice warn">命令里的代码只显示这一次,请现在就复制。</div>
<Field
label={`在要加入的机器上运行(${kind})`}
hint="需要 Docker 或 Podman。命令里已经填好面板地址和一次性代码,节点名字默认取主机名;身份和配置存在命名卷里,重启、换新镜像重建容器都不用再给参数。名字想自己定,或者提示名字被占用,在命令里加上 MOSHIRO_NAME=名字(Linux 和 macOS 写在 bash 或 zsh 前面,Windows 先执行 $env:MOSHIRO_NAME='名字')。"
>
<CommandTabs
storageKey="moshiro.install_tab"
initial={guessPlatform()}
tabs={[
{ id: 'linux', label: 'Linux', command: commands.linux },
{ id: 'macos', label: 'macOS', command: commands.macos },
{ id: 'windows', label: 'Windows', command: commands.windows },
]}
/>
</Field>
<Field label="代码">
<pre className="code">{created.plaintext}</pre>
<div>
<CopyButton text={created.plaintext} label="复制代码" />
</div>
</Field>
<p className="muted">
{kind === 'Agent' ? (created.token.auto_approve ? '使用它注册的 Agent 会自动启用。' : '使用它注册的 Agent 需要你在列表里审批之后才会启用。') : ''}
可用 {created.token.max_uses} 次,{fmtTime(created.token.expires_at)} 过期。
</p>
</div>
</Dialog>
)
}
+3 -36
View File
@@ -7,7 +7,8 @@ import { fmtAgo, fmtTime } from '@shared/format'
import { useQuery } from '@shared/hooks'
import { NodeTag, useNodeInfos } from '@shared/nodes'
import { IPV6_STATES } from '@shared/ipVersions'
import { Chip, CommandTabs, Confirm, CopyButton, Dialog, Empty, ErrorNotice, Field, guessPlatform, Spinner, useAction } from '@shared/ui'
import { Chip, Confirm, Dialog, Empty, ErrorNotice, Field, Spinner, useAction } from '@shared/ui'
import { JoinTokenCreated } from './JoinTokenCreated'
/**
* 线路:哪个 Probe 连哪个 Agent。自动连接开着时所有 Probe 和 Agent 两两连,关着时只连手动选中的;
@@ -96,41 +97,7 @@ function AddProbeDialog({ onClose, onCreated }: { onClose: () => void; onCreated
const [hours, setHours] = useState(24)
const [created, setCreated] = useState<CreatedJoinTokenDto>()
if (created) {
return (
<Dialog
title="加入令牌已创建"
onClose={onClose}
actions={
<button type="button" className="btn primary" onClick={onClose}>
完成
</button>
}
>
<div className="stack">
<div className="notice warn">令牌明文只显示这一次,请现在就复制。</div>
<Field label="在家宽机器上运行" hint="把 <node-name> 换成这台 Probe 的名字,例如 home。Probe 会自己生成密钥并注册;Agent 列表和更新都由面板下发。">
<CommandTabs
storageKey="moshiro.install_tab"
initial={guessPlatform()}
tabs={[
{ id: 'linux', label: 'Linux', command: created.install_commands.linux },
{ id: 'macos', label: 'macOS', command: created.install_commands.macos },
{ id: 'windows', label: 'Windows', command: created.install_commands.windows },
]}
/>
</Field>
<Field label="令牌">
<pre className="code">{created.plaintext}</pre>
<div>
<CopyButton text={created.plaintext} label="复制令牌" />
</div>
</Field>
<p className="muted">一次性令牌,{fmtTime(created.token.expires_at)} 过期。</p>
</div>
</Dialog>
)
}
if (created) return <JoinTokenCreated created={created} kind="Probe" onClose={onClose} />
return (
<Dialog