summaryrefslogtreecommitdiff
path: root/ppsnapback
blob: 520f32ddca979e167ce440c1c0d47e2cdf3c773f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
#!/bin/bash
#
# ppsnapback - push or pull snapshot backups using rsync
#
# Copyright (C) 2006-2007 Nicolas Schodet.
#
# See end of file for documentation, copyright and license.
#
# Man page can be generated using:
#
# $ pod2man -c '' -r '' ppsnapback > ppsnapback.1
#

set -e

MODULE=${1:-none}
BASEDIR=${2:-$HOME/backups}
MD=$BASEDIR/$MODULE

# Read configuration files.
if [[ ! -f $MD/config ]]; then
    echo "no module '$MODULE'" >&2
    echo "Usage: $0 MODULE [BACKUP_DIR]" >&2
    exit 1
fi
[[ -f $BASEDIR/default-config ]] && source $BASEDIR/default-config
source $MD/config
backlog=${backlog:-3}

if [[ $path ]]; then
    # The tabulation is a rsync quirk to disable word splitting.
    RSH="ssh${privkey:+ -o IdentitiesOnly	yes -i $MD/$privkey}"
    [[ -f $BASEDIR/default-exclude ]] && \
    DEFAULT_EXCLUDES="--exclude-from $BASEDIR/default-exclude"
    [[ -f $MD/exclude ]] && \
    EXCLUDES="--exclude-from $MD/exclude"
    if [[ $remote ]]; then
	if [[ $remote == *:* ]]; then
	    # Push backup to a remote server.
	    rsync -a --delete-excluded --rsh="$RSH" \
		$DEFAULT_EXCLUDES $EXCLUDES $extra "$path" "$remote"
	else
	    # Push backup to a remote ppsnapback.
	    rsync -a --delete-excluded --rsh="$RSH" \
		--rsync-path=please-configure-ssh-key \
		$DEFAULT_EXCLUDES $EXCLUDES $extra "$path" "$remote":invalid
	fi
    else
	# Store backup to the local repository.
	rm -rf $MD/new
	rsync -a --delete-excluded --rsh="$RSH" \
	    $DEFAULT_EXCLUDES $EXCLUDES $extra \
	    --link-dest=../0 "$path" $MD/new
	touch $MD/new
	# Rotate.
	[[ -d $MD/$backlog ]] && rm -rf $MD/$backlog
	for ((i=backlog-1; i >= 0; i--)); do
	    [[ -d $MD/$i ]] && mv $MD/$i $MD/$((i+1))
	done
	mv $MD/new $MD/0
    fi
elif [[ $fakeroot && -w $MD/$fakeroot && $UID -ne 0 ]]; then
    # Restart using fakeroot.
    fakeroot -i $MD/$fakeroot -s $MD/$fakeroot -- $0 $MODULE $BASEDIR
else
    # Receiver mode, called from ssh.
    #rm -rf $MD/new
    rsync --server -a --delete-excluded \
	$extra --link-dest=../0 . $MD/new
    touch $MD/new
    # Rotate.
    [[ -d $MD/$backlog ]] && rm -rf $MD/$backlog
    for ((i=backlog-1; i >= 0; i--)); do
	[[ -d $MD/$i ]] && mv $MD/$i $MD/$((i+1))
    done
    mv $MD/new $MD/0
fi

exit 0

__END__

=head1 NAME

ppsnapback - push or pull snapshot backups using rsync

=head1 SYNOPSIS

B<ppsnapback> I<module> [I<backup_dir>]

=head1 DESCRIPTION

B<ppsnapback> makes backup snapshots using rsync, possibly pulling or pushing
data from or to a B<ssh> connection.  When B<ppsnapback> is pushing data, it
can use fakeroot to save owners, groups and permissions.

B<ppsnapback> reads its configuration from a local hierarchy of files based at
I<backup_dir>.  If I<backup_dir> is not specified, it default to
F<~/backups/>.  Each backup module should have its own directory located under
I<backup_dir>.

A default configuration is read from F<I<backup_dir>/default-config> and the
module configuration is read from F<I<backup_dir>/I<module>/config>.

Additional exclude lists using the rsync(1) format are read from
F<I<backup_dir>/default-exclude> and F<I<backup_dir>/I<module>/exclude>.

The configuration files are shell files sourced by B<ppsnapback>.  They are
supposed to set configuration variables but can use any bash(1) shell
constructions to do so.

B<ppsnapback> will construct a set of directly accessible snapshots, using
hardlinks to limit hard disk usage.  It is based on a Mike Rubel's article
"Easy Automated Snapshot-Style Backups with Linux and Rsync" (see L</SEE
ALSO>).  The F<0> directory in the module directory will contain the most
recent snapshot.  The F<1> directory will contain the previous snapshot and so
on...

B<ppsnapback> aims to keep a simple and readable source code in order to allow
its users to hack any new or local features in it.

=head1 EXAMPLES

=head2 Local Backups

This case shows a configuration in which data to backup and backup repository
is on the same host.

Content of F<I<backup_dir>/local_example/config>:

    path=/home

That's all!  Run B<ppsnapback> using:

    B<ppsnapback> local_example I<backup_dir>

Be careful not to backup the backup repository!

=head2 Pull Remote Data to Backup

This case shows a configuration in which data to backup is located on a remote
host.  Ssh private key is used to secure the connection.  B<ppsnapback> do not
need to be installed on the remote host.

Content of F<I<backup_dir>/pull_example/config>:

    path=myuser@myhost.example.com:/home/myuser/
    privkey=myhost-myuser-backup

Content of F<myhost.example.com:/home/myuser/.ssh/authorized_keys> (line
breaks introduced by an anti-slash are provided for readability):

    from="backuphost.example.com",\
    command="/usr/bin/rsync --server --sender -logDtpr \
    . /home/myuser/",\
    no-pty,no-port-forwarding,no-x11-forwarding,no-agent-forwarding \
    ssh-rsa AAA...rest of public key...

The private key has been generated using ssh-keygen(1).  See
L</SECURING SSH CONNECTION> for more details.

Run B<ppsnapback> using:

    B<ppsnapback> pull_example I<backup_dir>

=head2 Push Remote Backup Repository With No Snapshot Rotation

This case shows a configuration in which local data to backup is pushed on a
remote host, but with no snapshot rotation.  Ssh private key is used to secure
the connection.  B<ppsnapback> do not need to be installed on the remote host.

Content of F<I<backup_dir>/push_nosnap_example/config>:

    path=/home/myuser
    remote=myuser@backup.example.com:backups/push_nosnap_example
    privkey=myhost-myuser-backup

Content of F<backup.example.com:/home/myuser/.ssh/authorized_keys> (line
breaks introduced by an anti-slash are provided for readability):

    from="myhost.example.com",\
    command="/usr/bin/rsync --server -logDtpr --delete-excluded \
    . backups/push_nosnap_example",\
    no-pty,no-port-forwarding,no-x11-forwarding,no-agent-forwarding \
    ssh-rsa AAA...rest of public key...

The private key has been generated using ssh-keygen(1).  See
L</SECURING SSH CONNECTION> for more details.

Run B<ppsnapback> using:

    B<ppsnapback> push_nosnap_example I<backup_dir>

=head2 Push Remote Backup Repository With Snapshot Rotation

This case is similar to the previous case, but requires that B<ppsnapback> is
installed on the remote host to perform the snapshots rotation.

Content of F<I<backup_dir>/push_example/config>:

    path=/home/myuser
    remote=myuser@backup.example.com
    privkey=myhost-myuser-backup

The file F<backup.example.com:I<backup_dir>/push_example/config> can be empty
but it must exist.

Content of F<backup.example.com:/home/myuser/.ssh/authorized_keys> (line
breaks introduced by an anti-slash are provided for readability):

    from="myhost.example.com",\
    command="/path/to/ppsnapback push_example",\
    no-pty,no-port-forwarding,no-x11-forwarding,no-agent-forwarding \
    ssh-rsa AAA...rest of public key...

The private key has been generated using ssh-keygen(1).  See
L</SECURING SSH CONNECTION> for more details.

Run B<ppsnapback> using:

    B<ppsnapback> push_example I<backup_dir>

=head2 Push Remote Backup Repository With Fakeroot

This case is similar to the previous case, but requires that fakeroot(1) is
installed on the remote host.  The advantage of using fakeroot(1) is that file
owners, groups and permissions are also saved.

The only difference is in the file
F<backup.example.com:I<backup_dir>/push_example/config>:

    fakeroot=fakeroot.dat

If users and groups do not match at the backup host, add the following option
to B<BOTH> sides:

    extra=--numeric-ids

=head1 CONFIGURATION VARIABLES

=over

=item I<path>

This is the path to data to backup.  Path can be a local path or remote path
using the rsync(1) syntax.  A trailing slash changes the B<ppsnapback>
behavior to avoid creating an additional directory level at the destination.
You can find more about this in the rsync(1) manual.

=item I<extra>

This variable is passed to rsync(1) as additional options.  Be careful, most
of these options must also be used at the remote end of the ssh(1) connection.

=item I<privkey>

This is the path to an optional ssh(1) private key, relative to the module
directory.

=item I<remote>

If this variable is defined, backups are not stored locally but on a remote
host.  In this case, this variable is the path to the remote backup
repository.  If the remote host does not override the sent command, a simple
rsync(1) will be used with no snapshot rotation.  See L</EXAMPLES> for more
informations.  If the remote host does override the sent command (this is the
recommended usage) only the host part of this variable does really matter.

=item I<backlog>

This variable is the number of older snapshots to keep in the backup
repository.  It defaults to 3.

=item I<fakeroot>

As B<ppsnapback> use normal files to store the backups, permissions, owners
and groups can only be saved if it is run as root.  Another alternative is to
use fakeroot(1) to simulate a root environment.  If this variable is set, it
gives the path relative to the module directory to the file used by
fakeroot(1) to save additional information like owners, groups and
permissions.  This file must exist prior to be used by B<ppsnapback>, you can
create it using touch(1).

=back

=head1 SECURING SSH CONNECTION

Doing backups should be an automated process and therefore, there can not be a
person to type a ssh(1) password when they are done.

The solution is to use a password less ssh(1) private key.  Please see
ssh-keygen(1) for details about how to use it.

The procedure to install a key is something like:

    ssh-keygen -t rsa -f backup-key -C "backup key" -N ""
    cat backup-key.pub | ssh remotehost "cat >> .ssh/authorized_keys"

One problem with this system is that anyone owning the private key file can
connect using ssh on the remotehost and do anything he want.

Happily, ssh(1) provide a way to restrict commands executed on the remote
host.  In order to do this, edit the .ssh/authorized_keys on the remote host
and add the following before the key you have just copied (line breaks
introduced by an anti-slash are provided for readability):

    from="myhost.example.com",\
    command="/path/to/my/command",\
    no-pty,no-port-forwarding,no-x11-forwarding,no-agent-forwarding \
    ssh-rsa AAA...rest of public key...

Now, the private key only works from myhost.example.com to run the command
indicated.  This method can be used to limit the usage of the private key to
backup.

=head1 AUTOMATING EXECUTION

To automatically execute the backup, place B<ppsnapback> in your crontab(5).
For example:

    0  * * * * /path/to/ppsnapback my_backup_every_4_hours
    4 42 * * 0 /path/to/ppsnapback my_weekly_backup

See your crontab(1), cron(8) and crontab(5) local documentation for more
details.

=head1 ENVIRONMENT VARIABLE

=over

=item HOME

Used to deduce I<backup_dir> if not provided on the command line.

=back

=head1 FILES

=over

=item F<$HOME/backups/>

Default directory for I<backup_dir>.

=item F<I<backup_dir>/default-config>

Default configuration, sourced for every module.

=item F<I<backup_dir>/default-exclude>

Default exclude list, used for every module, using the rsync(1) format.

=item F<I<backup_dir>/I<module>/config>

Module configuration.

=item F<I<backup_dir>/I<module>/exclude>

Module exclude, using the rsync(1) format.

=item F<~/.ssh/authorized_keys>

Not a B<ppsnapback> file, but used by ssh(1) to authorize and limit
connections using private/public keys.

=back

=head1 BUGS

B<ppsnapback> does not read rsync(1) options passed by the ssh connection.
This means that any extra option added on one end should also be added at the
other end.  The variables SSH_ORIGINAL_COMMAND and SSH2_ORIGINAL_COMMAND could
be read to fix this issue, taking care to accept only reasonable options.

=head1 RESTRICTIONS

B<ppsnapback> does not handle spaces in I<module> and I<backup_dir> arguments
or in the I<privkey> and I<fakeroot> configuration variables.  Support for
spaces could be added easily but will clutter the source code with
double-quotes. The I<path> or I<remote> variables however accept spaces.

=head1 SEE ALSO

rsync(1), ssh(1), ssh-keygen(1), bash(1), fakeroot(1), crontab(5)

Mike Rubel's article "Easy Automated Snapshot-Style Backups with Linux and
Rsync" http://www.mikerubel.org/computers/rsync_snapshots/

=head1 AUTHOR

Nicolas Schodet, http://ni.fr.eu.org/ppsnapback

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 Nicolas Schodet.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.

=cut