zsnapper 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env node
  2. var _ = require('underscore');
  3. var async = require('async');
  4. var CronJob = require('cron').CronJob;
  5. var util = require('util');
  6. var zfs = require('zfs')
  7. var fs = require('fs')
  8. // Zero pad an integer to the specified length.
  9. function padInteger(num, length) {
  10. "use strict";
  11. var r = '' + num;
  12. while (r.length < length) {
  13. r = '0' + r;
  14. }
  15. return r;
  16. }
  17. // Format a date to yyyymmddThhmmssZ (UTC).
  18. function formatDate(d) {
  19. "use strict";
  20. var year, month, day, hour, minute, second;
  21. year = d.getUTCFullYear();
  22. month = padInteger(d.getUTCMonth() + 1, 2);
  23. day = padInteger(d.getUTCDate(), 2);
  24. hour = padInteger(d.getUTCHours(), 2);
  25. minute = padInteger(d.getUTCMinutes(), 2);
  26. second = padInteger(d.getUTCSeconds(), 2);
  27. return year + month + day + 'T' + hour + minute + second + 'Z';
  28. };
  29. // Generate a snapshot name given a base.
  30. function snapshotName(base) {
  31. "use strict";
  32. var when = formatDate(new Date());
  33. return base + '-' + when;
  34. }
  35. // Create a snapshot on the specified dataset with the specified name, exluding all datasets in excl.
  36. function createSnapshot(ds, sn, excl) {
  37. "use strict";
  38. function destroyExcludes(snaps, excl) {
  39. var toDestroy = snaps.filter(function (s) {
  40. var fields = s.name.split('@');
  41. var createdNow = fields[1] === sn;
  42. var inExclude = _.contains(excl, fields[0]);
  43. return createdNow && inExclude;
  44. });
  45. async.forEachSeries(toDestroy, function (snap, cb) {
  46. util.log('Destroy snapshot ' + snap.name + ' (excluded)');
  47. zfs.destroy({ name: snap.name, recursive: true }, cb);
  48. });
  49. }
  50. util.log('Create snapshot ' + ds + '@' + sn);
  51. zfs.snapshot({ dataset: ds, name: sn, recursive: true }, function (err) {
  52. if (err) {
  53. util.log(err);
  54. } else {
  55. zfs.list({ type: 'snapshot' }, function (err, snaps) {
  56. if (err) {
  57. util.log(err);
  58. } else {
  59. destroyExcludes(snaps, excl);
  60. }
  61. });
  62. }
  63. });
  64. }
  65. // Keep only num snapshots with the specified base on the specified dataset
  66. function cleanSnapshots(ds, base, num) {
  67. zfs.list({ type: 'snapshot' }, function (err, snaps) {
  68. // Find all snapshots that match the specified dataset and base.
  69. var ourSnaps = snaps.filter(function (s) {
  70. var fields = s.name.split('@');
  71. var parts = fields[1].split('-');
  72. return fields[0] === ds && parts[0] === base;
  73. });
  74. if (ourSnaps.length > num) {
  75. // Get just the sorted list of names.
  76. var snapNames = ourSnaps.map(function (s) { return s.name; });
  77. snapNames.sort();
  78. // Get the ones that exceed the specified number of snapshots.
  79. var toDestroy = snapNames.slice(0, snapNames.length - num);
  80. // Destroy them, one after the other.
  81. async.forEachSeries(toDestroy, function (sn, cb) {
  82. util.log('Destroy snapshot ' + sn + ' (cleaned)');
  83. zfs.destroy({ name: sn, recursive: true }, cb);
  84. });
  85. }
  86. });
  87. }
  88. function snapshot(dataset, base, excl, num) {
  89. var name = snapshotName(base);
  90. createSnapshot(dataset, name, excl);
  91. cleanSnapshots(dataset, base, num);
  92. }
  93. function loadConfig() {
  94. var data = fs.readFileSync(process.argv[2], 'utf-8');
  95. var confObj = JSON.parse(data);
  96. return confObj;
  97. }
  98. var cronJobs = [];
  99. var conf = loadConfig();
  100. _.each(conf, function (jobs, dataset) {
  101. _.each(jobs, function (config, name) {
  102. var job = new CronJob('00 ' + config.when, function () {
  103. var excl = config.exclude || [];
  104. snapshot(dataset, name, excl, parseInt(config.count, 10));
  105. }, null, true, 'UTC');
  106. cronJobs.push(job);
  107. });
  108. });