├── .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 |
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 "$@"
--------------------------------------------------------------------------------