runtime/entrypoints/nginx.entrypoint.ts

runtime/entrypoints/nginx.entrypoint.ts is a file in Bane's Lab Deploy. 117 lines of code and 16 definitions.

import { CONNECTED, CONNECTING, FATAL, ROLLBACK_FAILED } from "#configuration/strings/deployment.strings";
import type { Journal, ManagedBackup, Secrets, Shell } from "#types/deployment.types";
import {
    LOCAL_BACKUP_LABEL,
    PULL_FULL_START,
    PULL_START,
    PUSH_START,
    SYNC_DONE,
    SYNC_FAILED,
    USAGE,
} from "#configuration/strings/nginx.strings";
import {
    PULL_COMMANDS,
    PULL_FULL_COMMANDS,
    PUSH_COMMANDS,
    REMOTE_SITE_CONFIG,
} from "#configuration/constants/nginx.constants";
import { localSiteConfig, pullConfig, pullEverything, verifyNginx } from "#core/steps/nginx.step";
import { pullManaged, restoreManaged } from "#core/steps/recovery.step";
import { reportPull, reportPullFailure, reportPush, reportPushFailure } from "#core/reporters/nginx.reporter";
import { SITE_URL } from "@banes-lab/web/core/assets/link.assets.ts";
import { createJournal } from "#core/reporters/journal.reporter";
import { createShell } from "#core/adapters/shell.adapter";
import { fetchIndex } from "#core/adapters/package.adapter";
import { loadSecrets } from "#core/loaders/environment.loader";
import { messageOf } from "#core/converters/failure.converter";
import { prepareServer } from "#core/steps/setup.step";
import { probeCertificate } from "#core/steps/certificate.step";
import { probeSite } from "#core/steps/probe.step";

const ARGUMENT_OFFSET = 2;

interface Session {
    readonly journal: Journal;
    readonly secrets: Secrets;
    readonly shell: Shell;
}

const open = async function open(session: Session): Promise<void> {
    session.journal.log(CONNECTING);
    await session.shell.connect();
    session.journal.log(CONNECTED);
};

const pull = async function pull(session: Session, everything: boolean): Promise<number> {
    const { journal, secrets, shell } = session;
    const startedAt = Date.now();
    try {
        journal.log(everything ? PULL_FULL_START : PULL_START);
        await open(session);
        const local = everything ? await pullEverything(shell, journal) : await pullConfig(shell, journal);
        journal.log(SYNC_DONE);
        const reported = await reportPull(journal, secrets.nginxWebhook, { finishedAt: Date.now(), startedAt }, local);
        return reported ? 0 : 1;
    } catch (error: unknown) {
        journal.error(SYNC_FAILED + messageOf(error));
        await reportPullFailure(journal, secrets.nginxWebhook, { finishedAt: Date.now(), startedAt }, messageOf(error));
        return 1;
    }
};

const push = async function push(session: Session): Promise<number> {
    const { journal, secrets, shell } = session;
    const startedAt = Date.now();
    let backups: readonly ManagedBackup[] = [];
    let localBackup = "";
    try {
        journal.log(PUSH_START);
        await open(session);
        backups = await pullManaged(shell, journal);
        localBackup = backups.find((backup) => backup.remote === REMOTE_SITE_CONFIG)?.local ?? "";
        await prepareServer(shell, journal, fetchIndex);
        await verifyNginx(shell, journal);
        await probeSite(journal, SITE_URL, shell);
        await probeCertificate(journal, SITE_URL);
        journal.log(SYNC_DONE);
        journal.log(LOCAL_BACKUP_LABEL + localBackup);
        const span = { finishedAt: Date.now(), startedAt };
        const reported = await reportPush(journal, secrets.nginxWebhook, span, localBackup, localSiteConfig());
        return reported ? 0 : 1;
    } catch (error: unknown) {
        journal.error(SYNC_FAILED + messageOf(error));
        if (backups.length > 0) {
            try {
                await restoreManaged(shell, journal, backups);
            } catch (rollbackError: unknown) {
                journal.error(ROLLBACK_FAILED + messageOf(rollbackError));
            }
        }
        const span = { finishedAt: Date.now(), startedAt };
        await reportPushFailure(journal, secrets.nginxWebhook, span, localBackup, messageOf(error));
        return 1;
    }
};

const run = async function run(): Promise<number> {
    const secrets = loadSecrets();
    const session: Session = { journal: createJournal(), secrets, shell: createShell(secrets) };
    const command = process.argv[ARGUMENT_OFFSET] ?? PUSH_COMMANDS[0] ?? "";
    try {
        if (PULL_COMMANDS.includes(command)) {
            return await pull(session, false);
        }
        if (PULL_FULL_COMMANDS.includes(command)) {
            return await pull(session, true);
        }
        if (PUSH_COMMANDS.includes(command)) {
            return await push(session);
        }
        session.journal.error(USAGE);
        return 1;
    } finally {
        session.shell.dispose();
    }
};

run()
    .then((status) => {
        process.exitCode = status;
    })
    .catch((error: unknown) => {
        console.error(FATAL + messageOf(error));
        process.exitCode = 1;
    });