diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index 29d0502..3f544e8 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -90,23 +90,31 @@ jobs: IMAGE_URI="${{ steps.cf.outputs.ecr_repo_uri }}:${{ github.sha }}" docker push "$IMAGE_URI" - - name: Write MongoDB URI to SSM (SecureString) + - name: Sync all secrets to SSM Parameter Store shell: bash - env: - MONGO_URI: ${{ secrets.MongoURI }} run: | set -euo pipefail - - if [ -z "${MONGO_URI:-}" ]; then - echo "MongoURI secret is missing/empty" - exit 1 - fi - - aws ssm put-parameter \ - --name "${{ steps.cf.outputs.mongo_param_name }}" \ - --type "SecureString" \ - --value "$MONGO_URI" \ - --overwrite + PARAM_PREFIX="/comptaleyes/dev" + + write_secret() { + local name=$1 + local value=$2 + if [ -n "${value}" ] && [ "${value}" != "" ]; then + echo "✓ $name" + aws ssm put-parameter --name "${PARAM_PREFIX}/${name}" --type "SecureString" --value "${value}" --overwrite >/dev/null + fi + } + + echo "📤 Syncing secrets to SSM..." + write_secret "mongodbUri" "${{ secrets.MONGO_URI }}" + write_secret "jwtSecret" "${{ secrets.JWT_SECRET }}" + write_secret "smtpHost" "${{ secrets.SMTP_HOST }}" + write_secret "smtpPort" "${{ secrets.SMTP_PORT }}" + write_secret "smtpUser" "${{ secrets.SMTP_USER }}" + write_secret "smtpPassword" "${{ secrets.SMTP_PASSWORD }}" + write_secret "smtpFrom" "${{ secrets.SMTP_FROM }}" + # Add any new secret here - just add to GitHub Secrets UI + echo "✅ Done" - name: Deploy on EC2 via SSM RunCommand (pull + restart container) shell: bash @@ -116,9 +124,6 @@ jobs: INSTANCE_ID="${{ steps.cf.outputs.instance_id }}" IMAGE_URI="${{ steps.cf.outputs.ecr_repo_uri }}:${{ github.sha }}" REGION="us-east-1" - MONGO_PARAM="${{ steps.cf.outputs.mongo_param_name }}" - - # <= 100 chars COMMENT="Deploy backend ${GITHUB_SHA::7}" SCRIPT=$(cat <<'EOF' @@ -129,38 +134,45 @@ jobs: HOST_PORT="3000" CONTAINER_PORT="3000" IMAGE_URI="__IMAGE_URI__" - MONGO_PARAM="__MONGO_PARAM__" - NODE_ENV_VALUE="production" + PARAM_PREFIX="/comptaleyes/dev" + + echo "[1/7] Fetch ALL parameters from SSM" + PARAMS_JSON=$(aws ssm get-parameters-by-path --path "$PARAM_PREFIX" --with-decryption --region "$REGION" --query 'Parameters[*].[Name,Value]' --output json) - echo "[1/7] Fetch Mongo URI from SSM" - MONGO_URI="$(aws ssm get-parameter --name "$MONGO_PARAM" --with-decryption --query 'Parameter.Value' --output text)" - if [ -z "$MONGO_URI" ] || [ "$MONGO_URI" = "None" ]; then - echo "Mongo URI is empty; aborting deploy" + if [ "$(echo "$PARAMS_JSON" | jq 'length')" -eq 0 ]; then + echo "❌ No parameters found" exit 1 fi + echo "✅ Found $(echo "$PARAMS_JSON" | jq 'length') parameters" + echo "[2/7] Ensure docker is running" sudo systemctl start docker || true + echo "[3/7] Login to ECR" echo "[3/7] Login to ECR" aws ecr get-login-password --region "$REGION" | sudo docker login --username AWS --password-stdin "$(echo "$IMAGE_URI" | cut -d/ -f1)" + echo "[4/7] Pull image" echo "[4/7] Pull image" sudo docker pull "$IMAGE_URI" - echo "[5/7] Stop/remove old container if exists" + echo "[5/7] Stop/remove old container" sudo docker rm -f "$APP_NAME" || true - echo "[6/7] Run new container" - sudo docker run -d \ - --name "$APP_NAME" \ - --restart unless-stopped \ - -p "${HOST_PORT}:${CONTAINER_PORT}" \ - -e MONGO_URI="$MONGO_URI" \ - -e NODE_ENV="$NODE_ENV_VALUE" \ - "$IMAGE_URI" + echo "[6/7] Run container with ALL SSM env vars" + ENV_FLAGS="-e PORT=$CONTAINER_PORT -e SERVICE_NAME=comptaleyes-backend" + + while IFS= read -r line; do + PARAM_NAME=$(echo "$line" | jq -r '.[0]' | sed 's|.*/||') + PARAM_VALUE=$(echo "$line" | jq -r '.[1]') + ENV_VAR=$(echo "$PARAM_NAME" | sed 's/Uri$/URI/' | sed 's/\([a-z]\)\([A-Z]\)/\1_\2/g' | tr '[:lower:]' '[:upper:]') + ENV_FLAGS="$ENV_FLAGS -e ${ENV_VAR}=\"${PARAM_VALUE}\"" + done < <(echo "$PARAMS_JSON" | jq -c '.[]') + + eval sudo docker run -d --name "$APP_NAME" --restart unless-stopped -p "${HOST_PORT}:${CONTAINER_PORT}" $ENV_FLAGS "$IMAGE_URI" - echo "[7/7] Show running container" + echo "[7/7] Container status" sudo docker ps --filter "name=$APP_NAME" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" EOF ) diff --git a/.gitignore b/.gitignore index 316cba1..5c6469e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ cdk.out/ .env.* .DS_Store +scripts/ \ No newline at end of file diff --git a/backend/src/core/config/env.schema.ts b/backend/src/core/config/env.schema.ts index 1f1dd33..294504a 100644 --- a/backend/src/core/config/env.schema.ts +++ b/backend/src/core/config/env.schema.ts @@ -4,6 +4,9 @@ export const envSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), PORT: z.coerce.number().int().positive().default(3000), + // Database + MONGO_URI: z.string().url().optional(), + // Optional but common; just a placeholder for later SERVICE_NAME: z.string().min(1).default('nest-backend-service'), });