Skip to content

Commit ec7bd7b

Browse files
Adding scripts initially
1 parent d0da4e3 commit ec7bd7b

File tree

2 files changed

+604
-0
lines changed

2 files changed

+604
-0
lines changed

rsnapshot-once.php

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#!/usr/bin/php
2+
<?php
3+
/*
4+
* rsnapshot-once
5+
* Copyright (C) 2013 Philipp C. Heckel <philipp.heckel@gmail.com>
6+
*
7+
* Original blog post at:
8+
* http://blog.philippheckel.com/2013/06/28/script-run-rsnapshot-backups-only-once-and-rollback-failed-backups-using-rsnapshot-once/
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License as published by
12+
* the Free Software Foundation, either version 3 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU General Public License
21+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
*/
23+
24+
$opts = getopt("c:");
25+
// General failure
26+
if (!$opts) {
27+
die(
28+
"Usage:\n"
29+
. " {$argv[0]} [-c cfgfile] (daily|weekly|monthly)\n\n"
30+
31+
."Description:\n"
32+
." rsnapshot-once is a wrapper for rsnapshot to ensure that daily, weekly\n"
33+
." and monthly tasks are run only once in the respective time period, i.e.\n"
34+
." it ensures that 'weekly' backups are executed only once a week,\n"
35+
." regardless how often rsnapshot-timer is called.\n\n"
36+
." rsnapshot-once accepts the same parameters as rsnapshot and uses the\n"
37+
." same config file. It does not need any additional configuration.\n\n"
38+
39+
."Example (crontab):\n"
40+
." # Job to run every hour, rsnapshot-once makes sure it only runs once a day.\n"
41+
." 0 * * * * {$argv[0]} -c /home/user/.rsnapshot/rsnapshot.home.conf daily\n"
42+
);
43+
}
44+
//print_r($opts);
45+
// -c (Config file)
46+
if (!isset($opts['c'])) {
47+
$opts['c'] = "/etc/rsnapshot.conf";
48+
}
49+
else if (isset($opts['c']) && !file_exists($opts['c'])) {
50+
die("Config file {$opts['c']} does not exist.\n");
51+
}
52+
$cfgfile = $opts['c'];
53+
// Read logfile
54+
$logfile = trim(`cat '{$opts['c']}' | grep '^logfile'`);
55+
if (!preg_match('!^logfile\t(.+)$!', $logfile, $m)) {
56+
die("Config option 'logfile' not found in config file.\n");
57+
}
58+
$logfile = $m[1];
59+
logft("## STARTING BACKUP ######################\n");
60+
$xargs = $argv; array_shift($xargs);
61+
logft("\$ ".basename($argv[0])." '".join("' '", $xargs)."'\n");
62+
logft("Config read from: $cfgfile\n");
63+
logft("- logfile = $logfile\n");
64+
// Read snapshot_root
65+
$snapshot_root = trim(`cat '{$opts['c']}' | grep '^snapshot_root'`);
66+
if (!preg_match('!^snapshot_root\t(.+)$!', $snapshot_root, $m)) {
67+
logft("Config option 'snapshot_root' not found in config file. EXITING.\n");
68+
exit;
69+
}
70+
$snapshot_root = $m[1];
71+
logft("- snapshot_root = $snapshot_root\n");
72+
if (!preg_match('!/$!', $snapshot_root)) {
73+
logft("Invalid config. 'snapshot_root' has no trailing slash. EXITING.\n");
74+
logft("## BACKUP ABORTED #######################\n\n");
75+
exit;
76+
}
77+
// Other argument (weekly, daily, monthly)
78+
$jobname = $argv[count($argv)-1];
79+
if (!in_array($jobname, array("daily", "weekly", "monthly"))) {
80+
logft("Jobname must be 'daily', 'weekly' or 'monthly'. EXITING.\n");
81+
logft("## BACKUP ABORTED #######################\n\n");
82+
exit;
83+
}
84+
// Check pid file
85+
$pidfile = "{$snapshot_root}.rsnapshot-once.pid";
86+
logft("Checking rsnapshot-once pidfile at $pidfile ... ");
87+
if (file_exists($pidfile)) {
88+
$pidfilepid = trim(file_get_contents($pidfile));
89+
if (file_exists("/proc/$pidfilepid")) {
90+
logf("Exists. PID $pidfilepid still running. ABORTING.\n");
91+
logft("## BACKUP ABORTED #######################\n\n");
92+
exit;
93+
}
94+
else {
95+
logf("Exists. PID $pidfilepid not running. Script crashed before.\n");
96+
$sorted_backups = glob("{$snapshot_root}$jobname.*");
97+
echo ": ".join($sorted_backups);
98+
exit;
99+
natsort($sorted_backups);
100+
if (count($sorted_backups) == 0) {
101+
logft("No previous backups found. No cleanup necessary.\n");
102+
}
103+
else {
104+
logft("Cleaning up unfinished backup ...\n");
105+
$first_backup = array_shift($sorted_backups);
106+
logft("- Deleting $first_backup ... ");
107+
108+
// Double check before deleting (!)
109+
if (!isset($jobname) || empty($jobname) || !file_exists($first_backup) || strlen($first_backup) < 2 || !preg_match('/^(.+)\.(\d+)$/', $first_backup)) {
110+
logf("Script security issue. EXITING.\n");
111+
logft("## BACKUP ABORTED #######################\n\n");
112+
exit;
113+
}
114+
else {
115+
// Delete!
116+
$slashed_first_backup = addslashes($first_backup);
117+
`rm -rf '$slashed_first_backup'`;
118+
logf("DONE\n");
119+
}
120+
121+
while (count($sorted_backups) > 0) {
122+
$nth_backup = array_shift($sorted_backups);
123+
124+
if (preg_match('/^(.+)\.(\d+)$/', $nth_backup, $m)) {
125+
$n_minus_1th_backup = $m[1].".".($m[2]-1);
126+
logft("- Moving $nth_backup to $n_minus_1th_backup ... ");
127+
rename($nth_backup, $n_minus_1th_backup);
128+
logf("DONE\n");
129+
}
130+
}
131+
}
132+
}
133+
}
134+
else {
135+
logf("Does not exist. Last backup was clean.\n");
136+
}
137+
logft("Checking delays (minimum 15 minutes since startup/wakeup) ...\n");
138+
// Backup delay (uptime/resumetime + 15 minutes)
139+
$uptime_minutes = 0;
140+
$uptime_minutes = strtok(exec("cat /proc/uptime"), ".")/60;
141+
if ($uptime_minutes) {
142+
if ($uptime_minutes < 15) {
143+
logft("- Computer uptime is ".sprintf("%.1f", $uptime_minutes)." minutes. NOT ENOUGH. EXITING.\n");
144+
logft("## BACKUP ABORTED #######################\n\n");
145+
exit;
146+
}
147+
else {
148+
logft("- Computer uptime is ".sprintf("%.1f", $uptime_minutes)." minutes. THAT'S OKAY.\n");
149+
}
150+
}
151+
// Get time of resume
152+
// http://unix.stackexchange.com/questions/22140/determine-time-of-last-suspend-to-ram
153+
$wakeup_minutes = 0;
154+
$wakeup_date = trim(`egrep 'Running hooks for (resume|thaw)' /var/log/pm-suspend.log | tail -n 1 | sed 's/^\(.*\):.*$/\\1/'`);
155+
if (isset($wakeup_date) && $wakeup_date) {
156+
$wakeup_minutes = (time() - intval(`date --date="$wakeup_date" +%s`))/60;
157+
if ($wakeup_minutes) {
158+
if ($wakeup_minutes < 15) {
159+
logft("- Computer resume time is ".sprintf("%.1f", $wakeup_minutes)." minutes. NOT ENOUGH. EXITING.\n");
160+
logft("## BACKUP ABORTED #######################\n\n");
161+
exit;
162+
}
163+
else {
164+
logft("- Computer resume time is ".sprintf("%.1f", $wakeup_minutes)." minutes. THAT'S OKAY.\n");
165+
}
166+
}
167+
}
168+
// Get date of newest folder (e.g. weekly.0, daily.0, monthly.0)
169+
// to figure out if the job needs to run
170+
$newest_backup_folder = "{$snapshot_root}{$jobname}.0";
171+
if (!file_exists($newest_backup_folder)) {
172+
logft("No backup exists for job '$jobname' at '$newest_backup_folder'.\n");
173+
$job_needs_to_run = true;
174+
}
175+
else {
176+
$backuptime = filemtime($newest_backup_folder);
177+
logft("Newest backup for '$jobname' at $newest_backup_folder was at ".date("d/M/Y, H:i:s", $backuptime).".\n");
178+
if ($jobname == "daily") {
179+
$job_needs_to_run = time() - $backuptime > 23*60*60;
180+
$text_between_runs = sprintf("%.1f", (time() - $backuptime)/60/60)." hour(s)";
181+
$text_min_time = "23 hours";
182+
}
183+
else if ($jobname == "weekly") {
184+
$job_needs_to_run = time() - $backuptime > 6.5*24*60*60;
185+
$text_between_runs = sprintf("%.1f", (time() - $backuptime)/60/60/24)." day(s)";
186+
$text_min_time = "6.5 days";
187+
}
188+
else if ($jobname == "monthly") {
189+
$job_needs_to_run = time() - $backuptime > 29*24*60*60;
190+
$text_between_runs = sprintf("%.1f", (time() - $backuptime)/60/60/24)." day(s)";
191+
$text_min_time = "29 days";
192+
}
193+
else {
194+
logft("Error: This should not happen. ERROR.\n");
195+
exit;
196+
}
197+
if (!$job_needs_to_run) {
198+
logft("Job does NOT need to run. Last run is only $text_between_runs ago (min. is $text_min_time). EXITING.\n");
199+
logft("## BACKUP ABORTED #######################\n\n");
200+
exit;
201+
}
202+
else {
203+
logft("Last run is $text_between_runs ago (min. is $text_min_time).\n");
204+
}
205+
}
206+
logft("Writing rsnapshot-once pidfile (PID ".getmypid().") to ".$pidfile.".\n");
207+
file_put_contents($pidfile, getmypid());
208+
logft("NOW RUNNING JOB: ");
209+
array_shift($argv);
210+
$escaped_pidfile = addslashes($pidfile);
211+
$cmd = "rsnapshot '".join("' '", $argv)."' ".'2>&1';
212+
logf("$cmd\n");
213+
$exitcode = -1;
214+
$configerror = false;
215+
$output = array();
216+
exec($cmd, $output, $exitcode);
217+
foreach ($output as $outline) {
218+
logft(" rsnapshot says: $outline\n");
219+
220+
if (preg_match("/rsnapshot encountered an error/", $outline)) {
221+
$configerror = true;
222+
}
223+
}
224+
if ($configerror) {
225+
logft("Exiting rsnapshot-once, because error in rsnapshot run detected.\n");
226+
logft("Removing rsnapshot-once pidfile at ".$pidfile." (CLEAN EXIT).\n");
227+
unlink($pidfile);
228+
229+
logft("## BACKUP ABORTED #######################\n");
230+
exit;
231+
}
232+
// pidfile should NOT exist if exit was clean
233+
if ($exitcode == 1) { // 1 means 'fatal error' in rsnapshot terminology
234+
logft("No clean exit. Backup aborted. Cleanup necessary on next run (DIRTY EXIT).\n");
235+
logft("## BACKUP ABORTED #######################\n");
236+
exit;
237+
}
238+
logft("Removing rsnapshot-once pidfile at ".$pidfile." (CLEAN EXIT).\n");
239+
unlink($pidfile);
240+
logft("Rotating log ...\n");
241+
logrotate();
242+
logft("## BACKUP COMPLETE ######################\n\n");
243+
#### FUNCTIONS #############################################################
244+
// log with time
245+
function logft($s) {
246+
logf("[".date("d/M/Y:H:i:s")."/rsnapshot-once] ".$s);
247+
}
248+
// log without time
249+
function logf($s) {
250+
echo $s;
251+
252+
if (isset($GLOBALS['logfile'])) {
253+
file_put_contents(trim($GLOBALS['logfile']), $s, FILE_APPEND | LOCK_EX);
254+
}
255+
}
256+
// rotate log
257+
function logrotate() {
258+
$logfile = $GLOBALS['logfile'];
259+
if (isset($GLOBALS['logfile'])) {
260+
`tail -n 1000 $logfile > $logfile.tmp`;
261+
`mv $logfile.tmp $logfile`;
262+
}
263+
}
264+
?>

0 commit comments

Comments
 (0)