Files
cameleer-server/.gitea/workflows/ci.yml
hsiegeln f8505401d7
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 43s
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Failing after 13s
CI / cleanup-branch (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m47s
CI / docker (pull_request) Has been skipped
CI / deploy (pull_request) Has been skipped
CI / deploy-feature (pull_request) Has been skipped
fix: ClickHouse auth credentials and non-fatal schema init
- Set CLICKHOUSE_USER/PASSWORD via k8s secret (fixes "disabling network
  access for user 'default'" when no password is set)
- Add clickhouse-credentials secret to CI deploy + feature branch copy
- Pass CLICKHOUSE_USERNAME/PASSWORD env vars to server pod
- Make schema initializer non-fatal so server starts even if CH is
  temporarily unavailable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:54:44 +02:00

405 lines
17 KiB
YAML

name: CI
on:
push:
branches: [main, 'feature/**', 'fix/**', 'feat/**']
tags-ignore:
- 'v*'
pull_request:
branches: [main]
delete:
jobs:
build:
runs-on: ubuntu-latest
if: github.event_name != 'delete'
container:
image: gitea.siegeln.net/cameleer/cameleer-build:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Configure Gitea Maven Registry
run: |
mkdir -p ~/.m2
cat > ~/.m2/settings.xml << 'SETTINGS'
<settings>
<servers>
<server>
<id>gitea</id>
<username>cameleer</username>
<password>${env.REGISTRY_TOKEN}</password>
</server>
</servers>
</settings>
SETTINGS
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Build UI
working-directory: ui
run: |
echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
npm ci
npm run build
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and Test
run: mvn clean verify -DskipITs -U --batch-mode
docker:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push'
container:
image: gitea.siegeln.net/cameleer/cameleer-docker-builder:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- name: Checkout
run: |
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Login to registry
run: echo "$REGISTRY_TOKEN" | docker login gitea.siegeln.net -u cameleer --password-stdin
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Compute branch slug
run: |
sanitize_branch() {
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^a-z0-9-]/-/g' \
| sed 's/--*/-/g; s/^-//; s/-$//' \
| cut -c1-20 \
| sed 's/-$//'
}
if [ "$GITHUB_REF_NAME" = "main" ]; then
echo "BRANCH_SLUG=main" >> "$GITHUB_ENV"
echo "IMAGE_TAGS=latest" >> "$GITHUB_ENV"
else
SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
echo "IMAGE_TAGS=branch-$SLUG" >> "$GITHUB_ENV"
fi
- name: Set up QEMU for cross-platform builds
run: docker run --rm --privileged gitea.siegeln.net/cameleer/binfmt:1 --install all
- name: Build and push server
run: |
docker buildx create --use --name cibuilder
TAGS="-t gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}"
for TAG in $IMAGE_TAGS; do
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer3-server:$TAG"
done
docker buildx build --platform linux/amd64 \
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
$TAGS \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache,mode=max \
--provenance=false \
--push .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push UI
run: |
TAGS="-t gitea.siegeln.net/cameleer/cameleer3-server-ui:${{ github.sha }}"
for TAG in $IMAGE_TAGS; do
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer3-server-ui:$TAG"
done
docker buildx build --platform linux/amd64 \
-f ui/Dockerfile \
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
$TAGS \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server-ui:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server-ui:buildcache,mode=max \
--provenance=false \
--push ui/
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Cleanup local Docker
run: docker system prune -af --filter "until=24h"
if: always()
- name: Cleanup old container images
run: |
API="https://gitea.siegeln.net/api/v1"
AUTH="Authorization: token ${REGISTRY_TOKEN}"
CURRENT_SHA="${{ github.sha }}"
# Build list of tags to keep
KEEP_TAGS="latest buildcache $CURRENT_SHA"
if [ "$BRANCH_SLUG" != "main" ]; then
KEEP_TAGS="$KEEP_TAGS branch-$BRANCH_SLUG"
fi
for PKG in cameleer3-server cameleer3-server-ui; do
curl -sf -H "$AUTH" "$API/packages/cameleer/container/$PKG" | \
jq -r '.[] | "\(.id) \(.version)"' | \
while read id version; do
SHOULD_KEEP=false
for KEEP in $KEEP_TAGS; do
if [ "$version" = "$KEEP" ]; then
SHOULD_KEEP=true
break
fi
done
if [ "$SHOULD_KEEP" = "false" ]; then
# Only clean up images for this branch
if [ "$BRANCH_SLUG" = "main" ] || echo "$version" | grep -q "branch-$BRANCH_SLUG"; then
echo "Deleting old image tag: $PKG:$version"
curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/$version"
fi
fi
done
done
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
if: always()
deploy:
needs: docker
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
container:
image: alpine/k8s:1.32.3
steps:
- name: Checkout
run: |
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Deploy
run: |
kubectl create namespace cameleer --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret docker-registry gitea-registry \
--namespace=cameleer \
--docker-server=gitea.siegeln.net \
--docker-username=cameleer \
--docker-password="$REGISTRY_TOKEN" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic cameleer-auth \
--namespace=cameleer \
--from-literal=CAMELEER_AUTH_TOKEN="$CAMELEER_AUTH_TOKEN" \
--from-literal=CAMELEER_UI_USER="${CAMELEER_UI_USER:-admin}" \
--from-literal=CAMELEER_UI_PASSWORD="${CAMELEER_UI_PASSWORD:-admin}" \
--from-literal=CAMELEER_JWT_SECRET="${CAMELEER_JWT_SECRET}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic postgres-credentials \
--namespace=cameleer \
--from-literal=POSTGRES_USER="$POSTGRES_USER" \
--from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
--from-literal=POSTGRES_DB="${POSTGRES_DB:-cameleer}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic opensearch-credentials \
--namespace=cameleer \
--from-literal=OPENSEARCH_USER="${OPENSEARCH_USER:-admin}" \
--from-literal=OPENSEARCH_PASSWORD="$OPENSEARCH_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic authentik-credentials \
--namespace=cameleer \
--from-literal=PG_USER="${AUTHENTIK_PG_USER:-authentik}" \
--from-literal=PG_PASSWORD="${AUTHENTIK_PG_PASSWORD}" \
--from-literal=AUTHENTIK_SECRET_KEY="${AUTHENTIK_SECRET_KEY}" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic clickhouse-credentials \
--namespace=cameleer \
--from-literal=CLICKHOUSE_USER="${CLICKHOUSE_USER:-default}" \
--from-literal=CLICKHOUSE_PASSWORD="$CLICKHOUSE_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deploy/postgres.yaml
kubectl -n cameleer rollout status statefulset/postgres --timeout=120s
kubectl apply -f deploy/opensearch.yaml
kubectl -n cameleer rollout status statefulset/opensearch --timeout=180s
kubectl apply -f deploy/clickhouse.yaml
kubectl -n cameleer rollout status statefulset/clickhouse --timeout=180s
kubectl apply -f deploy/authentik.yaml
kubectl -n cameleer rollout status deployment/authentik-server --timeout=180s
kubectl apply -k deploy/overlays/main
kubectl -n cameleer set image deployment/cameleer3-server \
server=gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer3-server --timeout=120s
kubectl -n cameleer set image deployment/cameleer3-ui \
ui=gitea.siegeln.net/cameleer/cameleer3-server-ui:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer3-ui --timeout=120s
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
CAMELEER_AUTH_TOKEN: ${{ secrets.CAMELEER_AUTH_TOKEN }}
CAMELEER_JWT_SECRET: ${{ secrets.CAMELEER_JWT_SECRET }}
CAMELEER_UI_USER: ${{ secrets.CAMELEER_UI_USER }}
CAMELEER_UI_PASSWORD: ${{ secrets.CAMELEER_UI_PASSWORD }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
OPENSEARCH_USER: ${{ secrets.OPENSEARCH_USER }}
OPENSEARCH_PASSWORD: ${{ secrets.OPENSEARCH_PASSWORD }}
AUTHENTIK_PG_USER: ${{ secrets.AUTHENTIK_PG_USER }}
AUTHENTIK_PG_PASSWORD: ${{ secrets.AUTHENTIK_PG_PASSWORD }}
AUTHENTIK_SECRET_KEY: ${{ secrets.AUTHENTIK_SECRET_KEY }}
CLICKHOUSE_USER: ${{ secrets.CLICKHOUSE_USER }}
CLICKHOUSE_PASSWORD: ${{ secrets.CLICKHOUSE_PASSWORD }}
deploy-feature:
needs: docker
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main' && github.event_name == 'push'
container:
image: alpine/k8s:1.32.3
steps:
- name: Checkout
run: |
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Compute branch variables
run: |
sanitize_branch() {
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^a-z0-9-]/-/g' \
| sed 's/--*/-/g; s/^-//; s/-$//' \
| cut -c1-20 \
| sed 's/-$//'
}
SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
NS="cam-${SLUG}"
SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
echo "BRANCH_NS=$NS" >> "$GITHUB_ENV"
echo "BRANCH_SCHEMA=$SCHEMA" >> "$GITHUB_ENV"
- name: Create namespace
run: kubectl create namespace "$BRANCH_NS" --dry-run=client -o yaml | kubectl apply -f -
- name: Copy secrets from cameleer namespace
run: |
for SECRET in gitea-registry postgres-credentials opensearch-credentials clickhouse-credentials cameleer-auth; do
kubectl get secret "$SECRET" -n cameleer -o json \
| jq 'del(.metadata.namespace, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.managedFields)' \
| kubectl apply -n "$BRANCH_NS" -f -
done
- name: Substitute placeholders and deploy
run: |
# Work on a copy preserving the directory structure so ../../base resolves
mkdir -p /tmp/feature-deploy/deploy/overlays
cp -r deploy/base /tmp/feature-deploy/deploy/base
cp -r deploy/overlays/feature /tmp/feature-deploy/deploy/overlays/feature
# Substitute all BRANCH_* placeholders
for f in /tmp/feature-deploy/deploy/overlays/feature/*.yaml; do
sed -i \
-e "s|BRANCH_NAMESPACE|${BRANCH_NS}|g" \
-e "s|BRANCH_SCHEMA|${BRANCH_SCHEMA}|g" \
-e "s|BRANCH_SLUG|${BRANCH_SLUG}|g" \
-e "s|BRANCH_SHA|${{ github.sha }}|g" \
"$f"
done
kubectl apply -k /tmp/feature-deploy/deploy/overlays/feature
- name: Wait for init-job
run: |
kubectl -n "$BRANCH_NS" wait --for=condition=complete job/init-schema --timeout=60s || \
echo "Warning: init-schema job did not complete in time"
- name: Wait for server rollout
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer3-server --timeout=120s
- name: Wait for UI rollout
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer3-ui --timeout=60s
- name: Print deployment URLs
run: |
echo "===================================="
echo "Feature branch deployed!"
echo "API: http://${BRANCH_SLUG}-api.cameleer.siegeln.net"
echo "UI: http://${BRANCH_SLUG}.cameleer.siegeln.net"
echo "===================================="
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
cleanup-branch:
runs-on: ubuntu-latest
if: github.event_name == 'delete' && github.event.ref_type == 'branch'
container:
image: alpine/k8s:1.32.3
steps:
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Compute branch variables
run: |
sanitize_branch() {
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^a-z0-9-]/-/g' \
| sed 's/--*/-/g; s/^-//; s/-$//' \
| cut -c1-20 \
| sed 's/-$//'
}
SLUG=$(sanitize_branch "${{ github.event.ref }}")
NS="cam-${SLUG}"
SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
echo "BRANCH_NS=$NS" >> "$GITHUB_ENV"
echo "BRANCH_SCHEMA=$SCHEMA" >> "$GITHUB_ENV"
- name: Delete namespace
run: kubectl delete namespace "$BRANCH_NS" --ignore-not-found
- name: Drop PostgreSQL schema
run: |
kubectl run cleanup-schema-${BRANCH_SLUG} \
--namespace=cameleer \
--image=postgres:16 \
--restart=Never \
--env="PGPASSWORD=$(kubectl get secret postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d)" \
--command -- sh -c "psql -h postgres -U $(kubectl get secret postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_USER}' | base64 -d) -d cameleer3 -c 'DROP SCHEMA IF EXISTS ${BRANCH_SCHEMA} CASCADE'"
kubectl wait --for=condition=Ready pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=30s || true
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=60s || true
kubectl delete pod cleanup-schema-${BRANCH_SLUG} -n cameleer --ignore-not-found
- name: Delete OpenSearch indices
run: |
kubectl run cleanup-indices-${BRANCH_SLUG} \
--namespace=cameleer \
--image=curlimages/curl:latest \
--restart=Never \
--command -- curl -sf -X DELETE "http://opensearch:9200/cam-${BRANCH_SLUG}-*"
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-indices-${BRANCH_SLUG} -n cameleer --timeout=60s || true
kubectl delete pod cleanup-indices-${BRANCH_SLUG} -n cameleer --ignore-not-found
- name: Cleanup Docker images
run: |
API="https://gitea.siegeln.net/api/v1"
AUTH="Authorization: token ${REGISTRY_TOKEN}"
for PKG in cameleer3-server cameleer3-server-ui; do
# Delete branch-specific tag
curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/branch-${BRANCH_SLUG}" || true
done
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}