diff --git a/CHANGELOG.md b/CHANGELOG.md index 2af1e89..f4ce1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Lumi changelog +## 0.2.4 + +- Changed the production diagnostics endpoint, cURL example, and local-client configuration example to use the actual URL from which the administrator opened Lumi, including trusted reverse-proxy HTTPS handling. + ## 0.2.3 - Fixed shared form action resolution so submit buttons without a `formaction` use their parent form's endpoint, restoring timed confirmations such as diagnostics-key creation. diff --git a/TODO.md b/TODO.md index ccded82..c798c61 100644 --- a/TODO.md +++ b/TODO.md @@ -677,6 +677,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no ## Done +- 2026-07-18: Updated production diagnostics examples in core 0.2.4 to derive the full endpoint and client base URL from the administrator's current Lumi request instead of showing a placeholder hostname. - 2026-07-18: Fixed the shared submit-action resolver in core 0.2.3: ordinary buttons now inherit their parent form endpoint unless they explicitly declare `formaction`, restoring timed diagnostics-key creation and preventing async update actions from posting back to the Updates page. - 2026-07-18: Added production-stage plugin update diagnostics in core 0.2.2: selected plugin source is verified before snapshotting, failures record their exact stage and target in update state, and the affected plugin row displays the server error directly. - 2026-07-18: Added opt-in production diagnostics in core 0.2.2: administrators can issue or revoke a one-time access key for an HTTPS-only, rate-limited endpoint with five fixed read-only checks, recursive secret/path redaction, and audited access; no arbitrary command, SQL, file, URL, or write capability is exposed. diff --git a/knowledge/core/lumi-core.md b/knowledge/core/lumi-core.md index 42e0d20..84f9c7e 100644 --- a/knowledge/core/lumi-core.md +++ b/knowledge/core/lumi-core.md @@ -14,7 +14,7 @@ editable: false Lumi is the core web UI and bot runtime. ## Runtime Package: lumi-bot -Version: 0.2.3 +Version: 0.2.4 ## Routes - GET /api/events - POST /api/destructive-confirmations diff --git a/package-lock.json b/package-lock.json index 8d2115d..52a62cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lumi-bot", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lumi-bot", - "version": "0.2.3", + "version": "0.2.4", "dependencies": { "adm-zip": "^0.5.12", "better-sqlite3": "^11.5.0", diff --git a/package.json b/package.json index cd3cd1a..1464940 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lumi-bot", - "version": "0.2.3", + "version": "0.2.4", "private": true, "type": "commonjs", "scripts": { diff --git a/release-index.json b/release-index.json index b30e37a..b838ddd 100644 --- a/release-index.json +++ b/release-index.json @@ -2,6 +2,36 @@ "schema_version": 1, "channel": "stable", "releases": [ + { + "version": "0.2.4", + "ref": "refs/tags/v0.2.4", + "released_at": "2026-07-18", + "installable": true, + "rollback_safe": true, + "replaces_versions": [ + "1.2.0" + ], + "data_policy": "preserve", + "dependency_policy": "sync_on_restart", + "migration_notes": "Uses the administrator's current request origin for production diagnostics endpoint and local-client examples.", + "plugins": { + "auto-vc": "0.1.6", + "birthday": "0.1.3", + "economy-framework": "0.2.10", + "economy-games": "0.1.7", + "expression-interaction": "0.2.1", + "lumi_ai": "0.8.2", + "moderation": "0.1.5", + "okf": "0.1.1", + "quotes": "0.1.2", + "sample-plugin": "0.1.0", + "throne_wishlist": "0.1.2", + "welcome_messages": "0.1.1" + }, + "tools": { + "lumi_ai_web_search": "0.1.1" + } + }, { "version": "0.2.3", "ref": "refs/tags/v0.2.3", diff --git a/scripts/build-core-repair-patch.js b/scripts/build-core-repair-patch.js index 587e25b..1136fce 100644 --- a/scripts/build-core-repair-patch.js +++ b/scripts/build-core-repair-patch.js @@ -4,7 +4,7 @@ const path = require("path"); const AdmZip = require("adm-zip"); const root = path.join(__dirname, ".."); -const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.3-repair.zip"); +const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.4-repair.zip"); const files = [ "CHANGELOG.md", "README.md", @@ -53,10 +53,10 @@ for (const relativePath of files) { const manifest = { schema_version: 1, - name: "Lumi core 0.2.3 repair", + name: "Lumi core 0.2.4 repair", target: "core", - from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3"], - to_version: "0.2.3", + from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3", "0.2.4"], + to_version: "0.2.4", data_policy: "preserve", dependency_policy: "sync_on_restart", created_at: new Date().toISOString(), diff --git a/scripts/verify-core-repair-patch.js b/scripts/verify-core-repair-patch.js index e6c1182..b12f348 100644 --- a/scripts/verify-core-repair-patch.js +++ b/scripts/verify-core-repair-patch.js @@ -7,7 +7,7 @@ const AdmZip = require("adm-zip"); const { verifyPatchPackage } = require("../src/services/update-manager"); const root = path.join(__dirname, ".."); -const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.3-repair.zip"); +const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.4-repair.zip"); assert.equal(fs.existsSync(archivePath), true, "build the repair patch first"); const zip = new AdmZip(archivePath); const entries = zip.getEntries().filter((entry) => !entry.isDirectory); @@ -15,9 +15,9 @@ const names = new Set(entries.map((entry) => entry.entryName.replace(/\\/g, "/") assert.equal(names.has("patch-manifest.json"), true); const manifest = JSON.parse(zip.readAsText("patch-manifest.json")); assert.equal(manifest.target, "core"); -assert.equal(manifest.to_version, "0.2.3"); +assert.equal(manifest.to_version, "0.2.4"); assert.equal(manifest.data_policy, "preserve"); -assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3"]); +assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1", "0.2.2", "0.2.3", "0.2.4"]); const forbidden = /^(?:data|plugins|node_modules|config|storage|uploads|logs|database|databases|knowledge\/(?:community|corrections))(?:\/|$)|^\.env(?:\.|$)|^\.secrets$/; for (const entry of entries) { @@ -29,7 +29,7 @@ for (const [relativePath, expected] of Object.entries(manifest.files)) { assert.equal(actual, expected, `${relativePath} checksum`); } assert.equal(Object.keys(manifest.files).length + 1, entries.length, "every repair file must be checksummed"); -assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.3"); +assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.4"); const simulation = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-repair-simulation-")); try { @@ -47,11 +47,11 @@ try { fs.writeFileSync(target, `preserve:${sentinel}`); } zip.extractAllTo(simulation, true); - assert.equal(verifyPatchPackage(simulation).to_version, "0.2.3"); + assert.equal(verifyPatchPackage(simulation).to_version, "0.2.4"); for (const sentinel of sentinels) { assert.equal(fs.readFileSync(path.join(simulation, sentinel), "utf8"), `preserve:${sentinel}`); } - assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.3"); + assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.4"); assert.equal(fs.existsSync(path.join(simulation, "src", "services", "dependency-manager.js")), true); assert.equal(fs.existsSync(path.join(simulation, "src", "services", "production-diagnostics.js")), true); assert.equal(fs.existsSync(path.join(simulation, "src", "web", "views", "admin-diagnostics.ejs")), true); diff --git a/scripts/verify-production-diagnostics.js b/scripts/verify-production-diagnostics.js index 5da9b4a..7a03ba0 100644 --- a/scripts/verify-production-diagnostics.js +++ b/scripts/verify-production-diagnostics.js @@ -79,7 +79,10 @@ const viewSource = fs.readFileSync(path.join(root, "src", "web", "views", "admin assert.match(viewSource, /no remote control/i); assert.match(viewSource, /data-confirm-mode="modal"/); assert.match(viewSource, /\.secrets/); -assert.match(viewSource, /\/api\/diagnostics\/v1\/run/); +assert.match(viewSource, /diagnosticsEndpointUrl/); +assert.match(viewSource, /diagnosticsBaseUrl/); +assert.doesNotMatch(viewSource, /https:\/\/your-lumi-host/); +assert.match(serverSource, /const diagnosticsBaseUrl = `\$\{req\.protocol\}:\/\/\$\{req\.get\("host"\)\}`/); const clientSource = fs.readFileSync(path.join(root, "scripts", "production-diagnostics-client.js"), "utf8"); assert.match(clientSource, /\.secrets["'], "production-diagnostics\.json"/); diff --git a/scripts/verify-release-metadata.js b/scripts/verify-release-metadata.js index d901e18..f62cb72 100644 --- a/scripts/verify-release-metadata.js +++ b/scripts/verify-release-metadata.js @@ -4,8 +4,8 @@ const path = require("path"); const { findSafeTarget } = require("../src/services/versioning"); const root = path.join(__dirname, ".."); -const releaseVersion = "0.2.3"; -const previousCoreVersion = "0.2.2"; +const releaseVersion = "0.2.4"; +const previousCoreVersion = "0.2.3"; const earliestCompatibleCoreVersion = "0.1.9"; const changedPlugins = { "auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" }, @@ -87,4 +87,4 @@ assert.equal(webSearch.minimum_lumi_version, "0.2.0"); assert.equal(webSearch.minimum_lumi_ai_version, changedPlugins.lumi_ai.to); assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true); -console.log("Release metadata verification passed: core 0.2.3 and 11 changed plugin/tool packages."); +console.log("Release metadata verification passed: core 0.2.4 and 11 changed plugin/tool packages."); diff --git a/scripts/verify-update-system.js b/scripts/verify-update-system.js index 948f560..92cc601 100644 --- a/scripts/verify-update-system.js +++ b/scripts/verify-update-system.js @@ -16,7 +16,7 @@ function readJson(relativePath) { const releaseIndex = readJson("release-index.json"); const releaseVersions = releaseIndex.releases.map((release) => release.version); -assert.deepEqual(releaseVersions, ["0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]); +assert.deepEqual(releaseVersions, ["0.2.4", "0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]); assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique"); for (const release of releaseIndex.releases) { assert.equal(normalizeRepositoryRef(release.ref), release.ref); @@ -35,8 +35,9 @@ for (const [toolId, version] of Object.entries(currentRelease.tools)) { } const baseTarget = { - current_version: "0.2.2", + current_version: "0.2.3", available_versions: [ + { version: "0.2.4", ref: "refs/tags/v0.2.4", rollback_safe: true }, { version: "0.2.3", ref: "refs/tags/v0.2.3", rollback_safe: true }, { version: "0.2.2", ref: "refs/tags/v0.2.2", rollback_safe: true }, { version: "0.2.1", ref: "refs/tags/v0.2.1", rollback_safe: true }, @@ -63,7 +64,7 @@ const corrected = buildStatus({ channel: "stable" }); assert.equal(corrected.version_correction, true); -assert.equal(corrected.safe_target_version, "0.2.3"); +assert.equal(corrected.safe_target_version, "0.2.4"); assert.equal(corrected.update_available, true); assert.equal(corrected.blocked, false); diff --git a/src/web/server.js b/src/web/server.js index ec2a4c0..7ade243 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -5769,7 +5769,8 @@ function createWebServer({ loadPlugins, discordClient }) { res.redirect("/admin/theming"); }); - const renderDiagnosticsAdmin = (res, values = {}) => { + const renderDiagnosticsAdmin = (req, res, values = {}) => { + const diagnosticsBaseUrl = `${req.protocol}://${req.get("host")}`; res.set("Cache-Control", "no-store"); res.render("admin-diagnostics", { title: "Production diagnostics", @@ -5779,12 +5780,14 @@ function createWebServer({ loadPlugins, discordClient }) { diagnosticResult: null, diagnosticError: null, selectedCheck: "system_health", + diagnosticsBaseUrl, + diagnosticsEndpointUrl: `${diagnosticsBaseUrl}/api/diagnostics/v1/run`, ...values }); }; - app.get("/admin/diagnostics", requireRole("admin"), (_req, res) => { - renderDiagnosticsAdmin(res); + app.get("/admin/diagnostics", requireRole("admin"), (req, res) => { + renderDiagnosticsAdmin(req, res); }); app.post("/admin/diagnostics/run", requireRole("admin"), (req, res) => { @@ -5800,7 +5803,7 @@ function createWebServer({ loadPlugins, discordClient }) { fingerprint: `admin:${req.session.user.id}`, duration_ms: Date.now() - startedAt }); - renderDiagnosticsAdmin(res, { diagnosticResult, selectedCheck: check }); + renderDiagnosticsAdmin(req, res, { diagnosticResult, selectedCheck: check }); } catch (error) { auditDiagnosticRequest({ request_id: requestId, @@ -5810,13 +5813,13 @@ function createWebServer({ loadPlugins, discordClient }) { reason: error.message, duration_ms: Date.now() - startedAt }); - renderDiagnosticsAdmin(res, { diagnosticError: error.message, selectedCheck: check }); + renderDiagnosticsAdmin(req, res, { diagnosticError: error.message, selectedCheck: check }); } }); - app.post("/admin/diagnostics/access/renew", requireRole("admin"), (_req, res) => { + app.post("/admin/diagnostics/access/renew", requireRole("admin"), (req, res) => { const issuedKey = issueDiagnosticsAccessKey(); - renderDiagnosticsAdmin(res, { issuedKey }); + renderDiagnosticsAdmin(req, res, { issuedKey }); }); app.post("/admin/diagnostics/access/revoke", requireRole("admin"), (req, res) => { diff --git a/src/web/views/admin-diagnostics.ejs b/src/web/views/admin-diagnostics.ejs index 9241dce..c05658d 100644 --- a/src/web/views/admin-diagnostics.ejs +++ b/src/web/views/admin-diagnostics.ejs @@ -16,7 +16,7 @@
Remote access<%= diagnosticsAccess.enabled ? "Enabled" : "Disabled" %>
Access key<%= diagnosticsAccess.configured ? diagnosticsAccess.key_prefix : "Not created" %>
Created<%= diagnosticsAccess.created_at ? new Date(diagnosticsAccess.created_at).toLocaleString() : "Never" %>
-
Endpoint/api/diagnostics/v1/run
+
Endpoint<%= diagnosticsEndpointUrl %>
<% if (issuedKey) { %> @@ -76,12 +76,12 @@
Connect from a trusted computer

Use the public HTTPS address for this Lumi installation. Plain HTTP is accepted only from the same machine.

-
curl -X POST "https://your-lumi-host/api/diagnostics/v1/run" \
+    
curl -X POST "<%= diagnosticsEndpointUrl %>" \
   -H "Authorization: Bearer $LUMI_DIAGNOSTICS_KEY" \
   -H "Content-Type: application/json" \
   --data '{"check":"update_state"}'

Keep the key out of shell history, tickets, chat, and logs. A local environment variable or an ignored .secrets file is safer than placing it directly in a command.

-

Repository maintainers can save { "base_url": "https://your-lumi-host", "key": "…" } in .secrets/production-diagnostics.json, then run npm run diagnostics:production -- update_state.

+

Repository maintainers can save { "base_url": "<%= diagnosticsBaseUrl %>", "key": "…" } in .secrets/production-diagnostics.json, then run npm run diagnostics:production -- update_state.

diff --git a/update-manifest.json b/update-manifest.json index 4a17664..6e73bc9 100644 --- a/update-manifest.json +++ b/update-manifest.json @@ -1,6 +1,6 @@ { "name": "Lumi Core", - "version": "0.2.3", + "version": "0.2.4", "channel": "stable", "released_at": "2026-07-18", "compatible_from": "0.1.9", @@ -8,7 +8,7 @@ "replaces_versions": [ "1.2.0" ], - "migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics, and the shared form-action fix required by timed confirmations and repository updates. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, and secrets.", + "migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics with request-aware examples, and the shared form-action fix required by timed confirmations and repository updates. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, and secrets.", "rollback_safe": true, "requirements": [ "Node.js 18 or newer" @@ -61,6 +61,18 @@ ], "rollback_safe": true, "migration_notes": "Fixes shared browser form endpoint resolution for timed confirmations and repository updates; preserved local data is not replaced." + }, + { + "version": "0.2.4", + "channel": "stable", + "released_at": "2026-07-18", + "compatible_from": "0.1.9", + "migration_kind": "patch", + "replaces_versions": [ + "1.2.0" + ], + "rollback_safe": true, + "migration_notes": "Uses the administrator's current Lumi origin in production diagnostics examples; preserved local data is not replaced." } ] }