diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a562191 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# OS / editor +.DS_Store +Thumbs.db +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Local env +.env +.env.local +.env.*.local + +# Python +__pycache__/ +*.py[cod] +*.pyd +.python-version +.venv/ +venv/ +env/ +ENV/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.pyre/ +.hypothesis/ +.tox/ +.nox/ +.coverage +.coverage.* +htmlcov/ +.eggs/ +*.egg-info/ +build/ +dist/ +site/ + +# Node / Next.js +node_modules/ +.pnpm-store/ +.pnp +.pnp.js +.next/ +.next-dev/ +out/ +coverage/ +*.tsbuildinfo +.turbo/ +.vercel/ +.eslintcache +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Misc +.cache/ +*.log diff --git a/README.md b/README.md index 74532bf..b6f0556 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - `authz-service/` - 鉴权服务容器。 - `src/` 来自 `Authz-service` - - `runtime/` 预留给后续启动脚本、数据目录映射说明。 + - 已包含 Dockerfile、启动脚本和空白种子数据。 - `auth-portal/` - 登录 / 注册 / 跳转入口容器。 - `src/` 来自 `nanobot-auth-portal` @@ -73,7 +73,8 @@ │ └── render-routes.py ├── authz-service │ ├── src/ -│ └── runtime/ +│ ├── runtime/ +│ └── Dockerfile └── auth-portal ├── src/ └── runtime/ @@ -95,6 +96,7 @@ - `/home/ivan/xuan/nano_project/app-instance/README.md` - `/home/ivan/xuan/nano_project/deploy-control/README.md` - `/home/ivan/xuan/nano_project/router-proxy/README.md` +- `/home/ivan/xuan/nano_project/authz-service/README.md` ## 后续建议 @@ -102,6 +104,5 @@ - `.env` 模板 - portal 到部署机的创建实例调用 -- authz-service / auth-portal 的 Dockerfile 和启动脚本 - portal 登录后的统一账号查找和跳转联调 -- authz-service 的部署脚本和配置注入 +- authz-service 的控制面接入和部署编排 diff --git a/authz-service/Dockerfile b/authz-service/Dockerfile new file mode 100644 index 0000000..853c0d1 --- /dev/null +++ b/authz-service/Dockerfile @@ -0,0 +1,35 @@ +# syntax=docker/dockerfile:1.7 + +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + AUTHZ_HOST=0.0.0.0 \ + AUTHZ_PORT=19090 \ + AUTHZ_DATA_DIR=/var/lib/authz-service/data + +WORKDIR /opt/authz-service + +COPY src/pyproject.toml src/uv.lock ./ +RUN mkdir -p app && touch app/__init__.py && \ + uv pip install --system --no-cache . + +COPY src/app ./app +RUN uv pip install --system --no-cache . + +COPY runtime/seed-data /opt/authz-service/seed-data +COPY docker-entrypoint.sh /opt/authz-service/docker-entrypoint.sh + +RUN chmod +x /opt/authz-service/docker-entrypoint.sh && \ + mkdir -p /var/lib/authz-service/data + +EXPOSE 19090 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=5 \ + CMD python - <<'PY' \ +import json, urllib.request \ +payload = json.loads(urllib.request.urlopen('http://127.0.0.1:19090/healthz', timeout=3).read().decode('utf-8')) \ +assert payload.get('status') == 'ok' \ +PY + +ENTRYPOINT ["/opt/authz-service/docker-entrypoint.sh"] diff --git a/authz-service/README.md b/authz-service/README.md new file mode 100644 index 0000000..f1f93f7 --- /dev/null +++ b/authz-service/README.md @@ -0,0 +1,54 @@ +# authz-service + +`authz-service` 现在已经整理成独立 Docker 单元。 + +## 组成 + +- `Dockerfile` + - 生产镜像构建入口 +- `docker-entrypoint.sh` + - 初始化数据目录并启动 `uvicorn` +- `start-authz.sh` + - 本地或服务器快速启动脚本 +- `env_template` + - 常用环境变量模板 +- `runtime/seed-data/` + - 空白初始化数据 +- `src/` + - 原始 FastAPI 应用代码 + +## 设计约定 + +- 容器监听端口:`19090` +- 容器内数据目录:`/var/lib/authz-service/data` +- 首次启动时如果数据目录为空,会自动写入空白 JSON +- `signing_key.pem` 不会被打进镜像 + - 如果挂载目录里不存在签名 key,服务会在首次启动时自动生成 + +## 快速启动 + +```bash +cd /home/ivan/xuan/nano_project/authz-service +AUTHZ_INTERNAL_TOKEN='change-me' ./start-authz.sh --build +``` + +启动后验证: + +```bash +curl http://127.0.0.1:19090/healthz +curl http://127.0.0.1:19090/.well-known/jwks.json +``` + +## 生产建议 + +- 用挂载卷保存 `runtime/data` +- 显式设置 `AUTHZ_INTERNAL_TOKEN` +- 显式设置外部可访问的 `AUTHZ_ISSUER` + - 例如 `https://authz.example.com` +- 不要把 `src/data/` 里的本地示例或真实数据直接拿去打镜像 + +## API 说明 + +接口说明仍然看: + +- `/home/ivan/xuan/nano_project/authz-service/src/README.md` diff --git a/authz-service/docker-entrypoint.sh b/authz-service/docker-entrypoint.sh new file mode 100755 index 0000000..50cdf35 --- /dev/null +++ b/authz-service/docker-entrypoint.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +AUTHZ_HOST="${AUTHZ_HOST:-0.0.0.0}" +AUTHZ_PORT="${AUTHZ_PORT:-19090}" +AUTHZ_DATA_DIR="${AUTHZ_DATA_DIR:-/var/lib/authz-service/data}" +AUTHZ_PRIVATE_KEY_PATH="${AUTHZ_PRIVATE_KEY_PATH:-${AUTHZ_DATA_DIR}/signing_key.pem}" +AUTHZ_ISSUER="${AUTHZ_ISSUER:-http://127.0.0.1:${AUTHZ_PORT}}" +SEED_DATA_DIR="/opt/authz-service/seed-data" + +mkdir -p "${AUTHZ_DATA_DIR}" + +for file_name in backends.json backend_credentials.json permissions.json settings.json users.json; do + if [[ ! -f "${AUTHZ_DATA_DIR}/${file_name}" ]]; then + cp "${SEED_DATA_DIR}/${file_name}" "${AUTHZ_DATA_DIR}/${file_name}" + fi +done + +export AUTHZ_HOST AUTHZ_PORT AUTHZ_DATA_DIR AUTHZ_PRIVATE_KEY_PATH AUTHZ_ISSUER + +exec uvicorn app.main:app --host "${AUTHZ_HOST}" --port "${AUTHZ_PORT}" diff --git a/authz-service/env_template b/authz-service/env_template new file mode 100644 index 0000000..054c002 --- /dev/null +++ b/authz-service/env_template @@ -0,0 +1,3 @@ +AUTHZ_ISSUER=http://127.0.0.1:19090 +AUTHZ_INTERNAL_TOKEN=change-me +AUTHZ_ACCESS_TOKEN_TTL_SECONDS=3600 diff --git a/authz-service/runtime/seed-data/backend_credentials.json b/authz-service/runtime/seed-data/backend_credentials.json new file mode 100644 index 0000000..4da0675 --- /dev/null +++ b/authz-service/runtime/seed-data/backend_credentials.json @@ -0,0 +1,3 @@ +{ + "credentials": [] +} diff --git a/authz-service/runtime/seed-data/backends.json b/authz-service/runtime/seed-data/backends.json new file mode 100644 index 0000000..4bdd9f1 --- /dev/null +++ b/authz-service/runtime/seed-data/backends.json @@ -0,0 +1,3 @@ +{ + "backends": [] +} diff --git a/authz-service/runtime/seed-data/permissions.json b/authz-service/runtime/seed-data/permissions.json new file mode 100644 index 0000000..fb6efa5 --- /dev/null +++ b/authz-service/runtime/seed-data/permissions.json @@ -0,0 +1,3 @@ +{ + "permissions": {} +} diff --git a/authz-service/runtime/seed-data/settings.json b/authz-service/runtime/seed-data/settings.json new file mode 100644 index 0000000..d920faf --- /dev/null +++ b/authz-service/runtime/seed-data/settings.json @@ -0,0 +1,3 @@ +{ + "settings": {} +} diff --git a/authz-service/runtime/seed-data/users.json b/authz-service/runtime/seed-data/users.json new file mode 100644 index 0000000..ea1ebff --- /dev/null +++ b/authz-service/runtime/seed-data/users.json @@ -0,0 +1,3 @@ +{ + "users": [] +} diff --git a/authz-service/start-authz.sh b/authz-service/start-authz.sh new file mode 100755 index 0000000..b9f00f3 --- /dev/null +++ b/authz-service/start-authz.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +IMAGE_NAME="${IMAGE_NAME:-nano/authz-service:latest}" +CONTAINER_NAME="${CONTAINER_NAME:-nano-authz-service}" +DATA_ROOT="${DATA_ROOT:-${SCRIPT_DIR}/runtime/data}" +HOST_PORT="${HOST_PORT:-19090}" +HOST_BIND_IP="${HOST_BIND_IP:-0.0.0.0}" +AUTHZ_ISSUER="${AUTHZ_ISSUER:-http://127.0.0.1:${HOST_PORT}}" +AUTHZ_INTERNAL_TOKEN="${AUTHZ_INTERNAL_TOKEN:-dev-internal-token}" +AUTHZ_ACCESS_TOKEN_TTL_SECONDS="${AUTHZ_ACCESS_TOKEN_TTL_SECONDS:-3600}" +FORCE_BUILD=0 +REPLACE=0 + +usage() { + cat <<'EOF' +Usage: + ./start-authz.sh [--build] [--replace] +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --build) + FORCE_BUILD=1 + shift + ;; + --replace) + REPLACE=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + printf '[start-authz] unknown argument: %s\n' "$1" >&2 + exit 1 + ;; + esac +done + +mkdir -p "${DATA_ROOT}" + +if [[ "${FORCE_BUILD}" -eq 1 ]] || ! docker image inspect "${IMAGE_NAME}" >/dev/null 2>&1; then + docker build -t "${IMAGE_NAME}" "${SCRIPT_DIR}" +fi + +if docker container inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then + if [[ "${REPLACE}" -eq 1 ]]; then + docker rm -f "${CONTAINER_NAME}" >/dev/null + else + printf '[start-authz] container already exists: %s\n' "${CONTAINER_NAME}" >&2 + exit 1 + fi +fi + +docker run -d \ + --name "${CONTAINER_NAME}" \ + --restart unless-stopped \ + -p "${HOST_BIND_IP}:${HOST_PORT}:19090" \ + -v "${DATA_ROOT}:/var/lib/authz-service/data" \ + -e "AUTHZ_ISSUER=${AUTHZ_ISSUER}" \ + -e "AUTHZ_INTERNAL_TOKEN=${AUTHZ_INTERNAL_TOKEN}" \ + -e "AUTHZ_ACCESS_TOKEN_TTL_SECONDS=${AUTHZ_ACCESS_TOKEN_TTL_SECONDS}" \ + "${IMAGE_NAME}" >/dev/null + +printf 'container_name=%s\n' "${CONTAINER_NAME}" +printf 'host_port=%s\n' "${HOST_PORT}" +printf 'data_root=%s\n' "${DATA_ROOT}"