First Commit
This commit is contained in:
commit
21d6ba5733
179
.gitignore
vendored
Normal file
179
.gitignore
vendored
Normal 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
57
README.md
Normal 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
90
index.js
Normal 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
216
lib/sync.js
Normal 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
17
package-lock.json
generated
Normal 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
18
package.json
Normal 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
27
tsconfig.json
Normal 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
28
types.js
Normal 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"
|
||||
*/
|
Loading…
Reference in New Issue
Block a user