-
Notifications
You must be signed in to change notification settings - Fork 2
/
contagious.js
289 lines (266 loc) · 7.44 KB
/
contagious.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/usr/bin/env node
/**
* Copyright (C) 2014 Claremont McKenna College, Scott A. Williams <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. **/
// Set our default parameters
var keyfile = '';
var verbose = false;
var recursive = false;
var paths = ['.'];
var exclude = [];
var servers = [''];
var user = 'root';
// Initialize our dependent modules
var Path = require('path');
var fs = require('fs');
var Async = require('async');
var Rsync = require('rsync');
var Inotify = require('inotify').Inotify;
inotify = new Inotify();
default_config = function(path){
return {path: path,
watch_for: Inotify.IN_DELETE | Inotify.IN_MODIFY | Inotify.IN_CREATE,
callback: dirwatch(path)
};
};
// Declare Functions
//
// Watch a directory
function addwatcher(path,conf){
// Add all paths to tracking
if (pathchk(path)){
var config = conf(Path.resolve(path));
inotify.addWatch(config);
}
}
// Event handler for when things get updated
function dirwatch(parent_path){
return function(event){
var name = event.name;
var mask = event.mask;
if (mask & Inotify.IN_Q_OVERFLOW){
// Nothing more we can do now. Let's get out of here.
console.log('Ran out of watchers! Check `sysctl -n fs.inotify.max_user_watches` for more info.');
process.exit(1);
}
if (!name){
return;
}
var path = parent_path + '/' + name;
if (!pathchk(path)){
return;
}
if (mask & Inotify.IN_ISDIR && mask & Inotify.IN_CREATE) {
inotify.addWatch(default_config(path));
}
if (mask){
switch (mask){
case Inotify.IN_DELETE:
msg = " was deleted."
break;
case Inotify.IN_MODIFY:
msg = " was modified."
break;
case Inotify.IN_CREATE:
msg = " was created."
break;
default:
msg = " was changed in some sort of way."
}
}
(verbose) ? console.log(path + msg) : '';
// Sync this change with all servers in the list
syncpool(parent_path);
}
}
// Recurse directories
var dive = function (dir,callback) {
// Assert that it's a function
if (typeof action !== "function")
action = function (error, file) { };
// Read the directory
fs.readdir(dir, function (err, list) {
// Return the error if something went wrong
if (err) callback(err);
// For every file in the list
list.forEach(function (file) {
// Full path of that file
var path = dir + "/" + file;
// Get the file's stats
fs.stat(path, function (err, stat) {
// If the file is a directory
if (stat && stat.isDirectory() && pathchk(path)){
addwatcher(path,default_config);
// Dive into the directory
dive(path, function(){});
}
});
});
});
setTimeout(function(){
callback(null, dir);
}, 3000);
};
// Read params from command line
function parseconfs(){
var args = process.argv;
args.splice(0,2);
function processconf(arg){
if (arg){
if (arg == '-h'){
usage();
}else if (arg == '-v'){
verbose = true;
}else if (arg == '-r'){
recursive = true;
}else if (arg.split('=')[0] == '--path'){
paths = arg.split('=')[1].split(',');
}else if (arg.split('=')[0] == '--exclude'){
exclude = arg.split('=')[1].split(',');
}else if (arg.split('=')[0] == '--server'){
servers = arg.split('=')[1].split(',');
}else if (arg.split('=')[0] == '--user'){
user = arg.split('=')[1];
}else if (arg.split('=')[0] == '--sshkey'){
keyfile = arg.split('=')[1];
// Exit if we don't know what to do
}else{
usage("Option \"" + arg.split("=")[0] + "\" was not recognized.");
}
processconf(args.shift());
}else{
// You can't sync without a destination
(servers.length <1) ? usage("Please specify at least one server.") : setwatchers();
}
}
// Ignore the first two
processconf(args.shift());
}
// Check if path is in exclude list
function pathchk(path){
for (i=0;i<exclude.length;i++){
if (path.indexOf(exclude[i]) != -1){
return false;
}
}
return true;
}
// Asynchronously walk and add watchers resursively on each path
function resync(dirs){
if (verbose) console.log("Finding sub-directories.");
function success(err, results){
(err) ? console.log(err) : console.log('All sub-directories were successfully added!');
}
Async.map(dirs,dive,success);
}
// Handler for recursive watching
function rpaths(){
if (verbose) console.log('Adding recursive paths.');
var results = [];
var ppaths = paths.slice(0);
function parsepath(path){
if (path){
if (verbose) console.log('Processing path ' + path);
var prspath = Path.resolve(path);
addwatcher(prspath,default_config);
results.push(prspath);
parsepath(ppaths.shift());
}else{
if (verbose) console.log('All paths resolved.');
return resync(results);
}
}
parsepath(ppaths.shift());
}
// Set Watchers
function setwatchers(){
// If recursive, find all the subdirectories
if (recursive) {
rpaths();
}else{
// Completely async since we don't care at this point
for (i<0;i<paths.length;i++){
var path = Path.resolve(paths[i]);
addwatcher(path,default_config);
}
}
}
// Basic rsync handler
function sync(srvpath,cb){
var rsync = new Rsync()
.shell('ssh -i ' + keyfile)
.flags('az')
.set('delete')
.source(srvpath.split(':')[1])
.destination(user + '@' + srvpath);
if (verbose){
rsync.flags += 'v';
console.log('Starting sync on ' + srvpath);
}
// Execute the command
return rsync.execute(function(error, code, cmd) {
if (error){
cb('RsyncError','WARNING: Error with syncing ' + srvpath + '. ' + error + ' using command ' + cmd);
}else{
cb(null, srvpath + ' was successfully synced. Using command: ' + cmd);
}
});
}
// Sync pool
// The uses async to put rsync tasks in the background
function syncpool(path){
var synclist = [];
var slist = servers.slice(0);
// Parse the path for rsync
function srvpathprs(server){
if (server) {
synclist.push(server + ':' + path + '/');
srvpathprs(slist.shift());
}else{
syncall(synclist);
}
}
// Handle the asynchronous rsync calls
function syncall(synclist){
Async.map(synclist,sync,function (err,results){
if (err){
console.log(results);
}else if(verbose){
for (i=0;i<results.length;i++){
console.log(results[i]);
}
}
});
}
srvpathprs(slist.shift());
}
// Print usage
function usage(error){
msg = 'Contagious.js Usage\n\n'
+ '-h Display this help text\n'
+ '-v Verbose output\n'
+ '-r Recursively watch directories\n'
+ '--path= Comma delimited paths: /home/foo,/var/www/html\n'
+ ' Current directory implied if omitted\n'
+ '--exclude= Comma delimited paths to be excluded: temp-write,.log\n'
+ '--server= Comma delimited servers: myserver.com,168.0.0.144\n'
+ '--user= SSH user name (root implied if omitted)\n'
+ '--sshkey= Path to SSH key\n';
msg += (typeof error !== 'undefined') ? '\n' + error : '';
console.log(msg);
(typeof error !== 'undefined') ? process.exit(1) : process.exit(0);
}
// Main Process
//
// Parse arguments and go from there
parseconfs();