From ed6bef8039f4363ce2f70c3f2fb8e5c2feb50e28 Mon Sep 17 00:00:00 2001 From: shay7sev Date: Wed, 11 Feb 2026 16:31:27 +0800 Subject: [PATCH] feat(deploy): add docker --- deploy/README.md | 42 ++++++++++++++++ deploy/docker/Dockerfile | 51 +++++++++++++++++++ deploy/docker/docker-compose.yml | 12 +++++ deploy/docker/start.sh | 16 ++++++ deploy/docker/stop.sh | 10 ++++ deploy/validate-env.sh | 85 ++++++++++++++++++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/docker/Dockerfile create mode 100644 deploy/docker/docker-compose.yml create mode 100755 deploy/docker/start.sh create mode 100755 deploy/docker/stop.sh create mode 100755 deploy/validate-env.sh diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..a7f4baa --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,42 @@ +# iam-service 部署(仅核心服务) + +本目录仅包含 **iam-service** 的部署配置,明确排除以下组件: +- PostgreSQL(不在本目录内提供部署/启动) +- Redis(不在本目录内提供部署/启动) +- iam-front(不在本目录内提供部署/启动) + +## 环境变量(唯一真实来源) +- 唯一来源:`iam-service/.env` +- 部署脚本与 docker-compose 均以该文件作为环境变量来源(包括端口映射所需的 `${PORT}` 插值)。 + +## 密钥文件验证(以 data 目录为准) +- `iam-service/data/jwt_private_key.pem` +- `iam-service/data/jwt_public_key.pem` + +若 `.env` 中显式设置了 `JWT_PRIVATE_KEY_PEM` / `JWT_PUBLIC_KEY_PEM`,校验脚本会要求其内容与上述文件内容一致;若未设置,则要求上述文件存在且非空。 + +## 部署文件结构 +- `deploy/validate-env.sh`:环境变量与密钥文件校验 +- `deploy/docker/Dockerfile`:iam-service 镜像构建 +- `deploy/docker/docker-compose.yml`:仅定义 iam-service 服务 +- `deploy/docker/start.sh`:启动(会先执行校验脚本) +- `deploy/docker/stop.sh`:停止 + +## 启动/停止 + +启动: +```bash +bash deploy/docker/start.sh +``` + +停止: +```bash +bash deploy/docker/stop.sh +``` + +## 外部依赖(由你自行提供) +iam-service 启动仍需要以下外部服务地址(来自 `.env`): +- `DATABASE_URL`:指向已存在的 PostgreSQL 实例 +- `REDIS_URL`:指向已存在的 Redis 实例 + +注意:若通过 Docker 部署,`.env` 中的 `localhost/127.0.0.1` 指向的是容器自身,不是宿主机。此时请将地址改为宿主机可达 IP 或网关域名(例如 `host.docker.internal`,视你的 Docker 环境而定)。 diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile new file mode 100644 index 0000000..989f840 --- /dev/null +++ b/deploy/docker/Dockerfile @@ -0,0 +1,51 @@ +FROM rust:1.91-slim-bookworm AS builder +WORKDIR /usr/src/app + +# 官方 Rust 镜像中 CARGO_HOME = /usr/local/cargo +RUN echo '[source.crates-io]' > $CARGO_HOME/config.toml \ + && echo 'replace-with = "rsproxy-sparse"' >> $CARGO_HOME/config.toml \ + && echo '[source.rsproxy]' >> $CARGO_HOME/config.toml \ + && echo 'registry = "https://rsproxy.cn/crates.io-index"' >> $CARGO_HOME/config.toml \ + && echo '[source.rsproxy-sparse]' >> $CARGO_HOME/config.toml \ + && echo 'registry = "sparse+https://rsproxy.cn/index/"' >> $CARGO_HOME/config.toml \ + && echo '[registries.rsproxy]' >> $CARGO_HOME/config.toml \ + && echo 'index = "https://rsproxy.cn/crates.io-index"' >> $CARGO_HOME/config.toml + +# 验证一下文件是否真的存在(构建时会在 log 打印出来,让你放心) +RUN cat $CARGO_HOME/config.toml + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates pkg-config libssl-dev git openssh-client \ + && rm -rf /var/lib/apt/lists/* + +COPY Cargo.toml Cargo.lock ./ +COPY .cargo ./.cargo +RUN mkdir -p src && echo "fn main() {}" > src/main.rs +RUN cargo build --release --locked + +COPY src ./src +COPY docs ./docs +RUN touch src/main.rs +RUN cargo build --release --locked + +FROM debian:bookworm-slim AS runner +WORKDIR /app + +RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources \ + && sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates libssl3 \ + && rm -rf /var/lib/apt/lists/* + +RUN groupadd --system --gid 10001 iam \ + && useradd --system --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin iam \ + && mkdir -p /app/log /app/data \ + && chown -R iam:iam /app/log + +ENV PORT=5020 +EXPOSE 5020 + +COPY --from=builder /usr/src/app/target/release/iam-service /app/iam-service +USER iam +CMD ["/app/iam-service"] diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 0000000..1832480 --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,12 @@ +services: + iam-service: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile + env_file: + - ../../.env + ports: + - "${PORT}:${PORT}" + volumes: + - ../../data:/app/data:ro + - ../../log:/app/log diff --git a/deploy/docker/start.sh b/deploy/docker/start.sh new file mode 100755 index 0000000..2f70a72 --- /dev/null +++ b/deploy/docker/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +# 1. Validate environment +export DEPLOY_TARGET=docker +bash ../validate-env.sh + +# 2. Start +echo "Starting iam-service..." +# Explicit project name to avoid conflict +docker compose --env-file ../../.env -p iam-service up -d --build + +echo "iam-service is running." diff --git a/deploy/docker/stop.sh b/deploy/docker/stop.sh new file mode 100755 index 0000000..b580026 --- /dev/null +++ b/deploy/docker/stop.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +echo "Stopping iam-service..." +docker compose --env-file ../../.env -p iam-service down + +echo "iam-service stopped." diff --git a/deploy/validate-env.sh b/deploy/validate-env.sh new file mode 100755 index 0000000..6a70f7a --- /dev/null +++ b/deploy/validate-env.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +ENV_FILE="${ROOT_DIR}/.env" +DATA_DIR="${ROOT_DIR}/data" + +if [[ ! -f "${ENV_FILE}" ]]; then + echo "Missing .env: ${ENV_FILE}" + exit 1 +fi + +require_key() { + local key="$1" + if ! grep -Eq "^[[:space:]]*${key}=" "${ENV_FILE}"; then + echo "Missing required env var in .env: ${key}" + exit 1 + fi +} + +require_key "DATABASE_URL" +require_key "REDIS_URL" +require_key "JWT_SECRET" +require_key "AUTH_CODE_JWT_SECRET" + +if grep -Eq "^[[:space:]]*PORT=" "${ENV_FILE}"; then + port="$(grep -E "^[[:space:]]*PORT=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r' | tr -d '"')" + if [[ -n "${port}" && ! "${port}" =~ ^[0-9]+$ ]]; then + echo "PORT must be a number in .env (got: ${port})" + exit 1 + fi +fi + +if [[ "${DEPLOY_TARGET:-}" == "docker" ]]; then + redis_url="$(grep -E "^[[:space:]]*REDIS_URL=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')" + if [[ "${redis_url}" == redis://localhost* || "${redis_url}" == redis://127.0.0.1* ]]; then + echo "REDIS_URL cannot use localhost/127.0.0.1 for docker deployment: ${redis_url}" + exit 1 + fi + + db_url="$(grep -E "^[[:space:]]*DATABASE_URL=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')" + if [[ "${db_url}" == *://localhost* || "${db_url}" == *://127.0.0.1* ]]; then + echo "DATABASE_URL cannot use localhost/127.0.0.1 for docker deployment: ${db_url}" + exit 1 + fi +fi + +if [[ ! -f "${DATA_DIR}/jwt_private_key.pem" ]]; then + echo "Missing key file: ${DATA_DIR}/jwt_private_key.pem" + exit 1 +fi +if [[ ! -f "${DATA_DIR}/jwt_public_key.pem" ]]; then + echo "Missing key file: ${DATA_DIR}/jwt_public_key.pem" + exit 1 +fi + +if [[ ! -s "${DATA_DIR}/jwt_private_key.pem" ]]; then + echo "Empty key file: ${DATA_DIR}/jwt_private_key.pem" + exit 1 +fi +if [[ ! -s "${DATA_DIR}/jwt_public_key.pem" ]]; then + echo "Empty key file: ${DATA_DIR}/jwt_public_key.pem" + exit 1 +fi + +if grep -Eq "^[[:space:]]*JWT_PRIVATE_KEY_PEM=" "${ENV_FILE}" || grep -Eq "^[[:space:]]*JWT_PUBLIC_KEY_PEM=" "${ENV_FILE}"; then + env_priv="$(grep -E "^[[:space:]]*JWT_PRIVATE_KEY_PEM=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')" + env_pub="$(grep -E "^[[:space:]]*JWT_PUBLIC_KEY_PEM=" "${ENV_FILE}" | tail -n 1 | cut -d= -f2- | tr -d '\r')" + + file_priv="$(cat "${DATA_DIR}/jwt_private_key.pem")" + file_pub="$(cat "${DATA_DIR}/jwt_public_key.pem")" + + if [[ -n "${env_priv}" && "${env_priv}" != "${file_priv}" ]]; then + echo "JWT_PRIVATE_KEY_PEM in .env does not match data/jwt_private_key.pem" + exit 1 + fi + if [[ -n "${env_pub}" && "${env_pub}" != "${file_pub}" ]]; then + echo "JWT_PUBLIC_KEY_PEM in .env does not match data/jwt_public_key.pem" + exit 1 + fi +fi + +echo "iam-service .env validation OK"