The main Bubble source repository. Contains the Bubble API server, the web UI, documentation and utilities. https://getbubblenow.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

360 lines
13 KiB

  1. #!/bin/bash
  2. #
  3. # Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
  4. #
  5. # Connect an Ubuntu 20.04 system to a Bubble
  6. # You must run this as a user who has "sudo" privileges
  7. #
  8. # Usage:
  9. #
  10. # ubuntu_connect_bubble [hostname]
  11. #
  12. # hostname : bubble to connect this Ubuntu system to
  13. # If not specified, an env var will be used (see below)
  14. # If neither BUBBLE_HOST (hostname) or BUBBLE_API (URL) are specified,
  15. # then you'll be asked to enter the Bubble hostname
  16. #
  17. # Environment Variables
  18. #
  19. # BUBBLE_HOST : hostname of the Bubble to use. The API base URI will be https://<hostname>/api
  20. # BUBBLE_API : full base URL of the Bubble API to use. Typically this ends with /api
  21. # BUBBLE_USER : account to use
  22. # BUBBLE_PASS : password for account
  23. # BUBBLE_CERT_WIZ : Set this to 'true' to run the Ubuntu certificate wizard, even if it looks
  24. # like the certificate is already installed
  25. #
  26. # This command will:
  27. # * Disconnect from any current Bubble
  28. # * Check if the device already exists for the Bubble. If not, creates the device
  29. # * Check if we have a local copy of the certificate and vpn.conf files. If not, download them
  30. # * Ensure routing configuration is correct
  31. # * Install the certificate (this step requires your interaction)
  32. # * Install the VPN config
  33. # * Start the VPN
  34. #
  35. function die() {
  36. echo 1>&2 "$0: fatal error: ${1}"
  37. exit 1
  38. }
  39. # from https://stackoverflow.com/a/34407620
  40. function uriencode { jq -nr --arg v "$1" '$v|@uri'; }
  41. SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)"
  42. INSTALL_PACKAGES=""
  43. if [[ -z "$(which wg)" ]] ; then
  44. echo "Adding WireGuard to install list ..."
  45. INSTALL_PACKAGES="${INSTALL_PACKAGES} wireguard"
  46. fi
  47. if [[ -z "$(which bget)" ]] ; then
  48. export PATH=${PATH}:${SCRIPT_DIR}
  49. if [[ -z "$(which bget)" ]] ; then
  50. die "bget command not found, even after adding ${SCRIPT_DIR} to PATH"
  51. fi
  52. fi
  53. if [[ -z "$(which java)" ]] ; then
  54. echo "Adding Java 11 to install list ..."
  55. INSTALL_PACKAGES="${INSTALL_PACKAGES} openjdk-11-jre"
  56. else
  57. JAVA_VERSION="$(java -version 2>&1 | grep "openjdk version" | awk -F '"' '{print $2}' | awk -F '.' '{print $1}')"
  58. if [[ -z "${JAVA_VERSION}" ]] ; then
  59. echo "Adding Java 11 to install list (unknown java version found) ..."
  60. INSTALL_PACKAGES="${INSTALL_PACKAGES} openjdk-11-jre"
  61. elif [[ ${JAVA_VERSION} -lt 11 ]] ; then
  62. echo "Adding Java 11 to install list (unexpected java version: ${JAVA_VERSION}) ..."
  63. INSTALL_PACKAGES="${INSTALL_PACKAGES} openjdk-11-jre"
  64. fi
  65. fi
  66. if [[ -z "$(which jq)" ]] ; then
  67. echo "Adding jq to install list ..."
  68. INSTALL_PACKAGES="${INSTALL_PACKAGES} jq"
  69. fi
  70. if [[ -z "$(which route)" ]] ; then
  71. echo "Adding net-tools to install list ..."
  72. INSTALL_PACKAGES="${INSTALL_PACKAGES} net-tools"
  73. fi
  74. if [[ -z "$(which resolvconf)" ]] ; then
  75. echo "Adding resolvconf to install list ..."
  76. INSTALL_PACKAGES="${INSTALL_PACKAGES} resolvconf"
  77. fi
  78. if [[ -n "${INSTALL_PACKAGES}" ]] ; then
  79. echo "
  80. The following apt packages will be installed:
  81. ${INSTALL_PACKAGES}
  82. Press Enter to continue with installation, or Control-C to exit"
  83. read -r DUMMY || die "Bubble connection canceled"
  84. # Do not quote INSTALL_PACKAGES, we want to install all packages...
  85. sudo apt update && sudo apt install -y ${INSTALL_PACKAGES} || die "Error installing packages: ${INSTALL_PACKAGES}"
  86. fi
  87. ARG_HOST="${1}"
  88. if [[ -z "${ARG_HOST}" ]] ; then
  89. if [[ -z "${BUBBLE_API}" ]] ; then
  90. if [[ -z "${BUBBLE_HOST}" ]] ; then
  91. echo -n "No hostname argument provided and neither BUBBLE_API nor BUBBLE_HOST env vars were defined.
  92. Enter Bubble hostname: "
  93. read -r ARG_HOST
  94. echo
  95. if [[ -z "${ARG_HOST}" ]] ; then
  96. die "Empty Bubble hostname"
  97. fi
  98. BUBBLE_API="https://${ARG_HOST}/api"
  99. else
  100. BUBBLE_API="https://${BUBBLE_HOST}/api"
  101. fi
  102. fi
  103. else
  104. BUBBLE_API="https://${ARG_HOST}/api"
  105. fi
  106. if [[ -z "${BUBBLE_USER}" ]] ; then
  107. echo -n "BUBBLE_USER env var not defined.
  108. Enter Bubble username: "
  109. read -r BUBBLE_USER
  110. echo
  111. fi
  112. if [[ -z "${BUBBLE_PASS}" ]] ; then
  113. echo -n "BUBBLE_PASS env var not defined.
  114. Enter Bubble password: "
  115. read -rs BUBBLE_PASS
  116. echo
  117. fi
  118. BUBBLE_HOST="$(echo -n "${BUBBLE_API}" | awk -F '/' '{print $3}' | awk -F ':' '{print $1}')"
  119. if [[ -z "${BUBBLE_HOST}" ]] ; then
  120. die "No hostname could be determined from BUBBLE_API: ${BUBBLE_API}"
  121. fi
  122. echo "Logging in to Bubble ${BUBBLE_API} ..."
  123. export BUBBLE_API
  124. export BUBBLE_USER
  125. export BUBBLE_PASS
  126. export BUBBLE_SKIP_PATH_WARNING=true
  127. bget me | jq .email > /dev/null || die "Error logging into Bubble with API: ${BUBBLE_API}"
  128. # Ensure WireGuard is not running
  129. if [[ -n "$(sudo wg show)" ]] ; then
  130. echo "Stopping WireGuard VPN ..."
  131. sudo wg-quick down wg0
  132. fi
  133. BUBBLE_DEVICE_BASE="${HOME}/bubble_devices"
  134. BUBBLE_DIR="${BUBBLE_DEVICE_BASE}/${BUBBLE_HOST}"
  135. mkdir -p "${BUBBLE_DIR}" || die "Error creating directory: ${BUBBLE_DIR}"
  136. CERT_FILE="${BUBBLE_DIR}"/bubble-${BUBBLE_HOST}.crt
  137. VPN_FILE="${BUBBLE_DIR}"/vpn.conf
  138. DEVICE_NAME_FILE=${BUBBLE_DIR}/bubble_device_name
  139. if [[ ! -f "${DEVICE_NAME_FILE}" ]] ; then
  140. DISTRO="$(cat /etc/os-release | grep ^NAME= | awk -F '=' '{print $2}' | tr -d '"')"
  141. echo -n "Linux_${DISTRO}_$(hostname -s)" >"${DEVICE_NAME_FILE}"
  142. echo "Initialized device: ${DEVICE_NAME_FILE}"
  143. fi
  144. DEVICE_NAME="$(cat "${DEVICE_NAME_FILE}")"
  145. # Check API to see if device is already registered
  146. if [[ $(bgetn me/devices | grep -c "${DEVICE_NAME}") -eq 0 ]] ; then
  147. echo "Device not found, registering now: ${DEVICE_NAME}"
  148. echo "{
  149. \"name\": \"${DEVICE_NAME}\",
  150. \"deviceType\": \"Linux\"
  151. }" | bput me/devices - > "${BUBBLE_DIR}/device.json" || die "Error creating device"
  152. rm -f "${VPN_FILE}" "${CERT_FILE}" || die "Error removing obsolete vpn.conf and cert files"
  153. else
  154. echo "Device already registered: ${DEVICE_NAME}"
  155. if [[ ! -f "${BUBBLE_DIR}/device.json" ]] ; then
  156. echo "Downloading device.json file ..."
  157. bget "me/devices/$(uriencode "${DEVICE_NAME}")" > "${BUBBLE_DIR}/device.json" || die "Error downloading device JSON file"
  158. fi
  159. fi
  160. # Do we have both a cert file and a vpn.conf? If so, we are done
  161. if [[ ! -s "${CERT_FILE}" ]] ; then
  162. # Download cert file
  163. echo "Downloading certificate ..."
  164. bget auth/cacert?deviceType=Linux --raw >"${CERT_FILE}" || die "Error downloading certificate file"
  165. fi
  166. if [[ ! -s "${VPN_FILE}" ]] ; then
  167. # Download vpn.conf file
  168. echo "Downloading vpn.conf ..."
  169. bget "me/devices/${DEVICE_NAME}/vpn/vpn.conf" --raw >"${VPN_FILE}" || die "Error downloading vpn.conf file"
  170. fi
  171. CURRENT_DEVICE_DIR="${BUBBLE_DEVICE_BASE}/current"
  172. CREATE_CURRENT_SYMLINK=0
  173. if [[ -e "${CURRENT_DEVICE_DIR}" ]] ; then
  174. if [[ "$(basename "$(readlink -f "${CURRENT_DEVICE_DIR}")")" != "${BUBBLE_HOST}" ]] ; then
  175. CREATE_CURRENT_SYMLINK=1
  176. fi
  177. else
  178. CREATE_CURRENT_SYMLINK=1
  179. fi
  180. if [[ ${CREATE_CURRENT_SYMLINK} -eq 1 ]] ; then
  181. echo "Marking ${BUBBLE_HOST} as current Bubble (cd ${BUBBLE_DEVICE_BASE} && ln -sf ${BUBBLE_HOST} current)..."
  182. cd "${BUBBLE_DEVICE_BASE}" && ln -sf "${BUBBLE_HOST}" current || die "Error creating symlink ${BUBBLE_DEVICE_BASE}/current -> ${BUBBLE_DEVICE_BASE}/${BUBBLE_HOST}"
  183. fi
  184. CURRENT_DEVICE_JSON="$(cat "${CURRENT_DEVICE_DIR}/device.json")"
  185. if [[ -z "${CURRENT_DEVICE_JSON}" ]] ; then
  186. die "File was empty: ${CURRENT_DEVICE_JSON}"
  187. fi
  188. DEVICE_UUID="$(echo "${CURRENT_DEVICE_JSON}" | jq -r .uuid)"
  189. if [[ -z "${DEVICE_UUID}" ]] ; then
  190. die "No device UUID could be read from JSON: ${CURRENT_DEVICE_JSON}"
  191. fi
  192. CERTS_DIR=/usr/share/ca-certificates/extra
  193. if [[ ! -d "${CERTS_DIR}" ]] ; then
  194. echo "Creating ${CERTS_DIR}"
  195. sudo mkdir -p ${CERTS_DIR}
  196. fi
  197. CERT_DEST="${CERTS_DIR}/$(basename "${CERT_FILE}")"
  198. RUN_CERT_WIZ=0
  199. if cmp "${CERT_FILE}" "${CERT_DEST}" ; then
  200. if [[ -z "${BUBBLE_CERT_WIZ}" ]] ; then
  201. echo "Certificate already installed: ${CERT_DEST}"
  202. elif [[ "${BUBBLE_CERT_WIZ}" == "true" ]] ; then
  203. RUN_CERT_WIZ=1
  204. else
  205. die "Invalid value for BUBBLE_CERT_WIZ env var: ${BUBBLE_CERT_WIZ} (expected 'true' or nothing)"
  206. fi
  207. else
  208. RUN_CERT_WIZ=1
  209. echo "Copying certificate to ${CERT_DEST} ..."
  210. sudo cp "${CERT_FILE}" "${CERT_DEST}" || die "Error copying certificate: ${CERT_FILE} -> ${CERT_DEST}"
  211. fi
  212. if [[ ${RUN_CERT_WIZ} -eq 1 ]] ; then
  213. echo "
  214. ### Finishing Certificate Installation for Ubuntu
  215. We're going to run the Ubuntu certificate wizard via:
  216. sudo dpkg-reconfigure ca-certificates
  217. When the wizard opens:
  218. - Press Enter with 'Yes' selected for 'Trust new certificates from certificate authorities?'
  219. - Press the space bar to check the box for your Bubble certificate
  220. - Press Enter to commit the changes
  221. To continue and run the Ubuntu certificate wizard, press Enter now
  222. "
  223. read -r DUMMY
  224. sudo dpkg-reconfigure ca-certificates || die "Error reconfiguring system CA certificates"
  225. fi
  226. # Ensure ssh route table exists
  227. if [[ $(sudo cat /etc/iproute2/rt_tables | grep -c ssh) -eq 0 ]] ; then
  228. sudo bash -c 'echo "2 ssh" >> /etc/iproute2/rt_tables'
  229. else
  230. echo "ssh table already exists in rt_tables"
  231. fi
  232. REBOOT_FILE=/tmp/reboot-required.bubble
  233. # Set sysctl vars
  234. SYSCTL="/etc/sysctl.conf"
  235. SYS_RP_FILTER="net.ipv4.conf.all.rp_filter"
  236. if [[ $(sudo cat ${SYSCTL} | grep -v '^#' | grep -c "${SYS_RP_FILTER}") -eq 0 ]] ; then
  237. sudo bash -c 'echo "'"${SYS_RP_FILTER}"' = 2" >> '"${SYSCTL}"'' || die "Error setting ${SYS_RP_FILTER} = 2 in ${SYSCTL}"
  238. touch ${REBOOT_FILE}
  239. else
  240. echo "${SYS_RP_FILTER} already defined in ${SYSCTL}"
  241. fi
  242. SYS_IP_FORWARD="net.ipv4.ip_forward"
  243. if [[ $(cat ${SYSCTL} | grep -v '^#' | grep -c "${SYS_IP_FORWARD}") -eq 0 ]] ; then
  244. sudo bash -c 'echo "'"${SYS_IP_FORWARD}"' = 1" >> '"${SYSCTL}"'' || die "Error setting ${SYS_IP_FORWARD} = 1 in ${SYSCTL}"
  245. touch ${REBOOT_FILE}
  246. else
  247. echo "${SYS_IP_FORWARD} already defined in ${SYSCTL}"
  248. fi
  249. if [[ -f ${REBOOT_FILE} ]] ; then
  250. echo "
  251. sysctl settings changed, reboot required.
  252. Press Enter to reboot, or Control-C to exit and reboot later"
  253. read -r DUMMY || die "Please reboot later to ensure sysctl changes take effect"
  254. sudo bash -c 'sync && shutdown -r now' && exit 1 || die "Error rebooting"
  255. fi
  256. WG_CONF=/etc/wireguard/wg0.conf
  257. echo "Copying vpn.conf to ${WG_CONF} ..."
  258. sudo cp "${VPN_FILE}" ${WG_CONF} || die "Error copying vpn.conf: ${VPN_FILE} -> ${WG_CONF}"
  259. if [[ $(sudo cat "${WG_CONF}" | grep -c PostUp) -ne 0 ]] ; then
  260. echo "${WG_CONF} already contains PostUp directives"
  261. else
  262. GATEWAY=$(route -n | grep "^0.0.0.0" | awk '{print $2}')
  263. if [[ -z "${GATEWAY}" ]] ; then
  264. die "Error determining gateway IP using 'route -n'"
  265. fi
  266. IFACE=$(route -n | grep "^0.0.0.0" | awk '{print $8}')
  267. if [[ -z "${IFACE}" ]] ; then
  268. die "Error determining gateway interface using 'route -n'"
  269. fi
  270. WG_TEMP=$(mktemp /tmp/wg.conf.XXXXXXX)
  271. sudo cat ${WG_CONF} | grep -A 3 '\[Interface\]' >> "${WG_TEMP}"
  272. echo "PostUp = ip route add default via ${GATEWAY} dev ${IFACE} table ssh
  273. PostUp = ip rule add fwmark 0x2 table ssh
  274. PostUp = /sbin/iptables -A OUTPUT -t mangle -o wg0 -p tcp --sport 22 -j MARK --set-mark 2
  275. PreDown = /sbin/iptables -D OUTPUT -t mangle -o wg0 -p tcp --sport 22 -j MARK --set-mark 2
  276. PreDown = ip rule del fwmark 0x2 table ssh
  277. PreDown = ip route del default via ${GATEWAY} dev ${IFACE} table ssh
  278. " >> "${WG_TEMP}"
  279. sudo cat ${WG_CONF} | grep -A 10 '\[Peer\]' >> "${WG_TEMP}"
  280. sudo mv "${WG_TEMP}" ${WG_CONF} || die "Error installing updated ${WG_CONF} file"
  281. fi
  282. echo "Starting WireGuard VPN ..."
  283. sudo wg-quick up wg0 || die "Error starting WireGuard"
  284. echo "
  285. ===============================================================
  286. ======= Ubuntu device successfully connected to Bubble! =======
  287. ===============================================================
  288. Device UUID : ${DEVICE_UUID}
  289. Device Name : ${DEVICE_NAME}
  290. Bubble Host : ${BUBBLE_HOST}
  291. Device Dir : ${BUBBLE_DIR}
  292. Certificate : ${CERT_DEST}
  293. VPN Config : ${WG_CONF}
  294. Public IP : $(curl -s http://checkip.amazonaws.com/)
  295. "
  296. if [[ -n "$(which firefox)" ]] ; then
  297. echo "
  298. ===========================================================
  299. ===================== Firefox Warning =====================
  300. ===========================================================
  301. Firefox does not use the Ubuntu certificate store. It has its
  302. own certificate store.
  303. Firefox will not properly connect to most websites until you
  304. install the Bubble certificate in Firefox.
  305. To install the Bubble Certificate in Firefox:
  306. * Open Firefox
  307. * In the search box (top right of page), enter \"certificate\"
  308. * Scroll down, click \"View Certificates\" button
  309. * Click \"Import\" button
  310. * Select your certificate file: ${CERT_FILE}
  311. * Check the box \"Trust this CA to identify websites\" and click OK.
  312. More detailed instructions (with screenshots) can be found here:
  313. https://git.bubblev.org/bubblev/bubble-docs/src/branch/master/cert_instructions/firefox_cert.md
  314. "
  315. fi