├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .gitignore ├── deep-package.json ├── deep.js └── deep ├── file2 └── folder └── file1 /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 grunjol et al. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nobin-debian-installer 2 | 3 | Create .deb packages from linux, windows, OSX with no binary dependencies. 4 | its a grunt stripped version of [sebestindragos/grunt-contrib-deb](https://github.com/sebestindragos/grunt-contrib-deb) 5 | 6 | ## Getting started 7 | 8 | Installation: 9 | 10 | ```shell 11 | npm install nobin-debian-installer --save-dev 12 | ``` 13 | 14 | Once installed, it may be executed with: 15 | 16 | ### Usage 17 | 18 | ```js 19 | 20 | var deb = require('nobin-debian-installer')() 21 | 22 | var definition = { 23 | package: require('./package.json'), // needed for extracting project info 24 | info: { 25 | rev: '512', // optional revision number 26 | arch: 'amd64', // optional architecture type 27 | name: 'my-package', // optional package name 28 | depends: 'libc6 (>= 2.4)', // optional dependency list 29 | targetDir: './dist', // optional folder where to build the .deb package 30 | scripts: { 31 | preinst: './deb/scripts/preinst', // optional pre install script 32 | postinst: './deb/scripts/postinst', // optional post install script 33 | prerm: './deb/scripts/prerm', // optional pre remove script 34 | postrm: './deb/scripts/postrm', // optional post remove script 35 | } 36 | } 37 | } 38 | 39 | var files = [{ 40 | src: ['src/**', '!tests/**'], 41 | dest: '/srv/myproject', 42 | cwd: './server', 43 | expand: true 44 | }, { // add configuration files (init scripts, logrotate, systemd, etc...) 45 | src: ['**'], 46 | dest: '/etc', 47 | cwd: './config', 48 | expand: true 49 | }] 50 | 51 | function callback () { 52 | console.log('done!'); 53 | } 54 | 55 | // magic 56 | deb.pack(definition, files, callback) 57 | ``` 58 | ## License 59 | 60 | MIT 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var tar = require('tar-stream') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var async = require('async') 5 | var ar = require('ar-async') 6 | var crypto = require('crypto') 7 | var zlib = require('zlib') 8 | var debug = require('debug')('nobin-debian-installer') 9 | 10 | /** 11 | * Class used for creating .deb packages 12 | */ 13 | function Deb () { 14 | this.data = tar.pack() 15 | this.control = tar.pack() 16 | this.pkgSize = 0 17 | this.controlFile = {} 18 | this.filesMd5 = [] 19 | this.dirs = {} 20 | } 21 | 22 | Deb.prototype.pack = function (definition, files, callback) { 23 | var self = this 24 | 25 | var tempPath = path.resolve(path.join(definition.info.targetDir || '.', 'nbd' + Math.floor(Math.random() * 100000))) 26 | 27 | async.series([ 28 | fs.mkdir.bind(fs, tempPath), 29 | packFiles.bind(this, tempPath, expandFiles(files)), 30 | buildControlFile.bind(this, tempPath, definition), 31 | function buildDebBinFile (done) { 32 | fs.writeFile(path.join(tempPath, 'debian-binary'), '2.0\n', done) 33 | }, 34 | function buildPackage (done) { 35 | var pkgName = './' + self.controlFile.Package + '_' + self.controlFile.Version + 36 | '_' + self.controlFile.Architecture + '.deb' 37 | 38 | var pkgPath = path.resolve(path.join(definition.info.targetDir || '', pkgName)) 39 | 40 | debug('creating %s package', pkgPath) 41 | var writer = new ar.ArWriter(pkgPath, {variant: 'gnu'}) 42 | writer.writeEntries([ 43 | path.join(tempPath, 'debian-binary'), 44 | path.join(tempPath, 'control.tar.gz'), 45 | path.join(tempPath, 'data.tar.gz') 46 | ], function (err) { 47 | if (err) debug('failed to write .deb file') 48 | 49 | // remove temp files 50 | async.parallel([ 51 | fs.unlink.bind(fs, path.join(tempPath, 'control.tar.gz')), 52 | fs.unlink.bind(fs, path.join(tempPath, 'data.tar.gz')), 53 | fs.unlink.bind(fs, path.join(tempPath, 'debian-binary')) 54 | ], function (err) { 55 | if (err) return done(err) 56 | fs.rmdir(tempPath, done) 57 | }) 58 | }) 59 | } 60 | ], callback) 61 | } 62 | 63 | /** 64 | * Build the control part of the .deb package. 65 | */ 66 | function buildControlFile (tempPath, definition, callback) { 67 | var self = this 68 | 69 | var author = '' 70 | if (definition.package.author) { 71 | author = typeof definition.package.author === 'string' 72 | ? definition.package.author 73 | : definition.package.author.name + ' <' + definition.package.author.email + '>' 74 | } 75 | 76 | self.controlFile = { 77 | Package: definition.info.name || definition.package.name, 78 | Version: definition.package.version + '-' + (definition.info.rev || '1'), 79 | 'Installed-Size': Math.ceil(self.pkgSize / 1024), 80 | Section: 'misc', 81 | Priority: 'optional', 82 | Architecture: definition.info.arch || 'all', 83 | Depends: definition.info.depends || 'lsb-base (>= 3.2)', 84 | Maintainer: author 85 | } 86 | 87 | // optional items 88 | if (definition.package.license) { 89 | self.controlFile.License = definition.package.license 90 | } 91 | 92 | // optional items 93 | if (definition.package.homepage) { 94 | self.controlFile.Homepage = definition.package.homepage 95 | } 96 | 97 | self.controlFile.Description = definition.package.description + '\n ' + 98 | (definition.info.name || definition.package.name) + 99 | ': ' + definition.package.description 100 | 101 | // create the control file 102 | async.parallel([ 103 | function createControlFile (prlDone) { 104 | var controlHeader = '' 105 | async.forEachOf(self.controlFile, function (value, key, done) { 106 | controlHeader += key + ': ' + value + '\n' 107 | done() 108 | }, function (err) { 109 | if (err) { 110 | debug('could not write control file') 111 | return prlDone(err) 112 | } 113 | 114 | self.control.entry({name: './control'}, controlHeader, prlDone) 115 | }) 116 | }, function createHashFile (prlDone) { 117 | var fileContent = '' 118 | for (var i = 0; i < self.filesMd5.length; i++) { 119 | fileContent += self.filesMd5[i].md5 + ' ' + self.filesMd5[i].path.replace(/^\W*/, '') + '\n' 120 | } 121 | self.control.entry({name: './md5sums'}, fileContent, prlDone) 122 | }, function addScripts (prlDone) { 123 | async.forEachOf(definition.info.scripts, function (path, scriptName, doneScript) { 124 | debug('processing script ', path) 125 | async.waterfall([ 126 | fs.access.bind(fs, path, fs.F_OK), 127 | fs.stat.bind(fs, path), 128 | function readFile (stats, wtfDone) { 129 | fs.readFile(path, function (err, data) { 130 | wtfDone(err, stats, data) 131 | }) 132 | }, 133 | function addScript (stats, data, wtfDone) { 134 | debug('adding script ', scriptName) 135 | self.control.entry({ 136 | name: './' + scriptName, 137 | size: stats.size, 138 | mode: parseInt('755', 8) 139 | }, data, wtfDone) 140 | } 141 | ], doneScript) 142 | }, prlDone) 143 | } 144 | ], function (err) { 145 | if (err) { 146 | debug('could not write control tarball') 147 | return callback(err) 148 | } 149 | 150 | debug('successfully created control file') 151 | 152 | self.control.finalize() 153 | 154 | var file = fs.createWriteStream(path.join(tempPath, 'control.tar.gz')) 155 | file.on('finish', callback) 156 | 157 | var compress = zlib.createGzip() 158 | compress.pipe(file) 159 | self.control.pipe(compress) 160 | }) 161 | } 162 | 163 | /** 164 | * Add files to the .deb package. 165 | * 166 | * @param files - an object with the following format {'path/to/source/dir': 'path/to/target/dir'} (e.g. {'../../src/lib': '/srv/productName/lib'}) 167 | */ 168 | function packFiles (tempPath, files, callback) { 169 | var self = this 170 | 171 | async.eachSeries(files, function (crtFile, done) { 172 | var filePath = path.resolve(crtFile.src[0]) 173 | debug('adding %s', filePath) 174 | async.waterfall([ 175 | fs.stat.bind(fs, filePath), 176 | function (stats, wtfDone) { 177 | if (stats.isDirectory()) { 178 | addParentDirs(self.data, crtFile.dest, self.dirs, done) 179 | } else { 180 | async.waterfall([ 181 | fs.readFile.bind(fs, filePath), 182 | function writeFileToTarball (data, wtf2Done) { 183 | self.data.entry({ 184 | name: '.' + crtFile.dest, 185 | size: stats.size 186 | }, data, function (err) { 187 | wtf2Done(err, data) 188 | }) 189 | }, 190 | 191 | function processFile (fileData, wtf2Done) { 192 | self.pkgSize += stats.size 193 | 194 | self.filesMd5.push({ 195 | path: crtFile.dest, 196 | md5: crypto.createHash('md5').update(fileData).digest('hex') 197 | }) 198 | 199 | wtf2Done() 200 | } 201 | ], wtfDone) 202 | } 203 | } 204 | ], done) 205 | }, function (err) { 206 | if (err) { 207 | debug('there was a problem adding files to the .deb package: ', err) 208 | callback(err) 209 | } else { 210 | debug('successfully added files to .deb package') 211 | 212 | var file = fs.createWriteStream(path.join(tempPath, 'data.tar.gz')) 213 | file.on('finish', callback) 214 | 215 | var compress = zlib.createGzip() 216 | compress.pipe(file) 217 | 218 | self.data.pipe(compress) 219 | self.data.finalize() 220 | } 221 | }) 222 | } 223 | 224 | function addParentDirs (tarball, dir, createdDirs, callback) { 225 | if (dir !== '/') { 226 | addParentDirs(tarball, path.dirname(dir), createdDirs, function (err) { 227 | if (err) return callback(err) 228 | 229 | addDir() 230 | }) 231 | } else { 232 | addDir() 233 | } 234 | 235 | function addDir () { 236 | if (!createdDirs[dir]) { 237 | createdDirs[dir] = 1 238 | var name = dir === '/' ? './.' : '.' + dir + '/' 239 | tarball.entry({name: name, type: 'directory'}, callback) 240 | } else { 241 | callback() 242 | } 243 | } 244 | } 245 | 246 | function expandFiles (files) { 247 | var expand = require('glob-expand') 248 | var expandedFiles = [] 249 | files.map(function (file) { 250 | var sources = expand(file, file.src) 251 | sources.map(function (source) { 252 | expandedFiles.push({ 253 | src: [path.join((file.cwd ? file.cwd : ''), source)], 254 | dest: path.join(file.dest, source) 255 | }) 256 | }) 257 | }) 258 | return expandedFiles 259 | } 260 | 261 | module.exports = function () { 262 | return new Deb() 263 | } 264 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nobin-debian-installer", 3 | "version": "0.0.10", 4 | "description": "Create deb packages with no binary dependencies", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard && tape test/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/grunjol/nobin-debian-installer.git" 12 | }, 13 | "keywords": [ 14 | "deb", 15 | "debian", 16 | "installer", 17 | "cross-platform" 18 | ], 19 | "author": "grunjol ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/grunjol/nobin-debian-installer/issues" 23 | }, 24 | "homepage": "https://github.com/grunjol/nobin-debian-installer#readme", 25 | "dependencies": { 26 | "ar-async": "^0.1.4", 27 | "async": "^2.0.0-rc.2", 28 | "debug": "^2.2.0", 29 | "glob-expand": "^0.1.0", 30 | "tar-stream": "^1.3.2" 31 | }, 32 | "devDependencies": { 33 | "standard": "^6.0.8", 34 | "tape": "^4.5.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /test/deep-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nobin-debian-installer-test", 3 | "version": "0.0.1", 4 | "description": "Create deb packages with no binary dependencies", 5 | "main": "index.js", 6 | "scripts": { 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/grunjol/nobin-debian-installer.git" 11 | }, 12 | "keywords": [ 13 | "deb", 14 | "debian", 15 | "installer", 16 | "cross-platform" 17 | ], 18 | "author": "grunjol ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/grunjol/nobin-debian-installer/issues" 22 | }, 23 | "homepage": "https://github.com/grunjol/nobin-debian-installer#readme", 24 | "dependencies": { 25 | }, 26 | "devDependencies": { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/deep.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var deb = require('../')() 3 | var pkg = require('./deep-package.json') 4 | 5 | test('basic.deep: create basic deep structure', function (t) { 6 | t.plan(2) 7 | deb.pack({ 8 | package: pkg, 9 | info: { 10 | arch: 'amd64', 11 | targetDir: 'test/dist' 12 | } 13 | }, [{ 14 | src: ['./**'], 15 | dest: '/opt/deep', 16 | expand: true, 17 | cwd: 'test/deep' 18 | }], function (err, done) { 19 | t.error(err, 'failed to create') 20 | t.pass('created Linux .deb file') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/deep/file2: -------------------------------------------------------------------------------- 1 | file2 2 | -------------------------------------------------------------------------------- /test/deep/folder/file1: -------------------------------------------------------------------------------- 1 | file1 2 | --------------------------------------------------------------------------------