Create a fresh safe mode instance when restarting Draupnir.

Specifically, when restart Draupnir fails, and safe mode is already
running, we will want to create a fresh safe mode instance that
contains the relevant recovery options, from the new cause of failure.
This commit is contained in:
gnuxie
2024-09-30 12:14:00 +01:00
parent b9c05fbb6b
commit 3c1cb07aa6
2 changed files with 63 additions and 18 deletions
+43 -16
View File
@@ -39,8 +39,9 @@ import {
StringUserID,
MatrixRoomID,
} from "@the-draupnir-project/matrix-basic-types";
import { Result, isError } from "@gnuxie/typescript-result";
import { Err, Ok, Result, isError } from "@gnuxie/typescript-result";
import {
DraupnirRestartError,
SafeModeToggle,
SafeModeToggleOptions,
} from "./safemode/SafeModeToggle";
@@ -70,6 +71,10 @@ interface BotModeTogle extends SafeModeToggle {
startFromScratch(
options?: SafeModeToggleOptions
): Promise<Result<Draupnir | SafeModeDraupnir>>;
maybeRecoverWithSafeMode(
error: ResultError,
options?: SafeModeToggleOptions
): Promise<Result<SafeModeDraupnir>>;
}
export class DraupnirBotModeToggle implements BotModeTogle {
@@ -168,12 +173,13 @@ export class DraupnirBotModeToggle implements BotModeTogle {
}
public async switchToDraupnir(
options?: SafeModeToggleOptions
): Promise<Result<Draupnir>> {
): Promise<Result<Draupnir, DraupnirRestartError | ResultError>> {
if (this.draupnir !== null) {
return ResultError.Result(
`There is a draupnir for ${this.clientUserID} already running`
);
}
this.stopSafeModeDraupnir();
const draupnirResult = await this.draupnirFactory.makeDraupnir(
this.clientUserID,
this.managementRoom,
@@ -181,7 +187,18 @@ export class DraupnirBotModeToggle implements BotModeTogle {
this
);
if (isError(draupnirResult)) {
return draupnirResult;
const safeModeResult = await this.maybeRecoverWithSafeMode(
draupnirResult.error,
options
);
if (isError(safeModeResult)) {
return safeModeResult;
} else {
return DraupnirRestartError.Result(
"Draupnir failed to start, switching to safe mode.",
{ safeModeDraupnir: safeModeResult.ok }
);
}
}
this.draupnir = draupnirResult.ok;
this.draupnir.start();
@@ -203,7 +220,6 @@ export class DraupnirBotModeToggle implements BotModeTogle {
}
}
}
this.stopSafeModeDraupnir();
return draupnirResult;
}
public async switchToSafeMode(
@@ -239,18 +255,8 @@ export class DraupnirBotModeToggle implements BotModeTogle {
): Promise<Result<Draupnir | SafeModeDraupnir>> {
const draupnirResult = await this.switchToDraupnir(options ?? {});
if (isError(draupnirResult)) {
if (this.config.safeMode?.bootIntoOnStartupFailure) {
log.error(
"Failed to start draupnir, switching to safe mode as configured",
draupnirResult.error
);
return await this.switchToSafeMode(
{
reason: SafeModeReason.InitializationError,
error: draupnirResult.error,
},
options ?? {}
);
if (draupnirResult.error instanceof DraupnirRestartError) {
return Ok(draupnirResult.error.safeModeDraupnir);
} else {
return draupnirResult;
}
@@ -258,6 +264,27 @@ export class DraupnirBotModeToggle implements BotModeTogle {
return draupnirResult;
}
public async maybeRecoverWithSafeMode(
error: ResultError,
options?: SafeModeToggleOptions | undefined
): Promise<Result<SafeModeDraupnir>> {
if (this.config.safeMode?.bootIntoOnStartupFailure) {
log.error(
"Failed to start draupnir, switching to safe mode as configured",
error
);
return await this.switchToSafeMode(
{
reason: SafeModeReason.InitializationError,
error: error,
},
options ?? {}
);
} else {
return Err(error);
}
}
public async encryptionInitialized(): Promise<void> {
if (this.draupnir !== null) {
try {
+20 -2
View File
@@ -2,13 +2,29 @@
//
// SPDX-License-Identifier: AFL-3.0
import { Result } from "@gnuxie/typescript-result";
import { Err, Result, ResultError } from "@gnuxie/typescript-result";
import { Draupnir } from "../Draupnir";
import { SafeModeDraupnir } from "./DraupnirSafeMode";
import { SafeModeCause } from "./SafeModeCause";
export type SafeModeToggleOptions = { sendStatusOnStart?: boolean };
export class DraupnirRestartError extends ResultError {
constructor(
message: string,
public readonly safeModeDraupnir: SafeModeDraupnir
) {
super(message);
}
public static Result(
message: string,
options: { safeModeDraupnir: SafeModeDraupnir }
): Result<never> {
return Err(new DraupnirRestartError(message, options.safeModeDraupnir));
}
}
export interface SafeModeToggle {
/**
* Switch the bot to Draupnir mode.
@@ -17,7 +33,9 @@ export interface SafeModeToggle {
* That means that by the command responds with ticks and crosses,
* draupnir will be running or we will still be in safe mode.
*/
switchToDraupnir(options?: SafeModeToggleOptions): Promise<Result<Draupnir>>;
switchToDraupnir(
options?: SafeModeToggleOptions
): Promise<Result<Draupnir, DraupnirRestartError | ResultError>>;
switchToSafeMode(
cause: SafeModeCause,
options?: SafeModeToggleOptions