From 8b4d1e9f751335a3f694a158c5ec264c3b6fb5e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= <pablocarranza@gmail.com>
Date: Thu, 24 Sep 2015 20:09:06 +0000
Subject: [PATCH] Ensure special env vars are persistent and add placeholder
 for device.setBootConfig

---
 src/app.coffee         | 12 +----------
 src/application.coffee | 47 ++++++++++++++++++++++++++++++++----------
 src/device.coffee      | 20 ++++++++++++++++++
 3 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/src/app.coffee b/src/app.coffee
index dc00f02e..5bd8410e 100644
--- a/src/app.coffee
+++ b/src/app.coffee
@@ -42,17 +42,7 @@ knex.init.then ->
 			)
 
 		console.log('Starting Apps..')
-		knex('app').select()
-		.then (apps) ->
-			Promise.all(apps.map(application.unlockAndStart))
-		.catch (error) ->
-			console.error('Error starting apps:', error)
-		.then ->
-			utils.mixpanelTrack('Start application update poll', {interval: config.appUpdatePollInterval})
-			setInterval(->
-				application.update()
-			, config.appUpdatePollInterval)
-			application.update()
+		application.initialize()
 
 		updateIpAddr = ->
 			callback = (error, response, body ) ->
diff --git a/src/application.coffee b/src/application.coffee
index d6980aae..cfcd7e83 100644
--- a/src/application.coffee
+++ b/src/application.coffee
@@ -297,6 +297,19 @@ specialActionEnvVars =
 
 executedSpecialActionEnvVars = {}
 
+executeSpecialActionsAndBootConfig = (env) ->
+	_.map specialActionEnvVars, (specialActionCallback, key) ->
+		if env[key]? && specialActionCallback?
+			# This makes the Special Action Envs only trigger their functions once.
+			if !_.has(executedSpecialActionEnvVars, key) or executedSpecialActionEnvVars[key] != env[key]
+				specialActionCallback(env[key])
+				executedSpecialActionEnvVars[key] = env[key]
+	bootConfigVars = _.pick env, (val, key) ->
+		configRegex = new RegExp('^/' + device.bootConfigEnvVarPrefix + '*/')
+		return configRegex.exec(key)?
+	if !_.isEmpty(bootConfigVars)
+		device.setBootConfig(bootConfigVars)
+
 UPDATE_IDLE = 0
 UPDATE_UPDATING = 1
 UPDATE_REQUIRED = 2
@@ -368,20 +381,13 @@ application.update = update = (force) ->
 				remoteAppIds = _.keys(remoteApps)
 
 				# Run special functions against variables if remoteAppEnvs has the corresponding variable function mapping.
-				_.map specialActionEnvVars, (specialActionCallback, key) ->
-					_.map remoteAppIds, (appId) ->
-						if remoteAppEnvs[appId][key]? && specialActionCallback?
-							# This makes the Special Action Envs only trigger their functions once.
-							if _.has(executedSpecialActionEnvVars, key)
-								if executedSpecialActionEnvVars[key] != remoteAppEnvs[appId][key]
-									specialActionCallback(remoteAppEnvs[appId][key])
-							else
-								specialActionCallback(remoteAppEnvs[appId][key])
-						executedSpecialActionEnvVars[key] = remoteAppEnvs[appId][key]
+				_.map remoteAppIds, (appId) ->
+					executeSpecialActionsAndBootConfig(remoteAppEnvs[appId])
 
 				apps = _.indexBy(apps, 'appId')
 				localApps = _.mapValues apps, (app) ->
-					_.pick(app, [ 'appId', 'commit', 'imageId', 'env' ])
+					app = _.pick(app, [ 'appId', 'commit', 'imageId', 'env' ])
+					app.env = JSON.stringify(_.omit(JSON.parse(app.env), _.keys(specialActionEnvVars)))
 				localAppIds = _.keys(localApps)
 
 				toBeRemoved = _.difference(localAppIds, remoteAppIds)
@@ -417,10 +423,14 @@ application.update = update = (force) ->
 								throw err
 						else if _.includes(toBeInstalled, appId)
 							app = remoteApps[appId]
+							# Restore the complete environment so that it's persisted in the DB
+							app.env = JSON.stringify(remoteAppEnvs[appId])
 							start(app)
 						else if _.includes(toBeUpdated, appId)
 							localApp = apps[appId]
 							app = remoteApps[appId]
+							# Restore the complete environment so that it's persisted in the DB
+							app.env = JSON.stringify(remoteAppEnvs[appId])
 							logSystemEvent(logTypes.updateApp, app) if localApp.imageId == app.imageId
 							forceThisApp = remoteAppEnvs[appId]['RESIN_OVERRIDE_LOCK'] == '1'
 							Promise.using lockUpdates(localApp, force || forceThisApp), ->
@@ -461,6 +471,21 @@ application.update = update = (force) ->
 			# Set the updating as finished in its own block, so it never has to worry about other code stopping this.
 			updateStatus.state = UPDATE_IDLE
 
+application.initialize = ->
+	knex('app').select()
+	.then (apps) ->
+		Promise.map apps, (app) ->
+			executeSpecialActionsAndBootConfig(JSON.parse(app.env))
+			unlockAndStart(app)
+	.catch (error) ->
+		console.error('Error starting apps:', error)
+	.then ->
+		utils.mixpanelTrack('Start application update poll', {interval: config.appUpdatePollInterval})
+		setInterval(->
+			application.update()
+		, config.appUpdatePollInterval)
+		application.update()
+
 module.exports = (uuid) ->
 	logger.init(
 		dockerSocket: config.dockerSocket
diff --git a/src/device.coffee b/src/device.coffee
index b6ede22a..b7476a08 100644
--- a/src/device.coffee
+++ b/src/device.coffee
@@ -4,7 +4,9 @@ knex = require './db'
 utils = require './utils'
 { resinApi } = require './request'
 device = exports
+config = require './config'
 configPath = '/boot/config.json'
+request = require 'request'
 
 exports.getID = do ->
 	deviceIdPromise = null
@@ -32,6 +34,24 @@ exports.getID = do ->
 					throw new Error('Could not find this device?!')
 				return devices[0].id
 
+rebootDevice = ->
+	request.post(config.gosuperAddress + '/v1/reboot', -> console.log('Rebooting.'))
+
+exports.bootConfigEnvVarPrefix = 'RESIN_HOST_CONFIG_'
+bootConfigPath = '/mnt/root/boot/config.txt'
+exports.setBootConfig = (env) ->
+	configChanged = false
+	# TODO:
+	# Translate env to config.txt-compatible statements
+	# Read and parse config.txt
+	# Detect if variables are present
+	# If not there or different:
+	#	mark configChanged as true
+	# 	Replace those and create new ones as necessary
+	# 	Create config.txt.new
+	# 	Move config.txt.new to config.txt
+	#	rebootDevice()
+
 exports.getDeviceType = do ->
 	deviceTypePromise = null
 	return ->