metamigo-api: runs in docker now
* great typescript module import refactor * refactor metamigo-cli so it is the entrypoint for the db, api, and worker
This commit is contained in:
parent
b45e2e8c11
commit
696ba16cb7
78 changed files with 319 additions and 180 deletions
12
apps/metamigo-cli/.eslintrc.js
Normal file
12
apps/metamigo-cli/.eslintrc.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
require("eslint-config-link/patch/modern-module-resolution");
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/profile/typescript",
|
||||
"eslint-config-link/profile/jest",
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
rules: {
|
||||
"new-cap": "off"
|
||||
},
|
||||
};
|
||||
54
apps/metamigo-cli/Dockerfile
Normal file
54
apps/metamigo-cli/Dockerfile
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
FROM node:20 as base
|
||||
|
||||
FROM base AS builder
|
||||
ARG APP_DIR=/opt/metamigo-cli
|
||||
RUN mkdir -p ${APP_DIR}/
|
||||
RUN npm i -g turbo
|
||||
WORKDIR ${APP_DIR}
|
||||
COPY . .
|
||||
RUN turbo prune --scope=@digiresilience/metamigo-cli --docker
|
||||
|
||||
|
||||
FROM base AS installer
|
||||
ARG APP_DIR=/opt/metamigo-cli
|
||||
WORKDIR ${APP_DIR}
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=builder ${APP_DIR}/out/json/ .
|
||||
COPY --from=builder ${APP_DIR}/out/package-lock.json ./package-lock.json
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY --from=builder ${APP_DIR}/out/full/ .
|
||||
RUN npm i -g turbo
|
||||
RUN turbo run build --filter=metamigo-cli
|
||||
|
||||
FROM base AS runner
|
||||
ARG APP_DIR=/opt/metamigo-cli
|
||||
WORKDIR ${APP_DIR}/
|
||||
ARG BUILD_DATE
|
||||
ARG VERSION
|
||||
LABEL maintainer="Darren Clarke <darren@redaranj.com>"
|
||||
LABEL org.label-schema.build-date=$BUILD_DATE
|
||||
LABEL org.label-schema.version=$VERSION
|
||||
ENV APP_DIR ${APP_DIR}
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
dumb-init
|
||||
RUN mkdir -p ${APP_DIR}
|
||||
RUN chown -R node ${APP_DIR}/
|
||||
|
||||
USER node
|
||||
WORKDIR ${APP_DIR}
|
||||
COPY --from=installer ${APP_DIR}/node_modules/ ./node_modules/
|
||||
COPY --from=installer ${APP_DIR}/packages/ ./packages/
|
||||
COPY --from=installer ${APP_DIR}/apps/metamigo-cli/ ./apps/metamigo-cli/
|
||||
COPY --from=installer ${APP_DIR}/apps/metamigo-api/ ./apps/metamigo-api/
|
||||
COPY --from=installer ${APP_DIR}/apps/metamigo-worker/ ./apps/metamigo-worker/
|
||||
COPY --from=installer ${APP_DIR}/package.json ./package.json
|
||||
USER root
|
||||
WORKDIR ${APP_DIR}/apps/metamigo-cli/
|
||||
RUN chmod +x docker-entrypoint.sh
|
||||
USER node
|
||||
EXPOSE 3000
|
||||
ENV PORT 3000
|
||||
ENV NODE_ENV production
|
||||
ENTRYPOINT ["/opt/metamigo-cli/apps/metamigo-cli/docker-entrypoint.sh"]
|
||||
3
apps/metamigo-cli/babel.config.json
Normal file
3
apps/metamigo-cli/babel.config.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["babel-preset-link"]
|
||||
}
|
||||
4
apps/metamigo-cli/cli
Executable file
4
apps/metamigo-cli/cli
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
node ./build/main/index.js ${@}
|
||||
21
apps/metamigo-cli/docker-entrypoint.sh
Normal file
21
apps/metamigo-cli/docker-entrypoint.sh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [[ "$1" == "api" ]]; then
|
||||
echo "docker-entrypoint: starting api server"
|
||||
./cli db -- migrate
|
||||
exec dumb-init ./cli api
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
echo "docker-entrypoint: starting worker"
|
||||
exec dumb-init ./cli worker
|
||||
elif [[ "$1" == "frontend" ]]; then
|
||||
echo "docker-entrypoint: starting frontend"
|
||||
exec dumb-init yarn workspace @app/frontend start
|
||||
elif [[ "$1" == "cli" ]]; then
|
||||
echo "docker-entrypoint: starting frontend"
|
||||
shift 1
|
||||
exec ./cli "$@"
|
||||
else
|
||||
echo "docker-entrypoint: missing argument, one of: api, worker, frontend, cli"
|
||||
exit 1
|
||||
fi
|
||||
4
apps/metamigo-cli/jest.config.json
Normal file
4
apps/metamigo-cli/jest.config.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"preset": "jest-config-link",
|
||||
"setupFiles": ["<rootDir>/src/setup.test.ts"]
|
||||
}
|
||||
42
apps/metamigo-cli/package.json
Normal file
42
apps/metamigo-cli/package.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "@digiresilience/metamigo-cli",
|
||||
"version": "0.2.0",
|
||||
"main": "build/main/index.js",
|
||||
"author": "Abel Luck <abel@guardianproject.info>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"metamigo": "./build/main/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@digiresilience/montar": "*",
|
||||
"@digiresilience/metamigo-config": "*",
|
||||
"@digiresilience/metamigo-db": "*",
|
||||
"@digiresilience/metamigo-api": "*",
|
||||
"@digiresilience/metamigo-worker": "*",
|
||||
"commander": "^10.0.1",
|
||||
"graphile-migrate": "^1.4.1",
|
||||
"graphile-worker": "^0.13.0",
|
||||
"node-jose": "^2.2.0",
|
||||
"postgraphile": "4.13.0",
|
||||
"graphql": "16.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"tsconfig-link": "*",
|
||||
"eslint-config-link": "*",
|
||||
"jest-config-link": "*",
|
||||
"babel-preset-link": "*",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"cli": "NODE_ENV=development node --unhandled-rejections=strict build/main/index.js",
|
||||
"fix:lint": "eslint src --ext .ts --fix",
|
||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||
"test": "echo no tests"
|
||||
}
|
||||
}
|
||||
22
apps/metamigo-cli/src/config.ts
Normal file
22
apps/metamigo-cli/src/config.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import {
|
||||
generateConfig,
|
||||
printConfigOptions,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import { IAppConfig, IAppConvict } from "@digiresilience/metamigo-config";
|
||||
import { loadConfigRaw } from "@digiresilience/metamigo-config";
|
||||
|
||||
export const genConf = async (): Promise<void> => {
|
||||
const c = await loadConfigRaw() as any;
|
||||
const generated = generateConfig(c) as any;
|
||||
console.log(generated);
|
||||
};
|
||||
|
||||
export const genSchema = async (): Promise<void> => {
|
||||
const c: any = await loadConfigRaw();
|
||||
console.log(c.getSchemaString());
|
||||
};
|
||||
|
||||
export const listConfig = async (): Promise<void> => {
|
||||
const c = await loadConfigRaw() as any;
|
||||
printConfigOptions(c);
|
||||
};
|
||||
66
apps/metamigo-cli/src/index.ts
Normal file
66
apps/metamigo-cli/src/index.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from "commander";
|
||||
import { startWithout } from "@digiresilience/montar";
|
||||
import { migrateWrapper } from "@digiresilience/metamigo-db";
|
||||
import { loadConfig } from "@digiresilience/metamigo-config";
|
||||
import { genConf, listConfig } from "./config.js";
|
||||
import { createTokenForTesting, generateJwks } from "./jwks.js";
|
||||
import { exportGraphqlSchema } from "./metamigo-postgraphile.js";
|
||||
import "@digiresilience/metamigo-api";
|
||||
import "@digiresilience/metamigo-worker";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
export async function runServer(): Promise<void> {
|
||||
await startWithout(["worker"]);
|
||||
}
|
||||
|
||||
export async function runWorker(): Promise<void> {
|
||||
await startWithout(["server"]);
|
||||
}
|
||||
|
||||
program
|
||||
.command("config-generate")
|
||||
.description("Generate a sample JSON configuration file (to stdout)")
|
||||
.action(genConf);
|
||||
|
||||
program
|
||||
.command("config-help")
|
||||
.description("Prints the entire convict config ")
|
||||
.action(listConfig);
|
||||
|
||||
program
|
||||
.command("api")
|
||||
.description("Run the application api server")
|
||||
.action(runServer);
|
||||
|
||||
program
|
||||
.command("worker")
|
||||
.description("Run the worker to process jobs")
|
||||
.action(runWorker);
|
||||
|
||||
program
|
||||
.command("db <commands...>")
|
||||
.description("Run graphile-migrate commands with your app's config loaded.")
|
||||
.action(async (args) => {
|
||||
const config = await loadConfig();
|
||||
return migrateWrapper(args, config);
|
||||
});
|
||||
|
||||
program
|
||||
.command("gen-jwks")
|
||||
.description("Generate the JWKS")
|
||||
.action(generateJwks);
|
||||
|
||||
program
|
||||
.command("gen-testing-jwt")
|
||||
.description("Generate a JWT for the test suite")
|
||||
.action(createTokenForTesting);
|
||||
|
||||
program
|
||||
.command("export-graphql-schema")
|
||||
.description("Export the graphql schema")
|
||||
.action(exportGraphqlSchema);
|
||||
|
||||
program.parse(process.argv);
|
||||
67
apps/metamigo-cli/src/jwks.ts
Normal file
67
apps/metamigo-cli/src/jwks.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import jose from "node-jose";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
|
||||
const generateKeystore = async () => {
|
||||
const keystore = jose.JWK.createKeyStore();
|
||||
await keystore.generate("oct", 256, {
|
||||
alg: "A256GCM",
|
||||
use: "enc",
|
||||
});
|
||||
await keystore.generate("oct", 256, {
|
||||
alg: "HS512",
|
||||
use: "sig",
|
||||
});
|
||||
return keystore;
|
||||
};
|
||||
|
||||
const safeString = (input) =>
|
||||
Buffer.from(JSON.stringify(input)).toString("base64");
|
||||
|
||||
const stringify = (v) => JSON.stringify(v, undefined, 2);
|
||||
|
||||
const _generateJwks = async () => {
|
||||
const keystore = await generateKeystore();
|
||||
const encryption = keystore.all({ use: "enc" })[0].toJSON(true);
|
||||
const signing = keystore.all({ use: "sig" })[0].toJSON(true);
|
||||
|
||||
return {
|
||||
nextAuth: {
|
||||
signingKeyB64: safeString(signing),
|
||||
encryptionKeyB64: safeString(encryption),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const generateJwks = async (): Promise<void> => {
|
||||
console.log(stringify(await _generateJwks()));
|
||||
};
|
||||
|
||||
export const createTokenForTesting = async (): Promise<void> => {
|
||||
const keys = await _generateJwks();
|
||||
const signingKey = Buffer.from(
|
||||
JSON.parse(
|
||||
Buffer.from(keys.nextAuth.signingKeyB64, "base64").toString("utf-8")
|
||||
).k,
|
||||
"base64"
|
||||
);
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
iss: "Test Env",
|
||||
iat: 1606893960,
|
||||
aud: "metamigo",
|
||||
sub: "abel@guardianproject.info",
|
||||
name: "Abel Luck",
|
||||
email: "abel@guardianproject.info",
|
||||
userRole: "admin",
|
||||
},
|
||||
signingKey,
|
||||
{ expiresIn: "100y", algorithm: "HS512" }
|
||||
);
|
||||
console.log("CONFIG");
|
||||
console.log(stringify(keys));
|
||||
console.log();
|
||||
console.log("TOKEN");
|
||||
console.log(token);
|
||||
console.log();
|
||||
};
|
||||
40
apps/metamigo-cli/src/metamigo-postgraphile.ts
Normal file
40
apps/metamigo-cli/src/metamigo-postgraphile.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { writeFileSync } from "node:fs";
|
||||
import {
|
||||
getIntrospectionQuery,
|
||||
GraphQLSchema,
|
||||
graphqlSync,
|
||||
lexicographicSortSchema,
|
||||
printSchema,
|
||||
} from "graphql";
|
||||
import { createPostGraphileSchema } from "postgraphile";
|
||||
import pg from "pg";
|
||||
import { loadConfig } from "@digiresilience/metamigo-config";
|
||||
import { getPostGraphileOptions } from "@digiresilience/metamigo-db";
|
||||
|
||||
const {Pool} = pg;
|
||||
|
||||
export const exportGraphqlSchema = async (): Promise<void> => {
|
||||
const config = await loadConfig();
|
||||
|
||||
const rootPgPool = new Pool({
|
||||
connectionString: config.db.connection,
|
||||
});
|
||||
const exportSchema = `../../data/schema.graphql`;
|
||||
const exportJson = `../../frontend/lib/graphql-schema.json`;
|
||||
try {
|
||||
const schema = (await createPostGraphileSchema(
|
||||
config.postgraphile.authConnection,
|
||||
"app_public",
|
||||
getPostGraphileOptions()
|
||||
)) as unknown as GraphQLSchema;
|
||||
const sorted = lexicographicSortSchema(schema);
|
||||
const json = graphqlSync({ schema, source: getIntrospectionQuery() });
|
||||
writeFileSync(exportSchema, printSchema(sorted));
|
||||
writeFileSync(exportJson, JSON.stringify(json));
|
||||
|
||||
console.log(`GraphQL schema exported to ${exportSchema}`);
|
||||
console.log(`GraphQL schema json exported to ${exportJson}`);
|
||||
} finally {
|
||||
rootPgPool.end();
|
||||
}
|
||||
};
|
||||
14
apps/metamigo-cli/tsconfig.json
Normal file
14
apps/metamigo-cli/tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"outDir": "build/main",
|
||||
"rootDir": "src",
|
||||
"baseUrl": "./",
|
||||
"skipLibCheck": true,
|
||||
"types": ["jest", "node"],
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue