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