Author SHA1 Message Date
ITWorx AppOps Automation 885dff413c ci: retire legacy unraid deploy runner workflow
MobilityOps acceptance / acceptance (pull_request) Successful in 33s
2026-09-14 14:26:23 +02:00
Jens f8a30c0f5c Merge pull request 'hygiene: prepare public release' (#10) from chore/public-release-hygiene-20260902 into master
Unraid autoredeploy / Deploy mobilityops (push) Failing after 3s
2026-09-03 00:58:52 +00:00
NuklearRabbit 2d92084489 test: stabilize browser assertions under CI load
MobilityOps acceptance / acceptance (pull_request) Successful in 8m45s
2026-09-03 01:13:21 +02:00
NuklearRabbit a42012d9c0 hygiene: prepare MobilityOps for public release
MobilityOps acceptance / acceptance (pull_request) Failing after 10m16s
2026-09-02 23:44:03 +02:00
Jens 0de9177e7c Merge pull request 'Use Gitea-compatible canary evidence upload' (#3) from codex/resolve-projectbrain-findings-20260828 into master
Unraid autoredeploy / Deploy mobilityops (push) Failing after 1m11s
Reviewed-on: #3
2026-09-01 20:09:22 +00:00
NuklearRabbit 48147465b5 security: refresh Alpine runtime packages
MobilityOps acceptance / acceptance (pull_request) Successful in 6m28s
2026-09-01 21:19:48 +02:00
NuklearRabbit 92bdfb421b Merge remote-tracking branch 'origin/master' into codex/resolve-projectbrain-findings-20260828
MobilityOps acceptance / acceptance (pull_request) Failing after 2m42s
# Conflicts:
#	.dockerignore
#	.gitea/workflows/ci.yml
#	.gitea/workflows/live-canary.yml
#	.gitea/workflows/release.yml
2026-09-01 20:42:10 +02:00
Jens 4257fea9d6 Merge pull request 'chore: sanitize MobilityOps operational evidence and public boundary' (#8) from chatgpt/repo-hygiene-mobilityops into master
Unraid autoredeploy / Deploy mobilityops (push) Failing after 1h58m50s
Reviewed-on: #8
2026-09-01 14:29:25 +02:00
NuklearRabbit e8eed3e641 fix(ci): package contract fixtures and recover deploy chunk races
MobilityOps acceptance / backend (pull_request) Failing after 2m29s
MobilityOps acceptance / frontend (pull_request) Successful in 1m10s
MobilityOps acceptance / e2e (pull_request) Skipped
Managed validation / full (pull_request) Successful in 3s
2026-08-29 00:40:51 +02:00
NuklearRabbit 2918608240 fix(ci): isolate contract and image scans from host paths
MobilityOps acceptance / backend (pull_request) Failing after 2m20s
MobilityOps acceptance / frontend (pull_request) Successful in 1m24s
MobilityOps acceptance / e2e (pull_request) Skipped
Managed validation / full (pull_request) Canceled after 0s
2026-08-29 00:27:17 +02:00
NuklearRabbit a99aed9a5f perf(ci): deduplicate branch validation
MobilityOps acceptance / backend (pull_request) Canceled after 0s
MobilityOps acceptance / frontend (pull_request) Canceled after 0s
MobilityOps acceptance / e2e (pull_request) Canceled after 0s
Managed validation / full (pull_request) Canceled after 0s
2026-08-28 23:59:30 +02:00
NuklearRabbit 00e8ec001b fix(ci): run secret scan inside isolated workspace
Managed validation / full (pull_request) Canceled after 0s
MobilityOps acceptance / frontend (pull_request) Successful in 1m14s
MobilityOps acceptance / backend (pull_request) Failing after 4m7s
MobilityOps acceptance / e2e (pull_request) Skipped
2026-08-28 23:57:46 +02:00
NuklearRabbit d20ff7a243 fix(ci): upgrade pinned Trivy action
Managed validation / full (pull_request) Canceled after 0s
MobilityOps acceptance / backend (pull_request) Failing after 32s
MobilityOps acceptance / frontend (pull_request) Successful in 1m1s
MobilityOps acceptance / e2e (pull_request) Skipped
2026-08-28 23:42:59 +02:00
NuklearRabbit bc0951115b fix(ci): use Gitea-compatible canary artifacts
Managed validation / full (pull_request) Successful in 10s
MobilityOps acceptance / frontend (pull_request) Successful in 1m17s
MobilityOps acceptance / backend (pull_request) Failing after 1m18s
MobilityOps acceptance / e2e (pull_request) Skipped
2026-08-28 23:11:09 +02:00
19 changed files with 111 additions and 47 deletions
+8
View File
@@ -19,6 +19,14 @@ frontend/node_modules
frontend/dist frontend/dist
dist dist
artifacts artifacts
docs
deploy
n8n/**
!n8n/workflows/
!n8n/workflows/**
frontend
*.tgz
*.tar.gz
coverage coverage
playwright-report playwright-report
test-results test-results
+4
View File
@@ -86,6 +86,10 @@ N8N_ENCRYPTION_KEY=replace-me
N8N_BASIC_AUTH_ACTIVE=true N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=change-me N8N_BASIC_AUTH_PASSWORD=change-me
# Recipient and sender used by the importable alert workflow. Configure real,
# monitored addresses in the deployment environment; repository defaults stay synthetic.
MOBILITYOPS_ALERT_RECIPIENT=alerts@example.test
MOBILITYOPS_ALERT_SENDER=n8n@example.test
MOBILITYOPS_CALLBACK_TOKEN=replace-me-n8n-callback-token MOBILITYOPS_CALLBACK_TOKEN=replace-me-n8n-callback-token
# Sent as the X-Fleet-Ops-Trigger-Token header when Fleet Ops calls the n8n return- # Sent as the X-Fleet-Ops-Trigger-Token header when Fleet Ops calls the n8n return-
# processing webhook, so the webhook trigger can require Header Auth instead of being # processing webhook, so the webhook trigger can require Header Auth instead of being
+1 -1
View File
@@ -14,7 +14,7 @@ permissions:
jobs: jobs:
chromium: chromium:
runs-on: ubuntu-latest runs-on: linux-validation
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
+33 -6
View File
@@ -12,7 +12,9 @@ permissions:
jobs: jobs:
acceptance: acceptance:
runs-on: ubuntu-latest # Never execute code from an untrusted public fork on the private runner.
if: ${{ gitea.event.pull_request.head.repo.full_name == gitea.repository }}
runs-on: linux-validation
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
@@ -31,10 +33,19 @@ jobs:
echo "Product or test change: running the complete acceptance gate." echo "Product or test change: running the complete acceptance gate."
fi fi
- name: Secret scan - name: Secret scan
uses: trufflesecurity/trufflehog@b9dd330365132cd2d01dd5dc8a857a056a2544e1 # v3.79.0 shell: bash
with: run: |
path: ./ set -euo pipefail
extra_args: --only-verified repository="$PWD"
source="file:///repo"
workspace=(-v "$repository:/repo" -w /repo)
if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then
source="file://$repository"
workspace=(--volumes-from "$HOSTNAME" -w "$repository")
fi
docker run --rm "${workspace[@]}" \
ghcr.io/trufflesecurity/trufflehog@sha256:7104dbb84d1ad2f5f6fa1134e92c6aa6f701f0a4ac2efd5a4c5c96225d899fe3 \
git "$source" --fail --no-update --github-actions --only-verified
- name: Backend tests in isolated PostgreSQL stack - name: Backend tests in isolated PostgreSQL stack
if: steps.scope.outputs.full == 'true' if: steps.scope.outputs.full == 'true'
run: sh scripts/run-isolated-tests.sh run: sh scripts/run-isolated-tests.sh
@@ -44,8 +55,24 @@ jobs:
docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --build --rm api ruff check app tests docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --build --rm api ruff check app tests
docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --rm api mypy app docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --rm api mypy app
docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --rm \ docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --rm \
-v "$PWD:/repo:ro" api python /repo/scripts/check-contracts.py api python scripts/check-contracts.py
python scripts/check-source-budgets.py python scripts/check-source-budgets.py
- name: Build production API image for vulnerability scan
if: steps.scope.outputs.full == 'true'
run: |
docker build --target runtime --build-arg VCS_REF="$GITHUB_SHA" \
--tag mobilityops-api-ci --file backend/Dockerfile .
- name: Production API image vulnerability scan (HIGH/CRITICAL)
if: steps.scope.outputs.full == 'true'
run: bash scripts/scan-ci-image.sh mobilityops-api-ci
- name: Build production web image for vulnerability scan
if: steps.scope.outputs.full == 'true'
run: |
docker build --build-arg VCS_REF="$GITHUB_SHA" \
--tag mobilityops-web-ci frontend
- name: Production web image vulnerability scan (HIGH/CRITICAL)
if: steps.scope.outputs.full == 'true'
run: bash scripts/scan-ci-image.sh mobilityops-web-ci
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
if: steps.scope.outputs.full == 'true' if: steps.scope.outputs.full == 'true'
with: with:
+1 -1
View File
@@ -14,7 +14,7 @@ permissions:
jobs: jobs:
public-probe: public-probe:
runs-on: ubuntu-latest runs-on: linux-validation
timeout-minutes: 3 timeout-minutes: 3
steps: steps:
- name: Verify HTTPS readiness and certificate horizon - name: Verify HTTPS readiness and certificate horizon
+3 -1
View File
@@ -1,6 +1,8 @@
name: Managed validation name: Managed validation
on: on:
push:
branches: [master]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
profile: profile:
@@ -20,7 +22,7 @@ concurrency:
jobs: jobs:
full: full:
name: full name: full
runs-on: ubuntu-latest runs-on: linux-validation
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs: jobs:
release-evidence: release-evidence:
runs-on: ubuntu-latest runs-on: linux-validation
steps: steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Build commit-labelled release images - name: Build commit-labelled release images
+1 -1
View File
@@ -14,7 +14,7 @@ permissions:
jobs: jobs:
images: images:
runs-on: ubuntu-latest runs-on: linux-validation
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
-31
View File
@@ -1,31 +0,0 @@
name: Unraid autoredeploy
on:
push:
branches: [master]
paths-ignore:
- ".gitea/**"
- "docs/**"
- "**/*.md"
workflow_dispatch:
concurrency:
group: unraid-production-mobilityops
cancel-in-progress: false
permissions:
contents: read
jobs:
deploy:
name: Deploy mobilityops
runs-on: unraid-deploy
timeout-minutes: 180
steps:
- name: Deploy exact Gitea revision
run: |
set -euo pipefail
docker exec gitea-deploy-control \
/opt/gitea-deploy/deploy.py deploy \
"$GITHUB_REPOSITORY" "$GITHUB_SHA"
+4
View File
@@ -0,0 +1,4 @@
81e3fd63bdbcb2e9c4ae1d709ea46f40537b6f62:backend/tests/test_data_quality.py:generic-api-key:869
0091c57c7fd82ffda16da1edfe5fa5589c8f1e13:backend/tests/test_return.py:generic-api-key:93
0091c57c7fd82ffda16da1edfe5fa5589c8f1e13:backend/tests/test_return.py:generic-api-key:122
0091c57c7fd82ffda16da1edfe5fa5589c8f1e13:backend/tests/test_return.py:generic-api-key:127
+1 -1
View File
@@ -15,7 +15,7 @@ release line only.
Do not disclose suspected vulnerabilities through a public issue. Do not disclose suspected vulnerabilities through a public issue.
Report them privately to `jens@itworx.tech` with: Report them privately to `security@itworx.tech` with:
- the affected revision, endpoint or component; - the affected revision, endpoint or component;
- reproduction steps and prerequisites; - reproduction steps and prerequisites;
+3
View File
@@ -24,6 +24,9 @@ FROM runtime-base AS test
COPY backend/requirements.lock ./requirements.lock COPY backend/requirements.lock ./requirements.lock
RUN pip install --no-cache-dir -r requirements.lock RUN pip install --no-cache-dir -r requirements.lock
COPY backend/tests ./tests COPY backend/tests ./tests
COPY contracts ./contracts
COPY scripts/check-contracts.py ./scripts/check-contracts.py
COPY n8n/workflows ./n8n/workflows
USER app USER app
FROM runtime-base AS runtime FROM runtime-base AS runtime
+2
View File
@@ -107,6 +107,8 @@ services:
N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:-change-me} N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:-change-me}
N8N_SECURE_COOKIE: "false" N8N_SECURE_COOKIE: "false"
N8N_BLOCK_ENV_ACCESS_IN_NODE: "false" N8N_BLOCK_ENV_ACCESS_IN_NODE: "false"
MOBILITYOPS_ALERT_RECIPIENT: ${MOBILITYOPS_ALERT_RECIPIENT:-alerts@example.test}
MOBILITYOPS_ALERT_SENDER: ${MOBILITYOPS_ALERT_SENDER:-n8n@example.test}
MOBILITYOPS_CALLBACK_TOKEN: ${MOBILITYOPS_CALLBACK_TOKEN:-replace-me-n8n-callback-token} MOBILITYOPS_CALLBACK_TOKEN: ${MOBILITYOPS_CALLBACK_TOKEN:-replace-me-n8n-callback-token}
ports: ports:
- "5678:5678" - "5678:5678"
+3
View File
@@ -11,6 +11,9 @@ ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build RUN npm run build
FROM nginx:1.30.4-alpine@sha256:97d490c12ba55b4946b01546d1c3ed324e8d41ab1c9fcb2a616aa470620e5b46 FROM nginx:1.30.4-alpine@sha256:97d490c12ba55b4946b01546d1c3ed324e8d41ab1c9fcb2a616aa470620e5b46
# The pinned upstream image can lag Alpine security rebuilds. Apply the current
# fixes from its pinned Alpine release before shipping the runtime image.
RUN apk upgrade --no-cache
ARG VCS_REF=development ARG VCS_REF=development
ARG BUILD_DATE=unknown ARG BUILD_DATE=unknown
LABEL org.opencontainers.image.title="Fleet Ops Web" \ LABEL org.opencontainers.image.title="Fleet Ops Web" \
+9
View File
@@ -26,6 +26,15 @@ test("non-destructive operator canary covers routes and grounded knowledge", asy
await page.goto("/login"); await page.goto("/login");
await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); await page.getByRole("button", { name: "Verken als Operationsmanager" }).click();
await expect(page).toHaveURL(/\/dashboard$/); await expect(page).toHaveURL(/\/dashboard$/);
if (pageErrors.some((message) => message.toLowerCase().includes("dynamically imported module"))) {
// A deploy can replace the SPA between loading index.html and its lazy
// dashboard chunk. One clean reload must recover; a persistent chunk or
// server error is collected again and still fails the canary below.
pageErrors.length = 0;
await page.reload({ waitUntil: "domcontentloaded" });
await expect(page).toHaveURL(/\/dashboard$/);
await expect(page.locator("main")).toBeVisible();
}
for (const [path, heading] of [ for (const [path, heading] of [
["/vehicles", "Wagenpark"], ["/vehicles", "Wagenpark"],
+4
View File
@@ -6,6 +6,10 @@ export default defineConfig({
// it mutates demo data via a reset and is run explicitly, not as part of the suite. // it mutates demo data via a reset and is run explicitly, not as part of the suite.
testIgnore: process.env.CAPTURE_EVIDENCE === "1" ? undefined : "**/_*.spec.ts", testIgnore: process.env.CAPTURE_EVIDENCE === "1" ? undefined : "**/_*.spec.ts",
timeout: 30_000, timeout: 30_000,
// CI exercises the browser while image scans and container services share the
// same runner. Keep assertions strict, but allow successful API-backed route
// transitions enough time to render under that bounded load.
expect: { timeout: 15_000 },
fullyParallel: false, fullyParallel: false,
workers: 1, workers: 1,
snapshotPathTemplate: "{testDir}/__screenshots__/{arg}{ext}", snapshotPathTemplate: "{testDir}/__screenshots__/{arg}{ext}",
+1 -1
View File
@@ -121,4 +121,4 @@ this handler was not observed live.
| Active status | Imported inactive by default; production deployment must publish it before enabling Alertmanager. | | Active status | Imported inactive by default; production deployment must publish it before enabling Alertmanager. |
| Error Workflow | `Fleet Ops — Workflow Error Handler` (wired) | | Error Workflow | `Fleet Ops — Workflow Error Handler` (wired) |
| Safety | Strict Alertmanager payload shape, bounded to 25 alerts and bounded text fields. | | Safety | Strict Alertmanager payload shape, bounded to 25 alerts and bounded text fields. |
| Checksum (sha256) | `3672ad3d14b65c603c8c3f2067197cc50372e9d4c2fc314d1c9b6aaf3ecef444` | | Checksum (sha256) | `4eb1ae41c4e675a9bb51a43722a507da8a6eae8a6ce8649b7e3a57417911a418` |
+1 -1
View File
@@ -17,7 +17,7 @@
"id": "accept", "name": "Accept alert", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [740, 300] "id": "accept", "name": "Accept alert", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [740, 300]
}, },
{ {
"parameters": {"toRecipients": "jens@itworx.tech", "subject": "={{ $('Validate and format').item.json.subject }}", "bodyContent": "={{ $('Validate and format').item.json.html }}", "additionalFields": {"from": "n8n@itworx.tech", "bodyContentType": "html"}}, "parameters": {"toRecipients": "={{ $env.MOBILITYOPS_ALERT_RECIPIENT || 'alerts@example.test' }}", "subject": "={{ $('Validate and format').item.json.subject }}", "bodyContent": "={{ $('Validate and format').item.json.html }}", "additionalFields": {"from": "={{ $env.MOBILITYOPS_ALERT_SENDER || 'n8n@example.test' }}", "bodyContentType": "html"}},
"id": "email", "name": "Send owner email", "type": "n8n-nodes-base.microsoftOutlook", "typeVersion": 2, "position": [980, 300], "id": "email", "name": "Send owner email", "type": "n8n-nodes-base.microsoftOutlook", "typeVersion": 2, "position": [980, 300],
"credentials": {"microsoftOutlookOAuth2Api": {"id": "EDTj3sOsganaoDVL", "name": "M365 n8n Shared Mailbox"}}, "credentials": {"microsoftOutlookOAuth2Api": {"id": "EDTj3sOsganaoDVL", "name": "M365 n8n Shared Mailbox"}},
"retryOnFail": true, "maxTries": 3, "waitBetweenTries": 1000 "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 1000
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TARGET_IMAGE="${1:?usage: scan-ci-image.sh <image>}"
TRIVY_IMAGE="aquasec/trivy@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e"
SAFE_NAME="$(printf '%s' "$TARGET_IMAGE" | tr -cs 'A-Za-z0-9._-' '-')"
ARCHIVE_RELATIVE="artifacts/.${SAFE_NAME}.tar"
ARCHIVE="$ROOT/$ARCHIVE_RELATIVE"
trap 'rm -f "$ARCHIVE"' EXIT
mkdir -p "$ROOT/artifacts"
docker image inspect "$TARGET_IMAGE" >/dev/null
docker save --output "$ARCHIVE" "$TARGET_IMAGE"
WORKSPACE_ARGS=(-v "$ROOT:/workspace:ro")
CONTAINER_ARCHIVE="/workspace/$ARCHIVE_RELATIVE"
if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then
WORKSPACE_ARGS=(--volumes-from "$HOSTNAME")
CONTAINER_ARCHIVE="$ROOT/$ARCHIVE_RELATIVE"
fi
docker run --rm "${WORKSPACE_ARGS[@]}" "$TRIVY_IMAGE" image \
--input "$CONTAINER_ARCHIVE" \
--scanners vuln \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--exit-code 1 \
--format table