diff --git a/README.md b/README.md index f38427c..0ac91ab 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,12 @@ | :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. | | --- | +| :warning: If you're using the Nvidia driver on your synology, you will need to re-start the Nvidia driver, or re-run `nvidia-ctk runtime configure` to re-add the nvidia runtime after each run of this script in order for the driver to get re-added to docker. | +| --- | + +| :exclamation: Portainer Users - Portainer currently has an [issue](https://github.com/portainer/portainer/issues/10462) where it persists the first-used logging driver alongside container definitions. You may have to completely recreate (or duplicate and edit) containers created in portainer to use the `local` log driver. It would be best to do that with all of your containers BEFORE running this update. Portainer has created some challenges for users migrating from one log driver to another. You may need to spend more time re-creating containers after this update than if you were using compose. +| --- | + [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. +## Preparation before upgrade +If you're using *compose* for your containers, I highly recommend that before you run the upgrade (or restore, if you're going back to the original version) you go through and stop each running container. +```console +cd /volume1/docker/{my_container} +docker-compose down +``` +Because this upgrade modifies the default logger for docker, stopping (removing) and re-starting each container is required, since the logging mechanism is persisted during a compose docker build / start. You don't HAVE to do this before the upgrade, however if you don't, you'll get errors related to the logger for your containers, and will have to stop and start each container / stack after the upgrade anyway. + +Stopping all the containers prior to the upgrade / restore will also make the upgrade a lot faster, since the service stop and restart normally has to do the work of stopping and starting all containers. + +For a convenient way of enumerating all of the running compose projects, run the script: + +```console +./syno_docker_list_containers.sh +``` + +...if you see a container listed with !---not_managed_by_compose---! you'll need to make sure you know how to recreate this container after the upgrade. + ## Usage *Synology-Docker* requires `sudo` rights. Use the following command to invoke *Synology-Docker* from the command line. ```console -$ sudo ./syno_docker_update.sh [OPTIONS] COMMAND +sudo ./syno_docker_update.sh [OPTIONS] COMMAND ``` + + + + ### Commands *Synology-Docker* supports the following commands. @@ -113,11 +141,11 @@ Under the hood, the five different commands invoke a specific workflow or sequen * **F) Extract downloaded binaries** - Extracts the files from a downloaded archive to the temp directory (`/tmp/docker_update`). * **G) Restore Docker binaries** - Restores the Docker binaries in `/var/packages/Docker/target/usr/bin/*` with the binaries extracted from a backup archive. * **H) Install Docker binaries** - Installs downloaded and extracted Docker binaries (including Docker Compose) to the folder `/var/packages/Docker/target/usr/bin/`. -* **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` +* **I) Update log driver** - Replaces Synology's log driver with a default log driver `local` to improve compatibility while optimizing writes and limiting log file growth. The configuration is updated at `/var/packages/Docker/etc/dockerd.json` * **J) Restore log driver** - Restores the log driver (`/var/packages/Docker/etc/dockerd.json`) from the configuration within a backup archive. * **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`. * **L) Restore Docker script** - Restores the `start-stop-status` script (`/var/packages/Docker/scripts/start-stop-status`) from the file within a backup archive. -* **M) Start Docker daemon** - Starts the Docker daemon by invoking `synoservicectl --start pkgctl-Docker`. +* **M) Start Docker daemon** - Starts the Docker daemon by invoking `synoservicectl --start pkgctl-Docker` (or `synopkg start ContainerManager` on DSM 7). * **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. diff --git a/syno_docker_list_containers.sh b/syno_docker_list_containers.sh new file mode 100755 index 0000000..a4fe349 --- /dev/null +++ b/syno_docker_list_containers.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Store container information in an array +containers_info=() +readonly NOT_COMPOSE="!---not_managed_by_compose---!" +readonly MAYBE_PORTAINER="!---maybe_managed_by_portainer---!" + +# Get the list of containers and their compose locations +for c in $(docker ps -q); do + container_info=$(docker inspect "$c" --format "{{.Name}} {{if index .Config.Labels \"com.docker.compose.project.config_files\"}}{{index .Config.Labels \"com.docker.compose.project.config_files\"}}{{else}}${NOT_COMPOSE}{{end}}") + containers_info+=("$container_info") +done + +# Sort the array based on the second field (compose location) +IFS=$'\n' sorted_containers_info=($(printf "%s\n" "${containers_info[@]}" | sort -u -t " " -k 2)) + +# Calculate the maximum length for the container names and compose locations +max_container_length=0 +max_location_length=0 +for info in "${sorted_containers_info[@]}"; do + container=$(echo "$info" | awk '{print $1}') + location=$(echo "$info" | awk '{print $2}') + [ ${#container} -gt $max_container_length ] && max_container_length=${#container} + [ ${#location} -gt $max_location_length ] && max_location_length=${#location} +done + +# Print the header +printf "%-${max_container_length}s %s\n" "Container" "Compose_Location" +printf "%-${max_container_length}s %-${max_location_length}s\n" \ + "$(printf '%*s' "${max_container_length}" '' | tr ' ' '-')" \ + "$(printf '%*s' "${max_location_length}" '' | tr ' ' '-')" + + +# Print the sorted container information +for info in "${sorted_containers_info[@]}"; do + container=$(echo "$info" | awk '{print $1}') + location=$(echo "$info" | sed -e 's/^[^ ]* //') + if [ "$location" != "$NOT_COMPOSE" ] && [ ! -f $location ];then + location="${MAYBE_PORTAINER}" + fi + printf "%-${max_container_length}s %s\n" "$container" "$location" +done + diff --git a/syno_docker_update.sh b/syno_docker_update.sh index 5e78617..68731e0 100755 --- a/syno_docker_update.sh +++ b/syno_docker_update.sh @@ -13,6 +13,19 @@ # Comments : Use this script at your own risk. Refer to the license for the warranty disclaimer. #====================================================================================================================== +#====================================================================================================================== +# Displays error message on console and terminates with non-zero error. +#====================================================================================================================== +# Arguments: +# $1 - Error message to display. +# Outputs: +# Writes error message to stderr, non-zero exit code. +#====================================================================================================================== +terminate() { + printf "${RED}${BOLD}%s${NC}\n" "ERROR: $1" + exit 1 +} + #====================================================================================================================== # Constants #====================================================================================================================== @@ -26,10 +39,13 @@ readonly CPU_ARCH='x86_64' readonly DOWNLOAD_DOCKER="https://download.docker.com/linux/static/stable/${CPU_ARCH}" readonly DOWNLOAD_GITHUB='https://github.com/docker/compose' readonly GITHUB_API_COMPOSE='https://api.github.com/repos/docker/compose/releases/latest' -readonly SYNO_DOCKER_SERV_NAME6='pkgctl-Docker' -readonly SYNO_DOCKER_SERV_NAME7='Docker' -readonly SYNO_SERVICE_TIMEOUT='5m' -readonly SYNO_DOCKER_DIR='/var/packages/Docker' +[ -d "/var/packages/ContainerManager" ] && readonly SYNO_DOCKER_DIR='/var/packages/ContainerManager' && \ + readonly SYNO_DOCKER_SERV_NAME='ContainerManager' +[ -d "/var/packages/Docker" ] && readonly SYNO_DOCKER_DIR='/var/packages/Docker' && \ + readonly SYNO_DOCKER_SERV_NAME='pkgctl-Docker' +if [ -z "$SYNO_DOCKER_DIR" ]; then + terminate "Docker (or ContainerManager) folder was not found." +fi readonly SYNO_DOCKER_BIN_PATH="${SYNO_DOCKER_DIR}/target/usr" readonly SYNO_DOCKER_BIN="${SYNO_DOCKER_BIN_PATH}/bin" readonly SYNO_DOCKER_SCRIPT_PATH="${SYNO_DOCKER_DIR}/scripts" @@ -38,11 +54,22 @@ readonly SYNO_DOCKER_JSON_PATH="${SYNO_DOCKER_DIR}/etc" readonly SYNO_DOCKER_JSON="${SYNO_DOCKER_JSON_PATH}/dockerd.json" readonly SYNO_DOCKER_JSON_CONFIG="{ \"data-root\" : \"$SYNO_DOCKER_DIR/target/docker\", - \"log-driver\" : \"json-file\", + \"log-driver\" : \"local\", + \"log-opts\" : { + \"max-size\" : \"10m\", + \"max-file\" : \"3\" + }, \"registry-mirrors\" : [], \"group\": \"administrators\" }" readonly SYNO_DOCKER_SCRIPT_FORWARDING='# ensure IP forwarding\n\t\tsudo iptables -P FORWARD ACCEPT\n' +readonly SYNO_SERVICE_STOP_TIMEOUT='5m' +RUNNING_CONTAINERS=$(docker ps -q 2>/dev/null | wc -l 2>/dev/null || echo 0) +if [ "$RUNNING_CONTAINERS" -gt 5 ]; then + readonly SYNO_SERVICE_START_TIMEOUT=$(echo "$RUNNING_CONTAINERS * 1.5" | bc)m +else + readonly SYNO_SERVICE_START_TIMEOUT='5m' +fi #====================================================================================================================== @@ -103,19 +130,6 @@ usage() { echo } -#====================================================================================================================== -# Displays error message on console and terminates with non-zero error. -#====================================================================================================================== -# Arguments: -# $1 - Error message to display. -# Outputs: -# Writes error message to stderr, non-zero exit code. -#====================================================================================================================== -terminate() { - printf "${RED}${BOLD}%s${NC}\n" "ERROR: $1" - exit 1 -} - #====================================================================================================================== # Print current progress to the console and shows progress against total number of steps. #====================================================================================================================== @@ -613,20 +627,20 @@ execute_stop_syno() { if [ "${stage}" = 'false' ] ; then case "${dsm_major_version}" in "6") - syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME6}" | grep running -o) + syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME}" | grep running -o) if [ "${syno_status}" = 'running' ] ; then - timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synoservicectl --stop "${SYNO_DOCKER_SERV_NAME6}" - syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME6}" | grep stop -o) + timeout --foreground "${SYNO_SERVICE_STOP_TIMEOUT}" synoservicectl --stop "${SYNO_DOCKER_SERV_NAME}" + syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME}" | grep stop -o) if [ "${syno_status}" != 'stop' ] ; then terminate "Could not stop Docker daemon" fi fi ;; "7") - syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME7}" | grep started -o) + syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME}" | grep started -o) if [ "${syno_status}" = 'started' ] ; then - timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synopkg stop "${SYNO_DOCKER_SERV_NAME7}" - syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME7}" | grep stopped -o) + timeout --foreground "${SYNO_SERVICE_STOP_TIMEOUT}" synopkg stop "${SYNO_DOCKER_SERV_NAME}" + syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME}" | grep stopped -o) if [ "${syno_status}" != 'stopped' ] ; then terminate "Could not stop Docker daemon" fi @@ -927,14 +941,14 @@ execute_restore_script() { # Started Docker daemon, or a non-zero exit code if the start failed or timed out. #====================================================================================================================== execute_start_syno() { - print_status "Starting Docker service" + print_status "Starting Docker service - May take a while to restart ${RUNNING_CONTAINERS} containers... (up to ${SYNO_SERVICE_START_TIMEOUT})" if [ "${stage}" = 'false' ] ; then case "${dsm_major_version}" in "6") - timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synoservicectl --start "${SYNO_DOCKER_SERV_NAME6}" + timeout --foreground "${SYNO_SERVICE_START_TIMEOUT}" synoservicectl --start "${SYNO_DOCKER_SERV_NAME}" - syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME6}" | grep running -o) + syno_status=$(synoservicectl --status "${SYNO_DOCKER_SERV_NAME}" | grep running -o) if [ "${syno_status}" != 'running' ] ; then if [ "${force}" != 'true' ] ; then terminate "Could not bring Docker Engine back online" @@ -944,9 +958,9 @@ execute_start_syno() { fi ;; "7") - timeout --foreground "${SYNO_SERVICE_TIMEOUT}" synopkg start "${SYNO_DOCKER_SERV_NAME7}" + timeout --foreground "${SYNO_SERVICE_START_TIMEOUT}" synopkg start "${SYNO_DOCKER_SERV_NAME}" - syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME7}" | grep started -o) + syno_status=$(synopkg status "${SYNO_DOCKER_SERV_NAME}" | grep started -o) if [ "${syno_status}" != 'started' ] ; then if [ "${force}" != 'true' ] ; then terminate "Could not bring Docker Engine back online" @@ -1133,4 +1147,4 @@ main() { echo "Done." } -main "$@" \ No newline at end of file +main "$@"