├── .gitignore ├── LICENSE ├── README.md ├── TODO.md ├── bin └── apiary ├── docs ├── index.md ├── logging.md └── resources │ ├── CommunicationArchitecture.jpg │ ├── ControllerArchitecture.jpg │ ├── DetailArchitecture.jpg │ ├── DetailSSE.jpg │ ├── HighLevelArchitecture.jpg │ └── SystemEnvironmentArchitecture.jpg ├── examples ├── deploy-cloud9.json └── deploy.json ├── lib ├── apiary.js ├── apie │ └── index.js ├── cli │ ├── api │ │ └── client.js │ ├── apps.js │ ├── config.js │ ├── index.js │ ├── startup │ │ └── apiarywrap.js │ ├── system.js │ ├── users.js │ └── utils.js ├── core │ ├── config.js │ ├── controller.js │ ├── logging.js │ ├── service.js │ └── utils │ │ ├── clone.js │ │ ├── directories.js │ │ ├── index.js │ │ └── randomstring.js ├── sc │ ├── apis │ │ ├── cliserver.js │ │ └── index.js │ └── index.js ├── sre │ ├── index.js │ └── srec │ │ ├── api │ │ └── app.js │ │ ├── haibufix.js │ │ ├── srec.js │ │ └── srecplugin.js └── sse │ ├── httpproxy │ └── index.js │ └── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | node_modules/* 4 | 5 | /config.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Documentation License 2 | ===================== 3 | 4 | Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License 5 | 6 | http://creativecommons.org/licenses/by-nc-sa/3.0/ 7 | 8 | Copyright (c)2011 TTC (http://www.tolsma.net)/Sander Tolsma (http://sander.tolsma.net) 9 | 10 | 11 | Code License 12 | ============ 13 | 14 | MIT License (http://www.opensource.org/licenses/mit-license.php) 15 | 16 | Copyright (c)2011 TTC (http://www.tolsma.net)/Sander Tolsma (http://sander.tolsma.net) 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apiary 2 | 3 | *spawn multi-system multi-user [Node.JS] clouds, on your own hardware and/or with 3rd party virtual servers* 4 | 5 | ### Under development!!! Not stable yet!!! 6 | 7 | # What is apiary? 8 | 9 | Apiary is the open-source project that uses [Node.JS] and [haibu] for spawning and managing several multi-user [Node.JS] applications on multiple (physical or virtual) servers. 10 | 11 | # How does it work? 12 | 13 | `apiary` (which is English for 'beehive area') creates per (virtual) system per user application hives (or haibu, which is Japanese for "hive"). By using [haibu], [Node.JS] applications are transformed into "drones". This approach allows `apiary` and [haibu] to directly interact with [Node.JS] applications and add all sorts of additional functionality. 14 | 15 | `apiary` builds on the concept of "drones" used in [haibu] and exposes a robust and granular API for interacting with your and others [Node.JS] applications acros multiple (virtual) systems. At a low level, the API of `apiary` is exposed as a RESTFul HTTP webservice. Any system that supports basic HTTP requests can communicate with `apiary` System Environment Controllers and installed applications. If you are working in [Node.JS], `apiary` comes with a high-level [Node.JS] User Environment Controller javascript API client module. 16 | 17 | ## Where can I run apiary? 18 | 19 | `apiary` doesn't discriminate. If your environment supports [Node.JS] (v4.x, not the new v5.x branch), you can install `apiary` and start up your own multi-system multi-user [Node.JS] cloud. This makes `apiary` an ideal tool for both development purposes and production usage since you can seamlessly setup `apiary` on your local machine, on utility computing providers (such as Amazon EC2 or Rackspace) or on dedicated servers! 20 | 21 | # Installation 22 | 23 | Apiary is available in the NPM repository and can be installed globally with: 24 | 25 | `sudo npm install apiary -g` 26 | 27 | # An overview of using Apiary 28 | 29 | All CLI interaction has to be done as a user with root rights. On most Linux systems a user command can be 'upgraded' with root rights by using sudo... 30 | With the -h flag help can be requested on all CLi commands: 31 | 32 | `[sudo] apiary -h` 33 | 34 | ``` 35 | [sander@development examples]$ sudo ../bin/apiary -h 36 | info: 37 | ___ .______ __ ___ .______ ____ ____ 38 | / \ | _ \ | | / \ | _ \ \ \ / / 39 | / ^ \ | |_) | | | / ^ \ | |_) | \ \/ / 40 | / /_\ \ | ___/ | | / /_\ \ | / \_ _/ 41 | / _____ \ | | | | / _____ \ | |\ \-. | | 42 | /__/ \__\ | _| |__| /__/ \__\ | _| `.__| |__| 43 | 44 | info: Running Apiary CLI version 0.0.2 45 | info: Using CLI config file /home/sander/.apiaryconf 46 | info: No Apiary System running! 47 | info: Deployment script is /home/sander/Development/apiary/examples/deploy.json 48 | info: Current application user: 49 | info: 50 | info: apiary 51 | info: CLI interface to manage an Apiary System. 52 | info: Please refer to documentation of commands using `-h` or `--help`. 53 | info: 54 | info: commands 55 | info: apiary start 56 | info: apiary stop 57 | info: apiary status 58 | info: apiary clean 59 | info: apiary apps 60 | info: apiary config 61 | info: 62 | info: flags 63 | info: -s --silent Do not log to console 64 | info: -d --debug Log extended error messages 65 | info: -c --conf Configuration filename to use (default: .apiaryconf) 66 | info: 67 | [sander@development examples]$ 68 | ``` 69 | 70 | ## Starting up an `apiary` environment 71 | 72 | To start an Apiary system: 73 | 74 | `[sudo] apiary start` 75 | 76 | ``` 77 | [sander@development examples]$ sudo ../bin/apiary start 78 | info: 79 | ___ .______ __ ___ .______ ____ ____ 80 | / \ | _ \ | | / \ | _ \ \ \ / / 81 | / ^ \ | |_) | | | / ^ \ | |_) | \ \/ / 82 | / /_\ \ | ___/ | | / /_\ \ | / \_ _/ 83 | / _____ \ | | | | / _____ \ | |\ \-. | | 84 | /__/ \__\ | _| |__| /__/ \__\ | _| `.__| |__| 85 | 86 | info: Running Apiary CLI version 0.0.2 87 | info: Using CLI config file /home/sander/.apiaryconf 88 | info: No Apiary System running! 89 | info: Deployment script is /home/sander/Development/apiary/examples/deploy.json 90 | info: Current application user: 91 | info: 92 | info: Starting the Apiary system! 93 | info: 94 | info: The Apiary system is started as daemon! 95 | info: 96 | [sander@development examples]$ 97 | ``` 98 | 99 | to stop a running Apairy system: 100 | 101 | `[sudo] apiary stop` 102 | 103 | ### User configuration 104 | 105 | To add a system user as an Apiary user: 106 | 107 | `[sudo] apiary users add apiary_user_1` 108 | 109 | To remove an Apiary user: 110 | 111 | `[sudo] apiary users remove apiary_user_1` 112 | 113 | To list all users configured on the Apiary system: 114 | 115 | `[sudo] apiary users list` 116 | 117 | To stop apps from, and remove an Apiary user and also clean the users filesystem of Apiary directories and files: 118 | 119 | `[sudo] apiary users clean apiary_user_1` 120 | 121 | ### App configuration 122 | 123 | To start an application described by examples/deploy.json: 124 | 125 | `[sudo] apiary -f examples/deploy.json -u apiary_user_1 apps start` 126 | 127 | To stop an application: 128 | 129 | `[sudo] apiary -f examples/deploy.json -u apiary_user_1 apps stop` 130 | 131 | To list all running applications: 132 | 133 | `[sudo] apiary apps list` 134 | 135 | To list all running applications for a specific user: 136 | 137 | `[sudo] apiary apps list apiary_user_1` 138 | 139 | To clean the user filesystem from all files related to a running app (and stop the app): 140 | 141 | `[sudo] apiary -u apiary_user_1 apps clean test` 142 | 143 | ### deploy.json attribute settings 144 | 145 | Apiary uses a .json formated file in order to determine what to deploy. 146 | Also, `apiary` is a pull based server; this means that it will pull files from outside of the server in order to deploy instead of using uploading directly into the process. 147 | 148 | A basic deploy.json for a node.js application on apiary: 149 | 150 | ```json 151 | { 152 | "name": "test", 153 | "domain": "example.com", 154 | "repository": { 155 | "type": "git", 156 | "url": "https://github.com/stolsma/hellonode.git", 157 | }, 158 | "scripts": { 159 | "start": "server.js" 160 | } 161 | } 162 | ``` 163 | 164 | #### User 165 | 166 | The user attribute is optional and will represent the system user which will own the application. 167 | If not defined then the default system user setting of the `apiary` CLI will be used. 168 | 169 | ```json 170 | { 171 | "user": "system user" 172 | } 173 | ``` 174 | 175 | ####Name 176 | 177 | The name attribute is required and will represent the name of the application being deployed. 178 | 179 | ```json 180 | { 181 | "name": "app-name" 182 | } 183 | ``` 184 | 185 | ####Repositories 186 | 187 | Five application code repository types are supported. 188 | 189 | ##### git 190 | 191 | This type of repository will pull a git repository into `apiary` and deploy its contents. 192 | The branch attribute is optional! 193 | 194 | ```json 195 | { 196 | "repository": { 197 | "type": "git", 198 | "url": "http://path/to/git/server", 199 | "branch": "branch name" 200 | } 201 | } 202 | ``` 203 | 204 | ##### local 205 | 206 | This type of repository will pull a directory from the local file system into `apiary` and deploy its contents. 207 | 208 | ```json 209 | { 210 | "repository": { 211 | "type": "local", 212 | "directory": "/path/to/application" 213 | } 214 | } 215 | ``` 216 | 217 | ##### tar 218 | 219 | This type of repository will pull a remote tar archive into the `apiary` system and deploy its contents. 220 | 221 | ```json 222 | { 223 | "repository": { 224 | "type": "tar", 225 | "url": "http://path/to/archive.tar" 226 | } 227 | } 228 | ``` 229 | 230 | ##### zip 231 | 232 | This type of repository will pull a remote zip archive to the `apiary` system and deploy its contents. 233 | 234 | ```json 235 | { 236 | "repository": { 237 | "type": "zip", 238 | "url": "http://path/to/archive.zip" 239 | } 240 | } 241 | ``` 242 | 243 | ##### npm 244 | 245 | This type of repository will install a npm package as application. The package will be available as directory under its name and the scripts will be installed in the `.bin` directory. 246 | So scripts.start should have one of both as relative directory: 247 | 248 | ```json 249 | "scripts": { 250 | "start": ".bin/server.js" 251 | } 252 | ``` 253 | 254 | or: 255 | 256 | ```json 257 | "scripts": { 258 | "start": "name of npm package/server.js" 259 | } 260 | ``` 261 | 262 | ```json 263 | { 264 | "repository": { 265 | "type": "npm", 266 | "package": "name of npm package following package.json dependencies rules" 267 | } 268 | } 269 | ``` 270 | 271 | 272 | # Code documentation 273 | 274 | The code documentation of `apiary` is still very much a work in progress. We'll be actively updating the documentation in the upcoming months to make it easier to contribute code to `apiary`. 275 | 276 | A first preview of the current alpha documentation can be found [here](https://github.com/stolsma/apiary/blob/master/docs/index.md)! 277 | 278 | ## Run Tests 279 | All of the `apiary` tests are written in [vows], and cover all of the use cases described above. 280 | 281 | the tests can be started by typing `npm test` in the `apiary` directory. 282 | 283 | (To be implemented) 284 | 285 | 286 | Open Source Projects Used 287 | ========================= 288 | 289 | `apiary` wouldn't exists weren't for the wildly productive [Node.JS] community producing so many high quality software. 290 | Especially many thanks to the [Nodejitsu] people for writing such nice code ([haibu], [forever], [hook.io]). I learned a lot about javascript coding by just looking through their code!! 291 | 292 | Main projects that we use as building blocks: 293 | 294 | * [async] by [caolan] 295 | * [clip] by [bmeck] 296 | * [colors] by [marak] 297 | * [dnode] by [substack] 298 | * [eventemitter2] by [hij1nx] 299 | * [eyes] by [cloudhead] 300 | * [forever] by [indexzero] 301 | * [haibu] by [Nodejitsu] 302 | * [haibu-carapace] by [Nodejitsu] 303 | * [hook.io] by [marak] and others [hookio] 304 | * [nconf] by [indexzero] 305 | * [optimist] by [substack] 306 | * and of course [Node.JS]! 307 | 308 | Thanks to all developers and contributors of these projects! 309 | 310 | Documentation License 311 | ===================== 312 | 313 | Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License 314 | 315 | http://creativecommons.org/licenses/by-nc-sa/3.0/ 316 | 317 | Copyright (c)2011 [TTC](http://www.tolsma.net)/[Sander Tolsma](http://sander.tolsma.net/) 318 | 319 | 320 | Code License 321 | ============ 322 | 323 | [MIT License](http://www.opensource.org/licenses/mit-license.php) 324 | 325 | Copyright (c)2011 [TTC](http://www.tolsma.net)/[Sander Tolsma](http://sander.tolsma.net/) 326 | 327 | Permission is hereby granted, free of charge, to any person obtaining a copy 328 | of this software and associated documentation files (the "Software"), to deal 329 | in the Software without restriction, including without limitation the rights 330 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 331 | copies of the Software, and to permit persons to whom the Software is 332 | furnished to do so, subject to the following conditions: 333 | 334 | The above copyright notice and this permission notice shall be included in 335 | all copies or substantial portions of the Software. 336 | 337 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 338 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 339 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 340 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 341 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 342 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 343 | THE SOFTWARE. 344 | 345 | 346 | [bmeck]: https://github.com/bmeck 347 | [caolan]: https://github.com/caolan 348 | [cloudhead]: https://github.com/cloudhead 349 | [hij1nx]: https://github.com/hij1nx 350 | [hookio]: https://github.com/hookio 351 | [indexzero]: https://github.com/indexzero 352 | [marak]: https://github.com/Marak 353 | [Nodejitsu]: http://nodejitsu.com 354 | [substack]: https://github.com/substack 355 | 356 | [async]: https://github.com/caolan/async 357 | [clip]: https://github.com/bmeck/clip 358 | [colors]: https://github.com/Marak/colors.js 359 | [dnode]: https://github.com/substack/dnode 360 | [eventemitter2]: https://github.com/hij1nx/EventEmitter2 361 | [eyes]: https://github.com/cloudhead/eyes.js 362 | [forever]: https://github.com/indexzero/forever 363 | [haibu]: https://github.com/nodejitsu/haibu 364 | [haibu-carapace]: https://github.com/nodejitsu/haibu-carapace 365 | [hook.io]: https://github.com/hookio/hook.io 366 | [nconf]: https://github.com/indexzero/nconf 367 | [optimist]: https://github.com/substack/node-optimist 368 | [Node.JS]: http://nodejs.org/ 369 | [vows]: http://vowsjs.org -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Things to develop (in no particular order): 4 | 5 | * Use sort of forever proces to control spawning and existence of SRE processes. Forever can't be used at this moment because it doesn't have a possibility to use fork to create child proces 6 | * Aditional documentation on architecture 7 | * Creation of system Network Gateway's. First to implement is http-proxy! 8 | * Implementation of System Service Environment (SSE) with system services like MySQL, Postgres, mDNS, Bonjour, uPnP for use by all user SRE services!! 9 | * Add REST interface to the System Controller 10 | * Add examples -------------------------------------------------------------------------------- /bin/apiary: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var clip = require('clip'), 4 | app = new clip(); 5 | 6 | require('../lib/cli')(app); 7 | require('../lib/cli/system')(app); 8 | require('../lib/cli/users')(app); 9 | require('../lib/cli/apps')(app); 10 | require('../lib/cli/config')(app); 11 | 12 | app.run(); -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Apiary code documentation 2 | 3 | The `apiary` code is structured in 5 main building blocks: 4 | 5 | * Central Service Store (CSS) 6 | * Service Environment Controller (SEC) 7 | * Physical or Virtual Operating Systems 8 | * System Controller (SC) 9 | * Service Runtime Environments (SRE) with the running Applications (or 'Drones') 10 | 11 | Those blocks as talking to each other through IP connections. 12 | 13 | ![High Level Architecture](https://github.com/stolsma/apiary/raw/master/docs/resources/HighLevelArchitecture.jpg "High Level Architecture") 14 | 15 | 16 | ## System Environment Architecture 17 | 18 | ![Detail System Architecture](https://github.com/stolsma/apiary/raw/master/docs/resources/DetailArchitecture.jpg "Detail System Architecture") 19 | 20 | ### System Environment Design 21 | 22 | ![System Environment Design](https://github.com/stolsma/apiary/raw/master/docs/resources/SystemEnvironmentArchitecture.jpg "System Environment Design") 23 | 24 | ### Communication Architecture Design 25 | 26 | ![Communication Architecture Design](https://github.com/stolsma/apiary/raw/master/docs/resources/CommunicationArchitecture.jpg "Communication Architecture Design") 27 | 28 | ### System Service Environment 29 | 30 | The System Environment also has standard System Services running (SS) in the System Service Environment like a HTTP/HTTPS/WEBSOCKET proxy 31 | 32 | ![Detail System Service Environment Design](https://github.com/stolsma/apiary/raw/master/docs/resources/DetailSSE.jpg "Detail System Service Environment Design") 33 | 34 | 35 | ## Control Architecture 36 | 37 | ![High Level Control Architecture](https://github.com/stolsma/apiary/raw/master/docs/resources/ControllerArchitecture.jpg "High Level Control Architecture") 38 | -------------------------------------------------------------------------------- /docs/logging.md: -------------------------------------------------------------------------------- 1 | 2 | ## Logging channels 3 | 4 | 5 | ### Event channel 6 | 7 | `event::[subsystem]::[event], [arguments]` or 8 | `event::[subsystem]::[name]::[event], [arguments]` 9 | 10 | `[subsystem]` can be one off: 11 | 12 | * `sc` System Controller Events 13 | * `sre` Service Resource Environment events 14 | * `sse` System Service Environment events 15 | * `apie` API Environment events 16 | * `srec` Service Resource Environment Controller events 17 | * `ss` System Service events 18 | * `apis` API Service events 19 | 20 | `[name]` is only available on `srec`, `ss` and `apis` subsystem events and is the name of the targeted service eventstream. 21 | 22 | `[event]` is the eventstream going through the 23 | 24 | 25 | ### Log channel 26 | 27 | `log::[subsystem], [description], [meta-object]` or 28 | `log::[subsystem]::[name]` 29 | 30 | `[subsystem]` can be one off: 31 | 32 | * `sc` System Controller Events 33 | * `sre` Service Resource Environment events 34 | * `sse` System Service Environment events 35 | * `apie` API Environment events 36 | * `srec` Service Resource Environment Controller events 37 | * `ss` System Service events 38 | * `apis` API Service events 39 | 40 | `[name]` is only available on `srec`, `ss` and `apis` subsystem log events and is the name of the targeted service log stream. 41 | 42 | 43 | ### Exception channel 44 | 45 | `exception::[process]` or 46 | `exception::[process]::[name]` 47 | 48 | `[process]` can be one off: 49 | 50 | * `apiary` System Controller process exceptions 51 | * `srec` Service Resource Environment process exceptions 52 | * `ss` System Service process exceptions 53 | * `apis` API Service process exceptions 54 | 55 | `[name]` is only available on `srec`, `ss` and `apis` process exceptions streams and is the name of the targeted service process. 56 | -------------------------------------------------------------------------------- /docs/resources/CommunicationArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/apiary/a2acb31e589b268439cda7c04c5ee88859a22027/docs/resources/CommunicationArchitecture.jpg -------------------------------------------------------------------------------- /docs/resources/ControllerArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/apiary/a2acb31e589b268439cda7c04c5ee88859a22027/docs/resources/ControllerArchitecture.jpg -------------------------------------------------------------------------------- /docs/resources/DetailArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/apiary/a2acb31e589b268439cda7c04c5ee88859a22027/docs/resources/DetailArchitecture.jpg -------------------------------------------------------------------------------- /docs/resources/DetailSSE.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/apiary/a2acb31e589b268439cda7c04c5ee88859a22027/docs/resources/DetailSSE.jpg -------------------------------------------------------------------------------- /docs/resources/HighLevelArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/apiary/a2acb31e589b268439cda7c04c5ee88859a22027/docs/resources/HighLevelArchitecture.jpg -------------------------------------------------------------------------------- /docs/resources/SystemEnvironmentArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/apiary/a2acb31e589b268439cda7c04c5ee88859a22027/docs/resources/SystemEnvironmentArchitecture.jpg -------------------------------------------------------------------------------- /examples/deploy-cloud9.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud9", 3 | "domain": "cloud9.localhost", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/ajaxorg/cloud9.git" 7 | }, 8 | "scripts": { 9 | "start": "bin/cloud9.js", 10 | "arguments": [ 11 | "-w", "%h", 12 | "-l", "::0" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "domains": [ 4 | "test1.tolsma.net", 5 | "test2.tolsma.net", 6 | "test3.tolsma.net" 7 | ], 8 | "sng": [{ 9 | "type": "http", 10 | "address": "::0", 11 | "port": 80 12 | }], 13 | "service": [], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/stolsma/hellonode.git" 17 | }, 18 | "scripts": { 19 | "start": "server.js", 20 | "arguments": [ 21 | "-w", "firstwarg", 22 | "-h", "%h", 23 | "-a", "%a", 24 | "-c", "%c", 25 | "-o", "%o" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/apiary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * apiary: spawn multi-system multi-user node.js clouds, on your own hardware and/or with 3rd party virtual servers 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module apiary 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | // catch all non caugth exceptions so we can do something with it from the stderr logs or TTY!! 13 | process.on('uncaughtException', function (err) { 14 | console.log('exception: ', err, err.stack) 15 | console.error('Caught exception: ', err); 16 | console.error('Caught exception: ', err.stack); 17 | }); 18 | 19 | // create the underlying System Controller for this Apiary process 20 | var apiary = module.exports = require('./sc')(); 21 | 22 | // and create the links to the important modules to make it possible to donkey punch classes/modules 23 | apiary.config = require('./core/config'); 24 | apiary.Logging = require('./core/logging'); 25 | apiary.Controller = require('./core/controller'); 26 | apiary.Service = require('./core/service'); 27 | apiary.Sc = require('./sc'); 28 | apiary.Sre = require('./sre'); 29 | apiary.Sse = require('./sse'); 30 | apiary.Apie = require('./apie'); 31 | apiary.initialized = false; 32 | 33 | 34 | /** 35 | * Start the Apiary System 36 | * @param {Object} options For options.system see apiary.init 37 | * @param {Function} cb Callback to call when ready (err) 38 | */ 39 | apiary.start = function start(options, cb) { 40 | options = options || {}; 41 | cb = cb || function(){}; 42 | 43 | // Initialize clean Apiary System 44 | apiary.init(options, function(err) { 45 | apiary.Sc.prototype.start.call(apiary, function(errList) { 46 | cb(errList); 47 | }); 48 | }); 49 | }; 50 | 51 | 52 | /** 53 | * Stop execution of Apiary System in a clean way 54 | * @param {Boolean} exit Also call process.exit(0) (default is true) 55 | * @param {Function} cb Callback to call when ready; Callback will be called with (err) 56 | */ 57 | apiary.stop = function stop(exit, cb) { 58 | exit = exit || true; 59 | cb = cb || function(){}; 60 | 61 | // call the SC to stop itself and its subsystems gracefully 62 | apiary.Sc.prototype.stop.call(apiary, function(errList) { 63 | if (exit) { 64 | // to get all events processed: on nextTick exit this process 65 | process.nextTick(function () { 66 | process.exit(0); 67 | }); 68 | } 69 | cb(errList); 70 | }); 71 | } 72 | 73 | 74 | /** 75 | * Initialize the Apiary environment 76 | * @param {Object} options Overriding configuration options 77 | * @param {Function} cb Callback to call when ready or error (err) 78 | */ 79 | apiary.init = function init(options, cb) { 80 | options = options || {}; 81 | 82 | // Get config file and default configuration values 83 | if (apiary.initialized) { 84 | return cb(); 85 | } 86 | 87 | apiary.config.init(options, function (err) { 88 | if (err) { 89 | return cb(err); 90 | } 91 | 92 | // startup logger 93 | apiary.logger = apiary.Logging(apiary.config.get('logging')); 94 | 95 | // Do some signal handling for SIGINT/ctrl-c (only react to it once!!!) 96 | process.once('SIGINT', apiary.stop.bind(apiary, true)); 97 | 98 | // initialized and call callback 99 | apiary.initialized = true; 100 | cb(); 101 | }); 102 | }; -------------------------------------------------------------------------------- /lib/apie/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An API Environment subsystem definition as used by the System Controller. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module apie 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var path = require('path'), 13 | inherits = require('util').inherits; 14 | 15 | var apiary = require('../apiary'); 16 | 17 | /** 18 | * The API Environment class definition as used by the System Controller 19 | * @class Apie 20 | * @extends Controller 21 | * 22 | * @constructor 23 | * Create API Environment subsystem instance 24 | * @param {Object} Options APIE config options: 25 | * `uid` {String/Integer} Socket user id, default is -1 (current) 26 | * `gid` {String/Integer} Socket group id, if not defined the given uid is used, if thats not defined -1 (current) 27 | */ 28 | var Apie = module.exports = function(options) { 29 | // if called as function return Instance 30 | if (!(this instanceof Apie)) return new Apie(options); 31 | 32 | // set default options 33 | options = options || {}; 34 | 35 | // Call the parent constructor 36 | Sse.super_.call(this, options); 37 | 38 | // fill serviceTypes store with known apie services 39 | // this.serviceAdd('httpproxy', path.join(__dirname, './httpproxy/index.js')); 40 | }; 41 | inherits(Apie, apiary.Controller); 42 | 43 | 44 | /** 45 | * Initialize all external events this instance will react on 46 | */ 47 | Apie.prototype.initEvents = function() { 48 | // Call the parent initEvents function 49 | Sre.super_.prototype.initEvents.call(this); 50 | 51 | // API services interface 52 | // this.on('', this.apisStart); 53 | }; 54 | 55 | 56 | /** 57 | * Start an API Service process 58 | * @param {Object} options Object with the following possible configuration options: 59 | * `name` {String} The name of the service to start 60 | * `type` {String} The API Service type to start 61 | * `cwd` {String} The current working directory to start the API Service in, default is current process cwd. 62 | * `uid` {String/Integer} API user uid, default is -1 (current) 63 | * `gid` {String/Integer} API user gid, if not defined the given uid is used, if thats not defined -1 (current) 64 | * @param {Function} cb Callback to call when API Service is running 65 | */ 66 | Apie.prototype.startService = function(options, cb) { 67 | // set default options 68 | options = options || {}; 69 | options.uid = options.uid || this.uid; 70 | options.gid = options.gid || this.gid; 71 | 72 | // start the Api Service with the given options and callback 73 | var child = Apie.super_.prototype.startService.call(this, options, cb); 74 | 75 | // 76 | // TODO: Temporary message passing, needs to be replaced by other 77 | // mechanism. At this moment only one, two and three deep events are bridged 78 | // 79 | function msgBridge() { 80 | var parts = this.event.split(this.delimiter), 81 | args = Array.prototype.slice.call(arguments); 82 | 83 | // cut child and replace with child name 84 | parts.splice(0, 1, options.name); 85 | parts.join(this.delimiter); 86 | 87 | // send message through to System Controller 88 | apiary.emit.apply(apiary, [parts.join(this.delimiter)].concat(args)); 89 | } 90 | child.on('child::*', msgBridge); 91 | child.on('child::*::*', msgBridge); 92 | child.on('child::*::*::*', msgBridge); 93 | 94 | return child; 95 | } 96 | 97 | 98 | /** 99 | * Log messages from this instance to the logger 100 | */ 101 | Apie.prototype.log = function() { 102 | apiary.emit.apply(apiary, arguments); 103 | }; -------------------------------------------------------------------------------- /lib/cli/api/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * client.js: API Client for the Apiary CLI. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @author TTC/Sander Tolsma 8 | * @docauthor TTC/Sander Tolsma 9 | */ 10 | 11 | var inherits = require('util').inherits, 12 | EventEmitter2 = require('eventemitter2').EventEmitter2, 13 | dnode = require('dnode'); 14 | 15 | /** 16 | * The Cli Client class definition 17 | * @class CliClient 18 | * @extends EventEmitter2 19 | * 20 | * @constructor 21 | * Create CLI Client class 22 | * @param {Object} options CliClient config options: 23 | */ 24 | var CliClient = module.exports = function(options) { 25 | // if called as function return Instance 26 | if (!(this instanceof CliClient)) return new CliClient(options); 27 | 28 | // call parent EventEmitter2 contructor 29 | EventEmitter2.call(this, options); 30 | 31 | // set default options 32 | options = options || {}; 33 | this.socket = options.cliSocket || null; 34 | 35 | // not connected yet!! 36 | this.connected = false; 37 | } 38 | inherits(CliClient, EventEmitter2); 39 | 40 | /** 41 | * Connect to the given CLI Socket 42 | * @param {String} socket (optional) Socket to connect to 43 | * @return {CliClient} This cliClient instance, makes linking possible 44 | */ 45 | CliClient.prototype.connect = function(socket) { 46 | var self = this; 47 | 48 | // check arguments 49 | if (typeof(socket) == 'function') { 50 | cb = socket; 51 | socket = this.socket || null; 52 | } 53 | 54 | // Connect to the CLI server on the given socket 55 | this.client = dnode.connect(socket, function (remote, conn) { 56 | // add connection listening events 57 | conn.once('end', function() { 58 | self.conn = null; 59 | self.remote = null; 60 | self.connected = false; 61 | self.emit('disconnected'); 62 | }); 63 | 64 | // Save remote CLI API functions, set to connected and emit 'connected' event 65 | self.conn = conn; 66 | self.remote = remote; 67 | self.connected = true; 68 | self.emit('connected', remote); 69 | }); 70 | 71 | // relay error events 72 | this.client.on('error', function (err) { 73 | self.emit('error', err); 74 | }) 75 | 76 | return this; 77 | } 78 | 79 | CliClient.prototype.end = function() { 80 | // end connection to the Apiary System 81 | this.conn.end(); 82 | } -------------------------------------------------------------------------------- /lib/cli/apps.js: -------------------------------------------------------------------------------- 1 | /* 2 | * apps.js: Commands for the `apps` resource in the apiary CLI. 3 | * 4 | * (C) 2010, Nodejitsu Inc. 5 | * (C) 2011, TTC/Sander Tolsma 6 | * See LICENSE file for license 7 | */ 8 | 9 | var fs = require('fs'), 10 | colors = require('colors'); 11 | 12 | // own modules 13 | var utils = require('./utils'); 14 | 15 | /** 16 | * Do setup operations on the given Clip App instance 17 | * @param {ClipApp} app The Clip application instance to execute operations on 18 | */ 19 | module.exports = function setupApps(app) { 20 | app.usage('/apps', function config(cmd, tty, end) { 21 | tty.info(''); 22 | tty.info('apiary apps'.bold.underline); 23 | tty.info(' Actions related to apiary application deployments.'); 24 | tty.info(''); 25 | tty.info('commands'.bold); 26 | tty.info(' apiary apps clean'.green + ' '.yellow); 27 | tty.info(' apiary apps list'.green + ' '.yellow); 28 | tty.info(' apiary apps start'.green); 29 | tty.info(' apiary apps stop'.green + ' '.yellow); 30 | tty.info(''); 31 | tty.info('flags'.bold); 32 | tty.info(' -s --silent Do not log to console'); 33 | tty.info(' -d --debug Log extended error messages'); 34 | tty.info(' -c --conf [.apiaryconf] The file to use as our configuration'); 35 | tty.info(' -f --file [deploy.json] The file that is our app deployment instruction'); 36 | tty.info(' -u --user Override the user to apply the commands on'); 37 | tty.info(''); 38 | end(); 39 | }); 40 | 41 | /** 42 | * Middleware to get data used by `apps` cli commands. Will be executed before the cli router!! 43 | */ 44 | app.use(function(cmd, tty, next) { 45 | var app = {}; 46 | var deploymentscript = cmd.config.get('f') || cmd.config.get('file') || 'deploy.json'; 47 | try { 48 | var deployment = JSON.parse(fs.readFileSync(deploymentscript)); 49 | tty.info('Deployment script is ' + fs.realpathSync(deploymentscript).magenta); 50 | ['repository', 'name', 'domain', 'domains', 'subdomain', 'subdomains', 'sng', 'service', 'env', 'user', 'scripts', 'dependencies'].forEach(function(item) { 51 | if (deployment.hasOwnProperty(item)) { 52 | app[item] = deployment[item]; 53 | } 54 | }) 55 | } catch (e) { 56 | tty.info('`' + deploymentscript.magenta + '` file was not found or its content is not correct JSON!'); 57 | } 58 | 59 | app.user = cmd.config.get('u') || cmd.config.get('user') || app.user || cmd.config.get('cli:user') || ''; 60 | tty.info('Current application user: ' + app.user.yellow); 61 | cmd.app = app; 62 | 63 | // and go to next middeware or the router 64 | next(); 65 | }); 66 | 67 | /** 68 | * Stops and removes (an) application(s) from the apiary system 69 | */ 70 | app.cli(['/apps/clean', '/apps/clean/:appname'], function appsClean(cmd, tty, end) { 71 | if (cmd.connected()) { 72 | var app = cmd.app; 73 | app.name = cmd.params.appname || cmd.app.name; 74 | cmd.client.remote.appClean(app.user, app, function(err, result) { 75 | if (err) { 76 | cmd.logError('Error cleaning app: ' + app.name, err); 77 | tty.end(1); 78 | end(); 79 | } 80 | 81 | tty.info(''); 82 | tty.info('Successfully cleaned app: ' + app.name.yellow); 83 | tty.info(''); 84 | tty.end(0); 85 | end(); 86 | }); 87 | } else { 88 | cmd.logError('No Apiary System running!'); 89 | tty.end(1); 90 | end(); 91 | } 92 | }); 93 | 94 | app.usage(['/apps/clean'], function appsCleanUsage(cmd, tty, end) { 95 | tty.info(''); 96 | tty.info('apiary apps clean'.green + ' '.yellow); 97 | tty.info(' Removes all traces of an application from the apiary system'); 98 | tty.info(' See `apiary apps -h` for more details'); 99 | tty.info(''); 100 | tty.info('params'.bold); 101 | tty.info(' appname [deployment script value] name of the application'); 102 | tty.info(''); 103 | end(); 104 | }); 105 | 106 | /** 107 | * Start an application on the apiary system 108 | */ 109 | app.cli(['/apps/start'], function appsStart(cmd, tty, end) { 110 | if (cmd.connected()) { 111 | var app = cmd.app; 112 | cmd.client.remote.appStart(app.user, app, function(err, result) { 113 | if (err) { 114 | cmd.logError('Error starting app: ' + app.name, err); 115 | tty.end(1); 116 | end(); 117 | } 118 | 119 | tty.info(''); 120 | tty.info('Successfully started app: ' + app.name.yellow + ' on ' + 121 | (result.host + ':' + result.port).green 122 | ); 123 | tty.info(''); 124 | tty.end(0); 125 | end(); 126 | }); 127 | } else { 128 | cmd.logError('No Apiary System running!'); 129 | tty.end(1); 130 | end(); 131 | } 132 | }); 133 | 134 | app.usage(['/apps/start'], function appsStartUsage(cmd, tty, end) { 135 | tty.info(''); 136 | tty.info('apiary apps start'.green); 137 | tty.info(' Starts and deploys if necessary an application on/to the apiary system'); 138 | tty.info(' See `apiary apps -h` for more details'); 139 | tty.info(''); 140 | end(); 141 | }); 142 | 143 | /** 144 | * Stops an application on the apiary system 145 | */ 146 | app.cli(['/apps/stop', '/apps/stop/:appname'], function appsStop(cmd, tty, end) { 147 | if (cmd.connected()) { 148 | var app = cmd.app; 149 | app.name = cmd.params.appname || app.name; 150 | 151 | cmd.client.remote.appStop(app.user, app, function(err, result) { 152 | if (err) { 153 | cmd.logError('Error stopping app: ' + app.name, err); 154 | tty.end(1); 155 | end(); 156 | } 157 | 158 | tty.info(''); 159 | tty.info('Successfully stopped app: ' + app.name.yellow); 160 | tty.end(0); 161 | end(); 162 | }); 163 | } else { 164 | cmd.logError('No Apiary System running!'); 165 | tty.end(1); 166 | end(); 167 | } 168 | }); 169 | 170 | app.usage(['/apps/stop'], function appsStopUsage(cmd, tty, end) { 171 | tty.info(''); 172 | tty.info('apiary apps stop'.green + ' '.yellow); 173 | tty.info(' Stops all drones of an application on the apiary system'); 174 | tty.info(' See `apiary apps -h` for more details'); 175 | tty.info(''); 176 | tty.info('params'.bold); 177 | tty.info(' appname [deployment script value] name of the application'); 178 | tty.info(''); 179 | end(); 180 | }); 181 | 182 | /** 183 | * Update an application on the apiary system 184 | */ 185 | app.cli(['/apps/update', '/apps/update/:appname'], function appsUpdate(cmd, tty, end) { 186 | if (cmd.connected()) { 187 | var app = cmd.app; 188 | app.name = cmd.params.appname || app.name; 189 | 190 | cmd.client.remote.appUpdate(app.user, app, function(err, result) { 191 | if (err) { 192 | cmd.logError('Error updating app: ' + app.name, err); 193 | tty.end(1); 194 | end(); 195 | } 196 | 197 | tty.info(''); 198 | tty.info('Successfully updated app: ' + app.name.yellow); 199 | tty.end(0); 200 | end(); 201 | }); 202 | } else { 203 | cmd.logError('No Apiary System running!'); 204 | tty.end(1); 205 | end(); 206 | } 207 | }); 208 | 209 | app.usage(['/apps/update'], function appsStopUsage(cmd, tty, end) { 210 | tty.info(''); 211 | tty.info('apiary apps update'.green + ' '.yellow); 212 | tty.info(' Stops an application, Cleans all source and deps and then starts '); 213 | tty.info(' all started drones of an application on the apiary system'); 214 | tty.info(' See `apiary apps -h` for more details'); 215 | tty.info(''); 216 | tty.info('params'.bold); 217 | tty.info(' appname [deployment script value] name of the application'); 218 | tty.info(''); 219 | end(); 220 | }); 221 | 222 | /** 223 | * Lists all apps running on the apiary system 224 | */ 225 | app.cli(['/apps/list', 'apps/list/:user'], function appsList (cmd, tty, end) { 226 | if (cmd.connected()) { 227 | var user = cmd.params.user || 'all'; 228 | 229 | cmd.client.remote.appList(user, function(err, result) { 230 | if (err) { 231 | cmd.logError('Error listing applications'); 232 | tty.end(1); 233 | end(); 234 | } 235 | 236 | if (user !== 'all') { 237 | result = { 238 | srecs: [user], 239 | results: [[result]] 240 | } 241 | } 242 | utils.processAppList(result, tty); 243 | tty.end(0); 244 | end(); 245 | }); 246 | } else { 247 | cmd.logError('No Apiary System running!'); 248 | tty.end(1); 249 | end(); 250 | } 251 | }); 252 | 253 | app.usage(['/apps/list'], function appsListUsage(cmd, tty, end) { 254 | tty.info(''); 255 | tty.info('apiary apps list'.green + ' '.yellow); 256 | tty.info(' Lists all the applications running on the apiary system'); 257 | tty.info(' See `apiary apps -h` for more details'); 258 | tty.info(''); 259 | tty.info('params'.bold); 260 | tty.info(' user [username] return all running apps from given user'); 261 | tty.info(''); 262 | end(); 263 | }); 264 | } -------------------------------------------------------------------------------- /lib/cli/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * config.js: Commands for the `config` resource in the apiary CLI. 3 | * 4 | * (C) 2010, Nodejitsu Inc. 5 | * (C) 2011, TTC/Sander Tolsma 6 | * See LICENSE file for license 7 | */ 8 | 9 | var colors = require('colors'); 10 | 11 | /** 12 | * Do setup operations on the given Clip App instance 13 | * @param {ClipApp} app The Clip application instance to execute operations on 14 | */ 15 | module.exports = function setupConfig(app) { 16 | app.usage('/config', function config(cmd, tty, end) { 17 | tty.info(''); 18 | tty.info('apiary config'.bold.underline); 19 | tty.info(' Actions related to the apiary configuration file.'); 20 | tty.info(''); 21 | tty.info('notes'.bold); 22 | tty.info(' The configuration will be found recursively up the file system.'); 23 | tty.info(' If no configuration file is found the HOME folder will be used.'); 24 | tty.info(' A default configuration file will be created if none exist.'); 25 | tty.info(''); 26 | tty.info('commands'.bold); 27 | tty.info(' apiary config get'.green + ' '.yellow); 28 | tty.info(' apiary config set'.green + ' '.yellow); 29 | tty.info(''); 30 | tty.info('flags'.bold); 31 | tty.info(' -c --conf [.apiaryconf] The file to use as our configuration'); 32 | tty.info(''); 33 | end(); 34 | }); 35 | 36 | /** 37 | * Shows a system configuration parameter 38 | */ 39 | app.cli('/config/get/:id', function configGet(cmd, tty, end) { 40 | tty.info(cmd.params.id + ' = ' + ('' + cmd.config.get(cmd.params.id)).yellow); 41 | tty.end(0); 42 | end(); 43 | }); 44 | 45 | app.usage('/config/get', function configGetUsage(cmd, tty, end) { 46 | tty.info(''); 47 | tty.info('apiary config get'.green + ' '.yellow); 48 | tty.info(' Gets the value of a property in the apiary configuration'); 49 | tty.info(' See `apiary config -h` for more details'); 50 | tty.info(''); 51 | tty.info('params'.bold); 52 | tty.info(' id - nconf compatible name of the property'); 53 | end(); 54 | }); 55 | 56 | /** 57 | * Sets a system configuration parameter 58 | */ 59 | app.cli('/config/set/:id/:value', function configSet(cmd, tty, end) { 60 | cmd.config.set(cmd.params.id, cmd.params.value); 61 | cmd.config.save(); 62 | tty.end(0); 63 | end(); 64 | }); 65 | 66 | app.usage('/config/set', function configSetUsage(cmd, tty, end) { 67 | tty.info(''); 68 | tty.info('apiary config set'.green + ' '.yellow); 69 | tty.info(' Sets the value of a property in the apiary configuration'); 70 | tty.info(' See `apiary config -h` for more details'); 71 | tty.info(''); 72 | tty.info('params'.bold); 73 | tty.info(' id - nconf compatible name of the property'); 74 | tty.info(' value - json compatible value of the property'); 75 | end(); 76 | }); 77 | } -------------------------------------------------------------------------------- /lib/cli/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * index.js: General configuration of the Apiary CLI. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var path = require('path'), 9 | fs = require('fs'), 10 | inspect = require('util').inspect, 11 | colors = require('colors'); 12 | 13 | // own cli modules 14 | var utils = require('./utils'); 15 | 16 | // base location of central Apiary config, log and socket files 17 | var base = path.join('/var', 'local', 'apiary'), 18 | apiaryConf = '.apiaryconf'; 19 | 20 | /** 21 | * Do setup operations on the given Clip App instance 22 | * @param {ClipApp} app The Clip application instance to execute operations on 23 | */ 24 | module.exports = function setupConfig(app) { 25 | // React on `s` or `silent` argument by removing the output 26 | app.flag(['s', 'silent'], function(cmd, tty, next) { 27 | tty.remove(tty.transports.Console); 28 | }) 29 | 30 | // Add error handler who will react on `d` or `debug` argument by doing 31 | // extended error output. If not already exists create central location for 32 | // Apiary config, log and socket files 33 | app.use(function(cmd, tty, next) { 34 | cmd.logError = function(msg, err) { 35 | tty.error(''); 36 | tty.error(msg); 37 | if (err) tty.error(err.message); 38 | tty.error(''); 39 | if (err && (cmd.flags['d'] || cmd.flags['debug'])) { 40 | inspect(err); 41 | tty.error(''); 42 | } 43 | }; 44 | 45 | // create .apiaryconf or -c/--conf if it doesnt exists 46 | // and change owner to ENV properties SUDO_UID / SUDO_GID 47 | var uid = parseInt(process.env.SUDO_UID) || 0, 48 | gid = parseInt(process.env.SUDO_GID) || 0, 49 | cliConfFile = cmd.flags['c'] || cmd.flags['conf'] || path.join(process.env.HOME, apiaryConf); 50 | 51 | try { 52 | if (!path.existsSync(cliConfFile)) { 53 | fs.writeFileSync(cliConfFile, JSON.stringify(utils.createDefaultConfig(cmd.flags['l'] || cmd.flags['location'] || base), null, 2)); 54 | fs.chmodSync(cliConfFile, 0660); 55 | fs.chownSync(cliConfFile, uid, gid); 56 | } 57 | } catch (e) { 58 | cmd.logError('Error creating ' + cliConfFile, e); 59 | } 60 | 61 | next(); 62 | }); 63 | 64 | // Read the given config file. 65 | app.config(apiaryConf, { 66 | flags: ['c', 'conf'] 67 | }); 68 | 69 | // Middleware to show where the configuration for this cli came from and 70 | // to connect to the config defined running Apiary System (if running) 71 | app.use(function(cmd, tty, next) { 72 | // Read all dirs from config file and look if they exists and if not create with standard file mode, UID and GID!!!! 73 | utils.createDirs(cmd.config.get('cli'), 0, 0, function() { 74 | // set banner 75 | utils.banner(tty); 76 | 77 | // set current version + show config file used 78 | var version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'))).version; 79 | cmd.config.set('apiary:system:version', version); 80 | tty.info('Running Apiary CLI version ' + version.magenta); 81 | tty.info('Using CLI config file ' + cmd.config.file.file.magenta); 82 | 83 | // define connected checking function 84 | cmd.connected = function() { 85 | return (cmd.client && cmd.client.connected); 86 | } 87 | 88 | // try to connect to a running Apiary System 89 | var cliSocket = path.join(cmd.config.get('cli:socket'), cmd.config.get('cli:socketFile')); 90 | utils.getClient(cliSocket, connected, notConnected); 91 | 92 | function connected(client, api) { 93 | cmd.client = client; 94 | tty.info('Apiary System running! Connected on socket: ' + cliSocket.magenta); 95 | next(); 96 | } 97 | 98 | function notConnected(err, client, cause) { 99 | if (err) { 100 | cmd.logError('Problem connecting to the Apiary System!', err); 101 | } else if (cause == 'EACCES') { 102 | cmd.logError('Not enough privileges to connect to the requested Apiary System!'); 103 | } else { 104 | tty.info('No Apiary System running!'.red); 105 | next(); 106 | } 107 | } 108 | }); 109 | }); 110 | 111 | /** 112 | * Standard usage info when nothing executes... 113 | */ 114 | app.usage(function usage(cmd, tty, end) { 115 | tty.info(''); 116 | tty.info('apiary'.bold.underline); 117 | tty.info(' CLI interface to manage an Apiary System.'); 118 | tty.info(' Please refer to documentation of commands using `-h` or `--help`.'); 119 | tty.info(''); 120 | tty.info('commands'.bold); 121 | tty.info(' apiary start'.green); 122 | tty.info(' apiary stop'.green); 123 | tty.info(' apiary status'.green); 124 | tty.info(' apiary clean'.green); 125 | tty.info(' apiary apps'.green); 126 | tty.info(' apiary users'.green); 127 | tty.info(' apiary config'.green); 128 | tty.info(''); 129 | tty.info('flags'.bold); 130 | tty.info(' -s --silent Do not log to console'); 131 | tty.info(' -d --debug Log extended error messages'); 132 | tty.info(' -c --conf Configuration filename to use (default: .apiaryconf)'); 133 | tty.info(' -l --location Base location of central Apiary config, log and socket'); 134 | tty.info(' files (default: /var/local/apiary). Only used when'); 135 | tty.info(' configuration file (see -c --conf) does not exists yet!'); 136 | tty.info(''); 137 | end(); 138 | }); 139 | } -------------------------------------------------------------------------------- /lib/cli/startup/apiarywrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * apiarywrap.js: Script wrap for starting the local Apiary core process. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module apiarywrap 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | // startup intercom ipc event communications channel 13 | require('intercom'); 14 | 15 | var path = require('path'); 16 | 17 | var apiary = require('../../apiary'), 18 | CliServer = require('../../sc/apis/cliserver'); 19 | 20 | // Function to execute when the parent asks us to start 21 | process.parent.once('child::start', function(options, cbEvent) { 22 | var apiaryOptions = options.apiaryOptions; 23 | 24 | // TODO implement the correct location for the cli API, i.e. in the SCAPI Service 25 | // This is temporary!!!! : create a CliServer instance and start listening on the given socket 26 | apiary.cliServer = CliServer().listen(path.join(options.socket, options.socketFile), function(err) { 27 | }); 28 | 29 | // location of the APIE and SSE socket files and the location of the log files 30 | apiaryOptions.socket = apiaryOptions.socket || options.socket; 31 | apiaryOptions.logging = { location: options.logs }; 32 | 33 | // and start the Apiary System with the given options 34 | apiary.start(apiaryOptions, function(err) { 35 | // return the result to the parent 36 | process.parent.emit(cbEvent, err); 37 | }); 38 | 39 | }); 40 | 41 | // Function to execute when the parent asks us to stop 42 | process.parent.on('child::stop', function(cbEvent) { 43 | apiary.stop(false, function(errList) { 44 | // return the result to the parent 45 | process.parent.emit(cbEvent, errList); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /lib/cli/system.js: -------------------------------------------------------------------------------- 1 | /** 2 | * system.js: Commands for the system resources in the Apiary CLI. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var path = require('path'), 9 | colors = require('colors'); 10 | 11 | // own modules 12 | var utils = require('./utils'); 13 | 14 | /** 15 | * Do setup operations on the given Clip App instance 16 | * @param {ClipApp} app The Clip application instance to execute operations on 17 | */ 18 | module.exports = function setupConfig(app) { 19 | /** 20 | * Starts an Apiary system in this process 21 | */ 22 | app.cli('/start/direct', function start(cmd, tty, end) { 23 | if (!cmd.connected()) { 24 | tty.info(''); 25 | tty.info('Starting the Apiary system!'.magenta); 26 | 27 | // start Apiary in this process 28 | utils.start(cmd.config, function(err) { 29 | if (err) { 30 | cmd.logError('Error starting the Apiary system!', err); 31 | tty.end(1); 32 | end(); 33 | } 34 | tty.info(''); 35 | tty.info('The Apiary system is started!'.magenta); 36 | tty.info(''); 37 | // No end() because this process needs to keep running!!! ;-) 38 | }); 39 | } else { 40 | cmd.logError('Already an Apiary System started!'); 41 | tty.end(1); 42 | end(); 43 | } 44 | }); 45 | 46 | /** 47 | * Starts a daemonized Apiary system 48 | */ 49 | app.cli(['/start', '/start/daemon'], function start(cmd, tty, end) { 50 | if (!cmd.connected()) { 51 | tty.info(''); 52 | tty.info('Starting the Apiary system!'.magenta); 53 | 54 | // daemonize this start script and start child process with Apiary 55 | utils.startDaemonized(cmd.config); 56 | 57 | tty.info(''); 58 | tty.info('The Apiary system is starting as daemon!'.magenta); 59 | tty.info(''); 60 | } else { 61 | cmd.logError('Already an Apiary System started!'); 62 | tty.end(1); 63 | end(); 64 | } 65 | }); 66 | 67 | app.usage('/start', function startUsage(cmd, tty) { 68 | tty.info(''); 69 | tty.info('apiary start'.green); 70 | tty.info(' Starts the Apiary System. This command must be executed with root privileges ([sudo])'); 71 | tty.info(' See `apiary config -h` for more configuration details'); 72 | tty.info(''); 73 | tty.info('commands'.bold); 74 | tty.info(' apiary start [deamon]'.green); 75 | tty.info(' apiary start [direct]'.green); 76 | tty.info(''); 77 | }); 78 | 79 | /** 80 | * Cleanly stops the running Apiary System 81 | */ 82 | app.cli('/stop', function stop(cmd, tty, end) { 83 | if (cmd.connected()) { 84 | utils.stop(cmd.client, cmd.config.get('cli'), function(err, pid) { 85 | if (err) { 86 | if (err.code == 'ESRCH') { 87 | return tty.error('Apiary Forever process not there!') 88 | } else 89 | return tty.error('Error stopping Apiary: ', err); 90 | } 91 | tty.info(''); 92 | tty.info('Pid of the Apiary Forever process stopped was: ' + pid); 93 | tty.info(''); 94 | tty.end(0); 95 | end(); 96 | }); 97 | } else { 98 | cmd.logError('No Apiary System running!'); 99 | tty.end(1); 100 | end(); 101 | } 102 | }); 103 | 104 | app.usage('/stop', function stopUsage(cmd, tty) { 105 | tty.info(''); 106 | tty.info('apiary stop'.green); 107 | tty.info(' Stops the Apiary System. This command must be executed with root privileges ([sudo])'); 108 | tty.info(' See `apiary config -h` for more configuration details'); 109 | tty.info(''); 110 | }); 111 | 112 | /** 113 | * Gives the status of the (running) Apiary System 114 | */ 115 | app.cli('/status', function status(cmd, tty, end) { 116 | if (cmd.connected()) { 117 | utils.status(function(err, result) { 118 | if (err) { 119 | tty.error(''); 120 | tty.error('Error getting the Apiary System status!!'.red); 121 | tty.error(err); 122 | return 123 | } 124 | 125 | tty.info(''); 126 | tty.info('Pid is: ' + result); 127 | tty.info(''); 128 | tty.end(0); 129 | end(); 130 | }); 131 | } else { 132 | tty.info(''); 133 | tty.info('No Apiary System running!'); 134 | tty.info(''); 135 | tty.end(1); 136 | end(); 137 | } 138 | }); 139 | 140 | app.usage('/status', function statusUsage(cmd, tty) { 141 | tty.info(''); 142 | tty.info('apiary status'.green); 143 | tty.info(' Gives the current status of the Apiary System. This command must be executed with root privileges ([sudo])'); 144 | tty.info(''); 145 | }); 146 | 147 | /** 148 | * Clean the logfiles, and other dynamic files 149 | */ 150 | app.cli('/clean', function clean(cmd, tty, end) { 151 | if (!cmd.connected()) { 152 | utils.cleanDirs(cmd.config.get('cli'), function(err, result){ 153 | if (err) { 154 | tty.error(''); 155 | tty.error('Error cleaning the Apiary System!!'.red); 156 | tty.error(err); 157 | return 158 | } 159 | 160 | tty.info(''); 161 | tty.info('Cleaned the Apiary System!!'); 162 | tty.info(''); 163 | tty.end(0); 164 | end(); 165 | }); 166 | } else { 167 | cmd.logError('Can not clean when the Apiary System is running!'); 168 | tty.end(1); 169 | end(); 170 | } 171 | }); 172 | 173 | app.usage('/clean', function stopClean(cmd, tty) { 174 | tty.info(''); 175 | tty.info('apiary clean'.green); 176 | tty.info(' Cleans the dynamic stored data like logs and configs. This command must be executed with root privileges ([sudo])'); 177 | tty.info(''); 178 | }); 179 | } -------------------------------------------------------------------------------- /lib/cli/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * users.js: Commands for the `users` resource in the apiary CLI. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var colors = require('colors'); 9 | 10 | // own modules 11 | var utils = require('./utils'); 12 | 13 | /** 14 | * Do setup operations on the given Clip App instance 15 | * @param {ClipApp} app The Clip application instance to execute operations on 16 | */ 17 | module.exports = function setupConfig(app) { 18 | app.usage('/users', function config(cmd, tty, end) { 19 | tty.info(''); 20 | tty.info('apiary users'.bold.underline); 21 | tty.info(' Actions related to system users.'); 22 | tty.info(''); 23 | tty.info('commands'.bold); 24 | tty.info(' apiary users add'.green + ' '.yellow); 25 | tty.info(' apiary users remove'.green + ' '.yellow); 26 | tty.info(' apiary users list'.green); 27 | tty.info(' apiary users clean'.green + ' '.yellow); 28 | tty.info(''); 29 | tty.info('flags'.bold); 30 | tty.info(' -c --conf [.apiaryconf] The file to use as our configuration'); 31 | tty.info(''); 32 | end(); 33 | }); 34 | 35 | /** 36 | * Adds a system user to the Apiary System 37 | */ 38 | app.cli('/users/add/:id', function usersAdd(cmd, tty, end) { 39 | if (cmd.connected()) { 40 | var user = cmd.params.id || cmd.app.user; 41 | if (!user) { 42 | cmd.logError('No system user given!'); 43 | tty.end(1); 44 | return end(); 45 | } 46 | cmd.client.remote.userAdd(user, function(err) { 47 | if (err) { 48 | cmd.logError('Error adding user: ' + user.yellow, err); 49 | tty.end(1); 50 | end(); 51 | } 52 | 53 | tty.info(''); 54 | tty.info('Successfully added user: ' + user.yellow); 55 | tty.info(''); 56 | tty.end(0); 57 | end(); 58 | }); 59 | } else { 60 | cmd.logError('No Apiary System running!'); 61 | tty.end(1); 62 | end(); 63 | } 64 | }); 65 | 66 | app.usage('/users/add', function usersAddUsage(cmd, tty, end) { 67 | tty.info(''); 68 | tty.info('apiary users add'.green + ' '.yellow); 69 | tty.info(' Adds the given username to the running Apiary system'); 70 | tty.info(' See `apiary config -h` for more details'); 71 | tty.info(''); 72 | tty.info('params'.bold); 73 | tty.info(' id - system user name to add'); 74 | tty.info(''); 75 | end(); 76 | }); 77 | 78 | /** 79 | * Removes a system user from the Apiary System 80 | */ 81 | app.cli('/users/remove/:id', function usersRemove(cmd, tty, end) { 82 | if (cmd.connected()) { 83 | var user = cmd.params.id || cmd.app.user; 84 | if (!user) { 85 | cmd.logError('No system user given!'); 86 | tty.end(1); 87 | return end(); 88 | } 89 | cmd.client.remote.userRemove(user, function(err) { 90 | if (err) { 91 | cmd.logError('Error removing user: ' + user.yellow, err); 92 | tty.end(1); 93 | end(); 94 | } 95 | 96 | tty.info(''); 97 | tty.info('Successfully removed user: ' + user.yellow); 98 | tty.end(0); 99 | end(); 100 | }); 101 | } else { 102 | cmd.logError('No Apiary System running!'); 103 | tty.end(1); 104 | end(); 105 | } 106 | }); 107 | 108 | app.usage('/users/remove', function usersRemoveUsage(cmd, tty, end) { 109 | tty.info(''); 110 | tty.info('apiary users remove'.green + ' '.yellow); 111 | tty.info(' Removes the given username from the running Apiary system'); 112 | tty.info(' See `apiary config -h` for more details'); 113 | tty.info(''); 114 | tty.info('params'.bold); 115 | tty.info(' id - system user name to remove'); 116 | tty.info(''); 117 | end(); 118 | }); 119 | 120 | /** 121 | * Lists all system users defined on the Apiary System 122 | */ 123 | app.cli('/users/list', function usersList(cmd, tty, end) { 124 | if (cmd.connected()) { 125 | cmd.client.remote.userList(function(err, result) { 126 | if (err) { 127 | cmd.logError('Error listing users'); 128 | tty.end(1); 129 | end(); 130 | } 131 | 132 | utils.processUserList(result, tty); 133 | tty.end(0); 134 | end(); 135 | }); 136 | } else { 137 | cmd.logError('No Apiary System running!'); 138 | tty.end(1); 139 | end(); 140 | } 141 | }); 142 | 143 | app.usage('/users/list', function usersListUsage(cmd, tty, end) { 144 | tty.info(''); 145 | tty.info('apiary users list'.green); 146 | tty.info(' Lists all users on the running Apiary system'); 147 | tty.info(' See `apiary config -h` for more details'); 148 | tty.info(''); 149 | end(); 150 | }); 151 | 152 | /** 153 | * Stops apps and resource environment, removes a user from the apiary system and 154 | * removes apiary files/directories from user environment 155 | */ 156 | app.cli(['/users/clean/:id'], function usersClean(cmd, tty, end) { 157 | if (cmd.connected()) { 158 | var user = cmd.params.id || cmd.app.user; 159 | if (!user) { 160 | cmd.logError('No system user given!'); 161 | tty.end(1); 162 | return end(); 163 | } 164 | cmd.client.remote.userClean(user, function(err, result) { 165 | if (err) { 166 | cmd.logError('Error cleaning user: ' + cmd.params.id, err); 167 | tty.end(1); 168 | end(); 169 | } 170 | 171 | tty.info(''); 172 | tty.info('Successfully cleaned user: ' + cmd.params.id.yellow); 173 | tty.info(''); 174 | tty.end(0); 175 | end(); 176 | }); 177 | } else { 178 | cmd.logError('No Apiary System running!'); 179 | tty.end(1); 180 | end(); 181 | } 182 | }); 183 | 184 | app.usage(['/users/clean'], function usersCleanUsage(cmd, tty, end) { 185 | tty.info(''); 186 | tty.info('apiary users clean'.green + ' '.yellow); 187 | tty.info(' Removes all traces of a user from the apiary system'); 188 | tty.info(' and removes all traces of apiary from the user environment'); 189 | tty.info(' See `apiary apps -h` for more details'); 190 | tty.info(''); 191 | tty.info('params'.bold); 192 | tty.info(' id - system user name to clean'); 193 | tty.info(''); 194 | end(); 195 | }); 196 | } -------------------------------------------------------------------------------- /lib/cli/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * utils.js: General functions for the Apiary CLI. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var path = require('path'), 9 | fs = require('fs'), 10 | color = require('colors'); 11 | 12 | var apiaryUtils = require('../core/utils'), 13 | CliClient = require('./api/client'); 14 | 15 | /** 16 | * Create a default CLI configuration object based from the given base location 17 | * @params {String} base Base directory for all Apiary files 18 | * @returns (Object) Created default configuration object 19 | */ 20 | exports.createDefaultConfig = function(base) { 21 | return { 22 | apiary: { 23 | config: { 24 | file: path.join(base, 'apiary.json'), // location of the general apiary configuration file 25 | } 26 | }, 27 | cli: { 28 | root: base, // root of all the apiary related files 29 | forever: path.join(base, '4evr'), // location of forever daemonizing control files 30 | pidFile: 'apiary.pid', // name of the pid locking file for the daemonized startup 31 | logs: { 32 | root: path.join(base, 'logs'), // Generic log root directory, specific location paramaters will prefer 33 | forever: path.join(base, '4evr'), // Path to log output from forever process (when daemonized) 34 | foreverFile: 'apiary.4evr', // File forever will write forever process stdout and stderr to. 35 | // stdout: path.join(base, 'logs'), // Path to log output from child stdout 36 | stdoutFile: 'apiary.stdout', // File forever will write child process stdout to. 37 | // stderr: path.join(base, 'logs'), // Path to log output from child stderr 38 | stderrFile: 'apiary.stderr' // File forever will write child process stderr to. 39 | }, 40 | socket: path.join(base, 'socket'), 41 | socketFile: 'apiary.socket' 42 | } 43 | } 44 | } 45 | 46 | 47 | /** 48 | * Start the Apiary System with given options and logging to stdout and stderr 49 | * @param {Object} config Apiary CLI configuration option store 50 | * @param {Function} cb Callbck to continue with (err) 51 | */ 52 | exports.start = function(config, cb) { 53 | var Child = require('intercom').EventChild, 54 | options = { 55 | apiaryOptions : config.get('apiary'), 56 | socket : config.get('cli:socket'), 57 | socketFile : config.get('cli:socketFile'), 58 | logs : config.get('cli:logs:root') 59 | }, 60 | forkOptions = { 61 | visible: true, 62 | forever: true, 63 | minUptime: 2000, // Minimum time a child process has to be up. Forever will 'exit' otherwise. 64 | spinSleepTime: 1000, // Interval between restarts if a child is spinning (i.e. alive < minUptime). 65 | cwd: process.cwd(), 66 | options: [], // Additional arguments to pass to the script, 67 | spawnWith: { 68 | setsid: true // new process group? (for signal sharing) 69 | } 70 | }; 71 | 72 | cb = cb || function(){} 73 | 74 | // start the main Apiary process 75 | var child = Child(path.join(__dirname, 'startup', 'apiarywrap.js'), forkOptions); 76 | 77 | child.on('stdout', function(txt) { 78 | console.log(new Date() + txt.toString()); 79 | }); 80 | 81 | child.on('stderr', function(txt) { 82 | console.error(new Date() + txt.toString()); 83 | }); 84 | 85 | // listen for callback info by using a random callback event of 60/6 = 10 characters 86 | var cbEvent = apiaryUtils.randomString(60) + '::ready'; 87 | // actions to execute when the Service is indicating that its initialized and running 88 | child.on(cbEvent, function(data) { 89 | cb(); 90 | }); 91 | 92 | // actions to execute when event communication is ready 93 | child.on('rpcready', function() { 94 | var childData = child.data; 95 | // give the new Apiary process its configuration to initialize with 96 | child.emit('child::start', options, cbEvent); 97 | }); 98 | 99 | // Do some signal handling for SIGINT/ctrl-c (only react to it once!!!) 100 | process.once('SIGINT', function() { 101 | // listen for callback info by using a random callback event of 60/6 = 10 characters 102 | var cbEvent = apiaryUtils.randomString(60) + '::ready'; 103 | // actions to execute when the Service is indicating that its initialized and running 104 | child.once(cbEvent, function(data) { 105 | child.stop(); 106 | process.exit(0); 107 | }); 108 | 109 | // stop the Apiary process 110 | child.emit('child::stop', cbEvent); 111 | }); 112 | 113 | // start the Apiary process 114 | child.start(); 115 | } 116 | 117 | 118 | /** 119 | * Start the Apiary System with a daemonized CLI control proces and logging to 120 | * path.join(config.logs.root/forever, config.logs.foreverFile) 121 | * @param {Object} config Apiary CLI configuration option store 122 | */ 123 | exports.startDaemonized = function(config) { 124 | var daemon = require('daemon'), 125 | logFile = path.join(config.get('cli:logs:forever') || config.get('cli:logs:root'), config.get('cli:logs:foreverFile')), 126 | pidFile = path.join(config.get('cli:forever'), config.get('cli:pidFile')); 127 | 128 | // let the CLI do its job and with next tick daemonize and start Apiary 129 | process.nextTick(function() { 130 | var fd = fs.openSync(logFile, 'a+'); 131 | 132 | daemon.start(fd); 133 | daemon.lock(pidFile); 134 | 135 | process.on('exit', function () { 136 | fs.unlinkSync(pidFile); 137 | }); 138 | 139 | // start the main Apiary process 140 | exports.start(config); 141 | }); 142 | } 143 | 144 | 145 | /** 146 | * Read pidfile for pid of the CLI daemon and use that to send SIGINT and stop 147 | * the running Apiary system 148 | */ 149 | exports.stop = function(cliClient, dirs, cb) { 150 | fs.readFile(path.join(dirs.forever, dirs.pidFile), function(err, pid) { 151 | 152 | function cliKill() { 153 | cliClient.remote.getServerPid(function(pid) { 154 | killProcess(pid, cb); 155 | }); 156 | } 157 | 158 | function killProcess(pid, cb) { 159 | // Signal the apiary system monitor to stop safely 160 | try { 161 | process.kill(pid, 'SIGINT'); 162 | } 163 | catch (err) { 164 | return cb(err, pid); 165 | } 166 | cb(null, pid); 167 | } 168 | 169 | return (err) ? 170 | cliKill() : 171 | killProcess(pid, function(err) { 172 | return (err) ? 173 | cliKill() : 174 | cb(null, pid); 175 | }); 176 | }); 177 | } 178 | 179 | 180 | /** 181 | * Create a CLI client connection to the running Apiary System (if any!) 182 | * @param (String} cliSocket The socket to try to connect to 183 | * @param {Function} connected Function to call when there is a connection with a running Apiary System 184 | * @param {Function} notConnected Function to call when it wasn't possible to connect to an Apiary System 185 | * @return {CliClient} Returns a cliClient instance that tries to connect 186 | */ 187 | exports.getClient = function(cliSocket, connected, notConnected) { 188 | // create client and connect to given socket 189 | var client = CliClient().connect(cliSocket); 190 | 191 | function error(err) { 192 | // remove listeners 193 | client.removeListener('error', error); 194 | client.removeListener('connected', connect); 195 | 196 | // check type of error (ENOENT = socket doesn't exist, EACCES = no privileges to connect to socket) 197 | if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT' || err.code === 'EACCES') { 198 | // no client connection possible, so return only client 199 | return notConnected(null, client, err.code); 200 | } 201 | 202 | // don't know this error so assume not connected 203 | return notConnected(err, client); 204 | } 205 | 206 | function connect(api) { 207 | // remove listeners 208 | client.removeListener('error', error); 209 | client.removeListener('connected', connect); 210 | 211 | // and call callback with the required arguments 212 | connected(client, api); 213 | } 214 | 215 | // add listening functions 216 | client.on('connected', connect); 217 | client.on('error', error); 218 | 219 | // make other handling possible 220 | return client; 221 | } 222 | 223 | 224 | /** 225 | * Create an array of directories from a given config. used by createDirs and cleanDirs 226 | * @private 227 | */ 228 | function dirList(config) { 229 | var confAttr = ['root', 'forever', 'logs.root', 'logs.forever', 'logs.stdout', 'logs.stderr', 'socket'], 230 | dirs = [], props, i, check; 231 | 232 | confAttr.forEach(function(key) { 233 | props = key.split('.'); 234 | check = config; 235 | for (i = 0; i < props.length; i++) { 236 | if (typeof(check[props[i]]) == 'undefined') break; 237 | check = check[props[i]]; 238 | if (i == props.length-1) dirs.push(check); 239 | } 240 | }); 241 | 242 | return dirs; 243 | } 244 | 245 | /** 246 | * Create all directories given in de config object with uid and gid. 247 | * @param {Object} config CLI config with directorie paths to initialize 248 | * @param {Integer} uid Uid of the directory to create 249 | * @param {Integer} gid Gid of the directory to create 250 | * @param {function} callback Continuation to respond to when complete 251 | */ 252 | exports.createDirs = function(config, uid, gid, cb) { 253 | var options = { 254 | mode: 0775, 255 | uid: uid, 256 | gid: gid 257 | }; 258 | apiaryUtils.directories.create(dirList(config), options, cb); 259 | } 260 | 261 | /** 262 | * Clean the filesystem of all files produced by a running Apiary System 263 | * @param {Object} config CLI config with directorie paths to delete 264 | * @param {Function} cb Callback function being called when ready 265 | */ 266 | exports.cleanDirs = function(config, cb) { 267 | apiaryUtils.directories.remove(dirList(config), cb); 268 | } 269 | 270 | /** 271 | * Process returned users list 272 | */ 273 | exports.processUserList = function(list, tty) { 274 | var rows = [['user']], 275 | colors = ['green']; 276 | 277 | for (var user in list) { 278 | rows.push([ 279 | user 280 | ]); 281 | } 282 | 283 | tty.info(''); 284 | if (rows.length === 1) { 285 | tty.info("No users found."); 286 | } else { 287 | tty.info('Users:'); 288 | tty.info(''); 289 | tty.putRows('data', rows, colors); 290 | } 291 | tty.info(''); 292 | } 293 | 294 | /** 295 | * Process returned app list 296 | */ 297 | exports.processAppList = function(list, tty) { 298 | var rows = [['user', 'app', 'domains', 'address', 'pid']], 299 | colors = ['green', 'yellow', 'red', 'magenta', 'magenta'], 300 | users = list.srecs, 301 | results = list.results, 302 | i; 303 | 304 | for (i=0; i < users.length; i++) { 305 | var user = users[i], 306 | appDrones = results[i][0]; 307 | 308 | for (var app in appDrones) { 309 | var appInfo = appDrones[app].app; 310 | var drones = appDrones[app].drones; 311 | drones.forEach(function (drone) { 312 | rows.push([ 313 | user, 314 | app, 315 | (appInfo.domains || [appInfo.domain]).map(function(item) { 316 | return item ? item : 'undefined'.blue; 317 | }).join(' & '), 318 | drone.host + ':' + drone.port, 319 | drone.pid 320 | ]); 321 | }); 322 | } 323 | } 324 | 325 | tty.info(''); 326 | if (rows.length === 1) { 327 | tty.info("No applications found."); 328 | } else { 329 | tty.info('Applications:'); 330 | tty.info(''); 331 | tty.putRows('data', rows, colors); 332 | } 333 | tty.info(''); 334 | } 335 | 336 | /** 337 | * Get the status of the running Apiary System 338 | */ 339 | exports.status = function(cb) { 340 | cb(); 341 | } 342 | 343 | /** 344 | * Put the banner on given output 345 | */ 346 | exports.banner = function(tty) { 347 | var welcome = [ 348 | '', 349 | ' ___ .______ __ ___ .______ ____ ____ ', 350 | ' / \\ | _ \\ | | / \\ | _ \\ \\ \\ / / ', 351 | ' / ^ \\ | |_) | | | / ^ \\ | |_) | \\ \\/ / ', 352 | ' / /_\\ \\ | ___/ | | / /_\\ \\ | / \\_ _/ ', 353 | ' / _____ \\ | | | | / _____ \\ | |\\ \\-. | | ', 354 | '/__/ \\__\\ | _| |__| /__/ \\__\\ | _| `.__| |__| ', 355 | '' 356 | ].join('\n'); 357 | 358 | tty.info(welcome.green); 359 | } -------------------------------------------------------------------------------- /lib/core/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * config.js: General configuration store of the Apiary System. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var path = require('path'), 9 | nconf = require('nconf'); 10 | 11 | var root = path.join(__dirname, '..', '..'), 12 | config = module.exports = new nconf.Provider(); 13 | 14 | /** 15 | * Sets up configuration options with default options for maximum 16 | * flexibility in usage. 17 | * @param {Object} options Options to setup for flexibility 18 | */ 19 | function setupOptions(options) { 20 | options = options || {}; 21 | 22 | // Config options 23 | options.config = options.config || {}; 24 | // main config file to use for loading and saving the configuration to. 25 | options.config.file = options.config.file || path.join(root, 'apiary.json'); 26 | 27 | // Logging options 28 | options.logging = options.logging || {}; 29 | // main logging location 30 | options.logging.location = options.logging.location || path.join(root, 'logs'); 31 | var groups = [ 32 | { event: '**', filename: 'apiary.all' }, 33 | { event: 'event::**', filename: 'apiary.events' }, 34 | { event: 'log::**', filename: 'apiary.log' }, 35 | { event: 'exception::**', filename: 'apiary.exceptions' } 36 | ]; 37 | options.logging.groups = options.logging.groups || groups; 38 | 39 | // System options 40 | options.system = options.system || {}; 41 | // define the SRE (Haibu) to Drone communication base port to start with 42 | options.system.haibuPort = options.system.haibuPort || 5200; 43 | 44 | // SRE options 45 | options.sre = options.sre || {}; 46 | 47 | return options; 48 | } 49 | 50 | /** 51 | * Deep merge a given object with the configuration 52 | * @param {Object} obj Object to write to the store 53 | * @param {String} root Key of the obj to write to 54 | */ 55 | function setObject(obj, root) { 56 | // check arguments 57 | if (!obj || (typeof(obj) != 'object')) return; 58 | root = root ? root : ''; 59 | 60 | Object.keys(obj).forEach(function (key) { 61 | if (typeof(obj[key]) == 'object') { 62 | var current = config.get(root + key); 63 | if (typeof(current) == 'undefined' || typeof(current) != 'object') config.set(root + key, {}); 64 | setObject(obj[key], root + key + ':'); 65 | } else 66 | config.set(root + key, obj[key]); 67 | }); 68 | } 69 | 70 | /** 71 | * Initialize the configuration store by reading the config file from disk 72 | * and merging it with given options. 73 | * @param {Object} options Overriding configuration options. 74 | * @param {function} cb Continuation to respond to when complete. 75 | */ 76 | config.init = function(options, cb) { 77 | if (!cb) { 78 | cb = options; 79 | options = {}; 80 | } 81 | 82 | // Setup `options` with default options. 83 | options = setupOptions(options); 84 | 85 | // Setup nconf to use the 'file' store 86 | config.add('file', { 87 | file: options.config.file 88 | }); 89 | 90 | // load the configuration and if non existent create an empy one. 91 | config.load(); 92 | 93 | // deep merge options to config, attributes in options are more important!!! 94 | setObject(options); 95 | 96 | // and save this new config 97 | config.save(); 98 | 99 | // continue!! 100 | cb(); 101 | }; 102 | 103 | 104 | /** 105 | * Save the Apiary configuration file 106 | */ 107 | config.save = function() { 108 | // save this config 109 | config.file.saveSync(); 110 | }; -------------------------------------------------------------------------------- /lib/core/controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * controller.js: Generic child service controller module. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module controller 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var path = require('path'), 13 | inherits = require('util').inherits, 14 | async = require('async'), 15 | EventEmitter2 = require('eventemitter2').EventEmitter2, 16 | Child = require('intercom').EventChild; 17 | 18 | var utils = require('../core/utils'); 19 | 20 | // The number to add to a self asigned Child Service name 21 | var serviceBase = 1; 22 | 23 | // eventEmitter2 constants 24 | var EVENT_OPTIONS = { 25 | delimiter: '::', 26 | wildcard: true 27 | }; 28 | 29 | /** 30 | * The Controller class definition 31 | * @class Controller 32 | * @extends EventEmitter2 33 | * 34 | * @constructor 35 | * Create Controller instance 36 | * @param {Object} Options Controller config options: 37 | */ 38 | var Controller = module.exports = function(options) { 39 | options = options || {}; 40 | this.name = options.name || 'service'; 41 | 42 | // empty serviceTypes store 43 | this.serviceTypes = {}; 44 | 45 | // emtpty running Services list 46 | this.running = {}; 47 | 48 | // Call the parent EventEmitter2 constructor 49 | EventEmitter2.call(this, options.eventOptions || EVENT_OPTIONS); 50 | 51 | // initialize external events 52 | this.initEvents(); 53 | }; 54 | inherits(Controller, EventEmitter2); 55 | 56 | 57 | /** 58 | * Stop this Controller with its child Services gracefully 59 | * @param {Function} cb Callback to call when ready; Callback will be called with an array of Error (if any) 60 | */ 61 | Controller.prototype.stop = function(cb) { 62 | var self = this, 63 | list = Object.keys(this.running); 64 | 65 | function stopService(name, next) { 66 | self.stopService(name, function(err, result) { 67 | if (err) self.log('log::' + self.name + '::' + name + '::stop::error', err); 68 | next(null, { 69 | name: name, 70 | err: err, 71 | result: result 72 | }); 73 | }) 74 | } 75 | 76 | // stop all running child Services 77 | async.map(list, stopService, function(err, results) { 78 | cb(results); 79 | }); 80 | } 81 | 82 | 83 | /** 84 | * Initialize all external events this instance will react on 85 | */ 86 | Controller.prototype.initEvents = function() { 87 | this.on('service::start', this.startService); 88 | this.on('service::stop', this.stopService); 89 | this.on('service::get', this.getService); 90 | }; 91 | 92 | 93 | /** 94 | * Log messages from this instance to the logger: override with actual log function 95 | */ 96 | Controller.prototype.log = function() { 97 | }; 98 | 99 | 100 | /** 101 | * Start a child Service process 102 | * @param {Object} options Object with the following possible configuration options: 103 | * `name` {String} The name of the Service to start 104 | * `type` {String} The Service type to start 105 | * @param {Function} cb Callback to call when the Service event API is running 106 | */ 107 | Controller.prototype.startService = function(options, cb) { 108 | var self = this, 109 | child; 110 | 111 | // set default options 112 | options = options || {}; 113 | var name = options.name = options.name || this.name + 'Service' + serviceBase++; 114 | 115 | // fork child Service and store 116 | var child = this.running[name] = this.fork(options); 117 | 118 | // bind the standard intercom Child events 119 | child.once('start', this.onStart.bind(this, name)); 120 | child.on('restart', this.onRestart.bind(this, name)); 121 | child.on('stop', this.onStop.bind(this, name)); 122 | child.on('exit', this.onExit.bind(this, name)); 123 | child.on('stdout', this.onStdout.bind(this, name)); 124 | child.on('stderr', this.onStderr.bind(this, name)); 125 | child.on('error', this.onError.bind(this, name)); 126 | child.on('warn', this.onStdout.bind(this, name)); 127 | 128 | // listen for callback info by using a random callback event of 60/6 = 10 characters 129 | var cbEvent = utils.randomString(60) + '::ready'; 130 | // actions to execute when the Service is indicating that its initialized and running 131 | child.on(cbEvent, function(data) { 132 | self.log('log::' + self.name + '::' + name + '::running', data, options); 133 | cb(); 134 | }); 135 | 136 | // actions to execute when event communication is ready 137 | child.on('rpcready', function() { 138 | var childData = child.data; 139 | self.log('log::' + self.name + '::' + name + '::rpcready', { 140 | 'name': name, 141 | 'type': options.type, 142 | 'file': childData.script, 143 | 'childData': childData 144 | }); 145 | // give the new Service instance its configuration to initialize with 146 | child.emit('child::start', options.serviceOptions || {}, cbEvent); 147 | }); 148 | 149 | // TODO: Timer to stop new child Service when it takes to long to get initialized 150 | 151 | // start the child Service 152 | child.start(); 153 | 154 | return child; 155 | } 156 | 157 | 158 | /** 159 | * Stop the given child Service 160 | * @param {String} name Name of the Service to stop. 161 | * @param {Number} timeout Time (ms) to wait for the Service to close. If not in time then cb is called with error. 162 | * @param {Function} cb Callback to call when ready with (err, self) 163 | */ 164 | Controller.prototype.stopService = function(name, timeout, cb) { 165 | var self = this, 166 | child = this.running[name], 167 | timeoutId; 168 | 169 | // argument screening 170 | cb = cb ? cb : (typeof(timeout) == 'function') ? timeout : function(){}; 171 | timeout = (typeof(timeout) == 'number') ? timeout : 5000; 172 | if (!child) return cb(new Error('System Service to stop (' + name + ') is not running!')); 173 | 174 | // if not stopped in stopTime ms call calback with error 175 | timeoutId = setTimeout( 176 | function(){ 177 | // clear timeout 178 | timeoutId = null; 179 | cb(new Error('Timeout stopping System Service :' + name), child); 180 | }, 181 | timeout 182 | ); 183 | 184 | // listen for callback info by using a random callback event of 60/6 = 10 characters 185 | var cbEvent = utils.randomString(60) + '::ready'; 186 | // actions to execute when the child Service is indicating that its ready to be stopped 187 | child.once(cbEvent, function(data) { 188 | // child Service is in a clean state. Now stop (i.e. kill) it... 189 | child.stop(); 190 | delete self.running[name]; 191 | self.log('log::' + self.name + '::' + name + '::stopped', data); 192 | if (timeoutId) { 193 | clearTimeout(timeoutId); 194 | cb(); 195 | } 196 | }); 197 | 198 | // let child Service do cleanup before stop 199 | child.emit('child::stop', cbEvent); 200 | } 201 | 202 | 203 | /** 204 | * Retrieve the child Service instance with the given name 205 | * @param {String} name Intercom.Child instance name to retrieve the Child for 206 | * @param {Function} cb Callback function if ready. `err` and `name` are given as arguments to this function 207 | * @return {Intercom.Child/undefined} If Child exists return it else null. If cb is given an undefined value is returned. 208 | */ 209 | Controller.prototype.getService = function(name, cb) { 210 | if (!this.running[name]) { 211 | return cb ? cb(new Error('Service with name:' + name + ' does not exists!!'), name) : null; 212 | } 213 | return cb ? cb(null, name) : this.running[name]; 214 | } 215 | 216 | 217 | /** 218 | * Called when the Intercom.Child object emits the 'start' event. 219 | * @param {Intercom.Child} child The Intercom.Child instance emitting the event 220 | * @param {Object} childData Object with information about the child: 221 | * `ctime` this.ctime, 222 | * `command` this.command, 223 | * `file` this.options[0], 224 | * `foreverPid` process.pid, 225 | * `logFile` this.logFile, 226 | * `options` this.options.slice(1), 227 | * `pid` this.child.pid, 228 | * `silent` this.silent, 229 | * `uid` this.uid 230 | */ 231 | Controller.prototype.onStart = function(name, child, childData) { 232 | this.log('log::' + this.name + '::' + name + '::start', childData); 233 | } 234 | 235 | 236 | /** 237 | * Called when the monitor object emits the 'restart' event. 238 | * @param {Intercom.Child} child The Intercom.Child instance emitting the event 239 | * @param {Object} childData Object with information about the child: 240 | * `ctime` this.ctime, 241 | * `command` this.command, 242 | * `file` this.options[0], 243 | * `foreverPid` process.pid, 244 | * `logFile` this.logFile, 245 | * `options` this.options.slice(1), 246 | * `pid` this.child.pid, 247 | * `silent` this.silent, 248 | * `uid` this.uid 249 | */ 250 | Controller.prototype.onRestart = function(name, child, childData) { 251 | this.log('log::' + this.name + '::' + name + '::restart', childData); 252 | } 253 | 254 | 255 | /** 256 | * Called when the Child.kill function is called 257 | * @param {Object} childData Object with information about the child: 258 | * ctime: this.ctime, 259 | * command: this.command, 260 | * file: this.options[0], 261 | * foreverPid: process.pid, 262 | * logFile: this.logFile, 263 | * options: this.options.slice(1), 264 | * pid: this.child.pid, 265 | * silent: this.silent, 266 | * uid: this.uid 267 | */ 268 | Controller.prototype.onStop = function(name, childData) { 269 | this.log('log::' + this.name + '::' + name + '::stop', childData); 270 | } 271 | 272 | 273 | /** 274 | * Called when the Intercom.Child object emits the 'exit' event. 275 | * @param {Intercom.Child} child The Intercom.Child instance emitting the event 276 | * @param {Boolean} spinning Exited within minimum required uptime (minUptime) 277 | */ 278 | Controller.prototype.onExit = function onExit(name, child, spinning) { 279 | // announce that this System Service died unexpectedly!! 280 | this.log('log::' + this.name + '::' + name + '::exit', child.pid, spinning); 281 | } 282 | 283 | 284 | /** 285 | * Called when the Intercom.Child object emits the 'stdout' event. 286 | * @param {String} data The data send with stdout 287 | */ 288 | Controller.prototype.onStdout = function(name, data) { 289 | this.log('log::' + this.name + '::' + name + '::stdout', data.toString()); 290 | } 291 | 292 | 293 | /** 294 | * Called when the Intercom.Child object emits the 'stderr' event. 295 | * @param {String} data The data send with stderr 296 | */ 297 | Controller.prototype.onStderr = function(name, data) { 298 | this.log('log::' + this.name + '::' + name + '::stderr', data.toString()); 299 | } 300 | 301 | 302 | /** 303 | * Called when the child monitor object emits the 'error' event. 304 | * @param {Error} data The error object send with error 305 | */ 306 | Controller.prototype.onError = function(name, data) { 307 | this.log('log::' + this.name + '::' + name + '::error', data.toString()); 308 | } 309 | 310 | 311 | /** 312 | * Fork a child Service for this Controller 313 | * @param {Object} options Object with the following possible configuration options: 314 | * `name` {String} The name of the child Service to start 315 | * `type` {String} The child Service type to startup 316 | * `cwd` {String} The current working directory to start the child Service in, default is current process cwd. 317 | * `uid` {String/Integer} System user uid, default is -1 (current) 318 | * `gid` {String/Integer} System user gid, if not defined the given uid is used, if thats not defined -1 (current) 319 | * @return {Child} Initialized Intercom.Child instance 320 | */ 321 | Controller.prototype.fork = function(options) { 322 | var cwd = options.cwd || process.cwd(), 323 | script = this.serviceGet(options.type), 324 | childOptions = { 325 | visible: true, 326 | max: options.max || 5, 327 | cwd: cwd, 328 | env: { 329 | HOME: cwd, 330 | PATH: path.dirname(process.execPath) + ':/usr/kerberos/sbin:/usr/kerberos/bin:/usr/lib/ccache:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:~/bin' 331 | }, 332 | options: options.options || [], 333 | spawnWith: { 334 | customFds: [-1, -1, -1], // Default is [-1, -1, -1] 335 | setsid: true, // Default is false 336 | uid: options.uid || -1, // not described option in NodeJS docs. Default is -1 337 | gid: options.gid || options.uid || -1 // not described option in NodeJS docs. Default is -1 338 | } 339 | }; 340 | 341 | // use Child to keep this Service running!! 342 | return new Child(script, childOptions); 343 | } 344 | 345 | 346 | /** 347 | * Retrieves the Service startup script location for the specified Service type 348 | * @param {string} type The type name of the service to retrieve 349 | */ 350 | Controller.prototype.serviceGet = function (type) { 351 | var type = typeof type === 'string' ? type.toLowerCase() : ''; 352 | 353 | if (!this.serviceTypes[type]) { 354 | throw new Error('Cannot return Service for unknown type ' + type); 355 | } 356 | return this.serviceTypes[type]; 357 | }; 358 | 359 | 360 | /** 361 | * Adds a new Service location to the Service list 362 | * @param {String} name Name of the Service to add 363 | * @param {String} script Startup script location of the Service instance 364 | */ 365 | Controller.prototype.serviceAdd = function (name, script) { 366 | if (this.serviceTypes[name]) { 367 | throw new Error('A Service with this name (' + name + ') already exists!'); 368 | } 369 | this.serviceTypes[name] = script; 370 | }; 371 | 372 | 373 | /** 374 | * Removes a Service startup script location from the Service list 375 | * @param {String} name Name of the Service to remove 376 | */ 377 | Controller.prototype.serviceRemove = function (name) { 378 | if (!this.serviceTypes[name]) { 379 | throw new Error('A Service type with this name (' + name + ') does not exists!'); 380 | } 381 | delete this.serviceTypes[name]; 382 | }; 383 | 384 | 385 | /** 386 | * Lists all Service types in the Service list. Returns array of names. 387 | * @returns {Array} Array of known Service types 388 | */ 389 | Controller.prototype.serviceList = function () { 390 | return Object.keys(this.serviceTypes); 391 | }; -------------------------------------------------------------------------------- /lib/core/logging.js: -------------------------------------------------------------------------------- 1 | /** 2 | * logging.js: General logging module of the Apiary System. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module logging 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var path = require('path'), 13 | inherits = require('util').inherits, 14 | inspect = require('util').inspect, 15 | EventEmitter2 = require('eventemitter2').EventEmitter2; 16 | winston = require('winston'); 17 | 18 | var apiary = require('../apiary'), 19 | randomString = require('./utils').randomString; 20 | 21 | /** 22 | * The Logging class definition 23 | * @class Logging 24 | * @extends EventEmitter2 25 | * 26 | * @constructor 27 | * Create a Logging instance 28 | */ 29 | var Logging = module.exports = function(options) { 30 | // if called as function return Instance 31 | if (!(this instanceof Logging)) return new Logging(options); 32 | var self = this; 33 | 34 | // set options to this instance 35 | this.location = options.location; 36 | this.groups = options.groups; 37 | this.channels = {} 38 | 39 | // Call the parent EventEmitter2 constructor 40 | EventEmitter2.call(this, { delimiter: '::', wildcard: true }); 41 | 42 | // start configured logging channels 43 | Object.keys(this.groups).forEach(function(group) { 44 | // hack because nconf doesn't understand arrays 45 | group = self.groups[group]; 46 | var event = group.event, 47 | filename = group.filename; 48 | 49 | // TODO check event for proper buildup!! 50 | if (!event || !filename) return 51 | 52 | // add group to the log channels 53 | self.addChannel(event, path.join(self.location, filename)); 54 | }); 55 | 56 | // send all events to stdout 57 | this.onAny(function() { 58 | console.log(this.event, arguments); 59 | }); 60 | }; 61 | inherits(Logging, EventEmitter2); 62 | 63 | 64 | /** 65 | * process the events send and put them into the log channel 66 | */ 67 | Logging.prototype.logger = function() { 68 | var event = this.event, 69 | args = Array.prototype.slice.call(arguments), 70 | channel = args.shift(), 71 | logger = args.shift(), 72 | eParts = event.split(this.delimiter), 73 | cParts = channel.split('::'); 74 | 75 | for (var i=0; i < cParts.length; i++) { 76 | // they are not the same and cParts != '**' : break 77 | if (eParts[i] && (cParts[i] !== '**') && (eParts[i] !== cParts[i])) break; 78 | // end of channel type or short event then log it 79 | if ((cParts[i] === '**') || !eParts[i]) { 80 | logger.info(event, args); 81 | break 82 | } 83 | } 84 | } 85 | 86 | 87 | /** 88 | * Stop all logging channels 89 | */ 90 | Logging.prototype.stop = function() { 91 | // close all channels 92 | var self = this 93 | Object.keys(this.channels).forEach(function(event) { 94 | self.removeChannel(event); 95 | }) 96 | }; 97 | 98 | 99 | /** 100 | * Add the log channel with the given channel name and filename 101 | * @param {String} name Name of the log channel to add 102 | * @param {String} filename Logfile to log to 103 | */ 104 | Logging.prototype.addChannel = function(name, filename) { 105 | var logger = winston.loggers.add(name, { 106 | file: { 107 | filename: filename, 108 | timestamp: true 109 | } 110 | }); 111 | 112 | // create listener function 113 | var eventFn = this.logger.bind(this, name, logger); 114 | 115 | // add listener function to get the requested events and throw them at the logger 116 | // TODO: when EventEmitter2 understands ** make the listener easier!! :-) 117 | this.onAny(eventFn) 118 | 119 | // administer the logger for later use 120 | this.channels[name] = { 121 | logger: logger, 122 | eventFn: eventFn 123 | } 124 | } 125 | 126 | 127 | /** 128 | * Remove the log channel with the given name 129 | * @param {String} name Name of the log channel to remove 130 | */ 131 | Logging.prototype.removeChannel = function(name) { 132 | var channel = this.channels[name]; 133 | if (!channel) return new Error('Can not remove, this log channel does not exist!!'); 134 | delete this.channels[name]; 135 | 136 | // remove the listener 137 | this.offAny(channel.eventFn); 138 | 139 | // close the logger 140 | winston.loggers.close(name); 141 | } 142 | 143 | 144 | /** 145 | * Get the correct log channel with the given name 146 | * @param {String} name Name of the log channel to return 147 | */ 148 | Logging.prototype.getChannel = function(name) { 149 | var channel = this.channels[name]; 150 | if (!channel) return new Error('Can not get requested channel, this log channel does not exist!!'); 151 | return channel; 152 | } -------------------------------------------------------------------------------- /lib/core/service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * service.js: Generic child service module. 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module controller 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var inherits = require('util').inherits, 13 | EventEmitter2 = require('eventemitter2').EventEmitter2; 14 | 15 | // startup intercom ipc event communications channel 16 | require('intercom'); 17 | 18 | /** 19 | * The Service class definition 20 | * @class Service 21 | * @extends EventEmitter2 22 | * 23 | * @constructor 24 | * Create Service instance 25 | * @param {Object} Options Service config options that will be applied to the created instance 26 | */ 27 | var Service = module.exports = function(options) { 28 | // if called as function return Instance 29 | if (!(this instanceof Service)) return new Service(options); 30 | var self = this; 31 | 32 | // apply all options attributes to this instance 33 | Object.keys(options).forEach(function(name){ 34 | self[name] = options[name]; 35 | }); 36 | 37 | // Function to execute when the event communication channel is ready 38 | process.parent.ready(this.ready.bind(this)); 39 | 40 | // Function to execute when the parent asks us to start 41 | process.parent.once('child::start', function(options, cbEvent) { 42 | self.start(options, function() { 43 | args = Array.prototype.slice.call(arguments); 44 | // return the result to the parent 45 | process.parent.emit.apply(process.parent, [cbEvent].concat(args)); 46 | }); 47 | }); 48 | 49 | // Function to execute when the parent asks us to stop 50 | process.parent.on('child::stop', function(cbEvent) { 51 | self.stop(function() { 52 | args = Array.prototype.slice.call(arguments); 53 | // return the result to the parent 54 | process.parent.emit.apply(process.parent, [cbEvent].concat(args)); 55 | }); 56 | }); 57 | }; 58 | inherits(Service, EventEmitter2); 59 | 60 | 61 | /** 62 | * Function that will be called when the event communication with the controller 63 | * is up and running. 64 | */ 65 | Service.prototype.ready = function() { 66 | } 67 | 68 | 69 | /** 70 | * Function that will be called when the 'child::start' event is received from the 71 | * parent controller. 72 | * @param {Object} options Service configuraion options send by the parent controller 73 | * @param {Function} cb Callback function to call. 74 | */ 75 | Service.prototype.start = function(options, cb) { 76 | cb('No start function defined!!'); 77 | } 78 | 79 | 80 | /** 81 | * Function that will be called when the 'child::stop' event is received from the 82 | * parent controller. 83 | * @param {Function} cb Callback function to call. 84 | */ 85 | Service.prototype.stop = function(cb) { 86 | cb('No stop function defined!!'); 87 | } -------------------------------------------------------------------------------- /lib/core/utils/clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * clone.js: Real, deep copied objects utility function 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * Copyright 2011 Marco Rogers 8 | * https://gist.github.com/1239728/2a25dbebb7c96098823596d317c5283b13098f66 9 | * Website/Blog: http://marcorogers.com/ 10 | * Company: Yammer Inc 11 | * Location: San Francisco, CA 12 | */ 13 | 14 | /** 15 | * Real, deep copied objects utility function 16 | * @param {Object/Array} obj Object to clone 17 | * @returns {Object/Array} Cloned object 18 | */ 19 | module.exports = function clone(obj) { 20 | if (Array.isArray(obj)) { 21 | return [].slice.call(obj, 0); 22 | } 23 | 24 | // Create a new object whose prototype is a new, empty object, 25 | // Using the second propertiesObject argument to copy the source properties 26 | return Object.create({}, (function(src) { 27 | var props = Object.getOwnPropertyNames(src), 28 | dest = {}; 29 | 30 | props.forEach(function(name) { 31 | var descriptor = Object.getOwnPropertyDescriptor(src, name), 32 | tmp; 33 | 34 | // Recurse on properties whose value is an object or array 35 | if (typeof src[name] === "object" ) { 36 | descriptor.value = clone(src[name]); 37 | } 38 | dest[name] = descriptor; 39 | }); 40 | 41 | return dest; 42 | })(obj)); 43 | } 44 | 45 | /** 46 | * ES5 Object.prototype.hasOwnProperty implementation 47 | */ 48 | function hasOwnProperty(key) { 49 | if(this[key]) { 50 | var proto = this.prototype; 51 | if(proto) { 52 | return ((key in this.prototype) && (this[key] === this.prototype[key])); 53 | } 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } 59 | if(!Object.prototype.hasOwnProperty) { Object.prototype.hasOwnProperty = hasOwnProperty; } 60 | 61 | /** 62 | * ES5 Object.create implementation 63 | */ 64 | function create(o) { 65 | if (arguments.length > 1) { 66 | throw new Error('Object.create implementation only accepts the first parameter.'); 67 | } 68 | function F() {} 69 | F.prototype = o; 70 | return new F(); 71 | } 72 | if(!Object.create) { Object.create = create; } 73 | 74 | /** 75 | * ES5 Array.isArray implementation 76 | */ 77 | function isArray(obj) { 78 | return Object.prototype.toString.call(obj) === '[object Array]'; 79 | } 80 | if(!Array.isArray) { Array.isArray = isArray; } -------------------------------------------------------------------------------- /lib/core/utils/directories.js: -------------------------------------------------------------------------------- 1 | /** 2 | * directories.js: General directory functions 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var fs = require('fs'), 9 | async = require('async'), 10 | mkdirp = require('mkdirp'), 11 | rimraf = require('rimraf'); 12 | 13 | /** 14 | * Creates all of the specified `directories`. 15 | * @param {Array} directories List of directorie paths to initialize 16 | * @param {Object} options Directory options to set like mode, uid and gid 17 | * @param {function} callback Continuation to respond to when complete 18 | */ 19 | exports.create = function(directories, options, callback) { 20 | function createDir(dir, next) { 21 | mkdirp(dir, options.mode || 0755, function (err) { 22 | if(!err && options.uid) fs.chownSync(dir, options.uid, options.gid || options.uid); 23 | next(); 24 | }); 25 | } 26 | 27 | async.forEachSeries(directories, createDir, function() { 28 | callback(null, directories); 29 | }); 30 | }; 31 | 32 | /** 33 | * Remove all of the specified `directories` including child directories and files!!!. 34 | * @param {Array} directories Directories to remove 35 | * @param {function} callback Continuation to respond to when complete 36 | */ 37 | exports.remove = function (directories, callback) { 38 | function removeDir(dir, next) { 39 | rimraf(dir, function() { 40 | next(); 41 | }); 42 | } 43 | 44 | async.forEachSeries(directories, removeDir, function() { 45 | callback(null, directories); 46 | }); 47 | }; -------------------------------------------------------------------------------- /lib/core/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * utils.js: Global General functions for Apiary 3 | * 4 | * (C) 2011, TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | exports.clone = require('./clone'); 9 | exports.directories = require('./directories'); 10 | exports.randomString = require('./randomstring'); -------------------------------------------------------------------------------- /lib/core/utils/randomstring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * randomstring.js: pseude-random ASCII string creation utility function 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * (C) 2010 Nodejitsu Inc. 8 | * MIT LICENCE 9 | */ 10 | 11 | /** 12 | * Returns a pseude-random ASCII string which contains at least 13 | * the specified number of bits of entropy the return value is a string of 14 | * length `bits/6` of characters from the base64 alphabet. 15 | * @param {Number} bits Bit-length of the base64 string to return. 16 | */ 17 | module.exports = function randomString(bits) { 18 | var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$', 19 | rand, i, ret = ''; 20 | 21 | // in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53) 22 | while (bits > 0) { 23 | rand = Math.floor(Math.random() * 0x100000000); // 32-bit integer 24 | // base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters. 25 | for (i = 26; i > 0 && bits > 0; i -= 6, bits -= 6) { 26 | ret += chars[0x3F & rand >>> i]; 27 | } 28 | } 29 | 30 | return ret; 31 | }; -------------------------------------------------------------------------------- /lib/sc/apis/cliserver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * server.js: API Server for the Apiary CLI. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @author TTC/Sander Tolsma 8 | * @docauthor TTC/Sander Tolsma 9 | */ 10 | 11 | var dnode = require('dnode'); 12 | 13 | // constants 14 | var noop = function(){}; 15 | 16 | 17 | /** 18 | * The Cli Server class definition 19 | * @class CliServer 20 | * 21 | * @constructor 22 | * Create CLI Server class 23 | * @param {Object} options (optional) CliServer config options: 24 | */ 25 | var CliServer = module.exports = function(apie, options) { 26 | // if called as function return Instance 27 | if (!(this instanceof CliServer)) return new CliServer(apie, options); 28 | 29 | // save apie event comms instance 30 | // this.apie = apie; 31 | this.apis = require('../../apiary'); 32 | 33 | // set default options 34 | options = options || {}; 35 | this.socket = options.cliSocket || null; 36 | } 37 | 38 | /** 39 | * Attempts to spawn the CLI server for this instance. 40 | * @param {String} socket (optional) Socket to connect to 41 | * @param {Function} cb Continuation function to respond to when complete 42 | * @return {CliServer} This cliServer instance, makes linking possible 43 | */ 44 | CliServer.prototype.listen = function (socket, cb) { 45 | if (typeof(socket) == 'function') { 46 | cb = socket; 47 | socket = undefined; 48 | } 49 | cb = cb || noop; 50 | 51 | var self = this; 52 | this.listening = true; 53 | 54 | this.server = dnode(function(client, conn) { 55 | self.apis.emit('child::cliserver::connected'); 56 | 57 | // start building the API 58 | this.getServerPid = self.getServerPid.bind(self); 59 | this.appStart = self.appStart.bind(self); 60 | this.appStop = self.appStop.bind(self); 61 | this.appUpdate = self.appUpdate.bind(self); 62 | this.appClean = self.appClean.bind(self); 63 | this.appList = self.appList.bind(self); 64 | this.userAdd = self.userAdd.bind(self); 65 | this.userRemove = self.userRemove.bind(self); 66 | this.userList = self.userList.bind(self); 67 | this.userClean = self.userClean.bind(self); 68 | }); 69 | 70 | this.server.on('connection', function(conn) { 71 | self.apis.emit('child::cliserver::open'); 72 | }); 73 | 74 | this.server.on('ready', function() { 75 | cb(); 76 | }); 77 | 78 | try { 79 | this.server.listen(socket || this.socket); 80 | } 81 | catch (ex) { 82 | cb(ex); 83 | } 84 | 85 | // make continuation possible 86 | return this; 87 | } 88 | 89 | /** 90 | * (API) Return the running pid to the given callback function 91 | * @param {Function} cb Continuation function to respond to when complete 92 | */ 93 | CliServer.prototype.getServerPid = function(cb) { 94 | cb(process.pid); 95 | } 96 | 97 | /** 98 | * (API) Start an application on this Apiary System 99 | * @param {String} user User to start the given application for 100 | * @param {Object} app Application description 101 | * @param {Function} cb (optional) Continuation function to respond to when complete 102 | */ 103 | CliServer.prototype.appStart = function(user, app, cb) { 104 | var self = this; 105 | cb = cb || noop; 106 | 107 | // and start the app in the given user SRE 108 | this.apis.emit('sre::' + user + '::app::start', app, function(err, result) { 109 | if (!err) { 110 | // add app to config file 111 | self.apis.config.set('sre:' + user + ':apps:' + app.name, app); 112 | self.apis.config.save(); 113 | } 114 | cb(err, result); 115 | }); 116 | } 117 | 118 | /** 119 | * (API) Stops an application on this Apiary System 120 | * @param {String} user User to stop the given application for 121 | * @param {Object} app Application description 122 | * @param {Function} cb (optional) Continuation function to respond to when complete 123 | */ 124 | CliServer.prototype.appStop = function(user, app, cb) { 125 | var self = this; 126 | cb = cb || noop; 127 | 128 | this.apis.emit('sre::' + user + '::app::stop', app, function(err, result) { 129 | if (!err) { 130 | // add app to config file 131 | self.apis.config.clear('sre:' + user + ':apps:' + app.name); 132 | self.apis.config.save(); 133 | } 134 | cb(err, result); 135 | }); 136 | } 137 | 138 | /** 139 | * (API) Updates an application on this Apiary System 140 | * @param {String} user User to update the given application for 141 | * @param {Object} app Application description 142 | * @param {Function} cb (optional) Continuation function to respond to when complete 143 | */ 144 | CliServer.prototype.appUpdate = function(user, app, cb) { 145 | var self = this; 146 | cb = cb || noop; 147 | 148 | this.apis.emit('sre::' + user + '::app::update', app, function(err, result) { 149 | if (!err) { 150 | // add app to config file 151 | self.apis.config.set('sre:' + user + ':apps:' + app.name, app); 152 | self.apis.config.save(); 153 | } 154 | cb(err, result); 155 | }); 156 | } 157 | 158 | /** 159 | * (API) Stops and removes (an) application(s) from this Apiary System 160 | * @param {String} user User to clean the given application for 161 | * @param {Object} app Application description 162 | * @param {Function} cb (optional) Continuation function to respond to when complete 163 | */ 164 | CliServer.prototype.appClean = function(user, app, cb) { 165 | var self = this; 166 | cb = cb || noop; 167 | 168 | this.apis.emit('sre::' + user + '::app::clean', app, function(err, result) { 169 | // add app to config file 170 | self.apis.config.clear('sre:' + user + ':apps:' + app.name); 171 | self.apis.config.save(); 172 | cb(err, result); 173 | }); 174 | } 175 | 176 | /** 177 | * (API) Lists all apps running on this Apiary System or running for given user 178 | * @param {String} user (optional) User to list the applications for or `all` for all users 179 | * @param {Function} cb (optional) Continuation function to respond to when complete 180 | */ 181 | CliServer.prototype.appList = function(user, cb) { 182 | if (!cb && typeof(user) == 'function') { 183 | cb = user; 184 | user = undefined; 185 | } 186 | cb = cb || noop; 187 | 188 | this.apis.emit('sre::' + user + '::app::list', user, function(err, result) { 189 | cb(err, result); 190 | }); 191 | } 192 | 193 | 194 | /** 195 | * (API) Add user to the running Apiary System 196 | * @param {String} user User to add 197 | * @param {Function} cb (optional) Continuation function to respond to when complete 198 | */ 199 | CliServer.prototype.userAdd = function(user, cb) { 200 | var self = this; 201 | cb = cb || noop; 202 | 203 | this.apis.emit('sre::service::get', user, function(err, name) { 204 | if (!err) return cb(new Error('Controller for user ' + user + ' is already running!'), user); 205 | 206 | // TODO: change user relation to system user, this can be different!!! 207 | var options = { 208 | name: user, 209 | user: user, 210 | directory: 'data', 211 | cwd: '/home/' + user, 212 | uid: user, 213 | gid: user 214 | }; 215 | 216 | // add user to config file 217 | self.apis.config.set('sre:' + user, options); 218 | self.apis.config.save(); 219 | // and start the corresponding SREC 220 | self.apis.emit('sre::service::start', options, function(err, result) { 221 | cb(err, result); 222 | }); 223 | }); 224 | } 225 | 226 | /** 227 | * (API) Remove user from this running Apiary System 228 | * @param {String} user User to remove 229 | * @param {Function} cb (optional) Continuation function to respond to when complete 230 | */ 231 | CliServer.prototype.userRemove = function(user, cb) { 232 | var self = this; 233 | cb = cb || noop; 234 | 235 | this.apis.emit('sre::service::stop', user, 5000, function(err, sre) { 236 | // remove user from config file 237 | self.apis.config.clear('sre:' + user); 238 | self.apis.config.save(); 239 | cb(err, user); 240 | }); 241 | } 242 | 243 | /** 244 | * (API) Lists all users on this running Apiary System 245 | * @param {Function} cb (optional) Continuation function to respond to when complete 246 | */ 247 | CliServer.prototype.userList = function(cb) { 248 | cb = cb || noop; 249 | 250 | cb(null, this.apis.config.get('sre')); 251 | } 252 | 253 | /** 254 | * (API) Stops apps and removes a user from this Apiary System 255 | * @param {String} user User to clean 256 | * @param {Function} cb (optional) Continuation function to respond to when complete 257 | */ 258 | CliServer.prototype.userClean = function(user, cb) { 259 | cb = cb || noop; 260 | 261 | cb(new Error('userClean not yet implemented in Apiary API!')); 262 | } -------------------------------------------------------------------------------- /lib/sc/apis/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A System Controller API Service, as used by the API Environment. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @author TTC/Sander Tolsma 8 | * @docauthor TTC/Sander Tolsma 9 | */ 10 | 11 | var inspect = require('util').inspect, 12 | Parent = require('intercom').Parent; 13 | 14 | var CliServer = require('./cliserver'); 15 | 16 | // startup SC API Service process with comms event channel to the API Environment parent 17 | var parent = Parent(function(){ 18 | 19 | // only start Servers when event comms channel is ready and with startup data from the apie parent!! 20 | parent.once('apie::start', function(data) { 21 | // start the cliServer for the Apiary administrators 22 | this.cliServer = CliServer(parent, data); 23 | this.cliServer.listen(); 24 | 25 | // 26 | // TODO: When all servers are up and running change the process owner to the given uid/gid 27 | // 28 | 29 | // indicate to the parent that we are running!! 30 | parent.emit('child::running'); 31 | }); 32 | 33 | // called when the API Environment controller wants to stop this API Service 34 | parent.once('apie::stop', function() { 35 | // tell the parent I'm in a save state 36 | parent.emit('child::stopready'); 37 | }); 38 | 39 | }); -------------------------------------------------------------------------------- /lib/sc/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An Apiary System Controller definition. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module sc 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var path = require('path'), 13 | inherits = require('util').inherits, 14 | async = require('async'), 15 | EventEmitter2 = require('eventemitter2').EventEmitter2; 16 | 17 | var randomString = require('../core/utils').randomString; 18 | 19 | /** 20 | * The System Controller class definition 21 | * @class Sc 22 | * @extends EventEmitter2 23 | * 24 | * @constructor 25 | * Create a System Controler instance 26 | */ 27 | var Sc = module.exports = function() { 28 | // if called as function return Instance 29 | if (!(this instanceof Sc)) return new Sc(); 30 | 31 | // Call the parent EventEmitter2 constructor 32 | EventEmitter2.call(this, { delimiter: '::', wildcard: true }); 33 | 34 | // Empty active subsystems list 35 | this.subsystems = {}; 36 | 37 | // And activate the message dispatcher to/from the subsystems 38 | this.onAny(this.dispatch); 39 | }; 40 | inherits(Sc, EventEmitter2); 41 | 42 | 43 | /** 44 | * Start this System Controller by starting all subsystems, srec's and apps 45 | * @param {Function} cb Callback to call when ready; Callback will be called with an array of Errors (if any) 46 | */ 47 | Sc.prototype.start = function(cb) { 48 | var self = this; 49 | 50 | function startSrec(user, next) { 51 | 52 | function startApp(app, next) { 53 | var appOpt = options.apps[app]; 54 | appOpt.name = app; 55 | self.emit('sre::' + user + '::app::start', appOpt, next); 56 | } 57 | 58 | function startApps(err, result) { 59 | if (options.apps) { 60 | async.forEach(Object.keys(options.apps), startApp, next); 61 | } else 62 | next(err, result); 63 | } 64 | 65 | var options = self.config.get('sre:' + user); 66 | options.user = user; 67 | options.name = options.name || user; 68 | options.directory = options.directory || 'data'; 69 | options.cwd = options.cwd || '/home/' + user; 70 | options.uid = options.uid || user; 71 | options.gid = options.gid || user 72 | 73 | self.emit('sre::service::start', options, startApps); 74 | } 75 | 76 | // Add/Startup required subsystems 77 | this.startSse(); 78 | this.startSre(); 79 | 80 | // start for the given users a SREC with the defined apps 81 | async.forEach(Object.keys(this.config.get('sre')), startSrec, cb); 82 | } 83 | 84 | 85 | /** 86 | * Stop this System Controller gracefully 87 | * @param {Function} cb Callback to call when ready; Callback will be called with an array of Error (if any) 88 | */ 89 | Sc.prototype.stop = function(cb) { 90 | var self = this; 91 | 92 | // 93 | // TODO: FIX error conditions and save shutdown if sse is not there etc!!! 94 | // 95 | 96 | if (self.subsystems.sse) { 97 | self.subsystems.sse.stop(function() { 98 | if (self.subsystems.sre) { 99 | self.subsystems.sre.stop(function(err) { 100 | cb(err); 101 | }); 102 | } 103 | }); 104 | } 105 | } 106 | 107 | 108 | /** 109 | * Event dispatch function. Will send directed events to the designated subsystems 110 | * @param {String/Array} event Event to dispatch to the correct subsystem 111 | * @param {Array} args Arguments to dispatch with the event to the correct subsystem 112 | */ 113 | Sc.prototype.dispatch = function() { 114 | var event = this.event, 115 | args = Array.prototype.slice.call(arguments), 116 | parts = event.split(this.delimiter), 117 | first = parts.shift(), 118 | subsystems = Object.keys(this.subsystems); 119 | 120 | // Send all global events to the logger to be logged if needed 121 | this.logger.emit.apply(this.logger, ['event::sc::' + event].concat(args)); 122 | 123 | // send to correct subsystem 124 | if (subsystems.indexOf(first) > -1) { 125 | var subsystem = this.subsystems[first]; 126 | subsystem.emit.apply(subsystem, [parts.join(subsystem.delimiter)].concat(args)); 127 | }; 128 | 129 | } 130 | 131 | /** 132 | * Start the System Service Environment subsystem for this Apiary System 133 | */ 134 | Sc.prototype.startSse = function() { 135 | // create System Service Environment 136 | this.subsystems.sse = this.Sse(this, { 137 | uid: undefined, 138 | gid: undefined 139 | }); 140 | 141 | // Temporaray !!!! Start an httpproxy System Service 142 | // TODO: look into config file for System Services to start!!! 143 | this.emit('sse::service::start', { 144 | name : 'httpproxy-80', 145 | type : 'httpproxy', 146 | uid : -1, 147 | gid : -1, 148 | serviceOptions : { 149 | port : 80, 150 | address : '::', 151 | destPort : 8000, 152 | destAddress : '127.0.0.1' 153 | } 154 | }, function(err, result) { 155 | // httpproxy startup callback 156 | }); 157 | } 158 | 159 | 160 | /** 161 | * Start the Service Resource Environment subsystem for this Apiary System 162 | */ 163 | Sc.prototype.startSre = function() { 164 | // create System Resource Environment 165 | this.subsystems.sre = this.Sre(this); 166 | } 167 | 168 | 169 | /** 170 | * Start the API Environment subsystem for this Apiary System 171 | */ 172 | Sc.prototype.startApie = function() { 173 | // create API Environment 174 | this.subsystems.apie = this.Apie(this); 175 | } -------------------------------------------------------------------------------- /lib/sre/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Service Runtime Environment subsystem definition as used by the System Controller. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module sre 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var path = require('path'), 13 | inherits = require('util').inherits, 14 | async = require('async'); 15 | 16 | var apiary = require('../apiary'), 17 | utils = require('../core/utils'); 18 | 19 | /** 20 | * The Service Runtime Environment class as used by the System Controller 21 | * @class Sre 22 | * @extends Controller 23 | * 24 | * @constructor 25 | * Create Service Runtime Environment subsystem instance 26 | * @param {Object} Options SRE config options: 27 | * `EVENT_OPTIONS` {Object} EventEmitter2 options. If not defined `delimiter: '::'` and `wildcard: true` will be used 28 | * `haibuport` {Integer} Haibu hook start portnumber range. Will be incremented for each new SREC that will be started 29 | */ 30 | var Sre = module.exports = function(options) { 31 | // if called as function return Instance 32 | if (!(this instanceof Sre)) return new Sre(options); 33 | 34 | options = options || {}; 35 | this.defHaibuPort = options.haibuPort || apiary.config.get('system:haibuPort'); 36 | 37 | // Call the parent constructor 38 | Sre.super_.call(this, options); 39 | 40 | // fill serviceTypes store with known sse services 41 | this.serviceAdd('srec', path.join(__dirname, './srec/srec.js')); 42 | }; 43 | inherits(Sre, apiary.Controller); 44 | 45 | 46 | /** 47 | * Initialize all external events this instance will react on 48 | */ 49 | Sre.prototype.initEvents = function() { 50 | // Call the parent initEvents function 51 | Sre.super_.prototype.initEvents.call(this); 52 | 53 | // apps events for the different SREC childs 54 | this.on('*::app::*', this.relayAppEvents); 55 | } 56 | 57 | 58 | /** 59 | * Start a child Sre controller process 60 | * @param {Object} options Object with the following possible configuration options: 61 | * `name` {String) The unique name of this SREC instance 62 | * `directory` {String) The relative (to the user dir) directory for Haibu to store its files 63 | * `cwd` {String} The current working directory to start the SREC in, default is current process cwd. 64 | * `uid` {String/Integer} System user uid, default is -1 (current) 65 | * `gid` {String/Integer} System user gid, if not defined the given uid is used, if thats not defined -1 (current) 66 | * `haibuport` {Integer} Unique Haibu (app)client-server communications port to listen on 67 | * @param {Function} cb Callback to call when new SREC is initialized and running 68 | */ 69 | Sre.prototype.startService = function(options, cb) { 70 | options = options || {}; 71 | options.type = 'srec'; 72 | options.serviceOptions = { 73 | 'name': options.name, 74 | 'env': 'development', 75 | 'haibu-hook-port': options.haibuPort || this.defHaibuPort++, 76 | 'directory': options.directory 77 | }; 78 | 79 | 80 | // start the SREC with the given options and callback 81 | var child = Sre.super_.prototype.startService.call(this, options, cb); 82 | 83 | // 84 | // TODO: Temporary message passing, needs to be replaced by other 85 | // mechanism. At this moment only one, two and three deep events are bridged 86 | // 87 | function msgBridge() { 88 | var parts = this.event.split(this.delimiter), 89 | args = Array.prototype.slice.call(arguments); 90 | 91 | // cut child and replace with child name 92 | // parts.splice(0, 1, name); 93 | parts.unshift(options.name); 94 | parts.join(this.delimiter); 95 | 96 | // send message through to System Controller 97 | apiary.emit.apply(apiary, [parts.join(this.delimiter)].concat(args)); 98 | } 99 | child.on('haibu-io::*', msgBridge); 100 | child.on('haibu-io::*::*', msgBridge); 101 | child.on('haibu-io::*::*::*', msgBridge); 102 | child.on('haibu-ev::*', msgBridge); 103 | child.on('haibu-ev::*::*', msgBridge); 104 | child.on('haibu-ev::*::*::*', msgBridge); 105 | child.on('haibu-ev::*::*::*::*', msgBridge); 106 | child.on('haibu-ev::*::*::*::*::*', msgBridge); 107 | 108 | return child; 109 | } 110 | 111 | 112 | /** 113 | * Log messages from this instance to the logger 114 | */ 115 | Sre.prototype.log = function() { 116 | apiary.emit.apply(apiary, arguments); 117 | }; 118 | 119 | 120 | /** 121 | * relay the App events to the correct SREC 122 | * @param {String} user User to retrieve the SREC Child for 123 | * @param {Function} cb Callback function if ready. `err` and `srec` are given as arguments to this function 124 | */ 125 | Sre.prototype.relayAppEvents = function(app, cb) { 126 | // TODO: if user is null then send message to all SREC childs and the results are returned in a object with user named objects 127 | var self = this, 128 | parts = this.event.split(this.delimiter), 129 | user = parts.shift(); 130 | 131 | function runEvent(user, cb) { 132 | var srec = self.getService(user); 133 | if (!srec) { 134 | return cb(new Error('Service Runtime Environment Controller (SREC) for user:' + user + ' does not exists!!'), user); 135 | } 136 | 137 | // listen for callback info by using a random callback event of 60/6 = 10 characters 138 | var rEvent = utils.randomString(60) + '::ready'; 139 | srec.once(rEvent, function() { 140 | cb.apply(null, arguments); 141 | }); 142 | 143 | srec.emit(parts.join(self.delimiter), app, rEvent); 144 | } 145 | 146 | if (user === 'all') { 147 | function getEventData(user, next) { 148 | runEvent(user, function(err) { 149 | ret = Array.prototype.slice.call(arguments); 150 | ret.shift(); 151 | next(err, ret); 152 | }); 153 | } 154 | 155 | // call all running srec 156 | var srecs = Object.keys(this.running); 157 | async.map(srecs, getEventData, function(err, results) { 158 | var result = { 159 | srecs: srecs, 160 | results: results 161 | } 162 | cb.call(null, err, result); 163 | }); 164 | } else { 165 | // call one running srec to sent the event 166 | runEvent(user, function() { 167 | cb.apply(null, arguments); 168 | }); 169 | } 170 | } -------------------------------------------------------------------------------- /lib/sre/srec/api/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Service Runtime Environment API, a haibu-carapace plugin! 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | /** 9 | * @class Sre 10 | * @param {EventEmitter2} ee The EventEmitter to add the App API events to. 11 | */ 12 | exports.start = function startAppApi(ee, droneApi) { 13 | /** 14 | * @event app::start 15 | * Fired to start an App in this SRE. 16 | * @param {Object} config Config object with 'app' object and 'ready' function 17 | */ 18 | ee.on('app::start', onAppStart.bind(ee, droneApi)); 19 | 20 | /** 21 | * @event app::stop 22 | * Fired to stop an App in this SRE. 23 | * @param {Object} config Config object with 'app' object and 'ready' function 24 | */ 25 | ee.on('app::stop', onAppStop.bind(ee, droneApi)); 26 | 27 | /** 28 | * @event app::restart 29 | * Fired to restart an App in this SRE. 30 | * @param {Object} config Config object with 'app' object and 'ready' function 31 | */ 32 | ee.on('app::restart', onAppRestart.bind(ee, droneApi)); 33 | 34 | /** 35 | * @event app::clean 36 | * Fired to clean an App in this SRE. 37 | * @param {Object} config Config object with 'app' object and 'ready' function 38 | */ 39 | ee.on('app::clean', onAppClean.bind(ee, droneApi)); 40 | 41 | /** 42 | * @event app::update 43 | * Fired to update an App in this SRE. 44 | * @param {Object} config Config object with 'app' object and 'ready' function 45 | */ 46 | ee.on('app::update', onAppUpdate.bind(ee, droneApi)); 47 | 48 | /** 49 | * @event app::show 50 | * Fired to return information about the requetsed App in this SRE. 51 | * @param {Object} config Config object with 'app' object and 'ready' function that is called with the app info as argument. 52 | */ 53 | ee.on('app::show', onAppShow.bind(ee, droneApi)); 54 | 55 | /** 56 | * @event app::list 57 | * Fired to return information about all known Apps in this SRE. 58 | * @param {Object} config Config object with 'app' object and 'ready' function that is called with the app list info as argument. 59 | */ 60 | ee.on('app::list', onAppList.bind(ee, droneApi)); 61 | } 62 | 63 | /** 64 | * Will be called when the App API needs to be stopped 65 | * @param {EventEmitter2} ee The EventEmitter to remove the App API events from. 66 | */ 67 | exports.stop = function stopAppApi(ee) { 68 | ee.removeAllListeners('app::start'); 69 | ee.removeAllListeners('app::stop'); 70 | ee.removeAllListeners('app::restart'); 71 | ee.removeAllListeners('app::clean'); 72 | ee.removeAllListeners('app::update'); 73 | ee.removeAllListeners('app::show'); 74 | ee.removeAllListeners('app::list'); 75 | } 76 | 77 | /** 78 | * React on the `app::start` event by starting an app 79 | */ 80 | function onAppStart(droneApi, app, rEvent) { 81 | var self = this; 82 | 83 | // do try.. catch because of throw in repository.create 84 | try { 85 | droneApi.start(app, function (err, result) { 86 | self.emit(rEvent, err, result); 87 | }); 88 | } catch (err) { 89 | data.ready(err); 90 | } 91 | } 92 | 93 | /** 94 | * Stop App 95 | */ 96 | function onAppStop(droneApi, app, rEvent) { 97 | var self = this; 98 | droneApi.stop(app.name, function (err, result) { 99 | self.emit(rEvent, err, result); 100 | }); 101 | } 102 | 103 | /** 104 | * Restart App 105 | */ 106 | function onAppRestart(droneApi, app, rEvent) { 107 | var self = this; 108 | droneApi.restart(app.name, function (err, result) { 109 | self.emit(rEvent, err, result); 110 | }); 111 | } 112 | 113 | /** 114 | * Clean App 115 | */ 116 | function onAppClean(droneApi, app, rEvent) { 117 | var self = this; 118 | droneApi.clean(app, function (err, result) { 119 | self.emit(rEvent, err, result); 120 | }); 121 | } 122 | 123 | /** 124 | * Update App 125 | */ 126 | function onAppUpdate(droneApi, app, rEvent) { 127 | var self = this; 128 | droneApi.update(app, function (err, result) { 129 | self.emit(rEvent, err, result); 130 | }); 131 | } 132 | 133 | /** 134 | * Show App information 135 | */ 136 | function onAppShow(droneApi, app, rEvent) { 137 | this.emit(rEvent, null, droneApi.show(app.name)); 138 | } 139 | 140 | /** 141 | * React on the `app::list` event by calling data.ready with information about all running apps 142 | */ 143 | function onAppList(droneApi, app, rEvent) { 144 | this.emit(rEvent, null, droneApi.list()); 145 | } -------------------------------------------------------------------------------- /lib/sre/srec/haibufix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * haibufix.js, realtime changes to haibu! 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var fs = require('fs'), 9 | path = require('path'), 10 | forever = require('forever'); 11 | 12 | module.exports = function(haibu) { 13 | 14 | // Donkey punch Drone.prototype.clean because wrong app.user check 15 | // until all required patches for removing user notion in haibu, are pushed by Nodejitsu 16 | haibu.drone.Drone.prototype.clean = function (app, callback) { 17 | if (typeof(app.user) == 'undefined' || typeof(app.name) == 'undefined') { 18 | return callback(new Error('Both `user` and `name` are required.')); 19 | } 20 | 21 | var appsDir = haibu.config.get('directories:apps'); 22 | 23 | this.stop(app.name, function (err, result) { 24 | // 25 | // Ignore errors and continue cleaning 26 | // 27 | haibu.utils.rmApp(appsDir, app, callback); 28 | }); 29 | }; 30 | 31 | // Donkey punch Spawner.prototype.spawn to add arguments support to haibu 32 | // until all required patches for arguments support in haibu are pushed by Nodejitsu 33 | // 34 | // ### function spawn (app, callback) 35 | // #### @repo {repository.Repository} App repository to attempt to spawn on this server. 36 | // #### @callback {function} Continuation passed to respond to. 37 | // spawns the appropriate carapace for an Application and bootstraps with the events listed 38 | // 39 | haibu.Spawner.prototype.spawn = function spawn (repo, callback) { 40 | if (!(repo instanceof haibu.repository.Repository)) throw (new Error('repo is not an instance of repository.Repository!')); 41 | 42 | var self = this, 43 | command = path.join(require.resolve('haibu-carapace'), '..', '..', 'bin', 'carapace'), 44 | app = repo.app, 45 | meta = { app: app.name }, 46 | script = repo.startScript, 47 | scriptArgs = (app.scripts && app.scripts.arguments) ? app.scripts.arguments : [], 48 | responded = false, 49 | stderr = [], 50 | foreverOptions, 51 | error, 52 | drone; 53 | 54 | haibu.emit('spawn:setup', 'info', meta); 55 | 56 | foreverOptions = { 57 | silent: true, 58 | cwd: repo.homeDir, 59 | hideEnv: haibu.config.get('hideEnv'), 60 | env: app.env, 61 | minUptime: this.minUptime, 62 | options: [] 63 | }; 64 | 65 | // 66 | // Concatenate the `argv` of any plugins onto the options 67 | // to be passed to the carapace script. 68 | // 69 | Object.keys(haibu.activePlugins).forEach(function (plugin) { 70 | var spawn; 71 | 72 | if (haibu.activePlugins[plugin].argv) { 73 | haibu.emit('plugin:argv', 'info', { 74 | app: app.name, 75 | user: app.user, 76 | plugin: plugin 77 | }); 78 | 79 | spawn = haibu.activePlugins[plugin].argv(repo); 80 | 81 | if (spawn.script) { 82 | script = spawn.script; 83 | } 84 | 85 | if (spawn.scriptArgs) { 86 | scriptArgs = spawn.scriptArgs; 87 | } 88 | 89 | if (spawn.argv) { 90 | foreverOptions.options = foreverOptions.options.concat(spawn.argv); 91 | } 92 | } 93 | }); 94 | 95 | foreverOptions.forever = typeof self.maxRestart === 'undefined'; 96 | if (typeof self.maxRestart !== 'undefined') { 97 | foreverOptions.max = self.maxRestart; 98 | } 99 | 100 | // 101 | // Before we attempt to spawn, let's check if the startPath actually points to a file 102 | // Trapping this specific error is useful as the error indicates an incorrect 103 | // scripts.start property in the package.json 104 | // 105 | fs.stat(repo.startScript, function (err, stats) { 106 | if (err) { 107 | return callback(new Error('package.json error: ' + 'can\'t find starting script: ' + repo.app.scripts.start)); 108 | } 109 | 110 | haibu.emit('spawn:start', 'info', { 111 | options: foreverOptions.options.join(' '), 112 | script: script, 113 | arguments: scriptArgs, 114 | app: meta.app, 115 | user: meta.user 116 | }); 117 | 118 | // create command line 119 | var cmdline = [command].concat(foreverOptions.options).concat(script); 120 | cmdline = (scriptArgs) ? cmdline.concat(scriptArgs) : cmdline; 121 | 122 | drone = new forever.Monitor(cmdline, foreverOptions); 123 | 124 | drone.on('error', function() { 125 | // 126 | // 'error' event needs to be caught, otherwise 127 | // the haibu process will die 128 | // 129 | }); 130 | 131 | // 132 | // Log data from `drone.stdout` to haibu 133 | // 134 | function onStdout (data) { 135 | haibu.emit('drone:stdout', 'info', data.toString(), meta); 136 | } 137 | 138 | // 139 | // Log data from `drone.stderr` to haibu 140 | // 141 | function onStderr (data) { 142 | data = data.toString() 143 | haibu.emit('drone:stderr', 'error', data, meta); 144 | 145 | if (!responded) { 146 | stderr = stderr.concat(data.split('\n').filter(function (line) { return line.length > 0 })); 147 | } 148 | } 149 | 150 | // 151 | // If the `forever.Monitor` instance emits an error then 152 | // pass this error back up to the callback. 153 | // 154 | function onError (err) { 155 | if (!responded) { 156 | errState = true; 157 | responded = true; 158 | callback(err); 159 | 160 | // 161 | // Remove listeners to related events. 162 | // 163 | drone.removeListener('exit', onExit); 164 | haibu.running.hook.removeListener('*::carapace::port', onCarapacePort); 165 | } 166 | } 167 | 168 | // 169 | // When the carapace provides the port that the drone 170 | // has bound to then respond to the callback 171 | // 172 | // Remark: What about `"worker"` processes that never 173 | // start and HTTP server? 174 | // 175 | function onCarapacePort (info) { 176 | if (!responded) { 177 | responded = true; 178 | result.socket = { 179 | host: self.host, 180 | port: info.port 181 | }; 182 | drone.minUptime = 0; 183 | 184 | callback(null, result); 185 | 186 | // 187 | // Remove listeners to related events 188 | // 189 | drone.removeListener('exit', onExit); 190 | drone.removeListener('error', onError); 191 | } 192 | } 193 | 194 | // 195 | // When the drone starts, update the result that 196 | // we will respond with and continue to wait for 197 | // `*::carapace::port` from `haibu-carapace`. 198 | // 199 | function onStart (monitor, data) { 200 | result = { 201 | monitor: monitor, 202 | process: monitor.child, 203 | drone: data 204 | }; 205 | } 206 | 207 | // 208 | // If the drone exits prematurely then respond with an error 209 | // containing the data we receieved from `stderr` 210 | // 211 | function onExit () { 212 | if (!responded) { 213 | errState = true; 214 | responded = true; 215 | error = new Error('Error spawning drone'); 216 | error.stderr = stderr.join('\n') 217 | callback(error); 218 | 219 | // 220 | // Remove listeners to related events. 221 | // 222 | drone.removeListener('error', onError); 223 | haibu.running.hook.removeListener('*::carapace::port', onCarapacePort); 224 | } 225 | } 226 | 227 | // 228 | // Listen to the appropriate events and start the drone process. 229 | // 230 | drone.on('stdout', onStdout); 231 | drone.on('stderr', onStderr); 232 | drone.once('exit', onExit); 233 | drone.once('error', onError); 234 | drone.once('start', onStart); 235 | haibu.running.hook.once('*::carapace::port', onCarapacePort); 236 | drone.start(); 237 | }); 238 | }; 239 | } -------------------------------------------------------------------------------- /lib/sre/srec/srec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Service Runtime Environment Controller startup WRAP 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var path = require('path'), 9 | haibu = require('haibu'), 10 | appApi = require('./api/app'); 11 | 12 | /** 13 | * Create the httproxy System Service with communication channel 14 | */ 15 | require('../../core/service')({ 16 | // Function to execute when the event communication channel is ready 17 | ready: function() { 18 | // catch SIGINT... 19 | process.on('SIGINT', function () { 20 | console.log('SIGINT event raised in SREC...'); 21 | process.exit(0); 22 | }); 23 | }, 24 | 25 | // Function to execute when the parent asks us to start 26 | start: function(options, cb) { 27 | // put the needed code fixes to haibu 28 | require('./haibufix')(haibu); 29 | 30 | // Store a reference to the original `haibu.emit` function 31 | // Overwrite `haibu.emit` to lazily add a listener to any 32 | // event 33 | var _emit = haibu.emit; 34 | haibu.emit = function (ev, level, msg, meta) { 35 | process.parent.emit('haibu-ev::' + ev, [msg, meta]); 36 | _emit.apply(haibu, arguments); 37 | } 38 | 39 | // initialize an extra plugin for haibu 40 | haibu.use(require('./srecplugin')); 41 | 42 | // create all directories that will be used 43 | var root = path.join(process.env.HOME, options.directory || 'data'); 44 | haibu.config.set('directories', { 45 | apps: path.join(root, 'local'), 46 | autostart: path.join(root, 'autostart'), 47 | packages: path.join(root, 'packages'), 48 | tmp: path.join(root, 'tmp'), 49 | }); 50 | 51 | var haibuOptions = { 52 | env: options.env, 53 | port: options.port, 54 | host: '127.0.0.1', 55 | hook: { 56 | 'hook-name': options.name + '_haibu', 57 | 'hook-host': '127.0.0.1', 58 | 'hook-port': options['haibu-hook-port'] 59 | } 60 | }; 61 | 62 | function haibuReady(err, running) { 63 | if (err) return process.parent.emit('child::startuperror', { err: err, running: running }); 64 | 65 | // bridge important haibu hook.io events to System Controller (SC) 66 | running.hook.onAny(function(data) { 67 | process.parent.emit('haibu-io::' + this.event, data) 68 | }); 69 | 70 | // start the internal API 71 | appApi.start(process.parent, running.drone); 72 | 73 | // indicate to the parent that we are running!! 74 | cb('SREC ' + options.name + ' is running!!!'); 75 | } 76 | 77 | function autoStart(err, running) { 78 | if (err) return haibuReady(err); 79 | 80 | haibu.drone.autostart(running, function (err) { 81 | haibuReady(err, running); 82 | }) 83 | } 84 | 85 | function startDrone(err, hook) { 86 | if (err) return haibuReady(err); 87 | 88 | haibu.running.server = {close: function(){}}; // just put a dummy server for clean exit... 89 | haibu.running.drone = new haibu.drone.Drone(haibuOptions); 90 | haibu.running.ports = {}; 91 | 92 | return haibuReady(null, haibu.running); 93 | } 94 | 95 | function startHook() { 96 | return haibu.drone.startHook(haibuOptions.hook, startDrone); 97 | } 98 | 99 | // Indicate that `haibu.drone` has started 100 | haibu.drone.started = true; 101 | 102 | // Start the haibu startup sequence 103 | startHook(); 104 | }, 105 | 106 | // Actions to execute when the parent asks us to stop 107 | stop: function(cb) { 108 | // stop the app API 109 | appApi.stop(process.parent); 110 | 111 | // and stop Haibu 112 | haibu.drone.stop(function() { 113 | // tell the parent I'm in a save state 114 | cb('Data??'); 115 | }); 116 | } 117 | }); -------------------------------------------------------------------------------- /lib/sre/srec/srecplugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Service Runtime Environment Controller plugin, thats a haibu plugin! 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | */ 7 | 8 | var haibu = require('haibu'); 9 | 10 | var srecplugin = exports; 11 | 12 | // Name this plugin so it can be accessed by name 13 | srecplugin.name = 'srecplugin'; 14 | 15 | /** 16 | * Initalizes the `srecplugin` plugin in the current `haibu` environment. 17 | * Init is called by the haibu.use function. 18 | * @param {Object} options Options to initialize this plugin with 19 | * @param {function} callback Continuation to call when complete 20 | */ 21 | srecplugin.init = function(options, callback) { 22 | return callback(); 23 | }; 24 | 25 | /** 26 | * Called by haibu Spawner to get extra spawn options 27 | * @param {Repository} repo Code repository we are spawning from 28 | * @return Returns the appropriate spawn options for the `haibu.Spawner` for 29 | * the `repo` along with extra `srecplugin` options. 30 | */ 31 | srecplugin.argv = function(repo) { 32 | // TODO: Check if this can be done a lot easier.... 33 | // check all script arguments for replacement strings and replace with appropriate variables 34 | var args = (repo.app.scripts && repo.app.scripts.arguments) ? repo.app.scripts.arguments : [], 35 | ss = '%', 36 | replacements = { 37 | 'h': process.env.HOME, 38 | 'a': repo.appDir, 39 | 'c': repo.homeDir 40 | }; 41 | 42 | // if string then split to array 43 | args = (typeof args == 'string') ? args.split(' ') : args; 44 | // replace all variable strings 45 | args.forEach(function(val, index) { 46 | var i, arg, repl; 47 | for (i = val.indexOf(ss); i != -1; i = val.indexOf(ss, i+1)) { 48 | arg = val[i+1]; 49 | repl = (replacements[arg]) ? replacements[arg] : null; 50 | if (repl) val = val.replace(ss + arg, repl); 51 | } 52 | // put result back 53 | args[index] = val; 54 | }); 55 | 56 | return { 57 | scriptArgs: args, 58 | argv: [ 59 | '--hook-name', 60 | repo.app.name, 61 | '--hook-host', 62 | '127.0.0.1', 63 | '--hook-port', 64 | haibu.running.hook['hook-port'] 65 | ] 66 | }; 67 | }; -------------------------------------------------------------------------------- /lib/sse/httpproxy/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A httpproxy System Service, as used by the System Service Controller. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @author TTC/Sander Tolsma 8 | * @docauthor TTC/Sander Tolsma 9 | */ 10 | 11 | var httpProxy = require('http-proxy'); 12 | 13 | /** 14 | * Create the httproxy System Service with communication channel 15 | */ 16 | require('../../core/service')({ 17 | // Function to execute when the event communication channel is ready 18 | ready: function() { 19 | process.parent.on('parent::*::*', function() { 20 | console.log('child received ' + this.event, inspect(arguments)); 21 | }); 22 | }, 23 | 24 | // Function to execute when the parent asks us to start 25 | start: function(options, cb) { 26 | // Create the proxy server 27 | httpProxy.createServer(options.destPort, options.destAddress).listen(options.port, options.address); 28 | 29 | // indicate to the parent that we are running!! 30 | cb('Http-Proxy Server running at http://[' + options.address + ']:' + options.port + '/'); 31 | }, 32 | 33 | // Actions to execute when the parent asks us to stop 34 | stop: function(cb) { 35 | // tell the parent I'm in a save state 36 | cb('Data??'); 37 | } 38 | }); -------------------------------------------------------------------------------- /lib/sse/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Service System Environment subsystem definition as used by the System Controller. 3 | * 4 | * Copyright 2011 TTC/Sander Tolsma 5 | * See LICENSE file for license 6 | * 7 | * @module sse 8 | * @author TTC/Sander Tolsma 9 | * @docauthor TTC/Sander Tolsma 10 | */ 11 | 12 | var path = require('path'), 13 | inherits = require('util').inherits; 14 | 15 | var apiary = require('../apiary'); 16 | 17 | /** 18 | * The System Service Environment class definition used by the System Controller 19 | * @class Sse 20 | * @extends Controller 21 | * 22 | * @constructor 23 | * Create System Service Environment subsystem instance 24 | * @param {Object} Options SSE config options: 25 | * `uid` {String/Integer} Socket user id, default is -1 (current) 26 | * `gid` {String/Integer} Socket group id, if not defined the given uid is used, if thats not defined -1 (current) 27 | */ 28 | var Sse = module.exports = function(options) { 29 | // if called as function return Instance 30 | if (!(this instanceof Sse)) return new Sse(options); 31 | 32 | // set default options 33 | options = options || {}; 34 | options.name = options.name || 'sse'; 35 | this.uid = options.uid || -1; 36 | this.gid = options.gid || -1; 37 | 38 | // Call the parent constructor 39 | Sse.super_.call(this, options); 40 | 41 | // fill serviceTypes store with known sse services 42 | this.serviceAdd('httpproxy', path.join(__dirname, './httpproxy/index.js')); 43 | }; 44 | inherits(Sse, apiary.Controller); 45 | 46 | 47 | /** 48 | * Start a child System Service process 49 | * @param {Object} options Object with the following possible configuration options: 50 | * `name` {String} The name of the service to start 51 | * `type` {String} The System Service type to start 52 | * `cwd` {String} The current working directory to start the System Service in, default is current process cwd. 53 | * `uid` {String/Integer} System user uid, default is -1 (current) 54 | * `gid` {String/Integer} System user gid, if not defined the given uid is used, if thats not defined -1 (current) 55 | * @param {Function} cb Callback to call when System Service API is running 56 | */ 57 | Sse.prototype.startService = function(options, cb) { 58 | // set default options 59 | options = options || {}; 60 | options.uid = options.uid || this.uid; 61 | options.gid = options.gid || this.gid; 62 | 63 | // start the System Service with the given options and callback 64 | var child = Sse.super_.prototype.startService.call(this, options, cb); 65 | 66 | // 67 | // TODO: Temporary message passing, needs to be replaced by other 68 | // mechanism. At this moment only one, two and three deep events are bridged 69 | // 70 | function msgBridge() { 71 | var parts = this.event.split(this.delimiter), 72 | args = Array.prototype.slice.call(arguments); 73 | 74 | // cut child and replace with child name 75 | parts.splice(0, 1, options.name); 76 | parts.join(this.delimiter); 77 | 78 | // send message through to System Controller 79 | apiary.emit.apply(apiary, [parts.join(this.delimiter)].concat(args)); 80 | } 81 | child.on('child::*', msgBridge); 82 | child.on('child::*::*', msgBridge); 83 | child.on('child::*::*::*', msgBridge); 84 | 85 | return child; 86 | } 87 | 88 | 89 | /** 90 | * Log messages from this instance to the logger 91 | */ 92 | Sse.prototype.log = function() { 93 | apiary.emit.apply(apiary, arguments); 94 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apiary", 3 | "description": "Spawn multi-system multi-user node.js clouds, on your own hardware and/or with 3rd party virtual servers", 4 | "keywords": ["distributed", "cloud computing", "automated deployment", "platform-as-a-service"], 5 | "version": "0.0.2", 6 | "author": "Tolsma Telematica Consultancy ", 7 | "maintainers": [ 8 | "stolsma " 9 | ], 10 | "contributers": [ 11 | ], 12 | "licenses": [{ 13 | "type": "MIT" 14 | }], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/stolsma/apiary" 18 | }, 19 | "dependencies": { 20 | "async": "0.1.x", 21 | "clip": "git://github.com/stolsma/clip.git#nconf", 22 | "colors": "0.5.x", 23 | "daemon": "0.3.x >=0.3.2", 24 | "dnode": "0.7.x", 25 | "eventemitter2": "0.4.x", 26 | "forever": "0.7.x", 27 | "haibu": "0.5.x", 28 | "haibu-carapace": "0.2.x >=0.2.7", 29 | "http-proxy": "0.7.x", 30 | "intercom": "0.2.x >=0.2.3", 31 | "mkdirp": "0.0.x", 32 | "nconf": "git://github.com/stolsma/nconf.git#get-patch", 33 | "rimraf": "1.0.x", 34 | "winston": "0.5.x" 35 | }, 36 | "main": "./lib/apiary", 37 | "bin": { 38 | "apiary": "./bin/apiary" 39 | }, 40 | "engines": { 41 | "node": "0.4.x" 42 | } 43 | } 44 | --------------------------------------------------------------------------------