|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- #!/bin/bash
- #
- # Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
- #
- # Connect an Ubuntu 20.04 system to a Bubble
- # You must run this as a user who has "sudo" privileges
- #
- # Usage:
- #
- # ubuntu_connect_bubble [hostname]
- #
- # hostname : bubble to connect this Ubuntu system to
- # If not specified, an env var will be used (see below)
- # If neither BUBBLE_HOST (hostname) or BUBBLE_API (URL) are specified,
- # then you'll be asked to enter the Bubble hostname
- #
- # Environment Variables
- #
- # BUBBLE_HOST : hostname of the Bubble to use. The API base URI will be https://<hostname>/api
- # BUBBLE_API : full base URL of the Bubble API to use. Typically this ends with /api
- # BUBBLE_USER : account to use
- # BUBBLE_PASS : password for account
- # BUBBLE_CERT_WIZ : Set this to 'true' to run the Ubuntu certificate wizard, even if it looks
- # like the certificate is already installed
- #
- # This command will:
- # * Disconnect from any current Bubble
- # * Check if the device already exists for the Bubble. If not, creates the device
- # * Check if we have a local copy of the certificate and vpn.conf files. If not, download them
- # * Ensure routing configuration is correct
- # * Install the certificate (this step requires your interaction)
- # * Install the VPN config
- # * Start the VPN
- #
- function die() {
- echo 1>&2 "$0: fatal error: ${1}"
- exit 1
- }
-
- # from https://stackoverflow.com/a/34407620
- function uriencode { jq -nr --arg v "$1" '$v|@uri'; }
-
- SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)"
-
- INSTALL_PACKAGES=""
- if [[ -z "$(which wg)" ]] ; then
- echo "Adding WireGuard to install list ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} wireguard"
- fi
-
- if [[ -z "$(which bget)" ]] ; then
- export PATH=${PATH}:${SCRIPT_DIR}
- if [[ -z "$(which bget)" ]] ; then
- die "bget command not found, even after adding ${SCRIPT_DIR} to PATH"
- fi
- fi
-
- if [[ -z "$(which java)" ]] ; then
- echo "Adding Java 11 to install list ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} openjdk-11-jre"
- else
- JAVA_VERSION="$(java -version 2>&1 | grep "openjdk version" | awk -F '"' '{print $2}' | awk -F '.' '{print $1}')"
- if [[ -z "${JAVA_VERSION}" ]] ; then
- echo "Adding Java 11 to install list (unknown java version found) ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} openjdk-11-jre"
- elif [[ ${JAVA_VERSION} -lt 11 ]] ; then
- echo "Adding Java 11 to install list (unexpected java version: ${JAVA_VERSION}) ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} openjdk-11-jre"
- fi
- fi
-
- if [[ -z "$(which jq)" ]] ; then
- echo "Adding jq to install list ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} jq"
- fi
- if [[ -z "$(which route)" ]] ; then
- echo "Adding net-tools to install list ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} net-tools"
- fi
- if [[ -z "$(which resolvconf)" ]] ; then
- echo "Adding resolvconf to install list ..."
- INSTALL_PACKAGES="${INSTALL_PACKAGES} resolvconf"
- fi
-
- if [[ -n "${INSTALL_PACKAGES}" ]] ; then
- echo "
- The following apt packages will be installed:
-
- ${INSTALL_PACKAGES}
-
- Press Enter to continue with installation, or Control-C to exit"
- read -r DUMMY || die "Bubble connection canceled"
-
- # Do not quote INSTALL_PACKAGES, we want to install all packages...
- sudo apt update && sudo apt install -y ${INSTALL_PACKAGES} || die "Error installing packages: ${INSTALL_PACKAGES}"
- fi
-
- ARG_HOST="${1}"
- if [[ -z "${ARG_HOST}" ]] ; then
- if [[ -z "${BUBBLE_API}" ]] ; then
- if [[ -z "${BUBBLE_HOST}" ]] ; then
- echo -n "No hostname argument provided and neither BUBBLE_API nor BUBBLE_HOST env vars were defined.
- Enter Bubble hostname: "
- read -r ARG_HOST
- echo
- if [[ -z "${ARG_HOST}" ]] ; then
- die "Empty Bubble hostname"
- fi
- BUBBLE_API="https://${ARG_HOST}/api"
- else
- BUBBLE_API="https://${BUBBLE_HOST}/api"
- fi
- fi
- else
- BUBBLE_API="https://${ARG_HOST}/api"
- fi
-
- if [[ -z "${BUBBLE_USER}" ]] ; then
- echo -n "BUBBLE_USER env var not defined.
- Enter Bubble username: "
- read -r BUBBLE_USER
- echo
- fi
- if [[ -z "${BUBBLE_PASS}" ]] ; then
- echo -n "BUBBLE_PASS env var not defined.
- Enter Bubble password: "
- read -rs BUBBLE_PASS
- echo
- fi
-
- BUBBLE_HOST="$(echo -n "${BUBBLE_API}" | awk -F '/' '{print $3}' | awk -F ':' '{print $1}')"
- if [[ -z "${BUBBLE_HOST}" ]] ; then
- die "No hostname could be determined from BUBBLE_API: ${BUBBLE_API}"
- fi
-
- echo "Logging in to Bubble ${BUBBLE_API} ..."
- export BUBBLE_API
- export BUBBLE_USER
- export BUBBLE_PASS
- export BUBBLE_SKIP_PATH_WARNING=true
- bget me | jq .email > /dev/null || die "Error logging into Bubble with API: ${BUBBLE_API}"
-
- # Ensure WireGuard is not running
- if [[ -n "$(sudo wg show)" ]] ; then
- echo "Stopping WireGuard VPN ..."
- sudo wg-quick down wg0
- fi
-
- BUBBLE_DEVICE_BASE="${HOME}/bubble_devices"
- BUBBLE_DIR="${BUBBLE_DEVICE_BASE}/${BUBBLE_HOST}"
- mkdir -p "${BUBBLE_DIR}" || die "Error creating directory: ${BUBBLE_DIR}"
-
- CERT_FILE="${BUBBLE_DIR}"/bubble-${BUBBLE_HOST}.crt
- VPN_FILE="${BUBBLE_DIR}"/vpn.conf
-
- DEVICE_NAME_FILE=${BUBBLE_DIR}/bubble_device_name
- if [[ ! -f "${DEVICE_NAME_FILE}" ]] ; then
- DISTRO="$(cat /etc/os-release | grep ^NAME= | awk -F '=' '{print $2}' | tr -d '"')"
- echo -n "Linux_${DISTRO}_$(hostname -s)" >"${DEVICE_NAME_FILE}"
- echo "Initialized device: ${DEVICE_NAME_FILE}"
- fi
- DEVICE_NAME="$(cat "${DEVICE_NAME_FILE}")"
-
- # Check API to see if device is already registered
- if [[ $(bgetn me/devices | grep -c "${DEVICE_NAME}") -eq 0 ]] ; then
- echo "Device not found, registering now: ${DEVICE_NAME}"
- echo "{
- \"name\": \"${DEVICE_NAME}\",
- \"deviceType\": \"Linux\"
- }" | bput me/devices - > "${BUBBLE_DIR}/device.json" || die "Error creating device"
- rm -f "${VPN_FILE}" "${CERT_FILE}" || die "Error removing obsolete vpn.conf and cert files"
- else
- echo "Device already registered: ${DEVICE_NAME}"
- if [[ ! -f "${BUBBLE_DIR}/device.json" ]] ; then
- echo "Downloading device.json file ..."
- bget "me/devices/$(uriencode "${DEVICE_NAME}")" > "${BUBBLE_DIR}/device.json" || die "Error downloading device JSON file"
- fi
- fi
-
- # Do we have both a cert file and a vpn.conf? If so, we are done
- if [[ ! -s "${CERT_FILE}" ]] ; then
- # Download cert file
- echo "Downloading certificate ..."
- bget auth/cacert?deviceType=Linux --raw >"${CERT_FILE}" || die "Error downloading certificate file"
- fi
- if [[ ! -s "${VPN_FILE}" ]] ; then
- # Download vpn.conf file
- echo "Downloading vpn.conf ..."
- bget "me/devices/${DEVICE_NAME}/vpn/vpn.conf" --raw >"${VPN_FILE}" || die "Error downloading vpn.conf file"
- fi
-
- CURRENT_DEVICE_DIR="${BUBBLE_DEVICE_BASE}/current"
- CREATE_CURRENT_SYMLINK=0
- if [[ -e "${CURRENT_DEVICE_DIR}" ]] ; then
- if [[ "$(basename "$(readlink -f "${CURRENT_DEVICE_DIR}")")" != "${BUBBLE_HOST}" ]] ; then
- CREATE_CURRENT_SYMLINK=1
- fi
- else
- CREATE_CURRENT_SYMLINK=1
- fi
- if [[ ${CREATE_CURRENT_SYMLINK} -eq 1 ]] ; then
- echo "Marking ${BUBBLE_HOST} as current Bubble (cd ${BUBBLE_DEVICE_BASE} && ln -sf ${BUBBLE_HOST} current)..."
- cd "${BUBBLE_DEVICE_BASE}" && ln -sf "${BUBBLE_HOST}" current || die "Error creating symlink ${BUBBLE_DEVICE_BASE}/current -> ${BUBBLE_DEVICE_BASE}/${BUBBLE_HOST}"
- fi
- CURRENT_DEVICE_JSON="$(cat "${CURRENT_DEVICE_DIR}/device.json")"
- if [[ -z "${CURRENT_DEVICE_JSON}" ]] ; then
- die "File was empty: ${CURRENT_DEVICE_JSON}"
- fi
- DEVICE_UUID="$(echo "${CURRENT_DEVICE_JSON}" | jq -r .uuid)"
- if [[ -z "${DEVICE_UUID}" ]] ; then
- die "No device UUID could be read from JSON: ${CURRENT_DEVICE_JSON}"
- fi
-
- CERTS_DIR=/usr/share/ca-certificates/extra
- if [[ ! -d "${CERTS_DIR}" ]] ; then
- echo "Creating ${CERTS_DIR}"
- sudo mkdir -p ${CERTS_DIR}
- fi
- CERT_DEST="${CERTS_DIR}/$(basename "${CERT_FILE}")"
- RUN_CERT_WIZ=0
- if cmp "${CERT_FILE}" "${CERT_DEST}" ; then
- if [[ -z "${BUBBLE_CERT_WIZ}" ]] ; then
- echo "Certificate already installed: ${CERT_DEST}"
- elif [[ "${BUBBLE_CERT_WIZ}" == "true" ]] ; then
- RUN_CERT_WIZ=1
- else
- die "Invalid value for BUBBLE_CERT_WIZ env var: ${BUBBLE_CERT_WIZ} (expected 'true' or nothing)"
- fi
- else
- RUN_CERT_WIZ=1
- echo "Copying certificate to ${CERT_DEST} ..."
- sudo cp "${CERT_FILE}" "${CERT_DEST}" || die "Error copying certificate: ${CERT_FILE} -> ${CERT_DEST}"
- fi
-
- if [[ ${RUN_CERT_WIZ} -eq 1 ]] ; then
- echo "
- ### Finishing Certificate Installation for Ubuntu
-
- We're going to run the Ubuntu certificate wizard via:
-
- sudo dpkg-reconfigure ca-certificates
-
- When the wizard opens:
- - Press Enter with 'Yes' selected for 'Trust new certificates from certificate authorities?'
- - Press the space bar to check the box for your Bubble certificate
- - Press Enter to commit the changes
-
- To continue and run the Ubuntu certificate wizard, press Enter now
- "
- read -r DUMMY
- sudo dpkg-reconfigure ca-certificates || die "Error reconfiguring system CA certificates"
- fi
-
- # Ensure ssh route table exists
- if [[ $(sudo cat /etc/iproute2/rt_tables | grep -c ssh) -eq 0 ]] ; then
- sudo bash -c 'echo "2 ssh" >> /etc/iproute2/rt_tables'
- else
- echo "ssh table already exists in rt_tables"
- fi
-
- REBOOT_FILE=/tmp/reboot-required.bubble
-
- # Set sysctl vars
- SYSCTL="/etc/sysctl.conf"
- SYS_RP_FILTER="net.ipv4.conf.all.rp_filter"
- if [[ $(sudo cat ${SYSCTL} | grep -v '^#' | grep -c "${SYS_RP_FILTER}") -eq 0 ]] ; then
- sudo bash -c 'echo "'"${SYS_RP_FILTER}"' = 2" >> '"${SYSCTL}"'' || die "Error setting ${SYS_RP_FILTER} = 2 in ${SYSCTL}"
- touch ${REBOOT_FILE}
- else
- echo "${SYS_RP_FILTER} already defined in ${SYSCTL}"
- fi
-
- SYS_IP_FORWARD="net.ipv4.ip_forward"
- if [[ $(cat ${SYSCTL} | grep -v '^#' | grep -c "${SYS_IP_FORWARD}") -eq 0 ]] ; then
- sudo bash -c 'echo "'"${SYS_IP_FORWARD}"' = 1" >> '"${SYSCTL}"'' || die "Error setting ${SYS_IP_FORWARD} = 1 in ${SYSCTL}"
- touch ${REBOOT_FILE}
- else
- echo "${SYS_IP_FORWARD} already defined in ${SYSCTL}"
- fi
-
- if [[ -f ${REBOOT_FILE} ]] ; then
- echo "
- sysctl settings changed, reboot required.
-
- Press Enter to reboot, or Control-C to exit and reboot later"
- read -r DUMMY || die "Please reboot later to ensure sysctl changes take effect"
- sudo bash -c 'sync && shutdown -r now' && exit 1 || die "Error rebooting"
- fi
-
- WG_CONF=/etc/wireguard/wg0.conf
- echo "Copying vpn.conf to ${WG_CONF} ..."
- sudo cp "${VPN_FILE}" ${WG_CONF} || die "Error copying vpn.conf: ${VPN_FILE} -> ${WG_CONF}"
-
- if [[ $(sudo cat "${WG_CONF}" | grep -c PostUp) -ne 0 ]] ; then
- echo "${WG_CONF} already contains PostUp directives"
- else
- GATEWAY=$(route -n | grep "^0.0.0.0" | awk '{print $2}')
- if [[ -z "${GATEWAY}" ]] ; then
- die "Error determining gateway IP using 'route -n'"
- fi
-
- IFACE=$(route -n | grep "^0.0.0.0" | awk '{print $8}')
- if [[ -z "${IFACE}" ]] ; then
- die "Error determining gateway interface using 'route -n'"
- fi
-
- WG_TEMP=$(mktemp /tmp/wg.conf.XXXXXXX)
- sudo cat ${WG_CONF} | grep -A 3 '\[Interface\]' >> "${WG_TEMP}"
- echo "PostUp = ip route add default via ${GATEWAY} dev ${IFACE} table ssh
- PostUp = ip rule add fwmark 0x2 table ssh
- PostUp = /sbin/iptables -A OUTPUT -t mangle -o wg0 -p tcp --sport 22 -j MARK --set-mark 2
- PreDown = /sbin/iptables -D OUTPUT -t mangle -o wg0 -p tcp --sport 22 -j MARK --set-mark 2
- PreDown = ip rule del fwmark 0x2 table ssh
- PreDown = ip route del default via ${GATEWAY} dev ${IFACE} table ssh
- " >> "${WG_TEMP}"
- sudo cat ${WG_CONF} | grep -A 10 '\[Peer\]' >> "${WG_TEMP}"
-
- sudo mv "${WG_TEMP}" ${WG_CONF} || die "Error installing updated ${WG_CONF} file"
- fi
-
- echo "Starting WireGuard VPN ..."
- sudo wg-quick up wg0 || die "Error starting WireGuard"
-
- echo "
- ===============================================================
- ======= Ubuntu device successfully connected to Bubble! =======
- ===============================================================
- Device UUID : ${DEVICE_UUID}
- Device Name : ${DEVICE_NAME}
- Bubble Host : ${BUBBLE_HOST}
- Device Dir : ${BUBBLE_DIR}
- Certificate : ${CERT_DEST}
- VPN Config : ${WG_CONF}
- Public IP : $(curl -s http://checkip.amazonaws.com/)
- "
-
- if [[ -n "$(which firefox)" ]] ; then
- echo "
- ===========================================================
- ===================== Firefox Warning =====================
- ===========================================================
- Firefox does not use the Ubuntu certificate store. It has its
- own certificate store.
-
- Firefox will not properly connect to most websites until you
- install the Bubble certificate in Firefox.
-
- To install the Bubble Certificate in Firefox:
- * Open Firefox
- * In the search box (top right of page), enter \"certificate\"
- * Scroll down, click \"View Certificates\" button
- * Click \"Import\" button
- * Select your certificate file: ${CERT_FILE}
- * Check the box \"Trust this CA to identify websites\" and click OK.
-
- More detailed instructions (with screenshots) can be found here:
- https://git.bubblev.org/bubblev/bubble-docs/src/branch/master/cert_instructions/firefox_cert.md
- "
- fi
|