feat: install nodes and the backend from a static compose file; remove dead code [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
Agent, Probe and Backend now start from one static compose.yaml plus a local
.env: the panel (or the release assets, for the very first Backend) provides
the file, the panel's command writes the public values into .env, checks the
file's SHA-256 and runs compose up. Updating is compose pull && compose up -d.
The image is chosen by the CI channel (:latest, or MOSHIRO_VERSION in .env),
not by the panel; the previous one-line script stays as a fallback.
- packaging/compose/{agent,probe,backend}/compose.yaml are the single source,
embedded in the Backend and served at /install/compose/{file}; the root
compose.yaml moved there. Releases also carry them and list them in the
signed SHA256SUMS.
- The container entrypoint defaults the node name to the host name.
- Join token creation returns a compose block (.env, url, hash, per-platform
commands); the admin dialog leads with it.
- Remove code with no references (HotLayer members, LiveHub.SubscriberCount,
QueryService.ParseParams, ReleaseService.Forget, a few constants) and reduce
the install-code lookup to the role it is for.
This commit is contained in:
1 parent
e014a19554
commit
12be44645f
31 files changed
+492
-176
No files matched your search
@@ -48,6 +48,78 @@ public sealed class InstallTests
|
||||
Assert.DoesNotContain("<", linux + macos + windows, StringComparison.Ordinal); // 没有要用户自己替换的占位符
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("agent")]
|
||||
[InlineData("probe")]
|
||||
public async Task ComposeInstall_WritesThePublicValuesToAnEnvFile_AndChecksTheComposeHash(string role)
|
||||
{
|
||||
JsonNode created = await CreateAsync(role);
|
||||
string code = created["plaintext"]!.GetValue<string>();
|
||||
JsonNode compose = created["compose"]!;
|
||||
string env = compose["env"]!.GetValue<string>();
|
||||
string url = compose["url"]!.GetValue<string>();
|
||||
string linux = compose["linux"]!.GetValue<string>();
|
||||
|
||||
Assert.EndsWith($"/install/compose/{role}.yaml", url, StringComparison.Ordinal);
|
||||
Assert.Contains($"MOSHIRO_JOIN={code}", env, StringComparison.Ordinal);
|
||||
Assert.Contains("MOSHIRO_SERVER=", env, StringComparison.Ordinal);
|
||||
Assert.Contains($"/moshiro-{role}", env, StringComparison.Ordinal); // MOSHIRO_IMAGE:仓库和命名空间来自后端的配置,不是写死的
|
||||
Assert.StartsWith($"mkdir -p moshiro-{role} && cd moshiro-{role} && cat > .env <<'EOF'\n", linux, StringComparison.Ordinal);
|
||||
Assert.Contains(env, linux, StringComparison.Ordinal);
|
||||
Assert.Contains("chmod 600 .env", linux, StringComparison.Ordinal);
|
||||
Assert.Contains($"echo \"{compose["sha256"]!.GetValue<string>()} compose.yaml\" | sha256sum -c -", linux, StringComparison.Ordinal);
|
||||
Assert.EndsWith("podman compose up -d", linux, StringComparison.Ordinal);
|
||||
|
||||
// 面板提供的就是命令里核对的那份
|
||||
using HttpResponseMessage served = await _backend.Anonymous.GetAsync($"/install/compose/{role}.yaml");
|
||||
Assert.Equal(HttpStatusCode.OK, served.StatusCode);
|
||||
Assert.Equal("text/yaml", served.Content.Headers.ContentType?.MediaType);
|
||||
byte[] body = await served.Content.ReadAsByteArrayAsync();
|
||||
Assert.Equal(compose["sha256"]!.GetValue<string>(), Convert.ToHexStringLower(System.Security.Cryptography.SHA256.HashData(body)));
|
||||
|
||||
if (role == "probe")
|
||||
{
|
||||
// Docker Desktop 没有宿主网络:macOS 和 Windows 的 .env 里改成 bridge
|
||||
string macos = compose["macos"]!.GetValue<string>();
|
||||
string windows = compose["windows"]!.GetValue<string>();
|
||||
Assert.Contains("MOSHIRO_NETWORK_MODE=bridge", macos + windows, StringComparison.Ordinal);
|
||||
Assert.Contains("docker compose up -d", macos, StringComparison.Ordinal);
|
||||
Assert.Contains("Get-FileHash compose.yaml", windows, StringComparison.Ordinal);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Agent 需要 Linux 的宿主网络
|
||||
Assert.Equal(string.Empty, compose["macos"]!.GetValue<string>());
|
||||
Assert.Equal(string.Empty, compose["windows"]!.GetValue<string>());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("agent.yaml")]
|
||||
[InlineData("probe.yaml")]
|
||||
[InlineData("backend.yaml")]
|
||||
public async Task TheComposeFiles_AreStatic_PinNothingToABuild_AndKeepTheNodeHardening(string file)
|
||||
{
|
||||
string body = await _backend.Anonymous.GetStringAsync("/install/compose/" + file);
|
||||
|
||||
Assert.Contains("${MOSHIRO_VERSION:-latest}", body, StringComparison.Ordinal); // 更新就是 pull 加 up,想固定版本在 .env 里写
|
||||
Assert.Contains("read_only: true", body, StringComparison.Ordinal);
|
||||
Assert.Contains("no-new-privileges:true", body, StringComparison.Ordinal);
|
||||
Assert.Contains("- ALL", body, StringComparison.Ordinal); // cap_drop
|
||||
Assert.DoesNotContain("jt_", body, StringComparison.Ordinal); // 没有任何一次性的值
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("evil.yaml")]
|
||||
[InlineData("agent")]
|
||||
[InlineData("..%2Finstall.sh")]
|
||||
public async Task OnlyTheThreeComposeFiles_AreServed(string file)
|
||||
{
|
||||
using HttpResponseMessage response = await _backend.Anonymous.GetAsync("/install/compose/" + file);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TheScripts_AreStatic_TheSameBytesForEveryone_AndNeedNoCode()
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed partial class OpenApiConformanceTests
|
||||
foreach ((string path, _) in doc["paths"]!.AsObject())
|
||||
{
|
||||
string[] prefixes = ["/api/public/", "/api/admin/", "/api/agent/v1/"];
|
||||
string[] roots = ["/healthz", "/metrics", "/install", "/install.ps1", "/install/manifest", "/install/manifest.sig", "/api/release/offer"];
|
||||
string[] roots = ["/healthz", "/metrics", "/install", "/install.ps1", "/install/manifest", "/install/manifest.sig", "/install/compose/{file}", "/api/release/offer"];
|
||||
Assert.True(prefixes.Any(p => path.StartsWith(p, StringComparison.Ordinal)) || roots.Contains(path), $"{path} is outside the documented layers");
|
||||
foreach (string segment in path.Split('/', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user