deploy_ad-hoc_server.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. #!/bin/bash
  2. # Make sure the script is run as root user, or someone in the 'docker' group
  3. if [[ ${EUID} -ne 0 ]]; then
  4. id | grep -q "docker"
  5. if [[ $? -ne 0 ]]; then
  6. echo "This script must be run as root, or a user in the 'docker' group"
  7. exit 1
  8. fi
  9. fi
  10. usage () {
  11. echo "Usage: $0 -a <install|remove> -h <host> [-p <port (default 22)>]"
  12. echo " Uses Docker and SSH to build an ad-hoc server container and deploy it"
  13. echo " (via the 'install' action) to a previously configured host. The process"
  14. echo " can be reversed by use of the 'remove' action, which restores the host"
  15. echo " to its previous configuration."
  16. echo ""
  17. echo " This script must be run with root privilges (for access to the docker daemon)"
  18. echo " You will be prompted for SSH credentials and host-key authentication as needed"
  19. echo ""
  20. echo " Options:"
  21. echo " -a/--action"
  22. echo " Action. Allowed actions are: 'install', 'remove'. Mandatory"
  23. echo " -u/--user"
  24. echo " Username to connect with. Default 'root'. Optional, but user must belong to the docker group"
  25. echo " -h/--host"
  26. echo " Host (or IP Address) to connect to. Mandatory"
  27. echo " -p/--port"
  28. echo " Port to connect to. Default 22. Optional"
  29. echo ""
  30. exit 1
  31. }
  32. generate_temporary_credentials () {
  33. echo "..Generating temporary 4096 bit RSA keypair"
  34. ssh-keygen -t rsa -b 4096 -C "temp-psiphond-ad_hoc" -f psiphond-ad_hoc -N ""
  35. PUBLIC_KEY=$(cat psiphond-ad_hoc.pub)
  36. if [ $USER == "root" ]; then
  37. echo "${PUBLIC_KEY}" | ssh -o PreferredAuthentications=interactive,password -p $PORT $USER@$HOST "cat >> /$USER/.ssh/authorized_keys"
  38. else
  39. echo "${PUBLIC_KEY}" | ssh -o PreferredAuthentications=interactive,password -p $PORT $USER@$HOST "cat >> /home/$USER/.ssh/authorized_keys"
  40. fi
  41. if [ $? -ne 0 ]; then
  42. echo "...Failed"
  43. return 1
  44. fi
  45. }
  46. destroy_temporary_credentials () {
  47. echo "..Removing the temporary key from the remote host"
  48. PUBLIC_KEY=$(cat psiphond-ad_hoc.pub)
  49. if [ $USER == "root" ]; then
  50. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "sed -i '/temp-psiphond-ad_hoc/d' /$USER/.ssh/authorized_keys"
  51. else
  52. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "sed -i '/temp-psiphond-ad_hoc/d' /home/$USER/.ssh/authorized_keys"
  53. fi
  54. if [ $? -ne 0 ]; then
  55. echo "...Failed"
  56. return 1
  57. fi
  58. echo "..Removing the local temporary keys"
  59. rm psiphond-ad_hoc psiphond-ad_hoc.pub
  60. if [ $? -ne 0 ]; then
  61. echo "...Failed"
  62. return 1
  63. fi
  64. }
  65. docker_build_builder () {
  66. echo "..Building the docker container 'psiphond-build'"
  67. docker build -f Dockerfile-binary-builder -t psiphond-builder .
  68. if [ $? -ne 0 ]; then
  69. echo "...Failed"
  70. return 1
  71. fi
  72. }
  73. docker_build_psiphond_binary () {
  74. echo "..Building 'psiphond' binary"
  75. cd .. && docker run --rm -v $PWD/.:/go/src/github.com/Psiphon-Labs/psiphon-tunnel-core psiphond-builder
  76. if [ $? -ne 0 ]; then
  77. echo "...Failed"
  78. cd $BASE
  79. return 1
  80. fi
  81. cd $BASE
  82. stat psiphond > /dev/null 2>&1
  83. if [ $? -ne 0 ]; then
  84. echo "...'psiphond' binary file not found"
  85. return 1
  86. fi
  87. }
  88. docker_build_psiphond_container () {
  89. echo "..Building the '${CONTAINER_TAG}' container"
  90. docker build -t ${CONTAINER_TAG} .
  91. if [ $? -ne 0 ]; then
  92. echo "...Failed"
  93. return 1
  94. fi
  95. }
  96. save_image () {
  97. echo "..Saving docker image to archive"
  98. docker save ${CONTAINER_TAG} | gzip > $ARCHIVE
  99. if [ $? -ne 0 ]; then
  100. echo "...Failed"
  101. return 1
  102. fi
  103. stat $ARCHIVE > /dev/null 2>&1
  104. if [ $? -ne 0 ]; then
  105. echo "...'${ARCHIVE}' not found"
  106. return 1
  107. fi
  108. }
  109. put_and_load_image () {
  110. echo "..Copying '${ARCHIVE}' to '${HOST}:/tmp'"
  111. scp -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -P $PORT $ARCHIVE $USER@$HOST:/tmp/
  112. if [ $? -ne 0 ]; then
  113. echo "...Failed"
  114. return 1
  115. fi
  116. echo "..Loading image into remote docker"
  117. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "zcat /tmp/$ARCHIVE | docker load && rm /tmp/$ARCHIVE"
  118. if [ $? -ne 0 ]; then
  119. echo "...Failed"
  120. return 1
  121. fi
  122. }
  123. remove_image () {
  124. echo "..Removing image from remote docker"
  125. # Single quotes prevents local substitution of variables
  126. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST 'docker rmi $(docker images -q psiphond/ad-hoc)'
  127. if [ $? -ne 0 ]; then
  128. echo "...Failed"
  129. return 1
  130. fi
  131. }
  132. put_systemd_dropin () {
  133. echo "..Creating ad-hoc environment variables file"
  134. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "sed 's/DOCKER_CONTENT_TRUST=1/DOCKER_CONTENT_TRUST=0/' /opt/psiphon/psiphond/config/psiphond.env | sed '/CONTAINER_TAG=/d' > /opt/psiphon/psiphond/config/psiphond.env.adhoc"
  135. if [ $? -ne 0 ]; then
  136. echo "...Failed"
  137. return 1
  138. fi
  139. echo "..Creating systemd unit drop-in"
  140. cat <<- EOF > ad-hoc.conf
  141. [Service]
  142. EnvironmentFile=/opt/psiphon/psiphond/config/psiphond.env.adhoc
  143. # Clear previous pre-start command before setting new one
  144. # Execute these commands prior to starting the service
  145. # "-" before the command means errors are not fatal to service startup
  146. ExecStartPre=
  147. ExecStartPre=-/usr/bin/docker stop %p-run
  148. ExecStartPre=-/usr/bin/docker rm %p-run
  149. # Clear previous start command before setting new one
  150. ExecStart=
  151. ExecStart=/usr/bin/docker run --rm \$CONTAINER_PORT_STRING \$CONTAINER_VOLUME_STRING \$CONTAINER_ULIMIT_STRING \$CONTAINER_SYSCTL_STRING --name %p-run ${CONTAINER_TAG}
  152. EOF
  153. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "mkdir -p /etc/systemd/system/psiphond.service.d"
  154. echo "..Ensuring drop-in directory is available"
  155. if [ $? -ne 0 ]; then
  156. echo "...Failed"
  157. return 1
  158. fi
  159. scp -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -P $PORT ad-hoc.conf $USER@$HOST:/etc/systemd/system/psiphond.service.d/
  160. echo "..Copying drop-in to remote host"
  161. if [ $? -ne 0 ]; then
  162. echo "...Failed"
  163. return 1
  164. fi
  165. rm ad-hoc.conf
  166. }
  167. remove_systemd_dropin () {
  168. echo "..Removing systemd unit drop-in"
  169. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "[ ! -f /etc/systemd/system/psiphond.service.d/ad-hoc.conf ] || rm /etc/systemd/system/psiphond.service.d/ad-hoc.conf"
  170. if [ $? -ne 0 ]; then
  171. echo "...Failed"
  172. return 1
  173. fi
  174. echo "..Removing ad-hoc environment variables file"
  175. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "[ ! -f /opt/psiphon/psiphond/config/psiphond.env.adhoc ] || rm /opt/psiphon/psiphond/config/psiphond.env.adhoc"
  176. if [ $? -ne 0 ]; then
  177. echo "...Failed"
  178. return 1
  179. fi
  180. }
  181. reload_systemd () {
  182. echo "..Reloading systemd unit file cache"
  183. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "systemctl daemon-reload"
  184. if [ $? -ne 0 ]; then
  185. echo "...Failed"
  186. return 1
  187. fi
  188. }
  189. restart_psiphond () {
  190. echo "..Restarting the 'psiphond' service"
  191. ssh -i psiphond-ad_hoc -o PreferredAuthentications=publickey -o IdentitiesOnly=yes -p $PORT $USER@$HOST "systemctl restart psiphond"
  192. if [ $? -ne 0 ]; then
  193. echo "...Failed"
  194. return 1
  195. fi
  196. }
  197. install() {
  198. docker_build_builder
  199. if [ $? -ne 0 ]; then
  200. return 1
  201. fi
  202. docker_build_psiphond_binary
  203. if [ $? -ne 0 ]; then
  204. return 1
  205. fi
  206. docker_build_psiphond_container
  207. if [ $? -ne 0 ]; then
  208. return 1
  209. fi
  210. save_image
  211. if [ $? -ne 0 ]; then
  212. return 1
  213. fi
  214. put_and_load_image
  215. if [ $? -ne 0 ]; then
  216. return 1
  217. fi
  218. put_systemd_dropin
  219. if [ $? -ne 0 ]; then
  220. return 1
  221. fi
  222. reload_systemd
  223. if [ $? -ne 0 ]; then
  224. return 1
  225. fi
  226. restart_psiphond
  227. if [ $? -ne 0 ]; then
  228. return 1
  229. fi
  230. }
  231. remove () {
  232. remove_systemd_dropin
  233. if [ $? -ne 0 ]; then
  234. echo "...Failed, continuing removal steps. Drop-in file or ad-hoc environment file may still exist"
  235. fi
  236. reload_systemd
  237. if [ $? -ne 0 ]; then
  238. echo "...Failed, continuing removal steps. 'systemctl daemon-reload' may still be needed to restore previous functionality"
  239. fi
  240. restart_psiphond
  241. if [ $? -ne 0 ]; then
  242. echo "...Failed, continuing removal steps. The old 'psiphond' may still be running"
  243. fi
  244. remove_image
  245. if [ $? -ne 0 ]; then
  246. echo "...Final step failed, check for leftover images manually with 'docker images psiphond/ad-hoc', then use 'docker rmi' to remove them"
  247. destroy_temporary_credentials
  248. return 1
  249. fi
  250. }
  251. # Locate and change to the directory containing the script
  252. BASE=$( cd "$(dirname "$0")" ; pwd -P )
  253. cd $BASE
  254. # Validate that we're in a git repository and store the revision
  255. REV=$(git rev-parse --short HEAD)
  256. if [ $? -ne 0 ]; then
  257. echo "Could not store git revision, aborting"
  258. exit 1
  259. fi
  260. # Parse command line arguments
  261. while [[ $# -gt 1 ]]; do
  262. key="$1"
  263. case $key in
  264. -a|--action)
  265. ACTION="$2"
  266. shift
  267. if [ "${ACTION}" != "install" ] && [ "${ACTION}" != "remove" ]; then
  268. echo "Got: '${ACTION}', Expected one of: 'install', or 'remove', aborting."
  269. exit 1
  270. fi
  271. ;;
  272. -u|--user)
  273. USER="$2"
  274. shift
  275. ;;
  276. -h|--host)
  277. HOST="$2"
  278. shift
  279. ;;
  280. -p|--port)
  281. PORT="$2"
  282. shift
  283. ;;
  284. *)
  285. usage
  286. ;;
  287. esac
  288. shift
  289. done
  290. # Validate all mandatory parameters were set
  291. if [ -z $ACTION ]; then
  292. echo "Action is a required parameter, aborting."
  293. echo ""
  294. usage
  295. fi
  296. if [ -z $HOST ]; then
  297. echo "Host is a required parameter, aborting."
  298. echo ""
  299. usage
  300. fi
  301. # Set default values for unset optional paramters
  302. if [ -z $USER ]; then
  303. USER=root
  304. fi
  305. if [ -z $PORT ]; then
  306. PORT=22
  307. fi
  308. # Set up other global variables
  309. TIMESTAMP=$(date +'%Y-%m-%d_%H-%M')
  310. CONTAINER_TAG="psiphond/ad-hoc:${TIMESTAMP}"
  311. ARCHIVE="psiphond-ad-hoc.tar.gz"
  312. # Display choices and pause
  313. echo "[$(date)] Ad-Hoc psiphond deploy starting."
  314. echo ""
  315. echo "Configuration:"
  316. echo " - Action: ${ACTION}"
  317. echo " - User: ${USER}"
  318. echo " - Host: ${HOST}"
  319. echo " - Port: ${PORT}"
  320. if [ $ACTION == "install" ]; then
  321. echo " - Containter Tag: ${CONTAINER_TAG}"
  322. echo " - Archive Name: ${ARCHIVE}"
  323. fi
  324. echo ""
  325. echo "Pausing 5 seconds to allow for ^C prior to starting"
  326. sleep 5
  327. generate_temporary_credentials
  328. if [ $? -ne 0 ]; then
  329. echo "Inability to generate temporary credentials is fatal, aborting"
  330. exit 1
  331. fi
  332. if [ "${ACTION}" == "install" ]; then
  333. install
  334. if [ $? -ne 0 ]; then
  335. echo "....Error during install, rolling back"
  336. remove
  337. if [ $? -ne 0 ]; then
  338. echo "....Rollback failed"
  339. fi
  340. fi
  341. elif [ "${ACTION}" == "remove" ]; then
  342. remove
  343. if [ $? -ne 0 ]; then
  344. echo "...Rollback failed"
  345. fi
  346. else
  347. echo "Parameter validation passed, but action was not 'install' or 'remove', aborting"
  348. exit 1
  349. fi
  350. destroy_temporary_credentials
  351. echo "[$(date)] Ad-Hoc psiphond deploy ended."