forked from baron/baron-sso
fix(deploy): align staging frontend runtime with production images
This commit is contained in:
@@ -18,6 +18,30 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y jq curl
|
||||
|
||||
- name: Validate RC build configuration
|
||||
env:
|
||||
HARBOR_ENDPOINT: ${{ vars.HARBOR_ENDPOINT }}
|
||||
HARBOR_HOSTNAME: ${{ vars.HARBOR_HOSTNAME }}
|
||||
HARBOR_ROBOT_ACCOUNT: ${{ vars.HARBOR_ROBOT_ACCOUNT }}
|
||||
HARBOR_ROBOT_KEY: ${{ secrets.HARBOR_ROBOT_KEY }}
|
||||
ADMINFRONT_URL: ${{ vars.ADMINFRONT_URL }}
|
||||
DEVFRONT_URL: ${{ vars.DEVFRONT_URL }}
|
||||
ORGFRONT_URL: ${{ vars.ORGFRONT_URL }}
|
||||
VITE_OIDC_AUTHORITY: ${{ vars.VITE_OIDC_AUTHORITY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
required_action_env="
|
||||
HARBOR_ENDPOINT HARBOR_HOSTNAME HARBOR_ROBOT_ACCOUNT HARBOR_ROBOT_KEY
|
||||
ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL VITE_OIDC_AUTHORITY
|
||||
"
|
||||
for key in ${required_action_env}; do
|
||||
if [ -z "${!key:-}" ]; then
|
||||
echo "::error::Missing required RC build value: ${key}. Check Gitea repo variables/secrets."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -93,6 +117,11 @@ jobs:
|
||||
file: ./adminfront/Dockerfile
|
||||
push: true
|
||||
tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/adminfront:${{ steps.rc_calculator.outputs.new_rc_tag }}
|
||||
build-args: |
|
||||
VITE_ADMIN_PUBLIC_URL=${{ vars.ADMINFRONT_URL }}
|
||||
VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}
|
||||
VITE_OIDC_CLIENT_ID=adminfront
|
||||
ORGFRONT_URL=${{ vars.ORGFRONT_URL }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
@@ -103,6 +132,10 @@ jobs:
|
||||
file: ./devfront/Dockerfile
|
||||
push: true
|
||||
tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/devfront:${{ steps.rc_calculator.outputs.new_rc_tag }}
|
||||
build-args: |
|
||||
VITE_DEVFRONT_PUBLIC_URL=${{ vars.DEVFRONT_URL }}
|
||||
VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}
|
||||
VITE_OIDC_CLIENT_ID=devfront
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
@@ -113,6 +146,10 @@ jobs:
|
||||
file: ./orgfront/Dockerfile
|
||||
push: true
|
||||
tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront:${{ steps.rc_calculator.outputs.new_rc_tag }}
|
||||
build-args: |
|
||||
VITE_ORGFRONT_PUBLIC_URL=${{ vars.ORGFRONT_URL }}
|
||||
VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}
|
||||
VITE_OIDC_CLIENT_ID=orgfront
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
|
||||
@@ -42,19 +42,13 @@ jobs:
|
||||
sudo apt-get update -y && sudo apt-get install -y skopeo
|
||||
fi
|
||||
|
||||
# Re-tag backend image
|
||||
echo "Re-tagging backend image..."
|
||||
skopeo copy --preserve-digests \
|
||||
--src-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" --dest-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
||||
--src-tls-verify=false --dest-tls-verify=false \
|
||||
"docker://${HARBOR_HOSTNAME}/baron_sso/backend:${BASE_TAG}" "docker://${HARBOR_HOSTNAME}/baron_sso/backend:${RE_TAG}"
|
||||
|
||||
# Re-tag userfront image
|
||||
echo "Re-tagging userfront image..."
|
||||
skopeo copy --preserve-digests \
|
||||
--src-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" --dest-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
||||
--src-tls-verify=false --dest-tls-verify=false \
|
||||
"docker://${HARBOR_HOSTNAME}/baron_sso/userfront:${BASE_TAG}" "docker://${HARBOR_HOSTNAME}/baron_sso/userfront:${RE_TAG}"
|
||||
for image in backend userfront adminfront devfront orgfront; do
|
||||
echo "Re-tagging ${image} image..."
|
||||
skopeo copy --preserve-digests \
|
||||
--src-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" --dest-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
||||
--src-tls-verify=false --dest-tls-verify=false \
|
||||
"docker://${HARBOR_HOSTNAME}/baron_sso/${image}:${BASE_TAG}" "docker://${HARBOR_HOSTNAME}/baron_sso/${image}:${RE_TAG}"
|
||||
done
|
||||
|
||||
echo "final_image_tag=${RE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -68,6 +62,9 @@ jobs:
|
||||
IMAGE_TAG: ${{ steps.retag.outputs.final_image_tag }}
|
||||
BACKEND_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/backend
|
||||
USERFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/userfront
|
||||
ADMINFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/adminfront
|
||||
DEVFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/devfront
|
||||
ORGFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront
|
||||
DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
||||
PROD_HOST: ${{ vars.PROD_HOST }}
|
||||
PROD_USER: ${{ vars.PROD_USER }}
|
||||
@@ -101,8 +98,12 @@ jobs:
|
||||
"CLICKHOUSE_PORT_NATIVE=${{ vars.PROD_CLICKHOUSE_PORT_NATIVE }}" \
|
||||
"CLICKHOUSE_USER=${{ vars.PROD_CLICKHOUSE_USER }}" \
|
||||
"CLICKHOUSE_PASSWORD=${{ secrets.PROD_CLICKHOUSE_PASSWORD }}" \
|
||||
"BACKEND_PORT=${{ vars.PROD_BACKEND_PORT }}" \
|
||||
"USERFRONT_PORT=${{ vars.PROD_USERFRONT_PORT }}" \
|
||||
"PROD_BACKEND_PORT=${{ vars.PROD_BACKEND_PORT }}" \
|
||||
"BACKEND_PORT=3000" \
|
||||
"USERFRONT_PORT=${{ vars.PROD_FRONTEND_PORT }}" \
|
||||
"ADMINFRONT_PORT=${{ vars.ADMINFRONT_PORT }}" \
|
||||
"DEVFRONT_PORT=${{ vars.DEVFRONT_PORT }}" \
|
||||
"ORGFRONT_PORT=${{ vars.ORGFRONT_PORT }}" \
|
||||
"DB_USER=${{ vars.PROD_DB_USER }}" \
|
||||
"DB_PASSWORD=${{ secrets.PROD_DB_PASSWORD }}" \
|
||||
"DB_NAME=${{ vars.PROD_DB_NAME }}" \
|
||||
@@ -117,10 +118,33 @@ jobs:
|
||||
"AWS_ACCESS_KEY_ID=${{ vars.AWS_ACCESS_KEY_ID }}" \
|
||||
"AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" \
|
||||
"AWS_SES_SENDER=${{ vars.AWS_SES_SENDER }}" \
|
||||
"USERFRONT_URL=${{ vars.PROD_USERFRONT_URL }}" \
|
||||
"USERFRONT_URL=${{ vars.PROD_FRONTEND_URL }}" \
|
||||
"ADMINFRONT_URL=${{ vars.ADMINFRONT_URL }}" \
|
||||
"DEVFRONT_URL=${{ vars.DEVFRONT_URL }}" \
|
||||
"ORGFRONT_URL=${{ vars.ORGFRONT_URL }}" \
|
||||
"BACKEND_URL=${{ vars.PROD_BACKEND_URL }}" \
|
||||
"VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}" \
|
||||
"ADMINFRONT_CALLBACK_URLS=${{ vars.ADMINFRONT_CALLBACK_URLS }}" \
|
||||
"DEVFRONT_CALLBACK_URLS=${{ vars.DEVFRONT_CALLBACK_URLS }}" \
|
||||
"ORGFRONT_CALLBACK_URLS=${{ vars.ORGFRONT_CALLBACK_URLS }}" \
|
||||
> .env
|
||||
|
||||
required_dotenv_keys="
|
||||
APP_ENV TZ DB_PORT CLICKHOUSE_PORT_HTTP CLICKHOUSE_PORT_NATIVE CLICKHOUSE_USER CLICKHOUSE_PASSWORD
|
||||
PROD_BACKEND_PORT BACKEND_PORT USERFRONT_PORT ADMINFRONT_PORT DEVFRONT_PORT ORGFRONT_PORT
|
||||
DB_USER DB_PASSWORD DB_NAME COOKIE_SECRET JWT_SECRET REDIS_ADDR
|
||||
NAVER_CLOUD_ACCESS_KEY NAVER_CLOUD_SECRET_KEY NAVER_CLOUD_SERVICE_ID NAVER_SENDER_PHONE_NUMBER
|
||||
AWS_REGION AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SES_SENDER
|
||||
USERFRONT_URL ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL BACKEND_URL VITE_OIDC_AUTHORITY
|
||||
ADMINFRONT_CALLBACK_URLS DEVFRONT_CALLBACK_URLS ORGFRONT_CALLBACK_URLS
|
||||
"
|
||||
for key in ${required_dotenv_keys}; do
|
||||
if ! grep -Eq "^${key}=.+" .env; then
|
||||
echo "::error::Missing required production .env value: ${key}. Check Gitea repo variables/secrets."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy compose template and .env file to the remote server
|
||||
scp adminfront/seed-tenant.csv "${PROD_USER}@${PROD_HOST}:${DEPLOY_PATH}/adminfront/"
|
||||
scp docker/docker-compose.template.yaml .env "${PROD_USER}@${PROD_HOST}:${DEPLOY_PATH}/"
|
||||
@@ -131,6 +155,9 @@ jobs:
|
||||
"export DEPLOY_PATH='${DEPLOY_PATH}'; \
|
||||
export BACKEND_IMAGE_NAME='${BACKEND_IMAGE_NAME}'; \
|
||||
export USERFRONT_IMAGE_NAME='${USERFRONT_IMAGE_NAME}'; \
|
||||
export ADMINFRONT_IMAGE_NAME='${ADMINFRONT_IMAGE_NAME}'; \
|
||||
export DEVFRONT_IMAGE_NAME='${DEVFRONT_IMAGE_NAME}'; \
|
||||
export ORGFRONT_IMAGE_NAME='${ORGFRONT_IMAGE_NAME}'; \
|
||||
export IMAGE_TAG='${IMAGE_TAG}'; \
|
||||
export HARBOR_ENDPOINT='${HARBOR_ENDPOINT}'; \
|
||||
export HARBOR_ROBOT_ACCOUNT='${HARBOR_ROBOT_ACCOUNT}'; \
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
CLICKHOUSE_PORT_NATIVE=${{ vars.CLICKHOUSE_PORT_NATIVE }}
|
||||
CLICKHOUSE_HOST=${{ vars.CLICKHOUSE_HOST }}
|
||||
CLICKHOUSE_USER=${{ vars.CLICKHOUSE_USER }}
|
||||
CLICKHOUSE_PASSWORD=${{ vars.CLICKHOUSE_PASSWORD }}
|
||||
CLICKHOUSE_PASSWORD=${{ secrets.CLICKHOUSE_PASSWORD }}
|
||||
|
||||
|
||||
BACKEND_PORT=${{ vars.BACKEND_PORT }}
|
||||
@@ -142,9 +142,32 @@ jobs:
|
||||
# OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }}
|
||||
EOF
|
||||
|
||||
required_dotenv_keys="
|
||||
APP_ENV BACKEND_LOG_LEVEL CLIENT_LOG_DEBUG WORKS_ADMIN_API_BASE_URL WORKS_ADMIN_OAUTH_TOKEN_URL TZ IDP_PROVIDER
|
||||
DB_PORT CLICKHOUSE_PORT_HTTP CLICKHOUSE_PORT_NATIVE CLICKHOUSE_HOST CLICKHOUSE_USER CLICKHOUSE_PASSWORD
|
||||
BACKEND_PORT ADMINFRONT_PORT DEVFRONT_PORT ORGFRONT_PORT USERFRONT_PORT OATHKEEPER_API_URL
|
||||
DB_USER DB_PASSWORD DB_NAME COOKIE_SECRET JWT_SECRET REDIS_ADDR CORS_ALLOWED_ORIGINS PROFILE_CACHE_TTL
|
||||
NAVER_CLOUD_ACCESS_KEY NAVER_CLOUD_SECRET_KEY NAVER_CLOUD_SERVICE_ID NAVER_SENDER_PHONE_NUMBER
|
||||
AWS_REGION AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SES_SENDER ADMIN_EMAIL ADMIN_PASSWORD
|
||||
USERFRONT_URL ORGFRONT_URL BACKEND_PUBLIC_URL BACKEND_URL OATHKEEPER_PUBLIC_URL
|
||||
ORY_POSTGRES_TAG ORY_POSTGRES_USER ORY_POSTGRES_PASSWORD ORY_POSTGRES_DB KRATOS_DB HYDRA_DB KETO_DB
|
||||
KRATOS_VERSION KRATOS_UI_NODE_VERSION HYDRA_VERSION KETO_VERSION ORY_SDK_URL KRATOS_PUBLIC_URL
|
||||
KRATOS_ADMIN_URL KRATOS_BROWSER_URL KRATOS_UI_URL HYDRA_ADMIN_URL HYDRA_PUBLIC_URL JWKS_URL
|
||||
OATHKEEPER_VERSION OATHKEEPER_UID OATHKEEPER_GID OATHKEEPER_HEALTH_URL OATHKEEPER_HEALTH_INTERVAL_SECONDS
|
||||
OATHKEEPER_HEALTH_TIMEOUT_SECONDS OATHKEEPER_HEALTH_ENABLED CSRF_COOKIE_NAME CSRF_COOKIE_SECRET
|
||||
VITE_OIDC_AUTHORITY ADMINFRONT_CALLBACK_URLS DEVFRONT_CALLBACK_URLS ORGFRONT_CALLBACK_URLS
|
||||
"
|
||||
for key in ${required_dotenv_keys}; do
|
||||
if ! grep -Eq "^${key}=.+" .env; then
|
||||
echo "::error::Missing required staging .env value: ${key}. Check Gitea repo variables/secrets."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 파일 복사
|
||||
ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p ${DEPLOY_PATH}/docker"
|
||||
ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p ${DEPLOY_PATH}/adminfront"
|
||||
ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p ${DEPLOY_PATH}/scripts"
|
||||
|
||||
# [중요] docker/ory 폴더 복사 (여기에 init-db/1-createdb.sql이 있어야 함)
|
||||
scp -r docker/ory "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/docker/"
|
||||
@@ -158,9 +181,10 @@ jobs:
|
||||
fi
|
||||
|
||||
scp adminfront/seed-tenant.csv "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/adminfront/"
|
||||
scp scripts/render_ory_config.sh "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/scripts/"
|
||||
scp docker/docker-compose.staging.template.yaml .env "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/"
|
||||
scp docker/compose.infra.yaml "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/compose.infra.yml"
|
||||
scp docker/compose.ory.yaml "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/compose.ory.yml"
|
||||
scp compose.ory.yaml "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/compose.ory.yml"
|
||||
|
||||
# 배포 실행
|
||||
echo "${HARBOR_ROBOT_KEY}" | ssh "${STAGE_USER}@${STAGE_HOST}" \
|
||||
@@ -181,6 +205,9 @@ jobs:
|
||||
for net in baron_net public_net ory-net hydranet kratosnet; do
|
||||
docker network inspect \"\$net\" >/dev/null 2>&1 || docker network create \"\$net\"
|
||||
done
|
||||
|
||||
bash scripts/render_ory_config.sh; \
|
||||
chmod -R 777 config/.generated/ory || true; \
|
||||
|
||||
envsubst < docker-compose.staging.template.yaml > docker-compose.yml; \
|
||||
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
FROM node:lts
|
||||
FROM node:lts AS build
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Set CI environment variable to true to avoid TTY issues with pnpm
|
||||
ENV CI=true
|
||||
ENV ADMINFRONT_BUILD_OUT_DIR=/workspace/adminfront/dist
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@10.5.2 --activate
|
||||
|
||||
# Copy workspace configs and common package
|
||||
COPY pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY common ./common
|
||||
COPY adminfront ./adminfront
|
||||
|
||||
# Install dependencies for the workspace
|
||||
RUN pnpm install --filter adminfront... --filter baron-sso... --no-frozen-lockfile --ignore-scripts
|
||||
ARG VITE_ADMIN_PUBLIC_URL
|
||||
ARG VITE_OIDC_AUTHORITY
|
||||
ARG VITE_OIDC_CLIENT_ID
|
||||
ARG ORGFRONT_URL
|
||||
ENV VITE_ADMIN_PUBLIC_URL=$VITE_ADMIN_PUBLIC_URL
|
||||
ENV VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY
|
||||
ENV VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID
|
||||
ENV ORGFRONT_URL=$ORGFRONT_URL
|
||||
|
||||
# 프로덕션 서빙을 위한 serve 패키지 글로벌 설치
|
||||
RUN npm install -g serve
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
WORKDIR /workspace/adminfront
|
||||
RUN npm run build
|
||||
|
||||
FROM node:24-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV FRONTEND_DIST_DIR=/app/dist
|
||||
ENV PORT=5173
|
||||
|
||||
COPY scripts/serve_frontend_prod.mjs ./serve_frontend_prod.mjs
|
||||
COPY --from=build /workspace/adminfront/dist ./dist
|
||||
|
||||
# Vite 기본 포트
|
||||
EXPOSE 5173
|
||||
|
||||
# 실행 스크립트: APP_ENV에 따라 개발 서버 또는 빌드 후 서빙
|
||||
RUN chmod +x ./scripts/runtime-mode.sh
|
||||
CMD ["sh", "./scripts/runtime-mode.sh"]
|
||||
CMD ["node", "./serve_frontend_prod.mjs"]
|
||||
|
||||
@@ -314,48 +314,56 @@ services:
|
||||
networks: [app_net]
|
||||
|
||||
adminfront:
|
||||
image: node:20-alpine
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: ./adminfront/Dockerfile
|
||||
args:
|
||||
VITE_ADMIN_PUBLIC_URL: ${ADMINFRONT_URL}
|
||||
VITE_OIDC_AUTHORITY: ${VITE_OIDC_AUTHORITY}
|
||||
VITE_OIDC_CLIENT_ID: adminfront
|
||||
ORGFRONT_URL: ${ORGFRONT_URL}
|
||||
container_name: ${COMPOSE_PROJECT_NAME}_adminfront
|
||||
working_dir: /app
|
||||
env_file: .env
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- API_PROXY_TARGET=http://backend:${BACKEND_PORT}
|
||||
ports:
|
||||
- "${ADMINFRONT_PORT}:5173"
|
||||
volumes:
|
||||
- ../../common:/common
|
||||
- ../../adminfront:/app
|
||||
- ./adminfront/vite.config.ts:/app/vite.config.ts:ro
|
||||
- ./adminfront/auth.ts:/app/src/lib/auth.ts:ro
|
||||
command: sh ./scripts/runtime-mode.sh
|
||||
networks: [app_net]
|
||||
|
||||
devfront:
|
||||
image: node:20-alpine
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: ./devfront/Dockerfile
|
||||
args:
|
||||
VITE_DEVFRONT_PUBLIC_URL: ${DEVFRONT_URL}
|
||||
VITE_OIDC_AUTHORITY: ${VITE_OIDC_AUTHORITY}
|
||||
VITE_OIDC_CLIENT_ID: devfront
|
||||
container_name: ${COMPOSE_PROJECT_NAME}_devfront
|
||||
working_dir: /app
|
||||
env_file: .env
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- API_PROXY_TARGET=http://backend:${BACKEND_PORT}
|
||||
ports:
|
||||
- "${DEVFRONT_PORT}:5173"
|
||||
volumes:
|
||||
- ../../common:/common
|
||||
- ../../devfront:/app
|
||||
- ./devfront/vite.config.ts:/app/vite.config.ts:ro
|
||||
- ./devfront/auth.ts:/app/src/lib/auth.ts:ro
|
||||
command: sh ./scripts/runtime-mode.sh
|
||||
networks: [app_net]
|
||||
|
||||
orgfront:
|
||||
image: node:20-alpine
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: ./orgfront/Dockerfile
|
||||
args:
|
||||
VITE_ORGFRONT_PUBLIC_URL: ${ORGFRONT_URL}
|
||||
VITE_OIDC_AUTHORITY: ${VITE_OIDC_AUTHORITY}
|
||||
VITE_OIDC_CLIENT_ID: orgfront
|
||||
container_name: ${COMPOSE_PROJECT_NAME}_orgfront
|
||||
working_dir: /app
|
||||
env_file: .env
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- API_PROXY_TARGET=http://backend:${BACKEND_PORT}
|
||||
- USERFRONT_URL=${USERFRONT_URL}
|
||||
ports:
|
||||
- "${ORGFRONT_PORT}:5175"
|
||||
volumes:
|
||||
- ../../common:/common
|
||||
- ../../orgfront:/app
|
||||
- ./orgfront/vite.config.ts:/app/vite.config.ts:ro
|
||||
- ./orgfront/auth.ts:/app/src/lib/auth.ts:ro
|
||||
command: sh ./scripts/runtime-mode.sh
|
||||
networks: [app_net]
|
||||
|
||||
networks:
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
FROM node:lts
|
||||
FROM node:lts AS build
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Set CI environment variable to true to avoid TTY issues with pnpm
|
||||
ENV CI=true
|
||||
ENV DEVFRONT_BUILD_OUT_DIR=/workspace/devfront/dist
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@10.5.2 --activate
|
||||
|
||||
# Copy workspace configs and common package
|
||||
COPY pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY common ./common
|
||||
COPY devfront ./devfront
|
||||
|
||||
# Install dependencies for the workspace
|
||||
RUN pnpm install --filter devfront... --filter baron-sso... --no-frozen-lockfile --ignore-scripts
|
||||
ARG VITE_DEVFRONT_PUBLIC_URL
|
||||
ARG VITE_OIDC_AUTHORITY
|
||||
ARG VITE_OIDC_CLIENT_ID
|
||||
ENV VITE_DEVFRONT_PUBLIC_URL=$VITE_DEVFRONT_PUBLIC_URL
|
||||
ENV VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY
|
||||
ENV VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID
|
||||
|
||||
# 프로덕션 서빙을 위한 serve 패키지 글로벌 설치
|
||||
RUN npm install -g serve
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
WORKDIR /workspace/devfront
|
||||
RUN npm run build
|
||||
|
||||
FROM node:24-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV FRONTEND_DIST_DIR=/app/dist
|
||||
ENV PORT=5173
|
||||
|
||||
COPY scripts/serve_frontend_prod.mjs ./serve_frontend_prod.mjs
|
||||
COPY --from=build /workspace/devfront/dist ./dist
|
||||
|
||||
# Vite 기본 포트
|
||||
EXPOSE 5173
|
||||
|
||||
# 실행 스크립트: APP_ENV에 따라 개발 서버 또는 빌드 후 서빙
|
||||
RUN chmod +x ./scripts/runtime-mode.sh
|
||||
CMD ["sh", "./scripts/runtime-mode.sh"]
|
||||
CMD ["node", "./serve_frontend_prod.mjs"]
|
||||
|
||||
@@ -17,15 +17,16 @@ services:
|
||||
- CLICKHOUSE_USER=${CLICKHOUSE_USER:-baron}
|
||||
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-password}
|
||||
- USERFRONT_URL=${USERFRONT_URL:-https://sso.hmac.kr}
|
||||
- BACKEND_PORT=3000
|
||||
- SEED_TENANT_CSV_PATH=/app/seed-tenant.csv
|
||||
ports:
|
||||
- "${BACKEND_PORT:-3010}:3010"
|
||||
- "${PROD_BACKEND_PORT:-3010}:3000"
|
||||
volumes:
|
||||
- ./adminfront/seed-tenant.csv:/app/seed-tenant.csv:ro
|
||||
depends_on:
|
||||
- infra_check
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3010/health"]
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -37,11 +38,71 @@ services:
|
||||
image: ${USERFRONT_IMAGE_NAME}:${IMAGE_TAG}
|
||||
container_name: baron_userfront
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- USERFRONT_URL=${USERFRONT_URL:-https://sso.hmac.kr}
|
||||
- BACKEND_URL=${USERFRONT_URL:-https://sso.hmac.kr}
|
||||
- BACKEND_URL=${BACKEND_URL:-https://sso.hmac.kr}
|
||||
ports:
|
||||
- "${USERFRONT_PORT:-80}:80"
|
||||
- "${USERFRONT_PORT:-80}:5000"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- baron_net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
adminfront:
|
||||
image: ${ADMINFRONT_IMAGE_NAME}:${IMAGE_TAG}
|
||||
container_name: baron_adminfront
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
- API_PROXY_TARGET=http://baron_backend:${BACKEND_PORT:-3000}
|
||||
ports:
|
||||
- "${ADMINFRONT_PORT:-5173}:5173"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- baron_net
|
||||
|
||||
devfront:
|
||||
image: ${DEVFRONT_IMAGE_NAME}:${IMAGE_TAG}
|
||||
container_name: baron_devfront
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
- API_PROXY_TARGET=http://baron_backend:${BACKEND_PORT:-3000}
|
||||
ports:
|
||||
- "${DEVFRONT_PORT:-5174}:5173"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- baron_net
|
||||
|
||||
orgfront:
|
||||
image: ${ORGFRONT_IMAGE_NAME}:${IMAGE_TAG}
|
||||
container_name: baron_orgfront
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
- API_PROXY_TARGET=http://baron_backend:${BACKEND_PORT:-3000}
|
||||
- USERFRONT_URL=${USERFRONT_URL}
|
||||
ports:
|
||||
- "${ORGFRONT_PORT:-5175}:5175"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -427,6 +427,11 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./adminfront/Dockerfile
|
||||
args:
|
||||
VITE_ADMIN_PUBLIC_URL: ${ADMINFRONT_URL:-}
|
||||
VITE_OIDC_AUTHORITY: ${VITE_OIDC_AUTHORITY:-}
|
||||
VITE_OIDC_CLIENT_ID: adminfront
|
||||
ORGFRONT_URL: ${ORGFRONT_URL:-}
|
||||
container_name: baron_adminfront
|
||||
env_file:
|
||||
- .env
|
||||
@@ -435,11 +440,6 @@ services:
|
||||
- API_PROXY_TARGET=http://baron_backend:3000
|
||||
ports:
|
||||
- "${ADMINFRONT_PORT:-5173}:5173"
|
||||
volumes:
|
||||
- ./adminfront:/app
|
||||
- ./common:/common
|
||||
- ./locales:/locales
|
||||
- /app/node_modules
|
||||
networks:
|
||||
- baron_net
|
||||
healthcheck:
|
||||
@@ -453,6 +453,10 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./devfront/Dockerfile
|
||||
args:
|
||||
VITE_DEVFRONT_PUBLIC_URL: ${DEVFRONT_URL:-}
|
||||
VITE_OIDC_AUTHORITY: ${VITE_OIDC_AUTHORITY:-}
|
||||
VITE_OIDC_CLIENT_ID: devfront
|
||||
container_name: baron_devfront
|
||||
env_file:
|
||||
- .env
|
||||
@@ -461,11 +465,6 @@ services:
|
||||
- API_PROXY_TARGET=http://baron_backend:3000
|
||||
ports:
|
||||
- "${DEVFRONT_PORT:-5174}:5173"
|
||||
volumes:
|
||||
- ./devfront:/app
|
||||
- ./common:/common
|
||||
- ./locales:/locales
|
||||
- /app/node_modules
|
||||
networks:
|
||||
- baron_net
|
||||
healthcheck:
|
||||
@@ -479,6 +478,10 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./orgfront/Dockerfile
|
||||
args:
|
||||
VITE_ORGFRONT_PUBLIC_URL: ${ORGFRONT_URL:-}
|
||||
VITE_OIDC_AUTHORITY: ${VITE_OIDC_AUTHORITY:-}
|
||||
VITE_OIDC_CLIENT_ID: orgfront
|
||||
container_name: baron_orgfront
|
||||
env_file:
|
||||
- .env
|
||||
@@ -488,11 +491,6 @@ services:
|
||||
- USERFRONT_URL=${USERFRONT_URL}
|
||||
ports:
|
||||
- "${ORGFRONT_PORT:-5175}:5175"
|
||||
volumes:
|
||||
- ./orgfront:/app
|
||||
- ./common:/common
|
||||
- ./locales:/locales
|
||||
- /app/node_modules
|
||||
networks:
|
||||
- baron_net
|
||||
healthcheck:
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
FROM node:lts
|
||||
FROM node:lts AS build
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Set CI environment variable to true to avoid TTY issues with pnpm
|
||||
ENV CI=true
|
||||
ENV ORGFRONT_BUILD_OUT_DIR=/workspace/orgfront/dist
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@10.5.2 --activate
|
||||
|
||||
# Copy workspace configs and common package
|
||||
COPY pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY common ./common
|
||||
COPY orgfront ./orgfront
|
||||
|
||||
# Install dependencies for the workspace
|
||||
RUN pnpm install --filter orgfront... --filter baron-sso... --no-frozen-lockfile --ignore-scripts
|
||||
ARG VITE_ORGFRONT_PUBLIC_URL
|
||||
ARG VITE_OIDC_AUTHORITY
|
||||
ARG VITE_OIDC_CLIENT_ID
|
||||
ENV VITE_ORGFRONT_PUBLIC_URL=$VITE_ORGFRONT_PUBLIC_URL
|
||||
ENV VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY
|
||||
ENV VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID
|
||||
|
||||
# 프로덕션 서빙을 위한 serve 패키지 글로벌 설치
|
||||
RUN npm install -g serve
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
WORKDIR /workspace/orgfront
|
||||
RUN npm run build
|
||||
|
||||
FROM node:24-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV FRONTEND_DIST_DIR=/app/dist
|
||||
ENV PORT=5175
|
||||
|
||||
COPY scripts/serve_frontend_prod.mjs ./serve_frontend_prod.mjs
|
||||
COPY --from=build /workspace/orgfront/dist ./dist
|
||||
|
||||
# Vite 기본 포트
|
||||
EXPOSE 5175
|
||||
|
||||
# 실행 스크립트: APP_ENV에 따라 개발 서버 또는 빌드 후 서빙
|
||||
RUN chmod +x ./scripts/runtime-mode.sh
|
||||
CMD ["sh", "./scripts/runtime-mode.sh"]
|
||||
CMD ["node", "./serve_frontend_prod.mjs"]
|
||||
|
||||
155
scripts/serve_frontend_prod.mjs
Normal file
155
scripts/serve_frontend_prod.mjs
Normal file
@@ -0,0 +1,155 @@
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
import { createServer } from "node:http";
|
||||
import { extname, join, normalize, resolve } from "node:path";
|
||||
|
||||
const distDir = resolve(process.env.FRONTEND_DIST_DIR ?? "/app/dist");
|
||||
const host = process.env.HOST ?? "0.0.0.0";
|
||||
const port = Number(process.env.PORT ?? 5173);
|
||||
const backendTarget = new URL(
|
||||
process.env.API_PROXY_TARGET || "http://localhost:3000",
|
||||
);
|
||||
|
||||
const contentTypes = {
|
||||
".css": "text/css; charset=utf-8",
|
||||
".html": "text/html; charset=utf-8",
|
||||
".ico": "image/x-icon",
|
||||
".js": "application/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".map": "application/json; charset=utf-8",
|
||||
".mjs": "application/javascript; charset=utf-8",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
".txt": "text/plain; charset=utf-8",
|
||||
".webp": "image/webp",
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
};
|
||||
|
||||
function getContentType(filePath) {
|
||||
return (
|
||||
contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
||||
);
|
||||
}
|
||||
|
||||
function sendJson(res, statusCode, body) {
|
||||
res.writeHead(statusCode, {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"Cache-Control": "no-store",
|
||||
});
|
||||
res.end(JSON.stringify(body));
|
||||
}
|
||||
|
||||
function toSafePath(pathname) {
|
||||
const decoded = decodeURIComponent(pathname);
|
||||
const relative = decoded.replace(/^\/+/, "");
|
||||
const safe = normalize(relative).replace(/^(\.\.(?:[\\/]|$))+/, "");
|
||||
return join(distDir, safe);
|
||||
}
|
||||
|
||||
async function tryReadFile(filePath) {
|
||||
try {
|
||||
return await readFile(filePath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function proxyToBackend(req, res, pathname, search) {
|
||||
const target = new URL(pathname + search, backendTarget);
|
||||
const headers = new Headers();
|
||||
|
||||
for (const [key, value] of Object.entries(req.headers)) {
|
||||
if (!value) continue;
|
||||
if (key === "host" || key === "content-length" || key === "connection") {
|
||||
continue;
|
||||
}
|
||||
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
||||
}
|
||||
|
||||
const hasBody = !["GET", "HEAD"].includes(req.method ?? "GET");
|
||||
const response = await fetch(target, {
|
||||
method: req.method,
|
||||
headers,
|
||||
body: hasBody ? req : undefined,
|
||||
duplex: hasBody ? "half" : undefined,
|
||||
});
|
||||
|
||||
const responseHeaders = new Headers(response.headers);
|
||||
responseHeaders.delete("content-length");
|
||||
responseHeaders.delete("transfer-encoding");
|
||||
responseHeaders.delete("connection");
|
||||
|
||||
res.writeHead(response.status, Object.fromEntries(responseHeaders.entries()));
|
||||
|
||||
if (req.method === "HEAD") {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
res.end(Buffer.from(arrayBuffer));
|
||||
}
|
||||
|
||||
async function serveStatic(req, res, pathname) {
|
||||
const indexPath = join(distDir, "index.html");
|
||||
const filePath = toSafePath(pathname);
|
||||
|
||||
let resolvedPath = filePath;
|
||||
try {
|
||||
const fileStat = await stat(resolvedPath);
|
||||
if (fileStat.isDirectory()) {
|
||||
resolvedPath = join(resolvedPath, "index.html");
|
||||
}
|
||||
} catch {
|
||||
resolvedPath = indexPath;
|
||||
}
|
||||
|
||||
let body = await tryReadFile(resolvedPath);
|
||||
if (!body) {
|
||||
body = await tryReadFile(indexPath);
|
||||
resolvedPath = indexPath;
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
sendJson(res, 500, { error: "dist_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
"Content-Type": getContentType(resolvedPath),
|
||||
"Cache-Control": resolvedPath.endsWith("index.html")
|
||||
? "no-cache"
|
||||
: "public, max-age=31536000, immutable",
|
||||
});
|
||||
|
||||
if (req.method === "HEAD") {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
createServer(async (req, res) => {
|
||||
try {
|
||||
const url = new URL(
|
||||
req.url ?? "/",
|
||||
`http://${req.headers.host ?? "localhost"}`,
|
||||
);
|
||||
const { pathname, search } = url;
|
||||
|
||||
if (pathname === "/api" || pathname.startsWith("/api/")) {
|
||||
await proxyToBackend(req, res, pathname, search);
|
||||
return;
|
||||
}
|
||||
|
||||
await serveStatic(req, res, pathname === "/" ? "/index.html" : pathname);
|
||||
} catch (error) {
|
||||
sendJson(res, 500, {
|
||||
error: "internal_server_error",
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}).listen(port, host, () => {
|
||||
console.log(`Frontend server listening on http://${host}:${port}`);
|
||||
});
|
||||
@@ -64,13 +64,19 @@ for file in "$STAGING_COMPOSE" "$PULL_COMPOSE"; do
|
||||
done
|
||||
|
||||
assert_contains "$STAGING_COMPOSE" 'image: ${ORGFRONT_IMAGE_NAME}:${IMAGE_TAG}'
|
||||
assert_contains "$PULL_COMPOSE" "context: ./orgfront"
|
||||
assert_contains "$DEPLOY_TEMPLATE" "../../orgfront:/app"
|
||||
assert_contains "$DEPLOY_TEMPLATE" "./orgfront/vite.config.ts:/app/vite.config.ts:ro"
|
||||
assert_contains "$DEPLOY_TEMPLATE" "./orgfront/auth.ts:/app/src/lib/auth.ts:ro"
|
||||
assert_contains "$PULL_COMPOSE" "context: ."
|
||||
assert_contains "$PULL_COMPOSE" "dockerfile: ./orgfront/Dockerfile"
|
||||
assert_contains "$PULL_COMPOSE" "VITE_ORGFRONT_PUBLIC_URL: \${ORGFRONT_URL:-}"
|
||||
assert_not_contains "$PULL_COMPOSE" "./orgfront:/app"
|
||||
assert_contains "$DEPLOY_TEMPLATE" "dockerfile: ./orgfront/Dockerfile"
|
||||
assert_contains "$DEPLOY_TEMPLATE" "VITE_ORGFRONT_PUBLIC_URL: \${ORGFRONT_URL}"
|
||||
assert_not_contains "$DEPLOY_TEMPLATE" "../../orgfront:/app"
|
||||
assert_not_contains "$DEPLOY_TEMPLATE" "./orgfront/vite.config.ts:/app/vite.config.ts:ro"
|
||||
assert_not_contains "$DEPLOY_TEMPLATE" "./orgfront/auth.ts:/app/src/lib/auth.ts:ro"
|
||||
|
||||
assert_contains "$BUILD_RC" "Build and push orgfront RC image"
|
||||
assert_contains "$BUILD_RC" "context: ./orgfront"
|
||||
assert_contains "$BUILD_RC" "context: ."
|
||||
assert_contains "$BUILD_RC" "file: ./orgfront/Dockerfile"
|
||||
assert_contains "$BUILD_RC" "/baron_sso/orgfront:"
|
||||
|
||||
assert_contains "$CODE_CHECK" "run_orgfront_tests"
|
||||
|
||||
102
test/production_image_release_policy_test.sh
Normal file
102
test/production_image_release_policy_test.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
if ! grep -Fq -- "$pattern" "$file"; then
|
||||
echo "ERROR: missing pattern in $file: $pattern" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
if grep -Fq -- "$pattern" "$file"; then
|
||||
echo "ERROR: forbidden pattern remains in $file: $pattern" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
build_rc="$ROOT_DIR/.gitea/workflows/build_RC.yml"
|
||||
staging_release="$ROOT_DIR/.gitea/workflows/staging_release.yml"
|
||||
production_release="$ROOT_DIR/.gitea/workflows/production_release.yml"
|
||||
production_compose="$ROOT_DIR/docker/docker-compose.template.yaml"
|
||||
|
||||
for file in "$build_rc" "$staging_release" "$production_release" "$production_compose"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "ERROR: expected file not found: $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for app in adminfront devfront orgfront; do
|
||||
assert_contains "$build_rc" "Build and push $app RC image"
|
||||
assert_contains "$build_rc" "file: ./$app/Dockerfile"
|
||||
assert_contains "$build_rc" "build-args: |"
|
||||
assert_contains "$build_rc" "VITE_OIDC_AUTHORITY=\${{ vars.VITE_OIDC_AUTHORITY }}"
|
||||
done
|
||||
assert_contains "$build_rc" "Validate RC build configuration"
|
||||
assert_contains "$build_rc" "Missing required RC build value"
|
||||
assert_contains "$build_rc" "Check Gitea repo variables/secrets"
|
||||
assert_contains "$build_rc" "VITE_ADMIN_PUBLIC_URL=\${{ vars.ADMINFRONT_URL }}"
|
||||
assert_contains "$build_rc" "VITE_DEVFRONT_PUBLIC_URL=\${{ vars.DEVFRONT_URL }}"
|
||||
assert_contains "$build_rc" "VITE_ORGFRONT_PUBLIC_URL=\${{ vars.ORGFRONT_URL }}"
|
||||
assert_contains "$build_rc" "ORGFRONT_URL=\${{ vars.ORGFRONT_URL }}"
|
||||
|
||||
assert_contains "$staging_release" "CLICKHOUSE_PASSWORD=\${{ secrets.CLICKHOUSE_PASSWORD }}"
|
||||
assert_not_contains "$staging_release" "CLICKHOUSE_PASSWORD=\${{ vars.CLICKHOUSE_PASSWORD }}"
|
||||
assert_contains "$staging_release" "PROFILE_CACHE_TTL=\${{ vars.PROFILE_CACHE_TTL }}"
|
||||
assert_contains "$staging_release" "KRATOS_UI_NODE_VERSION=\${{ vars.KRATOS_UI_NODE_VERSION }}"
|
||||
assert_contains "$staging_release" "Missing required staging .env value"
|
||||
assert_contains "$staging_release" "Check Gitea repo variables/secrets"
|
||||
assert_contains "$staging_release" "scp scripts/render_ory_config.sh"
|
||||
assert_contains "$staging_release" "scp compose.ory.yaml"
|
||||
assert_not_contains "$staging_release" "scp docker/compose.ory.yaml"
|
||||
assert_contains "$staging_release" "bash scripts/render_ory_config.sh"
|
||||
assert_contains "$staging_release" "chmod -R 777 config/.generated/ory"
|
||||
|
||||
assert_contains "$production_release" "for image in backend userfront adminfront devfront orgfront; do"
|
||||
assert_contains "$production_release" 'docker://${HARBOR_HOSTNAME}/baron_sso/${image}:${BASE_TAG}'
|
||||
assert_contains "$production_release" 'docker://${HARBOR_HOSTNAME}/baron_sso/${image}:${RE_TAG}'
|
||||
assert_contains "$production_release" "ADMINFRONT_IMAGE_NAME: \${{ vars.HARBOR_HOSTNAME }}/baron_sso/adminfront"
|
||||
assert_contains "$production_release" "DEVFRONT_IMAGE_NAME: \${{ vars.HARBOR_HOSTNAME }}/baron_sso/devfront"
|
||||
assert_contains "$production_release" "ORGFRONT_IMAGE_NAME: \${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront"
|
||||
assert_contains "$production_release" "USERFRONT_URL=\${{ vars.PROD_FRONTEND_URL }}"
|
||||
assert_contains "$production_release" "BACKEND_URL=\${{ vars.PROD_BACKEND_URL }}"
|
||||
assert_contains "$production_release" "USERFRONT_PORT=\${{ vars.PROD_FRONTEND_PORT }}"
|
||||
assert_contains "$production_release" "PROD_BACKEND_PORT=\${{ vars.PROD_BACKEND_PORT }}"
|
||||
assert_contains "$production_release" "BACKEND_PORT=3000"
|
||||
assert_contains "$production_release" "ADMINFRONT_URL=\${{ vars.ADMINFRONT_URL }}"
|
||||
assert_contains "$production_release" "DEVFRONT_URL=\${{ vars.DEVFRONT_URL }}"
|
||||
assert_contains "$production_release" "ORGFRONT_URL=\${{ vars.ORGFRONT_URL }}"
|
||||
assert_contains "$production_release" "VITE_OIDC_AUTHORITY=\${{ vars.VITE_OIDC_AUTHORITY }}"
|
||||
assert_contains "$production_release" "ADMINFRONT_CALLBACK_URLS=\${{ vars.ADMINFRONT_CALLBACK_URLS }}"
|
||||
assert_contains "$production_release" "DEVFRONT_CALLBACK_URLS=\${{ vars.DEVFRONT_CALLBACK_URLS }}"
|
||||
assert_contains "$production_release" "ORGFRONT_CALLBACK_URLS=\${{ vars.ORGFRONT_CALLBACK_URLS }}"
|
||||
assert_contains "$production_release" "ADMINFRONT_PORT=\${{ vars.ADMINFRONT_PORT }}"
|
||||
assert_contains "$production_release" "DEVFRONT_PORT=\${{ vars.DEVFRONT_PORT }}"
|
||||
assert_contains "$production_release" "ORGFRONT_PORT=\${{ vars.ORGFRONT_PORT }}"
|
||||
assert_contains "$production_release" "export ADMINFRONT_IMAGE_NAME='\${ADMINFRONT_IMAGE_NAME}'"
|
||||
assert_contains "$production_release" "export DEVFRONT_IMAGE_NAME='\${DEVFRONT_IMAGE_NAME}'"
|
||||
assert_contains "$production_release" "export ORGFRONT_IMAGE_NAME='\${ORGFRONT_IMAGE_NAME}'"
|
||||
assert_contains "$production_release" "Missing required production .env value"
|
||||
assert_not_contains "$production_release" "PROD_USERFRONT_URL"
|
||||
assert_not_contains "$production_release" "PROD_USERFRONT_PORT"
|
||||
|
||||
for app in adminfront devfront orgfront; do
|
||||
assert_contains "$production_compose" "$app:"
|
||||
done
|
||||
assert_contains "$production_compose" 'image: ${ADMINFRONT_IMAGE_NAME}:${IMAGE_TAG}'
|
||||
assert_contains "$production_compose" 'image: ${DEVFRONT_IMAGE_NAME}:${IMAGE_TAG}'
|
||||
assert_contains "$production_compose" 'image: ${ORGFRONT_IMAGE_NAME}:${IMAGE_TAG}'
|
||||
assert_contains "$production_compose" 'API_PROXY_TARGET=http://baron_backend:${BACKEND_PORT:-3000}'
|
||||
assert_contains "$production_compose" '${PROD_BACKEND_PORT:-3010}:3000'
|
||||
assert_contains "$production_compose" '${USERFRONT_PORT:-80}:5000'
|
||||
assert_contains "$production_compose" 'BACKEND_PORT=3000'
|
||||
assert_contains "$production_compose" 'http://127.0.0.1:3000/health'
|
||||
|
||||
echo "production image release policy checks passed"
|
||||
@@ -30,6 +30,9 @@ adminfront_vite="adminfront/vite.config.ts"
|
||||
adminfront_runtime="adminfront/scripts/runtime-mode.sh"
|
||||
devfront_runtime="devfront/scripts/runtime-mode.sh"
|
||||
orgfront_runtime="orgfront/scripts/runtime-mode.sh"
|
||||
adminfront_dockerfile="adminfront/Dockerfile"
|
||||
devfront_dockerfile="devfront/Dockerfile"
|
||||
orgfront_dockerfile="orgfront/Dockerfile"
|
||||
|
||||
for file in \
|
||||
"$staging_pull" \
|
||||
@@ -42,7 +45,10 @@ for file in \
|
||||
"$orgfront_vite" \
|
||||
"$adminfront_runtime" \
|
||||
"$devfront_runtime" \
|
||||
"$orgfront_runtime"
|
||||
"$orgfront_runtime" \
|
||||
"$adminfront_dockerfile" \
|
||||
"$devfront_dockerfile" \
|
||||
"$orgfront_dockerfile"
|
||||
do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "missing expected file: $file" >&2
|
||||
@@ -72,8 +78,11 @@ for app in adminfront devfront orgfront; do
|
||||
assert_contains "$pull_compose" "$app:"
|
||||
assert_contains "$pull_compose" "context: ."
|
||||
assert_contains "$pull_compose" "dockerfile: ./$app/Dockerfile"
|
||||
assert_contains "$pull_compose" "VITE_OIDC_AUTHORITY: \${VITE_OIDC_AUTHORITY:-}"
|
||||
assert_not_contains "$pull_compose" "context: ./$app"
|
||||
assert_not_contains "$pull_compose" "./$app:/app"
|
||||
done
|
||||
assert_not_contains "$pull_compose" "/app/node_modules"
|
||||
assert_contains "$pull_compose" "dockerfile: userfront/Dockerfile"
|
||||
assert_not_contains "$pull_compose" 'target: ${USERFRONT_BUILD_TARGET:-dev}'
|
||||
assert_not_contains "$pull_compose" "target: dev"
|
||||
@@ -82,8 +91,12 @@ assert_contains "$pull_compose" "http://127.0.0.1:5173/"
|
||||
assert_contains "$pull_compose" "http://127.0.0.1:5175/"
|
||||
assert_contains "$pull_compose" 'APP_ENV=${APP_ENV:-stage}'
|
||||
|
||||
assert_contains "$deploy_compose" "sh ./scripts/runtime-mode.sh"
|
||||
assert_contains "$deploy_compose" "dockerfile: ./adminfront/Dockerfile"
|
||||
assert_contains "$deploy_compose" "dockerfile: ./devfront/Dockerfile"
|
||||
assert_contains "$deploy_compose" "dockerfile: ./orgfront/Dockerfile"
|
||||
assert_not_contains "$deploy_compose" "sh ./scripts/runtime-mode.sh"
|
||||
assert_not_contains "$deploy_compose" "command: npm run dev"
|
||||
assert_not_contains "$deploy_compose" "image: node:20-alpine"
|
||||
assert_contains "$deploy_gateway" "root /usr/share/nginx/html;"
|
||||
assert_contains "$deploy_gateway" 'try_files $uri $uri/ /index.html;'
|
||||
assert_not_contains "$deploy_gateway" "baron_userfront"
|
||||
@@ -96,6 +109,21 @@ for app in adminfront devfront orgfront; do
|
||||
assert_not_contains ".gitea/workflows/build_RC.yml" "context: ./$app"
|
||||
done
|
||||
|
||||
for app in adminfront devfront orgfront; do
|
||||
dockerfile="$app/Dockerfile"
|
||||
assert_contains "$dockerfile" "COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./"
|
||||
assert_contains "$dockerfile" "RUN pnpm install --frozen-lockfile --ignore-scripts"
|
||||
assert_contains "$dockerfile" "FROM node:24-alpine AS production"
|
||||
assert_contains "$dockerfile" "COPY scripts/serve_frontend_prod.mjs ./serve_frontend_prod.mjs"
|
||||
assert_contains "$dockerfile" "RUN npm run build"
|
||||
assert_contains "$dockerfile" 'CMD ["node", "./serve_frontend_prod.mjs"]'
|
||||
assert_not_contains "$dockerfile" "cd common && pnpm install"
|
||||
assert_not_contains "$dockerfile" "npm install -g serve"
|
||||
assert_not_contains "$dockerfile" "runtime-mode.sh"
|
||||
done
|
||||
assert_contains "scripts/serve_frontend_prod.mjs" "pathname === \"/api\" || pathname.startsWith(\"/api/\")"
|
||||
assert_contains "scripts/serve_frontend_prod.mjs" "API_PROXY_TARGET"
|
||||
|
||||
assert_contains "$adminfront_vite" "/tmp/baron-sso-adminfront-dist"
|
||||
assert_contains "$adminfront_vite" "/tmp/baron-sso-adminfront-vite-cache"
|
||||
assert_contains "adminfront/biome.json" '".vite"'
|
||||
|
||||
Reference in New Issue
Block a user