chore: 清理macOS同步产生的重复文件

详细说明:
- 删除了352个带数字后缀的重复文件
- 更新.gitignore防止未来产生此类文件
- 这些文件是由iCloud或其他同步服务冲突产生的
- 不影响项目功能,仅清理冗余文件
This commit is contained in:
Yep_Q
2025-09-08 12:06:01 +08:00
parent 1564396449
commit d6f48d6d14
365 changed files with 2039 additions and 68301 deletions

View File

@@ -1,63 +0,0 @@
#!/bin/bash
#
# Script to initialize the benchmark environment on a VM
#
set -euo pipefail;
CURRENT_USER=$(whoami)
# Mount the data disk
# First wait for the disk to become available
WAIT_TIME=0
MAX_WAIT_TIME=60
while [ ! -e /dev/sdc ]; do
if [ $WAIT_TIME -ge $MAX_WAIT_TIME ]; then
echo "Error: /dev/sdc did not become available within $MAX_WAIT_TIME seconds."
exit 1
fi
echo "Waiting for /dev/sdc to be available... ($WAIT_TIME/$MAX_WAIT_TIME)"
sleep 1
WAIT_TIME=$((WAIT_TIME + 1))
done
# Then mount it
if [ -d "/n8n" ]; then
echo "Data disk already mounted. Clearing it..."
sudo rm -rf /n8n/*
sudo rm -rf /n8n/.[!.]*
else
sudo mkdir -p /n8n
sudo parted /dev/sdc --script mklabel gpt mkpart xfspart xfs 0% 100%
sudo mkfs.xfs /dev/sdc1
sudo partprobe /dev/sdc1
sudo mount /dev/sdc1 /n8n
sudo chown -R "$CURRENT_USER":"$CURRENT_USER" /n8n
fi
### Remove unneeded dependencies
# TTY
sudo systemctl disable getty@tty1.service
sudo systemctl disable serial-getty@ttyS0.service
# Snap
sudo systemctl disable snapd.service
# Unattended upgrades
sudo systemctl disable unattended-upgrades.service
# Cron
sudo systemctl disable cron.service
# Include nodejs v20 repository
curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
# Install docker, docker compose and nodejs
sudo DEBIAN_FRONTEND=noninteractive apt-get update -yq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq docker.io docker-compose nodejs
# Add the current user to the docker group
sudo usermod -aG docker "$CURRENT_USER"
# Install zx
npm install zx

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env zx
/**
* Script that deletes all resources created by the benchmark environment.
*
* This scripts tries to delete resources created by Terraform. If Terraform
* state file is not found, it will try to delete resources using Azure CLI.
* The terraform state is not persisted, so we want to support both cases.
*/
// @ts-check
import { $, minimist } from 'zx';
import { TerraformClient } from './clients/terraform-client.mjs';
const RESOURCE_GROUP_NAME = 'n8n-benchmarking';
const args = minimist(process.argv.slice(3), {
boolean: ['debug'],
});
const isVerbose = !!args.debug;
async function main() {
const terraformClient = new TerraformClient({ isVerbose });
if (terraformClient.hasTerraformState()) {
await terraformClient.destroyEnvironment();
} else {
await destroyUsingAz();
}
}
async function destroyUsingAz() {
const resourcesResult =
await $`az resource list --resource-group ${RESOURCE_GROUP_NAME} --query "[?tags.Id == 'N8nBenchmark'].{id:id, createdAt:tags.CreatedAt}" -o json`;
const resources = JSON.parse(resourcesResult.stdout);
const resourcesToDelete = resources.map((resource) => resource.id);
if (resourcesToDelete.length === 0) {
console.log('No resources found in the resource group.');
return;
}
await deleteResources(resourcesToDelete);
}
async function deleteResources(resourceIds) {
// We don't know the order in which resource should be deleted.
// Here's a poor person's approach to try deletion until all complete
const MAX_ITERATIONS = 100;
let i = 0;
const toDelete = [...resourceIds];
console.log(`Deleting ${resourceIds.length} resources...`);
while (toDelete.length > 0) {
const resourceId = toDelete.shift();
const deleted = await deleteById(resourceId);
if (!deleted) {
toDelete.push(resourceId);
}
if (i++ > MAX_ITERATIONS) {
console.log(
`Max iterations reached. Exiting. Could not delete ${toDelete.length} resources.`,
);
process.exit(1);
}
}
}
async function deleteById(id) {
try {
await $`az resource delete --ids ${id}`;
return true;
} catch (error) {
return false;
}
}
main().catch((error) => {
console.error('An error occurred destroying cloud env:');
console.error(error);
process.exit(1);
});

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env zx
/**
* Provisions the cloud benchmark environment
*
* NOTE: Must be run in the root of the package.
*/
// @ts-check
import { which, minimist } from 'zx';
import { TerraformClient } from './clients/terraform-client.mjs';
const args = minimist(process.argv.slice(3), {
boolean: ['debug'],
});
const isVerbose = !!args.debug;
export async function provision() {
await ensureDependencies();
const terraformClient = new TerraformClient({
isVerbose,
});
await terraformClient.provisionEnvironment();
}
async function ensureDependencies() {
await which('terraform');
}
provision().catch((error) => {
console.error('An error occurred while provisioning cloud env:');
console.error(error);
process.exit(1);
});

View File

@@ -1,186 +0,0 @@
#!/usr/bin/env zx
/**
* Script to run benchmarks either on the cloud benchmark environment or locally.
* The cloud environment needs to be provisioned using Terraform before running the benchmarks.
*
* NOTE: Must be run in the root of the package.
*/
// @ts-check
import fs from 'fs';
import minimist from 'minimist';
import path from 'path';
import { runInCloud } from './run-in-cloud.mjs';
import { runLocally } from './run-locally.mjs';
const paths = {
n8nSetupsDir: path.join(path.resolve('scripts'), 'n8n-setups'),
};
async function main() {
const config = await parseAndValidateConfig();
const n8nSetupsToUse =
config.n8nSetupToUse === 'all' ? readAvailableN8nSetups() : [config.n8nSetupToUse];
console.log('Using n8n tag', config.n8nTag);
console.log('Using benchmark cli tag', config.benchmarkTag);
console.log('Using environment', config.env);
console.log('Using n8n setups', n8nSetupsToUse.join(', '));
console.log('');
if (config.env === 'cloud') {
await runInCloud({
benchmarkTag: config.benchmarkTag,
isVerbose: config.isVerbose,
k6ApiToken: config.k6ApiToken,
resultWebhookUrl: config.resultWebhookUrl,
resultWebhookAuthHeader: config.resultWebhookAuthHeader,
n8nLicenseCert: config.n8nLicenseCert,
n8nTag: config.n8nTag,
n8nSetupsToUse,
vus: config.vus,
duration: config.duration,
});
} else if (config.env === 'local') {
await runLocally({
benchmarkTag: config.benchmarkTag,
isVerbose: config.isVerbose,
k6ApiToken: config.k6ApiToken,
resultWebhookUrl: config.resultWebhookUrl,
resultWebhookAuthHeader: config.resultWebhookAuthHeader,
n8nLicenseCert: config.n8nLicenseCert,
n8nTag: config.n8nTag,
runDir: config.runDir,
n8nSetupsToUse,
vus: config.vus,
duration: config.duration,
});
} else {
console.error('Invalid env:', config.env);
printUsage();
process.exit(1);
}
}
function readAvailableN8nSetups() {
const setups = fs.readdirSync(paths.n8nSetupsDir);
return setups;
}
/**
* @typedef {Object} Config
* @property {boolean} isVerbose
* @property {'cloud' | 'local'} env
* @property {string} n8nSetupToUse
* @property {string} n8nTag
* @property {string} benchmarkTag
* @property {string} [k6ApiToken]
* @property {string} [resultWebhookUrl]
* @property {string} [resultWebhookAuthHeader]
* @property {string} [n8nLicenseCert]
* @property {string} [runDir]
* @property {string} [vus]
* @property {string} [duration]
*
* @returns {Promise<Config>}
*/
async function parseAndValidateConfig() {
const args = minimist(process.argv.slice(3), {
boolean: ['debug', 'help'],
});
if (args.help) {
printUsage();
process.exit(0);
}
const n8nSetupToUse = await getAndValidateN8nSetup(args);
const isVerbose = args.debug || false;
const n8nTag = args.n8nTag || process.env.N8N_DOCKER_TAG || 'latest';
const benchmarkTag = args.benchmarkTag || process.env.BENCHMARK_DOCKER_TAG || 'latest';
const k6ApiToken = args.k6ApiToken || process.env.K6_API_TOKEN || undefined;
const resultWebhookUrl =
args.resultWebhookUrl || process.env.BENCHMARK_RESULT_WEBHOOK_URL || undefined;
const resultWebhookAuthHeader =
args.resultWebhookAuthHeader || process.env.BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER || undefined;
const n8nLicenseCert = args.n8nLicenseCert || process.env.N8N_LICENSE_CERT || undefined;
const runDir = args.runDir || undefined;
const env = args.env || 'local';
const vus = args.vus;
const duration = args.duration;
if (!env) {
printUsage();
process.exit(1);
}
return {
isVerbose,
env,
n8nSetupToUse,
n8nTag,
benchmarkTag,
k6ApiToken,
resultWebhookUrl,
resultWebhookAuthHeader,
n8nLicenseCert,
runDir,
vus,
duration,
};
}
/**
* @param {minimist.ParsedArgs} args
*/
async function getAndValidateN8nSetup(args) {
// Last parameter is the n8n setup to use
const n8nSetupToUse = args._[args._.length - 1];
if (!n8nSetupToUse || n8nSetupToUse === 'all') {
return 'all';
}
const availableSetups = readAvailableN8nSetups();
if (!availableSetups.includes(n8nSetupToUse)) {
printUsage();
process.exit(1);
}
return n8nSetupToUse;
}
function printUsage() {
const availableSetups = readAvailableN8nSetups();
console.log(`Usage: zx scripts/${path.basename(__filename)} [n8n setup name]`);
console.log(` eg: zx scripts/${path.basename(__filename)}`);
console.log('');
console.log('Options:');
console.log(
` [n8n setup name] Against which n8n setup to run the benchmarks. One of: ${['all', ...availableSetups].join(', ')}. Default is all`,
);
console.log(
' --env Env where to run the benchmarks. Either cloud or local. Default is local.',
);
console.log(' --debug Enable verbose output');
console.log(' --n8nTag Docker tag for n8n image. Default is latest');
console.log(' --benchmarkTag Docker tag for benchmark cli image. Default is latest');
console.log(' --vus How many concurrent requests to make');
console.log(' --duration Test duration, e.g. 1m or 30s');
console.log(
' --k6ApiToken API token for k6 cloud. Default is read from K6_API_TOKEN env var. If omitted, k6 cloud will not be used',
);
console.log(
' --runDir Directory to share with the n8n container for storing data. Needed only for local runs.',
);
console.log('');
}
main().catch((error) => {
console.error('An error occurred while running the benchmarks:');
console.error(error);
process.exit(1);
});

View File

@@ -1,158 +0,0 @@
#!/usr/bin/env zx
/**
* This script runs the benchmarks for the given n8n setup.
*/
// @ts-check
import path from 'path';
import { $, argv, fs } from 'zx';
import { DockerComposeClient } from './clients/docker-compose-client.mjs';
import { flagsObjectToCliArgs } from './utils/flags.mjs';
const paths = {
n8nSetupsDir: path.join(__dirname, 'n8n-setups'),
mockApiDataPath: path.join(__dirname, 'mock-api'),
};
const N8N_ENCRYPTION_KEY = 'very-secret-encryption-key';
async function main() {
const [n8nSetupToUse] = argv._;
validateN8nSetup(n8nSetupToUse);
const composeFilePath = path.join(paths.n8nSetupsDir, n8nSetupToUse);
const setupScriptPath = path.join(paths.n8nSetupsDir, n8nSetupToUse, 'setup.mjs');
const n8nTag = argv.n8nDockerTag || process.env.N8N_DOCKER_TAG || 'latest';
const benchmarkTag = argv.benchmarkDockerTag || process.env.BENCHMARK_DOCKER_TAG || 'latest';
const k6ApiToken = argv.k6ApiToken || process.env.K6_API_TOKEN || undefined;
const resultWebhookUrl =
argv.resultWebhookUrl || process.env.BENCHMARK_RESULT_WEBHOOK_URL || undefined;
const resultWebhookAuthHeader =
argv.resultWebhookAuthHeader || process.env.BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER || undefined;
const baseRunDir = argv.runDir || process.env.RUN_DIR || '/n8n';
const n8nLicenseCert = argv.n8nLicenseCert || process.env.N8N_LICENSE_CERT || undefined;
const n8nLicenseActivationKey = process.env.N8N_LICENSE_ACTIVATION_KEY || undefined;
const n8nLicenseTenantId = argv.n8nLicenseTenantId || process.env.N8N_LICENSE_TENANT_ID || '1';
const envTag = argv.env || 'local';
const vus = argv.vus;
const duration = argv.duration;
const hasN8nLicense = !!n8nLicenseCert || !!n8nLicenseActivationKey;
if (n8nSetupToUse === 'scaling-multi-main' && !hasN8nLicense) {
console.error(
'n8n license is required to run the multi-main scaling setup. Please provide N8N_LICENSE_CERT or N8N_LICENSE_ACTIVATION_KEY (and N8N_LICENSE_TENANT_ID if needed)',
);
process.exit(1);
}
if (!fs.existsSync(baseRunDir)) {
console.error(
`The run directory "${baseRunDir}" does not exist. Please specify a valid directory using --runDir`,
);
process.exit(1);
}
const runDir = path.join(baseRunDir, n8nSetupToUse);
fs.emptyDirSync(runDir);
const dockerComposeClient = new DockerComposeClient({
$: $({
cwd: composeFilePath,
verbose: true,
env: {
PATH: process.env.PATH,
N8N_VERSION: n8nTag,
N8N_LICENSE_CERT: n8nLicenseCert,
N8N_LICENSE_ACTIVATION_KEY: n8nLicenseActivationKey,
N8N_LICENSE_TENANT_ID: n8nLicenseTenantId,
N8N_ENCRYPTION_KEY,
BENCHMARK_VERSION: benchmarkTag,
K6_API_TOKEN: k6ApiToken,
BENCHMARK_RESULT_WEBHOOK_URL: resultWebhookUrl,
BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER: resultWebhookAuthHeader,
RUN_DIR: runDir,
MOCK_API_DATA_PATH: paths.mockApiDataPath,
},
}),
});
// Run the setup script if it exists
if (fs.existsSync(setupScriptPath)) {
const setupScript = await import(setupScriptPath);
await setupScript.setup({ runDir });
}
try {
await dockerComposeClient.$('up', '-d', '--remove-orphans', 'n8n');
const tags = Object.entries({
Env: envTag,
N8nVersion: n8nTag,
N8nSetup: n8nSetupToUse,
})
.map(([key, value]) => `${key}=${value}`)
.join(',');
const cliArgs = flagsObjectToCliArgs({
scenarioNamePrefix: n8nSetupToUse,
vus,
duration,
tags,
});
await dockerComposeClient.$('run', 'benchmark', 'run', ...cliArgs);
} catch (error) {
console.error('An error occurred while running the benchmarks:');
console.error(error.message);
console.error('');
await printContainerStatus(dockerComposeClient);
} finally {
await dumpLogs(dockerComposeClient);
await dockerComposeClient.$('down');
}
}
async function printContainerStatus(dockerComposeClient) {
console.error('Container statuses:');
await dockerComposeClient.$('ps', '-a');
}
async function dumpLogs(dockerComposeClient) {
console.info('Container logs:');
await dockerComposeClient.$('logs');
}
function printUsage() {
const availableSetups = getAllN8nSetups();
console.log('Usage: zx runForN8nSetup.mjs --runDir /path/for/n8n/data <n8n setup to use>');
console.log(` eg: zx runForN8nSetup.mjs --runDir /path/for/n8n/data ${availableSetups[0]}`);
console.log('');
console.log('Flags:');
console.log(
' --runDir <path> Directory to share with the n8n container for storing data. Default is /n8n',
);
console.log(' --n8nDockerTag <tag> Docker tag for n8n image. Default is latest');
console.log(
' --benchmarkDockerTag <tag> Docker tag for benchmark cli image. Default is latest',
);
console.log(' --k6ApiToken <token> K6 API token to upload the results');
console.log('');
console.log('Available setups:');
console.log(availableSetups.join(', '));
}
/**
* @returns {string[]}
*/
function getAllN8nSetups() {
return fs.readdirSync(paths.n8nSetupsDir);
}
function validateN8nSetup(givenSetup) {
const availableSetups = getAllN8nSetups();
if (!availableSetups.includes(givenSetup)) {
printUsage();
process.exit(1);
}
}
main();

View File

@@ -1,154 +0,0 @@
#!/usr/bin/env zx
/**
* Script to run benchmarks on the cloud benchmark environment.
* This script will:
* 1. Provision a benchmark environment using Terraform.
* 2. Run the benchmarks on the VM.
* 3. Destroy the cloud environment.
*
* NOTE: Must be run in the root of the package.
*/
// @ts-check
import { sleep, which, $, tmpdir } from 'zx';
import path from 'path';
import { SshClient } from './clients/ssh-client.mjs';
import { TerraformClient } from './clients/terraform-client.mjs';
import { flagsObjectToCliArgs } from './utils/flags.mjs';
/**
* @typedef {Object} BenchmarkEnv
* @property {string} vmName
* @property {string} ip
* @property {string} sshUsername
* @property {string} sshPrivateKeyPath
*/
/**
* @typedef {Object} Config
* @property {boolean} isVerbose
* @property {string[]} n8nSetupsToUse
* @property {string} n8nTag
* @property {string} benchmarkTag
* @property {string} [k6ApiToken]
* @property {string} [resultWebhookUrl]
* @property {string} [resultWebhookAuthHeader]
* @property {string} [n8nLicenseCert]
* @property {string} [vus]
* @property {string} [duration]
*
* @param {Config} config
*/
export async function runInCloud(config) {
await ensureDependencies();
const terraformClient = new TerraformClient({
isVerbose: config.isVerbose,
});
const benchmarkEnv = await terraformClient.getTerraformOutputs();
await runBenchmarksOnVm(config, benchmarkEnv);
}
async function ensureDependencies() {
await which('terraform');
await which('az');
}
/**
* @param {Config} config
* @param {BenchmarkEnv} benchmarkEnv
*/
async function runBenchmarksOnVm(config, benchmarkEnv) {
console.log(`Setting up the environment...`);
const sshClient = new SshClient({
ip: benchmarkEnv.ip,
username: benchmarkEnv.sshUsername,
privateKeyPath: benchmarkEnv.sshPrivateKeyPath,
verbose: config.isVerbose,
});
await ensureVmIsReachable(sshClient);
const scriptsDir = await transferScriptsToVm(sshClient, config);
// Bootstrap the environment with dependencies
console.log('Running bootstrap script...');
const bootstrapScriptPath = path.join(scriptsDir, 'bootstrap.sh');
await sshClient.ssh(`chmod a+x ${bootstrapScriptPath} && ${bootstrapScriptPath}`);
// Give some time for the VM to be ready
await sleep(1000);
for (const n8nSetup of config.n8nSetupsToUse) {
await runBenchmarkForN8nSetup({
config,
sshClient,
scriptsDir,
n8nSetup,
});
}
}
/**
* @param {{ config: Config; sshClient: any; scriptsDir: string; n8nSetup: string; }} opts
*/
async function runBenchmarkForN8nSetup({ config, sshClient, scriptsDir, n8nSetup }) {
console.log(`Running benchmarks for ${n8nSetup}...`);
const runScriptPath = path.join(scriptsDir, 'run-for-n8n-setup.mjs');
const cliArgs = flagsObjectToCliArgs({
n8nDockerTag: config.n8nTag,
benchmarkDockerTag: config.benchmarkTag,
k6ApiToken: config.k6ApiToken,
resultWebhookUrl: config.resultWebhookUrl,
resultWebhookAuthHeader: config.resultWebhookAuthHeader,
n8nLicenseCert: config.n8nLicenseCert,
vus: config.vus,
duration: config.duration,
env: 'cloud',
});
const flagsString = cliArgs.join(' ');
await sshClient.ssh(`npx zx ${runScriptPath} ${flagsString} ${n8nSetup}`, {
// Test run should always log its output
verbose: true,
});
}
async function ensureVmIsReachable(sshClient) {
try {
await sshClient.ssh('echo "VM is reachable"');
} catch (error) {
console.error(`VM is not reachable: ${error.message}`);
console.error(
`Did you provision the cloud environment first with 'pnpm provision-cloud-env'? You can also run the benchmarks locally with 'pnpm run benchmark-locally'.`,
);
process.exit(1);
}
}
/**
* @returns Path where the scripts are located on the VM
*/
async function transferScriptsToVm(sshClient, config) {
const cwd = process.cwd();
const scriptsDir = path.resolve(cwd, './scripts');
const tarFilename = 'scripts.tar.gz';
const scriptsTarPath = path.join(tmpdir('n8n-benchmark'), tarFilename);
const $$ = $({ verbose: config.isVerbose });
// Compress the scripts folder
await $$`tar -czf ${scriptsTarPath} ${scriptsDir} -C ${cwd} ./scripts`;
// Transfer the scripts to the VM
await sshClient.scp(scriptsTarPath, `~/${tarFilename}`);
// Extract the scripts on the VM
await sshClient.ssh(`tar -xzf ~/${tarFilename}`);
return '~/scripts';
}

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env zx
/**
* Script to run benchmarks on the cloud benchmark environment.
* This script will:
* 1. Provision a benchmark environment using Terraform.
* 2. Run the benchmarks on the VM.
* 3. Destroy the cloud environment.
*
* NOTE: Must be run in the root of the package.
*/
// @ts-check
import { $ } from 'zx';
import path from 'path';
import { flagsObjectToCliArgs } from './utils/flags.mjs';
/**
* @typedef {Object} BenchmarkEnv
* @property {string} vmName
*/
const paths = {
scriptsDir: path.join(path.resolve('scripts')),
};
/**
* @typedef {Object} Config
* @property {boolean} isVerbose
* @property {string[]} n8nSetupsToUse
* @property {string} n8nTag
* @property {string} benchmarkTag
* @property {string} [runDir]
* @property {string} [k6ApiToken]
* @property {string} [resultWebhookUrl]
* @property {string} [resultWebhookAuthHeader]
* @property {string} [n8nLicenseCert]
* @property {string} [vus]
* @property {string} [duration]
*
* @param {Config} config
*/
export async function runLocally(config) {
const runScriptPath = path.join(paths.scriptsDir, 'run-for-n8n-setup.mjs');
const cliArgs = flagsObjectToCliArgs({
n8nDockerTag: config.n8nTag,
benchmarkDockerTag: config.benchmarkTag,
runDir: config.runDir,
vus: config.vus,
duration: config.duration,
env: 'local',
});
try {
for (const n8nSetup of config.n8nSetupsToUse) {
console.log(`Running benchmarks for n8n setup: ${n8nSetup}`);
await $({
env: {
...process.env,
K6_API_TOKEN: config.k6ApiToken,
BENCHMARK_RESULT_WEBHOOK_URL: config.resultWebhookUrl,
BENCHMARK_RESULT_WEBHOOK_AUTH_HEADER: config.resultWebhookAuthHeader,
N8N_LICENSE_CERT: config.n8nLicenseCert,
},
})`npx ${runScriptPath} ${cliArgs} ${n8nSetup}`;
}
} catch (error) {
console.error('An error occurred while running the benchmarks:');
console.error(error);
}
}