diff --git a/README.md b/README.md index a47cd9c..ffc503e 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ deno task up auth client # some services deno task logs # logs of all services deno task logs client # logs of one service deno task down # stop everything +deno task reset # remove containers, s3 data and databases +deno task reset --configs # ...and the service configs +deno task reset --cache # ...and the deno cache ``` ## S3 diff --git a/deno.json b/deno.json index 90ec6f7..ad5a25e 100644 --- a/deno.json +++ b/deno.json @@ -2,6 +2,7 @@ "tasks": { "setup": "deno run --allow-read --allow-write --allow-run=git,docker setup.ts", "seed": "deno run --allow-read --allow-write --allow-net=localhost:2024 seed.ts", + "reset": "deno run --allow-read --allow-write --allow-run=docker reset.ts", "up": "docker compose up -d --build", "logs": "docker compose logs -f --tail=100", "down": "docker compose down" diff --git a/reset.ts b/reset.ts new file mode 100644 index 0000000..2d315d8 --- /dev/null +++ b/reset.ts @@ -0,0 +1,110 @@ +import { exists, expandGlob } from "@std/fs"; +import { join, relative } from "@std/path"; +import { + getReposDir, + loadEnv, + ok, + readYaml, + ROOT, + SERVICES, + warn, +} from "./utils.ts"; + +const SKIP_CONFIRM = Deno.args.includes("--yes"); +const RESET_CONFIGS = Deno.args.includes("--configs"); +const RESET_CACHE = Deno.args.includes("--cache"); + +const docker = async (...args: string[]): Promise => { + try { + const { success } = await new Deno.Command("docker", { + args, + cwd: ROOT, + stdout: "null", + stderr: "null", + }).output(); + + return success; + } catch { + return false; + } +}; + +const getPaths = async (reposDir: string): Promise => { + const paths: string[] = []; + + for (const service of SERVICES) { + const repoPath = join(reposDir, service.repo); + if (!(await exists(repoPath))) continue; + + const patterns = [ + ...service.data, + ...(RESET_CONFIGS ? [service.config] : []), + ]; + + for (const pattern of patterns) { + for await (const { path } of expandGlob(join(repoPath, pattern))) { + paths.push(path); + } + } + } + + return paths; +}; + +const reposDir = getReposDir(await loadEnv()); +const { name: project } = await readYaml<{ name: string }>( + join(ROOT, "compose.yml"), +); +const volumes = ["s3-data", ...(RESET_CACHE ? ["deno-cache"] : [])]; +const paths = await getPaths(reposDir); + +console.log(`This will remove: + - containers and their node_modules + - docker volumes: ${volumes.join(", ")} +${paths.map((path) => ` - ${relative(reposDir, path)}`).join("\n")} +`); + +if (!SKIP_CONFIRM && !confirm("Continue?")) Deno.exit(0); + +const containersRemoved = await docker( + "compose", + "rm", + "--stop", + "--force", + "--volumes", +); + +const networkRemoved = await docker("compose", "down"); + +if (containersRemoved && networkRemoved) { + ok("containers removed"); +} else { + warn("containers could not be removed"); +} + +for (const volume of volumes) { + const name = `${project}_${volume}`; + + const volumeExists = await docker("volume", "inspect", name); + if (!volumeExists) continue; + + const volumeRemoved = await docker("volume", "rm", name); + if (volumeRemoved) ok(`volume ${volume} removed`); + else warn(`volume ${volume} could not be removed`); +} + +for (const path of paths) { + try { + await Deno.remove(path, { recursive: true }); + ok(`${relative(reposDir, path)} removed`); + } catch (e) { + warn(`${relative(reposDir, path)} could not be removed: ${e}`); + } +} + +console.log(` +Done! Next steps:${RESET_CONFIGS ? "\n 0. deno task setup" : ""} + 1. deno task up auth + 2. deno task seed + 3. deno task up +`); diff --git a/setup.ts b/setup.ts index ba054ac..d4960a0 100644 --- a/setup.ts +++ b/setup.ts @@ -7,48 +7,11 @@ import { ok, readYaml, ROOT, + type Service, + SERVICES, warn, } from "./utils.ts"; -type Service = { - name: string; - repo: string; - config: string; -}; - -const SERVICES: Service[] = [ - { - name: "client", - repo: "openhotel", - config: "app/server/config.yml", - }, - { - name: "auth", - repo: "auth", - config: "app/server/config.yml", - }, - { - name: "onet", - repo: "onet", - config: "config.yml", - }, - { - name: "web", - repo: "web", - config: "app/server/config.yml", - }, - { - name: "asset-editor", - repo: "asset-editor", - config: "app/server/config.yml", - }, - { - name: "static", - repo: "static", - config: "app/server/config.yml", - }, -]; - const run = async (cmd: string, ...args: string[]): Promise => { try { const { success } = await new Deno.Command(cmd, { diff --git a/utils.ts b/utils.ts index 0018874..05af705 100644 --- a/utils.ts +++ b/utils.ts @@ -5,6 +5,56 @@ import { join, resolve } from "@std/path"; export const ROOT = import.meta.dirname!; +export type Service = { + name: string; + repo: string; + config: string; + data: string[]; +}; + +export const SERVICES: Service[] = [ + { + name: "client", + repo: "openhotel", + config: "app/server/config.yml", + data: ["app/server/server-database*", "app/server/database-backups"], + }, + { + name: "auth", + repo: "auth", + config: "app/server/config.yml", + data: [ + "app/server/database*", + "app/server/deleteme-database*", + "app/server/backups", + ], + }, + { + name: "onet", + repo: "onet", + config: "config.yml", + data: ["database*", "collections-key"], + }, + { + name: "web", + repo: "web", + config: "app/server/config.yml", + data: ["app/server/database*", "app/server/backups"], + }, + { + name: "asset-editor", + repo: "asset-editor", + config: "app/server/config.yml", + data: [], + }, + { + name: "static", + repo: "static", + config: "app/server/config.yml", + data: ["app/server/database*", "app/server/files"], + }, +]; + export const ok = (message: string) => console.log(`%c✔ ${message}`, "color: green"); export const warn = (message: string) =>