First Commit

This commit is contained in:
Benjamin Toby 2024-10-16 05:44:48 +01:00
commit 21d6ba5733
8 changed files with 632 additions and 0 deletions

179
.gitignore vendored Normal file
View File

@ -0,0 +1,179 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
/test
/lib-node
/dump

57
README.md Normal file
View File

@ -0,0 +1,57 @@
# Turbo Sync
A no-nonsense file/folder synchronization application
## Requirements
Turbo sync requires **node js** and **rsync**
## Installation
```bash
npm install --registry="https://git.tben.me/api/packages/Moduletrace/npm/" -g turbo-sync
```
## Usage
```bash
turbo-sync ./turbosync.config.json
```
### Config File
The config file is a json file that contains all the information needed to run turbo-sync. Example:
```json
[
{
"title": "Sync Files",
"files": [
"/home/user/file1.txt",
"/home/user/file2.txt",
{
"path": "/home/user/file3",
"user": "root",
"host": "5.34.75.236",
"ssh_key": "/home/user/.ssh/key"
}
]
},
{
"title": "Sync Folders",
"options": {
"delete": true
},
"folders": [
"/home/user/folder1",
"/home/user/folder2",
{
"path": "/home/user/folder3",
"user": "user",
"host": "5.39.67.76",
"ssh_key": "/home/user/.ssh/key"
}
]
}
]
```

90
index.js Normal file
View File

@ -0,0 +1,90 @@
#! /usr/bin/env node
// @ts-check
const fs = require("fs");
const path = require("path");
const { execSync, spawn, ChildProcess } = require("child_process");
/** @type {string[]} */
let dirs = [];
console.log("Running Folder Sync ...");
const defaultConfigFilePath = path.resolve(
process.cwd(),
"turbosync.config.json"
);
const confFileProvidedPath = process.argv[process.argv.length - 1];
const confFileComputedPath =
typeof confFileProvidedPath == "string" &&
confFileProvidedPath.endsWith(".json")
? path.resolve(process.cwd(), confFileProvidedPath)
: null;
if (!fs.existsSync(defaultConfigFilePath) && !confFileComputedPath) {
console.log(
"Please Provide the path to a config file or add a config file named `turbosync.config.json` to the path you're running this program"
);
process.exit();
}
if (
!defaultConfigFilePath &&
confFileComputedPath &&
!fs.existsSync(confFileComputedPath)
) {
console.log("Config File does not exist");
process.exit();
}
// /** @type {ChildProcess[]} */
// const childProcesses = [];
try {
const configJSON = fs.existsSync(defaultConfigFilePath)
? fs.readFileSync(defaultConfigFilePath, "utf8")
: confFileComputedPath
? fs.readFileSync(confFileComputedPath, "utf8")
: null;
if (!configJSON)
throw new Error(
"Config JSON could not be resolved. Please check your files."
);
/** @type {TurboSyncConfigArray} */
const configArray = JSON.parse(configJSON);
for (let i = 0; i < configArray.length; i++) {
const config = configArray[i];
console.log(`Syncing \`${config.title} ...\``);
const childProcess = spawn(
"node",
[
path.resolve(__dirname, "./lib/sync.js"),
`${JSON.stringify(config)}`,
],
{
stdio: "inherit",
detached: false,
}
);
}
} catch (error) {
console.log(`Process Error =>`, error.message);
process.exit();
}
setInterval(() => {
console.log("Turbo Sync Running ...");
}, 60000);
// process.on("exit", () => {
// for (let i = 0; i < childProcesses.length; i++) {
// const childProcess = childProcesses[i];
// childProcess.kill("SIGTERM");
// }
// });

216
lib/sync.js Normal file
View File

@ -0,0 +1,216 @@
#! /usr/bin/env node
// @ts-check
const fs = require("fs");
const path = require("path");
const { execSync, spawn } = require("child_process");
const confFileProvidedJSON = process.argv[process.argv.length - 1];
try {
/** @type {TurboSyncConfigObject} */
const configFileObject = JSON.parse(confFileProvidedJSON);
console.log(`Running '${configFileObject.title}' ...`);
if (
Array.isArray(configFileObject.files) &&
Array.isArray(configFileObject.folders)
) {
throw new Error("Choose wither `files` or `folders`. Not both");
}
const files = configFileObject?.files;
const firstFile = files?.[0];
const folders = configFileObject?.folders;
const firstFolder = folders?.[0];
const options = configFileObject.options;
if (firstFile && files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const filePath =
typeof file == "string" ? file : file?.path ? file.path : null;
const interval = typeof file == "object" ? file.interval : null;
if (!filePath) continue;
if (typeof file == "string" && !fs.existsSync(filePath))
throw new Error("File Doesn't exist");
if (typeof file == "string" && !fs.statSync(filePath).isFile()) {
throw new Error(`'${filePath}' is not a File!`);
}
if (typeof file == "object" && file.host) {
// TODO Handle SSH
} else if (typeof file == "string") {
fs.watchFile(
filePath,
{
interval: interval || 500,
},
(curr, prev) => {
let cmdArray = ["rsync", "-avh"];
if (options?.delete) {
cmdArray.push("--delete");
}
if (options?.exclude?.[0]) {
options.exclude.forEach((excl) => {
cmdArray.push(`--exclude '${excl}'`);
});
}
const destFiles = files.filter((fl) => {
if (typeof fl == "string") return fl !== filePath;
if (fl?.path) return fl.path !== filePath;
return false;
});
for (let j = 0; j < destFiles.length; j++) {
const dstFl = destFiles[j];
if (typeof dstFl == "string") {
if (!fs.existsSync(dstFl)) continue;
cmdArray.push(filePath, dstFl);
const cmd = cmdArray.join(" ");
execSync(cmd, {
stdio: "inherit",
});
} else if (dstFl.path) {
if (!dstFl.host && !fs.existsSync(dstFl.path))
continue;
if (dstFl.host && dstFl.ssh_key && dstFl.user) {
cmdArray.push(
"-e",
`'ssh -i ${dstFl.ssh_key}'`
);
cmdArray.push(
filePath,
`${dstFl.user}@${dstFl.host}:${dstFl.path}`
);
const cmd = cmdArray.join(" ");
execSync(cmd, {
stdio: "inherit",
});
} else {
cmdArray.push(filePath, dstFl.path);
const cmd = cmdArray.join(" ");
execSync(cmd, {
stdio: "inherit",
});
}
}
}
process.exit(1);
}
);
}
}
} else if (firstFolder && folders?.[0]) {
const dirs = folders;
for (let i = 0; i < dirs.length; i++) {
const dir = dirs[i];
if (!dir) continue;
const dirPath = typeof dir == "string" ? dir : dir.path;
if (typeof dir == "string") {
}
if (typeof dir == "string" && !fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, {
recursive: true,
});
}
if (typeof dir == "string") {
fs.watch(dirPath, { recursive: true }, (evt, fileName) => {
let cmdArray = ["rsync", "-avh"];
if (options?.delete) {
cmdArray.push("--delete");
}
if (options?.exclude?.[0]) {
options.exclude.forEach((excl) => {
cmdArray.push(`--exclude '${excl}'`);
});
}
const dstDirs = dirs.filter((dr) => {
if (typeof dr == "string") return dr !== dirPath;
if (dr?.path) return dr.path !== dirPath;
return false;
});
for (let j = 0; j < dstDirs.length; j++) {
const dstDr = dstDirs[j];
if (typeof dstDr == "string") {
if (!fs.existsSync(dstDr)) continue;
cmdArray.push(
path.normalize(dirPath) + "/",
path.normalize(dstDr) + "/"
);
const cmd = cmdArray.join(" ");
execSync(cmd, {
stdio: "inherit",
});
} else if (dstDr.path) {
if (!dstDr.host && !fs.existsSync(dstDr.path))
continue;
if (dstDr.host && dstDr.ssh_key && dstDr.user) {
cmdArray.push(
"-e",
`'ssh -i ${dstDr.ssh_key}'`
);
cmdArray.push(
path.normalize(dirPath) + "/",
`${dstDr.user}@${dstDr.host}:${dstDr.path}/`
);
const cmd = cmdArray.join(" ");
execSync(cmd, {
stdio: "inherit",
});
} else {
cmdArray.push(
path.normalize(dirPath),
path.normalize(dstDr.path)
);
const cmd = cmdArray.join(" ");
execSync(cmd, {
stdio: "inherit",
});
}
}
}
process.exit(1);
});
}
}
}
} catch (error) {
console.log(error);
process.exit();
}
process.on("exit", (code) => {
if (code == 1) {
const args = process.argv;
const cmd = args.shift();
if (cmd) {
spawn(cmd, args, {
stdio: "inherit",
});
}
} else {
process.exit(0);
}
});

17
package-lock.json generated Normal file
View File

@ -0,0 +1,17 @@
{
"name": "turbo-sync",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "turbo-sync",
"version": "1.0.0",
"license": "ISC",
"bin": {
"turbo-sync": "index.js",
"turbosync": "index.js"
}
}
}
}

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "turbo-sync",
"version": "1.0.0",
"module": "index.js",
"scripts": {
"start": "node index.ts",
"build": "tsc",
"dev": "node index.js --watch"
},
"bin": {
"turbo-sync": "./index.js",
"turbosync": "./index.js"
},
"description": "To install dependencies:",
"main": "index.js",
"author": "Benjamin Toby",
"license": "ISC"
}

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2015",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"emitDeclarationOnly": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"declaration": true,
"outDir": "dump/dist",
"noPropertyAccessFromIndexSignature": false
},
"include": ["index.ts", "index.d.ts", "**/*.ts"],
"exclude": ["dump"]
}

28
types.js Normal file
View File

@ -0,0 +1,28 @@
// @ts-check
/**
* @typedef {TurboSyncConfigObject[]} TurboSyncConfigArray
*/
/**
* @typedef {object} TurboSyncConfigObject
* @property {string} [title]
* @property {string[] | TurboSyncFileObject[]} [files]
* @property {string[] | TurboSyncFileObject[]} [folders]
* @property {TurboSyncOptions} [options]
*/
/**
* @typedef {object} TurboSyncFileObject
* @property {string} path
* @property {string} [host]
* @property {string} [user]
* @property {string} [ssh_key]
* @property {number} [interval]
*/
/**
* @typedef {object} TurboSyncOptions
* @property {boolean} [delete] - Should files removed be deleted in all destinations?
* @property {string[]} [exclude] - Patterns that should be ignored. Eg "*.log"
*/