diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index d675a07..d9a0a26 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -129,7 +129,47 @@ jobs: --push \ fe/web/teller-app - # --- Customer (Next.js) — enable when ready --- - # build-customer: - # runs-on: docker - # ... + build-customer: + runs-on: docker + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Docker registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | \ + docker login git.koppkb.com \ + -u "${{ secrets.REGISTRY_USERNAME }}" \ + --password-stdin + + - name: Build & push customer frontend image + run: | + set -e + + IMAGE="git.koppkb.com/kopkb/qms-fe-customer" + TAGS="-t ${IMAGE}:${{ gitea.sha }}" + API_URL="${{ secrets.NEXT_PUBLIC_API_URL }}" + if [ -z "${API_URL}" ]; then + API_URL="${{ secrets.VITE_API_URL }}" + fi + + if [ -z "${API_URL}" ]; then + echo "ERROR: Set Gitea secret NEXT_PUBLIC_API_URL or VITE_API_URL (e.g. http://YOUR_SERVER_IP:8080)" + exit 1 + fi + + if [ "${{ gitea.ref_type }}" = "tag" ] && echo "${{ gitea.ref_name }}" | grep -q '^v'; then + TAGS="${TAGS} -t ${IMAGE}:${{ gitea.ref_name }}" + fi + + echo "Building and pushing customer: ${TAGS}" + docker buildx build \ + --platform linux/amd64 \ + --build-arg NEXT_PUBLIC_API_URL="${API_URL}" \ + --cache-from type=registry,ref=${IMAGE}:buildcache,ignore-error=true \ + --cache-to type=registry,ref=${IMAGE}:buildcache,mode=max \ + -f fe/web/customer-app/Dockerfile \ + ${TAGS} \ + --push \ + fe/web/customer-app diff --git a/.gitea/workflows/fe-deploy.yml b/.gitea/workflows/fe-deploy.yml index 0f1a56b..5e79161 100644 --- a/.gitea/workflows/fe-deploy.yml +++ b/.gitea/workflows/fe-deploy.yml @@ -1,6 +1,6 @@ name: Deploy Frontend to Production -# Manual production deploy — pulls pre-built admin + teller images from the registry +# Manual production deploy — pulls pre-built admin + teller + customer images on: workflow_dispatch: inputs: @@ -12,6 +12,7 @@ on: env: ADMIN_IMAGE: git.koppkb.com/kopkb/qms-fe-admin TELLER_IMAGE: git.koppkb.com/kopkb/qms-fe-teller + CUSTOMER_IMAGE: git.koppkb.com/kopkb/qms-fe-customer DEPLOY_DIR: /home/arrahn/Project/qms-fe jobs: @@ -36,7 +37,7 @@ jobs: set -e IMAGE_TAG="${{ inputs.image_tag }}" - for IMAGE in "${ADMIN_IMAGE}:${IMAGE_TAG}" "${TELLER_IMAGE}:${IMAGE_TAG}"; do + for IMAGE in "${ADMIN_IMAGE}:${IMAGE_TAG}" "${TELLER_IMAGE}:${IMAGE_TAG}" "${CUSTOMER_IMAGE}:${IMAGE_TAG}"; do echo "Checking if image exists: ${IMAGE}" if ! docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then echo "ERROR: Image ${IMAGE} does not exist in registry!" @@ -51,6 +52,7 @@ jobs: IMAGE_TAG="${{ inputs.image_tag }}" docker pull "${ADMIN_IMAGE}:${IMAGE_TAG}" docker pull "${TELLER_IMAGE}:${IMAGE_TAG}" + docker pull "${CUSTOMER_IMAGE}:${IMAGE_TAG}" echo "✓ Images pulled successfully" - name: Sync compose file and deploy @@ -72,8 +74,9 @@ jobs: export IMAGE_TAG export ADMIN_IMAGE export TELLER_IMAGE + export CUSTOMER_IMAGE - docker compose -f docker-compose.yml pull admin teller + docker compose -f docker-compose.yml pull admin teller customer docker compose -f docker-compose.yml up -d --remove-orphans echo "✓ Deployed IMAGE_TAG=${IMAGE_TAG}" @@ -84,9 +87,9 @@ jobs: cd "${DEPLOY_DIR}" echo "Waiting for services to start..." - sleep 5 + sleep 8 - for SERVICE in admin teller; do + for SERVICE in admin teller customer; do STATUS=$(docker compose -f docker-compose.yml ps --status running --format '{{.Name}}' "$SERVICE" 2>/dev/null || true) if [ -z "$STATUS" ]; then echo "ERROR: ${SERVICE} is not running" @@ -99,8 +102,10 @@ jobs: ADMIN_HOST_PORT=$(grep -E '^ADMIN_HOST_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true) TELLER_HOST_PORT=$(grep -E '^TELLER_HOST_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true) + CUSTOMER_HOST_PORT=$(grep -E '^CUSTOMER_HOST_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true) ADMIN_HOST_PORT=${ADMIN_HOST_PORT:-5000} TELLER_HOST_PORT=${TELLER_HOST_PORT:-3001} + CUSTOMER_HOST_PORT=${CUSTOMER_HOST_PORT:-3000} check_http() { NAME=$1 @@ -120,3 +125,4 @@ jobs: check_http "admin" "${ADMIN_HOST_PORT}" check_http "teller" "${TELLER_HOST_PORT}" + check_http "customer" "${CUSTOMER_HOST_PORT}" diff --git a/fe/web/.env.example b/fe/web/.env.example index acc4eff..deef96d 100644 --- a/fe/web/.env.example +++ b/fe/web/.env.example @@ -1,12 +1,18 @@ # Host ports (change if they collide on the single server) ADMIN_HOST_PORT=5000 TELLER_HOST_PORT=3001 +CUSTOMER_HOST_PORT=3000 -# Baked into the Vite images at build time (set in Gitea secret/variable VITE_API_URL too) +# Baked into images at build time (set matching Gitea secrets too) VITE_API_URL=http://YOUR_SERVER_IP:8080 VITE_CUSTOMER_APP_URL=http://YOUR_SERVER_IP:3000 +NEXT_PUBLIC_API_URL=http://YOUR_SERVER_IP:8080 + +# Runtime (customer Next.js server) +QR_TOKEN_SECRET=qms-branch-qr-v1 # Registry images (used by compose / deploy) ADMIN_IMAGE=git.koppkb.com/kopkb/qms-fe-admin TELLER_IMAGE=git.koppkb.com/kopkb/qms-fe-teller +CUSTOMER_IMAGE=git.koppkb.com/kopkb/qms-fe-customer IMAGE_TAG=local diff --git a/fe/web/.env.production b/fe/web/.env.production index ed02fa8..a34d563 100644 --- a/fe/web/.env.production +++ b/fe/web/.env.production @@ -1,13 +1,17 @@ # Host ports (change if they collide on the single server) ADMIN_HOST_PORT=5000 TELLER_HOST_PORT=3001 +CUSTOMER_HOST_PORT=3000 -# Replace YOUR_SERVER_IP with the production server IP (API is on :8080) -# Also set the same value as Gitea secret/variable VITE_API_URL for CI image builds VITE_API_URL=http://172.16.6.200:8080 VITE_CUSTOMER_APP_URL=http://172.16.6.200:3000 +NEXT_PUBLIC_API_URL=http://172.16.6.200:8080 + +# Runtime (customer Next.js server) +QR_TOKEN_SECRET=qms-branch-qr-v1 # Registry images (used by compose / deploy) ADMIN_IMAGE=git.koppkb.com/kopkb/qms-fe-admin TELLER_IMAGE=git.koppkb.com/kopkb/qms-fe-teller -IMAGE_TAG=local +CUSTOMER_IMAGE=git.koppkb.com/kopkb/qms-fe-customer +IMAGE_TAG=local \ No newline at end of file diff --git a/fe/web/customer-app/.dockerignore b/fe/web/customer-app/.dockerignore new file mode 100644 index 0000000..7a3fdff --- /dev/null +++ b/fe/web/customer-app/.dockerignore @@ -0,0 +1,10 @@ +node_modules +.next +.git +.gitignore +*.md +.env +.env.* +!.env.example +.DS_Store +tsconfig.tsbuildinfo diff --git a/fe/web/customer-app/Dockerfile b/fe/web/customer-app/Dockerfile new file mode 100644 index 0000000..4e32717 --- /dev/null +++ b/fe/web/customer-app/Dockerfile @@ -0,0 +1,35 @@ +# Build stage +FROM node:20-bookworm AS build +WORKDIR /app + +RUN corepack enable && corepack prepare pnpm@9 --activate + +ARG NEXT_PUBLIC_API_URL=http://localhost:8080 +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY . . +RUN pnpm build + +# Runtime stage +FROM node:20-bookworm-slim AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +COPY --from=build /app/public ./public +COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/fe/web/customer-app/next.config.ts b/fe/web/customer-app/next.config.ts index ca6c939..ead555b 100644 --- a/fe/web/customer-app/next.config.ts +++ b/fe/web/customer-app/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { devIndicators: false, + output: "standalone", }; export default nextConfig; diff --git a/fe/web/customer-app/package.json b/fe/web/customer-app/package.json index f3d53dc..71e94ca 100644 --- a/fe/web/customer-app/package.json +++ b/fe/web/customer-app/package.json @@ -22,5 +22,11 @@ "eslint-config-next": "16.2.10", "tailwindcss": "^4", "typescript": "^5" + }, + "pnpm": { + "ignoredBuiltDependencies": [ + "sharp", + "unrs-resolver" + ] } } diff --git a/fe/web/customer-app/pnpm-workspace.yaml b/fe/web/customer-app/pnpm-workspace.yaml deleted file mode 100644 index 581a9d5..0000000 --- a/fe/web/customer-app/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -ignoredBuiltDependencies: - - sharp - - unrs-resolver diff --git a/fe/web/docker-compose.yml b/fe/web/docker-compose.yml index 39f315b..f27c26c 100644 --- a/fe/web/docker-compose.yml +++ b/fe/web/docker-compose.yml @@ -23,3 +23,17 @@ services: restart: unless-stopped ports: - "${TELLER_HOST_PORT:-3001}:80" + + customer: + image: ${CUSTOMER_IMAGE:-qms-fe-customer}:${IMAGE_TAG:-local} + build: + context: ./customer-app + dockerfile: Dockerfile + args: + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8080} + container_name: qms-fe-customer + restart: unless-stopped + ports: + - "${CUSTOMER_HOST_PORT:-3000}:3000" + environment: + QR_TOKEN_SECRET: ${QR_TOKEN_SECRET:-qms-branch-qr-v1}