Stabilize auth flow and profile images
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# scripts 폴더 안내
|
||||
|
||||
이 폴더는 `tdc114plus` 앱 초기 설정, Flutter 실행 보조, 테스트 자동화용 shell script를 모아둔 위치이다.
|
||||
|
||||
## 파일별 용도
|
||||
|
||||
- `api-smoke.sh`: `TDC114_API_BASE` 대상 Baron SSO 연동 API에 최소 smoke test를 수행한다. `TDC114_SMOKE_PHONE`이 있으면 `TDC114_SMOKE_AUTH_FLOW`에 따라 legacy `phone-login` 호환 경로 또는 headless 링크 흐름을 확인한다.
|
||||
- `smoke.env.example`: authenticated smoke용 로컬 env 예시 파일이다. `scripts/.env.smoke.local`로 복사해 `TDC114_API_BASE`, `TDC114_SMOKE_PHONE`을 넣어 사용할 수 있다.
|
||||
- `smoke.staging.env.example`: staging Baron SSO 검증용 env 예시 파일이다. `scripts/.env.staging.local`로 복사해 `TDC114_API_BASE`, `TDC114_SMOKE_PHONE`과 optional expected label 값을 넣어 사용할 수 있다.
|
||||
- `android-device.env.example`: Android 공기계 USB 테스트용 env 예시 파일이다. `scripts/.env.android-device.local`로 복사해 `adb reverse` 기준 `TDC114_API_BASE=http://127.0.0.1:5000`, `TDC114_AUTH_API_BASE=http://127.0.0.1:5001`을 사용한다.
|
||||
- `android-device.staging.env.example`: Android 공기계/실폰의 USB 없는 staging 직접 로그인 테스트용 env 예시 파일이다. `scripts/.env.android-device.staging.local`로 복사해 공개 staging HTTPS base URL을 넣어 사용한다.
|
||||
- `android-device.production.env.example`: Android 공기계/실폰의 USB 없는 production 직접 로그인 테스트용 env 예시 파일이다. `scripts/.env.android-device.production.local`로 복사해 공개 production HTTPS base URL을 넣어 사용한다.
|
||||
- `bootstrap-baron-api-env.sh`: Baron SSO API worktree의 `.env.sample`을 바탕으로 로컬 smoke용 `.env`를 생성하고 localhost/알림 비활성 override를 덧붙인다.
|
||||
- `check-baron-api-env.sh`: Baron SSO API worktree의 `.env`, compose, config, Docker runtime 준비 상태를 점검해 실제 API smoke 가능 여부를 빠르게 확인한다.
|
||||
- `check-android-device-env.sh`: Android 공기계 USB 연결, Windows ADB server 공유, 선택적 `adb reverse` 상태를 점검한다.
|
||||
- `host-adb.sh`: Docker를 거치지 않고 WSL 호스트의 Linux adb로 Windows ADB server에 접속한다. ADB 로그/스크린샷/reverse 조회처럼 Flutter가 필요 없는 작업에 사용한다.
|
||||
- `install-host-adb-from-docker.sh`: Docker Flutter 이미지에서 Linux adb 바이너리를 `.tools/android-platform-tools/adb`로 1회 추출한다. 이후 ADB 조회 작업은 Docker 권한 없이 `host-adb.sh`로 처리한다.
|
||||
- `startup.sh`: Baron SSO runtime 기동, 상태 확인, API smoke 재시도를 자동화한다. `check-baron-api-env.sh`에 warning이 남거나 `api-smoke.sh`가 실패하면 비정상 종료한다.
|
||||
- `shutdown.sh`: 대상 helper 프로세스 종료, compose 로그 수집, Baron SSO runtime 중지, 선택적 컨테이너 정리와 권한 복구를 자동화한다.
|
||||
- `bootstrap.sh`: 로컬 Flutter SDK가 설치된 환경에서 `app/` 프로젝트의 `flutter pub get`을 실행한다.
|
||||
- `create-flutter-app.sh`: `app/` Flutter 프로젝트가 없을 때 Android/iOS 기반 앱 골격을 생성한다.
|
||||
- `flutter-docker.sh`: Docker Flutter 이미지를 이용해 `app/` 디렉터리에서 Flutter 명령을 실행한다. `.android-adb/`, `.docker-cache/flutter/` 아래 ADB key, Gradle, pub, Android SDK 일부 cache를 재사용한다.
|
||||
- `manual-postlogin-run.sh`: Android target에 `dart-define`을 포함한 `flutter run` 경로로 앱을 띄운다. 기본 정책은 Baron SSO Hosted Login + PKCE 진입이며, 필요 시에만 legacy local `phone-login` bootstrap으로 post-login 상태를 seed 한다.
|
||||
- `format-dart.sh`: Docker Flutter 환경에서 Dart 코드 포맷을 적용한다.
|
||||
- `generate-release-report.sh`: 현재 git 상태와 릴리스 점검 항목을 기반으로 report를 생성한다.
|
||||
- `integration_tests.sh`: `app/integration_test/` smoke test를 실행한다. Android target 선행 확인은 실기기 기준이 기본이며 emulator는 fallback이다. `TDC114_API_BASE`와 `TDC114_SMOKE_PHONE`을 넘기면 실제 API 로그인 smoke까지 확장할 수 있다.
|
||||
- `mock-server.sh`: 향후 mock API 서버 제어용 scaffold 스크립트이다. 현재는 `status`, `stop`만 간단히 동작하고 `start`는 미구현 안내를 출력한다.
|
||||
- `perf_smoke.sh`: 현재 앱/테스트 파일 수와 기본 상태를 빠르게 확인하는 경량 smoke 스크립트이다.
|
||||
- `quality-gate.sh`: Docker Flutter 환경에서 `flutter analyze`와 `flutter test`를 순서대로 실행한다.
|
||||
- `save-snapshots.sh`: widget/integration snapshot 산출물이 있으면 지정한 report 경로로 복사해 보관한다.
|
||||
- `test.sh`: 로컬 Flutter SDK가 설치된 환경에서 `flutter analyze`와 `flutter test`를 실행한다.
|
||||
- `redteam/run_all.sh`: 현재 앱 범위에서는 미사용인 AI/LLM redteam 자동화용 scaffold 스크립트이다.
|
||||
|
||||
## 참고
|
||||
|
||||
- 테스트 자동화 운영 기준은 `docs/guide_tdc114plus_script_automation_plan_2026-07-02.md`를 따른다.
|
||||
- Docker 기반 Flutter 검증이 기본 경로이므로 일반적인 앱 변경 후에는 `format-dart.sh`, `quality-gate.sh`를 우선 사용한다.
|
||||
- `api-smoke.sh`, `integration_tests.sh`는 `scripts/.env.smoke.local` 파일이 있으면 자동으로 읽는다.
|
||||
- staging 검증 시에는 `TDC114_SMOKE_ENV_FILE=scripts/.env.staging.local` 방식으로 env 파일을 명시해 실행한다.
|
||||
- Android 공기계 USB 검증 시에는 `TDC114_SMOKE_ENV_FILE=scripts/.env.android-device.local`과 `TDC114_FLUTTER_DEVICE_ID=<PHYSICAL_DEVICE_ID>`를 명시한다.
|
||||
- ADB 로그 조회, `devices`, `reverse --list`, `screencap` 등 Flutter가 필요 없는 ADB 작업은 `flutter-docker.sh adb` 대신 `ADB_SERVER_SOCKET=tcp:172.21.128.1:5037 ./scripts/host-adb.sh ...`를 우선 사용한다.
|
||||
- USB 없는 staging 직접 로그인 검증 시에는 `TDC114_SMOKE_ENV_FILE=scripts/.env.android-device.staging.local`과 `TDC114_FLUTTER_DEVICE_ID=<PHYSICAL_DEVICE_ID>`를 명시한다.
|
||||
- 로그인만 staging, 직원/조직 정보만 production으로 분리해야 할 경우 `TDC114_AUTH_API_BASE`, `TDC114_DIRECTORY_API_BASE`, `TDC114_ORGANIZATION_API_BASE`를 추가로 지정한다. 값을 비우면 모두 `TDC114_API_BASE`를 따른다.
|
||||
- `startup.sh`, `shutdown.sh`는 테스트나 다른 worktree 재사용을 위해 `BARON_SSO_WORKTREE`, `TDC114_LOG_BASE`, `DOCKER_BIN` 같은 환경 변수 override를 지원한다.
|
||||
- 업무 시작 절차에서는 앱 직접 App Link callback 테스트 서버를 더 이상 기동하지 않는다. 현재 로그인은 `tdc114plus-auth`의 `/api/v1/auth/link/init`, `/api/v1/auth/link/poll` 경로를 사용한다.
|
||||
- Windows Android Studio emulator + WSL/Docker Flutter 운영 기준은 `docs/policy_android_studio_wsl_adb_2026-07-03.md`를 따른다.
|
||||
- Android 앱 설치/실행 재발 방지 기준은 `docs/policy_android_app_install_execution_2026-07-06.md`를 따른다.
|
||||
- 반복 지연 대응 정리는 `docs/troubleshooting/flutter-docker-repeated-delay-countermeasures-2026-07-03.md`를 참고한다.
|
||||
- `TDC114_SESSION_BOOTSTRAP_MODE=legacy-phone-login`은 옛 호환 점검용 예외 경로다. 신규 정책 기본값은 mock seed 또는 실제 Hosted Login + PKCE 검증이다.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copy this file to scripts/.env.android-device.local for physical Android device checks.
|
||||
|
||||
# Physical devices cannot use the Android emulator-only 10.0.2.2 host alias.
|
||||
# Preferred path:
|
||||
# adb reverse tcp:5000 tcp:5000
|
||||
# adb reverse tcp:5001 tcp:5001
|
||||
# TDC114_API_BASE=http://127.0.0.1:5000
|
||||
# TDC114_AUTH_API_BASE=http://127.0.0.1:5001
|
||||
#
|
||||
# Fallback path:
|
||||
# TDC114_API_BASE=http://<PC_LAN_IP>:5000
|
||||
# TDC114_AUTH_API_BASE=http://<PC_LAN_IP>:5001
|
||||
TDC114_API_BASE=http://127.0.0.1:5000
|
||||
TDC114_AUTH_API_BASE=http://127.0.0.1:5001
|
||||
|
||||
# Use a test phone number that exists in the local Baron SSO test data.
|
||||
TDC114_SMOKE_PHONE=01012345678
|
||||
TDC114_SMOKE_ASSUME_LOGGED_IN=1
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copy this file to scripts/.env.android-device.production.local for USB-free production-style tests.
|
||||
|
||||
# The app talks directly to the public Baron production API over HTTPS.
|
||||
# No adb reverse is required in this mode.
|
||||
TDC114_API_BASE=https://production.example.com
|
||||
|
||||
# Skip local phone-login bootstrap and open the normal in-app login flow.
|
||||
TDC114_SKIP_SESSION_BOOTSTRAP=true
|
||||
|
||||
# Optional hints for smoke/integration helpers.
|
||||
TDC114_SMOKE_AUTH_FLOW=link
|
||||
# TDC114_SMOKE_PHONE=010xxxxxxxx
|
||||
# TDC114_SMOKE_EXPECTED_NAME=홍길동
|
||||
# TDC114_SMOKE_EXPECTED_TENANT_LABEL=IS3
|
||||
|
||||
# Optional split mode:
|
||||
# Keep everything on production unless a temporary login/data split is required.
|
||||
# TDC114_AUTH_API_BASE=https://production.example.com
|
||||
# TDC114_DIRECTORY_API_BASE=https://production.example.com
|
||||
# TDC114_ORGANIZATION_API_BASE=https://production.example.com
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copy this file to scripts/.env.android-device.staging.local for USB-free staging login tests.
|
||||
|
||||
# The app talks directly to the public Baron staging API over HTTPS.
|
||||
# No adb reverse is required in this mode.
|
||||
TDC114_API_BASE=https://sso.hmac.kr
|
||||
TDC114_ORG_CONTEXT_API_BASE=https://sadmin.hmac.kr
|
||||
TDC114_ORG_CONTEXT_TENANT_SLUG=hanmac-family
|
||||
TDC114_BARON_KEY_ID=replace-with-staging-key-id
|
||||
TDC114_BARON_KEY_SECRET=replace-with-staging-key-secret
|
||||
|
||||
# Skip local phone-login bootstrap and open the normal in-app login flow.
|
||||
TDC114_SKIP_SESSION_BOOTSTRAP=true
|
||||
|
||||
# Optional hints for smoke/integration helpers.
|
||||
TDC114_SMOKE_AUTH_FLOW=link
|
||||
TDC114_SMOKE_PHONE=010xxxxxxxx
|
||||
# TDC114_SMOKE_EXPECTED_NAME=홍길동
|
||||
# TDC114_SMOKE_EXPECTED_TENANT_LABEL=IS3
|
||||
|
||||
# Optional split mode:
|
||||
# Login and org-context stay on staging by default.
|
||||
# TDC114_AUTH_API_BASE=https://sso.hmac.kr
|
||||
# TDC114_DIRECTORY_API_BASE=https://sadmin.hmac.kr
|
||||
# TDC114_ORGANIZATION_API_BASE=https://sadmin.hmac.kr
|
||||
Executable
+145
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LOCAL_ENV_FILE="${TDC114_SMOKE_ENV_FILE:-$ROOT_DIR/scripts/.env.smoke.local}"
|
||||
|
||||
if [ -f "$LOCAL_ENV_FILE" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$LOCAL_ENV_FILE"
|
||||
fi
|
||||
|
||||
if [ -z "${TDC114_API_BASE:-}" ]; then
|
||||
echo "TDC114_API_BASE is required for API smoke tests." >&2
|
||||
echo "Example: TDC114_API_BASE=http://127.0.0.1:5000 $0" >&2
|
||||
if [ -f "$LOCAL_ENV_FILE" ]; then
|
||||
echo "Loaded local smoke env from $LOCAL_ENV_FILE" >&2
|
||||
fi
|
||||
exit 64
|
||||
fi
|
||||
|
||||
if [ "${TDC114_API_BASE:-}" = "https://staging.example.com" ]; then
|
||||
echo "TDC114_API_BASE still points to the example placeholder. Update $LOCAL_ENV_FILE first." >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
BASE_URL="${TDC114_API_BASE%/}"
|
||||
AUTH_FLOW="${TDC114_SMOKE_AUTH_FLOW:-phone-login}"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
request() {
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
local body="${3:-}"
|
||||
local token="${4:-}"
|
||||
local output_file="$TMP_DIR/response"
|
||||
local headers=(-H "Accept: application/json")
|
||||
if [ -n "$token" ]; then
|
||||
headers+=(-H "Authorization: Bearer $token")
|
||||
fi
|
||||
if [ -n "$body" ]; then
|
||||
headers+=(-H "Content-Type: application/json")
|
||||
curl -sS --max-time 10 -o "$output_file" -w "%{http_code}" \
|
||||
-X "$method" "${headers[@]}" --data "$body" "$BASE_URL$path"
|
||||
else
|
||||
curl -sS --max-time 10 -o "$output_file" -w "%{http_code}" \
|
||||
-X "$method" "${headers[@]}" "$BASE_URL$path"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_status() {
|
||||
local name="$1"
|
||||
local actual="$2"
|
||||
shift 2
|
||||
for expected in "$@"; do
|
||||
if [ "$actual" = "$expected" ]; then
|
||||
echo "PASS $name: HTTP $actual"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "FAIL $name: HTTP $actual" >&2
|
||||
echo "Response body:" >&2
|
||||
sed -n '1,40p' "$TMP_DIR/response" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
extract_json_string() {
|
||||
local key="$1"
|
||||
sed -n "s/.*\"$key\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" "$TMP_DIR/response" | head -n 1
|
||||
}
|
||||
|
||||
echo "tdc114plus API smoke: $BASE_URL"
|
||||
|
||||
status="$(request GET "/api/v1/tdc114plus/directory/employees")"
|
||||
assert_status "unauthorized directory guard" "$status" 401 403
|
||||
|
||||
status="$(request POST "/api/v1/tdc114plus/auth/phone-login" '{"phoneNumber":"123","device":{"platform":"smoke","appVersion":"0.1.0","deviceName":"api-smoke"}}')"
|
||||
assert_status "invalid phone-login validation" "$status" 400
|
||||
|
||||
status="$(request POST "/api/v1/tdc114plus/auth/link/init" '{"phoneNumber":"123","device":{"platform":"smoke","appVersion":"0.1.0","deviceName":"api-smoke"}}')"
|
||||
assert_status "invalid link-init validation" "$status" 400
|
||||
|
||||
status="$(request POST "/api/v1/tdc114plus/auth/link/poll" '{"pendingRef":"api-smoke-missing-ref"}')"
|
||||
assert_status "link-poll expired pendingRef" "$status" 200
|
||||
|
||||
if [ "${TDC114_SKIP_AUTH_SMOKE:-false}" = "true" ]; then
|
||||
echo "SKIP authenticated smoke: TDC114_SKIP_AUTH_SMOKE=true"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "${TDC114_SMOKE_PHONE:-}" ]; then
|
||||
echo "SKIP authenticated smoke: TDC114_SMOKE_PHONE is not set"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$AUTH_FLOW" = "link" ]; then
|
||||
login_body="$(printf '{"phoneNumber":"%s","device":{"platform":"smoke","appVersion":"0.1.0","deviceName":"api-smoke"}}' "$TDC114_SMOKE_PHONE")"
|
||||
status="$(request POST "/api/v1/tdc114plus/auth/link/init" "$login_body")"
|
||||
assert_status "link-init success" "$status" 200
|
||||
|
||||
pending_ref="$(extract_json_string "pendingRef")"
|
||||
if [ -z "$pending_ref" ]; then
|
||||
echo "FAIL link-init success: pendingRef is missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status="$(request POST "/api/v1/tdc114plus/auth/link/poll" "$(printf '{"pendingRef":"%s"}' "$pending_ref")")"
|
||||
assert_status "link-poll pending" "$status" 200
|
||||
|
||||
poll_code="$(extract_json_string "code")"
|
||||
if [ -n "$poll_code" ] && [ "$poll_code" != "authorization_pending" ] && [ "$poll_code" != "slow_down" ]; then
|
||||
echo "FAIL link-poll pending: unexpected code=$poll_code" >&2
|
||||
sed -n '1,40p' "$TMP_DIR/response" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS staging link smoke: init succeeded and poll is pending"
|
||||
echo "NOTE manual approval is still required to complete end-to-end login."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$AUTH_FLOW" != "phone-login" ]; then
|
||||
echo "Unknown TDC114_SMOKE_AUTH_FLOW=$AUTH_FLOW" >&2
|
||||
echo "Expected one of: phone-login, link" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
login_body="$(printf '{"phoneNumber":"%s","device":{"platform":"smoke","appVersion":"0.1.0","deviceName":"api-smoke"}}' "$TDC114_SMOKE_PHONE")"
|
||||
status="$(request POST "/api/v1/tdc114plus/auth/phone-login" "$login_body")"
|
||||
assert_status "phone-login success" "$status" 200
|
||||
|
||||
token="$(extract_json_string "token")"
|
||||
if [ -z "$token" ]; then
|
||||
echo "FAIL phone-login success: token is missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status="$(request GET "/api/v1/tdc114plus/directory/employees?limit=10" "" "$token")"
|
||||
assert_status "employee list" "$status" 200
|
||||
|
||||
status="$(request GET "/api/v1/tdc114plus/organization/tenants" "" "$token")"
|
||||
assert_status "tenant list" "$status" 200
|
||||
|
||||
status="$(request GET "/api/v1/tdc114plus/organization/orgchart" "" "$token")"
|
||||
assert_status "orgchart" "$status" 200
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BARON_DIR="${BARON_SSO_WORKTREE:-/home/ubuntu/workspace/baron-sso-tdc114plus-api}"
|
||||
SOURCE_FILE="$BARON_DIR/.env.sample"
|
||||
TARGET_FILE="$BARON_DIR/.env"
|
||||
|
||||
if [ ! -f "$SOURCE_FILE" ]; then
|
||||
echo "Baron SSO env sample not found: $SOURCE_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$TARGET_FILE" ]; then
|
||||
echo "Target env file already exists: $TARGET_FILE" >&2
|
||||
echo "Remove or rename it first if you want to regenerate from the sample." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$SOURCE_FILE" "$TARGET_FILE"
|
||||
|
||||
cat >>"$TARGET_FILE" <<'EOF'
|
||||
|
||||
# --- tdc114plus local smoke overrides ---
|
||||
# Added by scripts/bootstrap-baron-api-env.sh
|
||||
APP_ENV=development
|
||||
BACKEND_LOG_LEVEL=debug
|
||||
CLIENT_LOG_DEBUG=true
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:5000
|
||||
USERFRONT_URL=http://localhost:5000
|
||||
BACKEND_PUBLIC_URL=http://localhost:5000
|
||||
BACKEND_URL=http://localhost:5000
|
||||
OATHKEEPER_PUBLIC_URL=http://localhost:5000
|
||||
ADMINFRONT_URL=http://localhost:5173
|
||||
DEVFRONT_URL=http://localhost:5174
|
||||
ORGFRONT_URL=http://localhost:5175
|
||||
KRATOS_UI_URL=http://localhost:5000
|
||||
KRATOS_BROWSER_URL=http://localhost:5000/auth
|
||||
WORKS_ADMIN_OAUTH_TOKEN_URL=https://auth.worksmobile.com/oauth2/v2.0/token
|
||||
WORKSMOBILE_ENABLED=false
|
||||
WORKSMOBILE_RELAY_ENABLED=false
|
||||
LOGIN_LOGOUT_SMS_ENABLED=false
|
||||
LOGIN_LOGOUT_NAVER_ENABLED=false
|
||||
VITE_CLIENT_LOG_DEBUG=false
|
||||
DEV_SERVER_WATCH_POLLING=true
|
||||
EOF
|
||||
|
||||
echo "Created $TARGET_FILE from $SOURCE_FILE"
|
||||
echo "A local smoke override block was appended to prefer localhost URLs and disable optional external notifications."
|
||||
echo "Next steps:"
|
||||
echo " 1. Review $TARGET_FILE and replace any placeholder secrets if your local backend requires real values."
|
||||
echo " 2. Run ./scripts/check-baron-api-env.sh"
|
||||
echo " 3. If runtime looks ready, run TDC114_API_BASE=http://127.0.0.1:5000 ./scripts/api-smoke.sh"
|
||||
Executable
+248
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
FLUTTER_DOCKER_SCRIPT="$ROOT_DIR/scripts/flutter-docker.sh"
|
||||
HOST_ADB_SCRIPT="${TDC114_HOST_ADB_SCRIPT:-$ROOT_DIR/scripts/host-adb.sh}"
|
||||
LOCAL_ENV_FILE="${TDC114_SMOKE_ENV_FILE:-$ROOT_DIR/scripts/.env.android-device.local}"
|
||||
|
||||
if [ -f "$LOCAL_ENV_FILE" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$LOCAL_ENV_FILE"
|
||||
fi
|
||||
|
||||
ADB_SOCKET="${ADB_SERVER_SOCKET:-}"
|
||||
DEVICE_ID="${TDC114_FLUTTER_DEVICE_ID:-}"
|
||||
API_BASE="${TDC114_API_BASE:-}"
|
||||
AUTH_API_BASE="${TDC114_AUTH_API_BASE:-}"
|
||||
REVERSE_PORT="${TDC114_ANDROID_REVERSE_PORT:-5000}"
|
||||
AUTH_REVERSE_PORT="${TDC114_AUTH_ANDROID_REVERSE_PORT:-5001}"
|
||||
|
||||
is_reverse_mode() {
|
||||
[ "$API_BASE" = "http://127.0.0.1:${REVERSE_PORT}" ]
|
||||
}
|
||||
|
||||
is_auth_reverse_mode() {
|
||||
[ "$AUTH_API_BASE" = "http://127.0.0.1:${AUTH_REVERSE_PORT}" ]
|
||||
}
|
||||
|
||||
print_header() {
|
||||
echo "[android-device-preflight]"
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
echo "ADB server socket: $ADB_SOCKET"
|
||||
else
|
||||
echo "ADB server socket: default container adb server"
|
||||
fi
|
||||
if [ -n "$DEVICE_ID" ]; then
|
||||
echo "Target device id: $DEVICE_ID"
|
||||
else
|
||||
echo "Target device id: first physical device in adb devices"
|
||||
fi
|
||||
if [ -n "$API_BASE" ]; then
|
||||
echo "TDC114_API_BASE: $API_BASE"
|
||||
if is_reverse_mode; then
|
||||
echo "Connection mode: local adb-reverse"
|
||||
else
|
||||
echo "Connection mode: direct HTTPS/LAN"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$AUTH_API_BASE" ]; then
|
||||
echo "TDC114_AUTH_API_BASE: $AUTH_API_BASE"
|
||||
if is_auth_reverse_mode; then
|
||||
echo "Auth connection mode: local adb-reverse"
|
||||
else
|
||||
echo "Auth connection mode: direct HTTPS/LAN"
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
fail_with_help() {
|
||||
local reason="$1"
|
||||
print_header
|
||||
echo "FAIL $reason"
|
||||
echo
|
||||
echo "Windows-side checklist:"
|
||||
echo "1. Connect the Android phone by USB."
|
||||
echo "2. Enable Developer options > USB debugging."
|
||||
echo "3. Accept the RSA debugging prompt on the phone."
|
||||
echo "4. In Windows PowerShell, run adb.exe devices and confirm the phone is device."
|
||||
echo "5. If using local API through adb reverse, run adb reverse tcp:${REVERSE_PORT} tcp:${REVERSE_PORT} from the same ADB server path."
|
||||
if is_auth_reverse_mode; then
|
||||
echo " Also run adb reverse tcp:${AUTH_REVERSE_PORT} tcp:${AUTH_REVERSE_PORT} for the local auth server."
|
||||
fi
|
||||
echo "6. If using direct staging/production HTTPS mode, confirm TDC114_API_BASE points to the public app API host."
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
echo "7. If WSL reports connection refused to ${ADB_SOCKET}, recreate the Windows 5037 portproxy and firewall rule first."
|
||||
fi
|
||||
echo
|
||||
echo "Recommended WSL command:"
|
||||
echo " ADB_SERVER_SOCKET=tcp:<WINDOWS_HOST_IP>:5037 \\"
|
||||
echo " TDC114_FLUTTER_DEVICE_ID=<PHYSICAL_DEVICE_ID> \\"
|
||||
echo " TDC114_SMOKE_ENV_FILE=scripts/.env.android-device.local \\"
|
||||
echo " ./scripts/manual-postlogin-run.sh"
|
||||
}
|
||||
|
||||
fail_with_adb_help() {
|
||||
local reason="$1"
|
||||
print_header
|
||||
echo "FAIL $reason"
|
||||
echo
|
||||
echo "WSL host-adb checklist:"
|
||||
echo "1. Confirm $HOST_ADB_SCRIPT exists and is executable."
|
||||
echo "2. If host adb is missing, run ./scripts/install-host-adb-from-docker.sh once."
|
||||
echo "3. Confirm ADB_SERVER_SOCKET points to the Windows ADB server, usually tcp:172.21.128.1:5037."
|
||||
echo "4. After host adb access recovers, rerun this script."
|
||||
echo
|
||||
echo "Recommended WSL command:"
|
||||
echo " ADB_SERVER_SOCKET=tcp:172.21.128.1:5037 ./scripts/host-adb.sh devices"
|
||||
}
|
||||
|
||||
if [ ! -x "$HOST_ADB_SCRIPT" ]; then
|
||||
echo "Missing executable script: $HOST_ADB_SCRIPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DEVICE_ID" == emulator-* ]]; then
|
||||
fail_with_help "TDC114_FLUTTER_DEVICE_ID points to an emulator, not a physical Android device."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
env_args=()
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
env_args+=("ADB_SERVER_SOCKET=$ADB_SOCKET")
|
||||
fi
|
||||
|
||||
set +e
|
||||
if [ "${#env_args[@]}" -gt 0 ]; then
|
||||
OUTPUT="$(env "${env_args[@]}" "$HOST_ADB_SCRIPT" devices 2>&1)"
|
||||
else
|
||||
OUTPUT="$("$HOST_ADB_SCRIPT" devices 2>&1)"
|
||||
fi
|
||||
STATUS=$?
|
||||
set -e
|
||||
|
||||
if [ "$STATUS" -ne 0 ]; then
|
||||
if printf '%s\n' "$OUTPUT" | grep -Fq "Host adb is not available"; then
|
||||
fail_with_adb_help "Host adb is not ready."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit "$STATUS"
|
||||
fi
|
||||
|
||||
fail_with_help "ADB devices command failed."
|
||||
if printf '%s\n' "$OUTPUT" | grep -Fq "Connection refused"; then
|
||||
echo
|
||||
echo "Hint: Windows ADB itself may be alive, but WSL shared access to port 5037 is blocked."
|
||||
echo "Rebuild portproxy: 0.0.0.0:5037 -> 127.0.0.1:5037, then retry."
|
||||
fi
|
||||
if printf '%s\n' "$OUTPUT" | grep -Fq "protocol fault (couldn't read status)"; then
|
||||
echo
|
||||
echo "Hint: Windows port 5037 may be owned by portproxy before adb.exe starts."
|
||||
echo "In Windows Administrator PowerShell: delete the 5037 portproxy, start adb.exe, confirm adb.exe devices, then recreate the portproxy."
|
||||
fi
|
||||
if printf '%s\n' "$OUTPUT" | grep -Fq "failed to read response from server"; then
|
||||
echo
|
||||
echo "Hint: Windows adb.exe may be talking to a stale or non-ADB listener on port 5037."
|
||||
echo "In Windows Administrator PowerShell: inspect netstat -ano | findstr 5037 before restarting adb.exe."
|
||||
fi
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit "$STATUS"
|
||||
fi
|
||||
|
||||
if [ -n "$DEVICE_ID" ]; then
|
||||
DEVICE_PATTERN="^${DEVICE_ID}[[:space:]]+device$"
|
||||
OFFLINE_PATTERN="^${DEVICE_ID}[[:space:]]+offline$"
|
||||
UNAUTHORIZED_PATTERN="^${DEVICE_ID}[[:space:]]+unauthorized$"
|
||||
else
|
||||
DEVICE_PATTERN="^[[:alnum:]_.:-]+[[:space:]]+device$"
|
||||
OFFLINE_PATTERN="^[[:alnum:]_.:-]+[[:space:]]+offline$"
|
||||
UNAUTHORIZED_PATTERN="^[[:alnum:]_.:-]+[[:space:]]+unauthorized$"
|
||||
fi
|
||||
|
||||
if [ -z "$DEVICE_ID" ]; then
|
||||
DETECTED_UNAUTHORIZED_ID="$(printf '%s\n' "$OUTPUT" | awk '$2 == "unauthorized" && $1 !~ /^emulator-/ {print $1; exit}')"
|
||||
if [ -n "$DETECTED_UNAUTHORIZED_ID" ]; then
|
||||
fail_with_help "Physical Android device is unauthorized."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DETECTED_OFFLINE_ID="$(printf '%s\n' "$OUTPUT" | awk '$2 == "offline" && $1 !~ /^emulator-/ {print $1; exit}')"
|
||||
if [ -n "$DETECTED_OFFLINE_ID" ]; then
|
||||
fail_with_help "Physical Android device is offline."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DETECTED_DEVICE_ID="$(printf '%s\n' "$OUTPUT" | awk '$2 == "device" && $1 !~ /^emulator-/ {print $1; exit}')"
|
||||
if [ -z "$DETECTED_DEVICE_ID" ]; then
|
||||
fail_with_help "No usable physical Android device was detected."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
DEVICE_ID="$DETECTED_DEVICE_ID"
|
||||
else
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "$UNAUTHORIZED_PATTERN"; then
|
||||
fail_with_help "Physical Android device is unauthorized."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "$OFFLINE_PATTERN"; then
|
||||
fail_with_help "Physical Android device is offline."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s\n' "$OUTPUT" | grep -Eq "$DEVICE_PATTERN"; then
|
||||
fail_with_help "No usable physical Android device was detected."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$DEVICE_ID" ]; then
|
||||
fail_with_help "No usable physical Android device was detected."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if is_reverse_mode || is_auth_reverse_mode; then
|
||||
reverse_args=("adb")
|
||||
if [ -n "$DEVICE_ID" ]; then
|
||||
reverse_args+=("-s" "$DEVICE_ID")
|
||||
fi
|
||||
reverse_args+=("reverse" "--list")
|
||||
|
||||
set +e
|
||||
if [ "${#env_args[@]}" -gt 0 ]; then
|
||||
REVERSE_OUTPUT="$(env "${env_args[@]}" "$HOST_ADB_SCRIPT" "${reverse_args[@]:1}" 2>&1)"
|
||||
else
|
||||
REVERSE_OUTPUT="$("$HOST_ADB_SCRIPT" "${reverse_args[@]:1}" 2>&1)"
|
||||
fi
|
||||
REVERSE_STATUS=$?
|
||||
set -e
|
||||
|
||||
if [ "$REVERSE_STATUS" -ne 0 ]; then
|
||||
fail_with_help "adb reverse tcp:${REVERSE_PORT} tcp:${REVERSE_PORT} is not active."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
printf '%s\n' "$REVERSE_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
if is_reverse_mode && ! printf '%s\n' "$REVERSE_OUTPUT" | grep -Fq "tcp:${REVERSE_PORT} tcp:${REVERSE_PORT}"; then
|
||||
fail_with_help "adb reverse tcp:${REVERSE_PORT} tcp:${REVERSE_PORT} is not active."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
printf '%s\n' "$REVERSE_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
if is_auth_reverse_mode && ! printf '%s\n' "$REVERSE_OUTPUT" | grep -Fq "tcp:${AUTH_REVERSE_PORT} tcp:${AUTH_REVERSE_PORT}"; then
|
||||
fail_with_help "adb reverse tcp:${AUTH_REVERSE_PORT} tcp:${AUTH_REVERSE_PORT} is not active."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
printf '%s\n' "$REVERSE_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_header
|
||||
echo "PASS Physical Android device is ready for Docker/WSL Flutter checks."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
Executable
+186
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DEFAULT_ANDROID_ADDRESS="${TDC114_STARTUP_ANDROID_ADDRESS:-172.21.128.1:5559}"
|
||||
ADB_ADDRESS="${TDC114_ADB_CONNECT_ADDRESS:-$DEFAULT_ANDROID_ADDRESS}"
|
||||
ADB_HOST="${ADB_ADDRESS%%:*}"
|
||||
ADB_PORT="${ADB_ADDRESS##*:}"
|
||||
ADB_SOCKET="${ADB_SERVER_SOCKET:-}"
|
||||
FLUTTER_DEVICE_ID="${TDC114_FLUTTER_DEVICE_ID:-}"
|
||||
FLUTTER_DOCKER_SCRIPT="$ROOT_DIR/scripts/flutter-docker.sh"
|
||||
|
||||
print_common_header() {
|
||||
echo "[android-preflight]"
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
echo "Target ADB server socket: $ADB_SOCKET"
|
||||
if [ -n "$FLUTTER_DEVICE_ID" ]; then
|
||||
echo "Target Flutter device id: $FLUTTER_DEVICE_ID"
|
||||
fi
|
||||
echo "Policy default: Windows ADB server sharing via ADB_SERVER_SOCKET"
|
||||
else
|
||||
echo "Target emulator address: $ADB_ADDRESS"
|
||||
echo "Fallback direct emulator bridge: $DEFAULT_ANDROID_ADDRESS"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
print_success() {
|
||||
print_common_header
|
||||
echo "PASS Android emulator is reachable from Docker/WSL."
|
||||
echo "Next step: continue runtime startup and later run integration/manual Android checks."
|
||||
}
|
||||
|
||||
print_failure_no_address() {
|
||||
echo "[android-preflight]"
|
||||
echo "FAIL Android emulator address is empty."
|
||||
echo "Set TDC114_ADB_CONNECT_ADDRESS first, for example:"
|
||||
echo " TDC114_ADB_CONNECT_ADDRESS=$DEFAULT_ANDROID_ADDRESS ./scripts/startup.sh --auto --wait=40"
|
||||
}
|
||||
|
||||
print_failure_instructions() {
|
||||
local reason="$1"
|
||||
|
||||
print_common_header
|
||||
echo "FAIL $reason"
|
||||
echo
|
||||
echo "Local PC work required before startup continues:"
|
||||
echo "1. On Windows, open Android Studio."
|
||||
echo "2. In Device Manager, keep only one manual-test emulator running."
|
||||
echo "3. In Windows PowerShell, run adb.exe devices and confirm the emulator is in device state."
|
||||
echo "4. If it is offline or missing there too, restart the emulator first."
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
echo "5. If Windows adb is healthy but WSL still fails, make sure portproxy forwards 5037 -> 127.0.0.1:5037 and the firewall rule for 5037 still exists."
|
||||
else
|
||||
echo "5. If Windows adb is healthy but WSL still fails, make sure portproxy forwards $ADB_PORT -> 127.0.0.1:$ADB_PORT and the firewall rule for that port still exists."
|
||||
fi
|
||||
echo "6. After Windows-side recovery, rerun:"
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
echo " ADB_SERVER_SOCKET=$ADB_SOCKET ./scripts/startup.sh --auto --wait=40"
|
||||
else
|
||||
echo " TDC114_ADB_CONNECT_ADDRESS=$ADB_ADDRESS ./scripts/startup.sh --auto --wait=40"
|
||||
fi
|
||||
echo
|
||||
echo "Minimum Windows-side action I cannot do from here:"
|
||||
echo "- Start or restart the emulator GUI."
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
echo "- If needed, fix Windows adb/portproxy state for port 5037."
|
||||
else
|
||||
echo "- If needed, fix Windows adb/portproxy state for port $ADB_PORT."
|
||||
fi
|
||||
echo
|
||||
echo "What I can keep doing from WSL after that:"
|
||||
echo "- Recheck Docker/WSL visibility."
|
||||
echo "- Continue Baron runtime startup."
|
||||
echo "- Run API smoke and integration commands."
|
||||
}
|
||||
|
||||
if [ -z "$ADB_ADDRESS" ]; then
|
||||
print_failure_no_address
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$FLUTTER_DOCKER_SCRIPT" ]; then
|
||||
echo "Missing executable script: $FLUTTER_DOCKER_SCRIPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$ADB_SOCKET" ]; then
|
||||
set +e
|
||||
OUTPUT="$(ADB_SERVER_SOCKET="$ADB_SOCKET" "$FLUTTER_DOCKER_SCRIPT" adb devices 2>&1)"
|
||||
STATUS=$?
|
||||
set -e
|
||||
|
||||
if [ -n "$FLUTTER_DEVICE_ID" ]; then
|
||||
DEVICE_PATTERN="^${FLUTTER_DEVICE_ID}[[:space:]]+device$"
|
||||
OFFLINE_PATTERN="^${FLUTTER_DEVICE_ID}[[:space:]]+offline$"
|
||||
UNAUTHORIZED_PATTERN="^${FLUTTER_DEVICE_ID}[[:space:]]+unauthorized$"
|
||||
else
|
||||
DEVICE_PATTERN="^emulator-[0-9]+[[:space:]]+device$"
|
||||
OFFLINE_PATTERN="^emulator-[0-9]+[[:space:]]+offline$"
|
||||
UNAUTHORIZED_PATTERN="^emulator-[0-9]+[[:space:]]+unauthorized$"
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "$DEVICE_PATTERN"; then
|
||||
print_success
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "$OFFLINE_PATTERN"; then
|
||||
print_failure_instructions "Android emulator is visible through Windows ADB server but offline."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "$UNAUTHORIZED_PATTERN"; then
|
||||
print_common_header
|
||||
echo "FAIL Android emulator is unauthorized through Windows ADB server."
|
||||
echo
|
||||
echo "Local PC work required before startup continues:"
|
||||
echo "1. Bring the emulator window to front."
|
||||
echo "2. Accept the RSA debugging prompt if it is visible."
|
||||
echo "3. Confirm Windows PowerShell adb.exe devices shows device."
|
||||
echo
|
||||
echo "After approval, rerun:"
|
||||
echo " ADB_SERVER_SOCKET=$ADB_SOCKET ./scripts/startup.sh --auto --wait=40"
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "protocol fault|Connection refused|cannot connect|failed to check server version"; then
|
||||
print_failure_instructions "Docker/WSL could not use the Windows ADB server socket."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_failure_instructions "Android emulator was not detected through the Windows ADB server socket."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit "${STATUS:-1}"
|
||||
fi
|
||||
|
||||
set +e
|
||||
OUTPUT="$(TDC114_ADB_CONNECT_ADDRESS="$ADB_ADDRESS" "$FLUTTER_DOCKER_SCRIPT" adb-devices 2>&1)"
|
||||
STATUS=$?
|
||||
set -e
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "^${ADB_ADDRESS}[[:space:]]+device$"; then
|
||||
print_success
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "^${ADB_ADDRESS}[[:space:]]+offline$"; then
|
||||
print_failure_instructions "Android emulator is visible but offline."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "^${ADB_ADDRESS}[[:space:]]+unauthorized$"; then
|
||||
print_common_header
|
||||
echo "FAIL Android emulator is unauthorized."
|
||||
echo
|
||||
echo "Local PC work required before startup continues:"
|
||||
echo "1. Bring the emulator window to front."
|
||||
echo "2. Accept the RSA debugging prompt if it is visible."
|
||||
echo "3. If no prompt appears, restart the emulator once and check adb.exe devices again on Windows."
|
||||
echo
|
||||
echo "After approval, rerun:"
|
||||
echo " TDC114_ADB_CONNECT_ADDRESS=$ADB_ADDRESS ./scripts/startup.sh --auto --wait=40"
|
||||
echo
|
||||
echo "What I can do after that:"
|
||||
echo "- Retry WSL/Docker device detection."
|
||||
echo "- Continue the rest of startup automatically."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if printf '%s\n' "$OUTPUT" | grep -Eq "failed to connect to ${ADB_HOST}:${ADB_PORT}|Connection refused|cannot connect|protocol fault"; then
|
||||
print_failure_instructions "Docker/WSL could not connect to the Windows emulator bridge."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_failure_instructions "Android emulator was not detected in a usable state."
|
||||
printf '%s\n' "$OUTPUT"
|
||||
exit "${STATUS:-1}"
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BARON_DIR="${BARON_SSO_WORKTREE:-/home/ubuntu/workspace/baron-sso-tdc114plus-api}"
|
||||
|
||||
failures=0
|
||||
warnings=0
|
||||
|
||||
section() {
|
||||
printf '\n[%s]\n' "$1"
|
||||
}
|
||||
|
||||
pass() {
|
||||
printf 'PASS %s\n' "$1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf 'WARN %s\n' "$1"
|
||||
warnings=$((warnings + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'FAIL %s\n' "$1"
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
check_file() {
|
||||
local path="$1"
|
||||
local label="$2"
|
||||
if [ -f "$path" ]; then
|
||||
pass "$label: $path"
|
||||
else
|
||||
fail "$label: missing $path"
|
||||
fi
|
||||
}
|
||||
|
||||
section "worktree"
|
||||
if [ -d "$BARON_DIR/.git" ] || [ -f "$BARON_DIR/.git" ]; then
|
||||
pass "Baron SSO worktree found: $BARON_DIR"
|
||||
else
|
||||
fail "Baron SSO worktree not found: $BARON_DIR"
|
||||
fi
|
||||
|
||||
check_file "$BARON_DIR/docker-compose.yaml" "compose file"
|
||||
check_file "$BARON_DIR/.env.sample" "env sample"
|
||||
|
||||
section "runtime files"
|
||||
if [ -f "$BARON_DIR/.env" ]; then
|
||||
pass "runtime env file exists"
|
||||
else
|
||||
fail "runtime env file missing (.env)"
|
||||
if [ -f "$BARON_DIR/config-restored/env.redacted" ]; then
|
||||
warn "redacted env reference exists at config-restored/env.redacted"
|
||||
fi
|
||||
fi
|
||||
|
||||
if find "$BARON_DIR/config" -maxdepth 1 -type f ! -name '.gitkeep' | grep -q .; then
|
||||
pass "config directory has runtime files"
|
||||
else
|
||||
warn "config directory has no runtime files beyond .gitkeep"
|
||||
fi
|
||||
|
||||
if [ -f "$BARON_DIR/config-restored/compose/docker-compose.yaml" ]; then
|
||||
pass "restored compose reference exists"
|
||||
else
|
||||
warn "restored compose reference is missing"
|
||||
fi
|
||||
|
||||
section "docker"
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
warn "docker command not found"
|
||||
else
|
||||
if docker network inspect baron_net >/dev/null 2>&1; then
|
||||
pass "docker network baron_net exists"
|
||||
else
|
||||
warn "docker network baron_net not found or not accessible"
|
||||
fi
|
||||
|
||||
if docker network inspect ory-net >/dev/null 2>&1; then
|
||||
pass "docker network ory-net exists"
|
||||
else
|
||||
warn "docker network ory-net not found or not accessible"
|
||||
fi
|
||||
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx 'baron_backend'; then
|
||||
pass "baron_backend container is running"
|
||||
else
|
||||
warn "baron_backend container is not running"
|
||||
fi
|
||||
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx 'baron_gateway'; then
|
||||
pass "baron_gateway container is running"
|
||||
else
|
||||
warn "baron_gateway container is not running"
|
||||
fi
|
||||
fi
|
||||
|
||||
section "next steps"
|
||||
if [ "$failures" -gt 0 ]; then
|
||||
printf 'RESULT blocked: %d failure(s), %d warning(s)\n' "$failures" "$warnings"
|
||||
printf 'Hint: create %s/.env from .env.sample and prepare runtime config before API smoke.\n' "$BARON_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'RESULT ready-ish: %d failure(s), %d warning(s)\n' "$failures" "$warnings"
|
||||
printf 'Next command example: TDC114_API_BASE=http://127.0.0.1:5000 ./scripts/api-smoke.sh\n'
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
STATE_DIR="${TDC114_WSL_MAINTENANCE_STATE_DIR:-$ROOT_DIR/.tmp}"
|
||||
STATE_FILE="${TDC114_WSL_MAINTENANCE_STATE_FILE:-$STATE_DIR/wsl-maintenance-last-check.txt}"
|
||||
ANCHOR_DATE="${TDC114_WSL_MAINTENANCE_ANCHOR_DATE:-2026-07-16}"
|
||||
INTERVAL_DAYS="${TDC114_WSL_MAINTENANCE_INTERVAL_DAYS:-14}"
|
||||
MODE="${1:-status}"
|
||||
TODAY="${TDC114_WSL_MAINTENANCE_TODAY:-$(date +%F)}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [status|record|reset]
|
||||
|
||||
status : print whether the recurring WSL maintenance check is due
|
||||
record : mark today's date as the completed maintenance date
|
||||
reset : remove the stored completion date
|
||||
EOF
|
||||
}
|
||||
|
||||
require_date() {
|
||||
local value="$1"
|
||||
if ! date -d "$value" +%F >/dev/null 2>&1; then
|
||||
echo "Invalid date: $value" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
require_non_negative_integer() {
|
||||
local value="$1"
|
||||
case "$value" in
|
||||
''|*[!0-9]*)
|
||||
echo "Interval must be a non-negative integer: $value" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
days_since() {
|
||||
local from_date="$1"
|
||||
local to_date="$2"
|
||||
local from_epoch
|
||||
local to_epoch
|
||||
|
||||
from_epoch="$(date -d "$from_date" +%s)"
|
||||
to_epoch="$(date -d "$to_date" +%s)"
|
||||
echo $(((to_epoch - from_epoch) / 86400))
|
||||
}
|
||||
|
||||
print_due_message() {
|
||||
local base_date="$1"
|
||||
local elapsed_days="$2"
|
||||
local next_due_date
|
||||
|
||||
next_due_date="$(date -d "$base_date + $INTERVAL_DAYS days" +%F)"
|
||||
|
||||
cat <<EOF
|
||||
WSL maintenance reminder: DUE
|
||||
Base date: $base_date
|
||||
Days elapsed: $elapsed_days
|
||||
Due date: $next_due_date
|
||||
|
||||
General PowerShell:
|
||||
1. Close VS Code, WSL terminals, Docker Desktop, and any \\\\wsl$ Explorer windows.
|
||||
2. Run: wsl --shutdown
|
||||
|
||||
Administrator PowerShell:
|
||||
1. Run: diskpart
|
||||
2. Then run:
|
||||
select vdisk file="%LOCALAPPDATA%\\Packages\\CanonicalGroupLimited.Ubuntu_79rhkp1fndgsc\\LocalState\\ext4.vhdx"
|
||||
attach vdisk readonly
|
||||
compact vdisk
|
||||
detach vdisk
|
||||
exit
|
||||
|
||||
After maintenance is complete, record it with:
|
||||
./scripts/check-wsl-maintenance.sh record
|
||||
EOF
|
||||
}
|
||||
|
||||
print_not_due_message() {
|
||||
local base_date="$1"
|
||||
local elapsed_days="$2"
|
||||
local remaining_days
|
||||
local next_due_date
|
||||
|
||||
remaining_days=$((INTERVAL_DAYS - elapsed_days))
|
||||
next_due_date="$(date -d "$base_date + $INTERVAL_DAYS days" +%F)"
|
||||
|
||||
cat <<EOF
|
||||
WSL maintenance reminder: OK
|
||||
Base date: $base_date
|
||||
Days elapsed: $elapsed_days
|
||||
Next due date: $next_due_date
|
||||
Days remaining: $remaining_days
|
||||
EOF
|
||||
}
|
||||
|
||||
require_date "$ANCHOR_DATE"
|
||||
require_date "$TODAY"
|
||||
require_non_negative_integer "$INTERVAL_DAYS"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
case "$MODE" in
|
||||
status)
|
||||
base_date="$ANCHOR_DATE"
|
||||
if [ -f "$STATE_FILE" ] && [ -s "$STATE_FILE" ]; then
|
||||
base_date="$(tr -d '[:space:]' < "$STATE_FILE")"
|
||||
require_date "$base_date"
|
||||
fi
|
||||
|
||||
elapsed_days="$(days_since "$base_date" "$TODAY")"
|
||||
if [ "$elapsed_days" -lt 0 ]; then
|
||||
echo "Stored date is in the future: $base_date" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ "$elapsed_days" -ge "$INTERVAL_DAYS" ]; then
|
||||
print_due_message "$base_date" "$elapsed_days"
|
||||
else
|
||||
print_not_due_message "$base_date" "$elapsed_days"
|
||||
fi
|
||||
;;
|
||||
record)
|
||||
printf '%s\n' "$TODAY" > "$STATE_FILE"
|
||||
echo "Recorded WSL maintenance date: $TODAY"
|
||||
;;
|
||||
reset)
|
||||
rm -f "$STATE_FILE"
|
||||
echo "Removed recorded WSL maintenance date: $STATE_FILE"
|
||||
;;
|
||||
--help|-h|help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown mode: $MODE" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT_SLUG = "hanmac-family"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Export Baron org-context JSON to CSV files for photo mapping."
|
||||
)
|
||||
parser.add_argument("--input", required=True, help="Path to org-context JSON file")
|
||||
parser.add_argument(
|
||||
"--output-dir", required=True, help="Directory where CSV files will be written"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def normalize_phone(phone: str) -> str:
|
||||
digits = "".join(ch for ch in (phone or "") if ch.isdigit())
|
||||
if digits.startswith("82") and len(digits) >= 11:
|
||||
digits = "0" + digits[2:]
|
||||
return digits
|
||||
|
||||
|
||||
def format_phone(phone: str) -> str:
|
||||
digits = normalize_phone(phone)
|
||||
if len(digits) == 11:
|
||||
return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}"
|
||||
if len(digits) == 10:
|
||||
return f"{digits[:3]}-{digits[3:6]}-{digits[6:]}"
|
||||
return phone or ""
|
||||
|
||||
|
||||
def email_local_part(email: str) -> str:
|
||||
if "@" not in (email or ""):
|
||||
return ""
|
||||
return email.split("@", 1)[0].strip()
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
def build_tree_indexes(tree: dict) -> tuple[dict, dict, dict]:
|
||||
tenant_by_id: dict[str, dict] = {}
|
||||
parent_by_id: dict[str, str | None] = {}
|
||||
children_by_id: dict[str, list[str]] = defaultdict(list)
|
||||
|
||||
def visit(node: dict, parent_id: str | None) -> None:
|
||||
tenant_id = node.get("id", "")
|
||||
if not tenant_id:
|
||||
return
|
||||
tenant_by_id[tenant_id] = node
|
||||
parent_by_id[tenant_id] = parent_id
|
||||
if parent_id:
|
||||
children_by_id[parent_id].append(tenant_id)
|
||||
for child in node.get("children", []) or []:
|
||||
if isinstance(child, dict):
|
||||
visit(child, tenant_id)
|
||||
|
||||
visit(tree, None)
|
||||
return tenant_by_id, parent_by_id, children_by_id
|
||||
|
||||
|
||||
def build_flat_tenant_index(tenants: list[dict]) -> dict[str, dict]:
|
||||
return {
|
||||
tenant.get("id", ""): tenant
|
||||
for tenant in tenants
|
||||
if isinstance(tenant, dict) and tenant.get("id")
|
||||
}
|
||||
|
||||
|
||||
def tenant_path(
|
||||
tenant_id: str, tenant_by_id: dict[str, dict], parent_by_id: dict[str, str | None]
|
||||
) -> list[dict]:
|
||||
path: list[dict] = []
|
||||
current_id = tenant_id
|
||||
while current_id:
|
||||
node = tenant_by_id.get(current_id)
|
||||
if node is None:
|
||||
break
|
||||
path.append(node)
|
||||
current_id = parent_by_id.get(current_id) or ""
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
|
||||
def resolve_company(path: list[dict], tenant: dict) -> tuple[str, str]:
|
||||
if not path:
|
||||
return tenant.get("id", ""), tenant.get("name", "")
|
||||
|
||||
if len(path) == 1:
|
||||
return path[0].get("id", ""), path[0].get("name", "")
|
||||
|
||||
for node in path[1:]:
|
||||
node_type = (node.get("type") or "").upper()
|
||||
if node_type in {"COMPANY", "ORGANIZATION"}:
|
||||
return node.get("id", ""), node.get("name", "")
|
||||
|
||||
first_child = path[1]
|
||||
return first_child.get("id", ""), first_child.get("name", "")
|
||||
|
||||
|
||||
def row_sort_key(row: dict) -> tuple:
|
||||
phone_key = row["phone_normalized"] or "99999999999"
|
||||
return (
|
||||
row["company_name"],
|
||||
phone_key,
|
||||
row["department_name"],
|
||||
row["name"],
|
||||
row["email"],
|
||||
)
|
||||
|
||||
|
||||
def primary_preference(row: dict) -> tuple:
|
||||
return (
|
||||
0 if row["is_primary"] == "true" else 1,
|
||||
0 if row["phone_normalized"] else 1,
|
||||
0 if row["grade"] else 1,
|
||||
0 if row["position"] else 1,
|
||||
row["company_name"],
|
||||
row["department_name"],
|
||||
row["email"],
|
||||
)
|
||||
|
||||
|
||||
def build_rows(payload: dict) -> tuple[list[dict], list[dict], list[dict]]:
|
||||
tree = payload.get("tree") or {}
|
||||
tenants = payload.get("tenants") or []
|
||||
tree_tenant_by_id, parent_by_id, _children_by_id = build_tree_indexes(tree)
|
||||
flat_tenant_by_id = build_flat_tenant_index(tenants)
|
||||
|
||||
for tenant_id, tenant in flat_tenant_by_id.items():
|
||||
tree_tenant_by_id.setdefault(tenant_id, tenant)
|
||||
|
||||
membership_rows: list[dict] = []
|
||||
for tenant in tenants:
|
||||
if not isinstance(tenant, dict):
|
||||
continue
|
||||
tenant_id = tenant.get("id", "")
|
||||
tenant_name = tenant.get("name", "")
|
||||
path = tenant_path(tenant_id, tree_tenant_by_id, parent_by_id)
|
||||
company_tenant_id, company_name = resolve_company(path, tenant)
|
||||
path_names = " > ".join(
|
||||
node.get("name", "")
|
||||
for node in path
|
||||
if node.get("name") and node.get("slug") != ROOT_SLUG
|
||||
)
|
||||
|
||||
for member in tenant.get("members", []) or []:
|
||||
if not isinstance(member, dict):
|
||||
continue
|
||||
raw_phone = (member.get("phone") or member.get("phoneNumber") or "").strip()
|
||||
row = {
|
||||
"company_tenant_id": company_tenant_id,
|
||||
"company_name": company_name,
|
||||
"department_tenant_id": tenant_id,
|
||||
"department_name": tenant_name,
|
||||
"department_path": path_names,
|
||||
"email": (member.get("email") or "").strip(),
|
||||
"email_id": email_local_part(member.get("email") or ""),
|
||||
"phone_raw": raw_phone,
|
||||
"phone_normalized": normalize_phone(raw_phone),
|
||||
"phone_display": format_phone(raw_phone),
|
||||
"name": (member.get("name") or "").strip(),
|
||||
"grade": (member.get("grade") or "").strip(),
|
||||
"position": (member.get("position") or "").strip(),
|
||||
"member_department": (member.get("department") or "").strip(),
|
||||
"is_primary": "true" if member.get("isPrimary") else "false",
|
||||
"member_id": (member.get("id") or "").strip(),
|
||||
}
|
||||
membership_rows.append(row)
|
||||
|
||||
membership_rows.sort(key=row_sort_key)
|
||||
|
||||
primary_rows = [row for row in membership_rows if row["is_primary"] == "true"]
|
||||
|
||||
phone_index: dict[str, list[dict]] = defaultdict(list)
|
||||
for row in membership_rows:
|
||||
if row["phone_normalized"]:
|
||||
phone_index[row["phone_normalized"]].append(row)
|
||||
|
||||
deduped_rows: list[dict] = []
|
||||
for phone in sorted(phone_index):
|
||||
best = sorted(phone_index[phone], key=primary_preference)[0]
|
||||
duplicate_count = len(phone_index[phone])
|
||||
selected = dict(best)
|
||||
selected["duplicate_row_count_for_phone"] = str(duplicate_count)
|
||||
deduped_rows.append(selected)
|
||||
|
||||
deduped_rows.sort(key=row_sort_key)
|
||||
return membership_rows, primary_rows, deduped_rows
|
||||
|
||||
|
||||
def write_csv(path: Path, rows: list[dict], fieldnames: list[str]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8-sig", newline="") as handle:
|
||||
writer = csv.DictWriter(handle, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
for row in rows:
|
||||
writer.writerow({field: row.get(field, "") for field in fieldnames})
|
||||
|
||||
|
||||
def write_summary(path: Path, membership_rows: list[dict], primary_rows: list[dict], deduped_rows: list[dict]) -> None:
|
||||
company_counts: dict[str, int] = defaultdict(int)
|
||||
for row in deduped_rows:
|
||||
company_counts[row["company_name"]] += 1
|
||||
|
||||
lines = [
|
||||
"Baron org-context CSV export summary",
|
||||
f"membership_rows={len(membership_rows)}",
|
||||
f"primary_rows={len(primary_rows)}",
|
||||
f"deduped_phone_rows={len(deduped_rows)}",
|
||||
"company_counts:",
|
||||
]
|
||||
for company_name in sorted(company_counts):
|
||||
lines.append(f"- {company_name}: {company_counts[company_name]}")
|
||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
payload = load_json(Path(args.input))
|
||||
membership_rows, primary_rows, deduped_rows = build_rows(payload)
|
||||
|
||||
output_dir = Path(args.output_dir)
|
||||
fieldnames = [
|
||||
"company_tenant_id",
|
||||
"company_name",
|
||||
"department_tenant_id",
|
||||
"department_name",
|
||||
"department_path",
|
||||
"email",
|
||||
"email_id",
|
||||
"phone_raw",
|
||||
"phone_normalized",
|
||||
"phone_display",
|
||||
"name",
|
||||
"grade",
|
||||
"position",
|
||||
"member_department",
|
||||
"is_primary",
|
||||
"member_id",
|
||||
]
|
||||
deduped_fieldnames = fieldnames + ["duplicate_row_count_for_phone"]
|
||||
|
||||
write_csv(output_dir / "baron_org_context_membership_rows.csv", membership_rows, fieldnames)
|
||||
write_csv(output_dir / "baron_org_context_primary_rows.csv", primary_rows, fieldnames)
|
||||
write_csv(output_dir / "baron_org_context_phone_mapping.csv", deduped_rows, deduped_fieldnames)
|
||||
write_summary(
|
||||
output_dir / "baron_org_context_export_summary.txt",
|
||||
membership_rows,
|
||||
primary_rows,
|
||||
deduped_rows,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -3,10 +3,65 @@ set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
IMAGE="${FLUTTER_DOCKER_IMAGE:-ghcr.io/cirruslabs/flutter:stable}"
|
||||
ADB_KEY_DIR="$ROOT_DIR/.android-adb"
|
||||
CACHE_ROOT="${FLUTTER_DOCKER_CACHE_ROOT:-$ROOT_DIR/.docker-cache/flutter}"
|
||||
GRADLE_CACHE_DIR="$CACHE_ROOT/gradle"
|
||||
PUB_CACHE_DIR="$CACHE_ROOT/pub"
|
||||
ANDROID_LICENSES_DIR="$CACHE_ROOT/android-sdk/licenses"
|
||||
ANDROID_NDK_DIR="$CACHE_ROOT/android-sdk/ndk"
|
||||
ANDROID_CMAKE_DIR="$CACHE_ROOT/android-sdk/cmake"
|
||||
MODE="${1:-flutter}"
|
||||
if [ "$#" -gt 0 ]; then
|
||||
shift || true
|
||||
fi
|
||||
|
||||
DOCKER_ENV_ARGS=()
|
||||
if [ -n "${ADB_SERVER_SOCKET:-}" ]; then
|
||||
DOCKER_ENV_ARGS+=("-e" "ADB_SERVER_SOCKET=${ADB_SERVER_SOCKET}")
|
||||
fi
|
||||
if [ -n "${TDC114_ADB_CONNECT_ADDRESS:-}" ]; then
|
||||
DOCKER_ENV_ARGS+=("-e" "TDC114_ADB_CONNECT_ADDRESS=${TDC114_ADB_CONNECT_ADDRESS}")
|
||||
fi
|
||||
|
||||
mkdir -p "$ADB_KEY_DIR"
|
||||
mkdir -p "$GRADLE_CACHE_DIR"
|
||||
mkdir -p "$PUB_CACHE_DIR"
|
||||
mkdir -p "$ANDROID_LICENSES_DIR"
|
||||
mkdir -p "$ANDROID_NDK_DIR"
|
||||
mkdir -p "$ANDROID_CMAKE_DIR"
|
||||
|
||||
docker run --rm \
|
||||
-u 0:0 \
|
||||
"${DOCKER_ENV_ARGS[@]}" \
|
||||
-v "$ROOT_DIR:/workspace" \
|
||||
-v "$ADB_KEY_DIR:/root/.android" \
|
||||
-v "$GRADLE_CACHE_DIR:/root/.gradle" \
|
||||
-v "$PUB_CACHE_DIR:/root/.pub-cache" \
|
||||
-v "$ANDROID_LICENSES_DIR:/opt/android-sdk-linux/licenses" \
|
||||
-v "$ANDROID_NDK_DIR:/opt/android-sdk-linux/ndk" \
|
||||
-v "$ANDROID_CMAKE_DIR:/opt/android-sdk-linux/cmake" \
|
||||
-w /workspace/app \
|
||||
"$IMAGE" \
|
||||
sh -lc "flutter pub get && flutter $* && chown -R 1000:1000 /workspace/app"
|
||||
sh -lc '
|
||||
set -e
|
||||
if command -v sdkmanager >/dev/null 2>&1; then
|
||||
yes | sdkmanager --licenses >/tmp/android-sdk-licenses.log 2>&1 || {
|
||||
cat /tmp/android-sdk-licenses.log >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
if [ -n "${TDC114_ADB_CONNECT_ADDRESS:-}" ]; then
|
||||
adb connect "$TDC114_ADB_CONNECT_ADDRESS" || true
|
||||
adb devices
|
||||
fi
|
||||
if [ "$1" = "adb-devices" ]; then
|
||||
exit 0
|
||||
fi
|
||||
if [ "$1" = "adb" ]; then
|
||||
shift
|
||||
exec adb "$@"
|
||||
fi
|
||||
flutter pub get
|
||||
flutter "$@"
|
||||
chown -R 1000:1000 /workspace/app
|
||||
' sh "$MODE" "$@"
|
||||
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LOCAL_ADB_BIN="$ROOT_DIR/.tools/android-platform-tools/adb"
|
||||
ADB_BIN="${TDC114_HOST_ADB_BIN:-}"
|
||||
|
||||
if [ -z "$ADB_BIN" ]; then
|
||||
if [ -x "$LOCAL_ADB_BIN" ]; then
|
||||
ADB_BIN="$LOCAL_ADB_BIN"
|
||||
elif command -v adb >/dev/null 2>&1; then
|
||||
ADB_BIN="$(command -v adb)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$ADB_BIN" ] || [ ! -x "$ADB_BIN" ]; then
|
||||
cat >&2 <<EOF
|
||||
Host adb is not available.
|
||||
|
||||
Expected one of:
|
||||
- $LOCAL_ADB_BIN
|
||||
- adb in PATH
|
||||
- TDC114_HOST_ADB_BIN=/path/to/adb
|
||||
|
||||
Run scripts/install-host-adb-from-docker.sh once to extract adb from the Flutter Docker image.
|
||||
EOF
|
||||
exit 127
|
||||
fi
|
||||
|
||||
exec "$ADB_BIN" "$@"
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
IMAGE="${FLUTTER_DOCKER_IMAGE:-ghcr.io/cirruslabs/flutter:stable}"
|
||||
OUT_DIR="$ROOT_DIR/.tools/android-platform-tools"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
docker run --rm \
|
||||
-v "$OUT_DIR:/out" \
|
||||
"$IMAGE" \
|
||||
sh -lc '
|
||||
set -e
|
||||
adb_path="$(command -v adb)"
|
||||
cp "$adb_path" /out/adb
|
||||
chmod 755 /out/adb
|
||||
'
|
||||
|
||||
"$OUT_DIR/adb" version
|
||||
@@ -3,10 +3,20 @@ set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
LOCAL_ENV_FILE="${TDC114_SMOKE_ENV_FILE:-$ROOT_DIR/scripts/.env.smoke.local}"
|
||||
ANDROID_PREFLIGHT_SCRIPT="${TDC114_ANDROID_PREFLIGHT_SCRIPT:-$ROOT_DIR/scripts/check-android-device-env.sh}"
|
||||
|
||||
if [ -f "$LOCAL_ENV_FILE" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$LOCAL_ENV_FILE"
|
||||
fi
|
||||
|
||||
if [ -z "${TDC114_API_BASE:-}" ]; then
|
||||
echo "TDC114_API_BASE is required for integration tests." >&2
|
||||
echo "Example: TDC114_API_BASE=https://staging.example.com $0" >&2
|
||||
if [ -f "$LOCAL_ENV_FILE" ]; then
|
||||
echo "Loaded local smoke env from $LOCAL_ENV_FILE" >&2
|
||||
fi
|
||||
exit 64
|
||||
fi
|
||||
|
||||
@@ -16,4 +26,152 @@ if [ ! -d "$APP_DIR/integration_test" ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
"$ROOT_DIR/scripts/flutter-docker.sh" test integration_test
|
||||
if [ -n "${TDC114_ADB_CONNECT_ADDRESS:-}" ] || [ -n "${TDC114_FLUTTER_DEVICE_ID:-}" ] || [ -n "${ADB_SERVER_SOCKET:-}" ]; then
|
||||
"$ANDROID_PREFLIGHT_SCRIPT"
|
||||
fi
|
||||
|
||||
TEST_ARGS=(
|
||||
"drive"
|
||||
"--dart-define=TDC114_API_BASE=${TDC114_API_BASE}"
|
||||
)
|
||||
|
||||
append_optional_define() {
|
||||
local key="$1"
|
||||
local value="${!key:-}"
|
||||
if [ -n "$value" ]; then
|
||||
TEST_ARGS+=("--dart-define=${key}=${value}")
|
||||
fi
|
||||
}
|
||||
|
||||
append_optional_define "SSO_BASE_URL"
|
||||
append_optional_define "TDC114_AUTH_API_BASE"
|
||||
append_optional_define "TDC114_DIRECTORY_API_BASE"
|
||||
append_optional_define "TDC114_ORGANIZATION_API_BASE"
|
||||
append_optional_define "TDC114_ORG_CONTEXT_API_BASE"
|
||||
append_optional_define "TDC114_ORG_CONTEXT_TENANT_SLUG"
|
||||
append_optional_define "TDC114_BARON_KEY_ID"
|
||||
append_optional_define "TDC114_BARON_KEY_SECRET"
|
||||
|
||||
TEST_DEVICE_ID="${TDC114_FLUTTER_DEVICE_ID:-${TDC114_ADB_CONNECT_ADDRESS:-}}"
|
||||
if [ -n "$TEST_DEVICE_ID" ]; then
|
||||
TEST_ARGS+=("-d" "$TEST_DEVICE_ID")
|
||||
fi
|
||||
|
||||
if [ "${TDC114_SMOKE_ASSUME_LOGGED_IN:-}" = "1" ] || [ "${TDC114_SMOKE_ASSUME_LOGGED_IN:-}" = "true" ]; then
|
||||
TEST_ARGS+=("--dart-define=TDC114_SMOKE_ASSUME_LOGGED_IN=true")
|
||||
bootstrap_mode="${TDC114_SESSION_BOOTSTRAP_MODE:-mock}"
|
||||
preauth_exports=""
|
||||
|
||||
if [ -z "${TDC114_SMOKE_PHONE:-}" ]; then
|
||||
TDC114_SMOKE_PHONE=01012345678
|
||||
fi
|
||||
|
||||
if [ "$bootstrap_mode" = "legacy-phone-login" ]; then
|
||||
bootstrap_api_base="${TDC114_SESSION_BOOTSTRAP_API_BASE:-$TDC114_API_BASE}"
|
||||
bootstrap_api_base="${bootstrap_api_base//10.0.2.2/127.0.0.1}"
|
||||
bootstrap_api_base="${bootstrap_api_base//localhost/127.0.0.1}"
|
||||
|
||||
login_payload="$(printf '{"phoneNumber":"%s","device":{"platform":"android","appVersion":"integration-smoke","deviceName":"android-bootstrap"}}' "$TDC114_SMOKE_PHONE")"
|
||||
set +e
|
||||
login_response="$(
|
||||
curl -fsS \
|
||||
-H 'content-type: application/json' \
|
||||
-d "$login_payload" \
|
||||
"${bootstrap_api_base%/}/api/v1/tdc114plus/auth/phone-login" 2>/tmp/tdc114plus_integration_bootstrap.err
|
||||
)"
|
||||
bootstrap_status=$?
|
||||
set -e
|
||||
|
||||
if [ "$bootstrap_status" -eq 0 ]; then
|
||||
preauth_exports="$(
|
||||
python3 -c '
|
||||
import json, sys
|
||||
data = json.loads(sys.argv[1])
|
||||
user = data.get("user") or {}
|
||||
fields = {
|
||||
"TDC114_PREAUTH_TOKEN": data.get("token", ""),
|
||||
"TDC114_PREAUTH_EXPIRES_AT": data.get("expiresAt", ""),
|
||||
"TDC114_PREAUTH_USER_ID": user.get("id", ""),
|
||||
"TDC114_PREAUTH_USER_NAME": user.get("name", ""),
|
||||
"TDC114_PREAUTH_USER_PHONE": user.get("phoneNumber", ""),
|
||||
"TDC114_PREAUTH_TENANT_ID": user.get("tenantId", ""),
|
||||
"TDC114_PREAUTH_TENANT_NAME": user.get("tenantName", ""),
|
||||
"TDC114_PREAUTH_TENANT_SLUG": user.get("tenantSlug", ""),
|
||||
"TDC114_PREAUTH_DEPARTMENT": user.get("department", ""),
|
||||
"TDC114_PREAUTH_GRADE": user.get("grade", ""),
|
||||
"TDC114_PREAUTH_POSITION": user.get("position", ""),
|
||||
"TDC114_PREAUTH_JOB_TITLE": user.get("jobTitle", ""),
|
||||
}
|
||||
for key, value in fields.items():
|
||||
if value is None:
|
||||
value = ""
|
||||
print(f"{key}={value}")
|
||||
' "$login_response"
|
||||
)"
|
||||
else
|
||||
echo "WARN legacy integration bootstrap phone-login failed; falling back to mock directory mode." >&2
|
||||
cat /tmp/tdc114plus_integration_bootstrap.err >&2 || true
|
||||
fi
|
||||
else
|
||||
echo "INFO integration session bootstrap uses mock mode by default under the current headless-login policy." >&2
|
||||
fi
|
||||
|
||||
if [ -z "$preauth_exports" ]; then
|
||||
preauth_exports="$(cat <<'EOF'
|
||||
TDC114_PREAUTH_TOKEN=mock-session-token
|
||||
TDC114_PREAUTH_EXPIRES_AT=2099-12-31T23:59:59Z
|
||||
TDC114_PREAUTH_USER_ID=user-001
|
||||
TDC114_PREAUTH_USER_NAME=김하늘
|
||||
TDC114_PREAUTH_USER_PHONE=+821012345678
|
||||
TDC114_PREAUTH_TENANT_ID=tenant-hanmac
|
||||
TDC114_PREAUTH_TENANT_NAME=한맥
|
||||
TDC114_PREAUTH_TENANT_SLUG=hanmac
|
||||
TDC114_PREAUTH_DEPARTMENT=기술연구소
|
||||
TDC114_PREAUTH_GRADE=책임
|
||||
TDC114_PREAUTH_POSITION=팀장
|
||||
TDC114_PREAUTH_JOB_TITLE=Flutter 개발
|
||||
EOF
|
||||
)"
|
||||
TEST_ARGS+=("--dart-define=TDC114_SMOKE_USE_MOCK_DIRECTORY=true")
|
||||
fi
|
||||
|
||||
while IFS='=' read -r key value; do
|
||||
[ -n "$key" ] || continue
|
||||
TEST_ARGS+=("--dart-define=${key}=${value}")
|
||||
done <<< "$preauth_exports"
|
||||
fi
|
||||
|
||||
if [ -n "${TDC114_SMOKE_PHONE:-}" ]; then
|
||||
TEST_ARGS+=("--dart-define=TDC114_SMOKE_PHONE=${TDC114_SMOKE_PHONE}")
|
||||
fi
|
||||
|
||||
if [ -n "${TDC114_SMOKE_EXPECTED_NAME:-}" ]; then
|
||||
TEST_ARGS+=("--dart-define=TDC114_SMOKE_EXPECTED_NAME=${TDC114_SMOKE_EXPECTED_NAME}")
|
||||
fi
|
||||
|
||||
if [ -n "${TDC114_SMOKE_EXPECTED_TENANT_LABEL:-}" ]; then
|
||||
TEST_ARGS+=("--dart-define=TDC114_SMOKE_EXPECTED_TENANT_LABEL=${TDC114_SMOKE_EXPECTED_TENANT_LABEL}")
|
||||
fi
|
||||
|
||||
TEST_ARGS+=(
|
||||
"--driver=test_driver/integration_driver.dart"
|
||||
"--target=integration_test/app_smoke_test.dart"
|
||||
)
|
||||
|
||||
OUTPUT_FILE="$(mktemp)"
|
||||
trap 'rm -f "$OUTPUT_FILE"' EXIT
|
||||
|
||||
set +e
|
||||
"$ROOT_DIR/scripts/flutter-docker.sh" "${TEST_ARGS[@]}" 2>&1 | tee "$OUTPUT_FILE"
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
if [ "$status" -ne 0 ] && grep -q "No supported devices connected." "$OUTPUT_FILE"; then
|
||||
echo >&2
|
||||
echo "integration test requires a supported runtime target." >&2
|
||||
echo "Current Flutter project only has Android/iOS runners." >&2
|
||||
echo "Connect an Android physical device first, or use an emulator fallback, before retrying." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
|
||||
Executable
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LOCAL_ENV_FILE="${TDC114_SMOKE_ENV_FILE:-$ROOT_DIR/scripts/.env.android-device.local}"
|
||||
|
||||
if [ -f "$LOCAL_ENV_FILE" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$LOCAL_ENV_FILE"
|
||||
fi
|
||||
|
||||
APP_API_BASE="${TDC114_API_BASE:-}"
|
||||
if [ -z "$APP_API_BASE" ]; then
|
||||
echo "TDC114_API_BASE is required." >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
if [ -z "${TDC114_ADB_CONNECT_ADDRESS:-}" ] && [ -z "${ADB_SERVER_SOCKET:-}" ]; then
|
||||
echo "Either ADB_SERVER_SOCKET or TDC114_ADB_CONNECT_ADDRESS is required." >&2
|
||||
echo "Example: ADB_SERVER_SOCKET=tcp:172.21.128.1:5037 TDC114_FLUTTER_DEVICE_ID=ce091719b0a418620d7e $0" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
if [ -n "${ADB_SERVER_SOCKET:-}" ] && [ -z "${TDC114_ADB_CONNECT_ADDRESS:-}" ] && [ -z "${TDC114_FLUTTER_DEVICE_ID:-}" ]; then
|
||||
echo "TDC114_FLUTTER_DEVICE_ID is required when using ADB_SERVER_SOCKET." >&2
|
||||
echo "Example: TDC114_FLUTTER_DEVICE_ID=ce091719b0a418620d7e" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
SMOKE_PHONE="${TDC114_SMOKE_PHONE:-01091365338}"
|
||||
BOOTSTRAP_API_BASE="${TDC114_SESSION_BOOTSTRAP_API_BASE:-$APP_API_BASE}"
|
||||
BOOTSTRAP_API_BASE="${BOOTSTRAP_API_BASE//10.0.2.2/127.0.0.1}"
|
||||
BOOTSTRAP_API_BASE="${BOOTSTRAP_API_BASE//localhost/127.0.0.1}"
|
||||
DEVICE_ID="${TDC114_FLUTTER_DEVICE_ID:-$TDC114_ADB_CONNECT_ADDRESS}"
|
||||
SKIP_SESSION_BOOTSTRAP="${TDC114_SKIP_SESSION_BOOTSTRAP:-false}"
|
||||
|
||||
tmp_body="$(mktemp)"
|
||||
tmp_err="$(mktemp)"
|
||||
trap 'rm -f "$tmp_body" "$tmp_err"' EXIT
|
||||
|
||||
DEFINE_ARGS=(
|
||||
"--dart-define=TDC114_API_BASE=${APP_API_BASE}"
|
||||
)
|
||||
|
||||
append_optional_define() {
|
||||
local key="$1"
|
||||
local value="${!key:-}"
|
||||
if [ -n "$value" ]; then
|
||||
DEFINE_ARGS+=("--dart-define=${key}=${value}")
|
||||
fi
|
||||
}
|
||||
|
||||
append_optional_define "SSO_BASE_URL"
|
||||
append_optional_define "TDC114_AUTH_API_BASE"
|
||||
append_optional_define "TDC114_DIRECTORY_API_BASE"
|
||||
append_optional_define "TDC114_ORGANIZATION_API_BASE"
|
||||
append_optional_define "TDC114_ORG_CONTEXT_API_BASE"
|
||||
append_optional_define "TDC114_ORG_CONTEXT_TENANT_SLUG"
|
||||
append_optional_define "TDC114_BARON_KEY_ID"
|
||||
append_optional_define "TDC114_BARON_KEY_SECRET"
|
||||
|
||||
if [ "$SKIP_SESSION_BOOTSTRAP" = "1" ] || [ "$SKIP_SESSION_BOOTSTRAP" = "true" ]; then
|
||||
echo "Skipping session bootstrap; app will open its normal login flow." >&2
|
||||
else
|
||||
login_payload="$(printf '{"phoneNumber":"%s","device":{"platform":"android","appVersion":"manual-smoke","deviceName":"android-manual"}}' "$SMOKE_PHONE")"
|
||||
|
||||
set +e
|
||||
http_code="$(
|
||||
curl -sS \
|
||||
-o "$tmp_body" \
|
||||
-w '%{http_code}' \
|
||||
-H 'content-type: application/json' \
|
||||
-d "$login_payload" \
|
||||
"${BOOTSTRAP_API_BASE%/}/api/v1/tdc114plus/auth/phone-login" 2>"$tmp_err"
|
||||
)"
|
||||
curl_status=$?
|
||||
set -e
|
||||
|
||||
if [ "$curl_status" -eq 0 ] && [ "$http_code" = "200" ]; then
|
||||
mapfile -t exported < <(
|
||||
python3 - "$tmp_body" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
with open(sys.argv[1], 'r', encoding='utf-8') as fh:
|
||||
data = json.load(fh)
|
||||
|
||||
user = data.get("user") or {}
|
||||
fields = {
|
||||
"TDC114_PREAUTH_TOKEN": data.get("token", ""),
|
||||
"TDC114_PREAUTH_EXPIRES_AT": data.get("expiresAt", ""),
|
||||
"TDC114_PREAUTH_USER_ID": user.get("id", ""),
|
||||
"TDC114_PREAUTH_USER_NAME": user.get("name", ""),
|
||||
"TDC114_PREAUTH_USER_PHONE": user.get("phoneNumber", ""),
|
||||
"TDC114_PREAUTH_TENANT_ID": user.get("tenantId", ""),
|
||||
"TDC114_PREAUTH_TENANT_NAME": user.get("tenantName", ""),
|
||||
"TDC114_PREAUTH_TENANT_SLUG": user.get("tenantSlug", ""),
|
||||
"TDC114_PREAUTH_DEPARTMENT": user.get("department", "") or "",
|
||||
"TDC114_PREAUTH_GRADE": user.get("grade", "") or "",
|
||||
"TDC114_PREAUTH_POSITION": user.get("position", "") or "",
|
||||
"TDC114_PREAUTH_JOB_TITLE": user.get("jobTitle", "") or "",
|
||||
}
|
||||
|
||||
for key, value in fields.items():
|
||||
print(f"{key}={value}")
|
||||
PY
|
||||
)
|
||||
for entry in "${exported[@]}"; do
|
||||
DEFINE_ARGS+=("--dart-define=${entry}")
|
||||
done
|
||||
echo "Using real local phone-login bootstrap for manual post-login run." >&2
|
||||
else
|
||||
echo "WARN local phone-login bootstrap failed; using mock directory fallback." >&2
|
||||
if [ -s "$tmp_err" ]; then
|
||||
cat "$tmp_err" >&2
|
||||
fi
|
||||
if [ -s "$tmp_body" ]; then
|
||||
cat "$tmp_body" >&2
|
||||
fi
|
||||
DEFINE_ARGS+=(
|
||||
"--dart-define=TDC114_SMOKE_USE_MOCK_DIRECTORY=true"
|
||||
"--dart-define=TDC114_PREAUTH_TOKEN=mock-session-token"
|
||||
"--dart-define=TDC114_PREAUTH_EXPIRES_AT=2099-12-31T23:59:59Z"
|
||||
"--dart-define=TDC114_PREAUTH_USER_ID=user-001"
|
||||
"--dart-define=TDC114_PREAUTH_USER_NAME=김하늘"
|
||||
"--dart-define=TDC114_PREAUTH_USER_PHONE=+821012345678"
|
||||
"--dart-define=TDC114_PREAUTH_TENANT_ID=tenant-hanmac"
|
||||
"--dart-define=TDC114_PREAUTH_TENANT_NAME=한맥"
|
||||
"--dart-define=TDC114_PREAUTH_TENANT_SLUG=hanmac"
|
||||
"--dart-define=TDC114_PREAUTH_DEPARTMENT=기술연구소"
|
||||
"--dart-define=TDC114_PREAUTH_GRADE=책임"
|
||||
"--dart-define=TDC114_PREAUTH_POSITION=팀장"
|
||||
"--dart-define=TDC114_PREAUTH_JOB_TITLE=Flutter 개발"
|
||||
)
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Launching app on device ${DEVICE_ID} with post-login seed..." >&2
|
||||
exec "$ROOT_DIR/scripts/flutter-docker.sh" \
|
||||
run \
|
||||
-d "$DEVICE_ID" \
|
||||
"${DEFINE_ARGS[@]}"
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/shutdown.sh
|
||||
# Graceful / safe shutdown helper for tdc114plus development environment
|
||||
# Usage: ./scripts/shutdown.sh [--dry-run] [--auto]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BARON_DIR="${BARON_SSO_WORKTREE:-/home/ubuntu/workspace/baron-sso-tdc114plus-api}"
|
||||
LOG_BASE_DIR="${TDC114_LOG_BASE:-$ROOT_DIR/logs}"
|
||||
LOG_DIR="$LOG_BASE_DIR/$(date +%F)"
|
||||
LOG_FILE="$LOG_DIR/shutdown.log"
|
||||
DOCKER_BIN="${DOCKER_BIN:-docker}"
|
||||
ADB_BIN="${ADB_BIN:-adb}"
|
||||
ADB_DISCONNECT_ADDRESS="${TDC114_ADB_CONNECT_ADDRESS:-${TDC114_STARTUP_ANDROID_ADDRESS:-}}"
|
||||
ADB_SERVER_SOCKET_VALUE="${ADB_SERVER_SOCKET:-}"
|
||||
PKILL_BIN="${PKILL_BIN:-pkill}"
|
||||
CHMOD_BIN="${CHMOD_BIN:-chmod}"
|
||||
CHOWN_BIN="${CHOWN_BIN:-chown}"
|
||||
AUTH_PORT="${TDC114_AUTH_PORT:-5001}"
|
||||
|
||||
DRY_RUN=true
|
||||
AUTO=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--auto) DRY_RUN=false; AUTO=true ;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: $0 [--dry-run] [--auto]
|
||||
|
||||
--dry-run : show commands and write non-destructive logs (default)
|
||||
--auto : actually perform shutdown and cleanup
|
||||
--help : show this message
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Unknown arg: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
log() {
|
||||
echo "$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
action() {
|
||||
log "+ $*"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
set +e
|
||||
bash -lc "$*" 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
return "$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
action_cmd() {
|
||||
log "+ $*"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
set +e
|
||||
"$@" 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
return "$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
printf "%s compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml %s" \
|
||||
"$DOCKER_BIN" "$1"
|
||||
}
|
||||
|
||||
log "Shutdown started: $(date)"
|
||||
|
||||
# 1) Stop only tdc114plus-related helper processes.
|
||||
action_cmd "$PKILL_BIN" -f "$ROOT_DIR/scripts/integration_tests.sh" || true
|
||||
action_cmd "$PKILL_BIN" -f "$ROOT_DIR/scripts/flutter-docker.sh" || true
|
||||
action "lsof -nP -iTCP:$AUTH_PORT -sTCP:LISTEN -t 2>/dev/null | xargs -r kill || true"
|
||||
|
||||
# 2) Disconnect adb only when the current target address is explicit.
|
||||
if [ -n "$ADB_DISCONNECT_ADDRESS" ]; then
|
||||
action_cmd env TDC114_ADB_CONNECT_ADDRESS="$ADB_DISCONNECT_ADDRESS" "$ADB_BIN" disconnect "$ADB_DISCONNECT_ADDRESS" || true
|
||||
else
|
||||
log "Skipping adb disconnect because TDC114_ADB_CONNECT_ADDRESS is not set."
|
||||
if [ -n "$ADB_SERVER_SOCKET_VALUE" ]; then
|
||||
log "ADB_SERVER_SOCKET is in use; physical USB device remains managed by the shared host ADB server."
|
||||
else
|
||||
log "No explicit Android target connection was provided for disconnect."
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) Save compose logs before bringing the stack down.
|
||||
action "cd '$BARON_DIR' && $(compose_cmd "logs --no-color") > '$LOG_DIR/baron-compose.log' 2>&1 || true"
|
||||
|
||||
# 4) Docker compose down for Baron SSO (safe stop).
|
||||
action "cd '$BARON_DIR' && $(compose_cmd "down")"
|
||||
|
||||
# 5) Optional: force remove old containers (only when --auto).
|
||||
if [ "$AUTO" = true ]; then
|
||||
action "$DOCKER_BIN ps -a --format '{{.Names}}' | grep -iE 'baron|ory|tdc114' | xargs -r $DOCKER_BIN rm -f || true"
|
||||
else
|
||||
log "(skipping destructive container rm; pass --auto to enable)"
|
||||
fi
|
||||
|
||||
# 6) Restore generated config ownership and permissions when needed.
|
||||
action "cd '$BARON_DIR' && $CHMOD_BIN -R u+w config/.generated/ 2>/dev/null || true"
|
||||
action "cd '$BARON_DIR' && $CHOWN_BIN -R \$(id -u):\$(id -g) config/.generated/ 2>/dev/null || true"
|
||||
|
||||
log "Shutdown finished: $(date)"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "Dry-run mode: no shutdown actions were executed. Use --auto to run for real."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copy this file to scripts/.env.smoke.local for local authenticated smoke runs.
|
||||
|
||||
TDC114_API_BASE=http://127.0.0.1:5000
|
||||
TDC114_SMOKE_PHONE=01012345678
|
||||
@@ -0,0 +1,16 @@
|
||||
TDC114_API_BASE=https://sso.hmac.kr
|
||||
TDC114_ORG_CONTEXT_API_BASE=https://sadmin.hmac.kr
|
||||
TDC114_ORG_CONTEXT_TENANT_SLUG=hanmac-family
|
||||
TDC114_BARON_KEY_ID=replace-with-staging-key-id
|
||||
TDC114_BARON_KEY_SECRET=replace-with-staging-key-secret
|
||||
TDC114_SMOKE_PHONE=010xxxxxxxx
|
||||
TDC114_SMOKE_AUTH_FLOW=link
|
||||
|
||||
# Optional: set these only when the staging account has stable expected labels.
|
||||
# TDC114_SMOKE_EXPECTED_NAME=홍길동
|
||||
# TDC114_SMOKE_EXPECTED_TENANT_LABEL=IS3
|
||||
|
||||
# Optional split mode:
|
||||
# TDC114_AUTH_API_BASE=https://sso.hmac.kr
|
||||
# TDC114_DIRECTORY_API_BASE=https://sadmin.hmac.kr
|
||||
# TDC114_ORGANIZATION_API_BASE=https://sadmin.hmac.kr
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start the local tdc114plus-auth broker with one env file.
|
||||
# Usage: ./scripts/start-auth-server.sh [--restart] [--env-file=PATH]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
AUTH_DIR="${TDC114_AUTH_WORKTREE:-/home/ubuntu/workspace/tdc114plus-auth}"
|
||||
ENV_FILE="$ROOT_DIR/scripts/.env.android-device.local"
|
||||
NAVER_WORKS_ENV_FILE="${NAVER_WORKS_ENV_FILE:-$ROOT_DIR/secrets/naver_works_service_account.local.env}"
|
||||
AUTH_GOCACHE="${AUTH_GOCACHE:-/tmp/tdc114plus-auth-gocache}"
|
||||
LOG_BASE_DIR="${TDC114_LOG_BASE:-$ROOT_DIR/logs}"
|
||||
LOG_DIR="$LOG_BASE_DIR/$(date +%F)"
|
||||
LOG_FILE="$LOG_DIR/tdc114plus-auth.log"
|
||||
PORT="${TDC114_AUTH_PORT:-5001}"
|
||||
RESTART=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--restart) RESTART=true ;;
|
||||
--env-file=*) ENV_FILE="${arg#*=}" ;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: $0 [--restart] [--env-file=PATH]
|
||||
|
||||
--restart Stop the current listener on the auth port before starting.
|
||||
--env-file=PATH Read local test values from this env file.
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Unknown arg: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
mkdir -p "$AUTH_GOCACHE"
|
||||
|
||||
log() {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_file() {
|
||||
[ -f "$1" ] || fail "missing file: $1"
|
||||
}
|
||||
|
||||
require_dir() {
|
||||
[ -d "$1" ] || fail "missing directory: $1"
|
||||
}
|
||||
|
||||
listener_pids() {
|
||||
lsof -nP -iTCP:"$PORT" -sTCP:LISTEN -t 2>/dev/null || true
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
local i=1
|
||||
while [ "$i" -le 20 ]; do
|
||||
if curl -fsSL --max-time 2 "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
require_dir "$AUTH_DIR"
|
||||
require_file "$AUTH_DIR/cmd/server/main.go"
|
||||
require_file "$ENV_FILE"
|
||||
require_file "$AUTH_DIR/secrets/private_key.pem"
|
||||
require_file "$AUTH_DIR/secrets/public_key.pem"
|
||||
|
||||
current_pids="$(listener_pids)"
|
||||
if [ -n "$current_pids" ]; then
|
||||
if [ "$RESTART" = true ]; then
|
||||
log "Stopping existing auth server on :$PORT"
|
||||
# shellcheck disable=SC2086
|
||||
kill $current_pids 2>/dev/null || true
|
||||
sleep 1
|
||||
else
|
||||
if wait_for_health; then
|
||||
log "tdc114plus-auth is already running on :$PORT"
|
||||
exit 0
|
||||
fi
|
||||
fail "port $PORT is already in use, but health check failed. Run with --restart."
|
||||
fi
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
if [ -f "$NAVER_WORKS_ENV_FILE" ]; then
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$NAVER_WORKS_ENV_FILE"
|
||||
set +a
|
||||
|
||||
if [ -n "${WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE:-}" ] && [[ "$WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE" != /* ]]; then
|
||||
WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE="$ROOT_DIR/$WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE"
|
||||
export WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY_FILE
|
||||
fi
|
||||
fi
|
||||
|
||||
: "${TDC114_ORG_CONTEXT_API_BASE:?TDC114_ORG_CONTEXT_API_BASE is required in $ENV_FILE}"
|
||||
: "${TDC114_ORG_CONTEXT_TENANT_SLUG:?TDC114_ORG_CONTEXT_TENANT_SLUG is required in $ENV_FILE}"
|
||||
: "${TDC114_BARON_KEY_ID:?TDC114_BARON_KEY_ID is required in $ENV_FILE}"
|
||||
: "${TDC114_BARON_KEY_SECRET:?TDC114_BARON_KEY_SECRET is required in $ENV_FILE}"
|
||||
|
||||
UPSTREAM_ORG_CONTEXT_BASE="${TDC114_AUTH_UPSTREAM_ORG_CONTEXT_API_BASE:-}"
|
||||
if [ -z "$UPSTREAM_ORG_CONTEXT_BASE" ]; then
|
||||
UPSTREAM_ORG_CONTEXT_BASE="$TDC114_ORG_CONTEXT_API_BASE"
|
||||
fi
|
||||
|
||||
log "Starting tdc114plus-auth on :$PORT"
|
||||
log "Log file: $LOG_FILE"
|
||||
|
||||
(
|
||||
cd "$AUTH_DIR"
|
||||
setsid env \
|
||||
GOCACHE="$AUTH_GOCACHE" \
|
||||
PORT="$PORT" \
|
||||
AUTH_PROVIDER=baron \
|
||||
APP_SESSION_SECRET="${APP_SESSION_SECRET:-tdc114plus-dev-session}" \
|
||||
BARON_BASE_URL="${BARON_BASE_URL:-https://sso.hmac.kr}" \
|
||||
BARON_CLIENT_ID="${BARON_CLIENT_ID:-243f0b04-a417-4fbe-ae59-a90b16783a79}" \
|
||||
BARON_OIDC_AUTHORIZATION_URL="${BARON_OIDC_AUTHORIZATION_URL:-https://sso.hmac.kr/oidc/oauth2/auth}" \
|
||||
BARON_OIDC_TOKEN_URL="${BARON_OIDC_TOKEN_URL:-https://sso.hmac.kr/oidc/oauth2/token}" \
|
||||
BARON_OIDC_REDIRECT_URI="${BARON_OIDC_REDIRECT_URI:-https://114-auth.hmac.kr/api/v1/auth/oidc/callback}" \
|
||||
BARON_OIDC_SCOPES="${BARON_OIDC_SCOPES:-openid profile email}" \
|
||||
BARON_PRIVATE_KEY_PATH="${BARON_PRIVATE_KEY_PATH:-./secrets/private_key.pem}" \
|
||||
BARON_PUBLIC_KEY_PATH="${BARON_PUBLIC_KEY_PATH:-./secrets/public_key.pem}" \
|
||||
BARON_JWKS_KID="${BARON_JWKS_KID:-tdc114plus-auth-key-1}" \
|
||||
BARON_LINK_RETURN_URI="${BARON_LINK_RETURN_URI:-}" \
|
||||
BARON_ORG_CONTEXT_BASE_URL="$UPSTREAM_ORG_CONTEXT_BASE" \
|
||||
BARON_ORG_CONTEXT_TENANT_SLUG="$TDC114_ORG_CONTEXT_TENANT_SLUG" \
|
||||
BARON_ORG_CONTEXT_KEY_ID="$TDC114_BARON_KEY_ID" \
|
||||
BARON_ORG_CONTEXT_KEY_SECRET="$TDC114_BARON_KEY_SECRET" \
|
||||
go run ./cmd/server >>"$LOG_FILE" 2>&1 < /dev/null &
|
||||
)
|
||||
|
||||
if ! wait_for_health; then
|
||||
tail -n 80 "$LOG_FILE" >&2 || true
|
||||
fail "tdc114plus-auth did not become healthy on :$PORT"
|
||||
fi
|
||||
|
||||
log "tdc114plus-auth ready on :$PORT"
|
||||
Executable
+443
@@ -0,0 +1,443 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/startup.sh
|
||||
# Startup helper for tdc114plus development environment
|
||||
# Usage: ./scripts/startup.sh [--dry-run] [--auto] [--wait=N] [--skip-android-check]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BARON_DIR="${BARON_SSO_WORKTREE:-/home/ubuntu/workspace/baron-sso-tdc114plus-api}"
|
||||
LOG_BASE_DIR="${TDC114_LOG_BASE:-$ROOT_DIR/logs}"
|
||||
LOG_DIR="$LOG_BASE_DIR/$(date +%F)"
|
||||
LOG_FILE="$LOG_DIR/startup.log"
|
||||
CHECK_SCRIPT="${TDC114_CHECK_SCRIPT:-$ROOT_DIR/scripts/check-baron-api-env.sh}"
|
||||
API_SMOKE_SCRIPT="${TDC114_API_SMOKE_SCRIPT:-$ROOT_DIR/scripts/api-smoke.sh}"
|
||||
DOCKER_BIN="${DOCKER_BIN:-docker}"
|
||||
MAKE_BIN="${MAKE_BIN:-make}"
|
||||
CHMOD_BIN="${CHMOD_BIN:-chmod}"
|
||||
REQUIRE_AUTH_SMOKE="${TDC114_REQUIRE_AUTH_SMOKE:-false}"
|
||||
ANDROID_PRECHECK_SCRIPT="${TDC114_ANDROID_PRECHECK_SCRIPT:-$ROOT_DIR/scripts/check-android-device-env.sh}"
|
||||
AUTH_SERVER_SCRIPT="${TDC114_AUTH_SERVER_SCRIPT:-$ROOT_DIR/scripts/start-auth-server.sh}"
|
||||
START_AUTH_SERVER="${TDC114_START_AUTH_SERVER:-true}"
|
||||
DAILY_HANDOFF_DIR="${TDC114_DAILY_HANDOFF_DIR:-$ROOT_DIR/docs/daily-issues}"
|
||||
REQUIRE_DAILY_HANDOFF="${TDC114_REQUIRE_DAILY_HANDOFF:-true}"
|
||||
WSL_MAINTENANCE_SCRIPT="${TDC114_WSL_MAINTENANCE_SCRIPT:-$ROOT_DIR/scripts/check-wsl-maintenance.sh}"
|
||||
WSL_MAINTENANCE_ENABLED="${TDC114_WSL_MAINTENANCE_ENABLED:-true}"
|
||||
|
||||
DRY_RUN=true
|
||||
AUTO=false
|
||||
WAIT=30
|
||||
RETRIES=6
|
||||
SLEEP_BETWEEN=10
|
||||
SKIP_ANDROID_CHECK=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--auto) DRY_RUN=false; AUTO=true ;;
|
||||
--wait=*) WAIT=${arg#*=} ;;
|
||||
--skip-android-check|--skip-device-check) SKIP_ANDROID_CHECK=true ;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: $0 [--dry-run] [--auto] [--wait=N] [--skip-android-check]
|
||||
|
||||
--dry-run : show commands and write non-destructive logs (default)
|
||||
--auto : actually perform startup actions
|
||||
--wait=N : seconds to wait for services to stabilize (default 30)
|
||||
--skip-android-check : skip Android target preflight (not recommended)
|
||||
--help : show this message
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
--wait) echo "Please use --wait=N" >&2; exit 2 ;;
|
||||
*) echo "Unknown arg: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$WAIT" in
|
||||
''|*[!0-9]*)
|
||||
echo "--wait must be a non-negative integer" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
log() {
|
||||
echo "$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
log "+ $*"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
set +e
|
||||
bash -lc "$*" 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
return "$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
run_cmd_capture() {
|
||||
local output_file="$1"
|
||||
shift
|
||||
|
||||
log "+ $*"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
set +e
|
||||
bash -lc "$*" 2>&1 | tee "$output_file" | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
return "$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
printf "%s compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml %s" \
|
||||
"$DOCKER_BIN" "$1"
|
||||
}
|
||||
|
||||
ensure_required_paths() {
|
||||
if [ ! -d "$BARON_DIR" ]; then
|
||||
log "Startup aborted: Baron worktree not found at $BARON_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -x "$CHECK_SCRIPT" ]; then
|
||||
log "Startup aborted: check script is missing or not executable at $CHECK_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -x "$API_SMOKE_SCRIPT" ]; then
|
||||
log "Startup aborted: api smoke script is missing or not executable at $API_SMOKE_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$SKIP_ANDROID_CHECK" = false ] && [ ! -x "$ANDROID_PRECHECK_SCRIPT" ]; then
|
||||
log "Startup aborted: Android precheck script is missing or not executable at $ANDROID_PRECHECK_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$START_AUTH_SERVER" = true ] && [ ! -x "$AUTH_SERVER_SCRIPT" ]; then
|
||||
log "Startup aborted: auth server script is missing or not executable at $AUTH_SERVER_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$DAILY_HANDOFF_DIR" ]; then
|
||||
log "Startup aborted: daily handoff directory is missing at $DAILY_HANDOFF_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$WSL_MAINTENANCE_ENABLED" = true ] && [ ! -x "$WSL_MAINTENANCE_SCRIPT" ]; then
|
||||
log "Startup aborted: WSL maintenance script is missing or not executable at $WSL_MAINTENANCE_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
review_daily_handoff() {
|
||||
local today
|
||||
local handoff_file
|
||||
|
||||
today="$(date +%F)"
|
||||
handoff_file="$(find "$DAILY_HANDOFF_DIR" -maxdepth 1 -type f -name '20*.md' ! -name "$today*" | sort | tail -n 1 || true)"
|
||||
|
||||
if [ -z "$handoff_file" ]; then
|
||||
log "Daily handoff review: no previous handoff file found in $DAILY_HANDOFF_DIR"
|
||||
if [ "$REQUIRE_DAILY_HANDOFF" = true ]; then
|
||||
log "Startup aborted: previous daily handoff is required. Set TDC114_REQUIRE_DAILY_HANDOFF=false only for exceptional recovery."
|
||||
exit 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Daily handoff review required before startup"
|
||||
log "Daily handoff file: $handoff_file"
|
||||
log "----- daily handoff excerpt start -----"
|
||||
sed -n '1,160p' "$handoff_file" | tee -a "$LOG_FILE"
|
||||
log "----- daily handoff excerpt end -----"
|
||||
}
|
||||
|
||||
review_wsl_maintenance() {
|
||||
if [ "$WSL_MAINTENANCE_ENABLED" != true ]; then
|
||||
log "WSL maintenance reminder skipped by TDC114_WSL_MAINTENANCE_ENABLED=$WSL_MAINTENANCE_ENABLED"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "WSL maintenance review started: $WSL_MAINTENANCE_SCRIPT status"
|
||||
set +e
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
"$WSL_MAINTENANCE_SCRIPT" status
|
||||
) 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "Startup aborted: WSL maintenance review script failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_android_precheck() {
|
||||
if [ "$SKIP_ANDROID_CHECK" = true ]; then
|
||||
log "Android target preflight skipped by option"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Android target preflight started: $ANDROID_PRECHECK_SCRIPT"
|
||||
set +e
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
"$ANDROID_PRECHECK_SCRIPT"
|
||||
) 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "Startup paused: Android target preflight did not pass"
|
||||
log "Resolve the physical-device or ADB steps above, then rerun startup."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Android target preflight passed"
|
||||
}
|
||||
|
||||
ensure_auth_server() {
|
||||
if [ "$START_AUTH_SERVER" != true ]; then
|
||||
log "tdc114plus-auth startup skipped by TDC114_START_AUTH_SERVER=$START_AUTH_SERVER"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "tdc114plus-auth startup started: $AUTH_SERVER_SCRIPT --restart"
|
||||
set +e
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
"$AUTH_SERVER_SCRIPT" --restart
|
||||
) 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "Startup aborted: tdc114plus-auth did not start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "tdc114plus-auth ready"
|
||||
}
|
||||
|
||||
ensure_generated_config_writable() {
|
||||
local generated_dir="$BARON_DIR/config/.generated"
|
||||
|
||||
run_cmd "mkdir -p '$generated_dir'"
|
||||
run_cmd "cd '$BARON_DIR' && $CHMOD_BIN -R u+w config/.generated 2>/dev/null || true"
|
||||
|
||||
if [ "$DRY_RUN" = false ] && [ ! -w "$generated_dir" ]; then
|
||||
log "Startup aborted: $generated_dir is not writable by $(id -un). Fix ownership/permissions before startup."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sanitize_rendered_kratos_config() {
|
||||
local kratos_config="$BARON_DIR/config/.generated/ory/kratos/kratos.yml"
|
||||
|
||||
if [ ! -f "$kratos_config" ]; then
|
||||
log "Startup aborted: rendered Kratos config is missing at $kratos_config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq " - []" "$kratos_config"; then
|
||||
log "Rendered Kratos config contains invalid empty allowed_return_urls entry; removing it automatically"
|
||||
set +e
|
||||
perl -0pi -e 's/^[ ]*-\s*\[\]\n//mg' "$kratos_config"
|
||||
local rc=$?
|
||||
set -e
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "Startup aborted: failed to sanitize $kratos_config"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_rendered_ory_files() {
|
||||
local oathkeeper_entrypoint="$BARON_DIR/config/.generated/ory/oathkeeper/entrypoint.sh"
|
||||
|
||||
sanitize_rendered_kratos_config
|
||||
|
||||
if [ ! -x "$oathkeeper_entrypoint" ]; then
|
||||
log "Startup aborted: rendered Oathkeeper entrypoint is missing or not executable at $oathkeeper_entrypoint"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
capture_failure_diagnostics() {
|
||||
local prefix="${1:-runtime}"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
run_cmd "mkdir -p '$LOG_DIR'"
|
||||
run_cmd "cd '$BARON_DIR' && $(compose_cmd "ps") > '$LOG_DIR/${prefix}-compose-ps.log' 2>&1 || true"
|
||||
run_cmd "$DOCKER_BIN ps -a --format '{{.Names}} {{.Status}}' | grep -E 'baron|ory' > '$LOG_DIR/${prefix}-docker-ps.log' 2>&1 || true"
|
||||
run_cmd "$DOCKER_BIN logs --tail 120 ory_kratos > '$LOG_DIR/${prefix}-ory_kratos.log' 2>&1 || true"
|
||||
run_cmd "$DOCKER_BIN logs --tail 120 ory_stack_check > '$LOG_DIR/${prefix}-ory_stack_check.log' 2>&1 || true"
|
||||
run_cmd "$DOCKER_BIN logs --tail 120 oathkeeper > '$LOG_DIR/${prefix}-oathkeeper.log' 2>&1 || true"
|
||||
run_cmd "$DOCKER_BIN logs --tail 120 baron_backend > '$LOG_DIR/${prefix}-baron_backend.log' 2>&1 || true"
|
||||
}
|
||||
|
||||
run_check_script() {
|
||||
local attempt="$1"
|
||||
local output_file="$LOG_DIR/check-baron-api-env.attempt-${attempt}.log"
|
||||
|
||||
set +e
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
"$CHECK_SCRIPT"
|
||||
) 2>&1 | tee "$output_file" | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "check-baron-api-env.sh failed on attempt $attempt"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! grep -Fq "RESULT ready-ish: 0 failure(s), 0 warning(s)" "$output_file"; then
|
||||
log "check-baron-api-env.sh reported warnings on attempt $attempt"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "check-baron-api-env.sh passed with zero warnings"
|
||||
return 0
|
||||
}
|
||||
|
||||
run_api_smoke_script() {
|
||||
local attempt="$1"
|
||||
local output_file="$LOG_DIR/api-smoke.attempt-${attempt}.log"
|
||||
local fallback_output_file="$LOG_DIR/api-smoke.base-only.attempt-${attempt}.log"
|
||||
|
||||
set +e
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
"$API_SMOKE_SCRIPT"
|
||||
) 2>&1 | tee "$output_file" | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
if [ "$REQUIRE_AUTH_SMOKE" != "true" ] && grep -Fq '"code":"login_failed"' "$output_file"; then
|
||||
log "api-smoke authenticated login failed; retrying in base-smoke-only mode because TDC114_REQUIRE_AUTH_SMOKE is false"
|
||||
set +e
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
TDC114_SKIP_AUTH_SMOKE=true "$API_SMOKE_SCRIPT"
|
||||
) 2>&1 | tee "$fallback_output_file" | tee -a "$LOG_FILE"
|
||||
rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
log "api-smoke base-smoke-only mode passed"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
log "api-smoke.sh failed on attempt $attempt"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "api-smoke.sh passed"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_stack() {
|
||||
local down_cmd="cd '$BARON_DIR' && $(compose_cmd "down")"
|
||||
local up_cmd="cd '$BARON_DIR' && $(compose_cmd "up -d")"
|
||||
local output_file="$LOG_DIR/compose-up.initial.log"
|
||||
|
||||
run_cmd_capture "$LOG_DIR/compose-down.initial.log" "$down_cmd" || true
|
||||
|
||||
if run_cmd_capture "$output_file" "$up_cmd"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -Eq "name conflict|is already in use by container" "$output_file"; then
|
||||
log "compose up failed due to stale or conflicting containers; attempting automatic cleanup"
|
||||
run_cmd "cd '$BARON_DIR' && $(compose_cmd "down") >/dev/null 2>&1 || true"
|
||||
run_cmd "$DOCKER_BIN ps -a --format '{{.Names}}' | grep -iE 'baron|ory' | xargs -r $DOCKER_BIN rm -f || true"
|
||||
run_cmd_capture "$LOG_DIR/compose-up.retry.log" "$up_cmd"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if grep -Eq "dependency failed to start|didn't complete successfully|is unhealthy" "$output_file"; then
|
||||
log "compose up reported dependency or health issues; continuing to stabilization checks before declaring failure"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
ensure_required_paths
|
||||
|
||||
log "Startup started: $(date)"
|
||||
|
||||
# 0) Always review the previous daily handoff before starting runtime work.
|
||||
review_daily_handoff
|
||||
review_wsl_maintenance
|
||||
|
||||
# 1) Verify Android target readiness before backend startup work
|
||||
run_android_precheck
|
||||
|
||||
# 2) Ensure networks and config rendered (if Makefile target exists)
|
||||
if [ -f "$BARON_DIR/Makefile" ]; then
|
||||
ensure_generated_config_writable
|
||||
run_cmd "cd '$BARON_DIR' && $MAKE_BIN ensure-networks || true"
|
||||
if ! run_cmd "cd '$BARON_DIR' && $MAKE_BIN render-ory-config"; then
|
||||
log "Startup aborted: make render-ory-config failed"
|
||||
exit 1
|
||||
fi
|
||||
ensure_rendered_ory_files
|
||||
fi
|
||||
|
||||
# 3) Start all necessary compose stacks, retrying once after conflict cleanup
|
||||
start_stack
|
||||
|
||||
# 4) Wait for services to stabilize
|
||||
run_cmd "sleep $WAIT"
|
||||
|
||||
# 5) Show container status
|
||||
run_cmd "$DOCKER_BIN ps --format '{{.Names}} {{.Status}}' | grep -E 'baron|ory' || true"
|
||||
|
||||
# 5-1) Start local auth broker used by the app.
|
||||
ensure_auth_server
|
||||
|
||||
# 6) Run health checks and smoke tests with retries
|
||||
startup_ok=false
|
||||
i=1
|
||||
while [ "$i" -le "$RETRIES" ]; do
|
||||
log "Health check attempt $i/$RETRIES"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
if run_check_script "$i" && run_api_smoke_script "$i"; then
|
||||
startup_ok=true
|
||||
break
|
||||
fi
|
||||
else
|
||||
log "(dry-run) would run check-baron-api-env.sh and api-smoke.sh"
|
||||
startup_ok=true
|
||||
break
|
||||
fi
|
||||
i=$((i + 1))
|
||||
if [ "$i" -le "$RETRIES" ]; then
|
||||
run_cmd "sleep $SLEEP_BETWEEN"
|
||||
fi
|
||||
done
|
||||
|
||||
# 7) Save logs
|
||||
run_cmd "cd '$BARON_DIR' && $(compose_cmd "logs --no-color") > '$LOG_DIR/baron-compose.log' 2>&1 || true"
|
||||
|
||||
if [ "$startup_ok" = false ]; then
|
||||
capture_failure_diagnostics "startup-failure"
|
||||
log "Startup failed: health checks did not pass after $RETRIES attempt(s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Startup finished: $(date)"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log "Dry-run mode: no actions were actually performed. Use --auto to run for real."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+441
@@ -0,0 +1,441 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TMP_ROOT="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_ROOT"' EXIT
|
||||
|
||||
PASS_COUNT=0
|
||||
|
||||
say() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'FAIL %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
if ! grep -Fq "$pattern" "$file"; then
|
||||
fail "expected '$pattern' in $file"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_exists_or_empty() {
|
||||
local file="$1"
|
||||
if [ -e "$file" ] && [ -s "$file" ]; then
|
||||
fail "expected empty or missing file: $file"
|
||||
fi
|
||||
}
|
||||
|
||||
new_fixture() {
|
||||
local name="$1"
|
||||
local dir="$TMP_ROOT/$name"
|
||||
mkdir -p "$dir/bin" "$dir/baron/config/.generated/ory/kratos" "$dir/baron/config/.generated/ory/oathkeeper" "$dir/logs" "$dir/state"
|
||||
touch "$dir/baron/Makefile"
|
||||
cat > "$dir/baron/config/.generated/ory/kratos/kratos.yml" <<'EOF'
|
||||
selfservice:
|
||||
allowed_return_urls:
|
||||
- http://localhost:5000
|
||||
- []
|
||||
EOF
|
||||
cat > "$dir/baron/config/.generated/ory/oathkeeper/entrypoint.sh" <<'EOF'
|
||||
#!/usr/bin/env sh
|
||||
exit 0
|
||||
EOF
|
||||
cat > "$dir/bin/docker" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log_file="${TEST_CMD_LOG:?}"
|
||||
printf 'docker %s\n' "$*" >> "$log_file"
|
||||
|
||||
case "$*" in
|
||||
"compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml up -d")
|
||||
if [ "${TEST_DOCKER_UP_FAIL_ONCE:-0}" = "1" ] && [ ! -f "${TEST_STATE_DIR:?}/docker-up.failed" ]; then
|
||||
touch "${TEST_STATE_DIR}/docker-up.failed"
|
||||
echo "name conflict"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${TEST_DOCKER_UP_HEALTH_FAILURE:-0}" = "1" ] && [ ! -f "${TEST_STATE_DIR:?}/docker-up.health-failed" ]; then
|
||||
touch "${TEST_STATE_DIR}/docker-up.health-failed"
|
||||
echo "dependency failed to start: container baron_backend is unhealthy"
|
||||
exit 1
|
||||
fi
|
||||
echo "compose up ok"
|
||||
;;
|
||||
"compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml down")
|
||||
echo "compose down ok"
|
||||
;;
|
||||
"compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml logs --no-color")
|
||||
echo "fake compose logs"
|
||||
;;
|
||||
"ps --format {{.Names}} {{.Status}}")
|
||||
printf 'baron_backend Up (healthy)\nory_kratos Up\n'
|
||||
;;
|
||||
"ps -a --format {{.Names}}")
|
||||
printf 'old_baron_backend\nold_ory_kratos\n'
|
||||
;;
|
||||
"ps --format {{.Names}}")
|
||||
printf 'baron_backend\nbaron_gateway\n'
|
||||
;;
|
||||
"rm -f old_baron_backend old_ory_kratos")
|
||||
echo "removed stale containers"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
cat > "$dir/bin/make" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'make %s\n' "$*" >> "${TEST_CMD_LOG:?}"
|
||||
EOF
|
||||
cat > "$dir/bin/adb" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'adb %s\n' "$*" >> "${TEST_CMD_LOG:?}"
|
||||
EOF
|
||||
cat > "$dir/bin/pkill" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'pkill %s\n' "$*" >> "${TEST_CMD_LOG:?}"
|
||||
EOF
|
||||
cat > "$dir/bin/chmod" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'chmod %s\n' "$*" >> "${TEST_CMD_LOG:?}"
|
||||
EOF
|
||||
cat > "$dir/bin/chown" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'chown %s\n' "$*" >> "${TEST_CMD_LOG:?}"
|
||||
EOF
|
||||
cat > "$dir/check.sh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
state_dir="${TEST_STATE_DIR:?}"
|
||||
attempt_file="$state_dir/check-attempts"
|
||||
count=0
|
||||
if [ -f "$attempt_file" ]; then
|
||||
count="$(cat "$attempt_file")"
|
||||
fi
|
||||
count=$((count + 1))
|
||||
printf '%s' "$count" > "$attempt_file"
|
||||
|
||||
mode="${CHECK_MODE:-pass}"
|
||||
case "$mode" in
|
||||
pass)
|
||||
echo "RESULT ready-ish: 0 failure(s), 0 warning(s)"
|
||||
;;
|
||||
warn_then_pass)
|
||||
if [ "$count" -eq 1 ]; then
|
||||
echo "RESULT ready-ish: 0 failure(s), 1 warning(s)"
|
||||
else
|
||||
echo "RESULT ready-ish: 0 failure(s), 0 warning(s)"
|
||||
fi
|
||||
;;
|
||||
fail)
|
||||
echo "RESULT blocked: 1 failure(s), 0 warning(s)"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "unknown CHECK_MODE=$mode" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
cat > "$dir/api-smoke.sh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
state_dir="${TEST_STATE_DIR:?}"
|
||||
attempt_file="$state_dir/api-attempts"
|
||||
count=0
|
||||
if [ -f "$attempt_file" ]; then
|
||||
count="$(cat "$attempt_file")"
|
||||
fi
|
||||
count=$((count + 1))
|
||||
printf '%s' "$count" > "$attempt_file"
|
||||
|
||||
mode="${API_MODE:-pass}"
|
||||
case "$mode" in
|
||||
pass)
|
||||
echo "PASS api smoke"
|
||||
;;
|
||||
login_failed)
|
||||
if [ "${TDC114_SKIP_AUTH_SMOKE:-false}" = "true" ]; then
|
||||
echo "SKIP authenticated smoke: TDC114_SKIP_AUTH_SMOKE=true"
|
||||
exit 0
|
||||
fi
|
||||
echo 'FAIL phone-login success: HTTP 401' >&2
|
||||
echo 'Response body:' >&2
|
||||
echo '{"error":"Login failed","code":"login_failed"}' >&2
|
||||
exit 1
|
||||
;;
|
||||
fail)
|
||||
echo "FAIL api smoke" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "unknown API_MODE=$mode" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
cat > "$dir/wsl-maintenance.sh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
mode="${1:-status}"
|
||||
case "$mode" in
|
||||
status)
|
||||
echo "${TEST_WSL_MAINTENANCE_OUTPUT:-WSL maintenance reminder: OK}"
|
||||
;;
|
||||
*)
|
||||
echo "unsupported mode: $mode" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$dir/bin/docker" "$dir/bin/make" "$dir/bin/adb" "$dir/bin/pkill" \
|
||||
"$dir/bin/chmod" "$dir/bin/chown" "$dir/check.sh" "$dir/api-smoke.sh" \
|
||||
"$dir/wsl-maintenance.sh" "$dir/baron/config/.generated/ory/oathkeeper/entrypoint.sh"
|
||||
printf '%s\n' "$dir"
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local name="$1"
|
||||
shift
|
||||
say "CASE $name"
|
||||
"$@"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
}
|
||||
|
||||
test_startup_dry_run() {
|
||||
local fixture
|
||||
fixture="$(new_fixture startup-dry-run)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_CHECK_SCRIPT="$fixture/check.sh" \
|
||||
TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \
|
||||
TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \
|
||||
TDC114_START_AUTH_SERVER=false \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
MAKE_BIN="$fixture/bin/make" \
|
||||
bash "$ROOT_DIR/scripts/startup.sh" --dry-run --wait=0 --skip-android-check
|
||||
|
||||
assert_not_exists_or_empty "$cmd_log"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "WSL maintenance reminder: OK"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "Dry-run mode: no actions were actually performed"
|
||||
}
|
||||
|
||||
test_startup_auto_retry_success() {
|
||||
local fixture
|
||||
fixture="$(new_fixture startup-auto-success)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
TEST_DOCKER_UP_FAIL_ONCE=1 \
|
||||
CHECK_MODE=warn_then_pass \
|
||||
API_MODE=pass \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_CHECK_SCRIPT="$fixture/check.sh" \
|
||||
TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \
|
||||
TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \
|
||||
TDC114_START_AUTH_SERVER=false \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
MAKE_BIN="$fixture/bin/make" \
|
||||
bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check
|
||||
|
||||
assert_file_contains "$cmd_log" "docker rm -f old_baron_backend old_ory_kratos"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "check-baron-api-env.sh reported warnings on attempt 1"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "check-baron-api-env.sh passed with zero warnings"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke.sh passed"
|
||||
if grep -Fq " - []" "$fixture/baron/config/.generated/ory/kratos/kratos.yml"; then
|
||||
fail "expected startup to sanitize invalid empty allowed_return_urls entry"
|
||||
fi
|
||||
}
|
||||
|
||||
test_startup_auto_health_failure_tolerated() {
|
||||
local fixture
|
||||
fixture="$(new_fixture startup-auto-health-failure)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
TEST_DOCKER_UP_HEALTH_FAILURE=1 \
|
||||
CHECK_MODE=pass \
|
||||
API_MODE=pass \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_CHECK_SCRIPT="$fixture/check.sh" \
|
||||
TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \
|
||||
TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \
|
||||
TDC114_START_AUTH_SERVER=false \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
MAKE_BIN="$fixture/bin/make" \
|
||||
bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check
|
||||
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "compose up reported dependency or health issues; continuing to stabilization checks before declaring failure"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke.sh passed"
|
||||
}
|
||||
|
||||
test_startup_login_failed_falls_back_to_base_smoke() {
|
||||
local fixture
|
||||
fixture="$(new_fixture startup-login-fallback)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
CHECK_MODE=pass \
|
||||
API_MODE=login_failed \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_CHECK_SCRIPT="$fixture/check.sh" \
|
||||
TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \
|
||||
TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \
|
||||
TDC114_START_AUTH_SERVER=false \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
MAKE_BIN="$fixture/bin/make" \
|
||||
bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check
|
||||
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke authenticated login failed; retrying in base-smoke-only mode because TDC114_REQUIRE_AUTH_SMOKE is false"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke base-smoke-only mode passed"
|
||||
}
|
||||
|
||||
test_startup_auto_failure() {
|
||||
local fixture
|
||||
fixture="$(new_fixture startup-auto-failure)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
set +e
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
CHECK_MODE=fail \
|
||||
API_MODE=pass \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_CHECK_SCRIPT="$fixture/check.sh" \
|
||||
TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \
|
||||
TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \
|
||||
TDC114_START_AUTH_SERVER=false \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
MAKE_BIN="$fixture/bin/make" \
|
||||
bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check
|
||||
local rc=$?
|
||||
set -e
|
||||
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
fail "startup auto failure case should exit non-zero"
|
||||
fi
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "Startup failed: health checks did not pass"
|
||||
}
|
||||
|
||||
test_wsl_maintenance_schedule() {
|
||||
local fixture
|
||||
fixture="$(new_fixture wsl-maintenance-schedule)"
|
||||
local state_dir="$fixture/state"
|
||||
|
||||
TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \
|
||||
TDC114_WSL_MAINTENANCE_ANCHOR_DATE=2026-07-16 \
|
||||
TDC114_WSL_MAINTENANCE_TODAY=2026-07-20 \
|
||||
bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" status > "$fixture/status-not-due.txt"
|
||||
|
||||
assert_file_contains "$fixture/status-not-due.txt" "WSL maintenance reminder: OK"
|
||||
assert_file_contains "$fixture/status-not-due.txt" "Next due date: 2026-07-30"
|
||||
|
||||
TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \
|
||||
TDC114_WSL_MAINTENANCE_ANCHOR_DATE=2026-07-16 \
|
||||
TDC114_WSL_MAINTENANCE_TODAY=2026-07-30 \
|
||||
bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" status > "$fixture/status-due.txt"
|
||||
|
||||
assert_file_contains "$fixture/status-due.txt" "WSL maintenance reminder: DUE"
|
||||
assert_file_contains "$fixture/status-due.txt" "./scripts/check-wsl-maintenance.sh record"
|
||||
|
||||
TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \
|
||||
TDC114_WSL_MAINTENANCE_TODAY=2026-07-30 \
|
||||
bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" record > "$fixture/record.txt"
|
||||
|
||||
assert_file_contains "$fixture/record.txt" "Recorded WSL maintenance date: 2026-07-30"
|
||||
|
||||
TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \
|
||||
TDC114_WSL_MAINTENANCE_ANCHOR_DATE=2026-07-16 \
|
||||
TDC114_WSL_MAINTENANCE_TODAY=2026-08-05 \
|
||||
bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" status > "$fixture/status-after-record.txt"
|
||||
|
||||
assert_file_contains "$fixture/status-after-record.txt" "WSL maintenance reminder: OK"
|
||||
assert_file_contains "$fixture/status-after-record.txt" "Base date: 2026-07-30"
|
||||
}
|
||||
|
||||
test_shutdown_dry_run() {
|
||||
local fixture
|
||||
fixture="$(new_fixture shutdown-dry-run)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_ADB_CONNECT_ADDRESS=172.21.128.1:5562 \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
ADB_BIN="$fixture/bin/adb" \
|
||||
PKILL_BIN="$fixture/bin/pkill" \
|
||||
CHMOD_BIN="$fixture/bin/chmod" \
|
||||
CHOWN_BIN="$fixture/bin/chown" \
|
||||
bash "$ROOT_DIR/scripts/shutdown.sh" --dry-run
|
||||
|
||||
assert_not_exists_or_empty "$cmd_log"
|
||||
assert_file_contains "$fixture/logs/$(date +%F)/shutdown.log" "Dry-run mode: no shutdown actions were executed"
|
||||
}
|
||||
|
||||
test_shutdown_auto_order_and_scope() {
|
||||
local fixture
|
||||
fixture="$(new_fixture shutdown-auto)"
|
||||
local cmd_log="$fixture/commands.log"
|
||||
|
||||
TEST_CMD_LOG="$cmd_log" \
|
||||
TEST_STATE_DIR="$fixture/state" \
|
||||
BARON_SSO_WORKTREE="$fixture/baron" \
|
||||
TDC114_LOG_BASE="$fixture/logs" \
|
||||
TDC114_ADB_CONNECT_ADDRESS=172.21.128.1:5562 \
|
||||
DOCKER_BIN="$fixture/bin/docker" \
|
||||
ADB_BIN="$fixture/bin/adb" \
|
||||
PKILL_BIN="$fixture/bin/pkill" \
|
||||
CHMOD_BIN="$fixture/bin/chmod" \
|
||||
CHOWN_BIN="$fixture/bin/chown" \
|
||||
bash "$ROOT_DIR/scripts/shutdown.sh" --auto
|
||||
|
||||
assert_file_contains "$cmd_log" "pkill -f $ROOT_DIR/scripts/integration_tests.sh"
|
||||
assert_file_contains "$cmd_log" "pkill -f $ROOT_DIR/scripts/flutter-docker.sh"
|
||||
assert_file_contains "$cmd_log" "adb disconnect 172.21.128.1:5562"
|
||||
assert_file_contains "$cmd_log" "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml logs --no-color"
|
||||
assert_file_contains "$cmd_log" "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml down"
|
||||
assert_file_contains "$cmd_log" "docker rm -f old_baron_backend old_ory_kratos"
|
||||
assert_file_contains "$cmd_log" "chmod -R u+w config/.generated/"
|
||||
assert_file_contains "$cmd_log" "chown -R"
|
||||
|
||||
local logs_line
|
||||
local down_line
|
||||
logs_line="$(grep -n "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml logs --no-color" "$cmd_log" | head -n1 | cut -d: -f1)"
|
||||
down_line="$(grep -n "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml down" "$cmd_log" | head -n1 | cut -d: -f1)"
|
||||
if [ -z "$logs_line" ] || [ -z "$down_line" ] || [ "$logs_line" -ge "$down_line" ]; then
|
||||
fail "expected compose logs collection before compose down"
|
||||
fi
|
||||
}
|
||||
|
||||
run_case "startup dry-run" test_startup_dry_run
|
||||
run_case "startup auto retry success" test_startup_auto_retry_success
|
||||
run_case "startup auto health failure tolerated" test_startup_auto_health_failure_tolerated
|
||||
run_case "startup login failure falls back" test_startup_login_failed_falls_back_to_base_smoke
|
||||
run_case "startup auto failure" test_startup_auto_failure
|
||||
run_case "wsl maintenance schedule" test_wsl_maintenance_schedule
|
||||
run_case "shutdown dry-run" test_shutdown_dry_run
|
||||
run_case "shutdown auto order and scope" test_shutdown_auto_order_and_scope
|
||||
|
||||
say "PASS $PASS_COUNT case(s)"
|
||||
Reference in New Issue
Block a user