diff --git a/.dockerignore b/.dockerignore index 0ef019adc..c49b5c3b1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ contrib docs tests !contrib/dokku-installer.py +!contrib/dokku-update diff --git a/.gitignore b/.gitignore index 704e2db40..f764c55d2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ data/ stack.tgz tmp coverage.out +contrib/dokku-update-version \ No newline at end of file diff --git a/contrib/build.Dockerfile b/contrib/build.Dockerfile index bcfc2bbbd..98ca33291 100644 --- a/contrib/build.Dockerfile +++ b/contrib/build.Dockerfile @@ -36,8 +36,8 @@ RUN PLUGIN_MAKE_TARGET=${PLUGIN_MAKE_TARGET} \ SKIP_GO_CLEAN=true \ make version copyfiles \ && rm -rf plugins/common/*.go plugins/common/glide* plugins/common/vendor/ \ - && make deb-herokuish deb-dokku deb-plugn deb-sshcommand deb-sigil \ - rpm-herokuish rpm-dokku rpm-plugn rpm-sshcommand rpm-sigil + && make deb-herokuish deb-dokku deb-plugn deb-sshcommand deb-sigil deb-dokku-update \ + rpm-herokuish rpm-dokku rpm-plugn rpm-sshcommand rpm-sigil rpm-dokku-update RUN mkdir -p /data \ && cp /tmp/*.deb /data \ diff --git a/contrib/dokku-update b/contrib/dokku-update new file mode 100755 index 000000000..cb47fac9d --- /dev/null +++ b/contrib/dokku-update @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -eo pipefail +shopt -s nullglob + +export DOKKU_ROOT=${DOKKU_ROOT:=~dokku} +[[ -f $DOKKU_ROOT/dokkurc ]] && source "$DOKKU_ROOT/dokkurc" +[[ -d $DOKKU_ROOT/.dokkurc ]] && for f in $DOKKU_ROOT/.dokkurc/*; do source "$f"; done +[[ $DOKKU_TRACE ]] && set -x + +export DOKKU_LIB_ROOT=${DOKKU_LIB_PATH:="/var/lib/dokku"} +export PLUGIN_PATH=${PLUGIN_PATH:="$DOKKU_LIB_ROOT/plugins"} +export PLUGIN_ENABLED_PATH=${PLUGIN_ENABLED_PATH:="$PLUGIN_PATH/enabled"} +export DEBIAN_FRONTEND=noninteractive + +dokku-log-info() { + declare desc="log info formatter" + echo "-----> $*" +} + +dokku-log-verbose() { + declare desc="log verbose formatter" + echo " $*" +} + +dokku-log-warn() { + declare desc="log warning formatter" + echo " ! $*" 1>&2 +} + +dokku-update-plugin() { + declare PLUGIN_NAME="$1" + if [[ -d "$PLUGIN_ENABLED_PATH/$PLUGIN_NAME/.git" ]]; then + dokku-log-verbose "Updating $PLUGIN_NAME" + dokku plugin:update "$PLUGIN_NAME" + fi +} + +main() { + declare COMMAND="$1" + local DOKKU_DISTRO PLUGIN_NAME VERSION + + if [[ -f "/etc/os-release" ]]; then + # shellcheck disable=SC1091 + DOKKU_DISTRO=$(. /etc/os-release && echo "$ID") + fi + + if [[ "$COMMAND" == "version" ]] || [[ "$COMMAND" == "-v" ]]; then + VERSION=UNRELEASED + if [[ -f "/var/lib/dokku-update/VERSION" ]]; then + VERSION="$(cat /var/lib/dokku-update/VERSION)" + fi + echo "dokku-update ${VERSION}" + exit 0 + fi + + dokku-log-info "Running system updates" + case "$DOKKU_DISTRO" in + arch) + yaourt -Syyua + ;; + debian|ubuntu) + apt-get update -qq > /dev/null + apt-get -qq -y --force-yes dist-upgrade + ;; + centos|opensuse) + dokku-log-warn "Updating this operating system is not supported" + ;; + *) + dokku-log-warn "Updating this operating system is not supported" + exit 1 + ;; + esac + + # update all plugins + dokku-log-info "Updating all plugins" + for PLUGIN_NAME in $(dokku plugin:list | grep enabled | awk '$1=$1' | cut -d' ' -f1); do + dokku-update-plugin "$PLUGIN_NAME" + done + dokku plugin:install + + # rebuild all applications + dokku-log-info "Rebuilding all applications" + dokku ps:rebuildall + + dokku-log-info "Waiting for old containers to stop" + sleep 120 + dokku-log-info "Cleaning up" + dokku cleanup +} + +main "$@" diff --git a/contrib/release-dokku-update b/contrib/release-dokku-update new file mode 100755 index 000000000..7babd2b4e --- /dev/null +++ b/contrib/release-dokku-update @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +readonly ROOT_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" +readonly TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-release.XXXX")" +readonly DOKKU_GIT_REV="$(git rev-parse HEAD)" + +trap 'rm -rf "$TMP_WORK_DIR" > /dev/null' RETURN INT TERM EXIT + +log-info() { + # shellcheck disable=SC2034 + declare desc="Log info formatter" + echo "$*" +} + +log-error() { + # shellcheck disable=SC2034 + declare desc="Log error formatter" + echo "! $*" 1>&2 +} + +log-fail() { + # shellcheck disable=SC2034 + declare desc="Log fail formatter" + log-error "$*" + exit -1 +} + +fn-build-dokku() { + declare desc="Builds dokku packages within a docker container" + + pushd "$ROOT_DIR" >/dev/null + docker build \ + -f "contrib/update-build.Dockerfile" \ + -t dokku-update:build . + return "$?" +} + +fn-extract-package() { + declare desc="Extract packages from a docker container to the root directory" + declare PACKAGE_NAME="$1" + + if [[ -z "$PACKAGE_NAME" ]]; then + log-error "Invalid deb file specified" + return 1 + fi + + log-info "(extract-package) writing ${PACKAGE_NAME} to correct path" + docker run --rm --entrypoint cat dokku-update:build "/data/${PACKAGE_NAME}" > "${ROOT_DIR}/${PACKAGE_NAME}" + return "$?" +} + +fn-publish-package() { + declare desc="Publishes a package to packagecloud" + declare RELEASE="$1" RELEASE_TYPE="$2" PACKAGE_NAME="$3" + local REPOSITORY=dokku/dokku-betafish DIST=ubuntu/trusty + + [[ "$RELEASE" != "betafish" ]] && REPOSITORY=dokku/dokku + [[ "$RELEASE_TYPE" == "rpm" ]] && DIST=el/7 + log-info "(release-dokku) pushing ${RELEASE_TYPE} to packagecloud.com/${REPOSITORY}/${DIST}" + package_cloud push "${REPOSITORY}/${DIST}" "$PACKAGE_NAME" + return "$?" +} + +fn-in-array() { + declare desc="return true if value ($1) is in list (all other arguments)" + + local e + for e in "${@:2}"; do + [[ "$e" == "$1" ]] && return 0 + done + return 1 +} + +fn-require-bin() { + declare desc="Checks that a binary exists" + declare BINARY="$1" + if ! command -v "$BINARY" &>/dev/null; then + log-fail "Missing ${BINARY}, please install it" + fi +} + +main() { + declare RELEASE="$1" + local CURRENT_VERSION NEXT_VERSION major minor patch versions + local VALID_RELEASE_LEVELS=("major" "minor" "patch" "betafish") + + if [[ "$RELEASE" == '--trace' ]]; then + shift 1 + RELEASE="$1" + TRACE=1 && set -x + fi + + if [[ -z "$RELEASE" ]]; then + log-fail "Argument 1 must be one of [major, minor, patch, betafish], none given" + fi + + if ! fn-in-array "$RELEASE" "${VALID_RELEASE_LEVELS[@]}"; then + log-fail "Argument 1 must be one of [major, minor, patch, betafish], '${RELEASE}' given" + fi + + fn-require-bin "docker" + fn-require-bin "package_cloud" + [[ -n "$PACKAGECLOUD_API_TOKEN" ]] || log-fail "Missing PACKAGECLOUD_API_TOKEN environment variable" + + NEXT_VERSION="$(grep DOKKU_UPDATE_VERSION deb.mk | head -n1 | cut -d'=' -f2 | xargs)" + + fn-build-dokku || log-fail "Error building package" + + fn-extract-package "dokku-update_${NEXT_VERSION}_amd64.deb" || log-fail "Error extracting deb package" + + fn-extract-package "dokku-update-${NEXT_VERSION}-1.x86_64.rpm" || log-fail "Error extracting rpm package" + + fn-publish-package "$RELEASE" "deb" "dokku-update_${NEXT_VERSION}_amd64.deb" || log-fail "Error publishing deb package" + + fn-publish-package "$RELEASE" "rpm" "dokku-update-${NEXT_VERSION}-1.x86_64.rpm" || log-fail "Error publishing rpm package" + +} + +main "$@" diff --git a/contrib/update-build.Dockerfile b/contrib/update-build.Dockerfile new file mode 100644 index 000000000..fb7424860 --- /dev/null +++ b/contrib/update-build.Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:14.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get -y install gcc git build-essential wget ruby-dev ruby1.9.1 lintian rpm help2man man-db +RUN command -v fpm > /dev/null || sudo gem install fpm --no-ri --no-rdoc + +ARG WORKDIR=/go/src/github.com/dokku/dokku + +WORKDIR ${WORKDIR} + +COPY . ${WORKDIR} + +RUN make deb-dokku-update rpm-dokku-update + +RUN mkdir -p /data \ + && cp /tmp/*.deb /data \ + && cp /tmp/*.rpm /data \ + && ls -lha /data/ diff --git a/deb.mk b/deb.mk index c1e73643c..0c6f0e7ac 100644 --- a/deb.mk +++ b/deb.mk @@ -8,6 +8,12 @@ DOKKU_DESCRIPTION = 'Docker powered PaaS that helps you build and manage the lif DOKKU_REPO_NAME ?= dokku/dokku DOKKU_ARCHITECTURE = amd64 +DOKKU_UPDATE_DESCRIPTION = 'Binary that handles updating Dokku and related systems' +DOKKU_UPDATE_REPO_NAME ?= dokku/dokku +DOKKU_UPDATE_VERSION ?= 0.1.0 +DOKKU_UPDATE_ARCHITECTURE = amd64 +DOKKU_UPDATE_PACKAGE_NAME = dokku-update_$(DOKKU_UPDATE_VERSION)_$(DOKKU_UPDATE_ARCHITECTURE).deb + define PLUGN_DESCRIPTION Hook system that lets users extend your application with plugins Plugin triggers are simply scripts that are executed by the system. @@ -52,7 +58,7 @@ export PLUGN_DESCRIPTION export SIGIL_DESCRIPTION export SSHCOMMAND_DESCRIPTION -.PHONY: install-from-deb deb-all deb-herokuish deb-dokku deb-plugn deb-setup deb-sshcommand deb-sigil +.PHONY: install-from-deb deb-all deb-herokuish deb-dokku deb-dokku-update deb-plugn deb-setup deb-sshcommand deb-sigil install-from-deb: @echo "--> Initial apt-get update" @@ -68,7 +74,7 @@ install-from-deb: sudo apt-get update -qq > /dev/null sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -yy dokku -deb-all: deb-setup deb-herokuish deb-dokku deb-plugn deb-sshcommand deb-sigil +deb-all: deb-setup deb-herokuish deb-dokku deb-plugn deb-sshcommand deb-sigil deb-dokku-update mv /tmp/*.deb . @echo "Done" @@ -150,6 +156,21 @@ endif dpkg-deb --build /tmp/build "/tmp/dokku_`cat /tmp/build/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" lintian "/tmp/dokku_`cat /tmp/build/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" +deb-dokku-update: + rm -rf /tmp/dokku-update*.deb dokku-update*.deb + echo "${DOKKU_UPDATE_VERSION}" > contrib/dokku-update-version + sudo fpm -t deb -s dir -n dokku-update \ + --version $(DOKKU_UPDATE_VERSION) \ + --architecture $(DOKKU_UPDATE_ARCHITECTURE) \ + --package $(DOKKU_UPDATE_PACKAGE_NAME) \ + --depends 'dokku' \ + --url "https://github.com/$(DOKKU_UPDATE_REPO_NAME)" \ + --description $(DOKKU_UPDATE_DESCRIPTION) \ + --license 'MIT License' \ + contrib/dokku-update=/usr/local/bin/dokku-update \ + contrib/dokku-update-version=/var/lib/dokku-update/VERSION + mv *.deb /tmp + deb-plugn: rm -rf /tmp/tmp /tmp/build $(PLUGN_PACKAGE_NAME) mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/bin diff --git a/debian/control b/debian/control index f3ee6d8aa..eee56e279 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Section: web Priority: optional Architecture: amd64 Depends: locales, git, make, curl, gcc, man-db, netcat, sshcommand (>= 0.6.0), gliderlabs-sigil, docker-engine-cs (>= 1.9.1) | docker-engine (>= 1.9.1) | docker-io (>= 1.9.1) | docker-ce | docker-ee, software-properties-common, python-software-properties | python3-software-properties, rsyslog -Recommends: herokuish (>= 0.3.4), parallel +Recommends: herokuish (>= 0.3.4), parallel, dokku-update Pre-Depends: nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python2.7, debconf Maintainer: Jose Diaz-Gonzalez Description: Docker-powered PaaS that helps build and manage the lifecycle of applications diff --git a/docs/getting-started/upgrading.md b/docs/getting-started/upgrading.md index 35ac6afa9..76998b794 100644 --- a/docs/getting-started/upgrading.md +++ b/docs/getting-started/upgrading.md @@ -2,6 +2,27 @@ If your version of Dokku is pre 0.3.0 (check with `dokku version`), we recommend [a fresh install](/docs/getting-started/installation.md) on a new server. +## Security Updates + +For any security related updates, please follow our [twitter account](https://twitter.com/dokku). As Dokku does not run any daemons, the security risk introduced by our software is minimal. + +Your operating system may occasionally provide security updates. We recommend setting unattended upgrades for your operating system. Here are some helpful links: + +- [Arch Linux System Maintenance](https://wiki.archlinux.org/index.php/System_maintenance) +- [Centos Automatic Security Updates](https://serversforhackers.com/c/automatic-security-updates-centos) +- [Debian Unattended Upgrades](https://wiki.debian.org/UnattendedUpgrades) +- [Ubuntu Unattended Upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates) + +Docker releases updates periodically to their engine. We recommend reading their release notes and upgrading accordingly. Please see the [Docker documentation](https://docs.docker.com/) for more details. + +Finally, we provide a helpful binary called `dokku-update`. This is a recommended package that: + +- Can be installed separately, so upgrading Dokku will not affect the running of this package. +- Automates many of the upgrade instructions for you. +- Provides a clean way for us to further enhance the upgrade process in the future. + +When installing from source, this is available from `contrib/dokku-update`, and is also available on Debian and RPM-based systems from our package repositories under the name `dokku-update`. + ## Migration Guides Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded. diff --git a/plugins/ps/subcommands/rebuild b/plugins/ps/subcommands/rebuild index 15128386a..242c65bf0 100755 --- a/plugins/ps/subcommands/rebuild +++ b/plugins/ps/subcommands/rebuild @@ -6,8 +6,14 @@ source "$PLUGIN_AVAILABLE_PATH/ps/functions" ps_rebuild_cmd() { declare desc="rebuilds an app from source" local cmd="ps:rebuild" - [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - ps_rebuild "$2" + local APP="$2" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + + if (is_deployed "$APP"); then + ps_rebuild "$APP" + else + dokku_log_warn "App $APP has not been deployed" + fi } ps_rebuild_cmd "$@" diff --git a/plugins/ps/subcommands/rebuildall b/plugins/ps/subcommands/rebuildall index 31809971c..61409e793 100755 --- a/plugins/ps/subcommands/rebuildall +++ b/plugins/ps/subcommands/rebuildall @@ -1,13 +1,37 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" ps_rebuildall_cmd() { declare desc="rebuilds all apps from source" local cmd="ps:rebuildall" + local GNU_PARALLEL + + if which parallel > /dev/null 2>&1; then + dokku_log_info1 "Rebuilding in parallel" + GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) + if [[ -z "$GNU_PARALLEL" ]]; then + # shellcheck disable=SC2046 + parallel -i bash -c "dokku ps:rebuild {}" -- $(dokku_apps) + else + dokku_apps | parallel 'dokku ps:rebuild {}' + fi + return + fi + for app in $(dokku_apps); do - (is_deployed "$app") && ps_rebuild "$app" + if ! (is_deployed "$app"); then + dokku_log_warn "App $app has not been deployed" + continue + fi + + dokku_log_verbose "Rebuilding app $app ..." + if ps_rebuild "$app"; then + continue + fi + dokku_log_warn "dokku ps:rebuild ${app} failed" done } diff --git a/plugins/ps/subcommands/restartall b/plugins/ps/subcommands/restartall index bfdeed29a..7f6e8aee2 100755 --- a/plugins/ps/subcommands/restartall +++ b/plugins/ps/subcommands/restartall @@ -1,13 +1,37 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" ps_restartall_cmd() { declare desc="restarts all apps via command line" local cmd="ps:restartall" + local GNU_PARALLEL + + if which parallel > /dev/null 2>&1; then + dokku_log_info1 "Restarting in parallel" + GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) + if [[ -z "$GNU_PARALLEL" ]]; then + # shellcheck disable=SC2046 + parallel -i bash -c "dokku ps:restart {}" -- $(dokku_apps) + else + dokku_apps | parallel 'dokku ps:restart {}' + fi + return + fi + for app in $(dokku_apps); do - ps_restart "$app" + if ! (is_deployed "$app"); then + dokku_log_warn "App $app has not been deployed" + continue + fi + + dokku_log_verbose "Restarting app $app ..." + if ps_restart "$app"; then + continue + fi + dokku_log_warn "dokku ps:restart ${app} failed" done } diff --git a/rpm.mk b/rpm.mk index ea178a2eb..4226c0d59 100644 --- a/rpm.mk +++ b/rpm.mk @@ -1,4 +1,5 @@ RPM_ARCHITECTURE = x86_64 +DOKKU_UPDATE_RPM_PACKAGE_NAME = dokku-update-$(DOKKU_UPDATE_VERSION)-1.$(RPM_ARCHITECTURE).rpm HEROKUISH_RPM_PACKAGE_NAME = herokuish-$(HEROKUISH_VERSION)-1.$(RPM_ARCHITECTURE).rpm PLUGN_RPM_PACKAGE_NAME = plugn-$(PLUGN_VERSION)-1.$(RPM_ARCHITECTURE).rpm SSHCOMMAND_RPM_PACKAGE_NAME = sshcommand-$(SSHCOMMAND_VERSION)-1.$(RPM_ARCHITECTURE).rpm @@ -6,7 +7,7 @@ SIGIL_RPM_PACKAGE_NAME = gliderlabs-sigil-$(SIGIL_VERSION)-1.$(RPM_ARCHITECTURE) .PHONY: rpm-all -rpm-all: rpm-setup rpm-herokuish rpm-dokku rpm-plugn rpm-sshcommand rpm-sigil +rpm-all: rpm-setup rpm-herokuish rpm-dokku rpm-plugn rpm-sshcommand rpm-sigil rpm-dokku-update mv /tmp/*.rpm . @echo "Done" @@ -111,6 +112,21 @@ endif . mv *.rpm "/tmp/dokku-`cat /tmp/build/var/lib/dokku/VERSION`-1.$(RPM_ARCHITECTURE).rpm" +rpm-dokku-update: + rm -rf /tmp/dokku-update*.rpm dokku-update*.rpm + echo "${DOKKU_UPDATE_VERSION}" > contrib/dokku-update-version + sudo fpm -t rpm -s dir -n dokku-update \ + --version $(DOKKU_UPDATE_VERSION) \ + --architecture $(RPM_ARCHITECTURE) \ + --package $(DOKKU_UPDATE_RPM_PACKAGE_NAME) \ + --depends 'dokku' \ + --url "https://github.com/$(DOKKU_UPDATE_REPO_NAME)" \ + --description $(DOKKU_UPDATE_DESCRIPTION) \ + --license 'MIT License' \ + contrib/dokku-update=/usr/local/bin/dokku-update \ + contrib/dokku-update-version=/var/lib/dokku-update/VERSION + mv *.rpm /tmp + rpm-plugn: rm -rf /tmp/tmp /tmp/build $(PLUGN_RPM_PACKAGE_NAME) mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/bin @@ -124,14 +140,14 @@ rpm-plugn: @echo "-> Creating $(PLUGN_RPM_PACKAGE_NAME)" sudo fpm -t rpm -s dir -C /tmp/build -n plugn \ - --version $(PLUGN_VERSION) \ - -a $(RPM_ARCHITECTURE) \ - --package $(PLUGN_RPM_PACKAGE_NAME) \ - --url "https://github.com/$(PLUGN_REPO_NAME)" \ - --category utils \ - --description "$$PLUGN_DESCRIPTION" \ - --license 'MIT License' \ - . + --version $(PLUGN_VERSION) \ + --architecture $(RPM_ARCHITECTURE) \ + --package $(PLUGN_RPM_PACKAGE_NAME) \ + --url "https://github.com/$(PLUGN_REPO_NAME)" \ + --category utils \ + --description "$$PLUGN_DESCRIPTION" \ + --license 'MIT License' \ + . mv *.rpm /tmp rpm-sshcommand: @@ -148,14 +164,14 @@ rpm-sshcommand: @echo "-> Creating $(SSHCOMMAND_RPM_PACKAGE_NAME)" sudo fpm -t rpm -s dir -C /tmp/build -n sshcommand \ - --version $(SSHCOMMAND_VERSION) \ - -a $(RPM_ARCHITECTURE) \ - --package $(SSHCOMMAND_RPM_PACKAGE_NAME) \ - --url "https://github.com/$(SSHCOMMAND_REPO_NAME)" \ - --category admin \ - --description "$$SSHCOMMAND_DESCRIPTION" \ - --license 'MIT License' \ - . + --version $(SSHCOMMAND_VERSION) \ + -a $(RPM_ARCHITECTURE) \ + --package $(SSHCOMMAND_RPM_PACKAGE_NAME) \ + --url "https://github.com/$(SSHCOMMAND_REPO_NAME)" \ + --category admin \ + --description "$$SSHCOMMAND_DESCRIPTION" \ + --license 'MIT License' \ + . mv *.rpm /tmp rpm-sigil: