taperole 1.5.5 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +3 -0
  3. data/.travis.yml +10 -0
  4. data/CHANGELOG.md +9 -0
  5. data/README.md +22 -5
  6. data/bin/tape +6 -0
  7. data/lib/tape/ansible_runner.rb +1 -0
  8. data/lib/tape/overwriter.rb +14 -0
  9. data/requirements.yml +1 -2
  10. data/roles/backend_install_essentials/meta/main.yml +1 -1
  11. data/roles/backend_install_essentials/tasks/main.yml +5 -0
  12. data/roles/backend_install_essentials/templates/gemrc.j2 +1 -0
  13. data/roles/postgres/meta/main.yml +2 -0
  14. data/roles/ruby/defaults/main.yml +11 -0
  15. data/roles/ruby/files/gemrc +5 -0
  16. data/{vendor/zzet.rbenv → roles/ruby}/tasks/apt_build_depends.yml +2 -3
  17. data/roles/ruby/tasks/main.yml +120 -0
  18. data/taperole.gemspec +1 -1
  19. data/templates/base/tape_vars.example.yml +17 -0
  20. data/test/base_docker_box/Dockerfile +38 -0
  21. data/test/rails/Dockerfile +34 -0
  22. data/test/rails/start_rails.sh +6 -0
  23. data/test/rails/tape_vars.yml +12 -0
  24. data/vars/defaults.yml +26 -0
  25. data/vendor/Stouts.backup/.bumpversion.cfg +6 -0
  26. data/vendor/Stouts.backup/.gitignore +1 -0
  27. data/vendor/Stouts.backup/.travis.yml +34 -0
  28. data/vendor/Stouts.backup/CONTRIBUTORS +7 -0
  29. data/vendor/Stouts.backup/LICENSE +21 -0
  30. data/vendor/Stouts.backup/Makefile +20 -0
  31. data/vendor/Stouts.backup/README.md +169 -0
  32. data/vendor/Stouts.backup/defaults/main.yml +93 -0
  33. data/vendor/Stouts.backup/meta/.galaxy_install_info +1 -0
  34. data/vendor/Stouts.backup/meta/main.yml +16 -0
  35. data/vendor/Stouts.backup/tasks/backup.yml +10 -0
  36. data/vendor/Stouts.backup/tasks/configure.yml +53 -0
  37. data/vendor/Stouts.backup/tasks/install.deb.yml +19 -0
  38. data/vendor/Stouts.backup/tasks/main.yml +5 -0
  39. data/vendor/Stouts.backup/tasks/remove.yml +12 -0
  40. data/vendor/Stouts.backup/templates/conf.j2 +78 -0
  41. data/vendor/Stouts.backup/templates/cron.j2 +10 -0
  42. data/vendor/Stouts.backup/templates/duply.sh.j2 +2240 -0
  43. data/vendor/Stouts.backup/templates/exclude.j2 +3 -0
  44. data/vendor/Stouts.backup/templates/logrotate.j2 +14 -0
  45. data/vendor/Stouts.backup/templates/post.j2 +14 -0
  46. data/vendor/Stouts.backup/templates/pre.j2 +26 -0
  47. data/vendor/Stouts.backup/templates/restore.j2 +29 -0
  48. data/vendor/Stouts.backup/test.yml +6 -0
  49. metadata +42 -26
  50. data/id_rsa_sb_basebox +0 -27
  51. data/vendor/zzet.rbenv/.kitchen.yml +0 -40
  52. data/vendor/zzet.rbenv/.travis.yml +0 -15
  53. data/vendor/zzet.rbenv/README.md +0 -100
  54. data/vendor/zzet.rbenv/defaults/main.yml +0 -20
  55. data/vendor/zzet.rbenv/files/default-gems +0 -1
  56. data/vendor/zzet.rbenv/files/gemrc +0 -4
  57. data/vendor/zzet.rbenv/meta/.galaxy_install_info +0 -1
  58. data/vendor/zzet.rbenv/meta/main.yml +0 -27
  59. data/vendor/zzet.rbenv/role.yml +0 -10
  60. data/vendor/zzet.rbenv/tasks/homebrew_build_depends.yml +0 -12
  61. data/vendor/zzet.rbenv/tasks/main.yml +0 -226
  62. data/vendor/zzet.rbenv/tasks/pacman_build_depends.yml +0 -2
  63. data/vendor/zzet.rbenv/tasks/yum_build_depends.yml +0 -15
  64. data/vendor/zzet.rbenv/test/integration/default/serverspec/rbenv_spec.rb +0 -26
  65. data/vendor/zzet.rbenv/test/integration/helpers/serverspec/spec_helper.rb +0 -10
  66. data/vendor/zzet.rbenv/test/integration/site.yml +0 -8
  67. data/vendor/zzet.rbenv/vars/user.yml +0 -2
  68. /data/{vendor/zzet.rbenv → roles/ruby}/files/vars +0 -0
  69. /data/{vendor/zzet.rbenv → roles/ruby}/handlers/main.yml +0 -0
  70. /data/{vendor/zzet.rbenv → roles/ruby}/templates/rbenv.sh.j2 +0 -0
  71. /data/{vendor/zzet.rbenv → roles/ruby}/vars/main.yml +0 -0
  72. /data/{vendor/zzet.rbenv → roles/ruby}/vars/system.yml +0 -0
@@ -0,0 +1,2240 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ ###############################################################################
4
+ # duply (grown out of ftplicity), is a shell front end to duplicity that #
5
+ # simplifies the usage by managing settings for backup jobs in profiles. #
6
+ # It supports executing multiple commands in a batch mode to enable single #
7
+ # line cron entries and executes pre/post backup scripts. #
8
+ # Since version 1.5.0 all duplicity backends are supported. Hence the name #
9
+ # changed from ftplicity to duply. #
10
+ # See http://duply.net or http://ftplicity.sourceforge.net/ for more info. #
11
+ # (c) 2006 Christiane Ruetten, Heise Zeitschriften Verlag, Germany #
12
+ # (c) 2008-2014 Edgar Soldin (changes since version 1.3) #
13
+ ###############################################################################
14
+ # LICENSE: #
15
+ # This program is licensed under GPLv2. #
16
+ # Please read the accompanying license information in gpl.txt. #
17
+ ###############################################################################
18
+ # TODO/IDEAS/KNOWN PROBLEMS:
19
+ # - possibility to restore time frames (incl. deleted files)
20
+ # realizable by listing each backup and restore from
21
+ # oldest to the newest, problem: not performant
22
+ # - search file in all backups function and show available
23
+ # versions with backups date (list old avail since 0.6.06)
24
+ # - edit profile opens conf file in vi
25
+ # - implement log-fd interpretation
26
+ # - add a duplicity option check against the options pending
27
+ # deprecation since 0.5.10 namely --time-separator
28
+ # --short-filenames
29
+ # --old-filenames
30
+ # - add 'exclude_<command>' list usage eg. exclude_verify
31
+ # - featreq 25: a download/install duplicity option
32
+ # - hint on install software if a piece is missing
33
+ # - import/export profile from/to .tgz function !!!
34
+ #
35
+ #
36
+ # CHANGELOG:
37
+ # 1.9.1 (13.10.2014)
38
+ # - export CMD_ERR now for scripts to detect if CMD_PREV failed/succeeded
39
+ # - bugfix: CMD_PREV contained command even if it was skipped
40
+ #
41
+ # 1.9.0 (24.8.2014)
42
+ # - bugfix: env vars were not exported when external script was executable
43
+ # - rework GPG_KEY handling, allow virtually anything now (uid, keyid etc.)
44
+ # see gpg manpage, section "How to specify a user ID"
45
+ # let gpg complain when the delivered values are invalid for whatever reason
46
+ # - started to rework tmp space checking, exposed folder & writable check
47
+ # TODO: reimplement enough file space available checking
48
+ #
49
+ # 1.8.0 (13.7.2014)
50
+ # - add command verifyPath to expose 'verify --file-to-restore' action
51
+ # - add time parameter support to verify command
52
+ # - add section time formats to usage output
53
+ #
54
+ # 1.7.4 (24.6.2014)
55
+ # - remove ubuntu one support, service is discontinued
56
+ # - featreq 31: add authenticated swift (contributed by Justus Seifert)
57
+ #
58
+ # 1.7.3 (3.4.2014)
59
+ # - bugfix: test routines, gpg2 asked for passphrase although GPG_PW was set
60
+ #
61
+ # 1.7.2 (1.4.2014 "April,April")
62
+ # - bugfix: debian Bug#743190 "duply no longer allows restoration without
63
+ # gpg passphrase in conf file"
64
+ # GPG_AGENT_INFO env var is now needed to trigger --use-agent
65
+ # - bugfix: gpg keyenc test routines didn't work if GPG_PW was not set
66
+ #
67
+ # 1.7.1 (30.3.2014)
68
+ # - bugfix: purge-* commands renamed to purgeFull, purgeIncr due to
69
+ # incompatibility with new minus batch separator
70
+ #
71
+ # 1.7.0 (20.3.2014)
72
+ # - disabled gpg key id plausibility check, too many valid possibilities
73
+ # - featreq 7 "Halt if precondition fails":
74
+ # added and(+), or(-) batch command(separator) support
75
+ # - featreq 26 "pre/post script with shebang line":
76
+ # if a script is flagged executable it's executed in a subshell
77
+ # now as opposed to sourced to bash, which is the default
78
+ # - bugfix: do not check if dpbx, swift credentials are set anymore
79
+ # - bugfix: properly escape profile name, archdir if used as arguments
80
+ # - add DUPL_PRECMD conf setting for use with e.g. trickle
81
+ #
82
+ # 1.6.0 (1.1.2014)
83
+ # - support gs backend
84
+ # - support dropbox backend
85
+ # - add gpg-agent support to gpg test routines
86
+ # - autoenable --use-agent if passwords were not defined in config
87
+ # - GPG_OPTS are now honored everywhere, keyrings or complete gpg
88
+ # homedir can thus be configured to be located anywhere
89
+ # - always import both secret and public key if avail from config profile
90
+ # - new explanatory comments in initial exclude file
91
+ # - bugfix 7: Duply only imports one key at a time
92
+ #
93
+ # 1.5.11 (19.07.2013)
94
+ # - purge-incr command for remove-all-inc-of-but-n-full feature added
95
+ # patch provided by Moritz Augsburger, thanks!
96
+ # - documented version command in man page
97
+ #
98
+ # 1.5.10 (26.03.2013)
99
+ # - minor indent and documentation fixes
100
+ # - bugfix: exclude filter failed on ubuntu, mawk w/o posix char class support
101
+ # - bugfix: fix url_decoding generally and for python3
102
+ # - bugfix 3609075: wrong script results in status line (thx David Epping)
103
+ #
104
+ # 1.5.9 (22.11.2012)
105
+ # - bugfix 3588926: filter --exclude* params for restore/fetch ate too much
106
+ # - restore/fetch now also ignores --include* or --exclude='foobar'
107
+ #
108
+ # 1.5.8 (26.10.2012)
109
+ # - bugfix 3575487: implement proper cloud files support
110
+ #
111
+ # 1.5.7 (10.06.2012)
112
+ # - bugfix 3531450: Cannot use space in target URL (file:///) anymore
113
+ #
114
+ # 1.5.6 (24.5.2012)
115
+ # - commands purge, purge-full have no default value anymore for security
116
+ # reasons; instead max value can be given via cmd line or must be set
117
+ # in profile; else an error is shown.
118
+ # - minor man page modifications
119
+ #
120
+ # versioning scheme will be simplified to [major].[minor].[patch] version
121
+ # with the next version raise
122
+ #
123
+ # 1.5.5.5 (4.2.2012)
124
+ # - bugfix 3479605: SEL context confused profile folder's permission check
125
+ # - colon ':' in url passphrase got ignored, added python driven url_decoding
126
+ # for user & pass to better process special chars
127
+ #
128
+ # 1.5.5.4 (16.10.2011)
129
+ # - bugfix 3421268: SFTP passwords from conf ignored and always prompted for
130
+ # - add support for separate sign passphrase (needs duplicity 0.6.14+)
131
+ #
132
+ # 1.5.5.3 (1.10.2011)
133
+ # - bugfix 3416690: preview threw echo1 error
134
+ # - fix unknown cmds error usage & friends if more than 2 params were given
135
+ #
136
+ # 1.5.5.2 (23.9.2011)
137
+ # - bugfix 3409643: ssh key auth did ask for passphrase (--ssh-askpass ?)
138
+ # - bugfix: mawk does not support \W and did not split multikey definitions
139
+ # - all parameters should survive single (') and double (") quotes now
140
+ #
141
+ # 1.5.5.1 (7.6.2011)
142
+ # - featreq 3311881: add ftps as supported by duplicity 0.6.13 (thx mape2k)
143
+ # - bugfix 3312208: signing detection broke symmetric gpg test routine
144
+ #
145
+ # 1.5.5 (2.5.2011)
146
+ # - bugfix: fetch problem with space char in path, escape all params
147
+ # containing non word chars
148
+ # - list available profiles, if given profile cannot be found
149
+ # - added --use-agent configuration hint
150
+ # - bugfix 3174133: --exclude* params in conf DUPL_PARAMS broke
151
+ # fetch/restore
152
+ # - version command now prints out 'using installed' info
153
+ # - featreq 3166169: autotrust imported keys, based on code submitted by
154
+ # Martin Ellis - imported keys are now automagically trusted ultimately
155
+ # - new txt2man feature to create manpages for package maintainers
156
+ #
157
+ # 1.5.4.2 (6.1.2011)
158
+ # - new command changelog
159
+ # - bugfix 3109884: freebsd awk segfaulted on printf '%*', use print again
160
+ # - bugfix: freebsd awk hangs on 'awk -W version'
161
+ # - bugfix 3150244: mawk does not know '--version'
162
+ # - minor help text improvements
163
+ # - new env vars CMD_PREV,CMD_NEXT replacing CMD env var for scripts
164
+ #
165
+ # 1.5.4.1 (4.12.2010)
166
+ # - output awk, python, bash version now in prolog
167
+ # - shebang uses /usr/bin/env now for freebsd compatibility,
168
+ # bash not in /bin/bash
169
+ # - new --disable-encryption parameter,
170
+ # to override profile encr settings for one run
171
+ # - added exclude-if-present setting to conf template
172
+ # - bug 3126972: GPG_PW only needed for signing/symmetric encryption
173
+ # (even though duplicity still needs it)
174
+ #
175
+ # 1.5.4 (15.11.2010)
176
+ # - as of 1.5.3 already, new ARCH_DIR config option
177
+ # - multiple key support
178
+ # - ftplicity-Feature Requests-2994929: separate encryption and signing key
179
+ # - key signing of symmetric encryption possible (duplicity patch committed)
180
+ # - gpg tests disable switch
181
+ # - gpg tests now previewable and more intelligent
182
+ #
183
+ # 1.5.3 (1.11.2010)
184
+ # - bugfix 3056628: improve busybox compatibility, grep did not have -m param
185
+ # - bugfix 2995408: allow empty password for PGP key
186
+ # - bugfix 2996459: Duply erroneously escapes '-' symbol in username
187
+ # - url_encode function is now pythonized
188
+ # - rsync uses FTP_PASSWORD now if duplicity 0.6.10+ , else issue warning
189
+ # - feature 3059262: Make pre and post aware of parameters,
190
+ # internal parameters + CMD of pre or post
191
+ #
192
+ # 1.5.2.3 (16.4.2010)
193
+ # - bugfix: date again, should now work virtually anywhere
194
+ #
195
+ # 1.5.2.2 (3.4.2010)
196
+ # - minor bugfix: duplicity 0.6.8b version string now parsable
197
+ # - added INSTALL.txt
198
+ #
199
+ # 1.5.2.1 (23.3.2010)
200
+ # - bugfix: date formatting is awked now and should work on all platforms
201
+ #
202
+ # 1.5.2 (2.3.2010)
203
+ # - bugfix: errors print to STD_ERR now, failed tasks print an error message
204
+ # - added --name=duply_<profile> for duplicity 0.6.01+ to name cache folder
205
+ # - simplified & cleaned profileless commands, removed second instance
206
+ # - generalized separator time routines
207
+ # - added support for --no-encryption (GPG_KEY='disabled'), see conf examples
208
+ # - minor fixes
209
+ #
210
+ # 1.5.1.5 (5.2.2010)
211
+ # - bugfix: added special handling of credentials for rsync, imap(s)
212
+ #
213
+ # 1.5.1.4 (7.1.2010)
214
+ # - bugfix: nsecs defaults now to zeroes if date does not deliver [0-9]{9}
215
+ # - check if ncftp binary is available if url protocol is ftp
216
+ # - bugfix: duplicity output is now printed to screen directly to resolve
217
+ # 'mem alloc problem' bug report
218
+ # - bugfix: passwords will not be in the url anymore to solve the 'duply shows
219
+ # sensitive data in process listing' bug report
220
+ #
221
+ # 1.5.1.3 (24.12.2009) 'merry xmas'
222
+ # - bugfix: gpg pass now apostrophed to allow space and friends
223
+ # - bugfix: credentials are now url encoded to allow special chars in them
224
+ # a note about url encoding has been added to the conf template
225
+ #
226
+ # 1.5.1.2 (1.11.2009)
227
+ # - bugfix: open parenthesis in password broke duplicity execution
228
+ # - bugfix: ssh/scp backend does not always need credentials e.g. key auth
229
+ #
230
+ # 1.5.1.1 (21.09.2009)
231
+ # - bugfix: fixed s3[+http] TARGET_PASS not needed routine
232
+ # - bugfix: TYPO in duply 1.5.1 prohibited the use of /etc/duply
233
+ # see https://sourceforge.net/tracker/index.php?func=detail&
234
+ # aid=2864410&group_id=217745&atid=1041147
235
+ #
236
+ # 1.5.1 (21.09.2009) - duply (fka. ftplicity)
237
+ # - first things first: ftplicity (being able to support all backends since
238
+ # some time) will be called duply (fka. ftplicity) from now on. The addendum
239
+ # is for the time being to circumvent confusion.
240
+ # - bugfix: exit code is 1 (error) not 0 (success), if at least on duplicity
241
+ # command failed
242
+ # - s3[+http] now supported natively by translating user/pass to access_key/
243
+ # secret_key environment variables needed by duplicity s3 boto backend
244
+ # - bugfix: additional output lines do not confuse version check anymore
245
+ # - list command supports now age parameter (patch by stefan on feature
246
+ # request tracker)
247
+ # - bugfix: option/param pairs are now correctly passed on to duplicity
248
+ # - bugfix: s3[+http] needs no TARGET_PASS if command is read only
249
+ #
250
+ # 1.5.0.2 (31.07.1009)
251
+ # - bugfix: insert password in target url didn't work with debian mawk
252
+ # related to previous bug report
253
+ #
254
+ # 1.5.0.1 (23.07.2009)
255
+ # - bugfix: gawk gensub dependency raised an error on debian's default mawk
256
+ # replaced with match/substr command combination (bug report)
257
+ # https://sf.net/tracker/?func=detail&atid=1041147&aid=2825388&
258
+ # group_id=217745
259
+ #
260
+ # 1.5.0 (01.07.2009)
261
+ # - removed ftp limitation, all duplicity backends should work now
262
+ # - bugfix: date for separator failed on openwrt busybox date, added a
263
+ # detecting workaround, milliseconds are not available w/ busybox date
264
+ #
265
+ # 1.4.2.1 (14.05.2009)
266
+ # - bugfix: free temp space detection failed with lvm, fixed awk parse routine
267
+ #
268
+ # 1.4.2 (22.04.2009)
269
+ # - gpg keys are now exported as gpgkey.[id].asc , the suffix reflects the
270
+ # armored ascii nature, the id helps if the key is switched for some reason
271
+ # im/export routines are updated accordingly (import is backward compatible
272
+ # to the old profile/gpgkey files)
273
+ # - profile argument is treated as path if it contains slashes
274
+ # (for details see usage)
275
+ # - non-ftplicity options (all but --preview currently) are now passed
276
+ # on to duplicity
277
+ # - removed need for stat in secure_conf, it is ls based now
278
+ # - added profile folder readable check
279
+ # - added gpg version & home info output
280
+ # - awk utility availability is now checked, because it was mandatory already
281
+ # - tmp space is now checked on writability and space requirement
282
+ # test fails on less than 25MB or configured $VOLSIZE,
283
+ # test warns if there is less than two times $VOLSIZE because
284
+ # that's required for --asynchronous-upload option
285
+ # - gpg functionality is tested now before executing duplicity
286
+ # test drive contains encryption, decryption, comparison, cleanup
287
+ # this is meant to detect non trusted or other gpg errors early
288
+ # - added possibility of doing symmetric encryption with duplicity
289
+ # set GPG_KEY="" or simply comment it out
290
+ # - added hints in config template on the depreciation of
291
+ # --short-filenames, --time-separator duplicity options
292
+ #
293
+ # new versioning scheme 1.4.2b => 1.4.2,
294
+ # beta b's are replaced by a patch count number e.g. 1.4.2.1 will be assigned
295
+ # to the first bug fixing version and 1.4.2.2 to the second and so on
296
+ # also the releases will now have a release date formatted (Day.Month.Year)
297
+ #
298
+ # 1.4.1b1 - bugfix: ftplicity changed filesystem permission of a folder
299
+ # named exactly as the profile if existing in executing dir
300
+ # - improved plausibility checking of config and profile folder
301
+ # - secure_conf only acts if needed and prints a warning now
302
+ #
303
+ # 1.4.1b - introduce status (duplicity collection-status) command
304
+ # - pre/post script output printed always now, not only on errors
305
+ # - new config parameter GPG_OPTS to pass gpg options
306
+ # added examples & comments to profile template conf
307
+ # - reworked separator times, added duration display
308
+ # - added --preview switch, to preview generated command lines
309
+ # - disabled MAX_AGE, MAX_FULL_BACKUPS, VERBOSITY in generated
310
+ # profiles because they have reasonable defaults now if not set
311
+ #
312
+ # 1.4.0b1 - bugfix: incr forces incremental backups on duplicity,
313
+ # therefore backup translates to pre_bkp_post now
314
+ # - bugfix: new command bkp, which represents duplicity's
315
+ # default action (incr or full if full_if_older matches
316
+ # or no earlier backup chain is found)
317
+ #
318
+ # new versioning scheme 1.4 => 1.4.0, added new minor revision number
319
+ # this is meant to slow down the rapid version growing but still keep
320
+ # versions cleanly separated.
321
+ # only additional features will raise the new minor revision number.
322
+ # all releases start as beta, each bugfix release will raise the beta
323
+ # count, usually new features arrive before a version 'ripes' to stable
324
+ #
325
+ # 1.4.0b
326
+ # 1.4b - added startup info on version, time, selected profile
327
+ # - added time output to separation lines
328
+ # - introduced: command purge-full implements duplicity's
329
+ # remove-all-but-n-full functionality (patch by unknown),
330
+ # uses config variable $MAX_FULL_BACKUPS (default = 1)
331
+ # - purge config var $MAX_AGE defaults to 1M (month) now
332
+ # - command full does not execute pre/post anymore
333
+ # use batch command pre_full_post if needed
334
+ # - introduced batch mode cmd1_cmd2_etc
335
+ # (in turn removed the bvp command)
336
+ # - unknown/undefined command issues a warning/error now
337
+ # - bugfix: version check works with 0.4.2 and older now
338
+ # 1.3b3 - introduced pre/post commands to execute/debug scripts
339
+ # - introduced bvp (backup, verify, purge)
340
+ # - bugfix: removed need for awk gensub, now mawk compatible
341
+ # 1.3b2 - removed pre/post need executable bit set
342
+ # - profiles now under ~/.ftplicity as folders
343
+ # - root can keep profiles in /etc/ftplicity, folder must be
344
+ # created by hand, existing profiles must be moved there
345
+ # - removed ftplicity in path requirement
346
+ # - bugfix: bash < v.3 did not know '=~'
347
+ # - bugfix: purge works again
348
+ # 1.3 - introduces multiple profiles support
349
+ # - modified some script errors/docs
350
+ # - reordered gpg key check import routine
351
+ # - added 'gpg key id not set' check
352
+ # - added error_gpg (adds how to setup gpg key howto)
353
+ # - bugfix: duplicity 0.4.4RC4+ parameter syntax changed
354
+ # - duplicity_version_check routine introduced
355
+ # - added time separator, shortnames, volsize, full_if_older
356
+ # duplicity options to config file (inspired by stevie
357
+ # from http://weareroot.de)
358
+ # 1.1.1 - bugfix: encryption reactivated
359
+ # 1.1 - introduced config directory
360
+ # 1.0 - first release
361
+ ###############################################################################
362
+
363
+
364
+ # important definitions #######################################################
365
+
366
+ ME_LONG="$0"
367
+ ME="$(basename $0)"
368
+ ME_NAME="${ME%%.*}"
369
+ ME_VERSION="1.9.1"
370
+ ME_WEBSITE="http://duply.net"
371
+
372
+ # default config values
373
+ DEFAULT_SOURCE='/path/of/source'
374
+ DEFAULT_TARGET='scheme://user[:password]@host[:port]/[/]path'
375
+ DEFAULT_TARGET_USER='_backend_username_'
376
+ DEFAULT_TARGET_PASS='_backend_password_'
377
+ DEFAULT_GPG_KEY='_KEY_ID_'
378
+ DEFAULT_GPG_PW='_GPG_PASSWORD_'
379
+
380
+ # function definitions ##########################
381
+ function set_config { # sets config vars
382
+ local CONFHOME_COMPAT="$HOME/.ftplicity"
383
+ local CONFHOME_ETC_COMPAT="/etc/ftplicity"
384
+ local CONFHOME_ETC="{{backup_home}}"
385
+ local CONFHOME="{{backup_home}}"
386
+
387
+ # confdir can be delivered as path (must contain /)
388
+ if [ `echo $FTPLCFG | grep /` ] ; then
389
+ CONFDIR=$(readlink -f $FTPLCFG 2>/dev/null || \
390
+ ( echo $FTPLCFG|grep -v '^/' 1>/dev/null 2>&1 \
391
+ && echo $(pwd)/${FTPLCFG} ) || \
392
+ echo ${FTPLCFG})
393
+ # or DEFAULT in home/.duply folder (NEW)
394
+ elif [ -d "${CONFHOME}" ]; then
395
+ CONFDIR="${CONFHOME}/${FTPLCFG}"
396
+ # or in home/.ftplicity folder (OLD)
397
+ elif [ -d "${CONFHOME_COMPAT}" ]; then
398
+ CONFDIR="${CONFHOME_COMPAT}/${FTPLCFG}"
399
+ warning_oldhome "${CONFHOME_COMPAT}" "${CONFHOME}"
400
+ # root can put profiles under /etc/duply (NEW) if path exists
401
+ elif [ -d "${CONFHOME_ETC}" ] && [ "$EUID" -eq 0 ]; then
402
+ CONFDIR="${CONFHOME_ETC}/${FTPLCFG}"
403
+ # root can keep profiles under /etc/ftplicity (OLD) if path exists
404
+ elif [ -d "${CONFHOME_ETC_COMPAT}" ] && [ "$EUID" -eq 0 ]; then
405
+ CONFDIR="${CONFHOME_ETC_COMPAT}/${FTPLCFG}"
406
+ warning_oldhome "${CONFHOME_ETC_COMPAT}" "${CONFHOME_ETC}"
407
+ # hmm no profile folder there, then use default for error later
408
+ else
409
+ CONFDIR="${CONFHOME}/${FTPLCFG}" # continue, will fail later in main
410
+ fi
411
+
412
+ # remove trailing slash, get profile name etc.
413
+ CONFDIR="${CONFDIR%/}"
414
+ NAME="${CONFDIR##*/}"
415
+ CONF="$CONFDIR/conf"
416
+ PRE="$CONFDIR/pre"
417
+ POST="$CONFDIR/post"
418
+ EXCLUDE="$CONFDIR/exclude"
419
+ KEYFILE="$CONFDIR/gpgkey.asc"
420
+
421
+ }
422
+
423
+ {% raw %}
424
+
425
+ function version_info { # print version information
426
+ cat <<END
427
+ $ME version $ME_VERSION
428
+ ($ME_WEBSITE)
429
+ END
430
+ }
431
+
432
+ function version_info_using {
433
+ cat <<END
434
+ $(version_info)
435
+
436
+ $(using_info)
437
+ END
438
+ }
439
+
440
+ function using_info {
441
+ duplicity_version_get
442
+ # freebsd awk (--version only), debian mawk (-W version only), deliver '' so awk does not wait for input
443
+ AWK_VERSION=$((awk --version '' 2>/dev/null || awk -W version '' 2>/dev/null) | awk '/.+/{sub(/^[Aa][Ww][Kk][ \t]*/,"",$0);print $0;exit}')
444
+ PYTHON_VERSION=$(python -V 2>&1| awk '{print tolower($0);exit}')
445
+ GPG_INFO=`gpg --version 2>/dev/null| awk '/^gpg/{v=$1" "$3};/^Home/{print v" ("$0")"}'`
446
+ BASH_VERSION=$(bash --version | awk '/^GNU bash, version/{sub(/GNU bash, version[ ]+/,"",$0);print $0}')
447
+ echo -e "Using installed duplicity version ${DUPL_VERSION:-(not found)}${PYTHON_VERSION+, $PYTHON_VERSION}\
448
+ ${GPG_INFO:+, $GPG_INFO}${AWK_VERSION:+, awk '${AWK_VERSION}'}${BASH_VERSION:+, bash '${BASH_VERSION}'}."
449
+ }
450
+
451
+ function usage_info { # print usage information
452
+
453
+ cat <<USAGE_EOF
454
+ VERSION:
455
+ $(version_info)
456
+
457
+ DESCRIPTION:
458
+ Duply deals as a wrapper for the mighty duplicity magic.
459
+ It simplifies running duplicity with cron or on command line by:
460
+
461
+ - keeping recurring settings in profiles per backup job
462
+ - enabling batch operations eg. backup_verify_purge
463
+ - executing pre/post scripts for every command
464
+ - precondition checking for flawless duplicity operation
465
+
466
+ For each backup job one configuration profile must be created.
467
+ The profile folder will be stored under '~/.${ME_NAME}/<profile>'
468
+ (where ~ is the current users home directory).
469
+ Hint:
470
+ If the folder '/etc/${ME_NAME}' exists, the profiles for the super
471
+ user root will be searched & created there.
472
+
473
+ USAGE:
474
+ first time usage (profile creation):
475
+ $ME <profile> create
476
+
477
+ general usage in single or batch mode (see EXAMPLES):
478
+ $ME <profile> <command>[[_|+|-]<command>[_|+|-]...] [<options> ...]
479
+
480
+ For batches the conditional separators can also be written as pseudo commands
481
+ and(+), or(-). See SEPARATORS for details.
482
+
483
+ Non $ME options are passed on to duplicity (see OPTIONS).
484
+ All conf parameters can also be defined in the environment instead.
485
+
486
+ PROFILE:
487
+ Indicated by a path or a profile name (<profile>), which is resolved
488
+ to '~/.${ME_NAME}/<profile>' (~ expands to environment variable \$HOME).
489
+
490
+ Superuser root can place profiles under '/etc/${ME_NAME}'. Simply create
491
+ the folder manually before running $ME as superuser.
492
+ Note:
493
+ Already existing profiles in root's profile folder will cease to work
494
+ unless there are moved to the new location manually.
495
+
496
+ example 1: $ME humbug backup
497
+
498
+ Alternatively a _path_ might be used e.g. useful for quick testing,
499
+ restoring or exotic locations. Shell expansion should work as usual.
500
+ Hint:
501
+ The path must contain at least one path separator '/',
502
+ e.g. './test' instead of only 'test'.
503
+
504
+ example 2: $ME ~/.${ME_NAME}/humbug backup
505
+
506
+ SEPARATORS:
507
+ _ (underscore)
508
+ neutral separator
509
+ + (plus sign), _and_
510
+ conditional AND
511
+ the next command will only be executed if the previous succeeded
512
+ - (minus sign), _or_
513
+ conditional OR
514
+ the next command will only be executed if the previous failed
515
+
516
+ example:
517
+ 'pre+bkp-verify_post' translates to 'pre_and_bkp_or_verify_post'
518
+
519
+ COMMANDS:
520
+ usage get usage help text
521
+
522
+ and/or pseudo commands for better batch cmd readability (see SEPARATORS)
523
+ create creates a configuration profile
524
+ backup backup with pre/post script execution (batch: pre_bkp_post),
525
+ full (if full_if_older matches or no earlier backup is found)
526
+ incremental (in all other cases)
527
+ pre/post execute '<profile>/$(basename "$PRE")', '<profile>/$(basename "$POST")' scripts
528
+ bkp as above but without executing pre/post scripts
529
+ full force full backup
530
+ incr force incremental backup
531
+ list [<age>]
532
+ list all files in backup (as it was at <age>, default: now)
533
+ status prints backup sets and chains currently in repository
534
+ verify [<age>] [--compare-data]
535
+ list files changed, since age if given
536
+ verifyPath <rel_path_in_bkp> <local_path> [<age>] [--compare-data]
537
+ list changes of a file or folder path in backup compared to a
538
+ local path, since age if given
539
+ restore <target_path> [<age>]
540
+ restore the complete backup to <target_path> [as it was at <age>]
541
+ fetch <src_path> <target_path> [<age>]
542
+ fetch single file/folder from backup [as it was at <age>]
543
+ purge [<max_age>] [--force]
544
+ list outdated backup files (older than \$MAX_AGE)
545
+ [use --force to actually delete these files]
546
+ purgeFull [<max_full_backups>] [--force]
547
+ list outdated backup files (\$MAX_FULL_BACKUPS being the number of
548
+ full backups and associated incrementals to keep, counting in
549
+ reverse chronological order)
550
+ [use --force to actually delete these files]
551
+ purgeIncr [<max_fulls_with_incrs>] [--force]
552
+ list outdated incremental backups (\$MAX_FULLS_WITH_INCRS being
553
+ the number of full backups which associated incrementals will be
554
+ kept, counting in reverse chronological order)
555
+ [use --force to actually delete these files]
556
+ cleanup [--force]
557
+ list broken backup chain files archives (e.g. after unfinished run)
558
+ [use --force to actually delete these files]
559
+
560
+ changelog print changelog / todo list
561
+ txt2man feature for package maintainers - create a manpage based on the
562
+ usage output. download txt2man from http://mvertes.free.fr/, put
563
+ it in the PATH and run '$ME txt2man' to create a man page.
564
+ version show version information of $ME and needed programs
565
+
566
+ OPTIONS:
567
+ --force passed to duplicity (see commands: purge, purge-full, cleanup)
568
+ --preview do nothing but print out generated duplicity command lines
569
+ --disable-encryption
570
+ disable encryption, overrides profile settings
571
+
572
+ TIME FORMATS:
573
+ For all time related parameters like age, max_age etc.
574
+ Refer to the duplicity manpage for all available formats. Here some examples:
575
+ 2002-01-25T07:00:00+02:00 (full date time format string)
576
+ 2002/3/5 (date string YYYY/MM/DD)
577
+ 12D (interval, 12 days ago)
578
+ 1h78m (interval, 1 hour 78 minutes ago)
579
+
580
+ PRE/POST SCRIPTS:
581
+ Useful internal duply variables will be readable in the scripts.
582
+ Some of interest may be
583
+
584
+ CONFDIR, SOURCE, TARGET_URL_<PROT|HOSTPATH|USER|PASS>,
585
+ GPG_<KEYS_ENC|KEY_SIGN|PW>, CMD_<PREV|NEXT>, CMD_ERR
586
+
587
+ The CMD_* variables were introduced to allow different actions according to
588
+ the command the scripts were attached to e.g. 'pre_bkp_post_pre_verify_post'
589
+ will call the pre script two times, with CMD_NEXT variable set to 'bkp'
590
+ on the first and to 'verify' on the second run.
591
+ CMD_ERR holds the exit code of the CMD_PREV .
592
+
593
+ EXAMPLES:
594
+ create profile 'humbug':
595
+ $ME humbug create (now edit the resulting conf file)
596
+ backup 'humbug' now:
597
+ $ME humbug backup
598
+ list available backup sets of profile 'humbug':
599
+ $ME humbug status
600
+ list and delete obsolete backup archives of 'humbug':
601
+ $ME humbug purge --force
602
+ restore latest backup of 'humbug' to /mnt/restore:
603
+ $ME humbug restore /mnt/restore
604
+ restore /etc/passwd of 'humbug' from 4 days ago to /root/pw:
605
+ $ME humbug fetch etc/passwd /root/pw 4D
606
+ (see "duplicity manpage", section TIME FORMATS)
607
+ a one line batch job on 'humbug' for cron execution:
608
+ $ME humbug backup_verify_purge --force
609
+
610
+ FILES:
611
+ in profile folder '~/.${ME_NAME}/<profile>' or '/etc/${ME_NAME}'
612
+ conf profile configuration file
613
+ pre,post pre/post scripts (see above for details)
614
+ gpgkey.*.asc exported GPG key files
615
+ exclude a globbing list of included or excluded files/folders
616
+ (see "duplicity manpage", section FILE SELECTION)
617
+
618
+ $(hint_profile)
619
+
620
+ SEE ALSO:
621
+ duplicity man page:
622
+ duplicity(1) or http://duplicity.nongnu.org/duplicity.1.html
623
+ USAGE_EOF
624
+ }
625
+
626
+ # to check call 'duply txt2man | man -l -'
627
+ function usage_txt2man {
628
+ usage_info | \
629
+ awk '/^^[^[:lower:][:space:]][^[:lower:]]+$/{gsub(/[^[:upper:]]/," ",$0)}{print}' |\
630
+ txt2man -t"$(toupper "${ME_NAME}")" -s1 -r"${ME_NAME}-${ME_VERSION}" -v'User Manuals'
631
+ }
632
+
633
+ function changelog {
634
+ cat $ME_LONG | awk '/^#####/{on=on+1}(on==3){sub(/^#( )?/,"",$0);print}'
635
+ }
636
+
637
+ function create_config {
638
+ if [ ! -d "$CONFDIR" ] ; then
639
+ mkdir -p "$CONFDIR" || error "Couldn't create config '$CONFDIR'."
640
+ # create initial config file
641
+ cat <<EOF >"$CONF"
642
+ # gpg encryption settings, simple settings:
643
+ # GPG_KEY='disabled' - disables encryption alltogether
644
+ # GPG_KEY='<key1>[,<key2>]'; GPG_PW='pass' - encrypt with keys,
645
+ # sign if secret key of key1 is available use GPG_PW for sign & decrypt
646
+ # Note: you can specify keys via all methods described in gpg manpage,
647
+ # section "How to specify a user ID", escape commas (,) via backslash (\)
648
+ # e.g. 'Mueller, Horst', 'Bernd' -> 'Mueller\, Horst, Bernd'
649
+ # as they are used to separate the entries
650
+ # GPG_PW='passphrase' - symmetric encryption using passphrase only
651
+ GPG_KEY='${DEFAULT_GPG_KEY}'
652
+ GPG_PW='${DEFAULT_GPG_PW}'
653
+ # gpg encryption settings in detail (extended settings)
654
+ # the above settings translate to the following more specific settings
655
+ # GPG_KEYS_ENC='<keyid1>[,<keyid2>,...]' - list of pubkeys to encrypt to
656
+ # GPG_KEY_SIGN='<keyid1>|disabled' - a secret key for signing
657
+ # GPG_PW='<passphrase>' - needed for signing, decryption and symmetric
658
+ # encryption. If you want to deliver different passphrases for e.g.
659
+ # several keys or symmetric encryption plus key signing you can use
660
+ # gpg-agent. Simply make sure that GPG_AGENT_INFO is set in environment.
661
+ # also see "A NOTE ON SYMMETRIC ENCRYPTION AND SIGNING" in duplicity manpage
662
+ # notes on en/decryption
663
+ # private key and passphrase will only be needed for decryption or signing.
664
+ # decryption happens on restore and incrementals (compare archdir contents).
665
+ # for security reasons it makes sense to separate the signing key from the
666
+ # encryption keys. https://answers.launchpad.net/duplicity/+question/107216
667
+ #GPG_KEYS_ENC='<pubkey1>,<pubkey2>,...'
668
+ #GPG_KEY_SIGN='<prvkey>'
669
+ # set if signing key passphrase differs from encryption (key) passphrase
670
+ # NOTE: available since duplicity 0.6.14, translates to SIGN_PASSPHRASE
671
+ #GPG_PW_SIGN='<signpass>'
672
+
673
+ # gpg options passed from duplicity to gpg process (default='')
674
+ # e.g. "--trust-model pgp|classic|direct|always"
675
+ # or "--compress-algo=bzip2 --bzip2-compress-level=9"
676
+ # or "--personal-cipher-preferences AES256,AES192,AES..."
677
+ # or "--homedir ~/.duply" - keep keyring and gpg settings duply specific
678
+ #GPG_OPTS=''
679
+
680
+ # disable preliminary tests with the following setting
681
+ #GPG_TEST='disabled'
682
+
683
+ # credentials & server address of the backup target (URL-Format)
684
+ # syntax is
685
+ # scheme://[user:password@]host[:port]/[/]path
686
+ # for details see duplicity manpage, section URL Format
687
+ # http://duplicity.nongnu.org/duplicity.1.html#sect8
688
+ # probably one out of
689
+ # # for cloudfiles backend user id is CLOUDFILES_USERNAME, password is
690
+ # # CLOUDFILES_APIKEY, you might need to set CLOUDFILES_AUTHURL manually
691
+ # cf+http://[user:password@]container_name
692
+ # dpbx:///some_dir
693
+ # file://[relative|/absolute]/local/path
694
+ # ftp[s]://user[:password]@other.host[:port]/some_dir
695
+ # gdocs://user[:password]@other.host/some_dir
696
+ # # for the google cloud storage (since duplicity 0.6.22)
697
+ # # user/password are GS_ACCESS_KEY_ID/GS_SECRET_ACCESS_KEY
698
+ # gs://bucket[/prefix]
699
+ # hsi://user[:password]@other.host/some_dir
700
+ # imap[s]://user[:password]@host.com[/from_address_prefix]
701
+ # mega://user[:password]@mega.co.nz/some_dir
702
+ # rsync://user[:password]@host.com[:port]::[/]module/some_dir
703
+ # # rsync over ssh (only keyauth)
704
+ # rsync://user@host.com[:port]/[relative|/absolute]_path
705
+ # # for the s3 user/password are AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
706
+ # s3://[user:password@]host/bucket_name[/prefix]
707
+ # s3+http://[user:password@]bucket_name[/prefix]
708
+ # # scp and sftp are aliases for the ssh backend
709
+ # ssh://user[:password]@other.host[:port]/[/]some_dir
710
+ # # for authenticated swift define TARGET_USER or SWIFT_USERNAME,
711
+ # # TARGET_PASS or SWIFT_PASSWORD, SWIFT_AUTHURL (mandatory, the path to
712
+ # # your identity service, omitting leads to an error with swift),
713
+ # # optionally SWIFT_AUTHVERSION (which defaults to "1")
714
+ # swift://container_name
715
+ # tahoe://alias/directory
716
+ # webdav[s]://user[:password]@other.host/some_dir
717
+ # ATTENTION: characters other than A-Za-z0-9.-_.~ in the URL have
718
+ # to be replaced by their url encoded pendants, see
719
+ # http://en.wikipedia.org/wiki/Url_encoding
720
+ # if you define the credentials as TARGET_USER, TARGET_PASS below
721
+ # duply will try to url_encode them for you if the need arises
722
+ TARGET='${DEFAULT_TARGET}'
723
+ # optionally the username/password can be defined as extra variables
724
+ # setting them here _and_ in TARGET results in an error
725
+ #TARGET_USER='${DEFAULT_TARGET_USER}'
726
+ #TARGET_PASS='${DEFAULT_TARGET_PASS}'
727
+
728
+ # base directory to backup
729
+ SOURCE='${DEFAULT_SOURCE}'
730
+
731
+ # a command that runs duplicity e.g.
732
+ # shape bandwidth use via trickle
733
+ # "trickle -s -u 640 -d 5120" # 5Mb up, 40Mb down"
734
+ #DUPL_PRECMD=""
735
+
736
+ # exclude folders containing exclusion file (since duplicity 0.5.14)
737
+ # Uncomment the following two lines to enable this setting.
738
+ #FILENAME='.duplicity-ignore'
739
+ #DUPL_PARAMS="\$DUPL_PARAMS --exclude-if-present '\$FILENAME'"
740
+
741
+ # Time frame for old backups to keep, Used for the "purge" command.
742
+ # see duplicity man page, chapter TIME_FORMATS)
743
+ #MAX_AGE=1M
744
+
745
+ # Number of full backups to keep. Used for the "purge-full" command.
746
+ # See duplicity man page, action "remove-all-but-n-full".
747
+ #MAX_FULL_BACKUPS=1
748
+
749
+ # Number of full backups for which incrementals will be kept for.
750
+ # Used for the "purge-incr" command.
751
+ # See duplicity man page, action "remove-all-inc-of-but-n-full".
752
+ #MAX_FULLS_WITH_INCRS=1
753
+
754
+ # activates duplicity --full-if-older-than option (since duplicity v0.4.4.RC3)
755
+ # forces a full backup if last full backup reaches a specified age, for the
756
+ # format of MAX_FULLBKP_AGE see duplicity man page, chapter TIME_FORMATS
757
+ # Uncomment the following two lines to enable this setting.
758
+ #MAX_FULLBKP_AGE=1M
759
+ #DUPL_PARAMS="\$DUPL_PARAMS --full-if-older-than \$MAX_FULLBKP_AGE "
760
+
761
+ # sets duplicity --volsize option (available since v0.4.3.RC7)
762
+ # set the size of backup chunks to VOLSIZE MB instead of the default 25MB.
763
+ # VOLSIZE must be number of MB's to set the volume size to.
764
+ # Uncomment the following two lines to enable this setting.
765
+ #VOLSIZE=50
766
+ #DUPL_PARAMS="\$DUPL_PARAMS --volsize \$VOLSIZE "
767
+
768
+ # verbosity of output (error 0, warning 1-2, notice 3-4, info 5-8, debug 9)
769
+ # default is 4, if not set
770
+ #VERBOSITY=5
771
+
772
+ # temporary file space. at least the size of the biggest file in backup
773
+ # for a successful restoration process. (default is '/tmp', if not set)
774
+ #TEMP_DIR=/tmp
775
+
776
+ # Modifies archive-dir option (since 0.6.0) Defines a folder that holds
777
+ # unencrypted meta data of the backup, enabling new incrementals without the
778
+ # need to decrypt backend metadata first. If empty or deleted somehow, the
779
+ # private key and it's password are needed.
780
+ # NOTE: This is confidential data. Put it somewhere safe. It can grow quite
781
+ # big over time so you might want to put it not in the home dir.
782
+ # default '~/.cache/duplicity/duply_<profile>/'
783
+ # if set '\${ARCH_DIR}/<profile>'
784
+ #ARCH_DIR=/some/space/safe/.duply-cache
785
+
786
+ # DEPRECATED setting
787
+ # sets duplicity --time-separator option (since v0.4.4.RC2) to allow users
788
+ # to change the time separator from ':' to another character that will work
789
+ # on their system. HINT: For Windows SMB shares, use --time-separator='_'.
790
+ # NOTE: '-' is not valid as it conflicts with date separator.
791
+ # ATTENTION: only use this with duplicity < 0.5.10, since then default file
792
+ # naming is compatible and this option is pending depreciation
793
+ #DUPL_PARAMS="\$DUPL_PARAMS --time-separator _ "
794
+
795
+ # DEPRECATED setting
796
+ # activates duplicity --short-filenames option, when uploading to a file
797
+ # system that can't have filenames longer than 30 characters (e.g. Mac OS 8)
798
+ # or have problems with ':' as part of the filename (e.g. Microsoft Windows)
799
+ # ATTENTION: only use this with duplicity < 0.5.10, later versions default file
800
+ # naming is compatible and this option is pending depreciation
801
+ #DUPL_PARAMS="\$DUPL_PARAMS --short-filenames "
802
+
803
+ # more duplicity command line options can be added in the following way
804
+ # don't forget to leave a separating space char at the end
805
+ #DUPL_PARAMS="\$DUPL_PARAMS --put_your_options_here "
806
+
807
+ EOF
808
+
809
+ # create initial exclude file
810
+ cat <<EOF >"$EXCLUDE"
811
+ # although called exclude, this file is actually a globbing file list
812
+ # duplicity accepts some globbing patterns, even including ones here
813
+ # here is an example, this incl. only 'dir/bar' except it's subfolder 'foo'
814
+ # - dir/bar/foo
815
+ # + dir/bar
816
+ # - **
817
+ # for more details see duplicity manpage, section File Selection
818
+ # http://duplicity.nongnu.org/duplicity.1.html#sect9
819
+
820
+ EOF
821
+
822
+ # Hints on first usage
823
+ cat <<EOF
824
+
825
+ Congratulations. You just created the profile '$FTPLCFG'.
826
+ The initial config file has been created as
827
+ '$CONF'.
828
+ You should now adjust this config file to your needs.
829
+
830
+ $(hint_profile)
831
+
832
+ EOF
833
+ fi
834
+
835
+ }
836
+
837
+ # used in usage AND create_config
838
+ function hint_profile {
839
+ cat <<EOF
840
+ IMPORTANT:
841
+ Copy the _whole_ profile folder after the first backup to a safe place.
842
+ It contains everything needed to restore your backups. You will need
843
+ it if you have to restore the backup from another system (e.g. after a
844
+ system crash). Keep access to these files restricted as they contain
845
+ _all_ informations (gpg data, ftp data) to access and modify your backups.
846
+
847
+ Repeat this step after _all_ configuration changes. Some configuration
848
+ options are crucial for restoration.
849
+
850
+ EOF
851
+ }
852
+
853
+ function separator {
854
+ echo "--- $@ ---"
855
+ }
856
+
857
+ function inform {
858
+ echo -e "\nINFO:\n\n$@\n"
859
+ }
860
+
861
+ function warning {
862
+ echo -e "\nWARNING:\n\n$@\n"
863
+ }
864
+
865
+ function warning_oldhome {
866
+ local old=$1 new=$2
867
+ warning " ftplicity changed name to duply since you created your profiles.
868
+ Please rename the old folder
869
+ '$old'
870
+ to
871
+ '$new'
872
+ and this warning will disappear.
873
+ If you decide not to do so profiles will _only_ work from the old location."
874
+ }
875
+
876
+ function error_print {
877
+ echo -e "$@" >&2
878
+ }
879
+
880
+ function error {
881
+ error_print "\nSorry. A fatal ERROR occured:\n\n$@\n"
882
+ exit -1
883
+ }
884
+
885
+ function error_gpg {
886
+ [ -n "$2" ] && local hint="\n $2\n\n "
887
+
888
+ error "$1
889
+
890
+ Hint${hint:+s}:
891
+ ${hint}Maybe you have not created a gpg key yet (e.g. gpg --gen-key)?
892
+ Don't forget the used _password_ as you will need it.
893
+ When done enter the 8 digit id & the password in the profile conf file.
894
+
895
+ The key id can be found doing a 'gpg --list-keys'. In the example output
896
+ below the key id would be FFFFFFFF for the public key.
897
+
898
+ pub 1024D/FFFFFFFF 2007-12-17
899
+ uid duplicity
900
+ sub 2048g/899FE27F 2007-12-17
901
+ "
902
+ }
903
+
904
+ function error_gpg_key {
905
+ local KEY_ID="$1"
906
+ local KIND="$2"
907
+ error_gpg "${KIND} gpg key '${KEY_ID}' cannot be found." \
908
+ "Doublecheck if the above key is listed by 'gpg --list-keys' or available
909
+ as gpg key file '$(basename "$(gpg_keyfile "${KEY_ID}")")' in the profile folder.
910
+ If not you can put it there and $ME will autoimport it on the next run.
911
+ Alternatively import it manually as the user you plan to run $ME with."
912
+ }
913
+
914
+ function error_gpg_test {
915
+ [ -n "$2" ] && local hint="\n $2\n\n "
916
+
917
+ error "$1
918
+
919
+ Hint${hint:+s}:
920
+ ${hint}This error means that gpg is probably misconfigured or not working
921
+ correctly. The error message above should help to solve the problem.
922
+ However, if for some reason $ME should misinterpret the situation you
923
+ can define GPG_TEST='disabled' in the conf file to bypass the test.
924
+ Please do not forget to report the bug in order to resolve the problem
925
+ in future versions of $ME.
926
+ "
927
+ }
928
+
929
+ function error_path {
930
+ error "$@
931
+ PATH='$PATH'
932
+ "
933
+ }
934
+
935
+ function error_to_string {
936
+ [ -n "$1" ] && [ "$1" -eq 0 ] && echo "OK" || echo "FAILED 'code $1'"
937
+ }
938
+
939
+ function duplicity_version_get {
940
+ var_isset DUPL_VERSION && return
941
+ DUPL_VERSION=`duplicity --version 2>&1 | awk '/^duplicity /{print $2; exit;}'`
942
+ #DUPL_VERSION='0.6.08b' #,0.4.4.RC4,0.6.08b
943
+ DUPL_VERSION_VALUE=0
944
+ DUPL_VERSION_AWK=$(awk -v v="$DUPL_VERSION" 'BEGIN{
945
+ if (match(v,/[^\.0-9]+[0-9]*$/)){
946
+ rest=substr(v,RSTART,RLENGTH);v=substr(v,0,RSTART-1);}
947
+ if (pos=match(rest,/RC([0-9]+)$/)) rc=substr(rest,pos+2)
948
+ split(v,f,"[. ]"); if(f[1]f[2]f[3]~/^[0-9]+$/) vvalue=f[1]*10000+f[2]*100+f[3]; else vvalue=0
949
+ print "#"v"_"rest"("rc"):"f[1]"-"f[2]"-"f[3]
950
+ print "DUPL_VERSION_VALUE=\047"vvalue"\047"
951
+ print "DUPL_VERSION_RC=\047"rc"\047"
952
+ print "DUPL_VERSION_SUFFIX=\047"rest"\047"
953
+ }')
954
+ eval "$DUPL_VERSION_AWK"
955
+ #echo -e ",$DUPL_VERSION,$DUPL_VERSION_VALUE,$DUPL_VERSION_RC,$DUPL_VERSION_SUFFIX,"
956
+ }
957
+
958
+ function duplicity_version_check {
959
+ if [ $DUPL_VERSION_VALUE -eq 0 ]; then
960
+ inform "duplicity version check failed (please report, this is a bug)"
961
+ elif [ $DUPL_VERSION_VALUE -le 404 ] && [ ${DUPL_VERSION_RC:-4} -lt 4 ]; then
962
+ error "The installed version $DUPL_VERSION is incompatible with $ME v$ME_VERSION.
963
+ You should upgrade your version of duplicity to at least v0.4.4RC4 or
964
+ use the older ftplicity version 1.1.1 from $ME_WEBSITE."
965
+ fi
966
+ }
967
+
968
+ function duplicity_version_ge {
969
+ [ "$DUPL_VERSION_VALUE" -ge "$1" ]
970
+ }
971
+
972
+ function duplicity_version_lt {
973
+ ! duplicity_version_ge "$1"
974
+ }
975
+
976
+ function run_script { # run pre/post scripts
977
+ local ERR=0
978
+ local SCRIPT="$1"
979
+ if [ ! -z "$PREVIEW" ] ; then
980
+ echo "$([ ! -x "$SCRIPT" ] && echo ". ")$SCRIPT"
981
+ elif [ -r "$SCRIPT" ] ; then
982
+ echo -n "Running '$SCRIPT' "
983
+ if [ -x "$SCRIPT" ]; then
984
+ OUT=$("$SCRIPT" 2>&1)
985
+ ERR=$?
986
+ else
987
+ OUT=$(. "$SCRIPT" 2>&1)
988
+ ERR=$?
989
+ fi
990
+ [ $ERR -eq "0" ] && echo "- OK" || echo "- FAILED (code $ERR)"
991
+ echo -en ${OUT:+"Output: $OUT\n"} ;
992
+ else
993
+ echo "Skipping n/a script '$SCRIPT'."
994
+ fi
995
+ return $ERR
996
+ }
997
+
998
+ function run_cmd {
999
+ # run or print escaped cmd string
1000
+ local CMD_ERR=0
1001
+ if [ -n "$PREVIEW" ]; then
1002
+ CMD_OUT=$( echo "$@ 2>&1" )
1003
+ CMD_MSG="-- Run cmd -- $CMD_MSG --\n$CMD_OUT"
1004
+ elif [ -n "$CMD_DISABLED" ]; then
1005
+ CMD_MSG="$CMD_MSG (DISABLED) - $CMD_DISABLED"
1006
+ else
1007
+ CMD_OUT=` eval "$@" 2>&1 `
1008
+ CMD_ERR=$?
1009
+ if [ "$CMD_ERR" = "0" ]; then
1010
+ CMD_MSG="$CMD_MSG (OK)"
1011
+ else
1012
+ CMD_MSG="$CMD_MSG (FAILED)"
1013
+ fi
1014
+ fi
1015
+ echo -e "$CMD_MSG"
1016
+ # reset
1017
+ unset CMD_DISABLED CMD_MSG
1018
+ return $CMD_ERR
1019
+ }
1020
+
1021
+ function qw { quotewrap "$@"; }
1022
+
1023
+ function quotewrap {
1024
+ local param="$@"
1025
+ # quote strings having non word chars (e.g. spaces)
1026
+ if echo "$param" | awk '/[^A-Za-z0-9_\.\-]/{exit 0}{exit 1}'; then
1027
+ echo "$param" | awk '{\
1028
+ gsub(/[\047]/,"\047\\\047\047",$0);\
1029
+ gsub(/[\042]/,"\047\\\042\047",$0);\
1030
+ print "\047"$0"\047"}'
1031
+ return
1032
+ fi
1033
+ echo $param
1034
+ }
1035
+
1036
+ function duplicity_params_global {
1037
+ # already done? return
1038
+ var_isset 'DUPL_PARAMS_GLOBAL' && return
1039
+ local DUPL_ARG_ENC
1040
+
1041
+ # use key only if set in config, else leave it to symmetric encryption
1042
+ if gpg_disabled; then
1043
+ local DUPL_PARAM_ENC='--no-encryption'
1044
+ else
1045
+ local DUPL_PARAM_ENC=$(gpg_prefix_keyset '--encrypt-key' "${GPG_KEYS_ENC_ARRAY[@]}")
1046
+ gpg_signing && local DUPL_PARAM_SIGN=$(gpg_prefix_keyset '--sign-key' "$GPG_KEY_SIGN")
1047
+ # interpret password settings
1048
+ var_isset 'GPG_PW' && DUPL_ARG_ENC="PASSPHRASE=$(qw "${GPG_PW}")"
1049
+ var_isset 'GPG_PW_SIGN' && DUPL_ARG_ENC="${DUPL_ARG_ENC} SIGN_PASSPHRASE=$(qw "${GPG_PW_SIGN}")"
1050
+ fi
1051
+
1052
+ local GPG_OPTS=${GPG_OPTS:+"--gpg-options $(qw "${GPG_OPTS}")"}
1053
+
1054
+ # set name for dupl archive folder, since 0.6.0
1055
+ if duplicity_version_ge 601; then
1056
+ local DUPL_ARCHDIR=''
1057
+ if var_isset 'ARCH_DIR'; then
1058
+ DUPL_ARCHDIR="--archive-dir $(qw "${ARCH_DIR}")"
1059
+ fi
1060
+ DUPL_ARCHDIR="${DUPL_ARCHDIR} --name $(qw "duply_${NAME}")"
1061
+ fi
1062
+
1063
+ DUPL_PARAMS_GLOBAL="${DUPL_ARCHDIR} ${DUPL_PARAM_ENC} \
1064
+ ${DUPL_PARAM_SIGN} --verbosity '${VERBOSITY:-4}' \
1065
+ ${GPG_OPTS}"
1066
+
1067
+ DUPL_VARS_GLOBAL="TMPDIR='$TEMP_DIR' \
1068
+ ${DUPL_ARG_ENC}"
1069
+ }
1070
+
1071
+ # filter the DUPL_PARAMS var from conf
1072
+ function duplicity_params_conf {
1073
+ # reuse cmd var from main loop
1074
+ ## in/exclude parameters are currently not supported on restores
1075
+ if [ "$cmd" = "fetch" ] || [ "$cmd" = "restore" ]; then
1076
+ # filter exclude params from fetch/restore
1077
+ echo "$DUPL_PARAMS" | awk '{gsub(/--(ex|in)clude[a-z-]*(([ \t]+|=)[^-][^ \t]+)?/,"");print}'
1078
+ return
1079
+ fi
1080
+
1081
+ echo "$DUPL_PARAMS"
1082
+ }
1083
+
1084
+ function duplify { # the actual wrapper function
1085
+ local PARAMSNOW DUPL_CMD DUPL_CMD_PARAMS
1086
+
1087
+ # put command (with params) first in duplicity parameters
1088
+ for param in "$@" ; do
1089
+ # split cmd from params (everything before splitchar --)
1090
+ if [ "$param" == "--" ] ; then
1091
+ PARAMSNOW=1
1092
+ else
1093
+ # wrap in quotes to protect from spaces
1094
+ [ ! $PARAMSNOW ] && \
1095
+ DUPL_CMD="$DUPL_CMD $(qw $param)" \
1096
+ || \
1097
+ DUPL_CMD_PARAMS="$DUPL_CMD_PARAMS $(qw $param)"
1098
+ fi
1099
+ done
1100
+
1101
+ # init global duplicity parameters same for all tasks
1102
+ duplicity_params_global
1103
+
1104
+ var_isset 'PREVIEW' && local RUN=echo || local RUN=eval
1105
+ $RUN ${DUPL_VARS_GLOBAL} ${BACKEND_PARAMS} \
1106
+ ${DUPL_PRECMD} duplicity $DUPL_CMD $DUPL_PARAMS_GLOBAL $(duplicity_params_conf)\
1107
+ $GPG_USEAGENT $DUPL_CMD_PARAMS ${PREVIEW:+}
1108
+
1109
+ local ERR=$?
1110
+ return $ERR
1111
+ }
1112
+
1113
+ function secureconf { # secure the configuration dir
1114
+ #PERMS=$(ls -la $(dirname $CONFDIR) | grep -e " $(basename $CONFDIR)\$" | awk '{print $1}')
1115
+ local PERMS="$(ls -la "$CONFDIR/." | awk 'NR==2{print $1}')"
1116
+ if [ "${PERMS/#drwx------*/OK}" != 'OK' ] ; then
1117
+ chmod u+rwX,go= "$CONFDIR"; local ERR=$?
1118
+ warning "The profile's folder
1119
+ '$CONFDIR'
1120
+ permissions are not safe ($PERMS). Secure them now. - ($(error_to_string $ERR))"
1121
+ fi
1122
+ }
1123
+
1124
+ # params are $1=timeformatstring (default like date output), $2=epoch seconds since 1.1.1970 (default now)
1125
+ function date_fix {
1126
+ local DEFAULTFORMAT='%a %b %d %H:%M:%S %Z %Y'
1127
+ # gnu date with -d @epoch
1128
+ date=$(date ${2:+-d @$2} ${1:++"$1"} 2> /dev/null) && \
1129
+ echo $date && return
1130
+ # date bsd,osx with -r epoch
1131
+ date=$(date ${2:+-r $2} ${1:++"$1"} 2> /dev/null) && \
1132
+ echo $date && return
1133
+ # date busybox with -d epoch -D %s
1134
+ date=$(date ${2:+-d $2 -D %s} ${1:++"$1"} 2> /dev/null) && \
1135
+ echo $date && return
1136
+ ## some date commands do not support giving a time w/o setting it systemwide (irix,solaris,others?)
1137
+ # python fallback
1138
+ date=$(python -c "import time;print time.strftime('${1:-$DEFAULTFORMAT}',time.localtime(${2}))" 2> /dev/null) && \
1139
+ echo $date && return
1140
+ # awk fallback
1141
+ date=$(awk "BEGIN{print strftime(\"${1:-$DEFAULTFORMAT}\"${2:+,$2})}" 2> /dev/null) && \
1142
+ echo $date && return
1143
+ # perl fallback
1144
+ date=$(perl -e "use POSIX qw(strftime);\$date = strftime(\"${1:-$DEFAULTFORMAT}\",localtime(${2}));print \"\$date\n\";" 2> /dev/null) && \
1145
+ echo $date && return
1146
+ # error
1147
+ echo "ERROR"
1148
+ return 1
1149
+ }
1150
+
1151
+ function nsecs {
1152
+ # only 9 digit returns, e.g. not all date(s) deliver nsecs
1153
+ local NSECS=$(date +%N 2> /dev/null | head -1 |grep -e "^[[:digit:]]\{9\}$")
1154
+ echo ${NSECS:-000000000}
1155
+ }
1156
+
1157
+ function nsecs_to_sec {
1158
+ echo $(($1/1000000000)).$(printf "%03d" $(($1/1000000%1000)) )
1159
+ }
1160
+
1161
+ function datefull_from_nsecs {
1162
+ date_from_nsecs $1 '%F %T'
1163
+ }
1164
+
1165
+ function date_from_nsecs {
1166
+ local FORMAT=${2:-%T}
1167
+ local TIME=$(nsecs_to_sec $1)
1168
+ local SECS=${TIME%.*}
1169
+ local DATE=$(date_fix "%T" ${SECS:-0})
1170
+ echo $DATE.${TIME#*.}
1171
+ }
1172
+
1173
+ function var_isset {
1174
+ if [ -z "$1" ]; then
1175
+ echo "ERROR: function var_isset needs a string as parameter"
1176
+ elif eval "[ \"\${$1}\" == 'not_set' ]" || eval "[ \"\${$1-not_set}\" != 'not_set' ]"; then
1177
+ return 0
1178
+ fi
1179
+ return 1
1180
+ }
1181
+
1182
+ function url_encode {
1183
+ # utilize python, silently do nothing on error - because no python no duplicity
1184
+ OUT=$(python -c "
1185
+ try: import urllib.request as urllib
1186
+ except ImportError: import urllib
1187
+ print(urllib.${2}quote('$1'));
1188
+ " 2>/dev/null ); ERR=$?
1189
+ [ "$ERR" -eq 0 ] && echo $OUT || echo $1
1190
+ }
1191
+
1192
+ function url_decode {
1193
+ # reuse function above with a simple string param hack
1194
+ url_encode "$1" "un"
1195
+ }
1196
+
1197
+ function toupper {
1198
+ echo "$@"|awk '$0=toupper($0)'
1199
+ }
1200
+
1201
+ function tolower {
1202
+ echo "$@"|awk '$0=tolower($0)'
1203
+ }
1204
+
1205
+ function isnumber {
1206
+ case "$*" in
1207
+ ''|*[!0-9]*) return 1;;
1208
+ *) return 0;;
1209
+ esac
1210
+ }
1211
+
1212
+ #function tmp_space {
1213
+ #
1214
+ # if ! isnumber $VOLSIZE; then
1215
+ # inform "failed to determine free space (please report, this is a bug)"
1216
+ # return
1217
+ # fi
1218
+ #
1219
+ # get free temp space
1220
+ # TEMP_FREE="$(df -P -k "$TEMP_DIR" 2>/dev/null | awk 'END{pos=(NF-2);if(pos>0) print $pos;}')"
1221
+ # # check for free space or FAIL
1222
+ # if [ $((${TEMP_FREE:-0}-${VOLSIZE:-0}*1024)) -lt 0-lt 0 ]; then
1223
+ # error "Temporary file space '$TEMP_DIR' free space is smaller ($((TEMP_FREE/1024))MB)
1224
+ #than one duplicity volume (${VOLSIZE}MB).
1225
+ #
1226
+ # Hint: Free space or change TEMP_DIR setting."
1227
+ #fi
1228
+ #
1229
+ #}
1230
+
1231
+ function gpg_disabled {
1232
+ echo "${GPG_KEY}" | grep -iq -e '^disabled$'
1233
+ }
1234
+
1235
+ # usage: join SEPARATOR "entry1" "entry2"
1236
+ function join {
1237
+ local SEP="$1" ENTRY OUT; shift;
1238
+ for ENTRY in "$@"; do
1239
+ ENTRY=${ENTRY//$SEP/\\$SEP}
1240
+ [ -z "$OUT" ] && OUT=$ENTRY || OUT="$OUT$SEP$ENTRY"
1241
+ done
1242
+ echo $OUT
1243
+ }
1244
+
1245
+ function gpg_signing {
1246
+ echo ${GPG_KEY_SIGN} | grep -v -q -e '^disabled$'
1247
+ }
1248
+
1249
+ # parameter key id, key_type
1250
+ function gpg_keyfile {
1251
+ local GPG_KEY=$(gpg_key_legalize $1) TYPE="$2"
1252
+ local KEYFILE="${KEYFILE//.asc/${GPG_KEY:+.$GPG_KEY}.asc}"
1253
+ echo "${KEYFILE//.asc/${TYPE:+.$(tolower $TYPE)}.asc}"
1254
+ }
1255
+
1256
+ # parameter key id
1257
+ function gpg_import {
1258
+ local i FILE FOUND=0 KEY_ID="$1" KEY_TYPE="$2" KEY_FP="" ERR=0
1259
+ # create a list of legacy key file names and current naming scheme
1260
+ # we always import pub and sec if they are avail in conf folder
1261
+ local KEYFILES=( "$CONFDIR/gpgkey" $(gpg_keyfile "$KEY_ID") \
1262
+ $(gpg_keyfile "$KEY_ID" PUB) $(gpg_keyfile "$KEY_ID" SEC))
1263
+
1264
+ # Try autoimport from existing old gpgkey files
1265
+ # and new gpgkey.XXX.asc files (since v1.4.2)
1266
+ # and even newer gpgkey.XXX.[pub|sec].asc
1267
+ for (( i = 0 ; i < ${#KEYFILES[@]} ; i++ )); do
1268
+ FILE=${KEYFILES[$i]}
1269
+ if [ -f "$FILE" ]; then
1270
+ FOUND=1
1271
+
1272
+ CMD_MSG="Import keyfile '$FILE' to keyring"
1273
+ run_cmd "$GPG" $GPG_OPTS --batch --import "$FILE"
1274
+ if [ "$?" != "0" ]; then
1275
+ warning "Import failed.${CMD_OUT:+\n$CMD_OUT}"
1276
+ ERR=1
1277
+ # continue with next
1278
+ continue
1279
+ fi
1280
+ fi
1281
+ done
1282
+
1283
+ if [ "$FOUND" -eq 0 ]; then
1284
+ warning "No keyfile for '$KEY_ID' found in profile\n'$CONFDIR'."
1285
+ fi
1286
+
1287
+ # try to set trust automagically
1288
+ CMD_MSG="Autoset trust of key '$KEY_ID' to ultimate"
1289
+ run_cmd echo $(gpg_fingerprint "$KEY_ID"):6: \| "$GPG" $GPG_OPTS --import-ownertrust --batch --logger-fd 1
1290
+ if [ "$?" = "0" ] && [ -z "$PREVIEW" ]; then
1291
+ # success on all levels, we're done
1292
+ return $ERR
1293
+ fi
1294
+
1295
+ # failover: user has to set trust manually
1296
+ echo -e "For $ME to work you have to set the trust level
1297
+ with the command \"trust\" to \"ultimate\" (5) now.
1298
+ Exit the edit mode of gpg with \"quit\"."
1299
+ CMD_MSG="Running gpg to manually edit key '$KEY_ID'"
1300
+ run_cmd sleep 5\; "$GPG" $GPG_OPTS --edit-key "$KEY_ID"
1301
+
1302
+ return $ERR
1303
+ }
1304
+
1305
+ # see 'How to specify a user ID' on gpg manpage
1306
+ function gpg_fingerprint {
1307
+ local PRINT=$("$GPG" $GPG_OPTS --fingerprint "$1" 2>&1|awk -F= 'NR==2{gsub(/ /,"",$2);$2=toupper($2); if ( $2 ~ /^[A-F0-9]+$/ && length($2) == 40 ) print $2; else exit 1}') \
1308
+ && [ -n "$PRINT" ] && echo $PRINT && return 0
1309
+ return 1
1310
+ }
1311
+
1312
+ function gpg_export_if_needed {
1313
+ local SUCCESS FILE KEY_TYPE
1314
+ local TMPFILE="$TEMP_DIR/${ME_NAME}.$$.$(date_fix %s).gpgexp"
1315
+ for KEY_ID in "$@"; do
1316
+ # check if already exported, do it if not
1317
+ for KEY_TYPE in PUB SEC; do
1318
+ FILE="$(gpg_keyfile "$KEY_ID" $KEY_TYPE)"
1319
+ if [ ! -f "$FILE" ] && eval gpg_$(tolower $KEY_TYPE)_avail \"$KEY_ID\"; then
1320
+ # exporting
1321
+ CMD_MSG="Export $KEY_TYPE key '$KEY_ID'"
1322
+ run_cmd $GPG $GPG_OPTS --armor --export"$(test "SEC" = "$KEY_TYPE" && echo -secret-keys)"" $(qw $KEY_ID) >> \"$TMPFILE\""
1323
+ CMD_ERR=$?
1324
+
1325
+ if [ "$CMD_ERR" = "0" ]; then
1326
+ CMD_MSG="Write file '"$(basename "$FILE")"'"
1327
+ run_cmd " mv \"$TMPFILE\" \"$FILE\""
1328
+ fi
1329
+
1330
+ if [ "$CMD_ERR" != "0" ]; then
1331
+ warning "Backup failed.${CMD_OUT:+\n$CMD_OUT}"
1332
+ else
1333
+ SUCCESS=1
1334
+ fi
1335
+
1336
+ # cleanup
1337
+ rm "$TMPFILE" 1>/dev/null 2>&1
1338
+ fi
1339
+ done
1340
+ done
1341
+
1342
+ [ -n "$SUCCESS" ] && inform "$ME exported new keys to your profile.
1343
+ You should backup your changed profile folder now and store it in a safe place."
1344
+ }
1345
+
1346
+ # replace all non-alnum chars with underscore (for file operations)
1347
+ function gpg_key_legalize {
1348
+ echo $* | awk '{gsub(/[^a-zA-Z0-9]/,"_",$0); print}'
1349
+ }
1350
+
1351
+ function gpg_key_cache {
1352
+ local RES
1353
+ local MODE=$1
1354
+ shift
1355
+ local PREFIX="GPG_KEY"
1356
+ local SUFFIX=$(gpg_key_legalize "$@")
1357
+ local KEYID="$*"
1358
+ local CACHE="${PREFIX}_${MODE}_${SUFFIX}"
1359
+ if [ "$MODE" = "RESET" ]; then
1360
+ eval unset ${PREFIX}_PUB_$SUFFIX ${PREFIX}_SEC_$SUFFIX
1361
+ return 255
1362
+ elif ! var_isset "$CACHE"; then
1363
+ if [ "$MODE" = "PUB" ]; then
1364
+ RES=$("$GPG" $GPG_OPTS --list-key "$KEYID" > /dev/null 2>&1; echo -n $?)
1365
+ elif [ "$MODE" = "SEC" ]; then
1366
+ RES=$("$GPG" $GPG_OPTS --list-secret-key "$KEYID" > /dev/null 2>&1; echo -n $?)
1367
+ else
1368
+ return 255
1369
+ fi
1370
+ eval $CACHE=$RES
1371
+ fi
1372
+ eval return \$$CACHE
1373
+ }
1374
+
1375
+ function gpg_pub_avail {
1376
+ gpg_key_cache PUB "$@"
1377
+ }
1378
+
1379
+ function gpg_sec_avail {
1380
+ gpg_key_cache SEC "$@"
1381
+ }
1382
+
1383
+ function gpg_key_format {
1384
+ echo $1 | grep -q '^[0-9a-fA-F]\{8\}$'
1385
+ }
1386
+
1387
+ #function gpg_split_keyset {
1388
+ # return
1389
+ # awk "BEGIN{ keys=toupper(\"$@\"); gsub(/[^A-Z0-9]/,\" \",keys); print keys }"
1390
+ #}
1391
+
1392
+ # splits a comma separated line into lines, respects escaped commas
1393
+ function gpg_split_keyset2 {
1394
+ local LIST
1395
+ LIST=$(echo "$@" | awk '{ gsub(/,/,"\n",$0); gsub(/\\\n/,",",$0); print $0 }')
1396
+ echo -e "$LIST"
1397
+ }
1398
+
1399
+ function gpg_prefix_keyset {
1400
+ local PREFIX="$1" OUT=""
1401
+ shift
1402
+ for KEY_ID in "$@"; do
1403
+ OUT="${OUT} $PREFIX $(qw ${KEY_ID})"
1404
+ done
1405
+ echo $OUT
1406
+ }
1407
+
1408
+ # grep a variable from conf text file (currently not used)
1409
+ function gpg_passwd {
1410
+ [ -r "$CONF" ] && \
1411
+ awk '/^[ \t]*GPG_PW[ \t=]/{\
1412
+ sub(/^[ \t]*GPG_PW[ \t]*=*/,"",$0);\
1413
+ gsub(/^[ \t]*[\047"]|[\047"][ \t]*$/,"",$0);\
1414
+ print $0; exit}' "$CONF"
1415
+ }
1416
+
1417
+ # return success if at least one secret key is available
1418
+ function gpg_key_decryptable {
1419
+ local KEY_ID
1420
+ for KEY_ID in "${GPG_KEYS_ENC_ARRAY[@]}"; do
1421
+ gpg_sec_avail "$KEY_ID" && return 0
1422
+ done
1423
+ return 1
1424
+ }
1425
+
1426
+ function gpg_symmetric {
1427
+ [ -z "${GPG_KEY}${GPG_KEYS_ENC_ARRAY}" ]
1428
+ }
1429
+
1430
+ # checks for max two params if they are set, typically GPG_PW & GPG_PW_SIGN
1431
+ function gpg_param_passwd {
1432
+ var_isset GPG_USEAGENT && exit 1
1433
+
1434
+ if ( [ -n "$1" ] && var_isset "$1" ) || ( [ -n "$2" ] && var_isset "$2" ); then
1435
+ echo "--passphrase-fd 0 --batch"
1436
+ fi
1437
+ }
1438
+
1439
+ # select the earlist defined and create an "echo <value> |" string
1440
+ function gpg_pass_pipein {
1441
+ var_isset GPG_USEAGENT && exit 1
1442
+
1443
+ for var in "$@"
1444
+ do
1445
+ if var_isset "$var"; then
1446
+ echo "echo $(qw $(eval echo \$$var)) |"
1447
+ return 0
1448
+ fi
1449
+ done
1450
+
1451
+ return 1
1452
+ }
1453
+
1454
+ # checks if gpg-agent is available, returns error code
1455
+ # 0 on success
1456
+ # 1 if GPG_AGENT_INFO is not set
1457
+ # 2 if GPG_AGENT_INFO is stale
1458
+ function gpg_agent_avail {
1459
+ local ERR=1
1460
+ if var_isset GPG_AGENT_INFO; then
1461
+ ps -p $(echo $GPG_AGENT_INFO|awk -F: '{print $2}') > /dev/null 2>&1 &&\
1462
+ ERR=0 || ERR=2
1463
+ fi
1464
+
1465
+ return $ERR
1466
+ }
1467
+
1468
+ # start of script #######################################################################
1469
+
1470
+ # confidentiality first, all we create is only readable by us
1471
+ umask 077
1472
+
1473
+ # check if ftplicity is there & executable
1474
+ [ -n "$ME_LONG" ] && [ -x "$ME_LONG" ] || error "$ME missing. Executable & available in path? ($ME_LONG)"
1475
+
1476
+ if [ ${#@} -eq 1 ]; then
1477
+ cmd="${1}"
1478
+ else
1479
+ FTPLCFG="${1}" ; cmd="${2}"
1480
+ fi
1481
+
1482
+ # deal with command before profile validation calls
1483
+ # show requested version
1484
+ # OR requested usage info
1485
+ # OR create a profile
1486
+ # OR fall through
1487
+ ##if [ ${#@} -le 2 ]; then
1488
+ case "$cmd" in
1489
+ changelog)
1490
+ changelog
1491
+ exit 0
1492
+ ;;
1493
+ create)
1494
+ set_config
1495
+ if [ -d "$CONFDIR" ]; then
1496
+ error "The profile '$FTPLCFG' already exists in
1497
+ '$CONFDIR'.
1498
+
1499
+ Hint:
1500
+ If you _really_ want to create a new profile by this name you will
1501
+ have to manually delete the existing profile folder first."
1502
+ exit 1
1503
+ else
1504
+ create_config
1505
+ exit 0
1506
+ fi
1507
+ ;;
1508
+ txt2man)
1509
+ set_config
1510
+ usage_txt2man
1511
+ exit 0
1512
+ ;;
1513
+ usage|-help|--help|-h|-H)
1514
+ set_config
1515
+ usage_info
1516
+ exit 0
1517
+ ;;
1518
+ version|-version|--version|-v|-V)
1519
+ version_info_using
1520
+ exit 0
1521
+ ;;
1522
+ # fallthrough.. we got a command that needs an existing profile
1523
+ *)
1524
+ # if we reach here, user either forgot profile or chose wrong profileless command
1525
+ if [ ${#@} -le 1 ]; then
1526
+ error "\
1527
+ Missing or wrong parameters.
1528
+ Only the commands
1529
+ changelog, create, usage, txt2man, version
1530
+ can be called without selecting an existing profile first.
1531
+ Your command was '$cmd'.
1532
+
1533
+ Hint: Run '$ME usage' to get help."
1534
+ fi
1535
+ esac
1536
+
1537
+
1538
+ # Hello world
1539
+ echo "Start $ME v$ME_VERSION, time is $(date_fix '%F %T')."
1540
+
1541
+ # check system environment
1542
+ DUPLICITY="$(which duplicity 2>/dev/null)"
1543
+ [ -z "$DUPLICITY" ] && error_path "duplicity missing. installed und available in path?"
1544
+ # init, exec duplicity version check info
1545
+ duplicity_version_get
1546
+ duplicity_version_check
1547
+
1548
+ [ -z "$(which awk 2>/dev/null)" ] && error_path "awk missing. installed und available in path?"
1549
+
1550
+ ### read configuration
1551
+ set_config
1552
+ # check validity
1553
+ if [ ! -d "$CONFDIR" ]; then
1554
+ error "Selected profile '$FTPLCFG' does not resolve to a profile folder in
1555
+ '$CONFDIR'.
1556
+
1557
+ Hints:
1558
+ Select one of the available profiles: $(ls -1p $(dirname "$CONFDIR")| awk 'BEGIN{ORS="";OFS=""}/\/$/&&!/^\.+\/$/{print sep"\047"substr($0,0,length($0)-1)"\047";sep=","}').
1559
+ Use '$ME <name> create' to create a new profile.
1560
+ Use '$ME usage' to get usage help."
1561
+ elif [ ! -x "$CONFDIR" ]; then
1562
+ error "\
1563
+ Profile folder in '$CONFDIR' cannot be accessed.
1564
+
1565
+ Hint:
1566
+ Check the filesystem permissions and set directory accessible e.g. 'chmod 700'."
1567
+ elif [ ! -f "$CONF" ] ; then
1568
+ error "'$CONF' not found."
1569
+ elif [ ! -r "$CONF" ] ; then
1570
+ error "'$CONF' not readable."
1571
+ else
1572
+ . "$CONF"
1573
+ #KEYFILE="${KEYFILE//.asc/${GPG_KEY:+.$GPG_KEY}.asc}"
1574
+ TEMP_DIR=${TEMP_DIR:-'/tmp'}
1575
+ # backward compatibility: old TARGET_PW overrides silently new TARGET_PASS if set
1576
+ if var_isset 'TARGET_PW'; then
1577
+ TARGET_PASS="${TARGET_PW}"
1578
+ fi
1579
+ fi
1580
+ echo "Using profile '$CONFDIR'."
1581
+
1582
+ # secure config dir, if needed w/ warning
1583
+ secureconf
1584
+
1585
+ # split TARGET in handy variables
1586
+ TARGET_SPLIT_URL=$(echo $TARGET | awk '{ \
1587
+ target=$0; match(target,/^([^\/:]+):\/\//); \
1588
+ prot=substr(target,RSTART,RLENGTH);\
1589
+ rest=substr(target,RSTART+RLENGTH); \
1590
+ if (credsavail=match(rest,/^[^@]*@/)){\
1591
+ creds=substr(rest,RSTART,RLENGTH-1);\
1592
+ credcount=split(creds,cred,":");\
1593
+ rest=substr(rest,RLENGTH+1);\
1594
+ # split creds with regexp\
1595
+ match(creds,/^([^:]+)/);\
1596
+ user=substr(creds,RSTART,RLENGTH);\
1597
+ pass=substr(creds,RSTART+1+RLENGTH);\
1598
+ };\
1599
+ # filter quotes or escape them\
1600
+ gsub(/[\047\042]/,"",prot);\
1601
+ gsub(/[\047\042]/,"",rest);\
1602
+ gsub(/[\047]/,"\047\\\047\047",creds);\
1603
+ print "TARGET_URL_PROT=\047"prot"\047\n"\
1604
+ "TARGET_URL_HOSTPATH=\047"rest"\047\n"\
1605
+ "TARGET_URL_CREDS=\047"creds"\047\n";\
1606
+ if(user){\
1607
+ gsub(/[\047]/,"\047\\\047\047",user);\
1608
+ print "TARGET_URL_USER=\047"user"\047\n"}\
1609
+ if(pass){\
1610
+ gsub(/[\047]/,"\047\\\047\047",pass);\
1611
+ print "TARGET_URL_PASS=$(url_decode \047"pass"\047)\n"}\
1612
+ }')
1613
+ eval ${TARGET_SPLIT_URL}
1614
+
1615
+ # check if backend specific software is in path
1616
+ [ -n "$(echo ${TARGET_URL_PROT} | grep -i -e '^ftp://$')" ] && \
1617
+ [ -z "$(which ncftp 2>/dev/null)" ] && error_path "Protocol 'ftp' needs ncftp. Installed und available in path?"
1618
+ [ -n "$(echo ${TARGET_URL_PROT} | grep -i -e '^ftps://$')" ] && \
1619
+ [ -z "$(which lftp 2>/dev/null)" ] && error_path "Protocol 'ftps' needs lftp. Installed und available in path?"
1620
+
1621
+ # fetch commmand from parameters ########################################################
1622
+ # Hint: cmds is also used to check if authentification info sufficient in the next step
1623
+ cmds="$2"; shift 2
1624
+
1625
+ # translate backup to batch command
1626
+ cmds=${cmds//backup/pre_bkp_post}
1627
+
1628
+ # complain if command(s) missing
1629
+ [ -z $cmds ] && error " No command given.
1630
+
1631
+ Hint:
1632
+ Use '$ME usage' to get usage help."
1633
+
1634
+ # process params
1635
+ for param in "$@"; do
1636
+ #echo !$param!
1637
+ case "$param" in
1638
+ # enable ftplicity preview mode
1639
+ '--preview')
1640
+ PREVIEW=1
1641
+ ;;
1642
+ # interpret duplicity disable encr switch
1643
+ '--disable-encryption')
1644
+ GPG_KEY='disabled'
1645
+ ;;
1646
+ *)
1647
+ if [ `echo "$param" | grep -e "^-"` ] || \
1648
+ [ `echo "$last_param" | grep -e "^-"` ] ; then
1649
+ # forward parameter[/option pairs] to duplicity
1650
+ dupl_opts["${#dupl_opts[@]}"]=${param}
1651
+ else
1652
+ # anything else must be a parameter (eg. for fetch, ...)
1653
+ ftpl_pars["${#ftpl_pars[@]}"]=${param}
1654
+ fi
1655
+ last_param=${param}
1656
+ ;;
1657
+ esac
1658
+ done
1659
+
1660
+ # plausibility check config - VARS & KEY ################################################
1661
+ # check if src, trg, trg pw
1662
+ # auth info sufficient
1663
+ # gpg key, gpg pwd (might be empty) set in config
1664
+ # OR key in local gpg db
1665
+ # OR key can be imported from keyfile
1666
+ # OR fail
1667
+ if [ -z "$SOURCE" ] || [ "$SOURCE" == "${DEFAULT_SOURCE}" ]; then
1668
+ error " Source Path (setting SOURCE) not set or still default value in conf file
1669
+ '$CONF'."
1670
+
1671
+ elif [ -z "$TARGET" ] || [ "$TARGET" == "${DEFAULT_TARGET}" ]; then
1672
+ error " Backup Target (setting TARGET) not set or still default value in conf file
1673
+ '$CONF'."
1674
+
1675
+ elif var_isset 'TARGET_USER' && var_isset 'TARGET_URL_USER' && \
1676
+ [ "${TARGET_USER}" != "${TARGET_URL_USER}" ]; then
1677
+ error " TARGET_USER ('${TARGET_USER}') _and_ user in TARGET url ('${TARGET_URL_USER}')
1678
+ are configured with different values. There can be only one.
1679
+
1680
+ Hint: Remove conflicting setting."
1681
+
1682
+ elif var_isset 'TARGET_PASS' && var_isset 'TARGET_URL_PASS' && \
1683
+ [ "${TARGET_PASS}" != "${TARGET_URL_PASS}" ]; then
1684
+ error " TARGET_PASS ('${TARGET_PASS}') _and_ password in TARGET url ('${TARGET_URL_PASS}')
1685
+ are configured with different values. There can be only one.
1686
+
1687
+ Hint: Remove conflicting setting."
1688
+ fi
1689
+
1690
+ # check if authentication information sufficient
1691
+ if ( ( ! var_isset 'TARGET_USER' && ! var_isset 'TARGET_URL_USER' ) && \
1692
+ ( ! var_isset 'TARGET_PASS' && ! var_isset 'TARGET_URL_PASS' ) ); then
1693
+ # ok here some exceptions:
1694
+ # protocols that do not need passwords
1695
+ # s3[+http] only needs password for write operations
1696
+ if [ -n "$( tolower "${TARGET_URL_PROT}" | grep -e '^\(dpbx\|file\|tahoe\|ssh\|scp\|sftp\|swift\)://$' )" ]; then
1697
+ : # all is well file/tahoe do not need passwords, ssh might use key auth
1698
+ elif [ -n "$(tolower "${TARGET_URL_PROT}" | grep -e '^s3\(\+http\)\?://$')" ] && \
1699
+ [ -z "$(echo ${cmds} | grep -e '\(bkp\|incr\|full\|purge\|cleanup\)')" ]; then
1700
+ : # still fine, it's possible to read only access configured buckets anonymously
1701
+ else
1702
+ error " Backup target credentials needed but not set in conf file
1703
+ '$CONF'.
1704
+ Setting TARGET_USER or TARGET_PASS or the corresponding values in TARGET url
1705
+ are missing. Some protocols only might need it for write access to the backup
1706
+ repository (commands: bkp,backup,full,incr,purge) but not for read only access
1707
+ (e.g. verify,list,restore,fetch).
1708
+
1709
+ Hints:
1710
+ Add the credentials (user,password) to the conf file.
1711
+ To force an empty password set TARGET_PASS='' or TARGET='prot://user:@host..'.
1712
+ "
1713
+ fi
1714
+ fi
1715
+
1716
+ # GPG config plausibility check1 (disabled check) #############################
1717
+ if gpg_disabled; then
1718
+ : # encryption disabled, all is well
1719
+ elif [ -z "${GPG_KEY}${GPG_KEYS_ENC}${GPG_KEY_SIGN}" ] && ! var_isset 'GPG_PW'; then
1720
+ warning "GPG_KEY, GPG_KEYS_ENC, GPG_KEY_SIGN and GPG_PW are empty/not set in conf file
1721
+ '$CONF'.
1722
+ Will disable encryption for duplicity now.
1723
+
1724
+ Hint:
1725
+ If you really want to use _no_ encryption you can disable this warning by
1726
+ setting GPG_KEY='disabled' in conf file."
1727
+ GPG_KEY='disabled'
1728
+ fi
1729
+
1730
+ # GPG availability check (now we know if gpg is really needed)#################
1731
+ if ! gpg_disabled; then
1732
+ GPG="$(which gpg 2>/dev/null)"
1733
+ [ -z "$GPG" ] && error_path "gpg missing. installed und available in path?"
1734
+ fi
1735
+
1736
+
1737
+ # Output versions info ########################################################
1738
+ using_info
1739
+
1740
+ # GPG create key settings, config check2 (needs gpg) ##########################
1741
+ if gpg_disabled; then
1742
+ : # the following tests are not necessary
1743
+ else
1744
+
1745
+ # key set?
1746
+ if [ "$GPG_KEY" == "${DEFAULT_GPG_KEY}" ]; then
1747
+ error_gpg "Encryption Key GPG_KEY still default in conf file
1748
+ '$CONF'."
1749
+ fi
1750
+
1751
+ # create array of gpg encr keys, for further processing
1752
+ OIFS="$IFS" IFS=$'\n'
1753
+ GPG_KEYS_ENC_ARRAY=( $( gpg_split_keyset2 ${GPG_KEY},${GPG_KEYS_ENC} ) )
1754
+ IFS="$OIFS"
1755
+
1756
+ # check gpg encr public keys availability
1757
+ for (( i = 0 ; i < ${#GPG_KEYS_ENC_ARRAY[@]} ; i++ )); do
1758
+ KEY_ID="${GPG_KEYS_ENC_ARRAY[$i]}"
1759
+ # test availability, try to import, retest
1760
+ if ! gpg_pub_avail "${KEY_ID}"; then
1761
+ echo "Encryption public key '${KEY_ID}' not found."
1762
+ gpg_import "${KEY_ID}" PUB
1763
+ gpg_key_cache RESET "${KEY_ID}"
1764
+ gpg_pub_avail "${KEY_ID}" || error_gpg_key "${KEY_ID}" "Public"
1765
+ fi
1766
+ done
1767
+
1768
+ # gpg secret sign key availability
1769
+ # if none set, autoset first encryption key as sign key
1770
+ if ! gpg_signing; then
1771
+ echo "Signing disabled per configuration."
1772
+ # try first key, if one set
1773
+ elif ! var_isset 'GPG_KEY_SIGN'; then
1774
+ KEY_ID="${GPG_KEYS_ENC_ARRAY[0]}"
1775
+ if [ -z "${KEY_ID}" ]; then
1776
+ echo "Signing disabled. Not GPG_KEY entries in config."
1777
+ GPG_KEY_SIGN='disabled'
1778
+ else
1779
+ # use avail OR try import OR fail
1780
+ if gpg_sec_avail "${KEY_ID}"; then
1781
+ GPG_KEY_SIGN="${KEY_ID}"
1782
+ else
1783
+ gpg_import "${KEY_ID}" SEC
1784
+ gpg_key_cache RESET "${KEY_ID}"
1785
+ if gpg_sec_avail "${KEY_ID}"; then
1786
+ GPG_KEY_SIGN="${KEY_ID}"
1787
+ fi
1788
+ fi
1789
+
1790
+ # interpret sign key setting
1791
+ if var_isset 'GPG_KEY_SIGN'; then
1792
+ echo "Autoset found secret key of first GPG_KEY entry '${KEY_ID}' for signing."
1793
+ else
1794
+ echo "Signing disabled. First GPG_KEY entry's '${KEY_ID}' private key is missing."
1795
+ GPG_KEY_SIGN='disabled'
1796
+ fi
1797
+ fi
1798
+ else
1799
+ KEY_ID="${GPG_KEY_SIGN}"
1800
+ if ! gpg_sec_avail "${KEY_ID}"; then
1801
+ inform "Secret signing key defined in setting GPG_KEY_SIGN='${KEY_ID}' not found.\nTry to import."
1802
+ gpg_import "${KEY_ID}" SEC
1803
+ gpg_key_cache RESET "${KEY_ID}"
1804
+ gpg_sec_avail "${KEY_ID}" || error_gpg_key "${KEY_ID}" "Private"
1805
+ fi
1806
+ fi
1807
+
1808
+ # pw set?
1809
+ # symmetric needs one, always
1810
+ if gpg_symmetric && ( [ -z "$GPG_PW" ] || [ "$GPG_PW" == "${DEFAULT_GPG_PW}" ] ) \
1811
+ ; then
1812
+ error_gpg "Encryption passphrase GPG_PW (needed for symmetric encryption)
1813
+ is empty/not set or still default value in conf file
1814
+ '$CONF'."
1815
+ fi
1816
+ # this is a technicality, we can only pump one pass via pipe into gpg
1817
+ # but symmetric already always needs one for encryption
1818
+ if gpg_symmetric && var_isset GPG_PW && var_isset GPG_PW_SIGN &&\
1819
+ [ -n "$GPG_PW_SIGN" ] && [ "$GPG_PW" != "$GPG_PW_SIGN" ]; then
1820
+ error_gpg "GPG_PW _and_ GPG_PW_SIGN are defined but not identical in config
1821
+ '$CONF'.
1822
+ This is unfortunately impossible. For details see duplicity manpage,
1823
+ section 'A Note On Symmetric Encryption And Signing'.
1824
+
1825
+ Tip: Separate signing keys may have empty passwords e.g. GPG_PW_SIGN=''.
1826
+ Tip2: Use gpg-agent."
1827
+ fi
1828
+ # key enc can deal without, but might profit from gpg-agent
1829
+ # if GPG_PW is not set alltogether
1830
+ # if signing key is different from first (main) enc key (we can only pipe one pass into gpg)
1831
+ if ! gpg_symmetric && \
1832
+ ( ! var_isset GPG_PW || \
1833
+ ( gpg_signing && ! var_isset GPG_PW_SIGN && [ "$GPG_KEY_SIGN" != "${GPG_KEYS_ENC_ARRAY[0]}" ] ) ); then
1834
+
1835
+ GPG_AGENT_ERR=$(gpg_agent_avail ; echo $?)
1836
+ if [ "$GPG_AGENT_ERR" -eq 1 ]; then
1837
+ echo "Cannot use gpg-agent. GPG_AGENT_INFO not set."
1838
+ elif [ "$GPG_AGENT_ERR" -eq 2 ]; then
1839
+ echo "Cannot use gpg-agent! GPG_AGENT_INFO contains stale pid."
1840
+ else
1841
+ echo "Autoenable use of gpg-agent. GPG_PW or GPG_PW_SIGN (enc != sign key) not set."
1842
+ GPG_USEAGENT="--use-agent"
1843
+ fi
1844
+ fi
1845
+
1846
+ # end GPG config plausibility check2
1847
+ fi
1848
+
1849
+ # config plausibility check - SPACE ###########################################
1850
+
1851
+ # is tmp is a folder
1852
+ CMD_MSG="Checking TEMP_DIR '${TEMP_DIR}' is a folder"
1853
+ run_cmd test -d "$TEMP_DIR"
1854
+ if [ "$?" != "0" ]; then
1855
+ error "Temporary file space '$TEMP_DIR' is not a directory."
1856
+ fi
1857
+ # is tmp writeable
1858
+ CMD_MSG="Checking TEMP_DIR '${TEMP_DIR}' is writable"
1859
+ run_cmd test -w "$TEMP_DIR"
1860
+ if [ "$?" != "0" ]; then
1861
+ error "Temporary file space '$TEMP_DIR' not writable."
1862
+ fi
1863
+
1864
+
1865
+ # get volsize, default duplicity volume size is 25MB since v0.5.07
1866
+ VOLSIZE=${VOLSIZE:-25}
1867
+ # double if asynch is on
1868
+ echo $@ $DUPL_PARAMS | grep -q -e '--asynchronous-upload' && FACTOR=2 || FACTOR=1
1869
+
1870
+ # TODO: check for enough (async= upload space and WARN only
1871
+ # use function tmp_space
1872
+ echo TODO: reimplent tmp space check
1873
+
1874
+
1875
+ # test - GPG SANITY #####################################################################
1876
+ # if encryption is disabled, skip this whole section
1877
+ if gpg_disabled; then
1878
+ echo -e "Test - En/Decryption skipped. (GPG disabled)"
1879
+ elif [ "$GPG_TEST" = "disabled" ]; then
1880
+ echo -e "Test - En/Decryption skipped. (Testing disabled)"
1881
+ else
1882
+
1883
+ GPG_TEST="$TEMP_DIR/${ME_NAME}.$$.$(date_fix %s)"
1884
+ function cleanup_gpgtest {
1885
+ echo -en "Cleanup - Delete '${GPG_TEST}_*'"
1886
+ rm ${GPG_TEST}_* 2>/dev/null && echo "(OK)" || echo "(FAILED)"
1887
+ }
1888
+
1889
+ # signing enabled?
1890
+ if gpg_signing; then
1891
+ CMD_PARAM_SIGN="--sign --default-key $(qw ${GPG_KEY_SIGN})"
1892
+ CMD_MSG_SIGN="Sign with '${GPG_KEY_SIGN}'"
1893
+ fi
1894
+
1895
+ # using keys
1896
+ if [ ${#GPG_KEYS_ENC_ARRAY[@]} -gt 0 ]; then
1897
+
1898
+ for KEY_ID in "${GPG_KEYS_ENC_ARRAY[@]}"; do
1899
+ CMD_PARAMS="$CMD_PARAMS -r $(qw ${KEY_ID})"
1900
+ done
1901
+ # check encrypting
1902
+ CMD_MSG="Test - Encrypt to '$(join "','" "${GPG_KEYS_ENC_ARRAY[@]}")'${CMD_MSG_SIGN:+ & $CMD_MSG_SIGN}"
1903
+ run_cmd $(gpg_pass_pipein GPG_PW_SIGN GPG_PW) $GPG $CMD_PARAM_SIGN $(gpg_param_passwd GPG_PW_SIGN GPG_PW) $CMD_PARAMS $GPG_USEAGENT --status-fd 1 $GPG_OPTS -o "${GPG_TEST}_ENC" -e "$ME_LONG"
1904
+ CMD_ERR=$?
1905
+
1906
+ if [ "$CMD_ERR" != "0" ]; then
1907
+ KEY_NOTRUST=$(echo "$CMD_OUT"|awk '/^\[GNUPG:\] INV_RECP 10/ { print $4 }')
1908
+ [ -n "$KEY_NOTRUST" ] && HINT="Key '${KEY_NOTRUST}' seems to be untrusted. If you really trust this key try to
1909
+ 'gpg --edit-key "$KEY_NOTRUST"' and raise the trust level to ultimate. If you
1910
+ can trust all of your keys set GPG_OPTS='--trust-model always' in conf file."
1911
+ error_gpg_test "Encryption failed (Code $CMD_ERR).${CMD_OUT:+\n$CMD_OUT}" "$HINT"
1912
+ fi
1913
+
1914
+ # check decrypting
1915
+ CMD_MSG="Test - Decrypt"
1916
+ gpg_key_decryptable || CMD_DISABLED="No matching secret key available."
1917
+ run_cmd $(gpg_pass_pipein GPG_PW) "$GPG" $(gpg_param_passwd GPG_PW) $GPG_OPTS -o "${GPG_TEST}_DEC" $GPG_USEAGENT -d "${GPG_TEST}_ENC"
1918
+ CMD_ERR=$?
1919
+
1920
+ if [ "$CMD_ERR" != "0" ]; then
1921
+ error_gpg_test "Decryption failed.${CMD_OUT:+\n$CMD_OUT}"
1922
+ fi
1923
+
1924
+ # symmetric only
1925
+ else
1926
+ # check encrypting
1927
+ CMD_MSG="Test - Encryption with passphrase${CMD_MSG_SIGN:+ & $CMD_MSG_SIGN}"
1928
+ run_cmd $(gpg_pass_pipein GPG_PW) "$GPG" $GPG_OPTS $CMD_PARAM_SIGN --passphrase-fd 0 -o "${GPG_TEST}_ENC" --batch -c "$ME_LONG"
1929
+ CMD_ERR=$?
1930
+ if [ "$CMD_ERR" != "0" ]; then
1931
+ error_gpg_test "Encryption failed.${CMD_OUT:+\n$CMD_OUT}"
1932
+ fi
1933
+
1934
+ # check decrypting
1935
+ CMD_MSG="Test - Decryption with passphrase"
1936
+ run_cmd $(gpg_pass_pipein GPG_PW) "$GPG" $GPG_OPTS --passphrase-fd 0 -o "${GPG_TEST}_DEC" --batch -d "${GPG_TEST}_ENC"
1937
+ CMD_ERR=$?
1938
+ if [ "$CMD_ERR" != "0" ]; then
1939
+ error_gpg_test "Decryption failed.${CMD_OUT:+\n$CMD_OUT}"
1940
+ fi
1941
+ fi
1942
+
1943
+ # compare original w/ decryptginal
1944
+ CMD_MSG="Test - Compare"
1945
+ [ -r "${GPG_TEST}_DEC" ] || CMD_DISABLED="File not found. Nothing to compare."
1946
+ run_cmd "test \"\$(cat '$ME_LONG')\" = \"\$(cat '${GPG_TEST}_DEC')\""
1947
+ CMD_ERR=$?
1948
+ if [ "$CMD_ERR" = "0" ]; then
1949
+ cleanup_gpgtest
1950
+ else
1951
+ error_gpg_test "Comparision failed.${CMD_OUT:+\n$CMD_OUT}"
1952
+ fi
1953
+
1954
+ fi # end disabled
1955
+
1956
+ ## an empty line
1957
+ #echo
1958
+
1959
+ # Exclude file is needed, create it if necessary
1960
+ [ -f "$EXCLUDE" ] || touch "$EXCLUDE"
1961
+
1962
+ # export only used keys, if bkp not already exists ######################################
1963
+ gpg_export_if_needed "${GPG_KEYS_ENC_ARRAY[@]}" "$(gpg_signing && echo $GPG_KEY_SIGN)"
1964
+
1965
+
1966
+ # command execution #####################################################################
1967
+
1968
+ # urldecode url vars into plain text
1969
+ var_isset 'TARGET_URL_USER' && TARGET_URL_USER="$(url_decode "$TARGET_URL_USER")"
1970
+ var_isset 'TARGET_URL_PASS' && TARGET_URL_PASS="$(url_decode "$TARGET_URL_PASS")"
1971
+
1972
+ # defined TARGET_USER&PASS vars replace their URL pendants
1973
+ # (double defs already dealt with)
1974
+ var_isset 'TARGET_USER' && TARGET_URL_USER="$TARGET_USER"
1975
+ var_isset 'TARGET_PASS' && TARGET_URL_PASS="$TARGET_PASS"
1976
+
1977
+ # build target backend data depending on protocol
1978
+ case "$(tolower "${TARGET_URL_PROT%%:*}")" in
1979
+ 's3'|'s3+http')
1980
+ BACKEND_PARAMS="AWS_ACCESS_KEY_ID='${TARGET_URL_USER}' AWS_SECRET_ACCESS_KEY='${TARGET_URL_PASS}'"
1981
+ BACKEND_URL="${TARGET_URL_PROT}${TARGET_URL_HOSTPATH}"
1982
+ ;;
1983
+ 'gs')
1984
+ BACKEND_PARAMS="GS_ACCESS_KEY_ID='${TARGET_URL_USER}' GS_SECRET_ACCESS_KEY='${TARGET_URL_PASS}'"
1985
+ BACKEND_URL="${TARGET_URL_PROT}${TARGET_URL_HOSTPATH}"
1986
+ ;;
1987
+ 'cf+http')
1988
+ # respect potentially set cloudfile env vars
1989
+ var_isset 'CLOUDFILES_USERNAME' && TARGET_URL_USER="$CLOUDFILES_USERNAME"
1990
+ var_isset 'CLOUDFILES_APIKEY' && TARGET_URL_PASS="$CLOUDFILES_APIKEY"
1991
+ # add them to duplicity params
1992
+ var_isset 'TARGET_URL_USER' && \
1993
+ BACKEND_PARAMS="CLOUDFILES_USERNAME=$(qw "${TARGET_URL_USER}")"
1994
+ var_isset 'TARGET_URL_PASS' && \
1995
+ BACKEND_PARAMS="$BACKEND_PARAMS CLOUDFILES_APIKEY=$(qw "${TARGET_URL_PASS}")"
1996
+ BACKEND_URL="${TARGET_URL_PROT}${TARGET_URL_HOSTPATH}"
1997
+ # info on missing AUTH_URL
1998
+ if ! var_isset 'CLOUDFILES_AUTHURL'; then
1999
+ echo -e "INFO: No CLOUDFILES_AUTHURL defined (in conf).\n Will use default from python-cloudfiles (probably rackspace)."
2000
+ else
2001
+ BACKEND_PARAMS="$BACKEND_PARAMS CLOUDFILES_AUTHURL=$(qw "${CLOUDFILES_AUTHURL}")"
2002
+ fi
2003
+ ;;
2004
+ 'file'|'tahoe'|'dpbx')
2005
+ BACKEND_URL="${TARGET_URL_PROT}${TARGET_URL_HOSTPATH}"
2006
+ ;;
2007
+ 'swift')
2008
+ BACKEND_URL="${TARGET_URL_PROT}${TARGET_URL_HOSTPATH}"
2009
+ # respect possibly set swift env vars
2010
+ var_isset 'SWIFT_USERNAME' && TARGET_URL_USER="$SWIFT_USERNAME"
2011
+ var_isset 'SWIFT_PASSWORD' && TARGET_URL_PASS="$SWIFT_PASSWORD"
2012
+ # add them to duplicity params like with cloudfile to make it look standardized
2013
+ var_isset 'TARGET_URL_USER' && \
2014
+ BACKEND_PARAMS="$BACKEND_PARAMS SWIFT_USERNAME=$(qw "${TARGET_URL_USER}")"
2015
+ var_isset 'SWIFT_AUTHURL' && \
2016
+ BACKEND_PARAMS="$BACKEND_PARAMS SWIFT_AUTHURL=$(qw "${SWIFT_AUTHURL}")"
2017
+ ( var_isset 'TARGET_URL_USER' && ! var_isset 'SWIFT_AUTHURL' ) &&\
2018
+ warning "\
2019
+ Swift will probably fail because the conf var SWIFT_AUTHURL was not defined!"
2020
+ var_isset 'SWIFT_AUTHVERSION' && \
2021
+ BACKEND_PARAMS="$BACKEND_PARAMS SWIFT_AUTHVERSION=$(qw "${SWIFT_AUTHVERSION}")"
2022
+ var_isset 'TARGET_URL_PASS' && \
2023
+ BACKEND_PARAMS="$BACKEND_PARAMS SWIFT_PASSWORD=$(qw "${TARGET_URL_PASS}")"
2024
+ ;;
2025
+ 'rsync')
2026
+ # everything in url (this backend does not support pass in env var)
2027
+ # this is obsolete from version 0.6.10 (buggy), hopefully fixed in 0.6.11
2028
+ # print warning older version is detected
2029
+ var_isset 'TARGET_URL_USER' && BACKEND_CREDS="$(url_encode "${TARGET_URL_USER}")"
2030
+ if duplicity_version_lt 610; then
2031
+ warning "\
2032
+ Duplicity version '$DUPL_VERSION' does not support providing the password as
2033
+ env var for rsync backend. For security reasons you should consider to
2034
+ update to a version greater than '0.6.10' of duplicity."
2035
+ var_isset 'TARGET_URL_PASS' && BACKEND_CREDS="${BACKEND_CREDS}:$(url_encode "${TARGET_URL_PASS}")"
2036
+ else
2037
+ var_isset 'TARGET_URL_PASS' && BACKEND_PARAMS="FTP_PASSWORD=$(qw "${TARGET_URL_PASS}")"
2038
+ fi
2039
+ var_isset 'BACKEND_CREDS' && BACKEND_CREDS="${BACKEND_CREDS}@"
2040
+ BACKEND_URL="${TARGET_URL_PROT}${BACKEND_CREDS}${TARGET_URL_HOSTPATH}"
2041
+ ;;
2042
+ *)
2043
+ # for all other protocols we put username in url and pass into env var
2044
+ # for sec˙rity reasons, we url_encode username to protect special chars
2045
+ var_isset 'TARGET_URL_USER' &&
2046
+ BACKEND_CREDS="$(url_encode "${TARGET_URL_USER}")@"
2047
+ # sortout backends with special ways to handle password
2048
+ case "$(tolower "${TARGET_URL_PROT%%:*}")" in
2049
+ 'imap'|'imaps')
2050
+ var_isset 'TARGET_URL_PASS' && BACKEND_PARAMS="IMAP_PASSWORD=$(qw "${TARGET_URL_PASS}")"
2051
+ ;;
2052
+ 'ssh'|'sftp'|'scp')
2053
+ # ssh backend wants to be told that theres a pass to use
2054
+ var_isset 'TARGET_URL_PASS' && \
2055
+ DUPL_PARAMS="$DUPL_PARAMS --ssh-askpass" && \
2056
+ BACKEND_PARAMS="FTP_PASSWORD=$(qw "${TARGET_URL_PASS}")"
2057
+ ;;
2058
+ *)
2059
+ # rest uses FTP_PASS var
2060
+ var_isset 'TARGET_URL_PASS' && \
2061
+ BACKEND_PARAMS="FTP_PASSWORD=$(qw "${TARGET_URL_PASS}")"
2062
+ ;;
2063
+ esac
2064
+ BACKEND_URL="${TARGET_URL_PROT}${BACKEND_CREDS}${TARGET_URL_HOSTPATH}"
2065
+ ;;
2066
+ esac
2067
+
2068
+ # protect eval from special chars in url (e.g. open ')' in password,
2069
+ # spaces in path, quotes) happens above in duplify() via quotewrap()
2070
+ SOURCE="$SOURCE"
2071
+ BACKEND_URL="$BACKEND_URL"
2072
+ EXCLUDE="$EXCLUDE"
2073
+
2074
+ # replace magic separators to condition command equivalents (+=and,-=or)
2075
+ cmds=$(awk -v cmds="$cmds" "BEGIN{ gsub(/\+/,\"_and_\",cmds); gsub(/\-/,\"_or_\",cmds); print cmds}")
2076
+ # convert cmds to array, lowercase for safety
2077
+ CMDS=( $(awk "BEGIN{ cmds=tolower(\"$cmds\"); gsub(/_/,\" \",cmds); print cmds }") )
2078
+
2079
+ # run cmds
2080
+ for cmd in ${CMDS[*]};
2081
+ do
2082
+
2083
+ ## init
2084
+ # raise index in cmd array for pre/post param
2085
+ var_isset 'CMD_NO' && CMD_NO=$((++CMD_NO)) || CMD_NO=0
2086
+
2087
+ # deal with condition "commands"
2088
+ unset SKIP_NOW
2089
+ if var_isset 'CMD_SKIP' && [ $CMD_SKIP -gt 0 ]; then
2090
+ echo -e "\n--- Skipping command $(toupper $cmd) ! ---"
2091
+ CMD_SKIP=$(($CMD_SKIP - 1))
2092
+ SKIP_NOW="yes"
2093
+ elif [ "$cmd" == 'and' ] && [ "$CMD_ERR" -ne "0" ]; then
2094
+ CMD_SKIP=1
2095
+ SKIP_NOW="yes"
2096
+ elif [ "$cmd" == 'or' ] && [ "$CMD_ERR" -eq "0" ]; then
2097
+ CMD_SKIP=1
2098
+ SKIP_NOW="yes"
2099
+ elif [ "$cmd" == 'and' ] || [ "$cmd" == 'or' ]; then
2100
+ unset 'CMD_SKIP';
2101
+ SKIP_NOW="yes"
2102
+ fi
2103
+
2104
+ # sum up how many commands we skip and actually skip
2105
+ if [ -n "$SKIP_NOW" ]; then
2106
+ CMD_SKIPPED=$((${CMD_SKIPPED-0} + 1))
2107
+ continue
2108
+ fi
2109
+
2110
+ # get prev/nextcmd vars
2111
+ nextno=$(($CMD_NO+1))
2112
+ [ "$nextno" -lt "${#CMDS[@]}" ] && CMD_NEXT=${CMDS[$nextno]} || CMD_NEXT='END'
2113
+ # get previous command minus skipped commands
2114
+ prevno=$(( $CMD_NO - ${CMD_SKIPPED-0} - 1 )); unset CMD_SKIPPED
2115
+ [ "$prevno" -ge 0 ] && CMD_PREV=${CMDS[$prevno]} || CMD_PREV='START'
2116
+
2117
+ # export some useful env vars for external scripts/programs to use
2118
+ export CONFDIR SOURCE TARGET_URL_PROT TARGET_URL_HOSTPATH \
2119
+ TARGET_URL_USER TARGET_URL_PASS \
2120
+ GPG_KEYS_ENC=$(join "\n" "${GPG_KEYS_ENC_ARRAY[@]}") GPG_KEY_SIGN \
2121
+ GPG_PW CMD_PREV CMD_NEXT CMD_ERR
2122
+
2123
+ # save start time
2124
+ RUN_START=$(date_fix %s)$(nsecs)
2125
+ # user info
2126
+ echo; separator "Start running command $(toupper $cmd) at $(date_from_nsecs $RUN_START)"
2127
+
2128
+ case "$(tolower $cmd)" in
2129
+ 'pre'|'post')
2130
+ if [ "$cmd" == 'pre' ]; then
2131
+ script=$PRE
2132
+ else
2133
+ script=$POST
2134
+ fi
2135
+ # script execution in a subshell, protect us from failures/var overwrites
2136
+ ( run_script "$script" )
2137
+ ;;
2138
+ 'bkp')
2139
+ duplify -- "${dupl_opts[@]}" --exclude-globbing-filelist "$EXCLUDE" \
2140
+ "$SOURCE" "$BACKEND_URL"
2141
+ ;;
2142
+ 'incr')
2143
+ duplify incr -- "${dupl_opts[@]}" --exclude-globbing-filelist "$EXCLUDE" \
2144
+ "$SOURCE" "$BACKEND_URL"
2145
+ ;;
2146
+ 'full')
2147
+ duplify full -- "${dupl_opts[@]}" --exclude-globbing-filelist "$EXCLUDE" \
2148
+ "$SOURCE" "$BACKEND_URL"
2149
+ ;;
2150
+ 'verify')
2151
+ TIME="${ftpl_pars[0]:+"-t ${ftpl_pars[0]}"}"
2152
+ duplify verify -- $TIME "${dupl_opts[@]}" --exclude-globbing-filelist "$EXCLUDE" \
2153
+ "$BACKEND_URL" "$SOURCE"
2154
+ ;;
2155
+ 'verifypath')
2156
+ TIME="${ftpl_pars[2]:+"-t ${ftpl_pars[2]}"}"
2157
+ IN_PATH="${ftpl_pars[0]}"; OUT_PATH="${ftpl_pars[1]}";
2158
+ ( [ -z "$IN_PATH" ] || [ -z "$OUT_PATH" ] ) && error " Missing parameter <rel_bkp_path> or <local_path> for verifyPath.
2159
+
2160
+ Hint:
2161
+ Syntax is -> $ME <profile> verifyPath <rel_bkp_path> <local_path> [<age>]"
2162
+
2163
+ duplify verify -- $TIME "${dupl_opts[@]}" --exclude-globbing-filelist "$EXCLUDE" \
2164
+ --file-to-restore "$IN_PATH" "$BACKEND_URL" "$OUT_PATH"
2165
+ ;;
2166
+ 'list')
2167
+ # time param exists since 0.5.10+
2168
+ TIME="${ftpl_pars[0]:+"-t ${ftpl_pars[0]}"}"
2169
+ duplify list-current-files -- $TIME "${dupl_opts[@]}" "$BACKEND_URL"
2170
+ ;;
2171
+ 'cleanup')
2172
+ duplify cleanup -- "${dupl_opts[@]}" "$BACKEND_URL"
2173
+ ;;
2174
+ 'purge')
2175
+ MAX_AGE=${ftpl_pars[0]:-$MAX_AGE}
2176
+ [ -z "$MAX_AGE" ] && error " Missing parameter <max_age>. Can be set in profile or as command line parameter."
2177
+
2178
+ duplify remove-older-than "${MAX_AGE}" \
2179
+ -- "${dupl_opts[@]}" "$BACKEND_URL"
2180
+ ;;
2181
+ 'purgefull')
2182
+ MAX_FULL_BACKUPS=${ftpl_pars[0]:-$MAX_FULL_BACKUPS}
2183
+ [ -z "$MAX_FULL_BACKUPS" ] && error " Missing parameter <max_full_backups>. Can be set in profile or as command line parameter."
2184
+
2185
+ duplify remove-all-but-n-full "${MAX_FULL_BACKUPS}" \
2186
+ -- "${dupl_opts[@]}" "$BACKEND_URL"
2187
+ ;;
2188
+ 'purgeincr')
2189
+ MAX_FULLS_WITH_INCRS=${ftpl_pars[0]:-$MAX_FULLS_WITH_INCRS}
2190
+ [ -z "$MAX_FULLS_WITH_INCRS" ] && error " Missing parameter <max_fulls_with_incrs>. Can be set in profile or as command line parameter."
2191
+
2192
+ duplify remove-all-inc-of-but-n-full "${MAX_FULLS_WITH_INCRS}" \
2193
+ -- "${dupl_opts[@]}" "$BACKEND_URL"
2194
+ ;;
2195
+ 'restore')
2196
+ OUT_PATH="${ftpl_pars[0]:-$SOURCE}"; TIME="${ftpl_pars[1]:-now}";
2197
+ [ -z "$OUT_PATH" ] && error " Missing parameter target_path for restore.
2198
+
2199
+ Hint:
2200
+ Syntax is -> $ME <profile> restore <target_path> [<age>]"
2201
+
2202
+ duplify -- -t "$TIME" "${dupl_opts[@]}" "$BACKEND_URL" "$OUT_PATH"
2203
+ run_script $CONFDIR/restore;
2204
+ ;;
2205
+ 'fetch')
2206
+ IN_PATH="${ftpl_pars[0]}"; OUT_PATH="${ftpl_pars[1]}";
2207
+ TIME="${ftpl_pars[2]:-now}";
2208
+ ( [ -z "$IN_PATH" ] || [ -z "$OUT_PATH" ] ) && error " Missing parameter <src_path> or <target_path> for fetch.
2209
+
2210
+ Hint:
2211
+ Syntax is -> $ME <profile> fetch <src_path> <target_path> [<age>]"
2212
+
2213
+ # duplicity 0.4.7 doesnt like cmd restore in combination with --file-to-restore
2214
+ duplify -- --restore-time "$TIME" "${dupl_opts[@]}" \
2215
+ --file-to-restore "$IN_PATH" "$BACKEND_URL" "$OUT_PATH"
2216
+ ;;
2217
+ 'status')
2218
+ duplify collection-status -- "${dupl_opts[@]}" "$BACKEND_URL"
2219
+ ;;
2220
+ *)
2221
+ warning "Unknown command '$cmd'."
2222
+ ;;
2223
+ esac
2224
+
2225
+ CMD_ERR=$?
2226
+ RUN_END=$(date_fix %s)$(nsecs) ; RUNTIME=$(( $RUN_END - $RUN_START ))
2227
+
2228
+ # print message on error; set error code
2229
+ if [ "$CMD_ERR" -ne 0 ]; then
2230
+ error_print "$(datefull_from_nsecs $RUN_END) Task '$(echo $cmd|awk '$0=toupper($0)')' failed with exit code '$CMD_ERR'."
2231
+ FTPL_ERR=1
2232
+ fi
2233
+
2234
+ separator "Finished state $(error_to_string $CMD_ERR) at $(date_from_nsecs $RUN_END) - \
2235
+ Runtime $(printf "%02d:%02d:%02d.%03d" $((RUNTIME/1000000000/60/60)) $((RUNTIME/1000000000/60%60)) $((RUNTIME/1000000000%60)) $((RUNTIME/1000000%1000)) )"
2236
+
2237
+ done
2238
+
2239
+ exit ${FTPL_ERR}
2240
+ {% endraw %}