diff --git a/.gitea/workflows/code_check.yml b/.gitea/workflows/code_check.yml index 6289251d..a2c4a05d 100644 --- a/.gitea/workflows/code_check.yml +++ b/.gitea/workflows/code_check.yml @@ -254,41 +254,46 @@ jobs: with: node-version: "24" + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + - name: Install adminfront dependencies run: | cd adminfront - npx pnpm install -C ../common --no-frozen-lockfile - npx pnpm install --no-frozen-lockfile + pnpm install -C ../common --no-frozen-lockfile + pnpm install --no-frozen-lockfile - name: Biome check adminfront run: | cd adminfront - npx biome check . --formatter-enabled=false --assist-enabled=false - npx biome check . --linter-enabled=false --assist-enabled=false + pnpm exec biome check . --formatter-enabled=false --assist-enabled=false + pnpm exec biome check . --linter-enabled=false --assist-enabled=false - name: Install devfront dependencies run: | cd devfront - npx pnpm install -C ../common --no-frozen-lockfile - npx pnpm install --no-frozen-lockfile + pnpm install -C ../common --no-frozen-lockfile + pnpm install --no-frozen-lockfile - name: Biome check devfront run: | cd devfront - npx biome check . --formatter-enabled=false --assist-enabled=false - npx biome check . --linter-enabled=false --assist-enabled=false + pnpm exec biome check . --formatter-enabled=false --assist-enabled=false + pnpm exec biome check . --linter-enabled=false --assist-enabled=false - name: Install orgfront dependencies run: | cd orgfront - npx pnpm install -C ../common --no-frozen-lockfile - npx pnpm install --no-frozen-lockfile + pnpm install -C ../common --no-frozen-lockfile + pnpm install --no-frozen-lockfile - name: Biome check orgfront run: | cd orgfront - npx biome check . --formatter-enabled=false --assist-enabled=false - npx biome check . --linter-enabled=false --assist-enabled=false + pnpm exec biome check . --formatter-enabled=false --assist-enabled=false + pnpm exec biome check . --linter-enabled=false --assist-enabled=false backend-tests: needs: @@ -731,9 +736,9 @@ jobs: set +e cd userfront-e2e if [ "$USERFRONT_E2E_FULL" = "true" ]; then - test_command="npm test" + test_command="npx playwright test" else - test_command="npm test -- --project=chromium-desktop --project=chromium-mobile-webapp" + test_command="npx playwright test --project=chromium-desktop --project=chromium-mobile-webapp" fi workers="${USERFRONT_E2E_WORKERS:-2}" case "$workers" in @@ -759,10 +764,10 @@ jobs: echo "3. \`cd ../userfront && flutter build web --wasm --release\`" if [ "$USERFRONT_E2E_FULL" = "true" ]; then echo "4. \`cd ../userfront-e2e && npx playwright install --with-deps\`" - echo "5. \`npm test\`" + echo "5. \`npx playwright test\`" else echo "4. \`cd ../userfront-e2e && npx playwright install --with-deps chromium\`" - echo "5. \`npm test -- --project=chromium-desktop --project=chromium-mobile-webapp\`" + echo "5. \`npx playwright test --project=chromium-desktop --project=chromium-mobile-webapp\`" fi echo echo "## Log Tail (last 200 lines)" @@ -1250,6 +1255,11 @@ jobs: with: node-version: "24" + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + - name: Get Playwright version id: playwright-version run: | @@ -1323,10 +1333,11 @@ jobs: path: | reports/adminfront-test-failure-report.md reports/adminfront-install.log + reports/adminfront-build.log reports/adminfront-provision.log reports/adminfront-test.log - adminfront/playwright-report - adminfront/test-results + reports/adminfront-playwright-report + reports/adminfront-test-results if-no-files-found: ignore devfront-tests: diff --git a/.gitea/workflows/production_image_deploy.yml b/.gitea/workflows/production_image_deploy.yml index dfa30803..f1eb6e1f 100644 --- a/.gitea/workflows/production_image_deploy.yml +++ b/.gitea/workflows/production_image_deploy.yml @@ -29,39 +29,58 @@ jobs: IMAGE_DEPLOY_PUBLIC_URL: ${{ vars.PROD_FRONTEND_URL }} IMAGE_DEPLOY_COMPOSE_TEMPLATE: deploy/templates/docker-compose.images.yaml IMAGE_DEPLOY_BUNDLE_FILE: prod-image-deploy-bundle.tgz - ADMINFRONT_URL: ${{ vars.ADMINFRONT_URL }} - DEVFRONT_URL: ${{ vars.DEVFRONT_URL }} - ORGFRONT_URL: ${{ vars.ORGFRONT_URL }} - VITE_OIDC_AUTHORITY: ${{ vars.VITE_OIDC_AUTHORITY }} + ADMINFRONT_URL: ${{ vars.PROD_ADMINFRONT_URL }} + DEVFRONT_URL: ${{ vars.PROD_DEVFRONT_URL }} + ORGFRONT_URL: ${{ vars.PROD_ORGFRONT_URL }} + VITE_OIDC_AUTHORITY: ${{ vars.PROD_VITE_OIDC_AUTHORITY }} + IMAGE_DEPLOY_BACKEND_LOG_LEVEL: ${{ vars.PROD_BACKEND_LOG_LEVEL }} + IMAGE_DEPLOY_CLIENT_LOG_DEBUG: ${{ vars.PROD_CLIENT_LOG_DEBUG }} + IMAGE_DEPLOY_BACKEND_PUBLIC_URL: ${{ vars.PROD_BACKEND_URL || vars.PROD_FRONTEND_URL }} + IMAGE_DEPLOY_BACKEND_URL: ${{ vars.PROD_BACKEND_URL || vars.PROD_FRONTEND_URL }} + WORKS_ADMIN_API_BASE_URL: ${{ vars.PROD_WORKS_ADMIN_API_BASE_URL }} + WORKS_ADMIN_OAUTH_TOKEN_URL: ${{ vars.PROD_WORKS_ADMIN_OAUTH_TOKEN_URL }} + PROFILE_CACHE_TTL: ${{ vars.PROD_PROFILE_CACHE_TTL }} + NAVER_CLOUD_ACCESS_KEY: ${{ secrets.PROD_NAVER_CLOUD_ACCESS_KEY }} + NAVER_CLOUD_SECRET_KEY: ${{ secrets.PROD_NAVER_CLOUD_SECRET_KEY }} + NAVER_CLOUD_SERVICE_ID: ${{ vars.PROD_NAVER_CLOUD_SERVICE_ID }} + NAVER_SENDER_PHONE_NUMBER: ${{ vars.PROD_NAVER_SENDER_PHONE_NUMBER }} + AWS_REGION: ${{ vars.PROD_AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ vars.PROD_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }} + AWS_SES_SENDER: ${{ vars.PROD_AWS_SES_SENDER }} + CORS_ALLOWED_ORIGINS: ${{ vars.PROD_CORS_ALLOWED_ORIGINS }} + OATHKEEPER_API_URL: ${{ vars.PROD_OATHKEEPER_API_URL }} + CLICKHOUSE_HOST: ${{ vars.PROD_CLICKHOUSE_HOST }} + CLICKHOUSE_USER: ${{ vars.PROD_CLICKHOUSE_USER }} IMAGE_DEPLOY_DB_PORT: ${{ vars.PROD_DB_PORT }} IMAGE_DEPLOY_REDIS_PORT: ${{ vars.PROD_REDIS_PORT }} IMAGE_DEPLOY_CLICKHOUSE_PORT_HTTP: ${{ vars.PROD_CLICKHOUSE_PORT_HTTP }} IMAGE_DEPLOY_CLICKHOUSE_PORT_NATIVE: ${{ vars.PROD_CLICKHOUSE_PORT_NATIVE }} IMAGE_DEPLOY_BACKEND_PORT: ${{ vars.PROD_BACKEND_PORT }} IMAGE_DEPLOY_FRONTEND_PORT: ${{ vars.PROD_FRONTEND_PORT }} - ADMINFRONT_PORT: ${{ vars.ADMINFRONT_PORT }} - DEVFRONT_PORT: ${{ vars.DEVFRONT_PORT }} - ORGFRONT_PORT: ${{ vars.ORGFRONT_PORT }} + ADMINFRONT_PORT: ${{ vars.PROD_ADMINFRONT_PORT }} + DEVFRONT_PORT: ${{ vars.PROD_DEVFRONT_PORT }} + ORGFRONT_PORT: ${{ vars.PROD_ORGFRONT_PORT }} IMAGE_DEPLOY_OATHKEEPER_PROXY_PORT: ${{ vars.PROD_OATHKEEPER_PROXY_PORT }} IMAGE_DEPLOY_DOMAIN_SUFFIX: ${{ vars.PROD_DOMAIN_SUFFIX }} - ADMINFRONT_CALLBACK_URLS: ${{ vars.ADMINFRONT_CALLBACK_URLS }} - DEVFRONT_CALLBACK_URLS: ${{ vars.DEVFRONT_CALLBACK_URLS }} - ORGFRONT_CALLBACK_URLS: ${{ vars.ORGFRONT_CALLBACK_URLS }} - HYDRA_REFRESH_TOKEN_TTL: ${{ vars.HYDRA_REFRESH_TOKEN_TTL }} - ORY_POSTGRES_USER: ${{ vars.ORY_POSTGRES_USER }} - ORY_POSTGRES_DB: ${{ vars.ORY_POSTGRES_DB }} - KRATOS_DB: ${{ vars.KRATOS_DB }} - HYDRA_DB: ${{ vars.HYDRA_DB }} - KETO_DB: ${{ vars.KETO_DB }} - KRATOS_VERSION: ${{ vars.KRATOS_VERSION }} - HYDRA_VERSION: ${{ vars.HYDRA_VERSION }} - KETO_VERSION: ${{ vars.KETO_VERSION }} - OATHKEEPER_VERSION: ${{ vars.OATHKEEPER_VERSION }} - ORY_POSTGRES_TAG: ${{ vars.ORY_POSTGRES_TAG }} - OATHKEEPER_UID: ${{ vars.OATHKEEPER_UID }} - OATHKEEPER_GID: ${{ vars.OATHKEEPER_GID }} - OATHKEEPER_INTROSPECT_CLIENT_ID: ${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} - ADMIN_EMAIL: ${{ vars.ADMIN_EMAIL }} + ADMINFRONT_CALLBACK_URLS: ${{ vars.PROD_ADMINFRONT_CALLBACK_URLS }} + DEVFRONT_CALLBACK_URLS: ${{ vars.PROD_DEVFRONT_CALLBACK_URLS }} + ORGFRONT_CALLBACK_URLS: ${{ vars.PROD_ORGFRONT_CALLBACK_URLS }} + HYDRA_REFRESH_TOKEN_TTL: ${{ vars.PROD_HYDRA_REFRESH_TOKEN_TTL }} + ORY_POSTGRES_USER: ${{ vars.PROD_ORY_POSTGRES_USER }} + ORY_POSTGRES_DB: ${{ vars.PROD_ORY_POSTGRES_DB }} + KRATOS_DB: ${{ vars.PROD_KRATOS_DB }} + HYDRA_DB: ${{ vars.PROD_HYDRA_DB }} + KETO_DB: ${{ vars.PROD_KETO_DB }} + KRATOS_VERSION: ${{ vars.PROD_KRATOS_VERSION }} + HYDRA_VERSION: ${{ vars.PROD_HYDRA_VERSION }} + KETO_VERSION: ${{ vars.PROD_KETO_VERSION }} + OATHKEEPER_VERSION: ${{ vars.PROD_OATHKEEPER_VERSION }} + ORY_POSTGRES_TAG: ${{ vars.PROD_ORY_POSTGRES_TAG }} + OATHKEEPER_UID: ${{ vars.PROD_OATHKEEPER_UID }} + OATHKEEPER_GID: ${{ vars.PROD_OATHKEEPER_GID }} + OATHKEEPER_INTROSPECT_CLIENT_ID: ${{ vars.PROD_OATHKEEPER_INTROSPECT_CLIENT_ID }} + ADMIN_EMAIL: ${{ vars.PROD_ADMIN_EMAIL }} HARBOR_HOSTNAME: ${{ vars.HARBOR_HOSTNAME }} BACKEND_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/backend USERFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/userfront diff --git a/.gitea/workflows/production_release.yml b/.gitea/workflows/production_release.yml index 5b9cd194..e234b50e 100644 --- a/.gitea/workflows/production_release.yml +++ b/.gitea/workflows/production_release.yml @@ -101,33 +101,33 @@ jobs: "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 }}" \ + "ADMINFRONT_PORT=${{ vars.PROD_ADMINFRONT_PORT }}" \ + "DEVFRONT_PORT=${{ vars.PROD_DEVFRONT_PORT }}" \ + "ORGFRONT_PORT=${{ vars.PROD_ORGFRONT_PORT }}" \ "DB_USER=${{ vars.PROD_DB_USER }}" \ "DB_PASSWORD=${{ secrets.PROD_DB_PASSWORD }}" \ "DB_NAME=${{ vars.PROD_DB_NAME }}" \ "COOKIE_SECRET=${{ secrets.PROD_COOKIE_SECRET }}" \ "JWT_SECRET=${{ secrets.PROD_JWT_SECRET }}" \ "REDIS_ADDR=${{ vars.PROD_REDIS_ADDR }}" \ - "NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }}" \ - "NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }}" \ - "NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }}" \ - "NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }}" \ - "AWS_REGION=${{ vars.AWS_REGION }}" \ - "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 }}" \ + "NAVER_CLOUD_ACCESS_KEY=${{ secrets.PROD_NAVER_CLOUD_ACCESS_KEY }}" \ + "NAVER_CLOUD_SECRET_KEY=${{ secrets.PROD_NAVER_CLOUD_SECRET_KEY }}" \ + "NAVER_CLOUD_SERVICE_ID=${{ vars.PROD_NAVER_CLOUD_SERVICE_ID }}" \ + "NAVER_SENDER_PHONE_NUMBER=${{ vars.PROD_NAVER_SENDER_PHONE_NUMBER }}" \ + "AWS_REGION=${{ vars.PROD_AWS_REGION }}" \ + "AWS_ACCESS_KEY_ID=${{ vars.PROD_AWS_ACCESS_KEY_ID }}" \ + "AWS_SECRET_ACCESS_KEY=${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}" \ + "AWS_SES_SENDER=${{ vars.PROD_AWS_SES_SENDER }}" \ "USERFRONT_URL=${{ vars.PROD_FRONTEND_URL }}" \ - "ADMINFRONT_URL=${{ vars.ADMINFRONT_URL }}" \ - "DEVFRONT_URL=${{ vars.DEVFRONT_URL }}" \ - "ORGFRONT_URL=${{ vars.ORGFRONT_URL }}" \ + "ADMINFRONT_URL=${{ vars.PROD_ADMINFRONT_URL }}" \ + "DEVFRONT_URL=${{ vars.PROD_DEVFRONT_URL }}" \ + "ORGFRONT_URL=${{ vars.PROD_ORGFRONT_URL }}" \ "BACKEND_URL=${{ vars.PROD_BACKEND_URL }}" \ - "VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}" \ - "HYDRA_REFRESH_TOKEN_TTL=${{ vars.HYDRA_REFRESH_TOKEN_TTL }}" \ - "ADMINFRONT_CALLBACK_URLS=${{ vars.ADMINFRONT_CALLBACK_URLS }}" \ - "DEVFRONT_CALLBACK_URLS=${{ vars.DEVFRONT_CALLBACK_URLS }}" \ - "ORGFRONT_CALLBACK_URLS=${{ vars.ORGFRONT_CALLBACK_URLS }}" \ + "VITE_OIDC_AUTHORITY=${{ vars.PROD_VITE_OIDC_AUTHORITY }}" \ + "HYDRA_REFRESH_TOKEN_TTL=${{ vars.PROD_HYDRA_REFRESH_TOKEN_TTL }}" \ + "ADMINFRONT_CALLBACK_URLS=${{ vars.PROD_ADMINFRONT_CALLBACK_URLS }}" \ + "DEVFRONT_CALLBACK_URLS=${{ vars.PROD_DEVFRONT_CALLBACK_URLS }}" \ + "ORGFRONT_CALLBACK_URLS=${{ vars.PROD_ORGFRONT_CALLBACK_URLS }}" \ > .env required_dotenv_keys=" diff --git a/.gitea/workflows/staging_code_pull.yml b/.gitea/workflows/staging_code_pull.yml index 04ae1003..6887c845 100644 --- a/.gitea/workflows/staging_code_pull.yml +++ b/.gitea/workflows/staging_code_pull.yml @@ -18,13 +18,13 @@ jobs: - name: Setup SSH uses: webfactory/ssh-agent@v0.9.0 with: - ssh-private-key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + ssh-private-key: ${{ secrets.STG_SSH_PRIVATE_KEY }} - name: Deploy to Staging by git pull env: - DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }} - STAGE_HOST: ${{ vars.STAGE_HOST }} - STAGE_USER: ${{ vars.STAGE_USER }} + DEPLOY_PATH: ${{ vars.STG_DEPLOY_PATH }} + STAGE_HOST: ${{ vars.STG_HOST }} + STAGE_USER: ${{ vars.STG_USER }} TARGET_BRANCH: ${{ inputs.target_branch }} run: | set -euo pipefail @@ -48,99 +48,99 @@ jobs: APP_ENV=stage BACKEND_LOG_LEVEL=debug CLIENT_LOG_DEBUG=true - WORKS_ADMIN_API_BASE_URL=${{ vars.WORKS_ADMIN_API_BASE_URL }} - WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.WORKS_ADMIN_OAUTH_TOKEN_URL }} + WORKS_ADMIN_API_BASE_URL=${{ vars.STG_WORKS_ADMIN_API_BASE_URL }} + WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.STG_WORKS_ADMIN_OAUTH_TOKEN_URL }} TZ=Asia/Seoul IDP_PROVIDER=ory # DB & Clickhouse - DB_PORT=${{ vars.DB_PORT }} - CLICKHOUSE_PORT_HTTP=${{ vars.CLICKHOUSE_PORT_HTTP }} - CLICKHOUSE_PORT_NATIVE=${{ vars.CLICKHOUSE_PORT_NATIVE }} - CLICKHOUSE_HOST=${{ vars.CLICKHOUSE_HOST }} - CLICKHOUSE_USER=${{ vars.CLICKHOUSE_USER }} - CLICKHOUSE_PASSWORD=${{ secrets.CLICKHOUSE_PASSWORD }} + DB_PORT=${{ vars.STG_DB_PORT }} + CLICKHOUSE_PORT_HTTP=${{ vars.STG_CLICKHOUSE_PORT_HTTP }} + CLICKHOUSE_PORT_NATIVE=${{ vars.STG_CLICKHOUSE_PORT_NATIVE }} + CLICKHOUSE_HOST=${{ vars.STG_CLICKHOUSE_HOST }} + CLICKHOUSE_USER=${{ vars.STG_CLICKHOUSE_USER }} + CLICKHOUSE_PASSWORD=${{ secrets.STG_CLICKHOUSE_PASSWORD }} - BACKEND_PORT=${{ vars.BACKEND_PORT }} - ADMINFRONT_PORT=${{ vars.ADMINFRONT_PORT }} - DEVFRONT_PORT=${{ vars.DEVFRONT_PORT }} - ORGFRONT_PORT=${{ vars.ORGFRONT_PORT }} - USERFRONT_PORT=${{ vars.USERFRONT_PORT }} + BACKEND_PORT=${{ vars.STG_BACKEND_PORT }} + ADMINFRONT_PORT=${{ vars.STG_ADMINFRONT_PORT }} + DEVFRONT_PORT=${{ vars.STG_DEVFRONT_PORT }} + ORGFRONT_PORT=${{ vars.STG_ORGFRONT_PORT }} + USERFRONT_PORT=${{ vars.STG_USERFRONT_PORT }} - OATHKEEPER_API_URL=${{ vars.OATHKEEPER_API_URL }} + OATHKEEPER_API_URL=${{ vars.STG_OATHKEEPER_API_URL }} - DB_USER=${{ vars.DB_USER }} + DB_USER=${{ vars.STG_DB_USER }} DB_PASSWORD=${{ secrets.STG_DB_PASSWORD }} - DB_NAME=${{ vars.DB_NAME }} + DB_NAME=${{ vars.STG_DB_NAME }} COOKIE_SECRET=${{ secrets.STG_COOKIE_SECRET }} JWT_SECRET=${{ secrets.STG_JWT_SECRET }} - REDIS_ADDR=${{ vars.REDIS_ADDR }} - CORS_ALLOWED_ORIGINS=${{ vars.CORS_ALLOWED_ORIGINS }} + REDIS_ADDR=${{ vars.STG_REDIS_ADDR }} + CORS_ALLOWED_ORIGINS=${{ vars.STG_CORS_ALLOWED_ORIGINS }} AUDIT_WORKER_COUNT=5 AUDIT_QUEUE_SIZE=2000 - PROFILE_CACHE_TTL=${{ vars.PROFILE_CACHE_TTL }} - NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }} - NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }} - NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }} - NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }} - AWS_REGION=${{ vars.AWS_REGION }} - 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 }} - ADMIN_EMAIL=${{ vars.ADMIN_EMAIL }} + PROFILE_CACHE_TTL=${{ vars.STG_PROFILE_CACHE_TTL }} + NAVER_CLOUD_ACCESS_KEY=${{ secrets.STG_NAVER_CLOUD_ACCESS_KEY }} + NAVER_CLOUD_SECRET_KEY=${{ secrets.STG_NAVER_CLOUD_SECRET_KEY }} + NAVER_CLOUD_SERVICE_ID=${{ vars.STG_NAVER_CLOUD_SERVICE_ID }} + NAVER_SENDER_PHONE_NUMBER=${{ vars.STG_NAVER_SENDER_PHONE_NUMBER }} + AWS_REGION=${{ vars.STG_AWS_REGION }} + AWS_ACCESS_KEY_ID=${{ vars.STG_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY=${{ secrets.STG_AWS_SECRET_ACCESS_KEY }} + AWS_SES_SENDER=${{ vars.STG_AWS_SES_SENDER }} + ADMIN_EMAIL=${{ vars.STG_ADMIN_EMAIL }} ADMIN_PASSWORD=${{ secrets.STG_ADMIN_PASSWORD }} - USERFRONT_URL=${{ vars.USERFRONT_URL }} - ADMINFRONT_URL=${{ vars.ADMINFRONT_URL }} - DEVFRONT_URL=${{ vars.DEVFRONT_URL }} - ORGFRONT_URL=${{ vars.ORGFRONT_URL }} - BACKEND_PUBLIC_URL=${{ vars.BACKEND_URL }} - BACKEND_URL=${{ vars.BACKEND_URL }} - OATHKEEPER_PUBLIC_URL=${{ vars.OATHKEEPER_PUBLIC_URL }} - ORY_POSTGRES_TAG=${{ vars.ORY_POSTGRES_TAG }} - ORY_POSTGRES_USER=${{ vars.ORY_POSTGRES_USER }} + USERFRONT_URL=${{ vars.STG_USERFRONT_URL }} + ADMINFRONT_URL=${{ vars.STG_ADMINFRONT_URL }} + DEVFRONT_URL=${{ vars.STG_DEVFRONT_URL }} + ORGFRONT_URL=${{ vars.STG_ORGFRONT_URL }} + BACKEND_PUBLIC_URL=${{ vars.STG_BACKEND_URL }} + BACKEND_URL=${{ vars.STG_BACKEND_URL }} + OATHKEEPER_PUBLIC_URL=${{ vars.STG_OATHKEEPER_PUBLIC_URL }} + ORY_POSTGRES_TAG=${{ vars.STG_ORY_POSTGRES_TAG }} + ORY_POSTGRES_USER=${{ vars.STG_ORY_POSTGRES_USER }} ORY_POSTGRES_PASSWORD=${{ secrets.STG_ORY_POSTGRES_PASSWORD }} - ORY_POSTGRES_DB=${{ vars.ORY_POSTGRES_DB }} - KRATOS_DB=${{ vars.KRATOS_DB }} - HYDRA_DB=${{ vars.HYDRA_DB }} - KETO_DB=${{ vars.KETO_DB }} - KRATOS_VERSION=${{ vars.KRATOS_VERSION }} - KRATOS_UI_NODE_VERSION=${{ vars.KRATOS_UI_NODE_VERSION }} - HYDRA_VERSION=${{ vars.HYDRA_VERSION }} - KETO_VERSION=${{ vars.KETO_VERSION }} - ORY_SDK_URL=${{ vars.ORY_SDK_URL }} - KRATOS_PUBLIC_URL=${{ vars.KRATOS_PUBLIC_URL }} - KRATOS_ADMIN_URL=${{ vars.KRATOS_ADMIN_URL }} - KRATOS_BROWSER_URL=${{ vars.KRATOS_BROWSER_URL }} - KRATOS_UI_URL=${{ vars.KRATOS_UI_URL }} - HYDRA_ADMIN_URL=${{ vars.HYDRA_ADMIN_URL }} - HYDRA_PUBLIC_URL=${{ vars.HYDRA_PUBLIC_URL }} - HYDRA_REFRESH_TOKEN_TTL=${{ vars.HYDRA_REFRESH_TOKEN_TTL }} - JWKS_URL=${{ vars.JWKS_URL }} - OATHKEEPER_VERSION=${{ vars.OATHKEEPER_VERSION }} - OATHKEEPER_UID=${{ vars.OATHKEEPER_UID }} - OATHKEEPER_GID=${{ vars.OATHKEEPER_GID }} - OATHKEEPER_HEALTH_URL=${{ vars.OATHKEEPER_HEALTH_URL }} - OATHKEEPER_HEALTH_INTERVAL_SECONDS=${{ vars.OATHKEEPER_HEALTH_INTERVAL_SECONDS }} - OATHKEEPER_HEALTH_TIMEOUT_SECONDS=${{ vars.OATHKEEPER_HEALTH_TIMEOUT_SECONDS }} - OATHKEEPER_HEALTH_ENABLED=${{ vars.OATHKEEPER_HEALTH_ENABLED }} - CSRF_COOKIE_NAME=${{ vars.CSRF_COOKIE_NAME }} + ORY_POSTGRES_DB=${{ vars.STG_ORY_POSTGRES_DB }} + KRATOS_DB=${{ vars.STG_KRATOS_DB }} + HYDRA_DB=${{ vars.STG_HYDRA_DB }} + KETO_DB=${{ vars.STG_KETO_DB }} + KRATOS_VERSION=${{ vars.STG_KRATOS_VERSION }} + KRATOS_UI_NODE_VERSION=${{ vars.STG_KRATOS_UI_NODE_VERSION }} + HYDRA_VERSION=${{ vars.STG_HYDRA_VERSION }} + KETO_VERSION=${{ vars.STG_KETO_VERSION }} + ORY_SDK_URL=${{ vars.STG_ORY_SDK_URL }} + KRATOS_PUBLIC_URL=${{ vars.STG_KRATOS_PUBLIC_URL }} + KRATOS_ADMIN_URL=${{ vars.STG_KRATOS_ADMIN_URL }} + KRATOS_BROWSER_URL=${{ vars.STG_KRATOS_BROWSER_URL }} + KRATOS_UI_URL=${{ vars.STG_KRATOS_UI_URL }} + HYDRA_ADMIN_URL=${{ vars.STG_HYDRA_ADMIN_URL }} + HYDRA_PUBLIC_URL=${{ vars.STG_HYDRA_PUBLIC_URL }} + HYDRA_REFRESH_TOKEN_TTL=${{ vars.STG_HYDRA_REFRESH_TOKEN_TTL }} + JWKS_URL=${{ vars.STG_JWKS_URL }} + OATHKEEPER_VERSION=${{ vars.STG_OATHKEEPER_VERSION }} + OATHKEEPER_UID=${{ vars.STG_OATHKEEPER_UID }} + OATHKEEPER_GID=${{ vars.STG_OATHKEEPER_GID }} + OATHKEEPER_HEALTH_URL=${{ vars.STG_OATHKEEPER_HEALTH_URL }} + OATHKEEPER_HEALTH_INTERVAL_SECONDS=${{ vars.STG_OATHKEEPER_HEALTH_INTERVAL_SECONDS }} + OATHKEEPER_HEALTH_TIMEOUT_SECONDS=${{ vars.STG_OATHKEEPER_HEALTH_TIMEOUT_SECONDS }} + OATHKEEPER_HEALTH_ENABLED=${{ vars.STG_OATHKEEPER_HEALTH_ENABLED }} + CSRF_COOKIE_NAME=${{ vars.STG_CSRF_COOKIE_NAME }} CSRF_COOKIE_SECRET=${{ secrets.STG_CSRF_COOKIE_SECRET }} # Frontend/Ory URL configs for Staging - 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 }} - KRATOS_ALLOWED_RETURN_URLS_JSON=${{ vars.KRATOS_ALLOWED_RETURN_URLS_JSON }} - KRATOS_ALLOWED_RETURN_URLS_EXTRA=${{ vars.KRATOS_ALLOWED_RETURN_URLS_EXTRA }} - # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} + VITE_OIDC_AUTHORITY=${{ vars.STG_VITE_OIDC_AUTHORITY }} + ADMINFRONT_CALLBACK_URLS=${{ vars.STG_ADMINFRONT_CALLBACK_URLS }} + DEVFRONT_CALLBACK_URLS=${{ vars.STG_DEVFRONT_CALLBACK_URLS }} + ORGFRONT_CALLBACK_URLS=${{ vars.STG_ORGFRONT_CALLBACK_URLS }} + KRATOS_ALLOWED_RETURN_URLS_JSON=${{ vars.STG_KRATOS_ALLOWED_RETURN_URLS_JSON }} + KRATOS_ALLOWED_RETURN_URLS_EXTRA=${{ vars.STG_KRATOS_ALLOWED_RETURN_URLS_EXTRA }} + # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.STG_OATHKEEPER_INTROSPECT_CLIENT_ID }} # OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }} # Monitoring & Alerts - SMS_WEBHOOK_PORT=${{ vars.SMS_WEBHOOK_PORT || '8080' }} - MONITOR_RECIPIENT_PHONES=${{ vars.MONITOR_RECIPIENT_PHONES || '01012345678,01098765432' }} - LOKI_URL=${{ vars.LOKI_URL || 'http://loki:3100/loki/api/v1/push' }} + SMS_WEBHOOK_PORT=${{ vars.STG_SMS_WEBHOOK_PORT || '8080' }} + MONITOR_RECIPIENT_PHONES=${{ vars.STG_MONITOR_RECIPIENT_PHONES || '01012345678,01098765432' }} + LOKI_URL=${{ vars.STG_LOKI_URL || 'http://loki:3100/loki/api/v1/push' }} EOF # 코드 업데이트 (Git) diff --git a/.gitea/workflows/staging_image_deploy.yml b/.gitea/workflows/staging_image_deploy.yml index 5fafa7a5..88fe17f3 100644 --- a/.gitea/workflows/staging_image_deploy.yml +++ b/.gitea/workflows/staging_image_deploy.yml @@ -18,50 +18,69 @@ jobs: - name: Setup SSH uses: webfactory/ssh-agent@v0.9.0 with: - ssh-private-key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + ssh-private-key: ${{ secrets.STG_SSH_PRIVATE_KEY }} - name: Build staging deployment bundle env: IMAGE_TAG: ${{ github.event.inputs.image_tag }} IMAGE_DEPLOY_ENV: stage - IMAGE_DEPLOY_INSTANCE_NAME: ${{ vars.STAGE_INSTANCE_NAME }} - IMAGE_DEPLOY_PORT_PREFIX: ${{ vars.STAGE_PORT_PREFIX }} - IMAGE_DEPLOY_PUBLIC_URL: ${{ vars.USERFRONT_URL }} + IMAGE_DEPLOY_INSTANCE_NAME: ${{ vars.STG_INSTANCE_NAME }} + IMAGE_DEPLOY_PORT_PREFIX: ${{ vars.STG_PORT_PREFIX }} + IMAGE_DEPLOY_PUBLIC_URL: ${{ vars.STG_USERFRONT_URL }} IMAGE_DEPLOY_COMPOSE_TEMPLATE: deploy/templates/docker-compose.images.yaml IMAGE_DEPLOY_BUNDLE_FILE: stage-image-deploy-bundle.tgz - ADMINFRONT_URL: ${{ vars.ADMINFRONT_URL }} - DEVFRONT_URL: ${{ vars.DEVFRONT_URL }} - ORGFRONT_URL: ${{ vars.ORGFRONT_URL }} - VITE_OIDC_AUTHORITY: ${{ vars.VITE_OIDC_AUTHORITY }} - IMAGE_DEPLOY_DB_PORT: ${{ vars.DB_PORT }} - IMAGE_DEPLOY_REDIS_PORT: ${{ vars.REDIS_PORT }} - IMAGE_DEPLOY_CLICKHOUSE_PORT_HTTP: ${{ vars.CLICKHOUSE_PORT_HTTP }} - IMAGE_DEPLOY_CLICKHOUSE_PORT_NATIVE: ${{ vars.CLICKHOUSE_PORT_NATIVE }} - IMAGE_DEPLOY_BACKEND_PORT: ${{ vars.BACKEND_PORT }} - IMAGE_DEPLOY_FRONTEND_PORT: ${{ vars.USERFRONT_PORT }} - ADMINFRONT_PORT: ${{ vars.ADMINFRONT_PORT }} - DEVFRONT_PORT: ${{ vars.DEVFRONT_PORT }} - ORGFRONT_PORT: ${{ vars.ORGFRONT_PORT }} - IMAGE_DEPLOY_OATHKEEPER_PROXY_PORT: ${{ vars.OATHKEEPER_PROXY_PORT }} - IMAGE_DEPLOY_DOMAIN_SUFFIX: ${{ vars.DOMAIN_SUFFIX }} - ADMINFRONT_CALLBACK_URLS: ${{ vars.ADMINFRONT_CALLBACK_URLS }} - DEVFRONT_CALLBACK_URLS: ${{ vars.DEVFRONT_CALLBACK_URLS }} - ORGFRONT_CALLBACK_URLS: ${{ vars.ORGFRONT_CALLBACK_URLS }} - HYDRA_REFRESH_TOKEN_TTL: ${{ vars.HYDRA_REFRESH_TOKEN_TTL }} - ORY_POSTGRES_USER: ${{ vars.ORY_POSTGRES_USER }} - ORY_POSTGRES_DB: ${{ vars.ORY_POSTGRES_DB }} - KRATOS_DB: ${{ vars.KRATOS_DB }} - HYDRA_DB: ${{ vars.HYDRA_DB }} - KETO_DB: ${{ vars.KETO_DB }} - KRATOS_VERSION: ${{ vars.KRATOS_VERSION }} - HYDRA_VERSION: ${{ vars.HYDRA_VERSION }} - KETO_VERSION: ${{ vars.KETO_VERSION }} - OATHKEEPER_VERSION: ${{ vars.OATHKEEPER_VERSION }} - ORY_POSTGRES_TAG: ${{ vars.ORY_POSTGRES_TAG }} - OATHKEEPER_UID: ${{ vars.OATHKEEPER_UID }} - OATHKEEPER_GID: ${{ vars.OATHKEEPER_GID }} - OATHKEEPER_INTROSPECT_CLIENT_ID: ${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} - ADMIN_EMAIL: ${{ vars.ADMIN_EMAIL }} + ADMINFRONT_URL: ${{ vars.STG_ADMINFRONT_URL }} + DEVFRONT_URL: ${{ vars.STG_DEVFRONT_URL }} + ORGFRONT_URL: ${{ vars.STG_ORGFRONT_URL }} + VITE_OIDC_AUTHORITY: ${{ vars.STG_VITE_OIDC_AUTHORITY }} + IMAGE_DEPLOY_BACKEND_LOG_LEVEL: ${{ vars.STG_BACKEND_LOG_LEVEL || 'debug' }} + IMAGE_DEPLOY_CLIENT_LOG_DEBUG: ${{ vars.STG_CLIENT_LOG_DEBUG || 'true' }} + IMAGE_DEPLOY_BACKEND_PUBLIC_URL: ${{ vars.STG_BACKEND_URL }} + IMAGE_DEPLOY_BACKEND_URL: ${{ vars.STG_BACKEND_URL }} + WORKS_ADMIN_API_BASE_URL: ${{ vars.STG_WORKS_ADMIN_API_BASE_URL }} + WORKS_ADMIN_OAUTH_TOKEN_URL: ${{ vars.STG_WORKS_ADMIN_OAUTH_TOKEN_URL }} + PROFILE_CACHE_TTL: ${{ vars.STG_PROFILE_CACHE_TTL }} + NAVER_CLOUD_ACCESS_KEY: ${{ secrets.STG_NAVER_CLOUD_ACCESS_KEY }} + NAVER_CLOUD_SECRET_KEY: ${{ secrets.STG_NAVER_CLOUD_SECRET_KEY }} + NAVER_CLOUD_SERVICE_ID: ${{ vars.STG_NAVER_CLOUD_SERVICE_ID }} + NAVER_SENDER_PHONE_NUMBER: ${{ vars.STG_NAVER_SENDER_PHONE_NUMBER }} + AWS_REGION: ${{ vars.STG_AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ vars.STG_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.STG_AWS_SECRET_ACCESS_KEY }} + AWS_SES_SENDER: ${{ vars.STG_AWS_SES_SENDER }} + CORS_ALLOWED_ORIGINS: ${{ vars.STG_CORS_ALLOWED_ORIGINS }} + OATHKEEPER_API_URL: ${{ vars.STG_OATHKEEPER_API_URL }} + CLICKHOUSE_HOST: ${{ vars.STG_CLICKHOUSE_HOST }} + CLICKHOUSE_USER: ${{ vars.STG_CLICKHOUSE_USER }} + IMAGE_DEPLOY_DB_PORT: ${{ vars.STG_DB_PORT }} + IMAGE_DEPLOY_REDIS_PORT: ${{ vars.STG_REDIS_PORT }} + IMAGE_DEPLOY_CLICKHOUSE_PORT_HTTP: ${{ vars.STG_CLICKHOUSE_PORT_HTTP }} + IMAGE_DEPLOY_CLICKHOUSE_PORT_NATIVE: ${{ vars.STG_CLICKHOUSE_PORT_NATIVE }} + IMAGE_DEPLOY_BACKEND_PORT: ${{ vars.STG_BACKEND_PORT }} + IMAGE_DEPLOY_FRONTEND_PORT: ${{ vars.STG_USERFRONT_PORT }} + ADMINFRONT_PORT: ${{ vars.STG_ADMINFRONT_PORT }} + DEVFRONT_PORT: ${{ vars.STG_DEVFRONT_PORT }} + ORGFRONT_PORT: ${{ vars.STG_ORGFRONT_PORT }} + IMAGE_DEPLOY_OATHKEEPER_PROXY_PORT: ${{ vars.STG_OATHKEEPER_PROXY_PORT }} + IMAGE_DEPLOY_DOMAIN_SUFFIX: ${{ vars.STG_DOMAIN_SUFFIX }} + ADMINFRONT_CALLBACK_URLS: ${{ vars.STG_ADMINFRONT_CALLBACK_URLS }} + DEVFRONT_CALLBACK_URLS: ${{ vars.STG_DEVFRONT_CALLBACK_URLS }} + ORGFRONT_CALLBACK_URLS: ${{ vars.STG_ORGFRONT_CALLBACK_URLS }} + HYDRA_REFRESH_TOKEN_TTL: ${{ vars.STG_HYDRA_REFRESH_TOKEN_TTL }} + ORY_POSTGRES_USER: ${{ vars.STG_ORY_POSTGRES_USER }} + ORY_POSTGRES_DB: ${{ vars.STG_ORY_POSTGRES_DB }} + KRATOS_DB: ${{ vars.STG_KRATOS_DB }} + HYDRA_DB: ${{ vars.STG_HYDRA_DB }} + KETO_DB: ${{ vars.STG_KETO_DB }} + KRATOS_VERSION: ${{ vars.STG_KRATOS_VERSION }} + HYDRA_VERSION: ${{ vars.STG_HYDRA_VERSION }} + KETO_VERSION: ${{ vars.STG_KETO_VERSION }} + OATHKEEPER_VERSION: ${{ vars.STG_OATHKEEPER_VERSION }} + ORY_POSTGRES_TAG: ${{ vars.STG_ORY_POSTGRES_TAG }} + OATHKEEPER_UID: ${{ vars.STG_OATHKEEPER_UID }} + OATHKEEPER_GID: ${{ vars.STG_OATHKEEPER_GID }} + OATHKEEPER_INTROSPECT_CLIENT_ID: ${{ vars.STG_OATHKEEPER_INTROSPECT_CLIENT_ID }} + ADMIN_EMAIL: ${{ vars.STG_ADMIN_EMAIL }} HARBOR_HOSTNAME: ${{ vars.HARBOR_HOSTNAME }} BACKEND_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/backend USERFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/userfront @@ -71,7 +90,7 @@ jobs: IMAGE_DEPLOY_DB_PASSWORD: ${{ secrets.STG_DB_PASSWORD }} IMAGE_DEPLOY_ORY_POSTGRES_PASSWORD: ${{ secrets.STG_ORY_POSTGRES_PASSWORD }} IMAGE_DEPLOY_OATHKEEPER_INTROSPECT_CLIENT_SECRET: ${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }} - IMAGE_DEPLOY_CLICKHOUSE_PASSWORD: ${{ secrets.CLICKHOUSE_PASSWORD }} + IMAGE_DEPLOY_CLICKHOUSE_PASSWORD: ${{ secrets.STG_CLICKHOUSE_PASSWORD }} IMAGE_DEPLOY_COOKIE_SECRET: ${{ secrets.STG_COOKIE_SECRET }} IMAGE_DEPLOY_JWT_SECRET: ${{ secrets.STG_JWT_SECRET }} IMAGE_DEPLOY_CSRF_COOKIE_SECRET: ${{ secrets.STG_CSRF_COOKIE_SECRET }} @@ -83,9 +102,9 @@ jobs: - name: Upload bundle and run requested staging image tag env: IMAGE_DEPLOY_BUNDLE_FILE: stage-image-deploy-bundle.tgz - DEPLOY_HOST: ${{ vars.STAGE_HOST }} - DEPLOY_USER: ${{ vars.STAGE_USER }} - DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }} + DEPLOY_HOST: ${{ vars.STG_HOST }} + DEPLOY_USER: ${{ vars.STG_USER }} + DEPLOY_PATH: ${{ vars.STG_DEPLOY_PATH }} HARBOR_ENDPOINT: ${{ vars.HARBOR_ENDPOINT }} HARBOR_ROBOT_ACCOUNT: ${{ vars.HARBOR_ROBOT_ACCOUNT }} HARBOR_ROBOT_KEY: ${{ secrets.HARBOR_ROBOT_KEY }} diff --git a/.gitea/workflows/staging_release.yml b/.gitea/workflows/staging_release.yml index c8b9d59a..8f779f5c 100644 --- a/.gitea/workflows/staging_release.yml +++ b/.gitea/workflows/staging_release.yml @@ -18,7 +18,7 @@ jobs: - name: Setup SSH uses: webfactory/ssh-agent@v0.9.0 with: - ssh-private-key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + ssh-private-key: ${{ secrets.STG_SSH_PRIVATE_KEY }} - name: Deploy to Staging env: @@ -30,9 +30,9 @@ jobs: ORGFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront # Staging-specific variables - DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }} - STAGE_HOST: ${{ vars.STAGE_HOST }} - STAGE_USER: ${{ vars.STAGE_USER }} + DEPLOY_PATH: ${{ vars.STG_DEPLOY_PATH }} + STAGE_HOST: ${{ vars.STG_HOST }} + STAGE_USER: ${{ vars.STG_USER }} HARBOR_ENDPOINT: ${{ vars.HARBOR_ENDPOINT }} HARBOR_ROBOT_ACCOUNT: ${{ vars.HARBOR_ROBOT_ACCOUNT }} @@ -58,88 +58,88 @@ jobs: APP_ENV=stage BACKEND_LOG_LEVEL=debug CLIENT_LOG_DEBUG=true - WORKS_ADMIN_API_BASE_URL=${{ vars.WORKS_ADMIN_API_BASE_URL }} - WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.WORKS_ADMIN_OAUTH_TOKEN_URL }} + WORKS_ADMIN_API_BASE_URL=${{ vars.STG_WORKS_ADMIN_API_BASE_URL }} + WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.STG_WORKS_ADMIN_OAUTH_TOKEN_URL }} TZ=Asia/Seoul IDP_PROVIDER=ory # DB & Clickhouse - DB_PORT=${{ vars.DB_PORT }} - CLICKHOUSE_PORT_HTTP=${{ vars.CLICKHOUSE_PORT_HTTP }} - CLICKHOUSE_PORT_NATIVE=${{ vars.CLICKHOUSE_PORT_NATIVE }} - CLICKHOUSE_HOST=${{ vars.CLICKHOUSE_HOST }} - CLICKHOUSE_USER=${{ vars.CLICKHOUSE_USER }} - CLICKHOUSE_PASSWORD=${{ secrets.CLICKHOUSE_PASSWORD }} + DB_PORT=${{ vars.STG_DB_PORT }} + CLICKHOUSE_PORT_HTTP=${{ vars.STG_CLICKHOUSE_PORT_HTTP }} + CLICKHOUSE_PORT_NATIVE=${{ vars.STG_CLICKHOUSE_PORT_NATIVE }} + CLICKHOUSE_HOST=${{ vars.STG_CLICKHOUSE_HOST }} + CLICKHOUSE_USER=${{ vars.STG_CLICKHOUSE_USER }} + CLICKHOUSE_PASSWORD=${{ secrets.STG_CLICKHOUSE_PASSWORD }} - BACKEND_PORT=${{ vars.BACKEND_PORT }} - ADMINFRONT_PORT=${{ vars.ADMINFRONT_PORT }} - DEVFRONT_PORT=${{ vars.DEVFRONT_PORT }} - ORGFRONT_PORT=${{ vars.ORGFRONT_PORT }} - USERFRONT_PORT=${{ vars.USERFRONT_PORT }} + BACKEND_PORT=${{ vars.STG_BACKEND_PORT }} + ADMINFRONT_PORT=${{ vars.STG_ADMINFRONT_PORT }} + DEVFRONT_PORT=${{ vars.STG_DEVFRONT_PORT }} + ORGFRONT_PORT=${{ vars.STG_ORGFRONT_PORT }} + USERFRONT_PORT=${{ vars.STG_USERFRONT_PORT }} - OATHKEEPER_API_URL=${{ vars.OATHKEEPER_API_URL }} + OATHKEEPER_API_URL=${{ vars.STG_OATHKEEPER_API_URL }} - DB_USER=${{ vars.DB_USER }} + DB_USER=${{ vars.STG_DB_USER }} DB_PASSWORD=${{ secrets.STG_DB_PASSWORD }} - DB_NAME=${{ vars.DB_NAME }} + DB_NAME=${{ vars.STG_DB_NAME }} COOKIE_SECRET=${{ secrets.STG_COOKIE_SECRET }} JWT_SECRET=${{ secrets.STG_JWT_SECRET }} - REDIS_ADDR=${{ vars.REDIS_ADDR }} - CORS_ALLOWED_ORIGINS=${{ vars.CORS_ALLOWED_ORIGINS }} + REDIS_ADDR=${{ vars.STG_REDIS_ADDR }} + CORS_ALLOWED_ORIGINS=${{ vars.STG_CORS_ALLOWED_ORIGINS }} AUDIT_WORKER_COUNT=5 AUDIT_QUEUE_SIZE=2000 - PROFILE_CACHE_TTL=${{ vars.PROFILE_CACHE_TTL }} - NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }} - NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }} - NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }} - NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }} - AWS_REGION=${{ vars.AWS_REGION }} - 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 }} - ADMIN_EMAIL=${{ vars.ADMIN_EMAIL }} + PROFILE_CACHE_TTL=${{ vars.STG_PROFILE_CACHE_TTL }} + NAVER_CLOUD_ACCESS_KEY=${{ secrets.STG_NAVER_CLOUD_ACCESS_KEY }} + NAVER_CLOUD_SECRET_KEY=${{ secrets.STG_NAVER_CLOUD_SECRET_KEY }} + NAVER_CLOUD_SERVICE_ID=${{ vars.STG_NAVER_CLOUD_SERVICE_ID }} + NAVER_SENDER_PHONE_NUMBER=${{ vars.STG_NAVER_SENDER_PHONE_NUMBER }} + AWS_REGION=${{ vars.STG_AWS_REGION }} + AWS_ACCESS_KEY_ID=${{ vars.STG_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY=${{ secrets.STG_AWS_SECRET_ACCESS_KEY }} + AWS_SES_SENDER=${{ vars.STG_AWS_SES_SENDER }} + ADMIN_EMAIL=${{ vars.STG_ADMIN_EMAIL }} ADMIN_PASSWORD=${{ secrets.STG_ADMIN_PASSWORD }} - USERFRONT_URL=${{ vars.USERFRONT_URL }} - ORGFRONT_URL=${{ vars.ORGFRONT_URL }} - BACKEND_PUBLIC_URL=${{ vars.BACKEND_URL }} - BACKEND_URL=${{ vars.BACKEND_URL }} - OATHKEEPER_PUBLIC_URL=${{ vars.OATHKEEPER_PUBLIC_URL }} - ORY_POSTGRES_TAG=${{ vars.ORY_POSTGRES_TAG }} - ORY_POSTGRES_USER=${{ vars.ORY_POSTGRES_USER }} + USERFRONT_URL=${{ vars.STG_USERFRONT_URL }} + ORGFRONT_URL=${{ vars.STG_ORGFRONT_URL }} + BACKEND_PUBLIC_URL=${{ vars.STG_BACKEND_URL }} + BACKEND_URL=${{ vars.STG_BACKEND_URL }} + OATHKEEPER_PUBLIC_URL=${{ vars.STG_OATHKEEPER_PUBLIC_URL }} + ORY_POSTGRES_TAG=${{ vars.STG_ORY_POSTGRES_TAG }} + ORY_POSTGRES_USER=${{ vars.STG_ORY_POSTGRES_USER }} ORY_POSTGRES_PASSWORD=${{ secrets.STG_ORY_POSTGRES_PASSWORD }} - ORY_POSTGRES_DB=${{ vars.ORY_POSTGRES_DB }} - KRATOS_DB=${{ vars.KRATOS_DB }} - HYDRA_DB=${{ vars.HYDRA_DB }} - KETO_DB=${{ vars.KETO_DB }} - KRATOS_VERSION=${{ vars.KRATOS_VERSION }} - KRATOS_UI_NODE_VERSION=${{ vars.KRATOS_UI_NODE_VERSION }} - HYDRA_VERSION=${{ vars.HYDRA_VERSION }} - KETO_VERSION=${{ vars.KETO_VERSION }} - ORY_SDK_URL=${{ vars.ORY_SDK_URL }} - KRATOS_PUBLIC_URL=${{ vars.KRATOS_PUBLIC_URL }} - KRATOS_ADMIN_URL=${{ vars.KRATOS_ADMIN_URL }} - KRATOS_BROWSER_URL=${{ vars.KRATOS_BROWSER_URL }} - KRATOS_UI_URL=${{ vars.KRATOS_UI_URL }} - HYDRA_ADMIN_URL=${{ vars.HYDRA_ADMIN_URL }} - HYDRA_PUBLIC_URL=${{ vars.HYDRA_PUBLIC_URL }} - HYDRA_REFRESH_TOKEN_TTL=${{ vars.HYDRA_REFRESH_TOKEN_TTL }} - JWKS_URL=${{ vars.JWKS_URL }} - OATHKEEPER_VERSION=${{ vars.OATHKEEPER_VERSION }} - OATHKEEPER_UID=${{ vars.OATHKEEPER_UID }} - OATHKEEPER_GID=${{ vars.OATHKEEPER_GID }} - OATHKEEPER_HEALTH_URL=${{ vars.OATHKEEPER_HEALTH_URL }} - OATHKEEPER_HEALTH_INTERVAL_SECONDS=${{ vars.OATHKEEPER_HEALTH_INTERVAL_SECONDS }} - OATHKEEPER_HEALTH_TIMEOUT_SECONDS=${{ vars.OATHKEEPER_HEALTH_TIMEOUT_SECONDS }} - OATHKEEPER_HEALTH_ENABLED=${{ vars.OATHKEEPER_HEALTH_ENABLED }} - CSRF_COOKIE_NAME=${{ vars.CSRF_COOKIE_NAME }} + ORY_POSTGRES_DB=${{ vars.STG_ORY_POSTGRES_DB }} + KRATOS_DB=${{ vars.STG_KRATOS_DB }} + HYDRA_DB=${{ vars.STG_HYDRA_DB }} + KETO_DB=${{ vars.STG_KETO_DB }} + KRATOS_VERSION=${{ vars.STG_KRATOS_VERSION }} + KRATOS_UI_NODE_VERSION=${{ vars.STG_KRATOS_UI_NODE_VERSION }} + HYDRA_VERSION=${{ vars.STG_HYDRA_VERSION }} + KETO_VERSION=${{ vars.STG_KETO_VERSION }} + ORY_SDK_URL=${{ vars.STG_ORY_SDK_URL }} + KRATOS_PUBLIC_URL=${{ vars.STG_KRATOS_PUBLIC_URL }} + KRATOS_ADMIN_URL=${{ vars.STG_KRATOS_ADMIN_URL }} + KRATOS_BROWSER_URL=${{ vars.STG_KRATOS_BROWSER_URL }} + KRATOS_UI_URL=${{ vars.STG_KRATOS_UI_URL }} + HYDRA_ADMIN_URL=${{ vars.STG_HYDRA_ADMIN_URL }} + HYDRA_PUBLIC_URL=${{ vars.STG_HYDRA_PUBLIC_URL }} + HYDRA_REFRESH_TOKEN_TTL=${{ vars.STG_HYDRA_REFRESH_TOKEN_TTL }} + JWKS_URL=${{ vars.STG_JWKS_URL }} + OATHKEEPER_VERSION=${{ vars.STG_OATHKEEPER_VERSION }} + OATHKEEPER_UID=${{ vars.STG_OATHKEEPER_UID }} + OATHKEEPER_GID=${{ vars.STG_OATHKEEPER_GID }} + OATHKEEPER_HEALTH_URL=${{ vars.STG_OATHKEEPER_HEALTH_URL }} + OATHKEEPER_HEALTH_INTERVAL_SECONDS=${{ vars.STG_OATHKEEPER_HEALTH_INTERVAL_SECONDS }} + OATHKEEPER_HEALTH_TIMEOUT_SECONDS=${{ vars.STG_OATHKEEPER_HEALTH_TIMEOUT_SECONDS }} + OATHKEEPER_HEALTH_ENABLED=${{ vars.STG_OATHKEEPER_HEALTH_ENABLED }} + CSRF_COOKIE_NAME=${{ vars.STG_CSRF_COOKIE_NAME }} CSRF_COOKIE_SECRET=${{ secrets.STG_CSRF_COOKIE_SECRET }} - 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 }} - # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} + VITE_OIDC_AUTHORITY=${{ vars.STG_VITE_OIDC_AUTHORITY }} + ADMINFRONT_CALLBACK_URLS=${{ vars.STG_ADMINFRONT_CALLBACK_URLS }} + DEVFRONT_CALLBACK_URLS=${{ vars.STG_DEVFRONT_CALLBACK_URLS }} + ORGFRONT_CALLBACK_URLS=${{ vars.STG_ORGFRONT_CALLBACK_URLS }} + # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.STG_OATHKEEPER_INTROSPECT_CLIENT_ID }} # OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }} EOF diff --git a/Makefile b/Makefile index a1a85749..510a7e12 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ DOCKER_IMAGE_REF ?= WORKS_DOCKER_COMMIT_CONTAINER ?= WORKS_DOCKER_IMAGE_ARCHIVE_DIR ?= /tmp/baron-sso-docker-image-upload -.PHONY: help build-auth-config validate-auth-config verify-auth-config render-ory-config up up-all up-infra up-ory up-app up-backend ensure-networks ensure-infra ensure-ory ensure-restore-containers up-dev up-front-dev dev dev-debug down drop down-app down-backend down-infra down-ory check-infra ps logs-infra logs-ory logs-app backup-tools-build dump restore dump-verify restore-verify dump-list restore-plan upload-cloud works-drive-refresh-token dump-upload-cloud docker-image-upload-works +.PHONY: help build-auth-config validate-auth-config verify-auth-config render-ory-config up up-all up-infra up-ory up-app up-backend ensure-networks ensure-infra ensure-ory ensure-restore-containers up-dev up-front-dev dev dev-debug down drop down-app down-backend down-infra down-ory check-infra ps logs-infra logs-ory logs-app backup-tools-build dump restore dump-verify restore-verify dump-list restore-plan upload-cloud works-drive-refresh-token dump-upload-cloud docker-image-upload-works docker-image-verify-works help: ## 생성된 타깃과 옵션 목록 표시 @printf "Usage:\n make [OPTION=value ...]\n\n" @@ -367,6 +367,9 @@ dump-upload-cloud: dump upload-cloud ## 백업 덤프 생성 후 클라우드 docker-image-upload-works: ## Docker 이미지를 WORKS Shared Drive archive로 업로드 WORKS_DOCKER_COMMIT_CONTAINER="$(WORKS_DOCKER_COMMIT_CONTAINER)" DOCKER_IMAGE_REF="$(DOCKER_IMAGE_REF)" WORKS_DOCKER_IMAGE_ARCHIVE_DIR="$(WORKS_DOCKER_IMAGE_ARCHIVE_DIR)" scripts/docker-image/upload_works_drive.sh +docker-image-verify-works: ## WORKS Shared Drive Docker image archive 검증 + WORKS_DOCKER_VERIFY_LOAD="$(WORKS_DOCKER_VERIFY_LOAD)" WORKS_DOCKER_IMAGE_ARCHIVE_DIR="$(WORKS_DOCKER_IMAGE_ARCHIVE_DIR)" scripts/docker-image/verify_archive.sh "$(WORKS_DOCKER_IMAGE_ARCHIVE_DIR)" + # --- 로컬 통합 코드 체크 --- PLAYWRIGHT_BROWSERS_PATH := $(HOME)/.cache/ms-playwright PLAYWRIGHT_CHROMIUM_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/chromium-1208/INSTALLATION_COMPLETE diff --git a/docs/works-drive-docker-image-archive.md b/docs/works-drive-docker-image-archive.md index 052ebde7..aaa67ffd 100644 --- a/docs/works-drive-docker-image-archive.md +++ b/docs/works-drive-docker-image-archive.md @@ -10,6 +10,35 @@ WORKS Drive는 Docker Registry HTTP API v2 backend로 직접 사용하지 않는 - 작은 규모의 프로덕션 배포 이미지 이관 - `docker load` 기반 오프라인 배포 +Harbor는 이 흐름의 1차 이미지 저장소다. Gitea Actions의 publish workflow가 `reg.hmac.kr/baron_sso/:` 형태로 이미지를 push하고, staging/production deploy workflow는 같은 image tag를 Harbor에서 pull한다. WORKS Drive는 같은 이미지를 별도로 보관하는 복구용 archive이며, staging/prod가 평상시에 직접 pull하는 대상이 아니다. + +## 현재 Gitea Actions 설정 상태 + +2026-06-19 기준 repo Actions 설정에서 Harbor 변수/시크릿은 등록되어 있다. + +- `HARBOR_ENDPOINT=https://reg.hmac.kr` +- `HARBOR_HOSTNAME=reg.hmac.kr` +- `HARBOR_ROBOT_ACCOUNT=robot$namecard_sso` +- secret `HARBOR_ROBOT_KEY` + +Docker image archive 업로드 단계는 `.gitea/workflows/production_image_publish.yml`의 `Upload pushed images to WORKS Drive archive` step에서 실행된다. 단, 이 step은 다음 조건을 만족할 때만 실행된다. + +```yaml +if: ${{ vars.WORKS_DRIVE_DOCKER_IMAGE_ARCHIVE_ENABLED == 'true' }} +``` + +현재 repo Actions 설정에는 Docker image archive용 WORKS Drive 변수/시크릿이 등록되어 있지 않다. 업로드를 켜려면 최소한 다음 값을 등록해야 한다. + +- variable `WORKS_DRIVE_DOCKER_IMAGE_ARCHIVE_ENABLED=true` +- variable `WORKS_SHAREDRIVE_DOCKER_IMAGE_DIR=docker-build-image` +- variable `WORKS_DRIVE_SHARED_DRIVE_ID` +- 선택 variable `WORKS_DRIVE_PARENT_FILE_ID` +- secret `WORKS_DRIVE_OAUTH_CLIENT_ID` +- secret `WORKS_DRIVE_OAUTH_CLIENT_SECRET` +- secret `WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT` +- secret `WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY` +- refresh token 방식을 쓸 경우 secret `WORKS_DRIVE_OAUTH_REFRESH_TOKEN` + ## 저장 구조 기본 최상위 디렉터리는 다음 환경 변수로 지정한다. @@ -92,6 +121,63 @@ DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \ scripts/docker-image/upload_works_drive.sh ``` +dry-run도 실제 `docker save`, `zstd`, checksum, manifest 생성을 수행한다. WORKS Drive API 호출만 생략하므로 업로드 전 산출물 검증에 사용할 수 있다. + +## 다운로드 및 복원 검증 CLI + +WORKS Drive에서 다음 세 파일을 같은 로컬 디렉터리로 내려받은 뒤 검증한다. + +```text +image.tar.zst +image.tar.zst.sha256 +manifest.json +``` + +checksum, manifest, zstd stream 무결성만 확인하려면 다음을 실행한다. + +```bash +scripts/docker-image/verify_archive.sh /path/to/downloaded/archive +``` + +`make` 타깃을 사용할 수도 있다. + +```bash +make docker-image-verify-works \ + WORKS_DOCKER_IMAGE_ARCHIVE_DIR=/path/to/downloaded/archive +``` + +실제 Docker image load까지 검증하려면 다음을 실행한다. + +```bash +WORKS_DOCKER_VERIFY_LOAD=true \ +scripts/docker-image/verify_archive.sh /path/to/downloaded/archive +``` + +검증은 다음 조건을 모두 확인한다. + +- `image.tar.zst.sha256` checksum 성공 +- `manifest.json`의 `schema_version=1`, `format=docker-save-zstd` +- manifest의 archive 파일명, sha256, size와 실제 파일 일치 +- `zstd -t` 무결성 성공 +- 선택적으로 `docker load` 성공 + +현재 repo에는 WORKS Drive API에서 파일을 자동 다운로드하는 CLI는 없다. 따라서 자동 다운로드 스크립트를 만들기 전까지는 WORKS Drive UI 또는 운영자가 승인한 API 도구로 세 파일을 내려받고, 위 검증 CLI로 복원 가능성을 확인한다. + +API로 다운로드할 때는 대상 archive 폴더의 children을 조회해 각 파일의 `fileId`를 얻은 뒤 다음 endpoint를 호출한다. + +```text +GET /v1.0/sharedrives//files//download +``` + +검증 결과 이 endpoint는 `302 Location`을 반환한다. `curl -L`만 사용하면 리다이렉트 대상 요청에 인증 헤더가 유지되지 않아 `UNAUTHORIZED` JSON이 파일로 저장될 수 있다. 자동화할 때는 리다이렉트 대상에도 인증 헤더를 유지하도록 처리해야 한다. `curl` 기준으로는 다음 형태를 사용한다. + +```bash +curl -sS -L --location-trusted \ + -H "Authorization: Bearer " \ + -o image.tar.zst \ + "https://www.worksapis.com/v1.0/sharedrives//files//download" +``` + smoke 검증에는 Alpine 계열보다 운영 환경과 libc/패키지 계열 차이가 적은 Debian slim 계열을 사용한다. ```bash @@ -103,6 +189,22 @@ DOCKER_IMAGE_REF=registry.example/baron_sso/works-smoke:works-test-ab12 \ scripts/docker-image/upload_works_drive.sh ``` +로컬 smoke 검증 예시는 다음과 같다. + +```bash +WORKS_DRIVE_DRY_RUN=true \ +WORKS_DOCKER_IMAGE_ARCHIVE_DIR=/tmp/baron-sso-works-verify-smoke \ +DOCKER_IMAGE_REF=alpine:latest \ +scripts/docker-image/upload_works_drive.sh + +scripts/docker-image/verify_archive.sh \ + /tmp/baron-sso-works-verify-smoke/alpine/latest + +WORKS_DOCKER_VERIFY_LOAD=true \ +scripts/docker-image/verify_archive.sh \ + /tmp/baron-sso-works-verify-smoke/alpine/latest +``` + ## Staging/Production 계약 Action에서 `dev` 브랜치를 checkout한 뒤 한 번만 이미지를 빌드하고 immutable `image_tag`를 계산한다. staging과 production은 같은 image_tag를 입력받아 같은 registry image를 pull한다. diff --git a/scripts/deploy/build_image_deploy_bundle.sh b/scripts/deploy/build_image_deploy_bundle.sh index 6a74c0b9..2ab6da17 100755 --- a/scripts/deploy/build_image_deploy_bundle.sh +++ b/scripts/deploy/build_image_deploy_bundle.sh @@ -23,8 +23,8 @@ host_from_url() { require_env IMAGE_TAG require_env IMAGE_DEPLOY_ENV -require_env IMAGE_DEPLOY_PORT_PREFIX require_env IMAGE_DEPLOY_PUBLIC_URL +require_env IMAGE_DEPLOY_BACKEND_PORT require_env ADMINFRONT_URL require_env DEVFRONT_URL require_env ORGFRONT_URL @@ -50,26 +50,43 @@ case "$IMAGE_DEPLOY_ENV" in esac instance_name="${IMAGE_DEPLOY_INSTANCE_NAME:-$default_instance_name}" +port_prefix="${IMAGE_DEPLOY_PORT_PREFIX:-${IMAGE_DEPLOY_BACKEND_PORT%???}}" +[[ -n "$port_prefix" ]] || die "IMAGE_DEPLOY_PORT_PREFIX is empty and could not be derived from IMAGE_DEPLOY_BACKEND_PORT." bundle_dir="${IMAGE_DEPLOY_BUNDLE_DIR:-$PWD/${instance_name}-image-deploy-bundle}" bundle_file="${IMAGE_DEPLOY_BUNDLE_FILE:-$PWD/${instance_name}-image-deploy-bundle.tgz}" compose_template="${IMAGE_DEPLOY_COMPOSE_TEMPLATE:-$repo_root/deploy/templates/docker-compose.images.yaml}" rm -rf "$bundle_dir" -TARGET_DIR="$bundle_dir" bash "$repo_root/deploy/create-instance.sh" "$instance_name" "$IMAGE_DEPLOY_PORT_PREFIX" +TARGET_DIR="$bundle_dir" bash "$repo_root/deploy/create-instance.sh" "$instance_name" "$port_prefix" cp "$compose_template" "$bundle_dir/docker-compose.yml" +sed "s/{{BACKEND_PORT}}/${IMAGE_DEPLOY_BACKEND_PORT}/g" \ + "$repo_root/deploy/templates/gateway/nginx.conf" >"$bundle_dir/gateway/nginx.conf" +sed "s/{{BACKEND_PORT}}/${IMAGE_DEPLOY_BACKEND_PORT}/g" \ + "$repo_root/deploy/templates/ory/oathkeeper/rules.json" >"$bundle_dir/ory/templates/oathkeeper/rules.json" +cp "$bundle_dir/ory/templates/oathkeeper/rules.json" "$bundle_dir/ory/templates/oathkeeper/rules.stage.json" +cp "$bundle_dir/ory/templates/oathkeeper/rules.json" "$bundle_dir/ory/templates/oathkeeper/rules.prod.json" +cp "$bundle_dir/ory/templates/oathkeeper/rules.json" "$bundle_dir/ory/templates/oathkeeper/rules.active.json" + public_host="$(host_from_url "$IMAGE_DEPLOY_PUBLIC_URL")" admin_host="$(host_from_url "$ADMINFRONT_URL")" dev_host="$(host_from_url "$DEVFRONT_URL")" org_host="$(host_from_url "$ORGFRONT_URL")" +backend_log_level="${IMAGE_DEPLOY_BACKEND_LOG_LEVEL:-${BACKEND_LOG_LEVEL:-info}}" +client_log_debug="${IMAGE_DEPLOY_CLIENT_LOG_DEBUG:-${CLIENT_LOG_DEBUG:-false}}" +backend_public_url="${IMAGE_DEPLOY_BACKEND_PUBLIC_URL:-${BACKEND_PUBLIC_URL:-${BACKEND_URL:-$IMAGE_DEPLOY_PUBLIC_URL}}}" +backend_url="${IMAGE_DEPLOY_BACKEND_URL:-${BACKEND_URL:-$backend_public_url}}" cat >"$bundle_dir/.env" </dev/null +) + +actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" +[[ "$actual_sha256" == "$manifest_sha256" ]] || backup_die "manifest sha256 mismatch: expected=$manifest_sha256 actual=$actual_sha256" + +actual_size="$(stat -c '%s' "$archive_file")" +[[ "$actual_size" == "$manifest_size" ]] || backup_die "manifest size mismatch: expected=$manifest_size actual=$actual_size" + +backup_log "Testing zstd archive integrity" +zstd -q -t "$archive_file" + +if [[ "$verify_load" == "true" ]]; then + backup_require_command docker + backup_log "Loading Docker image from archive" + zstd -q -d -c "$archive_file" | docker load +fi + +backup_log "Docker image archive verification passed: $archive_dir" diff --git a/scripts/test_deploy_workflow_env_prefixes.sh b/scripts/test_deploy_workflow_env_prefixes.sh new file mode 100644 index 00000000..d1208650 --- /dev/null +++ b/scripts/test_deploy_workflow_env_prefixes.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env sh +set -eu + +fail_if_contains() { + file="$1" + pattern="$2" + if grep -Fq "$pattern" "$file"; then + echo "forbidden pattern in $file: $pattern" >&2 + exit 1 + fi +} + +assert_contains() { + file="$1" + pattern="$2" + if ! grep -Fq "$pattern" "$file"; then + echo "missing pattern in $file: $pattern" >&2 + exit 1 + fi +} + +staging_workflows=" +.gitea/workflows/staging_code_pull.yml +.gitea/workflows/staging_release.yml +.gitea/workflows/staging_image_deploy.yml +" + +production_workflows=" +.gitea/workflows/production_release.yml +.gitea/workflows/production_image_deploy.yml +" + +for workflow in $staging_workflows; do + assert_contains "$workflow" "vars.STG_" + assert_contains "$workflow" "secrets.STG_" + fail_if_contains "$workflow" "vars.STAGE_" + fail_if_contains "$workflow" "secrets.STAGE_" + for name in \ + USERFRONT_URL ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL VITE_OIDC_AUTHORITY \ + BACKEND_URL BACKEND_LOG_LEVEL CLIENT_LOG_DEBUG PROFILE_CACHE_TTL CORS_ALLOWED_ORIGINS \ + WORKS_ADMIN_API_BASE_URL WORKS_ADMIN_OAUTH_TOKEN_URL NAVER_CLOUD_ACCESS_KEY \ + NAVER_CLOUD_SERVICE_ID NAVER_SENDER_PHONE_NUMBER AWS_REGION AWS_ACCESS_KEY_ID \ + AWS_SES_SENDER CLICKHOUSE_HOST CLICKHOUSE_USER DB_PORT DB_USER DB_NAME REDIS_ADDR + do + fail_if_contains "$workflow" "vars.$name" + done + for name in AWS_SECRET_ACCESS_KEY NAVER_CLOUD_SECRET_KEY CLICKHOUSE_PASSWORD STAGE_SSH_PRIVATE_KEY; do + fail_if_contains "$workflow" "secrets.$name" + done +done + +for workflow in $production_workflows; do + assert_contains "$workflow" "vars.PROD_" + assert_contains "$workflow" "secrets.PROD_" + for name in \ + ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL VITE_OIDC_AUTHORITY BACKEND_LOG_LEVEL \ + CLIENT_LOG_DEBUG PROFILE_CACHE_TTL CORS_ALLOWED_ORIGINS WORKS_ADMIN_API_BASE_URL \ + WORKS_ADMIN_OAUTH_TOKEN_URL NAVER_CLOUD_ACCESS_KEY NAVER_CLOUD_SERVICE_ID \ + NAVER_SENDER_PHONE_NUMBER AWS_REGION AWS_ACCESS_KEY_ID AWS_SES_SENDER \ + CLICKHOUSE_HOST CLICKHOUSE_USER ADMINFRONT_PORT DEVFRONT_PORT ORGFRONT_PORT + do + fail_if_contains "$workflow" "vars.$name" + done + for name in AWS_SECRET_ACCESS_KEY NAVER_CLOUD_SECRET_KEY CLICKHOUSE_PASSWORD; do + fail_if_contains "$workflow" "secrets.$name" + done +done + +echo "deploy workflow env prefix checks passed" diff --git a/scripts/test_docker_image_archive_verify.sh b/scripts/test_docker_image_archive_verify.sh new file mode 100755 index 00000000..1b7e5600 --- /dev/null +++ b/scripts/test_docker_image_archive_verify.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env sh +set -eu + +repo_root="$(cd "$(dirname "$0")/.." && pwd)" +verify_script="$repo_root/scripts/docker-image/verify_archive.sh" +tmp_root="$(mktemp -d)" + +cleanup() { + rm -rf "$tmp_root" +} +trap cleanup EXIT INT TERM + +require_command() { + command -v "$1" >/dev/null 2>&1 || { + echo "required command not found: $1" >&2 + exit 1 + } +} + +assert_fails() { + if "$@" >/dev/null 2>&1; then + echo "expected command to fail: $*" >&2 + exit 1 + fi +} + +require_command jq +require_command sha256sum +require_command zstd + +artifact_dir="$tmp_root/baron_sso/backend/v1.2606.ab12" +mkdir -p "$artifact_dir" + +printf 'docker image archive smoke\n' >"$artifact_dir/image.tar" +zstd -q -f -o "$artifact_dir/image.tar.zst" "$artifact_dir/image.tar" +rm -f "$artifact_dir/image.tar" + +archive_sha256="$(sha256sum "$artifact_dir/image.tar.zst" | awk '{print $1}')" +archive_size="$(wc -c <"$artifact_dir/image.tar.zst" | tr -d ' ')" +printf '%s image.tar.zst\n' "$archive_sha256" >"$artifact_dir/image.tar.zst.sha256" + +jq -n \ + --arg remotePath "docker-build-image/baron_sso/backend/v1.2606.ab12" \ + --arg archiveSha256 "$archive_sha256" \ + --argjson archiveSize "$archive_size" \ + '{ + schema_version: 1, + format: "docker-save-zstd", + image_ref: "reg.hmac.kr/baron_sso/backend:v1.2606.ab12", + repository: "baron_sso/backend", + tag: "v1.2606.ab12", + remote_path: $remotePath, + archive: { + file_name: "image.tar.zst", + size_bytes: $archiveSize, + sha256: $archiveSha256 + } + }' >"$artifact_dir/manifest.json" + +"$verify_script" "$artifact_dir" >/dev/null + +bad_checksum_dir="$tmp_root/bad-checksum" +cp -R "$artifact_dir" "$bad_checksum_dir" +printf '0000000000000000000000000000000000000000000000000000000000000000 image.tar.zst\n' >"$bad_checksum_dir/image.tar.zst.sha256" +assert_fails "$verify_script" "$bad_checksum_dir" + +bad_manifest_dir="$tmp_root/bad-manifest" +cp -R "$artifact_dir" "$bad_manifest_dir" +jq '.archive.sha256 = "1111111111111111111111111111111111111111111111111111111111111111"' \ + "$bad_manifest_dir/manifest.json" >"$bad_manifest_dir/manifest.json.tmp" +mv "$bad_manifest_dir/manifest.json.tmp" "$bad_manifest_dir/manifest.json" +assert_fails "$verify_script" "$bad_manifest_dir" + +bad_archive_dir="$tmp_root/bad-archive" +cp -R "$artifact_dir" "$bad_archive_dir" +printf 'not a zstd stream\n' >"$bad_archive_dir/image.tar.zst" +sha256sum "$bad_archive_dir/image.tar.zst" | awk '{print $1 " image.tar.zst"}' >"$bad_archive_dir/image.tar.zst.sha256" +assert_fails "$verify_script" "$bad_archive_dir" + +echo "docker image archive verification checks passed" diff --git a/scripts/test_image_deploy_env_override.sh b/scripts/test_image_deploy_env_override.sh new file mode 100644 index 00000000..bb552f90 --- /dev/null +++ b/scripts/test_image_deploy_env_override.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +tmp_root="$(mktemp -d)" + +cleanup() { + rm -rf "$tmp_root" +} +trap cleanup EXIT INT TERM + +assert_contains() { + local file="$1" + local pattern="$2" + if ! grep -Fq "$pattern" "$file"; then + printf 'missing pattern in %s: %s\n' "$file" "$pattern" >&2 + exit 1 + fi +} + +assert_env_value() { + local file="$1" + local key="$2" + local expected="$3" + if ! grep -Fxq "${key}=${expected}" "$file"; then + printf 'missing env value in %s: %s=%s\n' "$file" "$key" "$expected" >&2 + exit 1 + fi +} + +for workflow in \ + "$repo_root/.gitea/workflows/staging_image_deploy.yml" \ + "$repo_root/.gitea/workflows/production_image_deploy.yml" +do + assert_contains "$workflow" "IMAGE_DEPLOY_BACKEND_LOG_LEVEL:" + assert_contains "$workflow" "IMAGE_DEPLOY_CLIENT_LOG_DEBUG:" + assert_contains "$workflow" "WORKS_ADMIN_API_BASE_URL:" + assert_contains "$workflow" "WORKS_ADMIN_OAUTH_TOKEN_URL:" + assert_contains "$workflow" "PROFILE_CACHE_TTL:" + assert_contains "$workflow" "NAVER_CLOUD_ACCESS_KEY:" + assert_contains "$workflow" "NAVER_CLOUD_SECRET_KEY:" + assert_contains "$workflow" "NAVER_CLOUD_SERVICE_ID:" + assert_contains "$workflow" "NAVER_SENDER_PHONE_NUMBER:" + assert_contains "$workflow" "AWS_REGION:" + assert_contains "$workflow" "AWS_ACCESS_KEY_ID:" + assert_contains "$workflow" "AWS_SECRET_ACCESS_KEY:" + assert_contains "$workflow" "AWS_SES_SENDER:" + assert_contains "$workflow" "CORS_ALLOWED_ORIGINS:" + assert_contains "$workflow" "OATHKEEPER_API_URL:" +done + +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "secrets.STG_SSH_PRIVATE_KEY" +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "vars.STG_USERFRONT_URL" +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "vars.STG_BACKEND_URL" +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "vars.STG_WORKS_ADMIN_API_BASE_URL" +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "secrets.STG_NAVER_CLOUD_SECRET_KEY" +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "secrets.STG_AWS_SECRET_ACCESS_KEY" +assert_contains "$repo_root/.gitea/workflows/staging_image_deploy.yml" "secrets.STG_CLICKHOUSE_PASSWORD" + +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "secrets.PROD_SSH_PRIVATE_KEY" +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "vars.PROD_FRONTEND_URL" +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "vars.PROD_BACKEND_URL" +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "vars.PROD_WORKS_ADMIN_API_BASE_URL" +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "secrets.PROD_NAVER_CLOUD_SECRET_KEY" +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "secrets.PROD_AWS_SECRET_ACCESS_KEY" +assert_contains "$repo_root/.gitea/workflows/production_image_deploy.yml" "secrets.PROD_CLICKHOUSE_PASSWORD" + +bundle_dir="$tmp_root/stage-image-deploy-bundle" +bundle_file="$tmp_root/stage-image-deploy-bundle.tgz" + +( + cd "$repo_root" + IMAGE_TAG=v1.2606.ab12 \ + IMAGE_DEPLOY_ENV=stage \ + IMAGE_DEPLOY_INSTANCE_NAME=stage-test \ + IMAGE_DEPLOY_PORT_PREFIX=19 \ + IMAGE_DEPLOY_PUBLIC_URL=https://sso.example.test \ + IMAGE_DEPLOY_COMPOSE_TEMPLATE=deploy/templates/docker-compose.images.yaml \ + IMAGE_DEPLOY_BUNDLE_DIR="$bundle_dir" \ + IMAGE_DEPLOY_BUNDLE_FILE="$bundle_file" \ + ADMINFRONT_URL=https://sadmin.example.test \ + DEVFRONT_URL=https://sdev.example.test \ + ORGFRONT_URL=https://sorg.example.test \ + VITE_OIDC_AUTHORITY=https://sso.example.test/oidc \ + IMAGE_DEPLOY_DB_PORT=15432 \ + IMAGE_DEPLOY_REDIS_PORT=16379 \ + IMAGE_DEPLOY_CLICKHOUSE_PORT_HTTP=18123 \ + IMAGE_DEPLOY_CLICKHOUSE_PORT_NATIVE=19000 \ + IMAGE_DEPLOY_BACKEND_PORT=13000 \ + IMAGE_DEPLOY_FRONTEND_PORT=15000 \ + ADMINFRONT_PORT=15173 \ + DEVFRONT_PORT=15174 \ + ORGFRONT_PORT=15175 \ + IMAGE_DEPLOY_OATHKEEPER_PROXY_PORT=14455 \ + IMAGE_DEPLOY_DOMAIN_SUFFIX=example.test \ + ADMINFRONT_CALLBACK_URLS=https://sadmin.example.test/auth/callback \ + DEVFRONT_CALLBACK_URLS=https://sdev.example.test/auth/callback \ + ORGFRONT_CALLBACK_URLS=https://sorg.example.test/auth/callback \ + HYDRA_REFRESH_TOKEN_TTL=720h \ + ORY_POSTGRES_USER=ory \ + ORY_POSTGRES_DB=ory \ + KRATOS_DB=ory_kratos \ + HYDRA_DB=ory_hydra \ + KETO_DB=ory_keto \ + KRATOS_VERSION=v26.2.0-distroless \ + HYDRA_VERSION=v26.2.0-distroless \ + KETO_VERSION=v26.2.0-distroless \ + OATHKEEPER_VERSION=v26.2.0 \ + ORY_POSTGRES_TAG=17-trixie \ + OATHKEEPER_UID=1001 \ + OATHKEEPER_GID=1001 \ + OATHKEEPER_INTROSPECT_CLIENT_ID=oathkeeper-introspect \ + ADMIN_EMAIL=admin@example.test \ + HARBOR_HOSTNAME=reg.example.test \ + BACKEND_IMAGE_NAME=reg.example.test/baron_sso/backend \ + USERFRONT_IMAGE_NAME=reg.example.test/baron_sso/userfront \ + ADMINFRONT_IMAGE_NAME=reg.example.test/baron_sso/adminfront \ + DEVFRONT_IMAGE_NAME=reg.example.test/baron_sso/devfront \ + ORGFRONT_IMAGE_NAME=reg.example.test/baron_sso/orgfront \ + IMAGE_DEPLOY_DB_PASSWORD=db-secret \ + IMAGE_DEPLOY_ORY_POSTGRES_PASSWORD=ory-secret \ + IMAGE_DEPLOY_OATHKEEPER_INTROSPECT_CLIENT_SECRET=oathkeeper-secret \ + IMAGE_DEPLOY_CLICKHOUSE_PASSWORD=clickhouse-secret \ + IMAGE_DEPLOY_COOKIE_SECRET=cookie-secret \ + IMAGE_DEPLOY_JWT_SECRET=jwt-secret \ + IMAGE_DEPLOY_CSRF_COOKIE_SECRET=csrf-secret \ + IMAGE_DEPLOY_ADMIN_PASSWORD=admin-secret \ + IMAGE_DEPLOY_BACKEND_LOG_LEVEL=debug \ + IMAGE_DEPLOY_CLIENT_LOG_DEBUG=true \ + WORKS_ADMIN_API_BASE_URL=https://works-api.example.test \ + WORKS_ADMIN_OAUTH_TOKEN_URL=https://works-auth.example.test/token \ + PROFILE_CACHE_TTL=30m \ + NAVER_CLOUD_ACCESS_KEY=naver-access \ + NAVER_CLOUD_SECRET_KEY=naver-secret \ + NAVER_CLOUD_SERVICE_ID=naver-service \ + NAVER_SENDER_PHONE_NUMBER=021234567 \ + AWS_REGION=ap-northeast-2 \ + AWS_ACCESS_KEY_ID=aws-access \ + AWS_SECRET_ACCESS_KEY=aws-secret \ + AWS_SES_SENDER=support@example.test \ + CORS_ALLOWED_ORIGINS=https://sso.example.test \ + OATHKEEPER_API_URL=http://oathkeeper:4456 \ + CLICKHOUSE_HOST=clickhouse \ + CLICKHOUSE_USER=baron \ + scripts/deploy/build_image_deploy_bundle.sh >/dev/null +) + +env_file="$bundle_dir/.env" +assert_env_value "$env_file" "BACKEND_LOG_LEVEL" "debug" +assert_env_value "$env_file" "CLIENT_LOG_DEBUG" "true" +assert_env_value "$env_file" "WORKS_ADMIN_API_BASE_URL" "https://works-api.example.test" +assert_env_value "$env_file" "WORKS_ADMIN_OAUTH_TOKEN_URL" "https://works-auth.example.test/token" +assert_env_value "$env_file" "PROFILE_CACHE_TTL" "30m" +assert_env_value "$env_file" "NAVER_CLOUD_ACCESS_KEY" "naver-access" +assert_env_value "$env_file" "NAVER_CLOUD_SECRET_KEY" "naver-secret" +assert_env_value "$env_file" "NAVER_CLOUD_SERVICE_ID" "naver-service" +assert_env_value "$env_file" "NAVER_SENDER_PHONE_NUMBER" "021234567" +assert_env_value "$env_file" "AWS_REGION" "ap-northeast-2" +assert_env_value "$env_file" "AWS_ACCESS_KEY_ID" "aws-access" +assert_env_value "$env_file" "AWS_SECRET_ACCESS_KEY" "aws-secret" +assert_env_value "$env_file" "AWS_SES_SENDER" "support@example.test" +assert_env_value "$env_file" "CORS_ALLOWED_ORIGINS" "https://sso.example.test" +assert_env_value "$env_file" "BACKEND_PUBLIC_URL" "https://sso.example.test" +assert_env_value "$env_file" "BACKEND_URL" "https://sso.example.test" +assert_env_value "$env_file" "OATHKEEPER_API_URL" "http://oathkeeper:4456" +assert_env_value "$env_file" "CLICKHOUSE_HOST" "clickhouse" +assert_env_value "$env_file" "CLICKHOUSE_USER" "baron" + +echo "image deploy env override checks passed" diff --git a/scripts/test_staging_workflow_env.sh b/scripts/test_staging_workflow_env.sh index 1ee66d47..926c61c7 100644 --- a/scripts/test_staging_workflow_env.sh +++ b/scripts/test_staging_workflow_env.sh @@ -17,9 +17,12 @@ do assert_contains "$workflow" "APP_ENV=stage" assert_contains "$workflow" "BACKEND_LOG_LEVEL=debug" assert_contains "$workflow" "CLIENT_LOG_DEBUG=true" - assert_contains "$workflow" 'WORKS_ADMIN_API_BASE_URL=${{ vars.WORKS_ADMIN_API_BASE_URL }}' - assert_contains "$workflow" 'WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.WORKS_ADMIN_OAUTH_TOKEN_URL }}' - assert_contains "$workflow" 'BACKEND_PUBLIC_URL=${{ vars.BACKEND_URL }}' + assert_contains "$workflow" 'WORKS_ADMIN_API_BASE_URL=${{ vars.STG_WORKS_ADMIN_API_BASE_URL }}' + assert_contains "$workflow" 'WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.STG_WORKS_ADMIN_OAUTH_TOKEN_URL }}' + assert_contains "$workflow" 'BACKEND_PUBLIC_URL=${{ vars.STG_BACKEND_URL }}' + assert_contains "$workflow" 'NAVER_CLOUD_SECRET_KEY=${{ secrets.STG_NAVER_CLOUD_SECRET_KEY }}' + assert_contains "$workflow" 'AWS_SECRET_ACCESS_KEY=${{ secrets.STG_AWS_SECRET_ACCESS_KEY }}' + assert_contains "$workflow" 'CLICKHOUSE_PASSWORD=${{ secrets.STG_CLICKHOUSE_PASSWORD }}' done assert_contains ".gitea/workflows/staging_release.yml" "scp adminfront/seed-tenant.csv" diff --git a/userfront/lib/features/auth/presentation/forgot_password_screen.dart b/userfront/lib/features/auth/presentation/forgot_password_screen.dart index 9b72a485..5f9de98c 100644 --- a/userfront/lib/features/auth/presentation/forgot_password_screen.dart +++ b/userfront/lib/features/auth/presentation/forgot_password_screen.dart @@ -55,7 +55,8 @@ class _ForgotPasswordScreenState extends State { context.pop(); } else { final isLoggedIn = AuthTokenStore.hasToken(); - final localeCode = extractLocaleFromPath(Uri.base) ?? resolvePreferredLocaleCode(); + final localeCode = + extractLocaleFromPath(Uri.base) ?? resolvePreferredLocaleCode(); if (isLoggedIn) { context.go('/$localeCode/profile'); } else { diff --git a/userfront/pubspec.lock b/userfront/pubspec.lock index 8b6fff8c..b23d80a9 100644 --- a/userfront/pubspec.lock +++ b/userfront/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" cli_config: dependency: transitive description: @@ -268,14 +268,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" leak_tracker: dependency: transitive description: @@ -328,18 +320,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -661,26 +653,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.30.0" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.10" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.16" toml: dependency: "direct main" description: