146 lines
3.6 KiB
TypeScript
146 lines
3.6 KiB
TypeScript
import Client from "ssh2-sftp-client";
|
|||
|
|
import { readFileSync } from "node:fs";
|
||
|
|
import process from "node:process";
|
||
|
|
import { Worker, isMainThread, workerData } from "node:worker_threads";
|
||
|
|
|
||
|
|
type Config = {
|
||
|
|
host: string;
|
||
|
|
port: number;
|
||
|
|
username: string;
|
||
|
|
password: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
type Profiles = Record<string, Config>;
|
||
|
|
|
||
|
|
type ConfigFile = {
|
||
|
|
profiles: Profiles;
|
||
|
|
};
|
||
|
|
|
||
|
|
const CONFIG_PATH = new URL("./config.json", import.meta.url);
|
||
|
|
|
||
|
|
function loadConfig(configArg?: string): Config {
|
||
|
|
let configFile: ConfigFile;
|
||
|
|
|
||
|
|
try {
|
||
|
|
configFile = JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as ConfigFile;
|
||
|
|
} catch (error) {
|
||
|
|
throw new Error(
|
||
|
|
`Could not read config file at ${CONFIG_PATH.href}. Copy config.example.json to config.json and fill in your credentials.`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const profiles = configFile.profiles ?? {};
|
||
|
|
const profileName = configArg ?? "default";
|
||
|
|
const config = profiles[profileName];
|
||
|
|
|
||
|
|
if (!config) {
|
||
|
|
throw new Error(
|
||
|
|
`Unknown profile "${profileName}". Available profiles: ${Object.keys(profiles).join(", ")}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!config.host || !config.username || !config.password) {
|
||
|
|
throw new Error(
|
||
|
|
`Profile "${profileName}" is missing host, username, or password in config.json.`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return { ...config, port: Number(config.port) || 22 };
|
||
|
|
}
|
||
|
|
|
||
|
|
class SftpService {
|
||
|
|
private readonly config: Config;
|
||
|
|
|
||
|
|
constructor(config: Config) {
|
||
|
|
this.config = config;
|
||
|
|
}
|
||
|
|
|
||
|
|
async uploadFile(sourcePath: string, remotePath: string) {
|
||
|
|
const client = new Client();
|
||
|
|
|
||
|
|
try {
|
||
|
|
await client.connect(this.config);
|
||
|
|
await client.put(sourcePath, remotePath);
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof Error) {
|
||
|
|
console.error(error.message);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
await client.end();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async deleteFile(remotePath: string) {
|
||
|
|
const client = new Client();
|
||
|
|
|
||
|
|
try {
|
||
|
|
await client.connect(this.config);
|
||
|
|
await client.delete(remotePath);
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof Error) {
|
||
|
|
console.error(error.message);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
await client.end();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function runWorker(runIndex: number, configArg?: string): Promise<void> {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const worker = new Worker(new URL(import.meta.url), {
|
||
|
|
workerData: { runIndex, configArg },
|
||
|
|
});
|
||
|
|
|
||
|
|
worker.on("exit", (code) => {
|
||
|
|
if (code !== 0) {
|
||
|
|
reject(new Error(`Worker ${runIndex} exited with code ${code}`));
|
||
|
|
} else {
|
||
|
|
resolve();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
worker.on("error", (err) => reject(err));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async function executeIteration(runIndex: number, configArg?: string) {
|
||
|
|
const sftp = new SftpService(loadConfig(configArg));
|
||
|
|
|
||
|
|
const remotePath = `./${runIndex}-test.txt`;
|
||
|
|
const sourcePath = "./test.txt";
|
||
|
|
|
||
|
|
console.log(`[Worker ${runIndex}] Starting run number ${runIndex}`);
|
||
|
|
|
||
|
|
console.log(`[Worker ${runIndex}] Uploading file...`);
|
||
|
|
await sftp.uploadFile(sourcePath, remotePath);
|
||
|
|
console.log(`[Worker ${runIndex}] File uploaded`);
|
||
|
|
|
||
|
|
console.log(`[Worker ${runIndex}] Deleting file...`);
|
||
|
|
await sftp.deleteFile(remotePath);
|
||
|
|
console.log(`[Worker ${runIndex}] File deleted`);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
if (isMainThread) {
|
||
|
|
const configArg = process.argv[2];
|
||
|
|
const TOTAL_RUNS = 6;
|
||
|
|
|
||
|
|
console.log(`[Main] Spawning ${TOTAL_RUNS} parallel workers...`);
|
||
|
|
|
||
|
|
const workerPromises: Promise<void>[] = [];
|
||
|
|
|
||
|
|
for (let i = 0; i < TOTAL_RUNS; i++) {
|
||
|
|
workerPromises.push(runWorker(i, configArg));
|
||
|
|
}
|
||
|
|
|
||
|
|
await Promise.all(workerPromises);
|
||
|
|
console.log("[Main] All workers completed execution successfully.");
|
||
|
|
} else {
|
||
|
|
const { runIndex, configArg } = workerData;
|
||
|
|
await executeIteration(runIndex, configArg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await main();
|