1 | #!/bin/sh
|
---|
2 | # Sign files and upload them.
|
---|
3 |
|
---|
4 | scriptversion=2021-04-11.09; # UTC
|
---|
5 |
|
---|
6 | # Copyright (C) 2004-2021 Free Software Foundation, Inc.
|
---|
7 | #
|
---|
8 | # This program is free software; you can redistribute it and/or modify
|
---|
9 | # it under the terms of the GNU General Public License as published by
|
---|
10 | # the Free Software Foundation; either version 2, or (at your option)
|
---|
11 | # any later version.
|
---|
12 | #
|
---|
13 | # This program is distributed in the hope that it will be useful,
|
---|
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
16 | # GNU General Public License for more details.
|
---|
17 | #
|
---|
18 | # You should have received a copy of the GNU General Public License
|
---|
19 | # along with this program. If not, see <https://www.gnu.org/licenses/>.
|
---|
20 |
|
---|
21 | # Originally written by Alexandre Duret-Lutz <[email protected]>.
|
---|
22 | # The master copy of this file is maintained in the gnulib Git repository.
|
---|
23 | # Please send bug reports and feature requests to [email protected].
|
---|
24 |
|
---|
25 | set -e
|
---|
26 |
|
---|
27 | GPG=gpg
|
---|
28 | # Choose the proper version of gpg, so as to avoid a
|
---|
29 | # "gpg-agent is not available in this session" error
|
---|
30 | # when gpg-agent is version 2 but gpg is still version 1.
|
---|
31 | # FIXME-2020: remove, once all major distros ship gpg version 2 as /usr/bin/gpg
|
---|
32 | gpg_agent_version=`(gpg-agent --version) 2>/dev/null | sed -e '2,$d' -e 's/^[^0-9]*//'`
|
---|
33 | case "$gpg_agent_version" in
|
---|
34 | 2.*)
|
---|
35 | gpg_version=`(gpg --version) 2>/dev/null | sed -e '2,$d' -e 's/^[^0-9]*//'`
|
---|
36 | case "$gpg_version" in
|
---|
37 | 1.*)
|
---|
38 | if (type gpg2) >/dev/null 2>/dev/null; then
|
---|
39 | # gpg2 is present.
|
---|
40 | GPG=gpg2
|
---|
41 | else
|
---|
42 | # gpg2 is missing. Ubuntu users should install the package 'gnupg2'.
|
---|
43 | echo "WARNING: Using 'gpg', which is too old. You should install 'gpg2'." 1>&2
|
---|
44 | fi
|
---|
45 | ;;
|
---|
46 | esac
|
---|
47 | ;;
|
---|
48 | esac
|
---|
49 |
|
---|
50 | GPG="${GPG} --batch --no-tty"
|
---|
51 | conffile=.gnuploadrc
|
---|
52 | to=
|
---|
53 | dry_run=false
|
---|
54 | replace=
|
---|
55 | symlink_files=
|
---|
56 | delete_files=
|
---|
57 | delete_symlinks=
|
---|
58 | collect_var=
|
---|
59 | dbg=
|
---|
60 | nl='
|
---|
61 | '
|
---|
62 |
|
---|
63 | usage="Usage: $0 [OPTION]... [CMD] FILE... [[CMD] FILE...]
|
---|
64 |
|
---|
65 | Sign all FILES, and process them at the destinations specified with --to.
|
---|
66 | If CMD is not given, it defaults to uploading. See examples below.
|
---|
67 |
|
---|
68 | Commands:
|
---|
69 | --delete delete FILES from destination
|
---|
70 | --symlink create symbolic links
|
---|
71 | --rmsymlink remove symbolic links
|
---|
72 | -- treat the remaining arguments as files to upload
|
---|
73 |
|
---|
74 | Options:
|
---|
75 | --to DEST specify a destination DEST for FILES
|
---|
76 | (multiple --to options are allowed)
|
---|
77 | --user NAME sign with key NAME
|
---|
78 | --replace allow replacements of existing files
|
---|
79 | --symlink-regex[=EXPR] use sed script EXPR to compute symbolic link names
|
---|
80 | -n, --dry-run do nothing, show what would have been done
|
---|
81 | (including the constructed directive file)
|
---|
82 | --version output version information and exit
|
---|
83 | -h, --help print this help text and exit
|
---|
84 |
|
---|
85 | If --symlink-regex is given without EXPR, then the link target name
|
---|
86 | is created by replacing the version information with '-latest', e.g.:
|
---|
87 | foo-1.3.4.tar.gz -> foo-latest.tar.gz
|
---|
88 |
|
---|
89 | Recognized destinations are:
|
---|
90 | alpha.gnu.org:DIRECTORY
|
---|
91 | savannah.gnu.org:DIRECTORY
|
---|
92 | savannah.nongnu.org:DIRECTORY
|
---|
93 | ftp.gnu.org:DIRECTORY
|
---|
94 | build directive files and upload files by FTP
|
---|
95 | download.gnu.org.ua:{alpha|ftp}/DIRECTORY
|
---|
96 | build directive files and upload files by SFTP
|
---|
97 | [user@]host:DIRECTORY upload files with scp
|
---|
98 |
|
---|
99 | Options and commands are applied in order. If the file $conffile exists
|
---|
100 | in the current working directory, its contents are prepended to the
|
---|
101 | actual command line options. Use this to keep your defaults. Comments
|
---|
102 | (#) and empty lines in $conffile are allowed.
|
---|
103 |
|
---|
104 | <https://www.gnu.org/prep/maintain/html_node/Automated-FTP-Uploads.html>
|
---|
105 | gives some further background.
|
---|
106 |
|
---|
107 | Examples:
|
---|
108 | 1. Upload foobar-1.0.tar.gz to ftp.gnu.org:
|
---|
109 | gnupload --to ftp.gnu.org:foobar foobar-1.0.tar.gz
|
---|
110 |
|
---|
111 | 2. Upload foobar-1.0.tar.gz and foobar-1.0.tar.xz to ftp.gnu.org:
|
---|
112 | gnupload --to ftp.gnu.org:foobar foobar-1.0.tar.gz foobar-1.0.tar.xz
|
---|
113 |
|
---|
114 | 3. Same as above, and also create symbolic links to foobar-latest.tar.*:
|
---|
115 | gnupload --to ftp.gnu.org:foobar \\
|
---|
116 | --symlink-regex \\
|
---|
117 | foobar-1.0.tar.gz foobar-1.0.tar.xz
|
---|
118 |
|
---|
119 | 4. Create a symbolic link foobar-latest.tar.gz -> foobar-1.0.tar.gz
|
---|
120 | and likewise for the corresponding .sig file:
|
---|
121 | gnupload --to ftp.gnu.org:foobar \\
|
---|
122 | --symlink foobar-1.0.tar.gz foobar-latest.tar.gz \\
|
---|
123 | foobar-1.0.tar.gz.sig foobar-latest.tar.gz.sig
|
---|
124 | or (equivalent):
|
---|
125 | gnupload --to ftp.gnu.org:foobar \\
|
---|
126 | --symlink foobar-1.0.tar.gz foobar-latest.tar.gz \\
|
---|
127 | --symlink foobar-1.0.tar.gz.sig foobar-latest.tar.gz.sig
|
---|
128 |
|
---|
129 | 5. Upload foobar-0.9.90.tar.gz to two sites:
|
---|
130 | gnupload --to alpha.gnu.org:foobar \\
|
---|
131 | --to sources.redhat.com:~ftp/pub/foobar \\
|
---|
132 | foobar-0.9.90.tar.gz
|
---|
133 |
|
---|
134 | 6. Delete oopsbar-0.9.91.tar.gz and upload foobar-0.9.91.tar.gz
|
---|
135 | (the -- terminates the list of files to delete):
|
---|
136 | gnupload --to alpha.gnu.org:foobar \\
|
---|
137 | --to sources.redhat.com:~ftp/pub/foobar \\
|
---|
138 | --delete oopsbar-0.9.91.tar.gz \\
|
---|
139 | -- foobar-0.9.91.tar.gz
|
---|
140 |
|
---|
141 | gnupload executes a program ncftpput to do the transfers; if you don't
|
---|
142 | happen to have an ncftp package installed, the ncftpput-ftp script in
|
---|
143 | the build-aux/ directory of the gnulib package
|
---|
144 | (https://savannah.gnu.org/projects/gnulib) may serve as a replacement.
|
---|
145 |
|
---|
146 | Send patches and bug reports to <[email protected]>."
|
---|
147 |
|
---|
148 | copyright_year=`echo "$scriptversion" | sed -e 's/[^0-9].*//'`
|
---|
149 | copyright="Copyright (C) ${copyright_year} Free Software Foundation, Inc.
|
---|
150 | License GPLv2+: GNU GPL version 2 or later <https://gnu.org/licenses/gpl.html>.
|
---|
151 | This is free software: you are free to change and redistribute it.
|
---|
152 | There is NO WARRANTY, to the extent permitted by law."
|
---|
153 |
|
---|
154 | # Read local configuration file
|
---|
155 | if test -r "$conffile"; then
|
---|
156 | echo "$0: Reading configuration file $conffile"
|
---|
157 | conf=`sed 's/#.*$//;/^$/d' "$conffile" | tr "\015$nl" ' '`
|
---|
158 | eval set x "$conf \"\$@\""
|
---|
159 | shift
|
---|
160 | fi
|
---|
161 |
|
---|
162 | while test -n "$1"; do
|
---|
163 | case $1 in
|
---|
164 | -*)
|
---|
165 | collect_var=
|
---|
166 | case $1 in
|
---|
167 | -h | --help)
|
---|
168 | echo "$usage"
|
---|
169 | exit $?
|
---|
170 | ;;
|
---|
171 | --to)
|
---|
172 | if test -z "$2"; then
|
---|
173 | echo "$0: Missing argument for --to" 1>&2
|
---|
174 | exit 1
|
---|
175 | elif echo "$2" | grep 'ftp-upload\.gnu\.org' >/dev/null; then
|
---|
176 | echo "$0: Use ftp.gnu.org:PKGNAME or alpha.gnu.org:PKGNAME" >&2
|
---|
177 | echo "$0: for the destination, not ftp-upload.gnu.org (which" >&2
|
---|
178 | echo "$0: is used for direct ftp uploads, not with gnupload)." >&2
|
---|
179 | echo "$0: See --help and its examples if need be." >&2
|
---|
180 | exit 1
|
---|
181 | else
|
---|
182 | to="$to $2"
|
---|
183 | shift
|
---|
184 | fi
|
---|
185 | ;;
|
---|
186 | --user)
|
---|
187 | if test -z "$2"; then
|
---|
188 | echo "$0: Missing argument for --user" 1>&2
|
---|
189 | exit 1
|
---|
190 | else
|
---|
191 | GPG="$GPG --local-user $2"
|
---|
192 | shift
|
---|
193 | fi
|
---|
194 | ;;
|
---|
195 | --delete)
|
---|
196 | collect_var=delete_files
|
---|
197 | ;;
|
---|
198 | --replace)
|
---|
199 | replace="replace: true"
|
---|
200 | ;;
|
---|
201 | --rmsymlink)
|
---|
202 | collect_var=delete_symlinks
|
---|
203 | ;;
|
---|
204 | --symlink-regex=*)
|
---|
205 | symlink_expr=`expr "$1" : '[^=]*=\(.*\)'`
|
---|
206 | ;;
|
---|
207 | --symlink-regex)
|
---|
208 | symlink_expr='s|-[0-9][0-9\.]*\(-[0-9][0-9]*\)\{0,1\}\.|-latest.|'
|
---|
209 | ;;
|
---|
210 | --symlink)
|
---|
211 | collect_var=symlink_files
|
---|
212 | ;;
|
---|
213 | -n | --dry-run)
|
---|
214 | dry_run=:
|
---|
215 | ;;
|
---|
216 | --version)
|
---|
217 | echo "gnupload $scriptversion"
|
---|
218 | echo "$copyright"
|
---|
219 | exit 0
|
---|
220 | ;;
|
---|
221 | --)
|
---|
222 | shift
|
---|
223 | break
|
---|
224 | ;;
|
---|
225 | -*)
|
---|
226 | echo "$0: Unknown option '$1', try '$0 --help'" 1>&2
|
---|
227 | exit 1
|
---|
228 | ;;
|
---|
229 | esac
|
---|
230 | ;;
|
---|
231 | *)
|
---|
232 | if test -z "$collect_var"; then
|
---|
233 | break
|
---|
234 | else
|
---|
235 | eval "$collect_var=\"\$$collect_var $1\""
|
---|
236 | fi
|
---|
237 | ;;
|
---|
238 | esac
|
---|
239 | shift
|
---|
240 | done
|
---|
241 |
|
---|
242 | dprint()
|
---|
243 | {
|
---|
244 | echo "Running $* ..."
|
---|
245 | }
|
---|
246 |
|
---|
247 | if $dry_run; then
|
---|
248 | dbg=dprint
|
---|
249 | fi
|
---|
250 |
|
---|
251 | if test -z "$to"; then
|
---|
252 | echo "$0: Missing destination sites" >&2
|
---|
253 | exit 1
|
---|
254 | fi
|
---|
255 |
|
---|
256 | if test -n "$symlink_files"; then
|
---|
257 | x=`echo "$symlink_files" | sed 's/[^ ]//g;s/ //g'`
|
---|
258 | if test -n "$x"; then
|
---|
259 | echo "$0: Odd number of symlink arguments" >&2
|
---|
260 | exit 1
|
---|
261 | fi
|
---|
262 | fi
|
---|
263 |
|
---|
264 | if test $# = 0; then
|
---|
265 | if test -z "${symlink_files}${delete_files}${delete_symlinks}"; then
|
---|
266 | echo "$0: No file to upload" 1>&2
|
---|
267 | exit 1
|
---|
268 | fi
|
---|
269 | else
|
---|
270 | # Make sure all files exist. We don't want to ask
|
---|
271 | # for the passphrase if the script will fail.
|
---|
272 | for file
|
---|
273 | do
|
---|
274 | if test ! -f $file; then
|
---|
275 | echo "$0: Cannot find '$file'" 1>&2
|
---|
276 | exit 1
|
---|
277 | elif test -n "$symlink_expr"; then
|
---|
278 | linkname=`echo $file | sed "$symlink_expr"`
|
---|
279 | if test -z "$linkname"; then
|
---|
280 | echo "$0: symlink expression produces empty results" >&2
|
---|
281 | exit 1
|
---|
282 | elif test "$linkname" = $file; then
|
---|
283 | echo "$0: symlink expression does not alter file name" >&2
|
---|
284 | exit 1
|
---|
285 | fi
|
---|
286 | fi
|
---|
287 | done
|
---|
288 | fi
|
---|
289 |
|
---|
290 | # Make sure passphrase is not exported in the environment.
|
---|
291 | unset passphrase
|
---|
292 | unset passphrase_fd_0
|
---|
293 | GNUPGHOME=${GNUPGHOME:-$HOME/.gnupg}
|
---|
294 |
|
---|
295 | # Reset PATH to be sure that echo is a built-in. We will later use
|
---|
296 | # 'echo $passphrase' to output the passphrase, so it is important that
|
---|
297 | # it is a built-in (third-party programs tend to appear in 'ps'
|
---|
298 | # listings with their arguments...).
|
---|
299 | # Remember this script runs with 'set -e', so if echo is not built-in
|
---|
300 | # it will exit now.
|
---|
301 | if $dry_run || grep -q "^use-agent" $GNUPGHOME/gpg.conf; then :; else
|
---|
302 | PATH=/empty echo -n "Enter GPG passphrase: "
|
---|
303 | stty -echo
|
---|
304 | read -r passphrase
|
---|
305 | stty echo
|
---|
306 | echo
|
---|
307 | passphrase_fd_0="--passphrase-fd 0"
|
---|
308 | fi
|
---|
309 |
|
---|
310 | if test $# -ne 0; then
|
---|
311 | for file
|
---|
312 | do
|
---|
313 | echo "Signing $file ..."
|
---|
314 | rm -f $file.sig
|
---|
315 | echo "$passphrase" | $dbg $GPG $passphrase_fd_0 -ba -o $file.sig $file
|
---|
316 | done
|
---|
317 | fi
|
---|
318 |
|
---|
319 |
|
---|
320 | # mkdirective DESTDIR BASE FILE STMT
|
---|
321 | # Arguments: See upload, below
|
---|
322 | mkdirective ()
|
---|
323 | {
|
---|
324 | stmt="$4"
|
---|
325 | if test -n "$3"; then
|
---|
326 | stmt="
|
---|
327 | filename: $3$stmt"
|
---|
328 | fi
|
---|
329 |
|
---|
330 | cat >${2}.directive<<EOF
|
---|
331 | version: 1.2
|
---|
332 | directory: $1
|
---|
333 | comment: gnupload v. $scriptversion$stmt
|
---|
334 | EOF
|
---|
335 | if $dry_run; then
|
---|
336 | echo "File ${2}.directive:"
|
---|
337 | cat ${2}.directive
|
---|
338 | echo "File ${2}.directive:" | sed 's/./-/g'
|
---|
339 | fi
|
---|
340 | }
|
---|
341 |
|
---|
342 | mksymlink ()
|
---|
343 | {
|
---|
344 | while test $# -ne 0
|
---|
345 | do
|
---|
346 | echo "symlink: $1 $2"
|
---|
347 | shift
|
---|
348 | shift
|
---|
349 | done
|
---|
350 | }
|
---|
351 |
|
---|
352 | # upload DEST DESTDIR BASE FILE STMT FILES
|
---|
353 | # Arguments:
|
---|
354 | # DEST Destination site;
|
---|
355 | # DESTDIR Destination directory;
|
---|
356 | # BASE Base name for the directive file;
|
---|
357 | # FILE Name of the file to distribute (may be empty);
|
---|
358 | # STMT Additional statements for the directive file;
|
---|
359 | # FILES List of files to upload.
|
---|
360 | upload ()
|
---|
361 | {
|
---|
362 | dest=$1
|
---|
363 | destdir=$2
|
---|
364 | base=$3
|
---|
365 | file=$4
|
---|
366 | stmt=$5
|
---|
367 | files=$6
|
---|
368 |
|
---|
369 | rm -f $base.directive $base.directive.asc
|
---|
370 | case $dest in
|
---|
371 | alpha.gnu.org:*)
|
---|
372 | mkdirective "$destdir" "$base" "$file" "$stmt"
|
---|
373 | echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
|
---|
374 | $dbg ncftpput ftp-upload.gnu.org /incoming/alpha $files $base.directive.asc
|
---|
375 | ;;
|
---|
376 | ftp.gnu.org:*)
|
---|
377 | mkdirective "$destdir" "$base" "$file" "$stmt"
|
---|
378 | echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
|
---|
379 | $dbg ncftpput ftp-upload.gnu.org /incoming/ftp $files $base.directive.asc
|
---|
380 | ;;
|
---|
381 | savannah.gnu.org:*)
|
---|
382 | if test -z "$files"; then
|
---|
383 | echo "$0: warning: standalone directives not applicable for $dest" >&2
|
---|
384 | fi
|
---|
385 | $dbg ncftpput savannah.gnu.org /incoming/savannah/$destdir $files
|
---|
386 | ;;
|
---|
387 | savannah.nongnu.org:*)
|
---|
388 | if test -z "$files"; then
|
---|
389 | echo "$0: warning: standalone directives not applicable for $dest" >&2
|
---|
390 | fi
|
---|
391 | $dbg ncftpput savannah.nongnu.org /incoming/savannah/$destdir $files
|
---|
392 | ;;
|
---|
393 | download.gnu.org.ua:alpha/*|download.gnu.org.ua:ftp/*)
|
---|
394 | destdir_p1=`echo "$destdir" | sed 's,^[^/]*/,,'`
|
---|
395 | destdir_topdir=`echo "$destdir" | sed 's,/.*,,'`
|
---|
396 | mkdirective "$destdir_p1" "$base" "$file" "$stmt"
|
---|
397 | echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
|
---|
398 | for f in $files $base.directive.asc
|
---|
399 | do
|
---|
400 | echo put $f
|
---|
401 | done | $dbg sftp -b - puszcza.gnu.org.ua:/incoming/$destdir_topdir
|
---|
402 | ;;
|
---|
403 | /*)
|
---|
404 | dest_host=`echo "$dest" | sed 's,:.*,,'`
|
---|
405 | mkdirective "$destdir" "$base" "$file" "$stmt"
|
---|
406 | echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
|
---|
407 | $dbg cp $files $base.directive.asc $dest_host
|
---|
408 | ;;
|
---|
409 | *)
|
---|
410 | if test -z "$files"; then
|
---|
411 | echo "$0: warning: standalone directives not applicable for $dest" >&2
|
---|
412 | fi
|
---|
413 | $dbg scp $files $dest
|
---|
414 | ;;
|
---|
415 | esac
|
---|
416 | rm -f $base.directive $base.directive.asc
|
---|
417 | }
|
---|
418 |
|
---|
419 | #####
|
---|
420 | # Process any standalone directives
|
---|
421 | stmt=
|
---|
422 | if test -n "$symlink_files"; then
|
---|
423 | stmt="$stmt
|
---|
424 | `mksymlink $symlink_files`"
|
---|
425 | fi
|
---|
426 |
|
---|
427 | for file in $delete_files
|
---|
428 | do
|
---|
429 | stmt="$stmt
|
---|
430 | archive: $file"
|
---|
431 | done
|
---|
432 |
|
---|
433 | for file in $delete_symlinks
|
---|
434 | do
|
---|
435 | stmt="$stmt
|
---|
436 | rmsymlink: $file"
|
---|
437 | done
|
---|
438 |
|
---|
439 | if test -n "$stmt"; then
|
---|
440 | for dest in $to
|
---|
441 | do
|
---|
442 | destdir=`echo $dest | sed 's/[^:]*://'`
|
---|
443 | upload "$dest" "$destdir" "`hostname`-$$" "" "$stmt"
|
---|
444 | done
|
---|
445 | fi
|
---|
446 |
|
---|
447 | # Process actual uploads
|
---|
448 | for dest in $to
|
---|
449 | do
|
---|
450 | for file
|
---|
451 | do
|
---|
452 | echo "Uploading $file to $dest ..."
|
---|
453 | stmt=
|
---|
454 | #
|
---|
455 | # allowing file replacement is all or nothing.
|
---|
456 | if test -n "$replace"; then stmt="$stmt
|
---|
457 | $replace"
|
---|
458 | fi
|
---|
459 | #
|
---|
460 | files="$file $file.sig"
|
---|
461 | destdir=`echo $dest | sed 's/[^:]*://'`
|
---|
462 | if test -n "$symlink_expr"; then
|
---|
463 | linkname=`echo $file | sed "$symlink_expr"`
|
---|
464 | stmt="$stmt
|
---|
465 | symlink: $file $linkname
|
---|
466 | symlink: $file.sig $linkname.sig"
|
---|
467 | fi
|
---|
468 | upload "$dest" "$destdir" "$file" "$file" "$stmt" "$files"
|
---|
469 | done
|
---|
470 | done
|
---|
471 |
|
---|
472 | exit 0
|
---|
473 |
|
---|
474 | # Local variables:
|
---|
475 | # eval: (add-hook 'before-save-hook 'time-stamp)
|
---|
476 | # time-stamp-start: "scriptversion="
|
---|
477 | # time-stamp-format: "%:y-%02m-%02d.%02H"
|
---|
478 | # time-stamp-time-zone: "UTC0"
|
---|
479 | # time-stamp-end: "; # UTC"
|
---|
480 | # End:
|
---|