feat!: genesis-pinned operator set, server identity with an encrypted control link, key-less join codes; release key continuity
ci/woodpecker/tag/gitea-release Pipeline is pending
ci/woodpecker/tag/linux-release Pipeline failed
ci/woodpecker/tag/windows-release Pipeline was canceled

Join codes need no passkey touch and carry only public values (genesis
fingerprint, owner credentials, installer hashes). Nodes pin the genesis and
owners at enrollment and only follow passkey-signed operator set versions.
The panel's server key is listed in the set; pinned nodes require its
signature and run the control link under AES-256-GCM. Password sessions carry
no capabilities. The updater adopts a next_key from a signed manifest.

Bump version to 0.2.0. [CI SKIP]
This commit is contained in:
justme committed 2026-10-08 03:21:44 +08:00
1 parent 9bb6841dce
commit a2f88a344b
89 files changed
+2145 -1195

No files matched your search

+10 -2
View File
@@ -7,6 +7,7 @@ import { fmtAgo, fmtTime } from '@shared/format'
import { useQuery } from '@shared/hooks'
import { NodeTag, useNodeInfos } from '@shared/nodes'
import { Chip, Confirm, Dialog, Dot, Empty, ErrorNotice, Field, Segmented, Spinner, useAction } from '@shared/ui'
import { OwnerPicker } from './OwnerPicker'
import { JoinTokenCreated } from './JoinTokenCreated'
export const STATUS: Record<string, { label: string; tone: 'ok' | 'warn' | 'bad' }> = {
@@ -33,6 +34,7 @@ function AddAgentDialog({ onClose, onCreated }: { onClose: () => void; onCreated
const [maxUses, setMaxUses] = useState(1)
const [hours, setHours] = useState(24)
const [created, setCreated] = useState<CreatedJoinTokenDto>()
const [controllers, setControllers] = useState<string[] | null>(null)
if (created) return <JoinTokenCreated created={created} kind="Agent" onClose={onClose} />
@@ -57,6 +59,7 @@ function AddAgentDialog({ onClose, onCreated }: { onClose: () => void; onCreated
auto_approve: autoApprove,
max_uses: maxUses,
expires_in_hours: hours,
controllers: controllers ?? undefined,
})
setCreated(token)
onCreated()
@@ -69,7 +72,7 @@ function AddAgentDialog({ onClose, onCreated }: { onClose: () => void; onCreated
}
>
<p className="muted" style={{ marginBottom: 16 }}>
添加 Agent 就是创建一个一次性的加入令牌:在目标 VPS 上运行安装命令,Agent 会用它注册并出现在列表里。
添加 Agent 就是创建一个加入令牌:在目标 VPS 上运行安装命令,Agent 会用它注册并出现在列表里。一个令牌可以装任意多台(可用次数不设上限)。
</p>
<div className="form-grid">
<div className="wide">
@@ -83,11 +86,16 @@ function AddAgentDialog({ onClose, onCreated }: { onClose: () => void; onCreated
</Field>
</div>
<Field label="可用次数">
<input className="input" type="number" min={1} max={100} value={maxUses} onChange={(e) => setMaxUses(Number(e.target.value))} />
<input className="input" type="number" min={1} max={100000} value={maxUses} onChange={(e) => setMaxUses(Number(e.target.value))} />
</Field>
<Field label="有效期(小时)">
<input className="input" type="number" min={1} max={720} value={hours} onChange={(e) => setHours(Number(e.target.value))} />
</Field>
<div className="wide">
<Field label="属主(passkey)" hint="允许授权这个节点 web ssh 等敏感操作的 Passkey;只是选,不用触碰。装上后固定在节点本地,面板改不了。">
<OwnerPicker value={controllers} onChange={setControllers} />
</Field>
</div>
<div className="wide">
<label className="switch">
<input type="checkbox" checked={autoApprove} onChange={(e) => setAutoApprove(e.target.checked)} />