<QNAP 453D QTS-5.x> 日志记录:docker containers 管理脚本 dockerfilescopy.sh 可在 本地与容器间 互传文件,重启 删除 进入容器 免命令输入

created by Dave on 16Jan.2025

Updated on 6Feb.2025 ver1.3

Updated on 12Apr.2025 ver1.4 

  •         added option 8. Watching container logs 10.
  •         added option 9. Create new image from container

注:已经从最早发布的 3 个菜单版本,改到 7 菜单版本。8.添加 Entry container shell 功能

前言:

当昨天在文章中,露出了 $2 域名 daven.us , 这个域名是可以在公网上访问。 当初为了方便,但任何人都能在主页点 “编辑导航” 去编辑内容。刚添加了IP限制。还改了之前的保存会替换图片的错误,还有一个什么忘了。

修改文件后,要上传到 docker。每次都要敲几次键盘,用脚本替代这些重复操作。

然后想实现的功能,也随着越用也越多。最后是因为名字 docker file copy,增加了从 Container 上下载的功能,最后改为现在版本的样子。

都这么多了,添加第三选项:重启容器;
第四个选项:停止容器;
第五个选项:删除容器与关联 image 
第 6 选项:返回选择另一个容器

脚本:

环境:QNAP NAS, QTS-5.x
 

一、使用方式:

直接运行脚本 dcokerfilescopy.sh :

updated picture on 12Apr.2025.

它猫的,SecureCRT 不能显示颜色。网上抄的这功能没在上图表现出来。大圆点是 绿/红 。

二、功能描述:

  • 列出当前主机的所有 Containers 容器
  • 容器 Containers 按运行与停止分为两部分,显示在屏幕的上部与下部
  • 简化容器的显示信息,只显示:序号,Name,Image,ID,Ports 五个部分,并以此顺序显示
  • 使用序号输入,或是 0 退出程序。
  • 如果选择的容器是 停止的,会询问启动。
  • 输入序号扣,会显示选择的 容器 Container 信息,来确认。 确认使用 Yes/No
  • 提示“请选择操作”:  1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.停止容器 .退出
  • 当选择 1. 上传文件到容器
    • 就会执行文件复制到 容器 Container 的操作。但排除 venv 目录,与所有 "点/dot/."开始的文件与目录
    • 复制结束后,会询问 是否重启容器,并列出 容器的名字与ID。确认用 Yes/No
    • 重启后会显示,成功。
    • 判断容器是否运行。
    • 显示运行结果:“文件复制完成”  “操作完成,未重启容器” “容器重启成功" "容器正在运行" "操作成功完成” 这几个状态。
    • 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
  • 当选择 2.从容器下载文件
    • 先会在脚本运行目录上,创建一个 download_from_docker.container 子目录
    • 然后执行从 容器 Container 复制全部文件与目录 到 download_from_docker.container 目录下。
    • 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
  • 当选择 3.重启容器
    • 会立即执行 Container 容器的重启操作。
    • 重启后会显示,成功。
    • 判断容器是否运行。
    • 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
  • 当选择 4.停止容器
    • 会立即执行 Container 容器的停止操作
    • 容器停止后会显示,成功
    • 会判断容器是否运行。
    • 完成后返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
  • 选择 5. 删除容器与之关联 image 
    • 会询问是否继续 y/n
    • (字面意思)
    • 完成后返回 容器选择
  • 选择 6. 选择另一个容器
  • 选择 7.进入容器的shell
    • 退出会返回 提示“请选择操作”: 1. 上传文件到容器 2.从容器下载文件 3.重启容器 4.停止容器 5.删除容器与之关联 image 6.选择另一个容器 7.进入容器Shell 8.退出
  • added 选择 8. 监控 Container 日志输出。  按 ctrl+c 退出
  • added 选择 9. 使用当前 Container 制作新镜像
  • 选择 8 10  会退出脚本循环。
    • 返回 “程序结束”
  • 列表格式,基于我 laptop 显示器调整的。
  • 最后上传的是英文菜单版本。
  • 看别人的脚本,加入了进度计数。
  • 上传的版本使用中文提示语。

三、完整 dockerfilecopy.sh 文件内容

#!/bin/bash
# Created by Dave on 16Jan.2025
# Version 1.3 updated 6Feb.2025
# Version 1.4 updated on 12April2025
# Description: to copy files and directories to/from container and restart it.
# Updated History:
# 1.3 updated 6Feb.2025 added clear screen and menue 0 to display containers. 0 to refresh the container list and 99 to exit the program.
# 1.4 added Watching Docker logs and Create Image from container in option 8 and 9.



# error handling
set -euo pipefail

# Set UTF-8 encoding
export LANG=en_US.UTF-8

# Global variables
declare -a container_ids
declare -a container_names
declare -a container_images
declare -a container_ports
declare -a container_status
declare -i total_containers=0
readonly TIMEOUT=30
readonly DOWNLOAD_DIR="download_from_docker.container"

# Functions
handle_error() {
    echo "Error: $1" >&2
    exit 1
}


trap 'echo -e "\nOperation interrupted"; exit 1' INT TERM

check_container_access() {
    local container_id=$1
    if ! docker inspect "$container_id" >/dev/null 2>&1; then
        handle_error "Cannot access container: $container_id"
    fi
}

check_disk_space() {
    local required_space=$1 # in MB
    local available_space
    available_space=$(df -m . | awk 'NR==2 {print $4}')
    if [ "${available_space}" -lt "${required_space}" ]; then
        handle_error "Insufficient disk space. Required: ${required_space}MB, Available: ${available_space}MB"
    fi
}

display_containers() {
    clear
    echo "=================================================== Docker Containers Status ==================================================="
    
    echo -e "\033[1m🟢 Running Containers:\033[0m"
    printf "%2s │ %-30s │ %-35s │ %-12s │ %-40s\n" "No" "Name" "Image" "ID" "Ports"
    echo "───┼────────────────────────────────┼─────────────────────────────────────┼──────────────┼──────────────────────────────────────"
    
    container_ids=()
    container_names=()
    container_images=()
    container_ports=()
    container_status=()
    local count=1

    while read -r line; do
        if [ -n "$line" ]; then
            IFS='|' read -r id name image ports <<< "$line"
            printf "%2d │ %-30s │ %-35s │ %-12s │ %-40s\n" \
                   "$count" "$name" "$image" "$id" "$ports"
            container_ids[$count-1]="$id"
            container_names[$count-1]="$name"
            container_images[$count-1]="$image"
            container_ports[$count-1]="$ports"
            container_status[$count-1]="running"
            ((count++))
        fi
    done < <(docker ps --format "{{.ID}}|{{.Names}}|{{.Image}}|{{.Ports}}" 2>/dev/null || handle_error "Failed to get running containers")
    
    echo -e "\n\033[1m🔴 Stopped Containers:\033[0m"
    printf "%2s │ %-30s │ %-35s │ %-12s │ %-40s\n" "No" "Name" "Image" "ID" "Ports"
    echo "───┼────────────────────────────────┼─────────────────────────────────────┼──────────────┼──────────────────────────────────────"
      
    while read -r line; do
        if [ -n "$line" ]; then
            IFS='|' read -r id name image ports <<< "$line"
            printf "%2d │ %-30s │ %-35s │ %-12s │ %-40s\n" \
                   "$count" "$name" "$image" "$id" "$ports"
            container_ids[$count-1]="$id"
            container_names[$count-1]="$name"
            container_images[$count-1]="$image"
            container_ports[$count-1]="$ports"
            container_status[$count-1]="stopped"
            ((count++))
        fi
    done < <(docker ps -f "status=exited" --format "{{.ID}}|{{.Names}}|{{.Image}}|{{.Ports}}" 2>/dev/null || handle_error "Failed to get stopped containers")
    
    echo "════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════"
    
    total_containers=$((count-1))
    if [ $total_containers -eq 0 ]; then
        handle_error "No containers found"
    fi
}

display_selected_container() {
    local index=$1
    echo -e "\nSelected Container Information:"
    echo "────────────────────────────────────"
    printf "Number: %d\n" "$index"
    printf "Name: %s\n" "${container_names[$index-1]}"
    printf "Image: %s\n" "${container_images[$index-1]}"
    printf "Container ID: %s\n" "${container_ids[$index-1]}"
    printf "Ports: %s\n" "${container_ports[$index-1]}"
    printf "Status: %s\n" "${container_status[$index-1]}"
    echo "────────────────────────────────────"
}

restart_container() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    echo "Starting container restart: $container_name (ID: $container_id)"
    
    if ! docker restart "$container_id"; then
        handle_error "Failed to restart container"
    fi
    
    echo "Waiting for container to be ready..."
    local counter=0
    while [ $counter -lt $TIMEOUT ]; do
        if docker ps -q --filter "id=$container_id" | grep -q .; then
            echo "Container is running"
            return 0
        fi
        sleep 1
        ((counter++))
        echo -n "."
    done
    
    handle_error "Container failed to start within ${TIMEOUT} seconds"
}

download_from_container() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    
    echo -e "\nStarting file download from container..."
    echo "Container name: $container_name (ID: $container_id)"
    
    check_container_access "$container_id"
    
    check_disk_space 100
    
    if [ -d "$DOWNLOAD_DIR" ]; then
        echo "Removing existing download directory..."
        rm -rf "$DOWNLOAD_DIR" || handle_error "Failed to remove existing download directory"
    fi
    
    echo "Creating new download directory..."
    mkdir -p "$DOWNLOAD_DIR" || handle_error "Failed to create download directory"
    
    echo "Getting file list from container..."
    if ! docker exec "$container_id" find /app -type f > /tmp/container_files.txt; then
        handle_error "Failed to get file list from container"
    fi
    
    if ! docker exec "$container_id" find /app -type d > /tmp/container_dirs.txt; then
        handle_error "Failed to get directory list from container"
    fi
    
    echo "Creating directory structure..."
    while IFS= read -r dir; do
        local relative_dir=${dir#/app/}
        if [ -n "$relative_dir" ]; then
            mkdir -p "$DOWNLOAD_DIR/$relative_dir" || handle_error "Failed to create directory: $relative_dir"
        fi
    done < /tmp/container_dirs.txt
    
    echo "Downloading files..."
    local total_files=$(wc -l < /tmp/container_files.txt)
    local current_file=0
    
    while IFS= read -r file; do
        local relative_file=${file#/app/}
        if [ -n "$relative_file" ]; then
            ((current_file++))
            printf "Downloading (%d/%d): %s\n" "$current_file" "$total_files" "$relative_file"
            if ! docker cp "$container_id:$file" "$DOWNLOAD_DIR/$relative_file"; then
                echo "Warning: Failed to copy file: $relative_file"
            fi
        fi
    done < /tmp/container_files.txt
    
    rm -f /tmp/container_files.txt /tmp/container_dirs.txt
    
    echo "Download completed. Files saved in: $DOWNLOAD_DIR/"
}

upload_to_container() {
    local container_id=$1
    local total_files=0
    local current_file=0
    
    echo -e "\nStarting file upload to container..."
    
    check_container_access "$container_id"
    
    total_files=$(find . -type f -not -path "./venv/*" -not -path "./dockerfilecopy.sh" \
                    -not -path "*/.*" -not -path "./${DOWNLOAD_DIR}/*" \
                    -not -path "./Start_app.bat" | wc -l)
    
    find . -type f -not -path "./venv/*" -not -path "./dockerfilecopy.sh" \
           -not -path "*/.*" -not -path "./${DOWNLOAD_DIR}/*" -print0 | \
    while IFS= read -r -d '' file; do
        ((current_file++))
        printf "Uploading (%d/%d): %s\n" "$current_file" "$total_files" "$file"
        
        if [ ! -r "$file" ]; then
            echo "Warning: Cannot read file: $file"
            continue
        fi
        
        if ! docker cp "$file" "$container_id:/app/${file#./}"; then
            echo "Warning: Failed to copy file: $file"
        fi
    done
    
    echo "Uploading directories..."
    find . -type d -not -path "./venv" -not -path "./venv/*" -not -path "." \
           -not -path "*/.*" -not -path "./${DOWNLOAD_DIR}" \
           -not -path "./${DOWNLOAD_DIR}/*" -print0 | \
    while IFS= read -r -d '' dir; do
        echo "Copying directory: $dir"
        if ! docker cp "$dir" "$container_id:/app/"; then
            echo "Warning: Failed to copy directory: $dir"
        fi
    done
    
    echo "File upload completed"
}

stop_container() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    echo "Stopping container: $container_name (ID: $container_id)"
    
    if ! docker stop "$container_id"; then
        handle_error "Failed to stop container"
    fi
    
    echo "Waiting for container to stop..."
    local counter=0
    while [ $counter -lt $TIMEOUT ]; do
        if ! docker ps -q --filter "id=$container_id" | grep -q .; then
            echo "Container stopped successfully"
            return 0
        fi
        sleep 1
        ((counter++))
        echo -n "."
    done
    
    handle_error "Container failed to stop within ${TIMEOUT} seconds"
}

delete_container_and_image() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    local container_image="${container_images[$container_number-1]}"
    
    echo "Preparing to delete container: $container_name (ID: $container_id)"
    
    # Check if container is running and stop it first
    if [ "${container_status[$container_number-1]}" = "running" ]; then
        echo "Container is running. Stopping it first..."
        if ! stop_container "$container_id"; then
            handle_error "Failed to stop container before deletion"
        fi
    fi
    
    # Delete container
    echo "Deleting container..."
    if ! docker rm "$container_id"; then
        handle_error "Failed to delete container"
    fi
    
    # Delete image
    echo "Deleting associated image: $container_image"
    if ! docker rmi "$container_image"; then
        echo "Warning: Failed to delete image. It might be used by other containers."
    fi
    
    echo "Container and image deletion completed"

}

enter_container() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    
    echo "Entering container: $container_name (ID: $container_id)"
    
    if [ "${container_status[$container_number-1]}" = "stopped" ]; then
        echo "Container is stopped. Please start it first."
        return 1
    fi
    
    echo "Starting interactive shell..."
    if ! docker exec -it "$container_id" /bin/bash; then
        # Try with sh if bash is not available
        if ! docker exec -it "$container_id" /bin/sh; then
            handle_error "Failed to enter container. Neither bash nor sh shell available."
        fi
    fi
}

watching_container_logs() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    
    echo "Following logs for container: $container_name (ID: $container_id)"
    
    # 更明确地禁用中断陷阱
    trap '' INT
    
    if [ "${container_status[$container_number-1]}" = "stopped" ]; then
        echo "Warning: Container is stopped. Logs might be limited."
    fi
    
    echo "Press Ctrl+C to stop following logs and return to menu"
    sleep 1
    # 启动docker logs命令
    docker logs -f "$container_id" || true
    
    echo "Returning to menu..."
    
    # 重新启用中断陷阱
    trap 'echo -e "\nOperation interrupted"; exit 1' INT TERM
}

create_image_from_container() {
    local container_id=$1
    local container_name="${container_names[$container_number-1]}"
    local container_image="${container_images[$container_number-1]}"
    
    echo -e "\nCreating new image from container: $container_name (ID: $container_id)"
    
    # 询问新镜像的名称和标签
    echo -n "Enter new image name [default: ${container_image%:*}-new]: "
    read -r new_image_name
    
    # 如果用户没有输入,使用默认值
    if [ -z "$new_image_name" ]; then
        new_image_name="${container_image%:*}-new"
    fi
    
    echo -n "Enter tag [default: latest]: "
    read -r tag
    
    # 如果用户没有输入,使用默认值
    if [ -z "$tag" ]; then
        tag="latest"
    fi
    
    # 完整的新镜像名称
    local full_new_image="$new_image_name:$tag"
    
    echo "Creating new image: $full_new_image from container $container_name..."
    
    # 使用docker commit命令创建新镜像
    if ! docker commit "$container_id" "$full_new_image"; then
        handle_error "Failed to create new image from container"
    fi
    
    echo "New image created successfully: $full_new_image"
    
    # 询问是否要删除旧镜像
    echo -n "Do you want to remove the old image ($container_image)? (y/n): "
    read -r remove_old
    case $remove_old in
        [Yy]* )
            echo "Removing old image: $container_image"
            if ! docker rmi "$container_image"; then
                echo "Warning: Failed to remove old image. It might be used by other containers."
            else
                echo "Old image removed successfully"
            fi
            ;;
        * )
            echo "Old image retained"
            ;;
    esac
}

# menu
show_menu() {
    echo -e "\nPlease select operation:"
    echo "0. Display containers"
    echo "1. Upload files to container"
    echo "2. Download files from container"
    echo "3. Restart container"
    echo "4. Stop container"
    echo "5. Delete container and image"
    echo "6. Select another container"
    echo "7. Enter container shell"
    echo "8. Watching container logs"
    echo "9. Create new image from container"
    echo "10. Exit"
    echo -n "Enter option (0-9): "
}

# Main
main() {
    display_containers
    
    while true; do
        echo -n "Enter container number (1-$total_containers, Refresh 0, EXIT: 99): "
        read -r container_number
        
        if [ "$container_number" = "0" ]; then
            display_containers
            continue
        fi

        if [ "$container_number" = "99" ]; then
            echo "Program terminated"
            exit 0
        fi
        
        if ! [[ "$container_number" =~ ^[0-9]+$ ]] || \
           [ "$container_number" -lt 1 ] || \
           [ "$container_number" -gt "$total_containers" ]; then
            echo "Error: Please enter a valid number (0 to exit, 1-$total_containers)"
            continue
        fi
        
        display_selected_container "$container_number"
        
	echo -n "Confirm selection? (y=1/n=any): "
        read -r confirm
        case $confirm in
            [yY1]* )
                container_id="${container_ids[$container_number-1]}"
                break
                ;;
            * )
                echo "Cancelled, please select again"
                ;;
        esac
    done
    
    if [ "${container_status[$container_number-1]}" = "stopped" ]; then
        echo "Warning: Container is stopped"
        echo -n "Start container? (y=1/n=any): "
        read -r start_choice
        case $start_choice in
            [Yy1]* )
                if ! docker start "$container_id"; then
                    handle_error "Failed to start container"
                fi
                echo "Container started successfully"
                sleep 2
                ;;
            * )
                echo "Operation cancelled"
                exit 1
                ;;
        esac
    fi
    
    # Main menu loop
    while true; do
        show_menu
        read -r choice
        
        case $choice in
            0)
                display_containers
                ;;
            1)  
                upload_to_container "$container_id"
                while true; do
                    echo -n "Restart container? (y/n): "
                    read -r restart_choice
                    case $restart_choice in
                        [Yy]* )
                            if restart_container "$container_id"; then
                                echo "Operation completed successfully"
                            else
                                echo "Operation completed with restart errors"
                            fi
                            break
                            ;;
                        [Nn]* )
                            echo "Operation completed without container restart"
                            break
                            ;;
                        * )
                            echo "Please enter y or n"
                            ;;
                    esac
                done
                ;;
                
            2)  
                download_from_container "$container_id"
                ;;
                
            3)
                echo "Restarting container..."
                if restart_container "$container_id"; then
                    echo "Container restart completed successfully"
                else
                    echo "Container restart failed"
                fi
                ;;

            4)
                if [ "${container_status[$container_number-1]}" = "stopped" ]; then
                    echo "Container is already stopped"
                else
                    echo "Stopping container..."
                    if stop_container "$container_id"; then
                        container_status[$container_number-1]="stopped"
                        echo "Container stop completed successfully"
                    else
                        echo "Container stop failed"
                    fi
                fi
                ;;
            
            5)
                echo -n "Are you sure you want to delete the container:${container_names[$container_number-1]} and its image:${container_images[$container_number-1]}? (y/n): "
                read -r delete_confirm
                case $delete_confirm in
                    [Yy]* )
                        delete_container_and_image "$container_id"
                        echo "Returning to container selection..."
                        main
                        ;;
                    * )
                        echo "Deletion cancelled"
                        ;;
                esac
                ;;

            6)
                echo "Backing to container selection..."
                clear
                main
                ;;

            7)
                if enter_container "$container_id"; then
                    echo "Returning to container selection..."
                else
                    echo "Failed to enter container"
                fi
                ;;

            8)
                echo "Watching container logs (press Ctrl+C to return to menu)..."
                watching_container_logs "$container_id"
                ;;

            9)
                create_image_from_container "$container_id"
                ;;
        
            10)  
                echo "Program terminated"
                exit 0
                ;;

            *)
                echo "Invalid option, please try again"
                ;;
        esac
    done
}

main

提示:

  • 使用 chmod +x 给脚本执行权力 后运行

四、结束

这是写的并以文章发出的第4个 shell 批处理文件。个人的兴趣爱好,因为重复繁琐,所以编出的乱码。

  • 我用的 bash shell
  • 使用别人的脚本前,先查看脚本逻辑。
  • 本人不承担因脚本运行造成的损失。
  • 使用 AI 翻译成中文时,格式有错误,上传替换为英文菜单版本。
  • 除非主菜单有变化,才会更新 脚本文件。

笑话:

拿到一台 Linux 系统。

第一件事:删除法语语言包。 一定要在 root 用户下操作~

rm -fr /

别真做。再发一张美图。

Updated on 6Feb.2025

# 1.3 updated 6Feb.2025 added clear screen and menue 0 to display containers. 0 to refresh the container list and 99 to exit the program.

and posted full code in this article

Updated on 12Apr.2025

# Ver1.4 added Watching Docker logs and Create Image from container in option 8 and 9.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值