Added the -i option, changed the name of the temporary remote dir to include the...
[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 [-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 the -h option is provided @XREMOTE_HOST is ignored.
60
61   If the -i option is provided, all the files are installed and
62   scripts run in the specified directory on the remote host, but the
63   main executable and post-run commands are ignored
64
65   If no argument is provided to @XREMOTE_HOST, and the -h option is
66   not specified, the environment variable \$XREMOTE_HOST is used
67   instead
68
69   Contact <francois@fleuret.org> for comments.
70
71 EOF
72     return 0
73 }
74
75 function cleanup_remote_tmp () {
76     if [[ "${REMOTE_HOST}" ]] && [[ "${REMOTE_DIR}" ]] && [[ ! "${ARG_DIR}" ]]
77     then
78         echo "xremote: Clean up remote workdir."
79     fi
80 }
81
82 ######################################################################
83
84 while [[ "$1" =~ ^- ]]
85 do
86     case "$1"
87     in
88         -h)
89             shift
90             ARG_HOST="$1"
91             echo "xremote: remote forced to ${ARG_HOST}"
92             ;;
93
94         -i)
95             shift
96             ARG_DIR="$1"
97             echo "xremote: remote dir set to ${ARG_DIR}"
98             ;;
99
100         *)
101             echo "Unknown option $1"
102             exit 1
103             ;;
104     esac
105     shift
106 done
107
108 ######################################################################
109
110 [[ -a "$1" ]] || (help && exit 1)
111
112 main="$(basename "$1")"
113
114 cd "$(dirname "$1")"
115
116 shift
117
118 trap cleanup_remote_tmp EXIT
119
120 ######################################################################
121
122 while read line
123 do
124
125     if [[ "${line}" =~ '@XREMOTE' ]]
126     then
127
128         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
129         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
130
131         case "${label}" in
132
133             EXEC)
134                 check_remote_is_defined
135                 [[ "${REMOTE_EXEC}" ]] && (exit "Remote executable already defined!" >&2 && exit 1)
136                 REMOTE_EXEC="${value}"
137                 ;;
138
139             PRE)
140                 check_remote_is_defined
141                 echo "xremote: ${value}"
142                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
143                 ;;
144
145             SEND)
146                 check_remote_is_defined
147                 echo "xremote: -- sending files --------------------------------------------"
148                 tar ch ${value} | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mxv"
149                 ;;
150
151             HOST)
152                 [[ "${REMOTE_DIR}" ]] && (exit "Remote host already defined!" >&2 && exit 1)
153                 REMOTE_HOST="${ARG_HOST}" # Host given in argument has priority
154                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${value}"
155                 [[ "${REMOTE_HOST}" ]] || REMOTE_HOST="${XREMOTE_HOST}"
156                 [[ "${REMOTE_HOST}" ]] || (echo "xremote: No remote host specified." >&2 && exit 1)
157                 if [[ "${ARG_DIR}" ]]
158                 then
159                     ssh </dev/null "${REMOTE_HOST}" "mkdir -p \"${ARG_DIR}\""
160                     REMOTE_DIR="${ARG_DIR}"
161                 else
162                     REMOTE_DIR="$(ssh </dev/null "${REMOTE_HOST}" mktemp -d /tmp/xremote_\$\(whoami\)_from_"$(hostname)_$(date +%Y%m%d_%H%M%S)".XXXXXX)"
163                 fi
164                 echo "xremote: target is ${REMOTE_HOST}"
165                 ;;
166         esac
167     fi
168
169 done < "${main}"
170
171 ######################################################################
172
173 check_remote_is_defined
174
175 tar c "${main}" | ssh "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar mx"
176
177 if [[ "${ARG_DIR}" ]]
178 then
179     echo "xremote: everything has been set up in ${REMOTE_HOST}:${ARG_DIR}"
180     exit 0
181 fi
182
183 echo "xremote: -- running the executable -----------------------------------"
184
185 if [[ "${REMOTE_EXEC}" ]]
186 then
187     REMOTE_COMMAND="${REMOTE_EXEC} ${main}"
188 else
189     REMOTE_COMMAND="./${main}"
190 fi
191
192 ######################################################################
193
194 # I find this slightly ugly ...
195 for s in "$@"
196 do
197   quoted_args="${quoted_args} \"${s}\""
198 done
199
200 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${REMOTE_COMMAND} ${quoted_args}"
201
202 ######################################################################
203
204 # Disable globbing to keep wildcards for the remote side
205
206 echo "xremote: -- retrieving files -----------------------------------------"
207
208 set -f
209
210 while read line
211 do
212     if [[ "${line}" =~ '@XREMOTE' ]]
213     then
214         label=$(echo "${line}" | sed -e 's/^.*@XREMOTE_\([^:]*\):.*$/\1/')
215         value=$(echo "${line}" | sed -e 's/^.*@XREMOTE_[^:]*: *\(.*\)$/\1/')
216         case "${label}" in
217             POST)
218                 check_remote_is_defined
219                 echo "xremote: ${value}"
220                 ssh < /dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && ${value}"
221                 ;;
222
223             GET)
224                 check_remote_is_defined
225                 ssh </dev/null "${REMOTE_HOST}" "cd \"${REMOTE_DIR}\" && tar 2>/dev/null c ${value}" | tar mxv
226                 ;;
227         esac
228     fi
229 done < "${main}"
230
231 set +f
232
233 echo "xremote: -- finished -------------------------------------------------"
234
235 ######################################################################