diff --git a/README.md b/README.md
index e659215..c5231c0 100644
--- a/README.md
+++ b/README.md
@@ -22,8 +22,7 @@ Please use the Git clone way to participate.
* VirtualBox
* Vagrant >= 1.7
-* Git
-* (currently) NodeJS
+* Git + Gitbash (must be installed with the environment variables option)
```
@@ -49,11 +48,18 @@ Please use the Git clone way to participate.
## Ports and Proxy magic
- host port | vm port | docker expose | description |
----|---|---|---
- __4480__ | __4480__ | __80__ | Proxy that provides all started docker container
- __4443__ | __4443__ | | Proxy SSL termination point __cumming soon__
- __31313__ | [_31313_] | | Eintopf GUI server currently host only version
+ host port | vm port | description |
+---|---|---
+ __4480__ | __4480__ | Proxy that provides all started docker container
+ __4443__ | __4443__ | Proxy SSL termination point
+ __31313__ | [_31313_] | Eintopf GUI server currently host only version
+
+
+The containers being proxied must [expose](https://docs.docker.com/reference/run/#expose-incoming-ports) the port to be proxied, either by using the `EXPOSE` directive in their `Dockerfile` or by using the `--expose` flag to `docker run` or `docker create`.
+
+Provided your DNS is setup to forward foo.bar.com to the a host running nginx-proxy, the request will be routed to a container with the VIRTUAL_HOST env var set.
+
+For more information see [Proxy compatibility](#proxy-compatibility) or see the proxy [documentation](https://github.com/jwilder/nginx-proxy)
# Configuration
@@ -152,6 +158,157 @@ npm run app-install -- name_of_npm_module
```
Of course this method works also for pure-js modules, so you can use it all the time if you're able to remember such an ugly command.
+# Pattern Development
+
+A pattern is a project configuration which can be used by Eintopf.
+
+Minimal file system:
+
+ example-pattern/
+ package.json
+
+The file package.json defines this package as a Eintopf pattern. Minimal package.json:
+
+ {
+ "name": "example-pattern", # pattern id
+ "eintopf": {
+ "name": "Example-pattern", # pattern name
+ "description": "This is my example-pattern. It can be used as ..." # pattern description
+ }
+ }
+
+## Set your start and stop action
+
+To make Eintopf actually do things you have to set a start and stop action of the pattern. In this case we want to execute simple sh scripts.
+
+File system:
+
+ example-pattern/
+ package.json
+ start.sh
+ stop.sh
+
+Example package.json:
+
+ {
+ ...
+ "scripts": {
+ "start": "sh ./start.sh",
+ "stop": "sh ./stop.sh"
+ },
+ ...
+ }
+
+Example start.sh:
+
+ #/bin/sh
+
+ docker run -d --name examplepatterndev -e VIRTUAL_HOST=example-pattern.dev nginx
+
+Example stop.sh
+
+ #/bin/sh
+
+ docker stop examplepatterndev
+
+
+## Custom actions
+
+You can set custom actions which can be used in the Eintopf gui.
+
+File system:
+
+ example-pattern/
+ package.json
+ start.sh
+ stop.sh
+ customaction1.sh
+ customaction2.sh
+
+Example package.json:
+
+ {
+ ...
+ "scripts": {
+ "start": "sh ./start.sh",
+ "stop": "sh ./stop.sh",
+ "action1": "sh ./customaction1.sh",
+ "action2": "sh ./customaction2.sh"
+ },
+ "eintopf": {
+ "name": "Example-pattern",
+ "description": "This is my example-pattern. It can be used as ...",
+ "actions": [
+ {
+ "name": "customaction1",
+ "script": "action1" # maps to scripts.action1
+ },
+ {
+ "name": "customaction2",
+ "warning": "my custom action 2. This should execute script customaction2.sh",
+ "script": "action2" # maps to scripts.action2
+ }
+ ]
+ ...
+ }
+
+## Proxy compatibility
+
+The full proxy documentation can be found [here](https://github.com/jwilder/nginx-proxy).
+
+The containers being proxied must [expose](https://docs.docker.com/reference/run/#expose-incoming-ports) the port to be proxied, either by using the `EXPOSE` directive in their `Dockerfile` or by using the `--expose` flag to `docker run` or `docker create`.
+
+Provided your DNS is setup to forward foo.bar.com to the a host running nginx-proxy, the request will be routed to a container with the VIRTUAL_HOST env var set.
+
+### Multiple Ports
+
+If your container exposes multiple ports, the proxy will default to the service running on port 80. If you need to specify a different port, you can set a VIRTUAL_PORT env var to select a different one. If your container only exposes one port and it has a VIRTUAL_HOST env var set, that port will be selected.
+
+### Multiple Hosts
+
+If you need to support multiple virtual hosts for a container, you can separate each entry with commas. For example, `foo.bar.com,baz.bar.com,bar.com` and each host will be setup the same.
+
+### Wildcard Hosts
+
+You can also use wildcards at the beginning and the end of host name, like `*.bar.com` or `foo.bar.*`. Or even a regular expression, which can be very useful in conjunction with a wildcard DNS service like [xip.io](http://xip.io), using `~^foo\.bar\..*\.xip\.io` will match `foo.bar.127.0.0.1.xip.io`, `foo.bar.10.0.2.2.xip.io` and all other given IPs. More information about this topic can be found in the nginx documentation about [`server_names`](http://nginx.org/en/docs/http/server_names.html).
+
+
+Examples:
+
+ docker run -d --name examplepatterndev -e VIRTUAL_HOST=example-pattern.dev nginx
+ docker run -d --name examplepatterndev2 -e VIRTUAL_HOST=*.example-pattern.dev --expose 3000 nodejs # supports wildcard certificates
+ docker run -d --name examplepatterndev3 -e VIRTUAL_HOST=example-pattern3.dev --expose 3000 nodejs
+
+Example docker-compose.yml:
+
+ examplepatterndev:
+ image: nginx
+ environment:
+ - VIRTUAL_HOST=example-pattern.dev
+ ...
+
+## SSL certificates
+
+You can add your own ssl certificates which will be used by the proxy. The certificate files have to match the VIRTUAL_HOST name and have to end with .key and .crt.
+
+The files will be updated on pattern installation and on pattern update. They will be removed on pattern deletion. If the container was already running while installing the certs, you have to restart the container.
+
+All certificates are collected in eintopfHome/eintopfNamespace/proxy/certs/ (default: ~/.eintopf/default/proxy/certs/)
+
+File system:
+
+ configs/
+ example-pattern/
+ package.json
+ start.sh
+ stop.sh
+ certs/
+ example-pattern.dev.key
+ example-pattern.dev.crt
+ proxy/
+ certs/ # certificates are collected here
+ example-pattern.dev.key
+ example-pattern.dev.crt
# Making a release
diff --git a/app/app_modules/gui/public/src/partials/cooking.projects.recipe.html b/app/app_modules/gui/public/src/partials/cooking.projects.recipe.html
index da94d8b..b248f64 100644
--- a/app/app_modules/gui/public/src/partials/cooking.projects.recipe.html
+++ b/app/app_modules/gui/public/src/partials/cooking.projects.recipe.html
@@ -74,7 +74,7 @@
{{action.warning}}
diff --git a/app/main.js b/app/main.js
index 28ecbe4..ba8df5e 100644
--- a/app/main.js
+++ b/app/main.js
@@ -3,6 +3,7 @@ var shell = require('shell');
var BrowserWindow = require('browser-window');
var menuEntries = require('./app_modules/gui/public/src/js/services/app-menu');
var windowStateKeeper = require('./vendor/electron_boilerplate/window_state');
+var Menu = require('menu');
process.cwd = app.getAppPath;
@@ -16,7 +17,54 @@ var mainWindowState = windowStateKeeper('main', {
height: 600
});
+initMenu = function () {
+ //if (Menu.getApplicationMenu()) return; // ignore if menu is present
+
+ var template = [
+ {
+ label: "Eintopf",
+ submenu: [
+ {
+ label: "About", click: function () {
+ shell.openExternal('https://github.com/mazehall/eintopf');
+ }
+ },
+ {type: "separator"},
+ {
+ label: "Reload", accelerator: "CmdOrCtrl+R", click: function (item, focusedWindow) {
+ if (focusedWindow) focusedWindow.reload();
+ }
+ },
+ {type: "separator"},
+ {
+ label: "Quit", accelerator: "CmdOrCtrl+Q", click: function () {
+ app.quit();
+ }
+ }
+ ]
+ }
+ ];
+
+ if (process.platform == 'darwin') { // set edit actions for OS X
+ template.push({
+ label: "Edit",
+ submenu: [
+ {label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:"},
+ {label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:"},
+ {type: "separator"},
+ {label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:"},
+ {label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:"},
+ {label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:"},
+ {label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:"}
+ ]
+ });
+ }
+
+ Menu.setApplicationMenu(Menu.buildFromTemplate(template));
+};
+
app.on('ready', function () {
+ initMenu();
mainWindow = new BrowserWindow({
x: mainWindowState.x,
@@ -30,14 +78,14 @@ app.on('ready', function () {
mainWindow.maximize();
}
- process.on('app:serverstarted', function() {
+ process.on('app:serverstarted', function () {
var appUrl = "http://localhost:" + port;
mainWindow.loadUrl(appUrl, {userAgent: "electron"});
- webContents.on("will-navigate", function(event, targetUrl){
- if (targetUrl.indexOf(appUrl) === -1){
- shell.openExternal(targetUrl);
- event.preventDefault();
- }
+ webContents.on("will-navigate", function (event, targetUrl) {
+ if (targetUrl.indexOf(appUrl) === -1) {
+ shell.openExternal(targetUrl);
+ event.preventDefault();
+ }
});
});
process.emit('app:startserver', port);
@@ -48,7 +96,6 @@ app.on('ready', function () {
mainWindowState.saveState(mainWindow);
});
-
});
app.on('window-all-closed', function () {
diff --git a/app/models/projects/list.coffee b/app/models/projects/list.coffee
index b0cfd81..e161e5f 100644
--- a/app/models/projects/list.coffee
+++ b/app/models/projects/list.coffee
@@ -141,7 +141,7 @@ model.loadProjects = () ->
projects = foundProjects
.flatMap mazehall.readPackageJson
.filter (x) ->
- x.pkg.mazehall && x.pkg.eintopf && typeof x.pkg.eintopf is "object"
+ x.pkg.eintopf && typeof x.pkg.eintopf is "object"
.onValue (val) ->
jetpack.cwd(val.path).findAsync val.path, {matching: ["README*.{md,markdown,mdown}"], absolutePath: true}, "inspect"
.then (markdowns) ->
@@ -154,25 +154,6 @@ model.loadProjects = () ->
projects = foundProjects
watcherModel.set 'projects:list', projects
-model.startProject = (project, callback) ->
- return callback new Error 'invalid project given' if typeof project != "object" || ! project.path?
-
- process = child.exec 'npm start', {cwd: project.path}
- process.stdout.on 'data',(chunk) ->
- watcherModel.log 'res:project:start:' + project.id, chunk
- process.stderr.on 'data',(chunk) ->
- watcherModel.log 'res:project:start:' + project.id, chunk
-
-
-model.stopProject = (project, callback) ->
- return callback new Error 'invalid project given' if typeof project != "object" || ! project.path?
-
- process = child.exec 'npm stop', {cwd: project.path}
- process.stdout.on 'data',(chunk) ->
- watcherModel.log 'res:project:stop:' + project.id, chunk
- process.stderr.on 'data',(chunk) ->
- watcherModel.log 'res:project:stop:' + project.id, chunk
-
model.deleteProject = (project, callback) ->
return callback new Error 'invalid project given' if typeof project != "object" || ! project.path?
@@ -186,30 +167,37 @@ model.deleteProject = (project, callback) ->
model.loadProjects()
callback null, true
+model.startProject = (project, callback) ->
+ return callback new Error 'invalid project given' if typeof project != "object" || ! project.path?
+ logName = "res:project:start:#{project.id}"
+
+ return watcherModel.log logName, "script start does not exist\n" unless project.scripts["start"]
+ utilModel.runCmd project.scripts["start"], {cwd: project.path}, logName
+
+model.stopProject = (project, callback) ->
+ return callback new Error 'invalid project given' if typeof project != "object" || ! project.path?
+ logName = "res:project:stop:#{project.id}"
+
+ return watcherModel.log logName, "script stop does not exist\n" unless project.scripts["stop"]
+ utilModel.runCmd project.scripts["stop"], {cwd: project.path}, logName
+
model.updateProject = (project, callback) ->
return callback new Error 'invalid project given' if typeof project != "object" || ! project.path?
+ logName = "res:project:update:#{project.id}"
- watcherModel.log "res:project:update:#{project.id}", ["Start pulling...\n"]
- process = child.exec "git pull", {cwd: project.path}
- process.stdout.on 'data',(chunk) ->
- watcherModel.log "res:project:update:#{project.id}", chunk
- process.stderr.on 'data',(chunk) ->
- watcherModel.log "res:project:update:#{project.id}", chunk
- process.on 'close', () ->
+ watcherModel.log logName, ["Start pulling...\n"]
+ utilModel.runCmd "git pull", {cwd: project.path}, logName, (err, result) ->
+ return callback err if err
model.initProject project.path, callback
model.callAction = (project, action, callback) ->
if callback?
return callback new Error 'invalid project given' if typeof project != "object" || ! project.path? || ! action?
return callback new Error 'invalid script name' if project.scripts? or action.script? or project.scripts[action.script]?
+ logName = "res:project:action:script:#{project.id}"
- return watcherModel.log "res:project:action:script:#{project.id}", "script '#{action.script}' does not exists\n" unless project.scripts[action.script]
-
- process = child.exec "npm run #{action.script}", {cwd: project.path}
- process.stdout.on "data",(chunk) ->
- watcherModel.log "res:project:action:script:#{project.id}", chunk if chunk
- process.stderr.on "data",(chunk) ->
- watcherModel.log "res:project:action:script:#{project.id}", chunk
+ return watcherModel.log logName, "script '#{action.script}' does not exists\n" unless project.scripts[action.script]
+ utilModel.runCmd project.scripts[action.script], {cwd: project.path}, logName
watcherModel.propertyToKefir 'containers:list'
.onValue ->
diff --git a/app/models/util/index.coffee b/app/models/util/index.coffee
index 1f64236..f6c602d 100644
--- a/app/models/util/index.coffee
+++ b/app/models/util/index.coffee
@@ -1,5 +1,8 @@
config = require '../stores/config'
jetpack = require 'fs-jetpack'
+spawn = require('child_process').spawn
+
+watcherModel = require '../stores/watcher.coffee'
appConfig = config.get 'app'
@@ -20,3 +23,27 @@ module.exports.getConfigPath = () ->
module.exports.getConfigModulePath = () ->
return null if ! (configPath = @getConfigPath())? || ! appConfig.defaultNamespace
return jetpack.cwd(configPath).path appConfig.defaultNamespace
+
+module.exports.runCmd = (cmd, config, logName, callback) ->
+ config = {} if ! config
+ output = ''
+
+ sh = 'sh'
+ shFlag = '-c'
+
+ if process.platform == 'win32'
+ sh = process.env.comspec || 'cmd'
+ shFlag = '/d /s /c'
+ config.windowsVerbatimArguments = true
+
+ proc = spawn sh, [shFlag, cmd], config
+ proc.on 'error', (err) ->
+ return callback err if callback
+ proc.on 'close', (code, signal) ->
+ return callback null, output if callback
+ proc.stdout.on 'data', (chunk) ->
+ watcherModel.log logName, chunk.toString() if logName
+ output += chunk.toString()
+ proc.stderr.on 'data', (chunk) ->
+ watcherModel.log logName, chunk.toString() if logName
+ output += chunk.toString()
\ No newline at end of file
diff --git a/app/server.js b/app/server.js
index a6011a1..f2f13bc 100644
--- a/app/server.js
+++ b/app/server.js
@@ -1,8 +1,15 @@
+var _r = require('kefir');
+var mazehall = require('mazehall');
var server = require('./app.js');
-process.on('app:startserver', function(port) {
+serverStream = _r.fromEvents(process, 'app:startserver').filter();
+guiStream = mazehall.moduleStream.filter(function(val) { if(val.module == 'gui') return val; });
+
+_r.zip([guiStream, serverStream])
+.onValue(function(val) {
+ var port = val[1];
server.listen(port, function() {
- process.emit('app:serverstarted');
console.log('server listen on port: ' + port);
+ process.emit('app:serverstarted');
});
});
\ No newline at end of file