Replace the gosuper component with a node module that handles communication with systemd, and stop using an init system in the supervisor container

Change-Type: patch
Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
Pablo Carranza Velez 2018-03-16 11:01:50 -03:00 committed by Cameron Diver
parent 11e8899455
commit 348ff66cee
No known key found for this signature in database
GPG Key ID: E76D7ACBEE436E12
16 changed files with 113 additions and 239 deletions

View File

@ -1,63 +1,4 @@
ARG ARCH=amd64
# Build golang supervisor
FROM debian:jessie-20170723 as gosuper
RUN apt-get update \
&& apt-get install -y \
build-essential \
curl \
rsync \
&& rm -rf /var/lib/apt/lists/
ENV GOLANG_VERSION 1.8.3
ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
ENV GOLANG_DOWNLOAD_SHA256 1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772
COPY gosuper/go-${GOLANG_VERSION}-patches /go-${GOLANG_VERSION}-patches
RUN mkdir /usr/src/go \
&& cd /usr/src/go \
&& curl -L -o go.tar.gz $GOLANG_DOWNLOAD_URL \
&& echo "${GOLANG_DOWNLOAD_SHA256} go.tar.gz" | sha256sum -c - \
&& tar xzf go.tar.gz -C /usr/local \
&& cd /usr/src \
&& rm -rf go \
&& export GOROOT_BOOTSTRAP=/usr/local/go-bootstrap \
&& cp -r /usr/local/go /usr/local/go-bootstrap \
&& cd /usr/local/go/src \
&& patch -p2 -i /go-${GOLANG_VERSION}-patches/0001-dont-fail-when-no-mmx.patch \
&& patch -p2 -i /go-${GOLANG_VERSION}-patches/0002-implement-atomic-quadword-ops-with-FILD-FISTP.patch \
&& ./make.bash \
&& rm -rf /usr/local/go-bootstrap
ENV UPX_VERSION 3.94
# UPX doesn't provide fingerprints so I checked this one manually
ENV UPX_SHA256 e1fc0d55c88865ef758c7e4fabbc439e4b5693b9328d219e0b9b3604186abe20
RUN mkdir /usr/src/upx \
&& cd /usr/src/upx \
&& curl -L -o upx.tar.xz https://github.com/upx/upx/releases/download/v$UPX_VERSION/upx-$UPX_VERSION-amd64_linux.tar.xz \
&& echo "${UPX_SHA256} upx.tar.xz" | sha256sum -c - \
&& tar xf upx.tar.xz --strip-components=1 \
&& cp ./upx /usr/bin/ \
&& cd /usr/src \
&& rm -rf upx
ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
COPY ./gosuper /go/src/resin-supervisor/gosuper
WORKDIR /go/src/resin-supervisor/gosuper
ENV GOOS linux
ENV GO386=387
ARG ARCH
RUN bash ./build.sh
RUN rsync -a --delete /go/bin/gosuper /build/
##############################################################################
# The node version here should match the version of the runtime image which is
# specified in the base-image subdirectory in the project
@ -146,12 +87,7 @@ RUN find . -path '*/coverage/*' -o -path '*/test/*' -o -path '*/.nyc_output/*' \
&& find . -type f -path '*/node_modules/knex/build*' -delete \
&& rm -rf node_modules/sqlite3/node.dtps
# Create /var/run/resin for the gosuper to place its socket in
RUN mkdir -p rootfs-overlay/var/run/resin
COPY entry.sh run.sh package.json rootfs-overlay/usr/src/app/
COPY inittab rootfs-overlay/etc/inittab
COPY entry.sh package.json rootfs-overlay/usr/src/app/
RUN rsync -a --delete node_modules rootfs-overlay /build
@ -171,7 +107,6 @@ WORKDIR /usr/src/app
COPY --from=node-build /usr/src/app/dist ./dist
COPY --from=node-deps /build/node_modules ./node_modules
COPY --from=gosuper /build/gosuper ./gosuper
COPY --from=node-deps /build/rootfs-overlay/ /
VOLUME /data
@ -186,3 +121,5 @@ ENV CONFIG_MOUNT_POINT=/boot/config.json \
HEALTHCHECK --interval=5m --start-period=1m --timeout=30s --retries=3 \
CMD wget -qO- http://127.0.0.1:${LISTEN_PORT:-48484}/v1/healthy || exit 1
CMD [ "./entry.sh" ]

View File

@ -6,7 +6,6 @@
# Build targets (require Docker 17.05 or greater):
# * supervisor (default) - builds a resin-supervisor image
# * deploy - pushes a resin-supervisor image to the registry, retrying up to 3 times
# * gosuper - builds the "gosuper" component (a golang image with the Go supervisor component at /go/bin/gosuper and /build/gosuper)
# * nodedeps, nodebuild - builds the node component, with the node_modules and src at /usr/src/app and /build (also includes a rootfs-overlay there)
# * supervisor-dind: build the development docker-in-docker supervisor that run-supervisor uses (requires a SUPERVISOR_IMAGE to be available locally)
#
@ -19,8 +18,6 @@
#
# Test/development targets:
# * run-supervisor, stop-supervisor - build and start or stop a docker-in-docker resin-supervisor (requires aufs, ability to run privileged containers, and a SUPERVISOR_IMAGE to be available locally)
# * format-gosuper, test-gosuper - build a gosuper image and run formatting or unit tests
# * test-integration - run an integration test (see gosuper/supertest). Requires a docker-in-docker supervisor to be running
#
# Variables for test/dev targets:
# * IMAGE: image to build and run (either for run-supervisor or test-gosuper/integration)
@ -193,33 +190,4 @@ nodedeps:
nodebuild:
$(MAKE) -f $(THIS_FILE) TARGET_COMPONENT=node-build IMAGE=$(IMAGE) ARCH=$(ARCH) supervisor-image
gosuper:
$(MAKE) -f $(THIS_FILE) TARGET_COMPONENT=gosuper IMAGE=$(IMAGE) ARCH=$(ARCH) supervisor-image
test-gosuper: gosuper
docker run \
--rm \
-v /var/run/dbus:/mnt/root/run/dbus \
-e DBUS_SYSTEM_BUS_ADDRESS="/mnt/root/run/dbus/system_bus_socket" \
$(IMAGE) bash -c \
'./test_formatting.sh && go test -v ./gosuper'
format-gosuper: gosuper
docker run \
--rm \
-v $(shell pwd)/gosuper:/go/src/resin-supervisor/gosuper \
$(IMAGE) \
go fmt ./...
test-integration: gosuper
docker run \
--rm \
--net=host \
-e SUPERVISOR_IP="$(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' resin_supervisor_1)" \
--volumes-from resin_supervisor_1 \
-v /var/run/dbus:/mnt/root/run/dbus \
-e DBUS_SYSTEM_BUS_ADDRESS="/mnt/root/run/dbus/system_bus_socket" \
$(IMAGE) \
go test -v ./supertest
.PHONY: supervisor deploy nodedeps nodebuild gosuper supervisor-dind run-supervisor
.PHONY: supervisor deploy nodedeps nodebuild supervisor-dind run-supervisor

View File

@ -18,7 +18,6 @@
#
# It pulls intermediate images for caching, if available:
# resin/$ARCH-supervisor-node:$TAG
# resin/$ARCH-supervisor-go:$TAG
#
# In all of these cases it will use "master" if $TAG is not found.
#
@ -49,15 +48,12 @@ TARGET_IMAGE=resin/$ARCH-supervisor:$TAG
# Intermediate images and cache
NODE_IMAGE=resin/$ARCH-supervisor-node:$TAG
NODE_BUILD_IMAGE=resin/$ARCH-supervisor-node:$TAG-build
GO_IMAGE=resin/$ARCH-supervisor-go:$TAG
TARGET_CACHE=$TARGET_IMAGE
GO_CACHE=$GO_IMAGE
NODE_CACHE=$NODE_IMAGE
NODE_BUILD_CACHE=$NODE_BUILD_IMAGE
TARGET_CACHE_MASTER=resin/$ARCH-supervisor:master
GO_CACHE_MASTER=resin/$ARCH-supervisor-go:master
NODE_CACHE_MASTER=resin/$ARCH-supervisor-node:master
NODE_BUILD_CACHE_MASTER=resin/$ARCH-supervisor-node:master-build
@ -73,29 +69,17 @@ function tryPullForCache() {
# Only if the pull succeeds we add a --cache-from option
tryPullForCache $TARGET_CACHE
tryPullForCache $TARGET_CACHE_MASTER
tryPullForCache $GO_CACHE
tryPullForCache $GO_CACHE_MASTER
tryPullForCache $NODE_CACHE
tryPullForCache $NODE_CACHE_MASTER
tryPullForCache $NODE_BUILD_CACHE
tryPullForCache $NODE_BUILD_CACHE_MASTER
if [ "$ENABLE_TESTS" = "true" ]; then
make ARCH=$ARCH IMAGE=$GO_IMAGE test-gosuper
fi
export DOCKER_BUILD_OPTIONS=${CACHE_FROM}
export ARCH
export PUBNUB_PUBLISH_KEY
export PUBNUB_SUBSCRIBE_KEY
export MIXPANEL_TOKEN
make IMAGE=$GO_IMAGE gosuper
if [ "$PUSH_IMAGES" = "true" ]; then
make IMAGE=$GO_IMAGE deploy || true
fi
export DOCKER_BUILD_OPTIONS="${DOCKER_BUILD_OPTIONS} --cache-from ${GO_IMAGE}"
make IMAGE=$NODE_BUILD_IMAGE nodebuild
if [ "$PUSH_IMAGES" = "true" ]; then
make IMAGE=$NODE_BUILD_IMAGE deploy || true
@ -123,12 +107,10 @@ fi
if [ "$CLEANUP" = "true" ]; then
tryRemove $TARGET_IMAGE
tryRemove $GO_IMAGE
tryRemove $NODE_IMAGE
tryRemove $NODE_BUILD_IMAGE
tryRemove $TARGET_CACHE
tryRemove $GO_CACHE
tryRemove $NODE_BUILD_CACHE
tryRemove $NODE_CACHE
fi

View File

@ -18,7 +18,7 @@ Alternatively, the Resin API (api.resin.io) has a proxy endpoint at `POST /super
The API is versioned (currently at v1), except for `/ping`.
You might notice that the formats of some responses differ. This is because they were implemented later, and in Go instead of node.js.
You might notice that the formats of some responses differ. This is because they were implemented later, and in Go instead of node.js - even if the Go pieces were later removed, so we kept the response format for backwards compatibility.
Here's the full list of endpoints implemented so far. In all examples, replace everything between `< >` for the corresponding values.
@ -119,7 +119,6 @@ When successful, responds with 202 accepted and a JSON object:
"Error": ""
}
```
(This is implemented in Go)
#### Request body
Can contain a `force` property, which if set to `true` will cause the update lock to be overridden.
@ -158,7 +157,6 @@ When successful, responds with 202 accepted and a JSON object:
"Error": ""
}
```
(This is implemented in Go)
#### Request body
Can contain a `force` property, which if set to `true` will cause the update lock to be overridden.
@ -197,8 +195,6 @@ When successful, responds with 200 and a JSON object:
}
```
(This is implemented in Go)
#### Request body
Has to be a JSON object with an `appId` property, corresponding to the ID of the application the device is running.

View File

@ -2,16 +2,9 @@
set -o errexit
[ -d /dev/net ] ||
mkdir -p /dev/net
[ -c /dev/net/tun ] ||
mknod /dev/net/tun c 10 200
[ -d /mnt/root/tmp/resin-supervisor ] ||
mkdir -p /mnt/root/tmp/resin-supervisor
mkdir -p /var/run/resin
mount -t tmpfs -o size=1m tmpfs /var/run/resin
# If DOCKER_ROOT isn't set then default it
if [ -z "${DOCKER_ROOT}" ]; then
DOCKER_ROOT=/mnt/root/var/lib/rce
@ -23,3 +16,11 @@ DOCKER_LIB_PATH=${DOCKER_ROOT#/mnt/root}
if [ ! -d "${DOCKER_LIB_PATH}" ]; then
ln -s "${DOCKER_ROOT}" "${DOCKER_LIB_PATH}"
fi
if [ -z "$DOCKER_SOCKET" ]; then
export DOCKER_SOCKET=/run/docker.sock
fi
export DBUS_SYSTEM_BUS_ADDRESS="unix:path=/mnt/root/run/dbus/system_bus_socket"
exec node /usr/src/app/dist/app.js

View File

@ -1,7 +0,0 @@
# busybox inittab
# format: tty:ignored:action:command
stdout::sysinit:/usr/src/app/entry.sh
stdout::respawn:/usr/src/app/run.sh node /usr/src/app/dist/app.js
stdout::respawn:/usr/src/app/run.sh /usr/src/app/gosuper

View File

@ -29,6 +29,7 @@
"coffee-loader": "^0.7.3",
"coffee-script": "~1.11.0",
"copy-webpack-plugin": "^4.2.3",
"dbus-native": "^0.2.5",
"docker-delta": "^2.0.4",
"docker-progress": "^2.7.2",
"docker-toolbelt": "^3.2.1",

22
run.sh
View File

@ -1,22 +0,0 @@
#!/bin/sh
if [ -z "$GOSUPER_SOCKET" ]; then
export GOSUPER_SOCKET=/var/run/resin/gosuper.sock
fi
if [ -z "$DOCKER_SOCKET" ]; then
export DOCKER_SOCKET=/run/docker.sock
fi
if [ -z "$HOST_PROC" ]; then
export HOST_PROC=/mnt/root/proc
fi
export DBUS_SYSTEM_BUS_ADDRESS="/mnt/root/run/dbus/system_bus_socket"
# If DOCKER_ROOT isn't set then default it
if [ -z "${DOCKER_ROOT}" ]; then
DOCKER_ROOT=/mnt/root/var/lib/rce
fi
exec $@

View File

@ -4,9 +4,10 @@ childProcess = Promise.promisifyAll(require('child_process'))
fs = Promise.promisifyAll(require('fs'))
constants = require './lib/constants'
gosuper = require './lib/gosuper'
systemd = require './lib/systemd'
fsUtils = require './lib/fs-utils'
{ checkTruthy, checkInt } = require './lib/validation'
{ UnitNotLoadedError } = require './lib/errors'
hostConfigConfigVarPrefix = 'RESIN_HOST_'
bootConfigEnvVarPrefix = hostConfigConfigVarPrefix + 'CONFIG_'
@ -33,10 +34,12 @@ forbiddenConfigKeys = [
]
arrayConfigKeys = [ 'dtparam', 'dtoverlay', 'device_tree_param', 'device_tree_overlay' ]
logToDisplayServiceName = 'resin-info@tty1'
vpnServiceName = 'openvpn-resin'
module.exports = class DeviceConfig
constructor: ({ @db, @config, @logger }) ->
@rebootRequired = false
@gosuperHealthy = true
@configKeys = {
appUpdatePollInterval: { envVarName: 'RESIN_SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' }
localMode: { envVarName: 'RESIN_SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' }
@ -253,27 +256,33 @@ module.exports = class DeviceConfig
return @bootConfigToEnv(conf)
getLogToDisplay: ->
gosuper.get('/v1/log-to-display', { json: true })
.spread (res, body) ->
if res.statusCode == 404
return
if res.statusCode != 200
throw new Error("Error getting log to display status: #{res.statusCode} #{body.Error}")
return Boolean(body.Data)
systemd.serviceActiveState(logToDisplayServiceName)
.then (activeState) ->
return activeState not in [ 'inactive', 'deactivating' ]
.catchReturn(UnitNotLoadedError, null)
setLogToDisplay: (val) =>
Promise.try =>
enable = checkTruthy(val)
if !enable?
throw new Error("Invalid value in call to setLogToDisplay: #{val}")
gosuper.post('/v1/log-to-display', { json: true, body: Enable: enable })
.spread (response, body) =>
if response.statusCode != 200
throw new Error("#{response.statusCode} #{body.Error}")
@getLogToDisplay()
.then (currentState) ->
if !currentState? or currentState == enable
return false
if enable
systemd.startService(logToDisplayServiceName)
.then ->
systemd.enableService(logToDisplayServiceName)
.return(true)
else
if body.Data == true
@rebootRequired = true
return body.Data
systemd.stopService(logToDisplayServiceName)
.then ->
systemd.disableService(logToDisplayServiceName)
.return(true)
.tap (changed) =>
if changed
@rebootRequired = true
setBootConfig: (deviceType, target) =>
Promise.try =>
@ -302,18 +311,14 @@ module.exports = class DeviceConfig
@logger.logSystemMessage("Error setting boot config: #{err}", { error: err }, 'Apply boot config error')
getVPNEnabled: ->
gosuper.get('/v1/vpncontrol', { json: true })
.spread (res, body) ->
if res.statusCode != 200
throw new Error("Error getting vpn status: #{res.statusCode} #{body.Error}")
@gosuperHealthy = true
return Boolean(body.Data)
.tapCatch (err) =>
@gosuperHealthy = false
systemd.serviceActiveState(vpnServiceName)
.then (activeState) ->
return activeState not in [ 'inactive', 'deactivating' ]
.catchReturn(UnitNotLoadedError, null)
setVPNEnabled: (val) ->
enable = checkTruthy(val) ? true
gosuper.post('/v1/vpncontrol', { json: true, body: Enable: enable })
.spread (response, body) ->
if response.statusCode != 202
throw new Error("#{response.statusCode} #{body?.Error}")
if enable
systemd.startService(vpnServiceName)
else
systemd.stopService(vpnServiceName)

View File

@ -10,7 +10,7 @@ network = require './network'
constants = require './lib/constants'
validation = require './lib/validation'
device = require './lib/device'
systemd = require './lib/systemd'
updateLock = require './lib/update-lock'
{ singleToMulticontainerApp } = require './lib/migration'
@ -139,7 +139,7 @@ module.exports = class DeviceState extends EventEmitter
cycleTimeMs = cycleTime[0] * 1000 + cycleTime[1] / 1e6
cycleTimeWithinInterval = cycleTimeMs - @applications.timeSpentFetching < 2 * conf.appUpdatePollInterval
applyTargetHealthy = conf.offlineMode or !@applyInProgress or @applications.fetchesInProgress > 0 or cycleTimeWithinInterval
return applyTargetHealthy and @deviceConfig.gosuperHealthy
return applyTargetHealthy
normaliseLegacy: =>
# When legacy apps are present, we kill their containers and migrate their /data to a named volume
@ -345,7 +345,7 @@ module.exports = class DeviceState extends EventEmitter
@applications.stopAll({ force, skipLock })
.then =>
@logger.logSystemMessage('Rebooting', {}, 'Reboot')
device.reboot()
systemd.reboot()
.tap =>
@shuttingDown = true
@emitAsync('shutdown')
@ -354,7 +354,7 @@ module.exports = class DeviceState extends EventEmitter
@applications.stopAll({ force, skipLock })
.then =>
@logger.logSystemMessage('Shutting down', {}, 'Shutdown')
device.shutdown()
systemd.shutdown()
.tap =>
@shuttingDown = true
@emitAsync('shutdown')

View File

@ -1,6 +1,6 @@
Promise = require 'bluebird'
_ = require 'lodash'
gosuper = require './lib/gosuper'
systemd = require './lib/systemd'
path = require 'path'
constants = require './lib/constants'
fs = Promise.promisifyAll(require('fs'))
@ -28,18 +28,10 @@ redsocksFooter = '}\n'
proxyFields = [ 'type', 'ip', 'port', 'login', 'password' ]
proxyBasePath = path.join('/mnt/root', constants.bootMountPoint, 'system-proxy')
proxyBasePath = path.join(constants.rootMountPoint, constants.bootMountPoint, 'system-proxy')
redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf')
noProxyPath = path.join(proxyBasePath, 'no_proxy')
restartSystemdService = (serviceName) ->
gosuper.post('/v1/restart-service', { json: true, body: Name: serviceName })
.spread (response, body) ->
if response.statusCode != 200
err = new Error("Error restarting service #{serviceName}: #{response.statusCode} #{body}")
err.statusCode = response.statusCode
throw err
readProxy = ->
fs.readFileAsync(redsocksConfPath)
.then (redsocksConf) ->
@ -97,11 +89,11 @@ setProxy = (conf) ->
redsocksConf += redsocksFooter
writeFileAtomic(redsocksConfPath, redsocksConf)
.then ->
restartSystemdService('resin-proxy-config')
systemd.restartService('resin-proxy-config')
.then ->
restartSystemdService('redsocks')
systemd.restartService('redsocks')
hostnamePath = '/mnt/root/etc/hostname'
hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname')
readHostname = ->
fs.readFileAsync(hostnamePath)
.then (hostnameData) ->
@ -110,7 +102,7 @@ readHostname = ->
setHostname = (val, configModel) ->
configModel.set(hostname: val)
.then ->
restartSystemdService('resin-hostname')
systemd.restartService('resin-hostname')
exports.get = ->

View File

@ -8,7 +8,6 @@ supervisorNetworkInterface = 'supervisor0'
module.exports =
rootMountPoint: rootMountPoint
databasePath: checkString(process.env.DATABASE_PATH) ? '/data/database.sqlite'
gosuperAddress: "http://unix:#{process.env.GOSUPER_SOCKET}:"
dockerSocket: process.env.DOCKER_SOCKET ? '/var/run/docker.sock'
supervisorImage: checkString(process.env.SUPERVISOR_IMAGE) ? 'resin/rpi-supervisor'
ledFile: checkString(process.env.LED_FILE) ? '/sys/class/leds/led0/brightness'

View File

@ -1,14 +0,0 @@
gosuper = require './gosuper'
gosuperAction = (action) ->
gosuper.post("/v1/#{action}", { json: true })
.spread (res, body) ->
if res.statusCode != 202
throw new Error(body.Error)
return body
exports.reboot = ->
gosuperAction('reboot')
exports.shutdown = ->
gosuperAction('shutdown')

View File

@ -3,3 +3,4 @@
exports.NotFoundError = (err) -> checkInt(err.statusCode) is 404
exports.ENOENT = (err) -> err.code is 'ENOENT'
exports.EEXIST = (err) -> err.code is 'EEXIST'
exports.UnitNotLoadedError = (err) -> err[0]?.endsWith?('not loaded.')

View File

@ -1,20 +0,0 @@
Promise = require 'bluebird'
_ = require 'lodash'
{ request } = require './request'
constants = require './constants'
emptyHostRequest = request.defaults({ headers: Host: '' })
gosuperRequest = (method, endpoint, options = {}, callback) ->
if _.isFunction(options)
callback = options
options = {}
options.method = method
options.url = constants.gosuperAddress + endpoint
emptyHostRequest(options, callback)
gosuperPost = _.partial(gosuperRequest, 'POST')
gosuperGet = _.partial(gosuperRequest, 'GET')
module.exports =
post: Promise.promisify(gosuperPost, multiArgs: true)
get: Promise.promisify(gosuperGet, multiArgs: true)

55
src/lib/systemd.coffee Normal file
View File

@ -0,0 +1,55 @@
dbus = require 'dbus-native'
Promise = require 'bluebird'
bus = Promise.promisifyAll(dbus.systemBus())
systemdManagerMethodCall = (method, signature = '', body = []) ->
bus.invokeAsync({
path: '/org/freedesktop/systemd1'
destination: 'org.freedesktop.systemd1'
interface: 'org.freedesktop.systemd1.Manager'
member: method
signature
body
})
exports.restartService = (serviceName) ->
systemdManagerMethodCall('RestartUnit', 'ss', [ "#{serviceName}.service", 'fail' ])
exports.startService = (serviceName) ->
systemdManagerMethodCall('StartUnit', 'ss', [ "#{serviceName}.service", 'fail' ])
exports.stopService = (serviceName) ->
systemdManagerMethodCall('StopUnit', 'ss', [ "#{serviceName}.service", 'fail' ])
exports.enableService = (serviceName) ->
systemdManagerMethodCall('EnableUnitFiles', 'asbb', [ [ "#{serviceName}.service" ], false, false ])
exports.disableService = (serviceName) ->
systemdManagerMethodCall('DisableUnitFiles', 'asb', [ [ "#{serviceName}.service" ], false ])
exports.reboot = Promise.method ->
setTimeout( ->
systemdManagerMethodCall('Reboot')
, 1000)
exports.shutdown = Promise.method ->
setTimeout( ->
systemdManagerMethodCall('PowerOff')
, 1000)
getUnitProperty = (unitName, property) ->
systemdManagerMethodCall('GetUnit', 's', [ unitName ])
.then (unitPath) ->
bus.invokeAsync({
path: unitPath
destination: 'org.freedesktop.systemd1'
interface: 'org.freedesktop.DBus.Properties'
member: 'Get'
signature: 'ss'
body: [ 'org.freedesktop.systemd1.Unit', property ]
})
.get(1).get(0)
exports.serviceActiveState = (serviceName) ->
getUnitProperty("#{serviceName}.service", 'ActiveState')