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