Hugvvn 1 settimana fa
parent
commit
97150f4a8f

+ 20 - 0
Dockerfile.dev

@@ -0,0 +1,20 @@
+FROM node:18-alpine AS builder
+
+WORKDIR /app
+COPY package*.json ./
+RUN npm config set registry https://registry.npmmirror.com && npm install
+COPY . .
+RUN npx vite build
+
+FROM nginx:alpine
+
+RUN apk add --no-cache gettext
+COPY --from=builder /app/dist /usr/share/nginx/html
+COPY nginx/nginx.conf /etc/nginx/nginx.conf.tmpl
+COPY nginx/startup.sh /docker-entrypoint.d/01-envsubst.sh
+RUN chmod +x /docker-entrypoint.d/01-envsubst.sh
+
+ENV BACKEND_HOST=LQMDRetrieval:8007
+EXPOSE 80
+
+CMD ["nginx", "-g", "daemon off;"]

+ 21 - 0
docker/docker-compose.dev.yaml

@@ -0,0 +1,21 @@
+services:
+  LQMDRetrievalFront:
+    build:
+      context: /home/lq/lq_workspace/LQMDRetrievalFront/source/LQMDRetrievalFront
+      dockerfile: /home/lq/lq_workspace/LQMDRetrievalFront/source/LQMDRetrievalFront/Dockerfile.dev
+    image: lqmd_retrieval_front:v0.01
+    container_name: LQMDRetrievalFront
+    restart: always
+    environment:
+      - TZ=Asia/Shanghai
+      - BACKEND_HOST=LQMDRetrieval:8007
+    volumes:
+      - /home/lq/lq_workspace/LQMDRetrievalFront/nginx/logs:/var/log/nginx
+    ports:
+      - "0.0.0.0:9007:80"
+    networks:
+      - lq_network
+
+networks:
+  lq_network:
+    external: true

+ 47 - 0
nginx/nginx.conf

@@ -0,0 +1,47 @@
+events {
+    worker_connections 1024;
+}
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+    sendfile      on;
+    keepalive_timeout 65;
+    client_max_body_size 200m;
+
+    upstream backend {
+        server ${BACKEND_HOST};
+    }
+
+    server {
+        listen 80;
+        root /usr/share/nginx/html;
+
+        location / {
+            try_files $uri $uri/ /index.html;
+        }
+
+        location /api/ {
+            proxy_pass http://backend;
+            proxy_set_header Host $host:$server_port;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            proxy_set_header X-Forwarded-Port $server_port;
+            proxy_http_version 1.1;
+            proxy_set_header Connection "";
+            proxy_read_timeout 1200s;
+            proxy_buffering off;
+        }
+
+        location /health {
+            return 200 'ok';
+            add_header Content-Type text/plain;
+        }
+
+        error_page 500 502 503 504 /50x.html;
+        location = /50x.html {
+            root /usr/share/nginx/html;
+        }
+    }
+}

+ 4 - 0
nginx/startup.sh

@@ -0,0 +1,4 @@
+#!/bin/sh
+set -eu
+
+envsubst '${BACKEND_HOST}' < /etc/nginx/nginx.conf.tmpl > /etc/nginx/nginx.conf

+ 189 - 0
script/shell/deploy_lqmd_retrieval_front_dev.sh

@@ -0,0 +1,189 @@
+#!/bin/bash
+set -euo pipefail
+
+ENV="dev"
+PROJECT_NAME="LQMDRetrievalFront"
+BASE_DIR="/home/lq/lq_workspace/${PROJECT_NAME}"
+SRC_DIR="${BASE_DIR}/source/${PROJECT_NAME}"
+COMPOSE_RUN_DIR="${BASE_DIR}/app/docker"
+COMPOSE_FILE_DIR="${BASE_DIR}/app/docker"
+COMPOSE_FILE_NAME="docker-compose.${ENV}.yaml"
+SERVICES=("LQMDRetrievalFront")
+
+VERSION_SOURCE_SERVICE="${SERVICES[0]}"
+COMPOSE_FILE_PATH="${COMPOSE_FILE_DIR}/${COMPOSE_FILE_NAME}"
+
+log_info() {
+    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*"
+}
+
+log_error() {
+    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2
+}
+
+require_path() {
+    local type=$1
+    local path=$2
+    if [ "$type" = "file" ] && [ ! -f "$path" ]; then
+        log_error "file does not exist: $path"
+        exit 1
+    fi
+    if [ "$type" = "dir" ] && [ ! -d "$path" ]; then
+        log_error "directory does not exist: $path"
+        exit 1
+    fi
+}
+
+get_current_version() {
+    local service=$1
+    local compose_file=$2
+    local image_line
+    image_line=$(grep -A 20 "^[[:space:]]*${service}:" "$compose_file" | grep -E "^[[:space:]]*image:" | head -1) || {
+        log_error "image field not found for service: $service"
+        exit 1
+    }
+    image_line=$(echo "$image_line" | sed 's/#.*$//')
+    echo "$image_line" | sed -E 's/.*:([^:]*)$/\1/' | tr -d '[:space:]'
+}
+
+increment_version() {
+    local ver=$1
+    if [[ ! $ver =~ ^v[0-9]+\.[0-9]+$ ]]; then
+        log_error "invalid version format, expected v0.01 or v1.2, got: $ver"
+        exit 1
+    fi
+    local prefix="${ver%.*}"
+    local last_digit="${ver##*.}"
+    local len=${#last_digit}
+    local num=$((10#$last_digit + 1))
+    local new_last
+    new_last=$(printf "%0${len}d" "$num")
+    echo "${prefix}.${new_last}"
+}
+
+update_compose_version_for_services() {
+    local compose_file=$1
+    local new_version=$2
+    shift 2
+    local services=("$@")
+
+    cp "$compose_file" "${compose_file}.bak"
+    for svc in "${services[@]}"; do
+        sed -i -E "/^[[:space:]]*${svc}:/,/^[[:space:]]*image:/ s/(image:[[:space:]]*.*:)([^[:space:]#]*)([[:space:]]*#.*)?$/\1${new_version}\3/" "$compose_file"
+        log_info "updated $svc image version to $new_version"
+    done
+}
+
+get_image_repository() {
+    local service=$1
+    local compose_file=$2
+    local image_line
+    image_line=$(grep -A 20 "^[[:space:]]*${service}:" "$compose_file" | grep -E "^[[:space:]]*image:" | head -1)
+    image_line=$(echo "$image_line" | sed 's/#.*$//')
+    echo "$image_line" | sed -E 's/^[[:space:]]*image:[[:space:]]*//' | sed -E 's/:[^:]*$//' | tr -d '[:space:]'
+}
+
+cleanup_old_images_for_services() {
+    local compose_file=$1
+    shift
+    local services=("$@")
+
+    for svc in "${services[@]}"; do
+        local image_repo
+        image_repo=$(get_image_repository "$svc" "$compose_file")
+        mapfile -t all_tags < <(docker images --format "{{.Repository}}:{{.Tag}}" | grep "^${image_repo}:" | sed "s|${image_repo}:||" | sort -V)
+        if [ ${#all_tags[@]} -le 3 ]; then
+            log_info "$svc image count <= 3, skip cleanup"
+            continue
+        fi
+        local to_delete=$(( ${#all_tags[@]} - 3 ))
+        for (( i=0; i<to_delete; i++ )); do
+            docker rmi "${image_repo}:${all_tags[$i]}" || log_error "failed to remove image ${image_repo}:${all_tags[$i]}"
+        done
+    done
+}
+
+check_all_containers_running() {
+    local services=("$@")
+    for i in {1..15}; do
+        sleep 2
+        local all_running=true
+        for svc in "${services[@]}"; do
+            local container_id
+            container_id=$(docker compose -f "$COMPOSE_FILE_PATH" ps -q "$svc")
+            local status
+            status=$(docker inspect --format='{{.State.Status}}' "$container_id" 2>/dev/null || echo "not_found")
+            if [ "$status" != "running" ]; then
+                all_running=false
+                log_info "$svc status: $status, waiting..."
+                break
+            fi
+        done
+        if [ "$all_running" = true ]; then
+            log_info "all target services are running"
+            return 0
+        fi
+    done
+    return 1
+}
+
+stop_and_remove_containers() {
+    local services=("$@")
+    for svc in "${services[@]}"; do
+        docker compose -f "$COMPOSE_FILE_PATH" stop "$svc" >/dev/null 2>&1 || true
+        docker compose -f "$COMPOSE_FILE_PATH" rm -f "$svc" >/dev/null 2>&1 || true
+    done
+}
+
+start_containers() {
+    local services=("$@")
+    docker compose -f "$COMPOSE_FILE_PATH" up -d "${services[@]}"
+}
+
+require_path file "$COMPOSE_FILE_PATH"
+require_path dir "$SRC_DIR"
+require_path dir "$COMPOSE_RUN_DIR"
+
+log_info "pull source code: $SRC_DIR"
+cd "$SRC_DIR"
+current_branch=$(git symbolic-ref --short HEAD)
+log_info "current branch: $current_branch"
+git pull origin "$current_branch"
+git log -1 --format="[latest commit] %H %ad %s" --date=format:'%Y-%m-%d %H:%M:%S'
+
+cd "$COMPOSE_RUN_DIR"
+CURRENT_VERSION=$(get_current_version "$VERSION_SOURCE_SERVICE" "$COMPOSE_FILE_PATH")
+NEW_VERSION=$(increment_version "$CURRENT_VERSION")
+log_info "version: $CURRENT_VERSION -> $NEW_VERSION"
+
+update_compose_version_for_services "$COMPOSE_FILE_PATH" "$NEW_VERSION" "${SERVICES[@]}"
+
+for svc in "${SERVICES[@]}"; do
+    log_info "build service: $svc"
+    DOCKER_BUILDKIT=1 docker compose -f "$COMPOSE_FILE_PATH" build "$svc" || {
+        log_error "build failed, restore compose file"
+        mv "${COMPOSE_FILE_PATH}.bak" "$COMPOSE_FILE_PATH"
+        exit 1
+    }
+done
+
+stop_and_remove_containers "${SERVICES[@]}"
+start_containers "${SERVICES[@]}"
+
+if check_all_containers_running "${SERVICES[@]}"; then
+    log_info "deploy succeeded, running version: $NEW_VERSION"
+    cleanup_old_images_for_services "$COMPOSE_FILE_PATH" "${SERVICES[@]}"
+    rm -f "${COMPOSE_FILE_PATH}.bak"
+else
+    log_error "new version failed, rollback to: $CURRENT_VERSION"
+    stop_and_remove_containers "${SERVICES[@]}"
+    mv "${COMPOSE_FILE_PATH}.bak" "$COMPOSE_FILE_PATH"
+    start_containers "${SERVICES[@]}"
+    check_all_containers_running "${SERVICES[@]}" || {
+        log_error "rollback failed, manual check required"
+        exit 1
+    }
+    exit 1
+fi
+
+log_info "deploy finished"