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

+28 -12
View File
@@ -1,8 +1,10 @@
# MoShiro 节点一键安装(Windows,Agent 和 Probe 共用同一份)。面板在 /install.ps1?code=… 生成这份脚本,把下面四个值填好:
# irm 'https://panel/install.ps1?code=jt_…' | iex
# 镜像:向面板取 CI 签名的发布清单,用内置的发布公钥验签,再按清单里的 digest 拉取。面板还没有签名的清单时,设 $env:MOSHIRO_ALLOW_UNVERIFIED='1' 才会按标签安装。
# MoShiro 节点一键安装(Windows,Agent 和 Probe 共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
# 面板给的命令先下载这份文件、核对 SHA-256(CI 构建时算出,写进签名的发布清单,也公布在发布页),一致才执行。
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(agent 或 probe)、MOSHIRO_CODE(一次性的加入码)。
# 做的事:用 Docker Desktop 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取计算机名。
# 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)。
# 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)、MOSHIRO_GENESIS / MOSHIRO_CONTROLLERS(命令里带着:信任根指纹和这个节点的属主 passkey 凭证 id,都是公开值)、
# MOSHIRO_ALLOW_UNVERIFIED=1 加 MOSHIRO_IMAGE=镜像(面板还没有已签名的发布清单时,不验签、按这个镜像安装;默认拒绝)。
# 镜像:向面板取 CI 签名的发布清单,用内置的发布公钥验签,再按清单里的 digest 拉取。
function Test-MoShiroSignature([byte[]]$data, [byte[]]$derSignature, [string]$pem) {
# 发布公钥是 PEM 的 SPKI(P-256):最后 64 字节是 X、Y;签名是 DER,.NET 要 r||s
$b64 = ($pem -split "`n" | Where-Object { $_ -notmatch '^-----' }) -join ''
@@ -29,14 +31,22 @@ function Test-MoShiroSignature([byte[]]$data, [byte[]]$derSignature, [string]$pe
function Install-MoShiro {
$ErrorActionPreference = 'Stop'
$role = '@@ROLE@@'
$server = '@@SERVER@@'
$code = '@@CODE@@'
$image = '@@IMAGE@@'
$role = $env:MOSHIRO_ROLE
$server = $env:MOSHIRO_SERVER
$code = $env:MOSHIRO_CODE
$image = $env:MOSHIRO_IMAGE
$releaseKey = @'
@@RELEASE_KEY@@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfw7EkNcl0FZqOETK+Vt55RMZ9K0q
YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
-----END PUBLIC KEY-----
'@
# 这些值来自命令行环境:只允许正常的字符,因为它们会进到 URL 和容器参数里
if ($role -notin @('agent', 'probe')) { Write-Error 'MOSHIRO_ROLE must be agent or probe'; return }
if ($server -notmatch '^https?://[A-Za-z0-9._:/@~%+\[\]-]+$') { Write-Error 'MOSHIRO_SERVER must be the panel address (http:// or https://)'; return }
if ($code -notmatch '^[A-Za-z0-9._~%+-]+$') { Write-Error 'MOSHIRO_CODE is required (copy the whole command from the panel)'; return }
$name = if ($env:MOSHIRO_NAME) { $env:MOSHIRO_NAME } else { $env:COMPUTERNAME }
$name = $name -replace '[^A-Za-z0-9._-]', '-'
if ($name.Length -gt 64) { $name = $name.Substring(0, 64) }
@@ -72,8 +82,8 @@ function Install-MoShiro {
$manifest = $null
}
if ($null -eq $manifest) {
if ($env:MOSHIRO_ALLOW_UNVERIFIED -ne '1') {
Write-Error "the panel has no signed release for $role yet, so the image cannot be verified. Run again with `$env:MOSHIRO_ALLOW_UNVERIFIED='1' to install $image without verification"
if ($env:MOSHIRO_ALLOW_UNVERIFIED -ne '1' -or -not $image) {
Write-Error "the panel has no signed release for $role yet, so the image cannot be verified. To install anyway set `$env:MOSHIRO_ALLOW_UNVERIFIED='1' and `$env:MOSHIRO_IMAGE"
return
}
Write-Host "warning: installing $image without signature verification"
@@ -102,7 +112,13 @@ function Install-MoShiro {
'-v', "${volume}:/etc/moshiro")
# Docker Desktop 没有宿主网络:Agent 把端口发布出来(面板里的地址需要指向这台机器的公网地址)
if ($role -eq 'agent') { $arguments += @('-p', '7443:7443/tcp', '-p', '7443:7443/udp', '-p', '7444:7444/tcp') }
$arguments += @('-e', "MOSHIRO_SERVER=$server", '-e', "MOSHIRO_JOIN=$code", '-e', "MOSHIRO_NAME=$name", $image)
$arguments += @('-e', "MOSHIRO_SERVER=$server", '-e', "MOSHIRO_JOIN=$code", '-e', "MOSHIRO_NAME=$name")
# 命令里带着信任根指纹和属主(公开值):节点在注册时验证面板的操作员集合并把它们固定在本地
if ($env:MOSHIRO_GENESIS) {
if (($env:MOSHIRO_GENESIS + $env:MOSHIRO_CONTROLLERS) -notmatch '^[A-Za-z0-9_,-]+$') { Write-Error 'MOSHIRO_GENESIS or MOSHIRO_CONTROLLERS has unexpected characters'; return }
$arguments += @('-e', "MOSHIRO_GENESIS=$($env:MOSHIRO_GENESIS)", '-e', "MOSHIRO_CONTROLLERS=$($env:MOSHIRO_CONTROLLERS)")
}
$arguments += $image
Write-Host "starting $container as '$name'"
docker @arguments | Out-Null
+28 -11
View File
@@ -1,21 +1,33 @@
#!/bin/sh
# MoShiro 节点一键安装(Agent 和 Probe 共用同一份)。面板在 /install?code=… 生成这份脚本,把下面四个值填好:
# curl -fsSL 'https://panel/install?code=jt_…' | bash (macOS 用 zsh)
# MoShiro 节点一键安装(Agent 和 Probe 共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
# 面板给的命令先下载这份文件、核对 SHA-256(CI 构建时算出,写进签名的发布清单,也公布在发布页),一致才执行:
# f=$(mktemp) && curl -fsSL 'https://panel/install' -o "$f" && echo "<sha256> $f" | sha256sum -c - && MOSHIRO_SERVER=… MOSHIRO_ROLE=… MOSHIRO_CODE=… sh "$f"
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(agent 或 probe)、MOSHIRO_CODE(一次性的加入码)。
# 做的事:用 Docker 或 Podman 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取主机名。
# 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)、MOSHIRO_ENGINE=docker|podman、MOSHIRO_DRY_RUN=1(只打印命令)、
# MOSHIRO_ALLOW_UNVERIFIED=1(面板还没有已签名的发布清单时,不验签、按标签安装;默认拒绝)。
# MOSHIRO_GENESIS / MOSHIRO_CONTROLLERS(命令里带着:你的信任根指纹和这个节点的属主 passkey 凭证 id,节点在注册时验证面板的操作员集合并把它们固定在本地;都是公开值)、
# MOSHIRO_ALLOW_UNVERIFIED=1 加 MOSHIRO_IMAGE=镜像(面板还没有已签名的发布清单时,不验签、按这个镜像安装;默认拒绝)。
# 镜像:向面板取 CI 签名的发布清单,用下面内置的发布公钥验签,再按清单里的 digest 拉取(镜像按内容寻址,换不了)。
set -eu
ROLE='@@ROLE@@'
SERVER='@@SERVER@@'
CODE='@@CODE@@'
IMAGE='@@IMAGE@@'
RELEASE_KEY='@@RELEASE_KEY@@'
SERVER="${MOSHIRO_SERVER:-}"
ROLE="${MOSHIRO_ROLE:-}"
CODE="${MOSHIRO_CODE:-}"
IMAGE="${MOSHIRO_IMAGE:-}"
RELEASE_KEY='-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfw7EkNcl0FZqOETK+Vt55RMZ9K0q
YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
-----END PUBLIC KEY-----'
say() { printf '%s\n' "$*"; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
# 这些值来自命令行环境:只允许正常的字符,因为它们会进到 URL 和容器参数里
case "$ROLE" in agent|probe) ;; *) die "MOSHIRO_ROLE must be agent or probe" ;; esac
case "$SERVER" in http://*|https://*) ;; *) die "MOSHIRO_SERVER must be the panel address (http:// or https://)" ;; esac
case "$SERVER$CODE" in *[!A-Za-z0-9._:/@~%+\[\]-]*|'') die "MOSHIRO_SERVER or MOSHIRO_CODE has unexpected characters" ;; esac
[ -n "$CODE" ] || die "MOSHIRO_CODE is required (copy the whole command from the panel)"
name="${MOSHIRO_NAME:-$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo node)}"
name="$(printf '%s' "$name" | tr -c 'A-Za-z0-9._-' '-' | cut -c1-64)"
[ -n "$name" ] || name=node
@@ -51,14 +63,14 @@ fi
# 验发布清单:签名对不上、组件不对、digest 格式不对都直接拒绝,不装
resolve_image() {
if [ "${MOSHIRO_DRY_RUN:-}" = 1 ]; then return 0; fi
if [ "${MOSHIRO_DRY_RUN:-}" = 1 ]; then IMAGE="${IMAGE:-<the image of the signed release>}"; return 0; fi
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
have_manifest=1
curl -fsS "$SERVER/install/manifest?code=$CODE" -o "$tmp/manifest" 2>/dev/null </dev/null || have_manifest=0
curl -fsS "$SERVER/install/manifest.sig?code=$CODE" -o "$tmp/sig.b64" 2>/dev/null </dev/null || have_manifest=0
if [ "$have_manifest" -eq 0 ]; then
[ "${MOSHIRO_ALLOW_UNVERIFIED:-}" = 1 ] || die "the panel has no signed release for $ROLE yet, so the image cannot be verified. Run again with MOSHIRO_ALLOW_UNVERIFIED=1 to install $IMAGE without verification"
[ "${MOSHIRO_ALLOW_UNVERIFIED:-}" = 1 ] && [ -n "$IMAGE" ] || die "the panel has no signed release for $ROLE yet, so the image cannot be verified. To install anyway set MOSHIRO_ALLOW_UNVERIFIED=1 and MOSHIRO_IMAGE=<image>"
say "warning: installing $IMAGE without signature verification"
return 0
fi
@@ -96,7 +108,12 @@ elif [ "$ROLE" = agent ]; then
# macOS 上的 Docker 没有宿主网络:把 Agent 的端口发布出来(面板里的地址需要指向这台机器的公网地址)
set -- "$@" -p 7443:7443/tcp -p 7443:7443/udp -p 7444:7444/tcp
fi
set -- "$@" -e "MOSHIRO_SERVER=$SERVER" -e "MOSHIRO_JOIN=$CODE" -e "MOSHIRO_NAME=$name" "$IMAGE"
set -- "$@" -e "MOSHIRO_SERVER=$SERVER" -e "MOSHIRO_JOIN=$CODE" -e "MOSHIRO_NAME=$name"
if [ -n "${MOSHIRO_GENESIS:-}" ]; then
case "$MOSHIRO_GENESIS${MOSHIRO_CONTROLLERS:-}" in *[!A-Za-z0-9_,-]*) die "MOSHIRO_GENESIS or MOSHIRO_CONTROLLERS has unexpected characters" ;; esac
set -- "$@" -e "MOSHIRO_GENESIS=$MOSHIRO_GENESIS" -e "MOSHIRO_CONTROLLERS=${MOSHIRO_CONTROLLERS:-}"
fi
set -- "$@" "$IMAGE"
if [ "${MOSHIRO_DRY_RUN:-}" = 1 ]; then
say "$engine $*"