chore(sentry): avoid sending errors related to custom user configs (#1579)

This commit is contained in:
Nicolas Meienberger 2024-08-06 21:42:21 +02:00 committed by GitHub
parent eef0485436
commit f31daec736
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 36 additions and 68 deletions

View File

@ -6,10 +6,13 @@ const IgnoreErrors = [
// Innocuous browser errors
/ResizeObserver loop limit exceeded/,
/ResizeObserver loop completed with undelivered notifications/,
// Error on user's side
/no space left on device/,
// Dark reader extension
/WeakMap key undefined must be an object or an unregistered symbol/,
// Docker-compose error
/no space left on device/,
/port is already allocated/,
/address already in use/,
/Error with your custom app/,
];
const cleanseUrl = (url: string) => {

View File

@ -1,17 +1,21 @@
import path from 'path';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { execAsync, pathExists } from '@runtipi/shared/node';
import { SocketEvent, sanitizePath, socketEventSchema } from '@runtipi/shared';
import { type SocketEvent, sanitizePath, socketEventSchema } from '@runtipi/shared';
import { logger } from '@/lib/logger';
import { getEnv } from '@/lib/environment';
import { APP_DATA_DIR, DATA_DIR } from '@/config/constants';
import { Socket } from 'socket.io';
import type { Socket } from 'socket.io';
import { getRepoHash } from 'src/services/repo/repo.helpers';
import { DEFAULT_REPO_URL } from '../system/system.helpers';
const getBaseComposeArgsApp = async (appId: string) => {
const { arch, appsRepoId } = getEnv();
const appDataDirPath = path.join(APP_DATA_DIR, sanitizePath(appId));
const appDirPath = path.join(DATA_DIR, 'apps', sanitizePath(appId));
let isCustomConfig = appsRepoId !== getRepoHash(DEFAULT_REPO_URL);
const args: string[] = [`--env-file ${path.join(appDataDirPath, 'app.env')}`];
// User custom env file
@ -28,33 +32,23 @@ const getBaseComposeArgsApp = async (appId: string) => {
}
args.push(`-f ${composeFile}`);
const commonComposeFile = path.join(
DATA_DIR,
'repos',
sanitizePath(appsRepoId),
'apps',
'docker-compose.common.yml',
);
const commonComposeFile = path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps', 'docker-compose.common.yml');
args.push(`-f ${commonComposeFile}`);
// User defined overrides
const userComposeFile = path.join(
DATA_DIR,
'user-config',
sanitizePath(appId),
'docker-compose.yml',
);
const userComposeFile = path.join(DATA_DIR, 'user-config', sanitizePath(appId), 'docker-compose.yml');
if (await pathExists(userComposeFile)) {
isCustomConfig = true;
args.push(`--file ${userComposeFile}`);
}
return args;
return { args, isCustomConfig };
};
const getBaseComposeArgsTipi = async () => {
const args: string[] = [`--env-file ${path.join(DATA_DIR, '.env')}`];
args.push(`--project-name runtipi`);
args.push('--project-name runtipi');
const composeFile = path.join(DATA_DIR, 'docker-compose.yml');
args.push(`-f ${composeFile}`);
@ -74,24 +68,25 @@ const getBaseComposeArgsTipi = async () => {
* @param {string} command - Command to execute
*/
export const compose = async (appId: string, command: string) => {
const args = await getBaseComposeArgsApp(appId);
const { args, isCustomConfig } = await getBaseComposeArgsApp(appId);
args.push(command);
logger.info(`Running docker compose with args ${args.join(' ')}`);
const { stdout, stderr } = await execAsync(`docker-compose ${args.join(' ')}`);
if (stderr && stderr.includes('Command failed:')) {
if (stderr?.includes('Command failed:')) {
if (isCustomConfig) {
throw new Error(
`Error with your custom app: ${stderr}. Before opening an issue try to remove any user-config files or any custom app-store repo and try again.`,
);
}
throw new Error(stderr);
}
return { stdout, stderr };
};
export const handleViewRuntipiLogsEvent = async (
socket: Socket,
event: SocketEvent,
emit: (event: SocketEvent) => Promise<void>,
) => {
export const handleViewRuntipiLogsEvent = async (socket: Socket, event: SocketEvent, emit: (event: SocketEvent) => Promise<void>) => {
const { success, data } = socketEventSchema.safeParse(event);
if (!success) {
@ -142,11 +137,7 @@ export const handleViewRuntipiLogsEvent = async (
});
};
export const handleViewAppLogsEvent = async (
socket: Socket,
event: SocketEvent,
emit: (event: SocketEvent) => Promise<void>,
) => {
export const handleViewAppLogsEvent = async (socket: Socket, event: SocketEvent, emit: (event: SocketEvent) => Promise<void>) => {
const parsedEvent = socketEventSchema.safeParse(event);
if (!parsedEvent.success) {
@ -160,7 +151,7 @@ export const handleViewAppLogsEvent = async (
const { appId, maxLines } = parsedEvent.data.data;
const args = await getBaseComposeArgsApp(appId);
const { args } = await getBaseComposeArgsApp(appId);
args.push(`logs --follow -n ${maxLines || 25}`);
const logsCommand = `docker-compose ${args.join(' ')}`;

View File

@ -43,7 +43,7 @@ type EnvKeys =
| (string & {});
const OLD_DEFAULT_REPO_URL = 'https://github.com/meienberger/runtipi-appstore';
const DEFAULT_REPO_URL = 'https://github.com/runtipi/runtipi-appstore';
export const DEFAULT_REPO_URL = 'https://github.com/runtipi/runtipi-appstore';
/**
* Reads and returns the generated seed
@ -155,43 +155,26 @@ export const generateSystemEnvFile = async () => {
envMap.set('ARCHITECTURE', getArchitecture());
envMap.set('JWT_SECRET', jwtSecret);
envMap.set('DOMAIN', data.domain || envMap.get('DOMAIN') || 'example.com');
envMap.set(
'RUNTIPI_APP_DATA_PATH',
data.appDataPath || envMap.get('RUNTIPI_APP_DATA_PATH') || rootFolderHost,
);
envMap.set('RUNTIPI_APP_DATA_PATH', data.appDataPath || envMap.get('RUNTIPI_APP_DATA_PATH') || rootFolderHost);
envMap.set('POSTGRES_HOST', 'runtipi-db');
envMap.set('POSTGRES_DBNAME', 'tipi');
envMap.set('POSTGRES_USERNAME', 'tipi');
envMap.set('POSTGRES_PORT', String(5432));
envMap.set('REDIS_HOST', 'runtipi-redis');
envMap.set(
'DEMO_MODE',
typeof data.demoMode === 'boolean' ? String(data.demoMode) : envMap.get('DEMO_MODE') || 'false',
);
envMap.set(
'GUEST_DASHBOARD',
typeof data.guestDashboard === 'boolean'
? String(data.guestDashboard)
: envMap.get('GUEST_DASHBOARD') || 'false',
);
envMap.set('DEMO_MODE', typeof data.demoMode === 'boolean' ? String(data.demoMode) : envMap.get('DEMO_MODE') || 'false');
envMap.set('GUEST_DASHBOARD', typeof data.guestDashboard === 'boolean' ? String(data.guestDashboard) : envMap.get('GUEST_DASHBOARD') || 'false');
envMap.set('LOCAL_DOMAIN', data.localDomain || envMap.get('LOCAL_DOMAIN') || 'tipi.lan');
envMap.set(
'ALLOW_AUTO_THEMES',
typeof data.allowAutoThemes === 'boolean'
? String(data.allowAutoThemes)
: envMap.get('ALLOW_AUTO_THEMES') || 'true',
typeof data.allowAutoThemes === 'boolean' ? String(data.allowAutoThemes) : envMap.get('ALLOW_AUTO_THEMES') || 'true',
);
envMap.set(
'ALLOW_ERROR_MONITORING',
typeof data.allowErrorMonitoring === 'boolean'
? String(data.allowErrorMonitoring)
: envMap.get('ALLOW_ERROR_MONITORING') || 'false',
typeof data.allowErrorMonitoring === 'boolean' ? String(data.allowErrorMonitoring) : envMap.get('ALLOW_ERROR_MONITORING') || 'false',
);
envMap.set(
'PERSIST_TRAEFIK_CONFIG',
typeof data.persistTraefikConfig === 'boolean'
? String(data.persistTraefikConfig)
: envMap.get('PERSIST_TRAEFIK_CONFIG') || 'false',
typeof data.persistTraefikConfig === 'boolean' ? String(data.persistTraefikConfig) : envMap.get('PERSIST_TRAEFIK_CONFIG') || 'false',
);
await fs.promises.writeFile(envFilePath, envMapToString(envMap));
@ -221,10 +204,7 @@ export const copySystemFiles = async (envMap: Map<EnvKeys, string>) => {
logger.warn('Skipping the copy of traefik files because persistTraefikConfig is set to true');
} else {
logger.info('Copying traefik files');
await fs.promises.copyFile(
path.join(assetsFolder, 'traefik', 'traefik.yml'),
path.join(DATA_DIR, 'traefik', 'traefik.yml'),
);
await fs.promises.copyFile(path.join(assetsFolder, 'traefik', 'traefik.yml'), path.join(DATA_DIR, 'traefik', 'traefik.yml'));
await fs.promises.copyFile(
path.join(assetsFolder, 'traefik', 'dynamic', 'dynamic.yml'),
path.join(DATA_DIR, 'traefik', 'dynamic', 'dynamic.yml'),
@ -325,10 +305,7 @@ export const generateTlsCertificates = async (data: { domain?: string }) => {
const { stderr } = await execAsync(
`openssl req -x509 -newkey rsa:4096 -keyout ${DATA_DIR}/traefik/tls/key.pem -out ${DATA_DIR}/traefik/tls/cert.pem -days 365 -subj "${subject}" -addext "subjectAltName = ${subjectAltName}" -nodes`,
);
if (
!(await pathExists(path.join(tlsFolder, 'cert.pem'))) ||
!(await pathExists(path.join(tlsFolder, 'key.pem')))
) {
if (!(await pathExists(path.join(tlsFolder, 'cert.pem'))) || !(await pathExists(path.join(tlsFolder, 'key.pem')))) {
logger.error(`Failed to generate TLS certificate for ${data.domain}`);
logger.error(stderr);
} else {

View File

@ -25,6 +25,5 @@ if (allowErrorMonitoring && process.env.NODE_ENV === 'production' && process.env
dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584',
beforeSend: cleanseErrorData,
integrations: [Sentry.extraErrorDataIntegration()],
tracesSampleRate: 1.0,
});
}

View File

@ -9,7 +9,6 @@ export async function register() {
dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584',
beforeSend: cleanseErrorData,
integrations: [Sentry.extraErrorDataIntegration()],
tracesSampleRate: 1.0,
});
}
@ -19,7 +18,6 @@ export async function register() {
dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584',
beforeSend: cleanseErrorData,
integrations: [Sentry.extraErrorDataIntegration()],
tracesSampleRate: 1.0,
});
}
}