fix(mcp): подготовить доступ и отзыв разрешений для приёмки
This commit is contained in:
commit
ef7bb79d4e
311 files changed
+73119
No files matched your search
@@ -0,0 +1,133 @@
|
||||
# Auth Portal + Traefik in one Compose stack (production).
|
||||
# Docs: docs/deploy-traefik.md
|
||||
#
|
||||
# On server:
|
||||
# mkdir -p /opt/auth-portal/data
|
||||
# chown -R 1000:1000 /opt/auth-portal/data # container runs as non-root "node"
|
||||
# cp deploy/docker-compose.traefik.yml /opt/auth-portal/docker-compose.yml
|
||||
# cp deploy/env.traefik.example /opt/auth-portal/.env # fill secrets
|
||||
# docker login git.shx.one
|
||||
# cd /opt/auth-portal && docker compose pull && docker compose up -d
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:${TRAEFIK_IMAGE_TAG:-v3.7}
|
||||
container_name: auth-portal-traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "${TRAEFIK_HTTP_PORT:-80}:80"
|
||||
- "${TRAEFIK_HTTPS_PORT:-443}:443"
|
||||
environment:
|
||||
CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN:?set CF_DNS_API_TOKEN in .env}
|
||||
# Optional if DNS token lacks Zone:Read:
|
||||
# CF_ZONE_API_TOKEN: ${CF_ZONE_API_TOKEN:-}
|
||||
command:
|
||||
- --log.level=${TRAEFIK_LOG_LEVEL:-INFO}
|
||||
- --api.dashboard=false
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.docker.network=auth-portal
|
||||
- --ping=true
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL:?set LETSENCRYPT_EMAIL in .env}
|
||||
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
|
||||
- --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=15
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- traefik_letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- auth-portal
|
||||
healthcheck:
|
||||
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
app:
|
||||
image: git.shx.one/denozord/auth-portal:${AUTH_IMAGE_TAG:-latest}
|
||||
pull_policy: always
|
||||
container_name: auth-portal
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
stop_grace_period: 15s
|
||||
# Лимиты ресурсов: heap Node (NODE_OPTIONS в образе) рассчитан на 256 МиБ.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
cpus: "1.0"
|
||||
pids_limit: 128
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
depends_on:
|
||||
traefik:
|
||||
condition: service_healthy
|
||||
# No host ports — only Traefik publishes 80/443.
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
DATABASE_URL: sqlite:/data/app.db
|
||||
STATIC_DIR: /app/static
|
||||
NODE_ENV: production
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
|
||||
JWT_TTL_HOURS: ${JWT_TTL_HOURS:-1}
|
||||
REFRESH_TTL_DAYS: ${REFRESH_TTL_DAYS:-14}
|
||||
ISSUER: ${ISSUER:-https://auth.shnt.top}
|
||||
OIDC_ISSUER: ${OIDC_ISSUER:-}
|
||||
OIDC_RSA_PRIVATE_KEY: ${OIDC_RSA_PRIVATE_KEY:-}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@shnt.top}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?set ADMIN_PASSWORD in .env}
|
||||
ADMIN_NAME: ${ADMIN_NAME:-Admin}
|
||||
RETURN_TO_ALLOWLIST: ${RETURN_TO_ALLOWLIST:-.shnt.top}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- ./data:/data
|
||||
networks:
|
||||
- auth-portal
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=auth-portal
|
||||
- traefik.http.routers.auth-portal.rule=Host(`${AUTH_DOMAIN:-auth.shnt.top}`)
|
||||
- traefik.http.routers.auth-portal.entrypoints=websecure
|
||||
- traefik.http.routers.auth-portal.tls=true
|
||||
- traefik.http.routers.auth-portal.tls.certresolver=letsencrypt
|
||||
- traefik.http.services.auth-portal.loadbalancer.server.port=8080
|
||||
# Сжатие JSON/текста на стороне Traefik (ноль CPU/RAM в Node);
|
||||
# предсжатые .br/.gz статики Traefik не пересжимает (есть Content-Encoding).
|
||||
- traefik.http.middlewares.auth-portal-compress.compress=true
|
||||
- traefik.http.routers.auth-portal.middlewares=auth-portal-compress
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/ready"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
traefik_letsencrypt:
|
||||
name: auth_portal_traefik_letsencrypt
|
||||
|
||||
networks:
|
||||
auth-portal:
|
||||
name: auth-portal
|
||||
@@ -0,0 +1,54 @@
|
||||
# Docker-образы Auth Portal
|
||||
|
||||
## CI (Gitea Actions)
|
||||
|
||||
Сборка образов — **`docker buildx bake`** ([docker-bake.hcl](docker-bake.hcl)), не отдельные `docker build`.
|
||||
|
||||
**Publish:** push в `main` после quality gates — workflow [CD](../../.gitea/workflows/cd.yaml) job **publish**: semantic-release + зеркало base-образов + bake с `VERSION` из релиза.
|
||||
|
||||
Bake читает переменные из **окружения** (`REGISTRY`, `IMAGE_TAG`, `CACHE_REF_NODE`, `BASE_NODE`).
|
||||
|
||||
BuildKit кэширует pnpm store через `RUN --mount=type=cache`. На CI mounts живут, пока named builder `authportal` не удаляют (`cleanup: false`).
|
||||
|
||||
### Слои и runtime
|
||||
|
||||
| Образ | Runtime base | Заметка |
|
||||
|-------|----------------|---------|
|
||||
| `authportal`, `auth-portal` | `node:22-alpine` | один процесс: API + SPA + SQLite; `better-sqlite3` и `@node-rs/argon2` собираются в alpine на stage `deps`/`build` |
|
||||
|
||||
`python3` / `make` / `g++` только в `deps`/`build`. Context режется корневым [.dockerignore](../../.dockerignore).
|
||||
|
||||
Теги `authportal` и `auth-portal` — один и тот же образ (`auth-portal` — drop-in для текущего `docker pull` / Traefik compose).
|
||||
|
||||
### Кэш registry
|
||||
|
||||
- `git.shx.one/<owner>/authportal-buildcache:node-buildcache` — **запись** только из `node-build`
|
||||
- `git.shx.one/<owner>/authportal-buildcache:base-node-22-alpine` — зеркало FROM с Docker Hub. Только `linux/amd64`; skip если тег уже в Gitea. Неуспешный copy не валит CD — bake берёт Docker Hub FROM.
|
||||
|
||||
`pull = false` в bake: не перекачивать FROM, если слой уже в builder. Не запускайте `docker system prune -a` на runner.
|
||||
|
||||
Параллельный `cache-to` в один ref ломает manifest (`content descriptor … not found`).
|
||||
|
||||
Если CI падает на «not found» после смены схемы кэша — один раз удалите теги `authportal-buildcache:*` в registry и пересоберите.
|
||||
|
||||
## Локальная сборка
|
||||
|
||||
Из корня репозитория:
|
||||
|
||||
```bash
|
||||
cd deploy/docker
|
||||
export REGISTRY=git.shx.one/<owner>
|
||||
export IMAGE_TAG=latest
|
||||
export SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
export VERSION=dev
|
||||
export BUILD_TIME=
|
||||
docker buildx bake --allow=fs.read=../.. -f docker-bake.hcl default
|
||||
```
|
||||
|
||||
Проверка манифеста без сборки:
|
||||
|
||||
```bash
|
||||
docker buildx bake --allow=fs.read=../.. -f docker-bake.hcl --print default
|
||||
```
|
||||
|
||||
В `docker-bake.hcl`: `context = "../.."` резолвится **от cwd** (каталог `deploy/docker/`). `dockerfile` — путь от этого context (корень репо). Из корня репо не вызывать bake с `-f deploy/docker/docker-bake.hcl`: получится `lstat ../../deploy`. Можно задать `BUILDX_BAKE_FILE_RELATIVE_PATHS=1`.
|
||||
@@ -0,0 +1,78 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
# All-in-one Auth Portal (Fastify + SPA + SQLite). Analog cfdm / evofw.
|
||||
# Закреплён по digest (воспроизводимая сборка); обновляйте вместе с node:22-alpine
|
||||
# (digest: docker buildx imagetools inspect node:22-alpine).
|
||||
ARG BASE_NODE=docker.io/library/node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914
|
||||
|
||||
FROM ${BASE_NODE} AS deps
|
||||
WORKDIR /repo
|
||||
RUN apk add --no-cache python3 make g++ \
|
||||
&& corepack enable && corepack prepare pnpm@10.12.1 --activate
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./
|
||||
COPY apps/web/package.json apps/web/
|
||||
COPY apps/api/package.json apps/api/
|
||||
COPY packages/ui/package.json packages/ui/
|
||||
COPY packages/shared/package.json packages/shared/
|
||||
COPY packages/db/package.json packages/db/
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
FROM deps AS build
|
||||
RUN apk add --no-cache brotli
|
||||
COPY apps/web apps/web
|
||||
COPY apps/api apps/api
|
||||
COPY packages/ui packages/ui
|
||||
COPY packages/shared packages/shared
|
||||
COPY packages/db packages/db
|
||||
# Preview-маршруты дизайна (mock-данные) не нужны в проде: вырезаем до генерации routeTree
|
||||
# (-45% файлов и ~-10% gzip в бандле). Общие компоненты из src/design-preview остаются.
|
||||
RUN rm -f apps/web/src/routes/design-preview*.tsx
|
||||
# API собирается tsup в один файл (все зависимости внутри, кроме нативных).
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm turbo build --filter=web --filter=@authportal/api
|
||||
# Рантайм-слой: бандл + статика (только brotli) + нативные модули с теми же версиями, что в lockfile.
|
||||
RUN mkdir -p /out \
|
||||
&& cp -r apps/api/dist /out/dist \
|
||||
&& cp -r apps/web/dist /out/static \
|
||||
&& find /out/static -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.svg' -o -name '*.json' \) \
|
||||
-exec sh -c 'brotli -f -q 11 -o "$1.br" "$1"' _ {} \; \
|
||||
&& node -e "const v=(p)=>require(p).version;require('fs').writeFileSync('/out/package.json',JSON.stringify({name:'authportal-runtime',private:true,type:'module',dependencies:{'better-sqlite3':v('./packages/db/node_modules/better-sqlite3/package.json'),'@node-rs/argon2':v('./apps/api/node_modules/@node-rs/argon2/package.json')}}))"
|
||||
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
||||
cd /out && npm install --omit=dev --no-audit --no-fund \
|
||||
&& rm -rf node_modules/better-sqlite3/deps node_modules/better-sqlite3/src \
|
||||
node_modules/better-sqlite3/build/Release/obj node_modules/better-sqlite3/build/Release/.deps \
|
||||
node_modules/better-sqlite3/build/deps \
|
||||
&& node -e "const D=require('better-sqlite3');new D(':memory:').prepare('select 1').get();import('@node-rs/argon2').then(m=>m.hash('x')).then(()=>console.log('native modules ok'))"
|
||||
|
||||
FROM ${BASE_NODE} AS runtime
|
||||
WORKDIR /app
|
||||
# Не-root runtime; /data — bind mount с хоста, на сервере нужен
|
||||
# `chown -R 1000:1000 ./data` (см. deploy/docker-compose.traefik.yml).
|
||||
RUN mkdir -p /data && chown node:node /data
|
||||
ARG VERSION=dev
|
||||
ARG GIT_SHA=unknown
|
||||
ARG BUILD_TIME=
|
||||
LABEL org.opencontainers.image.title="auth-portal" \
|
||||
org.opencontainers.image.version="${VERSION}" \
|
||||
org.opencontainers.image.revision="${GIT_SHA}" \
|
||||
org.opencontainers.image.created="${BUILD_TIME}"
|
||||
# Куча V8 ограничена под контейнер 256 МиБ (RSS = куча + нативная память + argon2);
|
||||
# при другом mem_limit переопределите NODE_OPTIONS.
|
||||
ENV NODE_ENV=production \
|
||||
NODE_OPTIONS=--max-old-space-size=160 \
|
||||
UV_THREADPOOL_SIZE=4 \
|
||||
STATIC_DIR=/app/static \
|
||||
DATABASE_URL=sqlite:/data/app.db \
|
||||
SERVER_PORT=8080 \
|
||||
APP_VERSION=${VERSION} \
|
||||
GIT_SHA=${GIT_SHA} \
|
||||
BUILD_TIME=${BUILD_TIME}
|
||||
COPY --from=build --chown=node:node /out ./
|
||||
USER node
|
||||
EXPOSE 8080
|
||||
STOPSIGNAL SIGTERM
|
||||
VOLUME ["/data"]
|
||||
# busybox wget вместо запуска второго процесса node каждые 30 с.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD ["wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/ready"]
|
||||
CMD ["node", "dist/server.js"]
|
||||
@@ -0,0 +1,123 @@
|
||||
# Единая сборка образа Auth Portal (buildx bake: -f deploy/docker/docker-bake.hcl).
|
||||
# context = "../.." — корень репо. Bake резолвит context от cwd (не от HCL),
|
||||
# поэтому вызов из deploy/docker/ либо BUILDX_BAKE_FILE_RELATIVE_PATHS=1.
|
||||
|
||||
variable "REGISTRY" {
|
||||
default = "git.shx.one/denozord"
|
||||
}
|
||||
|
||||
variable "IMAGE_TAG" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "SHORT_SHA" {
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "SHA_FULL" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "VERSION" {
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "BUILD_TIME" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "CACHE_REF_NODE" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
# provenance + SBOM; включается только при --push (docker exporter attestations не поддерживает).
|
||||
variable "ATTEST" {
|
||||
default = "false"
|
||||
}
|
||||
|
||||
variable "BASE_NODE" {
|
||||
default = "docker.io/library/node:22-alpine@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914"
|
||||
}
|
||||
|
||||
function "node-cache-from" {
|
||||
params = []
|
||||
result = notequal("", CACHE_REF_NODE) ? ["type=registry,ref=${CACHE_REF_NODE}"] : []
|
||||
}
|
||||
|
||||
# Экспорт кэша — один writer на ref (параллельный cache-to в один ref ломает manifest).
|
||||
function "node-cache-to-export" {
|
||||
params = []
|
||||
result = notequal("", CACHE_REF_NODE) ? ["type=registry,ref=${CACHE_REF_NODE},mode=max"] : []
|
||||
}
|
||||
|
||||
function "image-tags" {
|
||||
params = [name]
|
||||
result = concat(
|
||||
notequal("", SHA_FULL) ? [
|
||||
"${REGISTRY}/${name}:${IMAGE_TAG}",
|
||||
"${REGISTRY}/${name}:${SHORT_SHA}",
|
||||
"${REGISTRY}/${name}:sha-${SHA_FULL}",
|
||||
] : [
|
||||
"${REGISTRY}/${name}:${IMAGE_TAG}",
|
||||
"${REGISTRY}/${name}:${SHORT_SHA}",
|
||||
],
|
||||
notequal(VERSION, "dev") && notequal(VERSION, "") ? [
|
||||
"${REGISTRY}/${name}:v${VERSION}",
|
||||
"${REGISTRY}/${name}:${VERSION}",
|
||||
] : []
|
||||
)
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["authportal"]
|
||||
}
|
||||
|
||||
target "_common" {
|
||||
platforms = ["linux/amd64"]
|
||||
pull = false
|
||||
}
|
||||
|
||||
target "node-deps" {
|
||||
inherits = ["_common"]
|
||||
context = "../.."
|
||||
dockerfile = "deploy/docker/authportal/Dockerfile"
|
||||
target = "deps"
|
||||
args = {
|
||||
BASE_NODE = BASE_NODE
|
||||
}
|
||||
cache-from = node-cache-from()
|
||||
}
|
||||
|
||||
target "node-build" {
|
||||
inherits = ["_common"]
|
||||
context = "../.."
|
||||
dockerfile = "deploy/docker/authportal/Dockerfile"
|
||||
target = "build"
|
||||
args = {
|
||||
BASE_NODE = BASE_NODE
|
||||
}
|
||||
contexts = {
|
||||
deps = "target:node-deps"
|
||||
}
|
||||
cache-from = node-cache-from()
|
||||
cache-to = node-cache-to-export()
|
||||
}
|
||||
|
||||
target "authportal" {
|
||||
inherits = ["_common"]
|
||||
context = "../.."
|
||||
dockerfile = "deploy/docker/authportal/Dockerfile"
|
||||
target = "runtime"
|
||||
args = {
|
||||
BASE_NODE = BASE_NODE
|
||||
VERSION = VERSION
|
||||
GIT_SHA = notequal("", SHA_FULL) ? SHA_FULL : SHORT_SHA
|
||||
BUILD_TIME = BUILD_TIME
|
||||
}
|
||||
contexts = {
|
||||
build = "target:node-build"
|
||||
}
|
||||
cache-from = node-cache-from()
|
||||
attest = equal(ATTEST, "true") ? ["type=provenance,mode=min", "type=sbom"] : []
|
||||
tags = concat(image-tags("authportal"), image-tags("auth-portal"))
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env sh
|
||||
# Copy-by-digest зеркало базовых образов в Gitea Container Registry (без rebuild).
|
||||
# Требует: docker login в REGISTRY host, docker buildx.
|
||||
# REGISTRY=git.shx.one/<owner> (без суффикса /authportal-buildcache)
|
||||
#
|
||||
# Источник library-образов — Docker Hub (docker.io).
|
||||
# Если тег уже есть в Gitea — skip (без повторного pull).
|
||||
# Копируем только linux/amd64 — bake platforms совпадает.
|
||||
# Шаг CD не должен падать: неуспешный copy оставляет bake на Docker Hub FROM.
|
||||
set -eu
|
||||
REGISTRY="${REGISTRY:?REGISTRY required (git.shx.one/<owner>)}"
|
||||
CACHE_REPO="${REGISTRY}/authportal-buildcache"
|
||||
ENV_FILE="${MIRROR_ENV_FILE:-}"
|
||||
PLATFORM="${MIRROR_PLATFORM:-linux/amd64}"
|
||||
RETRIES="${MIRROR_RETRIES:-4}"
|
||||
|
||||
tmp="$(mktemp)"
|
||||
cleanup() { rm -f "$tmp"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
dest_exists() {
|
||||
docker buildx imagetools inspect "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
copy_retry() {
|
||||
src="$1"
|
||||
dest="$2"
|
||||
n=0
|
||||
while [ "$n" -lt "$RETRIES" ]; do
|
||||
n=$((n + 1))
|
||||
if docker buildx imagetools create --platform "$PLATFORM" --tag "$dest" "$src"; then
|
||||
return 0
|
||||
fi
|
||||
delay=$((n * 25))
|
||||
echo "mirror retry ${n}/${RETRIES}, sleep ${delay}s: ${dest}"
|
||||
sleep "$delay"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
mirror() {
|
||||
src="$1"
|
||||
tag="$2"
|
||||
var="$3"
|
||||
dest="${CACHE_REPO}:${tag}"
|
||||
if dest_exists "$dest"; then
|
||||
echo "skip (already in registry): ${dest}"
|
||||
printf '%s=%s\n' "$var" "$dest" >>"$tmp"
|
||||
return 0
|
||||
fi
|
||||
echo "mirror ${src} -> ${dest} (${PLATFORM})"
|
||||
if copy_retry "$src" "$dest"; then
|
||||
printf '%s=%s\n' "$var" "$dest" >>"$tmp"
|
||||
return 0
|
||||
fi
|
||||
echo "warn: ${dest} not mirrored — bake uses Docker Hub FROM for ${var}"
|
||||
return 0
|
||||
}
|
||||
|
||||
mirror "docker.io/library/node:22-alpine" "base-node-22-alpine" BASE_NODE
|
||||
|
||||
echo "----- mirrored BASE_* -----"
|
||||
cat "$tmp"
|
||||
if [ -n "$ENV_FILE" ]; then
|
||||
env_dir="$(dirname "$ENV_FILE")"
|
||||
if [ -d "$env_dir" ]; then
|
||||
cp "$tmp" "$ENV_FILE"
|
||||
echo "wrote ${ENV_FILE}"
|
||||
else
|
||||
echo "warn: MIRROR_ENV_FILE dir missing (${env_dir}) — skip write"
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,58 @@
|
||||
# Production .env for deploy/docker-compose.traefik.yml (auth + Traefik).
|
||||
# Copy to /opt/auth-portal/.env and fill secrets. Do not commit.
|
||||
|
||||
# --- Traefik / Let's Encrypt (Cloudflare DNS-01) ---
|
||||
CF_DNS_API_TOKEN=
|
||||
LETSENCRYPT_EMAIL=admin@shnt.top
|
||||
AUTH_DOMAIN=auth.shnt.top
|
||||
# TRAEFIK_IMAGE_TAG=v3.7
|
||||
# TRAEFIK_HTTP_PORT=80
|
||||
# TRAEFIK_HTTPS_PORT=443
|
||||
# TRAEFIK_LOG_LEVEL=INFO
|
||||
|
||||
# --- Auth Portal ---
|
||||
AUTH_IMAGE_TAG=latest
|
||||
JWT_SECRET=
|
||||
JWT_TTL_HOURS=1
|
||||
REFRESH_TTL_DAYS=14
|
||||
ISSUER=https://auth.shnt.top
|
||||
# OIDC IdP for Technitium DNS and other external RPs (defaults to ISSUER if empty)
|
||||
# OIDC_ISSUER=https://auth.shnt.top
|
||||
# Optional PKCS8 RSA PEM; otherwise key is auto-generated into SQLite (/data)
|
||||
# OIDC_RSA_PRIVATE_KEY=
|
||||
ADMIN_EMAIL=admin@shnt.top
|
||||
ADMIN_PASSWORD=
|
||||
ADMIN_NAME=Admin
|
||||
# Include Technitium origin if used (e.g. https://dns.shnt.top)
|
||||
RETURN_TO_ALLOWLIST=.shnt.top,https://vps.shnt.top,https://cfdm.shnt.top,https://bgp.shnt.top,https://fw.shnt.top,https://dns.shnt.top,https://cdn.shnt.top
|
||||
LOG_LEVEL=info
|
||||
NODE_ENV=production
|
||||
# WebAuthn / passkeys (defaults from ISSUER hostname + origin)
|
||||
# WEBAUTHN_RP_ID=auth.shnt.top
|
||||
# WEBAUTHN_ORIGINS=https://auth.shnt.top
|
||||
# WEBAUTHN_RP_NAME=Auth Portal
|
||||
|
||||
# --- Ingest аудита от подключённых приложений (обязателен для /api/v1/ingest/audit) ---
|
||||
# AUDIT_INGEST_SECRET=
|
||||
|
||||
# --- Производительность / эксплуатация (все необязательны) ---
|
||||
# Максимум на graceful shutdown, мс
|
||||
# SHUTDOWN_TIMEOUT_MS=8000
|
||||
# Одновременных argon2-хешей (каждый ~19 МиБ RAM)
|
||||
# ARGON2_CONCURRENCY=2
|
||||
# Сколько дней хранить истёкшие/отозванные сессии и коды
|
||||
# SESSION_RETENTION_DAYS=7
|
||||
# Доверенные прокси для X-Forwarded-For (IP/CIDR через запятую, true/false)
|
||||
# TRUST_PROXY=127.0.0.1/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7
|
||||
# Сжатие ответов в Node (нужно только без Traefik compress)
|
||||
# ENABLE_COMPRESS=false
|
||||
# Глобальный лимит запросов на IP в минуту
|
||||
# RATE_LIMIT_MAX=200
|
||||
# Доп. origins для CORS (по умолчанию: issuer, RETURN_TO_ALLOWLIST, приложения App Switcher)
|
||||
# CORS_ORIGINS=
|
||||
# Бэкап SQLite: период в часах (0 = выключен), число копий, каталог
|
||||
# BACKUP_INTERVAL_HOURS=24
|
||||
# BACKUP_KEEP=7
|
||||
# BACKUP_DIR=/data/backups
|
||||
# Heap Node (образ рассчитан на mem_limit 256m); при другом лимите:
|
||||
# NODE_OPTIONS=--max-old-space-size=160
|
||||
Reference in new issue
Block a user