fix: 复用已有NapCat设备身份

This commit is contained in:
sunlei 2026-06-15 07:37:09 +08:00
parent ebf63f892f
commit 01a7e79bd1
2 changed files with 130 additions and 1 deletions

View File

@ -129,9 +129,17 @@ export class QqbotNapcatContainerService {
}
try {
const deviceIdentity = await this.resolveRuntimeDeviceIdentity(
container,
account,
);
await this.createRemoteDockerContainer({
account,
dataDir: container.dataDir || `${this.getRootDir()}/${container.name}`,
dataDir:
deviceIdentity?.dataDir ||
container.dataDir ||
`${this.getRootDir()}/${container.name}`,
deviceIdentity,
image: container.image,
loginPassword: options.clearLoginPassword
? undefined
@ -160,6 +168,36 @@ export class QqbotNapcatContainerService {
}
}
private async resolveRuntimeDeviceIdentity(
container: QqbotNapcatContainer,
selfId: string,
): Promise<NapcatDockerDeviceOptions | undefined> {
if (
!this.deviceIdentityService ||
!container.id ||
typeof this.bindingRepository.findOne !== 'function'
) {
return undefined;
}
const binding = await this.bindingRepository.findOne({
where: {
bindStatus: 'bound',
containerId: container.id,
isDeleted: false,
isPrimary: true,
},
});
if (!binding?.accountId) return undefined;
const identity = await this.deviceIdentityService.resolveForAccount({
accountId: binding.accountId,
containerId: container.id,
selfId,
});
return toNapcatDockerDeviceOptions(identity);
}
async tryAutoLogin(
container: QqbotNapcatContainer,
options: NapcatLoginEnvOptions,

View File

@ -270,4 +270,95 @@ describe('NapCat device identity persistence', () => {
expect(createScript).toContain('--mac-address "$NAPCAT_MAC_ADDRESS"');
expect(createScript).toContain('-v "$MACHINE_ID_PATH:/etc/machine-id:ro"');
});
it('reuses persisted device identity when rebuilding an existing account container login env', async () => {
const identityRepository = createIdentityRepository();
const identityService = new NapcatDeviceIdentityService(
identityRepository as any,
createIdentityConfig(),
);
const identity = await identityService.resolveForAccount({
accountId: 'account-10001',
containerId: 'container-1',
selfId: '10001',
});
const bindingRepository = {
findOne: jest.fn().mockResolvedValue({
accountId: 'account-10001',
containerId: 'container-1',
isDeleted: false,
isPrimary: true,
}),
};
const containerRepository = {
createQueryBuilder: jest.fn(() => ({
addSelect: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
getOne: jest.fn().mockResolvedValue({
dataDir: identity.dataDir,
id: 'container-1',
image: 'mlikiowa/napcat-docker:latest',
name: 'kt-qqbot-napcat-10001',
reverseWsUrl: 'ws://127.0.0.1:48085/qqbot/onebot/reverse',
webuiPort: 6100,
webuiToken: 'token-x',
}),
where: jest.fn().mockReturnThis(),
})),
update: jest.fn(),
};
const containerService = new QqbotNapcatContainerService(
{
get: jest.fn((key: string, defaultValue?: string) => {
const values: Record<string, string> = {
QQBOT_NAPCAT_CONTAINER_MODE: 'ssh',
QQBOT_NAPCAT_CONTAINER_PREFIX: 'kt-qqbot-napcat',
QQBOT_NAPCAT_IMAGE: 'mlikiowa/napcat-docker:latest',
QQBOT_NAPCAT_ROOT: '/vol1/docker/kt-qqbot/napcat-instances',
QQBOT_NAPCAT_SSH_TARGET: 'nas',
};
return values[key] || defaultValue || '';
}),
} as any,
containerRepository as any,
bindingRepository as any,
new ToolsService(),
identityService,
) as any;
containerService.runProcess = jest
.fn()
.mockResolvedValueOnce({
stderr: '',
stdout: 'ACCOUNT=10001\nNAPCAT_QUICK_PASSWORD=old-password\n',
})
.mockResolvedValueOnce({ stderr: '', stdout: '' })
.mockResolvedValueOnce({ stderr: '', stdout: 'ACCOUNT=10001\n' });
const recreated = await containerService.ensureRuntimeLoginEnv(
{ id: 'container-1', name: 'kt-qqbot-napcat-10001' },
{
clearLoginPassword: true,
selfId: '10001',
},
);
expect(recreated).toEqual({ changed: true, ok: true });
expect(bindingRepository.findOne).toHaveBeenCalledWith({
where: {
bindStatus: 'bound',
containerId: 'container-1',
isDeleted: false,
isPrimary: true,
},
});
const createScript = containerService.runProcess.mock.calls[1][2];
expect(createScript).toContain(`NAPCAT_HOSTNAME='${identity.hostname}'`);
expect(createScript).toContain(
`NAPCAT_MAC_ADDRESS='${identity.macAddress}'`,
);
expect(createScript).toContain(`MACHINE_ID_PATH='${identity.machineIdPath}'`);
expect(createScript).toContain('--hostname "$NAPCAT_HOSTNAME"');
expect(createScript).toContain('--mac-address "$NAPCAT_MAC_ADDRESS"');
expect(createScript).toContain('-v "$MACHINE_ID_PATH:/etc/machine-id:ro"');
});
});