core/steps/nginx.step.ts

core/steps/nginx.step.ts is a file in Bane's Lab Deploy. 165 lines of code and 24 definitions.

import {
    ACTIVE_STATUS,
    CONFIG_BACKUP_SUFFIX,
    CONFIG_MODE,
    CONFIG_OWNER,
    FULL_BACKUP_PREFIX,
    MAIN_CONFIG,
    NGINX_FOLDER,
    NGINX_PARENT,
    NGINX_ROOT,
    RELOAD_COMMAND,
    REMOTE_FULL_ARCHIVE,
    REMOTE_SCRIPTS,
    REMOTE_SITE_CONFIG,
    SCRIPT_STAGING_PREFIX,
    SITE_NAME,
    STATUS_COMMAND,
    TEST_COMMAND,
} from "#configuration/constants/nginx.constants";
import {
    ARCHIVE_SUFFIX,
    PATH_SEPARATOR,
    REMOTE_STAGING,
    STAMP_SEPARATOR,
} from "#configuration/constants/deployment.constants";
import {
    CONFIG_DOWNLOADING,
    CONFIG_SAVED,
    CONFIG_UPLOADED,
    FULL_ARCHIVE_CLEANED,
    FULL_ARCHIVE_CREATED,
    FULL_ARCHIVE_SAVED,
    LOCAL_NGINX_MISSING,
    NOT_RUNNING,
    RELOADED,
    RELOADING,
    RELOAD_FAILED,
    RUNNING,
    TESTING,
    TEST_FAILED,
    TEST_PASSED,
    UPLOADING_CONFIGS,
    VERIFYING,
} from "#configuration/strings/nginx.strings";
import type { Journal, Shell } from "#types/deployment.types";
import {
    existsSync,
    mkdirSync,
    mkdtempSync,
    readFileSync,
    readdirSync,
    rmSync,
    statSync,
    writeFileSync,
} from "node:fs";
import { scriptNameOf, scriptTextOf } from "#core/converters/nginx.converter";
import { absolutePath } from "@ssot/paths";
import { join } from "node:path";
import { stamp } from "#core/converters/text.converter";
import { tmpdir } from "node:os";

const nginxBackups = function nginxBackups(): string {
    const directory = absolutePath("app.backups");
    mkdirSync(directory, { recursive: true });
    return directory;
};

export const localSiteConfig = function localSiteConfig(): string {
    return absolutePath("app.nginxSite");
};

export const pullConfig = async function pullConfig(shell: Shell, journal: Journal): Promise<string> {
    journal.log(CONFIG_DOWNLOADING);
    const local = join(nginxBackups(), SITE_NAME + STAMP_SEPARATOR + stamp() + CONFIG_BACKUP_SUFFIX);
    await shell.download(local, REMOTE_SITE_CONFIG);
    journal.log(CONFIG_SAVED + local);
    return local;
};

export const pullEverything = async function pullEverything(shell: Shell, journal: Journal): Promise<string> {
    const local = join(nginxBackups(), FULL_BACKUP_PREFIX + stamp() + ARCHIVE_SUFFIX);
    await shell.run(`mkdir -p ${REMOTE_STAGING} && tar -czf ${REMOTE_FULL_ARCHIVE} -C ${NGINX_PARENT} ${NGINX_FOLDER}`);
    journal.log(FULL_ARCHIVE_CREATED);
    await shell.download(local, REMOTE_FULL_ARCHIVE);
    journal.log(FULL_ARCHIVE_SAVED + local);
    await shell.run(`rm -f ${REMOTE_FULL_ARCHIVE}`);
    journal.log(FULL_ARCHIVE_CLEANED);
    return local;
};

export const installConfig = async function installConfig(
    shell: Shell,
    journal: Journal,
    local: string,
    remote: string,
): Promise<void> {
    const staged = REMOTE_STAGING + remote.slice(remote.lastIndexOf(PATH_SEPARATOR));
    await shell.run(`mkdir -p ${REMOTE_STAGING}`);
    await shell.upload(local, staged);
    await shell.run(
        `sudo mv ${staged} ${remote} && sudo chown ${CONFIG_OWNER} ${remote} && sudo chmod ${CONFIG_MODE} ${remote}`,
    );
    journal.log(CONFIG_UPLOADED + remote);
};

const installEach = async function installEach(
    shell: Shell,
    journal: Journal,
    pairs: readonly (readonly [string, string])[],
): Promise<void> {
    const [first, ...rest] = pairs;
    if (first === undefined) {
        return;
    }
    await installConfig(shell, journal, first[0], first[1]);
    await installEach(shell, journal, rest);
};

const pushScripts = async function pushScripts(shell: Shell, journal: Journal): Promise<void> {
    const scripts = absolutePath("app.nginxScripts");
    if (!existsSync(scripts)) {
        return;
    }
    const names = readdirSync(scripts).filter((name) => statSync(join(scripts, name)).isFile());
    const staged = mkdtempSync(join(tmpdir(), SCRIPT_STAGING_PREFIX));
    const pairs = names.map((name): readonly [string, string] => {
        const target = scriptNameOf(name);
        const output = join(staged, target);
        const source = readFileSync(join(scripts, name), "utf8");
        writeFileSync(output, scriptTextOf(name, source));
        return [output, REMOTE_SCRIPTS + PATH_SEPARATOR + target];
    });
    await shell.run(`sudo mkdir -p ${REMOTE_SCRIPTS}`);
    await installEach(shell, journal, pairs);
    rmSync(staged, { force: true, recursive: true });
};

export const pushConfigs = async function pushConfigs(shell: Shell, journal: Journal): Promise<void> {
    journal.log(UPLOADING_CONFIGS);
    const local = absolutePath("app.nginx");
    if (!existsSync(local)) {
        throw new Error(LOCAL_NGINX_MISSING + local);
    }
    const main = absolutePath("app.nginxMain");
    if (existsSync(main)) {
        await installConfig(shell, journal, main, NGINX_ROOT + PATH_SEPARATOR + MAIN_CONFIG);
    }
    await pushScripts(shell, journal);
    const site = localSiteConfig();
    if (existsSync(site)) {
        await installConfig(shell, journal, site, REMOTE_SITE_CONFIG);
    }
};

export const verifyNginx = async function verifyNginx(shell: Shell, journal: Journal): Promise<void> {
    journal.log(TESTING);
    const test = await shell.run(TEST_COMMAND);
    if (test.code !== 0) {
        throw new Error(TEST_FAILED + test.stderr);
    }
    journal.log(TEST_PASSED);
    journal.log(RELOADING);
    const reload = await shell.run(RELOAD_COMMAND);
    if (reload.code !== 0) {
        throw new Error(RELOAD_FAILED + reload.stderr);
    }
    journal.log(RELOADED);
    journal.log(VERIFYING);
    const status = await shell.run(STATUS_COMMAND);
    if (status.stdout.trim() !== ACTIVE_STATUS) {
        throw new Error(NOT_RUNNING);
    }
    journal.log(RUNNING);
};