├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── LICENSE ├── README.md └── syno_docker_update.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: markdumay 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is, including any error messages. 12 | 13 | **To reproduce** 14 | 1. The invoked command, e.g. `./syno_docker_update.sh update` 15 | 2. Additional steps / commands... 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Log file** 21 | Capture the exact output of the 'syno_docker_update.sh' script. 22 | 23 | **Docker daemon configuration** 24 | Capture the content of the file `/var/packages/Docker/etc/dockerd.json`. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dotenv environment variables file 2 | .env 3 | 4 | # gren configuration 5 | .grenrc.yml 6 | 7 | # Local filesystem files 8 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mark Dumay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # synology-docker 2 | 3 | 4 |

5 | An Unofficial Script to Update or Restore Docker Engine and Docker Compose on Synology NAS 6 |
7 |

8 | 9 | 10 | 11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

25 | 26 | 27 |

28 | About • 29 | Built With • 30 | Prerequisites • 31 | Deployment • 32 | Usage • 33 | Contributing • 34 | Credits • 35 | Donate • 36 | License 37 |

38 | 39 | 40 | ## About 41 | | :warning: The repository 'Synology-Docker' is not supported by Synology and can potentially lead to malfunctioning of your NAS. Use this script at your own risk. Please keep a backup of your files. | 42 | | --- | 43 | 44 | [Synology][synology_url] is a popular manufacturer of Network Attached Storage (NAS) devices. It provides a web-based user interface called Disk Station Manager (DSM). Synology also supports Docker on selected [models][synology_docker]. Docker is a lightweight virtualization application that gives you the ability to run containers directly on your NAS. The add-on package provided by Synology to install Docker is typically a version behind on the latest available version from Docker. *Synology-Docker* is a POSIX-compliant shell script to update both the Docker Engine and Docker Compose on your NAS to the latest version or a specified version. 45 | 46 | 49 | 50 | ## Built With 51 | The project uses [Docker][docker_url], a lightweight virtualization application. 52 | 53 | ## Prerequisites 54 | *Synology-Docker* runs on a Synology NAS with DSM 6 or DSM 7. The script has been tested with a DS918+ running DSM 6.2.4-25556 and DSM 7.0.1-42218. Other prerequisites are: 55 | 56 | * **SSH admin access is required** - *Synology-Docker* runs as a shell script on the terminal. You can enable SSH access in DSM under `Control Panel ➡ Terminal & SNMP ➡ Terminal`. 57 | 58 | * **Docker is required** - *Synology-Docker* updates the binaries of an existing Docker installation only. Install Docker on your NAS in DSM via `Package Center ➡ All Packages ➡ Docker` and ensure the status is `Running`. 59 | 60 | * **SynoCommunity/Git is required** - *Synology-Docker* needs the [Git package](https://synocommunity.com/package/git) from [SynoCommunity](https://synocommunity.com) installed on your NAS. Install Git on your NAS by adding the SynoCommunity package repository (described [here](https://synocommunity.com/#easy-install)) and installing the Git package in DSM via `Package Center ➡ Community ➡ Git`. 61 | 62 | ## Deployment 63 | Deployment of *Synology-Docker* is a matter of cloning the GitHub repository. Login to your NAS terminal via SSH first. Assuming you are in the working folder of your choice, clone the repository files. Git automatically creates a new folder `synology-docker` and copies the files to this directory. Then change your current folder to simplify the execution of the shell script. 64 | 65 | ```console 66 | $ git clone https://github.com/markdumay/synology-docker.git 67 | $ cd synology-docker 68 | ``` 69 | 70 | 71 | 72 | ## Usage 73 | *Synology-Docker* requires `sudo` rights. Use the following command to invoke *Synology-Docker* from the command line. 74 | 75 | ```console 76 | $ sudo ./syno_docker_update.sh [OPTIONS] COMMAND 77 | ``` 78 | 79 | ### Commands 80 | *Synology-Docker* supports the following commands. 81 | 82 | | Command | Argument | Description | 83 | |----------------|-----------|-------------| 84 | | **`backup`** | | Create a backup of Docker binaries (including Docker Compose), `dockerd` configuration, and Synology's `start-stop-status` script | 85 | | **`download`** | PATH | Download Docker and Docker Compose binaries to *PATH* | 86 | | **`install`** | PATH | Update Docker and Docker Compose from files on *PATH* | 87 | | **`restore`** | | Restore Docker and Docker Compose from a backup | 88 | | **`update`** | | Update Docker and Docker Compose to a target version (creates a backup first) | 89 | 90 | Under the hood, the five different commands invoke a specific workflow or sequence of steps. The below table shows the workflows and the order of steps for each of the commands. 91 | | # | Workflow step | backup | download | install | restore | update | 92 | |----|-----------------------------|--------|----------|---------|---------|---------| 93 | | A) | Download Docker binary | | Step 1 | | | Step 1 | 94 | | B) | Download Compose binary | | Step 2 | | | Step 2 | 95 | | C) | Extract files from backup | | | | Step 1 | | 96 | | D) | Stop Docker daemon | Step 1 | | Step 1 | Step 2 | Step 3 | 97 | | E) | Backup current files | Step 2 | | Step 2 | | Step 4 | 98 | | F) | Extract downloaded binaries | | | Step 3 | | Step 5 | 99 | | G) | Restore Docker binaries | | | | Step 3 | | 100 | | H) | Install Docker binaries | | | Step 4 | | Step 6 | 101 | | I) | Update log driver | | | Step 5 | | Step 7 | 102 | | J) | Restore log driver | | | | Step 4 | | 103 | | K) | Update Docker script | | | Step 5 | | Step 8 | 104 | | L) | Restore Docker script | | | | Step 5 | | 105 | | M) | Start Docker daemon | Step 3 | | Step 6 | Step 6 | Step 9 | 106 | | N) | Clean temp folder | | | | | Step 10 | 107 | 108 | * **A) Download Docker binary** - Downloads an archive containing Docker Engine binaries from `https://download.docker.com/linux/static/stable/x86_64/docker-${VERSION}.tgz`. The binaries are compatible with the Intel x86 (64 bit) architecture. Unless a specific version is specified by the `--docker` flag, *Synology-Docker* pulls the latest stable version available. 109 | * **B) Download Compose binary** - Downloads the Docker Compose binary from `https://github.com/docker/compose/releases/download/${VERSION}/docker-compose-Linux-x86_64`. Unless a specific version is specified by the `--compose` flag, *Synology-Docker* pulls the latest stable version available. 110 | * **C) Extract files from backup** - Extracts the files from a backup archive specified by the `--backup` flag to the temp directory (`/tmp/docker_update`). 111 | * **D) Stop Docker daemon** - Stops the Docker daemon by invoking `synoservicectl --stop pkgctl-Docker`. 112 | * **E) Backup current files** - Creates a backup of the current Docker binaries, including Docker Compose. The configuration of the logging driver and Synology's `start-stop-status` script are included in the archive too. The files included refer to `/var/packages/Docker/target/usr/bin/*`, `/var/packages/Docker/etc/dockerd.json`, and `/var/packages/Docker/scripts/start-stop-status`. 113 | * **F) Extract downloaded binaries** - Extracts the files from a downloaded archive to the temp directory (`/tmp/docker_update`). 114 | * **G) Restore Docker binaries** - Restores the Docker binaries in `/var/packages/Docker/target/usr/bin/*` with the binaries extracted from a backup archive. 115 | * **H) Install Docker binaries** - Installs downloaded and extracted Docker binaries (including Docker Compose) to the folder `/var/packages/Docker/target/usr/bin/`. 116 | * **I) Update log driver** - Replaces Synology's log driver with a default log driver `json-file` to improve compatibility. The configuration is updated at `/var/packages/Docker/etc/dockerd.json` 117 | * **J) Restore log driver** - Restores the log driver (`/var/packages/Docker/etc/dockerd.json`) from the configuration within a backup archive. 118 | * **K) Update Docker script** - Updates Synology's `start-stop-status` script for Docker to enable IP forwarding. This ensures containers can be properly reached in bridge networking mode. The script is updated at the location `/var/packages/Docker/scripts/start-stop-status`. 119 | * **L) Restore Docker script** - Restores the `start-stop-status` script (`/var/packages/Docker/scripts/start-stop-status`) from the file within a backup archive. 120 | * **M) Start Docker daemon** - Starts the Docker daemon by invoking `synoservicectl --start pkgctl-Docker`. 121 | * **N) Clean temp folder** - Removes files from the temp directory (`/tmp/docker_update`). The temporary files are created when extracting a downloaded archive or extracting a backup. 122 | 123 | 124 | ### Options 125 | *Synology-Docker* supports the following options. 126 | 127 | | Option | Alias | Argument | Description | 128 | |-------------|-------------|------------|-------------| 129 | | `-b` | `--backup` | `NAME` | Name of the backup (defaults to `docker_backup_YYMMDDHHMMSS.tgz`) | 130 | | `-c` | `--compose` | `VERSION` | Specify the Docker Compose target version (defaults to latest available on github.com) | 131 | | `-d` | `--docker` | `VERSION` | Specify the Docker target version (defaults to latest available on docker.com) | 132 | | `-f` | `--force` | | Force the update and bypass compatibility check / confirmation check | 133 | | `-p` | `--path` | | Path of the backup (defaults to current directory) | 134 | | `-s` | `--stage` | | Stage only, do not replace binaries or the configuration of log driver | 135 | 136 | ### Known Issues 137 | This [link][known_issues] contains an overview of known issues, including available workarounds. 138 | 139 | ## Contributing 140 | 1. Clone the repository and create a new branch 141 | ```console 142 | $ git checkout https://github.com/markdumay/synology-docker.git -b name_for_new_branch 143 | ``` 144 | 2. Make and test the changes 145 | 3. Submit a Pull Request with a comprehensive description of the changes 146 | 147 | ## Credits 148 | *Synology-Docker* is inspired by this [gist][gist_mikado8231] from Mikado8231. 149 | 150 | ## Donate 151 | Buy Me A Coffee 152 | 153 | ## License 154 | 155 | 156 | 157 | 158 | Copyright © [Mark Dumay][blog] 159 | 160 | 161 | 162 | 163 | [synology_url]: https://www.synology.com 164 | [synology_docker]: https://www.synology.com/en-us/dsm/packages/Docker 165 | [gist_mikado8231]: https://gist.github.com/Mikado8231/bf207a019373f9e539af4d511ae15e0d 166 | 167 | [acmesh_deploy]: https://github.com/acmesh-official/acme.sh/wiki/deployhooks 168 | [acmesh_url]: https://acme.sh 169 | [crontab_guru]: https://crontab.guru 170 | [docker_url]: https://docker.com 171 | [luka_wildcard]: https://www.blackvoid.club/lets-encrypt-docker-wild-card-certs/ 172 | [markus_renew]: https://lippertmarkus.com/2020/03/14/synology-le-dns-auto-renew/ 173 | [swarm_init]: https://docs.docker.com/engine/reference/commandline/swarm_init/ 174 | [xfelix_letsencrypt]: https://www.xfelix.com/2017/06/synology-letsencrypt-dns-01-cert-issue-and-install/ 175 | [portainer]: https://www.portainer.io 176 | [known_issues]: https://github.com/markdumay/synology-docker/issues/42 177 | [macvlan]: [https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/] 178 | 179 | 180 | 181 | 184 | [blog]: https://github.com/markdumay 185 | [repository]: https://github.com/markdumay/synology-docker.git 186 | -------------------------------------------------------------------------------- /syno_docker_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #====================================================================================================================== 4 | # Title : syno_docker_update.sh 5 | # Description : An Unofficial Script to Update or Restore Docker Engine and Docker Compose on Synology 6 | # Author : Mark Dumay 7 | # Date : November 26th, 2021 8 | # Version : 1.4.2 9 | # Usage : sudo ./syno_docker_update.sh [OPTIONS] COMMAND 10 | # Repository : https://github.com/markdumay/synology-docker.git 11 | # License : MIT - https://github.com/markdumay/synology-docker/blob/master/LICENSE 12 | # Credits : Inspired by https://gist.github.com/Mikado8231/bf207a019373f9e539af4d511ae15e0d 13 | # Comments : Use this script at your own risk. Refer to the license for the warranty disclaimer. 14 | #====================================================================================================================== 15 | 16 | #====================================================================================================================== 17 | # Constants 18 | #====================================================================================================================== 19 | readonly RED='\e[31m' # Red color 20 | readonly NC='\e[m' # No color / reset 21 | readonly BOLD='\e[1m' # Bold font 22 | readonly DSM_SUPPORTED_VERSION=6 23 | readonly DEFAULT_DOCKER_VERSION='20.10.11' 24 | readonly DEFAULT_COMPOSE_VERSION='2.1.1' 25 | readonly CPU_ARCH='x86_64' 26 | readonly DOWNLOAD_DOCKER="https://download.docker.com/linux/static/stable/${CPU_ARCH}" 27 | readonly DOWNLOAD_GITHUB='https://github.com/docker/compose' 28 | readonly GITHUB_API_COMPOSE='https://api.github.com/repos/docker/compose/releases/latest' 29 | readonly SYNO_DOCKER_SERV_NAME6='pkgctl-Docker' 30 | readonly SYNO_DOCKER_SERV_NAME7='Docker' 31 | readonly SYNO_SERVICE_TIMEOUT='5m' 32 | readonly SYNO_DOCKER_DIR='/var/packages/Docker' 33 | readonly SYNO_DOCKER_BIN_PATH="${SYNO_DOCKER_DIR}/target/usr" 34 | readonly SYNO_DOCKER_BIN="${SYNO_DOCKER_BIN_PATH}/bin" 35 | readonly SYNO_DOCKER_SCRIPT_PATH="${SYNO_DOCKER_DIR}/scripts" 36 | readonly SYNO_DOCKER_SCRIPT="${SYNO_DOCKER_SCRIPT_PATH}/start-stop-status" 37 | readonly SYNO_DOCKER_JSON_PATH="${SYNO_DOCKER_DIR}/etc" 38 | readonly SYNO_DOCKER_JSON="${SYNO_DOCKER_JSON_PATH}/dockerd.json" 39 | readonly SYNO_DOCKER_JSON_CONFIG="{ 40 | \"data-root\" : \"$SYNO_DOCKER_DIR/target/docker\", 41 | \"log-driver\" : \"json-file\", 42 | \"registry-mirrors\" : [], 43 | \"group\": \"administrators\" 44 | }" 45 | readonly SYNO_DOCKER_SCRIPT_FORWARDING='# ensure IP forwarding\n\t\tsudo iptables -P FORWARD ACCEPT\n' 46 | 47 | 48 | #====================================================================================================================== 49 | # Variables 50 | #====================================================================================================================== 51 | dsm_major_version='' 52 | docker_version='' 53 | compose_version='' 54 | temp_dir="/tmp/docker_update" 55 | backup_dir="${PWD}" 56 | download_dir="${temp_dir}" 57 | docker_backup_filename="docker_backup_$(date +%Y%m%d_%H%M%S).tgz" 58 | skip_docker_update='false' 59 | skip_compose_update='false' 60 | skip_driver_update='false' 61 | force='false' 62 | stage='false' 63 | command='' 64 | target='all' 65 | target_docker_version='' 66 | target_compose_version='' 67 | backup_filename_flag='false' 68 | step=0 69 | total_steps=0 70 | 71 | 72 | #====================================================================================================================== 73 | # Helper Functions 74 | #====================================================================================================================== 75 | 76 | #====================================================================================================================== 77 | # Display usage message. 78 | #====================================================================================================================== 79 | # Globals: 80 | # - backup_dir 81 | # Outputs: 82 | # Writes message to stdout. 83 | #====================================================================================================================== 84 | usage() { 85 | echo "Usage: $0 [OPTIONS] COMMAND" 86 | echo 87 | echo "Options:" 88 | echo " -b, --backup NAME Name of the backup (defaults to 'docker_backup_YYMMDDHHMMSS.tgz')" 89 | echo " -c, --compose VERSION Docker Compose target version (defaults to latest)" 90 | echo " -d, --docker VERSION Docker target version (defaults to latest)" 91 | echo " -f, --force Force update (bypass compatibility check and confirmation check)" 92 | echo " -p, --path PATH Path of the backup (defaults to '${backup_dir}')" 93 | echo " -s, --stage Stage only, do not actually replace binaries or configuration of log driver" 94 | echo " -t, --target Target to update, either 'all' (default), 'engine', 'compose', or 'driver'" 95 | echo 96 | echo "Commands:" 97 | echo " backup Create a backup of Docker and Docker Compose binaries and dockerd configuration" 98 | echo " download [PATH] Download Docker and Docker Compose binaries to PATH" 99 | echo " install [PATH] Update Docker and Docker Compose from files on PATH" 100 | echo " restore Restore Docker and Docker Compose from backup" 101 | echo " update Update Docker and Docker Compose to target version (creates backup first)" 102 | echo " validate Validates versions available for update" 103 | echo 104 | } 105 | 106 | #====================================================================================================================== 107 | # Displays error message on console and terminates with non-zero error. 108 | #====================================================================================================================== 109 | # Arguments: 110 | # $1 - Error message to display. 111 | # Outputs: 112 | # Writes error message to stderr, non-zero exit code. 113 | #====================================================================================================================== 114 | terminate() { 115 | printf "${RED}${BOLD}%s${NC}\n" "ERROR: $1" 116 | exit 1 117 | } 118 | 119 | #====================================================================================================================== 120 | # Print current progress to the console and shows progress against total number of steps. 121 | #====================================================================================================================== 122 | # Arguments: 123 | # $1 - Progress message to display. 124 | # Outputs: 125 | # Writes message to stdout. 126 | #====================================================================================================================== 127 | print_status() { 128 | step=$((step + 1)) 129 | printf "${BOLD}%s${NC}\n" "Step ${step} from ${total_steps}: $1" 130 | } 131 | 132 | #====================================================================================================================== 133 | # Detects the current versions for DSM, Docker, and Docker Compose and displays them on the console. It also verifies 134 | # the host runs DSM and that Docker (including Compose) is already installed, unless 'force' is set to true. 135 | #====================================================================================================================== 136 | # Globals: 137 | # - dsm_version 138 | # - dsm_major_version 139 | # - docker_version 140 | # - compose_version 141 | # - force 142 | # Outputs: 143 | # Writes message to stdout. Terminates with non-zero exit code if host is incompatible, unless 'force' is true. 144 | #====================================================================================================================== 145 | detect_current_versions() { 146 | # Detect current DSM version 147 | dsm_version=$(test -f '/etc.defaults/VERSION' && < '/etc.defaults/VERSION' grep '^productversion' | \ 148 | cut -d'=' -f2 | sed "s/\"//g") 149 | dsm_major_version=$(test -f '/etc.defaults/VERSION' && < '/etc.defaults/VERSION' grep '^majorversion' | \ 150 | cut -d'=' -f2 | sed "s/\"//g") 151 | 152 | # Detect current Docker version 153 | docker_version=$(docker -v 2>/dev/null | grep -Eo "[0-9]*.[0-9]*.[0-9]*," | cut -d',' -f 1) 154 | 155 | # Detect current Docker Compose version 156 | compose_version=$(docker-compose -v 2>/dev/null | grep -Eo "v[0-9]+.[0-9]*.[0-9]*" | cut -c 2-) 157 | if [ -z "${compose_version}" ] ; then 158 | compose_version=$(docker-compose -v 2>/dev/null | grep -Eo "[0-9]*.[0-9]*.[0-9]*," | cut -d',' -f 1) 159 | fi 160 | 161 | echo "Current DSM version: ${dsm_version:-Unknown}" 162 | echo "Current Docker version: ${docker_version:-Unknown}" 163 | echo "Current Docker Compose version: ${compose_version:-Unknown}" 164 | if [ "${force}" != 'true' ] ; then 165 | validate_current_version 166 | fi 167 | } 168 | 169 | #====================================================================================================================== 170 | # Verifies the host has the right CPU, runs DSM and that Docker (including Compose) is already installed. 171 | #====================================================================================================================== 172 | # Globals: 173 | # - dsm_version 174 | # - docker_version 175 | # - compose_version 176 | # - skip_docker_update 177 | # - skip_compose_update 178 | # Outputs: 179 | # Terminates with non-zero exit code if host is incompatible. 180 | #====================================================================================================================== 181 | validate_current_version() { 182 | # Test host has supported CPU, exit otherwise 183 | current_arch=$(uname -m) 184 | if [ "${current_arch}" != "${CPU_ARCH}" ]; then 185 | terminate "This script supports ${CPU_ARCH} CPUs only, use --force to override" 186 | fi 187 | 188 | # Test if host is DSM 6 or later, exit otherwise 189 | if [ "${dsm_major_version}" -lt "${DSM_SUPPORTED_VERSION}" ] ; then 190 | terminate "This script supports DSM 6.x or later only, use --force to override" 191 | fi 192 | 193 | # Test Docker version is present, exit otherwise 194 | if [ -z "${docker_version}" ] && [ "${skip_docker_update}" = 'false' ] ; then 195 | terminate "Could not detect current Docker version, use --force to override" 196 | fi 197 | 198 | # Test Docker Compose version is present, exit otherwise 199 | if [ -z "${compose_version}" ] && [ "${skip_compose_update}" = 'false' ]; then 200 | terminate "Could not detect current Docker Compose version, use --force to override" 201 | fi 202 | } 203 | 204 | #====================================================================================================================== 205 | # Detects Docker versions downloaded on disk and updates the target Docker version accordingly. Downloads are ignored 206 | # if a specific target Docker version is already specified. 207 | #====================================================================================================================== 208 | # Globals: 209 | # - target_docker_version 210 | # Outputs: 211 | # Updated 'target_docker_version'. 212 | #====================================================================================================================== 213 | detect_available_downloads() { 214 | if [ -z "${target_docker_version}" ] ; then 215 | downloads=$(find "${download_dir}/" -maxdepth 1 -type f | cut -c 4- | \ 216 | grep -Eo 'docker-[0-9]*.[0-9]*.[0-9]*(-ce)?.tgz') 217 | latest_download=$(echo "${downloads}" | sort -bt. -k1,1 -k2,2n -k3,3n -k4,4n -k5,5n | tail -1) 218 | target_docker_version=$(echo "${latest_download}" | sed "s/docker-//g" | sed "s/.tgz//g") 219 | fi 220 | } 221 | 222 | #====================================================================================================================== 223 | # Detects latest stable versions of Docker and Docker Compose available for download. The detection is skipped if a 224 | # specific target Docker and/or Compose version is already specified. Default versions are assigned if the detection 225 | # fails for some reason. 226 | #====================================================================================================================== 227 | # Globals: 228 | # - target_docker_version 229 | # - target_compose_version 230 | # - skip_docker_update 231 | # - skip_compose_update 232 | # Outputs: 233 | # Updated 'target_docker_version' and 'target_compose_version'. 234 | #====================================================================================================================== 235 | detect_available_versions() { 236 | # Detect latest available Docker version 237 | if [ -z "${target_docker_version}" ] && [ "${skip_docker_update}" = 'false' ] ; then 238 | docker_bin_files=$(curl -s "${DOWNLOAD_DOCKER}/" | grep -Eo '>docker-[0-9]*.[0-9]*.[0-9]*(-ce)?.tgz' | \ 239 | cut -c 2-) 240 | latest_docker_bin=$(echo "${docker_bin_files}" | sort -bt. -k1,1 -k2,2n -k3,3n -k4,4n -k5,5n | tail -1) 241 | target_docker_version=$(echo "${latest_docker_bin}" | sed "s/docker-//g" | sed "s/.tgz//g" ) 242 | 243 | if [ -z "${target_docker_version}" ] ; then 244 | echo "Could not detect Docker versions available for download, setting default value" 245 | target_docker_version="${DEFAULT_DOCKER_VERSION}" 246 | fi 247 | fi 248 | 249 | # Detect latest available stable Docker Compose version (ignores release candidates) 250 | if [ -z "${target_compose_version}" ] && [ "${skip_compose_update}" = 'false' ] ; then 251 | target_compose_version=$(curl -s "${GITHUB_API_COMPOSE}" | grep "tag_name" | grep -Eo "[0-9]+.[0-9]+.[0-9]+") 252 | 253 | if [ -z "${target_compose_version}" ] ; then 254 | echo "Could not detect Docker Compose versions available for download, setting default value" 255 | target_compose_version="${DEFAULT_COMPOSE_VERSION}" 256 | fi 257 | fi 258 | } 259 | 260 | #====================================================================================================================== 261 | # Validates the target versions for Docker and Docker Compose are defined, exits otherwise. 262 | #====================================================================================================================== 263 | # Globals: 264 | # - target_docker_version 265 | # - target_compose_version 266 | # - skip_docker_update 267 | # - skip_compose_update 268 | # Outputs: 269 | # Terminates with non-zero exit code if target version is unavailable for either Docker or Docker Compose. 270 | #====================================================================================================================== 271 | validate_available_versions() { 272 | # Test Docker is available for download, exit otherwise 273 | if [ -z "${target_docker_version}" ] && [ "${skip_docker_update}" = 'false' ] ; then 274 | terminate "Could not find Docker binaries for downloading" 275 | fi 276 | 277 | # Test Docker Compose is available for download, exit otherwise 278 | if [ -z "${target_compose_version}" ] && [ "${skip_compose_update}" = 'false' ] ; then 279 | terminate "Could not find Docker Compose binaries for downloading" 280 | fi 281 | } 282 | 283 | #====================================================================================================================== 284 | # Validates downloaded files for Docker and Docker Compose are available on the download path. The Docker binaries are 285 | # expected to be present as tar archive, whilst Docker compose should be a single binary file. The script exits if 286 | # either file is missing. 287 | #====================================================================================================================== 288 | # Globals: 289 | # - download_dir 290 | # - target_docker_version 291 | # - target_compose_version 292 | # - skip_docker_update 293 | # - skip_compose_update 294 | # Outputs: 295 | # Terminates with non-zero exit code if downloaded files for either Docker or Docker Compose are unavailable. 296 | #====================================================================================================================== 297 | validate_downloaded_versions() { 298 | # Test Docker archive is available on path 299 | target_docker_bin="docker-${target_docker_version}.tgz" 300 | if [ ! -f "${download_dir}/${target_docker_bin}" ] && [ "${skip_docker_update}" = 'false' ] ; then 301 | terminate "Could not find Docker archive (${download_dir}/${target_docker_bin})" 302 | fi 303 | 304 | # Test Docker-compose binary is available on path 305 | if [ ! -f "${download_dir}/docker-compose" ] && [ "${skip_compose_update}" = 'false' ] ; then 306 | terminate "Could not find Docker compose binary (${download_dir}/docker-compose)" 307 | fi 308 | } 309 | 310 | #====================================================================================================================== 311 | # Validates if a provided version string conforms to the expected SemVer pattern. The pattern should resemble 312 | # 'major.minor.revision'. For example, '6.2.3' is a valid version string, while '6.1' is not. 313 | #====================================================================================================================== 314 | # Arguments: 315 | # $1 - Version string to be verified. 316 | # $2 - Error message. 317 | # Outputs: 318 | # Terminates with non-zero exit code if the version string does conform to the expected pattern. 319 | #====================================================================================================================== 320 | validate_version_input() { 321 | validation=$(echo "$1" | grep -Eo "^[0-9]+.[0-9]+.[0-9]+") 322 | if [ "${validation}" != "$1" ] ; then 323 | usage 324 | terminate "$2" 325 | fi 326 | } 327 | 328 | #====================================================================================================================== 329 | # Verifies if the provided filename for the Docker backup is provided, exists otherwise. The backup directory and 330 | # backup filename are updated if the filename contains a path. 331 | #====================================================================================================================== 332 | # Globals: 333 | # - backup_dir 334 | # - docker_backup_filename 335 | # Arguments: 336 | # $1 - Error message. 337 | # Outputs: 338 | # Terminates with non-zero exit code if the provided backup filename is missing. 339 | #====================================================================================================================== 340 | validate_backup_filename() { 341 | # check filename is provided 342 | prefix=$(echo "${docker_backup_filename}" | cut -c1) 343 | if [ -z "${docker_backup_filename}" ] || [ "${prefix}" = "-" ] ; then 344 | usage 345 | terminate "$1" 346 | fi 347 | 348 | # split into directory and filename if applicable 349 | # TODO: test 350 | basepath=$(dirname "${docker_backup_filename}") 351 | if [ -z "${basepath}" ] || [ "${basepath}" != "." ]; then 352 | abs_path_and_file=$(readlink -f "${docker_backup_filename}") 353 | backup_dir=$(dirname "${abs_path_and_file}") 354 | docker_backup_filename=$(basename "${abs_path_and_file}") 355 | fi 356 | } 357 | 358 | #====================================================================================================================== 359 | # Verifies if the provided download directory is provided and available, exists otherwise. The download directory is 360 | # formatted as absolute path. 361 | #====================================================================================================================== 362 | # Globals: 363 | # - download_dir 364 | # Arguments: 365 | # $1 - Error message when path is not specified 366 | # $2 - Error message when path is not found 367 | # Outputs: 368 | # Terminates with non-zero exit code if the provided download path is missing or unavailable. Formats the download 369 | # directory as absolute path. 370 | #====================================================================================================================== 371 | validate_provided_download_path() { 372 | # check PATH is provided 373 | prefix=$(echo "${download_dir}" | cut -c1) 374 | if [ -z "${download_dir}" ] || [ "${prefix}" = "-" ] ; then 375 | usage 376 | terminate "$1" 377 | fi 378 | 379 | # cut trailing '/' and convert to absolute path 380 | download_dir=$(readlink -f "${download_dir}") 381 | 382 | # check PATH exists 383 | if [ ! -d "${download_dir}" ] ; then 384 | usage 385 | terminate "$2" 386 | fi 387 | } 388 | 389 | #====================================================================================================================== 390 | # Verifies if the provided backup directory is provided and available, exists otherwise. The backup directory should 391 | # also differ from the temp path, to avoid accidentaly removing the backup files. The backup directory is formatted as 392 | # absolute path. 393 | #====================================================================================================================== 394 | # Globals: 395 | # - backup_dir 396 | # Arguments: 397 | # $1 - Error message when path is not specified 398 | # $2 - Error message when path is not found 399 | # $3 - Error message when backup path equals temp directory 400 | # Outputs: 401 | # Terminates with non-zero exit code if the provided backup path is missing, unavailable, or invalid. Formats the 402 | # backup directory as absolute path. 403 | #====================================================================================================================== 404 | validate_provided_backup_path() { 405 | # check PATH is provided 406 | prefix=$(echo "${backup_dir}" | cut -c1) 407 | if [ -z "${backup_dir}" ] || [ "${prefix}" = "-" ] ; then 408 | usage 409 | terminate "$1" 410 | fi 411 | 412 | # cut trailing '/' and convert to absolute path 413 | backup_dir=$(readlink -f "${backup_dir}") 414 | 415 | # check PATH exists 416 | if [ ! -d "${backup_dir}" ] ; then 417 | usage 418 | terminate "$2" 419 | fi 420 | 421 | # confirm backup dir is different from temp dir 422 | if [ "${backup_dir}" = "${temp_dir}" ] ; then 423 | usage 424 | terminate "$3" 425 | fi 426 | } 427 | 428 | #====================================================================================================================== 429 | # Validates if the specified target is supported. Supported targets are 'all', 'engine', 'compose', or 'driver'. If no 430 | # target is specified, the default value is 'all'. The validation is case sensitive. 431 | #====================================================================================================================== 432 | # Globals: 433 | # - target 434 | # - skip_docker_update 435 | # - skip_compose_update 436 | # - skip_driver_update 437 | # Arguments: 438 | # $1 - Error message when target is invalid 439 | # Outputs: 440 | # Terminates with non-zero exit code if the specified target is invalid. 441 | #====================================================================================================================== 442 | validate_target() { 443 | case "${target}" in 444 | all ) 445 | skip_docker_update='false' 446 | skip_compose_update='false' 447 | skip_driver_update='false' 448 | ;; 449 | engine ) 450 | skip_docker_update='false' 451 | skip_compose_update='true' 452 | skip_driver_update='true' 453 | ;; 454 | compose ) 455 | skip_docker_update='true' 456 | skip_compose_update='false' 457 | skip_driver_update='true' 458 | ;; 459 | driver ) 460 | skip_docker_update='true' 461 | skip_compose_update='true' 462 | skip_driver_update='false' 463 | ;; 464 | * ) 465 | usage 466 | terminate "$1" 467 | esac 468 | } 469 | 470 | #====================================================================================================================== 471 | # Validates if the target version for either Docker or Docker Compose is newer than the currently installed version. 472 | # Terminates the script if both Docker and Docker Compose are already up to date, unless an update is forced. 473 | # Individual updates for either Docker or Docker Compose are skipped if they are already update to date, unless forced. 474 | #====================================================================================================================== 475 | # Globals: 476 | # - compose_version 477 | # - docker_version 478 | # - force 479 | # - skip_compose_update 480 | # - skip_docker_update 481 | # - target_compose_version 482 | # - target_docker_version 483 | # - total_steps 484 | # Outputs: 485 | # Terminates with non-zero exit code if both Docker and Docker Compose are already up to date, unless forced. 486 | #====================================================================================================================== 487 | define_update() { 488 | if [ "${force}" != 'true' ] ; then 489 | if [ "${docker_version}" = "${target_docker_version}" ] && \ 490 | [ "${compose_version}" = "${target_compose_version}" ] ; then 491 | terminate "Already on target version for Docker and Docker Compose" 492 | fi 493 | if [ "${docker_version}" = "${target_docker_version}" ] && [ "${skip_docker_update}" = 'false' ] ; then 494 | skip_docker_update='true' 495 | total_steps=$((total_steps-1)) 496 | fi 497 | if [ "${compose_version}" = "${target_compose_version}" ] && [ "${skip_compose_update}" = 'false' ]; then 498 | skip_compose_update='true' 499 | total_steps=$((total_steps-1)) 500 | fi 501 | fi 502 | } 503 | 504 | #====================================================================================================================== 505 | # Verifies a backup file is provided as argument for a restore operation. 506 | #====================================================================================================================== 507 | # Globals: 508 | # - backup_filename_flag 509 | # Outputs: 510 | # Terminates with non-zero exit code if no backup file is provided. 511 | #====================================================================================================================== 512 | define_restore() { 513 | if [ "${backup_filename_flag}" != 'true' ]; then 514 | terminate "Please specify backup filename (--backup NAME)" 515 | fi 516 | } 517 | 518 | #====================================================================================================================== 519 | # Defines the target versions for Docker and Docker Compose. See detect_available_versions() and 520 | # validate_available_versions() for additional information. 521 | #====================================================================================================================== 522 | # Globals: 523 | # - target_compose_version 524 | # - target_docker_version 525 | # - skip_docker_update 526 | # - skip_compose_update 527 | #====================================================================================================================== 528 | define_target_version() { 529 | detect_available_versions 530 | [ "${skip_docker_update}" = 'false' ] && echo "Target Docker version: ${target_docker_version:-Unknown}" 531 | [ "${skip_compose_update}" = 'false' ] && echo "Target Docker Compose version: ${target_compose_version:-Unknown}" 532 | validate_available_versions 533 | } 534 | 535 | #====================================================================================================================== 536 | # Identifies the version of a downloaded Docker archive. See detect_available_downloads() for additional 537 | # information. 538 | #====================================================================================================================== 539 | # Globals: 540 | # - target_docker_version 541 | # - skip_docker_update 542 | # - skip_compose_update 543 | #====================================================================================================================== 544 | define_target_download() { 545 | detect_available_downloads 546 | [ "${skip_docker_update}" = 'false' ] && echo "Target Docker version: ${target_docker_version:-Unknown}" 547 | [ "${skip_compose_update}" = 'false' ] && echo "Target Docker Compose version: Unknown" 548 | validate_downloaded_versions 549 | } 550 | 551 | #====================================================================================================================== 552 | # Prompts the user to confirm the operation, unless forced. 553 | #====================================================================================================================== 554 | # Globals: 555 | # - force 556 | # - skip_docker_update 557 | # - skip_compose_update 558 | # - skip_driver_update 559 | # Outputs: 560 | # Terminates with zero exit code if user does not confirm the operation. 561 | #====================================================================================================================== 562 | confirm_operation() { 563 | if [ "${force}" != 'true' ] ; then 564 | echo 565 | echo "WARNING! This will replace:" 566 | [ "${skip_docker_update}" = "false" ] && echo " - Docker Engine" 567 | [ "${skip_compose_update}" = "false" ] && echo " - Docker Compose" 568 | [ "${skip_driver_update}" = "false" ] && echo " - Docker daemon log driver" 569 | echo 570 | 571 | while true; do 572 | printf "Are you sure you want to continue? [y/N] " 573 | read -r yn 574 | yn=$(echo "${yn}" | tr '[:upper:]' '[:lower:]') 575 | 576 | case "${yn}" in 577 | y | yes ) break;; 578 | n | no | "" ) exit;; 579 | * ) echo "Please answer y(es) or n(o)";; 580 | esac 581 | done 582 | fi 583 | } 584 | 585 | #====================================================================================================================== 586 | # Workflow Functions 587 | #====================================================================================================================== 588 | 589 | #====================================================================================================================== 590 | # Recreates an empty temp folder. 591 | #====================================================================================================================== 592 | # Globals: 593 | # - temp_dir 594 | # Outputs: 595 | # An empty temp folder. 596 | #====================================================================================================================== 597 | execute_prepare() { 598 | execute_clean 'silent' 599 | mkdir -p "${temp_dir}" 600 | } 601 | 602 | #====================================================================================================================== 603 | # Stops a running Docker daemon by invoking 'synoservicectl' or 'synopkg', unless 'stage' is set to true. 604 | #====================================================================================================================== 605 | # Globals: 606 | # - stage 607 | # Outputs: 608 | # Stopped Docker daemon, or a non-zero exit code if the stop failed or timed out. 609 | #====================================================================================================================== 610 | execute_stop_syno() { 611 | print_status "Stopping Docker service" 612 | 613 | if [ "${stage}" = 'false' ] ; then 614 | case "${dsm_major_version}" in 615 | "6") 616 | syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME6}" | grep running -o) 617 | if [ "${syno_status}" = 'running' ] ; then 618 | timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synoservicectl --stop "${SYNO_DOCKER_SERV_NAME6}" 619 | syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME6}" | grep stop -o) 620 | if [ "${syno_status}" != 'stop' ] ; then 621 | terminate "Could not stop Docker daemon" 622 | fi 623 | fi 624 | ;; 625 | "7") 626 | syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME7}" | grep started -o) 627 | if [ "${syno_status}" = 'started' ] ; then 628 | timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synopkg stop "${SYNO_DOCKER_SERV_NAME7}" 629 | syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME7}" | grep stopped -o) 630 | if [ "${syno_status}" != 'stopped' ] ; then 631 | terminate "Could not stop Docker daemon" 632 | fi 633 | fi 634 | ;; 635 | *) 636 | echo "ERROR: Cannot start Docker package, unsupported DSM version: ${dsm_major_version}" 637 | esac 638 | else 639 | echo "Skipping Docker service control in STAGE mode" 640 | fi 641 | } 642 | 643 | #====================================================================================================================== 644 | # Creates a backup of the current Docker binaries (including Docker Compose), Docker daemon configuration, and 645 | # the 'start-stop-status' script. 646 | #====================================================================================================================== 647 | # Globals: 648 | # - backup_dir 649 | # - docker_backup_filename 650 | # Outputs: 651 | # A backup archive. 652 | #====================================================================================================================== 653 | execute_backup() { 654 | print_status "Backing up current Docker binaries (${backup_dir}/${docker_backup_filename})" 655 | cd "${backup_dir}" || terminate "Backup directory does not exist" 656 | tar -czvf "${docker_backup_filename}" -C "$SYNO_DOCKER_BIN_PATH" bin -C "$SYNO_DOCKER_JSON_PATH" "dockerd.json" \ 657 | -C "${SYNO_DOCKER_SCRIPT_PATH}" "start-stop-status" 658 | if [ ! -f "${docker_backup_filename}" ] ; then 659 | terminate "Backup issue" 660 | fi 661 | } 662 | 663 | #====================================================================================================================== 664 | # Downloads the targeted Docker binary archive, unless instructed to skip the download. 665 | #====================================================================================================================== 666 | # Globals: 667 | # - download_dir 668 | # - skip_docker_update 669 | # - target_docker_version 670 | # Outputs: 671 | # A downloaded Docker binaries archive, or a non-zero exit code if the download has failed. 672 | #====================================================================================================================== 673 | execute_download_bin() { 674 | if [ "${skip_docker_update}" = 'false' ] ; then 675 | target_docker_bin="docker-${target_docker_version}.tgz" 676 | print_status "Downloading target Docker binary (${DOWNLOAD_DOCKER}/${target_docker_bin})" 677 | response=$(curl "${DOWNLOAD_DOCKER}/$target_docker_bin" --write-out '%{http_code}' \ 678 | -o "${download_dir}/${target_docker_bin}") 679 | if [ "${response}" != 200 ] ; then 680 | terminate "Binary could not be downloaded" 681 | fi 682 | fi 683 | } 684 | 685 | #====================================================================================================================== 686 | # Extracts a downloaded Docker binaries archive in the temp folder, unless instructed to skip the update. 687 | #====================================================================================================================== 688 | # Globals: 689 | # - download_dir 690 | # - skip_docker_update 691 | # - target_docker_version 692 | # - temp_dir 693 | # Outputs: 694 | # An extracted Docker binaries archive, or a non-zero exit code if the extraction has failed. 695 | #====================================================================================================================== 696 | execute_extract_bin() { 697 | if [ "${skip_docker_update}" = 'false' ] ; then 698 | target_docker_bin="docker-${target_docker_version}.tgz" 699 | print_status "Extracting target Docker binary (${download_dir}/${target_docker_bin})" 700 | 701 | if [ ! -f "${download_dir}/${target_docker_bin}" ] ; then 702 | terminate "Docker binary archive not found" 703 | fi 704 | 705 | cd "${temp_dir}" || terminate "Temp directory does not exist" 706 | tar -zxvf "${download_dir}/${target_docker_bin}" 707 | if [ ! -d "docker" ] ; then 708 | terminate "Files could not be extracted from archive" 709 | fi 710 | fi 711 | } 712 | 713 | # TODO: fix 714 | #====================================================================================================================== 715 | # Extracts a Docker binaries backup archive in the temp folder. 716 | #====================================================================================================================== 717 | # Globals: 718 | # - backup_dir 719 | # - docker_backup_filename 720 | # - temp_dir 721 | # Outputs: 722 | # An extracted Docker binaries archive, or a non-zero exit code if expected files are not present in the backup. 723 | #====================================================================================================================== 724 | execute_extract_backup() { 725 | print_status "Extracting Docker backup (${backup_dir}/${docker_backup_filename})" 726 | 727 | if [ ! -f "${backup_dir}/${docker_backup_filename}" ] ; then 728 | terminate "Backup file not found" 729 | fi 730 | 731 | cd "${temp_dir}" || terminate "Temp directory does not exist" 732 | tar -zxvf "${backup_dir}/${docker_backup_filename}" 733 | mv bin docker 734 | 735 | if [ ! -d "docker" ] ; then 736 | terminate "Docker binaries could not be extracted from archive" 737 | fi 738 | if [ ! -f "docker/docker-compose" ] ; then 739 | terminate "Docker compose binary could not be extracted from archive" 740 | fi 741 | if [ ! -f "dockerd.json" ] ; then 742 | terminate "Log driver configuration could not be extracted from archive" 743 | fi 744 | } 745 | 746 | #====================================================================================================================== 747 | # Downloads the targeted Docker Compose binary, unless instructed to skip the download. As the download path has 748 | # changed since release of Docker Compose v2, this function checks the major version of the target binary and updates 749 | # the path accordingly. 750 | #====================================================================================================================== 751 | # Globals: 752 | # - download_dir 753 | # - skip_compose_update 754 | # - target_compose_version 755 | # Outputs: 756 | # A downloaded Docker Compose binary, or a non-zero exit code if the download has failed. 757 | #====================================================================================================================== 758 | execute_download_compose() { 759 | if [ "${skip_compose_update}" = 'false' ] ; then 760 | major_compose=$(echo "${target_compose_version}" | cut -d" " -f3 | cut -d "." -f1) 761 | base_path="${DOWNLOAD_GITHUB}/releases/download" 762 | # as of version 2, the download path uses a 'v' prefix and is in lower case 763 | compose_bin="${base_path}/v${target_compose_version}/docker-compose-linux-${CPU_ARCH}" 764 | if [ "${major_compose}" -lt 2 ] ; then 765 | # below version 2, the download path does not use a 'v' prefix and uses sentence case for the platform 766 | compose_bin="${base_path}/${target_compose_version}/docker-compose-Linux-${CPU_ARCH}" 767 | fi 768 | 769 | print_status "Downloading target Docker Compose binary (${compose_bin})" 770 | response=$(curl -L "${compose_bin}" --write-out '%{http_code}' -o "${download_dir}/docker-compose") 771 | if [ "${response}" != 200 ] ; then 772 | terminate "Binary could not be downloaded" 773 | fi 774 | fi 775 | } 776 | 777 | #====================================================================================================================== 778 | # Install the Docker and Docker Compose binaries, unless instructed to skip installation or when 'stage' is set to 779 | # true. 780 | #====================================================================================================================== 781 | # Globals: 782 | # - download_dir 783 | # - skip_compose_update 784 | # - skip_docker_update 785 | # - stage 786 | # - temp_dir 787 | # Outputs: 788 | # Installed Docker and Docker Compose binaries. 789 | #====================================================================================================================== 790 | execute_install_bin() { 791 | print_status "Installing binaries" 792 | if [ "${stage}" = 'false' ] ; then 793 | if [ "${skip_docker_update}" = 'false' ] ; then 794 | cp "${temp_dir}"/docker/* "${SYNO_DOCKER_BIN}"/ 795 | fi 796 | if [ "${skip_compose_update}" = 'false' ] ; then 797 | cp "${download_dir}"/docker-compose "${SYNO_DOCKER_BIN}"/docker-compose 798 | fi 799 | chown root:root "${SYNO_DOCKER_BIN}"/* 800 | chmod +x "${SYNO_DOCKER_BIN}"/* 801 | mkdir -p /var/lib/docker/volumes # creates folder to improve compatability for some containers 802 | else 803 | echo "Skipping installation in STAGE mode" 804 | fi 805 | } 806 | 807 | #====================================================================================================================== 808 | # Restores the Docker and Docker Compose binaries extracted from a backup archive, unless 'stage' is set to true. 809 | #====================================================================================================================== 810 | # Globals: 811 | # - stage 812 | # - temp_dir 813 | # - skip_docker_update 814 | # - skip_compose_update 815 | # Outputs: 816 | # Restored Docker and Docker Compose binaries. 817 | #====================================================================================================================== 818 | # TODO: validate this function 819 | execute_restore_bin() { 820 | print_status "Restoring binaries" 821 | if [ "${stage}" = 'false' ] ; then 822 | if [ "${skip_docker_update}" = 'true' ] && [ "${skip_compose_update}" = 'true' ] ; then 823 | echo "Skipping restore of binaries" 824 | fi 825 | # copy Docker Engine binaries 826 | if [ "${skip_docker_update}" = 'false' ] ; then 827 | find "${temp_dir}"/docker/ -type f \( ! -name docker-compose \) -print -exec cp -rpf '{}' "${SYNO_DOCKER_BIN}"/ ";" 828 | fi 829 | # copy Docker Compose 830 | if [ "${skip_compose_update}" = 'false' ] ; then 831 | cp "${temp_dir}"/docker/docker-compose "${SYNO_DOCKER_BIN}"/ 832 | fi 833 | chown root:root "${SYNO_DOCKER_BIN}"/* 834 | chmod +x "${SYNO_DOCKER_BIN}"/* 835 | else 836 | echo "Skipping restore in STAGE mode" 837 | fi 838 | } 839 | 840 | #====================================================================================================================== 841 | # Updates the log driver of the Docker daemon, unless 'stage' is set to true. 842 | #====================================================================================================================== 843 | # Globals: 844 | # - stage 845 | # - skip_driver_update 846 | # Outputs: 847 | # Updated Docker daemon configuration. 848 | #====================================================================================================================== 849 | execute_update_log() { 850 | print_status "Configuring log driver" 851 | if [ "${stage}" = 'false' ] && [ "${skip_driver_update}" = 'false' ] ; then 852 | log_driver=$(grep "json-file" "${SYNO_DOCKER_JSON}") 853 | if [ ! -f "${SYNO_DOCKER_JSON}" ] || [ -z "${log_driver}" ] ; then 854 | mkdir -p "${SYNO_DOCKER_JSON_PATH}" 855 | echo "${SYNO_DOCKER_JSON_CONFIG}" > "${SYNO_DOCKER_JSON}" 856 | fi 857 | else 858 | echo "Skipping configuration in STAGE mode or TARGET mode" 859 | fi 860 | } 861 | 862 | #====================================================================================================================== 863 | # Updates Synology's start-stop-status script for Docker to ensure IP forwarding is enabled, unless 'stage' is set to 864 | # true. 865 | #====================================================================================================================== 866 | # Globals: 867 | # - stage 868 | # Outputs: 869 | # Updated start-stop-status script. 870 | #====================================================================================================================== 871 | execute_update_script() { 872 | print_status "Enabling IP forwarding" 873 | if [ "${stage}" = 'false' ] ; then 874 | if ! grep -q 'iptables -P FORWARD ACCEPT' "${SYNO_DOCKER_SCRIPT}"; then 875 | match='# start docker' 876 | sed -i "s/${match}/${SYNO_DOCKER_SCRIPT_FORWARDING}\n\t\t${match}/" "${SYNO_DOCKER_SCRIPT}" 877 | fi 878 | else 879 | echo "Skipping configuration in STAGE mode" 880 | fi 881 | } 882 | 883 | #====================================================================================================================== 884 | # Restores the Docker daemon log driver extracted from a backup archive, unless 'stage' is set to true. 885 | #====================================================================================================================== 886 | # Globals: 887 | # - stage 888 | # - temp_dir 889 | # - skip_driver_update 890 | # Outputs: 891 | # Updated Docker daemon configuration. 892 | #====================================================================================================================== 893 | execute_restore_log() { 894 | print_status "Restoring log driver" 895 | if [ "${stage}" = 'false' ] && [ "${skip_driver_update}" = 'false' ] ; then 896 | cp "${temp_dir}"/dockerd.json "${SYNO_DOCKER_JSON}" 897 | else 898 | echo "Skipping restoring in STAGE mode or TARGET mode" 899 | fi 900 | } 901 | 902 | #====================================================================================================================== 903 | # Restores Synology's Docker start-stop-status script from a backup archive, unless 'stage' is set to true. 904 | #====================================================================================================================== 905 | # Globals: 906 | # - stage 907 | # - temp_dir 908 | # Outputs: 909 | # Updated start-stop-status script. 910 | #====================================================================================================================== 911 | execute_restore_script() { 912 | print_status "Restoring start-stop-status script" 913 | if [ "${stage}" = 'false' ] ; then 914 | cp "${temp_dir}"/start-stop-status "${SYNO_DOCKER_SCRIPT}" 915 | else 916 | echo "Skipping restoring in STAGE mode or TARGET mode" 917 | fi 918 | } 919 | 920 | #====================================================================================================================== 921 | # Start the Docker daemon by invoking 'synoservicectl' or 'synopkg', unless 'stage' is set to true. 922 | #====================================================================================================================== 923 | # Globals: 924 | # - force 925 | # - stage 926 | # Outputs: 927 | # Started Docker daemon, or a non-zero exit code if the start failed or timed out. 928 | #====================================================================================================================== 929 | execute_start_syno() { 930 | print_status "Starting Docker service" 931 | 932 | if [ "${stage}" = 'false' ] ; then 933 | case "${dsm_major_version}" in 934 | "6") 935 | timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synoservicectl --start "${SYNO_DOCKER_SERV_NAME6}" 936 | 937 | syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME6}" | grep running -o) 938 | if [ "${syno_status}" != 'running' ] ; then 939 | if [ "${force}" != 'true' ] ; then 940 | terminate "Could not bring Docker Engine back online" 941 | else 942 | echo "ERROR: Could not bring Docker Engine back online" 943 | fi 944 | fi 945 | ;; 946 | "7") 947 | timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synopkg start "${SYNO_DOCKER_SERV_NAME7}" 948 | 949 | syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME7}" | grep started -o) 950 | if [ "${syno_status}" != 'started' ] ; then 951 | if [ "${force}" != 'true' ] ; then 952 | terminate "Could not bring Docker Engine back online" 953 | else 954 | echo "ERROR: Could not bring Docker Engine back online" 955 | fi 956 | fi 957 | ;; 958 | *) 959 | echo "ERROR: Cannot start Docker package, unsupported DSM version: ${dsm_major_version}" 960 | esac 961 | else 962 | echo "Skipping Docker service control in STAGE mode" 963 | fi 964 | } 965 | 966 | #====================================================================================================================== 967 | # Removes the temp folder. 968 | #====================================================================================================================== 969 | # Globals: 970 | # - temp_dir 971 | # Arguments: 972 | # $1 - Silences any status messages if set to 'silent' 973 | # Outputs: 974 | # Removed temp folder. 975 | #====================================================================================================================== 976 | execute_clean() { 977 | if [ "$1" != 'silent' ] ; then 978 | print_status "Cleaning the temp folder" 979 | fi 980 | rm -rf "${temp_dir}" 981 | } 982 | 983 | #====================================================================================================================== 984 | # Main Script 985 | #====================================================================================================================== 986 | 987 | #====================================================================================================================== 988 | # Entrypoint for the script. It initializes the environment variables, generates the DNS plugin configuration, and 989 | # runs the certbot to issue/renew the certificate. 990 | #====================================================================================================================== 991 | main() { 992 | # Show header 993 | echo "Update Docker Engine and Docker Compose on Synology to target version" 994 | echo 995 | 996 | # Test if script has root privileges, exit otherwise 997 | id=$(id -u) 998 | if [ "${id}" -ne 0 ]; then 999 | usage 1000 | terminate "You need to be root to run this script" 1001 | fi 1002 | 1003 | # Process and validate command-line arguments 1004 | while [ "$1" != "" ]; do 1005 | case "$1" in 1006 | -b | --backup ) 1007 | shift 1008 | docker_backup_filename="$1" 1009 | backup_filename_flag='true' 1010 | validate_backup_filename "Filename not provided" 1011 | ;; 1012 | -c | --compose ) 1013 | shift 1014 | target_compose_version="$1" 1015 | validate_version_input "${target_compose_version}" "Unrecognized target Docker Compose version" 1016 | ;; 1017 | -d | --docker ) 1018 | shift 1019 | target_docker_version="$1" 1020 | validate_version_input "${target_docker_version}" "Unrecognized target Docker version" 1021 | ;; 1022 | -f | --force ) 1023 | force='true' 1024 | ;; 1025 | -h | --help ) 1026 | usage 1027 | exit 1028 | ;; 1029 | -p | --path ) 1030 | shift 1031 | backup_dir="$1" 1032 | validate_provided_backup_path "Path not specified" "Path not found" \ 1033 | "Path is equal to temp directory, please specify a different path" 1034 | ;; 1035 | -s | --stage ) 1036 | stage='true' 1037 | ;; 1038 | -t | --target ) 1039 | shift 1040 | target="$1" 1041 | validate_target "Invalid target" 1042 | ;; 1043 | backup | restore | update | validate ) 1044 | command="$1" 1045 | ;; 1046 | download | install ) 1047 | command="$1" 1048 | shift 1049 | download_dir="$1" 1050 | validate_provided_download_path "Path not specified" "Path not found" 1051 | ;; 1052 | * ) 1053 | usage 1054 | terminate "Unrecognized parameter ($1)" 1055 | esac 1056 | shift 1057 | done 1058 | 1059 | # Execute workflows 1060 | case "${command}" in 1061 | backup ) 1062 | total_steps=3 1063 | detect_current_versions 1064 | execute_prepare 1065 | execute_stop_syno 1066 | execute_backup 1067 | execute_start_syno 1068 | ;; 1069 | download ) 1070 | total_steps=2 1071 | detect_current_versions 1072 | execute_prepare 1073 | define_target_version 1074 | execute_download_bin 1075 | execute_download_compose 1076 | ;; 1077 | install ) 1078 | total_steps=7 1079 | detect_current_versions 1080 | execute_prepare 1081 | define_target_download 1082 | confirm_operation 1083 | execute_stop_syno 1084 | execute_backup 1085 | execute_extract_bin 1086 | execute_install_bin 1087 | execute_update_log 1088 | execute_update_script 1089 | execute_start_syno 1090 | ;; 1091 | restore ) 1092 | total_steps=6 1093 | detect_current_versions 1094 | execute_prepare 1095 | define_restore 1096 | confirm_operation 1097 | execute_extract_backup 1098 | execute_stop_syno 1099 | execute_restore_bin 1100 | execute_restore_log 1101 | execute_restore_script 1102 | execute_start_syno 1103 | ;; 1104 | update ) 1105 | total_steps=10 1106 | detect_current_versions 1107 | execute_prepare 1108 | define_target_version 1109 | define_update 1110 | confirm_operation 1111 | execute_download_bin 1112 | execute_download_compose 1113 | execute_stop_syno 1114 | execute_backup 1115 | execute_extract_bin 1116 | execute_install_bin 1117 | execute_update_log 1118 | execute_update_script 1119 | execute_start_syno 1120 | execute_clean 1121 | ;; 1122 | validate ) 1123 | total_steps=3 1124 | detect_current_versions 1125 | define_target_version 1126 | define_update 1127 | ;; 1128 | * ) 1129 | usage 1130 | terminate "No command specified" 1131 | esac 1132 | 1133 | echo "Done." 1134 | } 1135 | 1136 | main "$@" --------------------------------------------------------------------------------