Initial commit

This commit is contained in:
2018-09-17 20:32:19 +08:00
commit 3a9804f020
58 changed files with 31566 additions and 0 deletions

874
CHANGELOG.md Normal file
View File

@@ -0,0 +1,874 @@
# 1.4.2 (2018-04-12)
Main changes: add instance with desired number, Force using of socket.io 2.1.0, Bugfixes
* (bluefox) fix error with letsencrypt debug output
* (bluefox) fix delObject
* (bluefox) fix restore of backup
* (bluefox) allow to add instance with desired number
* (apollon77) fix auto multihost
* (bluefox) fix for adapter update
* (bluefox) Force using of socket.io 2.1.0
# 1.4.0 (2018-04-03)
Main changes: uninstall via npm, downgrade winston-syslog, refactored deleteAdapter, object.json auto-backups
* (AlCalzone) fix npm version pre-install check
* (bluefox) remove winston-syslog 2.0 and replace it with 1.2.6
* (AlCalzone) refactored and promisified `deleteAdapter` and `deleteInstance`
* (bluefox) remove controller from the adapters list in admin
* (bluefox) also uninstall adapters using npm
* (bluefox) backup object.json files every 2 hours for last 48 hours (warning! Disk usage)
* (bluefox) added cli command to update/add the vis/knx license
# 1.3.0 (2018-03-11)
Main changes: socket.io Version downgraded because of bug. Better npm5 support
* (AlCalzone) Remove the outdated npm package and disable package-lock before installing (#175)
* (AlCalzone) reworked npm adapter version check without `npm` package
* (AlCalzone) ignore local NPM for version check
* (AlCalzone) don't modify the parent's process PATH variable
* (AlCalzone) check npm version before installing and potentially disable package-lock
* (AlCalzone) don't cancel installation, or we're breaking yunkong2 anyways
* (AlCalzone) ignore local npm version for preinstall check
* (bluefox) move socket.io from 2.0.4 to 1.5.1 because of bug
* (bluefox) add intro to default tabs
# 1.2.7 (2018-03-06)
Main changes: support of npm5, Multihost fixed, added promises to adapter.js
* (bluefox) fix multihost connect
* (bluefox) add "multihost status" command
* (bluefox) make statistics interval adjustable
* (bluefox) better scan of installed adapters
* (bluefox) better deletion of adapters
* (bluefox) fix requests like getStates('*.info.connection')
* (bluefox) create instance's objects by start.
* (AlCalzone) Add promisified methods to the adapter class
* (AlCalzone) enable basic type-checking and fix found error
* (Apollon77) fix potential error and check if that.log exists
* (bluefox) updates npm packets
* (Apollon77) log an error when npmInstallWithCheck throws
* (AlCalzone) [npm5] Disable package-lock.json before installing anything
* (Apollon77) use stable tag from admin in dependencies
* (bluefox) fix empty ID error
# 1.2.5 (2018-01-27)
* (bluefox) move buildRepository.js to yunkong2.repositories
* (bluefox) fix adapter download
* (Apollon77) also include npm5 fix into reinstall.sh
* (bluefox) add functions to standard enums
* (bluefox) check if pattern is valid
* (bluefox) catch error by deleting of adapter.
* (bluefox) better adapter directory search
* (Apollon77) another fix for reinstall.sh, add --unsafe-perm
* (Apollon77) exit mocha tests explicitely when completed, needed pot. with most current version of mocha
* (Apollon77) add npm5 check and "Block" to "yunkong2 install" commands
# 1.2.4 (2017-12-15)
* (bluefox) The fix for npm5
# 1.2.3 (2017-11-24)
* (bluefox) fix windows problem and storing of error messages
* (bluefox) fix logging level: silly
* (bluefox) fix dependency check
* (bluefox) fix small errors
* (bluefox) add repo commands to cli
* (bluefox) setTimeout(0) => setImmediate
* (bluefox) add timestamp and "from" information object
* (bluefox) allow to enable redis by setup
* (bluefox) catch backup errors
* (bluefox) ignore errors by setup first
* (bluefox) fix generate repository
* (bluefox) calculate number of datapoints in vis and deliver it in statistics
* (bluefox) fix restoring of backup
* (AlCalzone) fix install urls ending with ".git"
# 1.2.0 (2017-09-24)
* (bluefox) fixed upgrade command
* (bluefox) allow install from custom repositories
* (bluefox) remove online and sources repositories
* (bluefox) fix multihosts command
* (bluefox) catch the error outputs of instances if they die
* (bluefox) no more support for node.js 0.10/0.12
* (bluefox) add new logging level: silly
# 1.1.3 (2017-08-13)
* (bluefox) Extend statistics (node.js versions and some HW parameters will be reported)
* (bluefox) Update npm packets
* (bluefox) catch semver error
* (bluefox) change interface of getDevices functions
* (bluefox) change interface of createChannel functions
* (bluefox) working on multihost service
* (bluefox) fix users cli
* (bluefox) implement defaultNewAcl
* (bluefox) remove 0.10 and add 8 by tests
* (bluefox) fix restart of adapters
# 1.1.2 (2017-07-13)
* (bluefox) Close sockets by default for external connects
# 1.1.1 (2017-06-29)
* (jens-maus) allow redis connections via unix sockets by specifying host as e.g. '/var/run/redis/redis.sock' and setting port to 0. This should slightly improve performance on busy installations.
* (Apollon77) optimizations for permission handling
# 1.1.0 (2017-06-08)
* (bluefox) BREAKING Changes: For multihost systems the user MUST explicit allow connections from other IPs in /opt/yunkong2/yunkong2-data/yunkong2.json
```
"host": "127.0.0.1",
=>
"host": "0.0.0.0",
```
for objects (line 11) and for states (line 21).
# 1.0.3 (2017-06-01)
* (bluefox) getHostInfo for new admin
* (bluefox) allow using of files for certificates
* (bluefox) always install zwave with unsafe-perm option
* (apollon77) add handling for undefined
* (apollon77) uptime is a number only and no String/List/Array beside the fact that also with a number a "toString" should exist, or it's undefined because an old host is listed with no value ...
# 1.0.1 (2017-05-03)
* (bluefox) Change repository generation (not relevant for users)
* (bluefox) small change for install porcess (not relevant for users)
# 1.0.0 (2017-04-23)
* (bluefox) No big changes, just version
* (bluefox) remove warning about iDs
* (bluefox) fix tests
* (bluefox) try to ignore npm error 1
* (bluefox) fix possible error.
* (bluefox) create states with ack=true by default
# 0.17.2 (2017-03-25)
* (bluefox) do not store logs and messages and just publish it
* (bluefox) remove mochawsome
# 0.17.1 (2017-03-15)
* (bluefox) add stable repository
* (bluefox) fix bug with user=>users (required for user rights)
* (bluefox) install discovery adapter too if exists at first start
* (bluefox) extend configuration with city, country and GPS coordinates
* (bluefox) send city and country in statistics and allow to disable it
# 0.16.2 (2017-03-08)
* (bluefox) fix "yunkong2 passwd username"
# 0.16.1 (2017-02-27)
* (bluefox) use SHA256 instead of SHA1 (All passwords must be reset via "yunkong2 passwd username")
* (bluefox) change cli commands for user: user add, user del, user set, user enable...
* (bluefox) replace letsencrypt with greenlock
* (bluefox) try to fix sha256 issue with node.js 0.10.x
* (bluefox) add uniti
* (Apollon77) change reinstall.sh script
* (bluefox) add setStateChanged
* (bluefox) implement event counters
* (bluefox) read values from cache if possible in adapter
* (bluefox) add parser adapter, smartmeter, fakeroku, wetty, fronius, Worx Landroid Rasenmäher
* (bluefox) fix log sources
* (bluefox) fix callbacks calls by setObjectNotExists
* (bluefox) fix getForeignObjects
* (bluefox) write tests
* (Patrick) adapter.namespace now always int (was int or string mixed)
* (Patrick) _fixId better results for empty obj and string (return namespace + '.')
* (bluefox) small fixes for multihost
* (bluefox) add getInstalledAdapter for autodiscovery
# 0.15.3 (2017-01-20)
* (bluefox) fix of autoSubscribe
* (bluefox) no extra uplaod if install of adapters from github
# 0.15.1 (2017-01-14)
* (bluefox) documentation of adapter
* (bluefox) support of autoSubscribe
* (bluefox) add innogy-smarthome, vis-players
* (bluefox) fix error with "preserve" settings
* (bluefox) restart adapter immediately if desired
* (bluefox) support of web extensions
# 0.14.0 (2016-12-17)
* (bluefox) add debug information for logging
* (bluefox) remove occ
* (bluefox) add mpd, icons-fatcow-hosting
* (bluefox) update node-schedule
* (bluefox) add cli command "show uuid"
* (bluefox) try to fix issue with npm3
* (bluefox) add console command "repo [name]"
* (bluefox) try to extract the information from local sources-dist.json it online not reachable
* (bluefox) add host remove
* (bluefox) install non enabled adapters
* (bluefox) install icons on backup too
* (bluefox) fix some sporadic errors
# 0.13.3 (2016-11-26)
* (bluefox) support of getLocationOnDisk message for admin
* (bluefox) allow upgrade to specific version, like adapter@0.1.0
* (bluefox) you can change any native parameter of instance with set
* (bluefox) add support of syslog (see yunkong2.json to enable)
* (bluefox) add radar
* (bluefox) do not change title of instance by upload and update
# 0.13.2 (2016-11-04)
* (bluefox) fix new installation
# 0.13.1 (2016-10-31)
* (bluefox) fix renaming of host by start
* (bluefox) implement auto-objects and auto-states for adapter
* (bluefox) recursive deletion of folders with objects.unlink
* (bluefox) support of tarballs as install path
* (bluefox) rename rpi to rpi2, homekit to homekit2
* (bluefox) add upnp
* (bluefox) fix fileName of log file
* (bluefox) remove peerDependencies
# 0.13.0 (2016-10-21)
* (bluefox) fix letsencrypt to use fullchain.pem
* (bluefox) fix error with emitter
* (bluefox) fix formatDate for russian month
* (bluefox) backup letsencrypt files too
* (bluefox) install missing adapters one after other and not parallel.
* (bluefox) let remove UUID to prepare images with yunkong2
* (bluefox) using peerDependencies
* (bluefox) rename host automatically if singlehost
* (bluefox) add write/read properties to alive variable
* (bluefox) fix creation of package.json for npm 3
* (bluefox) try catch for parse of states
* (appolon77) add possibility to send messages with json
* (bluefox) updates some packages
* (bluefox) print node.js version at start
* (bluefox) force logger to use local time
* (appolon77) fix multi instance messaging
* (bluefox) using peerDependencies
* (bluefox) rename host automatically if singlehost
* (bluefox) update artnet
* (bluefox) add vis-canvas-gauges
* (bluefox) add rflink
* (bluefox) add foobar2000
* (bluefox) add mqtt client
* (bluefox) add lgtv
* (bluefox) add pushsafer
# 0.12.2 (2016-09-04)
* (bluefox) fix read versions by multihost
* (bluefox) add owntracks
* (bluefox) add amazon-dash
* (bluefox) control enabled for instance over system.adapter.NAME.INSTANCE.alive variable
# 0.12.1 (2016-09-02)
* (bluefox) fixed letsencrypt file
# 0.12.0 (2016-08-27)
* (bluefox) working on objects in redis
* (bluefox) add botvac adapter
* (bluefox) better multihost
* (bluefox) fix formatDate
# 0.11.3 (2016-08-24)
* (PArns) fix upgrade of adapters
* (bluefox) update "_design/xyz" by upgrade
# 0.11.2 (2016-08-13)
* (bluefox) fix upgrade of adapters
# 0.11.1 (2016-07-30)
* (bluefox) fix dependency if depend on js-controller
# 0.11.0 (2016-07-27)
* (bluefox) implement auto-restart of adapters (expert mode)
* (bluefox) add rights check for getBinaryState/setBinaryState
* (bluefox) support of default ttl for sessions
* (bluefox) fix custom setup
* (bluefox) fix upload binary files
* (bluefox) fix list of files in subdirectories
# 0.10.1 (2016-07-06)
* (bluefox) support of chained certificates
* (bluefox) add nut
* (bluefox) add vis-map
# 0.10.0 (2016-07-01)
* (bluefox) suport of exportand import
* (bluefox) activate redis for states
* (bluefox) fix install of adapter with singletonHost
* (bluefox) issue event if state deleted (redis)
* (bluefox) fix error with administrator users
* (bluefox) do not store repository if with errors
* (bluefox) fix checkPassword and setPassword
* (bluefox) update wrong SSL certificates
* (bluefox) add freemem state to host
* (bluefox) add milliseconds to formatDate
* (bluefox) update tar.gz
* (bluefox) add fhem
* (bluefox) add netatmo
* (bluefox) add tankerkoenig
* (bluefox) add vis-history
* (bluefox) add homepilot
* (bluefox) add cloud
# 0.9.0 (2016-05-23)
* (bluefox) make from seconds the ms
* (bluefox) add console command "isrun"
* (bluefox) add "--timeout 5000"
* (bluefox) fix small errors
* (bluefox) change function formatValue
* (bluefox) fix stop of scheduled adapters
* (bluefox) add "--logs" flag for adapter start (required by adapter debugging)
* (bluefox) make hostname configurable
* (bluefox) fix update of adapters and settings
# 0.8.10 (2016-04-25)
* (bluefox) fix restart script
* (bluefox) update default certificates
# 0.8.9 (2016-04-23)
* (bluefox) do not handle exceptions in logger
* (bluefox) change logger
* (bluefox) set valid mimeType for *.manifest
* (bluefox) add noolite adapter
* (bluefox) change download script
* (bluefox) change rename script
* (bluefox) add starline
* (bluefox) change repository building
* (bluefox) add 'delete' objects
* (bluefox) change behavior by exceptions
* (bluefox) workaround for DHCP delay
* (bluefox) fix passwd command
* (bluefox) do not write error under windows: "cannot delete log file"
# 0.8.8 (2016-02-29)
* (bluefox) replace winston with latest module
* (bluefox) add syslog support
* (bluefox) fix some LINT warnings
* (bluefox) add "host self" command (identical to "host this")
* (bluefox) fix error with npm 3 if no node_modules directory found
* (bluefox) support of noCache flag
* (bluefox) fix error if _data.json file broken
* (bluefox) support of file uploading: yunkong2 fileName /adapter/fileName
# 0.8.7 (2016-02-24)
* (bluefox) fix getForeignObjects
* (bluefox) add telegram
* (bluefox) enable OEM naming
* (bluefox) fix small error if multihost not available
* (bluefox) add reinstall script
* (bluefox) add vis-justgage adapter
* (bluefox) add mysensors
# 0.8.6 (2016-02-04)
* (bluefox) add text2command adapter
* (bluefox) fix upload problem
* (bluefox) use node-schedule 1.0.0
* (bluefox) extend node node_modules/yunkong2.js-controller/lib/buildRepository.js command
# 0.8.5 (2016-02-01)
* (bluefox) update version of node-schedule to fix problem with Februar.2016
* (bluefox) update socket.io version
* (bluefox) add logo image
* (bluefox) add buildRepository.js
* (bluefox) If desired, that adapter must be terminated
* (bluefox) use isFloatComma in formatValue
* (soef) formatDate extended and formatValue added
* (soef) formatDate extended to use seconds as duratiorn
* (soef) formatValue added to convert a value to a string with thousand separator....
* (bluefox) add homekit and miele
* (bluefox) fix upload of files
# 0.8.4 (2016-01-22)
* (bluefox) fix version
* (bluefox) fix log outputs
# 0.8.3 (2016-01-21)
* (bluefox) add commands like "npm start"
* (bluefox) check singletonHost one on host
* (bluefox) add memoryLimitMB for controller and adapters
* (bluefox) make install from NON-git sources possible again.
* (bluefox) add rpi, weatherunderground, chromecast, geofency, samsung, squeezebox, vcard, yamaha
* (husky-koglhof) occ und rpi Adapter
* (angelnu) visdebug - check for different adapter directories
* (bluefox) enable install of icons-open-icon-library-png
# 0.8.2 (2015-12-14)
* (bluefox) fix upgrade.
# 0.8.1 (2015-12-14)
* (bluefox) fix permissions for administrator group, but not admin user.
* (bluefox) support of getHistory command and defaultHistory
* (bluefox) implement "yunkong2 restart adapter"
* (bluefox) enable write dependencies as an object
* (bluefox) remove directory adapter and move example to yunkong2.template
* (bluefox) prepare support of syslog
* (bluefox) add yunkong2.sql
* (bluefox) add yunkong2.influxdb
* (bluefox) remove example adapter (it is replaced with yunkong2.template)
* (bluefox) start of renaming of js-controller to enable branding
# 0.7.15 (2015-11-10)
* (bluefox) add command visdebug
* (bluefox) add flag preserveSettings
* (bluefox) add vis-keyboard
* (bluefox) fix error with host rename
* (bluefox) fix sendTo and sendToHost with callback.
* (bluefox) update objects by upload of adapter (important for community adapters)
* (bluefox) add vis-google-fonts
* (bluefox) support of quality in setState
* (bluefox) add adapter mobile
# 0.7.14 (2015-10-13)
* (bluefox) fix restart under linux
* (bluefox) add wolf adapter to repository
* (smilingJack) increase timeout by update of repository
* (bluefox) fix set --ssl
* (bluefox) add "connectTimeout" parameter to yunkong2.json. Try to fix EADDRINUSE error under raspi.
# 0.7.13 (2015-09-30)
* (bluefox) add vis-jqui-mfd
* (bluefox) allow install direct from github in admin
* (bluefox) add vis-fancyswitch, vis-rgraph
* (bluefox) fill state by createState even if no default value set
* (bluefox) add modbus
# 0.7.12 (2015-09-15)
* (bluefox) add terminal adapter
* (bluefox) inplement "yunkong2 url xxx"
* (bluefox) fix restore
* (bluefox) fix _failCounter entry in Adapters
* (bluefox) fix log outputs
# 0.7.11 (2015-08-23)
* (bluefox) fix installation of adapter
# 0.7.10 (2015-08-22)
* (bluefox) fix first installation
# 0.7.9 (2015-08-20)
* (bluefox) fix broker upgrade
* (bluefox) improve vis upload
* (bluefox) fix adapter.deleteChannel
* (bluefox) use regex by deleting of channel or instance
* (bluefox) fix delete instance errors
* (bluefox) add new console commands: set, host. To set settings of instance from console and change host name.
# 0.7.8 (2015-08-12)
* (bluefox) fix error with node-red
* (bluefox) move setup files into directories
# 0.7.7 (2015-08-11)
* (bluefox) add harmony to repository
# 0.7.6 (2015-08-06)
* (bluefox) change log file extension to .log
* (bluefox) enable destroyDB, but check before if yunkong2 is running
* (bluefox) update packages
* (bluefox) add bars, plumbs, scenes
# 0.7.5 (2015-07-27)
* (bluefox) add "yunkong2 upload all" and "yunkong2 start all" commands
* (bluefox) fix "yunkong2 package"
* (bluefox) make it possible to allow OBJECTS and STATES only on localhost
* (bluefox) add new adapter pushbullet
* (bluefox) fix restart by installing.
# 0.7.4 (2015-07-19)
* (bluefox) fix restartAdapters flag.(for vis-metro and co)
* (bluefox) add vis-hqWidgets
* (bluefox) add vis-colorpicker
* (bluefox) normalize paths in backup
* (bluefox) add better backup
# 0.7.3 (2015-07-12)
* (bluefox) add flot to repository
* (bluefox) add chmodFile for adapter
* (bluefox) implement rm
* (bluefox) fix permissions problem
* (bluefox) set permission by creation of file
* (bluefox) make possible upload of subtree
* (bluefox) fix user name
* (bluefox) fix update function
# 0.7.2 (2015-06-29)
* (bluefox) remove _failCounter from adapter list
* (bluefox) update license
# 0.7.1 (2015-06-28)
* (bluefox) support of permissions
* (bluefox) fix backup
* (bluefox) fix error with noFileCache
* (bluefox) add unsubscribeForeignObjects to adapters
* (bluefox) add icon sets to repository
* (bluefox) implement list: objets, states, instances, adapters
* (bluefox) support of "list hosts"
* (bluefox) verify version by install and start
* (bluefox) fix error with dependencies
* (bluefox) fix delete of logs
* (bluefox) fix getPort function in adapter.
# 0.7.0 (2015-05-07)
* (bluefox) support of permissions
# 0.6.6 (2015-05-07)
* (bluefox) implement daily rolling files.
* (bluefox) fix addChannelToEnum, deleteChannelFromEnum, deleteChannel if no device name
* (bluefox) fix "state set", "message" console commands
* (bluefox) fix errors in adapter.js
# 0.6.5 (2015-04-27)
* (bluefox) add default certificates
# 0.6.4 (2015-04-17)
* (bluefox) optimize install call
* (bluefox) backup/restore
# 0.6.3 (2015-04-16)
* (bluefox) use system npm for update and install
* (bluefox) generate uuid as hash of MAC
* (bluefox) delete empty adaptors from repository
# 0.6.0 (2015-03-22)
* (bluefox) try to implement backup/restore
* (bluefox) support of "--install" flag for sayIt
* (bluefox) add megad to repository
* (bluefox) enable subscribeStates() same as subscribeStates('*')
* (bluefox) replace "slient" mode with "install" mode
# 0.5.14 (2015-03-11)
* (bluefox) enable silent mode
# 0.5.14 (2015-03-08)
* (bluefox) update utils.js (silent mode)
* (bluefox) fix error by setup.js
# 0.5.12 (2015-03-07)
* (bluefox) fix error with sendTo('email')
* (bluefox) increase timeout for npm to 5000 ms
# 0.5.11 (2015-02-26)
* (bluefox) fix function deleteDevice in adapter.js
# 0.5.10 (2015-02-26)
* (bluefox) do not start more times the scheduled task after a long sleep
# 0.5.9 (2015-02-21)
* (bluefox) fix error with trimFifo (used for history adapter)
* (bluefox) use system "npm" by updating of js-controller
# 0.5.8 (2015-02-18)
* (bluefox) add start/stop/restart adapter from console.
* (bluefox) better wakeup of adapters.
# 0.5.7 (2015-01-14)
* (bluefox) add sayit adapter
* (bluefox) fix clear of log file
# 0.5.4 (2015-01-27)
* (bluefox) fix restart under windows
# 0.5.5 (2015-01-30)
* (bluefox) add yr as npm
* (bluefox) extend adapter.js with formatDate
# 0.5.6 (2015-02-06)
* (bluefox) add simple-api
# 0.5.3 (2015-01-27)
* (bluefox) fix log for restart
# 0.5.2 (2015-01-27)
* (bluefox) remove node-windows from dependencies
# 0.5.1 (2015-01-26)
* (bluefox) fix log
* (bluefox) show npm version and not git version
* (bluefox) use npm packet to install and not the exec npm
# 0.5.0 (2015-01-23)
* (bluefox) make it possible to install yunkong2 with "npm install yunkong2"
# 0.4.6 (2015-01-21)
* (bluefox) add developing flag "noFileCache" to do not cache web files.
* (bluefox) improve "adapter.getPort" on windows.
* (bluefox) create yunkong2.sh with 0777 by install
# 0.4.5 (2015-01-20)
* (bluefox) fix problem with no objects after "setup" started
# 0.4.4 (2015-01-20)
* (bluefox) move "data" directory by "npm install" to "../../yunkong2-data"
# 0.4.3 (2015-01-18)
* (bluefox) restart objects socket if some exception occurs
# 0.4.2 (2015-01-14)
* (bluefox) fix error in objectsInMemClient and objectsInMemServer
# 0.4.1 (2015-01-10)
* (bluefox) fix first setup
# 0.4.0 (2015-01-10)
* (bluefox) support of multiple hosts
# 0.3.17 (2015-01-10)
* (bluefox) fix problem with "hosts are not shown in admin"
# 0.3.16 (2015-01-09)
* (bluefox) support of multiple hosts
# 0.3.15 (2015-01-09)
* (bluefox) "chmod 777 * -R /opt/yunkong2" => "chmod 777 -R /opt/yunkong2"
# 0.3.14 (2015-01-09)
* (bluefox) fix error update of js-controller under linux/osx
# 0.3.13 (2015-01-08)
* (bluefox) fix error with publish/subscribe
# 0.3.12 (2015-01-07)
* (bluefox) support of "onlyWWW" flag
# 0.3.11 (2015-01-06)
* (bluefox) fix error if state is null or undefined
* (bluefox) store fifos from states in file
# 0.3.10 (2015-01-06)
* (bluefox) support of file manager in vis
# 0.3.9 (2015-01-04)
* (bluefox) try to fix update of controller
# 0.3.8 (2015-01-04)
* (bluefox) fix error with subscribes
* (bluefox) fix error with extendObject
* (bluefox) fix error with delete adapter
* (bluefox) fix error in deleteChannelFromEnum
# 0.3.7 (2015-01-03)
* (bluefox) fix upload problem
# 0.3.6 (2015-01-03)
* (bluefox) fix package.json
# 0.3.1 (2015-01-02)
* (bluefox) enable npm install
# 0.3.0 (2014-12-28)
* (bluefox) no redis any more
# 0.2.9 (2014-12-20)
* (bluefox) fix problem with restart controller
* (bluefox) check flag supportStopInstance before send signal to adapter
# 0.2.8 (2014-12-20)
* (bluefox) fix problem with upgrade adapter
# 0.2.7 (2014-12-19)
* (bluefox) fix problem with upload adapter
# 0.2.6 (2014-12-19)
* (bluefox) implement getConfigKeys in redis.
* (bluefox) new running mode: "once"
# 0.2.5 (2014-12-14)
* (bluefox) enable start of "no-daemon" adapters like "rickshaw" or "vis".
# 0.2.4 (2014-12-10)
* (bluefox) fix delObject function
* (bluefox) remove unused log message
# 0.2.3 (2014-12-08)
* (bluefox) optimize start/stop/restart.
# 0.2.2 (2014-12-06)
* (bluefox) fix error in redis.
# 0.2.1 (2014-12-06)
* (bluefox) fix error in redis.
# 0.2.0 (2014-12-04)
* (bluefox) remove couchDB and store everything in redis.
# 0.1.6 (2014-11-29)
* (bluefox) use npm to install some adapters.
# 0.1.5 (2014-11-26)
* (bluefox) fix log in controller.js one more time
# 0.1.4 (2014-11-26)
* (bluefox) fix log in controller.js
# 0.1.3 (2014-11-24)
* (bluefox) fix some errors and add restart.js
# 0.1.2 (2014-11-24)
* (bluefox) fix messageboxes
# 0.1.1 (2014-11-23)
* (bluefox) fix log output in admin.
# 0.1.0 (2014-11-22)
* (bluefox) new naming concept. No children and parents set extra.
# 0.0.37 (2014-11-16)
* (bluefox) fix adapter.js
* (bluefox) call "chmod +x yunkong2" after updgrade of controller
# 0.0.36 (2014-11-15)
* (bluefox) fix adapter.js
# 0.0.35 (2014-11-09)
* (bluefox) add logging to controller
# 0.0.34 (2014-11-08)
* (bluefox) create restore/backup (from console)
# 0.0.33 (2014-11-04)
* (bluefox) support of node-red as adapter and defined exit codes for errors
# 0.0.32 (2014-11-04)
* (bluefox) support of node-red as adapter
# 0.0.31 (2014-11-02)
* (bluefox) fix error with binary states
# 0.0.30 (2014-11-01)
* (bluefox) fix error in "support of listDevices for configuration"
# 0.0.29 (2014-10-30)
* (bluefox) (bluefox) fix creatChannel for adapter
# 0.0.28 (2014-10-30)
* (bluefox) support of listDevices for configuration
# 0.0.27 (2014-10-30)
* (bluefox) check common.os (e.g. to install adapter only on linux)
* (bluefox) support of common.install adapter settings.
# 0.0.26 (2014-10-25)
* (bluefox) change state names to 'io.*'
# 0.0.25 (2014-10-24)
* (bluefox) show version in log
# 0.0.24 (2014-10-22)
* (bluefox) fix dependencies of packets
# 0.0.22 (2014-10-20)
* (bluefox) fix error in adapter.js
# 0.0.21 (2014-10-19)
* (bluefox) store repository in the DB
# 0.0.20 (2014-10-19)
* (bluefox) change example adapter for emitEvent
* (bluefox) support of certificates
* (bluefox) fix names for states
# 0.0.19 (2014-10-02)
* (bluefox) fix add/delete adapter
* (bluefox) fill source-dist.json with grunt
* (bluefox) call "npm install" after adapter updated
# 0.0.18 (2014-09-27)
* (bluefox) new concept of updates and repositories
# 0.0.17 (2014-09-04)
* (hobbyquaker) trimFifo calls callback with trimmed data
* (hobbyquaker) fix instance restart
# 0.0.16 (2014-08-22)
* (hobbyquaker) admin-ui: enums
* (hobbyquaker) admin-ui: ...
* (hobbyquaker) fixes
# 0.0.15 (2014-08-17)
* (hobbyquaker) admin-ui: adapter-settings
* (hobbyquaker) admin-ui: add instance
* (hobbyquaker) admin-ui: cmd execution
# 0.0.14 (2014-08-11)
* (bluefox) adapter admin: https
* (bluefox) adapter admin: auth
* (bluefox) admin-ui: user and group management
* (hobbyquaker) fixes
* (hobbyquaker) added adapter cul to sources-dist.json
# 0.0.13 (2014-07-31)
* (hobbyquaker) new object types user and group
* (hobbyquaker) yunkong2 setup: create user and group admin. Default password: yunkong2
# 0.0.12
* (hobbyquaker) setup.js fixes
* (hobbyquaker) setup.js create multiple system objects
# 0.0.11
* (hobbyquaker) admin ui: instances
# 0.0.10
* (hobbyquaker) refactoring controller.js and setup.js
* (hobbyquaker) yunkong2.js command line options
* (hobbyquaker) yunkong2 with shebang (needs chmod +x)
* (hobbyquaker) added dbdump.js
* (hobbyquaker) fixes and other stuff...
# 0.0.9
* (hobbyquaker) Javascript Script Engine
* (bluefox) Gruntfile.js
* (bluefox) SCHEMA.md
# 0.0.8
* (hobbyquaker) ctrl: instance mode schedule
* (hobbyquaker) yunkong2.js add: set instanceObjects (new attribute in io-package.json)
* (hobbyquaker) added meta attribute to sources.json
* (hobbyquaker) added adapter yr to sources.json
# 0.0.7
* (hobbyquaker) fix Admin UI - handle IDs with spaces
# 0.0.6
* (hobbyquaker) download adapters via ```yunkong2.js add <adapter-name>``` (has to be defined in conf/sources.json)
* (hobbyquaker) automatically install node dependencies on ```yunkong2.js add```
* (hobbyquaker) restructuring
* (hobbyquaker) history adapter
* (hobbyquaker) renamed adapter web to admin (this adapters purpose is to do only the admin-ui)
* (hobbyquaker) renamed adapter legacy to web (this adapter should provide a ccu.io-like webserver for easy porting of dashui, scriptgui, yahui, ...)
* (hobbyquaker) renamed adapter dummy to example
# 0.0.5
* (hobbyquaker) hm-rpc Adapter checks Datapoint-Type and warns if readonly
* (hobbyquaker) Admin-UI - gridStates update on stateChange
# 0.0.4
* (hobbyquaker) hm-rega Adapter
* (hobbyquaker) ctrl restarts crashed adapters automatically
# 0.0.3
* (hobbyquaker) Adapter web
* (hobbyquaker) Admin UI
# 0.0.2
* (hobbyquaker) Installation/instancing of adapters via ```yunkong2.js add```
* (hobbyquaker) Adapter command line param instead of IPC
* (hobbyquaker) Config-file yunkong2.json
# 0.0.1
* (hobbyquaker) first release

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014-2018 bluefox<dogafox@gmail.com>,
Copyright (c) 2014 hobbyquaker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

135
README.md Normal file
View File

@@ -0,0 +1,135 @@
![Logo](lib/img/yunkong2.png)
# yunkong2.js-controller
==================
[![NPM version](http://img.shields.io/npm/v/yunkong2.js-controller.svg)](https://www.npmjs.com/package/yunkong2.js-controller)
[![Downloads](https://img.shields.io/npm/dm/yunkong2.js-controller.svg)](https://www.npmjs.com/package/yunkong2.js-controller)
[![Tests](http://img.shields.io/travis/yunkong2/yunkong2.js-controller/master.svg)](https://travis-ci.org/yunkong2/yunkong2.js-controller)
[![NPM](https://nodei.co/npm/yunkong2.js-controller.png?downloads=true)](https://nodei.co/npm/yunkong2.js-controller/)
Here you can find change [log](CHANGELOG.md).
This is a main controller, that starts all other yunkong2 adapters.
Official Web-Site: http://yunkong2.net
Forum: http://forum.yunkong2.net
Trello: https://trello.com/b/q0SZKdfW/yunkong2-whiteboard
yunkong2 wiki: https://github.com/yunkong2/yunkong2/wiki/Home-(English)
Explanation of the concept: https://github.com/yunkong2/yunkong2
----------------------------------------------------------------------
This is a Javascript/Node.js implementation of an yunkong2 controller.
## Manual installation of yunkong2.js-controller on Debian based Linux (Raspbian, Ubuntu, ...)
### Install Node.js
```
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install curl build-essential
sudo curl -sL https://deb.nodesource.com/setup_6.x | sudo bash -
sudo apt-get install -y nodejs
```
### Install yunkong2 on linux
```
sudo mkdir /opt/yunkong2
sudo cd /opt/yunkong2
sudo chmod 777 /opt/yunkong2
sudo npm install yunkong2 --unsafe-perm
```
After that the yunkong2 should run and be available in browser under ```http://<ip>:8081/```
### Start yunkong2 controller on linux
* run ```./yunkong2 start``` in the yunkong2 directory to start the yunkong2 controller in the background
* watch the logfile ```tail -f log/yunkong2.log```
or
* run ```node node_modules/yunkong2.js-controller/controller.js``` to start the yunkong2 controller in foreground and watch the log on console
### Install js-controller on windows
* Create and change to the directory under which you want to install yunkong2.
```mkdir C:/yunkong2```
```cd C:/yunkong2```
* install npm packet from created directory
```npm install yunkong2```
### Start yunkong2 controller on windows
* run ```yunkong2 start``` in the yunkong2 directory to start the yunkong2 controller in the background console
* check the logfile ```node_modules/yunkong2.js-controller/log/yunkong2.log```
or
* run ```node node_modules/yunkong2.js-controller/controller.js``` to start the yunkong2 controller in foreground and watch the log on console
### Admin UI
The admin adapter starts a web-server that hosts the Admin UI. Default port is 8081, so just open http://&lt;yunkong2&gt;:8081/
If port 8081 is occupied, you can install second Admin UI on alternate port and change port for first admin UI:
* run ```./yunkong2 add admin --enabled --port 8090``` and go to the http://&lt;yunkong2&gt;:8090/. Of course you can change port 8090 to other one.
## Using REDIS as States-DB
There is a possibility to use REDIS as states database. It is reasonable to do that for big installations or for systems with performance problems.
It is possible to switch anytime between REDIS and In-Memory Javascript DB. The only problem, that all states must be updated by adapters again (values will be lost).
Objects and configuration are not affected.
To install REDIS on linux/debuan just write: ```apt-get install redis-server``` .
If you plan to use mulithost installation you must allow connections to redis from any address (default only 127.0.0.1).
To do that edit file */etc/redis/redis.conf* (```sudo nano /etc/redis/redis.conf```) and replace ```bind 127.0.0.1``` with ```bind 0.0.0.0``` .
Don't forget to restart redis after that. (```sudo /etc/init.d/redis-server restart```)
To install on windows download latest release here [https://github.com/MSOpenTech/redis/releases](https://github.com/MSOpenTech/redis/releases).
To switch to REDIS write in the console following:
```
>yunkong2 stop
>yunkong2 setup custom
```
And then:
```
Type of objects DB [file, couch, redis], default [file]:
Host of objects DB(file), default[127.0.0.1]:
Port of objects DB(file), default[9001]:
Type of states DB [file, redis], default [file]: redis
Host of states DB (redis), default[127.0.0.1]:
Port of states DB (redis), default[6379]:
Data directory (file), default[../../../yunkong2-data/]:
Host name of this machine [FastPC]:
creating conf/yunkong2.json
```
Note that in fourth line it was entered **redis**.
Now yunkong2 can be started.
Of course redis must be first installed and firewall rules must be checked.
To switch back to JS States write the same commands again, just instead of **redis** in fourth line write nothing and press ENTER.
## License
The MIT License (MIT)
Copyright (c) 2014-2018 bluefox <dogafox@gmail.com>,
Copyright (c) 2014 hobbyquaker

3
_service_iobroker.bat Normal file
View File

@@ -0,0 +1,3 @@
echo Used for restart from js-controller itself. Not for user.
%WINDIR%\system32\net.exe stop yunkong2
%WINDIR%\system32\net.exe start yunkong2

15
conf/cert.crt Normal file
View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICSTCCAbICCQDwWQ5sMoq7ETANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJE
RTETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIaW9Ccm9rZXIxEDAOBgNV
BAMMB0JsdWVmb3gxIDAeBgkqhkiG9w0BCQEWEWRvZ2Fmb3hAZ21haWwuY29tMB4X
DTE2MDQyNTIxMjQwMVoXDTE3MDQyNTIxMjQwMVowaTELMAkGA1UEBhMCREUxEzAR
BgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCGlvQnJva2VyMRAwDgYDVQQDDAdC
bHVlZm94MSAwHgYJKoZIhvcNAQkBFhFkb2dhZm94QGdtYWlsLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEAxTR0JnD7ucvZ4CA92FAq9jVGP3gXHhCnRXBM
IGQY6dQzOdnf/SWBUq0Bh0DLy6KRubokF1YqdJkfea2cWhdJqOcHUwITNaXMU4Rn
goIWVZvT6C262pL5ffoEh6GGzVcX7/X4tHD2HOwm3opboVlktIZtVaVjzPD1+q5H
c+a/TiMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBVhdIg59lHKtdpv5O0icvqD4f0
tbqMvhWJ/7fhzr1fdjb5OK74g2G90KMhYnzOk0aZu4pgEoXHugpBLb+ndxJnG41p
IYe2qg4tp6AjR/uFswdrBLRUhW63yls3FiTEJjKCrGNEdjZoqsTEfwhXab3EoT7t
Wu+st1V0yiHlsvRGTg==
-----END CERTIFICATE-----

15
conf/cert.key Normal file
View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDFNHQmcPu5y9ngID3YUCr2NUY/eBceEKdFcEwgZBjp1DM52d/9
JYFSrQGHQMvLopG5uiQXVip0mR95rZxaF0mo5wdTAhM1pcxThGeCghZVm9PoLbra
kvl9+gSHoYbNVxfv9fi0cPYc7CbeiluhWWS0hm1VpWPM8PX6rkdz5r9OIwIDAQAB
AoGBAJVSWoChHHpa+ObUgv+/9Efpnv+AF0EUqxPRLFN6d8LWgtNTPl+YfovzpCyd
y7KtrlpLr/hbrloLd+HSq4ksCQEfJ7Le/4fjc2lt3Ib/K9qSr3bnmIWAK00VU+fF
mN1NTFJTV0O2+ctCOY9ZRwue5ehTp9eqPjsGwdeldii1WbSBAkEA6Z0YjMg+04z1
M8FEUWSdPf6AHWB45hDJ+qPuIDNZxvVOcEsTyRsfkb1PKZm2NDx6mBN16po13Vka
QPy35ApoOwJBANgaMdbig76A1tvyhtklJPTU0g0N7CzXy+PNu8B3YghY8dYF/gSv
cBr0d8xGaZEczGQ35C0Tb9gTadHL64kxuzkCQHYaQYsKwRhaLqxXjJ5Ja2UoAMTZ
PMWyvynDLmOBEmYPJfSHQB1vZOpc9mRlnUOTP7caP4a3J3wby7YHDUBwMnkCQHGx
1mbn5chkoKY3gxrboAXvslOL76XoIy1HIHCyXrFlmlav8GUmqCSGWkDvCrt+G0re
3P2aLE3SaOooD1OvBoECQQDXMxPNYVGIErO7hxp9T9BXKcbnQV/mNhJYdl9VUoVB
gcVGatR1dBZX31Yt+HY4/ym9YdQ8MGCg2Kfmm0haLakP
-----END RSA PRIVATE KEY-----

96
conf/iobroker-dist.json Normal file
View File

@@ -0,0 +1,96 @@
{
"system": {
"memoryLimitMB": 0,
"hostname": "",
"statisticsInterval": 15000,
"statisticsIntervalComment": "Interval how often the counters for input/output in adapters and controller will be updated"
},
"multihostService": {
"enabled": false,
"secure": true
},
"network": {
"IPv4": true,
"IPv6": true,
"bindAddress": null
},
"objects" : {
"type": "file",
"typeComment": "Possible values: 'file' - [port 9001], redis - [port 6379], couch - [port 5984].",
"host": "127.0.0.1",
"port": 9001,
"user": "",
"pass": "",
"noFileCache": false,
"connectTimeout": 2000,
"backup": {
"disabled": false,
"files": 24,
"filesComment": "Minimal number of backup files, after the deletion will be executed according to backupTime settings",
"hours": 48,
"hoursComment": "All backups older than 48 hours will be deleted. But only if the number of files is greater than of backupNumber",
"period": 120,
"periodComment": "by default backup every 2 hours. Time is in minutes. To disable backup set the value to 0",
"path": "",
"pathComment": "Absolute path to backup directory or empty to backup in data directory"
}
},
"states" : {
"type": "file",
"typeComment": "Possible values: 'file' - [port 9000], 'redis' - [port 6379].",
"host": "127.0.0.1",
"port": 9000,
"maxQueue": 1000,
"options": {
"auth_pass" : null,
"retry_max_delay" : 15000
},
"backup": {
"disabled": false,
"files": 24,
"filesComment": "Minimal number of backup files, after the deletion will be executed according to backupTime settings",
"hours": 48,
"hoursComment": "All backups older than 48 hours will be deleted. But only if the number of files is greater than of backupNumber",
"period": 120,
"periodComment": "by default backup every 2 hours. Time is in minutes. To disable backup set the value to 0",
"path": "",
"pathComment": "Absolute path to backup directory or empty to backup in data directory"
}
},
"log": {
"level": "info",
"maxDays": 7,
"noStdout": true,
"transport": {
"file1": {
"type": "file",
"enabled": true,
"filename": "log/yunkong2",
"fileext": ".log",
"maxsize": null,
"maxFiles": null
},
"syslog1": {
"type": "syslog",
"enabled": false,
"host": "localhost",
"host_comment": "The host running syslogd, defaults to localhost.",
"port_comment": "The port on the host that syslog is running on, defaults to syslogd's default port(514/UDP).",
"protocol": "udp4",
"protocol_comment": "The network protocol to log over (e.g. tcp4, udp4, unix, unix-connect, etc).",
"path_comment": "The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).",
"facility_comment": "Syslog facility to use (Default: local0).",
"localhost": "yunkong2",
"localhost_comment": "Host to indicate that log messages are coming from (Default: localhost).",
"sysLogType_comment": "The type of the syslog protocol to use (Default: BSD).",
"app_name_comment": "The name of the application (Default: process.title).",
"eol_comment": "The end of line character to be added to the end of the message (Default: Message without modifications)."
}
}
},
"dataDirComment": "Always relative to yunkong2.js-controller/"
}

1213
conf/sources-dist.json Normal file

File diff suppressed because it is too large Load Diff

2624
controller.js Normal file

File diff suppressed because it is too large Load Diff

495
io-package.json Normal file
View File

@@ -0,0 +1,495 @@
{
"common": {
"name": "js-controller",
"version": "1.4.2",
"platform": "Javascript/Node.js",
"controller": true,
"title": "JS controller",
"titleLang": {
"en": "JS controller",
"de": "JS-Controller",
"ru": "Контроллер JS",
"pt": "Controlador JS",
"nl": "JS-controller",
"fr": "Contrôleur JS",
"it": "Controller JS",
"es": "Controlador JS",
"pl": "Kontroler JS"
},
"news": {
"1.4.2": {
"en": "see CHANGELOG.md",
"de": "Sehe CHANGELOG.md",
"ru": "см. CHANGELOG.md",
"pt": "veja CHANGELOG.md",
"nl": "zie CHANGELOG.md",
"fr": "voir CHANGELOG.md",
"it": "vedi CHANGELOG.md",
"es": "ver CHANGELOG.md",
"pl": "zobacz CHANGELOG.md"
},
"1.4.0": {
"en": "see CHANGELOG.md",
"de": "Sehe CHANGELOG.md",
"ru": "см. CHANGELOG.md",
"pt": "veja CHANGELOG.md",
"nl": "zie CHANGELOG.md",
"fr": "voir CHANGELOG.md",
"it": "vedi CHANGELOG.md",
"es": "ver CHANGELOG.md",
"pl": "zobacz CHANGELOG.md"
},
"1.3.0": {
"en": "socket.io Version was downgraded because of bug\nBetter npm5 support",
"de": "socket.io Die Version wurde aufgrund eines Fehlers heruntergestuft\nBessere npm5-Unterstützung",
"ru": "Версия socket.io была понижена из-за ошибки\nЛучшая поддержка npm5",
"pt": "A versão socket.io foi rebaixada por causa do erro\nMelhor suporte npm5",
"nl": "socket.io De versie is gedowngraded vanwege een bug\nBetere npm5-ondersteuning",
"fr": "La version de socket.io a été déclassée à cause d'un bug\nMeilleur support npm5",
"it": "socket.io La versione è stata declassata a causa di un bug\nMigliore supporto per npm5",
"es": "La versión de socket.io se ha degradado debido a errores\nMejor soporte npm5",
"pl": "Wersja socket.io została obniżona z powodu błędu\nLepsza obsługa npm5"
},
"1.2.7": {
"en": "see CHANGELOG.md",
"de": "Sehe CHANGELOG.md",
"ru": "см. CHANGELOG.md",
"pt": "veja CHANGELOG.md",
"nl": "zie CHANGELOG.md",
"fr": "voir CHANGELOG.md",
"it": "vedi CHANGELOG.md",
"es": "ver CHANGELOG.md",
"pl": "zobacz CHANGELOG.md"
},
"1.2.4": {
"en": "The fix for npm5",
"de": "Der Fix für npm5",
"ru": "Исправление для npm5",
"pt": "A correção para npm5",
"fr": "Le correctif pour npm5",
"nl": "De oplossing voor npm5"
},
"1.2.3": {
"en": "fix logging level: silly\nfix dependency check\nfix small errors\nsee changelog",
"de": "Korrigiere Log-Silly\nKorrigiere Abhängigkeitsprüfung\nKorrigiere kleine Fehler\nSchaue Changelog",
"ru": "Поправлен лог silly\nИсправлена проверка зависимостей пакетов\nИсправлены маленькие ошибки\nСм. список изменений"
},
"1.2.0": {
"en": "fixed upgrade command\nallow install from custom repositories\nremove online and sources repositories\nfix multihosts command\ncatch the error outputs of instances if they die\nno more support for node.js 0.10/0.12\nadd new logging level: silly",
"de": "fixed upgrade command\nallow install from custom repositories\nremove online and sources repositories\nfix multihosts command\ncatch the error outputs of instances if they die\nno more support for node.js 0.10/0.12\nadd new logging level: silly",
"ru": "fixed upgrade command\nallow install from custom repositories\nremove online and sources repositories\nfix multihosts command\ncatch the error outputs of instances if they die\nno more support for node.js 0.10/0.12\nadd new logging level: silly"
},
"1.1.3": {
"en": "Extend statistics\nUpdate npm packets",
"de": "Statistik erweitern\nnpm Pakete aktualisieren",
"ru": "Расширение статистики\nОбновление пакетов npm"
},
"1.1.2": {
"en": "Close sockets by default for external connects",
"de": "Erlaube die Verbindung auf Objeke/States-Sockets nicht",
"ru": "По умолчанию закрыто соединение с сокетами для внешних подключений"
},
"1.1.1": {
"en": "Allow redis connections via unix sockets",
"de": "Erlaube Redis verbindungen via Unix Sockets",
"ru": "Allow redis unix socket connections"
},
"1.1.0": {
"en": "Close sockets by default for external connects",
"de": "Erlaube die Verbindung auf Objeke/States-Sockets nicht",
"ru": "По умолчанию закрыто соединение с сокетами для внешних подключений"
},
"1.0.3": {
"en": "see CHANGELOG.md",
"de": "Sehe CHANGELOG.md",
"ru": "Смотри CHANGELOG.md"
},
"1.0.0": {
"en": "No big changes, just version",
"de": "Keine große Änderungen, nur Versionsänderung",
"ru": "Никаких больших изменений. Только номер версии."
}
},
"desc": {
"en": "Javascript/Node.js implementation of yunkong2 controller",
"de": "Javascript/Node.js Implementierung des yunkong2-Controllers",
"ru": "Javascript/Node.js реализация контроллера yunkong2",
"pt": "Implementação do Javascript/Node.js do controlador yunkong2",
"nl": "Javascript/Node.js implementatie van yunkong2-controller",
"fr": "Implémentation Javascript/Node.js du contrôleur yunkong2",
"it": "Implementazione Javascript/Node.js del controller yunkong2",
"es": "Implementación de JavaScript/Node.js del controlador yunkong2",
"pl": "Implementacja JavaScript/Node.js kontrolera yunkong2"
},
"messagebox": true,
"readme": "https://github.com/yunkong2/yunkong2.js-controller/blob/master/README.md",
"authors": [
"bluefox <bluefox@gmail.com>",
"hobbyquaker <hq@ccu.io>"
],
"license": "MIT",
"type": "general",
"unsafePerm": true
},
"objects": [
{
"_id": "_design/system",
"language": "javascript",
"common": {
"dontDelete": true
},
"views": {
"host": {
"map": "function(doc) { if (doc.type === 'host') emit(doc._id, doc) }"
},
"adapter": {
"map": "function(doc) { if (doc.type === 'adapter') emit(doc._id, doc) }"
},
"instance": {
"map": "function(doc) { if (doc.type === 'instance') emit(doc._id, doc) }"
},
"instanceStats": {
"map": "function(doc) { if (doc.type === 'instance') emit(doc._id, parseInt(doc._id.split('.').pop(), 10)) }",
"reduce": "_stats"
},
"meta": {
"map": "function(doc) { if (doc.type === 'meta') emit(doc._id, doc) }"
},
"device": {
"map": "function(doc) { if (doc.type === 'device') emit(doc._id, doc) }"
},
"channel": {
"map": "function(doc) { if (doc.type === 'channel') emit(doc._id, doc) }"
},
"state": {
"map": "function(doc) { if (doc.type === 'state') emit(doc._id, doc) }"
},
"enum": {
"map": "function(doc) { if (doc.type === 'enum') emit(doc._id, doc) }"
},
"script": {
"map": "function(doc) { if (doc.type === 'script') emit(doc._id, doc) }"
},
"group": {
"map": "function(doc) { if (doc.type === 'group') emit(doc.common.name, doc) }"
},
"user": {
"map": "function(doc) { if (doc.type === 'user') emit(doc.common.name, doc) }"
},
"config": {
"map": "function(doc) { if (doc.type === 'config') emit(doc.common.name, doc) }"
}
},
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"object": 1092
}
},
{
"_id": "system.group.administrator",
"type": "group",
"common": {
"name": {
"en": "Administrator",
"de": "Administrator",
"ru": "Администратор",
"pt": "Administrador",
"nl": "Beheerder",
"fr": "Administrateur",
"it": "Amministratore",
"es": "Administrador",
"pl": "Administrator"
},
"description": {
"en": "Can do everything with System",
"de": "Kann alles mit System machen",
"ru": "Может делать все с помощью системы",
"pt": "Pode fazer tudo com o sistema",
"nl": "Kan alles doen met System",
"fr": "Peut tout faire avec le système",
"it": "Può fare tutto con il sistema",
"es": "Puede hacer todo con System",
"pl": "Potrafi zrobić wszystko dzięki Systemowi"
},
"members": [
"system.user.admin"
],
"dontDelete": true,
"acl": {
"object": {
"list": true,
"read": true,
"write": true,
"delete": true
},
"state": {
"list": true,
"read": true,
"write": true,
"create": true,
"delete": true
},
"users": {
"list": true,
"read": true,
"write": true,
"create": true,
"delete": true
},
"other": {
"execute": true,
"http": true,
"sendto": true
},
"file": {
"list": true,
"read": true,
"write": true,
"create": true,
"delete": true
}
}
},
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"object": 1604
}
},
{
"_id": "system.group.user",
"type": "group",
"common": {
"name": {
"en": "User",
"de": "Benutzer",
"ru": "Пользователь",
"pt": "Do utilizador",
"nl": "Gebruiker",
"fr": "Utilisateur",
"it": "Utente",
"es": "Usuario",
"pl": "Użytkownik"
},
"description": {
"en": "Cannot modify everything",
"de": "Kann nicht alles ändern",
"ru": "Не может изменять все",
"pt": "Não é possível modificar tudo",
"nl": "Kan niet alles wijzigen",
"fr": "Impossible de tout modifier",
"it": "Non è possibile modificare tutto",
"es": "No se puede modificar todo",
"pl": "Nie można modyfikować wszystkiego"
},
"members": [],
"dontDelete": true,
"url": "https://github.com/yunkong2/yunkong2.js-controller/archive/master.zip",
"meta": "https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/io-package.json",
"acl": {
"object": {
"list": true,
"read": true,
"write": false,
"delete": false
},
"state": {
"list": true,
"read": true,
"write": true,
"create": true,
"delete": false
},
"users": {
"list": true,
"read": true,
"write": false,
"create": false,
"delete": false
},
"other": {
"execute": false,
"http": true,
"sendto": false
},
"file": {
"list": true,
"read": true,
"write": false,
"create": false,
"delete": false
}
}
},
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"object": 1604
}
},
{
"_id": "enum.rooms",
"common": {
"icon": "home",
"name": {
"en": "Rooms",
"de": "Räume",
"ru": "Комнаты",
"pt": "Quartos",
"nl": "Kamers",
"fr": "Pièces",
"it": "Camere",
"es": "Habitaciones",
"pl": "Pokoje"
},
"desc": {
"en": "List of the rooms",
"de": "Liste der Räumen",
"ru": "Список комнат",
"pt": "Lista dos quartos",
"nl": "Lijst met kamers",
"fr": "Liste des chambres",
"it": "Elenco delle stanze",
"es": "Lista de las habitaciones",
"pl": "Lista pokoi"
},
"members": [],
"dontDelete": true
},
"type": "enum",
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"permissions": 1911
}
},
{
"_id": "enum.functions",
"common": {
"icon": "lightbulb_outline",
"name": {
"en": "Functions",
"de": "Funktionen",
"ru": "функции",
"pt": "Funções",
"nl": "functies",
"fr": "Les fonctions",
"it": "funzioni",
"es": "Funciones",
"pl": "Funkcje"
},
"desc": {
"en": "List of the functions",
"de": "Liste der Funktionen",
"ru": "Список функций",
"pt": "Lista das funções",
"nl": "Lijst met functies",
"fr": "Liste des fonctions",
"it": "Elenco delle funzioni",
"es": "Lista de las funciones",
"pl": "Lista funkcji"
},
"members": [],
"dontDelete": true
},
"type": "enum",
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"permissions": 1911
}
},
{
"_id": "system.config",
"type": "config",
"common": {
"name": "System configuration",
"city": "",
"country": "",
"longitude": "",
"latitude": "",
"language": "",
"tempUnit": "°C",
"currency": "",
"dontDelete": true,
"dateFormat": "DD.MM.YYYY",
"isFloatComma": true,
"licenseConfirmed": false,
"defaultHistory": "",
"activeRepo": "default",
"diag": "extended",
"tabs": [
"tab-intro",
"tab-adapters",
"tab-instances",
"tab-objects",
"tab-log",
"tab-scenes",
"tab-javascript",
"tab-text2command-0",
"tab-node-red-0"
]
},
"native": {},
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"permissions": 1604
}
},
{
"_id": "system.repositories",
"type": "config",
"common": {
"name": "System repositories",
"dontDelete": true
},
"native": {
"repositories": {
"default": {
"link": "http://download.yunkong2.net/sources-dist.json",
"json": null
},
"latest": {
"link": "http://download.yunkong2.net/sources-dist-latest.json",
"json": null
}
},
"oldRepositories": {
"sources": {
"link": "conf/sources-dist.json",
"json": null
},
"online": {
"link": "https://raw.githubusercontent.com/yunkong2/yunkong2.repositories/master/sources-dist.json",
"json": null
}
}
},
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"permissions": 1604
}
},
{
"_id": "system.certificates",
"type": "config",
"common": {
"name": "System certificates"
},
"native": {
"certificates": {
"defaultPrivate": "-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDFNHQmcPu5y9ngID3YUCr2NUY/eBceEKdFcEwgZBjp1DM52d/9JYFSrQGHQMvLopG5uiQXVip0mR95rZxaF0mo5wdTAhM1pcxThGeCghZVm9PoLbrakvl9+gSHoYbNVxfv9fi0cPYc7CbeiluhWWS0hm1VpWPM8PX6rkdz5r9OIwIDAQABAoGBAJVSWoChHHpa+ObUgv+/9Efpnv+AF0EUqxPRLFN6d8LWgtNTPl+YfovzpCydy7KtrlpLr/hbrloLd+HSq4ksCQEfJ7Le/4fjc2lt3Ib/K9qSr3bnmIWAK00VU+fFmN1NTFJTV0O2+ctCOY9ZRwue5ehTp9eqPjsGwdeldii1WbSBAkEA6Z0YjMg+04z1M8FEUWSdPf6AHWB45hDJ+qPuIDNZxvVOcEsTyRsfkb1PKZm2NDx6mBN16po13VkaQPy35ApoOwJBANgaMdbig76A1tvyhtklJPTU0g0N7CzXy+PNu8B3YghY8dYF/gSvcBr0d8xGaZEczGQ35C0Tb9gTadHL64kxuzkCQHYaQYsKwRhaLqxXjJ5Ja2UoAMTZPMWyvynDLmOBEmYPJfSHQB1vZOpc9mRlnUOTP7caP4a3J3wby7YHDUBwMnkCQHGx1mbn5chkoKY3gxrboAXvslOL76XoIy1HIHCyXrFlmlav8GUmqCSGWkDvCrt+G0re3P2aLE3SaOooD1OvBoECQQDXMxPNYVGIErO7hxp9T9BXKcbnQV/mNhJYdl9VUoVBgcVGatR1dBZX31Yt+HY4/ym9YdQ8MGCg2Kfmm0haLakP\r\n-----END RSA PRIVATE KEY-----\r\n",
"defaultPublic": "-----BEGIN CERTIFICATE-----\r\nMIICSTCCAbICCQDwWQ5sMoq7ETANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJERTETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIaW9Ccm9rZXIxEDAOBgNVBAMMB0JsdWVmb3gxIDAeBgkqhkiG9w0BCQEWEWRvZ2Fmb3hAZ21haWwuY29tMB4XDTE2MDQyNTIxMjQwMVoXDTE3MDQyNTIxMjQwMVowaTELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCGlvQnJva2VyMRAwDgYDVQQDDAdCbHVlZm94MSAwHgYJKoZIhvcNAQkBFhFkb2dhZm94QGdtYWlsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxTR0JnD7ucvZ4CA92FAq9jVGP3gXHhCnRXBMIGQY6dQzOdnf/SWBUq0Bh0DLy6KRubokF1YqdJkfea2cWhdJqOcHUwITNaXMU4RngoIWVZvT6C262pL5ffoEh6GGzVcX7/X4tHD2HOwm3opboVlktIZtVaVjzPD1+q5Hc+a/TiMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBVhdIg59lHKtdpv5O0icvqD4f0tbqMvhWJ/7fhzr1fdjb5OK74g2G90KMhYnzOk0aZu4pgEoXHugpBLb+ndxJnG41pIYe2qg4tp6AjR/uFswdrBLRUhW63yls3FiTEJjKCrGNEdjZoqsTEfwhXab3EoT7tWu+st1V0yiHlsvRGTg==\r\n-----END CERTIFICATE-----\r\n"
}
},
"acl": {
"owner": "system.user.admin",
"ownerGroup": "system.group.administrator",
"permissions": 1536
}
}
]
}

2
iobroker Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
require(__dirname + '/lib/setup.js');

1
iobroker.bat Normal file
View File

@@ -0,0 +1 @@
node yunkong2.js %1 %2 %3 %4 %5 %6 %7 %8

2
killall.sh Normal file
View File

@@ -0,0 +1,2 @@
sudo pgrep -f '^io.*' | sudo xargs kill -9
sudo pgrep -f '^node-red*' | sudo xargs kill -9

5102
lib/adapter.js Normal file

File diff suppressed because it is too large Load Diff

78
lib/dbdump.js Normal file
View File

@@ -0,0 +1,78 @@
/**
*
* dpdump.js
*
* Utility to get JSON Database Dumps
*
* 7'2014 hobbyquaker <hq@ccu.io>
*
*/
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
'use strict';
var yargs = require('yargs')
.usage('$0 [-d design] [-s search] [-p pattern]')
.example('$0 -s state', 'get all objects with type=state')
.example('$0 -s state -p hue.*', 'get all objects from the adapter hue with type=state')
// Todo .example('$0 -p system.*', 'get all objects with _id=system.*')
.alias('s', 'search')
.alias('d', 'design')
.default('design', 'system')
;
if (!yargs.argv.s && !yargs.argv.p) {
yargs.showHelp();
process.exit(0);
}
var Objects = require(__dirname + '/objects.js');
var db = new Objects({
logger: {
silly: function (msg) { },
debug: function (msg) { },
info: function (msg) { },
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: function () {
if (yargs.argv.search) {
db.getObjectView(yargs.argv.design, yargs.argv.search, {}, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
showResult(res);
});
} else {
db.getObjectList({include_docs: true}, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
showResult(res);
});
}
}
});
function showResult(res) {
var outArr = [];
for (var i = 0; i < res.total_rows; i++) {
var obj = res.rows[i].value;
delete obj._rev;
outArr.push(obj);
}
console.log(JSON.stringify(outArr, null, ' '));
process.exit(0);
}

60
lib/defaultObjs.js Normal file
View File

@@ -0,0 +1,60 @@
'use strict';
function createDefaults(lang, temperature, currency) {
var defaults = {
"level.dimmer": {
"def": 0,
"type": "number",
"read": true,
"write": true,
"min": 0,
"max": 100,
"unit": "%"
},
"indicator.working": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true
},
"indicator.maintenance": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true
},
"indicator.maintenance.lowbat": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true,
"desc": "Low battery"
},
"indicator.maintenance.unreach": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true,
"desc": "Device unreachable"
},
"switch": {
"def": false,
"type": "boolean",
"read": true,
"write": true
}
};
return defaults;
}
module.exports = createDefaults;

BIN
lib/img/iobroker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

182
lib/letsencrypt.js Normal file
View File

@@ -0,0 +1,182 @@
'use strict';
function createServer(app, settings, certificates, leSettings, log) {
let server;
const leLog = function (debug, arg1, arg2, arg3) {
if (debug) {
const args = Array.prototype.slice.call(arguments);
args.shift(); // remove debug argument
// skip "no match"
if (args[0][0] === 'n' && args[0][1] === 'o') return;
log.info(arg1 + (arg2 || '') + (arg3 || ''));
}
};
if (settings.secure) {
if (leSettings && (!leSettings.email || !leSettings.email) && settings.leEnabled) {
log.error('Please specify the email address and domains to use Let\'s Encrypt certificates!');
}
if (leSettings && leSettings.email && leSettings.domains && settings.leEnabled) {
const tools = require(__dirname + '/tools');
const tls = require('tls');
const fs = require('fs');
const LE = require('greenlock');
let leDir;
let configPath = tools.getConfigFileName().replace(/\\/g, '/');
let parts = configPath.split('/');
parts.pop();
configPath = parts.join('/');
if (leSettings.path && (leSettings.path[0] === '/' || leSettings.path.match(/^[A-Za-z]:/))) {
leDir = leSettings.path;
} else {
leDir = configPath + '/' + (leSettings.path || 'letsencrypt');
}
// for lex outputs
if (!console.debug) console.debug = console.log;
if (!fs.existsSync(leDir)) fs.mkdirSync(leDir);
// prepare domains
if (typeof leSettings.domains === 'string') {
leSettings.domains = leSettings.domains.split(',');
for (let d = leSettings.domains.length - 1; d >= 0; d--) {
leSettings.domains[d] = leSettings.domains[d].trim();
if (!leSettings.domains[d]) leSettings.domainss.splice(d, 1);
}
}
let lex = LE.create({
debug: true,
configDir: leDir,
agreeTos: true,
store: require(__dirname + '/letsencryptStore.js').create({debug: true, log: leLog}),
server: 'https://acme-v01.api.letsencrypt.org/directory', //'staging',
email: leSettings.email,
approvedDomains: leSettings.domains,
log: leLog
});
if (settings.leUpdate) {
settings.lePort = parseInt(settings.lePort, 10) || 80;
// handles acme-challenge and redirects to https
// used for validation of requests like http://example.com/.well-known/acme-challenge/BLABALBAL
require('http').createServer(lex.middleware(function redirectHttps(req, res) {
res.setHeader('Location', 'https://' + req.headers.host + req.url);
res.statusCode = 302;
res.end('<!-- Hello Developer Person! Please use HTTPS instead -->');
})).listen(settings.lePort, function () {
log.info('LetsEncrypt challenge server is started on ' + settings.lePort);
});
}
let options = JSON.parse(JSON.stringify(certificates));
let defaultTls = tls.createSecureContext(certificates);
let hostTls;
let running;
options.SNICallback = function (hostname, cb) {
if (leSettings.domains.indexOf(hostname) !== -1) {
if (settings.leUpdate) {
if (running === true) {
cb(null, hostTls || defaultTls);
} else
if (running) {
running.push(cb);
} else {
running = [cb];
return lex.httpsOptions.SNICallback(hostname, function (err, tls) {
if (tls) log.debug('Got valid certificates from letsencrypt');
if (err) log.error('Cannot get certificates: ' + err);
lex.debug = false;
hostTls = tls;
for (let r = 0; r < running.length; r++) {
running[r](err, tls || defaultTls);
}
running = true;
});
}
} else {
if (!hostTls) {
// validate certificates
lex.check({domains: leSettings.domains}).then(function (certInfo) {
if (certInfo) {
hostTls = tls.createSecureContext({
key: certInfo.privkey || certInfo.key, // privkey.pem
cert: certInfo.fullchain || certInfo.cert, // fullchain.pem (cert.pem + '\n' + chain.pem)
ca: certInfo.ca
});
cb(null, hostTls);
} else {
log.error('No letsencrypt certificates found in "' + leDir + '"');
cb(null, defaultTls);
// do not register domain
/*
if (!running) {
running = [cb];
lex.letsencrypt.register({
domains: leSettings.domains,
email: leSettings.email,
agreeTos: true
}, function (err, certInfo) {
//log.debug("[LEX] '" + hostname + "' register completed", err && err.stack || null, certInfo);
if ((!err || !err.stack) && !certInfo) {
log.error((new Error('[LEX] SANITY FAIL: no error and yet no certs either')).stack);
}
if (certInfo) {
hostTls = tls.createSecureContext({
key: certInfo.privkey || certInfo.key, // privkey.pem
cert: certInfo.fullchain || certInfo.cert, // fullchain.pem (cert.pem + '\n' + chain.pem)
ca: certInfo.ca
});
for (var r = 0; r < running.length; r++) {
running[r](null, hostTls);
}
} else {
for (var r = 0; r < running.length; r++) {
running[r](null, defaultTls);
}
}
running = true;
});
} else {
if (running === true) {
cb(null, defaultTls);
} else {
running.push(cb);
}
}
*/
}
},
function (err) {
if (err) log.error(err);
cb(null, defaultTls);
});
} else {
cb(null, hostTls);
}
}
} else {
cb(null, defaultTls);
}
};
server = require('https').createServer(options, lex.middleware(app));
} else {
server = require('https').createServer(certificates, app);
}
} else {
server = require('http').createServer(app);
}
return server;
}
exports.createServer = createServer;

628
lib/letsencryptStore.js Normal file
View File

@@ -0,0 +1,628 @@
// Initially copied from https://github.com/Daplie/le-store-certbot
// but must be completely rewritten to support DB
'use strict';
var PromiseA = require('bluebird');
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
var path = require('path');
var fs = PromiseA.promisifyAll(require('fs'));
var sfs = require('safe-replace');
var log = function (debug) {
if (debug) {
var args = Array.prototype.slice.call(arguments);
args.shift();
args.unshift('[le-store-certbot]');
console.log.apply(console, args);
}
};
function writeRenewalConfig(args) {
var pyobj = args.pyobj;
pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0;
var pyconf = PromiseA.promisifyAll(require('pyconf'));
var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem');
var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem');
var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem');
var privkeyPath = args.privkeyPath || pyobj.privkey
//|| args.domainPrivateKeyPath || args.domainKeyPath || pyobj.keyPath
|| path.join(liveDir, 'privkey.pem');
log(args.debug, 'writeRenewalConfig privkeyPath', privkeyPath);
var updates = {
account: args.account.id
, configDir: args.configDir
, domains: args.domains
, email: args.email
, tos: args.agreeTos && true
// yes, it's an array. weird, right?
, webrootPath: args.webrootPath && [args.webrootPath] || []
, server: args.server || args.acmeDiscoveryUrl
, privkey: privkeyPath
, fullchain: fullchainPath
, cert: certPath
, chain: chainPath
, http01Port: args.http01Port
, keyPath: args.domainPrivateKeyPath || args.privkeyPath
, rsaKeySize: args.rsaKeySize
, checkpoints: pyobj.checkpoints
/* // TODO XXX what's the deal with these? they don't make sense
// are they just old junk? or do they have a meaning that I don't know about?
, fullchainPath: path.join(args.configDir, 'chain.pem')
, certPath: path.join(args.configDir, 'cert.pem')
, chainPath: path.join(args.configDir, 'chain.pem')
*/ // TODO XXX end
, workDir: args.workDir
, logsDir: args.logsDir
};
// final section is completely dynamic
// :hostname = :webroot_path
args.domains.forEach(function (hostname) {
updates[hostname] = args.webrootPath;
});
// must write back to the original pyobject or
// annotations will be lost
Object.keys(updates).forEach(function (key) {
pyobj[key] = updates[key];
});
return mkdirpAsync(path.dirname(args.renewalPath)).then(function () {
return pyconf.writeFileAsync(args.renewalPath, pyobj);
}).then(function () {
// NOTE
// writing twice seems to causes a bug,
// so instead we re-read the file from the disk
return pyconf.readFileAsync(args.renewalPath);
});
}
function pyToJson(pyobj) {
if (!pyobj) {
return null;
}
var jsobj = {};
Object.keys(pyobj).forEach(function (key) {
jsobj[key] = pyobj[key];
});
jsobj.__lines = undefined;
jsobj.__keys = undefined;
return jsobj;
}
var defaults = {
configDir: [ '~', 'letsencrypt', 'etc' ].join(path.sep) // /etc/letsencrypt/
, logsDir: [ '~', 'letsencrypt', 'var', 'log' ].join(path.sep) // /var/log/letsencrypt/
, workDir: [ '~', 'letsencrypt', 'var', 'lib' ].join(path.sep) // /var/lib/letsencrypt/
, accountsDir: [ ':configDir', 'accounts', ':serverDir' ].join(path.sep)
, renewalPath: [ ':configDir', 'renewal', ':hostname.conf' ].join(path.sep)
, renewalDir: [ ':configDir', 'renewal', '' ].join(path.sep)
, serverDirGet: function (copy) {
return (copy.server || '').replace('https://', '').replace(/(\/)$/, '').replace(/\//g, path.sep);
}
, privkeyPath: ':configDir/live/:hostname/privkey.pem'.split(/\//).join(path.sep)
, fullchainPath: [ ':configDir', 'live', ':hostname', 'fullchain.pem' ].join(path.sep)
, certPath: [ ':configDir', 'live', ':hostname', 'cert.pem' ].join(path.sep)
, chainPath: [ ':configDir', 'live', ':hostname', 'chain.pem' ].join(path.sep)
, rsaKeySize: 2048
, webrootPath: [ ':workDir', 'acme-challenge' ].join(path.sep)
};
module.exports.create = function (configs) {
var mergedConfigs;
if (configs && typeof configs.log === 'function') log = configs.log;
var store = {
getOptions: function () {
if (mergedConfigs) {
return configs;
}
if (!configs.domainKeyPath) {
configs.domainKeyPath = configs.privkeyPath || defaults.privkeyPath;
}
Object.keys(defaults).forEach(function (key) {
if (!configs[key]) {
configs[key] = defaults[key];
}
});
mergedConfigs = configs;
return configs;
}
, keypairs: {
checkAsync: function (keypath, format) {
if (!keypath) {
return null;
}
return fs.readFileAsync(keypath, 'ascii').then(function (key) {
if ('jwk' === format) {
return { privateKeyJwk: JSON.parse(key) };
}
else {
return { privateKeyPem: key };
}
}, function (err) {
if ('ENOENT' !== err.code) {
throw err;
}
return null;
});
}
, setAsync: function (keypath, keypair, format) {
return mkdirpAsync(path.dirname(keypath)).then(function () {
var key;
if ('jwk' === format) {
key = JSON.stringify(keypair.privateKeyJwk, null, ' ');
}
else {
key = keypair.privateKeyPem;
}
return fs.writeFileAsync(keypath, key, 'ascii').then(function () {
return keypair;
});
});
}
}
//
// Certificates
//
, certificates: {
// Certificates
checkKeypairAsync: function (args) {
if (!args.domainKeyPath) {
return PromiseA.reject(new Error("missing options.domainKeyPath"));
}
return store.keypairs.checkAsync(args.domainKeyPath, 'pem');
}
// Certificates
, setKeypairAsync: function (args, keypair) {
return store.keypairs.setAsync(args.domainKeyPath, keypair, 'pem');
}
// Certificates
, checkAsync: function (args) {
if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) {
return PromiseA.reject(new Error("missing one or more of privkeyPath, fullchainPath, certPath, chainPath from options"));
}
//, fs.readFileAsync(fullchainPath, 'ascii')
// note: if this ^^ gets added back in, the arrays below must change
return PromiseA.all([
fs.readFileAsync(args.privkeyPath, 'ascii') // 0
, fs.readFileAsync(args.certPath, 'ascii') // 1
, fs.readFileAsync(args.chainPath, 'ascii') // 2
, fs.readFileAsync(args.fullchainPath, 'ascii') // 3
// stat the file, not the link
, fs.statAsync(args.certPath) // 4
]).then(function (arr) {
return {
privkey: arr[0] // privkey.pem
, cert: arr[1] // cert.pem
, chain: arr[2] // chain.pem
, fullchain: arr[3] // fullchain.pem
/*
// TODO populate these values only if they are known
, issuedAt: arr[4].mtime.valueOf()
, expiresAt: arr[4].mtime.valueOf() + (90 * 24 * 60 * 60 * 100)
*/
};
}, function (err) {
if (args.debug) {
console.error("[le-store-certbot] certificates.check");
console.error(err.stack);
}
return null;
});
}
// Certificates
, setAsync: function (args) {
return store.configs.getAsync(args).then(function (pyobj) {
var pems = args.pems;
pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0;
var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem');
var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem');
var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem');
var privkeyPath = args.privkeyPath || pyobj.privkey
|| args.domainKeyPath
|| path.join(liveDir, 'privkey.pem');
var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]);
var checkpoints = pyobj.checkpoints.toString();
var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem');
var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem');
var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem');
var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem');
return mkdirpAsync(archiveDir).then(function () {
return PromiseA.all([
sfs.writeFileAsync(certArchive, pems.cert, 'ascii')
, sfs.writeFileAsync(chainArchive, pems.chain, 'ascii')
, sfs.writeFileAsync(fullchainArchive, pems.cert + pems.chain, 'ascii')
, sfs.writeFileAsync(privkeyArchive, pems.privkey, 'ascii')
]);
}).then(function () {
return mkdirpAsync(liveDir);
}).then(function () {
return PromiseA.all([
sfs.writeFileAsync(certPath, pems.cert, 'ascii')
, sfs.writeFileAsync(chainPath, pems.chain, 'ascii')
, sfs.writeFileAsync(fullchainPath, pems.cert + pems.chain, 'ascii')
, sfs.writeFileAsync(privkeyPath, pems.privkey, 'ascii')
]);
}).then(function () {
pyobj.checkpoints += 1;
args.checkpoints += 1;
// TODO other than for compatibility this is optional, right?
// or is it actually needful for renewal? (i.e. list of domains)
return writeRenewalConfig(args);
}).then(function () {
return {
privkey: pems.privkey
, cert: pems.cert
, chain: pems.chain
/*
// TODO populate these only if they are actually known
, issuedAt: Date.now()
, expiresAt: Date.now() + (90 * 24 * 60 * 60 * 100)
*/
};
});
});
}
}
//
// Accounts
//
, accounts: {
// Accounts
_getAccountKeyPath: function (args) {
var promise = PromiseA.resolve(args.accountId);
if (args.email && !args.accountKeyPath && !args.accountId) {
promise = store.accounts._getAccountIdByEmail(args);
}
return promise.then(function (accountId) {
if (!accountId) {
return null;
}
return args.accountKeyPath || path.join(args.accountsDir, accountId, 'private_key.json');
});
}
// Accounts
, _getAccountIdByEmail: function (args) {
// If we read 10,000 account directories looking for
// just one email address, that could get crazy.
// We should have a folder per email and list
// each account as a file in the folder
// TODO
var email = args.email;
if ('string' !== typeof email) {
log(args.debug, "No email given");
return PromiseA.resolve(null);
}
return fs.readdirAsync(args.accountsDir).then(function (nodes) {
log(args.debug, "success reading arg.accountsDir");
return PromiseA.all(nodes.map(function (node) {
return fs.readFileAsync(path.join(args.accountsDir, node, 'regr.json'), 'utf8').then(function (text) {
var regr = JSON.parse(text);
regr.__accountId = node;
return regr;
});
})).then(function (regrs) {
var accountId;
log(args.debug, "regrs.length", regrs.length);
regrs.some(function (regr) {
return regr.body.contact.some(function (contact) {
var match = contact.toLowerCase() === 'mailto:' + email.toLowerCase();
if (match) {
accountId = regr.__accountId;
return true;
}
});
});
if (!accountId) {
return null;
}
return accountId;
});
}).then(function (accountId) {
return accountId;
}, function (err) {
if ('ENOENT' === err.code) {
// ignore error
return null;
}
return PromiseA.reject(err);
});
}
// Accounts
, _getAccountIdByPublicKey: function (keypair) {
// we use insecure md5 - even though we know it's bad - because that's how the python client did
return require('crypto').createHash('md5').update(keypair.publicKeyPem).digest('hex');
}
// Accounts
, checkKeypairAsync: function (args) {
if (!(args.accountKeyPath || args.accountsDir)) {
return PromiseA.reject(new Error("must provide one of options.accountKeyPath or options.accountsDir"));
}
return store.accounts._getAccountKeyPath(args).then(function (keypath) {
return store.keypairs.checkAsync(keypath, 'jwk');
});
}
// Accounts
, setKeypairAsync: function (args, keypair) {
var accountId;
if (args.email) {
accountId = store.accounts._getAccountIdByPublicKey(keypair);
}
return store.accounts._getAccountKeyPath({
accountsDir: args.accountsDir
, email: args.email
, accountId: args.accountId || accountId
}).then(function (keypath) {
return store.keypairs.setAsync(keypath, keypair, 'jwk');
});
}
// Accounts
, checkAsync: function (args) {
var promise;
var files = {};
var accountId;
if (args.accountId) {
promise = PromiseA.resolve(args.accountId);
}
else if (args.email) {
promise = store.accounts._getAccountIdByEmail(args);
}
else {
promise = PromiseA.reject(new Error("must provide accountId or email"));
}
return promise.then(function (_accountId) {
log(args.debug, 'accountId:', _accountId);
if (!_accountId) {
return false;
}
accountId = _accountId;
var accountDir = path.join(args.accountsDir, accountId);
var configs = [ 'meta.json', 'private_key.json', 'regr.json' ];
return PromiseA.all(configs.map(function (filename) {
var keyname = filename.slice(0, -5);
return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) {
var data;
try {
data = JSON.parse(text);
} catch(e) {
files[keyname] = { error: e };
return;
}
files[keyname] = data;
return true;
}, function (err) {
log(args.debug, 'Error reading account files:', err);
files[keyname] = { error: err };
});
}));
}).then(function (hasAccount) {
if (!hasAccount) {
return null;
}
var err;
if (!Object.keys(files).every(function (key) {
return !files[key].error;
}) || !files.private_key || !files.private_key.n) {
err = new Error("Account '" + accountId + "' was corrupt (had id, but was missing files).");
err.code = 'E_ACCOUNT_CORRUPT';
err.data = files;
return PromiseA.reject(err);
}
//files.private_key;
//files.regr;
//files.meta;
files.accountId = accountId; // preserve current account id
files.id = accountId;
files.keypair = { privateKeyJwk: files.private_key };
return files;
});
}
// Accounts
, setAsync: function (args, reg) {
var os = require("os");
var accountId = store.accounts._getAccountIdByPublicKey(reg.keypair);
var accountDir = path.join(args.accountsDir, accountId);
var accountMeta = {
creation_host: os.hostname()
, creation_dt: new Date().toISOString()
};
return mkdirpAsync(accountDir).then(function () {
// TODO abstract file writing
return PromiseA.all([
// meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"}
fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(accountMeta), 'utf8')
// private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" }
, fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(reg.keypair.privateKeyJwk), 'utf8')
// regr.json:
/*
{ body: { contact: [ 'mailto:coolaj86@gmail.com' ],
agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf',
key: { e: 'AQAB', kty: 'RSA', n: '...' } },
uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272',
new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz',
terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' }
*/
, fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify({ body: reg.receipt }), 'utf8')
]);
}).then(function () {
return {
id: accountId
, accountId: accountId
, email: args.email
, keypair: reg.keypair
, receipt: reg.receipt
};
});
}
// Accounts
, getAccountIdAsync: function (args) {
var pyconf = PromiseA.promisifyAll(require('pyconf'));
return pyconf.readFileAsync(args.renewalPath).then(function (renewal) {
var accountId = renewal.account;
renewal = renewal.account;
return accountId;
}, function (err) {
if ("ENOENT" === err.code) {
return store.accounts._getAccountIdByEmail(args);
}
return PromiseA.reject(err);
});
}
}
//
// Configs
//
, configs: {
// Configs
checkAsync: function (copy) {
copy.domains = [];
return store.configs._checkHelperAsync(copy).then(function (pyobj) {
var exists = pyobj.checkpoints >= 0;
if (!exists) {
return null;
}
return pyToJson(pyobj);
});
}
// Configs
, _checkHelperAsync: function (args) {
var pyconf = PromiseA.promisifyAll(require('pyconf'));
return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) {
return pyobj;
}, function () {
return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) {
return pyobj;
});
});
}
// Configs
, getAsync: function (args) {
return store.configs._checkHelperAsync(args).then(function (pyobj) {
var minver = pyobj.checkpoints >= 0;
args.pyobj = pyobj;
if (!minver) {
args.checkpoints = 0;
pyobj.checkpoints = 0;
return writeRenewalConfig(args);
}
// args.account.id = pyobj.account
// args.configDir = args.configDir || pyobj.configDir;
args.checkpoints = pyobj.checkpoints;
args.agreeTos = (args.agreeTos || pyobj.tos) && true;
args.email = args.email || pyobj.email;
args.domains = args.domains || pyobj.domains;
// yes, it's an array. weird, right?
args.webrootPath = args.webrootPath || pyobj.webrootPath[0];
args.server = args.server || args.acmeDiscoveryUrl || pyobj.server;
args.certPath = args.certPath || pyobj.cert;
args.privkeyPath = args.privkeyPath || pyobj.privkey;
args.chainPath = args.chainPath || pyobj.chain;
args.fullchainPath = args.fullchainPath || pyobj.fullchain;
//, workDir: args.workDir
//, logsDir: args.logsDir
args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize;
args.http01Port = args.http01Port || pyobj.http01Port;
args.domainKeyPath = args.domainKeyPath || args.keyPath || pyobj.keyPath;
return writeRenewalConfig(args);
});
}
// Configs
, allAsync: function (copy) {
copy.domains = [];
return fs.readdirAsync(copy.renewalDir).then(function (nodes) {
nodes = nodes.filter(function (node) {
return /^[a-z0-9]+.*\.conf$/.test(node);
});
return PromiseA.all(nodes.map(function (node) {
copy.domains = [node.replace(/\.conf$/, '')];
return store.configs.getAsync(copy);
}));
});
}
}
};
return store;
};

251
lib/logger.js Normal file
View File

@@ -0,0 +1,251 @@
/* jshint -W097 */
/* jshint strict: false */
/*jslint node: true */
'use strict';
var winston = require('winston');
var DailyRotateFile = require('winston-daily-rotate-file');
var fs = require('fs');
var path = require('path');
var os = require('os');
var tools = require(__dirname + '/tools.js');
var SysLog;
var hostname = tools.getHostName();
try {
SysLog = require('winston-syslog').Syslog;
} catch (ex) {
//console.log('No syslog support');
}
var logger = function (level, files, noStdout, prefix) {
var userOptions = {};
var options = {
transports: []
};
//var defaultMaxSize;// = 10 * 1024 * 1024;
if (typeof files === 'string') {
files = [files];
}
files = files || [];
var isNpm = (__dirname.replace(/\\/g, '/').toLowerCase().indexOf('node_modules/' + tools.appName.toLowerCase() + '.js-controller') !== -1);
if (typeof level === 'object') {
userOptions = Object.assign({}, level);
level = userOptions.level;
prefix = userOptions.prefix;
noStdout = userOptions.noStdout;
if (userOptions.prefix !== undefined) delete userOptions.prefix;
if (userOptions.transport) {
var fName = 0;
for (var f in userOptions.transport) {
if (!userOptions.transport.hasOwnProperty(f)) continue;
if (userOptions.transport[f].type === 'file' && userOptions.transport[f].enabled !== false) {
userOptions.transport[f].filename = userOptions.transport[f].filename || 'log/' + tools.appName;
if (!userOptions.transport[f].fileext && userOptions.transport[f].filename.indexOf('.log') === -1) {
userOptions.transport[f].fileext = '.log';
}
if (!fName) userOptions.transport[f].systemLog = true;
userOptions.transport[f].handleExceptions = false;
userOptions.transport[f].name = !fName ? tools.appName : 'dailyRotateFile' + fName;
fName++;
userOptions.transport[f].filename = userOptions.transport[f].filename.replace(/\\/g, '/');
if (userOptions.transport[f].filename.match(/^\w:\/|^\//)) {
userOptions.transport[f].filename = path.normalize(userOptions.transport[f].filename);
} else {
userOptions.transport[f].filename = path.normalize(__dirname + (isNpm ? '/../../../' : '/../') + userOptions.transport[f].filename);
}
userOptions.transport[f].label = prefix || '';
userOptions.transport[f].level = userOptions.transport[f].level || level;
userOptions.transport[f].json = (userOptions.transport[f].json !== undefined) ? userOptions.transport[f].json : false;
userOptions.transport[f].silent = (userOptions.transport[f].silent !== undefined) ? userOptions.transport[f].silent : false;
userOptions.transport[f].colorize = (userOptions.transport[f].colorize !== undefined) ? userOptions.transport[f].colorize : ((userOptions.colorize === undefined) ? true : userOptions.colorize);
userOptions.transport[f].localTime = (userOptions.transport[f].localTime !== undefined) ? userOptions.transport[f].localTime : ((userOptions.localTime === undefined) ? true : userOptions.localTime);
// userOptions.transport[f].maxsize = (userOptions.transport[f].maxsize !== undefined) ? userOptions.transport[f].maxsize : defaultMaxSize;
userOptions.transport[f].timestamp = timestamp;
userOptions.transport[f].datePattern = '.yyyy-MM-dd' + (userOptions.transport[f].fileext || '');
/*userOptions.transport[f].logException = function (message, info, next, err) {
console.error(message);
};*/
var _log = new DailyRotateFile(userOptions.transport[f]);
options.transports.push(_log);
} else if (userOptions.transport[f].type === 'syslog' && userOptions.transport[f].enabled !== false) {
if (!SysLog) {
console.error('Syslog configured, but not installed!');
continue;
}
// host: The host running syslogd, defaults to localhost.
// port: The port on the host that syslog is running on, defaults to syslogd's default port.
// protocol: The network protocol to log over (e.g. tcp4, udp4, unix, unix-connect, etc).
// path: The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).
// pid: PID of the process that log messages are coming from (Default process.pid).
// facility: Syslog facility to use (Default: local0).
// localhost: Host to indicate that log messages are coming from (Default: localhost).
// sysLogType: The type of the syslog protocol to use (Default: BSD).
// app_name: The name of the application (Default: process.title).
// eol: The end of line character to be added to the end of the message (Default: Message without modifications).
// replace the used by syslog attribute "type" with own "sysLogType"
// If no name defined, use hostname as name
userOptions.transport[f].localhost = userOptions.transport[f].localhost || hostname;
if (userOptions.transport[f].sysLogType) {
userOptions.transport[f].type = userOptions.transport[f].sysLogType;
delete userOptions.transport[f].sysLogType;
} else {
delete userOptions.transport[f].type;
}
try {
options.transports.push(new SysLog(userOptions.transport[f]));
} catch (err) {
console.log('Cannot activate syslog: ' + err);
}
}
}
}
} else {
for (var i = 0; i < files.length; i++) {
var opt = {
name: !i ? tools.appName : 'dailyRotateFile' + i,
filename: path.normalize(isNpm ? __dirname + '/../../../log/' + files[i] : __dirname + '/../log/' + files[i]),
datePattern: '.yyyy-MM-dd.log',
json: false, // If true, messages will be logged as JSON (default true).
level: level,
silent: false,
localTime: true,
colorize: (userOptions.colorize === undefined) ? true : userOptions.colorize,
timestamp: timestamp,
label: prefix || '',
handleExceptions: false
//maxsize: defaultMaxSize
};
options.transports.push(new DailyRotateFile(opt));
}
}
if (!noStdout) {
options.transports.push(new winston.transports.Console({
level: level,
silent: false,
colorize: (userOptions.colorize === undefined) ? true : userOptions.colorize,
timestamp: timestamp,
label: prefix || ''
}));
}
var log = new winston.Logger(options);
log.getFileName = function () {
if (this.transports && this.transports[tools.appName]) {
if (this.transports[tools.appName].filename) {
return this.transports[tools.appName].dirname + '/' + this.transports[tools.appName].filename;
} else if (this.transports[tools.appName]._getFilename) {
return this.transports[tools.appName].dirname + '/' + this.transports[tools.appName]._getFilename();
} else {
return '';
}
} else {
return '';
}
};
log.activateDateChecker = function (isEnabled, daysCount) {
if (!isEnabled && this._fileChecker) {
clearInterval(this._fileChecker);
} else if (isEnabled && !this._fileChecker) {
if (!daysCount) daysCount = 3;
// Check every hour
this._fileChecker = setInterval(function () {
if (this.transports[tools.appName] && fs.existsSync(this.transports[tools.appName].dirname)) {
var files = fs.readdirSync(this.transports[tools.appName].dirname);
var for3days = new Date();
for3days.setDate(for3days.getDate() - daysCount);
for (var i = 0; i < files.length; i++) {
var match = files[i].match(/.+\.(\d+-\d+-\d+)/);
if (match) {
var date = new Date(match[1]);
if (date < for3days) {
// delete file
try {
this.transports[tools.appName].log('info', 'host.' + hostname + ' Delete log file ' + files[i]);
fs.unlinkSync(this.transports[tools.appName].dirname + '/' + files[i]);
} catch (e) {
// there is a bug under windows, that file stays opened and cannot be deleted
this.log(os.platform().match(/^win/) ? 'info' : 'error', 'host.' + hostname + ' Cannot delete file "' + path.normalize(this.transports[tools.appName].dirname + '/' + files[i]) + '": ' + e);
}
}
}
}
}
}.bind(this), 3600000); // every hour
}
};
winston.unhandleExceptions();
return log;
};
function timestamp() {
var ts = new Date();
var result = ts.getFullYear() + '-';
/** @type {number | string} */
var value = ts.getMonth() + 1;
if (value < 10) value = '0' + value;
result += value + '-';
value = ts.getDate();
if (value < 10) value = '0' + value;
result += value + ' ';
value = ts.getHours();
if (value < 10) value = '0' + value;
result += value + ':';
value = ts.getMinutes();
if (value < 10) value = '0' + value;
result += value + ':';
value = ts.getSeconds();
if (value < 10) value = '0' + value;
result += value + '.';
value = ts.getMilliseconds();
if (value < 10) {
value = '00' + value;
} else
if (value < 100) {
value = '0' + value;
}
result += value + ' ';
return result;
}
module.exports = logger;

215
lib/multihostClient.js Normal file
View File

@@ -0,0 +1,215 @@
'use strict';
var dgram = require('dgram');
var crypto = null;
var port = 50005;
var MULTICAST_ADDR = '239.255.255.250';
function MHClient(hostname, logger, config, info) {
var id = 1;
var server;
var timer;
function getIPs() {
var ifaces = require('os').networkInterfaces();
var ipArr = [];
for (var dev in ifaces) {
if (!ifaces.hasOwnProperty(dev)) continue;
/*jshint loopfunc:true */
ifaces[dev].forEach(function (details) {
//noinspection JSUnresolvedVariable
if (!details.internal) ipArr.push(details.address);
});
}
return ipArr;
}
function stopServer() {
if (server) {
try {
server.close();
} catch (e) {
}
server = null;
}
if (timer) {
clearTimeout(timer);
timer = null;
}
}
function sha(secret, salt, callback) {
// calculate sha256
crypto = crypto || require('crypto');
var hash = crypto.createHash('sha256');
hash.on('readable', function () {
var data = hash.read();
if (data) {
callback(data.toString('hex'));
}
});
hash.write(secret + salt);
hash.end();
}
function startServer(isBroadcast, timeout, onReady, onMessage, onFinished) {
if (server) {
onFinished('Some operation still active');
return;
}
server = dgram.createSocket('udp4');
timeout = parseInt(timeout, 10) || 2000;
timer = setTimeout(function () {
stopServer();
if (onFinished) {
onFinished(null);
onFinished = null;
}
}, timeout);
server.on('error', function (err) {
stopServer();
if (onFinished) {
onFinished(err);
onFinished = null;
}
});
server.on('message', function (msg, rinfo) {
msg = msg.toString();
try {
msg = JSON.parse(msg);
if (onMessage) {
if (onMessage(server, msg, rinfo)) {
stopServer();
onFinished = null;
}
}
} catch (e) {
console.error('Invalid answer: ' + msg);
}
});
server.on('listening', function (msg, rinfo) {
// var address = server.address();
if (isBroadcast) server.setBroadcast(true);
onReady && onReady(server);
});
server.bind();
}
this.browse = function (timeout, isDebug, callback) {
var result = [];
var ownIps = getIPs();
startServer(true, timeout,
function onReady(srv) {
var text = JSON.stringify({
cmd: 'browse',
id: ++id
});
server.send(text, 0, text.length, port, MULTICAST_ADDR);
},
function onMessage (srv, msg, rinfo) {
// ignore own answers
if (isDebug || rinfo.address !== '127.0.0.1' && ownIps.indexOf(rinfo.address) === -1) {
if (msg.result === 'not authenticated') {
result.push({ip: rinfo.address, hostname: rinfo.address, info: 'authentication required', auth: msg.auth});
} else if (msg.result === 'ok') {
result.push(msg);
} else {
console.log('Unknown answer: ' + JSON.stringify(msg));
}
}
if (isDebug) {
console.log(JSON.stringify(msg));
}
},
function onFinished(err) {
callback(err, result);
}
);
};
this.connect = function (ip, password, callback) {
startServer(false, 2000,
function onReady(srv) {
var text = JSON.stringify({
cmd: 'browse',
id: ++id
});
server.send(text, 0, text.length, port, ip);
},
function onMessage (srv, msg, rinfo) {
if (msg.cmd === 'browse' && msg.id === id) {
if (msg.result === 'ok') {
if (callback) {
if (!msg.objects) {
if (callback) {
callback('Invalid configuration received: ' + JSON.stringify(msg));
callback = null;
}
} else if (!msg.states) {
if (callback) {
callback('Invalid configuration received: ' + JSON.stringify(msg));
callback = null;
}
} else {
callback && callback(null, msg.objects, msg.states, rinfo.address);
}
}
} else if (msg.result === 'not authenticated') {
if (!password) {
if (callback) {
callback('not authenticated' + msg);
callback = null;
}
} else {
sha(password, msg.salt, function (shaText) {
// send password
var text = JSON.stringify({
cmd: 'browse',
id: ++id,
password: shaText
});
server.send(text, 0, text.length, port, ip);
});
return false;
}
} else if (msg.result === 'invalid password') {
if (callback) {
callback('invalid password');
callback = null;
}
} else {
console.log(msg.result);
}
return true;
} else {
console.warn('Unexpected message: ' + JSON.stringify(msg));
}
},
function onFinished(err) {
if (callback) {
callback(err);
callback = null;
}
}
);
};
return this;
}
module.exports = MHClient;

275
lib/multihostServer.js Normal file
View File

@@ -0,0 +1,275 @@
'use strict';
var dgram = require('dgram');
var port = 50005;
var MULTICAST_ADDR = '239.255.255.250';
function MHServer(hostname, logger, config, info, ips, secret) {
var server = null;
var count = 0;
var initTimer = null;
var buffer = {};
var lastFrame = {};
var crypto;
var that = this;
var authList = {};
config = Object.assign({}, config); // make a copy
if (config.objects) {
config.objects = {
type: config.objects.type,
host: config.objects.host,
port: config.objects.port,
user: config.objects.user,
pass: config.objects.pass
};
}
if (config.states) {
config.states = {
type: config.states.type,
host: config.states.host,
port: config.states.port,
user: config.states.user,
pass: config.states.pass,
options: config.states.options,
maxQueue: config.states.maxQueue
};
}
function send(msg, rinfo) {
if (server) {
setImmediate(function () {
var text = JSON.stringify(msg);
try {
server.send(text, 0, text.length, rinfo.port, rinfo.address);
} catch (e) {
logger.warn('host.' + hostname + ' cannot send answer to ' + rinfo.address + ':' + rinfo.port + ': ' + e);
}
});
}
}
// delete all old connections
function checkAuthList(ts) {
ts = ts || new Date().getTime();
for (var id in authList) {
if (authList.hasOwnProperty(id)) {
if (!authList[id]) {
delete authList[id];
} else if (ts - authList[id].ts > 31000) {
delete authList[id];
}
}
}
}
function isSlave(oHost, sHots, ownIps) {
return !(oHost === 'localhost' || oHost === '127.0.0.1' || ownIps.indexOf(oHost) !== -1);
}
function sha(secret, salt, callback) {
// calculate sha256
crypto = crypto || require('crypto');
var hash = crypto.createHash('sha256');
hash.on('readable', function () {
var data = hash.read();
if (data) {
callback(data.toString('hex'));
}
});
hash.write(secret + salt);
hash.end();
}
// hello => auth => browse
function process(msg, rinfo) {
if (!msg) return;
var ts = new Date().getTime();
checkAuthList(ts);
var id = rinfo.address + ':' + rinfo.port;
switch (msg.cmd) {
case 'browse':
if (secret && msg.password && authList[id]) {
sha(secret, authList[id].salt, function (shaText) {
if (shaText !== msg.password) {
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
result: 'invalid password'
}, rinfo);
} else {
authList[id].auth = true;
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
objects: config.objects,
states: config.states,
info: info,
hostname: hostname,
slave: isSlave(config.objects.host, config.states.host, ips),
result: 'ok'
}, rinfo);
}
});
return;
}
if (!config.multihostService.secure || (authList[id] && authList[id].auth)) {
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
objects: config.objects,
states: config.states,
info: info,
hostname: hostname,
slave: isSlave(config.objects.host, config.states.host, ips),
result: 'ok'
}, rinfo);
} else {
authList[id] = {
time: ts,
salt: (Math.random() * 1000000 + ts).toString().substring(0, 16),
auth: false
};
// padding
if (authList[id].salt.length < 16) {
authList[id].salt += new Array(16 - authList[id].salt.length).join('_');
}
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
result: 'not authenticated',
salt: authList[id].salt
}, rinfo);
}
break;
default:
send({
cmd: msg.cmd,
id: msg.id,
result: 'unknown command'
}, rinfo);
break;
}
}
this.init = function () {
if (initTimer) {
clearTimeout(initTimer);
initTimer = null;
}
if (count > 10) {
logger.warn('host.' + hostname + ' Port ' + port + ' is occupied. Service stopped.');
return;
}
server = dgram.createSocket('udp4');
server.on('error', function (err) {
logger.error('host.' + hostname + ' multihost service error: ' + err.stack);
server.close();
server = null;
if (!initTimer) {
initTimer = setTimeout(function () {
initTimer = null;
that.init();
}, 5000);
}
});
server.on('close', function (err) {
server = null;
if (!initTimer) {
initTimer = setTimeout(function () {
initTimer = null;
that.init();
}, 5000);
}
});
server.on('message', function (msg, rinfo) {
// following messages are allowed
var text = msg.toString();
var now = new Date().getTime();
var id = rinfo.address + ':' + rinfo.port;
for (var ids in buffer) {
if (!lastFrame[ids]) {
delete buffer[ids];
} else if (now - lastFrame[ids] > 1000) {
delete buffer[ids];
delete lastFrame[ids];
}
}
if (lastFrame[id] && now - lastFrame[id] > 1000) {
buffer[id] = '';
}
lastFrame[id] = now;
if (!buffer[id] && text[0] !== '{') {
// ignore message
logger.warn('host.' + hostname + ' Message ignored: ' + text);
} else {
buffer[id] = (buffer[id] || '') + msg.toString();
if (buffer[id] && buffer[id][buffer[id].length - 1] === '}') {
try {
var data = JSON.parse(buffer[id]);
buffer[id] = '';
if (data) {
process(data, rinfo);
}
} catch (e) {
// may be not yet complete.
}
}
}
});
server.on('listening', function () {
server.addMembership(MULTICAST_ADDR);
var address = server.address();
logger.warn('host.' + hostname + ' multihost service started on ' + address.address + ':' + address.port);
});
server.bind(50005);
};
this.close = function (callback) {
if (initTimer) {
clearTimeout(initTimer);
initTimer = null;
}
if (server) {
try {
server.close(callback);
server = null;
} catch (e) {
server = null;
callback && callback()
}
} else if (callback) {
callback();
}
};
this.init();
return this;
}
module.exports = MHServer;

13
lib/objects.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
var getConfigFileName = require(__dirname + '/tools').getConfigFileName;
var config = JSON.parse(require('fs').readFileSync(getConfigFileName(), 'utf8'));
if (!config.objects) config.objects = {type: 'file'};
if (config.objects.type === 'file') {
module.exports = require(__dirname + '/objects/objectsInMemClient');
} else if (config.objects.type === 'redis') {
module.exports = require(__dirname + '/objects/objectsInRedis');
} else {
throw 'Unknown objects type: ' + config.objects.type;
}

View File

@@ -0,0 +1,501 @@
/**
* Object DB in memory - Client
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
*
* MIT License
*
*/
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
/* jshint -W061 */
'use strict';
const io = require('socket.io-client');
const util = require('util');
const stream = require('stream');
const Writable = stream.Writable;
let memStore = {};
/* Writable memory stream */
function WMStrm(key, options) {
// allow use without new operator
if (!(this instanceof WMStrm)) {
return new WMStrm(key, options);
}
Writable.call(this, options); // init super
this.key = key; // save key
memStore[key] = new Buffer(''); // empty
}
util.inherits(WMStrm, Writable);
WMStrm.prototype._write = function (chunk, enc, cb) {
if (chunk) {
// our memory store stores things in buffers
let buffer = (Buffer.isBuffer(chunk)) ?
chunk : // already is Buffer use it
new Buffer(chunk, enc); // string, convert
// concatenate to the buffer already there
if (!memStore[this.key]) {
memStore[this.key] = new Buffer('');
console.log('memstore for ' + this.key + ' is null');
}
memStore[this.key] = Buffer.concat([memStore[this.key], buffer]);
}
if (!cb) throw 'No callback for WMStrm.prototype._write';
cb();
};
function ObjectsInMemClient(settings) {
let client;
let that = this;
let subscribes = [];
let connectionTimeout;
let log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/*console.log(msg);*/},
debug: function (msg) {/*console.log(msg);*/},
info: function (msg) {/*console.log(msg);*/},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
let __construct = (function () {
if (!settings.connection.secure) {
client = io.connect('http://' + (settings.connection.host !=='0.0.0.0' ? settings.connection.host || '127.0.0.1' : '127.0.0.1') + ':' + (settings.connection.port || 9001));
} else {
client = io.connect('https://' + (settings.connection.host !=='0.0.0.0' ? settings.connection.host || '127.0.0.1' : '127.0.0.1') + ':' + (settings.connection.port || 9001));
}
if (typeof settings.change === 'function') {
client.on('message', function (pattern, channel, message) {
log.silly(settings.namespace + ' inMem message ', pattern, channel, message);
try {
settings.change(channel, message);
} catch (e) {
log.error(settings.namespace + ' message ' + channel + ' ' + message + ' ' + e.message);
log.error(settings.namespace + ' ' + e.stack);
}
});
}
client.on('disconnect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
}
});
client.on('error', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.error(settings.namespace + ' ' + error.message);
log.error(settings.namespace + ' ' + error.stack);
}
});
client.on('connect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.connected === 'function') settings.connected('InMemoryDB ' + settings.connection.host + ':' + settings.connection.port);
});
client.on('reconnect', function (error) {
// Re-initialise subscribes
for (let i = 0; i < subscribes.length; i++) {
client.emit('subscribe', subscribes[i]);
}
if (typeof settings.connected === 'function') settings.connected('InMemoryDB ' + settings.connection.host + ':' + settings.connection.port);
});
connectionTimeout = setTimeout(function () {
if (typeof settings.connectTimeout === 'function') settings.connectTimeout('Connection timeout');
connectionTimeout = null;
}, 5000);
})();
settings = settings || {};
this.subscribe = function (pattern, options) {
if (subscribes.indexOf(pattern) === -1) subscribes.push(pattern);
if (!client) return;
client.emit('subscribe', pattern, options);
};
this.unsubscribe = function (pattern) {
let pos = subscribes.indexOf(pattern);
if (pos !== -1) subscribes.splice(pos, 1);
if (!client) return;
client.emit('unsubscribe', pattern);
};
this.destroy = function (callback) {
if (!client) return;
// Client may not close the DB
if (callback) callback();
//client.emit('destroy', callback);
};
this.enableFileCache = function (enabled, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('enableFileCache', enabled, options, callback);
};
this.insert = function (id, attName, ignore, options, obj, callback) {
//return pipe for write into redis
let strm = new WMStrm(id + '/' + attName);
strm.on('finish', function () {
if (!memStore[id + '/' + attName]) log.error(settings.namespace + ' File ' + id + ' / ' + attName + ' is empty');
that.writeFile(id, attName, memStore[id + '/' + attName], options, function () {
if (memStore[id + '/' + attName] !== undefined) delete memStore[id + '/' + attName];
if (callback) callback(null, null);
});
});
return strm;
};
this.writeFile = function (id, name, data, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('writeFile', id, name, data, options, callback);
};
this.readFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('readFile', id, name, options, callback);
};
this.readDir = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('readDir', id, name, options, callback);
};
this.readDirAsZip = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('readDirAsZip', id, name, options, callback);
};
this.writeDirAsZip = function (id, name, options, data, callback) {
if (typeof data === 'function') {
callback = data;
data = options;
options = null;
}
if (!client) return;
client.emit('writeDirAsZip', id, name, options, data, callback);
};
this.unlink = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('unlink', id, name, options, callback);
};
this.delFile = this.unlink;
this.rename = function (id, oldName, newName, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('rename', id, oldName, newName, options, callback);
};
this.mkdir = function (id, dirname, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('mkdir', id, dirname, options, callback);
};
this.chmodFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chmodFile', id, name, options, callback);
};
this.chownFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chownFile', id, name, options, callback);
};
this.touch = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('touch', id, name, options, callback);
};
this.rm = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('rm', id, name, options, callback);
};
this.chmodObject = function (pattern, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chmodObject', pattern, options, callback);
};
this.chownObject = function (pattern, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chownObject', pattern, options, callback);
};
this.getObjectView = function (design, search, params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjectView', design, search, params, options, callback);
};
this.getObjectList = function (params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjectList', params, options, callback);
};
this.getObjectListAsync = (params, options) => {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.getObjectList(params, options, (err, arr) => {
if (err) {
reject(err);
} else {
resolve(arr);
}
})
});
};
this.extendObject = function (id, obj, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('extendObject', id, obj, options, callback);
};
this.extendObjectAsync = function (id, obj, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.extendObject(id, obj, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.getKeys = function (pattern, options, callback, dontModify) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getKeys', pattern, options, callback, dontModify);
};
this.getConfigKeys = this.getKeys;
this.getObjects = function (keys, options, callback, dontModify) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjects', keys, options, callback, dontModify);
};
this.getObjectsByPattern = (pattern, options, callback) => {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjectsByPattern', pattern, options, callback);
};
this.getObjectsByPatternAsync = (pattern, options) => {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.getObjectsByPattern(pattern, options, (err, objs) => {
if (err) {
reject(err);
} else {
resolve(objs);
}
});
});
};
this.setObject = function (id, obj, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('setObject', id, obj, options, callback);
};
this.setObjectAsync = function (id, obj, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.setObject(id, obj, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.delObject = function (id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('delObject', id, options, callback);
};
this.delObjectAsync = function (id, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.delObject(id, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.getObject = function (id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObject', id, options, callback);
};
this.getObjectAsync = function (id, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.getObject(id, options, (err, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
};
this.findObject = function (idOrName, type, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('findObject', idOrName, type, options, callback);
};
this.findObjectAsync = function (idOrName, type, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.findObject(idOrName, type, options, (err, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
};
this.destroyDB = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('destroyDB', options, callback);
};
}
module.exports = ObjectsInMemClient;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

87
lib/password.js Normal file
View File

@@ -0,0 +1,87 @@
/**
*
* password hash and check
*
* 7'2014-2016 Bluefox <dogafox@gmail.com>
* 2014 hobbyquaker <hq@ccu.io>
*
* derived from https://github.com/florianheinemann/password-hash-and-salt/ (MIT License)
*
* The created hash is of the following format: <algorithm>$<iterations>$<hash>$<salt>
*
* Usage Example:
var password = require('./lib/password.js');
password('test').hash(null, null, function (err, res) {
console.log(res);
password('test').check(res, function (err, res) {
console.log('test: ' + res);
});
password('muh').check(res, function (err, res) {
console.log('muh: ' + res);
});
});
*
*/
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const crypto = require('crypto');
let version = null;
const password = pw => {
return {
hash: (salt, iterations, callback) => {
salt = salt || crypto.randomBytes(16).toString('hex');
iterations = iterations || 10000;
// version 0.10 has no 'sha256' and this option must be ignored
if (version === null) {
version = process.version.replace('v', '');
version = !version.match(/^0\.10\./);
}
if (version) {
crypto.pbkdf2(pw, salt, iterations, 256, 'sha256', (err, key) => {
if (err) return callback(err);
callback(null, `pbkdf2$${iterations}$${key.toString('hex')}$${salt}`);
});
} else {
crypto.pbkdf2(pw, salt, iterations, 64, (err, key) => {
if (err) return callback(err);
callback(null, `pbkdf2$${iterations}$${key.toString('hex')}$${salt}`);
});
}
},
check: function (hashedPassword, callback) {
if (!hashedPassword || !password) return callback(null, false);
let key = hashedPassword.split('$');
if (key.length !== 4 || !key[2] || !key[3]) return callback('Hash not formatted correctly');
if (key[0] !== 'pbkdf2') return callback('Unknown');
this.hash(key[3], parseInt(key[1], 10), function (error, newHash) {
if (error) return callback(error);
callback(null, newHash === hashedPassword);
});
},
complexity: callback => {
// Todo: Check for password complexity
return true;
}
};
};
module.exports = password;

159
lib/preinstall_check.js Normal file
View File

@@ -0,0 +1,159 @@
'use strict';
// we cannot use semver here, because dependencies are not installed yet
// so the version checks get a bit messy
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const os = require('os');
// where yunkong2 is installed
const rootDir = __dirname.substr(0, __dirname.lastIndexOf('node_modules'));
function checkNpmVersion() {
// Get npm version
try {
// remove local node_modules\.bin dir from path
// or we potentially get a wrong npm version
let newEnv = Object.assign({}, process.env);
newEnv.PATH = (newEnv.PATH || newEnv.Path || newEnv.path)
.split(path.delimiter)
.filter(dir => {
dir = dir.toLowerCase();
return !(dir.indexOf('yunkong2') > -1 && dir.indexOf(path.join('node_modules', '.bin')) > -1);
})
.join(path.delimiter);
let npmVersion = child_process.execSync('npm -v', {encoding: 'utf8', env: newEnv});
npmVersion = npmVersion.trim();
console.log('NPM version: ' + npmVersion);
if (gte(npmVersion, '5.0.0') && lt(npmVersion, '5.7.1')) {
console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
console.warn('WARNING:');
console.warn('You are using an unsupported npm version!');
console.warn('This can lead to problems when installing further packages');
console.warn();
console.warn('Please use "npm install -g npm@4" to downgrade npm to 4.x or ');
console.warn('use "npm install -g npm@latest" to install a supported version of npm 5!');
console.warn('You need to make sure to repeat this step after installing an update to NodeJS and/or npm.');
console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
return npmVersion;
} catch (e) {
console.error('Could not check npm version: ' + e);
console.error('Assuming that correct version is installed.');
process.exit(0);
}
}
if (gte(checkNpmVersion(), '5.0.0')) {
// disables NPM's package-lock.json on NPM >= 5 because that creates heaps of problems
console.log('npm version >= 5: disabling package-lock');
fs.writeFileSync(path.join(rootDir, '.npmrc'), 'package-lock=false' + os.EOL, 'utf8');
}
process.exit(0);
// ======================================
// all the functions to replace `semver`:
/**
* @typedef {{major: number, minor: number, build: number}} Version
*/
/**
* Parses a version string
* @param {string} version The version string to parse
* @returns {Version | null} The parsed version
*/
function parseVersion(version) {
const versionRegExp = /^v?(\d+)\.(\d+)\.(\d+).*?/;
const parsed = versionRegExp.exec(version);
if (!parsed) return null;
return {
major: +parsed[1],
minor: +parsed[2],
build: +parsed[3]
};
}
/**
* Checks if v1 > v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function gt(v1, v2) {
if (typeof v1 === "string") v1 = parseVersion(v1);
if (typeof v2 === "string") v2 = parseVersion(v2);
if (v1.major > v2.major) return true;
else if (v1.major < v2.major) return false;
if (v1.minor > v2.minor) return true;
else if (v1.minor < v2.minor) return false;
return (v1.build > v2.build);
}
/**
* Checks if v1 < v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function lt(v1, v2) {
if (typeof v1 === "string") v1 = parseVersion(v1);
if (typeof v2 === "string") v2 = parseVersion(v2);
if (v1.major < v2.major) return true;
else if (v1.major > v2.major) return false;
if (v1.minor < v2.minor) return true;
else if (v1.minor > v2.minor) return false;
return (v1.build < v2.build);
}
/**
* Checks if v1 == v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function eq(v1, v2) {
if (typeof v1 === "string") v1 = parseVersion(v1);
if (typeof v2 === "string") v2 = parseVersion(v2);
if (v1.major !== v2.major) return false;
if (v1.minor !== v2.minor) return false;
return v1.build === v2.build;
}
/**
* Checks if v1 != v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function ne(v1, v2) {
return !eq(v1, v2);
}
/**
* Checks if v1 >= v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function gte(v1, v2) {
return gt(v1, v2) || eq(v1, v2);
}
/**
* Checks if v1 <= v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function lte(v1, v2) {
return lt(v1, v2) || eq(v1, v2);
}

68
lib/renewal.conf.tpl Normal file
View File

@@ -0,0 +1,68 @@
#cert = :configDir/live/:hostname/cert.pem
cert = :cert_path
privkey = :privkey_path
chain = :chain_path
fullchain = :fullchain_path
# Options and defaults used in the renewal process
[renewalparams]
apache_enmod = a2enmod
no_verify_ssl = False
ifaces = None
apache_dismod = a2dismod
register_unsafely_without_email = False
uir = None
installer = none
config_dir = :configDir
text_mode = True
# junk?
# https://github.com/letsencrypt/letsencrypt/issues/1955
func = <function obtain_cert at 0x30c9500>
prepare = False
work_dir = :work_dir
tos = :agree_tos
init = False
http01_port = :http_01_port
duplicate = False
# this is for the domain
key_path = :privkey_path
nginx = False
fullchain_path = :fullchain_path
email = :email
csr = None
agree_dev_preview = None
redirect = None
verbose_count = -3
config_file = None
renew_by_default = True
hsts = False
authenticator = webroot
domains = :hostnames #comma,delimited,list
rsa_key_size = :rsa_key_size
# starts at 0 and increments at every renewal
checkpoints = -1
manual_test_mode = False
apache = False
cert_path = :cert_path
webroot_path = :webroot_paths # comma,delimited,list
strict_permissions = False
apache_server_root = /etc/apache2
# https://github.com/letsencrypt/letsencrypt/issues/1948
account = :account_id
manual_public_ip_logging_ok = False
chain_path = :chain_path
standalone = False
manual = False
server = :acme_discovery_url
standalone_supported_challenges = "http-01,tls-sni-01"
webroot = True
apache_init_script = None
user_agent = None
apache_ctl = apache2ctl
apache_le_vhost_ext = -le-ssl.conf
debug = False
tls_sni_01_port = 443
logs_dir = :logs_dir
configurator = None
[[webroot_map]]
# :hostname = :webroot_path

181
lib/restart.js Normal file
View File

@@ -0,0 +1,181 @@
/**
* @fileOverview restart the controller
* @author bluefox
* @version 0.1
*/
'use strict';
/** @module restart */
var fs = require('fs');
var tools = require(__dirname + '/tools.js');
var logFile;
try {
var dir = __dirname + '/../' + tools.getDefaultDataDir() + '../log';
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
logFile = dir + '/restart.log';
console.log(require('path').normalize(logFile));
if (fs.existsSync(logFile)) fs.unlinkSync(logFile);
fs.writeFileSync(logFile, 'started\n');
} catch (err) {
console.error(err);
}
function log(text) {
var t = (new Date()).toString() + text;
console.log(t);
fs.appendFileSync(logFile, t + '\n');
}
function checkRoot(callback) {
var data = '';
var child = require('child_process').spawn('whoami', []);
child.stdout.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.stderr.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.on('exit', function (exitCode) {
callback(data.trim() == 'root');
});
}
function killPidsScript(callback) {
checkRoot(function (isRoot) {
var data = '';
try {
fs.chmodSync(__dirname + '/../killall.sh', '777');
if (isRoot) {
fs.writeFileSync(__dirname + '/../killall.sh', "sudo pgrep -f '^io.*' | sudo xargs kill -9\nsudo pgrep -f '^node-red*' | sudo xargs kill -9");
} else {
fs.writeFileSync(__dirname + '/../killall.sh', "pgrep -f '^io.*' | xargs kill -9\pgrep -f '^node-red*' | xargs kill -9");
}
var child = require('child_process').spawn(__dirname + '/../killall.sh', []);
child.stdout.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.stderr.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.on('exit', function (exitCode) {
if (exitCode) log('Exit code for "killall.sh": ' + exitCode);
callback(exitCode, data);
});
} catch (e) {
log('Cannot create "' + __dirname + '/../killall.sh"');
callback(-1);
}
});
}
function killPid(pid, callback) {
var data = '';
var child = require('child_process').spawn('kill', ['-KILL', pid]);
child.stdout.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.stderr.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.on('exit', function (exitCode) {
if (exitCode) log('Exit code for "kill -KILL ' + pid + '": ' + exitCode);
callback(exitCode, data);
});
}
function killPids(pids, callback) {
if (pids && pids.length) {
killPid(pids.pop(), function () {
killPids(pids, callback);
});
} else {
callback();
}
}
if (require('os').platform().match(/^win/) && fs.existsSync(__dirname + '/../_service_' + tools.appName + '.bat')) {
log('Restarting service ' + tools.appName + '...');
var spawn = require('child_process').spawn;
var out;
var err;
var stat;
var fileName;
if (fs.existsSync(__dirname + '/../../../log')) {
fileName = __dirname + '/../../../log/restart.log';
} else {
fileName = __dirname + '/../log/restart.log';
}
stat = fs.statSync(fileName);
if (stat.size > 1024 * 1024) {
try {
fs.unlinkSync(fileName);
} catch (e) {
console.log('File is too big, but cannot delete restart.log: ' + e.toString());
}
}
out = fs.openSync(fileName, 'a');
err = out;
log('Starting ' + __dirname + '/../_service_' + tools.appName + '.bat');
var child = spawn('cmd.exe', ['/c', __dirname + '/../_service_' + tools.appName + '.bat'], {
detached: true,
stdio: ['ignore', out, err]
});
child.unref();
process.exit();
} else if (!fs.existsSync(__dirname + '/' + tools.appName + '.pid')) {
log(tools.appName + ' was started manually or was not running. Please restart it manually.');
} else {
log('Restarting ' + tools.appName + '...');
var daemon = require('daemonize2').setup({
main: '../controller.js',
name: tools.appName + ' controller',
pidfile: __dirname + '/' + tools.appName + '.pid',
stopTimeout: 5000
});
log('Stopping daemon ' + tools.appName + '...');
daemon.stop(function (err, pid) {
// force to stop all adapters
if (fs.existsSync(__dirname + '/../pids.txt')) {
try {
var pids = JSON.parse(fs.readFileSync(__dirname + '/../pids.txt').toString());
killPids(pids, function () {
killPidsScript(function () {
log('Starting daemon ' + tools.appName + '...');
daemon.start(function (err, pid) {
log('Daemon ' + tools.appName + ' started');
process.exit();
});
});
});
} catch (e) {
log('Error by pids.txt: ' + e);
log('Starting daemon ' + tools.appName + '...');
daemon.start(function (err, pid) {
log('Daemon ' + tools.appName + ' started');
process.exit();
});
}
} else {
log('Starting daemon ' + tools.appName + '...');
daemon.start(function (err, pid) {
log('Daemon ' + tools.appName + ' started');
process.exit();
});
}
});
}

112
lib/scripts/scripts.js Normal file
View File

@@ -0,0 +1,112 @@
var https = require('https');
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
function httpsGet(link, callback) {
https.get(link, function (res) {
var statusCode = res.statusCode;
if (statusCode !== 200) {
// consume response data to free up memory
res.resume();
callback(statusCode, null, link);
}
res.setEncoding('utf8');
var rawData = '';
res.on('data', function (chunk) {
rawData += chunk;
});
res.on('end', function () {
callback(null, rawData ? rawData.toString() : null, link);
});
}).on('error', function (e) {
callback(e.message, null, link);
});
}
var stableURL = 'https://raw.githubusercontent.com/' + tools.appName + '/' + tools.appName + '.repositories/master/sources-dist-stable.json';
function updateVersion(name, callback, _sources) {
if (!_sources) {
httpsGet(stableURL, function (err, body) {
updateVersion(name, callback, JSON.parse(body));
});
}
var cmd = 'npm show ' + tools.appName + '.' + name + ' version';
var exec = require('child_process').exec;
var result = '';
var child = exec(cmd, function(error, stdout, stderr){
result = stdout;
});
child.stderr.pipe(process.stdout);
child.on('exit', function (code /* , signal */) {
if (code) {
console.error('host.' + tools.getHostName() + ' Cannot get version of ' + tools.appName + '.' + name + ': ' + code);
callback(code, _sources, name, null);
} else {
_sources[name].version = result;
callback(null, _sources, name, result);
}
});
}
function updateVersions(callback) {
httpsGet(stableURL, function (err, body) {
var sources = JSON.parse(body);
var count = 0;
for (var name in sources) {
if (!sources.hasOwnProperty(name)) continue;
if (!sources[name].version) {
count++;
updateVersion(name, function () {
if (!--count) callback(sources);
}, sources);
}
}
if (!count) callback(sources);
});
}
// get the sources-dist.json
if (process.argv.indexOf('--prepublish') !== -1) {
httpsGet(stableURL, function (err, body) {
if (err || !body) {
console.error('Cannot read sources file "' + stableURL + '": ' + err);
process.exit(2);
} else {
fs.writeFileSync(__dirname + '/../../conf/sources-dist.json', body);
process.exit();
}
});
}
// update versions for all adapter, which do not have the version
if (process.argv.indexOf('--init') !== -1) {
updateVersions(function (sources) {
var file = process.argv.indexOf('--file');
if (file !== -1 && process.argv[file + 1]) {
fs.writeFileSync(file, JSON.stringify(sources, null, 2));
} else {
console.log(JSON.stringify(sources, null, 2));
}
});
}
// update version for one adapter
if (process.argv.indexOf('--update') !== -1) {
var pos = process.argv.indexOf('--update');
if (process.argv[pos + 1]) {
updateVersion(process.argv[pos + 1], function (sources) {
var file = process.argv.indexOf('--file');
if (file !== -1 && process.argv[file + 1]) {
fs.writeFileSync(file, JSON.stringify(sources, null, 2));
} else {
console.log(JSON.stringify(sources, null, 2));
}
});
} else {
console.warn('Pleas specify name of adapter to update: script.js --update admin');
process.exit(1);
}
}

73
lib/session.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
module.exports = function (session, defaultTtl) {
var Store = session.Store;
defaultTtl = defaultTtl || 3600;
function AdapterStore(options) {
var that = this;
this.adapter = options.adapter;
options = options || {};
Store.call(this, options);
}
// Object.getPrototypeOf(AdapterStore.prototype) ?
// AdapterStore.prototype.__proto__ = Store.prototype;
AdapterStore.prototype = Object.create(Store.prototype);
/**
* Attempt to fetch session by the given `sid`.
*
* @param {String} sid
* @param {Function} fn
* @api public
*/
AdapterStore.prototype.get = function (sid, fn) {
this.adapter.getSession(sid, function (obj) {
if (obj) {
if (fn) return fn(null, obj);
} else {
if (fn) return fn();
}
});
};
/**
* Commit the given `sess` object associated with the given `sid`.
*
* @param {String} sid
* @param {Session} sess
* @param {Function} fn
* @api public
*/
AdapterStore.prototype.set = function (sid, ttl, sess, fn) {
if (typeof ttl === 'object') {
fn = sess;
sess = ttl;
ttl = defaultTtl;
}
ttl = ttl || defaultTtl;
this.adapter.setSession(sid, ttl, sess, function () {
if (fn) fn.apply(this, arguments);
});
};
/**
* Destroy the session associated with the given `sid`.
*
* @param {String} sid
* @api public
*/
AdapterStore.prototype.destroy = function (sid, fn) {
this.adapter.destroySession(sid, fn);
};
return AdapterStore;
};

3167
lib/setup.js Normal file

File diff suppressed because it is too large Load Diff

602
lib/setup/setupBackup.js Normal file
View File

@@ -0,0 +1,602 @@
'use strict';
function BackupRestore(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var pathLib = require('path');
var tmpDir = pathLib.normalize(__dirname + '/../../tmp');
var bkpDir = pathLib.normalize(__dirname + '/../../backups');
var hostname = tools.getHostName();
// allow use without new operator
if (!(this instanceof BackupRestore)) return new BackupRestore(options);
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
if (!options.cleanDatabase) throw 'Invalid arguments: cleanDatabase is missing';
if (!options.restartController) throw 'Invalid arguments: restartController is missing';
var objects = options.objects;
var states = options.states;
var processExit = options.processExit;
var cleanDatabase = options.cleanDatabase;
var restartController = options.restartController;
var mime;
var Upload = require(__dirname + '/setupUpload.js');
var upload = new Upload(options);
// --------------------------------------- BACKUP ---------------------------------------------------
function _copyFile(id, srcPath, destPath, callback) {
objects.readFile(id, srcPath, "", function (err, data) {
if (data) fs.writeFileSync(destPath, data);
callback();
});
}
function copyDir(id, srcPath, destPath, callback) {
var count = 0;
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath);
objects.readDir(id, srcPath, function (err, res) {
if (res) {
for (var t = 0; t < res.length; t++) {
if (res[t].isDir) {
count++;
copyDir(id, srcPath + '/' + res[t].file, destPath + '/' + res[t].file, function () {
count--;
if (!count) callback();
});
} else {
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath);
count++;
_copyFile(id, srcPath + '/' + res[t].file, destPath + '/' + res[t].file, function () {
count--;
if (!count) callback();
});
}
}
}
if (!count) callback();
});
}
function getBackupDir() {
var dataDir = tools.getDefaultDataDir();
// All pathes are returned always relative to /node_modules/appName.js-controller
if (dataDir) {
if (dataDir[0] === '.' && dataDir[1] === '.') {
dataDir = __dirname + '/../../' + dataDir;
} else if (dataDir[0] === '.' && dataDir[1] === '/') {
dataDir = __dirname + '/../../' + dataDir.substring(2);
}
}
dataDir = dataDir.replace(/\\/g, '/');
if (dataDir[dataDir.length - 1] !== '/') dataDir += '/';
var parts = dataDir.split('/');
parts.pop();// remove data or appName-data
parts.pop();
return parts.join('/') + '/backups/';
}
function copyFileSync(source, target) {
var targetFile = target;
// if target is a directory a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = pathLib.join(target, pathLib.basename(source));
}
}
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
function copyFolderRecursiveSync(source, target) {
var files = [];
if (!fs.existsSync(target)) fs.mkdirSync(target);
// check if folder needs to be created or integrated
var targetFolder = pathLib.join(target, pathLib.basename(source));
if (!fs.existsSync(targetFolder)) fs.mkdirSync(targetFolder);
// copy
if (fs.lstatSync(source).isDirectory() ) {
files = fs.readdirSync(source);
files.forEach(function (file) {
var curSource = pathLib.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
this.createBackup = function (name, callback) {
var count = 0;
if (!name) {
var d = new Date();
name = d.getFullYear() + '_' +
('0' + (d.getMonth() + 1)).slice(-2) + '_' +
('0' + d.getDate()).slice(-2) + '-' +
('0' + d.getHours()).slice(-2) + '_' +
('0' + d.getMinutes()).slice(-2) + '_' +
('0' + d.getSeconds()).slice(-2) + '_backup' + tools.appName;
}
name = name.replace(/\\/g, '/');
if (name.indexOf('/') === -1) {
var path = getBackupDir();
// create directory if not exists
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
if (name.indexOf('.tar.gz') === -1) {
name = path + name + '.tar.gz';
} else {
name = path + name;
}
}
objects.getObjectList({include_docs: true}, function (err, res) {
var result = {objects: null, states: {}, config: null};
if (err) {
console.error('host.' + hostname + ' Cannot get objects: ' + err);
} else {
result.objects = res.rows;
}
if (fs.existsSync(tools.getConfigFileName())) result.config = JSON.parse(fs.readFileSync(tools.getConfigFileName(), 'utf8'));
states.getKeys('io.*', function (err, keys) {
/*for (var i = keys.length - 1; i >= 0; i--) {
if (keys[i].match(/^messagebox\./) || keys[i].match(/^log\./)) {
keys.splice(i, 1);
}
}*/
states.getStates(keys, function (err, obj) {
var hostname = tools.getHostName();
var r = new RegExp('^system\\.host\\.' + hostname + '\\.(\\w+)$');
for (var i = 0; i < keys.length; i++) {
if (obj[i].from === 'system.host.' + hostname) {
obj[i].from = 'system.host.$$__hostname__$$';
}
if (r.test(keys[i])) {
keys[i] = keys[i].replace(hostname, '$$__hostname__$$');
}
result.states[keys[i]] = obj[i];
}
if (!fs.existsSync(bkpDir)) fs.mkdirSync(bkpDir);
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
if (!fs.existsSync(tmpDir + '/backup')) fs.mkdirSync(tmpDir + '/backup');
if (!fs.existsSync(tmpDir + '/backup/files')) fs.mkdirSync(tmpDir + '/backup/files');
// try to find user files
for (var j = 0; j < result.objects.length; j++) {
if (!result.objects[j].value || !result.objects[j].value._id) continue;
//if (result.objects[j].doc) delete result.objects[j].doc;
if (result.objects[j].value._id.match(/^system\.adapter\.([\w\d_-]+).(\d+)$/) &&
result.objects[j].value.common.host === hostname) {
result.objects[j].value.common.host = '$$__hostname__$$';
if (result.objects[j].doc) {
result.objects[j].doc.common.host = '$$__hostname__$$';
}
} else
if (r.test(result.objects[j].value._id)) {
result.objects[j].value._id = result.objects[j].value._id.replace(hostname, '$$__hostname__$$');
result.objects[j].id = result.objects[j].value._id;
if (result.objects[j].doc) {
result.objects[j].doc._id = result.objects[j].value._id;
}
} else if (result.objects[j].value._id === 'system.host.' + hostname) {
result.objects[j].value._id = 'system.host.$$__hostname__$$';
result.objects[j].value.common.name = result.objects[j].value._id;
result.objects[j].value.common.hostname = '$$__hostname__$$';
if (result.objects[j].value.native && result.objects[j].value.native.os) {
result.objects[j].value.native.os.hostname = '$$__hostname__$$';
}
result.objects[j].id = result.objects[j].value._id;
if (result.objects[j].doc) {
result.objects[j].doc._id = result.objects[j].value._id;
result.objects[j].doc.common.name = result.objects[j].value._id;
result.objects[j].doc.common.hostname = '$$__hostname__$$';
if (result.objects[j].doc.native && result.objects[j].value.native.os) {
result.objects[j].doc.native.os.hostname = '$$__hostname__$$';
}
}
}
// Read all files
if (result.objects[j].value.type === 'meta' &&
result.objects[j].value.common &&
result.objects[j].value.common.type === 'meta.user') {
count++;
copyDir(result.objects[j].id, '', tmpDir + '/backup/files/' + result.objects[j].id, function () {
count--;
if (!count) {
// todo: store letsencrypt files too => change it as letitbit will be better integrated
var configDir = tools.getConfigFileName().split('/');
configDir.pop();
configDir.push('letsencrypt');
var letsEncrypt = configDir.join('/');
if (fs.existsSync(letsEncrypt)) {
copyFolderRecursiveSync(letsEncrypt, tmpDir + '/backup');
}
var tar = require('tar');
var f = fs.createWriteStream(name);
f.on('finish', function () {
tools.rmdirRecursiveSync(tmpDir + '/backup');
if (callback) callback(pathLib.normalize(name));
});
f.on('error', function (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
});
try {
tar.create({gzip: true, cwd: tmpDir + '/'}, ['backup']).pipe(f);
} catch (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
}
}
});
}
}
fs.writeFileSync(tmpDir + '/backup/backup.json', JSON.stringify(result, null, 2));
if (!count) {
var tar = require('tar');
var f = fs.createWriteStream(name);
f.on('finish', function () {
tools.rmdirRecursiveSync(tmpDir + '/backup');
if (callback) callback(pathLib.normalize(name));
});
f.on('error', function (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
});
try {
tar.create({gzip: true, cwd: tmpDir + '/'}, ['backup']).pipe(f);
} catch (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
}
}
});
});
});
};
//--------------------------------------- RESTORE ---------------------------------------------------
function _setStateHelper(_index, statesList, stateObjects, callback) {
states.setRawState(statesList[_index], stateObjects[statesList[_index]], function () {
if ((_index % 200) === 0) console.log('host.' + hostname + ' Processed ' + _index + '/' + statesList.length + ' states');
_index++;
if (_index < statesList.length) {
setImmediate(_setStateHelper, _index, statesList, stateObjects, callback);
} else {
if (callback) callback();
}
});
}
function _setObjHelper(_index, _objects, callback) {
// Disable all adapters.
if (_objects[_index].id.match(/^system\.adapter\./) && !_objects[_index].id.match(/^system\.adapter\.admin/)) {
if (_objects[_index].doc.common && _objects[_index].doc.common.enabled) {
_objects[_index].doc.common.enabled = false;
}
}
if (_objects[_index].doc && _objects[_index].doc._rev) delete _objects[_index].doc._rev;
objects.setObject(_objects[_index].id, _objects[_index].doc, function (err /* , obj */) {
if (err) {
console.warn('host.' + hostname + ' Cannot restore ' + _objects[_index].id + ': ' + err);
}
if ((_index % 200) === 0) console.log('host.' + hostname + ' Processed ' + _index + '/' + _objects.length + ' objects');
_index++;
if (_index < _objects.length) {
setImmediate(_setObjHelper, _index, _objects, callback);
} else {
if (callback) callback();
}
});
}
function reloadAdapterObject(index, objectList, callback) {
if (objectList && index < objectList.length) {
objects.getObject(objectList[index]._id, function (err, obj) {
if (err || !obj) {
objects.setObject(objectList[index]._id, objectList[index], function () {
console.log('host.' + hostname + ' object ' + objectList[index]._id + ' created');
index++;
setImmediate(reloadAdapterObject, index, objectList, callback);
});
} else {
index++;
setImmediate(reloadAdapterObject, index, objectList, callback);
}
});
} else {
if (callback) callback();
}
}
function reloadAdaptersObjects(callback, dirs, index) {
if (!dirs) {
dirs = [];
var _modules;
if (fs.existsSync(__dirname + '/../../node_modules')) {
_modules = fs.readdirSync(__dirname + '/../../node_modules');
if (_modules) {
var regEx = new RegExp('^' + tools.appName + '\\.', 'i');
for (var i = 0; i < _modules.length; i++) {
if (regEx.test(_modules[i]) &&
dirs.indexOf(_modules[i].substring(tools.appName.length + 1)) === -1) {
dirs.push(_modules[i]);
}
}
}
}
// if installed as npm
if (fs.existsSync(__dirname + '/../../../../node_modules/' + tools.appName + '.js-controller')) {
_modules = fs.readdirSync(__dirname + '/../../..');
var regEx_ = new RegExp('^' + tools.appName + '\\.', 'i');
for (var j = 0; j < _modules.length; j++) {
// if starting from application name + '.'
if (regEx_.test(_modules[j]) &&
// If not js-controller
(_modules[j].substring(tools.appName.length + 1) !== 'js-controller') &&
dirs.indexOf(_modules[j].substring(tools.appName.length + 1)) === -1) dirs.push(_modules[j]);
}
}
if (dirs.length) {
reloadAdaptersObjects(callback, dirs, 0);
} else {
if (callback) callback();
}
} else {
if (index < dirs.length) {
upload.uploadAdapter(dirs[index], false, true, function () {
upload.uploadAdapter(dirs[index], true, true, function () {
var pkg = null;
if (!dirs[index]) {
console.error('Wrong');
}
var adapterDir = tools.getAdapterDir(dirs[index]);
if (fs.existsSync(adapterDir + '/io-package.json')) {
pkg = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
}
if (pkg && pkg.objects && pkg.objects.length) {
console.log('host.' + hostname + ' Setup "' + dirs[index] + '" adapter');
reloadAdapterObject(0, pkg.objects, function () {
index++;
setImmediate(reloadAdaptersObjects, callback, dirs, index);
});
} else {
index++;
reloadAdaptersObjects(callback, dirs, index);
}
});
});
} else {
if (callback) callback();
}
}
}
function uploadUserFiles(root, path, callback) {
if (typeof path === 'function') {
callback = path;
path = '';
}
var called = false;
if (!fs.existsSync(root)) {
callback();
return;
}
var files = fs.readdirSync(root + path);
var count = files.length;
for (var i = 0; i < files.length; i++) {
var stat = fs.statSync(root + path + '/' + files[i]);
if (stat.isDirectory()) {
called = true;
uploadUserFiles(root, path + '/' + files[i], function (err) {
if (err) console.error('Error: ' + err);
if (!--count) setImmediate(callback);
});
} else {
var parts = path.split('/');
var adapter = parts.splice(0, 2);
adapter = adapter[1];
var _path = parts.join('/') + '/' + files[i];
console.log('host.' + hostname + ' Upload user file "' + adapter + "/" + _path);
called = true;
objects.writeFile(adapter, _path, fs.readFileSync(root + path + '/' + files[i]), null, function (err) {
if (err) console.error('Error: ' + err);
if (!--count) setImmediate(callback);
});
}
}
if (!called) callback();
}
function restoreAfterStop(restartOnFinish, callback) {
// Open file
var data = fs.readFileSync(tmpDir + '/backup/backup.json').toString();
var hostname = tools.getHostName();
data = data.replace(/\$\$__hostname__\$\$/g, hostname);
fs.writeFileSync(tmpDir + '/backup/backup_.json', data);
var restore;
try {
restore = JSON.parse(data);
} catch (e) {
console.error('Cannot parse "' + tmpDir + '/backup/backup_.json": ' + e);
if (callback) callback(31);
}
// stop all adapters
console.log('host.' + hostname + ' Clear all objects and states...');
cleanDatabase(false, function () {
console.log('host.' + hostname + ' done.');
// upload all data into DB
// restore ioBorker.json
if (restore.config) fs.writeFileSync(tools.getConfigFileName(), JSON.stringify(restore.config, null, 2));
var sList = [];
for (var state in restore.states) {
if (restore.states.hasOwnProperty(state)) {
sList.push(state);
}
}
_setStateHelper(0, sList, restore.states, function () {
console.log(sList.length + ' states restored.');
_setObjHelper(0, restore.objects, function () {
console.log(restore.objects.length + ' objects restored.');
// Required for upload adapter
mime = require('mime');
// Load user files into DB
uploadUserFiles(tmpDir + '/backup/files', function () {
// reload objects of adapters
reloadAdaptersObjects(function () {
// Reload host objects
var pckgio = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
reloadAdapterObject(0, pckgio ? pckgio.objects : null, function () {
if (restartOnFinish) {
restartController(callback);
} else {
if (callback) callback();
}
});
});
});
});
});
});
}
this.listBackups = function () {
var dir = getBackupDir();
var result = [];
if (fs.existsSync(dir)) {
var files = fs.readdirSync(dir);
for (var i = 0; i < files.length; i++) {
if (files[i].match(/\.tar\.gz$/i)) {
result.push(files[i]);
}
}
return result;
} else {
return result;
}
};
this.restoreBackup = function (name, callback) {
var backups;
if (!name && name !== 0) {
// List all available backups
console.log('Please specify one of the backup names:');
backups = this.listBackups();
backups.sort(function (a, b) {
return b > a;
});
if (backups.length) {
for (var t = 0; t < backups.length; t++){
console.log(backups[t] + ' or ' + backups[t].replace('_backup' + tools.appName + '.tar.gz', '') + ' or ' + t);
}
} else {
console.warn('No backups found');
}
processExit(10);
}
if (!options.cleanDatabase) throw "Invalid arguments: cleanDatabase is missing";
if (!options.restartController) throw "Invalid arguments: restartController is missing";
if (parseInt(name, 10).toString() === name.toString()) {
backups = this.listBackups();
backups.sort(function (a, b) {
return b > a;
});
name = backups[parseInt(name, 10)];
console.log('host.' + hostname + ' Using backup file ' + name);
}
name = (name || '').toString().replace(/\\/g, '/');
if (name.indexOf('/') === -1) {
name = getBackupDir() + name;
var regEx = new RegExp('_backup' + tools.appName, 'i');
if (!regEx.test(name)) name += '_backup' + tools.appName;
if (!name.match(/\.tar\.gz$/i)) name += '.tar.gz';
}
if (!fs.existsSync(name)) {
console.error('host.' + hostname + ' Cannot find ' + name);
processExit(11);
}
var tar = require('tar');
if (fs.existsSync(tmpDir + '/backup/backup.json')) {
fs.unlinkSync(tmpDir + '/backup/backup.json');
}
tar.extract({
file: name,
cwd: tmpDir
}, function (err) {
if (err) {
console.error('host.' + hostname + ' Cannot extract from file "' + name + '"');
processExit(9);
}
if (!fs.existsSync(tmpDir + '/backup/backup.json')) {
console.error('host.' + hostname + ' Cannot find extracted file from file "' + tmpDir + '/backup/backup.json"');
processExit(9);
}
// Stop controller
var daemon = require('daemonize2').setup({
main: '../../controller.js',
name: tools.appName + ' controller',
pidfile: __dirname + '/../' + tools.appName + '.pid',
cwd: '../../',
stopTimeout: 1000
});
daemon.on('error', function (/* error */) {
restoreAfterStop(false, callback);
});
daemon.on('stopped', function () {
restoreAfterStop(true, callback);
});
daemon.on('notrunning', function () {
console.log('host.' + hostname + ' OK.');
restoreAfterStop(false, callback);
});
daemon.stop();
});
}
}
module.exports = BackupRestore;

1397
lib/setup/setupInstall.js Normal file

File diff suppressed because it is too large Load Diff

76
lib/setup/setupLicense.js Normal file
View File

@@ -0,0 +1,76 @@
'use strict';
function License(options) {
const fs = require('fs');
const jwt = require('jsonwebtoken');
options = options || {};
let objects = options.objects;
// read info from '/etc/iob_vendor.json' and executes instructions stored there
this.setLicense = file => {
if (fs.existsSync(file)) {
try {
file = fs.readFileSync(file).toString('utf8');
} catch (e) {
return Promise.reject(e);
}
}
// try to encode license
let license = jwt.decode(file);
if (!license) {
return Promise.reject('License cannot be decoded');
}
if (!license.name) {
return Promise.reject('Name not found in the license');
}
let adapter = license.name.split('.')[1];
if (!adapter) {
return Promise.reject(`Invalid license name ${license.name}`);
}
// read all instances of this adapter
return objects.getObjectListAsync({
startkey: 'system.adapter.' + adapter + '.',
endkey: 'system.adapter.' + adapter + '.\u9999'
}, {checked: true}).then(arr => {
let promises = [];
if (arr && arr.rows && arr.rows.length) {
for (let g = 0; g < arr.rows.length; g++) {
let obj = arr.rows[g].value;
if (obj && obj.type === 'instance') {
obj.native = obj.native || {};
obj.native.license = file;
promises.push(objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`Instance "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
}));
}
}
}
if (!promises.length) {
console.warn(`no instances of ${adapter} found`);
if (arr && arr.rows && arr.rows.length) {
for (let g = 0; g < arr.rows.length; g++) {
let obj = arr.rows[g].value;
if (obj && obj.type === 'adapter') {
obj.native = obj.native || {};
obj.native.license = file;
promises.push(objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`Adapter "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
}));
}
}
}
}
if (!promises.length) {
console.error(`no installations of ${adapter} found`);
}
return Promise.all(promises);
});
};
return this;
}
module.exports = License;

627
lib/setup/setupList.js Normal file
View File

@@ -0,0 +1,627 @@
'use strict';
function List(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var that = this;
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
var objects = options.objects;
var states = options.states;
var processExit = options.processExit;
function perm2str(perm) {
var result = '';
// user
result += (perm & 0x400) ? 'r' : '-';
result += (perm & 0x200) ? 'w' : '-';
result += (perm & 0x100) ? 'x' : '-';
// group
result += (perm & 0x040) ? 'r' : '-';
result += (perm & 0x020) ? 'w' : '-';
result += (perm & 0x010) ? 'x' : '-';
// any
result += (perm & 0x004) ? 'r' : '-';
result += (perm & 0x002) ? 'w' : '-';
result += (perm & 0x001) ? 'x' : '-';
return result;
}
this.showFileHeader = function () {
console.log(' Modified at |Permission| User | Group | Size | Name');
console.log('----------------+----------+--------------+--------------+------+---------');
};
this.showFile = function (adapter, path, file) {
//drwxr-xr-x 1 odroid odroid 43 Oct 3 2013 .xsessionrc
var text = '';
var time;
if (file.modifiedAt) {
var ts = new Date(file.modifiedAt);
time = ts.toISOString();
time = time.replace('T', ' ');
time = time.substring(0, 16) + ' ';
} else {
time = Array(18).join(' ');
}
text += time;
if (file.acl){
text += (file.isDir ? 'd' : '-') + perm2str(file.acl.permissions || 0);
var owner = file.acl.owner;
if (owner) {
owner = owner.substring(12);
if (owner.length < 15) owner = Array(15 - owner.length).join(' ') + owner;
} else {
owner = Array(15).join(' ');
}
text += ' ' + owner;
var group = file.acl.ownerGroup;
if (group) {
group = group.substring(13);
if (group.length < 15) group = Array(15 - group.length).join(' ') + group;
} else {
group = Array(15).join(' ');
}
text += ' ' + group;
} else {
text += (file.isDir ? 'd' : '-') + '?????????' + Array(31).join(' ');
}
var size = (file.stats) ? file.stats.size.toString() : '';
if (size.length < 7) size = Array(7 - size.length).join(' ') + size;
text += ' ' + size + ' ' + adapter + ((!path || path[0] === '/') ? '' : '/') + path + '/' + file.file;
if (file.isDir) {
text += '/';
console.log(text);
} else {
console.log(text);
}
};
this.showObjectHeader = function () {
console.log('ObjectAC | StateAC | User | Group | ID');
console.log('---------+---------+--------------+--------------+--------------');
};
this.showObject = function (obj) {
//drwxr-xr-x 1 odroid odroid 43 Oct 3 2013 .xsessionrc
var text = '';
if (obj.acl){
text += perm2str(obj.acl.object || 0) + ' ' + ((obj.type === 'state') ? perm2str(obj.acl.state || 0) : ' ');
var owner = obj.acl.owner;
if (owner) {
owner = owner.substring(12);
if (owner.length < 15) owner = Array(15 - owner.length).join(' ') + owner;
} else {
owner = Array(15).join(' ');
}
text += ' ' + owner;
var group = obj.acl.ownerGroup;
if (group) {
group = group.substring(13);
if (group.length < 15) group = Array(15 - group.length).join(' ') + group;
} else {
group = Array(15).join(' ');
}
text += ' ' + group;
} else {
text += '?????????' + ((obj.type === 'state') ? ' ?????????' : ' ') + Array(31).join(' ');
}
text += ' ' + obj._id;
console.log(text);
};
this.listDirectory = function (adapter, path, allFiles, callback) {
if (typeof path === 'function') {
callback = path;
path = '';
allFiles = [];
}
if (typeof allFiles === 'function') {
callback = allFiles;
allFiles = [];
}
allFiles = allFiles || [];
path = path || '';
objects.readDir(adapter, path, null, function (err, files) {
if (err && err.code === 'ENOTDIR') {
var pos = path.lastIndexOf('/');
if (pos !== -1) {
var dir = path.substring(0, pos);
var fname = path.substring(pos + 1);
objects.readDir(adapter, dir, null, function (err, files) {
if (err) {
console.log('Cannot read "' + path + '": ' + err);
callback(allFiles);
} else {
for (var f = 0; f < files.length; f++) {
if (files[f].file === '.' || files[f].file === '..') continue;
if (files[f].file === fname) {
allFiles.push({adapter: adapter, path: path, file: files[f]});
break;
}
}
callback(allFiles);
}
});
} else {
callback(allFiles);
}
} else {
var count = 0;
for (var f = 0; f < files.length; f++) {
if (files[f].file === '.' || files[f].file === '..') continue;
allFiles.push({adapter: adapter, path: path, file: files[f]});
if (files[f].isDir) {
count++;
that.listDirectory(adapter, path + '/' + files[f].file, allFiles, function () {
if (!--count && callback) {
callback(allFiles);
}
});
}
}
if (!count && callback) callback(allFiles);
}
});
};
function pattern2RegEx(pattern) {
if (pattern !== '*') {
if (pattern[0] === '*' && pattern[pattern.length - 1] !== '*') pattern += '$';
if (pattern[0] !== '*' && pattern[pattern.length - 1] === '*') pattern = '^' + pattern;
}
pattern = pattern.replace(/\./g, '\\.');
pattern = pattern.replace(/\*/g, '.*');
return pattern;
}
function sortFiles (a, b) {
var a1 = a.path + a.file.file;
if (a1[0] !== '/') a1 = '/' + a1;
var b1 = b.path + b.file.file;
if (b1[0] !== '/') b1 = '/' + b1;
return a1.localeCompare(b1);
}
this.listAdaptersFiles = function (adapters, filter, callback) {
if (typeof filter === 'function') {
callback = filter;
filter = null;
}
if (filter) {
filter = filter.replace('*', '');
if (filter[filter.length - 1] === '/') filter = filter.substring(0, filter.length - 1);
}
if (adapters && adapters.length) {
var adapter = adapters.pop();
this.listDirectory(adapter, filter, function (files) {
files.sort(sortFiles);
that.showFileHeader();
for (var k = 0; k < files.length; k++) {
if (filter && filter !== (files[k].path + '/'+ files[k].file.file).substring(0, filter.length)) continue;
that.showFile(files[k].adapter, files[k].path, files[k].file);
}
that.listDirectory(adapter + '.admin', filter, function (files) {
files.sort(sortFiles);
for (var k = 0; k < files.length; k++) {
if (filter && filter !== (files[k].path + '/'+ files[k].file.file).substring(0, filter.length)) continue;
that.showFile(files[k].adapter, files[k].path, files[k].file);
}
that.listAdaptersFiles(adapters, callback);
});
});
} else {
if (callback) callback ();
}
};
this.list = function (type, filter, flags) {
switch (type) {
case 'objects':
case 'o':
objects.getObjectList(null, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx(filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
if (objs.rows[i].value.type) {
var id = objs.rows[i].value._id;
var type = objs.rows[i].value.type;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (type.length < 10) type += Array(10 - type.length).join(' ');
console.log(id + ': ' + type + ' - ' + (objs.rows[i].value.common.name || ''));
} else {
console.log(objs.rows[i].value._id);
}
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'states':
case 's':
states.getKeys(filter || '*', function (err, keys) {
if (err) {
console.error(err);
processExit(23);
}
states.getStates(keys, function (err, states) {
if (err) {
console.error(err);
processExit(23);
}
for (var i = 0; i < states.length; i++) {
var id = keys[i];
var from = states[i].from || '';
var type = typeof states[i].val;
if (type.length < 10) type += Array(10 - type.length).join(' ');
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (from.length < 30) from += Array(30 - from.length).join(' ');
console.log(id + ': from [' + from + '] (' + type + ') ' + (states[i].ack ? ' ack': 'not ack') + ' ' + JSON.stringify(states[i].val));
}
setTimeout(function () {
processExit();
}, 1000);
});
});
break;
case 'adapters':
case 'a':
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('system.adapter.' + filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'adapter') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id;
var name = objs.rows[i].value.common.name;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (name.length < 12) name += Array(12 - name.length).join(' ');
var text = id + ': ' + name + ' - ' +
objs.rows[i].value.common.version;
console.log(text);
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'instances':
case 'i':
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('system.adapter.' + filter)) : null;
objs.rows.sort(function (a, b) {
if (a.id > b.id) return 1;
if (a.id < b.id) return -1;
return 0;
});
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'instance') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
if (flags.enabled && !objs.rows[i].value.common.enabled) continue;
if (flags.disabled && objs.rows[i].value.common.enabled) continue;
if (flags.port && objs.rows[i].value.native.port === undefined) continue;
if (flags.ssl && objs.rows[i].value.native.secure === undefined) continue;
if (flags.ip && objs.rows[i].value.native.bind === undefined) continue;
var id = objs.rows[i].value._id;
var name = objs.rows[i].value.common.name;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (name && name.length < 12) name += Array(12 - name.length).join(' ');
var text = id + ': ' + (name || '') + ' - ' +
(objs.rows[i].value.common.enabled ? ' enabled' : 'disabled');
if (objs.rows[i].value.native && objs.rows[i].value.native.port) {
text += ', port: ' + objs.rows[i].value.native.port;
}
if (objs.rows[i].value.native && objs.rows[i].value.native.bind) {
text += ', bind: ' + objs.rows[i].value.native.bind;
}
if (objs.rows[i].value.native && objs.rows[i].value.native.secure) {
text += ' (SSL)';
}
if (objs.rows[i].value.native && objs.rows[i].value.native.defaultUser) {
text += ', run as: ' + objs.rows[i].value.native.defaultUser;
}
console.log(text);
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'users':
case 'u':
objects.getObjectList({startkey: 'system.user.', endkey: 'system.user.\u9999'}, function (err, objs) {
objects.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, groups) {
var reg = filter ? new RegExp(pattern2RegEx('system.user.' + filter)) : null;
console.log(' ID | Name | Active | Groups');
console.log('---------------------------------------+-------------+----------+--------------');
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'user') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id;
var name = objs.rows[i].value.common.name;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (name.length < 12) name += Array(12 - name.length).join(' ');
var text = id + '| ' + name + ' | ' +
(objs.rows[i].value.common.enabled ? ' enabled' : 'disabled') + ' |';
var gs = [];
// find all groups
for (var g = 0; g < groups.rows.length; g++) {
if (groups.rows[g].value.common.members && groups.rows[g].value.common.members.indexOf(objs.rows[i].value._id) !== -1) {
gs.push(groups.rows[g].value._id.substring(13));
}
}
console.log(text + ' ' + gs.join(', '));
}
}
setTimeout(function () {
processExit();
}, 1000);
});
});
break;
case 'groups':
case 'g':
objects.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('system.group.' + filter)) : null;
console.log('');
console.log(' system.group | object | state | file | user | others | users');
console.log(' | l r w d | l r w d | l r w c d | w c d | |');
console.log('--------------------+---------+---------+-----------+-------+------------------------+---------');
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'group') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id.substring(13);
//var name = objs.rows[i].value.common.name;
if (id === 'administrator') {
objs.rows[i].value.common.acl = {
file: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},
object: {
read: true,
write: true,
'delete': true,
list: true
},
state: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},
user: {
write: true,
create: true,
'delete': true
},
other: {
execute: true,
http: true,
sendto: true
}
};
}
if (id.length < 20) id += Array(20 - id.length).join(' ');
var text = id;
text += ' | ';
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.object) {
text += (objs.rows[i].value.common.acl.object.list ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.object.read ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.object.write ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.object.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.state) {
text += ' ';
text +=(objs.rows[i].value.common.acl.state.list ? '+': '-') + ' ';
text +=(objs.rows[i].value.common.acl.state.read ? '+': '-') + ' ';
text +=(objs.rows[i].value.common.acl.state.write ? '+': '-') + ' ';
text +=(objs.rows[i].value.common.acl.state.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.file) {
text += ' ';
text += (objs.rows[i].value.common.acl.file.list ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.read ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.write ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.create ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.users) {
text += ' ';
text += (objs.rows[i].value.common.acl.users.write ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.users.create ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.users.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.other) {
text += ' ';
var others = '';
for (var r in objs.rows[i].value.common.acl.other) {
others += r + (objs.rows[i].value.common.acl.other[r] ? '+' : '-') + ' ';
}
if (others.length < 23) others += Array(23 - others.length).join(' ');
text += others + '|';
} else {
text += Array(25).join(' ') + '|';
}
//if (name.length < 30) name += Array(30 - name.length).join(' ');
if ( objs.rows[i].value.common.members) {
for (var m = 0; m < objs.rows[i].value.common.members.length; m++) {
objs.rows[i].value.common.members[m] = objs.rows[i].value.common.members[m].substring(12);
}
text += ' ' + objs.rows[i].value.common.members.join(', ');
}
//text += '| (' + name + ')';
console.log(text);
}
}
console.log('--------------------+---------+---------+-----------+-------+------------------------+---------');
console.log('Legend: (l)ist, (r)ead, (w)rite, (c)reate, (d)elete');
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'h':
case 'hosts':
objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.\u9999'}, function (err, objs) {
states.getKeys('system.host.*', function (err, keys) {
states.getStates(keys, function (err, states) {
var reg = filter ? new RegExp(pattern2RegEx('system.host.' + filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'host') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id.substring(12);
var name = objs.rows[i].value.common.name;
if (id.length < 20) id += Array(20 - id.length).join(' ');
var hostname = objs.rows[i].value.common.hostname;
if (hostname.length < 15) hostname += Array(15 - hostname.length).join(' ');
var version = objs.rows[i].value.common.installedVersion;
var alive = '';
var uptime = '';
for (var k = 0; k < keys.length; k++) {
if (keys[k] === objs.rows[i].value._id + '.alive') {
alive = states[k].val;
}
if (keys[k] === objs.rows[i].value._id + '.uptime') {
uptime = states[k].val;
}
}
alive = alive ? 'alive' : ' dead';
//if (uptime.toString().length < 10) uptime = Array(10 - uptime.toString().length).join(' ') + uptime.toString();
if (!uptime) uptime = '-';
var text = id + ' ' + name + ' (version: ' + version + ', hostname: ' + hostname + ', ' + alive + ', uptime: ' + uptime + ')';
// todo
console.log(text);
}
}
setTimeout(function () {
processExit();
}, 1000);
});
});
});
break;
case 'enums':
case 'e':
objects.getObjectList({startkey: 'enum.', endkey: 'enum.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('enum.' + filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'enum') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
console.log('\n=====================================================================================');
var id = objs.rows[i].value._id.substring(5);
var name = objs.rows[i].value.common.name;
if (id.length < 20) id += Array(20 - id.length).join(' ');
console.log(id + '(' + name + ')');
console.log('-------------------------------------------------------------------------------------');
if (objs.rows[i].value.common.members) {
console.log(objs.rows[i].value.common.members.join(', '));
}
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'files':
case 'f':
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, objs) {
var adapter = filter || null;
var names = filter ? filter.split('/') : null;
if (names && !names[0]) names.splice(0, 1);
var adapters = [];
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type === 'adapter') {
if (adapter && objs.rows[i].value.common.name !== names[0]) continue;
adapters.push(objs.rows[i].value.common.name);
} else if (objs.rows[i].value.type === 'instance') {
if (adapter && objs.rows[i].value._id.substring(15) !== names[0]) continue;
adapters.push(objs.rows[i].value._id.substring(15));
}
}
if (names) names.shift();
that.listAdaptersFiles(adapters, names ? names.join('/') : null, function () {
setTimeout(function () {
processExit();
}, 1000);
});
});
break;
default:
if (type) {
console.error('Unknown type: ' + type);
processExit(23);
} else {
console.log('Please specify type: objects, states, instances, adapters, users, groups, enums, files');
processExit();
}
break;
}
};
}
module.exports = List;

294
lib/setup/setupMultihost.js Normal file
View File

@@ -0,0 +1,294 @@
'use strict';
function Multihost(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var configName = tools.getConfigFileName();
var that = this;
options = options || {};
var params = options.params || {};
var objects = options.objects;
function getConfig() {
var config;
// read actual configuration
try {
if (fs.existsSync(configName)) {
config = JSON.parse(fs.readFileSync(configName, 'utf8'));
} else {
config = require(__dirname + '/../../conf/' + tools.appName + '-dist.json');
}
} catch (e) {
config = require(__dirname + '/../../conf/' + tools.appName + '-dist.json');
}
return config;
}
function leftPad(text, len) {
text = text || '';
if (text.length >= len) return len;
return new Array(len - text.length).join(' ') + text;
}
this.showHosts = function (list) {
if (!list || !list.length) {
console.info('Nothing found');
} else {
for (var i = 0; i < list.length; i++) {
console.log((i + 1) + ' | ' + leftPad(list[i].hostname, 20) + ' | ' + (list[i].slave ? 'slave' : ' host') + ' | ' + leftPad(list[i].ip, 20) + ' | ' + JSON.stringify(list[i].info));
}
}
};
this.browse = function (callback) {
var MHClient = require(__dirname + '/../multihostClient');
var mhClient = new MHClient();
mhClient.browse(2000, params.debug, function (err, list) {
if (err) {
callback('Cannot browse: ' + err);
} else {
callback(null, list);
}
});
};
function showMHState(config, changed, callback) {
if (config.multihostService.enabled) {
if (config.objects.type === 'file' && (config.objects.host === '127.0.0.1' || config.objects.host === 'localhost')) {
console.log('Server accept connections for objects on all IP addresses.');
config.objects.host = '0.0.0.0';
changed = true;
} else if (config.objects.type === 'redis') {
console.log('Please check the binding of redis service. By default it is only local: http://download.redis.io/redis-stable/redis.conf\nChange "bind 127.0.0.1" to "bind 0.0.0.0" or to others.')
}
if (config.states.type === 'file' && (config.states.host === '127.0.0.1' || config.states.host === 'localhost')) {
console.log('Server accept connections for states on all IP addresses.');
config.states.host = '0.0.0.0';
changed = true;
} else if (config.states.type === 'redis') {
console.log('Please check the binding of redis service. By default it is only local: http://download.redis.io/redis-stable/redis.conf\nChange "bind 127.0.0.1" to "bind 0.0.0.0" or to others.')
}
}
if (!changed) {
console.log('Nothing changed.');
} else {
fs.writeFileSync(configName, JSON.stringify(config, null, 2));
console.log('Please restart yunkong2: "yunkong2 restart"');
}
console.log('\n');
console.log('Miltihost: ' + (config.multihostService.enabled ? 'enabled' : 'disabled'));
console.log('Authentication: ' + (config.multihostService.secure ? 'enabled' : 'disabled'));
console.log('Objects: ' + config.objects.type + ' on ' + config.objects.host);
console.log('States: ' + config.states.type + ' on ' + config.states.host);
callback();
}
this.enable = function (isEnable, callback) {
var changed = false;
var config = getConfig();
config.multihostService = config.multihostService || {enabled: false, secure: true};
if (isEnable && !config.multihostService.enabled) {
changed = true;
config.multihostService.enabled = true;
config.multihostService.password = '';
console.log('Miltihost activated.')
} else if (!isEnable && config.multihostService.enabled) {
changed = true;
config.multihostService.enabled = false;
config.multihostService.password = '';
console.log('Miltihost deactivated.')
}
if (params.secure === undefined) {
params.secure = true;
}
if (isEnable && (config.multihostService.secure !== params.secure || (config.multihostService.secure && !config.multihostService.password))) {
changed = true;
config.multihostService.secure = params.secure;
console.log('Authentication ' + (params.secure ? 'activated' : 'deactivated') + '.');
if (config.multihostService.secure) {
var prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
var schema = {
properties: {
password: {
description: 'Enter secret phrase for connection:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
},
passwordRepeat: {
description: 'Repeat secret phrase for connection:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, password) {
if (password && password.password) {
if (password.password !== password.passwordRepeat) {
callback('Secret phrases are not equal!');
} else {
objects.getObject('system.config', function (err, obj) {
tools.encryptPhrase(obj.native.secret, password.password, function (encoded) {
config.multihostService.password = encoded;
showMHState(config, changed, callback);
})
});
}
} else {
callback('No secret phrase entered!');
}
});
} else {
showMHState(config, changed, callback);
}
} else {
showMHState(config, changed, callback);
}
};
this.status = function (callback) {
var config = getConfig();
config.multihostService = config.multihostService || {enabled: false, secure: true};
showMHState(config, false, callback);
};
function readPassword(callback) {
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function hidden(query, callback) {
var stdin = process.openStdin();
process.stdin.on('data', function (char) {
char = char + '';
switch (char) {
case '\n':
case '\r':
case '\u0004':
stdin.pause();
break;
default:
process.stdout.write('\x1B[2K\x1B[200D' + query + new Array(rl.line.length + 1).join('*'));
break;
}
});
rl.question(query, function (value) {
rl.history = rl.history.slice(1);
callback(value);
});
}
hidden('Enter secret phrase for connection: ', function(password) {
callback(password);
});
}
function connect(mhClient, ip, pass, callback) {
mhClient.connect(ip, pass, function (err, oObjects, oStates, ipHost) {
if (err) {
callback('Cannot connect to "' + ip + '": ' + err);
} else if (oObjects && oStates) {
var config = getConfig();
config.objects = oObjects;
config.states = oStates;
if (config.objects.host === '127.0.0.1' || config.objects.host === 'localhost' || config.states.host === '127.0.0.1' || config.states.host === 'localhost') {
callback('IP Address of the host is 127.0.0.1. It accepts no connections. Please change.');
} else {
if (config.states.host === '0.0.0.0') {
config.states.host = ipHost;
}
if (config.objects.host === '0.0.0.0') {
config.objects.host = ipHost;
}
fs.writeFileSync(configName, JSON.stringify(config, null, 2));
console.log('Config ok. Please restart yunkong2: "yunkong2 restart"');
callback();
}
} else {
callback('No configuration received!');
}
});
}
this.connect = function (number, pass, callback) {
if (typeof pass === 'function') {
callback = pass;
pass = null;
}
if (typeof number === 'function') {
callback = number;
number = null;
}
var MHClient = require(__dirname + '/../multihostClient');
var mhClient = new MHClient();
mhClient.browse(2000, params.debug, function (err, list) {
if (err) {
callback('Cannot browse: ' + err);
} else {
that.showHosts(list);
if (number !== null && number !== undefined && parseInt(number, 10) > 0) {
number = parseInt(number, 10);
if (list && number < list.length + 1) {
if (!pass) {
callback('No password defined: please use "multihost connect <NUMBER> <PASSWORD>"');
} else {
connect(mhClient, list[number - 1].ip, pass, callback);
}
} else {
callback('Invalid index: ' + number);
}
} else
if (list && list.length) {
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Please select host [1]: ', function (answer) {
if (answer === '' || answer === null || answer === undefined) {
answer = 1;
}
answer = parseInt(answer, 10) - 1;
if (!list[answer]) {
rl.close();
callback('Invalid index: ' + answer);
} else {
if (list[answer].auth) {
readPassword(function (password) {
if (password) {
connect(mhClient, list[answer].ip, password, callback);
} else {
callback('No password entered!');
}
});
} else {
connect(mhClient, list[answer].ip, null, callback);
}
}
});
} else {
callback(null, list);
}
}
});
};
}
module.exports = Multihost;

279
lib/setup/setupRepo.js Normal file
View File

@@ -0,0 +1,279 @@
'use strict';
function Repo(options) {
const tools = require(__dirname + '/../tools.js');
const extend = require('node.extend');
let defaultSystemRepo = {
"common": {
"name": "System repositories",
"dontDelete": true
},
"native": {
"repositories": {
"default": {
"link": "http://download.yunkong2.net/sources-dist.json",
"json": null
},
"latest": {
"link": "http://download.yunkong2.net/sources-dist-latest.json",
"json": null
}
}
},
"_id": "system.repositories",
"type": "config"
};
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
var objects = options.objects;
function updateRepo(repoUrl, callback) {
var result = {};
if (!repoUrl || typeof repoUrl !== 'object') {
tools.getRepositoryFile(repoUrl, function (err, sources) {
updateRepo(sources, callback);
});
return;
}
var sources = repoUrl;
var downloads = [];
function download() {
if (downloads.length < 1) {
console.log('update done');
if (callback) callback(result);
} else {
var name = downloads.pop();
if (sources[name].version) {
result[name] = sources[name];
} else if (sources[name].meta) {
tools.getJson(sources[name].meta, function (ioPack) {
if (ioPack && ioPack.common) {
result[name] = extend(true, sources[name], ioPack.common);
}
setImmediate(download);
});
return;
} else if (sources[name].url) {
console.log('Cannot get version of "' + name + '".');
result[name] = sources[name];
} else {
console.log('Cannot get any information of "' + name + '". Ignored.');
}
setImmediate(download);
}
}
// Read repository file, local or by url
for (var name in sources) {
if (!sources.hasOwnProperty(name)) continue;
downloads.push(name);
}
download(/*sources*/);
}
this.showRepo = function (repoUrl, flags, callback) {
if (typeof flags === 'function') {
callback = flags;
flags = {};
}
function showRepoResult(_name, sources) {
var installed = tools.getInstalledInfo();
var updatable;
var keys = [];
for (var key in sources) {
if (!sources.hasOwnProperty(key)) continue;
keys.push(key);
}
keys.sort();
for (var i = 0; i < keys.length; i++) {
var name = keys[i];
if (!sources.hasOwnProperty(name)) continue;
updatable = false;
var text = (sources[name].controller ? 'Controller ' : 'Adapter ');
text += '"' + name + '"' + ((name.length < 15) ? new Array(15 - name.length).join(' ') : '');
var tLen = 10;
if (name.length >= 15) tLen -= (name.length > tLen ? 1 : 0);
if (tLen < 0) tLen = 0;
if (sources[name].version) {
text += ': ' + sources[name].version + ((sources[name].version.length < tLen) ? new Array(tLen - sources[name].version.length).join(' ') : '');
} else {
text += new Array(tLen).join(' ')
}
if ((flags.installed || flags.i) && !installed[name]) {
continue;
}
if (installed[name] && installed[name].version) {
text += ', installed ' + installed[name].version;
if (sources[name].version !== installed[name].version &&
sources[name].version &&
!tools.upToDate(sources[name].version, installed[name].version)) {
updatable = true;
text += ' [Updateable]';
}
}
if ((flags.updatable || flags.u) && !updatable) {
continue;
}
console.log(text);
}
}
// Get the repositories
objects.getObject('system.config', function (err, sysConfig) {
objects.getObject('system.repositories', function (err, obj) {
if (err || !obj) {
console.log('Error: Object "system.config" not found');
} else {
if (!obj.native || !obj.native.repositories) {
console.log('Error: no repositories found in the "system.config');
} else {
repoUrl = repoUrl || sysConfig.common.activeRepo;
console.log('Used repository: ' + repoUrl);
// If known repository
if (obj.native.repositories[repoUrl]) {
if (typeof obj.native.repositories[repoUrl] === 'string') {
obj.native.repositories[repoUrl] = {
link: obj.native.repositories[repoUrl],
json: null
};
}
updateRepo(obj.native.repositories[repoUrl].link, function (sources) {
obj.native.repositories[repoUrl].json = sources;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function () {
showRepoResult(repoUrl, sources);
if (callback) callback();
});
});
} else {
updateRepo(repoUrl, function (sources) {
showRepoResult(null, sources);
if (callback) callback();
});
}
}
}
});
});
};
this.showRepoStatus = function (callback) {
objects.getObject('system.repositories', function (err, obj) {
if (err || !obj) {
console.error('Cannot get list: ' + err);
callback(102);
} else {
if (obj.native.repositories) {
for (var r in obj.native.repositories) {
if (obj.native.repositories.hasOwnProperty(r)){
console.log(r + (r.length < 12 ? new Array(12 - r.length).join(' ') : '') + ': ' + obj.native.repositories[r].link);
}
}
objects.getObject('system.config', function (err, obj) {
if (obj) {
console.log('\nActive repo: ' + obj.common.activeRepo);
}
callback();
});
} else {
console.error('Cannot get list: ' + err);
callback(102);
}
}
});
};
this.add = function (repoName, repoUrl, callback) {
objects.getObject('system.repositories', function (err, obj) {
if (err) {
callback && callback(err);
} else
if (!obj) {
obj = defaultSystemRepo;
}
if (obj.native.repositories[repoName]) {
callback && callback('Repository "' + repoName + '" yet exists: ' + obj.native.repositories[repoName].link);
} else {
obj.native.repositories[repoName] = {
link: repoUrl,
json: null
};
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.repositories', obj, callback);
}
});
};
this.del = function (repoName, callback) {
objects.getObject('system.config', function (err, obj) {
if (err) {
callback && callback(err);
} else {
if (obj.common.activeRepo === repoName) {
callback && callback('Cannot delete active repository: ' + repoName);
} else {
objects.getObject('system.repositories', function (err, obj) {
if (err) {
callback && callback(err);
} else if (!obj) {
callback && callback();
} else {
if (!obj.native.repositories[repoName]) {
callback && callback('Repository "' + repoName + '" not found.');
} else {
delete obj.native.repositories[repoName];
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.repositories', obj, callback);
}
}
});
}
}
});
};
this.setActive = function (repoName, callback) {
objects.getObject('system.repositories', function (err, obj) {
if (err) {
callback && callback(err);
} else
if (!obj) {
obj = defaultSystemRepo;
}
if (!obj.native.repositories[repoName]) {
callback && callback('Repository "' + repoName + '" not found.');
} else {
objects.getObject('system.config', function (err, obj) {
if (err) {
callback && callback(err);
} else {
obj.common.activeRepo = repoName;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.config', obj, callback);
}
});
}
})
};
}
module.exports = Repo;

199
lib/setup/setupSetup.js Normal file
View File

@@ -0,0 +1,199 @@
'use strict';
function Setup(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
options = options || {};
var processExit = options.processExit;
var dbConnect = options.dbConnect;
var params = options.params;
var password;
var objects;
function mkpathSync(rootpath, dirpath) {
// Remove filename
dirpath = dirpath.split('/');
dirpath.pop();
if (!dirpath.length) return;
for (var i = 0; i < dirpath.length; i++) {
rootpath += dirpath[i] + '/';
if (!fs.existsSync(rootpath)) {
if (dirpath[i] !== '..') {
fs.mkdirSync(rootpath);
} else {
throw 'Cannot create ' + rootpath + dirpath.join('/');
}
}
}
}
function setupReady(callback) {
if (!callback) {
console.log('database setup done. you can add adapters and start ' + tools.appName + ' now');
processExit(0);
} else {
callback();
}
}
function dbSetup(iopkg, callback) {
if (iopkg.objects && iopkg.objects.length > 0) {
var obj = iopkg.objects.pop();
objects.getObject(obj._id, function (err, _obj) {
if (err || !_obj) {
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function () {
console.log('object ' + obj._id + ' created');
setTimeout(dbSetup, 25, iopkg, callback);
});
} else {
console.log('object ' + obj._id + ' yet exists');
setTimeout(dbSetup, 25, iopkg, callback);
}
});
} else {
tools.createUuid(objects, function () {
// check if encrypt secret exists
objects.getObject('system.config', function (err, obj) {
if (obj && (!obj.native || !obj.native.secret)) {
require('crypto').randomBytes(24, function (ex, buf) {
obj.native = obj.native || {};
obj.native.secret = buf.toString('hex');
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.config', obj, function () {
setupReady(callback);
});
});
} else {
setupReady(callback);
}
});
});
}
}
function setupObjects(callback) {
dbConnect(params, function (_objects, _states) {
objects = _objects;
var iopkg = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
dbSetup(iopkg, callback);
});
}
this.setup = function (callback, ignoreIfExist, useRedis) {
password = require(__dirname + '/../password');
var config;
var isCreated = false;
var platform = require('os').platform();
var otherInstallDirs = [];
// Delete files for other OS
if (platform.match(/^win/)) {
otherInstallDirs.push(__dirname + '/../../' + tools.appName);
otherInstallDirs.push(__dirname + '/../../killall.sh');
otherInstallDirs.push(__dirname + '/../../reinstall.sh');
} else {
otherInstallDirs.push(__dirname + '/../../_service_' + tools.appName + '.bat');
otherInstallDirs.push(__dirname + '/../../' + tools.appName + '.bat');
// copy scripts to root directory
if (fs.existsSync(__dirname + '/../../../../node_modules/')) {
try {
if (fs.existsSync(__dirname + '/../../killall.sh')) {
fs.writeFileSync(__dirname + '/../../../../killall.sh', fs.readFileSync(__dirname + '/../../killall.sh'));
}
if (fs.existsSync(__dirname + '/../../reinstall.sh')) {
fs.writeFileSync(__dirname + '/../../../../reinstall.sh', fs.readFileSync(__dirname + '/../../reinstall.sh'));
}
if (fs.existsSync(__dirname + '/../..' + tools.appName)) {
fs.writeFileSync(__dirname + '/../../../..' + tools.appName, fs.readFileSync(__dirname + '/../..' + tools.appName));
}
} catch (e) {
console.warn('Cannot write file. Not critical: ' + e);
}
}
}
for (var t = 0; t < otherInstallDirs.length; t++) {
if (fs.existsSync(otherInstallDirs[t])) {
var stat = fs.statSync(otherInstallDirs[t]);
if (stat.isDirectory()) {
var files = fs.readdirSync(otherInstallDirs[t]);
for (var f = 0; f < files.length; f++) {
fs.unlinkSync(otherInstallDirs[t] + '/' + files[f]);
}
fs.rmdirSync(otherInstallDirs[t]);
} else {
try {
fs.unlinkSync(otherInstallDirs[t]);
}
catch(e) {
console.warn('Cannot delete file. Not critical: ' + e);
}
}
}
}
// Create log and tmp directory
if (!fs.existsSync(__dirname + '/../../tmp')) fs.mkdirSync(__dirname + '/../../tmp');
if (!fs.existsSync(tools.getConfigFileName())) {
isCreated = true;
if (fs.existsSync(__dirname + '/../../conf/' + tools.appName + '-dist.json')) {
config = require(__dirname + '/../../conf/' + tools.appName + '-dist.json');
} else {
config = require(__dirname + '/../../conf/' + tools.appName.toLowerCase() + '-dist.json');
}
console.log('creating conf/' + tools.appName + '.json');
config.objects.host = params.objects || '127.0.0.1';
config.states.host = params.states || '127.0.0.1';
if (useRedis) {
config.states.type = 'redis';
config.states.port = 6379;
}
// this path is relative to yunkong2.js-controller
config.dataDir = tools.getDefaultDataDir();
if (fs.existsSync(__dirname + '/../../../node_modules/' + tools.appName + '.js-controller')) {
mkpathSync(__dirname + '/../', config.dataDir);
} else {
mkpathSync(__dirname + '/../', '../' + config.dataDir);
}
// Create default data dir
fs.writeFileSync(tools.getConfigFileName(), JSON.stringify(config, null, 2));
try {
// Create
if (__dirname.toLowerCase().replace(/\\/g, '/').indexOf('node_modules/' + tools.appName + '.js-controller') !== -1) {
var parts = config.dataDir.split('/');
// Remove appName-data/
parts.pop();
parts.pop();
var path = parts.join('/');
if (!fs.existsSync(__dirname + '/../../' + path + '/log')) fs.mkdirSync(__dirname + '/../../' + path + '/log');
} else {
if (!fs.existsSync(__dirname + '/../../log')) fs.mkdirSync(__dirname + '/../../log');
}
} catch (e) {
console.log('Non-critical error: ' + e.message);
}
} else if (ignoreIfExist) {
if (callback) callback();
return;
}
setupObjects(function () {
if (callback) callback(isCreated);
});
}
}
module.exports = Setup;

360
lib/setup/setupUpgrade.js Normal file
View File

@@ -0,0 +1,360 @@
'use strict';
function Upgrade(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var that = this;
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
if (!options.installNpm) throw 'Invalid arguments: installNpm is missing';
if (!options.restartController) throw 'Invalid arguments: restartController is missing';
if (!options.getRepository) throw 'Invalid arguments: getRepository is missing';
var objects = options.objects;
var processExit = options.processExit;
var installNpm = options.installNpm;
var restartController = options.restartController;
var getRepository = options.getRepository;
var params = options.params;
var semver;
var Upload = require(__dirname + '/setupUpload.js');
var upload = new Upload(options);
var Install = require(__dirname + '/setupInstall.js');
var install = new Install(options);
this.upgradeAdapterHelper = function (repoUrl, list, i, forceDowngrade, callback) {
that.upgradeAdapter(repoUrl, list[i], forceDowngrade, function () {
i++;
while (repoUrl[list[i]] && repoUrl[list[i]].controller) {
i++;
}
if (list[i]) {
setImmediate(function () {
that.upgradeAdapterHelper(repoUrl, list, i, forceDowngrade, callback);
});
} else if (callback) {
callback();
}
});
};
function checkDependencies(dependencies) {
if (!dependencies) return '';
// like [{"js-controller": ">=0.10.1"}]
var adapters;
if (dependencies instanceof Array) {
adapters = {};
for (var a = 0; a < dependencies.length; a++) {
if (typeof dependencies[a] === 'string') continue;
for (var b in dependencies[a]) adapters[b] = dependencies[a][b];
}
} else {
adapters = dependencies;
}
for (var adapter in adapters) {
var adapterDir = tools.getAdapterDir(adapter);
var iopack;
if (!semver) semver = require('semver');
if (adapter === 'js-controller') {
try {
iopack = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
} catch (e) {
return 'Cannot find io-package.json in "' + __dirname + '/../../": ' + e;
}
if (!iopack || !iopack.common || !iopack.common.version) return 'No version of "js-controller"';
if (!semver.satisfies(iopack.common.version, adapters[adapter])) return 'Invalid version of js-controler. Required ' + adapters[adapter];
} else {
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
return 'Cannot find io-package.json in "' + adapterDir + '": ' + e;
}
if (!iopack || !iopack.common || !iopack.common.version) return 'No version of "' + adapter + '"';
if (!semver.satisfies(iopack.common.version, adapters[adapter])) return 'Invalid version of "' + adapter + '"';
}
}
return '';
}
this.upgradeAdapter = function (repoUrl, adapter, forceDowngrade, callback) {
if (!repoUrl || typeof repoUrl !== 'object') {
getRepository(repoUrl, params, function (err, sources) {
if (err) {
processExit(err);
} else {
that.upgradeAdapter(sources, adapter, forceDowngrade, callback);
}
});
return;
}
function finishUpgrade(name, iopack, callback) {
if (!iopack) {
var adapterDir = tools.getAdapterDir(name);
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
console.error('Cannot find io-package.json in ' + adapterDir);
processExit(10);
}
}
var count = 0;
installNpm(name, function (err, _name) {
if (err) {
processExit(err);
} else {
// Upload www and admin files of adapter into CouchDB
count++;
upload.uploadAdapter(name, false, true, function () {
// extend all adapter instance default configs with current config
// (introduce potentially new attributes while keeping current settings)
upload.upgradeAdapterObjects(name, iopack, function () {
count--;
if (!count) {
console.log('Adapter "' + name + '" updated');
if (callback) callback(name);
}
});
});
count++;
upload.uploadAdapter(name, true, true, function () {
count--;
if (!count) {
console.log('Adapter "' + name + '" updated');
if (callback) callback(name);
}
});
}
});
}
var sources = repoUrl;
var version;
if (adapter.indexOf('@') !== -1) {
var parts = adapter.split('@');
adapter = parts[0];
version = parts[1];
} else {
version = '';
}
if (version) forceDowngrade = true;
var adapterDir = tools.getAdapterDir(adapter);
// Read actual description of installed adapter with version
if (!version && !fs.existsSync(adapterDir + '/io-package.json')) {
console.log('Adapter "' + adapter + '"' + ((adapter.length < 15) ? new Array(15 - adapter.length).join(' '): '') + ' is not installed.');
if (callback) callback();
return;
}
// Get the url of io-package.json or direct the version
if (!repoUrl[adapter]) {
console.log('Adapter "' + adapter + '" is not in the repository and cannot be updated.');
if (callback) callback();
return;
}
var ioInstalled;
if (fs.existsSync(adapterDir + '/io-package.json')) {
ioInstalled = require(adapterDir + '/io-package.json');
}
if (!ioInstalled) {
ioInstalled = {common: {version: '0.0.0'}};
}
// If version is included in repository
if (repoUrl[adapter].version) {
if (!forceDowngrade) {
var error = checkDependencies(repoUrl[adapter].dependencies);
if (error) {
console.error(error);
if (callback) callback();
return;
}
}
if (!forceDowngrade && (repoUrl[adapter].version === ioInstalled.common.version ||
tools.upToDate(repoUrl[adapter].version, ioInstalled.common.version))) {
console.log('Adapter "' + adapter + '"' + ((adapter.length < 15) ? new Array(15 - adapter.length).join(' '): '') + ' is up to date.');
if (callback) callback();
} else {
console.log('Update ' + adapter + ' from @' + ioInstalled.common.version + ' to @' + (version || repoUrl[adapter].version));
// Get the adapter from web site
install.downloadPacket(sources, adapter + '@' + (version || repoUrl[adapter].version), null, function (name, ioPack) {
finishUpgrade(name, ioPack, callback);
});
}
} else if (repoUrl[adapter].meta) {
// Read repository from url or file
tools.getJson(repoUrl[adapter].meta, function (iopack) {
if (!iopack) {
console.error('Cannot parse file' + repoUrl[adapter].meta);
if (callback) callback();
return;
}
if (!forceDowngrade) {
var error = checkDependencies(iopack.common ? iopack.common.dependencies : null);
if (error) {
console.error(error);
if (callback) callback();
return;
}
}
if (!version && (iopack.common.version === ioInstalled.common.version ||
(!forceDowngrade && tools.upToDate(iopack.common.version, ioInstalled.common.version)))) {
console.log('Adapter "' + adapter + '"' + ((adapter.length < 15) ? new Array(15 - adapter.length).join(' '): '') + ' is up to date.');
if (callback) callback();
} else {
// Get the adapter from web site
console.log('Update ' + adapter + ' from @' + ioInstalled.common.version + ' to @' + (version || iopack.common.version));
install.downloadPacket(sources, adapter + '@' + (version || iopack.common.version), null, function (name, ioPack) {
finishUpgrade(name, ioPack, callback);
});
}
});
} else {
if (forceDowngrade) {
console.warn('Unable to get version for "' + adapter + '". Update anyway.');
console.log('Update ' + adapter + ' from @' + ioInstalled.common.version + ' to @' + version);
// Get the adapter from web site
install.downloadPacket(sources, adapter + '@' + version, null, function (name, ioPack) {
finishUpgrade(name, ioPack, callback);
});
} else {
console.error('Unable to get version for "' + adapter + '".');
if (callback) callback();
}
}
};
this.upgradeController = function (repoUrl, forceDowngrade, callback) {
if (!repoUrl || typeof repoUrl !== 'object') {
getRepository(repoUrl, params, function (err, sources) {
if (!sources) {
console.warn('Cannot get repository under "' + repoUrl + '"');
if (callback) callback(err);
} else {
that.upgradeController(sources, forceDowngrade, callback);
}
});
return;
}
var hostname = tools.getHostName();
var installed = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
if (!installed || !installed.common || !installed.common.version) {
console.error('Host "' + hostname + '"' + ((hostname.length < 15) ? new Array(15 - hostname.length).join(' '): '') + ' is not installed.');
if (callback) callback();
return;
}
if (!repoUrl[installed.common.name]) {
// no info for controller
console.error('Cannot find this controller "' + installed.common.name + '" in repository.');
if (callback) callback();
return;
}
if (repoUrl[installed.common.name].version) {
if (!forceDowngrade && (repoUrl[installed.common.name].version === installed.common.version ||
tools.upToDate(repoUrl[installed.common.name].version, installed.common.version))) {
console.log('Host "' + hostname + '"' + ((hostname.length < 15) ? new Array(15 - hostname.length).join(' '): '') + ' is up to date.');
if (callback) {
callback();
}
} else {
console.log('Update ' + installed.common.name + ' from @' + installed.common.version + ' to @' + repoUrl[installed.common.name].version);
// Get the controller from web site
install.downloadPacket(repoUrl, installed.common.name + '@' + repoUrl[installed.common.name].version, null, function (name) {
installNpm(function (err, _name) {
if (err) {
processExit(err);
} else {
setChmod(function () {
restartController(callback);
});
}
});
});
}
} else {
tools.getJson(repoUrl[installed.common.name].meta, function (ioPack) {
if ((!ioPack || !ioPack.common) && !forceDowngrade) {
console.warn('Cannot read version. Write "' + tools.appName + ' upgrade self --force" to upgrade controller anyway.');
if (callback) {
callback();
}
return;
}
var version = ioPack && ioPack.common ? ioPack && ioPack.common.version : '';
if (version) {
version = '@' + version;
}
if ((ioPack && ioPack.common && ioPack.common.version === installed.common.version) ||
(!forceDowngrade && ioPack && ioPack.common && tools.upToDate(ioPack.common.version, installed.common.version))) {
console.log('Host "' + hostname + '"' + ((hostname.length < 15) ? new Array(15 - hostname.length).join(' '): '') + ' is up to date.');
if (callback) callback();
} else {
var name = (ioPack && ioPack.common && ioPack.common.name) ? ioPack.common.name : installed.common.name;
console.log('Update ' + name + ' from @' + installed.common.version + ' to ' + version);
// Get the controller from web site
install.downloadPacket(repoUrl, name + version, null, function (name) {
installNpm(function (err, _name) {
if (err) {
processExit(err);
} else {
setChmod(function () {
restartController(callback);
});
}
});
});
}
});
}
};
function setChmod(callback) {
var platform = require('os').platform();
console.log('Host "' + tools.getHostName() + '" (' + platform + ') updated');
// Call command chmod +x __dirname if under linux or darwin
if (platform === 'linux' || platform === 'darwin') {
var exec = require('child_process').exec;
var dir;
if (__dirname.toLowerCase().replace(/\\/g, '/').indexOf('node_modules/' + tools.appName + '.js-controller') !== -1) {
dir = require('path').normalize(__dirname + '/../../../..').replace(/\\/g, '/');
} else {
dir = require('path').normalize(__dirname + '/../..').replace(/\\/g, '/');
}
var cmd = 'chmod -R 777 ' + dir;
console.log('Execute: ' + cmd);
var child = exec(cmd);
child.stderr.pipe(process.stdout);
child.on('exit', function () {
console.log('Chmod finished. Restart controller');
if (callback) callback();
});
} else {
if (callback) callback();
}
}
}
module.exports = Upgrade;

543
lib/setup/setupUpload.js Normal file
View File

@@ -0,0 +1,543 @@
'use strict';
function Upload(options) {
const fs = require('fs');
const tools = require(__dirname + '/../tools.js');
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
let states = options.states;
let objects = options.objects;
let mime;
// get all instances of one adapter
function getInstances(adapter, callback) {
objects.getObjectList({startkey: 'system.adapter.' + adapter + '.', endkey: 'system.adapter.' + adapter + '.\u9999'}, (err, arr) => {
let instances = [];
if (!err && arr && arr.rows) {
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'instance') continue;
instances.push(arr.rows[i].value._id);
}
}
callback(instances);
});
}
// get all instances of all adapters in the list
function getAllInstances(adapters, callback) {
let instances = [];
let count = 0;
for (let k = 0; k < adapters.length; k++) {
if (!adapters[k]) continue;
if (adapters[k].indexOf('.') === -1) count++;
}
for (let i = 0; i < adapters.length; i++) {
if (!adapters[i]) continue;
if (adapters[i].indexOf('.') === -1) {
getInstances(adapters[i], inst => {
for (let j = 0; j < inst.length; j++) {
if (instances.indexOf(inst[j]) === -1) {
instances.push(inst[j]);
}
}
if (!--count && callback) {
callback(instances);
callback = null;
}
});
} else {
if (instances.indexOf(adapters[i]) === -1) {
instances.push(adapters[i]);
}
}
}
if (!count && callback) {
callback(instances);
callback = null;
}
}
// Check if some adapters must be restarted and restart them
function checkRestartOther(adapter, callback) {
let adapterDir = tools.getAdapterDir(adapter);
try {
let adapterConf = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
if (adapterConf.common.restartAdapters) {
if (typeof adapterConf.common.restartAdapters !== 'object') adapterConf.common.restartAdapters = [adapterConf.common.restartAdapters];
if (adapterConf.common.restartAdapters.length && adapterConf.common.restartAdapters[0]) {
getAllInstances(adapterConf.common.restartAdapters, instances => {
if (!instances || !instances.length) {
if (callback) {
callback();
callback = null;
}
} else {
let instancesCount = instances.length;
for (let r = 0; r < instances.length; r++) {
objects.getObject(instances[r], (err, obj) => {
// if instance is enabled
if (!err && obj && obj.common.enabled) {
obj.common.enabled = false; // disable instance
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject(obj._id, obj, err => {
if (!err) {
obj.common.enabled = true; // enable instance
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject(obj._id, obj, err => {
console.log('Adapter "' + obj._id + '" restarted.');
if (!--instancesCount && callback) {
callback();
callback = null;
}
});
} else {
if (err) {
console.error('Cannot restart adapter "' + obj._id + '": ' + err);
} else {
console.warn('Adapter "' + obj._id + '" is disabled and cannot be restarted.');
}
if (!--instancesCount && callback) {
callback();
callback = null;
}
}
});
} else if (!--instancesCount && callback) {
callback();
callback = null;
}
});
}
}
});
} else if (callback) {
callback();
callback = null;
}
} else if (callback) {
callback();
callback = null;
}
} catch (e) {
console.error('Cannot parse ' + adapterDir + '/io-package.json:' + e);
if (callback) {
callback();
callback = null;
}
}
}
this.uploadAdapterFull = (adapters, callback) => {
if (!adapters || !adapters.length) {
if (callback) callback();
return;
}
const adapter = adapters.pop();
this.uploadAdapter(adapter, true, true, () => {
this.upgradeAdapterObjects(adapter, () => {
this.uploadAdapter(adapter, false, true, () => {
setImmediate(() => this.uploadAdapterFull(adapters, callback));
});
});
});
};
this.uploadFile = (source, target, callback) => {
const request = require('request');
target = target.replace(/\\/g, '/');
source = source.replace(/\\/g, '/');
if (target[0] === '/') target = target.substring(1);
if (target[target.length - 1] === '/') {
let name = source.split('/').pop();
name = name.split('?')[0];
if (name.indexOf('.') === -1) name = 'index.html';
target += name;
}
let parts = target.split('/');
const adapter = parts[0];
parts.splice(0, 1);
target = parts.join('/');
if (source.match(/^http:\/\/|^https:\/\//)) {
request(source, (error, response, body) => {
if (!error && response.statusCode === 200) {
objects.writeFile(adapter, target, body, err => {
if (err) console.error(err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
});
} else {
console.error('Cannot get URL: ' + error || response.statusCode);
if (typeof callback === 'function') callback(error || response.statusCode, adapter + '/' + target);
}
});
} else {
try {
objects.writeFile(adapter, target, fs.readFileSync(source), err => {
if (err) console.error(err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
});
} catch (err) {
console.error('Cannot read file "' + source + '": ' + err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
}
}
};
// Upload www folder of adapter into ObjectsDB
this.uploadAdapter = (adapter, isAdmin, forceUpload, subTree, callback) => {
const id = adapter + (isAdmin ? '.admin' : '');
const adapterDir = tools.getAdapterDir(adapter);
let dir = adapterDir + (isAdmin ? '/admin' : '/www');
let files = [];
let rev;
if (typeof subTree === 'function') {
callback = subTree;
subTree = null;
}
if (subTree) {
dir += '/' + subTree;
}
if (!isAdmin) {
let cfg;
// check for common.wwwDontUpload (required for legacy adapter)
if (fs.existsSync(adapterDir + '/io-package.json')) {
cfg = require(adapterDir + '/io-package.json');
}
if (cfg && cfg.common && cfg.common.wwwDontUpload) {
if (typeof callback === 'function') callback(adapter);
return;
}
}
// do not upload www dir of admin adapter
if (adapter === 'admin' && !isAdmin) {
// To DO remove after a while
console.log('This should never happens!');
if (typeof callback === 'function') callback(adapter);
return;
}
// Create "upload progress" object if not exists
if (!isAdmin) {
objects.getObject('system.adapter.' + adapter + '.upload', (err, obj) => {
if (err || !obj) {
objects.setObject('system.adapter.' + adapter + '.upload',
{
_id: 'system.adapter.' + adapter + '.upload',
type: 'state',
common: {
name: adapter + '.upload',
type: 'number',
role: 'indicator.state',
unit: '%',
def: 0,
desc: 'Upload process indicator'
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: Date.now(),
native: {}
}
);
}
});
// Set indicator to 0
states.setState('system.adapter.' + adapter + '.upload', 0, true);
}
if (!mime) mime = require('mime');
function done(err, res) {
if (err) {
callback();
} else {
console.log('got ' + dir);
files = res;
setTimeout(_adapter => {
maxFiles = files.length || 1;
upload(_adapter);
}, 25, adapter);
}
}
let maxFiles = 0;
let lastProgressUpdate = (new Date()).getTime();
function upload(adapter) {
let file;
if (!files.length) {
if (!isAdmin) {
states.setState('system.adapter.' + adapter + '.upload', {val: 0, ack: true}, () => {
if (typeof callback === 'function') callback(adapter);
});
} else {
if (typeof callback === 'function') callback(adapter);
}
} else {
file = files.pop();
if (file === '.gitignore') {
upload(adapter);
return;
}
const mimeType = mime.lookup(file);
let attName;
attName = file.split('/' + tools.appName + '.');
if (attName.length === 1) {
// try to find anyway if adapter is not lower case
const pos = file.toLowerCase().indexOf(tools.appName.toLowerCase());
if (pos !== -1) {
attName = ['', file.substring(tools.appName.length + 2)];
}
}
attName = attName.pop();
attName = attName.split('/').slice(2).join('/');
if (files.length > 100) {
if (!(files.length % 50)) {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
} else if (files.length > 20) {
if (!(files.length % 10)) {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
} else {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
// Update upload indicator
if (!isAdmin) {
const now = (new Date()).getTime();
if (now - lastProgressUpdate > 1000) {
lastProgressUpdate = now;
states.setState('system.adapter.' + adapter + '.upload', {val: Math.round(1000 * (maxFiles - files.length) / maxFiles) / 10, ack: true});
}
}
fs.createReadStream(file).pipe(
objects.insert(id, attName, null, mimeType, {
rev: rev
}, (err, res) => {
if (err) {
console.log(err);
if (typeof callback === 'function') callback(adapter);
}
if (res) rev = res.rev;
setTimeout(upload, 50, adapter);
})
);
}
}
function walk(dir, done) {
let results = [];
fs.readdir(dir, (err, list) => {
if (err) return done(err);
let i = 0;
(function next() {
let file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, (err, stat) => {
if (stat && stat.isDirectory()) {
walk(file, (err, res) => {
results = results.concat(res);
next();
});
} else {
if (!file.match(/\.npmignore$/) && !file.match(/\.gitignore$/)) results.push(file);
next();
}
});
})();
});
}
objects.getObject(id, (err, res) => {
if (err || !res) {
objects.setObject(id, {
type: 'meta',
common: {
name: id.split('.').pop(),
type: isAdmin ? 'admin' : 'www'
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: Date.now(),
native: {}
}, (err, res) => {
if (res) rev = res.rev;
if (!isAdmin) {
checkRestartOther(adapter, () => walk(dir, done));
} else {
walk(dir, done);
}
});
} else {
if (!forceUpload) {
if (typeof callback === 'function') callback(adapter);
} else {
rev = res.rev;
if (!isAdmin) {
checkRestartOther(adapter, () => walk(dir, done));
} else {
walk(dir, done);
}
}
}
});
};
function extendNative(target, additional) {
for (let attr in additional) {
if (additional.hasOwnProperty(attr)) {
if (target[attr] === undefined) {
target[attr] = additional[attr];
} else if (typeof additional[attr] === 'object' && !(additional[attr] instanceof Array)) {
try {
target[attr] = target[attr] || {};
} catch (e) {
console.warn(`Cannot update attribute ${attr} of native`);
}
extendNative(target[attr], additional[attr]);
}
}
}
return target;
}
function extendCommon(target, additional) {
for (let attr in additional) {
if (additional.hasOwnProperty(attr)) {
if (attr === 'title' || attr === 'schedule' || attr === 'mode' || attr === 'loglevel' || attr === 'enabled') {
if (target[attr] === undefined) {
target[attr] = additional[attr];
}
} else if (typeof additional[attr] !== 'object' || (additional[attr] instanceof Array)) {
try {
target[attr] = additional[attr];
} catch (e) {
console.warn(`Cannot update attribute ${attr} of common`);
}
} else {
target[attr] = target[attr] || {};
if (typeof target[attr] !== 'object') {
target[attr] = {}; // here we clean the simple value with object
}
extendCommon(target[attr], additional[attr]);
}
}
}
return target;
}
this.upgradeAdapterObjects = (name, iopack, callback) => {
if (typeof iopack === 'function') {
callback = iopack;
iopack = null;
}
if (!iopack) {
const adapterDir = tools.getAdapterDir(name);
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
console.error('Cannot find io-package.json in ' + adapterDir);
iopack = null;
}
}
if (!iopack) {
callback(name);
} else {
objects.getObject('system.adapter.' + name, (err, obj) => {
if (err || !obj) {
console.error('system.adapter.' + name + ' does not exist');
callback(name);
} else {
obj.common = iopack.common || {};
obj.native = iopack.native || {};
obj.common.installedVersion = iopack.common.version;
const hostname = tools.getHostName();
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject('system.adapter.' + name, obj, () => {
// Update all instances of this host
objects.getObjectView('system', 'instance', {startkey: 'system.adapter.' + name + '.', endkey: 'system.adapter.' + name + '.\u9999'}, null, function (err, res) {
let cntr = 0;
if (res) {
for (let i = 0; i < res.rows.length; i++) {
if (res.rows[i].value.common.host === hostname) {
cntr++;
objects.getObject(res.rows[i].id, (err, _obj) => {
let newObject = JSON.parse(JSON.stringify(_obj));
// all common settings should be taken from new one
newObject.common = extendCommon(newObject.common, iopack.common);
newObject.native = extendNative(newObject.native, iopack.native);
newObject.common.installedVersion = iopack.common.version;
newObject.common.version = iopack.common.version;
// Compare objects to reduce restarts of instances
if (JSON.stringify(newObject) !== JSON.stringify(_obj)) {
console.log('Update "' + newObject._id + '"');
newObject.from = 'system.host.' + tools.getHostName() + '.cli';
newObject.ts = Date.now();
objects.setObject(newObject._id, newObject, () => {
if (!--cntr && callback) callback(name);
});
} else {
if (!--cntr && callback) callback(name);
}
});
}
}
}
// updates "_design/system" and co
if (iopack.objects && typeof iopack.objects === 'object') {
for (let _id in iopack.objects) {
if (!iopack.objects.hasOwnProperty(_id)) continue;
cntr++;
iopack.objects[_id].from = 'system.host.' + tools.getHostName() + '.cli';
iopack.objects[_id].ts = Date.now();
objects.setObject(iopack.objects[_id]._id, iopack.objects[_id], err => {
if (err) console.error('Cannot update object: ' + err);
if (!--cntr && callback) callback(name);
});
}
}
if (!cntr && callback) callback(name);
});
});
}
});
}
};
}
module.exports = Upload;

522
lib/setup/setupUsers.js Normal file
View File

@@ -0,0 +1,522 @@
'use strict';
function Users(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var that = this;
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
var objects = options.objects;
var processExit = options.processExit;
this.addUser = function (user, pw, callback) {
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (obj) {
if (callback) callback('User yet exists');
} else {
objects.setObject('system.user.' + _user, {
type: 'user',
common: {
name: user,
enabled: true
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: new Date().getTime(),
native: {}
}, function (err) {
if (!err) {
that.setPassword(user, pw, callback);
} else {
if (typeof callback === 'function') callback(err);
}
});
}
});
};
this.isUser = function (user, callback) {
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (callback) callback(null, !!obj);
});
};
this.setPassword = function (user, pw, callback) {
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('User does not exist');
return;
}
var password = require(__dirname + '/../password');
password(pw).hash(null, null, function (err, res) {
if (err) {
if (typeof callback === 'function') callback(err);
return;
}
obj.common.password = res;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.user.' + _user, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
});
});
};
this.checkPassword = function (user, pw, callback) {
objects.getObject('system.user.' + user, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('User does not exist');
return;
}
var password = require(__dirname + '/../password');
password(pw).check(obj.common.password, function (err, res) {
if (typeof callback === 'function') callback(err, res);
});
});
};
this.delUser = function (user, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "userdel user"');
return;
}
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (err || !obj) {
if (callback) callback('User does not exist');
} else {
if (obj.common.dontDelete) {
if (callback) callback('Cannot delete user, while is system user');
} else {
objects.delObject('system.user.' + _user, function (err) {
// Remove this user from all groups
if (!err) {
objects.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, groups) {
var count = 0;
for (var i = 0; i < groups.rows.length; i++) {
if (groups.rows[i].value.type != 'group') continue;
// find all groups
if (groups.rows[i].value.common.members && groups.rows[i].value.common.members.indexOf('system.user.' + _user) != -1) {
var pos = groups.rows[i].value.common.members.indexOf('system.user.' + _user);
groups.rows[i].value.common.members.splice(pos, 1);
count++;
groups.rows[i].value.from = 'system.host.' + tools.getHostName() + '.cli';
groups.rows[i].value.ts = new Date().getTime();
objects.setObject(groups.rows[i].value._id, groups.rows[i].value, function (err) {
if (!(--count)) callback(err);
});
}
}
if (!count) callback();
});
} else if (callback) {
callback(err);
}
});
}
}
});
};
this.addUserToGroup = function (user, group, callback) {
var _user = user.replace(/\s/g, '_');
if (!group.match(/^system\.group\./)) group = 'system.group.' + group;
if (!_user.match(/^system\.user\./)) _user = 'system.user.' + _user;
objects.getObject(_user, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('User does not exist');
return;
}
objects.getObject(group, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('Group does not exist');
return;
}
obj.common = obj.common || {};
obj.common.members = obj.common.members || [];
if (obj.common.members.indexOf(_user) == -1) {
obj.common.members.push(_user);
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(group, obj, function (err) {
callback(err);
});
} else {
callback();
}
});
});
};
this.addUserPrompt = function (user, group, password, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "adduser newUser"');
return;
}
// Check group
if (group.substring(0, 13) !== 'system.group.' ) group = 'system.group.' + group;
objects.getObject(group, function (err, obj) {
if (!obj) {
if (callback) callback('Unknown group: ' + group);
return;
}
if (!password) {
var prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
var schema = {
properties: {
password: {
description: 'Enter your password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
},
repeatPassword: {
description: 'Repeat your password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
if (result) {
if (result.password !== result.repeatPassword) {
console.log('Passwords are not identical!');
processExit(31);
}
//create user
that.addUser(user, result.password, function (err) {
if (err) {
if (callback) callback(err);
} else {
that.addUserToGroup(user, group, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
}
});
} else {
if (callback) callback(err);
}
});
} else {
that.addUser(user, password, function (err) {
if (err) {
if (callback) callback(err);
} else {
that.addUserToGroup(user, group, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
}
});
}
});
};
this.setUserPassword = function (user, password, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "passwd username"');
return;
}
this.isUser(user, function (err, result) {
if (err) console.error('Cannot read user: ' + err);
if (!result) {
if (callback) callback('User "' + user + '" does not exist.');
} else {
// Check group
if (!password) {
var prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
var schema = {
properties: {
password: {
description: 'Enter your password:',
pattern: /^[^'"]*$/,
message: 'No " are allowed',
hidden: true
},
repeatPassword: {
description: 'Repeat your password:',
pattern: /^[^'"]*$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
if (result) {
if (result.password !== result.repeatPassword) {
if (callback) callback('Passwords are not identical!');
return;
}
// set user password
that.setPassword(user, result.password, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
} else {
if (callback) callback('No password entered!');
}
});
} else {
that.setPassword(user, password, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
}
}
});
};
this.enableUser = function (user, enable, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "enable username"');
return;
}
if (user && user.match(/^system\.user\./)) user = user.substring('system.user.'.length);
if (user === 'admin' && !enable) {
if (callback) callback('User admin cannot be disabled');
return;
}
objects.getObject('system.user.' + user, function (err, obj) {
if (err) {
if (callback) callback('Cannot read user: ' + err);
} if (!obj) {
if (callback) callback('User "' + user + '" not found');
} else {
obj.common.enabled = enable;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
};
this.checkUserPassword = function (user, password, callback) {
var prompt;
var schema;
if (!user && !password) {
prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
schema = {
properties: {
username: {
description: 'Enter username to check password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: false
},
password: {
description: 'Enter current password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
that.checkPassword(result.username, result.password, function (err, res) {
if (err || !res) {
if (callback) callback('Password for user "' + result.username + '" does not matched' + (err ? ': ' + err : ''));
} else {
if (callback) callback(null);
}
});
});
} else if (!password) {
prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
schema = {
properties: {
password: {
description: 'Enter current password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
that.checkPassword(user, result.password, function (err, res) {
if (err || !res) {
if (callback) callback('Password for user "' + user + '" does not matched' + (err ? ': ' + err : ''));
} else {
if (callback) callback(null);
}
});
});
} else{
this.checkPassword(user, password, function (err, res) {
if (err || !res) {
if (callback) callback('Password for user "' + user + '" does not matched' + (err ? ': ' + err : ''));
} else {
if (callback) callback(null);
}
});
}
};
this.getUser = function (user, callback) {
objects.getObject('system.user.' + user, function (err, obj) {
if (err) {
if (callback) callback('Cannot read user: ' + err);
} if (!obj) {
if (callback) callback('User "' + user + '" not found');
} else {
if (callback) callback(null, obj.common.enabled);
}
});
};
this.getGroup = function (group, callback) {
objects.getObject('system.group.' + group, function (err, obj) {
if (err) {
if (callback) callback('Cannot read group: ' + err);
} if (!obj) {
if (callback) callback('Group "' + group + '" not found');
} else {
if (callback) callback(null, obj.common.enabled, obj.common.members);
}
});
};
this.enableGroup = function (group, enable, callback) {
if (!group) {
if (callback) callback('Please define group name, like: "enable groupname"');
return;
}
if (group && group.match(/^system\.group\./)) group = group.substring('system.group.'.length);
if (group === 'administrator' && !enable) {
if (callback) callback('Group "administrator" cannot be disabled');
return;
}
objects.getObject('system.group.' + group, function (err, obj) {
if (err) {
if (callback) callback('Cannot read group: ' + err);
} if (!obj) {
if (callback) callback('Group "' + group + '" not found');
} else {
obj.common.enabled = enable;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
};
this.addGroup = function (group, callback) {
var _group = group.replace(/\s/g, '_');
objects.getObject('system.group.' + _group, function (err, obj) {
if (obj) {
if (callback) callback('Group yet exists');
} else {
objects.setObject('system.group.' + _group, {
type: 'group',
common: {
name: group,
enabled: true,
members: []
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: new Date().getTime(),
native: {}
}, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
};
this.delGroup = function (group, callback) {
var _group = group.replace(/\s/g, '_');
if (group === 'administrator') {
if (typeof callback === 'function') callback('Group "administrator" cannot be deleted');
} else {
objects.getObject('system.group.' + _group, function (err, obj) {
if (!obj) {
if (callback) callback('Group does not exists');
} else {
objects.delObject('system.group.' + _group, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
}
};
this.removeUserFromGroup = function (user, group, callback) {
var _group = group.replace(/\s/g, '_');
objects.getObject('system.group.' + _group, function (err, obj) {
if (!obj) {
if (callback) callback('Group does not exists');
} else {
var pos = obj.common.members.indexOf('system.user.' + user);
if (pos === -1) {
if (typeof callback === 'function') callback('User not in group');
} else {
obj.common.members.splice(pos, 1);
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
}
}
});
};
}
module.exports = Users;

197
lib/setup/setupVendor.js Normal file
View File

@@ -0,0 +1,197 @@
'use strict';
function Vendor(options) {
const fs = require('fs');
const tools = require(__dirname + '/../tools.js');
options = options || {};
let objects = options.objects;
// read info from '/etc/iob_vendor.json' and executes instructions stored there
this.checkVendor = (file, password) => {
file = file || '/etc/iob-vendor.json';
let data;
if (fs.existsSync(file)) {
try {
data = JSON.parse(fs.readFileSync(file).toString('utf8'));
} catch (e) {
return Promise.reject(`cannot read or parse "${file}": ${JSON.stringify(e)}`);
}
} else {
return Promise.reject(`"${file}" does not exist`);
}
let promises = [];
if (data.uuid) {
const uuid = data.uuid;
data.uuid = null;
// check UUID
promises.push(objects.getObjectAsync('system.meta.uuid').then(obj => {
if (obj && obj.native) {
if (obj.native.uuid !== uuid) {
obj.native.uuid = uuid;
console.log(`Update "system.meta.uuid:native.uuid" = "${obj.native.uuid}"`);
obj.nonEdit = obj.nonEdit || {};
obj.nonEdit.password = password;
return objects.setObjectAsync('system.meta.uuid', obj).then(() => {
console.log('object system.meta.uuid updated: ' + uuid);
}).catch(err => {
console.error(`Cannot update system.meta.uuid: ${err}`);
});
}
} else {
return objects.setObjectAsync('system.meta.uuid', {
type: 'meta',
common: {
name: 'uuid',
type: 'uuid'
},
ts: new Date().getTime(),
from: 'system.host.' + tools.getHostName() + '.tools',
native: {
uuid: uuid
}
}).then(() => {
console.log('object system.meta.uuid created: ' + uuid);
}).catch(err => {
console.error(`Cannot create system.meta.uuid: ${err}`);
});
}
}));
}
if (data.model) {
const model = data.model;
data.model = null;
const hostname = tools.getHostName();
promises.push(objects.getObjectAsync('system.host.' + hostname).then(obj => {
if (obj && obj.common) {
if ((model.name && model.name !== 'JS controller' && obj.common.title === 'JS controller') ||
(model.icon && !obj.common.icon) ||
(model.color && !obj.common.color)) {
obj.common.title = model.name;
obj.common.icon = model.icon;
obj.common.color = model.color;
obj.nonEdit = obj.nonEdit || {};
obj.nonEdit.password = password;
console.log(`Update "system.host.${hostname}:common.title" = "${obj.common.title}"`);
console.log(`Update "system.host.${hostname}:common.icon" = "${!!obj.common.icon}"`);
console.log(`Update "system.host.${hostname}:common.color" = "${obj.common.color}"`);
return objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`object "system.host.${hostname}" updated`);
}).catch(err => {
console.error(`Cannot update "system.host.${hostname}": ${err}`);
});
}
}
}));
}
if (data.vendor) {
const vendor = JSON.parse(JSON.stringify(data.vendor));
data._vendor = JSON.parse(JSON.stringify(vendor));
data.vendor = null;
// store vendor
promises.push(objects.getObjectAsync('system.config').then(obj => {
if (obj && obj.native) {
if (JSON.stringify(obj.native.vendor) !== JSON.stringify(vendor)) {
obj.native.vendor = vendor;
obj.nonEdit = obj.nonEdit || {};
obj.nonEdit.password = password;
return objects.setObjectAsync(obj._id, obj).then(() => {
console.log('object system.config updated');
}).catch(err => {
console.error(`Cannot update system.config: ${err}`);
});
}
}
}));
}
return Promise.all(promises).then(() => {
let _promises = [];
// update all existing objects according to vendor
if (data.objects) {
for (let id in data.objects) {
if (!data.objects.hasOwnProperty(id)) continue;
if (id.indexOf('*') === -1) {
((_id, _obj) => {
_promises.push(objects.getObjectAsync(_id).then(obj => {
if (obj) {
obj.nonEdit = obj.nonEdit || {};
const originalObj = JSON.stringify(obj);
_obj.nonEdit = _obj.nonEdit || {};
_obj.nonEdit.passHash = obj.nonEdit.passHash;
// merge objects
tools.copyAttributes(_obj, obj);
if (originalObj !== JSON.stringify(obj)) {
delete obj.nonEdit.passHash;
obj.nonEdit.password = password;
console.log(`Update "${obj._id}"`);
return objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`object "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
});
}
} else {
return objects.setObjectAsync(_id, _obj).then(() => {
console.log(`object "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
});
}
}));
})(id, data.objects[id]);
} else {
id = id.replace('*', '');
((_id, _obj) => {
_promises.push(objects.getObjectListAsync({
startkey: _id,
endkey: _id + '\u9999'
}, {checked: true}).then(arr => {
let tasks = [];
if (arr && arr.rows && arr.rows.length) {
for (let g = 0; g < arr.rows.length; g++) {
let obj = arr.rows[g].value;
if (obj) {
obj.nonEdit = obj.nonEdit || {};
const originalObj = JSON.stringify(obj);
_obj.nonEdit = _obj.nonEdit || {};
_obj.nonEdit.passHash = obj.nonEdit.passHash;
// merge objects
tools.copyAttributes(_obj, obj);
if (originalObj !== JSON.stringify(obj)) {
delete obj.nonEdit.passHash;
obj.nonEdit.password = password;
console.log(`Update "${obj._id}"`);
tasks.push(objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`object "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
}));
}
}
}
}
return Promise.all(tasks);
}));
})(id, data.objects[id]);
}
}
}
return Promise.all(_promises);
});
};
return this;
}
module.exports = Vendor;

175
lib/setup/setupVisDebug.js Normal file
View File

@@ -0,0 +1,175 @@
'use strict';
function VisDebug(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var path = require('path');
// allow use without new operator
if (!(this instanceof VisDebug)) return new VisDebug(options);
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
var objects = options.objects;
var processExit = options.processExit;
// upload widget directory to vis directory
function uploadWidgets(dir, adapter, path, callback) {
var dirs = fs.readdirSync(dir);
var count = 0;
for (var d = 0; d < dirs.length; d++) {
var stat = fs.statSync(dir + '/' + dirs[d]);
count++;
if (stat.isDirectory()) {
uploadWidgets(dir + '/' + dirs[d], adapter, path + '/' + dirs[d], function () {
if (!--count && callback) callback();
});
} else {
console.log('Upload "' + dir + '/' + dirs[d] + '"');
objects.writeFile(adapter, path + '/' + dirs[d], fs.readFileSync(dir + '/' + dirs[d]), function () {
if (!--count && callback) callback();
});
}
}
if (!count && callback) callback();
}
this.enableDebug = function (widgetset) {
if (widgetset) {
// Try to find out the adapter directory out of a list of options
var adapterDir;
var adapterNames2Try = ['vis-' + widgetset, widgetset];
if (adapterNames2Try[0] === adapterNames2Try[1]) adapterNames2Try.splice(1, 1);
for (var i = 0; i < adapterNames2Try.length; i++) {
try {
var adapterDir2Try = tools.getAdapterDir(adapterNames2Try[i]);
// Query the entry
var stats = fs.lstatSync(adapterDir2Try);
// Is it a directory?
if (stats.isDirectory()) {
//found it!
adapterDir = adapterDir2Try;
break;
}
} catch (e) {
}
}
if (!adapterDir) throw 'Adapter not found. Tried: ' + adapterNames2Try.join(', ');
}
// copy index.html.original to index.html
// copy edit.html.original to edit.html
// correct appName.json
// correct config.js
var visDir = __dirname + '/../../node_modules/' + tools.appName + '.vis';
if (!fs.existsSync(visDir)) {
visDir = __dirname + '/../../node_modules/' + tools.appName.toLowerCase() + '.vis';
if (!fs.existsSync(visDir)) {
visDir = __dirname + '/../../../' + tools.appName + '.vis';
if (!fs.existsSync(visDir)) {
visDir = __dirname + '/../../../' + tools.appName.toLowerCase() + '.vis';
if (!fs.existsSync(visDir)) {
console.error('Cannot find ' + tools.appName + '.vis');
processExit(40);
}
}
}
}
console.log('Upload "' + path.normalize(visDir + '/www/index.html.original') + '"');
var file = fs.readFileSync(visDir + '/www/index.html.original');
objects.writeFile('vis', 'index.html', file);
console.log('Upload "' + path.normalize(visDir + '/www/edit.html.original') + '"');
file = fs.readFileSync(visDir + '/www/edit.html.original');
objects.writeFile('vis', 'edit.html', file);
console.log('Modify "' + path.normalize(visDir + '/www/cache.manifest') + '"');
file = fs.readFileSync(visDir + '/www/cache.manifest', 'utf8');
var n = file.match(/# dev build (\d+)/, '5');
n = n[1];
file = file.replace('# dev build '+ n, '# dev build ' + (parseInt(n, 10) + 1));
objects.writeFile('vis', 'cache.manifest', file);
file = fs.readFileSync(tools.getConfigFileName(), 'utf8');
file = JSON.parse(file);
var count = 0;
if (!file.objects.noFileCache) {
file.objects.noFileCache = true;
fs.writeFileSync(tools.getConfigFileName(), JSON.stringify(file, null, 2));
count++;
objects.enableFileCache(false, function (err, actual) {
console.log('Disable cache');
if (!--count) processExit();
});
}
if (widgetset) {
count++;
objects.readFile('vis', 'js/config.js', null, function (err, data) {
data = data.replace(/[\r\n]/g, '');
var json = JSON.parse(data.match(/"widgetSets":\s(.*)};/)[1]);
var found = false;
for (var f = 0; f < json.length; f++) {
if (json[f] === widgetset || json[f].name === widgetset) {
found = true;
break;
}
}
// if widget-set not found in config.js
if (!found) {
console.log('Modify config.js');
var pckg = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
if (pckg.native && pckg.native.dependencies && pckg.native.dependencies.length){
json.push({
name: widgetset,
depends: pckg.native.dependencies
});
} else {
json.push(widgetset);
}
data = data.replace(/"widgetSets":\s+.*};/, '"widgetSets": ' + JSON.stringify(json, null, 2) + '};');
objects.writeFile('vis', 'js/config.js', data, function () {
// upload all files into vis
console.log('Upload ' + adapterDir + '/widgets');
uploadWidgets(adapterDir + '/widgets', 'vis', 'widgets', function () {
if (!--count) {
// timeoout to print all messages
setTimeout(function () {
processExit();
}, 100);
}
});
});
} else {
// upload all files into vis
console.log('Upload "' + adapterDir + '/widgets' + '"');
uploadWidgets(adapterDir + '/widgets', 'vis', 'widgets', function () {
if (!--count) {
// timeoout to print all messages
setTimeout(function () {
processExit();
}, 100);
}
});
}
});
} else {
if (!count) processExit();
}
};
}
module.exports = VisDebug;

13
lib/states.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
var getConfigFileName = require(__dirname + '/tools').getConfigFileName;
var config = JSON.parse(require('fs').readFileSync(getConfigFileName(), 'utf8'));
if (!config.states) config.states = {type: 'file'};
if (config.states.type === 'file') {
module.exports = require(__dirname + '/states/statesInMemClient');
} else if (config.states.type === 'redis') {
module.exports = require(__dirname + '/states/statesInRedis');
} else {
throw 'Unknown objects type: ' + config.objects.type;
}

View File

@@ -0,0 +1,368 @@
/**
* States DB in memory - Client
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
*
* MIT License
*
*/
/** @module statesRedis */
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const io = require('socket.io-client');
function StatesInMemClient(settings) {
var client;
var subscribes = {};
var connectionTimeout;
var log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/*console.log(msg);*/},
debug: function (msg) {/*console.log(msg);*/},
info: function (msg) {/*console.log(msg);*/},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
var __construct = (function () {
if (!settings.connection.secure) {
client = io.connect('http://' + ((settings.connection.host && settings.connection.host !== '0.0.0.0') ? settings.connection.host : '127.0.0.1') + ':' + (settings.connection.port || 9000));
} else {
client = io.connect('https://' + ((settings.connection.host && settings.connection.host !== '0.0.0.0') ? settings.connection.host : '127.0.0.1') + ':' + (settings.connection.port || 9000));
}
if (typeof settings.change === 'function') {
client.on('message', function (pattern, channel, message) {
log.silly(settings.namespace + ' inMem message ', pattern, channel, message);
try {
settings.change(channel, message);
} catch (e) {
log.error(settings.namespace + ' message ' + channel + ' ' + message + ' ' + e.message);
log.error(settings.namespace + ' ' + e.stack);
}
});
}
client.on('disconnect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.silly(settings.namespace + ' ' + error);
}
});
client.on('error', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.error(settings.namespace + ' ' + error.message);
log.error(settings.namespace + ' ' + error.stack);
}
});
client.on('connect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.connected === 'function') settings.connected();
});
client.on('reconnect', function (error) {
// Re-init subscribes
for (var t in subscribes) {
for (var i = 0; i < subscribes[t].length; i++) {
client.emit(t, subscribes[t][i]);
}
}
if (typeof settings.connected === 'function') settings.connected();
});
connectionTimeout = setTimeout(function () {
if (typeof settings.connectTimeout === 'function') settings.connectTimeout('Connection timeout');
connectionTimeout = null;
}, 5000);
})();
/**
* @method setState
* @param id {String} the id of the value. '<namespace>.' will be prepended
* @param state {any}
*
*
* an object containing the actual value and some metadata:<br>
* setState(id, {'val': val, 'ts': ts, 'ack': ack, 'from': from, 'lc': lc})
*
* if no object is given state is treated as val:<br>
* setState(id, val)
*
* <ul><li><b>val</b> the actual value. Can be any JSON-stringifiable object. If undefined the
* value is kept unchanged.</li>
*
* <li><b>ack</b> a boolean that can be used to mark a value as confirmed, used in bidirectional systems which
* acknowledge that a value has been successfully set. Will be set to false if undefined.</li>
*
* <li><b>ts</b> a unix timestamp indicating the last write-operation on the state. Will be set by the
* setState method if undefined.</li>
*
* <li><b>lc</b> a unix timestamp indicating the last change of the actual value. this should be undefined
* when calling setState, it will be set by the setValue method itself.</li></ul>
*
* @param callback {Function} will be called when redis confirmed reception of the command
*
*
*/
this.setState = function (id, state, callback) {
if (!client) return;
client.emit('setState', id, state, callback);
};
// Used for restore function (do not call it
this.setRawState = function (id, state, callback) {
if (!client) return;
client.emit('setRawState', id, state, callback);
};
/**
* @method getState
*
* @param {String} id
* @param callback
*/
this.getState = function (id, callback) {
if (!client) return;
client.emit('getState', id, callback);
};
this.getStates = function (keys, callback) {
if (!client) return;
client.emit('getStates', keys, function (err, res) {
if (callback) callback(err, res);
});
};
this.delState = function (id, callback) {
if (!client) return;
client.emit('delState', id, callback);
};
this.getKeys = function (pattern, callback, dontModify) {
if (!client) return;
client.emit('getKeys', pattern, callback);
};
/**
* @method subscribe
*
* @param {string} pattern
* @param {function} callback
*/
this.subscribe = function (pattern, callback) {
subscribes.subscribe = subscribes.subscribe || [];
if (subscribes.subscribe.indexOf(pattern) === -1) subscribes.subscribe.push(pattern);
if (!client) return;
client.emit('subscribe', pattern, callback);
};
this.unsubscribe = function (pattern, callback) {
if (subscribes.subscribe) {
var pos = subscribes.subscribe.indexOf(pattern);
if (pos !== -1) subscribes.subscribe.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribe', pattern, callback);
};
// this.pushFifoExists = function (id, state, callback) {
// if (!client) return;
// client.emit('pushFifoExists', id, state, callback);
// };
//
// this.pushFifo = function (id, state, callback) {
// if (!client) return;
// client.emit('pushFifo', id, state, callback);
// };
//
// this.lenFifo = function (id, callback) {
// if (!client) return;
// client.emit('lenFifo', id, callback);
// };
//
// this.getFifo = function (id, callback) {
// if (!client) return;
// client.emit('getFifo', id, callback);
// };
//
// this.getFifoRange = function (id, start, end, callback) {
// if (!client) return;
// client.emit('getFifoRange', id, start, end, callback);
// };
//
// this.trimFifo = function (id, minLength, maxLength, callback) {
// if (!client) return;
// client.emit('trimFifo', id, minLength, maxLength, callback);
// };
this.pushMessage = function (id, state, callback) {
if (!client) return;
client.emit('pushMessage', id, state, callback);
};
this.lenMessage = function (id, callback) {
if (!client) return;
client.emit('lenMessage', id, callback);
};
this.getMessage = function (id, callback) {
if (!client) return;
client.emit('getMessage', id, callback);
};
this.delMessage = function (id, messageId, callback) {
if (!client) return;
client.emit('delMessage', id, messageId, callback);
};
this.subscribeMessage = function (pattern, callback) {
subscribes.subscribeMessage = subscribes.subscribeMessage || [];
if (subscribes.subscribeMessage.indexOf(pattern) === -1) subscribes.subscribeMessage.push(pattern);
if (!client) return;
client.emit('subscribeMessage', pattern, callback);
};
this.unsubscribeMessage = function (pattern, callback) {
if (subscribes.subscribeMessage) {
var pos = subscribes.subscribeMessage.indexOf(pattern);
if (pos !== -1) subscribes.subscribeMessage.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribeMessage', pattern, callback);
};
this.pushLog = function (id, state, callback) {
if (!client) return;
client.emit('pushLog', id, state, callback);
};
this.lenLog = function (id, callback) {
if (!client) return;
client.emit('lenLog', id, callback);
};
this.getLog = function (id, callback) {
if (!client) return;
client.emit('getLog', id, callback);
};
this.delLog = function (id, logId, callback) {
if (!client) return;
client.emit('delLog', id, logId, callback);
};
this.subscribeLog = function (pattern, callback) {
subscribes.subscribeLog = subscribes.subscribeLog || [];
if (subscribes.subscribeLog.indexOf(pattern) === -1) subscribes.subscribeLog.push(pattern);
if (!client) return;
client.emit('subscribeLog', pattern, callback);
};
this.unsubscribeLog = function (pattern, callback) {
if (subscribes.subscribeLog) {
var pos = subscribes.subscribeLog.indexOf(pattern);
if (pos !== -1) subscribes.subscribeLog.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribeLog', pattern, callback);
};
this.getSession = function (id, callback) {
if (!client) return;
client.emit('getSession', id, callback);
};
this.setSession = function (id, expire, obj, callback) {
if (!client) return;
client.emit('setSession', id, expire, obj, callback);
};
this.destroySession = function (id, callback) {
if (!client) return;
client.emit('destroySession', id, callback);
};
this.getConfig = function (id, callback) {
if (!client) return;
client.emit('getConfig', id, callback);
};
this.getConfigKeys = function (pattern, callback, dontModify) {
if (!client) return;
client.emit('getConfigKeys', pattern, callback);
};
this.getConfigs = function (keys, callback, dontModify) {
if (!client) return;
client.emit('getConfigs', keys, callback);
};
this.setConfig = function (id, obj, callback) {
if (!client) return;
client.emit('setConfig', id, obj, callback);
};
this.delConfig = function (id, callback) {
if (!client) return;
client.emit('delConfig', id, callback);
};
this.subscribeConfig = function (pattern, callback) {
subscribes.subscribeConfig = subscribes.subscribeConfig || [];
if (subscribes.subscribeConfig.indexOf(pattern) === -1) subscribes.subscribeConfig.push(pattern);
if (!client) return;
client.emit('subscribeConfig', pattern, callback);
};
this.unsubscribeConfig = function (pattern, callback) {
if (subscribes.subscribeConfig) {
var pos = subscribes.subscribeConfig.indexOf(pattern);
if (pos !== -1) subscribes.subscribeConfig.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribeConfig', pattern, callback);
};
this.setBinaryState = function (id, data, callback) {
if (!client) return;
client.emit('setBinaryState', id, data, callback);
};
this.getBinaryState = function (id, callback) {
if (!client) return;
client.emit('getBinaryState', id, callback);
};
this.delBinaryState = function (id, callback) {
if (!client) return;
client.emit('delBinaryState', id, callback);
};
}
module.exports = StatesInMemClient;

File diff suppressed because it is too large Load Diff

636
lib/states/statesInRedis.js Normal file
View File

@@ -0,0 +1,636 @@
/**
* States DB in redis - Client
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
* Copyright 2013-2014 hobbyquaker
*
* MIT License
*
*/
/** @module statesRedis */
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const redis = require('redis');
function StateRedis(settings) {
const redisNamespace = (settings.redisNamespace || 'io') + '.';
const namespaceMsg = (settings.namespaceMsg || 'messagebox') + '.';
const namespaceLog = (settings.namespaceLog || 'log') + '.';
const namespaceSession = (settings.namespaceSession || 'session') + '.';
// const namespaceConfig = (settings.namespaceConfig || 'config') + '.';
const onChange = settings.change; // on change handler
let globalMessageId = Math.round(Math.random() * 100000000);
let globalLogId = Math.round(Math.random() * 100000000);
settings.namespace = settings.namespace || settings.hostname || '';
let client;
let clientBin;
let sub;
const ioRegExp = new RegExp('^' + redisNamespace);
let log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/* console.log(msg); */},
debug: function (msg) {/* console.log(msg); */},
info: function (msg) {/* console.log(msg); */},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
// limit max number of log entries in the list
settings.connection.maxQueue = settings.connection.maxQueue || 1000;
if (settings.connection.options) {
if (settings.connection.options.retry_max_delay) {
const retry_max_delay = settings.connection.options.retry_max_delay;
// convert redis 0.1 options to redis 3.0
settings.connection.options.retry_strategy = function (options) {
// A function that receives an options object as parameter including the retry attempt,
// the total_retry_time indicating how much time passed since the last time connected,
// the error why the connection was lost and the number of times_connected in total.
// If you return a number from this function, the retry will happen exactly after that
// time in milliseconds. If you return a non-number, no further retry will happen and
// all offline commands are flushed with errors. Return an error to return that
// specific error to all offline commands.
return retry_max_delay;
/*if (options.error.code === 'ECONNREFUSED') {
// End reconnecting on a specific error and flush all commands with a individual error
return new Error('The server refused the connection');
}
if (options.total_retry_time > 1000 * 60 * 60) {
// End reconnecting after a specific timeout and flush all commands with a individual error
return new Error('Retry time exhausted');
}
if (options.times_connected > 10) {
// End reconnecting with built in error
return undefined;
}
// reconnect after
return Math.max(options.attempt * 100, 3000);*/
};
delete settings.connection.options.retry_max_delay;
}
}
/**
* @method setState
* @param id {String} the id of the value. '<redisNamespace>.' will be prepended
* @param state {any}
*
*
* an object containing the actual value and some metadata:<br>
* setState(id, {'val': val, 'ts': ts, 'ack': ack, 'from': from, 'lc': lc})
*
* if no object is given state is treated as val:<br>
* setState(id, val)
*
* <ul><li><b>val</b> the actual value. Can be any JSON-stringifiable object. If undefined the
* value is kept unchanged.</li>
*
* <li><b>ack</b> a boolean that can be used to mark a value as confirmed, used in bidirectional systems which
* acknowledge that a value has been successfully set. Will be set to false if undefined.</li>
*
* <li><b>ts</b> a unix timestamp indicating the last write-operation on the state. Will be set by the
* setState method if undefined.</li>
*
* <li><b>lc</b> a unix timestamp indicating the last change of the actual value. this should be undefined
* when calling setState, it will be set by the setValue method itself.</li></ul>
*
* @param callback {Function} will be called when redis confirmed reception of the command
*/
this.setState = function (id, state, callback) {
let expire;
if (state.expire) {
expire = state.expire;
delete state.expire;
}
//var that = this;
let obj = {};
if (typeof state !== 'object') {
state = {
val: state
};
}
client.get(redisNamespace + id, function (err, oldObj) {
// TODO Error Handling
if (err) log.warn(settings.namespace + ' ' + err);
if (!oldObj) {
oldObj = {};
} else {
try {
oldObj = JSON.parse(oldObj);
} catch (e) {
oldObj = {};
}
}
if (state.val !== undefined) {
obj.val = state.val;
} else {
obj.val = oldObj.val;
}
if (state.ack !== undefined) {
obj.ack = state.ack;
} else {
obj.ack = false;
}
if (state.ts !== undefined) {
obj.ts = (state.ts < 946681200000) ? state.ts * 1000 : state.ts; // if less 2000.01.01 00:00:00
} else {
obj.ts = (new Date()).getTime();
}
if (state.q !== undefined) {
obj.q = state.q;
} else {
obj.q = 0;
}
obj.from = state.from;
let hasChanged;
if (state.lc !== undefined) {
obj.lc = state.lc;
} else {
if (typeof obj.val === 'object') {
hasChanged = JSON.stringify(oldObj.val) !== JSON.stringify(obj.val);
} else {
hasChanged = oldObj.val !== obj.val;
}
if (!oldObj.lc || hasChanged) {
obj.lc = obj.ts;
} else {
obj.lc = oldObj.lc;
}
}
// publish event in redis
log.silly(settings.namespace + ' redis publish ' + redisNamespace + id + ' ' + JSON.stringify(obj));
client.publish(redisNamespace + id, JSON.stringify(obj));
// set object in redis
if (expire) {
//console.log('setex',redisNamespace + id, expire, JSON.stringify(obj));
client.setex(redisNamespace + id, expire, JSON.stringify(obj), function () {
if (typeof callback === 'function') {
callback();
}
});
} else {
//console.log('set',redisNamespace + id, JSON.stringify(obj));
client.set(redisNamespace + id, JSON.stringify(obj), function () {
if (typeof callback === 'function') {
callback();
}
});
}
});
};
// Used for restore function (do not call it)
this.setRawState = function (id, state, callback) {
//console.log('set',redisNamespace + id, JSON.stringify(obj));
client.set(redisNamespace + id, state, function () {
if (typeof callback === 'function') {
callback();
}
});
};
/**
* @method getState
*
* @param {String} id
* @param callback
*/
this.getState = function (id, callback) {
client.get(redisNamespace + id, function (err, obj) {
if (err) {
log.warn(settings.namespace + ' redis get ' + id + ', error - ' + err);
} else {
log.silly(settings.namespace + ' redis get ' + id + ' ok: ' + obj);
}
if (typeof callback === 'function') {
callback(err, obj ? JSON.parse(obj) : null);
}
});
};
this.getStates = function (keys, callback, dontModify) {
if (!keys) {
if (callback) callback('no keys', null);
return;
}
if (!keys.length) {
if (callback) callback(null, []);
return;
}
let _keys;
if (!dontModify) {
_keys = [];
for (let i = 0; i < keys.length; i++) {
_keys[i] = redisNamespace + keys[i];
}
} else {
_keys = keys;
}
client.mget(_keys, function (err, obj) {
if (err) {
log.warn(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length + ', err: ' + err);
} else {
log.silly(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length);
}
if (typeof callback === 'function') callback(err, obj);
});
};
// Destructor of the class. Called by shutting down.
this.destroy = function () {
if (client) {
client.end(true);
client = null;
}
if (sub) {
sub.end();
sub = null;
}
};
this.delState = function (id, callback) {
client.del(redisNamespace + id, function (err) {
if (err) {
log.warn(settings.namespace + ' redis del ' + id + ', error - ' + err);
} else {
client.publish(redisNamespace + id, 'null');
log.silly(settings.namespace + ' redis del ' + id + ', ok');
}
if (typeof callback === 'function') callback(err);
});
};
this.getKeys = function (pattern, callback, dontModify) {
client.keys(redisNamespace + pattern, function (err, obj) {
log.silly(settings.namespace + ' redis keys ' + obj.length + ' ' + pattern);
if (typeof callback === 'function') {
if (obj && !dontModify) {
const len = redisNamespace.length;
for (let i = 0; i < obj.length; i++) {
obj[i] = obj[i].substring(len);
}
}
callback(err, obj);
}
});
};
/**
* @method subscribe
*
* @param pattern
* @param {function} callback callback function (optional)
*/
this.subscribe = function (pattern, callback) {
log.silly(settings.namespace + ' redis psubscribe ' + redisNamespace + pattern);
sub.psubscribe(redisNamespace + pattern, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribe = function (pattern, callback) {
log.silly(settings.namespace + ' redis punsubscribe ' + redisNamespace + pattern);
sub.punsubscribe(redisNamespace + pattern, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.pushMessage = function (id, state, callback) {
state._id = globalMessageId++;
if (globalMessageId >= 0xFFFFFFFF) globalMessageId = 0;
client.publish(namespaceMsg + id, JSON.stringify(state));
if (typeof callback === 'function') callback(null, id);
};
// todo: delete it
this.lenMessage = function (id, callback) {
if (typeof callback === 'function') callback(null, 0, id);
};
// todo: delete it
this.getMessage = function (id, callback) {
if (typeof callback === 'function') callback(null, null, id);
};
// todo: delete it
this.delMessage = function (id, messageId, callback) {
if (typeof callback === 'function') callback(null, id);
};
// todo: delete it
this.clearAllMessages = function (callback) {
client.keys(namespaceLog + '*', function (err, obj) {
if (obj) {
for (let i = 0; i < obj.length; i++) {
log.silly('redis clear message for ' + obj[i]);
client.del(obj[i]);
}
}
if (typeof callback === 'function') callback(err);
});
};
this.subscribeMessage = function (id, callback) {
if (id && id[0] === '.') id = id.substring(1);
log.silly('redis subscribeMessage ' + namespaceMsg + id);
sub.psubscribe(namespaceMsg + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribeMessage = function (id, callback) {
if (id && id[0] === '.') id = id.substring(1);
log.silly('redis unsubscribeMessage ' + namespaceMsg + id);
sub.punsubscribe(namespaceMsg + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.pushLog = function (id, log, callback) {
log._id = globalLogId++;
if (globalLogId >= 0xFFFFFFFF) globalLogId = 0;
client.publish(namespaceLog + id, JSON.stringify(log));
if (typeof callback === 'function') callback(null, id);
};
// todo: delete it
this.lenLog = function (id, callback) {
if (typeof callback === 'function') callback('Not exists', 0, id);
// client.llen(namespaceLog + id, function (err, obj) {
// if (typeof callback === 'function') callback(err, obj, id);
// });
};
// todo: delete it
this.getLog = function (id, callback) {
if (typeof callback === 'function') {
callback('Not exists', null, 0);
}
};
// todo: delete it
this.delLog = function (id, logId, callback) {
if (typeof callback === 'function') {
callback('Not exists');
}
};
// todo: delete it
this.clearAllLogs = function (callback) {
client.keys(namespaceLog + '*', function (err, obj) {
if (obj) {
for (let i = 0; i < obj.length; i++) {
log.silly(settings.namespace + ' redis clear log for ' + obj[i]);
client.del(obj[i]);
}
}
if (typeof callback === 'function') {
callback(err);
}
});
};
this.subscribeLog = function (id, callback) {
log.silly(settings.namespace + ' redis subscribeMessage ' + namespaceLog + id);
sub.psubscribe(namespaceLog + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribeLog = function (id, callback) {
log.silly(settings.namespace + ' redis unsubscribeMessage ' + namespaceLog + id);
sub.punsubscribe(namespaceLog + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.getSession = function (id, callback) {
client.get(namespaceSession + id, function (err, obj) {
log.silly(settings.namespace + ' redis get ' + id + ' ' + obj);
if (typeof callback === 'function') callback(obj ? JSON.parse(obj) : null);
});
};
this.setSession = function (id, expire, obj, callback) {
client.setex(namespaceSession + id, expire, JSON.stringify(obj), function () {
log.silly(settings.namespace + ' redis setex', id, expire, obj);
if (typeof callback === 'function') callback();
});
};
this.destroySession = function (id, callback) {
id = namespaceSession + id;
log.silly(settings.namespace + ' redis del ' + id);
client.del(id, function () {
if (typeof callback === 'function') callback();
});
};
/* this.getConfig = function (id, callback) {
id = namespaceConfig + id;
client.get(id, function (err, obj) {
log.silly(settings.namespace + ' redis get ' + id + ' ' + obj);
if (typeof callback === 'function') callback(err, obj ? JSON.parse(obj) : null);
});
};
this.getConfigKeys = function (pattern, callback, dontModify) {
client.keys(namespaceConfig + pattern, function (err, obj) {
log.silly(settings.namespace + ' redis config keys ' + obj.length + ' ' + pattern);
if (typeof callback === 'function') {
if (obj && !dontModify) {
var len = redisNamespace.length;
for (var i = 0; i < obj.length; i++) {
obj[i] = obj[i].substring(len);
}
}
callback(err, obj);
}
});
};
this.getConfigs = function (keys, callback, dontModify) {
if (!keys) {
if (callback) callback('no keys', null);
return;
}
if (!keys.length) {
if (callback) callback(null, []);
return;
}
var _keys;
if (!dontModify) {
_keys = [];
for (var i = 0; i < keys.length; i++) {
_keys[i] = namespaceConfig + keys[i];
}
} else {
_keys = keys;
}
client.mget(_keys, function (err, obj) {
if (err) {
log.warn(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length + ', err: ' + err);
} else {
log.silly(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length);
}
if (typeof callback === 'function') callback(err, obj);
});
};
this.setConfig = function (id, obj, callback) {
id = namespaceConfig + id;
client.set(id, JSON.stringify(obj), function (err) {
log.silly(settings.namespace + ' redis set', id, obj);
client.publish(id, JSON.stringify(obj));
if (typeof callback === 'function') callback(err, {id: id});
});
};
this.delConfig = function (id, callback) {
id = namespaceConfig + id;
log.silly(settings.namespace + ' redis del ' + id);
client.del(id, function (err) {
client.publish(id, null);
if (typeof callback === 'function') callback(err);
});
};
this.subscribeConfig = function (id, callback) {
log.silly(settings.namespace + ' redis subscribeConfig ' + namespaceConfig + id);
sub.psubscribe(namespaceConfig + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribeConfig = function (id, callback) {
log.silly(settings.namespace + ' redis unsubscribeConfig ' + namespaceConfig + id);
sub.punsubscribe(namespaceConfig + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};*/
function _createBinaryClient() {
if (!clientBin) {
settings.connection.options = settings.connection.options || {};
let opt = JSON.parse(JSON.stringify(settings.connection.options));
opt.return_buffers = true;
if (settings.connection.port === 0) {
// initiate a unix socket connection using the parameter 'host'
clientBin = redis.createClient(settings.connection.host, opt);
} else {
clientBin = redis.createClient(settings.connection.port, settings.connection.host, opt);
}
}
}
this.setBinaryState = function (id, data, callback) {
if (!clientBin) _createBinaryClient ();
clientBin.set(id, data, callback);
};
this.getBinaryState = function (id, callback) {
if (!clientBin) _createBinaryClient ();
clientBin.get(id, function (err, data) {
if (!err && data) {
if (callback) callback(err, new Buffer(data, 'binary'));
} else {
if (callback) callback(err);
}
});
};
this.delBinaryState = function (id, callback) {
if (!clientBin) _createBinaryClient ();
clientBin.del(id, function () {
if (typeof callback === 'function') callback();
});
};
(function __construct() {
if (settings.connection.port === 0) {
// initiate a unix socket connection using the parameter 'host'
client = redis.createClient(settings.connection.host, settings.connection.options);
sub = redis.createClient(settings.connection.host, settings.connection.options);
} else {
client = redis.createClient(settings.connection.port, settings.connection.host, settings.connection.options);
sub = redis.createClient(settings.connection.port, settings.connection.host, settings.connection.options);
}
if (typeof onChange === 'function') {
sub.on('pmessage', function (pattern, channel, message) {
log.debug(settings.namespace + ' redis pmessage ', pattern, channel, message);
try {
if (ioRegExp.test(channel)) {
onChange(channel.slice(redisNamespace.length), message ? JSON.parse(message) : null);
} else {
onChange(channel, message ? JSON.parse(message) : null);
}
} catch (e) {
log.error(settings.namespace + ' pmessage ' + channel + ' ' + message + ' ' + e.message);
log.error(settings.namespace + ' ' + e.stack);
}
});
}
client.on('error', error => {
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.error(settings.namespace + ' ' + error.message);
log.error(settings.namespace + ' ' + error.stack);
}
});
sub.on('error', error => {
log.error(settings.namespace + ' No redis connection!');
});
sub.on('connect', error => {
if (settings.connection.port === 0) {
log.info(settings.namespace + ' States connected to redis: ' + settings.connection.host);
} else {
log.info(settings.namespace + ' States connected to redis: ' + settings.connection.host + ':' + settings.connection.port);
}
});
client.on('connect', error => {
if (typeof settings.connected === 'function') settings.connected();
});
})();
return this;
}
module.exports = StateRedis;

1203
lib/tools.js Normal file

File diff suppressed because it is too large Load Diff

144
lib/uploadFiles.js Normal file
View File

@@ -0,0 +1,144 @@
/**
*
* upload.js
*
* bulk upload a folder as attachment into one CouchDB object
*
* 8'2014 hobbyquaker <hq@ccu.io>
*
*/
'use strict';
var yargs = require('yargs')
.alias('d', 'dir')
.alias('o', 'object')
.alias('p', 'prefix')
.demand(['object'])
.usage('$0 --object object._id [--dir directory] [--prefix prefix]\n' +
'Example: \n' +
' Upload the content of the folder "images" into the virtual folder "img" of the virtual filesystem "fs.www"\n' +
' $0 -o fs.www -d images -p img')
;
var fs = require('fs');
var mime = require('mime');
var Objects = require(__dirname + '/objects.js');
var tools = require('./tools');
var files = [];
var rev;
var dir;
var prefix;
var db = new Objects({
logger: {
silly: function (msg) { },
debug: function (msg) { },
info: function (msg) { },
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: function (type) {
var id = yargs.argv.object;
dir = yargs.argv.dir || './';
prefix = yargs.argv.prefix || '';
db.getObject(yargs.argv.object, function (err, res) {
if (err || !res) {
db.setObject(yargs.argv.object, {
type: 'fs',
parent: 'fs',
common: {
name: yargs.argv.object.split('.').pop()
},
ts: new Date().getTime(),
from: 'system.host.' + tools.getHostName() + '.cli',
native: {}
}, function (err, res) {
rev = res.rev;
main();
});
} else {
rev = res._rev;
main();
}
});
}
});
function main() {
walk(dir, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
files = res;
upload();
});
}
function upload() {
var file;
if (!files.length) {
console.log('done. ' + yargs.argv.object + '._rev=' + rev);
process.exit(0);
} else {
file = files.pop();
var mimeType = mime.lookup(file);
var attName = prefix + (file.split('/').slice(1).join('/'));
console.log('upload', file, attName, mimeType);
fs.createReadStream(file).pipe(
db.insert(yargs.argv.object, attName, null, mimeType, {
rev: rev
}, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
rev = res.rev;
setTimeout(function () {
upload();
}, 50);
})
);
}
}
function walk(dir, done) {
var results = [];
fs.readdir(dir, function (err, list) {
if (err) return done(err);
var i = 0;
(function next() {
var file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, function (err, stat) {
if (stat && stat.isDirectory()) {
walk(file, function (err, res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
}

53
lib/vis/states.js Normal file
View File

@@ -0,0 +1,53 @@
var getUsedObjectIDs = require(__dirname + '/../www/js/visUtils').getUsedObjectIDs;
function calcProject(objects, projects, instance, result, callback) {
if (!projects || !projects.length) {
callback(null, result || []);
return;
}
result = result || [];
var project = projects.shift();
if (!project || !project.isDir) {
setImmediate(calcProject, objects, projects, instance, result, callback);
return;
}
// calculate datapoints in one project
objects.readFile('vis.' + instance, '/' + project.file + '/vis-views.json', function (err, data) {
var json;
try {
json = JSON.parse(data);
} catch (e) {
console.error('Cannot parse "/' + project.file + '/vis-views.json');
setImmediate(calcProject, objects, projects, instance, result, callback);
return;
}
var dps = getUsedObjectIDs(json, false);
if (dps && dps.IDs) {
result.push({id: 'vis.' + instance + '.datapoints.' + project.file.replace(/[.\\s]/g, '_'), val: dps.IDs.length});
}
setImmediate(calcProject, objects, projects, instance, result, callback);
});
}
function calcProjects(objects, states, instance, config, callback) {
objects.readDir('vis.' + instance, '/', function (err, projects) {
if (err || !projects || !projects.length) {
callback && callback(err || null, [{id: 'vis.' + instance + '.datapoints.total', val: 0}]);
} else {
calcProject(objects, projects, instance, [], function (err, result) {
if (result && result.length) {
var total = 0;
for (var r = 0; r < result.length; r++) {
total += result[r].val;
}
result.push({id: 'vis.' + instance + '.datapoints.total', val: total});
}
callback && callback(err, result);
});
}
});
}
module.exports = calcProjects;

537
lib/www/js/visUtils.js Normal file
View File

@@ -0,0 +1,537 @@
function getWidgetGroup(views, view, widget) {
var widgets = views[view].widgets;
var members;
for (var w in widgets) {
if (!widgets.hasOwnProperty(w)) continue;
members = views[view].widgets[w].data.members;
if (members && members.indexOf(widget) !== -1) {
return w;
}
}
return null;
}
function extractBinding(format) {
var oid = format.match(/{(.+?)}/g);
var result = null;
if (oid) {
if (oid.length > 50) {
console.warn('Too many bindings in one widget: ' + oid.length + '[max = 50]');
}
for (var p = 0; p < oid.length && p < 50; p++) {
var _oid = oid[p].substring(1, oid[p].length - 1);
if (_oid[0] === '{') continue;
// If first symbol '"' => it is JSON
if (_oid && _oid[0] === '"') continue;
var parts = _oid.split(';');
result = result || [];
var systemOid = parts[0].trim();
var visOid = systemOid;
var test1 = visOid.substring(visOid.length - 4);
var test2 = visOid.substring(visOid.length - 3);
if (visOid && test1 !== '.val' && test2 !== '.ts' && test2 !== '.lc' && test1 !== '.ack') {
visOid = visOid + '.val';
}
var isSeconds = (test2 === '.ts' || test2 === '.lc');
test1 = systemOid.substring(systemOid.length - 4);
test2 = systemOid.substring(systemOid.length - 3);
if (test1 === '.val' || test1 === '.ack') {
systemOid = systemOid.substring(0, systemOid.length - 4);
} else if (test2 === '.lc' || test2 === '.ts') {
systemOid = systemOid.substring(0, systemOid.length - 3);
}
var operations = null;
var isEval = visOid.match(/[\d\w_.]+:[-\d\w_.]+/) || (!visOid.length && parts.length > 0);//(visOid.indexOf(':') !== -1) && (visOid.indexOf('::') === -1);
if (isEval) {
var xx = visOid.split(':', 2);
var yy = systemOid.split(':', 2);
visOid = xx[1];
systemOid = yy[1];
operations = operations || [];
operations.push({
op: 'eval',
arg: [{
name: xx[0],
visOid: visOid,
systemOid: systemOid
}]
});
}
for (var u = 1; u < parts.length; u++) {
// eval construction
if (isEval) {
if (parts[u].trim().match(/^[\d\w_.]+:[-.\d\w_]+$/)) {//parts[u].indexOf(':') !== -1 && parts[u].indexOf('::') === -1) {
var _systemOid = parts[u].trim();
var _visOid = _systemOid;
test1 = _visOid.substring(_visOid.length - 4);
test2 = _visOid.substring(_visOid.length - 3);
if (test1 !== '.val' && test2 !== '.ts' && test2 !== '.lc' && test1 !== '.ack') {
_visOid = _visOid + '.val';
}
test1 = systemOid.substring(_systemOid.length - 4);
test2 = systemOid.substring(_systemOid.length - 3);
if (test1 === '.val' || test1 === '.ack') {
_systemOid = _systemOid.substring(0, _systemOid.length - 4);
} else if (test2 === '.lc' || test2 === '.ts') {
_systemOid = _systemOid.substring(0, _systemOid.length - 3);
}
var x1 = _visOid.split(':', 2);
var y1 = _systemOid.split(':', 2);
operations[0].arg.push({
name: x1[0],
visOid: x1[1],
systemOid: y1[1]
});
} else {
parts[u] = parts[u].replace(/::/g, ':');
if (operations[0].formula) {
var n = JSON.parse(JSON.stringify(operations[0]));
n.formula = parts[u];
operations.push(n);
} else {
operations[0].formula = parts[u];
}
}
} else {
var parse = parts[u].match(/([\w\s\/+*-]+)(\(.+\))?/);
if (parse && parse[1]) {
parse[1] = parse[1].trim();
// operators requires parameter
if (parse[1] === '*' ||
parse[1] === '+' ||
parse[1] === '-' ||
parse[1] === '/' ||
parse[1] === '%' ||
parse[1] === 'min' ||
parse[1] === 'max') {
if (parse[2] === undefined) {
console.log('Invalid format of format string: ' + format);
parse[2] = null;
} else {
parse[2] = (parse[2] || '').trim().replace(',', '.');
parse[2] = parse[2].substring(1, parse[2].length - 1);
parse[2] = parseFloat(parse[2].trim());
if (parse[2].toString() === 'NaN') {
console.log('Invalid format of format string: ' + format);
parse[2] = null;
} else {
operations = operations || [];
operations.push({op: parse[1], arg: parse[2]});
}
}
} else
// date formatting
if (parse[1] === 'date') {
operations = operations || [];
parse[2] = (parse[2] || '').trim();
parse[2] = parse[2].substring(1, parse[2].length - 1);
operations.push({op: parse[1], arg: parse[2]});
} else
// returns array[value]. e.g.: {id.ack;array(ack is false,ack is true)}
if (parse[1] === 'array') {
operations = operations || [];
param = (parse[2] || '').trim();
param = param.substring(1, param.length - 1);
param = param.split(',');
if (Array.isArray(param)) {
operations.push ({op: parse[1], arg: param}); //xxx
}
} else
// value formatting
if (parse[1] === 'value') {
operations = operations || [];
var param = (parse[2] === undefined) ? '(2)' : (parse[2] || '');
param = param.trim();
param = param.substring(1, param.length - 1);
operations.push({op: parse[1], arg: param});
} else
// operators have optional parameter
if (parse[1] === 'pow' || parse[1] === 'round' || parse[1] === 'random') {
if (parse[2] === undefined) {
operations = operations || [];
operations.push({op: parse[1]});
} else {
parse[2] = (parse[2] || '').trim().replace(',', '.');
parse[2] = parse[2].substring(1, parse[2].length - 1);
parse[2] = parseFloat(parse[2].trim());
if (parse[2].toString() === 'NaN') {
console.log('Invalid format of format string: ' + format);
parse[2] = null;
} else {
operations = operations || [];
operations.push({op: parse[1], arg: parse[2]});
}
}
} else
// operators without parameter
{
operations = operations || [];
operations.push({op: parse[1]});
}
} else {
console.log('Invalid format ' + format);
}
}
}
result.push({
visOid: visOid,
systemOid: systemOid,
token: oid[p],
operations: operations ? operations : undefined,
format: format,
isSeconds: isSeconds
});
}
}
return result;
}
function getUsedObjectIDs(views, isByViews) {
if (!views) {
console.log('Check why views are not yet loaded!');
return null;
}
var _views = isByViews ? {} : null;
var IDs = [];
var visibility = {};
var bindings = {};
var lastChanges = {};
var signals = {};
var view;
var id;
var sidd;
for (view in views) {
if (!views.hasOwnProperty(view)) continue;
if (view === '___settings') continue;
if (_views) _views[view] = [];
for (id in views[view].widgets) {
if (!views[view].widgets.hasOwnProperty(id)) continue;
// Check all attributes
var data = views[view].widgets[id].data;
var style = views[view].widgets[id].style;
// fix error in naming
if (views[view].widgets[id].groupped) {
views[view].widgets[id].grouped = true;
delete views[view].widgets[id].groupped;
}
// rename hqWidgets => hqwidgets
if (views[view].widgets[id].widgetSet === 'hqWidgets') {
views[view].widgets[id].widgetSet = 'hqwidgets';
}
// rename RGraph => rgraph
if (views[view].widgets[id].widgetSet === 'RGraph') {
views[view].widgets[id].widgetSet = 'rgraph';
}
// rename timeAndWeather => timeandweather
if (views[view].widgets[id].widgetSet === 'timeAndWeather') {
views[view].widgets[id].widgetSet = 'timeandweather';
}
// convert "Show on Value" to HTML
if (views[view].widgets[id].tpl === 'tplShowValue') {
views[view].widgets[id].tpl = 'tplHtml';
views[view].widgets[id].data['visibility-oid'] = views[view].widgets[id].data.oid;
views[view].widgets[id].data['visibility-val'] = views[view].widgets[id].data.value;
delete views[view].widgets[id].data.oid;
delete views[view].widgets[id].data.value;
}
// convert "Hide on >0/True" to HTML
if (views[view].widgets[id].tpl === 'tplHideTrue') {
views[view].widgets[id].tpl = 'tplHtml';
views[view].widgets[id].data['visibility-cond'] = '!=';
views[view].widgets[id].data['visibility-oid'] = views[view].widgets[id].data.oid;
views[view].widgets[id].data['visibility-val'] = true;
delete views[view].widgets[id].data.oid;
}
// convert "Hide on 0/False" to HTML
if (views[view].widgets[id].tpl === 'tplHide') {
views[view].widgets[id].tpl = 'tplHtml';
views[view].widgets[id].data['visibility-cond'] = '!=';
views[view].widgets[id].data['visibility-oid'] = views[view].widgets[id].data.oid;
views[view].widgets[id].data['visibility-val'] = false;
delete views[view].widgets[id].data.oid;
}
// convert "Door/Window sensor" to HTML
if (views[view].widgets[id].tpl === 'tplHmWindow') {
views[view].widgets[id].tpl = 'tplValueBool';
views[view].widgets[id].data.html_false = views[view].widgets[id].data.html_closed;
views[view].widgets[id].data.html_true = views[view].widgets[id].data.html_open;
delete views[view].widgets[id].data.html_closed;
delete views[view].widgets[id].data.html_open;
}
// convert "Door/Window sensor" to HTML
if (views[view].widgets[id].tpl === 'tplHmWindowRotary') {
views[view].widgets[id].tpl = 'tplValueListHtml8';
views[view].widgets[id].data.count = 2;
views[view].widgets[id].data.value0 = views[view].widgets[id].data.html_closed;
views[view].widgets[id].data.value1 = views[view].widgets[id].data.html_open;
views[view].widgets[id].data.value2 = views[view].widgets[id].data.html_tilt;
delete views[view].widgets[id].data.html_closed;
delete views[view].widgets[id].data.html_open;
delete views[view].widgets[id].data.html_tilt;
}
// convert "tplBulbOnOff" to tplBulbOnOffCtrl
if (views[view].widgets[id].tpl === 'tplBulbOnOff') {
views[view].widgets[id].tpl = 'tplBulbOnOffCtrl';
views[view].widgets[id].data.readOnly = true;
}
// convert "tplValueFloatBarVertical" to tplValueFloatBar
if (views[view].widgets[id].tpl === 'tplValueFloatBarVertical') {
views[view].widgets[id].tpl = 'tplValueFloatBar';
views[view].widgets[id].data.orientation = 'vertical';
}
for (var attr in data) {
if (!data.hasOwnProperty(attr) || !attr) continue;
/* TODO DO do not forget remove it after a while. Required for import from DashUI */
if (attr === 'state_id') {
data.state_oid = data[attr];
delete data[attr];
attr = 'state_oid';
} else
if (attr === 'number_id') {
data.number_oid = data[attr];
delete data[attr];
attr = 'number_oid';
} else
if (attr === 'toggle_id') {
data.toggle_oid = data[attr];
delete data[attr];
attr = 'toggle_oid';
} else
if (attr === 'set_id') {
data.set_oid = data[attr];
delete data[attr];
attr = 'set_oid';
} else
if (attr === 'temp_id') {
data.temp_oid = data[attr];
delete data[attr];
attr = 'temp_oid';
} else
if (attr === 'drive_id') {
data.drive_oid = data[attr];
delete data[attr];
attr = 'drive_oid';
} else
if (attr === 'content_id') {
data.content_oid = data[attr];
delete data[attr];
attr = 'content_oid';
} else
if (attr === 'dialog_id') {
data.dialog_oid = data[attr];
delete data[attr];
attr = 'dialog_oid';
} else
if (attr === 'max_value_id') {
data.max_value_oid = data[attr];
delete data[attr];
attr = 'max_value_oid';
} else
if (attr === 'dialog_id') {
data.dialog_oid = data[attr];
delete data[attr];
attr = 'dialog_oid';
} else
if (attr === 'weoid') {
data.woeid = data[attr];
delete data[attr];
attr = 'woeid';
}
if (typeof data[attr] === 'string') {
var m;
var oids = extractBinding(data[attr]);
if (oids) {
for (var t = 0; t < oids.length; t++) {
var ssid = oids[t].systemOid;
if (ssid) {
if (IDs.indexOf(ssid) === -1) IDs.push(ssid);
if (_views && _views[view].indexOf(ssid) === -1) _views[view].push(ssid);
if (!bindings[ssid]) bindings[ssid] = [];
oids[t].type = 'data';
oids[t].attr = attr;
oids[t].view = view;
oids[t].widget = id;
bindings[ssid].push(oids[t]);
}
if (oids[t].operations && oids[t].operations[0].arg instanceof Array) {
for (var ww = 0; ww < oids[t].operations[0].arg.length; ww++) {
ssid = oids[t].operations[0].arg[ww].systemOid;
if (!ssid) continue;
if (IDs.indexOf(ssid) === -1) IDs.push(ssid);
if (_views && _views[view].indexOf(ssid) === -1) _views[view].push(ssid);
if (!bindings[ssid]) bindings[ssid] = [];
bindings[ssid].push(oids[t]);
}
}
}
} else
if (attr !== 'oidTrueValue' && attr !== 'oidFalseValue' && ((attr.match(/oid\d{0,2}$/) || attr.match(/^oid/) || attr.match(/^signals-oid-/) || attr === 'lc-oid') && data[attr])) {
if (data[attr] && data[attr] !== 'nothing_selected') {
if (IDs.indexOf(data[attr]) === -1) IDs.push(data[attr]);
if (_views && _views[view].indexOf(data[attr]) === -1) _views[view].push(data[attr]);
}
// Visibility binding
if (attr === 'visibility-oid' && data['visibility-oid']) {
var vid = data['visibility-oid'];
if (vid.match(/^groupAttr(\d+)$/)) {
var vgroup = getWidgetGroup(views, view, id);
if (vgroup) vid = views[view].widgets[vgroup].data[vid];
}
if (!visibility[vid]) visibility[vid] = [];
visibility[vid].push({view: view, widget: id});
}
// Signal binding
if (attr.match(/^signals-oid-/) && data[attr]) {
var sid = data[attr];
if (sid.match(/^groupAttr(\d+)$/)) {
var group = getWidgetGroup(views, view, id);
if (group) sid = views[view].widgets[group].data[sid];
}
if (!signals[sid]) signals[sid] = [];
signals[sid].push({
view: view,
widget: id,
index: parseInt(attr.substring('signals-oid-'.length), 10)
});
}
if (attr === 'lc-oid') {
var lcsid = data[attr];
if (lcsid.match(/^groupAttr(\d+)$/)) {
var ggroup = getWidgetGroup(views, view, id);
if (ggroup) lcsid = views[view].widgets[ggroup].data[lcsid];
}
if (!lastChanges[lcsid]) lastChanges[lcsid] = [];
lastChanges[lcsid].push({
view: view,
widget: id
});
}
} else
if ((m = attr.match(/^attrType(\d+)$/)) && data[attr] === 'id') {
var _id = 'groupAttr' + m[1];
if (data[_id]) {
if (IDs.indexOf(data[_id]) === -1) IDs.push(data[_id]);
if (_views && _views[view].indexOf(data[_id]) === -1) _views[view].push(data[_id]);
}
}
}
}
// build bindings for styles
if (style) {
for (var cssAttr in style) {
if (!style.hasOwnProperty(cssAttr) || !cssAttr) continue;
if (typeof style[cssAttr] === 'string') {
var objIDs = extractBinding(style[cssAttr]);
if (objIDs) {
for (var tt = 0; tt < objIDs.length; tt++) {
sidd = objIDs[tt].systemOid;
if (sidd) {
if (IDs.indexOf(sidd) === -1) IDs.push(sidd);
if (_views && _views[view].indexOf(sidd) === -1) _views[view].push(sidd);
if (!bindings[sidd]) bindings[sidd] = [];
objIDs[tt].type = 'style';
objIDs[tt].attr = cssAttr;
objIDs[tt].view = view;
objIDs[tt].widget = id;
bindings[sidd].push(objIDs[tt]);
}
if (objIDs[tt].operations && objIDs[tt].operations[0].arg instanceof Array) {
for (var w = 0; w < objIDs[tt].operations[0].arg.length; w++) {
sidd = objIDs[tt].operations[0].arg[w].systemOid;
if (!sidd) continue;
if (IDs.indexOf(sidd) === -1) IDs.push(sidd);
if (_views && _views[view].indexOf(sidd) === -1) _views[view].push(sidd);
if (!bindings[sidd]) bindings[sidd] = [];
bindings[sidd].push(objIDs[tt]);
}
}
}
}
}
}
}
}
}
if (_views) {
var changed;
do {
changed = false;
// Check containers
for (view in views) {
if (!views.hasOwnProperty(view)) continue;
if (view === '___settings') continue;
for (id in views[view].widgets) {
if (!views[view].widgets.hasOwnProperty(id)) continue;
// Add all OIDs from this view to parent
if (views[view].widgets[id].tpl === 'tplContainerView' && views[view].widgets[id].data.contains_view) {
var ids = _views[views[view].widgets[id].data.contains_view];
if (ids) {
for (var a = 0; a < ids.length; a++) {
if (ids[a] && _views[view].indexOf(ids[a]) === -1) {
_views[view].push(ids[a]);
changed = true;
}
}
} else {
console.warn('View does not exist: "' + views[view].widgets[id].data.contains_view + '"');
}
}
}
}
} while (changed);
}
return {IDs: IDs, byViews: _views, visibility: visibility, bindings: bindings, lastChanges: lastChanges, signals: signals};
}
if (module && module.parent) {
module.exports.getUsedObjectIDs = getUsedObjectIDs;
}

366
lib/zipFiles.js Normal file
View File

@@ -0,0 +1,366 @@
'use strict';
var JSZip;
var tools = require(__dirname + '/tools');
function _getAllFilesInDir(objects, id, name, options, callback, result) {
result = result || [];
objects.readDir(id, name, options, function (err, files) {
var count = 0;
var errors = [];
if (files) {
for (var f = 0; f < files.length; f++) {
if (files[f].isDir) {
count++;
_getAllFilesInDir(objects, id, name + '/' + files[f].file, options, function (err, _result) {
if (err) errors.push(err);
if (!--count) callback(errors.length ? errors : null, _result);
}, result);
} else {
result.push(name + '/' + files[f].file);
}
}
}
if (!count) callback(null, result);
});
}
function _addFile(objects, id, name, options, zip, files, callback) {
objects.readFile(id, name, options, function (err, data, mime) {
if (!zip) {
console.log(err);
callback('Cannot read file "' + name + '": ' + err, files);
} else {
// if handler installed
if (options.stringify) {
try {
data = options.stringify(name, data, options ? options.settings : null, files);
} catch (error) {
console.error('Cannot stringify file "' + name + '": ' + error);
if (!err) err = 'Cannot stringify file "' + name + '": ' + error;
}
}
var parts = name.split('/');
if (parts.length > 1) {
parts.shift();
name = parts.join('/');
}
zip.file(name, data);
setImmediate(function () {
callback(err, files);
});
}
});
}
// pack all files as zip and send it back
function readDirAsZip(objects, id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (name[0] === '/') name = name.substring(1);
options = options || {};
var adapter = id;
if (adapter.indexOf('.') !== -1) {
adapter = id.split('.')[0];
}
// try to load processor of adapter
try {
options.stringify = require(tools.appName + '.' + adapter + '/lib/convert.js').stringify;
} catch (e) {
}
_getAllFilesInDir(objects, id, name, options, function (err, files) {
var count = 0;
if (files) {
JSZip = JSZip || require('jszip');
var zip = new JSZip();
var additional = [];
for (var f = 0; f < files.length; f++) {
count++;
_addFile(objects, id, files[f], options, zip, additional, function (err, additional) {
if (!--count) {
if (additional && additional.length) {
for (var ff = 0; ff < additional.length; ff++) {
if (!additional[ff] || typeof additional[ff] !== 'string') continue;
count++;
if (additional[ff][0] === '/') additional[ff] = additional[ff].substring(1);
var parts = additional[ff].split('/');
var adapter = parts.shift();
_addFile(objects, adapter, parts.join('/'), options, zip, null, function (err) {
if (!--count) {
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
zip = null;
}
});
}
if (!count) {
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
zip = null;
}
} else {
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
zip = null;
}
}
});
}
}
if (!count) {
callback(err, null);
}
});
}
function _checkDir(objects, id, root, parts, options, callback) {
if (!parts || !parts.length) {
callback();
return;
}
root += '/' + parts.shift();
objects.readDir(id, root, options, function (err, files) {
if (err === 'Not exists') {
objects.mkdir(id, root, options, function (err) {
_checkDir(objects, id, root, parts, options, callback);
});
} else {
_checkDir(objects, id, root, parts, options, callback);
}
});
}
function _writeOneFile(objects, zip, id, name, filename, options, callback) {
zip.files[filename].async('nodebuffer').then(function (data) {
var _err;
if (options.parse) {
try {
data = options.parse(name, filename, data, options ? options.settings : null);
} catch (e) {
_err = e;
}
}
var fName = name + filename;
var parts = fName.split('/');
parts.pop();
_checkDir(objects, id, '', parts, options, function () {
objects.writeFile(id, name + filename, data, options, function (err) {
callback(_err || err);
});
});
}, function (err) {
callback(err);
});
}
function writeDirAsZip(objects, id, name, data, options, callback) {
JSZip = JSZip || require('jszip');
var zip = new JSZip();
options = options || {};
var adapter = id;
if (adapter.indexOf('.') !== -1) {
adapter = id.split('.')[0];
}
// try to load processor of adapter
try {
options.parse = require(tools.appName + '.' + adapter + '/lib/convert.js').parse;
} catch (e) {
}
try {
zip.loadAsync(data).then(function () {
var count = 0;
var error = [];
if (name[name.length - 1] !== '/') name += '/';
for (var filename in zip.files) {
if (!filename || filename[filename.length - 1] === '/') continue;
count++;
try {
_writeOneFile(objects, zip, id, name, filename, options, function (err) {
if (err) {
error.push('Cannot write file "' + filename + '":' + err.toString());
}
if (!--count && callback) {
callback(error.length ? error.join(', ') : null);
callback = null;
}
});
} catch (error) {
if (callback) {
callback(error.toString());
callback = null;
}
}
}
}, function (error) {
if (callback) {
callback(error.toString());
callback = null;
}
});
} catch (error) {
if (callback) {
callback(error.toString());
callback = null;
}
}
}
// pack all files as zip and send it back
function readObjectsAsZip(objects, rootId, adapter, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (adapter) {
// try to load processor of adapter
try {
options.stringify = require(tools.appName + '.' + adapter + '/lib/convert.js').stringify;
} catch (e) {
}
}
objects.getConfigKeys(rootId + '.*', options, function (err, keys) {
objects.getObjects(keys, options, function(err, objs) {
JSZip = JSZip || require('jszip');
var zip = new JSZip();
for (var f = 0; f < objs.length; f++) {
var data = {id: keys[f], data: objs[f]};
if (options.stringify) {
try {
data = options.stringify(data, options ? options.settings : null);
} catch (e) {
data.id = keys[f].replace(/\./g, '/').substring(rootId.length + 1) + '.json';
}
} else {
data.id = keys[f].replace(/\./g, '/').substring(rootId.length + 1) + '.json';
}
if (typeof data.data === 'object') data.data = JSON.stringify(data.data, null, 2);
zip.file(data.id, data.data);
}
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
});
});
}
function _writeOneObject(objects, zip, rootId, filename, options, callback) {
zip.files[filename].async('nodebuffer').then(function (data) {
data = {data: data.toString(), id: filename};
if (options.parse){
try {
data = options.parse(data, options ? options.settings : null);
} catch (e) {
callback('Cannot custom parse "' + data.id + '": ' + e);
return;
}
} else {
data.id = (rootId ? (rootId + '.') : '') + data.id.replace(/\//g, '.').replace(/\.json$/, '');
}
if (typeof data.data !== 'object') {
try {
data.data = JSON.parse(data.data);
} catch (e) {
callback('Cannot parse "' + data.id + '": ' + e);
return;
}
}
if (data && data.id && data.data) {
options.ts = new Date().getTime();
options.from = 'system.host.' + tools.getHostName() + '.cli';
objects.setObject(data.id, data.data, options, function (err) {
callback(err);
});
} else {
if (data && data.error) {
callback(data.error);
} else {
callback();
}
}
}, function (err) {
callback('Cannot parse unzip: ' + err);
});
}
function writeObjectsAsZip(objects, rootId, adapter, data, options, callback) {
JSZip = JSZip || require('jszip');
options = options || {};
if (adapter) {
// try to load processor of adapter
try {
options.parse = require(tools.appName + '.' + adapter + '/lib/convert.js').parse;
} catch (e) {
}
}
var zip = new JSZip();
try {
zip.loadAsync(data).then(function () {
var count = 0;
var error = [];
for (var filename in zip.files) {
if (filename[filename.length - 1] === '/') continue;
count++;
_writeOneObject(objects, zip, rootId, filename, options, function (err) {
if (err) error.push(err.toString());
if (!--count && callback) {
callback(error.length ? error.join(', ') : null);
callback = null;
}
});
}
}, function (error) {
if (callback) {
callback(error.toString());
callback = null;
}
});
} catch (error) {
if (callback) {
callback(error.toString());
callback = null;
}
}
}
module.exports.readDirAsZip = readDirAsZip;
module.exports.writeDirAsZip = writeDirAsZip;
module.exports.readObjectsAsZip = readObjectsAsZip;
module.exports.writeObjectsAsZip = writeObjectsAsZip;

101
package.json Normal file
View File

@@ -0,0 +1,101 @@
{
"name": "yunkong2.js-controller",
"version": "1.4.2",
"engines": {
"node": ">=4"
},
"optionalDependencies": {
"redis": "^2.8.0",
"greenlock": "^2.1.19",
"le-challenge-fs": "^2.0.8",
"le-sni-auto": "^2.1.1",
"winston-syslog": "^1.2.6"
},
"bin": {
"yunkong2": "./yunkong2.js"
},
"dependencies": {
"bluebird": "^3.5.1",
"daemonize2": "^0.4.2",
"yunkong2.admin": ">=2.0.9",
"jszip": "^3.1.5",
"mime": "^1.4.0",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"node-schedule": "^1.3.0",
"node.extend": "^2.0.0",
"prompt": "^1.0.0",
"pyconf": "^1.1.2",
"request": "^2.85.0",
"jsonwebtoken": "^8.2.1",
"safe-replace": "^1.0.2",
"semver": "^5.5.0",
"socket.io": "~2.1.0",
"socket.io-client": "~2.1.0",
"tar": "^4.4.1",
"winston": "^2.4.1",
"winston-daily-rotate-file": "^1.7.2",
"yargs": "^11.0.0"
},
"devDependencies": {
"@types/node": "^4.2.23",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"grunt": "^1.0.1",
"grunt-contrib-jshint": "^1.1.0",
"grunt-http": "^2.2.0",
"grunt-jscs": "^3.0.1",
"grunt-jsdoc": "^2.1.0",
"grunt-replace": "^1.0.1",
"istanbul": "^0.4.5",
"mocha": "^5.0.1"
},
"homepage": "http://www.yunkong2.com",
"description": "...domesticate the Internet of Things",
"keywords": [
"yunkong2",
"Smarthome",
"Home Automation",
"Smart Metering",
"Homematic",
"Hue",
"KNX",
"Z-Wave",
"ZigBee",
"Bidcos",
"TV",
"Sonos",
"AV Receiver"
],
"bugs": {
"url": "https://github.com/yunkong2/yunkong2.js-controller/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/yunkong2/yunkong2.js-controller/blob/master/LICENSE"
}
],
"author": "bluefox <dogafox@gmail.com>",
"contributors": [
"bluefox <dogafox@gmail.com>",
"hobbyquaker <hq@ccu.io>"
],
"repository": {
"type": "git",
"url": "https://github.com/yunkong2/yunkong2.js-controller"
},
"scripts": {
"preinstall": "node lib/preinstall_check.js",
"install": "node yunkong2.js setup first",
"start": "node yunkong2.js start",
"stop": "node yunkong2.js stop",
"restart": "node yunkong2.js restart",
"prepublish": "node lib/scripts/scripts.js --prepublish",
"test": "node node_modules/mocha/bin/mocha test --exit",
"test-redis-socket": "node node_modules/mocha/bin/mocha test/redis-socket/ --exit",
"coverage": "node node_modules/istanbul/lib/cli.js cover --config istanbul.yml node_modules/mocha/bin/_mocha ./test -- --ui bdd -R spec"
},
"main": "controller.js",
"license": "MIT"
}

33
reinstall.sh Normal file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
yunkong2 stop
BASE=$(pwd)
if [ ! -f "$BASE/yunkong2" ]
then
echo "Script needs to be started in the yunkong2 base directory (normally /opt/yunkong2 on linux)"
exit
fi
if [ -d ./node_modules ]
then
ls -1 ./node_modules | grep yunkong2. > reinstall.list.txt
chmod -R 777 *
cd node_modules
rm -R *
pwd
while read IN
do
npm install $IN --production --save --unsafe-perm --prefix $BASE
if [ $? -eq 0 ]
then
echo "DONE $IN"
else
echo "FAIL $IN"
fi
done < "$BASE/reinstall.list.txt"
chmod -R 777 *
rm "$BASE/reinstall.list.txt"
yunkong2 upload all
fi

58
tsconfig.json Normal file
View File

@@ -0,0 +1,58 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["es6"], /* Specify library files to be included in the compilation: */
"allowJs": true, /* Allow javascript files to be compiled. */
"checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": false, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"include": [
"yunkong2.js",
"lib/**/*.js"
]
}

1
yunkong2.js Normal file
View File

@@ -0,0 +1 @@
require(__dirname + '/lib/setup.js').execute();