# core/steps/recovery.step.ts

> 116 lines of code and 20 definitions.

Tree: Bane's Lab Deploy
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/deploy#file-deploy-core-steps-recovery-step-ts
Source text: https://banes-lab.com/source/deploy/core/steps/recovery.step.ts.txt

Listed in [core/steps](https://banes-lab.com/api/source/deploy/core/steps.md), after [core/steps/probe.step.ts](https://banes-lab.com/source/deploy/core/steps/probe.step.ts.md) and before [core/steps/release.step.ts](https://banes-lab.com/source/deploy/core/steps/release.step.ts.md).

## Definitions

- `pullManaged` (lexical_declaration, line 88, exported)
- `restoreManaged` (lexical_declaration, line 108, exported)
- `scriptTargets` (lexical_declaration, line 31)
- `managedTargets` (lexical_declaration, line 41, exported)
- `restoreEach` (lexical_declaration, line 93)
- `pullEach` (lexical_declaration, line 74)
- `scripts` (lexical_declaration, line 32)
- `main` (lexical_declaration, line 42, exported)
- `site` (lexical_declaration, line 43, exported)
- `backupNameOf` (lexical_declaration, line 47, exported)
- `base` (lexical_declaration, line 48, exported)
- `dot` (lexical_declaration, line 49, exported)
- `pullOne` (lexical_declaration, line 56)
- `present` (lexical_declaration, line 62)
- `folder` (lexical_declaration, line 66)
- `local` (lexical_declaration, line 68)
- `backup` (lexical_declaration, line 84)
- `rest]` (lexical_declaration, line 98)
- `test` (lexical_declaration, line 115, exported)
- `reload` (lexical_declaration, line 119, exported)

## Contained in

- [core/steps](https://banes-lab.com/anatomy/deploy/folder-deploy-core-steps.md)

## Uses

- [core/converters/nginx.converter.ts](https://banes-lab.com/source/deploy/core/converters/nginx.converter.ts.md)
- [core/converters/text.converter.ts](https://banes-lab.com/source/deploy/core/converters/text.converter.ts.md)
- [core/steps/nginx.step.ts](https://banes-lab.com/source/deploy/core/steps/nginx.step.ts.md)

## Used by

- [runtime/entrypoints/deployment.entrypoint.ts](https://banes-lab.com/source/deploy/runtime/entrypoints/deployment.entrypoint.ts.md)
- [runtime/entrypoints/nginx.entrypoint.ts](https://banes-lab.com/source/deploy/runtime/entrypoints/nginx.entrypoint.ts.md)

## Grounds

- [Rollback](https://banes-lab.com/records/architecture/rollback.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/deploy/folder-deploy-core-converters.md)
- [core/steps](https://banes-lab.com/anatomy/deploy/folder-deploy-core-steps.md)
- [runtime/entrypoints](https://banes-lab.com/anatomy/deploy/folder-deploy-runtime-entrypoints.md)

## Source

```typescript
import {
    BACKUP_MARKER,
    CONFIG_BACKUP_SUFFIX,
    FILE_TEST_COMMAND,
    MAIN_CONFIG,
    NAME_SEPARATOR,
    NGINX_ROOT,
    RELOAD_COMMAND,
    REMOTE_SCRIPTS,
    REMOTE_SITE_CONFIG,
    REMOVE_FILE_COMMAND,
    TEST_COMMAND,
} from "#configuration/constants/nginx.constants";
import {
    CONFIG_DOWNLOADING,
    CONFIG_SAVED,
    RELOAD_FAILED,
    RESTORED,
    RESTORE_TEST_FAILED,
    RESTORING,
} from "#configuration/strings/nginx.strings";
import type { Journal, ManagedBackup, Shell } from "#types/deployment.types";
import { PATH_SEPARATOR, STAMP_SEPARATOR } from "#configuration/constants/deployment.constants";
import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
import { absolutePath } from "@ssot/paths";
import { installConfig } from "#core/steps/nginx.step";
import { join } from "node:path";
import { scriptNameOf } from "#core/converters/nginx.converter";
import { stamp } from "#core/converters/text.converter";

const scriptTargets = function scriptTargets(): string[] {
    const scripts = absolutePath("app.nginxScripts");
    if (!existsSync(scripts)) {
        return [];
    }
    return readdirSync(scripts)
        .filter((name) => statSync(join(scripts, name)).isFile())
        .map((name) => REMOTE_SCRIPTS + PATH_SEPARATOR + scriptNameOf(name));
};

export const managedTargets = function managedTargets(): string[] {
    const main = existsSync(absolutePath("app.nginxMain")) ? [NGINX_ROOT + PATH_SEPARATOR + MAIN_CONFIG] : [];
    const site = existsSync(absolutePath("app.nginxSite")) ? [REMOTE_SITE_CONFIG] : [];
    return [...main, ...scriptTargets(), ...site];
};

export const backupNameOf = function backupNameOf(remote: string, at: string): string {
    const base = remote.slice(remote.lastIndexOf(PATH_SEPARATOR) + 1);
    const dot = base.lastIndexOf(NAME_SEPARATOR);
    if (dot === -1) {
        return base + STAMP_SEPARATOR + at + CONFIG_BACKUP_SUFFIX;
    }
    return base.slice(0, dot) + STAMP_SEPARATOR + at + BACKUP_MARKER + base.slice(dot);
};

const pullOne = async function pullOne(
    shell: Shell,
    journal: Journal,
    remote: string,
    at: string,
): Promise<ManagedBackup> {
    const present = await shell.run(`${FILE_TEST_COMMAND} ${remote}`);
    if (present.code !== 0) {
        return { local: null, remote };
    }
    const folder = absolutePath("app.backups");
    mkdirSync(folder, { recursive: true });
    const local = join(folder, backupNameOf(remote, at));
    await shell.download(local, remote);
    journal.log(CONFIG_SAVED + local);
    return { local, remote };
};

const pullEach = async function pullEach(
    shell: Shell,
    journal: Journal,
    remotes: readonly string[],
    at: string,
): Promise<ManagedBackup[]> {
    const [remote, ...rest] = remotes;
    if (remote === undefined) {
        return [];
    }
    const backup = await pullOne(shell, journal, remote, at);
    return [backup, ...(await pullEach(shell, journal, rest, at))];
};

export const pullManaged = async function pullManaged(shell: Shell, journal: Journal): Promise<ManagedBackup[]> {
    journal.log(CONFIG_DOWNLOADING);
    return pullEach(shell, journal, managedTargets(), stamp());
};

const restoreEach = async function restoreEach(
    shell: Shell,
    journal: Journal,
    backups: readonly ManagedBackup[],
): Promise<void> {
    const [backup, ...rest] = backups;
    if (backup === undefined) {
        return;
    }
    await (backup.local === null
        ? shell.run(`${REMOVE_FILE_COMMAND} ${backup.remote}`)
        : installConfig(shell, journal, backup.local, backup.remote));
    await restoreEach(shell, journal, rest);
};

export const restoreManaged = async function restoreManaged(
    shell: Shell,
    journal: Journal,
    backups: readonly ManagedBackup[],
): Promise<void> {
    journal.log(RESTORING);
    await restoreEach(shell, journal, backups);
    const test = await shell.run(TEST_COMMAND);
    if (test.code !== 0) {
        throw new Error(RESTORE_TEST_FAILED + test.stderr);
    }
    const reload = await shell.run(RELOAD_COMMAND);
    if (reload.code !== 0) {
        throw new Error(RELOAD_FAILED + reload.stderr);
    }
    journal.log(RESTORED);
};
```
