Created
July 1, 2013 18:42
-
-
Save miklschmidt/5903434 to your computer and use it in GitHub Desktop.
Scripts for making autoupdates in node-webkit for Linux and Windows. This is a very early work in progress, more like a proof of concept. TODO: Add OS X support.
Check for and notify about filelocks.
Use a real db.
Abstract out update logic into strategies (node-webkit, assets, updater).
Use semver for version handling.
Changelogs in Markdown.
P…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fs = require('fs') | |
http = require('http') | |
https = require('https') | |
express = require('express') | |
path = require 'path' | |
_ = require('underscore') | |
sessionToken = "secretString" | |
secretUploadPass = "Change me" | |
secretPass = "Change me too" | |
port = 3000 | |
mode = 'production' | |
unless fs.existsSync("./db.json") | |
content = "[]" | |
fs.writeFileSync "./db.json", content | |
versionInfo = JSON.parse fs.readFileSync("./db.json").toString() | |
if process.argv[2]? and process.argv[2] is '--dev' | |
mode = 'development' | |
options = {} | |
if mode is 'production' | |
options.key = fs.readFileSync('/etc/ssl/localcerts/node.key').toString() | |
options.cert = fs.readFileSync('/etc/ssl/localcerts/node.pem').toString() | |
server = express(options) | |
store = new express.session.MemoryStore; | |
server.use express.cookieParser() | |
server.use express.session {secret: sessionToken, store: store, httpOnly: no} | |
server.use require('connect').bodyParser() | |
allowedDownloads = [] | |
server.get '/download', (req, res) -> | |
sid = req.sessionID | |
if not req.query.v? or not req.session? or not req.session.download? | |
res.writeHead(404) | |
res.end('Not found') | |
return | |
download = req.session.download | |
version = req.query.v | |
unless req.session.download.version is version | |
res.writeHead(403) | |
res.end('Access denied') | |
return | |
else | |
req.session.download = null | |
res.sendfile(download.path) | |
server.post '/upload', (req, res) -> | |
unless req.body? and req.body.secret? and req.body.secret is secretUploadPass | |
if req.files.win32? and req.files.win32.path? and fs.existsSync(req.files.win32.path) | |
fs.unlinkSync(req.files.win32.path) | |
if req.files.linux64? and req.files.linux64.path? and fs.existsSync(req.files.linux64.path) | |
fs.unlinkSync(req.files.linux64.path) | |
if req.files.linux32? and req.files.linux32.path? and fs.existsSync(req.files.linux32.path) | |
fs.unlinkSync(req.files.linux32.path) | |
return res.json 403, {result: 'error', msg: "Access denied"} | |
versionNumber = req.body.version | |
linux32 = req.files.linux32 | |
linux64 = req.files.linux64 | |
win32 = req.files.win32 | |
unless versionNumber | |
if req.files.win32? and req.files.win32.path? and fs.existsSync(req.files.win32.path) | |
fs.unlinkSync(req.files.win32.path) | |
if req.files.linux64? and req.files.linux64.path? and fs.existsSync(req.files.linux64.path) | |
fs.unlinkSync(req.files.linux64.path) | |
if req.files.linux32? and req.files.linux32.path? and fs.existsSync(req.files.linux32.path) | |
fs.unlinkSync(req.files.linux32.path) | |
return res.json 403, {result: 'error', msg: "Missing version number"} | |
versionExists = false | |
for info in versionInfo when info.version is versionNumber | |
versionExists = true | |
break | |
if versionExists | |
if req.files.win32? and req.files.win32.path? and fs.existsSync(req.files.win32.path) | |
fs.unlinkSync(req.files.win32.path) | |
if req.files.linux64? and req.files.linux64.path? and fs.existsSync(req.files.linux64.path) | |
fs.unlinkSync(req.files.linux64.path) | |
if req.files.linux32? and req.files.linux32.path? and fs.existsSync(req.files.linux32.path) | |
fs.unlinkSync(req.files.linux32.path) | |
return res.json 403, {result: 'error', msg: "The version you're trying to publish allready exists"} | |
newVersion = | |
version: versionNumber | |
files: {} | |
linux32result = linux64result = win32result = false | |
if linux32? | |
data = fs.readFileSync linux32.path | |
linux32result = fs.writeFileSync (path.join __dirname, 'dist', linux32.name), data | |
newVersion.files["linux-ia32"] = linux32.name | |
if linux64? | |
data = fs.readFileSync linux64.path | |
linux64result = fs.writeFileSync (path.join __dirname, 'dist', linux64.name), data | |
newVersion.files["linux-x64"] = linux64.name | |
if win32? | |
data = fs.readFileSync win32.path | |
win32result = fs.writeFileSync (path.join __dirname, 'dist', win32.name), data | |
newVersion.files["win-ia32"] = win32.name | |
if linux32 or linux64 or win32 | |
versionInfo.push newVersion | |
result = fs.writeFileSync "./db.json", JSON.stringify(versionInfo) | |
return res.json result: 'ok' | |
else | |
res.json 412, result: 'error', msg: 'missing file' | |
server.post '/check', (req, res) -> | |
unless req.body? | |
res.writeHead(403) | |
res.end "Access denied" | |
return | |
secret = req.body.secret | |
if secret isnt secretPass | |
res.writeHead(403) | |
res.end "Access denied" | |
return | |
clientVersion = req.query.v if req.query.v? | |
clientVersion = req.query.version unless clientVersion or not req.query.version? | |
clientPlatform = req.query.p if req.query.p? | |
clientPlatform = req.query.platform unless clientPlatform or not req.query.platform? | |
res.writeHead(412) unless clientVersion and clientPlatform | |
return res.end "No version/platform parameters to check against" unless clientVersion and clientPlatform | |
sid = req.sessionID | |
result = {} | |
latestVersionFound = false | |
currentVersionFound = false | |
#console.log "Looking for newer version than", clientVersion, "for platform", clientPlatform | |
for v, i in versionInfo | |
if v.files[clientPlatform]? | |
latestVersionFound = true | |
latestVersion = v | |
latestVersionIndex = i | |
for v, i in versionInfo | |
if v.version is clientVersion and v.files[clientPlatform]? | |
currentVersionFound = true | |
currentVersion = v | |
currentVersionIndex = i | |
break | |
#console.log currentVersionIndex, latestVersionIndex | |
if currentVersionFound is false or latestVersionFound is false | |
# console.log "Couldn't find current version:", currentVersion unless currentVersionFound | |
# console.log "Couldn't find latest version:", latestVersion unless latestVersionFound | |
result.result = "unknown version" | |
else if currentVersionIndex is latestVersionIndex | |
# console.log "Client is allready on latest version:", currentVersionIndex, latestVersionIndex, latestVersion.version | |
result.result = "noupdates" | |
else | |
result.result = "ok" | |
url = "/download?v=#{latestVersion.version}" | |
allowedDownload = | |
url: url | |
version: latestVersion.version | |
path: path.join(__dirname, 'dist', latestVersion.files[clientPlatform]) | |
req.session.download = allowedDownload | |
result.update = | |
path: url | |
host: "yourhost.name" | |
port: 3000 | |
protocol: if mode is 'production' then "https" else "http" | |
version: latestVersion.version | |
filename: latestVersion.files[clientPlatform] | |
changelog: 'Changelogs will be here soon' | |
res.json result | |
if mode is 'production' | |
https.createServer(options,server).listen port, () -> | |
console.log("Timetracker Update https server listening on port " + port) | |
else | |
server.listen port, () -> | |
console.log "Timetracker Update server listening on port " + port |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
admzip = require 'adm-zip' | |
http = require 'http' | |
https = require 'https' | |
fs = require 'fs' | |
path = require 'path' | |
request = require 'superagent' | |
ProgressBar = require('progress') | |
execFile = require('child_process').execFile | |
secretString = "secretstring configured in the server" | |
serverAddress = "your.update.server:3000" | |
applicationExecutable = "timetracker" | |
if process.platform.match(/^win/) | |
ds = "\\" | |
else | |
ds = "/" | |
pathfrags = process.execPath.split(ds) | |
__APPDIR = '' | |
len = pathfrags.length | |
# Get rid of filename | |
for frag, index in pathfrags when index < len-1 | |
__APPDIR += frag + ds | |
__PARENTDIR = path.join(__APPDIR, "..#{ds}") | |
if process.platform.match(/^win/) | |
platform = 'win-ia32' | |
else if process.platform.match(/^darwin/) | |
platform = 'osx-ia32' | |
else if process.platform.match(/^linux/) | |
platform = 'linux-' + process.arch | |
infoFile = path.join(__PARENTDIR, "VERSION") | |
version = fs.readFileSync(infoFile).toString() | |
console.log "You are currently on version:", version | |
downloadUpdate = (download, callback) -> | |
fs.open download.filename, 'w', (err, destination) -> | |
if (err) | |
console.log "could not open " + download.filename + " for downloading " + download.filename + ": " + err.message | |
callback null, err | |
return | |
req = https.request | |
hostname: download.host | |
port: download.port | |
path: download.path | |
headers: | |
Cookie: download.cookie | |
method: 'GET' | |
req.on 'error', (err) -> | |
fs.closeSync destination | |
console.error "failed to get " + download.filename + " from " + download.host + ": " + err.message | |
callback null, err | |
req.on 'response', (res) -> | |
len = parseInt(res.headers['content-length'], 10) | |
totalDownloaded = 0 | |
$('#description').text("Downloading " + download.filename + " 0%") | |
res.on 'data', (chunk) -> | |
if chunk.length | |
totalDownloaded += chunk.length | |
$('#description').text("Downloading " + download.filename + " " + Math.round((totalDownloaded/len)*100) + "%") | |
fs.writeSync(destination, chunk, 0, chunk.length, null) | |
res.on 'end', () -> | |
console.log() | |
fs.closeSync(destination) | |
callback(download.filename) | |
req.end() | |
checkUpdate = (callback) -> | |
url = "https://#{serverAddress}/check?v=#{version}&p=#{platform}" | |
request.post(url) | |
.type('json') | |
.send({secret: secretString}) | |
.end (err, res) => | |
if err? | |
console.log err | |
return callback null, err | |
if res.body.result? and res.body.result is 'ok' | |
toDownload = res.body.update | |
toDownload.cookie = res.header['set-cookie'] | |
console.log "New version available! (v" + toDownload.version + ")" | |
console.log() | |
callback toDownload | |
else | |
callback false | |
console.log res | |
restartApp = () -> | |
if platform is 'win-ia32' | |
gui.Shell.openItem __PARENTDIR + applicationExecutable '.exe' | |
else | |
# Linux only so far | |
fs.chmodSync __PARENTDIR + applicationExecutable, '775' | |
execFile __PARENTDIR + applicationExecutable, [], {cwd: __PARENTDIR} | |
run = () -> | |
checkUpdate (download, err) -> | |
if err or not download | |
msg = 'No updates available.. Exiting..' | |
console.log msg | |
$('#description').text(msg) | |
console.log err if err | |
gui.Window.get().close(true) | |
return | |
$('#version').text(download.version) | |
downloadUpdate download, (filepath, err) -> | |
return console.log "Couldn't download update: ", err if err | |
msg = "Finished downloading files for version " + download.version | |
$('#description').text(msg) | |
zip = new admzip(filepath) | |
$('#description').text('Extracting...') | |
zip.extractAllTo __PARENTDIR, true | |
$('#description').text('All done, restarting timetracker') | |
restartApp() | |
# Exit the updater | |
gui.Window.get().close(true) | |
module.exports = run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment