ci: optimera pipelines + automatisk produktionssmoke #447

Merged
admin merged 1 commits from chore/optimize-pipelines into main 2026-11-02 17:25:58 +00:00
7 changed files with 138 additions and 2 deletions
+8
View File
@@ -18,6 +18,14 @@ on:
push:
branches: [main]
workflow_dispatch:
# Never let two deploys race the same Coolify app. Unlike CI we do NOT cancel an
# in-flight deploy (it may already be restarting the container); the newest
# commit still wins because Coolify builds from the branch head.
concurrency:
group: deploy-0pt-main
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
+11 -1
View File
@@ -17,10 +17,16 @@ name: CI (SIAX Cloud)
# push/PR/weekly scanner (this file is push/PR-only).
on:
push:
branches: ["**"]
branches: ["main"]
pull_request:
workflow_dispatch:
# One run per ref; a branch push that also updates a PR used to start two full
# quality runs (and, on main, two deploy chains). Cancel the superseded one.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
@@ -30,6 +36,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
cache-dependency-path: pnpm-lock.yaml
- name: Enable corepack + pin pnpm@9 (pnpm-lock.yaml is lockfileVersion 9.0)
run: |
corepack enable
@@ -126,6 +134,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
cache-dependency-path: pnpm-lock.yaml
- name: Enable corepack + pin pnpm@9
run: |
corepack enable
+8
View File
@@ -8,14 +8,21 @@ on:
schedule:
- cron: "0 4 * * *" # daily 04:00 UTC
concurrency:
group: nightly-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
cache-dependency-path: pnpm-lock.yaml
- name: Enable corepack + pin pnpm@9
run: |
corepack enable
@@ -34,6 +41,7 @@ jobs:
security:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Install Trivy
+77
View File
@@ -0,0 +1,77 @@
name: Production smoke (0pt.app)
# Automated end-to-end delivery verification. Every 30 minutes (and on demand)
# this checks that the live service is actually ready and that the running
# container is the commit that main points at — so a green deploy is never
# confused with a healthy, current service.
on:
workflow_dispatch:
schedule:
- cron: "*/30 * * * *"
concurrency:
group: prod-smoke
cancel-in-progress: true
jobs:
smoke:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Readiness (retry on a fresh deploy)
run: |
set -eu
for attempt in 1 2 3 4 5 6; do
body="$(curl -fsS --max-time 15 https://0pt.app/api/ready || true)"
echo "attempt $attempt: ${body:-<no response>}"
if printf '%s' "$body" | grep -q '"ready":true'; then
exit 0
fi
sleep 20
done
echo "::error::0pt.app/api/ready never reported ready:true"
exit 1
- name: Health envelope
run: |
set -eu
body="$(curl -fsS --max-time 15 https://0pt.app/api/health)"
echo "$body"
printf '%s' "$body" | grep -q '"db":"ok"' || { echo "::error::database is not ok"; exit 1; }
printf '%s' "$body" | grep -q '"stripe":"ok"' || { echo "::error::stripe is not ok"; exit 1; }
- name: Deployed commit equals main
env:
SIAX_CI_TOKEN: ${{ secrets.SIAX_CI_TOKEN }}
run: |
set -eu
version="$(curl -fsS --max-time 15 https://0pt.app/api/version)"
echo "$version"
running="$(printf '%s' "$version" | sed -n 's/.*"commit":"\([0-9a-f]*\)".*/\1/p')"
[ -n "$running" ] || { echo "::error::/api/version returned no commit"; exit 1; }
if [ -z "${SIAX_CI_TOKEN:-}" ]; then
echo "::warning::SIAX_CI_TOKEN is not set — cannot compare with main; commit $running is live"
exit 0
fi
expected="$(curl -fsS --max-time 15 \
-H "Authorization: token ${SIAX_CI_TOKEN}" \
https://git.siax.io/api/v1/repos/admin/0pt/commits/main \
| sed -n 's/.*"sha":"\([0-9a-f]*\)".*/\1/p')"
echo "running=$running expected(main)=$expected"
[ -n "$expected" ] || { echo "::error::could not read main SHA from Gitea"; exit 1; }
[ "$running" = "$expected" ] || {
echo "::error::production is $running but main is $expected — deploy did not land"
exit 1
}
- name: Document pipeline surface responds (auth required, never 404/5xx)
run: |
set -eu
for path in /api/0pt/documents/upload /api/0pt/documents/structure /api/0pt/documents/models /api/0pt/credits/balance; do
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 -X POST "https://0pt.app${path}")"
echo "$path -> $code"
case "$code" in
401|405) ;; # unauthenticated or method guard: the route exists
*) echo "::error::$path returned $code (expected 401/405)"; exit 1 ;;
esac
done
+5
View File
@@ -39,6 +39,11 @@ on:
branches: [main]
workflow_dispatch:
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
IMAGE_REPO: git.cloud.siax.io/sax3l/0pt
DOCKERFILE: Dockerfile
+5 -1
View File
@@ -19,12 +19,16 @@ name: Security Scan (SIAX Cloud)
# and docs/compliance/GOLDEN-STANDARD-REPORT.md control C6 for full context.
on:
push:
branches: ["**"]
branches: ["main"]
pull_request:
workflow_dispatch:
schedule:
- cron: "0 5 * * 1" # weekly Monday re-scan
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true
jobs:
security:
runs-on: ubuntu-latest
+24
View File
@@ -0,0 +1,24 @@
import { NextResponse } from 'next/server';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
/**
* GET /api/version — deployment identity for automated verification.
*
* Returns only non-sensitive build identity (no env values, no config). The
* scheduled prod-smoke workflow compares `commit` with the current main SHA so
* "the container is up" is never confused with "the intended commit is live".
*/
export async function GET() {
const commit =
process.env.SOURCE_COMMIT ?? process.env.GIT_COMMIT_SHA ?? process.env.VERCEL_GIT_COMMIT_SHA ?? null;
return NextResponse.json(
{
service: '0pt-web',
commit,
built_at: process.env.BUILD_TIME ?? null,
},
{ headers: { 'cache-control': 'no-store' } },
);
}