diff --git a/.vscode/ink.code-snippets b/.vscode/ink.code-snippets new file mode 100644 index 000000000..74dd266b8 --- /dev/null +++ b/.vscode/ink.code-snippets @@ -0,0 +1,21 @@ +{ + "ink-comp": { + "scope": "typescriptreact,javascriptreact", + "prefix": "ink-comp", + "body": [ + "import React from \"react\";", + "import { Box } from \"ink\";", + + "", + "const ${1:$TM_FILENAME_BASE} = ({ setConfig, next }: {setConfig: SetConfig; next: Next; }) => {", + "\treturn (", + "\t\t", + "\t\t\t${0}", + "\t\t", + "\t);", + "}", + "", + "export default ${1:$TM_FILENAME_BASE};" + ] + }, +} \ No newline at end of file diff --git a/cli/src/App.tsx b/cli/src/App.tsx index 786db9514..31187d70e 100644 --- a/cli/src/App.tsx +++ b/cli/src/App.tsx @@ -8,6 +8,8 @@ import Intro from "./components/Intro"; import Setup from "./components/Setup"; import Periphery from "./components/Periphery"; import Mongo from "./components/mongo/Mongo"; +import { dockerNotInstalled } from "./monitor-cli"; +import Docker from "./components/docker/Docker"; const App = () => { const [current, next, prev] = useSequence(); @@ -27,6 +29,7 @@ const App = () => { const pages: ReactNode[] = [ , + ...(dockerNotInstalled ? [] : []), , ...(periphery === true ? peripheryPages : []), ...(periphery === false ? corePages : []), diff --git a/cli/src/components/docker/Docker.tsx b/cli/src/components/docker/Docker.tsx new file mode 100644 index 000000000..6351f202a --- /dev/null +++ b/cli/src/components/docker/Docker.tsx @@ -0,0 +1,37 @@ +import React, { useState } from "react"; +import { Text } from "ink"; +import { Next } from "../../types"; +import YesNo from "../util/YesNo"; +import InstallDocker from "./InstallDocker"; + +const Docker = ({ next }: { next: Next }) => { + const [installDocker, setInstallDocker] = useState(); + if (installDocker === undefined) { + return ( + + Would you like to install Docker? This will begin the{" "} + + Docker Install Helper + + + } + onSelect={(res) => { + if (res === "yes") { + setInstallDocker(true); + } else { + next(); + } + }} + direction="vertical" + /> + ); + } else if (installDocker) { + return + } else { + return null; + } +}; + +export default Docker; diff --git a/cli/src/components/docker/InstallDocker.tsx b/cli/src/components/docker/InstallDocker.tsx new file mode 100644 index 000000000..b50ebbfcf --- /dev/null +++ b/cli/src/components/docker/InstallDocker.tsx @@ -0,0 +1,157 @@ +import React, { Fragment, useState } from "react"; +import { Box, Newline, Text, useInput } from "ink"; +import { Next, SetConfig } from "../../types"; +import YesNo from "../util/YesNo"; +import { installDockerUbuntu, InstallLog } from "../../helpers/docker"; + +const InstallDocker = ({ next }: { next: Next }) => { + const [stage, setStage] = useState< + | "userGroup" + | "sysCtlEnable" + | "confirm" + | "install" + | "installing" + | "finish" + | "error" + >("userGroup"); + const [addToUG, setAddToUG] = useState<"yes" | "no">(); + const [sysCtlEnable, setSysCtlEnable] = useState<"yes" | "no">(); + const [logs, setLogs] = useState([]); + useInput(async (_, key) => { + if (key.return) { + switch (stage) { + case "confirm": + const log = await installDockerUbuntu( + (log) => setLogs((logs) => [...logs, log]), + addToUG === "yes", + sysCtlEnable === "yes" + ); + if (log) { + // there was some error + setLogs((logs) => [...logs, log]) + setStage("error"); + } else { + setStage("finish"); + } + break; + + case "finish": + next(); + break; + + case "error": + setAddToUG(undefined); + setSysCtlEnable(undefined); + setStage("userGroup"); + break; + + default: + break; + } + } else if (key.leftArrow) { + switch (stage) { + case "sysCtlEnable": + setAddToUG(undefined); + setStage("userGroup"); + break; + + case "confirm": + setSysCtlEnable(undefined); + setStage("sysCtlEnable"); + break; + + default: + break; + } + } + }); + return ( + + + Docker Install Helper + + + {addToUG === undefined && ( + { + setAddToUG(res); + setStage("sysCtlEnable"); + }} + direction="vertical" + /> + )} + {addToUG !== undefined && ( + + add to user group: {addToUG} + + )} + {stage === "sysCtlEnable" && sysCtlEnable === undefined && ( + { + setSysCtlEnable(res); + setStage("confirm"); + }} + direction="vertical" + /> + )} + {sysCtlEnable !== undefined && ( + + start on boot: {sysCtlEnable} + + )} + {stage === "confirm" && ( + + + + press enter to install docker + + + )} + {(stage === "installing" || stage === "finish") && ( + + {stage === "installing" && installing...} + + {logs.map(({ stage, log }) => { + + + {stage} + + + command: {log.command} + + {log.log.stdout ? ( + + stdout: {log.log.stdout} + + ) : undefined} + {log.log.stderr ? ( + + stderr: {log.log.stderr} + + ) : undefined} + + ; + })} + + )} + {stage === "finish" && ( + + Docker has finished installing. Press enter{" "} + to continue. + + )} + {stage === "error" && ( + + There was an error during install. Press{" "} + enter to try again. + + )} + + ); +}; + +export default InstallDocker; diff --git a/cli/src/components/util/YesNo.tsx b/cli/src/components/util/YesNo.tsx index 3d941484c..cead4cdfe 100644 --- a/cli/src/components/util/YesNo.tsx +++ b/cli/src/components/util/YesNo.tsx @@ -5,12 +5,14 @@ const YesNo = ({ label, onYes, onNo, + onSelect, direction, labelColor }: { label: ReactNode; onYes?: () => void; onNo?: () => void; + onSelect?: (res: "yes" | "no") => void; direction?: "vertical" | "horizontal"; labelColor?: "green" | "white" }) => { @@ -22,8 +24,9 @@ const YesNo = ({ if (item === "yes") { onYes && onYes(); } else { - onNo && onNo() + onNo && onNo(); } + onSelect && onSelect(item as "yes" | "no"); }} direction={direction} labelColor={labelColor} diff --git a/cli/src/helpers/docker.ts b/cli/src/helpers/docker.ts new file mode 100644 index 000000000..16968d2a5 --- /dev/null +++ b/cli/src/helpers/docker.ts @@ -0,0 +1,116 @@ +import { CommandLogError } from "@monitor/types"; +import { execute } from "./execute"; + +export type InstallLog = { + stage: string; + log: CommandLogError; +}; + +export async function installDockerUbuntu( + onCommandEnd: (log: InstallLog) => void, + addToUserGroup?: boolean, + systemCtlEnable?: boolean +) { + const total = 5 + (addToUserGroup ? 1 : 0) + (systemCtlEnable ? 1 : 0); + const update = await execute("sudo apt-get update"); + if (update.isError) return { + stage: "error updating system", + log: update + }; + onCommandEnd({ + stage: `updated system (1 of ${total})`, + log: update, + }); + + + const installDeps = await execute(` + sudo apt-get install \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + `); + if (installDeps.isError) return { + stage: "error installing dependencies", + log: installDeps + }; + onCommandEnd({ + stage: `installed dependencies (2 of ${total})`, + log: installDeps, + }); + + + const addKey = await execute( + "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg" + ); + if (addKey.isError) return { + stage: "error adding docker key", + log: addKey, + }; + onCommandEnd({ + stage: `added docker key (3 of ${total})`, + log: addKey, + }); + + + const setStableRepository = await execute(` + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + `); + if (setStableRepository.isError) return { + stage: "error adding stable repository", + log: setStableRepository, + }; + onCommandEnd({ + stage: `set docker stable repository (4 of ${total})`, + log: setStableRepository, + }); + + const installDocker = await execute( + "sudo apt-get udpate && sudo apt-get install docker-ce docker-ce-cli containerd.io" + ); + if (installDocker.isError) return { + stage: "error installing docker", + log: installDocker, + }; + onCommandEnd({ + stage: `installed docker (5 of ${total})`, + log: installDocker, + }); + + if (addToUserGroup) { + const addUser = await execute( + "sudo groupadd docker && sudo usermod -aG docker $USER && newgrp docker" + ); + if (addUser.isError) return { + stage: "error adding user to docker group", + log: addUser, + }; + onCommandEnd({ + stage: `added user to docker user group (6 of ${total})`, + log: addUser, + }); + } + + if (systemCtlEnable) { + const startOnBoot = await execute( + "sudo systemctl enable docker.service && sudo systemctl enable containerd.service" + ); + if (startOnBoot.isError) return { + stage: "error configuring to start on boot", + log: startOnBoot, + }; + onCommandEnd({ + stage: `configured to start on boot (7 of ${total})`, + log: startOnBoot, + }); + } + + return; +} + +export async function checkDockerNotInstalled() { + const res = await execute("docker ps"); + return res.isError +} diff --git a/cli/src/monitor-cli.tsx b/cli/src/monitor-cli.tsx index bf3cfe660..80ea13ca0 100644 --- a/cli/src/monitor-cli.tsx +++ b/cli/src/monitor-cli.tsx @@ -3,6 +3,7 @@ import React from "react"; import { render } from "ink"; // import meow from "meow"; import App from "./App"; +import { checkDockerNotInstalled } from "./helpers/docker"; // const cli = meow( // ` @@ -24,5 +25,8 @@ import App from "./App"; // }, // } // ); - -render(); +export let dockerNotInstalled = true; +checkDockerNotInstalled().then((res) => { + dockerNotInstalled = res; + render(); +}); \ No newline at end of file diff --git a/cli/src/types.d.ts b/cli/src/types.d.ts index db02ed88b..a2c718a31 100644 --- a/cli/src/types.d.ts +++ b/cli/src/types.d.ts @@ -7,3 +7,5 @@ export type SetConfig = ( field: keyof Config, val: string | number | boolean ) => void; + +export type Next = () => void;