1 |
#!/bin/sh |
2 |
# |
3 |
# distupload -- a script to upload software distributions to a server |
4 |
# |
5 |
# This script is part of OSN Commons. Copyright (C) 2024 OSN Developers. |
6 |
# |
7 |
# OSN Commons is free software: you can redistribute it and/or modify it |
8 |
# under the terms of the GNU General Public License as published by |
9 |
# the Free Software Foundation, either version 3 of the License, or |
10 |
# (at your option) any later version. |
11 |
# |
12 |
# OSN Commons is distributed in the hope that it will be useful, |
13 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
# GNU General Public License for more details. |
16 |
# |
17 |
# You should have received a copy of the GNU General Public License |
18 |
# along with OSN Commons. If not, see <https://www.gnu.org/licenses/>. |
19 |
# |
20 |
|
21 |
me=$0 |
22 |
canonical_name=$(echo $me | rev | cut -d'/' -f1 | rev) |
23 |
|
24 |
if [ $(echo $me | cut -c1) = "/" ]; then |
25 |
me=$canonical_name |
26 |
fi |
27 |
|
28 |
version="1.0.0" |
29 |
|
30 |
check_requirements() { |
31 |
if [ -z "$(command -v grep)" ]; then |
32 |
echo "$me: GNU grep is required to use this script" >&2 |
33 |
exit 1 |
34 |
fi |
35 |
} |
36 |
|
37 |
show_help() { |
38 |
cat <<EOF |
39 |
Usage: |
40 |
$me [options] <file...> |
41 |
|
42 |
Options: |
43 |
-h, --help Display this help and exit |
44 |
-v, --version Display version information and exit |
45 |
-r, --remote=REMOTE Specify the remote domain to connect to. |
46 |
Defaults to 'localhost'. |
47 |
-m, --method=METHOD Specify the method to use to upload the files. |
48 |
Supported methods are: scp (default), ftp, sftp |
49 |
-u, --username=[USERNAME] Set your username to authenticate with the |
50 |
remote. |
51 |
If no parameter is passed to this option, it |
52 |
will default to your current system username. |
53 |
-p, --password=PASSWORD Specify a password to authenticate as USERNAME, |
54 |
when required. |
55 |
Note that when the METHOD is set to scp, you |
56 |
must specify the password when scp explicitly |
57 |
asks you in your terminal, command line options |
58 |
have no effect in this case. |
59 |
-l, --location=LOCATION Specify a location where the files will be |
60 |
uploaded on the remote server. |
61 |
When using ftp as METHOD, the server might have |
62 |
chroot confinement, so the "/" might point to a |
63 |
different directory. In that case, specify a |
64 |
location accordingly. |
65 |
-q, --quiet Do not print any output generated from |
66 |
ftp/sftp/scp. |
67 |
|
68 |
Feedback, bug reports and general questions should be sent |
69 |
to <[email protected]>. |
70 |
EOF |
71 |
} |
72 |
|
73 |
show_version() { |
74 |
cat <<EOF |
75 |
$canonical_name (OSN Commons) v$version |
76 |
|
77 |
This program is free software: you can redistribute it and/or modify it |
78 |
under the terms of the GNU General Public License as published by |
79 |
the Free Software Foundation, either version 3 of the License, |
80 |
or (at your option) any later version. |
81 |
|
82 |
Written by Ar Rakin. |
83 |
EOF |
84 |
} |
85 |
|
86 |
check_optarg() { |
87 |
if [ -z "$2" ]; then |
88 |
echo "$me: option '$1' requires an argument" >&2 |
89 |
echo "Try '$me --help' for more information." >&2 |
90 |
exit 1 |
91 |
fi |
92 |
} |
93 |
|
94 |
validate_domain() { |
95 |
pattern='^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?)*$' |
96 |
|
97 |
if echo "$1" | grep -E "^$pattern$" >/dev/null 2>&1; then |
98 |
return |
99 |
fi |
100 |
|
101 |
echo "$me: invalid domain '$1'" >&2 |
102 |
exit 1 |
103 |
} |
104 |
|
105 |
check_command() { |
106 |
if [ -z "$(command -v $1)" ]; then |
107 |
echo "$me: could not find $1 in \$PATH" >&2 |
108 |
echo "$me: please install $1 to use this script" >&2 |
109 |
exit 1 |
110 |
fi |
111 |
} |
112 |
|
113 |
check_files() { |
114 |
local output="" |
115 |
|
116 |
for file in "$@"; do |
117 |
output=$(stat "$file" 2>&1) |
118 |
|
119 |
if [ $? -ne 0 ]; then |
120 |
local msg=$(echo $output | cut -c 7-) |
121 |
echo "$me: $msg" >&2 |
122 |
exit 1 |
123 |
fi |
124 |
done |
125 |
} |
126 |
|
127 |
generate_gpg_signature() { |
128 |
local files="$@" |
129 |
|
130 |
if [ -z "$(command -v gpg)" ]; then |
131 |
echo "$me: gpg could not be found in \$PATH, please make sure it is installed" >&2 |
132 |
exit 1 |
133 |
fi |
134 |
|
135 |
echo "$me: generating GPG signatures, please enter your passphrase when asked" |
136 |
|
137 |
for file in $files; do |
138 |
if [ -f "$file.sig" ]; then |
139 |
echo "$me: signature file $file.sig already exists, skipping" >&2 |
140 |
continue |
141 |
fi |
142 |
|
143 |
echo "$me: generating GPG signature for $file" |
144 |
gpg --detach-sign --armor -o - -- "$file" >"$file.sig" |
145 |
|
146 |
if [ $? -ne 0 ]; then |
147 |
echo "$me: failed to generate signature for $file" >&2 |
148 |
rm -f "$file.sig" |
149 |
exit 1 |
150 |
fi |
151 |
done |
152 |
} |
153 |
|
154 |
check_requirements |
155 |
|
156 |
remote="localhost" |
157 |
method="scp" |
158 |
username="" |
159 |
password="" |
160 |
files="" |
161 |
location="/srv/www" |
162 |
quiet=0 |
163 |
|
164 |
posargs=0 |
165 |
|
166 |
while [ $# -gt 0 ]; do |
167 |
ended=0 |
168 |
|
169 |
if [ $posargs -eq 0 ]; then |
170 |
case "$1" in |
171 |
-h | -\? | --help) |
172 |
show_help |
173 |
exit |
174 |
;; |
175 |
-v | --version) |
176 |
show_version |
177 |
exit |
178 |
;; |
179 |
-m | --method) |
180 |
check_optarg $@ |
181 |
method="$2" |
182 |
shift 2 |
183 |
;; |
184 |
-q | --quiet) |
185 |
quiet=1 |
186 |
shift |
187 |
;; |
188 |
-r | --remote) |
189 |
check_optarg $@ |
190 |
validate_domain "$2" |
191 |
remote="$2" |
192 |
shift 2 |
193 |
;; |
194 |
-l | --location) |
195 |
check_optarg $@ |
196 |
location="$2" |
197 |
shift 2 |
198 |
;; |
199 |
-p | --password) |
200 |
check_optarg $@ |
201 |
password="$2" |
202 |
shift 2 |
203 |
;; |
204 |
-u | --username) |
205 |
if [ -z "$2" ]; then |
206 |
username="$USER" |
207 |
else |
208 |
username="$2" |
209 |
fi |
210 |
|
211 |
shift 2 |
212 |
;; |
213 |
--) |
214 |
posargs=1 |
215 |
shift |
216 |
;; |
217 |
-*) |
218 |
echo "$me: invalid option -- '$1'" >&2 |
219 |
echo "Try '$me --help' for more information" >&2 |
220 |
exit 1 |
221 |
;; |
222 |
*) |
223 |
ended=1 |
224 |
;; |
225 |
esac |
226 |
else |
227 |
ended=1 |
228 |
fi |
229 |
|
230 |
if [ $ended -eq 1 ]; then |
231 |
if [ ! -z "$files" ]; then |
232 |
files="$files " |
233 |
fi |
234 |
|
235 |
if echo "$file_name" | grep -q "[\ \'\"]"; then |
236 |
echo "$me: file names must not contain spaces or quotes" >&2 |
237 |
exit 1 |
238 |
fi |
239 |
|
240 |
files="$files$1" |
241 |
shift |
242 |
fi |
243 |
done |
244 |
|
245 |
if [ -z "$files" ]; then |
246 |
echo "$me: missing file operand" >&2 |
247 |
echo "Try '$me --help' for more information." >&2 |
248 |
exit 1 |
249 |
fi |
250 |
|
251 |
check_files "$files" |
252 |
|
253 |
target="$remote" |
254 |
|
255 |
if [ ! -z "$username" ]; then |
256 |
target="$username@$target" |
257 |
fi |
258 |
|
259 |
echo "$me: using $method to upload files to $target:$location" |
260 |
|
261 |
if [ ! -z "$username" ]; then |
262 |
echo "$me: authenticating as $username" |
263 |
fi |
264 |
|
265 |
generate_gpg_signature "$files" |
266 |
|
267 |
echo "$me: files to upload: $files (with GPG signatures)" |
268 |
|
269 |
sigs="" |
270 |
|
271 |
for file in $files; do |
272 |
sigs="$sigs $file.sig" |
273 |
done |
274 |
|
275 |
files="$files $sigs" |
276 |
|
277 |
time_start=$(date +%s) |
278 |
|
279 |
case $method in |
280 |
scp) |
281 |
check_command scp |
282 |
|
283 |
if [ ! -z "$password" ]; then |
284 |
echo "$me: cannot make any use of the password provided via command line options when using scp" |
285 |
echo "$me: please enter the password when asked by scp" |
286 |
fi |
287 |
|
288 |
if [ $quiet -eq 1 ]; then |
289 |
scp -- $files "$target:$location" >/dev/null 2>&1 |
290 |
else |
291 |
scp -- $files "$target:$location" |
292 |
fi |
293 |
|
294 |
code=$? |
295 |
|
296 |
if [ $code -ne 0 ]; then |
297 |
echo "$me: scp failed with exit code $code" >&2 |
298 |
exit 1 |
299 |
fi |
300 |
;; |
301 |
|
302 |
ftp | sftp) |
303 |
check_command $method |
304 |
|
305 |
if [ -z "$username" ]; then |
306 |
echo "$me: no username provided, using 'anonymous' as default" |
307 |
username=anonymous |
308 |
fi |
309 |
|
310 |
batch_file=$(mktemp) |
311 |
|
312 |
if [ $? -ne 0 ]; then |
313 |
echo "$me: failed to create temporary file" >&2 |
314 |
exit 1 |
315 |
fi |
316 |
|
317 |
if [ "$method" = "ftp" ]; then |
318 |
echo "user \"$username\" \"$password\"" >"$batch_file" |
319 |
echo "passive on" >>"$batch_file" |
320 |
fi |
321 |
|
322 |
echo "cd $location" >>"$batch_file" |
323 |
|
324 |
for file in $files; do |
325 |
echo "put $file" >>"$batch_file" |
326 |
done |
327 |
|
328 |
echo "bye" >>"$batch_file" |
329 |
|
330 |
if [ "$method" = "sftp" ]; then |
331 |
if [ $quiet -eq 1 ]; then |
332 |
sftp -oBatchMode=no -b "$batch_file" "$target" >/dev/null 2>&1 |
333 |
else |
334 |
sftp -oBatchMode=no -b "$batch_file" "$target" |
335 |
fi |
336 |
else |
337 |
if [ $quiet -eq 1 ]; then |
338 |
cat "$batch_file" | $method -inv "$remote" >/dev/null 2>&1 |
339 |
else |
340 |
cat "$batch_file" | $method -inv "$remote" |
341 |
fi |
342 |
fi |
343 |
|
344 |
code=$? |
345 |
|
346 |
if [ $code -ne 0 ]; then |
347 |
echo "$me: $method failed with exit code $code" >&2 |
348 |
exit 1 |
349 |
fi |
350 |
;; |
351 |
|
352 |
*) |
353 |
echo "$me: unsupported method '$method'" >&2 |
354 |
exit 1 |
355 |
;; |
356 |
esac |
357 |
|
358 |
time_end=$(date +%s) |
359 |
time_diff=$((time_end - time_start)) |
360 |
|
361 |
echo "$me: cleaning up signature files" |
362 |
|
363 |
for sigfile in $sigs; do |
364 |
rm -f "$sigfile" |
365 |
done |
366 |
|
367 |
echo "$me: upload complete in ${time_diff}s" |