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 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 # The node version here should match the version of the runtime image which is
# specified in the base-image subdirectory in the project # 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 \ && find . -type f -path '*/node_modules/knex/build*' -delete \
&& rm -rf node_modules/sqlite3/node.dtps && rm -rf node_modules/sqlite3/node.dtps
# Create /var/run/resin for the gosuper to place its socket in COPY entry.sh package.json rootfs-overlay/usr/src/app/
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
RUN rsync -a --delete node_modules rootfs-overlay /build 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-build /usr/src/app/dist ./dist
COPY --from=node-deps /build/node_modules ./node_modules COPY --from=node-deps /build/node_modules ./node_modules
COPY --from=gosuper /build/gosuper ./gosuper
COPY --from=node-deps /build/rootfs-overlay/ / COPY --from=node-deps /build/rootfs-overlay/ /
VOLUME /data VOLUME /data
@ -186,3 +121,5 @@ ENV CONFIG_MOUNT_POINT=/boot/config.json \
HEALTHCHECK --interval=5m --start-period=1m --timeout=30s --retries=3 \ 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 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): # Build targets (require Docker 17.05 or greater):
# * supervisor (default) - builds a resin-supervisor image # * supervisor (default) - builds a resin-supervisor image
# * deploy - pushes a resin-supervisor image to the registry, retrying up to 3 times # * 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) # * 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) # * 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: # 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) # * 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: # Variables for test/dev targets:
# * IMAGE: image to build and run (either for run-supervisor or test-gosuper/integration) # * IMAGE: image to build and run (either for run-supervisor or test-gosuper/integration)
@ -193,33 +190,4 @@ nodedeps:
nodebuild: nodebuild:
$(MAKE) -f $(THIS_FILE) TARGET_COMPONENT=node-build IMAGE=$(IMAGE) ARCH=$(ARCH) supervisor-image $(MAKE) -f $(THIS_FILE) TARGET_COMPONENT=node-build IMAGE=$(IMAGE) ARCH=$(ARCH) supervisor-image
gosuper: .PHONY: supervisor deploy nodedeps nodebuild supervisor-dind run-supervisor
$(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

View File

@ -18,7 +18,6 @@
# #
# It pulls intermediate images for caching, if available: # It pulls intermediate images for caching, if available:
# resin/$ARCH-supervisor-node:$TAG # resin/$ARCH-supervisor-node:$TAG
# resin/$ARCH-supervisor-go:$TAG
# #
# In all of these cases it will use "master" if $TAG is not found. # 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 # Intermediate images and cache
NODE_IMAGE=resin/$ARCH-supervisor-node:$TAG NODE_IMAGE=resin/$ARCH-supervisor-node:$TAG
NODE_BUILD_IMAGE=resin/$ARCH-supervisor-node:$TAG-build NODE_BUILD_IMAGE=resin/$ARCH-supervisor-node:$TAG-build
GO_IMAGE=resin/$ARCH-supervisor-go:$TAG
TARGET_CACHE=$TARGET_IMAGE TARGET_CACHE=$TARGET_IMAGE
GO_CACHE=$GO_IMAGE
NODE_CACHE=$NODE_IMAGE NODE_CACHE=$NODE_IMAGE
NODE_BUILD_CACHE=$NODE_BUILD_IMAGE NODE_BUILD_CACHE=$NODE_BUILD_IMAGE
TARGET_CACHE_MASTER=resin/$ARCH-supervisor:master TARGET_CACHE_MASTER=resin/$ARCH-supervisor:master
GO_CACHE_MASTER=resin/$ARCH-supervisor-go:master
NODE_CACHE_MASTER=resin/$ARCH-supervisor-node:master NODE_CACHE_MASTER=resin/$ARCH-supervisor-node:master
NODE_BUILD_CACHE_MASTER=resin/$ARCH-supervisor-node:master-build 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 # Only if the pull succeeds we add a --cache-from option
tryPullForCache $TARGET_CACHE tryPullForCache $TARGET_CACHE
tryPullForCache $TARGET_CACHE_MASTER tryPullForCache $TARGET_CACHE_MASTER
tryPullForCache $GO_CACHE
tryPullForCache $GO_CACHE_MASTER
tryPullForCache $NODE_CACHE tryPullForCache $NODE_CACHE
tryPullForCache $NODE_CACHE_MASTER tryPullForCache $NODE_CACHE_MASTER
tryPullForCache $NODE_BUILD_CACHE tryPullForCache $NODE_BUILD_CACHE
tryPullForCache $NODE_BUILD_CACHE_MASTER 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 DOCKER_BUILD_OPTIONS=${CACHE_FROM}
export ARCH export ARCH
export PUBNUB_PUBLISH_KEY export PUBNUB_PUBLISH_KEY
export PUBNUB_SUBSCRIBE_KEY export PUBNUB_SUBSCRIBE_KEY
export MIXPANEL_TOKEN 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 make IMAGE=$NODE_BUILD_IMAGE nodebuild
if [ "$PUSH_IMAGES" = "true" ]; then if [ "$PUSH_IMAGES" = "true" ]; then
make IMAGE=$NODE_BUILD_IMAGE deploy || true make IMAGE=$NODE_BUILD_IMAGE deploy || true
@ -123,12 +107,10 @@ fi
if [ "$CLEANUP" = "true" ]; then if [ "$CLEANUP" = "true" ]; then
tryRemove $TARGET_IMAGE tryRemove $TARGET_IMAGE
tryRemove $GO_IMAGE
tryRemove $NODE_IMAGE tryRemove $NODE_IMAGE
tryRemove $NODE_BUILD_IMAGE tryRemove $NODE_BUILD_IMAGE
tryRemove $TARGET_CACHE tryRemove $TARGET_CACHE
tryRemove $GO_CACHE
tryRemove $NODE_BUILD_CACHE tryRemove $NODE_BUILD_CACHE
tryRemove $NODE_CACHE tryRemove $NODE_CACHE
fi 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`. 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. 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": "" "Error": ""
} }
``` ```
(This is implemented in Go)
#### Request body #### Request body
Can contain a `force` property, which if set to `true` will cause the update lock to be overridden. 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": "" "Error": ""
} }
``` ```
(This is implemented in Go)
#### Request body #### Request body
Can contain a `force` property, which if set to `true` will cause the update lock to be overridden. 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 #### Request body
Has to be a JSON object with an `appId` property, corresponding to the ID of the application the device is running. 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 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 ] || [ -d /mnt/root/tmp/resin-supervisor ] ||
mkdir -p /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 DOCKER_ROOT isn't set then default it
if [ -z "${DOCKER_ROOT}" ]; then if [ -z "${DOCKER_ROOT}" ]; then
DOCKER_ROOT=/mnt/root/var/lib/rce DOCKER_ROOT=/mnt/root/var/lib/rce
@ -23,3 +16,11 @@ DOCKER_LIB_PATH=${DOCKER_ROOT#/mnt/root}
if [ ! -d "${DOCKER_LIB_PATH}" ]; then if [ ! -d "${DOCKER_LIB_PATH}" ]; then
ln -s "${DOCKER_ROOT}" "${DOCKER_LIB_PATH}" ln -s "${DOCKER_ROOT}" "${DOCKER_LIB_PATH}"
fi 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-loader": "^0.7.3",
"coffee-script": "~1.11.0", "coffee-script": "~1.11.0",
"copy-webpack-plugin": "^4.2.3", "copy-webpack-plugin": "^4.2.3",
"dbus-native": "^0.2.5",
"docker-delta": "^2.0.4", "docker-delta": "^2.0.4",
"docker-progress": "^2.7.2", "docker-progress": "^2.7.2",
"docker-toolbelt": "^3.2.1", "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')) fs = Promise.promisifyAll(require('fs'))
constants = require './lib/constants' constants = require './lib/constants'
gosuper = require './lib/gosuper' systemd = require './lib/systemd'
fsUtils = require './lib/fs-utils' fsUtils = require './lib/fs-utils'
{ checkTruthy, checkInt } = require './lib/validation' { checkTruthy, checkInt } = require './lib/validation'
{ UnitNotLoadedError } = require './lib/errors'
hostConfigConfigVarPrefix = 'RESIN_HOST_' hostConfigConfigVarPrefix = 'RESIN_HOST_'
bootConfigEnvVarPrefix = hostConfigConfigVarPrefix + 'CONFIG_' bootConfigEnvVarPrefix = hostConfigConfigVarPrefix + 'CONFIG_'
@ -33,10 +34,12 @@ forbiddenConfigKeys = [
] ]
arrayConfigKeys = [ 'dtparam', 'dtoverlay', 'device_tree_param', 'device_tree_overlay' ] arrayConfigKeys = [ 'dtparam', 'dtoverlay', 'device_tree_param', 'device_tree_overlay' ]
logToDisplayServiceName = 'resin-info@tty1'
vpnServiceName = 'openvpn-resin'
module.exports = class DeviceConfig module.exports = class DeviceConfig
constructor: ({ @db, @config, @logger }) -> constructor: ({ @db, @config, @logger }) ->
@rebootRequired = false @rebootRequired = false
@gosuperHealthy = true
@configKeys = { @configKeys = {
appUpdatePollInterval: { envVarName: 'RESIN_SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' } appUpdatePollInterval: { envVarName: 'RESIN_SUPERVISOR_POLL_INTERVAL', varType: 'int', defaultValue: '60000' }
localMode: { envVarName: 'RESIN_SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' } localMode: { envVarName: 'RESIN_SUPERVISOR_LOCAL_MODE', varType: 'bool', defaultValue: 'false' }
@ -253,27 +256,33 @@ module.exports = class DeviceConfig
return @bootConfigToEnv(conf) return @bootConfigToEnv(conf)
getLogToDisplay: -> getLogToDisplay: ->
gosuper.get('/v1/log-to-display', { json: true }) systemd.serviceActiveState(logToDisplayServiceName)
.spread (res, body) -> .then (activeState) ->
if res.statusCode == 404 return activeState not in [ 'inactive', 'deactivating' ]
return .catchReturn(UnitNotLoadedError, null)
if res.statusCode != 200
throw new Error("Error getting log to display status: #{res.statusCode} #{body.Error}")
return Boolean(body.Data)
setLogToDisplay: (val) => setLogToDisplay: (val) =>
Promise.try => Promise.try =>
enable = checkTruthy(val) enable = checkTruthy(val)
if !enable? if !enable?
throw new Error("Invalid value in call to setLogToDisplay: #{val}") throw new Error("Invalid value in call to setLogToDisplay: #{val}")
gosuper.post('/v1/log-to-display', { json: true, body: Enable: enable }) @getLogToDisplay()
.spread (response, body) => .then (currentState) ->
if response.statusCode != 200 if !currentState? or currentState == enable
throw new Error("#{response.statusCode} #{body.Error}") return false
if enable
systemd.startService(logToDisplayServiceName)
.then ->
systemd.enableService(logToDisplayServiceName)
.return(true)
else else
if body.Data == true systemd.stopService(logToDisplayServiceName)
@rebootRequired = true .then ->
return body.Data systemd.disableService(logToDisplayServiceName)
.return(true)
.tap (changed) =>
if changed
@rebootRequired = true
setBootConfig: (deviceType, target) => setBootConfig: (deviceType, target) =>
Promise.try => Promise.try =>
@ -302,18 +311,14 @@ module.exports = class DeviceConfig
@logger.logSystemMessage("Error setting boot config: #{err}", { error: err }, 'Apply boot config error') @logger.logSystemMessage("Error setting boot config: #{err}", { error: err }, 'Apply boot config error')
getVPNEnabled: -> getVPNEnabled: ->
gosuper.get('/v1/vpncontrol', { json: true }) systemd.serviceActiveState(vpnServiceName)
.spread (res, body) -> .then (activeState) ->
if res.statusCode != 200 return activeState not in [ 'inactive', 'deactivating' ]
throw new Error("Error getting vpn status: #{res.statusCode} #{body.Error}") .catchReturn(UnitNotLoadedError, null)
@gosuperHealthy = true
return Boolean(body.Data)
.tapCatch (err) =>
@gosuperHealthy = false
setVPNEnabled: (val) -> setVPNEnabled: (val) ->
enable = checkTruthy(val) ? true enable = checkTruthy(val) ? true
gosuper.post('/v1/vpncontrol', { json: true, body: Enable: enable }) if enable
.spread (response, body) -> systemd.startService(vpnServiceName)
if response.statusCode != 202 else
throw new Error("#{response.statusCode} #{body?.Error}") systemd.stopService(vpnServiceName)

View File

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

View File

@ -1,6 +1,6 @@
Promise = require 'bluebird' Promise = require 'bluebird'
_ = require 'lodash' _ = require 'lodash'
gosuper = require './lib/gosuper' systemd = require './lib/systemd'
path = require 'path' path = require 'path'
constants = require './lib/constants' constants = require './lib/constants'
fs = Promise.promisifyAll(require('fs')) fs = Promise.promisifyAll(require('fs'))
@ -28,18 +28,10 @@ redsocksFooter = '}\n'
proxyFields = [ 'type', 'ip', 'port', 'login', 'password' ] 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') redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf')
noProxyPath = path.join(proxyBasePath, 'no_proxy') 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 = -> readProxy = ->
fs.readFileAsync(redsocksConfPath) fs.readFileAsync(redsocksConfPath)
.then (redsocksConf) -> .then (redsocksConf) ->
@ -97,11 +89,11 @@ setProxy = (conf) ->
redsocksConf += redsocksFooter redsocksConf += redsocksFooter
writeFileAtomic(redsocksConfPath, redsocksConf) writeFileAtomic(redsocksConfPath, redsocksConf)
.then -> .then ->
restartSystemdService('resin-proxy-config') systemd.restartService('resin-proxy-config')
.then -> .then ->
restartSystemdService('redsocks') systemd.restartService('redsocks')
hostnamePath = '/mnt/root/etc/hostname' hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname')
readHostname = -> readHostname = ->
fs.readFileAsync(hostnamePath) fs.readFileAsync(hostnamePath)
.then (hostnameData) -> .then (hostnameData) ->
@ -110,7 +102,7 @@ readHostname = ->
setHostname = (val, configModel) -> setHostname = (val, configModel) ->
configModel.set(hostname: val) configModel.set(hostname: val)
.then -> .then ->
restartSystemdService('resin-hostname') systemd.restartService('resin-hostname')
exports.get = -> exports.get = ->

View File

@ -8,7 +8,6 @@ supervisorNetworkInterface = 'supervisor0'
module.exports = module.exports =
rootMountPoint: rootMountPoint rootMountPoint: rootMountPoint
databasePath: checkString(process.env.DATABASE_PATH) ? '/data/database.sqlite' 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' dockerSocket: process.env.DOCKER_SOCKET ? '/var/run/docker.sock'
supervisorImage: checkString(process.env.SUPERVISOR_IMAGE) ? 'resin/rpi-supervisor' supervisorImage: checkString(process.env.SUPERVISOR_IMAGE) ? 'resin/rpi-supervisor'
ledFile: checkString(process.env.LED_FILE) ? '/sys/class/leds/led0/brightness' 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.NotFoundError = (err) -> checkInt(err.statusCode) is 404
exports.ENOENT = (err) -> err.code is 'ENOENT' exports.ENOENT = (err) -> err.code is 'ENOENT'
exports.EEXIST = (err) -> err.code is 'EEXIST' 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')