var execute_global = function(options) { var action = options.action; var config = cfg.system(cfg.global); switch (action) { case 'register': if (!options.data) error.raise({ action : action, msg : "Needs data" }); var data = parse_kv_pairs(options.data) if (options.unit !== undefined) data.name = options.unit; config.set(data); break; case 'unregister': if (!options.unit) error.raise({ action : action, msg : "Needs unit name" }); config.rm(options.unit); break; default: error.raise({ msg : "Unknown action", action : action}); break; } };
var exec_script = function(action) { debug.info('SCRIPT>>>', config.script, 'action', action); if (!os.path.isexec(config.script)) error.raise({msg : "Should be executable" , script : config.script}); var args = ['--action', action, '--dir', data_dir.absolute, '--bin-dir', blobs_dir.absolute, '--home-dir', home ]; var ps = subprocess.system(config.script, args); var trace_res = debug.info; if (ps.rc()) trace_res = debug.error; trace_res("RC", ps.rc()); trace_res("STDOUT", ps.stdout().toString()); trace_res("<<STDOUT"); trace_res("STDERR", ps.stderr().toString()); trace_res("<<STDERR"); trace_res('<<<SCRIPT', config.script, 'action', action, "is done"); if (ps.rc()) { var msg = "Backup script " + config.script + " exited with rc=" + ps.rc(); error.raise({msg: msg, stdout: ps.stdout().toString() , stderr: ps.stderr().toString()}); } };
var backup_unit = function() { var status, i; // cleanup directories for data and blobs in // the repository os.rmtree(data_dir.absolute); os.rmtree(blobs_dir.absolute); mkdir(root_dir.absolute); mkdir(data_dir.absolute); mkdir(blobs_dir.absolute); exec_script('export'); // save blobs util.forEach(vcs.status(blobs_dir.relative), function(status) { var git_path = status.src; if (status.index === ' ' && status.tree === 'D') return vcs.rm(git_path); // service files are not blobs var fname = os.path.fileName(git_path); var service_prefix = cfg.prefix, len = service_prefix.length; if (fname.length >= len && fname.substr(0, len) == service_prefix) return vcs.add(git_path); return blob(git_path).add(); }); // commit data status = vcs.status(root_dir.relative); if (!status.length) { debug.info("Nothing to backup for " + name); return; } // add all only in data dir to avoid blobs to get into git // objects storage vcs.add(data_dir.relative, ['-A']); status = vcs.status(root_dir.relative); if (stat.is_tree_dirty(status)) error.raise({msg : "Dirty tree", dir : root_dir, status : status_dump(status) }); vcs.commit(">" + name); status = vcs.status(root_dir.relative); if (stat.is_dirty(status)) error.raise({msg : "Not fully commited", dir : root_dir, status : status_dump(status)}); };
var create_repo = function() { if (vcs.init()) error.raise({ msg : "Can't init git", path : path, stderr : vcs.stderr()}); if (!os.path.exists(storage)) error.raise({ msg : "Can't find .git", path : path, stderr : vcs.stderr()}); };
var execute = function(options) { if (options.global) return execute_global(options); if(!options.vault) error.raise({msg : "Missing option", name : "vault"}); var vault = mk_vault(options.vault); var action = options.action; var res, units = options.unit ? [options.unit] : undefined; switch (action) { case 'init': res = vault.init(parse_kv_pairs(options.git_config)); break; case 'export': case 'backup': res = vault.backup(options.home, {units : units, message : options.message}, results); break; case 'import': case 'restore': if (!options.tag) error.raise({msg : "tag should be provided to restore"}); vault.snapshots.activate(options.tag); res = vault.restore(options.home, {units : units}, results); break; case 'list-snapshots': res = vault.snapshots.list(); print(res.join('\n')); break; case 'register': if (!options.data) error.raise({ action : action, msg : "Needs data" }); vault.register(parse_kv_pairs(options.data)); break; case 'unregister': if (!options.unit) error.raise({ action : action, msg : "Needs unit name" }); res = vault.unregister(options.unit); break; default: error.raise({ msg : "Unknown action", action : action}); break; } return res; };
var parse_next_status = function(stream) { var s = stream.next(); if (s.length < 4 && s[2] != " ") error.raise({ msg : "Unexpected status format, need 'XX ...'", got : s }); var item = Object.create(status_item); item.init(s); if (item.isRename()) { if (stream.end()) error.raise({ msg : "No dst after rename op", format : s }); item.dst = item.src; item.src = stream.next(); } return item; };
var backup = function(home, options, on_progress) { if (options) debug.debug(util.dump("Backup", options)); if (!os.path.isDir(home)) error.raise({msg: "Home is not a dir", path: home }); if (typeof(on_progress) !== 'function') on_progress = function(status) { debug.debug(util.dump("Progress", status)); }; options = options || {}; var res = { succeeded :[], failed : [] }; var config = vault_config(); var start_time_tag = sys.date().toGitTag(); var name, message; var backup_unit = function(name) { var head_before = vcs.rev_parse('HEAD'); var unit = mk_unit(config.units()[name], home); try { on_progress({ unit: name, status: "begin" }); unit.backup(); on_progress({ unit: name, status: "ok" }); res.succeeded.push(name); } catch (err) { err.unit = name; debug.error("Can't backup " + name + util.dump("Reason:", err)); on_progress({ unit: name, status: err.reason || "fail" }); res.failed.push(name); unit.reset(head_before); } }; reset(); vcs.checkout('master', ['-f']); if (options.units) { options.units.each(backup_unit); } else { config.units().each(function(name, value) { return backup_unit(name); }); } message = (options.message ? [start_time_tag, options.message].join('\n') : start_time_tag); os.write_file(files.message, message); vcs.add(".message"); vcs.commit([start_time_tag, message].join('\n')); snapshots.tag(start_time_tag); vcs.notes.add(options.message || ""); return res; };
var init = function(config) { var create_repo = function() { if (vcs.init()) error.raise({ msg : "Can't init git", path : path, stderr : vcs.stderr()}); if (!os.path.exists(storage)) error.raise({ msg : "Can't find .git", path : path, stderr : vcs.stderr()}); }; var setup_git_config = function(config) { config["status.showUntrackedFiles"] = "all"; vcs.config.set(config); }; var init_versions = function() { init_version("tree"); vcs.add(files.version.tree); vcs.commit('anchor'); vcs.tag(['anchor']); os.path.isdir(blob_storage) || os.mkdir(blob_storage); init_version("repository"); }; var exclude_service_files = function() { var exclude = vcs.get_local().exclude; exclude.add(".vault.*"); exclude.commit(); set_state("new"); }; if (!os.mkdir(path)) error.raise({ msg : "Can't init vault", path : path, reason : "directory already exists" }); try { create_repo(); setup_git_config(config); exclude_service_files(); init_versions(); } catch (err) { os.rmtree(path); throw err; } };
var restore = function(home, options, on_progress) { if (options) debug.debug(util.dump("Restore", options)); if (!os.path.isDir(home)) error.raise({msg: "Home is not a dir", path: home }); if (typeof(on_progress) !== 'function') on_progress = function(status) { debug.debug(util.dump("Progress", status)); }; options = options || {}; var config = vault_config(); var res = { succeeded :[], failed : [] }; var name; var restore_unit = function(name) { var unit = mk_unit(config.units()[name], home); try { on_progress({ unit: name, status: "begin" }); unit.restore(); on_progress({ unit: name, status: "ok" }); res.succeeded.push(name); } catch (err) { err.unit = name; debug.error("Can't restore " + name + util.dump("Reason:", err)); on_progress({ unit: name, status: err.reason || "fail" }); res.failed.push(name); } }; if (options.units) { options.units.each(restore_unit); } else { config.units().each(function(name, value) { restore_unit(name); }); } };
var add = function() { var origTime; os.path.isDir(blob_dir) || os.mkdir(blob_dir); origTime = os.path.lastModified(link_fname); if (os.path.isFile(blob_fname)) { os.unlink(link_fname); } else { os.rename(link_fname, blob_fname); } os.path.setLastModified(blob_fname, origTime); var target = os.path.relative(blob_fname, os.path.dirname(link_fname)); os.symlink(target, link_fname); if (!(os.path.isSymLink(link_fname) && os.path.isFile(blob_fname))) { error.raise({ msg: "Blob should be symlinked", link: link_fname, target: target }); } vcs.add(git_path); };
var checkout = function(treeish) { if (typeof(treeish) !== 'string') error.raise({msg: 'expected string as treeish', actual: treeish}); vcs.checkout(treeish, ['-f']); reset(treeish); };
var restore_unit = function() { if (!os.path.isDir(root_dir.absolute)) error.raise({reason: "absent", name: name}); exec_script('import'); };
set_state = function(name) { if (os.write_file(files.state, name) !== name.length) error.raise({message: "State is not written", fname: files.state}); };