1 | #!/usr/bin/env node
|
2 |
|
3 |
|
4 |
|
5 | const help=`Usage: skale [options] <command> [<args>]
|
6 |
|
7 | Create, run, deploy clustered node applications
|
8 |
|
9 | Commands:
|
10 | create <app> Create a new application
|
11 | run [<args>...] Run application
|
12 | deploy [<args>...] Deploy application
|
13 | status print status of local skale cluster
|
14 | stop Stop local skale cluster
|
15 |
|
16 | Options:
|
17 | -f, --file program to run (default: package name)
|
18 | -h, --help Show help
|
19 | -m, --memory MB set the memory space limit per worker (default 4000 MB)
|
20 | -r, --remote run in the cloud instead of locally
|
21 | --reset Restart cluster and cluster log
|
22 | -V, --version Show version
|
23 | -w, --worker num set the number of workers (default 2)
|
24 | `;
|
25 |
|
26 | const child_process = require('child_process');
|
27 | const fs = require('fs');
|
28 | const net = require('net');
|
29 |
|
30 | const argv = require('minimist')(process.argv.slice(2), {
|
31 | string: [
|
32 | 'c', 'config',
|
33 | 'f', 'file',
|
34 | 'H', 'host',
|
35 | 'k', 'key',
|
36 | 'p', 'port',
|
37 | 'm', 'memory',
|
38 | 'w', 'worker',
|
39 | ],
|
40 | boolean: [
|
41 | 'h', 'help',
|
42 | 'r', 'remote',
|
43 | 'V', 'version',
|
44 | 'reset',
|
45 | ],
|
46 | default: {
|
47 | H: 'skale.me', 'host': 'skale.me',
|
48 | p: '8888', 'port': 8888,
|
49 | }
|
50 | });
|
51 |
|
52 | var skale_port = 12346;
|
53 |
|
54 | if (argv.h || argv.help) {
|
55 | console.log(help);
|
56 | process.exit();
|
57 | }
|
58 | if (argv.V || argv.version) {
|
59 | var pkg = require('./package');
|
60 | console.log(pkg.name + '-' + pkg.version);
|
61 | process.exit();
|
62 | }
|
63 |
|
64 | const config = load(argv);
|
65 | const proto = config.ssl ? require('https') : require('http');
|
66 | const memory = argv.m || argv.memory || 4000;
|
67 | const worker = argv.w || argv.worker || 2;
|
68 |
|
69 | switch (argv._[0]) {
|
70 | case 'create':
|
71 | create(argv._[1]);
|
72 | break;
|
73 | case 'deploy':
|
74 | deploy(argv._.splice(1));
|
75 | break;
|
76 | case 'run':
|
77 | if (argv.r || argv.remote) run_remote(argv._.splice(1));
|
78 | else if (argv.reset) {
|
79 | stop_local_server(function () {
|
80 | fs.rename('skale-server.log', 'skale-server.log.old', function () {
|
81 | run_local(argv._.splice(1));
|
82 | });
|
83 | });
|
84 | } else
|
85 | run_local(argv._.splice(1));
|
86 | break;
|
87 | case 'status':
|
88 | status_local();
|
89 | break;
|
90 | case 'stop':
|
91 | stop_local_server();
|
92 | break;
|
93 | default:
|
94 | die('Error: invalid command: ' + argv._[0]);
|
95 | }
|
96 |
|
97 | function create(name) {
|
98 | console.log('create application ' + name);
|
99 | try {
|
100 | fs.mkdirSync(name);
|
101 | } catch (error) {
|
102 | die('skale create error: ' + error.message);
|
103 | }
|
104 | process.chdir(name);
|
105 | const pkg = {
|
106 | name: name,
|
107 | version: '0.1.0',
|
108 | private: true,
|
109 | keywords: [ 'skale' ],
|
110 | dependencies: {
|
111 | 'skale-engine': '^0.5.0'
|
112 | }
|
113 | };
|
114 | fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
115 | var src = `#!/usr/bin/env node
|
116 |
|
117 | var sc = require('skale-engine').context();
|
118 |
|
119 | sc.parallelize(['Hello world']).collect().then(function (res) {
|
120 | console.log(res);
|
121 | sc.end();
|
122 | });
|
123 | `;
|
124 | fs.writeFileSync(name + '.js', src);
|
125 | const npm = child_process.spawnSync('npm', ['install'], {stdio: 'inherit'});
|
126 | if (npm.status) die('skale create error: npm install failed');
|
127 | console.log(`Project ${name} is now ready.
|
128 | Pleas change directory to ${name}: "cd ${name}"
|
129 | To run your app: "skale run"
|
130 | To modify your app: edit ${name}.js`)
|
131 | }
|
132 |
|
133 | function die(err) {
|
134 | console.error(help);
|
135 | console.error(err);
|
136 | process.exit(1);
|
137 | }
|
138 |
|
139 | function load(argv) {
|
140 | var conf = {}, save = false;
|
141 | var path = argv.c || argv.config || process.env.SKALE_CONFIG || process.env.HOME + '/.skalerc';
|
142 | try { conf = JSON.parse(fs.readFileSync(path)); } catch (error) { save = true; }
|
143 | conf.host = argv.H || argv.host || process.env.SKALE_HOST || conf.host;
|
144 | conf.port = argv.p || argv.port || process.env.SKALE_PORT || conf.port;
|
145 | conf.key = argv.k || argv.key || conf.key;
|
146 | conf.ssl = argv.s || argv.ssl || (conf.ssl ? true : false);
|
147 | if (save || argv._[0] == 'init') fs.writeFileSync(path, JSON.stringify(conf, null, 2));
|
148 | return conf;
|
149 | }
|
150 |
|
151 | function start_skale(done) {
|
152 | const out = fs.openSync('skale-server.log', 'a');
|
153 | const err = fs.openSync('skale-server.log', 'a');
|
154 | const child = child_process.spawn('node_modules/skale-engine/bin/server.js', ['-l', worker, '-m', memory], {
|
155 | detached: true,
|
156 | stdio: ['ignore', out, err]
|
157 | });
|
158 | child.unref();
|
159 | try_connect(5, 1000, done);
|
160 | }
|
161 |
|
162 | function try_connect(nb_try, timeout, done) {
|
163 | const sock = net.connect(skale_port);
|
164 | sock.on('connect', function () {
|
165 | sock.end();
|
166 | done(null);
|
167 | });
|
168 | sock.on('error', function (err) {
|
169 | if (--nb_try <= 0) return done('skale-server not ok');
|
170 | setTimeout(function () { try_connect(nb_try, timeout, done); }, timeout);
|
171 | });
|
172 | }
|
173 |
|
174 | function stop_local_server(done) {
|
175 | const child = child_process.execFile('/usr/bin/pgrep', ['-f', 'skale-server ' + skale_port], function (err, pid) {
|
176 | if (pid) process.kill(pid.trim());
|
177 | if (done) done();
|
178 | });
|
179 | }
|
180 |
|
181 | function status_local() {
|
182 | const child = child_process.execFile('/bin/ps', ['ux'], function (err, out) {
|
183 | const lines = out.split(/\r\n|\r|\n/);
|
184 | for (var i = 0; i < lines.length; i++)
|
185 | if (i == 0 || lines[i].match(/ skale-/)) console.log(lines[i].trim());
|
186 | });
|
187 | }
|
188 |
|
189 | function run_local(args) {
|
190 | const pkg = JSON.parse(fs.readFileSync('package.json'));
|
191 | const cmd = argv.f || argv.file || pkg.name + '.js';
|
192 | args.splice(0, 0, cmd);
|
193 | try_connect(0, 0, function (err) {
|
194 | if (!err) return run_app();
|
195 | start_skale(run_app);
|
196 | });
|
197 | function run_app() { child = child_process.spawn('node', args, {stdio: 'inherit'}); }
|
198 | }
|
199 |
|
200 | function deploy(args) {
|
201 | const pkg = JSON.parse(fs.readFileSync('package.json'));
|
202 | fs.readFile(pkg.name + '.js', {encoding: 'utf8'}, function (err, data) {
|
203 | if (err) throw err;
|
204 |
|
205 | const postdata = JSON.stringify({pkg: pkg, src: data, args: args});
|
206 |
|
207 | const options = {
|
208 | hostname: config.host,
|
209 | port: config.port,
|
210 | path: '/deploy',
|
211 | method: 'POST',
|
212 | headers: {
|
213 | 'X-Auth': config.key,
|
214 | 'Content-Type': 'application/json',
|
215 | 'Content-Length': Buffer.byteLength(postdata)
|
216 | }
|
217 | };
|
218 |
|
219 | const req = proto.request(options, function (res) {
|
220 | res.setEncoding('utf8');
|
221 | res.pipe(process.stdout);
|
222 | });
|
223 |
|
224 | req.on('error', function (err) {throw err;});
|
225 | req.end(postdata);
|
226 | });
|
227 | }
|
228 |
|
229 | function run_remote(args) {
|
230 | const name = process.cwd().split('/').pop();
|
231 | const postdata = JSON.stringify({name: name, args: args});
|
232 |
|
233 | const options = {
|
234 | hostname: config.host,
|
235 | port: config.port,
|
236 | path: '/run',
|
237 | method: 'POST',
|
238 | headers: {
|
239 | 'X-Auth': config.key,
|
240 | 'Content-Type': 'application/json',
|
241 | 'Content-Length': Buffer.byteLength(postdata)
|
242 | }
|
243 | };
|
244 |
|
245 | const req = proto.request(options, function (res) {
|
246 | res.setEncoding('utf8');
|
247 | res.pipe(process.stdout);
|
248 | });
|
249 |
|
250 | req.on('error', function (err) {throw err;});
|
251 | req.end(postdata);
|
252 | }
|