ipk-builder
Version:
Build IPK files for use with ipkg or opkg.
262 lines (226 loc) • 8.58 kB
JavaScript
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var fse = require('fs-extra');
var IPKBuilder = function(opts) {
this.opts = opts || {};
this.files = [];
this.confFiles = [];
this.postScripts = [];
this.basePath = path.resolve(__dirname);
this.setBasePath = function(dir) {
this.basePath = path.resolve(dir);
};
this.resolve = function(apath, mode) {
return {
realPath: path.resolve(apath), // the path on the filesystem
packagePath: path.relative(this.basePath, apath), // the path in the ipk
mode: mode // the mode of the original file
};
};
this.addFiles = function() {
var i;
for(i=0; i < arguments.length; i++) {
if(!fs.existsSync(arguments[i])) {
if(this.opts.ignoreMissing) {
continue;
} else {
throw arguments[i] + " does not exist";
}
}
var stat = fs.statSync(arguments[i]);
if(stat.isFile()) {
this.files.push(this.resolve(arguments[i], stat.mode));
} else if(stat.isDirectory()) {
var files = fs.readdirSync(arguments[i]);
var j;
for(j=0; j < files.length; j++) {
this.addFiles(path.join(arguments[i], files[j]));
}
} else {
throw arguments[i] + " is neither a file nor a directory";
}
}
return this.files;
};
this.addConfFiles = function() {
var i;
for(i=0; i < arguments.length; i++) {
if(!fs.existsSync(arguments[i])) {
if(this.opts.ignoreMissing) {
continue;
} else {
throw arguments[i] + " does not exist";
}
}
var stat = fs.statSync(arguments[i]);
if(stat.isFile()) {
this.confFiles.push(this.resolve(arguments[i]));
} else {
throw arguments[i] + " is not a file";
}
}
return this.confFiles;
};
this.addPostScripts = function() {
var i;
for(i=0; i < arguments.length; i++) {
if(!fs.existsSync(arguments[i])) {
if(this.opts.ignoreMissing) {
continue;
} else {
throw arguments[i] + " does not exist";
}
}
var stat = fs.statSync(arguments[i]);
if(stat.isFile()) {
this.postScripts.push(path.resolve(arguments[i]));
} else if(stat.isDirectory()) {
var files = fs.readdirSync(arguments[i]);
var j;
for(j=0; j < files.length; j++) {
this.addPostScripts(path.join(arguments[i], files[j]));
}
} else {
throw arguments[i] + " is neither a file nor a directory";
}
}
return this.postScripts;
};
this.setMeta = function(obj) {
if(!obj.package) {
throw "Missing required field 'package'. Should be set to package name.";
}
if(!obj.version) {
throw "Missing required field 'version'. Should be set to package version string.";
}
if(!obj.maintainer) {
throw "Missing required field 'maintainer'. Should be set to name and email of maintainer. E.g: 'Foo Bar <foobar@example.com>'";
}
if(!obj.architecture) {
throw "Missing required field 'architecture'. Should be set to architecture name, e.g: 'ar71xx'";
}
if(!obj.description) {
throw "Missing required field 'description'. Should be set to a string describing the package.";
}
this.meta = obj;
};
this.setControl = this.setMeta;
this.stage = function() {
var stageDir, i;
do {
stageDir = path.join('/tmp', 'ipkg-builder-' + Math.round(Math.random() * 100000000));
} while(fs.exists(stageDir));
fs.mkdirSync(stageDir);
// create data dir
var dataDir = path.join(stageDir, 'data')
fs.mkdirSync(dataDir);
// copy files to target dir with correct directory structure
var files = this.files.concat(this.confFiles);
var targetDir;
for(i=0; i < files.length; i++) {
targetDir = path.join(dataDir, path.dirname(files[i].packagePath));
fse.mkdirpSync(targetDir);
var stat = fs.statSync(path.dirname(files[i].realPath));
fs.chmodSync(targetDir, stat.mode);
var newPath = path.join(targetDir, path.basename(files[i].realPath));
fse.copySync(files[i].realPath, newPath);
fs.chmodSync(newPath, files[i].mode);
}
// create control dir
var controlDir = path.join(stageDir, 'control')
fs.mkdirSync(path.join(stageDir, 'control'));
// write version file
fs.writeFileSync(path.join(stageDir, 'debian-binary'), '2.0');
// write list of configuration files
var confFilesList = '';
for(i=0; i < this.confFiles.length; i++) {
confFilesList += '/' + this.confFiles[i].packagePath + "\n";
}
if(confFilesList) {
fs.writeFileSync(path.join(controlDir, 'conffiles'), confFilesList);
}
// sort post scripts
this.postScripts.sort(function(a, b) {
if(a > b) {
return 1;
} else if(b > a) {
return -1;
} else {
return 0;
}
});
// combine post scripts into a single script and write it
var postScript = '';
for(i=0; i < this.postScripts.length; i++) {
postScript += "## BEGIN " + path.basename(this.postScripts[i]) + " ##\n";
postScript += fs.readFileSync(this.postScripts[i]) + "\n";
postScript += "## END " + path.basename(this.postScripts[i]) + " ##\n";
}
if(postScript) {
var postScriptPath = path.join(controlDir, 'postinst');
fs.writeFileSync(postScriptPath)
fs.chmodSync(postScriptPath, '755');
}
// write meta/control file
var value;
var control = '';
for(key in this.meta) {
value = this.meta[key];
if(key == 'description') {
value = value.replace(/\n/g, "\n ");
}
control += this.capitalize(key) + ': ' + value + "\n";
}
fs.writeFileSync(path.join(controlDir, 'control'), control);
return stageDir;
};
this.build = function(outPath, callback) {
try {
var stageDir = this.stage();
} catch(e) {
callback(e.message);
return;
}
var origDir = process.cwd();
outPath = path.resolve(outPath);
exec("fakeroot tar -pczf ../data.tar.gz *", {
cwd: path.join(stageDir, 'data')
}, function(err, stdout, stderr) {
if(err) {
callback("Error writing ipk: " + stderr);
return;
}
exec("fakeroot tar -pczf ../control.tar.gz *", {
cwd: path.join(stageDir, 'control')
}, function(err, stdout, stderr) {
if(err) {
callback("Error writing ipk: " + stderr);
return;
}
exec("tar -czf " + outPath + " data.tar.gz control.tar.gz debian-binary", {
cwd: stageDir
}, function(err, stdout, stderr) {
if(err) {
callback("Error writing ipk: " + stderr);
return;
}
fse.remove(stageDir, function(err) {
if(err) {
callback("Error cleaning up /tmp staging directory: " + err);
return;
}
callback(null, outPath);
});
});
});
});
};
this.capitalize = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
};
module.exports = function(opts) {
return new IPKBuilder(opts);
};