diff --git a/.gitea/workflows/pr-security-check.yml b/.gitea/workflows/pr-security-check.yml new file mode 100644 index 0000000..1f5353e --- /dev/null +++ b/.gitea/workflows/pr-security-check.yml @@ -0,0 +1,58 @@ +name: PR -> develop + +on: + pull_request: + branches: + - main + - develop + +env: + GITLEAKS_VERSION: "8.30.1" + +jobs: + + # ── 1. Secret scan ──────────────────────────────────────────────────────────── + gitleaks: + name: Scan for secrets (Gitleaks) + runs-on: stage-gatehouse-ui + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Gitleaks + run: | + if command -v gitleaks >/dev/null 2>&1; then + echo "gitleaks already installed: $(gitleaks version)" + exit 0 + fi + curl -sSfL \ + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar xz gitleaks + mv gitleaks /usr/local/bin/gitleaks + + - name: Run secret scan + run: gitleaks detect --source . --exit-code 1 --redact --verbose --log-level debug + + # ── 2. CVE scan ─────────────────────────────────────────────────────────────── + trivy: + name: Scan for CVEs (Trivy) + runs-on: stage-gatehouse-ui + + steps: + - uses: actions/checkout@v4 + + - name: Install Trivy + run: | + command -v trivy >/dev/null 2>&1 || \ + curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ + | sh -s -- -b /usr/local/bin + + - name: Run filesystem scan + run: | + trivy fs \ + --exit-code 1 \ + --severity HIGH,CRITICAL \ + --no-progress \ + . diff --git a/.gitea/workflows/push-develop.yml b/.gitea/workflows/push-develop.yml new file mode 100644 index 0000000..e25a3c3 --- /dev/null +++ b/.gitea/workflows/push-develop.yml @@ -0,0 +1,78 @@ +name: Push -> develop + +on: + push: + branches: + - develop + +jobs: + + # ── 1. Build ────────────────────────────────────────────────────────────────── + build: + name: Build Docker image + runs-on: stage-gatehouse-ui + outputs: + tag: ${{ steps.sha.outputs.tag }} + + steps: + - uses: actions/checkout@v4 + + - name: Set image tag + id: sha + run: echo "tag=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + + - name: Build ui image + run: | + # VITE_API_BASE_URL is baked into the static bundle at build time. + # Source it from the deployed env on this (stage) runner. + set -a; . /opt/gatehouse-ui/.env; set +a + docker build \ + --build-arg VITE_API_BASE_URL="${VITE_API_BASE_URL}" \ + -t "gatehouse-ui:${{ steps.sha.outputs.tag }}" \ + -t "gatehouse-ui:latest" \ + . + + - name: Scan ui image for vulnerabilities (Trivy) + run: | + command -v trivy >/dev/null 2>&1 || \ + curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ + | sh -s -- -b /usr/local/bin + + trivy image \ + --exit-code 0 \ + --severity HIGH,CRITICAL \ + --no-progress \ + "gatehouse-ui:${{ steps.sha.outputs.tag }}" + + # ── 2. Deploy ───────────────────────────────────────────────────────────────── + deploy: + name: Deploy + runs-on: stage-gatehouse-ui + needs: build + env: + COMPOSE_DIR: /opt/gatehouse-ui + + steps: + - uses: actions/checkout@v4 + + - name: Deploy (docker compose up) + run: | + cp docker-compose.yml "${COMPOSE_DIR}/docker-compose.yml" + cd "${COMPOSE_DIR}" + IMAGE_TAG="${{ needs.build.outputs.tag }}" docker compose up -d --remove-orphans + + # ── 3. Alert ────────────────────────────────────────────────────────────────── + alert: + name: Notify on result + runs-on: stage-gatehouse-ui + needs: deploy + if: always() + + steps: + - name: Send notification + run: | + STATUS="${{ needs.deploy.result }}" + echo "TODO: send alert — deploy status: ${STATUS}" + # curl -X POST "${{ secrets.ALERT_WEBHOOK }}" \ + # -H 'Content-Type: application/json' \ + # -d "{\"text\": \"[gatehouse-ui] Deploy ${STATUS} — tag: ${{ needs.build.outputs.tag }}\"}" diff --git a/.gitea/workflows/push-main.yml b/.gitea/workflows/push-main.yml new file mode 100644 index 0000000..1b5dded --- /dev/null +++ b/.gitea/workflows/push-main.yml @@ -0,0 +1,78 @@ +name: Push -> main + +on: + push: + branches: + - main + +jobs: + + # ── 1. Build ────────────────────────────────────────────────────────────────── + build: + name: Build Docker image + runs-on: prod-gatehouse-ui + outputs: + tag: ${{ steps.sha.outputs.tag }} + + steps: + - uses: actions/checkout@v4 + + - name: Set image tag + id: sha + run: echo "tag=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + + - name: Build ui image + run: | + # VITE_API_BASE_URL is baked into the static bundle at build time. + # Source it from the deployed env on this (prod) runner. + set -a; . /opt/gatehouse-ui/.env; set +a + docker build \ + --build-arg VITE_API_BASE_URL="${VITE_API_BASE_URL}" \ + -t "gatehouse-ui:${{ steps.sha.outputs.tag }}" \ + -t "gatehouse-ui:latest" \ + . + + - name: Scan ui image for vulnerabilities (Trivy) + run: | + command -v trivy >/dev/null 2>&1 || \ + curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ + | sh -s -- -b /usr/local/bin + + trivy image \ + --exit-code 0 \ + --severity HIGH,CRITICAL \ + --no-progress \ + "gatehouse-ui:${{ steps.sha.outputs.tag }}" + + # ── 2. Deploy ───────────────────────────────────────────────────────────────── + deploy: + name: Deploy + runs-on: prod-gatehouse-ui + needs: build + env: + COMPOSE_DIR: /opt/gatehouse-ui + + steps: + - uses: actions/checkout@v4 + + - name: Deploy (docker compose up) + run: | + cp docker-compose.yml "${COMPOSE_DIR}/docker-compose.yml" + cd "${COMPOSE_DIR}" + IMAGE_TAG="${{ needs.build.outputs.tag }}" docker compose up -d --remove-orphans + + # ── 3. Alert ────────────────────────────────────────────────────────────────── + alert: + name: Notify on result + runs-on: prod-gatehouse-ui + needs: deploy + if: always() + + steps: + - name: Send notification + run: | + STATUS="${{ needs.deploy.result }}" + echo "TODO: send alert — deploy status: ${STATUS}" + # curl -X POST "${{ secrets.ALERT_WEBHOOK }}" \ + # -H 'Content-Type: application/json' \ + # -d "{\"text\": \"[gatehouse-ui] Deploy ${STATUS} — tag: ${{ needs.build.outputs.tag }}\"}" diff --git a/Dockerfile b/Dockerfile index bf123fa..d7094d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,9 @@ RUN bun run build # Production stage FROM nginx:alpine AS production +# Patch inherited Alpine OS packages to clear known CVEs not yet in the base image +RUN apk upgrade --no-cache + COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/docker-compose.yml b/docker-compose.yml index 02048f0..25a6c23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,7 @@ services: ui: - build: - context: . - args: - - VITE_API_BASE_URL=${VITE_API_BASE_URL} + image: gatehouse-ui:${IMAGE_TAG:-latest} container_name: gatehouse-ui - env_file: - - .env ports: - "8080:8080" restart: unless-stopped