1111 * - During plugin startup, if the live config has an empty appId or
1212 * secret, the gateway consults the backup and restores the values
1313 * via the config mutation API.
14- * - Legacy JSON backups are imported on first read, then removed after
15- * SQLite has the canonical copy .
14+ * - Legacy JSON backups are imported by `openclaw doctor --fix`, not by
15+ * runtime startup .
1616 *
1717 * Safety notes:
1818 * - Only restore when credentials are **actually empty** — never
2121 * precisely when appId is unknown.
2222 */
2323
24- import fs from "node:fs" ;
25- import path from "node:path" ;
26- import { loadJsonFile } from "openclaw/plugin-sdk/json-store" ;
27- import { getCredentialBackupFile , getLegacyCredentialBackupFile } from "../utils/data-paths.js" ;
28- import { getQQBotDataPath } from "../utils/platform.js" ;
2924import { buildQQBotStateKey , openQQBotSyncKeyedStore } from "../utils/sqlite-state.js" ;
3025
3126interface CredentialBackup {
@@ -35,8 +30,8 @@ interface CredentialBackup {
3530 savedAt : string ;
3631}
3732
38- const CREDENTIAL_BACKUPS_NAMESPACE = "credential-backups" ;
39- const MAX_CREDENTIAL_BACKUPS = 1000 ;
33+ export const CREDENTIAL_BACKUPS_NAMESPACE = "credential-backups" ;
34+ export const MAX_CREDENTIAL_BACKUPS = 1000 ;
4035
4136function createCredentialBackupStore ( ) {
4237 return openQQBotSyncKeyedStore < CredentialBackup > ( {
@@ -45,77 +40,27 @@ function createCredentialBackupStore() {
4540 } ) ;
4641}
4742
48- function safeName ( id : string ) : string {
49- return id . replace ( / [ ^ a - z A - Z 0 - 9 . _ - ] / g, "_" ) ;
50- }
51-
52- function getLegacyOsHomeCredentialBackupFile ( accountId : string ) : string {
53- return path . join ( getQQBotDataPath ( "data" ) , `credential-backup-${ safeName ( accountId ) } .json` ) ;
54- }
55-
56- function getLegacyOsHomeCredentialBackupFileWithoutAccount ( ) : string {
57- return path . join ( getQQBotDataPath ( "data" ) , "credential-backup.json" ) ;
58- }
59-
60- function credentialBackupKey ( accountId : string ) : string {
43+ export function credentialBackupKey ( accountId : string ) : string {
6144 return buildQQBotStateKey ( "credential-backup" , accountId ) ;
6245}
6346
6447function isUsableBackup ( data : CredentialBackup | null | undefined ) : data is CredentialBackup {
6548 return Boolean ( data ?. accountId && data . appId && data . clientSecret ) ;
6649}
6750
68- function loadUsableBackupFromFile ( filePath : string ) : CredentialBackup | null {
69- const data = loadJsonFile < CredentialBackup > ( filePath ) ;
70- return isUsableBackup ( data ) ? data : null ;
71- }
72-
73- function removeFileQuietly ( filePath : string ) : void {
74- try {
75- fs . unlinkSync ( filePath ) ;
76- } catch {
77- /* ignore cleanup errors */
78- }
79- }
80-
81- function findLegacyBackup ( accountId ?: string ) : { data : CredentialBackup ; filePath : string } | null {
82- const candidates = accountId
83- ? [
84- getCredentialBackupFile ( accountId ) ,
85- getLegacyCredentialBackupFile ( ) ,
86- getLegacyOsHomeCredentialBackupFile ( accountId ) ,
87- getLegacyOsHomeCredentialBackupFileWithoutAccount ( ) ,
88- ]
89- : [ getLegacyCredentialBackupFile ( ) , getLegacyOsHomeCredentialBackupFileWithoutAccount ( ) ] ;
90-
91- for ( const filePath of candidates ) {
92- const data = loadUsableBackupFromFile ( filePath ) ;
93- if ( ! data ) {
94- continue ;
95- }
96- if ( accountId && data . accountId !== accountId ) {
97- continue ;
98- }
99- return { data, filePath } ;
100- }
101- return null ;
102- }
103-
10451/** Persist a credential snapshot (called once gateway reaches READY). */
10552export function saveCredentialBackup ( accountId : string , appId : string , clientSecret : string ) : void {
10653 if ( ! appId || ! clientSecret ) {
10754 return ;
10855 }
10956 try {
110- const backupPath = getCredentialBackupFile ( accountId ) ;
11157 const data : CredentialBackup = {
11258 accountId,
11359 appId,
11460 clientSecret,
11561 savedAt : new Date ( ) . toISOString ( ) ,
11662 } ;
11763 createCredentialBackupStore ( ) . register ( credentialBackupKey ( accountId ) , data ) ;
118- removeFileQuietly ( backupPath ) ;
11964 } catch {
12065 /* best-effort — ignore */
12166 }
@@ -124,8 +69,8 @@ export function saveCredentialBackup(accountId: string, appId: string, clientSec
12469/**
12570 * Load a credential snapshot for `accountId`.
12671 *
127- * Consults SQLite first; falls back to shipped JSON backups and imports
128- * them when the embedded `accountId` matches the request .
72+ * Reads SQLite only. Legacy JSON backup import is owned by doctor/setup
73+ * migration so runtime startup stays canonical-state-only .
12974 */
13075export function loadCredentialBackup ( accountId ?: string ) : CredentialBackup | null {
13176 try {
@@ -136,16 +81,6 @@ export function loadCredentialBackup(accountId?: string): CredentialBackup | nul
13681 return data ;
13782 }
13883 }
139-
140- const legacy = findLegacyBackup ( accountId ) ;
141- if ( legacy ) {
142- createCredentialBackupStore ( ) . register (
143- credentialBackupKey ( legacy . data . accountId ) ,
144- legacy . data ,
145- ) ;
146- removeFileQuietly ( legacy . filePath ) ;
147- return legacy . data ;
148- }
14984 } catch {
15085 /* corrupt file — ignore */
15186 }
0 commit comments