Updates
This commit is contained in:
@@ -2,18 +2,15 @@ import { AppContext } from "@/src/pages/_app";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
} from "@/src/types";
|
||||
} from "@/src/types/turboci";
|
||||
import ArrowedLink from "@/twui/components/layout/ArrowedLink";
|
||||
import Button from "@/twui/components/layout/Button";
|
||||
import H2 from "@/twui/components/layout/H2";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import Stack from "@/twui/components/layout/Stack";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import ServiceClusterServer from "../service/(partials)/cluster-server";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Select from "@/twui/components/form/Select";
|
||||
import useStatus from "@/twui/components/hooks/useStatus";
|
||||
import Loading from "@/twui/components/elements/Loading";
|
||||
import _ from "lodash";
|
||||
|
||||
type Props = {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Dispatch, Fragment, SetStateAction, useContext, useRef } from "react";
|
||||
import { ParsedDeploymentServiceConfig } from "@/src/types";
|
||||
import { ParsedDeploymentServiceConfig } from "@/src/types/turboci";
|
||||
import Select, { TWUISelectOptionObject } from "@/twui/components/form/Select";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import Button from "@/twui/components/layout/Button";
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { Dispatch, SetStateAction } from "react";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
} from "@/src/types";
|
||||
} from "@/src/types/turboci";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import ServiceClusterServerLogSelectorSetCustomLog from "./cluster-server-log-selector-set-custom-log";
|
||||
import ServiceClusterServerLogSelectorSelectLog from "./cluster-server-log-selector-select-log";
|
||||
|
||||
@@ -2,8 +2,6 @@ import Stack from "@/twui/components/layout/Stack";
|
||||
import { RefObject, useContext, useEffect, useRef, useState } from "react";
|
||||
import { AppContext } from "@/src/pages/_app";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
ServerTerminalTargets,
|
||||
TtydInfoObject,
|
||||
WebSocketDataType,
|
||||
@@ -15,6 +13,10 @@ import Center from "@/twui/components/layout/Center";
|
||||
import TtydIframe from "@/src/components/general/ttyd-iframe";
|
||||
import useIntersectionObserver from "@/twui/components/hooks/useIntersectionObserver";
|
||||
import useStatus from "@/twui/components/hooks/useStatus";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
} from "@/src/types/turboci";
|
||||
|
||||
type Props = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
|
||||
@@ -4,8 +4,7 @@ import { AppContext } from "@/src/pages/_app";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
ServerTerminalTargets,
|
||||
} from "@/src/types";
|
||||
} from "@/src/types/turboci";
|
||||
import useIntersectionObserver from "@/twui/components/hooks/useIntersectionObserver";
|
||||
import Center from "@/twui/components/layout/Center";
|
||||
import Loading from "@/twui/components/elements/Loading";
|
||||
@@ -14,6 +13,7 @@ import Row from "@/twui/components/layout/Row";
|
||||
import Button from "@/twui/components/layout/Button";
|
||||
import ServiceClusterServerLogSelector from "./cluster-server-log-selector";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ServerTerminalTargets } from "@/src/types";
|
||||
|
||||
type Props = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
@@ -51,6 +51,7 @@ export default function ServiceClusterServer({
|
||||
<Row className="w-full justify-between p-4 -mb-6">
|
||||
<Row>
|
||||
<code>{server.private_ip}</code>
|
||||
{server.public_ip ? <code>{server.public_ip}</code> : null}
|
||||
</Row>
|
||||
|
||||
<Row className="">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AppContext } from "@/src/pages/_app";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
} from "@/src/types";
|
||||
} from "@/src/types/turboci";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import ServiceClusterServer from "./cluster-server";
|
||||
|
||||
|
||||
@@ -2,15 +2,9 @@ import Stack from "@/twui/components/layout/Stack";
|
||||
import { useContext, useState } from "react";
|
||||
import { AppContext } from "@/src/pages/_app";
|
||||
import ServiceCluster from "../(partials)/cluster";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
} from "@/src/types";
|
||||
import Button from "@/twui/components/layout/Button";
|
||||
import { ParsedDeploymentServiceConfig } from "@/src/types/turboci";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Select from "@/twui/components/form/Select";
|
||||
import ServiceClusterServer from "../(partials)/cluster-server";
|
||||
import H2 from "@/twui/components/layout/H2";
|
||||
import _ from "lodash";
|
||||
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
} from "@/src/types";
|
||||
import execSSH from "@/src/utils/exec-ssh";
|
||||
import serviceFlight from "@/src/utils/flight";
|
||||
import grabTurboCiConfig from "@/src/utils/grab-turboci-config";
|
||||
|
||||
export default async function cronCheckServices() {
|
||||
const config = grabTurboCiConfig();
|
||||
|
||||
for (let i = 0; i < config.services.length; i++) {
|
||||
const service = config.services[i];
|
||||
|
||||
if (!service.servers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let srv = 0; srv < service.servers.length; srv++) {
|
||||
const server = service.servers[srv];
|
||||
|
||||
if (service.healthcheck) {
|
||||
const test = await healthcheck({ server, service });
|
||||
|
||||
if (!test) {
|
||||
console.log(
|
||||
`Server ${server.private_ip} down. Restarting ...`,
|
||||
);
|
||||
|
||||
const MAX_RETRIES = 5;
|
||||
let retries = 0;
|
||||
|
||||
while (retries < MAX_RETRIES) {
|
||||
console.log(`Retryig #${retries + 1} ...`);
|
||||
|
||||
await serviceFlight({
|
||||
deployment: config,
|
||||
servers: [server],
|
||||
service,
|
||||
});
|
||||
|
||||
await Bun.sleep(4000);
|
||||
|
||||
const retest = await healthcheck({ server, service });
|
||||
|
||||
if (retest) {
|
||||
break;
|
||||
} else {
|
||||
retries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function healthcheck({
|
||||
server,
|
||||
service,
|
||||
}: {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
server: NormalizedServerObject;
|
||||
}) {
|
||||
if (!service.healthcheck?.cmd || !server.private_ip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const res = await execSSH({
|
||||
cmd: service.healthcheck.cmd,
|
||||
ip: server.private_ip,
|
||||
});
|
||||
|
||||
const test = Boolean(res?.match(service.healthcheck.test));
|
||||
|
||||
return test;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
TCIGlobalConfig,
|
||||
} from "@/src/types/turboci";
|
||||
import execSSH from "@/src/utils/exec-ssh";
|
||||
import serviceFlight from "@/src/utils/flight";
|
||||
|
||||
type Params = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
config: TCIGlobalConfig;
|
||||
};
|
||||
|
||||
export default async function cronCheckServicesGit({
|
||||
config,
|
||||
service,
|
||||
}: Params) {
|
||||
if (service.git) {
|
||||
const service_git_array = Array.isArray(service.git)
|
||||
? service.git
|
||||
: [service.git];
|
||||
|
||||
for (let i = 0; i < service_git_array.length; i++) {
|
||||
const service_git = service_git_array[i];
|
||||
|
||||
if (!service_git.keep_updated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const work_dir = service_git.work_dir || "/turboci/app";
|
||||
|
||||
let cmd = ``;
|
||||
|
||||
cmd += `set -e\n`;
|
||||
cmd += `\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
TCIGlobalConfig,
|
||||
} from "@/src/types/turboci";
|
||||
import execSSH from "@/src/utils/exec-ssh";
|
||||
import serviceFlight from "@/src/utils/flight";
|
||||
|
||||
type Params = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
server: NormalizedServerObject;
|
||||
config: TCIGlobalConfig;
|
||||
};
|
||||
|
||||
export default async function cronCheckServicesHealtcheck({
|
||||
config,
|
||||
server,
|
||||
service,
|
||||
}: Params) {
|
||||
if (service.healthcheck) {
|
||||
const test = await healthcheck({ server, service });
|
||||
|
||||
if (!test) {
|
||||
console.log(`Server ${server.private_ip} down. Restarting ...`);
|
||||
|
||||
const MAX_RETRIES = 5;
|
||||
let retries = 0;
|
||||
|
||||
while (retries < MAX_RETRIES) {
|
||||
console.log(`Retryig #${retries + 1} ...`);
|
||||
|
||||
await serviceFlight({
|
||||
deployment: config,
|
||||
servers: [server],
|
||||
service,
|
||||
});
|
||||
|
||||
await Bun.sleep(4000);
|
||||
|
||||
const retest = await healthcheck({ server, service });
|
||||
|
||||
if (retest) {
|
||||
break;
|
||||
} else {
|
||||
retries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function healthcheck({
|
||||
server,
|
||||
service,
|
||||
}: {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
server: NormalizedServerObject;
|
||||
}) {
|
||||
if (!service.healthcheck?.cmd || !server.private_ip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const res = await execSSH({
|
||||
cmd: service.healthcheck.cmd,
|
||||
ip: server.private_ip,
|
||||
});
|
||||
|
||||
const test = Boolean(res?.match(service.healthcheck.test));
|
||||
|
||||
return test;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import grabTurboCiConfig from "@/src/utils/grab-turboci-config";
|
||||
import cronCheckServicesHealtcheck from "./healthcheck";
|
||||
import serviceGitCheck from "../../utils/service-git-check";
|
||||
|
||||
export default async function cronCheckServices() {
|
||||
const config = grabTurboCiConfig();
|
||||
|
||||
for (let i = 0; i < config.services.length; i++) {
|
||||
const service = config.services[i];
|
||||
|
||||
if (!service.servers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await serviceGitCheck({ deployment: config, service });
|
||||
|
||||
for (let srv = 0; srv < service.servers.length; srv++) {
|
||||
const server = service.servers[srv];
|
||||
await cronCheckServicesHealtcheck({ config, server, service });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
ParsedDeploymentServiceConfig,
|
||||
ResponseObject,
|
||||
TCIGlobalConfig,
|
||||
} from "@/src/types/turboci";
|
||||
import bunGrabBulkSyncScripts from "@/src/utils/bun-grab-bulk-sync-script";
|
||||
import serviceFlight from "@/src/utils/flight";
|
||||
import grabGitRepoName from "@/src/utils/grab-git-repo-name";
|
||||
import relayExecSSH from "@/src/utils/relay-exec-ssh";
|
||||
import turboCIPkgrabDirNames from "@/src/utils/turboci-pkg-grab-dir-names";
|
||||
import _ from "lodash";
|
||||
import path from "path";
|
||||
|
||||
const { relayServerRsyncDir } = turboCIPkgrabDirNames();
|
||||
|
||||
type Params = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
deployment: TCIGlobalConfig;
|
||||
};
|
||||
|
||||
export default async function serviceGitCheck(
|
||||
params: Params,
|
||||
): Promise<ResponseObject> {
|
||||
const { service, deployment } = params;
|
||||
|
||||
if (!service.git || !service.servers?.[0]) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const servers_private_ips = service.servers
|
||||
.map((srv) => srv.private_ip)
|
||||
.filter((ip) => Boolean(ip)) as string[];
|
||||
|
||||
const git_array = Array.isArray(service.git) ? service.git : [service.git];
|
||||
|
||||
for (let i = 0; i < git_array.length; i++) {
|
||||
const service_git = git_array[i];
|
||||
|
||||
if (!service_git) continue;
|
||||
|
||||
const git_url = service_git.repo_url;
|
||||
const repo_name = grabGitRepoName({ git_url });
|
||||
|
||||
if (!repo_name) continue;
|
||||
|
||||
const relay_dst = path.join(
|
||||
relayServerRsyncDir,
|
||||
service.service_name,
|
||||
"git",
|
||||
repo_name,
|
||||
);
|
||||
|
||||
let git_pull_cmd = ``;
|
||||
|
||||
git_pull_cmd += `cd ${relay_dst}\n`;
|
||||
git_pull_cmd += `git pull\n`;
|
||||
|
||||
const git_pull_check = await relayExecSSH({
|
||||
cmd: git_pull_cmd,
|
||||
log_error: true,
|
||||
});
|
||||
|
||||
if (git_pull_check?.match(/Already up to date./i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cmd = ``;
|
||||
|
||||
const src = relay_dst + "/";
|
||||
const dst = (service_git.work_dir || "/turboci/app") + "/";
|
||||
|
||||
const sync_cmd = await relayExecSSH({
|
||||
cmd: bunGrabBulkSyncScripts({
|
||||
dst,
|
||||
src,
|
||||
private_server_ips: servers_private_ips,
|
||||
parrallel: true,
|
||||
relay_ignore: [".git"],
|
||||
}),
|
||||
bun: true,
|
||||
return_cmd_only: true,
|
||||
});
|
||||
|
||||
cmd += `${sync_cmd}\n`;
|
||||
|
||||
cmd += `echo "Git Setup Success!"\n`;
|
||||
|
||||
const res = await relayExecSSH({
|
||||
cmd,
|
||||
log_error: true,
|
||||
debug: true,
|
||||
});
|
||||
|
||||
if (!res?.match(/Git Setup Success/)) {
|
||||
console.error(
|
||||
`\`${service.service_name}\` service git prep failed!`,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
await serviceFlight({
|
||||
deployment,
|
||||
servers: service.servers,
|
||||
service,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,11 @@ import {
|
||||
NSQLITE_TURBOCI_ADMIN_USERS_PORTS,
|
||||
NSQLiteTables,
|
||||
} from "@/src/db/types";
|
||||
import { PrivateServerTtydParadigms, TtydInfoObject, User } from "@/src/types";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
PrivateServerTtydParadigms,
|
||||
TtydInfoObject,
|
||||
User,
|
||||
} from "@/src/types";
|
||||
} from "@/src/types/turboci";
|
||||
import grabDirNames from "@/src/utils/grab-dir-names";
|
||||
import getNextAvailablePort from "@/src/utils/grab-next-available-port";
|
||||
import grabSSHPrefix from "@/src/utils/grab-ssh-prefix";
|
||||
|
||||
+5
-167
@@ -4,178 +4,16 @@ import useAppInit from "../hooks/use-app-init";
|
||||
import { ServerWebSocket } from "bun";
|
||||
import { ChildProcess } from "child_process";
|
||||
import { NSQLITE_TURBOCI_ADMIN_USERS } from "../db/types";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
TCIGlobalConfig,
|
||||
} from "./turboci";
|
||||
|
||||
export type User = DATASQUIREL_LoggedInUser & {
|
||||
super_admin?: boolean;
|
||||
};
|
||||
|
||||
export const CloudProviders = [
|
||||
{
|
||||
title: "Hetzner",
|
||||
value: "hetzner",
|
||||
},
|
||||
{
|
||||
title: "Amazon Web Services",
|
||||
value: "aws",
|
||||
},
|
||||
{
|
||||
title: "Google Cloud Platform",
|
||||
value: "gcp",
|
||||
},
|
||||
{
|
||||
title: "Microsoft Azure",
|
||||
value: "azure",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type ParsedDeploymentServiceConfig = TCIConfigServiceConfig & {
|
||||
service_name: string;
|
||||
parent_service_name?: string;
|
||||
servers?: NormalizedServerObject[];
|
||||
};
|
||||
|
||||
export type NormalizedServerObject = {
|
||||
public_ip?: string;
|
||||
private_ip?: string;
|
||||
};
|
||||
|
||||
export type TCIConfig = {
|
||||
deployment_name: string;
|
||||
duplicate_deployment_name?: string;
|
||||
description?: string;
|
||||
location?: string;
|
||||
availability_zone?: string;
|
||||
provider: (typeof CloudProviders)[number]["value"];
|
||||
services: TCIConfigService;
|
||||
env?: { [k: string]: string };
|
||||
env_file?: string;
|
||||
pre_deployment?: TCIRunObj;
|
||||
};
|
||||
|
||||
export type TCIGlobalConfig = Omit<TCIConfig, "services"> & {
|
||||
services: ParsedDeploymentServiceConfig[];
|
||||
relay_server_ip?: string;
|
||||
};
|
||||
|
||||
export type TCIConfigService = {
|
||||
[k: string]: TCIConfigServiceConfig;
|
||||
};
|
||||
|
||||
export const TCIServiceTypes = [
|
||||
{
|
||||
title: "Default Service",
|
||||
value: "default",
|
||||
},
|
||||
{
|
||||
title: "Docker",
|
||||
value: "docker",
|
||||
},
|
||||
{
|
||||
title: "Load Balancer",
|
||||
value: "load_balancer",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const TCIServiceOS = [
|
||||
{
|
||||
title: "Debian 12 Bookworm",
|
||||
value: "debian_12",
|
||||
},
|
||||
{
|
||||
title: "Debian 13 Buster",
|
||||
value: "debian_13",
|
||||
},
|
||||
{
|
||||
title: "Ubuntu 23.0.4",
|
||||
value: "ubuntu_23_0_4",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const TCIServiceDependecyTypes = [
|
||||
{
|
||||
title: "Debian APT",
|
||||
value: "apt",
|
||||
},
|
||||
{
|
||||
title: "Turbo CI",
|
||||
value: "turboci",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type TCIConfigServiceConfig = {
|
||||
type?: (typeof TCIServiceTypes)[number]["value"];
|
||||
os?: string;
|
||||
server_type?: string;
|
||||
enable_public_ip?: boolean;
|
||||
instances?: number;
|
||||
clusters?: number;
|
||||
dir_mappings?: TCIConfigServiceConfigDirMApping[];
|
||||
dependencies?: {
|
||||
[k in (typeof TCIServiceDependecyTypes)[number]["value"]]?: string[];
|
||||
};
|
||||
env?: { [k: string]: string };
|
||||
env_file?: string;
|
||||
target_services?: TCIConfigServiceConfigLBTarget[];
|
||||
run?: TCIConfigServiceConfigRun;
|
||||
ssl?: TCIConfigServiceSSL;
|
||||
duplicate_service_name?: string;
|
||||
healthcheck?: TCIConfigServiceHealthcheck;
|
||||
/**
|
||||
* Commoands to Run on first run
|
||||
*/
|
||||
init?: string[];
|
||||
logs?: TCIConfigServiceConfigLog[];
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigLog =
|
||||
| string
|
||||
| {
|
||||
cmd: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceHealthcheck = {
|
||||
cmd: string;
|
||||
test: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceDomain = {
|
||||
domain_name: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceSSL = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigLBTarget = {
|
||||
service_name: string;
|
||||
port: number;
|
||||
weight?: number;
|
||||
backup?: boolean;
|
||||
domains?: (string | TCIConfigServiceDomain)[];
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigRun = {
|
||||
preflight?: TCIRunObj;
|
||||
start?: TCIRunObj;
|
||||
postflight?: TCIRunObj;
|
||||
work_dir?: string;
|
||||
};
|
||||
|
||||
export type TCIRunObj = {
|
||||
cmds?: string[];
|
||||
work_dir?: string;
|
||||
file?: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigDirMApping = {
|
||||
src: string;
|
||||
dst: string;
|
||||
ignore_file?: string;
|
||||
ignore_patterns?: string[];
|
||||
use_gitignore?: boolean;
|
||||
relay_ignore?: string[];
|
||||
};
|
||||
|
||||
export type ServiceScriptObject = {
|
||||
sh: string;
|
||||
service_name: string;
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
import type { ExecSyncOptions } from "child_process";
|
||||
|
||||
export const TCICommands = [
|
||||
{
|
||||
name: "up",
|
||||
description: "Deploy Stack",
|
||||
},
|
||||
{
|
||||
name: "down",
|
||||
description: "Destroy Stack",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export interface PackageJson {
|
||||
name?: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
bin?: Record<string, string>;
|
||||
dependencies?: Record<string, string>;
|
||||
devDependencies?: Record<string, string>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const CloudProviders = [
|
||||
{
|
||||
title: "Hetzner",
|
||||
value: "hetzner",
|
||||
},
|
||||
{
|
||||
title: "Amazon Web Services",
|
||||
value: "aws",
|
||||
},
|
||||
{
|
||||
title: "Google Cloud Platform",
|
||||
value: "gcp",
|
||||
},
|
||||
{
|
||||
title: "Microsoft Azure",
|
||||
value: "azure",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type GrabConfigReturn = {
|
||||
deployments: TCIGlobalConfig[];
|
||||
envs?: string[];
|
||||
};
|
||||
|
||||
export type TCIConfig =
|
||||
| TCIConfigDeployment[]
|
||||
| {
|
||||
deployments: TCIConfigDeployment[];
|
||||
envs?: string[];
|
||||
};
|
||||
|
||||
export type TCIConfigDeployment = {
|
||||
deployment_name: string;
|
||||
duplicate_deployment_name?: string;
|
||||
description?: string;
|
||||
location?: string;
|
||||
availability_zone?: string;
|
||||
provider: (typeof CloudProviders)[number]["value"];
|
||||
services: TCIConfigService;
|
||||
env?: { [k: string]: string };
|
||||
env_file?: string;
|
||||
pre_deployment?: TCIRunObj;
|
||||
relay_server_options?: TCIConfigRelayServerOptions;
|
||||
};
|
||||
|
||||
export type TCIConfigRelayServerOptions = {
|
||||
server_type?: string;
|
||||
};
|
||||
|
||||
export type TCIGlobalConfig = Omit<TCIConfigDeployment, "services"> & {
|
||||
services: ParsedDeploymentServiceConfig[];
|
||||
relay_server_ip?: string;
|
||||
};
|
||||
|
||||
export type TCIConfigService = {
|
||||
[k: string]: TCIConfigServiceConfig;
|
||||
};
|
||||
|
||||
export const TCIServiceTypes = [
|
||||
{
|
||||
title: "Default Service",
|
||||
value: "default",
|
||||
},
|
||||
{
|
||||
title: "Docker",
|
||||
value: "docker",
|
||||
},
|
||||
{
|
||||
title: "Load Balancer",
|
||||
value: "load_balancer",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const TCIServiceOS = [
|
||||
{
|
||||
title: "Debian 12 Bookworm",
|
||||
value: "debian_12",
|
||||
},
|
||||
{
|
||||
title: "Debian 13 Buster",
|
||||
value: "debian_13",
|
||||
},
|
||||
{
|
||||
title: "Ubuntu 23.0.4",
|
||||
value: "ubuntu_23_0_4",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const TCIServiceDependecyTypes = [
|
||||
{
|
||||
title: "Debian APT",
|
||||
value: "apt",
|
||||
},
|
||||
{
|
||||
title: "Turbo CI",
|
||||
value: "turboci",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const TCIGitParadigms = [
|
||||
{
|
||||
title: "Github",
|
||||
value: "github",
|
||||
},
|
||||
{
|
||||
title: "Gitlab",
|
||||
value: "gitlab",
|
||||
},
|
||||
{
|
||||
title: "Gitea",
|
||||
value: "gitea",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const TCIContainerRegistryParadigms = [
|
||||
{
|
||||
title: "Docker Hub",
|
||||
value: "dockerhub",
|
||||
},
|
||||
{
|
||||
title: "Github Container Registry",
|
||||
value: "ghcr",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type TCIConfigServiceConfig = {
|
||||
type?: (typeof TCIServiceTypes)[number]["value"];
|
||||
os?: string;
|
||||
server_type?: string;
|
||||
enable_public_ip?: boolean;
|
||||
instances?: number;
|
||||
clusters?: number;
|
||||
dir_mappings?: TCIConfigServiceConfigDirMApping[];
|
||||
dependencies?: {
|
||||
[k in (typeof TCIServiceDependecyTypes)[number]["value"]]?: string[];
|
||||
};
|
||||
env?: { [k: string]: string };
|
||||
env_file?: string;
|
||||
target_services?: TCIConfigServiceConfigLBTarget[];
|
||||
run?: TCIConfigServiceConfigRun;
|
||||
ssl?: TCIConfigServiceSSL;
|
||||
duplicate_service_name?: string;
|
||||
healthcheck?: TCIConfigServiceHealthcheck;
|
||||
/**
|
||||
* Commoands to Run on first run
|
||||
*/
|
||||
init?: string[];
|
||||
logs?: TCIConfigServiceConfigLog[];
|
||||
git?: TCIConfigServiceConfigGit | TCIConfigServiceConfigGit[];
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigGit = {
|
||||
paradigm?: (typeof TCIGitParadigms)[number]["value"];
|
||||
repo_url: string;
|
||||
branch?: string;
|
||||
/**
|
||||
* Directory in target servers where the repo should
|
||||
* live. Defaults to `/app`
|
||||
*/
|
||||
work_dir?: string;
|
||||
public_repo?: boolean;
|
||||
username?: string;
|
||||
api_key?: string;
|
||||
/**
|
||||
* If true, this will continuosly pull from the git source
|
||||
* and rerun the flight commands
|
||||
*/
|
||||
keep_updated?: boolean;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigDocker = {
|
||||
container_registry_paradigm?: (typeof TCIContainerRegistryParadigms)[number]["value"];
|
||||
container_registry_url?: string;
|
||||
|
||||
compose?: { [k: string]: any };
|
||||
/**
|
||||
* Location of the dockerfile in the target servers
|
||||
*/
|
||||
docker_file_path?: { [k: string]: any };
|
||||
/**
|
||||
* Directory in target servers which is the docker
|
||||
* reference for files like `docker-compose.yaml` or
|
||||
* `Dockerfile`.
|
||||
*/
|
||||
work_dir?: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigLog =
|
||||
| string
|
||||
| {
|
||||
cmd: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceHealthcheck = {
|
||||
cmd: string;
|
||||
test: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceDomain = {
|
||||
domain_name: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceSSL = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigLBTarget = {
|
||||
service_name: string;
|
||||
port: number;
|
||||
weight?: number;
|
||||
backup?: boolean;
|
||||
domains?: (string | TCIConfigServiceDomain)[];
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigRun = {
|
||||
preflight?: TCIRunObj;
|
||||
start?: TCIRunObj;
|
||||
postflight?: TCIRunObj;
|
||||
work_dir?: string;
|
||||
};
|
||||
|
||||
export type TCIRunObj = {
|
||||
cmds?: string[];
|
||||
work_dir?: string;
|
||||
file?: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigDirMApping = {
|
||||
src: string;
|
||||
dst: string;
|
||||
ignore_file?: string;
|
||||
ignore_patterns?: string[];
|
||||
use_gitignore?: boolean;
|
||||
relay_ignore?: string[];
|
||||
};
|
||||
|
||||
export type TCICommandOptions = {
|
||||
file?: string;
|
||||
};
|
||||
|
||||
export type TCIOptions = {
|
||||
config?: TCIConfig;
|
||||
};
|
||||
|
||||
export const TurboCIPreferedOS = ["debian", "ubuntu"] as const;
|
||||
|
||||
export const TurboCIOsPreferenceRegexp = new RegExp(
|
||||
`${TurboCIPreferedOS.join("|")}`,
|
||||
`i`,
|
||||
);
|
||||
|
||||
export type SSHRelayServerReturn = {
|
||||
ip: string;
|
||||
private_ip: string;
|
||||
};
|
||||
|
||||
export type NormalizedServerObject = {
|
||||
public_ip?: string;
|
||||
private_ip?: string;
|
||||
};
|
||||
|
||||
export type SyncRemoteDirsParams = {
|
||||
ip?: string;
|
||||
ips?: string[];
|
||||
user?: string;
|
||||
src: string;
|
||||
dst: string;
|
||||
ignore_path?: string;
|
||||
ignore_patterns?: string[];
|
||||
use_gitignore?: boolean;
|
||||
delete?: boolean;
|
||||
debug?: boolean;
|
||||
use_relay_server?: boolean;
|
||||
deployment?: Omit<TCIConfigDeployment, "services">;
|
||||
options?: ExecSyncOptions;
|
||||
service?: TCIConfigServiceConfig;
|
||||
service_name?: string;
|
||||
relay_ignore?: string[];
|
||||
};
|
||||
|
||||
export type DefaultPrepParams = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
deployment: TCIGlobalConfig;
|
||||
servers: NormalizedServerObject[];
|
||||
};
|
||||
|
||||
export type ServiceScriptObject = {
|
||||
sh: string;
|
||||
service_name: string;
|
||||
deployment_name: string;
|
||||
work_dir?: string;
|
||||
};
|
||||
|
||||
export type ParsedDeploymentServiceConfig = TCIConfigServiceConfig & {
|
||||
service_name: string;
|
||||
parent_service_name?: string;
|
||||
servers?: NormalizedServerObject[];
|
||||
};
|
||||
|
||||
export type DefaultDeploymentParams = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
deployment: TCIGlobalConfig;
|
||||
};
|
||||
|
||||
export type CommanderDefaultOptions = {
|
||||
skip?: string[];
|
||||
target?: string[];
|
||||
};
|
||||
|
||||
export type TurbociControlServer = NormalizedServerObject & {
|
||||
service_name?: "__relay" | (string & {});
|
||||
deployment_name?: string;
|
||||
};
|
||||
|
||||
export type TurbociControlReturn = {
|
||||
servers?: TurbociControlServer[];
|
||||
};
|
||||
|
||||
export const TurboCIDependencies = [
|
||||
{
|
||||
package_name: "bun",
|
||||
},
|
||||
{
|
||||
package_name: "node",
|
||||
},
|
||||
{
|
||||
package_name: "docker",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type DeploymentAndServicesToUpdate = {
|
||||
deployment: TCIGlobalConfig;
|
||||
services: ParsedDeploymentServiceConfig[];
|
||||
skipped_services: ParsedDeploymentServiceConfig[];
|
||||
};
|
||||
|
||||
export type ResponseObject = {
|
||||
success: boolean;
|
||||
msg?: string;
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { statSync } from "fs";
|
||||
import _ from "lodash";
|
||||
import path from "path";
|
||||
import turboCIPkgrabDirNames from "./turboci-pkg-grab-dir-names";
|
||||
import { AppData } from "../data/app-data";
|
||||
|
||||
type Params = {
|
||||
private_server_ips: string[];
|
||||
parrallel?: boolean;
|
||||
/**
|
||||
* Source on the relay server
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* Destination on private servers
|
||||
*/
|
||||
dst: string;
|
||||
relay_ignore?: string[];
|
||||
};
|
||||
|
||||
export default function bunGrabBulkSyncScripts({
|
||||
private_server_ips,
|
||||
parrallel,
|
||||
src,
|
||||
dst,
|
||||
relay_ignore,
|
||||
}: Params) {
|
||||
const { relayServerSshPrivateKeyFile } = turboCIPkgrabDirNames();
|
||||
|
||||
// const srcStats = statSync(src);
|
||||
// const isSrcFile = srcStats.isFile();
|
||||
|
||||
const dst_dir = src.match(/\.{1,5}$/)
|
||||
? path.dirname(dst)
|
||||
: path.normalize(dst);
|
||||
|
||||
let bunCmd = "";
|
||||
|
||||
bunCmd += `import _ from "lodash";\n`;
|
||||
bunCmd += `import { execSync } from "child_process";\n`;
|
||||
bunCmd += `\n`;
|
||||
bunCmd += `const SSH_KEY = "${relayServerSshPrivateKeyFile}";\n`;
|
||||
bunCmd += `const SSH_OPTS = \`-i \${SSH_KEY} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -C -c aes128-ctr\`;\n`;
|
||||
bunCmd += `const REMOTE_HOSTS = [${private_server_ips
|
||||
.map((h) => `"${h.replace(/\"/g, "")}"`)
|
||||
.join(", ")}];\n`;
|
||||
bunCmd += `const DEFAULT_SSH_USER = "root";\n`;
|
||||
bunCmd += `const BATCH_SIZE = ${AppData["private_server_batch_exec_size"]};\n`;
|
||||
bunCmd += `\n`;
|
||||
|
||||
bunCmd += `async function run(host: string) {\n`;
|
||||
bunCmd += ` let execCmd = \`ssh \${SSH_OPTS} \${DEFAULT_SSH_USER}@\${host} mkdir -p ${dst_dir}\`;\n`;
|
||||
bunCmd += ` execCmd += \` && rsync -avz -e 'ssh \${SSH_OPTS}' --delete\`;\n`;
|
||||
if (relay_ignore) {
|
||||
bunCmd += relay_ignore
|
||||
.map((patt) => ` execCmd += \` --exclude='${patt}'\`;\n`)
|
||||
.join("");
|
||||
}
|
||||
bunCmd += ` execCmd += \` ${src} \${DEFAULT_SSH_USER}@\${host}:${dst}\`;\n`;
|
||||
bunCmd += ` try {\n`;
|
||||
bunCmd += ` execSync(execCmd);\n`;
|
||||
bunCmd += ` console.log("Sync Success!");\n`;
|
||||
bunCmd += ` } catch (error) {\n`;
|
||||
bunCmd += ` process.exit(1);\n`;
|
||||
bunCmd += ` }\n`;
|
||||
bunCmd += `}\n`;
|
||||
bunCmd += `\n`;
|
||||
|
||||
if (parrallel) {
|
||||
bunCmd += `const first_host = REMOTE_HOSTS.splice(0,1)[0];\n`;
|
||||
bunCmd += `await run(first_host)\n`;
|
||||
bunCmd += `\n`;
|
||||
bunCmd += `const chunks = _.chunk(REMOTE_HOSTS, BATCH_SIZE);\n`;
|
||||
bunCmd += `for (let i = 0; i < chunks.length; i++) {\n`;
|
||||
bunCmd += ` const chunk = chunks[i];\n`;
|
||||
bunCmd += ` const runChunk = await Promise.all(chunk.map(h => run(h)));\n`;
|
||||
bunCmd += `}\n`;
|
||||
} else {
|
||||
bunCmd += `for (let i = 0; i < REMOTE_HOSTS.length; i++) {\n`;
|
||||
bunCmd += ` const host = REMOTE_HOSTS[i];\n`;
|
||||
bunCmd += ` const runHost = await run(host);\n`;
|
||||
bunCmd += `}\n`;
|
||||
}
|
||||
|
||||
return bunCmd;
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
||||
ParsedDeploymentServiceConfig,
|
||||
ServiceScriptObject,
|
||||
TCIGlobalConfig,
|
||||
} from "../types";
|
||||
} from "@/src/types/turboci";
|
||||
import { AppNames } from "./app-names";
|
||||
import grabSHEnvs from "./grab-sh-env";
|
||||
import bunGrabPrivateIPsBulkScripts from "./bun-grab-private-ips-bulk-scripts";
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export default function grabGitRepoName({
|
||||
git_url,
|
||||
}: {
|
||||
git_url: string;
|
||||
}): string | undefined {
|
||||
const git_arr = git_url.split("/");
|
||||
|
||||
const repo_name = git_arr.pop()?.replace(/\.git$/, "");
|
||||
const repo_user_name = git_arr.pop();
|
||||
|
||||
return `${repo_user_name}/${repo_name}`;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import _ from "lodash";
|
||||
import path from "path";
|
||||
import { ParsedDeploymentServiceConfig, TCIGlobalConfig } from "../types";
|
||||
import {
|
||||
ParsedDeploymentServiceConfig,
|
||||
TCIGlobalConfig,
|
||||
} from "@/src/types/turboci";
|
||||
import parseEnv from "./parse-env";
|
||||
|
||||
type Params = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from "fs";
|
||||
import grabDirNames from "./grab-dir-names";
|
||||
import { TCIGlobalConfig } from "@/src/types";
|
||||
import { TCIGlobalConfig } from "@/src/types/turboci";
|
||||
|
||||
export default function grabTurboCiConfig() {
|
||||
const { TURBOCI_CONFIG_JSON_FILE } = grabDirNames();
|
||||
|
||||
Reference in New Issue
Block a user