Use Agent for native, container, systemd and OpenWrt installations and signed updates. Remove retired Probe deployment templates and smoke scripts.
155 lines
8.9 KiB
PowerShell
155 lines
8.9 KiB
PowerShell
# MoShiro 节点一键安装(Windows,监听和拨号两种模式共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
|
||
# 面板给的命令先下载这份文件、核对 SHA-256(CI 构建时算出,写进签名的发布清单,也公布在发布页),一致才执行。
|
||
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(默认 agent,监听并拨号)、MOSHIRO_CODE(一次性的加入码)。
|
||
# 做的事:用 Docker Desktop 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取计算机名。
|
||
# 可选环境变量: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 ''
|
||
$spki = [Convert]::FromBase64String($b64.Trim())
|
||
$xy = $spki[($spki.Length - 64)..($spki.Length - 1)]
|
||
$blob = [byte[]]@(0x45, 0x43, 0x53, 0x31, 0x20, 0, 0, 0) + $xy
|
||
$i = 2
|
||
if ($derSignature[1] -band 0x80) { $i = 2 + ($derSignature[1] -band 0x7f) }
|
||
$parts = @()
|
||
for ($n = 0; $n -lt 2; $n++) {
|
||
$len = $derSignature[$i + 1]
|
||
$part = [byte[]]$derSignature[($i + 2)..($i + 1 + $len)]
|
||
while ($part.Length -gt 32 -and $part[0] -eq 0) { $part = [byte[]]$part[1..($part.Length - 1)] }
|
||
if ($part.Length -gt 32) { return $false }
|
||
$parts += , ([byte[]](New-Object byte[] (32 - $part.Length)) + $part)
|
||
$i += 2 + $len
|
||
}
|
||
$key = [Security.Cryptography.CngKey]::Import($blob, [Security.Cryptography.CngKeyBlobFormat]::EccPublicBlob)
|
||
try {
|
||
$ecdsa = New-Object Security.Cryptography.ECDsaCng($key)
|
||
return $ecdsa.VerifyData($data, [byte[]]($parts[0] + $parts[1]), [Security.Cryptography.HashAlgorithmName]::SHA256)
|
||
} finally { $key.Dispose() }
|
||
}
|
||
|
||
function Install-MoShiro {
|
||
$ErrorActionPreference = 'Stop'
|
||
$role = if ($env:MOSHIRO_ROLE) { $env:MOSHIRO_ROLE } else { 'agent' }
|
||
$server = $env:MOSHIRO_SERVER
|
||
$code = $env:MOSHIRO_CODE
|
||
$image = $env:MOSHIRO_IMAGE
|
||
$releaseKey = @'
|
||
-----BEGIN PUBLIC KEY-----
|
||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfw7EkNcl0FZqOETK+Vt55RMZ9K0q
|
||
YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
|
||
-----END PUBLIC KEY-----
|
||
'@
|
||
|
||
# 这些值来自命令行环境:只允许正常的字符,因为它们会进到 URL 和容器参数里
|
||
if ($role -notin @('agent')) { Write-Error 'MOSHIRO_ROLE must be agent'; 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) }
|
||
if (-not $name) { $name = 'node' }
|
||
|
||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
||
Write-Error 'Docker Desktop is required: https://docs.docker.com/desktop/setup/install/windows-install/'
|
||
return
|
||
}
|
||
docker info *> $null
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Error 'Docker is installed but not running: start Docker Desktop and run this again'
|
||
return
|
||
}
|
||
|
||
$container = "moshiro-$role"
|
||
$volume = "moshiro-$role-config"
|
||
docker container inspect $container *> $null
|
||
if ($LASTEXITCODE -eq 0) {
|
||
docker start $container | Out-Null
|
||
Write-Host "$container is already installed and running."
|
||
Write-Host "reinstall: docker rm -f $container; docker volume rm $volume (then run the install command again)"
|
||
return
|
||
}
|
||
|
||
# 验发布清单:签名对不上、组件不对、digest 格式不对都直接拒绝,不装
|
||
$client = New-Object System.Net.WebClient
|
||
$manifest = $null
|
||
try {
|
||
$manifest = $client.DownloadData("$server/install/manifest?code=$code")
|
||
$signature = [Convert]::FromBase64String(($client.DownloadString("$server/install/manifest.sig?code=$code")).Trim())
|
||
} catch {
|
||
$manifest = $null
|
||
}
|
||
if ($null -eq $manifest) {
|
||
if ($env:MOSHIRO_ALLOW_UNVERIFIED -ne '1' -or -not $image) {
|
||
Write-Error "the panel has no signed release 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"
|
||
} else {
|
||
if (-not (Test-MoShiroSignature $manifest $signature $releaseKey)) {
|
||
Write-Error 'the release signature does not verify against the built-in release key; refusing to install'
|
||
return
|
||
}
|
||
$release = [Text.Encoding]::UTF8.GetString($manifest) | ConvertFrom-Json
|
||
if ($release.component -ne 'agent') { Write-Error "the signed release is for '$($release.component)', not 'agent'"; return }
|
||
if ($release.digest -notmatch '^sha256:[0-9a-f]{64}$') { Write-Error 'the signed release has a malformed digest'; return }
|
||
if ($release.image -notmatch '^[A-Za-z0-9._/:-]+$') { Write-Error 'the signed release has a malformed image name'; return }
|
||
$image = "$($release.image)@$($release.digest)"
|
||
Write-Host "verified release $($release.version) ($($release.tag)): $image"
|
||
}
|
||
|
||
Write-Host "pulling $image"
|
||
docker pull $image | Out-Null
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Error "could not pull $image (private registry? run: docker login $($image.Split('/')[0]))"
|
||
return
|
||
}
|
||
|
||
$arguments = @('run', '-d', '--name', $container, '--restart', 'unless-stopped',
|
||
'--read-only', '--tmpfs', '/tmp:rw,noexec,nosuid,size=64m', '--cap-drop', 'all', '--security-opt', 'no-new-privileges',
|
||
'-v', "${volume}:/etc/moshiro")
|
||
# Docker Desktop 没有宿主网络:监听的节点把端口发布出来(面板里的地址需要指向这台机器的公网地址)
|
||
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", '-e', "MOSHIRO_MODE=$role")
|
||
# 每个节点都必须有属主(passkey):命令里的信任根指纹和属主缺一个就不装(面板的命令总是带着;只有没有信任根的老面板才需要 MOSHIRO_ALLOW_NO_OWNER=1)
|
||
if ((-not $env:MOSHIRO_GENESIS -or -not $env:MOSHIRO_CONTROLLERS) -and $env:MOSHIRO_ALLOW_NO_OWNER -ne '1') {
|
||
Write-Error 'this node has no owner: MOSHIRO_GENESIS and MOSHIRO_CONTROLLERS are required (copy the complete command from the panel; set up the trust root first)'
|
||
return
|
||
}
|
||
# 命令里带着信任根指纹和属主(公开值):节点在注册时验证面板的操作员集合并把它们固定在本地
|
||
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
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Error 'docker run failed'
|
||
return
|
||
}
|
||
|
||
# 等注册完成:第一次启动会用上面的令牌注册,成功之后才算装好
|
||
for ($i = 0; $i -lt 30; $i++) {
|
||
$running = docker container inspect -f '{{.State.Running}}' $container 2>$null
|
||
$logs = (docker logs $container 2>&1) -join "`n"
|
||
if ($logs -match 'link up') {
|
||
Write-Host "done: $role '$name' is online. It shows up in the panel now."
|
||
Write-Host "logs: docker logs -f $container"
|
||
return
|
||
}
|
||
if ($running -ne 'true') {
|
||
($logs -split "`n" | Select-Object -Last 8) -join "`n" | Write-Host
|
||
Write-Error "$container stopped. If the name '$name' is taken, run again with `$env:MOSHIRO_NAME='<other-name>'. To start over: docker rm -f $container; docker volume rm $volume"
|
||
return
|
||
}
|
||
Start-Sleep -Seconds 1
|
||
}
|
||
Write-Host "$container is running, but has not reported online yet. Check: docker logs $container"
|
||
}
|
||
|
||
Install-MoShiro
|