Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix Bug 69111 - Add an offline installation option for docker supply #302

Closed
wants to merge 8 commits into from
129 changes: 129 additions & 0 deletions .github/workflows/ci-oci-docker-install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Install OneClickInstall Docker

on:
pull_request:
types: [opened, reopened, synchronize]
paths:
- '.github/workflows/ci-oci-docker-install.yml'
- 'install/OneClickInstall/install-Docker.sh'
workflow_dispatch:
inputs:
offline:
description: 'Publish offline self-extracting archive'
required: true
default: false
type: boolean

jobs:
Install-OneClickInstall-Docker:
runs-on: ubuntu-22.04
steps:
- name: Determine Branch Name
id: set-branch-name
run: |
BRANCH_NAME=$([ "${{ github.event_name }}" = "pull_request" ] && echo "${{ github.event.pull_request.head.ref }}" || echo "${GITHUB_REF#refs/heads/}")
echo "BRANCH_NAME=master" >> $GITHUB_ENV

- name: Test OCI docker scripts
run: |
sudo docker image prune --all --force

wget https://download.onlyoffice.com/docspace/docspace-enterprise-install.sh
sed '/bash install-Docker.sh/i sed -i "1i set -x" install-Docker.sh' -i docspace-enterprise-install.sh
sudo bash docspace-enterprise-install.sh docker -skiphc true -noni true $([ ${{ env.BRANCH_NAME }} != "master" ] && echo "-gb ${{ env.BRANCH_NAME }} -s 4testing-") || exit $?

echo -n "Waiting for all containers to start..."
timeout 300 bash -c 'while docker ps | grep -q "starting"; do sleep 5; done' && echo "OK" || echo "container_status=timeout" >> $GITHUB_ENV

- name: Check container status
run: |
docker ps --all --format "{{.Names}}" | xargs -I {} sh -c '
status=$(docker inspect --format="{{if .State.Health}}{{.State.Health.Status}}{{else}}no healthcheck{{end}}" {});
case "$status" in
healthy) color="\033[0;32m" ;; # green
"no healthcheck") color="\033[0;33m" ;; # yellow
*) color="\033[0;31m"; echo "container_status=red" >> $GITHUB_ENV ;; # red
esac;
printf "%-50s ${color}%s\033[0m\n" "{}:" "$status";
'

- name: Print logs for crashed container
run: |
docker ps --all --format "{{.Names}}" | xargs -I {} sh -c '
status=$(docker inspect --format="{{if .State.Health}}{{.State.Health.Status}}{{else}}no healthcheck{{end}}" {});
case "$status" in
healthy | "no healthcheck") ;;
*)
echo "Logs for container {}:";
docker logs --tail 30 {} | sed "s/^/\t/g";
;;
esac;
'
case "${{ env.container_status }}" in
timeout) echo "Timeout reached. Not all containers are running."; exit 1 ;;
red) echo "One or more containers have status 'red'. Job will fail."; exit 1 ;;
esac

- name: Checkout repository
if: ${{ github.event.inputs.offline == 'true' }}
uses: actions/checkout@v4
with:
ref: feature/oci-offline

- name: Creating offline self-extracting archive
if: ${{ github.event.inputs.offline == 'true' }}
run: |
INSTALL_PATH=${{ github.workspace }}/install

docker stop $(docker ps -a -q) && docker rm $(docker ps -a -q) && docker volume rm $(docker volume ls -q)
sudo rm -rf /usr/local/lib/android /opt/ghc

[ "${{ env.BRANCH_NAME }}" != "master" ] && STATUS="4testing-" && sed -i "s~\(STATUS=\"\).*\"$~\14testing-\"~" "${INSTALL_PATH}/OneClickInstall/install-Docker.sh"
sed -i 's~\(OFFLINE_INSTALLATION="\|SKIP_HARDWARE_CHECK="\).*"$~\1true"~' "${INSTALL_PATH}/OneClickInstall/install-Docker.sh"

echo "Creating offline self-extracting archive..."
docker save onlyoffice/docspace-api | xz --verbose -T0 -z -9e > ${INSTALL_PATH}/docker_images.tar.xz
cd ${INSTALL_PATH}/docker && tar -czvf ${INSTALL_PATH}/docker.tar.gz --exclude='config/supervisor*' *.yml .env config/

tar -cvf ${INSTALL_PATH}/offline-docspace.tar \
-C "${INSTALL_PATH}/OneClickInstall" install-Docker.sh \
-C "${INSTALL_PATH}" docker_images.tar.xz \
-C "${INSTALL_PATH}" docker.tar.gz
rm -rf ${INSTALL_PATH}/docker_images.tar.xz ${INSTALL_PATH}/docker.tar.gz

echo "ARTIFACT_NAME=${ARTIFACT_NAME:=${STATUS}offline-docspace-installation.sh}" >> $GITHUB_ENV
[ "${{ env.BRANCH_NAME }}" = "master" ] && echo "ARTIFACT_VERSION_NAME=${STATUS}offline-docspace-$(docker images onlyoffice/docspace-api --format "{{.Tag}}")-installation.sh" >> $GITHUB_ENV

cat ${INSTALL_PATH}/common/self-extracting.sh ${INSTALL_PATH}/offline-docspace.tar > ${INSTALL_PATH}/${ARTIFACT_NAME}
chmod +x ${INSTALL_PATH}/${ARTIFACT_NAME}


- name: Configure AWS Credentials
if: ${{ github.event.inputs.offline == 'true' }}
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_OCI }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_OCI }}
aws-region: us-east-1

- name: Upload offline self-extracting archive
if: ${{ github.event.inputs.offline == 'true' }}
run: |
aws s3 cp ${{ github.workspace }}/install/${{ env.ARTIFACT_NAME }} \
${{ secrets.AWS_BUCKET_URL_OCI }}/${{ env.ARTIFACT_NAME }} \
--acl public-read \
--content-type application/x-shellscript \
--metadata-directive REPLACE
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.AWS_DISTRIBUTION_ID_OCI }} \
--paths "/docspace/${{ env.ARTIFACT_NAME }}"

- name: Upload offline self-extracting archive with version
if: ${{ env.ARTIFACT_VERSION_NAME != '' }}
run: |
aws s3 sync ${{ secrets.AWS_BUCKET_URL_OCI }}/${{ env.ARTIFACT_NAME }}\
${{ secrets.AWS_BUCKET_URL_OCI }}/${{ env.ARTIFACT_VERSION_NAME }} \
--exact-timestamps
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.AWS_DISTRIBUTION_ID_OCI }} \
--paths "/docspace/${{ env.ARTIFACT_VERSION_NAME }}"
92 changes: 73 additions & 19 deletions install/OneClickInstall/install-Docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ PROXY_YML="${BASE_DIR}/proxy.yml"
STATUS=""
DOCKER_TAG=""
INSTALLATION_TYPE="ENTERPRISE"
IMAGE_NAME="${PACKAGE_SYSNAME}/${PRODUCT}-api"
IMAGE_NAME="${PACKAGE_SYSNAME}/${STATUS}${PRODUCT}-api"
CONTAINER_NAME="${PACKAGE_SYSNAME}-api"

NETWORK_NAME=${PACKAGE_SYSNAME}
Expand Down Expand Up @@ -105,8 +105,9 @@ LETS_ENCRYPT_DOMAIN=""
LETS_ENCRYPT_MAIL=""

HELP_TARGET="install-Docker.sh";
OFFLINE_INSTALLATION="false"

SKIP_HARDWARE_CHECK="false";
SKIP_HARDWARE_CHECK="false"

EXTERNAL_PORT="80"

Expand Down Expand Up @@ -491,6 +492,13 @@ while [ "$1" != "" ]; do
shift
fi
;;

-off | --offline )
if [ "$2" != "" ]; then
OFFLINE_INSTALLATION=$2
shift
fi
;;

-? | -h | --help )
echo " Usage: bash $HELP_TARGET [PARAMETER] [[PARAMETER], ...]"
Expand Down Expand Up @@ -543,6 +551,7 @@ while [ "$1" != "" ]; do
echo " -lem, --letsencryptmail defines the domain administator mail address for Let's Encrypt certificate"
echo " -cf, --certfile path to the certificate file for the domain"
echo " -ckf, --certkeyfile path to the private key file for the certificate"
echo " -off, --offline set the script for offline installation (true|false)"
echo " -noni, --noninteractive auto confirm all questions (true|false)"
echo " -dbm, --databasemigration database migration (true|false)"
echo " -ms, --makeswap make swap file (true|false)"
Expand Down Expand Up @@ -1046,7 +1055,7 @@ retrieving_tag_from_hub () {
if ! command_exists jq ; then
if command_exists yum; then
if ! rpm -q epel-release > /dev/null 2>&1; then
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm
[ "${OFFLINE_INSTALLATION}" = "false" ] && rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm
fi
fi
install_service jq
Expand Down Expand Up @@ -1079,15 +1088,19 @@ retrieving_tag_from_hub () {
}

get_available_version () {
retrieving_tag_from_hub ${1}
[ "${OFFLINE_INSTALLATION}" = "false" ] && retrieving_tag_from_hub ${1} || TAGS_RESP=$(docker images --format "{{.Tag}}" ${1})

VERSION_REGEX='^[0-9]+\.[0-9]+(\.[0-9]+){0,2}$'
[ ${#TAGS_RESP[@]} -eq 1 ] && LATEST_TAG="${TAGS_RESP[0]}" || LATEST_TAG=$(printf "%s\n" "${TAGS_RESP[@]}" | grep -E "$VERSION_REGEX" | sort -V | tail -n 1)

if [ ! -z "${LATEST_TAG}" ]; then
echo "${LATEST_TAG}" | sed "s/\"//g"
else
echo "Unable to retrieve tag from ${1} repository" >&2
if [ "${OFFLINE_INSTALLATION}" = "false" ]; then
echo "Unable to retrieve tag from ${1} repository" >&2
else
echo "Error: The image '${1}' is not found in the local Docker registry." >&2
fi
kill -s TERM $PID
fi
}
Expand Down Expand Up @@ -1173,15 +1186,19 @@ set_installation_type_data () {

download_files () {
if ! command_exists docker-compose; then
install_docker_compose
[ "${OFFLINE_INSTALLATION}" = "false" ] && install_docker_compose || { echo "docker-compose not installed"; exit 1; }
else
if [ "$(docker-compose --version | grep -oP '(?<=v)\d+\.\d+'| sed 's/\.//')" -lt "21" ]; then
[ "$OFFLINE_INSTALLATION" = "false" ] && install_docker_compose || { echo "docker-compose version is outdated"; exit 1; }
fi
fi

# Fixes issues with variables when upgrading to v1.1.3
HOSTS=("ELK_HOST" "REDIS_HOST" "RABBIT_HOST" "MYSQL_HOST");
for HOST in "${HOSTS[@]}"; do [[ "${!HOST}" == *CONTAINER_PREFIX* || "${!HOST}" == *$PACKAGE_SYSNAME* ]] && export "$HOST="; done
[[ "${APP_URL_PORTAL}" == *${PACKAGE_SYSNAME}-proxy* ]] && APP_URL_PORTAL=""

echo -n "Downloading configuration files to the ${BASE_DIR} directory..."
[ "${OFFLINE_INSTALLATION}" = "false" ] && echo -n "Downloading configuration files to ${BASE_DIR}..." || echo "Unzip docker.tar.gz to ${BASE_DIR}..."

if ! command_exists tar; then
install_service tar
Expand All @@ -1190,15 +1207,25 @@ download_files () {
[ -d "${BASE_DIR}" ] && rm -rf "${BASE_DIR}"
mkdir -p ${BASE_DIR}

if [ -z "${GIT_BRANCH}" ]; then
curl -sL -o docker.tar.gz "https://download.${PACKAGE_SYSNAME}.com/${PRODUCT}/docker.tar.gz"
tar -xf docker.tar.gz -C ${BASE_DIR}

if [ "${OFFLINE_INSTALLATION}" = "false" ]; then
if [ -z "${GIT_BRANCH}" ]; then
DOWNLOAD_URL="https://download.${PACKAGE_SYSNAME}.com/${PRODUCT}/docker.tar.gz"
else
DOWNLOAD_URL="https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}-buildtools/archive/${GIT_BRANCH}.tar.gz"
STRIP_COMPONENTS="--strip-components=3 --wildcards */install/docker/*"
fi

curl -sL "${DOWNLOAD_URL}" | tar -xzf - -C "${BASE_DIR}" ${STRIP_COMPONENTS}
else
curl -sL -o docker.tar.gz "https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}-buildtools/archive/${GIT_BRANCH}.tar.gz"
tar -xf docker.tar.gz --strip-components=3 -C ${BASE_DIR} --wildcards '*/install/docker/*'
if [ -f "$(dirname "$0")/docker.tar.gz" ]; then
tar -xf $(dirname "$0")/docker.tar.gz -C "${BASE_DIR}"
else
echo "Error: docker.tar.gz not found in the same directory as the script."
echo "You need to download the docker.tar.gz file from https://download.${PACKAGE_SYSNAME}.com/${PRODUCT}/docker.tar.gz"
exit 1
fi
fi

rm -rf docker.tar.gz

echo "OK"

Expand Down Expand Up @@ -1226,6 +1253,7 @@ install_mysql_server () {
reconfigure MYSQL_VERSION ${MYSQL_VERSION}

if [[ -z ${MYSQL_HOST} ]] && [ "$INSTALL_MYSQL_SERVER" == "true" ]; then
offline_check_docker_image ${BASE_DIR}/db.yml
docker-compose -f $BASE_DIR/db.yml up -d
elif [ "$INSTALL_MYSQL_SERVER" == "pull" ]; then
docker-compose -f $BASE_DIR/db.yml pull
Expand Down Expand Up @@ -1254,6 +1282,7 @@ install_document_server () {

install_rabbitmq () {
if [[ -z ${RABBIT_HOST} ]] && [ "$INSTALL_RABBITMQ" == "true" ]; then
offline_check_docker_image ${BASE_DIR}/rabbitmq.yml
docker-compose -f $BASE_DIR/rabbitmq.yml up -d
elif [ "$INSTALL_RABBITMQ" == "pull" ]; then
docker-compose -f $BASE_DIR/rabbitmq.yml pull
Expand All @@ -1269,6 +1298,7 @@ install_rabbitmq () {

install_redis () {
if [[ -z ${REDIS_HOST} ]] && [ "$INSTALL_REDIS" == "true" ]; then
offline_check_docker_image ${BASE_DIR}/redis.yml
docker-compose -f $BASE_DIR/redis.yml up -d
elif [ "$INSTALL_REDIS" == "pull" ]; then
docker-compose -f $BASE_DIR/redis.yml pull
Expand All @@ -1289,6 +1319,7 @@ install_elasticsearch () {
else
sed -i 's/Xms[0-9]g/Xms1g/g; s/Xmx[0-9]g/Xmx1g/g' $BASE_DIR/opensearch.yml
fi
offline_check_docker_image ${BASE_DIR}/opensearch.yml
docker-compose -f $BASE_DIR/opensearch.yml up -d
elif [ "$INSTALL_ELASTICSEARCH" == "pull" ]; then
docker-compose -f $BASE_DIR/opensearch.yml pull
Expand Down Expand Up @@ -1325,6 +1356,9 @@ install_fluent_bit () {
reconfigure DASHBOARDS_USERNAME "${DASHBOARDS_USERNAME:-"${PACKAGE_SYSNAME}"}"
reconfigure DASHBOARDS_PASSWORD "${DASHBOARDS_PASSWORD:-$(get_random_str 20)}"

offline_check_docker_image ${BASE_DIR}/fluent.yml
offline_check_docker_image ${BASE_DIR}/dashboards.yml

docker-compose -f ${BASE_DIR}/fluent.yml -f ${BASE_DIR}/dashboards.yml up -d
elif [ "$INSTALL_FLUENT_BIT" == "pull" ]; then
docker-compose -f ${BASE_DIR}/fluent.yml -f ${BASE_DIR}/dashboards.yml pull
Expand All @@ -1349,13 +1383,23 @@ install_product () {
reconfigure APP_URL_PORTAL "${APP_URL_PORTAL:-"http://${PACKAGE_SYSNAME}-router:8092"}"
reconfigure EXTERNAL_PORT ${EXTERNAL_PORT}

if [[ -z ${MYSQL_HOST} ]] && [ "$INSTALL_MYSQL_SERVER" == "true" ]; then
if [[ -z ${MYSQL_HOST} ]] && [ "$INSTALL_MYSQL_SERVER" == "true" ] && [[ -n $(docker ps -q --filter "name=${PACKAGE_SYSNAME}-mysql-server") ]]; then
echo -n "Waiting for MySQL container to become healthy..."
(timeout 30 bash -c "while ! docker inspect --format '{{json .State.Health.Status }}' ${PACKAGE_SYSNAME}-mysql-server | grep -q 'healthy'; do sleep 1; done") && echo "OK" || (echo "FAILED")
fi

offline_check_docker_image ${BASE_DIR}/migration-runner.yml
offline_check_docker_image ${BASE_DIR}/${PRODUCT}.yml
offline_check_docker_image ${PROXY_YML}
offline_check_docker_image ${BASE_DIR}/notify.yml
offline_check_docker_image ${BASE_DIR}/healthchecks.yml

docker-compose -f $BASE_DIR/migration-runner.yml up -d
echo -n "Waiting for database migration to complete..." && docker wait ${PACKAGE_SYSNAME}-migration-runner && echo "OK"
if [[ -n $(docker ps -q --filter "name=${PACKAGE_SYSNAME}-migration-runner") ]]; then
echo -n "Waiting for database migration to complete..."
timeout 30 bash -c "while [ $(docker wait ${PACKAGE_SYSNAME}-migration-runner) -ne 0 ]; do sleep 1; done;" && echo "OK" || echo "FAILED"
fi

docker-compose -f $BASE_DIR/${PRODUCT}.yml up -d
docker-compose -f ${PROXY_YML} up -d
docker-compose -f $BASE_DIR/notify.yml up -d
Expand Down Expand Up @@ -1405,6 +1449,16 @@ make_swap () {
fi
}

offline_check_docker_image() {
if [ "${OFFLINE_INSTALLATION}" != "false" ]; then
[ ! -f "$1" ] && { echo "Error: File '$1' does not exist."; exit 1; }

docker-compose -f "$1" config | grep -oP 'image:\s*\K\S+' | while IFS= read -r IMAGE_TAG; do
docker images "${IMAGE_TAG}" | grep -q "${IMAGE_TAG%%:*}" || { echo "Error: The image '${IMAGE_TAG}' is not found in the local Docker registry."; kill -s TERM $PID; }
done
fi
}

check_hub_connection() {
retrieving_tag_from_hub ${IMAGE_NAME}
[ -z "$TAGS_RESP" ] && { echo -e "Unable to download tags from ${HUB:-hub.docker.com}.\nTry specifying another dockerhub name using -hub"; exit 1; } || true
Expand Down Expand Up @@ -1435,16 +1489,16 @@ start_installation () {
check_docker_version
service docker start
else
install_docker
[ "${OFFLINE_INSTALLATION}" = "false" ] && install_docker || { echo "docker not installed"; exit 1; }
fi

docker_login

check_hub_connection
[ "${OFFLINE_INSTALLATION}" = "false" ] && check_hub_connection

create_network

domain_check
[ "${OFFLINE_INSTALLATION}" = "false" ] && domain_check

if [ "$UPDATE" = "true" ]; then
set_docspace_params
Expand Down
29 changes: 29 additions & 0 deletions install/common/self-extracting.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
set -e

[ "$(id -u)" -ne 0 ] && { echo "To perform this action you must be logged in with root rights"; exit 1; }

TEMP_DIR=$(mktemp -d)

trap 'echo "Cleaning up temporary files..."; rm -rf "${TEMP_DIR}"' EXIT

! type docker &> /dev/null && { echo "docker not installed"; exit 1; }
! type docker-compose &> /dev/null && { echo "docker-compose not installed"; exit 1; }

echo "Extracting docker images to ${TEMP_DIR}..."
tail -n +$(awk '/^__END_OF_SHELL_SCRIPT__$/{print NR + 1; exit 0;}' "$0") "$0" | tar x -C "${TEMP_DIR}"

echo "Loading docker images..."
docker load -i ${TEMP_DIR}/docker_images.tar.xz

echo "Extracting OneClickInstall files to the current directory..."
mv -f ${TEMP_DIR}/docker.tar.gz $(dirname "$0")/docker.tar.gz
mv -f ${TEMP_DIR}/install-Docker.sh $(dirname "$0")/install-Docker.sh

echo "Running the install-Docker.sh script..."
chmod +x $(dirname "$0")/install-Docker.sh
$(dirname "$0")/install-Docker.sh

exit 0

__END_OF_SHELL_SCRIPT__
Loading
Loading