outline-server/src/server_manager/install_scripts/do_install_server.sh
2018-03-29 15:38:27 -04:00

172 lines
6.2 KiB
Bash
Executable file

# Copyright 2018 The Outline Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Script to install Shadowbox on a DigitalOcean droplet
# You must set the following environment variables:
# DO_ACCESS_TOKEN: DigitalOcean access token, with read and write privileges.
# You may set the following environment variables, overriding their defaults:
# SB_IMAGE: Shadowbox Docker image to install, e.g. quay.io/outline/shadowbox:nightly
# SB_API_PORT: The port number of the management API.
# SENTRY_API_URL: Url to post Sentry report to on error.
# WATCHTOWER_REFRESH_SECONDS: refresh interval in seconds to check for updates,
# defaults to 3600.
set -euxo pipefail
export SHADOWBOX_DIR="${SHADOWBOX_DIR:-${HOME:-/root}/shadowbox}"
mkdir -p $SHADOWBOX_DIR
# Save output for debugging
exec 2>&1 >$SHADOWBOX_DIR/install-shadowbox-output
# Initialize sentry log file.
export SENTRY_LOG_FILE="$SHADOWBOX_DIR/sentry-log-file.txt"
> $SENTRY_LOG_FILE
function log_for_sentry() {
echo [$(date "+%Y-%m-%d@%H:%M:%S")] "do_install_server.sh" "$@" >>$SENTRY_LOG_FILE
}
function post_sentry_report() {
if [[ -n "$SENTRY_API_URL" ]]; then
# Get JSON formatted string. This command replaces newlines with literal '\n'
# but otherwise assumes that there are no other characters to escape for JSON.
# If we need better escaping, we can install the jq command line tool.
readonly SENTRY_PAYLOAD_BYTE_LIMIT=8000
SENTRY_PAYLOAD="{\"message\": \"Install error:\n$(cat $SENTRY_LOG_FILE | awk '{printf "%s\\n", $0}' | tail --bytes $SENTRY_PAYLOAD_BYTE_LIMIT)\"}"
# See Sentry documentation at:
# https://media.readthedocs.org/pdf/sentry/7.1.0/sentry.pdf
curl "$SENTRY_API_URL" -H "Origin: shadowbox" --data-binary "$SENTRY_PAYLOAD"
fi
}
# For backward-compatibility:
readonly DO_ACCESS_TOKEN="${DO_ACCESS_TOKEN:-ACCESS_TOKEN}"
if [[ -z "${DO_ACCESS_TOKEN}" ]]; then
echo "Access token must be supplied"
exit 1
fi
# DigitalOcean's Metadata API base url.
# This URL only supports HTTP (not HTTPS) requests, however it is a local link
# address so not at risk for man-in-the-middle attacks or eavesdropping.
# More detail at https://serverfault.com/questions/427018/what-is-this-ip-address-169-254-169-254
readonly DO_METADATA_URL="http://169.254.169.254/metadata/v1"
function cloud::public_ip() {
curl ${DO_METADATA_URL}/interfaces/public/0/ipv4/address
}
# Applies a tag to this droplet.
function cloud::add_tag() {
local tag="$1"
declare -a base_flags=(-X POST -H 'Content-Type: application/json')
base_flags+=(-H "Authorization: Bearer ${DO_ACCESS_TOKEN}")
local TAGS_URL='https://api.digitalocean.com/v2/tags'
# Create the tag
curl "${base_flags[@]}" -d "{\"name\":\"${tag}\"}" "${TAGS_URL}"
local droplet_id="$(curl ${DO_METADATA_URL}/id)"
printf -v droplet_obj '
{
"resources": [{
"resource_id": "%s",
"resource_type": "droplet"
}]
}' "${droplet_id}"
# Link the tag to this droplet
curl "${base_flags[@]}" -d "${droplet_obj}" "${TAGS_URL}/${tag}/resources"
}
# Adds a key-value tag to the droplet.
# Takes the key as the only argument and reads the value from stdin.
# add_kv_tag() converts the input value to hex, because (1) DigitalOcean
# tags may only contain letters, numbers, : - and _, and (2) there is
# currently a bug that makes tags case-insensitive, so we can't use base64.
function cloud::add_kv_tag() {
local key="$1"
local value="$(xxd -p -c 255)"
cloud::add_tag "kv:${key}:${value}"
}
# Adds a key-value tag where the value is already hex-encoded.
function cloud::add_encoded_kv_tag() {
local key="$1"
read value
cloud::add_tag "kv:${key}:${value}"
}
log_for_sentry "Starting install"
# DigitalOcean's docker image comes with ufw enabled by default, disable so when
# can serve the shadowbox manager and instances on arbitrary high number ports.
log_for_sentry "Disabling ufw"
ufw disable
# Recent DigitalOcean Ubuntu droplets have unattended-upgrades configured from
# the outset but we want to enable automatic rebooting so that critical updates
# are applied without the Outline user's intervention.
readonly UNATTENDED_UPGRADES_CONFIG=/etc/apt/apt.conf.d/50unattended-upgrades
if [ -f $UNATTENDED_UPGRADES_CONFIG ]; then
log_for_sentry "Configuring auto-updates"
cat >> $UNATTENDED_UPGRADES_CONFIG << EOF
// Enabled by Outline manager installer.
Unattended-Upgrade::Automatic-Reboot "true";
EOF
fi
log_for_sentry "Getting SB_PUBLIC_IP"
export SB_PUBLIC_IP=$(cloud::public_ip)
log_for_sentry "Initializing ACCESS_CONFIG"
export ACCESS_CONFIG="$SHADOWBOX_DIR/access.txt"
> $ACCESS_CONFIG
# Set trap which publishes an error tag and sentry report only if there is an error.
function finish {
INSTALL_SERVER_EXIT_CODE=$?
log_for_sentry "In EXIT trap, exit code $INSTALL_SERVER_EXIT_CODE"
if [[ -z $(grep apiUrl $ACCESS_CONFIG) ]] || [[ -z $(grep certSha256 $ACCESS_CONFIG) ]]; then
echo "INSTALL_SCRIPT_FAILED: $INSTALL_SERVER_EXIT_CODE" | cloud::add_kv_tag "install-error"
# Post error report to sentry.
post_sentry_report
fi
}
trap finish EXIT
# Run install script asynchronously, so tags can be written as soon as they are ready.
log_for_sentry "Running install_server.sh"
./install_server.sh&
install_pid=$!
# Save tags for access information.
log_for_sentry "Reading tags from ACCESS_CONFIG"
tail -f $ACCESS_CONFIG --pid=$install_pid | while IFS=: read key value; do
case "$key" in
certSha256)
# Bypass encoding
log_for_sentry "Writing certSha256 tag"
echo $value | cloud::add_encoded_kv_tag "$key"
;;
apiUrl)
log_for_sentry "Writing apiUrl tag"
echo -n $value | cloud::add_kv_tag "$key"
;;
esac
done
# Wait for install script to finish, so that if there is any error in install_server.sh,
# the finish trap in this file will be able to access its error code.
wait $install_pid