taperole 1.5.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %}