ci: build musl only for arm64 and reuse toolchain cache [CI]
This commit is contained in:
1 parent
490b008a57
commit
a6f9759238
22 files changed
+62
-73
No files matched your search
@@ -36,18 +36,6 @@ steps:
|
||||
- python3 ci/run-with-timeout.py 1800 bash ci/build-dev-image.sh backend
|
||||
depends_on: []
|
||||
|
||||
- name: build-musl-amd64
|
||||
image: bash
|
||||
environment:
|
||||
GITEA_BASE: http://127.0.0.1:3100
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
RELEASE_SIGNING_KEY:
|
||||
from_secret: release_signing_key
|
||||
commands:
|
||||
- python3 ci/run-with-timeout.py 3600 bash ci/build-linux-agent.sh linux-musl-x64
|
||||
depends_on: []
|
||||
|
||||
- name: build-musl-arm64
|
||||
image: bash
|
||||
environment:
|
||||
@@ -100,7 +88,7 @@ steps:
|
||||
commands:
|
||||
- python3 ci/run-with-timeout.py 900 bash ci/release-node-images.sh
|
||||
depends_on:
|
||||
- build-musl-amd64
|
||||
- build-glibc-amd64
|
||||
- build-musl-arm64
|
||||
|
||||
- name: backend-e2e-tests
|
||||
|
||||
@@ -16,18 +16,6 @@ when:
|
||||
concurrency: 1
|
||||
|
||||
steps:
|
||||
- name: build-musl-amd64
|
||||
image: bash
|
||||
environment:
|
||||
GITEA_BASE: http://127.0.0.1:3100
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
RELEASE_SIGNING_KEY:
|
||||
from_secret: release_signing_key
|
||||
commands:
|
||||
- python3 ci/run-with-timeout.py 3600 bash ci/build-linux-agent.sh linux-musl-x64
|
||||
depends_on: []
|
||||
|
||||
- name: build-musl-arm64
|
||||
image: bash
|
||||
environment:
|
||||
|
||||
@@ -83,7 +83,7 @@ Triggers, tags and image paths are defined in `.woodpecker/*.yml` and [`docs/ops
|
||||
- A commit message containing `[CI]` builds and pushes development images. `[CI SKIP]` skips the pipeline; `[SKIP TESTS]` skips only the Backend tests. Slow end-to-end tests run after image publication and do not block it.
|
||||
- A tag `vMAJOR.MINOR.PATCH` runs the full cross-platform release pipeline. No version-bump commit is needed.
|
||||
- Versions are produced by `ci/version.sh`. The `<Version>` in `backend/Directory.Build.props` and the `version` in `web` `package.json` files are placeholders (`0.0.0-dev`) and must not be edited.
|
||||
- Linux Agent builds use `ci/build-linux-agent.sh` for musl/glibc on amd64/arm64 in both dev and tag workflows. OCI and OpenWrt installers reuse these native outputs; Windows runners build only Windows/macOS.
|
||||
- Linux Agent builds use `ci/build-linux-agent.sh` for glibc on amd64/arm64 and musl on arm64 in both dev and tag workflows. OCI and OpenWrt installers reuse these native outputs; Windows runners build only Windows/macOS.
|
||||
- Build stores keep NuGet cache mounts, retain image layers for 7 days, and clean temporary outputs under store locks (shared for native builds, exclusive for cleanup). Do not use `buildah prune` on these stores: it removes restore caches too.
|
||||
- Development images use the `moshiro-dev/` repository path and release images use `moshiro/`. A development build must never overwrite a release tag such as `latest`.
|
||||
- Release manifests are registered at `/api/release/offer`, which requires no token because the panel verifies the signature.
|
||||
+1
-1
@@ -38,7 +38,7 @@ dotnet publish src/MoShiro.Backend -c Release -r <RID> -p:PublishAot=true
|
||||
|
||||
例如 `<RID>` 可为 `win-x64`、`linux-x64` 或 `linux-arm64`。NativeAOT 发布需要对应平台的 C/C++ 工具链。仓库根目录的 `scripts/publish.sh backend <RID>` 可构建单个平台的 Backend 可执行文件;容器发布由 Woodpecker tag 流水线完成,详见 [`../docs/ops/deployment.md`](../docs/ops/deployment.md)。
|
||||
|
||||
正式发布通过 Woodpecker tag 流水线生成 Linux/amd64 与 Linux/arm64 OCI 镜像,容器内监听 `8080`,数据写入挂载的 `/data` 卷。Agent 的多平台二进制包单独发布,不包含在 Backend 镜像中;Linux runner 构建四个 Linux RID,Windows runner 构建 macOS 和 Windows。
|
||||
正式发布通过 Woodpecker tag 流水线生成 Linux/amd64 与 Linux/arm64 OCI 镜像,容器内监听 `8080`,数据写入挂载的 `/data` 卷。Agent 的多平台二进制包单独发布,不包含在 Backend 镜像中;Linux runner 构建三个 Linux RID,Windows runner 构建 macOS 和 Windows。
|
||||
|
||||
## 本地运行
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet('linux-x64', 'linux-arm64', 'linux-musl-x64', 'linux-musl-arm64')]
|
||||
[ValidateSet('linux-x64', 'linux-arm64', 'linux-musl-arm64')]
|
||||
[string]$RuntimeIdentifier,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Destination
|
||||
|
||||
+14
-4
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# One native build/package path for glibc and musl, used by development and tags.
|
||||
set -euo pipefail
|
||||
rid="${1:?usage: build-linux-agent.sh <linux-x64|linux-arm64|linux-musl-x64|linux-musl-arm64>}"
|
||||
rid="${1:?usage: build-linux-agent.sh <linux-x64|linux-arm64|linux-musl-arm64>}"
|
||||
case "$rid" in
|
||||
linux-x64|linux-arm64) static_msquic=false ;;
|
||||
linux-musl-x64|linux-musl-arm64) static_msquic=true ;;
|
||||
linux-musl-arm64) static_msquic=true ;;
|
||||
*) echo "unsupported Linux RID: $rid" >&2; exit 2 ;;
|
||||
esac
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -35,14 +35,20 @@ trap cleanup EXIT
|
||||
context="$work/context"
|
||||
# Serialize toolchain import/build; the native jobs use its resolved immutable ID.
|
||||
exec 7> "$base_home/.toolchain.lock"
|
||||
echo "[$rid] waiting for the shared NativeAOT toolchain lock"
|
||||
flock -w 1800 7
|
||||
echo "[$rid] acquired the toolchain lock; checking the local cache"
|
||||
builder_image="localhost/moshiro-dotnet10-nativeaot-aotanywhere:ci"
|
||||
source "$root/ci/nativeaot-toolchain.sh"
|
||||
moshiro_nativeaot_settings "$root"
|
||||
builder_cache="${MOSHIRO_RUNNER_IMAGE_CACHE:-/home/justme/woodpecker/cache/runner-image}"
|
||||
cached_key="$(cat "$builder_cache/nativeaot-builder.key" 2>/dev/null || true)"
|
||||
builder_id="$(cat "$builder_cache/nativeaot-builder.id" 2>/dev/null || true)"
|
||||
if [[ "$cached_key" == "$MOSHIRO_NATIVEAOT_BUILDER_KEY" && "$builder_id" =~ ^[0-9a-f]{64}$ && -f "$builder_cache/nativeaot-builder-$builder_id.oci" ]]; then
|
||||
local_key="$(cat "$base_home/nativeaot-builder.key" 2>/dev/null || true)"
|
||||
local_builder_id="$(buildah images --noheading --no-trunc --format '{{.ID}}' "$builder_image" 2>/dev/null || true)"
|
||||
if [[ "$local_key" == "$MOSHIRO_NATIVEAOT_BUILDER_KEY" && "${local_builder_id#sha256:}" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "[$rid] reusing the toolchain already built in this store"
|
||||
elif [[ "$cached_key" == "$MOSHIRO_NATIVEAOT_BUILDER_KEY" && "$builder_id" =~ ^[0-9a-f]{64}$ && -f "$builder_cache/nativeaot-builder-$builder_id.oci" ]]; then
|
||||
local_builder_id="$(buildah images --noheading --no-trunc --format '{{.ID}}' "$builder_image" 2>/dev/null || true)"
|
||||
if [[ "${local_builder_id#sha256:}" != "$builder_id" ]]; then
|
||||
python3 "$root/ci/run-with-timeout.py" 600 buildah pull "oci-archive:$builder_cache/nativeaot-builder-$builder_id.oci"
|
||||
@@ -53,13 +59,17 @@ else
|
||||
builder_context="$work/nativeaot-builder"
|
||||
mkdir -p "$builder_context"
|
||||
tr -d '\r' < "$root/global.json" > "$builder_context/global.json"
|
||||
python3 "$root/ci/run-with-timeout.py" 1800 buildah bud --layers --network host --tls-verify=true --arch amd64 \
|
||||
touch -t 200001010000 "$builder_context/global.json"
|
||||
echo "[$rid] preparing the shared toolchain (other targets wait for this build)"
|
||||
python3 "$root/ci/run-with-timeout.py" 1800 buildah bud --layers --force-rm --network host --tls-verify=true --arch amd64 \
|
||||
--build-arg "AOTANYWHERE_VERSION=$MOSHIRO_AOTANYWHERE_VERSION" \
|
||||
--build-arg "ZIG_TOOLSET_VERSION=$MOSHIRO_ZIG_TOOLSET_VERSION" \
|
||||
-f "$root/packaging/container/NativeAot.Builder.Containerfile" \
|
||||
-t "$builder_image" "$builder_context"
|
||||
fi
|
||||
builder_image="$(buildah images --noheading --no-trunc --format '{{.ID}}' "$builder_image")"
|
||||
printf '%s\n' "$MOSHIRO_NATIVEAOT_BUILDER_KEY" > "$base_home/nativeaot-builder.key"
|
||||
echo "[$rid] toolchain ready: $builder_image; starting the native build"
|
||||
flock -u 7
|
||||
exec 7>&-
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
# Caller holds its store lock. Keep recent image layers and persistent RUN caches.
|
||||
set -euo pipefail
|
||||
: "${CONTAINERS_STORAGE_CONF:?an explicit, locked CI Buildah store is required}"
|
||||
# No build is active while the caller holds the exclusive store lock. Remove
|
||||
# working containers left by canceled builds before considering old image layers.
|
||||
buildah unshare buildah rm --all >/dev/null 2>&1 || true
|
||||
buildah rmi localhost/moshiro-agent-binaries:linux-musl-x64 >/dev/null 2>&1 || true
|
||||
# prune also deletes RUN cache mounts; remove only expired intermediate layers and versioned application images.
|
||||
mapfile -t expired < <(buildah images --all --no-trunc --format '{{.ID}} {{.Created}} {{.Name}}:{{.Tag}}' | python3 -c '
|
||||
import re,sys,time
|
||||
|
||||
@@ -6,7 +6,6 @@ binaries="${2:?}"; destination="${3:?}"
|
||||
case "$rid" in
|
||||
linux-x64) suffix=amd64 ;;
|
||||
linux-arm64) suffix=arm64 ;;
|
||||
linux-musl-x64) suffix=musl-amd64 ;;
|
||||
linux-musl-arm64) suffix=musl-arm64 ;;
|
||||
*) echo "unsupported Linux RID: $rid" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
@@ -29,7 +29,7 @@ trap 'rm -rf "$tmp"' EXIT
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
install_sh="$(sha256sum "$root_dir/packaging/install/install.sh" | cut -d" " -f1)"
|
||||
install_ps1="$(sha256sum "$root_dir/packaging/install/install.ps1" | cut -d" " -f1)"
|
||||
release_body="Install script SHA-256 (compare with the panel before you run an install command): install.sh $install_sh, install.ps1 $install_ps1. Agent builds: Linux glibc and musl (x64, arm64), macOS (x64, arm64), and Windows (x64, arm64). Linux musl Agent OCI image: registry.oeo.one/moshiro/moshiro-agent:$version (linux/amd64 and linux/arm64). Backend OCI image: registry.oeo.one/moshiro/moshiro-backend:$version (linux/amd64 and linux/arm64). Compose files for Agent and Backend: moshiro-compose-<role>.yaml (images use :latest, or pin MOSHIRO_VERSION in .env). macOS binaries are not Developer ID signed or notarized."
|
||||
release_body="Install script SHA-256 (compare with the panel before you run an install command): install.sh $install_sh, install.ps1 $install_ps1. Agent builds: Linux glibc (x64, arm64) and musl (arm64), macOS (x64, arm64), and Windows (x64, arm64). Linux Agent OCI image: registry.oeo.one/moshiro/moshiro-agent:$version (linux/amd64 and linux/arm64). Backend OCI image: registry.oeo.one/moshiro/moshiro-backend:$version (linux/amd64 and linux/arm64). Compose files for Agent and Backend: moshiro-compose-<role>.yaml (images use :latest, or pin MOSHIRO_VERSION in .env). macOS binaries are not Developer ID signed or notarized."
|
||||
payload="$(printf '{"tag_name":"%s","target_commitish":"%s","name":"MoShiro %s","body":"%s","draft":false,"prerelease":false}' \
|
||||
"$tag" "${CI_COMMIT_SHA:-}" "$tag" "$release_body")"
|
||||
release_status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
|
||||
@@ -65,7 +65,6 @@ curl --fail-with-body --silent --show-error --retry 3 --netrc-file "$netrc" \
|
||||
assets=(
|
||||
"moshiro-agent-v$version-linux-amd64.tar.gz"
|
||||
"moshiro-agent-v$version-linux-arm64.tar.gz"
|
||||
"moshiro-agent-v$version-linux-musl-amd64.tar.gz"
|
||||
"moshiro-agent-v$version-linux-musl-arm64.tar.gz"
|
||||
"moshiro-agent-v$version-macos-x64.tar.gz"
|
||||
"moshiro-agent-v$version-macos-arm64.tar.gz"
|
||||
|
||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
channel="${1:?usage: publish-openwrt.sh <dev|release> <rid> <binary-directory>}"
|
||||
rid="${2:?}"; source_dir="${3:?}"
|
||||
[[ "$channel" == dev || "$channel" == release ]] || exit 2
|
||||
[[ "$rid" == linux-musl-arm64 || "$rid" == linux-musl-x64 ]] || exit 2
|
||||
[[ "$rid" == linux-musl-arm64 ]] || exit 2
|
||||
if [[ "$channel" == release ]]; then
|
||||
[[ "${CI_COMMIT_TAG:-}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo 'release feed requires a stable version tag' >&2; exit 2; }
|
||||
else
|
||||
|
||||
@@ -2,7 +2,7 @@ param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet(
|
||||
'linux-x64', 'linux-arm64',
|
||||
'linux-musl-x64', 'linux-musl-arm64',
|
||||
'linux-musl-arm64',
|
||||
'osx-x64', 'osx-arm64', 'win-x64', 'win-arm64'
|
||||
)]
|
||||
[string]$RuntimeIdentifier,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Publish musl OCI images for the Linux Agent on amd64 and arm64.
|
||||
# Publish the Agent's glibc amd64 and musl arm64 OCI images.
|
||||
set -euo pipefail
|
||||
|
||||
tag="${CI_COMMIT_TAG:-}"
|
||||
@@ -64,22 +64,29 @@ build_arch_image() {
|
||||
local component="$1" arch="$2" suffix="$3"
|
||||
local filename="moshiro-$component-v$version-linux-$suffix.tar.gz"
|
||||
local context="$work/$component-$arch"
|
||||
local extracted="$work/extracted-$component-$arch"
|
||||
local archive="$work/$filename"
|
||||
local containerfile="$root/packaging/container/${component^}.Containerfile"
|
||||
local image="$image_root/moshiro-$component"
|
||||
local local_image="localhost/moshiro-$component:ci-$channel-$arch"
|
||||
local runtime_base=mcr.microsoft.com/dotnet/runtime-deps:10.0
|
||||
[[ "$suffix" != musl-* ]] || runtime_base=mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
|
||||
|
||||
curl --fail-with-body --silent --show-error --retry 3 --netrc-file "$netrc" \
|
||||
"$gitea_base/api/packages/justme/generic/moshiro-node/$version/$filename" -o "$archive"
|
||||
mkdir -p "$context/app" "$context/config"
|
||||
tar -xzf "$archive" -C "$work"
|
||||
install -m 0755 "$work/fleet-enroll" "$context/app/fleet-enroll"
|
||||
install -m 0755 "$work/moshiro-$component" "$context/app/moshiro-$component"
|
||||
mkdir -p "$extracted"
|
||||
tar -xzf "$archive" -C "$extracted"
|
||||
install -m 0755 "$extracted/fleet-enroll" "$context/app/fleet-enroll"
|
||||
install -m 0755 "$extracted/moshiro-$component" "$context/app/moshiro-$component"
|
||||
for library in "$extracted"/lib*.so.*; do
|
||||
[[ ! -f "$library" ]] || install -m 0644 "$library" "$context/app/"
|
||||
done
|
||||
: > "$context/config/.keep"
|
||||
install -m 0755 "$root/packaging/container/entrypoint.sh" "$context/entrypoint.sh"
|
||||
|
||||
buildah bud --tls-verify=true --arch "$arch" \
|
||||
--build-arg "APP_VERSION=$version" \
|
||||
--build-arg "APP_VERSION=$version" --build-arg "RUNTIME_BASE=$runtime_base" \
|
||||
-f "$containerfile" -t "$local_image" "$context" >/dev/null
|
||||
|
||||
local pushed=false
|
||||
@@ -116,7 +123,7 @@ publish_index() {
|
||||
}
|
||||
|
||||
for component in agent; do
|
||||
build_arch_image "$component" amd64 musl-amd64
|
||||
build_arch_image "$component" amd64 amd64
|
||||
build_arch_image "$component" arm64 musl-arm64
|
||||
publish_index "$component"
|
||||
# 发布清单:给多架构镜像的 digest 签名(私钥只在 CI 密钥里),放进镜像仓库(<仓库>-manifest:release),面板在线时再登记一份加速。
|
||||
|
||||
@@ -134,7 +134,7 @@ def normalize_mtimes(directory: Path) -> None:
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("component", choices=tuple(CONTAINERFILES))
|
||||
parser.add_argument("runtime_identifier", choices=("linux-x64", "linux-arm64", "linux-musl-x64", "linux-musl-arm64"))
|
||||
parser.add_argument("runtime_identifier", choices=("linux-x64", "linux-arm64", "linux-musl-arm64"))
|
||||
parser.add_argument("destination", type=Path)
|
||||
args = parser.parse_args()
|
||||
stage(args.component, args.runtime_identifier, args.destination)
|
||||
|
||||
@@ -232,12 +232,12 @@ Backend 允许的包:ASP.NET Core 共享框架、`Microsoft.Data.Sqlite`、`Mi
|
||||
| --- | --- | --- |
|
||||
| Agent | `linux-x64`、`linux-arm64` | VPS |
|
||||
| 拨号方 | `linux-x64`、`linux-arm64`、`win-x64` | 普通 PC 或服务器 |
|
||||
| 拨号方(OpenWrt) | `linux-musl-x64`、`linux-musl-arm64` | 正式支持;`linux-musl-arm`(armv7)视工具链验证结果决定是否支持 |
|
||||
| 拨号方(OpenWrt) | `linux-musl-arm64` | 正式支持;`linux-musl-arm`(armv7)视工具链验证结果决定是否支持 |
|
||||
| fleet-enroll | 与所在节点相同 | |
|
||||
| Backend | `linux-x64`、`linux-arm64`、`win-x64` | |
|
||||
|
||||
- MIPS 不支持(NativeAOT 没有 MIPS 目标)。使用 MIPS 路由器(例如 MT7621)时,把 拨号方 运行在局域网内的其他设备上,比如 NAS、树莓派或 PC,测量结果同样代表这条家宽。
|
||||
- NativeAOT 需要目标平台的原生工具链:musl 目标在 Alpine 容器中构建,arm64 使用交叉编译镜像。CI 矩阵为 Windows、Linux glibc、Linux musl。
|
||||
- NativeAOT 需要目标平台的原生工具链:Linux amd64/arm64 发布 glibc,OpenWrt ARM64 发布静态 musl;arm64 使用交叉编译镜像。CI 矩阵为 Windows、macOS、Linux glibc amd64/arm64 和 Linux musl arm64。
|
||||
- `scripts/publish.sh <agent|backend|fleet-enroll> <rid>` 输出到 `backend/artifacts/publish/<project>/<rid>/`。
|
||||
- 容器镜像由 CI 构建并对镜像 digest 签名,节点的宿主机更新器验签后更新,见 [deployment.md](../ops/deployment.md);纯二进制节点的更新见 [路线图](../roadmap.md)。
|
||||
- Linux 上 TLS 依赖系统 OpenSSL(`libssl.so.3`)。
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
# 部署
|
||||
|
||||
节点(Agent)使用 NativeAOT。Linux 的开发版和正式版共用 `ci/build-linux-agent.sh`:musl amd64/arm64 静态链接 MsQuic、OpenSSL 和 .NET 加密 shim;glibc amd64/arm64 附带 MsQuic、NUMA、zstd 和 zlib 共享库,主机仍需提供 OpenSSL 3 和对应 libc。Agent OCI 镜像以 Alpine 为基底,直接使用同版本的 musl 原生包。OpenWrt 更新包也使用这份二进制,安装与签名清单沿用设备端现有协议。
|
||||
节点(Agent)使用 NativeAOT。Linux 的开发版和正式版共用 `ci/build-linux-agent.sh`:musl arm64 静态链接 MsQuic、OpenSSL 和 .NET 加密 shim;glibc amd64/arm64 附带 MsQuic、NUMA、zstd 和 zlib 共享库,主机仍需提供 OpenSSL 3 和对应 libc。Agent OCI 镜像复用同版本的原生包:amd64 使用 glibc 与 Debian 基底,arm64 使用静态 musl 与 Alpine 基底。OpenWrt 更新包也使用这份二进制,安装与签名清单沿用设备端现有协议。
|
||||
|
||||
## 1. 构建与打包
|
||||
|
||||
@@ -42,13 +42,13 @@ scripts/package.sh <agent> <rid> → backend/artifacts
|
||||
| rid | 目标 | 构建方式 |
|
||||
| --- | --- | --- |
|
||||
| `linux-x64`、`linux-arm64` | Debian/Ubuntu glibc 服务器 | Linux amd64 runner 通过 AotAnywhere 交叉编译 |
|
||||
| `linux-musl-x64`、`linux-musl-arm64` | Alpine、amd64 OpenWrt、aarch64 OpenWrt 路由器 | Linux amd64 runner 通过 AotAnywhere 交叉编译 |
|
||||
| `linux-musl-arm64` | ARM64 Alpine、aarch64 OpenWrt 路由器 | Linux amd64 runner 通过 AotAnywhere 交叉编译 |
|
||||
| `osx-x64`、`osx-arm64` | Intel Mac、Apple Silicon | Windows CI 主机通过 AotAnywhere 交叉编译;发布包未做 Developer ID 签名或 Apple 公证 |
|
||||
| `win-x64`、`win-arm64` | Windows x64、ARM64 | Windows CI 主机上的 Visual Studio C++ 工具链 |
|
||||
|
||||
macOS 的 Apple Silicon 产物带临时 ad-hoc 签名,Intel 产物未签名。通过浏览器下载时,macOS Gatekeeper 可能阻止启动;面向普通用户分发前需要 Developer ID 签名和 Apple 公证。MIPS 不在当前目标列表中(见 `project-layout.md`)。
|
||||
|
||||
仓库内的 `scripts/publish.sh` 面向本机目标平台;Woodpecker 的开发版和 tag 流水线在 Linux runner 上构建 Agent 与 `fleet-enroll` 的四个 Linux RID;Windows runner 负责 macOS 和 Windows。Backend OCI 镜像在 Linux runner 上构建。
|
||||
仓库内的 `scripts/publish.sh` 面向本机目标平台;Woodpecker 的开发版和 tag 流水线在 Linux runner 上构建 Agent 与 `fleet-enroll` 的三个 Linux RID;Windows runner 负责 macOS 和 Windows。Backend OCI 镜像在 Linux runner 上构建。
|
||||
|
||||
## Backend 容器
|
||||
|
||||
@@ -84,25 +84,25 @@ Backend 的 NativeAOT 发布默认启用 `SqliteStaticLink`,支持 Linux glibc
|
||||
|
||||
## 节点二进制发布
|
||||
|
||||
Woodpecker 在 `vMAJOR.MINOR.PATCH` tag 上,由 Windows runner 使用 AotAnywhere 并行构建 Agent 的 Linux glibc、Linux musl、macOS 和 Windows 产物。Agent 包还包含一次性注册工具 `fleet-enroll`。Linux 归档带有安装脚本和 systemd/OpenWrt 单元;Windows 产物为 `.zip`,Linux/macOS 产物为 `.tar.gz`。glibc sidecar 选用 Debian 12 兼容构建,musl sidecar 来自 Alpine v3.22。Linux musl 的 Agent 二进制还会打包为 OCI 镜像,基底为 `mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine`,支持 `linux/amd64` 和 `linux/arm64`;镜像直接带有归档中的 MsQuic、NUMA 和 LTTng 共享库,并通过 `LD_LIBRARY_PATH` 加载。Linux 原生库包按 RID 缓存在 CI runner 的 `.nuget/moshiro-quic-runtime`,索引最多缓存 24 小时,已下载的版本包会复用。所有平台产物和 OCI 镜像发布成功后,CI 会创建对应的 Gitea Release,并附上压缩包和 SHA-256 校验文件。macOS 产物目前未签名/公证。安装说明见 [Agent](../../backend/agent/README.md) 文档。
|
||||
Woodpecker 在 `vMAJOR.MINOR.PATCH` tag 上构建 Agent 的 Linux、macOS 和 Windows 产物。Linux amd64/arm64 使用 glibc,OpenWrt ARM64 使用静态 musl;Agent 包还包含一次性注册工具 `fleet-enroll`。Linux 归档带有安装脚本和 systemd/OpenWrt 单元;Windows 产物为 `.zip`,Linux/macOS 产物为 `.tar.gz`。glibc sidecar 选用 Debian 12 兼容构建,musl sidecar 来自 Alpine v3.22。Agent OCI 镜像使用 amd64 glibc/Debian 和 arm64 musl/Alpine,直接携带相应归档中的共享库,并通过 `LD_LIBRARY_PATH` 加载。Linux 原生库包按 RID 缓存在 CI runner 的 `.nuget/moshiro-quic-runtime`,索引最多缓存 24 小时,已下载的版本包会复用。所有平台产物和 OCI 镜像发布成功后,CI 会创建对应的 Gitea Release,并附上压缩包和 SHA-256 校验文件。macOS 产物目前未签名/公证。安装说明见 [Agent](../../backend/agent/README.md) 文档。
|
||||
|
||||
## CI 工作流
|
||||
|
||||
### Linux runner 的运行环境
|
||||
|
||||
Linux workflow 使用 `ci=domain-linux-native`、`platform=linux/amd64` 标签和 Woodpecker `local` backend;步骤里的 `image: bash` 是执行程序,构建工具来自 runner 环境。测试、前端和 Backend 编译使用 .NET 10、Node/npm、Python、clang 和 Zig;Agent 的 Linux 原生包通过 Buildah 的 NativeAOT builder 编译,OCI 镜像从已发布的 musl 包组装。
|
||||
Linux workflow 使用 `ci=domain-linux-native`、`platform=linux/amd64` 标签和 Woodpecker `local` backend;步骤里的 `image: bash` 是执行程序,构建工具来自 runner 环境。测试、前端和 Backend 编译使用 .NET 10、Node/npm、Python、clang 和 Zig;Agent 的 Linux 原生包通过 Buildah 的 NativeAOT builder 编译,OCI 镜像从已发布的 glibc amd64、musl arm64 包组装。
|
||||
|
||||
WSL 的 agent 通过 rootless Podman 运行,systemd 模板见 `ci/woodpecker-agent-podman.service`。工具全部在 `packaging/container/Woodpecker.Agent.Containerfile` 中安装,包括 .NET 10 SDK、Node/npm、Buildah、netavark/aardvark-dns、Git、Python、clang、Woodpecker agent/clone 插件和测试用 MsQuic 库;AotAnywhere SDK、Zig 与四个 Linux RID 的 NativeAOT 包由 `NativeAot.Builder.Containerfile` 预热。容器 PID 上限是 8192,避免多个并行 AotAnywhere/Zig 链接时撞到默认 2048 的限制。容器只把 `/home/justme` 作为可写工作区和缓存,不挂载宿主工具目录。连接信息与 7897 代理配置放在宿主 `/home/justme/woodpecker/agent-podman.env`,不要提交密钥。容器允许 `unshare`、提供 `SYS_ADMIN` 和 `/dev/fuse`,以便 Buildah 挂载镜像层;不要通过 `chroot /host` 启动 agent,因为 Linux 会拒绝 chroot 进程创建新的用户命名空间。
|
||||
WSL 的 agent 通过 rootless Podman 运行,systemd 模板见 `ci/woodpecker-agent-podman.service`。工具全部在 `packaging/container/Woodpecker.Agent.Containerfile` 中安装,包括 .NET 10 SDK、Node/npm、Buildah、netavark/aardvark-dns、Git、Python、clang、Woodpecker agent/clone 插件和测试用 MsQuic 库;AotAnywhere SDK、Zig 与三个 Linux RID 的 NativeAOT 包由 `NativeAot.Builder.Containerfile` 预热。容器 PID 上限是 8192,避免多个并行 AotAnywhere/Zig 链接时撞到默认 2048 的限制。容器只把 `/home/justme` 作为可写工作区和缓存,不挂载宿主工具目录。连接信息与 7897 代理配置放在宿主 `/home/justme/woodpecker/agent-podman.env`,不要提交密钥。容器允许 `unshare`、提供 `SYS_ADMIN` 和 `/dev/fuse`,以便 Buildah 挂载镜像层;不要通过 `chroot /host` 启动 agent,因为 Linux 会拒绝 chroot 进程创建新的用户命名空间。
|
||||
|
||||
以 WSL 的 `justme` 用户运行 `bash ci/build-runner-image.sh` 构建工具镜像,验证后安装 systemd 模板并重启 agent。该脚本使用 Podman 的持久镜像层缓存和 `--pull=missing`,服务启动使用 `--pull=never`;流水线和服务重启都不会执行工具安装。NativeAOT builder 只在 SDK/工具版本或 Containerfile 改变时更新,不受每次开发构建的版本号影响。它按镜像 ID 导出到 `woodpecker/cache/runner-image`,Linux Agent 的共享 Buildah store 从本地归档导入一次,之后直接复用镜像。只有所需归档不存在或工具配置改变时,构建脚本才回退到 Containerfile 构建。应用的 NuGet restore 缓存独立持久化在 `woodpecker/cache/nuget/packages`,启动时从镜像复制已有工具包作为初始缓存。
|
||||
|
||||
Agent 构建按 `build-musl-amd64`、`build-musl-arm64`、`build-glibc-amd64`、`build-glibc-arm64` 拆分。`ci/stage-node-image.py` 只把 Agent、`fleet-enroll` 的依赖工程和父目录构建配置放进还原层,文件时间固定;C# 源码进入后续编译层。四个 Linux RID 共用一个 Buildah store、工具链镜像和 `moshiro-nuget` 缓存挂载,首次从工具镜像的包目录初始化。restore 独占写入,后续编译和发布只读;镜像层命中时跳过 restore,依赖变化时仍复用已下载包。
|
||||
Agent 构建按 `build-musl-arm64`、`build-glibc-amd64`、`build-glibc-arm64` 拆分。`ci/stage-node-image.py` 只把 Agent、`fleet-enroll` 的依赖工程和父目录构建配置放进还原层,文件时间固定;C# 源码进入后续编译层。三个 Linux RID 共用一个 Buildah store、工具链镜像和 `moshiro-nuget` 缓存挂载,首次从工具镜像的包目录初始化。restore 独占写入,后续编译和发布只读;镜像层命中时跳过 restore,依赖变化时仍复用已下载包。
|
||||
|
||||
`Agent.Native.proj` 在一个 MSBuild 图内编译两份程序和公共依赖。随后两个独立 stage 用 `publish --no-build --no-restore` 完成 NativeAOT,Buildah `--jobs 2` 并行执行;中间目录随 stage 隔离。最终 scratch stage 只保留两份可执行文件,`ci/extract-node-binaries.sh` 在同一个 `buildah unshare` 会话内挂载、复制和卸载。`ci/package-linux-agent.sh` 生成原生安装归档,`ci/release-node-images.sh` 复用已发布归档生成 OCI 镜像。
|
||||
|
||||
任务结束时清理本次上下文、导出目录和临时包,失败退出也清理临时容器。构建持有共享 store 锁和当前 RID 的锁;清理需要独占 store 锁;`ci/cleanup-build-cache.sh` 清理超过 7 天的中间镜像与旧版本应用镜像,保留具名工具镜像和当前构建镜像,删除时不强制移除仍被引用的层。已退役的 Probe、独立 Agent 架构及索引 store,在存储元数据连续 7 天未更新且可取得独占锁时整目录清理;近期使用、锁定或没有可识别元数据的目录保留。NuGet 缓存挂载保留,不使用会同时删除挂载缓存的 `buildah prune`。`ci/build-runner-image.sh` 在持锁期间保留当前工具链和最近的上一份 OCI 导出归档。
|
||||
|
||||
所有镜像构建、索引及签名清单发布脚本通过 `ci/buildah-env.sh` 显式配置 Buildah 的 `graphroot` 和 `runroot`。Agent 原生构建的四个 RID 共用 `buildah-home/linux-agent`,Backend 和 OCI 发布使用各自的 store,运行状态留在当前 runner 的临时目录;容器内 UID 0 不再使各步骤落到同一个 `/var/lib/containers/storage`。`RUN` 默认使用 `chroot` isolation,避免额外的 OCI runtime/cgroup 嵌套;这与上面禁止的 agent 启动方式是不同阶段。
|
||||
所有镜像构建、索引及签名清单发布脚本通过 `ci/buildah-env.sh` 显式配置 Buildah 的 `graphroot` 和 `runroot`。Agent 原生构建的三个 RID 共用 `buildah-home/linux-agent`,Backend 和 OCI 发布使用各自的 store,运行状态留在当前 runner 的临时目录;容器内 UID 0 不再使各步骤落到同一个 `/var/lib/containers/storage`。`RUN` 默认使用 `chroot` isolation,避免额外的 OCI runtime/cgroup 嵌套;这与上面禁止的 agent 启动方式是不同阶段。
|
||||
|
||||
WSL 的 systemd 服务不会单独阻止发行版空闲退出。Windows 登录用户运行 `powershell -ExecutionPolicy Bypass -File ci/start-wsl-runner.ps1`,注册并启动隐藏的登录任务,持续运行 Debian 的前台 `sleep infinity`,让 CI server/agent 保持在线。任务使用当前用户的普通权限;需要暂停 WSL 时先停止任务 `MoShiro WSL runner (Debian)`。agent 单元等待 `user@1000.service` 并设置用户 D-Bus 地址,让 rootless Podman 使用已就绪的用户会话。
|
||||
|
||||
@@ -138,7 +138,7 @@ Pull request 和 `main` 的 push 会跑三个 workflow:
|
||||
| Backend | `registry.oeo.one/moshiro-dev/moshiro-backend:latest` | `registry.oeo.one/moshiro-dev/moshiro-backend:<版本>`(如 `0.4.1-dev.12-044ce0c`) |
|
||||
| Agent | `registry.oeo.one/moshiro-dev/moshiro-agent:latest` | `registry.oeo.one/moshiro-dev/moshiro-agent:<版本>` |
|
||||
|
||||
Agent 也发布架构固定标签 `:<版本>-amd64` 与 `:<版本>-arm64`。原生构建在 amd64 的 `sdk:10.0-noble-aot` 衍生 builder 中使用 AotAnywhere 编译四个 Linux RID,ARM64 无需 QEMU;builder 预热项目固定版本的 SDK、Zig 和运行时包,NuGet 使用共享的持久缓存挂载,restore 独占写入、编译与发布只读。运行基底为 `runtime-deps:10.0-alpine`。开发镜像不创建 Gitea Release,也不修改正式版 `moshiro/` 仓库中的标签。普通 main push 不会构建镜像。节点和 Backend 判断自己是不是开发构建,看构建信息里的 tag 含 `-dev.`(旧的 `dev-<sha>`、`main-<sha>` 也认)。
|
||||
Agent 也发布架构固定标签 `:<版本>-amd64` 与 `:<版本>-arm64`。原生构建在 amd64 的 `sdk:10.0-noble-aot` 衍生 builder 中使用 AotAnywhere 编译三个 Linux RID,ARM64 无需 QEMU;builder 预热项目固定版本的 SDK、Zig 和运行时包,NuGet 使用共享的持久缓存挂载,restore 独占写入、编译与发布只读。amd64 镜像基底为 `runtime-deps:10.0`,arm64 为 `runtime-deps:10.0-alpine`。开发镜像不创建 Gitea Release,也不修改正式版 `moshiro/` 仓库中的标签。普通 main push 不会构建镜像。节点和 Backend 判断自己是不是开发构建,看构建信息里的 tag 含 `-dev.`(旧的 `dev-<sha>`、`main-<sha>` 也认)。
|
||||
|
||||
### 正式版发布
|
||||
|
||||
@@ -192,7 +192,7 @@ Linux 产物比 Windows 大,是因为 .NET 的加密、压缩、网络本机
|
||||
|
||||
#### 原生一键安装与自动更新
|
||||
|
||||
管理端「添加 拨号方」提供 OpenWrt · 原生二进制 命令,以 root 执行。支持 ARM64/aarch64 和 x86_64,不需要容器或 .NET;自动安装依赖、验签下载、固定属主并注册、配置 procd。身份在 `/etc/moshiro/`,更新策略在 `/etc/moshiro-native.conf`(`CHANNEL=dev|release`、`POLICY=auto|manual`、`INTERVAL=30`)。动态 musl 正式包仍需 `libopenssl3`,实际设备兼容性需实机验证。
|
||||
管理端「添加 拨号方」提供 OpenWrt · 原生二进制 命令,以 root 执行。支持 ARM64/aarch64,不需要容器或 .NET;自动安装依赖、验签下载、固定属主并注册、配置 procd。身份在 `/etc/moshiro/`,更新策略在 `/etc/moshiro-native.conf`(`CHANNEL=dev|release`、`POLICY=auto|manual`、`INTERVAL=30`)。原生包使用静态 musl 可执行文件,实际设备兼容性需实机验证。
|
||||
|
||||
带 `[CI]` 的 main push 发布 dev,稳定 tag 发布 release,沿用 `gitea_token` 和 `release_signing_key`;Gitea generic package `moshiro-openwrt` 需允许匿名下载。制品发布后,在线节点收到通知就唤醒更新器,另有 30 秒轮询兜底,不等待 10 分钟。更新器验签、防重放、原子切换完整版本,健康检查失败回滚;这些步骤由独立的更新器完成,不在 拨号方 进程内执行。
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
|
||||
ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/runtime-deps:10.0
|
||||
FROM ${RUNTIME_BASE}
|
||||
|
||||
ARG APP_VERSION=unknown
|
||||
LABEL org.opencontainers.image.title="MoShiro Agent" \
|
||||
@@ -6,8 +7,7 @@ LABEL org.opencontainers.image.title="MoShiro Agent" \
|
||||
org.opencontainers.image.licenses="Apache-2.0" \
|
||||
org.opencontainers.image.version="$APP_VERSION"
|
||||
|
||||
COPY --chown=1654:1654 app/moshiro-agent /usr/local/bin/moshiro-agent
|
||||
COPY --chown=1654:1654 app/fleet-enroll /usr/local/bin/fleet-enroll
|
||||
COPY --chown=1654:1654 app/ /usr/local/bin/
|
||||
COPY --chown=1654:1654 config/ /etc/moshiro/
|
||||
COPY --chown=1654:1654 --chmod=0755 entrypoint.sh /usr/local/bin/moshiro-entrypoint
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ FROM --platform=linux/amd64 ${NATIVEAOT_BUILDER_IMAGE} AS toolchain
|
||||
FROM toolchain AS restore
|
||||
WORKDIR /src/backend
|
||||
COPY restore/backend/ ./
|
||||
ARG NODE_RID=linux-musl-x64
|
||||
ARG NODE_RID=linux-musl-arm64
|
||||
ARG STATIC_MSQUIC=true
|
||||
ENV NUGET_PACKAGES=/root/.nuget/packages
|
||||
# Layer hits skip restore. A dependency change still reuses downloaded packages.
|
||||
|
||||
@@ -28,7 +28,6 @@ RUN printf '%s\n' \
|
||||
" <PackageReference Include=\"Vezel.Zig.Toolsets.linux-x64\" Version=\"$ZIG_TOOLSET_VERSION\" GeneratePathProperty=\"true\" />" \
|
||||
' </ItemGroup>' \
|
||||
'</Project>' > Warmup.csproj \
|
||||
&& dotnet restore Warmup.csproj -p:RuntimeFrameworkVersion=10.0.12 -p:PublishAot=true -p:UseAotAnywhere=true -r linux-musl-x64 \
|
||||
&& dotnet restore Warmup.csproj -p:RuntimeFrameworkVersion=10.0.12 -p:PublishAot=true -p:UseAotAnywhere=true -r linux-musl-arm64 \
|
||||
&& dotnet restore Warmup.csproj -p:RuntimeFrameworkVersion=10.0.12 -p:PublishAot=true -p:UseAotAnywhere=true -r linux-x64 \
|
||||
&& dotnet restore Warmup.csproj -p:RuntimeFrameworkVersion=10.0.12 -p:PublishAot=true -p:UseAotAnywhere=true -r linux-arm64 \
|
||||
|
||||
@@ -84,7 +84,7 @@ load_config() {
|
||||
. "$CONF"
|
||||
case "$CHANNEL" in release|dev) ;; *) die "invalid channel" ;; esac
|
||||
case "$POLICY" in auto|manual) ;; *) die "invalid policy" ;; esac
|
||||
case "$RID" in linux-musl-arm64|linux-musl-x64) ;; *) die "unsupported architecture" ;; esac
|
||||
case "$RID" in linux-musl-arm64) ;; *) die "unsupported architecture" ;; esac
|
||||
case "$FEED" in https://*) ;; *) die "feed must use HTTPS" ;; esac
|
||||
case "$FEED" in *[!A-Za-z0-9:/._-]*) die "invalid feed URL" ;; esac
|
||||
case "$INTERVAL" in ''|*[!0-9]*) die "invalid interval" ;; esac
|
||||
@@ -270,7 +270,7 @@ install() {
|
||||
load_config
|
||||
restoring=true
|
||||
fi
|
||||
case "$(uname -m)" in aarch64|arm64) detected_rid=linux-musl-arm64 ;; x86_64) detected_rid=linux-musl-x64 ;; *) die "only ARM64 and x86_64 are supported" ;; esac
|
||||
case "$(uname -m)" in aarch64|arm64) detected_rid=linux-musl-arm64 ;; *) die "only ARM64 is supported" ;; esac
|
||||
if [ "$restoring" = true ]; then
|
||||
[ "$RID" = "$detected_rid" ] || die "saved updater configuration belongs to a different architecture"
|
||||
fi
|
||||
|
||||
+1
-6
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 发布一个节点程序:scripts/publish.sh <agent|backend|fleet-enroll> <rid>
|
||||
# 输出到 backend/artifacts/publish/<project>/<rid>/。linux-musl-x64(Alpine)和 linux-x64(Ubuntu noble,glibc)在 NativeAOT SDK 容器里构建(任何主机都可以,包括 Windows),
|
||||
# 输出到 backend/artifacts/publish/<project>/<rid>/。linux-x64(Ubuntu noble,glibc)在 NativeAOT SDK 容器里构建(任何主机都可以,包括 Windows),
|
||||
# 其他 rid 用本机的 dotnet(NativeAOT 不能交叉编译到别的操作系统,所以 win-x64 要在 Windows 上、linux-x64 要在 glibc 的 Linux 上构建)。
|
||||
set -euo pipefail
|
||||
|
||||
@@ -29,11 +29,6 @@ fi
|
||||
args=(publish "$csproj" -c Release -r "$rid" -p:PublishAot=true -p:DebugType=none -p:DebugSymbols=false "${publish_options[@]}" -o "/src/$out" --artifacts-path /artifacts -nologo -v q)
|
||||
|
||||
case "$rid" in
|
||||
linux-musl-x64)
|
||||
# SDK 镜像的版本可以比 global.json 新(rollForward = latestFeature)
|
||||
docker run --rm "${build_env[@]}" -v "$root:/src" -v moshiro-nuget:/root/.nuget -v "moshiro-artifacts-$rid:/artifacts" -w /src \
|
||||
mcr.microsoft.com/dotnet/sdk:10.0-alpine-aot dotnet "${args[@]}"
|
||||
;;
|
||||
linux-x64)
|
||||
docker run --rm "${build_env[@]}" -v "$root:/src" -v moshiro-nuget:/root/.nuget -v "moshiro-artifacts-$rid:/artifacts" -w /src mcr.microsoft.com/dotnet/sdk:10.0-noble-aot dotnet "${args[@]}"
|
||||
;;
|
||||
|
||||
@@ -80,7 +80,7 @@ esac
|
||||
OLD_TIME=str(int(time.time()) - 8 * 86400), RECENT_TIME=str(int(time.time())),
|
||||
CONTAINERS_STORAGE_CONF=str(base / 'moshiro-storage.conf'))
|
||||
subprocess.run(['bash', str(ROOT / 'ci/cleanup-build-cache.sh')], env=env, check=True, capture_output=True)
|
||||
self.assertEqual((base / 'removed').read_text(), 'rmi old\nrmi version\n')
|
||||
self.assertEqual((base / 'removed').read_text(), 'rmi localhost/moshiro-agent-binaries:linux-musl-x64\nrmi old\nrmi version\n')
|
||||
|
||||
def test_export_requires_one_unshare_session_and_cleans_up_on_copy_failure(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
@@ -122,7 +122,7 @@ esac
|
||||
self.assertEqual((base / 'output/moshiro-agent').read_bytes(), b'agent')
|
||||
self.assertEqual((base / 'output/fleet-enroll').read_bytes(), b'enroll')
|
||||
|
||||
def test_four_linux_packages_preserve_installers_and_shared_library_payloads(self):
|
||||
def test_linux_packages_preserve_installers_and_shared_library_payloads(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
base = Path(directory)
|
||||
binaries = base / 'binaries'
|
||||
@@ -130,7 +130,7 @@ esac
|
||||
for name in ('moshiro-agent', 'fleet-enroll', 'libmsquic.so.2'):
|
||||
(binaries / name).write_bytes(name.encode())
|
||||
for rid, suffix in [('linux-x64', 'amd64'), ('linux-arm64', 'arm64'),
|
||||
('linux-musl-x64', 'musl-amd64'), ('linux-musl-arm64', 'musl-arm64')]:
|
||||
('linux-musl-arm64', 'musl-arm64')]:
|
||||
with self.subTest(rid=rid):
|
||||
output = base / rid
|
||||
env = dict(os.environ, CI_COMMIT_TAG='v1.2.3')
|
||||
|
||||
@@ -34,7 +34,7 @@ export function JoinTokenCreated({ created, onClose }: { created: CreatedJoinTok
|
||||
{script.openwrt && (
|
||||
<Field
|
||||
label="OpenWrt · 原生二进制(自动更新)"
|
||||
hint="以 root 运行,支持 ARM64 和 x86_64,不需要 Docker/Podman。通过 curl 或 OpenWrt 自带的 uclient-fetch 下载,缺少依赖时自动用 opkg/apk 安装。动态 musl 正式包需 libopenssl3。自动注册属主并配置 procd;发布后自动验签、安装,失败回滚。默认限制 20 Mbps,可在 /etc/moshiro/agent.json 修改。"
|
||||
hint="以 root 运行,支持 ARM64,不需要 Docker/Podman。通过 curl 或 OpenWrt 自带的 uclient-fetch 下载,缺少依赖时自动用 opkg/apk 安装。原生包使用静态 musl 可执行文件。自动注册属主并配置 procd;发布后自动验签、安装,失败回滚。默认限制 20 Mbps,可在 /etc/moshiro/agent.json 修改。"
|
||||
>
|
||||
<CommandTabs storageKey="moshiro.openwrt_tab" initial="openwrt" tabs={[{ id: 'openwrt', label: 'OpenWrt · procd', command: script.openwrt }]} />
|
||||
</Field>
|
||||
|
||||
Reference in new issue
Block a user