chore: add Clouderized deploy workflow [skip ci]

This commit is contained in:
clouderized-bot
2026-06-05 11:54:06 +08:00
parent f173865833
commit 883bf6decf
+240
View File
@@ -0,0 +1,240 @@
name: Deploy (Clouderized)
on:
push:
branches: [main, master]
workflow_dispatch:
inputs:
no_cache:
description: 'Build without Docker layer cache'
required: false
default: 'false'
jobs:
deploy:
runs-on: clouderized-runner
concurrency:
group: clouderized-build # global — only 1 build at a time, limited host resources
cancel-in-progress: false # queue, don't cancel — a new push from any customer must not kill another's build
steps:
- name: Cancel stale same-repo runs
run: |
curl -sf -X POST http://194.233.75.123:5200/apps/${{ gitea.repository_owner }}/${REPO#*/}/cancel-stale-runs \
-H "X-Internal-Token: ${{ secrets.INTERNAL_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"run_id\": ${{ gitea.run_id }}}" \
-o /dev/null || true
env:
REPO: ${{ gitea.repository }}
- name: Checkout
run: |
git clone "https://x-access-token:${{ gitea.token }}@git.clouderized.com/${{ gitea.repository }}.git" .
- name: Check app config
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
YAML="/home/cldrzd/customers/$CUSTOMER/$APP/clouderized.yaml"
if [ ! -f "$YAML" ]; then
echo "ERROR: clouderized.yaml not found — app not provisioned yet"
exit 1
fi
echo "App provisioned — proceeding with build"
- name: Build image
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
CFG="/home/cldrzd/customers/$CUSTOMER/$APP/clouderized.yaml"
DOCKERFILE=$(python3 -c "import yaml; c=yaml.safe_load(open('$CFG')); print(c.get('dockerfile','Dockerfile'))" 2>/dev/null || echo "Dockerfile")
[ -f "$DOCKERFILE" ] || { echo "ERROR: Backend Dockerfile not found at $DOCKERFILE"; exit 1; }
NO_CACHE_FLAG=""
if [ "${{ inputs.no_cache }}" = "true" ]; then
NO_CACHE_FLAG="--no-cache"
fi
BUILD_CTX=$(dirname "$DOCKERFILE")
podman build --memory=4g $NO_CACHE_FLAG \
-f "$DOCKERFILE" \
-t $CUSTOMER-$APP:latest \
-t $CUSTOMER-$APP:${{ gitea.sha }} \
"$BUILD_CTX"
- name: Log image pull
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
CFG="/home/cldrzd/customers/$CUSTOMER/$APP/clouderized.yaml"
DOCKERFILE=$(python3 -c "import yaml; c=yaml.safe_load(open('$CFG')); print(c.get('dockerfile','Dockerfile'))" 2>/dev/null || echo "Dockerfile")
[ -f "$DOCKERFILE" ] || { echo "ERROR: Backend Dockerfile not found at $DOCKERFILE"; exit 1; }
BASE_IMAGE=$(grep '^FROM ' "$DOCKERFILE" | tail -1 | awk '{print $2}')
IMAGE_ID=$(podman inspect --format='{{.Id}}' $CUSTOMER-$APP:latest)
curl -s -X POST http://194.233.75.123:5200/apps/$CUSTOMER/$APP/image-pull \
-H 'Content-Type: application/json' \
-H "X-Internal-Token: ${{ secrets.INTERNAL_TOKEN }}" \
-d "{\"base_image\":\"$BASE_IMAGE\",\"image_id\":\"$IMAGE_ID\",\"git_sha\":\"${{ gitea.sha }}\"}" \
|| true
- name: Generate compose
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
python3 /home/cldrzd/clouderized/scripts/generate_compose.py \
/home/cldrzd/customers/$CUSTOMER/$APP/clouderized.yaml \
--output /home/cldrzd/customers/$CUSTOMER/$APP/docker-compose.yml
- name: Write env file
env:
APP_ENV: ${{ secrets.APP_ENV }}
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
ENV_FILE="/home/cldrzd/customers/$CUSTOMER/$APP/app.env"
> "$ENV_FILE"
if [ -n "$APP_ENV" ]; then
printf '%s\n' "$APP_ENV" >> "$ENV_FILE"
fi
echo "Wrote env file: $(wc -l < "$ENV_FILE") var(s)"
- name: Build frontend
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
CFG="/home/cldrzd/customers/$CUSTOMER/$APP/clouderized.yaml"
ENABLED=$(python3 -c "import yaml; c=yaml.safe_load(open('$CFG')); print(str(c.get('frontend',{}).get('enabled',False)).lower())" 2>/dev/null || echo "false")
if [ "$ENABLED" = "true" ]; then
BUILD_DIR=$(python3 -c "import yaml; c=yaml.safe_load(open('$CFG')); print(c.get('frontend',{}).get('build_dir','frontend'))")
DIST_DIR=$(python3 -c "import yaml; c=yaml.safe_load(open('$CFG')); print(c.get('frontend',{}).get('dist_dir','dist'))")
ABS_BUILD="$(pwd)/$BUILD_DIR"
[ -d "$ABS_BUILD" ] || { echo "ERROR: Frontend directory '$BUILD_DIR' not found in repo. Check 'Frontend directory' in portal settings."; exit 1; }
DEST="/home/cldrzd/customers/$CUSTOMER/$APP/frontend"
mkdir -p "$DEST"
podman run --rm \
-v "$ABS_BUILD:/app:z" \
-w /app \
node:lts-alpine \
sh -c "if [ -f package-lock.json ]; then npm ci; else npm install; fi && npm run build"
rsync -a --delete "$ABS_BUILD/$DIST_DIR/" "$DEST/"
echo "Frontend built and synced to $DEST"
else
echo "Frontend companion not enabled — skipping"
fi
- name: Seed data
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
if [ -d "data" ]; then
DEST="/home/cldrzd/customers/$CUSTOMER/$APP/data/$APP/app-data"
mkdir -p "$DEST"
if [ -z "$(ls -A "$DEST" 2>/dev/null)" ]; then
cp -r data/. "$DEST/"
echo "Seeded /app/data: $(find data -type f | wc -l) file(s)"
else
echo "Skipped: /app/data already has data"
fi
fi
- name: Ensure internal network
run: |
CUSTOMER="${{ gitea.repository_owner }}"
podman network inspect $CUSTOMER-internal >/dev/null 2>&1 \
|| podman network create --driver bridge --internal \
$CUSTOMER-internal
- name: Deploy
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
cd /home/cldrzd/customers/$CUSTOMER/$APP
podman-compose up -d --force-recreate
podman images --format '{{.Repository}}:{{.Tag}}' \
| grep "^localhost/$CUSTOMER-$APP:" | grep -v ':latest' \
| xargs -r podman rmi --force 2>/dev/null || true
- name: Health check
run: |
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
python3 /home/cldrzd/clouderized/scripts/healthcheck.py \
--customer ${{ gitea.repository_owner }} \
--app $APP
- name: Ping orchestrator
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
curl -sf -X POST http://194.233.75.123:5200/apps/$CUSTOMER/$APP/ping \
-H "X-Internal-Token: ${{ secrets.INTERNAL_TOKEN }}" || true
- name: Release deploy lock
if: always()
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
curl -sf -X POST http://194.233.75.123:5200/apps/$CUSTOMER/$APP/redeploy/release \
-H "X-Internal-Token: ${{ secrets.INTERNAL_TOKEN }}" || true
- name: Report deploy failure
if: failure()
run: |
CUSTOMER="${{ gitea.repository_owner }}"
REPO="${{ gitea.repository }}"
APP="${REPO#*/}"
# Collect container logs
APP_DIR="/home/cldrzd/customers/$CUSTOMER/$APP"
if [ -d "$APP_DIR" ] && [ -f "$APP_DIR/docker-compose.yml" ]; then
LOGS=$(cd "$APP_DIR" && podman-compose logs --tail=50 2>&1 || true)
[ -z "$LOGS" ] && LOGS="No container logs — container may not have started."
else
LOGS="Container not started — build or compose step may have failed."
fi
printf '%s' "Deploy failed — $APP (run #${{ gitea.run_number }})" > /tmp/_ci_title
cat > /tmp/_ci_body <<ENDBODY
## Deploy failed
A deploy workflow failed for \`$APP\`.
**Run:** [#${{ gitea.run_number }}](https://git.clouderized.com/$CUSTOMER/$APP/actions/runs/${{ gitea.run_id }}) | **Commit:** \`${{ gitea.sha }}\`
### Container logs (last 50 lines)
\`\`\`
$LOGS
\`\`\`
### What to do next
- Review the failing step in the workflow run linked above.
- Fix the issue in your code and push again.
- If your Dockerfile needs fixing, see: https://docs.clouderized.com/sample-dockerfiles
ENDBODY
python3 - "${{ gitea.token }}" "$CUSTOMER/$APP" <<'PYEOF'
import sys, json, urllib.request
token, repo = sys.argv[1], sys.argv[2]
title = open('/tmp/_ci_title').read()
body = open('/tmp/_ci_body').read()
url = f"https://git.clouderized.com/api/v1/repos/{repo}/issues"
data = json.dumps({"title": title, "body": body}).encode()
req = urllib.request.Request(url, data=data, method="POST")
req.add_header("Authorization", f"token {token}")
req.add_header("Content-Type", "application/json")
try:
resp = urllib.request.urlopen(req)
print("Issue created:", json.loads(resp.read())["html_url"])
except Exception as e:
print(f"Warning: could not create issue: {e}")
PYEOF