refactor: new dev/prod containers

- Use a single Dockerfile for all Zeppelin services
- Add a Dockerfile in project root that can be used by
  app hosting services
- Provide a standalone and lightweight prod setup
  - Standalone is the same as the old setup, with mysql+nginx
  - Lightweight only runs bot+backend+dash, no mysql/nginx
- Remove mounted mysql data folders for dev and prod
  - This resolves permission issues caused by the mount
  - The mysql service uses a regular named volume now
- Simplify .env options and clearly separate different prod setups
- Remove update.sh
  - Different setups require different update procedures, so a common
    update.sh no longer works
This commit is contained in:
Dragory 2024-03-17 18:49:31 +02:00
parent 730b8c1d6b
commit 509d96ce83
No known key found for this signature in database
30 changed files with 948 additions and 274 deletions

View file

@ -4,28 +4,28 @@
"description": "",
"private": true,
"scripts": {
"watch": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node start-dev.js\"",
"watch-yaml-parse-test": "cross-env NODE_ENV=development tsc-watch --onSuccess \"node dist/backend/src/yamlParseTest.js\"",
"watch": "NODE_ENV=development HOST_MODE=development tsc-watch --onSuccess \"node start-dev.js\"",
"watch-yaml-parse-test": "NODE_ENV=development HOST_MODE=development tsc-watch --onSuccess \"node dist/backend/src/yamlParseTest.js\"",
"build": "tsc --build",
"start-bot-dev": "cross-env NODE_ENV=development node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9229 dist/backend/src/index.js",
"start-bot-dev": "NODE_ENV=development HOST_MODE=development node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9229 dist/backend/src/index.js",
"start-bot-dev-debug": "NODE_ENV=development DEBUG=true clinic heapprofiler --collect-only --dest .clinic-bot -- node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9229 dist/backend/src/index.js",
"start-bot-prod": "cross-env NODE_ENV=production node --enable-source-maps --stack-trace-limit=30 dist/backend/src/index.js",
"start-bot-prod-debug": "NODE_ENV=production DEBUG=true clinic heapprofiler --collect-only --dest .clinic-bot -- node --enable-source-maps --stack-trace-limit=30 dist/backend/src/index.js",
"watch-bot": "cross-env NODE_ENV=development tsc-watch --onSuccess \"npm run start-bot-dev\"",
"start-api-dev": "cross-env NODE_ENV=development node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9239 dist/backend/src/api/index.js",
"watch-bot": "NODE_ENV=development HOST_MODE=development tsc-watch --onSuccess \"npm run start-bot-dev\"",
"start-api-dev": "NODE_ENV=development HOST_MODE=development node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9239 dist/backend/src/api/index.js",
"start-api-dev-debug": "NODE_ENV=development DEBUG=true clinic heapprofiler --collect-only --dest .clinic-api -- node --enable-source-maps --stack-trace-limit=30 --inspect=0.0.0.0:9239 dist/backend/src/api/index.js",
"start-api-prod": "cross-env NODE_ENV=production node --enable-source-maps --stack-trace-limit=30 dist/backend/src/api/index.js",
"start-api-prod-debug": "NODE_ENV=production DEBUG=true clinic heapprofiler --collect-only --dest .clinic-api -- node --enable-source-maps --stack-trace-limit=30 dist/backend/src/api/index.js",
"watch-api": "cross-env NODE_ENV=development tsc-watch --onSuccess \"npm run start-api-dev\"",
"watch-api": "NODE_ENV=development HOST_MODE=development tsc-watch --onSuccess \"npm run start-api-dev\"",
"typeorm": "node ./node_modules/typeorm/cli.js",
"migrate": "npm run typeorm -- migration:run -d dist/backend/src/data/dataSource.js",
"migrate-prod": "cross-env NODE_ENV=production npm run migrate",
"migrate-dev": "cross-env NODE_ENV=development npm run build && npm run migrate",
"migrate-dev": "NODE_ENV=development HOST_MODE=development npm run build && npm run migrate",
"migrate-rollback": "npm run typeorm -- migration:revert -d dist/backend/src/data/dataSource.js",
"migrate-rollback-prod": "cross-env NODE_ENV=production npm run migrate",
"migrate-rollback-dev": "cross-env NODE_ENV=development npm run build && npm run migrate",
"validate-active-configs": "cross-env NODE_ENV=development node --enable-source-maps dist/backend/src/validateActiveConfigs.js > ../config-errors.txt",
"export-config-json-schema": "cross-env NODE_ENV=development node --enable-source-maps dist/backend/src/exportSchemas.js > ../config-schema.json",
"migrate-rollback-dev": "NODE_ENV=development HOST_MODE=development npm run build && npm run migrate",
"validate-active-configs": "NODE_ENV=development HOST_MODE=development node --enable-source-maps dist/backend/src/validateActiveConfigs.js > ../config-errors.txt",
"export-config-json-schema": "NODE_ENV=development HOST_MODE=development node --enable-source-maps dist/backend/src/exportSchemas.js > ../config-schema.json",
"test": "npm run build && npm run run-tests",
"run-tests": "ava",
"test-watch": "tsc-watch --onSuccess \"npx ava\""

View file

@ -51,7 +51,7 @@ app.use((req, res, next) => {
return notFound(res);
});
const port = env.API_PORT;
const port = 3001;
app.listen(port, "0.0.0.0", () => console.log(`API server listening on port ${port}`)); // tslint:disable-line
startBackgroundTasks();

View file

@ -1,6 +1,7 @@
import moment from "moment-timezone";
import path from "path";
import { DataSource } from "typeorm";
import { MysqlConnectionOptions } from "typeorm/driver/mysql/MysqlConnectionOptions.js";
import { env } from "../env";
import { backendDir } from "../paths";
@ -9,13 +10,39 @@ moment.tz.setDefault("UTC");
const entities = path.relative(process.cwd(), path.resolve(backendDir, "dist/backend/src/data/entities/*.js"));
const migrations = path.relative(process.cwd(), path.resolve(backendDir, "dist/backend/src/migrations/*.js"));
type DbOpts = Pick<MysqlConnectionOptions, "host" | "port" | "username" | "password" | "database">;
let dbOpts: DbOpts;
if (env.HOST_MODE === "development") {
dbOpts = {
host: "mysql",
port: 3306,
username: "zeppelin",
password: env.DEVELOPMENT_MYSQL_PASSWORD,
database: "zeppelin",
};
} else if (env.HOST_MODE === "standalone") {
dbOpts = {
host: "mysql",
port: 3306,
username: "zeppelin",
password: env.STANDALONE_MYSQL_PASSWORD,
database: "zeppelin",
};
} else if (env.HOST_MODE === "lightweight") {
dbOpts = {
host: env.LIGHTWEIGHT_DB_HOST,
port: env.LIGHTWEIGHT_DB_PORT,
username: env.LIGHTWEIGHT_DB_USER,
password: env.LIGHTWEIGHT_DB_PASSWORD,
database: env.LIGHTWEIGHT_DB_DATABASE,
};
} else {
throw new Error(`Unknown host mode: ${env.HOST_MODE}`);
}
export const dataSource = new DataSource({
type: "mysql",
host: env.DB_HOST,
port: env.DB_PORT,
username: env.DB_USER,
password: env.DB_PASSWORD,
database: env.DB_DATABASE,
...dbOpts,
charset: "utf8mb4",
supportBigNumbers: true,
bigNumberStrings: true,

View file

@ -13,7 +13,6 @@ const envType = z.object({
DASHBOARD_URL: z.string().url(),
API_URL: z.string().url(),
API_PORT: z.preprocess((v) => Number(v), z.number().min(1).max(65535)).default(3000),
STAFF: z
.preprocess(
@ -39,18 +38,16 @@ const envType = z.object({
PHISHERMAN_API_KEY: z.string().optional(),
DOCKER_DEV_MYSQL_PASSWORD: z.string().optional(), // Included here for the DB_PASSWORD default in development
DOCKER_PROD_MYSQL_PASSWORD: z.string().optional(), // Included here for the DB_PASSWORD default in production
DEVELOPMENT_MYSQL_PASSWORD: z.string().optional(), // Included here for the DB_PASSWORD default in development
STANDALONE_MYSQL_PASSWORD: z.string().optional(), // Included here for the DB_PASSWORD default in production
DB_HOST: z.string().optional().default("mysql"),
DB_PORT: z
.preprocess((v) => Number(v), z.number())
.optional()
.default(3306),
DB_USER: z.string().optional().default("zeppelin"),
DB_PASSWORD: z.string().optional(), // Default is set to DOCKER_MYSQL_PASSWORD further below
DB_DATABASE: z.string().optional().default("zeppelin"),
LIGHTWEIGHT_DB_HOST: z.string().optional(),
LIGHTWEIGHT_DB_PORT: z.preprocess((v) => Number(v), z.number()).optional(),
LIGHTWEIGHT_DB_USER: z.string().optional(),
LIGHTWEIGHT_DB_PASSWORD: z.string().optional(),
LIGHTWEIGHT_DB_DATABASE: z.string().optional(),
HOST_MODE: z.enum(["development", "standalone", "lightweight"]).optional().default("lightweight"),
DEBUG: z
.string()
.optional()
@ -65,11 +62,3 @@ if (fs.existsSync(envPath)) {
}
export const env = envType.parse(toValidate);
if (!env.DB_PASSWORD) {
if (process.env.NODE_ENV === "production" && env.DOCKER_PROD_MYSQL_PASSWORD) {
env.DB_PASSWORD = env.DOCKER_PROD_MYSQL_PASSWORD;
} else if (env.DOCKER_DEV_MYSQL_PASSWORD) {
env.DB_PASSWORD = env.DOCKER_DEV_MYSQL_PASSWORD;
}
}

View file

@ -164,7 +164,7 @@ if (process.env.NODE_ENV === "production") {
}
// Verify required Node.js version
const REQUIRED_NODE_VERSION = "14.0.0";
const REQUIRED_NODE_VERSION = "16.9.0";
const requiredParts = REQUIRED_NODE_VERSION.split(".").map((v) => parseInt(v, 10));
const actualVersionParts = process.versions.node.split(".").map((v) => parseInt(v, 10));
for (const [i, part] of actualVersionParts.entries()) {