release: harden plugin deployment and recovery
This commit is contained in:
@@ -24,12 +24,14 @@ PLUGIN_CONFIG_KEY=local-development-secret-at-least-32-chars \
|
||||
go run .
|
||||
```
|
||||
|
||||
`PLUGIN_ALLOW_UNSIGNED=true` is a development-only switch. Production
|
||||
packages must contain `signature.json`, use Ed25519, and match a trusted key
|
||||
from `PLUGIN_TRUSTED_PUBLISHERS` (a JSON object of key ID to base64 public
|
||||
key). `PLUGIN_CONFIG_KEY` is required in every environment; use a randomly
|
||||
generated secret in production and keep it stable across restarts so encrypted
|
||||
plugin configuration remains decryptable.
|
||||
`PLUGIN_ALLOW_UNSIGNED=true` is a development-only switch for business package
|
||||
uploads. Production `.s2plugin` packages must contain `signature.json`, use
|
||||
Ed25519, and match a trusted key from `PLUGIN_TRUSTED_PUBLISHERS` (a JSON
|
||||
object of key ID to base64 public key). Plugin Admin itself is deployed as a
|
||||
pinned source checkout and binary, not as a `.s2plugin` package. `PLUGIN_CONFIG_KEY`
|
||||
is required in every environment; use a randomly generated secret in production
|
||||
and keep it stable across restarts so encrypted plugin configuration remains
|
||||
decryptable.
|
||||
|
||||
When the optional subscription module is enabled, set `PLUGIN_PUBLIC_URL` to
|
||||
the externally reachable Plugin Admin base URL (without `/admin`), for example
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema_version": 1,
|
||||
"plugin_id": "qiu.plugin-admin",
|
||||
"name": "Business Plugin Control Plane",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"core_api_baseline": "sub2api-0.1.183",
|
||||
"tested_core_versions": ["0.1.183"],
|
||||
"capabilities": ["plugin.admin.v1"],
|
||||
|
||||
@@ -44,7 +44,7 @@ import (
|
||||
const (
|
||||
pluginID = "qiu.plugin-admin"
|
||||
subscriptionPluginID = "qiu.subscription-admin"
|
||||
pluginVersion = "1.1.0"
|
||||
pluginVersion = "1.1.1"
|
||||
sessionCookieName = "plugin_admin_session"
|
||||
maxJSONBytes = 2 << 20
|
||||
maxPackageBytes = 128 << 20
|
||||
@@ -2668,18 +2668,25 @@ func (a *app) startRevision(p *pluginRecord, revisionID, processKey string) (str
|
||||
probe.Endpoint = endpoint
|
||||
var probeErr error
|
||||
attempts := 1
|
||||
probeDelay := 50 * time.Millisecond
|
||||
var startupDeadline time.Time
|
||||
if pluginManifest.Backend.Command != "" {
|
||||
// A freshly spawned process can need a short interval before binding its
|
||||
// port. Retry the probe within the bounded startup window.
|
||||
attempts = 20
|
||||
// A freshly spawned interpreter-backed process can need several seconds
|
||||
// before binding its port (notably on macOS under concurrent test load).
|
||||
// Keep the window bounded while avoiding a fixed one-second flake.
|
||||
attempts = 100
|
||||
startupDeadline = time.Now().Add(5 * time.Second)
|
||||
}
|
||||
for attempt := 0; attempt < attempts; attempt++ {
|
||||
if !startupDeadline.IsZero() && time.Now().After(startupDeadline) {
|
||||
break
|
||||
}
|
||||
probeErr = a.checkPluginHealth(&probe)
|
||||
if probeErr == nil {
|
||||
break
|
||||
}
|
||||
if attempt+1 < attempts {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if attempt+1 < attempts && (startupDeadline.IsZero() || time.Now().Add(probeDelay).Before(startupDeadline)) {
|
||||
time.Sleep(probeDelay)
|
||||
}
|
||||
}
|
||||
if probeErr != nil {
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@awaioi/plugin-admin-ui",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@awaioi/plugin-admin-ui",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
"echarts": "^6.1.0",
|
||||
"pinia": "^3.0.4",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@awaioi/plugin-admin-ui",
|
||||
"private": true,
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1 --port 3002",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</t-menu>
|
||||
<div class="aside-footer">
|
||||
<t-tag theme="success" variant="light" class="status-tag"><CheckCircleIcon size="14px" /> Core 会话正常</t-tag>
|
||||
<span class="version-text">Plugin Admin v1.1.0</span>
|
||||
<span class="version-text">Plugin Admin v1.1.1</span>
|
||||
</div>
|
||||
</t-aside>
|
||||
<t-layout class="main-layout">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@ PLUGIN_PORT=8091 \
|
||||
go run .
|
||||
```
|
||||
|
||||
本地后端可打开 `http://127.0.0.1:8091/healthz` 进行服务契约调试;生产浏览器入口应从 Plugin Admin 的订阅模块路由进入。生产环境应通过 HTTPS 反向代理,并设置 `PLUGIN_COOKIE_SECURE=true`。默认情况下本服务只暴露健康检查、就绪检查和交接页,不暴露第二套登录、Cookie 或 Core 数据 API。`PLUGIN_STANDALONE_AUTH=true` 仅在 `PLUGIN_ENV=development` 且监听地址为 loopback 时生效,生产必须保持关闭。
|
||||
本地后端可打开 `http://127.0.0.1:8091/healthz` 进行服务契约调试;生产浏览器入口应从 Plugin Admin 的订阅模块路由进入。生产环境应通过 HTTPS 反向代理,并设置 `PLUGIN_COOKIE_SECURE=true`。默认情况下本服务只暴露健康检查、就绪检查和交接页,不暴露第二套登录、Cookie 或 Core 数据 API。`PLUGIN_STANDALONE_AUTH=true` 仅在 `PLUGIN_ENV=development` 且监听地址为 loopback 时生效;该开发兼容模式会创建模块 Cookie,生产必须保持关闭。
|
||||
|
||||
## V1 范围
|
||||
|
||||
@@ -78,8 +78,9 @@ node --check ui/app.js
|
||||
```
|
||||
|
||||
脚本生成 `dist/qiu.subscription-admin.s2plugin`,包内根文件名为
|
||||
`manifest.json`,并包含清单声明哈希的 UI 文件。该模块采用外部服务模式:
|
||||
安装后先独立启动 `subscription-admin`,再在 `plugin-admin` 的配置中填写
|
||||
`manifest.json`,并包含清单声明哈希的 UI 文件。该模块采用外部服务模式;
|
||||
归档只携带清单和 UI 资源,不会替部署者启动后端。安装后先独立启动
|
||||
`subscription-admin`(或由 systemd 持续运行),再在 `plugin-admin` 的配置中填写
|
||||
`service_url`(插件 loopback 地址),并为 Plugin Admin 设置
|
||||
`PLUGIN_PUBLIC_URL`(例如 `https://CORE_ORIGIN/extensions/qiu.plugin-admin`)。
|
||||
然后执行启用、健康检查和菜单应用。浏览器前端由 `plugin-admin` 统一挂载并共享
|
||||
|
||||
Reference in New Issue
Block a user