Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,115 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: REUSE License Check
on:
pull_request:
jobs:
reuse-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install REUSE tool
run: pip install reuse
- name: Run REUSE lint with filtering
run: |
# Run reuse lint and capture output
if ! reuse lint > reuse_output.txt 2>&1; then
echo "REUSE lint found issues. Checking against ignore list..."
# Check if .reuse/ignore-files.txt exists
if [ -f ".reuse/ignore-files.txt" ]; then
echo "Found .reuse/ignore-files.txt file. Filtering results..."
# Extract failing files from the REUSE output
sed '/SUMMARY/q' reuse_output.txt | grep "^\* " | sed 's/^\* //' > failing_files.txt
# Function to check if a file should be ignored
should_ignore_file() {
local file="$1"
local ignore_file="$2"
# Skip the reuse output file itself
if [[ "$file" == "reuse_output.txt" ]]; then
return 0 # ignore (return true)
fi
while IFS= read -r ignore_pattern; do
# Skip empty lines and comments
[[ -z "$ignore_pattern" || "$ignore_pattern" =~ ^[[:space:]]*# ]] && continue
# Remove trailing whitespace
ignore_pattern=$(echo "$ignore_pattern" | sed 's/[[:space:]]*$//')
# Check for exact match first
if [[ "$file" == "$ignore_pattern" ]]; then
return 0 # ignore
fi
# Check if it's a directory pattern (ends with /)
if [[ "$ignore_pattern" == */ ]]; then
# Check if file is in this directory
if [[ "$file" == "$ignore_pattern"* ]]; then
return 0 # ignore
fi
else
# Use shell pattern matching for globs
# Enable extended globbing for more complex patterns
shopt -s extglob nullglob
if [[ "$file" == $ignore_pattern ]]; then
return 0 # ignore
fi
shopt -u extglob nullglob
fi
done < "$ignore_file"
return 1 # don't ignore
}
# Find files that are failing but NOT in the ignore list
> unignored_failures.txt
while IFS= read -r failing_file; do
if ! should_ignore_file "$failing_file" ".reuse/ignore-files.txt"; then
echo "$failing_file" >> unignored_failures.txt
fi
done < failing_files.txt
# Check if there are any unignored failures
if [ -s unignored_failures.txt ]; then
echo "❌ REUSE compliance failed for the following files:"
cat unignored_failures.txt
echo ""
echo "These files need proper license headers or should be added to .reuse/ignore-files.txt"
echo ""
exit 1
else
echo "✅ All REUSE lint failures are accounted for in .reuse/ignore-files.txt"
ignored_count=$(wc -l < failing_files.txt)
echo "Ignored $ignored_count file(s) as specified in .reuse/ignore-files.txt"
fi
else
echo "❌ REUSE lint failed and no .reuse/ignore-files.txt file found"
echo ""
echo "Full REUSE lint output:"
cat reuse_output.txt
exit 1
fi
else
echo "✅ REUSE lint passed - all files have proper license compliance"
fi
- name: Clean up temporary files
if: always()
run: |
rm -f reuse_output.txt failing_files.txt unignored_failures.txt

View File

@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Linter
on:
pull_request:
concurrency:
# Rerun check when the PR is updated, https://stackoverflow.com/a/72408109
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
linter:
name: Linter
runs-on: ubuntu-22.04 # todo: to prevent needing to run npm i we can have our own image where everything is installed
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.19.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm run lint

View File

@@ -0,0 +1,80 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Publish Swagger
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
outputs:
swagger_valid: ${{ steps.validate-swagger.outputs.swagger_valid }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Build and Run Docker Compose
run: |
docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml up -d
- name: Generate Swagger JSON
run: |
echo "Fetching Swagger JSON..."
curl --retry 3 --retry-delay 3 --retry-all-errors -o swagger.json http://localhost:8080/docs/json
- name: Validate Swagger JSON
id: validate-swagger
run: |
if jq empty swagger.json; then
echo "swagger_valid=true" >> $GITHUB_OUTPUT
else
echo "swagger_valid=false" >> $GITHUB_OUTPUT && exit 1
fi
- name: Upload Swagger JSON as Artifact
uses: actions/upload-artifact@v4
with:
name: swagger-json
path: swagger.json
- name: Clean up Docker
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml down
publish-swagger:
needs: build
if: needs.build.outputs.swagger_valid == 'true'
runs-on: ubuntu-latest
steps:
- name: Download Swagger Artifact
uses: actions/download-artifact@v4
with:
name: swagger-json
- name: Checkout citrineos.github.io
uses: actions/checkout@v4
with:
repository: citrineos/citrineos.github.io
ssh-key: ${{ secrets.CITRINEOS_GITHUB_IO_DEPLOY_KEY }}
path: citrineos.github.io
- name: Push Swagger JSON
run: |
cd citrineos.github.io
git checkout -b swagger-updates || git checkout swagger-updates
cp ../swagger.json ./docs/assets
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add ./docs/assets/swagger.json
git commit -m "Update Swagger JSON from PR"
git push -f origin swagger-updates --verbose

View File

@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Push Release Tagged Operator UI
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
strategy:
matrix:
image:
- name: citrineos-operator-ui
context: ./
dockerfile: ./apps/operator-ui/Dockerfile
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ matrix.image.name }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ matrix.image.name }}-
- name: Log in to GitHub Container Registry
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.name }}
tags: |
type=ref,event=tag
type=raw,value=latest
- name: Build and push operator-ui image
uses: docker/build-push-action@v5
with:
context: ${{ matrix.image.context }}
file: ${{ matrix.image.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
# Move cache to avoid ever-growing cache
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache

View File

@@ -0,0 +1,77 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Push Release Tagged Server
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
strategy:
matrix:
image:
- name: citrineos-server
context: ./
dockerfile: ./apps/Server/deploy.Dockerfile
config: ''
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ matrix.image.name }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ matrix.image.name }}-
- name: Log in to GitHub Container Registry
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.name }}
tags: |
type=ref,event=tag
type=raw,value=latest
- name: Build and push server image
if: ${{ matrix.image.name == 'citrineos-server' }}
uses: docker/build-push-action@v5
with:
context: ${{ matrix.image.context }}
file: ${{ matrix.image.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
# Move cache to avoid ever-growing cache
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache

View File

@@ -0,0 +1,111 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Renew Certificates
# This workflow is to renew the certificates which are used for security profile 2&3
# It can be triggered manually or automatically every month to check if the certificates are still valid
# and renew them if needed on main branch
on:
workflow_dispatch:
schedule:
- cron: '0 0 1 * *' # Runs on the 1st of every month
env:
TARGET_BRANCH: main
jobs:
renew-certs:
runs-on: ubuntu-latest
permissions:
contents: write
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ env.TARGET_BRANCH }}
- name: Set up OpenSSL + Node
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
- name: Check certificate validity
id: check_cert
run: |
CERT_FILE="apps/Server/src/assets/certificates/certChain.pem"
# extract the expiry date of the leaf certificate (first cert in the chain)
EXPIRY_DATE=$(awk 'BEGIN{found=0} /BEGIN CERTIFICATE/{if (++found==1) print > "/tmp/leaf.pem"; next} found==1 {print > "/tmp/leaf.pem"} /END CERTIFICATE/{if (found==1) exit}' "$CERT_FILE")
END_DATE=$(openssl x509 -enddate -noout -in /tmp/leaf.pem | cut -d= -f2)
END_TIMESTAMP=$(date -d "$END_DATE" +%s)
NOW_TIMESTAMP=$(date +%s)
DIFF_DAYS=$(( (END_TIMESTAMP - NOW_TIMESTAMP) / 86400 ))
echo "Leaf certificate expires in $DIFF_DAYS days."
if [ "$DIFF_DAYS" -lt 31 ]; then
echo "cert_needs_renewal=true" >> "$GITHUB_OUTPUT"
else
echo "cert_needs_renewal=false" >> "$GITHUB_OUTPUT"
fi
- name: Skip if certificate is valid
if: steps.check_cert.outputs.cert_needs_renewal == 'false'
run: echo "Certificate is still valid. Skipping renewal."
- name: Generate new certificates
if: steps.check_cert.outputs.cert_needs_renewal == 'true'
run: |
CERT_DIR="apps/Server/src/assets/certificates"
CONFIG_FILE=".github/workflows/renew-certs/openssl.config"
mkdir -p "$CERT_DIR"
# Create root key and self-signed root cert
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$CERT_DIR/rootKey.pem"
openssl req -x509 -new -nodes -key "$CERT_DIR/rootKey.pem" -sha256 -days 365 \
-subj "/CN=host.docker.internal root/O=S44/C=US" \
-out "$CERT_DIR/rootCertificate.pem" \
-config "$CONFIG_FILE" -extensions v3_ca
# Create sub-CA key and CSR
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$CERT_DIR/subCAKey.pem"
openssl req -new -key "$CERT_DIR/subCAKey.pem" \
-subj "/CN=host.docker.internal subCA/O=S44/C=US" \
-out "$CERT_DIR/subCA.csr" \
-config "$CONFIG_FILE"
# Sign sub-CA cert with root
openssl x509 -req -in "$CERT_DIR/subCA.csr" -CA "$CERT_DIR/rootCertificate.pem" -CAkey "$CERT_DIR/rootKey.pem" -CAcreateserial \
-out "$CERT_DIR/subCA.pem" -days 365 -sha256 \
-extfile "$CONFIG_FILE" -extensions v3_subca
# Create leaf key and CSR
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$CERT_DIR/leafKey.pem"
openssl req -new -key "$CERT_DIR/leafKey.pem" \
-subj "/CN=host.docker.internal/O=S44/C=US" \
-out "$CERT_DIR/leaf.csr" \
-config "$CONFIG_FILE"
# Sign leaf cert with sub-CA
openssl x509 -req -in "$CERT_DIR/leaf.csr" -CA "$CERT_DIR/subCA.pem" -CAkey "$CERT_DIR/subCAKey.pem" -CAcreateserial \
-out "$CERT_DIR/leaf.pem" -days 365 -sha256 \
-extfile "$CONFIG_FILE" -extensions v3_leaf
# Combine into certChain.pem
cat "$CERT_DIR/leaf.pem" "$CERT_DIR/subCA.pem" > "$CERT_DIR/certChain.pem"
# Clean up intermediate files
rm -f "$CERT_DIR/"*.csr "$CERT_DIR/"*.srl "$CERT_DIR/leaf.pem" "$CERT_DIR/subCA.pem"
- name: Commit and push renewed certificates
if: steps.check_cert.outputs.cert_needs_renewal == 'true'
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add apps/Server/src/assets/certificates
git commit -m "chore(cert): Renewed certificates [auto]"
git push origin $TARGET_BRANCH

View File

@@ -0,0 +1,17 @@
[ v3_ca ]
basicConstraints = critical,CA:true,pathlen:1
keyUsage = critical,digitalSignature, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
[ v3_subca ]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,digitalSignature, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
[ v3_leaf ]
basicConstraints = CA:false
keyUsage = critical,digitalSignature, keyEncipherment
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer

View File

@@ -0,0 +1,86 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Test Server on Pull Request
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
outputs:
server: ${{ steps.filter.outputs.server }}
shared: ${{ steps.filter.outputs.shared }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
shared:
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- 'tsconfig*.json'
- '.github/workflows/**'
server:
- 'apps/Server/**'
- 'packages/**'
build:
needs: changes
if: ${{ needs.changes.outputs.server == 'true' || needs.changes.outputs.shared == 'true' }}
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Build and Run Docker Compose
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml up -d
- name: Install Newman
run: |
npm install -g newman
sleep 10
- name: Integration test
run: |
RETRY_COUNT=0
MAX_RETRIES=5
RETRY_DELAY=10
until [ $RETRY_COUNT -ge $MAX_RETRIES ]
do
newman run ${{ github.workspace }}/.github/workflows/tests/CI/collection.json -e ${{ github.workspace }}/.github/workflows/tests/CI/environment.json --reporters cli && break
RETRY_COUNT=$((RETRY_COUNT+1))
echo "Retry $RETRY_COUNT/$MAX_RETRIES in $RETRY_DELAY seconds..."
sleep $RETRY_DELAY
done
shell: bash
- name: Get logs for all containers
if: failure()
run: |
docker ps -a
docker container ls -aq | xargs -I {} docker logs {}
- name: Clean up
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml down

View File

@@ -0,0 +1,44 @@
{
"info": {
"name": "API Collection",
"_postman_id": "a1234567-b89c-4d12-95a3-b4567e89c0d1",
"description": "A collection contains CI tests.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Check Health",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"body": {
"mode": "none"
},
"url": {
"raw": "{{host}}:{{port}}/health",
"protocol": "http",
"host": ["{{host}}"],
"port": "{{port}}",
"path": ["health"]
}
},
"response": []
}
],
"auth": {
"type": "noauth"
}
}

View File

@@ -0,0 +1,16 @@
{
"id": "4454509f-00c2-4b1b-837a-e45d9814dbf6",
"name": "Local Environment",
"values": [
{
"key": "host",
"value": "localhost",
"enabled": true
},
{
"key": "port",
"value": "8080",
"enabled": true
}
]
}

View File

@@ -0,0 +1,181 @@
# SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
#
# SPDX-License-Identifier: Apache-2.0
name: Tests
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
outputs:
base: ${{ steps.filter.outputs.base }}
core: ${{ steps.filter.outputs.core }}
server: ${{ steps.filter.outputs.server }}
operator-ui: ${{ steps.filter.outputs.operator-ui }}
shared: ${{ steps.filter.outputs.shared }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
shared:
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- 'tsconfig*.json'
- 'vitest.config.ts'
- 'eslint.config.base.js'
- '.github/workflows/**'
base:
- 'packages/base/**'
core:
- 'packages/core/**'
- 'packages/base/**'
server:
- 'apps/Server/**'
- 'packages/**'
operator-ui:
- 'apps/operator-ui/**'
- 'packages/base/**'
tests:
needs: changes
if: ${{ needs.changes.outputs.base == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.server == 'true' || needs.changes.outputs.shared == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.19.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24.16.0"
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm run build
- name: Run tests
env:
NODE_OPTIONS: "--max_old_space_size=4096"
run: pnpm test
operator-ui-e2e:
needs: changes
if: ${{ needs.changes.outputs.operator-ui == 'true' || needs.changes.outputs.shared == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.19.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24.16.0"
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build @citrineos/operator-ui and its workspace deps
run: pnpm --filter "@citrineos/operator-ui..." run build
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('apps/operator-ui/package.json') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: apps/operator-ui
run: pnpm exec playwright install --with-deps chromium
- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
working-directory: apps/operator-ui
run: pnpm exec playwright install-deps chromium
- name: Start backend stack (Hasura + CitrineOS Core)
# Build with the default docker driver (builds straight into the daemon's
# image store) — no tarball export/import or cache round-trip, which is
# what made the build-push-action approach ~3min slower than the build itself.
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml up -d --build
- name: Wait for backend healthy
run: |
set -e
for i in $(seq 1 60); do
HASURA_OK=$(curl -fs http://localhost:8090/healthz > /dev/null && echo yes || echo no)
CORE_OK=$(curl -fs http://localhost:8080/health > /dev/null && echo yes || echo no)
if [ "$HASURA_OK" = "yes" ] && [ "$CORE_OK" = "yes" ]; then
echo "Backend healthy after ${i} attempts."
exit 0
fi
echo "Waiting for backend (attempt ${i}/60): hasura=${HASURA_OK} core=${CORE_OK}"
sleep 5
done
echo "Backend did not become healthy in time."
exit 1
- name: Run operator-ui Playwright tests
env:
CI: "true"
NODE_OPTIONS: "--max_old_space_size=4096"
NEXT_PUBLIC_ADMIN_EMAIL: admin@citrineos.com
ADMIN_PASSWORD: CitrineOS!
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: test-secret-for-e2e-only
HASURA_ADMIN_SECRET: CitrineOS!
NEXT_PUBLIC_API_URL: http://localhost:8090/v1/graphql
NEXT_PUBLIC_WS_URL: ws://localhost:8090/v1/graphql
NEXT_PUBLIC_CITRINE_CORE_URL: http://localhost:8080
NEXT_PUBLIC_FILE_SERVER_URL: http://localhost:8050
ALLOW_IMAGE_UPLOAD: "false"
working-directory: apps/operator-ui
run: pnpm exec playwright test --project=chromium-desktop
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: operator-ui-playwright-report
path: |
apps/operator-ui/playwright-report
apps/operator-ui/test-results
apps/operator-ui/reports
retention-days: 7
- name: Dump backend logs on failure
if: failure()
run: |
docker ps -a
docker container ls -aq | xargs -I {} docker logs {} || true
- name: Tear down backend stack
if: always()
run: docker compose -f ${{ github.workspace }}/apps/Server/docker-compose.yml down -v