From 56c9d51d132f87f9e9c45727cdf9932a95d083ea Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Wed, 16 Aug 2023 21:18:49 +0200 Subject: [PATCH] feat: create app update event --- .github/workflows/ci.yml | 6 +++ packages/cli/assets/docker-compose.yml | 20 +++++----- packages/cli/build.js | 2 +- packages/cli/package.json | 3 +- packages/cli/src/executors/app/app.helpers.ts | 4 +- packages/cli/src/executors/app/env.helpers.ts | 30 -------------- packages/cli/src/services/watcher/watcher.ts | 6 ++- packages/shared/.eslintignore | 1 + packages/shared/.eslintrc.js | 39 ++++++++++++++++++ packages/shared/package.json | 5 ++- packages/shared/src/schemas/queue-schemas.ts | 10 ++++- packages/shared/tsconfig.json | 1 - src/server/services/apps/apps.helpers.ts | 40 ------------------- src/server/services/system/system.service.ts | 10 ++--- 14 files changed, 83 insertions(+), 94 deletions(-) create mode 100644 packages/shared/.eslintignore create mode 100644 packages/shared/.eslintrc.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a77ec7b5..413bbda6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,12 @@ jobs: - name: Install dependencies run: pnpm install + - name: Run linter + run: pnpm run lint + + - name: Run linter on packages + run: pnpm -r run lint + - name: Get number of CPU cores id: cpu-cores uses: SimenB/github-actions-cpu-cores@v1 diff --git a/packages/cli/assets/docker-compose.yml b/packages/cli/assets/docker-compose.yml index b24baf4c..f2dea63c 100644 --- a/packages/cli/assets/docker-compose.yml +++ b/packages/cli/assets/docker-compose.yml @@ -11,8 +11,8 @@ services: command: --providers.docker volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - - ${PWD}/traefik:/root/.config - - ${PWD}/traefik/shared:/shared + - ./traefik:/root/.config + - ./traefik/shared:/shared networks: - tipi_main_network @@ -22,7 +22,7 @@ services: restart: on-failure stop_grace_period: 1m volumes: - - ${PWD}/data/postgres:/var/lib/postgresql/data + - ./data/postgres:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_USER: tipi @@ -65,14 +65,14 @@ services: env_file: - .env environment: - NODE_ENV: development + NODE_ENV: production volumes: - - ${PWD}/.env:/runtipi/.env - - ${PWD}/state:/runtipi/state - - ${PWD}/repos:/runtipi/repos:ro - - ${PWD}/apps:/runtipi/apps - - ${PWD}/logs:/app/logs - - ${PWD}/traefik:/runtipi/traefik + - ./.env:/runtipi/.env + - ./state:/runtipi/state + - ./repos:/runtipi/repos:ro + - ./apps:/runtipi/apps + - ./logs:/app/logs + - ./traefik:/runtipi/traefik - ${STORAGE_PATH}:/app/storage labels: # Main diff --git a/packages/cli/build.js b/packages/cli/build.js index 102a666b..98e885cc 100644 --- a/packages/cli/build.js +++ b/packages/cli/build.js @@ -8,7 +8,7 @@ async function bundle() { entryPoints: ['./src/index.ts'], outfile: './dist/index.js', platform: 'node', - target: 'node20', + target: 'node18', bundle: true, color: true, sourcemap: commandArgs.includes('--sourcemap'), diff --git a/packages/cli/package.json b/packages/cli/package.json index a5a1b401..368a3646 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -13,7 +13,8 @@ "build": "node build.js", "build:meta": "esbuild ./src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --metafile=meta.json --analyze", "dev": "dotenv -e ../../.env nodemon", - "lint": "eslint . --ext .ts" + "lint": "eslint . --ext .ts", + "tsc": "tsc --noEmit" }, "pkg": { "assets": "assets/**/*", diff --git a/packages/cli/src/executors/app/app.helpers.ts b/packages/cli/src/executors/app/app.helpers.ts index 3c34fadf..91e87fe8 100644 --- a/packages/cli/src/executors/app/app.helpers.ts +++ b/packages/cli/src/executors/app/app.helpers.ts @@ -1,9 +1,9 @@ import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; -import { appInfoSchema } from '@runtipi/shared'; +import { appInfoSchema, envMapToString, envStringToMap } from '@runtipi/shared'; import { getEnv } from '@/utils/environment/environment'; -import { envMapToString, envStringToMap, generateVapidKeys, getAppEnvMap } from './env.helpers'; +import { generateVapidKeys, getAppEnvMap } from './env.helpers'; import { pathExists } from '@/utils/fs-helpers'; /** diff --git a/packages/cli/src/executors/app/env.helpers.ts b/packages/cli/src/executors/app/env.helpers.ts index bff60291..ea44550f 100644 --- a/packages/cli/src/executors/app/env.helpers.ts +++ b/packages/cli/src/executors/app/env.helpers.ts @@ -3,36 +3,6 @@ import fs from 'fs'; import path from 'path'; import { getEnv } from '@/utils/environment/environment'; -/** - * Convert a string of environment variables to a Map - * - * @param {string} envString - String of environment variables - */ -export const envStringToMap = (envString: string) => { - const envMap = new Map(); - const envArray = envString.split('\n'); - - envArray.forEach((env) => { - const [key, value] = env.split('='); - if (key && value) { - envMap.set(key, value); - } - }); - - return envMap; -}; - -/** - * Convert a Map of environment variables to a valid string of environment variables - * that can be used in a .env file - * - * @param {Map} envMap - Map of environment variables - */ -export const envMapToString = (envMap: Map) => { - const envArray = Array.from(envMap).map(([key, value]) => `${key}=${value}`); - return envArray.join('\n'); -}; - /** * This function reads the env file for the app with the provided id and returns a Map containing the key-value pairs of the environment variables. * It reads the app.env file, splits it into individual environment variables, and stores them in a Map, with the environment variable name as the key and its value as the value. diff --git a/packages/cli/src/services/watcher/watcher.ts b/packages/cli/src/services/watcher/watcher.ts index 0b749565..9e4b39bd 100644 --- a/packages/cli/src/services/watcher/watcher.ts +++ b/packages/cli/src/services/watcher/watcher.ts @@ -9,7 +9,7 @@ const execAsync = promisify(exec); const runCommand = async (jobData: unknown) => { const { installApp, startApp, stopApp, uninstallApp, updateApp, regenerateAppEnv } = new AppExecutors(); const { cloneRepo, pullRepo } = new RepoExecutors(); - const { systemInfo, restart } = new SystemExecutors(); + const { systemInfo, restart, update } = new SystemExecutors(); const event = eventSchema.safeParse(jobData); @@ -62,6 +62,10 @@ const runCommand = async (jobData: unknown) => { if (data.command === 'restart') { ({ success, message } = await restart()); } + + if (data.command === 'update') { + ({ success, message } = await update(data.version)); + } } return { success, message }; diff --git a/packages/shared/.eslintignore b/packages/shared/.eslintignore new file mode 100644 index 00000000..a9ba028c --- /dev/null +++ b/packages/shared/.eslintignore @@ -0,0 +1 @@ +.eslintrc.js diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js new file mode 100644 index 00000000..13f66352 --- /dev/null +++ b/packages/shared/.eslintrc.js @@ -0,0 +1,39 @@ +module.exports = { + root: true, + plugins: ['@typescript-eslint', 'import'], + extends: ['plugin:@typescript-eslint/recommended', 'airbnb', 'airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript', 'prettier'], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, + rules: { + 'import/prefer-default-export': 0, + 'class-methods-use-this': 0, + 'import/extensions': [ + 'error', + 'ignorePackages', + { + '': 'never', + js: 'never', + jsx: 'never', + ts: 'never', + tsx: 'never', + }, + ], + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: ['build.js', '**/*.test.{ts,tsx}', '**/mocks/**', '**/__mocks__/**', '**/*.setup.{ts,js}', '**/*.config.{ts,js}', '**/tests/**'], + }, + ], + 'arrow-body-style': 0, + 'no-underscore-dangle': 0, + 'no-console': 0, + }, + globals: { + NodeJS: true, + }, +}; diff --git a/packages/shared/package.json b/packages/shared/package.json index 495a99b9..0313fc5d 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -3,7 +3,10 @@ "version": "1.0.0", "description": "", "main": "src/index.ts", - "scripts": {}, + "scripts": { + "lint": "eslint --ext .ts src", + "tsc": "tsc --noEmit" + }, "keywords": [], "author": "", "license": "ISC", diff --git a/packages/shared/src/schemas/queue-schemas.ts b/packages/shared/src/schemas/queue-schemas.ts index 05eb8510..22eb5f3d 100644 --- a/packages/shared/src/schemas/queue-schemas.ts +++ b/packages/shared/src/schemas/queue-schemas.ts @@ -23,10 +23,16 @@ const repoCommandSchema = z.object({ const systemCommandSchema = z.object({ type: z.literal(EVENT_TYPES.SYSTEM), - command: z.union([z.literal('restart'), z.literal('update'), z.literal('system_info')]), + command: z.union([z.literal('restart'), z.literal('system_info')]), }); -export const eventSchema = appCommandSchema.or(repoCommandSchema).or(systemCommandSchema); +const updateSchema = z.object({ + type: z.literal(EVENT_TYPES.SYSTEM), + command: z.literal('update'), + version: z.string(), +}); + +export const eventSchema = appCommandSchema.or(repoCommandSchema).or(systemCommandSchema).or(updateSchema); export const eventResultSchema = z.object({ success: z.boolean(), diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 875960b3..b0866020 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -19,7 +19,6 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true, "strictNullChecks": true, "allowSyntheticDefaultImports": true, "noUncheckedIndexedAccess": true, diff --git a/src/server/services/apps/apps.helpers.ts b/src/server/services/apps/apps.helpers.ts index 5443589e..26b7033d 100644 --- a/src/server/services/apps/apps.helpers.ts +++ b/src/server/services/apps/apps.helpers.ts @@ -1,6 +1,4 @@ -import fs from 'fs-extra'; import { App } from '@/server/db/schema'; -import { getAppEnvMap } from '@/server/utils/env-generation'; import { appInfoSchema } from '@runtipi/shared'; import { fileExists, readdirSync, readFile, readJsonFile } from '../../common/fs.helpers'; import { getConfig } from '../../core/TipiConfig'; @@ -32,44 +30,6 @@ export const checkAppRequirements = (appName: string) => { return parsedConfig.data; }; -/** - * This function checks if the env file for the app with the provided name is valid. - * It reads the config.json file for the app, parses it, - * and uses the app's form fields to check if all required fields are present in the env file. - * If the config.json file is invalid, it throws an error. - * If a required variable is missing in the env file, it throws an error. - * - * @param {string} appName - The name of the app. - * @throws Will throw an error if the app has an invalid config.json file or if a required variable is missing in the env file. - */ -export const checkEnvFile = async (appName: string) => { - const configFile = await fs.promises.readFile(`/runtipi/apps/${appName}/config.json`); - - let jsonConfig: unknown; - try { - jsonConfig = JSON.parse(configFile.toString()); - } catch (e) { - throw new Error(`App ${appName} has invalid config.json file`); - } - - const parsedConfig = appInfoSchema.safeParse(jsonConfig); - - if (!parsedConfig.success) { - throw new Error(`App ${appName} has invalid config.json file`); - } - - const envMap = await getAppEnvMap(appName); - - parsedConfig.data.form_fields.forEach((field) => { - const envVar = field.env_variable; - const envVarValue = envMap.get(envVar); - - if (!envVarValue && field.required) { - throw new Error('New info needed. App config needs to be updated'); - } - }); -}; - /** This function reads the apps directory and skips certain system files, then reads the config.json and metadata/description.md files for each app, parses the config file, filters out any apps that are not available and returns an array of app information. diff --git a/src/server/services/system/system.service.ts b/src/server/services/system/system.service.ts index 020a9d80..6b118be5 100644 --- a/src/server/services/system/system.service.ts +++ b/src/server/services/system/system.service.ts @@ -48,16 +48,16 @@ export class SystemServiceClass { let body = await this.cache.get('latestVersionBody'); if (!version) { - const { data } = await axios.get<{ name: string; body: string }>('https://api.github.com/repos/meienberger/runtipi/releases/latest'); + const { data } = await axios.get<{ tag_name: string; body: string }>('https://api.github.com/repos/meienberger/runtipi/releases/latest'); - version = data.name.replace('v', ''); + version = data.tag_name; body = data.body; - await this.cache.set('latestVersion', version?.replace('v', '') || '', 60 * 60); + await this.cache.set('latestVersion', version || '', 60 * 60); await this.cache.set('latestVersionBody', body || '', 60 * 60); } - return { current: TipiConfig.getConfig().version, latest: version?.replace('v', ''), body }; + return { current: TipiConfig.getConfig().version, latest: version, body }; } catch (e) { Logger.error(e); return { current: TipiConfig.getConfig().version, latest: undefined }; @@ -101,7 +101,7 @@ export class SystemServiceClass { TipiConfig.setConfig('status', 'UPDATING'); - this.dispatcher.dispatchEvent({ type: 'system', command: 'update' }); + this.dispatcher.dispatchEvent({ type: 'system', command: 'update', version: latest }); return true; };