feat: report Docker image source in system info

This commit is contained in:
tianma-if committed 2026-08-30 18:46:50 +08:00
1 parent 17c8181f85
commit 10b50829ee
16 files changed
+117 -2

No files matched your search

+1
View File
@@ -7,6 +7,7 @@ export type Bindings = {
storage: StorageAdapter;
EDGE_EVER_AUTH_USERNAME?: string;
EDGE_EVER_RUNTIME?: string;
EDGE_EVER_CONTAINER_IMAGE?: string;
EDGE_EVER_AUTH_PASSWORD?: string;
EDGE_EVER_AUTH_PASSWORD_HASH?: string;
EDGE_EVER_SESSION_TTL_DAYS?: string;
@@ -0,0 +1,18 @@
import { describe, expect, test } from "bun:test";
import { resolveContainerImageSource } from "./container-image-source";
describe("container image source diagnostics", () => {
test("recognizes official image repositories with tags or digests", () => {
expect(resolveContainerImageSource("ghcr.io/tianma-if/edgeever")).toBe("official-ghcr");
expect(resolveContainerImageSource("ghcr.io/tianma-if/edgeever:v1.2.3")).toBe("official-ghcr");
expect(resolveContainerImageSource("GHCR.IO/TIANMA-IF/EDGEEVER@sha256:abc")).toBe("official-ghcr");
expect(resolveContainerImageSource("ccr.ccs.tencentyun.com/edgeever/edgeever:latest")).toBe("official-cn-mirror");
});
test("does not expose custom image references through the classification", () => {
expect(resolveContainerImageSource("registry.internal.example/team/edgeever:latest")).toBe("custom");
expect(resolveContainerImageSource("ghcr.io/tianma-if/edgeever-fork")).toBe("custom");
expect(resolveContainerImageSource(" ")).toBe("unknown");
expect(resolveContainerImageSource(undefined)).toBe("unknown");
});
});
+23
View File
@@ -0,0 +1,23 @@
export type ContainerImageSource =
| "official-ghcr"
| "official-cn-mirror"
| "custom"
| "unknown";
const OFFICIAL_GHCR_IMAGE = "ghcr.io/tianma-if/edgeever";
const OFFICIAL_CN_MIRROR_IMAGE = "ccr.ccs.tencentyun.com/edgeever/edgeever";
const matchesImageRepository = (reference: string, repository: string) =>
reference === repository
|| reference.startsWith(`${repository}:`)
|| reference.startsWith(`${repository}@`);
export const resolveContainerImageSource = (
imageReference: string | null | undefined,
): ContainerImageSource => {
const normalized = imageReference?.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/+$/, "") ?? "";
if (!normalized) return "unknown";
if (matchesImageRepository(normalized, OFFICIAL_GHCR_IMAGE)) return "official-ghcr";
if (matchesImageRepository(normalized, OFFICIAL_CN_MIRROR_IMAGE)) return "official-cn-mirror";
return "custom";
};
+7 -1
View File
@@ -6,6 +6,7 @@ import {
} from "@edgeever/shared";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { resolveContainerImageSource } from "./container-image-source";
import openApiSpec from "../../../docs/openapi.json";
import releaseSummary from "../../../release-summary.json";
import {
@@ -198,10 +199,15 @@ app.get("/api/health", async (c) => {
);
}
const runtime = c.env.EDGE_EVER_RUNTIME ?? "cloudflare-workers";
return c.json({
ok: true,
name: "edgeever",
runtime: c.env.EDGE_EVER_RUNTIME ?? "cloudflare-workers",
runtime,
...(runtime === "self-hosted-bun"
? { containerImageSource: resolveContainerImageSource(c.env.EDGE_EVER_CONTAINER_IMAGE) }
: {}),
authMode,
build: INSTANCE_BUILD_ID.slice(0, 12),
migration: await getAppliedMigration(c.env),
@@ -38,6 +38,7 @@ struct InstanceHealth: Codable, Equatable, Sendable {
var ok: Bool
var name: String
var runtime: String?
var containerImageSource: String?
var authMode: String?
var build: String?
var migration: String?
@@ -552,6 +552,12 @@ struct SettingsView: View {
env.preferences.t("部署平台", en: "Deployment platform"),
deploymentPlatformLabel(instanceHealth?.runtime)
))
if instanceHealth?.runtime == "self-hosted-bun" {
items.append((
env.preferences.t("容器镜像来源", en: "Container image source"),
containerImageSourceLabel(instanceHealth?.containerImageSource)
))
}
return items
}
@@ -606,6 +612,15 @@ struct SettingsView: View {
}
}
private func containerImageSourceLabel(_ source: String?) -> String {
switch source {
case "official-ghcr": return "GitHub Container Registry (GHCR)"
case "official-cn-mirror": return env.preferences.t("中国大陆官方镜像", en: "Official mainland China mirror")
case "custom": return env.preferences.t("自定义镜像", en: "Custom image")
default: return unknownSystemInfoValue
}
}
private var systemInfoText: String {
[
systemInfoTextSection(env.preferences.t("云端实例", en: "Cloud instance"), items: cloudSystemInfoItems),
@@ -488,6 +488,7 @@ const getMobileSystemInfoText = (localePreference: MobileLocaleMode) =>
connectionDescription: "Connection and local sync queue status.",
connectionFailed: "Connection failed",
connectionSection: "Connection and sync",
containerImageSource: "Container image source",
databaseBackend: "Database backend",
databaseVersion: "Database version",
description: "View cloud instance and current client diagnostics.",
@@ -527,6 +528,7 @@ const getMobileSystemInfoText = (localePreference: MobileLocaleMode) =>
connectionDescription: "实例连接与本地同步队列状态。",
connectionFailed: "连接失败",
connectionSection: "连接与同步",
containerImageSource: "容器镜像来源",
databaseBackend: "数据库后端",
databaseVersion: "数据库版本",
description: "查看云端实例与当前客户端的诊断信息。",
@@ -561,6 +563,15 @@ const getMobileDeploymentPlatform = (runtime: string | null | undefined, english
return english ? "Unknown" : "未知";
};
const getMobileContainerImageSource = (source: string | null | undefined, english: boolean) => {
switch (source) {
case "official-ghcr": return "GitHub Container Registry (GHCR)";
case "official-cn-mirror": return english ? "Official mainland China mirror" : "中国大陆官方镜像";
case "custom": return english ? "Custom image" : "自定义镜像";
default: return english ? "Unknown" : "未知";
}
};
const getMobileDatabaseBackend = (backend: string | null | undefined, unknown: string) => {
if (backend === "d1") return "D1";
if (backend === "sqlite") return "SQLite";
@@ -627,6 +638,9 @@ const getMobileSystemInfoGroups = (
? [{ label: copy.existingAttachments, value: copy.existingAttachmentsOriginalStorage }]
: []),
{ label: copy.deploymentPlatform, value: getMobileDeploymentPlatform(instance?.health.runtime, english) },
...(instance?.health.runtime === "self-hosted-bun"
? [{ label: copy.containerImageSource, value: getMobileContainerImageSource(instance.health.containerImageSource, english) }]
: []),
],
title: copy.cloudSection,
},
+1
View File
@@ -8,6 +8,7 @@ services:
environment:
EDGE_EVER_AUTH_USERNAME: "${EDGE_EVER_AUTH_USERNAME:-admin}"
EDGE_EVER_AUTH_PASSWORD: "${EDGE_EVER_AUTH_PASSWORD:?Set EDGE_EVER_AUTH_PASSWORD before starting EdgeEver}"
EDGE_EVER_CONTAINER_IMAGE: "${EDGE_EVER_IMAGE:-ghcr.io/tianma-if/edgeever}"
volumes:
- edgeever-data:/data
security_opt:
@@ -25,7 +25,7 @@ export type SystemInfoItem = {
status?: "connected" | "connecting" | "failed" | "warning" | "error" | "default";
};
type InstanceSystemDiagnostics = Pick<InstanceHealth, "build" | "migration" | "objectStorageProvider" | "storage"> & {
type InstanceSystemDiagnostics = Pick<InstanceHealth, "build" | "containerImageSource" | "migration" | "objectStorageProvider" | "storage"> & {
runtime?: string | null;
};
@@ -62,6 +62,11 @@ const getDeploymentDescription = (t: (key: string) => string) => {
return `${trigger} · ${method}`;
};
const getContainerImageSourceTranslationKey = (source: string | null | undefined) => {
if (source === "official-ghcr" || source === "official-cn-mirror" || source === "custom") return source;
return "unknown";
};
const getColSpanClass = (colSpan?: SystemInfoItem["colSpan"]) => {
if (colSpan === "full") return "sm:col-span-2 lg:col-span-3";
if (colSpan === "two") return "sm:col-span-2 lg:col-span-2";
@@ -129,6 +134,12 @@ const getWebSystemInfoGroups = (
label: t("systemInfo.deploymentPlatform"),
value: t(`systemInfo.deploymentPlatforms.${resolveDeploymentPlatform(diagnostics.instance?.runtime)}`),
},
...(diagnostics.instance?.runtime === "self-hosted-bun"
? [{
label: t("systemInfo.containerImageSource"),
value: t(`systemInfo.containerImageSources.${getContainerImageSourceTranslationKey(diagnostics.instance.containerImageSource)}`),
}]
: []),
{ label: t("systemInfo.deployment"), value: getDeploymentDescription(t), colSpan: "full" },
],
},
+1
View File
@@ -8,6 +8,7 @@ services:
environment:
EDGE_EVER_AUTH_USERNAME: "${EDGE_EVER_AUTH_USERNAME:-admin}"
EDGE_EVER_AUTH_PASSWORD: "${EDGE_EVER_AUTH_PASSWORD:?Set EDGE_EVER_AUTH_PASSWORD before starting EdgeEver}"
EDGE_EVER_CONTAINER_IMAGE: "${EDGE_EVER_IMAGE:-ghcr.io/tianma-if/edgeever}"
volumes:
- edgeever-data:/data
security_opt:
+5
View File
@@ -40,6 +40,11 @@
"runtime": {
"type": "string"
},
"containerImageSource": {
"type": "string",
"enum": ["official-ghcr", "official-cn-mirror", "custom", "unknown"],
"description": "Current container image repository classification for Docker deployments. Custom repository addresses are not exposed."
},
"authMode": {
"type": "string",
"enum": ["required", "disabled"]
+1
View File
@@ -57,6 +57,7 @@ export type InstanceHealth = {
ok: true;
name: string;
runtime?: string | null;
containerImageSource?: "official-ghcr" | "official-cn-mirror" | "custom" | "unknown" | string | null;
authMode?: string | null;
build?: string | null;
migration?: string | null;
+7
View File
@@ -668,6 +668,13 @@ export const enUS = {
docker: "Docker",
unknown: "Unknown",
},
containerImageSource: "Container image source",
containerImageSources: {
"official-ghcr": "GitHub Container Registry (GHCR)",
"official-cn-mirror": "Official mainland China mirror",
custom: "Custom image",
unknown: "Unknown",
},
deployment: "Deployment",
deploymentTriggers: {
github_release: "GitHub Release trigger",
+7
View File
@@ -668,6 +668,13 @@ export const zhCN = {
docker: "Docker",
unknown: "未知",
},
containerImageSource: "容器镜像来源",
containerImageSources: {
"official-ghcr": "GitHub Container Registry(GHCR)",
"official-cn-mirror": "中国大陆官方镜像",
custom: "自定义镜像",
unknown: "未知",
},
deployment: "自动部署方式",
deploymentTriggers: {
github_release: "正式 Release 触发",
+3
View File
@@ -80,6 +80,9 @@ describe("Docker release contract", () => {
expect(compose).toContain(
'EDGE_EVER_AUTH_PASSWORD: "${EDGE_EVER_AUTH_PASSWORD:?',
);
expect(compose).toContain(
'EDGE_EVER_CONTAINER_IMAGE: "${EDGE_EVER_IMAGE:-ghcr.io/tianma-if/edgeever}"',
);
expect(compose).toContain("edgeever-data:/data");
expect(compose).toContain("no-new-privileges:true");
});
+1
View File
@@ -77,6 +77,7 @@ const env = {
storage,
EDGE_EVER_AUTH_USERNAME: runtimeEnvironment.EDGE_EVER_AUTH_USERNAME ?? "admin",
EDGE_EVER_RUNTIME: "self-hosted-bun",
EDGE_EVER_CONTAINER_IMAGE: runtimeEnvironment.EDGE_EVER_CONTAINER_IMAGE,
EDGE_EVER_AUTH_PASSWORD: runtimeEnvironment.EDGE_EVER_AUTH_PASSWORD,
EDGE_EVER_AUTH_PASSWORD_HASH: runtimeEnvironment.EDGE_EVER_AUTH_PASSWORD_HASH,
EDGE_EVER_SESSION_TTL_DAYS: runtimeEnvironment.EDGE_EVER_SESSION_TTL_DAYS ?? "400",