Refactor infra/setup/cli.ts into smaller command and prompt modules
Decomposes the monolith `infra/setup/cli.ts` into clean `infra/setup/prompts/` and `infra/setup/commands/` directories while adhering to Cliffy idiomatic modularity. Validated via `deno check`, tests, and format. Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
This commit is contained in:
parent
e7a2aa8df3
commit
752bfcf03e
@ -1,303 +1,15 @@
|
|||||||
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
||||||
import { Input, Secret, Select } from "jsr:@cliffy/prompt@1.0.0-rc.7";
|
|
||||||
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import { runSetupWizard } from "./prompts/wizard.ts";
|
||||||
|
import { authCommand } from "./commands/auth.ts";
|
||||||
|
import { testCommand } from "./commands/test.ts";
|
||||||
import {
|
import {
|
||||||
generateDockerCompose,
|
buildCommand,
|
||||||
generateSpireDockerCompose,
|
compileProtoCommand,
|
||||||
} from "./compose.ts";
|
releaseCommand,
|
||||||
import {
|
} from "./commands/build.ts";
|
||||||
AuthSetupConfig,
|
import { dumpComposeCommand, dumpEnvCommand } from "./commands/compose.ts";
|
||||||
COMPOSE_PATH,
|
import { secretsCommand } from "./commands/secrets.ts";
|
||||||
DEFAULT_AUTH_CONFIG,
|
|
||||||
ENV_PATH,
|
|
||||||
generateEnv,
|
|
||||||
generateSpireEnv,
|
|
||||||
readEnv,
|
|
||||||
SPIRE_COMPOSE_PATH,
|
|
||||||
SPIRE_ENV_PATH,
|
|
||||||
} from "./env.ts";
|
|
||||||
import {
|
|
||||||
downloadWorkloadProto,
|
|
||||||
executeBuildImage,
|
|
||||||
executeProtobufCompilation,
|
|
||||||
generateBuildCommands,
|
|
||||||
} from "./build.ts";
|
|
||||||
|
|
||||||
export async function generateAuthSetupFiles(
|
|
||||||
config: AuthSetupConfig,
|
|
||||||
): Promise<void> {
|
|
||||||
const envContent = generateEnv(config);
|
|
||||||
await Deno.writeTextFile(ENV_PATH, envContent);
|
|
||||||
|
|
||||||
const spireEnvContent = generateSpireEnv(config);
|
|
||||||
await Deno.writeTextFile(SPIRE_ENV_PATH, spireEnvContent);
|
|
||||||
|
|
||||||
const composeContent = generateDockerCompose();
|
|
||||||
await Deno.writeTextFile(COMPOSE_PATH, composeContent);
|
|
||||||
|
|
||||||
const spireComposeContent = generateSpireDockerCompose();
|
|
||||||
await Deno.writeTextFile(SPIRE_COMPOSE_PATH, spireComposeContent);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
colors.green(
|
|
||||||
`\n✓ Successfully generated ${ENV_PATH}, ${SPIRE_ENV_PATH}, ${COMPOSE_PATH}, and ${SPIRE_COMPOSE_PATH}!`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.green("Setup complete. You may deploy your stacks by running:\n"),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"1. Deploy SPIRE Stack:\n" +
|
|
||||||
" podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml up -d\n\n" +
|
|
||||||
"2. Deploy Auth-Yes Stack:\n" +
|
|
||||||
" podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleAuthSetup(
|
|
||||||
currentConfig: AuthSetupConfig,
|
|
||||||
): Promise<AuthSetupConfig> {
|
|
||||||
console.log(
|
|
||||||
colors.gray(
|
|
||||||
"Please provide the following Auth Yes configuration details.\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const reg = await Input.prompt({
|
|
||||||
message: "Enter the Primary Container Registry URL:",
|
|
||||||
default: currentConfig.reg,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ghcrReg = await Input.prompt({
|
|
||||||
message: "Enter the GHCR Mirror Registry URL (for SPIRE):",
|
|
||||||
default: currentConfig.ghcrReg || "ghcr.atyg.org",
|
|
||||||
});
|
|
||||||
|
|
||||||
const domainName = await Input.prompt({
|
|
||||||
message: "Enter the Auth Domain Name:",
|
|
||||||
hint: "E.g., auth.system.local",
|
|
||||||
default: currentConfig.domainName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dbDataPath = await Input.prompt({
|
|
||||||
message: "Enter the Database Path on the Host:",
|
|
||||||
default: currentConfig.dbDataPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
const spireDataPath = await Input.prompt({
|
|
||||||
message: "Enter the SPIRE Path on the Host:",
|
|
||||||
default: currentConfig.spireDataPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appSecret = await Secret.prompt({
|
|
||||||
message: "Enter the App Secret for the IDP:",
|
|
||||||
default: currentConfig.appSecret,
|
|
||||||
minLength: 16,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pwdMessage = currentConfig.dbPassword
|
|
||||||
? "Enter the PostgreSQL database password: (Leave blank to keep existing password)"
|
|
||||||
: "Enter the PostgreSQL database password:";
|
|
||||||
|
|
||||||
const pwdInput = await Secret.prompt({
|
|
||||||
message: pwdMessage,
|
|
||||||
minLength: currentConfig.dbPassword ? 0 : 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dbPassword = pwdInput === "" && currentConfig.dbPassword !== ""
|
|
||||||
? currentConfig.dbPassword
|
|
||||||
: pwdInput;
|
|
||||||
|
|
||||||
const newConfig: AuthSetupConfig = {
|
|
||||||
reg,
|
|
||||||
ghcrReg,
|
|
||||||
domainName,
|
|
||||||
dbPassword,
|
|
||||||
dbDataPath,
|
|
||||||
spireDataPath,
|
|
||||||
appSecret,
|
|
||||||
};
|
|
||||||
|
|
||||||
await generateAuthSetupFiles(newConfig);
|
|
||||||
|
|
||||||
return newConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleTestConnection(domainName: string): Promise<void> {
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(`\n=== Testing Auth Connection (https://${domainName}) ===`),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
||||||
const res1 = await fetch(`https://${domainName}/`, {
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (res1.status === 502 || res1.status === 503 || res1.status === 504) {
|
|
||||||
throw new Error(`Gateway error: ${res1.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res1.body) {
|
|
||||||
await res1.body.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
colors.green(
|
|
||||||
`✓ Auth service is up and reachable at https://${domainName}/`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(
|
|
||||||
colors.red(
|
|
||||||
`✗ Error: Could not reach the auth service at https://${domainName}/`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.log(colors.red(` Reason: ${error.message}`));
|
|
||||||
} else {
|
|
||||||
console.log(colors.red(` Reason: ${error}`));
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
colors.red(
|
|
||||||
" Please verify the container is running and DNS/Traefik is resolving.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleReviewConfigs(): Promise<void> {
|
|
||||||
const envContent = await Deno.readTextFile(ENV_PATH).catch(() => null);
|
|
||||||
const composeContent = await Deno.readTextFile(COMPOSE_PATH).catch(() =>
|
|
||||||
null
|
|
||||||
);
|
|
||||||
const spireComposeContent = await Deno.readTextFile(SPIRE_COMPOSE_PATH).catch(
|
|
||||||
() => null,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!envContent && !composeContent && !spireComposeContent) {
|
|
||||||
console.log(
|
|
||||||
colors.red("\n✗ No generated configs found. Run the setup first.\n"),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const action = await Select.prompt({
|
|
||||||
message: "Review Generated Configs",
|
|
||||||
options: [
|
|
||||||
{ name: "[Show .env]", value: "env" },
|
|
||||||
{ name: "[Show compose.yml]", value: "compose" },
|
|
||||||
{ name: "[Show compose.spire.yml]", value: "spire_compose" },
|
|
||||||
{ name: "[Back to Main Menu]", value: "back" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (action === "env") {
|
|
||||||
console.log(colors.bold(colors.blue(`\n=== ${ENV_PATH} ===\n`)));
|
|
||||||
console.log(envContent || colors.yellow("File not found."));
|
|
||||||
console.log();
|
|
||||||
} else if (action === "compose") {
|
|
||||||
console.log(colors.bold(colors.blue(`\n=== ${COMPOSE_PATH} ===\n`)));
|
|
||||||
console.log(composeContent || colors.yellow("File not found."));
|
|
||||||
console.log();
|
|
||||||
} else if (action === "spire_compose") {
|
|
||||||
console.log(
|
|
||||||
colors.bold(colors.blue(`\n=== ${SPIRE_COMPOSE_PATH} ===\n`)),
|
|
||||||
);
|
|
||||||
console.log(spireComposeContent || colors.yellow("File not found."));
|
|
||||||
console.log();
|
|
||||||
} else if (action === "back") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIDE EFFECT: Runs the interactive CLI wizard.
|
|
||||||
export async function runSetupWizard(): Promise<void> {
|
|
||||||
console.log(colors.bold(colors.blue("=== Auth Setup Wizard ===\n")));
|
|
||||||
|
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
let currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Object.keys(loadedEnv).length > 0 && currentConfig.domainName) {
|
|
||||||
await handleTestConnection(currentConfig.domainName);
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const action = await Select.prompt({
|
|
||||||
message: "Main Menu",
|
|
||||||
options: [
|
|
||||||
{ name: "[Test Auth Connection]", value: "test" },
|
|
||||||
{ name: "[Configure Auth Yes API]", value: "auth" },
|
|
||||||
{ name: "[Compile Protobuf Definitions]", value: "compile_proto" },
|
|
||||||
{ name: "[Review Generated Configs]", value: "review" },
|
|
||||||
{ name: "[Build and Push Auth Image]", value: "build" },
|
|
||||||
{ name: "[Exit]", value: "exit" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (action === "test") {
|
|
||||||
await handleTestConnection(currentConfig.domainName);
|
|
||||||
console.log();
|
|
||||||
} else if (action === "auth") {
|
|
||||||
currentConfig = await handleAuthSetup(currentConfig);
|
|
||||||
} else if (action === "compile_proto") {
|
|
||||||
try {
|
|
||||||
await downloadWorkloadProto();
|
|
||||||
await executeProtobufCompilation();
|
|
||||||
console.log(colors.green("\n✓ Successfully compiled protobufs!\n"));
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.log(
|
|
||||||
colors.red(`\n✗ Protobuf compilation failed: ${error.message}\n`),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
colors.red(`\n✗ Protobuf compilation failed: ${error}\n`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (action === "review") {
|
|
||||||
await handleReviewConfigs();
|
|
||||||
} else if (action === "build") {
|
|
||||||
try {
|
|
||||||
const commands = generateBuildCommands(currentConfig.reg);
|
|
||||||
await executeBuildImage(commands);
|
|
||||||
console.log(colors.green("\n✓ Successfully built and pushed image!"));
|
|
||||||
console.log(
|
|
||||||
colors.green("You may now deploy your stack by running:\n"),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.log(
|
|
||||||
colors.red(`\n✗ Build process failed: ${error.message}\n`),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(colors.red(`\n✗ Build process failed: ${error}\n`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (action === "exit") {
|
|
||||||
console.log(colors.gray("Exiting...\n"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runCli(args = Deno.args): Promise<void> {
|
export async function runCli(args = Deno.args): Promise<void> {
|
||||||
if (import.meta.main || args.length === 0) {
|
if (import.meta.main || args.length === 0) {
|
||||||
@ -314,458 +26,18 @@ export async function runCli(args = Deno.args): Promise<void> {
|
|||||||
const cmd = new Command()
|
const cmd = new Command()
|
||||||
.name("auth-setup")
|
.name("auth-setup")
|
||||||
.description("Auth setup wizard and CLI")
|
.description("Auth setup wizard and CLI")
|
||||||
|
.version("1.0.0")
|
||||||
.action(() => {
|
.action(() => {
|
||||||
runSetupWizard();
|
runSetupWizard();
|
||||||
})
|
})
|
||||||
.command("auth", "Configure Auth Yes API")
|
.command("auth", authCommand)
|
||||||
.option("--auto, --headless", "Run in headless mode")
|
.command("test", testCommand)
|
||||||
.option("--registry <reg:string>", "Container Registry URL")
|
.command("compile_proto", compileProtoCommand)
|
||||||
.option("--ghcr-registry <ghcrReg:string>", "GHCR Mirror Registry URL")
|
.command("build", buildCommand)
|
||||||
.option("--domain <domain:string>", "Auth Domain Name")
|
.command("release", releaseCommand)
|
||||||
.option("--db-path <path:string>", "Database Path on the Host")
|
.command("dump_compose", dumpComposeCommand)
|
||||||
.option("--spire-path <path:string>", "SPIRE Path on the Host")
|
.command("dump_env", dumpEnvCommand)
|
||||||
.action(async (options) => {
|
.command("secrets", secretsCommand);
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
const currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.auto) {
|
|
||||||
if (!options.registry || !options.domain || !options.dbPath) {
|
|
||||||
console.error(
|
|
||||||
colors.red(
|
|
||||||
"Error: --registry, --domain, and --db-path are required in headless mode.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
const dbPassword = Deno.env.get("POSTGRES_PASSWORD") ||
|
|
||||||
currentConfig.dbPassword;
|
|
||||||
const appSecret = Deno.env.get("APP_SECRET") ||
|
|
||||||
currentConfig.appSecret;
|
|
||||||
if (!dbPassword) {
|
|
||||||
console.error(
|
|
||||||
colors.red(
|
|
||||||
"Error: POSTGRES_PASSWORD environment variable is required in headless mode.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
if (!appSecret) {
|
|
||||||
console.error(
|
|
||||||
colors.red(
|
|
||||||
"Error: APP_SECRET environment variable is required in headless mode.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newConfig: AuthSetupConfig = {
|
|
||||||
reg: options.registry,
|
|
||||||
ghcrReg: options.ghcrRegistry || currentConfig.ghcrReg ||
|
|
||||||
"ghcr.atyg.org",
|
|
||||||
domainName: options.domain,
|
|
||||||
dbPassword,
|
|
||||||
dbDataPath: options.dbPath || currentConfig.dbDataPath ||
|
|
||||||
"/volume1/docker/auth-yes/data",
|
|
||||||
spireDataPath: options.spirePath || currentConfig.spireDataPath ||
|
|
||||||
"/volume1/docker/spire",
|
|
||||||
appSecret,
|
|
||||||
};
|
|
||||||
await generateAuthSetupFiles(newConfig);
|
|
||||||
} else {
|
|
||||||
if (options.registry) currentConfig.reg = options.registry;
|
|
||||||
if (options.ghcrRegistry) currentConfig.ghcrReg = options.ghcrRegistry;
|
|
||||||
if (options.domain) currentConfig.domainName = options.domain;
|
|
||||||
if (options.dbPath) currentConfig.dbDataPath = options.dbPath;
|
|
||||||
if (options.spirePath) currentConfig.spireDataPath = options.spirePath;
|
|
||||||
await handleAuthSetup(currentConfig);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.command("test", "Test Auth Connection")
|
|
||||||
.action(async () => {
|
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
const currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
await handleTestConnection(currentConfig.domainName);
|
|
||||||
})
|
|
||||||
.command("compile_proto", "Compile Protobuf Definitions")
|
|
||||||
.action(async () => {
|
|
||||||
try {
|
|
||||||
await downloadWorkloadProto();
|
|
||||||
await executeProtobufCompilation();
|
|
||||||
console.log(colors.green("\n✓ Successfully compiled protobufs!\n"));
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.log(
|
|
||||||
colors.red(`\n✗ Protobuf compilation failed: ${error.message}\n`),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
colors.red(`\n✗ Protobuf compilation failed: ${error}\n`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.command("build", "Build and Push Auth Image")
|
|
||||||
.action(async () => {
|
|
||||||
try {
|
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
const currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
const commands = generateBuildCommands(currentConfig.reg);
|
|
||||||
await executeBuildImage(commands);
|
|
||||||
console.log(
|
|
||||||
colors.green("\n✓ Successfully built and pushed auth image!"),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.green("You may now deploy your stack by running:\n"),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.log(
|
|
||||||
colors.red(`\n✗ Build process failed: ${error.message}\n`),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(colors.red(`\n✗ Build process failed: ${error}\n`));
|
|
||||||
}
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.command("dump_compose", "Output all generated Compose YAML configurations")
|
|
||||||
.action(() => {
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"\n================================================================",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.green(" STACK 1: Auth-Yes Stack (infra/compose.yml)"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================\n",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(generateDockerCompose());
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.green(
|
|
||||||
" STACK 2: Standalone SPIRE Stack (infra/compose.spire.yml)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================\n",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(generateSpireDockerCompose());
|
|
||||||
})
|
|
||||||
.command(
|
|
||||||
"dump_env",
|
|
||||||
"Output current environment configuration (.env and .env.spire)",
|
|
||||||
)
|
|
||||||
.action(async () => {
|
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
const currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"\n================================================================",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.green(" STACK 1: Auth-Yes Environment (infra/stack.env)"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================\n",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(generateEnv(currentConfig));
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.green(
|
|
||||||
" STACK 2: SPIRE Stack Environment (infra/.env.spire)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================\n",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(generateSpireEnv(currentConfig));
|
|
||||||
})
|
|
||||||
.command("secrets", "Inspect secrets status and persistence paths")
|
|
||||||
.action(async () => {
|
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
const currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"\n=== Auth-Yes Secrets & Persistence Topology ===\n",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`${
|
|
||||||
colors.cyan("Database Persistence Path:")
|
|
||||||
} ${currentConfig.dbDataPath}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`${
|
|
||||||
colors.cyan("SPIRE Persistence Path:")
|
|
||||||
} ${currentConfig.spireDataPath}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`${
|
|
||||||
colors.cyan("Local Artifact Files:")
|
|
||||||
} ${ENV_PATH}, ${SPIRE_ENV_PATH}, ${COMPOSE_PATH}, ${SPIRE_COMPOSE_PATH}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`${colors.cyan("PostgreSQL Password:")} ${
|
|
||||||
currentConfig.dbPassword
|
|
||||||
? colors.green(
|
|
||||||
"✓ Configured (len: " + currentConfig.dbPassword.length + ")",
|
|
||||||
)
|
|
||||||
: colors.red("✗ Missing")
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`${colors.cyan("Application Secret:")} ${
|
|
||||||
currentConfig.appSecret
|
|
||||||
? colors.green(
|
|
||||||
"✓ Configured (len: " + currentConfig.appSecret.length + ")",
|
|
||||||
)
|
|
||||||
: colors.red("✗ Missing")
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.gray(
|
|
||||||
"\nTip: To dump raw environment variables, run: deno task setup dump_env\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.command(
|
|
||||||
"release",
|
|
||||||
"Run complete build & release pipeline with copy-paste deployment instructions",
|
|
||||||
)
|
|
||||||
.action(async () => {
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"\n================================================================",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.green(
|
|
||||||
" Auth-Yes Infrastructure Build & Release Pipeline",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.blue(
|
|
||||||
"================================================================\n",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 1. Quality Gates
|
|
||||||
console.log(colors.cyan("[1/4] Running Quality Gates & Test Suite..."));
|
|
||||||
const testCmd = new Deno.Command("deno", {
|
|
||||||
args: ["task", "test"],
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
});
|
|
||||||
const testRes = await testCmd.output();
|
|
||||||
if (testRes.code !== 0) {
|
|
||||||
console.error(
|
|
||||||
colors.red("\n✗ Quality gates failed. Aborting release."),
|
|
||||||
);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Compile Protobufs
|
|
||||||
console.log(
|
|
||||||
colors.cyan("\n[2/4] Downloading & Compiling Protobufs..."),
|
|
||||||
);
|
|
||||||
await downloadWorkloadProto();
|
|
||||||
await executeProtobufCompilation();
|
|
||||||
|
|
||||||
// 3. Build & Push Images
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"\n[3/4] Building and Pushing Custom Container Images...",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const loadedEnv = await readEnv();
|
|
||||||
const currentConfig: AuthSetupConfig = {
|
|
||||||
...DEFAULT_AUTH_CONFIG,
|
|
||||||
...loadedEnv,
|
|
||||||
};
|
|
||||||
const commands = generateBuildCommands(currentConfig.reg);
|
|
||||||
await executeBuildImage(commands);
|
|
||||||
console.log(
|
|
||||||
colors.green(
|
|
||||||
"\n✓ Successfully built and pushed all stack images to " +
|
|
||||||
currentConfig.reg + "!",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4. Output Copy-Paste Guides
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"\n[4/4] Release Complete. Deployment & Configuration Guides:\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.yellow(
|
|
||||||
"┌──────────────────────────────────────────────────────────────┐",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.yellow(
|
|
||||||
"│ A. FOR NEW SYSTEM INSTALLATIONS │",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.yellow(
|
|
||||||
"└──────────────────────────────────────────────────────────────┘",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.gray("# 1. Inspect environment variables and secrets:"),
|
|
||||||
);
|
|
||||||
console.log(colors.cyan("deno task setup dump_env"));
|
|
||||||
console.log(
|
|
||||||
colors.gray("\n# 2. Inspect generated Docker Compose files:"),
|
|
||||||
);
|
|
||||||
console.log(colors.cyan("deno task setup dump_compose"));
|
|
||||||
console.log(
|
|
||||||
colors.gray("\n# 3. Create persistent storage paths on host:"),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
`mkdir -p ${currentConfig.spireDataPath} ${currentConfig.dbDataPath}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(colors.gray("\n# 4. Deploy fresh stacks:"));
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml up -d",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.yellow(
|
|
||||||
"\n┌──────────────────────────────────────────────────────────────┐",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.yellow(
|
|
||||||
"│ B. FOR EXISTING SYSTEM UPDATES (ROLLING RESTART) │",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.bold(
|
|
||||||
colors.yellow(
|
|
||||||
"└──────────────────────────────────────────────────────────────┘",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.gray(
|
|
||||||
"# Pull newly pushed custom images and restart containers:",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml pull",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml up -d\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml pull",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
colors.cyan(
|
|
||||||
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await cmd.parse(args);
|
await cmd.parse(args);
|
||||||
}
|
}
|
||||||
|
|||||||
71
infra/setup/commands/auth.ts
Normal file
71
infra/setup/commands/auth.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import { AuthSetupConfig, DEFAULT_AUTH_CONFIG, readEnv } from "../env.ts";
|
||||||
|
import { generateAuthSetupFiles, handleAuthSetup } from "../prompts/auth.ts";
|
||||||
|
|
||||||
|
export const authCommand = new Command()
|
||||||
|
.description("Configure Auth Yes API")
|
||||||
|
.option("--auto, --headless", "Run in headless mode")
|
||||||
|
.option("--registry <reg:string>", "Container Registry URL")
|
||||||
|
.option("--ghcr-registry <ghcrReg:string>", "GHCR Mirror Registry URL")
|
||||||
|
.option("--domain <domain:string>", "Auth Domain Name")
|
||||||
|
.option("--db-path <path:string>", "Database Path on the Host")
|
||||||
|
.option("--spire-path <path:string>", "SPIRE Path on the Host")
|
||||||
|
.action(async (options) => {
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
const currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.auto) {
|
||||||
|
if (!options.registry || !options.domain || !options.dbPath) {
|
||||||
|
console.error(
|
||||||
|
colors.red(
|
||||||
|
"Error: --registry, --domain, and --db-path are required in headless mode.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
const dbPassword = Deno.env.get("POSTGRES_PASSWORD") ||
|
||||||
|
currentConfig.dbPassword;
|
||||||
|
const appSecret = Deno.env.get("APP_SECRET") || currentConfig.appSecret;
|
||||||
|
if (!dbPassword) {
|
||||||
|
console.error(
|
||||||
|
colors.red(
|
||||||
|
"Error: POSTGRES_PASSWORD environment variable is required in headless mode.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
if (!appSecret) {
|
||||||
|
console.error(
|
||||||
|
colors.red(
|
||||||
|
"Error: APP_SECRET environment variable is required in headless mode.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newConfig: AuthSetupConfig = {
|
||||||
|
reg: options.registry,
|
||||||
|
ghcrReg: options.ghcrRegistry || currentConfig.ghcrReg ||
|
||||||
|
"ghcr.atyg.org",
|
||||||
|
domainName: options.domain,
|
||||||
|
dbPassword,
|
||||||
|
dbDataPath: options.dbPath || currentConfig.dbDataPath ||
|
||||||
|
"/volume1/docker/auth-yes/data",
|
||||||
|
spireDataPath: options.spirePath || currentConfig.spireDataPath ||
|
||||||
|
"/volume1/docker/spire",
|
||||||
|
appSecret,
|
||||||
|
};
|
||||||
|
await generateAuthSetupFiles(newConfig);
|
||||||
|
} else {
|
||||||
|
if (options.registry) currentConfig.reg = options.registry;
|
||||||
|
if (options.ghcrRegistry) currentConfig.ghcrReg = options.ghcrRegistry;
|
||||||
|
if (options.domain) currentConfig.domainName = options.domain;
|
||||||
|
if (options.dbPath) currentConfig.dbDataPath = options.dbPath;
|
||||||
|
if (options.spirePath) currentConfig.spireDataPath = options.spirePath;
|
||||||
|
await handleAuthSetup(currentConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
235
infra/setup/commands/build.ts
Normal file
235
infra/setup/commands/build.ts
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import { AuthSetupConfig, DEFAULT_AUTH_CONFIG, readEnv } from "../env.ts";
|
||||||
|
import {
|
||||||
|
downloadWorkloadProto,
|
||||||
|
executeBuildImage,
|
||||||
|
executeProtobufCompilation,
|
||||||
|
generateBuildCommands,
|
||||||
|
} from "../build.ts";
|
||||||
|
|
||||||
|
export const compileProtoCommand = new Command()
|
||||||
|
.description("Compile Protobuf Definitions")
|
||||||
|
.action(async () => {
|
||||||
|
try {
|
||||||
|
await downloadWorkloadProto();
|
||||||
|
await executeProtobufCompilation();
|
||||||
|
console.log(colors.green("\n✓ Successfully compiled protobufs!\n"));
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log(
|
||||||
|
colors.red(`\n✗ Protobuf compilation failed: ${error.message}\n`),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(colors.red(`\n✗ Protobuf compilation failed: ${error}\n`));
|
||||||
|
}
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const buildCommand = new Command()
|
||||||
|
.description("Build and Push Auth Image")
|
||||||
|
.action(async () => {
|
||||||
|
try {
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
const currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
const commands = generateBuildCommands(currentConfig.reg);
|
||||||
|
await executeBuildImage(commands);
|
||||||
|
console.log(
|
||||||
|
colors.green("\n✓ Successfully built and pushed auth image!"),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.green("You may now deploy your stack by running:\n"),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log(
|
||||||
|
colors.red(`\n✗ Build process failed: ${error.message}\n`),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(colors.red(`\n✗ Build process failed: ${error}\n`));
|
||||||
|
}
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const releaseCommand = new Command()
|
||||||
|
.description(
|
||||||
|
"Run complete build & release pipeline with copy-paste deployment instructions",
|
||||||
|
)
|
||||||
|
.action(async () => {
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"\n================================================================",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.green(
|
||||||
|
" Auth-Yes Infrastructure Build & Release Pipeline",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================\n",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 1. Quality Gates
|
||||||
|
console.log(colors.cyan("[1/4] Running Quality Gates & Test Suite..."));
|
||||||
|
const testCmd = new Deno.Command("deno", {
|
||||||
|
args: ["task", "test"],
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
});
|
||||||
|
const testRes = await testCmd.output();
|
||||||
|
if (testRes.code !== 0) {
|
||||||
|
console.error(
|
||||||
|
colors.red("\n✗ Quality gates failed. Aborting release."),
|
||||||
|
);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Compile Protobufs
|
||||||
|
console.log(
|
||||||
|
colors.cyan("\n[2/4] Downloading & Compiling Protobufs..."),
|
||||||
|
);
|
||||||
|
await downloadWorkloadProto();
|
||||||
|
await executeProtobufCompilation();
|
||||||
|
|
||||||
|
// 3. Build & Push Images
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"\n[3/4] Building and Pushing Custom Container Images...",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
const currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
const commands = generateBuildCommands(currentConfig.reg);
|
||||||
|
await executeBuildImage(commands);
|
||||||
|
console.log(
|
||||||
|
colors.green(
|
||||||
|
"\n✓ Successfully built and pushed all stack images to " +
|
||||||
|
currentConfig.reg + "!",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Output Copy-Paste Guides
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"\n[4/4] Release Complete. Deployment & Configuration Guides:\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.yellow(
|
||||||
|
"┌──────────────────────────────────────────────────────────────┐",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.yellow(
|
||||||
|
"│ A. FOR NEW SYSTEM INSTALLATIONS │",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.yellow(
|
||||||
|
"└──────────────────────────────────────────────────────────────┘",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.gray("# 1. Inspect environment variables and secrets:"),
|
||||||
|
);
|
||||||
|
console.log(colors.cyan("deno task setup dump_env"));
|
||||||
|
console.log(
|
||||||
|
colors.gray("\n# 2. Inspect generated Docker Compose files:"),
|
||||||
|
);
|
||||||
|
console.log(colors.cyan("deno task setup dump_compose"));
|
||||||
|
console.log(
|
||||||
|
colors.gray("\n# 3. Create persistent storage paths on host:"),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
`mkdir -p ${currentConfig.spireDataPath} ${currentConfig.dbDataPath}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(colors.gray("\n# 4. Deploy fresh stacks:"));
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml up -d",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.yellow(
|
||||||
|
"\n┌──────────────────────────────────────────────────────────────┐",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.yellow(
|
||||||
|
"│ B. FOR EXISTING SYSTEM UPDATES (ROLLING RESTART) │",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.yellow(
|
||||||
|
"└──────────────────────────────────────────────────────────────┘",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.gray(
|
||||||
|
"# Pull newly pushed custom images and restart containers:",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml pull",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml up -d\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml pull",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
112
infra/setup/commands/compose.ts
Normal file
112
infra/setup/commands/compose.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import {
|
||||||
|
generateDockerCompose,
|
||||||
|
generateSpireDockerCompose,
|
||||||
|
} from "../compose.ts";
|
||||||
|
import {
|
||||||
|
AuthSetupConfig,
|
||||||
|
DEFAULT_AUTH_CONFIG,
|
||||||
|
generateEnv,
|
||||||
|
generateSpireEnv,
|
||||||
|
readEnv,
|
||||||
|
} from "../env.ts";
|
||||||
|
|
||||||
|
export const dumpComposeCommand = new Command()
|
||||||
|
.description("Output all generated Compose YAML configurations")
|
||||||
|
.action(() => {
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"\n================================================================",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.green(" STACK 1: Auth-Yes Stack (infra/compose.yml)"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================\n",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(generateDockerCompose());
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.green(
|
||||||
|
" STACK 2: Standalone SPIRE Stack (infra/compose.spire.yml)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================\n",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(generateSpireDockerCompose());
|
||||||
|
});
|
||||||
|
|
||||||
|
export const dumpEnvCommand = new Command()
|
||||||
|
.description("Output current environment configuration (.env and .env.spire)")
|
||||||
|
.action(async () => {
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
const currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"\n================================================================",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.green(" STACK 1: Auth-Yes Environment (infra/stack.env)"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================\n",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(generateEnv(currentConfig));
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.green(
|
||||||
|
" STACK 2: SPIRE Stack Environment (infra/.env.spire)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"================================================================\n",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(generateSpireEnv(currentConfig));
|
||||||
|
});
|
||||||
66
infra/setup/commands/secrets.ts
Normal file
66
infra/setup/commands/secrets.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import {
|
||||||
|
AuthSetupConfig,
|
||||||
|
COMPOSE_PATH,
|
||||||
|
DEFAULT_AUTH_CONFIG,
|
||||||
|
ENV_PATH,
|
||||||
|
readEnv,
|
||||||
|
SPIRE_COMPOSE_PATH,
|
||||||
|
SPIRE_ENV_PATH,
|
||||||
|
} from "../env.ts";
|
||||||
|
|
||||||
|
export const secretsCommand = new Command()
|
||||||
|
.description("Inspect secrets status and persistence paths")
|
||||||
|
.action(async () => {
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
const currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(
|
||||||
|
"\n=== Auth-Yes Secrets & Persistence Topology ===\n",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${
|
||||||
|
colors.cyan("Database Persistence Path:")
|
||||||
|
} ${currentConfig.dbDataPath}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${
|
||||||
|
colors.cyan("SPIRE Persistence Path:")
|
||||||
|
} ${currentConfig.spireDataPath}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${
|
||||||
|
colors.cyan("Local Artifact Files:")
|
||||||
|
} ${ENV_PATH}, ${SPIRE_ENV_PATH}, ${COMPOSE_PATH}, ${SPIRE_COMPOSE_PATH}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${colors.cyan("PostgreSQL Password:")} ${
|
||||||
|
currentConfig.dbPassword
|
||||||
|
? colors.green(
|
||||||
|
"✓ Configured (len: " + currentConfig.dbPassword.length + ")",
|
||||||
|
)
|
||||||
|
: colors.red("✗ Missing")
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${colors.cyan("Application Secret:")} ${
|
||||||
|
currentConfig.appSecret
|
||||||
|
? colors.green(
|
||||||
|
"✓ Configured (len: " + currentConfig.appSecret.length + ")",
|
||||||
|
)
|
||||||
|
: colors.red("✗ Missing")
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.gray(
|
||||||
|
"\nTip: To dump raw environment variables, run: deno task setup dump_env\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
61
infra/setup/commands/test.ts
Normal file
61
infra/setup/commands/test.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Command } from "jsr:@cliffy/command@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import { AuthSetupConfig, DEFAULT_AUTH_CONFIG, readEnv } from "../env.ts";
|
||||||
|
|
||||||
|
export async function handleTestConnection(domainName: string): Promise<void> {
|
||||||
|
console.log(
|
||||||
|
colors.bold(
|
||||||
|
colors.blue(`\n=== Testing Auth Connection (https://${domainName}) ===`),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||||
|
const res1 = await fetch(`https://${domainName}/`, {
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (res1.status === 502 || res1.status === 503 || res1.status === 504) {
|
||||||
|
throw new Error(`Gateway error: ${res1.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res1.body) {
|
||||||
|
await res1.body.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
colors.green(
|
||||||
|
`✓ Auth service is up and reachable at https://${domainName}/`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
colors.red(
|
||||||
|
`✗ Error: Could not reach the auth service at https://${domainName}/`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log(colors.red(` Reason: ${error.message}`));
|
||||||
|
} else {
|
||||||
|
console.log(colors.red(` Reason: ${error}`));
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
colors.red(
|
||||||
|
" Please verify the container is running and DNS/Traefik is resolving.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testCommand = new Command()
|
||||||
|
.description("Test Auth Connection")
|
||||||
|
.action(async () => {
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
const currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
await handleTestConnection(currentConfig.domainName);
|
||||||
|
});
|
||||||
134
infra/setup/prompts/auth.ts
Normal file
134
infra/setup/prompts/auth.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { Input, Secret } from "jsr:@cliffy/prompt@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import {
|
||||||
|
generateDockerCompose,
|
||||||
|
generateSpireDockerCompose,
|
||||||
|
} from "../compose.ts";
|
||||||
|
import {
|
||||||
|
AuthSetupConfig,
|
||||||
|
COMPOSE_PATH,
|
||||||
|
ENV_PATH,
|
||||||
|
generateEnv,
|
||||||
|
generateSpireEnv,
|
||||||
|
SPIRE_COMPOSE_PATH,
|
||||||
|
SPIRE_ENV_PATH,
|
||||||
|
} from "../env.ts";
|
||||||
|
|
||||||
|
export async function promptAuthConfig(
|
||||||
|
currentConfig: AuthSetupConfig,
|
||||||
|
): Promise<Partial<AuthSetupConfig>> {
|
||||||
|
console.log(
|
||||||
|
colors.gray(
|
||||||
|
"Please provide the following Auth Yes configuration details.\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const reg = await Input.prompt({
|
||||||
|
message: "Enter the Primary Container Registry URL:",
|
||||||
|
default: currentConfig.reg,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ghcrReg = await Input.prompt({
|
||||||
|
message: "Enter the GHCR Mirror Registry URL (for SPIRE):",
|
||||||
|
default: currentConfig.ghcrReg || "ghcr.atyg.org",
|
||||||
|
});
|
||||||
|
|
||||||
|
const domainName = await Input.prompt({
|
||||||
|
message: "Enter the Auth Domain Name:",
|
||||||
|
hint: "E.g., auth.system.local",
|
||||||
|
default: currentConfig.domainName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const appSecret = await Secret.prompt({
|
||||||
|
message: "Enter the App Secret for the IDP:",
|
||||||
|
default: currentConfig.appSecret,
|
||||||
|
minLength: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pwdMessage = currentConfig.dbPassword
|
||||||
|
? "Enter the PostgreSQL database password: (Leave blank to keep existing password)"
|
||||||
|
: "Enter the PostgreSQL database password:";
|
||||||
|
|
||||||
|
const pwdInput = await Secret.prompt({
|
||||||
|
message: pwdMessage,
|
||||||
|
minLength: currentConfig.dbPassword ? 0 : 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbPassword = pwdInput === "" && currentConfig.dbPassword !== ""
|
||||||
|
? currentConfig.dbPassword
|
||||||
|
: pwdInput;
|
||||||
|
|
||||||
|
return { reg, ghcrReg, domainName, appSecret, dbPassword };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function promptSpireConfig(
|
||||||
|
currentConfig: AuthSetupConfig,
|
||||||
|
): Promise<Partial<AuthSetupConfig>> {
|
||||||
|
const dbDataPath = await Input.prompt({
|
||||||
|
message: "Enter the Database Path on the Host:",
|
||||||
|
default: currentConfig.dbDataPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const spireDataPath = await Input.prompt({
|
||||||
|
message: "Enter the SPIRE Path on the Host:",
|
||||||
|
default: currentConfig.spireDataPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { dbDataPath, spireDataPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function promptConfirmation(): Promise<void> {
|
||||||
|
// Empty implementation as there was no explicit confirmation in cli.ts before
|
||||||
|
// But we add it to cleanly delineate the flow as requested.
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateAuthSetupFiles(
|
||||||
|
config: AuthSetupConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
const envContent = generateEnv(config);
|
||||||
|
await Deno.writeTextFile(ENV_PATH, envContent);
|
||||||
|
|
||||||
|
const spireEnvContent = generateSpireEnv(config);
|
||||||
|
await Deno.writeTextFile(SPIRE_ENV_PATH, spireEnvContent);
|
||||||
|
|
||||||
|
const composeContent = generateDockerCompose();
|
||||||
|
await Deno.writeTextFile(COMPOSE_PATH, composeContent);
|
||||||
|
|
||||||
|
const spireComposeContent = generateSpireDockerCompose();
|
||||||
|
await Deno.writeTextFile(SPIRE_COMPOSE_PATH, spireComposeContent);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
colors.green(
|
||||||
|
`\n✓ Successfully generated ${ENV_PATH}, ${SPIRE_ENV_PATH}, ${COMPOSE_PATH}, and ${SPIRE_COMPOSE_PATH}!`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.green("Setup complete. You may deploy your stacks by running:\n"),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"1. Deploy SPIRE Stack:\n" +
|
||||||
|
" podman-compose --project-name spire --env-file infra/.env.spire -f infra/compose.spire.yml up -d\n\n" +
|
||||||
|
"2. Deploy Auth-Yes Stack:\n" +
|
||||||
|
" podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleAuthSetup(
|
||||||
|
currentConfig: AuthSetupConfig,
|
||||||
|
): Promise<AuthSetupConfig> {
|
||||||
|
const authConfig = await promptAuthConfig(currentConfig);
|
||||||
|
const spireConfig = await promptSpireConfig(currentConfig);
|
||||||
|
|
||||||
|
const newConfig: AuthSetupConfig = {
|
||||||
|
...currentConfig,
|
||||||
|
...authConfig,
|
||||||
|
...spireConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
await promptConfirmation();
|
||||||
|
await generateAuthSetupFiles(newConfig);
|
||||||
|
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
145
infra/setup/prompts/wizard.ts
Normal file
145
infra/setup/prompts/wizard.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { Select } from "jsr:@cliffy/prompt@1.0.0-rc.7";
|
||||||
|
import * as colors from "jsr:@std/fmt@0.225.2/colors";
|
||||||
|
import {
|
||||||
|
AuthSetupConfig,
|
||||||
|
COMPOSE_PATH,
|
||||||
|
DEFAULT_AUTH_CONFIG,
|
||||||
|
ENV_PATH,
|
||||||
|
readEnv,
|
||||||
|
SPIRE_COMPOSE_PATH,
|
||||||
|
} from "../env.ts";
|
||||||
|
import { handleAuthSetup } from "./auth.ts";
|
||||||
|
import { handleTestConnection } from "../commands/test.ts";
|
||||||
|
import {
|
||||||
|
downloadWorkloadProto,
|
||||||
|
executeBuildImage,
|
||||||
|
executeProtobufCompilation,
|
||||||
|
generateBuildCommands,
|
||||||
|
} from "../build.ts";
|
||||||
|
|
||||||
|
export async function handleReviewConfigs(): Promise<void> {
|
||||||
|
const envContent = await Deno.readTextFile(ENV_PATH).catch(() => null);
|
||||||
|
const composeContent = await Deno.readTextFile(COMPOSE_PATH).catch(() =>
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const spireComposeContent = await Deno.readTextFile(SPIRE_COMPOSE_PATH).catch(
|
||||||
|
() => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!envContent && !composeContent && !spireComposeContent) {
|
||||||
|
console.log(
|
||||||
|
colors.red("\n✗ No generated configs found. Run the setup first.\n"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const action = await Select.prompt({
|
||||||
|
message: "Review Generated Configs",
|
||||||
|
options: [
|
||||||
|
{ name: "[Show .env]", value: "env" },
|
||||||
|
{ name: "[Show compose.yml]", value: "compose" },
|
||||||
|
{ name: "[Show compose.spire.yml]", value: "spire_compose" },
|
||||||
|
{ name: "[Back to Main Menu]", value: "back" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (action === "env") {
|
||||||
|
console.log(colors.bold(colors.blue(`\n=== ${ENV_PATH} ===\n`)));
|
||||||
|
console.log(envContent || colors.yellow("File not found."));
|
||||||
|
console.log();
|
||||||
|
} else if (action === "compose") {
|
||||||
|
console.log(colors.bold(colors.blue(`\n=== ${COMPOSE_PATH} ===\n`)));
|
||||||
|
console.log(composeContent || colors.yellow("File not found."));
|
||||||
|
console.log();
|
||||||
|
} else if (action === "spire_compose") {
|
||||||
|
console.log(
|
||||||
|
colors.bold(colors.blue(`\n=== ${SPIRE_COMPOSE_PATH} ===\n`)),
|
||||||
|
);
|
||||||
|
console.log(spireComposeContent || colors.yellow("File not found."));
|
||||||
|
console.log();
|
||||||
|
} else if (action === "back") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIDE EFFECT: Runs the interactive CLI wizard.
|
||||||
|
export async function runSetupWizard(): Promise<void> {
|
||||||
|
console.log(colors.bold(colors.blue("=== Auth Setup Wizard ===\n")));
|
||||||
|
|
||||||
|
const loadedEnv = await readEnv();
|
||||||
|
let currentConfig: AuthSetupConfig = {
|
||||||
|
...DEFAULT_AUTH_CONFIG,
|
||||||
|
...loadedEnv,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(loadedEnv).length > 0 && currentConfig.domainName) {
|
||||||
|
await handleTestConnection(currentConfig.domainName);
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const action = await Select.prompt({
|
||||||
|
message: "Main Menu",
|
||||||
|
options: [
|
||||||
|
{ name: "[Test Auth Connection]", value: "test" },
|
||||||
|
{ name: "[Configure Auth Yes API]", value: "auth" },
|
||||||
|
{ name: "[Compile Protobuf Definitions]", value: "compile_proto" },
|
||||||
|
{ name: "[Review Generated Configs]", value: "review" },
|
||||||
|
{ name: "[Build and Push Auth Image]", value: "build" },
|
||||||
|
{ name: "[Exit]", value: "exit" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (action === "test") {
|
||||||
|
await handleTestConnection(currentConfig.domainName);
|
||||||
|
console.log();
|
||||||
|
} else if (action === "auth") {
|
||||||
|
currentConfig = await handleAuthSetup(currentConfig);
|
||||||
|
} else if (action === "compile_proto") {
|
||||||
|
try {
|
||||||
|
await downloadWorkloadProto();
|
||||||
|
await executeProtobufCompilation();
|
||||||
|
console.log(colors.green("\n✓ Successfully compiled protobufs!\n"));
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log(
|
||||||
|
colors.red(`\n✗ Protobuf compilation failed: ${error.message}\n`),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
colors.red(`\n✗ Protobuf compilation failed: ${error}\n`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (action === "review") {
|
||||||
|
await handleReviewConfigs();
|
||||||
|
} else if (action === "build") {
|
||||||
|
try {
|
||||||
|
const commands = generateBuildCommands(currentConfig.reg);
|
||||||
|
await executeBuildImage(commands);
|
||||||
|
console.log(colors.green("\n✓ Successfully built and pushed image!"));
|
||||||
|
console.log(
|
||||||
|
colors.green("You may now deploy your stack by running:\n"),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
colors.cyan(
|
||||||
|
"podman-compose --project-name auth-yes --env-file infra/stack.env -f infra/compose.yml up -d\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log(
|
||||||
|
colors.red(`\n✗ Build process failed: ${error.message}\n`),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(colors.red(`\n✗ Build process failed: ${error}\n`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (action === "exit") {
|
||||||
|
console.log(colors.gray("Exiting...\n"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user