zsnapper 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/node/bin/node
  2. "use strict";
  3. var CronJob = require('cron').CronJob;
  4. var async = require('async');
  5. var fs = require('fs')
  6. var minimatch = require('minimatch')
  7. var util = require('util');
  8. var zfs = require('zfs')
  9. // Generate a snapshot name given a base.
  10. function snapshotName(base) {
  11. var when = (new Date()).toISOString().replace(/[-:]|(\.\d\d\d)/g, '');
  12. return base + '-' + when;
  13. }
  14. // Create a snapshot on the specified dataset with the specified name, exluding all datasets in excl.
  15. function createSnapshot(ds, sn, cb) {
  16. util.log('Create snapshot ' + ds + '@' + sn);
  17. zfs.snapshot({ dataset: ds, name: sn, recursive: true }, cb);
  18. }
  19. // Keep only num snapshots with the specified base on the specified dataset
  20. function cleanSnapshots(patterns, base, num, cb) {
  21. zfs.list({ type: 'snapshot' }, function (err, snaps) {
  22. var allSnaps = {};
  23. patterns.forEach(function (pat) {
  24. snaps.forEach(function (s) {
  25. var fields = s.name.split('@');
  26. var parts = fields[1].split('-');
  27. if (minimatch(fields[0], pat) && parts[0] === base) {
  28. var l = allSnaps[fields[0]] || [];
  29. l.push(s.name);
  30. allSnaps[fields[0]] = l;
  31. }
  32. });
  33. });
  34. async.eachLimit(Object.keys(allSnaps), 10, function (dsName, cb) {
  35. var snapNames = allSnaps[dsName];
  36. if (snapNames.length > num) {
  37. snapNames.sort();
  38. // Get the ones that exceed the specified number of snapshots.
  39. var toDestroy = snapNames.slice(0, snapNames.length - num);
  40. // Destroy them, one after the other.
  41. async.eachSeries(toDestroy, function (sn, cb) {
  42. util.log('Destroy snapshot ' + sn + ' (cleaned)');
  43. zfs.destroy({ name: sn, recursive: true }, cb);
  44. }, cb);
  45. } else {
  46. cb(null);
  47. }
  48. }, cb);
  49. });
  50. }
  51. function loadConfig() {
  52. var data = fs.readFileSync(process.argv[2], 'utf-8');
  53. var confObj = JSON.parse(data);
  54. return confObj;
  55. }
  56. function snap(datasets, snapBase, num) {
  57. var snapId = snapshotName(snapBase);
  58. zfs.list({type: 'filesystem,volume'}, function (err, dss) {
  59. dss = dss.map(function (s) { return s.name; });
  60. var toSnap = dss.filter(function (ds) {
  61. return datasets.filter(function (pat) { return minimatch(ds, pat) }).length > 0;
  62. });
  63. async.eachLimit(toSnap, 10, function (ds, cb) {
  64. createSnapshot(ds, snapId, cb);
  65. }, function () {
  66. cleanSnapshots(datasets, snapBase, num, function () {
  67. util.log("Done " + snapId);
  68. });
  69. });
  70. });
  71. }
  72. var cronJobs = [];
  73. var conf = loadConfig();
  74. Object.keys(conf).forEach(function (snapBase) {
  75. var config = conf[snapBase];
  76. var job = new CronJob('00 ' + config.when, function () {
  77. snap(config.datasets, snapBase, parseInt(config.count, 10));
  78. }, null, true);
  79. cronJobs.push(job);
  80. });
  81. // vim: set filetype=javascript: