diff --git a/Dockerfile b/Dockerfile index 8ee6e2ae..ee867982 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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" ] diff --git a/Makefile b/Makefile index e079ce82..a7c0df4e 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/automation/build.sh b/automation/build.sh index d9711c45..ca72bd17 100755 --- a/automation/build.sh +++ b/automation/build.sh @@ -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 diff --git a/docs/API.md b/docs/API.md index 09cfb3df..ca7f9734 100644 --- a/docs/API.md +++ b/docs/API.md @@ -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. diff --git a/entry.sh b/entry.sh index 3cac07a5..722634f8 100755 --- a/entry.sh +++ b/entry.sh @@ -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 diff --git a/inittab b/inittab deleted file mode 100644 index 4b9a0a1d..00000000 --- a/inittab +++ /dev/null @@ -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 diff --git a/package.json b/package.json index 9b5fabee..918ca65a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/run.sh b/run.sh deleted file mode 100755 index 8aa55a0d..00000000 --- a/run.sh +++ /dev/null @@ -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 $@ diff --git a/src/device-config.coffee b/src/device-config.coffee index 5e5ae5e7..fc3ba304 100644 --- a/src/device-config.coffee +++ b/src/device-config.coffee @@ -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) diff --git a/src/device-state.coffee b/src/device-state.coffee index b3ec2800..4ca29635 100644 --- a/src/device-state.coffee +++ b/src/device-state.coffee @@ -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') diff --git a/src/host-config.coffee b/src/host-config.coffee index ec169a48..7c2a3f01 100644 --- a/src/host-config.coffee +++ b/src/host-config.coffee @@ -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 = -> diff --git a/src/lib/constants.coffee b/src/lib/constants.coffee index fcbe74a4..72afbd63 100644 --- a/src/lib/constants.coffee +++ b/src/lib/constants.coffee @@ -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' diff --git a/src/lib/device.coffee b/src/lib/device.coffee deleted file mode 100644 index 2a3eb2b7..00000000 --- a/src/lib/device.coffee +++ /dev/null @@ -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') diff --git a/src/lib/errors.coffee b/src/lib/errors.coffee index a7ae268a..bd174ef5 100644 --- a/src/lib/errors.coffee +++ b/src/lib/errors.coffee @@ -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.') diff --git a/src/lib/gosuper.coffee b/src/lib/gosuper.coffee deleted file mode 100644 index 198d151f..00000000 --- a/src/lib/gosuper.coffee +++ /dev/null @@ -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) diff --git a/src/lib/systemd.coffee b/src/lib/systemd.coffee new file mode 100644 index 00000000..ca455c3e --- /dev/null +++ b/src/lib/systemd.coffee @@ -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')