Added some error messages.
[xremote.git] / xremote.sh
1 #!/bin/bash
2
3 #########################################################################
4 # This program is free software: you can redistribute it and/or modify  #
5 # it under the terms of the version 3 of the GNU General Public License #
6 # as published by the Free Software Foundation.                         #
7 #                                                                       #
8 # This program is distributed in the hope that it will be useful, but   #
9 # WITHOUT ANY WARRANTY; without even the implied warranty of            #
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      #
11 # General Public License for more details.                              #
12 #                                                                       #
13 # You should have received a copy of the GNU General Public License     #
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.  #
15 #                                                                       #
16 # Written by and Copyright (C) Francois Fleuret                         #
17 # Contact <francois@fleuret.org> for comments & bug reports             #
18 #########################################################################
19
20 set -e
21
22 # set -o pipefail
23
24 ######################################################################
25
26 function check_remote_is_defined () {
27     if [[ "${REMOTE_HOST}" ]] && [[ "${REMOTE_DIR}" ]]
28     then
29         return 0
30     else
31         echo "@XREMOTE_HOST should come first." >&2
32         exit 1
33     fi
34 }
35
36 function help () {
37     cat <<EOF
38 xremote.sh [--help] [-h <remote_host>] [-i <remote_dir>] <script> [script arguments]
39
40   This script takes a script as argument and executes it remotely in a
41   temporary directory on a ssh-accessible server.
42
43   It parses the script first to find embedded arguments which define
44   the hostname on which to run, the files to send, the files to get
45   back when the execution is over, and commands to execute before
46   running the executable remotely.
47
48   These arguments can appear multiple times, except the one that
49   specifies the remote host.
50
51   Example:
52
53     @XREMOTE_HOST: elk.fleuret.org
54     @XREMOTE_EXEC: python3
55     @XREMOTE_SEND: main.cf
56     @XREMOTE_GET: *.dat
57     @XREMOTE_PRE: ln -s /home/fleuret/data/pytorch ./data
58
59   If a file with the same name as the script with the .xremote
60   extension appended to it exists, arguments will be read from it by
61   default.
62
63   If the -h option is provided @XREMOTE_HOST is ignored.
64
65   If the -i option is provided, all the files are installed and
66   scripts run in the specified directory on the remote host, but the
67   main executable and post-run commands are ignored
68
69   If no argument is provided to @XREMOTE_HOST, and the -h option is
70   not specified, the environment variable \$XREMOTE_HOST is used
71   instead
72
73   Contact <francois@fleuret.org> for comments.
74
75 EOF
76     return 0
77 }
78
79 function cleanup_remote_tmp () {
80     if [[ "${REMOTE_HOST}" ]] && [[ "${REMOTE_DIR}" ]] && [[ ! "${ARG_DIR}" ]]
81     then
82         echo "xremote: Clean up remote workdir."
83     fi
84 }
85
86 ######################################################################
87
88 while [[ "$1" =~ ^- ]]
89 do
90     case "$1"
91     in
92         -h)
93             shift
94             ARG_HOST="$1"
95             [[ ${ARG_HOST} ]] || (echo "xremote: Hostname missing." && exit 1)
96             echo "xremote: remote forced to ${ARG_HOST}"
97             ;;
98
99         -i)
100             shift
101             ARG_DIR="$1"
102             [[ ${ARG_DIR} ]] || (echo "xremote: Directory missing." && exit 1)
103             echo "xremote: remote dir set to ${ARG_DIR}"
104             ;;
105
106         --help)
107             help
108             exit 0
109             ;;
110
111         *)
112             echo "xremote: Unknown option $1"
113             exit 1
114             ;;
115     esac
116     shift
117 done
118
119 ######################################################################
120
121 [[ "$1" ]] || (echo "xremote: Script name missing" && exit 1)
122
123 [[ -a "$1" ]] || (help && echo >&2 "xremote: Cannot find script \`$1'" && exit 1)
124
125 cd "$(dirname "$1")"
126
127 main="$(basename "$1")"
128 main_config="${main}.xremote"
129
130 if [[ -f "${main_config}" ]]
131 then
132     echo "xremote: found ${main_config}"
133 else
134     main_config="${main}"
135 fi
136
137 shift
138
139 trap cleanup_remote_tmp EXIT
140
141 ######################################################################
142
143 while read line
144 do
145
146     if [[ "${line}" =~ '@XREMOTE' ]]
147     then
148
149         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
150         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
151
152         case "${label}" in
153
154             EXEC)
155                 check_remote_is_defined
156                 [[ "${REMOTE_EXEC}" ]] && (exit "Remote executable already defined!" >&2 && exit 1)
157                 REMOTE_EXEC="${value}"
158                 ;;
159
160             PRE)
161                 check_remote_is_defined
162                 echo "xremote: ${value}"
163                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
164                 ;;
165
166             SEND)
167                 check_remote_is_defined
168                 echo "xremote: -- sending files --------------------------------------------"
169                 tar ch ${value} | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mxv"
170                 ;;
171
172             HOST)
173                 [[ "${REMOTE_DIR}" ]] && (exit "Remote host already defined!" >&2 && exit 1)
174                 REMOTE_HOST="${ARG_HOST}" # Host given in argument has priority
175                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${value}"
176                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${XREMOTE_HOST}"
177                 [[ "${REMOTE_HOST}" ]] || (echo "xremote: No remote host specified." >&2 && exit 1)
178                 if [[ "${ARG_DIR}" ]]
179                 then
180                     ssh </dev/null "${REMOTE_HOST}" "mkdir -p \"${ARG_DIR}\""
181                     REMOTE_DIR="${ARG_DIR}"
182                 else
183                     REMOTE_DIR="$(ssh </dev/null "${REMOTE_HOST}" mktemp -d /tmp/xremote_\$\(whoami\)_from_"$(hostname)_$(date +%Y%m%d_%H%M%S)".XXXXXX)"
184                 fi
185                 echo "xremote: target is ${REMOTE_HOST}"
186                 ;;
187         esac
188     fi
189
190 done < "${main_config}"
191
192 ######################################################################
193
194 check_remote_is_defined
195
196 tar c "${main}" | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mx"
197
198 if [[ "${ARG_DIR}" ]]
199 then
200     echo "xremote: everything has been set up in ${REMOTE_HOST}:${ARG_DIR}"
201     exit 0
202 fi
203
204 echo "xremote: -- running the executable -----------------------------------"
205
206 if [[ "${REMOTE_EXEC}" ]]
207 then
208     REMOTE_COMMAND="${REMOTE_EXEC} ${main}"
209 else
210     REMOTE_COMMAND="./${main}"
211 fi
212
213 ######################################################################
214
215 # I find this slightly ugly ...
216
217 for s in "$@"
218 do
219   quoted_args="${quoted_args} \"${s}\""
220 done
221
222 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${REMOTE_COMMAND} ${quoted_args}"
223
224 ######################################################################
225
226 # Disable globbing to keep wildcards for the remote side
227
228 echo "xremote: -- retrieving files -----------------------------------------"
229
230 set -f
231
232 while read line
233 do
234     if [[ "${line}" =~ '@XREMOTE' ]]
235     then
236         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
237         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
238         case "${label}" in
239             POST)
240                 check_remote_is_defined
241                 echo "xremote: ${value}"
242                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
243                 ;;
244
245             GET)
246                 check_remote_is_defined
247                 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar 2>/dev/null c ${value}" | tar mxv
248                 ;;
249         esac
250     fi
251
252 done < "${main_config}"
253
254 set +f
255
256 echo "xremote: -- finished -------------------------------------------------"
257
258 ######################################################################